Skip to content

Commit 5c8c785

Browse files
committed
Don't store mempool txs before all prevouts are available
The indexing process was adding transactions into the store so that prevouts funded & spent within the same batch could be looked up via Mempool::lookup_txos(). If the indexing process later failed for any reason, these transactions would remain in the store. With this change, we instead explicitly look for prevouts funded within the same batch, then look for the rest in the chain/mempool indexes and fail if any are missing, without keeping the transactions in the store.
1 parent 5b07357 commit 5c8c785

4 files changed

Lines changed: 57 additions & 44 deletions

File tree

src/new_index/mempool.rs

Lines changed: 41 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::new_index::{
2121
SpendingInfo, SpendingInput, TxHistoryInfo, Utxo,
2222
};
2323
use crate::util::fees::{make_fee_histogram, TxFeeInfo};
24-
use crate::util::{extract_tx_prevouts, full_hash, has_prevout, is_spendable, Bytes};
24+
use crate::util::{extract_tx_prevouts, full_hash, get_prev_outpoints, is_spendable, Bytes};
2525

2626
#[cfg(feature = "liquid")]
2727
use crate::elements::asset;
@@ -288,40 +288,57 @@ impl Mempool {
288288
self.backlog_stats = (BacklogStats::new(&self.feeinfo), Instant::now());
289289
}
290290

