changelog: updated

This commit is contained in:
nym21
2026-04-16 10:15:23 +02:00
parent 5cc85b0619
commit 78d6d9d6f1
10 changed files with 395 additions and 39 deletions

View File

@@ -4,6 +4,365 @@ All notable changes to the Bitcoin Research Kit (BRK) project will be documented
> *This changelog was generated by Claude Code*
## [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<Block>`. 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<ReadBlock>` 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<Result<ReadBlock>>` 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<T>` extension trait with a single `data(self) -> Result<T>` 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<T>` — 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<B, M>` and `LazyPercentCumulativeRolling<B, S1T>` 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<M::Stored<...>>` 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<V::I, V::T> + Clone + 'static`, matching the `CachedVec<V>` wrapper migration in vecdb. Callers pass any cacheable source vec directly and get a `CachedVec<V>` 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<T, E>`, 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<V>` 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<V>` 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 `<aside>` and `<footer>` out of `<main>` in `index.html`, and moved `#resize-bar` between the main and aside elements so the flex row directly resizes only the main column ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.1/website/index.html))
- Refactored the explorer address pane: `initAddrDetails(parent, linkHandler)` now creates the heading, row stubs, and transactions section once, and `update(address, signal)` only writes the row values and mounts transaction rows. A `currentAddr` guard aborts late-arriving txs pages from a previous address, and the labels array (`Address`, `Confirmed Balance`, …, `Avg Cost Basis`) is shared between the DOM rows and the update path ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.1/website/scripts/explorer/address.js))
- Refactored `explorer/tx.js` to follow the same init/update/show/hide/clear shape as block.js and address.js (`initTxDetails(parent, linkHandler)`, `update(tx)`, `clear()`, `show()`, `hide()`) ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.1/website/scripts/explorer/tx.js))
- Added `deselectCube()` to the chain module and a `silent` option on `selectCube` that skips the `onSelect` firing. `loadInitial` now returns the top block hash so the router can target the correct cube, and `getBlock(hash)` is a new lookup helper ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.1/website/scripts/explorer/chain.js))
- Simplified the explorer router in `index.js`: `load()` reads the path segments, calls `loadInitial(height)`, and dispatches to `showPanel("block" | "tx" | "addr")`. A single `showPanel` helper hides the other two panels in one pass, replacing the scattered `showBlockPanel` / `showSecondaryPanel` calls and the `pendingTx` mutable state ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.1/website/scripts/explorer/index.js))
- Added `showPanel` / `hidePanel` helpers in `explorer/render.js` (set `hidden = false`, scroll to top) and routed `block.js`, `tx.js`, and `address.js` `show()` / `hide()` through them for consistent behaviour ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.1/website/scripts/explorer/render.js))
- `utils/url.js` gained a `buildUrl(pathname, urlParams)` helper that composes the pathname and query string in one place; `pushHistory` now uses it instead of building the URL inline ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.1/website/scripts/utils/url.js))
- Replaced inline `style.opacity = "0.5"` hacks in `chain.js` and `block.js` with a shared `.dim` CSS class so the dimmed appearance is consistent across the explorer ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.1/website/scripts/explorer/block.js))
### Bug Fixes
#### `brk_types`
- Fixed schemars example annotations on numeric newtypes using a by-reference form so serde-json serialises them correctly: `Dollars`, `FeeRate`, `RawLockTime`, `Sats`, `Timestamp`, `TxVersionRaw`, `Vin`, `Vout`, `VSize`, and `Weight` now all use `example = &literal` (rather than bare `literal`), which resolves the recent OpenAPI spec regression where these fields either lost their examples or rendered as the wrong JSON type ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.1/crates/brk_types/src/dollars.rs))
### Internal Changes
#### `website`
- Deleted the orphan `website/styles/panes/sim.css` (75 lines) and `website/styles/panes/table.css` (139 lines), which were no longer referenced after the explorer rewrite ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.1/website/styles/main.css))
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.3.0-beta.0...v0.3.0-beta.1)
## [v0.3.0-beta.0](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.3.0-beta.0) - 2026-04-08
### Breaking Changes
#### `brk_types`
- `AddrChainStats` gained a required `realized_price: Dollars` field (average cost basis in USD), populated by the addr query. The struct no longer derives `Default` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_types/src/addr_chain_stats.rs))
- `AddrStats` gained a required `addr_type: OutputType` field so the `/api/address/{addr}` response reports the address script type (p2pkh, p2sh, v0_p2wpkh, …) alongside the existing chain and mempool stats ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_types/src/addr_stats.rs))
### New Features
#### `brk_types`
- Added `#[schemars(example = …)]` annotations to dozens of numeric fields across `BlockInfo`, `BlockExtras`, `BlockStatus`, `BlockTimestamp`, `BlockPool`, `TxVersionRaw`, `DifficultyEntry`, `DifficultyAdjustmentEntry`, `FeeRatePercentiles`, `HashrateEntry`, `HashrateSummary`, `HistoricalPrice`, `MempoolInfo`, `MerkleProof`, `PoolDetailInfo`, `PoolHashrateEntry`, `PoolInfo`, `PoolStats`, `PoolsSummary`, `RawLockTime`, `Sats`, `Timestamp`, `TxIndex`, `TxOutspend`, `Vin`, `Vout`, `VSize`, `Weight`, `Dollars`, `FeeRate`, and others, so the generated OpenAPI schema and type docs now carry realistic example values for every field ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_types/src/block_info.rs))
- `AddrValidation` now includes a boolean schemars example, and `HistoricalPrice`, `PoolSlug`, and other types grew richer doc comments ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_types/src/addr_validation.rs))
#### `brk_server`
- Added `CacheParams::tip(prefix)`, `block_bound(version, prefix)`, `mempool_hash(hash)`, and consolidated `static_version()` constructors. `CacheParams::resolve` now delegates to these constructors, and `cache_control` is stored as `&'static str`, with a shared `CACHE_CONTROL = "public, max-age=1, must-revalidate"` constant reused by every strategy ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_server/src/cache.rs))
- Added `/// doc comments` on every path parameter under `brk_server::params/*`: `AddrParam`, `BlockCountParam`, `BlockHashParam`, `BlockHashStartIndexParam`, `BlockHashTxIndexParam`, `HeightParam`, `PoolSlugParam`, `TimePeriodParam`, `TimestampParam`, `TxidParam`, so the OpenAPI/Scalar docs render descriptions for every route parameter ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_server/src/params/addr_param.rs))
- Added `TransformOperationExtended::binary_response()` and `not_modified()` helpers, and wired `.not_modified()` into the series endpoints (`/api/series/{name}/{index}/last`, `/length`, `/version`, plus the cost-basis cohorts/dates/histogram routes) so 304 responses are documented in the OpenAPI spec ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_server/src/extended/transform_operation.rs))
- `CACHE_CONTROL` moved from `api/series/mod.rs` into `cache::CACHE_CONTROL` and is re-used by both the series endpoints and every cache strategy, removing a duplicate constant ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_server/src/api/series/mod.rs))
- `AppState::cached_*` now computes the tip prefix once per request and reuses it for both the cache-key generation and the tip-change invalidation check (instead of resolving it twice), shaving a sync round-trip on each cached response ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_server/src/state.rs))
#### `brk_query`
- `Query::addr_stats` now populates `realized_price` from the address data's realized price (dollars) for funded addresses and `Dollars::default()` for empty ones, and sets `addr_type` from the decoded `OutputType`, backing the new `AddrChainStats`/`AddrStats` fields ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_query/src/impl/addr.rs))
- Rewrote `addr_txindices` to avoid the old "prefix scan then filter" pattern when paginating by `after_txid`: the new code computes `AddrIndexTxIndex::min_for_addr(type_index)` and the bound from `(type_index, after_tx_index)` and uses `store.range(min..bound).rev().take(limit)` for an O(limit) lookup. The no-pagination branch keeps the `prefix().rev().take(limit)` fast path ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_query/src/impl/addr.rs))
- Reworked `addr_utxos` to reuse block-hash / block-time per `height` via a `cached_block: Option<(Height, BlockHash, Timestamp)>` guard, so a batch of UTXOs in the same block only issues one blockhash read and one timestamp cursor advance ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_query/src/impl/addr.rs))
- Replaced the per-row `transactions_by_range(start, count)` bulk reader with a `transactions_by_indices(&[TxIndex])` helper used by `addr_txs`, `block_txs`, and `transaction_by_index`. The helper does batched range reads (txids/heights/locktimes/total_sizes/first_txin_indices/positions) and re-uses a `cached_block: Option<(Height, BlockHash, Timestamp)>` so a batch of txs in the same block only issues one blockhash + one timestamp lookup ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_query/src/impl/block/txs.rs))
- `Query::transaction_raw` now short-circuits on mempool txs: if the mempool has the txid, it returns the stored hex directly instead of falling through to the on-chain lookup ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_query/src/impl/tx.rs))
- `Query::outspend(txid, vout)` no longer calls `outspends` (which materialised every output). Two new private helpers are extracted in `tx.rs`: `resolve_tx_outputs(txid) -> (TxIndex, TxOutIndex, usize)` resolves the tx's first `TxOutIndex` and output count, and `resolve_outspend(txout_index: TxOutIndex) -> Result<TxOutspend>` looks up a single outspend. `outspend` bounds-checks `vout` against the count and calls `resolve_outspend(first_txout + vout)` for O(1) work; mempool txs return `TxOutspend::UNSPENT` immediately without a store lookup ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_query/src/impl/tx.rs))
- `Query::block_by_timestamp` and the mining `epochs`/`hashrate`/`pools` loops now advance dedicated cursors (`ts_cursor`, `height_cursor`, `difficulty_cursor`, `hr_cursor`) instead of calling `collect_one_at` per iteration, reducing the per-element overhead of repeated random reads ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_query/src/impl/block/timestamp.rs))
- `block_info` now fetches the block header and coinbase scriptsig in a single `reader.reader_at(position)` streamed pass instead of issuing separate `read_raw_bytes` calls for the header and coinbase — one seek per block, no intermediate 1000-byte buffer. A new `parse_coinbase_from_read` reads the rest of the coinbase transaction straight off the same reader ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_query/src/impl/block/info.rs))
- `Query::cpfp_info` returns an empty `CpfpInfo::default()` when the tx is absent from the mempool (removing an `Error::NotFound` branch) and removes an unused `pool_blocks` allocation from the mining pools path ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_query/src/impl/mempool.rs))
#### `brk_reader`
- Added `rlimit = "0.11.0"` and raised the process's `NOFILE` soft limit to at least 15000 on `ReaderInner::new` so concurrent blk file access doesn't hit the OS FD cap ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_reader/Cargo.toml))
- `ReaderInner` now holds a `blk_file_cache: RwLock<BTreeMap<u16, File>>` and an `ensure_blk_cached` helper that opens each blk file once and reuses the handle, replacing the old `File::open` + seek on every `read_raw_bytes` call. `read_raw_bytes` switched to `FileExt::read_at` (positional pread) so concurrent readers no longer contend on a shared seek cursor ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_reader/src/lib.rs))
- Added `Reader::reader_at(position) -> Result<BlkRead<'_>>`, a streaming `Read` impl that seeks once and XOR-decodes bytes on the fly as callers pull from it, avoiding the upfront buffer allocation for variable-length reads like full coinbase transactions ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_reader/src/lib.rs))
- Added `XORIndex::decode_at(buffer, offset, xor_bytes)` — a static helper equivalent to the old `Self::default(); add_assign(offset); bytes(buffer, xor_bytes)` sequence, now used by `read_raw_bytes` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_reader/src/xor_index.rs))
#### `brk_indexer`
- Dropped the `rlimit` dependency and the corresponding `Resource::NOFILE` bump in `forced_import_inner`, since the limit raise now lives in `brk_reader::ReaderInner::new` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_indexer/src/lib.rs))
#### `brk_mempool`
- `EntryPool` now maintains a `parent_to_children: FxHashMap<TxidPrefix, SmallVec<[TxidPrefix; 2]>>` adjacency map and exposes a `children(&prefix) -> &[TxidPrefix]` accessor. `insert` pushes the new tx onto each parent's child list, and `remove` walks `entry.depends` to unlink itself from each parent and drop empty entries, enabling O(1) descendant lookup for CPFP computations instead of a full entries scan ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_mempool/src/entry_pool.rs))
#### `brk_store`
- Added `Store::range(range: Range<B>)` returning a `DoubleEndedIterator<(K, V)>` by deserialising the keyspace's raw byte range. This is the primitive used by the new `addr_txindices` paginated scan ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_store/src/lib.rs))
#### `brk_bindgen` / generated clients
- Generated JavaScript client methods now accept an `options` bag with `signal?: AbortSignal` and `onUpdate?: (value) => void`. `getJson`, `getText`, and `get` all thread the `AbortSignal` into `fetch` (combined with the timeout signal via `AbortSignal.any(...)`), and `onUpdate` is invoked when either the cache or network branch resolves first, enabling stale-while-revalidate rendering in the frontend ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_bindgen/src/generators/javascript/client.rs))
- Each generated API method signature changed from `method(...params)` to `method(...params, { signal, onUpdate } = {})`, with the same forwarding for both JSON and CSV series endpoints ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_bindgen/src/generators/javascript/api.rs))
#### `website`
- Rewrote the explorer from the single `panes/explorer.js` (deleted, 401 lines) into a modular `website/scripts/explorer/` package with `index.js` (lifecycle + routing), `chain.js` (block list with dual sentinels and deep-linking), `block.js`, `tx.js`, `address.js` (new entity views), and `render.js` (shared row/table rendering). The module supports `/block/{height_or_hash}`, `/tx/{txid}`, and `/address/{addr}` URLs ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/website/scripts/explorer/index.js))
- Moved `website/scripts/chart/` into `website/scripts/utils/chart/` and moved `options/utils.js` to `utils/time.js`. Removed the standalone `website/scripts/chart/oklch.js` (107 lines) — its color utilities were merged into `website/scripts/utils/colors.js` which grew by ~125 lines ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/website/scripts/utils/colors.js))
- Deleted `website/scripts/panes/_table.js` (433 lines, unused after the explorer rewrite) and added `website/scripts/utils/cache.js` with ~30 lines of shared cache helpers used by the new explorer modules ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/website/scripts/utils/cache.js))
- Expanded `options/mining.js` (+150 lines) and rewrote `options/network.js` to match the new hashrate/pool series layout; extended `options/types.js` with the additional pattern types the rewritten options reference ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/website/scripts/options/mining.js))
### Bug Fixes
#### `brk_server`
- `ResponseExtended::new_not_modified` in the bulk/data/legacy series paths now sets the etag and cache-control headers on the 304 response instead of emitting a bare empty body, so conditional GETs see the same headers as the original 200 ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-beta.0/crates/brk_server/src/api/series/mod.rs))
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.3.0-alpha.6...v0.3.0-beta.0)
## [v0.3.0-alpha.6](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.3.0-alpha.6) - 2026-04-05
### Breaking Changes
#### `brk_types`
- Renamed `Pool::unique_id()` to `Pool::mempool_unique_id()` (the 0-indexed value from `pools-v2.json`) and added a new `Pool::mempool_id()` helper that returns the 1-indexed value, matching mempool.space's `id` field. `PoolDetailInfo::id` and `PoolStats::pool_id` now serialize the 1-indexed `mempool_id()`, while the existing `unique_id`/`pool_unique_id` fields stay on the 0-indexed value ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_types/src/pool.rs))
- Reverted `BlockExtras::utxo_set_change` semantics: the field is once again computed as `total_outputs - total_inputs` (the signed delta including unspendable outputs like `OP_RETURN`), matching mempool.space and `bitcoin-cli`. The `utxo_set_size` doc comment now explicitly states it is the "total spendable UTXO set size" and intentionally differs from the change field ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_types/src/block_extras.rs))
- `TimePeriod` gained an `All` variant that parses and displays as `"all"` and maps to `usize::MAX` block count, allowing endpoints and lookups to select the entire chain history rather than a bounded window ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_types/src/time_period.rs))
### New Features
#### `brk_types`
- Added `Deserialize` derives to `BlockFeeRatesEntry` and `FeeRatePercentiles` (the types themselves existed before this release) so clients can round-trip the new `/api/v1/mining/blocks/fee-rates/{time_period}` response without manual deserialisation ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_types/src/feerate_percentiles.rs))
- Added `TxOutspend::is_deeply_spent(current_height)` which returns true when the output is spent and confirmed more than 6 blocks deep, used by the server to promote outspend responses to immutable caching ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_types/src/txout_spend.rs))
- Added a total-reward column to `PoolDetail` (the detail-view response, not the nested `PoolDetailInfo` metadata) as `total_reward: Option<Sats>`, reporting each pool's lifetime sats earned ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_types/src/pool_detail.rs))
- Relaxed `CpfpInfo::effective_fee_per_vsize`, `fee`, and `adjusted_vsize` to `Option<_>` and added `#[serde(skip_serializing_if = …)]` on `best_descendant`, `descendants`, `effective_fee_per_vsize`, `fee`, and `adjusted_vsize`. Empty fields now disappear from the JSON, but the field-type change is an API break for any client that was typing these as non-optional ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_types/src/cpfp.rs))
#### `brk_query`
- Implemented `Query::block_fee_rates(time_period)`: reads min/10/25/median/75/90/max from the effective fee-rate block distribution for the requested window, averages per sub-window using the same `BlockWindow` helper used by `block_fees`, and returns `Vec<BlockFeeRatesEntry>` (previously the endpoint returned `Error::not_implemented`) ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_query/src/impl/mining/block_fee_rates.rs))
- Filled in `estimated_hashrate` on pool detail responses by multiplying the current-height day1 network hashrate by each pool's 24h share, removing the previous `TODO` zero placeholder ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_query/src/impl/mining/pools.rs))
- `block_window` and `pools` window lookups now handle `TimePeriod::All` by returning a `None` lookback (treated as 0, covering the full history) so every mining endpoint that takes a period accepts `all` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_query/src/impl/mining/block_window.rs))
- `Query::outspend` now returns `TxOutspend::UNSPENT` when the requested vout is out of range instead of `Error::OutOfRange`, matching mempool.space's behavior of reporting "unspent" for any unknown output ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_query/src/impl/tx.rs))
- `Query::cpfp_info` returns a default `CpfpInfo` when the transaction is not in the mempool instead of `Error::NotFound`, so confirmed-tx CPFP lookups respond with an empty object like mempool.space ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_query/src/impl/mempool.rs))
- Deduplicated `coinbase_addresses` in the block-info response so the list only contains each payout address once ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_query/src/impl/block/info.rs))
#### `brk_server`
- Added a tip-invalidating in-memory response cache: `AppState` now carries a `last_tip: Arc<AtomicU64>`, and every `cached_json` / `cached_text` call compares the freshly fetched tip prefix with the stored value. When the tip changes, `cache.clear()` is called before serving the response, so reorgs no longer leak stale `Tip`-strategy entries ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_server/src/state.rs))
- Added `CacheParams::immutable(version)` producing `etag=i{version}` with `cache-control: public, max-age=1, must-revalidate`, used by the new outspend immutability logic and the tx transactions module ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_server/src/cache.rs))
- Added `AppState::timestamp_cache(version, timestamp)` which returns `Immutable(version)` for timestamps older than 6 hours and `Tip` otherwise. Wired into `/api/v1/historical-price` and `/api/v1/mining/blocks/timestamp/{timestamp}` so historical queries stop being treated as tip-volatile ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_server/src/state.rs))
- `/api/tx/{txid}/outspend/{vout}` and `/api/tx/{txid}/outspends` now compute the outspend first, then pick `Immutable(v)` when every output is >6 blocks deep (or `Tip` otherwise), with a short-circuit `if immutable.matches_etag(headers)` check that avoids the query altogether on cache hits ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_server/src/api/mempool_space/transactions.rs))
- `addr_cache` gained a `chain_only: bool` flag. `/api/address/{addr}/txs/chain` passes `true` so it skips the mempool-activity check (confirmed-only responses do not depend on mempool state), while the other address endpoints pass `false` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_server/src/state.rs))
- `/api/v1/mining/pool/{slug}/blocks/{height}` now uses the existing `height_cache` helper to return immutable responses for deep historical heights instead of always `Tip` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_server/src/api/mempool_space/mining.rs))
- `/api/v1/mining/blocks/fee-rates/{time_period}` is no longer a stub: it routes through `cached_json` with a `Tip` strategy and advertises a `Vec<BlockFeeRatesEntry>` response in the OpenAPI spec (the old "work in progress" wording is gone) ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_server/src/api/mempool_space/mining.rs))
- Added `ResponseExtended::static_bytes(headers, bytes, content_type, content_encoding)` helper and refactored `/scalar.js` to use it, giving pre-compressed static assets the same 304/etag short-circuit via `CacheParams::static_version()` that JSON responses already had (note: unlike JSON responses it sets `Content-Encoding` directly, so it does not emit `Vary: Accept-Encoding`) ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_server/src/extended/response.rs))
- `HeaderMapExtended` gained an `insert_vary_accept_encoding()` helper and `insert_content_encoding` now calls it automatically, so every JSON/CSV response that goes through the helper (as opposed to a direct `h.insert(header::CONTENT_ENCODING, …)` call) sets `Vary: Accept-Encoding` and downstream caches can distinguish br/gzip/identity variants ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_server/src/extended/header_map.rs))
- `ResponseExtended::new_not_modified` now takes explicit `etag` and `cache_control` arguments (instead of building an empty response), and a new `new_not_modified_with(params)` convenience accepts a `CacheParams` to compute both. Series bulk/data/legacy endpoints and the static JSON path all go through these helpers ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_server/src/extended/response.rs))
- `POST /api/tx` broadcast_transaction now runs on the async `state.run` executor instead of the synchronous `state.sync`, avoiding blocking the tokio thread on upstream RPC latency ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_server/src/api/mempool_space/transactions.rs))
#### `website`
- Added 57 mining-pool SVG logos under `website/assets/pools/`, including light-theme variants for `default`, `unknown`, `foundryusa`, `ultimuspool`, and `whitepool`. Fetched via a new `website/assets/pools.sh` script that downloads the `mempool/mining-pool-logos` archive and prunes non-SVG files ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/website/assets/pools.sh))
- Rewrote the explorer infinite-scroll to use two sentinels: `olderSentinel` (bottom of the block list) and `newerSentinel` (top). A scroll listener with a 200px margin triggers `loadOlder`/`loadNewer` instead of `IntersectionObserver`, so scrolling up in the middle of the chain fetches newer blocks until the tip is reached. A `reachedTip` flag guards the `loadLatest` polling path ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/website/scripts/panes/explorer.js))
- Added deep-linking: `getStartHeight()` parses `/block/{height_or_hash}` from the URL, and the initial load uses `getBlocksV1FromHeight` when a start height is available. The selected block is then auto-scrolled into the centre of the viewport with `scrollIntoView({ block: "center" })` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/website/scripts/panes/explorer.js))
#### `scripts`
- Added `scripts/compare_bitcoin_cli.sh` to diff BRK's block/transaction responses against a local `bitcoin-cli` node, useful for validating mempool.space API compatibility ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/scripts/compare_bitcoin_cli.sh))
### Bug Fixes
#### `brk_types`
- Fixed the `TxIn` serializer leaking an empty `inner_redeem_script_asm` for P2SH inputs whose scriptsig happened to not decode: the `has_inner_redeem` field-count flag now keys off `is_p2sh && !is_coinbase` instead of `!inner_redeem.is_empty()`, ensuring the field is serialized for every real P2SH input and omitted for every non-P2SH input ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_types/src/txin.rs))
### Internal Changes
#### `brk_types`
- Added `crates/brk_types/pools.sh` with a `curl` that fetches `pools-v2.json` from `mempool/mining-pools`, giving the local pool metadata refresh a checked-in script ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.6/crates/brk_types/pools.sh))
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.3.0-alpha.5...v0.3.0-alpha.6)
## [v0.3.0-alpha.5](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.3.0-alpha.5) - 2026-04-04
### Breaking Changes
#### `brk_types`
- Changed `Transaction::version` from the indexed `TxVersion` (u8) to a new `TxVersionRaw` (i32) type so that coinbase transactions which carry non-standard version numbers for miner signaling or branding are serialized as the actual protocol value instead of being remapped through the indexed table ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.5/crates/brk_types/src/tx.rs))
- Changed `OutputType` serde/strum representations for P2PK and multisig variants to match mempool.space's `scriptpubkey_type` values: both `P2PK65` and `P2PK33` now serialize as `p2pk`, and `P2MS` serializes as `multisig` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.5/crates/brk_types/src/output_type.rs))
- `TxOut` serialization now omits `scriptpubkey_address` for `P2PK65` and `P2PK33` outputs (P2PK has no standard address format), dropping the field entirely rather than emitting whatever `addr()` produced ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.5/crates/brk_types/src/txout.rs))
### New Features
#### `brk_types`
- Added `TxVersionRaw(i32)` wrapper with a `From<bitcoin::transaction::Version>` impl, exported from the crate root. Used by `Transaction` to expose the raw protocol version while the existing `TxVersion` (u8) remains available for indexed/aggregation use cases ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.5/crates/brk_types/src/tx_version_raw.rs))
### Bug Fixes
#### `brk_types`
- Fixed `TxIn` inner redeem-script detection: `inner_redeem_script_asm` now checks whether the previous output's `script_pubkey.is_p2sh()` instead of using the `has_scriptsig && has_witness` heuristic, so plain P2SH spends (no witness) also expose their redeem script and non-P2SH inputs that happen to have both a scriptsig and a witness no longer produce spurious redeem-script output ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.5/crates/brk_types/src/txin.rs))
- Fixed `TxIn::inner_witness_script_asm` for P2TR script-path spends: when `prevout.script_pubkey.is_p2tr()` and the witness has more than two items, the inner script is now read from the second-to-last witness item (the last item is the control block), not from the last item as was the case for P2WSH/P2SH-P2WSH ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.5/crates/brk_types/src/txin.rs))
#### `brk_query`
- Fixed `BlockExtras::utxo_set_change` reporting the signed output-minus-input delta instead of the real UTXO-set change. The query now reads `utxo_set_size` from one height before the range begin and computes `utxo_set_size - prev_utxo_set_size` per block, so the value matches the actual change in unspent outputs ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.5/crates/brk_query/src/impl/block/info.rs))
- Fixed `coinbase_signature` selection when the miner payout comes after a witness-commitment `OP_RETURN`: the search now picks the first non-`OP_RETURN` output (with a fallback to the first output) instead of "first output that decodes to a Bitcoin mainnet address", which misclassified some payout scripts ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.5/crates/brk_query/src/impl/block/info.rs))
- Block-transactions endpoint now derives `Transaction::version` directly from the decoded `bitcoin::Transaction` via `tx.version.into()` (`TxVersionRaw`) instead of looking up the indexed `tx_version` column, removing an unused bulk read and ensuring coinbase non-standard versions are preserved ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.5/crates/brk_query/src/impl/block/txs.rs))
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.3.0-alpha.4...v0.3.0-alpha.5)
## [v0.3.0-alpha.4](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.3.0-alpha.4) - 2026-04-04
### Breaking Changes
#### `brk_types`
- Reordered `BlockInfo` fields so that `bits`, `nonce`, and `difficulty` serialize immediately after `timestamp`, followed by `merkle_root`, `tx_count`, `size`, `weight`, `previous_block_hash`, and `median_time`. The new order matches mempool.space's `/api/block/{hash}` field layout ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.4/crates/brk_types/src/block_info.rs))
- Changed `BlockPool::miner_names` from `Option<String>` to `Option<Vec<String>>` so pools that tag multiple miner names in a single coinbase (e.g. OCEAN DATUM) can surface each name separately ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.4/crates/brk_types/src/block_pool.rs))
- Changed `OutputType` serde/strum representations to match mempool.space's `scriptpubkey_type` values: `OpReturn``op_return`, `P2WPKH``v0_p2wpkh`, `P2WSH``v0_p2wsh`, `P2TR``v1_p2tr` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.4/crates/brk_types/src/output_type.rs))
- `TxOut` now serializes `scriptpubkey_address` only when an address is present: the struct serializer computes a field count of 5 (with address) or 4 (without), and omits the null field entirely when there is no address ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.4/crates/brk_types/src/txout.rs))
### New Features
#### `brk_types`
- Added `first_seen: Option<u64>` and `orphans: Vec<String>` fields to `BlockExtras` to match mempool.space's `/api/v1/blocks` payload shape. Both are currently populated as `None`/empty (noted in doc comments as "always null, not yet supported" and "always empty") ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.4/crates/brk_types/src/block_extras.rs))
- `TxIn` serialization now resolves `inner_witness_script_asm` for P2SH-wrapped P2WSH inputs: the `!has_scriptsig` guard on the witness-script branch was removed so that P2SH-P2WSH spends still report the inner witnessScript disassembly, and the P2SH `inner_redeem_script_asm` branch now skips coinbase inputs ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.4/crates/brk_types/src/txin.rs))
- Documented that `TxIn::vout` is stored as `u16` (coinbase sentinel `65535`) while mempool.space's `u32` sentinel is `4294967295`, clarifying the mapping in the JSON schema ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.4/crates/brk_types/src/txin.rs))
#### `brk_query`
- Added `parse_datum_miner_names()` helper that parses the OCEAN DATUM miner-name tag payload from a coinbase scriptsig: it skips the BIP34 height push, reads the tag-length byte (with `OP_PUSHDATA1` handling), splits the payload on `0x0F`, and keeps alphanumeric + space characters. Used only when `pool_slug == PoolSlug::Ocean` to populate `BlockPool::miner_names` with the list of tagged miners ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.4/crates/brk_query/src/impl/block/info.rs))
- Added `compact_size_len()` helper that returns the Bitcoin VarInt byte length for a given transaction count (1/3/5 bytes) so the coinbase offset can be computed from the block header size plus the VarInt prefix, removing the earlier bulk-read of all coinbase tx positions ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.4/crates/brk_query/src/impl/block/info.rs))
- `parse_coinbase_tx()` now takes an explicit `len: usize` argument so the exact coinbase byte range is read from the block file instead of a fixed 1000-byte window, and returns the raw scriptsig bytes as a sixth tuple element for the DATUM parser ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.4/crates/brk_query/src/impl/block/info.rs))
- Populated the new `BlockExtras::first_seen` (always `None`) and `orphans` (always empty `vec![]`) fields when constructing block responses ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.4/crates/brk_query/src/impl/block/info.rs))
- Reordered the `BlockInfo` struct literal sites in both the V2 and V1 code paths to match the new field order in `brk_types` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.4/crates/brk_query/src/impl/block/info.rs))
- Changed `coinbase_signature` selection from `tx.output.first()` to `tx.output.iter().find(|o| bitcoin::Address::from_script(&o.script_pubkey, Network::Bitcoin).is_ok()).or(tx.output.first())` so blocks with a leading witness-commitment `OP_RETURN` get their actual payout script as the signature. (This was refined again in v0.3.0-alpha.5 to skip `OP_RETURN` outputs directly rather than filtering on address decodability) ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.4/crates/brk_query/src/impl/block/info.rs))
### Bug Fixes
#### `brk_query`
- Fixed `total_sigop_cost` for block transactions returning inaccurate values: the call now supplies a real previous-output lookup that scans `tx.input` for a matching outpoint and resolves the prevout `value` and `script_pubkey` via the input's cached prevout, instead of always returning `None` and under-counting witness sigops ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.4/crates/brk_query/src/impl/block/txs.rs))
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.3.0-alpha.3...v0.3.0-alpha.4)
## [v0.3.0-alpha.3](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.3.0-alpha.3) - 2026-04-04
### New Features
#### `brk_types`
- Added `price: Dollars` field to `BlockExtras`, exposing the USD spot price at each block height on the `/api/v1/blocks` response ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.3/crates/brk_types/src/block_extras.rs))
#### `brk_computer`
- Added a hot-path price cache on `Prices`: `cached_spot_cents: CachedVec<Height, Cents>` wrapping the on-disk `price_cents.height` vec, plus `cached_spot_usd: LazyVecFrom1<Height, Dollars, Height, Cents>` built via `LazyVecFrom1::transformed::<CentsUnsignedToDollars>` on top of the cached cents vec. This keeps repeated spot lookups in memory instead of re-reading from disk on every compute pass ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.3/crates/brk_computer/src/prices/mod.rs))
- Migrated every compute caller of `prices.spot.cents.height` / `prices.spot.usd.height` to the new `prices.cached_spot_cents` / `prices.cached_spot_usd` fields across cointime reserve_risk and value, market ath/lookback/moving_average/range/returns, market technical MACD, investing returns, indicators realized_envelope, distribution profitability, distribution unrealized/full, distribution cohort all/basic/core/extended/minimal/type, and internal per_block amount/base, amount/block, and ratio/price_extended ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.3/crates/brk_computer/src/cointime/value/compute.rs))
- Relaxed `realized_envelope::compute_index` and `compute_score` signatures to accept `&impl ReadableVec<Height, Cents>` instead of a concrete `&EagerVec<PcoVec<Height, Cents>>`, so they can consume the new cached lazy price vecs ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.3/crates/brk_computer/src/indicators/realized_envelope.rs))
#### `brk_query`
- Populated the new `BlockExtras::price` field from `prices.cached_spot_usd` when assembling per-height block info, so every block returned by range queries carries its USD spot price ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.3/crates/brk_query/src/impl/block/info.rs))
- Removed the local private `max_height()` helper in `impl/block/info.rs` (which computed `blockhash.len().saturating_sub(1)`) and rewired `block_by_height` and `block_hash_by_height` to the existing public `Query::indexed_height()` accessor. `block_by_height_v1` was moved onto the `Query::height()` helper (min of indexed and computed heights) to stay consistent with the other series-bound paths ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.3/crates/brk_query/src/impl/block/info.rs))
- Switched the mining `block_window` computation to read prices from `cached_spot_cents` instead of `spot.cents.height` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.3/crates/brk_query/src/impl/mining/block_window.rs))
#### `brk_server`
- `/api/blocks/tip/height` now answers with `q.indexed_height()` (the last indexed block's height) instead of `q.height()`, matching the cleaner helper added in `brk_query` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.3/crates/brk_server/src/api/mempool_space/blocks.rs))
#### `website`
- Added a "Price" row to the explorer block-details pane, rendering `extras.price` as a formatted USD amount above the pool information ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.3/website/scripts/panes/explorer.js))
### Bug Fixes
#### `brk_computer`
- Fixed distribution aggregation contaminating stats with zeros when inputs are skipped: when `skip_count > 0`, `LazyDistribution::compute_range_at` now calls `values.retain(|v| *v > zero)` before computing min/max/quartiles so that skipped entries (e.g. coinbase, zero-fee txs) do not pull the distribution toward zero ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.3/crates/brk_computer/src/internal/per_block/computed/distribution.rs))
- Bumped the transactions-fees aggregation `VERSION` from 2 to 3 (comment now reads "skip coinbase, skip zero-fee") to force recomputation of all fee/feerate metrics under the new zero-filter semantics ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.3/crates/brk_computer/src/transactions/fees/import.rs))
### Internal Changes
#### `brk_logger`
- Re-added `fjall=off` and `lsm_tree=off` to the default `RUST_LOG` directive set so the embedded KV store stops logging at the default level ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.3/crates/brk_logger/src/lib.rs))
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.3.0-alpha.2...v0.3.0-alpha.3)
## [v0.3.0-alpha.2](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.3.0-alpha.2) - 2026-04-03
### Breaking Changes
#### `brk_store`
- Removed `reset()` from the `AnyStore` trait and its `Store` implementation. The previous `keyspace.clear()`-based reset left a journal record that was replayed on every recovery, so the operation has been removed in favor of deleting the stores directory outright ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.2/crates/brk_store/src/any.rs))
#### `brk_indexer`
- Removed `Stores::reset()` method in line with the `brk_store` trait change ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.2/crates/brk_indexer/src/stores.rs))
### New Features
#### `brk_indexer`
- Added `Indexer::full_reset()` which deletes the on-disk `stores/` directory and rebuilds the store set from scratch via `Stores::forced_import`, providing a clean recovery path without leaving journal replay records behind ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.2/crates/brk_indexer/src/lib.rs))
- Wired `full_reset()` into the XOR key change handler and the data inconsistency recovery branch so that both reset paths use directory nuking instead of keyspace clearing ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.2/crates/brk_indexer/src/lib.rs))
- Added failure logging in `Stores::forced_import_inner`, so the reason a store open failed is printed before the retry path deletes the directory and reimports ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.2/crates/brk_indexer/src/stores.rs))
#### `website`
- Added a block details side pane in the explorer that renders full metadata for the currently selected block: hash, previous hash, merkle root, timestamp, median time, version, bits, nonce, difficulty, size, weight, transaction count, pool info (name/id/slug/miner names), reward and total fees, median/avg fee rate, fee range, fee percentiles, avg tx size, virtual size, input/output totals and amounts, UTXO set change and size, SegWit totals, and coinbase address/raw/signature fields ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.2/website/scripts/panes/explorer.js))
- Added click-to-select interaction on block cubes with a `selectedCube` highlight and a `blocksByHash` cache so the details pane can look up block data without refetching; the chain tip is auto-selected on first load ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.2/website/scripts/panes/explorer.js))
- Restyled the explorer pane to host the new details panel alongside the block chain view ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.2/website/styles/panes/explorer.css))
### Internal Changes
#### `brk_logger`
- Trimmed the default `RUST_LOG` directive set down to `bitcoin=off,bitcoincore_rpc=off,corepc=off,tracing=off,aide=off,tower_http=off`, dropping the per-crate overrides for `fjall`, `brk_fjall`, `lsm_tree`, `brk_rolldown`, `rolldown`, `rustls`, `notify`, and `oxc_resolver` that the embedded KV store and HTTP stack no longer need silenced by default ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.2/crates/brk_logger/src/lib.rs))
#### `workspace`
- Pinned `fjall` from `3.1.2` to `=3.0.4` in the workspace `Cargo.toml` to avoid the 3.1.x journal-replay regression that motivated the whole store reset rework in this release ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.2/Cargo.toml))
#### `brk_query`
- Replaced the manual ceiling division in `BlockWindowRange::count()` with the standard-library `div_ceil` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.2/crates/brk_query/src/impl/mining/block_window.rs))
- Refactored pool hashrate entry computation in `mining/pools.rs` to use let-chains for the `total_blocks > 0 && Some(hr) = …` check and read the daily hashrate via direct deref instead of `f64::from(**hr)` ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.2/crates/brk_query/src/impl/mining/pools.rs))
#### `brk_client` (Rust)
- Reordered imports in `brk_client` to group `std` modules before external re-exports ([source](https://github.com/bitcoinresearchkit/brk/blob/v0.3.0-alpha.2/crates/brk_client/src/lib.rs))
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.3.0-alpha.1...v0.3.0-alpha.2)
## [v0.3.0-alpha.1](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.3.0-alpha.1) - 2026-04-03
### Breaking Changes

View File

@@ -7005,7 +7005,7 @@ function createTransferPattern(client, acc) {
* @extends BrkClientBase
*/
class BrkClient extends BrkClientBase {
VERSION = "v0.3.0-beta.1";
VERSION = "v0.3.0-beta.2";
INDEXES = /** @type {const} */ ([
"minute10",

View File

@@ -40,5 +40,5 @@
"url": "git+https://github.com/bitcoinresearchkit/brk.git"
},
"type": "module",
"version": "0.3.0-beta.1"
"version": "0.3.0-beta.2"
}

View File

@@ -6316,7 +6316,7 @@ class SeriesTree:
class BrkClient(BrkClientBase):
"""Main BRK client with series tree and API methods."""
VERSION = "v0.3.0-beta.1"
VERSION = "v0.3.0-beta.2"
INDEXES = [
"minute10",

View File

@@ -1,6 +1,6 @@
[project]
name = "brk-client"
version = "0.3.0-beta.1"
version = "0.3.0-beta.2"
description = "Bitcoin on-chain analytics client — thousands of metrics, block explorer, and address index"
readme = "README.md"
requires-python = ">=3.9"

View File

@@ -76,9 +76,23 @@
}
let storedLayout;
try { storedLayout = localStorage.getItem("split-view"); } catch (_) {}
const isSplit = storedLayout !== "false" && window.matchMedia("(min-width: 768px)").matches;
try {
storedLayout = localStorage.getItem("split-view");
} catch (_) {}
const isSplit =
storedLayout !== "false" &&
window.matchMedia("(min-width: 768px)").matches;
document.documentElement.dataset.layout = isSplit ? "split" : "full";
try {
const savedWidth = localStorage.getItem("bar-width");
if (savedWidth) {
window.document.documentElement.style.setProperty(
"--sidebar-width",
`${savedWidth}px`,
);
}
} catch (_) {}
</script>
<script type="module" src="/scripts/entry.js"></script>
@@ -111,7 +125,6 @@
</header>
<ul id="search-results"></ul>
</search>
</main>
<div id="resize-bar"></div>
<aside id="aside">
@@ -127,12 +140,7 @@
title="View sidebar"
class="full-only"
>
<input
type="radio"
name="frame"
id="aside-selector"
value="aside"
/>
<input type="radio" name="frame" id="aside-selector" value="aside" />
View
</label>
@@ -141,11 +149,7 @@
Browse
</label>
<label
id="search-selector-label"
for="search-selector"
title="Search"
>
<label id="search-selector-label" for="search-selector" title="Search">
<input
type="radio"
name="frame"
@@ -166,17 +170,5 @@
<a id="share-anchor" href="/"></a>
</div>
</div>
<script>
try {
// Prevent width jumping
const savedWidth = localStorage.getItem("bar-width");
if (savedWidth) {
const main = window.document.getElementById("main");
if (!main) throw "Should exist";
main.style.width = `${savedWidth}px`;
}
} catch (_) {}
</script>
</body>
</html>

View File

@@ -198,19 +198,20 @@ onFirstIntersection(navElement, () => {
function initResizeBar() {
const bar = getElementById("resize-bar");
const key = "bar-width";
const root = document.documentElement;
const max = () => parseFloat(style.getPropertyValue("--max-main-width")) / 100 * window.innerWidth;
const saved = readStored(key);
if (saved) mainElement.style.width = `${saved}px`;
if (saved) root.style.setProperty("--sidebar-width", `${saved}px`);
/** @param {number | null} width */
function setWidth(width) {
if (width != null) {
const clamped = Math.min(width, max());
mainElement.style.width = `${clamped}px`;
root.style.setProperty("--sidebar-width", `${clamped}px`);
writeToStorage(key, String(clamped));
} else {
mainElement.style.width = "";
root.style.removeProperty("--sidebar-width");
removeStored(key);
}
}

View File

@@ -23,8 +23,12 @@ export function onChange(callback) {
function setDark(value) {
if (dark === value) return;
dark = value;
apply(value);
callbacks.forEach((cb) => cb());
const swap = () => {
apply(value);
callbacks.forEach((cb) => cb());
};
if (document.startViewTransition) document.startViewTransition(swap);
else swap();
}
/** @param {boolean} isDark */

View File

@@ -76,7 +76,9 @@ body {
position: relative;
html[data-layout="split"] & {
grid-template-columns: minmax(min-content, auto) 1fr;
grid-template-columns:
minmax(min-content, var(--sidebar-width, max-content))
1fr;
html[data-display="standalone"] & {
border-top: 1px;
@@ -267,7 +269,6 @@ summary {
}
}
html[data-layout="split"] .full-only {
display: none !important;
}

View File

@@ -40,9 +40,8 @@ main {
}
html[data-layout="split"] & {
min-width: 100%;
max-width: var(--max-main-width);
overflow-x: hidden;
contain: inline-size;
}
> nav,