global: snapshot

This commit is contained in:
nym21
2026-03-18 12:02:53 +01:00
parent 04ddc6223e
commit b397b811f9
46 changed files with 943 additions and 1197 deletions

View File

@@ -1087,34 +1087,34 @@ pub struct CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern {
/// Pattern struct for repeated tree structure.
pub struct AverageCumulativeMaxMedianMinPct10Pct25Pct75Pct90RollingSumPattern {
pub average: SeriesPattern18<StoredU64>,
pub cumulative: SeriesPattern18<StoredU64>,
pub max: SeriesPattern18<StoredU64>,
pub median: SeriesPattern18<StoredU64>,
pub min: SeriesPattern18<StoredU64>,
pub pct10: SeriesPattern18<StoredU64>,
pub pct25: SeriesPattern18<StoredU64>,
pub pct75: SeriesPattern18<StoredU64>,
pub pct90: SeriesPattern18<StoredU64>,
pub average: SeriesPattern1<StoredU64>,
pub cumulative: SeriesPattern1<StoredU64>,
pub max: SeriesPattern1<StoredU64>,
pub median: SeriesPattern1<StoredU64>,
pub min: SeriesPattern1<StoredU64>,
pub pct10: SeriesPattern1<StoredU64>,
pub pct25: SeriesPattern1<StoredU64>,
pub pct75: SeriesPattern1<StoredU64>,
pub pct90: SeriesPattern1<StoredU64>,
pub rolling: AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern,
pub sum: SeriesPattern18<StoredU64>,
pub sum: SeriesPattern1<StoredU64>,
}
impl AverageCumulativeMaxMedianMinPct10Pct25Pct75Pct90RollingSumPattern {
/// Create a new pattern node with accumulated series name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
average: SeriesPattern18::new(client.clone(), _m(&acc, "average")),
cumulative: SeriesPattern18::new(client.clone(), _m(&acc, "cumulative")),
max: SeriesPattern18::new(client.clone(), _m(&acc, "max")),
median: SeriesPattern18::new(client.clone(), _m(&acc, "median")),
min: SeriesPattern18::new(client.clone(), _m(&acc, "min")),
pct10: SeriesPattern18::new(client.clone(), _m(&acc, "pct10")),
pct25: SeriesPattern18::new(client.clone(), _m(&acc, "pct25")),
pct75: SeriesPattern18::new(client.clone(), _m(&acc, "pct75")),
pct90: SeriesPattern18::new(client.clone(), _m(&acc, "pct90")),
average: SeriesPattern1::new(client.clone(), _m(&acc, "average")),
cumulative: SeriesPattern1::new(client.clone(), _m(&acc, "cumulative")),
max: SeriesPattern1::new(client.clone(), _m(&acc, "max")),
median: SeriesPattern1::new(client.clone(), _m(&acc, "median")),
min: SeriesPattern1::new(client.clone(), _m(&acc, "min")),
pct10: SeriesPattern1::new(client.clone(), _m(&acc, "pct10")),
pct25: SeriesPattern1::new(client.clone(), _m(&acc, "pct25")),
pct75: SeriesPattern1::new(client.clone(), _m(&acc, "pct75")),
pct90: SeriesPattern1::new(client.clone(), _m(&acc, "pct90")),
rolling: AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern::new(client.clone(), acc.clone()),
sum: SeriesPattern18::new(client.clone(), _m(&acc, "sum")),
sum: SeriesPattern1::new(client.clone(), _m(&acc, "sum")),
}
}
}
@@ -1260,6 +1260,34 @@ impl AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern {
}
}
/// Pattern struct for repeated tree structure.
pub struct AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2 {
pub average: SeriesPattern18<Weight>,
pub max: SeriesPattern18<Weight>,
pub median: SeriesPattern18<Weight>,
pub min: SeriesPattern18<Weight>,
pub pct10: SeriesPattern18<Weight>,
pub pct25: SeriesPattern18<Weight>,
pub pct75: SeriesPattern18<Weight>,
pub pct90: SeriesPattern18<Weight>,
}
impl AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2 {
/// Create a new pattern node with accumulated series name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
average: SeriesPattern18::new(client.clone(), _m(&acc, "average")),
max: SeriesPattern18::new(client.clone(), _m(&acc, "max")),
median: SeriesPattern18::new(client.clone(), _m(&acc, "median")),
min: SeriesPattern18::new(client.clone(), _m(&acc, "min")),
pct10: SeriesPattern18::new(client.clone(), _m(&acc, "pct10")),
pct25: SeriesPattern18::new(client.clone(), _m(&acc, "pct25")),
pct75: SeriesPattern18::new(client.clone(), _m(&acc, "pct75")),
pct90: SeriesPattern18::new(client.clone(), _m(&acc, "pct90")),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct BaseCapitulationCumulativeNegativeSumToValuePattern {
pub base: CentsUsdPattern2,
@@ -1286,28 +1314,28 @@ pub struct BpsCentsPercentilesRatioSatsSmaStdUsdPattern {
/// Pattern struct for repeated tree structure.
pub struct AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern<T> {
pub average: SeriesPattern18<T>,
pub max: SeriesPattern18<T>,
pub median: SeriesPattern18<T>,
pub min: SeriesPattern18<T>,
pub pct10: SeriesPattern18<T>,
pub pct25: SeriesPattern18<T>,
pub pct75: SeriesPattern18<T>,
pub pct90: SeriesPattern18<T>,
pub average: SeriesPattern1<T>,
pub max: SeriesPattern1<T>,
pub median: SeriesPattern1<T>,
pub min: SeriesPattern1<T>,
pub pct10: SeriesPattern1<T>,
pub pct25: SeriesPattern1<T>,
pub pct75: SeriesPattern1<T>,
pub pct90: SeriesPattern1<T>,
}
impl<T: DeserializeOwned> AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern<T> {
/// Create a new pattern node with accumulated series name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
average: SeriesPattern18::new(client.clone(), _m(&acc, "average")),
max: SeriesPattern18::new(client.clone(), _m(&acc, "max")),
median: SeriesPattern18::new(client.clone(), _m(&acc, "median")),
min: SeriesPattern18::new(client.clone(), _m(&acc, "min")),
pct10: SeriesPattern18::new(client.clone(), _m(&acc, "pct10")),
pct25: SeriesPattern18::new(client.clone(), _m(&acc, "pct25")),
pct75: SeriesPattern18::new(client.clone(), _m(&acc, "pct75")),
pct90: SeriesPattern18::new(client.clone(), _m(&acc, "pct90")),
average: SeriesPattern1::new(client.clone(), _m(&acc, "average")),
max: SeriesPattern1::new(client.clone(), _m(&acc, "max")),
median: SeriesPattern1::new(client.clone(), _m(&acc, "median")),
min: SeriesPattern1::new(client.clone(), _m(&acc, "min")),
pct10: SeriesPattern1::new(client.clone(), _m(&acc, "pct10")),
pct25: SeriesPattern1::new(client.clone(), _m(&acc, "pct25")),
pct75: SeriesPattern1::new(client.clone(), _m(&acc, "pct75")),
pct90: SeriesPattern1::new(client.clone(), _m(&acc, "pct90")),
}
}
}
@@ -3086,14 +3114,14 @@ impl SeriesTree_Blocks_Weight {
/// Series tree node.
pub struct SeriesTree_Blocks_Count {
pub target: SeriesPattern1<StoredU64>,
pub target: _1m1w1y24hPattern<StoredU64>,
pub total: BaseCumulativeSumPattern2,
}
impl SeriesTree_Blocks_Count {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
target: SeriesPattern1::new(client.clone(), "block_count_target".to_string()),
target: _1m1w1y24hPattern::new(client.clone(), "block_count_target".to_string()),
total: BaseCumulativeSumPattern2::new(client.clone(), "block_count".to_string()),
}
}
@@ -3198,17 +3226,17 @@ impl SeriesTree_Blocks_Lookback {
/// Series tree node.
pub struct SeriesTree_Blocks_Fullness {
pub bps: _1m1w1y24hBasePattern<BasisPoints16>,
pub ratio: SeriesPattern1<StoredF32>,
pub percent: SeriesPattern1<StoredF32>,
pub bps: SeriesPattern18<BasisPoints16>,
pub ratio: SeriesPattern18<StoredF32>,
pub percent: SeriesPattern18<StoredF32>,
}
impl SeriesTree_Blocks_Fullness {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
bps: _1m1w1y24hBasePattern::new(client.clone(), "block_fullness_bps".to_string()),
ratio: SeriesPattern1::new(client.clone(), "block_fullness_ratio".to_string()),
percent: SeriesPattern1::new(client.clone(), "block_fullness".to_string()),
bps: SeriesPattern18::new(client.clone(), "block_fullness_bps".to_string()),
ratio: SeriesPattern18::new(client.clone(), "block_fullness_ratio".to_string()),
percent: SeriesPattern18::new(client.clone(), "block_fullness".to_string()),
}
}
}
@@ -3302,14 +3330,31 @@ impl SeriesTree_Transactions_Count {
/// Series tree node.
pub struct SeriesTree_Transactions_Size {
pub vsize: _6bBlockTxPattern<VSize>,
pub weight: _6bBlockTxPattern<Weight>,
pub weight: SeriesTree_Transactions_Size_Weight,
}
impl SeriesTree_Transactions_Size {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
vsize: _6bBlockTxPattern::new(client.clone(), "tx_vsize".to_string()),
weight: _6bBlockTxPattern::new(client.clone(), "tx_weight".to_string()),
weight: SeriesTree_Transactions_Size_Weight::new(client.clone(), format!("{base_path}_weight")),
}
}
}
/// Series tree node.
pub struct SeriesTree_Transactions_Size_Weight {
pub tx_index: SeriesPattern19<Weight>,
pub block: AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2,
pub _6b: AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2,
}
impl SeriesTree_Transactions_Size_Weight {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
tx_index: SeriesPattern19::new(client.clone(), "tx_weight".to_string()),
block: AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2::new(client.clone(), "tx_weight".to_string()),
_6b: AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2::new(client.clone(), "tx_weight_6b".to_string()),
}
}
}
@@ -3353,7 +3398,6 @@ impl SeriesTree_Transactions_Versions {
/// Series tree node.
pub struct SeriesTree_Transactions_Volume {
pub transfer_volume: BaseCumulativeSumPattern4,
pub output_volume: BaseCumulativeSumPattern4,
pub tx_per_sec: SeriesPattern1<StoredF32>,
pub outputs_per_sec: SeriesPattern1<StoredF32>,
pub inputs_per_sec: SeriesPattern1<StoredF32>,
@@ -3363,7 +3407,6 @@ impl SeriesTree_Transactions_Volume {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
transfer_volume: BaseCumulativeSumPattern4::new(client.clone(), "transfer_volume_bis".to_string()),
output_volume: BaseCumulativeSumPattern4::new(client.clone(), "output_volume".to_string()),
tx_per_sec: SeriesPattern1::new(client.clone(), "tx_per_sec".to_string()),
outputs_per_sec: SeriesPattern1::new(client.clone(), "outputs_per_sec".to_string()),
inputs_per_sec: SeriesPattern1::new(client.clone(), "inputs_per_sec".to_string()),

View File

@@ -5,7 +5,10 @@ use vecdb::Database;
use super::Vecs;
use crate::{
indexes,
internal::{BlockCountTarget, CachedWindowStarts, PerBlockCumulativeWithSums, ConstantVecs},
internal::{
BlockCountTarget24h, BlockCountTarget1w, BlockCountTarget1m, BlockCountTarget1y,
CachedWindowStarts, PerBlockCumulativeWithSums, ConstantVecs, Windows,
},
};
impl Vecs {
@@ -16,11 +19,12 @@ impl Vecs {
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
Ok(Self {
target: ConstantVecs::new::<BlockCountTarget>(
"block_count_target",
version,
indexes,
),
target: Windows {
_24h: ConstantVecs::new::<BlockCountTarget24h>("block_count_target_24h", version, indexes),
_1w: ConstantVecs::new::<BlockCountTarget1w>("block_count_target_1w", version, indexes),
_1m: ConstantVecs::new::<BlockCountTarget1m>("block_count_target_1m", version, indexes),
_1y: ConstantVecs::new::<BlockCountTarget1y>("block_count_target_1y", version, indexes),
},
total: PerBlockCumulativeWithSums::forced_import(
db,
"block_count",

View File

@@ -2,10 +2,10 @@ use brk_traversable::Traversable;
use brk_types::{StoredU32, StoredU64};
use vecdb::{Rw, StorageMode};
use crate::internal::{PerBlockCumulativeWithSums, ConstantVecs};
use crate::internal::{PerBlockCumulativeWithSums, ConstantVecs, Windows};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub target: ConstantVecs<StoredU64>,
pub target: Windows<ConstantVecs<StoredU64>>,
pub total: PerBlockCumulativeWithSums<StoredU32, StoredU64, M>,
}

View File

@@ -26,20 +26,12 @@ pub const DB_NAME: &str = "blocks";
pub(crate) const TARGET_BLOCKS_PER_DAY_F64: f64 = 144.0;
pub(crate) const TARGET_BLOCKS_PER_DAY_F32: f32 = 144.0;
pub(crate) const TARGET_BLOCKS_PER_MINUTE10: u64 = 1;
pub(crate) const TARGET_BLOCKS_PER_MINUTE30: u64 = 3;
pub(crate) const TARGET_BLOCKS_PER_HOUR1: u64 = 6;
pub(crate) const TARGET_BLOCKS_PER_HOUR4: u64 = 24;
pub(crate) const TARGET_BLOCKS_PER_HOUR12: u64 = 72;
pub(crate) const TARGET_BLOCKS_PER_DAY: u64 = 144;
pub(crate) const TARGET_BLOCKS_PER_DAY3: u64 = 3 * TARGET_BLOCKS_PER_DAY;
pub(crate) const TARGET_BLOCKS_PER_WEEK: u64 = 7 * TARGET_BLOCKS_PER_DAY;
pub(crate) const TARGET_BLOCKS_PER_MONTH: u64 = 30 * TARGET_BLOCKS_PER_DAY;
pub(crate) const TARGET_BLOCKS_PER_QUARTER: u64 = 3 * TARGET_BLOCKS_PER_MONTH;
pub(crate) const TARGET_BLOCKS_PER_SEMESTER: u64 = 2 * TARGET_BLOCKS_PER_QUARTER;
pub(crate) const TARGET_BLOCKS_PER_YEAR: u64 = 2 * TARGET_BLOCKS_PER_SEMESTER;
pub(crate) const TARGET_BLOCKS_PER_DECADE: u64 = 10 * TARGET_BLOCKS_PER_YEAR;
pub(crate) const TARGET_BLOCKS_PER_HALVING: u64 = 210_000;
pub(crate) const ONE_TERA_HASH: f64 = 1_000_000_000_000.0;
#[derive(Traversable)]

View File

@@ -12,16 +12,12 @@ impl Vecs {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.fullness
.compute(starting_indexes.height, exit, |vec| {
vec.compute_transform(
starting_indexes.height,
&indexer.vecs.blocks.weight,
|(h, weight, ..)| (h, BasisPoints16::from(weight.fullness())),
exit,
)?;
Ok(())
})?;
self.fullness.bps.compute_transform(
starting_indexes.height,
&indexer.vecs.blocks.weight,
|(h, weight, ..)| (h, BasisPoints16::from(weight.fullness())),
exit,
)?;
Ok(())
}

View File

@@ -6,7 +6,7 @@ use super::Vecs;
use crate::{
blocks::SizeVecs,
indexes,
internal::{CachedWindowStarts, LazyPerBlockRolling, PercentPerBlockRollingAverage, VBytesToWeight},
internal::{CachedWindowStarts, LazyPerBlockRolling, PercentVec, VBytesToWeight},
};
impl Vecs {
@@ -25,13 +25,7 @@ impl Vecs {
indexes,
);
let fullness = PercentPerBlockRollingAverage::forced_import(
db,
"block_fullness",
version,
indexes,
cached_starts,
)?;
let fullness = PercentVec::forced_import(db, "block_fullness", version)?;
Ok(Self { weight, fullness })
}

View File

@@ -2,10 +2,10 @@ use brk_traversable::Traversable;
use brk_types::{BasisPoints16, StoredU64, Weight};
use vecdb::{Rw, StorageMode};
use crate::internal::{LazyPerBlockRolling, PercentPerBlockRollingAverage};
use crate::internal::{LazyPerBlockRolling, PercentVec};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub weight: LazyPerBlockRolling<Weight, StoredU64>,
pub fullness: PercentPerBlockRollingAverage<BasisPoints16, M>,
pub fullness: PercentVec<BasisPoints16, M>,
}

View File

@@ -67,8 +67,8 @@ pub(crate) fn process_blocks(
let height_to_first_txout_index = &indexer.vecs.outputs.first_txout_index;
let height_to_first_txin_index = &indexer.vecs.inputs.first_txin_index;
let height_to_tx_count = &transactions.count.total.base.height;
let height_to_output_count = &outputs.count.total.full.sum;
let height_to_input_count = &inputs.count.full.sum;
let height_to_output_count = &outputs.count.total.full.sum.height;
let height_to_input_count = &inputs.count.full.sum.height;
let tx_index_to_output_count = &indexes.tx_index.output_count;
let tx_index_to_input_count = &indexes.tx_index.input_count;

View File

@@ -35,12 +35,14 @@ pub(super) fn compute(
gini.bps
.height
.validate_computed_version_or_reset(source_version)?;
gini.bps.height.truncate_if_needed_at(
gini.bps
.height
.len()
.min(starting_indexes.height.to_usize()),
)?;
let min_len = gini
.bps
.height
.len()
.min(starting_indexes.height.to_usize());
gini.bps.height.truncate_if_needed_at(min_len)?;
let total_heights = supply_vecs
.iter()

View File

@@ -1,124 +0,0 @@
use brk_error::Result;
use brk_traversable::Traversable;
use schemars::JsonSchema;
use vecdb::{
CheckedSub, Database, EagerVec, Exit, ImportableVec, PcoVec, ReadableVec, Ro, Rw, StorageMode,
StoredVec, VecIndex, VecValue, Version,
};
use crate::internal::{
ComputedVecValue, DistributionStats,
algo::{compute_aggregations, compute_aggregations_nblock_window},
};
#[derive(Traversable)]
pub struct Distribution<I: VecIndex, T: ComputedVecValue + JsonSchema, M: StorageMode = Rw> {
pub average: M::Stored<EagerVec<PcoVec<I, T>>>,
pub min: M::Stored<EagerVec<PcoVec<I, T>>>,
pub max: M::Stored<EagerVec<PcoVec<I, T>>>,
pub pct10: M::Stored<EagerVec<PcoVec<I, T>>>,
pub pct25: M::Stored<EagerVec<PcoVec<I, T>>>,
pub median: M::Stored<EagerVec<PcoVec<I, T>>>,
pub pct75: M::Stored<EagerVec<PcoVec<I, T>>>,
pub pct90: M::Stored<EagerVec<PcoVec<I, T>>>,
}
impl<I: VecIndex, T: ComputedVecValue + JsonSchema> Distribution<I, T> {
pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result<Self> {
let s = DistributionStats::<()>::SUFFIXES;
Ok(Self {
average: EagerVec::forced_import(db, &format!("{name}_{}", s[0]), version)?,
min: EagerVec::forced_import(db, &format!("{name}_{}", s[1]), version)?,
max: EagerVec::forced_import(db, &format!("{name}_{}", s[2]), version)?,
pct10: EagerVec::forced_import(db, &format!("{name}_{}", s[3]), version)?,
pct25: EagerVec::forced_import(db, &format!("{name}_{}", s[4]), version)?,
median: EagerVec::forced_import(db, &format!("{name}_{}", s[5]), version)?,
pct75: EagerVec::forced_import(db, &format!("{name}_{}", s[6]), version)?,
pct90: EagerVec::forced_import(db, &format!("{name}_{}", s[7]), version)?,
})
}
/// Compute distribution stats, skipping first N items from all calculations.
///
/// Use `skip_count: 1` to exclude coinbase transactions from fee/feerate stats.
pub(crate) fn compute_with_skip<A>(
&mut self,
max_from: I,
source: &impl ReadableVec<A, T>,
first_indexes: &impl ReadableVec<I, A>,
count_indexes: &impl ReadableVec<I, brk_types::StoredU64>,
exit: &Exit,
skip_count: usize,
) -> Result<()>
where
A: VecIndex + VecValue + brk_types::CheckedSub<A>,
{
compute_aggregations(
max_from,
source,
first_indexes,
count_indexes,
exit,
skip_count,
None, // first
None, // last
Some(&mut self.min),
Some(&mut self.max),
Some(&mut self.average),
None, // sum
None, // cumulative
Some(&mut self.median),
Some(&mut self.pct10),
Some(&mut self.pct25),
Some(&mut self.pct75),
Some(&mut self.pct90),
)
}
/// Compute distribution stats from a fixed n-block rolling window.
///
/// For each index `i`, aggregates all source items from blocks `max(0, i - n_blocks + 1)..=i`.
pub(crate) fn compute_from_nblocks<A>(
&mut self,
max_from: I,
source: &(impl ReadableVec<A, T> + Sized),
first_indexes: &impl ReadableVec<I, A>,
count_indexes: &impl ReadableVec<I, brk_types::StoredU64>,
n_blocks: usize,
exit: &Exit,
) -> Result<()>
where
T: CheckedSub,
A: VecIndex + VecValue + brk_types::CheckedSub<A>,
{
compute_aggregations_nblock_window(
max_from,
source,
first_indexes,
count_indexes,
n_blocks,
exit,
&mut self.min,
&mut self.max,
&mut self.average,
&mut self.median,
&mut self.pct10,
&mut self.pct25,
&mut self.pct75,
&mut self.pct90,
)
}
pub fn read_only_clone(&self) -> Distribution<I, T, Ro> {
Distribution {
average: StoredVec::read_only_clone(&self.average),
min: StoredVec::read_only_clone(&self.min),
max: StoredVec::read_only_clone(&self.max),
pct10: StoredVec::read_only_clone(&self.pct10),
pct25: StoredVec::read_only_clone(&self.pct25),
median: StoredVec::read_only_clone(&self.median),
pct75: StoredVec::read_only_clone(&self.pct75),
pct90: StoredVec::read_only_clone(&self.pct90),
}
}
}

View File

@@ -1,75 +0,0 @@
use brk_error::Result;
use brk_traversable::Traversable;
use schemars::JsonSchema;
use vecdb::{
Database, EagerVec, Exit, ImportableVec, PcoVec, ReadableVec, Ro, Rw, StorageMode, StoredVec,
VecIndex, VecValue, Version,
};
use crate::internal::{ComputedVecValue, algo::compute_aggregations};
use super::Distribution;
/// Full stats aggregate: sum + cumulative + distribution
#[derive(Traversable)]
pub struct DistributionFull<I: VecIndex, T: ComputedVecValue + JsonSchema, M: StorageMode = Rw> {
pub sum: M::Stored<EagerVec<PcoVec<I, T>>>,
pub cumulative: M::Stored<EagerVec<PcoVec<I, T>>>,
#[traversable(flatten)]
pub distribution: Distribution<I, T, M>,
}
impl<I: VecIndex, T: ComputedVecValue + JsonSchema> DistributionFull<I, T> {
pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result<Self> {
Ok(Self {
distribution: Distribution::forced_import(db, name, version)?,
sum: EagerVec::forced_import(db, &format!("{name}_sum"), version)?,
cumulative: EagerVec::forced_import(db, &format!("{name}_cumulative"), version)?,
})
}
/// Compute all stats, skipping first N items from all calculations.
///
/// Use `skip_count: 1` to exclude coinbase transactions from fee/feerate stats.
pub(crate) fn compute_with_skip<A>(
&mut self,
max_from: I,
source: &impl ReadableVec<A, T>,
first_indexes: &impl ReadableVec<I, A>,
count_indexes: &impl ReadableVec<I, brk_types::StoredU64>,
exit: &Exit,
skip_count: usize,
) -> Result<()>
where
A: VecIndex + VecValue + brk_types::CheckedSub<A>,
{
compute_aggregations(
max_from,
source,
first_indexes,
count_indexes,
exit,
skip_count,
None, // first
None, // last
Some(&mut self.distribution.min),
Some(&mut self.distribution.max),
Some(&mut self.distribution.average),
Some(&mut self.sum),
Some(&mut self.cumulative),
Some(&mut self.distribution.median),
Some(&mut self.distribution.pct10),
Some(&mut self.distribution.pct25),
Some(&mut self.distribution.pct75),
Some(&mut self.distribution.pct90),
)
}
pub fn read_only_clone(&self) -> DistributionFull<I, T, Ro> {
DistributionFull {
distribution: self.distribution.read_only_clone(),
sum: StoredVec::read_only_clone(&self.sum),
cumulative: StoredVec::read_only_clone(&self.cumulative),
}
}
}

View File

@@ -1,7 +0,0 @@
mod distribution;
mod distribution_full;
mod lazy_distribution;
pub use distribution::*;
pub use distribution_full::*;
pub use lazy_distribution::*;

View File

@@ -4,10 +4,12 @@ mod per_resolution;
mod window_24h;
mod windows;
mod windows_from_1w;
mod percent;
mod windows_to_1m;
pub use constant::*;
pub use distribution_stats::*;
pub use percent::*;
pub use per_resolution::*;
pub use window_24h::*;
pub use windows::*;

View File

@@ -0,0 +1,8 @@
use brk_traversable::Traversable;
#[derive(Clone, Traversable)]
pub struct Percent<A, B = A, C = B> {
pub bps: A,
pub ratio: B,
pub percent: C,
}

View File

@@ -1,4 +1,3 @@
mod aggregate;
pub(crate) mod algo;
mod amount;
mod containers;
@@ -9,7 +8,6 @@ mod indexes;
mod traits;
mod transform;
pub(crate) use aggregate::*;
pub(crate) use amount::*;
pub(crate) use containers::*;
pub(crate) use per_block::*;

View File

@@ -1,4 +1,4 @@
//! PerBlockAggregated - DistributionFull (distribution + sum + cumulative) + RollingComplete.
//! PerBlockAggregated - PerBlockDistributionFull (distribution + sum + cumulative) + RollingComplete.
//!
//! For metrics aggregated per-block from finer-grained sources (e.g., per-tx data),
//! where we want full per-block stats plus rolling window stats.
@@ -11,7 +11,7 @@ use vecdb::{Database, Exit, Rw, StorageMode};
use crate::{
indexes,
internal::{CachedWindowStarts, DistributionFull, NumericValue, RollingComplete, WindowStarts},
internal::{CachedWindowStarts, PerBlockDistributionFull, NumericValue, RollingComplete, WindowStarts},
};
#[derive(Traversable)]
@@ -20,7 +20,7 @@ where
T: NumericValue + JsonSchema,
{
#[traversable(flatten)]
pub full: DistributionFull<Height, T, M>,
pub full: PerBlockDistributionFull<T, M>,
pub rolling: RollingComplete<T, M>,
}
@@ -35,26 +35,26 @@ where
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let full = DistributionFull::forced_import(db, name, version)?;
let full = PerBlockDistributionFull::forced_import(db, name, version, indexes)?;
let rolling = RollingComplete::forced_import(
db,
name,
version,
indexes,
&full.cumulative,
&full.cumulative.height,
cached_starts,
)?;
Ok(Self { full, rolling })
}
/// Compute DistributionFull stats via closure, then rolling distribution from the per-block sum.
/// Compute PerBlockDistributionFull stats via closure, then rolling distribution from the per-block sum.
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
exit: &Exit,
compute_full: impl FnOnce(&mut DistributionFull<Height, T>) -> Result<()>,
compute_full: impl FnOnce(&mut PerBlockDistributionFull<T>) -> Result<()>,
) -> Result<()>
where
T: From<f64> + Default + Copy + Ord,
@@ -62,7 +62,7 @@ where
{
compute_full(&mut self.full)?;
self.rolling
.compute(max_from, windows, &self.full.sum, exit)?;
.compute(max_from, windows, &self.full.sum.height, exit)?;
Ok(())
}
}

View File

@@ -0,0 +1,103 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::Height;
use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use vecdb::{
CheckedSub, Database, Exit, ReadableVec, Rw, StorageMode,
VecIndex, VecValue, Version,
};
use crate::{
indexes,
internal::{
ComputedVecValue, DistributionStats, NumericValue, PerBlock,
algo::{compute_aggregations, compute_aggregations_nblock_window},
},
};
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct PerBlockDistribution<T: ComputedVecValue + PartialOrd + JsonSchema, M: StorageMode = Rw>(
pub DistributionStats<PerBlock<T, M>>,
);
impl<T: NumericValue + JsonSchema> PerBlockDistribution<T> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(DistributionStats::try_from_fn(|suffix| {
PerBlock::forced_import(db, &format!("{name}_{suffix}"), version, indexes)
})?))
}
pub(crate) fn compute_with_skip<A>(
&mut self,
max_from: Height,
source: &impl ReadableVec<A, T>,
first_indexes: &impl ReadableVec<Height, A>,
count_indexes: &impl ReadableVec<Height, brk_types::StoredU64>,
exit: &Exit,
skip_count: usize,
) -> Result<()>
where
A: VecIndex + VecValue + brk_types::CheckedSub<A>,
{
let s = &mut self.0;
compute_aggregations(
max_from,
source,
first_indexes,
count_indexes,
exit,
skip_count,
None,
None,
Some(&mut s.min.height),
Some(&mut s.max.height),
Some(&mut s.average.height),
None,
None,
Some(&mut s.median.height),
Some(&mut s.pct10.height),
Some(&mut s.pct25.height),
Some(&mut s.pct75.height),
Some(&mut s.pct90.height),
)
}
pub(crate) fn compute_from_nblocks<A>(
&mut self,
max_from: Height,
source: &(impl ReadableVec<A, T> + Sized),
first_indexes: &impl ReadableVec<Height, A>,
count_indexes: &impl ReadableVec<Height, brk_types::StoredU64>,
n_blocks: usize,
exit: &Exit,
) -> Result<()>
where
T: CheckedSub,
A: VecIndex + VecValue + brk_types::CheckedSub<A>,
{
let s = &mut self.0;
compute_aggregations_nblock_window(
max_from,
source,
first_indexes,
count_indexes,
n_blocks,
exit,
&mut s.min.height,
&mut s.max.height,
&mut s.average.height,
&mut s.median.height,
&mut s.pct10.height,
&mut s.pct25.height,
&mut s.pct75.height,
&mut s.pct90.height,
)
}
}

View File

@@ -0,0 +1,73 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::Height;
use schemars::JsonSchema;
use vecdb::{
Database, Exit, ReadableVec, Rw, StorageMode,
VecIndex, VecValue, Version,
};
use crate::{
indexes,
internal::{ComputedVecValue, NumericValue, PerBlock, algo::compute_aggregations},
};
use super::PerBlockDistribution;
#[derive(Traversable)]
pub struct PerBlockDistributionFull<T: ComputedVecValue + PartialOrd + JsonSchema, M: StorageMode = Rw> {
pub sum: PerBlock<T, M>,
pub cumulative: PerBlock<T, M>,
#[traversable(flatten)]
pub distribution: PerBlockDistribution<T, M>,
}
impl<T: NumericValue + JsonSchema> PerBlockDistributionFull<T> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
distribution: PerBlockDistribution::forced_import(db, name, version, indexes)?,
sum: PerBlock::forced_import(db, &format!("{name}_sum"), version, indexes)?,
cumulative: PerBlock::forced_import(db, &format!("{name}_cumulative"), version, indexes)?,
})
}
pub(crate) fn compute_with_skip<A>(
&mut self,
max_from: Height,
source: &impl ReadableVec<A, T>,
first_indexes: &impl ReadableVec<Height, A>,
count_indexes: &impl ReadableVec<Height, brk_types::StoredU64>,
exit: &Exit,
skip_count: usize,
) -> Result<()>
where
A: VecIndex + VecValue + brk_types::CheckedSub<A>,
{
let d = &mut self.distribution.0;
compute_aggregations(
max_from,
source,
first_indexes,
count_indexes,
exit,
skip_count,
None,
None,
Some(&mut d.min.height),
Some(&mut d.max.height),
Some(&mut d.average.height),
Some(&mut self.sum.height),
Some(&mut self.cumulative.height),
Some(&mut d.median.height),
Some(&mut d.pct10.height),
Some(&mut d.pct25.height),
Some(&mut d.pct75.height),
Some(&mut d.pct90.height),
)
}
}