291-
pub fn add_by_txid(&mut self, daemon: &Daemon, txid: &Txid) {
291+
pub fn add_by_txid(&mut self, daemon: &Daemon, txid: &Txid) -> Result<()> {
292292
if self.txstore.get(txid).is_none() {
293293
if let Ok(tx) = daemon.getmempooltx(&txid) {
294294
self.add(vec![tx])
295+
} else {
296+
bail!("add_by_txid cannot find {}", txid);
295297
}
298+
} else {
299+
Ok(())
296300
}
297301
}
298302

299-
fn add(&mut self, txs: Vec<Transaction>) {
303+
fn add(&mut self, txs: Vec<Transaction>) -> Result<()> {
300304
self.delta
301305
.with_label_values(&["add"])
302306
.observe(txs.len() as f64);
303307
let _timer = self.latency.with_label_values(&["add"]).start_timer();
304308

305-
let mut txids = vec![];
306-
// Phase 1: add to txstore
307-
for tx in txs {
308-
let txid = tx.txid();
309-
txids.push(txid);
309+
let spent_prevouts = get_prev_outpoints(&txs);
310+
let txs_map = txs
311+
.into_iter()
312+
.map(|tx| (tx.txid(), tx))
313+
.collect::<HashMap<_, _>>();
314+
315+
// Lookup spent prevouts that were funded within the same `add` batch
316+
let mut txos = HashMap::new();
317+
let remain_prevouts = spent_prevouts
318+
.into_iter()
319+
.filter(|prevout| {
320+
if let Some(prevtx) = txs_map.get(&prevout.txid) {
321+
if let Some(out) = prevtx.output.get(prevout.vout as usize) {
322+
txos.insert(prevout.clone(), out.clone());
323+
// remove from the list of remaining `prevouts`
324+
return false;
325+
}
326+
}
327+
true
328+
})
329+
.collect();
330+
331+
// Lookup remaining spent prevouts in mempool & on-chain
332+
// Fails if any are missing.
333+
txos.extend(self.lookup_txos(remain_prevouts)?);
334+
335+
// Add to txstore and indexes
336+
for (txid, tx) in txs_map {
310337
self.txstore.insert(txid, tx);
311-
}
312-
// Phase 2: index history and spend edges (can fail if some txos cannot be found)
313-
let txos = match self.lookup_txos(self.get_prevouts(&txids)) {
314-
Ok(txos) => txos,
315-
Err(err) => {
316-
warn!("lookup txouts failed: {}", err);
317-
// TODO: should we remove txids from txstore?
318-
return;
319-
}
320-
};
321-
for txid in txids {
322-
let tx = self.txstore.get(&txid).expect("missing mempool tx");
323-
let txid_bytes = full_hash(&txid[..]);
338+
let tx = self.txstore.get(&txid).expect("was just added");
339+
324340
let prevouts = extract_tx_prevouts(&tx, &txos, false);
341+
let txid_bytes = full_hash(&txid[..]);
325342

326343
// Get feeinfo for caching and recent tx overview
327344
let feeinfo = TxFeeInfo::new(&tx, &prevouts, self.config.network_type);
@@ -395,6 +412,8 @@ impl Mempool {
395412
&mut self.asset_issuance,
396413
);
397414
}
415+
416+
Ok(())
398417
}
399418

400419
fn lookup_txo(&self, outpoint: &OutPoint) -> Option<TxOut> {
@@ -423,24 +442,6 @@ impl Mempool {
423442
Ok(txos)
424443
}
425444

426-
fn get_prevouts(&self, txids: &[Txid]) -> BTreeSet<OutPoint> {
427-
let _timer = self
428-
.latency
429-
.with_label_values(&["get_prevouts"])
430-
.start_timer();
431-
432-
txids
433-
.iter()
434-
.map(|txid| self.txstore.get(txid).expect("missing mempool tx"))
435-
.flat_map(|tx| {
436-
tx.input
437-
.iter()
438-
.filter(|txin| has_prevout(txin))
439-
.map(|txin| txin.previous_output)
440-
})
441-
.collect()
442-
}
443-
444445
fn remove(&mut self, to_remove: HashSet<&Txid>) {
445446
self.delta
446447
.with_label_values(&["remove"])
@@ -510,7 +511,7 @@ impl Mempool {
510511
{
511512
let mut mempool = mempool.write().unwrap();
512513
// Add new transactions
513-
mempool.add(txs_to_add);
514+
mempool.add(txs_to_add)?;
514515

515516
mempool
516517
.count

src/new_index/query.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ impl Query {
7171

7272
pub fn broadcast_raw(&self, txhex: &str) -> Result<Txid> {
7373
let txid = self.daemon.broadcast_raw(txhex)?;
74-
self.mempool
74+
let _ = self
75+
.mempool
7576
.write()
7677
.unwrap()
7778
.add_by_txid(&self.daemon, &txid);

src/util/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ pub use self::block::{
1212
pub use self::fees::get_tx_fee;
1313
pub use self::script::{get_innerscripts, ScriptToAddr, ScriptToAsm};
1414
pub use self::transaction::{
15-
extract_tx_prevouts, has_prevout, is_coinbase, is_spendable, serialize_outpoint,
16-
TransactionStatus, TxInput,
15+
extract_tx_prevouts, get_prev_outpoints, has_prevout, is_coinbase, is_spendable,
16+
serialize_outpoint, TransactionStatus, TxInput,
1717
};
1818

1919
use std::collections::HashMap;

src/util/transaction.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::chain::{BlockHash, OutPoint, Transaction, TxIn, TxOut, Txid};
22
use crate::util::BlockId;
33

4-
use std::collections::HashMap;
4+
use std::collections::{BTreeSet, HashMap};
55

66
#[cfg(feature = "liquid")]
77
lazy_static! {
@@ -96,6 +96,17 @@ pub fn extract_tx_prevouts<'a>(
9696
.collect()
9797
}
9898

99+
pub fn get_prev_outpoints(txs: &[Transaction]) -> BTreeSet<OutPoint> {
100+
txs.iter()
101+
.flat_map(|tx| {
102+
tx.input
103+
.iter()
104+
.filter(|txin| has_prevout(txin))
105+
.map(|txin| txin.previous_output)
106+
})
107+
.collect()
108+
}
109+
99110
pub fn serialize_outpoint<S>(outpoint: &OutPoint, serializer: S) -> Result<S::Ok, S::Error>
100111
where
101112
S: serde::ser::Serializer,

0 commit comments

Comments
 (0)