Commit e301cc4
authored
feat(StateDiffsWriter): replace StateComposition plugin (#11788)
* feat(StateDiffsWriter): replace StateComposition plugin
Replaces the in-process Nethermind.StateComposition plugin with a leaner
Nethermind.StateDiffsWriter plugin that writes per-block trie diffs to a
dedicated BlockDiffs column family. The full state-composition tracker
moves out of the engine into an external Go sidecar that consumes the CF
via RocksDB secondary mode.
Why
- StateComposition kept the codehash/slot-count tracker in process. At
5x mainnet state it OOM-ed mid-scan and pinned the engine's heap with
hundreds of millions of dict entries.
- The bootstrap-scan path was the hot spot. Moving it out of the engine
lets the engine run lean while a separate sidecar does the scanning
with whatever budget it needs (and can be restarted independently).
- StateDiffsWriter only emits a deterministic, append-only stream of
trie diffs per block. No in-engine tracker state, no bootstrap scan.
Scope
- Add Nethermind.StateDiff.Core (shared trie-diff walker + records)
- Add Nethermind.StateDiffsWriter (plugin: per-block diff -> RocksDB CF)
- Add Nethermind.StateDiffsWriter.Test
- Remove Nethermind.StateComposition / Nethermind.StateComposition.Test
- Swap the registration in Runner/NethermindPlugins.cs
- Update Runner csproj project ref + plugin glob lists
- Update Nethermind.slnx folder layout
The matching sidecar consumer lives in
NethermindEth/eth-perf-research#77.
* chore(CODEOWNERS): track plugin rename
Mirror the directory swap in this PR. The CODEOWNERS entries pointed at the
removed plugin tree; repoint them at the new one.
* feat(StateDiffsWriter): per-block memtable flush for sidecar visibility
The bloating sidecar opens the BlockDiffs DB in RocksDB secondary mode
and calls TryCatchUpWithPrimary to pick up new writes. Secondary-mode
iterators read SSTs and the manifest — they cannot materialise the
primary's in-memory memtable. Without an explicit flush after each
WriteBlockDiff, new records sit in the memtable indefinitely and the
sidecar's tracker.lastBlock stalls, causing the orchestrator's strict
waitSensorForBlock to time out at 2 s per poll.
Flush the Default column family inside the per-block WriteLock so the
sequence write→flush is atomic w.r.t. the pruner. RocksDB flush of a
sub-KB memtable is one small SST write — cheap enough at our cadence
(~3 blocks/s).
A WAL-only sync (Flush(onlyWal: true)) was tried and reverted on
2026-05-27: secondary iterators still couldn't read memtable contents,
so the 3 s sensor-lag ceiling persisted. Full memtable flush remains
the minimum viable visibility primitive.
* chore(Runner): regenerate packages.lock.json for StateDiffsWriter projects
* style(StateDiffsWriter): remove unused usings (IDE0005)
* docs(StateDiffsWriter): drop useless ISortedKeyValueStore using-comment
The 3-line note explaining why there is no separate using for
ISortedKeyValueStore (it lives in Nethermind.Core, already imported) is pure
trivia between the usings and the namespace. Remove it.
* docs(StateDiffsWriter): drop stale reference to deleted StateComposition.Service
This PR deletes Nethermind.StateComposition, so 'Matches the legacy
StateComposition.Service approach' points at code that no longer exists and
will mislead the next reader. Keep the scope-per-side explanation, drop the
dangling reference.
* chore(StateDiffsWriter): port to master RlpReader/RlpWriter API and ulong block numbers
Rebasing onto master surfaced two breaking API changes that the plugin
must follow to compile:
- RLP serialization moved from RlpStream/Rlp.ValueDecoderContext to the
generic RlpReader/RlpWriter (IRlpWriteBackend) surface. Port the
BlockDiffRecord encoder/decoder, the store's write/read paths, and the
back-compat decoder tests. Wire bytes are unchanged (the byte-identical
and legacy round-trip tests still pass), so the Go sidecar is unaffected.
- Block.Number is now ulong; cast to long at the NM-API boundary where it
feeds the long-typed BlockDiffRecord/metrics/pruner domain (block numbers
are < 2^63, so the narrowing is safe).
* fix(StateDiffsWriter): address PR review feedback
Resolves the seven review findings on #11788:
- High — reorg/SlotCounts: detect a new head that does not build on the
last-written block (DetectReorg) and surface it via a warning plus the new
StateDiffsWriterReorgsTotal counter. Document that OldCount is advisory across
a reorg; a per-height rollback is intentionally not implemented (the sidecar
re-derives running totals from the diff chain, the bloatnet is reorg-free,
PoS mainnet reorgs are single-slot).
- Medium — code-missing: extract ResolveNewCodeSize; when an account reports a
code hash but the code DB has no bytes, record 0 and emit a warning + the
code_missing labeled counter instead of undercounting silently.
- Medium — negative KeepLastNBlocks: guard PruneOnce so a negative window
disables pruning (and warns) instead of wrapping into a delete-everything
cutoff; document the >= 0 contract on the config item.
- Low — pruner shutdown: implement IAsyncDisposable (await the loop) and make
Dispose a non-blocking best-effort cancel, dropping the up-to-5s Task.Wait.
- Low — Dispose idempotency: guard DiffsWriterService.Dispose with a _disposed
flag so double-dispose unsubscribes once.
- Low — GetSlotCount allocation: read into a stack buffer via the span Get
overload, removing the per-lookup byte[] in the block-write path.
- Low — walker isolation: allocate a fresh TrieDiffWalker per ComputeRecord
instead of sharing a mutable instance field.
Adds tests for reorg detection, code-size resolution (present/missing/none),
negative-keep pruning, idempotent dispose, and async dispose.
* style(StateDiffsWriter): minimise comments to why-only
Per repo rule "comments explain why, not what": delete comments that restate
the code, drop project-internal jargon (an external consumer's version label,
project phases) that means nothing upstream, and hold every remaining comment
to 1-2 lines. Kept: SPDX headers, [Description]/[ConfigItem] metadata, the
BlockDiffRecord RLP wire-format layout (a cross-process contract), one-line XML
summaries on public APIs, and genuine non-obvious why-notes.
* docs(StateDiffsWriter): note why FlushDefault is a full flush, not WAL-only
A secondary-mode reader's iterator surfaces only flushed SSTs, not WAL/memtable
rows, so Flush(onlyWal: true) would leave the newest block invisible to the
consumer until it reached an L0 SST. The full flush is deliberate.1 parent 8b44b1a commit e301cc4
87 files changed
Lines changed: 2539 additions & 7695 deletions
File tree
- .github
- src/Nethermind
- Nethermind.Runner
- Nethermind.StateComposition.Test
- Diff
- Helpers
- Rpc
- Service
- Snapshots
- Visitors
- Nethermind.StateComposition
- Data
- Diff
- Rpc
- Service
- Snapshots
- Visitors
- Nethermind.StateDiff.Core
- Data
- Diff
- Nethermind.StateDiffsWriter.Test
- Data
- Service
- Storage
- Nethermind.StateDiffsWriter
- Data
- Service
- Storage
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
42 | | - | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
43 | 44 | | |
44 | 45 | | |
45 | 46 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
83 | | - | |
| 83 | + | |
| 84 | + | |
84 | 85 | | |
85 | 86 | | |
86 | 87 | | |
| |||
113 | 114 | | |
114 | 115 | | |
115 | 116 | | |
116 | | - | |
117 | | - | |
| 117 | + | |
| 118 | + | |
118 | 119 | | |
119 | 120 | | |
120 | 121 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
31 | | - | |
| 31 | + | |
32 | 32 | | |
33 | 33 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1140 | 1140 | | |
1141 | 1141 | | |
1142 | 1142 | | |
1143 | | - | |
| 1143 | + | |
| 1144 | + | |
| 1145 | + | |
| 1146 | + | |
| 1147 | + | |
| 1148 | + | |
| 1149 | + | |
| 1150 | + | |
| 1151 | + | |
1144 | 1152 | | |
1145 | 1153 | | |
1146 | 1154 | | |
1147 | 1155 | | |
| 1156 | + | |
1148 | 1157 | | |
| 1158 | + | |
| 1159 | + | |
1149 | 1160 | | |
1150 | 1161 | | |
1151 | 1162 | | |
| |||
Lines changed: 0 additions & 155 deletions
This file was deleted.
0 commit comments