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
File diff suppressed because it is too large Load Diff
+24 -24
View File
@@ -39,7 +39,7 @@ pub const AGE_BOUNDARIES: [usize; 20] = [
/// Age range bounds (end = usize::MAX means unbounded)
pub const AGE_RANGE_BOUNDS: AgeRange<Range<usize>> = AgeRange {
up_to_1h: 0..HOURS_1H,
under_1h: 0..HOURS_1H,
_1h_to_1d: HOURS_1H..HOURS_1D,
_1d_to_1w: HOURS_1D..HOURS_1W,
_1w_to_1m: HOURS_1W..HOURS_1M,
@@ -59,12 +59,12 @@ pub const AGE_RANGE_BOUNDS: AgeRange<Range<usize>> = AgeRange {
_8y_to_10y: HOURS_8Y..HOURS_10Y,
_10y_to_12y: HOURS_10Y..HOURS_12Y,
_12y_to_15y: HOURS_12Y..HOURS_15Y,
from_15y: HOURS_15Y..usize::MAX,
over_15y: HOURS_15Y..usize::MAX,
};
/// Age range filters
pub const AGE_RANGE_FILTERS: AgeRange<Filter> = AgeRange {
up_to_1h: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS.up_to_1h)),
under_1h: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS.under_1h)),
_1h_to_1d: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._1h_to_1d)),
_1d_to_1w: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._1d_to_1w)),
_1w_to_1m: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._1w_to_1m)),
@@ -84,12 +84,12 @@ pub const AGE_RANGE_FILTERS: AgeRange<Filter> = AgeRange {
_8y_to_10y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._8y_to_10y)),
_10y_to_12y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._10y_to_12y)),
_12y_to_15y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._12y_to_15y)),
from_15y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS.from_15y)),
over_15y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS.over_15y)),
};
/// Age range names
pub const AGE_RANGE_NAMES: AgeRange<CohortName> = AgeRange {
up_to_1h: CohortName::new("under_1h_old", "<1h", "Under 1 Hour Old"),
under_1h: CohortName::new("under_1h_old", "<1h", "Under 1 Hour Old"),
_1h_to_1d: CohortName::new("1h_to_1d_old", "1h-1d", "1 Hour to 1 Day Old"),
_1d_to_1w: CohortName::new("1d_to_1w_old", "1d-1w", "1 Day to 1 Week Old"),
_1w_to_1m: CohortName::new("1w_to_1m_old", "1w-1m", "1 Week to 1 Month Old"),
@@ -109,7 +109,7 @@ pub const AGE_RANGE_NAMES: AgeRange<CohortName> = AgeRange {
_8y_to_10y: CohortName::new("8y_to_10y_old", "8y-10y", "8 to 10 Years Old"),
_10y_to_12y: CohortName::new("10y_to_12y_old", "10y-12y", "10 to 12 Years Old"),
_12y_to_15y: CohortName::new("12y_to_15y_old", "12y-15y", "12 to 15 Years Old"),
from_15y: CohortName::new("over_15y_old", "15y+", "15+ Years Old"),
over_15y: CohortName::new("over_15y_old", "15y+", "15+ Years Old"),
};
impl AgeRange<CohortName> {
@@ -120,7 +120,7 @@ impl AgeRange<CohortName> {
#[derive(Default, Clone, Traversable, Serialize)]
pub struct AgeRange<T> {
pub up_to_1h: T,
pub under_1h: T,
pub _1h_to_1d: T,
pub _1d_to_1w: T,
pub _1w_to_1m: T,
@@ -140,7 +140,7 @@ pub struct AgeRange<T> {
pub _8y_to_10y: T,
pub _10y_to_12y: T,
pub _12y_to_15y: T,
pub from_15y: T,
pub over_15y: T,
}
impl<T> AgeRange<T> {
@@ -148,7 +148,7 @@ impl<T> AgeRange<T> {
#[inline]
pub fn get_mut(&mut self, age: Age) -> &mut T {
match age.hours() {
0..HOURS_1H => &mut self.up_to_1h,
0..HOURS_1H => &mut self.under_1h,
HOURS_1H..HOURS_1D => &mut self._1h_to_1d,
HOURS_1D..HOURS_1W => &mut self._1d_to_1w,
HOURS_1W..HOURS_1M => &mut self._1w_to_1m,
@@ -168,7 +168,7 @@ impl<T> AgeRange<T> {
HOURS_8Y..HOURS_10Y => &mut self._8y_to_10y,
HOURS_10Y..HOURS_12Y => &mut self._10y_to_12y,
HOURS_12Y..HOURS_15Y => &mut self._12y_to_15y,
_ => &mut self.from_15y,
_ => &mut self.over_15y,
}
}
@@ -176,7 +176,7 @@ impl<T> AgeRange<T> {
#[inline]
pub fn get(&self, age: Age) -> &T {
match age.hours() {
0..HOURS_1H => &self.up_to_1h,
0..HOURS_1H => &self.under_1h,
HOURS_1H..HOURS_1D => &self._1h_to_1d,
HOURS_1D..HOURS_1W => &self._1d_to_1w,
HOURS_1W..HOURS_1M => &self._1w_to_1m,
@@ -196,14 +196,14 @@ impl<T> AgeRange<T> {
HOURS_8Y..HOURS_10Y => &self._8y_to_10y,
HOURS_10Y..HOURS_12Y => &self._10y_to_12y,
HOURS_12Y..HOURS_15Y => &self._12y_to_15y,
_ => &self.from_15y,
_ => &self.over_15y,
}
}
pub fn from_array(arr: [T; 21]) -> Self {
let [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20] = arr;
Self {
up_to_1h: a0,
under_1h: a0,
_1h_to_1d: a1,
_1d_to_1w: a2,
_1w_to_1m: a3,
@@ -223,7 +223,7 @@ impl<T> AgeRange<T> {
_8y_to_10y: a17,
_10y_to_12y: a18,
_12y_to_15y: a19,
from_15y: a20,
over_15y: a20,
}
}
@@ -234,7 +234,7 @@ impl<T> AgeRange<T> {
let f = AGE_RANGE_FILTERS;
let n = AGE_RANGE_NAMES;
Self {
up_to_1h: create(f.up_to_1h.clone(), n.up_to_1h.id),
under_1h: create(f.under_1h.clone(), n.under_1h.id),
_1h_to_1d: create(f._1h_to_1d.clone(), n._1h_to_1d.id),
_1d_to_1w: create(f._1d_to_1w.clone(), n._1d_to_1w.id),
_1w_to_1m: create(f._1w_to_1m.clone(), n._1w_to_1m.id),
@@ -254,7 +254,7 @@ impl<T> AgeRange<T> {
_8y_to_10y: create(f._8y_to_10y.clone(), n._8y_to_10y.id),
_10y_to_12y: create(f._10y_to_12y.clone(), n._10y_to_12y.id),
_12y_to_15y: create(f._12y_to_15y.clone(), n._12y_to_15y.id),
from_15y: create(f.from_15y.clone(), n.from_15y.id),
over_15y: create(f.over_15y.clone(), n.over_15y.id),
}
}
@@ -265,7 +265,7 @@ impl<T> AgeRange<T> {
let f = AGE_RANGE_FILTERS;
let n = AGE_RANGE_NAMES;
Ok(Self {
up_to_1h: create(f.up_to_1h.clone(), n.up_to_1h.id)?,
under_1h: create(f.under_1h.clone(), n.under_1h.id)?,
_1h_to_1d: create(f._1h_to_1d.clone(), n._1h_to_1d.id)?,
_1d_to_1w: create(f._1d_to_1w.clone(), n._1d_to_1w.id)?,
_1w_to_1m: create(f._1w_to_1m.clone(), n._1w_to_1m.id)?,
@@ -285,13 +285,13 @@ impl<T> AgeRange<T> {
_8y_to_10y: create(f._8y_to_10y.clone(), n._8y_to_10y.id)?,
_10y_to_12y: create(f._10y_to_12y.clone(), n._10y_to_12y.id)?,
_12y_to_15y: create(f._12y_to_15y.clone(), n._12y_to_15y.id)?,
from_15y: create(f.from_15y.clone(), n.from_15y.id)?,
over_15y: create(f.over_15y.clone(), n.over_15y.id)?,
})
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
[
&self.up_to_1h,
&self.under_1h,
&self._1h_to_1d,
&self._1d_to_1w,
&self._1w_to_1m,
@@ -311,14 +311,14 @@ impl<T> AgeRange<T> {
&self._8y_to_10y,
&self._10y_to_12y,
&self._12y_to_15y,
&self.from_15y,
&self.over_15y,
]
.into_iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
[
&mut self.up_to_1h,
&mut self.under_1h,
&mut self._1h_to_1d,
&mut self._1d_to_1w,
&mut self._1w_to_1m,
@@ -338,7 +338,7 @@ impl<T> AgeRange<T> {
&mut self._8y_to_10y,
&mut self._10y_to_12y,
&mut self._12y_to_15y,
&mut self.from_15y,
&mut self.over_15y,
]
.into_iter()
}
@@ -348,7 +348,7 @@ impl<T> AgeRange<T> {
T: Send + Sync,
{
[
&mut self.up_to_1h,
&mut self.under_1h,
&mut self._1h_to_1d,
&mut self._1d_to_1w,
&mut self._1w_to_1m,
@@ -368,7 +368,7 @@ impl<T> AgeRange<T> {
&mut self._8y_to_10y,
&mut self._10y_to_12y,
&mut self._12y_to_15y,
&mut self.from_15y,
&mut self.over_15y,
]
.into_par_iter()
}
+28 -52
View File
@@ -74,50 +74,26 @@ pub const AMOUNT_RANGE_BOUNDS: AmountRange<Range<Sats>> = AmountRange {
_100btc_to_1k_btc: Sats::_100BTC..Sats::_1K_BTC,
_1k_btc_to_10k_btc: Sats::_1K_BTC..Sats::_10K_BTC,
_10k_btc_to_100k_btc: Sats::_10K_BTC..Sats::_100K_BTC,
_100k_btc_or_more: Sats::_100K_BTC..Sats::MAX,
over_100k_btc: Sats::_100K_BTC..Sats::MAX,
};
/// Amount range names
pub const AMOUNT_RANGE_NAMES: AmountRange<CohortName> = AmountRange {
_0sats: CohortName::new("with_0sats", "0 sats", "0 Sats"),
_1sat_to_10sats: CohortName::new("above_1sat_under_10sats", "1-10 sats", "1-10 Sats"),
_10sats_to_100sats: CohortName::new("above_10sats_under_100sats", "10-100 sats", "10-100 Sats"),
_100sats_to_1k_sats: CohortName::new(
"above_100sats_under_1k_sats",
"100-1k sats",
"100-1K Sats",
),
_1k_sats_to_10k_sats: CohortName::new(
"above_1k_sats_under_10k_sats",
"1k-10k sats",
"1K-10K Sats",
),
_10k_sats_to_100k_sats: CohortName::new(
"above_10k_sats_under_100k_sats",
"10k-100k sats",
"10K-100K Sats",
),
_100k_sats_to_1m_sats: CohortName::new(
"above_100k_sats_under_1m_sats",
"100k-1M sats",
"100K-1M Sats",
),
_1m_sats_to_10m_sats: CohortName::new(
"above_1m_sats_under_10m_sats",
"1M-10M sats",
"1M-10M Sats",
),
_10m_sats_to_1btc: CohortName::new("above_10m_sats_under_1btc", "0.1-1 BTC", "0.1-1 BTC"),
_1btc_to_10btc: CohortName::new("above_1btc_under_10btc", "1-10 BTC", "1-10 BTC"),
_10btc_to_100btc: CohortName::new("above_10btc_under_100btc", "10-100 BTC", "10-100 BTC"),
_100btc_to_1k_btc: CohortName::new("above_100btc_under_1k_btc", "100-1k BTC", "100-1K BTC"),
_1k_btc_to_10k_btc: CohortName::new("above_1k_btc_under_10k_btc", "1k-10k BTC", "1K-10K BTC"),
_10k_btc_to_100k_btc: CohortName::new(
"above_10k_btc_under_100k_btc",
"10k-100k BTC",
"10K-100K BTC",
),
_100k_btc_or_more: CohortName::new("above_100k_btc", "100k+ BTC", "100K+ BTC"),
_0sats: CohortName::new("0sats", "0 sats", "0 Sats"),
_1sat_to_10sats: CohortName::new("1sat_to_10sats", "1-10 sats", "1-10 Sats"),
_10sats_to_100sats: CohortName::new("10sats_to_100sats", "10-100 sats", "10-100 Sats"),
_100sats_to_1k_sats: CohortName::new("100sats_to_1k_sats", "100-1k sats", "100-1K Sats"),
_1k_sats_to_10k_sats: CohortName::new("1k_sats_to_10k_sats", "1k-10k sats", "1K-10K Sats"),
_10k_sats_to_100k_sats: CohortName::new("10k_sats_to_100k_sats", "10k-100k sats", "10K-100K Sats"),
_100k_sats_to_1m_sats: CohortName::new("100k_sats_to_1m_sats", "100k-1M sats", "100K-1M Sats"),
_1m_sats_to_10m_sats: CohortName::new("1m_sats_to_10m_sats", "1M-10M sats", "1M-10M Sats"),
_10m_sats_to_1btc: CohortName::new("10m_sats_to_1btc", "0.1-1 BTC", "0.1-1 BTC"),
_1btc_to_10btc: CohortName::new("1btc_to_10btc", "1-10 BTC", "1-10 BTC"),
_10btc_to_100btc: CohortName::new("10btc_to_100btc", "10-100 BTC", "10-100 BTC"),
_100btc_to_1k_btc: CohortName::new("100btc_to_1k_btc", "100-1k BTC", "100-1K BTC"),
_1k_btc_to_10k_btc: CohortName::new("1k_btc_to_10k_btc", "1k-10k BTC", "1K-10K BTC"),
_10k_btc_to_100k_btc: CohortName::new("10k_btc_to_100k_btc", "10k-100k BTC", "10K-100K BTC"),
over_100k_btc: CohortName::new("over_100k_btc", "100k+ BTC", "100K+ BTC"),
};
/// Amount range filters
@@ -148,7 +124,7 @@ pub const AMOUNT_RANGE_FILTERS: AmountRange<Filter> = AmountRange {
_10k_btc_to_100k_btc: Filter::Amount(AmountFilter::Range(
AMOUNT_RANGE_BOUNDS._10k_btc_to_100k_btc,
)),
_100k_btc_or_more: Filter::Amount(AmountFilter::Range(AMOUNT_RANGE_BOUNDS._100k_btc_or_more)),
over_100k_btc: Filter::Amount(AmountFilter::Range(AMOUNT_RANGE_BOUNDS.over_100k_btc)),
};
#[derive(Debug, Default, Clone, Traversable, Serialize)]
@@ -167,7 +143,7 @@ pub struct AmountRange<T> {
pub _100btc_to_1k_btc: T,
pub _1k_btc_to_10k_btc: T,
pub _10k_btc_to_100k_btc: T,
pub _100k_btc_or_more: T,
pub over_100k_btc: T,
}
impl AmountRange<CohortName> {
@@ -204,7 +180,7 @@ impl<T> AmountRange<T> {
_100btc_to_1k_btc: create(f._100btc_to_1k_btc.clone(), n._100btc_to_1k_btc.id),
_1k_btc_to_10k_btc: create(f._1k_btc_to_10k_btc.clone(), n._1k_btc_to_10k_btc.id),
_10k_btc_to_100k_btc: create(f._10k_btc_to_100k_btc.clone(), n._10k_btc_to_100k_btc.id),
_100k_btc_or_more: create(f._100k_btc_or_more.clone(), n._100k_btc_or_more.id),
over_100k_btc: create(f.over_100k_btc.clone(), n.over_100k_btc.id),
}
}
@@ -244,7 +220,7 @@ impl<T> AmountRange<T> {
f._10k_btc_to_100k_btc.clone(),
n._10k_btc_to_100k_btc.id,
)?,
_100k_btc_or_more: create(f._100k_btc_or_more.clone(), n._100k_btc_or_more.id)?,
over_100k_btc: create(f.over_100k_btc.clone(), n.over_100k_btc.id)?,
})
}
@@ -265,7 +241,7 @@ impl<T> AmountRange<T> {
11 => &self._100btc_to_1k_btc,
12 => &self._1k_btc_to_10k_btc,
13 => &self._10k_btc_to_100k_btc,
_ => &self._100k_btc_or_more,
_ => &self.over_100k_btc,
}
}
@@ -293,7 +269,7 @@ impl<T> AmountRange<T> {
11 => &mut self._100btc_to_1k_btc,
12 => &mut self._1k_btc_to_10k_btc,
13 => &mut self._10k_btc_to_100k_btc,
_ => &mut self._100k_btc_or_more,
_ => &mut self.over_100k_btc,
}
}
@@ -313,7 +289,7 @@ impl<T> AmountRange<T> {
&self._100btc_to_1k_btc,
&self._1k_btc_to_10k_btc,
&self._10k_btc_to_100k_btc,
&self._100k_btc_or_more,
&self.over_100k_btc,
]
.into_iter()
}
@@ -334,7 +310,7 @@ impl<T> AmountRange<T> {
(Sats::_100BTC, &self._100btc_to_1k_btc),
(Sats::_1K_BTC, &self._1k_btc_to_10k_btc),
(Sats::_10K_BTC, &self._10k_btc_to_100k_btc),
(Sats::_100K_BTC, &self._100k_btc_or_more),
(Sats::_100K_BTC, &self.over_100k_btc),
]
.into_iter()
}
@@ -355,7 +331,7 @@ impl<T> AmountRange<T> {
&mut self._100btc_to_1k_btc,
&mut self._1k_btc_to_10k_btc,
&mut self._10k_btc_to_100k_btc,
&mut self._100k_btc_or_more,
&mut self.over_100k_btc,
]
.into_iter()
}
@@ -379,7 +355,7 @@ impl<T> AmountRange<T> {
&mut self._100btc_to_1k_btc,
&mut self._1k_btc_to_10k_btc,
&mut self._10k_btc_to_100k_btc,
&mut self._100k_btc_or_more,
&mut self.over_100k_btc,
]
.into_par_iter()
}
@@ -406,7 +382,7 @@ where
_100btc_to_1k_btc: self._100btc_to_1k_btc + rhs._100btc_to_1k_btc,
_1k_btc_to_10k_btc: self._1k_btc_to_10k_btc + rhs._1k_btc_to_10k_btc,
_10k_btc_to_100k_btc: self._10k_btc_to_100k_btc + rhs._10k_btc_to_100k_btc,
_100k_btc_or_more: self._100k_btc_or_more + rhs._100k_btc_or_more,
over_100k_btc: self.over_100k_btc + rhs.over_100k_btc,
}
}
}
@@ -430,6 +406,6 @@ where
self._100btc_to_1k_btc += rhs._100btc_to_1k_btc;
self._1k_btc_to_10k_btc += rhs._1k_btc_to_10k_btc;
self._10k_btc_to_100k_btc += rhs._10k_btc_to_100k_btc;
self._100k_btc_or_more += rhs._100k_btc_or_more;
self.over_100k_btc += rhs.over_100k_btc;
}
}
-1
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 }
@@ -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(())
}
@@ -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)?;
}
@@ -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};
@@ -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)?)
@@ -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
@@ -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;
@@ -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
@@ -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,
)
})?;
@@ -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,
@@ -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![
@@ -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()
@@ -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],
@@ -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)?;
@@ -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,
)?;
@@ -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,
)?;
@@ -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,
)?;
@@ -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,
@@ -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,
@@ -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,
@@ -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,
@@ -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,
})
}
@@ -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(())
}
}
@@ -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
+17 -17
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,
@@ -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),
}
}
}
+65 -21
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,
};
@@ -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),
})
}
}
@@ -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,
@@ -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(),
})
}
@@ -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),
}
};
+1 -1
View File
@@ -31,7 +31,7 @@ derive_more = { workspace = true }
vecdb = { workspace = true }
zstd = "0.13"
jiff = { workspace = true }
quick_cache = "0.6.18"
quick_cache = "0.6.19"
schemars = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
+5 -4
View File
@@ -8,9 +8,9 @@ pub use brk_traversable_derive::Traversable;
use schemars::JsonSchema;
use serde::Serialize;
use vecdb::{
AnyExportableVec, AnyVec, BytesVec, BytesVecValue, CompressionStrategy, EagerVec, Formattable,
LazyAggVec, LazyVecFrom1, LazyVecFrom2, LazyVecFrom3, RawStrategy, ReadOnlyCompressedVec,
ReadOnlyRawVec, StoredVec, VecIndex, VecValue,
AggFold, AnyExportableVec, AnyVec, BytesVec, BytesVecValue, CompressionStrategy, EagerVec,
Formattable, LazyAggVec, LazyVecFrom1, LazyVecFrom2, LazyVecFrom3, RawStrategy,
ReadOnlyCompressedVec, ReadOnlyRawVec, StoredVec, VecIndex, VecValue,
};
pub trait Traversable {
@@ -217,13 +217,14 @@ where
}
}
impl<I, O, S1I, S2T, S1T> Traversable for LazyAggVec<I, O, S1I, S2T, S1T>
impl<I, O, S1I, S2T, S1T, Strat> Traversable for LazyAggVec<I, O, S1I, S2T, S1T, Strat>
where
I: VecIndex,
O: VecValue + Formattable + Serialize + JsonSchema,
S1I: VecIndex,
S2T: VecValue,
S1T: VecValue,
Strat: AggFold<O, S1I, S2T, S1T>,
{
fn iter_any_exportable(&self) -> impl Iterator<Item = &dyn AnyExportableVec> {
std::iter::once(self as &dyn AnyExportableVec)
+1 -1
View File
@@ -298,7 +298,7 @@ impl<'de> Deserialize<'de> for Index {
D: serde::Deserializer<'de>,
{
let str = String::deserialize(deserializer)?;
Index::try_from(str.as_str()).map_err(|e| serde::de::Error::custom(e))
Index::try_from(str.as_str()).map_err(serde::de::Error::custom)
}
}
+50 -35
View File
@@ -46,32 +46,47 @@ pub struct MetricLeafWithSchema {
pub schema: serde_json::Value,
}
/// Extract JSON type from a schema, following $ref if needed.
/// Extract JSON type from a root schema, following $ref and composition keywords.
pub fn extract_json_type(schema: &serde_json::Value) -> String {
extract_json_type_inner(schema, schema)
}
fn extract_json_type_inner(node: &serde_json::Value, root: &serde_json::Value) -> String {
// Direct type field
if let Some(t) = schema.get("type").and_then(|v| v.as_str()) {
if let Some(t) = node.get("type").and_then(|v| v.as_str()) {
return t.to_string();
}
// Handle $ref - look up in definitions
if let Some(ref_path) = schema.get("$ref").and_then(|v| v.as_str())
// Handle $ref - resolve against root definitions
if let Some(ref_path) = node.get("$ref").and_then(|v| v.as_str())
&& let Some(def_name) = ref_path.rsplit('/').next()
{
// Check both "$defs" (draft 2020-12) and "definitions" (older drafts)
for defs_key in &["$defs", "definitions"] {
if let Some(defs) = schema.get(defs_key)
if let Some(defs) = root.get(defs_key)
&& let Some(def) = defs.get(def_name)
{
return extract_json_type(def);
return extract_json_type_inner(def, root);
}
}
}
// Handle allOf with single element
if let Some(all_of) = schema.get("allOf").and_then(|v| v.as_array())
if let Some(all_of) = node.get("allOf").and_then(|v| v.as_array())
&& all_of.len() == 1
{
return extract_json_type(&all_of[0]);
return extract_json_type_inner(&all_of[0], root);
}
// Handle anyOf/oneOf (e.g. Option<T> generates {"anyOf": [{"type":"null"}, ...]})
for key in &["anyOf", "oneOf"] {
if let Some(variants) = node.get(key).and_then(|v| v.as_array()) {
for variant in variants {
let t = extract_json_type_inner(variant, root);
if t != "null" {
return t;
}
}
}
}
"object".to_string()
@@ -136,7 +151,7 @@ pub enum TreeNode {
Leaf(MetricLeafWithSchema),
}
const BASE: &str = "base";
const BASE: &str = "raw";
impl TreeNode {
pub fn is_empty(&self) -> bool {
@@ -708,13 +723,13 @@ mod tests {
#[test]
fn case3_computed_block_sum() {
// ComputedBlockSum:
// - height: wrap="base" → Branch { "base": Leaf(height) }
// - height: wrap="raw" → Branch { "raw": Leaf(height) }
// - rest (flatten): DerivedComputedBlockSum → branches with "sum" children
let tree = branch(vec![
// height wrapped as "base"
// height wrapped as "raw"
(
"height",
branch(vec![("base", leaf("metric", Index::Height))]),
branch(vec![("raw", leaf("metric", Index::Height))]),
),
// rest (flattened) produces branches
(
@@ -729,7 +744,7 @@ mod tests {
let merged = tree.merge_branches().unwrap();
// DESIRED: { "base": Leaf(height), "sum": Leaf(day1, week1) }
// DESIRED: { "raw": Leaf(height), "sum": Leaf(day1, week1) }
match &merged {
TreeNode::Branch(map) => {
assert_eq!(
@@ -740,7 +755,7 @@ mod tests {
);
// base should have Height only
let base_indexes = get_leaf_indexes(map.get("base").unwrap()).unwrap();
let base_indexes = get_leaf_indexes(map.get("raw").unwrap()).unwrap();
assert!(base_indexes.contains(&Index::Height));
assert_eq!(base_indexes.len(), 1);
@@ -759,13 +774,13 @@ mod tests {
#[test]
fn case4_computed_block_last() {
// ComputedBlockLast:
// - height: wrap="base" → Branch { "base": Leaf(height) }
// - height: wrap="raw" → Branch { "raw": Leaf(height) }
// - rest (flatten): DerivedComputedBlockLast → branches with "last" children
let tree = branch(vec![
// height wrapped as "base"
// height wrapped as "raw"
(
"height",
branch(vec![("base", leaf("metric", Index::Height))]),
branch(vec![("raw", leaf("metric", Index::Height))]),
),
// rest (flattened) produces branches with "last" key
(
@@ -780,7 +795,7 @@ mod tests {
let merged = tree.merge_branches().unwrap();
// DESIRED: { "base": Leaf(height), "last": Leaf(day1, week1) }
// DESIRED: { "raw": Leaf(height), "last": Leaf(day1, week1) }
match &merged {
TreeNode::Branch(map) => {
assert_eq!(
@@ -791,7 +806,7 @@ mod tests {
);
// base should have Height only
let base_indexes = get_leaf_indexes(map.get("base").unwrap()).unwrap();
let base_indexes = get_leaf_indexes(map.get("raw").unwrap()).unwrap();
assert!(base_indexes.contains(&Index::Height));
assert_eq!(base_indexes.len(), 1);
@@ -810,17 +825,17 @@ mod tests {
#[test]
fn case5_computed_block_full() {
// ComputedBlockFull has:
// - height: wrapped as "base" (raw values, not aggregated)
// - height: wrapped as "raw" (raw values, not aggregated)
// - rest (flatten): DerivedComputedBlockFull {
// height_cumulative: CumulativeVec → Branch{"cumulative": Leaf}
// day1: Full → Branch{avg, min, max, sum, cumulative}
// dates (flatten): more aggregation branches
// }
let tree = branch(vec![
// height wrapped as "base" (raw values at height granularity)
// height wrapped as "raw" (raw values at height granularity)
(
"height",
branch(vec![("base", leaf("metric", Index::Height))]),
branch(vec![("raw", leaf("metric", Index::Height))]),
),
// height_cumulative wrapped as cumulative
(
@@ -867,7 +882,7 @@ mod tests {
);
// base should have Height only
let base_indexes = get_leaf_indexes(map.get("base").unwrap()).unwrap();
let base_indexes = get_leaf_indexes(map.get("raw").unwrap()).unwrap();
assert!(base_indexes.contains(&Index::Height));
assert_eq!(base_indexes.len(), 1);
@@ -991,7 +1006,7 @@ mod tests {
// ========== Case 8: BinaryBlockSumCum ==========
// After derive applies all inner merges and flatten, before parent merge:
// - height wrapped as "base" → { base: Leaf(Height) }
// - height wrapped as "raw" → { base: Leaf(Height) }
// - height_cumulative wrapped as "cumulative" → { cumulative: Leaf(Height) }
// - rest (flatten): children from already-merged inner struct inserted directly
//
@@ -1002,10 +1017,10 @@ mod tests {
fn case8_binary_block_sum_cum() {
// Tree AFTER derive applies inner merges, flatten lifts rest's children:
let tree = branch(vec![
// height with wrap="base"
// height with wrap="raw"
(
"height",
branch(vec![("base", leaf("metric", Index::Height))]),
branch(vec![("raw", leaf("metric", Index::Height))]),
),
// height_cumulative with wrap="cumulative"
(
@@ -1040,7 +1055,7 @@ mod tests {
);
// base: only Height
let base_indexes = get_leaf_indexes(map.get("base").unwrap()).unwrap();
let base_indexes = get_leaf_indexes(map.get("raw").unwrap()).unwrap();
assert_eq!(base_indexes.len(), 1);
assert!(base_indexes.contains(&Index::Height));
@@ -1067,19 +1082,19 @@ mod tests {
// Each denomination has already been merged internally
// Simulating the output after inner merge
let sats_merged = branch(vec![
("base", leaf("metric", Index::Height)),
("raw", leaf("metric", Index::Height)),
("sum", leaf("metric_sum", Index::Day1)),
("cumulative", leaf("metric_cumulative", Index::Height)),
]);
let bitcoin_merged = branch(vec![
("base", leaf("metric_btc", Index::Height)),
("raw", leaf("metric_btc", Index::Height)),
("sum", leaf("metric_btc_sum", Index::Day1)),
("cumulative", leaf("metric_btc_cumulative", Index::Height)),
]);
let dollars_merged = branch(vec![
("base", leaf("metric_usd", Index::Height)),
("raw", leaf("metric_usd", Index::Height)),
("sum", leaf("metric_usd_sum", Index::Day1)),
("cumulative", leaf("metric_usd_cumulative", Index::Height)),
]);
@@ -1099,7 +1114,7 @@ mod tests {
match map.get(denom) {
Some(TreeNode::Branch(inner)) => {
assert_eq!(inner.len(), 3);
assert!(inner.contains_key("base"));
assert!(inner.contains_key("raw"));
assert!(inner.contains_key("sum"));
assert!(inner.contains_key("cumulative"));
}
@@ -1156,14 +1171,14 @@ mod tests {
#[test]
fn case10_computed_date_last_collapses_to_leaf() {
// ComputedDateLast<T> with merge:
// - day1 with wrap="base" → { base: Leaf }
// - day1 with wrap="raw" → { base: Leaf }
// - rest (flatten): DerivedDateLast already merged to Leaf
// → flatten inserts with field name "rest" as key
//
// Both have same metric name → collapses to single Leaf
let tree = branch(vec![
// day1 with wrap="base"
("day1", branch(vec![("base", leaf("metric", Index::Day1))])),
// day1 with wrap="raw"
("day1", branch(vec![("raw", leaf("metric", Index::Day1))])),
// rest (flatten): DerivedDateLast merged to Leaf
// Same metric name as base
("rest", leaf("metric", Index::Week1)),