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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ 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 REPL mode not persisting wallet changes, so a new session resumed from stale state (e.g. reusing already-handed-out addresses) and lost transactions, UTXO locks and scan results

## [4.0.0]

Expand Down
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ async fn run(cli_opts: CliOpts) -> Result<(), Error> {
.await
.map_err(Error::Generic)?;

// Persist after every command so the session's changes survive an exit,
// a crash, or Ctrl-C, matching the non-REPL path.
wallet.persist()?;

if should_exit {
break;
}
Expand Down
66 changes: 66 additions & 0 deletions tests/integration/offline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,3 +491,69 @@ mod multipath_tests {
.failure();
}
}

#[cfg(all(feature = "repl", feature = "sqlite"))]
mod repl_persist_tests {
use crate::common::BdkCli;
use serde_json::Value;
use tempfile::TempDir;

const WALLET_NAME: &str = "repl_persist_wallet";

fn setup_repl_wallet() -> (BdkCli, TempDir) {
let temp = TempDir::new().unwrap();
let cli = BdkCli::new("regtest", Some(temp.path().to_path_buf()));

let desc = cli.cmd("descriptor", &["--type", "tr"]).output().unwrap();
let v: Value = serde_json::from_slice(&desc.stdout).unwrap();
let ext = v["private_descriptors"]["external"].as_str().unwrap();
let int = v["private_descriptors"]["internal"].as_str().unwrap();

cli.build_base_cmd()
.args(["wallet", "--wallet", WALLET_NAME, "config"])
.args(["--ext-descriptor", ext, "--int-descriptor", int])
.args(["--database-type", "sqlite"])
.assert()
.success();

(cli, temp)
}

/// Runs `wallet new_address` in a fresh REPL process and returns the index it printed.
fn repl_new_address_index(cli: &BdkCli) -> u64 {
let output = cli
.build_base_cmd()
.args(["repl", "--wallet", WALLET_NAME])
.write_stdin("wallet new_address\nexit\n")
.output()
.expect("failed to run repl");

assert!(
output.status.success(),
"repl exited non-zero: {}",
String::from_utf8_lossy(&output.stderr)
);

let stdout = String::from_utf8_lossy(&output.stdout);
let marker = "\"index\": ";
let start = stdout
.find(marker)
.unwrap_or_else(|| panic!("new_address output missing index:\n{stdout}"))
+ marker.len();
stdout[start..]
.chars()
.take_while(|c| c.is_ascii_digit())
.collect::<String>()
.parse()
.expect("index was not a number")
}

#[test]
fn repl_persists_address_index_across_sessions() {
let (cli, _temp) = setup_repl_wallet();

assert_eq!(repl_new_address_index(&cli), 0);
// A second, separate REPL process must see the index persisted by the first one.
assert_eq!(repl_new_address_index(&cli), 1);
}
}