global: snapshot

This commit is contained in:
nym21
2026-03-14 18:27:25 +01:00
parent 9d365f4bbb
commit 0d177494d9
55 changed files with 1117 additions and 1006 deletions
@@ -2,7 +2,7 @@ use brk_cohort::{AmountBucket, ByAddressType};
use brk_error::Result;
use brk_types::{Age, Cents, CheckedSub, Height, Sats, Timestamp, TypeIndex};
use rustc_hash::FxHashSet;
use vecdb::{VecIndex, unlikely};
use vecdb::VecIndex;
use crate::distribution::{
address::{AddressTypeToActivityCounts, HeightToAddressTypeToVec},
@@ -49,8 +49,7 @@ pub(crate) fn process_sent(
let prev_timestamp = height_to_timestamp[receive_height.to_usize()];
let age = Age::new(current_timestamp, prev_timestamp);
// Compute peak price during holding period for peak regret
// This is the max HIGH price between receive and send heights
// Compute peak spot price during holding period for peak regret
let peak_price = price_range_max.max_between(receive_height, current_height);
for (output_type, vec) in by_type.unwrap().into_iter() {
@@ -84,72 +83,33 @@ pub(crate) fn process_sent(
let new_bucket = AmountBucket::from(new_balance);
let crossing_boundary = prev_bucket != new_bucket;
let cohort_state = cohorts
.amount_range
.get_mut_by_bucket(prev_bucket)
.state
.as_mut()
.unwrap();
cohort_state.send(addr_data, value, current_price, prev_price, peak_price, age)?;
// If crossing a bucket boundary, remove the (now-updated) address from old bucket
if will_be_empty || crossing_boundary {
// Subtract from old cohort
let cohort_state = cohorts
.amount_range
.get_mut_by_bucket(prev_bucket)
.state
.as_mut()
.unwrap();
// Debug info for tracking down underflow issues
if unlikely(
cohort_state.inner.supply.utxo_count < addr_data.utxo_count() as u64,
) {
panic!(
"process_sent: cohort underflow detected!\n\
Block context: receive_height={:?}, output_type={:?}, type_index={:?}\n\
prev_balance={}, new_balance={}, value={}\n\
will_be_empty={}, crossing_boundary={}\n\
Address: {:?}",
receive_height,
output_type,
type_index,
prev_balance,
new_balance,
value,
will_be_empty,
crossing_boundary,
addr_data
);
}
cohort_state.subtract(addr_data);
}
// Update address data
addr_data.send(value, prev_price)?;
if will_be_empty {
// Address becoming empty - invariant check
if new_balance.is_not_zero() {
unreachable!()
}
*type_address_count -= 1;
*type_empty_count += 1;
// Move from funded to empty
lookup.move_to_empty(output_type, type_index);
} else {
// Add to new cohort
cohorts
.amount_range
.get_mut_by_bucket(new_bucket)
.state
.as_mut()
.unwrap()
.add(addr_data);
}
} else {
// Address staying in same cohort - update in place
// Migrate address to new bucket or mark as empty
if will_be_empty {
*type_address_count -= 1;
*type_empty_count += 1;
lookup.move_to_empty(output_type, type_index);
} else if crossing_boundary {
cohorts
.amount_range
.get_mut_by_bucket(new_bucket)
.state
.as_mut()
.unwrap()
.send(addr_data, value, current_price, prev_price, peak_price, age)?;
.add(addr_data);
}
}
}
@@ -33,18 +33,12 @@ impl UTXOCohorts<Rw> {
.as_mut()
.unwrap()
.receive_utxo_snapshot(&supply_state, &snapshot);
self.epoch
.mut_vec_from_height(height)
.state
.as_mut()
.unwrap()
.receive_utxo_snapshot(&supply_state, &snapshot);
self.class
.mut_vec_from_timestamp(timestamp)
.state
.as_mut()
.unwrap()
.receive_utxo_snapshot(&supply_state, &snapshot);
if let Some(v) = self.epoch.mut_vec_from_height(height) {
v.state.as_mut().unwrap().receive_utxo_snapshot(&supply_state, &snapshot);
}
if let Some(v) = self.class.mut_vec_from_timestamp(timestamp) {
v.state.as_mut().unwrap().receive_utxo_snapshot(&supply_state, &snapshot);
}
// Update output type cohorts (skip types with no outputs this block)
self.type_.iter_typed_mut().for_each(|(output_type, vecs)| {
@@ -63,34 +63,22 @@ impl UTXOCohorts<Rw> {
.as_mut()
.unwrap()
.send_utxo_precomputed(&sent.spendable_supply, &pre);
self.epoch
.mut_vec_from_height(receive_height)
.state
.as_mut()
.unwrap()
.send_utxo_precomputed(&sent.spendable_supply, &pre);
self.class
.mut_vec_from_timestamp(block_state.timestamp)
.state
.as_mut()
.unwrap()
.send_utxo_precomputed(&sent.spendable_supply, &pre);
if let Some(v) = self.epoch.mut_vec_from_height(receive_height) {
v.state.as_mut().unwrap().send_utxo_precomputed(&sent.spendable_supply, &pre);
}
if let Some(v) = self.class.mut_vec_from_timestamp(block_state.timestamp) {
v.state.as_mut().unwrap().send_utxo_precomputed(&sent.spendable_supply, &pre);
}
} else if sent.spendable_supply.utxo_count > 0 {
// Zero-value UTXOs: just subtract supply
self.age_range.get_mut(age).state.as_mut().unwrap().supply -=
&sent.spendable_supply;
self.epoch
.mut_vec_from_height(receive_height)
.state
.as_mut()
.unwrap()
.supply -= &sent.spendable_supply;
self.class
.mut_vec_from_timestamp(block_state.timestamp)
.state
.as_mut()
.unwrap()
.supply -= &sent.spendable_supply;
if let Some(v) = self.epoch.mut_vec_from_height(receive_height) {
v.state.as_mut().unwrap().supply -= &sent.spendable_supply;
}
if let Some(v) = self.class.mut_vec_from_timestamp(block_state.timestamp) {
v.state.as_mut().unwrap().supply -= &sent.spendable_supply;
}
}
// Update output type cohorts (skip zero-supply entries)
@@ -460,7 +460,7 @@ impl RealizedFull {
.change_1m_rel_to_rcap
.compute_binary::<CentsSigned, Cents, RatioCentsSignedCentsBps32>(
starting_indexes.height,
&self.core.net_pnl.delta.change._1m.cents.height,
&self.core.net_pnl.delta.absolute._1m.cents.height,
&self.core.minimal.cap.cents.height,
exit,
)?;
@@ -468,7 +468,7 @@ impl RealizedFull {
.change_1m_rel_to_mcap
.compute_binary::<CentsSigned, Dollars, RatioCentsSignedDollarsBps32>(
starting_indexes.height,
&self.core.net_pnl.delta.change._1m.cents.height,
&self.core.net_pnl.delta.absolute._1m.cents.height,
height_to_market_cap,
exit,
)?;
@@ -373,6 +373,7 @@ impl<S: Accumulate> CostBasisOps for CostBasisData<S> {
let (base, rest) = CostBasisDistribution::deserialize_with_rest(&data)?;
self.map = Some(base);
self.raw.state = Some(RawState::deserialize(rest)?);
debug_assert!(rest.len() >= 32, "CostBasisData state too short: {} bytes", rest.len());
self.investor_cap_raw = CentsSquaredSats::from_bytes(&rest[16..32])?;
self.pending.clear();
self.raw.pending_cap = PendingCapDelta::default();
@@ -431,6 +432,14 @@ impl<S: Accumulate> CostBasisOps for CostBasisData<S> {
self.apply_map_pending();
self.raw.apply_pending_cap();
self.investor_cap_raw += self.pending_investor_cap.inc;
debug_assert!(
self.investor_cap_raw >= self.pending_investor_cap.dec,
"CostBasis investor_cap_raw underflow!\n\
Path: {:?}\n\
Current (after increments): {:?}\n\
Trying to decrement by: {:?}",
self.raw.pathbuf, self.investor_cap_raw, self.pending_investor_cap.dec
);
self.investor_cap_raw -= self.pending_investor_cap.dec;
self.pending_investor_cap = PendingInvestorCapDelta::default();
}