Skip to content

Commit 1b90edf

Browse files
committed
Timestamp mempool evictions when observed
Locally inserted transactions can be newer than Bitcoin Core's latest mempool timestamp. Reporting that stale timestamp for an eviction makes BDK ignore it and leaves the transaction's inputs unavailable. Use the later of the local observation time and Bitcoin Core's mempool time. This makes local transactions evictable without regressing nodes whose Bitcoin Core clock is ahead of the application clock. Co-Authored-By: HAL 9000
1 parent eb9a6aa commit 1b90edf

1 file changed

Lines changed: 62 additions & 10 deletions

File tree

src/chain/bitcoind.rs

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,18 +1302,18 @@ impl BitcoindClient {
13021302
&self, bdk_unconfirmed_txids: Vec<Txid>,
13031303
) -> Result<Vec<(Txid, u64)>, BitcoindClientError> {
13041304
match self {
1305-
BitcoindClient::Rpc { latest_mempool_timestamp, mempool_entries_cache, .. } => {
1305+
BitcoindClient::Rpc { mempool_entries_cache, latest_mempool_timestamp, .. } => {
13061306
Self::get_evicted_mempool_txids_and_timestamp_inner(
1307-
latest_mempool_timestamp,
13081307
mempool_entries_cache,
1308+
latest_mempool_timestamp,
13091309
bdk_unconfirmed_txids,
13101310
)
13111311
.await
13121312
},
1313-
BitcoindClient::Rest { latest_mempool_timestamp, mempool_entries_cache, .. } => {
1313+
BitcoindClient::Rest { mempool_entries_cache, latest_mempool_timestamp, .. } => {
13141314
Self::get_evicted_mempool_txids_and_timestamp_inner(
1315-
latest_mempool_timestamp,
13161315
mempool_entries_cache,
1316+
latest_mempool_timestamp,
13171317
bdk_unconfirmed_txids,
13181318
)
13191319
.await
@@ -1322,16 +1322,17 @@ impl BitcoindClient {
13221322
}
13231323

13241324
async fn get_evicted_mempool_txids_and_timestamp_inner(
1325-
latest_mempool_timestamp: &AtomicU64,
13261325
mempool_entries_cache: &tokio::sync::Mutex<HashMap<Txid, MempoolEntry>>,
1327-
bdk_unconfirmed_txids: Vec<Txid>,
1326+
latest_mempool_timestamp: &AtomicU64, bdk_unconfirmed_txids: Vec<Txid>,
13281327
) -> Result<Vec<(Txid, u64)>, BitcoindClientError> {
1329-
let latest_mempool_timestamp = latest_mempool_timestamp.load(Ordering::Relaxed);
13301328
let mempool_entries_cache = mempool_entries_cache.lock().await;
1329+
let observed_at =
1330+
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
1331+
let evicted_at = observed_at.max(latest_mempool_timestamp.load(Ordering::Relaxed));
13311332
let evicted_txids = bdk_unconfirmed_txids
13321333
.into_iter()
13331334
.filter(|txid| !mempool_entries_cache.contains_key(txid))
1334-
.map(|txid| (txid, latest_mempool_timestamp))
1335+
.map(|txid| (txid, evicted_at))
13351336
.collect();
13361337
Ok(evicted_txids)
13371338
}
@@ -1616,8 +1617,10 @@ impl std::error::Error for BitcoindClientError {}
16161617

16171618
#[cfg(test)]
16181619
mod tests {
1620+
use std::collections::HashMap;
1621+
use std::sync::atomic::{AtomicU64, Ordering};
16191622
use std::sync::Mutex;
1620-
use std::time::Duration;
1623+
use std::time::{Duration, SystemTime, UNIX_EPOCH};
16211624

16221625
use bitcoin::hashes::Hash;
16231626
use bitcoin::{FeeRate, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, Txid, Witness};
@@ -1628,7 +1631,7 @@ mod tests {
16281631
use serde_json::json;
16291632

16301633
use crate::chain::bitcoind::{
1631-
acquire_initial_wallet_sync_guard, FeeResponse, GetMempoolEntryResponse,
1634+
acquire_initial_wallet_sync_guard, BitcoindClient, FeeResponse, GetMempoolEntryResponse,
16321635
GetRawMempoolResponse, GetRawTransactionResponse, MempoolMinFeeResponse,
16331636
};
16341637
use crate::chain::{WalletSyncGuard, WalletSyncStatus};
@@ -1659,6 +1662,55 @@ mod tests {
16591662
acquired_guard.complete(Ok(()));
16601663
}
16611664

1665+
#[tokio::test]
1666+
async fn eviction_uses_absence_observation_time() {
1667+
let txid = Txid::all_zeros();
1668+
let mempool_entries = tokio::sync::Mutex::new(HashMap::new());
1669+
let latest_mempool_timestamp = AtomicU64::new(0);
1670+
let before = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
1671+
1672+
let evicted = BitcoindClient::get_evicted_mempool_txids_and_timestamp_inner(
1673+
&mempool_entries,
1674+
&latest_mempool_timestamp,
1675+
vec![txid],
1676+
)
1677+
.await
1678+
.unwrap();
1679+
1680+
assert_eq!(evicted.len(), 1);
1681+
assert_eq!(evicted[0].0, txid);
1682+
assert!(evicted[0].1 >= before, "eviction timestamp must record when absence was observed");
1683+
}
1684+
1685+
#[tokio::test]
1686+
async fn eviction_preserves_newer_mempool_time() {
1687+
let txid = Txid::from_byte_array([1; 32]);
1688+
let client = BitcoindClient::new_rpc(
1689+
"127.0.0.1".to_string(),
1690+
18443,
1691+
"user".to_string(),
1692+
"password".to_string(),
1693+
);
1694+
let observed_at =
1695+
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
1696+
let newer_mempool_time = observed_at.saturating_add(60);
1697+
match &client {
1698+
BitcoindClient::Rpc { latest_mempool_timestamp, .. } => {
1699+
latest_mempool_timestamp.store(newer_mempool_time, Ordering::Relaxed);
1700+
},
1701+
BitcoindClient::Rest { .. } => unreachable!(),
1702+
}
1703+
1704+
let evicted = client.get_evicted_mempool_txids_and_timestamp(vec![txid]).await.unwrap();
1705+
1706+
assert_eq!(evicted.len(), 1);
1707+
assert_eq!(evicted[0].0, txid);
1708+
assert_eq!(
1709+
evicted[0].1, newer_mempool_time,
1710+
"eviction timestamp must not precede Bitcoin Core's mempool time"
1711+
);
1712+
}
1713+
16621714
prop_compose! {
16631715
fn arbitrary_witness()(
16641716
witness_elements in vec(vec(any::<u8>(), 0..100), 0..20)

0 commit comments

Comments
 (0)