perf(l1): decoded TrieLayerCache: skip Node::decode() on cache hits#6348
perf(l1): decoded TrieLayerCache: skip Node::decode() on cache hits#6348
TrieLayerCache: skip Node::decode() on cache hits#6348Conversation
🤖 Kimi Code ReviewReview SummaryThis PR introduces Critical Issues
Security Concerns
Performance & Correctness
Code Quality Issues
Specific Recommendations
The PR introduces valuable caching improvements but needs attention to the critical issues above before merging. Automated review by Kimi (Moonshot AI) · custom prompt |
Lines of code reportTotal lines added: Detailed view |
🤖 Codex Code Review
I could not run Automated review by OpenAI Codex · custom prompt |
🤖 Claude Code ReviewNow I have enough context to write a thorough review. Here's my analysis: Code Review: Decoded
|
TrieLayerCache: skip Node::decode() on cache hitsTrieLayerCache: skip Node::decode() on cache hits
Benchmark Block Execution Results Comparison Against Main
|
a03408a to
fdb6071
Compare
Decoded TrieLayerCache: skip Node::decode() on cache hits
Summary
Arc<Node>in the trie layer cache instead of RLP-encoded bytes, eliminating ~100nsNode::decode()per cache hit (~200K lookups/block)TrieLayermap into separateaccount_nodes,storage_nodes, andleaf_valuesmaps with a newCachedTrieEntry { node: Arc<Node>, encoded: Vec<u8> }typeget_node()method to theTrieDBtrait (default no-op) soNodeRef::get_node/get_node_mut/get_node_checkedcan retrieve decoded nodes directly from cache before falling back toget()+ decodeTrieCommitEntryenum to carry both decodedArc<Node>and RLP encoding through the commit pipeline, replacing flat(Nibbles, Vec<u8>)tuplesMotivation
The
TrieLayerCachewas the only cache in ethrex storing RLP-encoded bytes — every other cache (CodeCache, GeneralizedDatabase, etc.) stores decoded Rust structs. On every cache hit, the caller still had to callNode::decode(&rlp), wasting CPU on deserialization of data we just serialized a few blocks ago. Additionally, account andstorage trie nodes were mixed in a single
FxHashMapusing a nibble-prefix hack with an invalid separator byte (0x11).Design
TrieCommitEntry— A structured enum produced byNodeRef::commit():Node { path, node: Arc<Node>, encoded: Vec<u8> }— trie nodes with both formsLeafValue { path, value: Vec<u8> }— FKV leaf values (application-level data, not decoded at trie level)This flows through
collect_changes_since_last_hash()→AccountUpdatesList→TrieLayerCache::put_batch(). Theinto_rlp_pair()method provides backward-compatible(Nibbles, Vec<u8>)conversion for DB writes andTrie::commit().TrieDB::get_node()— New trait method with defaultOk(None).TrieWrapperimplements it to returnArc<Node>from the layer cache.NodeRef::get_node/get_node_mut/get_node_checkedtry this first, falling back to the existingget()+Node::decode()path only on cache miss.BackendTrieDBandInMemoryTrieDBinherit thedefault (always go to DB).
Split
TrieLayer— Three separate maps instead of one:account_nodes: FxHashMap<Vec<u8>, CachedTrieEntry>— unprefixed nibble pathsstorage_nodes: FxHashMap<Vec<u8>, CachedTrieEntry>— prefixed nibble paths (existing format)leaf_values: FxHashMap<Vec<u8>, Vec<u8>>— FKV entries (both account and storage)The single bloom filter is retained across all three maps.
commit()recombines entries into the same flatVec<(Vec<u8>, Vec<u8>)>format so the disk dispatch logic inapply_trie_updatesis unchanged.put_batch()signature change — Takes separateaccount_entriesandstorage_entriesinstead of a pre-flattenedVec<(Nibbles, Vec<u8>)>. The prefix application for storage entries moves inside the cache, simplifyingapply_trie_updates.