diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..e586beca --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,46 @@ +name: Test + +on: [push] + +env: + CARGO_TERM_COLOR: always + +jobs: + + test: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + features: [ "bitcoind/0_21_1" ] + + + steps: + - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1.2.0 + with: + key: ${{ matrix.features }} + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --all --features ${{ matrix.features }} + + cosmetics: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1.2.0 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + profile: minimal + components: rustfmt, clippy + - name: fmt + run: cargo fmt -- --check +# - name: clippy +# run: cargo clippy -- -D warnings diff --git a/Cargo.toml b/Cargo.toml index df0a30c2..f2a91b5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,5 +3,4 @@ members = [ "json", "client", - "integration_test", ] diff --git a/README.md b/README.md index d94313be..2ac0c217 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +** This is a fork of https://github.com/rust-bitcoin/rust-bitcoincore-rpc with bitcoin 0.27 ** + [![Status](https://travis-ci.org/rust-bitcoin/rust-bitcoincore-rpc.png?branch=master)](https://travis-ci.org/rust-bitcoin/rust-bitcoincore-rpc) # Rust RPC client for Bitcoin Core JSON-RPC diff --git a/client/Cargo.toml b/client/Cargo.toml index de478d87..307e6700 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -1,24 +1,25 @@ [package] -name = "bitcoincore-rpc" -version = "0.13.0" +name = "core-rpc" +version = "0.14.0" authors = [ "Steven Roose ", "Jean Pierre Dudey ", "Dawid Ciężarkiewicz ", + "Riccardo Casatta ", ] license = "CC0-1.0" -homepage = "https://github.com/rust-bitcoin/rust-bitcoincore-rpc/" -repository = "https://github.com/rust-bitcoin/rust-bitcoincore-rpc/" +homepage = "https://github.com/RCasatta/rust-bitcoincore-rpc/" +repository = "https://github.com/RCasatta/rust-bitcoincore-rpc/" description = "RPC client library for the Bitcoin Core JSON-RPC API." keywords = ["crypto", "bitcoin", "bitcoin-core", "rpc"] readme = "README.md" [lib] -name = "bitcoincore_rpc" +name = "core_rpc" path = "src/lib.rs" [dependencies] -bitcoincore-rpc-json = { version = "0.13.0", path = "../json" } +core-rpc-json = { version = "0.14.0", path = "../json" } log = "0.4.5" jsonrpc = "0.12.0" @@ -26,3 +27,9 @@ jsonrpc = "0.12.0" # Used for deserialization of JSON. serde = "1" serde_json = "1" + + +[dev-dependencies] +bitcoin = { version = "0.27.0", features = ["rand"] } +bitcoind = { version = "0.18.0" } +lazy_static = "1.4.0" diff --git a/client/examples/retry_client.rs b/client/examples/retry_client.rs index 10b2fed3..0ef6592c 100644 --- a/client/examples/retry_client.rs +++ b/client/examples/retry_client.rs @@ -8,7 +8,7 @@ // If not, see . // -extern crate bitcoincore_rpc; +extern crate core_rpc as bitcoincore_rpc; extern crate jsonrpc; extern crate serde; extern crate serde_json; diff --git a/client/examples/test_against_node.rs b/client/examples/test_against_node.rs index e658e781..79a95b81 100644 --- a/client/examples/test_against_node.rs +++ b/client/examples/test_against_node.rs @@ -10,7 +10,7 @@ //! A very simple example used as a self-test of this library against a Bitcoin //! Core node. -extern crate bitcoincore_rpc; +extern crate core_rpc as bitcoincore_rpc; use bitcoincore_rpc::{bitcoin, Auth, Client, Error, RpcApi}; diff --git a/client/src/client.rs b/client/src/client.rs index 651acd96..3b258218 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -276,6 +276,7 @@ pub trait RpcApi: Sized { blank: Option, passphrase: Option<&str>, avoid_reuse: Option, + descriptors: Option, ) -> Result { let mut args = [ wallet.into(), @@ -283,13 +284,25 @@ pub trait RpcApi: Sized { opt_into_json(blank)?, opt_into_json(passphrase)?, opt_into_json(avoid_reuse)?, + opt_into_json(descriptors)?, ]; self.call( "createwallet", - handle_defaults(&mut args, &[false.into(), false.into(), into_json("")?, false.into()]), + handle_defaults( + &mut args, + &[false.into(), false.into(), into_json("")?, false.into(), false.into()], + ), ) } + fn import_descriptors( + &self, + descriptors: Vec, + ) -> Result> { + let arg = into_json(descriptors)?; + self.call("importdescriptors", &[arg]) + } + fn list_wallets(&self) -> Result> { self.call("listwallets", &[]) } @@ -1136,7 +1149,6 @@ impl RpcApi for Client { if log_enabled!(Debug) { debug!(target: "bitcoincore_rpc", "JSON-RPC request: {} {}", cmd, serde_json::Value::from(args)); } - let resp = self.client.send_request(req).map_err(Error::from); log_response(cmd, &resp); Ok(resp?.result()?) diff --git a/client/src/lib.rs b/client/src/lib.rs index d1dd7b37..6051ab72 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -13,7 +13,7 @@ //! This is a client library for the Bitcoin Core JSON-RPC API. //! -#![crate_name = "bitcoincore_rpc"] +#![crate_name = "core_rpc"] #![crate_type = "rlib"] #[macro_use] @@ -25,8 +25,8 @@ extern crate serde_json; pub extern crate jsonrpc; -pub extern crate bitcoincore_rpc_json; -pub use bitcoincore_rpc_json as json; +pub extern crate core_rpc_json; +pub use core_rpc_json as json; pub use json::bitcoin; mod client; diff --git a/integration_test/src/main.rs b/client/tests/integration.rs similarity index 93% rename from integration_test/src/main.rs rename to client/tests/integration.rs index 0e35ee79..a268ca42 100644 --- a/integration_test/src/main.rs +++ b/client/tests/integration.rs @@ -11,12 +11,16 @@ #![deny(unused)] extern crate bitcoin; -extern crate bitcoincore_rpc; +extern crate core_rpc as bitcoincore_rpc; #[macro_use] extern crate lazy_static; +extern crate bitcoind; extern crate log; +use bitcoincore_rpc::core_rpc_json as bitcoincore_rpc_json; + use std::collections::HashMap; +use std::str::FromStr; use bitcoincore_rpc::json; use bitcoincore_rpc::jsonrpc::error::Error as JsonRpcError; @@ -30,9 +34,8 @@ use bitcoin::{ Address, Amount, Network, OutPoint, PrivateKey, Script, SigHashType, SignedAmount, Transaction, TxIn, TxOut, Txid, }; -use bitcoincore_rpc::bitcoincore_rpc_json::{ - GetBlockTemplateModes, GetBlockTemplateRules, ScanTxOutRequest, -}; +use bitcoincore_rpc_json::{GetBlockTemplateModes, GetBlockTemplateRules, ScanTxOutRequest}; +use bitcoind::{BitcoinD, P2P}; lazy_static! { static ref SECP: secp256k1::Secp256k1 = secp256k1::Secp256k1::new(); @@ -107,33 +110,30 @@ fn sbtc>(btc: F) -> SignedAmount { SignedAmount::from_btc(btc.into()).unwrap() } -fn get_rpc_url() -> String { - return std::env::var("RPC_URL").expect("RPC_URL must be set"); -} - -fn get_auth() -> bitcoincore_rpc::Auth { - if let Ok(cookie) = std::env::var("RPC_COOKIE") { - return Auth::CookieFile(cookie.into()); - } else if let Ok(user) = std::env::var("RPC_USER") { - return Auth::UserPass(user, std::env::var("RPC_PASS").unwrap_or_default()); - } else { - panic!("Either RPC_COOKIE or RPC_USER + RPC_PASS must be set."); - }; -} - -fn main() { +#[test] +fn integration_test() { log::set_logger(&LOGGER).map(|()| log::set_max_level(log::LevelFilter::max())).unwrap(); - let rpc_url = format!("{}/wallet/testwallet", get_rpc_url()); - let auth = get_auth(); + let mut conf = bitcoind::Conf::default(); + conf.args.push("-blockfilterindex=1"); + conf.p2p = P2P::Yes; + let bitcoind = + bitcoind::BitcoinD::with_conf(bitcoind::downloaded_exe_path().unwrap(), &conf).unwrap(); + + let mut conf2 = bitcoind::Conf::default(); + conf2.p2p = bitcoind.p2p_connect(true).unwrap(); + let _bitcoind2 = + bitcoind::BitcoinD::with_conf(bitcoind::downloaded_exe_path().unwrap(), &conf2).unwrap(); - let cl = Client::new(&rpc_url, auth).unwrap(); + let rpc_url = bitcoind.rpc_url_with_wallet("testwallet"); + let auth = Auth::CookieFile(bitcoind.params.cookie_file.clone()); + let cl = Client::new(&rpc_url, auth.clone()).unwrap(); test_get_network_info(&cl); unsafe { VERSION = cl.version().unwrap() }; println!("Version: {}", version()); - cl.create_wallet("testwallet", None, None, None, None).unwrap(); + cl.create_wallet("testwallet", None, None, None, None, None).unwrap(); test_get_mining_info(&cl); test_get_blockchain_info(&cl); @@ -184,7 +184,7 @@ fn main() { test_ping(&cl); test_get_peer_info(&cl); test_rescan_blockchain(&cl); - test_create_wallet(&cl); + test_create_wallet(&cl, &bitcoind); test_get_tx_out_set_info(&cl); test_get_chain_tips(&cl); test_get_net_totals(&cl); @@ -203,6 +203,11 @@ fn main() { //TODO load_wallet(&self, wallet: &str) -> Result { //TODO unload_wallet(&self, wallet: Option<&str>) -> Result<()> { //TODO backup_wallet(&self, destination: Option<&str>) -> Result<()> { + + let rpc_url = bitcoind.rpc_url_with_wallet("testdescriptorwallet"); + let desc_cl = Client::new(&rpc_url, auth).unwrap(); + + test_descriptor_wallet(&desc_cl); test_stop(cl); } @@ -896,7 +901,7 @@ fn test_rescan_blockchain(cl: &Client) { assert_eq!(stop, Some(count - 1)); } -fn test_create_wallet(cl: &Client) { +fn test_create_wallet(cl: &Client, bitcoind: &BitcoinD) { let wallet_names = vec!["alice", "bob", "carol", "denise", "emily"]; struct WalletParams<'a> { @@ -905,6 +910,7 @@ fn test_create_wallet(cl: &Client) { blank: Option, passphrase: Option<&'a str>, avoid_reuse: Option, + descriptors: Option, } let mut wallet_params = vec![ @@ -914,6 +920,7 @@ fn test_create_wallet(cl: &Client) { blank: None, passphrase: None, avoid_reuse: None, + descriptors: None, }, WalletParams { name: wallet_names[1], @@ -921,6 +928,7 @@ fn test_create_wallet(cl: &Client) { blank: None, passphrase: None, avoid_reuse: None, + descriptors: None, }, WalletParams { name: wallet_names[2], @@ -928,6 +936,7 @@ fn test_create_wallet(cl: &Client) { blank: Some(true), passphrase: None, avoid_reuse: None, + descriptors: None, }, ]; @@ -938,6 +947,7 @@ fn test_create_wallet(cl: &Client) { blank: None, passphrase: Some("pass"), avoid_reuse: None, + descriptors: None, }); wallet_params.push(WalletParams { name: wallet_names[4], @@ -945,6 +955,7 @@ fn test_create_wallet(cl: &Client) { blank: None, passphrase: None, avoid_reuse: Some(true), + descriptors: None, }); } @@ -956,6 +967,7 @@ fn test_create_wallet(cl: &Client) { wallet_param.blank, wallet_param.passphrase, wallet_param.avoid_reuse, + wallet_param.descriptors, ) .unwrap(); @@ -968,8 +980,10 @@ fn test_create_wallet(cl: &Client) { }; assert_eq!(result.warning, expected_warning); - let wallet_client_url = format!("{}{}{}", get_rpc_url(), "/wallet/", wallet_param.name); - let wallet_client = Client::new(&wallet_client_url, get_auth()).unwrap(); + let wallet_client_url = bitcoind.rpc_url_with_wallet(wallet_param.name); + let auth = Auth::CookieFile(bitcoind.params.cookie_file.clone()); + + let wallet_client = Client::new(&wallet_client_url, auth).unwrap(); let wallet_info = wallet_client.get_wallet_info().unwrap(); assert_eq!(wallet_info.wallet_name, wallet_param.name); @@ -995,7 +1009,7 @@ fn test_create_wallet(cl: &Client) { wallet_list.retain(|w| w != "testwallet" && w != ""); // Created wallets - assert!(wallet_list.iter().zip(wallet_names).all(|(a, b)| a == b)); + assert!(wallet_names.iter().any(|e| wallet_list.contains(&e.to_string()))); } fn test_get_tx_out_set_info(cl: &Client) { @@ -1050,3 +1064,25 @@ fn test_getblocktemplate(cl: &Client) { fn test_stop(cl: Client) { println!("Stopping: '{}'", cl.stop().unwrap()); } + +fn test_descriptor_wallet(cl: &Client) { + cl.create_wallet( + "testdescriptorwallet", + Some(false), + Some(true), + Some(""), + Some(false), + Some(true), + ) + .unwrap(); + + cl.import_descriptors( + vec![ + json::ImportDescriptorRequest::new("wpkh(tprv8ZgxMBicQKsPeRBCAfUGsZhyHy9dwWyPqhSJmaMnMJQWWtt8L2SkTeHaiF82CUCGtiTiHAs3cMkjdKckGKiCWeYtvMPF1jDTWYTryRMicpx/86h/1h/0h/0/*)#ymr4jlz6", false), + json::ImportDescriptorRequest::new("wpkh(tprv8ZgxMBicQKsPeRBCAfUGsZhyHy9dwWyPqhSJmaMnMJQWWtt8L2SkTeHaiF82CUCGtiTiHAs3cMkjdKckGKiCWeYtvMPF1jDTWYTryRMicpx/86h/1h/0h/1/*)#40x502jz", true), + ] + ).unwrap(); + + let add = cl.get_new_address(None, Some(json::AddressType::Bech32)).unwrap(); + assert_eq!(add, Address::from_str("bcrt1q7crcza94drr00skmu5x0n00rhmwnthde2frhwk").unwrap()); +} diff --git a/integration_test/Cargo.toml b/integration_test/Cargo.toml deleted file mode 100644 index ba9c3aa5..00000000 --- a/integration_test/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "integration_test" -version = "0.1.0" -authors = ["Steven Roose "] - -[dependencies] -bitcoincore-rpc = { path = "../client" } -bitcoin = { version = "0.26", features = [ "use-serde", "rand" ] } -lazy_static = "1.4.0" -log = "0.4" diff --git a/integration_test/run.sh b/integration_test/run.sh deleted file mode 100755 index b40aa78a..00000000 --- a/integration_test/run.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/sh - -TESTDIR=/tmp/rust_bitcoincore_rpc_test - -rm -rf ${TESTDIR} -mkdir -p ${TESTDIR}/1 ${TESTDIR}/2 - -# To kill any remaining open bitcoind. -killall -9 bitcoind - -bitcoind -regtest \ - -datadir=${TESTDIR}/1 \ - -port=12348 \ - -server=0 \ - -printtoconsole=0 & -PID1=$! - -# Make sure it's listening on its p2p port. -sleep 3 - -BLOCKFILTERARG="" -if bitcoind -version | grep -q "v0\.\(19\|2\)"; then - BLOCKFILTERARG="-blockfilterindex=1" -fi - -FALLBACKFEEARG="" -if bitcoind -version | grep -q "v0\.2"; then - FALLBACKFEEARG="-fallbackfee=0.00001000" -fi - -bitcoind -regtest $BLOCKFILTERARG $FALLBACKFEEARG \ - -datadir=${TESTDIR}/2 \ - -connect=127.0.0.1:12348 \ - -rpcport=12349 \ - -server=1 \ - -printtoconsole=0 & -PID2=$! - -# Let it connect to the other node. -sleep 5 - -RPC_URL=http://localhost:12349 \ - RPC_COOKIE=${TESTDIR}/2/regtest/.cookie \ - cargo run - -RESULT=$? - -kill -9 $PID1 $PID2 - -exit $RESULT diff --git a/json/Cargo.toml b/json/Cargo.toml index 25f28e1b..b5273000 100644 --- a/json/Cargo.toml +++ b/json/Cargo.toml @@ -1,24 +1,25 @@ [package] -name = "bitcoincore-rpc-json" -version = "0.13.0" +name = "core-rpc-json" +version = "0.14.0" authors = [ "Steven Roose ", "Jean Pierre Dudey ", - "Dawid Ciężarkiewicz " + "Dawid Ciężarkiewicz ", + "Riccardo Casatta ", ] license = "CC0-1.0" -homepage = "https://github.com/rust-bitcoin/rust-bitcoincore-rpc/" -repository = "https://github.com/rust-bitcoin/rust-bitcoincore-rpc/" +homepage = "https://github.com/RCasatta/rust-bitcoincore-rpc/" +repository = "https://github.com/RCasatta/rust-bitcoincore-rpc/" description = "JSON-enabled type structs for bitcoincore-rpc crate." keywords = [ "crypto", "bitcoin", "bitcoin-core", "rpc" ] readme = "README.md" [lib] -name = "bitcoincore_rpc_json" +name = "core_rpc_json" path = "src/lib.rs" [dependencies] serde = { version = "1", features = [ "derive" ] } serde_json = "1" -bitcoin = { version = "0.26", features = [ "use-serde" ] } +bitcoin = { version = "0.27", features = [ "use-serde" ] } diff --git a/json/src/lib.rs b/json/src/lib.rs index 80dd4581..57e33a3e 100644 --- a/json/src/lib.rs +++ b/json/src/lib.rs @@ -13,7 +13,7 @@ //! This is a client library for the Bitcoin Core JSON-RPC API. //! -#![crate_name = "bitcoincore_rpc_json"] +#![crate_name = "core_rpc_json"] #![crate_type = "rlib"] pub extern crate bitcoin; @@ -835,7 +835,7 @@ impl<'a> serde::Serialize for ImportMultiRequestScriptPubkey<'a> { #[derive(Serialize)] struct Tmp<'a> { pub address: &'a Address, - }; + } serde::Serialize::serialize( &Tmp { address: addr, @@ -1066,11 +1066,13 @@ pub struct GetPeerInfoResult { } #[derive(Copy, Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] -#[serde(rename_all = "lowercase")] +#[serde(rename_all = "snake_case")] pub enum GetPeerInfoResultNetwork { Ipv4, Ipv6, Onion, + I2p, + NotPubliclyRoutable, // this is undocumented upstream Unroutable, } @@ -1607,6 +1609,7 @@ pub enum AddressType { Legacy, P2shSegwit, Bech32, + Bech32m, } /// Used to represent arguments that can either be an address or a public key. @@ -1686,3 +1689,37 @@ where } Ok(Some(res)) } + +/// Import Descriptor Request +#[derive(Serialize, Clone, PartialEq, Eq, Debug)] +pub struct ImportDescriptorRequest { + pub active: bool, + #[serde(rename = "desc")] + pub descriptor: String, + pub range: [i64; 2], + pub next_index: i64, + pub timestamp: String, + pub internal: bool, +} + +impl ImportDescriptorRequest { + /// Create a new Import Descriptor request providing just the descriptor and internal flags + pub fn new(descriptor: &str, internal: bool) -> Self { + ImportDescriptorRequest { + descriptor: descriptor.to_string(), + internal, + active: true, + range: [0, 100], + next_index: 0, + timestamp: "now".to_string(), + } + } +} + +/// Imported Descriptor Result +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] +pub struct ImportDescriptorResult { + pub success: bool, + pub warnings: Option>, + pub error: Option, +}