From 519a450c436970a8319f6c7cf383bf99cbc2c55d Mon Sep 17 00:00:00 2001 From: CPerezz <37264926+CPerezz@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:27:29 +0100 Subject: [PATCH] core/state: skip redundant trie Commit for Verkle in stateObject.commit (#34021) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary **Bug fix.** In Verkle mode, all state objects share a single unified trie (`OpenStorageTrie` returns `self`). During `stateDB.commit()`, the main account trie is committed via `s.trie.Commit(true)`, which calls `CollectNodes` to traverse and serialize the entire tree. However, each dirty account's `obj.commit()` also calls `s.trie.Commit(false)` on the **same trie object**, redundantly traversing and serializing the full tree once per dirty account. With N dirty accounts per block, this causes **N+1 full-tree traversals** instead of 1. On a write-heavy workload (2250 SSTOREs), this produces ~131 GB of allocations per block from duplicate NodeSet creation and serialization. It also causes a latent data race from N+1 goroutines concurrently calling `CollectNodes` on shared `InternalNode` objects. This commit adds an `IsVerkle()` early return in `stateObject.commit()` to skip the redundant `trie.Commit()` call. ## Benchmark (AMD EPYC 48-core, 500K entries, `--benchtime=10s --count=3`) | Metric | Baseline | Fixed | Delta | |--------|----------|-------|-------| | Approve (Mgas/s) | 4.16 ± 0.37 | **220.2 ± 10.1** | **+5190%** | | BalanceOf (Mgas/s) | 966.2 ± 8.1 | 971.0 ± 3.0 | +0.5% | | Allocs/op (approve) | 136.4M | 792K | **-99.4%** | Resolves the TODO in statedb.go about the account trie commit being "very heavy" and "something's wonky". --------- Co-authored-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> --- core/state/state_object.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/state/state_object.go b/core/state/state_object.go index dd30bb64a5..ec0c511737 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -474,6 +474,14 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) { s.origin = s.data.Copy() return op, nil, nil } + // In Verkle/binary trie mode, all state objects share one unified trie. + // The main account trie commit in stateDB.commit() already calls + // CollectNodes on this trie, so calling Commit here again would + // redundantly traverse and serialize the entire tree per dirty account. + if s.db.GetTrie().IsVerkle() { + s.origin = s.data.Copy() + return op, nil, nil + } root, nodes := s.trie.Commit(false) s.data.Root = root s.origin = s.data.Copy()