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) {