mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-19 14:24:47 -07:00
119 lines
4.8 KiB
Rust
119 lines
4.8 KiB
Rust
mod fetched;
|
|
|
|
pub use fetched::Fetched;
|
|
|
|
use brk_error::Result;
|
|
use brk_rpc::Client;
|
|
use brk_types::{MempoolEntryInfo, Timestamp, Txid, VSize};
|
|
use parking_lot::RwLock;
|
|
use rustc_hash::FxHashSet;
|
|
use tracing::warn;
|
|
|
|
use crate::State;
|
|
|
|
/// Cap before the batch RPC so we never hand bitcoind an unbounded batch.
|
|
/// GBT-synthesized entries are not subject to this cap: they're bounded
|
|
/// by the block weight limit Core enforces on its own template.
|
|
const MAX_TX_FETCHES_PER_CYCLE: usize = 10_000;
|
|
|
|
/// Two batched round-trips per cycle, scaling with churn rather than
|
|
/// mempool size: `getblocktemplate` + `getrawmempool false` +
|
|
/// `getmempoolinfo` in one mixed batch, then `getmempoolentry` +
|
|
/// `getrawtransaction` for *new* non-GBT txids in a second mixed batch.
|
|
///
|
|
/// GBT entries already carry the full tx body and stats, so any GBT tx
|
|
/// not yet in the local pool is materialized inline from the GBT
|
|
/// payload instead of being refetched. That removes the GBT/listing
|
|
/// race that used to skip cycles when a tx vanished from the mempool
|
|
/// between the GBT and `getrawmempool` calls: block 0 always reflects
|
|
/// Core's exact selection because we never ask for that data twice.
|
|
///
|
|
/// Confirmed prevouts are resolved post-apply by the caller-supplied
|
|
/// resolver passed to `Mempool::tick_with`, so the in-crate path no
|
|
/// longer issues a third batch for parents.
|
|
pub struct Fetcher;
|
|
|
|
impl Fetcher {
|
|
pub fn fetch(client: &Client, lock: &RwLock<State>) -> Result<Fetched> {
|
|
let (mut state, block_template) = client.fetch_mempool_state()?;
|
|
|
|
// One read snapshot decides both the RPC fetch list and the
|
|
// GBT-synthesis set, so they agree on what's "already known".
|
|
let (new_txids, gbt_synth_set) = {
|
|
let mempool = lock.read();
|
|
let mut gbt_txids: FxHashSet<Txid> =
|
|
FxHashSet::with_capacity_and_hasher(block_template.len(), Default::default());
|
|
let mut gbt_synth_set: FxHashSet<Txid> = FxHashSet::default();
|
|
for g in &block_template {
|
|
gbt_txids.insert(g.txid);
|
|
if !mempool.txs.contains(&g.txid) {
|
|
gbt_synth_set.insert(g.txid);
|
|
}
|
|
}
|
|
let new_txids: Vec<Txid> = state
|
|
.live_txids
|
|
.iter()
|
|
.filter(|t| !mempool.txs.contains(t) && !gbt_txids.contains(t))
|
|
.take(MAX_TX_FETCHES_PER_CYCLE)
|
|
.copied()
|
|
.collect();
|
|
if new_txids.len() == MAX_TX_FETCHES_PER_CYCLE {
|
|
warn!(
|
|
cap = MAX_TX_FETCHES_PER_CYCLE,
|
|
"Fetcher: new-tx batch hit the per-cycle cap; remainder defers to the next cycle"
|
|
);
|
|
}
|
|
(new_txids, gbt_synth_set)
|
|
};
|
|
|
|
let (mut new_entries, mut new_txs) = client.fetch_new_pool_data(&new_txids)?;
|
|
new_entries.reserve(gbt_synth_set.len());
|
|
new_txs.reserve(gbt_synth_set.len());
|
|
|
|
// Consume `block_template` by value: GBT-only txs move their
|
|
// body and depends into the synthesis path (no clones), and
|
|
// the GBT ordering is captured as a `Vec<Txid>` for the
|
|
// Rebuilder, which is the only downstream consumer and only
|
|
// reads txids.
|
|
//
|
|
// GBT carries no per-tx arrival timestamp. `now` is correct to
|
|
// within ~1 cycle for a tx that just entered Core's mempool
|
|
// (the only kind that triggers synthesis: not in our pool yet
|
|
// means it just appeared this cycle).
|
|
let now = Timestamp::now();
|
|
let block_template_txids: Vec<Txid> = block_template
|
|
.into_iter()
|
|
.map(|g| {
|
|
let txid = g.txid;
|
|
if gbt_synth_set.contains(&txid) {
|
|
new_entries.push(MempoolEntryInfo {
|
|
txid,
|
|
vsize: VSize::from(g.weight),
|
|
weight: g.weight,
|
|
fee: g.fee,
|
|
first_seen: now,
|
|
depends: g.depends,
|
|
});
|
|
new_txs.insert(txid, g.tx);
|
|
}
|
|
txid
|
|
})
|
|
.collect();
|
|
|
|
// Promote `live_txids` to the union of `getrawmempool` and GBT:
|
|
// the two RPC views can disagree by a cycle, so a tx visible to
|
|
// GBT but missing from `getrawmempool` (or vice versa) is still
|
|
// alive. Without the union, GBT-only txs would oscillate enter ↔
|
|
// leave every cycle as `Preparer::classify_removals` buried what
|
|
// GBT had just resurrected.
|
|
state.live_txids.extend(block_template_txids.iter().copied());
|
|
|
|
Ok(Fetched {
|
|
state,
|
|
new_entries,
|
|
new_txs,
|
|
block_template_txids,
|
|
})
|
|
}
|
|
}
|