mempool: fixes

This commit is contained in:
nym21
2026-05-13 18:36:02 +02:00
parent 5cc3fbfa6e
commit 528c134f26
12 changed files with 669 additions and 30 deletions
+48 -10
View File
@@ -35,9 +35,9 @@ use brk_error::Result;
use brk_oracle::Histogram;
use brk_rpc::Client;
use brk_types::{
AddrBytes, AddrMempoolStats, BlockTemplate, BlockTemplateDiff, FeeRate, MempoolBlock,
MempoolInfo, MempoolRecentTx, NextBlockHash, OutpointPrefix, Timestamp, Transaction, TxOut,
Txid, TxidPrefix, Vin, Vout,
AddrBytes, AddrMempoolStats, BlockTemplate, BlockTemplateDiff, BlockTemplateDiffEntry, FeeRate,
MempoolBlock, MempoolInfo, MempoolRecentTx, NextBlockHash, OutpointPrefix, Timestamp,
Transaction, TxOut, Txid, TxidPrefix, Vin, Vout,
};
use parking_lot::{RwLock, RwLockReadGuard};
use rustc_hash::{FxHashMap, FxHashSet};
@@ -138,18 +138,41 @@ impl Mempool {
/// Delta of the projected next block since `since`. `None` when
/// `since` has aged out of the rebuilder's history (server should
/// 404 → client falls back to `block_template`). `removed` is just
/// txids; `added` carries full bodies so clients can patch their
/// local view in one round trip.
/// 404 → client falls back to `block_template`).
///
/// `order` walks the new template in template order; each entry is
/// either a `Retained` index into the prior template (which the
/// client cached when it obtained `since`) or a `New` inline body.
/// `removed` is the convenience list of txids that left.
pub fn block_template_diff(&self, since: NextBlockHash) -> Option<BlockTemplateDiff> {
let past = self.0.rebuilder.historical_block0(since)?;
let prior_index: FxHashMap<Txid, u32> = past
.iter()
.enumerate()
.map(|(idx, txid)| (*txid, idx as u32))
.collect();
let snap = self.snapshot();
let current: FxHashSet<Txid> = snap.block0_txids().collect();
let state = self.read();
let mut order = Vec::with_capacity(snap.blocks.first().map_or(0, Vec::len));
let mut current: FxHashSet<Txid> = FxHashSet::default();
for txid in snap.block0_txids() {
current.insert(txid);
match prior_index.get(&txid) {
Some(&idx) => order.push(BlockTemplateDiffEntry::Retained(idx)),
None => {
let tx = Self::lookup_body(&state, &txid)
.expect("snapshot tx body must be in txs or graveyard");
order.push(BlockTemplateDiffEntry::New(tx));
}
}
}
drop(state);
let removed = past.into_iter().filter(|t| !current.contains(t)).collect();
Some(BlockTemplateDiff {
hash: snap.next_block_hash,
since,
added: self.collect_txs(current.difference(&past).copied()),
removed: past.difference(&current).copied().collect(),
order,
removed,
})
}
@@ -157,10 +180,25 @@ impl Mempool {
let state = self.read();
txids
.into_iter()
.filter_map(|txid| state.txs.get(&txid).cloned())
.map(|txid| {
Self::lookup_body(&state, &txid)
.expect("snapshot tx body must be in txs or graveyard")
})
.collect()
}
/// Body for a txid in a published snapshot. Graveyard fallback
/// covers the eviction race: an Applier may have buried the tx
/// after the snapshot was built. Burial retention (1h) >> snapshot
/// cycle (~1s), so reachability is guaranteed.
fn lookup_body(state: &State, txid: &Txid) -> Option<Transaction> {
state
.txs
.get(txid)
.or_else(|| state.graveyard.get(txid).map(|t| &t.tx))
.cloned()
}
pub fn addr_state_hash(&self, addr: &AddrBytes) -> u64 {
self.read().addrs.stats_hash(addr)
}
+11 -9
View File
@@ -27,8 +27,10 @@ const HISTORY: usize = 10;
#[derive(Default)]
pub struct Rebuilder {
snapshot: RwLock<Arc<Snapshot>>,
/// Past block-0 txid sets keyed by `next_block_hash`, oldest first.
history: RwLock<VecDeque<(NextBlockHash, FxHashSet<Txid>)>>,
/// Past block-0 txid lists keyed by `next_block_hash`, oldest first.
/// Ordered so `block_template_diff` can emit `Retained(prior_index)`
/// entries that line up with the client's cached prior template.
history: RwLock<VecDeque<(NextBlockHash, Vec<Txid>)>>,
rebuild_count: AtomicU64,
}
@@ -40,13 +42,13 @@ impl Rebuilder {
/// is the driver loop's job.
pub fn tick(&self, lock: &RwLock<State>, gbt_txids: &[Txid], min_fee: FeeRate) {
let snap = Self::build_snapshot(lock, gbt_txids, min_fee);
let block0_set: FxHashSet<Txid> = snap.block0_txids().collect();
let block0: Vec<Txid> = snap.block0_txids().collect();
let next_hash = snap.next_block_hash;
*self.snapshot.write() = Arc::new(snap);
let mut hist = self.history.write();
hist.retain(|(h, _)| *h != next_hash);
hist.push_back((next_hash, block0_set));
hist.push_back((next_hash, block0));
while hist.len() > HISTORY {
hist.pop_front();
}
@@ -55,15 +57,15 @@ impl Rebuilder {
self.rebuild_count.fetch_add(1, Ordering::Relaxed);
}
/// Past block-0 txid set for `hash`, or `None` if it has aged out
/// (or was never seen). Used by `block_template_diff` to decide
/// 200 vs 404.
pub fn historical_block0(&self, hash: NextBlockHash) -> Option<FxHashSet<Txid>> {
/// Past block-0 ordered txid list for `hash`, or `None` if it has
/// aged out (or was never seen). Used by `block_template_diff` to
/// decide 200 vs 404 and to resolve `Retained(prior_index)` entries.
pub fn historical_block0(&self, hash: NextBlockHash) -> Option<Vec<Txid>> {
self.history
.read()
.iter()
.find(|(h, _)| *h == hash)
.map(|(_, set)| set.clone())
.map(|(_, block0)| block0.clone())
}
pub fn rebuild_count(&self) -> u64 {