You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The local-file processing path is already competitive with Bitcoin Core, but the loopback P2P path still trails Core by approximately 1.37x in the historical matched 0→150k measurement. The current implementation also exposes storage-cache configuration that does not appear to reach the selected backend.
This issue tracks two concrete sources of remaining headroom:
P2P download/staging depth and memory representation.
Effective database-cache configuration and measured storage behavior.
This is a scoped investigation under #33 and #39. The controlled P2P fixture in #35 should be used for acceptance measurements.
Current observations
Fixed 128-block P2P window
crates/node/src/sync.rs currently couples:
PENDING_BUDGET = 128
RECEIVED_BLOCK_BUDGET = 128
a 2 MiB estimated block size
an approximately 256 MiB pending/staging byte budget
16 in-flight blocks per peer while fan-out is active
The count bound is reasonable for modern 1–2 MiB blocks, but it binds far before the byte budget for small historical blocks. This limits both request depth and the script-verification windows formed by P2P sync. The README already identifies the 128-block staging depth as the reason peer sync does not reach local-replay throughput.
A constant-only increase is not acceptable. The count is coupled to staller arming, pending/staged parity, inline capacities, channel drain sizing, peer fan-out, and memory assertions.
Staging retains decoded and serialized bodies
crates/node/src/sync/stage.rs retains both:
a decoded bitcoin::Block
the original bytes::Bytes P2P payload
The byte budget accounts for the serialized payload, while the decoded block adds substantial unaccounted memory. A fully out-of-order window can therefore retain roughly twice the nominal staged bytes.
dbcache_mb is not plumbed into backend construction
Config::dbcache_mb is parsed and defaults to 450 MiB, but NodeStorage::open calls backend constructors without passing it.
Current backend behavior appears to be:
Fjall: builder defaults
RocksDB: fixed 256 MiB block cache
redb/MDBX: backend defaults
The configured cache target therefore does not currently define a process-level database cache posture. This also makes cache-parity claims difficult to interpret.
Investigation plan
1. Add attribution before tuning
Measure at least:
time apply is idle waiting for blocks
time download/staging is blocked by apply
per-peer block RTT and bytes/sec EWMA
inbound queue wait and block decode time
pending/staged block and byte high-water marks
actual retained memory for decoded plus serialized bodies
retry count, duplicate delivery, staller disconnects, and pending timeouts
backend write, flush, compaction, and cache behavior
Do not select a mechanism until the controlled P2P baseline attributes the gap.
2. Evaluate a dynamic count-and-byte window
Evaluate a larger count ceiling while preserving the existing byte ceiling:
small historical blocks: hundreds or thousands of blocks may be in flight/staged
modern large blocks: the byte ceiling keeps the effective depth near the current range
one oversized valid block remains processable
no eviction/re-download churn is introduced
staller detection continues to blame only a peer-caused blocked frontier
All count-coupled invariants and tests must be made explicit rather than mechanically changing 128.
3. Evaluate raw-first staging
Evaluate retaining only the checksum-validated wire payload plus the minimum identity metadata needed by the scheduler, deferring full bitcoin::Block decode until the block enters an apply window.
This candidate should be judged primarily as a way to:
make the byte budget reflect actual retained block data more closely
permit a deeper small-block window under the same RSS limit
remove full decode work from peer reader threads
It must not add another parse or serialization pass to the apply path.
4. Evaluate measured peer-quality scheduling
Only if the baseline shows peer heterogeneity is material, evaluate request allocation using recent per-peer throughput, completion latency, and frontier-blocking history.
Any policy must retain peer diversity and bounded exploration rather than assigning all work permanently to the currently fastest peer. Existing staller disconnect, cooldown, single-peer fallback, and no-blame backpressure behavior must remain intact.
5. Make dbcache_mb effective
Introduce backend constructors that accept a cache budget and define how one process-level budget is divided among chainstate and optional indexes.
Requirements:
Fjall cache capacity is configured explicitly.
RocksDB no longer ignores the configured value in favor of an unconditional 256 MiB cache.
Enabling txindex and blockfilterindex does not multiply the total configured budget once per database.
Effective capacities are observable in logs or metrics.
Zero, minimum, overflow, and unsupported-backend behavior is defined.
Measure cold start, warm replay, modern-corpus apply, and indexed workloads. The RAM-resident UTXO path means an early-chain replay alone is insufficient evidence for a cache change.
6. Keep lower-ceiling storage work secondary
Window-level block-file/index batching and incremental checkpoints may be investigated only after attribution shows they are material.
Historical 0→150k decomposition bounded block-body persistence at approximately 4.66 seconds total. Do not weaken body availability, reorg readiness, pruning ordering, or crash durability to remove that bounded cost.
Problem
The local-file processing path is already competitive with Bitcoin Core, but the loopback P2P path still trails Core by approximately 1.37x in the historical matched 0→150k measurement. The current implementation also exposes storage-cache configuration that does not appear to reach the selected backend.
This issue tracks two concrete sources of remaining headroom:
This is a scoped investigation under #33 and #39. The controlled P2P fixture in #35 should be used for acceptance measurements.
Current observations
Fixed 128-block P2P window
crates/node/src/sync.rscurrently couples:PENDING_BUDGET = 128RECEIVED_BLOCK_BUDGET = 128The count bound is reasonable for modern 1–2 MiB blocks, but it binds far before the byte budget for small historical blocks. This limits both request depth and the script-verification windows formed by P2P sync. The README already identifies the 128-block staging depth as the reason peer sync does not reach local-replay throughput.
A constant-only increase is not acceptable. The count is coupled to staller arming, pending/staged parity, inline capacities, channel drain sizing, peer fan-out, and memory assertions.
Staging retains decoded and serialized bodies
crates/node/src/sync/stage.rsretains both:bitcoin::Blockbytes::BytesP2P payloadThe byte budget accounts for the serialized payload, while the decoded block adds substantial unaccounted memory. A fully out-of-order window can therefore retain roughly twice the nominal staged bytes.
dbcache_mbis not plumbed into backend constructionConfig::dbcache_mbis parsed and defaults to 450 MiB, butNodeStorage::opencalls backend constructors without passing it.Current backend behavior appears to be:
The configured cache target therefore does not currently define a process-level database cache posture. This also makes cache-parity claims difficult to interpret.
Investigation plan
1. Add attribution before tuning
Measure at least:
Do not select a mechanism until the controlled P2P baseline attributes the gap.
2. Evaluate a dynamic count-and-byte window
Evaluate a larger count ceiling while preserving the existing byte ceiling:
The intended behavior is:
All count-coupled invariants and tests must be made explicit rather than mechanically changing
128.3. Evaluate raw-first staging
Evaluate retaining only the checksum-validated wire payload plus the minimum identity metadata needed by the scheduler, deferring full
bitcoin::Blockdecode until the block enters an apply window.This candidate should be judged primarily as a way to:
It must not add another parse or serialization pass to the apply path.
4. Evaluate measured peer-quality scheduling
Only if the baseline shows peer heterogeneity is material, evaluate request allocation using recent per-peer throughput, completion latency, and frontier-blocking history.
Any policy must retain peer diversity and bounded exploration rather than assigning all work permanently to the currently fastest peer. Existing staller disconnect, cooldown, single-peer fallback, and no-blame backpressure behavior must remain intact.
5. Make
dbcache_mbeffectiveIntroduce backend constructors that accept a cache budget and define how one process-level budget is divided among chainstate and optional indexes.
Requirements:
Measure cold start, warm replay, modern-corpus apply, and indexed workloads. The RAM-resident UTXO path means an early-chain replay alone is insufficient evidence for a cache change.
6. Keep lower-ceiling storage work secondary
Window-level block-file/index batching and incremental checkpoints may be investigated only after attribution shows they are material.
Historical 0→150k decomposition bounded block-body persistence at approximately 4.66 seconds total. Do not weaken body availability, reorg readiness, pruning ordering, or crash durability to remove that bounded cost.
Acceptance criteria
dbcache_mbproduces observable backend capacities without multiplying the process budget across optional indexes.Non-goals