mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-19 14:24:47 -07:00
global: next block template (+ diff)
This commit is contained in:
@@ -8953,7 +8953,7 @@ pub struct BrkClient {
|
|||||||
|
|
||||||
impl BrkClient {
|
impl BrkClient {
|
||||||
/// Client version.
|
/// Client version.
|
||||||
pub const VERSION: &'static str = "v0.3.0-beta.8";
|
pub const VERSION: &'static str = "v0.3.0-beta.9";
|
||||||
|
|
||||||
/// Create a new client with the given base URL.
|
/// Create a new client with the given base URL.
|
||||||
pub fn new(base_url: impl Into<String>) -> Self {
|
pub fn new(base_url: impl Into<String>) -> Self {
|
||||||
@@ -9734,7 +9734,7 @@ impl BrkClient {
|
|||||||
|
|
||||||
/// Projected mempool blocks
|
/// Projected mempool blocks
|
||||||
///
|
///
|
||||||
/// Get projected blocks from the mempool for fee estimation.
|
/// Projected blocks for fee estimation. Block 0 reflects Bitcoin Core's actual next-block selection; blocks 1+ are a fee-tier approximation.
|
||||||
///
|
///
|
||||||
/// *[Mempool.space docs](https://mempool.space/docs/api/rest#get-mempool-blocks-fees)*
|
/// *[Mempool.space docs](https://mempool.space/docs/api/rest#get-mempool-blocks-fees)*
|
||||||
///
|
///
|
||||||
@@ -9745,7 +9745,7 @@ impl BrkClient {
|
|||||||
|
|
||||||
/// Recommended fees
|
/// Recommended fees
|
||||||
///
|
///
|
||||||
/// Get recommended fee rates for different confirmation targets.
|
/// Recommended fee rates by confirmation target.
|
||||||
///
|
///
|
||||||
/// *[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees)*
|
/// *[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees)*
|
||||||
///
|
///
|
||||||
@@ -9756,7 +9756,7 @@ impl BrkClient {
|
|||||||
|
|
||||||
/// Precise recommended fees
|
/// Precise recommended fees
|
||||||
///
|
///
|
||||||
/// Get recommended fee rates with up to 3 decimal places.
|
/// Recommended fee rates with sub-integer precision.
|
||||||
///
|
///
|
||||||
/// *[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees-precise)*
|
/// *[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees-precise)*
|
||||||
///
|
///
|
||||||
@@ -9778,10 +9778,10 @@ impl BrkClient {
|
|||||||
|
|
||||||
/// Mempool content hash
|
/// Mempool content hash
|
||||||
///
|
///
|
||||||
/// Returns an opaque `u64` that changes whenever the projected next block changes. Same value as the mempool ETag. Useful as a freshness/liveness signal: if it stays constant for tens of seconds on a live network, the mempool sync loop has stalled.
|
/// Returns an opaque hash that changes whenever the projected next block changes. Same value as the mempool ETag. Useful as a freshness/liveness signal: if it stays constant for tens of seconds on a live network, the mempool sync loop has stalled.
|
||||||
///
|
///
|
||||||
/// Endpoint: `GET /api/mempool/hash`
|
/// Endpoint: `GET /api/mempool/hash`
|
||||||
pub fn get_mempool_hash(&self) -> Result<i64> {
|
pub fn get_mempool_hash(&self) -> Result<NextBlockHash> {
|
||||||
self.base.get_json(&format!("/api/mempool/hash"))
|
self.base.get_json(&format!("/api/mempool/hash"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9829,6 +9829,24 @@ impl BrkClient {
|
|||||||
self.base.get_json(&format!("/api/v1/fullrbf/replacements"))
|
self.base.get_json(&format!("/api/v1/fullrbf/replacements"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Projected next block template
|
||||||
|
///
|
||||||
|
/// Bitcoin Core's `getblocktemplate` selection: full transaction bodies in GBT order with aggregate stats. The returned `hash` is an opaque content token; pass it as `<hash>` on `/api/v1/mempool/block-template/diff/{hash}` to fetch deltas instead of refetching the whole template.
|
||||||
|
///
|
||||||
|
/// Endpoint: `GET /api/v1/mempool/block-template`
|
||||||
|
pub fn get_block_template(&self) -> Result<BlockTemplate> {
|
||||||
|
self.base.get_json(&format!("/api/v1/mempool/block-template"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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`.
|
||||||
|
///
|
||||||
|
/// Endpoint: `GET /api/v1/mempool/block-template/diff/{hash}`
|
||||||
|
pub fn get_block_template_diff(&self, hash: NextBlockHash) -> Result<BlockTemplateDiff> {
|
||||||
|
self.base.get_json(&format!("/api/v1/mempool/block-template/diff/{hash}"))
|
||||||
|
}
|
||||||
|
|
||||||
/// Live BTC/USD price
|
/// Live BTC/USD price
|
||||||
///
|
///
|
||||||
/// Returns the current BTC/USD price in dollars, derived from on-chain round-dollar output patterns in the last 12 blocks plus mempool.
|
/// Returns the current BTC/USD price in dollars, derived from on-chain round-dollar output patterns in the last 12 blocks plus mempool.
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const MAX_LOG_AGE_DAYS: u64 = 7;
|
|||||||
/// `*.txt` file older than 7 days is pruned on startup.
|
/// `*.txt` file older than 7 days is pruned on startup.
|
||||||
pub fn init(dir: Option<&Path>) -> io::Result<()> {
|
pub fn init(dir: Option<&Path>) -> io::Result<()> {
|
||||||
tracing_log::LogTracer::init().ok();
|
tracing_log::LogTracer::init().ok();
|
||||||
|
install_panic_hook();
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
const DEFAULT_LEVEL: &str = "debug";
|
const DEFAULT_LEVEL: &str = "debug";
|
||||||
@@ -65,6 +66,24 @@ pub fn init(dir: Option<&Path>) -> io::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn install_panic_hook() {
|
||||||
|
std::panic::set_hook(Box::new(|info| {
|
||||||
|
let location = info
|
||||||
|
.location()
|
||||||
|
.map(|l| format!("{}:{}:{}", l.file(), l.line(), l.column()))
|
||||||
|
.unwrap_or_else(|| "unknown".to_string());
|
||||||
|
let payload = info.payload();
|
||||||
|
let msg = payload
|
||||||
|
.downcast_ref::<&str>()
|
||||||
|
.copied()
|
||||||
|
.map(str::to_owned)
|
||||||
|
.or_else(|| payload.downcast_ref::<String>().cloned())
|
||||||
|
.unwrap_or_else(|| "Box<dyn Any>".to_owned());
|
||||||
|
let backtrace = std::backtrace::Backtrace::capture();
|
||||||
|
tracing::error!(location, backtrace = %backtrace, "panic: {msg}");
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/// Register a hook that gets called for every log message.
|
/// Register a hook that gets called for every log message.
|
||||||
pub fn register_hook<F>(hook: F) -> Result<(), &'static str>
|
pub fn register_hook<F>(hook: F) -> Result<(), &'static str>
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -30,9 +30,11 @@ use std::{
|
|||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_rpc::Client;
|
use brk_rpc::Client;
|
||||||
use brk_types::{
|
use brk_types::{
|
||||||
AddrBytes, AddrMempoolStats, FeeRate, MempoolInfo, MempoolRecentTx, OutpointPrefix, OutputType,
|
AddrBytes, AddrMempoolStats, BlockTemplate, BlockTemplateDiff, FeeRate, MempoolBlock,
|
||||||
Sats, Timestamp, Transaction, TxOut, Txid, TxidPrefix, Vin, Vout,
|
MempoolInfo, MempoolRecentTx, NextBlockHash, OutpointPrefix, OutputType, Sats, Timestamp,
|
||||||
|
Transaction, TxOut, Txid, TxidPrefix, Vin, Vout,
|
||||||
};
|
};
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
use parking_lot::{RwLock, RwLockReadGuard};
|
use parking_lot::{RwLock, RwLockReadGuard};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
@@ -102,10 +104,58 @@ impl Mempool {
|
|||||||
self.snapshot().block_stats.clone()
|
self.snapshot().block_stats.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_block_hash(&self) -> u64 {
|
pub fn next_block_hash(&self) -> NextBlockHash {
|
||||||
self.snapshot().next_block_hash
|
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 {
|
pub fn addr_state_hash(&self, addr: &AddrBytes) -> u64 {
|
||||||
self.read().addrs.stats_hash(addr)
|
self.read().addrs.stats_hash(addr)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
use std::sync::{
|
use std::{
|
||||||
|
collections::VecDeque,
|
||||||
|
sync::{
|
||||||
Arc,
|
Arc,
|
||||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use brk_rpc::BlockTemplateTx;
|
use brk_rpc::BlockTemplateTx;
|
||||||
use brk_types::{FeeRate, TxidPrefix};
|
use brk_types::{FeeRate, NextBlockHash, Txid, TxidPrefix};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
@@ -20,10 +23,13 @@ pub use brk_types::RecommendedFees;
|
|||||||
pub use snapshot::{BlockStats, SnapTx, Snapshot, TxIndex};
|
pub use snapshot::{BlockStats, SnapTx, Snapshot, TxIndex};
|
||||||
|
|
||||||
const NUM_BLOCKS: usize = 8;
|
const NUM_BLOCKS: usize = 8;
|
||||||
|
const HISTORY: usize = 10;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Rebuilder {
|
pub struct Rebuilder {
|
||||||
snapshot: RwLock<Arc<Snapshot>>,
|
snapshot: RwLock<Arc<Snapshot>>,
|
||||||
|
/// Past block-0 txid sets keyed by `next_block_hash`, oldest first.
|
||||||
|
history: RwLock<VecDeque<(NextBlockHash, FxHashSet<Txid>)>>,
|
||||||
dirty: AtomicBool,
|
dirty: AtomicBool,
|
||||||
rebuild_count: AtomicU64,
|
rebuild_count: AtomicU64,
|
||||||
skip_clean: AtomicU64,
|
skip_clean: AtomicU64,
|
||||||
@@ -49,11 +55,38 @@ impl Rebuilder {
|
|||||||
self.skip_clean.fetch_add(1, Ordering::Relaxed);
|
self.skip_clean.fetch_add(1, Ordering::Relaxed);
|
||||||
return;
|
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.dirty.store(false, Ordering::Release);
|
||||||
self.rebuild_count.fetch_add(1, Ordering::Relaxed);
|
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 {
|
pub fn rebuild_count(&self) -> u64 {
|
||||||
self.rebuild_count.load(Ordering::Relaxed)
|
self.rebuild_count.load(Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ pub use tx_index::TxIndex;
|
|||||||
|
|
||||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||||
|
|
||||||
use brk_types::{FeeRate, RecommendedFees, TxidPrefix};
|
use brk_types::{FeeRate, NextBlockHash, RecommendedFees, TxidPrefix};
|
||||||
|
|
||||||
use fees::Fees;
|
use fees::Fees;
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ pub struct Snapshot {
|
|||||||
pub fees: RecommendedFees,
|
pub fees: RecommendedFees,
|
||||||
/// Content hash of the projected next block. Same value as the
|
/// Content hash of the projected next block. Same value as the
|
||||||
/// mempool ETag.
|
/// mempool ETag.
|
||||||
pub next_block_hash: u64,
|
pub next_block_hash: NextBlockHash,
|
||||||
/// Per-snapshot `TxidPrefix -> TxIndex` index, so live queries can
|
/// Per-snapshot `TxidPrefix -> TxIndex` index, so live queries can
|
||||||
/// resolve a prefix to the snapshot's compact index without
|
/// resolve a prefix to the snapshot's compact index without
|
||||||
/// re-walking `txs`. Built once by `builder::build_txs` and reused
|
/// 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 {
|
let Some(block) = blocks.first() else {
|
||||||
return 0;
|
return NextBlockHash::ZERO;
|
||||||
};
|
};
|
||||||
let mut hasher = DefaultHasher::new();
|
let mut hasher = DefaultHasher::new();
|
||||||
block.hash(&mut hasher);
|
block.hash(&mut hasher);
|
||||||
hasher.finish()
|
NextBlockHash::new(hasher.finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tx(&self, idx: TxIndex) -> Option<&SnapTx> {
|
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};
|
use super::{SnapTx, TxIndex};
|
||||||
|
|
||||||
@@ -83,3 +83,9 @@ impl BlockStats {
|
|||||||
self.fee_range[3]
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ use crate::Query;
|
|||||||
use brk_error::{Error, Result};
|
use brk_error::{Error, Result};
|
||||||
use brk_mempool::{Mempool, PrevoutResolver, RbfForTx, RbfNode};
|
use brk_mempool::{Mempool, PrevoutResolver, RbfForTx, RbfNode};
|
||||||
use brk_types::{
|
use brk_types::{
|
||||||
CheckedSub, FeeRate, MempoolBlock, MempoolInfo, MempoolRecentTx, OutputType, RbfResponse,
|
BlockTemplate, BlockTemplateDiff, CheckedSub, FeeRate, MempoolBlock, MempoolInfo,
|
||||||
RbfTx, RecommendedFees, ReplacementNode, Sats, Timestamp, TxOut, TxOutIndex, Txid, TxidPrefix,
|
MempoolRecentTx, NextBlockHash, OutputType, RbfResponse, RbfTx, RecommendedFees,
|
||||||
TypeIndex,
|
ReplacementNode, Sats, Timestamp, TxOut, TxOutIndex, Txid, TxidPrefix, TypeIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
const RECENT_REPLACEMENTS_LIMIT: usize = 25;
|
const RECENT_REPLACEMENTS_LIMIT: usize = 25;
|
||||||
@@ -28,23 +28,7 @@ impl Query {
|
|||||||
|
|
||||||
pub fn mempool_blocks(&self) -> Result<Vec<MempoolBlock>> {
|
pub fn mempool_blocks(&self) -> Result<Vec<MempoolBlock>> {
|
||||||
let mempool = self.require_mempool()?;
|
let mempool = self.require_mempool()?;
|
||||||
|
Ok(mempool.block_stats().iter().map(MempoolBlock::from).collect())
|
||||||
let block_stats = mempool.block_stats();
|
|
||||||
|
|
||||||
let blocks = block_stats
|
|
||||||
.into_iter()
|
|
||||||
.map(|stats| {
|
|
||||||
MempoolBlock::new(
|
|
||||||
stats.tx_count,
|
|
||||||
stats.total_size,
|
|
||||||
stats.total_vsize,
|
|
||||||
stats.total_fee,
|
|
||||||
stats.fee_range,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(blocks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indexer-backed resolver for confirmed-parent prevouts. Pass
|
/// Indexer-backed resolver for confirmed-parent prevouts. Pass
|
||||||
@@ -172,7 +156,22 @@ impl Query {
|
|||||||
|
|
||||||
/// Content hash of the projected next block. Same value as the
|
/// Content hash of the projected next block. Same value as the
|
||||||
/// mempool ETag. Polling lets monitors detect a stalled sync.
|
/// mempool ETag. Polling lets monitors detect a stalled sync.
|
||||||
pub fn mempool_hash(&self) -> Result<u64> {
|
pub fn mempool_hash(&self) -> Result<NextBlockHash> {
|
||||||
Ok(self.require_mempool()?.next_block_hash())
|
Ok(self.require_mempool()?.next_block_hash())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Full projected next block (Core's `getblocktemplate` selection)
|
||||||
|
/// with stats and full tx bodies in GBT order.
|
||||||
|
pub fn block_template(&self) -> Result<BlockTemplate> {
|
||||||
|
Ok(self.require_mempool()?.block_template())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delta of the projected next block since `since`. `NotFound`
|
||||||
|
/// when `since` has aged out (client should fall back to
|
||||||
|
/// `block_template`).
|
||||||
|
pub fn block_template_diff(&self, since: NextBlockHash) -> Result<BlockTemplateDiff> {
|
||||||
|
self.require_mempool()?
|
||||||
|
.block_template_diff(since)
|
||||||
|
.ok_or_else(|| Error::NotFound(format!("unknown since hash: {since}")))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -373,22 +373,25 @@ impl Client {
|
|||||||
|
|
||||||
/// Verbose mempool listing + Core's projected next block + live
|
/// Verbose mempool listing + Core's projected next block + live
|
||||||
/// `mempoolminfee`, fetched in a single bitcoind round-trip.
|
/// `mempoolminfee`, fetched in a single bitcoind round-trip.
|
||||||
/// Validates that every GBT txid is present in the verbose listing
|
/// `getblocktemplate` runs first so that any tx arriving between
|
||||||
/// and returns `Ok(None)` on mismatch so the caller can skip the
|
/// the two intra-batch calls lands in the verbose listing only,
|
||||||
/// cycle (within-batch races inside bitcoind are rare; persistent
|
/// preserving GBT ⊆ verbose for the common race. Validates that
|
||||||
/// drift is bug-shaped). Other failures bubble up as `Err`.
|
/// every GBT txid is present in the verbose listing and returns
|
||||||
|
/// `Ok(None)` on mismatch so the caller can skip the cycle:
|
||||||
|
/// republishing block 0 with missing txids would diverge from
|
||||||
|
/// Core's exact selection. Other failures bubble up as `Err`.
|
||||||
pub fn fetch_mempool_state(&self) -> Result<Option<MempoolState>> {
|
pub fn fetch_mempool_state(&self) -> Result<Option<MempoolState>> {
|
||||||
let requests: [(&str, Vec<Value>); 3] = [
|
let requests: [(&str, Vec<Value>); 3] = [
|
||||||
("getrawmempool", vec![Value::Bool(true)]),
|
|
||||||
(
|
(
|
||||||
"getblocktemplate",
|
"getblocktemplate",
|
||||||
vec![serde_json::json!({ "rules": ["segwit"] })],
|
vec![serde_json::json!({ "rules": ["segwit"] })],
|
||||||
),
|
),
|
||||||
|
("getrawmempool", vec![Value::Bool(true)]),
|
||||||
("getmempoolinfo", vec![]),
|
("getmempoolinfo", vec![]),
|
||||||
];
|
];
|
||||||
let mut out = self.0.call_mixed_batch(&requests)?.into_iter();
|
let mut out = self.0.call_mixed_batch(&requests)?.into_iter();
|
||||||
let verbose_raw = out.next().ok_or(Error::Internal("missing verbose"))??;
|
|
||||||
let gbt_raw = out.next().ok_or(Error::Internal("missing gbt"))??;
|
let gbt_raw = out.next().ok_or(Error::Internal("missing gbt"))??;
|
||||||
|
let verbose_raw = out.next().ok_or(Error::Internal("missing verbose"))??;
|
||||||
let info_raw = out.next().ok_or(Error::Internal("missing mempoolinfo"))??;
|
let info_raw = out.next().ok_or(Error::Internal("missing mempoolinfo"))??;
|
||||||
|
|
||||||
let verbose: FxHashMap<String, VerboseEntryRaw> = serde_json::from_str(verbose_raw.get())?;
|
let verbose: FxHashMap<String, VerboseEntryRaw> = serde_json::from_str(verbose_raw.get())?;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ impl FeesRoutes for ApiRouter<AppState> {
|
|||||||
op.id("get_mempool_blocks")
|
op.id("get_mempool_blocks")
|
||||||
.fees_tag()
|
.fees_tag()
|
||||||
.summary("Projected mempool blocks")
|
.summary("Projected mempool blocks")
|
||||||
.description("Get projected blocks from the mempool for fee estimation.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-mempool-blocks-fees)*")
|
.description("Projected blocks for fee estimation. Block 0 reflects Bitcoin Core's actual next-block selection; blocks 1+ are a fee-tier approximation.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-mempool-blocks-fees)*")
|
||||||
.json_response::<Vec<MempoolBlock>>()
|
.json_response::<Vec<MempoolBlock>>()
|
||||||
.not_modified()
|
.not_modified()
|
||||||
.server_error()
|
.server_error()
|
||||||
@@ -48,7 +48,7 @@ impl FeesRoutes for ApiRouter<AppState> {
|
|||||||
op.id("get_recommended_fees")
|
op.id("get_recommended_fees")
|
||||||
.fees_tag()
|
.fees_tag()
|
||||||
.summary("Recommended fees")
|
.summary("Recommended fees")
|
||||||
.description("Get recommended fee rates for different confirmation targets.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees)*")
|
.description("Recommended fee rates by confirmation target.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees)*")
|
||||||
.json_response::<RecommendedFees>()
|
.json_response::<RecommendedFees>()
|
||||||
.not_modified()
|
.not_modified()
|
||||||
.server_error()
|
.server_error()
|
||||||
@@ -69,7 +69,7 @@ impl FeesRoutes for ApiRouter<AppState> {
|
|||||||
op.id("get_precise_fees")
|
op.id("get_precise_fees")
|
||||||
.fees_tag()
|
.fees_tag()
|
||||||
.summary("Precise recommended fees")
|
.summary("Precise recommended fees")
|
||||||
.description("Get recommended fee rates with up to 3 decimal places.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees-precise)*")
|
.description("Recommended fee rates with sub-integer precision.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees-precise)*")
|
||||||
.json_response::<RecommendedFees>()
|
.json_response::<RecommendedFees>()
|
||||||
.not_modified()
|
.not_modified()
|
||||||
.server_error()
|
.server_error()
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
use aide::axum::{ApiRouter, routing::get_with};
|
use aide::axum::{ApiRouter, routing::get_with};
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::State,
|
extract::{Path, State},
|
||||||
http::{HeaderMap, Uri},
|
http::{HeaderMap, Uri},
|
||||||
};
|
};
|
||||||
use brk_types::{Dollars, MempoolInfo, MempoolRecentTx, ReplacementNode, Txid};
|
use brk_types::{
|
||||||
|
BlockTemplate, BlockTemplateDiff, Dollars, MempoolInfo, MempoolRecentTx, NextBlockHash,
|
||||||
|
ReplacementNode, Txid,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{AppState, extended::TransformResponseExtended, params::Empty};
|
use crate::{
|
||||||
|
AppState,
|
||||||
|
extended::TransformResponseExtended,
|
||||||
|
params::{Empty, NextBlockHashParam},
|
||||||
|
};
|
||||||
|
|
||||||
pub trait MempoolRoutes {
|
pub trait MempoolRoutes {
|
||||||
fn add_mempool_routes(self) -> Self;
|
fn add_mempool_routes(self) -> Self;
|
||||||
@@ -44,8 +51,8 @@ impl MempoolRoutes for ApiRouter<AppState> {
|
|||||||
op.id("get_mempool_hash")
|
op.id("get_mempool_hash")
|
||||||
.mempool_tag()
|
.mempool_tag()
|
||||||
.summary("Mempool content hash")
|
.summary("Mempool content hash")
|
||||||
.description("Returns an opaque `u64` that changes whenever the projected next block changes. Same value as the mempool ETag. Useful as a freshness/liveness signal: if it stays constant for tens of seconds on a live network, the mempool sync loop has stalled.")
|
.description("Returns an opaque hash that changes whenever the projected next block changes. Same value as the mempool ETag. Useful as a freshness/liveness signal: if it stays constant for tens of seconds on a live network, the mempool sync loop has stalled.")
|
||||||
.json_response::<u64>()
|
.json_response::<NextBlockHash>()
|
||||||
.not_modified()
|
.not_modified()
|
||||||
.server_error()
|
.server_error()
|
||||||
},
|
},
|
||||||
@@ -131,6 +138,53 @@ impl MempoolRoutes for ApiRouter<AppState> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.api_route(
|
||||||
|
"/api/v1/mempool/block-template",
|
||||||
|
get_with(
|
||||||
|
async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
|
||||||
|
state
|
||||||
|
.respond_json(&headers, state.mempool_strategy(), &uri, |q| {
|
||||||
|
q.block_template()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
},
|
||||||
|
|op| {
|
||||||
|
op.id("get_block_template")
|
||||||
|
.mempool_tag()
|
||||||
|
.summary("Projected next block template")
|
||||||
|
.description("Bitcoin Core's `getblocktemplate` selection: full transaction bodies in GBT order with aggregate stats. The returned `hash` is an opaque content token; pass it as `<hash>` on `/api/v1/mempool/block-template/diff/{hash}` to fetch deltas instead of refetching the whole template.")
|
||||||
|
.json_response::<BlockTemplate>()
|
||||||
|
.not_modified()
|
||||||
|
.server_error()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.api_route(
|
||||||
|
"/api/v1/mempool/block-template/diff/{hash}",
|
||||||
|
get_with(
|
||||||
|
async |uri: Uri,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Path(path): Path<NextBlockHashParam>,
|
||||||
|
_: Empty,
|
||||||
|
State(state): State<AppState>| {
|
||||||
|
state
|
||||||
|
.respond_json(&headers, state.mempool_strategy(), &uri, move |q| {
|
||||||
|
q.block_template_diff(path.hash)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
},
|
||||||
|
|op| {
|
||||||
|
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`.")
|
||||||
|
.json_response::<BlockTemplateDiff>()
|
||||||
|
.not_modified()
|
||||||
|
.not_found()
|
||||||
|
.server_error()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
.api_route(
|
.api_route(
|
||||||
"/api/mempool/price",
|
"/api/mempool/price",
|
||||||
get_with(
|
get_with(
|
||||||
|
|||||||
@@ -102,9 +102,11 @@ impl Server {
|
|||||||
let response_time_layer = axum::middleware::from_fn(
|
let response_time_layer = axum::middleware::from_fn(
|
||||||
async |request: Request<Body>, next: Next| -> Response<Body> {
|
async |request: Request<Body>, next: Next| -> Response<Body> {
|
||||||
let uri = request.uri().clone();
|
let uri = request.uri().clone();
|
||||||
|
let method = request.method().clone();
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let mut response = next.run(request).await;
|
let mut response = next.run(request).await;
|
||||||
response.extensions_mut().insert(uri);
|
response.extensions_mut().insert(uri);
|
||||||
|
response.extensions_mut().insert(method);
|
||||||
response.headers_mut().insert(
|
response.headers_mut().insert(
|
||||||
"X-Response-Time",
|
"X-Response-Time",
|
||||||
format!("{}us", start.elapsed().as_micros())
|
format!("{}us", start.elapsed().as_micros())
|
||||||
@@ -182,14 +184,19 @@ impl Server {
|
|||||||
.on_response(
|
.on_response(
|
||||||
|response: &Response<Body>, latency: Duration, _: &tracing::Span| {
|
|response: &Response<Body>, latency: Duration, _: &tracing::Span| {
|
||||||
let status = response.status().as_u16();
|
let status = response.status().as_u16();
|
||||||
let unknown = Uri::from_static("/unknown");
|
let unknown_uri = Uri::from_static("/unknown");
|
||||||
let uri = response.extensions().get::<Uri>().unwrap_or(&unknown);
|
let unknown_method = axum::http::Method::default();
|
||||||
|
let uri = response.extensions().get::<Uri>().unwrap_or(&unknown_uri);
|
||||||
|
let method = response
|
||||||
|
.extensions()
|
||||||
|
.get::<axum::http::Method>()
|
||||||
|
.unwrap_or(&unknown_method);
|
||||||
match response.status() {
|
match response.status() {
|
||||||
StatusCode::OK => info!(status, %uri, ?latency),
|
StatusCode::OK => info!(%method, status, %uri, ?latency),
|
||||||
StatusCode::NOT_MODIFIED
|
StatusCode::NOT_MODIFIED
|
||||||
| StatusCode::TEMPORARY_REDIRECT
|
| StatusCode::TEMPORARY_REDIRECT
|
||||||
| StatusCode::PERMANENT_REDIRECT => info!(status, %uri, ?latency),
|
| StatusCode::PERMANENT_REDIRECT => info!(%method, status, %uri, ?latency),
|
||||||
_ => error!(status, %uri, ?latency),
|
_ => error!(%method, status, %uri, ?latency),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -209,8 +216,6 @@ impl Server {
|
|||||||
let router = router
|
let router = router
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
.merge(website_router)
|
.merge(website_router)
|
||||||
.layer(response_time_layer)
|
|
||||||
.layer(trace_layer)
|
|
||||||
.layer(TimeoutLayer::with_status_code(
|
.layer(TimeoutLayer::with_status_code(
|
||||||
StatusCode::GATEWAY_TIMEOUT,
|
StatusCode::GATEWAY_TIMEOUT,
|
||||||
REQUEST_TIMEOUT,
|
REQUEST_TIMEOUT,
|
||||||
@@ -242,7 +247,9 @@ impl Server {
|
|||||||
.or_else(|| panic.downcast_ref::<&str>().copied())
|
.or_else(|| panic.downcast_ref::<&str>().copied())
|
||||||
.unwrap_or("Unknown panic");
|
.unwrap_or("Unknown panic");
|
||||||
Error::internal(msg).into_response()
|
Error::internal(msg).into_response()
|
||||||
}));
|
}))
|
||||||
|
.layer(response_time_layer)
|
||||||
|
.layer(trace_layer);
|
||||||
|
|
||||||
let (listener, port) = match port {
|
let (listener, port) = match port {
|
||||||
Some(port) => {
|
Some(port) => {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ mod blockhash_start_index;
|
|||||||
mod blockhash_tx_index;
|
mod blockhash_tx_index;
|
||||||
mod empty;
|
mod empty;
|
||||||
mod height_param;
|
mod height_param;
|
||||||
|
mod next_block_hash_param;
|
||||||
mod pool_slug_param;
|
mod pool_slug_param;
|
||||||
mod series_param;
|
mod series_param;
|
||||||
mod time_period_param;
|
mod time_period_param;
|
||||||
@@ -25,6 +26,7 @@ pub use blockhash_start_index::*;
|
|||||||
pub use blockhash_tx_index::*;
|
pub use blockhash_tx_index::*;
|
||||||
pub use empty::*;
|
pub use empty::*;
|
||||||
pub use height_param::*;
|
pub use height_param::*;
|
||||||
|
pub use next_block_hash_param::*;
|
||||||
pub use pool_slug_param::*;
|
pub use pool_slug_param::*;
|
||||||
pub use series_param::*;
|
pub use series_param::*;
|
||||||
pub use time_period_param::*;
|
pub use time_period_param::*;
|
||||||
|
|||||||
10
crates/brk_server/src/params/next_block_hash_param.rs
Normal file
10
crates/brk_server/src/params/next_block_hash_param.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use brk_types::NextBlockHash;
|
||||||
|
|
||||||
|
/// `since` hash for `/api/v1/mining/block-template/diff/{hash}`.
|
||||||
|
#[derive(Deserialize, JsonSchema)]
|
||||||
|
pub struct NextBlockHashParam {
|
||||||
|
pub hash: NextBlockHash,
|
||||||
|
}
|
||||||
@@ -124,7 +124,7 @@ impl AppState {
|
|||||||
if let Some(mempool) = q.mempool()
|
if let Some(mempool) = q.mempool()
|
||||||
&& mempool.contains_txid(txid)
|
&& mempool.contains_txid(txid)
|
||||||
{
|
{
|
||||||
return CacheStrategy::MempoolHash(mempool.next_block_hash());
|
return CacheStrategy::MempoolHash(mempool.next_block_hash().into());
|
||||||
}
|
}
|
||||||
if let Ok((_, height)) = q.resolve_tx(txid)
|
if let Ok((_, height)) = q.resolve_tx(txid)
|
||||||
&& let Ok(block_hash) = q.block_hash_by_height(height)
|
&& let Ok(block_hash) = q.block_hash_by_height(height)
|
||||||
@@ -136,7 +136,7 @@ impl AppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn mempool_strategy(&self) -> CacheStrategy {
|
pub fn mempool_strategy(&self) -> CacheStrategy {
|
||||||
let hash = self.sync(|q| q.mempool().map(|m| m.next_block_hash()).unwrap_or(0));
|
let hash = self.sync(|q| q.mempool().map(|m| m.next_block_hash().into()).unwrap_or(0));
|
||||||
CacheStrategy::MempoolHash(hash)
|
CacheStrategy::MempoolHash(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
21
crates/brk_types/src/block_template.rs
Normal file
21
crates/brk_types/src/block_template.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
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`.
|
||||||
|
#[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.
|
||||||
|
pub hash: NextBlockHash,
|
||||||
|
|
||||||
|
/// Aggregate stats for this block (size, vsize, fee range, ...).
|
||||||
|
pub stats: MempoolBlock,
|
||||||
|
|
||||||
|
/// Full transaction bodies in `getblocktemplate` order.
|
||||||
|
pub transactions: Vec<Transaction>,
|
||||||
|
}
|
||||||
25
crates/brk_types/src/block_template_diff.rs
Normal file
25
crates/brk_types/src/block_template_diff.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{NextBlockHash, Transaction, Txid};
|
||||||
|
|
||||||
|
/// Delta between the current `getblocktemplate` projection and a prior
|
||||||
|
/// one identified by `since`. Returned by
|
||||||
|
/// `GET /api/v1/mining/block-template/diff/{hash}`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct BlockTemplateDiff {
|
||||||
|
/// Current next-block hash. Use as `since` on the next diff call.
|
||||||
|
pub hash: NextBlockHash,
|
||||||
|
|
||||||
|
/// 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>,
|
||||||
|
|
||||||
|
/// Txids that left the projected next block since `since`
|
||||||
|
/// (confirmed, evicted, replaced, or pushed past block 0).
|
||||||
|
pub removed: Vec<Txid>,
|
||||||
|
}
|
||||||
@@ -80,6 +80,8 @@ mod hour4;
|
|||||||
mod index;
|
mod index;
|
||||||
mod index_info;
|
mod index_info;
|
||||||
mod limit;
|
mod limit;
|
||||||
|
mod block_template;
|
||||||
|
mod block_template_diff;
|
||||||
mod mempool_block;
|
mod mempool_block;
|
||||||
mod mempool_entry_info;
|
mod mempool_entry_info;
|
||||||
mod mempool_info;
|
mod mempool_info;
|
||||||
@@ -90,6 +92,7 @@ mod minute30;
|
|||||||
mod month1;
|
mod month1;
|
||||||
mod month3;
|
mod month3;
|
||||||
mod month6;
|
mod month6;
|
||||||
|
mod next_block_hash;
|
||||||
mod ohlc;
|
mod ohlc;
|
||||||
mod op_return_index;
|
mod op_return_index;
|
||||||
mod option_ext;
|
mod option_ext;
|
||||||
@@ -225,6 +228,8 @@ pub use block_rewards_entry::*;
|
|||||||
pub use block_size_entry::*;
|
pub use block_size_entry::*;
|
||||||
pub use block_sizes_weights::*;
|
pub use block_sizes_weights::*;
|
||||||
pub use block_status::*;
|
pub use block_status::*;
|
||||||
|
pub use block_template::*;
|
||||||
|
pub use block_template_diff::*;
|
||||||
pub use block_timestamp::*;
|
pub use block_timestamp::*;
|
||||||
pub use block_tx_index::*;
|
pub use block_tx_index::*;
|
||||||
pub use block_weight_entry::*;
|
pub use block_weight_entry::*;
|
||||||
@@ -283,6 +288,7 @@ pub use minute30::*;
|
|||||||
pub use month1::*;
|
pub use month1::*;
|
||||||
pub use month3::*;
|
pub use month3::*;
|
||||||
pub use month6::*;
|
pub use month6::*;
|
||||||
|
pub use next_block_hash::*;
|
||||||
pub use ohlc::*;
|
pub use ohlc::*;
|
||||||
pub use op_return_index::*;
|
pub use op_return_index::*;
|
||||||
pub use option_ext::*;
|
pub use option_ext::*;
|
||||||
|
|||||||
51
crates/brk_types/src/next_block_hash.rs
Normal file
51
crates/brk_types/src/next_block_hash.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
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
|
||||||
|
/// deltas.
|
||||||
|
#[derive(
|
||||||
|
Debug,
|
||||||
|
Default,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Hash,
|
||||||
|
PartialOrd,
|
||||||
|
Ord,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
JsonSchema,
|
||||||
|
)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct NextBlockHash(u64);
|
||||||
|
|
||||||
|
impl NextBlockHash {
|
||||||
|
pub const ZERO: Self = Self(0);
|
||||||
|
|
||||||
|
pub const fn new(value: u64) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for NextBlockHash {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u64> for NextBlockHash {
|
||||||
|
fn from(value: u64) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NextBlockHash> for u64 {
|
||||||
|
fn from(value: NextBlockHash) -> Self {
|
||||||
|
value.0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -281,6 +281,30 @@ Matches mempool.space/bitcoin-cli behavior.
|
|||||||
* @property {(Height|null)=} height - Block height (only if in best chain)
|
* @property {(Height|null)=} height - Block height (only if in best chain)
|
||||||
* @property {(BlockHash|null)=} nextBest - Hash of the next block in the best chain (null if tip)
|
* @property {(BlockHash|null)=} nextBest - Hash of the next block in the best chain (null if tip)
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Projected next-block contents from Bitcoin Core's `getblocktemplate`
|
||||||
|
* (block 0 of the snapshot). Returned by
|
||||||
|
* `GET /api/v1/mining/block-template`.
|
||||||
|
*
|
||||||
|
* @typedef {Object} BlockTemplate
|
||||||
|
* @property {NextBlockHash} hash - Pass back as `<hash>` on
|
||||||
|
`/api/v1/mining/block-template/diff/{hash}` to fetch deltas.
|
||||||
|
* @property {MempoolBlock} stats - Aggregate stats for this block (size, vsize, fee range, ...).
|
||||||
|
* @property {Transaction[]} transactions - Full transaction bodies in `getblocktemplate` order.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Delta between the current `getblocktemplate` projection and a prior
|
||||||
|
* one identified by `since`. Returned by
|
||||||
|
* `GET /api/v1/mining/block-template/diff/{hash}`.
|
||||||
|
*
|
||||||
|
* @typedef {Object} BlockTemplateDiff
|
||||||
|
* @property {NextBlockHash} hash - Current next-block hash. Use as `since` on the next diff call.
|
||||||
|
* @property {NextBlockHash} since - Echoed prior hash the diff was computed against.
|
||||||
|
* @property {Transaction[]} added - Full bodies of transactions that joined the projected next
|
||||||
|
block since `since`.
|
||||||
|
* @property {Txid[]} removed - Txids that left the projected next block since `since`
|
||||||
|
(confirmed, evicted, replaced, or pushed past block 0).
|
||||||
|
*/
|
||||||
/**
|
/**
|
||||||
* Block information returned for timestamp queries
|
* Block information returned for timestamp queries
|
||||||
*
|
*
|
||||||
@@ -711,6 +735,20 @@ ancestors and no descendants (matches mempool.space).
|
|||||||
/** @typedef {number} Month1 */
|
/** @typedef {number} Month1 */
|
||||||
/** @typedef {number} Month3 */
|
/** @typedef {number} Month3 */
|
||||||
/** @typedef {number} Month6 */
|
/** @typedef {number} Month6 */
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* deltas.
|
||||||
|
*
|
||||||
|
* @typedef {number} NextBlockHash
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* `since` hash for `/api/v1/mining/block-template/diff/{hash}`.
|
||||||
|
*
|
||||||
|
* @typedef {Object} NextBlockHashParam
|
||||||
|
* @property {NextBlockHash} hash
|
||||||
|
*/
|
||||||
/**
|
/**
|
||||||
* OHLC (Open, High, Low, Close) data in cents
|
* OHLC (Open, High, Low, Close) data in cents
|
||||||
*
|
*
|
||||||
@@ -11620,7 +11658,7 @@ class BrkClient extends BrkClientBase {
|
|||||||
/**
|
/**
|
||||||
* Projected mempool blocks
|
* Projected mempool blocks
|
||||||
*
|
*
|
||||||
* Get projected blocks from the mempool for fee estimation.
|
* Projected blocks for fee estimation. Block 0 reflects Bitcoin Core's actual next-block selection; blocks 1+ are a fee-tier approximation.
|
||||||
*
|
*
|
||||||
* *[Mempool.space docs](https://mempool.space/docs/api/rest#get-mempool-blocks-fees)*
|
* *[Mempool.space docs](https://mempool.space/docs/api/rest#get-mempool-blocks-fees)*
|
||||||
*
|
*
|
||||||
@@ -11636,7 +11674,7 @@ class BrkClient extends BrkClientBase {
|
|||||||
/**
|
/**
|
||||||
* Recommended fees
|
* Recommended fees
|
||||||
*
|
*
|
||||||
* Get recommended fee rates for different confirmation targets.
|
* Recommended fee rates by confirmation target.
|
||||||
*
|
*
|
||||||
* *[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees)*
|
* *[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees)*
|
||||||
*
|
*
|
||||||
@@ -11652,7 +11690,7 @@ class BrkClient extends BrkClientBase {
|
|||||||
/**
|
/**
|
||||||
* Precise recommended fees
|
* Precise recommended fees
|
||||||
*
|
*
|
||||||
* Get recommended fee rates with up to 3 decimal places.
|
* Recommended fee rates with sub-integer precision.
|
||||||
*
|
*
|
||||||
* *[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees-precise)*
|
* *[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees-precise)*
|
||||||
*
|
*
|
||||||
@@ -11684,11 +11722,11 @@ class BrkClient extends BrkClientBase {
|
|||||||
/**
|
/**
|
||||||
* Mempool content hash
|
* Mempool content hash
|
||||||
*
|
*
|
||||||
* Returns an opaque `u64` that changes whenever the projected next block changes. Same value as the mempool ETag. Useful as a freshness/liveness signal: if it stays constant for tens of seconds on a live network, the mempool sync loop has stalled.
|
* Returns an opaque hash that changes whenever the projected next block changes. Same value as the mempool ETag. Useful as a freshness/liveness signal: if it stays constant for tens of seconds on a live network, the mempool sync loop has stalled.
|
||||||
*
|
*
|
||||||
* Endpoint: `GET /api/mempool/hash`
|
* Endpoint: `GET /api/mempool/hash`
|
||||||
* @param {{ signal?: AbortSignal, onValue?: (value: number) => void }} [options]
|
* @param {{ signal?: AbortSignal, onValue?: (value: NextBlockHash) => void }} [options]
|
||||||
* @returns {Promise<number>}
|
* @returns {Promise<NextBlockHash>}
|
||||||
*/
|
*/
|
||||||
async getMempoolHash({ signal, onValue } = {}) {
|
async getMempoolHash({ signal, onValue } = {}) {
|
||||||
const path = `/api/mempool/hash`;
|
const path = `/api/mempool/hash`;
|
||||||
@@ -11759,6 +11797,36 @@ class BrkClient extends BrkClientBase {
|
|||||||
return this.getJson(path, { signal, onValue });
|
return this.getJson(path, { signal, onValue });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Projected next block template
|
||||||
|
*
|
||||||
|
* Bitcoin Core's `getblocktemplate` selection: full transaction bodies in GBT order with aggregate stats. The returned `hash` is an opaque content token; pass it as `<hash>` on `/api/v1/mempool/block-template/diff/{hash}` to fetch deltas instead of refetching the whole template.
|
||||||
|
*
|
||||||
|
* Endpoint: `GET /api/v1/mempool/block-template`
|
||||||
|
* @param {{ signal?: AbortSignal, onValue?: (value: BlockTemplate) => void }} [options]
|
||||||
|
* @returns {Promise<BlockTemplate>}
|
||||||
|
*/
|
||||||
|
async getBlockTemplate({ signal, onValue } = {}) {
|
||||||
|
const path = `/api/v1/mempool/block-template`;
|
||||||
|
return this.getJson(path, { signal, onValue });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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`.
|
||||||
|
*
|
||||||
|
* Endpoint: `GET /api/v1/mempool/block-template/diff/{hash}`
|
||||||
|
*
|
||||||
|
* @param {NextBlockHash} hash
|
||||||
|
* @param {{ signal?: AbortSignal, onValue?: (value: BlockTemplateDiff) => void }} [options]
|
||||||
|
* @returns {Promise<BlockTemplateDiff>}
|
||||||
|
*/
|
||||||
|
async getBlockTemplateDiff(hash, { signal, onValue } = {}) {
|
||||||
|
const path = `/api/v1/mempool/block-template/diff/${hash}`;
|
||||||
|
return this.getJson(path, { signal, onValue });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Live BTC/USD price
|
* Live BTC/USD price
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -68,6 +68,37 @@ BlockHash = str
|
|||||||
# Position of a transaction within a single block (0 = coinbase).
|
# Position of a transaction within a single block (0 = coinbase).
|
||||||
# Distinct from `TxIndex`, which is the chain-wide global tx index.
|
# Distinct from `TxIndex`, which is the chain-wide global tx index.
|
||||||
BlockTxIndex = int
|
BlockTxIndex = int
|
||||||
|
# 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
|
||||||
|
# deltas.
|
||||||
|
NextBlockHash = int
|
||||||
|
# Transaction locktime. Values below 500,000,000 are interpreted as block heights; values at or above are Unix timestamps.
|
||||||
|
RawLockTime = int
|
||||||
|
# BIP-141 sigop cost. The block-level budget is 80,000, so a `u32`
|
||||||
|
# fits a single tx's count with room to spare.
|
||||||
|
#
|
||||||
|
# Witness sigops count as 1; legacy and P2SH-redeem sigops count as 4.
|
||||||
|
# Five vbytes per sigop is the policy adjustment Core applies in
|
||||||
|
# `nSigOpCost` to discourage sigop-heavy txs (`max(weight/4, sigops*5)`).
|
||||||
|
SigOps = int
|
||||||
|
# Index of the output being spent in the previous transaction
|
||||||
|
Vout = int
|
||||||
|
# Transaction witness: a stack of byte arrays, one per witness item.
|
||||||
|
#
|
||||||
|
# Wraps `bitcoin::Witness` (single-buffer layout with offsets, much
|
||||||
|
# more compact than `Vec<Vec<u8>>`). Serializes as a JSON array of
|
||||||
|
# hex strings - the format used by Bitcoin Core REST and mempool.space
|
||||||
|
# and matching brk's `script_sig: ScriptBuf` (bytes internally, hex
|
||||||
|
# on the wire).
|
||||||
|
Witness = List[str]
|
||||||
|
# Chain-wide transaction index (0 = the genesis coinbase). For an
|
||||||
|
# in-block position, use `BlockTxIndex` instead.
|
||||||
|
TxIndex = int
|
||||||
|
# Raw transaction version (i32) from Bitcoin protocol.
|
||||||
|
# Unlike TxVersion (u8, indexed), this preserves non-standard values
|
||||||
|
# used in coinbase txs for miner signaling/branding.
|
||||||
|
TxVersionRaw = int
|
||||||
# Unsigned cents (u64) - for values that should never be negative.
|
# Unsigned cents (u64) - for values that should never be negative.
|
||||||
# Used for invested capital, realized cap, etc.
|
# Used for invested capital, realized cap, etc.
|
||||||
Cents = int
|
Cents = int
|
||||||
@@ -106,13 +137,6 @@ UrpdAggregation = Literal["raw", "lin200", "lin500", "lin1000", "log10", "log50"
|
|||||||
# Position of a transaction inside a `CpfpCluster.txs` array. Cluster-local,
|
# Position of a transaction inside a `CpfpCluster.txs` array. Cluster-local,
|
||||||
# has no meaning outside the enclosing cluster.
|
# has no meaning outside the enclosing cluster.
|
||||||
CpfpClusterTxIndex = int
|
CpfpClusterTxIndex = int
|
||||||
# BIP-141 sigop cost. The block-level budget is 80,000, so a `u32`
|
|
||||||
# fits a single tx's count with room to spare.
|
|
||||||
#
|
|
||||||
# Witness sigops count as 1; legacy and P2SH-redeem sigops count as 4.
|
|
||||||
# Five vbytes per sigop is the policy adjustment Core applies in
|
|
||||||
# `nSigOpCost` to discourage sigop-heavy txs (`max(weight/4, sigops*5)`).
|
|
||||||
SigOps = int
|
|
||||||
# Virtual size in vbytes (weight / 4, rounded up). Max block vsize is ~1,000,000 vB.
|
# Virtual size in vbytes (weight / 4, rounded up). Max block vsize is ~1,000,000 vB.
|
||||||
VSize = int
|
VSize = int
|
||||||
# Date in YYYYMMDD format stored as u32
|
# Date in YYYYMMDD format stored as u32
|
||||||
@@ -179,8 +203,6 @@ P2WPKHAddrIndex = TypeIndex
|
|||||||
P2WPKHBytes = U8x20
|
P2WPKHBytes = U8x20
|
||||||
P2WSHAddrIndex = TypeIndex
|
P2WSHAddrIndex = TypeIndex
|
||||||
P2WSHBytes = U8x32
|
P2WSHBytes = U8x32
|
||||||
# Transaction locktime. Values below 500,000,000 are interpreted as block heights; values at or above are Unix timestamps.
|
|
||||||
RawLockTime = int
|
|
||||||
# Fractional satoshis (f64) - for representing USD prices in sats
|
# Fractional satoshis (f64) - for representing USD prices in sats
|
||||||
#
|
#
|
||||||
# Formula: `sats_fract = usd_value * 100_000_000 / btc_price`
|
# Formula: `sats_fract = usd_value * 100_000_000 / btc_price`
|
||||||
@@ -219,23 +241,6 @@ StoredU64 = int
|
|||||||
# Used to specify the lookback window for pool statistics, hashrate calculations,
|
# Used to specify the lookback window for pool statistics, hashrate calculations,
|
||||||
# and other time-based mining series.
|
# and other time-based mining series.
|
||||||
TimePeriod = Literal["24h", "3d", "1w", "1m", "3m", "6m", "1y", "2y", "3y", "all"]
|
TimePeriod = Literal["24h", "3d", "1w", "1m", "3m", "6m", "1y", "2y", "3y", "all"]
|
||||||
# Index of the output being spent in the previous transaction
|
|
||||||
Vout = int
|
|
||||||
# Transaction witness: a stack of byte arrays, one per witness item.
|
|
||||||
#
|
|
||||||
# Wraps `bitcoin::Witness` (single-buffer layout with offsets, much
|
|
||||||
# more compact than `Vec<Vec<u8>>`). Serializes as a JSON array of
|
|
||||||
# hex strings - the format used by Bitcoin Core REST and mempool.space
|
|
||||||
# and matching brk's `script_sig: ScriptBuf` (bytes internally, hex
|
|
||||||
# on the wire).
|
|
||||||
Witness = List[str]
|
|
||||||
# Chain-wide transaction index (0 = the genesis coinbase). For an
|
|
||||||
# in-block position, use `BlockTxIndex` instead.
|
|
||||||
TxIndex = int
|
|
||||||
# Raw transaction version (i32) from Bitcoin protocol.
|
|
||||||
# Unlike TxVersion (u8, indexed), this preserves non-standard values
|
|
||||||
# used in coinbase txs for miner signaling/branding.
|
|
||||||
TxVersionRaw = int
|
|
||||||
# Hierarchical tree node for organizing series into categories
|
# Hierarchical tree node for organizing series into categories
|
||||||
TreeNode = Union[dict[str, "TreeNode"], "SeriesLeafWithSchema"]
|
TreeNode = Union[dict[str, "TreeNode"], "SeriesLeafWithSchema"]
|
||||||
TxInIndex = int
|
TxInIndex = int
|
||||||
@@ -640,6 +645,142 @@ class BlockStatus(TypedDict):
|
|||||||
height: Union[Height, None]
|
height: Union[Height, None]
|
||||||
next_best: Union[BlockHash, None]
|
next_best: Union[BlockHash, None]
|
||||||
|
|
||||||
|
class MempoolBlock(TypedDict):
|
||||||
|
"""
|
||||||
|
Block info in a mempool.space like format for fee estimation.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
blockSize: Total serialized block size in bytes (witness + non-witness).
|
||||||
|
blockVSize: Total block virtual size in vbytes
|
||||||
|
nTx: Number of transactions in the projected block
|
||||||
|
totalFees: Total fees in satoshis
|
||||||
|
medianFee: Median fee rate in sat/vB
|
||||||
|
feeRange: Fee rate range: [min, 10%, 25%, 50%, 75%, 90%, max]
|
||||||
|
"""
|
||||||
|
blockSize: int
|
||||||
|
blockVSize: float
|
||||||
|
nTx: int
|
||||||
|
totalFees: Sats
|
||||||
|
medianFee: FeeRate
|
||||||
|
feeRange: List[FeeRate]
|
||||||
|
|
||||||
|
class TxOut(TypedDict):
|
||||||
|
"""
|
||||||
|
Transaction output
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
scriptpubkey: Script pubkey (locking script)
|
||||||
|
value: Value of the output in satoshis
|
||||||
|
"""
|
||||||
|
scriptpubkey: str
|
||||||
|
value: Sats
|
||||||
|
|
||||||
|
class TxIn(TypedDict):
|
||||||
|
"""
|
||||||
|
Transaction input
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
txid: Transaction ID of the output being spent
|
||||||
|
vout: Output index being spent (u16: coinbase is 65535, mempool.space uses u32: 4294967295)
|
||||||
|
prevout: Information about the previous output being spent
|
||||||
|
scriptsig: Signature script (hex, for non-SegWit inputs)
|
||||||
|
scriptsig_asm: Signature script in assembly format
|
||||||
|
witness: Witness data (stack items, present for SegWit inputs; hex-encoded on the wire)
|
||||||
|
is_coinbase: Whether this input is a coinbase (block reward) input
|
||||||
|
sequence: Input sequence number
|
||||||
|
inner_redeemscript_asm: Inner redeemscript in assembly (for P2SH-wrapped SegWit: scriptsig + witness both present)
|
||||||
|
inner_witnessscript_asm: Inner witnessscript in assembly (for P2WSH: last witness item decoded as script)
|
||||||
|
"""
|
||||||
|
txid: Txid
|
||||||
|
vout: Vout
|
||||||
|
prevout: Union[TxOut, None]
|
||||||
|
scriptsig: str
|
||||||
|
scriptsig_asm: str
|
||||||
|
witness: Witness
|
||||||
|
is_coinbase: bool
|
||||||
|
sequence: int
|
||||||
|
inner_redeemscript_asm: str
|
||||||
|
inner_witnessscript_asm: str
|
||||||
|
|
||||||
|
class TxStatus(TypedDict):
|
||||||
|
"""
|
||||||
|
Transaction confirmation status
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
confirmed: Whether the transaction is confirmed
|
||||||
|
block_height: Block height (only present if confirmed)
|
||||||
|
block_hash: Block hash (only present if confirmed)
|
||||||
|
block_time: Block timestamp (only present if confirmed)
|
||||||
|
"""
|
||||||
|
confirmed: bool
|
||||||
|
block_height: Union[Height, None]
|
||||||
|
block_hash: Union[BlockHash, None]
|
||||||
|
block_time: Union[Timestamp, None]
|
||||||
|
|
||||||
|
class Transaction(TypedDict):
|
||||||
|
"""
|
||||||
|
Transaction information compatible with mempool.space API format
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
index: Internal transaction index (brk-specific, not in mempool.space)
|
||||||
|
txid: Transaction ID
|
||||||
|
version: Transaction version (raw i32 from Bitcoin protocol, may contain non-standard values in coinbase txs)
|
||||||
|
locktime: Transaction lock time
|
||||||
|
vin: Transaction inputs
|
||||||
|
vout: Transaction outputs
|
||||||
|
size: Transaction size in bytes
|
||||||
|
weight: Transaction weight
|
||||||
|
sigops: Number of signature operations
|
||||||
|
fee: Transaction fee in satoshis
|
||||||
|
status: Confirmation status (confirmed, block height/hash/time)
|
||||||
|
"""
|
||||||
|
index: Union[TxIndex, None]
|
||||||
|
txid: Txid
|
||||||
|
version: TxVersionRaw
|
||||||
|
locktime: RawLockTime
|
||||||
|
vin: List[TxIn]
|
||||||
|
vout: List[TxOut]
|
||||||
|
size: int
|
||||||
|
weight: Weight
|
||||||
|
sigops: SigOps
|
||||||
|
fee: Sats
|
||||||
|
status: TxStatus
|
||||||
|
|
||||||
|
class BlockTemplate(TypedDict):
|
||||||
|
"""
|
||||||
|
Projected next-block contents from Bitcoin Core's `getblocktemplate`
|
||||||
|
(block 0 of the snapshot). Returned by
|
||||||
|
`GET /api/v1/mining/block-template`.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
hash: Pass back as `<hash>` on
|
||||||
|
`/api/v1/mining/block-template/diff/{hash}` to fetch deltas.
|
||||||
|
stats: Aggregate stats for this block (size, vsize, fee range, ...).
|
||||||
|
transactions: Full transaction bodies in `getblocktemplate` order.
|
||||||
|
"""
|
||||||
|
hash: NextBlockHash
|
||||||
|
stats: MempoolBlock
|
||||||
|
transactions: List[Transaction]
|
||||||
|
|
||||||
|
class BlockTemplateDiff(TypedDict):
|
||||||
|
"""
|
||||||
|
Delta between the current `getblocktemplate` projection and a prior
|
||||||
|
one identified by `since`. Returned by
|
||||||
|
`GET /api/v1/mining/block-template/diff/{hash}`.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
hash: Current next-block hash. Use as `since` on the next diff call.
|
||||||
|
since: Echoed prior hash the diff was computed against.
|
||||||
|
added: Full bodies of transactions that joined the projected next
|
||||||
|
block since `since`.
|
||||||
|
removed: Txids that left the projected next block since `since`
|
||||||
|
(confirmed, evicted, replaced, or pushed past block 0).
|
||||||
|
"""
|
||||||
|
hash: NextBlockHash
|
||||||
|
since: NextBlockHash
|
||||||
|
added: List[Transaction]
|
||||||
|
removed: List[Txid]
|
||||||
|
|
||||||
class BlockTimestamp(TypedDict):
|
class BlockTimestamp(TypedDict):
|
||||||
"""
|
"""
|
||||||
Block information returned for timestamp queries
|
Block information returned for timestamp queries
|
||||||
@@ -1022,25 +1163,6 @@ class LegacySeriesWithIndex(TypedDict):
|
|||||||
metric: SeriesName
|
metric: SeriesName
|
||||||
index: Index
|
index: Index
|
||||||
|
|
||||||
class MempoolBlock(TypedDict):
|
|
||||||
"""
|
|
||||||
Block info in a mempool.space like format for fee estimation.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
blockSize: Total serialized block size in bytes (witness + non-witness).
|
|
||||||
blockVSize: Total block virtual size in vbytes
|
|
||||||
nTx: Number of transactions in the projected block
|
|
||||||
totalFees: Total fees in satoshis
|
|
||||||
medianFee: Median fee rate in sat/vB
|
|
||||||
feeRange: Fee rate range: [min, 10%, 25%, 50%, 75%, 90%, max]
|
|
||||||
"""
|
|
||||||
blockSize: int
|
|
||||||
blockVSize: float
|
|
||||||
nTx: int
|
|
||||||
totalFees: Sats
|
|
||||||
medianFee: FeeRate
|
|
||||||
feeRange: List[FeeRate]
|
|
||||||
|
|
||||||
class MempoolInfo(TypedDict):
|
class MempoolInfo(TypedDict):
|
||||||
"""
|
"""
|
||||||
Mempool statistics with incrementally maintained fee histogram.
|
Mempool statistics with incrementally maintained fee histogram.
|
||||||
@@ -1084,6 +1206,12 @@ class MerkleProof(TypedDict):
|
|||||||
merkle: List[str]
|
merkle: List[str]
|
||||||
pos: int
|
pos: int
|
||||||
|
|
||||||
|
class NextBlockHashParam(TypedDict):
|
||||||
|
"""
|
||||||
|
`since` hash for `/api/v1/mining/block-template/diff/{hash}`.
|
||||||
|
"""
|
||||||
|
hash: NextBlockHash
|
||||||
|
|
||||||
class OHLCCents(TypedDict):
|
class OHLCCents(TypedDict):
|
||||||
"""
|
"""
|
||||||
OHLC (Open, High, Low, Close) data in cents
|
OHLC (Open, High, Low, Close) data in cents
|
||||||
@@ -1517,88 +1645,6 @@ class TimestampParam(TypedDict):
|
|||||||
"""
|
"""
|
||||||
timestamp: Timestamp
|
timestamp: Timestamp
|
||||||
|
|
||||||
class TxOut(TypedDict):
|
|
||||||
"""
|
|
||||||
Transaction output
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
scriptpubkey: Script pubkey (locking script)
|
|
||||||
value: Value of the output in satoshis
|
|
||||||
"""
|
|
||||||
scriptpubkey: str
|
|
||||||
value: Sats
|
|
||||||
|
|
||||||
class TxIn(TypedDict):
|
|
||||||
"""
|
|
||||||
Transaction input
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
txid: Transaction ID of the output being spent
|
|
||||||
vout: Output index being spent (u16: coinbase is 65535, mempool.space uses u32: 4294967295)
|
|
||||||
prevout: Information about the previous output being spent
|
|
||||||
scriptsig: Signature script (hex, for non-SegWit inputs)
|
|
||||||
scriptsig_asm: Signature script in assembly format
|
|
||||||
witness: Witness data (stack items, present for SegWit inputs; hex-encoded on the wire)
|
|
||||||
is_coinbase: Whether this input is a coinbase (block reward) input
|
|
||||||
sequence: Input sequence number
|
|
||||||
inner_redeemscript_asm: Inner redeemscript in assembly (for P2SH-wrapped SegWit: scriptsig + witness both present)
|
|
||||||
inner_witnessscript_asm: Inner witnessscript in assembly (for P2WSH: last witness item decoded as script)
|
|
||||||
"""
|
|
||||||
txid: Txid
|
|
||||||
vout: Vout
|
|
||||||
prevout: Union[TxOut, None]
|
|
||||||
scriptsig: str
|
|
||||||
scriptsig_asm: str
|
|
||||||
witness: Witness
|
|
||||||
is_coinbase: bool
|
|
||||||
sequence: int
|
|
||||||
inner_redeemscript_asm: str
|
|
||||||
inner_witnessscript_asm: str
|
|
||||||
|
|
||||||
class TxStatus(TypedDict):
|
|
||||||
"""
|
|
||||||
Transaction confirmation status
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
confirmed: Whether the transaction is confirmed
|
|
||||||
block_height: Block height (only present if confirmed)
|
|
||||||
block_hash: Block hash (only present if confirmed)
|
|
||||||
block_time: Block timestamp (only present if confirmed)
|
|
||||||
"""
|
|
||||||
confirmed: bool
|
|
||||||
block_height: Union[Height, None]
|
|
||||||
block_hash: Union[BlockHash, None]
|
|
||||||
block_time: Union[Timestamp, None]
|
|
||||||
|
|
||||||
class Transaction(TypedDict):
|
|
||||||
"""
|
|
||||||
Transaction information compatible with mempool.space API format
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
index: Internal transaction index (brk-specific, not in mempool.space)
|
|
||||||
txid: Transaction ID
|
|
||||||
version: Transaction version (raw i32 from Bitcoin protocol, may contain non-standard values in coinbase txs)
|
|
||||||
locktime: Transaction lock time
|
|
||||||
vin: Transaction inputs
|
|
||||||
vout: Transaction outputs
|
|
||||||
size: Transaction size in bytes
|
|
||||||
weight: Transaction weight
|
|
||||||
sigops: Number of signature operations
|
|
||||||
fee: Transaction fee in satoshis
|
|
||||||
status: Confirmation status (confirmed, block height/hash/time)
|
|
||||||
"""
|
|
||||||
index: Union[TxIndex, None]
|
|
||||||
txid: Txid
|
|
||||||
version: TxVersionRaw
|
|
||||||
locktime: RawLockTime
|
|
||||||
vin: List[TxIn]
|
|
||||||
vout: List[TxOut]
|
|
||||||
size: int
|
|
||||||
weight: Weight
|
|
||||||
sigops: SigOps
|
|
||||||
fee: Sats
|
|
||||||
status: TxStatus
|
|
||||||
|
|
||||||
class TxIndexParam(TypedDict):
|
class TxIndexParam(TypedDict):
|
||||||
"""
|
"""
|
||||||
Transaction index path parameter
|
Transaction index path parameter
|
||||||
@@ -8482,7 +8528,7 @@ class BrkClient(BrkClientBase):
|
|||||||
def get_mempool_blocks(self) -> List[MempoolBlock]:
|
def get_mempool_blocks(self) -> List[MempoolBlock]:
|
||||||
"""Projected mempool blocks.
|
"""Projected mempool blocks.
|
||||||
|
|
||||||
Get projected blocks from the mempool for fee estimation.
|
Projected blocks for fee estimation. Block 0 reflects Bitcoin Core's actual next-block selection; blocks 1+ are a fee-tier approximation.
|
||||||
|
|
||||||
*[Mempool.space docs](https://mempool.space/docs/api/rest#get-mempool-blocks-fees)*
|
*[Mempool.space docs](https://mempool.space/docs/api/rest#get-mempool-blocks-fees)*
|
||||||
|
|
||||||
@@ -8492,7 +8538,7 @@ class BrkClient(BrkClientBase):
|
|||||||
def get_recommended_fees(self) -> RecommendedFees:
|
def get_recommended_fees(self) -> RecommendedFees:
|
||||||
"""Recommended fees.
|
"""Recommended fees.
|
||||||
|
|
||||||
Get recommended fee rates for different confirmation targets.
|
Recommended fee rates by confirmation target.
|
||||||
|
|
||||||
*[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees)*
|
*[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees)*
|
||||||
|
|
||||||
@@ -8502,7 +8548,7 @@ class BrkClient(BrkClientBase):
|
|||||||
def get_precise_fees(self) -> RecommendedFees:
|
def get_precise_fees(self) -> RecommendedFees:
|
||||||
"""Precise recommended fees.
|
"""Precise recommended fees.
|
||||||
|
|
||||||
Get recommended fee rates with up to 3 decimal places.
|
Recommended fee rates with sub-integer precision.
|
||||||
|
|
||||||
*[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees-precise)*
|
*[Mempool.space docs](https://mempool.space/docs/api/rest#get-recommended-fees-precise)*
|
||||||
|
|
||||||
@@ -8519,10 +8565,10 @@ class BrkClient(BrkClientBase):
|
|||||||
Endpoint: `GET /api/mempool`"""
|
Endpoint: `GET /api/mempool`"""
|
||||||
return self.get_json('/api/mempool')
|
return self.get_json('/api/mempool')
|
||||||
|
|
||||||
def get_mempool_hash(self) -> int:
|
def get_mempool_hash(self) -> NextBlockHash:
|
||||||
"""Mempool content hash.
|
"""Mempool content hash.
|
||||||
|
|
||||||
Returns an opaque `u64` that changes whenever the projected next block changes. Same value as the mempool ETag. Useful as a freshness/liveness signal: if it stays constant for tens of seconds on a live network, the mempool sync loop has stalled.
|
Returns an opaque hash that changes whenever the projected next block changes. Same value as the mempool ETag. Useful as a freshness/liveness signal: if it stays constant for tens of seconds on a live network, the mempool sync loop has stalled.
|
||||||
|
|
||||||
Endpoint: `GET /api/mempool/hash`"""
|
Endpoint: `GET /api/mempool/hash`"""
|
||||||
return self.get_json('/api/mempool/hash')
|
return self.get_json('/api/mempool/hash')
|
||||||
@@ -8567,6 +8613,22 @@ class BrkClient(BrkClientBase):
|
|||||||
Endpoint: `GET /api/v1/fullrbf/replacements`"""
|
Endpoint: `GET /api/v1/fullrbf/replacements`"""
|
||||||
return self.get_json('/api/v1/fullrbf/replacements')
|
return self.get_json('/api/v1/fullrbf/replacements')
|
||||||
|
|
||||||
|
def get_block_template(self) -> BlockTemplate:
|
||||||
|
"""Projected next block template.
|
||||||
|
|
||||||
|
Bitcoin Core's `getblocktemplate` selection: full transaction bodies in GBT order with aggregate stats. The returned `hash` is an opaque content token; pass it as `<hash>` on `/api/v1/mempool/block-template/diff/{hash}` to fetch deltas instead of refetching the whole template.
|
||||||
|
|
||||||
|
Endpoint: `GET /api/v1/mempool/block-template`"""
|
||||||
|
return self.get_json('/api/v1/mempool/block-template')
|
||||||
|
|
||||||
|
def get_block_template_diff(self, hash: NextBlockHash) -> BlockTemplateDiff:
|
||||||
|
"""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`.
|
||||||
|
|
||||||
|
Endpoint: `GET /api/v1/mempool/block-template/diff/{hash}`"""
|
||||||
|
return self.get_json(f'/api/v1/mempool/block-template/diff/{hash}')
|
||||||
|
|
||||||
def get_live_price(self) -> Dollars:
|
def get_live_price(self) -> Dollars:
|
||||||
"""Live BTC/USD price.
|
"""Live BTC/USD price.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user