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