global: fixes

This commit is contained in:
nym21
2026-04-27 12:52:02 +02:00
parent b24bfdc15c
commit 76869ed2b6
114 changed files with 6623 additions and 1981 deletions

View File

@@ -126,7 +126,11 @@ pub fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
} }
if endpoint.supports_csv { if endpoint.supports_csv {
writeln!(output, " if (format === 'csv') return this.getText(path, {{ signal, onUpdate }});").unwrap(); writeln!(
output,
" if (format === 'csv') return this.getText(path, {{ signal, onUpdate }});"
)
.unwrap();
} }
writeln!(output, " return {};", fetch_call).unwrap(); writeln!(output, " return {};", fetch_call).unwrap();

File diff suppressed because it is too large Load Diff

View File

@@ -89,21 +89,17 @@ impl<T> ByType<T> {
} }
pub fn iter_typed(&self) -> impl Iterator<Item = (OutputType, &T)> { pub fn iter_typed(&self) -> impl Iterator<Item = (OutputType, &T)> {
self.spendable self.spendable.iter_typed().chain(std::iter::once((
.iter_typed() OutputType::OpReturn,
.chain(std::iter::once(( &self.unspendable.op_return,
OutputType::OpReturn, )))
&self.unspendable.op_return,
)))
} }
pub fn iter_typed_mut(&mut self) -> impl Iterator<Item = (OutputType, &mut T)> { pub fn iter_typed_mut(&mut self) -> impl Iterator<Item = (OutputType, &mut T)> {
self.spendable self.spendable.iter_typed_mut().chain(std::iter::once((
.iter_typed_mut() OutputType::OpReturn,
.chain(std::iter::once(( &mut self.unspendable.op_return,
OutputType::OpReturn, )))
&mut self.unspendable.op_return,
)))
} }
} }

View File

@@ -41,7 +41,7 @@ pub struct Vecs<M: StorageMode = Rw> {
pub _9m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 270d pub _9m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 270d
pub _350d: M::Stored<EagerVec<PcoVec<Height, Height>>>, pub _350d: M::Stored<EagerVec<PcoVec<Height, Height>>>,
pub _12m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 360d pub _12m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 360d
pub _1y: CachedVec<M::Stored<EagerVec<PcoVec<Height, Height>>>>, // 365d pub _1y: CachedVec<M::Stored<EagerVec<PcoVec<Height, Height>>>>, // 365d
pub _14m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 420d pub _14m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 420d
pub _2y: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 730d pub _2y: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 730d
pub _26m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 780d pub _26m: M::Stored<EagerVec<PcoVec<Height, Height>>>, // 780d

View File

@@ -165,9 +165,7 @@ impl ActivityCountVecs {
self.reactivated.block.push(counts.reactivated.into()); self.reactivated.block.push(counts.reactivated.into());
self.sending.block.push(counts.sending.into()); self.sending.block.push(counts.sending.into());
self.receiving.block.push(counts.receiving.into()); self.receiving.block.push(counts.receiving.into());
self.bidirectional self.bidirectional.block.push(counts.bidirectional.into());
.block
.push(counts.bidirectional.into());
let active = counts.sending + counts.receiving - counts.bidirectional; let active = counts.sending + counts.receiving - counts.bidirectional;
self.active.block.push(active.into()); self.active.block.push(active.into());
} }

View File

