Skip to content

execution/commitment: v3 edge-record format and the hoodi 1M v2/v3 run #23773

Description

@awskii

Commitment v3 replaces each bundled branch row with one record per trie edge; leaves are records too, not fields of the parent row. The design note predates the implementation, and the cited code lines in awskii/commitment-v3 are the specification.
Uses #17838 as a part of scheme

Format

Item Encoding or rule
Node key Whole nibble pairs are packed two per byte. An even-length path ends with the terminator 0x00; for an odd-length path the trailing nibble is not packed, it rides in the terminator as 0xf0|nibble. Length is floor(n/2)+1; no parity byte or pad nibble. nibbles_v2.go:24, nibbles_v3.go:32-51
Record key nodeKey(parent) || (0x80|childNibble), length floor(depth/2)+2. Siblings occupy [nodeKey||0x80, nodeKey||0x90). nibbles_v3.go:85-93, nibbles_v3.go:112-115, nibbles_v3.go:127-135
State key 0x00, also the empty-path root node key; value shape distinguishes state from records. branch_cache.go:40, branch_cache.go:104-107, commitment_context.go:1328-1330
Flags 0x01 leaf, 0x02 odd extension, 0x04 storage leaf, 0x08 account has storage, 0x10 hash present, 0x20 hoisted slot. For a branch child, 0x10 is always set and the 32-byte hash is mandatory; for a leaf, it marks an optional 32-byte state hash directly after the flags byte. record.go:31-39
Branch child [flags:1][childMask:2 BE][hash:32][extension:tail]; 0x10 is set. record.go:154-168
Storage leaf [flags:1][stateHash:32?][storageSlot:32]; no extension. record.go:220-221, record.go:394
Hoisted slot [flags:1][stateHash:32?][storageMask:2 BE][accountAddr:20][storageSlot:32][extension:tail]. record.go:222-228
Account with storage [flags:1][stateHash:32?][storageRoot:32][storageMask:2 BE][accountAddr:20][extension:tail]. record.go:229-235
Plain account [flags:1][stateHash:32?][accountAddr:20][extension:tail?]. record.go:236-240
Extension Packed two nibbles per byte; length follows from record length, 0x02 marks odd count, and the pad nibble is zero. record.go:383-433
Masks A non-root node’s mask is stored in its incoming edge record. RootMask lives in the state blob. Unknown-mask reads request all 16 children. record.go:126-168, hex_patricia_hashed.go:3120-3123, hex_patricia_hashed.go:3169, commitment_records.go:44-57
Deletion Write a zero-length value at the cleared child’s record key; mask-driven reads omit it, while merges drop it. commitment.go:604-626
Version gate Edge .kv files use v3.0; bundled rows use v2.2, or v2.1 with references. A file is edge-record format iff version ≥3.0; merges derive output format from inputs. state_schema.go:185-210
Configuration COMMITMENT_EDGE_RECORDS is read once, defaults true, and has no CLI or erigondb.toml setting. Existing commitment .kv files persist the datadir choice and override the default; mixed formats are rejected. state_schema.go:217-220, commitment_format_resolve.go:33-71, aggregator.go:396
Compatibility Record reads skip files below v3.0. erigon commitment convert changes legacy key encoding and .kv squeeze state only, refuses edge-record files, and does not migrate history or accessors. commitment_records.go:136-139, commitment_convert.go:84, commitment_convert.go:390, commitment_convert.go:404
History Each entry holds the previous value of one edge-record key, 34 B per entry in the raw step-44 file on hoodi; v2 holds the previous bundled row. Unchanged values are not written. commitment.go:625, domain_shared.go:1856-1866, temporal_mem_batch.go:151-161

Results

Two identical AMD EPYC 4344P hosts with 16 threads and 125 GiB RAM ran hoodi from wiped chaindata with no state snapshot files to block 1M using 3.7.0-dev-d7a9d63f, --chain=hoodi --prune.mode=full --prune.include-commitment-history --exec.serial --experimental.parallel-commitment, and KV_READ_METRICS=true; the v2 arm set COMMITMENT_EDGE_RECORDS=false. Wall clock was 22,389 s for v2 and 20,609 s for v3: v3 was 8.0% faster and led every 100k-block range.

On disk v2 v3 v3/v2
.kv 9,609,434,637 B 7,536,176,763 B 0.78
.bt 52,560,944 B 150,406,752 B 2.86
.kvei 36,260,880 B 116,359,184 B 3.21
.v history 28,704,912,438 B 17,586,615,076 B 0.61
.vi 1,954,684,787 B 2,548,136,820 B 1.30
.ef 1,818,839,737 B 4,480,275,804 B 2.46
.efi 160,629,568 B 606,486,055 B 3.78
All commitment files 42,337,322,991 B 33,024,456,454 B 0.78
chaindata (mdbx file size, never shrinks) 36,674,994,176 B 19,864,223,744 B 0.54

The v3 format saves 13.2 GB of data, adds 3.9 GB of index, and saves 9.3 GB net; every per-key index grows 2.5–3.8x. The v2 arm’s unmerged step file inflates its history total. Between-cycle flush falls from 2,748 s to 1,209 s, including MDBX sync from 984 s to 159 s; storage writes fall from 1,133 GB to 502 GB. The v3 format pays 1.93x in commitment compute and 17.6x in warmup file-record reads for +3.5% process CPU.

#23199 reports 110–140 MiB/block of MDBX copy-on-write writes at mainnet tip; v3 cuts mdbx cow page ops 4.0x and storage writes 2.26x on this from-0 hoodi run, while tip behaviour remains unmeasured.

Open

  • Whether the per-record cost is acceptable: keys 2.14x at step 44 (3.03x at step 0), history entries 1.30x, every per-key index 2.5-3.8x; the alternative is bundled rows for the trunk and edge records only inside storage subtrees.
  • Reproduce the mainnet-tip write profile on v3.
  • Measure unknown-mask reads across a deep file stack using the branch counters; the rig binary predates them.
  • Add history and accessor migration before changing any published snapshot set.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions