bitcoindevkit / bitcoindevkit/bdk
Document that eviction is inferred from a single omission by the chain source
- Dominant language
- Rust
- Stars
- 1.1k
- Forks
- 483
- Avg merge
- 20d 3h
- Merged PRs (30d)
- 3
Description
**Describe the bug**
`SyncRequestBuilder::expected_spk_txids` (`crates/core/src/spk_client.rs:179-191`) is documented as a way to detect a replaced incoming transaction, and `TxUpdate::evicted_ats` as "when transactions were discovered to be missing (evicted) from the mempool". Neither explains how an eviction is determined or what follows from it:
- The Electrum and Esplora clients stamp `evicted_at = start_time` for every expected txid that is absent from the script's history in that one response (`crates/electrum/src/bdk_electrum_client.rs:327-331`, `crates/esplora/src/blocking_ext.rs:314-318` and `344-346`, same in `async_ext.rs`). There is no distinction between "the transaction left the mempool" and "the source did not list it"; a single incomplete or inconsistent history response is enough.
- Once `last_evicted >= last_seen` (`TxNode::is_evicted`, `crates/chain/src/tx_graph.rs:229-233`), the transaction is excluded from the canonical view. It disappears from balances and transaction lists, and the outputs it spent appear unspent again, i.e. they become eligible for coin selection in a new, conflicting transaction. This applies to the wallet's own broadcast transactions as much as to incoming ones.
- `last_evicted` only ever increases (`insert_evicted_at`, `crates/chain/src/tx_graph.rs:882-887`) and is persisted. The transaction only comes back if a later sync reports it again with a newer `seen_at`, or it receives an anchor.
These may well be the intended trade-offs, but callers deciding whether to pass expected txids (e.g. from `CanonicalView::list_expected_spk_txids`), or how to treat a transaction for which `is_evicted()` is true, currently have to infer the trust placed in the chain source from the implementation.
This issue was found by AI.
**To Reproduce**
1. Sync a wallet that has an unconfirmed transaction `T`, passing `T`'s txid via `expected_spk_txids` for one of its scripts.
2. Let the source omit `T` from that script's history once; the update contains `(T, start_time)` in `evicted_ats`.
3. After applying the update, `get_tx_node(T).is_evicted()` is `true`, `T` is gone from the canonical view and balance, and its inputs are unspent again.
4. Once the source lists `T` again in a later sync, `T` is back.
**Expected behavior**
The documentation of `expected_spk_txids`, `evicted_ats`, `insert_evicted_at` and `is_evicted` should state how evictions are inferred and what they imply for the canonical view, balances and spendable outputs, so callers can judge the trust they place in their chain source.
Contributor guide
Research direction
Start with SyncRequestBuilder::expected_spk_txids in crates/core/src/spk_client.rs, then read TxUpdate::evicted_ats, TxNode::is_evicted, and insert_evicted_at in crates/chain/src/tx_graph.rs. Check the Electrum and Esplora call sites in bdk_electrum_client.rs, blocking_ext.rs, and async_ext.rs. Done means the relevant documentation explains the single-response omission, canonical-view effects, persistence, and recovery behavior described in the issue.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- documentation
- Issue type
- Documentation
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100