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_core::consensus::COINBASE_MATURITY; use grin_core::core::{Input, OutputFeatures, Transaction}; use grin_util::ToHex; use serde_json::json; use std::net::SocketAddr; const ENDPOINT: &str = "/v2/foreign"; #[derive(Clone)] pub struct HTTPNodeClient { node_url: SocketAddr, node_api_secret: Option, } impl HTTPNodeClient { /// Create a new client that will communicate with the given grin node pub fn new(node_url: &SocketAddr, node_api_secret: Option) -> HTTPNodeClient { HTTPNodeClient { node_url: node_url.to_owned(), node_api_secret: node_api_secret, } } fn send_json_request( &self, method: &str, params: &serde_json::Value, ) -> Result { let url = format!("http://{}{}", self.node_url, ENDPOINT); let req = build_request(method, params); let res = client::post::(url.as_str(), self.node_api_secret.clone(), &req)?; let parsed = res.clone().into_result()?; Ok(parsed) } } #[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 { 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> { 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> { let commits : Vec = vec![output_commit.to_hex()]; let start_height : Option = None; let end_height : Option = None; let include_proof : Option = Some(false); let include_merkle_proof : Option = Some(false); let params = json!([Some(commits), start_height, end_height, include_proof, include_merkle_proof]); let outputs = self.client.send_json_request::>("get_outputs", ¶ms)?; if outputs.is_empty() { return Ok(None); } Ok(Some(outputs[0].clone())) } /// Gets the height of the chain tip pub fn get_chain_height(&self) -> Result { let params = json!([]); let tip_json = self.client.send_json_request::("get_tip", ¶ms)?; let tip: Result = serde_json::from_value(tip_json["Ok"].clone()) .map_err(|_| ErrorKind::SerdeJsonError.into()); Ok(tip?.height) } /// Posts a transaction to the grin node pub fn post_tx(&self, tx: &Transaction) -> Result<()> { let params = json!([tx, true]); self.client.send_json_request::("push_transaction", ¶ms)?; Ok(()) } // milestone 3: needed to handle chain reorgs pub fn chain_has_kernel( &self, kernel_excess: &Commitment, start_height: Option, end_height: Option, ) -> Result { let params = json!([kernel_excess, start_height, end_height]); let located_kernel_json = self.client.send_json_request::("get_kernel", ¶ms)?; let located_kernel : Result = serde_json::from_value(located_kernel_json["Ok"].clone()) .map_err(|_| ErrorKind::SerdeJsonError.into()); Ok(located_kernel.is_ok()) } // milestone 3: needed to handle chain reorgs pub fn block_has_kernel(&self, block_hash: &String, kernel_excess: &Commitment) -> Result { 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) } // milestone 3: needed to handle chain reorgs fn get_block(&self, height: Option, hash: Option, commit: Option, ) -> Result { let params = json!([height, hash, commit]); let block_printable = self.client.send_json_request::("get_block", ¶ms)?; Ok(block_printable) } }