diff --git a/crates/chain/src/indexer/keychain_txout.rs b/crates/chain/src/indexer/keychain_txout.rs index 99931cf5e..36cc0d3ae 100644 --- a/crates/chain/src/indexer/keychain_txout.rs +++ b/crates/chain/src/indexer/keychain_txout.rs @@ -10,7 +10,7 @@ use crate::{ spk_txout::SpkTxOutIndex, DescriptorExt, DescriptorId, Indexed, Indexer, KeychainIndexed, SpkIterator, }; -use alloc::{borrow::ToOwned, vec::Vec}; +use alloc::{borrow::ToOwned, string::String, string::ToString, vec::Vec}; use bitcoin::{ key::Secp256k1, Amount, OutPoint, Script, ScriptBuf, SignedAmount, Transaction, TxOut, Txid, }; @@ -973,25 +973,39 @@ pub enum InsertDescriptorError { }, } -impl core::fmt::Display for InsertDescriptorError { +/// Returns a shortened string representation of a descriptor. +/// Shows the first and last `edge_len` characters, separated by `...`. +fn short_descriptor(desc: &Descriptor, edge_len: usize) -> String { + let s = ToString::to_string(desc); + if s.len() <= edge_len * 2 { + return s; + } + format!("{}...{}", &s[..edge_len], &s[s.len() - edge_len..]) +} + +impl core::fmt::Display for InsertDescriptorError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { InsertDescriptorError::DescriptorAlreadyAssigned { - existing_assignment: existing, + existing_assignment, descriptor, } => { + let short_desc = short_descriptor(descriptor, 4); write!( f, - "attempt to re-assign descriptor {descriptor:?} already assigned to {existing:?}" + "Descriptor '{}' is already in use by keychain '{}'. Please use a different descriptor.", + short_desc, existing_assignment ) } InsertDescriptorError::KeychainAlreadyAssigned { - existing_assignment: existing, + existing_assignment, keychain, } => { + let short_desc = short_descriptor(existing_assignment, 4); write!( f, - "attempt to re-assign keychain {keychain:?} already assigned to {existing:?}" + "Keychain '{}' is already associated with descriptor '{}'. Please choose a different keychain.", + keychain, short_desc ) } } @@ -999,7 +1013,7 @@ impl core::fmt::Display for InsertDescriptorError { } #[cfg(feature = "std")] -impl std::error::Error for InsertDescriptorError {} +impl std::error::Error for InsertDescriptorError {} /// `ChangeSet` represents persistent updates to a [`KeychainTxOutIndex`]. /// diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index 97d4ecc02..457a478aa 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -256,10 +256,28 @@ pub enum CalculateFeeError { impl fmt::Display for CalculateFeeError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - CalculateFeeError::MissingTxOut(outpoints) => write!( - f, - "missing `TxOut` for one or more of the inputs of the tx: {outpoints:?}", - ), + CalculateFeeError::MissingTxOut(outpoints) => { + let max_show = 3; + let shown = outpoints.iter().take(max_show); + let remaining = outpoints.len().saturating_sub(max_show); + + write!(f, "missing `TxOut` for input(s)")?; + if outpoints.is_empty() { + write!(f, ": ") + } else { + write!(f, ": ")?; + for (i, op) in shown.enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", op)?; + } + if remaining > 0 { + write!(f, " (+{} more)", remaining)?; + } + Ok(()) + } + } CalculateFeeError::NegativeFee(fee) => write!( f, "transaction is invalid according to the graph and has negative fee: {}", diff --git a/crates/chain/tests/test_keychain_txout_index.rs b/crates/chain/tests/test_keychain_txout_index.rs index 0cce8476d..263a0fa86 100644 --- a/crates/chain/tests/test_keychain_txout_index.rs +++ b/crates/chain/tests/test_keychain_txout_index.rs @@ -18,6 +18,15 @@ enum TestKeychain { Internal, } +impl core::fmt::Display for TestKeychain { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + TestKeychain::External => write!(f, "External"), + TestKeychain::Internal => write!(f, "Internal"), + } + } +} + fn parse_descriptor(descriptor: &str) -> Descriptor { let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only(); Descriptor::::parse_descriptor(&secp, descriptor) diff --git a/crates/core/src/spk_client.rs b/crates/core/src/spk_client.rs index 2c80e70af..8612959cb 100644 --- a/crates/core/src/spk_client.rs +++ b/crates/core/src/spk_client.rs @@ -225,6 +225,7 @@ impl SyncRequestBuilder { /// [`chain_tip`](SyncRequestBuilder::chain_tip) (if provided). /// /// ```rust +/// # use std::io::{self, Write}; /// # use bdk_chain::{bitcoin::{hashes::Hash, ScriptBuf}, local_chain::LocalChain}; /// # use bdk_chain::spk_client::SyncRequest; /// # let (local_chain, _) = LocalChain::from_genesis(Hash::all_zeros()); @@ -236,7 +237,22 @@ impl SyncRequestBuilder { /// // Provide list of scripts to scan for transactions against. /// .spks(scripts) /// // This is called for every synced item. -/// .inspect(|item, progress| println!("{} (remaining: {})", item, progress.remaining())) +/// .inspect(|item, progress| { +/// let pc = (100.0 * progress.consumed() as f32) / progress.total() as f32; +/// match item { +/// // In this example I = (), so the first field of Spk is unit. +/// bdk_chain::spk_client::SyncItem::Spk((), spk) => { +/// eprintln!("[ SCANNING {pc:03.0}% ] script {}", spk); +/// } +/// bdk_chain::spk_client::SyncItem::Txid(txid) => { +/// eprintln!("[ SCANNING {pc:03.0}% ] txid {}", txid); +/// } +/// bdk_chain::spk_client::SyncItem::OutPoint(op) => { +/// eprintln!("[ SCANNING {pc:03.0}% ] outpoint {}", op); +/// } +/// } +/// let _ = io::stderr().flush(); +/// }) /// // Finish constructing the sync request. /// .build(); /// ``` diff --git a/crates/file_store/src/lib.rs b/crates/file_store/src/lib.rs index d7dd35bf4..786e26da6 100644 --- a/crates/file_store/src/lib.rs +++ b/crates/file_store/src/lib.rs @@ -26,11 +26,18 @@ pub enum StoreError { impl core::fmt::Display for StoreError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - Self::Io(e) => write!(f, "io error trying to read file: {e}"), - Self::InvalidMagicBytes { got, expected } => write!( - f, - "file has invalid magic bytes: expected={expected:?} got={got:?}", - ), + Self::Io(e) => write!(f, "io error trying to read file: {}", e), + Self::InvalidMagicBytes { got, expected } => { + write!(f, "file has invalid magic bytes: expected=0x")?; + for b in expected { + write!(f, "{:02x}", b)?; + } + write!(f, " got=0x")?; + for b in got { + write!(f, "{:02x}", b)?; + } + Ok(()) + } Self::Bincode(e) => write!(f, "bincode error while reading entry {e}"), } } diff --git a/examples/example_electrum/src/main.rs b/examples/example_electrum/src/main.rs index aa89f07e1..cf9516e79 100644 --- a/examples/example_electrum/src/main.rs +++ b/examples/example_electrum/src/main.rs @@ -210,9 +210,22 @@ fn main() -> anyhow::Result<()> { .chain_tip(chain_tip.clone()) .inspect(|item, progress| { let pc = (100 * progress.consumed()) as f32 / progress.total() as f32; - eprintln!("[ SCANNING {pc:03.0}% ] {item}"); + match item { + bdk_chain::spk_client::SyncItem::Spk((keychain, index), spk) => { + eprintln!( + "[ SCANNING {pc:03.0}% ] script {} {} {}", + keychain, index, spk + ); + } + bdk_chain::spk_client::SyncItem::Txid(txid) => { + eprintln!("[ SCANNING {pc:03.0}% ] txid {}", txid); + } + bdk_chain::spk_client::SyncItem::OutPoint(op) => { + eprintln!("[ SCANNING {pc:03.0}% ] outpoint {}", op); + } + } + let _ = io::stderr().flush(); }); - let canonical_view = graph.canonical_view( &*chain, chain_tip.block_id(), diff --git a/examples/example_esplora/src/main.rs b/examples/example_esplora/src/main.rs index 99f72391c..4766303be 100644 --- a/examples/example_esplora/src/main.rs +++ b/examples/example_esplora/src/main.rs @@ -215,8 +215,20 @@ fn main() -> anyhow::Result<()> { .chain_tip(local_tip.clone()) .inspect(|item, progress| { let pc = (100 * progress.consumed()) as f32 / progress.total() as f32; - eprintln!("[ SCANNING {pc:03.0}% ] {item}"); - // Flush early to ensure we print at every iteration. + match item { + bdk_chain::spk_client::SyncItem::Spk((keychain, index), spk) => { + eprintln!( + "[ SCANNING {pc:03.0}% ] script {} {} {}", + keychain, index, spk + ); + } + bdk_chain::spk_client::SyncItem::Txid(txid) => { + eprintln!("[ SCANNING {pc:03.0}% ] txid {}", txid); + } + bdk_chain::spk_client::SyncItem::OutPoint(op) => { + eprintln!("[ SCANNING {pc:03.0}% ] outpoint {}", op); + } + } let _ = io::stderr().flush(); });