├── .editorconfig ├── .gitignore ├── .rustfmt.toml ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── client ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── examples │ ├── retry_client.rs │ └── test_against_node.rs └── src │ ├── client.rs │ ├── error.rs │ ├── lib.rs │ └── queryable.rs ├── contrib └── test.sh ├── integration_test ├── Cargo.toml ├── run.sh └── src │ └── main.rs └── json ├── Cargo.toml ├── LICENSE ├── README.md └── src └── lib.rs /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2018" 2 | use_small_heuristics = "Off" 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | jobs: 4 | include: 5 | - rust: stable 6 | env: RUSTFMTCHECK=true 7 | - rust: nightly 8 | - rust: 1.29.0 9 | # Integration tests 10 | - rust: stable 11 | env: BITCOINVERSION=0.18.0 12 | - rust: stable 13 | env: BITCOINVERSION=0.18.1 14 | - rust: stable 15 | env: BITCOINVERSION=0.19.0.1 16 | - rust: stable 17 | env: BITCOINVERSION=0.19.1 18 | - rust: stable 19 | env: BITCOINVERSION=0.20.0 20 | - rust: stable 21 | env: BITCOINVERSION=0.20.1 22 | 23 | script: 24 | - ./contrib/test.sh 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.12.0 2 | - bump `bitcoin` dependency to version `0.25`, increasing our MSRV to `1.29.0` 3 | - test against `bitcoind` `0.20.0` and `0.20.1` 4 | - add `get_balances` 5 | - add `get_mempool_entry` 6 | - add `list_since_block` 7 | - add `get_mempool_entry` 8 | - add `list_since_block` 9 | - add `uptime` 10 | - add `get_network_hash_ps` 11 | - add `get_tx_out_set_info` 12 | - add `get_net_totals` 13 | - partially implement `scantxoutset` 14 | - extend `create_wallet` and related APIs 15 | - extend `GetWalletInfoResult` 16 | - extend `WalletTxInfo` 17 | - extend testsuite 18 | - fix `GetPeerInfoResult` 19 | - fix `GetNetworkInfoResult` 20 | - fix `GetTransactionResultDetailCategory` 21 | - fix `GetMempoolEntryResult` for bitcoind prior to `0.19.0` 22 | - fix `GetBlockResult` and `GetBlockHeaderResult` 23 | 24 | # 0.11.0 25 | 26 | - fix `minimum_sum_amount` field name in `ListUnspentQueryOptions` 27 | - add missing "orphan" variant for `GetTransactionResultDetailCategory` 28 | - add `ImportMultiRescanSince` to support "now" for `importmulti`'s 29 | `timestamp` parameter 30 | - rename logging target to `bitcoincore_rpc` instead of `bitcoincore_rpc::client` 31 | - other logging improvements 32 | 33 | # 0.10.0 34 | 35 | - rename `dump_priv_key` -> `dump_private_key` + change return type 36 | - rename `get_block_header_xxx` methods to conform with `get_block_xxx` methods 37 | - rename `get_raw_transaction_xxx` methods to conform with `get_block_xxx` methods 38 | - rename `GetBlockHeaderResult` fields 39 | - rename `GetMiningInfoResult` fields 40 | - represent difficulty values as `f64` instead of `BigUint` 41 | - fix `get_peer_info` 42 | - fix `get_transaction` 43 | - fix `get_balance` 44 | - fix `get_blockchain_info` and make compatible with both 0.18 and 0.19 45 | - fix `get_address_info` 46 | - fix `send_to_address` 47 | - fix `estimate_smart_fee` 48 | - fix `import_private_key` 49 | - fix `list_received_by_address` 50 | - fix `import_address` 51 | - fix `finalize_psbt` 52 | - fix `fund_raw_transaction` 53 | - fix `test_mempool_accept` 54 | - fix `stop` 55 | - fix `rescan_blockchain` 56 | - add `import_address_script` 57 | - add `get_network_info` 58 | - add `version` 59 | - add `Error::UnexpectedStructure` 60 | - add `GetTransactionResultDetailCategory::Immature` 61 | - make `list_unspent` more ergonomic 62 | - made all exported enum types implement `Copy` 63 | - export `jsonrpc` dependency. 64 | - remove `num_bigint` dependency 65 | 66 | # v0.9.1 67 | 68 | - Add `wallet_create_funded_psbt` 69 | - Add `get_descriptor_info` 70 | - Add `combine_psbt` 71 | - Add `derive_addresses` 72 | - Add `finalize_psbt` 73 | - Add `rescan_blockchain` 74 | 75 | # v0.7.0 76 | 77 | - use `bitcoin::PublicKey` instead of `secp256k1::PublicKey` 78 | - fix get_mining_info result issue 79 | - fix test_mempool_accept issue 80 | - fix get_transaction result issues 81 | - fix bug in fund_raw_transaction 82 | - add list_transactions 83 | - add get_raw_mempool 84 | - add reconsider_block 85 | - add import_multi 86 | - add import_public_key 87 | - add set_label 88 | - add lock_unspent 89 | - add unlock_unspent 90 | - add create_wallet 91 | - add load_wallet 92 | - add unload_wallet 93 | - increased log level for requests to debug 94 | 95 | # v0.6.0 96 | 97 | - polish Auth to use owned Strings 98 | - fix using Amount type and Address types where needed 99 | - use references of sha256d::Hashes instead of owned/copied 100 | 101 | # v0.5.1 102 | 103 | - add get_tx_out_proof 104 | - add import_address 105 | - add list_received_by_address 106 | 107 | # v0.5.0 108 | 109 | - add support for cookie authentication 110 | - add fund_raw_transaction command 111 | - deprecate sign_raw_transaction 112 | - use PrivateKey type for calls instead of string 113 | - fix for sign_raw_transaction 114 | - use 32-bit integers for confirmations, signed when needed 115 | 116 | # v0.4.0 117 | 118 | - add RawTx trait for commands that take raw transactions 119 | - update jsonrpc dependency to v0.11.0 120 | - fix for create_raw_transaction 121 | - fix for send_to_address 122 | - fix for get_new_address 123 | - fix for get_tx_out 124 | - fix for get_raw_transaction_verbose 125 | - use `secp256k1::SecretKey` type in API 126 | 127 | # v0.3.0 128 | 129 | - removed the GetTransaction and GetScript traits 130 | (those methods are now directly implemented on types) 131 | - introduce RpcApi trait 132 | - use bitcoin_hashes library 133 | - add signrawtransactionwithkey command 134 | - add testmempoolaccept command 135 | - add generate command 136 | - improve hexadecimal byte value representation 137 | - bugfix getrawtransaction (support coinbase txs) 138 | - update rust-bitcoin dependency v0.16.0 -> v0.18.0 139 | - add RetryClient example 140 | 141 | # v0.2.0 142 | 143 | - add send_to_address command 144 | - add create_raw_transaction command 145 | - Client methods take self without mut 146 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "json", 5 | "client", 6 | "integration_test", 7 | ] 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Status](https://travis-ci.org/rust-bitcoin/rust-bitcoincore-rpc.png?branch=master)](https://travis-ci.org/rust-bitcoin/rust-bitcoincore-rpc) 2 | 3 | # Rust RPC client for Bitcoin Core JSON-RPC 4 | 5 | This is a Rust RPC client library for calling the Bitcoin Core JSON-RPC API. It provides a layer of abstraction over 6 | [rust-jsonrpc](https://github.com/apoelstra/rust-jsonrpc) and makes it easier to talk to the Bitcoin JSON-RPC interface 7 | 8 | This git package compiles into two crates. 9 | 1. [bitcoincore-rpc](https://crates.io/crates/bitcoincore-rpc) - contains an implementation of an rpc client that exposes 10 | the Bitcoin Core JSON-RPC APIs as rust functions. 11 | 12 | 2. [bitcoincore-rpc-json](https://crates.io/crates/bitcoincore-rpc-json) - contains rust data structures that represent 13 | the json responses from the Bitcoin Core JSON-RPC APIs. bitcoincore-rpc depends on this. 14 | 15 | # Usage 16 | Given below is an example of how to connect to the Bitcoin Core JSON-RPC for a Bitcoin Core node running on `localhost` 17 | and print out the hash of the latest block. 18 | 19 | It assumes that the node has password authentication setup, the RPC interface is enabled at port `8332` and the node 20 | is set up to accept RPC connections. 21 | 22 | ```rust 23 | extern crate bitcoincore_rpc; 24 | 25 | use bitcoincore_rpc::{Auth, Client, RpcApi}; 26 | 27 | fn main() { 28 | 29 | let rpc = Client::new("http://localhost:8332".to_string(), 30 | Auth::UserPass("".to_string(), 31 | "".to_string())).unwrap(); 32 | let best_block_hash = rpc.get_best_block_hash().unwrap(); 33 | println!("best block hash: {}", best_block_hash); 34 | } 35 | ``` 36 | 37 | See `client/examples/` for more usage examples. 38 | 39 | # Supported Bitcoin Core Versions 40 | The following versions are officially supported and automatically tested: 41 | * 0.18.0 42 | * 0.18.1 43 | * 0.19.0.1 44 | * 0.19.1 45 | * 0.20.0 46 | * 0.20.1 47 | 48 | # Minimum Supported Rust Version (MSRV) 49 | This library should always compile with any combination of features on **Rust 1.29**. 50 | 51 | Because some dependencies have broken the build in minor/patch releases, to 52 | compile with 1.29.0 you will need to run the following version-pinning command: 53 | ``` 54 | cargo update --package "cc" --precise "1.0.41" 55 | cargo update --package "cfg-if" --precise "0.1.9" 56 | cargo update --package "unicode-normalization" --precise "0.1.9" 57 | cargo update --package "serde_json" --precise "1.0.39" 58 | cargo update --package "serde" --precise "1.0.98" 59 | cargo update --package "serde_derive" --precise "1.0.98" 60 | ``` 61 | -------------------------------------------------------------------------------- /client/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | # v0.2.0 5 | 6 | - updated dependencies: 7 | - bitcoin: 0.15 -> 0.16 8 | - secp256ka: 0.11 -> 0.12 9 | - Client methods take `&self` instead of `&mut self` 10 | - added `create_raw_transaction` 11 | - updated `get_new_address` to Core 0.16 spec 12 | -------------------------------------------------------------------------------- /client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitcoincore-rpc-async" 3 | version = "4.0.1-alpha.2" 4 | authors = [ 5 | "Jeremy Rubin ", 6 | "Steven Roose ", 7 | "Jean Pierre Dudey ", 8 | "Dawid Ciężarkiewicz " 9 | ] 10 | edition="2018" 11 | license = "CC0-1.0" 12 | homepage = "https://github.com/jeremyrubin/rust-bitcoincore-rpc-async/" 13 | repository = "https://github.com/jeremyrubin/rust-bitcoincore-rpc-async/" 14 | description = "RPC client library for the Bitcoin Core JSON-RPC API." 15 | keywords = [ "crypto", "bitcoin", "bitcoin-core", "rpc", "asynchronous" ] 16 | readme = "README.md" 17 | 18 | [lib] 19 | name = "bitcoincore_rpc_async" 20 | path = "src/lib.rs" 21 | 22 | [dependencies] 23 | bitcoincore-rpc-json-async = { version = "4.0.1-alpha.2", path = "../json"} 24 | async-trait = "0.1.42" 25 | log = "0.4.5" 26 | jsonrpc-async = {version="2.0.2"} 27 | 28 | # Used for deserialization of JSON. 29 | serde = "1" 30 | serde_json = "1.0.61" 31 | 32 | [dev-dependencies] 33 | tokio = { version = "1", features = ["full"] } 34 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | use async_trait::async_trait; 12 | use bitcoincore_rpc_async; 13 | use jsonrpc_async as jsonrpc; 14 | use serde; 15 | use serde_json; 16 | 17 | use bitcoincore_rpc_async::{Client, Error, Result, RpcApi}; 18 | 19 | pub struct RetryClient { 20 | client: Client, 21 | } 22 | 23 | const INTERVAL: u64 = 1000; 24 | const RETRY_ATTEMPTS: u8 = 10; 25 | 26 | #[async_trait] 27 | impl RpcApi for RetryClient { 28 | async fn call serde::de::Deserialize<'a>>( 29 | &self, 30 | cmd: &str, 31 | args: &[serde_json::Value], 32 | ) -> Result { 33 | for _ in 0..RETRY_ATTEMPTS { 34 | match self.client.call(cmd, args).await { 35 | Ok(ret) => return Ok(ret), 36 | Err(Error::JsonRpc(jsonrpc::error::Error::Rpc(ref rpcerr))) 37 | if rpcerr.code == -28 => 38 | { 39 | ::std::thread::sleep(::std::time::Duration::from_millis(INTERVAL)); 40 | continue; 41 | } 42 | Err(e) => return Err(e), 43 | } 44 | } 45 | self.client.call(cmd, args).await 46 | } 47 | } 48 | 49 | fn main() {} 50 | -------------------------------------------------------------------------------- /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 | use bitcoincore_rpc_async; 14 | 15 | use bitcoincore_rpc_async::{bitcoin, Auth, Client, Error, RpcApi}; 16 | use tokio; 17 | 18 | async fn main_result() -> Result<(), Error> { 19 | let mut args = std::env::args(); 20 | 21 | let _exe_name = args.next().unwrap(); 22 | 23 | let url = args.next().expect("Usage: "); 24 | let user = args.next().expect("no user given"); 25 | let pass = args.next().expect("no pass given"); 26 | 27 | let rpc = Client::new(url, Auth::UserPass(user, pass)).await.unwrap(); 28 | 29 | let _blockchain_info = rpc.get_blockchain_info().await?; 30 | 31 | let best_block_hash = rpc.get_best_block_hash().await?; 32 | println!("best block hash: {}", best_block_hash); 33 | let bestblockcount = rpc.get_block_count().await?; 34 | println!("best block height: {}", bestblockcount); 35 | let best_block_hash_by_height = rpc.get_block_hash(bestblockcount).await?; 36 | println!("best block hash by height: {}", best_block_hash_by_height); 37 | assert_eq!(best_block_hash_by_height, best_block_hash); 38 | 39 | let bitcoin_block: bitcoin::Block = rpc.get_by_id(&best_block_hash).await?; 40 | println!("best block hash by `get`: {}", bitcoin_block.header.prev_blockhash); 41 | let bitcoin_tx: bitcoin::Transaction = rpc.get_by_id(&bitcoin_block.txdata[0].txid()).await?; 42 | println!("tx by `get`: {}", bitcoin_tx.txid()); 43 | 44 | Ok(()) 45 | } 46 | 47 | #[tokio::main] 48 | async fn main() { 49 | main_result().await.unwrap(); 50 | } 51 | -------------------------------------------------------------------------------- /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::iter::FromIterator; 14 | use std::path::PathBuf; 15 | use std::{fmt, result}; 16 | 17 | use bitcoincore_rpc_json_async as json; 18 | use bitcoincore_rpc_json_async::bitcoin; 19 | use jsonrpc_async as jsonrpc; 20 | use serde::*; 21 | 22 | use async_trait::async_trait; 23 | use bitcoin::hashes::hex::{FromHex, ToHex}; 24 | use bitcoin::{ 25 | Address, Amount, Block, BlockHeader, OutPoint, PrivateKey, PublicKey, Script, Transaction, 26 | }; 27 | use log::Level::{Debug, Trace, Warn}; 28 | use log::{debug, log_enabled, trace}; 29 | 30 | use crate::error::*; 31 | use crate::queryable; 32 | 33 | /// Crate-specific Result type, shorthand for `std::result::Result` with our 34 | /// crate-specific Error type; 35 | pub type Result = result::Result; 36 | 37 | #[derive(Clone, Debug, Serialize, Deserialize)] 38 | struct JsonOutPoint { 39 | pub txid: bitcoin::Txid, 40 | pub vout: u32, 41 | } 42 | 43 | impl From for JsonOutPoint { 44 | fn from(o: OutPoint) -> JsonOutPoint { 45 | JsonOutPoint { 46 | txid: o.txid, 47 | vout: o.vout, 48 | } 49 | } 50 | } 51 | 52 | impl From for OutPoint { 53 | fn from(jop: JsonOutPoint) -> OutPoint { 54 | OutPoint { 55 | txid: jop.txid, 56 | vout: jop.vout, 57 | } 58 | } 59 | } 60 | 61 | /// Shorthand for converting a variable into a serde_json::Value. 62 | fn into_json(val: T) -> Result 63 | where 64 | T: serde::ser::Serialize, 65 | { 66 | Ok(serde_json::to_value(val)?) 67 | } 68 | 69 | /// Shorthand for converting an Option into an Option. 70 | fn opt_into_json(opt: Option) -> Result 71 | where 72 | T: serde::ser::Serialize, 73 | { 74 | match opt { 75 | Some(val) => Ok(into_json(val)?), 76 | None => Ok(serde_json::Value::Null), 77 | } 78 | } 79 | 80 | /// Shorthand for `serde_json::Value::Null`. 81 | fn null() -> serde_json::Value { 82 | serde_json::Value::Null 83 | } 84 | 85 | /// Shorthand for an empty serde_json::Value array. 86 | fn empty_arr() -> serde_json::Value { 87 | serde_json::Value::Array(vec![]) 88 | } 89 | 90 | /// Shorthand for an empty serde_json object. 91 | fn empty_obj() -> serde_json::Value { 92 | serde_json::Value::Object(Default::default()) 93 | } 94 | 95 | /// Handle default values in the argument list 96 | /// 97 | /// Substitute `Value::Null`s with corresponding values from `defaults` table, 98 | /// except when they are trailing, in which case just skip them altogether 99 | /// in returned list. 100 | /// 101 | /// Note, that `defaults` corresponds to the last elements of `args`. 102 | /// 103 | /// ```norust 104 | /// arg1 arg2 arg3 arg4 105 | /// def1 def2 106 | /// ``` 107 | /// 108 | /// Elements of `args` without corresponding `defaults` value, won't 109 | /// be substituted, because they are required. 110 | fn handle_defaults<'a, 'b>( 111 | args: &'a mut [serde_json::Value], 112 | defaults: &'b [serde_json::Value], 113 | ) -> &'a [serde_json::Value] { 114 | assert!(args.len() >= defaults.len()); 115 | 116 | // Pass over the optional arguments in backwards order, filling in defaults after the first 117 | // non-null optional argument has been observed. 118 | let mut first_non_null_optional_idx = None; 119 | for i in 0..defaults.len() { 120 | let args_i = args.len() - 1 - i; 121 | let defaults_i = defaults.len() - 1 - i; 122 | if args[args_i] == serde_json::Value::Null { 123 | if first_non_null_optional_idx.is_some() { 124 | if defaults[defaults_i] == serde_json::Value::Null { 125 | panic!("Missing `default` for argument idx {}", args_i); 126 | } 127 | args[args_i] = defaults[defaults_i].clone(); 128 | } 129 | } else if first_non_null_optional_idx.is_none() { 130 | first_non_null_optional_idx = Some(args_i); 131 | } 132 | } 133 | 134 | let required_num = args.len() - defaults.len(); 135 | 136 | if let Some(i) = first_non_null_optional_idx { 137 | &args[..i + 1] 138 | } else { 139 | &args[..required_num] 140 | } 141 | } 142 | 143 | /// Convert a possible-null result into an Option. 144 | fn opt_result serde::de::Deserialize<'a>>( 145 | result: serde_json::Value, 146 | ) -> Result> { 147 | if result == serde_json::Value::Null { 148 | Ok(None) 149 | } else { 150 | Ok(serde_json::from_value(result)?) 151 | } 152 | } 153 | 154 | /// Used to pass raw txs into the API. 155 | pub trait RawTx: Sized + Clone { 156 | fn raw_hex(self) -> String; 157 | } 158 | 159 | impl<'a> RawTx for &'a Transaction { 160 | fn raw_hex(self) -> String { 161 | bitcoin::consensus::encode::serialize(self).to_hex() 162 | } 163 | } 164 | 165 | impl<'a> RawTx for &'a [u8] { 166 | fn raw_hex(self) -> String { 167 | self.to_hex() 168 | } 169 | } 170 | 171 | impl<'a> RawTx for &'a Vec { 172 | fn raw_hex(self) -> String { 173 | self.to_hex() 174 | } 175 | } 176 | 177 | impl<'a> RawTx for &'a str { 178 | fn raw_hex(self) -> String { 179 | self.to_owned() 180 | } 181 | } 182 | 183 | impl RawTx for String { 184 | fn raw_hex(self) -> String { 185 | self 186 | } 187 | } 188 | 189 | /// The different authentication methods for the client. 190 | #[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] 191 | pub enum Auth { 192 | None, 193 | UserPass(String, String), 194 | CookieFile(PathBuf), 195 | } 196 | 197 | impl Auth { 198 | /// Convert into the arguments that jsonrpc::Client needs. 199 | fn get_user_pass(self) -> Result> { 200 | use std::io::Read; 201 | match self { 202 | Auth::None => Ok(None), 203 | Auth::UserPass(u, p) => Ok(Some((u, p))), 204 | Auth::CookieFile(path) => { 205 | let mut file = File::open(path)?; 206 | let mut contents = String::new(); 207 | file.read_to_string(&mut contents)?; 208 | let mut split = contents.splitn(2, ':'); 209 | let u = split.next().ok_or(Error::InvalidCookieFile)?.into(); 210 | let p = split.next().ok_or(Error::InvalidCookieFile)?.into(); 211 | Ok(Some((u, p))) 212 | } 213 | } 214 | } 215 | } 216 | 217 | #[async_trait] 218 | pub trait RpcApi: Sized { 219 | /// Call a `cmd` rpc with given `args` list 220 | async 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 | async fn get_by_id>( 228 | &self, 229 | id: &>::Id, 230 | ) -> Result 231 | where 232 | T: Sync + Send, 233 | >::Id: Sync + Send, 234 | { 235 | T::query(self, id).await 236 | } 237 | 238 | async fn get_network_info(&self) -> Result { 239 | self.call("getnetworkinfo", &[]).await 240 | } 241 | 242 | async fn version(&self) -> Result { 243 | #[derive(Deserialize)] 244 | struct Response { 245 | pub version: usize, 246 | } 247 | let res: Response = self.call("getnetworkinfo", &[]).await?; 248 | Ok(res.version) 249 | } 250 | 251 | async 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()])).await 265 | } 266 | 267 | async fn load_wallet(&self, wallet: &str) -> Result { 268 | self.call("loadwallet", &[wallet.into()]).await 269 | } 270 | 271 | async 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()])).await 274 | } 275 | 276 | async 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 | .await 296 | } 297 | 298 | async fn list_wallets(&self) -> Result> { 299 | self.call("listwallets", &[]).await 300 | } 301 | 302 | async fn get_wallet_info(&self) -> Result { 303 | self.call("getwalletinfo", &[]).await 304 | } 305 | 306 | async fn backup_wallet(&self, destination: Option<&str>) -> Result<()> { 307 | let mut args = [opt_into_json(destination)?]; 308 | self.call("backupwallet", handle_defaults(&mut args, &[null()])).await 309 | } 310 | 311 | async fn dump_private_key(&self, address: &Address) -> Result { 312 | self.call("dumpprivkey", &[address.to_string().into()]).await 313 | } 314 | 315 | async fn encrypt_wallet(&self, passphrase: &str) -> Result<()> { 316 | self.call("encryptwallet", &[into_json(passphrase)?]).await 317 | } 318 | 319 | async fn get_difficulty(&self) -> Result { 320 | self.call("getdifficulty", &[]).await 321 | } 322 | 323 | async fn get_connection_count(&self) -> Result { 324 | self.call("getconnectioncount", &[]).await 325 | } 326 | 327 | async fn get_block(&self, hash: &bitcoin::BlockHash) -> Result { 328 | let hex: String = self.call("getblock", &[into_json(hash)?, 0.into()]).await?; 329 | let bytes: Vec = FromHex::from_hex(&hex)?; 330 | Ok(bitcoin::consensus::encode::deserialize(&bytes)?) 331 | } 332 | 333 | async fn get_block_hex(&self, hash: &bitcoin::BlockHash) -> Result { 334 | self.call("getblock", &[into_json(hash)?, 0.into()]).await 335 | } 336 | 337 | async fn get_block_info(&self, hash: &bitcoin::BlockHash) -> Result { 338 | self.call("getblock", &[into_json(hash)?, 1.into()]).await 339 | } 340 | //TODO(stevenroose) add getblock_txs 341 | 342 | async fn get_block_header(&self, hash: &bitcoin::BlockHash) -> Result { 343 | let hex: String = self.call("getblockheader", &[into_json(hash)?, false.into()]).await?; 344 | let bytes: Vec = FromHex::from_hex(&hex)?; 345 | Ok(bitcoin::consensus::encode::deserialize(&bytes)?) 346 | } 347 | 348 | async fn get_block_header_info( 349 | &self, 350 | hash: &bitcoin::BlockHash, 351 | ) -> Result { 352 | self.call("getblockheader", &[into_json(hash)?, true.into()]).await 353 | } 354 | 355 | async fn get_mining_info(&self) -> Result { 356 | self.call("getmininginfo", &[]).await 357 | } 358 | 359 | /// Returns a data structure containing various state info regarding 360 | /// blockchain processing. 361 | async fn get_blockchain_info(&self) -> Result { 362 | let mut raw: serde_json::Value = self.call("getblockchaininfo", &[]).await?; 363 | // The softfork fields are not backwards compatible: 364 | // - 0.18.x returns a "softforks" array and a "bip9_softforks" map. 365 | // - 0.19.x returns a "softforks" map. 366 | Ok(if self.version().await? < 190000 { 367 | use Error::UnexpectedStructure as err; 368 | 369 | // First, remove both incompatible softfork fields. 370 | // We need to scope the mutable ref here for v1.29 borrowck. 371 | let (bip9_softforks, old_softforks) = { 372 | let map = raw.as_object_mut().ok_or(err)?; 373 | let bip9_softforks = map.remove("bip9_softforks").ok_or(err)?; 374 | let old_softforks = map.remove("softforks").ok_or(err)?; 375 | // Put back an empty "softforks" field. 376 | map.insert("softforks".into(), serde_json::Map::new().into()); 377 | (bip9_softforks, old_softforks) 378 | }; 379 | let mut ret: json::GetBlockchainInfoResult = serde_json::from_value(raw)?; 380 | 381 | // Then convert both softfork types and add them. 382 | for sf in old_softforks.as_array().ok_or(err)?.iter() { 383 | let json = sf.as_object().ok_or(err)?; 384 | let id = json.get("id").ok_or(err)?.as_str().ok_or(err)?; 385 | let reject = json.get("reject").ok_or(err)?.as_object().ok_or(err)?; 386 | let active = reject.get("status").ok_or(err)?.as_bool().ok_or(err)?; 387 | ret.softforks.insert( 388 | id.into(), 389 | json::Softfork { 390 | type_: json::SoftforkType::Buried, 391 | bip9: None, 392 | height: None, 393 | active, 394 | }, 395 | ); 396 | } 397 | for (id, sf) in bip9_softforks.as_object().ok_or(err)?.iter() { 398 | #[derive(Deserialize)] 399 | struct OldBip9SoftFork { 400 | pub status: json::Bip9SoftforkStatus, 401 | pub bit: Option, 402 | #[serde(rename = "startTime")] 403 | pub start_time: i64, 404 | pub timeout: u64, 405 | pub since: u32, 406 | pub statistics: Option, 407 | } 408 | let sf: OldBip9SoftFork = serde_json::from_value(sf.clone())?; 409 | ret.softforks.insert( 410 | id.clone(), 411 | json::Softfork { 412 | type_: json::SoftforkType::Bip9, 413 | bip9: Some(json::Bip9SoftforkInfo { 414 | status: sf.status, 415 | bit: sf.bit, 416 | start_time: sf.start_time, 417 | timeout: sf.timeout, 418 | since: sf.since, 419 | statistics: sf.statistics, 420 | }), 421 | height: None, 422 | active: sf.status == json::Bip9SoftforkStatus::Active, 423 | }, 424 | ); 425 | } 426 | ret 427 | } else { 428 | serde_json::from_value(raw)? 429 | }) 430 | } 431 | 432 | /// Returns the numbers of block in the longest chain. 433 | async fn get_block_count(&self) -> Result { 434 | self.call("getblockcount", &[]).await 435 | } 436 | 437 | /// Returns the hash of the best (tip) block in the longest blockchain. 438 | async fn get_best_block_hash(&self) -> Result { 439 | self.call("getbestblockhash", &[]).await 440 | } 441 | 442 | /// Get block hash at a given height 443 | async fn get_block_hash(&self, height: u64) -> Result { 444 | self.call("getblockhash", &[height.into()]).await 445 | } 446 | 447 | async fn get_raw_transaction( 448 | &self, 449 | txid: &bitcoin::Txid, 450 | block_hash: Option<&bitcoin::BlockHash>, 451 | ) -> Result { 452 | let mut args = [into_json(txid)?, into_json(false)?, opt_into_json(block_hash)?]; 453 | let hex: String = 454 | self.call("getrawtransaction", handle_defaults(&mut args, &[null()])).await?; 455 | let bytes: Vec = FromHex::from_hex(&hex)?; 456 | Ok(bitcoin::consensus::encode::deserialize(&bytes)?) 457 | } 458 | 459 | async fn get_raw_transaction_hex( 460 | &self, 461 | txid: &bitcoin::Txid, 462 | block_hash: Option<&bitcoin::BlockHash>, 463 | ) -> Result { 464 | let mut args = [into_json(txid)?, into_json(false)?, opt_into_json(block_hash)?]; 465 | self.call("getrawtransaction", handle_defaults(&mut args, &[null()])).await 466 | } 467 | 468 | async fn get_raw_transaction_info( 469 | &self, 470 | txid: &bitcoin::Txid, 471 | block_hash: Option<&bitcoin::BlockHash>, 472 | ) -> Result { 473 | let mut args = [into_json(txid)?, into_json(true)?, opt_into_json(block_hash)?]; 474 | self.call("getrawtransaction", handle_defaults(&mut args, &[null()])).await 475 | } 476 | 477 | async fn get_block_filter( 478 | &self, 479 | block_hash: &bitcoin::BlockHash, 480 | ) -> Result { 481 | self.call("getblockfilter", &[into_json(block_hash)?]).await 482 | } 483 | 484 | async fn get_balance( 485 | &self, 486 | minconf: Option, 487 | include_watchonly: Option, 488 | ) -> Result { 489 | let mut args = ["*".into(), opt_into_json(minconf)?, opt_into_json(include_watchonly)?]; 490 | Ok(Amount::from_btc( 491 | self.call("getbalance", handle_defaults(&mut args, &[0.into(), null()])).await?, 492 | )?) 493 | } 494 | 495 | async fn get_balances(&self) -> Result { 496 | Ok(self.call("getbalances", &[]).await?) 497 | } 498 | 499 | async fn get_received_by_address( 500 | &self, 501 | address: &Address, 502 | minconf: Option, 503 | ) -> Result { 504 | let mut args = [address.to_string().into(), opt_into_json(minconf)?]; 505 | Ok(Amount::from_btc( 506 | self.call("getreceivedbyaddress", handle_defaults(&mut args, &[null()])).await?, 507 | )?) 508 | } 509 | 510 | async fn get_transaction( 511 | &self, 512 | txid: &bitcoin::Txid, 513 | include_watchonly: Option, 514 | ) -> Result { 515 | let mut args = [into_json(txid)?, opt_into_json(include_watchonly)?]; 516 | self.call("gettransaction", handle_defaults(&mut args, &[null()])).await 517 | } 518 | 519 | async fn list_transactions( 520 | &self, 521 | label: Option<&str>, 522 | count: Option, 523 | skip: Option, 524 | include_watchonly: Option, 525 | ) -> Result> { 526 | let mut args = [ 527 | label.unwrap_or("*").into(), 528 | opt_into_json(count)?, 529 | opt_into_json(skip)?, 530 | opt_into_json(include_watchonly)?, 531 | ]; 532 | self.call("listtransactions", handle_defaults(&mut args, &[10.into(), 0.into(), null()])) 533 | .await 534 | } 535 | 536 | async fn list_since_block( 537 | &self, 538 | blockhash: Option<&bitcoin::BlockHash>, 539 | target_confirmations: Option, 540 | include_watchonly: Option, 541 | include_removed: Option, 542 | ) -> Result { 543 | let mut args = [ 544 | opt_into_json(blockhash)?, 545 | opt_into_json(target_confirmations)?, 546 | opt_into_json(include_watchonly)?, 547 | opt_into_json(include_removed)?, 548 | ]; 549 | self.call("listsinceblock", handle_defaults(&mut args, &[null()])).await 550 | } 551 | 552 | async fn get_tx_out( 553 | &self, 554 | txid: &bitcoin::Txid, 555 | vout: u32, 556 | include_mempool: Option, 557 | ) -> Result> { 558 | let mut args = [into_json(txid)?, into_json(vout)?, opt_into_json(include_mempool)?]; 559 | opt_result(self.call("gettxout", handle_defaults(&mut args, &[null()])).await?) 560 | } 561 | 562 | async fn get_tx_out_proof( 563 | &self, 564 | txids: &[bitcoin::Txid], 565 | block_hash: Option<&bitcoin::BlockHash>, 566 | ) -> Result> { 567 | let mut args = [into_json(txids)?, opt_into_json(block_hash)?]; 568 | let hex: String = self.call("gettxoutproof", handle_defaults(&mut args, &[null()])).await?; 569 | Ok(FromHex::from_hex(&hex)?) 570 | } 571 | 572 | async fn import_public_key( 573 | &self, 574 | pubkey: &PublicKey, 575 | label: Option<&str>, 576 | rescan: Option, 577 | ) -> Result<()> { 578 | let mut args = [pubkey.to_string().into(), opt_into_json(label)?, opt_into_json(rescan)?]; 579 | self.call("importpubkey", handle_defaults(&mut args, &[into_json("")?, null()])).await 580 | } 581 | 582 | async fn import_private_key( 583 | &self, 584 | privkey: &PrivateKey, 585 | label: Option<&str>, 586 | rescan: Option, 587 | ) -> Result<()> { 588 | let mut args = [privkey.to_string().into(), opt_into_json(label)?, opt_into_json(rescan)?]; 589 | self.call("importprivkey", handle_defaults(&mut args, &[into_json("")?, null()])).await 590 | } 591 | 592 | async fn import_address( 593 | &self, 594 | address: &Address, 595 | label: Option<&str>, 596 | rescan: Option, 597 | ) -> Result<()> { 598 | let mut args = [address.to_string().into(), opt_into_json(label)?, opt_into_json(rescan)?]; 599 | self.call("importaddress", handle_defaults(&mut args, &[into_json("")?, null()])).await 600 | } 601 | 602 | async fn import_address_script( 603 | &self, 604 | script: &Script, 605 | label: Option<&str>, 606 | rescan: Option, 607 | p2sh: Option, 608 | ) -> Result<()> { 609 | let mut args = [ 610 | script.to_hex().into(), 611 | opt_into_json(label)?, 612 | opt_into_json(rescan)?, 613 | opt_into_json(p2sh)?, 614 | ]; 615 | self.call( 616 | "importaddress", 617 | handle_defaults(&mut args, &[into_json("")?, true.into(), null()]), 618 | ) 619 | .await 620 | } 621 | 622 | async fn import_multi( 623 | &self, 624 | requests: &[json::ImportMultiRequest<'_>], 625 | options: Option<&json::ImportMultiOptions>, 626 | ) -> Result> { 627 | let mut json_requests = Vec::with_capacity(requests.len()); 628 | for req in requests { 629 | json_requests.push(serde_json::to_value(req)?); 630 | } 631 | let mut args = [json_requests.into(), opt_into_json(options)?]; 632 | self.call("importmulti", handle_defaults(&mut args, &[null()])).await 633 | } 634 | 635 | async fn set_label(&self, address: &Address, label: &str) -> Result<()> { 636 | self.call("setlabel", &[address.to_string().into(), label.into()]).await 637 | } 638 | 639 | async fn key_pool_refill(&self, new_size: Option) -> Result<()> { 640 | let mut args = [opt_into_json(new_size)?]; 641 | self.call("keypoolrefill", handle_defaults(&mut args, &[null()])).await 642 | } 643 | 644 | async fn list_unspent( 645 | &self, 646 | minconf: Option, 647 | maxconf: Option, 648 | addresses: Option<&[&Address]>, 649 | include_unsafe: Option, 650 | query_options: Option, 651 | ) -> Result> { 652 | let mut args = [ 653 | opt_into_json(minconf)?, 654 | opt_into_json(maxconf)?, 655 | opt_into_json(addresses)?, 656 | opt_into_json(include_unsafe)?, 657 | opt_into_json(query_options)?, 658 | ]; 659 | let defaults = [into_json(0)?, into_json(9999999)?, empty_arr(), into_json(true)?, null()]; 660 | self.call("listunspent", handle_defaults(&mut args, &defaults)).await 661 | } 662 | 663 | /// To unlock, use [unlock_unspent]. 664 | async fn lock_unspent(&self, outputs: &[OutPoint]) -> Result { 665 | let outputs: Vec<_> = 666 | outputs.iter().map(|o| serde_json::to_value(JsonOutPoint::from(*o)).unwrap()).collect(); 667 | self.call("lockunspent", &[false.into(), outputs.into()]).await 668 | } 669 | 670 | async fn unlock_unspent(&self, outputs: &[OutPoint]) -> Result { 671 | let outputs: Vec<_> = 672 | outputs.iter().map(|o| serde_json::to_value(JsonOutPoint::from(*o)).unwrap()).collect(); 673 | self.call("lockunspent", &[true.into(), outputs.into()]).await 674 | } 675 | 676 | async fn list_received_by_address( 677 | &self, 678 | address_filter: Option<&Address>, 679 | minconf: Option, 680 | include_empty: Option, 681 | include_watchonly: Option, 682 | ) -> Result> { 683 | let mut args = [ 684 | opt_into_json(minconf)?, 685 | opt_into_json(include_empty)?, 686 | opt_into_json(include_watchonly)?, 687 | opt_into_json(address_filter)?, 688 | ]; 689 | let defaults = [1.into(), false.into(), false.into(), null()]; 690 | self.call("listreceivedbyaddress", handle_defaults(&mut args, &defaults)).await 691 | } 692 | 693 | async fn create_raw_transaction_hex( 694 | &self, 695 | utxos: &[json::CreateRawTransactionInput], 696 | outs: &HashMap, 697 | locktime: Option, 698 | replaceable: Option, 699 | ) -> Result { 700 | let outs_converted = serde_json::Map::from_iter( 701 | outs.iter().map(|(k, v)| (k.clone(), serde_json::Value::from(v.as_btc()))), 702 | ); 703 | let mut args = [ 704 | into_json(utxos)?, 705 | into_json(outs_converted)?, 706 | opt_into_json(locktime)?, 707 | opt_into_json(replaceable)?, 708 | ]; 709 | let defaults = [into_json(0i64)?, null()]; 710 | self.call("createrawtransaction", handle_defaults(&mut args, &defaults)).await 711 | } 712 | 713 | async fn create_raw_transaction( 714 | &self, 715 | utxos: &[json::CreateRawTransactionInput], 716 | outs: &HashMap, 717 | locktime: Option, 718 | replaceable: Option, 719 | ) -> Result { 720 | let hex: String = 721 | self.create_raw_transaction_hex(utxos, outs, locktime, replaceable).await?; 722 | let bytes: Vec = FromHex::from_hex(&hex)?; 723 | Ok(bitcoin::consensus::encode::deserialize(&bytes)?) 724 | } 725 | 726 | async fn fund_raw_transaction( 727 | &self, 728 | tx: R, 729 | options: Option<&json::FundRawTransactionOptions>, 730 | is_witness: Option, 731 | ) -> Result 732 | where 733 | R: Sync + Send, 734 | { 735 | let mut args = [tx.raw_hex().into(), opt_into_json(options)?, opt_into_json(is_witness)?]; 736 | let defaults = [empty_obj(), null()]; 737 | self.call("fundrawtransaction", handle_defaults(&mut args, &defaults)).await 738 | } 739 | 740 | #[deprecated] 741 | async fn sign_raw_transaction( 742 | &self, 743 | tx: R, 744 | utxos: Option<&[json::SignRawTransactionInput]>, 745 | private_keys: Option<&[PrivateKey]>, 746 | sighash_type: Option, 747 | ) -> Result 748 | where 749 | R: Sync + Send, 750 | { 751 | let mut args = [ 752 | tx.raw_hex().into(), 753 | opt_into_json(utxos)?, 754 | opt_into_json(private_keys)?, 755 | opt_into_json(sighash_type)?, 756 | ]; 757 | let defaults = [empty_arr(), empty_arr(), null()]; 758 | self.call("signrawtransaction", handle_defaults(&mut args, &defaults)).await 759 | } 760 | 761 | async fn sign_raw_transaction_with_wallet( 762 | &self, 763 | tx: R, 764 | utxos: Option<&[json::SignRawTransactionInput]>, 765 | sighash_type: Option, 766 | ) -> Result 767 | where 768 | R: Sync + Send, 769 | { 770 | let mut args = [tx.raw_hex().into(), opt_into_json(utxos)?, opt_into_json(sighash_type)?]; 771 | let defaults = [empty_arr(), null()]; 772 | self.call("signrawtransactionwithwallet", handle_defaults(&mut args, &defaults)).await 773 | } 774 | 775 | async fn sign_raw_transaction_with_key( 776 | &self, 777 | tx: R, 778 | privkeys: &[PrivateKey], 779 | prevtxs: Option<&[json::SignRawTransactionInput]>, 780 | sighash_type: Option, 781 | ) -> Result 782 | where 783 | R: Sync + Send, 784 | { 785 | let mut args = [ 786 | tx.raw_hex().into(), 787 | into_json(privkeys)?, 788 | opt_into_json(prevtxs)?, 789 | opt_into_json(sighash_type)?, 790 | ]; 791 | let defaults = [empty_arr(), null()]; 792 | self.call("signrawtransactionwithkey", handle_defaults(&mut args, &defaults)).await 793 | } 794 | 795 | async fn test_mempool_accept( 796 | &self, 797 | rawtxs: &[R], 798 | ) -> Result> 799 | where 800 | R: Sync + Send, 801 | { 802 | let hexes: Vec = 803 | rawtxs.iter().cloned().map(|r| r.raw_hex().into()).collect(); 804 | self.call("testmempoolaccept", &[hexes.into()]).await 805 | } 806 | 807 | async fn stop(&self) -> Result { 808 | self.call("stop", &[]).await 809 | } 810 | 811 | async fn verify_message( 812 | &self, 813 | address: &Address, 814 | signature: &bitcoin::secp256k1::ecdsa::Signature, 815 | message: &str, 816 | ) -> Result { 817 | let args = [address.to_string().into(), signature.to_string().into(), into_json(message)?]; 818 | self.call("verifymessage", &args).await 819 | } 820 | 821 | /// Generate new address under own control 822 | async fn get_new_address( 823 | &self, 824 | label: Option<&str>, 825 | address_type: Option, 826 | ) -> Result
{ 827 | self.call("getnewaddress", &[opt_into_json(label)?, opt_into_json(address_type)?]).await 828 | } 829 | 830 | async fn get_address_info(&self, address: &Address) -> Result { 831 | self.call("getaddressinfo", &[address.to_string().into()]).await 832 | } 833 | 834 | /// Mine `block_num` blocks and pay coinbase to `address` 835 | /// 836 | /// Returns hashes of the generated blocks 837 | async fn generate_to_address( 838 | &self, 839 | block_num: u64, 840 | address: &Address, 841 | ) -> Result> { 842 | self.call("generatetoaddress", &[block_num.into(), address.to_string().into()]).await 843 | } 844 | 845 | /// Mine up to block_num blocks immediately (before the RPC call returns) 846 | /// to an address in the wallet. 847 | async fn generate( 848 | &self, 849 | block_num: u64, 850 | maxtries: Option, 851 | ) -> Result> { 852 | self.call("generate", &[block_num.into(), opt_into_json(maxtries)?]).await 853 | } 854 | 855 | /// Mark a block as invalid by `block_hash` 856 | async fn invalidate_block(&self, block_hash: &bitcoin::BlockHash) -> Result<()> { 857 | self.call("invalidateblock", &[into_json(block_hash)?]).await 858 | } 859 | 860 | /// Mark a block as valid by `block_hash` 861 | async fn reconsider_block(&self, block_hash: &bitcoin::BlockHash) -> Result<()> { 862 | self.call("reconsiderblock", &[into_json(block_hash)?]).await 863 | } 864 | 865 | /// Get txids of all transactions in a memory pool 866 | async fn get_raw_mempool(&self) -> Result> { 867 | self.call("getrawmempool", &[]).await 868 | } 869 | 870 | /// Get mempool data for given transaction 871 | async fn get_mempool_entry(&self, txid: &bitcoin::Txid) -> Result { 872 | self.call("getmempoolentry", &[into_json(txid)?]).await 873 | } 874 | 875 | #[allow(clippy::too_many_arguments)] 876 | async fn send_to_address( 877 | &self, 878 | address: &Address, 879 | amount: Amount, 880 | comment: Option<&str>, 881 | comment_to: Option<&str>, 882 | subtract_fee: Option, 883 | replaceable: Option, 884 | confirmation_target: Option, 885 | estimate_mode: Option, 886 | ) -> Result { 887 | let mut args = [ 888 | address.to_string().into(), 889 | into_json(amount.as_btc())?, 890 | opt_into_json(comment)?, 891 | opt_into_json(comment_to)?, 892 | opt_into_json(subtract_fee)?, 893 | opt_into_json(replaceable)?, 894 | opt_into_json(confirmation_target)?, 895 | opt_into_json(estimate_mode)?, 896 | ]; 897 | self.call( 898 | "sendtoaddress", 899 | handle_defaults( 900 | &mut args, 901 | &["".into(), "".into(), false.into(), false.into(), 6.into(), null()], 902 | ), 903 | ) 904 | .await 905 | } 906 | 907 | /// Returns data about each connected network node as an array of 908 | /// [`PeerInfo`][] 909 | /// 910 | /// [`PeerInfo`]: net/struct.PeerInfo.html 911 | async fn get_peer_info(&self) -> Result> { 912 | self.call("getpeerinfo", &[]).await 913 | } 914 | 915 | /// Requests that a ping be sent to all other nodes, to measure ping 916 | /// time. 917 | /// 918 | /// Results provided in `getpeerinfo`, `pingtime` and `pingwait` fields 919 | /// are decimal seconds. 920 | /// 921 | /// Ping command is handled in queue with all other commands, so it 922 | /// measures processing backlog, not just network ping. 923 | async fn ping(&self) -> Result<()> { 924 | self.call("ping", &[]).await 925 | } 926 | 927 | async fn send_raw_transaction(&self, tx: R) -> Result 928 | where 929 | R: Sync + Send, 930 | { 931 | self.call("sendrawtransaction", &[tx.raw_hex().into()]).await 932 | } 933 | 934 | async fn estimate_smart_fee( 935 | &self, 936 | conf_target: u16, 937 | estimate_mode: Option, 938 | ) -> Result { 939 | let mut args = [into_json(conf_target)?, opt_into_json(estimate_mode)?]; 940 | self.call("estimatesmartfee", handle_defaults(&mut args, &[null()])).await 941 | } 942 | 943 | /// Waits for a specific new block and returns useful info about it. 944 | /// Returns the current block on timeout or exit. 945 | /// 946 | /// # Arguments 947 | /// 948 | /// 1. `timeout`: Time in milliseconds to wait for a response. 0 949 | /// indicates no timeout. 950 | async fn wait_for_new_block(&self, timeout: u64) -> Result { 951 | self.call("waitfornewblock", &[into_json(timeout)?]).await 952 | } 953 | 954 | /// Waits for a specific new block and returns useful info about it. 955 | /// Returns the current block on timeout or exit. 956 | /// 957 | /// # Arguments 958 | /// 959 | /// 1. `blockhash`: Block hash to wait for. 960 | /// 2. `timeout`: Time in milliseconds to wait for a response. 0 961 | /// indicates no timeout. 962 | async fn wait_for_block( 963 | &self, 964 | blockhash: &bitcoin::BlockHash, 965 | timeout: u64, 966 | ) -> Result { 967 | let args = [into_json(blockhash)?, into_json(timeout)?]; 968 | self.call("waitforblock", &args).await 969 | } 970 | 971 | async fn wallet_create_funded_psbt( 972 | &self, 973 | inputs: &[json::CreateRawTransactionInput], 974 | outputs: &HashMap, 975 | locktime: Option, 976 | options: Option, 977 | bip32derivs: Option, 978 | ) -> Result { 979 | let outputs_converted = serde_json::Map::from_iter( 980 | outputs.iter().map(|(k, v)| (k.clone(), serde_json::Value::from(v.as_btc()))), 981 | ); 982 | let mut args = [ 983 | into_json(inputs)?, 984 | into_json(outputs_converted)?, 985 | opt_into_json(locktime)?, 986 | opt_into_json(options)?, 987 | opt_into_json(bip32derivs)?, 988 | ]; 989 | self.call( 990 | "walletcreatefundedpsbt", 991 | handle_defaults(&mut args, &[0.into(), serde_json::Map::new().into(), false.into()]), 992 | ) 993 | .await 994 | } 995 | 996 | async fn get_descriptor_info(&self, desc: &str) -> Result { 997 | self.call("getdescriptorinfo", &[desc.to_string().into()]).await 998 | } 999 | 1000 | async fn combine_psbt(&self, psbts: &[String]) -> Result { 1001 | self.call("combinepsbt", &[into_json(psbts)?]).await 1002 | } 1003 | 1004 | async fn finalize_psbt( 1005 | &self, 1006 | psbt: &str, 1007 | extract: Option, 1008 | ) -> Result { 1009 | let mut args = [into_json(psbt)?, opt_into_json(extract)?]; 1010 | self.call("finalizepsbt", handle_defaults(&mut args, &[true.into()])).await 1011 | } 1012 | 1013 | async fn derive_addresses( 1014 | &self, 1015 | descriptor: &str, 1016 | range: Option<[u32; 2]>, 1017 | ) -> Result> { 1018 | let mut args = [into_json(descriptor)?, opt_into_json(range)?]; 1019 | self.call("deriveaddresses", handle_defaults(&mut args, &[null()])).await 1020 | } 1021 | 1022 | async fn rescan_blockchain( 1023 | &self, 1024 | start_from: Option, 1025 | stop_height: Option, 1026 | ) -> Result<(usize, Option)> { 1027 | let mut args = [opt_into_json(start_from)?, opt_into_json(stop_height)?]; 1028 | 1029 | #[derive(Deserialize)] 1030 | struct Response { 1031 | pub start_height: usize, 1032 | pub stop_height: Option, 1033 | } 1034 | let res: Response = 1035 | self.call("rescanblockchain", handle_defaults(&mut args, &[0.into(), null()])).await?; 1036 | Ok((res.start_height, res.stop_height)) 1037 | } 1038 | 1039 | /// Returns statistics about the unspent transaction output set. 1040 | /// This call may take some time. 1041 | async fn get_tx_out_set_info(&self) -> Result { 1042 | self.call("gettxoutsetinfo", &[]).await 1043 | } 1044 | 1045 | /// Returns information about network traffic, including bytes in, bytes out, 1046 | /// and current time. 1047 | async fn get_net_totals(&self) -> Result { 1048 | self.call("getnettotals", &[]).await 1049 | } 1050 | 1051 | /// Returns the estimated network hashes per second based on the last n blocks. 1052 | async fn get_network_hash_ps(&self, nblocks: Option, height: Option) -> Result { 1053 | let mut args = [opt_into_json(nblocks)?, opt_into_json(height)?]; 1054 | self.call("getnetworkhashps", handle_defaults(&mut args, &[null(), null()])).await 1055 | } 1056 | 1057 | /// Returns the total uptime of the server in seconds 1058 | async fn uptime(&self) -> Result { 1059 | self.call("uptime", &[]).await 1060 | } 1061 | 1062 | async fn scan_tx_out_set_blocking( 1063 | &self, 1064 | descriptors: &[json::ScanTxOutRequest], 1065 | ) -> Result { 1066 | self.call("scantxoutset", &["start".into(), into_json(descriptors)?]).await 1067 | } 1068 | } 1069 | 1070 | /// Client implements a JSON-RPC client for the Bitcoin Core daemon or compatible APIs. 1071 | pub struct Client { 1072 | client: jsonrpc::client::Client, 1073 | } 1074 | 1075 | impl fmt::Debug for Client { 1076 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 1077 | write!(f, "bitcoincore_rpc::Client(jsonrpc::client::Client(last_nonce=?))",) 1078 | } 1079 | } 1080 | 1081 | impl Client { 1082 | /// Creates a client to a bitcoind JSON-RPC server. 1083 | /// 1084 | /// Can only return [Err] when using cookie authentication. 1085 | pub async fn new(url: String, auth: Auth) -> Result { 1086 | let mut client = jsonrpc::simple_http::SimpleHttpTransport::builder() 1087 | .url(&url) 1088 | .await 1089 | .map_err(|e| Error::JsonRpc(e.into()))?; 1090 | if let Some((user, pass)) = auth.get_user_pass()? { 1091 | client = client.auth(user, Some(pass)); 1092 | } 1093 | 1094 | Ok(Client { 1095 | client: jsonrpc::client::Client::with_transport(client.build()), 1096 | }) 1097 | } 1098 | 1099 | /// Create a new Client. 1100 | pub fn from_jsonrpc(client: jsonrpc::client::Client) -> Client { 1101 | Client { 1102 | client, 1103 | } 1104 | } 1105 | 1106 | /// Get the underlying JSONRPC client. 1107 | pub fn get_jsonrpc_client(&self) -> &jsonrpc::client::Client { 1108 | &self.client 1109 | } 1110 | } 1111 | 1112 | #[async_trait] 1113 | impl RpcApi for Client { 1114 | /// Call an `cmd` rpc with given `args` list 1115 | async fn call serde::de::Deserialize<'a>>( 1116 | &self, 1117 | cmd: &str, 1118 | args: &[serde_json::Value], 1119 | ) -> Result { 1120 | let v_args: Vec<_> = args 1121 | .iter() 1122 | .map(serde_json::value::to_raw_value) 1123 | .collect::>()?; 1124 | let req = self.client.build_request(cmd, &v_args[..]); 1125 | if log_enabled!(Debug) { 1126 | debug!(target: "bitcoincore_rpc", "JSON-RPC request: {} {}", cmd, serde_json::Value::from(args)); 1127 | } 1128 | 1129 | let resp = self.client.send_request(req).await.map_err(Error::from); 1130 | log_response(cmd, &resp); 1131 | Ok(resp?.result()?) 1132 | } 1133 | } 1134 | 1135 | fn log_response(cmd: &str, resp: &Result) { 1136 | if log_enabled!(Warn) || log_enabled!(Debug) || log_enabled!(Trace) { 1137 | match resp { 1138 | Err(ref e) => { 1139 | if log_enabled!(Debug) { 1140 | debug!(target: "bitcoincore_rpc", "JSON-RPC failed parsing reply of {}: {:?}", cmd, e); 1141 | } 1142 | } 1143 | Ok(ref resp) => { 1144 | if let Some(ref e) = resp.error { 1145 | if log_enabled!(Debug) { 1146 | debug!(target: "bitcoincore_rpc", "JSON-RPC error for {}: {:?}", cmd, e); 1147 | } 1148 | } else if log_enabled!(Trace) { 1149 | let rawnull = 1150 | serde_json::value::to_raw_value(&serde_json::Value::Null).unwrap(); 1151 | let result = resp.result.as_ref().unwrap_or(&rawnull); 1152 | trace!(target: "bitcoincore_rpc", "JSON-RPC response for {}: {}", cmd, result); 1153 | } 1154 | } 1155 | } 1156 | } 1157 | } 1158 | 1159 | #[cfg(test)] 1160 | mod tests { 1161 | use super::*; 1162 | use bitcoin; 1163 | use serde_json; 1164 | use tokio; 1165 | 1166 | #[tokio::test] 1167 | async fn test_raw_tx() { 1168 | use bitcoin::consensus::encode; 1169 | let client = Client::new("http://localhost/".into(), Auth::None).await.unwrap(); 1170 | let tx: bitcoin::Transaction = encode::deserialize(&Vec::::from_hex("0200000001586bd02815cf5faabfec986a4e50d25dbee089bd2758621e61c5fab06c334af0000000006b483045022100e85425f6d7c589972ee061413bcf08dc8c8e589ce37b217535a42af924f0e4d602205c9ba9cb14ef15513c9d946fa1c4b797883e748e8c32171bdf6166583946e35c012103dae30a4d7870cd87b45dd53e6012f71318fdd059c1c2623b8cc73f8af287bb2dfeffffff021dc4260c010000001976a914f602e88b2b5901d8aab15ebe4a97cf92ec6e03b388ac00e1f505000000001976a914687ffeffe8cf4e4c038da46a9b1d37db385a472d88acfd211500").unwrap()).unwrap(); 1171 | 1172 | assert!(client.send_raw_transaction(&tx).await.is_err()); 1173 | assert!(client.send_raw_transaction(&encode::serialize(&tx)).await.is_err()); 1174 | assert!(client.send_raw_transaction("deadbeef").await.is_err()); 1175 | assert!(client.send_raw_transaction("deadbeef".to_owned()).await.is_err()); 1176 | } 1177 | 1178 | fn test_handle_defaults_inner() -> Result<()> { 1179 | { 1180 | let mut args = [into_json(0)?, null(), null()]; 1181 | let defaults = [into_json(1)?, into_json(2)?]; 1182 | let res = [into_json(0)?]; 1183 | assert_eq!(handle_defaults(&mut args, &defaults), &res); 1184 | } 1185 | { 1186 | let mut args = [into_json(0)?, into_json(1)?, null()]; 1187 | let defaults = [into_json(2)?]; 1188 | let res = [into_json(0)?, into_json(1)?]; 1189 | assert_eq!(handle_defaults(&mut args, &defaults), &res); 1190 | } 1191 | { 1192 | let mut args = [into_json(0)?, null(), into_json(5)?]; 1193 | let defaults = [into_json(2)?, into_json(3)?]; 1194 | let res = [into_json(0)?, into_json(2)?, into_json(5)?]; 1195 | assert_eq!(handle_defaults(&mut args, &defaults), &res); 1196 | } 1197 | { 1198 | let mut args = [into_json(0)?, null(), into_json(5)?, null()]; 1199 | let defaults = [into_json(2)?, into_json(3)?, into_json(4)?]; 1200 | let res = [into_json(0)?, into_json(2)?, into_json(5)?]; 1201 | assert_eq!(handle_defaults(&mut args, &defaults), &res); 1202 | } 1203 | { 1204 | let mut args = [null(), null()]; 1205 | let defaults = [into_json(2)?, into_json(3)?]; 1206 | let res: [serde_json::Value; 0] = []; 1207 | assert_eq!(handle_defaults(&mut args, &defaults), &res); 1208 | } 1209 | { 1210 | let mut args = [null(), into_json(1)?]; 1211 | let defaults = []; 1212 | let res = [null(), into_json(1)?]; 1213 | assert_eq!(handle_defaults(&mut args, &defaults), &res); 1214 | } 1215 | { 1216 | let mut args = []; 1217 | let defaults = []; 1218 | let res: [serde_json::Value; 0] = []; 1219 | assert_eq!(handle_defaults(&mut args, &defaults), &res); 1220 | } 1221 | { 1222 | let mut args = [into_json(0)?]; 1223 | let defaults = [into_json(2)?]; 1224 | let res = [into_json(0)?]; 1225 | assert_eq!(handle_defaults(&mut args, &defaults), &res); 1226 | } 1227 | Ok(()) 1228 | } 1229 | 1230 | #[test] 1231 | fn test_handle_defaults() { 1232 | test_handle_defaults_inner().unwrap(); 1233 | } 1234 | } 1235 | -------------------------------------------------------------------------------- /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 super::bitcoin; 14 | use bitcoin::hashes::hex; 15 | use bitcoin::secp256k1; 16 | use jsonrpc_async as jsonrpc; 17 | 18 | /// The error type for errors produced in this library. 19 | #[derive(Debug)] 20 | pub enum Error { 21 | JsonRpc(jsonrpc::error::Error), 22 | Hex(hex::Error), 23 | Json(serde_json::error::Error), 24 | BitcoinSerialization(bitcoin::consensus::encode::Error), 25 | Secp256k1(secp256k1::Error), 26 | Io(io::Error), 27 | InvalidAmount(bitcoin::util::amount::ParseAmountError), 28 | InvalidCookieFile, 29 | /// The JSON result had an unexpected structure. 30 | UnexpectedStructure, 31 | } 32 | 33 | impl From for Error { 34 | fn from(e: jsonrpc::error::Error) -> Error { 35 | Error::JsonRpc(e) 36 | } 37 | } 38 | 39 | impl From for Error { 40 | fn from(e: hex::Error) -> Error { 41 | Error::Hex(e) 42 | } 43 | } 44 | 45 | impl From for Error { 46 | fn from(e: serde_json::error::Error) -> Error { 47 | Error::Json(e) 48 | } 49 | } 50 | 51 | impl From for Error { 52 | fn from(e: bitcoin::consensus::encode::Error) -> Error { 53 | Error::BitcoinSerialization(e) 54 | } 55 | } 56 | 57 | impl From for Error { 58 | fn from(e: secp256k1::Error) -> Error { 59 | Error::Secp256k1(e) 60 | } 61 | } 62 | 63 | impl From for Error { 64 | fn from(e: io::Error) -> Error { 65 | Error::Io(e) 66 | } 67 | } 68 | 69 | impl From for Error { 70 | fn from(e: bitcoin::util::amount::ParseAmountError) -> Error { 71 | Error::InvalidAmount(e) 72 | } 73 | } 74 | 75 | impl fmt::Display for Error { 76 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 77 | match *self { 78 | Error::JsonRpc(ref e) => write!(f, "JSON-RPC error: {}", e), 79 | Error::Hex(ref e) => write!(f, "hex decode error: {}", e), 80 | Error::Json(ref e) => write!(f, "JSON error: {}", e), 81 | Error::BitcoinSerialization(ref e) => write!(f, "Bitcoin serialization error: {}", e), 82 | Error::Secp256k1(ref e) => write!(f, "secp256k1 error: {}", e), 83 | Error::Io(ref e) => write!(f, "I/O error: {}", e), 84 | Error::InvalidAmount(ref e) => write!(f, "invalid amount: {}", e), 85 | Error::InvalidCookieFile => write!(f, "invalid cookie file"), 86 | Error::UnexpectedStructure => write!(f, "the JSON result had an unexpected structure"), 87 | } 88 | } 89 | } 90 | 91 | impl error::Error for Error { 92 | fn description(&self) -> &str { 93 | "bitcoincore-rpc error" 94 | } 95 | 96 | fn cause(&self) -> Option<&dyn error::Error> { 97 | match *self { 98 | Error::JsonRpc(ref e) => Some(e), 99 | Error::Hex(ref e) => Some(e), 100 | Error::Json(ref e) => Some(e), 101 | Error::BitcoinSerialization(ref e) => Some(e), 102 | Error::Secp256k1(ref e) => Some(e), 103 | Error::Io(ref e) => Some(e), 104 | _ => None, 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /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 | pub use bitcoincore_rpc_json_async as json; 17 | pub use json::bitcoin; 18 | pub use jsonrpc_async as jsonrpc; 19 | 20 | mod client; 21 | mod error; 22 | mod queryable; 23 | 24 | pub use client::*; 25 | pub use error::Error; 26 | pub use queryable::*; 27 | -------------------------------------------------------------------------------- /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 super::bitcoin; 12 | use super::json; 13 | 14 | use crate::client::Result; 15 | use crate::client::RpcApi; 16 | use async_trait::async_trait; 17 | 18 | /// A type that can be queried from Bitcoin Core. 19 | #[async_trait] 20 | pub trait Queryable: Sized + Send + Sync { 21 | /// Type of the ID used to query the item. 22 | type Id; 23 | /// Query the item using `rpc` and convert to `Self`. 24 | async fn query(rpc: &C, id: &Self::Id) -> Result; 25 | } 26 | 27 | #[async_trait] 28 | impl Queryable for bitcoin::blockdata::block::Block { 29 | type Id = bitcoin::BlockHash; 30 | 31 | async fn query(rpc: &C, id: &Self::Id) -> Result { 32 | let rpc_name = "getblock"; 33 | let hex: String = rpc.call(rpc_name, &[serde_json::to_value(id)?, 0.into()]).await?; 34 | let bytes: Vec = bitcoin::hashes::hex::FromHex::from_hex(&hex)?; 35 | Ok(bitcoin::consensus::encode::deserialize(&bytes)?) 36 | } 37 | } 38 | 39 | #[async_trait] 40 | impl Queryable for bitcoin::blockdata::transaction::Transaction { 41 | type Id = bitcoin::Txid; 42 | 43 | async fn query(rpc: &C, id: &Self::Id) -> Result { 44 | let rpc_name = "getrawtransaction"; 45 | let hex: String = rpc.call(rpc_name, &[serde_json::to_value(id)?]).await?; 46 | let bytes: Vec = bitcoin::hashes::hex::FromHex::from_hex(&hex)?; 47 | Ok(bitcoin::consensus::encode::deserialize(&bytes)?) 48 | } 49 | } 50 | 51 | #[async_trait] 52 | impl Queryable for Option { 53 | type Id = bitcoin::OutPoint; 54 | 55 | async fn query(rpc: &C, id: &Self::Id) -> Result { 56 | rpc.get_tx_out(&id.txid, id.vout, Some(true)).await 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /contrib/test.sh: -------------------------------------------------------------------------------- 1 | 2 | set -xe 3 | 4 | # Just echo all the relevant env vars to help debug Travis. 5 | echo "TRAVIS_RUST_VERSION: \"$TRAVIS_RUST_VERSION\"" 6 | echo "RUSTFMTCHECK: \"$RUSTFMTCHECK\"" 7 | echo "BITCOINVERSION: \"$BITCOINVERSION\"" 8 | echo "PATH: \"$PATH\"" 9 | 10 | 11 | # Pin dependencies for Rust v1.29 12 | if [ "$TRAVIS_RUST_VERSION" = "1.29.0" ]; then 13 | cargo generate-lockfile --verbose 14 | cargo update --verbose --package "cc" --precise "1.0.41" 15 | cargo update --verbose --package "cfg-if" --precise "0.1.9" 16 | cargo update --verbose --package "unicode-normalization" --precise "0.1.9" 17 | cargo update --verbose --package "serde_json" --precise "1.0.39" 18 | cargo update --verbose --package "serde" --precise "1.0.98" 19 | cargo update --verbose --package "serde_derive" --precise "1.0.98" 20 | fi 21 | 22 | if [ -n "$RUSTFMTCHECK" ]; then 23 | rustup component add rustfmt 24 | cargo fmt --all -- --check 25 | fi 26 | 27 | # Integration test. 28 | if [ -n "$BITCOINVERSION" ]; then 29 | wget https://bitcoincore.org/bin/bitcoin-core-$BITCOINVERSION/bitcoin-$BITCOINVERSION-x86_64-linux-gnu.tar.gz 30 | tar -xzvf bitcoin-$BITCOINVERSION-x86_64-linux-gnu.tar.gz 31 | export PATH=$PATH:$(pwd)/bitcoin-$BITCOINVERSION/bin 32 | cd integration_test 33 | ./run.sh 34 | exit 0 35 | fi 36 | 37 | # Regular build/unit test. 38 | cargo build --verbose 39 | cargo test --verbose 40 | cargo build --verbose --examples 41 | -------------------------------------------------------------------------------- /integration_test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "integration_test_async" 3 | version = "0.1.0" 4 | authors = ["Jeremy Rubin ", "Steven Roose "] 5 | edition="2018" 6 | publish=false 7 | 8 | [dependencies] 9 | bitcoincore-rpc-async = { path = "../client" } 10 | bitcoin = { version = "0.28.0-rc.3", features = [ "use-serde", "rand" ], package = "sapio-bitcoin" } 11 | lazy_static = "1.4.0" 12 | log = "0.4" 13 | tokio = { version = "1", features = ["full"] } 14 | -------------------------------------------------------------------------------- /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 | PID2=$! 39 | 40 | # Let it connect to the other node. 41 | sleep 5 42 | 43 | RPC_URL=http://localhost:12349 \ 44 | RPC_COOKIE=${TESTDIR}/2/regtest/.cookie \ 45 | cargo run 46 | 47 | RESULT=$? 48 | 49 | kill -9 $PID1 $PID2 50 | 51 | exit $RESULT 52 | -------------------------------------------------------------------------------- /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 | 18 | use bitcoincore_rpc::json; 19 | use bitcoincore_rpc::jsonrpc::error::Error as JsonRpcError; 20 | use bitcoincore_rpc::{Auth, Client, Error, RpcApi}; 21 | use bitcoincore_rpc_async as bitcoincore_rpc; 22 | 23 | use bitcoin::consensus::encode::{deserialize, serialize}; 24 | use bitcoin::hashes::hex::{FromHex, ToHex}; 25 | use bitcoin::hashes::Hash; 26 | use bitcoin::secp256k1; 27 | use bitcoin::{ 28 | Address, Amount, EcdsaSighashType, Network, OutPoint, PrivateKey, Script, SignedAmount, 29 | Transaction, TxIn, TxOut, Txid, Witness, 30 | }; 31 | use bitcoincore_rpc::json::ScanTxOutRequest; 32 | 33 | lazy_static! { 34 | static ref SECP: secp256k1::Secp256k1 = secp256k1::Secp256k1::new(); 35 | static ref NET: Network = Network::Regtest; 36 | /// A random address not owned by the node. 37 | static ref RANDOM_ADDRESS: Address = "mgR9fN5UzZ64mSUUtk6NwxxS6kwVfoEtPG".parse().unwrap(); 38 | /// The default fee amount to use when needed. 39 | static ref FEE: Amount = Amount::from_btc(0.001).unwrap(); 40 | } 41 | 42 | struct StdLogger; 43 | 44 | impl log::Log for StdLogger { 45 | fn enabled(&self, metadata: &log::Metadata) -> bool { 46 | metadata.target().contains("jsonrpc") || metadata.target().contains("bitcoincore_rpc") 47 | } 48 | 49 | fn log(&self, record: &log::Record) { 50 | if self.enabled(record.metadata()) { 51 | println!("[{}][{}]: {}", record.level(), record.metadata().target(), record.args()); 52 | } 53 | } 54 | 55 | fn flush(&self) {} 56 | } 57 | 58 | static LOGGER: StdLogger = StdLogger; 59 | 60 | /// Assert that the call returns a "deprecated" error. 61 | macro_rules! assert_deprecated { 62 | ($call:expr) => { 63 | match $call.await.unwrap_err() { 64 | Error::JsonRpc(JsonRpcError::Rpc(ref e)) if e.code == -32 => {} 65 | e => panic!("expected deprecated error for {}, got: {}", stringify!($call), e), 66 | } 67 | }; 68 | } 69 | 70 | /// Assert that the call returns a "method not found" error. 71 | macro_rules! assert_not_found { 72 | ($call:expr) => { 73 | match $call.await.unwrap_err() { 74 | Error::JsonRpc(JsonRpcError::Rpc(ref e)) if e.code == -32601 => {} 75 | e => panic!("expected method not found error for {}, got: {}", stringify!($call), e), 76 | } 77 | }; 78 | } 79 | 80 | static mut VERSION: usize = 0; 81 | /// Get the version of the node that is running. 82 | fn version() -> usize { 83 | unsafe { VERSION } 84 | } 85 | 86 | /// Quickly create a BTC amount. 87 | fn btc>(btc: F) -> Amount { 88 | Amount::from_btc(btc.into()).unwrap() 89 | } 90 | /// Quickly create a signed BTC amount. 91 | fn sbtc>(btc: F) -> SignedAmount { 92 | SignedAmount::from_btc(btc.into()).unwrap() 93 | } 94 | 95 | fn get_rpc_url() -> String { 96 | std::env::var("RPC_URL").expect("RPC_URL must be set") 97 | } 98 | 99 | fn get_auth() -> bitcoincore_rpc::Auth { 100 | if let Ok(cookie) = std::env::var("RPC_COOKIE") { 101 | Auth::CookieFile(cookie.into()) 102 | } else if let Ok(user) = std::env::var("RPC_USER") { 103 | Auth::UserPass(user, std::env::var("RPC_PASS").unwrap_or_default()) 104 | } else { 105 | panic!("Either RPC_COOKIE or RPC_USER + RPC_PASS must be set.") 106 | } 107 | } 108 | 109 | #[tokio::main] 110 | async fn main() { 111 | log::set_logger(&LOGGER).map(|()| log::set_max_level(log::LevelFilter::max())).unwrap(); 112 | 113 | // let rpc_url = get_rpc_url(); 114 | let rpc_url = format!("{}/wallet/testwallet", get_rpc_url()); 115 | let auth = get_auth(); 116 | 117 | let cl = Client::new(rpc_url, auth).await.unwrap(); 118 | 119 | test_get_network_info(&cl).await; 120 | unsafe { VERSION = cl.version().await.unwrap() }; 121 | println!("Version: {}", version()); 122 | 123 | cl.create_wallet("testwallet", None, None, None, None).await.unwrap(); 124 | 125 | test_get_mining_info(&cl).await; 126 | test_get_blockchain_info(&cl).await; 127 | test_get_new_address(&cl).await; 128 | test_dump_private_key(&cl).await; 129 | test_generate(&cl).await; 130 | test_get_balance_generate_to_address(&cl).await; 131 | test_get_balances_generate_to_address(&cl).await; 132 | test_get_best_block_hash(&cl).await; 133 | test_get_block_count(&cl).await; 134 | test_get_block_hash(&cl).await; 135 | test_get_block(&cl).await; 136 | test_get_block_header_get_block_header_info(&cl).await; 137 | test_get_address_info(&cl).await; 138 | test_set_label(&cl).await; 139 | test_send_to_address(&cl).await; 140 | test_get_received_by_address(&cl).await; 141 | test_list_unspent(&cl).await; 142 | test_get_difficulty(&cl).await; 143 | test_get_connection_count(&cl).await; 144 | test_get_raw_transaction(&cl).await; 145 | test_get_raw_mempool(&cl).await; 146 | test_get_transaction(&cl).await; 147 | test_list_transactions(&cl).await; 148 | test_list_since_block(&cl).await; 149 | test_get_tx_out(&cl).await; 150 | test_get_tx_out_proof(&cl).await; 151 | test_get_mempool_entry(&cl).await; 152 | test_lock_unspent_unlock_unspent(&cl).await; 153 | test_get_block_filter(&cl).await; 154 | test_sign_raw_transaction_with_send_raw_transaction(&cl).await; 155 | test_invalidate_block_reconsider_block(&cl).await; 156 | test_key_pool_refill(&cl).await; 157 | test_create_raw_transaction(&cl).await; 158 | test_fund_raw_transaction(&cl).await; 159 | test_test_mempool_accept(&cl).await; 160 | test_wallet_create_funded_psbt(&cl).await; 161 | test_combine_psbt(&cl).await; 162 | test_finalize_psbt(&cl).await; 163 | test_list_received_by_address(&cl).await; 164 | test_scantxoutset(&cl).await; 165 | test_import_public_key(&cl).await; 166 | test_import_priv_key(&cl).await; 167 | test_import_address(&cl).await; 168 | test_import_address_script(&cl).await; 169 | test_estimate_smart_fee(&cl).await; 170 | test_ping(&cl).await; 171 | test_get_peer_info(&cl).await; 172 | test_rescan_blockchain(&cl).await; 173 | test_create_wallet(&cl).await; 174 | test_get_tx_out_set_info(&cl).await; 175 | test_get_net_totals(&cl).await; 176 | test_get_network_hash_ps(&cl).await; 177 | test_uptime(&cl).await; 178 | //TODO import_multi( 179 | //TODO verify_message( 180 | //TODO wait_for_new_block(&self, timeout: u64) -> Result { 181 | //TODO wait_for_block( 182 | //TODO get_descriptor_info(&self, desc: &str) -> Result { 183 | //TODO derive_addresses(&self, descriptor: &str, range: Option<[u32; 2]>) -> Result> { 184 | //TODO encrypt_wallet(&self, passphrase: &str) -> Result<()> { 185 | //TODO get_by_id>( 186 | //TODO add_multisig_address( 187 | //TODO load_wallet(&self, wallet: &str) -> Result { 188 | //TODO unload_wallet(&self, wallet: Option<&str>) -> Result<()> { 189 | //TODO backup_wallet(&self, destination: Option<&str>) -> Result<()> { 190 | test_stop(cl).await; 191 | } 192 | 193 | async fn test_get_network_info(cl: &Client) { 194 | let _ = cl.get_network_info().await.unwrap(); 195 | } 196 | 197 | async fn test_get_mining_info(cl: &Client) { 198 | let _ = cl.get_mining_info().await.unwrap(); 199 | } 200 | 201 | async fn test_get_blockchain_info(cl: &Client) { 202 | let info = cl.get_blockchain_info().await.unwrap(); 203 | assert_eq!(&info.chain, "regtest"); 204 | } 205 | 206 | async fn test_get_new_address(cl: &Client) { 207 | let addr = cl.get_new_address(None, Some(json::AddressType::Legacy)).await.unwrap(); 208 | assert_eq!(addr.address_type(), Some(bitcoin::AddressType::P2pkh)); 209 | 210 | let addr = cl.get_new_address(None, Some(json::AddressType::Bech32)).await.unwrap(); 211 | assert_eq!(addr.address_type(), Some(bitcoin::AddressType::P2wpkh)); 212 | 213 | let addr = cl.get_new_address(None, Some(json::AddressType::P2shSegwit)).await.unwrap(); 214 | assert_eq!(addr.address_type(), Some(bitcoin::AddressType::P2sh)); 215 | } 216 | 217 | async fn test_dump_private_key(cl: &Client) { 218 | let addr = cl.get_new_address(None, Some(json::AddressType::Bech32)).await.unwrap(); 219 | let sk = cl.dump_private_key(&addr).await.unwrap(); 220 | assert_eq!(addr, Address::p2wpkh(&sk.public_key(&SECP), *NET).unwrap()); 221 | } 222 | 223 | async fn test_generate(cl: &Client) { 224 | if version() < 180000 { 225 | let blocks = cl.generate(4, None).await.unwrap(); 226 | assert_eq!(blocks.len(), 4); 227 | let blocks = cl.generate(6, Some(45)).await.unwrap(); 228 | assert_eq!(blocks.len(), 6); 229 | } else if version() < 190000 { 230 | assert_deprecated!(cl.generate(5, None)); 231 | } else if version() < 210000 { 232 | assert_not_found!(cl.generate(5, None)); 233 | } else { 234 | assert_not_found!(cl.generate(5, None)); 235 | } 236 | } 237 | 238 | async fn test_get_balance_generate_to_address(cl: &Client) { 239 | let initial = cl.get_balance(None, None).await.unwrap(); 240 | 241 | let blocks = 242 | cl.generate_to_address(500, &cl.get_new_address(None, None).await.unwrap()).await.unwrap(); 243 | assert_eq!(blocks.len(), 500); 244 | assert_ne!(cl.get_balance(None, None).await.unwrap(), initial); 245 | } 246 | 247 | async fn test_get_balances_generate_to_address(cl: &Client) { 248 | if version() >= 190000 { 249 | let initial = cl.get_balances().await.unwrap(); 250 | 251 | let blocks = cl 252 | .generate_to_address(500, &cl.get_new_address(None, None).await.unwrap()) 253 | .await 254 | .unwrap(); 255 | assert_eq!(blocks.len(), 500); 256 | assert_ne!(cl.get_balances().await.unwrap(), initial); 257 | } 258 | } 259 | 260 | async fn test_get_best_block_hash(cl: &Client) { 261 | let _ = cl.get_best_block_hash().await.unwrap(); 262 | } 263 | 264 | async fn test_get_block_count(cl: &Client) { 265 | let height = cl.get_block_count().await.unwrap(); 266 | assert!(height > 0); 267 | } 268 | 269 | async fn test_get_block_hash(cl: &Client) { 270 | let h = cl.get_block_count().await.unwrap(); 271 | assert_eq!(cl.get_block_hash(h).await.unwrap(), cl.get_best_block_hash().await.unwrap()); 272 | } 273 | 274 | async fn test_get_block(cl: &Client) { 275 | let tip = cl.get_best_block_hash().await.unwrap(); 276 | let block = cl.get_block(&tip).await.unwrap(); 277 | let hex = cl.get_block_hex(&tip).await.unwrap(); 278 | assert_eq!(block, deserialize(&Vec::::from_hex(&hex).unwrap()).unwrap()); 279 | assert_eq!(hex, serialize(&block).to_hex()); 280 | 281 | let tip = cl.get_best_block_hash().await.unwrap(); 282 | let info = cl.get_block_info(&tip).await.unwrap(); 283 | assert_eq!(info.hash, tip); 284 | assert_eq!(info.confirmations, 1); 285 | } 286 | 287 | async fn test_get_block_header_get_block_header_info(cl: &Client) { 288 | let tip = cl.get_best_block_hash().await.unwrap(); 289 | let header = cl.get_block_header(&tip).await.unwrap(); 290 | let info = cl.get_block_header_info(&tip).await.unwrap(); 291 | assert_eq!(header.block_hash(), info.hash); 292 | assert_eq!(header.version, info.version); 293 | assert_eq!(header.merkle_root, info.merkle_root); 294 | assert_eq!(info.confirmations, 1); 295 | assert_eq!(info.next_block_hash, None); 296 | assert!(info.previous_block_hash.is_some()); 297 | } 298 | 299 | async fn test_get_address_info(cl: &Client) { 300 | let addr = cl.get_new_address(None, Some(json::AddressType::Legacy)).await.unwrap(); 301 | let info = cl.get_address_info(&addr).await.unwrap(); 302 | assert!(!info.is_witness.unwrap()); 303 | 304 | let addr = cl.get_new_address(None, Some(json::AddressType::Bech32)).await.unwrap(); 305 | let info = cl.get_address_info(&addr).await.unwrap(); 306 | assert!(!info.witness_program.unwrap().is_empty()); 307 | 308 | let addr = cl.get_new_address(None, Some(json::AddressType::P2shSegwit)).await.unwrap(); 309 | let info = cl.get_address_info(&addr).await.unwrap(); 310 | assert!(!info.hex.unwrap().is_empty()); 311 | } 312 | 313 | #[allow(deprecated)] 314 | async fn test_set_label(cl: &Client) { 315 | let addr = cl.get_new_address(Some("label"), None).await.unwrap(); 316 | let info = cl.get_address_info(&addr).await.unwrap(); 317 | if version() >= 20_00_00 { 318 | assert!(info.label.is_none()); 319 | assert_eq!(info.labels[0], json::GetAddressInfoResultLabel::Simple("label".into())); 320 | } else { 321 | assert_eq!(info.label.as_ref().unwrap(), "label"); 322 | assert_eq!( 323 | info.labels[0], 324 | json::GetAddressInfoResultLabel::WithPurpose { 325 | name: "label".into(), 326 | purpose: json::GetAddressInfoResultLabelPurpose::Receive, 327 | } 328 | ); 329 | } 330 | 331 | cl.set_label(&addr, "other").await.unwrap(); 332 | let info = cl.get_address_info(&addr).await.unwrap(); 333 | if version() >= 20_00_00 { 334 | assert!(info.label.is_none()); 335 | assert_eq!(info.labels[0], json::GetAddressInfoResultLabel::Simple("other".into())); 336 | } else { 337 | assert_eq!(info.label.as_ref().unwrap(), "other"); 338 | assert_eq!( 339 | info.labels[0], 340 | json::GetAddressInfoResultLabel::WithPurpose { 341 | name: "other".into(), 342 | purpose: json::GetAddressInfoResultLabelPurpose::Receive, 343 | } 344 | ); 345 | } 346 | } 347 | 348 | async fn test_send_to_address(cl: &Client) { 349 | let addr = cl.get_new_address(None, None).await.unwrap(); 350 | let est = json::EstimateMode::Conservative; 351 | let _ = cl 352 | .send_to_address(&addr, btc(1.0f64), Some("cc"), None, None, None, None, None) 353 | .await 354 | .unwrap(); 355 | let _ = cl 356 | .send_to_address(&addr, btc(1.0f64), None, Some("tt"), None, None, None, None) 357 | .await 358 | .unwrap(); 359 | let _ = cl 360 | .send_to_address(&addr, btc(1.0f64), None, None, Some(true), None, None, None) 361 | .await 362 | .unwrap(); 363 | let _ = cl 364 | .send_to_address(&addr, btc(1.0f64), None, None, None, Some(true), None, None) 365 | .await 366 | .unwrap(); 367 | let _ = cl 368 | .send_to_address(&addr, btc(1.0f64), None, None, None, None, Some(3), None) 369 | .await 370 | .unwrap(); 371 | let _ = cl 372 | .send_to_address(&addr, btc(1.0f64), None, None, None, None, None, Some(est)) 373 | .await 374 | .unwrap(); 375 | } 376 | 377 | async fn test_get_received_by_address(cl: &Client) { 378 | let addr = cl.get_new_address(None, None).await.unwrap(); 379 | let _ = cl.send_to_address(&addr, btc(1), None, None, None, None, None, None).await.unwrap(); 380 | assert_eq!(cl.get_received_by_address(&addr, Some(0)).await.unwrap(), btc(1)); 381 | assert_eq!(cl.get_received_by_address(&addr, Some(1)).await.unwrap(), btc(0)); 382 | let _ = 383 | cl.generate_to_address(7, &cl.get_new_address(None, None).await.unwrap()).await.unwrap(); 384 | assert_eq!(cl.get_received_by_address(&addr, Some(6)).await.unwrap(), btc(1)); 385 | assert_eq!(cl.get_received_by_address(&addr, None).await.unwrap(), btc(1)); 386 | } 387 | 388 | async fn test_list_unspent(cl: &Client) { 389 | let addr = cl.get_new_address(None, None).await.unwrap(); 390 | let txid = cl.send_to_address(&addr, btc(1), None, None, None, None, None, None).await.unwrap(); 391 | let unspent = cl.list_unspent(Some(0), None, Some(&[&addr]), None, None).await.unwrap(); 392 | assert_eq!(unspent[0].txid, txid); 393 | assert_eq!(unspent[0].address.as_ref(), Some(&addr)); 394 | assert_eq!(unspent[0].amount, btc(1)); 395 | 396 | let txid = cl.send_to_address(&addr, btc(7), None, None, None, None, None, None).await.unwrap(); 397 | let options = json::ListUnspentQueryOptions { 398 | minimum_amount: Some(btc(7)), 399 | maximum_amount: Some(btc(7)), 400 | ..Default::default() 401 | }; 402 | let unspent = 403 | cl.list_unspent(Some(0), None, Some(&[&addr]), None, Some(options)).await.unwrap(); 404 | assert_eq!(unspent.len(), 1); 405 | assert_eq!(unspent[0].txid, txid); 406 | assert_eq!(unspent[0].address.as_ref(), Some(&addr)); 407 | assert_eq!(unspent[0].amount, btc(7)); 408 | } 409 | 410 | async fn test_get_difficulty(cl: &Client) { 411 | let _ = cl.get_difficulty().await.unwrap(); 412 | } 413 | 414 | async fn test_get_connection_count(cl: &Client) { 415 | let _ = cl.get_connection_count().await.unwrap(); 416 | } 417 | 418 | async fn test_get_raw_transaction(cl: &Client) { 419 | let addr = cl.get_new_address(None, None).await.unwrap(); 420 | let txid = 421 | cl.send_to_address(&addr, btc(1.0f64), None, None, None, None, None, None).await.unwrap(); 422 | let tx = cl.get_raw_transaction(&txid, None).await.unwrap(); 423 | let hex = cl.get_raw_transaction_hex(&txid, None).await.unwrap(); 424 | assert_eq!(tx, deserialize(&Vec::::from_hex(&hex).unwrap()).unwrap()); 425 | assert_eq!(hex, serialize(&tx).to_hex()); 426 | 427 | let info = cl.get_raw_transaction_info(&txid, None).await.unwrap(); 428 | assert_eq!(info.txid, txid); 429 | 430 | let blocks = 431 | cl.generate_to_address(7, &cl.get_new_address(None, None).await.unwrap()).await.unwrap(); 432 | let _ = cl.get_raw_transaction_info(&txid, Some(&blocks[0])).await.unwrap(); 433 | } 434 | 435 | async fn test_get_raw_mempool(cl: &Client) { 436 | let _ = cl.get_raw_mempool().await.unwrap(); 437 | } 438 | 439 | async fn test_get_transaction(cl: &Client) { 440 | let txid = cl 441 | .send_to_address(&RANDOM_ADDRESS, btc(1), None, None, None, None, None, None) 442 | .await 443 | .unwrap(); 444 | let tx = cl.get_transaction(&txid, None).await.unwrap(); 445 | assert_eq!(tx.amount, sbtc(-1.0)); 446 | assert_eq!(tx.info.txid, txid); 447 | 448 | let fake = Txid::hash(&[1, 2]); 449 | assert!(cl.get_transaction(&fake, Some(true)).await.is_err()); 450 | } 451 | 452 | async fn test_list_transactions(cl: &Client) { 453 | let _ = cl.list_transactions(None, None, None, None).await.unwrap(); 454 | let _ = cl.list_transactions(Some("l"), None, None, None).await.unwrap(); 455 | let _ = cl.list_transactions(None, Some(3), None, None).await.unwrap(); 456 | let _ = cl.list_transactions(None, None, Some(3), None).await.unwrap(); 457 | let _ = cl.list_transactions(None, None, None, Some(true)).await.unwrap(); 458 | } 459 | 460 | async fn test_list_since_block(cl: &Client) { 461 | let r = cl.list_since_block(None, None, None, None).await.unwrap(); 462 | assert_eq!(r.lastblock, cl.get_best_block_hash().await.unwrap()); 463 | assert!(!r.transactions.is_empty()); 464 | } 465 | 466 | async fn test_get_tx_out(cl: &Client) { 467 | let txid = cl 468 | .send_to_address(&RANDOM_ADDRESS, btc(1.0f64), None, None, None, None, None, None) 469 | .await 470 | .unwrap(); 471 | let out = cl.get_tx_out(&txid, 0, Some(false)).await.unwrap(); 472 | assert!(out.is_none()); 473 | let out = cl.get_tx_out(&txid, 0, Some(true)).await.unwrap(); 474 | assert!(out.is_some()); 475 | let _ = cl.get_tx_out(&txid, 0, None).await.unwrap(); 476 | } 477 | 478 | async fn test_get_tx_out_proof(cl: &Client) { 479 | let txid1 = cl 480 | .send_to_address(&RANDOM_ADDRESS, btc(1.0f64), None, None, None, None, None, None) 481 | .await 482 | .unwrap(); 483 | let txid2 = cl 484 | .send_to_address(&RANDOM_ADDRESS, btc(1.0f64), None, None, None, None, None, None) 485 | .await 486 | .unwrap(); 487 | let blocks = 488 | cl.generate_to_address(7, &cl.get_new_address(None, None).await.unwrap()).await.unwrap(); 489 | let proof = cl.get_tx_out_proof(&[txid1, txid2], Some(&blocks[0])).await.unwrap(); 490 | assert!(!proof.is_empty()); 491 | } 492 | 493 | async fn test_get_mempool_entry(cl: &Client) { 494 | let txid = cl 495 | .send_to_address(&RANDOM_ADDRESS, btc(1.0f64), None, None, None, None, None, None) 496 | .await 497 | .unwrap(); 498 | let entry = cl.get_mempool_entry(&txid).await.unwrap(); 499 | assert!(entry.spent_by.is_empty()); 500 | 501 | let fake = Txid::hash(&[1, 2]); 502 | assert!(cl.get_mempool_entry(&fake).await.is_err()); 503 | } 504 | 505 | async fn test_lock_unspent_unlock_unspent(cl: &Client) { 506 | let addr = cl.get_new_address(None, None).await.unwrap(); 507 | let txid = 508 | cl.send_to_address(&addr, btc(1.0f64), None, None, None, None, None, None).await.unwrap(); 509 | 510 | assert!(cl.lock_unspent(&[OutPoint::new(txid, 0)]).await.unwrap()); 511 | assert!(cl.unlock_unspent(&[OutPoint::new(txid, 0)]).await.unwrap()); 512 | } 513 | 514 | async fn test_get_block_filter(cl: &Client) { 515 | let blocks = 516 | cl.generate_to_address(7, &cl.get_new_address(None, None).await.unwrap()).await.unwrap(); 517 | if version() >= 190000 { 518 | let _ = cl.get_block_filter(&blocks[0]).await.unwrap(); 519 | } else { 520 | assert_not_found!(cl.get_block_filter(&blocks[0])); 521 | } 522 | } 523 | 524 | async fn test_sign_raw_transaction_with_send_raw_transaction(cl: &Client) { 525 | let sk = PrivateKey { 526 | network: Network::Regtest, 527 | inner: secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()), 528 | compressed: true, 529 | }; 530 | let addr = Address::p2wpkh(&sk.public_key(&SECP), Network::Regtest).unwrap(); 531 | 532 | let options = json::ListUnspentQueryOptions { 533 | minimum_amount: Some(btc(2)), 534 | ..Default::default() 535 | }; 536 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).await.unwrap(); 537 | let unspent = unspent.into_iter().next().unwrap(); 538 | 539 | let tx = Transaction { 540 | version: 1, 541 | lock_time: 0, 542 | input: vec![TxIn { 543 | previous_output: OutPoint { 544 | txid: unspent.txid, 545 | vout: unspent.vout, 546 | }, 547 | sequence: 0xFFFFFFFF, 548 | script_sig: Script::new(), 549 | witness: Witness::new(), 550 | }], 551 | output: vec![TxOut { 552 | value: (unspent.amount - *FEE).as_sat(), 553 | script_pubkey: addr.script_pubkey(), 554 | }], 555 | }; 556 | 557 | let input = json::SignRawTransactionInput { 558 | txid: unspent.txid, 559 | vout: unspent.vout, 560 | script_pub_key: unspent.script_pub_key, 561 | redeem_script: None, 562 | amount: Some(unspent.amount), 563 | }; 564 | let res = cl.sign_raw_transaction_with_wallet(&tx, Some(&[input]), None).await.unwrap(); 565 | assert!(res.complete); 566 | let txid = cl.send_raw_transaction(&res.transaction().unwrap()).await.unwrap(); 567 | 568 | let tx = Transaction { 569 | version: 1, 570 | lock_time: 0, 571 | input: vec![TxIn { 572 | previous_output: OutPoint { 573 | txid, 574 | vout: 0, 575 | }, 576 | script_sig: Script::new(), 577 | sequence: 0xFFFFFFFF, 578 | witness: Witness::new(), 579 | }], 580 | output: vec![TxOut { 581 | value: (unspent.amount - *FEE - *FEE).as_sat(), 582 | script_pubkey: RANDOM_ADDRESS.script_pubkey(), 583 | }], 584 | }; 585 | 586 | let res = cl 587 | .sign_raw_transaction_with_key(&tx, &[sk], None, Some(EcdsaSighashType::All.into())) 588 | .await 589 | .unwrap(); 590 | assert!(res.complete); 591 | let _ = cl.send_raw_transaction(&res.transaction().unwrap()).await.unwrap(); 592 | } 593 | 594 | async fn test_invalidate_block_reconsider_block(cl: &Client) { 595 | let hash = cl.get_best_block_hash().await.unwrap(); 596 | cl.invalidate_block(&hash).await.unwrap(); 597 | cl.reconsider_block(&hash).await.unwrap(); 598 | } 599 | 600 | async fn test_key_pool_refill(cl: &Client) { 601 | cl.key_pool_refill(Some(100)).await.unwrap(); 602 | cl.key_pool_refill(None).await.unwrap(); 603 | } 604 | 605 | async fn test_create_raw_transaction(cl: &Client) { 606 | let options = json::ListUnspentQueryOptions { 607 | minimum_amount: Some(btc(2)), 608 | ..Default::default() 609 | }; 610 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).await.unwrap(); 611 | let unspent = unspent.into_iter().next().unwrap(); 612 | 613 | let input = json::CreateRawTransactionInput { 614 | txid: unspent.txid, 615 | vout: unspent.vout, 616 | sequence: None, 617 | }; 618 | let mut output = HashMap::new(); 619 | output.insert(RANDOM_ADDRESS.to_string(), btc(1.0f64)); 620 | 621 | let tx = cl 622 | .create_raw_transaction(&[input.clone()], &output, Some(500_000), Some(true)) 623 | .await 624 | .unwrap(); 625 | let hex = 626 | cl.create_raw_transaction_hex(&[input], &output, Some(500_000), Some(true)).await.unwrap(); 627 | assert_eq!(tx, deserialize(&Vec::::from_hex(&hex).unwrap()).unwrap()); 628 | assert_eq!(hex, serialize(&tx).to_hex()); 629 | } 630 | 631 | async fn test_fund_raw_transaction(cl: &Client) { 632 | let addr = cl.get_new_address(None, None).await.unwrap(); 633 | let mut output = HashMap::new(); 634 | output.insert(RANDOM_ADDRESS.to_string(), btc(1.0f64)); 635 | 636 | let options = json::FundRawTransactionOptions { 637 | change_address: Some(addr), 638 | change_position: Some(0), 639 | change_type: None, 640 | include_watching: Some(true), 641 | lock_unspents: Some(true), 642 | fee_rate: Some(*FEE), 643 | subtract_fee_from_outputs: Some(vec![0]), 644 | replaceable: Some(true), 645 | conf_target: None, 646 | estimate_mode: None, 647 | }; 648 | let tx = cl.create_raw_transaction_hex(&[], &output, Some(500_000), Some(true)).await.unwrap(); 649 | let funded = cl.fund_raw_transaction(tx, Some(&options), Some(false)).await.unwrap(); 650 | let _ = funded.transaction().unwrap(); 651 | 652 | let options = json::FundRawTransactionOptions { 653 | change_address: None, 654 | change_position: Some(0), 655 | change_type: Some(json::AddressType::Legacy), 656 | include_watching: Some(true), 657 | lock_unspents: Some(true), 658 | fee_rate: None, 659 | subtract_fee_from_outputs: Some(vec![0]), 660 | replaceable: Some(true), 661 | conf_target: Some(2), 662 | estimate_mode: Some(json::EstimateMode::Conservative), 663 | }; 664 | let tx = cl.create_raw_transaction_hex(&[], &output, Some(500_000), Some(true)).await.unwrap(); 665 | let funded = cl.fund_raw_transaction(tx, Some(&options), Some(false)).await.unwrap(); 666 | let _ = funded.transaction().unwrap(); 667 | } 668 | 669 | async fn test_test_mempool_accept(cl: &Client) { 670 | let options = json::ListUnspentQueryOptions { 671 | minimum_amount: Some(btc(2)), 672 | ..Default::default() 673 | }; 674 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).await.unwrap(); 675 | let unspent = unspent.into_iter().next().unwrap(); 676 | 677 | let input = json::CreateRawTransactionInput { 678 | txid: unspent.txid, 679 | vout: unspent.vout, 680 | sequence: Some(0xFFFFFFFF), 681 | }; 682 | let mut output = HashMap::new(); 683 | output.insert(RANDOM_ADDRESS.to_string(), unspent.amount - *FEE); 684 | 685 | let tx = cl 686 | .create_raw_transaction(&[input.clone()], &output, Some(500_000), Some(false)) 687 | .await 688 | .unwrap(); 689 | let res = cl.test_mempool_accept(&[&tx]).await.unwrap(); 690 | assert!(!res[0].allowed); 691 | assert!(res[0].reject_reason.is_some()); 692 | let signed = 693 | cl.sign_raw_transaction_with_wallet(&tx, None, None).await.unwrap().transaction().unwrap(); 694 | let res = cl.test_mempool_accept(&[&signed]).await.unwrap(); 695 | assert!(res[0].allowed, "not allowed: {:?}", res[0].reject_reason); 696 | } 697 | 698 | async fn test_wallet_create_funded_psbt(cl: &Client) { 699 | let addr = cl.get_new_address(None, None).await.unwrap(); 700 | let options = json::ListUnspentQueryOptions { 701 | minimum_amount: Some(btc(2)), 702 | ..Default::default() 703 | }; 704 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).await.unwrap(); 705 | let unspent = unspent.into_iter().next().unwrap(); 706 | 707 | let input = json::CreateRawTransactionInput { 708 | txid: unspent.txid, 709 | vout: unspent.vout, 710 | sequence: None, 711 | }; 712 | let mut output = HashMap::new(); 713 | output.insert(RANDOM_ADDRESS.to_string(), btc(1.0f64)); 714 | 715 | let options = json::WalletCreateFundedPsbtOptions { 716 | change_address: None, 717 | change_position: Some(1), 718 | change_type: Some(json::AddressType::Legacy), 719 | include_watching: Some(true), 720 | lock_unspent: Some(true), 721 | fee_rate: Some(*FEE), 722 | subtract_fee_from_outputs: vec![0], 723 | replaceable: Some(true), 724 | conf_target: None, 725 | estimate_mode: None, 726 | }; 727 | let _ = cl 728 | .wallet_create_funded_psbt( 729 | &[input.clone()], 730 | &output, 731 | Some(500_000), 732 | Some(options), 733 | Some(true), 734 | ) 735 | .await 736 | .unwrap(); 737 | 738 | let options = json::WalletCreateFundedPsbtOptions { 739 | change_address: Some(addr), 740 | change_position: Some(1), 741 | change_type: None, 742 | include_watching: Some(true), 743 | lock_unspent: Some(true), 744 | fee_rate: None, 745 | subtract_fee_from_outputs: vec![0], 746 | replaceable: Some(true), 747 | conf_target: Some(3), 748 | estimate_mode: Some(json::EstimateMode::Conservative), 749 | }; 750 | let psbt = cl 751 | .wallet_create_funded_psbt(&[input], &output, Some(500_000), Some(options), Some(true)) 752 | .await 753 | .unwrap(); 754 | assert!(!psbt.psbt.is_empty()); 755 | } 756 | 757 | async fn test_combine_psbt(cl: &Client) { 758 | let options = json::ListUnspentQueryOptions { 759 | minimum_amount: Some(btc(2)), 760 | ..Default::default() 761 | }; 762 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).await.unwrap(); 763 | let unspent = unspent.into_iter().next().unwrap(); 764 | let input = json::CreateRawTransactionInput { 765 | txid: unspent.txid, 766 | vout: unspent.vout, 767 | sequence: None, 768 | }; 769 | let mut output = HashMap::new(); 770 | output.insert(RANDOM_ADDRESS.to_string(), btc(1.0f64)); 771 | let psbt1 = cl 772 | .wallet_create_funded_psbt(&[input.clone()], &output, Some(500_000), None, Some(true)) 773 | .await 774 | .unwrap(); 775 | 776 | let psbt = cl.combine_psbt(&[psbt1.psbt.clone(), psbt1.psbt]).await.unwrap(); 777 | assert!(!psbt.is_empty()); 778 | } 779 | 780 | async fn test_finalize_psbt(cl: &Client) { 781 | let options = json::ListUnspentQueryOptions { 782 | minimum_amount: Some(btc(2)), 783 | ..Default::default() 784 | }; 785 | let unspent = cl.list_unspent(Some(6), None, None, None, Some(options)).await.unwrap(); 786 | let unspent = unspent.into_iter().next().unwrap(); 787 | let input = json::CreateRawTransactionInput { 788 | txid: unspent.txid, 789 | vout: unspent.vout, 790 | sequence: None, 791 | }; 792 | let mut output = HashMap::new(); 793 | output.insert(RANDOM_ADDRESS.to_string(), btc(1.0f64)); 794 | let psbt = cl 795 | .wallet_create_funded_psbt(&[input.clone()], &output, Some(500_000), None, Some(true)) 796 | .await 797 | .unwrap(); 798 | 799 | let res = cl.finalize_psbt(&psbt.psbt, Some(true)).await.unwrap(); 800 | assert!(!res.complete); 801 | //TODO(stevenroose) add sign psbt and test hex field 802 | //assert!(res.hex.is_some()); 803 | } 804 | 805 | async fn test_list_received_by_address(cl: &Client) { 806 | let addr = cl.get_new_address(None, None).await.unwrap(); 807 | let txid = 808 | cl.send_to_address(&addr, btc(1.0f64), None, None, None, None, None, None).await.unwrap(); 809 | 810 | let _ = cl.list_received_by_address(Some(&addr), None, None, None).await.unwrap(); 811 | let _ = cl.list_received_by_address(Some(&addr), None, Some(true), None).await.unwrap(); 812 | let _ = cl.list_received_by_address(Some(&addr), None, None, Some(true)).await.unwrap(); 813 | let _ = cl.list_received_by_address(None, Some(200), None, None).await.unwrap(); 814 | 815 | let res = cl.list_received_by_address(Some(&addr), Some(0), None, None).await.unwrap(); 816 | assert_eq!(res[0].txids, vec![txid]); 817 | } 818 | 819 | async fn test_import_public_key(cl: &Client) { 820 | let sk = PrivateKey { 821 | network: Network::Regtest, 822 | inner: secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()), 823 | compressed: true, 824 | }; 825 | cl.import_public_key(&sk.public_key(&SECP), None, None).await.unwrap(); 826 | cl.import_public_key(&sk.public_key(&SECP), Some("l"), None).await.unwrap(); 827 | cl.import_public_key(&sk.public_key(&SECP), None, Some(false)).await.unwrap(); 828 | } 829 | 830 | async fn test_import_priv_key(cl: &Client) { 831 | let sk = PrivateKey { 832 | network: Network::Regtest, 833 | inner: secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()), 834 | compressed: true, 835 | }; 836 | cl.import_private_key(&sk, None, None).await.unwrap(); 837 | cl.import_private_key(&sk, Some("l"), None).await.unwrap(); 838 | cl.import_private_key(&sk, None, Some(false)).await.unwrap(); 839 | } 840 | 841 | async fn test_import_address(cl: &Client) { 842 | let sk = PrivateKey { 843 | network: Network::Regtest, 844 | inner: secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()), 845 | compressed: true, 846 | }; 847 | let addr = Address::p2pkh(&sk.public_key(&SECP), Network::Regtest); 848 | cl.import_address(&addr, None, None).await.unwrap(); 849 | cl.import_address(&addr, Some("l"), None).await.unwrap(); 850 | cl.import_address(&addr, None, Some(false)).await.unwrap(); 851 | } 852 | 853 | async fn test_import_address_script(cl: &Client) { 854 | let sk = PrivateKey { 855 | network: Network::Regtest, 856 | inner: secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng()), 857 | compressed: true, 858 | }; 859 | let addr = Address::p2pkh(&sk.public_key(&SECP), Network::Regtest); 860 | cl.import_address_script(&addr.script_pubkey(), None, None, None).await.unwrap(); 861 | cl.import_address_script(&addr.script_pubkey(), Some("l"), None, None).await.unwrap(); 862 | cl.import_address_script(&addr.script_pubkey(), None, Some(false), None).await.unwrap(); 863 | cl.import_address_script(&addr.script_pubkey(), None, None, Some(true)).await.unwrap(); 864 | } 865 | 866 | async fn test_estimate_smart_fee(cl: &Client) { 867 | let mode = json::EstimateMode::Unset; 868 | let res = cl.estimate_smart_fee(3, Some(mode)).await.unwrap(); 869 | 870 | // With a fresh node, we can't get fee estimates. 871 | if let Some(errors) = res.errors { 872 | if errors == ["Insufficient data or no feerate found"] { 873 | println!("Cannot test estimate_smart_fee because no feerate found!"); 874 | return; 875 | } else { 876 | panic!("Unexpected error(s) for estimate_smart_fee: {:?}", errors); 877 | } 878 | } 879 | 880 | assert!(res.fee_rate.is_some(), "no fee estimate available: {:?}", res.errors); 881 | assert!(res.fee_rate.unwrap() >= btc(0)); 882 | } 883 | 884 | async fn test_ping(cl: &Client) { 885 | cl.ping().await.unwrap(); 886 | } 887 | 888 | async fn test_get_peer_info(cl: &Client) { 889 | let info = cl.get_peer_info().await.unwrap(); 890 | if info.is_empty() { 891 | panic!("No peers are connected so we can't test get_peer_info"); 892 | } 893 | } 894 | 895 | async fn test_rescan_blockchain(cl: &Client) { 896 | let count = cl.get_block_count().await.unwrap() as usize; 897 | assert!(count > 21); 898 | let (start, stop) = cl.rescan_blockchain(Some(count - 20), Some(count - 1)).await.unwrap(); 899 | assert_eq!(start, count - 20); 900 | assert_eq!(stop, Some(count - 1)); 901 | } 902 | 903 | async fn test_create_wallet(cl: &Client) { 904 | let wallet_names = vec!["alice", "bob", "carol", "denise", "emily"]; 905 | 906 | struct WalletParams<'a> { 907 | name: &'a str, 908 | disable_private_keys: Option, 909 | blank: Option, 910 | passphrase: Option<&'a str>, 911 | avoid_reuse: Option, 912 | } 913 | 914 | let mut wallet_params = vec![ 915 | WalletParams { 916 | name: wallet_names[0], 917 | disable_private_keys: None, 918 | blank: None, 919 | passphrase: None, 920 | avoid_reuse: None, 921 | }, 922 | WalletParams { 923 | name: wallet_names[1], 924 | disable_private_keys: Some(true), 925 | blank: None, 926 | passphrase: None, 927 | avoid_reuse: None, 928 | }, 929 | WalletParams { 930 | name: wallet_names[2], 931 | disable_private_keys: None, 932 | blank: Some(true), 933 | passphrase: None, 934 | avoid_reuse: None, 935 | }, 936 | ]; 937 | 938 | if version() >= 190000 { 939 | wallet_params.push(WalletParams { 940 | name: wallet_names[3], 941 | disable_private_keys: None, 942 | blank: None, 943 | passphrase: Some("pass"), 944 | avoid_reuse: None, 945 | }); 946 | wallet_params.push(WalletParams { 947 | name: wallet_names[4], 948 | disable_private_keys: None, 949 | blank: None, 950 | passphrase: None, 951 | avoid_reuse: Some(true), 952 | }); 953 | } 954 | 955 | for wallet_param in wallet_params { 956 | let result = cl 957 | .create_wallet( 958 | wallet_param.name, 959 | wallet_param.disable_private_keys, 960 | wallet_param.blank, 961 | wallet_param.passphrase, 962 | wallet_param.avoid_reuse, 963 | ) 964 | .await 965 | .unwrap(); 966 | 967 | assert_eq!(result.name, wallet_param.name); 968 | let expected_warning = match (wallet_param.passphrase, wallet_param.avoid_reuse) { 969 | (None, Some(true)) => { 970 | Some("Empty string given as passphrase, wallet will not be encrypted.".to_string()) 971 | } 972 | _ => Some("".to_string()), 973 | }; 974 | assert_eq!(result.warning, expected_warning); 975 | 976 | let wallet_client_url = format!("{}{}{}", get_rpc_url(), "/wallet/", wallet_param.name); 977 | let wallet_client = Client::new(wallet_client_url, get_auth()).await.unwrap(); 978 | let wallet_info = wallet_client.get_wallet_info().await.unwrap(); 979 | 980 | assert_eq!(wallet_info.wallet_name, wallet_param.name); 981 | 982 | let has_private_keys = !wallet_param.disable_private_keys.unwrap_or(false); 983 | assert_eq!(wallet_info.private_keys_enabled, has_private_keys); 984 | let has_hd_seed = has_private_keys && !wallet_param.blank.unwrap_or(false); 985 | assert_eq!(wallet_info.hd_seed_id.is_some(), has_hd_seed); 986 | let has_avoid_reuse = wallet_param.avoid_reuse.unwrap_or(false); 987 | assert_eq!(wallet_info.avoid_reuse.unwrap_or(false), has_avoid_reuse); 988 | assert_eq!( 989 | wallet_info.scanning.unwrap_or(json::ScanningDetails::NotScanning(false)), 990 | json::ScanningDetails::NotScanning(false) 991 | ); 992 | } 993 | 994 | let mut wallet_list = cl.list_wallets().await.unwrap(); 995 | 996 | wallet_list.sort(); 997 | 998 | // Default wallet 999 | assert_eq!(wallet_list.remove(0), ""); 1000 | 1001 | // Created wallets 1002 | assert!(wallet_list.iter().zip(wallet_names).all(|(a, b)| a == b)); 1003 | } 1004 | 1005 | async fn test_get_tx_out_set_info(cl: &Client) { 1006 | cl.get_tx_out_set_info().await.unwrap(); 1007 | } 1008 | 1009 | async fn test_get_net_totals(cl: &Client) { 1010 | cl.get_net_totals().await.unwrap(); 1011 | } 1012 | 1013 | async fn test_get_network_hash_ps(cl: &Client) { 1014 | cl.get_network_hash_ps(None, None).await.unwrap(); 1015 | } 1016 | 1017 | async fn test_uptime(cl: &Client) { 1018 | cl.uptime().await.unwrap(); 1019 | } 1020 | 1021 | async fn test_scantxoutset(cl: &Client) { 1022 | let addr = cl.get_new_address(None, None).await.unwrap(); 1023 | 1024 | cl.generate_to_address(2, &addr).await.unwrap(); 1025 | cl.generate_to_address(7, &cl.get_new_address(None, None).await.unwrap()).await.unwrap(); 1026 | 1027 | let utxos = cl 1028 | .scan_tx_out_set_blocking(&[ScanTxOutRequest::Single(format!("addr({})", addr))]) 1029 | .await 1030 | .unwrap(); 1031 | 1032 | assert_eq!(utxos.unspents.len(), 2); 1033 | assert_eq!(utxos.success, Some(true)); 1034 | } 1035 | 1036 | async fn test_stop(cl: Client) { 1037 | println!("Stopping: '{}'", cl.stop().await.unwrap()); 1038 | } 1039 | -------------------------------------------------------------------------------- /json/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitcoincore-rpc-json-async" 3 | version = "4.0.1-alpha.2" 4 | authors = [ 5 | "Jeremy Rubin ", 6 | "Steven Roose ", 7 | "Jean Pierre Dudey ", 8 | "Dawid Ciężarkiewicz " 9 | ] 10 | license = "CC0-1.0" 11 | homepage = "https://github.com/jeremyrubin/rust-bitcoincore-rpc-async/" 12 | repository = "https://github.com/jeremyrubin/rust-bitcoincore-rpc-async/" 13 | description = "JSON-enabled type structs for bitcoincore-rpc crate." 14 | keywords = [ "crypto", "bitcoin", "bitcoin-core", "rpc", "asynchronous" ] 15 | readme = "README.md" 16 | edition="2018" 17 | 18 | [lib] 19 | name = "bitcoincore_rpc_json_async" 20 | path = "src/lib.rs" 21 | 22 | [dependencies] 23 | serde = { version = "1", features = [ "derive" ] } 24 | serde_json = "1" 25 | hex = "0.3" 26 | 27 | bitcoin = { package = "sapio-bitcoin", version = "0.28.0-rc.3", features = [ "use-serde" ] } 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /json/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 | pub use bitcoin; 17 | use std::collections::HashMap; 18 | 19 | use bitcoin::consensus::encode; 20 | use bitcoin::hashes::hex::{FromHex, ToHex}; 21 | use bitcoin::hashes::sha256; 22 | use bitcoin::util::{bip158, bip32}; 23 | use bitcoin::{Address, Amount, PrivateKey, PublicKey, Script, SignedAmount, Transaction}; 24 | use serde::de::Error as SerdeError; 25 | use serde::{Deserialize, Serialize}; 26 | 27 | //TODO(stevenroose) consider using a Time type 28 | 29 | /// A module used for serde serialization of bytes in hexadecimal format. 30 | /// 31 | /// The module is compatible with the serde attribute. 32 | pub mod serde_hex { 33 | use bitcoin::hashes::hex::{FromHex, ToHex}; 34 | use serde::de::Error; 35 | use serde::{Deserializer, Serializer}; 36 | 37 | pub fn serialize(b: &Vec, s: S) -> Result { 38 | s.serialize_str(&b.to_hex()) 39 | } 40 | 41 | pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result, D::Error> { 42 | let hex_str: String = ::serde::Deserialize::deserialize(d)?; 43 | FromHex::from_hex(&hex_str).map_err(D::Error::custom) 44 | } 45 | 46 | pub mod opt { 47 | use bitcoin::hashes::hex::{FromHex, ToHex}; 48 | use serde::de::Error; 49 | use serde::{Deserializer, Serializer}; 50 | 51 | pub fn serialize(b: &Option>, s: S) -> Result { 52 | match *b { 53 | None => s.serialize_none(), 54 | Some(ref b) => s.serialize_str(&b.to_hex()), 55 | } 56 | } 57 | 58 | pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result>, D::Error> { 59 | let hex_str: String = ::serde::Deserialize::deserialize(d)?; 60 | Ok(Some(FromHex::from_hex(&hex_str).map_err(D::Error::custom)?)) 61 | } 62 | } 63 | } 64 | 65 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] 66 | pub struct GetNetworkInfoResultNetwork { 67 | pub name: String, 68 | pub limited: bool, 69 | pub reachable: bool, 70 | pub proxy: String, 71 | pub proxy_randomize_credentials: bool, 72 | } 73 | 74 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] 75 | pub struct GetNetworkInfoResultAddress { 76 | pub address: String, 77 | pub port: usize, 78 | pub score: usize, 79 | } 80 | 81 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] 82 | pub struct GetNetworkInfoResult { 83 | pub version: usize, 84 | pub subversion: String, 85 | #[serde(rename = "protocolversion")] 86 | pub protocol_version: usize, 87 | #[serde(rename = "localservices")] 88 | pub local_services: String, 89 | #[serde(rename = "localrelay")] 90 | pub local_relay: bool, 91 | #[serde(rename = "timeoffset")] 92 | pub time_offset: isize, 93 | pub connections: usize, 94 | #[serde(rename = "networkactive")] 95 | pub network_active: bool, 96 | pub networks: Vec, 97 | #[serde(rename = "relayfee", with = "bitcoin::util::amount::serde::as_btc")] 98 | pub relay_fee: Amount, 99 | #[serde(rename = "incrementalfee", with = "bitcoin::util::amount::serde::as_btc")] 100 | pub incremental_fee: Amount, 101 | #[serde(rename = "localaddresses")] 102 | pub local_addresses: Vec, 103 | pub warnings: String, 104 | } 105 | 106 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] 107 | #[serde(rename_all = "camelCase")] 108 | pub struct AddMultiSigAddressResult { 109 | pub address: Address, 110 | pub redeem_script: Script, 111 | } 112 | 113 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] 114 | pub struct LoadWalletResult { 115 | pub name: String, 116 | pub warning: Option, 117 | } 118 | 119 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] 120 | pub struct GetWalletInfoResult { 121 | #[serde(rename = "walletname")] 122 | pub wallet_name: String, 123 | #[serde(rename = "walletversion")] 124 | pub wallet_version: u32, 125 | #[serde(with = "bitcoin::util::amount::serde::as_btc")] 126 | pub balance: Amount, 127 | #[serde(with = "bitcoin::util::amount::serde::as_btc")] 128 | pub unconfirmed_balance: Amount, 129 | #[serde(with = "bitcoin::util::amount::serde::as_btc")] 130 | pub immature_balance: Amount, 131 | #[serde(rename = "txcount")] 132 | pub tx_count: usize, 133 | #[serde(rename = "keypoololdest")] 134 | pub keypool_oldest: usize, 135 | #[serde(rename = "keypoolsize")] 136 | pub keypool_size: usize, 137 | #[serde(rename = "keypoolsize_hd_internal")] 138 | pub keypool_size_hd_internal: usize, 139 | pub unlocked_until: Option, 140 | #[serde(rename = "paytxfee", with = "bitcoin::util::amount::serde::as_btc")] 141 | pub pay_tx_fee: Amount, 142 | #[serde(rename = "hdseedid")] 143 | pub hd_seed_id: Option, 144 | pub private_keys_enabled: bool, 145 | pub avoid_reuse: Option, 146 | pub scanning: Option, 147 | } 148 | 149 | #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] 150 | #[serde(untagged)] 151 | pub enum ScanningDetails { 152 | Scanning { 153 | duration: usize, 154 | progress: f32, 155 | }, 156 | /// The bool in this field will always be false. 157 | NotScanning(bool), 158 | } 159 | 160 | impl Eq for ScanningDetails {} 161 | 162 | #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] 163 | #[serde(rename_all = "camelCase")] 164 | pub struct GetBlockResult { 165 | pub hash: bitcoin::BlockHash, 166 | pub confirmations: u32, 167 | pub size: usize, 168 | pub strippedsize: Option, 169 | pub weight: usize, 170 | pub height: usize, 171 | pub version: i32, 172 | #[serde(default, with = "serde_hex::opt")] 173 | pub version_hex: Option>, 174 | pub merkleroot: bitcoin::TxMerkleNode, 175 | pub tx: Vec, 176 | pub time: usize, 177 | pub mediantime: Option, 178 | pub nonce: u32, 179 | pub bits: String, 180 | pub difficulty: f64, 181 | #[serde(with = "serde_hex")] 182 | pub chainwork: Vec, 183 | pub n_tx: usize, 184 | pub previousblockhash: Option, 185 | pub nextblockhash: Option, 186 | } 187 | 188 | #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] 189 | #[serde(rename_all = "camelCase")] 190 | pub struct GetBlockHeaderResult { 191 | pub hash: bitcoin::BlockHash, 192 | pub confirmations: u32, 193 | pub height: usize, 194 | pub version: i32, 195 | #[serde(default, with = "serde_hex::opt")] 196 | pub version_hex: Option>, 197 | #[serde(rename = "merkleroot")] 198 | pub merkle_root: bitcoin::TxMerkleNode, 199 | pub time: usize, 200 | #[serde(rename = "mediantime")] 201 | pub median_time: Option, 202 | pub nonce: u32, 203 | pub bits: String, 204 | pub difficulty: f64, 205 | #[serde(with = "serde_hex")] 206 | pub chainwork: Vec, 207 | pub n_tx: usize, 208 | #[serde(rename = "previousblockhash")] 209 | pub previous_block_hash: Option, 210 | #[serde(rename = "nextblockhash")] 211 | pub next_block_hash: Option, 212 | } 213 | 214 | #[derive(Clone, PartialEq, Debug, Deserialize, Serialize)] 215 | #[serde(rename_all = "camelCase")] 216 | pub struct GetMiningInfoResult { 217 | pub blocks: u32, 218 | #[serde(rename = "currentblockweight")] 219 | pub current_block_weight: Option, 220 | #[serde(rename = "currentblocktx")] 221 | pub current_block_tx: Option, 222 | pub difficulty: f64, 223 | #[serde(rename = "networkhashps")] 224 | pub network_hash_ps: f64, 225 | #[serde(rename = "pooledtx")] 226 | pub pooled_tx: usize, 227 | pub chain: String, 228 | pub warnings: String, 229 | } 230 | 231 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] 232 | #[serde(rename_all = "camelCase")] 233 | pub struct GetRawTransactionResultVinScriptSig { 234 | pub asm: String, 235 | #[serde(with = "serde_hex")] 236 | pub hex: Vec, 237 | } 238 | 239 | impl GetRawTransactionResultVinScriptSig { 240 | pub fn script(&self) -> Result { 241 | Ok(Script::from(self.hex.clone())) 242 | } 243 | } 244 | 245 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] 246 | #[serde(rename_all = "camelCase")] 247 | pub struct GetRawTransactionResultVin { 248 | pub sequence: u32, 249 | /// The raw scriptSig in case of a coinbase tx. 250 | #[serde(default, with = "serde_hex::opt")] 251 | pub coinbase: Option>, 252 | /// Not provided for coinbase txs. 253 | pub txid: Option, 254 | /// Not provided for coinbase txs. 255 | pub vout: Option, 256 | /// The scriptSig in case of a non-coinbase tx. 257 | pub script_sig: Option, 258 | /// Not provided for coinbase txs. 259 | #[serde(default, deserialize_with = "deserialize_hex_array_opt")] 260 | pub txinwitness: Option>>, 261 | } 262 | 263 | impl GetRawTransactionResultVin { 264 | /// Whether this input is from a coinbase tx. 265 | /// The [txid], [vout] and [script_sig] fields are not provided 266 | /// for coinbase transactions. 267 | pub fn is_coinbase(&self) -> bool { 268 | self.coinbase.is_some() 269 | } 270 | } 271 | 272 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] 273 | #[serde(rename_all = "camelCase")] 274 | pub struct GetRawTransactionResultVoutScriptPubKey { 275 | pub asm: String, 276 | #[serde(with = "serde_hex")] 277 | pub hex: Vec, 278 | pub req_sigs: Option, 279 | #[serde(rename = "type")] 280 | pub type_: Option, 281 | pub addresses: Option>, 282 | } 283 | 284 | impl GetRawTransactionResultVoutScriptPubKey { 285 | pub fn script(&self) -> Result { 286 | Ok(Script::from(self.hex.clone())) 287 | } 288 | } 289 | 290 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] 291 | #[serde(rename_all = "camelCase")] 292 | pub struct GetRawTransactionResultVout { 293 | #[serde(with = "bitcoin::util::amount::serde::as_btc")] 294 | pub value: Amount, 295 | pub n: u32, 296 | pub script_pub_key: GetRawTransactionResultVoutScriptPubKey, 297 | } 298 | 299 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] 300 | #[serde(rename_all = "camelCase")] 301 | pub struct GetRawTransactionResult { 302 | #[serde(rename = "in_active_chain")] 303 | pub in_active_chain: Option, 304 | #[serde(with = "serde_hex")] 305 | pub hex: Vec, 306 | pub txid: bitcoin::Txid, 307 | pub hash: bitcoin::Wtxid, 308 | pub size: usize, 309 | pub vsize: usize, 310 | pub version: u32, 311 | pub locktime: u32, 312 | pub vin: Vec, 313 | pub vout: Vec, 314 | pub blockhash: Option, 315 | pub confirmations: Option, 316 | pub time: Option, 317 | pub blocktime: Option, 318 | } 319 | 320 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] 321 | pub struct GetBlockFilterResult { 322 | pub header: bitcoin::FilterHash, 323 | #[serde(with = "serde_hex")] 324 | pub filter: Vec, 325 | } 326 | 327 | impl GetBlockFilterResult { 328 | /// Get the filter. 329 | /// Note that this copies the underlying filter data. To prevent this, 330 | /// use [into_filter] instead. 331 | pub fn to_filter(&self) -> bip158::BlockFilter { 332 | bip158::BlockFilter::new(&self.filter) 333 | } 334 | 335 | /// Convert the result in the filter type. 336 | pub fn into_filter(self) -> bip158::BlockFilter { 337 | bip158::BlockFilter { 338 | content: self.filter, 339 | } 340 | } 341 | } 342 | 343 | impl GetRawTransactionResult { 344 | /// Whether this tx is a coinbase tx. 345 | pub fn is_coinbase(&self) -> bool { 346 | self.vin.len() == 1 && self.vin[0].is_coinbase() 347 | } 348 | 349 | pub fn transaction(&self) -> Result { 350 | encode::deserialize(&self.hex) 351 | } 352 | } 353 | 354 | /// Enum to represent the BIP125 replaceable status for a transaction. 355 | #[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] 356 | #[serde(rename_all = "lowercase")] 357 | pub enum Bip125Replaceable { 358 | Yes, 359 | No, 360 | Unknown, 361 | } 362 | 363 | /// Enum to represent the category of a transaction. 364 | #[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] 365 | #[serde(rename_all = "lowercase")] 366 | pub enum GetTransactionResultDetailCategory { 367 | Send, 368 | Receive, 369 | Generate, 370 | Immature, 371 | Orphan, 372 | } 373 | 374 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize)] 375 | pub struct GetTransactionResultDetail { 376 | pub address: Option
, 377 | pub category: GetTransactionResultDetailCategory, 378 | #[serde(with = "bitcoin::util::amount::serde::as_btc")] 379 | pub amount: SignedAmount, 380 | pub label: Option, 381 | pub vout: u32, 382 | #[serde(default, with = "bitcoin::util::amount::serde::as_btc::opt")] 383 | pub fee: Option, 384 | pub abandoned: Option, 385 | } 386 | 387 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize)] 388 | pub struct WalletTxInfo { 389 | pub confirmations: i32, 390 | pub blockhash: Option, 391 | pub blockindex: Option, 392 | pub blocktime: Option, 393 | pub blockheight: Option, 394 | pub txid: bitcoin::Txid, 395 | pub time: u64, 396 | pub timereceived: u64, 397 | #[serde(rename = "bip125-replaceable")] 398 | pub bip125_replaceable: Bip125Replaceable, 399 | } 400 | 401 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize)] 402 | pub struct GetTransactionResult { 403 | #[serde(flatten)] 404 | pub info: WalletTxInfo, 405 | #[serde(with = "bitcoin::util::amount::serde::as_btc")] 406 | pub amount: SignedAmount, 407 | #[serde(default, with = "bitcoin::util::amount::serde::as_btc::opt")] 408 | pub fee: Option, 409 | pub details: Vec, 410 | #[serde(with = "serde_hex")] 411 | pub hex: Vec, 412 | } 413 | 414 | impl GetTransactionResult { 415 | pub fn transaction(&self) -> Result { 416 | encode::deserialize(&self.hex) 417 | } 418 | } 419 | 420 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize)] 421 | pub struct ListTransactionResult { 422 | #[serde(flatten)] 423 | pub info: WalletTxInfo, 424 | #[serde(flatten)] 425 | pub detail: GetTransactionResultDetail, 426 | 427 | pub trusted: Option, 428 | pub comment: Option, 429 | } 430 | 431 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize)] 432 | pub struct ListSinceBlockResult { 433 | pub transactions: Vec, 434 | #[serde(default)] 435 | pub removed: Vec, 436 | pub lastblock: bitcoin::BlockHash, 437 | } 438 | 439 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] 440 | #[serde(rename_all = "camelCase")] 441 | pub struct GetTxOutResult { 442 | pub bestblock: bitcoin::BlockHash, 443 | pub confirmations: u32, 444 | #[serde(with = "bitcoin::util::amount::serde::as_btc")] 445 | pub value: Amount, 446 | pub script_pub_key: GetRawTransactionResultVoutScriptPubKey, 447 | pub coinbase: bool, 448 | } 449 | 450 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize, Default)] 451 | #[serde(rename_all = "camelCase")] 452 | pub struct ListUnspentQueryOptions { 453 | #[serde( 454 | rename = "minimumAmount", 455 | with = "bitcoin::util::amount::serde::as_btc::opt", 456 | skip_serializing_if = "Option::is_none" 457 | )] 458 | pub minimum_amount: Option, 459 | #[serde( 460 | rename = "maximumAmount", 461 | with = "bitcoin::util::amount::serde::as_btc::opt", 462 | skip_serializing_if = "Option::is_none" 463 | )] 464 | pub maximum_amount: Option, 465 | #[serde(rename = "maximumCount", skip_serializing_if = "Option::is_none")] 466 | pub maximum_count: Option, 467 | #[serde( 468 | rename = "minimumSumAmount", 469 | with = "bitcoin::util::amount::serde::as_btc::opt", 470 | skip_serializing_if = "Option::is_none" 471 | )] 472 | pub minimum_sum_amount: Option, 473 | } 474 | 475 | #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] 476 | #[serde(rename_all = "camelCase")] 477 | pub struct ListUnspentResultEntry { 478 | pub txid: bitcoin::Txid, 479 | pub vout: u32, 480 | pub address: Option
, 481 | pub label: Option, 482 | pub redeem_script: Option