server: cache fixes

This commit is contained in:
nym21
2026-04-05 22:43:30 +02:00
parent 2b15a24b6d
commit acd3d6f425
15 changed files with 156 additions and 48 deletions
@@ -30,7 +30,7 @@ impl AddrRoutes for ApiRouter<AppState> {
Path(path): Path<AddrParam>,
State(state): State<AppState>
| {
let strategy = state.addr_cache(Version::ONE, &path.addr);
let strategy = state.addr_cache(Version::ONE, &path.addr, false);
state.cached_json(&headers, strategy, &uri, move |q| q.addr(path.addr)).await
}, |op| op
.id("get_address")
@@ -53,7 +53,7 @@ impl AddrRoutes for ApiRouter<AppState> {
Query(params): Query<AddrTxidsParam>,
State(state): State<AppState>
| {
let strategy = state.addr_cache(Version::ONE, &path.addr);
let strategy = state.addr_cache(Version::ONE, &path.addr, false);
state.cached_json(&headers, strategy, &uri, move |q| q.addr_txs(path.addr, params.after_txid, 50)).await
}, |op| op
.id("get_address_txs")
@@ -76,7 +76,7 @@ impl AddrRoutes for ApiRouter<AppState> {
Query(params): Query<AddrTxidsParam>,
State(state): State<AppState>
| {
let strategy = state.addr_cache(Version::ONE, &path.addr);
let strategy = state.addr_cache(Version::ONE, &path.addr, true);
state.cached_json(&headers, strategy, &uri, move |q| q.addr_txs(path.addr, params.after_txid, 25)).await
}, |op| op
.id("get_address_confirmed_txs")
@@ -119,7 +119,7 @@ impl AddrRoutes for ApiRouter<AppState> {
Path(path): Path<AddrParam>,
State(state): State<AppState>
| {
let strategy = state.addr_cache(Version::ONE, &path.addr);
let strategy = state.addr_cache(Version::ONE, &path.addr, false);
state.cached_json(&headers, strategy, &uri, move |q| q.addr_utxos(path.addr)).await
}, |op| op
.id("get_address_utxos")
@@ -117,7 +117,7 @@ impl BlockRoutes for ApiRouter<AppState> {
headers: HeaderMap,
Path(path): Path<TimestampParam>,
State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.block_by_timestamp(path.timestamp)).await
state.cached_json(&headers, state.timestamp_cache(Version::ONE, path.timestamp), &uri, move |q| q.block_by_timestamp(path.timestamp)).await
},
|op| {
op.id("get_block_by_timestamp")
@@ -3,7 +3,7 @@ use axum::{
extract::{Query, State},
http::{HeaderMap, Uri},
};
use brk_types::{DifficultyAdjustment, HistoricalPrice, Prices, Timestamp};
use brk_types::{DifficultyAdjustment, HistoricalPrice, Prices, Timestamp, Version};
use crate::{
AppState, CacheStrategy, extended::TransformResponseExtended, params::OptionalTimestampParam,
@@ -66,8 +66,12 @@ impl GeneralRoutes for ApiRouter<AppState> {
headers: HeaderMap,
Query(params): Query<OptionalTimestampParam>,
State(state): State<AppState>| {
let strategy = params
.timestamp
.map(|ts| state.timestamp_cache(Version::ONE, ts))
.unwrap_or(CacheStrategy::Tip);
state
.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| {
.cached_json(&headers, strategy, &uri, move |q| {
q.historical_price(params.timestamp)
})
.await
@@ -8,7 +8,7 @@ use axum::{
use brk_types::{
BlockFeeRatesEntry, BlockFeesEntry, BlockInfoV1, BlockRewardsEntry, BlockSizesWeights,
DifficultyAdjustmentEntry, HashrateSummary, PoolDetail, PoolHashrateEntry, PoolInfo,
PoolsSummary, RewardStats,
PoolsSummary, RewardStats, Version,
};
use crate::{
@@ -154,7 +154,7 @@ impl MiningRoutes for ApiRouter<AppState> {
"/api/v1/mining/pool/{slug}/blocks/{height}",
get_with(
async |uri: Uri, headers: HeaderMap, Path(PoolSlugAndHeightParam {slug, height}): Path<PoolSlugAndHeightParam>, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.pool_blocks(slug, Some(height))).await
state.cached_json(&headers, state.height_cache(Version::ONE, height), &uri, move |q| q.pool_blocks(slug, Some(height))).await
},
|op| {
op.id("get_pool_blocks_from")
@@ -10,7 +10,8 @@ use brk_types::{CpfpInfo, MerkleProof, Transaction, TxOutspend, TxStatus, Txid,
use crate::{
AppState, CacheStrategy,
extended::TransformResponseExtended,
cache::CacheParams,
extended::{ResponseExtended, TransformResponseExtended},
params::{TxidParam, TxidVout, TxidsParam},
};
@@ -132,7 +133,16 @@ impl TxRoutes for ApiRouter<AppState> {
Path(path): Path<TxidVout>,
State(state): State<AppState>
| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.outspend(&path.txid, path.vout)).await
let v = Version::ONE;
let immutable = CacheParams::immutable(v);
if immutable.matches_etag(&headers) {
return ResponseExtended::new_not_modified_with(&immutable);
}
let outspend = state.run(move |q| q.outspend(&path.txid, path.vout)).await;
let height = state.sync(|q| q.height());
let is_deep = outspend.as_ref().is_ok_and(|o| o.is_deeply_spent(height));
let strategy = if is_deep { CacheStrategy::Immutable(v) } else { CacheStrategy::Tip };
state.cached_json(&headers, strategy, &uri, move |_| outspend).await
},
|op| op
.id("get_tx_outspend")
@@ -157,7 +167,16 @@ impl TxRoutes for ApiRouter<AppState> {
Path(param): Path<TxidParam>,
State(state): State<AppState>
| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.outspends(&param.txid)).await
let v = Version::ONE;
let immutable = CacheParams::immutable(v);
if immutable.matches_etag(&headers) {
return ResponseExtended::new_not_modified_with(&immutable);
}
let outspends = state.run(move |q| q.outspends(&param.txid)).await;
let height = state.sync(|q| q.height());
let all_deep = outspends.as_ref().is_ok_and(|os| os.iter().all(|o| o.is_deeply_spent(height)));
let strategy = if all_deep { CacheStrategy::Immutable(v) } else { CacheStrategy::Tip };
state.cached_json(&headers, strategy, &uri, move |_| outspends).await
},
|op| op
.id("get_tx_outspends")
@@ -237,7 +256,8 @@ impl TxRoutes for ApiRouter<AppState> {
post_with(
async |State(state): State<AppState>, body: String| {
let hex = body.trim().to_string();
state.sync(|q| q.broadcast_transaction(&hex))
state.run(move |q| q.broadcast_transaction(&hex))
.await
.map(|txid| txid.to_string())
.map_err(crate::Error::from)
},
+6 -7
View File
@@ -6,7 +6,7 @@ use aide::{
};
use axum::{
Extension,
http::{HeaderMap, header},
http::HeaderMap,
response::{Html, Redirect, Response},
routing::get,
};
@@ -78,13 +78,12 @@ impl ApiRoutes for ApiRouter<AppState> {
)
.route("/api", get(Html::from(include_str!("./scalar.html"))))
// Pre-compressed with: brotli -c -q 11 scalar.js > scalar.js.br
.route("/scalar.js", get(|| async {
(
[
(header::CONTENT_TYPE, "application/javascript"),
(header::CONTENT_ENCODING, "br"),
],
.route("/scalar.js", get(|headers: HeaderMap| async move {
Response::static_bytes(
&headers,
include_bytes!("./scalar.js.br").as_slice(),
"application/javascript",
"br",
)
}))
.route(
+4 -4
View File
@@ -4,15 +4,15 @@ use axum::{
Extension,
body::{Body, Bytes},
extract::{Query, State},
http::{HeaderMap, StatusCode, Uri},
response::{IntoResponse, Response},
http::{HeaderMap, Uri},
response::Response,
};
use brk_types::{Format, Output, SeriesSelection};
use crate::{
Result,
api::series::{CACHE_CONTROL, max_weight},
extended::{ContentEncoding, HeaderMapExtended},
extended::{ContentEncoding, HeaderMapExtended, ResponseExtended},
};
use super::AppState;
@@ -34,7 +34,7 @@ pub async fn handler(
let csv_filename = resolved.csv_filename();
if headers.has_etag(etag.as_str()) {
return Ok((StatusCode::NOT_MODIFIED, "").into_response());
return Ok(Response::new_not_modified(&etag, CACHE_CONTROL));
}
// Phase 2: Format (expensive, server-side cached)
+4 -4
View File
@@ -4,8 +4,8 @@ use axum::{
Extension,
body::{Body, Bytes},
extract::{Query, State},
http::{HeaderMap, StatusCode, Uri},
response::{IntoResponse, Response},
http::{HeaderMap, Uri},
response::Response,
};
use brk_error::Result as BrkResult;
use brk_query::{Query as BrkQuery, ResolvedQuery};
@@ -14,7 +14,7 @@ use brk_types::{Format, Output, SeriesOutput, SeriesSelection};
use crate::{
Result,
api::series::{CACHE_CONTROL, max_weight},
extended::{ContentEncoding, HeaderMapExtended},
extended::{ContentEncoding, HeaderMapExtended, ResponseExtended},
};
use super::AppState;
@@ -57,7 +57,7 @@ async fn format_and_respond(
let csv_filename = resolved.csv_filename();
if headers.has_etag(etag.as_str()) {
return Ok((StatusCode::NOT_MODIFIED, "").into_response());
return Ok(Response::new_not_modified(&etag, CACHE_CONTROL));
}
// Phase 2: Format (expensive, server-side cached)
+4 -4
View File
@@ -4,15 +4,15 @@ use axum::{
Extension,
body::{Body, Bytes},
extract::{Query, State},
http::{HeaderMap, StatusCode, Uri},
response::{IntoResponse, Response},
http::{HeaderMap, Uri},
response::Response,
};
use brk_types::{Format, OutputLegacy, SeriesSelection};
use crate::{
Result,
api::series::{CACHE_CONTROL, max_weight},
extended::{ContentEncoding, HeaderMapExtended},
extended::{ContentEncoding, HeaderMapExtended, ResponseExtended},
};
pub const SUNSET: &str = "2027-01-01T00:00:00Z";
@@ -36,7 +36,7 @@ pub async fn handler(
let csv_filename = resolved.csv_filename();
if headers.has_etag(etag.as_str()) {
return Ok((StatusCode::NOT_MODIFIED, "").into_response());
return Ok(Response::new_not_modified(&etag, CACHE_CONTROL));
}
// Phase 2: Format (expensive, server-side cached)