@@ -46,115 +46,18 @@ func isCommitmentStateKey(prefix []byte) bool {
4646 return bytes .Equal (prefix , KeyCommitmentState )
4747}
4848
49- // BranchCache stores commitment-trie branch data:
49+ // BranchCache stores commitment-trie branch data: a bounded LRU tail plus a
50+ // single never-evicted slot for the root branch (a length-0 / no-key prefix).
51+ // Aggregator-scope (one instance per Domain), pulled via BranchCacheProvider and
52+ // plumbed to the trie through InitializeTrieAndUpdates. It is a passive store —
53+ // the trie walker/encoder drive all reads and writes; the cache never fetches
54+ // state itself.
5055//
51- // - Bounded LRU tail with configurable capacity (eviction is well-defined,
52- // suitable for long-lived caching across many Process calls without
53- // unbounded memory growth).
54- // - Single pinned slot for the root branch (always hottest, always present
55- // once populated, never subject to LRU eviction). Compact prefix of
56- // length 0 (or single-byte "no-key" form) targets this slot.
57- //
58- // Lifetime: aggregator-scope (one instance per Domain). SharedDomains
59- // pulls the instance via BranchCacheProvider on the AggregatorRoTx;
60- // commitment-context plumbs it through to the trie via
61- // InitializeTrieAndUpdates. The previous WarmupCache type (per-Process,
62- // duplicating account/storage/branch caching above this layer) was
63- // deleted in the WarmupCache consolidation; BranchCache is now the
64- // single branch cache.
65- //
66- // # Responsibility split (architectural)
67- //
68- // The cache is a passive store. Reads and writes are driven by the
69- // trie walker / encoder; the cache itself never reaches into the
70- // underlying state.
71- //
72- // - BranchCache: passive store of branch bytes.
73- // Doesn't fetch anything.
74- // - Branch warmer (warmuper.go): narrow scope — pre-fetches
75- // *branches* along touched-key paths via SD.GetLatest. No
76- // account/storage prefetch — that conflated branch warm-up with
77- // leaf-data fetch. If a fold needs leaf data the trie walker
78- // fetches it directly (or it's already in Updates / memoized as
79- // stateHash).
80- // - Trie walker, block-processing path: receives Updates from the
81- // executor, folds them. Memoized stateHashes serve siblings; new
82- // values come from Updates. Doesn't reach into leaf data via
83- // prefetch.
84- // - Trie walker, witness / proof generation path: walks the trie
85- // structure and *needs* to fetch state to materialize the proof.
86- // This is the walker's responsibility — it drives its own reads
87- // against SD. If that path turns out to be cold-bound on real
88- // workloads it may indicate a need for separate account / storage
89- // caches (the `add_execution_context_with_caches` work has a
90- // reference design for these). Treat that as a separate concern
91- // from this BranchCache — different scope, different lifetime,
92- // different invalidation. Do not regrow the branch warmer's
93- // scope to cover it.
94- //
95- // The disk_sto / disk_acc counters on the [commitment][cache-fp] log
96- // line surface any fall-through where the trie compute reaches the
97- // underlying ctx.Account / ctx.Storage paths. On block-processing
98- // workloads they should remain zero; non-zero values signal a
99- // memoization gap or a missing walker-side prefetch.
100- //
101- // # Concurrency contract — caller invariants
102- //
103- // Internally, the LRU tail is thread-safe (hashicorp/golang-lru/v2) and
104- // the pinned root slot is an atomic.Pointer. So any combination of
105- // concurrent Get / Put / Invalidate is mechanically safe — no panics, no
106- // torn reads. But "mechanically safe" is NOT the same as "logically
107- // consistent across writers." The cache is designed to be used under one
108- // caller invariant:
109- //
110- // - Single writer per prefix at any moment. The cache does not coordinate
111- // concurrent writes to the same key — last-Put-wins semantics, with no
112- // guarantee that the winning value is the one the application wanted.
113- //
114- // # Concurrency contract — how the existing concurrent trie satisfies it
115- //
116- // The current ConcurrentPatriciaHashed (parallel commitment calculator)
117- // satisfies that invariant by construction:
118- //
119- // - Mounts partition the prefix space by FIRST NIBBLE. Mount N's
120- // encoder only writes branches whose key starts with [0x0N ...].
121- // Different mounts therefore never write to the same prefix.
122- // (See hex_concurrent_patricia_hashed.go: NewConcurrentPatriciaHashed
123- // creates 16 mounts via SpawnSubTrie; each mount has its own HPH,
124- // own BranchEncoder, own PatriciaContext / roTx.)
125- //
126- // - Root branch (prefix [0x00]) is written by the single root fold
127- // that runs SEQUENTIALLY after errgroup.Wait() in ParallelHashSort.
128- // One writer for the pinned root slot.
129- //
130- // - Mount→root grid roll-up is mutex-protected via
131- // ConcurrentPatriciaHashed.rootMu — but that updates IN-MEMORY grid
132- // cells, not the cache. The cache only sees the eventual root
133- // branch when the post-Wait root fold encodes it.
134- //
135- // # Concurrency contract — what future parallel fold work must preserve
136- //
137- // A future parallel tree-reduce fold would change the picture: the parent
138- // fold (incl. root) would no longer be a single post-Wait sequential pass.
139- // Multiple goroutines would compute parent branches in parallel as their
140- // children complete. This MUST not violate "single writer per prefix" —
141- // any future Stage F design needs an explicit per-prefix coordination layer
142- // (atomic counter on parent "children remaining"; only the last-decrementer
143- // writes the parent). That coordination belongs at the orchestrator layer;
144- // the cache itself does NOT add per-prefix locking because that would be
145- // wasted work for the current architecture.
146- //
147- // If you are implementing parallel fold (or any other architecture that
148- // breaks the "single writer per prefix" invariant), do NOT relax the
149- // invariant by adding internal locking to the cache. Add the
150- // coordination at the orchestrator layer where the partitioning logic
151- // lives. The cache stays simple; the orchestrator owns the discipline.
152- //
153- // Likewise if you change the prefix partitioning (e.g. by-second-nibble
154- // mounts, depth-based partitioning, anything other than first-nibble),
155- // re-validate that distinct workers continue to write disjoint prefix
156- // spaces. Re-read the partitioning code in
157- // hex_concurrent_patricia_hashed.go and confirm.
56+ // Concurrency: the LRU tail and the atomic-pointer root slot make any mix of
57+ // concurrent Get/Put/Invalidate mechanically safe, but the cache does not
58+ // coordinate writers — callers must ensure a single writer per prefix
59+ // (last-Put-wins otherwise); add any such coordination at the orchestrator, not
60+ // by locking the cache.
15861type BranchCache struct {
15962 // Root tier — single slot for the root branch (always hottest, always
16063 // present). Atomic-pointer access so no lock is needed for the hot
0 commit comments