@@ -14,8 +14,7 @@ use super::TotalAddrCountVecs;
/// New address count per block (global + per-type). /// New address count per block (global + per-type).
#[derive(Deref, DerefMut, Traversable)] #[derive(Deref, DerefMut, Traversable)]
pub struct NewAddrCountVecs<M: StorageMode = Rw>( pub struct NewAddrCountVecs<M: StorageMode = Rw>(
#[traversable(flatten)] #[traversable(flatten)] pub WithAddrTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
pub WithAddrTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
); );
impl NewAddrCountVecs { impl NewAddrCountVecs {
@@ -28,7 +27,11 @@ impl NewAddrCountVecs {
Ok(Self(WithAddrTypes::< Ok(Self(WithAddrTypes::<
PerBlockCumulativeRolling<StoredU64, StoredU64>, PerBlockCumulativeRolling<StoredU64, StoredU64>,
>::forced_import( >::forced_import(
db, "new_addr_count", version, indexes, cached_starts db,
"new_addr_count",
version,
indexes,
cached_starts,
)?)) )?))
} }

View File

@@ -92,34 +92,30 @@ impl AddrEventsVecs {
cached_starts, cached_starts,
) )
}; };
let import_percent = |name: &str| -> Result<WithAddrTypes< let import_percent =
PercentCumulativeRolling<BasisPoints16>, |name: &str| -> Result<WithAddrTypes<PercentCumulativeRolling<BasisPoints16>>> {
>> { Ok(WithAddrTypes {
Ok(WithAddrTypes { all: PercentCumulativeRolling::forced_import(db, name, version, indexes)?,
all: PercentCumulativeRolling::forced_import(db, name, version, indexes)?, by_addr_type: ByAddrType::new_with_name(|type_name| {
by_addr_type: ByAddrType::new_with_name(|type_name| { PercentCumulativeRolling::forced_import(
PercentCumulativeRolling::forced_import( db,
db, &format!("{type_name}_{name}"),
&format!("{type_name}_{name}"), version,
version, indexes,
indexes, )
) })?,
})?, })
}) };
};
let output_to_reused_addr_count = let output_to_reused_addr_count = import_count(&format!("output_to_{name}_addr_count"))?;
import_count(&format!("output_to_{name}_addr_count"))?; let output_to_reused_addr_share = import_percent(&format!("output_to_{name}_addr_share"))?;
let output_to_reused_addr_share =
import_percent(&format!("output_to_{name}_addr_share"))?;
let spendable_output_to_reused_addr_share = PercentCumulativeRolling::forced_import( let spendable_output_to_reused_addr_share = PercentCumulativeRolling::forced_import(
db, db,
&format!("spendable_output_to_{name}_addr_share"), &format!("spendable_output_to_{name}_addr_share"),
version, version,
indexes, indexes,
)?; )?;
let input_from_reused_addr_count = let input_from_reused_addr_count = import_count(&format!("input_from_{name}_addr_count"))?;
import_count(&format!("input_from_{name}_addr_count"))?;
let input_from_reused_addr_share = let input_from_reused_addr_share =
import_percent(&format!("input_from_{name}_addr_share"))?; import_percent(&format!("input_from_{name}_addr_share"))?;
@@ -229,12 +225,13 @@ impl AddrEventsVecs {
starting_indexes.height, starting_indexes.height,
exit, exit,
)?; )?;
self.spendable_output_to_reused_addr_share.compute_count_ratio( self.spendable_output_to_reused_addr_share
&self.output_to_reused_addr_count.all, .compute_count_ratio(
&outputs_by_type.spendable_output_count, &self.output_to_reused_addr_count.all,
starting_indexes.height, &outputs_by_type.spendable_output_count,
exit, starting_indexes.height,
)?; exit,
)?;
self.input_from_reused_addr_share.all.compute_count_ratio( self.input_from_reused_addr_share.all.compute_count_ratio(
&self.input_from_reused_addr_count.all, &self.input_from_reused_addr_count.all,
&inputs_by_type.input_count.all, &inputs_by_type.input_count.all,
@@ -246,7 +243,9 @@ impl AddrEventsVecs {
.by_addr_type .by_addr_type
.get_mut_unwrap(otype) .get_mut_unwrap(otype)
.compute_count_ratio( .compute_count_ratio(
self.output_to_reused_addr_count.by_addr_type.get_unwrap(otype), self.output_to_reused_addr_count
.by_addr_type
.get_unwrap(otype),
outputs_by_type.output_count.by_type.get(otype), outputs_by_type.output_count.by_type.get(otype),
starting_indexes.height, starting_indexes.height,
exit, exit,
@@ -255,7 +254,9 @@ impl AddrEventsVecs {
.by_addr_type .by_addr_type
.get_mut_unwrap(otype) .get_mut_unwrap(otype)
.compute_count_ratio( .compute_count_ratio(
self.input_from_reused_addr_count.by_addr_type.get_unwrap(otype), self.input_from_reused_addr_count
.by_addr_type
.get_unwrap(otype),
inputs_by_type.input_count.by_type.get(otype), inputs_by_type.input_count.by_type.get(otype),
starting_indexes.height, starting_indexes.height,
exit, exit,

View File

@@ -2,9 +2,7 @@ use brk_types::{FundedAddrData, Height, OutputType, Sats};
use crate::distribution::{block::TrackingStatus, vecs::AddrMetricsVecs}; use crate::distribution::{block::TrackingStatus, vecs::AddrMetricsVecs};
use super::{ use super::{AddrTypeToActivityCounts, AddrTypeToAddrCount, ExposedAddrState, ReusedAddrState};
AddrTypeToActivityCounts, AddrTypeToAddrCount, ExposedAddrState, ReusedAddrState,
};
/// Bundle of per-block runtime state for the full address-metrics pipeline. /// Bundle of per-block runtime state for the full address-metrics pipeline.
/// Feeds `process_received` / `process_sent` and is pushed to [`AddrMetricsVecs`] /// Feeds `process_received` / `process_sent` and is pushed to [`AddrMetricsVecs`]
@@ -162,7 +160,8 @@ impl AddrMetricsState {
also_received, also_received,
will_be_empty, will_be_empty,
); );
self.exposed.on_send(output_type, addr_data, pre, will_be_empty); self.exposed
.on_send(output_type, addr_data, pre, will_be_empty);
} }
} }

View File

@@ -67,8 +67,7 @@ pub(crate) fn process_funded_addrs(
// Pure pushes - no holes remain // Pure pushes - no holes remain
addrs_data.funded.reserve_pushed(pushes_iter.len()); addrs_data.funded.reserve_pushed(pushes_iter.len());
for (next_index, (addr_type, type_index, data)) in for (next_index, (addr_type, type_index, data)) in (addrs_data.funded.len()..).zip(pushes_iter)
(addrs_data.funded.len()..).zip(pushes_iter)
{ {
addrs_data.funded.push(data); addrs_data.funded.push(data);
result.get_mut(addr_type).unwrap().insert( result.get_mut(addr_type).unwrap().insert(
@@ -138,9 +137,7 @@ pub(crate) fn process_empty_addrs(
// Pure pushes - no holes remain // Pure pushes - no holes remain
addrs_data.empty.reserve_pushed(pushes_iter.len()); addrs_data.empty.reserve_pushed(pushes_iter.len());
for (next_index, (addr_type, type_index, data)) in for (next_index, (addr_type, type_index, data)) in (addrs_data.empty.len()..).zip(pushes_iter) {
(addrs_data.empty.len()..).zip(pushes_iter)
{
addrs_data.empty.push(data); addrs_data.empty.push(data);
result.get_mut(addr_type).unwrap().insert( result.get_mut(addr_type).unwrap().insert(
type_index, type_index,

View File

@@ -1,9 +1,9 @@
use std::{cmp::Reverse, collections::BinaryHeap, fs, path::Path}; use std::{cmp::Reverse, collections::BinaryHeap, fs, path::Path};
use brk_cohort::{AGE_RANGE_NAMES, CohortContext, Filtered, PROFITABILITY_RANGE_COUNT, TERM_NAMES}; use brk_cohort::{AGE_RANGE_NAMES, CohortContext, Filtered, PROFITABILITY_RANGE_COUNT, TERM_NAMES};
use rayon::prelude::*;
use brk_error::Result; use brk_error::Result;
use brk_types::{BasisPoints16, Cents, CentsCompact, UrpdRaw, Date, Dollars, Sats}; use brk_types::{BasisPoints16, Cents, CentsCompact, Date, Dollars, Sats, UrpdRaw};
use rayon::prelude::*;
use crate::distribution::metrics::{CostBasis, ProfitabilityMetrics}; use crate::distribution::metrics::{CostBasis, ProfitabilityMetrics};

View File

@@ -190,7 +190,10 @@ pub(crate) fn process_blocks(
.first_index .first_index
.collect_range_at(start_usize, end_usize); .collect_range_at(start_usize, end_usize);
debug!("recovering addr metrics state from height {}", starting_height); debug!(
"recovering addr metrics state from height {}",
starting_height
);
let mut state = AddrMetricsState::from((&vecs.addrs, starting_height)); let mut state = AddrMetricsState::from((&vecs.addrs, starting_height));
debug!("addr metrics state recovered"); debug!("addr metrics state recovered");

View File

@@ -9,7 +9,7 @@ use crate::{
metrics::ImportConfig, metrics::ImportConfig,
state::{CohortState, CostBasisOps, RealizedOps}, state::{CohortState, CostBasisOps, RealizedOps},
}, },
internal::{ValuePerBlockCumulativeRolling, PerBlockCumulativeRolling}, internal::{PerBlockCumulativeRolling, ValuePerBlockCumulativeRolling},
prices, prices,
}; };

View File

@@ -7,11 +7,11 @@ use vecdb::{BytesVec, BytesVecValue, Database, ImportableVec};
use crate::{ use crate::{
indexes, indexes,
internal::{ internal::{
ValuePerBlock, ValuePerBlockCumulative, ValuePerBlockCumulativeRolling, FiatType, FiatPerBlock, FiatPerBlockCumulativeWithSums, FiatType, NumericValue, PerBlock,
FiatPerBlock, FiatPerBlockCumulativeWithSums, NumericValue, PerBlock,
PerBlockCumulativeRolling, PercentPerBlock, PercentRollingWindows, Price, PerBlockCumulativeRolling, PercentPerBlock, PercentRollingWindows, Price,
PriceWithRatioExtendedPerBlock, PriceWithRatioPerBlock, RatioPerBlock, PriceWithRatioExtendedPerBlock, PriceWithRatioPerBlock, RatioPerBlock,
RollingWindow24hPerBlock, RollingWindows, RollingWindowsFrom1w, WindowStartVec, Windows, RollingWindow24hPerBlock, RollingWindows, RollingWindowsFrom1w, ValuePerBlock,
ValuePerBlockCumulative, ValuePerBlockCumulativeRolling, WindowStartVec, Windows,
}, },
}; };

View File

@@ -201,7 +201,10 @@ impl CostBasis {
if invested_raw == 0 { if invested_raw == 0 {
return (h, spot); return (h, spot);
} }
(h, Cents::new((capitalized_cap.inner() / invested_raw) as u64)) (
h,
Cents::new((capitalized_cap.inner() / invested_raw) as u64),
)
}, },
exit, exit,
)?; )?;
@@ -215,7 +218,10 @@ impl CostBasis {
if invested_raw == 0 { if invested_raw == 0 {
return (h, spot); return (h, spot);
} }
(h, Cents::new((capitalized_cap.inner() / invested_raw) as u64)) (
h,
Cents::new((capitalized_cap.inner() / invested_raw) as u64),
)
}, },
exit, exit,
)?; )?;

View File

@@ -7,7 +7,7 @@ use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec};
use crate::{ use crate::{
indexes, indexes,
internal::{ internal::{
ValuePerBlock, ValuePerBlockWithDeltas, PerBlock, RatioPerBlock, WindowStartVec, Windows, PerBlock, RatioPerBlock, ValuePerBlock, ValuePerBlockWithDeltas, WindowStartVec, Windows,
}, },
prices, prices,
}; };

View File

@@ -11,11 +11,11 @@ use crate::{
blocks, blocks,
distribution::state::{CohortState, CostBasisData, RealizedState, WithCapital}, distribution::state::{CohortState, CostBasisData, RealizedState, WithCapital},
internal::{ internal::{
ValuePerBlockCumulativeRolling, FiatPerBlockCumulativeWithSums, PercentPerBlock, FiatPerBlockCumulativeWithSums, PercentPerBlock, PercentRollingWindows,
PercentRollingWindows, PriceWithRatioExtendedPerBlock, RatioCents64, RatioCentsBp32, PriceWithRatioExtendedPerBlock, RatioCents64, RatioCentsBp32, RatioCentsSignedCentsBps32,
RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RatioDollarsBp32, RatioCentsSignedDollarsBps32, RatioDollarsBp32, RatioPerBlockPercentiles,
RatioPerBlockPercentiles, RatioPerBlockStdDevBands, RatioSma, RollingWindows, RatioPerBlockStdDevBands, RatioSma, RollingWindows, RollingWindowsFrom1w,
RollingWindowsFrom1w, ValuePerBlockCumulativeRolling,
}, },
prices, prices,
}; };

View File

@@ -76,13 +76,12 @@ impl SupplyBase {
all_supply_sats: &impl ReadableVec<Height, Sats>, all_supply_sats: &impl ReadableVec<Height, Sats>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.dominance self.dominance.compute_binary::<Sats, Sats, RatioSatsBp16>(
.compute_binary::<Sats, Sats, RatioSatsBp16>( max_from,
max_from, &self.total.sats.height,
&self.total.sats.height, all_supply_sats,
all_supply_sats, exit,
exit, )
)
} }
pub(crate) fn compute_from_stateful( pub(crate) fn compute_from_stateful(

View File

@@ -7,7 +7,7 @@ use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::{distribution::state::UnrealizedState, prices}; use crate::{distribution::state::UnrealizedState, prices};
use crate::internal::{ use crate::internal::{
ValuePerBlock, HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin, LazyValuePerBlock, HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin, LazyValuePerBlock, ValuePerBlock,
}; };
use crate::distribution::metrics::ImportConfig; use crate::distribution::metrics::ImportConfig;

View File

@@ -2,6 +2,6 @@ mod avg_amount;
mod base; mod base;
mod core; mod core;
pub use avg_amount::AvgAmountMetrics;
pub use self::core::SupplyCore; pub use self::core::SupplyCore;
pub use avg_amount::AvgAmountMetrics;
pub use base::SupplyBase; pub use base::SupplyBase;

View File

@@ -215,8 +215,12 @@ impl<R: RealizedOps, C: CostBasisOps> CohortState<R, C> {
pre.prev_capitalized_cap, pre.prev_capitalized_cap,
); );
self.cost_basis self.cost_basis.decrement(
.decrement(pre.prev_price, pre.sats, pre.prev_ps, pre.prev_capitalized_cap); pre.prev_price,
pre.sats,
pre.prev_ps,
pre.prev_capitalized_cap,
);
} }
pub(crate) fn send_utxo( pub(crate) fn send_utxo(

View File

@@ -5,14 +5,14 @@ use std::{
}; };
use brk_error::{Error, Result}; use brk_error::{Error, Result};
use brk_types::{ use brk_types::{Cents, CentsCompact, CentsSats, CentsSquaredSats, Height, Sats, UrpdRaw};
Cents, CentsCompact, CentsSats, CentsSquaredSats, UrpdRaw, Height, Sats,
};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use vecdb::{Bytes, unlikely}; use vecdb::{Bytes, unlikely};
use super::{Accumulate, CachedUnrealizedState, UnrealizedState}; use super::{Accumulate, CachedUnrealizedState, UnrealizedState};
use crate::distribution::state::pending::{PendingCapDelta, PendingDelta, PendingCapitalizedCapRawDelta}; use crate::distribution::state::pending::{
PendingCapDelta, PendingCapitalizedCapRawDelta, PendingDelta,
};
/// Type alias for the price-to-sats map used in cost basis data. /// Type alias for the price-to-sats map used in cost basis data.
pub(super) type CostBasisMap = BTreeMap<CentsCompact, Sats>; pub(super) type CostBasisMap = BTreeMap<CentsCompact, Sats>;

View File

@@ -200,12 +200,14 @@ impl RealizedOps for CoreRealizedState {
#[inline] #[inline]
fn increment_snapshot(&mut self, price_sats: CentsSats, _capitalized_cap: CentsSquaredSats) { fn increment_snapshot(&mut self, price_sats: CentsSats, _capitalized_cap: CentsSquaredSats) {
self.minimal.increment_snapshot(price_sats, _capitalized_cap); self.minimal
.increment_snapshot(price_sats, _capitalized_cap);
} }
#[inline] #[inline]
fn decrement_snapshot(&mut self, price_sats: CentsSats, _capitalized_cap: CentsSquaredSats) { fn decrement_snapshot(&mut self, price_sats: CentsSats, _capitalized_cap: CentsSquaredSats) {
self.minimal.decrement_snapshot(price_sats, _capitalized_cap); self.minimal
.decrement_snapshot(price_sats, _capitalized_cap);
} }
#[inline] #[inline]
@@ -301,7 +303,8 @@ impl RealizedOps for RealizedState {
fn increment(&mut self, price: Cents, sats: Sats) { fn increment(&mut self, price: Cents, sats: Sats) {
self.core.increment(price, sats); self.core.increment(price, sats);
if sats.is_not_zero() { if sats.is_not_zero() {
self.capitalized_cap_raw += CentsSats::from_price_sats(price, sats).to_capitalized_cap(price); self.capitalized_cap_raw +=
CentsSats::from_price_sats(price, sats).to_capitalized_cap(price);
} }
} }

View File

@@ -220,8 +220,7 @@ impl Vecs {
let addr_count = AddrCountsVecs::forced_import(&db, "addr_count", version, indexes)?; let addr_count = AddrCountsVecs::forced_import(&db, "addr_count", version, indexes)?;
let empty_addr_count = let empty_addr_count =
AddrCountsVecs::forced_import(&db, "empty_addr_count", version, indexes)?; AddrCountsVecs::forced_import(&db, "empty_addr_count", version, indexes)?;
let addr_activity = let addr_activity = AddrActivityVecs::forced_import(&db, version, indexes, cached_starts)?;
AddrActivityVecs::forced_import(&db, version, indexes, cached_starts)?;
// Stored total = addr_count + empty_addr_count (global + per-type, with all derived indexes) // Stored total = addr_count + empty_addr_count (global + per-type, with all derived indexes)
let total_addr_count = TotalAddrCountVecs::forced_import(&db, version, indexes)?; let total_addr_count = TotalAddrCountVecs::forced_import(&db, version, indexes)?;
@@ -548,7 +547,9 @@ impl Vecs {
self.addrs.empty.compute_rest(starting_indexes, exit)?; self.addrs.empty.compute_rest(starting_indexes, exit)?;
let t = &self.utxo_cohorts.type_; let t = &self.utxo_cohorts.type_;
let type_supply_sats = ByAddrType::new(|filter| { let type_supply_sats = ByAddrType::new(|filter| {
let Filter::Type(ot) = filter else { unreachable!() }; let Filter::Type(ot) = filter else {
unreachable!()
};
&t.get(ot).metrics.supply.total.sats.height &t.get(ot).metrics.supply.total.sats.height
}); });
let all_supply_sats = &self.utxo_cohorts.all.metrics.supply.total.sats.height; let all_supply_sats = &self.utxo_cohorts.all.metrics.supply.total.sats.height;

View File

@@ -61,7 +61,11 @@ impl Vecs {
Ok(()) Ok(())
}, },
|agg| { |agg| {
push_block(&mut self.input_count, agg.entries_all, &agg.entries_per_type); push_block(
&mut self.input_count,
agg.entries_all,
&agg.entries_per_type,
);
push_block(&mut self.tx_count, agg.txs_all, &agg.txs_per_type); push_block(&mut self.tx_count, agg.txs_all, &agg.txs_per_type);
if self.input_count.all.block.batch_limit_reached() { if self.input_count.all.block.batch_limit_reached() {
@@ -81,8 +85,7 @@ impl Vecs {
self.input_count self.input_count
.compute_rest(starting_indexes.height, exit)?; .compute_rest(starting_indexes.height, exit)?;
self.tx_count self.tx_count.compute_rest(starting_indexes.height, exit)?;
.compute_rest(starting_indexes.height, exit)?;
} }
for (otype, source) in self.input_count.by_type.iter_typed() { for (otype, source) in self.input_count.by_type.iter_typed() {

View File

@@ -6,9 +6,7 @@ use vecdb::Database;
use super::{Vecs, WithInputTypes}; use super::{Vecs, WithInputTypes};
use crate::{ use crate::{
indexes, indexes,
internal::{ internal::{PerBlockCumulativeRolling, PercentCumulativeRolling, WindowStartVec, Windows},
PerBlockCumulativeRolling, PercentCumulativeRolling, WindowStartVec, Windows,
},
}; };
impl Vecs { impl Vecs {
@@ -18,26 +16,24 @@ impl Vecs {
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &Windows<&WindowStartVec>, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
let input_count = WithInputTypes::< let input_count =
PerBlockCumulativeRolling<StoredU64, StoredU64>, WithInputTypes::<PerBlockCumulativeRolling<StoredU64, StoredU64>>::forced_import_with(
>::forced_import_with( db,
db, "input_count_bis",
"input_count_bis", |t| format!("{t}_prevout_count"),
|t| format!("{t}_prevout_count"), version,
version, indexes,
indexes, cached_starts,
cached_starts, )?;
)?; let tx_count =
let tx_count = WithInputTypes::< WithInputTypes::<PerBlockCumulativeRolling<StoredU64, StoredU64>>::forced_import_with(
PerBlockCumulativeRolling<StoredU64, StoredU64>, db,
>::forced_import_with( "non_coinbase_tx_count",
db, |t| format!("tx_count_with_{t}_prevout"),
"non_coinbase_tx_count", version,
|t| format!("tx_count_with_{t}_prevout"), indexes,
version, cached_starts,
indexes, )?;
cached_starts,
)?;
let input_share = SpendableType::try_new(|_, name| { let input_share = SpendableType::try_new(|_, name| {
PercentCumulativeRolling::forced_import( PercentCumulativeRolling::forced_import(

View File

@@ -68,7 +68,9 @@ where
dep_version: Version, dep_version: Version,
at_height: Height, at_height: Height,
) -> Result<()> { ) -> Result<()> {
self.all.block.validate_and_truncate(dep_version, at_height)?; self.all
.block
.validate_and_truncate(dep_version, at_height)?;
for v in self.by_type.iter_mut() { for v in self.by_type.iter_mut() {
v.block.validate_and_truncate(dep_version, at_height)?; v.block.validate_and_truncate(dep_version, at_height)?;
} }

View File

@@ -20,8 +20,7 @@ impl Vecs {
self.spent.compute(indexer, starting_indexes, exit)?; self.spent.compute(indexer, starting_indexes, exit)?;
self.count self.count
.compute(indexer, indexes, blocks, starting_indexes, exit)?; .compute(indexer, indexes, blocks, starting_indexes, exit)?;
self.per_sec self.per_sec.compute(&self.count, starting_indexes, exit)?;
.compute(&self.count, starting_indexes, exit)?;
self.by_type.compute(indexer, starting_indexes, exit)?; self.by_type.compute(indexer, starting_indexes, exit)?;
let exit = exit.clone(); let exit = exit.clone();

View File

@@ -81,4 +81,3 @@ pub(crate) fn walk_blocks(
Ok(()) Ok(())
} }

View File

@@ -6,7 +6,7 @@ use vecdb::{Database, Exit, Rw, StorageMode};
use crate::{ use crate::{
indexes, indexes,
internal::{ internal::{
FiatType, FiatBlock, FiatPerBlock, LazyRollingSumsFiatFromHeight, WindowStartVec, Windows, FiatBlock, FiatPerBlock, FiatType, LazyRollingSumsFiatFromHeight, WindowStartVec, Windows,
}, },
}; };

View File

@@ -9,7 +9,7 @@ use crate::{
internal::{BpsType, LazyRollingDeltasFiatFromHeight, WindowStartVec, Windows}, internal::{BpsType, LazyRollingDeltasFiatFromHeight, WindowStartVec, Windows},
}; };
use super::{FiatType, FiatPerBlockCumulativeWithSums}; use super::{FiatPerBlockCumulativeWithSums, FiatType};
#[derive(Deref, DerefMut, Traversable)] #[derive(Deref, DerefMut, Traversable)]
pub struct FiatPerBlockCumulativeWithSumsAndDeltas<C, CS, B, M: StorageMode = Rw> pub struct FiatPerBlockCumulativeWithSumsAndDeltas<C, CS, B, M: StorageMode = Rw>

View File

@@ -6,7 +6,7 @@ use vecdb::{DeltaSub, LazyDeltaVec, LazyVecFrom1, ReadOnlyClone, ReadableCloneab
use crate::{ use crate::{
indexes, indexes,
internal::{ internal::{
FiatType, DerivedResolutions, LazyPerBlock, LazyRollingSumFromHeight, Resolutions, DerivedResolutions, FiatType, LazyPerBlock, LazyRollingSumFromHeight, Resolutions,
WindowStartVec, Windows, WindowStartVec, Windows,
}, },
}; };
@@ -19,9 +19,7 @@ pub struct LazyRollingSumFiatFromHeight<C: FiatType> {
#[derive(Clone, Deref, DerefMut, Traversable)] #[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)] #[traversable(transparent)]
pub struct LazyRollingSumsFiatFromHeight<C: FiatType>( pub struct LazyRollingSumsFiatFromHeight<C: FiatType>(pub Windows<LazyRollingSumFiatFromHeight<C>>);
pub Windows<LazyRollingSumFiatFromHeight<C>>,
);
impl<C: FiatType> LazyRollingSumsFiatFromHeight<C> { impl<C: FiatType> LazyRollingSumsFiatFromHeight<C> {
pub fn new( pub fn new(

View File

@@ -10,7 +10,7 @@ use crate::{
internal::{BpsType, LazyRollingDeltasFiatFromHeight, WindowStartVec, Windows}, internal::{BpsType, LazyRollingDeltasFiatFromHeight, WindowStartVec, Windows},
}; };
use super::{FiatType, FiatPerBlock}; use super::{FiatPerBlock, FiatType};
#[derive(Deref, DerefMut, Traversable)] #[derive(Deref, DerefMut, Traversable)]
pub struct FiatPerBlockWithDeltas<C, CS, B, M: StorageMode = Rw> pub struct FiatPerBlockWithDeltas<C, CS, B, M: StorageMode = Rw>

View File

@@ -44,7 +44,9 @@ where
Self { Self {
height: LazyVecFrom1::transformed::<F>(name, version, height_source), height: LazyVecFrom1::transformed::<F>(name, version, height_source),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<F>( resolutions: Box::new(DerivedResolutions::from_derived_computed::<F>(
name, version, resolutions, name,
version,
resolutions,
)), )),
} }
} }

View File

@@ -10,7 +10,9 @@ use vecdb::{BinaryTransform, Database, Exit, ReadableVec, Rw, StorageMode, VecVa
use crate::{ use crate::{
indexes, indexes,
internal::{BpsType, PerBlockCumulativeRolling, PercentPerBlock, PercentRollingWindows, RatioU64Bp16}, internal::{
BpsType, PerBlockCumulativeRolling, PercentPerBlock, PercentRollingWindows, RatioU64Bp16,
},
}; };
#[derive(Traversable)] #[derive(Traversable)]

View File

@@ -27,8 +27,7 @@ impl<B: BpsType> LazyPercentCumulativeRolling<B> {
version: Version, version: Version,
source: &PercentCumulativeRolling<B>, source: &PercentCumulativeRolling<B>,
) -> Self { ) -> Self {
let cumulative = let cumulative = LazyPercentPerBlock::from_percent::<F>(name, version, &source.cumulative);
LazyPercentPerBlock::from_percent::<F>(name, version, &source.cumulative);
let rolling = LazyPercentRollingWindows::from_rolling::<F>(name, version, &source.rolling); let rolling = LazyPercentRollingWindows::from_rolling::<F>(name, version, &source.rolling);
Self { Self {
cumulative, cumulative,

View File

@@ -46,12 +46,7 @@ where
starts_version, starts_version,
move || cached.cached(), move || cached.cached(),
); );
let resolutions = Resolutions::forced_import( let resolutions = Resolutions::forced_import(&full_name, avg.clone(), version, indexes);
&full_name,
avg.clone(),
version,
indexes,
);
LazyRollingAvgFromHeight { LazyRollingAvgFromHeight {
height: avg, height: avg,
resolutions: Box::new(resolutions), resolutions: Box::new(resolutions),

View File

@@ -369,12 +369,8 @@ where
move || cached.cached() move || cached.cached()
}, },
); );
let change_resolutions = Resolutions::forced_import( let change_resolutions =
&cents_name, Resolutions::forced_import(&cents_name, change_vec.clone(), version, indexes);
change_vec.clone(),
version,
indexes,
);
let cents = LazyDeltaFromHeight { let cents = LazyDeltaFromHeight {
height: change_vec, height: change_vec,
resolutions: Box::new(change_resolutions), resolutions: Box::new(change_resolutions),

View File

@@ -7,7 +7,7 @@ use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{ use crate::{
indexes, indexes,
internal::{ internal::{
ValuePerBlockCumulative, LazyRollingAvgsAmountFromHeight, LazyRollingSumsAmountFromHeight, LazyRollingAvgsAmountFromHeight, LazyRollingSumsAmountFromHeight, ValuePerBlockCumulative,
WindowStartVec, Windows, WindowStartVec, Windows,
}, },
prices, prices,

View File

@@ -7,7 +7,7 @@ use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{ use crate::{
indexes, indexes,
internal::{ internal::{
ValuePerBlockCumulativeRolling, RollingDistributionValuePerBlock, WindowStartVec, RollingDistributionValuePerBlock, ValuePerBlockCumulativeRolling, WindowStartVec,
WindowStarts, Windows, WindowStarts, Windows,
}, },
prices, prices,

View File

@@ -6,7 +6,7 @@ use derive_more::{Deref, DerefMut};
use vecdb::UnaryTransform; use vecdb::UnaryTransform;
use crate::internal::{ use crate::internal::{
ValuePerBlock, Identity, LazyValue, LazyValueDerivedResolutions, SatsToBitcoin, Identity, LazyValue, LazyValueDerivedResolutions, SatsToBitcoin, ValuePerBlock,
}; };
/// Lazy value wrapper with height + all derived last transforms from ValuePerBlock. /// Lazy value wrapper with height + all derived last transforms from ValuePerBlock.

View File

@@ -2,7 +2,7 @@ use brk_traversable::Traversable;
use brk_types::{Bitcoin, Cents, Dollars, Sats, Version}; use brk_types::{Bitcoin, Cents, Dollars, Sats, Version};
use vecdb::UnaryTransform; use vecdb::UnaryTransform;
use crate::internal::{ValuePerBlock, DerivedResolutions}; use crate::internal::{DerivedResolutions, ValuePerBlock};
#[derive(Clone, Traversable)] #[derive(Clone, Traversable)]
pub struct LazyValueDerivedResolutions { pub struct LazyValueDerivedResolutions {

View File

@@ -7,7 +7,7 @@ use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{ use crate::{
indexes, indexes,
internal::{ internal::{
ValuePerBlock, DistributionStats, WindowStarts, Windows, DistributionStats, ValuePerBlock, WindowStarts, Windows,
algo::compute_rolling_distribution_from_starts, algo::compute_rolling_distribution_from_starts,
}, },
}; };

View File

@@ -8,7 +8,9 @@ use brk_indexer::Indexer;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Indexes, TxIndex, VSize}; use brk_types::{Indexes, TxIndex, VSize};
use schemars::JsonSchema; use schemars::JsonSchema;
use vecdb::{Database, EagerVec, Exit, ImportableVec, PcoVec, ReadableVec, Rw, StorageMode, Version}; use vecdb::{
Database, EagerVec, Exit, ImportableVec, PcoVec, ReadableVec, Rw, StorageMode, Version,
};
use crate::{ use crate::{
indexes, indexes,

View File

@@ -13,7 +13,7 @@ use vecdb::{AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, WritableVec}
use crate::{indexes, prices}; use crate::{indexes, prices};
use super::{ use super::{
ValuePerBlock, BpsType, NumericValue, PerBlock, PerBlockCumulativeRolling, PercentPerBlock, BpsType, NumericValue, PerBlock, PerBlockCumulativeRolling, PercentPerBlock, ValuePerBlock,
WindowStartVec, Windows, WindowStartVec, Windows,
}; };
@@ -83,11 +83,7 @@ where
} }
/// Compute `all.height` as the per-block sum of the per-type vecs. /// Compute `all.height` as the per-block sum of the per-type vecs.
pub(crate) fn compute_rest( pub(crate) fn compute_rest(&mut self, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
&mut self,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
let sources: Vec<&EagerVec<PcoVec<Height, T>>> = let sources: Vec<&EagerVec<PcoVec<Height, T>>> =
self.by_addr_type.values().map(|v| &v.height).collect(); self.by_addr_type.values().map(|v| &v.height).collect();
self.all self.all
@@ -109,13 +105,8 @@ where
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &Windows<&WindowStartVec>, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
let all = PerBlockCumulativeRolling::forced_import( let all =
db, PerBlockCumulativeRolling::forced_import(db, name, version, indexes, cached_starts)?;
name,
version,
indexes,
cached_starts,
)?;
let by_addr_type = ByAddrType::new_with_name(|type_name| { let by_addr_type = ByAddrType::new_with_name(|type_name| {
PerBlockCumulativeRolling::forced_import( PerBlockCumulativeRolling::forced_import(
db, db,

View File

@@ -9,7 +9,7 @@ use super::{ByDcaCagr, ByDcaClass, ByDcaPeriod, Vecs};
use crate::{ use crate::{
indexes, indexes,
internal::{ internal::{
ValuePerBlock, PercentPerBlock, Price, PercentPerBlock, Price, ValuePerBlock,
db_utils::{finalize_db, open_db}, db_utils::{finalize_db, open_db},
}, },
}; };

View File

@@ -3,7 +3,7 @@ use brk_types::{BasisPointsSigned32, Cents, Height, Sats};
use vecdb::{Database, EagerVec, PcoVec, Rw, StorageMode}; use vecdb::{Database, EagerVec, PcoVec, Rw, StorageMode};
use super::{ByDcaCagr, ByDcaClass, ByDcaPeriod}; use super::{ByDcaCagr, ByDcaClass, ByDcaPeriod};
use crate::internal::{ValuePerBlock, PerBlock, PercentPerBlock, Price}; use crate::internal::{PerBlock, PercentPerBlock, Price, ValuePerBlock};
#[derive(Traversable)] #[derive(Traversable)]
pub struct PeriodVecs<M: StorageMode = Rw> { pub struct PeriodVecs<M: StorageMode = Rw> {

View File

@@ -87,9 +87,8 @@ impl Computer {
let cached_starts = blocks.lookback.cached_window_starts(); let cached_starts = blocks.lookback.cached_window_starts();
let (inputs, outputs, mining, transactions, pools, cointime) = timed( let (inputs, outputs, mining, transactions, pools, cointime) =
"Imported inputs/outputs/mining/tx/pools/cointime", timed("Imported inputs/outputs/mining/tx/pools/cointime", || {
|| {
thread::scope(|s| -> Result<_> { thread::scope(|s| -> Result<_> {
let inputs_handle = big_thread().spawn_scoped(s, || -> Result<_> { let inputs_handle = big_thread().spawn_scoped(s, || -> Result<_> {
Ok(Box::new(inputs::Vecs::forced_import( Ok(Box::new(inputs::Vecs::forced_import(
@@ -152,8 +151,7 @@ impl Computer {
Ok((inputs, outputs, mining, transactions, pools, cointime)) Ok((inputs, outputs, mining, transactions, pools, cointime))
}) })
}, })?;
)?;
// Market, indicators, and distribution are independent; import in parallel. // Market, indicators, and distribution are independent; import in parallel.
// Supply depends on distribution so it runs after. // Supply depends on distribution so it runs after.
@@ -271,9 +269,8 @@ impl Computer {
{ {
info!("Removing obsolete database folder: {}", name); info!("Removing obsolete database folder: {}", name);
let path = entry.path(); let path = entry.path();
fs::remove_dir_all(&path).map_err(|e| { fs::remove_dir_all(&path)
std::io::Error::other(format!("remove_dir_all {path:?}: {e}")) .map_err(|e| std::io::Error::other(format!("remove_dir_all {path:?}: {e}")))?;
})?;
} }
} }

View File

@@ -6,9 +6,9 @@ use super::Vecs;
use crate::{ use crate::{
indexes, indexes,
internal::{ internal::{
ValuePerBlockCumulative, ValuePerBlockCumulativeRolling, ValuePerBlockFull,
LazyPercentCumulativeRolling, OneMinusBp16, PercentCumulativeRolling, RatioRollingWindows, LazyPercentCumulativeRolling, OneMinusBp16, PercentCumulativeRolling, RatioRollingWindows,
WindowStartVec, Windows, ValuePerBlockCumulative, ValuePerBlockCumulativeRolling, ValuePerBlockFull, WindowStartVec,
Windows,
}, },
}; };

View File

@@ -3,8 +3,8 @@ use brk_types::{BasisPoints16, BasisPoints32, Height, Sats};
use vecdb::{EagerVec, PcoVec, Rw, StorageMode}; use vecdb::{EagerVec, PcoVec, Rw, StorageMode};
use crate::internal::{ use crate::internal::{
ValuePerBlockCumulative, ValuePerBlockCumulativeRolling, ValuePerBlockFull,
LazyPercentCumulativeRolling, PercentCumulativeRolling, RatioRollingWindows, LazyPercentCumulativeRolling, PercentCumulativeRolling, RatioRollingWindows,
ValuePerBlockCumulative, ValuePerBlockCumulativeRolling, ValuePerBlockFull,
}; };
#[derive(Traversable)] #[derive(Traversable)]

View File

@@ -68,10 +68,14 @@ impl Vecs {
Ok(()) Ok(())
}, },
|agg| { |agg| {
push_block(&mut self.output_count, agg.entries_all, &agg.entries_per_type); push_block(
&mut self.output_count,
agg.entries_all,
&agg.entries_per_type,
);
push_block(&mut self.tx_count, agg.txs_all, &agg.txs_per_type); push_block(&mut self.tx_count, agg.txs_all, &agg.txs_per_type);
let spendable_total = agg.entries_all let spendable_total =
- agg.entries_per_type[OutputType::OpReturn as usize]; agg.entries_all - agg.entries_per_type[OutputType::OpReturn as usize];
self.spendable_output_count self.spendable_output_count
.block .block
.push(StoredU64::from(spendable_total)); .push(StoredU64::from(spendable_total));
@@ -97,8 +101,7 @@ impl Vecs {
.compute_rest(starting_indexes.height, exit)?; .compute_rest(starting_indexes.height, exit)?;
self.spendable_output_count self.spendable_output_count
.compute_rest(starting_indexes.height, exit)?; .compute_rest(starting_indexes.height, exit)?;
self.tx_count self.tx_count.compute_rest(starting_indexes.height, exit)?;
.compute_rest(starting_indexes.height, exit)?;
} }
for (otype, source) in self.output_count.by_type.iter_typed() { for (otype, source) in self.output_count.by_type.iter_typed() {

View File

@@ -16,26 +16,24 @@ impl Vecs {
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
cached_starts: &Windows<&WindowStartVec>, cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> { ) -> Result<Self> {
let output_count = WithOutputTypes::< let output_count =
PerBlockCumulativeRolling<StoredU64, StoredU64>, WithOutputTypes::<PerBlockCumulativeRolling<StoredU64, StoredU64>>::forced_import_with(
>::forced_import_with( db,
db, "output_count_bis",
"output_count_bis", |t| format!("{t}_output_count"),
|t| format!("{t}_output_count"), version,
version, indexes,
indexes, cached_starts,
cached_starts, )?;
)?; let tx_count =
let tx_count = WithOutputTypes::< WithOutputTypes::<PerBlockCumulativeRolling<StoredU64, StoredU64>>::forced_import_with(
PerBlockCumulativeRolling<StoredU64, StoredU64>, db,
>::forced_import_with( "tx_count_bis",
db, |t| format!("tx_count_with_{t}_output"),
"tx_count_bis", version,
|t| format!("tx_count_with_{t}_output"), indexes,
version, cached_starts,
indexes, )?;
cached_starts,
)?;
let spendable_output_count = PerBlockCumulativeRolling::forced_import( let spendable_output_count = PerBlockCumulativeRolling::forced_import(
db, db,

View File

@@ -67,7 +67,9 @@ where
dep_version: Version, dep_version: Version,
at_height: Height, at_height: Height,
) -> Result<()> { ) -> Result<()> {
self.all.block.validate_and_truncate(dep_version, at_height)?; self.all
.block
.validate_and_truncate(dep_version, at_height)?;
for v in self.by_type.iter_mut() { for v in self.by_type.iter_mut() {
v.block.validate_and_truncate(dep_version, at_height)?; v.block.validate_and_truncate(dep_version, at_height)?;
} }

View File

@@ -22,8 +22,7 @@ impl Vecs {
self.count self.count
.compute(indexer, indexes, blocks, starting_indexes, exit)?; .compute(indexer, indexes, blocks, starting_indexes, exit)?;
self.per_sec self.per_sec.compute(&self.count, starting_indexes, exit)?;
.compute(&self.count, starting_indexes, exit)?;
self.value self.value
.compute(indexer, prices, starting_indexes, exit)?; .compute(indexer, prices, starting_indexes, exit)?;
self.by_type.compute(indexer, starting_indexes, exit)?; self.by_type.compute(indexer, starting_indexes, exit)?;

View File

@@ -7,7 +7,7 @@ use vecdb::{BinaryTransform, Database, Exit, ReadableVec, Rw, StorageMode, Versi
use crate::{ use crate::{
blocks, indexes, blocks, indexes,
internal::{ internal::{
ValuePerBlockCumulativeRolling, MaskSats, PercentRollingWindows, RatioU64Bp16, MaskSats, PercentRollingWindows, RatioU64Bp16, ValuePerBlockCumulativeRolling,
WindowStartVec, Windows, WindowStartVec, Windows,
}, },
mining, prices, mining, prices,

View File

@@ -14,9 +14,7 @@ impl PoolHeights {
let mut map: FxHashMap<PoolSlug, Vec<Height>> = FxHashMap::default(); let mut map: FxHashMap<PoolSlug, Vec<Height>> = FxHashMap::default();
let reader = pool.reader(); let reader = pool.reader();
for h in 0..len { for h in 0..len {
map.entry(reader.get(h)) map.entry(reader.get(h)).or_default().push(Height::from(h));
.or_default()
.push(Height::from(h));
} }
Self(Arc::new(RwLock::new(map))) Self(Arc::new(RwLock::new(map)))
} }

View File

@@ -84,7 +84,11 @@ impl Vecs {
.height .height
.len() .len()
.min(starting_indexes.height.to_usize()); .min(starting_indexes.height.to_usize());
self.spot.cents.height.inner.truncate_if_needed_at(truncate_to)?; self.spot
.cents
.height
.inner
.truncate_if_needed_at(truncate_to)?;
if self.spot.cents.height.len() < START_HEIGHT { if self.spot.cents.height.len() < START_HEIGHT {
for line in brk_oracle::PRICES for line in brk_oracle::PRICES

View File

@@ -6,7 +6,7 @@ use brk_types::Version;
use crate::{ use crate::{
cointime, distribution, indexes, cointime, distribution, indexes,
internal::{ internal::{
LazyValuePerBlock, LazyFiatPerBlock, LazyRollingDeltasFiatFromHeight, PercentPerBlock, LazyFiatPerBlock, LazyRollingDeltasFiatFromHeight, LazyValuePerBlock, PercentPerBlock,
RollingWindows, WindowStartVec, Windows, RollingWindows, WindowStartVec, Windows,
db_utils::{finalize_db, open_db}, db_utils::{finalize_db, open_db},
}, },
@@ -63,11 +63,8 @@ impl Vecs {
indexes, indexes,
)?; )?;
let hodled_or_lost = LazyValuePerBlock::identity( let hodled_or_lost =
"hodled_or_lost_supply", LazyValuePerBlock::identity("hodled_or_lost_supply", &cointime.supply.vaulted, version);
&cointime.supply.vaulted,
version,
);
let this = Self { let this = Self {
db, db,

View File

@@ -4,7 +4,7 @@ use vecdb::{Database, Rw, StorageMode};
use super::{burned, velocity}; use super::{burned, velocity};
use crate::internal::{ use crate::internal::{
LazyValuePerBlock, LazyFiatPerBlock, LazyRollingDeltasFiatFromHeight, PercentPerBlock, LazyFiatPerBlock, LazyRollingDeltasFiatFromHeight, LazyValuePerBlock, PercentPerBlock,
RollingWindows, RollingWindows,
}; };

View File

@@ -5,7 +5,7 @@ use vecdb::Database;
use super::Vecs; use super::Vecs;
use crate::{ use crate::{
indexes, indexes,
internal::{ValuePerBlockCumulativeRolling, PerBlock, WindowStartVec, Windows}, internal::{PerBlock, ValuePerBlockCumulativeRolling, WindowStartVec, Windows},
}; };
impl Vecs { impl Vecs {

View File

@@ -2,7 +2,7 @@ use brk_traversable::Traversable;
use brk_types::StoredF32; use brk_types::StoredF32;
use vecdb::{Rw, StorageMode}; use vecdb::{Rw, StorageMode};
use crate::internal::{ValuePerBlockCumulativeRolling, PerBlock, Windows}; use crate::internal::{PerBlock, ValuePerBlockCumulativeRolling, Windows};
#[derive(Traversable)] #[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> { pub struct Vecs<M: StorageMode = Rw> {

View File

@@ -62,10 +62,7 @@ impl Fetcher {
/// Parent txids referenced by `new_raws` inputs that aren't already /// Parent txids referenced by `new_raws` inputs that aren't already
/// resolvable: not in the mempool store, not in `new_raws` itself. /// resolvable: not in the mempool store, not in `new_raws` itself.
fn unique_confirmed_parents( fn unique_confirmed_parents(new_raws: &FxHashMap<Txid, RawTx>, known: &TxStore) -> Vec<Txid> {
new_raws: &FxHashMap<Txid, RawTx>,
known: &TxStore,
) -> Vec<Txid> {
let mut set: FxHashSet<Txid> = FxHashSet::default(); let mut set: FxHashSet<Txid> = FxHashSet::default();
for raw in new_raws.values() { for raw in new_raws.values() {
for txin in &raw.tx.input { for txin in &raw.tx.input {

View File

@@ -37,12 +37,9 @@ fn synthetic_mempool(n: usize) -> Vec<Option<Entry>> {
_ if i > 1 => { _ if i > 1 => {
let p1 = (i.wrapping_mul(7919)) % i; let p1 = (i.wrapping_mul(7919)) % i;
let p2 = (i.wrapping_mul(6151)) % i; let p2 = (i.wrapping_mul(6151)) % i;
[ [TxidPrefix::from(&txids[p1]), TxidPrefix::from(&txids[p2])]
TxidPrefix::from(&txids[p1]), .into_iter()
TxidPrefix::from(&txids[p2]), .collect()
]
.into_iter()
.collect()
} }
_ => SmallVec::new(), _ => SmallVec::new(),
}; };

View File

@@ -52,7 +52,12 @@ pub fn linearize_clusters(graph: &Graph) -> Vec<Package> {
continue; continue;
} }
for (chunk_order, chunk) in sfl::linearize(&cluster).iter().enumerate() { for (chunk_order, chunk) in sfl::linearize(&cluster).iter().enumerate() {
packages.push(chunk_to_package(&cluster, chunk, cluster_id, chunk_order as u32)); packages.push(chunk_to_package(
&cluster,
chunk,
cluster_id,
chunk_order as u32,
));
} }
} }

View File

@@ -327,7 +327,10 @@ fn random_dag(n: usize, seed: u64) -> FvAndEdges {
(fees_vsizes, edges) (fees_vsizes, edges)
} }
#[expect(dead_code, reason = "kept for ad-hoc oracle sweeps; called via uncommented stress tests")] #[expect(
dead_code,
reason = "kept for ad-hoc oracle sweeps; called via uncommented stress tests"
)]
fn assert_optimal_on_random(n: usize, seed: u64) { fn assert_optimal_on_random(n: usize, seed: u64) {
let (fv, edges) = random_dag(n, seed); let (fv, edges) = random_dag(n, seed);
let cluster = super::make_cluster(&fv, &edges); let cluster = super::make_cluster(&fv, &edges);
@@ -376,7 +379,11 @@ fn optimality_gap_of(got: &[(u64, u64)], want: &[(u64, u64)]) -> Option<u128> {
worst_gap = worst_gap.max(fb - fa); worst_gap = worst_gap.max(fb - fa);
} }
} }
if worst_gap == 0 { None } else { Some(worst_gap) } if worst_gap == 0 {
None
} else {
Some(worst_gap)
}
} }
/// Gap for the production linearizer on one random DAG. /// Gap for the production linearizer on one random DAG.
@@ -479,10 +486,7 @@ fn perf_linearize() {
} else { } else {
format!("{} ns", avg_ns) format!("{} ns", avg_ns)
}; };
eprintln!( eprintln!(" {:<4} {:<8} {:<10} {:.2?}", n, calls, pretty, elapsed);
" {:<4} {:<8} {:<10} {:.2?}",
n, calls, pretty, elapsed
);
} }
eprintln!(); eprintln!();
} }

View File

@@ -15,10 +15,7 @@ const LOOK_AHEAD_COUNT: usize = 100;
/// Look-ahead respects intra-cluster order: a chunk is only taken once /// Look-ahead respects intra-cluster order: a chunk is only taken once
/// every earlier-rate chunk of the same cluster has been placed, so a /// every earlier-rate chunk of the same cluster has been placed, so a
/// child chunk never lands in an earlier block than its parent chunk. /// child chunk never lands in an earlier block than its parent chunk.
pub fn partition_into_blocks( pub fn partition_into_blocks(mut packages: Vec<Package>, num_blocks: usize) -> Vec<Vec<Package>> {
mut packages: Vec<Package>,
num_blocks: usize,
) -> Vec<Vec<Package>> {
// Stable sort preserves SFL's per-cluster non-increasing-rate emission // Stable sort preserves SFL's per-cluster non-increasing-rate emission
// order in the global list, which is what `cluster_next` relies on. // order in the global list, which is what `cluster_next` relies on.
packages.sort_by_key(|p| Reverse(p.fee_rate)); packages.sort_by_key(|p| Reverse(p.fee_rate));
@@ -67,7 +64,13 @@ fn fill_normal_blocks(
let remaining_space = BLOCK_VSIZE.saturating_sub(current_vsize); let remaining_space = BLOCK_VSIZE.saturating_sub(current_vsize);
if pkg.vsize <= remaining_space { if pkg.vsize <= remaining_space {
take(slots, idx, &mut current_block, &mut current_vsize, cluster_next); take(
slots,
idx,
&mut current_block,
&mut current_vsize,
cluster_next,
);
idx += 1; idx += 1;
continue; continue;
} }
@@ -75,7 +78,13 @@ fn fill_normal_blocks(
if current_block.is_empty() { if current_block.is_empty() {
// Oversized package with no partial block to preserve; take it // Oversized package with no partial block to preserve; take it
// anyway so we don't stall on a package larger than BLOCK_VSIZE. // anyway so we don't stall on a package larger than BLOCK_VSIZE.
take(slots, idx, &mut current_block, &mut current_vsize, cluster_next); take(
slots,
idx,
&mut current_block,
&mut current_vsize,
cluster_next,
);
idx += 1; idx += 1;
continue; continue;
} }

View File

@@ -45,12 +45,7 @@ impl Verifier {
} }
} }
fn live_entry( fn live_entry(entries: &[Option<Entry>], tx_index: TxIndex, b: usize, p: usize) -> &Entry {
entries: &[Option<Entry>],
tx_index: TxIndex,
b: usize,
p: usize,
) -> &Entry {
entries[tx_index.as_usize()] entries[tx_index.as_usize()]
.as_ref() .as_ref()
.unwrap_or_else(|| panic!("block {b} pkg {p}: dead tx_index {tx_index:?}")) .unwrap_or_else(|| panic!("block {b} pkg {p}: dead tx_index {tx_index:?}"))
@@ -65,10 +60,7 @@ impl Verifier {
) { ) {
for parent in &entry.depends { for parent in &entry.depends {
if in_pool.contains(parent) && !placed.contains(parent) { if in_pool.contains(parent) && !placed.contains(parent) {
panic!( panic!("block {b} pkg {p}: {} placed before its parent", entry.txid);
"block {b} pkg {p}: {} placed before its parent",
entry.txid
);
} }
} }
} }

View File

@@ -60,7 +60,10 @@ impl EntryPool {
/// Remove an entry by its txid prefix, returning it if present. /// Remove an entry by its txid prefix, returning it if present.
pub fn remove(&mut self, prefix: &TxidPrefix) -> Option<Entry> { pub fn remove(&mut self, prefix: &TxidPrefix) -> Option<Entry> {
let idx = self.prefix_to_idx.remove(prefix)?; let idx = self.prefix_to_idx.remove(prefix)?;
let entry = self.entries.get_mut(idx.as_usize()).and_then(Option::take)?; let entry = self
.entries
.get_mut(idx.as_usize())
.and_then(Option::take)?;
self.free_slots.push(idx); self.free_slots.push(idx);
Some(entry) Some(entry)
} }

View File

@@ -15,7 +15,12 @@ pub struct Tombstone {
} }
impl Tombstone { impl Tombstone {
pub(super) fn new(tx: Transaction, entry: Entry, removal: Removal, removed_at: Instant) -> Self { pub(super) fn new(
tx: Transaction,
entry: Entry,
removal: Removal,
removed_at: Instant,
) -> Self {
Self { Self {
tx, tx,
entry, entry,

View File

@@ -36,9 +36,9 @@ impl TxGraveyard {
&'a self, &'a self,
replacer: &'a Txid, replacer: &'a Txid,
) -> impl Iterator<Item = (&'a Txid, &'a Tombstone)> { ) -> impl Iterator<Item = (&'a Txid, &'a Tombstone)> {
self.tombstones self.tombstones.iter().filter_map(move |(txid, ts)| {
.iter() (ts.replaced_by() == Some(replacer)).then_some((txid, ts))
.filter_map(move |(txid, ts)| (ts.replaced_by() == Some(replacer)).then_some((txid, ts))) })
} }
pub fn bury(&mut self, txid: Txid, tx: Transaction, entry: Entry, removal: Removal) { pub fn bury(&mut self, txid: Txid, tx: Transaction, entry: Entry, removal: Removal) {

View File

@@ -85,9 +85,9 @@ impl Query {
tx_count: addr_data.tx_count, tx_count: addr_data.tx_count,
realized_price, realized_price,
}, },
mempool_stats: self.mempool().and_then(|m| { mempool_stats: self
m.addrs().get(&bytes).map(|(stats, _)| stats.clone()) .mempool()
}), .and_then(|m| m.addrs().get(&bytes).map(|(stats, _)| stats.clone())),
}) })
} }

View File

@@ -57,12 +57,7 @@ impl Query {
} }
let height = Height::from(best_height); let height = Height::from(best_height);
let blockhash = indexer let blockhash = indexer.vecs.blocks.blockhash.collect_one(height).data()?;
.vecs
.blocks
.blockhash
.collect_one(height)
.data()?;
// Convert timestamp to ISO 8601 format // Convert timestamp to ISO 8601 format
let ts_secs: i64 = (*best_ts).into(); let ts_secs: i64 = (*best_ts).into();

View File

@@ -3,9 +3,9 @@ use std::cmp::Ordering;
use brk_error::{Error, Result}; use brk_error::{Error, Result};
use brk_mempool::{Entry, EntryPool, Removal, Tombstone, TxGraveyard, TxStore}; use brk_mempool::{Entry, EntryPool, Removal, Tombstone, TxGraveyard, TxStore};
use brk_types::{ use brk_types::{
CheckedSub, CpfpEntry, CpfpInfo, FeeRate, MempoolBlock, MempoolInfo, MempoolRecentTx, OutputType, CheckedSub, CpfpEntry, CpfpInfo, FeeRate, MempoolBlock, MempoolInfo, MempoolRecentTx,
RbfResponse, RbfTx, RecommendedFees, ReplacementNode, Sats, Timestamp, Transaction, TxOut, OutputType, RbfResponse, RbfTx, RecommendedFees, ReplacementNode, Sats, Timestamp, Transaction,
TxOutIndex, Txid, TxidPrefix, TypeIndex, VSize, Weight, TxOut, TxOutIndex, Txid, TxidPrefix, TypeIndex, VSize, Weight,
}; };
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use vecdb::VecIndex; use vecdb::VecIndex;
@@ -178,7 +178,8 @@ impl Query {
let graveyard = mempool.graveyard(); let graveyard = mempool.graveyard();
let mut root_txid = txid.clone(); let mut root_txid = txid.clone();
while let Some(Removal::Replaced { by }) = graveyard.get(&root_txid).map(Tombstone::reason) { while let Some(Removal::Replaced { by }) = graveyard.get(&root_txid).map(Tombstone::reason)
{
root_txid = by.clone(); root_txid = by.clone();
} }
@@ -188,8 +189,8 @@ impl Query {
.collect(); .collect();
let replaces = (!replaces_vec.is_empty()).then_some(replaces_vec); let replaces = (!replaces_vec.is_empty()).then_some(replaces_vec);
let replacements = Self::build_rbf_node(&root_txid, None, &txs, &entries, &graveyard) let replacements =
.map(|mut node| { Self::build_rbf_node(&root_txid, None, &txs, &entries, &graveyard).map(|mut node| {
node.tx.full_rbf = Some(node.full_rbf); node.tx.full_rbf = Some(node.full_rbf);
node.interval = None; node.interval = None;
node node
@@ -210,14 +211,10 @@ impl Query {
entries: &'a EntryPool, entries: &'a EntryPool,
graveyard: &'a TxGraveyard, graveyard: &'a TxGraveyard,
) -> Option<(&'a Transaction, &'a Entry)> { ) -> Option<(&'a Transaction, &'a Entry)> {
if let (Some(tx), Some(entry)) = if let (Some(tx), Some(entry)) = (txs.get(txid), entries.get(&TxidPrefix::from(txid))) {
(txs.get(txid), entries.get(&TxidPrefix::from(txid)))
{
return Some((tx, entry)); return Some((tx, entry));
} }
graveyard graveyard.get(txid).map(|tomb| (&tomb.tx, &tomb.entry))
.get(txid)
.map(|tomb| (&tomb.tx, &tomb.entry))
} }
/// Recursively build an RBF tree node rooted at `txid`. /// Recursively build an RBF tree node rooted at `txid`.

View File

@@ -78,7 +78,9 @@ impl BlockWindow {
.collect_range_at(self.start, self.end); .collect_range_at(self.start, self.end);
let all_prices: Vec<Cents> = computer let all_prices: Vec<Cents> = computer
.prices .prices
.spot.cents.height .spot
.cents
.height
.collect_range_at(self.start, self.end); .collect_range_at(self.start, self.end);
let read_start = self.start.saturating_sub(1); let read_start = self.start.saturating_sub(1);
let all_cum = cumulative.collect_range_at(read_start, self.end); let all_cum = cumulative.collect_range_at(read_start, self.end);

View File

@@ -3,10 +3,10 @@ use std::{collections::BTreeMap, sync::LazyLock};
use brk_error::{Error, Result}; use brk_error::{Error, Result};
use brk_traversable::TreeNode; use brk_traversable::TreeNode;
use brk_types::{ use brk_types::{
BlockHashPrefix, Date, DetailedSeriesCount, Epoch, Format, Halving, Height, Index, BlockHashPrefix, Date, DetailedSeriesCount, Epoch, Format, Halving, Height, Index, IndexInfo,
IndexInfo, LegacyValue, Limit, Output, OutputLegacy, PaginatedSeries, Pagination, LegacyValue, Limit, Output, OutputLegacy, PaginatedSeries, Pagination, PaginationIndex,
PaginationIndex, RangeIndex, RangeMap, SearchQuery, SeriesData, SeriesInfo, SeriesName, RangeIndex, RangeMap, SearchQuery, SeriesData, SeriesInfo, SeriesName, SeriesOutput,
SeriesOutput, SeriesOutputLegacy, SeriesSelection, Timestamp, Version, SeriesOutputLegacy, SeriesSelection, Timestamp, Version,
}; };
use parking_lot::RwLock; use parking_lot::RwLock;
use vecdb::{AnyExportableVec, ReadableVec}; use vecdb::{AnyExportableVec, ReadableVec};

View File

@@ -190,16 +190,15 @@ impl Query {
let spending_txid = txid_reader.get(spending_tx_index.to_usize()); let spending_txid = txid_reader.get(spending_tx_index.to_usize());
let spending_height: Height = tx_heights.get_shared(spending_tx_index).data()?; let spending_height: Height = tx_heights.get_shared(spending_tx_index).data()?;
let (block_hash, block_time) = let (block_hash, block_time) = if let Some((h, ref bh, bt)) = cached_status
if let Some((h, ref bh, bt)) = cached_status && h == spending_height
&& h == spending_height {
{ (bh.clone(), bt)
(bh.clone(), bt) } else {
} else { let (bh, bt) = self.block_hash_and_time(spending_height)?;
let (bh, bt) = self.block_hash_and_time(spending_height)?; cached_status = Some((spending_height, bh.clone(), bt));
cached_status = Some((spending_height, bh.clone(), bt)); (bh, bt)
(bh, bt) };
};
outspends.push(TxOutspend { outspends.push(TxOutspend {
spent: true, spent: true,

View File

@@ -7,8 +7,8 @@ use brk_types::Height;
use tracing::warn; use tracing::warn;
use crate::{ use crate::{
BlkIndexToBlkPath, OUT_OF_ORDER_FILE_BACKOFF, XORBytes, XORIndex, BlkIndexToBlkPath, OUT_OF_ORDER_FILE_BACKOFF, XORBytes, XORIndex, parse::HEADER_LEN,
parse::HEADER_LEN, scan::find_magic, scan::find_magic,
}; };
const PROBE_BUF_LEN: usize = 4096; const PROBE_BUF_LEN: usize = 4096;

View File

@@ -28,7 +28,9 @@ impl BlkIndexToBlkPath {
let Some(file_name) = path.file_name().and_then(|n| n.to_str()) else { let Some(file_name) = path.file_name().and_then(|n| n.to_str()) else {
continue; continue;
}; };
let Some(index_str) = file_name.strip_prefix(BLK).and_then(|s| s.strip_suffix(DOT_DAT)) let Some(index_str) = file_name
.strip_prefix(BLK)
.and_then(|s| s.strip_suffix(DOT_DAT))
else { else {
continue; continue;
}; };

View File

@@ -47,8 +47,14 @@ pub(super) fn pipeline_forward(
} }
drop(parser_recv); drop(parser_recv);
let read_result = let read_result = read_and_dispatch(
read_and_dispatch(paths, first_blk_index, xor_bytes, canonical, &parser_send, &stop); paths,
first_blk_index,
xor_bytes,
canonical,
&parser_send,
&stop,
);
drop(parser_send); drop(parser_send);
read_result read_result
})?; })?;
@@ -125,8 +131,7 @@ fn read_and_dispatch(
else { else {
return ControlFlow::Continue(()); return ControlFlow::Continue(());
}; };
if !canonical if !canonical.verify_prev(canonical_offset, &BlockHash::from(header.prev_blockhash))
.verify_prev(canonical_offset, &BlockHash::from(header.prev_blockhash))
{ {
let _ = stop.set(Stop::Failed(Error::Internal( let _ = stop.set(Stop::Failed(Error::Internal(
"forward pipeline: canonical batch stitched across a reorg", "forward pipeline: canonical batch stitched across a reorg",

View File

@@ -5,10 +5,7 @@ use brk_rpc::Client;
use brk_types::{Height, ReadBlock}; use brk_types::{Height, ReadBlock};
use crossbeam::channel::{Receiver, bounded}; use crossbeam::channel::{Receiver, bounded};
use crate::{ use crate::{BlkIndexToBlkPath, ReaderInner, XORBytes, bisect, canonical::CanonicalRange};
BlkIndexToBlkPath, ReaderInner, XORBytes, bisect,
canonical::CanonicalRange,
};
mod forward; mod forward;
mod reorder; mod reorder;

View File

@@ -74,9 +74,7 @@ pub(super) fn pipeline_tail(
if slots[offset as usize].is_some() { if slots[offset as usize].is_some() {
return ControlFlow::Continue(()); return ControlFlow::Continue(());
} }
if !canonical if !canonical.verify_prev(offset, &BlockHash::from(header.prev_blockhash)) {
.verify_prev(offset, &BlockHash::from(header.prev_blockhash))
{
parse_failure = Some(Error::Internal( parse_failure = Some(Error::Internal(
"tail pipeline: canonical batch stitched across a reorg", "tail pipeline: canonical batch stitched across a reorg",
)); ));

View File

@@ -1,9 +1,7 @@
use std::{thread::sleep, time::Duration}; use std::{thread::sleep, time::Duration};
use brk_error::{Error, Result}; use brk_error::{Error, Result};
use corepc_jsonrpc::{ use corepc_jsonrpc::{Client as JsonRpcClient, Request, error::Error as JsonRpcError, simple_http};
Client as JsonRpcClient, Request, error::Error as JsonRpcError, simple_http,
};
use parking_lot::RwLock; use parking_lot::RwLock;
use serde::Deserialize; use serde::Deserialize;
use serde_json::{Value, value::RawValue}; use serde_json::{Value, value::RawValue};

View File

@@ -12,7 +12,9 @@ use serde::Deserialize;
use serde_json::Value; use serde_json::Value;
use tracing::{debug, info}; use tracing::{debug, info};
use crate::{BlockHeaderInfo, BlockInfo, BlockTemplateTx, BlockchainInfo, Client, RawTx, TxOutInfo}; use crate::{
BlockHeaderInfo, BlockInfo, BlockTemplateTx, BlockchainInfo, Client, RawTx, TxOutInfo,
};
/// Per-batch request count for `get_block_hashes_range`. Sized so the /// Per-batch request count for `get_block_hashes_range`. Sized so the
/// JSON request body stays well under a megabyte and bitcoind doesn't /// JSON request body stays well under a megabyte and bitcoind doesn't
@@ -44,10 +46,9 @@ impl Client {
&'a H: Into<&'a bitcoin::BlockHash>, &'a H: Into<&'a bitcoin::BlockHash>,
{ {
let hash: &bitcoin::BlockHash = hash.into(); let hash: &bitcoin::BlockHash = hash.into();
let r: GetBlockVerboseZero = self.0.call_with_retry( let r: GetBlockVerboseZero = self
"getblock", .0
&[serde_json::to_value(hash)?, Value::from(0u8)], .call_with_retry("getblock", &[serde_json::to_value(hash)?, Value::from(0u8)])?;
)?;
r.block() r.block()
.map_err(|e| Error::Parse(format!("decode getblock: {e}"))) .map_err(|e| Error::Parse(format!("decode getblock: {e}")))
} }
@@ -57,10 +58,9 @@ impl Client {
&'a H: Into<&'a bitcoin::BlockHash>, &'a H: Into<&'a bitcoin::BlockHash>,
{ {
let hash: &bitcoin::BlockHash = hash.into(); let hash: &bitcoin::BlockHash = hash.into();
let r: GetBlockVerboseOne = self.0.call_with_retry( let r: GetBlockVerboseOne = self
"getblock", .0
&[serde_json::to_value(hash)?, Value::from(1u8)], .call_with_retry("getblock", &[serde_json::to_value(hash)?, Value::from(1u8)])?;
)?;
Ok(BlockInfo { Ok(BlockInfo {
height: r.height as usize, height: r.height as usize,
confirmations: r.confirmations, confirmations: r.confirmations,
@@ -241,7 +241,10 @@ impl Client {
pub fn get_mempool_raw_tx(&self, txid: &Txid) -> Result<RawTx> { pub fn get_mempool_raw_tx(&self, txid: &Txid) -> Result<RawTx> {
let hex = self.get_raw_transaction_hex(txid, None as Option<&BlockHash>)?; let hex = self.get_raw_transaction_hex(txid, None as Option<&BlockHash>)?;
let tx = encode::deserialize_hex::<bitcoin::Transaction>(&hex)?; let tx = encode::deserialize_hex::<bitcoin::Transaction>(&hex)?;
Ok(RawTx { tx, hex: hex.into() }) Ok(RawTx {
tx,
hex: hex.into(),
})
} }
/// Batched `getrawtransaction` over a slice of txids. Returns a map keyed /// Batched `getrawtransaction` over a slice of txids. Returns a map keyed
@@ -250,10 +253,7 @@ impl Client {
/// are logged and dropped so a single bad entry doesn't kill the batch. /// are logged and dropped so a single bad entry doesn't kill the batch.
/// ///
/// Chunked at `BATCH_CHUNK` requests per round-trip. /// Chunked at `BATCH_CHUNK` requests per round-trip.
pub fn get_raw_transactions( pub fn get_raw_transactions(&self, txids: &[Txid]) -> Result<FxHashMap<Txid, RawTx>> {
&self,
txids: &[Txid],
) -> Result<FxHashMap<Txid, RawTx>> {
let mut out: FxHashMap<Txid, RawTx> = let mut out: FxHashMap<Txid, RawTx> =
FxHashMap::with_capacity_and_hasher(txids.len(), Default::default()); FxHashMap::with_capacity_and_hasher(txids.len(), Default::default());
@@ -271,7 +271,10 @@ impl Client {
for (txid, res) in chunk.iter().zip(results) { for (txid, res) in chunk.iter().zip(results) {
match res.and_then(|hex| { match res.and_then(|hex| {
let tx = encode::deserialize_hex::<bitcoin::Transaction>(&hex)?; let tx = encode::deserialize_hex::<bitcoin::Transaction>(&hex)?;
Ok::<_, Error>(RawTx { tx, hex: hex.into() }) Ok::<_, Error>(RawTx {
tx,
hex: hex.into(),
})
}) { }) {
Ok(raw) => { Ok(raw) => {
out.insert(txid.clone(), raw); out.insert(txid.clone(), raw);
@@ -279,7 +282,9 @@ impl Client {
// Silenced: users without `-txindex` expect -5 for // Silenced: users without `-txindex` expect -5 for
// every confirmed tx. Downgraded so the mempool // every confirmed tx. Downgraded so the mempool
// parent-fetch loop doesn't spam the log each cycle. // parent-fetch loop doesn't spam the log each cycle.
Err(e) => debug!(txid = %txid, error = %e, "getrawtransaction batch: item failed"), Err(e) => {
debug!(txid = %txid, error = %e, "getrawtransaction batch: item failed")
}
} }
} }
} }

View File

@@ -10,7 +10,7 @@ use brk_types::{AddrStats, AddrValidation, Transaction, Txid, Utxo, Version};
use crate::{ use crate::{
AppState, CacheStrategy, AppState, CacheStrategy,
extended::TransformResponseExtended, extended::TransformResponseExtended,
params::{AddrParam, AddrTxidsParam, ValidateAddrParam}, params::{AddrParam, AddrTxidsParam, Empty, ValidateAddrParam},
}; };
pub trait AddrRoutes { pub trait AddrRoutes {
@@ -28,6 +28,7 @@ impl AddrRoutes for ApiRouter<AppState> {
uri: Uri, uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(path): Path<AddrParam>, Path(path): Path<AddrParam>,
_: Empty,
State(state): State<AppState> State(state): State<AppState>
| { | {
let strategy = state.addr_cache(Version::ONE, &path.addr, false); let strategy = state.addr_cache(Version::ONE, &path.addr, false);
@@ -96,6 +97,7 @@ impl AddrRoutes for ApiRouter<AppState> {
uri: Uri, uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(path): Path<AddrParam>, Path(path): Path<AddrParam>,
_: Empty,
State(state): State<AppState> State(state): State<AppState>
| { | {
let hash = state.sync(|q| q.addr_mempool_hash(&path.addr)); let hash = state.sync(|q| q.addr_mempool_hash(&path.addr));
@@ -118,6 +120,7 @@ impl AddrRoutes for ApiRouter<AppState> {
uri: Uri, uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(path): Path<AddrParam>, Path(path): Path<AddrParam>,
_: Empty,
State(state): State<AppState> State(state): State<AppState>
| { | {
let strategy = state.addr_cache(Version::ONE, &path.addr, false); let strategy = state.addr_cache(Version::ONE, &path.addr, false);
@@ -140,6 +143,7 @@ impl AddrRoutes for ApiRouter<AppState> {
uri: Uri, uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(path): Path<ValidateAddrParam>, Path(path): Path<ValidateAddrParam>,
_: Empty,
State(state): State<AppState> State(state): State<AppState>
| { | {
state.cached_json(&headers, CacheStrategy::Deploy, &uri, move |_q| Ok(AddrValidation::from_addr(&path.addr))).await state.cached_json(&headers, CacheStrategy::Deploy, &uri, move |_q| Ok(AddrValidation::from_addr(&path.addr))).await

View File

@@ -11,7 +11,9 @@ use brk_types::{
use crate::{ use crate::{
AppState, CacheStrategy, AppState, CacheStrategy,
extended::TransformResponseExtended, extended::TransformResponseExtended,
params::{BlockHashParam, BlockHashStartIndex, BlockHashTxIndex, HeightParam, TimestampParam}, params::{
BlockHashParam, BlockHashStartIndex, BlockHashTxIndex, Empty, HeightParam, TimestampParam,
},
}; };
pub trait BlockRoutes { pub trait BlockRoutes {
@@ -26,7 +28,7 @@ impl BlockRoutes for ApiRouter<AppState> {
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(path): Path<BlockHashParam>, Path(path): Path<BlockHashParam>,
State(state): State<AppState>| { _: Empty, State(state): State<AppState>| {
let strategy = state.block_cache(Version::ONE, &path.hash); let strategy = state.block_cache(Version::ONE, &path.hash);
state.cached_json(&headers, strategy, &uri, move |q| q.block(&path.hash)).await state.cached_json(&headers, strategy, &uri, move |q| q.block(&path.hash)).await
}, },
@@ -48,7 +50,7 @@ impl BlockRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/block/{hash}", "/api/v1/block/{hash}",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(path): Path<BlockHashParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(path): Path<BlockHashParam>, _: Empty, State(state): State<AppState>| {
let strategy = state.block_cache(Version::ONE, &path.hash); let strategy = state.block_cache(Version::ONE, &path.hash);
state.cached_json(&headers, strategy, &uri, move |q| { state.cached_json(&headers, strategy, &uri, move |q| {
let height = q.height_by_hash(&path.hash)?; let height = q.height_by_hash(&path.hash)?;
@@ -71,7 +73,7 @@ impl BlockRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/block/{hash}/header", "/api/block/{hash}/header",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(path): Path<BlockHashParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(path): Path<BlockHashParam>, _: Empty, State(state): State<AppState>| {
let strategy = state.block_cache(Version::ONE, &path.hash); let strategy = state.block_cache(Version::ONE, &path.hash);
state.cached_text(&headers, strategy, &uri, move |q| q.block_header_hex(&path.hash)).await state.cached_text(&headers, strategy, &uri, move |q| q.block_header_hex(&path.hash)).await
}, },
@@ -94,7 +96,7 @@ impl BlockRoutes for ApiRouter<AppState> {
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(path): Path<HeightParam>, Path(path): Path<HeightParam>,
State(state): State<AppState>| { _: Empty, State(state): State<AppState>| {
state.cached_text(&headers, state.height_cache(Version::ONE, path.height), &uri, move |q| q.block_hash_by_height(path.height).map(|h| h.to_string())).await state.cached_text(&headers, state.height_cache(Version::ONE, path.height), &uri, move |q| q.block_hash_by_height(path.height).map(|h| h.to_string())).await
}, },
|op| { |op| {
@@ -118,7 +120,7 @@ impl BlockRoutes for ApiRouter<AppState> {
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(path): Path<TimestampParam>, Path(path): Path<TimestampParam>,
State(state): State<AppState>| { _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, state.timestamp_cache(Version::ONE, path.timestamp), &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| {
@@ -140,7 +142,7 @@ impl BlockRoutes for ApiRouter<AppState> {
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(path): Path<BlockHashParam>, Path(path): Path<BlockHashParam>,
State(state): State<AppState>| { _: Empty, State(state): State<AppState>| {
let strategy = state.block_cache(Version::ONE, &path.hash); let strategy = state.block_cache(Version::ONE, &path.hash);
state.cached_bytes(&headers, strategy, &uri, move |q| q.block_raw(&path.hash)).await state.cached_bytes(&headers, strategy, &uri, move |q| q.block_raw(&path.hash)).await
}, },
@@ -165,7 +167,7 @@ impl BlockRoutes for ApiRouter<AppState> {
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(path): Path<BlockHashParam>, Path(path): Path<BlockHashParam>,
State(state): State<AppState>| { _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, state.block_status_cache(Version::ONE, &path.hash), &uri, move |q| q.block_status(&path.hash)).await state.cached_json(&headers, state.block_status_cache(Version::ONE, &path.hash), &uri, move |q| q.block_status(&path.hash)).await
}, },
|op| { |op| {
@@ -186,7 +188,7 @@ impl BlockRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/blocks/tip/height", "/api/blocks/tip/height",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state.cached_text(&headers, CacheStrategy::Tip, &uri, |q| Ok(q.indexed_height().to_string())).await state.cached_text(&headers, CacheStrategy::Tip, &uri, |q| Ok(q.indexed_height().to_string())).await
}, },
|op| { |op| {
@@ -203,7 +205,7 @@ impl BlockRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/blocks/tip/hash", "/api/blocks/tip/hash",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state.cached_text(&headers, CacheStrategy::Tip, &uri, |q| Ok(q.tip_blockhash().to_string())).await state.cached_text(&headers, CacheStrategy::Tip, &uri, |q| Ok(q.tip_blockhash().to_string())).await
}, },
|op| { |op| {
@@ -223,7 +225,7 @@ impl BlockRoutes for ApiRouter<AppState> {
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(path): Path<BlockHashTxIndex>, Path(path): Path<BlockHashTxIndex>,
State(state): State<AppState>| { _: Empty, State(state): State<AppState>| {
let strategy = state.block_cache(Version::ONE, &path.hash); let strategy = state.block_cache(Version::ONE, &path.hash);
state.cached_text(&headers, strategy, &uri, move |q| q.block_txid_at_index(&path.hash, path.index).map(|t| t.to_string())).await state.cached_text(&headers, strategy, &uri, move |q| q.block_txid_at_index(&path.hash, path.index).map(|t| t.to_string())).await
}, },
@@ -248,7 +250,7 @@ impl BlockRoutes for ApiRouter<AppState> {
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(path): Path<BlockHashParam>, Path(path): Path<BlockHashParam>,
State(state): State<AppState>| { _: Empty, State(state): State<AppState>| {
let strategy = state.block_cache(Version::ONE, &path.hash); let strategy = state.block_cache(Version::ONE, &path.hash);
state.cached_json(&headers, strategy, &uri, move |q| q.block_txids(&path.hash)).await state.cached_json(&headers, strategy, &uri, move |q| q.block_txids(&path.hash)).await
}, },
@@ -273,7 +275,7 @@ impl BlockRoutes for ApiRouter<AppState> {
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(path): Path<BlockHashParam>, Path(path): Path<BlockHashParam>,
State(state): State<AppState>| { _: Empty, State(state): State<AppState>| {
let strategy = state.block_cache(Version::ONE, &path.hash); let strategy = state.block_cache(Version::ONE, &path.hash);
state.cached_json(&headers, strategy, &uri, move |q| q.block_txs(&path.hash, TxIndex::default())).await state.cached_json(&headers, strategy, &uri, move |q| q.block_txs(&path.hash, TxIndex::default())).await
}, },
@@ -299,7 +301,7 @@ impl BlockRoutes for ApiRouter<AppState> {
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(path): Path<BlockHashStartIndex>, Path(path): Path<BlockHashStartIndex>,
State(state): State<AppState>| { _: Empty, State(state): State<AppState>| {
let strategy = state.block_cache(Version::ONE, &path.hash); let strategy = state.block_cache(Version::ONE, &path.hash);
state.cached_json(&headers, strategy, &uri, move |q| q.block_txs(&path.hash, path.start_index)).await state.cached_json(&headers, strategy, &uri, move |q| q.block_txs(&path.hash, path.start_index)).await
}, },
@@ -322,7 +324,7 @@ impl BlockRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/blocks", "/api/blocks",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state state
.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.blocks(None)) .cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.blocks(None))
.await .await
@@ -344,7 +346,7 @@ impl BlockRoutes for ApiRouter<AppState> {
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(path): Path<HeightParam>, Path(path): Path<HeightParam>,
State(state): State<AppState>| { _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, state.height_cache(Version::ONE, path.height), &uri, move |q| q.blocks(Some(path.height))).await state.cached_json(&headers, state.height_cache(Version::ONE, path.height), &uri, move |q| q.blocks(Some(path.height))).await
}, },
|op| { |op| {
@@ -364,7 +366,7 @@ impl BlockRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/blocks", "/api/v1/blocks",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state state
.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.blocks_v1(None)) .cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.blocks_v1(None))
.await .await
@@ -386,7 +388,7 @@ impl BlockRoutes for ApiRouter<AppState> {
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(path): Path<HeightParam>, Path(path): Path<HeightParam>,
State(state): State<AppState>| { _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, state.height_cache(Version::ONE, path.height), &uri, move |q| q.blocks_v1(Some(path.height))).await state.cached_json(&headers, state.height_cache(Version::ONE, path.height), &uri, move |q| q.blocks_v1(Some(path.height))).await
}, },
|op| { |op| {

View File

@@ -5,7 +5,7 @@ use axum::{
}; };
use brk_types::{MempoolBlock, RecommendedFees}; use brk_types::{MempoolBlock, RecommendedFees};
use crate::{AppState, extended::TransformResponseExtended}; use crate::{AppState, extended::TransformResponseExtended, params::Empty};
pub trait FeesRoutes { pub trait FeesRoutes {
fn add_fees_routes(self) -> Self; fn add_fees_routes(self) -> Self;
@@ -16,7 +16,7 @@ impl FeesRoutes for ApiRouter<AppState> {
self.api_route( self.api_route(
"/api/v1/fees/mempool-blocks", "/api/v1/fees/mempool-blocks",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state state
.cached_json(&headers, state.mempool_cache(), &uri, |q| { .cached_json(&headers, state.mempool_cache(), &uri, |q| {
q.mempool_blocks() q.mempool_blocks()
@@ -37,7 +37,7 @@ impl FeesRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/fees/recommended", "/api/v1/fees/recommended",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state state
.cached_json(&headers, state.mempool_cache(), &uri, |q| { .cached_json(&headers, state.mempool_cache(), &uri, |q| {
q.recommended_fees() q.recommended_fees()
@@ -58,7 +58,7 @@ impl FeesRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/fees/precise", "/api/v1/fees/precise",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state state
.cached_json(&headers, state.mempool_cache(), &uri, |q| { .cached_json(&headers, state.mempool_cache(), &uri, |q| {
q.recommended_fees() q.recommended_fees()

View File

@@ -6,7 +6,9 @@ use axum::{
use brk_types::{DifficultyAdjustment, HistoricalPrice, Prices, Timestamp, Version}; use brk_types::{DifficultyAdjustment, HistoricalPrice, Prices, Timestamp, Version};
use crate::{ use crate::{
AppState, CacheStrategy, extended::TransformResponseExtended, params::OptionalTimestampParam, AppState, CacheStrategy,
extended::TransformResponseExtended,
params::{Empty, OptionalTimestampParam},
}; };
pub trait GeneralRoutes { pub trait GeneralRoutes {
@@ -18,7 +20,7 @@ impl GeneralRoutes for ApiRouter<AppState> {
self.api_route( self.api_route(
"/api/v1/difficulty-adjustment", "/api/v1/difficulty-adjustment",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state state
.cached_json(&headers, CacheStrategy::Tip, &uri, |q| { .cached_json(&headers, CacheStrategy::Tip, &uri, |q| {
q.difficulty_adjustment() q.difficulty_adjustment()
@@ -39,7 +41,7 @@ impl GeneralRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/prices", "/api/v1/prices",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state state
.cached_json(&headers, state.mempool_cache(), &uri, |q| { .cached_json(&headers, state.mempool_cache(), &uri, |q| {
Ok(Prices { Ok(Prices {

View File

@@ -5,7 +5,7 @@ use axum::{
}; };
use brk_types::{Dollars, MempoolInfo, MempoolRecentTx, Txid}; use brk_types::{Dollars, MempoolInfo, MempoolRecentTx, Txid};
use crate::{AppState, extended::TransformResponseExtended}; use crate::{AppState, extended::TransformResponseExtended, params::Empty};
pub trait MempoolRoutes { pub trait MempoolRoutes {
fn add_mempool_routes(self) -> Self; fn add_mempool_routes(self) -> Self;
@@ -16,7 +16,7 @@ impl MempoolRoutes for ApiRouter<AppState> {
self.api_route( self.api_route(
"/api/mempool", "/api/mempool",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state state
.cached_json(&headers, state.mempool_cache(), &uri, |q| q.mempool_info()) .cached_json(&headers, state.mempool_cache(), &uri, |q| q.mempool_info())
.await .await
@@ -35,7 +35,7 @@ impl MempoolRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/mempool/txids", "/api/mempool/txids",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state state
.cached_json(&headers, state.mempool_cache(), &uri, |q| q.mempool_txids()) .cached_json(&headers, state.mempool_cache(), &uri, |q| q.mempool_txids())
.await .await
@@ -54,7 +54,7 @@ impl MempoolRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/mempool/recent", "/api/mempool/recent",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state state
.cached_json(&headers, state.mempool_cache(), &uri, |q| q.mempool_recent()) .cached_json(&headers, state.mempool_cache(), &uri, |q| q.mempool_recent())
.await .await
@@ -73,7 +73,7 @@ impl MempoolRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/mempool/price", "/api/mempool/price",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state state
.cached_json(&headers, state.mempool_cache(), &uri, |q| q.live_price()) .cached_json(&headers, state.mempool_cache(), &uri, |q| q.live_price())
.await .await

View File

@@ -17,7 +17,7 @@ use brk_types::{
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{CacheStrategy, Error, extended::TransformResponseExtended}; use crate::{CacheStrategy, Error, extended::TransformResponseExtended, params::Empty};
use super::AppState; use super::AppState;
use super::series_legacy; use super::series_legacy;
@@ -46,7 +46,7 @@ impl ApiMetricsLegacyRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/metrics", "/api/metrics",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Deploy, &uri, |q| Ok(q.series_catalog().clone())).await state.cached_json(&headers, CacheStrategy::Deploy, &uri, |q| Ok(q.series_catalog().clone())).await
}, },
|op| op |op| op
@@ -68,6 +68,7 @@ impl ApiMetricsLegacyRoutes for ApiRouter<AppState> {
async | async |
uri: Uri, uri: Uri,
headers: HeaderMap, headers: HeaderMap,
_: Empty,
State(state): State<AppState> State(state): State<AppState>
| { | {
state.cached_json(&headers, CacheStrategy::Deploy, &uri, |q| Ok(q.series_count())).await state.cached_json(&headers, CacheStrategy::Deploy, &uri, |q| Ok(q.series_count())).await
@@ -91,6 +92,7 @@ impl ApiMetricsLegacyRoutes for ApiRouter<AppState> {
async | async |
uri: Uri, uri: Uri,
headers: HeaderMap, headers: HeaderMap,
_: Empty,
State(state): State<AppState> State(state): State<AppState>
| { | {
state.cached_json(&headers, CacheStrategy::Deploy, &uri, |q| Ok(q.indexes().to_vec())).await state.cached_json(&headers, CacheStrategy::Deploy, &uri, |q| Ok(q.indexes().to_vec())).await
@@ -186,6 +188,7 @@ impl ApiMetricsLegacyRoutes for ApiRouter<AppState> {
async | async |
uri: Uri, uri: Uri,
headers: HeaderMap, headers: HeaderMap,
_: Empty,
State(state): State<AppState>, State(state): State<AppState>,
Path(path): Path<LegacySeriesParam> Path(path): Path<LegacySeriesParam>
| { | {
@@ -273,6 +276,7 @@ impl ApiMetricsLegacyRoutes for ApiRouter<AppState> {
get_with( get_with(
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
_: Empty,
State(state): State<AppState>, State(state): State<AppState>,
Path(path): Path<LegacySeriesWithIndex>| { Path(path): Path<LegacySeriesWithIndex>| {
state state
@@ -299,6 +303,7 @@ impl ApiMetricsLegacyRoutes for ApiRouter<AppState> {
get_with( get_with(
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
_: Empty,
State(state): State<AppState>, State(state): State<AppState>,
Path(path): Path<LegacySeriesWithIndex>| { Path(path): Path<LegacySeriesWithIndex>| {
state state
@@ -325,6 +330,7 @@ impl ApiMetricsLegacyRoutes for ApiRouter<AppState> {
get_with( get_with(
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
_: Empty,
State(state): State<AppState>, State(state): State<AppState>,
Path(path): Path<LegacySeriesWithIndex>| { Path(path): Path<LegacySeriesWithIndex>| {
state state

View File

@@ -14,7 +14,7 @@ use brk_types::{
use crate::{ use crate::{
AppState, CacheStrategy, AppState, CacheStrategy,
extended::TransformResponseExtended, extended::TransformResponseExtended,
params::{BlockCountParam, PoolSlugAndHeightParam, PoolSlugParam, TimePeriodParam}, params::{BlockCountParam, Empty, PoolSlugAndHeightParam, PoolSlugParam, TimePeriodParam},
}; };
pub trait MiningRoutes { pub trait MiningRoutes {
@@ -30,7 +30,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/pools", "/api/v1/mining/pools",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
// Pool list is compiled-in, only changes on deploy // Pool list is compiled-in, only changes on deploy
state.cached_json(&headers, CacheStrategy::Deploy, &uri, |q| Ok(q.all_pools())).await state.cached_json(&headers, CacheStrategy::Deploy, &uri, |q| Ok(q.all_pools())).await
}, },
@@ -48,7 +48,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/pools/{time_period}", "/api/v1/mining/pools/{time_period}",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.mining_pools(path.time_period)).await state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.mining_pools(path.time_period)).await
}, },
|op| { |op| {
@@ -66,7 +66,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/pool/{slug}", "/api/v1/mining/pool/{slug}",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(path): Path<PoolSlugParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(path): Path<PoolSlugParam>, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.pool_detail(path.slug)).await state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.pool_detail(path.slug)).await
}, },
|op| { |op| {
@@ -84,7 +84,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/hashrate/pools", "/api/v1/mining/hashrate/pools",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, |q| q.pools_hashrate(None)).await state.cached_json(&headers, CacheStrategy::Tip, &uri, |q| q.pools_hashrate(None)).await
}, },
|op| { |op| {
@@ -101,7 +101,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/hashrate/pools/{time_period}", "/api/v1/mining/hashrate/pools/{time_period}",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.pools_hashrate(Some(path.time_period))).await state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.pools_hashrate(Some(path.time_period))).await
}, },
|op| { |op| {
@@ -119,7 +119,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/pool/{slug}/hashrate", "/api/v1/mining/pool/{slug}/hashrate",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(path): Path<PoolSlugParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(path): Path<PoolSlugParam>, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.pool_hashrate(path.slug)).await state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.pool_hashrate(path.slug)).await
}, },
|op| { |op| {
@@ -137,7 +137,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/pool/{slug}/blocks", "/api/v1/mining/pool/{slug}/blocks",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(path): Path<PoolSlugParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(path): Path<PoolSlugParam>, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.pool_blocks(path.slug, None)).await state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.pool_blocks(path.slug, None)).await
}, },
|op| { |op| {
@@ -155,7 +155,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/pool/{slug}/blocks/{height}", "/api/v1/mining/pool/{slug}/blocks/{height}",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(PoolSlugAndHeightParam {slug, height}): Path<PoolSlugAndHeightParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(PoolSlugAndHeightParam {slug, height}): Path<PoolSlugAndHeightParam>, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, state.height_cache(Version::ONE, height), &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| {
@@ -173,7 +173,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/hashrate", "/api/v1/mining/hashrate",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, |q| q.hashrate(None)).await state.cached_json(&headers, CacheStrategy::Tip, &uri, |q| q.hashrate(None)).await
}, },
|op| { |op| {
@@ -190,7 +190,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/hashrate/{time_period}", "/api/v1/mining/hashrate/{time_period}",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.hashrate(Some(path.time_period))).await state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.hashrate(Some(path.time_period))).await
}, },
|op| { |op| {
@@ -208,7 +208,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/difficulty-adjustments", "/api/v1/mining/difficulty-adjustments",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, |q| q.difficulty_adjustments(None)).await state.cached_json(&headers, CacheStrategy::Tip, &uri, |q| q.difficulty_adjustments(None)).await
}, },
|op| { |op| {
@@ -225,7 +225,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/difficulty-adjustments/{time_period}", "/api/v1/mining/difficulty-adjustments/{time_period}",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.difficulty_adjustments(Some(path.time_period))).await state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.difficulty_adjustments(Some(path.time_period))).await
}, },
|op| { |op| {
@@ -243,7 +243,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/reward-stats/{block_count}", "/api/v1/mining/reward-stats/{block_count}",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(path): Path<BlockCountParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(path): Path<BlockCountParam>, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.reward_stats(path.block_count)).await state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.reward_stats(path.block_count)).await
}, },
|op| { |op| {
@@ -261,7 +261,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/blocks/fees/{time_period}", "/api/v1/mining/blocks/fees/{time_period}",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.block_fees(path.time_period)).await state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.block_fees(path.time_period)).await
}, },
|op| { |op| {
@@ -279,7 +279,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/blocks/rewards/{time_period}", "/api/v1/mining/blocks/rewards/{time_period}",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.block_rewards(path.time_period)).await state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.block_rewards(path.time_period)).await
}, },
|op| { |op| {
@@ -297,7 +297,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/blocks/fee-rates/{time_period}", "/api/v1/mining/blocks/fee-rates/{time_period}",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.block_fee_rates(path.time_period)).await state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.block_fee_rates(path.time_period)).await
}, },
|op| { |op| {
@@ -315,7 +315,7 @@ impl MiningRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/mining/blocks/sizes-weights/{time_period}", "/api/v1/mining/blocks/sizes-weights/{time_period}",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(path): Path<TimePeriodParam>, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.block_sizes_weights(path.time_period)).await state.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| q.block_sizes_weights(path.time_period)).await
}, },
|op| { |op| {

View File

@@ -25,7 +25,7 @@ use brk_types::{
use crate::{ use crate::{
AppState, CacheParams, CacheStrategy, Result, AppState, CacheParams, CacheStrategy, Result,
extended::{HeaderMapExtended, TransformResponseExtended}, extended::{HeaderMapExtended, TransformResponseExtended},
params::SeriesParam, params::{Empty, SeriesParam},
}; };
/// Shared response pipeline for every series endpoint. /// Shared response pipeline for every series endpoint.
@@ -42,9 +42,7 @@ pub(super) async fn serve(
to_bytes: impl FnOnce(&BrkQuery, ResolvedQuery) -> BrkResult<Bytes> + Send + 'static, to_bytes: impl FnOnce(&BrkQuery, ResolvedQuery) -> BrkResult<Bytes> + Send + 'static,
) -> Result<Response> { ) -> Result<Response> {
let max_weight = state.max_weight_for(&addr); let max_weight = state.max_weight_for(&addr);
let resolved = state let resolved = state.run(move |q| q.resolve(params, max_weight)).await?;
.run(move |q| q.resolve(params, max_weight))
.await?;
let format = resolved.format(); let format = resolved.format();
let csv_filename = resolved.csv_filename(); let csv_filename = resolved.csv_filename();
@@ -114,7 +112,7 @@ impl ApiSeriesRoutes for ApiRouter<AppState> {
self.api_route( self.api_route(
"/api/series", "/api/series",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, CacheStrategy::Deploy, &uri, |q| Ok(q.series_catalog().clone())).await state.cached_json(&headers, CacheStrategy::Deploy, &uri, |q| Ok(q.series_catalog().clone())).await
}, },
|op| op |op| op
@@ -135,6 +133,7 @@ impl ApiSeriesRoutes for ApiRouter<AppState> {
async | async |
uri: Uri, uri: Uri,
headers: HeaderMap, headers: HeaderMap,
_: Empty,
State(state): State<AppState> State(state): State<AppState>
| { | {
state.cached_json(&headers, CacheStrategy::Deploy, &uri, |q| Ok(q.series_count())).await state.cached_json(&headers, CacheStrategy::Deploy, &uri, |q| Ok(q.series_count())).await
@@ -154,6 +153,7 @@ impl ApiSeriesRoutes for ApiRouter<AppState> {
async | async |
uri: Uri, uri: Uri,
headers: HeaderMap, headers: HeaderMap,
_: Empty,
State(state): State<AppState> State(state): State<AppState>
| { | {
state.cached_json(&headers, CacheStrategy::Deploy, &uri, |q| Ok(q.indexes().to_vec())).await state.cached_json(&headers, CacheStrategy::Deploy, &uri, |q| Ok(q.indexes().to_vec())).await
@@ -216,6 +216,7 @@ impl ApiSeriesRoutes for ApiRouter<AppState> {
async | async |
uri: Uri, uri: Uri,
headers: HeaderMap, headers: HeaderMap,
_: Empty,
State(state): State<AppState>, State(state): State<AppState>,
Path(path): Path<SeriesParam> Path(path): Path<SeriesParam>
| { | {
@@ -309,6 +310,7 @@ impl ApiSeriesRoutes for ApiRouter<AppState> {
get_with( get_with(
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
_: Empty,
State(state): State<AppState>, State(state): State<AppState>,
Path(path): Path<SeriesNameWithIndex>| { Path(path): Path<SeriesNameWithIndex>| {
state state
@@ -334,6 +336,7 @@ impl ApiSeriesRoutes for ApiRouter<AppState> {
get_with( get_with(
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
_: Empty,
State(state): State<AppState>, State(state): State<AppState>,
Path(path): Path<SeriesNameWithIndex>| { Path(path): Path<SeriesNameWithIndex>| {
state state
@@ -357,6 +360,7 @@ impl ApiSeriesRoutes for ApiRouter<AppState> {
get_with( get_with(
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
_: Empty,
State(state): State<AppState>, State(state): State<AppState>,
Path(path): Path<SeriesNameWithIndex>| { Path(path): Path<SeriesNameWithIndex>| {
state state

View File

@@ -29,6 +29,7 @@ use vecdb::ReadableOptionVec;
use crate::{ use crate::{
AppState, CacheStrategy, Result, AppState, CacheStrategy, Result,
extended::{HeaderMapExtended, TransformResponseExtended}, extended::{HeaderMapExtended, TransformResponseExtended},
params::Empty,
}; };
pub const SUNSET: &str = "2027-01-01T00:00:00Z"; pub const SUNSET: &str = "2027-01-01T00:00:00Z";
@@ -43,7 +44,8 @@ pub async fn handler(
Query(params): Query<SeriesSelection>, Query(params): Query<SeriesSelection>,
State(state): State<AppState>, State(state): State<AppState>,
) -> Result<Response> { ) -> Result<Response> {
let mut response = super::series::serve(state, uri, headers, addr, params, legacy_bytes).await?; let mut response =
super::series::serve(state, uri, headers, addr, params, legacy_bytes).await?;
if response.status() == StatusCode::OK { if response.status() == StatusCode::OK {
response.headers_mut().insert_deprecation(SUNSET); response.headers_mut().insert_deprecation(SUNSET);
} }
@@ -151,7 +153,7 @@ impl ApiSeriesLegacyRoutes for ApiRouter<AppState> {
self.api_route( self.api_route(
"/api/series/cost-basis", "/api/series/cost-basis",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state state
.cached_json(&headers, CacheStrategy::Deploy, &uri, |q| q.urpd_cohorts()) .cached_json(&headers, CacheStrategy::Deploy, &uri, |q| q.urpd_cohorts())
.await .await
@@ -177,6 +179,7 @@ impl ApiSeriesLegacyRoutes for ApiRouter<AppState> {
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(params): Path<CostBasisCohortParam>, Path(params): Path<CostBasisCohortParam>,
_: Empty,
State(state): State<AppState>| { State(state): State<AppState>| {
state state
.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| { .cached_json(&headers, CacheStrategy::Tip, &uri, move |q| {

View File

@@ -7,7 +7,7 @@ use axum::{
}; };
use brk_types::{DiskUsage, Health, SyncStatus}; use brk_types::{DiskUsage, Health, SyncStatus};
use crate::{CacheStrategy, VERSION, extended::TransformResponseExtended}; use crate::{CacheStrategy, VERSION, extended::TransformResponseExtended, params::Empty};
use super::AppState; use super::AppState;
@@ -20,7 +20,7 @@ impl ServerRoutes for ApiRouter<AppState> {
self.api_route( self.api_route(
"/health", "/health",
get_with( get_with(
async |State(state): State<AppState>| -> axum::Json<Health> { async |_: Empty, State(state): State<AppState>| -> axum::Json<Health> {
let uptime = state.started_instant.elapsed(); let uptime = state.started_instant.elapsed();
let started_at = state.started_at.to_string(); let started_at = state.started_at.to_string();
let sync = state let sync = state
@@ -55,7 +55,7 @@ impl ServerRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/version", "/version",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state state
.cached_json(&headers, CacheStrategy::Deploy, &uri, |_| { .cached_json(&headers, CacheStrategy::Deploy, &uri, |_| {
Ok(env!("CARGO_PKG_VERSION")) Ok(env!("CARGO_PKG_VERSION"))
@@ -75,7 +75,7 @@ impl ServerRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/server/sync", "/api/server/sync",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state state
.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| { .cached_json(&headers, CacheStrategy::Tip, &uri, move |q| {
let tip_height = q.client().get_last_height()?; let tip_height = q.client().get_last_height()?;
@@ -99,7 +99,7 @@ impl ServerRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/server/disk", "/api/server/disk",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
let brk_path = state.data_path.clone(); let brk_path = state.data_path.clone();
state state
.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| { .cached_json(&headers, CacheStrategy::Tip, &uri, move |q| {

View File

@@ -5,15 +5,16 @@ use aide::axum::{
use axum::{ use axum::{
extract::{Path, State}, extract::{Path, State},
http::{HeaderMap, Uri}, http::{HeaderMap, Uri},
response::Response,
}; };
use brk_types::{ use brk_types::{
CpfpInfo, MerkleProof, RbfResponse, Transaction, TxOutspend, TxStatus, Txid, Version, CpfpInfo, MerkleProof, RbfResponse, Transaction, TxOutspend, TxStatus, Txid, Version,
}; };
use crate::{ use crate::{
AppState, CacheStrategy, AppState, CacheStrategy, Error, Result,
extended::TransformResponseExtended, extended::TransformResponseExtended,
params::{TxIndexParam, TxidParam, TxidVout, TxidsParam}, params::{Empty, TxIndexParam, TxidParam, TxidVout, TxidsParam},
}; };
pub trait TxRoutes { pub trait TxRoutes {
@@ -26,7 +27,7 @@ impl TxRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/tx-index/{index}", "/api/tx-index/{index}",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(param): Path<TxIndexParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(param): Path<TxIndexParam>, _: Empty, State(state): State<AppState>| {
state.cached_text(&headers, CacheStrategy::Immutable(Version::ONE), &uri, move |q| q.txid_by_index(param.index).map(|t| t.to_string())).await state.cached_text(&headers, CacheStrategy::Immutable(Version::ONE), &uri, move |q| q.txid_by_index(param.index).map(|t| t.to_string())).await
}, },
|op| op |op| op
@@ -44,7 +45,7 @@ impl TxRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/cpfp/{txid}", "/api/v1/cpfp/{txid}",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(param): Path<TxidParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(param): Path<TxidParam>, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, state.tx_cache(Version::ONE, &param.txid), &uri, move |q| q.cpfp(&param.txid)).await state.cached_json(&headers, state.tx_cache(Version::ONE, &param.txid), &uri, move |q| q.cpfp(&param.txid)).await
}, },
|op| op |op| op
@@ -62,7 +63,7 @@ impl TxRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/tx/{txid}/rbf", "/api/v1/tx/{txid}/rbf",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(param): Path<TxidParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(param): Path<TxidParam>, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, state.mempool_cache(), &uri, move |q| q.tx_rbf(&param.txid)).await state.cached_json(&headers, state.mempool_cache(), &uri, move |q| q.tx_rbf(&param.txid)).await
}, },
|op| op |op| op
@@ -84,6 +85,7 @@ impl TxRoutes for ApiRouter<AppState> {
uri: Uri, uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(param): Path<TxidParam>, Path(param): Path<TxidParam>,
_: Empty,
State(state): State<AppState> State(state): State<AppState>
| { | {
state.cached_json(&headers, state.tx_cache(Version::ONE, &param.txid), &uri, move |q| q.transaction(&param.txid)).await state.cached_json(&headers, state.tx_cache(Version::ONE, &param.txid), &uri, move |q| q.transaction(&param.txid)).await
@@ -109,6 +111,7 @@ impl TxRoutes for ApiRouter<AppState> {
uri: Uri, uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(param): Path<TxidParam>, Path(param): Path<TxidParam>,
_: Empty,
State(state): State<AppState> State(state): State<AppState>
| { | {
state.cached_text(&headers, state.tx_cache(Version::ONE, &param.txid), &uri, move |q| q.transaction_hex(&param.txid)).await state.cached_text(&headers, state.tx_cache(Version::ONE, &param.txid), &uri, move |q| q.transaction_hex(&param.txid)).await
@@ -130,7 +133,7 @@ impl TxRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/tx/{txid}/merkleblock-proof", "/api/tx/{txid}/merkleblock-proof",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(param): Path<TxidParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(param): Path<TxidParam>, _: Empty, State(state): State<AppState>| {
state.cached_text(&headers, state.tx_cache(Version::ONE, &param.txid), &uri, move |q| q.merkleblock_proof(&param.txid)).await state.cached_text(&headers, state.tx_cache(Version::ONE, &param.txid), &uri, move |q| q.merkleblock_proof(&param.txid)).await
}, },
|op| op |op| op
@@ -148,7 +151,7 @@ impl TxRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/tx/{txid}/merkle-proof", "/api/tx/{txid}/merkle-proof",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(param): Path<TxidParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(param): Path<TxidParam>, _: Empty, State(state): State<AppState>| {
state.cached_json(&headers, state.tx_cache(Version::ONE, &param.txid), &uri, move |q| q.merkle_proof(&param.txid)).await state.cached_json(&headers, state.tx_cache(Version::ONE, &param.txid), &uri, move |q| q.merkle_proof(&param.txid)).await
}, },
|op| op |op| op
@@ -170,6 +173,7 @@ impl TxRoutes for ApiRouter<AppState> {
uri: Uri, uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(path): Path<TxidVout>, Path(path): Path<TxidVout>,
_: Empty,
State(state): State<AppState> State(state): State<AppState>
| { | {
let v = Version::ONE; let v = Version::ONE;
@@ -204,6 +208,7 @@ impl TxRoutes for ApiRouter<AppState> {
uri: Uri, uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(param): Path<TxidParam>, Path(param): Path<TxidParam>,
_: Empty,
State(state): State<AppState> State(state): State<AppState>
| { | {
let v = Version::ONE; let v = Version::ONE;
@@ -232,7 +237,7 @@ impl TxRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/tx/{txid}/raw", "/api/tx/{txid}/raw",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, Path(param): Path<TxidParam>, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, Path(param): Path<TxidParam>, _: Empty, State(state): State<AppState>| {
state.cached_bytes(&headers, state.tx_cache(Version::ONE, &param.txid), &uri, move |q| q.transaction_raw(&param.txid)).await state.cached_bytes(&headers, state.tx_cache(Version::ONE, &param.txid), &uri, move |q| q.transaction_raw(&param.txid)).await
}, },
|op| op |op| op
@@ -254,6 +259,7 @@ impl TxRoutes for ApiRouter<AppState> {
uri: Uri, uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(param): Path<TxidParam>, Path(param): Path<TxidParam>,
_: Empty,
State(state): State<AppState> State(state): State<AppState>
| { | {
state.cached_json(&headers, state.tx_cache(Version::ONE, &param.txid), &uri, move |q| q.transaction_status(&param.txid)).await state.cached_json(&headers, state.tx_cache(Version::ONE, &param.txid), &uri, move |q| q.transaction_status(&param.txid)).await
@@ -275,9 +281,10 @@ impl TxRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/v1/transaction-times", "/api/v1/transaction-times",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| -> Result<Response> {
let params = TxidsParam::from_query(uri.query().unwrap_or("")); let params = TxidsParam::from_query(uri.query().unwrap_or(""))
state.cached_json(&headers, state.mempool_cache(), &uri, move |q| q.transaction_times(&params.txids)).await .map_err(Error::bad_request)?;
Ok(state.cached_json(&headers, state.mempool_cache(), &uri, move |q| q.transaction_times(&params.txids)).await)
}, },
|op| op |op| op
.id("get_transaction_times") .id("get_transaction_times")
@@ -292,12 +299,12 @@ impl TxRoutes for ApiRouter<AppState> {
.api_route( .api_route(
"/api/tx", "/api/tx",
post_with( post_with(
async |State(state): State<AppState>, body: String| { async |_: Empty, State(state): State<AppState>, body: String| {
let hex = body.trim().to_string(); let hex = body.trim().to_string();
state.run(move |q| q.broadcast_transaction(&hex)) state.run(move |q| q.broadcast_transaction(&hex))
.await .await
.map(|txid| txid.to_string()) .map(|txid| txid.to_string())
.map_err(crate::Error::from) .map_err(Error::from)
}, },
|op| { |op| {
op.id("post_tx") op.id("post_tx")

View File

@@ -8,7 +8,7 @@ use brk_types::{Cohort, Date, Urpd, Version};
use crate::{ use crate::{
CacheStrategy, CacheStrategy,
extended::TransformResponseExtended, extended::TransformResponseExtended,
params::{UrpdCohortParam, UrpdParams, UrpdQuery}, params::{Empty, UrpdCohortParam, UrpdParams, UrpdQuery},
}; };
use super::AppState; use super::AppState;
@@ -22,7 +22,7 @@ impl ApiUrpdRoutes for ApiRouter<AppState> {
self.api_route( self.api_route(
"/api/urpd", "/api/urpd",
get_with( get_with(
async |uri: Uri, headers: HeaderMap, State(state): State<AppState>| { async |uri: Uri, headers: HeaderMap, _: Empty, State(state): State<AppState>| {
state state
.cached_json(&headers, CacheStrategy::Deploy, &uri, |q| q.urpd_cohorts()) .cached_json(&headers, CacheStrategy::Deploy, &uri, |q| q.urpd_cohorts())
.await .await
@@ -47,6 +47,7 @@ impl ApiUrpdRoutes for ApiRouter<AppState> {
async |uri: Uri, async |uri: Uri,
headers: HeaderMap, headers: HeaderMap,
Path(params): Path<UrpdCohortParam>, Path(params): Path<UrpdCohortParam>,
_: Empty,
State(state): State<AppState>| { State(state): State<AppState>| {
state state
.cached_json(&headers, CacheStrategy::Tip, &uri, move |q| { .cached_json(&headers, CacheStrategy::Tip, &uri, move |q| {

View File

@@ -52,9 +52,5 @@ pub(crate) fn init(mode: CdnCacheMode) {
/// Cached-tier directive for stable responses. Defaults to `Live` if [`init`] /// Cached-tier directive for stable responses. Defaults to `Live` if [`init`]
/// was never called (tests, library use without a `Server`). /// was never called (tests, library use without a `Server`).
pub(super) fn cdn_cached() -> &'static str { pub(super) fn cdn_cached() -> &'static str {
CDN_CACHE_MODE CDN_CACHE_MODE.get().copied().unwrap_or_default().as_str()
.get()
.copied()
.unwrap_or_default()
.as_str()
} }

View File

@@ -0,0 +1,27 @@
use aide::OperationInput;
use axum::{extract::FromRequestParts, http::request::Parts};
use crate::Error;
/// Extractor that rejects requests carrying any query string.
/// Used on path-only endpoints to prevent cache-busting via injected
/// query params (the cache key includes the URI).
pub struct Empty;
impl<S> FromRequestParts<S> for Empty
where
S: Send + Sync,
{
type Rejection = Error;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
match parts.uri.query() {
Some(q) if !q.is_empty() => Err(Error::bad_request(format!(
"this endpoint does not accept query parameters (got `?{q}`)"
))),
_ => Ok(Empty),
}
}
}
impl OperationInput for Empty {}

View File

@@ -4,6 +4,7 @@ mod block_count_param;
mod blockhash_param; mod blockhash_param;
mod blockhash_start_index; mod blockhash_start_index;
mod blockhash_tx_index; mod blockhash_tx_index;
mod empty;
mod height_param; mod height_param;
mod pool_slug_param; mod pool_slug_param;
mod series_param; mod series_param;
@@ -22,6 +23,7 @@ pub use block_count_param::*;
pub use blockhash_param::*; pub use blockhash_param::*;
pub use blockhash_start_index::*; pub use blockhash_start_index::*;
pub use blockhash_tx_index::*; pub use blockhash_tx_index::*;
pub use empty::*;
pub use height_param::*; pub use height_param::*;
pub use pool_slug_param::*; pub use pool_slug_param::*;
pub use series_param::*; pub use series_param::*;

View File

@@ -13,19 +13,25 @@ pub struct TxidsParam {
impl TxidsParam { impl TxidsParam {
/// Parsed manually from URI since serde_urlencoded doesn't support repeated keys. /// Parsed manually from URI since serde_urlencoded doesn't support repeated keys.
pub fn from_query(query: &str) -> Self { /// Rejects unknown keys to prevent cache-busting via injected query params.
Self { pub fn from_query(query: &str) -> Result<Self, String> {
txids: query if query.is_empty() {
.split('&') return Ok(Self { txids: Vec::new() });
.filter_map(|pair| {
let (key, val) = pair.split_once('=')?;
if key == "txId[]" || key == "txId%5B%5D" {
Txid::from_str(val).ok()
} else {
None
}
})
.collect(),
} }
let mut txids = Vec::new();
for pair in query.split('&') {
let (key, val) = pair.split_once('=').ok_or_else(|| {
format!("malformed query parameter `{pair}`, expected `txId[]=<txid>`")
})?;
if key == "txId[]" || key == "txId%5B%5D" {
let txid = Txid::from_str(val).map_err(|e| format!("invalid txid `{val}`: {e}"))?;
txids.push(txid);
} else {
return Err(format!(
"unknown query parameter `{key}`, expected `txId[]`"
));
}
}
Ok(Self { txids })
} }
} }

View File

@@ -197,10 +197,9 @@ impl AppState {
let encoding = ContentEncoding::negotiate(headers); let encoding = ContentEncoding::negotiate(headers);
let cache_key = format!("{}-{}-{}", uri, params.etag, encoding.as_str()); let cache_key = format!("{}-{}-{}", uri, params.etag, encoding.as_str());
let result = self let result = self
.get_or_insert( .get_or_insert(&cache_key, async move {
&cache_key, self.run(move |q| f(q, encoding)).await
async move { self.run(move |q| f(q, encoding)).await }, })
)
.await; .await;
match result { match result {
@@ -296,7 +295,10 @@ impl AppState {
uri, uri,
params, params,
|h| { |h| {
h.insert(header::CONTENT_TYPE, HeaderValue::from_static("application/json")); h.insert(
header::CONTENT_TYPE,
HeaderValue::from_static("application/json"),
);
}, },
move |_q, enc| { move |_q, enc| {
let value = value_result?; let value = value_result?;

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More