From 6030b61f49a5fb2274c873a3d442a3d6f35253e4 Mon Sep 17 00:00:00 2001 From: Clark Moody <clark@clarkmoody.com> Date: Sun, 12 Jun 2022 18:38:56 -0500 Subject: [PATCH 1/3] Update `get_tx_out_set_info` to Core 22.0+ --- client/src/client.rs | 13 ++++-- integration_test/src/main.rs | 2 +- json/src/lib.rs | 85 ++++++++++++++++++++++++++++++++---- 3 files changed, 88 insertions(+), 12 deletions(-) diff --git a/client/src/client.rs b/client/src/client.rs index 57f70678..6dc50d31 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -1133,9 +1133,16 @@ pub trait RpcApi: Sized { } /// Returns statistics about the unspent transaction output set. - /// This call may take some time. - fn get_tx_out_set_info(&self) -> Result<json::GetTxOutSetInfoResult> { - self.call("gettxoutsetinfo", &[]) + /// Note this call may take some time if you are not using coinstatsindex. + fn get_tx_out_set_info( + &self, + hash_type: Option<json::TxOutSetHashType>, + hash_or_height: Option<json::HashOrHeight>, + use_index: Option<bool>, + ) -> Result<json::GetTxOutSetInfoResult> { + let mut args = + [opt_into_json(hash_type)?, opt_into_json(hash_or_height)?, opt_into_json(use_index)?]; + self.call("gettxoutsetinfo", handle_defaults(&mut args, &[null(), null(), null()])) } /// Returns information about network traffic, including bytes in, bytes out, diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index a8b7a33e..a3f23e8b 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -1030,7 +1030,7 @@ fn test_create_wallet(cl: &Client) { } fn test_get_tx_out_set_info(cl: &Client) { - cl.get_tx_out_set_info().unwrap(); + cl.get_tx_out_set_info(None, None, None).unwrap(); } fn test_get_chain_tips(cl: &Client) { diff --git a/json/src/lib.rs b/json/src/lib.rs index 58e8933c..6fe07af2 100644 --- a/json/src/lib.rs +++ b/json/src/lib.rs @@ -1852,27 +1852,96 @@ pub struct SignRawTransactionInput { pub amount: Option<Amount>, } +/// Used to represent UTXO set hash type +#[derive(Clone, Serialize, PartialEq, Eq, Debug)] +#[serde(rename_all = "snake_case")] +pub enum TxOutSetHashType { + HashSerialized2, + Muhash, + None, +} + +/// Used to specify a block hash or a height +#[derive(Clone, Serialize, PartialEq, Eq, Debug)] +#[serde(untagged)] +pub enum HashOrHeight { + BlockHash(bitcoin::BlockHash), + Height(u64), +} + #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] pub struct GetTxOutSetInfoResult { - /// The current block height (index) + /// The block height (index) of the returned statistics pub height: u64, - /// The hash of the block at the tip of the chain + /// The hash of the block at which these statistics are calculated #[serde(rename = "bestblock")] pub best_block: bitcoin::BlockHash, - /// The number of transactions with unspent outputs - pub transactions: u64, + /// The number of transactions with unspent outputs (not available when coinstatsindex is used) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub transactions: Option<u64>, /// The number of unspent transaction outputs #[serde(rename = "txouts")] pub tx_outs: u64, /// A meaningless metric for UTXO set size pub bogosize: u64, - /// The serialized hash - pub hash_serialized_2: sha256::Hash, - /// The estimated size of the chainstate on disk - pub disk_size: u64, + /// The serialized hash (only present if 'hash_serialized_2' hash_type is chosen) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub hash_serialized_2: Option<sha256::Hash>, + /// The serialized hash (only present if 'muhash' hash_type is chosen) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub muhash: Option<sha256::Hash>, + /// The estimated size of the chainstate on disk (not available when coinstatsindex is used) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub disk_size: Option<u64>, /// The total amount #[serde(with = "bitcoin::util::amount::serde::as_btc")] pub total_amount: Amount, + /// The total amount of coins permanently excluded from the UTXO set (only available if coinstatsindex is used) + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "bitcoin::util::amount::serde::as_btc::opt" + )] + pub total_unspendable_amount: Option<Amount>, + /// Info on amounts in the block at this block height (only available if coinstatsindex is used) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub block_info: Option<BlockInfo>, +} + +/// Info on amounts in the block at the block height of the `gettxoutsetinfo` call (only available if coinstatsindex is used) +#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] +pub struct BlockInfo { + /// Amount of previous outputs spent + #[serde(with = "bitcoin::util::amount::serde::as_btc")] + pub prevout_spent: Amount, + /// Output size of the coinbase transaction + #[serde(with = "bitcoin::util::amount::serde::as_btc")] + pub coinbase: Amount, + /// Newly-created outputs + #[serde(with = "bitcoin::util::amount::serde::as_btc")] + pub new_outputs_ex_coinbase: Amount, + /// Amount of unspendable outputs + #[serde(with = "bitcoin::util::amount::serde::as_btc")] + pub unspendable: Amount, + /// Detailed view of the unspendable categories + pub unspendables: Unspendables, +} + +/// Detailed view of the unspendable categories +#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] +pub struct Unspendables { + /// Unspendable coins from the Genesis block + #[serde(with = "bitcoin::util::amount::serde::as_btc")] + pub genesis_block: Amount, + /// Transactions overridden by duplicates (no longer possible with BIP30) + #[serde(with = "bitcoin::util::amount::serde::as_btc")] + pub bip30: Amount, + /// Amounts sent to scripts that are unspendable (for example OP_RETURN outputs) + #[serde(with = "bitcoin::util::amount::serde::as_btc")] + pub scripts: Amount, + /// Fee rewards that miners did not claim in their coinbase transaction + #[serde(with = "bitcoin::util::amount::serde::as_btc")] + pub unclaimed_rewards: Amount, } #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] From 9aa1ec26f84506eabf327b4e1db7748e7e3e8dd5 Mon Sep 17 00:00:00 2001 From: Clark Moody <clark@clarkmoody.com> Date: Sun, 12 Jun 2022 19:04:04 -0500 Subject: [PATCH 2/3] Add support for Core 22.0 & 23.0 --- .github/workflows/rust.yml | 2 ++ README.md | 2 ++ integration_test/run.sh | 7 ++++++- integration_test/src/main.rs | 6 +++++- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index fc4cabaf..f4c643d2 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -46,6 +46,8 @@ jobs: "0.20.0", "0.20.1", "0.21.0", + "22.0", + "23.0", ] steps: - name: Checkout Crate diff --git a/README.md b/README.md index c7517e74..0d9da1e3 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ The following versions are officially supported and automatically tested: * 0.20.0 * 0.20.1 * 0.21.0 +* 22.0 +* 23.0 # Minimum Supported Rust Version (MSRV) This library should always compile with any combination of features on **Rust 1.29**. diff --git a/integration_test/run.sh b/integration_test/run.sh index a442ae47..bb06cfdf 100755 --- a/integration_test/run.sh +++ b/integration_test/run.sh @@ -28,7 +28,12 @@ if bitcoind -version | grep -q "v0\.2"; then FALLBACKFEEARG="-fallbackfee=0.00001000" fi -bitcoind -regtest $BLOCKFILTERARG $FALLBACKFEEARG \ +COINSTATSINDEXARG="" +if bitcoind -version | grep -q "v[2-9]"; then + COINSTATSINDEXARG="-coinstatsindex=1" +fi + +bitcoind -regtest $BLOCKFILTERARG $FALLBACKFEEARG $COINSTATSINDEXARG \ -datadir=${TESTDIR}/2 \ -connect=127.0.0.1:12348 \ -rpcport=12349 \ diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index a3f23e8b..b10d1530 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -1030,7 +1030,11 @@ fn test_create_wallet(cl: &Client) { } fn test_get_tx_out_set_info(cl: &Client) { - cl.get_tx_out_set_info(None, None, None).unwrap(); + if version() >= 220000 { + cl.get_tx_out_set_info(Some(json::TxOutSetHashType::Muhash), None, Some(true)).unwrap(); + } else { + cl.get_tx_out_set_info(None, None, None).unwrap(); + } } fn test_get_chain_tips(cl: &Client) { From 05e0ae3c25e694cf9b35b1aed2cf909bcc3ff58b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= <hello@evanlinjin.me> Date: Tue, 21 Jun 2022 01:45:50 +0800 Subject: [PATCH 3/3] Update `create_wallet` to support Core v0.23.0 --- client/src/client.rs | 12 +++++++++--- integration_test/run.sh | 4 ++++ integration_test/src/main.rs | 38 ++++++++++++++++++++++++++---------- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/client/src/client.rs b/client/src/client.rs index 6dc50d31..9808efc5 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -271,22 +271,28 @@ pub trait RpcApi: Sized { fn create_wallet( &self, - wallet: &str, + wallet_name: &str, disable_private_keys: Option<bool>, blank: Option<bool>, passphrase: Option<&str>, avoid_reuse: Option<bool>, + descriptors: Option<bool>, + load_on_startup: Option<bool>, + external_signer: Option<bool>, ) -> Result<json::LoadWalletResult> { let mut args = [ - wallet.into(), + wallet_name.into(), opt_into_json(disable_private_keys)?, opt_into_json(blank)?, opt_into_json(passphrase)?, opt_into_json(avoid_reuse)?, + opt_into_json(descriptors)?, + opt_into_json(load_on_startup)?, + opt_into_json(external_signer)?, ]; self.call( "createwallet", - handle_defaults(&mut args, &[false.into(), false.into(), into_json("")?, false.into()]), + handle_defaults(&mut args, &[false.into(), false.into(), into_json("")?, false.into(), true.into(), false.into(), false.into()]), ) } diff --git a/integration_test/run.sh b/integration_test/run.sh index bb06cfdf..89c14a75 100755 --- a/integration_test/run.sh +++ b/integration_test/run.sh @@ -21,11 +21,15 @@ sleep 3 BLOCKFILTERARG="" if bitcoind -version | grep -q "v0\.\(19\|2\)"; then BLOCKFILTERARG="-blockfilterindex=1" +elif bitcoind -version | grep -q "v\(22\|23\)"; then + BLOCKFILTERARG="-blockfilterindex=1" fi FALLBACKFEEARG="" if bitcoind -version | grep -q "v0\.2"; then FALLBACKFEEARG="-fallbackfee=0.00001000" +elif bitcoind -version | grep -q "v\(22\|23\)"; then + FALLBACKFEEARG="-fallbackfee=0.00001000" fi COINSTATSINDEXARG="" diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index b10d1530..dd3399e9 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -27,8 +27,8 @@ use bitcoin::hashes::hex::{FromHex, ToHex}; use bitcoin::hashes::Hash; use bitcoin::secp256k1; use bitcoin::{ - Address, Amount, Network, OutPoint, PrivateKey, Script, EcdsaSighashType, SignedAmount, Transaction, - TxIn, TxOut, Txid, Witness, + Address, Amount, EcdsaSighashType, Network, OutPoint, PrivateKey, Script, SignedAmount, + Transaction, TxIn, TxOut, Txid, Witness, }; use bitcoincore_rpc::bitcoincore_rpc_json::{ GetBlockTemplateModes, GetBlockTemplateRules, ScanTxOutRequest, @@ -134,7 +134,7 @@ fn main() { unsafe { VERSION = cl.version().unwrap() }; println!("Version: {}", version()); - cl.create_wallet("testwallet", None, None, None, None).unwrap(); + cl.create_wallet("testwallet", None, None, None, None, Some(false), None, None).unwrap(); test_get_mining_info(&cl); test_get_blockchain_info(&cl); @@ -596,8 +596,9 @@ fn test_sign_raw_transaction_with_send_raw_transaction(cl: &Client) { }], }; - let res = - cl.sign_raw_transaction_with_key(&tx, &[sk], None, Some(EcdsaSighashType::All.into())).unwrap(); + let res = cl + .sign_raw_transaction_with_key(&tx, &[sk], None, Some(EcdsaSighashType::All.into())) + .unwrap(); assert!(res.complete); let _ = cl.send_raw_transaction(&res.transaction().unwrap()).unwrap(); } @@ -936,6 +937,9 @@ fn test_create_wallet(cl: &Client) { blank: Option<bool>, passphrase: Option<&'a str>, avoid_reuse: Option<bool>, + descriptors: Option<bool>, + load_on_startup: Option<bool>, + external_signer: Option<bool>, } let mut wallet_params = vec![ @@ -945,6 +949,9 @@ fn test_create_wallet(cl: &Client) { blank: None, passphrase: None, avoid_reuse: None, + descriptors: None, + load_on_startup: None, + external_signer: None, }, WalletParams { name: wallet_names[1], @@ -952,6 +959,9 @@ fn test_create_wallet(cl: &Client) { blank: None, passphrase: None, avoid_reuse: None, + descriptors: None, + load_on_startup: None, + external_signer: None, }, WalletParams { name: wallet_names[2], @@ -959,6 +969,9 @@ fn test_create_wallet(cl: &Client) { blank: Some(true), passphrase: None, avoid_reuse: None, + descriptors: None, + load_on_startup: None, + external_signer: None, }, ]; @@ -969,6 +982,9 @@ fn test_create_wallet(cl: &Client) { blank: None, passphrase: Some("pass"), avoid_reuse: None, + descriptors: None, + load_on_startup: None, + external_signer: None, }); wallet_params.push(WalletParams { name: wallet_names[4], @@ -976,6 +992,9 @@ fn test_create_wallet(cl: &Client) { blank: None, passphrase: None, avoid_reuse: Some(true), + descriptors: None, + load_on_startup: None, + external_signer: None, }); } @@ -987,6 +1006,9 @@ fn test_create_wallet(cl: &Client) { wallet_param.blank, wallet_param.passphrase, wallet_param.avoid_reuse, + wallet_param.descriptors, + wallet_param.load_on_startup, + wallet_param.external_signer, ) .unwrap(); @@ -1085,11 +1107,7 @@ fn test_add_ban(cl: &Client) { let res = cl.list_banned().unwrap(); assert_eq!(res.len(), 0); - assert_error_message!( - cl.add_ban("INVALID_STRING", 0, false), - -30, - "Error: Invalid IP/Subnet" - ); + assert_error_message!(cl.add_ban("INVALID_STRING", 0, false), -30, "Error: Invalid IP/Subnet"); } fn test_set_network_active(cl: &Client) {