├── .gitignore ├── .rustfmt.toml ├── Cargo.toml ├── contrib ├── crates.sh └── integration_test.sh ├── .editorconfig ├── json ├── README.md ├── contrib │ └── test_vars.sh ├── Cargo.toml └── LICENSE ├── README.md ├── client ├── contrib │ ├── test_vars.sh │ └── extra_test.sh ├── README.md ├── src │ ├── lib.rs │ ├── queryable.rs │ ├── error.rs │ └── client.rs ├── Cargo.toml ├── examples │ ├── retry_client.rs │ └── test_against_node.rs └── LICENSE ├── integration_test ├── Cargo.toml ├── run.sh └── src │ └── main.rs ├── justfile ├── .github └── workflows │ └── ci.yaml └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | /Cargo.lock 2 | /target 3 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_small_heuristics = "Off" 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | resolver = "2" 4 | 5 | members = [ 6 | "json", 7 | "client", 8 | "integration_test", 9 | ] 10 | -------------------------------------------------------------------------------- /contrib/crates.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Crates in this workspace to test (excl. fuzz an integration-tests). 4 | CRATES=("json" "client") 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # see https://editorconfig.org for more options, and setup instructions for yours editor 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | -------------------------------------------------------------------------------- /json/README.md: -------------------------------------------------------------------------------- 1 | bitcoincore-rpc-json 2 | ==================== 3 | 4 | A collection of JSON-enabled data types used in the `bitcoincore-rpc` crate. 5 | 6 | # License 7 | 8 | All code is licensed using the CC0 license, as per the LICENSE file. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `ord-bitcoincore-rpc` 2 | ===================== 3 | 4 | This crate is a fork of 5 | [rust-bitcoin/rust-bitcoincore-rpc](https://github.com/rust-bitcoin/rust-bitcoincore-rpc) 6 | for use in [ord](https://github.com/ordinals/ord/). 7 | -------------------------------------------------------------------------------- /json/contrib/test_vars.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Test all these features with "std" enabled. 4 | FEATURES_WITH_STD="" 5 | 6 | # Test all these features without "std" enabled. 7 | FEATURES_WITHOUT_STD="" 8 | 9 | # Run these examples. 10 | EXAMPLES="" 11 | -------------------------------------------------------------------------------- /client/contrib/test_vars.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Test all these features with "std" enabled. 4 | FEATURES_WITH_STD="" 5 | 6 | # Test all these features without "std" enabled. 7 | FEATURES_WITHOUT_STD="" 8 | 9 | # Run these examples. 10 | EXAMPLES="" 11 | -------------------------------------------------------------------------------- /integration_test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test" 3 | version = "0.1.0" 4 | authors = ["Steven Roose "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | ord-bitcoincore-rpc = { path = "../client", features = ["rand"] } 9 | bitcoin = { version = "0.32.0", features = ["serde", "rand"] } 10 | lazy_static = "1.4.0" 11 | log = "0.4" 12 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | watch +args='test': 2 | cargo watch --clear --exec '{{args}}' 3 | 4 | build: 5 | cargo build --workspace --all-targets 6 | 7 | check: 8 | cargo check --workspace --all-targets 9 | 10 | lint: 11 | cargo clippy --workspace --all-targets 12 | 13 | fmt: 14 | cargo fmt --all 15 | 16 | format: 17 | cargo fmt --all --check 18 | 19 | test: 20 | cargo test --workspace --all-targets 21 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | bitcoincore-rpc 2 | =============== 3 | 4 | Rust client library for the Bitcoin Core daemon's JSON-RPC API. 5 | 6 | Separate `bitcoincore-rpc-json` crate with the JSON-enabled data types used 7 | in the interface of this crate. 8 | 9 | 10 | ## MSRV 11 | 12 | please see the parent README for the current MSRV. 13 | 14 | # License 15 | 16 | All code is licensed using the CC0 license, as per the LICENSE file. 17 | -------------------------------------------------------------------------------- /client/contrib/extra_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euox pipefail 4 | 5 | # Currently there are two problems with the examples in this repo in relation to the `test_vas.sh` script. 6 | # 7 | # 1. `retry_client` cannot be run from `run_task.sh` because the `EXAMPLES` var 8 | # expects a feature and the `client` crate has no features, not even "default". 9 | # 2. `test_against_node` is not meant to be run. 10 | # 11 | # Therefore we just build the examples. 12 | cargo build --verbose --examples 13 | -------------------------------------------------------------------------------- /json/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ord-bitcoincore-rpc-json" 3 | version = "0.19.0" 4 | authors = [ 5 | "Steven Roose ", 6 | "Jean Pierre Dudey ", 7 | "Dawid Ciężarkiewicz " 8 | ] 9 | license = "CC0-1.0" 10 | homepage = "https://github.com/rust-bitcoin/rust-bitcoincore-rpc/" 11 | repository = "https://github.com/rust-bitcoin/rust-bitcoincore-rpc/" 12 | description = "JSON-enabled type structs for bitcoincore-rpc crate." 13 | keywords = [ "crypto", "bitcoin", "bitcoin-core", "rpc" ] 14 | readme = "README.md" 15 | edition = "2021" 16 | rust-version = "1.56.1" 17 | 18 | [lib] 19 | name = "bitcoincore_rpc_json" 20 | path = "src/lib.rs" 21 | 22 | [features] 23 | default = ["rand"] 24 | rand = ["bitcoin/rand-std"] 25 | 26 | [dependencies] 27 | serde = { version = "1", features = [ "derive" ] } 28 | serde_json = "1" 29 | 30 | bitcoin = { version = "0.32.0", features = ["serde"] } 31 | -------------------------------------------------------------------------------- /client/src/lib.rs: -------------------------------------------------------------------------------- 1 | // To the extent possible under law, the author(s) have dedicated all 2 | // copyright and related and neighboring rights to this software to 3 | // the public domain worldwide. This software is distributed without 4 | // any warranty. 5 | // 6 | // You should have received a copy of the CC0 Public Domain Dedication 7 | // along with this software. 8 | // If not, see . 9 | // 10 | 11 | //! # Rust Client for Bitcoin Core API 12 | //! 13 | //! This is a client library for the Bitcoin Core JSON-RPC API. 14 | //! 15 | 16 | #![crate_name = "bitcoincore_rpc"] 17 | #![crate_type = "rlib"] 18 | 19 | #[macro_use] 20 | extern crate log; 21 | #[macro_use] // `macro_use` is needed for v1.24.0 compilation. 22 | extern crate serde; 23 | 24 | pub extern crate jsonrpc; 25 | 26 | pub extern crate bitcoincore_rpc_json; 27 | pub use crate::json::bitcoin; 28 | pub use bitcoincore_rpc_json as json; 29 | 30 | mod client; 31 | mod error; 32 | mod queryable; 33 | 34 | pub use crate::client::*; 35 | pub use crate::error::Error; 36 | pub use crate::queryable::*; 37 | -------------------------------------------------------------------------------- /client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ord-bitcoincore-rpc" 3 | version = "0.19.0" 4 | authors = [ 5 | "Steven Roose ", 6 | "Jean Pierre Dudey ", 7 | "Dawid Ciężarkiewicz ", 8 | ] 9 | license = "CC0-1.0" 10 | homepage = "https://github.com/rust-bitcoin/rust-bitcoincore-rpc/" 11 | repository = "https://github.com/rust-bitcoin/rust-bitcoincore-rpc/" 12 | description = "RPC client library for the Bitcoin Core JSON-RPC API." 13 | keywords = ["crypto", "bitcoin", "bitcoin-core", "rpc"] 14 | readme = "README.md" 15 | edition = "2018" 16 | 17 | [lib] 18 | name = "bitcoincore_rpc" 19 | path = "src/lib.rs" 20 | 21 | [features] 22 | default = ["rand"] 23 | rand = ["ord-bitcoincore-rpc-json/rand"] 24 | 25 | [dependencies] 26 | ord-bitcoincore-rpc-json = { version = "0.19.0", path = "../json" } 27 | 28 | log = "0.4.5" 29 | jsonrpc = { version = "0.18.0", features = ["minreq_http"] } 30 | 31 | # Used for deserialization of JSON. 32 | serde = "1" 33 | serde_json = "1" 34 | 35 | [dev-dependencies] 36 | tempfile = "3.3.0" 37 | 38 | [[example]] 39 | name = "retry_client" 40 | -------------------------------------------------------------------------------- /contrib/integration_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Run the integration test optionally downloading Bitcoin Core binary if BITCOINVERSION is set. 4 | 5 | set -euo pipefail 6 | 7 | REPO_DIR=$(git rev-parse --show-toplevel) 8 | 9 | # Make all cargo invocations verbose. 10 | export CARGO_TERM_VERBOSE=true 11 | 12 | main() { 13 | # If a specific version of Bitcoin Core is set then download the binary. 14 | if [ -n "${BITCOINVERSION+x}" ]; then 15 | download_binary 16 | fi 17 | 18 | need_cmd bitcoind 19 | 20 | cd integration_test 21 | ./run.sh 22 | } 23 | 24 | download_binary() { 25 | wget https://bitcoincore.org/bin/bitcoin-core-$BITCOINVERSION/bitcoin-$BITCOINVERSION-x86_64-linux-gnu.tar.gz 26 | tar -xzvf bitcoin-$BITCOINVERSION-x86_64-linux-gnu.tar.gz 27 | export PATH=$PATH:$(pwd)/bitcoin-$BITCOINVERSION/bin 28 | } 29 | 30 | err() { 31 | echo "$1" >&2 32 | exit 1 33 | } 34 | 35 | need_cmd() { 36 | if ! command -v "$1" > /dev/null 2>&1 37 | then err "need '$1' (command not found)" 38 | fi 39 | } 40 | 41 | # 42 | # Main script 43 | # 44 | main "$@" 45 | exit 0 46 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | defaults: 12 | run: 13 | shell: bash 14 | 15 | env: 16 | RUSTFLAGS: --deny warnings 17 | 18 | jobs: 19 | lint: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | - name: Install Rust Toolchain Components 26 | uses: actions-rs/toolchain@v1 27 | with: 28 | components: clippy, rustfmt 29 | override: true 30 | toolchain: stable 31 | 32 | - uses: Swatinem/rust-cache@v2 33 | 34 | - name: Clippy 35 | run: cargo clippy --all --all-targets 36 | 37 | - name: Format 38 | run: cargo fmt --all -- --check 39 | 40 | test: 41 | runs-on: ubuntu-latest 42 | 43 | steps: 44 | - uses: actions/checkout@v2 45 | 46 | - name: Install Rust Toolchain Components 47 | uses: actions-rs/toolchain@v1 48 | with: 49 | profile: minimal 50 | toolchain: 1.79.0 51 | 52 | - uses: Swatinem/rust-cache@v2 53 | 54 | - name: Unit Tests 55 | run: cargo test --all 56 | 57 | - name: Integration Tests 58 | env: 59 | BITCOINVERSION: 0.21.0 60 | run: ./contrib/integration_test.sh 61 | -------------------------------------------------------------------------------- /integration_test/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TESTDIR=/tmp/rust_bitcoincore_rpc_test 4 | 5 | rm -rf ${TESTDIR} 6 | mkdir -p ${TESTDIR}/1 ${TESTDIR}/2 7 | 8 | # To kill any remaining open bitcoind. 9 | killall -9 bitcoind 10 | 11 | bitcoind -regtest \ 12 | -datadir=${TESTDIR}/1 \ 13 | -port=12348 \ 14 | -server=0 \ 15 | -printtoconsole=0 & 16 | PID1=$! 17 | 18 | # Make sure it's listening on its p2p port. 19 | sleep 3 20 | 21 | BLOCKFILTERARG="" 22 | if bitcoind -version | grep -q "v0\.\(19\|2\)"; then 23 | BLOCKFILTERARG="-blockfilterindex=1" 24 | fi 25 | 26 | FALLBACKFEEARG="" 27 | if bitcoind -version | grep -q "v0\.2"; then 28 | FALLBACKFEEARG="-fallbackfee=0.00001000" 29 | fi 30 | 31 | bitcoind -regtest $BLOCKFILTERARG $FALLBACKFEEARG \ 32 | -datadir=${TESTDIR}/2 \ 33 | -connect=127.0.0.1:12348 \ 34 | -rpcport=12349 \ 35 | -server=1 \ 36 | -txindex=1 \ 37 | -printtoconsole=0 \ 38 | -zmqpubrawblock=tcp://0.0.0.0:28332 \ 39 | -zmqpubrawtx=tcp://0.0.0.0:28333 & 40 | PID2=$! 41 | 42 | # Let it connect to the other node. 43 | sleep 5 44 | 45 | RPC_URL=http://localhost:12349 \ 46 | RPC_COOKIE=${TESTDIR}/2/regtest/.cookie \ 47 | TESTDIR=${TESTDIR} \ 48 | cargo run 49 | 50 | RESULT=$? 51 | 52 | kill -9 $PID1 $PID2 53 | 54 | exit $RESULT 55 | -------------------------------------------------------------------------------- /client/examples/retry_client.rs: -------------------------------------------------------------------------------- 1 | // To the extent possible under law, the author(s) have dedicated all 2 | // copyright and related and neighboring rights to this software to 3 | // the public domain worldwide. This software is distributed without 4 | // any warranty. 5 | // 6 | // You should have received a copy of the CC0 Public Domain Dedication 7 | // along with this software. 8 | // If not, see . 9 | // 10 | 11 | extern crate bitcoincore_rpc; 12 | extern crate jsonrpc; 13 | extern crate serde; 14 | extern crate serde_json; 15 | 16 | use bitcoincore_rpc::{Client, Error, Result, RpcApi}; 17 | 18 | pub struct RetryClient { 19 | client: Client, 20 | } 21 | 22 | const INTERVAL: u64 = 1000; 23 | const RETRY_ATTEMPTS: u8 = 10; 24 | 25 | impl RpcApi for RetryClient { 26 | fn call serde::de::Deserialize<'a>>( 27 | &self, 28 | cmd: &str, 29 | args: &[serde_json::Value], 30 | ) -> Result { 31 | for _ in 0..RETRY_ATTEMPTS { 32 | match self.client.call(cmd, args) { 33 | Ok(ret) => return Ok(ret), 34 | Err(Error::JsonRpc(jsonrpc::error::Error::Rpc(ref rpcerr))) 35 | if rpcerr.code == -28 => 36 | { 37 | ::std::thread::sleep(::std::time::Duration::from_millis(INTERVAL)); 38 | continue; 39 | } 40 | Err(e) => return Err(e), 41 | } 42 | } 43 | self.client.call(cmd, args) 44 | } 45 | } 46 | 47 | fn main() {} 48 | -------------------------------------------------------------------------------- /client/src/queryable.rs: -------------------------------------------------------------------------------- 1 | // To the extent possible under law, the author(s) have dedicated all 2 | // copyright and related and neighboring rights to this software to 3 | // the public domain worldwide. This software is distributed without 4 | // any warranty. 5 | // 6 | // You should have received a copy of the CC0 Public Domain Dedication 7 | // along with this software. 8 | // If not, see . 9 | // 10 | 11 | use crate::bitcoin; 12 | 13 | use crate::client::Result; 14 | use crate::client::RpcApi; 15 | 16 | /// A type that can be queried from Bitcoin Core. 17 | pub trait Queryable: Sized { 18 | /// Type of the ID used to query the item. 19 | type Id; 20 | /// Query the item using `rpc` and convert to `Self`. 21 | fn query(rpc: &C, id: &Self::Id) -> Result; 22 | } 23 | 24 | impl Queryable for bitcoin::block::Block { 25 | type Id = bitcoin::BlockHash; 26 | 27 | fn query(rpc: &C, id: &Self::Id) -> Result { 28 | let rpc_name = "getblock"; 29 | let hex: String = rpc.call(rpc_name, &[serde_json::to_value(id)?, 0.into()])?; 30 | Ok(bitcoin::consensus::encode::deserialize_hex(&hex)?) 31 | } 32 | } 33 | 34 | impl Queryable for bitcoin::transaction::Transaction { 35 | type Id = bitcoin::Txid; 36 | 37 | fn query(rpc: &C, id: &Self::Id) -> Result { 38 | let rpc_name = "getrawtransaction"; 39 | let hex: String = rpc.call(rpc_name, &[serde_json::to_value(id)?])?; 40 | Ok(bitcoin::consensus::encode::deserialize_hex(&hex)?) 41 | } 42 | } 43 | 44 | impl Queryable for Option { 45 | type Id = bitcoin::OutPoint; 46 | 47 | fn query(rpc: &C, id: &Self::Id) -> Result { 48 | rpc.get_tx_out(&id.txid, id.vout, Some(true)) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /client/examples/test_against_node.rs: -------------------------------------------------------------------------------- 1 | // To the extent possible under law, the author(s) have dedicated all 2 | // copyright and related and neighboring rights to this software to 3 | // the public domain worldwide. This software is distributed without 4 | // any warranty. 5 | // 6 | // You should have received a copy of the CC0 Public Domain Dedication 7 | // along with this software. 8 | // If not, see . 9 | // 10 | 11 | //! A very simple example used as a self-test of this library against a Bitcoin 12 | //! Core node. 13 | extern crate bitcoincore_rpc; 14 | 15 | use bitcoincore_rpc::{bitcoin, Auth, Client, Error, RpcApi}; 16 | 17 | fn main_result() -> Result<(), Error> { 18 | let mut args = std::env::args(); 19 | 20 | let _exe_name = args.next().unwrap(); 21 | 22 | let url = args.next().expect("Usage: "); 23 | let user = args.next().expect("no user given"); 24 | let pass = args.next().expect("no pass given"); 25 | 26 | let rpc = Client::new(&url, Auth::UserPass(user, pass)).unwrap(); 27 | 28 | let _blockchain_info = rpc.get_blockchain_info()?; 29 | 30 | let best_block_hash = rpc.get_best_block_hash()?; 31 | println!("best block hash: {}", best_block_hash); 32 | let bestblockcount = rpc.get_block_count()?; 33 | println!("best block height: {}", bestblockcount); 34 | let best_block_hash_by_height = rpc.get_block_hash(bestblockcount)?; 35 | println!("best block hash by height: {}", best_block_hash_by_height); 36 | assert_eq!(best_block_hash_by_height, best_block_hash); 37 | 38 | let bitcoin_block: bitcoin::Block = rpc.get_by_id(&best_block_hash)?; 39 | println!("best block hash by `get`: {}", bitcoin_block.header.prev_blockhash); 40 | let bitcoin_tx: bitcoin::Transaction = 41 | rpc.get_by_id(&bitcoin_block.txdata[0].compute_txid())?; 42 | println!("tx by `get`: {}", bitcoin_tx.compute_txid()); 43 | 44 | Ok(()) 45 | } 46 | 47 | fn main() { 48 | main_result().unwrap(); 49 | } 50 | -------------------------------------------------------------------------------- /client/src/error.rs: -------------------------------------------------------------------------------- 1 | // To the extent possible under law, the author(s) have dedicated all 2 | // copyright and related and neighboring rights to this software to 3 | // the public domain worldwide. This software is distributed without 4 | // any warranty. 5 | // 6 | // You should have received a copy of the CC0 Public Domain Dedication 7 | // along with this software. 8 | // If not, see . 9 | // 10 | 11 | use std::{error, fmt, io}; 12 | 13 | use crate::bitcoin; 14 | use crate::bitcoin::hashes::hex; 15 | use crate::bitcoin::secp256k1; 16 | 17 | /// The error type for errors produced in this library. 18 | #[derive(Debug)] 19 | pub enum Error { 20 | JsonRpc(jsonrpc::error::Error), 21 | Hex(hex::HexToBytesError), 22 | Json(serde_json::error::Error), 23 | BitcoinSerialization(bitcoin::consensus::encode::FromHexError), 24 | Secp256k1(secp256k1::Error), 25 | Io(io::Error), 26 | InvalidAmount(bitcoin::amount::ParseAmountError), 27 | InvalidCookieFile, 28 | /// The JSON result had an unexpected structure. 29 | UnexpectedStructure, 30 | /// The daemon returned an error string. 31 | ReturnedError(String), 32 | } 33 | 34 | impl From for Error { 35 | fn from(e: jsonrpc::error::Error) -> Error { 36 | Error::JsonRpc(e) 37 | } 38 | } 39 | 40 | impl From for Error { 41 | fn from(e: hex::HexToBytesError) -> Error { 42 | Error::Hex(e) 43 | } 44 | } 45 | 46 | impl From for Error { 47 | fn from(e: serde_json::error::Error) -> Error { 48 | Error::Json(e) 49 | } 50 | } 51 | 52 | impl From for Error { 53 | fn from(e: bitcoin::consensus::encode::FromHexError) -> Error { 54 | Error::BitcoinSerialization(e) 55 | } 56 | } 57 | 58 | impl From for Error { 59 | fn from(e: secp256k1::Error) -> Error { 60 | Error::Secp256k1(e) 61 | } 62 | } 63 | 64 | impl From for Error { 65 | fn from(e: io::Error) -> Error { 66 | Error::Io(e) 67 | } 68 | } 69 | 70 | impl From for Error { 71 | fn from(e: bitcoin::amount::ParseAmountError) -> Error { 72 | Error::InvalidAmount(e) 73 | } 74 | } 75 | 76 | impl fmt::Display for Error { 77 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 78 | match *self { 79 | Error::JsonRpc(ref e) => write!(f, "JSON-RPC error: {}", e), 80 | Error::Hex(ref e) => write!(f, "hex decode error: {}", e), 81 | Error::Json(ref e) => write!(f, "JSON error: {}", e), 82 | Error::BitcoinSerialization(ref e) => write!(f, "Bitcoin serialization error: {}", e), 83 | Error::Secp256k1(ref e) => write!(f, "secp256k1 error: {}", e), 84 | Error::Io(ref e) => write!(f, "I/O error: {}", e), 85 | Error::InvalidAmount(ref e) => write!(f, "invalid amount: {}", e), 86 | Error::InvalidCookieFile => write!(f, "invalid cookie file"), 87 | Error::UnexpectedStructure => write!(f, "the JSON result had an unexpected structure"), 88 | Error::ReturnedError(ref s) => write!(f, "the daemon returned an error string: {}", s), 89 | } 90 | } 91 | } 92 | 93 | impl error::Error for Error { 94 | fn description(&self) -> &str { 95 | "bitcoincore-rpc error" 96 | } 97 | 98 | fn cause(&self) -> Option<&dyn error::Error> { 99 | match *self { 100 | Error::JsonRpc(ref e) => Some(e), 101 | Error::Hex(ref e) => Some(e), 102 | Error::Json(ref e) => Some(e), 103 | Error::BitcoinSerialization(ref e) => Some(e), 104 | Error::Secp256k1(ref e) => Some(e), 105 | Error::Io(ref e) => Some(e), 106 | _ => None, 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /client/LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | 123 | -------------------------------------------------------------------------------- /json/LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | 123 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.19.0 2 | 3 | - Change MSRV from 1.48.0 to 1.56.1 [#334](https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/334) 4 | - Implement `verifymessage` RCP call (and add "verifymessage" feature) 5 | - [#326](https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/326) 6 | - [#343](https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/343) 7 | - Upgrade `bitcoin` dependency to `v0.32.0` [#337](https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/337) 8 | - Upgrade `jsonrpc` dependency to `v0.18.0` [#339](https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/339) 9 | - Use `jsonrpc` "minreq_http" feature [#341](https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/341) 10 | - Add "rand" feature [#342](https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/342) 11 | 12 | # 0.18.0 13 | 14 | - MSRV changed from 1.41.1 to 1.48.0 15 | - Use `bitcoin::Network` in `GetBlockchainInfoResult `. 16 | - Make checksum optional in `GetDescriptorInfoResult`. 17 | - Make `getmempoolinfo` compatible with supported RPC versions. 18 | 19 | # 0.17.0 20 | 21 | - add `list_wallet_dir` rpc 22 | - add `join_psbt` rpc 23 | - add `get_raw_change_address` rpc 24 | - add `create_psbt` rpc 25 | - add `combine_raw_transaction` rpc 26 | - add `decode_raw_transaction` rpc 27 | - add `import_descriptors` rpc 28 | - add `get_mempool_info` rpc 29 | - add `get_index_info` rpc 30 | - change return type of `unload_wallet` 31 | - update `jsonrpc` dependency to 0.14.0 32 | - update `bitcoin` dependency to 0.30.0 33 | 34 | # 0.16.0 35 | 36 | - MSRV changed from 1.29 to 1.41.1 37 | - bump bitcoin crate version to 0.29.0 38 | - moved to Rust edition 2018 39 | - make get_tx_out_set_info compatible with v22+ 40 | - add `submit_block`, `submit_block_bytes`, `submit_block_hex` 41 | 42 | # 0.15.0 43 | 44 | - bump bitcoin crate version to 0.28.0 45 | - add `get_block_stats` 46 | - add `add_node` 47 | - add `remove_node` 48 | - add `onetry_node` 49 | - add `disconnect_node` 50 | - add `disconnect_node_by_id` 51 | - add `get_added_node_info` 52 | - add `get_node_addresses` 53 | - add `list_banned` 54 | - add `clear_banned` 55 | - add `add_ban` 56 | - add `remove_ban` 57 | - make `Auth::get_user_pass` public 58 | - add `ScriptPubkeyType::witness_v1_taproot` 59 | 60 | # 0.14.0 61 | 62 | - add `wallet_conflicts` field in `WalletTxInfo` 63 | - add `get_chain_tips` 64 | - add `get_block_template` 65 | - implement `From` and `From>` for `ImportMultiRescanSince` 66 | - bump rust-bitcoin dependency to 0.27 67 | - bump json-rpc dependency to 0.12.0 68 | - remove dependency on `hex` 69 | 70 | # 0.13.0 71 | 72 | - add `wallet_process_psbt` 73 | - add `unlock_unspent_all` 74 | - compatibility with Bitcoin Core v0.21 75 | - bump rust-bitcoin dependency to 0.26 76 | - implement Deserialize for ImportMultiRescanSince 77 | - some fixes for some negative confirmation values 78 | 79 | # 0.12.0 80 | 81 | - bump `bitcoin` dependency to version `0.25`, increasing our MSRV to `1.29.0` 82 | - test against `bitcoind` `0.20.0` and `0.20.1` 83 | - add `get_balances` 84 | - add `get_mempool_entry` 85 | - add `list_since_block` 86 | - add `get_mempool_entry` 87 | - add `list_since_block` 88 | - add `uptime` 89 | - add `get_network_hash_ps` 90 | - add `get_tx_out_set_info` 91 | - add `get_net_totals` 92 | - partially implement `scantxoutset` 93 | - extend `create_wallet` and related APIs 94 | - extend `GetWalletInfoResult` 95 | - extend `WalletTxInfo` 96 | - extend testsuite 97 | - fix `GetPeerInfoResult` 98 | - fix `GetNetworkInfoResult` 99 | - fix `GetTransactionResultDetailCategory` 100 | - fix `GetMempoolEntryResult` for bitcoind prior to `0.19.0` 101 | - fix `GetBlockResult` and `GetBlockHeaderResult` 102 | 103 | # 0.11.0 104 | 105 | - fix `minimum_sum_amount` field name in `ListUnspentQueryOptions` 106 | - add missing "orphan" variant for `GetTransactionResultDetailCategory` 107 | - add `ImportMultiRescanSince` to support "now" for `importmulti`'s 108 | `timestamp` parameter 109 | - rename logging target to `bitcoincore_rpc` instead of `bitcoincore_rpc::client` 110 | - other logging improvements 111 | 112 | # 0.10.0 113 | 114 | - rename `dump_priv_key` -> `dump_private_key` + change return type 115 | - rename `get_block_header_xxx` methods to conform with `get_block_xxx` methods 116 | - rename `get_raw_transaction_xxx` methods to conform with `get_block_xxx` methods 117 | - rename `GetBlockHeaderResult` fields 118 | - rename `GetMiningInfoResult` fields 119 | - represent difficulty values as `f64` instead of `BigUint` 120 | - fix `get_peer_info` 121 | - fix `get_transaction` 122 | - fix `get_balance` 123 | - fix `get_blockchain_info` and make compatible with both 0.18 and 0.19 124 | - fix `get_address_info` 125 | - fix `send_to_address` 126 | - fix `estimate_smart_fee` 127 | - fix `import_private_key` 128 | - fix `list_received_by_address` 129 | - fix `import_address` 130 | - fix `finalize_psbt` 131 | - fix `fund_raw_transaction` 132 | - fix `test_mempool_accept` 133 | - fix `stop` 134 | - fix `rescan_blockchain` 135 | - add `import_address_script` 136 | - add `get_network_info` 137 | - add `version` 138 | - add `Error::UnexpectedStructure` 139 | - add `GetTransactionResultDetailCategory::Immature` 140 | - make `list_unspent` more ergonomic 141 | - made all exported enum types implement `Copy` 142 | - export `jsonrpc` dependency. 143 | - remove `num_bigint` dependency 144 | 145 | # v0.9.1 146 | 147 | - Add `wallet_create_funded_psbt` 148 | - Add `get_descriptor_info` 149 | - Add `combine_psbt` 150 | - Add `derive_addresses` 151 | - Add `finalize_psbt` 152 | - Add `rescan_blockchain` 153 | 154 | # v0.7.0 155 | 156 | - use `bitcoin::PublicKey` instead of `secp256k1::PublicKey` 157 | - fix get_mining_info result issue 158 | - fix test_mempool_accept issue 159 | - fix get_transaction result issues 160 | - fix bug in fund_raw_transaction 161 | - add list_transactions 162 | - add get_raw_mempool 163 | - add reconsider_block 164 | - add import_multi 165 | - add import_public_key 166 | - add set_label 167 | - add lock_unspent 168 | - add unlock_unspent 169 | - add create_wallet 170 | - add load_wallet 171 | - add unload_wallet 172 | - increased log level for requests to debug 173 | 174 | # v0.6.0 175 | 176 | - polish Auth to use owned Strings 177 | - fix using Amount type and Address types where needed 178 | - use references of sha256d::Hashes instead of owned/copied 179 | 180 | # v0.5.1 181 | 182 | - add get_tx_out_proof 183 | - add import_address 184 | - add list_received_by_address 185 | 186 | # v0.5.0 187 | 188 | - add support for cookie authentication 189 | - add fund_raw_transaction command 190 | - deprecate sign_raw_transaction 191 | - use PrivateKey type for calls instead of string 192 | - fix for sign_raw_transaction 193 | - use 32-bit integers for confirmations, signed when needed 194 | 195 | # v0.4.0 196 | 197 | - add RawTx trait for commands that take raw transactions 198 | - update jsonrpc dependency to v0.11.0 199 | - fix for create_raw_transaction 200 | - fix for send_to_address 201 | - fix for get_new_address 202 | - fix for get_tx_out 203 | - fix for get_raw_transaction_verbose 204 | - use `secp256k1::SecretKey` type in API 205 | 206 | # v0.3.0 207 | 208 | - removed the GetTransaction and GetScript traits 209 | (those methods are now directly implemented on types) 210 | - introduce RpcApi trait 211 | - use bitcoin_hashes library 212 | - add signrawtransactionwithkey command 213 | - add testmempoolaccept command 214 | - add generate command 215 | - improve hexadecimal byte value representation 216 | - bugfix getrawtransaction (support coinbase txs) 217 | - update rust-bitcoin dependency v0.16.0 -> v0.18.0 218 | - add RetryClient example 219 | 220 | # v0.2.0 221 | 222 | - add send_to_address command 223 | - add create_raw_transaction command 224 | - Client methods take self without mut 225 | -------------------------------------------------------------------------------- /client/src/client.rs: -------------------------------------------------------------------------------- 1 | // To the extent possible under law, the author(s) have dedicated all 2 | // copyright and related and neighboring rights to this software to 3 | // the public domain worldwide. This software is distributed without 4 | // any warranty. 5 | // 6 | // You should have received a copy of the CC0 Public Domain Dedication 7 | // along with this software. 8 | // If not, see . 9 | // 10 | 11 | use std::collections::HashMap; 12 | use std::fs::File; 13 | use std::io::{BufRead, BufReader}; 14 | use std::iter::FromIterator; 15 | use std::path::PathBuf; 16 | use std::{fmt, result}; 17 | 18 | use crate::bitcoin; 19 | use crate::bitcoin::consensus::encode; 20 | use bitcoin::hex::DisplayHex; 21 | 22 | use crate::bitcoin::address::{NetworkChecked, NetworkUnchecked}; 23 | use crate::bitcoin::hashes::hex::FromHex; 24 | use crate::bitcoin::secp256k1::ecdsa::Signature; 25 | use crate::bitcoin::{ 26 | Address, Amount, Block, OutPoint, PrivateKey, PublicKey, Script, Transaction, 27 | }; 28 | use log::Level::{Debug, Trace, Warn}; 29 | 30 | use crate::error::*; 31 | use crate::json; 32 | use crate::queryable; 33 | 34 | /// Crate-specific Result type, shorthand for `std::result::Result` with our 35 | /// crate-specific Error type; 36 | pub type Result = result::Result; 37 | 38 | /// Outpoint that serializes and deserializes as a map, instead of a string, 39 | /// for use as RPC arguments 40 | #[derive(Clone, Debug, Serialize, Deserialize)] 41 | pub struct JsonOutPoint { 42 | pub txid: bitcoin::Txid, 43 | pub vout: u32, 44 | } 45 | 46 | impl From for JsonOutPoint { 47 | fn from(o: OutPoint) -> JsonOutPoint { 48 | JsonOutPoint { 49 | txid: o.txid, 50 | vout: o.vout, 51 | } 52 | } 53 | } 54 | 55 | impl From for OutPoint { 56 | fn from(outpoint: JsonOutPoint) -> OutPoint { 57 | OutPoint { 58 | txid: outpoint.txid, 59 | vout: outpoint.vout, 60 | } 61 | } 62 | } 63 | 64 | /// Shorthand for converting a variable into a serde_json::Value. 65 | fn into_json(val: T) -> Result 66 | where 67 | T: serde::ser::Serialize, 68 | { 69 | Ok(serde_json::to_value(val)?) 70 | } 71 | 72 | /// Shorthand for converting an Option into an Option. 73 | fn opt_into_json(opt: Option) -> Result 74 | where 75 | T: serde::ser::Serialize, 76 | { 77 | match opt { 78 | Some(val) => Ok(into_json(val)?), 79 | None => Ok(serde_json::Value::Null), 80 | } 81 | } 82 | 83 | /// Shorthand for `serde_json::Value::Null`. 84 | fn null() -> serde_json::Value { 85 | serde_json::Value::Null 86 | } 87 | 88 | /// Shorthand for an empty serde_json::Value array. 89 | fn empty_arr() -> serde_json::Value { 90 | serde_json::Value::Array(vec![]) 91 | } 92 | 93 | /// Shorthand for an empty serde_json object. 94 | fn empty_obj() -> serde_json::Value { 95 | serde_json::Value::Object(Default::default()) 96 | } 97 | 98 | /// Handle default values in the argument list 99 | /// 100 | /// Substitute `Value::Null`s with corresponding values from `defaults` table, 101 | /// except when they are trailing, in which case just skip them altogether 102 | /// in returned list. 103 | /// 104 | /// Note, that `defaults` corresponds to the last elements of `args`. 105 | /// 106 | /// ```norust 107 | /// arg1 arg2 arg3 arg4 108 | /// def1 def2 109 | /// ``` 110 | /// 111 | /// Elements of `args` without corresponding `defaults` value, won't 112 | /// be substituted, because they are required. 113 | fn handle_defaults<'a>( 114 | args: &'a mut [serde_json::Value], 115 | defaults: &[serde_json::Value], 116 | ) -> &'a [serde_json::Value] { 117 | assert!(args.len() >= defaults.len()); 118 | 119 | // Pass over the optional arguments in backwards order, filling in defaults after the first 120 | // non-null optional argument has been observed. 121 | let mut first_non_null_optional_idx = None; 122 | for i in 0..defaults.len() { 123 | let args_i = args.len() - 1 - i; 124 | let defaults_i = defaults.len() - 1 - i; 125 | if args[args_i] == serde_json::Value::Null { 126 | if first_non_null_optional_idx.is_some() { 127 | if defaults[defaults_i] == serde_json::Value::Null { 128 | panic!("Missing `default` for argument idx {}", args_i); 129 | } 130 | args[args_i] = defaults[defaults_i].clone(); 131 | } 132 | } else if first_non_null_optional_idx.is_none() { 133 | first_non_null_optional_idx = Some(args_i); 134 | } 135 | } 136 | 137 | let required_num = args.len() - defaults.len(); 138 | 139 | if let Some(i) = first_non_null_optional_idx { 140 | &args[..i + 1] 141 | } else { 142 | &args[..required_num] 143 | } 144 | } 145 | 146 | /// Convert a possible-null result into an Option. 147 | fn opt_result serde::de::Deserialize<'a>>( 148 | result: serde_json::Value, 149 | ) -> Result> { 150 | if result == serde_json::Value::Null { 151 | Ok(None) 152 | } else { 153 | Ok(serde_json::from_value(result)?) 154 | } 155 | } 156 | 157 | /// Used to pass raw txs into the API. 158 | pub trait RawTx: Sized + Clone { 159 | fn raw_hex(self) -> String; 160 | } 161 | 162 | impl<'a> RawTx for &'a Transaction { 163 | fn raw_hex(self) -> String { 164 | encode::serialize_hex(self) 165 | } 166 | } 167 | 168 | impl<'a> RawTx for &'a [u8] { 169 | fn raw_hex(self) -> String { 170 | self.to_lower_hex_string() 171 | } 172 | } 173 | 174 | impl<'a> RawTx for &'a Vec { 175 | fn raw_hex(self) -> String { 176 | self.to_lower_hex_string() 177 | } 178 | } 179 | 180 | impl<'a> RawTx for &'a str { 181 | fn raw_hex(self) -> String { 182 | self.to_owned() 183 | } 184 | } 185 | 186 | impl RawTx for String { 187 | fn raw_hex(self) -> String { 188 | self 189 | } 190 | } 191 | 192 | /// The different authentication methods for the client. 193 | #[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] 194 | pub enum Auth { 195 | None, 196 | UserPass(String, String), 197 | CookieFile(PathBuf), 198 | } 199 | 200 | impl Auth { 201 | /// Convert into the arguments that jsonrpc::Client needs. 202 | pub fn get_user_pass(self) -> Result<(Option, Option)> { 203 | match self { 204 | Auth::None => Ok((None, None)), 205 | Auth::UserPass(u, p) => Ok((Some(u), Some(p))), 206 | Auth::CookieFile(path) => { 207 | let line = BufReader::new(File::open(path)?) 208 | .lines() 209 | .next() 210 | .ok_or(Error::InvalidCookieFile)??; 211 | let colon = line.find(':').ok_or(Error::InvalidCookieFile)?; 212 | Ok((Some(line[..colon].into()), Some(line[colon + 1..].into()))) 213 | } 214 | } 215 | } 216 | } 217 | 218 | pub trait RpcApi: Sized { 219 | /// Call a `cmd` rpc with given `args` list 220 | fn call serde::de::Deserialize<'a>>( 221 | &self, 222 | cmd: &str, 223 | args: &[serde_json::Value], 224 | ) -> Result; 225 | 226 | /// Query an object implementing `Querable` type 227 | fn get_by_id>( 228 | &self, 229 | id: &>::Id, 230 | ) -> Result { 231 | T::query(self, id) 232 | } 233 | 234 | fn get_network_info(&self) -> Result { 235 | self.call("getnetworkinfo", &[]) 236 | } 237 | 238 | fn get_index_info(&self) -> Result { 239 | self.call("getindexinfo", &[]) 240 | } 241 | 242 | fn version(&self) -> Result { 243 | #[derive(Deserialize)] 244 | struct Response { 245 | pub version: usize, 246 | } 247 | let res: Response = self.call("getnetworkinfo", &[])?; 248 | Ok(res.version) 249 | } 250 | 251 | fn add_multisig_address( 252 | &self, 253 | nrequired: usize, 254 | keys: &[json::PubKeyOrAddress], 255 | label: Option<&str>, 256 | address_type: Option, 257 | ) -> Result { 258 | let mut args = [ 259 | into_json(nrequired)?, 260 | into_json(keys)?, 261 | opt_into_json(label)?, 262 | opt_into_json(address_type)?, 263 | ]; 264 | self.call("addmultisigaddress", handle_defaults(&mut args, &[into_json("")?, null()])) 265 | } 266 | 267 | fn load_wallet(&self, wallet: &str) -> Result { 268 | self.call("loadwallet", &[wallet.into()]) 269 | } 270 | 271 | fn unload_wallet(&self, wallet: Option<&str>) -> Result> { 272 | let mut args = [opt_into_json(wallet)?]; 273 | self.call("unloadwallet", handle_defaults(&mut args, &[null()])) 274 | } 275 | 276 | fn create_wallet( 277 | &self, 278 | wallet: &str, 279 | disable_private_keys: Option, 280 | blank: Option, 281 | passphrase: Option<&str>, 282 | avoid_reuse: Option, 283 | ) -> Result { 284 | let mut args = [ 285 | wallet.into(), 286 | opt_into_json(disable_private_keys)?, 287 | opt_into_json(blank)?, 288 | opt_into_json(passphrase)?, 289 | opt_into_json(avoid_reuse)?, 290 | ]; 291 | self.call( 292 | "createwallet", 293 | handle_defaults(&mut args, &[false.into(), false.into(), into_json("")?, false.into()]), 294 | ) 295 | } 296 | 297 | fn list_wallets(&self) -> Result> { 298 | self.call("listwallets", &[]) 299 | } 300 | 301 | fn list_wallet_dir(&self) -> Result> { 302 | let result: json::ListWalletDirResult = self.call("listwalletdir", &[])?; 303 | let names = result.wallets.into_iter().map(|x| x.name).collect(); 304 | Ok(names) 305 | } 306 | 307 | fn get_wallet_info(&self) -> Result { 308 | self.call("getwalletinfo", &[]) 309 | } 310 | 311 | fn backup_wallet(&self, destination: Option<&str>) -> Result<()> { 312 | let mut args = [opt_into_json(destination)?]; 313 | self.call("backupwallet", handle_defaults(&mut args, &[null()])) 314 | } 315 | 316 | fn dump_private_key(&self, address: &Address) -> Result { 317 | self.call("dumpprivkey", &[address.to_string().into()]) 318 | } 319 | 320 | fn encrypt_wallet(&self, passphrase: &str) -> Result<()> { 321 | self.call("encryptwallet", &[into_json(passphrase)?]) 322 | } 323 | 324 | fn get_difficulty(&self) -> Result { 325 | self.call("getdifficulty", &[]) 326 | } 327 | 328 | fn get_connection_count(&self) -> Result { 329 | self.call("getconnectioncount", &[]) 330 | } 331 | 332 | fn get_block(&self, hash: &bitcoin::BlockHash) -> Result { 333 | let hex: String = self.call("getblock", &[into_json(hash)?, 0.into()])?; 334 | Ok(encode::deserialize_hex(&hex)?) 335 | } 336 | 337 | fn get_block_hex(&self, hash: &bitcoin::BlockHash) -> Result { 338 | self.call("getblock", &[into_json(hash)?, 0.into()]) 339 | } 340 | 341 | fn get_block_info(&self, hash: &bitcoin::BlockHash) -> Result { 342 | self.call("getblock", &[into_json(hash)?, 1.into()]) 343 | } 344 | //TODO(stevenroose) add getblock_txs 345 | 346 | fn get_block_header(&self, hash: &bitcoin::BlockHash) -> Result { 347 | let hex: String = self.call("getblockheader", &[into_json(hash)?, false.into()])?; 348 | Ok(encode::deserialize_hex(&hex)?) 349 | } 350 | 351 | fn get_block_header_info( 352 | &self, 353 | hash: &bitcoin::BlockHash, 354 | ) -> Result { 355 | self.call("getblockheader", &[into_json(hash)?, true.into()]) 356 | } 357 | 358 | fn get_mining_info(&self) -> Result { 359 | self.call("getmininginfo", &[]) 360 | } 361 | 362 | fn get_block_template( 363 | &self, 364 | mode: json::GetBlockTemplateModes, 365 | rules: &[json::GetBlockTemplateRules], 366 | capabilities: &[json::GetBlockTemplateCapabilities], 367 | ) -> Result { 368 | #[derive(Serialize)] 369 | struct Argument<'a> { 370 | mode: json::GetBlockTemplateModes, 371 | rules: &'a [json::GetBlockTemplateRules], 372 | capabilities: &'a [json::GetBlockTemplateCapabilities], 373 | } 374 | 375 | self.call( 376 | "getblocktemplate", 377 | &[into_json(Argument { 378 | mode, 379 | rules, 380 | capabilities, 381 | })?], 382 | ) 383 | } 384 | 385 | /// Returns a data structure containing various state info regarding 386 | /// blockchain processing. 387 | fn get_blockchain_info(&self) -> Result { 388 | let mut raw: serde_json::Value = self.call("getblockchaininfo", &[])?; 389 | // The softfork fields are not backwards compatible: 390 | // - 0.18.x returns a "softforks" array and a "bip9_softforks" map. 391 | // - 0.19.x returns a "softforks" map. 392 | Ok(if self.version()? < 190000 { 393 | use crate::Error::UnexpectedStructure as err; 394 | 395 | // First, remove both incompatible softfork fields. 396 | // We need to scope the mutable ref here for v1.29 borrowck. 397 | let (bip9_softforks, old_softforks) = { 398 | let map = raw.as_object_mut().ok_or(err)?; 399 | let bip9_softforks = map.remove("bip9_softforks").ok_or(err)?; 400 | let old_softforks = map.remove("softforks").ok_or(err)?; 401 | // Put back an empty "softforks" field. 402 | map.insert("softforks".into(), serde_json::Map::new().into()); 403 | (bip9_softforks, old_softforks) 404 | }; 405 | let mut ret: json::GetBlockchainInfoResult = serde_json::from_value(raw)?; 406 | 407 | // Then convert both softfork types and add them. 408 | for sf in old_softforks.as_array().ok_or(err)?.iter() { 409 | let json = sf.as_object().ok_or(err)?; 410 | let id = json.get("id").ok_or(err)?.as_str().ok_or(err)?; 411 | let reject = json.get("reject").ok_or(err)?.as_object().ok_or(err)?; 412 | let active = reject.get("status").ok_or(err)?.as_bool().ok_or(err)?; 413 | ret.softforks.insert( 414 | id.into(), 415 | json::Softfork { 416 | type_: json::SoftforkType::Buried, 417 | bip9: None, 418 | height: None, 419 | active, 420 | }, 421 | ); 422 | } 423 | for (id, sf) in bip9_softforks.as_object().ok_or(err)?.iter() { 424 | #[derive(Deserialize)] 425 | struct OldBip9SoftFork { 426 | pub status: json::Bip9SoftforkStatus, 427 | pub bit: Option, 428 | #[serde(rename = "startTime")] 429 | pub start_time: i64, 430 | pub timeout: u64, 431 | pub since: u32, 432 | pub statistics: Option, 433 | } 434 | let sf: OldBip9SoftFork = serde_json::from_value(sf.clone())?; 435 | ret.softforks.insert( 436 | id.clone(), 437 | json::Softfork { 438 | type_: json::SoftforkType::Bip9, 439 | bip9: Some(json::Bip9SoftforkInfo { 440 | status: sf.status, 441 | bit: sf.bit, 442 | start_time: sf.start_time, 443 | timeout: sf.timeout, 444 | since: sf.since, 445 | statistics: sf.statistics, 446 | }), 447 | height: None, 448 | active: sf.status == json::Bip9SoftforkStatus::Active, 449 | }, 450 | ); 451 | } 452 | ret 453 | } else { 454 | serde_json::from_value(raw)? 455 | }) 456 | } 457 | 458 | /// Returns the numbers of block in the longest chain. 459 | fn get_block_count(&self) -> Result { 460 | self.call("getblockcount", &[]) 461 | } 462 | 463 | /// Returns the hash of the best (tip) block in the longest blockchain. 464 | fn get_best_block_hash(&self) -> Result { 465 | self.call("getbestblockhash", &[]) 466 | } 467 | 468 | /// Get block hash at a given height 469 | fn get_block_hash(&self, height: u64) -> Result { 470 | self.call("getblockhash", &[height.into()]) 471 | } 472 | 473 | fn get_block_stats(&self, height: u64) -> Result { 474 | self.call("getblockstats", &[height.into()]) 475 | } 476 | 477 | fn get_block_stats_fields( 478 | &self, 479 | height: u64, 480 | fields: &[json::BlockStatsFields], 481 | ) -> Result { 482 | self.call("getblockstats", &[height.into(), fields.into()]) 483 | } 484 | 485 | fn get_raw_transaction( 486 | &self, 487 | txid: &bitcoin::Txid, 488 | block_hash: Option<&bitcoin::BlockHash>, 489 | ) -> Result { 490 | let mut args = [into_json(txid)?, into_json(false)?, opt_into_json(block_hash)?]; 491 | let hex: String = self.call("getrawtransaction", handle_defaults(&mut args, &[null()]))?; 492 | Ok(encode::deserialize_hex(&hex)?) 493 | } 494 | 495 | fn get_raw_transaction_hex( 496 | &self, 497 | txid: &bitcoin::Txid, 498 | block_hash: Option<&bitcoin::BlockHash>, 499 | ) -> Result { 500 | let mut args = [into_json(txid)?, into_json(false)?, opt_into_json(block_hash)?]; 501 | self.call("getrawtransaction", handle_defaults(&mut args, &[null()])) 502 | } 503 | 504 | fn get_raw_transaction_info( 505 | &self, 506 | txid: &bitcoin::Txid, 507 | block_hash: Option<&bitcoin::BlockHash>, 508 | ) -> Result { 509 | let mut args = [into_json(txid)?, into_json(true)?, opt_into_json(block_hash)?]; 510 | self.call("getrawtransaction", handle_defaults(&mut args, &[null()])) 511 | } 512 | 513 | fn get_block_filter( 514 | &self, 515 | block_hash: &bitcoin::BlockHash, 516 | ) -> Result { 517 | self.call("getblockfilter", &[into_json(block_hash)?]) 518 | } 519 | 520 | fn get_balance( 521 | &self, 522 | minconf: Option, 523 | include_watchonly: Option, 524 | ) -> Result { 525 | let mut args = ["*".into(), opt_into_json(minconf)?, opt_into_json(include_watchonly)?]; 526 | Ok(Amount::from_btc( 527 | self.call("getbalance", handle_defaults(&mut args, &[0.into(), null()]))?, 528 | )?) 529 | } 530 | 531 | fn get_balances(&self) -> Result { 532 | self.call("getbalances", &[]) 533 | } 534 | 535 | fn get_received_by_address(&self, address: &Address, minconf: Option) -> Result { 536 | let mut args = [address.to_string().into(), opt_into_json(minconf)?]; 537 | Ok(Amount::from_btc( 538 | self.call("getreceivedbyaddress", handle_defaults(&mut args, &[null()]))?, 539 | )?) 540 | } 541 | 542 | fn get_transaction( 543 | &self, 544 | txid: &bitcoin::Txid, 545 | include_watchonly: Option, 546 | ) -> Result { 547 | let mut args = [into_json(txid)?, opt_into_json(include_watchonly)?]; 548 | self.call("gettransaction", handle_defaults(&mut args, &[null()])) 549 | } 550 | 551 | fn list_transactions( 552 | &self, 553 | label: Option<&str>, 554 | count: Option, 555 | skip: Option, 556 | include_watchonly: Option, 557 | ) -> Result> { 558 | let mut args = [ 559 | label.unwrap_or("*").into(), 560 | opt_into_json(count)?, 561 | opt_into_json(skip)?, 562 | opt_into_json(include_watchonly)?, 563 | ]; 564 | self.call("listtransactions", handle_defaults(&mut args, &[10.into(), 0.into(), null()])) 565 | } 566 | 567 | fn list_since_block( 568 | &self, 569 | blockhash: Option<&bitcoin::BlockHash>, 570 | target_confirmations: Option, 571 | include_watchonly: Option, 572 | include_removed: Option, 573 | ) -> Result { 574 | let mut args = [ 575 | opt_into_json(blockhash)?, 576 | opt_into_json(target_confirmations)?, 577 | opt_into_json(include_watchonly)?, 578 | opt_into_json(include_removed)?, 579 | ]; 580 | self.call("listsinceblock", handle_defaults(&mut args, &[null()])) 581 | } 582 | 583 | fn get_tx_out( 584 | &self, 585 | txid: &bitcoin::Txid, 586 | vout: u32, 587 | include_mempool: Option, 588 | ) -> Result> { 589 | let mut args = [into_json(txid)?, into_json(vout)?, opt_into_json(include_mempool)?]; 590 | opt_result(self.call("gettxout", handle_defaults(&mut args, &[null()]))?) 591 | } 592 | 593 | fn get_tx_out_proof( 594 | &self, 595 | txids: &[bitcoin::Txid], 596 | block_hash: Option<&bitcoin::BlockHash>, 597 | ) -> Result> { 598 | let mut args = [into_json(txids)?, opt_into_json(block_hash)?]; 599 | let hex: String = self.call("gettxoutproof", handle_defaults(&mut args, &[null()]))?; 600 | Ok(FromHex::from_hex(&hex)?) 601 | } 602 | 603 | fn import_public_key( 604 | &self, 605 | pubkey: &PublicKey, 606 | label: Option<&str>, 607 | rescan: Option, 608 | ) -> Result<()> { 609 | let mut args = [pubkey.to_string().into(), opt_into_json(label)?, opt_into_json(rescan)?]; 610 | self.call("importpubkey", handle_defaults(&mut args, &[into_json("")?, null()])) 611 | } 612 | 613 | fn import_private_key( 614 | &self, 615 | privkey: &PrivateKey, 616 | label: Option<&str>, 617 | rescan: Option, 618 | ) -> Result<()> { 619 | let mut args = [privkey.to_string().into(), opt_into_json(label)?, opt_into_json(rescan)?]; 620 | self.call("importprivkey", handle_defaults(&mut args, &[into_json("")?, null()])) 621 | } 622 | 623 | fn import_address( 624 | &self, 625 | address: &Address, 626 | label: Option<&str>, 627 | rescan: Option, 628 | ) -> Result<()> { 629 | let mut args = [address.to_string().into(), opt_into_json(label)?, opt_into_json(rescan)?]; 630 | self.call("importaddress", handle_defaults(&mut args, &[into_json("")?, null()])) 631 | } 632 | 633 | fn import_address_script( 634 | &self, 635 | script: &Script, 636 | label: Option<&str>, 637 | rescan: Option, 638 | p2sh: Option, 639 | ) -> Result<()> { 640 | let mut args = [ 641 | script.to_hex_string().into(), 642 | opt_into_json(label)?, 643 | opt_into_json(rescan)?, 644 | opt_into_json(p2sh)?, 645 | ]; 646 | self.call( 647 | "importaddress", 648 | handle_defaults(&mut args, &[into_json("")?, true.into(), null()]), 649 | ) 650 | } 651 | 652 | fn import_multi( 653 | &self, 654 | requests: &[json::ImportMultiRequest], 655 | options: Option<&json::ImportMultiOptions>, 656 | ) -> Result> { 657 | let mut json_requests = Vec::with_capacity(requests.len()); 658 | for req in requests { 659 | json_requests.push(serde_json::to_value(req)?); 660 | } 661 | let mut args = [json_requests.into(), opt_into_json(options)?]; 662 | self.call("importmulti", handle_defaults(&mut args, &[null()])) 663 | } 664 | 665 | fn import_descriptors( 666 | &self, 667 | req: &[json::ImportDescriptors], 668 | ) -> Result> { 669 | let json_request = serde_json::to_value(req)?; 670 | self.call("importdescriptors", handle_defaults(&mut [json_request], &[null()])) 671 | } 672 | 673 | fn set_label(&self, address: &Address, label: &str) -> Result<()> { 674 | self.call("setlabel", &[address.to_string().into(), label.into()]) 675 | } 676 | 677 | fn key_pool_refill(&self, new_size: Option) -> Result<()> { 678 | let mut args = [opt_into_json(new_size)?]; 679 | self.call("keypoolrefill", handle_defaults(&mut args, &[null()])) 680 | } 681 | 682 | fn list_unspent( 683 | &self, 684 | minconf: Option, 685 | maxconf: Option, 686 | addresses: Option<&[&Address]>, 687 | include_unsafe: Option, 688 | query_options: Option, 689 | ) -> Result> { 690 | let mut args = [ 691 | opt_into_json(minconf)?, 692 | opt_into_json(maxconf)?, 693 | opt_into_json(addresses)?, 694 | opt_into_json(include_unsafe)?, 695 | opt_into_json(query_options)?, 696 | ]; 697 | let defaults = [into_json(0)?, into_json(9999999)?, empty_arr(), into_json(true)?, null()]; 698 | self.call("listunspent", handle_defaults(&mut args, &defaults)) 699 | } 700 | 701 | /// To unlock, use [unlock_unspent]. 702 | fn lock_unspent(&self, outputs: &[OutPoint]) -> Result { 703 | let outputs: Vec<_> = 704 | outputs.iter().map(|o| serde_json::to_value(JsonOutPoint::from(*o)).unwrap()).collect(); 705 | self.call("lockunspent", &[false.into(), outputs.into()]) 706 | } 707 | 708 | fn unlock_unspent(&self, outputs: &[OutPoint]) -> Result { 709 | let outputs: Vec<_> = 710 | outputs.iter().map(|o| serde_json::to_value(JsonOutPoint::from(*o)).unwrap()).collect(); 711 | self.call("lockunspent", &[true.into(), outputs.into()]) 712 | } 713 | 714 | /// Unlock all unspent UTXOs. 715 | fn unlock_unspent_all(&self) -> Result { 716 | self.call("lockunspent", &[true.into()]) 717 | } 718 | 719 | fn list_received_by_address( 720 | &self, 721 | address_filter: Option<&Address>, 722 | minconf: Option, 723 | include_empty: Option, 724 | include_watchonly: Option, 725 | ) -> Result> { 726 | let mut args = [ 727 | opt_into_json(minconf)?, 728 | opt_into_json(include_empty)?, 729 | opt_into_json(include_watchonly)?, 730 | opt_into_json(address_filter)?, 731 | ]; 732 | let defaults = [1.into(), false.into(), false.into(), null()]; 733 | self.call("listreceivedbyaddress", handle_defaults(&mut args, &defaults)) 734 | } 735 | 736 | fn create_psbt( 737 | &self, 738 | inputs: &[json::CreateRawTransactionInput], 739 | outputs: &HashMap, 740 | locktime: Option, 741 | replaceable: Option, 742 | ) -> Result { 743 | let outs_converted = serde_json::Map::from_iter( 744 | outputs.iter().map(|(k, v)| (k.clone(), serde_json::Value::from(v.to_btc()))), 745 | ); 746 | self.call( 747 | "createpsbt", 748 | &[ 749 | into_json(inputs)?, 750 | into_json(outs_converted)?, 751 | into_json(locktime)?, 752 | into_json(replaceable)?, 753 | ], 754 | ) 755 | } 756 | 757 | fn create_raw_transaction_hex( 758 | &self, 759 | utxos: &[json::CreateRawTransactionInput], 760 | outs: &HashMap, 761 | locktime: Option, 762 | replaceable: Option, 763 | ) -> Result { 764 | let outs_converted = serde_json::Map::from_iter( 765 | outs.iter().map(|(k, v)| (k.clone(), serde_json::Value::from(v.to_btc()))), 766 | ); 767 | let mut args = [ 768 | into_json(utxos)?, 769 | into_json(outs_converted)?, 770 | opt_into_json(locktime)?, 771 | opt_into_json(replaceable)?, 772 | ]; 773 | let defaults = [into_json(0i64)?, null()]; 774 | self.call("createrawtransaction", handle_defaults(&mut args, &defaults)) 775 | } 776 | 777 | fn create_raw_transaction( 778 | &self, 779 | utxos: &[json::CreateRawTransactionInput], 780 | outs: &HashMap, 781 | locktime: Option, 782 | replaceable: Option, 783 | ) -> Result { 784 | let hex: String = self.create_raw_transaction_hex(utxos, outs, locktime, replaceable)?; 785 | Ok(encode::deserialize_hex(&hex)?) 786 | } 787 | 788 | fn decode_raw_transaction( 789 | &self, 790 | tx: R, 791 | is_witness: Option, 792 | ) -> Result { 793 | let mut args = [tx.raw_hex().into(), opt_into_json(is_witness)?]; 794 | let defaults = [null()]; 795 | self.call("decoderawtransaction", handle_defaults(&mut args, &defaults)) 796 | } 797 | 798 | fn fund_raw_transaction( 799 | &self, 800 | tx: R, 801 | options: Option<&json::FundRawTransactionOptions>, 802 | is_witness: Option, 803 | ) -> Result { 804 | let mut args = [tx.raw_hex().into(), opt_into_json(options)?, opt_into_json(is_witness)?]; 805 | let defaults = [empty_obj(), null()]; 806 | self.call("fundrawtransaction", handle_defaults(&mut args, &defaults)) 807 | } 808 | 809 | #[deprecated] 810 | fn sign_raw_transaction( 811 | &self, 812 | tx: R, 813 | utxos: Option<&[json::SignRawTransactionInput]>, 814 | private_keys: Option<&[PrivateKey]>, 815 | sighash_type: Option, 816 | ) -> Result { 817 | let mut args = [ 818 | tx.raw_hex().into(), 819 | opt_into_json(utxos)?, 820 | opt_into_json(private_keys)?, 821 | opt_into_json(sighash_type)?, 822 | ]; 823 | let defaults = [empty_arr(), empty_arr(), null()]; 824 | self.call("signrawtransaction", handle_defaults(&mut args, &defaults)) 825 | } 826 | 827 | fn sign_raw_transaction_with_wallet( 828 | &self, 829 | tx: R, 830 | utxos: Option<&[json::SignRawTransactionInput]>, 831 | sighash_type: Option, 832 | ) -> Result { 833 | let mut args = [tx.raw_hex().into(), opt_into_json(utxos)?, opt_into_json(sighash_type)?]; 834 | let defaults = [empty_arr(), null()]; 835 | self.call("signrawtransactionwithwallet", handle_defaults(&mut args, &defaults)) 836 | } 837 | 838 | fn sign_raw_transaction_with_key( 839 | &self, 840 | tx: R, 841 | privkeys: &[PrivateKey], 842 | prevtxs: Option<&[json::SignRawTransactionInput]>, 843 | sighash_type: Option, 844 | ) -> Result { 845 | let mut args = [ 846 | tx.raw_hex().into(), 847 | into_json(privkeys)?, 848 | opt_into_json(prevtxs)?, 849 | opt_into_json(sighash_type)?, 850 | ]; 851 | let defaults = [empty_arr(), null()]; 852 | self.call("signrawtransactionwithkey", handle_defaults(&mut args, &defaults)) 853 | } 854 | 855 | fn test_mempool_accept( 856 | &self, 857 | rawtxs: &[R], 858 | ) -> Result> { 859 | let hexes: Vec = 860 | rawtxs.iter().cloned().map(|r| r.raw_hex().into()).collect(); 861 | self.call("testmempoolaccept", &[hexes.into()]) 862 | } 863 | 864 | fn stop(&self) -> Result { 865 | self.call("stop", &[]) 866 | } 867 | 868 | fn verify_message( 869 | &self, 870 | address: &Address, 871 | signature: &Signature, 872 | message: &str, 873 | ) -> Result { 874 | let args = [address.to_string().into(), signature.to_string().into(), into_json(message)?]; 875 | self.call("verifymessage", &args) 876 | } 877 | 878 | /// Generate new address under own control 879 | fn get_new_address( 880 | &self, 881 | label: Option<&str>, 882 | address_type: Option, 883 | ) -> Result> { 884 | self.call("getnewaddress", &[opt_into_json(label)?, opt_into_json(address_type)?]) 885 | } 886 | 887 | /// Generate new address for receiving change 888 | fn get_raw_change_address( 889 | &self, 890 | address_type: Option, 891 | ) -> Result> { 892 | self.call("getrawchangeaddress", &[opt_into_json(address_type)?]) 893 | } 894 | 895 | fn get_address_info(&self, address: &Address) -> Result { 896 | self.call("getaddressinfo", &[address.to_string().into()]) 897 | } 898 | 899 | /// Mine `block_num` blocks and pay coinbase to `address` 900 | /// 901 | /// Returns hashes of the generated blocks 902 | fn generate_to_address( 903 | &self, 904 | block_num: u64, 905 | address: &Address, 906 | ) -> Result> { 907 | self.call("generatetoaddress", &[block_num.into(), address.to_string().into()]) 908 | } 909 | 910 | /// Mine up to block_num blocks immediately (before the RPC call returns) 911 | /// to an address in the wallet. 912 | fn generate(&self, block_num: u64, maxtries: Option) -> Result> { 913 | self.call("generate", &[block_num.into(), opt_into_json(maxtries)?]) 914 | } 915 | 916 | /// Mark a block as invalid by `block_hash` 917 | fn invalidate_block(&self, block_hash: &bitcoin::BlockHash) -> Result<()> { 918 | self.call("invalidateblock", &[into_json(block_hash)?]) 919 | } 920 | 921 | /// Mark a block as valid by `block_hash` 922 | fn reconsider_block(&self, block_hash: &bitcoin::BlockHash) -> Result<()> { 923 | self.call("reconsiderblock", &[into_json(block_hash)?]) 924 | } 925 | 926 | /// Returns details on the active state of the TX memory pool 927 | fn get_mempool_info(&self) -> Result { 928 | self.call("getmempoolinfo", &[]) 929 | } 930 | 931 | /// Get txids of all transactions in a memory pool 932 | fn get_raw_mempool(&self) -> Result> { 933 | self.call("getrawmempool", &[]) 934 | } 935 | 936 | /// Get details for the transactions in a memory pool 937 | fn get_raw_mempool_verbose( 938 | &self, 939 | ) -> Result> { 940 | self.call("getrawmempool", &[into_json(true)?]) 941 | } 942 | 943 | /// Get mempool data for given transaction 944 | fn get_mempool_entry(&self, txid: &bitcoin::Txid) -> Result { 945 | self.call("getmempoolentry", &[into_json(txid)?]) 946 | } 947 | 948 | /// Get information about all known tips in the block tree, including the 949 | /// main chain as well as stale branches. 950 | fn get_chain_tips(&self) -> Result { 951 | self.call("getchaintips", &[]) 952 | } 953 | 954 | #[allow(clippy::too_many_arguments)] 955 | fn send_to_address( 956 | &self, 957 | address: &Address, 958 | amount: Amount, 959 | comment: Option<&str>, 960 | comment_to: Option<&str>, 961 | subtract_fee: Option, 962 | replaceable: Option, 963 | confirmation_target: Option, 964 | estimate_mode: Option, 965 | ) -> Result { 966 | let mut args = [ 967 | address.to_string().into(), 968 | into_json(amount.to_btc())?, 969 | opt_into_json(comment)?, 970 | opt_into_json(comment_to)?, 971 | opt_into_json(subtract_fee)?, 972 | opt_into_json(replaceable)?, 973 | opt_into_json(confirmation_target)?, 974 | opt_into_json(estimate_mode)?, 975 | ]; 976 | self.call( 977 | "sendtoaddress", 978 | handle_defaults( 979 | &mut args, 980 | &["".into(), "".into(), false.into(), false.into(), 6.into(), null()], 981 | ), 982 | ) 983 | } 984 | 985 | /// Attempts to add a node to the addnode list. 986 | /// Nodes added using addnode (or -connect) are protected from DoS disconnection and are not required to be full nodes/support SegWit as other outbound peers are (though such peers will not be synced from). 987 | fn add_node(&self, addr: &str) -> Result<()> { 988 | self.call("addnode", &[into_json(addr)?, into_json("add")?]) 989 | } 990 | 991 | /// Attempts to remove a node from the addnode list. 992 | fn remove_node(&self, addr: &str) -> Result<()> { 993 | self.call("addnode", &[into_json(addr)?, into_json("remove")?]) 994 | } 995 | 996 | /// Attempts to connect to a node without permanently adding it to the addnode list. 997 | fn onetry_node(&self, addr: &str) -> Result<()> { 998 | self.call("addnode", &[into_json(addr)?, into_json("onetry")?]) 999 | } 1000 | 1001 | /// Immediately disconnects from the specified peer node. 1002 | fn disconnect_node(&self, addr: &str) -> Result<()> { 1003 | self.call("disconnectnode", &[into_json(addr)?]) 1004 | } 1005 | 1006 | fn disconnect_node_by_id(&self, node_id: u32) -> Result<()> { 1007 | self.call("disconnectnode", &[into_json("")?, into_json(node_id)?]) 1008 | } 1009 | 1010 | /// Returns information about the given added node, or all added nodes (note that onetry addnodes are not listed here) 1011 | fn get_added_node_info(&self, node: Option<&str>) -> Result> { 1012 | if let Some(addr) = node { 1013 | self.call("getaddednodeinfo", &[into_json(addr)?]) 1014 | } else { 1015 | self.call("getaddednodeinfo", &[]) 1016 | } 1017 | } 1018 | 1019 | /// Return known addresses which can potentially be used to find new nodes in the network 1020 | fn get_node_addresses( 1021 | &self, 1022 | count: Option, 1023 | ) -> Result> { 1024 | let cnt = count.unwrap_or(1); 1025 | self.call("getnodeaddresses", &[into_json(cnt)?]) 1026 | } 1027 | 1028 | /// List all banned IPs/Subnets. 1029 | fn list_banned(&self) -> Result> { 1030 | self.call("listbanned", &[]) 1031 | } 1032 | 1033 | /// Clear all banned IPs. 1034 | fn clear_banned(&self) -> Result<()> { 1035 | self.call("clearbanned", &[]) 1036 | } 1037 | 1038 | /// Attempts to add an IP/Subnet to the banned list. 1039 | fn add_ban(&self, subnet: &str, bantime: u64, absolute: bool) -> Result<()> { 1040 | self.call( 1041 | "setban", 1042 | &[into_json(subnet)?, into_json("add")?, into_json(bantime)?, into_json(absolute)?], 1043 | ) 1044 | } 1045 | 1046 | /// Attempts to remove an IP/Subnet from the banned list. 1047 | fn remove_ban(&self, subnet: &str) -> Result<()> { 1048 | self.call("setban", &[into_json(subnet)?, into_json("remove")?]) 1049 | } 1050 | 1051 | /// Disable/enable all p2p network activity. 1052 | fn set_network_active(&self, state: bool) -> Result { 1053 | self.call("setnetworkactive", &[into_json(state)?]) 1054 | } 1055 | 1056 | /// Returns data about each connected network node as an array of 1057 | /// [`PeerInfo`][] 1058 | /// 1059 | /// [`PeerInfo`]: net/struct.PeerInfo.html 1060 | fn get_peer_info(&self) -> Result> { 1061 | self.call("getpeerinfo", &[]) 1062 | } 1063 | 1064 | /// Requests that a ping be sent to all other nodes, to measure ping 1065 | /// time. 1066 | /// 1067 | /// Results provided in `getpeerinfo`, `pingtime` and `pingwait` fields 1068 | /// are decimal seconds. 1069 | /// 1070 | /// Ping command is handled in queue with all other commands, so it 1071 | /// measures processing backlog, not just network ping. 1072 | fn ping(&self) -> Result<()> { 1073 | self.call("ping", &[]) 1074 | } 1075 | 1076 | fn send_raw_transaction(&self, tx: R) -> Result { 1077 | self.call("sendrawtransaction", &[tx.raw_hex().into()]) 1078 | } 1079 | 1080 | fn estimate_smart_fee( 1081 | &self, 1082 | conf_target: u16, 1083 | estimate_mode: Option, 1084 | ) -> Result { 1085 | let mut args = [into_json(conf_target)?, opt_into_json(estimate_mode)?]; 1086 | self.call("estimatesmartfee", handle_defaults(&mut args, &[null()])) 1087 | } 1088 | 1089 | /// Waits for a specific new block and returns useful info about it. 1090 | /// Returns the current block on timeout or exit. 1091 | /// 1092 | /// # Arguments 1093 | /// 1094 | /// 1. `timeout`: Time in milliseconds to wait for a response. 0 1095 | /// indicates no timeout. 1096 | fn wait_for_new_block(&self, timeout: u64) -> Result { 1097 | self.call("waitfornewblock", &[into_json(timeout)?]) 1098 | } 1099 | 1100 | /// Waits for a specific new block and returns useful info about it. 1101 | /// Returns the current block on timeout or exit. 1102 | /// 1103 | /// # Arguments 1104 | /// 1105 | /// 1. `blockhash`: Block hash to wait for. 1106 | /// 2. `timeout`: Time in milliseconds to wait for a response. 0 1107 | /// indicates no timeout. 1108 | fn wait_for_block( 1109 | &self, 1110 | blockhash: &bitcoin::BlockHash, 1111 | timeout: u64, 1112 | ) -> Result { 1113 | let args = [into_json(blockhash)?, into_json(timeout)?]; 1114 | self.call("waitforblock", &args) 1115 | } 1116 | 1117 | fn wallet_create_funded_psbt( 1118 | &self, 1119 | inputs: &[json::CreateRawTransactionInput], 1120 | outputs: &HashMap, 1121 | locktime: Option, 1122 | options: Option, 1123 | bip32derivs: Option, 1124 | ) -> Result { 1125 | let outputs_converted = serde_json::Map::from_iter( 1126 | outputs.iter().map(|(k, v)| (k.clone(), serde_json::Value::from(v.to_btc()))), 1127 | ); 1128 | let mut args = [ 1129 | into_json(inputs)?, 1130 | into_json(outputs_converted)?, 1131 | opt_into_json(locktime)?, 1132 | opt_into_json(options)?, 1133 | opt_into_json(bip32derivs)?, 1134 | ]; 1135 | self.call( 1136 | "walletcreatefundedpsbt", 1137 | handle_defaults(&mut args, &[0.into(), serde_json::Map::new().into(), false.into()]), 1138 | ) 1139 | } 1140 | 1141 | fn wallet_process_psbt( 1142 | &self, 1143 | psbt: &str, 1144 | sign: Option, 1145 | sighash_type: Option, 1146 | bip32derivs: Option, 1147 | ) -> Result { 1148 | let mut args = [ 1149 | into_json(psbt)?, 1150 | opt_into_json(sign)?, 1151 | opt_into_json(sighash_type)?, 1152 | opt_into_json(bip32derivs)?, 1153 | ]; 1154 | let defaults = [ 1155 | true.into(), 1156 | into_json(json::SigHashType::from(bitcoin::sighash::EcdsaSighashType::All))?, 1157 | true.into(), 1158 | ]; 1159 | self.call("walletprocesspsbt", handle_defaults(&mut args, &defaults)) 1160 | } 1161 | 1162 | fn get_descriptor_info(&self, desc: &str) -> Result { 1163 | self.call("getdescriptorinfo", &[desc.to_string().into()]) 1164 | } 1165 | 1166 | fn join_psbt(&self, psbts: &[String]) -> Result { 1167 | self.call("joinpsbts", &[into_json(psbts)?]) 1168 | } 1169 | 1170 | fn combine_psbt(&self, psbts: &[String]) -> Result { 1171 | self.call("combinepsbt", &[into_json(psbts)?]) 1172 | } 1173 | 1174 | fn combine_raw_transaction(&self, hex_strings: &[String]) -> Result { 1175 | self.call("combinerawtransaction", &[into_json(hex_strings)?]) 1176 | } 1177 | 1178 | fn finalize_psbt(&self, psbt: &str, extract: Option) -> Result { 1179 | let mut args = [into_json(psbt)?, opt_into_json(extract)?]; 1180 | self.call("finalizepsbt", handle_defaults(&mut args, &[true.into()])) 1181 | } 1182 | 1183 | fn derive_addresses( 1184 | &self, 1185 | descriptor: &str, 1186 | range: Option<[u32; 2]>, 1187 | ) -> Result>> { 1188 | let mut args = [into_json(descriptor)?, opt_into_json(range)?]; 1189 | self.call("deriveaddresses", handle_defaults(&mut args, &[null()])) 1190 | } 1191 | 1192 | fn rescan_blockchain( 1193 | &self, 1194 | start_from: Option, 1195 | stop_height: Option, 1196 | ) -> Result<(usize, Option)> { 1197 | let mut args = [opt_into_json(start_from)?, opt_into_json(stop_height)?]; 1198 | 1199 | #[derive(Deserialize)] 1200 | struct Response { 1201 | pub start_height: usize, 1202 | pub stop_height: Option, 1203 | } 1204 | let res: Response = 1205 | self.call("rescanblockchain", handle_defaults(&mut args, &[0.into(), null()]))?; 1206 | Ok((res.start_height, res.stop_height)) 1207 | } 1208 | 1209 | /// Returns statistics about the unspent transaction output set. 1210 | /// Note this call may take some time if you are not using coinstatsindex. 1211 | fn get_tx_out_set_info( 1212 | &self, 1213 | hash_type: Option, 1214 | hash_or_height: Option, 1215 | use_index: Option, 1216 | ) -> Result { 1217 | let mut args = 1218 | [opt_into_json(hash_type)?, opt_into_json(hash_or_height)?, opt_into_json(use_index)?]; 1219 | self.call("gettxoutsetinfo", handle_defaults(&mut args, &[null(), null(), null()])) 1220 | } 1221 | 1222 | /// Returns information about network traffic, including bytes in, bytes out, 1223 | /// and current time. 1224 | fn get_net_totals(&self) -> Result { 1225 | self.call("getnettotals", &[]) 1226 | } 1227 | 1228 | /// Returns the estimated network hashes per second based on the last n blocks. 1229 | fn get_network_hash_ps(&self, nblocks: Option, height: Option) -> Result { 1230 | let mut args = [opt_into_json(nblocks)?, opt_into_json(height)?]; 1231 | self.call("getnetworkhashps", handle_defaults(&mut args, &[null(), null()])) 1232 | } 1233 | 1234 | /// Returns the total uptime of the server in seconds 1235 | fn uptime(&self) -> Result { 1236 | self.call("uptime", &[]) 1237 | } 1238 | 1239 | /// Submit a block 1240 | fn submit_block(&self, block: &bitcoin::Block) -> Result<()> { 1241 | let block_hex: String = bitcoin::consensus::encode::serialize_hex(block); 1242 | self.submit_block_hex(&block_hex) 1243 | } 1244 | 1245 | /// Submit a raw block 1246 | fn submit_block_bytes(&self, block_bytes: &[u8]) -> Result<()> { 1247 | let block_hex: String = block_bytes.to_lower_hex_string(); 1248 | self.submit_block_hex(&block_hex) 1249 | } 1250 | 1251 | /// Submit a block as a hex string 1252 | fn submit_block_hex(&self, block_hex: &str) -> Result<()> { 1253 | match self.call("submitblock", &[into_json(block_hex)?]) { 1254 | Ok(serde_json::Value::Null) => Ok(()), 1255 | Ok(res) => Err(Error::ReturnedError(res.to_string())), 1256 | Err(err) => Err(err), 1257 | } 1258 | } 1259 | 1260 | fn scan_tx_out_set_blocking( 1261 | &self, 1262 | descriptors: &[json::ScanTxOutRequest], 1263 | ) -> Result { 1264 | self.call("scantxoutset", &["start".into(), into_json(descriptors)?]) 1265 | } 1266 | 1267 | /// Returns information about the active ZeroMQ notifications 1268 | fn get_zmq_notifications(&self) -> Result> { 1269 | self.call("getzmqnotifications", &[]) 1270 | } 1271 | } 1272 | 1273 | /// Client implements a JSON-RPC client for the Bitcoin Core daemon or compatible APIs. 1274 | pub struct Client { 1275 | client: jsonrpc::client::Client, 1276 | } 1277 | 1278 | impl fmt::Debug for Client { 1279 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 1280 | write!(f, "bitcoincore_rpc::Client({:?})", self.client) 1281 | } 1282 | } 1283 | 1284 | impl Client { 1285 | /// Creates a client to a bitcoind JSON-RPC server. 1286 | /// 1287 | /// Can only return [Err] when using cookie authentication. 1288 | pub fn new(url: &str, auth: Auth) -> Result { 1289 | let (user, pass) = auth.get_user_pass()?; 1290 | jsonrpc::client::Client::simple_http(url, user, pass) 1291 | .map(|client| Client { 1292 | client, 1293 | }) 1294 | .map_err(|e| super::error::Error::JsonRpc(e.into())) 1295 | } 1296 | 1297 | /// Create a new Client using the given [jsonrpc::Client]. 1298 | pub fn from_jsonrpc(client: jsonrpc::client::Client) -> Client { 1299 | Client { 1300 | client, 1301 | } 1302 | } 1303 | 1304 | /// Get the underlying JSONRPC client. 1305 | pub fn get_jsonrpc_client(&self) -> &jsonrpc::client::Client { 1306 | &self.client 1307 | } 1308 | } 1309 | 1310 | impl RpcApi for Client { 1311 | /// Call an `cmd` rpc with given `args` list 1312 | fn call serde::de::Deserialize<'a>>( 1313 | &self, 1314 | cmd: &str, 1315 | args: &[serde_json::Value], 1316 | ) -> Result { 1317 | let raw = serde_json::value::to_raw_value(args)?; 1318 | let req = self.client.build_request(cmd, Some(&*raw)); 1319 | if log_enabled!(Debug) { 1320 | debug!(target: "bitcoincore_rpc", "JSON-RPC request: {} {}", cmd, serde_json::Value::from(args)); 1321 | } 1322 | 1323 | let resp = self.client.send_request(req).map_err(Error::from); 1324 | log_response(cmd, &resp); 1325 | Ok(resp?.result()?) 1326 | } 1327 | } 1328 | 1329 | fn log_response(cmd: &str, resp: &Result) { 1330 | if log_enabled!(Warn) || log_enabled!(Debug) || log_enabled!(Trace) { 1331 | match resp { 1332 | Err(ref e) => { 1333 | if log_enabled!(Debug) { 1334 | debug!(target: "bitcoincore_rpc", "JSON-RPC failed parsing reply of {}: {:?}", cmd, e); 1335 | } 1336 | } 1337 | Ok(ref resp) => { 1338 | if let Some(ref e) = resp.error { 1339 | if log_enabled!(Debug) { 1340 | debug!(target: "bitcoincore_rpc", "JSON-RPC error for {}: {:?}", cmd, e); 1341 | } 1342 | } else if log_enabled!(Trace) { 1343 | let def = 1344 | serde_json::value::to_raw_value(&serde_json::value::Value::Null).unwrap(); 1345 | let result = resp.result.as_ref().unwrap_or(&def); 1346 | trace!(target: "bitcoincore_rpc", "JSON-RPC response for {}: {}", cmd, result); 1347 | } 1348 | } 1349 | } 1350 | } 1351 | } 1352 | 1353 | #[cfg(test)] 1354 | mod tests { 1355 | use super::*; 1356 | use crate::bitcoin; 1357 | 1358 | #[test] 1359 | fn test_raw_tx() { 1360 | use crate::bitcoin::consensus::encode; 1361 | let client = Client::new("http://localhost/", Auth::None).unwrap(); 1362 | let tx: bitcoin::Transaction = encode::deserialize(&Vec::::from_hex("0200000001586bd02815cf5faabfec986a4e50d25dbee089bd2758621e61c5fab06c334af0000000006b483045022100e85425f6d7c589972ee061413bcf08dc8c8e589ce37b217535a42af924f0e4d602205c9ba9cb14ef15513c9d946fa1c4b797883e748e8c32171bdf6166583946e35c012103dae30a4d7870cd87b45dd53e6012f71318fdd059c1c2623b8cc73f8af287bb2dfeffffff021dc4260c010000001976a914f602e88b2b5901d8aab15ebe4a97cf92ec6e03b388ac00e1f505000000001976a914687ffeffe8cf4e4c038da46a9b1d37db385a472d88acfd211500").unwrap()).unwrap(); 1363 | 1364 | assert!(client.send_raw_transaction(&tx).is_err()); 1365 | assert!(client.send_raw_transaction(&encode::serialize(&tx)).is_err()); 1366 | assert!(client.send_raw_transaction("deadbeef").is_err()); 1367 | assert!(client.send_raw_transaction("deadbeef".to_owned()).is_err()); 1368 | } 1369 | 1370 | fn test_handle_defaults_inner() -> Result<()> { 1371 | { 1372 | let mut args = [into_json(0)?, null(), null()]; 1373 | let defaults = [into_json(1)?, into_json(2)?]; 1374 | let res = [into_json(0)?]; 1375 | assert_eq!(handle_defaults(&mut args, &defaults), &res); 1376 | } 1377 | { 1378 | let mut args = [into_json(0)?, into_json(1)?, null()]; 1379 | let defaults = [into_json(2)?]; 1380 | let res = [into_json(0)?, into_json(1)?]; 1381 | assert_eq!(handle_defaults(&mut args, &defaults), &res); 1382 | } 1383 | { 1384 | let mut args = [into_json(0)?, null(), into_json(5)?]; 1385 | let defaults = [into_json(2)?, into_json(3)?]; 1386 | let res = [into_json(0)?, into_json(2)?, into_json(5)?]; 1387 | assert_eq!(handle_defaults(&mut args, &defaults), &res); 1388 | } 1389 | { 1390 | let mut args = [into_json(0)?, null(), into_json(5)?, null()]; 1391 | let defaults = [into_json(2)?, into_json(3)?, into_json(4)?]; 1392 | let res = [into_json(0)?, into_json(2)?, into_json(5)?]; 1393 | assert_eq!(handle_defaults(&mut args, &defaults), &res); 1394 | } 1395 | { 1396 | let mut args = [null(), null()]; 1397 | let defaults = [into_json(2)?, into_json(3)?]; 1398 | let res: [serde_json::Value; 0] = []; 1399 | assert_eq!(handle_defaults(&mut args, &defaults), &res); 1400 | } 1401 | { 1402 | let mut args = [null(), into_json(1)?]; 1403 | let defaults = []; 1404 | let res = [null(), into_json(1)?]; 1405 | assert_eq!(handle_defaults(&mut args, &defaults), &res); 1406 | } 1407 | { 1408 | let mut args = []; 1409 | let defaults = []; 1410 | let res: [serde_json::Value; 0] = []; 1411 | assert_eq!(handle_defaults(&mut args, &defaults), &res); 1412 | } 1413 | { 1414 | let mut args = [into_json(0)?]; 1415 | let defaults = [into_json(2)?]; 1416 | let res = [into_json(0)?]; 1417 | assert_eq!(handle_defaults(&mut args, &defaults), &res); 1418 | } 1419 | Ok(()) 1420 | } 1421 | 1422 | #[test] 1423 | fn test_handle_defaults() { 1424 | test_handle_defaults_inner().unwrap(); 1425 | } 1426 | 1427 | #[test] 1428 | fn auth_cookie_file_ignores_newline() { 1429 | let tempdir = tempfile::tempdir().unwrap(); 1430 | let path = tempdir.path().join("cookie"); 1431 | std::fs::write(&path, "foo:bar\n").unwrap(); 1432 | assert_eq!( 1433 | Auth::CookieFile(path).get_user_pass().unwrap(), 1434 | (Some("foo".into()), Some("bar".into())), 1435 | ); 1436 | } 1437 | 1438 | #[test] 1439 | fn auth_cookie_file_ignores_additional_lines() { 1440 | let tempdir = tempfile::tempdir().unwrap(); 1441 | let path = tempdir.path().join("cookie"); 1442 | std::fs::write(&path, "foo:bar\nbaz").unwrap(); 1443 | assert_eq!( 1444 | Auth::CookieFile(path).get_user_pass().unwrap(), 1445 | (Some("foo".into()), Some("bar".into())), 1446 | ); 1447 | } 1448 | 1449 | #[test] 1450 | fn auth_cookie_file_fails_if_colon_isnt_present() { 1451 | let tempdir = tempfile::tempdir().unwrap(); 1452 | let path = tempdir.path().join("cookie"); 1453 | std::fs::write(&path, "foobar").unwrap(); 1454 | assert!(matches!(Auth::CookieFile(path).get_user_pass(), Err(Error::InvalidCookieFile))); 1455 | } 1456 | } 1457 | -------------------------------------------------------------------------------- /integration_test/src/main.rs: -------------------------------------------------------------------------------- 1 | //! # rust-bitcoincore-rpc integration test 2 | //! 3 | //! The test methods are named to mention the methods tested. 4 | //! Individual test methods don't use any methods not tested before or 5 | //! mentioned in the test method name. 6 | //! 7 | //! The goal of this test is not to test the correctness of the server, but 8 | //! to test the serialization of arguments and deserialization of responses. 9 | //! 10 | 11 | #![deny(unused)] 12 | 13 | #[macro_use] 14 | extern crate lazy_static; 15 | 16 | use std::collections::HashMap; 17 | use std::str::FromStr; 18 | 19 | use bitcoin::absolute::LockTime; 20 | use bitcoin::address::{NetworkChecked, NetworkUnchecked}; 21 | use bitcoincore_rpc::json; 22 | use bitcoincore_rpc::jsonrpc::error::Error as JsonRpcError; 23 | use bitcoincore_rpc::{Auth, Client, Error, RpcApi}; 24 | 25 | use crate::json::BlockStatsFields as BsFields; 26 | use bitcoin::consensus::encode::{deserialize, serialize_hex}; 27 | use bitcoin::hashes::hex::FromHex; 28 | use bitcoin::hashes::Hash; 29 | use bitcoin::{secp256k1, sighash, ScriptBuf}; 30 | use bitcoin::{ 31 | transaction, Address, Amount, CompressedPublicKey, Network, OutPoint, PrivateKey, Sequence, 32 | SignedAmount, Transaction, TxIn, TxOut, Txid, Witness, 33 | }; 34 | use bitcoincore_rpc::bitcoincore_rpc_json::{ 35 | GetBlockTemplateModes, GetBlockTemplateRules, GetZmqNotificationsResult, ScanTxOutRequest, 36 | }; 37 | 38 | lazy_static! { 39 | static ref SECP: secp256k1::Secp256k1 = secp256k1::Secp256k1::new(); 40 | static ref NET: Network = Network::Regtest; 41 | /// A random address not owned by the node. 42 | static ref RANDOM_ADDRESS: Address = Address::from_str("mgR9fN5UzZ64mSUUtk6NwxxS6kwVfoEtPG").unwrap().assume_checked(); 43 | /// The default fee amount to use when needed. 44 | static ref FEE: Amount = Amount::from_btc(0.001).unwrap(); 45 | } 46 | 47 | struct StdLogger; 48 | 49 | impl log::Log for StdLogger { 50 | fn enabled(&self, metadata: &log::Metadata) -> bool { 51 | metadata.target().contains("jsonrpc") || metadata.target().contains("bitcoincore_rpc") 52 | } 53 | 54 | fn log(&self, record: &log::Record) { 55 | if self.enabled(record.metadata()) { 56 | println!("[{}][{}]: {}", record.level(), record.metadata().target(), record.args()); 57 | } 58 | } 59 | 60 | fn flush(&self) {} 61 | } 62 | 63 | static LOGGER: StdLogger = StdLogger; 64 | 65 | /// Assert that the call returns a "deprecated" error. 66 | macro_rules! assert_deprecated { 67 | ($call:expr) => { 68 | match $call.unwrap_err() { 69 | Error::JsonRpc(JsonRpcError::Rpc(ref e)) if e.code == -32 => {} 70 | e => panic!("expected deprecated error for {}, got: {}", stringify!($call), e), 71 | } 72 | }; 73 | } 74 | 75 | /// Assert that the call returns a "method not found" error. 76 | macro_rules! assert_not_found { 77 | ($call:expr) => { 78 | match $call.unwrap_err() { 79 | Error::JsonRpc(JsonRpcError::Rpc(ref e)) if e.code == -32601 => {} 80 | e => panic!("expected method not found error for {}, got: {}", stringify!($call), e), 81 | } 82 | }; 83 | } 84 | 85 | /// Assert that the call returns the specified error message. 86 | macro_rules! assert_error_message { 87 | ($call:expr, $code:expr, $msg:expr) => { 88 | match $call.unwrap_err() { 89 | Error::JsonRpc(JsonRpcError::Rpc(ref e)) 90 | if e.code == $code && e.message.contains($msg) => {} 91 | e => panic!("expected '{}' error for {}, got: {}", $msg, stringify!($call), e), 92 | } 93 | }; 94 | } 95 | 96 | static mut VERSION: usize = 0; 97 | /// Get the version of the node that is running. 98 | fn version() -> usize { 99 | unsafe { VERSION } 100 | } 101 | 102 | /// Quickly create a BTC amount. 103 | fn btc>(btc: F) -> Amount { 104 | Amount::from_btc(btc.into()).unwrap() 105 | } 106 | /// Quickly create a signed BTC amount. 107 | fn sbtc>(btc: F) -> SignedAmount { 108 | SignedAmount::from_btc(btc.into()).unwrap() 109 | } 110 | 111 | fn get_testdir() -> String { 112 | std::env::var("TESTDIR").expect("TESTDIR must be set") 113 | } 114 | 115 | fn get_rpc_url() -> String { 116 | std::env::var("RPC_URL").expect("RPC_URL must be set") 117 | } 118 | 119 | fn get_auth() -> bitcoincore_rpc::Auth { 120 | if let Ok(cookie) = std::env::var("RPC_COOKIE") { 121 | Auth::CookieFile(cookie.into()) 122 | } else if let Ok(user) = std::env::var("RPC_USER") { 123 | Auth::UserPass(user, std::env::var("RPC_PASS").unwrap_or_default()) 124 | } else { 125 | panic!("Either RPC_COOKIE or RPC_USER + RPC_PASS must be set."); 126 | } 127 | } 128 | 129 | fn new_wallet_client(wallet_name: &str) -> Client { 130 | let url = format!("{}{}{}", get_rpc_url(), "/wallet/", wallet_name); 131 | Client::new(&url, get_auth()).unwrap() 132 | } 133 | 134 | fn main() { 135 | log::set_logger(&LOGGER).map(|()| log::set_max_level(log::LevelFilter::max())).unwrap(); 136 | 137 | let cl = new_wallet_client("testwallet"); 138 | 139 | test_get_network_info(&cl); 140 | unsafe { VERSION = cl.version().unwrap() }; 141 | println!("Version: {}", version()); 142 | 143 | cl.create_wallet("testwallet", None, None, None, None).unwrap(); 144 | 145 | test_get_mining_info(&cl); 146 | test_get_blockchain_info(&cl); 147 | test_get_new_address(&cl); 148 | test_get_raw_change_address(&cl); 149 | test_dump_private_key(&cl); 150 | test_generate(&cl); 151 | test_get_balance_generate_to_address(&cl); 152 | test_get_balances_generate_to_address(&cl); 153 | test_get_best_block_hash(&cl); 154 | test_get_block_count(&cl); 155 | test_get_block_hash(&cl); 156 | test_get_block(&cl); 157 | test_get_block_header_get_block_header_info(&cl); 158 | test_get_block_stats(&cl); 159 | test_get_block_stats_fields(&cl); 160 | test_get_address_info(&cl); 161 | test_set_label(&cl); 162 | test_send_to_address(&cl); 163 | test_get_received_by_address(&cl); 164 | test_list_unspent(&cl); 165 | test_get_difficulty(&cl); 166 | test_get_connection_count(&cl); 167 | test_get_raw_transaction(&cl); 168 | test_get_raw_mempool(&cl); 169 | test_get_raw_mempool_verbose(&cl); 170 | test_get_transaction(&cl); 171 | test_list_transactions(&cl); 172 | test_list_since_block(&cl); 173 | test_get_tx_out(&cl); 174 | test_get_tx_out_proof(&cl); 175 | test_get_mempool_entry(&cl); 176 | test_lock_unspent_unlock_unspent(&cl); 177 | test_get_block_filter(&cl); 178 | test_sign_raw_transaction_with_send_raw_transaction(&cl); 179 | test_invalidate_block_reconsider_block(&cl); 180 | test_key_pool_refill(&cl); 181 | test_create_raw_transaction(&cl); 182 | test_decode_raw_transaction(&cl); 183 | test_fund_raw_transaction(&cl); 184 | test_test_mempool_accept(&cl); 185 | test_wallet_create_funded_psbt(&cl); 186 | test_wallet_process_psbt(&cl); 187 | test_join_psbt(&cl); 188 | test_combine_psbt(&cl); 189 | test_combine_raw_transaction(&cl); 190 | test_create_psbt(&cl); 191 | test_finalize_psbt(&cl); 192 | test_list_received_by_address(&cl); 193 | test_scantxoutset(&cl); 194 | test_import_public_key(&cl); 195 | test_import_priv_key(&cl); 196 | test_import_address(&cl); 197 | test_import_address_script(&cl); 198 | test_estimate_smart_fee(&cl); 199 | test_ping(&cl); 200 | test_get_peer_info(&cl); 201 | test_rescan_blockchain(&cl); 202 | test_create_wallet(&cl); 203 | test_get_tx_out_set_info(&cl); 204 | test_get_chain_tips(&cl); 205 | test_get_net_totals(&cl); 206 | test_get_network_hash_ps(&cl); 207 | test_uptime(&cl); 208 | test_getblocktemplate(&cl); 209 | test_unloadwallet(&cl); 210 | test_loadwallet(&cl); 211 | test_backupwallet(&cl); 212 | test_wait_for_new_block(&cl); 213 | test_wait_for_block(&cl); 214 | test_get_descriptor_info(&cl); 215 | test_derive_addresses(&cl); 216 | test_get_mempool_info(&cl); 217 | test_add_multisig_address(&cl); 218 | //TODO import_multi( 219 | //TODO verify_message( 220 | //TODO encrypt_wallet(&self, passphrase: &str) -> Result<()> { 221 | //TODO get_by_id>( 222 | test_add_node(&cl); 223 | test_get_added_node_info(&cl); 224 | test_get_node_addresses(&cl); 225 | test_disconnect_node(&cl); 226 | test_add_ban(&cl); 227 | test_set_network_active(&cl); 228 | test_get_index_info(&cl); 229 | test_get_zmq_notifications(&cl); 230 | test_stop(cl); 231 | } 232 | 233 | fn test_get_network_info(cl: &Client) { 234 | let _ = cl.get_network_info().unwrap(); 235 | } 236 | 237 | fn test_get_mining_info(cl: &Client) { 238 | let _ = cl.get_mining_info().unwrap(); 239 | } 240 | 241 | fn test_get_blockchain_info(cl: &Client) { 242 | let info = cl.get_blockchain_info().unwrap(); 243 | assert_eq!(info.chain, Network::Regtest); 244 | } 245 | 246 | fn test_get_new_address(cl: &Client) { 247 | let addr = cl.get_new_address(None, Some(json::AddressType::Legacy)).unwrap().assume_checked(); 248 | assert_eq!(addr.address_type(), Some(bitcoin::AddressType::P2pkh)); 249 | 250 | let addr = cl.get_new_address(None, Some(json::AddressType::Bech32)).unwrap().assume_checked(); 251 | assert_eq!(addr.address_type(), Some(bitcoin::AddressType::P2wpkh)); 252 | 253 | let addr = 254 | cl.get_new_address(None, Some(json::AddressType::P2shSegwit)).unwrap().assume_checked(); 255 | assert_eq!(addr.address_type(), Some(bitcoin::AddressType::P2sh)); 256 | } 257 | 258 | fn test_get_raw_change_address(cl: &Client) { 259 | let addr = cl.get_raw_change_address(Some(json::AddressType::Legacy)).unwrap().assume_checked(); 260 | assert_eq!(addr.address_type(), Some(bitcoin::AddressType::P2pkh)); 261 | 262 | let addr = cl.get_raw_change_address(Some(json::AddressType::Bech32)).unwrap().assume_checked(); 263 | assert_eq!(addr.address_type(), Some(bitcoin::AddressType::P2wpkh)); 264 | 265 | let addr = 266 | cl.get_raw_change_address(Some(json::AddressType::P2shSegwit)).unwrap().assume_checked(); 267 | assert_eq!(addr.address_type(), Some(bitcoin::AddressType::P2sh)); 268 | } 269 | 270 | fn test_dump_private_key(cl: &Client) { 271 | let addr = cl.get_new_address(None, Some(json::AddressType::Bech32)).unwrap().assume_checked(); 272 | let sk = cl.dump_private_key(&addr).unwrap(); 273 | let pk = CompressedPublicKey::from_private_key(&SECP, &sk).unwrap(); 274 | assert_eq!(addr, Address::p2wpkh(&pk, *NET)); 275 | } 276 | 277 | fn test_generate(cl: &Client) { 278 | if version() < 180000 { 279 | let blocks = cl.generate(4, None).unwrap(); 280 | assert_eq!(blocks.len(), 4); 281 | let blocks = cl.generate(6, Some(45)).unwrap(); 282 | assert_eq!(blocks.len(), 6); 283 | } else if version() < 190000 { 284 | assert_deprecated!(cl.generate(5, None)); 285 | } else if version() < 210000 { 286 | assert_not_found!(cl.generate(5, None)); 287 | } else { 288 | // Bitcoin Core v0.21 appears to return this with a generic -1 error code, 289 | // rather than the expected -32601 code (RPC_METHOD_NOT_FOUND). 290 | assert_error_message!(cl.generate(5, None), -1, "replaced by the -generate cli option"); 291 | } 292 | } 293 | 294 | fn test_get_balance_generate_to_address(cl: &Client) { 295 | let initial = cl.get_balance(None, None).unwrap(); 296 | 297 | let blocks = cl 298 | .generate_to_address(500, &cl.get_new_address(None, None).unwrap().assume_checked()) 299 | .unwrap(); 300 | assert_eq!(blocks.len(), 500); 301 | assert_ne!(cl.get_balance(None, None).unwrap(), initial); 302 | } 303 | 304 | fn test_get_balances_generate_to_address(cl: &Client) { 305 | if version() >= 190000 { 306 | let initial = cl.get_balances().unwrap(); 307 | 308 | let blocks = cl 309 | .generate_to_address(500, &cl.get_new_address(None, None).unwrap().assume_checked()) 310 | .unwrap(); 311 | assert_eq!(blocks.len(), 500); 312 | assert_ne!(cl.get_balances().unwrap(), initial); 313 | } 314 | } 315 | 316 | fn test_get_best_block_hash(cl: &Client) { 317 | let _ = cl.get_best_block_hash().unwrap(); 318 | } 319 | 320 | fn test_get_block_count(cl: &Client) { 321 | let height = cl.get_block_count().unwrap(); 322 | assert!(height > 0); 323 | } 324 | 325 | fn test_get_block_hash(cl: &Client) { 326 | let h = cl.get_block_count().unwrap(); 327 | assert_eq!(cl.get_block_hash(h).unwrap(), cl.get_best_block_hash().unwrap()); 328 | } 329 | 330 | fn test_get_block(cl: &Client) { 331 | let tip = cl.get_best_block_hash().unwrap(); 332 | let block = cl.get_block(&tip).unwrap(); 333 | let hex = cl.get_block_hex(&tip).unwrap(); 334 | assert_eq!(block, deserialize(&Vec::::from_hex(&hex).unwrap()).unwrap()); 335 | assert_eq!(hex, serialize_hex(&block)); 336 | 337 | let tip = cl.get_best_block_hash().unwrap(); 338 | let info = cl.get_block_info(&tip).unwrap(); 339 | assert_eq!(info.hash, tip); 340 | assert_eq!(info.confirmations, 1); 341 | } 342 | 343 | fn test_get_block_header_get_block_header_info(cl: &Client) { 344 | let tip = cl.get_best_block_hash().unwrap(); 345 | let header = cl.get_block_header(&tip).unwrap(); 346 | let info = cl.get_block_header_info(&tip).unwrap(); 347 | assert_eq!(header.block_hash(), info.hash); 348 | assert_eq!(header.version, info.version); 349 | assert_eq!(header.merkle_root, info.merkle_root); 350 | assert_eq!(info.confirmations, 1); 351 | assert_eq!(info.next_block_hash, None); 352 | assert!(info.previous_block_hash.is_some()); 353 | } 354 | 355 | fn test_get_block_stats(cl: &Client) { 356 | let tip = cl.get_block_count().unwrap(); 357 | let tip_hash = cl.get_best_block_hash().unwrap(); 358 | let header = cl.get_block_header(&tip_hash).unwrap(); 359 | let stats = cl.get_block_stats(tip).unwrap(); 360 | assert_eq!(header.block_hash(), stats.block_hash); 361 | assert_eq!(header.time, stats.time as u32); 362 | assert_eq!(tip, stats.height); 363 | } 364 | 365 | fn test_get_block_stats_fields(cl: &Client) { 366 | let tip = cl.get_block_count().unwrap(); 367 | let tip_hash = cl.get_best_block_hash().unwrap(); 368 | let header = cl.get_block_header(&tip_hash).unwrap(); 369 | let fields = [BsFields::BlockHash, BsFields::Height, BsFields::TotalFee]; 370 | let stats = cl.get_block_stats_fields(tip, &fields).unwrap(); 371 | assert_eq!(header.block_hash(), stats.block_hash.unwrap()); 372 | assert_eq!(tip, stats.height.unwrap()); 373 | assert!(stats.total_fee.is_some()); 374 | assert!(stats.avg_fee.is_none()); 375 | } 376 | 377 | fn test_get_address_info(cl: &Client) { 378 | let addr = cl.get_new_address(None, Some(json::AddressType::Legacy)).unwrap().assume_checked(); 379 | let info = cl.get_address_info(&addr).unwrap(); 380 | assert!(!info.is_witness.unwrap()); 381 | 382 | let addr = cl.get_new_address(None, Some(json::AddressType::Bech32)).unwrap().assume_checked(); 383 | let info = cl.get_address_info(&addr).unwrap(); 384 | assert!(!info.witness_program.unwrap().is_empty()); 385 | 386 | let addr = 387 | cl.get_new_address(None, Some(json::AddressType::P2shSegwit)).unwrap().assume_checked(); 388 | let info = cl.get_address_info(&addr).unwrap(); 389 | assert!(!info.hex.unwrap().is_empty()); 390 | } 391 | 392 | #[allow(deprecated)] 393 | fn test_set_label(cl: &Client) { 394 | let addr = cl.get_new_address(Some("label"), None).unwrap().assume_checked(); 395 | let info = cl.get_address_info(&addr).unwrap(); 396 | if version() >= 20_00_00 { 397 | assert!(info.label.is_none()); 398 | assert_eq!(info.labels[0], json::GetAddressInfoResultLabel::Simple("label".into())); 399 | } else { 400 | assert_eq!(info.label.as_ref().unwrap(), "label"); 401 | assert_eq!( 402 | info.labels[0], 403 | json::GetAddressInfoResultLabel::WithPurpose { 404 | name: "label".into(), 405 | purpose: json::GetAddressInfoResultLabelPurpose::Receive, 406 | } 407 | ); 408 | } 409 | 410 | cl.set_label(&addr, "other").unwrap(); 411 | let info = cl.get_address_info(&addr).unwrap(); 412 | if version() >= 20_00_00 { 413 | assert!(info.label.is_none()); 414 | assert_eq!(info.labels[0], json::GetAddressInfoResultLabel::Simple("other".into())); 415 | } else { 416 | assert_eq!(info.label.as_ref().unwrap(), "other"); 417 | assert_eq!( 418 | info.labels[0], 419 | json::GetAddressInfoResultLabel::WithPurpose { 420 | name: "other".into(), 421 | purpose: json::GetAddressInfoResultLabelPurpose::Receive, 422 | } 423 | ); 424 | } 425 | } 426 | 427 | fn test_send_to_address(cl: &Client) { 428 | let addr = cl.get_new_address(None, None).unwrap().assume_checked(); 429 | let est = json::EstimateMode::Conservative; 430 | let _ = cl.send_to_address(&addr, btc(1), Some("cc"), None, None, None, None, None).unwrap(); 431 | let _ = cl.send_to_address(&addr, btc(1), None, Some("tt"), None, None, None, None).unwrap(); 432 | let _ = cl.send_to_address(&addr, btc(1), None, None, Some(true), None, None, None).unwrap(); 433 | let _ = cl.send_to_address(&addr, btc(1), None, None, None, Some(true), None, None).unwrap(); 434 | let _ = cl.send_to_address(&addr, btc(1), None, None, None, None, Some(3), None).unwrap(); 435 | let _ = cl.send_to_address(&addr, btc(1), None, None, None, None, None, Some(est)).unwrap(); 436 | } 437 | 438 | fn test_get_received_by_address(cl: &Client) { 439 | let addr = cl.get_new_address(None, None).unwrap().assume_checked(); 440 | let _ = cl.send_to_address(&addr, btc(1), None, None, None, None, None, None).unwrap(); 441 | assert_eq!(cl.get_received_by_address(&addr, Some(0)).unwrap(), btc(1)); 442 | assert_eq!(cl.get_received_by_address(&addr, Some(1)).unwrap(), btc(0)); 443 | let _ = cl 444 | .generate_to_address(7, &cl.get_new_address(None, None).unwrap().assume_checked()) 445 | .unwrap(); 446 | assert_eq!(cl.get_received_by_address(&addr, Some(6)).unwrap(), btc(1)); 447 | assert_eq!(cl.get_received_by_address(&addr, None).unwrap(), btc(1)); 448 | } 449 | 450 | fn test_list_unspent(cl: &Client) { 451 | let addr = cl.get_new_address(None, None).unwrap(); 452 | let addr_checked = addr.clone().assume_checked(); 453 | let txid = cl 454 | .send_to_address(&addr.clone().assume_checked(), btc(1), None, None, None, None, None, None) 455 | .unwrap(); 456 | let unspent = cl.list_unspent(Some(0), None, Some(&[&addr_checked]), None, None).unwrap(); 457 | assert_eq!(unspent[0].txid, txid); 458 | assert_eq!(unspent[0].address.as_ref(), Some(&addr)); 459 | assert_eq!(unspent[0].amount, btc(1)); 460 | 461 | let txid = 462 | cl.send_to_address(&addr_checked, btc(7), None, None, None, None, None, None).unwrap(); 463 | let options = json::ListUnspentQueryOptions { 464 | minimum_amount: Some(btc(7)), 465 | maximum_amount: Some(btc(7)), 466 | ..Default::default() 467 | }; 468 | let unspent = 469 | cl.list_unspent(Some(0), None, Some(&[&addr_checked]), None, Some(options)).unwrap(); 470 | assert_eq!(unspent.len(), 1); 471 | assert_eq!(unspent[0].txid, txid); 472 | assert_eq!(unspent[0].address.as_ref(), Some(&addr)); 473 | assert_eq!(unspent[0].amount, btc(7)); 474 | } 475 | 476 | fn test_get_difficulty(cl: &Client) { 477 | let _ = cl.get_difficulty().unwrap(); 478 | } 479 | 480 | fn test_get_connection_count(cl: &Client) { 481 | let _ = cl.get_connection_count().unwrap(); 482 | } 483 | 484 | fn test_get_raw_transaction(cl: &Client) { 485 | let addr = cl.get_new_address(None, None).unwrap().assume_checked(); 486 | let txid = cl.send_to_address(&addr, btc(1), None, None, None, None, None, None).unwrap(); 487 | let tx = cl.get_raw_transaction(&txid, None).unwrap(); 488 | let hex = cl.get_raw_transaction_hex(&txid, None).unwrap(); 489 | assert_eq!(tx, deserialize(&Vec::::from_hex(&hex).unwrap()).unwrap()); 490 | assert_eq!(hex, serialize_hex(&tx)); 491 | 492 | let info = cl.get_raw_transaction_info(&txid, None).unwrap(); 493 | assert_eq!(info.txid, txid); 494 | 495 | let blocks = cl 496 | .generate_to_address(7, &cl.get_new_address(None, None).unwrap().assume_checked()) 497 | .unwrap(); 498 | let _ = cl.get_raw_transaction_info(&txid, Some(&blocks[0])).unwrap(); 499 | } 500 | 501 | fn test_get_raw_mempool(cl: &Client) { 502 | let _ = cl.get_raw_mempool().unwrap(); 503 | } 504 | 505 | fn test_get_raw_mempool_verbose(cl: &Client) { 506 | cl.send_to_address(&RANDOM_ADDRESS, btc(1), None, None, None, None, None, None).unwrap(); 507 | let _ = cl.get_raw_mempool_verbose().unwrap(); 508 | 509 | // cleanup mempool transaction 510 | cl.generate_to_address(2, &RANDOM_ADDRESS).unwrap(); 511 | } 512 | 513 | fn test_get_transaction(cl: &Client) { 514 | let txid = 515 | cl.send_to_address(&RANDOM_ADDRESS, btc(1), None, None, None, None, None, None).unwrap(); 516 | let tx = cl.get_transaction(&txid, None).unwrap(); 517 | assert_eq!(tx.amount, sbtc(-1.0)); 518 | assert_eq!(tx.info.txid, txid); 519 | 520 | let fake = Txid::hash(&[1, 2]); 521 | assert!(cl.get_transaction(&fake, Some(true)).is_err()); 522 | } 523 | 524 | fn test_list_transactions(cl: &Client) { 525 | let _ = cl.list_transactions(None, None, None, None).unwrap(); 526 | let _ = cl.list_transactions(Some("l"), None, None, None).unwrap(); 527 | let _ = cl.list_transactions(None, Some(3), None, None).unwrap(); 528 | let _ = cl.list_transactions(None, None, Some(3), None).unwrap(); 529 | let _ = cl.list_transactions(None, None, None, Some(true)).unwrap(); 530 | } 531 | 532 | fn test_list_since_block(cl: &Client) { 533 | let r = cl.list_since_block(None, None, None, None).unwrap(); 534 | assert_eq!(r.lastblock, cl.get_best_block_hash().unwrap()); 535 | assert!(!r.transactions.is_empty()); 536 | } 537 | 538 | fn test_get_tx_out(cl: &Client) { 539 | let txid = 540 | cl.send_to_address(&RANDOM_ADDRESS, btc(1), None, None, None, None, None, None).unwrap(); 541 | let out = cl.get_tx_out(&txid, 0, Some(false)).unwrap(); 542 | assert!(out.is_none()); 543 | let out = cl.get_tx_out(&txid, 0, Some(true)).unwrap(); 544 | assert!(out.is_some()); 545 | let _ = cl.get_tx_out(&txid, 0, None).unwrap(); 546 | } 547 | 548 | fn test_get_tx_out_proof(cl: &Client) { 549 | let txid1 = 550 | cl.send_to_address(&RANDOM_ADDRESS, btc(1), None, None, None, None, None, None).unwrap(); 551 | let txid2 = 552 | cl.send_to_address(&RANDOM_ADDRESS, btc(1), None, None, None, None, None, None).unwrap(); 553 | let blocks = cl 554 | .generate_to_address(7, &cl.get_new_address(None, None).unwrap().assume_checked()) 555 | .unwrap(); 556 | let proof = cl.get_tx_out_proof(&[txid1, txid2], Some(&blocks[0])).unwrap(); 557 | assert!(!proof.is_empty()); 558 | } 559 | 560 | fn test_get_mempool_entry(cl: &Client) { 561 | let txid = 562 | cl.send_to_address(&RANDOM_ADDRESS, btc(1), None, None, None, None, None, None).unwrap(); 563 | let entry = cl.get_mempool_entry(&txid).unwrap(); 564 | assert!(entry.spent_by.is_empty()); 565 | 566 | let fake = Txid::hash(&[1, 2]); 567 | assert!(cl.get_mempool_entry(&fake).is_err()); 568 | } 569 | 570 | fn test_lock_unspent_unlock_unspent(cl: &Client) { 571 | let addr = cl.get_new_address(None, None).unwrap().assume_checked(); 572 | let txid = cl.send_to_address(&addr, btc(1), None, None, None, None, None, None).unwrap(); 573 | 574 | assert!(cl.lock_unspent(&[OutPoint::new(txid, 0)]).unwrap()); 575 | assert!(cl.unlock_unspent(&[OutPoint::new(txid, 0)]).unwrap()); 576 | 577 | assert!(cl.lock_unspent(&[OutPoint::new(txid, 0)]).unwrap()); 578 | assert!(cl.unlock_unspent_all().unwrap()); 579 | } 580 | 581 | fn test_get_block_filter(cl: &Client) { 582 | let blocks = cl 583 | .generate_to_address(7, &cl.get_new_address(None, None).unwrap().assume_checked()) 584 | .unwrap(); 585 | if version() >= 190000 { 586 | let _ = cl.get_block_filter(&blocks[0]).unwrap(); 587 | } else { 588 | assert_not_found!(cl.get_block_filter(&blocks[0])); 589 | } 590 | } 591 | 592 | fn test_sign_raw_transaction_with_send_raw_transaction(cl: &Client) { 593 | let sk = PrivateKey { 594 | network: Network::Regtest.into(), 595 | inner: secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()), 596 | compressed: true, 597 | }; 598 | let pk = CompressedPublicKey::from_private_key(&SECP, &sk).unwrap(); 599 | let addr = Address::p2wpkh(&pk, Network::Regtest); 600 | 601 | let options = json::ListUnspentQueryOptions { 602 | minimum_amount: Some(btc(2)), 603 | ..Default::default() 604 | }; 605 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).unwrap(); 606 | let unspent = unspent.into_iter().next().unwrap(); 607 | 608 | let tx = Transaction { 609 | version: transaction::Version::ONE, 610 | lock_time: LockTime::ZERO, 611 | input: vec![TxIn { 612 | previous_output: OutPoint { 613 | txid: unspent.txid, 614 | vout: unspent.vout, 615 | }, 616 | sequence: Sequence::MAX, 617 | script_sig: ScriptBuf::new(), 618 | witness: Witness::new(), 619 | }], 620 | output: vec![TxOut { 621 | value: (unspent.amount - *FEE), 622 | script_pubkey: addr.script_pubkey(), 623 | }], 624 | }; 625 | 626 | let input = json::SignRawTransactionInput { 627 | txid: unspent.txid, 628 | vout: unspent.vout, 629 | script_pub_key: unspent.script_pub_key, 630 | redeem_script: None, 631 | amount: Some(unspent.amount), 632 | }; 633 | let res = cl.sign_raw_transaction_with_wallet(&tx, Some(&[input]), None).unwrap(); 634 | assert!(res.complete); 635 | let txid = cl.send_raw_transaction(&res.transaction().unwrap()).unwrap(); 636 | 637 | let tx = Transaction { 638 | version: transaction::Version::ONE, 639 | lock_time: LockTime::ZERO, 640 | input: vec![TxIn { 641 | previous_output: OutPoint { 642 | txid, 643 | vout: 0, 644 | }, 645 | script_sig: ScriptBuf::new(), 646 | sequence: Sequence::MAX, 647 | witness: Witness::new(), 648 | }], 649 | output: vec![TxOut { 650 | value: (unspent.amount - *FEE - *FEE), 651 | script_pubkey: RANDOM_ADDRESS.script_pubkey(), 652 | }], 653 | }; 654 | 655 | let res = cl 656 | .sign_raw_transaction_with_key( 657 | &tx, 658 | &[sk], 659 | None, 660 | Some(sighash::EcdsaSighashType::All.into()), 661 | ) 662 | .unwrap(); 663 | assert!(res.complete); 664 | let _ = cl.send_raw_transaction(&res.transaction().unwrap()).unwrap(); 665 | } 666 | 667 | fn test_invalidate_block_reconsider_block(cl: &Client) { 668 | let hash = cl.get_best_block_hash().unwrap(); 669 | cl.invalidate_block(&hash).unwrap(); 670 | cl.reconsider_block(&hash).unwrap(); 671 | } 672 | 673 | fn test_key_pool_refill(cl: &Client) { 674 | cl.key_pool_refill(Some(100)).unwrap(); 675 | cl.key_pool_refill(None).unwrap(); 676 | } 677 | 678 | fn test_create_raw_transaction(cl: &Client) { 679 | let options = json::ListUnspentQueryOptions { 680 | minimum_amount: Some(btc(2)), 681 | ..Default::default() 682 | }; 683 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).unwrap(); 684 | let unspent = unspent.into_iter().next().unwrap(); 685 | 686 | let input = json::CreateRawTransactionInput { 687 | txid: unspent.txid, 688 | vout: unspent.vout, 689 | sequence: None, 690 | }; 691 | let mut output = HashMap::new(); 692 | output.insert(RANDOM_ADDRESS.to_string(), btc(1)); 693 | 694 | let tx = 695 | cl.create_raw_transaction(&[input.clone()], &output, Some(500_000), Some(true)).unwrap(); 696 | let hex = cl.create_raw_transaction_hex(&[input], &output, Some(500_000), Some(true)).unwrap(); 697 | assert_eq!(tx, deserialize(&Vec::::from_hex(&hex).unwrap()).unwrap()); 698 | assert_eq!(hex, serialize_hex(&tx)); 699 | } 700 | 701 | fn test_decode_raw_transaction(cl: &Client) { 702 | let options = json::ListUnspentQueryOptions { 703 | minimum_amount: Some(btc(2)), 704 | ..Default::default() 705 | }; 706 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).unwrap(); 707 | let unspent = unspent.into_iter().next().unwrap(); 708 | 709 | let input = json::CreateRawTransactionInput { 710 | txid: unspent.txid, 711 | vout: unspent.vout, 712 | sequence: None, 713 | }; 714 | let mut output = HashMap::new(); 715 | output.insert(RANDOM_ADDRESS.to_string(), btc(1)); 716 | 717 | let tx = 718 | cl.create_raw_transaction(&[input.clone()], &output, Some(500_000), Some(true)).unwrap(); 719 | let hex = cl.create_raw_transaction_hex(&[input], &output, Some(500_000), Some(true)).unwrap(); 720 | 721 | let decoded_transaction = cl.decode_raw_transaction(hex, None).unwrap(); 722 | 723 | assert_eq!(tx.compute_txid(), decoded_transaction.txid); 724 | assert_eq!(500_000, decoded_transaction.locktime); 725 | 726 | assert_eq!(decoded_transaction.vin[0].txid.unwrap(), unspent.txid); 727 | assert_eq!(decoded_transaction.vout[0].clone().value, btc(1)); 728 | } 729 | 730 | fn test_fund_raw_transaction(cl: &Client) { 731 | let addr = cl.get_new_address(None, None).unwrap().assume_checked(); 732 | let mut output = HashMap::new(); 733 | output.insert(RANDOM_ADDRESS.to_string(), btc(1)); 734 | 735 | let options = json::FundRawTransactionOptions { 736 | add_inputs: None, 737 | change_address: Some(addr), 738 | change_position: Some(0), 739 | change_type: None, 740 | include_watching: Some(true), 741 | lock_unspents: Some(true), 742 | fee_rate: Some(*FEE), 743 | subtract_fee_from_outputs: Some(vec![0]), 744 | replaceable: Some(true), 745 | conf_target: None, 746 | estimate_mode: None, 747 | }; 748 | let tx = cl.create_raw_transaction_hex(&[], &output, Some(500_000), Some(true)).unwrap(); 749 | let funded = cl.fund_raw_transaction(tx, Some(&options), Some(false)).unwrap(); 750 | let _ = funded.transaction().unwrap(); 751 | 752 | let options = json::FundRawTransactionOptions { 753 | add_inputs: None, 754 | change_address: None, 755 | change_position: Some(0), 756 | change_type: Some(json::AddressType::Legacy), 757 | include_watching: Some(true), 758 | lock_unspents: Some(true), 759 | fee_rate: None, 760 | subtract_fee_from_outputs: Some(vec![0]), 761 | replaceable: Some(true), 762 | conf_target: Some(2), 763 | estimate_mode: Some(json::EstimateMode::Conservative), 764 | }; 765 | let tx = cl.create_raw_transaction_hex(&[], &output, Some(500_000), Some(true)).unwrap(); 766 | let funded = cl.fund_raw_transaction(tx, Some(&options), Some(false)).unwrap(); 767 | let _ = funded.transaction().unwrap(); 768 | } 769 | 770 | fn test_test_mempool_accept(cl: &Client) { 771 | let options = json::ListUnspentQueryOptions { 772 | minimum_amount: Some(btc(2)), 773 | ..Default::default() 774 | }; 775 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).unwrap(); 776 | let unspent = unspent.into_iter().next().unwrap(); 777 | 778 | let input = json::CreateRawTransactionInput { 779 | txid: unspent.txid, 780 | vout: unspent.vout, 781 | sequence: Some(0xFFFFFFFF), 782 | }; 783 | let mut output = HashMap::new(); 784 | output.insert(RANDOM_ADDRESS.to_string(), unspent.amount - *FEE); 785 | 786 | let tx = 787 | cl.create_raw_transaction(&[input.clone()], &output, Some(500_000), Some(false)).unwrap(); 788 | let res = cl.test_mempool_accept(&[&tx]).unwrap(); 789 | assert!(!res[0].allowed); 790 | assert!(res[0].reject_reason.is_some()); 791 | let signed = 792 | cl.sign_raw_transaction_with_wallet(&tx, None, None).unwrap().transaction().unwrap(); 793 | let res = cl.test_mempool_accept(&[&signed]).unwrap(); 794 | assert!(res[0].allowed, "not allowed: {:?}", res[0].reject_reason); 795 | } 796 | 797 | fn test_wallet_create_funded_psbt(cl: &Client) { 798 | let addr = cl.get_new_address(None, None).unwrap(); 799 | let options = json::ListUnspentQueryOptions { 800 | minimum_amount: Some(btc(2)), 801 | ..Default::default() 802 | }; 803 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).unwrap(); 804 | let unspent = unspent.into_iter().next().unwrap(); 805 | 806 | let input = json::CreateRawTransactionInput { 807 | txid: unspent.txid, 808 | vout: unspent.vout, 809 | sequence: None, 810 | }; 811 | let mut output = HashMap::new(); 812 | output.insert(RANDOM_ADDRESS.to_string(), btc(1)); 813 | 814 | let options = json::WalletCreateFundedPsbtOptions { 815 | add_inputs: None, 816 | change_address: None, 817 | change_position: Some(1), 818 | change_type: Some(json::AddressType::Legacy), 819 | include_watching: Some(true), 820 | lock_unspent: Some(true), 821 | fee_rate: Some(*FEE), 822 | subtract_fee_from_outputs: vec![0], 823 | replaceable: Some(true), 824 | conf_target: None, 825 | estimate_mode: None, 826 | }; 827 | let _ = cl 828 | .wallet_create_funded_psbt( 829 | &[input.clone()], 830 | &output, 831 | Some(500_000), 832 | Some(options), 833 | Some(true), 834 | ) 835 | .unwrap(); 836 | 837 | let options = json::WalletCreateFundedPsbtOptions { 838 | add_inputs: None, 839 | change_address: Some(addr), 840 | change_position: Some(1), 841 | change_type: None, 842 | include_watching: Some(true), 843 | lock_unspent: Some(true), 844 | fee_rate: None, 845 | subtract_fee_from_outputs: vec![0], 846 | replaceable: Some(true), 847 | conf_target: Some(3), 848 | estimate_mode: Some(json::EstimateMode::Conservative), 849 | }; 850 | let psbt = cl 851 | .wallet_create_funded_psbt(&[input], &output, Some(500_000), Some(options), Some(true)) 852 | .unwrap(); 853 | assert!(!psbt.psbt.is_empty()); 854 | } 855 | 856 | fn test_wallet_process_psbt(cl: &Client) { 857 | let options = json::ListUnspentQueryOptions { 858 | minimum_amount: Some(btc(2)), 859 | ..Default::default() 860 | }; 861 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).unwrap(); 862 | let unspent = unspent.into_iter().next().unwrap(); 863 | let input = json::CreateRawTransactionInput { 864 | txid: unspent.txid, 865 | vout: unspent.vout, 866 | sequence: None, 867 | }; 868 | let mut output = HashMap::new(); 869 | output.insert(RANDOM_ADDRESS.to_string(), btc(1)); 870 | let psbt = cl 871 | .wallet_create_funded_psbt(&[input.clone()], &output, Some(500_000), None, Some(true)) 872 | .unwrap(); 873 | 874 | let res = cl.wallet_process_psbt(&psbt.psbt, Some(true), None, Some(true)).unwrap(); 875 | assert!(res.complete); 876 | } 877 | 878 | fn test_join_psbt(cl: &Client) { 879 | let options = json::ListUnspentQueryOptions { 880 | minimum_amount: Some(btc(2)), 881 | ..Default::default() 882 | }; 883 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).unwrap(); 884 | let unspent1 = unspent[0].clone(); 885 | let input = json::CreateRawTransactionInput { 886 | txid: unspent1.txid, 887 | vout: unspent1.vout, 888 | sequence: None, 889 | }; 890 | let mut output = HashMap::new(); 891 | output.insert(RANDOM_ADDRESS.to_string(), btc(1)); 892 | let psbt1 = cl 893 | .wallet_create_funded_psbt(&[input.clone()], &output, Some(500_000), None, Some(true)) 894 | .unwrap(); 895 | 896 | let unspent = unspent.into_iter().nth(1).unwrap(); 897 | let input2 = json::CreateRawTransactionInput { 898 | txid: unspent.txid, 899 | vout: unspent.vout, 900 | sequence: None, 901 | }; 902 | let mut output2 = HashMap::new(); 903 | output2.insert(RANDOM_ADDRESS.to_string(), btc(1)); 904 | let psbt2 = cl 905 | .wallet_create_funded_psbt(&[input2.clone()], &output, Some(500_000), None, Some(true)) 906 | .unwrap(); 907 | 908 | let psbt = cl.join_psbt(&[psbt1.psbt, psbt2.psbt]).unwrap(); 909 | assert!(!psbt.is_empty()); 910 | } 911 | 912 | fn test_combine_psbt(cl: &Client) { 913 | let options = json::ListUnspentQueryOptions { 914 | minimum_amount: Some(btc(2)), 915 | ..Default::default() 916 | }; 917 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).unwrap(); 918 | let unspent = unspent.into_iter().next().unwrap(); 919 | let input = json::CreateRawTransactionInput { 920 | txid: unspent.txid, 921 | vout: unspent.vout, 922 | sequence: None, 923 | }; 924 | let mut output = HashMap::new(); 925 | output.insert(RANDOM_ADDRESS.to_string(), btc(1)); 926 | let psbt1 = cl 927 | .wallet_create_funded_psbt(&[input.clone()], &output, Some(500_000), None, Some(true)) 928 | .unwrap(); 929 | 930 | let psbt = cl.combine_psbt(&[psbt1.psbt.clone(), psbt1.psbt]).unwrap(); 931 | assert!(!psbt.is_empty()); 932 | } 933 | 934 | fn test_combine_raw_transaction(cl: &Client) { 935 | let options = json::ListUnspentQueryOptions { 936 | minimum_amount: Some(btc(2)), 937 | ..Default::default() 938 | }; 939 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).unwrap(); 940 | let unspent = unspent.into_iter().next().unwrap(); 941 | let input = json::CreateRawTransactionInput { 942 | txid: unspent.txid, 943 | vout: unspent.vout, 944 | sequence: None, 945 | }; 946 | let mut output = HashMap::new(); 947 | output.insert(RANDOM_ADDRESS.to_string(), btc(1)); 948 | let tx = cl.create_raw_transaction_hex(&[input.clone()], &output, Some(500_000), None).unwrap(); 949 | 950 | let transaction = cl.combine_raw_transaction(&[tx.clone(), tx]).unwrap(); 951 | 952 | assert!(!transaction.is_empty()); 953 | } 954 | 955 | fn test_create_psbt(cl: &Client) { 956 | let options = json::ListUnspentQueryOptions { 957 | minimum_amount: Some(btc(2)), 958 | ..Default::default() 959 | }; 960 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).unwrap(); 961 | let unspent = unspent.into_iter().next().unwrap(); 962 | 963 | let input = json::CreateRawTransactionInput { 964 | txid: unspent.txid, 965 | vout: unspent.vout, 966 | sequence: None, 967 | }; 968 | let mut output = HashMap::new(); 969 | output.insert(RANDOM_ADDRESS.to_string(), btc(1)); 970 | 971 | let _ = cl.create_psbt(&[input], &output, Some(500_000), Some(true)).unwrap(); 972 | } 973 | 974 | fn test_finalize_psbt(cl: &Client) { 975 | let options = json::ListUnspentQueryOptions { 976 | minimum_amount: Some(btc(2)), 977 | ..Default::default() 978 | }; 979 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).unwrap(); 980 | let unspent = unspent.into_iter().next().unwrap(); 981 | let input = json::CreateRawTransactionInput { 982 | txid: unspent.txid, 983 | vout: unspent.vout, 984 | sequence: None, 985 | }; 986 | let mut output = HashMap::new(); 987 | output.insert(RANDOM_ADDRESS.to_string(), btc(1)); 988 | let psbt = cl 989 | .wallet_create_funded_psbt(&[input.clone()], &output, Some(500_000), None, Some(true)) 990 | .unwrap(); 991 | 992 | let res = cl.finalize_psbt(&psbt.psbt, Some(true)).unwrap(); 993 | assert!(!res.complete); 994 | //TODO(stevenroose) add sign psbt and test hex field 995 | //assert!(res.hex.is_some()); 996 | } 997 | 998 | fn test_list_received_by_address(cl: &Client) { 999 | let addr = cl.get_new_address(None, None).unwrap().assume_checked(); 1000 | let txid = cl.send_to_address(&addr, btc(1), None, None, None, None, None, None).unwrap(); 1001 | 1002 | let _ = cl.list_received_by_address(Some(&addr), None, None, None).unwrap(); 1003 | let _ = cl.list_received_by_address(Some(&addr), None, Some(true), None).unwrap(); 1004 | let _ = cl.list_received_by_address(Some(&addr), None, None, Some(true)).unwrap(); 1005 | let _ = cl.list_received_by_address(None, Some(200), None, None).unwrap(); 1006 | 1007 | let res = cl.list_received_by_address(Some(&addr), Some(0), None, None).unwrap(); 1008 | assert_eq!(res[0].txids, vec![txid]); 1009 | } 1010 | 1011 | fn test_import_public_key(cl: &Client) { 1012 | let sk = PrivateKey { 1013 | network: Network::Regtest.into(), 1014 | inner: secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()), 1015 | compressed: true, 1016 | }; 1017 | cl.import_public_key(&sk.public_key(&SECP), None, None).unwrap(); 1018 | cl.import_public_key(&sk.public_key(&SECP), Some("l"), None).unwrap(); 1019 | cl.import_public_key(&sk.public_key(&SECP), None, Some(false)).unwrap(); 1020 | } 1021 | 1022 | fn test_import_priv_key(cl: &Client) { 1023 | let sk = PrivateKey { 1024 | network: Network::Regtest.into(), 1025 | inner: secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()), 1026 | compressed: true, 1027 | }; 1028 | cl.import_private_key(&sk, None, None).unwrap(); 1029 | cl.import_private_key(&sk, Some("l"), None).unwrap(); 1030 | cl.import_private_key(&sk, None, Some(false)).unwrap(); 1031 | } 1032 | 1033 | fn test_import_address(cl: &Client) { 1034 | let sk = PrivateKey { 1035 | network: Network::Regtest.into(), 1036 | inner: secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()), 1037 | compressed: true, 1038 | }; 1039 | let addr = Address::p2pkh(sk.public_key(&SECP), Network::Regtest); 1040 | cl.import_address(&addr, None, None).unwrap(); 1041 | cl.import_address(&addr, Some("l"), None).unwrap(); 1042 | cl.import_address(&addr, None, Some(false)).unwrap(); 1043 | } 1044 | 1045 | fn test_import_address_script(cl: &Client) { 1046 | let sk = PrivateKey { 1047 | network: Network::Regtest.into(), 1048 | inner: secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()), 1049 | compressed: true, 1050 | }; 1051 | let addr = Address::p2pkh(sk.public_key(&SECP), Network::Regtest); 1052 | cl.import_address_script(&addr.script_pubkey(), None, None, None).unwrap(); 1053 | cl.import_address_script(&addr.script_pubkey(), Some("l"), None, None).unwrap(); 1054 | cl.import_address_script(&addr.script_pubkey(), None, Some(false), None).unwrap(); 1055 | cl.import_address_script(&addr.script_pubkey(), None, None, Some(true)).unwrap(); 1056 | } 1057 | 1058 | fn test_estimate_smart_fee(cl: &Client) { 1059 | let mode = json::EstimateMode::Unset; 1060 | let res = cl.estimate_smart_fee(3, Some(mode)).unwrap(); 1061 | 1062 | // With a fresh node, we can't get fee estimates. 1063 | if let Some(errors) = res.errors { 1064 | if errors == ["Insufficient data or no feerate found"] { 1065 | println!("Cannot test estimate_smart_fee because no feerate found!"); 1066 | return; 1067 | } else { 1068 | panic!("Unexpected error(s) for estimate_smart_fee: {:?}", errors); 1069 | } 1070 | } 1071 | 1072 | assert!(res.fee_rate.is_some(), "no fee estimate available: {:?}", res.errors); 1073 | assert!(res.fee_rate.unwrap() >= btc(0)); 1074 | } 1075 | 1076 | fn test_ping(cl: &Client) { 1077 | cl.ping().unwrap(); 1078 | } 1079 | 1080 | fn test_get_peer_info(cl: &Client) { 1081 | let info = cl.get_peer_info().unwrap(); 1082 | if info.is_empty() { 1083 | panic!("No peers are connected so we can't test get_peer_info"); 1084 | } 1085 | } 1086 | 1087 | fn test_rescan_blockchain(cl: &Client) { 1088 | let count = cl.get_block_count().unwrap() as usize; 1089 | assert!(count > 21); 1090 | let (start, stop) = cl.rescan_blockchain(Some(count - 20), Some(count - 1)).unwrap(); 1091 | assert_eq!(start, count - 20); 1092 | assert_eq!(stop, Some(count - 1)); 1093 | } 1094 | 1095 | fn test_create_wallet(cl: &Client) { 1096 | let wallet_names = vec!["alice", "bob", "carol", "denise", "emily"]; 1097 | 1098 | struct WalletParams<'a> { 1099 | name: &'a str, 1100 | disable_private_keys: Option, 1101 | blank: Option, 1102 | passphrase: Option<&'a str>, 1103 | avoid_reuse: Option, 1104 | } 1105 | 1106 | let mut wallet_params = vec![ 1107 | WalletParams { 1108 | name: wallet_names[0], 1109 | disable_private_keys: None, 1110 | blank: None, 1111 | passphrase: None, 1112 | avoid_reuse: None, 1113 | }, 1114 | WalletParams { 1115 | name: wallet_names[1], 1116 | disable_private_keys: Some(true), 1117 | blank: None, 1118 | passphrase: None, 1119 | avoid_reuse: None, 1120 | }, 1121 | WalletParams { 1122 | name: wallet_names[2], 1123 | disable_private_keys: None, 1124 | blank: Some(true), 1125 | passphrase: None, 1126 | avoid_reuse: None, 1127 | }, 1128 | ]; 1129 | 1130 | if version() >= 190000 { 1131 | wallet_params.push(WalletParams { 1132 | name: wallet_names[3], 1133 | disable_private_keys: None, 1134 | blank: None, 1135 | passphrase: Some("pass"), 1136 | avoid_reuse: None, 1137 | }); 1138 | wallet_params.push(WalletParams { 1139 | name: wallet_names[4], 1140 | disable_private_keys: None, 1141 | blank: None, 1142 | passphrase: None, 1143 | avoid_reuse: Some(true), 1144 | }); 1145 | } 1146 | 1147 | for wallet_param in wallet_params { 1148 | let result = cl 1149 | .create_wallet( 1150 | wallet_param.name, 1151 | wallet_param.disable_private_keys, 1152 | wallet_param.blank, 1153 | wallet_param.passphrase, 1154 | wallet_param.avoid_reuse, 1155 | ) 1156 | .unwrap(); 1157 | 1158 | assert_eq!(result.name, wallet_param.name); 1159 | let expected_warning = match (wallet_param.passphrase, wallet_param.avoid_reuse) { 1160 | (None, Some(true)) => { 1161 | Some("Empty string given as passphrase, wallet will not be encrypted.".to_string()) 1162 | } 1163 | _ => Some("".to_string()), 1164 | }; 1165 | assert_eq!(result.warning, expected_warning); 1166 | 1167 | let wallet_client = new_wallet_client(wallet_param.name); 1168 | let wallet_info = wallet_client.get_wallet_info().unwrap(); 1169 | 1170 | assert_eq!(wallet_info.wallet_name, wallet_param.name); 1171 | 1172 | let has_private_keys = !wallet_param.disable_private_keys.unwrap_or(false); 1173 | assert_eq!(wallet_info.private_keys_enabled, has_private_keys); 1174 | let has_hd_seed = has_private_keys && !wallet_param.blank.unwrap_or(false); 1175 | assert_eq!(wallet_info.hd_seed_id.is_some(), has_hd_seed); 1176 | let has_avoid_reuse = wallet_param.avoid_reuse.unwrap_or(false); 1177 | assert_eq!(wallet_info.avoid_reuse.unwrap_or(false), has_avoid_reuse); 1178 | assert_eq!( 1179 | wallet_info.scanning.unwrap_or(json::ScanningDetails::NotScanning(false)), 1180 | json::ScanningDetails::NotScanning(false) 1181 | ); 1182 | } 1183 | 1184 | let mut loaded_wallet_list = cl.list_wallets().unwrap(); 1185 | 1186 | loaded_wallet_list.sort(); 1187 | 1188 | // Main wallet created for tests 1189 | assert!(loaded_wallet_list.iter().any(|w| w == "testwallet")); 1190 | loaded_wallet_list.retain(|w| w != "testwallet" && w.is_empty()); 1191 | 1192 | // Created wallets 1193 | assert!(loaded_wallet_list.iter().zip(wallet_names).all(|(a, b)| a == b)); 1194 | 1195 | // get all wallets, including any that are not loaded 1196 | let wallet_list = cl.list_wallet_dir().unwrap(); 1197 | // check that wallet_list is a superset of loaded_wallet_list 1198 | for ref wallet in loaded_wallet_list { 1199 | assert!(wallet_list.iter().any(|x| x == wallet)); 1200 | } 1201 | } 1202 | 1203 | fn test_get_tx_out_set_info(cl: &Client) { 1204 | cl.get_tx_out_set_info(None, None, None).unwrap(); 1205 | } 1206 | 1207 | fn test_get_chain_tips(cl: &Client) { 1208 | let tips = cl.get_chain_tips().unwrap(); 1209 | assert_eq!(tips.len(), 1); 1210 | } 1211 | 1212 | fn test_add_node(cl: &Client) { 1213 | cl.add_node("127.0.0.1:1234").unwrap(); 1214 | assert_error_message!(cl.add_node("127.0.0.1:1234"), -23, "Error: Node already added"); 1215 | cl.remove_node("127.0.0.1:1234").unwrap(); 1216 | cl.onetry_node("127.0.0.1:1234").unwrap(); 1217 | } 1218 | 1219 | fn test_get_added_node_info(cl: &Client) { 1220 | cl.add_node("127.0.0.1:1234").unwrap(); 1221 | cl.add_node("127.0.0.1:4321").unwrap(); 1222 | 1223 | assert!(cl.get_added_node_info(Some("127.0.0.1:1111")).is_err()); 1224 | assert_eq!(cl.get_added_node_info(None).unwrap().len(), 2); 1225 | assert_eq!(cl.get_added_node_info(Some("127.0.0.1:1234")).unwrap().len(), 1); 1226 | assert_eq!(cl.get_added_node_info(Some("127.0.0.1:4321")).unwrap().len(), 1); 1227 | } 1228 | 1229 | fn test_get_node_addresses(cl: &Client) { 1230 | cl.get_node_addresses(None).unwrap(); 1231 | } 1232 | 1233 | fn test_disconnect_node(cl: &Client) { 1234 | assert_error_message!( 1235 | cl.disconnect_node("127.0.0.1:1234"), 1236 | -29, 1237 | "Node not found in connected nodes" 1238 | ); 1239 | assert_error_message!(cl.disconnect_node_by_id(1), -29, "Node not found in connected nodes"); 1240 | } 1241 | 1242 | fn test_add_ban(cl: &Client) { 1243 | cl.add_ban("127.0.0.1", 0, false).unwrap(); 1244 | let res = cl.list_banned().unwrap(); 1245 | assert_eq!(res.len(), 1); 1246 | 1247 | cl.remove_ban("127.0.0.1").unwrap(); 1248 | let res = cl.list_banned().unwrap(); 1249 | assert_eq!(res.len(), 0); 1250 | 1251 | cl.add_ban("127.0.0.1", 0, false).unwrap(); 1252 | let res = cl.list_banned().unwrap(); 1253 | assert_eq!(res.len(), 1); 1254 | 1255 | cl.clear_banned().unwrap(); 1256 | let res = cl.list_banned().unwrap(); 1257 | assert_eq!(res.len(), 0); 1258 | 1259 | assert_error_message!(cl.add_ban("INVALID_STRING", 0, false), -30, "Error: Invalid IP/Subnet"); 1260 | } 1261 | 1262 | fn test_set_network_active(cl: &Client) { 1263 | cl.set_network_active(false).unwrap(); 1264 | cl.set_network_active(true).unwrap(); 1265 | } 1266 | 1267 | fn test_get_net_totals(cl: &Client) { 1268 | cl.get_net_totals().unwrap(); 1269 | } 1270 | 1271 | fn test_get_network_hash_ps(cl: &Client) { 1272 | cl.get_network_hash_ps(None, None).unwrap(); 1273 | } 1274 | 1275 | fn test_uptime(cl: &Client) { 1276 | cl.uptime().unwrap(); 1277 | } 1278 | 1279 | fn test_scantxoutset(cl: &Client) { 1280 | let addr = cl.get_new_address(None, None).unwrap().assume_checked(); 1281 | 1282 | cl.generate_to_address(2, &addr).unwrap(); 1283 | cl.generate_to_address(7, &cl.get_new_address(None, None).unwrap().assume_checked()).unwrap(); 1284 | 1285 | let utxos = cl 1286 | .scan_tx_out_set_blocking(&[ScanTxOutRequest::Single(format!("addr({})", addr))]) 1287 | .unwrap(); 1288 | 1289 | assert_eq!(utxos.unspents.len(), 2); 1290 | assert_eq!(utxos.success, Some(true)); 1291 | } 1292 | 1293 | fn test_getblocktemplate(cl: &Client) { 1294 | // We want to have a transaction in the mempool so the GetBlockTemplateResult 1295 | // contains an entry in the vector of GetBlockTemplateResultTransaction. 1296 | // Otherwise the GetBlockTemplateResultTransaction deserialization wouldn't 1297 | // be tested. 1298 | cl.send_to_address(&RANDOM_ADDRESS, btc(1), None, None, None, None, None, None).unwrap(); 1299 | 1300 | cl.get_block_template(GetBlockTemplateModes::Template, &[GetBlockTemplateRules::SegWit], &[]) 1301 | .unwrap(); 1302 | 1303 | // cleanup mempool transaction 1304 | cl.generate_to_address(2, &RANDOM_ADDRESS).unwrap(); 1305 | } 1306 | 1307 | fn test_unloadwallet(cl: &Client) { 1308 | cl.create_wallet("testunloadwallet", None, None, None, None).unwrap(); 1309 | 1310 | let res = new_wallet_client("testunloadwallet").unload_wallet(None).unwrap(); 1311 | 1312 | if version() >= 210000 { 1313 | assert!(res.is_some()); 1314 | } else { 1315 | assert!(res.is_none()); 1316 | } 1317 | } 1318 | 1319 | fn test_loadwallet(_: &Client) { 1320 | let wallet_name = "testloadwallet"; 1321 | let wallet_client = new_wallet_client(wallet_name); 1322 | 1323 | assert!(wallet_client.load_wallet(wallet_name).is_err()); 1324 | wallet_client.create_wallet(wallet_name, None, None, None, None).unwrap(); 1325 | assert!(wallet_client.load_wallet(wallet_name).is_err()); 1326 | wallet_client.unload_wallet(None).unwrap(); 1327 | 1328 | let res = wallet_client.load_wallet(wallet_name).unwrap(); 1329 | assert_eq!(res.name, wallet_name); 1330 | assert_eq!(res.warning, Some("".into())); 1331 | } 1332 | 1333 | fn test_backupwallet(_: &Client) { 1334 | let wallet_client = new_wallet_client("testbackupwallet"); 1335 | let backup_path = format!("{}/testbackupwallet.dat", get_testdir()); 1336 | 1337 | assert!(wallet_client.backup_wallet(None).is_err()); 1338 | assert!(wallet_client.backup_wallet(Some(&backup_path)).is_err()); 1339 | wallet_client.create_wallet("testbackupwallet", None, None, None, None).unwrap(); 1340 | assert!(wallet_client.backup_wallet(None).is_err()); 1341 | assert!(wallet_client.backup_wallet(Some(&backup_path)).is_ok()); 1342 | } 1343 | 1344 | fn test_wait_for_new_block(cl: &Client) { 1345 | let height = cl.get_block_count().unwrap(); 1346 | let hash = cl.get_block_hash(height).unwrap(); 1347 | 1348 | assert!(cl.wait_for_new_block(u64::MAX).is_err()); // JSON integer out of range 1349 | assert_eq!( 1350 | cl.wait_for_new_block(100).unwrap(), 1351 | json::BlockRef { 1352 | hash, 1353 | height 1354 | } 1355 | ); 1356 | } 1357 | 1358 | fn test_wait_for_block(cl: &Client) { 1359 | let height = cl.get_block_count().unwrap(); 1360 | let hash = cl.get_block_hash(height).unwrap(); 1361 | 1362 | assert!(cl.wait_for_block(&hash, u64::MAX).is_err()); // JSON integer out of range 1363 | assert_eq!( 1364 | cl.wait_for_block(&hash, 0).unwrap(), 1365 | json::BlockRef { 1366 | hash, 1367 | height 1368 | } 1369 | ); 1370 | } 1371 | 1372 | fn test_get_descriptor_info(cl: &Client) { 1373 | let res = cl 1374 | .get_descriptor_info(r"pkh(cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR)") 1375 | .unwrap(); 1376 | assert_eq!( 1377 | res.descriptor, 1378 | r"pkh(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)#62k9sn4x" 1379 | ); 1380 | assert!(!res.is_range); 1381 | assert!(res.is_solvable); 1382 | assert!(res.has_private_keys); 1383 | 1384 | // Checksum introduced in: https://github.com/bitcoin/bitcoin/commit/26d3fad1093dfc697048313be7a96c9adf723654 1385 | if version() >= 190000 { 1386 | assert_eq!(res.checksum, Some("37v3lm8x".to_string())); 1387 | } else { 1388 | assert!(res.checksum.is_none()); 1389 | } 1390 | 1391 | assert!(cl.get_descriptor_info("abcdef").is_err()); 1392 | } 1393 | 1394 | fn test_add_multisig_address(cl: &Client) { 1395 | let addr1 = cl.get_new_address(None, Some(json::AddressType::Bech32)).unwrap().assume_checked(); 1396 | let addr2 = cl.get_new_address(None, Some(json::AddressType::Bech32)).unwrap().assume_checked(); 1397 | let addresses = 1398 | [json::PubKeyOrAddress::Address(&addr1), json::PubKeyOrAddress::Address(&addr2)]; 1399 | 1400 | assert!(cl.add_multisig_address(addresses.len(), &addresses, None, None).is_ok()); 1401 | assert!(cl.add_multisig_address(addresses.len() - 1, &addresses, None, None).is_ok()); 1402 | assert!(cl.add_multisig_address(addresses.len() + 1, &addresses, None, None).is_err()); 1403 | assert!(cl.add_multisig_address(0, &addresses, None, None).is_err()); 1404 | assert!(cl.add_multisig_address(addresses.len(), &addresses, Some("test_label"), None).is_ok()); 1405 | assert!(cl 1406 | .add_multisig_address(addresses.len(), &addresses, None, Some(json::AddressType::Legacy)) 1407 | .is_ok()); 1408 | assert!(cl 1409 | .add_multisig_address( 1410 | addresses.len(), 1411 | &addresses, 1412 | None, 1413 | Some(json::AddressType::P2shSegwit) 1414 | ) 1415 | .is_ok()); 1416 | assert!(cl 1417 | .add_multisig_address(addresses.len(), &addresses, None, Some(json::AddressType::Bech32)) 1418 | .is_ok()); 1419 | } 1420 | 1421 | #[rustfmt::skip] 1422 | fn test_derive_addresses(cl: &Client) { 1423 | let descriptor = r"pkh(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)#62k9sn4x"; 1424 | assert_eq!( 1425 | cl.derive_addresses(descriptor, None).unwrap(), 1426 | vec!["mrkwtj5xpYQjHeJe5wsweNjVeTKkvR5fCr".parse::>().unwrap()] 1427 | ); 1428 | assert!(cl.derive_addresses(descriptor, Some([0, 1])).is_err()); // Range should not be specified for an unranged descriptor 1429 | 1430 | let descriptor = std::concat!( 1431 | r"wpkh([1004658e/84'/1'/0']tpubDCBEcmVKbfC9KfdydyLbJ2gfNL88grZu1XcWSW9ytTM6fi", 1432 | r"tvaRmVyr8Ddf7SjZ2ZfMx9RicjYAXhuh3fmLiVLPodPEqnQQURUfrBKiiVZc8/0/*)#g8l47ngv", 1433 | ); 1434 | assert_eq!(cl.derive_addresses(descriptor, Some([0, 1])).unwrap(), vec![ 1435 | "bcrt1q5n5tjkpva8v5s0uadu2y5f0g7pn4h5eqaq2ux2".parse::>().unwrap(), 1436 | "bcrt1qcgl303ht03ja2e0hudpwk7ypcxk5t478wspzlt".parse::>().unwrap(), 1437 | ]); 1438 | assert!(cl.derive_addresses(descriptor, None).is_err()); // Range must be specified for a ranged descriptor 1439 | } 1440 | 1441 | fn test_get_mempool_info(cl: &Client) { 1442 | let res = cl.get_mempool_info().unwrap(); 1443 | 1444 | if version() >= 190000 { 1445 | assert!(res.loaded.is_some()); 1446 | } else { 1447 | assert!(res.loaded.is_none()); 1448 | } 1449 | 1450 | if version() >= 210000 { 1451 | assert!(res.unbroadcast_count.is_some()); 1452 | } else { 1453 | assert!(res.unbroadcast_count.is_none()); 1454 | } 1455 | 1456 | if version() >= 220000 { 1457 | assert!(res.total_fee.is_some()); 1458 | } else { 1459 | assert!(res.total_fee.is_none()); 1460 | } 1461 | 1462 | if version() >= 240000 { 1463 | assert!(res.incremental_relay_fee.is_some()); 1464 | assert!(res.full_rbf.is_some()); 1465 | } else { 1466 | assert!(res.incremental_relay_fee.is_none()); 1467 | assert!(res.full_rbf.is_none()); 1468 | } 1469 | } 1470 | 1471 | fn test_get_index_info(cl: &Client) { 1472 | if version() >= 210000 { 1473 | let gii = cl.get_index_info().unwrap(); 1474 | assert!(gii.txindex.is_some()); 1475 | assert!(gii.coinstatsindex.is_none()); 1476 | assert!(gii.basic_block_filter_index.is_some()); 1477 | } 1478 | } 1479 | 1480 | fn test_get_zmq_notifications(cl: &Client) { 1481 | let mut zmq_info = cl.get_zmq_notifications().unwrap(); 1482 | 1483 | // it doesn't matter in which order Bitcoin Core returns the result, 1484 | // but checking it is easier if it has a known order 1485 | 1486 | // sort_by_key does not allow returning references to parameters of the compare function 1487 | // (removing the lifetime from the return type mimics this behavior, but we don't want it) 1488 | fn compare_fn(result: &GetZmqNotificationsResult) -> impl Ord + '_ { 1489 | (&result.address, &result.notification_type, result.hwm) 1490 | } 1491 | zmq_info.sort_by(|a, b| compare_fn(a).cmp(&compare_fn(b))); 1492 | 1493 | assert!( 1494 | zmq_info 1495 | == [ 1496 | GetZmqNotificationsResult { 1497 | notification_type: "pubrawblock".to_owned(), 1498 | address: "tcp://0.0.0.0:28332".to_owned(), 1499 | hwm: 1000 1500 | }, 1501 | GetZmqNotificationsResult { 1502 | notification_type: "pubrawtx".to_owned(), 1503 | address: "tcp://0.0.0.0:28333".to_owned(), 1504 | hwm: 1000 1505 | }, 1506 | ] 1507 | ); 1508 | } 1509 | 1510 | fn test_stop(cl: Client) { 1511 | println!("Stopping: '{}'", cl.stop().unwrap()); 1512 | } 1513 | --------------------------------------------------------------------------------