mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-24 09:28:11 -07:00
global: next block template (+ diff)
This commit is contained in:
@@ -27,7 +27,7 @@ impl FeesRoutes for ApiRouter<AppState> {
|
||||
op.id("get_mempool_blocks")
|
||||
.fees_tag()
|
||||
.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>>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
@@ -48,7 +48,7 @@ impl FeesRoutes for ApiRouter<AppState> {
|
||||
op.id("get_recommended_fees")
|
||||
.fees_tag()
|
||||
.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>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
@@ -69,7 +69,7 @@ impl FeesRoutes for ApiRouter<AppState> {
|
||||
op.id("get_precise_fees")
|
||||
.fees_tag()
|
||||
.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>()
|
||||
.not_modified()
|
||||
.server_error()
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
use aide::axum::{ApiRouter, routing::get_with};
|
||||
use axum::{
|
||||
extract::State,
|
||||
extract::{Path, State},
|
||||
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 {
|
||||
fn add_mempool_routes(self) -> Self;
|
||||
@@ -44,8 +51,8 @@ impl MempoolRoutes for ApiRouter<AppState> {
|
||||
op.id("get_mempool_hash")
|
||||
.mempool_tag()
|
||||
.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.")
|
||||
.json_response::<u64>()
|
||||
.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::<NextBlockHash>()
|
||||
.not_modified()
|
||||
.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/mempool/price",
|
||||
get_with(
|
||||
|
||||
@@ -102,9 +102,11 @@ impl Server {
|
||||
let response_time_layer = axum::middleware::from_fn(
|
||||
async |request: Request<Body>, next: Next| -> Response<Body> {
|
||||
let uri = request.uri().clone();
|
||||
let method = request.method().clone();
|
||||
let start = Instant::now();
|
||||
let mut response = next.run(request).await;
|
||||
response.extensions_mut().insert(uri);
|
||||
response.extensions_mut().insert(method);
|
||||
response.headers_mut().insert(
|
||||
"X-Response-Time",
|
||||
format!("{}us", start.elapsed().as_micros())
|
||||
@@ -182,14 +184,19 @@ impl Server {
|
||||
.on_response(
|
||||
|response: &Response<Body>, latency: Duration, _: &tracing::Span| {
|
||||
let status = response.status().as_u16();
|
||||
let unknown = Uri::from_static("/unknown");
|
||||
let uri = response.extensions().get::<Uri>().unwrap_or(&unknown);
|
||||
let unknown_uri = Uri::from_static("/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() {
|
||||
StatusCode::OK => info!(status, %uri, ?latency),
|
||||
StatusCode::OK => info!(%method, status, %uri, ?latency),
|
||||
StatusCode::NOT_MODIFIED
|
||||
| StatusCode::TEMPORARY_REDIRECT
|
||||
| StatusCode::PERMANENT_REDIRECT => info!(status, %uri, ?latency),
|
||||
_ => error!(status, %uri, ?latency),
|
||||
| StatusCode::PERMANENT_REDIRECT => info!(%method, status, %uri, ?latency),
|
||||
_ => error!(%method, status, %uri, ?latency),
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -209,8 +216,6 @@ impl Server {
|
||||
let router = router
|
||||
.with_state(state)
|
||||
.merge(website_router)
|
||||
.layer(response_time_layer)
|
||||
.layer(trace_layer)
|
||||
.layer(TimeoutLayer::with_status_code(
|
||||
StatusCode::GATEWAY_TIMEOUT,
|
||||
REQUEST_TIMEOUT,
|
||||
@@ -242,7 +247,9 @@ impl Server {
|
||||
.or_else(|| panic.downcast_ref::<&str>().copied())
|
||||
.unwrap_or("Unknown panic");
|
||||
Error::internal(msg).into_response()
|
||||
}));
|
||||
}))
|
||||
.layer(response_time_layer)
|
||||
.layer(trace_layer);
|
||||
|
||||
let (listener, port) = match port {
|
||||
Some(port) => {
|
||||
|
||||
@@ -6,6 +6,7 @@ mod blockhash_start_index;
|
||||
mod blockhash_tx_index;
|
||||
mod empty;
|
||||
mod height_param;
|
||||
mod next_block_hash_param;
|
||||
mod pool_slug_param;
|
||||
mod series_param;
|
||||
mod time_period_param;
|
||||
@@ -25,6 +26,7 @@ pub use blockhash_start_index::*;
|
||||
pub use blockhash_tx_index::*;
|
||||
pub use empty::*;
|
||||
pub use height_param::*;
|
||||
pub use next_block_hash_param::*;
|
||||
pub use pool_slug_param::*;
|
||||
pub use series_param::*;
|
||||
pub use time_period_param::*;
|
||||
|
||||
@@ -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()
|
||||
&& 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)
|
||||
&& let Ok(block_hash) = q.block_hash_by_height(height)
|
||||
@@ -136,7 +136,7 @@ impl AppState {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user