Skip to content

Commit e6fa232

Browse files
committed
Retrieve and apply evicted transactions to BDK wallet
With version 2.0, BDK introduced a new `Wallet::apply_evicted_txs` API that requires that we tell the `Wallet` about any mempool evictions. Here, we implement this as part of our mempool polling, which should fix a bug that left the wallet unable to spend funds if previously-canoncical mempool transactions ended up being evicted.
1 parent 00cf9d9 commit e6fa232

File tree

3 files changed

+53
-7
lines changed

3 files changed

+53
-7
lines changed

src/chain/bitcoind_rpc.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,24 @@ impl BitcoindRpcClient {
190190
Ok(())
191191
}
192192

193+
/// Returns two `Vec`s:
194+
/// - mempool transactions, alongside their first-seen unix timestamps.
195+
/// - transactions that have been evicted from the mempool, alongside the last time they were seen absent.
196+
pub(crate) async fn get_updated_mempool_transactions(
197+
&self, best_processed_height: u32, unconfirmed_txids: Vec<Txid>,
198+
) -> std::io::Result<(Vec<(Transaction, u64)>, Vec<(Txid, u64)>)> {
199+
let mempool_txs =
200+
self.get_mempool_transactions_and_timestamp_at_height(best_processed_height).await?;
201+
let evicted_txids = self.get_evicted_mempool_txids_and_timestamp(unconfirmed_txids).await?;
202+
Ok((mempool_txs, evicted_txids))
203+
}
204+
193205
/// Get mempool transactions, alongside their first-seen unix timestamps.
194206
///
195207
/// This method is an adapted version of `bdk_bitcoind_rpc::Emitter::mempool`. It emits each
196208
/// transaction only once, unless we cannot assume the transaction's ancestors are already
197209
/// emitted.
198-
pub(crate) async fn get_mempool_transactions_and_timestamp_at_height(
210+
async fn get_mempool_transactions_and_timestamp_at_height(
199211
&self, best_processed_height: u32,
200212
) -> std::io::Result<Vec<(Transaction, u64)>> {
201213
let prev_mempool_time = self.latest_mempool_timestamp.load(Ordering::Relaxed);
@@ -252,6 +264,23 @@ impl BitcoindRpcClient {
252264
}
253265
Ok(txs_to_emit)
254266
}
267+
268+
// Retrieve a list of Txids that have been evicted from the mempool.
269+
//
270+
// To this end, we first update our local mempool_entries_cache and then return all unconfirmed
271+
// wallet `Txid`s that don't appear in the mempool still.
272+
async fn get_evicted_mempool_txids_and_timestamp(
273+
&self, unconfirmed_txids: Vec<Txid>,
274+
) -> std::io::Result<Vec<(Txid, u64)>> {
275+
let latest_mempool_timestamp = self.latest_mempool_timestamp.load(Ordering::Relaxed);
276+
let mempool_entries_cache = self.mempool_entries_cache.lock().await;
277+
let evicted_txids = unconfirmed_txids
278+
.into_iter()
279+
.filter(|txid| mempool_entries_cache.contains_key(txid))
280+
.map(|txid| (txid, latest_mempool_timestamp))
281+
.collect();
282+
Ok(evicted_txids)
283+
}
255284
}
256285

257286
impl BlockSource for BitcoindRpcClient {

src/chain/mod.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,18 +1095,24 @@ impl ChainSource {
10951095
let cur_height = channel_manager.current_best_block().height;
10961096

10971097
let now = SystemTime::now();
1098+
let unconfirmed_txids = onchain_wallet.get_unconfirmed_txids();
10981099
match bitcoind_rpc_client
1099-
.get_mempool_transactions_and_timestamp_at_height(cur_height)
1100+
.get_updated_mempool_transactions(cur_height, unconfirmed_txids)
11001101
.await
11011102
{
1102-
Ok(unconfirmed_txs) => {
1103+
Ok((unconfirmed_txs, evicted_txids)) => {
11031104
log_trace!(
11041105
logger,
1105-
"Finished polling mempool of size {} in {}ms",
1106+
"Finished polling mempool of size {} and {} evicted transactions in {}ms",
11061107
unconfirmed_txs.len(),
1108+
evicted_txids.len(),
11071109
now.elapsed().unwrap().as_millis()
11081110
);
1109-
let _ = onchain_wallet.apply_unconfirmed_txs(unconfirmed_txs);
1111+
onchain_wallet
1112+
.apply_mempool_txs(unconfirmed_txs, evicted_txids)
1113+
.unwrap_or_else(|e| {
1114+
log_error!(logger, "Failed to apply mempool transactions: {:?}", e);
1115+
});
11101116
},
11111117
Err(e) => {
11121118
log_error!(logger, "Failed to poll for mempool transactions: {:?}", e);

src/wallet/mod.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ where
107107
self.inner.lock().unwrap().tx_graph().full_txs().map(|tx_node| tx_node.tx).collect()
108108
}
109109

110+
pub(crate) fn get_unconfirmed_txids(&self) -> Vec<Txid> {
111+
self.inner
112+
.lock()
113+
.unwrap()
114+
.transactions()
115+
.filter(|t| t.chain_position.is_unconfirmed())
116+
.map(|t| t.tx_node.txid)
117+
.collect()
118+
}
119+
110120
pub(crate) fn current_best_block(&self) -> BestBlock {
111121
let checkpoint = self.inner.lock().unwrap().latest_checkpoint();
112122
BestBlock { block_hash: checkpoint.hash(), height: checkpoint.height() }
@@ -136,11 +146,12 @@ where
136146
}
137147
}
138148

139-
pub(crate) fn apply_unconfirmed_txs(
140-
&self, unconfirmed_txs: Vec<(Transaction, u64)>,
149+
pub(crate) fn apply_mempool_txs(
150+
&self, unconfirmed_txs: Vec<(Transaction, u64)>, evicted_txids: Vec<(Txid, u64)>,
141151
) -> Result<(), Error> {
142152
let mut locked_wallet = self.inner.lock().unwrap();
143153
locked_wallet.apply_unconfirmed_txs(unconfirmed_txs);
154+
locked_wallet.apply_evicted_txs(evicted_txids);
144155

145156
let mut locked_persister = self.persister.lock().unwrap();
146157
locked_wallet.persist(&mut locked_persister).map_err(|e| {

0 commit comments

Comments
 (0)