global: snapshot

This commit is contained in:
nym21
2025-12-13 10:52:00 +01:00
parent 3526a177fc
commit 158b0254ed
73 changed files with 2230 additions and 195 deletions

View File

@@ -1,11 +1,11 @@
use aide::axum::{ApiRouter, routing::get_with};
use axum::{
extract::{Path, State},
extract::{Path, Query, State},
http::HeaderMap,
response::{Redirect, Response},
routing::get,
};
use brk_types::{Address, AddressStats};
use brk_types::{Address, AddressStats, AddressTxidsParam, Txid, Utxo};
use crate::{
VERSION,
@@ -46,5 +46,52 @@ impl AddressRoutes for ApiRouter<AppState> {
.server_error()
),
)
.api_route(
"/api/address/{address}/txs",
get_with(async |
headers: HeaderMap,
Path(address): Path<Address>,
Query(params): Query<AddressTxidsParam>,
State(state): State<AppState>
| {
let etag = format!("{VERSION}-{}", state.get_height().await);
if headers.has_etag(&etag) {
return Response::new_not_modified();
}
state.get_address_txids(address, params.after_txid, params.limit).await.to_json_response(&etag)
}, |op| op
.addresses_tag()
.summary("Address transaction IDs")
.description("Get transaction IDs for an address, newest first. Use after_txid for pagination.")
.ok_response::<Vec<Txid>>()
.not_modified()
.bad_request()
.not_found()
.server_error()
),
)
.api_route(
"/api/address/{address}/utxo",
get_with(async |
headers: HeaderMap,
Path(address): Path<Address>,
State(state): State<AppState>
| {
let etag = format!("{VERSION}-{}", state.get_height().await);
if headers.has_etag(&etag) {
return Response::new_not_modified();
}
state.get_address_utxos(address).await.to_json_response(&etag)
}, |op| op
.addresses_tag()
.summary("Address UTXOs")
.description("Get unspent transaction outputs for an address.")
.ok_response::<Vec<Utxo>>()
.not_modified()
.bad_request()
.not_found()
.server_error()
),
)
}
}

View File

@@ -0,0 +1,165 @@
use aide::axum::{ApiRouter, routing::get_with};
use axum::{
extract::{Path, State},
http::HeaderMap,
response::{Redirect, Response},
routing::get,
};
use brk_types::{BlockHashPath, BlockInfo, BlockStatus, Height, HeightPath, StartHeightPath, Txid};
use crate::{
VERSION,
extended::{HeaderMapExtended, ResponseExtended, ResultExtended, TransformResponseExtended},
};
use super::AppState;
pub trait BlockRoutes {
fn add_block_routes(self) -> Self;
}
impl BlockRoutes for ApiRouter<AppState> {
fn add_block_routes(self) -> Self {
self.route("/api/block", get(Redirect::temporary("/api/blocks")))
.route(
"/api/blocks",
get(Redirect::temporary("/api#tag/blocks")),
)
.api_route(
"/api/block/{hash}",
get_with(
async |headers: HeaderMap,
Path(path): Path<BlockHashPath>,
State(state): State<AppState>| {
let etag = format!("{VERSION}-{}", state.get_height().await);
if headers.has_etag(&etag) {
return Response::new_not_modified();
}
state.get_block(path.hash).await.to_json_response(&etag)
},
|op| {
op.blocks_tag()
.summary("Block information")
.description(
"Retrieve block information by block hash. Returns block metadata including height, timestamp, difficulty, size, weight, and transaction count.",
)
.ok_response::<BlockInfo>()
.not_modified()
.bad_request()
.not_found()
.server_error()
},
),
)
.api_route(
"/api/block/{hash}/status",
get_with(
async |headers: HeaderMap,
Path(path): Path<BlockHashPath>,
State(state): State<AppState>| {
let etag = format!("{VERSION}-{}", state.get_height().await);
if headers.has_etag(&etag) {
return Response::new_not_modified();
}
state
.get_block_status(path.hash)
.await
.to_json_response(&etag)
},
|op| {
op.blocks_tag()
.summary("Block status")
.description(
"Retrieve the status of a block. Returns whether the block is in the best chain and, if so, its height and the hash of the next block.",
)
.ok_response::<BlockStatus>()
.not_modified()
.bad_request()
.not_found()
.server_error()
},
),
)
.api_route(
"/api/block-height/{height}",
get_with(
async |headers: HeaderMap,
Path(path): Path<HeightPath>,
State(state): State<AppState>| {
let etag = format!("{VERSION}-{}", state.get_height().await);
if headers.has_etag(&etag) {
return Response::new_not_modified();
}
state
.get_block_by_height(Height::from(path.height))
.await
.to_json_response(&etag)
},
|op| {
op.blocks_tag()
.summary("Block by height")
.description(
"Retrieve block information by block height. Returns block metadata including hash, timestamp, difficulty, size, weight, and transaction count.",
)
.ok_response::<BlockInfo>()
.not_modified()
.bad_request()
.not_found()
.server_error()
},
),
)
.api_route(
"/api/blocks/{start_height}",
get_with(
async |headers: HeaderMap,
Path(path): Path<StartHeightPath>,
State(state): State<AppState>| {
let etag = format!("{VERSION}-{}", state.get_height().await);
if headers.has_etag(&etag) {
return Response::new_not_modified();
}
let start_height = path.start_height.map(Height::from);
state.get_blocks(start_height).await.to_json_response(&etag)
},
|op| {
op.blocks_tag()
.summary("Recent blocks")
.description(
"Retrieve the last 10 blocks, optionally starting from a specific height. Returns block metadata for each block.",
)
.ok_response::<Vec<BlockInfo>>()
.not_modified()
.bad_request()
.server_error()
},
),
)
.api_route(
"/api/block/{hash}/txids",
get_with(
async |headers: HeaderMap,
Path(path): Path<BlockHashPath>,
State(state): State<AppState>| {
let etag = format!("{VERSION}-{}", state.get_height().await);
if headers.has_etag(&etag) {
return Response::new_not_modified();
}
state.get_block_txids(path.hash).await.to_json_response(&etag)
},
|op| {
op.blocks_tag()
.summary("Block transaction IDs")
.description(
"Retrieve all transaction IDs in a block by block hash.",
)
.ok_response::<Vec<Txid>>()
.not_modified()
.bad_request()
.not_found()
.server_error()
},
),
)
}
}

View File

@@ -0,0 +1,66 @@
use aide::axum::{ApiRouter, routing::get_with};
use axum::{
extract::State,
http::HeaderMap,
response::{Redirect, Response},
routing::get,
};
use brk_types::{MempoolInfo, Txid};
use crate::{
VERSION,
extended::{HeaderMapExtended, ResponseExtended, ResultExtended, TransformResponseExtended},
};
use super::AppState;
pub trait MempoolRoutes {
fn add_mempool_routes(self) -> Self;
}
impl MempoolRoutes for ApiRouter<AppState> {
fn add_mempool_routes(self) -> Self {
self
.route("/api/mempool", get(Redirect::temporary("/api#tag/mempool")))
.api_route(
"/api/mempool/info",
get_with(
async |headers: HeaderMap, State(state): State<AppState>| {
let etag = format!("{VERSION}-{}", state.get_height().await);
if headers.has_etag(&etag) {
return Response::new_not_modified();
}
state.get_mempool_info().await.to_json_response(&etag)
},
|op| {
op.mempool_tag()
.summary("Mempool statistics")
.description("Get current mempool statistics including transaction count, total vsize, and total fees.")
.ok_response::<MempoolInfo>()
.not_modified()
.server_error()
},
),
)
.api_route(
"/api/mempool/txids",
get_with(
async |headers: HeaderMap, State(state): State<AppState>| {
let etag = format!("{VERSION}-{}", state.get_height().await);
if headers.has_etag(&etag) {
return Response::new_not_modified();
}
state.get_mempool_txids().await.to_json_response(&etag)
},
|op| {
op.mempool_tag()
.summary("Mempool transaction IDs")
.description("Get all transaction IDs currently in the mempool.")
.ok_response::<Vec<Txid>>()
.not_modified()
.server_error()
},
),
)
}
}

View File

@@ -14,13 +14,18 @@ use brk_types::Health;
use crate::{
VERSION,
api::{addresses::AddressRoutes, metrics::ApiMetricsRoutes, transactions::TxRoutes},
api::{
addresses::AddressRoutes, blocks::BlockRoutes, mempool::MempoolRoutes,
metrics::ApiMetricsRoutes, transactions::TxRoutes,
},
extended::{HeaderMapExtended, ResponseExtended, TransformResponseExtended},
};
use super::AppState;
mod addresses;
mod blocks;
mod mempool;
mod metrics;
mod openapi;
mod transactions;
@@ -34,6 +39,8 @@ pub trait ApiRoutes {
impl ApiRoutes for ApiRouter<AppState> {
fn add_api_routes(self) -> Self {
self.add_addresses_routes()
.add_block_routes()
.add_mempool_routes()
.add_tx_routes()
.add_metrics_routes()
.route("/api/server", get(Redirect::temporary("/api#tag/server")))

View File

@@ -5,7 +5,7 @@ use axum::{
response::{Redirect, Response},
routing::get,
};
use brk_types::{Transaction, TxidPath};
use brk_types::{Transaction, TxStatus, TxidPath};
use crate::{
VERSION,
@@ -50,5 +50,59 @@ impl TxRoutes for ApiRouter<AppState> {
.server_error(),
),
)
.api_route(
"/api/tx/{txid}/status",
get_with(
async |
headers: HeaderMap,
Path(txid): Path<TxidPath>,
State(state): State<AppState>
| {
let etag = format!("{VERSION}-{}", state.get_height().await);
if headers.has_etag(&etag) {
return Response::new_not_modified();
}
state.get_transaction_status(txid).await.to_json_response(&etag)
},
|op| op
.transactions_tag()
.summary("Transaction status")
.description(
"Retrieve the confirmation status of a transaction. Returns whether the transaction is confirmed and, if so, the block height, hash, and timestamp.",
)
.ok_response::<TxStatus>()
.not_modified()
.bad_request()
.not_found()
.server_error(),
),
)
.api_route(
"/api/tx/{txid}/hex",
get_with(
async |
headers: HeaderMap,
Path(txid): Path<TxidPath>,
State(state): State<AppState>
| {
let etag = format!("{VERSION}-{}", state.get_height().await);
if headers.has_etag(&etag) {
return Response::new_not_modified();
}
state.get_transaction_hex(txid).await.to_text_response(&etag)
},
|op| op
.transactions_tag()
.summary("Transaction hex")
.description(
"Retrieve the raw transaction as a hex-encoded string. Returns the serialized transaction in hexadecimal format.",
)
.ok_response::<String>()
.not_modified()
.bad_request()
.not_found()
.server_error(),
),
)
}
}