@@ -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) ]
16181619mod 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