fix(trie): make TrieImpl.insert() idempotent for duplicate key-value#6696
fix(trie): make TrieImpl.insert() idempotent for duplicate key-value#6696halibobo1205 wants to merge 3 commits intotronprotocol:developfrom
Conversation
…puts
Reorder condition checks in insert() so that commonPrefix.equals(k) is
evaluated before commonPrefix.isEmpty(). When both k and currentNodeKey
are empty (which happens on a duplicate put of a fully-split key), the
old order incorrectly fired the "no common prefix" branch and replaced
KVNode("",v) with BranchNode{terminal:v}, corrupting the root hash.
Also short-circuit kvNodeSetValueOrNode() when the new value equals the
existing one (by reference or by byte content) to avoid unnecessary
dirty marking and downstream hash recomputation.
close tronprotocol#6608
|
[MUST] The consensus-impact analysis is already documented in #6608, but per the repo’s consensus-change requirements, please mirror a short summary into the PR description itself and explicitly document:
A short summary referencing #6608 is sufficient; no need to restate the full analysis. Optional cleanup: since this PR already touches |
|
|
I have a question regarding the description: the current code does not affect the consensus. The modification is merely intended to ensure that the results of the random write are consistent. |
Summary
Fix two bugs in
TrieImpl.insert()that together break Merkle Trie idempotency when the same key is put more than once (regardless of whether the value changes):Correctness (root-hash corruption). Reorder the condition checks in
insert()so thatcommonPrefix.equals(k)is evaluated beforecommonPrefix.isEmpty(). The two conditions overlap only when bothkandcurrentNodeKeyare empty, which the recursion reaches exclusively on a duplicate put of an already-inserted key on a fully-split KV leaf. In that state, the old order incorrectly triggered the "no common prefix" branch and replacedKVNode("", v_old)withBranchNode{terminal: v_new}, corrupting the trie structure and, in turn, the root hash. This happens whether or not the new value equals the old one.Optimization (avoid unnecessary recomputation). Short-circuit
kvNodeSetValueOrNode()when the newbyte[]value equals the existing one (by reference or byte content) to skip redundant dirty marking and downstream hash recomputation on the KV-leaf path. This does not by itself fix the root-hash corruption; it only reduces cache churn. Branch-terminal writes andNode-valued writes are intentionally not short-circuited (parent nodes still need dirty propagation when a child is mutated in place).Closes #6608.
When the bug triggers
The only production path into
TrieImplisAccountStateCallBack. ItstrieEntryListis an undeduplicatedArrayList: if multiple transactions touch an account in the same block, `trie.put(addr, …) is called multiple times with the same key, hitting bug 1.Consensus impact and release scope
AccountStateCallBackis gated behind proposal 25 (ALLOW_ACCOUNT_STATE_ROOT), which has not been activated on mainnet or nile. Therefore, this fix has no impact on the current consensus.If proposal 25 is ever activated,
AccountStateCallBackwill compute per-block account-state roots stored in block headers. Without this fix, two honest nodes processing the same block — where any account is modified by more than one transaction — could compute different state roots and fork. This fix must therefore be merged and released before any release that could run a chain on which proposal 25 may activate.during
ALLOW_ACCOUNT_STATE_ROOTactivation.See #6608 for the full reproduction and affected code paths.