2024-04-16 18:16:53 +03:00
use std ::net ::SocketAddr ;
2024-07-17 14:37:34 +03:00
use std ::net ::ToSocketAddrs ;
2024-04-16 18:16:53 +03:00
use std ::sync ::Arc ;
2022-02-11 04:33:35 +03:00
2024-04-16 18:16:53 +03:00
use async_trait ::async_trait ;
use grin_api ::LocatedTxKernel ;
2022-04-27 06:13:44 +03:00
use grin_api ::{ OutputPrintable , OutputType , Tip } ;
2022-02-11 04:33:35 +03:00
use grin_core ::consensus ::COINBASE_MATURITY ;
2024-04-16 18:16:53 +03:00
use grin_core ::core ::hash ::Hash ;
2024-04-03 00:27:08 +03:00
use grin_core ::core ::{ Committed , Input , OutputFeatures , Transaction } ;
2022-02-11 04:33:35 +03:00
use grin_util ::ToHex ;
use serde_json ::json ;
2022-08-22 20:03:10 +03:00
use thiserror ::Error ;
2022-02-11 04:33:35 +03:00
2024-04-16 18:16:53 +03:00
use grin_onion ::crypto ::secp ::Commitment ;
use crate ::http ;
2023-12-19 03:12:05 +03:00
#[ async_trait ]
2022-07-01 12:33:34 +03:00
pub trait GrinNode : Send + Sync {
/// Retrieves the unspent output with a matching commitment
2023-12-19 03:12:05 +03:00
async fn async_get_utxo (
& self ,
output_commit : & Commitment ,
) -> Result < Option < OutputPrintable > , NodeError > ;
2022-04-27 06:13:44 +03:00
2024-04-16 18:16:53 +03:00
/// Gets the height and hash of the chain tip
async fn async_get_chain_tip ( & self ) -> Result < ( u64 , Hash ) , NodeError > ;
2022-04-27 06:13:44 +03:00
2022-07-01 12:33:34 +03:00
/// Posts a transaction to the grin node
2023-12-19 03:12:05 +03:00
async fn async_post_tx ( & self , tx : & Transaction ) -> Result < ( ) , NodeError > ;
/// Returns a LocatedTxKernel based on the kernel excess.
/// The min_height and max_height parameters are both optional.
/// If not supplied, min_height will be set to 0 and max_height will be set to the head of the chain.
/// The method will start at the block height max_height and traverse the kernel MMR backwards, until either the kernel is found or min_height is reached.
async fn async_get_kernel (
& self ,
excess : & Commitment ,
min_height : Option < u64 > ,
max_height : Option < u64 > ,
) -> Result < Option < LocatedTxKernel > , NodeError > ;
2022-08-22 20:03:10 +03:00
}
/// Error types for interacting with nodes
#[ derive(Error, Debug) ]
pub enum NodeError {
#[ error( " Error decoding JSON response: {0:?} " ) ]
DecodeResponseError ( serde_json ::Error ) ,
#[ error( " JSON-RPC API communication error: {0:?} " ) ]
ApiCommError ( grin_api ::Error ) ,
2024-04-16 18:16:53 +03:00
#[ error( " Client error: {0:?} " ) ]
NodeCommError ( http ::HttpError ) ,
2022-08-22 20:03:10 +03:00
#[ error( " Error decoding JSON-RPC response: {0:?} " ) ]
ResponseParseError ( grin_api ::json_rpc ::Error ) ,
2022-04-27 06:13:44 +03:00
}
2022-04-27 23:30:15 +03:00
/// Checks if a commitment is in the UTXO set
2023-12-19 03:12:05 +03:00
pub async fn async_is_unspent (
node : & Arc < dyn GrinNode > ,
commit : & Commitment ,
) -> Result < bool , NodeError > {
let utxo = node . async_get_utxo ( & commit ) . await ? ;
2022-07-01 12:33:34 +03:00
Ok ( utxo . is_some ( ) )
2022-04-27 23:30:15 +03:00
}
2022-04-27 06:13:44 +03:00
/// Checks whether a commitment is spendable at the block height provided
2023-12-19 03:12:05 +03:00
pub async fn async_is_spendable (
2022-07-01 12:33:34 +03:00
node : & Arc < dyn GrinNode > ,
2023-01-08 00:09:04 +03:00
commit : & Commitment ,
2022-07-01 12:33:34 +03:00
next_block_height : u64 ,
2022-08-22 20:03:10 +03:00
) -> Result < bool , NodeError > {
2023-12-19 03:12:05 +03:00
let output = node . async_get_utxo ( & commit ) . await ? ;
2022-07-01 12:33:34 +03:00
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 )
2022-04-27 06:13:44 +03:00
}
/// Builds an input for an unspent output commitment
2023-12-19 03:12:05 +03:00
pub async fn async_build_input (
2022-08-22 20:03:10 +03:00
node : & Arc < dyn GrinNode > ,
output_commit : & Commitment ,
) -> Result < Option < Input > , NodeError > {
2023-12-19 03:12:05 +03:00
let output = node . async_get_utxo ( & output_commit ) . await ? ;
2022-02-11 04:33:35 +03:00
2022-07-01 12:33:34 +03:00
if let Some ( out ) = output {
let features = match out . output_type {
OutputType ::Coinbase = > OutputFeatures ::Coinbase ,
OutputType ::Transaction = > OutputFeatures ::Plain ,
} ;
2022-04-27 06:13:44 +03:00
2022-07-01 12:33:34 +03:00
let input = Input ::new ( features , out . commit ) ;
return Ok ( Some ( input ) ) ;
}
2022-04-27 06:13:44 +03:00
2022-07-01 12:33:34 +03:00
Ok ( None )
2022-04-27 06:13:44 +03:00
}
2024-04-16 18:16:53 +03:00
pub async fn async_is_tx_valid (
node : & Arc < dyn GrinNode > ,
tx : & Transaction ,
) -> Result < bool , NodeError > {
let next_block_height = node . async_get_chain_tip ( ) . await ? . 0 + 1 ;
for input_commit in & tx . inputs_committed ( ) {
if ! async_is_spendable ( & node , & input_commit , next_block_height ) . await ? {
return Ok ( false ) ;
}
}
for output_commit in & tx . outputs_committed ( ) {
if async_is_unspent ( & node , & output_commit ) . await ? {
return Ok ( false ) ;
}
}
Ok ( true )
2024-04-03 00:27:08 +03:00
}
2022-07-01 12:33:34 +03:00
/// HTTP (JSON-RPC) implementation of the 'GrinNode' trait
2022-02-11 04:33:35 +03:00
#[ derive(Clone) ]
2022-04-27 06:13:44 +03:00
pub struct HttpGrinNode {
2022-07-01 12:33:34 +03:00
node_url : SocketAddr ,
node_api_secret : Option < String > ,
2022-02-11 04:33:35 +03:00
}
2022-04-27 06:13:44 +03:00
const ENDPOINT : & str = " /v2/foreign " ;
impl HttpGrinNode {
2024-07-17 14:37:34 +03:00
pub fn new ( node_url : & str , node_api_secret : & Option < String > ) -> HttpGrinNode {
let mut addrs_iter = node_url . to_socket_addrs ( ) . unwrap ( ) ;
let node_url = addrs_iter . next ( ) . unwrap ( ) ;
2022-07-01 12:33:34 +03:00
HttpGrinNode {
2024-07-17 14:37:34 +03:00
node_url ,
2022-07-01 12:33:34 +03:00
node_api_secret : node_api_secret . to_owned ( ) ,
}
}
2023-12-19 03:12:05 +03:00
async fn async_send_request < D : serde ::de ::DeserializeOwned > (
2022-07-01 12:33:34 +03:00
& self ,
method : & str ,
params : & serde_json ::Value ,
2022-08-22 20:03:10 +03:00
) -> Result < D , NodeError > {
2022-07-01 12:33:34 +03:00
let url = format! ( " http:// {} {} " , self . node_url , ENDPOINT ) ;
2024-04-16 18:16:53 +03:00
let parsed = http ::async_send_json_request ( & url , & self . node_api_secret , & method , & params )
. await
. map_err ( NodeError ::NodeCommError ) ? ;
2022-07-01 12:33:34 +03:00
Ok ( parsed )
}
2022-02-11 04:33:35 +03:00
}
2023-12-19 03:12:05 +03:00
#[ async_trait ]
2022-04-27 06:13:44 +03:00
impl GrinNode for HttpGrinNode {
2023-12-19 03:12:05 +03:00
async fn async_get_utxo (
& self ,
output_commit : & Commitment ,
) -> Result < Option < OutputPrintable > , NodeError > {
2022-07-01 12:33:34 +03:00
let commits : Vec < String > = vec! [ output_commit . to_hex ( ) ] ;
let start_height : Option < u64 > = None ;
let end_height : Option < u64 > = None ;
let include_proof : Option < bool > = Some ( false ) ;
let include_merkle_proof : Option < bool > = Some ( false ) ;
let params = json! ( [
Some ( commits ) ,
start_height ,
end_height ,
include_proof ,
include_merkle_proof
] ) ;
2023-12-19 03:12:05 +03:00
let outputs = self
. async_send_request ::< Vec < OutputPrintable > > ( " get_outputs " , & params )
. await ? ;
2022-07-01 12:33:34 +03:00
if outputs . is_empty ( ) {
return Ok ( None ) ;
}
Ok ( Some ( outputs [ 0 ] . clone ( ) ) )
}
2024-04-16 18:16:53 +03:00
async fn async_get_chain_tip ( & self ) -> Result < ( u64 , Hash ) , NodeError > {
2022-07-01 12:33:34 +03:00
let params = json! ( [ ] ) ;
2023-12-19 03:12:05 +03:00
let tip_json = self
. async_send_request ::< serde_json ::Value > ( " get_tip " , & params )
. await ? ;
2022-09-14 23:14:31 +03:00
let tip =
serde_json ::from_value ::< Tip > ( tip_json ) . map_err ( NodeError ::DecodeResponseError ) ? ;
2022-07-01 12:33:34 +03:00
2024-04-16 18:16:53 +03:00
Ok ( (
tip . height ,
Hash ::from_hex ( tip . last_block_pushed . as_str ( ) ) . unwrap ( ) ,
) )
2022-07-01 12:33:34 +03:00
}
2023-12-19 03:12:05 +03:00
async fn async_post_tx ( & self , tx : & Transaction ) -> Result < ( ) , NodeError > {
2022-07-01 12:33:34 +03:00
let params = json! ( [ tx , true ] ) ;
2023-12-19 03:12:05 +03:00
self . async_send_request ::< serde_json ::Value > ( " push_transaction " , & params )
. await ? ;
2022-07-01 12:33:34 +03:00
Ok ( ( ) )
}
2023-12-19 03:12:05 +03:00
async fn async_get_kernel (
& self ,
excess : & Commitment ,
min_height : Option < u64 > ,
max_height : Option < u64 > ,
) -> Result < Option < LocatedTxKernel > , NodeError > {
let params = json! ( [ excess . 0. as_ref ( ) . to_hex ( ) , min_height , max_height ] ) ;
let value = self
. async_send_request ::< serde_json ::Value > ( " get_kernel " , & params )
. await ? ;
let contents = format! ( " {:?} " , value ) ;
if contents . contains ( " NotFound " ) {
return Ok ( None ) ;
}
let located_kernel = serde_json ::from_value ::< LocatedTxKernel > ( value )
. map_err ( NodeError ::DecodeResponseError ) ? ;
Ok ( Some ( located_kernel ) )
}
2022-04-27 06:13:44 +03:00
}
2022-02-11 04:33:35 +03:00
2022-08-01 06:17:08 +03:00
#[ cfg(test) ]
pub mod mock {
2024-04-16 18:16:53 +03:00
use std ::collections ::HashMap ;
use std ::sync ::RwLock ;
2022-08-01 06:17:08 +03:00
2023-12-19 03:12:05 +03:00
use async_trait ::async_trait ;
use grin_api ::{ LocatedTxKernel , OutputPrintable , OutputType } ;
2024-04-16 18:16:53 +03:00
use grin_core ::core ::hash ::Hash ;
2022-08-01 06:17:08 +03:00
use grin_core ::core ::Transaction ;
2024-04-16 18:16:53 +03:00
2023-12-19 03:12:05 +03:00
use grin_onion ::crypto ::secp ::Commitment ;
2024-04-16 18:16:53 +03:00
use super ::{ GrinNode , NodeError } ;
2022-08-01 06:17:08 +03:00
/// Implementation of 'GrinNode' trait that mocks a grin node instance.
/// Use only for testing purposes.
pub struct MockGrinNode {
utxos : HashMap < Commitment , OutputPrintable > ,
txns_posted : RwLock < Vec < Transaction > > ,
2023-12-19 03:12:05 +03:00
kernels : HashMap < Commitment , LocatedTxKernel > ,
2022-08-01 06:17:08 +03:00
}
2022-04-27 06:13:44 +03:00
2022-08-01 06:17:08 +03:00
impl MockGrinNode {
2023-01-08 00:09:04 +03:00
pub fn new ( ) -> Self {
2022-08-01 06:17:08 +03:00
MockGrinNode {
utxos : HashMap ::new ( ) ,
txns_posted : RwLock ::new ( Vec ::new ( ) ) ,
2023-12-19 03:12:05 +03:00
kernels : HashMap ::new ( ) ,
2022-08-01 06:17:08 +03:00
}
2022-07-01 12:33:34 +03:00
}
2023-01-08 00:09:04 +03:00
pub fn new_with_utxos ( utxos : & Vec < & Commitment > ) -> Self {
let mut node = MockGrinNode {
utxos : HashMap ::new ( ) ,
txns_posted : RwLock ::new ( Vec ::new ( ) ) ,
2023-12-19 03:12:05 +03:00
kernels : HashMap ::new ( ) ,
2023-01-08 00:09:04 +03:00
} ;
for utxo in utxos {
node . add_default_utxo ( utxo ) ;
}
node
}
2022-08-01 06:17:08 +03:00
pub fn add_utxo ( & mut self , output_commit : & Commitment , utxo : & OutputPrintable ) {
self . utxos . insert ( output_commit . clone ( ) , utxo . clone ( ) ) ;
}
2022-07-01 12:33:34 +03:00
2022-08-01 06:17:08 +03:00
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 ) ;
}
2022-07-01 12:33:34 +03:00
2022-08-01 06:17:08 +03:00
pub fn get_posted_txns ( & self ) -> Vec < Transaction > {
let read = self . txns_posted . read ( ) . unwrap ( ) ;
read . clone ( )
}
2023-12-19 03:12:05 +03:00
pub fn add_kernel ( & mut self , kernel : & LocatedTxKernel ) {
self . kernels
. insert ( kernel . tx_kernel . excess . clone ( ) , kernel . clone ( ) ) ;
}
2022-07-01 12:33:34 +03:00
}
2023-12-19 03:12:05 +03:00
#[ async_trait ]
2022-08-01 06:17:08 +03:00
impl GrinNode for MockGrinNode {
2023-12-19 03:12:05 +03:00
async fn async_get_utxo (
2022-08-22 20:03:10 +03:00
& self ,
output_commit : & Commitment ,
) -> Result < Option < OutputPrintable > , NodeError > {
2022-08-01 06:17:08 +03:00
if let Some ( utxo ) = self . utxos . get ( & output_commit ) {
return Ok ( Some ( utxo . clone ( ) ) ) ;
}
2022-02-11 04:33:35 +03:00
2022-08-01 06:17:08 +03:00
Ok ( None )
2022-07-01 12:33:34 +03:00
}
2024-04-16 18:16:53 +03:00
async fn async_get_chain_tip ( & self ) -> Result < ( u64 , Hash ) , NodeError > {
Ok ( ( 100 , Hash ::default ( ) ) )
2022-08-01 06:17:08 +03:00
}
2022-07-01 12:33:34 +03:00
2023-12-19 03:12:05 +03:00
async fn async_post_tx ( & self , tx : & Transaction ) -> Result < ( ) , NodeError > {
2022-08-01 06:17:08 +03:00
let mut write = self . txns_posted . write ( ) . unwrap ( ) ;
write . push ( tx . clone ( ) ) ;
Ok ( ( ) )
}
2023-12-19 03:12:05 +03:00
async fn async_get_kernel (
& self ,
excess : & Commitment ,
_min_height : Option < u64 > ,
_max_height : Option < u64 > ,
) -> Result < Option < LocatedTxKernel > , NodeError > {
if let Some ( kernel ) = self . kernels . get ( & excess ) {
return Ok ( Some ( kernel . clone ( ) ) ) ;
}
Ok ( None )
}
2022-07-01 12:33:34 +03:00
}
}