mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-19 22:34:46 -07:00
global: next block template (+ diff)
This commit is contained in:
@@ -30,9 +30,11 @@ use std::{
|
||||
use brk_error::Result;
|
||||
use brk_rpc::Client;
|
||||
use brk_types::{
|
||||
AddrBytes, AddrMempoolStats, FeeRate, MempoolInfo, MempoolRecentTx, OutpointPrefix, OutputType,
|
||||
Sats, Timestamp, Transaction, TxOut, Txid, TxidPrefix, Vin, Vout,
|
||||
AddrBytes, AddrMempoolStats, BlockTemplate, BlockTemplateDiff, FeeRate, MempoolBlock,
|
||||
MempoolInfo, MempoolRecentTx, NextBlockHash, OutpointPrefix, OutputType, Sats, Timestamp,
|
||||
Transaction, TxOut, Txid, TxidPrefix, Vin, Vout,
|
||||
};
|
||||
use rustc_hash::FxHashSet;
|
||||
use parking_lot::{RwLock, RwLockReadGuard};
|
||||
use tracing::error;
|
||||
|
||||
@@ -102,10 +104,58 @@ impl Mempool {
|
||||
self.snapshot().block_stats.clone()
|
||||
}
|
||||
|
||||
pub fn next_block_hash(&self) -> u64 {
|
||||
pub fn next_block_hash(&self) -> NextBlockHash {
|
||||
self.snapshot().next_block_hash
|
||||
}
|
||||
|
||||
/// Full projected next block: Core's `getblocktemplate` selection
|
||||
/// (block 0) with aggregate stats and full tx bodies in GBT order.
|
||||
pub fn block_template(&self) -> BlockTemplate {
|
||||
let snap = self.snapshot();
|
||||
let stats = MempoolBlock::from(&snap.block_stats[0]);
|
||||
let txids: Vec<Txid> = snap.blocks[0]
|
||||
.iter()
|
||||
.map(|idx| snap.txs[idx.as_usize()].txid)
|
||||
.collect();
|
||||
let transactions = self.collect_txs(&txids);
|
||||
BlockTemplate {
|
||||
hash: snap.next_block_hash,
|
||||
stats,
|
||||
transactions,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn block_template_diff(&self, since: NextBlockHash) -> Option<BlockTemplateDiff> {
|
||||
let past = self.0.rebuilder.historical_block0(since)?;
|
||||
let snap = self.snapshot();
|
||||
let current: FxHashSet<Txid> = snap.blocks[0]
|
||||
.iter()
|
||||
.map(|idx| snap.txs[idx.as_usize()].txid)
|
||||
.collect();
|
||||
let added_txids: Vec<Txid> = current.difference(&past).copied().collect();
|
||||
let removed: Vec<Txid> = past.difference(¤t).copied().collect();
|
||||
let added = self.collect_txs(&added_txids);
|
||||
Some(BlockTemplateDiff {
|
||||
hash: snap.next_block_hash,
|
||||
since,
|
||||
added,
|
||||
removed,
|
||||
})
|
||||
}
|
||||
|
||||
fn collect_txs(&self, txids: &[Txid]) -> Vec<Transaction> {
|
||||
let state = self.read();
|
||||
txids
|
||||
.iter()
|
||||
.filter_map(|txid| state.txs.get(txid).cloned())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn addr_state_hash(&self, addr: &AddrBytes) -> u64 {
|
||||
self.read().addrs.stats_hash(addr)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
},
|
||||
};
|
||||
|
||||
use brk_rpc::BlockTemplateTx;
|
||||
use brk_types::{FeeRate, TxidPrefix};
|
||||
use brk_types::{FeeRate, NextBlockHash, Txid, TxidPrefix};
|
||||
use parking_lot::RwLock;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
@@ -20,10 +23,13 @@ pub use brk_types::RecommendedFees;
|
||||
pub use snapshot::{BlockStats, SnapTx, Snapshot, TxIndex};
|
||||
|
||||
const NUM_BLOCKS: usize = 8;
|
||||
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>)>>,
|
||||
dirty: AtomicBool,
|
||||
rebuild_count: AtomicU64,
|
||||
skip_clean: AtomicU64,
|
||||
@@ -49,11 +55,38 @@ impl Rebuilder {
|
||||
self.skip_clean.fetch_add(1, Ordering::Relaxed);
|
||||
return;
|
||||
}
|
||||
*self.snapshot.write() = Arc::new(Self::build_snapshot(lock, gbt, min_fee));
|
||||
let snap = Self::build_snapshot(lock, gbt, min_fee);
|
||||
let block0_set: FxHashSet<Txid> = snap.blocks[0]
|
||||
.iter()
|
||||
.map(|idx| snap.txs[idx.as_usize()].txid)
|
||||
.collect();
|
||||
let next_hash = snap.next_block_hash;
|
||||
*self.snapshot.write() = Arc::new(snap);
|
||||
self.push_history(next_hash, block0_set);
|
||||
self.dirty.store(false, Ordering::Release);
|
||||
self.rebuild_count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn push_history(&self, hash: NextBlockHash, set: FxHashSet<Txid>) {
|
||||
let mut hist = self.history.write();
|
||||
hist.retain(|(h, _)| *h != hash);
|
||||
hist.push_back((hash, set));
|
||||
while hist.len() > HISTORY {
|
||||
hist.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>> {
|
||||
self.history
|
||||
.read()
|
||||
.iter()
|
||||
.find(|(h, _)| *h == hash)
|
||||
.map(|(_, set)| set.clone())
|
||||
}
|
||||
|
||||
pub fn rebuild_count(&self) -> u64 {
|
||||
self.rebuild_count.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ pub use tx_index::TxIndex;
|
||||
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
|
||||
use brk_types::{FeeRate, RecommendedFees, TxidPrefix};
|
||||
use brk_types::{FeeRate, NextBlockHash, RecommendedFees, TxidPrefix};
|
||||
|
||||
use fees::Fees;
|
||||
|
||||
@@ -30,7 +30,7 @@ pub struct Snapshot {
|
||||
pub fees: RecommendedFees,
|
||||
/// Content hash of the projected next block. Same value as the
|
||||
/// mempool ETag.
|
||||
pub next_block_hash: u64,
|
||||
pub next_block_hash: NextBlockHash,
|
||||
/// Per-snapshot `TxidPrefix -> TxIndex` index, so live queries can
|
||||
/// resolve a prefix to the snapshot's compact index without
|
||||
/// re-walking `txs`. Built once by `builder::build_txs` and reused
|
||||
@@ -70,13 +70,13 @@ impl Snapshot {
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_next_block(blocks: &[Vec<TxIndex>]) -> u64 {
|
||||
fn hash_next_block(blocks: &[Vec<TxIndex>]) -> NextBlockHash {
|
||||
let Some(block) = blocks.first() else {
|
||||
return 0;
|
||||
return NextBlockHash::ZERO;
|
||||
};
|
||||
let mut hasher = DefaultHasher::new();
|
||||
block.hash(&mut hasher);
|
||||
hasher.finish()
|
||||
NextBlockHash::new(hasher.finish())
|
||||
}
|
||||
|
||||
pub fn tx(&self, idx: TxIndex) -> Option<&SnapTx> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use brk_types::{FeeRate, Sats, VSize, get_weighted_percentile};
|
||||
use brk_types::{FeeRate, MempoolBlock, Sats, VSize, get_weighted_percentile};
|
||||
|
||||
use super::{SnapTx, TxIndex};
|
||||
|
||||
@@ -83,3 +83,9 @@ impl BlockStats {
|
||||
self.fee_range[3]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&BlockStats> for MempoolBlock {
|
||||
fn from(s: &BlockStats) -> Self {
|
||||
Self::new(s.tx_count, s.total_size, s.total_vsize, s.total_fee, s.fee_range)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user