global: address -> addr rename

This commit is contained in:
nym21
2026-03-17 11:01:21 +01:00
parent 5609e6c010
commit f62943199c
141 changed files with 3788 additions and 3754 deletions
+138
View File
@@ -0,0 +1,138 @@
use brk_cohort::ByAddrType;
use brk_error::Result;
use brk_types::{
AnyAddrDataIndexEnum, EmptyAddrData, FundedAddrData, OutputType, TxIndex, TypeIndex,
};
use smallvec::SmallVec;
use crate::distribution::{
addr::{AddrTypeToTypeIndexMap, AddrsDataVecs, AnyAddrIndexesVecs},
compute::VecsReaders,
};
use super::super::cohort::{WithAddrDataSource, update_tx_counts};
use super::lookup::AddrLookup;
/// Cache for address data within a flush interval.
pub struct AddrCache {
/// Addrs with non-zero balance
funded: AddrTypeToTypeIndexMap<WithAddrDataSource<FundedAddrData>>,
/// Addrs that became empty (zero balance)
empty: AddrTypeToTypeIndexMap<WithAddrDataSource<EmptyAddrData>>,
}
impl Default for AddrCache {
fn default() -> Self {
Self::new()
}
}
impl AddrCache {
pub(crate) fn new() -> Self {
Self {
funded: AddrTypeToTypeIndexMap::default(),
empty: AddrTypeToTypeIndexMap::default(),
}
}
/// Check if address is in cache (either funded or empty).
#[inline]
pub(crate) fn contains(&self, addr_type: OutputType, type_index: TypeIndex) -> bool {
self.funded
.get(addr_type)
.is_some_and(|m| m.contains_key(&type_index))
|| self
.empty
.get(addr_type)
.is_some_and(|m| m.contains_key(&type_index))
}
/// Merge address data into funded cache.
#[inline]
pub(crate) fn merge_funded(
&mut self,
data: AddrTypeToTypeIndexMap<WithAddrDataSource<FundedAddrData>>,
) {
self.funded.merge_mut(data);
}
/// Create an AddrLookup view into this cache.
#[inline]
pub(crate) fn as_lookup(&mut self) -> AddrLookup<'_> {
AddrLookup {
funded: &mut self.funded,
empty: &mut self.empty,
}
}
/// Update transaction counts for addresses.
pub(crate) fn update_tx_counts(
&mut self,
tx_index_vecs: AddrTypeToTypeIndexMap<SmallVec<[TxIndex; 4]>>,
) {
update_tx_counts(&mut self.funded, &mut self.empty, tx_index_vecs);
}
/// Take the cache contents for flushing, leaving empty caches.
pub(crate) fn take(
&mut self,
) -> (
AddrTypeToTypeIndexMap<WithAddrDataSource<EmptyAddrData>>,
AddrTypeToTypeIndexMap<WithAddrDataSource<FundedAddrData>>,
) {
(
std::mem::take(&mut self.empty),
std::mem::take(&mut self.funded),
)
}
}
/// Load address data from storage or create new.
///
/// Returns None if address is already in cache (funded or empty).
#[allow(clippy::too_many_arguments)]
pub(crate) fn load_uncached_addr_data(
addr_type: OutputType,
type_index: TypeIndex,
first_addr_indexes: &ByAddrType<TypeIndex>,
cache: &AddrCache,
vr: &VecsReaders,
any_addr_indexes: &AnyAddrIndexesVecs,
addrs_data: &AddrsDataVecs,
) -> Result<Option<WithAddrDataSource<FundedAddrData>>> {
// Check if this is a new address (type_index >= first for this height)
let first = *first_addr_indexes.get(addr_type).unwrap();
if first <= type_index {
return Ok(Some(WithAddrDataSource::New(
FundedAddrData::default(),
)));
}
// Skip if already in cache
if cache.contains(addr_type, type_index) {
return Ok(None);
}
// Read from storage
let reader = vr.addr_reader(addr_type);
let any_addr_index = any_addr_indexes.get(addr_type, type_index, reader)?;
Ok(Some(match any_addr_index.to_enum() {
AnyAddrDataIndexEnum::Funded(funded_index) => {
let reader = &vr.any_addr_index_to_any_addr_data.funded;
let funded_data = addrs_data
.funded
.get_any_or_read_at(funded_index.into(), reader)?
.unwrap();
WithAddrDataSource::FromFunded(funded_index, funded_data)
}
AnyAddrDataIndexEnum::Empty(empty_index) => {
let reader = &vr.any_addr_index_to_any_addr_data.empty;
let empty_data = addrs_data
.empty
.get_any_or_read_at(empty_index.into(), reader)?
.unwrap();
WithAddrDataSource::FromEmpty(empty_index, empty_data.into())
}
}))
}
@@ -1,138 +0,0 @@
use brk_cohort::ByAddressType;
use brk_error::Result;
use brk_types::{
AnyAddressDataIndexEnum, EmptyAddressData, FundedAddressData, OutputType, TxIndex, TypeIndex,
};
use smallvec::SmallVec;
use crate::distribution::{
address::{AddressTypeToTypeIndexMap, AddressesDataVecs, AnyAddressIndexesVecs},
compute::VecsReaders,
};
use super::super::cohort::{WithAddressDataSource, update_tx_counts};
use super::lookup::AddressLookup;
/// Cache for address data within a flush interval.
pub struct AddressCache {
/// Addresses with non-zero balance
funded: AddressTypeToTypeIndexMap<WithAddressDataSource<FundedAddressData>>,
/// Addresses that became empty (zero balance)
empty: AddressTypeToTypeIndexMap<WithAddressDataSource<EmptyAddressData>>,
}
impl Default for AddressCache {
fn default() -> Self {
Self::new()
}
}
impl AddressCache {
pub(crate) fn new() -> Self {
Self {
funded: AddressTypeToTypeIndexMap::default(),
empty: AddressTypeToTypeIndexMap::default(),
}
}
/// Check if address is in cache (either funded or empty).
#[inline]
pub(crate) fn contains(&self, address_type: OutputType, type_index: TypeIndex) -> bool {
self.funded
.get(address_type)
.is_some_and(|m| m.contains_key(&type_index))
|| self
.empty
.get(address_type)
.is_some_and(|m| m.contains_key(&type_index))
}
/// Merge address data into funded cache.
#[inline]
pub(crate) fn merge_funded(
&mut self,
data: AddressTypeToTypeIndexMap<WithAddressDataSource<FundedAddressData>>,
) {
self.funded.merge_mut(data);
}
/// Create an AddressLookup view into this cache.
#[inline]
pub(crate) fn as_lookup(&mut self) -> AddressLookup<'_> {
AddressLookup {
funded: &mut self.funded,
empty: &mut self.empty,
}
}
/// Update transaction counts for addresses.
pub(crate) fn update_tx_counts(
&mut self,
tx_index_vecs: AddressTypeToTypeIndexMap<SmallVec<[TxIndex; 4]>>,
) {
update_tx_counts(&mut self.funded, &mut self.empty, tx_index_vecs);
}
/// Take the cache contents for flushing, leaving empty caches.
pub(crate) fn take(
&mut self,
) -> (
AddressTypeToTypeIndexMap<WithAddressDataSource<EmptyAddressData>>,
AddressTypeToTypeIndexMap<WithAddressDataSource<FundedAddressData>>,
) {
(
std::mem::take(&mut self.empty),
std::mem::take(&mut self.funded),
)
}
}
/// Load address data from storage or create new.
///
/// Returns None if address is already in cache (funded or empty).
#[allow(clippy::too_many_arguments)]
pub(crate) fn load_uncached_address_data(
address_type: OutputType,
type_index: TypeIndex,
first_address_indexes: &ByAddressType<TypeIndex>,
cache: &AddressCache,
vr: &VecsReaders,
any_address_indexes: &AnyAddressIndexesVecs,
addresses_data: &AddressesDataVecs,
) -> Result<Option<WithAddressDataSource<FundedAddressData>>> {
// Check if this is a new address (type_index >= first for this height)
let first = *first_address_indexes.get(address_type).unwrap();
if first <= type_index {
return Ok(Some(WithAddressDataSource::New(
FundedAddressData::default(),
)));
}
// Skip if already in cache
if cache.contains(address_type, type_index) {
return Ok(None);
}
// Read from storage
let reader = vr.address_reader(address_type);
let any_address_index = any_address_indexes.get(address_type, type_index, reader)?;
Ok(Some(match any_address_index.to_enum() {
AnyAddressDataIndexEnum::Funded(funded_index) => {
let reader = &vr.any_address_index_to_any_address_data.funded;
let funded_data = addresses_data
.funded
.get_any_or_read_at(funded_index.into(), reader)?
.unwrap();
WithAddressDataSource::FromFunded(funded_index, funded_data)
}
AnyAddressDataIndexEnum::Empty(empty_index) => {
let reader = &vr.any_address_index_to_any_address_data.empty;
let empty_data = addresses_data
.empty
.get_any_or_read_at(empty_index.into(), reader)?
.unwrap();
WithAddressDataSource::FromEmpty(empty_index, empty_data.into())
}
}))
}
+15 -15
View File
@@ -1,8 +1,8 @@
use brk_types::{EmptyAddressData, FundedAddressData, OutputType, TypeIndex};
use brk_types::{EmptyAddrData, FundedAddrData, OutputType, TypeIndex};
use crate::distribution::address::AddressTypeToTypeIndexMap;
use crate::distribution::addr::AddrTypeToTypeIndexMap;
use super::super::cohort::WithAddressDataSource;
use super::super::cohort::WithAddrDataSource;
/// Tracking status of an address - determines cohort update strategy.
#[derive(Clone, Copy)]
@@ -16,18 +16,18 @@ pub enum TrackingStatus {
}
/// Context for looking up and storing address data during block processing.
pub struct AddressLookup<'a> {
pub funded: &'a mut AddressTypeToTypeIndexMap<WithAddressDataSource<FundedAddressData>>,
pub empty: &'a mut AddressTypeToTypeIndexMap<WithAddressDataSource<EmptyAddressData>>,
pub struct AddrLookup<'a> {
pub funded: &'a mut AddrTypeToTypeIndexMap<WithAddrDataSource<FundedAddrData>>,
pub empty: &'a mut AddrTypeToTypeIndexMap<WithAddrDataSource<EmptyAddrData>>,
}
impl<'a> AddressLookup<'a> {
impl<'a> AddrLookup<'a> {
pub(crate) fn get_or_create_for_receive(
&mut self,
output_type: OutputType,
type_index: TypeIndex,
) -> (
&mut WithAddressDataSource<FundedAddressData>,
&mut WithAddrDataSource<FundedAddrData>,
TrackingStatus,
) {
use std::collections::hash_map::Entry;
@@ -36,7 +36,7 @@ impl<'a> AddressLookup<'a> {
match map.entry(type_index) {
Entry::Occupied(entry) => {
// Address is in cache. Need to determine if it's been processed
// Addr is in cache. Need to determine if it's been processed
// by process_received (added to a cohort) or just funded this block.
//
// - If wrapper is New AND funded_txo_count == 0: hasn't received yet,
@@ -47,15 +47,15 @@ impl<'a> AddressLookup<'a> {
// - If wrapper is FromEmpty AND utxo_count == 0: still empty → WasEmpty
// - If wrapper is FromEmpty AND utxo_count > 0: already received → Tracked
let status = match entry.get() {
WithAddressDataSource::New(data) => {
WithAddrDataSource::New(data) => {
if data.funded_txo_count == 0 {
TrackingStatus::New
} else {
TrackingStatus::Tracked
}
}
WithAddressDataSource::FromFunded(..) => TrackingStatus::Tracked,
WithAddressDataSource::FromEmpty(_, data) => {
WithAddrDataSource::FromFunded(..) => TrackingStatus::Tracked,
WithAddrDataSource::FromEmpty(_, data) => {
if data.utxo_count() == 0 {
TrackingStatus::WasEmpty
} else {
@@ -72,7 +72,7 @@ impl<'a> AddressLookup<'a> {
return (entry.insert(empty_data.into()), TrackingStatus::WasEmpty);
}
(
entry.insert(WithAddressDataSource::New(FundedAddressData::default())),
entry.insert(WithAddrDataSource::New(FundedAddrData::default())),
TrackingStatus::New,
)
}
@@ -84,12 +84,12 @@ impl<'a> AddressLookup<'a> {
&mut self,
output_type: OutputType,
type_index: TypeIndex,
) -> &mut WithAddressDataSource<FundedAddressData> {
) -> &mut WithAddrDataSource<FundedAddrData> {
self.funded
.get_mut(output_type)
.unwrap()
.get_mut(&type_index)
.expect("Address must exist for send")
.expect("Addr must exist for send")
}
/// Move address from funded to empty set.
+2 -2
View File
@@ -1,5 +1,5 @@
mod address;
mod addr;
mod lookup;
pub use address::*;
pub use addr::*;
pub use lookup::*;
@@ -0,0 +1,152 @@
use brk_error::Result;
use brk_types::{
AnyAddrIndex, EmptyAddrData, EmptyAddrIndex, FundedAddrData, FundedAddrIndex,
OutputType, TypeIndex,
};
use vecdb::AnyVec;
use crate::distribution::{AddrTypeToTypeIndexMap, AddrsDataVecs};
use super::with_source::WithAddrDataSource;
/// Process funded address data updates.
///
/// Handles:
/// - New funded address: push to funded storage
/// - Updated funded address (was funded): update in place
/// - Transition empty -> funded: delete from empty, push to funded
pub(crate) fn process_funded_addrs(
addrs_data: &mut AddrsDataVecs,
funded_updates: AddrTypeToTypeIndexMap<WithAddrDataSource<FundedAddrData>>,
) -> Result<AddrTypeToTypeIndexMap<AnyAddrIndex>> {
let total: usize = funded_updates.iter().map(|(_, m)| m.len()).sum();
let mut updates: Vec<(FundedAddrIndex, FundedAddrData)> = Vec::with_capacity(total);
let mut deletes: Vec<EmptyAddrIndex> = Vec::with_capacity(total);
let mut pushes: Vec<(OutputType, TypeIndex, FundedAddrData)> = Vec::with_capacity(total);
for (addr_type, items) in funded_updates.into_iter() {
for (type_index, source) in items {
match source {
WithAddrDataSource::New(data) => {
pushes.push((addr_type, type_index, data));
}
WithAddrDataSource::FromFunded(index, data) => {
updates.push((index, data));
}
WithAddrDataSource::FromEmpty(empty_index, data) => {
deletes.push(empty_index);
pushes.push((addr_type, type_index, data));
}
}
}
}
// Phase 1: Deletes (creates holes)
for empty_index in deletes {
addrs_data.empty.delete(empty_index);
}
// Phase 2: Updates (in-place)
for (index, data) in updates {
addrs_data.funded.update(index, data)?;
}
// Phase 3: Pushes (fill holes first, then pure pushes)
let mut result = AddrTypeToTypeIndexMap::with_capacity(pushes.len() / 4);
let holes_count = addrs_data.funded.holes().len();
let mut pushes_iter = pushes.into_iter();
for (addr_type, type_index, data) in pushes_iter.by_ref().take(holes_count) {
let index = addrs_data.funded.fill_first_hole_or_push(data)?;
result
.get_mut(addr_type)
.unwrap()
.insert(type_index, AnyAddrIndex::from(index));
}
// Pure pushes - no holes remain
addrs_data.funded.reserve_pushed(pushes_iter.len());
let mut next_index = addrs_data.funded.len();
for (addr_type, type_index, data) in pushes_iter {
addrs_data.funded.push(data);
result.get_mut(addr_type).unwrap().insert(
type_index,
AnyAddrIndex::from(FundedAddrIndex::from(next_index)),
);
next_index += 1;
}
Ok(result)
}
/// Process empty address data updates.
///
/// Handles:
/// - New empty address: push to empty storage
/// - Updated empty address (was empty): update in place
/// - Transition funded -> empty: delete from funded, push to empty
pub(crate) fn process_empty_addrs(
addrs_data: &mut AddrsDataVecs,
empty_updates: AddrTypeToTypeIndexMap<WithAddrDataSource<EmptyAddrData>>,
) -> Result<AddrTypeToTypeIndexMap<AnyAddrIndex>> {
let total: usize = empty_updates.iter().map(|(_, m)| m.len()).sum();
let mut updates: Vec<(EmptyAddrIndex, EmptyAddrData)> = Vec::with_capacity(total);
let mut deletes: Vec<FundedAddrIndex> = Vec::with_capacity(total);
let mut pushes: Vec<(OutputType, TypeIndex, EmptyAddrData)> = Vec::with_capacity(total);
for (addr_type, items) in empty_updates.into_iter() {
for (type_index, source) in items {
match source {
WithAddrDataSource::New(data) => {
pushes.push((addr_type, type_index, data));
}
WithAddrDataSource::FromEmpty(index, data) => {
updates.push((index, data));
}
WithAddrDataSource::FromFunded(funded_index, data) => {
deletes.push(funded_index);
pushes.push((addr_type, type_index, data));
}
}
}
}
// Phase 1: Deletes (creates holes)
for funded_index in deletes {
addrs_data.funded.delete(funded_index);
}
// Phase 2: Updates (in-place)
for (index, data) in updates {
addrs_data.empty.update(index, data)?;
}
// Phase 3: Pushes (fill holes first, then pure pushes)
let mut result = AddrTypeToTypeIndexMap::with_capacity(pushes.len() / 4);
let holes_count = addrs_data.empty.holes().len();
let mut pushes_iter = pushes.into_iter();
for (addr_type, type_index, data) in pushes_iter.by_ref().take(holes_count) {
let index = addrs_data.empty.fill_first_hole_or_push(data)?;
result
.get_mut(addr_type)
.unwrap()
.insert(type_index, AnyAddrIndex::from(index));
}
// Pure pushes - no holes remain
addrs_data.empty.reserve_pushed(pushes_iter.len());
let mut next_index = addrs_data.empty.len();
for (addr_type, type_index, data) in pushes_iter {
addrs_data.empty.push(data);
result.get_mut(addr_type).unwrap().insert(
type_index,
AnyAddrIndex::from(EmptyAddrIndex::from(next_index)),
);
next_index += 1;
}
Ok(result)
}
@@ -1,152 +0,0 @@
use brk_error::Result;
use brk_types::{
AnyAddressIndex, EmptyAddressData, EmptyAddressIndex, FundedAddressData, FundedAddressIndex,
OutputType, TypeIndex,
};
use vecdb::AnyVec;
use crate::distribution::{AddressTypeToTypeIndexMap, AddressesDataVecs};
use super::with_source::WithAddressDataSource;
/// Process funded address data updates.
///
/// Handles:
/// - New funded address: push to funded storage
/// - Updated funded address (was funded): update in place
/// - Transition empty -> funded: delete from empty, push to funded
pub(crate) fn process_funded_addresses(
addresses_data: &mut AddressesDataVecs,
funded_updates: AddressTypeToTypeIndexMap<WithAddressDataSource<FundedAddressData>>,
) -> Result<AddressTypeToTypeIndexMap<AnyAddressIndex>> {
let total: usize = funded_updates.iter().map(|(_, m)| m.len()).sum();
let mut updates: Vec<(FundedAddressIndex, FundedAddressData)> = Vec::with_capacity(total);
let mut deletes: Vec<EmptyAddressIndex> = Vec::with_capacity(total);
let mut pushes: Vec<(OutputType, TypeIndex, FundedAddressData)> = Vec::with_capacity(total);
for (address_type, items) in funded_updates.into_iter() {
for (type_index, source) in items {
match source {
WithAddressDataSource::New(data) => {
pushes.push((address_type, type_index, data));
}
WithAddressDataSource::FromFunded(index, data) => {
updates.push((index, data));
}
WithAddressDataSource::FromEmpty(empty_index, data) => {
deletes.push(empty_index);
pushes.push((address_type, type_index, data));
}
}
}
}
// Phase 1: Deletes (creates holes)
for empty_index in deletes {
addresses_data.empty.delete(empty_index);
}
// Phase 2: Updates (in-place)
for (index, data) in updates {
addresses_data.funded.update(index, data)?;
}
// Phase 3: Pushes (fill holes first, then pure pushes)
let mut result = AddressTypeToTypeIndexMap::with_capacity(pushes.len() / 4);
let holes_count = addresses_data.funded.holes().len();
let mut pushes_iter = pushes.into_iter();
for (address_type, type_index, data) in pushes_iter.by_ref().take(holes_count) {
let index = addresses_data.funded.fill_first_hole_or_push(data)?;
result
.get_mut(address_type)
.unwrap()
.insert(type_index, AnyAddressIndex::from(index));
}
// Pure pushes - no holes remain
addresses_data.funded.reserve_pushed(pushes_iter.len());
let mut next_index = addresses_data.funded.len();
for (address_type, type_index, data) in pushes_iter {
addresses_data.funded.push(data);
result.get_mut(address_type).unwrap().insert(
type_index,
AnyAddressIndex::from(FundedAddressIndex::from(next_index)),
);
next_index += 1;
}
Ok(result)
}
/// Process empty address data updates.
///
/// Handles:
/// - New empty address: push to empty storage
/// - Updated empty address (was empty): update in place
/// - Transition funded -> empty: delete from funded, push to empty
pub(crate) fn process_empty_addresses(
addresses_data: &mut AddressesDataVecs,
empty_updates: AddressTypeToTypeIndexMap<WithAddressDataSource<EmptyAddressData>>,
) -> Result<AddressTypeToTypeIndexMap<AnyAddressIndex>> {
let total: usize = empty_updates.iter().map(|(_, m)| m.len()).sum();
let mut updates: Vec<(EmptyAddressIndex, EmptyAddressData)> = Vec::with_capacity(total);
let mut deletes: Vec<FundedAddressIndex> = Vec::with_capacity(total);
let mut pushes: Vec<(OutputType, TypeIndex, EmptyAddressData)> = Vec::with_capacity(total);
for (address_type, items) in empty_updates.into_iter() {
for (type_index, source) in items {
match source {
WithAddressDataSource::New(data) => {
pushes.push((address_type, type_index, data));
}
WithAddressDataSource::FromEmpty(index, data) => {
updates.push((index, data));
}
WithAddressDataSource::FromFunded(funded_index, data) => {
deletes.push(funded_index);
pushes.push((address_type, type_index, data));
}
}
}
}
// Phase 1: Deletes (creates holes)
for funded_index in deletes {
addresses_data.funded.delete(funded_index);
}
// Phase 2: Updates (in-place)
for (index, data) in updates {
addresses_data.empty.update(index, data)?;
}
// Phase 3: Pushes (fill holes first, then pure pushes)
let mut result = AddressTypeToTypeIndexMap::with_capacity(pushes.len() / 4);
let holes_count = addresses_data.empty.holes().len();
let mut pushes_iter = pushes.into_iter();
for (address_type, type_index, data) in pushes_iter.by_ref().take(holes_count) {
let index = addresses_data.empty.fill_first_hole_or_push(data)?;
result
.get_mut(address_type)
.unwrap()
.insert(type_index, AnyAddressIndex::from(index));
}
// Pure pushes - no holes remain
addresses_data.empty.reserve_pushed(pushes_iter.len());
let mut next_index = addresses_data.empty.len();
for (address_type, type_index, data) in pushes_iter {
addresses_data.empty.push(data);
result.get_mut(address_type).unwrap().insert(
type_index,
AnyAddressIndex::from(EmptyAddressIndex::from(next_index)),
);
next_index += 1;
}
Ok(result)
}
@@ -1,10 +1,10 @@
mod address_updates;
mod addr_updates;
mod received;
mod sent;
mod tx_counts;
mod with_source;
pub(crate) use address_updates::*;
pub(crate) use addr_updates::*;
pub(crate) use received::*;
pub(crate) use sent::*;
pub(crate) use tx_counts::*;
@@ -1,13 +1,13 @@
use brk_cohort::{AmountBucket, ByAddressType};
use brk_cohort::{AmountBucket, ByAddrType};
use brk_types::{Cents, Sats, TypeIndex};
use rustc_hash::FxHashMap;
use crate::distribution::{
address::{AddressTypeToActivityCounts, AddressTypeToVec},
cohorts::AddressCohorts,
addr::{AddrTypeToActivityCounts, AddrTypeToVec},
cohorts::AddrCohorts,
};
use super::super::cache::{AddressLookup, TrackingStatus};
use super::super::cache::{AddrLookup, TrackingStatus};
/// Aggregated receive data for a single address within a block.
#[derive(Default)]
@@ -18,13 +18,13 @@ struct AggregatedReceive {
#[allow(clippy::too_many_arguments)]
pub(crate) fn process_received(
received_data: AddressTypeToVec<(TypeIndex, Sats)>,
cohorts: &mut AddressCohorts,
lookup: &mut AddressLookup<'_>,
received_data: AddrTypeToVec<(TypeIndex, Sats)>,
cohorts: &mut AddrCohorts,
lookup: &mut AddrLookup<'_>,
price: Cents,
address_count: &mut ByAddressType<u64>,
empty_address_count: &mut ByAddressType<u64>,
activity_counts: &mut AddressTypeToActivityCounts,
addr_count: &mut ByAddrType<u64>,
empty_addr_count: &mut ByAddrType<u64>,
activity_counts: &mut AddrTypeToActivityCounts,
) {
let max_type_len = received_data.iter().map(|(_, v)| v.len()).max().unwrap_or(0);
let mut aggregated: FxHashMap<TypeIndex, AggregatedReceive> =
@@ -36,8 +36,8 @@ pub(crate) fn process_received(
}
// Cache mutable refs for this address type
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_addr_count = addr_count.get_mut(output_type).unwrap();
let type_empty_count = empty_addr_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_address_count += 1;
*type_addr_count += 1;
}
TrackingStatus::WasEmpty => {
*type_address_count += 1;
*type_addr_count += 1;
*type_empty_count -= 1;
// Reactivated - was empty, now has funds
type_activity.reactivated += 1;
@@ -100,7 +100,7 @@ pub(crate) fn process_received(
"process_received: cohort underflow detected!\n\
output_type={:?}, type_index={:?}\n\
prev_balance={}, new_balance={}, total_value={}\n\
Address: {:?}",
Addr: {:?}",
output_type,
type_index,
prev_balance,
@@ -1,16 +1,16 @@
use brk_cohort::{AmountBucket, ByAddressType};
use brk_cohort::{AmountBucket, ByAddrType};
use brk_error::Result;
use brk_types::{Age, Cents, CheckedSub, Height, Sats, Timestamp, TypeIndex};
use rustc_hash::FxHashSet;
use vecdb::VecIndex;
use crate::distribution::{
address::{AddressTypeToActivityCounts, HeightToAddressTypeToVec},
cohorts::AddressCohorts,
addr::{AddrTypeToActivityCounts, HeightToAddrTypeToVec},
cohorts::AddrCohorts,
compute::PriceRangeMax,
};
use super::super::cache::AddressLookup;
use super::super::cache::AddrLookup;
/// Process sent outputs for address cohorts.
///
@@ -27,20 +27,20 @@ use super::super::cache::AddressLookup;
/// for accurate peak regret calculation.
#[allow(clippy::too_many_arguments)]
pub(crate) fn process_sent(
sent_data: HeightToAddressTypeToVec<(TypeIndex, Sats)>,
cohorts: &mut AddressCohorts,
lookup: &mut AddressLookup<'_>,
sent_data: HeightToAddrTypeToVec<(TypeIndex, Sats)>,
cohorts: &mut AddrCohorts,
lookup: &mut AddrLookup<'_>,
current_price: Cents,
price_range_max: &PriceRangeMax,
address_count: &mut ByAddressType<u64>,
empty_address_count: &mut ByAddressType<u64>,
activity_counts: &mut AddressTypeToActivityCounts,
received_addresses: &ByAddressType<FxHashSet<TypeIndex>>,
addr_count: &mut ByAddrType<u64>,
empty_addr_count: &mut ByAddrType<u64>,
activity_counts: &mut AddrTypeToActivityCounts,
received_addrs: &ByAddrType<FxHashSet<TypeIndex>>,
height_to_price: &[Cents],
height_to_timestamp: &[Timestamp],
current_height: Height,
current_timestamp: Timestamp,
seen_senders: &mut ByAddressType<FxHashSet<TypeIndex>>,
seen_senders: &mut ByAddrType<FxHashSet<TypeIndex>>,
) -> Result<()> {
seen_senders.values_mut().for_each(|set| set.clear());
@@ -54,10 +54,10 @@ pub(crate) fn process_sent(
for (output_type, vec) in by_type.unwrap().into_iter() {
// Cache mutable refs for this address type
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_addr_count = addr_count.get_mut(output_type).unwrap();
let type_empty_count = empty_addr_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_received = received_addrs.get(output_type);
let type_seen = seen_senders.get_mut_unwrap(output_type);
for (type_index, value) in vec {
@@ -99,7 +99,7 @@ pub(crate) fn process_sent(
// Migrate address to new bucket or mark as empty
if will_be_empty {
*type_address_count -= 1;
*type_addr_count -= 1;
*type_empty_count += 1;
lookup.move_to_empty(output_type, type_index);
} else if crossing_boundary {
@@ -1,9 +1,9 @@
use brk_types::{EmptyAddressData, FundedAddressData, TxIndex};
use brk_types::{EmptyAddrData, FundedAddrData, TxIndex};
use smallvec::SmallVec;
use crate::distribution::address::AddressTypeToTypeIndexMap;
use crate::distribution::addr::AddrTypeToTypeIndexMap;
use super::with_source::WithAddressDataSource;
use super::with_source::WithAddrDataSource;
/// Update tx_count for addresses based on unique transactions they participated in.
///
@@ -14,9 +14,9 @@ use super::with_source::WithAddressDataSource;
/// Addresses are looked up in funded_cache first, then empty_cache.
/// NOTE: This should be called AFTER merging parallel-fetched address data into funded_cache.
pub(crate) fn update_tx_counts(
funded_cache: &mut AddressTypeToTypeIndexMap<WithAddressDataSource<FundedAddressData>>,
empty_cache: &mut AddressTypeToTypeIndexMap<WithAddressDataSource<EmptyAddressData>>,
mut tx_index_vecs: AddressTypeToTypeIndexMap<SmallVec<[TxIndex; 4]>>,
funded_cache: &mut AddrTypeToTypeIndexMap<WithAddrDataSource<FundedAddrData>>,
empty_cache: &mut AddrTypeToTypeIndexMap<WithAddrDataSource<EmptyAddrData>>,
mut tx_index_vecs: AddrTypeToTypeIndexMap<SmallVec<[TxIndex; 4]>>,
) {
// First, deduplicate tx_index_vecs for addresses that appear multiple times in a block
for (_, map) in tx_index_vecs.iter_mut() {
@@ -29,20 +29,20 @@ pub(crate) fn update_tx_counts(
}
// Update tx_count on address data
for (address_type, type_index, tx_index_vec) in tx_index_vecs
for (addr_type, type_index, tx_index_vec) in tx_index_vecs
.into_iter()
.flat_map(|(t, m)| m.into_iter().map(move |(i, v)| (t, i, v)))
{
let tx_count = tx_index_vec.len() as u32;
if let Some(addr_data) = funded_cache
.get_mut(address_type)
.get_mut(addr_type)
.unwrap()
.get_mut(&type_index)
{
addr_data.tx_count += tx_count;
} else if let Some(addr_data) = empty_cache
.get_mut(address_type)
.get_mut(addr_type)
.unwrap()
.get_mut(&type_index)
{
@@ -1,20 +1,20 @@
use brk_types::{EmptyAddressData, EmptyAddressIndex, FundedAddressData, FundedAddressIndex};
use brk_types::{EmptyAddrData, EmptyAddrIndex, FundedAddrData, FundedAddrIndex};
/// Address data wrapped with its source location for flush operations.
///
/// This enum tracks where the data came from so it can be correctly
/// updated or created during the flush phase.
#[derive(Debug, Clone)]
pub enum WithAddressDataSource<T> {
pub enum WithAddrDataSource<T> {
/// Brand new address (never seen before)
New(T),
/// Funded from funded address storage (with original index)
FromFunded(FundedAddressIndex, T),
FromFunded(FundedAddrIndex, T),
/// Funded from empty address storage (with original index)
FromEmpty(EmptyAddressIndex, T),
FromEmpty(EmptyAddrIndex, T),
}
impl<T> std::ops::Deref for WithAddressDataSource<T> {
impl<T> std::ops::Deref for WithAddrDataSource<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
@@ -24,7 +24,7 @@ impl<T> std::ops::Deref for WithAddressDataSource<T> {
}
}
impl<T> std::ops::DerefMut for WithAddressDataSource<T> {
impl<T> std::ops::DerefMut for WithAddrDataSource<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
match self {
Self::New(v) | Self::FromFunded(_, v) | Self::FromEmpty(_, v) => v,
@@ -32,24 +32,24 @@ impl<T> std::ops::DerefMut for WithAddressDataSource<T> {
}
}
impl From<WithAddressDataSource<EmptyAddressData>> for WithAddressDataSource<FundedAddressData> {
impl From<WithAddrDataSource<EmptyAddrData>> for WithAddrDataSource<FundedAddrData> {
#[inline]
fn from(value: WithAddressDataSource<EmptyAddressData>) -> Self {
fn from(value: WithAddrDataSource<EmptyAddrData>) -> Self {
match value {
WithAddressDataSource::New(v) => Self::New(v.into()),
WithAddressDataSource::FromFunded(i, v) => Self::FromFunded(i, v.into()),
WithAddressDataSource::FromEmpty(i, v) => Self::FromEmpty(i, v.into()),
WithAddrDataSource::New(v) => Self::New(v.into()),
WithAddrDataSource::FromFunded(i, v) => Self::FromFunded(i, v.into()),
WithAddrDataSource::FromEmpty(i, v) => Self::FromEmpty(i, v.into()),
}
}
}
impl From<WithAddressDataSource<FundedAddressData>> for WithAddressDataSource<EmptyAddressData> {
impl From<WithAddrDataSource<FundedAddrData>> for WithAddrDataSource<EmptyAddrData> {
#[inline]
fn from(value: WithAddressDataSource<FundedAddressData>) -> Self {
fn from(value: WithAddrDataSource<FundedAddrData>) -> Self {
match value {
WithAddressDataSource::New(v) => Self::New(v.into()),
WithAddressDataSource::FromFunded(i, v) => Self::FromFunded(i, v.into()),
WithAddressDataSource::FromEmpty(i, v) => Self::FromEmpty(i, v.into()),
WithAddrDataSource::New(v) => Self::New(v.into()),
WithAddrDataSource::FromFunded(i, v) => Self::FromFunded(i, v.into()),
WithAddrDataSource::FromEmpty(i, v) => Self::FromEmpty(i, v.into()),
}
}
}
@@ -1,21 +1,21 @@
use brk_cohort::ByAddressType;
use brk_cohort::ByAddrType;
use brk_error::Result;
use brk_types::{FundedAddressData, Height, OutputType, Sats, TxIndex, TypeIndex};
use brk_types::{FundedAddrData, Height, OutputType, Sats, TxIndex, TypeIndex};
use rayon::prelude::*;
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
use crate::distribution::{
address::{AddressTypeToTypeIndexMap, AddressesDataVecs, AnyAddressIndexesVecs},
addr::{AddrTypeToTypeIndexMap, AddrsDataVecs, AnyAddrIndexesVecs},
compute::VecsReaders,
state::Transacted,
};
use crate::distribution::address::HeightToAddressTypeToVec;
use crate::distribution::addr::HeightToAddrTypeToVec;
use super::super::{
cache::{AddressCache, load_uncached_address_data},
cohort::WithAddressDataSource,
cache::{AddrCache, load_uncached_addr_data},
cohort::WithAddrDataSource,
};
/// Result of processing inputs for a block.
@@ -23,11 +23,11 @@ pub struct InputsResult {
/// Map from UTXO creation height -> aggregated sent supply.
pub height_to_sent: FxHashMap<Height, Transacted>,
/// Per-height, per-address-type sent data: (type_index, value) for each address.
pub sent_data: HeightToAddressTypeToVec<(TypeIndex, Sats)>,
/// Address data looked up during processing, keyed by (address_type, type_index).
pub address_data: AddressTypeToTypeIndexMap<WithAddressDataSource<FundedAddressData>>,
pub sent_data: HeightToAddrTypeToVec<(TypeIndex, Sats)>,
/// Address data looked up during processing, keyed by (addr_type, type_index).
pub addr_data: AddrTypeToTypeIndexMap<WithAddrDataSource<FundedAddrData>>,
/// Transaction indexes per address for tx_count tracking.
pub tx_index_vecs: AddressTypeToTypeIndexMap<SmallVec<[TxIndex; 4]>>,
pub tx_index_vecs: AddrTypeToTypeIndexMap<SmallVec<[TxIndex; 4]>>,
}
/// Process inputs (spent UTXOs) for a block.
@@ -51,11 +51,11 @@ pub(crate) fn process_inputs(
txin_index_to_output_type: &[OutputType],
txin_index_to_type_index: &[TypeIndex],
txin_index_to_prev_height: &[Height],
first_address_indexes: &ByAddressType<TypeIndex>,
cache: &AddressCache,
first_addr_indexes: &ByAddrType<TypeIndex>,
cache: &AddrCache,
vr: &VecsReaders,
any_address_indexes: &AnyAddressIndexesVecs,
addresses_data: &AddressesDataVecs,
any_addr_indexes: &AnyAddrIndexesVecs,
addrs_data: &AddrsDataVecs,
) -> Result<InputsResult> {
let map_fn = |local_idx: usize| -> Result<_> {
let tx_index = txin_index_to_tx_index[local_idx];
@@ -64,21 +64,21 @@ pub(crate) fn process_inputs(
let value = txin_index_to_value[local_idx];
let input_type = txin_index_to_output_type[local_idx];
if input_type.is_not_address() {
if input_type.is_not_addr() {
return Ok((prev_height, value, input_type, None));
}
let type_index = txin_index_to_type_index[local_idx];
// Look up address data
let addr_data_opt = load_uncached_address_data(
let addr_data_opt = load_uncached_addr_data(
input_type,
type_index,
first_address_indexes,
first_addr_indexes,
cache,
vr,
any_address_indexes,
addresses_data,
any_addr_indexes,
addrs_data,
)?;
Ok((
@@ -108,13 +108,13 @@ pub(crate) fn process_inputs(
estimated_unique_heights,
Default::default(),
);
let mut sent_data = HeightToAddressTypeToVec::with_capacity(estimated_unique_heights);
let mut address_data =
AddressTypeToTypeIndexMap::<WithAddressDataSource<FundedAddressData>>::with_capacity(
let mut sent_data = HeightToAddrTypeToVec::with_capacity(estimated_unique_heights);
let mut addr_data =
AddrTypeToTypeIndexMap::<WithAddrDataSource<FundedAddrData>>::with_capacity(
estimated_per_type,
);
let mut tx_index_vecs =
AddressTypeToTypeIndexMap::<SmallVec<[TxIndex; 4]>>::with_capacity(estimated_per_type);
AddrTypeToTypeIndexMap::<SmallVec<[TxIndex; 4]>>::with_capacity(estimated_per_type);
for (prev_height, value, output_type, addr_info) in items {
height_to_sent
@@ -130,8 +130,8 @@ pub(crate) fn process_inputs(
.unwrap()
.push((type_index, value));
if let Some(addr_data) = addr_data_opt {
address_data.insert_for_type(output_type, type_index, addr_data);
if let Some(source) = addr_data_opt {
addr_data.insert_for_type(output_type, type_index, source);
}
tx_index_vecs
@@ -146,7 +146,7 @@ pub(crate) fn process_inputs(
Ok(InputsResult {
height_to_sent,
sent_data,
address_data,
addr_data,
tx_index_vecs,
})
}
@@ -1,20 +1,20 @@
use brk_cohort::ByAddressType;
use brk_cohort::ByAddrType;
use brk_error::Result;
use brk_types::{FundedAddressData, Sats, TxIndex, TypeIndex};
use brk_types::{FundedAddrData, Sats, TxIndex, TypeIndex};
use rayon::prelude::*;
use smallvec::SmallVec;
use crate::distribution::{
address::{
AddressTypeToTypeIndexMap, AddressTypeToVec, AddressesDataVecs, AnyAddressIndexesVecs,
addr::{
AddrTypeToTypeIndexMap, AddrTypeToVec, AddrsDataVecs, AnyAddrIndexesVecs,
},
compute::{TxOutData, VecsReaders},
state::Transacted,
};
use super::super::{
cache::{AddressCache, load_uncached_address_data},
cohort::WithAddressDataSource,
cache::{AddrCache, load_uncached_addr_data},
cohort::WithAddrDataSource,
};
/// Result of processing outputs for a block.
@@ -22,11 +22,11 @@ pub struct OutputsResult {
/// Aggregated supply transacted in this block.
pub transacted: Transacted,
/// Per-address-type received data: (type_index, value) for each address.
pub received_data: AddressTypeToVec<(TypeIndex, Sats)>,
/// Address data looked up during processing, keyed by (address_type, type_index).
pub address_data: AddressTypeToTypeIndexMap<WithAddressDataSource<FundedAddressData>>,
pub received_data: AddrTypeToVec<(TypeIndex, Sats)>,
/// Address data looked up during processing, keyed by (addr_type, type_index).
pub addr_data: AddrTypeToTypeIndexMap<WithAddrDataSource<FundedAddrData>>,
/// Transaction indexes per address for tx_count tracking.
pub tx_index_vecs: AddressTypeToTypeIndexMap<SmallVec<[TxIndex; 4]>>,
pub tx_index_vecs: AddrTypeToTypeIndexMap<SmallVec<[TxIndex; 4]>>,
}
/// Process outputs (new UTXOs) for a block.
@@ -40,35 +40,35 @@ pub struct OutputsResult {
pub(crate) fn process_outputs(
txout_index_to_tx_index: &[TxIndex],
txout_data_vec: &[TxOutData],
first_address_indexes: &ByAddressType<TypeIndex>,
cache: &AddressCache,
first_addr_indexes: &ByAddrType<TypeIndex>,
cache: &AddrCache,
vr: &VecsReaders,
any_address_indexes: &AnyAddressIndexesVecs,
addresses_data: &AddressesDataVecs,
any_addr_indexes: &AnyAddrIndexesVecs,
addrs_data: &AddrsDataVecs,
) -> Result<OutputsResult> {
let output_count = txout_data_vec.len();
// Phase 1: Address lookups (mmap reads) — parallel for large blocks, sequential for small
// Phase 1: Addr lookups (mmap reads) — parallel for large blocks, sequential for small
let map_fn = |local_idx: usize| -> Result<_> {
let txout_data = &txout_data_vec[local_idx];
let value = txout_data.value;
let output_type = txout_data.output_type;
if output_type.is_not_address() {
if output_type.is_not_addr() {
return Ok((value, output_type, None));
}
let type_index = txout_data.type_index;
let tx_index = txout_index_to_tx_index[local_idx];
let addr_data_opt = load_uncached_address_data(
let addr_data_opt = load_uncached_addr_data(
output_type,
type_index,
first_address_indexes,
first_addr_indexes,
cache,
vr,
any_address_indexes,
addresses_data,
any_addr_indexes,
addrs_data,
)?;
Ok((
@@ -92,13 +92,13 @@ pub(crate) fn process_outputs(
// Phase 2: Sequential accumulation
let estimated_per_type = (output_count / 8).max(8);
let mut transacted = Transacted::default();
let mut received_data = AddressTypeToVec::with_capacity(estimated_per_type);
let mut address_data =
AddressTypeToTypeIndexMap::<WithAddressDataSource<FundedAddressData>>::with_capacity(
let mut received_data = AddrTypeToVec::with_capacity(estimated_per_type);
let mut addr_data =
AddrTypeToTypeIndexMap::<WithAddrDataSource<FundedAddrData>>::with_capacity(
estimated_per_type,
);
let mut tx_index_vecs =
AddressTypeToTypeIndexMap::<SmallVec<[TxIndex; 4]>>::with_capacity(estimated_per_type);
AddrTypeToTypeIndexMap::<SmallVec<[TxIndex; 4]>>::with_capacity(estimated_per_type);
for (value, output_type, addr_info) in items {
transacted.iterate(value, output_type);
@@ -109,8 +109,8 @@ pub(crate) fn process_outputs(
.unwrap()
.push((type_index, value));
if let Some(addr_data) = addr_data_opt {
address_data.insert_for_type(output_type, type_index, addr_data);
if let Some(source) = addr_data_opt {
addr_data.insert_for_type(output_type, type_index, source);
}
tx_index_vecs
@@ -125,7 +125,7 @@ pub(crate) fn process_outputs(
Ok(OutputsResult {
transacted,
received_data,
address_data,
addr_data,
tx_index_vecs,
})
}