global: snapshot

This commit is contained in:
nym21
2026-03-08 12:06:55 +01:00
parent 6bb5c63db7
commit 7f1f6044dc
8 changed files with 226 additions and 21 deletions

View File

@@ -6,22 +6,28 @@ use brk_cohort::{
};
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Indexes, Version};
use brk_types::{Dollars, Height, Indexes, Sats, Version};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, Database, Exit, ReadOnlyClone, ReadableVec, Rw, StorageMode};
use vecdb::{AnyStoredVec, Database, Exit, ReadOnlyClone, ReadableVec, Rw, StorageMode, WritableVec};
use crate::{blocks, distribution::DynCohortVecs, indexes, prices};
use crate::distribution::metrics::{
AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase, CoreCohortMetrics,
ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig, MinimalCohortMetrics,
ProfitabilityMetrics, SupplyMetrics,
use crate::{
blocks,
distribution::{
DynCohortVecs,
metrics::{
AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase, CoreCohortMetrics,
ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig,
MinimalCohortMetrics, ProfitabilityMetrics, SupplyMetrics,
},
state::UTXOCohortState,
},
indexes,
internal::ValueFromHeight,
prices,
};
use super::{percentiles::PercentileCache, vecs::UTXOCohortVecs};
use crate::distribution::state::UTXOCohortState;
const VERSION: Version = Version::new(0);
/// All UTXO cohorts organized by filter type.
@@ -40,6 +46,7 @@ pub struct UTXOCohorts<M: StorageMode = Rw> {
pub lt_amount: ByLowerThanAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
pub type_: BySpendableType<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
pub profitability: ProfitabilityMetrics<M>,
pub matured: ByAgeRange<ValueFromHeight<M>>,
#[traversable(skip)]
pub(super) percentile_cache: PercentileCache,
/// Cached partition_point positions for tick_tock boundary searches.
@@ -218,6 +225,10 @@ impl UTXOCohorts<Rw> {
let lt_amount = ByLowerThanAmount::try_new(&minimal_no_state)?;
let ge_amount = ByGreatEqualAmount::try_new(&minimal_no_state)?;
let matured = ByAgeRange::try_new(&|_f: Filter, name: &'static str| -> Result<ValueFromHeight> {
ValueFromHeight::forced_import(db, &format!("utxo_{name}_matured"), v, indexes)
})?;
Ok(Self {
all,
sth,
@@ -232,11 +243,24 @@ impl UTXOCohorts<Rw> {
lt_amount,
ge_amount,
profitability,
matured,
percentile_cache: PercentileCache::default(),
tick_tock_cached_positions: [0; 20],
})
}
/// Push maturation sats to the matured vecs for the given height.
pub(crate) fn push_maturation(
&mut self,
height: Height,
matured: &ByAgeRange<Sats>,
) -> Result<()> {
for (v, &sats) in self.matured.iter_mut().zip(matured.iter()) {
v.sats.height.truncate_push(height, sats)?;
}
Ok(())
}
pub(crate) fn par_iter_separate_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn DynCohortVecs> {
@@ -383,6 +407,11 @@ impl UTXOCohorts<Rw> {
.try_for_each(|v| v.compute_rest_part1(blocks, prices, starting_indexes, exit))?;
}
// Compute matured cents from sats × price
self.matured
.par_iter_mut()
.try_for_each(|v| v.compute(prices, starting_indexes.height, exit))?;
Ok(())
}
@@ -596,6 +625,11 @@ impl UTXOCohorts<Rw> {
vecs.extend(v.metrics.collect_all_vecs_mut());
}
vecs.extend(self.profitability.collect_all_vecs_mut());
for v in self.matured.iter_mut() {
let base = &mut v.base;
vecs.push(&mut base.sats.height);
vecs.push(&mut base.cents.height);
}
vecs.into_par_iter()
}
@@ -609,6 +643,7 @@ impl UTXOCohorts<Rw> {
pub(crate) fn min_separate_stateful_height_len(&self) -> Height {
self.iter_separate()
.map(|v| Height::from(v.min_stateful_height_len()))
.chain(self.matured.iter().map(|v| Height::from(v.min_stateful_len())))
.min()
.unwrap_or_default()
.min(Height::from(self.profitability.min_stateful_height_len()))

View File

@@ -1,5 +1,5 @@
use brk_cohort::AGE_BOUNDARIES;
use brk_types::{CostBasisSnapshot, ONE_HOUR_IN_SEC, Timestamp};
use brk_cohort::{AGE_BOUNDARIES, ByAgeRange};
use brk_types::{CostBasisSnapshot, ONE_HOUR_IN_SEC, Sats, Timestamp};
use vecdb::{Rw, unlikely};
use crate::distribution::state::BlockState;
@@ -15,13 +15,16 @@ impl UTXOCohorts<Rw> {
/// Uses cached positions per boundary to avoid binary search.
/// Since timestamps are monotonic, positions only advance forward.
/// Complexity: O(k * c) where k = 20 boundaries, c = ~1 (forward scan steps).
///
/// Returns how many sats matured INTO each cohort from the younger adjacent one.
/// `up_to_1h` is always zero since nothing ages into the youngest cohort.
pub(crate) fn tick_tock_next_block(
&mut self,
chain_state: &[BlockState],
timestamp: Timestamp,
) {
) -> ByAgeRange<Sats> {
if chain_state.is_empty() {
return;
return ByAgeRange::default();
}
let prev_timestamp = chain_state.last().unwrap().timestamp;
@@ -29,9 +32,11 @@ impl UTXOCohorts<Rw> {
// Skip if no time has passed
if elapsed == 0 {
return;
return ByAgeRange::default();
}
let mut matured = [Sats::ZERO; 21];
// Get age_range cohort states (indexed 0..21)
// Cohort i covers hours [BOUNDARIES[i-1], BOUNDARIES[i])
// Cohort 0 covers [0, 1) hours
@@ -87,7 +92,10 @@ impl UTXOCohorts<Rw> {
if let Some(state) = age_cohorts[boundary_idx + 1].as_mut() {
state.increment_snapshot(&snapshot);
}
matured[boundary_idx + 1] += block_state.supply.value;
}
}
ByAgeRange::from_array(matured)
}
}

View File

@@ -268,11 +268,8 @@ pub(crate) fn process_blocks(
};
// Process outputs, inputs, and tick-tock in parallel via rayon::join
let (_, oi_result) = rayon::join(
|| {
vecs.utxo_cohorts
.tick_tock_next_block(chain_state, timestamp);
},
let (matured, oi_result) = rayon::join(
|| vecs.utxo_cohorts.tick_tock_next_block(chain_state, timestamp),
|| -> Result<_> {
let (outputs_result, inputs_result) = rayon::join(
|| {
@@ -355,6 +352,9 @@ pub(crate) fn process_blocks(
timestamp,
});
// Record maturation (sats crossing age boundaries)
vecs.utxo_cohorts.push_maturation(height, &matured)?;
// Build set of addresses that received this block (for detecting "both" in sent)
// Reuse pre-allocated hashsets: clear preserves capacity, avoiding reallocation
received_addresses.values_mut().for_each(|set| set.clear());