# Changelog All notable changes to the Bitcoin Research Kit (BRK) project will be documented in this file. > *This changelog was generated by Claude Code* ## [v0.3.0-beta.9](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.3.0-beta.9) - 2026-05-08 ### Breaking Changes #### `brk_rpc` - Removed the public `BlockchainInfo` struct and `Client::get_blockchain_info()` method. The single internal caller (`wait_for_synced_node`) now declares a local `SyncProgress { headers, blocks }` and invokes `getblockchaininfo` directly. Downstream consumers that called `client.get_blockchain_info()` must inline the corresponding `corepc-jsonrpc` call ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.9/crates/brk_rpc/src/lib.rs)) - The corepc-types import surface narrowed from a flat `v30::*` to version-pinned `v17::{GetBlockCount, GetBlockHash, GetBlockHeader, GetBlockHeaderVerbose, GetBlockVerboseOne, GetBlockVerboseZero, GetRawMempool, GetTxOut}` plus `v24::GetMempoolInfo`. Each wrapper now imports the response shape that matches what Bitcoin Core returns at the version that first stabilized the field set, instead of relying on the v30 re-exports ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.9/crates/brk_rpc/src/methods.rs)) #### `brk_types` - Removed the `mined: Option` field from `RbfTx`. The flag was misleading: an RBF tree's tx is by definition not yet mined (mined replacements collapse the tree), so the field was either redundant or actively wrong on stale captures. Clients that read `tx.mined` from the RBF response must drop the lookup ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.9/crates/brk_types/src/rbf.rs)) ### Bug Fixes #### `brk_mempool` - Fixed an SFL chunk-extraction bug where uniform-rate chains and same-rate sibling subtrees produced singleton chunks "above" the main chunk under integer-vsize rounding, silently breaking the descending-rate invariant the partitioner relies on. The initial chunk selection now prefers the larger ancestor-closed set on rate ties, and a new extension loop greedily extends the picked chunk with any other ancestor-closed subset whose union keeps the chunk rate `>=` the current rate. Helper functions `closure(items, remaining, excluded, start)` and `sum_fee_vsize(items, set)` are factored out so the extension loop reuses the same closure walk as the initial pick ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.9/crates/brk_mempool/src/chunking.rs)) - `Mempool::graveyard_fee_rate(txid)` previously returned `entry.fee_rate()`, the tx's isolated `fee/vsize`. For an RBF predecessor evicted to the graveyard this misrepresented the bid that displaced it, since the live tx had been propagated with its package-effective rate. Burials now snapshot `entry.chunk_rate` (Core's chunk rate at apply time), and `graveyard_fee_rate` returns the same value the tx reported via `live_effective_fee_rate` while alive. The doc comment is updated to call out the package-effective semantics ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.9/crates/brk_mempool/src/lib.rs)) #### `brk_query` - `tx_rbf` was threading a `mined` field through every node of the RBF tree, but the field was always synthesized from a stale lookup that could disagree with the canonical `TxStatus` for the same txid. The field is now dropped from both `RbfTx` and the per-node construction in `impl/mempool.rs`, so clients that need confirmation status query `/api/v1/tx/:txid/status` instead and avoid the inconsistency window ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.9/crates/brk_query/src/impl/mempool.rs)) ### Internal Changes #### `clients` - Bumped the auto-generated `modules/brk-client` (npm) and `packages/brk_client` (PyPI) bindings to reflect the dropped `RbfTx.mined` field and the removed `BlockchainInfo` type ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.9/modules/brk-client/index.js)) #### `deps` - Workspace and per-crate versions stepped to `0.3.0-beta.9` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.9/Cargo.toml)) [View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.3.0-beta.8...v0.3.0-beta.9) ## [v0.3.0-beta.8](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.3.0-beta.8) - 2026-05-08 ### Breaking Changes #### `brk_mempool` - Replaced the entire intra-mempool block-building pipeline with Bitcoin Core's `getblocktemplate`. All of `steps/rebuilder/block_builder/{graph,graph_bench,linearize/**,package,partitioner,pool_index,tx_node,mod}.rs` and `steps/rebuilder/projected_blocks/{fees,snapshot,stats,verify,mod}.rs` are deleted (~2,000 lines). The new `Rebuilder` lives in `steps/rebuilder/` and is a dirty-bit-driven snapshot publisher: it copies Core's next-block template verbatim into `blocks[0]` and uses a simple `Partitioner` to fill `blocks[1..8]` from the remaining mempool ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_mempool/src/steps/rebuilder/mod.rs)) - `Mempool::start_with` / `Mempool::update_with` now take a `PrevoutResolver = Box Option + Send + Sync>` and drive the new dedicated `steps::Prevouts::fill` step that backfills `prevout: None` from same-cycle in-mempool parents (direct memory lookup) and from confirmed parents via the supplied resolver. The previous `fill_prevouts(resolver)` accessor is removed; backfill is now part of the cycle ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_mempool/src/steps/prevouts.rs)) - Removed `MempoolState` / `stores::state` and replaced it with a top-level `state::State` re-exported as `pub(crate) use state::State`. `Mempool(Arc)` now holds a single `state: RwLock` instead of one lock per sub-store, and one batched RPC (`getrawmempool verbose` + `getblocktemplate` + `getmempoolinfo`) drives the cycle instead of five separately-locked stores. The five-lock acquire order in the old `MempoolState::apply` is gone ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_mempool/src/state.rs)) - Flattened the rest of the stores layout: `stores/{entry,entry_pool,tombstone}.rs` are deleted, `stores/addr_tracker.rs` is split into `stores/addr_tracker/{mod,addr_entry}.rs`, `stores/tx_graveyard.rs` is split into `stores/tx_graveyard/{mod,tombstone}.rs`, and `stores/outpoint_spends.rs` is added. The new public types `TxStore`, `TxGraveyard`, `TxTombstone`, `TxEntry`, `TxRemoval`, `BlockStats`, `RecommendedFees`, `Snapshot` are re-exported from the crate root ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_mempool/src/lib.rs)) #### `brk_indexer` - Split the flat `indexes.rs` into three files: `lengths.rs` (the `Lengths` struct with per-vec counters), `indexes.rs` (the typed `Indexes` view), and the new `safe_lengths.rs` (`SafeLengths`, a pipeline-wide `Arc>` snapshot). Callers that imported `Lengths` from `indexes` must update to `brk_indexer::lengths::Lengths` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_indexer/src/lib.rs)) - Split the flat `processor.rs` into `processor/{mod,metadata,sigops,tx,txin,txout}.rs`. The block processor is now `BlockProcessor` with explicit `process_inputs`, `process_outputs`, `compute_sigops` methods instead of a monolithic free function. External callers that imported `processor::*` may need to drop submodule prefixes ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_indexer/src/processor/mod.rs)) #### `brk_types` - Split the flat `cpfp.rs` into a submodule: `cpfp/{mod,cluster,cluster_chunk,cluster_tx,cluster_tx_index,entry,info}.rs`. Public types `Cluster`, `ClusterChunk`, `ClusterTx`, `ClusterTxIndex`, `CpfpEntry`, `CpfpInfo` keep their names but now live in `brk_types::cpfp::*` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_types/src/cpfp/mod.rs)) - Deleted `pagination_index.rs` and `series_count.rs`. Pagination is now expressed through the typed index in `index.rs` (new `Index` newtype), and series count is folded into the response wrapper ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_types/src/lib.rs)) #### `brk_server` - Deleted `extended/encoding.rs`. JSON / text / bytes encoding is now selected per-route via the new `typed_text.rs` `TypedText` extended response type and per-route helpers on `AppState` (`cached_json`, `cached_text`, `cached_bytes`). Custom handlers that called `encoding::*` must switch to the typed helpers ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_server/src/extended/typed_text.rs)) #### `brk_computer` - Workspace-wide signature simplification: every compute method dropped its `starting_indexes: &Indexes` (and `starting_indexes: Indexes` on `Computer::compute`) parameter across ~115 files. The top-level `Computer::compute(&mut self, indexer: &Indexer, exit: &Exit) -> Result<()>` no longer threads a starting-indexes value through `blocks`, `inputs`, `outputs`, `transactions`, `mining`, `market`, `pools`, `investing`, `distribution`, `prices`, `supply`, `cointime`, `indicators`, and every sub-import call site. Consumers driving the compute pipeline must drop the argument; the equivalent snapshot is now reached through `Indexer::safe_lengths()` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_computer/src/lib.rs)) #### `brk_indexer` - `Indexer::index(...)` now returns `Result<()>` instead of `Result`; the local pipeline variable is renamed `starting_indexes` → `starting_lengths` and the value is no longer surfaced (consumers that want the post-index snapshot call `Indexer::safe_lengths()`). On the storage layer, `Vecs::rollback_if_needed(starting_indexes: &Indexes)` is renamed to `rollback_if_needed(starting_lengths: &Lengths)`, and the per-vec `truncate` cascade follows suit. Downstream callers driving rollback or reading the indexed-result value must update both names and the return type ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_indexer/src/lib.rs)) - New `total_sigop_cost: PcoVec` column persisted in `vecs/transactions.rs` and `TxsAppender`. Consumers that destructured `TxsAppender` field-by-field must add the new field, and any external code that snapshots the transactions vec set picks up the additional column on disk ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_indexer/src/vecs/transactions.rs)) #### `brk_error` - `Error::OutOfRange(String)` is now `Error::OutOfRange(Cow<'static, str>)` so static error messages avoid an allocation; callers that constructed it with `OutOfRange(format!(...))` keep working (the `Cow::Owned` arm), but callers that pattern-matched on `OutOfRange(String)` must update to `OutOfRange(Cow<'static, str>)` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_error/src/lib.rs)) #### `brk_logger` - `tracing_subscriber::init(path: Option<&Path>)` renamed parameter to `init(dir: Option<&Path>)` to match the semantics (the argument is the log directory, not a single file path). Callers passing positional arguments are unaffected; named-argument callers must update ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_logger/src/lib.rs)) ### New Features #### `blk` (new crate) - New `blk` CLI binary for inspecting Bitcoin Core blocks. Reads `blk*.dat` files directly via `brk_reader` and resolves the chain tip / heights via the Bitcoin Core RPC. Output is shell-friendly (bare values, NDJSON, pretty JSON, or TSV). Supports field selectors like `tx.0.vout.0.value`, height ranges (`0..2`), `tip` alias, and named outputs (`hash`, `height`, `time`, etc.) ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/blk/src/main.rs)) #### `brk_mempool` - Projected next block is now Bitcoin Core's own `getblocktemplate` rather than BRK's reimplementation. The same fetch cycle that pulls `getrawmempool verbose` now also pulls GBT, so the snapshot's `blocks[0]` is byte-for-byte what Core would mine if it produced a block at that instant. Blocks `[1..8]` are partitioned from the remaining mempool by descending package feerate via the new lightweight `Partitioner::partition`. The previous SFL + cluster linearization + parent-before-child verifier is no longer needed and was deleted ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_mempool/src/steps/rebuilder/mod.rs)) - New `Mempool::stats()` returns a `MempoolStats` summary (rebuild count, skip-clean count, etc.) for diagnostics. The `diagnostics` module exposes counters that previously lived in scattered fields on the rebuilder ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_mempool/src/diagnostics.rs)) - New top-level `rbf` module producing `RbfForTx { tx, replacements }` and recursive `RbfNode { tx, replaces }` shapes for the BIP-125 replacement tree. Backs the `/api/v1/tx/:txid/rbf` and `/api/v1/replacements` server endpoints. Standalone `cpfp` module exposes `CpfpInfo` ancestor walks deduplicated via `FxHashSet` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_mempool/src/rbf.rs)) - New public `chunking::linearize(ChunkInput) -> Vec` exposes the per-cluster chunk ordering primitive previously buried inside the block-builder, so external crates can run the same package-grouping logic without depending on the rebuilder ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_mempool/src/chunking.rs)) #### `brk_indexer` - New `SafeLengths(Arc>)` pipeline-wide snapshot of safe-read counts: `bound.f = N` means positions `0..N` are fully written and readers reject `pos >= bound.f`. Eliminates the stamp-before-write race where a `Ro` reader could observe `stamp == N` but `collect_one(N)` returns `None` (see project memory). `Indexer::safe_lengths()` exposes the snapshot; readers consult it before issuing range reads ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_indexer/src/safe_lengths.rs)) - New `BlockProcessor::compute_sigops` returns per-tx BIP-141 sigop cost (legacy + P2SH-redeem + witness, weighted). Dispatches on the already-resolved `OutputType` so canonical-shape scripts (~99% of outputs and ~95% of inputs on mainnet) skip the script walk entirely. Sigops are persisted into a new per-tx column read by mining/sigops endpoints ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_indexer/src/processor/sigops.rs)) #### `brk_types` - New `SigOps(u32)` newtype representing BIP-141 sigop cost. Const `VBYTES_PER_SIGOP = 5` matches Core's `nSigOpCost` adjustment factor (`adjusted_vsize = max(vsize, sigops * 5)`). Carries `Pco`, `Formattable`, `JsonSchema`, and serde `transparent` impls so it can be written into vecdb columns and exposed through the OpenAPI surface without bespoke serialization ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_types/src/sigops.rs)) - New `Index` newtype in `index.rs` consolidating pagination and series indexing, and new `OutpointPrefix` (`outpoint_prefix.rs`) and `BlockTxIndex` (`block_tx_index.rs`) types used by the new mempool-compat and block-tx endpoints ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_types/src/outpoint_prefix.rs)) #### `brk_query` - New `cpfp.rs` query impl provides `cpfp_info_for_tx(&Txid)` returning ancestor+descendant package fee/vsize from a single FxHashSet-deduplicated walk over the mempool entry graph. Replaces the per-call ancestor recursion that previously lived inside the mempool endpoint handler ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_query/src/impl/cpfp.rs)) - New `mining/period_start.rs` resolves the height of the difficulty / halving period boundary used by mining endpoints, replacing inline boundary math scattered across `block_window`, `difficulty_adjustments`, `epochs`, and `hashrate` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_query/src/impl/mining/period_start.rs)) #### `brk_server` - New `extended/typed_text.rs` adds `TypedText`, a content-typed text response wrapper that lets a single handler return `text/plain`, `application/hex`, or any RFC 6838 subtype with one ETag pipeline. Used by `block_raw`, `tx_hex`, and the merkle-proof endpoints ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_server/src/extended/typed_text.rs)) - New `params/addr_after_txid_param.rs` provides typed `?after_txid=...` cursor support for address tx-history endpoints, replacing the ad-hoc `Option` field on `AddrTxidsParam` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_server/src/params/addr_after_txid_param.rs)) - Split the OpenAPI document into `api/openapi/{compact,full,mod}.rs`. `compact` emits a slimmed schema (path-only operations) for fast loading in browser-side Scalar viewers, while `full` keeps every schema and response variant for code generation ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_server/src/api/openapi/mod.rs)) - New crate-level constants `MAX_ERROR_BODY_BYTES = 4096` (caps the buffered upstream-error body that the JSON-error middleware re-wraps) and `REQUEST_TIMEOUT = Duration::from_secs(5)` (per-request timeout layer, returns `504 Gateway Timeout` on hit). The previous inline `4096` and `Duration::from_secs(5)` are now named, audited constants ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_server/src/lib.rs)) - New `is_json_content_type(s: &str) -> bool` helper recognizes both `application/json` and any `application/...+json` subtype, ignoring parameters like `; charset=utf-8`. The JSON-error rewriter consults it to skip re-wrapping already-JSON upstream bodies ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_server/src/lib.rs)) - JSON-error rewriter now propagates an upstream `Allow` header through to the rewrapped error response, so 405 Method Not Allowed responses keep their `Allow:` list intact after being normalized to BRK's `application/problem+json` shape ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_server/src/lib.rs)) #### `brk_error` - New `Error::TooManyUtxos` variant rendered as `"Too many unspent transaction outputs (>1000)."`. Wired into the address-UTXO endpoints so requests that would exceed `DEFAULT_MAX_UTXOS` return a structured `400` instead of being silently truncated or rejected as a generic weight overflow ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_error/src/lib.rs)) #### `clients` - The auto-generated `brk-client` (npm) and `brk_client` (PyPI) bindings gained a `.len()` method on every list-shaped response. Removes the need for callers to read `.data.length` / `len(data)` through nested wrappers and aligns with the new `Index` pagination type ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/modules/brk-client/index.js)) - Added a comprehensive `mempool_compat` Python pytest suite under `packages/brk_client/tests/mempool_compat/` and `scripts/mempool_compat/` covering all major mempool.space-aligned endpoint families: addresses (info, txs, txs/chain, txs/mempool, utxo, validate), blocks (block, header, height, raw, status, txid-by-index, txids, txs, v1 variants, blocks-at-height, tip hash/height), fees (mempool-blocks, precise, recommended), general (difficulty adjustment, historical price, prices), mempool (`/mempool`, recent, replacements, fullRBF replacements, txids), mining (blocks fee rates / fees / rewards / sizes / timestamps, difficulty adjustments, hashrate, hashrate-pools, pool detail, pool blocks, pool hashrate, pools, pools period, reward stats), and transactions (cpfp, post-tx, times, tx, hex, merkle proof, merkleblock proof, outspend, outspends, raw, rbf, status). Each test drives the live BRK server against the live mempool.space JSON, asserting shape and value parity ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/packages/brk_client/tests/mempool_compat/conftest.py)) ### Bug Fixes #### `brk_bindgen` - JavaScript client generator was emitting the field name `txid` for repeated-key query parameters declared as `txId[]`. Generated callers that built URLs from the type signature produced the wrong key and got `400 unknown query parameter`. The generator now preserves the bracketed form so the emitted code sends `txId[]=...` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_bindgen/src/generators/javascript/api.rs)) #### `brk_indexer` - The previous indexing pipeline updated each vec's header stamp before the pushed data finished flushing to disk. A `Ro` reader could observe `stamp == N` while `collect_one(N)` returned `None`, manifesting as intermittent 404s right at the tip. `SafeLengths::advance` is now called only after every vec in the pipeline has completed its flush for a given height, so readers gate range reads on `pos < safe_lengths.f` and never see an in-flight write ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_indexer/src/safe_lengths.rs)) #### `brk_mempool` - The old projected-next-block reimplementation occasionally disagreed with Bitcoin Core's `getblocktemplate` on tx ordering inside CPFP-heavy clusters (parent rates near the dust line, anchor outputs), so the BRK projection could include or exclude a tx the real next block would not. Switching `blocks[0]` to GBT verbatim eliminates the divergence by definition ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_mempool/src/steps/rebuilder/mod.rs)) - Fixed a panic in the prevout backfill when a same-cycle parent appeared after its child in the iteration order. `Prevouts::fill` now does a two-pass walk: first index every added tx into the entry pool, then resolve each `prevout: None` against either the pool or the resolver. Prevents the "child seen before parent" None-deref that crashed the rebuild thread under burst load ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_mempool/src/steps/prevouts.rs)) ### Internal Changes #### `brk_query` - Moved `urpd.rs` to its own impl module, and added a new `async.rs` providing async wrappers over the synchronous core. The cleanup folds the previously-inline UTXO Realized Price Distribution glue into a stand-alone surface and lets server handlers `.await` query calls without spawning blocking-pool wrappers per endpoint ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_query/src/impl/urpd.rs)) - Series query rewritten: `impl/series.rs` expanded by ~300 lines to thread the new `Index` newtype, range/format limits, and weight-aware truncation through every series fetch. Per-mining-endpoint files (`block_fee_rates`, `block_fees`, `block_rewards`, `block_sizes`, `block_window`, `difficulty_adjustments`, `epochs`, `hashrate`, `pools`, `reward_stats`) were rewritten on top of the new `period_start` helper ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_query/src/impl/series.rs)) #### `brk_logger` - The `rate_limit` module is rewritten (215 lines) to use a per-target sliding window with bounded memory. Previously a long-running ingest would unboundedly accumulate per-log-key counters; the new structure caps the active key set and drops the oldest on overflow ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_logger/src/rate_limit.rs)) #### `brk_iterator` - Added `examples/sigops_bench.rs` benchmarking the new sigops dispatch path against a full script walk. Used to validate the ~95-99% short-circuit rate before persisting the column ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_iterator/examples/sigops_bench.rs)) #### `brk_rpc` - `methods.rs` reorganized so each RPC wrapper (`getblock` variants, `getrawtransaction`, `getmempoolinfo`, `getrawmempool`, `getblocktemplate`, `getblockchaininfo`) lives next to its batched counterpart. `RawTx { tx, hex }` formatting was tidied for the new mempool-compat endpoints that need the raw hex alongside the parsed `bitcoin::Transaction` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_rpc/src/methods.rs)) #### `brk_server` - Crate root re-exports were reshaped from `{DEFAULT_CACHE_SIZE, DEFAULT_MAX_WEIGHT, DEFAULT_MAX_WEIGHT_LOCALHOST, ServerConfig}` to `{DEFAULT_MAX_UTXOS, DEFAULT_MAX_WEIGHT, ServerConfig}`. The per-route response cap is now expressed as a UTXO count (`max_utxos`) instead of a localhost-vs-public byte-weight pair plus a separate `quick_cache` size. `AppState` lost its `cache: Arc` field and its `last_tip` atomic, since the per-URI quick-cache is no longer the cache layer (CDN ETag pipeline handles it) ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/crates/brk_server/src/lib.rs)) #### `website` - New `scripts/explorer/address.js` and `scripts/explorer/block.js` page modules: dedicated address-detail and block-detail explorer views replacing the previous monolithic `explorer/index.js` flow. Deleted `scripts/utils/cache.js` (38 lines) since cache-busting is now handled server-side via the `Empty` extractor and `deny_unknown_fields` from beta.7 ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/website/scripts/explorer/address.js)) #### `deps` - Bumped workspace dependencies and refreshed `Cargo.lock`. Each crate's local version stepped to `0.3.0-beta.8` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.8/Cargo.toml)) [View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.3.0-beta.7...v0.3.0-beta.8) ## [v0.3.0-beta.7](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.3.0-beta.7) - 2026-04-27 ### Breaking Changes #### `brk_types` - Deleted `crates/brk_types/src/data_range.rs` and removed `DataRange` from the public surface. `DataRangeFormat`, `SeriesSelection`, and `SeriesSelectionLegacy` no longer carry a nested `range: DataRangeFormat` (or `range: DataRange`) field via `#[serde(flatten)]`. Instead, a new `with_range_format!` macro inlines `start`, `end`, `limit`, and `format` directly into each struct (with the same `s`/`from`/`f`, `e`/`to`/`t`, `l`/`count`/`c` aliases) and emits matching accessors. Downstream callers that read `selection.range.start()` must switch to `selection.start()` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.7/crates/brk_types/src/with_range_format.rs)) - `TxidsParam::from_query(query: &str)` now returns `Result` instead of `Self`. Malformed `txId[]` pairs and unknown keys produce a structured error instead of being silently dropped, so callers must propagate the new error type ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.7/crates/brk_server/src/params/txids_param.rs)) #### `brk_server` - Every path-only endpoint now takes a new `Empty` extractor (added on `/api/block/...`, `/api/blocks/...`, `/api/v1/block{,s}/...`, `/api/v1/mining/...`, `/api/v1/tx/...`, `/api/v1/address/...`, `/api/fees/...`, `/api/mempool/...`, etc.) that rejects requests carrying any query string. Hand-rolled async handlers must add the `_: Empty` parameter or the route will fail to compile ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.7/crates/brk_server/src/params/empty.rs)) ### New Features #### `brk_server` - Added the `Empty` query-string-rejecting extractor in `brk_server::params::empty`. When a client sends a path-only endpoint with `?foo=bar`, the handler short-circuits with `400 Bad Request` and a message naming the offending query. This is wired into every cacheable endpoint that does not legitimately accept query params, so injected keys can no longer bypass the URI-keyed cache ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.7/crates/brk_server/src/params/empty.rs)) - `TxidsParam::from_query` now reports a structured error string instead of silently dropping malformed pairs, so the new bulk-tx handler can return a precise `400` (e.g., `"invalid txid `xyz`: ..."` or `"unknown query parameter `foo`, expected `txId[]`"`) ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.7/crates/brk_server/src/params/txids_param.rs)) #### `brk_types` - Added the `with_range_format!` declarative macro (`crates/brk_types/src/with_range_format.rs`) that expands a struct definition by appending the shared `start` / `end` / `limit` / `format` fields plus their aliases and emits matching accessors and setters. Used to keep `DataRangeFormat`, `SeriesSelection`, and `SeriesSelectionLegacy` byte-for-byte consistent without `#[serde(flatten)]`, which is required so `#[serde(deny_unknown_fields)]` actually fires ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.7/crates/brk_types/src/with_range_format.rs)) - Refreshed the upstream `pools-v2.json` data (694 insertions / 215 deletions): added the `Noderunners` pool in slot 170 (replacing the previous `Dummy170` skip slot) and bumped `POOL_COUNT` from 170 to 171. Block-by-pool attribution now recognizes Noderunners-signed coinbases ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.7/crates/brk_types/src/pool_slug.rs)) ### Bug Fixes #### `brk_types` - `#[serde(deny_unknown_fields)]` on `SeriesSelection`, `SeriesSelectionLegacy`, and `DataRangeFormat` was silently inert because each carried a `#[serde(flatten)] range: DataRangeFormat` field, and `serde` skips unknown-field rejection through any flatten chain. Clients could append arbitrary unknown query params (`?xyz=1`) and bypass the URI-keyed CDN/cache, since the validator never failed. Eliminated the flatten via `with_range_format!`, so every selection struct now rejects unknown keys at deserialization time ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.7/crates/brk_types/src/series_selection.rs)) - Added `#[serde(deny_unknown_fields)]` to the remaining query/param structs that lacked it: `Pagination`, `SearchQuery`, `OptionalTimestampParam`, `AddrTxidsParam`, `UrpdQuery`, and the CLI `Config`. Combined with the new `Empty` extractor, this closes the last paths where an injected query param could be used to cache-bust an otherwise-cacheable response ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.7/crates/brk_types/src/pagination.rs)) #### `brk_bindgen` - The Python type-dependency walker previously kept self-references in the dep set, so a type that recursively referenced itself (e.g., a node-graph schema) produced a circular import in the emitted Python module. Self-references are now dropped at collection time (handled at emit by quoting via `current_type`), eliminating the import cycle ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.7/crates/brk_bindgen/src/generators/python/types.rs)) ### Internal Changes #### `brk_cli` - Removed every per-field `deserialize_with = "default_on_error"` from `Config`. With `deny_unknown_fields` now on the struct, silently-defaulting fields would mask the validation error, so each option falls back to `serde(default)` and lets the loader report a real error on bad input ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.7/crates/brk_cli/src/config.rs)) #### `brk_computer` - Threaded `cached_starts: &Windows<&WindowStartVec>` through additional `forced_import_with` call sites (`distribution::addr::new_addr_count`, `inputs::by_type::import`, `outputs::by_type::import`) so windowed rolling sums share the same starts cache used by the rest of the distribution pipeline, instead of recomputing per-import ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.7/crates/brk_computer/src/distribution/addr/new_addr_count.rs)) #### `clients` - Bumped the auto-generated `modules/brk-client` (npm) and `packages/brk_client` (PyPI) bindings to reflect the new validation surface: every cacheable endpoint now rejects unknown query keys, `txId[]` rejects malformed values with a 400, and the range/selection types no longer expose a `range` sub-object ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.7/modules/brk-client/index.js)) #### `scripts` - Updated `scripts/cf-purge.sh` for the tightened cache surface so post-deploy purges still cover every cacheable path under the new `Empty`-extractor regime ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.7/scripts/cf-purge.sh)) [View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.3.0-beta.6...v0.3.0-beta.7) ## [v0.3.0-beta.6](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.3.0-beta.6) - 2026-04-27 ### Breaking Changes #### `brk_mempool` - Rewrote the crate around `Mempool(Arc)`, where `Inner` holds `client`, `state: MempoolState`, and `rebuilder: Rebuilder`. The previous flat module layout is gone: behavior lives under `steps/` (`fetcher`, `preparer`, `applier`, `resolver`, `rebuilder`) and storage under `stores/` (`addr_tracker`, `entry`, `entry_pool`, `state`, `tombstone`, `tx_graveyard`, `tx_index`, `tx_store`). The previous top-level `entry.rs`, `entry_pool.rs`, `tx_store.rs`, `sync.rs`, `types/`, `block_builder/` are deleted ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_mempool/src/lib.rs)) - `MempoolState` consolidates the five sub-stores (`info`, `txs`, `addrs`, `entries`, `graveyard`) each behind its own `RwLock`. `MempoolState::apply(Pulled) -> bool` takes the five write locks in a fixed order and runs the new `Applier` step, replacing the per-store ad-hoc locking that previously lived in `sync.rs` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_mempool/src/stores/state.rs)) - Public accessors renamed: `Mempool` now exposes `info()`, `snapshot()`, `fees()`, `block_stats()`, `next_block_hash()`, `addr_state_hash()`, `txs()`, `entries()`, `addrs()`, `graveyard()`. The old `sync`/`update`/`tx_store`/`entry_pool`/`addr_tracker` getters are gone. `Mempool::start_with(after_update)` lets callers run a callback after every successful tick, and `Mempool::fill_prevouts(resolver)` exposes the prevout-resolution step to outside crates ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_mempool/src/lib.rs)) #### `brk_rpc` - Removed the dual-backend layout. The `[features]` block, `bitcoincore-rpc` backend, `corepc-client` dep, and `backend/{mod,bitcoincore,corepc}.rs` files are all deleted. The crate now depends directly on `corepc-jsonrpc`, `corepc-types`, `rustc-hash`, and `serde`/`serde_json`, and `brk_error/corepc+serde_json` is no longer optional ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_rpc/Cargo.toml)) - `Client` is now backed by `ClientInner` wrapping a `corepc-jsonrpc` `Client` over `simple_http::Builder` (pooled TCP socket with reconnect). This replaces the upstream `corepc-client` path that uses `bitreq_http`, which collapses under concurrent load. New methods `call_with_retry`, `call_once`, `call_batch`, and `call_batch_per_item` centralize retry/backoff for single and batched RPC calls ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_rpc/src/client.rs)) #### `brk_server` - `Server::new` now takes `(query, config: ServerConfig)` instead of a long positional argument list. `ServerConfig { data_path, website, cdn_cache_mode, max_weight, max_weight_localhost, cache_size }` lives in the new `crates/brk_server/src/config.rs` with `DEFAULT_MAX_WEIGHT = 4 * 8 * 10_000`, `DEFAULT_MAX_WEIGHT_LOCALHOST = 50_000_000`, and `DEFAULT_CACHE_SIZE = 1000`. `Server::new` calls `cache::init(config.cdn_cache_mode)` to set the process-global CDN mode ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_server/src/lib.rs)) - The `api/mempool_space/` namespace is removed. Its routes are reorganized into top-level modules (`addrs`, `blocks`, `fees`, `general`, `mempool`, `mining`, `transactions`) alongside the existing `metrics`, `series`, `series_legacy`, `urpd`, and `server`. `ApiRoutes::add_api_routes` mounts them at the api root, so the `/api/v1/...` URL shape is preserved but the Rust import paths change ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_server/src/api/mod.rs)) - The previous `cache.rs` single file is split into `cache/{mod,mode,params,strategy}.rs`. `CacheStrategy` now has four variants (`Tip`, `Immutable(Version)`, `Deploy`, `BlockBound(Version, BlockHashPrefix)`, `MempoolHash(u64)`). `CacheParams { etag, cache_control, cdn_cache_control }` is constructed via named constructors (`tip`, `immutable`, `block_bound`, `deploy`, `mempool_hash`, `series`, `error`). `new_not_modified` now takes `&CacheParams` and `new_json_cached` is a private free function inside the response module ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_server/src/cache/mod.rs)) #### `brk_types` - Moved `Etag` out of `brk_types` and into `brk_server::etag` (`brk_types/src/etag.rs` deleted). Downstream consumers that imported `brk_types::Etag` must now import `brk_server::etag::Etag` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_types/src/lib.rs)) - `TxIn.witness` field changed from `Vec` to the new `Witness` newtype. Callers that read the field as a vector of hex strings must switch to the `Witness` API (`len`, `last`, `second_to_last`, iteration over `&[u8]` items). The on-the-wire JSON shape is preserved by `Witness`'s `Serialize`/`Deserialize`, but Rust-level field access is a breaking change ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_types/src/txin.rs)) #### `brk_error` - Dropped the `BitcoinRPC(#[from] bitcoincore_rpc::Error)` variant alongside the corresponding feature flag, since `bitcoincore-rpc` is no longer a backend. The `CorepcRPC` variant now wraps `corepc_jsonrpc::error::Error` instead of `corepc_client::client_sync::Error`. Callers matching on `Error::BitcoinRPC` must drop the arm; callers matching on `Error::CorepcRPC` must update the inner type ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_error/src/lib.rs)) #### `brk_server` - Deleted `crates/brk_server/src/params/limit_param.rs`. The `LimitParam` struct (which wrapped a single `limit: Limit` query field via `serde(default)`) is removed from the public surface. Routes that needed limit handling now reach it through `DataRangeFormat` / per-route param structs aligned with the new `with_range_format!` macro pipeline ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_server/src/params/mod.rs)) ### New Features #### `brk_server` - Added `CdnCacheMode` with `Live` (`Cache-Control: public, max-age=1, stale-if-error=300`) and `Aggressive` (`public, max-age=31536000, immutable`, RFC 9213 style). The selected mode is stored in a process-global `OnceLock` initialized once via `cache::init`, and `CacheParams::series(version, total, end, hash)` automatically picks `Live` for tail-bound series and the configured mode for fully-historical responses ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_server/src/cache/mode.rs)) - Per-request weight caps: `State::max_weight_for(addr)` returns the localhost cap for loopback peers and the public cap for everyone else, with both numbers configurable. Series and bulk endpoints consult this before serving large responses, so an operator can run a generous local CLI alongside a tighter public endpoint without redeploying ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_server/src/state.rs)) - New RBF endpoints aligned with mempool.space's surface: `/api/v1/tx/:txid/rbf` returns `RbfResponse { tx: RbfTx, replacements: Option> }`, including the BIP-125 `rbf` signal and a `fullRbf` flag per node. The endpoint is wired through `brk_server::api::transactions` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_server/src/api/transactions.rs)) #### `brk_cli` - Configuration gained four new fields: `cdn` (`Live` / `Aggressive`), `maxweight`, `maxweightlocal`, and `cachesize`. They are exposed via `Config::cdn_cache_mode()`, `max_weight()`, `max_weight_localhost()`, and `cache_size()`, and threaded into `ServerConfig` on startup ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_cli/src/config.rs)) - The CLI now starts the mempool via `Mempool::start_with(|| query_clone.sync(|q| q.fill_mempool_prevouts()))` so every successful update triggers a prevout backfill against the indexer without exposing the resolver to user code ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_cli/src/main.rs)) #### `brk_logger` - Default `RUST_LOG` directives dropped the `bitcoincore_rpc=off` filter (no longer linked) and the standalone `bitcoin=off` (the `corepc=off` filter covers the surviving traces from the new RPC stack) ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_logger/src/lib.rs)) #### `brk_query` - Added `fill_mempool_prevouts() -> bool` on `Query`. For each mempool tx with a `prevout == None`, it resolves the predecessor via `txid_prefix_to_tx_index`, walks `first_txout_index_reader` + `vout` to read `output_type`, `type_index`, and `value`, and reconstructs `script_pubkey`. Returns `true` when at least one prevout was filled, so callers can short-circuit no-op ticks ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_query/src/impl/mempool.rs)) - Added `tx_rbf(&Txid) -> Result` returning the BIP-125 replacement tree for a given txid, complete with descendant replacements. The CPFP ancestor walk now deduplicates visited nodes via `FxHashSet` and aggregates `package_fee` and `package_vsize` alongside the existing single-tx metrics, eliminating an O(n^2) revisit pattern on dense clusters ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_query/src/impl/mempool.rs)) #### `brk_types` - Added the `Witness(bitcoin::Witness)` newtype with `Serialize` / `Deserialize` impls that round-trip as `Vec` (hex per item), matching the JSON-RPC convention. Used by the new RBF/Tx response types ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_types/src/witness.rs)) - `Tx` gained `encode_bytes() -> Vec` (consensus-encode round-trip through `bitcoin::Transaction`) and `total_sigop_cost()` (legacy + P2SH redeem-script + witness sigops, using saturating arithmetic). Added `impl From<&Transaction> for bitcoin::Transaction` so server code can hand BRK txs to any `rust-bitcoin` consumer ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_types/src/tx.rs)) - New `RbfTx`, `ReplacementNode`, and `RbfResponse` types under `brk_types::rbf`, shaped to match mempool.space's `/api/v1/tx/:txid/rbf` response and powering the new endpoint ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_types/src/rbf.rs)) #### `brk_mempool` - New `Tombstone` store records every tx that leaves the mempool, the reason (`Removal` vs `Replacement`), and the block hash that evicted it. `Applier::apply` moves removals into the graveyard, hydrates `Revived` additions by `exhume()`-ing from the graveyard when a previously-evicted txid reappears, and evicts old tombstones when the graveyard exceeds its bound ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_mempool/src/steps/applier.rs)) - `Mempool::fill_prevouts(resolver)` runs an external prevout resolver against the current entry pool and rewrites missing `TxIn::prevout` fields under a single write lock. Used by the CLI to pull prevouts from the indexer after every successful update ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_mempool/src/lib.rs)) ### Bug Fixes #### `brk_query` - The CPFP ancestor walk in `cpfp_info_for_tx` previously revisited shared ancestors, double-counting fees and vsize on diamond-shaped clusters. Visited nodes are now tracked in an `FxHashSet`, so `package_fee` and `package_vsize` reflect each ancestor exactly once ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_query/src/impl/mempool.rs)) #### `brk_server` - `new_not_modified` previously took a bare `&Etag` and inconsistently set CDN headers, so 304 responses from `Live`-mode endpoints could be cached aggressively by upstream CDNs. It now takes the full `CacheParams` and emits the matching `Cache-Control` and `CDN-Cache-Control` headers for every 304 ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_server/src/extended/response.rs)) - `Error::into_response_with_etag` now takes an owned `Etag` (was `&str`) and routes through `CacheParams::error(etag)`, and the bare `IntoResponse for Error` impl calls `CacheParams::apply_error_cache_control(headers)`. Error responses now ship matching `Cache-Control` and `CDN-Cache-Control` headers under both code paths, so a 4xx/5xx from a `Live`-mode route is no longer cacheable by an upstream CDN even when the previous helper path was bypassed ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_server/src/error.rs)) ### Internal Changes #### `brk_server` - `static_json` and `static_bytes` now go through `CacheParams::deploy()` so all bundled-website assets share the same deploy-versioned ETag and cache profile. Error responses route through `CacheParams::error(etag) -> apply_error_cache_control(headers)` to set short TTLs without forking the response builder ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_server/src/extended/response.rs)) - `crates/brk_server/src/etag.rs` introduces `Etag(String)`, replacing the `brk_types::Etag` that was deleted in the same release. Keeping the type next to `CacheParams` lets the cache layer evolve without touching the shared types crate ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_server/src/etag.rs)) #### `brk_types` - `Hex` newtype gained `Deref`, `AsRef`, `AsRef<[u8]>`, `Display`, `From`, `From for String`, and an inherent `as_str()` so call sites that need a borrowed `&str` or `&[u8]` no longer round-trip through `.into()` / `.to_string()` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_types/src/hex.rs)) - Added `From<&TxIn> for bitcoin::TxIn`, `From<&TxOut> for bitcoin::TxOut`, and `From for bitcoin::transaction::Version`. BRK shapes hand off to `rust-bitcoin` consumers without re-encoding through hex, so the RBF / CPFP / sigops paths can build a `bitcoin::Transaction` directly from the indexed form. Separately, `Vin` gained `From for usize` for `Vec` slice indexing ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_types/src/txin.rs)) #### `brk_mempool` - The block-builder pipeline (`graph`, `linearize/sfl`, `partitioner`, `package`, `pool_index`, `tx_node`, `projected_blocks/{fees,snapshot,stats,verify}`) moved under `steps/rebuilder/block_builder/` and `steps/rebuilder/projected_blocks/` so the rebuild step owns its own data. The `mempool.rs` example was updated to drive the new `start_with` shape ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/crates/brk_mempool/src/steps/rebuilder/mod.rs)) #### `scripts` - Added `scripts/cf-purge.sh`, a Cloudflare API helper for invalidating cached URLs when running in `CdnCacheMode::Aggressive`. The script reads `CF_ZONE_ID` and `CF_API_TOKEN` from the environment and POSTs a `purge_cache` request for the paths supplied on the command line ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/scripts/cf-purge.sh)) #### `website` - Upgraded Lightweight Charts from 5.1.0 to 5.2.0. The new bundle ships under `modules/lightweight-charts/5.2.0/`, and the 5.1.0 tree is deleted. Explorer chart and DOM helpers (`scripts/explorer/chain.js`, `scripts/explorer/index.js`, `scripts/utils/chart/index.js`, `scripts/utils/dom.js`) were adapted to the 5.2.0 API surface, and `styles/chart.css`, `styles/components.css`, `styles/elements.css`, `styles/main.css`, and `styles/panes/explorer.css` were updated to match ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/website/scripts/utils/chart/index.js)) #### `clients` - Bumped the auto-generated `modules/brk-client` (npm) and `packages/brk_client` (PyPI) bindings to match the new server surface (RBF endpoint, renamed mempool routes, removed `mempool_space/` paths) ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.6/modules/brk-client/index.js)) [View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.3.0-beta.5...v0.3.0-beta.6) ## [v0.3.0-beta.5](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.3.0-beta.5) - 2026-04-23 ### Breaking Changes #### `brk_computer` - Reorganized the `distribution::addr` module: the bespoke `addr_count`, `exposed/count`, `exposed/supply`, and `reused/count` files are gone, replaced by shared `count/` and `supply/` submodules (`AddrCountsVecs`, `AddrCountFundedTotalVecs`, `AddrSupplyVecs`, `AddrSupplyShareVecs`, `AddrTypeToAddrCount`, `AddrTypeToSupply`) reused by exposed, reused, and respent. The old type aliases `AddrTypeToExposedAddrCount`, `AddrTypeToExposedSupply`, `AddrTypeToReusedAddrCount` are removed from the public surface ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_computer/src/distribution/addr/mod.rs)) - Renamed `ReusedAddrEventsVecs` to `AddrEventsVecs` and `AddrTypeToReusedAddrEventCount` to `AddrTypeToAddrEventCount` since the same struct now backs both reused and respent metrics ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_computer/src/distribution/addr/reused/events/mod.rs)) - `ReusedAddrVecs::forced_import` and the supporting count/events/supply importers now take a `name: &str` parameter (`"reused"` vs `"respent"`) so the same vec layout can be instantiated twice under different on-disk prefixes ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_computer/src/distribution/addr/reused/mod.rs)) #### `brk_rpc` - Feature flags are now `default = ["corepc"]`, `bitcoincore-rpc`, and `corepc`. Builds that previously enabled both backends must pick one ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_rpc/Cargo.toml)) ### New Features #### `brk_computer` - Address-reuse tracking now has a spend-side counterpart, "respent": addresses with `spent_txo_count > 1` are tracked alongside the existing receive-side `funded_txo_count > 1` reused metric. `distribution::vecs::AddrMetricsVecs` gained a `respent: ReusedAddrVecs` field that stores funded/total count, output/input events, share of outputs and inputs, supply, and supply share under the `respent_*` on-disk prefix ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_computer/src/distribution/vecs.rs)) - Reused tracking gained the `supply` and `supply_share` fields previously exclusive to exposed addresses, so both predicates now report total balance held by the funded subset plus its share of all-supply and per-type supply ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_computer/src/distribution/addr/reused/mod.rs)) - Introduced `AddrMetricsState` as a single per-block runtime bundle covering funded/empty counts, activity counters, exposed/reused/respent state, and pre/post snapshots (`AddrReceivePreState`, `AddrSendPreState`). The block loop calls `state.on_receive_applied` / `state.on_send_applied` instead of threading thirteen separate mutable counters through `process_received` and `process_sent`. `From<(&AddrMetricsVecs, Height)>` rebuilds the bundle from disk when resuming a run ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_computer/src/distribution/addr/state.rs)) - Added named state structs `ExposedAddrState` and `ReusedAddrState` with explicit `on_receive_as_reused`, `on_receive_as_respent`, `on_send_as_reused`, `on_send_as_respent` methods. The respent threshold-crossing logic correctly handles the case where the 2nd-ever spend also empties the address (skips the funded-count bump so the `will_be_empty` decrement doesn't underflow) ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_computer/src/distribution/addr/reused/state.rs)) #### `brk_mempool` - Projected blocks now expose intra-cluster ordering: `Package` carries `cluster_id` and `chunk_order` so the partitioner's look-ahead can refuse to pull a child chunk into an earlier block than its parent chunk. `partition_into_blocks` maintains a per-cluster `cluster_next` cursor and skips any candidate whose `chunk_order` is past its cluster's next-expected slot, eliminating a class of CPFP-ordering bugs in the projected next-block view ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_mempool/src/block_builder/partitioner.rs)) - Debug builds run a new `projected_blocks::verify::Verifier::check` after each projection. It asserts parents-before-children inside every package, no duplicate txids across blocks, each package's stored vsize equals the sum of its tx vsizes, and non-final blocks fit within `BLOCK_VSIZE`. It also pulls Bitcoin Core's `getblocktemplate` and logs the Jaccard overlap and fee delta (in bps) between BRK's next block and Core's ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_mempool/src/projected_blocks/verify.rs)) #### `brk_rpc` - Added `Client::get_block_template_txs()` on both backends. It calls Core's `getblocktemplate` with the `segwit` rule and returns `Vec` (txid + fee in sats), powering the new mempool projection verifier ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_rpc/src/lib.rs)) #### `brk_types` - `FundedAddrData` gained `is_respent()` (true when `spent_txo_count > 1`), `respent_supply_contribution()` (balance if currently funded AND respent, else zero), and `reused_supply_contribution()` (same gate on `is_reused()`). These power the new shared address-supply tracking and align documentation by describing `is_reused` as the receive-side proxy and `is_respent` as the spend-side counterpart ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_types/src/funded_addr_data.rs)) #### `website` - Reused subtrees (per cohort and the network-wide chart) now overlay two series per chart: 2+ Funded (reused, primary color) and 2+ Spent (respent, gray). The Funded / Total / Outputs / Inputs sections each show the pair, and new Supply + Share sections plot funded supply and share-of-supply for both predicates. The previous "Compare" tab is replaced by combining funded and total into the dedicated tabs ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/website/scripts/options/shared.js)) - Refreshed the site favicon and PWA icon set: new `favicon.svg`, `favicon-96x96.png`, `favicon.ico`, `apple-touch-icon.png`, and `web-app-manifest-{192,512}x512.png` under `/assets/favicon/`, plus new `logo.svg`, `logo-light.svg`, `logo-dark.svg`, `logo-orange.svg` under `/assets/logo/`. The OpenAPI Scalar HTML, `manifest.webmanifest`, and `index.html` now point at the new paths, and the old `manifest-icon-*` and `apple-icon-180.png` files are deleted ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/website/index.html)) ### Bug Fixes #### `brk_query` - `urpd_dates` now sorts cohorts via `sort_by_key(|a| a.to_string())` rather than building two `to_string()` values per comparison, and `urpd_at` no longer takes an unused `agg` parameter (the aggregation is applied in the downstream URPD builder, not the dates lookup) ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_query/src/impl/urpd.rs)) #### `brk_mempool` - SFL's `recurse` function got an `#[allow(clippy::too_many_arguments)]` shim so the lint stops firing on the linearization solver's hot path ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_mempool/src/block_builder/linearize/sfl.rs)) ### Internal Changes #### `brk_computer` - The block loop simplifies dramatically: the bespoke "recover seven separate counters from height" block in `compute/block_loop.rs` collapses to `AddrMetricsState::from((&vecs.addrs, starting_height))`, and `process_received` / `process_sent` lost their nine and ten parameters respectively in favour of `state: &mut AddrMetricsState` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_computer/src/distribution/compute/block_loop.rs)) - `AddrMetricsVecs` gained `reset_height`, `min_stateful_len`, `par_iter_stateful_height_mut`, `par_iter_height_mut`, and `push_height(state, active_addr_count)` aggregators so the rest of the pipeline no longer needs to iterate the funded/empty/activity/reused/respent/exposed vecs individually ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_computer/src/distribution/vecs.rs)) #### `brk_mempool` - The block builder switched from `for cluster in clusters` to enumerating `(cluster_id, cluster)` and `(chunk_order, chunk)`, propagating the indices through `singleton_package` and `chunk_to_package`. A `take` helper centralizes the partitioner's debug_assert that the picked chunk matches `cluster_next[cluster_id]` before bumping the cursor ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_mempool/src/block_builder/linearize/mod.rs)) #### `brk_server` - Open Graph and Twitter card images on the Scalar API docs page now point at `/assets/favicon/web-app-manifest-512x512.png` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/crates/brk_server/src/api/scalar.html)) #### `website` - `network.js` rebuilt the network-level reused subtree (`reusedSubtreeForAll`) to share the same overlay pattern as the per-cohort `reusedSubtree` in `shared.js`. `distribution/index.js` now passes `cohort.respent` into `reusedSubtree`, and `distribution/data.js` exposes `respent` alongside `reused` on each cohort entry ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.5/website/scripts/options/network.js)) [View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.3.0-beta.4...v0.3.0-beta.5) ## [v0.3.0-beta.4](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.3.0-beta.4) - 2026-04-22 ### Breaking Changes #### `brk_types` - Renamed the cost-basis distribution surface to URPD (UTXO Realized Price Distribution): `CostBasisBucket` is now `UrpdAggregation`, `CostBasisDistribution` is now `UrpdRaw`. `CostBasisValue` and `CostBasisFormatted` are gone from the public crate root (the legacy server route keeps a private copy with a 2027-01-01 sunset). A fresh `Urpd` struct (`cohort`, `date`, `aggregation`, `close`, `total_supply`, `buckets`) and a `UrpdBucket` struct (`price_floor`, `supply`, `realized_cap`, `unrealized_pnl`) replace the old map-of-floats shape, so each bucket now carries supply (BTC), realized cap contribution (USD = `price_floor * supply`), and unrealized P&L vs that date's close (USD, can be negative) in one response ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_types/src/urpd.rs)) - Every age-bucket slug in the `Cohort` schemars enum gained a `utxos_` prefix: `under_1h_old` is now `utxos_under_1h_old`, the 18 in-between buckets follow the same rule, and `over_15y_old` is `utxos_over_15y_old`. OpenAPI consumers that hard-coded the old slugs need to update their cohort lists ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_types/src/cohort.rs)) - `Cohort` gained `Debug + Clone + Serialize` derives so it can travel through API response bodies (the new `Urpd.cohort` field requires it), not just inbound path params ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_types/src/cohort.rs)) #### `brk_query` - The `cost_basis_*` methods (`cost_basis_cohorts`, `cost_basis_distribution`, `cost_basis_dates`, `cost_basis_formatted`) were replaced by URPD-named equivalents: `urpd_cohorts() -> Vec`, `urpd_dates(&Cohort) -> Vec`, `urpd_raw(&Cohort, Date) -> UrpdRaw`, `urpd_at(&Cohort, Date, UrpdAggregation) -> Urpd`, and a new `urpd_latest(&Cohort, UrpdAggregation) -> Urpd`. Cohorts are now returned as typed `Cohort` values instead of `String`. The cohort directory layout on disk moved from `utxo_{cohort}_cost_basis/by_date/{date}` to `{cohort}/urpd/{date}` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_query/src/impl/urpd.rs)) #### `brk_server` - `/api/series/cost-basis`, `/api/series/cost-basis/{cohort}/dates`, and `/api/series/cost-basis/{cohort}/{date}` are now marked deprecated with a 2027-01-01 sunset and live in a dedicated `api/series/cost_basis.rs` module behind `add_cost_basis_legacy_routes()`. The canonical replacements live under `/api/urpd` (see new features) ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_server/src/api/series/cost_basis.rs)) - `params/cost_basis_params.rs` (`CostBasisParams`, `CostBasisCohortParam`, `CostBasisQuery`) was removed from the public `params` re-exports; the legacy route keeps its own private copies. Downstream code that imported the public types must migrate to `UrpdParams` / `UrpdCohortParam` / `UrpdQuery` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_server/src/params/mod.rs)) #### `brk_computer` - Internal renames across the whole crate: the `internal::amount` module is now `internal::value`, `AmountPerBlock` is `ValuePerBlock`, `AmountBlock` is `ValueBlock`, `AmountPerBlockCumulative` / `AmountPerBlockCumulativeRolling` / `AmountPerBlockWithDeltas` / `LazyAmountPerBlock` follow the same `Amount→Value` rename. `LazyRollingDeltasFromHeight` (the amount-typed delta) is now `LazyRollingDeltasAmountFromHeight`, paired with a new `LazyDeltaAmountFromHeight`. Touches `distribution/`, `cointime/supply/`, `mining/rewards/`, `outputs/value/`, `supply/`, `supply/burned/`, `investing/`, `transactions/volume/`, and `pools/major.rs` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_computer/src/internal/per_block/value/base.rs)) - `internal::CentsType` (the trait associating a cents type with its `ToDollars` transform) is now `internal::FiatType`. All `FiatPerBlock` / `FiatPerBlockCumulativeWithSums` / `LazyFiatPerBlock` / `LazyRollingSumsFiatFromHeight` / `LazyDeltaFiatFromHeight` / `FiatPerBlockWithDeltas` bounds use the new name ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_computer/src/internal/per_block/fiat/base.rs)) - The on-disk URPD layout moved from `utxo_{cohort_id}_cost_basis/by_date/{date}` to `utxo_{cohort_id}/urpd/{date}` (per-date snapshots) and from `utxo_{cohort_id}_cost_basis/by_height/{height}` to `utxo_{cohort_id}/cost_basis/{height}` (per-height state). Existing computed state directories must be re-generated or moved before upgrading ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_computer/src/distribution/cohorts/utxo/percentiles.rs)) ### New Features #### `brk_server` - New `/api/urpd` top-level route group with four endpoints: `GET /api/urpd` (list cohorts, returns `Vec`), `GET /api/urpd/{cohort}/dates` (history), `GET /api/urpd/{cohort}` (latest snapshot), and `GET /api/urpd/{cohort}/{date}` (specific snapshot). Each (cohort, date) response carries close price, total supply, and per-bucket `{price_floor, supply, realized_cap, unrealized_pnl}` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_server/src/api/urpd/mod.rs)) - Bucketing now flows through a single `agg` query parameter (with `bucket` accepted as an alias for the legacy clients) supporting `raw`, `lin200` / `lin500` / `lin1000` (linear, $200 / $500 / $1000 wide), and `log10` / `log50` / `log100` / `log200` (logarithmic, N bins per price decade). The aggregation echoes back on the response under `aggregation` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_server/src/params/urpd_params.rs)) - New OpenAPI `URPD` tag with a long-form description covering the response shape, bucket semantics, and the discovery flow (cohorts, latest, dates, specific). Added a `urpd_tag()` helper on the `TransformOperation` extension ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_server/src/api/openapi/mod.rs)) - `Metrics` OpenAPI tag is now flagged `deprecated: true` via the `extensions` map, so Swagger UI consumers see the entire legacy section greyed out ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_server/src/api/openapi/mod.rs)) #### `brk_types` - `Sats` gained `ONE_BTC_I128: i128 = 100_000_000` and `as_i128(self) -> i128` so signed-sats math can stay in `i128` without going through `i64::from`/`i128::from` casts ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_types/src/sats.rs)) #### `brk_computer` - Added `LazyDeltaAmountFromHeight` (a sats-stored absolute change vec paired with a lazy BTC view) and `LazyRollingDeltasAmountFromHeight` (the four-window absolute / rate analog of the existing fiat-typed `LazyRollingDeltasFiatFromHeight`). Powers the new `SupplyBase.delta` shape and unlocks the `AmountDeltaPattern` consumed by the website's per-cohort change charts ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_computer/src/internal/per_block/rolling/delta.rs)) #### `website` - New website helpers in `scripts/options/shared.js`: `amount({ pattern, ... })` and `amountBaseline({ pattern, ... })` emit paired `btc` + `sats` line / baseline series from a single `{ btc, sats }` pattern, removing the per-call `Unit.sats` plumbing for amount-typed cohort series ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/website/scripts/options/shared.js)) - New `amountSumsTreeBaseline({ windows, title, metric, legend })` builder in `scripts/options/series.js` mirrors `sumsTreeBaseline` for the `AmountPattern` shape, emitting per-window compare + per-window detail charts that show both BTC and sats ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/website/scripts/options/series.js)) - Distribution Holdings options switched to the new amount-typed delta plumbing: `singleAmountDeltaItems` and `groupedAmountDeltaItems` replace the prior `Unit.sats`-only path for Supply Change and Growth Rate charts, so the change views now expose both sats and lazy BTC per rolling window ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/website/scripts/options/distribution/holdings.js)) - New JSDoc typedefs: `AmountDeltaPattern` (= `Brk.AbsoluteRatePattern3`) and `AmountPattern` (= `Brk.BtcSatsPattern`), surfacing the bindgen split between fiat-typed and amount-typed rolling deltas ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/website/scripts/_types.js)) ### Internal Changes #### `brk_computer` - `utxo/percentiles.rs` now derives the on-disk directory name via `CohortContext::Utxo.prefixed(name.id)` instead of inlining `format!("utxo_{name}")`, keeping all UTXO-cohort path construction in one helper ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_computer/src/distribution/cohorts/utxo/percentiles.rs)) - `CostBasisRaw` dropped the redundant `by_height` subdirectory and the matching `path_by_height` helper; state files now live directly under `{cohort}/cost_basis/{height}` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_computer/src/distribution/state/cost_basis/data.rs)) #### `brk_server` - Added `rustc-hash` workspace dep so the legacy cost-basis bucket aggregator can use `FxHashMap` for its in-route accumulator without pulling the dep through `brk_query` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/crates/brk_server/Cargo.toml)) #### `website` - `panes/chart.js` and `utils/chart/index.js` now drive the chart's candlestick body, border, and wicks from `colors.default` + the new `colors.background` foreground/background pair (previously hard-coded to the bi-color `p1` palette). Adds a `priceLineColor` and turns on `borderVisible` so candle outlines render in the chart's foreground color ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/website/scripts/panes/chart.js)) - `colors.background` exposes the CSS `--background-color` variable as a chart-ready `Color` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.4/website/scripts/utils/colors.js)) [View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.3.0-beta.3...v0.3.0-beta.4) ## [v0.3.0-beta.3](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.3.0-beta.3) - 2026-04-22 ### Breaking Changes #### `brk_types` - Renamed every public "investor" symbol to "capitalized": `Cents::to_investor_cap` is now `to_capitalized_cap`, `CentsSats::to_investor_cap` is now `to_capitalized_cap`, `CostBasisSnapshot.investor_cap` is now `capitalized_cap_raw`, and `FundedAddrData.investor_cap_raw` is now `capitalized_cap_raw`. The on-the-wire byte layout of `FundedAddrData` is preserved, but every consumer that names the field has to migrate ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_types/src/funded_addr_data.rs)) - `FundedAddrData::exposed_supply_contribution` now returns `Sats` instead of `u64`, eliminating the implicit `u64::from` at every call site ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_types/src/funded_addr_data.rs)) - `MempoolBlock.block_size` is now the serialized byte size (witness + non-witness), not `total_vsize * 4`. `MempoolBlock::new` gained a `total_size: u64` parameter that callers must thread through ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_types/src/mempool_block.rs)) - `Cohort`'s schemars annotation switched from per-example hints to an explicit `extend("enum" = [...])` listing all 22 valid cohort slugs (`all`, `sth`, `lth`, the 18 age buckets, and `over_15y_old`), so OpenAPI consumers get a proper string enum instead of free-form text with examples ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_types/src/cohort.rs)) - `TxWithHex` moved out of `brk_types` (deleted from `lib.rs` re-exports) and into `brk_mempool::types::TxWithHex`; downstream code that imported `brk_types::TxWithHex` now needs `brk_mempool::TxWithHex` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_mempool/src/types/tx_with_hex.rs)) #### `brk_rpc` - Renamed `Client::get_mempool_transaction(txid) -> Result` to `Client::get_mempool_raw_tx(txid) -> Result<(bitcoin::Transaction, String)>`. The wrapper no longer resolves prevouts or spawns per-input `get_tx_out` calls. Address resolution and `TxIn`/`TxOut` reconstruction moved into `brk_mempool::sync::build_transaction`, which batches one `get_raw_transaction` per unique parent txid via an `FxHashMap` cache (huge win when a mempool descendant has many in-mempool ancestors) ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_rpc/src/lib.rs)) #### `brk_mempool` - Replaced `selector.rs` (114 lines) and `heap_entry.rs` (66 lines) with a new `linearize` module implementing cluster-mempool linearization. The inner SFL algorithm (`linearize/sfl.rs`, 264 lines) does a topologically-closed-subset search, optimal for clusters up to 18 txs and near-optimal beyond. Comes with a fresh test suite under `linearize/tests/{basic,oracle,stress}.rs` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_mempool/src/block_builder/linearize/mod.rs)) - `build_projected_blocks` now returns `Vec>` instead of `Vec>`. Blocks 1 through `NUM_BLOCKS - 1` are still ~1MB blocks sorted by placement rate, but the eighth block is now a catch-all containing every remaining package so no low-rate tx silently disappears (matches mempool.space's last-block behavior). `SelectedTx` is gone; `Package` (with `fee_rate`, ordered `txs`, aggregate sats / vsize) is the new selection unit ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_mempool/src/block_builder/mod.rs)) - `Snapshot::build` and `compute_block_stats` now take `&[Package]` instead of `&[SelectedTx]`; per-tx fee rate in the percentile distribution comes from the containing package's `fee_rate` (the rate the miner actually collects per vsize) ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_mempool/src/projected_blocks/stats.rs)) #### `brk_computer` - Mass rename of the "investor" cohort metric to "capitalized": `RealizedInvestor` is now `RealizedCapitalized`, `realized.investor.*` is now `realized.capitalized.*`, and the on-disk vec names `investor_price` / `investor_cap_raw` were renamed to `capitalized_price` / `capitalized_cap_raw`. The `CostBasisData` accumulator trait methods (`set_investor_cap_raw`, `increment_snapshot`, `decrement_snapshot`) and `RealizedFullAccum` follow the same rename ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_computer/src/distribution/metrics/realized/full.rs)) - `RelativeFull.supply_in_profit_to_own` and `supply_in_loss_to_own` are now `supply_in_profit_share` / `supply_in_loss_share`. The disk-stored series gain a `_share` suffix and lose `_to_own` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_computer/src/distribution/metrics/relative/full.rs)) - Deleted the `RelativeToAll` module. `supply_to_circulating`, `supply_in_profit_to_circulating`, and `supply_in_loss_to_circulating` are no longer emitted from the per-cohort tier; they have a single replacement in the new `ExposedSupplyShareVecs` at the exposed-supply level (see new features). `RelativeWithExtended` swapped out the `rel_to_all` field for the new `invested_capital` field ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_computer/src/distribution/metrics/relative/mod.rs)) - Renamed `AddrTypeToExposedAddrSupply` to `AddrTypeToExposedSupply`, with matching parameter name changes (`exposed_addr_supply` → `exposed_supply`) in the block-cohort received / sent compute paths ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_computer/src/distribution/addr/exposed/supply/mod.rs)) - `Cohorts::compute_rest_part2` (and the matching impls in `cohorts/addr/groups.rs`, `cohorts/addr/vecs.rs`, `cohorts/traits.rs`, `cohorts/utxo/groups.rs`) now takes `all_supply_sats: &impl ReadableVec` next to the existing `all_utxo_count`, dropping the per-call `HM: ReadableVec + Sync` generic in favor of an `impl Trait` parameter ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_computer/src/distribution/cohorts/traits.rs)) ### New Features #### `brk_mempool` - `BlockStats.total_size` and `Entry.size` track the actual serialized byte size (witness + non-witness, from the raw tx hex) alongside vsize, so projected-block JSON now reports both the byte size and vsize ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_mempool/src/projected_blocks/stats.rs)) - `Sync::fetch_new_txs` was rewritten as a builder pipeline. The new `build_transaction(entry, mempool_txs)` reads raw hex once via `get_mempool_raw_tx`, scans all inputs to collect the unique set of non-mempool parent txids, fetches each parent exactly once via `get_raw_transaction` (no per-input `get_tx_out` round-trip), and uses the cached prev-outputs to assemble `TxIn` / `TxOut` directly. Sigops are precomputed via `bitcoin::Transaction::total_sigop_cost` for the new `Entry.size` field ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_mempool/src/sync.rs)) - Added `impl From for u64` so callers reading mempool indices in bulk no longer need the `as_usize() as u64` two-step ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_mempool/src/types/tx_index.rs)) #### `brk_computer` - Added `AvgAmountMetrics` (`avg_utxo_amount` = supply / utxo count, `avg_addr_amount` = supply / funded addr count) computed per `all` + per address type. Each pair carries sats and cents/usd lazy slots, mirroring the `AmountPerBlock` shape. Wired into `distribution::vecs::Vecs.addrs.avg_amount` via the new `WithAddrTypes` impl ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_computer/src/distribution/metrics/supply/avg_amount.rs)) - Added `ExposedSupplyShareVecs` (`exposed_supply_share` series, BasisPoints16), exposing the share of exposed (revealed-pubkey, quantum-risk) supply relative to total supply for `all` + per address type. Lives under `addrs.exposed.supply_share` and is computed against the `all` UTXO-cohort supply and the per-type UTXO-cohort supply, respectively ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_computer/src/distribution/addr/exposed/supply/share.rs)) - Added `RelativeInvestedCapital`: `invested_capital_in_profit_share` and `invested_capital_in_loss_share` (= invested-capital / own realized cap, in BasisPoints16). Computed for cohorts that already track `UnrealizedFull` (`all`, `sth`, `lth`) ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_computer/src/distribution/metrics/relative/invested_capital.rs)) - Added `SatsSignedToBitcoin` UnaryTransform so a signed-sats delta vec can be lazily projected to a btc-typed view, completing the `Sats` / `SatsSigned` / `Bitcoin` triangle ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_computer/src/internal/transform/currency.rs)) - Added `RatioCentsBp16` BinaryTransform (Cents/Cents → BasisPoints16), used by `RelativeInvestedCapital` and `ExposedSupplyShareVecs` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_computer/src/internal/transform/ratio.rs)) #### `brk_types` - `Sats` division was rewritten to use `checked_div`: `Sats / Dollars` returns `Sats::MAX` on a zero denominator, `Sats / Sats` and `Sats / usize` return `0`, and the new `Div for Sats` impl lets cents-precision vecs divide by stored u64 lookups without manual casts ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_types/src/sats.rs)) - `OutputType` adds `#![allow(unreachable_patterns)]` with a reason explaining that `P2PK65` and `P2PK33` both serialize as `'p2pk'`, silencing the new lint without changing semantics ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_types/src/output_type.rs)) #### `brk_query` - `block_info`'s `avg_fee` now uses `total_fees_u64.checked_div(non_coinbase).unwrap_or(0)` instead of an explicit `non_coinbase > 0` branch, so an all-coinbase block returns 0 rather than relying on the inner guard ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_query/src/impl/block/info.rs)) - `block_window`'s `avg_value` switched to `checked_div(block_count)` and skips the result when the divisor is zero, so partial windows at the chain tip don't push a divide-by-zero result ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_query/src/impl/mining/block_window.rs)) - The pool ranking and the bindgen pattern detector both switched from `sort_by(|a, b| b.cmp(&a))` to `sort_by_key(|x| Reverse(...))` — fewer lines, stable, and explicit about descending order ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_query/src/impl/mining/pools.rs)) #### `brk_server` - Added `state.date_cache(version, date) -> CacheStrategy` — returns `Immutable(version)` when `date < Date::from(indexed_tip_timestamp)`, otherwise `Tip`. Cost-basis per-date responses use this so a historical date's response is `must-revalidate, max-age=1` until the tip crosses the day boundary, then becomes long-lived immutable ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_server/src/state.rs)) - Added deprecated `/api/metrics/cost-basis`, `/api/metrics/cost-basis/{cohort}/dates`, and `/api/metrics/cost-basis/{cohort}/{date}` routes that delegate to the same handlers but mark the operation `.deprecated()` and document a `Sunset date: 2027-01-01`. Cost-basis routes have moved to `/api/series/cost-basis/*` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_server/src/api/metrics/mod.rs)) #### `website` - New `scripts/explorer/cube.js` — a pure-CSS HTML cube generator. Populates a `.cube` element with 15 face divs (3 glass-rear, 3 liquid-rear, 3 liquid-front, 3 glass-front, 3 face-text) styled via `explorer.css` transforms. Replaces the earlier SVG-based renderer that broke in Safari due to its long-standing `` transform bugs ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/website/scripts/explorer/cube.js)) - Cube DOM nodes are now `HTMLAnchorElement` (``) instead of `
`, so deep-link navigation works without JavaScript-only handlers. `chain.js` updates `findCube`, `selectCube`, scroll detection, and the IntersectionObserver to operate on anchors ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/website/scripts/explorer/chain.js)) - Added `scripts/options/shared.js::exposedSubtree(exposed, key, title)` — a reusable builder for the Compare / Funded / Total / Supply / Share layout shared between Network and Distribution per-type cohort views ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/website/scripts/options/shared.js)) - `options/distribution/holdings.js` rewritten to drive the new `share` / `dominance` naming and to split the old combined "Profitability" chart into separate "Amount" (total + halved + in-profit + in-loss sats/btc/usd) and "Composition" charts ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/website/scripts/options/distribution/holdings.js)) - Added `website/assets/logo/demo.html` and `demo-svg.html` plus a local `.gitignore`, used to iterate on cube-style logo variants ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/website/assets/logo/demo.html)) ### Internal Changes #### `workspace` - Bumped the pinned Rust toolchain to `1.95.0` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/rust-toolchain.toml)) - Bumped `mimalloc` to 0.1.50 and `libmimalloc-sys` to 0.1.47 in `brk_alloc` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_alloc/Cargo.toml)) #### `brk_computer` - `internal/with_addr_types.rs` gained `WithAddrTypes::forced_import` (plus `collect_vecs_mut`, `par_iter_height_mut`, `reset_height`) and `WithAddrTypes>::forced_import` so the new metrics plug into the existing `WithAddrTypes` machinery ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_computer/src/internal/with_addr_types.rs)) - `block/cohort/addr_updates.rs` collapses the funded / empty `let mut next_index = len(); for ... { next_index += 1; }` loops into `(start..).zip(pushes_iter)`, keeping the index assignment and the push in one expression ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_computer/src/distribution/block/cohort/addr_updates.rs)) - `utxo/percentiles.rs` parallelised the per-cohort `write_distribution` calls via `par_iter()` for the sub-buckets, and merged the `all` / `sth` / `lth` writes into a single `into_par_iter().try_for_each` pass ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_computer/src/distribution/cohorts/utxo/percentiles.rs)) #### `brk_bindgen` - Generated JS tree client constructor calls `_buildTree()` (no `basePath` argument). Path tracking now lives entirely on the generator side, so the runtime builder is one fewer parameter ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.3/crates/brk_bindgen/src/generators/javascript/tree.rs)) [View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.3.0-beta.2...v0.3.0-beta.3) ## [v0.3.0-beta.2](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.3.0-beta.2) - 2026-04-15 ### Breaking Changes #### `brk_types` - `BlockInfoV1` gained a `stale: bool` field (defaults to `false`, serialised alongside `info` and `extras`) that marks blocks which have been replaced by a longer chain after a reorg ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_types/src/block_info_v1.rs)) - Rewrote `Etag::from_series`: the signature changed from `(version, total, start, end, height)` to `(version, total, end, hash_prefix: BlockHashPrefix)`. Slices that reach the end of the series now produce `v{version}-{hash_prefix:x}` (tied to the current tip's reorg state), and bounded slices produce `v{version}-{total}` (stable until the series grows). The old `{start}-{end}` and `{start}-{total}-{height}` forms are gone ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_types/src/etag.rs)) #### `brk_iterator` - `BlockIterator::Item` changed from `Block` to `Result`. The `State::Rpc` arm now propagates `get_block_hash` / `get_block` errors as `Some(Err(e))` instead of ending iteration via `.ok()?`, and mid-iteration reorg detection returns `Some(Err(Error::Internal("rpc iterator: chain continuity broken (likely reorg mid-iteration)")))` so callers see the failure instead of a silent `None`. The `State::Reader` arm propagates `Result` from the new reader pipeline ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_iterator/src/iterator.rs)) #### `brk_computer` - Removed the `scripts` top-level module entirely (`scripts::Vecs`, `scripts::{count, compute, import, mod}`). The remaining `outputs_value` logic moved to `outputs/value/vecs.rs` and the lib.rs wiring no longer mentions scripts. Downstream consumers referencing `computer.scripts.*` must switch to the corresponding `outputs.*` paths ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_computer/src/lib.rs)) - `blocks::lookback::Vecs::cached_window_starts` is no longer a public struct field. It is now a method `cached_window_starts() -> Windows<&WindowStartVec>`, and every downstream importer (inputs/outputs/mining/transactions/cointime/distribution) that used to pass `&blocks.lookback.cached_window_starts` now passes `&cached_starts` as a `Windows<&WindowStartVec>`. The companion `CachedWindowStarts` alias was replaced by the more explicit `Windows<&WindowStartVec>` type ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_computer/src/blocks/lookback.rs)) - Removed `prices::Vecs::cached_spot_cents` and `cached_spot_usd` fields. The hot-path spot-price cache now lives on the `price_cents` `CachedPerBlock` itself, so every compute caller that referenced `prices.cached_spot_cents` / `prices.cached_spot_usd` in beta.1 has been migrated to use the cached fields on `price_cents.height` / `price_usd.height` directly ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_computer/src/prices/mod.rs)) ### New Features #### `brk_reader` - Rewrote the blk-file streaming pipeline as a dedicated `pipeline` module. New files: `pipeline/mod.rs` exposing `DEFAULT_PARSER_THREADS` and `spawn(inner, canonical, parser_threads)`, `pipeline/forward.rs` driving the forward blk scanner, `pipeline/reorder.rs` handling out-of-order writes by bitcoind (up to `OUT_OF_ORDER_FILE_BACKOFF = 21` files), and `pipeline/tail.rs` streaming blocks that bitcoind appends live while the pipeline is running ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_reader/src/pipeline/mod.rs)) - Added `canonical.rs` with a `CanonicalRange::walk(client, after_hash, tip)` helper that resolves the canonical chain hash list for the streaming pipeline, and `bisect.rs` with `first_block_height(client, blk_path, xor_bytes)` that binary-searches a blk file for the first canonical block height. Replaces the previous inline `find_start_blk_index` logic ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_reader/src/canonical.rs)) - Added `parse.rs` as the single parsing entry point, replacing `decode.rs` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_reader/src/parse.rs)) - Split `Reader::new` into `new` (raises NOFILE via `Self::raise_fd_limit()` then delegates) and `new_without_rlimit(blocks_dir, client)`, so CI and embedded consumers can opt out of the `setrlimit` call. `raise_fd_limit` is public, clamps to the hard limit, and warns (not panics) on failure — a `TARGET_NOFILE = 15_000` constant replaces the ad-hoc `max(15_000)` call ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_reader/src/lib.rs)) - Added `Reader::all()`, `Reader::after(hash)`, and a new configurable `Reader::after_with(hash, parser_threads)` that return a `Receiver>` streaming canonical blocks from `hash + 1` (or genesis) to the tip. The old `read(start, end)` / `read_rev` API is gone ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_reader/src/lib.rs)) - `ReaderInner::open_blk(blk_index)` is the new single entry point for opening blk file handles. `read_raw_bytes` switched to `FileExt::read_exact_at` (positional pread) and `reader_at` uses the new `XORIndex::at_offset(offset)` constructor ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_reader/src/xor_index.rs)) - Added reader benchmark examples under `examples/`: `after_bench.rs` (measures full-chain streaming throughput), `last_n_bench.rs` (measures the tail-fetch path), `reader.rs`, `reader_single.rs`, and `blk_heights.rs` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_reader/examples/after_bench.rs)) #### `brk_error` - Added an `OptionData` extension trait with a single `data(self) -> Result` method that converts `None` into `Error::Internal("data unavailable")`. The doc comment explicitly notes it replaces `.unwrap()` in query paths so a missing value returns HTTP 500 instead of crashing the server under `panic = "abort"` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_error/src/lib.rs)) #### `brk_computer` - Added `internal::block_walker::walk_blocks(fi_batch, txid_len, coinbase, scan_tx, store)` with a `BlockAggregate` aggregate (totals plus `[u64; 12]` per-output-type entries/txs counters) and a `CoinbasePolicy::{Include, Skip}` control. Used by `inputs::by_type` and `outputs::by_type` to share a single per-block / per-type cursor walker, eliminating ~100 lines of duplicated loops ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_computer/src/internal/block_walker.rs)) - Added `internal::with_addr_types::WithAddrTypes` — a generic `all + per-AddrType` container that mirrors the existing `WithSth` pattern along the address-type axis. It exposes `forced_import`, `min_stateful_len`, `par_iter_height_mut`, `reset_height`, `push_height(total, per_type)`, and `compute_rest(starting_indexes, exit)` which writes `all.height` as the per-block sum of the per-type vecs. Specialised `forced_import` impls exist for `PerBlock`, `PerBlockCumulativeRolling`, and similar families so every metric that tracks one aggregate alongside a per-address-type breakdown can use the same container ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_computer/src/internal/with_addr_types.rs)) - Added a `CachedPerBlock` wrapper type in `internal::per_block::computed::cached`, used by `prices::Vecs::price_cents`. The wrapper carries an in-memory `CachedVec` over the on-disk `PerBlock` so every compute caller that previously reached for the separate `cached_spot_cents` field now reads through `price_cents.height` directly ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_computer/src/internal/per_block/computed/cached.rs)) - Added `PercentCumulativeRolling` and `LazyPercentCumulativeRolling` under `internal::per_block::percent`, covering the new "cumulative + rolling window" percent metric shape used by several distribution modules ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_computer/src/internal/per_block/percent/cumulative_rolling.rs)) - `blocks::lookback::Vecs` now wraps the `_24h`, `_1w`, `_1m`, and `_1y` lookback vecs in `CachedVec>` so the most frequently read lookback windows serve from an in-memory cache instead of re-reading from disk every block ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_computer/src/blocks/lookback.rs)) - `internal::cache_budget::cache_wrap` is now generic over `V: TypedVec + ReadableVec + Clone + 'static`, matching the `CachedVec` wrapper migration in vecdb. Callers pass any cacheable source vec directly and get a `CachedVec` back ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_computer/src/internal/cache_budget.rs)) #### `brk_cohort` - `ByType::try_new(create)` constructor builds both the spendable and unspendable parts from a single `FnMut(Filter, &'static str) -> Result`, and the unspendable half now reuses the shared `OP_RETURN: &'static str` constant with `Filter::Type(OutputType::OpReturn)` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_cohort/src/by_type.rs)) - Added `ByType::iter`, `iter_mut`, `par_iter_mut`, `iter_typed`, and `iter_typed_mut` helpers so downstream metric modules can iterate all 12 output-type slots (11 spendable + op_return) without unpacking the struct manually. The existing `SpendableType` also grew matching methods ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_cohort/src/spendable_type.rs)) #### `brk_query` - Added `rustc-hash` dependency and new `indexed_height()` accessor that reads `blockhash.inner.stamp()` directly instead of computing `len - 1`, giving the server a stable, bounded way to pick the canonical tip height for cache strategies ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_query/src/lib.rs)) - Rewrote `tx.rs`, `block/txs.rs`, `block/info.rs`, `addr.rs`, and `mining/pools.rs` to thread `Result`s through every lookup path using the new `OptionData::data()` method and the refreshed `CachedVec` reader API. Null-safety audits that previously `unwrap()`'d through chained cursor reads now return `Error::Internal("data unavailable")` so the HTTP layer answers 500 instead of aborting the process ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_query/src/impl/tx.rs)) #### `brk_server` - Added `params::tx_index_param::TxIndexParam`, documented in the params module tree, so new endpoints can take a `TxIndex` path segment with schema-driven validation ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_server/src/params/tx_index_param.rs)) #### `brk_bindgen` / generated clients - Extracted a shared `BrkClientBase._getCached(path, parse, options)` method that runs the cache-vs-network race, invokes `onUpdate` when either branch resolves, and writes fresh responses back to the `caches` storage. Both `getJson(path, options)` and `getText(path, options)` now delegate to it with their own `parse` function (`res.json()` + `_addCamelGetters` for JSON, `res.text()` for text), giving CSV/text fetches the same caching, ETag short-circuit, and `onUpdate` semantics that JSON fetches already had. `getText` also gains `AbortSignal` and `onUpdate` option support ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_bindgen/src/generators/javascript/client.rs)) - Each generated `fetchCsv()` endpoint method now passes the same `{ signal, onUpdate }` options bag through `getText`, so `brkClient.series.*.fetchCsv({ signal, onUpdate })` benefits from browser-cache reuse and cancellation ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_bindgen/src/generators/javascript/api.rs)) #### `scripts` - Added a new `scripts/mempool_compat/` pytest project (managed with `uv`) that compares every brk mempool_space endpoint against the real mempool.space API using live blockchain data. It ships `conftest.py` + seven test modules covering addresses, blocks, fees, general, mempool, mining, and transactions, with its own `pyproject.toml` and lockfile. Nothing is hardcoded or deterministic — the tests pull live data on each run ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/scripts/mempool_compat/conftest.py)) #### `website` - Rewrote `options/mining.js`, `options/network.js`, `options/market.js`, `options/cointime.js`, `options/partial.js`, `options/full.js`, and the distribution sub-modules to match the new backend series layout (new per-addr-type cohorts, percent cumulative-rolling metrics, etc.). Added `options/series.js` and `options/shared.js` for shared builders ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/website/scripts/options/mining.js)) - Added a new `website/CLAUDE.md` with frontend-specific contribution notes, paired with a top-level `CLAUDE.md` update ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/website/CLAUDE.md)) - Assorted explorer fixes in `chain.js`, `index.js`, and search (`panes/search.js`) plus format/utils updates, accompanying the main/nav/explorer CSS polish ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/website/scripts/explorer/chain.js)) ### Internal Changes #### `brk_indexer` - `processor/{mod,tx,metadata}.rs` and `vecs/{blocks,transactions}.rs` updated to match the reader pipeline and `CachedVec` wrapper migration in vecdb — no user-visible behavior change ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_indexer/src/processor/mod.rs)) #### `brk_rpc` - `backend/{bitcoincore,corepc}.rs` refreshed for the new iterator error-propagation shape ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_rpc/src/backend/bitcoincore.rs)) #### `brk_oracle` - Refreshed examples (`compare_digits`, `determinism`, `noise`, `report`, `sweep_digits`, `sweep_tolerance`, `validate`) to compile against the new query result types ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.2/crates/brk_oracle/examples/validate.rs)) [View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.3.0-beta.1...v0.3.0-beta.2) ## [v0.3.0-beta.1](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.3.0-beta.1) - 2026-04-08 ### New Features #### `website` - Replaced the pinned/unpinned sidebar model with a split/full layout mode. The preference is stored under the new `split-view` localStorage key (replacing `sidebar-pinned`), and `data-layout` now holds `"split"` or `"full"` instead of `"desktop"`/`"mobile"`. A new inline script in `index.html` reads the preference synchronously before modules load and sets `data-layout` to avoid a flash of unstyled layout ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.1/website/scripts/main.js)) - Added a layout-toggle button (`#layout-button`, replacing `#pin-button`) that flips between split and full layouts on any device. `syncFrame()` now calls the relevant selector label when the layout changes, so the visible frame follows the mode switch automatically ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.1/website/index.html)) - Rewrote the desktop resize bar as `initResizeBar()` using pointer events (`pointerdown`/`pointermove`/`pointerup`/`pointercancel` with `setPointerCapture`), clamping the width to `--max-main-width * innerWidth`, persisting the value under `bar-width`, and resetting on double-click. Replaces the old `initDesktopResizeBar` which used three separate mouse listeners ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.1/website/scripts/main.js)) - Moved `