Skip to content
Open
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ page. See [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details.
- Updated dependencies to resolve RUSTSEC advisories in rustls, rustls-webpki, webpki-roots, quinn-proto, h2 and zstd-sys; bumped async-hwi to 0.0.35.
- Fixed `create_tx`, `create_dns_tx`, `create_sp_tx` and `bump_fee` building transactions over Bitcoin Core's 400,000 wu (100,000 vB) standard weight limit, which no default node relays
- Fixed `key generate` silently producing a 24-word mnemonic for any `--entropy` value other than 12; values other than 12 and 24 are now rejected
- Fixed `wallet config --force` silently changing an existing wallet's network, which left the wallet unable to load; changing the network is now rejected
- Improved the network mismatch error to name the wallet, the network in its database and the network in `config.toml`, and to say how to fix it
- Clarified the `--network` help text: wallet commands other than `wallet config` use the network saved in the wallet configuration

## [4.0.0]

Expand Down
2 changes: 2 additions & 0 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ use crate::handlers::hwi::{HwiCommand, WalletHwiCommand};
#[command(version, about, long_about = None)]
pub struct CliOpts {
/// Sets the network: bitcoin, testnet, testnet4, signet, or regtest.
/// Wallet commands other than `wallet config` use the network saved in
/// the wallet configuration.
#[arg(
env = "NETWORK",
short = 'n',
Expand Down
10 changes: 10 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ pub enum BDKCliError {
#[error("Miniscript compiler error: {0}")]
MiniscriptCompilerError(#[from] bdk_wallet::miniscript::policy::compiler::CompilerError),

#[cfg(any(feature = "sqlite", feature = "redb"))]
#[error(
"Network mismatch for wallet '{wallet_name}': its database uses '{db_network}', but config.toml has '{config_network}'. Set network = \"{db_network}\" for this wallet in config.toml."
)]
NetworkMismatch {
wallet_name: String,
db_network: bdk_wallet::bitcoin::Network,
config_network: bdk_wallet::bitcoin::Network,
},

#[error(
"Transaction can weigh up to {weight:#} ({} vB) once signed, over the {:#} ({} vB) \
standard limit, so Bitcoin Core nodes will not relay it",
Expand Down
10 changes: 10 additions & 0 deletions src/handlers/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ impl AppCommand<AppContext<Init>> for SaveConfigCommand {
)));
};

if let Some(existing) = config.wallets.get(wallet_name.as_str())
&& existing.network != ctx.network.to_string()
{
return Err(Error::Generic(format!(
"Wallet '{}' is configured for network '{}', and --force cannot change it to '{}'. \
To update other settings, pass --network {}.",
wallet_name, existing.network, ctx.network, existing.network
)));
}

#[cfg(any(
feature = "electrum",
feature = "esplora",
Expand Down
16 changes: 14 additions & 2 deletions src/persister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use bdk_redb::redb::TableHandle;
use bdk_wallet::Wallet;
use bdk_wallet::bitcoin::Network;
#[cfg(any(feature = "sqlite", feature = "redb"))]
use bdk_wallet::{KeychainKind, PersistedWallet, WalletPersister};
use bdk_wallet::{
KeychainKind, LoadError, LoadMismatch, LoadWithPersistError, PersistedWallet, WalletPersister,
};
#[cfg(any(feature = "sqlite", feature = "redb"))]
use clap::ValueEnum;

