mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-19 14:24:47 -07:00
mempool: fixes
This commit is contained in:
@@ -9840,7 +9840,7 @@ impl BrkClient {
|
||||
|
||||
/// Block template diff since hash
|
||||
///
|
||||
/// Delta of the projected next block since `<hash>`. `added` carries full transaction bodies; `removed` is just txids. Returns `404` when `<hash>` has aged out of server history; clients should fall back to `/api/v1/mempool/block-template`.
|
||||
/// Delta of the projected next block since `<hash>`. `order` is the full new template in order: each entry is either a number (index into the prior template the client cached at `<hash>`) or a transaction object (new body to insert at this position). Walk `order` once to rebuild; `removed` is a convenience list of txids that left so clients can evict cached bodies. After applying, use the response `hash` as `<hash>` on the next call to keep iterating. Returns `404` when `<hash>` has aged out of server history; clients should fall back to `/api/v1/mempool/block-template`.
|
||||
///
|
||||
/// Endpoint: `GET /api/v1/mempool/block-template/diff/{hash}`
|
||||
pub fn get_block_template_diff(&self, hash: NextBlockHash) -> Result<BlockTemplateDiff> {
|
||||
|
||||
@@ -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(¤t).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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -177,7 +177,7 @@ impl MempoolRoutes for ApiRouter<AppState> {
|
||||
op.id("get_block_template_diff")
|
||||
.mempool_tag()
|
||||
.summary("Block template diff since hash")
|
||||
.description("Delta of the projected next block since `<hash>`. `added` carries full transaction bodies; `removed` is just txids. Returns `404` when `<hash>` has aged out of server history; clients should fall back to `/api/v1/mempool/block-template`.")
|
||||
.description("Delta of the projected next block since `<hash>`. `order` is the full new template in order: each entry is either a number (index into the prior template the client cached at `<hash>`) or a transaction object (new body to insert at this position). Walk `order` once to rebuild; `removed` is a convenience list of txids that left so clients can evict cached bodies. After applying, use the response `hash` as `<hash>` on the next call to keep iterating. Returns `404` when `<hash>` has aged out of server history; clients should fall back to `/api/v1/mempool/block-template`.")
|
||||
.json_response::<BlockTemplateDiff>()
|
||||
.not_modified()
|
||||
.not_found()
|
||||
|
||||
@@ -3,7 +3,7 @@ use serde::Deserialize;
|
||||
|
||||
use brk_types::NextBlockHash;
|
||||
|
||||
/// `since` hash for `/api/v1/mining/block-template/diff/{hash}`.
|
||||
/// `since` hash for `/api/v1/mempool/block-template/diff/{hash}`.
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
pub struct NextBlockHashParam {
|
||||
pub hash: NextBlockHash,
|
||||
|
||||
@@ -5,12 +5,12 @@ use crate::{MempoolBlock, NextBlockHash, Transaction};
|
||||
|
||||
/// Projected next-block contents from Bitcoin Core's `getblocktemplate`
|
||||
/// (block 0 of the snapshot). Returned by
|
||||
/// `GET /api/v1/mining/block-template`.
|
||||
/// `GET /api/v1/mempool/block-template`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BlockTemplate {
|
||||
/// Pass back as `<hash>` on
|
||||
/// `/api/v1/mining/block-template/diff/{hash}` to fetch deltas.
|
||||
/// `/api/v1/mempool/block-template/diff/{hash}` to fetch deltas.
|
||||
pub hash: NextBlockHash,
|
||||
|
||||
/// Aggregate stats for this block (size, vsize, fee range, ...).
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{NextBlockHash, Transaction, Txid};
|
||||
use crate::{BlockTemplateDiffEntry, NextBlockHash, Txid};
|
||||
|
||||
/// Delta between the current `getblocktemplate` projection and a prior
|
||||
/// one identified by `since`. Returned by
|
||||
/// `GET /api/v1/mining/block-template/diff/{hash}`.
|
||||
/// `GET /api/v1/mempool/block-template/diff/{hash}`.
|
||||
///
|
||||
/// `order` carries the full new template in template order: each entry
|
||||
/// is either a `Retained(idx)` pointing into the prior template (which
|
||||
/// the client cached at `since`) or a `New(tx)` inline body. Walk it
|
||||
/// once to rebuild the new template; no separate `added` array to
|
||||
/// cross-reference.
|
||||
///
|
||||
/// `removed` is redundant (computable from `order` by collecting prior
|
||||
/// indices that don't appear) but shipped for cache-eviction ergonomics.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BlockTemplateDiff {
|
||||
@@ -15,9 +24,9 @@ pub struct BlockTemplateDiff {
|
||||
/// Echoed prior hash the diff was computed against.
|
||||
pub since: NextBlockHash,
|
||||
|
||||
/// Full bodies of transactions that joined the projected next
|
||||
/// block since `since`.
|
||||
pub added: Vec<Transaction>,
|
||||
/// New template in order. Each entry is either an index into the
|
||||
/// prior template's transactions or a full transaction body.
|
||||
pub order: Vec<BlockTemplateDiffEntry>,
|
||||
|
||||
/// Txids that left the projected next block since `since`
|
||||
/// (confirmed, evicted, replaced, or pushed past block 0).
|
||||
|
||||
22
crates/brk_types/src/block_template_diff_entry.rs
Normal file
22
crates/brk_types/src/block_template_diff_entry.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::Transaction;
|
||||
|
||||
/// One slot of the new template in a `BlockTemplateDiff`.
|
||||
///
|
||||
/// Untagged on the wire so JSON type disambiguates the variants:
|
||||
/// - `Retained(idx)` serializes as a bare integer - index into the
|
||||
/// transactions of the prior template (which the client cached at
|
||||
/// `since`).
|
||||
/// - `New(tx)` serializes as a transaction object - a body that was
|
||||
/// not in the prior template and must be added at this position.
|
||||
///
|
||||
/// Reconstruction is a single pass: for each entry, either copy
|
||||
/// `prior[idx]` or append the inline body.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum BlockTemplateDiffEntry {
|
||||
Retained(u32),
|
||||
New(Transaction),
|
||||
}
|
||||
@@ -82,6 +82,7 @@ mod index_info;
|
||||
mod limit;
|
||||
mod block_template;
|
||||
mod block_template_diff;
|
||||
mod block_template_diff_entry;
|
||||
mod mempool_block;
|
||||
mod mempool_entry_info;
|
||||
mod mempool_info;
|
||||
@@ -230,6 +231,7 @@ pub use block_sizes_weights::*;
|
||||
pub use block_status::*;
|
||||
pub use block_template::*;
|
||||
pub use block_template_diff::*;
|
||||
pub use block_template_diff_entry::*;
|
||||
pub use block_timestamp::*;
|
||||
pub use block_tx_index::*;
|
||||
pub use block_weight_entry::*;
|
||||
|
||||
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Content hash of the projected next block (block 0 of the mempool
|
||||
/// snapshot). Same value as the mempool ETag. Opaque token: pass back
|
||||
/// as `since` on `/api/v1/mining/block-template/diff/{hash}` to fetch
|
||||
/// as `since` on `/api/v1/mempool/block-template/diff/{hash}` to fetch
|
||||
/// deltas.
|
||||
#[derive(
|
||||
Debug,
|
||||
|
||||
Reference in New Issue
Block a user