mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-30 14:08:34 -07:00
crates: snapshot
This commit is contained in:
@@ -11,6 +11,14 @@ use crate::{
|
||||
params::{AddrAfterTxidParam, AddrParam, Empty, ValidateAddrParam},
|
||||
};
|
||||
|
||||
/// Esplora `/txs` and `/txs/chain` page sizes. Wire-protocol constants from
|
||||
/// mempool.space/esplora, not deployment policy. `/txs` returns up to
|
||||
/// `MEMPOOL_PAGE` mempool entries plus a chain page sized to reach
|
||||
/// `TXS_TOTAL_TARGET` total, floored at `CHAIN_PAGE`.
|
||||
const MEMPOOL_PAGE: usize = 50;
|
||||
const CHAIN_PAGE: usize = 25;
|
||||
const TXS_TOTAL_TARGET: usize = 50;
|
||||
|
||||
pub trait AddrRoutes {
|
||||
fn add_addr_routes(self) -> Self;
|
||||
}
|
||||
@@ -26,7 +34,7 @@ impl AddrRoutes for ApiRouter<AppState> {
|
||||
_: Empty,
|
||||
State(state): State<AppState>
|
||||
| {
|
||||
let strategy = state.addr_strategy(Version::ONE, &path.addr, false);
|
||||
let strategy = state.addr_strategy(Version::ONE, &path.addr, false, None);
|
||||
state.respond_json(&headers, strategy, &uri, move |q| q.addr(path.addr)).await
|
||||
}, |op| op
|
||||
.id("get_address")
|
||||
@@ -49,13 +57,24 @@ impl AddrRoutes for ApiRouter<AppState> {
|
||||
_: Empty,
|
||||
State(state): State<AppState>
|
||||
| {
|
||||
let strategy = state.addr_strategy(Version::ONE, &path.addr, false);
|
||||
state.respond_json(&headers, strategy, &uri, move |q| q.addr_txs(path.addr, 50, 50)).await
|
||||
let strategy = state.addr_strategy(Version::ONE, &path.addr, false, None);
|
||||
state.respond_json(&headers, strategy, &uri, move |q| {
|
||||
let mempool_txs = if q.mempool().is_some() {
|
||||
q.addr_mempool_txs(&path.addr, MEMPOOL_PAGE)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let chain_limit = TXS_TOTAL_TARGET.saturating_sub(mempool_txs.len()).max(CHAIN_PAGE);
|
||||
let chain_txs = q.addr_txs_chain(&path.addr, None, chain_limit)?;
|
||||
let mut out = mempool_txs;
|
||||
out.extend(chain_txs);
|
||||
Ok(out)
|
||||
}).await
|
||||
}, |op| op
|
||||
.id("get_address_txs")
|
||||
.addrs_tag()
|
||||
.summary("Address transactions")
|
||||
.description("Get transaction history for an address, sorted with newest first. Returns up to 50 entries: mempool transactions first, then confirmed transactions filling the remainder. To paginate further confirmed transactions, use `/address/{address}/txs/chain/{last_seen_txid}`.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-address-transactions)*")
|
||||
.description("Get transaction history for an address, newest first. Returns up to 50 mempool transactions plus a confirmed page sized to fill the response to 50 total (chain floor of 25, so 25-50 confirmed depending on mempool weight). To paginate further confirmed history, use `/address/{address}/txs/chain/{last_seen_txid}`.\n\n*[Mempool.space docs](https://mempool.space/docs/api/rest#get-address-transactions)*")
|
||||
.json_response::<Vec<Transaction>>()
|
||||
.not_modified()
|
||||
.bad_request()
|
||||
@@ -72,8 +91,8 @@ impl AddrRoutes for ApiRouter<AppState> {
|
||||
_: Empty,
|
||||
State(state): State<AppState>
|
||||
| {
|
||||
let strategy = state.addr_strategy(Version::ONE, &path.addr, true);
|
||||
state.respond_json(&headers, strategy, &uri, move |q| q.addr_txs_chain(&path.addr, None, 25)).await
|
||||
let strategy = state.addr_strategy(Version::ONE, &path.addr, true, None);
|
||||
state.respond_json(&headers, strategy, &uri, move |q| q.addr_txs_chain(&path.addr, None, CHAIN_PAGE)).await
|
||||
}, |op| op
|
||||
.id("get_address_confirmed_txs")
|
||||
.addrs_tag()
|
||||
@@ -95,8 +114,8 @@ impl AddrRoutes for ApiRouter<AppState> {
|
||||
_: Empty,
|
||||
State(state): State<AppState>
|
||||
| {
|
||||
let strategy = state.addr_strategy(Version::ONE, &path.addr, true);
|
||||
state.respond_json(&headers, strategy, &uri, move |q| q.addr_txs_chain(&path.addr, Some(path.after_txid), 25)).await
|
||||
let strategy = state.addr_strategy(Version::ONE, &path.addr, true, Some(&path.after_txid));
|
||||
state.respond_json(&headers, strategy, &uri, move |q| q.addr_txs_chain(&path.addr, Some(path.after_txid), CHAIN_PAGE)).await
|
||||
}, |op| op
|
||||
.id("get_address_confirmed_txs_after")
|
||||
.addrs_tag()
|
||||
@@ -119,7 +138,7 @@ impl AddrRoutes for ApiRouter<AppState> {
|
||||
State(state): State<AppState>
|
||||
| {
|
||||
let hash = state.sync(|q| q.addr_mempool_hash(&path.addr)).unwrap_or(0);
|
||||
state.respond_json(&headers, CacheStrategy::MempoolHash(hash), &uri, move |q| q.addr_mempool_txs(&path.addr, 50)).await
|
||||
state.respond_json(&headers, CacheStrategy::MempoolHash(hash), &uri, move |q| q.addr_mempool_txs(&path.addr, MEMPOOL_PAGE)).await
|
||||
}, |op| op
|
||||
.id("get_address_mempool_txs")
|
||||
.addrs_tag()
|
||||
@@ -141,7 +160,7 @@ impl AddrRoutes for ApiRouter<AppState> {
|
||||
_: Empty,
|
||||
State(state): State<AppState>
|
||||
| {
|
||||
let strategy = state.addr_strategy(Version::ONE, &path.addr, false);
|
||||
let strategy = state.addr_strategy(Version::ONE, &path.addr, false, None);
|
||||
let max_utxos = state.max_utxos;
|
||||
state.respond_json(&headers, strategy, &uri, move |q| q.addr_utxos(path.addr, max_utxos)).await
|
||||
}, |op| op
|
||||
|
||||
@@ -135,7 +135,8 @@ impl MiningRoutes for ApiRouter<AppState> {
|
||||
"/api/v1/mining/pool/{slug}/blocks",
|
||||
get_with(
|
||||
async |uri: Uri, headers: HeaderMap, Path(path): Path<PoolSlugParam>, _: Empty, State(state): State<AppState>| {
|
||||
state.respond_json(&headers, CacheStrategy::Tip, &uri, move |q| q.pool_blocks(path.slug, None, POOL_BLOCKS_LIMIT)).await
|
||||
let strategy = state.pool_blocks_strategy(Version::ONE, path.slug);
|
||||
state.respond_json(&headers, strategy, &uri, move |q| q.pool_blocks(path.slug, None, POOL_BLOCKS_LIMIT)).await
|
||||
},
|
||||
|op| {
|
||||
op.id("get_pool_blocks")
|
||||
|
||||
@@ -29,13 +29,7 @@ impl ServerRoutes for ApiRouter<AppState> {
|
||||
let uptime = state.started_instant.elapsed();
|
||||
let started_at = state.started_at.to_string();
|
||||
let sync = state
|
||||
.run(move |q| {
|
||||
let tip_height = q
|
||||
.client()
|
||||
.get_last_height()
|
||||
.unwrap_or(q.height());
|
||||
q.sync_status(tip_height)
|
||||
})
|
||||
.run(move |q| q.sync_status(q.height()))
|
||||
.await
|
||||
.expect("health sync task panicked");
|
||||
let mut response = axum::Json(Health {
|
||||
@@ -57,7 +51,7 @@ impl ServerRoutes for ApiRouter<AppState> {
|
||||
op.id("get_health")
|
||||
.server_tag()
|
||||
.summary("Health check")
|
||||
.description("Returns the health status of the API server, including uptime information.")
|
||||
.description("Liveness probe. Returns server identity, uptime, and indexed/computed heights from local state only (no bitcoind round-trip). For real chain-tip catch-up, see `/api/server/sync`.")
|
||||
.json_response::<Health>()
|
||||
},
|
||||
),
|
||||
|
||||
@@ -6,13 +6,13 @@ use axum::{
|
||||
};
|
||||
use brk_query::AsyncQuery;
|
||||
use brk_types::{
|
||||
Addr, BlockHash, BlockHashPrefix, Date, Height, ONE_HOUR_IN_SEC, Timestamp as BrkTimestamp,
|
||||
Txid, Version,
|
||||
Addr, BlockHash, BlockHashPrefix, Date, Height, ONE_HOUR_IN_SEC, PoolSlug,
|
||||
Timestamp as BrkTimestamp, Txid, Version,
|
||||
};
|
||||
use derive_more::Deref;
|
||||
use jiff::Timestamp;
|
||||
use serde::Serialize;
|
||||
use vecdb::ReadableVec;
|
||||
use vecdb::{ReadableVec, VecIndex};
|
||||
|
||||
use crate::{CacheParams, CacheStrategy, Error, Website, extended::ResponseExtended};
|
||||
|
||||
@@ -70,16 +70,26 @@ impl AppState {
|
||||
})
|
||||
}
|
||||
|
||||
/// Smart address caching: checks mempool activity first (unless `chain_only`), then on-chain.
|
||||
/// - Address has mempool txs → `MempoolHash(addr_specific_hash)`
|
||||
/// - No mempool, has on-chain activity → `BlockBound(last_activity_block)`
|
||||
/// - Unknown address → `Tip`
|
||||
pub fn addr_strategy(&self, version: Version, addr: &Addr, chain_only: bool) -> CacheStrategy {
|
||||
/// Smart address caching. Checks mempool activity first (unless `chain_only`), then on-chain.
|
||||
/// - Address has mempool txs: `MempoolHash(addr_specific_hash)`
|
||||
/// - No mempool, has on-chain activity: `BlockBound(last_activity_block)`
|
||||
/// - Unknown address: `Tip`
|
||||
///
|
||||
/// `before_txid` narrows the on-chain branch to the newest activity strictly
|
||||
/// older than the cursor, so paginated chain pages stay cacheable when newer
|
||||
/// activity arrives above the cursor.
|
||||
pub fn addr_strategy(
|
||||
&self,
|
||||
version: Version,
|
||||
addr: &Addr,
|
||||
chain_only: bool,
|
||||
before_txid: Option<&Txid>,
|
||||
) -> CacheStrategy {
|
||||
self.sync(|q| {
|
||||
if !chain_only && let Some(mempool_hash) = q.addr_mempool_hash(addr) {
|
||||
return CacheStrategy::MempoolHash(mempool_hash);
|
||||
}
|
||||
q.addr_last_activity_height(addr)
|
||||
q.addr_last_activity_height(addr, before_txid)
|
||||
.and_then(|h| {
|
||||
let block_hash = q.block_hash_by_height(h)?;
|
||||
Ok(CacheStrategy::BlockBound(
|
||||
@@ -135,6 +145,29 @@ impl AppState {
|
||||
})
|
||||
}
|
||||
|
||||
/// `BlockBound` on the pool's last-mined block hash, `Tip` if the pool has
|
||||
/// never mined. Lets the no-cursor pool-blocks page stay cached when *other*
|
||||
/// pools mine; only invalidates when this pool itself mines.
|
||||
pub fn pool_blocks_strategy(&self, version: Version, slug: PoolSlug) -> CacheStrategy {
|
||||
self.sync(|q| {
|
||||
let tip = q.height().to_usize();
|
||||
let last = q
|
||||
.computer()
|
||||
.pools
|
||||
.pool_heights
|
||||
.read()
|
||||
.get(&slug)
|
||||
.and_then(|heights| {
|
||||
let pos = heights.partition_point(|h| h.to_usize() <= tip);
|
||||
pos.checked_sub(1).map(|i| heights[i])
|
||||
});
|
||||
match last.and_then(|h| q.block_hash_by_height(h).ok()) {
|
||||
Some(hash) => CacheStrategy::BlockBound(version, BlockHashPrefix::from(&hash)),
|
||||
None => CacheStrategy::Tip,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn mempool_strategy(&self) -> CacheStrategy {
|
||||
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