From 714fd5e142ffdf95d5ca96cd5a1d6edbc68239bb Mon Sep 17 00:00:00 2001 From: jp1ac4 <121959000+jp1ac4@users.noreply.github.com> Date: Fri, 10 Nov 2023 09:36:10 +0000 Subject: [PATCH] bitcoin: add `mempool_spenders` to Bitcoin interface `is_in_mempool` has been updated to use `mempool_entry`. --- src/bitcoin/d/mod.rs | 78 ++++++++++++++++++++++++++++++++++++++++++-- src/bitcoin/mod.rs | 16 ++++++++- src/testutils.rs | 6 +++- 3 files changed, 96 insertions(+), 4 deletions(-) diff --git a/src/bitcoin/d/mod.rs b/src/bitcoin/d/mod.rs index 93f8af7b..601ca10c 100644 --- a/src/bitcoin/d/mod.rs +++ b/src/bitcoin/d/mod.rs @@ -1120,20 +1120,49 @@ impl BitcoinD { /// Whether this transaction is in the mempool. pub fn is_in_mempool(&self, txid: &bitcoin::Txid) -> bool { + self.mempool_entry(txid).is_some() + } + + /// Get mempool entry of the given transaction. + /// Returns `None` if it is not in the mempool. + pub fn mempool_entry(&self, txid: &bitcoin::Txid) -> Option { match self .make_fallible_node_request("getmempoolentry", ¶ms!(Json::String(txid.to_string()))) { - Ok(_) => true, + Ok(json) => Some(MempoolEntry::from(json)), Err(BitcoindError::Server(jsonrpc::Error::Rpc(jsonrpc::error::RpcError { code: -5, .. - }))) => false, + }))) => None, Err(e) => { panic!("Unexpected error returned by bitcoind {}", e); } } } + /// Get the list of txids spending those outpoints in mempool. + pub fn mempool_txs_spending_prevouts( + &self, + outpoints: &[bitcoin::OutPoint], + ) -> Vec { + let prevouts: Json = outpoints + .iter() + .map(|op| serde_json::json!({"txid": op.txid.to_string(), "vout": op.vout})) + .collect(); + self.make_node_request("gettxspendingprevout", ¶ms!(prevouts)) + .as_array() + .expect("Always returns an array") + .iter() + .filter_map(|e| { + e.get("spendingtxid").map(|e| { + e.as_str() + .and_then(|s| bitcoin::Txid::from_str(s).ok()) + .expect("Must be a valid txid if present") + }) + }) + .collect() + } + /// Stop bitcoind. pub fn stop(&self) { self.make_node_request("stop", &[]); @@ -1394,3 +1423,48 @@ impl<'a> CachedTxGetter<'a> { } } } + +#[derive(Debug, Clone)] +pub struct MempoolEntry { + pub vsize: u64, + pub fees: MempoolEntryFees, +} + +impl From for MempoolEntry { + fn from(json: Json) -> MempoolEntry { + let vsize = json + .get("vsize") + .and_then(Json::as_u64) + .expect("Must be present in bitcoind response"); + let fees = json + .get("fees") + .as_ref() + .expect("Must be present in bitcoind response") + .into(); + + MempoolEntry { vsize, fees } + } +} + +#[derive(Debug, Clone)] +pub struct MempoolEntryFees { + pub base: bitcoin::Amount, + pub descendant: bitcoin::Amount, +} + +impl From<&&Json> for MempoolEntryFees { + fn from(json: &&Json) -> MempoolEntryFees { + let json = json.as_object().expect("fees must be an object"); + let base = json + .get("base") + .and_then(Json::as_f64) + .and_then(|a| bitcoin::Amount::from_btc(a).ok()) + .expect("Must be present and a valid amount"); + let descendant = json + .get("descendant") + .and_then(Json::as_f64) + .and_then(|a| bitcoin::Amount::from_btc(a).ok()) + .expect("Must be present and a valid amount"); + MempoolEntryFees { base, descendant } + } +} diff --git a/src/bitcoin/mod.rs b/src/bitcoin/mod.rs index bfed816a..4ac4680d 100644 --- a/src/bitcoin/mod.rs +++ b/src/bitcoin/mod.rs @@ -9,7 +9,7 @@ use crate::{ bitcoin::d::{BitcoindError, CachedTxGetter, LSBlockEntry}, descriptors, }; -pub use d::SyncProgress; +pub use d::{MempoolEntry, MempoolEntryFees, SyncProgress}; use std::{fmt, sync}; @@ -113,6 +113,9 @@ pub trait BitcoinInterface: Send { &self, txid: &bitcoin::Txid, ) -> Option<(bitcoin::Transaction, Option)>; + + /// Get the details of unconfirmed transactions spending these outpoints, if any. + fn mempool_spenders(&self, outpoints: &[bitcoin::OutPoint]) -> Vec; } impl BitcoinInterface for d::BitcoinD { @@ -356,6 +359,13 @@ impl BitcoinInterface for d::BitcoinD { ) -> Option<(bitcoin::Transaction, Option)> { self.get_transaction(txid).map(|res| (res.tx, res.block)) } + + fn mempool_spenders(&self, outpoints: &[bitcoin::OutPoint]) -> Vec { + self.mempool_txs_spending_prevouts(outpoints) + .into_iter() + .filter_map(|txid| self.mempool_entry(&txid)) + .collect() + } } // FIXME: do we need to repeat the entire trait implemenation? Isn't there a nicer way? @@ -442,6 +452,10 @@ impl BitcoinInterface for sync::Arc> ) -> Option<(bitcoin::Transaction, Option)> { self.lock().unwrap().wallet_transaction(txid) } + + fn mempool_spenders(&self, outpoints: &[bitcoin::OutPoint]) -> Vec { + self.lock().unwrap().mempool_spenders(outpoints) + } } // FIXME: We could avoid this type (and all the conversions entailing allocations) if bitcoind diff --git a/src/testutils.rs b/src/testutils.rs index 4f0505b3..3bd663b0 100644 --- a/src/testutils.rs +++ b/src/testutils.rs @@ -1,5 +1,5 @@ use crate::{ - bitcoin::{BitcoinInterface, Block, BlockChainTip, SyncProgress, UTxO}, + bitcoin::{BitcoinInterface, Block, BlockChainTip, MempoolEntry, SyncProgress, UTxO}, config::{BitcoinConfig, Config}, database::{BlockInfo, Coin, CoinStatus, DatabaseConnection, DatabaseInterface, LabelItem}, descriptors, DaemonHandle, @@ -119,6 +119,10 @@ impl BitcoinInterface for DummyBitcoind { ) -> Option<(bitcoin::Transaction, Option)> { self.txs.get(txid).cloned() } + + fn mempool_spenders(&self, _: &[bitcoin::OutPoint]) -> Vec { + Vec::new() + } } struct DummyDbState {