Skip to content

Commit

Permalink
fix(chain,wallet): Provide unconfirmed spends to chain src sync request
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
evanlinjin authored and LagginTimes committed Dec 24, 2024
1 parent 82c8e33 commit 58a6704
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 1 deletion.
47 changes: 46 additions & 1 deletion crates/chain/src/indexer/keychain_txout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
};
Expand Down Expand Up @@ -879,6 +881,18 @@ pub trait SyncRequestBuilderExt<K> {

/// Add [`Script`](bitcoin::Script)s that are revealed by the `indexer` but currently unused.
fn unused_spks_from_indexer(self, indexer: &KeychainTxOutIndex<K>) -> 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<A, C>(
self,
canonical_iter: CanonicalIter<A, C>,
indexer: &KeychainTxOutIndex<K>,
) -> Self
where
A: Anchor,
C: ChainOracle<Error = Infallible>;
}

impl<K: Clone + Ord + core::fmt::Debug> SyncRequestBuilderExt<K> for SyncRequestBuilder<(K, u32)> {
Expand All @@ -892,6 +906,37 @@ impl<K: Clone + Ord + core::fmt::Debug> SyncRequestBuilderExt<K> for SyncRequest
fn unused_spks_from_indexer(self, indexer: &KeychainTxOutIndex<K>) -> Self {
self.spks_with_indexes(indexer.unused_spks())
}

fn unconfirmed_outpoints<A, C>(
self,
canonical_iter: CanonicalIter<A, C>,
indexer: &KeychainTxOutIndex<K>,
) -> Self
where
A: Anchor,
C: ChainOracle<Error = Infallible>,
{
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::<Vec<_>>()
}),
)
}
}

/// Trait to extend [`FullScanRequestBuilder`].
Expand Down
26 changes: 26 additions & 0 deletions crates/wallet/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2445,13 +2445,39 @@ 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()
.chain_tip(self.chain.tip())
.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
Expand Down

0 comments on commit 58a6704

Please sign in to comment.