Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce evicted-at/last-evicted timestamps #1839

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

evanlinjin
Copy link
Member

@evanlinjin evanlinjin commented Feb 16, 2025

Partially Fixes #1740.
Replaces #1765.
Replaces #1811.

Description

This PR allows the receiving structures (bdk_chain, bdk_wallet) to detect and evict incoming transactions that are double spent (cancelled).

We add a new field to TxUpdate (TxUpdate::evicted_ats), which in turn, updates the last_evicted timestamps that are tracked/persisted by TxGraph. This is similar to how TxUpdate::seen_ats update the last_seen timestamp in TxGraph. Transactions with a last_evicted timestamp higher than their last_seen timestamp are considered evicted.

SpkWithExpectedTxids is introduced in SpkClient to track expected Txids for each spk. During a sync, if any Txids from SpkWithExpectedTxids are not in the current history of an spk obtained from the chain source, those Txids are considered missing. Support for SpkWithExpectedTxids has been added to both bdk_electrum and bdk_esplora chain source crates.

The canonicalization algorithm is updated to disregard transactions with a last_evicted timestamp greater than or equal to their last_seen timestamp, except in cases where transitivity rules apply.

Notes to the reviewers

This PR does not fix #1740 for block-by-block chain source (such as bdk_bitcoind_rpc). This work is done in a separate PR (#1857).

Changelog notice

  • Add TxUpdate::evicted_ats which tracks transactions that have been replaced and are no longer present in mempool.
  • Change TxGraph to track last_evicted timestamps. This is when a transaction is last marked as missing from the mempool.
  • The canonicalization algorithm now disregards transactions with a last_evicted timestamp greater than or equal to it's last_seen timestamp, except when a canonical descendant exists due to rules of transitivity.
  • Add SpkWithExpectedTxids in spk_client which keeps track of expected Txids for each spk.
  • Change bdk_electrum and bdk_esplora to understand SpkWithExpectedTxids.
  • Add SyncRequestBuilder::expected_txids_of_spk method which adds an association between txids and spks.

Checklists

All Submissions:

  • I've signed all my commits
  • I followed the contribution guidelines
  • I ran cargo fmt and cargo clippy before committing

New Features:

  • I've added tests for the new feature
  • I've added docs for the new feature

Bugfixes:

  • This pull request breaks the existing API
  • I've added tests to reproduce the issue which are now passing
  • I'm linking the issue being fixed by this PR

@evanlinjin evanlinjin force-pushed the evicted_at branch 5 times, most recently from 6dc26c4 to 0fcc097 Compare February 21, 2025 10:48
@LagginTimes LagginTimes force-pushed the evicted_at branch 2 times, most recently from 540a83f to 5107b3a Compare February 26, 2025 14:26
@evanlinjin evanlinjin force-pushed the evicted_at branch 6 times, most recently from 5718a3e to 07903a8 Compare February 28, 2025 03:34
@evanlinjin evanlinjin changed the title Temporary PR: Evicted at Introduce evicted-at/last-evicted timestamps Feb 28, 2025
@evanlinjin evanlinjin marked this pull request as ready for review February 28, 2025 04:00
@evanlinjin evanlinjin added bug Something isn't working api A breaking API change labels Feb 28, 2025
@evanlinjin evanlinjin requested a review from notmandatory March 2, 2025 06:13
Copy link
Contributor

@nymius nymius left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cACK 9d6c795

This unlocks functionality on demand and I think the approach is OK. Tests are thorough enough.

stevenroose added a commit to ark-bitcoin/bark that referenced this pull request Mar 6, 2025
This branch includes:
- bitcoindevkit/bdk#1855
- bitcoindevkit/bdk#1839
- the patch for esplora-client
Copy link
Contributor

@LagginTimes LagginTimes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Minor typos and grammar fixes.

This is a set of evicted txs from the mempool.
@evanlinjin evanlinjin force-pushed the evicted_at branch 3 times, most recently from c11515d to 6afb703 Compare March 7, 2025 05:19
evanlinjin and others added 3 commits March 7, 2025 16:22
The evicted-at and last-evicted timestamp informs the `TxGraph` when the
transaction was last deemed as missing (evicted) from the mempool.

The canonicalization algorithm is changed to disregard transactions with
a last-evicted timestamp greater or equal to it's last-seen timestamp.
The exception is when we have a canonical descendant due to rules of
transitivity.

Update rusqlite_impl to persist `last_evicted`.

Also update docs:
* Remove duplicate paragraphs about `ChangeSet`s.
* Add "Canonicalization" section which expands on methods that require
  canonicalization and the associated data types used in the
  canonicalization algorithm.
The spk history returned from Electrum should have these txs present.
Any missing tx will be considered evicted from the mempool.
* `TxGraph::try_list_expected_spk_txids`
* `TxGraph::list_expected_spk_txids`
* `IndexedTxGraph::try_list_expected_spk_txids`
* `IndexedTxGraph::list_expected_spk_txids`

