Skip to content

Follow up to v21 TODO #293

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions client/src/client_sync/v21/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@ crate::impl_client_v17__abort_rescan!();
crate::impl_client_v17__add_multisig_address!();
crate::impl_client_v17__backup_wallet!();
crate::impl_client_v17__bump_fee!();
crate::impl_client_v17__create_wallet!();
crate::impl_client_v21__create_wallet_with_descriptors!();
crate::impl_client_v21__create_wallet!();
crate::impl_client_v17__dump_priv_key!();
crate::impl_client_v17__dump_wallet!();
crate::impl_client_v17__encrypt_wallet!();
Expand Down Expand Up @@ -193,3 +192,10 @@ pub struct ImportDescriptorsRequest {
/// Time from which to start rescanning the blockchain for this descriptor, in UNIX epoch time or "now".
pub timestamp: serde_json::Value,
}

impl ImportDescriptorsRequest {
/// Constructs a new ImportDescriptorsRequest.
pub fn new(descriptor: impl Into<String>, timestamp: impl Into<serde_json::Value>) -> Self {
ImportDescriptorsRequest { descriptor: descriptor.into(), timestamp: timestamp.into() }
}
}
55 changes: 42 additions & 13 deletions client/src/client_sync/v21/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,51 @@
//!
//! See or use the `define_jsonrpc_minreq_client!` macro to define a `Client`.

