diff --git a/client/src/client_sync/v17/blockchain.rs b/client/src/client_sync/v17/blockchain.rs index dade229..90fbe60 100644 --- a/client/src/client_sync/v17/blockchain.rs +++ b/client/src/client_sync/v17/blockchain.rs @@ -254,7 +254,7 @@ macro_rules! impl_client_v17__gettxout { macro_rules! impl_client_v17__gettxoutproof { () => { impl Client { - pub fn get_tx_out_proof(&self, txids: Vec) -> Result { + pub fn get_tx_out_proof(&self, txids: &[Txid]) -> Result { self.call("gettxoutproof", &[into_json(txids)?]) } } @@ -266,7 +266,7 @@ macro_rules! impl_client_v17__gettxoutproof { macro_rules! impl_client_v17__gettxoutsetinfo { () => { impl Client { - pub fn get_tx_out_set_info(&self) -> Result { + pub fn get_tx_out_set_info(&self) -> Result { self.call("gettxoutsetinfo", &[]) } } @@ -279,7 +279,7 @@ macro_rules! impl_client_v17__verifytxoutproof { () => { impl Client { // `proof` is the hex-encoded proof generated by `gettxoutproof`. - pub fn verify_tx_out_proof(&self, proof: &str) -> Result { + pub fn verify_tx_out_proof(&self, proof: &str) -> Result { self.call("verifytxoutproof", &[into_json(proof)?]) } } diff --git a/client/src/client_sync/v18.rs b/client/src/client_sync/v18.rs index ffeea10..6ff168d 100644 --- a/client/src/client_sync/v18.rs +++ b/client/src/client_sync/v18.rs @@ -34,6 +34,10 @@ crate::impl_client_v17__getmempooldescendants!(); crate::impl_client_v17__getmempoolentry!(); crate::impl_client_v17__getmempoolinfo!(); crate::impl_client_v17__getrawmempool!(); +crate::impl_client_v17__gettxout!(); +crate::impl_client_v17__gettxoutproof!(); +crate::impl_client_v17__gettxoutsetinfo!(); +crate::impl_client_v17__verifytxoutproof!(); // == Control == crate::impl_client_v17__getmemoryinfo!(); diff --git a/integration_test/src/lib.rs b/integration_test/src/lib.rs index bfca4b3..66735ea 100644 --- a/integration_test/src/lib.rs +++ b/integration_test/src/lib.rs @@ -48,12 +48,19 @@ pub trait NodeExt { /// Generates [`NBLOCKS`] to an address controlled by the loaded wallet. fn fund_wallet(&self); - /// Mine a block. + /// Mines a block. /// /// Should send mining reward to a new address for the loaded wallet. fn mine_a_block(&self); - /// Create a transaction and mine it. + /// Creates a transaction in the mempool. + /// + /// # Returns + /// + /// The receive address and the transaction. + fn create_mempool_transaction(&self) -> (bitcoin::Address, bitcoin::Txid); + + /// Creates a transaction and mines a block that includes it in the chain. /// /// # Returns /// @@ -77,12 +84,22 @@ impl NodeExt for Node { self.client.generate_to_address(NBLOCKS, &address).expect("failed to generate to address"); } - fn create_mined_transaction(&self) -> (bitcoin::Address, bitcoin::Transaction) { + fn mine_a_block(&self) { + let address = self.client.new_address().expect("failed to get new address"); + self.client.generate_to_address(1, &address).expect("failed to generate to address"); + } + + fn create_mempool_transaction(&self) -> (bitcoin::Address, bitcoin::Txid) { const MILLION_SATS: bitcoin::Amount = bitcoin::Amount::from_sat(1000000); let address = self.client.new_address().expect("failed to get new address"); - let _ = self.client.send_to_address(&address, MILLION_SATS); + let txid = self.client.send_to_address(&address, MILLION_SATS).expect("failed to send to address").txid().expect("failed to convert hex to txid"); + (address, txid) + } + + fn create_mined_transaction(&self) -> (bitcoin::Address, bitcoin::Transaction) { + let (address, _) = self.create_mempool_transaction(); self.mine_a_block(); let best_block_hash = self.client.best_block_hash().expect("best_block_hash"); @@ -91,12 +108,6 @@ impl NodeExt for Node { (address, tx) } - - fn mine_a_block(&self) { - // TODO: Consider returning the error. - let address = self.client.new_address().expect("failed to get new address"); - self.client.generate_to_address(1, &address).expect("failed to generate to address"); - } } /// Return a temporary file path. diff --git a/integration_test/tests/blockchain.rs b/integration_test/tests/blockchain.rs index fc10fc8..7bf1921 100644 --- a/integration_test/tests/blockchain.rs +++ b/integration_test/tests/blockchain.rs @@ -134,37 +134,103 @@ fn get_difficulty() { #[test] #[cfg(feature = "TODO")] -fn get_mempool_ancestors() { todo!() } +fn get_mempool_ancestors() { + // We can probably get away with not testing this because it returns the + // same type as `getmempoolentry` which is tested below. +} #[test] #[cfg(feature = "TODO")] -fn get_mempool_descendants() { todo!() } +fn get_mempool_descendants() { + // We can probably get away with not testing this because it returns the + // same type as `getmempoolentry` which is tested below. +} #[test] -#[cfg(feature = "TODO")] -fn get_mempool_entry() { todo!() } +fn get_mempool_entry() { + let node = Node::new_with_default_wallet(); + node.fund_wallet(); + let (_address, txid) = node.create_mempool_transaction(); + + let json = node.client.get_mempool_entry(txid).expect("getmempoolentry"); + assert!(json.into_model().is_ok()); +} #[test] -#[cfg(feature = "TODO")] -fn get_mempool_info() { todo!() } +fn get_mempool_info() { + let node = Node::new_with_default_wallet(); + node.fund_wallet(); + let (_address, _txid) = node.create_mempool_transaction(); + + // Test the type and into model conversion code. + let json = node.client.get_mempool_info().expect("getmempoolinfo"); + let info = json.into_model().expect("into_model"); + // Sanity check. + assert_eq!(info.size, 1); +} #[test] -#[cfg(feature = "TODO")] -fn get_raw_mempool() { todo!() } +fn get_raw_mempool() { + let node = Node::new_with_default_wallet(); + node.fund_wallet(); + let (_address, _txid) = node.create_mempool_transaction(); + + // Test the type and into model conversion code. + let json = node.client.get_raw_mempool().expect("getrawmempool"); + let mempool = json.into_model().expect("into_model"); + // Sanity check. + assert_eq!(mempool.0.len(), 1); +} #[test] +// FIXME: Fails with getrawmempool verbose: JsonRpc(Json(Error("invalid type: map, expected a sequence", line: 1, column: 0))) #[cfg(feature = "TODO")] -fn get_tx_out() { todo!() } +fn get_raw_mempool_verbose() { + let node = Node::new_with_default_wallet(); + node.fund_wallet(); + let (_address, _txid) = node.create_mempool_transaction(); + + // Test the type and into model conversion code. + let json = node.client.get_raw_mempool_verbose().expect("getrawmempool verbose"); + let mempool = json.into_model().expect("into_model"); + // Sanity check. + assert_eq!(mempool.0.len(), 1); +} #[test] -#[cfg(feature = "TODO")] -fn get_tx_out_proof() { todo!() } +fn get_tx_out() { + let node = Node::new_with_default_wallet(); + node.fund_wallet(); + let (_address, tx) = node.create_mined_transaction(); + let txid = tx.compute_txid(); + + // Test the type and into model conversion code. + let json = node.client.get_tx_out(txid, 1).expect("gettxout"); + let _ = json.into_model().expect("into_model"); +} #[test] -#[cfg(feature = "TODO")] -fn get_tx_out_set_info() { todo!() } +fn get_tx_out_set_info() { + let node = Node::new_with_default_wallet(); + node.fund_wallet(); + let (_address, _tx) = node.create_mined_transaction(); + // Test the type and into model conversion code. + let json = node.client.get_tx_out_set_info().expect("gettxoutsetinfo"); + let _ = json.into_model().expect("into_model"); + +} + +// Implicitly tests the omitted method `gettxoutproof` as well. #[test] -#[cfg(feature = "TODO")] -fn verify_tx_out_proof() { todo!() } +fn verify_tx_out_proof() { + let node = Node::new_with_default_wallet(); + node.fund_wallet(); + let (_address, tx) = node.create_mined_transaction(); + let txid = tx.compute_txid(); + + let proof = node.client.get_tx_out_proof(&[txid]).expect("gettxoutproof"); + let txids = node.client.verify_tx_out_proof(&proof).expect("verifytxoutproof"); + assert_eq!(txids.0.len(), 1); +} diff --git a/justfile b/justfile index f5fd526..607aa5f 100644 --- a/justfile +++ b/justfile @@ -1,3 +1,7 @@ +set export + +REPO_DIR := `git rev-parse --show-toplevel` + default: @just --list @@ -12,10 +16,12 @@ check: # Lint everything. lint: cargo +$(cat ./nightly-version) clippy --workspace --all-targets --all-features -- --deny warnings + cd $REPO_DIR/node > /dev/null; cargo +$(cat ../nightly-version) clippy --all-targets --all-features -- --deny warnings # Run cargo fmt fmt: cargo +$(cat ./nightly-version) fmt --all + cd $REPO_DIR/node > /dev/null; cargo +$(cat ../nightly-version) fmt # Check the formatting format: diff --git a/node/src/lib.rs b/node/src/lib.rs index 8197bc3..b09155e 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -401,9 +401,9 @@ impl Node { match client_base.create_wallet(wallet) { Ok(json) => { debug!("created wallet: {}", json.name()); - }, + } Err(e) => { - debug!("initial create_wallet unsuccessful, try loading instead: {:?}", e); + debug!("initial create_wallet failed, try load instead: {:?}", e); let wallet = client_base.load_wallet(wallet)?.name(); debug!("loaded wallet: {}", wallet); } @@ -577,9 +577,9 @@ mod test { use tempfile::TempDir; use super::*; - use crate::{exe_path, get_available_port, Node, Conf, LOCAL_IP}; #[cfg(not(feature = "28_0"))] use crate::P2P; + use crate::{exe_path, get_available_port, Conf, Node, LOCAL_IP}; #[test] fn test_local_ip() { @@ -706,7 +706,8 @@ mod test { let node2 = Node::with_conf(&exe, &conf_node2).unwrap(); // Create Node 3 Connected To Node - let conf_node3 = Conf::<'_> { p2p: node2.p2p_connect(false).unwrap(), ..Default::default() }; + let conf_node3 = + Conf::<'_> { p2p: node2.p2p_connect(false).unwrap(), ..Default::default() }; let node3 = Node::with_conf(exe_path().unwrap(), &conf_node3).unwrap(); // Get each nodes Peers diff --git a/node/src/versions.rs b/node/src/versions.rs index d217ce0..3af085d 100644 --- a/node/src/versions.rs +++ b/node/src/versions.rs @@ -47,6 +47,22 @@ pub const VERSION: &str = "0.18.1"; pub const VERSION: &str = "0.17.1"; // To make --no-default-features work we have to enable some feature, use most recent version same as for default. -#[cfg(all(not(feature = "28_0"), not(feature = "27_1"), not(feature = "27_0"), not(feature = "26_2"), not(feature = "26_1"), not(feature = "26_0"), not(feature = "25_2"), not(feature = "24_2"), not(feature = "23_2"), not(feature = "22_1"), not(feature = "0_21_2"), not(feature = "0_20_2"), not(feature = "0_19_1"), not(feature = "0_18_1"), not(feature = "0_17_1")))] -#[allow(dead_code)] // for --no-default-features +#[cfg(all( + not(feature = "28_0"), + not(feature = "27_1"), + not(feature = "27_0"), + not(feature = "26_2"), + not(feature = "26_1"), + not(feature = "26_0"), + not(feature = "25_2"), + not(feature = "24_2"), + not(feature = "23_2"), + not(feature = "22_1"), + not(feature = "0_21_2"), + not(feature = "0_20_2"), + not(feature = "0_19_1"), + not(feature = "0_18_1"), + not(feature = "0_17_1") +))] +#[allow(dead_code)] // for --no-default-features pub const VERSION: &str = "28.0"; diff --git a/types/src/v17/blockchain/into.rs b/types/src/v17/blockchain/into.rs index c2638b5..4e22375 100644 --- a/types/src/v17/blockchain/into.rs +++ b/types/src/v17/blockchain/into.rs @@ -502,14 +502,6 @@ impl GetTxOut { } } -impl GetTxOutProof { - /// Converts version specific type to a version nonspecific, more strongly typed type. - pub fn into_model(self) -> Result { - let data = Vec::from_hex(&self.0)?; - Ok(model::GetTxOutProof(data)) - } -} - impl GetTxOutSetInfo { /// Converts version specific type to a version nonspecific, more strongly typed type. pub fn into_model(self) -> Result { diff --git a/types/src/v17/blockchain/mod.rs b/types/src/v17/blockchain/mod.rs index a96856e..bae4660 100644 --- a/types/src/v17/blockchain/mod.rs +++ b/types/src/v17/blockchain/mod.rs @@ -599,7 +599,7 @@ pub struct GetTxOut { /// The transaction value in BTC. pub value: f64, /// The script pubkey. - #[serde(rename = "scriptPubkey")] + #[serde(rename = "scriptPubKey")] pub script_pubkey: ScriptPubkey, /// Coinbase or not. pub coinbase: bool, @@ -621,28 +621,6 @@ pub struct ScriptPubkey { pub addresses: Vec, } -/// Result of JSON-RPC method `gettxoutproof`. -/// -/// > gettxoutproof ["txid",...] ( blockhash ) -/// > -/// > Returns a hex-encoded proof that "txid" was included in a block. -/// > -/// > NOTE: By default this function only works sometimes. This is when there is an -/// > unspent output in the utxo for this transaction. To make it always work, -/// > you need to maintain a transaction index, using the -txindex command line option or -/// > specify the block in which the transaction is included manually (by blockhash). -/// > -/// > Arguments: -/// > 1. "txids" (string) A json array of txids to filter -/// > [ -/// > "txid" (string) A transaction hash -/// > ,... -/// > ] -/// -/// Inner field is a string that is a serialized, hex-encoded data for the proof. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct GetTxOutProof(pub String); - /// Result of JSON-RPC method `gettxoutsetinfo`. /// /// > gettxoutsetinfo diff --git a/types/src/v17/mod.rs b/types/src/v17/mod.rs index fd8b30c..ea7ef6a 100644 --- a/types/src/v17/mod.rs +++ b/types/src/v17/mod.rs @@ -29,18 +29,18 @@ //! | getdifficulty | done | //! | getmempoolancestors | done (untested) | //! | getmempooldescendants | done (untested) | -//! | getmempoolentry | done (untested) | -//! | getmempoolinfo | done (untested) | -//! | getrawmempool | done (untested) | -//! | gettxout | done (untested) | -//! | gettxoutproof | done (untested) | -//! | gettxoutsetinfo | done (untested) | +//! | getmempoolentry | done | +//! | getmempoolinfo | done | +//! | getrawmempool | done | +//! | gettxout | done | +//! | gettxoutproof | omitted | +//! | gettxoutsetinfo | done | //! | preciousblock | omitted | //! | pruneblockchain | omitted | //! | savemempool | omitted | //! | scantxoutset | omitted | //! | verifychain | omitted | -//! | verifytxoutproof | done (untested) | +//! | verifytxoutproof | done | //! //! //! @@ -237,8 +237,8 @@ pub use self::{ GetBlockVerbosityOne, GetBlockVerbosityZero, GetBlockchainInfo, GetChainTips, GetChainTxStats, GetDifficulty, GetMempoolAncestors, GetMempoolAncestorsVerbose, GetMempoolDescendants, GetMempoolDescendantsVerbose, GetMempoolEntry, GetMempoolInfo, - GetRawMempool, GetRawMempoolVerbose, GetTxOut, GetTxOutProof, GetTxOutSetInfo, - MempoolEntry, MempoolEntryFees, ScriptPubkey, Softfork, SoftforkReject, VerifyTxOutProof, + GetRawMempool, GetRawMempoolVerbose, GetTxOut, GetTxOutSetInfo, MempoolEntry, + MempoolEntryFees, ScriptPubkey, Softfork, SoftforkReject, VerifyTxOutProof, }, control::{GetMemoryInfoStats, Locked, Logging, Uptime}, generating::{Generate, GenerateToAddress}, diff --git a/types/src/v18/mod.rs b/types/src/v18/mod.rs index fce71f5..349404b 100644 --- a/types/src/v18/mod.rs +++ b/types/src/v18/mod.rs @@ -34,18 +34,18 @@ //! | getdifficulty | done | //! | getmempoolancestors | done (untested) | //! | getmempooldescendants | done (untested) | -//! | getmempoolentry | done (untested) | -//! | getmempoolinfo | done (untested) | -//! | getrawmempool | done (untested) | -//! | gettxout | done (untested) | -//! | gettxoutproof | done (untested) | -//! | gettxoutsetinfo | done (untested) | +//! | getmempoolentry | done | +//! | getmempoolinfo | done | +//! | getrawmempool | done | +//! | gettxout | done | +//! | gettxoutproof | omitted | +//! | gettxoutsetinfo | done | //! | preciousblock | omitted | //! | pruneblockchain | omitted | //! | savemempool | omitted | //! | scantxoutset | omitted | //! | verifychain | omitted | -//! | verifytxoutproof | done (untested) | +//! | verifytxoutproof | done | //! //! //! @@ -237,13 +237,13 @@ pub use crate::v17::{ GetMempoolDescendantsVerbose, GetMempoolEntry, GetMempoolInfo, GetNetTotals, GetNetworkInfo, GetNetworkInfoAddress, GetNetworkInfoError, GetNetworkInfoNetwork, GetNewAddress, GetPeerInfo, GetRawChangeAddress, GetRawMempool, GetRawMempoolVerbose, GetReceivedByAddress, GetTransaction, - GetTransactionDetail, GetTxOut, GetTxOutProof, GetTxOutSetInfo, GetUnconfirmedBalance, - GetWalletInfo, GetZmqNotifications, ListAddressGroupings, ListAddressGroupingsItem, ListBanned, - ListLabels, ListLockUnspent, ListLockUnspentItem, ListReceivedByAddress, - ListReceivedByAddressItem, ListSinceBlock, ListSinceBlockTransaction, ListTransactions, - ListTransactionsItem, ListUnspent, ListUnspentItem, ListWallets, LoadWallet, Locked, Logging, - MempoolEntry, MempoolEntryFees, PeerInfo, RescanBlockchain, ScriptPubkey, SendMany, - SendRawTransaction, SendToAddress, SignErrorData, SignMessage, SignRawTransactionWithWallet, - Softfork, SoftforkReject, TransactionCategory, UploadTarget, Uptime, VerifyTxOutProof, - WalletCreateFundedPsbt, WalletProcessPsbt, + GetTransactionDetail, GetTxOut, GetTxOutSetInfo, GetUnconfirmedBalance, GetWalletInfo, + GetZmqNotifications, ListAddressGroupings, ListAddressGroupingsItem, ListBanned, ListLabels, + ListLockUnspent, ListLockUnspentItem, ListReceivedByAddress, ListReceivedByAddressItem, + ListSinceBlock, ListSinceBlockTransaction, ListTransactions, ListTransactionsItem, ListUnspent, + ListUnspentItem, ListWallets, LoadWallet, Locked, Logging, MempoolEntry, MempoolEntryFees, + PeerInfo, RescanBlockchain, ScriptPubkey, SendMany, SendRawTransaction, SendToAddress, + SignErrorData, SignMessage, SignRawTransactionWithWallet, Softfork, SoftforkReject, + TransactionCategory, UploadTarget, Uptime, VerifyTxOutProof, WalletCreateFundedPsbt, + WalletProcessPsbt, };