diff --git a/Cargo.lock b/Cargo.lock index db77bb697..966dc9f67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4218,8 +4218,6 @@ dependencies = [ [[package]] name = "rawdb" version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48b018c8650c492e984d9f6bda52fff0742e1b91ebd97a8ea7046bc12cf1e321" dependencies = [ "libc", "log", @@ -5410,8 +5408,6 @@ checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23" [[package]] name = "vecdb" version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc5945ac4513223cdff7cb954725adfd8941c4dddae3e8765ad2faad26d11903" dependencies = [ "ctrlc", "log", @@ -5419,6 +5415,7 @@ dependencies = [ "parking_lot", "pco", "rawdb", + "schemars", "serde", "serde_json", "thiserror 2.0.17", @@ -5430,8 +5427,6 @@ dependencies = [ [[package]] name = "vecdb_derive" version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f901bd1eab67e00047ed19fcc1e0550b0073716c972866ce06bcecee74f0096" dependencies = [ "quote", "syn 2.0.111", diff --git a/Cargo.toml b/Cargo.toml index fac46d404..298e6692b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,8 +79,8 @@ serde_derive = "1.0.228" serde_json = { version = "1.0.145", features = ["float_roundtrip"] } smallvec = "1.15.1" tokio = { version = "1.48.0", features = ["rt-multi-thread"] } -vecdb = { version = "0.4.4", features = ["derive", "serde_json", "pco"] } -# vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] } +# vecdb = { version = "0.4.4", features = ["derive", "serde_json", "pco"] } +vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] } # vecdb = { git = "https://github.com/anydb-rs/anydb", features = ["derive", "serde_json", "pco"] } [workspace.metadata.release] diff --git a/crates/brk_computer/src/grouped/price_percentiles.rs b/crates/brk_computer/src/grouped/price_percentiles.rs index 0a6b2f4d3..4882ce6c2 100644 --- a/crates/brk_computer/src/grouped/price_percentiles.rs +++ b/crates/brk_computer/src/grouped/price_percentiles.rs @@ -1,7 +1,10 @@ use brk_error::Result; use brk_traversable::{Traversable, TreeNode}; use brk_types::{DateIndex, Dollars, Version}; -use vecdb::{AnyExportableVec, AnyStoredVec, Database, EagerVec, Exit, GenericStoredVec, PcoVec}; +use rayon::prelude::*; +use vecdb::{ + AnyExportableVec, AnyStoredVec, Database, EagerVec, Exit, GenericStoredVec, PcoVec, +}; use crate::{Indexes, indexes}; @@ -91,6 +94,17 @@ impl PricePercentiles { Ok(()) } + /// Returns a parallel iterator over all vecs for parallel writing. + pub fn par_iter_mut(&mut self) -> impl ParallelIterator { + self.vecs + .iter_mut() + .flatten() + .filter_map(|v| v.dateindex.as_mut()) + .map(|v| v as &mut dyn AnyStoredVec) + .collect::>() + .into_par_iter() + } + /// Validate computed versions or reset if mismatched. pub fn validate_computed_version_or_reset(&mut self, version: Version) -> Result<()> { for vec in self.vecs.iter_mut().flatten() { diff --git a/crates/brk_computer/src/stateful/address/address_count.rs b/crates/brk_computer/src/stateful/address/address_count.rs index 51790a047..eee01ff86 100644 --- a/crates/brk_computer/src/stateful/address/address_count.rs +++ b/crates/brk_computer/src/stateful/address/address_count.rs @@ -5,6 +5,7 @@ use brk_grouper::ByAddressType; use brk_traversable::Traversable; use brk_types::{Height, StoredU64, Version}; use derive_deref::{Deref, DerefMut}; +use rayon::prelude::*; use vecdb::{ AnyStoredVec, AnyVec, Database, EagerVec, Exit, GenericStoredVec, ImportableVec, PcoVec, TypedVecIterator, @@ -88,6 +89,22 @@ impl AddressTypeToHeightToAddressCount { Ok(()) } + /// Returns a parallel iterator over all vecs for parallel writing. + pub fn par_iter_mut(&mut self) -> impl ParallelIterator { + let inner = &mut self.0; + [ + &mut inner.p2pk65 as &mut dyn AnyStoredVec, + &mut inner.p2pk33 as &mut dyn AnyStoredVec, + &mut inner.p2pkh as &mut dyn AnyStoredVec, + &mut inner.p2sh as &mut dyn AnyStoredVec, + &mut inner.p2wpkh as &mut dyn AnyStoredVec, + &mut inner.p2wsh as &mut dyn AnyStoredVec, + &mut inner.p2tr as &mut dyn AnyStoredVec, + &mut inner.p2a as &mut dyn AnyStoredVec, + ] + .into_par_iter() + } + pub fn safe_write(&mut self, exit: &Exit) -> Result<()> { self.p2pk65.safe_write(exit)?; self.p2pk33.safe_write(exit)?; diff --git a/crates/brk_computer/src/stateful/address/any_address_indexes.rs b/crates/brk_computer/src/stateful/address/any_address_indexes.rs index 271786f2b..dcef48ec3 100644 --- a/crates/brk_computer/src/stateful/address/any_address_indexes.rs +++ b/crates/brk_computer/src/stateful/address/any_address_indexes.rs @@ -7,6 +7,7 @@ use brk_types::{ P2PKHAddressIndex, P2SHAddressIndex, P2TRAddressIndex, P2WPKHAddressIndex, P2WSHAddressIndex, TypeIndex, Version, }; +use rayon::prelude::*; use vecdb::{ AnyStoredVec, BytesVec, Database, GenericStoredVec, ImportOptions, ImportableVec, Reader, Stamp, }; @@ -81,6 +82,11 @@ macro_rules! define_any_address_indexes_vecs { $(self.$field.stamped_write_maybe_with_changes(stamp, with_changes)?;)* Ok(()) } + + /// Returns a parallel iterator over all vecs for parallel writing. + pub fn par_iter_mut(&mut self) -> impl ParallelIterator { + vec![$(&mut self.$field as &mut dyn AnyStoredVec),*].into_par_iter() + } } }; } diff --git a/crates/brk_computer/src/stateful/address/data.rs b/crates/brk_computer/src/stateful/address/data.rs index f8310d6e7..b3568cd45 100644 --- a/crates/brk_computer/src/stateful/address/data.rs +++ b/crates/brk_computer/src/stateful/address/data.rs @@ -5,6 +5,7 @@ use brk_traversable::Traversable; use brk_types::{ EmptyAddressData, EmptyAddressIndex, Height, LoadedAddressData, LoadedAddressIndex, Version, }; +use rayon::prelude::*; use vecdb::{ AnyStoredVec, BytesVec, Database, GenericStoredVec, ImportOptions, ImportableVec, Stamp, }; @@ -63,4 +64,13 @@ impl AddressesDataVecs { .stamped_write_maybe_with_changes(stamp, with_changes)?; Ok(()) } + + /// Returns a parallel iterator over all vecs for parallel writing. + pub fn par_iter_mut(&mut self) -> impl ParallelIterator { + vec![ + &mut self.loaded as &mut dyn AnyStoredVec, + &mut self.empty as &mut dyn AnyStoredVec, + ] + .into_par_iter() + } } diff --git a/crates/brk_computer/src/stateful/cohorts/address.rs b/crates/brk_computer/src/stateful/cohorts/address.rs index 1da0f4a22..0175d764c 100644 --- a/crates/brk_computer/src/stateful/cohorts/address.rs +++ b/crates/brk_computer/src/stateful/cohorts/address.rs @@ -6,6 +6,7 @@ use brk_error::Result; use brk_grouper::{CohortContext, Filter, Filtered}; use brk_traversable::Traversable; use brk_types::{Bitcoin, DateIndex, Dollars, Height, StoredU64, Version}; +use rayon::prelude::*; use vecdb::{ AnyStoredVec, AnyVec, Database, EagerVec, Exit, GenericStoredVec, ImportableVec, IterableVec, PcoVec, @@ -113,6 +114,20 @@ impl AddressCohortVecs { .min(self.metrics.supply.min_len()) .min(self.metrics.activity.min_len()) } + + /// Returns a parallel iterator over all vecs for parallel writing. + pub fn par_iter_vecs_mut(&mut self) -> impl ParallelIterator { + rayon::iter::once(&mut self.height_to_addr_count as &mut dyn AnyStoredVec) + .chain(self.metrics.par_iter_mut()) + } + + /// Commit state to disk (separate from vec writes for parallelization). + pub fn write_state(&mut self, height: Height, cleanup: bool) -> Result<()> { + if let Some(state) = self.state.as_mut() { + state.inner.write(height, cleanup)?; + } + Ok(()) + } } impl Filtered for AddressCohortVecs { @@ -224,17 +239,6 @@ impl DynCohortVecs for AddressCohortVecs { Ok(()) } - fn write_stateful_vecs(&mut self, height: Height) -> Result<()> { - self.height_to_addr_count.write()?; - self.metrics.write()?; - - if let Some(state) = self.state.as_mut() { - state.inner.commit(height)?; - } - - Ok(()) - } - fn compute_rest_part1( &mut self, indexes: &indexes::Vecs, diff --git a/crates/brk_computer/src/stateful/cohorts/address_cohorts.rs b/crates/brk_computer/src/stateful/cohorts/address_cohorts.rs index 3bf48ddde..03dde5fbb 100644 --- a/crates/brk_computer/src/stateful/cohorts/address_cohorts.rs +++ b/crates/brk_computer/src/stateful/cohorts/address_cohorts.rs @@ -11,7 +11,7 @@ use brk_traversable::Traversable; use brk_types::{Bitcoin, DateIndex, Dollars, Height, Sats, Version}; use derive_deref::{Deref, DerefMut}; use rayon::prelude::*; -use vecdb::{Database, Exit, IterableVec}; +use vecdb::{AnyStoredVec, Database, Exit, IterableVec}; use crate::{Indexes, indexes, price, stateful::DynCohortVecs}; @@ -222,10 +222,20 @@ impl AddressCohorts { }) } - /// Write stateful vectors for separate cohorts. - pub fn write_stateful_vecs(&mut self, height: Height) -> Result<()> { + /// Returns a parallel iterator over all vecs for parallel writing. + pub fn par_iter_vecs_mut(&mut self) -> impl ParallelIterator { + // Collect all vecs from all cohorts + self.0 + .iter_mut() + .flat_map(|v| v.par_iter_vecs_mut().collect::>()) + .collect::>() + .into_par_iter() + } + + /// Commit all states to disk (separate from vec writes for parallelization). + pub fn commit_all_states(&mut self, height: Height, cleanup: bool) -> Result<()> { self.par_iter_separate_mut() - .try_for_each(|v| v.write_stateful_vecs(height)) + .try_for_each(|v| v.write_state(height, cleanup)) } /// Get minimum height from all separate cohorts' height-indexed vectors. diff --git a/crates/brk_computer/src/stateful/cohorts/traits.rs b/crates/brk_computer/src/stateful/cohorts/traits.rs index 6b1277567..2cc878793 100644 --- a/crates/brk_computer/src/stateful/cohorts/traits.rs +++ b/crates/brk_computer/src/stateful/cohorts/traits.rs @@ -34,9 +34,6 @@ pub trait DynCohortVecs: Send + Sync { date_price: Option>, ) -> Result<()>; - /// Write stateful vectors to disk. - fn write_stateful_vecs(&mut self, height: Height) -> Result<()>; - /// First phase of post-processing computations. #[allow(clippy::too_many_arguments)] fn compute_rest_part1( diff --git a/crates/brk_computer/src/stateful/cohorts/utxo.rs b/crates/brk_computer/src/stateful/cohorts/utxo.rs index 4aed89d60..b5cf24b1c 100644 --- a/crates/brk_computer/src/stateful/cohorts/utxo.rs +++ b/crates/brk_computer/src/stateful/cohorts/utxo.rs @@ -6,7 +6,8 @@ use brk_error::Result; use brk_grouper::{CohortContext, Filter, Filtered, StateLevel}; use brk_traversable::Traversable; use brk_types::{Bitcoin, DateIndex, Dollars, Height, Version}; -use vecdb::{Database, Exit, IterableVec}; +use rayon::prelude::*; +use vecdb::{AnyStoredVec, Database, Exit, IterableVec}; use crate::{ Indexes, indexes, price, @@ -87,6 +88,19 @@ impl UTXOCohortVecs { state.reset(); } } + + /// Returns a parallel iterator over all vecs for parallel writing. + pub fn par_iter_vecs_mut(&mut self) -> impl ParallelIterator { + self.metrics.par_iter_mut() + } + + /// Commit state to disk (separate from vec writes for parallelization). + pub fn write_state(&mut self, height: Height, cleanup: bool) -> Result<()> { + if let Some(state) = self.state.as_mut() { + state.write(height, cleanup)?; + } + Ok(()) + } } impl Filtered for UTXOCohortVecs { @@ -189,16 +203,6 @@ impl DynCohortVecs for UTXOCohortVecs { Ok(()) } - fn write_stateful_vecs(&mut self, height: Height) -> Result<()> { - self.metrics.write()?; - - if let Some(state) = self.state.as_mut() { - state.commit(height)?; - } - - Ok(()) - } - fn compute_rest_part1( &mut self, indexes: &indexes::Vecs, diff --git a/crates/brk_computer/src/stateful/cohorts/utxo_cohorts/mod.rs b/crates/brk_computer/src/stateful/cohorts/utxo_cohorts/mod.rs index 516b986d7..c3e235686 100644 --- a/crates/brk_computer/src/stateful/cohorts/utxo_cohorts/mod.rs +++ b/crates/brk_computer/src/stateful/cohorts/utxo_cohorts/mod.rs @@ -9,16 +9,18 @@ use std::path::Path; use brk_error::Result; use brk_grouper::{ AmountFilter, ByAgeRange, ByAmountRange, ByEpoch, ByGreatEqualAmount, ByLowerThanAmount, - ByMaxAge, ByMinAge, BySpendableType, ByTerm, ByYear, Filter, Filtered, StateLevel, Term, - TimeFilter, UTXOGroups, DAYS_10Y, DAYS_12Y, DAYS_15Y, DAYS_1D, DAYS_1M, DAYS_1W, DAYS_1Y, + ByMaxAge, ByMinAge, BySpendableType, ByTerm, ByYear, DAYS_1D, DAYS_1M, DAYS_1W, DAYS_1Y, DAYS_2M, DAYS_2Y, DAYS_3M, DAYS_3Y, DAYS_4M, DAYS_4Y, DAYS_5M, DAYS_5Y, DAYS_6M, DAYS_6Y, - DAYS_7Y, DAYS_8Y, + DAYS_7Y, DAYS_8Y, DAYS_10Y, DAYS_12Y, DAYS_15Y, Filter, Filtered, StateLevel, Term, TimeFilter, + UTXOGroups, }; use brk_traversable::Traversable; -use brk_types::{Bitcoin, DateIndex, Dollars, HalvingEpoch, Height, OutputType, Sats, Version, Year}; +use brk_types::{ + Bitcoin, DateIndex, Dollars, HalvingEpoch, Height, OutputType, Sats, Version, Year, +}; use derive_deref::{Deref, DerefMut}; use rayon::prelude::*; -use vecdb::{Database, Exit, IterableVec}; +use vecdb::{AnyStoredVec, Database, Exit, IterableVec}; use crate::{ Indexes, @@ -372,18 +374,20 @@ impl UTXOCohorts { }) } - /// Write stateful vectors for separate and aggregate cohorts. - pub fn write_stateful_vecs(&mut self, height: Height) -> Result<()> { - // Flush separate cohorts (includes metrics + state) - self.par_iter_separate_mut() - .try_for_each(|v| v.write_stateful_vecs(height))?; + /// Returns a parallel iterator over all vecs for parallel writing. + pub fn par_iter_vecs_mut(&mut self) -> impl ParallelIterator { + // Collect all vecs from all cohorts (separate + aggregate) + self.0 + .iter_mut() + .flat_map(|v| v.par_iter_vecs_mut().collect::>()) + .collect::>() + .into_par_iter() + } - // Write aggregate cohorts' metrics (including price_percentiles) - // Note: aggregate cohorts no longer maintain price_to_amount state - for v in self.0.iter_aggregate_mut() { - v.metrics.write()?; - } - Ok(()) + /// Commit all states to disk (separate from vec writes for parallelization). + pub fn commit_all_states(&mut self, height: Height, cleanup: bool) -> Result<()> { + self.par_iter_separate_mut() + .try_for_each(|v| v.write_state(height, cleanup)) } /// Get minimum height from all separate cohorts' height-indexed vectors. diff --git a/crates/brk_computer/src/stateful/compute/write.rs b/crates/brk_computer/src/stateful/compute/write.rs index 1b8e405b7..c219a6692 100644 --- a/crates/brk_computer/src/stateful/compute/write.rs +++ b/crates/brk_computer/src/stateful/compute/write.rs @@ -53,11 +53,11 @@ pub fn process_address_updates( /// Flush checkpoint to disk (pure I/O, no processing). /// -/// Writes all accumulated data: -/// - Cohort stateful vectors +/// Writes all accumulated data in parallel: +/// - Cohort stateful vectors (parallel internally) /// - Height-indexed vectors -/// - Address indexes and data (parallel) -/// - Transaction output index mappings (parallel) +/// - Address indexes and data +/// - Transaction output index mappings /// - Chain state /// /// Set `with_changes=true` near chain tip to enable rollback support. @@ -67,43 +67,48 @@ pub fn write( chain_state: &[BlockState], with_changes: bool, ) -> Result<()> { + use rayon::prelude::*; + info!("Writing to disk..."); let i = Instant::now(); - // Flush cohort states (separate + aggregate) - vecs.utxo_cohorts.write_stateful_vecs(height)?; - vecs.address_cohorts.write_stateful_vecs(height)?; - - // Flush height-indexed vectors - vecs.height_to_unspendable_supply.write()?; - vecs.height_to_opreturn_supply.write()?; - vecs.addresstype_to_height_to_addr_count.write()?; - vecs.addresstype_to_height_to_empty_addr_count.write()?; - - // Flush large vecs in parallel let stamp = Stamp::from(height); - let any_address_indexes = &mut vecs.any_address_indexes; - let addresses_data = &mut vecs.addresses_data; - let txoutindex_to_txinindex = &mut vecs.txoutindex_to_txinindex; - let (addr_result, txout_result) = rayon::join( - || { - any_address_indexes - .write(stamp, with_changes) - .and(addresses_data.write(stamp, with_changes)) - }, - || txoutindex_to_txinindex.stamped_write_maybe_with_changes(stamp, with_changes), - ); - addr_result?; - txout_result?; - - // Sync in-memory chain_state to persisted and flush + // Prepare chain_state before parallel write vecs.chain_state.truncate_if_needed(Height::ZERO)?; for block_state in chain_state { vecs.chain_state.push(block_state.supply.clone()); } - vecs.chain_state - .stamped_write_maybe_with_changes(stamp, with_changes)?; + + // Write all vecs in parallel using chained iterators + vecs.any_address_indexes + .par_iter_mut() + .chain(vecs.addresses_data.par_iter_mut()) + .chain(vecs.addresstype_to_height_to_addr_count.par_iter_mut()) + .chain( + vecs.addresstype_to_height_to_empty_addr_count + .par_iter_mut(), + ) + .chain(rayon::iter::once( + &mut vecs.txoutindex_to_txinindex as &mut dyn AnyStoredVec, + )) + .chain(rayon::iter::once( + &mut vecs.chain_state as &mut dyn AnyStoredVec, + )) + .chain(rayon::iter::once( + &mut vecs.height_to_unspendable_supply as &mut dyn AnyStoredVec, + )) + .chain(rayon::iter::once( + &mut vecs.height_to_opreturn_supply as &mut dyn AnyStoredVec, + )) + .chain(vecs.utxo_cohorts.par_iter_vecs_mut()) + .chain(vecs.address_cohorts.par_iter_vecs_mut()) + .try_for_each(|v| v.any_stamped_write_maybe_with_changes(stamp, with_changes))?; + + // Commit states after vec writes + let cleanup = with_changes; + vecs.utxo_cohorts.commit_all_states(height, cleanup)?; + vecs.address_cohorts.commit_all_states(height, cleanup)?; info!("Wrote in {:?}", i.elapsed()); diff --git a/crates/brk_computer/src/stateful/metrics/activity.rs b/crates/brk_computer/src/stateful/metrics/activity.rs index ec31725f3..d5208bcc3 100644 --- a/crates/brk_computer/src/stateful/metrics/activity.rs +++ b/crates/brk_computer/src/stateful/metrics/activity.rs @@ -5,6 +5,7 @@ use brk_error::Result; use brk_traversable::Traversable; use brk_types::{Bitcoin, Height, Sats, StoredF64, Version}; +use rayon::prelude::*; use vecdb::{AnyStoredVec, AnyVec, EagerVec, Exit, GenericStoredVec, ImportableVec, PcoVec}; use crate::{ @@ -121,6 +122,16 @@ impl ActivityMetrics { Ok(()) } + /// Returns a parallel iterator over all vecs for parallel writing. + pub fn par_iter_mut(&mut self) -> impl ParallelIterator { + vec![ + &mut self.height_to_sent as &mut dyn AnyStoredVec, + &mut self.height_to_satblocks_destroyed as &mut dyn AnyStoredVec, + &mut self.height_to_satdays_destroyed as &mut dyn AnyStoredVec, + ] + .into_par_iter() + } + /// Validate computed versions against base version. pub fn validate_computed_versions(&mut self, _base_version: Version) -> Result<()> { // Validation logic for computed vecs diff --git a/crates/brk_computer/src/stateful/metrics/mod.rs b/crates/brk_computer/src/stateful/metrics/mod.rs index 6b210be38..52f8e408b 100644 --- a/crates/brk_computer/src/stateful/metrics/mod.rs +++ b/crates/brk_computer/src/stateful/metrics/mod.rs @@ -28,7 +28,8 @@ use brk_error::Result; use brk_grouper::Filter; use brk_traversable::Traversable; use brk_types::{Bitcoin, DateIndex, Dollars, Height, Version}; -use vecdb::{Exit, IterableVec}; +use rayon::prelude::*; +use vecdb::{AnyStoredVec, Exit, IterableVec}; use crate::{Indexes, indexes, price, stateful::states::CohortState}; @@ -125,6 +126,28 @@ impl CohortMetrics { Ok(()) } + /// Returns a parallel iterator over all vecs for parallel writing. + pub fn par_iter_mut(&mut self) -> impl ParallelIterator { + let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new(); + + vecs.extend(self.supply.par_iter_mut().collect::>()); + vecs.extend(self.activity.par_iter_mut().collect::>()); + + if let Some(realized) = self.realized.as_mut() { + vecs.extend(realized.par_iter_mut().collect::>()); + } + + if let Some(unrealized) = self.unrealized.as_mut() { + vecs.extend(unrealized.par_iter_mut().collect::>()); + } + + if let Some(price_paid) = self.price_paid.as_mut() { + vecs.extend(price_paid.par_iter_mut().collect::>()); + } + + vecs.into_par_iter() + } + /// Validate computed versions against base version. pub fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> { self.supply.validate_computed_versions(base_version)?; diff --git a/crates/brk_computer/src/stateful/metrics/price_paid.rs b/crates/brk_computer/src/stateful/metrics/price_paid.rs index a01b29570..4bd08d9aa 100644 --- a/crates/brk_computer/src/stateful/metrics/price_paid.rs +++ b/crates/brk_computer/src/stateful/metrics/price_paid.rs @@ -5,6 +5,7 @@ use brk_error::Result; use brk_traversable::Traversable; use brk_types::{DateIndex, Dollars, Height, Version}; +use rayon::prelude::*; use vecdb::{AnyStoredVec, EagerVec, Exit, GenericStoredVec, ImportableVec, PcoVec}; use crate::{ @@ -121,6 +122,24 @@ impl PricePaidMetrics { Ok(()) } + /// Returns a parallel iterator over all vecs for parallel writing. + pub fn par_iter_mut(&mut self) -> impl ParallelIterator { + let mut vecs: Vec<&mut dyn AnyStoredVec> = vec![ + &mut self.height_to_min_price_paid, + &mut self.height_to_max_price_paid, + ]; + if let Some(pp) = self.price_percentiles.as_mut() { + vecs.extend( + pp.vecs + .iter_mut() + .flatten() + .filter_map(|v| v.dateindex.as_mut()) + .map(|v| v as &mut dyn AnyStoredVec), + ); + } + vecs.into_par_iter() + } + /// Validate computed versions or reset if mismatched. pub fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> { if let Some(price_percentiles) = self.price_percentiles.as_mut() { diff --git a/crates/brk_computer/src/stateful/metrics/realized.rs b/crates/brk_computer/src/stateful/metrics/realized.rs index dba021a51..a86f2d9db 100644 --- a/crates/brk_computer/src/stateful/metrics/realized.rs +++ b/crates/brk_computer/src/stateful/metrics/realized.rs @@ -5,6 +5,7 @@ use brk_error::Result; use brk_traversable::Traversable; use brk_types::{Bitcoin, DateIndex, Dollars, Height, StoredF32, StoredF64, Version}; +use rayon::prelude::*; use vecdb::{AnyStoredVec, EagerVec, Exit, GenericStoredVec, ImportableVec, IterableVec, PcoVec}; use crate::{ @@ -448,6 +449,24 @@ impl RealizedMetrics { Ok(()) } + /// Returns a parallel iterator over all vecs for parallel writing. + pub fn par_iter_mut(&mut self) -> impl ParallelIterator { + let mut vecs: Vec<&mut dyn AnyStoredVec> = vec![ + &mut self.height_to_realized_cap, + &mut self.height_to_realized_profit, + &mut self.height_to_realized_loss, + &mut self.height_to_value_created, + &mut self.height_to_value_destroyed, + ]; + if let Some(v) = self.height_to_adjusted_value_created.as_mut() { + vecs.push(v); + } + if let Some(v) = self.height_to_adjusted_value_destroyed.as_mut() { + vecs.push(v); + } + vecs.into_par_iter() + } + /// Validate computed versions against base version. pub fn validate_computed_versions(&mut self, _base_version: Version) -> Result<()> { // Validation logic for computed vecs diff --git a/crates/brk_computer/src/stateful/metrics/supply.rs b/crates/brk_computer/src/stateful/metrics/supply.rs index 6ebe4ca6d..55d2503e2 100644 --- a/crates/brk_computer/src/stateful/metrics/supply.rs +++ b/crates/brk_computer/src/stateful/metrics/supply.rs @@ -5,6 +5,7 @@ use brk_error::Result; use brk_traversable::Traversable; use brk_types::{Bitcoin, DateIndex, Dollars, Height, Sats, StoredU64, Version}; +use rayon::prelude::*; use vecdb::{ AnyStoredVec, AnyVec, EagerVec, Exit, GenericStoredVec, ImportableVec, IterableVec, PcoVec, TypedVecIterator, @@ -137,6 +138,15 @@ impl SupplyMetrics { Ok(()) } + /// Returns a parallel iterator over all vecs for parallel writing. + pub fn par_iter_mut(&mut self) -> impl ParallelIterator { + vec![ + &mut self.height_to_supply as &mut dyn AnyStoredVec, + &mut self.height_to_utxo_count as &mut dyn AnyStoredVec, + ] + .into_par_iter() + } + /// Validate computed versions against base version. pub fn validate_computed_versions(&mut self, _base_version: Version) -> Result<()> { // Validation logic for computed vecs diff --git a/crates/brk_computer/src/stateful/metrics/unrealized.rs b/crates/brk_computer/src/stateful/metrics/unrealized.rs index 10c1f706c..d0e68c441 100644 --- a/crates/brk_computer/src/stateful/metrics/unrealized.rs +++ b/crates/brk_computer/src/stateful/metrics/unrealized.rs @@ -5,6 +5,7 @@ use brk_error::Result; use brk_traversable::Traversable; use brk_types::{DateIndex, Dollars, Height, Sats, Version}; +use rayon::prelude::*; use vecdb::{ AnyStoredVec, EagerVec, Exit, GenericStoredVec, ImportableVec, IterableCloneableVec, PcoVec, }; @@ -231,6 +232,21 @@ impl UnrealizedMetrics { Ok(()) } + /// Returns a parallel iterator over all vecs for parallel writing. + pub fn par_iter_mut(&mut self) -> impl ParallelIterator { + vec![ + &mut self.height_to_supply_in_profit as &mut dyn AnyStoredVec, + &mut self.height_to_supply_in_loss as &mut dyn AnyStoredVec, + &mut self.height_to_unrealized_profit as &mut dyn AnyStoredVec, + &mut self.height_to_unrealized_loss as &mut dyn AnyStoredVec, + &mut self.dateindex_to_supply_in_profit as &mut dyn AnyStoredVec, + &mut self.dateindex_to_supply_in_loss as &mut dyn AnyStoredVec, + &mut self.dateindex_to_unrealized_profit as &mut dyn AnyStoredVec, + &mut self.dateindex_to_unrealized_loss as &mut dyn AnyStoredVec, + ] + .into_par_iter() + } + /// Compute aggregate values from separate cohorts. pub fn compute_from_stateful( &mut self, diff --git a/crates/brk_computer/src/stateful/states/address_cohort.rs b/crates/brk_computer/src/stateful/states/address_cohort.rs index 5d2112929..15a9a660e 100644 --- a/crates/brk_computer/src/stateful/states/address_cohort.rs +++ b/crates/brk_computer/src/stateful/states/address_cohort.rs @@ -173,14 +173,11 @@ impl AddressCohortState { ) }); - self.inner.decrement_( - &addr_supply, - addressdata.realized_cap, - realized_price, - ); + self.inner + .decrement_(&addr_supply, addressdata.realized_cap, realized_price); } - pub fn commit(&mut self, height: Height) -> Result<()> { - self.inner.commit(height) + pub fn write(&mut self, height: Height, cleanup: bool) -> Result<()> { + self.inner.write(height, cleanup) } } diff --git a/crates/brk_computer/src/stateful/states/cohort.rs b/crates/brk_computer/src/stateful/states/cohort.rs index d931f076c..4e3755de5 100644 --- a/crates/brk_computer/src/stateful/states/cohort.rs +++ b/crates/brk_computer/src/stateful/states/cohort.rs @@ -369,9 +369,9 @@ impl CohortState { } /// Flush state to disk at checkpoint. - pub fn commit(&mut self, height: Height) -> Result<()> { + pub fn write(&mut self, height: Height, cleanup: bool) -> Result<()> { if let Some(p) = self.price_to_amount.as_mut() { - p.flush(height)?; + p.write(height, cleanup)?; } Ok(()) } diff --git a/crates/brk_computer/src/stateful/states/price_to_amount.rs b/crates/brk_computer/src/stateful/states/price_to_amount.rs index 007baf628..3b52d0925 100644 --- a/crates/brk_computer/src/stateful/states/price_to_amount.rs +++ b/crates/brk_computer/src/stateful/states/price_to_amount.rs @@ -144,9 +144,7 @@ impl PriceToAmount { for (&price, &amount) in state.iter() { cumsum += u64::from(amount); - while idx < PERCENTILES_LEN - && cumsum >= total * u64::from(PERCENTILES[idx]) / 100 - { + while idx < PERCENTILES_LEN && cumsum >= total * u64::from(PERCENTILES[idx]) / 100 { result[idx] = price; idx += 1; } @@ -181,16 +179,19 @@ impl PriceToAmount { .collect::>()) } - pub fn flush(&mut self, height: Height) -> Result<()> { + /// Flush state to disk, optionally cleaning up old state files. + pub fn write(&mut self, height: Height, cleanup: bool) -> Result<()> { self.apply_pending(); - let files = self.read_dir(Some(height))?; + if cleanup { + let files = self.read_dir(Some(height))?; - for (_, path) in files - .iter() - .take(files.len().saturating_sub(STATE_TO_KEEP - 1)) - { - fs::remove_file(path)?; + for (_, path) in files + .iter() + .take(files.len().saturating_sub(STATE_TO_KEEP - 1)) + { + fs::remove_file(path)?; + } } fs::write(self.path_state(height), self.state.u().serialize()?)?; diff --git a/crates/brk_indexer/src/vecs/address.rs b/crates/brk_indexer/src/vecs/address.rs index e41a60360..e673a4385 100644 --- a/crates/brk_indexer/src/vecs/address.rs +++ b/crates/brk_indexer/src/vecs/address.rs @@ -6,6 +6,7 @@ use brk_types::{ P2SHBytes, P2TRAddressIndex, P2TRBytes, P2WPKHAddressIndex, P2WPKHBytes, P2WSHAddressIndex, P2WSHBytes, TypeIndex, Version, }; +use rayon::prelude::*; use vecdb::{ AnyStoredVec, BytesVec, Database, GenericStoredVec, ImportableVec, PcoVec, Reader, Stamp, TypedVecIterator, @@ -136,7 +137,7 @@ impl AddressVecs { Ok(()) } - pub fn iter_mut_any(&mut self) -> impl Iterator { + pub fn par_iter_mut_any(&mut self) -> impl ParallelIterator { [ &mut self.height_to_first_p2pk65addressindex as &mut dyn AnyStoredVec, &mut self.height_to_first_p2pk33addressindex, @@ -155,7 +156,7 @@ impl AddressVecs { &mut self.p2traddressindex_to_p2trbytes, &mut self.p2aaddressindex_to_p2abytes, ] - .into_iter() + .into_par_iter() } /// Get address bytes by output type, using the reader for the specific address type. diff --git a/crates/brk_indexer/src/vecs/blocks.rs b/crates/brk_indexer/src/vecs/blocks.rs index a1a9ae657..a02971067 100644 --- a/crates/brk_indexer/src/vecs/blocks.rs +++ b/crates/brk_indexer/src/vecs/blocks.rs @@ -1,6 +1,7 @@ use brk_error::Result; use brk_traversable::Traversable; use brk_types::{BlockHash, Height, StoredF64, StoredU64, Timestamp, Version, Weight}; +use rayon::prelude::*; use vecdb::{AnyStoredVec, BytesVec, Database, GenericStoredVec, ImportableVec, PcoVec, Stamp}; #[derive(Clone, Traversable)] @@ -38,7 +39,7 @@ impl BlockVecs { Ok(()) } - pub fn iter_mut_any(&mut self) -> impl Iterator { + pub fn par_iter_mut_any(&mut self) -> impl ParallelIterator { [ &mut self.height_to_blockhash as &mut dyn AnyStoredVec, &mut self.height_to_difficulty, @@ -46,6 +47,6 @@ impl BlockVecs { &mut self.height_to_total_size, &mut self.height_to_weight, ] - .into_iter() + .into_par_iter() } } diff --git a/crates/brk_indexer/src/vecs/mod.rs b/crates/brk_indexer/src/vecs/mod.rs index 4f1249863..e140225a6 100644 --- a/crates/brk_indexer/src/vecs/mod.rs +++ b/crates/brk_indexer/src/vecs/mod.rs @@ -121,16 +121,14 @@ impl Vecs { } pub fn flush(&mut self, height: Height) -> Result<()> { - self.iter_mut_any_stored_vec() - // self.par_iter_mut_any_stored_vec() - .par_bridge() + self.par_iter_mut_any_stored_vec() .try_for_each(|vec| vec.stamped_write(Stamp::from(height)))?; self.db.flush()?; Ok(()) } pub fn starting_height(&mut self) -> Height { - self.iter_mut_any_stored_vec() + self.par_iter_mut_any_stored_vec() .map(|vec| { let h = Height::from(vec.stamp()); if h > Height::ZERO { h.incremented() } else { h } @@ -152,26 +150,18 @@ impl Vecs { self.address.iter_hashes_from(address_type, height) } - fn iter_mut_any_stored_vec(&mut self) -> impl Iterator { + fn par_iter_mut_any_stored_vec( + &mut self, + ) -> impl ParallelIterator { self.block - .iter_mut_any() - .chain(self.tx.iter_mut_any()) - .chain(self.txin.iter_mut_any()) - .chain(self.txout.iter_mut_any()) - .chain(self.address.iter_mut_any()) - .chain(self.output.iter_mut_any()) + .par_iter_mut_any() + .chain(self.tx.par_iter_mut_any()) + .chain(self.txin.par_iter_mut_any()) + .chain(self.txout.par_iter_mut_any()) + .chain(self.address.par_iter_mut_any()) + .chain(self.output.par_iter_mut_any()) } - // fn par_iter_mut_any_stored_vec(&mut self) -> impl Iterator { - // self.block - // .iter_mut_any() - // .chain(self.tx.iter_mut_any()) - // .chain(self.txin.iter_mut_any()) - // .chain(self.txout.iter_mut_any()) - // .chain(self.address.iter_mut_any()) - // .chain(self.output.iter_mut_any()) - // } - pub fn db(&self) -> &Database { &self.db } diff --git a/crates/brk_indexer/src/vecs/output.rs b/crates/brk_indexer/src/vecs/output.rs index 02bc70d4c..8e3c764d5 100644 --- a/crates/brk_indexer/src/vecs/output.rs +++ b/crates/brk_indexer/src/vecs/output.rs @@ -3,6 +3,7 @@ use brk_traversable::Traversable; use brk_types::{ EmptyOutputIndex, Height, OpReturnIndex, P2MSOutputIndex, TxIndex, UnknownOutputIndex, Version, }; +use rayon::prelude::*; use vecdb::{AnyStoredVec, Database, GenericStoredVec, ImportableVec, PcoVec, Stamp}; #[derive(Clone, Traversable)] @@ -77,7 +78,7 @@ impl OutputVecs { Ok(()) } - pub fn iter_mut_any(&mut self) -> impl Iterator { + pub fn par_iter_mut_any(&mut self) -> impl ParallelIterator { [ &mut self.height_to_first_emptyoutputindex as &mut dyn AnyStoredVec, &mut self.height_to_first_opreturnindex, @@ -88,6 +89,6 @@ impl OutputVecs { &mut self.p2msoutputindex_to_txindex, &mut self.unknownoutputindex_to_txindex, ] - .into_iter() + .into_par_iter() } } diff --git a/crates/brk_indexer/src/vecs/tx.rs b/crates/brk_indexer/src/vecs/tx.rs index c9ee4aded..aae22d9e1 100644 --- a/crates/brk_indexer/src/vecs/tx.rs +++ b/crates/brk_indexer/src/vecs/tx.rs @@ -4,6 +4,7 @@ use brk_types::{ Height, RawLockTime, StoredBool, StoredU32, TxInIndex, TxIndex, TxOutIndex, TxVersion, Txid, Version, }; +use rayon::prelude::*; use vecdb::{AnyStoredVec, BytesVec, Database, GenericStoredVec, ImportableVec, PcoVec, Stamp}; #[derive(Clone, Traversable)] @@ -60,7 +61,7 @@ impl TxVecs { Ok(()) } - pub fn iter_mut_any(&mut self) -> impl Iterator { + pub fn par_iter_mut_any(&mut self) -> impl ParallelIterator { [ &mut self.height_to_first_txindex as &mut dyn AnyStoredVec, &mut self.txindex_to_height, @@ -73,6 +74,6 @@ impl TxVecs { &mut self.txindex_to_first_txinindex, &mut self.txindex_to_first_txoutindex, ] - .into_iter() + .into_par_iter() } } diff --git a/crates/brk_indexer/src/vecs/txin.rs b/crates/brk_indexer/src/vecs/txin.rs index b79176e30..140c2b19b 100644 --- a/crates/brk_indexer/src/vecs/txin.rs +++ b/crates/brk_indexer/src/vecs/txin.rs @@ -1,6 +1,7 @@ use brk_error::Result; use brk_traversable::Traversable; use brk_types::{Height, OutPoint, TxInIndex, TxIndex, Version}; +use rayon::prelude::*; use vecdb::{AnyStoredVec, Database, GenericStoredVec, ImportableVec, PcoVec, Stamp}; #[derive(Clone, Traversable)] @@ -29,12 +30,12 @@ impl TxinVecs { Ok(()) } - pub fn iter_mut_any(&mut self) -> impl Iterator { + pub fn par_iter_mut_any(&mut self) -> impl ParallelIterator { [ &mut self.height_to_first_txinindex as &mut dyn AnyStoredVec, &mut self.txinindex_to_outpoint, &mut self.txinindex_to_txindex, ] - .into_iter() + .into_par_iter() } } diff --git a/crates/brk_indexer/src/vecs/txout.rs b/crates/brk_indexer/src/vecs/txout.rs index 9e54ddf1f..66e764218 100644 --- a/crates/brk_indexer/src/vecs/txout.rs +++ b/crates/brk_indexer/src/vecs/txout.rs @@ -1,6 +1,7 @@ use brk_error::Result; use brk_traversable::Traversable; use brk_types::{Height, OutputType, Sats, TxIndex, TxOutIndex, TypeIndex, Version}; +use rayon::prelude::*; use vecdb::{AnyStoredVec, BytesVec, Database, GenericStoredVec, ImportableVec, PcoVec, Stamp}; #[derive(Clone, Traversable)] @@ -37,7 +38,7 @@ impl TxoutVecs { Ok(()) } - pub fn iter_mut_any(&mut self) -> impl Iterator { + pub fn par_iter_mut_any(&mut self) -> impl ParallelIterator { [ &mut self.height_to_first_txoutindex as &mut dyn AnyStoredVec, &mut self.txoutindex_to_value, @@ -45,6 +46,6 @@ impl TxoutVecs { &mut self.txoutindex_to_typeindex, &mut self.txoutindex_to_txindex, ] - .into_iter() + .into_par_iter() } } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 36bdd189e..0568b1964 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,87 @@ All notable changes to the Bitcoin Research Kit (BRK) project will be documented > *This changelog was generated by Claude Code* +## [v0.1.0-alpha.1](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.1.0-alpha.1) - 2025-12-21 + +### New Features + +#### `brk_binder` +- Implemented complete multi-language client generation system that produces typed API clients from the metric catalog and OpenAPI specification ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/crates/brk_binder/src/lib.rs)) +- Added JavaScript client generator with full JSDoc type annotations for IDE autocomplete support across 20k+ metrics ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/crates/brk_binder/src/javascript.rs)) +- Added Python client generator with type hints and httpx for HTTP requests ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/crates/brk_binder/src/python.rs)) +- Added Rust client generator with strong typing using reqwest ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/crates/brk_binder/src/rust.rs)) +- Implemented OpenAPI 3.1 integration using the `oas3` crate for parsing endpoint definitions ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/crates/brk_binder/src/openapi.rs)) +- Created structural pattern detection system that identifies repeating tree structures for type reuse ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/crates/brk_binder/src/types/patterns.rs)) +- Added generic pattern detection that groups type-parameterized structures to reduce generated code size +- Created `ClientMetadata` for extracting catalog structure, patterns, and index sets from `brk_query::Vecs` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/crates/brk_binder/src/types/mod.rs)) + +#### `brk_types` +- Added `MetricLeaf` struct containing metric name, value type, and available indexes ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/crates/brk_types/src/treenode.rs)) +- Added `MetricLeafWithSchema` that wraps `MetricLeaf` with a JSON Schema for client type generation +- Changed `TreeNode::Leaf` from `String` to `MetricLeafWithSchema` to carry full metadata through the tree +- Added `JsonSchema` derive to byte array types (`U8x2`, `U8x20`, `U8x32`) with custom implementations for `U8x33` and `U8x65` +- Added `JsonSchema` derive to OHLC types (`OHLCCents`, `OHLCDollars`, `OHLCSats`, `Open`, `High`, `Low`, `Close`) +- Added `JsonSchema` derive to all index types (date, week, month, quarter, semester, year, decade, halving epoch, difficulty epoch) +- Added `JsonSchema` derive to address index types (P2PKH, P2SH, P2WPKH, P2WSH, P2TR, P2PK33, P2PK65, P2A, OP_RETURN, empty, unknown) +- Implemented index merging when collapsing tree nodes with the same metric name + +#### `brk_traversable` +- Added `make_leaf` helper function that generates `MetricLeafWithSchema` nodes with schemars-based JSON Schema generation ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/crates/brk_traversable/src/lib.rs)) +- Updated all `Traversable` implementations to require `JsonSchema` bound and generate full leaf metadata + +#### `brk_computer` +- Added `neg_realized_loss` metric (realized_loss * -1) for charting convenience ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/crates/brk_computer/src/stateful/metrics/realized.rs)) +- Added `net_realized_pnl` metric (realized_profit - realized_loss) +- Added `realized_value` metric (realized_profit + realized_loss) +- Added `total_realized_pnl` at both height and dateindex levels +- Added `realized_price` metric (realized_cap / supply) +- Added `realized_cap_30d_delta` for 30-day realized cap changes +- Added `sopr` (Spent Output Profit Ratio) with 7-day and 30-day EMA variants +- Added `adjusted_sopr` with EMA variants for coinbase-adjusted analysis +- Added `sell_side_risk_ratio` (realized_value / realized_cap) with 7-day and 30-day EMAs +- Added realized profit/loss ratios relative to realized cap +- Added `net_realized_pnl_cumulative_30d_delta` with ratios relative to realized cap and market cap +- Added `neg_unrealized_loss_rel_to_market_cap` and `net_unrealized_pnl_rel_to_market_cap` metrics ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/crates/brk_computer/src/stateful/metrics/relative.rs)) +- Added `supply_in_profit/loss_rel_to_circulating_supply` metrics +- Added unrealized profit/loss relative to own market cap at both height and indexes levels +- Added full benchmark example with indexer, computer, and bencher integration ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/crates/brk_computer/examples/full_bench.rs)) + +#### `brk_grouper` +- Added public constants for age boundaries (`DAYS_1D`, `DAYS_1W`, `DAYS_1M`, etc.) for external use ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/crates/brk_grouper/src/by_age_range.rs)) +- Added `get_mut_by_days_old()` method to `ByAgeRange` for O(1) direct bucket access by days +- Added `AmountBucket` type for O(1) amount range classification ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/crates/brk_grouper/src/by_amount_range.rs)) +- Added `amounts_in_different_buckets()` helper for cheap bucket comparison +- Optimized `get()` and `get_mut()` in `ByAmountRange` using match on bucket index instead of if-else chain + +#### `brk_server` +- Integrated automatic client generation on startup when `brk_binder/clients/` directory exists ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/crates/brk_server/src/lib.rs)) +- Added panic catching for client generation to prevent server startup failures + +#### `brk_cli` +- Enabled distribution via cargo-dist (`dist = true` in package metadata) +- Added performance benchmarks section to README with timing and resource usage data + +#### `workspace` +- Added `rust-toolchain.toml` pinning toolchain to Rust 1.92.0 +- Created comprehensive `scripts/publish.sh` for automated crate publishing in dependency order ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/scripts/publish.sh)) +- Updated `scripts/update.sh` to also update rust-toolchain version + +### Internal Changes + +#### `brk_computer` +- Removed `FenwickTree` state module (O(log n) prefix sum data structure) +- Removed `PriceBuckets` state module (logarithmic price bucket distribution) +- Simplified cohort state management after removal of Fenwick-based percentile computation + +#### `brk_indexer` +- Renamed `push_if_needed()` to `checked_push()` throughout for API consistency ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.1.0-alpha.1/crates/brk_indexer/src/processor.rs)) +- Renamed `Indexes::push_if_needed()` to `Indexes::checked_push()` + +#### `brk_cli` +- Updated `zip` dependency from 6.0.0 to 7.0.0 + +[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.1.0-alpha.0...v0.1.0-alpha.1) + ## [v0.1.0-alpha.0](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.1.0-alpha.0) - 2025-12-18 ### Breaking Changes