bitcoindevkit / bitcoindevkit/bdk
Clock changes can hide present transactions or keep dropped transactions pending
- Dominant language
- Rust
- Stars
- 1.1k
- Forks
- 483
- Avg merge
- 20d 3h
- Merged PRs (30d)
- 3
Description
**Describe the bug**
If the device clock moves backwards between syncs, Electrum/Esplora stamp later eviction observations with timestamps older than `last_seen`. `TxGraph` continues counting the dropped transaction toward pending balance while these timestamps remain behind. Reusing an older `SyncRequest::builder_at` timestamp has the same effect.
A future-dated eviction timestamp causes the reverse problem: after correcting the clock, later sightings cannot exceed last_evicted, so a present transaction remains hidden. This survives changeset persistence and reload.
This issue was found by AI.
**To Reproduce**
Add this test to `crates/chain/tests/test_tx_graph.rs` and run `cargo test -p bdk_chain --test test_tx_graph backward_clock_keeps_dropped_transaction_pending`. Reproduced on `acc06e53220960caa89efd5984d7b43914640dd4`.
```rust
#[test]
fn backward_clock_keeps_dropped_transaction_pending() {
use bdk_chain::bitcoin::{
absolute, hashes::Hash, transaction, Amount, BlockHash, OutPoint, ScriptBuf,
Transaction, TxIn, TxOut, Txid,
};
use bdk_chain::{local_chain::LocalChain, BlockId, TxGraph};
let chain = LocalChain::from_blocks([(0, BlockHash::all_zeros())].into()).unwrap();
let tx = Transaction {
version: transaction::Version::TWO,
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint::new(Txid::all_zeros(), 0),
..Default::default()
}],
output: vec![TxOut {
value: Amount::from_sat(75_000),
script_pubkey: ScriptBuf::new(),
}],
};
let txid = tx.compute_txid();
let mut graph = TxGraph::::default();
let _ = graph.insert_tx(tx);
let _ = graph.insert_seen_at(txid, 1_750_000_000);
let pending = |graph: &TxGraph| {
chain.canonical_view(graph, chain.tip().block_id(), Default::default())
.balance([((), OutPoint::new(txid, 0))], |_, _| false, 1)
.untrusted_pending
};
assert_eq!(pending(&graph), Amount::from_sat(75_000));
// Later syncs report the transaction missing after the clock moved backwards.
for evicted_at in 1_749_999_900..1_749_999_905 {
let _ = graph.insert_evicted_at(txid, evicted_at);
}
assert_eq!(pending(&graph), Amount::ZERO); // Fails: still 75,000 sat.
}
```
**Expected behavior**
Clock changes should not leave dropped transactions pending or hide transactions that later syncs report present.
Contributor guide
Research direction
Start with crates/chain/tests/test_tx_graph.rs and run cargo test -p bdk_chain --test test_tx_graph backward_clock_keeps_dropped_transaction_pending. Trace TxGraph's insert_seen_at and insert_evicted_at handling for the timestamp cases shown in the reproduction. Done means backward and forward clock changes no longer leave dropped transactions pending or hide transactions later reported present, including after persistence and reload.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- blockchain
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 70/100