mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-29 11:48:12 -07:00
global: big snapshot
This commit is contained in:
@@ -1,22 +1,18 @@
|
||||
use brk_cohort::ByAddrType;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{BasisPointsSigned32, StoredI64, StoredU64, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{LazyRollingDeltasFromHeight, WindowStartVec, Windows},
|
||||
};
|
||||
|
||||
use super::AddrCountsVecs;
|
||||
use super::{AddrCountsVecs, WithAddrTypes};
|
||||
|
||||
type AddrDelta = LazyRollingDeltasFromHeight<StoredU64, StoredI64, BasisPointsSigned32>;
|
||||
|
||||
#[derive(Clone, Traversable)]
|
||||
pub struct DeltaVecs {
|
||||
pub all: AddrDelta,
|
||||
#[traversable(flatten)]
|
||||
pub by_addr_type: ByAddrType<AddrDelta>,
|
||||
}
|
||||
#[derive(Clone, Deref, DerefMut, Traversable)]
|
||||
pub struct DeltaVecs(#[traversable(flatten)] pub WithAddrTypes<AddrDelta>);
|
||||
|
||||
impl DeltaVecs {
|
||||
pub(crate) fn new(
|
||||
@@ -45,6 +41,6 @@ impl DeltaVecs {
|
||||
)
|
||||
});
|
||||
|
||||
Self { all, by_addr_type }
|
||||
Self(WithAddrTypes { all, by_addr_type })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
//! Exposed address count tracking — running counters of how many addresses
|
||||
//! are currently in (or have ever been in) the exposed set, per address type
|
||||
//! plus an aggregated `all`. See the parent [`super`] module for the
|
||||
//! definition of "exposed" and how it varies by address type.
|
||||
|
||||
mod state;
|
||||
mod vecs;
|
||||
|
||||
pub use state::AddrTypeToExposedAddrCount;
|
||||
pub use vecs::ExposedAddrCountAllVecs;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Indexes, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyStoredVec, Database, Exit, Rw, StorageMode};
|
||||
|
||||
use crate::indexes;
|
||||
|
||||
/// Exposed address counts: funded (currently at-risk) and total (ever at-risk).
|
||||
#[derive(Traversable)]
|
||||
pub struct ExposedAddrCountsVecs<M: StorageMode = Rw> {
|
||||
pub funded: ExposedAddrCountAllVecs<M>,
|
||||
pub total: ExposedAddrCountAllVecs<M>,
|
||||
}
|
||||
|
||||
impl ExposedAddrCountsVecs {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
funded: ExposedAddrCountAllVecs::forced_import(
|
||||
db,
|
||||
"exposed_addr_count",
|
||||
version,
|
||||
indexes,
|
||||
)?,
|
||||
total: ExposedAddrCountAllVecs::forced_import(
|
||||
db,
|
||||
"total_exposed_addr_count",
|
||||
version,
|
||||
indexes,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn min_stateful_len(&self) -> usize {
|
||||
self.funded
|
||||
.min_stateful_len()
|
||||
.min(self.total.min_stateful_len())
|
||||
}
|
||||
|
||||
pub(crate) fn par_iter_height_mut(
|
||||
&mut self,
|
||||
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
|
||||
self.funded
|
||||
.par_iter_height_mut()
|
||||
.chain(self.total.par_iter_height_mut())
|
||||
}
|
||||
|
||||
pub(crate) fn reset_height(&mut self) -> Result<()> {
|
||||
self.funded.reset_height()?;
|
||||
self.total.reset_height()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest(&mut self, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
|
||||
self.funded.compute_rest(starting_indexes, exit)?;
|
||||
self.total.compute_rest(starting_indexes, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
use brk_cohort::ByAddrType;
|
||||
use brk_types::{Height, StoredU64};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::ReadableVec;
|
||||
|
||||
use crate::internal::PerBlock;
|
||||
|
||||
use super::vecs::ExposedAddrCountAllVecs;
|
||||
|
||||
/// Runtime counter for exposed address counts per address type.
|
||||
#[derive(Debug, Default, Deref, DerefMut)]
|
||||
pub struct AddrTypeToExposedAddrCount(ByAddrType<u64>);
|
||||
|
||||
impl AddrTypeToExposedAddrCount {
|
||||
#[inline]
|
||||
pub(crate) fn sum(&self) -> u64 {
|
||||
self.0.values().sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&ExposedAddrCountAllVecs, Height)> for AddrTypeToExposedAddrCount {
|
||||
#[inline]
|
||||
fn from((vecs, starting_height): (&ExposedAddrCountAllVecs, Height)) -> Self {
|
||||
if let Some(prev_height) = starting_height.decremented() {
|
||||
let read = |v: &PerBlock<StoredU64>| -> u64 {
|
||||
v.height.collect_one(prev_height).unwrap().into()
|
||||
};
|
||||
Self(ByAddrType {
|
||||
p2pk65: read(&vecs.by_addr_type.p2pk65),
|
||||
p2pk33: read(&vecs.by_addr_type.p2pk33),
|
||||
p2pkh: read(&vecs.by_addr_type.p2pkh),
|
||||
p2sh: read(&vecs.by_addr_type.p2sh),
|
||||
p2wpkh: read(&vecs.by_addr_type.p2wpkh),
|
||||
p2wsh: read(&vecs.by_addr_type.p2wsh),
|
||||
p2tr: read(&vecs.by_addr_type.p2tr),
|
||||
p2a: read(&vecs.by_addr_type.p2a),
|
||||
})
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{StoredU64, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Database, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
distribution::addr::WithAddrTypes,
|
||||
indexes,
|
||||
internal::PerBlock,
|
||||
};
|
||||
|
||||
/// Exposed address count (`all` + per-type) for a single variant (funded or total).
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
pub struct ExposedAddrCountAllVecs<M: StorageMode = Rw>(
|
||||
#[traversable(flatten)] pub WithAddrTypes<PerBlock<StoredU64, M>>,
|
||||
);
|
||||
|
||||
impl ExposedAddrCountAllVecs {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
Ok(Self(WithAddrTypes::<PerBlock<StoredU64>>::forced_import(
|
||||
db, name, version, indexes,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//! Exposed address tracking (quantum / pubkey-exposure sense).
|
||||
//!
|
||||
//! An address is "exposed" once its public key is in the blockchain. Once
|
||||
//! exposed, any funds at that address are at cryptographic risk (e.g. from
|
||||
//! a quantum attacker capable of recovering the private key from the pubkey).
|
||||
//!
|
||||
//! When the pubkey gets exposed depends on the address type:
|
||||
//!
|
||||
//! - **P2PK33, P2PK65, P2TR**: the pubkey (or P2TR's tweaked output key) is
|
||||
//! directly in the locking script of the funding output. These addresses are
|
||||
//! exposed the moment they receive any funds.
|
||||
//! - **P2PKH, P2SH, P2WPKH, P2WSH**: the locking script contains a hash of
|
||||
//! the pubkey/script. The pubkey is only revealed when spending. Note that
|
||||
//! even the spending tx itself exposes the pubkey while the address still
|
||||
//! holds funds — during the mempool window between broadcast and confirmation,
|
||||
//! the pubkey is visible while the UTXO being spent is still unspent on-chain.
|
||||
//! So every spent address of these types has had at least one moment with
|
||||
//! funds at quantum risk.
|
||||
//! - **P2A**: anyone-can-spend, no pubkey at all. Excluded from both counters.
|
||||
//!
|
||||
//! Formally, with `is_funding_exposed` = `output_type.pubkey_exposed_at_funding()`:
|
||||
//! - `funded` (count): `(utxo_count > 0) AND (is_funding_exposed OR spent_txo_count >= 1)`
|
||||
//! - `total` (count): `(is_funding_exposed AND ever received) OR spent_txo_count >= 1`
|
||||
//! - `supply` (sats): sum of balances of addresses currently in the funded set
|
||||
//!
|
||||
//! For P2PK/P2TR types this means `total ≡ total_addr_count` and
|
||||
//! `funded ≡ funded_addr_count` (every address of those types is exposed by
|
||||
//! virtue of existing). For P2PKH/P2SH/P2WPKH/P2WSH it's the strict subset of
|
||||
//! addresses that have been spent from. The aggregate `all` exposed counter
|
||||
//! sums these, giving "Bitcoin addresses currently with funds at quantum risk".
|
||||
//!
|
||||
//! All metrics are tracked as running counters and require no extra fields
|
||||
//! on the address data — they're maintained via delta detection in
|
||||
//! `process_received` and `process_sent`.
|
||||
|
||||
mod count;
|
||||
mod supply;
|
||||
|
||||
pub use count::{AddrTypeToExposedAddrCount, ExposedAddrCountsVecs};
|
||||
pub use supply::{AddrTypeToExposedAddrSupply, ExposedAddrSupplyVecs};
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Indexes, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyStoredVec, Database, Exit, Rw, StorageMode};
|
||||
|
||||
use crate::indexes;
|
||||
|
||||
/// Top-level container for all exposed address tracking: counts (funded +
|
||||
/// total) plus the funded supply.
|
||||
#[derive(Traversable)]
|
||||
pub struct ExposedAddrVecs<M: StorageMode = Rw> {
|
||||
pub count: ExposedAddrCountsVecs<M>,
|
||||
pub supply: ExposedAddrSupplyVecs<M>,
|
||||
}
|
||||
|
||||
impl ExposedAddrVecs {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
count: ExposedAddrCountsVecs::forced_import(db, version, indexes)?,
|
||||
supply: ExposedAddrSupplyVecs::forced_import(db, version, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn min_stateful_len(&self) -> usize {
|
||||
self.count
|
||||
.min_stateful_len()
|
||||
.min(self.supply.min_stateful_len())
|
||||
}
|
||||
|
||||
pub(crate) fn par_iter_height_mut(
|
||||
&mut self,
|
||||
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
|
||||
self.count
|
||||
.par_iter_height_mut()
|
||||
.chain(self.supply.par_iter_height_mut())
|
||||
}
|
||||
|
||||
pub(crate) fn reset_height(&mut self) -> Result<()> {
|
||||
self.count.reset_height()?;
|
||||
self.supply.reset_height()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest(&mut self, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
|
||||
self.count.compute_rest(starting_indexes, exit)?;
|
||||
self.supply.compute_rest(starting_indexes, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//! Exposed address supply (sats) tracking — running sum of balances held by
|
||||
//! addresses currently in the funded exposed set, per address type plus an
|
||||
//! aggregated `all`. See the parent [`super`] module for the definition of
|
||||
//! "exposed" and how it varies by address type.
|
||||
|
||||
mod state;
|
||||
mod vecs;
|
||||
|
||||
pub use state::AddrTypeToExposedAddrSupply;
|
||||
pub use vecs::ExposedAddrSupplyVecs;
|
||||
@@ -0,0 +1,43 @@
|
||||
use brk_cohort::ByAddrType;
|
||||
use brk_types::{Height, Sats};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::ReadableVec;
|
||||
|
||||
use crate::internal::PerBlock;
|
||||
|
||||
use super::vecs::ExposedAddrSupplyVecs;
|
||||
|
||||
/// Runtime running counter for the total balance (sats) held by funded
|
||||
/// exposed addresses, per address type.
|
||||
#[derive(Debug, Default, Deref, DerefMut)]
|
||||
pub struct AddrTypeToExposedAddrSupply(ByAddrType<u64>);
|
||||
|
||||
impl AddrTypeToExposedAddrSupply {
|
||||
#[inline]
|
||||
pub(crate) fn sum(&self) -> u64 {
|
||||
self.0.values().sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&ExposedAddrSupplyVecs, Height)> for AddrTypeToExposedAddrSupply {
|
||||
#[inline]
|
||||
fn from((vecs, starting_height): (&ExposedAddrSupplyVecs, Height)) -> Self {
|
||||
if let Some(prev_height) = starting_height.decremented() {
|
||||
let read = |v: &PerBlock<Sats>| -> u64 {
|
||||
u64::from(v.height.collect_one(prev_height).unwrap())
|
||||
};
|
||||
Self(ByAddrType {
|
||||
p2pk65: read(&vecs.by_addr_type.p2pk65),
|
||||
p2pk33: read(&vecs.by_addr_type.p2pk33),
|
||||
p2pkh: read(&vecs.by_addr_type.p2pkh),
|
||||
p2sh: read(&vecs.by_addr_type.p2sh),
|
||||
p2wpkh: read(&vecs.by_addr_type.p2wpkh),
|
||||
p2wsh: read(&vecs.by_addr_type.p2wsh),
|
||||
p2tr: read(&vecs.by_addr_type.p2tr),
|
||||
p2a: read(&vecs.by_addr_type.p2a),
|
||||
})
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Sats, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Database, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
distribution::addr::WithAddrTypes,
|
||||
indexes,
|
||||
internal::PerBlock,
|
||||
};
|
||||
|
||||
/// Exposed address supply (sats) — `all` + per-address-type. Tracks the total
|
||||
/// balance held by addresses currently in the funded exposed set.
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
pub struct ExposedAddrSupplyVecs<M: StorageMode = Rw>(
|
||||
#[traversable(flatten)] pub WithAddrTypes<PerBlock<Sats, M>>,
|
||||
);
|
||||
|
||||
impl ExposedAddrSupplyVecs {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
Ok(Self(WithAddrTypes::<PerBlock<Sats>>::forced_import(
|
||||
db,
|
||||
"exposed_addr_supply",
|
||||
version,
|
||||
indexes,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,26 @@ mod activity;
|
||||
mod addr_count;
|
||||
mod data;
|
||||
mod delta;
|
||||
mod exposed;
|
||||
mod indexes;
|
||||
mod new_addr_count;
|
||||
mod reused;
|
||||
mod total_addr_count;
|
||||
mod type_map;
|
||||
mod with_addr_types;
|
||||
|
||||
pub use activity::{AddrActivityVecs, AddrTypeToActivityCounts};
|
||||
pub use addr_count::{AddrCountsVecs, AddrTypeToAddrCount};
|
||||
pub use data::AddrsDataVecs;
|
||||
pub use delta::DeltaVecs;
|
||||
pub use exposed::{
|
||||
AddrTypeToExposedAddrCount, AddrTypeToExposedAddrSupply, ExposedAddrVecs,
|
||||
};
|
||||
pub use indexes::AnyAddrIndexesVecs;
|
||||
pub use new_addr_count::NewAddrCountVecs;
|
||||
pub use reused::{
|
||||
AddrTypeToReusedAddrCount, AddrTypeToReusedAddrUseCount, ReusedAddrVecs,
|
||||
};
|
||||
pub use total_addr_count::TotalAddrCountVecs;
|
||||
pub use type_map::{AddrTypeToTypeIndexMap, AddrTypeToVec, HeightToAddrTypeToVec};
|
||||
pub use with_addr_types::WithAddrTypes;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use brk_cohort::ByAddrType;
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Height, StoredU64, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Database, Exit, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
@@ -9,15 +9,14 @@ use crate::{
|
||||
internal::{PerBlockCumulativeRolling, WindowStartVec, Windows},
|
||||
};
|
||||
|
||||
use super::TotalAddrCountVecs;
|
||||
use super::{TotalAddrCountVecs, WithAddrTypes};
|
||||
|
||||
/// New address count per block (global + per-type)
|
||||
#[derive(Traversable)]
|
||||
pub struct NewAddrCountVecs<M: StorageMode = Rw> {
|
||||
pub all: PerBlockCumulativeRolling<StoredU64, StoredU64, M>,
|
||||
/// New address count per block (global + per-type).
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
pub struct NewAddrCountVecs<M: StorageMode = Rw>(
|
||||
#[traversable(flatten)]
|
||||
pub by_addr_type: ByAddrType<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
|
||||
}
|
||||
pub WithAddrTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
|
||||
);
|
||||
|
||||
impl NewAddrCountVecs {
|
||||
pub(crate) fn forced_import(
|
||||
@@ -26,25 +25,11 @@ impl NewAddrCountVecs {
|
||||
indexes: &indexes::Vecs,
|
||||
cached_starts: &Windows<&WindowStartVec>,
|
||||
) -> Result<Self> {
|
||||
let all = PerBlockCumulativeRolling::forced_import(
|
||||
db,
|
||||
"new_addr_count",
|
||||
version,
|
||||
indexes,
|
||||
cached_starts,
|
||||
)?;
|
||||
|
||||
let by_addr_type = ByAddrType::new_with_name(|name| {
|
||||
PerBlockCumulativeRolling::forced_import(
|
||||
db,
|
||||
&format!("{name}_new_addr_count"),
|
||||
version,
|
||||
indexes,
|
||||
cached_starts,
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Self { all, by_addr_type })
|
||||
Ok(Self(WithAddrTypes::<
|
||||
PerBlockCumulativeRolling<StoredU64, StoredU64>,
|
||||
>::forced_import(
|
||||
db, "new_addr_count", version, indexes, cached_starts
|
||||
)?))
|
||||
}
|
||||
|
||||
pub(crate) fn compute(
|
||||
@@ -53,11 +38,12 @@ impl NewAddrCountVecs {
|
||||
total_addr_count: &TotalAddrCountVecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.all.compute(max_from, exit, |height_vec| {
|
||||
self.0.all.compute(max_from, exit, |height_vec| {
|
||||
Ok(height_vec.compute_change(max_from, &total_addr_count.all.height, 1, exit)?)
|
||||
})?;
|
||||
|
||||
for ((_, new), (_, total)) in self
|
||||
.0
|
||||
.by_addr_type
|
||||
.iter_mut()
|
||||
.zip(total_addr_count.by_addr_type.iter())
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
//! Reused address count tracking — running counters of how many addresses
|
||||
//! are currently in (or have ever been in) the reused set, per address type
|
||||
//! plus an aggregated `all`. See the parent [`super`] module for the
|
||||
//! definition of "reused".
|
||||
//!
|
||||
//! Two counters are exposed:
|
||||
//! - `funded`: addresses currently funded AND with `funded_txo_count > 1`
|
||||
//! - `total`: addresses that have ever satisfied `funded_txo_count > 1` (monotonic)
|
||||
|
||||
mod state;
|
||||
mod vecs;
|
||||
|
||||
pub use state::AddrTypeToReusedAddrCount;
|
||||
pub use vecs::ReusedAddrCountAllVecs;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Indexes, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyStoredVec, Database, Exit, Rw, StorageMode};
|
||||
|
||||
use crate::indexes;
|
||||
|
||||
/// Reused address counts: funded (currently with balance) and total (ever reused).
|
||||
#[derive(Traversable)]
|
||||
pub struct ReusedAddrCountsVecs<M: StorageMode = Rw> {
|
||||
pub funded: ReusedAddrCountAllVecs<M>,
|
||||
pub total: ReusedAddrCountAllVecs<M>,
|
||||
}
|
||||
|
||||
impl ReusedAddrCountsVecs {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
funded: ReusedAddrCountAllVecs::forced_import(
|
||||
db,
|
||||
"reused_addr_count",
|
||||
version,
|
||||
indexes,
|
||||
)?,
|
||||
total: ReusedAddrCountAllVecs::forced_import(
|
||||
db,
|
||||
"total_reused_addr_count",
|
||||
version,
|
||||
indexes,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn min_stateful_len(&self) -> usize {
|
||||
self.funded
|
||||
.min_stateful_len()
|
||||
.min(self.total.min_stateful_len())
|
||||
}
|
||||
|
||||
pub(crate) fn par_iter_height_mut(
|
||||
&mut self,
|
||||
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
|
||||
self.funded
|
||||
.par_iter_height_mut()
|
||||
.chain(self.total.par_iter_height_mut())
|
||||
}
|
||||
|
||||
pub(crate) fn reset_height(&mut self) -> Result<()> {
|
||||
self.funded.reset_height()?;
|
||||
self.total.reset_height()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest(&mut self, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
|
||||
self.funded.compute_rest(starting_indexes, exit)?;
|
||||
self.total.compute_rest(starting_indexes, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
use brk_cohort::ByAddrType;
|
||||
use brk_types::{Height, StoredU64};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::ReadableVec;
|
||||
|
||||
use crate::internal::PerBlock;
|
||||
|
||||
use super::vecs::ReusedAddrCountAllVecs;
|
||||
|
||||
/// Runtime counter for reused address counts per address type.
|
||||
#[derive(Debug, Default, Deref, DerefMut)]
|
||||
pub struct AddrTypeToReusedAddrCount(ByAddrType<u64>);
|
||||
|
||||
impl AddrTypeToReusedAddrCount {
|
||||
#[inline]
|
||||
pub(crate) fn sum(&self) -> u64 {
|
||||
self.0.values().sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&ReusedAddrCountAllVecs, Height)> for AddrTypeToReusedAddrCount {
|
||||
#[inline]
|
||||
fn from((vecs, starting_height): (&ReusedAddrCountAllVecs, Height)) -> Self {
|
||||
if let Some(prev_height) = starting_height.decremented() {
|
||||
let read = |v: &PerBlock<StoredU64>| -> u64 {
|
||||
v.height.collect_one(prev_height).unwrap().into()
|
||||
};
|
||||
Self(ByAddrType {
|
||||
p2pk65: read(&vecs.by_addr_type.p2pk65),
|
||||
p2pk33: read(&vecs.by_addr_type.p2pk33),
|
||||
p2pkh: read(&vecs.by_addr_type.p2pkh),
|
||||
p2sh: read(&vecs.by_addr_type.p2sh),
|
||||
p2wpkh: read(&vecs.by_addr_type.p2wpkh),
|
||||
p2wsh: read(&vecs.by_addr_type.p2wsh),
|
||||
p2tr: read(&vecs.by_addr_type.p2tr),
|
||||
p2a: read(&vecs.by_addr_type.p2a),
|
||||
})
|
||||
} else {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{StoredU64, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Database, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
distribution::addr::WithAddrTypes,
|
||||
indexes,
|
||||
internal::PerBlock,
|
||||
};
|
||||
|
||||
/// Reused address count (`all` + per-type) for a single variant (funded or total).
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
pub struct ReusedAddrCountAllVecs<M: StorageMode = Rw>(
|
||||
#[traversable(flatten)] pub WithAddrTypes<PerBlock<StoredU64, M>>,
|
||||
);
|
||||
|
||||
impl ReusedAddrCountAllVecs {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
Ok(Self(WithAddrTypes::<PerBlock<StoredU64>>::forced_import(
|
||||
db, name, version, indexes,
|
||||
)?))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//! Reused address tracking.
|
||||
//!
|
||||
//! An address is "reused" if its lifetime `funded_txo_count > 1` — i.e. it
|
||||
//! has received more than one output across its lifetime. This is the
|
||||
//! simplest output-multiplicity proxy for address linkability.
|
||||
//!
|
||||
//! Two facets are tracked here:
|
||||
//! - [`count`] — how many distinct addresses are currently reused (funded)
|
||||
//! and how many have *ever* been reused (total). Per address type plus
|
||||
//! an aggregated `all`.
|
||||
//! - [`uses`] — per-block count of outputs going to addresses that were
|
||||
//! already reused, plus the derived percent over total address-output
|
||||
//! count (denominator from `scripts::count`).
|
||||
|
||||
mod count;
|
||||
mod uses;
|
||||
|
||||
pub use count::{AddrTypeToReusedAddrCount, ReusedAddrCountsVecs};
|
||||
pub use uses::{AddrTypeToReusedAddrUseCount, ReusedAddrUsesVecs};
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Indexes, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyStoredVec, Database, Exit, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{WindowStartVec, Windows},
|
||||
scripts,
|
||||
};
|
||||
|
||||
/// Top-level container for all reused address tracking: counts (funded +
|
||||
/// total) plus per-block uses (count + percent).
|
||||
#[derive(Traversable)]
|
||||
pub struct ReusedAddrVecs<M: StorageMode = Rw> {
|
||||
pub count: ReusedAddrCountsVecs<M>,
|
||||
pub uses: ReusedAddrUsesVecs<M>,
|
||||
}
|
||||
|
||||
impl ReusedAddrVecs {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
cached_starts: &Windows<&WindowStartVec>,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
count: ReusedAddrCountsVecs::forced_import(db, version, indexes)?,
|
||||
uses: ReusedAddrUsesVecs::forced_import(db, version, indexes, cached_starts)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn min_stateful_len(&self) -> usize {
|
||||
self.count
|
||||
.min_stateful_len()
|
||||
.min(self.uses.min_stateful_len())
|
||||
}
|
||||
|
||||
pub(crate) fn par_iter_height_mut(
|
||||
&mut self,
|
||||
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
|
||||
self.count
|
||||
.par_iter_height_mut()
|
||||
.chain(self.uses.par_iter_height_mut())
|
||||
}
|
||||
|
||||
pub(crate) fn reset_height(&mut self) -> Result<()> {
|
||||
self.count.reset_height()?;
|
||||
self.uses.reset_height()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest(
|
||||
&mut self,
|
||||
starting_indexes: &Indexes,
|
||||
scripts_count: &scripts::CountVecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.count.compute_rest(starting_indexes, exit)?;
|
||||
self.uses
|
||||
.compute_rest(starting_indexes, scripts_count, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
//! Per-block reused-address-use tracking. See [`vecs::ReusedAddrUsesVecs`]
|
||||
//! for the full description of the metric.
|
||||
|
||||
mod state;
|
||||
mod vecs;
|
||||
|
||||
pub use state::AddrTypeToReusedAddrUseCount;
|
||||
pub use vecs::ReusedAddrUsesVecs;
|
||||
@@ -0,0 +1,22 @@
|
||||
use brk_cohort::ByAddrType;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
||||
/// Per-block running counter of reused address uses, per address type.
|
||||
/// Reset at the start of each block (no disk recovery needed since the
|
||||
/// per-block flow is reconstructed from `process_received` deterministically).
|
||||
#[derive(Debug, Default, Deref, DerefMut)]
|
||||
pub struct AddrTypeToReusedAddrUseCount(ByAddrType<u64>);
|
||||
|
||||
impl AddrTypeToReusedAddrUseCount {
|
||||
#[inline]
|
||||
pub(crate) fn sum(&self) -> u64 {
|
||||
self.0.values().sum()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn reset(&mut self) {
|
||||
for v in self.0.values_mut() {
|
||||
*v = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
use brk_cohort::ByAddrType;
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{BasisPoints16, Height, Indexes, OutputType, StoredU64, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyStoredVec, Database, Exit, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
distribution::addr::WithAddrTypes,
|
||||
indexes,
|
||||
internal::{
|
||||
PerBlockCumulativeRolling, PercentCumulativeRolling, RatioU64Bp16, WindowStartVec, Windows,
|
||||
},
|
||||
scripts,
|
||||
};
|
||||
|
||||
use super::state::AddrTypeToReusedAddrUseCount;
|
||||
|
||||
/// Per-block reused-address-use metrics. A "use" is a single output going
|
||||
/// to an address (not deduplicated): an address receiving N outputs in one
|
||||
/// block contributes N. The count only includes uses going to addresses
|
||||
/// that were *already* reused at the moment of the use, so the use that
|
||||
/// makes an address reused is not itself counted.
|
||||
///
|
||||
/// The denominator for the percent (total address-output count) lives in
|
||||
/// `scripts::count` and is reused here rather than duplicated.
|
||||
#[derive(Traversable)]
|
||||
pub struct ReusedAddrUsesVecs<M: StorageMode = Rw> {
|
||||
pub reused_addr_use_count:
|
||||
WithAddrTypes<PerBlockCumulativeRolling<StoredU64, StoredU64, M>>,
|
||||
pub reused_addr_use_percent: WithAddrTypes<PercentCumulativeRolling<BasisPoints16, M>>,
|
||||
}
|
||||
|
||||
impl ReusedAddrUsesVecs {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
cached_starts: &Windows<&WindowStartVec>,
|
||||
) -> Result<Self> {
|
||||
let reused_addr_use_count =
|
||||
WithAddrTypes::<PerBlockCumulativeRolling<StoredU64, StoredU64>>::forced_import(
|
||||
db,
|
||||
"reused_addr_use_count",
|
||||
version,
|
||||
indexes,
|
||||
cached_starts,
|
||||
)?;
|
||||
let percent_name = "reused_addr_use_percent";
|
||||
let reused_addr_use_percent = WithAddrTypes {
|
||||
all: PercentCumulativeRolling::forced_import(db, percent_name, version, indexes)?,
|
||||
by_addr_type: ByAddrType::new_with_name(|type_name| {
|
||||
PercentCumulativeRolling::forced_import(
|
||||
db,
|
||||
&format!("{type_name}_{percent_name}"),
|
||||
version,
|
||||
indexes,
|
||||
)
|
||||
})?,
|
||||
};
|
||||
Ok(Self {
|
||||
reused_addr_use_count,
|
||||
reused_addr_use_percent,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn min_stateful_len(&self) -> usize {
|
||||
self.reused_addr_use_count.min_stateful_len()
|
||||
}
|
||||
|
||||
pub(crate) fn par_iter_height_mut(
|
||||
&mut self,
|
||||
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
|
||||
self.reused_addr_use_count.par_iter_height_mut()
|
||||
}
|
||||
|
||||
pub(crate) fn reset_height(&mut self) -> Result<()> {
|
||||
self.reused_addr_use_count.reset_height()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_height(&mut self, reused: &AddrTypeToReusedAddrUseCount) {
|
||||
self.reused_addr_use_count
|
||||
.push_height(reused.sum(), reused.values().copied());
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest(
|
||||
&mut self,
|
||||
starting_indexes: &Indexes,
|
||||
scripts_count: &scripts::CountVecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.reused_addr_use_count
|
||||
.compute_rest(starting_indexes.height, exit)?;
|
||||
|
||||
compute_one_percent(
|
||||
&mut self.reused_addr_use_percent.all,
|
||||
&self.reused_addr_use_count.all,
|
||||
&scripts_count.addr_output_count,
|
||||
starting_indexes.height,
|
||||
exit,
|
||||
)?;
|
||||
for otype in OutputType::ADDR_TYPES {
|
||||
compute_one_percent(
|
||||
self.reused_addr_use_percent
|
||||
.by_addr_type
|
||||
.get_mut_unwrap(otype),
|
||||
self.reused_addr_use_count.by_addr_type.get_unwrap(otype),
|
||||
denom_for_type(scripts_count, otype),
|
||||
starting_indexes.height,
|
||||
exit,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn compute_one_percent(
|
||||
percent: &mut PercentCumulativeRolling<BasisPoints16>,
|
||||
reused: &PerBlockCumulativeRolling<StoredU64, StoredU64>,
|
||||
denom: &PerBlockCumulativeRolling<StoredU64, StoredU64>,
|
||||
starting_height: Height,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
percent.compute_binary::<StoredU64, StoredU64, RatioU64Bp16, _, _, _, _>(
|
||||
starting_height,
|
||||
&reused.cumulative.height,
|
||||
&denom.cumulative.height,
|
||||
reused.sum.as_array().map(|w| &w.height),
|
||||
denom.sum.as_array().map(|w| &w.height),
|
||||
exit,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn denom_for_type(
|
||||
scripts_count: &scripts::CountVecs,
|
||||
otype: OutputType,
|
||||
) -> &PerBlockCumulativeRolling<StoredU64, StoredU64> {
|
||||
match otype {
|
||||
OutputType::P2PK33 => &scripts_count.p2pk33,
|
||||
OutputType::P2PK65 => &scripts_count.p2pk65,
|
||||
OutputType::P2PKH => &scripts_count.p2pkh,
|
||||
OutputType::P2SH => &scripts_count.p2sh,
|
||||
OutputType::P2WPKH => &scripts_count.p2wpkh,
|
||||
OutputType::P2WSH => &scripts_count.p2wsh,
|
||||
OutputType::P2TR => &scripts_count.p2tr,
|
||||
OutputType::P2A => &scripts_count.p2a,
|
||||
_ => unreachable!("OutputType::ADDR_TYPES contains only address types"),
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,18 @@
|
||||
use brk_cohort::ByAddrType;
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Height, StoredU64, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Database, Exit, Rw, StorageMode};
|
||||
|
||||
use crate::{indexes, internal::PerBlock};
|
||||
|
||||
use super::AddrCountsVecs;
|
||||
use super::{AddrCountsVecs, WithAddrTypes};
|
||||
|
||||
/// Total address count (global + per-type) with all derived indexes
|
||||
#[derive(Traversable)]
|
||||
pub struct TotalAddrCountVecs<M: StorageMode = Rw> {
|
||||
pub all: PerBlock<StoredU64, M>,
|
||||
#[traversable(flatten)]
|
||||
pub by_addr_type: ByAddrType<PerBlock<StoredU64, M>>,
|
||||
}
|
||||
/// Total address count (global + per-type) with all derived indexes.
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
pub struct TotalAddrCountVecs<M: StorageMode = Rw>(
|
||||
#[traversable(flatten)] pub WithAddrTypes<PerBlock<StoredU64, M>>,
|
||||
);
|
||||
|
||||
impl TotalAddrCountVecs {
|
||||
pub(crate) fn forced_import(
|
||||
@@ -22,13 +20,12 @@ impl TotalAddrCountVecs {
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let all = PerBlock::forced_import(db, "total_addr_count", version, indexes)?;
|
||||
|
||||
let by_addr_type: ByAddrType<PerBlock<StoredU64>> = ByAddrType::new_with_name(|name| {
|
||||
PerBlock::forced_import(db, &format!("{name}_total_addr_count"), version, indexes)
|
||||
})?;
|
||||
|
||||
Ok(Self { all, by_addr_type })
|
||||
Ok(Self(WithAddrTypes::<PerBlock<StoredU64>>::forced_import(
|
||||
db,
|
||||
"total_addr_count",
|
||||
version,
|
||||
indexes,
|
||||
)?))
|
||||
}
|
||||
|
||||
/// Eagerly compute total = addr_count + empty_addr_count.
|
||||
@@ -39,14 +36,14 @@ impl TotalAddrCountVecs {
|
||||
empty_addr_count: &AddrCountsVecs,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.all.height.compute_add(
|
||||
self.0.all.height.compute_add(
|
||||
max_from,
|
||||
&addr_count.all.height,
|
||||
&empty_addr_count.all.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
for ((_, total), ((_, addr), (_, empty))) in self.by_addr_type.iter_mut().zip(
|
||||
for ((_, total), ((_, addr), (_, empty))) in self.0.by_addr_type.iter_mut().zip(
|
||||
addr_count
|
||||
.by_addr_type
|
||||
.iter()
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
//! Generic `all` + per-`AddrType` container, mirrors the `WithSth` pattern
|
||||
//! along the address-type axis. Used by every metric that tracks one
|
||||
//! aggregate value alongside a per-address-type breakdown.
|
||||
|
||||
use brk_cohort::ByAddrType;
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Height, Indexes, Version};
|
||||
use rayon::prelude::*;
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, WritableVec};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{NumericValue, PerBlock, PerBlockCumulativeRolling, WindowStartVec, Windows},
|
||||
};
|
||||
|
||||
/// `all` aggregate plus per-`AddrType` breakdown.
|
||||
#[derive(Clone, Traversable)]
|
||||
pub struct WithAddrTypes<T> {
|
||||
pub all: T,
|
||||
#[traversable(flatten)]
|
||||
pub by_addr_type: ByAddrType<T>,
|
||||
}
|
||||
|
||||
impl<T> WithAddrTypes<PerBlock<T>>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
{
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let all = PerBlock::forced_import(db, name, version, indexes)?;
|
||||
let by_addr_type = ByAddrType::new_with_name(|type_name| {
|
||||
PerBlock::forced_import(db, &format!("{type_name}_{name}"), version, indexes)
|
||||
})?;
|
||||
Ok(Self { all, by_addr_type })
|
||||
}
|
||||
|
||||
pub(crate) fn min_stateful_len(&self) -> usize {
|
||||
self.by_addr_type
|
||||
.values()
|
||||
.map(|v| v.height.len())
|
||||
.min()
|
||||
.unwrap()
|
||||
.min(self.all.height.len())
|
||||
}
|
||||
|
||||
pub(crate) fn par_iter_height_mut(
|
||||
&mut self,
|
||||
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
|
||||
rayon::iter::once(&mut self.all.height as &mut dyn AnyStoredVec).chain(
|
||||
self.by_addr_type
|
||||
.par_values_mut()
|
||||
.map(|v| &mut v.height as &mut dyn AnyStoredVec),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn reset_height(&mut self) -> Result<()> {
|
||||
self.all.height.reset()?;
|
||||
for v in self.by_addr_type.values_mut() {
|
||||
v.height.reset()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_height<U>(&mut self, total: U, per_type: impl IntoIterator<Item = U>)
|
||||
where
|
||||
U: Into<T>,
|
||||
{
|
||||
self.all.height.push(total.into());
|
||||
for (v, value) in self.by_addr_type.values_mut().zip(per_type) {
|
||||
v.height.push(value.into());
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute `all.height` as the per-block sum of the per-type vecs.
|
||||
pub(crate) fn compute_rest(
|
||||
&mut self,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let sources: Vec<&EagerVec<PcoVec<Height, T>>> =
|
||||
self.by_addr_type.values().map(|v| &v.height).collect();
|
||||
self.all
|
||||
.height
|
||||
.compute_sum_of_others(starting_indexes.height, &sources, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, C> WithAddrTypes<PerBlockCumulativeRolling<T, C>>
|
||||
where
|
||||
T: NumericValue + JsonSchema + Into<C>,
|
||||
C: NumericValue + JsonSchema,
|
||||
{
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
cached_starts: &Windows<&WindowStartVec>,
|
||||
) -> Result<Self> {
|
||||
let all = PerBlockCumulativeRolling::forced_import(
|
||||
db,
|
||||
name,
|
||||
version,
|
||||
indexes,
|
||||
cached_starts,
|
||||
)?;
|
||||
let by_addr_type = ByAddrType::new_with_name(|type_name| {
|
||||
PerBlockCumulativeRolling::forced_import(
|
||||
db,
|
||||
&format!("{type_name}_{name}"),
|
||||
version,
|
||||
indexes,
|
||||
cached_starts,
|
||||
)
|
||||
})?;
|
||||
Ok(Self { all, by_addr_type })
|
||||
}
|
||||
|
||||
pub(crate) fn min_stateful_len(&self) -> usize {
|
||||
self.by_addr_type
|
||||
.values()
|
||||
.map(|v| v.block.len())
|
||||
.min()
|
||||
.unwrap()
|
||||
.min(self.all.block.len())
|
||||
}
|
||||
|
||||
pub(crate) fn par_iter_height_mut(
|
||||
&mut self,
|
||||
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
|
||||
rayon::iter::once(&mut self.all.block as &mut dyn AnyStoredVec).chain(
|
||||
self.by_addr_type
|
||||
.par_values_mut()
|
||||
.map(|v| &mut v.block as &mut dyn AnyStoredVec),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn reset_height(&mut self) -> Result<()> {
|
||||
self.all.block.reset()?;
|
||||
for v in self.by_addr_type.values_mut() {
|
||||
v.block.reset()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_height<U>(&mut self, total: U, per_type: impl IntoIterator<Item = U>)
|
||||
where
|
||||
U: Into<T>,
|
||||
{
|
||||
self.all.block.push(total.into());
|
||||
for (v, value) in self.by_addr_type.values_mut().zip(per_type) {
|
||||
v.block.push(value.into());
|
||||
}
|
||||
}
|
||||
|
||||
/// Finalize `cumulative` / `sum` / `average` for `all` and every per-type vec.
|
||||
pub(crate) fn compute_rest(&mut self, max_from: Height, exit: &Exit) -> Result<()> {
|
||||
self.all.compute_rest(max_from, exit)?;
|
||||
for v in self.by_addr_type.values_mut() {
|
||||
v.compute_rest(max_from, exit)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user