diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2e7d3d3a..97ee2a9c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,62 +1,87 @@ -on: [push, pull_request] +on: + push: + branches: + - master + - 'test-ci/**' + pull_request: name: Continuous integration jobs: - tests: - name: Tests - runs-on: ubuntu-latest - strategy: - matrix: - include: - - rust: stable - env: - RUSTFMTCHK: true - - rust: nightly - env: - RUSTFMTCHK: false - - rust: 1.56.1 - env: - RUSTFMTCHK: false - steps: - - name: Checkout Crate - uses: actions/checkout@v2 - - name: Checkout Toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - name: Running test script - env: ${{ matrix.env }} - run: ./contrib/test.sh + Stable: + name: Test - stable toolchain + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: "Checkout repo" + uses: actions/checkout@v4 + - name: "Select toolchain" + uses: dtolnay/rust-toolchain@stable + - name: "Run test script" + run: ./contrib/run_task.sh stable - integrations-tests: - name: Integration Tests - runs-on: ubuntu-latest - strategy: - matrix: - rust: [stable] - bitcoinversion: - [ - "0.18.0", - "0.18.1", - "0.19.0.1", - "0.19.1", - "0.20.0", - "0.20.1", - "0.21.0", - ] - steps: - - name: Checkout Crate - uses: actions/checkout@v2 - - name: Checkout Toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - name: Running test script - env: - BITCOINVERSION: ${{ matrix.bitcoinversion }} - run: ./contrib/test.sh + Nightly: + name: Test - nightly toolchain + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: "Checkout repo" + uses: actions/checkout@v4 + - name: "Select toolchain" + uses: dtolnay/rust-toolchain@nightly + - name: "Run test script" + run: ./contrib/run_task.sh nightly + + MSRV: + name: Test - 1.56.1 toolchain + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: "Checkout repo" + uses: actions/checkout@v4 + - name: "Select toolchain" + uses: dtolnay/rust-toolchain@stable + with: + toolchain: "1.56.1" + - name: "Run test script" + run: ./contrib/run_task.sh msrv + + Format: + name: Format - stable toolchain + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - name: "Checkout repo" + uses: actions/checkout@v4 + - name: "Select toolchain" + uses: dtolnay/rust-toolchain@stable + - name: "Check formatting" + run: cargo fmt --all -- --check + + Integration: + name: Integration Tests - stable toolchain + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + bitcoin_version: + [ + "0.18.0", + "0.18.1", + "0.19.0.1", + "0.19.1", + "0.20.0", + "0.20.1", + "0.21.0", + ] + steps: + - name: "Checkout repo" + uses: actions/checkout@v4 + - name: "Select toolchain" + uses: dtolnay/rust-toolchain@stable + - name: Running test script + run: ./contrib/run_task.sh integration ${{ matrix.bitcoin_version }} diff --git a/client/Cargo.toml b/client/Cargo.toml index cb2573d1..3f1e01f4 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -18,6 +18,9 @@ edition = "2018" name = "bitcoincore_rpc" path = "src/lib.rs" +[features] +verifymessage = ["bitcoincore-rpc-json/verifymessage"] + [dependencies] bitcoincore-rpc-json = { version = "0.18.0", path = "../json" } @@ -25,9 +28,16 @@ log = "0.4.5" jsonrpc = "0.14.0" # Used for deserialization of JSON. -serde = "1" -serde_json = "1" +serde = "1.0.156" +serde_json = "1.0.96" [dev-dependencies] -tempfile = "3.3.0" +tempfile = "3.6.0" + +[[example]] +name = "retry_client" +required-features = ["verifymessage"] +[[example]] +name = "test_against_node" +required-features = [] diff --git a/client/src/client.rs b/client/src/client.rs index 5a917903..30a6d35c 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -21,12 +21,12 @@ use jsonrpc; use serde; use serde_json; -use crate::bitcoin::address::{NetworkUnchecked, NetworkChecked}; +use crate::bitcoin::address::{NetworkChecked, NetworkUnchecked}; use crate::bitcoin::hashes::hex::FromHex; -use bitcoin::sign_message::MessageSignature; use crate::bitcoin::{ Address, Amount, Block, OutPoint, PrivateKey, PublicKey, Script, Transaction, }; +use bitcoin::sign_message::MessageSignature; use log::Level::{Debug, Trace, Warn}; use crate::error::*; @@ -871,6 +871,7 @@ pub trait RpcApi: Sized { self.call("stop", &[]) } + #[cfg(feature = "verifymessage")] fn verify_message( &self, address: &Address, @@ -891,7 +892,10 @@ pub trait RpcApi: Sized { } /// Generate new address for receiving change - fn get_raw_change_address(&self, address_type: Option) -> Result> { + fn get_raw_change_address( + &self, + address_type: Option, + ) -> Result> { self.call("getrawchangeaddress", &[opt_into_json(address_type)?]) } @@ -1182,7 +1186,11 @@ pub trait RpcApi: Sized { self.call("finalizepsbt", handle_defaults(&mut args, &[true.into()])) } - fn derive_addresses(&self, descriptor: &str, range: Option<[u32; 2]>) -> Result>> { + fn derive_addresses( + &self, + descriptor: &str, + range: Option<[u32; 2]>, + ) -> Result>> { let mut args = [into_json(descriptor)?, opt_into_json(range)?]; self.call("deriveaddresses", handle_defaults(&mut args, &[null()])) } diff --git a/contrib/run_task.sh b/contrib/run_task.sh new file mode 100755 index 00000000..97d3dde0 --- /dev/null +++ b/contrib/run_task.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash +# +# Run CI task, called by the `rust.yml` GitHub action. + +set -euo pipefail + +REPO_DIR=$(git rev-parse --show-toplevel) +MSRV="1.56.1" + +usage() { + cat < /dev/null; + + cargo "$toolchain" build + cargo "$toolchain" test + + popd > /dev/null + done +} + +# Pin dependencies to get the MSRV build to work. +do_msrv_pins() { + cargo update -p tempfile --precise 3.6.0 + cargo update -p cc --precise 1.0.79 + cargo update -p log --precise 0.4.18 + cargo update -p serde_json --precise 1.0.96 + cargo update -p serde --precise 1.0.156 +} + +# Check the workspace formatting. +do_fmt() { + cargo +stable fmt --all --check +} + +# Pulls down Bitcoin Core binary and runs the integration tests. +integration() { + local core_version="$1" + + cd "$REPO_DIR" + + if [ "$core_version" != "none" ]; then + wget "https://bitcoincore.org/bin/bitcoin-core-$bitcoin_version/bitcoin-$bitcoin_version-x86_64-linux-gnu.tar.gz" + tar -xzvf "bitcoin-$bitcoin_version-x86_64-linux-gnu.tar.gz" + export PATH=$PATH:"$REPO_DIR/bitcoin-$bitcoin_version/bin" + fi + + need_cmd "bitcoind" + + cd "$REPO_DIR/integration_test" + ./run.sh +} + +# Check all the commands we use are present in the current environment. +check_required_commands() { + need_cmd cargo + need_cmd rustc +} + +need_cmd() { + if ! command -v "$1" > /dev/null 2>&1 + then err "need '$1' (command not found)" + fi +} + +err() { + echo "$1" >&2 + exit 1 +} + +# +# Main script +# +main "$@" +exit 0 diff --git a/contrib/test.sh b/contrib/test.sh index e308ce3c..2aa5a279 100755 --- a/contrib/test.sh +++ b/contrib/test.sh @@ -16,7 +16,10 @@ fi # Test pinned versions. if cargo --version | grep ${MSRV}; then cargo update -p tempfile --precise 3.3.0 + cargo update -p cc --precise 1.0.79 cargo update -p log --precise 0.4.18 + cargo update -p serde_json --precise 1.0.96 + cargo update -p serde --precise 1.0.156 fi # Integration test. diff --git a/integration_test/Cargo.toml b/integration_test/Cargo.toml index 8ad6c1b5..9b4a8b5e 100644 --- a/integration_test/Cargo.toml +++ b/integration_test/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Steven Roose "] edition = "2018" [dependencies] -bitcoincore-rpc = { path = "../client" } +bitcoincore-rpc = { path = "../client", features = ["verifymessage"] } bitcoin = { version = "0.31.0", features = ["serde", "rand", "base64"]} lazy_static = "1.4.0" log = "0.4" diff --git a/integration_test/src/main.rs b/integration_test/src/main.rs index 6105a4d9..3da4fddc 100644 --- a/integration_test/src/main.rs +++ b/integration_test/src/main.rs @@ -27,7 +27,7 @@ use bitcoin::consensus::encode::{deserialize, serialize_hex}; use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::Hash; use bitcoin::sign_message::MessageSignature; -use bitcoin::{secp256k1, ScriptBuf, sighash}; +use bitcoin::{secp256k1, sighash, ScriptBuf}; use bitcoin::{ transaction, Address, Amount, Network, OutPoint, PrivateKey, Sequence, SignedAmount, Transaction, TxIn, TxOut, Txid, Witness, @@ -252,7 +252,8 @@ fn test_get_new_address(cl: &Client) { let addr = cl.get_new_address(None, Some(json::AddressType::Bech32)).unwrap().assume_checked(); assert_eq!(addr.address_type(), Some(bitcoin::AddressType::P2wpkh)); - let addr = cl.get_new_address(None, Some(json::AddressType::P2shSegwit)).unwrap().assume_checked(); + let addr = + cl.get_new_address(None, Some(json::AddressType::P2shSegwit)).unwrap().assume_checked(); assert_eq!(addr.address_type(), Some(bitcoin::AddressType::P2sh)); } @@ -263,7 +264,8 @@ fn test_get_raw_change_address(cl: &Client) { let addr = cl.get_raw_change_address(Some(json::AddressType::Bech32)).unwrap().assume_checked(); assert_eq!(addr.address_type(), Some(bitcoin::AddressType::P2wpkh)); - let addr = cl.get_raw_change_address(Some(json::AddressType::P2shSegwit)).unwrap().assume_checked(); + let addr = + cl.get_raw_change_address(Some(json::AddressType::P2shSegwit)).unwrap().assume_checked(); assert_eq!(addr.address_type(), Some(bitcoin::AddressType::P2sh)); } @@ -293,7 +295,9 @@ fn test_generate(cl: &Client) { fn test_get_balance_generate_to_address(cl: &Client) { let initial = cl.get_balance(None, None).unwrap(); - let blocks = cl.generate_to_address(500, &cl.get_new_address(None, None).unwrap().assume_checked()).unwrap(); + let blocks = cl + .generate_to_address(500, &cl.get_new_address(None, None).unwrap().assume_checked()) + .unwrap(); assert_eq!(blocks.len(), 500); assert_ne!(cl.get_balance(None, None).unwrap(), initial); } @@ -302,7 +306,9 @@ fn test_get_balances_generate_to_address(cl: &Client) { if version() >= 190000 { let initial = cl.get_balances().unwrap(); - let blocks = cl.generate_to_address(500, &cl.get_new_address(None, None).unwrap().assume_checked()).unwrap(); + let blocks = cl + .generate_to_address(500, &cl.get_new_address(None, None).unwrap().assume_checked()) + .unwrap(); assert_eq!(blocks.len(), 500); assert_ne!(cl.get_balances().unwrap(), initial); } @@ -378,7 +384,8 @@ fn test_get_address_info(cl: &Client) { let info = cl.get_address_info(&addr).unwrap(); assert!(!info.witness_program.unwrap().is_empty()); - let addr = cl.get_new_address(None, Some(json::AddressType::P2shSegwit)).unwrap().assume_checked(); + let addr = + cl.get_new_address(None, Some(json::AddressType::P2shSegwit)).unwrap().assume_checked(); let info = cl.get_address_info(&addr).unwrap(); assert!(!info.hex.unwrap().is_empty()); } @@ -434,7 +441,9 @@ fn test_get_received_by_address(cl: &Client) { let _ = cl.send_to_address(&addr, btc(1), None, None, None, None, None, None).unwrap(); assert_eq!(cl.get_received_by_address(&addr, Some(0)).unwrap(), btc(1)); assert_eq!(cl.get_received_by_address(&addr, Some(1)).unwrap(), btc(0)); - let _ = cl.generate_to_address(7, &cl.get_new_address(None, None).unwrap().assume_checked()).unwrap(); + let _ = cl + .generate_to_address(7, &cl.get_new_address(None, None).unwrap().assume_checked()) + .unwrap(); assert_eq!(cl.get_received_by_address(&addr, Some(6)).unwrap(), btc(1)); assert_eq!(cl.get_received_by_address(&addr, None).unwrap(), btc(1)); } @@ -442,19 +451,23 @@ fn test_get_received_by_address(cl: &Client) { fn test_list_unspent(cl: &Client) { let addr = cl.get_new_address(None, None).unwrap(); let addr_checked = addr.clone().assume_checked(); - let txid = cl.send_to_address(&addr.clone().assume_checked(), btc(1), None, None, None, None, None, None).unwrap(); - let unspent = cl.list_unspent(Some(0), None, Some(&[ &addr_checked]), None, None).unwrap(); + let txid = cl + .send_to_address(&addr.clone().assume_checked(), btc(1), None, None, None, None, None, None) + .unwrap(); + let unspent = cl.list_unspent(Some(0), None, Some(&[&addr_checked]), None, None).unwrap(); assert_eq!(unspent[0].txid, txid); assert_eq!(unspent[0].address.as_ref(), Some(&addr)); assert_eq!(unspent[0].amount, btc(1)); - let txid = cl.send_to_address(&addr_checked, btc(7), None, None, None, None, None, None).unwrap(); + let txid = + cl.send_to_address(&addr_checked, btc(7), None, None, None, None, None, None).unwrap(); let options = json::ListUnspentQueryOptions { minimum_amount: Some(btc(7)), maximum_amount: Some(btc(7)), ..Default::default() }; - let unspent = cl.list_unspent(Some(0), None, Some(&[&addr_checked]), None, Some(options)).unwrap(); + let unspent = + cl.list_unspent(Some(0), None, Some(&[&addr_checked]), None, Some(options)).unwrap(); assert_eq!(unspent.len(), 1); assert_eq!(unspent[0].txid, txid); assert_eq!(unspent[0].address.as_ref(), Some(&addr)); @@ -480,7 +493,9 @@ fn test_get_raw_transaction(cl: &Client) { let info = cl.get_raw_transaction_info(&txid, None).unwrap(); assert_eq!(info.txid, txid); - let blocks = cl.generate_to_address(7, &cl.get_new_address(None, None).unwrap().assume_checked()).unwrap(); + let blocks = cl + .generate_to_address(7, &cl.get_new_address(None, None).unwrap().assume_checked()) + .unwrap(); let _ = cl.get_raw_transaction_info(&txid, Some(&blocks[0])).unwrap(); } @@ -536,7 +551,9 @@ fn test_get_tx_out_proof(cl: &Client) { cl.send_to_address(&RANDOM_ADDRESS, btc(1), None, None, None, None, None, None).unwrap(); let txid2 = cl.send_to_address(&RANDOM_ADDRESS, btc(1), None, None, None, None, None, None).unwrap(); - let blocks = cl.generate_to_address(7, &cl.get_new_address(None, None).unwrap().assume_checked()).unwrap(); + let blocks = cl + .generate_to_address(7, &cl.get_new_address(None, None).unwrap().assume_checked()) + .unwrap(); let proof = cl.get_tx_out_proof(&[txid1, txid2], Some(&blocks[0])).unwrap(); assert!(!proof.is_empty()); } @@ -563,7 +580,9 @@ fn test_lock_unspent_unlock_unspent(cl: &Client) { } fn test_get_block_filter(cl: &Client) { - let blocks = cl.generate_to_address(7, &cl.get_new_address(None, None).unwrap().assume_checked()).unwrap(); + let blocks = cl + .generate_to_address(7, &cl.get_new_address(None, None).unwrap().assume_checked()) + .unwrap(); if version() >= 190000 { let _ = cl.get_block_filter(&blocks[0]).unwrap(); } else { @@ -634,7 +653,12 @@ fn test_sign_raw_transaction_with_send_raw_transaction(cl: &Client) { }; let res = cl - .sign_raw_transaction_with_key(&tx, &[sk], None, Some(sighash::EcdsaSighashType::All.into())) + .sign_raw_transaction_with_key( + &tx, + &[sk], + None, + Some(sighash::EcdsaSighashType::All.into()), + ) .unwrap(); assert!(res.complete); let _ = cl.send_raw_transaction(&res.transaction().unwrap()).unwrap(); @@ -1283,9 +1307,7 @@ fn test_getblocktemplate(cl: &Client) { fn test_unloadwallet(cl: &Client) { cl.create_wallet("testunloadwallet", None, None, None, None).unwrap(); - let res = new_wallet_client("testunloadwallet") - .unload_wallet(None) - .unwrap(); + let res = new_wallet_client("testunloadwallet").unload_wallet(None).unwrap(); if version() >= 210000 { assert!(res.is_some()); @@ -1324,7 +1346,13 @@ fn test_wait_for_new_block(cl: &Client) { let hash = cl.get_block_hash(height).unwrap(); assert!(cl.wait_for_new_block(std::u64::MAX).is_err()); // JSON integer out of range - assert_eq!(cl.wait_for_new_block(100).unwrap(), json::BlockRef{hash, height}); + assert_eq!( + cl.wait_for_new_block(100).unwrap(), + json::BlockRef { + hash, + height + } + ); } fn test_wait_for_block(cl: &Client) { @@ -1332,12 +1360,23 @@ fn test_wait_for_block(cl: &Client) { let hash = cl.get_block_hash(height).unwrap(); assert!(cl.wait_for_block(&hash, std::u64::MAX).is_err()); // JSON integer out of range - assert_eq!(cl.wait_for_block(&hash, 0).unwrap(), json::BlockRef{hash, height}); + assert_eq!( + cl.wait_for_block(&hash, 0).unwrap(), + json::BlockRef { + hash, + height + } + ); } fn test_get_descriptor_info(cl: &Client) { - let res = cl.get_descriptor_info(r"pkh(cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR)").unwrap(); - assert_eq!(res.descriptor, r"pkh(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)#62k9sn4x"); + let res = cl + .get_descriptor_info(r"pkh(cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR)") + .unwrap(); + assert_eq!( + res.descriptor, + r"pkh(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)#62k9sn4x" + ); assert_eq!(res.is_range, false); assert_eq!(res.is_solvable, true); assert_eq!(res.has_private_keys, true); @@ -1355,29 +1394,40 @@ fn test_get_descriptor_info(cl: &Client) { fn test_add_multisig_address(cl: &Client) { let addr1 = cl.get_new_address(None, Some(json::AddressType::Bech32)).unwrap().assume_checked(); let addr2 = cl.get_new_address(None, Some(json::AddressType::Bech32)).unwrap().assume_checked(); - let addresses = [ - json::PubKeyOrAddress::Address(&addr1), - json::PubKeyOrAddress::Address(&addr2), - ]; + let addresses = + [json::PubKeyOrAddress::Address(&addr1), json::PubKeyOrAddress::Address(&addr2)]; assert!(cl.add_multisig_address(addresses.len(), &addresses, None, None).is_ok()); assert!(cl.add_multisig_address(addresses.len() - 1, &addresses, None, None).is_ok()); assert!(cl.add_multisig_address(addresses.len() + 1, &addresses, None, None).is_err()); assert!(cl.add_multisig_address(0, &addresses, None, None).is_err()); assert!(cl.add_multisig_address(addresses.len(), &addresses, Some("test_label"), None).is_ok()); - assert!(cl.add_multisig_address(addresses.len(), &addresses, None, Some(json::AddressType::Legacy)).is_ok()); - assert!(cl.add_multisig_address(addresses.len(), &addresses, None, Some(json::AddressType::P2shSegwit)).is_ok()); - assert!(cl.add_multisig_address(addresses.len(), &addresses, None, Some(json::AddressType::Bech32)).is_ok()); + assert!(cl + .add_multisig_address(addresses.len(), &addresses, None, Some(json::AddressType::Legacy)) + .is_ok()); + assert!(cl + .add_multisig_address( + addresses.len(), + &addresses, + None, + Some(json::AddressType::P2shSegwit) + ) + .is_ok()); + assert!(cl + .add_multisig_address(addresses.len(), &addresses, None, Some(json::AddressType::Bech32)) + .is_ok()); } fn test_verify_message_with_messagesignature(cl: &Client) { - let addr: Address = Address::from_str("mm68FdwbpxkVcqjU3fu7iiBGEwrsC6Hk66").unwrap().assume_checked(); + let addr: Address = + Address::from_str("mm68FdwbpxkVcqjU3fu7iiBGEwrsC6Hk66").unwrap().assume_checked(); let signature = MessageSignature::from_base64( - "H3X+ic7axKtHGIsKiqDq0TmP9HIAkONwunln17ROlvB4SOVVUoG5e79EwAz94x2eERPwqcGJ5rLuWRhIu85pEwE=",) - .expect("a valid signature"); + "H3X+ic7axKtHGIsKiqDq0TmP9HIAkONwunln17ROlvB4SOVVUoG5e79EwAz94x2eERPwqcGJ5rLuWRhIu85pEwE=", + ) + .expect("a valid signature"); let message = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"; - assert!(cl.verify_message(&addr, &signature, message).expect("a valid signature")); + assert!(cl.verify_message(&addr, &signature, message).expect("a valid signature")); } #[rustfmt::skip] diff --git a/json/Cargo.toml b/json/Cargo.toml index 8f3d67cc..4ac8ab3f 100644 --- a/json/Cargo.toml +++ b/json/Cargo.toml @@ -19,8 +19,11 @@ rust-version = "1.56.1" name = "bitcoincore_rpc_json" path = "src/lib.rs" +[features] +verifymessage = ["bitcoin/base64"] + [dependencies] -serde = { version = "1", features = [ "derive" ] } -serde_json = "1" +serde = { version = "1.0.156", features = [ "derive" ] } +serde_json = "1.0.96" bitcoin = { version = "0.31.0", features = ["serde", "rand-std"]} diff --git a/json/src/lib.rs b/json/src/lib.rs index bc3468f4..808cd39c 100644 --- a/json/src/lib.rs +++ b/json/src/lib.rs @@ -24,13 +24,15 @@ extern crate serde_json; use std::collections::HashMap; - use bitcoin::address::NetworkUnchecked; use bitcoin::block::Version; use bitcoin::consensus::encode; use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::sha256; -use bitcoin::{Address, Amount, PrivateKey, PublicKey, SignedAmount, Transaction, ScriptBuf, Script, bip158, bip32, Network}; +use bitcoin::{ + bip158, bip32, Address, Amount, Network, PrivateKey, PublicKey, Script, ScriptBuf, + SignedAmount, Transaction, +}; use serde::de::Error as SerdeError; use serde::{Deserialize, Serialize}; use std::fmt; @@ -1880,10 +1882,7 @@ pub struct FundRawTransactionOptions { /// The fee rate to pay per kvB. NB. This field is converted to camelCase /// when serialized, so it is receeived by fundrawtransaction as `feeRate`, /// which fee rate per kvB, and *not* `fee_rate`, which is per vB. - #[serde( - with = "bitcoin::amount::serde::as_btc::opt", - skip_serializing_if = "Option::is_none" - )] + #[serde(with = "bitcoin::amount::serde::as_btc::opt", skip_serializing_if = "Option::is_none")] pub fee_rate: Option, #[serde(skip_serializing_if = "Option::is_none")] pub subtract_fee_from_outputs: Option>, @@ -2183,7 +2182,7 @@ where /// deserialize_bip70_network deserializes a Bitcoin Core network according to BIP70 /// The accepted input variants are: {"main", "test", "signet", "regtest"} -fn deserialize_bip70_network<'de, D>(deserializer: D) -> Result +fn deserialize_bip70_network<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, { @@ -2192,8 +2191,12 @@ where type Value = Network; fn visit_str(self, s: &str) -> Result { - Network::from_core_arg(s) - .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(s), &"bitcoin network encoded as a string")) + Network::from_core_arg(s).map_err(|_| { + E::invalid_value( + serde::de::Unexpected::Str(s), + &"bitcoin network encoded as a string", + ) + }) } fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {