global: snapshot

This commit is contained in:
nym21
2025-12-19 15:25:48 +01:00
parent 7c86c803fa
commit 03b83846ef
87 changed files with 2851 additions and 470 deletions
@@ -1,6 +1,7 @@
use brk_error::{Error, Result};
use brk_traversable::Traversable;
use brk_types::{CheckedSub, StoredU64, Version};
use schemars::JsonSchema;
use vecdb::{
AnyStoredVec, Database, EagerVec, Exit, GenericStoredVec, ImportableVec, IterableVec, PcoVec,
VecIndex, VecValue,
@@ -16,7 +17,7 @@ const VERSION: Version = Version::ZERO;
pub struct EagerVecsBuilder<I, T>
where
I: VecIndex,
T: ComputedVecValue,
T: ComputedVecValue + JsonSchema,
{
pub first: Option<Box<EagerVec<PcoVec<I, T>>>>,
pub average: Option<Box<EagerVec<PcoVec<I, T>>>>,
@@ -35,7 +36,7 @@ where
impl<I, T> EagerVecsBuilder<I, T>
where
I: VecIndex,
T: ComputedVecValue,
T: ComputedVecValue + JsonSchema,
{
pub fn forced_import(
db: &Database,
@@ -159,7 +160,12 @@ where
/// Compute percentiles from sorted values (assumes values is already sorted)
fn compute_percentiles_from_sorted(&mut self, index: usize, values: &[T]) -> Result<()> {
if let Some(max) = self.max.as_mut() {
max.truncate_push_at(index, *values.last().ok_or(Error::Internal("Empty values for percentiles"))?)?;
max.truncate_push_at(
index,
*values
.last()
.ok_or(Error::Internal("Empty values for percentiles"))?,
)?;
}
if let Some(pct90) = self.pct90.as_mut() {
pct90.truncate_push_at(index, get_percentile(values, 0.90))?;
@@ -1,5 +1,6 @@
use brk_traversable::Traversable;
use brk_types::Version;
use schemars::JsonSchema;
use vecdb::{FromCoarserIndex, IterableBoxedVec, IterableCloneableVec, LazyVecFrom2, VecIndex};
use crate::grouped::{EagerVecsBuilder, VecBuilderOptions};
@@ -12,7 +13,7 @@ use super::ComputedVecValue;
pub struct LazyVecsBuilder<I, T, S1I, S2T>
where
I: VecIndex,
T: ComputedVecValue,
T: ComputedVecValue + JsonSchema,
S1I: VecIndex,
S2T: ComputedVecValue,
{
@@ -30,7 +31,7 @@ const VERSION: Version = Version::ZERO;
impl<I, T, S1I, S2T> LazyVecsBuilder<I, T, S1I, S2T>
where
I: VecIndex,
T: ComputedVecValue + 'static,
T: ComputedVecValue + JsonSchema + 'static,
S1I: VecIndex + 'static + FromCoarserIndex<I>,
S2T: ComputedVecValue,
{
@@ -4,6 +4,7 @@ use brk_traversable::Traversable;
use brk_types::{
DateIndex, DecadeIndex, MonthIndex, QuarterIndex, SemesterIndex, Version, WeekIndex, YearIndex,
};
use schemars::JsonSchema;
use vecdb::{
AnyExportableVec, Database, EagerVec, Exit, ImportableVec, IterableCloneableVec, IterableVec,
PcoVec,
@@ -16,7 +17,7 @@ use super::{ComputedVecValue, EagerVecsBuilder, Source, VecBuilderOptions};
#[derive(Clone)]
pub struct ComputedVecsFromDateIndex<T>
where
T: ComputedVecValue + PartialOrd,
T: ComputedVecValue + PartialOrd + JsonSchema,
{
pub dateindex: Option<EagerVec<PcoVec<DateIndex, T>>>,
pub dateindex_extra: EagerVecsBuilder<DateIndex, T>,
@@ -32,7 +33,7 @@ const VERSION: Version = Version::ZERO;
impl<T> ComputedVecsFromDateIndex<T>
where
T: ComputedVecValue + 'static,
T: ComputedVecValue + JsonSchema + 'static,
{
#[allow(clippy::too_many_arguments)]
pub fn forced_import(
@@ -149,7 +150,7 @@ where
impl<T> Traversable for ComputedVecsFromDateIndex<T>
where
T: ComputedVecValue,
T: ComputedVecValue + JsonSchema,
{
fn to_tree_node(&self) -> brk_traversable::TreeNode {
let dateindex_extra_node = self.dateindex_extra.to_tree_node();
@@ -5,6 +5,7 @@ use brk_types::{
DateIndex, DecadeIndex, DifficultyEpoch, Height, MonthIndex, QuarterIndex, SemesterIndex,
Version, WeekIndex, YearIndex,
};
use schemars::JsonSchema;
use vecdb::{
AnyExportableVec, Database, EagerVec, Exit, ImportableVec, IterableCloneableVec, IterableVec,
PcoVec,
@@ -22,7 +23,7 @@ use super::{ComputedVecValue, EagerVecsBuilder, VecBuilderOptions};
#[derive(Clone)]
pub struct ComputedVecsFromHeight<T>
where
T: ComputedVecValue + PartialOrd,
T: ComputedVecValue + PartialOrd + JsonSchema,
{
pub height: Option<EagerVec<PcoVec<Height, T>>>,
pub height_extra: EagerVecsBuilder<Height, T>,
@@ -41,7 +42,7 @@ const VERSION: Version = Version::ZERO;
impl<T> ComputedVecsFromHeight<T>
where
T: ComputedVecValue + Ord + From<f64> + 'static,
T: ComputedVecValue + Ord + From<f64> + JsonSchema + 'static,
f64: From<T>,
{
#[allow(clippy::too_many_arguments)]
@@ -202,7 +203,7 @@ where
impl<T> Traversable for ComputedVecsFromHeight<T>
where
T: ComputedVecValue,
T: ComputedVecValue + JsonSchema,
{
fn to_tree_node(&self) -> brk_traversable::TreeNode {
let height_extra_node = self.height_extra.to_tree_node();
@@ -2,6 +2,7 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{DifficultyEpoch, Height, Version};
use schemars::JsonSchema;
use vecdb::{AnyExportableVec, Database, EagerVec, Exit, ImportableVec, PcoVec};
use crate::{Indexes, indexes};
@@ -11,7 +12,7 @@ use super::{ComputedVecValue, EagerVecsBuilder, VecBuilderOptions};
#[derive(Clone)]
pub struct ComputedVecsFromHeightStrict<T>
where
T: ComputedVecValue + PartialOrd,
T: ComputedVecValue + PartialOrd + JsonSchema,
{
pub height: EagerVec<PcoVec<Height, T>>,
pub height_extra: EagerVecsBuilder<Height, T>,
@@ -23,7 +24,7 @@ const VERSION: Version = Version::ZERO;
impl<T> ComputedVecsFromHeightStrict<T>
where
T: ComputedVecValue + Ord + From<f64>,
T: ComputedVecValue + Ord + From<f64> + JsonSchema,
f64: From<T>,
{
pub fn forced_import(
@@ -85,7 +86,7 @@ where
impl<T> Traversable for ComputedVecsFromHeightStrict<T>
where
T: ComputedVecValue,
T: ComputedVecValue + JsonSchema,
{
fn to_tree_node(&self) -> brk_traversable::TreeNode {
let height_extra_node = self.height_extra.to_tree_node();
+31 -87
View File
@@ -5,6 +5,7 @@ use brk_types::{
Bitcoin, DateIndex, DecadeIndex, DifficultyEpoch, Dollars, Height, MonthIndex, QuarterIndex,
Sats, SemesterIndex, TxIndex, Version, WeekIndex, YearIndex,
};
use schemars::JsonSchema;
use vecdb::{
AnyExportableVec, AnyVec, CollectableVec, Database, EagerVec, Exit, GenericStoredVec,
ImportableVec, IterableCloneableVec, PcoVec, TypedVecIterator, VecIndex,
@@ -22,7 +23,7 @@ use super::{ComputedVecValue, EagerVecsBuilder, VecBuilderOptions};
#[derive(Clone)]
pub struct ComputedVecsFromTxindex<T>
where
T: ComputedVecValue + PartialOrd,
T: ComputedVecValue + PartialOrd + JsonSchema,
{
pub txindex: Option<Box<EagerVec<PcoVec<TxIndex, T>>>>,
pub height: EagerVecsBuilder<Height, T>,
@@ -41,7 +42,7 @@ const VERSION: Version = Version::ZERO;
impl<T> ComputedVecsFromTxindex<T>
where
T: ComputedVecValue + Ord + From<f64> + 'static,
T: ComputedVecValue + Ord + From<f64> + JsonSchema + 'static,
f64: From<T>,
{
#[allow(clippy::too_many_arguments)]
@@ -256,10 +257,8 @@ impl ComputedVecsFromTxindex<Bitcoin> {
.map(Height::from)
.try_for_each(|height| -> Result<()> {
if let Some(first) = self.height.first.as_mut() {
first.truncate_push(
height,
Bitcoin::from(first_iter.um().get_unwrap(height)),
)?;
first
.truncate_push(height, Bitcoin::from(first_iter.um().get_unwrap(height)))?;
}
if let Some(average) = self.height.average.as_mut() {
average.truncate_push(
@@ -268,28 +267,18 @@ impl ComputedVecsFromTxindex<Bitcoin> {
)?;
}
if let Some(sum) = self.height.sum.as_mut() {
sum.truncate_push(
height,
Bitcoin::from(sum_iter.um().get_unwrap(height)),
)?;
sum.truncate_push(height, Bitcoin::from(sum_iter.um().get_unwrap(height)))?;
}
if let Some(max) = self.height.max.as_mut() {
max.truncate_push(
height,
Bitcoin::from(max_iter.um().get_unwrap(height)),
)?;
max.truncate_push(height, Bitcoin::from(max_iter.um().get_unwrap(height)))?;
}
if let Some(pct90) = self.height.pct90.as_mut() {
pct90.truncate_push(
height,
Bitcoin::from(pct90_iter.um().get_unwrap(height)),
)?;
pct90
.truncate_push(height, Bitcoin::from(pct90_iter.um().get_unwrap(height)))?;
}
if let Some(pct75) = self.height.pct75.as_mut() {
pct75.truncate_push(
height,
Bitcoin::from(pct75_iter.um().get_unwrap(height)),
)?;
pct75
.truncate_push(height, Bitcoin::from(pct75_iter.um().get_unwrap(height)))?;
}
if let Some(median) = self.height.median.as_mut() {
median.truncate_push(
@@ -298,28 +287,18 @@ impl ComputedVecsFromTxindex<Bitcoin> {
)?;
}
if let Some(pct25) = self.height.pct25.as_mut() {
pct25.truncate_push(
height,
Bitcoin::from(pct25_iter.um().get_unwrap(height)),
)?;
pct25
.truncate_push(height, Bitcoin::from(pct25_iter.um().get_unwrap(height)))?;
}
if let Some(pct10) = self.height.pct10.as_mut() {
pct10.truncate_push(
height,
Bitcoin::from(pct10_iter.um().get_unwrap(height)),
)?;
pct10
.truncate_push(height, Bitcoin::from(pct10_iter.um().get_unwrap(height)))?;
}
if let Some(min) = self.height.min.as_mut() {
min.truncate_push(
height,
Bitcoin::from(min_iter.um().get_unwrap(height)),
)?;
min.truncate_push(height, Bitcoin::from(min_iter.um().get_unwrap(height)))?;
}
if let Some(last) = self.height.last.as_mut() {
last.truncate_push(
height,
Bitcoin::from(last_iter.um().get_unwrap(height)),
)?;
last.truncate_push(height, Bitcoin::from(last_iter.um().get_unwrap(height)))?;
}
if let Some(cumulative) = self.height.cumulative.as_mut() {
cumulative.truncate_push(
@@ -381,76 +360,41 @@ impl ComputedVecsFromTxindex<Dollars> {
let price = *close_iter.get_unwrap(height);
if let Some(first) = self.height.first.as_mut() {
first.truncate_push(
height,
price * first_iter.um().get_unwrap(height),
)?;
first.truncate_push(height, price * first_iter.um().get_unwrap(height))?;
}
if let Some(average) = self.height.average.as_mut() {
average.truncate_push(
height,
price * average_iter.um().get_unwrap(height),
)?;
average.truncate_push(height, price * average_iter.um().get_unwrap(height))?;
}
if let Some(sum) = self.height.sum.as_mut() {
sum.truncate_push(
height,
price * sum_iter.um().get_unwrap(height),
)?;
sum.truncate_push(height, price * sum_iter.um().get_unwrap(height))?;
}
if let Some(max) = self.height.max.as_mut() {
max.truncate_push(
height,
price * max_iter.um().get_unwrap(height),
)?;
max.truncate_push(height, price * max_iter.um().get_unwrap(height))?;
}
if let Some(pct90) = self.height.pct90.as_mut() {
pct90.truncate_push(
height,
price * pct90_iter.um().get_unwrap(height),
)?;
pct90.truncate_push(height, price * pct90_iter.um().get_unwrap(height))?;
}
if let Some(pct75) = self.height.pct75.as_mut() {
pct75.truncate_push(
height,
price * pct75_iter.um().get_unwrap(height),
)?;
pct75.truncate_push(height, price * pct75_iter.um().get_unwrap(height))?;
}
if let Some(median) = self.height.median.as_mut() {
median.truncate_push(
height,
price * median_iter.um().get_unwrap(height),
)?;
median.truncate_push(height, price * median_iter.um().get_unwrap(height))?;
}
if let Some(pct25) = self.height.pct25.as_mut() {
pct25.truncate_push(
height,
price * pct25_iter.um().get_unwrap(height),
)?;
pct25.truncate_push(height, price * pct25_iter.um().get_unwrap(height))?;
}
if let Some(pct10) = self.height.pct10.as_mut() {
pct10.truncate_push(
height,
price * pct10_iter.um().get_unwrap(height),
)?;
pct10.truncate_push(height, price * pct10_iter.um().get_unwrap(height))?;
}
if let Some(min) = self.height.min.as_mut() {
min.truncate_push(
height,
price * min_iter.um().get_unwrap(height),
)?;
min.truncate_push(height, price * min_iter.um().get_unwrap(height))?;
}
if let Some(last) = self.height.last.as_mut() {
last.truncate_push(
height,
price * last_iter.um().get_unwrap(height),
)?;
last.truncate_push(height, price * last_iter.um().get_unwrap(height))?;
}
if let Some(cumulative) = self.height.cumulative.as_mut() {
cumulative.truncate_push(
height,
price * cumulative_iter.um().get_unwrap(height),
)?;
cumulative
.truncate_push(height, price * cumulative_iter.um().get_unwrap(height))?;
}
Ok(())
})?;
@@ -463,7 +407,7 @@ impl ComputedVecsFromTxindex<Dollars> {
impl<T> Traversable for ComputedVecsFromTxindex<T>
where
T: ComputedVecValue,
T: ComputedVecValue + JsonSchema,
{
fn to_tree_node(&self) -> brk_traversable::TreeNode {
brk_traversable::TreeNode::Branch(
@@ -1,9 +1,11 @@
use std::mem;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Date, DateIndex, Dollars, StoredF32, Version};
use vecdb::{PcoVec,
use vecdb::{
AnyStoredVec, AnyVec, BoxedVecIterator, CollectableVec, Database, EagerVec, Exit,
GenericStoredVec, IterableVec, VecIndex,
GenericStoredVec, IterableVec, PcoVec, VecIndex,
};
use crate::{Indexes, grouped::source::Source, indexes, utils::OptionExt};
@@ -109,8 +111,14 @@ impl ComputedStandardDeviationVecsFromDateIndex {
macro_rules! import {
($suffix:expr) => {
ComputedVecsFromDateIndex::forced_import(
db, &format!("{name}_{}", $suffix), Source::Compute, version, indexes, opts,
).unwrap()
db,
&format!("{name}_{}", $suffix),
Source::Compute,
version,
indexes,
opts,
)
.unwrap()
};
}
@@ -183,9 +191,7 @@ impl ComputedStandardDeviationVecsFromDateIndex {
source: &impl CollectableVec<DateIndex, StoredF32>,
price_opt: Option<&impl IterableVec<DateIndex, Dollars>>,
) -> Result<()> {
let sma = sma_opt.unwrap_or_else(|| unsafe {
std::mem::transmute(&self.sma.u().dateindex)
});
let sma = sma_opt.unwrap_or_else(|| unsafe { mem::transmute(&self.sma.u().dateindex) });
let min_date = DateIndex::try_from(Date::MIN_RATIO).unwrap();
@@ -345,7 +351,11 @@ impl ComputedStandardDeviationVecsFromDateIndex {
.try_for_each(|v| v.safe_flush(exit))?;
self.mut_stateful_computed().try_for_each(|v| {
v.compute_rest(starting_indexes, exit, None as Option<&EagerVec<PcoVec<_, _>>>)
v.compute_rest(
starting_indexes,
exit,
None as Option<&EagerVec<PcoVec<_, _>>>,
)
})?;
if let Some(zscore) = self.zscore.as_mut() {
@@ -536,7 +546,6 @@ impl ComputedStandardDeviationVecsFromDateIndex {
fn mut_stateful_date_vecs(
&mut self,
) -> impl Iterator<Item = &mut EagerVec<PcoVec<DateIndex, StoredF32>>> {
self.mut_stateful_computed()
.map(|c| c.dateindex.um())
self.mut_stateful_computed().map(|c| c.dateindex.um())
}
}
+1 -1
View File
@@ -217,7 +217,7 @@ impl Vecs {
.or_else(|| self.pools.find_from_coinbase_tag(&coinbase_tag))
.unwrap_or(unknown);
self.height_to_pool.push_if_needed(height, pool.slug)?;
self.height_to_pool.truncate_push(height, pool.slug)?;
Ok(())
})?;
@@ -6,7 +6,7 @@ use brk_traversable::Traversable;
use brk_types::{Height, StoredU64, Version};
use derive_deref::{Deref, DerefMut};
use vecdb::{
AnyStoredVec, Database, EagerVec, Exit, GenericStoredVec, ImportableVec, PcoVec,
AnyStoredVec, AnyVec, Database, EagerVec, Exit, GenericStoredVec, ImportableVec, PcoVec,
TypedVecIterator,
};
@@ -54,6 +54,18 @@ impl From<ByAddressType<EagerVec<PcoVec<Height, StoredU64>>>>
}
impl AddressTypeToHeightToAddressCount {
pub fn min_len(&self) -> usize {
self.p2pk65
.len()
.min(self.p2pk33.len())
.min(self.p2pkh.len())
.min(self.p2sh.len())
.min(self.p2wpkh.len())
.min(self.p2wsh.len())
.min(self.p2tr.len())
.min(self.p2a.len())
}
pub fn forced_import(db: &Database, name: &str, version: Version) -> Result<Self> {
Ok(Self::from(ByAddressType::new_with_name(|type_name| {
Ok(EagerVec::forced_import(
@@ -36,6 +36,8 @@ pub fn process_address_updates(
empty_updates: AddressTypeToTypeIndexMap<EmptyAddressDataWithSource>,
loaded_updates: AddressTypeToTypeIndexMap<LoadedAddressDataWithSource>,
) -> Result<()> {
info!("Processing address updates...");
let empty_result = process_empty_addresses(addresses_data, empty_updates)?;
let loaded_result = process_loaded_addresses(addresses_data, loaded_updates)?;
let all_updates = empty_result.merge(loaded_result);
@@ -5,52 +5,52 @@ use brk_types::{LoadedAddressData, OutputType, TypeIndex};
use super::super::address::AddressTypeToTypeIndexMap;
use super::{EmptyAddressDataWithSource, LoadedAddressDataWithSource, WithAddressDataSource};
/// Source of an address in lookup - reports where the data came from.
#[derive(Clone, Copy)]
pub enum AddressSource {
/// Brand new address (never seen before)
New,
/// Loaded from disk (has existing balance)
Loaded,
/// Was empty (zero balance), now receiving
FromEmpty,
}
/// Context for looking up and storing address data during block processing.
///
/// All addresses should be pre-fetched into the cache before using this.
/// - `loaded`: addresses with non-zero balance (wrapped with source info)
/// - `empty`: addresses that became empty this block (wrapped with source info)
pub struct AddressLookup<'a> {
/// Loaded addresses touched in current block
pub loaded: &'a mut AddressTypeToTypeIndexMap<LoadedAddressDataWithSource>,
/// Empty addresses touched in current block
pub empty: &'a mut AddressTypeToTypeIndexMap<EmptyAddressDataWithSource>,
}
impl<'a> AddressLookup<'a> {
/// Get or create address data for a receive operation.
///
/// Returns (address_data, is_new, from_empty)
pub fn get_or_create_for_receive(
&mut self,
output_type: OutputType,
type_index: TypeIndex,
) -> (&mut LoadedAddressDataWithSource, bool, bool) {
) -> (&mut LoadedAddressDataWithSource, AddressSource) {
use std::collections::hash_map::Entry;
let map = self.loaded.get_mut(output_type).unwrap();
match map.entry(type_index) {
Entry::Occupied(entry) => {
// Entry already exists - check its source
let data = entry.into_mut();
let is_new = data.is_new();
let from_empty = data.is_from_emptyaddressdata();
(data, is_new, from_empty)
let source = match entry.get() {
WithAddressDataSource::New(_) => AddressSource::New,
WithAddressDataSource::FromLoaded(..) => AddressSource::Loaded,
WithAddressDataSource::FromEmpty(..) => AddressSource::FromEmpty,
};
(entry.into_mut(), source)
}
Entry::Vacant(entry) => {
// Check if it was in empty set
if let Some(empty_data) =
self.empty.get_mut(output_type).unwrap().remove(&type_index)
{
let data = entry.insert(empty_data.into());
return (data, false, true);
return (entry.insert(empty_data.into()), AddressSource::FromEmpty);
}
// Not found - create new address
let data =
entry.insert(WithAddressDataSource::New(LoadedAddressData::default()));
(data, true, false)
(
entry.insert(WithAddressDataSource::New(LoadedAddressData::default())),
AddressSource::New,
)
}
}
}
@@ -1,23 +1,13 @@
//! Process received outputs for address cohorts.
//!
//! Updates address cohort states when addresses receive funds:
//! - New addresses enter a cohort
//! - Existing addresses may cross cohort boundaries
//! - Empty addresses become non-empty again
use brk_grouper::{ByAddressType, Filtered};
use brk_grouper::{AmountBucket, ByAddressType};
use brk_types::{Dollars, Sats, TypeIndex};
use rustc_hash::FxHashMap;
use super::super::address::AddressTypeToVec;
use super::super::cohorts::AddressCohorts;
use super::lookup::AddressLookup;
use super::lookup::{AddressLookup, AddressSource};
/// Process received outputs for address cohorts.
///
/// For each received output:
/// 1. Look up or create address data
/// 2. Update address balance and cohort membership
/// 3. Update cohort states (add/subtract for boundary crossings, receive otherwise)
pub fn process_received(
received_data: AddressTypeToVec<(TypeIndex, Sats)>,
cohorts: &mut AddressCohorts,
@@ -31,30 +21,47 @@ pub fn process_received(
continue;
}
// Aggregate receives by address - each address processed exactly once
// Track (total_value, output_count) for correct UTXO counting
let mut aggregated: FxHashMap<TypeIndex, (Sats, u32)> = FxHashMap::default();
for (type_index, value) in vec {
let (addr_data, is_new, from_empty) =
lookup.get_or_create_for_receive(output_type, type_index);
let entry = aggregated.entry(type_index).or_default();
entry.0 += value;
entry.1 += 1;
}
// Update address counts
if is_new || from_empty {
*addr_count.get_mut(output_type).unwrap() += 1;
if from_empty {
for (type_index, (total_value, output_count)) in aggregated {
let (addr_data, source) = lookup.get_or_create_for_receive(output_type, type_index);
match source {
AddressSource::New => {
*addr_count.get_mut(output_type).unwrap() += 1;
}
AddressSource::FromEmpty => {
*addr_count.get_mut(output_type).unwrap() += 1;
*empty_addr_count.get_mut(output_type).unwrap() -= 1;
}
AddressSource::Loaded => {}
}
let prev_balance = addr_data.balance();
let new_balance = prev_balance + value;
let is_new_entry = matches!(source, AddressSource::New | AddressSource::FromEmpty);
// Check if crossing cohort boundary
let prev_cohort = cohorts.amount_range.get(prev_balance);
let new_cohort = cohorts.amount_range.get(new_balance);
let filters_differ = prev_cohort.filter() != new_cohort.filter();
if is_new_entry {
// New/from-empty address - just add to cohort
addr_data.receive_outputs(total_value, price, output_count);
cohorts
.amount_range
.get_mut(total_value) // new_balance = 0 + total_value
.state
.as_mut()
.unwrap()
.add(addr_data);
} else {
let prev_balance = addr_data.balance();
let new_balance = prev_balance + total_value;
if is_new || from_empty || filters_differ {
// Address entering or changing cohorts
if !is_new && !from_empty {
// Subtract from old cohort
if AmountBucket::from(prev_balance) != AmountBucket::from(new_balance) {
// Crossing cohort boundary - subtract from old, add to new
cohorts
.amount_range
.get_mut(prev_balance)
@@ -62,28 +69,24 @@ pub fn process_received(
.as_mut()
.unwrap()
.subtract(addr_data);
addr_data.receive_outputs(total_value, price, output_count);
cohorts
.amount_range
.get_mut(new_balance)
.state
.as_mut()
.unwrap()
.add(addr_data);
} else {
// Staying in same cohort - just receive
cohorts
.amount_range
.get_mut(new_balance)
.state
.as_mut()
.unwrap()
.receive_outputs(addr_data, total_value, price, output_count);
}
// Update address data
addr_data.receive(value, price);
// Add to new cohort
cohorts
.amount_range
.get_mut(new_balance)
.state
.as_mut()
.unwrap()
.add(addr_data);
} else {
// Address staying in same cohort - update in place
cohorts
.amount_range
.get_mut(new_balance)
.state
.as_mut()
.unwrap()
.receive(addr_data, value, price);
}
}
}
@@ -26,16 +26,6 @@ pub enum WithAddressDataSource<T> {
FromEmpty(EmptyAddressIndex, T),
}
impl<T> WithAddressDataSource<T> {
pub fn is_new(&self) -> bool {
matches!(self, Self::New(_))
}
pub fn is_from_emptyaddressdata(&self) -> bool {
matches!(self, Self::FromEmpty(..))
}
}
impl<T> std::ops::Deref for WithAddressDataSource<T> {
type Target = T;
@@ -89,6 +89,16 @@ impl AddressCohortState {
address_data: &mut LoadedAddressData,
value: Sats,
price: Option<Dollars>,
) {
self.receive_outputs(address_data, value, price, 1);
}
pub fn receive_outputs(
&mut self,
address_data: &mut LoadedAddressData,
value: Sats,
price: Option<Dollars>,
output_count: u32,
) {
let compute_price = price.is_some();
@@ -98,7 +108,7 @@ impl AddressCohortState {
value: address_data.balance(),
};
address_data.receive(value, price);
address_data.receive_outputs(value, price, output_count);
let supply_state = SupplyState {
utxo_count: address_data.utxo_count() as u64,
@@ -107,7 +117,7 @@ impl AddressCohortState {
self.inner.receive_(
&SupplyState {
utxo_count: 1,
utxo_count: output_count as u64,
value,
},
price,
@@ -1,10 +1,11 @@
use std::ops::{Add, AddAssign, SubAssign};
use brk_types::{CheckedSub, LoadedAddressData, Sats};
use schemars::JsonSchema;
use serde::Serialize;
use vecdb::{Bytes, Formattable};
#[derive(Debug, Default, Clone, Serialize)]
#[derive(Debug, Default, Clone, Serialize, JsonSchema)]
pub struct SupplyState {
pub utxo_count: u64,
pub value: Sats,
+5 -1
View File
@@ -269,7 +269,11 @@ impl Vecs {
.min(self.any_address_indexes.min_stamped_height())
.min(self.addresses_data.min_stamped_height())
.min(Height::from(self.height_to_unspendable_supply.len()))
.min(Height::from(self.height_to_opreturn_supply.len()));
.min(Height::from(self.height_to_opreturn_supply.len()))
.min(Height::from(self.addresstype_to_height_to_addr_count.min_len()))
.min(Height::from(
self.addresstype_to_height_to_empty_addr_count.min_len(),
));
// 2. Determine start mode and recover/reset state
let start_mode = determine_start_mode(stateful_min, chain_state_height);