Expand Down Expand Up @@ -62,6 +64,7 @@ impl WalletPersister for Persister {

#[cfg(any(feature = "sqlite", feature = "redb"))]
pub(crate) fn new_persisted_wallet<P: WalletPersister>(
wallet_name: &str,
network: Network,
persister: &mut P,
wallet_opts: &WalletOpts,
Expand Down Expand Up @@ -92,7 +95,16 @@ where
let wallet_opt = wallet_load_params
.check_network(network)
.load_wallet(persister)
.map_err(|e| Error::Generic(e.to_string()))?;
.map_err(|e| match e {
LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch(
LoadMismatch::Network { loaded, expected },
)) => Error::NetworkMismatch {
wallet_name: wallet_name.to_owned(),
db_network: loaded,
config_network: expected,
},
other => Error::Generic(other.to_string()),
})?;

let wallet = match wallet_opt {
Some(wallet) => wallet,
Expand Down
7 changes: 6 additions & 1 deletion src/utils/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,12 @@ impl WalletRuntime {
#[cfg(any(feature = "sqlite", feature = "redb"))]
if require_db {
let mut persister = self.create_persister()?;
let wallet = new_persisted_wallet(self.network, &mut persister, &self.wallet_opts)?;
let wallet = new_persisted_wallet(
&self.wallet_name,
self.network,
&mut persister,
&self.wallet_opts,
)?;
return Ok(RuntimeWallet::Persisted(
Box::new(wallet),
Box::new(persister),
Expand Down
93 changes: 87 additions & 6 deletions tests/integration/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,7 @@ mod test_config {
use serde_json::Value;
use std::fs;

fn save_wallet(cli: &BdkCli, wallet_name: &str) {
fn save_wallet_cmd(cli: &BdkCli, wallet_name: &str, force: bool) -> assert_cmd::Command {
let desc = cli
.cmd("descriptor", &["--type", "tr"])
.output()
Expand All @@ -718,7 +718,13 @@ mod test_config {
.arg("wallet")
.arg("--wallet")
.arg(wallet_name)
.arg("config")
.arg("config");

if force {
command.arg("--force");
}

command
.arg("--ext-descriptor")
.arg(pub_desc["external"].as_str().unwrap())
.arg("--int-descriptor")
Expand All @@ -731,11 +737,13 @@ mod test_config {
.arg("--url")
.arg("http://localhost:18443");

command.arg("--database-type").arg("sqlite");

command
.arg("--database-type")
.arg("sqlite")
.assert()
.success();
}

fn save_wallet(cli: &BdkCli, wallet_name: &str) {
save_wallet_cmd(cli, wallet_name, false).assert().success();
}

#[cfg(feature = "rpc")]
Expand Down Expand Up @@ -1030,6 +1038,79 @@ rpc_password = "preserved-password"
Some("preserved-password")
);
}

#[test]
fn test_config_force_rejects_network_change() {
let temp_dir = TempDir::new().unwrap();
let regtest = BdkCli::new("regtest", Some(temp_dir.path().to_path_buf()));
let signet = BdkCli::new("signet", Some(temp_dir.path().to_path_buf()));
let wallet_name = "network_wallet";
let config_path = temp_dir.path().join("config.toml");

save_wallet(&regtest, wallet_name);
let before = fs::read_to_string(&config_path).unwrap();

save_wallet_cmd(&signet, wallet_name, true)
.assert()
.failure()
.stderr(predicate::str::contains(
"Wallet 'network_wallet' is configured for network 'regtest', and --force cannot change it to 'signet'",
));

let after = fs::read_to_string(&config_path).unwrap();
assert_eq!(
before, after,
"rejected --force must not change config.toml"
);
}

#[test]
fn test_config_force_same_network_updates_settings() {
let temp_dir = TempDir::new().unwrap();
let cli = BdkCli::new("regtest", Some(temp_dir.path().to_path_buf()));
let wallet_name = "network_wallet";
let config_path = temp_dir.path().join("config.toml");

save_wallet(&cli, wallet_name);
let before = fs::read_to_string(&config_path).unwrap();

save_wallet_cmd(&cli, wallet_name, true).assert().success();

let after = fs::read_to_string(&config_path).unwrap();
assert_ne!(before, after, "--force should save the new descriptors");
assert!(after.contains("network = \"regtest\""));
}

#[test]
fn test_network_mismatch_error_names_wallet_and_networks() {
let temp_dir = TempDir::new().unwrap();
let cli = BdkCli::new("regtest", Some(temp_dir.path().to_path_buf()));
let wallet_name = "mismatch_wallet";
let config_path = temp_dir.path().join("config.toml");

save_wallet(&cli, wallet_name);

// Create the wallet database on regtest.
cli.wallet_cmd(&["--wallet", wallet_name, "new_address"])
.assert()
.success();

// Make config.toml disagree with the database.
let config = fs::read_to_string(&config_path).unwrap();
assert!(config.contains("network = \"regtest\""));
fs::write(
&config_path,
config.replace("network = \"regtest\"", "network = \"signet\""),
)
.unwrap();

cli.wallet_cmd(&["--wallet", wallet_name, "new_address"])
.assert()
.failure()
.stderr(predicate::str::contains(
"Network mismatch for wallet 'mismatch_wallet': its database uses 'regtest', but config.toml has 'signet'",
));
}
}

// SILENT PAYMENTS
Expand Down
Loading