View File

@@ -3,10 +3,10 @@ use brk_types::{Height, Version};
use schemars::JsonSchema;
use vecdb::{LazyVecFrom1, ReadableCloneableVec, UnaryTransform, VecIndex};
use crate::internal::{ComputedVecValue, Distribution, DistributionStats};
use crate::internal::{ComputedVecValue, PerBlockDistribution, DistributionStats};
/// Lazy analog of `Distribution<I, T>`: 8 `LazyVecFrom1` fields,
/// each derived by transforming the corresponding field of a source `Distribution<I, S1T>`.
/// Lazy analog of `Distribution<T>`: 8 `LazyVecFrom1` fields,
/// each derived by transforming the corresponding field of a source `PerBlockDistribution<S1T>`.
#[derive(Clone, Traversable)]
pub struct LazyDistribution<I, T, S1T>
where
@@ -27,54 +27,54 @@ where
impl<T, S1T> LazyDistribution<Height, T, S1T>
where
T: ComputedVecValue + JsonSchema + 'static,
S1T: ComputedVecValue + JsonSchema,
S1T: ComputedVecValue + PartialOrd + JsonSchema,
{
pub(crate) fn from_distribution<F: UnaryTransform<S1T, T>>(
name: &str,
version: Version,
source: &Distribution<Height, S1T>,
source: &PerBlockDistribution<S1T>,
) -> Self {
let s = DistributionStats::<()>::SUFFIXES;
Self {
average: LazyVecFrom1::transformed::<F>(
&format!("{name}_{}", s[0]),
version,
source.average.read_only_boxed_clone(),
source.average.height.read_only_boxed_clone(),
),
min: LazyVecFrom1::transformed::<F>(
&format!("{name}_{}", s[1]),
version,
source.min.read_only_boxed_clone(),
source.min.height.read_only_boxed_clone(),
),
max: LazyVecFrom1::transformed::<F>(
&format!("{name}_{}", s[2]),
version,
source.max.read_only_boxed_clone(),
source.max.height.read_only_boxed_clone(),
),
pct10: LazyVecFrom1::transformed::<F>(
&format!("{name}_{}", s[3]),
version,
source.pct10.read_only_boxed_clone(),
source.pct10.height.read_only_boxed_clone(),
),
pct25: LazyVecFrom1::transformed::<F>(
&format!("{name}_{}", s[4]),
version,
source.pct25.read_only_boxed_clone(),
source.pct25.height.read_only_boxed_clone(),
),
median: LazyVecFrom1::transformed::<F>(
&format!("{name}_{}", s[5]),
version,
source.median.read_only_boxed_clone(),
source.median.height.read_only_boxed_clone(),
),
pct75: LazyVecFrom1::transformed::<F>(
&format!("{name}_{}", s[6]),
version,
source.pct75.read_only_boxed_clone(),
source.pct75.height.read_only_boxed_clone(),
),
pct90: LazyVecFrom1::transformed::<F>(
&format!("{name}_{}", s[7]),
version,
source.pct90.read_only_boxed_clone(),
source.pct90.height.read_only_boxed_clone(),
),
}
}

View File

@@ -2,10 +2,13 @@ mod aggregated;
mod base;
mod cumulative;
mod cumulative_sum;
mod distribution;
mod distribution_full;
mod full;
mod lazy_distribution;
mod lazy_rolling;
mod resolutions;
mod rolling;
mod lazy_rolling;
mod full;
mod rolling_average;
mod with_deltas;
@@ -13,9 +16,12 @@ pub use aggregated::*;
pub use base::*;
pub use cumulative::*;
pub use cumulative_sum::*;
pub use distribution::*;
pub use distribution_full::*;
pub use full::*;
pub use lazy_distribution::*;
pub use lazy_rolling::*;
pub use resolutions::*;
pub use rolling::*;
pub use lazy_rolling::*;
pub use full::*;
pub use rolling_average::*;
pub use with_deltas::*;

View File

@@ -2,11 +2,11 @@ use brk_traversable::Traversable;
use brk_types::{Height, Version};
use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use vecdb::{LazyVecFrom1, ReadableBoxedVec, ReadableCloneableVec, UnaryTransform};
use vecdb::{LazyVecFrom1, ReadOnlyClone, ReadableBoxedVec, ReadableCloneableVec, UnaryTransform};
use crate::{
indexes,
internal::{PerBlock, ComputedVecValue, DerivedResolutions, NumericValue},
internal::{ComputedVecValue, DerivedResolutions, NumericValue, PerBlock},
};
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(merge)]
@@ -38,7 +38,9 @@ where
{
Self {
height: LazyVecFrom1::transformed::<F>(name, version, height_source),
resolutions: Box::new(DerivedResolutions::from_computed::<F>(name, version, source)),
resolutions: Box::new(DerivedResolutions::from_computed::<F>(
name, version, source,
)),
}
}
@@ -86,3 +88,14 @@ where
}
}
}
impl<T, S1T> ReadOnlyClone for LazyPerBlock<T, S1T>
where
T: ComputedVecValue + PartialOrd + JsonSchema,
S1T: ComputedVecValue,
{
type ReadOnly = Self;
fn read_only_clone(&self) -> Self {
self.clone()
}
}

