bitcoindevkit / bitcoindevkit/bdk

Clock changes can hide present transactions or keep dropped transactions pending

Open
#2,280 0 comments 0 reactions 0 assignees View on GitHub
bug
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.