/// Implements Bitcoin Core JSON-RPC API method `createwallet` with descriptors=true (descriptor wallet).
/// Implements Bitcoin Core JSON-RPC API method `createwallet`.
#[macro_export]
macro_rules! impl_client_v21__create_wallet_with_descriptors {
macro_rules! impl_client_v21__create_wallet {
() => {
impl Client {
pub fn create_wallet_with_descriptors(&self, wallet: &str) -> Result<CreateWallet> {
let args = [
wallet.into(),
false.into(), // disable_private_keys
false.into(), // blank
serde_json::Value::Null, // passphrase
false.into(), // avoid_reuse
true.into(), // descriptors=true
serde_json::Value::Null, // load_on_startup
];
self.call("createwallet", &args)
/// Calls `createwallet` with `wallet` as the only argument.
///
/// In v21 and v22 this creates a legacy wallet. Use `create_descriptor_wallet` to create
/// a descriptor wallet.
pub fn create_wallet(&self, wallet: &str) -> Result<CreateWallet> {
self.call("createwallet", &[wallet.into()])
}

/// Creates a wallet with descriptors=true (descriptor wallet).
///
/// > createwallet "wallet_name" ( disable_private_keys blank "passphrase" avoid_reuse descriptors load_on_startup )
/// >
/// > Creates and loads a new wallet.
/// >
/// > Arguments:
/// > 1. wallet_name (string, required) The name for the new wallet. If this is a path, the wallet will be created at the path location.
/// > 2. disable_private_keys (boolean, optional, default=false) Disable the possibility of private keys (only watchonlys are possible in this mode).
/// > 3. blank (boolean, optional, default=false) Create a blank wallet. A blank wallet has no keys or HD seed. One can be set using sethdseed.
/// > 4. passphrase (string, optional) Encrypt the wallet with this passphrase.
/// > 5. avoid_reuse (boolean, optional, default=false) Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind.
/// > 6. descriptors (boolean, optional, default=true) Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation
/// > 7. load_on_startup (boolean, optional) Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged.
pub fn create_descriptor_wallet(&self, wallet: &str) -> Result<CreateWallet> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is waaay better, nice one man.

let disable_private_keys = false;
let blank = false;
let passphrase = String::new();
let avoid_reuse = false;
let descriptors = true;

self.call(
"createwallet",
&[
wallet.into(),
disable_private_keys.into(),
blank.into(),
passphrase.into(),
avoid_reuse.into(),
descriptors.into(),
],
)
}
}
};
Expand Down
3 changes: 1 addition & 2 deletions client/src/client_sync/v22/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,7 @@ crate::impl_client_v17__abort_rescan!();
crate::impl_client_v17__add_multisig_address!();
crate::impl_client_v17__backup_wallet!();
crate::impl_client_v17__bump_fee!();
crate::impl_client_v17__create_wallet!();
crate::impl_client_v21__create_wallet_with_descriptors!();
crate::impl_client_v21__create_wallet!();
crate::impl_client_v17__dump_priv_key!();
crate::impl_client_v17__dump_wallet!();
crate::impl_client_v17__encrypt_wallet!();
Expand Down
1 change: 0 additions & 1 deletion client/src/client_sync/v23/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ crate::impl_client_v17__add_multisig_address!();
crate::impl_client_v17__backup_wallet!();
crate::impl_client_v17__bump_fee!();
crate::impl_client_v23__create_wallet!();
crate::impl_client_v21__create_wallet_with_descriptors!();
crate::impl_client_v17__dump_priv_key!();
crate::impl_client_v17__dump_wallet!();
crate::impl_client_v17__encrypt_wallet!();
Expand Down
3 changes: 3 additions & 0 deletions client/src/client_sync/v23/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ macro_rules! impl_client_v23__create_wallet {
() => {
impl Client {
/// Calls `createwallet` with `wallet` as the only argument.
///
/// In v23 and later this creates a descriptor wallet. Use `create_legacy_wallet` to create
/// a legacy wallet.
pub fn create_wallet(&self, wallet: &str) -> Result<CreateWallet> {
self.call("createwallet", &[wallet.into()])
}
Expand Down
1 change: 0 additions & 1 deletion client/src/client_sync/v24/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ crate::impl_client_v17__add_multisig_address!();
crate::impl_client_v17__backup_wallet!();
crate::impl_client_v17__bump_fee!();
crate::impl_client_v23__create_wallet!();
crate::impl_client_v21__create_wallet_with_descriptors!();
crate::impl_client_v17__dump_priv_key!();
crate::impl_client_v17__dump_wallet!();
crate::impl_client_v17__encrypt_wallet!();
Expand Down
1 change: 0 additions & 1 deletion client/src/client_sync/v25/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ crate::impl_client_v17__add_multisig_address!();
crate::impl_client_v17__backup_wallet!();
crate::impl_client_v17__bump_fee!();
crate::impl_client_v23__create_wallet!();
crate::impl_client_v21__create_wallet_with_descriptors!();
crate::impl_client_v17__dump_priv_key!();
crate::impl_client_v17__dump_wallet!();
crate::impl_client_v17__encrypt_wallet!();
Expand Down
1 change: 0 additions & 1 deletion client/src/client_sync/v26/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ crate::impl_client_v17__add_multisig_address!();
crate::impl_client_v17__backup_wallet!();
crate::impl_client_v17__bump_fee!();
crate::impl_client_v23__create_wallet!();
crate::impl_client_v21__create_wallet_with_descriptors!();
crate::impl_client_v17__dump_priv_key!();
crate::impl_client_v17__dump_wallet!();
crate::impl_client_v17__encrypt_wallet!();
Expand Down
1 change: 0 additions & 1 deletion client/src/client_sync/v27/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ crate::impl_client_v17__add_multisig_address!();
crate::impl_client_v17__backup_wallet!();
crate::impl_client_v17__bump_fee!();
crate::impl_client_v23__create_wallet!();
crate::impl_client_v21__create_wallet_with_descriptors!();
crate::impl_client_v17__dump_priv_key!();
crate::impl_client_v17__dump_wallet!();
crate::impl_client_v17__encrypt_wallet!();
Expand Down
1 change: 0 additions & 1 deletion client/src/client_sync/v28/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ crate::impl_client_v17__add_multisig_address!();
crate::impl_client_v17__backup_wallet!();
crate::impl_client_v17__bump_fee!();
crate::impl_client_v23__create_wallet!();
crate::impl_client_v21__create_wallet_with_descriptors!();
crate::impl_client_v17__dump_priv_key!();
crate::impl_client_v17__dump_wallet!();
crate::impl_client_v17__encrypt_wallet!();
Expand Down
1 change: 0 additions & 1 deletion client/src/client_sync/v29/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ crate::impl_client_v17__add_multisig_address!();
crate::impl_client_v17__backup_wallet!();
crate::impl_client_v17__bump_fee!();
crate::impl_client_v23__create_wallet!();
crate::impl_client_v21__create_wallet_with_descriptors!();
crate::impl_client_v17__dump_priv_key!();
crate::impl_client_v17__dump_wallet!();
crate::impl_client_v17__encrypt_wallet!();
Expand Down
7 changes: 5 additions & 2 deletions integration_test/tests/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,11 @@ fn util__get_descriptor_info() {
#[cfg(not(feature = "v20_and_below"))]
#[test]
fn util__get_index_info() {
let node = Node::with_wallet(Wallet::Default, &[]);
let _: GetIndexInfo = node.client.get_index_info().expect("getindexinfo");
let node = Node::with_wallet(Wallet::Default, &["-txindex"]);
let index_info: GetIndexInfo = node.client.get_index_info().expect("getindexinfo");

let txindex_info = index_info.0.get("txindex").unwrap();
assert!(txindex_info.best_block_height < u32::MAX, "best_block_height should be a valid block height");
}

#[test]
Expand Down
59 changes: 45 additions & 14 deletions integration_test/tests/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
//! Tests for methods found under the `== Wallet ==` section of the API docs.

#![allow(non_snake_case)] // Test names intentionally use double underscore.
#![allow(unused_imports)] // Some imports are only used in specific versions.

#[cfg(feature = "TODO")]
use bitcoin::address::{Address, NetworkChecked};
use bitcoin::{Amount, FeeRate, PrivateKey, PublicKey};
use bitcoin::address::{Address, KnownHrp, NetworkChecked};
use bitcoin::{secp256k1, Amount, CompressedPublicKey, FeeRate, PrivateKey, PublicKey};
use integration_test::{Node, NodeExt as _, Wallet};
use node::{mtype, AddressType, ImportMultiRequest, ImportMultiScriptPubKey, ImportMultiTimestamp};

#[cfg(not(feature = "v20_and_below"))]
use node::ImportDescriptorsRequest;

use node::vtype::*; // All the version specific types.
use std::fs;
use std::time::{SystemTime, UNIX_EPOCH};

#[test]
fn wallet__abandon_transaction() {
Expand Down Expand Up @@ -326,21 +331,47 @@ fn wallet__import_address() {
#[test]
#[cfg(not(feature = "v20_and_below"))]
fn wallet__import_descriptors() {
use node::{serde_json, ImportDescriptorsRequest};

let node = Node::with_wallet(Wallet::None, &[]);
let wallet_name = "desc_wallet";
node.client.create_wallet_with_descriptors(wallet_name).expect("create descriptor wallet");

let address = node.client.new_address().expect("failed to get new address");
let descriptor = format!("addr({})", address);
#[cfg(feature = "v22_and_below")]
node.client.create_descriptor_wallet(wallet_name).expect("create descriptor wallet");

let request = ImportDescriptorsRequest {
descriptor,
timestamp: serde_json::Value::String("now".to_string()),
};
// v23 onwards uses descriptor wallets by default.
#[cfg(not(feature = "v22_and_below"))]
node.client.create_wallet(wallet_name).expect("create wallet");

node.fund_wallet();

// 1. Get the current time
let start_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("failed to get current time")
.as_secs();

// 2. Use a known private key, derive the address from it and send some coins to it.
let privkey =
PrivateKey::from_wif("cVt4o7BGAig1UXywgGSmARhxMdzP5qvQsxKkSsc1XEkw3tDTQFpy").unwrap();
let secp = secp256k1::Secp256k1::new();
let pubkey = privkey.public_key(&secp);
let address = Address::p2wpkh(&CompressedPublicKey(pubkey.inner), KnownHrp::Regtest);
let amount = Amount::from_sat(10_000);
let _txid = node.client.send_to_address(&address, amount).expect("sendtoaddress");

// 3. Get the descriptor from the private key.
let raw_descriptor = format!("wpkh({})", privkey.to_wif());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my education, why does the descriptor use wpkh but the address above is a p2pkh address?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because one bit was copied from another test 😊. I have changed it now so both are native SegWit.

let info = node.client.get_descriptor_info(&raw_descriptor).expect("get_descriptor_info");
let descriptor = format!("{}#{}", raw_descriptor, info.checksum);
Comment on lines +363 to +364
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clever!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This took me a bit to figure out. It kept complaining that that the descriptor had no private key when it looked like it should.


// 4. Mine 100 blocks
let mining_address = node.client.new_address().expect("failed to get mining address");
let _blocks = node.client.generate_to_address(100, &mining_address).expect("generatetoaddress");

let _: ImportDescriptors = node.client.import_descriptors(&[request]).expect("importdescriptors");
// 5. Scan for the descriptor using the time from (1)
let request = ImportDescriptorsRequest::new(descriptor, start_time);
let result: ImportDescriptors = node.client.import_descriptors(&[request]).expect("importdescriptors");
assert_eq!(result.0.len(), 1, "should have exactly one import result");
assert!(result.0[0].success);
}

#[test]
Expand Down Expand Up @@ -515,7 +546,7 @@ fn wallet__list_descriptors() {
let wallet_name = "desc_wallet";

#[cfg(feature = "v22_and_below")]
node.client.create_wallet_with_descriptors(wallet_name).expect("create descriptor wallet");
node.client.create_descriptor_wallet(wallet_name).expect("create descriptor wallet");

// v23 onwards uses descriptor wallets by default.
#[cfg(not(feature = "v22_and_below"))]
Expand Down