One map for address_head / hashhead / segmented / scripthash_head.
Confirm stages (lookup / load / scripts / write) and allowed IO live in
invariants.md. Roles: concurrency.md.
On-disk bytes: SCHEMA.md.
header.hash ──► header.head HashHead gen 0 (2²² mainnet; 64 tiny)
──► header.head.gN overflow gens (same slots; probe newest first)
16 B prefix + 8 B fk; .mlt if multi
txid mix ──► tx.head/ AddressHead inside SegmentedTxHead
open: 4 B rel, page-local mix_txid; seal: MPHF+fuse8
(fuse mmap from .fuse8, heap 0; packed BDZ g FdOnly 4 KiB pages; index = rel-1)
spk hash ──► scripthash.head/NN.mphf+.val sealed BDZ3 main (2-bit g + rank; pack8; tags, no fuse)
──► scripthash.body/NN dir-variant main slabs/pages (file variant: scripthash.body)
──► scripthash.ovf/ingest incremental + post-seal new keys (key16+pack8)
──► scripthash.ovf/body dir-variant ingest + sealed ovf slabs
──► scripthash.ovf/NNNNNN L0 SHSR pack8; compact once → L1 MPHF+fuse8
| Module | On disk | Key → value | Who reads it |
|---|---|---|---|
HashHead |
header.head (+ .gN) |
header hash prefix → header fk (.mlt if several) |
Header ensure / has_block / prev walk |
AddressHead + SegmentedTxHead |
tx.head/ (meta, open NNNNNN, sealed `.mphf |
.fuse8`) | mixed txid → relative create_fk (body-verify on txid.body). Live OA rolls at 80% slots; wipe-rebuild seals 2²⁵ keys/range in parallel (no OA). Lookup maps .fuse8 read-only (heap fuse8=0); packed BDZ g is FdOnly (4 KiB page stream); MPHF output is rel−1. Open OA is keyless; the seal sidecar collects keys from txid.body. |
| Sealed SH main | scripthash.head/NN.mphf + .val |
Electrum scripthash prefix → pack8 locators. Compact BDZ3: packed 2-bit g FdOnly; occupancy mapped (prefix through occ, not tags); tags/val FdOnly. Pass-1 extract BDZ writes this path (inline_one vs Empty) before pack; MphfHead::exists is not pack-done (RAM unsealed until publish_packed_shard). |
After tip bulk |
| Ingest + L0/L1 ovf | scripthash.ovf/ingest, L0 SHSR, L1 MPHF, ovf/body |
Same pack8 key for incremental / post-seal new keys | Tip; lookup ingest → L0 → L1 → main |
tx.head is not a HashHead. Header slots come from HeadScale (RBITCOIN_HEAD_SLOTS_HEADER override). Sorted/MPHF SH shards are sh_main_shard_count (tiny=1, mainnet=64), not a HashHead. Leftover 256-way header.head/ is Layout refuse.
- Live pipeline pin by prev_txid (same Weak as outs).
- Open wave: every unsealed OA (insert tail + in-flight seal), newest-first — one probe, then two-shot
txid.body. Retire keys whose create is fence-connected. - Still unfinished: each sealed segment, newest first. Fuse + one MPHF batch (or the sealed OA), then the same two-shot identity, then retire. A fence-connected hit skips every older segment. An unconnected body match does not.
TipThenAny / TipOnly still walk older segments after an unconnected earlier
hit so a connected sibling can win.
sealed_age_from_index vs HEAD_PROBE_HOT_MAX_AGE (3) still labels sealed
age for stats and for the SealedHot / Cold probe-coverage split. Lookup
does not union ages 1..=3 before identity. It is not an IO flag.
RWF_DONTCACHE is retired (SCHEMA.md Schema 17 freeze).
Allowed/Forbidden IO and in-flight prune: invariants.md.
Roles: concurrency.md.
lookup is the only stage that probes tx.head: BQ-ahead TipOnly
get_fk_by_txid_batch (open wave, then each sealed segment newest-first;
a segment runs only for keys still unfinished). In-page hop keeps 8
(depth, fk) on the stack and spills past that; page grouping and the
uring stream are unchanged.
Combined head_loc cdf3 was ~90% on late-mainnet — not enough to pay a
full-depth probe for every key. Revisit if leftover-split wave cdf3 is
<60%. Write inserts via head_insert_many on ibd-confirm-head (Drain ∥
Class C). RPC get_fk_by_txid hits durable head only until that drain.