View File

@@ -1,16 +1,17 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, StoredF32, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{
BinaryTransform, Database, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode, VecValue,
};
use crate::{
indexes,
internal::{BpsType, algo::ComputeDrawdown},
internal::{BpsType, Percent, algo::ComputeDrawdown},
};
use crate::internal::{PerBlock, LazyPerBlock};
use crate::internal::{LazyPerBlock, PerBlock};
/// Basis-point storage with both ratio and percentage float views.
///
@@ -18,12 +19,11 @@ use crate::internal::{PerBlock, LazyPerBlock};
/// exposes two lazy StoredF32 views:
/// - `ratio`: bps / 10000 (e.g., 4523 bps -> 0.4523)
/// - `percent`: bps / 100 (e.g., 4523 bps -> 45.23%)
#[derive(Traversable)]
pub struct PercentPerBlock<B: BpsType, M: StorageMode = Rw> {
pub bps: PerBlock<B, M>,
pub ratio: LazyPerBlock<StoredF32, B>,
pub percent: LazyPerBlock<StoredF32, B>,
}
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct PercentPerBlock<B: BpsType, M: StorageMode = Rw>(
pub Percent<PerBlock<B, M>, LazyPerBlock<StoredF32, B>>,
);
impl<B: BpsType> PercentPerBlock<B> {
pub(crate) fn forced_import(
@@ -44,11 +44,11 @@ impl<B: BpsType> PercentPerBlock<B> {
let percent = LazyPerBlock::from_computed::<B::ToPercent>(name, version, bps_clone, &bps);
Ok(Self {
Ok(Self(Percent {
bps,
ratio,
percent,
})
}))
}
pub(crate) fn compute_binary<S1T, S2T, F>(

View File

@@ -1,19 +1,19 @@
use brk_traversable::Traversable;
use brk_types::{StoredF32, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{ReadableCloneableVec, UnaryTransform};
use crate::internal::{BpsType, LazyPerBlock, PercentPerBlock};
use crate::internal::{BpsType, LazyPerBlock, Percent, PercentPerBlock};
/// Fully lazy variant of `PercentPerBlock` — no stored vecs.
///
/// BPS values are lazily derived from a source `PercentPerBlock` via a unary transform,
/// and ratio/percent float views are chained from the lazy BPS.
#[derive(Clone, Traversable)]
pub struct LazyPercentPerBlock<B: BpsType> {
pub bps: LazyPerBlock<B, B>,
pub ratio: LazyPerBlock<StoredF32, B>,
pub percent: LazyPerBlock<StoredF32, B>,
}
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct LazyPercentPerBlock<B: BpsType>(
pub Percent<LazyPerBlock<B, B>, LazyPerBlock<StoredF32, B>>,
);
impl<B: BpsType> LazyPercentPerBlock<B> {
/// Create from a stored `PercentPerBlock` source via a BPS-to-BPS unary transform.
@@ -37,10 +37,10 @@ impl<B: BpsType> LazyPercentPerBlock<B> {
let percent = LazyPerBlock::from_lazy::<B::ToPercent, B>(name, version, &bps);
Self {
Self(Percent {
bps,
ratio,
percent,
}
})
}
}

View File

@@ -1,11 +1,11 @@
mod base;
mod lazy;
mod lazy_windows;
mod rolling_average;
mod vec;
mod windows;
pub use base::*;
pub use lazy::*;
pub use lazy_windows::*;
pub use rolling_average::*;
pub use vec::*;
pub use windows::*;

View File

@@ -1,66 +0,0 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, StoredF32, Version};
use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableCloneableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{BpsType, CachedWindowStarts},
};
use crate::internal::{PerBlockRollingAverage, LazyPerBlock};
/// Like PercentPerBlock but with rolling average stats on the bps data.
#[derive(Traversable)]
pub struct PercentPerBlockRollingAverage<B: BpsType, M: StorageMode = Rw> {
pub bps: PerBlockRollingAverage<B, M>,
pub ratio: LazyPerBlock<StoredF32, B>,
pub percent: LazyPerBlock<StoredF32, B>,
}
impl<B: BpsType> PercentPerBlockRollingAverage<B> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let bps = PerBlockRollingAverage::forced_import(
db,
&format!("{name}_bps"),
version,
indexes,
cached_starts,
)?;
let ratio = LazyPerBlock::from_height_source::<B::ToRatio>(
&format!("{name}_ratio"),
version,
bps.base.read_only_boxed_clone(),
indexes,
);
let percent = LazyPerBlock::from_height_source::<B::ToPercent>(
name,
version,
bps.base.read_only_boxed_clone(),
indexes,
);
Ok(Self {
bps,
ratio,
percent,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
exit: &Exit,
compute_height: impl FnOnce(&mut EagerVec<PcoVec<Height, B>>) -> Result<()>,
) -> Result<()> {
self.bps.compute(max_from, exit, compute_height)
}
}

View File

@@ -0,0 +1,43 @@
use brk_traversable::Traversable;
use brk_types::{Height, StoredF32, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{
Database, EagerVec, ImportableVec, LazyVecFrom1, PcoVec, ReadableCloneableVec, Rw, StorageMode,
};
use crate::internal::{BpsType, Percent};
/// Lightweight percent container: BPS height vec + lazy ratio + lazy percent.
/// No resolutions, no rolling stats.
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
#[allow(clippy::type_complexity)]
pub struct PercentVec<B: BpsType, M: StorageMode = Rw>(
pub Percent<M::Stored<EagerVec<PcoVec<Height, B>>>, LazyVecFrom1<Height, StoredF32, Height, B>>,
);
impl<B: BpsType> PercentVec<B> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
) -> brk_error::Result<Self> {
let bps: EagerVec<PcoVec<Height, B>> =
EagerVec::forced_import(db, &format!("{name}_bps"), version)?;
let bps_clone = bps.read_only_boxed_clone();
let ratio = LazyVecFrom1::transformed::<B::ToRatio>(
&format!("{name}_ratio"),
version,
bps_clone.clone(),
);
let percent = LazyVecFrom1::transformed::<B::ToPercent>(name, version, bps_clone);
Ok(Self(Percent {
bps,
ratio,
percent,
}))
}
}

View File

@@ -1,37 +1,36 @@
//! TxDerivedDistribution - per-block + rolling window distribution stats from tx-level data.
//!
//! Computes true distribution stats (average, min, max, median, percentiles) by reading
//! actual tx values for each scope: current block, last 6 blocks.
use brk_error::Result;
use brk_indexer::Indexer;
use brk_traversable::Traversable;
use brk_types::{Height, Indexes, TxIndex};
use brk_types::{Indexes, TxIndex};
use schemars::JsonSchema;
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode, Version};
use crate::{
indexes,
internal::{ComputedVecValue, Distribution, NumericValue},
internal::{ComputedVecValue, NumericValue, PerBlockDistribution},
};
/// 6-block rolling window distribution with 8 distribution stat vecs.
#[derive(Traversable)]
pub struct BlockRollingDistribution<T, M: StorageMode = Rw>
where
T: ComputedVecValue + PartialOrd + JsonSchema,
{
pub _6b: Distribution<Height, T, M>,
pub _6b: PerBlockDistribution<T, M>,
}
impl<T> BlockRollingDistribution<T>
where
T: NumericValue + JsonSchema,
{
pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result<Self> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
_6b: Distribution::forced_import(db, &format!("{name}_6b"), version)?,
_6b: PerBlockDistribution::forced_import(db, &format!("{name}_6b"), version, indexes)?,
})
}
}
@@ -41,20 +40,28 @@ pub struct TxDerivedDistribution<T, M: StorageMode = Rw>
where
T: ComputedVecValue + PartialOrd + JsonSchema,
{
pub block: Distribution<Height, T, M>,
pub block: PerBlockDistribution<T, M>,
#[traversable(flatten)]
pub rolling: BlockRollingDistribution<T, M>,
pub distribution: BlockRollingDistribution<T, M>,
}
impl<T> TxDerivedDistribution<T>
where
T: NumericValue + JsonSchema,
{
pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result<Self> {
let block = Distribution::forced_import(db, name, version)?;
let rolling = BlockRollingDistribution::forced_import(db, name, version)?;
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let block = PerBlockDistribution::forced_import(db, name, version, indexes)?;
let distribution = BlockRollingDistribution::forced_import(db, name, version, indexes)?;
Ok(Self { block, rolling })
Ok(Self {
block,
distribution,
})
}
pub(crate) fn derive_from(
@@ -72,10 +79,6 @@ where
self.derive_from_with_skip(indexer, indexes, starting_indexes, tx_index_source, exit, 0)
}
/// Derive from source, skipping first N transactions per block from per-block stats.
///
/// Use `skip_count: 1` to exclude coinbase transactions from fee/feerate stats.
/// Rolling window distributions do NOT skip (negligible impact over many blocks).
#[allow(clippy::too_many_arguments)]
pub(crate) fn derive_from_with_skip(
&mut self,
@@ -90,7 +93,6 @@ where
T: Copy + Ord + From<f64> + Default,
f64: From<T>,
{
// Per-block distribution (supports skip for coinbase exclusion)
self.block.compute_with_skip(
starting_indexes.height,
tx_index_source,
@@ -100,8 +102,7 @@ where
skip_count,
)?;
// 6-block rolling: true distribution from all txs in last 6 blocks
self.rolling._6b.compute_from_nblocks(
self.distribution._6b.compute_from_nblocks(
starting_indexes.height,
tx_index_source,
&indexer.vecs.transactions.first_tx_index,

View File

@@ -29,9 +29,9 @@ impl<T> PerTxDistribution<T>
where
T: NumericValue + JsonSchema,
{
pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result<Self> {
pub(crate) fn forced_import(db: &Database, name: &str, version: Version, indexes: &indexes::Vecs) -> Result<Self> {
let tx_index = EagerVec::forced_import(db, name, version)?;
let distribution = TxDerivedDistribution::forced_import(db, name, version)?;
let distribution = TxDerivedDistribution::forced_import(db, name, version, indexes)?;
Ok(Self {
tx_index,
distribution,

View File

@@ -14,8 +14,6 @@ where
pub _6b: LazyDistribution<Height, T, S1T>,
}
/// Lazy analog of `TxDerivedDistribution<T>`: per-block + 6-block rolling,
/// each derived by transforming the corresponding source distribution.
#[derive(Clone, Traversable)]
pub struct LazyTxDerivedDistribution<T, S1T>
where
@@ -24,13 +22,13 @@ where
{
pub block: LazyDistribution<Height, T, S1T>,
#[traversable(flatten)]
pub rolling: LazyBlockRollingDistribution<T, S1T>,
pub distribution: LazyBlockRollingDistribution<T, S1T>,
}
impl<T, S1T> LazyTxDerivedDistribution<T, S1T>
where
T: ComputedVecValue + JsonSchema + 'static,
S1T: ComputedVecValue + JsonSchema,
S1T: ComputedVecValue + PartialOrd + JsonSchema,
{
pub(crate) fn from_tx_derived<F: UnaryTransform<S1T, T>>(
name: &str,
@@ -38,13 +36,16 @@ where
source: &TxDerivedDistribution<S1T>,
) -> Self {
let block = LazyDistribution::from_distribution::<F>(name, version, &source.block);
let rolling = LazyBlockRollingDistribution {
let distribution = LazyBlockRollingDistribution {
_6b: LazyDistribution::from_distribution::<F>(
&format!("{name}_6b"),
version,
&source.rolling._6b,
&source.distribution._6b,
),
};
Self { block, rolling }
Self {
block,
distribution,
}
}
}

View File

@@ -32,9 +32,10 @@ where
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
tx_index: LazyVecFrom2<TxIndex, T, TxIndex, S1, TxIndex, S2>,
) -> Result<Self> {
let distribution = TxDerivedDistribution::forced_import(db, name, version)?;
let distribution = TxDerivedDistribution::forced_import(db, name, version, indexes)?;
Ok(Self {
tx_index,
distribution,

View File

@@ -28,4 +28,4 @@ pub use ratio::{
RatioCentsSignedDollarsBps32, RatioDiffCentsBps32, RatioDiffDollarsBps32, RatioDiffF32Bps32,
RatioDollarsBp16, RatioDollarsBp32, RatioDollarsBps32, RatioSatsBp16, RatioU64Bp16,
};
pub use specialized::{BlockCountTarget, OhlcCentsToDollars, OhlcCentsToSats};
pub use specialized::{BlockCountTarget24h, BlockCountTarget1w, BlockCountTarget1m, BlockCountTarget1y, OhlcCentsToDollars, OhlcCentsToSats};

View File

@@ -1,132 +1,36 @@
use brk_types::{
Close, Day1, Day3, Epoch, Halving, Height, High, Hour1, Hour4, Hour12, Low,
Minute10, Minute30, Month1, Month3, Month6, OHLCCents, OHLCDollars, OHLCSats, Open, StoredU64,
Week1, Year1, Year10,
Close, Day1, Day3, Epoch, Halving, Height, High, Hour1, Hour4, Hour12, Low, Minute10, Minute30,
Month1, Month3, Month6, OHLCCents, OHLCDollars, OHLCSats, Open, StoredU64, Week1, Year1,
Year10,
};
use vecdb::UnaryTransform;
use super::CentsUnsignedToSats;
use crate::blocks::{
TARGET_BLOCKS_PER_DAY, TARGET_BLOCKS_PER_DAY3, TARGET_BLOCKS_PER_DECADE,
TARGET_BLOCKS_PER_HALVING, TARGET_BLOCKS_PER_HOUR1, TARGET_BLOCKS_PER_HOUR4,
TARGET_BLOCKS_PER_HOUR12, TARGET_BLOCKS_PER_MINUTE10, TARGET_BLOCKS_PER_MINUTE30,
TARGET_BLOCKS_PER_MONTH, TARGET_BLOCKS_PER_QUARTER, TARGET_BLOCKS_PER_SEMESTER,
TARGET_BLOCKS_PER_WEEK, TARGET_BLOCKS_PER_YEAR,
TARGET_BLOCKS_PER_DAY, TARGET_BLOCKS_PER_MONTH, TARGET_BLOCKS_PER_WEEK, TARGET_BLOCKS_PER_YEAR,
};
pub struct BlockCountTarget;
impl UnaryTransform<Height, StoredU64> for BlockCountTarget {
#[inline(always)]
fn apply(_: Height) -> StoredU64 {
StoredU64::from(TARGET_BLOCKS_PER_DAY)
}
macro_rules! const_block_target {
($name:ident, $value:expr) => {
pub struct $name;
const_block_target!(@impl $name, $value, Height, Minute10, Minute30, Hour1, Hour4, Hour12, Day1, Day3, Week1, Month1, Month3, Month6, Year1, Year10, Halving, Epoch);
};
(@impl $name:ident, $value:expr, $($idx:ty),*) => {
$(
impl UnaryTransform<$idx, StoredU64> for $name {
#[inline(always)]
fn apply(_: $idx) -> StoredU64 {
StoredU64::from($value)
}
}
)*
};
}
impl UnaryTransform<Minute10, StoredU64> for BlockCountTarget {
#[inline(always)]
fn apply(_: Minute10) -> StoredU64 {
StoredU64::from(TARGET_BLOCKS_PER_MINUTE10)
}
}
impl UnaryTransform<Minute30, StoredU64> for BlockCountTarget {
#[inline(always)]
fn apply(_: Minute30) -> StoredU64 {
StoredU64::from(TARGET_BLOCKS_PER_MINUTE30)
}
}
impl UnaryTransform<Hour1, StoredU64> for BlockCountTarget {
#[inline(always)]
fn apply(_: Hour1) -> StoredU64 {
StoredU64::from(TARGET_BLOCKS_PER_HOUR1)
}
}
impl UnaryTransform<Hour4, StoredU64> for BlockCountTarget {
#[inline(always)]
fn apply(_: Hour4) -> StoredU64 {
StoredU64::from(TARGET_BLOCKS_PER_HOUR4)
}
}
impl UnaryTransform<Hour12, StoredU64> for BlockCountTarget {
#[inline(always)]
fn apply(_: Hour12) -> StoredU64 {
StoredU64::from(TARGET_BLOCKS_PER_HOUR12)
}
}
impl UnaryTransform<Day1, StoredU64> for BlockCountTarget {
#[inline(always)]
fn apply(_: Day1) -> StoredU64 {
StoredU64::from(TARGET_BLOCKS_PER_DAY)
}
}
impl UnaryTransform<Day3, StoredU64> for BlockCountTarget {
#[inline(always)]
fn apply(_: Day3) -> StoredU64 {
StoredU64::from(TARGET_BLOCKS_PER_DAY3)
}
}
impl UnaryTransform<Week1, StoredU64> for BlockCountTarget {
#[inline(always)]
fn apply(_: Week1) -> StoredU64 {
StoredU64::from(TARGET_BLOCKS_PER_WEEK)
}
}
impl UnaryTransform<Month1, StoredU64> for BlockCountTarget {
#[inline(always)]
fn apply(_: Month1) -> StoredU64 {
StoredU64::from(TARGET_BLOCKS_PER_MONTH)
}
}
impl UnaryTransform<Month3, StoredU64> for BlockCountTarget {
#[inline(always)]
fn apply(_: Month3) -> StoredU64 {
StoredU64::from(TARGET_BLOCKS_PER_QUARTER)
}
}
impl UnaryTransform<Month6, StoredU64> for BlockCountTarget {
#[inline(always)]
fn apply(_: Month6) -> StoredU64 {
StoredU64::from(TARGET_BLOCKS_PER_SEMESTER)
}
}
impl UnaryTransform<Year1, StoredU64> for BlockCountTarget {
#[inline(always)]
fn apply(_: Year1) -> StoredU64 {
StoredU64::from(TARGET_BLOCKS_PER_YEAR)
}
}
impl UnaryTransform<Year10, StoredU64> for BlockCountTarget {
#[inline(always)]
fn apply(_: Year10) -> StoredU64 {
StoredU64::from(TARGET_BLOCKS_PER_DECADE)
}
}
impl UnaryTransform<Halving, StoredU64> for BlockCountTarget {
#[inline(always)]
fn apply(_: Halving) -> StoredU64 {
StoredU64::from(TARGET_BLOCKS_PER_HALVING)
}
}
impl UnaryTransform<Epoch, StoredU64> for BlockCountTarget {
#[inline(always)]
fn apply(_: Epoch) -> StoredU64 {
StoredU64::from(2016u64)
}
}
const_block_target!(BlockCountTarget24h, TARGET_BLOCKS_PER_DAY);
const_block_target!(BlockCountTarget1w, TARGET_BLOCKS_PER_WEEK);
const_block_target!(BlockCountTarget1m, TARGET_BLOCKS_PER_MONTH);
const_block_target!(BlockCountTarget1y, TARGET_BLOCKS_PER_YEAR);
pub struct OhlcCentsToDollars;

View File

@@ -33,8 +33,8 @@ impl Vecs {
self.unspent.height.compute_transform3(
starting_indexes.height,
&self.total.full.cumulative,
&inputs_count.full.cumulative,
&self.total.full.cumulative.height,
&inputs_count.full.cumulative.height,
&scripts_count.op_return.cumulative.height,
|(h, output_count, input_count, op_return_count, ..)| {
let block_count = u64::from(h + 1_usize);

View File

@@ -39,14 +39,14 @@ impl Vecs {
self.taproot.compute_binary::<_, _, RatioU64Bp16>(
starting_indexes.height,
&count.p2tr.base.height,
&outputs_count.total.full.sum,
&outputs_count.total.full.sum.height,
exit,
)?;
self.segwit.compute_binary::<_, _, RatioU64Bp16>(
starting_indexes.height,
&count.segwit.base.height,
&outputs_count.total.full.sum,
&outputs_count.total.full.sum.height,
exit,
)?;

View File

@@ -3,19 +3,19 @@ use brk_types::Version;
use vecdb::{Database, EagerVec, ImportableVec};
use super::Vecs;
use crate::internal::PerTxDistribution;
use crate::{indexes, internal::PerTxDistribution};
/// Bump this when fee/feerate aggregation logic changes (e.g., skip coinbase).
const VERSION: Version = Version::new(2);
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
pub(crate) fn forced_import(db: &Database, version: Version, indexes: &indexes::Vecs) -> Result<Self> {
let v = version + VERSION;
Ok(Self {
input_value: EagerVec::forced_import(db, "input_value", version)?,
output_value: EagerVec::forced_import(db, "output_value", version)?,
fee: PerTxDistribution::forced_import(db, "fee", v)?,
fee_rate: PerTxDistribution::forced_import(db, "fee_rate", v)?,
fee: PerTxDistribution::forced_import(db, "fee", v, indexes)?,
fee_rate: PerTxDistribution::forced_import(db, "fee_rate", v, indexes)?,
})
}
}

View File

@@ -23,8 +23,8 @@ impl Vecs {
let version = parent_version;
let count = CountVecs::forced_import(&db, version, indexer, indexes, cached_starts)?;
let size = SizeVecs::forced_import(&db, version, indexer)?;
let fees = FeesVecs::forced_import(&db, version)?;
let size = SizeVecs::forced_import(&db, version, indexer, indexes)?;
let fees = FeesVecs::forced_import(&db, version, indexes)?;
let versions = VersionsVecs::forced_import(&db, version, indexes, cached_starts)?;
let volume = VolumeVecs::forced_import(&db, version, indexes, cached_starts)?;

View File

@@ -4,13 +4,14 @@ use brk_types::{TxIndex, VSize, Version, Weight};
use vecdb::{Database, LazyVecFrom2, ReadableCloneableVec};
use super::Vecs;
use crate::internal::{LazyPerTxDistribution, LazyPerTxDistributionTransformed, VSizeToWeight};
use crate::{indexes, internal::{LazyPerTxDistribution, LazyPerTxDistributionTransformed, VSizeToWeight}};
impl Vecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexer: &Indexer,
indexes: &indexes::Vecs,
) -> Result<Self> {
let tx_index_to_vsize = LazyVecFrom2::init(
"tx_vsize",
@@ -23,7 +24,7 @@ impl Vecs {
);
let vsize =
LazyPerTxDistribution::forced_import(db, "tx_vsize", version, tx_index_to_vsize)?;
LazyPerTxDistribution::forced_import(db, "tx_vsize", version, indexes, tx_index_to_vsize)?;
let tx_index_to_weight = LazyVecFrom2::init(
"tx_weight",

View File

@@ -22,44 +22,21 @@ impl Vecs {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
// sent_sum and received_sum are independent — parallelize
let (r1, r2) = rayon::join(
|| {
self.transfer_volume.compute(
self.transfer_volume.compute(
starting_indexes.height,
prices,
exit,
|sats_vec| {
Ok(sats_vec.compute_filtered_sum_from_indexes(
starting_indexes.height,
prices,
&indexer.vecs.transactions.first_tx_index,
&indexes.height.tx_index_count,
&fees_vecs.input_value,
|sats| !sats.is_max(),
exit,
|sats_vec| {
Ok(sats_vec.compute_filtered_sum_from_indexes(
starting_indexes.height,
&indexer.vecs.transactions.first_tx_index,
&indexes.height.tx_index_count,
&fees_vecs.input_value,
|sats| !sats.is_max(),
exit,
)?)
},
)
)?)
},
|| {
self.output_volume.compute(
starting_indexes.height,
prices,
exit,
|sats_vec| {
Ok(sats_vec.compute_sum_from_indexes(
starting_indexes.height,
&indexer.vecs.transactions.first_tx_index,
&indexes.height.tx_index_count,
&fees_vecs.output_value,
exit,
)?)
},
)
},
);
r1?;
r2?;
)?;
self.tx_per_sec
.height
@@ -73,7 +50,7 @@ impl Vecs {
.height
.compute_binary::<_, Timestamp, PerSec>(
starting_indexes.height,
&inputs_count.full.sum,
&inputs_count.full.sum.height,
&blocks.interval.base,
exit,
)?;
@@ -81,7 +58,7 @@ impl Vecs {
.height
.compute_binary::<_, Timestamp, PerSec>(
starting_indexes.height,
&outputs_count.total.full.sum,
&outputs_count.total.full.sum.height,
&blocks.interval.base,
exit,
)?;

View File

@@ -24,13 +24,6 @@ impl Vecs {
indexes,
cached_starts,
)?,
output_volume: AmountPerBlockCumulativeWithSums::forced_import(
db,
"output_volume",
version,
indexes,
cached_starts,
)?,
tx_per_sec: PerBlock::forced_import(db, "tx_per_sec", version + v2, indexes)?,
outputs_per_sec: PerBlock::forced_import(
db,

View File

@@ -7,7 +7,6 @@ use crate::internal::{AmountPerBlockCumulativeWithSums, PerBlock};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub transfer_volume: AmountPerBlockCumulativeWithSums<M>,
pub output_volume: AmountPerBlockCumulativeWithSums<M>,
pub tx_per_sec: PerBlock<StoredF32, M>,
pub outputs_per_sec: PerBlock<StoredF32, M>,
pub inputs_per_sec: PerBlock<StoredF32, M>,