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 @@ -23,6 +23,7 @@ page. See [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details.
- 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 restore` ignoring `--password`, which restored passphrase-protected mnemonics to the wrong key
- 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
- Gated `full_scan`'s `--scan-stop-gap` to the Electrum and Esplora backends that use it; RPC and CBF wallets now reject the flag instead of silently ignoring it, while a `STOP_GAP` environment variable keeps working for supported backends and no longer breaks unsupported ones

## [4.0.0]

Expand Down
78 changes: 74 additions & 4 deletions src/handlers/online.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,45 @@ impl OnlineWalletSubCommand {
#[derive(Parser, Debug, PartialEq, Clone, Eq)]
pub struct FullScanCommand {
/// Stop searching addresses for transactions after finding an unused gap of this length.
#[arg(env = "STOP_GAP", long = "scan-stop-gap", default_value = "20")]
stop_gap: usize,
/// Only available with the `electrum` and `esplora` backends. When the flag is omitted,
/// the `STOP_GAP` environment variable is read if it is set. [default: 20]
#[cfg(any(feature = "electrum", feature = "esplora"))]
#[arg(long = "scan-stop-gap")]
stop_gap: Option<usize>,

#[clap(long, default_value = "5")]
pub parallel_request: usize,
}

#[cfg(any(feature = "electrum", feature = "esplora"))]
const DEFAULT_STOP_GAP: usize = 20;

#[cfg(any(feature = "electrum", feature = "esplora"))]
impl FullScanCommand {
/// Resolves the stop gap, in order, from `--scan-stop-gap`, the `STOP_GAP` environment
/// variable, or the default.
///
/// `STOP_GAP` is read here instead of through clap so that a globally exported value is
/// ignored by backends that do not support the option (RPC and CBF in a multi-backend
/// build) instead of turning their scans into an error.
fn stop_gap(&self) -> Result<usize, Error> {
if let Some(stop_gap) = self.stop_gap {
return Ok(stop_gap);
}
match std::env::var("STOP_GAP") {
Ok(value) => value.parse().map_err(|_| {
Error::Generic(format!(
"`STOP_GAP` must be a positive integer, got `{value}`"
))
}),
Err(std::env::VarError::NotPresent) => Ok(DEFAULT_STOP_GAP),
Err(std::env::VarError::NotUnicode(_)) => Err(Error::Generic(
"`STOP_GAP` is not valid unicode".to_string(),
)),
}
}
}

#[cfg(any(
feature = "electrum",
feature = "esplora",
Expand Down Expand Up @@ -121,7 +154,7 @@ impl AsyncAppCommand<AppContext<OnlineOperations<'_>>> for FullScanCommand {
#[cfg(feature = "electrum")]
Electrum { client, batch_size } => {
client.populate_tx_cache(wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx));
let update = client.full_scan(request, self.stop_gap, *batch_size, false)?;
let update = client.full_scan(request, self.stop_gap()?, *batch_size, false)?;
let events = wallet.apply_update_events(update)?;
print_wallet_events(&events);
}
Expand All @@ -132,14 +165,22 @@ impl AsyncAppCommand<AppContext<OnlineOperations<'_>>> for FullScanCommand {
parallel_requests,
} => {
let update = client
.full_scan(request, self.stop_gap, *parallel_requests)
.full_scan(request, self.stop_gap()?, *parallel_requests)
.await
.map_err(|e| *e)?;
let events = wallet.apply_update_events(update)?;
print_wallet_events(&events);
}
#[cfg(feature = "rpc")]
RpcClient { client } => {
#[cfg(any(feature = "electrum", feature = "esplora"))]
if self.stop_gap.is_some() {
return Err(Error::Generic(
"`--scan-stop-gap` is only supported by the electrum and esplora backends"
.into(),
));
}

let blockchain_info = client.get_blockchain_info()?;

let genesis_block = bdk_wallet::bitcoin::constants::genesis_block(wallet.network());
Expand Down Expand Up @@ -187,6 +228,14 @@ impl AsyncAppCommand<AppContext<OnlineOperations<'_>>> for FullScanCommand {

#[cfg(feature = "cbf")]
KyotoClient { client } => {
#[cfg(any(feature = "electrum", feature = "esplora"))]
if self.stop_gap.is_some() {
return Err(Error::Generic(
"`--scan-stop-gap` is only supported by the electrum and esplora backends"
.into(),
));
}

sync_kyoto_client(wallet, client).await?;
}
}
Expand All @@ -196,6 +245,27 @@ impl AsyncAppCommand<AppContext<OnlineOperations<'_>>> for FullScanCommand {
}
}

#[cfg(test)]
mod tests {
use super::FullScanCommand;
use clap::Parser;

#[test]
fn stop_gap_is_gated_to_supported_backends() {
#[cfg(any(feature = "electrum", feature = "esplora"))]
{
let default = FullScanCommand::try_parse_from(["bdk-cli"]).unwrap();
assert_eq!(default.stop_gap, None);
let custom =
FullScanCommand::try_parse_from(["bdk-cli", "--scan-stop-gap", "42"]).unwrap();
assert_eq!(custom.stop_gap, Some(42));
}

#[cfg(not(any(feature = "electrum", feature = "esplora")))]
assert!(FullScanCommand::try_parse_from(["bdk-cli", "--scan-stop-gap", "42"]).is_err());
}
}

#[derive(Parser, Debug, PartialEq, Eq, Clone)]
pub struct SyncCommand;

Expand Down