global: snapshot

This commit is contained in:
nym21
2026-03-12 13:46:13 +01:00
parent 90078760c1
commit c2135a7066
41 changed files with 1788 additions and 1397 deletions

View File

@@ -22,7 +22,6 @@ brk_store = { workspace = true }
brk_traversable = { workspace = true }
brk_types = { workspace = true }
derive_more = { workspace = true }
pco = { workspace = true }
tracing = { workspace = true }
rayon = { workspace = true }
rustc-hash = { workspace = true }

View File

@@ -12,11 +12,11 @@ use vecdb::{
use crate::{indexes, internal::ComputedPerBlock};
#[derive(Deref, DerefMut, Traversable)]
pub struct AddrCountVecs<M: StorageMode = Rw>(
pub struct AddressCountVecs<M: StorageMode = Rw>(
#[traversable(flatten)] pub ComputedPerBlock<StoredU64, M>,
);
impl AddrCountVecs {
impl AddressCountVecs {
pub(crate) fn forced_import(
db: &Database,
name: &str,
@@ -40,9 +40,9 @@ impl AddressTypeToAddressCount {
}
}
impl From<(&AddressTypeToAddrCountVecs, Height)> for AddressTypeToAddressCount {
impl From<(&AddressTypeToAddressCountVecs, Height)> for AddressTypeToAddressCount {
#[inline]
fn from((groups, starting_height): (&AddressTypeToAddrCountVecs, Height)) -> Self {
fn from((groups, starting_height): (&AddressTypeToAddressCountVecs, Height)) -> Self {
if let Some(prev_height) = starting_height.decremented() {
Self(ByAddressType {
p2pk65: groups
@@ -102,25 +102,25 @@ impl From<(&AddressTypeToAddrCountVecs, Height)> for AddressTypeToAddressCount {
/// Address count per address type, with height + derived indexes.
#[derive(Deref, DerefMut, Traversable)]
pub struct AddressTypeToAddrCountVecs<M: StorageMode = Rw>(ByAddressType<AddrCountVecs<M>>);
pub struct AddressTypeToAddressCountVecs<M: StorageMode = Rw>(ByAddressType<AddressCountVecs<M>>);
impl From<ByAddressType<AddrCountVecs>> for AddressTypeToAddrCountVecs {
impl From<ByAddressType<AddressCountVecs>> for AddressTypeToAddressCountVecs {
#[inline]
fn from(value: ByAddressType<AddrCountVecs>) -> Self {
fn from(value: ByAddressType<AddressCountVecs>) -> Self {
Self(value)
}
}
impl AddressTypeToAddrCountVecs {
impl AddressTypeToAddressCountVecs {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self::from(ByAddressType::<AddrCountVecs>::new_with_name(
Ok(Self::from(ByAddressType::<AddressCountVecs>::new_with_name(
|type_name| {
AddrCountVecs::forced_import(db, &format!("{type_name}_{name}"), version, indexes)
AddressCountVecs::forced_import(db, &format!("{type_name}_{name}"), version, indexes)
},
)?))
}
@@ -140,9 +140,9 @@ impl AddressTypeToAddrCountVecs {
pub(crate) fn truncate_push_height(
&mut self,
height: Height,
addr_counts: &AddressTypeToAddressCount,
address_counts: &AddressTypeToAddressCount,
) -> Result<()> {
for (vecs, &count) in self.0.values_mut().zip(addr_counts.values()) {
for (vecs, &count) in self.0.values_mut().zip(address_counts.values()) {
vecs.height.truncate_push(height, count.into())?;
}
Ok(())
@@ -162,13 +162,13 @@ impl AddressTypeToAddrCountVecs {
}
#[derive(Traversable)]
pub struct AddrCountsVecs<M: StorageMode = Rw> {
pub all: AddrCountVecs<M>,
pub struct AddressCountsVecs<M: StorageMode = Rw> {
pub all: AddressCountVecs<M>,
#[traversable(flatten)]
pub by_addresstype: AddressTypeToAddrCountVecs<M>,
pub by_addresstype: AddressTypeToAddressCountVecs<M>,
}
impl AddrCountsVecs {
impl AddressCountsVecs {
pub(crate) fn forced_import(
db: &Database,
name: &str,
@@ -176,8 +176,8 @@ impl AddrCountsVecs {
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
all: AddrCountVecs::forced_import(db, name, version, indexes)?,
by_addresstype: AddressTypeToAddrCountVecs::forced_import(db, name, version, indexes)?,
all: AddressCountVecs::forced_import(db, name, version, indexes)?,
by_addresstype: AddressTypeToAddressCountVecs::forced_import(db, name, version, indexes)?,
})
}
@@ -202,11 +202,11 @@ impl AddrCountsVecs {
&mut self,
height: Height,
total: u64,
addr_counts: &AddressTypeToAddressCount,
address_counts: &AddressTypeToAddressCount,
) -> Result<()> {
self.all.height.truncate_push(height, total.into())?;
self.by_addresstype
.truncate_push_height(height, addr_counts)?;
.truncate_push_height(height, address_counts)?;
Ok(())
}

View File

@@ -9,7 +9,7 @@ use crate::{
internal::{WindowStarts, RollingDelta},
};
use super::AddrCountsVecs;
use super::AddressCountsVecs;
#[derive(Traversable)]
pub struct DeltaVecs<M: StorageMode = Rw> {
@@ -26,10 +26,10 @@ impl DeltaVecs {
) -> Result<Self> {
let version = version + Version::TWO;
let all = RollingDelta::forced_import(db, "addr_count", version, indexes)?;
let all = RollingDelta::forced_import(db, "address_count", version, indexes)?;
let by_addresstype = ByAddressType::new_with_name(|name| {
RollingDelta::forced_import(db, &format!("{name}_addr_count"), version, indexes)
RollingDelta::forced_import(db, &format!("{name}_address_count"), version, indexes)
})?;
Ok(Self {
@@ -42,16 +42,16 @@ impl DeltaVecs {
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
addr_count: &AddrCountsVecs,
address_count: &AddressCountsVecs,
exit: &Exit,
) -> Result<()> {
self.all
.compute(max_from, windows, &addr_count.all.height, exit)?;
.compute(max_from, windows, &address_count.all.height, exit)?;
for ((_, growth), (_, addr)) in self
.by_addresstype
.iter_mut()
.zip(addr_count.by_addresstype.iter())
.zip(address_count.by_addresstype.iter())
{
growth.compute(max_from, windows, &addr.height, exit)?;
}

View File

@@ -3,15 +3,15 @@ mod address_count;
mod data;
mod delta;
mod indexes;
mod new_addr_count;
mod total_addr_count;
mod new_address_count;
mod total_address_count;
mod type_map;
pub use activity::{AddressActivityVecs, AddressTypeToActivityCounts};
pub use address_count::{AddrCountsVecs, AddressTypeToAddressCount};
pub use address_count::{AddressCountsVecs, AddressTypeToAddressCount};
pub use data::AddressesDataVecs;
pub use delta::DeltaVecs;
pub use indexes::AnyAddressIndexesVecs;
pub use new_addr_count::NewAddrCountVecs;
pub use total_addr_count::TotalAddrCountVecs;
pub use new_address_count::NewAddressCountVecs;
pub use total_address_count::TotalAddressCountVecs;
pub use type_map::{AddressTypeToTypeIndexMap, AddressTypeToVec, HeightToAddressTypeToVec};

View File

@@ -9,29 +9,29 @@ use crate::{
internal::{ComputedPerBlockSum, WindowStarts},
};
use super::TotalAddrCountVecs;
use super::TotalAddressCountVecs;
/// New address count per block (global + per-type)
#[derive(Traversable)]
pub struct NewAddrCountVecs<M: StorageMode = Rw> {
pub struct NewAddressCountVecs<M: StorageMode = Rw> {
pub all: ComputedPerBlockSum<StoredU64, M>,
#[traversable(flatten)]
pub by_addresstype: ByAddressType<ComputedPerBlockSum<StoredU64, M>>,
}
impl NewAddrCountVecs {
impl NewAddressCountVecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let all = ComputedPerBlockSum::forced_import(db, "new_addr_count", version, indexes)?;
let all = ComputedPerBlockSum::forced_import(db, "new_address_count", version, indexes)?;
let by_addresstype: ByAddressType<ComputedPerBlockSum<StoredU64>> =
ByAddressType::new_with_name(|name| {
ComputedPerBlockSum::forced_import(
db,
&format!("{name}_new_addr_count"),
&format!("{name}_new_address_count"),
version,
indexes,
)
@@ -47,17 +47,17 @@ impl NewAddrCountVecs {
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
total_addr_count: &TotalAddrCountVecs,
total_address_count: &TotalAddressCountVecs,
exit: &Exit,
) -> Result<()> {
self.all.compute(max_from, windows, exit, |height_vec| {
Ok(height_vec.compute_change(max_from, &total_addr_count.all.height, 1, exit)?)
Ok(height_vec.compute_change(max_from, &total_address_count.all.height, 1, exit)?)
})?;
for ((_, new), (_, total)) in self
.by_addresstype
.iter_mut()
.zip(total_addr_count.by_addresstype.iter())
.zip(total_address_count.by_addresstype.iter())
{
new.compute(max_from, windows, exit, |height_vec| {
Ok(height_vec.compute_change(max_from, &total.height, 1, exit)?)

View File

@@ -6,29 +6,29 @@ use vecdb::{Database, Exit, Rw, StorageMode};
use crate::{indexes, internal::ComputedPerBlock};
use super::AddrCountsVecs;
use super::AddressCountsVecs;
/// Total address count (global + per-type) with all derived indexes
#[derive(Traversable)]
pub struct TotalAddrCountVecs<M: StorageMode = Rw> {
pub struct TotalAddressCountVecs<M: StorageMode = Rw> {
pub all: ComputedPerBlock<StoredU64, M>,
#[traversable(flatten)]
pub by_addresstype: ByAddressType<ComputedPerBlock<StoredU64, M>>,
}
impl TotalAddrCountVecs {
impl TotalAddressCountVecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let all = ComputedPerBlock::forced_import(db, "total_addr_count", version, indexes)?;
let all = ComputedPerBlock::forced_import(db, "total_address_count", version, indexes)?;
let by_addresstype: ByAddressType<ComputedPerBlock<StoredU64>> =
ByAddressType::new_with_name(|name| {
ComputedPerBlock::forced_import(
db,
&format!("{name}_total_addr_count"),
&format!("{name}_total_address_count"),
version,
indexes,
)
@@ -40,26 +40,26 @@ impl TotalAddrCountVecs {
})
}
/// Eagerly compute total = addr_count + empty_addr_count.
/// Eagerly compute total = address_count + empty_address_count.
pub(crate) fn compute(
&mut self,
max_from: Height,
addr_count: &AddrCountsVecs,
empty_addr_count: &AddrCountsVecs,
address_count: &AddressCountsVecs,
empty_address_count: &AddressCountsVecs,
exit: &Exit,
) -> Result<()> {
self.all.height.compute_add(
max_from,
&addr_count.all.height,
&empty_addr_count.all.height,
&address_count.all.height,
&empty_address_count.all.height,
exit,
)?;
for ((_, total), ((_, addr), (_, empty))) in self.by_addresstype.iter_mut().zip(
addr_count
address_count
.by_addresstype
.iter()
.zip(empty_addr_count.by_addresstype.iter()),
.zip(empty_address_count.by_addresstype.iter()),
) {
total
.height

View File

@@ -22,8 +22,8 @@ pub(crate) fn process_received(
cohorts: &mut AddressCohorts,
lookup: &mut AddressLookup<'_>,
price: Cents,
addr_count: &mut ByAddressType<u64>,
empty_addr_count: &mut ByAddressType<u64>,
address_count: &mut ByAddressType<u64>,
empty_address_count: &mut ByAddressType<u64>,
activity_counts: &mut AddressTypeToActivityCounts,
) {
let max_type_len = received_data.iter().map(|(_, v)| v.len()).max().unwrap_or(0);
@@ -36,8 +36,8 @@ pub(crate) fn process_received(
}
// Cache mutable refs for this address type
let type_addr_count = addr_count.get_mut(output_type).unwrap();
let type_empty_count = empty_addr_count.get_mut(output_type).unwrap();
let type_address_count = address_count.get_mut(output_type).unwrap();
let type_empty_count = empty_address_count.get_mut(output_type).unwrap();
let type_activity = activity_counts.get_mut_unwrap(output_type);
// Aggregate receives by address - each address processed exactly once
@@ -55,10 +55,10 @@ pub(crate) fn process_received(
match status {
TrackingStatus::New => {
*type_addr_count += 1;
*type_address_count += 1;
}
TrackingStatus::WasEmpty => {
*type_addr_count += 1;
*type_address_count += 1;
*type_empty_count -= 1;
// Reactivated - was empty, now has funds
type_activity.reactivated += 1;

View File

@@ -32,8 +32,8 @@ pub(crate) fn process_sent(
lookup: &mut AddressLookup<'_>,
current_price: Cents,
price_range_max: &PriceRangeMax,
addr_count: &mut ByAddressType<u64>,
empty_addr_count: &mut ByAddressType<u64>,
address_count: &mut ByAddressType<u64>,
empty_address_count: &mut ByAddressType<u64>,
activity_counts: &mut AddressTypeToActivityCounts,
received_addresses: &ByAddressType<FxHashSet<TypeIndex>>,
height_to_price: &[Cents],
@@ -55,8 +55,8 @@ pub(crate) fn process_sent(
for (output_type, vec) in by_type.unwrap().into_iter() {
// Cache mutable refs for this address type
let type_addr_count = addr_count.get_mut(output_type).unwrap();
let type_empty_count = empty_addr_count.get_mut(output_type).unwrap();
let type_address_count = address_count.get_mut(output_type).unwrap();
let type_empty_count = empty_address_count.get_mut(output_type).unwrap();
let type_activity = activity_counts.get_mut_unwrap(output_type);
let type_received = received_addresses.get(output_type);
let type_seen = seen_senders.get_mut_unwrap(output_type);
@@ -126,7 +126,7 @@ pub(crate) fn process_sent(
unreachable!()
}
*type_addr_count -= 1;
*type_address_count -= 1;
*type_empty_count += 1;
// Move from funded to empty

View File

@@ -96,10 +96,10 @@ impl AddressCohorts {
exit: &Exit,
) -> Result<()> {
self.par_iter_mut().try_for_each(|v| {
v.addr_count_delta.compute(
v.address_count_delta.compute(
starting_indexes.height,
&blocks.lookback._1m,
&v.addr_count.height,
&v.address_count.height,
exit,
)
})?;

View File

@@ -28,9 +28,9 @@ pub struct AddressCohortVecs<M: StorageMode = Rw> {
#[traversable(flatten)]
pub metrics: MinimalCohortMetrics<M>,
pub addr_count: ComputedPerBlock<StoredU64, M>,
#[traversable(wrap = "addr_count", rename = "delta")]
pub addr_count_delta: RollingDelta1m<StoredU64, StoredI64, M>,
pub address_count: ComputedPerBlock<StoredU64, M>,
#[traversable(wrap = "address_count", rename = "delta")]
pub address_count_delta: RollingDelta1m<StoredU64, StoredI64, M>,
}
impl AddressCohortVecs {
@@ -59,15 +59,15 @@ impl AddressCohortVecs {
metrics: MinimalCohortMetrics::forced_import(&cfg)?,
addr_count: ComputedPerBlock::forced_import(
address_count: ComputedPerBlock::forced_import(
db,
&cfg.name("addr_count"),
&cfg.name("address_count"),
version,
indexes,
)?,
addr_count_delta: RollingDelta1m::forced_import(
address_count_delta: RollingDelta1m::forced_import(
db,
&cfg.name("addr_count_delta"),
&cfg.name("address_count_delta"),
version + Version::ONE,
indexes,
)?,
@@ -82,7 +82,7 @@ impl AddressCohortVecs {
&mut self,
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
vecs.push(&mut self.addr_count.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.address_count.height as &mut dyn AnyStoredVec);
vecs.extend(self.metrics.collect_all_vecs_mut());
vecs.into_par_iter()
}
@@ -103,7 +103,7 @@ impl Filtered for AddressCohortVecs {
impl DynCohortVecs for AddressCohortVecs {
fn min_stateful_len(&self) -> usize {
self.addr_count
self.address_count
.height
.len()
.min(self.metrics.min_stateful_len())
@@ -136,7 +136,7 @@ impl DynCohortVecs for AddressCohortVecs {
.height
.collect_one(prev_height)
.unwrap();
state.addr_count = *self.addr_count.height.collect_one(prev_height).unwrap();
state.address_count = *self.address_count.height.collect_one(prev_height).unwrap();
state.inner.restore_realized_cap();
@@ -155,7 +155,7 @@ impl DynCohortVecs for AddressCohortVecs {
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
use vecdb::WritableVec;
self.addr_count
self.address_count
.height
.validate_computed_version_or_reset(base_version)?;
Ok(())
@@ -167,9 +167,9 @@ impl DynCohortVecs for AddressCohortVecs {
}
if let Some(state) = self.state.as_ref() {
self.addr_count
self.address_count
.height
.truncate_push(height, state.addr_count.into())?;
.truncate_push(height, state.address_count.into())?;
self.metrics.supply.truncate_push(height, &state.inner)?;
self.metrics.outputs.truncate_push(height, &state.inner)?;
self.metrics.realized.truncate_push(height, &state.inner)?;
@@ -226,11 +226,11 @@ impl CohortVecs for AddressCohortVecs {
others: &[&Self],
exit: &Exit,
) -> Result<()> {
self.addr_count.height.compute_sum_of_others(
self.address_count.height.compute_sum_of_others(
starting_indexes.height,
others
.iter()
.map(|v| &v.addr_count.height)
.map(|v| &v.address_count.height)
.collect::<Vec<_>>()
.as_slice(),
exit,

View File

@@ -520,10 +520,10 @@ impl UTXOCohorts<Rw> {
where
HM: ReadableVec<Height, Dollars> + Sync,
{
// Get up_to_1h value sources for adjusted computation (cloned to avoid borrow conflicts).
let up_to_1h_value_created = self
// Get under_1h value sources for adjusted computation (cloned to avoid borrow conflicts).
let under_1h_value_created = self
.age_range
.up_to_1h
.under_1h
.metrics
.realized
.minimal
@@ -532,9 +532,9 @@ impl UTXOCohorts<Rw> {
.raw
.height
.read_only_clone();
let up_to_1h_value_destroyed = self
let under_1h_value_destroyed = self
.age_range
.up_to_1h
.under_1h
.metrics
.realized
.minimal
@@ -550,8 +550,8 @@ impl UTXOCohorts<Rw> {
prices,
starting_indexes,
height_to_market_cap,
&up_to_1h_value_created,
&up_to_1h_value_destroyed,
&under_1h_value_created,
&under_1h_value_destroyed,
exit,
)?;
@@ -576,8 +576,8 @@ impl UTXOCohorts<Rw> {
// All remaining groups run in parallel. Each closure owns an exclusive &mut
// to its field and shares read-only references to common data.
let vc = &up_to_1h_value_created;
let vd = &up_to_1h_value_destroyed;
let vc = &under_1h_value_created;
let vd = &under_1h_value_destroyed;
let ss = &all_supply_sats;
let tasks: Vec<Box<dyn FnOnce() -> Result<()> + Send + '_>> = vec![

View File

@@ -9,7 +9,7 @@ impl UTXOCohorts<Rw> {
/// Process received outputs for this block.
///
/// New UTXOs are added to:
/// - The "up_to_1h" age cohort (all new UTXOs start at 0 hours old)
/// - The "under_1h" age cohort (all new UTXOs start at 0 hours old)
/// - The appropriate epoch cohort based on block height
/// - The appropriate class cohort based on block timestamp
/// - The appropriate output type cohort (P2PKH, P2SH, etc.)
@@ -26,9 +26,9 @@ impl UTXOCohorts<Rw> {
// Pre-compute snapshot once for the 3 cohorts sharing the same supply_state
let snapshot = CostBasisSnapshot::from_utxo(price, &supply_state);
// New UTXOs go into up_to_1h, current epoch, and current class
// New UTXOs go into under_1h, current epoch, and current class
self.age_range
.up_to_1h
.under_1h
.state
.as_mut()
.unwrap()

View File

@@ -17,7 +17,7 @@ impl UTXOCohorts<Rw> {
/// 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.
/// `under_1h` is always zero since nothing ages into the youngest cohort.
pub(crate) fn tick_tock_next_block(
&mut self,
chain_state: &[BlockState],

View File

@@ -183,22 +183,22 @@ pub(crate) fn process_blocks(
.collect_range_at(start_usize, end_usize);
// Track running totals - recover from previous height if resuming
debug!("recovering addr_counts from height {}", starting_height);
let (mut addr_counts, mut empty_addr_counts) = if starting_height > Height::ZERO {
let addr_counts =
debug!("recovering address_counts from height {}", starting_height);
let (mut address_counts, mut empty_address_counts) = if starting_height > Height::ZERO {
let address_counts =
AddressTypeToAddressCount::from((&vecs.addresses.funded.by_addresstype, starting_height));
let empty_addr_counts = AddressTypeToAddressCount::from((
let empty_address_counts = AddressTypeToAddressCount::from((
&vecs.addresses.empty.by_addresstype,
starting_height,
));
(addr_counts, empty_addr_counts)
(address_counts, empty_address_counts)
} else {
(
AddressTypeToAddressCount::default(),
AddressTypeToAddressCount::default(),
)
};
debug!("addr_counts recovered");
debug!("address_counts recovered");
// Track activity counts - reset each block
let mut activity_counts = AddressTypeToActivityCounts::default();
@@ -406,8 +406,8 @@ pub(crate) fn process_blocks(
&mut vecs.address_cohorts,
&mut lookup,
block_price,
&mut addr_counts,
&mut empty_addr_counts,
&mut address_counts,
&mut empty_address_counts,
&mut activity_counts,
);
@@ -418,8 +418,8 @@ pub(crate) fn process_blocks(
&mut lookup,
block_price,
ctx.price_range_max,
&mut addr_counts,
&mut empty_addr_counts,
&mut address_counts,
&mut empty_address_counts,
&mut activity_counts,
&received_addresses,
height_to_price_vec,
@@ -437,11 +437,11 @@ pub(crate) fn process_blocks(
// Push to height-indexed vectors
vecs.addresses.funded
.truncate_push_height(height, addr_counts.sum(), &addr_counts)?;
.truncate_push_height(height, address_counts.sum(), &address_counts)?;
vecs.addresses.empty.truncate_push_height(
height,
empty_addr_counts.sum(),
&empty_addr_counts,
empty_address_counts.sum(),
&empty_address_counts,
)?;
vecs.addresses.activity
.truncate_push_height(height, &activity_counts)?;

View File

@@ -114,8 +114,8 @@ impl AllCohortMetrics {
prices: &prices::Vecs,
starting_indexes: &Indexes,
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
up_to_1h_value_created: &impl ReadableVec<Height, Cents>,
up_to_1h_value_destroyed: &impl ReadableVec<Height, Cents>,
under_1h_value_created: &impl ReadableVec<Height, Cents>,
under_1h_value_destroyed: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
self.realized.compute_rest_part2(
@@ -132,8 +132,8 @@ impl AllCohortMetrics {
starting_indexes,
&self.realized.minimal.sopr.value_created.raw.height,
&self.realized.minimal.sopr.value_destroyed.raw.height,
up_to_1h_value_created,
up_to_1h_value_destroyed,
under_1h_value_created,
under_1h_value_destroyed,
exit,
)?;

View File

@@ -63,8 +63,8 @@ impl ExtendedAdjustedCohortMetrics {
prices: &prices::Vecs,
starting_indexes: &Indexes,
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
up_to_1h_value_created: &impl ReadableVec<Height, Cents>,
up_to_1h_value_destroyed: &impl ReadableVec<Height, Cents>,
under_1h_value_created: &impl ReadableVec<Height, Cents>,
under_1h_value_destroyed: &impl ReadableVec<Height, Cents>,
all_supply_sats: &impl ReadableVec<Height, Sats>,
exit: &Exit,
) -> Result<()> {
@@ -82,8 +82,8 @@ impl ExtendedAdjustedCohortMetrics {
starting_indexes,
&self.inner.realized.minimal.sopr.value_created.raw.height,
&self.inner.realized.minimal.sopr.value_destroyed.raw.height,
up_to_1h_value_created,
up_to_1h_value_destroyed,
under_1h_value_created,
under_1h_value_destroyed,
exit,
)?;

View File

@@ -24,11 +24,11 @@ pub struct AdjustedSopr<M: StorageMode = Rw> {
impl AdjustedSopr {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
Ok(Self {
value_created: cfg.import("adjusted_value_created", Version::ZERO)?,
value_destroyed: cfg.import("adjusted_value_destroyed", Version::ZERO)?,
value_created_sum: cfg.import("adjusted_value_created", Version::ONE)?,
value_destroyed_sum: cfg.import("adjusted_value_destroyed", Version::ONE)?,
ratio: cfg.import("adjusted_sopr", Version::ONE)?,
value_created: cfg.import("adj_value_created", Version::ZERO)?,
value_destroyed: cfg.import("adj_value_destroyed", Version::ZERO)?,
value_created_sum: cfg.import("adj_value_created", Version::ONE)?,
value_destroyed_sum: cfg.import("adj_value_destroyed", Version::ONE)?,
ratio: cfg.import("asopr", Version::ONE)?,
})
}
@@ -39,21 +39,21 @@ impl AdjustedSopr {
starting_indexes: &Indexes,
base_value_created: &impl ReadableVec<Height, Cents>,
base_value_destroyed: &impl ReadableVec<Height, Cents>,
up_to_1h_value_created: &impl ReadableVec<Height, Cents>,
up_to_1h_value_destroyed: &impl ReadableVec<Height, Cents>,
under_1h_value_created: &impl ReadableVec<Height, Cents>,
under_1h_value_destroyed: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
// Compute value_created = base.value_created - up_to_1h.value_created
// Compute value_created = base.value_created - under_1h.value_created
self.value_created.height.compute_subtract(
starting_indexes.height,
base_value_created,
up_to_1h_value_created,
under_1h_value_created,
exit,
)?;
self.value_destroyed.height.compute_subtract(
starting_indexes.height,
base_value_destroyed,
up_to_1h_value_destroyed,
under_1h_value_destroyed,
exit,
)?;

View File

@@ -154,7 +154,7 @@ impl RealizedFull {
&profit_value_destroyed,
);
let profit = RealizedProfit {
rel_to_rcap: cfg.import("realized_profit_rel_to_realized_cap", Version::new(2))?,
rel_to_rcap: cfg.import("realized_profit_rel_to_rcap", Version::new(2))?,
value_created: cfg.import("profit_value_created", v0)?,
value_destroyed: profit_value_destroyed,
value_created_sum: cfg.import("profit_value_created", v1)?,
@@ -173,7 +173,7 @@ impl RealizedFull {
&loss_value_destroyed,
);
let loss = RealizedLoss {
rel_to_rcap: cfg.import("realized_loss_rel_to_realized_cap", Version::new(2))?,
rel_to_rcap: cfg.import("realized_loss_rel_to_rcap", Version::new(2))?,
value_created: cfg.import("loss_value_created", v0)?,
value_destroyed: loss_value_destroyed,
value_created_sum: cfg.import("loss_value_created", v1)?,
@@ -192,15 +192,15 @@ impl RealizedFull {
// Net PnL
let net_pnl = RealizedNetPnl {
rel_to_rcap: cfg
.import("net_realized_pnl_rel_to_realized_cap", Version::new(2))?,
.import("net_realized_pnl_rel_to_rcap", Version::new(2))?,
cumulative: cfg.import("net_realized_pnl_cumulative", v1)?,
sum_extended: cfg.import("net_realized_pnl", v1)?,
delta: cfg.import("net_pnl_delta", Version::new(5))?,
delta_extended: cfg.import("net_pnl_delta", Version::new(5))?,
change_1m_rel_to_rcap: cfg
.import("net_pnl_change_1m_rel_to_realized_cap", Version::new(4))?,
.import("net_pnl_change_1m_rel_to_rcap", Version::new(4))?,
change_1m_rel_to_mcap: cfg
.import("net_pnl_change_1m_rel_to_market_cap", Version::new(4))?,
.import("net_pnl_change_1m_rel_to_mcap", Version::new(4))?,
};
// SOPR
@@ -214,7 +214,7 @@ impl RealizedFull {
let peak_regret = RealizedPeakRegret {
value: cfg.import("realized_peak_regret", Version::new(2))?,
rel_to_rcap: cfg
.import("realized_peak_regret_rel_to_realized_cap", Version::new(2))?,
.import("realized_peak_regret_rel_to_rcap", Version::new(2))?,
};
// Investor
@@ -241,7 +241,7 @@ impl RealizedFull {
profit_to_loss_ratio: cfg.import("realized_profit_to_loss_ratio", v1)?,
cap_delta_extended: cfg.import("realized_cap_delta", Version::new(5))?,
cap_raw: cfg.import("cap_raw", v0)?,
cap_rel_to_own_mcap: cfg.import("realized_cap_rel_to_own_market_cap", v1)?,
cap_rel_to_own_mcap: cfg.import("realized_cap_rel_to_own_mcap", v1)?,
price_ratio_percentiles: RatioPerBlockPercentiles::forced_import(
cfg.db,
&realized_price_name,

View File

@@ -10,12 +10,12 @@ use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
/// Extended relative metrics for own market cap (extended && rel_to_all).
#[derive(Traversable)]
pub struct RelativeExtendedOwnMarketCap<M: StorageMode = Rw> {
#[traversable(wrap = "unrealized/profit", rename = "rel_to_own_market_cap")]
pub unrealized_profit_rel_to_own_market_cap: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "unrealized/loss", rename = "rel_to_own_market_cap")]
pub unrealized_loss_rel_to_own_market_cap: PercentPerBlock<BasisPoints32, M>,
#[traversable(wrap = "unrealized/net_pnl", rename = "rel_to_own_market_cap")]
pub net_unrealized_pnl_rel_to_own_market_cap: PercentPerBlock<BasisPointsSigned32, M>,
#[traversable(wrap = "unrealized/profit", rename = "rel_to_own_mcap")]
pub unrealized_profit_rel_to_own_mcap: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "unrealized/loss", rename = "rel_to_own_mcap")]
pub unrealized_loss_rel_to_own_mcap: PercentPerBlock<BasisPoints32, M>,
#[traversable(wrap = "unrealized/net_pnl", rename = "rel_to_own_mcap")]
pub net_unrealized_pnl_rel_to_own_mcap: PercentPerBlock<BasisPointsSigned32, M>,
}
impl RelativeExtendedOwnMarketCap {
@@ -23,12 +23,12 @@ impl RelativeExtendedOwnMarketCap {
let v2 = Version::new(2);
Ok(Self {
unrealized_profit_rel_to_own_market_cap: cfg
.import("unrealized_profit_rel_to_own_market_cap", v2)?,
unrealized_loss_rel_to_own_market_cap: cfg
.import("unrealized_loss_rel_to_own_market_cap", Version::new(3))?,
net_unrealized_pnl_rel_to_own_market_cap: cfg
.import("net_unrealized_pnl_rel_to_own_market_cap", Version::new(3))?,
unrealized_profit_rel_to_own_mcap: cfg
.import("unrealized_profit_rel_to_own_mcap", v2)?,
unrealized_loss_rel_to_own_mcap: cfg
.import("unrealized_loss_rel_to_own_mcap", Version::new(3))?,
net_unrealized_pnl_rel_to_own_mcap: cfg
.import("net_unrealized_pnl_rel_to_own_mcap", Version::new(3))?,
})
}
@@ -39,21 +39,21 @@ impl RelativeExtendedOwnMarketCap {
own_market_cap: &impl ReadableVec<Height, Dollars>,
exit: &Exit,
) -> Result<()> {
self.unrealized_profit_rel_to_own_market_cap
self.unrealized_profit_rel_to_own_mcap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from,
&unrealized.profit.raw.usd.height,
own_market_cap,
exit,
)?;
self.unrealized_loss_rel_to_own_market_cap
self.unrealized_loss_rel_to_own_mcap
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
max_from,
&unrealized.loss.raw.usd.height,
own_market_cap,
exit,
)?;
self.net_unrealized_pnl_rel_to_own_market_cap
self.net_unrealized_pnl_rel_to_own_mcap
.compute_binary::<Dollars, Dollars, RatioDollarsBps32>(
max_from,
&unrealized.net_pnl.usd.height,

View File

@@ -11,15 +11,15 @@ use crate::{
/// Full relative metrics (sth/lth/all tier).
#[derive(Traversable)]
pub struct RelativeFull<M: StorageMode = Rw> {
#[traversable(wrap = "supply/in_profit", rename = "rel_to_own_supply")]
pub supply_in_profit_rel_to_own_supply: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "supply/in_loss", rename = "rel_to_own_supply")]
pub supply_in_loss_rel_to_own_supply: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "supply/in_profit", rename = "rel_to_own")]
pub supply_in_profit_rel_to_own: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "supply/in_loss", rename = "rel_to_own")]
pub supply_in_loss_rel_to_own: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "unrealized/profit", rename = "rel_to_market_cap")]
pub unrealized_profit_rel_to_market_cap: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "unrealized/loss", rename = "rel_to_market_cap")]
pub unrealized_loss_rel_to_market_cap: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "unrealized/profit", rename = "rel_to_mcap")]
pub unrealized_profit_rel_to_mcap: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "unrealized/loss", rename = "rel_to_mcap")]
pub unrealized_loss_rel_to_mcap: PercentPerBlock<BasisPoints16, M>,
}
impl RelativeFull {
@@ -28,13 +28,13 @@ impl RelativeFull {
let v2 = Version::new(2);
Ok(Self {
supply_in_profit_rel_to_own_supply: cfg
.import("supply_in_profit_rel_to_own_supply", v1)?,
supply_in_loss_rel_to_own_supply: cfg.import("supply_in_loss_rel_to_own_supply", v1)?,
unrealized_profit_rel_to_market_cap: cfg
.import("unrealized_profit_rel_to_market_cap", v2)?,
unrealized_loss_rel_to_market_cap: cfg
.import("unrealized_loss_rel_to_market_cap", v2)?,
supply_in_profit_rel_to_own: cfg
.import("supply_in_profit_rel_to_own", v1)?,
supply_in_loss_rel_to_own: cfg.import("supply_in_loss_rel_to_own", v1)?,
unrealized_profit_rel_to_mcap: cfg
.import("unrealized_profit_rel_to_mcap", v2)?,
unrealized_loss_rel_to_mcap: cfg
.import("unrealized_loss_rel_to_mcap", v2)?,
})
}
@@ -46,14 +46,14 @@ impl RelativeFull {
market_cap: &impl ReadableVec<Height, Dollars>,
exit: &Exit,
) -> Result<()> {
self.supply_in_profit_rel_to_own_supply
self.supply_in_profit_rel_to_own
.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from,
&supply.in_profit.sats.height,
&supply.total.sats.height,
exit,
)?;
self.supply_in_loss_rel_to_own_supply
self.supply_in_loss_rel_to_own
.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from,
&supply.in_loss.sats.height,
@@ -61,14 +61,14 @@ impl RelativeFull {
exit,
)?;
self.unrealized_profit_rel_to_market_cap
self.unrealized_profit_rel_to_mcap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from,
&unrealized.profit.raw.usd.height,
market_cap,
exit,
)?;
self.unrealized_loss_rel_to_market_cap
self.unrealized_loss_rel_to_mcap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from,
&unrealized.loss.raw.usd.height,

View File

@@ -11,22 +11,22 @@ use crate::distribution::metrics::{ImportConfig, SupplyCore};
#[derive(Traversable)]
pub struct RelativeToAll<M: StorageMode = Rw> {
#[traversable(wrap = "supply", rename = "rel_to_circulating")]
pub supply_rel_to_circulating_supply: PercentPerBlock<BasisPoints16, M>,
pub supply_rel_to_circulating: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "supply/in_profit", rename = "rel_to_circulating")]
pub supply_in_profit_rel_to_circulating_supply: PercentPerBlock<BasisPoints16, M>,
pub supply_in_profit_rel_to_circulating: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "supply/in_loss", rename = "rel_to_circulating")]
pub supply_in_loss_rel_to_circulating_supply: PercentPerBlock<BasisPoints16, M>,
pub supply_in_loss_rel_to_circulating: PercentPerBlock<BasisPoints16, M>,
}
impl RelativeToAll {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
Ok(Self {
supply_rel_to_circulating_supply: cfg
.import("supply_rel_to_circulating_supply", Version::ONE)?,
supply_in_profit_rel_to_circulating_supply: cfg
.import("supply_in_profit_rel_to_circulating_supply", Version::ONE)?,
supply_in_loss_rel_to_circulating_supply: cfg
.import("supply_in_loss_rel_to_circulating_supply", Version::ONE)?,
supply_rel_to_circulating: cfg
.import("supply_rel_to_circulating", Version::ONE)?,
supply_in_profit_rel_to_circulating: cfg
.import("supply_in_profit_rel_to_circulating", Version::ONE)?,
supply_in_loss_rel_to_circulating: cfg
.import("supply_in_loss_rel_to_circulating", Version::ONE)?,
})
}
@@ -37,21 +37,21 @@ impl RelativeToAll {
all_supply_sats: &impl ReadableVec<Height, Sats>,
exit: &Exit,
) -> Result<()> {
self.supply_rel_to_circulating_supply
self.supply_rel_to_circulating
.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from,
&supply.total.sats.height,
all_supply_sats,
exit,
)?;
self.supply_in_profit_rel_to_circulating_supply
self.supply_in_profit_rel_to_circulating
.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from,
&supply.in_profit.sats.height,
all_supply_sats,
exit,
)?;
self.supply_in_loss_rel_to_circulating_supply
self.supply_in_loss_rel_to_circulating
.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from,
&supply.in_loss.sats.height,

View File

@@ -16,23 +16,23 @@ use crate::distribution::metrics::ImportConfig;
#[derive(Traversable)]
pub struct SupplyBase<M: StorageMode = Rw> {
pub total: AmountPerBlock<M>,
pub halved: LazyAmountPerBlock,
pub half: LazyAmountPerBlock,
}
impl SupplyBase {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let supply = cfg.import("supply", Version::ZERO)?;
let supply_halved = LazyAmountPerBlock::from_block_source::<
let supply_half = LazyAmountPerBlock::from_block_source::<
HalveSats,
HalveSatsToBitcoin,
HalveCents,
HalveDollars,
>(&cfg.name("supply_halved"), &supply, cfg.version);
>(&cfg.name("supply_half"), &supply, cfg.version);
Ok(Self {
total: supply,
halved: supply_halved,
half: supply_half,
})
}

View File

@@ -0,0 +1,47 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, Cents, Height, Version};
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::internal::RatioPerBlock;
use crate::distribution::metrics::ImportConfig;
#[derive(Traversable)]
pub struct UnrealizedMinimal<M: StorageMode = Rw> {
pub nupl: RatioPerBlock<BasisPointsSigned32, M>,
}
impl UnrealizedMinimal {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
Ok(Self {
nupl: cfg.import("nupl", Version::ONE)?,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
spot_price: &impl ReadableVec<Height, Cents>,
realized_price: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
self.nupl.bps.height.compute_transform2(
max_from,
spot_price,
realized_price,
|(i, price, realized_price, ..)| {
let p = price.as_u128();
if p == 0 {
(i, BasisPointsSigned32::ZERO)
} else {
let rp = realized_price.as_u128();
let nupl_bps = ((p as i128 - rp as i128) * 10000) / p as i128;
(i, BasisPointsSigned32::from(nupl_bps as i32))
}
},
exit,
)?;
Ok(())
}
}

View File

@@ -11,21 +11,21 @@ use super::base::CohortState;
const COST_BASIS_PRICE_DIGITS: i32 = 4;
pub struct AddressCohortState<R: RealizedOps> {
pub addr_count: u64,
pub address_count: u64,
pub inner: CohortState<R, CostBasisRaw>,
}
impl<R: RealizedOps> AddressCohortState<R> {
pub(crate) fn new(path: &Path, name: &str) -> Self {
Self {
addr_count: 0,
address_count: 0,
inner: CohortState::new(path, name).with_price_rounding(COST_BASIS_PRICE_DIGITS),
}
}
/// Reset state for fresh start.
pub(crate) fn reset(&mut self) {
self.addr_count = 0;
self.address_count = 0;
self.inner.supply = SupplyState::default();
self.inner.sent = Sats::ZERO;
self.inner.satdays_destroyed = Sats::ZERO;
@@ -84,7 +84,7 @@ impl<R: RealizedOps> AddressCohortState<R> {
}
pub(crate) fn add(&mut self, addressdata: &FundedAddressData) {
self.addr_count += 1;
self.address_count += 1;
self.inner
.increment_snapshot(&addressdata.cost_basis_snapshot());
}
@@ -96,12 +96,12 @@ impl<R: RealizedOps> AddressCohortState<R> {
if unlikely(self.inner.supply.utxo_count < snapshot.supply_state.utxo_count) {
panic!(
"AddressCohortState::subtract underflow!\n\
Cohort state: addr_count={}, supply={}\n\
Cohort state: address_count={}, supply={}\n\
Address being subtracted: {}\n\
Address supply: {}\n\
Realized price: {}\n\
This means the address is not properly tracked in this cohort.",
self.addr_count,
self.address_count,
self.inner.supply,
addressdata,
snapshot.supply_state,
@@ -111,12 +111,12 @@ impl<R: RealizedOps> AddressCohortState<R> {
if unlikely(self.inner.supply.value < snapshot.supply_state.value) {
panic!(
"AddressCohortState::subtract value underflow!\n\
Cohort state: addr_count={}, supply={}\n\
Cohort state: address_count={}, supply={}\n\
Address being subtracted: {}\n\
Address supply: {}\n\
Realized price: {}\n\
This means the address is not properly tracked in this cohort.",
self.addr_count,
self.address_count,
self.inner.supply,
addressdata,
snapshot.supply_state,
@@ -124,9 +124,9 @@ impl<R: RealizedOps> AddressCohortState<R> {
);
}
self.addr_count = self.addr_count.checked_sub(1).unwrap_or_else(|| {
self.address_count = self.address_count.checked_sub(1).unwrap_or_else(|| {
panic!(
"AddressCohortState::subtract addr_count underflow! addr_count=0\n\
"AddressCohortState::subtract address_count underflow! address_count=0\n\
Address being subtracted: {}\n\
Realized price: {}",
addressdata, snapshot.realized_price

View File

@@ -30,7 +30,7 @@ use crate::{
use super::{
AddressCohorts, AddressesDataVecs, AnyAddressIndexesVecs, RangeMap, UTXOCohorts,
address::{
AddrCountsVecs, AddressActivityVecs, DeltaVecs, NewAddrCountVecs, TotalAddrCountVecs,
AddressCountsVecs, AddressActivityVecs, DeltaVecs, NewAddressCountVecs, TotalAddressCountVecs,
},
compute::aggregates,
};
@@ -39,11 +39,11 @@ const VERSION: Version = Version::new(22);
#[derive(Traversable)]
pub struct AddressMetricsVecs<M: StorageMode = Rw> {
pub funded: AddrCountsVecs<M>,
pub empty: AddrCountsVecs<M>,
pub funded: AddressCountsVecs<M>,
pub empty: AddressCountsVecs<M>,
pub activity: AddressActivityVecs<M>,
pub total: TotalAddrCountVecs<M>,
pub new: NewAddrCountVecs<M>,
pub total: TotalAddressCountVecs<M>,
pub new: NewAddressCountVecs<M>,
pub delta: DeltaVecs<M>,
#[traversable(wrap = "indexes", rename = "funded")]
pub funded_index:
@@ -135,19 +135,19 @@ impl Vecs {
|index, _| index,
);
let addr_count = AddrCountsVecs::forced_import(&db, "addr_count", version, indexes)?;
let empty_addr_count =
AddrCountsVecs::forced_import(&db, "empty_addr_count", version, indexes)?;
let address_count = AddressCountsVecs::forced_import(&db, "address_count", version, indexes)?;
let empty_address_count =
AddressCountsVecs::forced_import(&db, "empty_address_count", version, indexes)?;
let address_activity =
AddressActivityVecs::forced_import(&db, "address_activity", version, indexes)?;
// Stored total = addr_count + empty_addr_count (global + per-type, with all derived indexes)
let total_addr_count = TotalAddrCountVecs::forced_import(&db, version, indexes)?;
// Stored total = address_count + empty_address_count (global + per-type, with all derived indexes)
let total_address_count = TotalAddressCountVecs::forced_import(&db, version, indexes)?;
// Per-block delta of total (global + per-type)
let new_addr_count = NewAddrCountVecs::forced_import(&db, version, indexes)?;
let new_address_count = NewAddressCountVecs::forced_import(&db, version, indexes)?;
// Growth rate: new / addr_count (global + per-type)
// Growth rate: new / address_count (global + per-type)
let delta = DeltaVecs::forced_import(&db, version, indexes)?;
let this = Self {
@@ -157,11 +157,11 @@ impl Vecs {
)?,
addresses: AddressMetricsVecs {
funded: addr_count,
empty: empty_addr_count,
funded: address_count,
empty: empty_address_count,
activity: address_activity,
total: total_addr_count,
new: new_addr_count,
total: total_address_count,
new: new_address_count,
delta,
funded_index: funded_address_index,
empty_index: empty_address_index,
@@ -423,7 +423,7 @@ impl Vecs {
self.addresses.funded.compute_rest(starting_indexes, exit)?;
self.addresses.empty.compute_rest(starting_indexes, exit)?;
// 6c. Compute total_addr_count = addr_count + empty_addr_count
// 6c. Compute total_address_count = address_count + empty_address_count
self.addresses.total.compute(
starting_indexes.height,
&self.addresses.funded,

View File

@@ -0,0 +1,48 @@
use brk_types::{
Day1, Day3, Epoch, Halving, Height, Hour1, Hour4, Hour12, Minute10, Minute30, Month1, Month3,
Month6, Week1, Year1, Year10,
};
use vecdb::CachedVec;
use super::Vecs;
#[derive(Clone)]
pub struct CachedMappings {
pub minute10_first_height: CachedVec<Minute10, Height>,
pub minute30_first_height: CachedVec<Minute30, Height>,
pub hour1_first_height: CachedVec<Hour1, Height>,
pub hour4_first_height: CachedVec<Hour4, Height>,
pub hour12_first_height: CachedVec<Hour12, Height>,
pub day1_first_height: CachedVec<Day1, Height>,
pub day3_first_height: CachedVec<Day3, Height>,
pub week1_first_height: CachedVec<Week1, Height>,
pub month1_first_height: CachedVec<Month1, Height>,
pub month3_first_height: CachedVec<Month3, Height>,
pub month6_first_height: CachedVec<Month6, Height>,
pub year1_first_height: CachedVec<Year1, Height>,
pub year10_first_height: CachedVec<Year10, Height>,
pub halving_identity: CachedVec<Halving, Halving>,
pub epoch_identity: CachedVec<Epoch, Epoch>,
}
impl CachedMappings {
pub fn new(vecs: &Vecs) -> Self {
Self {
minute10_first_height: CachedVec::new(&vecs.minute10.first_height),
minute30_first_height: CachedVec::new(&vecs.minute30.first_height),
hour1_first_height: CachedVec::new(&vecs.hour1.first_height),
hour4_first_height: CachedVec::new(&vecs.hour4.first_height),
hour12_first_height: CachedVec::new(&vecs.hour12.first_height),
day1_first_height: CachedVec::new(&vecs.day1.first_height),
day3_first_height: CachedVec::new(&vecs.day3.first_height),
week1_first_height: CachedVec::new(&vecs.week1.first_height),
month1_first_height: CachedVec::new(&vecs.month1.first_height),
month3_first_height: CachedVec::new(&vecs.month3.first_height),
month6_first_height: CachedVec::new(&vecs.month6.first_height),
year1_first_height: CachedVec::new(&vecs.year1.first_height),
year10_first_height: CachedVec::new(&vecs.year10.first_height),
halving_identity: CachedVec::new(&vecs.halving.identity),
epoch_identity: CachedVec::new(&vecs.epoch.identity),
}
}
}

View File

@@ -1,4 +1,5 @@
mod address;
mod cached_mappings;
mod day1;
mod day3;
mod epoch;
@@ -28,13 +29,14 @@ use brk_types::{
Date, Day1, Day3, Height, Hour1, Hour4, Hour12, Indexes, Minute10, Minute30, Month1, Month3,
Month6, Version, Week1, Year1, Year10,
};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use vecdb::{CachedVec, Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
blocks,
internal::{finalize_db, open_db},
};
pub use cached_mappings::CachedMappings;
pub use address::Vecs as AddressVecs;
pub use day1::Vecs as Day1Vecs;
pub use day3::Vecs as Day3Vecs;
@@ -61,6 +63,8 @@ pub const DB_NAME: &str = "indexes";
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
db: Database,
#[traversable(skip)]
pub cached_mappings: CachedMappings,
pub address: AddressVecs,
pub height: HeightVecs<M>,
pub epoch: EpochVecs<M>,
@@ -93,27 +97,67 @@ impl Vecs {
let version = parent_version;
let address = AddressVecs::forced_import(version, indexer);
let height = HeightVecs::forced_import(&db, version)?;
let epoch = EpochVecs::forced_import(&db, version)?;
let halving = HalvingVecs::forced_import(&db, version)?;
let minute10 = Minute10Vecs::forced_import(&db, version)?;
let minute30 = Minute30Vecs::forced_import(&db, version)?;
let hour1 = Hour1Vecs::forced_import(&db, version)?;
let hour4 = Hour4Vecs::forced_import(&db, version)?;
let hour12 = Hour12Vecs::forced_import(&db, version)?;
let day1 = Day1Vecs::forced_import(&db, version)?;
let day3 = Day3Vecs::forced_import(&db, version)?;
let week1 = Week1Vecs::forced_import(&db, version)?;
let month1 = Month1Vecs::forced_import(&db, version)?;
let month3 = Month3Vecs::forced_import(&db, version)?;
let month6 = Month6Vecs::forced_import(&db, version)?;
let year1 = Year1Vecs::forced_import(&db, version)?;
let year10 = Year10Vecs::forced_import(&db, version)?;
let txindex = TxIndexVecs::forced_import(&db, version, indexer)?;
let txinindex = TxInIndexVecs::forced_import(version, indexer);
let txoutindex = TxOutIndexVecs::forced_import(version, indexer);
let cached_mappings = CachedMappings {
minute10_first_height: CachedVec::new(&minute10.first_height),
minute30_first_height: CachedVec::new(&minute30.first_height),
hour1_first_height: CachedVec::new(&hour1.first_height),
hour4_first_height: CachedVec::new(&hour4.first_height),
hour12_first_height: CachedVec::new(&hour12.first_height),
day1_first_height: CachedVec::new(&day1.first_height),
day3_first_height: CachedVec::new(&day3.first_height),
week1_first_height: CachedVec::new(&week1.first_height),
month1_first_height: CachedVec::new(&month1.first_height),
month3_first_height: CachedVec::new(&month3.first_height),
month6_first_height: CachedVec::new(&month6.first_height),
year1_first_height: CachedVec::new(&year1.first_height),
year10_first_height: CachedVec::new(&year10.first_height),
halving_identity: CachedVec::new(&halving.identity),
epoch_identity: CachedVec::new(&epoch.identity),
};
let this = Self {
address: AddressVecs::forced_import(version, indexer),
height: HeightVecs::forced_import(&db, version)?,
epoch: EpochVecs::forced_import(&db, version)?,
halving: HalvingVecs::forced_import(&db, version)?,
minute10: Minute10Vecs::forced_import(&db, version)?,
minute30: Minute30Vecs::forced_import(&db, version)?,
hour1: Hour1Vecs::forced_import(&db, version)?,
hour4: Hour4Vecs::forced_import(&db, version)?,
hour12: Hour12Vecs::forced_import(&db, version)?,
day1: Day1Vecs::forced_import(&db, version)?,
day3: Day3Vecs::forced_import(&db, version)?,
week1: Week1Vecs::forced_import(&db, version)?,
month1: Month1Vecs::forced_import(&db, version)?,
month3: Month3Vecs::forced_import(&db, version)?,
month6: Month6Vecs::forced_import(&db, version)?,
year1: Year1Vecs::forced_import(&db, version)?,
year10: Year10Vecs::forced_import(&db, version)?,
txindex: TxIndexVecs::forced_import(&db, version, indexer)?,
txinindex: TxInIndexVecs::forced_import(version, indexer),
txoutindex: TxOutIndexVecs::forced_import(version, indexer),
cached_mappings,
address,
height,
epoch,
halving,
minute10,
minute30,
hour1,
hour4,
hour12,
day1,
day3,
week1,
month1,
month3,
month6,
year1,
year10,
txindex,
txinindex,
txoutindex,
db,
};

View File

@@ -1,3 +1,5 @@
use std::marker::PhantomData;
use brk_traversable::Traversable;
use brk_types::{
Day1, Day3, Epoch, FromCoarserIndex, Halving, Height, Hour1, Hour4, Hour12, Minute10, Minute30,
@@ -6,7 +8,7 @@ use brk_types::{
use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use vecdb::{
Cursor, LazyAggVec, ReadOnlyClone, ReadableBoxedVec, ReadableCloneableVec, VecIndex, VecValue,
AggFold, Cursor, LazyAggVec, ReadOnlyClone, ReadableBoxedVec, ReadableVec, VecIndex, VecValue,
};
use crate::{
@@ -14,6 +16,53 @@ use crate::{
internal::{ComputedVecValue, NumericValue, PerResolution},
};
/// Aggregation strategy for epoch-based indices (Halving, Epoch).
///
/// Uses `FromCoarserIndex::max_from` to compute the target height for each
/// coarse index, rather than reading from the mapping. The mapping is only
/// used for its length.
pub struct CoarserIndex<I>(PhantomData<I>);
impl<I, O, S1I, S2T> AggFold<O, S1I, S2T, O> for CoarserIndex<I>
where
I: VecIndex,
O: VecValue,
S1I: VecIndex + FromCoarserIndex<I>,
S2T: VecValue,
{
#[inline]
fn try_fold<S: ReadableVec<S1I, O> + ?Sized, B, E, F: FnMut(B, O) -> Result<B, E>>(
source: &S,
mapping: &[S2T],
from: usize,
to: usize,
init: B,
mut f: F,
) -> Result<B, E> {
let mapping_len = mapping.len();
let source_len = source.len();
let mut cursor = Cursor::new(source);
let mut acc = init;
for i in from..to.min(mapping_len) {
let target = S1I::max_from(I::from(i), source_len);
if let Some(v) = cursor.get(target) {
acc = f(acc, v)?;
}
}
Ok(acc)
}
#[inline]
fn collect_one<S: ReadableVec<S1I, O> + ?Sized>(
source: &S,
_mapping: &[S2T],
index: usize,
) -> Option<O> {
let target = S1I::max_from(I::from(index), source.len());
source.collect_one_at(target)
}
}
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct Resolutions<T>(
@@ -32,8 +81,8 @@ pub struct Resolutions<T>(
LazyAggVec<Month6, Option<T>, Height, Height, T>,
LazyAggVec<Year1, Option<T>, Height, Height, T>,
LazyAggVec<Year10, Option<T>, Height, Height, T>,
LazyAggVec<Halving, T, Height, Halving>,
LazyAggVec<Epoch, T, Height, Epoch>,
LazyAggVec<Halving, T, Height, Halving, T, CoarserIndex<Halving>>,
LazyAggVec<Epoch, T, Height, Epoch, T, CoarserIndex<Epoch>>,
>,
)
where
@@ -59,71 +108,38 @@ where
version: Version,
indexes: &indexes::Vecs,
) -> Self {
macro_rules! period {
($idx:ident) => {
LazyAggVec::sparse_from_first_index(
name,
version,
height_source.clone(),
indexes.$idx.first_height.read_only_boxed_clone(),
)
};
}
let cm = &indexes.cached_mappings;
fn for_each_range<
I: VecIndex,
O: VecValue,
S1I: VecIndex + FromCoarserIndex<I>,
S2T: VecValue,
>(
from: usize,
to: usize,
source: &ReadableBoxedVec<S1I, O>,
mapping: &ReadableBoxedVec<I, S2T>,
f: &mut dyn FnMut(O),
) {
let mapping_len = mapping.len();
let source_len = source.len();
let mut cursor = Cursor::new(&**source);
for i in from..to {
if i >= mapping_len {
break;
}
let target = S1I::max_from(I::from(i), source_len);
if let Some(v) = cursor.get(target) {
f(v);
}
}
}
macro_rules! epoch {
($idx:ident) => {
macro_rules! res {
($cached:expr) => {{
let cached = $cached.clone();
let mapping_version = cached.version();
LazyAggVec::new(
name,
version,
mapping_version,
height_source.clone(),
indexes.$idx.identity.read_only_boxed_clone(),
for_each_range,
move || cached.get(),
)
};
}};
}
Self(PerResolution {
minute10: period!(minute10),
minute30: period!(minute30),
hour1: period!(hour1),
hour4: period!(hour4),
hour12: period!(hour12),
day1: period!(day1),
day3: period!(day3),
week1: period!(week1),
month1: period!(month1),
month3: period!(month3),
month6: period!(month6),
year1: period!(year1),
year10: period!(year10),
halving: epoch!(halving),
epoch: epoch!(epoch),
minute10: res!(cm.minute10_first_height),
minute30: res!(cm.minute30_first_height),
hour1: res!(cm.hour1_first_height),
hour4: res!(cm.hour4_first_height),
hour12: res!(cm.hour12_first_height),
day1: res!(cm.day1_first_height),
day3: res!(cm.day3_first_height),
week1: res!(cm.week1_first_height),
month1: res!(cm.month1_first_height),
month3: res!(cm.month3_first_height),
month6: res!(cm.month6_first_height),
year1: res!(cm.year1_first_height),
year10: res!(cm.year10_first_height),
halving: res!(cm.halving_identity),
epoch: res!(cm.epoch_identity),
})
}
}

View File

@@ -35,7 +35,7 @@ impl Price<ComputedPerBlock<Cents>> {
let cents =
ComputedPerBlock::forced_import(db, &format!("{name}_cents"), version, indexes)?;
let usd = LazyPerBlock::from_computed::<CentsUnsignedToDollars>(
&format!("{name}_usd"),
name,
version,
cents.height.read_only_boxed_clone(),
&cents,

View File

@@ -62,21 +62,21 @@ impl RatioPerBlockPercentiles {
}
macro_rules! import_band {
($suffix:expr) => {
($pct:expr) => {
RatioBand {
ratio: import_ratio!($suffix),
price: import_price!($suffix),
ratio: import_ratio!(concat!("ratio_", $pct)),
price: import_price!($pct),
}
};
}
Ok(Self {
pct99: import_band!("ratio_pct99"),
pct98: import_band!("ratio_pct98"),
pct95: import_band!("ratio_pct95"),
pct5: import_band!("ratio_pct5"),
pct2: import_band!("ratio_pct2"),
pct1: import_band!("ratio_pct1"),
pct99: import_band!("pct99"),
pct98: import_band!("pct98"),
pct95: import_band!("pct95"),
pct5: import_band!("pct5"),
pct2: import_band!("pct2"),
pct1: import_band!("pct1"),
expanding_pct: ExpandingPercentiles::default(),
})
}

View File

@@ -71,7 +71,7 @@ impl StdDevPerBlockExtended {
macro_rules! import_band {
($suffix:expr) => {
StdDevBand {
value: import!($suffix),
value: import!(concat!("ratio_", $suffix)),
price: import_price!($suffix),
}
};