Co-authored-by: valued mammal <valuedmammal@protonmail.com>
Co-authored-by: Wei Chen <wzc110@gmail.com>
@evanlinjin evanlinjin force-pushed the evicted_at branch 5 times, most recently from c23120c to e538e66 Compare March 7, 2025 06:28
evanlinjin and others added 2 commits March 7, 2025 17:59
Co-authored-by: Wei Chen <wzc110@gmail.com>
Also add `detect_receive_tx_cancel` test.
Also rm `miniscript` dependency.
Update ci to rm `miniscript/no-std` for `bdk_esplora`.

Co-authored-by: Wei Chen <wzc110@gmail.com>
Copy link
Member

@notmandatory notmandatory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ut ACK 1eb0c32

Thanks for the fix and all the new docs, looks like a solid solution. Before (or after) this goes in it would be nice to have some confirmation from @ErikDeSmedt that it fixes #1740 for him (at least using electrum and esplora).

@@ -94,7 +94,7 @@ jobs:
- name: Check esplora
working-directory: ./crates/esplora
# TODO "--target thumbv6m-none-eabi" should work but currently does not
run: cargo check --no-default-features --features miniscript/no-std,bdk_chain/hashbrown
run: cargo check --no-default-features --features bdk_chain/hashbrown
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 thanks for cleaning this up!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It raised my attention, shouldn't we be using bdk_core/hashbrown instead ?

//! methods only consider transactions that are "canonical" (i.e., in the best chain or in mempool).
//! We decide which transactions are canonical based on the transaction's anchors and the
//! `last_seen` (as unconfirmed) timestamp.
//! # Canonicalization
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Good to see these new docs as this is core to how the the tx_graph logic works.

@@ -1107,6 +1245,9 @@ pub struct ChangeSet<A = ()> {
pub anchors: BTreeSet<(A, Txid)>,
/// Added last-seen unix timestamps of transactions.
pub last_seen: BTreeMap<Txid, u64>,
/// Added timestamps of when a transaction is last evicted from the mempool.
#[cfg_attr(feature = "serde", serde(default))]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this cfg_attr only needed to load data from a pre-v2 serialized file?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this ensures backwards compatibility of our serialized changeset (as we promised).

Copy link
Contributor

@oleonardolima oleonardolima left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cACK 1eb0c32

Great work! I left some minor comments/questions that if applicable could be done in a follow-up PR.

/// When transactions were discovered to be missing (evicted) from the mempool.
///
/// [`SyncRequest::start_time`](crate::spk_client::SyncRequest::start_time) can be used to
/// provide the `seen_at` value.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
/// provide the `seen_at` value.
/// provide the `evicted_at` value.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be used to provide both actually. Will change.

Comment on lines +4 to +14
const PK_BYTES: &[u8] = &[
12, 244, 72, 4, 163, 4, 211, 81, 159, 82, 153, 123, 125, 74, 142, 40, 55, 237, 191, 231, 31,
114, 89, 165, 83, 141, 8, 203, 93, 240, 53, 101,
];

#[allow(dead_code)]
pub fn get_test_spk() -> ScriptBuf {
let secp = Secp256k1::new();
let pk = UntweakedPublicKey::from_slice(PK_BYTES).expect("Must be valid PK");
ScriptBuf::new_p2tr(&secp, pk, None)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: as it's common between bdk_esplora and bdk_electrum too, couldn't it be moved to bdk_testenv instead?

Comment on lines +375 to +380
/// 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)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: the iter_spks fn above is not being used anymore, at least for SyncRequest, do we need to keep it?

@@ -94,7 +94,7 @@ jobs:
- name: Check esplora
working-directory: ./crates/esplora
# TODO "--target thumbv6m-none-eabi" should work but currently does not
run: cargo check --no-default-features --features miniscript/no-std,bdk_chain/hashbrown
run: cargo check --no-default-features --features bdk_chain/hashbrown
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It raised my attention, shouldn't we be using bdk_core/hashbrown instead ?

Comment on lines +266 to +277
let (spks, expected_spk_txids): (Vec<(u32, ScriptBuf)>, HashMap<ScriptBuf, _>) = spks
.iter()
.map(|(spk_i, spk_with_expected_txids)| {
(
(*spk_i, spk_with_expected_txids.spk.clone()),
(
spk_with_expected_txids.spk.clone(),
spk_with_expected_txids.expected_txids.clone(),
),
)
})
.unzip();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious about the reasoning behind this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About what exactly? What the two variables are used for?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why split the batch of spks into two new containers?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api A breaking API change bug Something isn't working
Projects
Status: Needs Review
Development

Successfully merging this pull request may close these issues.

Undetected double-spent
6 participants