Skip to content

Commit

Permalink
feat(core): Add expected txids to SyncRequest spks
Browse files Browse the repository at this point in the history
The spk history returned from Electrum should have these txs present.
Any missing tx will be considered evicted from the mempool.
  • Loading branch information
LagginTimes authored and evanlinjin committed Feb 16, 2025
1 parent bf77832 commit 6681ebf
Showing 1 changed file with 66 additions and 1 deletion.
67 changes: 66 additions & 1 deletion crates/core/src/spk_client.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Helper types for spk-based blockchain clients.
use crate::{
alloc::{boxed::Box, collections::VecDeque, vec::Vec},
collections::BTreeMap,
collections::{BTreeMap, HashMap, HashSet},
CheckPoint, ConfirmationBlockTime, Indexed,
};
use bitcoin::{OutPoint, Script, ScriptBuf, Txid};
Expand Down Expand Up @@ -86,6 +86,18 @@ impl SyncProgress {
}
}

/// [`Script`] with expected [`Txid`] histories.
pub struct SpkWithExpectedTxids {
/// Script pubkey.
pub spk: ScriptBuf,

/// [`Txid`]s that we expect to appear in the chain source's spk history response.
///
/// Any transaction listed here that is missing form the spk history response shoud be
/// considred evicted from the mempool.
pub expected_txids: HashSet<Txid>,
}

/// Builds a [`SyncRequest`].
///
/// Construct with [`SyncRequest::builder`].
Expand Down Expand Up @@ -154,6 +166,20 @@ impl<I> SyncRequestBuilder<I> {
self
}

/// Add transactions that are expected to exist under then given spks.
///
/// This is useful for detecting a malicious replacement of an incoming transaction.
pub fn expected_spk_txids(mut self, txs: impl IntoIterator<Item = (ScriptBuf, Txid)>) -> Self {
for (spk, txid) in txs {
self.inner
.spk_expected_txids
.entry(spk)
.or_default()
.insert(txid);
}
self
}

/// Add [`Txid`]s that will be synced against.
pub fn txids(mut self, txids: impl IntoIterator<Item = Txid>) -> Self {
self.inner.txids.extend(txids);
Expand Down Expand Up @@ -210,6 +236,7 @@ pub struct SyncRequest<I = ()> {
chain_tip: Option<CheckPoint>,
spks: VecDeque<(I, ScriptBuf)>,
spks_consumed: usize,
spk_expected_txids: HashMap<ScriptBuf, HashSet<Txid>>,
txids: VecDeque<Txid>,
txids_consumed: usize,
outpoints: VecDeque<OutPoint>,
Expand All @@ -232,6 +259,7 @@ impl<I> SyncRequest<I> {
chain_tip: None,
spks: VecDeque::new(),
spks_consumed: 0,
spk_expected_txids: HashMap::new(),
txids: VecDeque::new(),
txids_consumed: 0,
outpoints: VecDeque::new(),
Expand Down Expand Up @@ -283,6 +311,23 @@ impl<I> SyncRequest<I> {
Some(spk)
}

/// Advances the sync request and returns the next [`ScriptBuf`] with corresponding [`Txid`]
/// history.
///
/// Returns [`None`] when there are no more scripts remaining in the request.
pub fn next_spk_with_expected_txids(&mut self) -> Option<SpkWithExpectedTxids> {
let next_spk = self.next_spk()?;
let spk_history = self
.spk_expected_txids
.get(&next_spk)
.cloned()
.unwrap_or_default();
Some(SpkWithExpectedTxids {
spk: next_spk,
expected_txids: spk_history,
})
}

/// Advances the sync request and returns the next [`Txid`].
///
/// Returns [`None`] when there are no more txids remaining in the request.
Expand All @@ -308,6 +353,13 @@ impl<I> SyncRequest<I> {
SyncIter::<I, ScriptBuf>::new(self)
}

/// Iterate over [`ScriptBuf`]s with corresponding [`Txid`] histories contained in this request.
pub fn iter_spks_with_expected_txids(
&mut self,
) -> impl ExactSizeIterator<Item = SpkWithExpectedTxids> + '_ {
SyncIter::<I, SpkWithExpectedTxids>::new(self)
}

/// Iterate over [`Txid`]s contained in this request.
pub fn iter_txids(&mut self) -> impl ExactSizeIterator<Item = Txid> + '_ {
SyncIter::<I, Txid>::new(self)
Expand Down Expand Up @@ -536,6 +588,19 @@ impl<I> Iterator for SyncIter<'_, I, ScriptBuf> {
}
}

impl<I> Iterator for SyncIter<'_, I, SpkWithExpectedTxids> {
type Item = SpkWithExpectedTxids;

fn next(&mut self) -> Option<Self::Item> {
self.request.next_spk_with_expected_txids()
}

fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.request.spks.len();
(remaining, Some(remaining))
}
}

impl<I> Iterator for SyncIter<'_, I, Txid> {
type Item = Txid;

Expand Down

0 comments on commit 6681ebf

Please sign in to comment.