Skip to content

Reduce CAccountMap NIF surface with map-owned storage and frozen-only lock#43

Merged
dominicletz merged 16 commits into
masterfrom
feat/cmerkle-nif-refactor
Jul 17, 2026
Merged

Reduce CAccountMap NIF surface with map-owned storage and frozen-only lock#43
dominicletz merged 16 commits into
masterfrom
feat/cmerkle-nif-refactor

Conversation

@dominicletz

@dominicletz dominicletz commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

  • Move account storage ownership into CAccountMap: EVM/RPC/Edge use storage_put_map / storage_* / proofs instead of live merkletree resources from get.
  • Shrink the production NIF surface (~21 always-on exports): merge put/storage/roots/proof NIFs; gate bare-tree + debug NIFs behind CMERKLE_TEST_NIFS (on unless MIX_ENV=prod).
  • Map-native genesis / Voyager / from_binary; remove dead Account.tree / storage_* / compact APIs. Speculative paths use State.clone/1; account_map_lock is O(1) frozen only.
  • No live state_trie export — Edge uses state_root_hashes/1. State.compact/1 is one account_map_compact NIF. Storage get/size/range stay on the normal scheduler.
  • Docs: docs/caccount-map-nif.md, AGENTS.md, LOCK_ORDER.md, SECURITY_REVIEW.md.
  • Fix prod startup OOM: SyncSql no longer SELECTs the entire sync.sq3 into memory (batched + background purge). Add MemoryGuard to halt on absolute/rapid RSS growth.

Test plan

  • mix test test/cmerkle_*.exs test/chain_state_*.exs test/caccount_map*.exs test/chain_account_hash_nif_test.exs test/cmerkletree_test.exs --exclude external --no-start
  • MIX_ENV=prod mix compile (bare-tree NIFs omitted)
  • mix test test/memory_guard_test.exs
  • Smoke ./run / prod start: RSS stays ~flat during SyncSql purge (no immediate OOM)
  • mix run --no-start scripts/cmerkle_fuzz.exs -- --iterations 2000 --seed 1
  • mix run --no-start scripts/cmerkle_parallel_stress.exs -- --waves 5 --tasks 48
  • Smoke eth_call / sendtransaction speculative path on a local ./dev node
  • Confirm jump-block compact → uncompact → hash still matches a synced fixture if available

dominicletz and others added 2 commits July 16, 2026 12:10
…oot.

Speculative paths use clone_lazy; Chain.State drops :store in favor of an
internal state_trie with map-level freeze and native difference/apply.

Co-authored-by: Cursor <cursoragent@cursor.com>
OptionParser treated child flags like --no-start as watchdog options;
parse manually like the deadlock watchdog so the nightly CI command works.

Co-authored-by: Cursor <cursoragent@cursor.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors state management by moving the state root trie into C++ via the CAccountMap NIF, removing the separate :store field from Elixir's Chain.State, and introducing a speculative clone_lazy mechanism for RPC and EdgeV2 execution. Critical feedback on the C++ implementation highlights a refcounting leak and potential Use-After-Free (UAF) vulnerability with merkletree resources, a BEAM VM crash risk from using enif_make_existing_atom on uninitialized atoms, and dead code in the destructor of SharedAccountMap that would cause a double-decrement of has_clone if activated.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread c_src/nif.cpp
Comment thread c_src/nif.cpp
Comment thread c_src/nif.cpp Outdated
dominicletz and others added 2 commits July 16, 2026 12:20
Remove dead SharedAccountMap COW fields, share one fork helper for
eager/lazy clone, reject writes on frozen maps, and own materialized
storage with a single map ref so uncompact no longer over-keeps.

Co-authored-by: Cursor <cursoragent@cursor.com>
…lock.

Stop exporting live storage tries from account_map_get, batch EVM writes via
storage_put_map, and drop clone_lazy / per-trie seal so speculative paths and
lock cost stay simpler.

Co-authored-by: Cursor <cursoragent@cursor.com>
@dominicletz dominicletz changed the title Refactor CMerkleTree account map for lazy clone and NIF-owned state root Keep account storage in NIF with map-owned APIs and frozen-only lock Jul 16, 2026
dominicletz and others added 2 commits July 16, 2026 15:54
Export state root hashes instead of a live state_trie, compact via one
account_map_compact NIF call, and make map-backed accounts explicit with
map_backed on Chain.Account.

Co-authored-by: Cursor <cursoragent@cursor.com>
Merge duplicate account_map NIFs, gate bare-tree exports behind CMERKLE_TEST_NIFS, drop dead Account storage APIs, and keep EVM storage reads on the normal scheduler.

Co-authored-by: Cursor <cursoragent@cursor.com>
@dominicletz dominicletz changed the title Keep account storage in NIF with map-owned APIs and frozen-only lock Reduce CAccountMap NIF surface with map-owned storage and frozen-only lock Jul 16, 2026
dominicletz and others added 10 commits July 16, 2026 16:44
Normalize ids the same way as ensure_account so cache_account and other EVM paths type-check.

Co-authored-by: Cursor <cursoragent@cursor.com>
Drop put_tree and live-tree root_hash, make root_hash a struct field, require genesis_storage/0 on all chains, and route Edge account roots through Account.root_hash.

Co-authored-by: Cursor <cursoragent@cursor.com>
…nce.

Startup purge of sync.sq3 was SELECT-ing every block into memory; batch and
background it, and add MemoryGuard so absolute/rapid RSS growth stops the VM.

Co-authored-by: Cursor <cursoragent@cursor.com>
Document Phases A–E (cached compact roots, SharedState equality, NIF-returned
roots, CompactStorage COW, state_trie-driven diff) and add a bench that
reproduces the multi-second compact_small_delta hotspot.

Co-authored-by: Cursor <cursoragent@cursor.com>
Production already uses account-map APIs only; remove gated bare-tree exports,
legacy merkle benches, and update fuzz/stress/tests to the map-native surface.

Co-authored-by: Cursor <cursoragent@cursor.com>
Lock in compact/uncompact root, clone isolation, difference tuple shape,
and nif_stats coverage so Phases A–E stay regression-tested.

Co-authored-by: Cursor <cursoragent@cursor.com>
P10/P11 (and nightly P4/P5) no longer exist; use P12/P14 (and P12–P16 nightly).
Also slim the state-diff contract tests and drop the redundant NIF surface file.

Co-authored-by: Cursor <cursoragent@cursor.com>
CompactStorage caches storage roots; difference_full is state_trie-driven and
returns 6-tuples so Elixir skips a second storage_root_hash pass. Clone shares
compact slots via shared_ptr. Align docs/LOCK_ORDER/bench with shipping behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
Reproduce the >2s Model.ChainSql.state/1 paths without starting the node, and
document them next to the existing State.difference bench.

Co-authored-by: Cursor <cursoragent@cursor.com>
@dominicletz
dominicletz merged commit 9fd4f70 into master Jul 17, 2026
4 checks passed
@dominicletz
dominicletz deleted the feat/cmerkle-nif-refactor branch July 17, 2026 11:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant