mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-20 23:04:46 -07:00
global: snapshot
This commit is contained in:
@@ -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()))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user