mirror of
https://github.com/jwinterm/mwixnet.git
synced 2026-10-26 00:48:31 +01:00
milestone 2
This commit is contained in:
+121
-100
@@ -1,31 +1,85 @@
|
||||
use crate::config::ServerConfig;
|
||||
use crate::error::{ErrorKind, Result};
|
||||
use crate::secp::Commitment;
|
||||
|
||||
use grin_api::client;
|
||||
use grin_api::json_rpc::{build_request, Request, Response};
|
||||
use grin_api::{BlockPrintable, LocatedTxKernel, OutputPrintable, OutputType, Tip};
|
||||
use grin_api::{OutputPrintable, OutputType, Tip};
|
||||
use grin_core::consensus::COINBASE_MATURITY;
|
||||
use grin_core::core::{Input, OutputFeatures, Transaction};
|
||||
use grin_util::ToHex;
|
||||
|
||||
use serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
const ENDPOINT: &str = "/v2/foreign";
|
||||
pub trait GrinNode : Send + Sync {
|
||||
/// Retrieves the unspent output with a matching commitment
|
||||
fn get_utxo(&self, output_commit: &Commitment) -> Result<Option<OutputPrintable>>;
|
||||
|
||||
/// Gets the height of the chain tip
|
||||
fn get_chain_height(&self) -> Result<u64>;
|
||||
|
||||
/// Posts a transaction to the grin node
|
||||
fn post_tx(&self, tx: &Transaction) -> Result<()>;
|
||||
}
|
||||
|
||||
/// Checks whether a commitment is spendable at the block height provided
|
||||
pub fn is_spendable(node: &Arc<dyn GrinNode>, output_commit: &Commitment, next_block_height: u64) -> Result<bool> {
|
||||
let output = node.get_utxo(&output_commit)?;
|
||||
if let Some(out) = output {
|
||||
let is_coinbase = match out.output_type {
|
||||
OutputType::Coinbase => true,
|
||||
OutputType::Transaction => false,
|
||||
};
|
||||
|
||||
if is_coinbase {
|
||||
if let Some(block_height) = out.block_height {
|
||||
if block_height + COINBASE_MATURITY < next_block_height {
|
||||
return Ok(false);
|
||||
}
|
||||
} else {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Builds an input for an unspent output commitment
|
||||
pub fn build_input(node: &Arc<dyn GrinNode>, output_commit: &Commitment) -> Result<Option<Input>> {
|
||||
let output = node.get_utxo(&output_commit)?;
|
||||
|
||||
if let Some(out) = output {
|
||||
let features = match out.output_type {
|
||||
OutputType::Coinbase => OutputFeatures::Coinbase,
|
||||
OutputType::Transaction => OutputFeatures::Plain,
|
||||
};
|
||||
|
||||
let input = Input::new(features, out.commit);
|
||||
return Ok(Some(input));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// HTTP (JSONRPC) implementation of the 'GrinNode' trait
|
||||
#[derive(Clone)]
|
||||
pub struct HTTPNodeClient {
|
||||
pub struct HttpGrinNode {
|
||||
node_url: SocketAddr,
|
||||
node_api_secret: Option<String>,
|
||||
}
|
||||
|
||||
impl HTTPNodeClient {
|
||||
/// Create a new client that will communicate with the given grin node
|
||||
pub fn new(node_url: &SocketAddr, node_api_secret: Option<String>) -> HTTPNodeClient {
|
||||
HTTPNodeClient {
|
||||
const ENDPOINT: &str = "/v2/foreign";
|
||||
|
||||
impl HttpGrinNode {
|
||||
pub fn new(node_url: &SocketAddr, node_api_secret: &Option<String>) -> HttpGrinNode {
|
||||
HttpGrinNode {
|
||||
node_url: node_url.to_owned(),
|
||||
node_api_secret: node_api_secret,
|
||||
node_api_secret: node_api_secret.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,61 +97,8 @@ impl HTTPNodeClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GrinNode {
|
||||
client: HTTPNodeClient,
|
||||
}
|
||||
|
||||
impl GrinNode {
|
||||
pub fn new(server_config: &ServerConfig) -> GrinNode {
|
||||
GrinNode {
|
||||
client: HTTPNodeClient::new(&server_config.grin_node_url, None),
|
||||
}
|
||||
}
|
||||
|
||||
// Checks whether a commitment is spendable at the block height provided
|
||||
pub fn is_spendable(&self, output_commit: &Commitment, next_block_height: u64) -> Result<bool> {
|
||||
let output = self.get_output(&output_commit)?;
|
||||
if let Some(out) = output {
|
||||
let is_coinbase = match out.output_type {
|
||||
OutputType::Coinbase => true,
|
||||
OutputType::Transaction => false,
|
||||
};
|
||||
|
||||
if is_coinbase {
|
||||
if let Some(block_height) = out.block_height {
|
||||
if block_height + COINBASE_MATURITY < next_block_height {
|
||||
return Ok(false);
|
||||
}
|
||||
} else {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Builds an input for an unspent output commitment
|
||||
pub fn build_input(&self, output_commit: &Commitment) -> Result<Option<Input>> {
|
||||
let output = self.get_output(&output_commit)?;
|
||||
|
||||
if let Some(out) = output {
|
||||
let features = match out.output_type {
|
||||
OutputType::Coinbase => OutputFeatures::Coinbase,
|
||||
OutputType::Transaction => OutputFeatures::Plain,
|
||||
};
|
||||
|
||||
let input = Input::new(features, out.commit);
|
||||
return Ok(Some(input));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_output(&self, output_commit: &Commitment) -> Result<Option<OutputPrintable>> {
|
||||
impl GrinNode for HttpGrinNode {
|
||||
fn get_utxo(&self, output_commit: &Commitment) -> Result<Option<OutputPrintable>> {
|
||||
let commits : Vec<String> = vec![output_commit.to_hex()];
|
||||
let start_height : Option<u64> = None;
|
||||
let end_height : Option<u64> = None;
|
||||
@@ -105,7 +106,7 @@ impl GrinNode {
|
||||
let include_merkle_proof : Option<bool> = Some(false);
|
||||
|
||||
let params = json!([Some(commits), start_height, end_height, include_proof, include_merkle_proof]);
|
||||
let outputs = self.client.send_json_request::<Vec<OutputPrintable>>("get_outputs", ¶ms)?;
|
||||
let outputs = self.send_json_request::<Vec<OutputPrintable>>("get_outputs", ¶ms)?;
|
||||
if outputs.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -113,10 +114,9 @@ impl GrinNode {
|
||||
Ok(Some(outputs[0].clone()))
|
||||
}
|
||||
|
||||
/// Gets the height of the chain tip
|
||||
pub fn get_chain_height(&self) -> Result<u64> {
|
||||
fn get_chain_height(&self) -> Result<u64> {
|
||||
let params = json!([]);
|
||||
let tip_json = self.client.send_json_request::<serde_json::Value>("get_tip", ¶ms)?;
|
||||
let tip_json = self.send_json_request::<serde_json::Value>("get_tip", ¶ms)?;
|
||||
|
||||
let tip: Result<Tip> = serde_json::from_value(tip_json["Ok"].clone())
|
||||
.map_err(|_| ErrorKind::SerdeJsonError.into());
|
||||
@@ -124,46 +124,67 @@ impl GrinNode {
|
||||
Ok(tip?.height)
|
||||
}
|
||||
|
||||
/// Posts a transaction to the grin node
|
||||
pub fn post_tx(&self, tx: &Transaction) -> Result<()> {
|
||||
fn post_tx(&self, tx: &Transaction) -> Result<()> {
|
||||
let params = json!([tx, true]);
|
||||
self.client.send_json_request::<serde_json::Value>("push_transaction", ¶ms)?;
|
||||
self.send_json_request::<serde_json::Value>("push_transaction", ¶ms)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// milestone 3: needed to handle chain reorgs
|
||||
pub fn chain_has_kernel(
|
||||
&self,
|
||||
kernel_excess: &Commitment,
|
||||
start_height: Option<u64>,
|
||||
end_height: Option<u64>,
|
||||
) -> Result<bool> {
|
||||
let params = json!([kernel_excess, start_height, end_height]);
|
||||
let located_kernel_json = self.client.send_json_request::<serde_json::Value>("get_kernel", ¶ms)?;
|
||||
pub struct MockGrinNode {
|
||||
utxos: HashMap<Commitment, OutputPrintable>,
|
||||
txns_posted: RwLock<Vec<Transaction>>,
|
||||
}
|
||||
|
||||
let located_kernel : Result<LocatedTxKernel> = serde_json::from_value(located_kernel_json["Ok"].clone())
|
||||
.map_err(|_| ErrorKind::SerdeJsonError.into());
|
||||
|
||||
Ok(located_kernel.is_ok())
|
||||
impl MockGrinNode {
|
||||
pub fn new() -> MockGrinNode {
|
||||
MockGrinNode {
|
||||
utxos: HashMap::new(),
|
||||
txns_posted: RwLock::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
// milestone 3: needed to handle chain reorgs
|
||||
pub fn block_has_kernel(&self, block_hash: &String, kernel_excess: &Commitment) -> Result<bool> {
|
||||
let block = self.get_block(None, Some(block_hash.clone()), None)?;
|
||||
|
||||
let found = block.kernels.into_iter()
|
||||
.any(|kern| kern.excess == kernel_excess.to_hex());
|
||||
Ok(found)
|
||||
pub fn add_utxo(&mut self, output_commit: &Commitment, utxo: &OutputPrintable) {
|
||||
self.utxos.insert(output_commit.clone(), utxo.clone());
|
||||
}
|
||||
|
||||
// milestone 3: needed to handle chain reorgs
|
||||
fn get_block(&self,
|
||||
height: Option<u64>,
|
||||
hash: Option<String>,
|
||||
commit: Option<String>,
|
||||
) -> Result<BlockPrintable> {
|
||||
let params = json!([height, hash, commit]);
|
||||
let block_printable = self.client.send_json_request::<BlockPrintable>("get_block", ¶ms)?;
|
||||
Ok(block_printable)
|
||||
pub fn add_default_utxo(&mut self, output_commit: &Commitment) {
|
||||
let utxo = OutputPrintable{
|
||||
output_type: OutputType::Transaction,
|
||||
commit: output_commit.to_owned(),
|
||||
spent: false,
|
||||
proof: None,
|
||||
proof_hash: String::from(""),
|
||||
block_height: None,
|
||||
merkle_proof: None,
|
||||
mmr_index: 0
|
||||
};
|
||||
|
||||
self.add_utxo(&output_commit, &utxo);
|
||||
}
|
||||
|
||||
pub fn get_posted_txns(&self) -> Vec<Transaction> {
|
||||
let read = self.txns_posted.read().unwrap();
|
||||
read.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl GrinNode for MockGrinNode {
|
||||
fn get_utxo(&self, output_commit: &Commitment) -> Result<Option<OutputPrintable>> {
|
||||
if let Some(utxo) = self.utxos.get(&output_commit) {
|
||||
return Ok(Some(utxo.clone()));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_chain_height(&self) -> Result<u64> {
|
||||
Ok(100)
|
||||
}
|
||||
|
||||
fn post_tx(&self, tx: &Transaction) -> Result<()> {
|
||||
let mut write = self.txns_posted.write().unwrap();
|
||||
write.push(tx.clone());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user