From 23af05ff856d1e59c242c7ba5a9d0bea6f6820b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Tue, 24 Dec 2024 16:51:32 +1100 Subject: [PATCH] fix(chain,wallet): Provide unconfirmed spends to chain src sync request Unconfirmed outputs can now be easily provided to the `SyncRequest` via `SyncRequestExt::unconfirmed_spends`. This allows the chain src to detect receiving txs being replaced/cancelled. `Wallet::start_sync_with_revealed_spks` has been deprecated in favor of `start_sync` which included unconfirmed spends. --- crates/chain/src/indexer/keychain_txout.rs | 47 +++++++++++++++++++++- crates/wallet/src/wallet/mod.rs | 26 ++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/crates/chain/src/indexer/keychain_txout.rs b/crates/chain/src/indexer/keychain_txout.rs index 80bd879d7..5de7e0f1e 100644 --- a/crates/chain/src/indexer/keychain_txout.rs +++ b/crates/chain/src/indexer/keychain_txout.rs @@ -7,11 +7,13 @@ use crate::{ spk_client::{FullScanRequestBuilder, SyncRequestBuilder}, spk_iter::BIP32_MAX_INDEX, spk_txout::SpkTxOutIndex, - DescriptorExt, DescriptorId, Indexed, Indexer, KeychainIndexed, SpkIterator, + Anchor, CanonicalIter, ChainOracle, DescriptorExt, DescriptorId, Indexed, Indexer, + KeychainIndexed, SpkIterator, }; use alloc::{borrow::ToOwned, vec::Vec}; use bitcoin::{Amount, OutPoint, ScriptBuf, SignedAmount, Transaction, TxOut, Txid}; use core::{ + convert::Infallible, fmt::Debug, ops::{Bound, RangeBounds}, }; @@ -879,6 +881,18 @@ pub trait SyncRequestBuilderExt { /// Add [`Script`](bitcoin::Script)s that are revealed by the `indexer` but currently unused. fn unused_spks_from_indexer(self, indexer: &KeychainTxOutIndex) -> Self; + + /// Add [`OutPoint`]s which are spent by unconfirmed transactions. + /// + /// This allows the chain source to detect transactions which are cancelled/replaced. + fn unconfirmed_outpoints( + self, + canonical_iter: CanonicalIter, + indexer: &KeychainTxOutIndex, + ) -> Self + where + A: Anchor, + C: ChainOracle; } impl SyncRequestBuilderExt for SyncRequestBuilder<(K, u32)> { @@ -892,6 +906,37 @@ impl SyncRequestBuilderExt for SyncRequest fn unused_spks_from_indexer(self, indexer: &KeychainTxOutIndex) -> Self { self.spks_with_indexes(indexer.unused_spks()) } + + fn unconfirmed_outpoints( + self, + canonical_iter: CanonicalIter, + indexer: &KeychainTxOutIndex, + ) -> Self + where + A: Anchor, + C: ChainOracle, + { + self.outpoints( + canonical_iter + .filter_map(|r| { + let (_, tx, reason) = r.expect("infallible"); + match reason { + crate::CanonicalReason::ObservedIn { .. } + if indexer.is_tx_relevant(&tx) => + { + Some(tx) + } + _ => None, + } + }) + .flat_map(|tx| { + tx.input + .iter() + .map(|txin| txin.previous_output) + .collect::>() + }), + ) + } } /// Trait to extend [`FullScanRequestBuilder`]. diff --git a/crates/wallet/src/wallet/mod.rs b/crates/wallet/src/wallet/mod.rs index 2e068a95f..f703970ef 100644 --- a/crates/wallet/src/wallet/mod.rs +++ b/crates/wallet/src/wallet/mod.rs @@ -2445,6 +2445,10 @@ impl Wallet { /// This is the first step when performing a spk-based wallet partial sync, the returned /// [`SyncRequest`] collects all revealed script pubkeys from the wallet keychain needed to /// start a blockchain sync with a spk based blockchain client. + #[deprecated( + since = "1.1.0", + note = "start_sync_with_revealed_spks could not detect receiving transactions being replaced. Use start_sync instead" + )] pub fn start_sync_with_revealed_spks(&self) -> SyncRequestBuilder<(KeychainKind, u32)> { use bdk_chain::keychain_txout::SyncRequestBuilderExt; SyncRequest::builder() @@ -2452,6 +2456,28 @@ impl Wallet { .revealed_spks_from_indexer(&self.indexed_graph.index, ..) } + /// Create a [`SyncRequest`] for this wallet. + /// + /// This assembles a request which initiates a sync against a spk-based chain source. This + /// request contains all revealed script pubkeys and unconfirmed spends. + /// + /// This request can detect when transactions get cancelled/replaced. + pub fn start_sync(&self) -> SyncRequestBuilder<(KeychainKind, u32)> { + use bdk_chain::keychain_txout::SyncRequestBuilderExt; + + let chain = &self.chain; + let tx_graph = &self.indexed_graph.graph(); + let tx_index = &self.indexed_graph.index; + + SyncRequest::builder() + .chain_tip(chain.tip()) + .revealed_spks_from_indexer(tx_index, ..) + .unconfirmed_outpoints( + tx_graph.canonical_iter(chain, chain.tip().block_id()), + tx_index, + ) + } + /// Create a [`FullScanRequest] for this wallet. /// /// This is the first step when performing a spk-based wallet full scan, the returned