refactor(mpt): give digest node references a fixed-size type - #680
Draft
Qumeric wants to merge 1 commit into
Draft
refactor(mpt): give digest node references a fixed-size type#680Qumeric wants to merge 1 commit into
Qumeric wants to merge 1 commit into
Conversation
`NodeRef::Digest` and `NodeData::Digest` documented their payload as always 32 bytes but held a `&[u8]`, so the length was lost at every use and had to be re-established by hand to keep comparisons and copies inline. Carrying `&Digest` instead states the invariant in the type: the two helpers that re-derived it are gone, and the conversions now happen once, where witness bytes enter the trie. The only site that never checked the length is the root reference, where a non-list node of at least 32 bytes was assumed to be a digest; it now reports a malformed witness instead. Dropping the `debug_assert` in `from_rlp_slice` likewise turns a debug-only panic on a reference of an unexpected length into an ordinary reference mismatch.
Qumeric
force-pushed
the
valery/mpt-digest-type
branch
from
July 25, 2026 12:27
5a70c6e to
bd39d15
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
digest_eqandput_digest(both now ondevelop-v2.1.0) each re-establish a fact the code alreadyknows: a digest node reference is 32 bytes. They have to, because
NodeRef::DigestandNodeData::Digestdocument that in a comment while holding a&[u8]— so the length is thrown awayat every use, and the compiler falls back to
memcmp/memcpycalls for operations that should be ahandful of word moves.
Carrying
&Digest(&[u8; 32]) states it in the type instead. Both helpers disappear: comparing is==and copying isput_slice, each already inline. The conversions happen once, where witness bytesenter the trie, through a single
digest_from. The diff removes more than it adds.Why this is worth landing even though it is performance-neutral
Measured at +0.053% instructions — neutral, as expected, because
digest_eq/put_digestalreadyachieve the same codegen by hand. The reason to land it is that it turns two unchecked assumptions
into ordinary errors:
decode_trie) treated any non-list node of at least 32 bytes as a digestwithout checking its length. It now reports a malformed witness.
from_rlp_sliceassertedslice.len() < 32for anything that was not 33 bytes — adebug_assert,so release builds dropped the check and silently built a reference that violated the invariant.
A reference of an unexpected length is now simply a
Bytesreference, which fails the comparisonagainst its parent and surfaces as
NodeRefMismatch.The guest runs in release. Encoding the invariant in the type is what makes the second one
unrepresentable rather than merely asserted.
bytes_eqstays for the one remaining caller, comparing references shorter than 32 bytes, where thebyte loop is still cheaper than a call. Its doc comment now explains why digests do not need it.
One footgun worth knowing about for reviewers:
DIGEST_LENis used inmatcharms. Without theimport in scope an uppercase identifier in a pattern becomes a fresh binding that matches everything,
silently. rustc does catch it — unreachable pattern, unused variable, and non-snake-case warnings all
fire — and I verified that by removing the import and watching all three appear.
Draft: this needs both #684 and #668 first
Its base branch predates the current queue. The change touches the digest sites that #684 reorganizes
into
rlp.rsand the node-hashing site that #668 converts to the sponge, so a meaningful base has tocontain both — no single open PR does. Rebasing it onto #684 alone leaves conflicts that are only
resolvable by reintroducing sponge code, and onto
develop-v2.1.0alone it would have to carry bothPRs' changes in its own diff.
Staying draft until #684 and #668 land, then rebasing onto
develop-v2.1.0for a clean two-file diff.