Skip to content

Implement importmulti method and test #263

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 2 commits into from
Jun 28, 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
48 changes: 47 additions & 1 deletion client/src/client_sync/v17/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use std::path::Path;

use bitcoin::address::{Address, NetworkChecked};
use bitcoin::{sign_message, Amount, Block, BlockHash, PublicKey, Txid};
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Serialize, Serializer};

use crate::client_sync::into_json;
use crate::types::v17::*;
Expand Down Expand Up @@ -128,6 +128,7 @@ crate::impl_client_v17__get_transaction!();
crate::impl_client_v17__get_unconfirmed_balance!();
crate::impl_client_v17__get_wallet_info!();
crate::impl_client_v17__import_address!();
crate::impl_client_v17__import_multi!();
crate::impl_client_v17__import_privkey!();
crate::impl_client_v17__import_pruned_funds!();
crate::impl_client_v17__import_pubkey!();
Expand Down Expand Up @@ -247,3 +248,48 @@ pub enum SetBanCommand {
Add,
Remove,
}

/// Args for the `importmulti` method
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct ImportMultiRequest {
/// Descriptor to import. If using descriptor, donot also provide address/scriptPubKey, scripts, or pubkeys.
#[serde(skip_serializing_if = "Option::is_none")]
pub desc: Option<String>, // from core v18 onwards.
/// Type of scriptPubKey (string for script, json for address). Should not be provided if using descriptor.
#[serde(rename = "scriptPubKey", skip_serializing_if = "Option::is_none")]
pub script_pub_key: Option<ImportMultiScriptPubKey>,
Comment on lines +259 to +260
Copy link
Member

Choose a reason for hiding this comment

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

This arg is required by v17 and v29 so shouldn't be Option, right?

Copy link
Contributor Author

@GideonBature GideonBature Jun 27, 2025

Choose a reason for hiding this comment

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

yes, it is required by v17 - v29. Why I made it Option is because for v18 - v29 either scriptPubkey or descriptor is required but not both.

Copy link
Member

Choose a reason for hiding this comment

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

ooo, I missed that. Thanks

Copy link
Member

Choose a reason for hiding this comment

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

Needs rename to script_pubkey.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will open a patch PR for the renaming.

/// Creation time of the key expressed in UNIX epoch time, or the string "now" to substitute the current synced blockchain time.
pub timestamp: ImportMultiTimestamp,
}

/// `scriptPubKey` can be a string for script or json for address.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(untagged)]
pub enum ImportMultiScriptPubKey {
/// The script.
Script(String),
/// The address.
Address { address: String },
}

/// `timestamp` can be a number (UNIX epoch time) or the string `"now"`
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum ImportMultiTimestamp {
/// The string "now".
Now,
/// The UNIX timestamp.
Time(u64),
}

impl Serialize for ImportMultiTimestamp {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
ImportMultiTimestamp::Now => serializer.serialize_str("now"),
ImportMultiTimestamp::Time(t) => serializer.serialize_u64(*t),
}
}
}
12 changes: 12 additions & 0 deletions client/src/client_sync/v17/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,18 @@ macro_rules! impl_client_v17__import_address {
};
}

/// Implements Bitcoin Core JSON-RPC API method `importmulti`
#[macro_export]
macro_rules! impl_client_v17__import_multi {
() => {
impl Client {
pub fn import_multi(&self, requests: &[ImportMultiRequest]) -> Result<ImportMulti> {
self.call("importmulti", &[into_json(requests)?])
}
}
};
}

/// Implements Bitcoin Core JSON-RPC API method `importprivkey`.
#[macro_export]
macro_rules! impl_client_v17__import_privkey {
Expand Down
5 changes: 3 additions & 2 deletions client/src/client_sync/v18/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use crate::types::v18::*;
#[rustfmt::skip] // Keep public re-exports separate.
pub use crate::client_sync::{
v17::{
AddNodeCommand, AddressType, Input, Output, SetBanCommand, TemplateRequest,
TemplateRules, WalletCreateFundedPsbtInput
AddNodeCommand, AddressType, ImportMultiRequest, ImportMultiScriptPubKey, ImportMultiTimestamp, Input, Output, SetBanCommand, TemplateRequest,
TemplateRules, WalletCreateFundedPsbtInput,
},
};

Expand Down Expand Up @@ -143,6 +143,7 @@ crate::impl_client_v17__get_transaction!();
crate::impl_client_v17__get_unconfirmed_balance!();
crate::impl_client_v17__get_wallet_info!();
crate::impl_client_v17__import_address!();
crate::impl_client_v17__import_multi!();
crate::impl_client_v17__import_privkey!();
crate::impl_client_v17__import_pruned_funds!();
crate::impl_client_v17__import_pubkey!();
Expand Down
5 changes: 3 additions & 2 deletions client/src/client_sync/v19/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ use crate::types::v19::*;
#[rustfmt::skip] // Keep public re-exports separate.
pub use crate::client_sync::{
v17::{
AddNodeCommand, AddressType, Input, Output, SetBanCommand, TemplateRequest,
TemplateRules, WalletCreateFundedPsbtInput
AddNodeCommand, AddressType, ImportMultiRequest, ImportMultiScriptPubKey, ImportMultiTimestamp, Input, Output, SetBanCommand, TemplateRequest,
TemplateRules, WalletCreateFundedPsbtInput,
},
};

Expand Down Expand Up @@ -139,6 +139,7 @@ crate::impl_client_v17__get_transaction!();
crate::impl_client_v17__get_unconfirmed_balance!();
crate::impl_client_v17__get_wallet_info!();
crate::impl_client_v17__import_address!();
crate::impl_client_v17__import_multi!();
crate::impl_client_v17__import_privkey!();
crate::impl_client_v17__import_pruned_funds!();
crate::impl_client_v17__import_pubkey!();
Expand Down
5 changes: 3 additions & 2 deletions client/src/client_sync/v20/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use crate::types::v20::*;
#[rustfmt::skip] // Keep public re-exports separate.
pub use crate::client_sync::{
v17::{
AddressType, AddNodeCommand, Input, Output, SetBanCommand, TemplateRequest, TemplateRules,
WalletCreateFundedPsbtInput
AddressType, AddNodeCommand, ImportMultiRequest, ImportMultiScriptPubKey, ImportMultiTimestamp, Input, Output, SetBanCommand, TemplateRequest, TemplateRules,
WalletCreateFundedPsbtInput,
},
};

Expand Down Expand Up @@ -136,6 +136,7 @@ crate::impl_client_v17__get_transaction!();
crate::impl_client_v17__get_unconfirmed_balance!();
crate::impl_client_v17__get_wallet_info!();
crate::impl_client_v17__import_address!();
crate::impl_client_v17__import_multi!();
crate::impl_client_v17__import_privkey!();
crate::impl_client_v17__import_pruned_funds!();
crate::impl_client_v17__import_pubkey!();
Expand Down
5 changes: 3 additions & 2 deletions client/src/client_sync/v21/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ use crate::types::v21::*;
#[rustfmt::skip] // Keep public re-exports separate.
pub use crate::client_sync::{
v17::{
AddNodeCommand, AddressType, Input, Output, SetBanCommand, TemplateRequest,
TemplateRules, WalletCreateFundedPsbtInput
AddNodeCommand, AddressType, ImportMultiRequest, ImportMultiScriptPubKey, ImportMultiTimestamp, Input, Output, SetBanCommand, TemplateRequest,
TemplateRules, WalletCreateFundedPsbtInput,
},
};

Expand Down Expand Up @@ -138,6 +138,7 @@ crate::impl_client_v17__get_transaction!();
crate::impl_client_v17__get_unconfirmed_balance!();
crate::impl_client_v17__get_wallet_info!();
crate::impl_client_v17__import_address!();
crate::impl_client_v17__import_multi!();
crate::impl_client_v17__import_privkey!();
crate::impl_client_v17__import_pruned_funds!();
crate::impl_client_v17__import_pubkey!();
Expand Down
5 changes: 3 additions & 2 deletions client/src/client_sync/v22/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ use crate::types::v22::*;
#[rustfmt::skip] // Keep public re-exports separate.
pub use crate::client_sync::{
v17::{
AddNodeCommand, AddressType, Input, Output, SetBanCommand, TemplateRequest,
TemplateRules, WalletCreateFundedPsbtInput
AddNodeCommand, AddressType, ImportMultiRequest, ImportMultiScriptPubKey, ImportMultiTimestamp, Input, Output, SetBanCommand, TemplateRequest,
TemplateRules, WalletCreateFundedPsbtInput,
},
};

Expand Down Expand Up @@ -138,6 +138,7 @@ crate::impl_client_v17__get_transaction!();
crate::impl_client_v17__get_unconfirmed_balance!();
crate::impl_client_v17__get_wallet_info!();
crate::impl_client_v17__import_address!();
crate::impl_client_v17__import_multi!();
crate::impl_client_v17__import_privkey!();
crate::impl_client_v17__import_pruned_funds!();
crate::impl_client_v17__import_pubkey!();
Expand Down
5 changes: 3 additions & 2 deletions client/src/client_sync/v23/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ use crate::types::v23::*;
#[rustfmt::skip] // Keep public re-exports separate.
pub use crate::client_sync::{
v17::{
AddNodeCommand, Input, Output, SetBanCommand, TemplateRequest, TemplateRules,
WalletCreateFundedPsbtInput
AddNodeCommand, ImportMultiRequest, ImportMultiScriptPubKey, ImportMultiTimestamp, Input, Output, SetBanCommand, TemplateRequest, TemplateRules,
WalletCreateFundedPsbtInput,
},
};

Expand Down Expand Up @@ -140,6 +140,7 @@ crate::impl_client_v17__get_transaction!();
crate::impl_client_v17__get_unconfirmed_balance!();
crate::impl_client_v17__get_wallet_info!();
crate::impl_client_v17__import_address!();
crate::impl_client_v17__import_multi!();
crate::impl_client_v17__import_privkey!();
crate::impl_client_v17__import_pruned_funds!();
crate::impl_client_v17__import_pubkey!();
Expand Down
5 changes: 3 additions & 2 deletions client/src/client_sync/v24/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use crate::types::v24::*;
#[rustfmt::skip] // Keep public re-exports separate.
pub use crate::client_sync::{
v17::{
AddNodeCommand, Input, Output, SetBanCommand, TemplateRequest, TemplateRules,
WalletCreateFundedPsbtInput
AddNodeCommand, ImportMultiRequest, ImportMultiScriptPubKey, ImportMultiTimestamp, Input, Output, SetBanCommand, TemplateRequest, TemplateRules,
WalletCreateFundedPsbtInput,
},
v23::AddressType,
};
Expand Down Expand Up @@ -137,6 +137,7 @@ crate::impl_client_v17__get_transaction!();
crate::impl_client_v17__get_unconfirmed_balance!();
crate::impl_client_v17__get_wallet_info!();
crate::impl_client_v17__import_address!();
crate::impl_client_v17__import_multi!();
crate::impl_client_v17__import_privkey!();
crate::impl_client_v17__import_pruned_funds!();
crate::impl_client_v17__import_pubkey!();
Expand Down
5 changes: 3 additions & 2 deletions client/src/client_sync/v25/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use crate::types::v25::*;
#[rustfmt::skip] // Keep public re-exports separate.
pub use crate::client_sync::{
v17::{
AddNodeCommand, Input, Output, SetBanCommand, TemplateRequest, TemplateRules,
WalletCreateFundedPsbtInput
AddNodeCommand, ImportMultiRequest, ImportMultiScriptPubKey, ImportMultiTimestamp, Input, Output, SetBanCommand, TemplateRequest, TemplateRules,
WalletCreateFundedPsbtInput,
},
v23::AddressType,
};
Expand Down Expand Up @@ -137,6 +137,7 @@ crate::impl_client_v17__get_transaction!();
crate::impl_client_v17__get_unconfirmed_balance!();
crate::impl_client_v17__get_wallet_info!();
crate::impl_client_v17__import_address!();
crate::impl_client_v17__import_multi!();
crate::impl_client_v17__import_privkey!();
crate::impl_client_v17__import_pruned_funds!();
crate::impl_client_v17__import_pubkey!();
Expand Down
5 changes: 3 additions & 2 deletions client/src/client_sync/v26/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ use crate::types::v26::*;
#[rustfmt::skip] // Keep public re-exports separate.
pub use crate::client_sync::{
v17::{
AddNodeCommand, Input, Output, SetBanCommand, TemplateRequest, TemplateRules,
WalletCreateFundedPsbtInput
AddNodeCommand, ImportMultiRequest, ImportMultiScriptPubKey, ImportMultiTimestamp, Input, Output, SetBanCommand, TemplateRequest, TemplateRules,
WalletCreateFundedPsbtInput,
},
v23::AddressType,
};
Expand Down Expand Up @@ -143,6 +143,7 @@ crate::impl_client_v17__get_transaction!();
crate::impl_client_v17__get_unconfirmed_balance!();
crate::impl_client_v17__get_wallet_info!();
crate::impl_client_v17__import_address!();
crate::impl_client_v17__import_multi!();
crate::impl_client_v17__import_privkey!();
crate::impl_client_v17__import_pruned_funds!();
crate::impl_client_v17__import_pubkey!();
Expand Down
5 changes: 3 additions & 2 deletions client/src/client_sync/v27/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use crate::types::v27::*;
#[rustfmt::skip] // Keep public re-exports separate.
pub use crate::client_sync::{
v17::{
AddNodeCommand, Input, Output, SetBanCommand, TemplateRequest, TemplateRules,
WalletCreateFundedPsbtInput
AddNodeCommand, ImportMultiRequest, ImportMultiScriptPubKey, ImportMultiTimestamp, Input, Output, SetBanCommand, TemplateRequest, TemplateRules,
WalletCreateFundedPsbtInput,
},
v23::AddressType,
};
Expand Down Expand Up @@ -139,6 +139,7 @@ crate::impl_client_v17__get_transaction!();
crate::impl_client_v17__get_unconfirmed_balance!();
crate::impl_client_v17__get_wallet_info!();
crate::impl_client_v17__import_address!();
crate::impl_client_v17__import_multi!();
crate::impl_client_v17__import_privkey!();
crate::impl_client_v17__import_pruned_funds!();
crate::impl_client_v17__import_pubkey!();
Expand Down
5 changes: 3 additions & 2 deletions client/src/client_sync/v28/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ use crate::types::v28::*;
#[rustfmt::skip] // Keep public re-exports separate.
pub use crate::client_sync::{
v17::{
AddNodeCommand, Input, Output, SetBanCommand, TemplateRequest, TemplateRules,
WalletCreateFundedPsbtInput
AddNodeCommand, ImportMultiRequest, ImportMultiScriptPubKey, ImportMultiTimestamp, Input, Output, SetBanCommand, TemplateRequest, TemplateRules,
WalletCreateFundedPsbtInput,
},
v23::AddressType,
};
Expand Down Expand Up @@ -141,6 +141,7 @@ crate::impl_client_v17__get_transaction!();
crate::impl_client_v17__get_unconfirmed_balance!();
crate::impl_client_v17__get_wallet_info!();
crate::impl_client_v17__import_address!();
crate::impl_client_v17__import_multi!();
crate::impl_client_v17__import_privkey!();
crate::impl_client_v17__import_pruned_funds!();
crate::impl_client_v17__import_pubkey!();
Expand Down
3 changes: 2 additions & 1 deletion client/src/client_sync/v29/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::types::v29::*;

#[rustfmt::skip] // Keep public re-exports separate.
pub use crate::client_sync::{
v17::{AddNodeCommand, Input, Output, SetBanCommand, WalletCreateFundedPsbtInput},
v17::{AddNodeCommand, ImportMultiRequest, ImportMultiScriptPubKey, ImportMultiTimestamp, Input, Output, SetBanCommand, WalletCreateFundedPsbtInput,},
v23::AddressType,
};

Expand Down Expand Up @@ -141,6 +141,7 @@ crate::impl_client_v17__get_transaction!();
crate::impl_client_v17__get_unconfirmed_balance!();
crate::impl_client_v17__get_wallet_info!();
crate::impl_client_v17__import_address!();
crate::impl_client_v17__import_multi!();
crate::impl_client_v17__import_privkey!();
crate::impl_client_v17__import_pruned_funds!();
crate::impl_client_v17__import_pubkey!();
Expand Down
66 changes: 65 additions & 1 deletion integration_test/tests/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use bitcoin::address::{Address, NetworkChecked};
use bitcoin::{Amount, PrivateKey, PublicKey};
use integration_test::{Node, NodeExt as _, Wallet};
use node::{mtype,AddressType};
use node::{mtype, AddressType, ImportMultiRequest, ImportMultiScriptPubKey, ImportMultiTimestamp};
use node::vtype::*; // All the version specific types.
use std::fs;

Expand Down Expand Up @@ -343,6 +343,70 @@ fn wallet__list_received_by_label__modelled() {
assert!(model.0.iter().any(|item| item.label == label));
}

#[test]
fn wallet__import_multi() {
let node = match () {
#[cfg(feature = "v22_and_below")]
() => Node::with_wallet(Wallet::Default, &[]),
#[cfg(not(feature = "v22_and_below"))]
() => {
let node = Node::with_wallet(Wallet::None, &["-deprecatedrpc=create_bdb"]);
node.client.create_legacy_wallet("wallet_name").expect("createlegacywallet");
node
}
};

let dummy_script_hex = "76a914aabbccddeeff00112233445566778899aabbccdd88ac";
let addr = node.client.new_address().expect("newaddress");
let dummy_desc = "pkh(02c6047f9441ed7d6d3045406e95c07cd85a2a0e5c1e507a7a7e3d2f0d6c3d8ef8)#tp9h0863";

// Uses scriptPubKey (valid): success - true, without warnings nor error.
// NOTE: On v17, use a wallet-generated address (not raw script)
// to ensure import succeeds, since the wallet already knows the key.
let req1 = ImportMultiRequest {
desc: None,
script_pub_key: Some(ImportMultiScriptPubKey::Script(dummy_script_hex.to_string())),
timestamp: ImportMultiTimestamp::Now,
};

// Uses an address (valid): success - false, with JSON-RPC error.
let req2 = ImportMultiRequest {
desc: None,
script_pub_key: Some(ImportMultiScriptPubKey::Address {
address: addr.to_string(),
}),
timestamp: ImportMultiTimestamp::Now,
};

// Uses descriptor (valid): success - true
// on v18 onwards, it will return a watch-only warning.
// NOTE: Works only for v18 onwards, as v17 doesn't support descriptors.
let req3 = ImportMultiRequest {
desc: Some(dummy_desc.to_string()),
script_pub_key: None,
timestamp: ImportMultiTimestamp::Time(1_700_000_000),
};

let json: ImportMulti = node.client.import_multi(&[req1, req2, req3]).expect("importmulti");

#[cfg(not(feature = "v17"))]
{
// result of req1: should succeed, no error, no warning.
// just any random script doesn't work with v17.
assert!(json.0[0].success);
assert!(json.0[0].error.is_none());

// result of req3: should succeed, with warning for v18 onwards
assert!(json.0[2].success);
assert!(json.0[2].error.is_none());
assert!(json.0[2].warnings.is_some());
}

// result of req2: should fail with error (wallet already contains privkey for address/script)
assert!(!json.0[1].success);
assert!(json.0[1].error.is_some());
}

#[test]
fn wallet__import_privkey() {
let node = match () {
Expand Down
Loading