Commit graph

3 commits

Author SHA1 Message Date
CPerezz
21f243ff8a
triedb/pathdb,core/state: fix disklayer.storage fail-open gate and historicStateReader rlp.Split bug
Addresses review finding C4 + Opus agent audit secondary bug.

Bug 1 — fail-open gate in disklayer.storage:

disklayer.storage() compared a 64-byte merkle-shaped combinedKey
(accountHash || storageHash) against the 32-byte bintrie generator
marker via codec.MarkerCompare. For bintrie, accountHash is always
common.Hash{} (since bintrieFlatCodec.StorageKey returns zero for
the account key), so the combinedKey started with 32 zero bytes.
The sha256-derived marker's first byte is essentially never 0x00,
so bytes.Compare returned -1, the > 0 branch never fired, and the
generator-progress gate was silently DISABLED. During active
generation, disklayer.storage served whatever was on disk (nil or
stale) without returning errNotCoveredYet.

Fix: add StorageMarkerKey(accountHash, storageHash) to the
flatStateCodec interface. Merkle returns the 64-byte concatenation
(preserving existing behavior); bintrie returns storageHash[:]
(the 32-byte stem||offset key matching the generator marker shape).
disklayer.storage now uses the codec method.

Bug 2 — rlp.Split on raw bintrie storage leaves in historicStateReader:

historicStateReader.Storage at core/state/database_history.go:87
calls rlp.Split(blob) on whatever bytes the pathdb historical reader
returns. Merkle storage values are RLP-encoded (trimmed-left-zeros);
bintrie leaves are raw 32 bytes. rlp.Split on raw 32-byte input
either errors or decodes garbage. Even after fixing Bug 1, bintrie
historical storage reads were broken end-to-end.

Fix: add isVerkle bool to historicStateReader; when true, bypass
rlp.Split and copy the raw 32-byte blob directly. The flag is set
from db.triedb.IsVerkle() at construction time.
2026-04-15 15:00:41 +02:00
CPerezz
a1ff36d9e1
core/state,triedb/pathdb: wire bintrie leaves through stateUpdate
Drains the binaryHasher's LeafProducer side-channel in StateDB.commit and
threads the stem writes through stateUpdate.encodeBinary into the pathdb
state set as per-offset accountData entries (key = stem||offset, value =
32-byte leaf or nil for clears).

The flat-state codec gains a Flush method that owns the in-memory→disk
write path, replacing the codec-agnostic per-entry loop in writeStates.
The merkle codec preserves its historical per-entry behavior verbatim;
the bintrie codec aggregates per-offset writes by stem so each stem hits
disk via a single read-modify-write, satisfying the codec's pre-aggregation
requirement and updating the clean cache with the merged blob it just
produced (no extra disk read).

stateUpdate.encodeBinary returns empty origin maps for the bintrie path:
state-history rollback for bintrie is deferred to a follow-up PR (see
BINTRIE_FLAT_STATE_REORG_GAP.md), and the diskLayer.revert path will
panic before consuming origins anyway.
2026-04-15 15:00:40 +02:00
CPerezz
eaf5523a5a
triedb/pathdb: introduce flatStateCodec abstraction
Introduce flatStateCodec, a small interface that captures the
trie-specific aspects of flat-state storage: key derivation from
(address, slot), persistence of account/storage entries, clean-cache
key disambiguation, iterator setup, and progress-marker handling.

Mirrors the existing nodeHasher pattern and complements the Hasher
interface from state-hasher-iface-2 (which abstracts trie-side hashing
and commit). The codec is stored on Database alongside the existing
hasher field, ready to be threaded through the flat-state call sites
(disklayer, flush, generator, reader) in the next commit.

Provides merkleFlatCodec, a thin wrapper over the existing rawdb
snapshot accessors and helpers. This is a pure refactor: behavior is
unchanged. The bintrie-side codec implementation is added in a later
commit, after all call sites have been routed through the abstraction.
2026-04-15 15:00:39 +02:00