mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-19 22:34:46 -07:00
global: fixes
This commit is contained in:
@@ -6,9 +6,17 @@
|
||||
//! Confirmed-tx CPFP (the same-block connected component on the
|
||||
//! chain) lives in `brk_query`, since it reads indexer/computer vecs.
|
||||
|
||||
use brk_types::{CpfpEntry, CpfpInfo, FeeRate, Sats, TxidPrefix, VSize, Weight};
|
||||
use rustc_hash::FxHashSet;
|
||||
use brk_types::{
|
||||
CpfpCluster, CpfpClusterChunk, CpfpClusterTx, CpfpClusterTxIndex, CpfpEntry, CpfpInfo, FeeRate,
|
||||
TxidPrefix, VSize, Weight,
|
||||
};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::steps::rebuilder::linearize::{
|
||||
LocalIdx, cluster::Cluster, cluster_node::ClusterNode, sfl::Sfl,
|
||||
};
|
||||
use crate::stores::{EntryPool, TxIndex};
|
||||
use crate::{Mempool, TxEntry};
|
||||
|
||||
/// Cap matches Bitcoin Core's default mempool ancestor/descendant
|
||||
@@ -24,19 +32,16 @@ impl Mempool {
|
||||
pub fn cpfp_info(&self, prefix: &TxidPrefix) -> Option<CpfpInfo> {
|
||||
let snapshot = self.snapshot();
|
||||
let entries = self.entries();
|
||||
let txs = self.txs();
|
||||
let seed_idx = entries.idx_of(prefix)?;
|
||||
let seed = entries.slot(seed_idx)?;
|
||||
|
||||
let mut sum_fee = u64::from(seed.fee);
|
||||
let mut sum_vsize = u64::from(seed.vsize);
|
||||
let mut ancestor_idxs: Vec<TxIndex> = Vec::new();
|
||||
let mut descendant_idxs: Vec<TxIndex> = Vec::new();
|
||||
let mut ancestors: Vec<CpfpEntry> = Vec::new();
|
||||
let mut descendants: Vec<CpfpEntry> = Vec::new();
|
||||
|
||||
if let Some(seed_block) = snapshot.block_of(seed_idx) {
|
||||
// Ancestor BFS gated to the seed's projected block.
|
||||
// `visited` dedupes the walk; stale parent prefixes
|
||||
// (confirmed/evicted between snapshot and now) are skipped
|
||||
// when `idx_of` returns None.
|
||||
let mut visited: FxHashSet<TxidPrefix> = FxHashSet::default();
|
||||
visited.insert(*prefix);
|
||||
let mut stack: Vec<TxidPrefix> = seed.depends.iter().copied().collect();
|
||||
@@ -52,17 +57,11 @@ impl Mempool {
|
||||
continue;
|
||||
}
|
||||
let Some(anc) = entries.slot(idx) else { continue };
|
||||
sum_fee += u64::from(anc.fee);
|
||||
sum_vsize += u64::from(anc.vsize);
|
||||
ancestor_idxs.push(idx);
|
||||
ancestors.push(to_entry(anc));
|
||||
stack.extend(anc.depends.iter().copied());
|
||||
}
|
||||
|
||||
// Descendant sweep. `desc_set` starts with only the seed
|
||||
// so siblings (txs sharing an ancestor with seed but not
|
||||
// downstream of it) are excluded. The topological ordering
|
||||
// of `Snapshot.blocks` guarantees that all in-block
|
||||
// ancestors of any tx are visited before it.
|
||||
let mut desc_set: FxHashSet<TxidPrefix> = FxHashSet::default();
|
||||
desc_set.insert(*prefix);
|
||||
for &i in &snapshot.blocks[seed_block.as_usize()] {
|
||||
@@ -74,8 +73,7 @@ impl Mempool {
|
||||
continue;
|
||||
}
|
||||
desc_set.insert(e.txid_prefix());
|
||||
sum_fee += u64::from(e.fee);
|
||||
sum_vsize += u64::from(e.vsize);
|
||||
descendant_idxs.push(i);
|
||||
descendants.push(to_entry(e));
|
||||
}
|
||||
}
|
||||
@@ -85,16 +83,39 @@ impl Mempool {
|
||||
.max_by_key(|e| FeeRate::from((e.fee, e.weight)))
|
||||
.cloned();
|
||||
|
||||
let package_rate = FeeRate::from((Sats::from(sum_fee), VSize::from(sum_vsize)));
|
||||
let effective = seed.fee_rate().max(package_rate);
|
||||
let sigops = txs.get(&seed.txid).map(|tx| {
|
||||
// Bitcoin Core's `total_sigop_cost` is the segwit-weighted sigop
|
||||
// count (legacy * 4 + segwit * 1), divided by 5 to match
|
||||
// mempool.space's reported `sigops`. Mempool.space converts
|
||||
// back to count via `sigopcost / 5`.
|
||||
u32::try_from(tx.total_sigop_cost / 5).unwrap_or(u32::MAX)
|
||||
});
|
||||
|
||||
// mempool.space's adjustedVsize = max(vsize, sigops * 5).
|
||||
let adjusted_vsize = match sigops {
|
||||
Some(s) => VSize::from(u64::from(seed.vsize).max(u64::from(s) * 5)),
|
||||
None => seed.vsize,
|
||||
};
|
||||
|
||||
let cluster = build_cluster(seed_idx, &ancestor_idxs, &descendant_idxs, &entries);
|
||||
|
||||
// mempool.space sets effectiveFeePerVsize to the seed's chunk feerate
|
||||
// when the cluster is known, falls back to the seed's own rate.
|
||||
let effective = cluster
|
||||
.as_ref()
|
||||
.and_then(|c| c.chunks.get(c.chunk_index as usize))
|
||||
.map(|chunk| chunk.feerate)
|
||||
.unwrap_or_else(|| seed.fee_rate());
|
||||
|
||||
Some(CpfpInfo {
|
||||
ancestors,
|
||||
best_descendant,
|
||||
descendants,
|
||||
effective_fee_per_vsize: Some(effective),
|
||||
sigops,
|
||||
fee: Some(seed.fee),
|
||||
adjusted_vsize: Some(seed.vsize),
|
||||
adjusted_vsize: Some(adjusted_vsize),
|
||||
cluster,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -106,3 +127,120 @@ fn to_entry(e: &TxEntry) -> CpfpEntry {
|
||||
fee: e.fee,
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the cluster output: seed + ancestors + descendants in topological
|
||||
/// order, with parent indexes inside the cluster, plus SFL-linearized chunks.
|
||||
fn build_cluster(
|
||||
seed_idx: TxIndex,
|
||||
ancestor_idxs: &[TxIndex],
|
||||
descendant_idxs: &[TxIndex],
|
||||
entries: &EntryPool,
|
||||
) -> Option<CpfpCluster> {
|
||||
let mut ordered: Vec<TxIndex> = Vec::with_capacity(ancestor_idxs.len() + 1 + descendant_idxs.len());
|
||||
ordered.extend(ancestor_idxs.iter().copied());
|
||||
ordered.push(seed_idx);
|
||||
ordered.extend(descendant_idxs.iter().copied());
|
||||
|
||||
let pool: Vec<&TxEntry> = ordered.iter().filter_map(|&i| entries.slot(i)).collect();
|
||||
if pool.len() != ordered.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let prefix_to_local: FxHashMap<TxidPrefix, LocalIdx> = pool
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, e)| (e.txid_prefix(), i as LocalIdx))
|
||||
.collect();
|
||||
|
||||
let mut children_of: Vec<SmallVec<[LocalIdx; 2]>> = vec![SmallVec::new(); pool.len()];
|
||||
let parents_of: Vec<SmallVec<[LocalIdx; 2]>> = pool
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, e)| {
|
||||
let parents: SmallVec<[LocalIdx; 2]> = e
|
||||
.depends
|
||||
.iter()
|
||||
.filter_map(|p| prefix_to_local.get(p).copied())
|
||||
.collect();
|
||||
for &p in &parents {
|
||||
children_of[p as usize].push(i as LocalIdx);
|
||||
}
|
||||
parents
|
||||
})
|
||||
.collect();
|
||||
|
||||
let cluster_nodes: Vec<ClusterNode> = pool
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, e)| ClusterNode {
|
||||
tx_index: ordered[i],
|
||||
fee: e.fee,
|
||||
vsize: e.vsize,
|
||||
parents: parents_of[i].clone(),
|
||||
children: children_of[i].clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let cluster = Cluster::new(cluster_nodes);
|
||||
|
||||
// Re-order pool so parents come before children (mempool.space convention).
|
||||
// `topo_rank[i]` gives the position of local index `i` in topological order.
|
||||
let mut local_to_topo: Vec<usize> = (0..pool.len()).collect();
|
||||
local_to_topo.sort_unstable_by_key(|&i| cluster.topo_rank[i]);
|
||||
let topo_to_local: Vec<usize> = {
|
||||
let mut v = vec![0usize; pool.len()];
|
||||
for (topo_pos, &local) in local_to_topo.iter().enumerate() {
|
||||
v[local] = topo_pos;
|
||||
}
|
||||
v
|
||||
};
|
||||
|
||||
let topo_idx = |local: usize| CpfpClusterTxIndex::from(topo_to_local[local] as u32);
|
||||
|
||||
let txs: Vec<CpfpClusterTx> = local_to_topo
|
||||
.iter()
|
||||
.map(|&local| {
|
||||
let e = pool[local];
|
||||
let parents: Vec<CpfpClusterTxIndex> = parents_of[local]
|
||||
.iter()
|
||||
.map(|&p| topo_idx(p as usize))
|
||||
.collect();
|
||||
CpfpClusterTx {
|
||||
txid: e.txid.clone(),
|
||||
fee: e.fee,
|
||||
weight: Weight::from(e.vsize),
|
||||
parents,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let raw_chunks = Sfl::linearize(&cluster);
|
||||
let chunks: Vec<CpfpClusterChunk> = raw_chunks
|
||||
.iter()
|
||||
.map(|chunk| {
|
||||
let mut chunk_txs: Vec<CpfpClusterTxIndex> = chunk
|
||||
.nodes
|
||||
.iter()
|
||||
.map(|&local| topo_idx(local as usize))
|
||||
.collect();
|
||||
chunk_txs.sort_unstable();
|
||||
CpfpClusterChunk {
|
||||
txs: chunk_txs,
|
||||
feerate: chunk.fee_rate(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let seed_local = *prefix_to_local.get(&entries.slot(seed_idx)?.txid_prefix())?;
|
||||
let seed_topo = topo_idx(seed_local as usize);
|
||||
let chunk_index = chunks
|
||||
.iter()
|
||||
.position(|c| c.txs.contains(&seed_topo))
|
||||
.unwrap_or(0) as u32;
|
||||
|
||||
Some(CpfpCluster {
|
||||
txs,
|
||||
chunks,
|
||||
chunk_index,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
//! prevouts against `known` or `parent_raws`, build a full
|
||||
//! `Transaction` + `Entry`.
|
||||
//! - **Revived** - tx in the graveyard. Rebuild the `Entry` only
|
||||
//! (preserving `first_seen`, `rbf`, `size`). The Applier exhumes
|
||||
//! the cached tx body. No raw decoding.
|
||||
//! (preserving `rbf`, `size`). The Applier exhumes the cached tx
|
||||
//! body. No raw decoding.
|
||||
|
||||
use std::mem;
|
||||
|
||||
use brk_rpc::RawTx;
|
||||
use brk_types::{MempoolEntryInfo, Timestamp, Transaction, TxIn, TxOut, TxStatus, Txid, Vout};
|
||||
use brk_types::{MempoolEntryInfo, Transaction, TxIn, TxOut, TxStatus, Txid, Vout};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::{TxTombstone, stores::TxStore};
|
||||
@@ -35,7 +35,7 @@ impl TxAddition {
|
||||
let total_size = raw.hex.len() / 2;
|
||||
let rbf = raw.tx.input.iter().any(|i| i.sequence.is_rbf());
|
||||
let tx = Self::build_tx(info, raw, total_size, mempool_txs, parent_raws);
|
||||
let entry = TxEntry::new(info, total_size as u64, rbf, Timestamp::now());
|
||||
let entry = TxEntry::new(info, total_size as u64, rbf);
|
||||
Self::Fresh { tx, entry }
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ impl TxAddition {
|
||||
}
|
||||
|
||||
pub(super) fn revived(info: &MempoolEntryInfo, tomb: &TxTombstone) -> Self {
|
||||
let entry = TxEntry::new(info, tomb.entry.size, tomb.entry.rbf, tomb.entry.first_seen);
|
||||
let entry = TxEntry::new(info, tomb.entry.size, tomb.entry.rbf);
|
||||
Self::Revived { entry }
|
||||
}
|
||||
|
||||
|
||||
@@ -26,14 +26,14 @@ pub struct TxEntry {
|
||||
}
|
||||
|
||||
impl TxEntry {
|
||||
pub(super) fn new(info: &MempoolEntryInfo, size: u64, rbf: bool, first_seen: Timestamp) -> Self {
|
||||
pub(super) fn new(info: &MempoolEntryInfo, size: u64, rbf: bool) -> Self {
|
||||
Self {
|
||||
txid: info.txid.clone(),
|
||||
fee: info.fee,
|
||||
vsize: VSize::from(info.vsize),
|
||||
size,
|
||||
depends: info.depends.iter().map(TxidPrefix::from).collect(),
|
||||
first_seen,
|
||||
first_seen: info.first_seen,
|
||||
rbf,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user