brk: metric trimming part X + 1

This commit is contained in:
nym21
2026-07-29 23:40:11 +02:00
parent e236669f87
commit c1745793b8
23 changed files with 656 additions and 386 deletions
@@ -394,7 +394,7 @@ impl UTXOCohorts<Rw> {
#[inline(always)]
pub(crate) fn push_maturation(&mut self, matured: &AgeRange<Sats>) {
for (v, &sats) in self.matured.iter_mut().zip(matured.iter()) {
v.block.sats.push(sats);
v.push_block_sats(sats);
}
}
@@ -605,7 +605,7 @@ impl UTXOCohorts<Rw> {
.transfer_volume
.block
.cents
.read_only_clone();
.clone();
let under_1h_value_destroyed = self
.age_range
.under_1h
@@ -801,8 +801,6 @@ impl UTXOCohorts<Rw> {
vecs.extend(self.profitability.collect_all_vecs_mut());
for v in self.matured.iter_mut() {
let inner = &mut v.inner;
vecs.push(&mut inner.block.sats);
vecs.push(&mut inner.block.cents);
vecs.push(&mut inner.cumulative.sats.height);
vecs.push(&mut inner.cumulative.cents.height);
}
@@ -821,7 +819,7 @@ impl UTXOCohorts<Rw> {
.chain(
self.matured
.iter()
.map(|v| Height::from(v.block.sats.len())),
.map(|v| Height::from(v.cumulative.sats.height.len())),
)
.min()
.unwrap_or_default()
@@ -3,7 +3,7 @@ use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, StoredF64, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode};
use crate::{
distribution::{
@@ -45,8 +45,8 @@ impl ActivityCore {
self.minimal
.min_len()
.min(self.coindays_destroyed.block.len())
.min(self.transfer_volume_in_profit.block.sats.len())
.min(self.transfer_volume_in_loss.block.sats.len())
.min(self.transfer_volume_in_profit.cumulative.sats.height.len())
.min(self.transfer_volume_in_loss.cumulative.sats.height.len())
}
#[inline(always)]
@@ -55,22 +55,18 @@ impl ActivityCore {
self.coindays_destroyed
.push_block(StoredF64::from(Bitcoin::from(state.satdays_destroyed)));
self.transfer_volume_in_profit
.block
.sats
.push(state.realized.sent_in_profit());
.push_block_sats(state.realized.sent_in_profit());
self.transfer_volume_in_loss
.block
.sats
.push(state.realized.sent_in_loss());
.push_block_sats(state.realized.sent_in_loss());
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs = self.minimal.collect_vecs_mut();
vecs.push(self.coindays_destroyed.stored_mut());
vecs.push(&mut self.transfer_volume_in_profit.inner.block.sats);
vecs.push(&mut self.transfer_volume_in_profit.inner.block.cents);
vecs.push(&mut self.transfer_volume_in_loss.inner.block.sats);
vecs.push(&mut self.transfer_volume_in_loss.inner.block.cents);
vecs.push(&mut self.transfer_volume_in_profit.inner.cumulative.sats.height);
vecs.push(&mut self.transfer_volume_in_profit.inner.cumulative.cents.height);
vecs.push(&mut self.transfer_volume_in_loss.inner.cumulative.sats.height);
vecs.push(&mut self.transfer_volume_in_loss.inner.cumulative.cents.height);
vecs
}
@@ -89,8 +85,8 @@ impl ActivityCore {
.compute_from_stateful(starting_lengths, &minimal_refs, exit)?;
sum_others!(self, starting_lengths, others, exit; coindays_destroyed.cumulative.height);
sum_others!(self, starting_lengths, others, exit; transfer_volume_in_profit.block.sats);
sum_others!(self, starting_lengths, others, exit; transfer_volume_in_loss.block.sats);
sum_others!(self, starting_lengths, others, exit; transfer_volume_in_profit.cumulative.sats.height);
sum_others!(self, starting_lengths, others, exit; transfer_volume_in_loss.cumulative.sats.height);
Ok(())
}
@@ -2,7 +2,7 @@ use brk_error::Result;
use brk_indexer::Lengths;
use brk_traversable::Traversable;
use brk_types::Version;
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode};
use crate::{
distribution::{
@@ -27,19 +27,19 @@ impl ActivityMinimal {
}
pub(crate) fn min_len(&self) -> usize {
self.transfer_volume.block.sats.len()
self.transfer_volume.cumulative.sats.height.len()
}
#[inline(always)]
pub(crate) fn push_state(&mut self, state: &CohortState<impl RealizedOps, impl CostBasisOps>) {
self.transfer_volume.block.sats.push(state.sent);
self.transfer_volume.push_block_sats(state.sent);
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let inner = &mut self.transfer_volume.inner;
vec![
&mut inner.block.sats as &mut dyn AnyStoredVec,
&mut inner.block.cents,
&mut inner.cumulative.sats.height as &mut dyn AnyStoredVec,
&mut inner.cumulative.cents.height,
]
}
@@ -49,14 +49,18 @@ impl ActivityMinimal {
others: &[&Self],
exit: &Exit,
) -> Result<()> {
self.transfer_volume.block.sats.compute_sum_of_others(
starting_lengths.height,
&others
.iter()
.map(|v| &v.transfer_volume.block.sats)
.collect::<Vec<_>>(),
exit,
)?;
self.transfer_volume
.cumulative
.sats
.height
.compute_sum_of_others(
starting_lengths.height,
&others
.iter()
.map(|v| &v.transfer_volume.cumulative.sats.height)
.collect::<Vec<_>>(),
exit,
)?;
Ok(())
}
@@ -105,7 +105,6 @@ impl MinimalCohortMetrics {
self.outputs.compute_rest(starting_lengths.height, exit)?;
self.activity
.compute_rest_part1(prices, starting_lengths, exit)?;
self.realized.compute_rest_part1(starting_lengths, exit)?;
Ok(())
}
@@ -67,7 +67,6 @@ impl TypeCohortMetrics {
self.outputs.compute_rest(starting_lengths.height, exit)?;
self.activity
.compute_rest_part1(prices, starting_lengths, exit)?;
self.realized.compute_rest_part1(starting_lengths, exit)?;
Ok(())
}
@@ -142,22 +142,13 @@ impl RealizedCore {
starting_lengths: &Lengths,
exit: &Exit,
) -> Result<()> {
self.minimal.compute_rest_part1(starting_lengths, exit)?;
self.net_pnl.block.cents.compute_transform2(
self.net_pnl.compute_from_cumulative_pair(
starting_lengths.height,
&self.minimal.profit.block.cents,
&self.minimal.loss.block.cents,
|(i, profit, loss, ..)| {
(
i,
CentsSigned::new(profit.inner() as i64 - loss.inner() as i64),
)
},
&self.minimal.profit.cumulative.cents.height,
&self.minimal.loss.cumulative.cents.height,
|_, profit, loss| CentsSigned::new(profit.inner() as i64 - loss.inner() as i64),
exit,
)?;
Ok(())
)
}
pub(crate) fn compute_rest_part2(
@@ -171,8 +162,6 @@ impl RealizedCore {
self.minimal
.compute_rest_part2(prices, starting_lengths, height_to_supply, exit)?;
self.net_pnl.compute_rest(starting_lengths.height, exit)?;
self.sopr
.ratio
._24h
@@ -128,7 +128,7 @@ impl RealizedFull {
.len()
.min(self.cap_raw.len())
.min(self.capitalized.cap_raw.len())
.min(self.peak_regret.value.block.cents.len())
.min(self.peak_regret.value.cumulative.cents.height.len())
}
#[inline(always)]
@@ -148,9 +148,7 @@ impl RealizedFull {
.push(state.realized.capitalized_cap_raw());
self.peak_regret
.value
.block
.cents
.push(state.realized.peak_regret());
.push_block(state.realized.peak_regret());
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
@@ -158,7 +156,7 @@ impl RealizedFull {
vecs.push(&mut self.capitalized.price.cents.height);
vecs.push(&mut self.cap_raw as &mut dyn AnyStoredVec);
vecs.push(&mut self.capitalized.cap_raw as &mut dyn AnyStoredVec);
vecs.push(&mut self.peak_regret.value.block.cents);
vecs.push(self.peak_regret.value.stored_mut());
vecs
}
@@ -169,9 +167,7 @@ impl RealizedFull {
exit: &Exit,
) -> Result<()> {
self.core
.compute_from_stateful(starting_lengths, others, exit)?;
Ok(())
.compute_from_stateful(starting_lengths, others, exit)
}
#[inline(always)]
@@ -189,7 +185,7 @@ impl RealizedFull {
};
self.capitalized.price.cents.height.push(capitalized_price);
self.peak_regret.value.block.cents.push(accum.peak_regret());
self.peak_regret.value.push_block(accum.peak_regret());
capitalized_price
}
@@ -199,12 +195,7 @@ impl RealizedFull {
starting_lengths: &Lengths,
exit: &Exit,
) -> Result<()> {
self.core.compute_rest_part1(starting_lengths, exit)?;
self.peak_regret
.value
.compute_rest(starting_lengths.height, exit)?;
Ok(())
self.core.compute_rest_part1(starting_lengths, exit)
}
#[allow(clippy::too_many_arguments)]
@@ -243,13 +234,13 @@ impl RealizedFull {
}
// Gross PnL
self.gross_pnl.block.cents.compute_add(
self.gross_pnl.compute_from_cumulative_pair(
starting_lengths.height,
&self.core.minimal.profit.block.cents,
&self.core.minimal.loss.block.cents,
&self.core.minimal.profit.cumulative.cents.height,
&self.core.minimal.loss.cumulative.cents.height,
|_, profit, loss| profit + loss,
exit,
)?;
self.gross_pnl.compute_rest(starting_lengths.height, exit)?;
// Net PnL 1m change relative to rcap and mcap
self.net_pnl
@@ -61,22 +61,22 @@ impl RealizedMinimal {
.cents
.height
.len()
.min(self.profit.block.cents.len())
.min(self.loss.block.cents.len())
.min(self.profit.cumulative.cents.height.len())
.min(self.loss.cumulative.cents.height.len())
}
#[inline(always)]
pub(crate) fn push_state(&mut self, state: &CohortState<impl RealizedOps, impl CostBasisOps>) {
self.cap.cents.height.push(state.realized.cap());
self.profit.block.cents.push(state.realized.profit());
self.loss.block.cents.push(state.realized.loss());
self.profit.push_block(state.realized.profit());
self.loss.push_block(state.realized.loss());
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
vec![
&mut self.cap.cents.height as &mut dyn AnyStoredVec,
&mut self.profit.block.cents,
&mut self.loss.block.cents,
self.profit.stored_mut(),
self.loss.stored_mut(),
]
}
@@ -87,18 +87,16 @@ impl RealizedMinimal {
exit: &Exit,
) -> Result<()> {
sum_others!(self, starting_lengths, others, exit; cap.cents.height);
sum_others!(self, starting_lengths, others, exit; profit.block.cents);
sum_others!(self, starting_lengths, others, exit; loss.block.cents);
Ok(())
}
pub(crate) fn compute_rest_part1(
&mut self,
starting_lengths: &Lengths,
exit: &Exit,
) -> Result<()> {
self.profit.compute_rest(starting_lengths.height, exit)?;
self.loss.compute_rest(starting_lengths.height, exit)?;
self.profit.compute_sum_of_others(
starting_lengths.height,
&others.iter().map(|v| &v.profit).collect::<Vec<_>>(),
exit,
)?;
self.loss.compute_sum_of_others(
starting_lengths.height,
&others.iter().map(|v| &v.loss).collect::<Vec<_>>(),
exit,
)?;
Ok(())
}
@@ -1,25 +0,0 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Version};
use vecdb::{
Database, EagerVec, ImportableVec, LazyVecFrom1, PcoVec, ReadableCloneableVec, Rw, StorageMode,
};
use super::FiatType;
/// Raw per-block fiat data: cents (stored) + usd (lazy), no resolutions.
#[derive(Traversable)]
pub struct FiatBlock<C: FiatType, M: StorageMode = Rw> {
pub usd: LazyVecFrom1<Height, Dollars, Height, C>,
pub cents: M::Stored<EagerVec<PcoVec<Height, C>>>,
}
impl<C: FiatType> FiatBlock<C> {
pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result<Self> {
let cents: EagerVec<PcoVec<Height, C>> =
EagerVec::forced_import(db, &format!("{name}_cents"), version)?;
let usd =
LazyVecFrom1::transformed::<C::ToDollars>(name, version, cents.read_only_boxed_clone());
Ok(Self { usd, cents })
}
}
@@ -1,22 +1,29 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use vecdb::{Database, Exit, Rw, StorageMode};
use vecdb::{
AnyStoredVec, AnyVec, Database, Exit, ReadableVec, Rw, StorageMode, VecValue, WritableVec,
};
use crate::{
indexes,
internal::{
FiatBlock, FiatPerBlock, FiatType, LazyRollingSumsFiatFromHeight, WindowStartVec, Windows,
FiatPerBlock, FiatType, LazyFiatBlock, LazyRollingSumsFiatFromHeight, WindowStartVec,
Windows,
},
};
#[derive(Traversable)]
pub struct FiatPerBlockCumulativeWithSums<C: FiatType, M: StorageMode = Rw> {
pub block: FiatBlock<C, M>,
pub block: LazyFiatBlock<C>,
pub cumulative: FiatPerBlock<C, M>,
pub sum: LazyRollingSumsFiatFromHeight<C>,
#[traversable(skip)]
last_cumulative: Option<(usize, C)>,
}
const VERSION: Version = Version::ONE;
impl<C: FiatType> FiatPerBlockCumulativeWithSums<C> {
pub(crate) fn forced_import(
db: &Database,
@@ -25,12 +32,18 @@ impl<C: FiatType> FiatPerBlockCumulativeWithSums<C> {
indexes: &indexes::Vecs,
cached_starts: &Windows<&WindowStartVec>,
) -> Result<Self> {
let block = FiatBlock::forced_import(db, name, version)?;
let v = version + VERSION;
let cumulative =
FiatPerBlock::forced_import(db, &format!("{name}_cumulative"), version, indexes)?;
FiatPerBlock::forced_import(db, &format!("{name}_cumulative"), v, indexes)?;
let last_cumulative = cumulative
.cents
.height
.collect_last()
.map(|value| (cumulative.cents.height.len(), value));
let block = LazyFiatBlock::from_cumulative(name, v, &cumulative);
let sum = LazyRollingSumsFiatFromHeight::new(
&format!("{name}_sum"),
version,
v,
&cumulative.cents.height,
cached_starts,
indexes,
@@ -39,17 +52,73 @@ impl<C: FiatType> FiatPerBlockCumulativeWithSums<C> {
block,
cumulative,
sum,
last_cumulative,
})
}
pub(crate) fn compute_rest(&mut self, max_from: Height, exit: &Exit) -> Result<()>
#[inline(always)]
pub(crate) fn push_block(&mut self, value: C)
where
C: Default,
C: Copy,
{
self.cumulative
.cents
.height
.compute_cumulative(max_from, &self.block.cents, exit)?;
let len = self.cumulative.cents.height.len();
let mut cumulative = match self.last_cumulative {
Some((cached_len, value)) if cached_len == len => value,
_ => self
.cumulative
.cents
.height
.collect_last()
.unwrap_or_default(),
};
cumulative += value;
self.cumulative.cents.height.push(cumulative);
self.last_cumulative = Some((len + 1, cumulative));
}
pub(crate) fn compute_from_cumulative_pair<S1, S2>(
&mut self,
max_from: Height,
source1: &impl ReadableVec<Height, S1>,
source2: &impl ReadableVec<Height, S2>,
mut transform: impl FnMut(Height, S1, S2) -> C,
exit: &Exit,
) -> Result<()>
where
S1: VecValue,
S2: VecValue,
{
self.cumulative.cents.height.compute_transform2(
max_from,
source1,
source2,
|(height, value1, value2, ..)| (height, transform(height, value1, value2)),
exit,
)?;
self.last_cumulative = None;
Ok(())
}
pub(crate) fn compute_sum_of_others(
&mut self,
max_from: Height,
others: &[&Self],
exit: &Exit,
) -> Result<()> {
self.cumulative.cents.height.compute_sum_of_others(
max_from,
&others
.iter()
.map(|v| &v.cumulative.cents.height)
.collect::<Vec<_>>(),
exit,
)?;
self.last_cumulative = None;
Ok(())
}
pub(crate) fn stored_mut(&mut self) -> &mut dyn AnyStoredVec {
self.last_cumulative = None;
&mut self.cumulative.cents.height
}
}
@@ -0,0 +1,30 @@
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Version};
use vecdb::{LazyVecFrom1, ReadableCloneableVec};
use crate::internal::{FiatPerBlock, FiatType, LazyPreviousDeltaVec};
/// Per-block fiat data derived from stored cumulative cents.
#[derive(Clone, Traversable)]
pub struct LazyFiatBlock<C: FiatType> {
pub usd: LazyVecFrom1<Height, Dollars, Height, C>,
pub cents: LazyPreviousDeltaVec<Height, C>,
}
impl<C: FiatType> LazyFiatBlock<C> {
pub(crate) fn from_cumulative(
name: &str,
version: Version,
cumulative: &FiatPerBlock<C>,
) -> Self {
let cents = LazyPreviousDeltaVec::new(
&format!("{name}_cents"),
version,
cumulative.cents.height.read_only_boxed_clone(),
);
let usd =
LazyVecFrom1::transformed::<C::ToDollars>(name, version, cents.read_only_boxed_clone());
Self { usd, cents }
}
}
@@ -1,14 +1,14 @@
mod base;
mod block;
mod cumulative_sum;
mod cumulative_sum_with_deltas;
mod lazy;
mod lazy_block;
mod lazy_rolling_sum;
mod with_deltas;
pub use base::*;
pub use block::*;
pub use cumulative_sum::*;
pub use cumulative_sum_with_deltas::*;
pub use lazy::*;
pub use lazy_block::*;
pub use lazy_rolling_sum::*;
pub use with_deltas::*;
@@ -1,58 +0,0 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Cents, Dollars, Height, Sats, Version};
use vecdb::{
Database, EagerVec, Exit, ImportableVec, LazyVecFrom1, PcoVec, ReadableCloneableVec, Rw,
StorageMode,
};
use crate::{
internal::{CentsUnsignedToDollars, SatsToBitcoin, SatsToCents},
price,
};
/// Raw per-block amount data: sats + cents (stored), btc + usd (lazy), no resolutions.
#[derive(Traversable)]
pub struct ValueBlock<M: StorageMode = Rw> {
pub btc: LazyVecFrom1<Height, Bitcoin, Height, Sats>,
pub sats: M::Stored<EagerVec<PcoVec<Height, Sats>>>,
pub usd: LazyVecFrom1<Height, Dollars, Height, Cents>,
pub cents: M::Stored<EagerVec<PcoVec<Height, Cents>>>,
}
impl ValueBlock {
pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result<Self> {
let sats: EagerVec<PcoVec<Height, Sats>> =
EagerVec::forced_import(db, &format!("{name}_sats"), version)?;
let btc =
LazyVecFrom1::transformed::<SatsToBitcoin>(name, version, sats.read_only_boxed_clone());
let cents: EagerVec<PcoVec<Height, Cents>> =
EagerVec::forced_import(db, &format!("{name}_cents"), version)?;
let usd = LazyVecFrom1::transformed::<CentsUnsignedToDollars>(
&format!("{name}_usd"),
version,
cents.read_only_boxed_clone(),
);
Ok(Self {
btc,
sats,
usd,
cents,
})
}
pub(crate) fn compute_cents(
&mut self,
max_from: Height,
prices: &price::Vecs,
exit: &Exit,
) -> Result<()> {
self.cents.compute_binary::<Sats, Cents, SatsToCents>(
max_from,
&self.sats,
&prices.spot.cents.height,
exit,
)?;
Ok(())
}
}
@@ -1,18 +1,23 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Sats, Version};
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use vecdb::{
AnyVec, BinaryTransform, Database, Exit, ReadableVec, Rw, StorageMode, VecIndex, VecValue,
WritableVec,
};
use crate::{
indexes,
internal::{ValueBlock, ValuePerBlock},
internal::{LazyValueBlock, SatsToCents, ValuePerBlock},
price,
};
#[derive(Traversable)]
pub struct ValuePerBlockCumulative<M: StorageMode = Rw> {
pub block: ValueBlock<M>,
pub block: LazyValueBlock,
pub cumulative: ValuePerBlock<M>,
#[traversable(skip)]
last_cumulative_sats: Option<(usize, Sats)>,
}
const VERSION: Version = Version::ONE;
@@ -25,47 +30,278 @@ impl ValuePerBlockCumulative {
indexes: &indexes::Vecs,
) -> Result<Self> {
let v = version + VERSION;
let cumulative =
ValuePerBlock::forced_import(db, &format!("{name}_cumulative"), v, indexes)?;
let last_cumulative_sats = cumulative
.sats
.height
.collect_last()
.map(|value| (cumulative.sats.height.len(), value));
let block = LazyValueBlock::from_cumulative(name, v, &cumulative);
Ok(Self {
block: ValueBlock::forced_import(db, name, v)?,
cumulative: ValuePerBlock::forced_import(
db,
&format!("{name}_cumulative"),
v,
indexes,
)?,
block,
cumulative,
last_cumulative_sats,
})
}
pub(crate) fn compute(
#[inline(always)]
pub(crate) fn push_block_sats(&mut self, value: Sats) {
let len = self.cumulative.sats.height.len();
let mut cumulative = match self.last_cumulative_sats {
Some((cached_len, value)) if cached_len == len => value,
_ => self
.cumulative
.sats
.height
.collect_last()
.unwrap_or_default(),
};
cumulative += value;
self.cumulative.sats.height.push(cumulative);
self.last_cumulative_sats = Some((len + 1, cumulative));
}
pub(crate) fn compute_from<S>(
&mut self,
prices: &price::Vecs,
max_from: Height,
prices: &price::Vecs,
source: &impl ReadableVec<Height, S>,
transform: impl FnMut(Height, S) -> Sats,
exit: &Exit,
) -> Result<()> {
self.cumulative
.sats
.height
.compute_cumulative(max_from, &self.block.sats, exit)?;
) -> Result<()>
where
S: VecValue,
{
self.compute_sats_from(max_from, source, transform, exit)?;
self.compute_cents(max_from, prices, exit)
}
self.block.compute_cents(max_from, prices, exit)?;
pub(crate) fn compute_from_pair<S1, S2>(
&mut self,
max_from: Height,
prices: &price::Vecs,
source1: &impl ReadableVec<Height, S1>,
source2: &impl ReadableVec<Height, S2>,
transform: impl FnMut(Height, S1, S2) -> Sats,
exit: &Exit,
) -> Result<()>
where
S1: VecValue,
S2: VecValue,
{
self.compute_sats_from_pair(max_from, source1, source2, transform, exit)?;
self.compute_cents(max_from, prices, exit)
}
self.cumulative
.cents
.height
.compute_cumulative(max_from, &self.block.cents, exit)?;
#[allow(clippy::too_many_arguments)]
pub(crate) fn compute_from_indexes<A, B>(
&mut self,
max_from: Height,
prices: &price::Vecs,
first_indexes: &impl ReadableVec<Height, A>,
indexes_count: &impl ReadableVec<Height, B>,
source: &impl ReadableVec<A, Sats>,
exit: &Exit,
) -> Result<()>
where
A: VecIndex + VecValue,
B: VecValue,
usize: From<B>,
{
self.compute_sats_from_indexes(
max_from,
first_indexes,
indexes_count,
source,
|_| true,
exit,
)?;
self.compute_cents(max_from, prices, exit)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn compute_filtered_from_indexes<A, B>(
&mut self,
max_from: Height,
prices: &price::Vecs,
first_indexes: &impl ReadableVec<Height, A>,
indexes_count: &impl ReadableVec<Height, B>,
source: &impl ReadableVec<A, Sats>,
filter: impl FnMut(&Sats) -> bool,
exit: &Exit,
) -> Result<()>
where
A: VecIndex + VecValue,
B: VecValue,
usize: From<B>,
{
self.compute_sats_from_indexes(
max_from,
first_indexes,
indexes_count,
source,
filter,
exit,
)?;
self.compute_cents(max_from, prices, exit)
}
fn compute_sats_from<S>(
&mut self,
max_from: Height,
source: &impl ReadableVec<Height, S>,
mut transform: impl FnMut(Height, S) -> Sats,
exit: &Exit,
) -> Result<()>
where
S: VecValue,
{
let mut cumulative = None;
self.cumulative.sats.height.compute_transform(
max_from,
source,
|(height, value, this)| {
let cumulative = cumulative.get_or_insert_with(|| {
height
.decremented()
.and_then(|height| this.collect_one(height))
.unwrap_or_default()
});
*cumulative += transform(height, value);
(height, *cumulative)
},
exit,
)?;
self.last_cumulative_sats = None;
Ok(())
}
pub(crate) fn compute_with(
fn compute_sats_from_pair<S1, S2>(
&mut self,
max_from: Height,
source1: &impl ReadableVec<Height, S1>,
source2: &impl ReadableVec<Height, S2>,
mut transform: impl FnMut(Height, S1, S2) -> Sats,
exit: &Exit,
) -> Result<()>
where
S1: VecValue,
S2: VecValue,
{
let mut cumulative = None;
self.cumulative.sats.height.compute_transform2(
max_from,
source1,
source2,
|(height, value1, value2, this)| {
let cumulative = cumulative.get_or_insert_with(|| {
height
.decremented()
.and_then(|height| this.collect_one(height))
.unwrap_or_default()
});
*cumulative += transform(height, value1, value2);
(height, *cumulative)
},
exit,
)?;
self.last_cumulative_sats = None;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn compute_sats_from_indexes<A, B>(
&mut self,
max_from: Height,
first_indexes: &impl ReadableVec<Height, A>,
indexes_count: &impl ReadableVec<Height, B>,
source: &impl ReadableVec<A, Sats>,
mut filter: impl FnMut(&Sats) -> bool,
exit: &Exit,
) -> Result<()>
where
A: VecIndex + VecValue,
B: VecValue,
usize: From<B>,
{
let target = &mut self.cumulative.sats.height;
target.validate_computed_version_or_reset(
first_indexes.version() + indexes_count.version() + source.version(),
)?;
target.truncate_if_needed(max_from)?;
target.repeat_until_complete(exit, |target| {
let skip = target.len();
let end = target.batch_end(indexes_count.len());
if skip >= end || skip >= first_indexes.len() {
return Ok(());
}
let source_start = first_indexes.collect_one_at(skip).unwrap().to_usize();
let counts: Vec<usize> = indexes_count
.collect_range_at(skip, end)
.into_iter()
.map(usize::from)
.collect();
let source_end = source_start + counts.iter().sum::<usize>();
let mut cumulative = skip
.checked_sub(1)
.and_then(|index| target.collect_one_at(index))
.unwrap_or_default();
let mut group_index = 0;
while group_index < counts.len() && counts[group_index] == 0 {
target.push(cumulative);
group_index += 1;
}
if group_index < counts.len() {
let mut remaining = counts[group_index];
source.fold_range_at(source_start, source_end, Sats::ZERO, |sum, value| {
let sum = if filter(&value) { sum + value } else { sum };
remaining -= 1;
if remaining == 0 {
cumulative += sum;
target.push(cumulative);
group_index += 1;
while group_index < counts.len() && counts[group_index] == 0 {
target.push(cumulative);
group_index += 1;
}
if group_index < counts.len() {
remaining = counts[group_index];
}
Sats::ZERO
} else {
sum
}
});
}
Ok(())
})?;
self.last_cumulative_sats = None;
Ok(())
}
pub(crate) fn compute_cents(
&mut self,
max_from: Height,
prices: &price::Vecs,
exit: &Exit,
compute_sats: impl FnOnce(&mut EagerVec<PcoVec<Height, Sats>>) -> Result<()>,
) -> Result<()> {
compute_sats(&mut self.block.sats)?;
self.compute(prices, max_from, exit)
self.cumulative
.cents
.height
.compute_cumulative_transformed_binary(
max_from,
&self.block.sats,
&prices.spot.cents.height,
SatsToCents::apply,
exit,
)?;
Ok(())
}
}
@@ -1,8 +1,8 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Sats, Version};
use brk_types::{Height, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use vecdb::{Database, Exit, Rw, StorageMode};
use crate::{
indexes,
@@ -60,23 +60,12 @@ impl ValuePerBlockCumulativeRolling {
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
prices: &price::Vecs,
exit: &Exit,
compute_sats: impl FnOnce(&mut EagerVec<PcoVec<Height, Sats>>) -> Result<()>,
) -> Result<()> {
compute_sats(&mut self.block.sats)?;
self.compute_rest(max_from, prices, exit)
}
pub(crate) fn compute_rest(
&mut self,
max_from: Height,
prices: &price::Vecs,
exit: &Exit,
) -> Result<()> {
self.inner.compute(prices, max_from, exit)
self.inner.compute_cents(max_from, prices, exit)
}
}
@@ -2,7 +2,7 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode, VecIndex, VecValue};
use crate::{
indexes,
@@ -45,18 +45,39 @@ impl ValuePerBlockFull {
})
}
pub(crate) fn compute(
#[allow(clippy::too_many_arguments)]
pub(crate) fn compute_from_indexes<A, B>(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
prices: &price::Vecs,
first_indexes: &impl ReadableVec<Height, A>,
indexes_count: &impl ReadableVec<Height, B>,
source: &impl ReadableVec<A, Sats>,
exit: &Exit,
) -> Result<()>
where
A: VecIndex + VecValue,
B: VecValue,
usize: From<B>,
{
self.inner.compute_from_indexes(
max_from,
prices,
first_indexes,
indexes_count,
source,
exit,
)?;
self.compute_distribution(max_from, windows, exit)
}
fn compute_distribution(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
exit: &Exit,
compute_sats: impl FnOnce(&mut EagerVec<PcoVec<Height, Sats>>) -> Result<()>,
) -> Result<()> {
compute_sats(&mut self.inner.block.sats)?;
self.inner.compute_rest(max_from, prices, exit)?;
self.distribution.compute(
max_from,
windows,
@@ -0,0 +1,47 @@
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Cents, Dollars, Height, Sats, Version};
use vecdb::{LazyVecFrom1, ReadableCloneableVec};
use crate::internal::{CentsUnsignedToDollars, LazyPreviousDeltaVec, SatsToBitcoin, ValuePerBlock};
/// Per-block amount data derived from stored cumulative sats and cents.
#[derive(Clone, Traversable)]
pub struct LazyValueBlock {
pub btc: LazyVecFrom1<Height, Bitcoin, Height, Sats>,
pub sats: LazyPreviousDeltaVec<Height, Sats>,
pub usd: LazyVecFrom1<Height, Dollars, Height, Cents>,
pub cents: LazyPreviousDeltaVec<Height, Cents>,
}
impl LazyValueBlock {
pub(crate) fn from_cumulative(
name: &str,
version: Version,
cumulative: &ValuePerBlock,
) -> Self {
let sats = LazyPreviousDeltaVec::new(
&format!("{name}_sats"),
version,
cumulative.sats.height.read_only_boxed_clone(),
);
let btc =
LazyVecFrom1::transformed::<SatsToBitcoin>(name, version, sats.read_only_boxed_clone());
let cents = LazyPreviousDeltaVec::new(
&format!("{name}_cents"),
version,
cumulative.cents.height.read_only_boxed_clone(),
);
let usd = LazyVecFrom1::transformed::<CentsUnsignedToDollars>(
&format!("{name}_usd"),
version,
cents.read_only_boxed_clone(),
);
Self {
btc,
sats,
usd,
cents,
}
}
}
@@ -1,9 +1,9 @@
mod base;
mod block;
mod cumulative;
mod cumulative_rolling;
mod full;
mod lazy;
mod lazy_block;
mod lazy_derived_resolutions;
mod lazy_rolling_avg;
mod lazy_rolling_sum;
@@ -11,11 +11,11 @@ mod rolling_distribution;
mod with_deltas;
pub use base::*;
pub use block::*;
pub use cumulative::*;
pub use cumulative_rolling::*;
pub use full::*;
pub use lazy::*;
pub use lazy_block::*;
pub use lazy_derived_resolutions::*;
pub use lazy_rolling_avg::*;
pub use lazy_rolling_sum::*;
@@ -27,67 +27,59 @@ impl Vecs {
let window_starts = lookback.window_starts();
let (r_coinbase, r_fees) = rayon::join(
|| {
self.coinbase.compute(starting_height, prices, exit, |vec| {
let mut txout_cursor = indexer.vecs.transactions.first_txout_index.cursor();
let mut count_cursor = indexes.tx_index.output_count.cursor();
self.coinbase.compute_from(
starting_height,
prices,
&indexer.vecs.transactions.first_tx_index,
|_, tx_index| {
let mut txout_cursor = indexer.vecs.transactions.first_txout_index.cursor();
let mut count_cursor = indexes.tx_index.output_count.cursor();
vec.compute_transform(
starting_height,
&indexer.vecs.transactions.first_tx_index,
|(height, tx_index, ..)| {
let ti = tx_index.to_usize();
let ti = tx_index.to_usize();
txout_cursor.advance(ti - txout_cursor.position());
let first_txout_index = txout_cursor.next().unwrap().to_usize();
txout_cursor.advance(ti - txout_cursor.position());
let first_txout_index = txout_cursor.next().unwrap().to_usize();
count_cursor.advance(ti - count_cursor.position());
let output_count: usize = count_cursor.next().unwrap().into();
count_cursor.advance(ti - count_cursor.position());
let output_count: usize = count_cursor.next().unwrap().into();
let sats = indexer.vecs.outputs.value.fold_range_at(
first_txout_index,
first_txout_index + output_count,
Sats::ZERO,
|acc, v| acc + v,
);
(height, sats)
},
exit,
)?;
Ok(())
})
indexer.vecs.outputs.value.fold_range_at(
first_txout_index,
first_txout_index + output_count,
Sats::ZERO,
|acc, v| acc + v,
)
},
exit,
)
},
|| {
self.fees
.compute(starting_height, &window_starts, prices, exit, |vec| {
vec.compute_sum_from_indexes(
starting_height,
&indexer.vecs.transactions.first_tx_index,
&indexes.height.tx_index_count,
&transactions.fees.fee.tx_index,
exit,
)?;
Ok(())
})
self.fees.compute_from_indexes(
starting_height,
&window_starts,
prices,
&indexer.vecs.transactions.first_tx_index,
&indexes.height.tx_index_count,
&transactions.fees.fee.tx_index,
exit,
)
},
);
r_coinbase?;
r_fees?;
self.subsidy.block.sats.compute_transform2(
self.subsidy.compute_from_pair(
starting_height,
prices,
&self.coinbase.block.sats,
&self.fees.block.sats,
|(height, coinbase, fees, ..)| {
(
height,
coinbase.checked_sub(fees).unwrap_or_else(|| {
panic!("coinbase {coinbase:?} < fees {fees:?} at {height:?}")
}),
)
|height, coinbase, fees| {
coinbase.checked_sub(fees).unwrap_or_else(|| {
panic!("coinbase {coinbase:?} < fees {fees:?} at {height:?}")
})
},
exit,
)?;
self.subsidy.compute_rest(starting_height, prices, exit)?;
self.output_volume.compute_subtract(
starting_height,
@@ -96,17 +88,17 @@ impl Vecs {
exit,
)?;
self.unclaimed.block.sats.compute_transform(
self.unclaimed.compute_from(
starting_height,
prices,
&self.subsidy.block.sats,
|(height, subsidy, ..)| {
|height, subsidy| {
let halving = Halving::from(height);
let expected = Sats::FIFTY_BTC / 2_usize.pow(halving.to_usize() as u32);
(height, expected.checked_sub(subsidy).unwrap())
expected.checked_sub(subsidy).unwrap()
},
exit,
)?;
self.unclaimed.compute(prices, starting_height, exit)?;
self.fee_dominance
.compute_binary::<Sats, Sats, RatioSats<PartsPerMillion32>, _, _, _, _>(
@@ -14,85 +14,88 @@ impl Vecs {
exit: &Exit,
) -> Result<()> {
let starting_lengths = indexer.safe_lengths();
self.op_return
.compute_with(starting_lengths.height, prices, exit, |height_vec| {
// Validate computed versions against dependencies
let dep_version = indexer.vecs.outputs.first_txout_index.version()
+ indexer.vecs.outputs.output_type.version()
+ indexer.vecs.outputs.value.version();
height_vec.validate_computed_version_or_reset(dep_version)?;
let height_vec = &mut self.op_return.cumulative.sats.height;
// Get target height
let target_len = indexer.vecs.outputs.first_txout_index.len();
if target_len == 0 {
return Ok(());
}
let target_height = Height::from(target_len - 1);
// Validate computed versions against dependencies
let dep_version = indexer.vecs.outputs.first_txout_index.version()
+ indexer.vecs.outputs.output_type.version()
+ indexer.vecs.outputs.value.version();
height_vec.validate_computed_version_or_reset(dep_version)?;
// Find starting height for this vec
let current_len = height_vec.len();
let starting_height =
Height::from(current_len.min(starting_lengths.height.to_usize()));
// Get target height
let target_len = indexer.vecs.outputs.first_txout_index.len();
if target_len == 0 {
self.op_return
.compute_cents(starting_lengths.height, prices, exit)?;
return Ok(());
}
let target_height = Height::from(target_len - 1);
if starting_height > target_height {
return Ok(());
}
// Find starting height for this vec
let current_len = height_vec.len();
let starting_height = Height::from(current_len.min(starting_lengths.height.to_usize()));
// Pre-collect height-indexed data
let first_txout_indexes: Vec<TxOutIndex> =
indexer.vecs.outputs.first_txout_index.collect_range_at(
starting_height.to_usize(),
target_height.to_usize()
+ 2.min(indexer.vecs.outputs.first_txout_index.len()),
);
if starting_height <= target_height {
// Pre-collect height-indexed data
let first_txout_indexes: Vec<TxOutIndex> =
indexer.vecs.outputs.first_txout_index.collect_range_at(
starting_height.to_usize(),
target_height.to_usize() + 2.min(indexer.vecs.outputs.first_txout_index.len()),
);
let mut output_types_buf: Vec<OutputType> = Vec::new();
let mut values_buf: Vec<Sats> = Vec::new();
let mut output_types_buf: Vec<OutputType> = Vec::new();
let mut values_buf: Vec<Sats> = Vec::new();
let mut cumulative = starting_height
.decremented()
.and_then(|height| height_vec.collect_one(height))
.unwrap_or_default();
height_vec.truncate_if_needed(starting_height)?;
height_vec.truncate_if_needed(starting_height)?;
// Iterate blocks
for h in starting_height.to_usize()..=target_height.to_usize() {
let local_idx = h - starting_height.to_usize();
// Iterate blocks
for h in starting_height.to_usize()..=target_height.to_usize() {
let local_idx = h - starting_height.to_usize();
// Get output range for this block
let first_txout_index = first_txout_indexes[local_idx];
let next_first_txout_index =
if let Some(&next) = first_txout_indexes.get(local_idx + 1) {
next
} else {
TxOutIndex::from(indexer.vecs.outputs.value.len())
};
// Get output range for this block
let first_txout_index = first_txout_indexes[local_idx];
let next_first_txout_index =
if let Some(&next) = first_txout_indexes.get(local_idx + 1) {
next
} else {
TxOutIndex::from(indexer.vecs.outputs.value.len())
};
let out_start = first_txout_index.to_usize();
let out_end = next_first_txout_index.to_usize();
let out_start = first_txout_index.to_usize();
let out_end = next_first_txout_index.to_usize();
// Pre-collect both vecs into reusable buffers
indexer.vecs.outputs.output_type.collect_range_into_at(
out_start,
out_end,
&mut output_types_buf,
);
indexer.vecs.outputs.value.collect_range_into_at(
out_start,
out_end,
&mut values_buf,
);
// Pre-collect both vecs into reusable buffers
indexer.vecs.outputs.output_type.collect_range_into_at(
out_start,
out_end,
&mut output_types_buf,
);
indexer.vecs.outputs.value.collect_range_into_at(
out_start,
out_end,
&mut values_buf,
);
let mut op_return_value = Sats::ZERO;
for (ot, val) in output_types_buf.iter().zip(values_buf.iter()) {
if *ot == OutputType::OpReturn {
op_return_value += *val;
}
let mut op_return_value = Sats::ZERO;
for (ot, val) in output_types_buf.iter().zip(values_buf.iter()) {
if *ot == OutputType::OpReturn {
op_return_value += *val;
}
height_vec.push(op_return_value);
}
height_vec.write()?;
cumulative += op_return_value;
height_vec.push(cumulative);
}
Ok(())
})?;
height_vec.write()?;
}
self.op_return
.compute_cents(starting_lengths.height, prices, exit)?;
Ok(())
}
+8 -10
View File
@@ -87,16 +87,14 @@ impl Vecs {
)?;
}
self.rewards.compute(starting_height, prices, exit, |vec| {
Ok(vec.compute_transform2(
starting_height,
&self.base.blocks_mined.block,
&mining.rewards.coinbase.block.sats,
|(h, mask, val, ..)| (h, MaskSats::apply(mask, val)),
exit,
)?)
})?;
self.rewards.compute_from_pair(
starting_height,
prices,
&self.base.blocks_mined.block,
&mining.rewards.coinbase.block.sats,
|_, mask, value| MaskSats::apply(mask, value),
exit,
)?;
Ok(())
}
}
@@ -17,24 +17,21 @@ impl Vecs {
) -> Result<()> {
let starting_height = indexer.safe_lengths().height;
self.total
.compute_with(starting_height, prices, exit, |sats| {
Ok(sats.compute_transform2(
starting_height,
&outputs.value.op_return.block.sats,
&mining.rewards.unclaimed.block.sats,
|(h, op_return, unclaimed, ..)| {
let genesis = if h.to_usize() == 0 {
Sats::FIFTY_BTC
} else {
Sats::ZERO
};
(h, genesis + op_return + unclaimed)
},
exit,
)?)
})?;
self.total.compute_from_pair(
starting_height,
prices,
&outputs.value.op_return.block.sats,
&mining.rewards.unclaimed.block.sats,
|height, op_return, unclaimed| {
let genesis = if height.to_usize() == 0 {
Sats::FIFTY_BTC
} else {
Sats::ZERO
};
genesis + op_return + unclaimed
},
exit,
)?;
Ok(())
}
}
@@ -20,18 +20,15 @@ impl Vecs {
) -> Result<()> {
let starting_height = indexer.safe_lengths().height;
self.transfer_volume
.compute(starting_height, prices, exit, |sats_vec| {
Ok(sats_vec.compute_filtered_sum_from_indexes(
starting_height,
&indexer.vecs.transactions.first_tx_index,
&indexes.height.tx_index_count,
&fees_vecs.input_value,
|sats| !sats.is_max(),
exit,
)?)
})?;
self.transfer_volume.compute_filtered_from_indexes(
starting_height,
prices,
&indexer.vecs.transactions.first_tx_index,
&indexes.height.tx_index_count,
&fees_vecs.input_value,
|sats| !sats.is_max(),
exit,
)?;
let tx_sums = count_vecs.total.rolling.sum.0.as_array();
let tx_per_sec = self.tx_per_sec.as_mut_array();
for (i, &secs) in Windows::<()>::SECS.iter().enumerate() {