Fix inaccurate snap sync and abstract state sync operation#10469
Fix inaccurate snap sync and abstract state sync operation#10469
Conversation
Extract non-flat snap sync improvements from flat/snap-sync branch: - Add ISnapTrieFactory/ISnapTree abstractions for pluggable trie implementations - Add PatriciaSnap* implementations for Patricia trie-based snap sync - Add ITreeSyncStore/PatriciaTreeSyncStore for state sync storage - Improve SnapProviderHelper with better range handling and boundary proofs - Add core utilities (ConcurrencyController, ReadWriteLockBox, RefCountingDisposable) - Add TrieLeafIterator for efficient trie traversal - Refactor tests with ISnapServerContext, RemoteDbContext, and IStateSyncTestOperation - Improve test data with proper hash partitioning and parameterized tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove ConcurrencyController additions, ReadWriteLockBox, RefCountingDisposable, IWorldStateManager IDisposable change, TrieStoreScopeProvider refactoring, E2ESyncTests improvements, and most Trie changes. Keep only minimal Trie additions needed by snap sync: GetInlineNodeRlp and ToUpperBoundPath. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR refactors snap/state sync to support pluggable trie/storage backends (e.g., future flat implementations) and introduces multiple fixes to prevent snap sync double-writes and improve range partition correctness (inclusive bounds, proof stitching, and storage range splitting behavior).
Changes:
- Add new abstractions for snap sync (
ISnapTrieFactory,ISnapTree) and state sync storage (ITreeSyncStore) with Patricia-backed implementations. - Fix snap sync range boundary handling to avoid double-processing/double-writes (inclusive limits, upper-bound commit filtering, proof stitching adjustments).
- Refactor and expand sync test infrastructure to use the new abstractions and updated range semantics.
Reviewed changes
Copilot reviewed 39 out of 39 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Nethermind/Nethermind.Trie/TrieNode.cs | Adds helper to extract inline child node RLP for proof handling. |
| src/Nethermind/Nethermind.Trie/Pruning/TreePath.cs | Adds subtree upper-bound path helper for commit limiting. |
| src/Nethermind/Nethermind.Synchronization/Synchronizer.cs | Registers new snap/state sync abstractions in DI. |
| src/Nethermind/Nethermind.Synchronization/SnapSync/UnknownNodeResolver.cs | Adds resolver for proof-only decoding without DB lookups. |
| src/Nethermind/Nethermind.Synchronization/SnapSync/SnapUpperBoundAdapter.cs | Wraps trie store commits to enforce an upper bound and skip boundary proof nodes. |
| src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProviderHelper.cs | Refactors range commit/stitching logic; applies inclusive bound handling. |
| src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs | Switches provider to factory-based trees and updated range semantics. |
| src/Nethermind/Nethermind.Synchronization/SnapSync/ProgressTracker.cs | Adjusts partition limits to be inclusive; updates storage split behavior & healing tracking. |
| src/Nethermind/Nethermind.Synchronization/SnapSync/PatriciaSnapTrieFactory.cs | Patricia implementation of ISnapTrieFactory. |
| src/Nethermind/Nethermind.Synchronization/SnapSync/PatriciaSnapStorageTree.cs | Patricia implementation of ISnapTree for storage tries. |
| src/Nethermind/Nethermind.Synchronization/SnapSync/PatriciaSnapStateTree.cs | Patricia implementation of ISnapTree for the state trie. |
| src/Nethermind/Nethermind.Synchronization/SnapSync/ISnapTrieFactory.cs | New interface for creating snap trees. |
| src/Nethermind/Nethermind.Synchronization/SnapSync/ISnapTree.cs | New interface for snap tree operations used during proof fill/commit. |
| src/Nethermind/Nethermind.Synchronization/SnapSync/AddRangeResult.cs | Renames empty slots result to EmptyRange. |
| src/Nethermind/Nethermind.Synchronization/FastSync/TreeSync.cs | Abstracts storage ops via ITreeSyncStore; changes finalization and verification flow. |
| src/Nethermind/Nethermind.Synchronization/FastSync/PatriciaTreeSyncStore.cs | Patricia implementation of the new ITreeSyncStore. |
| src/Nethermind/Nethermind.Synchronization/FastSync/ITreeSyncStore.cs | New abstraction for state sync storage + verification context. |
| src/Nethermind/Nethermind.Synchronization.Test/TestSynchronizerModule.cs | Updates test module wiring for new snap abstractions. |
| src/Nethermind/Nethermind.Synchronization.Test/SnapSync/TestSnapTrieFactory.cs | Test factory implementation for ISnapTrieFactory. |
| src/Nethermind/Nethermind.Synchronization.Test/SnapSync/SnapServerTest.cs | Refactors snap server tests to use an abstract server context + batching. |
| src/Nethermind/Nethermind.Synchronization.Test/SnapSync/SnapProviderTests.cs | Updates tests for new APIs and adds persistence coverage for storage ranges. |
| src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromStorageRangesTests.cs | Refactors to container-based setup and new result semantics. |
| src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromAccountRangesTests.cs | Refactors to container-based setup and helper-based assertions. |
| src/Nethermind/Nethermind.Synchronization.Test/SnapSync/ProgressTrackerTests.cs | Updates expected partition start/limit (inclusive) and slot splitting semantics. |
| src/Nethermind/Nethermind.Synchronization.Test/SnapSync/ISnapTestHelper.cs | Adds helper abstraction for trie-node DB assertions in tests. |
| src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTestsBase.cs | Refactors remote/local DB setup into RemoteDbContext and test operations abstraction. |
| src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTests.cs | Updates state sync feed tests to use new contexts and abstractions. |
| src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs | Updates healing tests to use new snap factory + test operations abstraction. |
| src/Nethermind/Nethermind.Synchronization.Test/FastSync/LocalDbContext.cs | Implements local DB test operations used by state sync feed tests. |
| src/Nethermind/Nethermind.Synchronization.Test/FastSync/IStateSyncTestOperation.cs | Introduces abstraction for local test DB operations and comparisons. |
| src/Nethermind/Nethermind.State/StateTree.cs | Adjusts bulk setter to accept nullable accounts. |
| src/Nethermind/Nethermind.Core/Crypto/Hash256.cs | Adds increment/decrement helpers for inclusive/exclusive path progression. |
| src/Nethermind/Nethermind.Core/Collections/CollectionExtensions.cs | Adds dictionary add-or-update range helper. |
| src/Nethermind/Nethermind.Core.Test/TestMemDb.cs | Extends TestMemDb with sorted-view capabilities for tests. |
| src/Nethermind/Nethermind.Core.Test/TestMemColumnDb.cs | Tightens generic constraints and adds snapshot support in tests. |
| src/Nethermind/Nethermind.Core.Test/KeccakTests.cs | Adds tests for increment/decrement path helpers. |
| src/Nethermind/Nethermind.Core.Test/Builders/TestItem.Tree.cs | Updates storage slot test data encoding. |
| src/Nethermind/Nethermind.Blockchain/Synchronization/SyncConfig.cs | Enables storage range splitting by default. |
| src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncConfig.cs | Updates default config metadata to match new default. |
Comments suppressed due to low confidence (1)
src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromStorageRangesTests.cs:200
- This assignment to rootFinished is useless, since its value is never read.
(AddRangeResult result, bool moreChildrenToRight, Hash256 _, bool rootFinished) = SnapProviderHelper.AddStorageRange(
factory,
pathWithAccount,
[
new PathWithStorageSlot(new ValueHash256("0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563"), Bytes.FromHexString("94654f75e491acf8c380d2a6906e67e2e56813665e")),
],
Keccak.Zero,
null,
proofs: [
Bytes.FromHexString("f838a120290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5639594654f75e491acf8c380d2a6906e67e2e56813665e"),
Bytes.FromHexString("f838a120290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5639594654f75e491acf8c380d2a6906e67e2e56813665e"),
]);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProviderHelper.cs
Outdated
Show resolved
Hide resolved
src/Nethermind/Nethermind.Synchronization/SnapSync/SnapUpperBoundAdapter.cs
Show resolved
Hide resolved
src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProviderHelper.cs
Show resolved
Hide resolved
src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProviderHelper.cs
Outdated
Show resolved
Hide resolved
src/Nethermind/Nethermind.Synchronization.Test/SnapSync/RecreateStateFromStorageRangesTests.cs
Show resolved
Hide resolved
src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs
Outdated
Show resolved
Hide resolved
src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…ix/inaccurate-snap-sync
| public ValueHash256 IncrementPath() | ||
| { | ||
| ValueHash256 result = this; | ||
| Span<byte> bytes = result.BytesAsSpan; | ||
|
|
||
| for (int i = 31; i >= 0; i--) | ||
| { | ||
| if (bytes[i] < 0xFF) | ||
| { | ||
| bytes[i]++; | ||
| return result; | ||
| } | ||
| bytes[i] = 0x00; | ||
| } | ||
|
|
||
| // Overflow - return max (shouldn't happen in practice) | ||
| result = ValueKeccak.Zero; | ||
| result.BytesAsSpan.Fill(0xFF); | ||
| return result; | ||
| } | ||
|
|
||
| public ValueHash256 DecrementPath() | ||
| { | ||
| ValueHash256 result = this; | ||
| Span<byte> bytes = result.BytesAsSpan; | ||
|
|
||
| for (int i = 31; i >= 0; i--) | ||
| { | ||
| if (bytes[i] > 0) | ||
| { | ||
| bytes[i]--; | ||
| return result; | ||
| } | ||
| bytes[i] = 0xFF; | ||
| } | ||
|
|
||
| // Underflow - return zero (shouldn't happen in practice) | ||
| return ValueKeccak.Zero; | ||
| } |
There was a problem hiding this comment.
Kind of belong better as extension methods
src/Nethermind/Nethermind.Synchronization.Test/FastSync/LocalDbContext.cs
Outdated
Show resolved
Hide resolved
src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs
Outdated
Show resolved
Hide resolved
| if (result == AddRangeResult.MissingRootHashInProofs) | ||
| { | ||
| _logger.Trace( | ||
| $"SNAP - AddStorageRange failed, missing root hash {actualRootHash} in the proofs, startingHash:{request.StartingHash}"); | ||
| } | ||
| else if (accountIndex == 0 && request.Accounts.Count == 1) | ||
| else if (result == AddRangeResult.DifferentRootHash) | ||
| { | ||
| _progressTracker.OnCompletedLargeStorage(pathWithAccount); | ||
| _logger.Trace( | ||
| $"SNAP - AddStorageRange failed, expected storage root hash:{pathWithAccount.Account.StorageRoot} but was {actualRootHash}, startingHash:{request.StartingHash}"); | ||
| } | ||
| else if (result == AddRangeResult.InvalidOrder) | ||
| { | ||
| if (_logger.IsTrace) | ||
| _logger.Trace( | ||
| $"SNAP - AddStorageRange failed, slots are not in sorted order, startingHash:{request.StartingHash}"); | ||
| } | ||
| else if (result == AddRangeResult.OutOfBounds) | ||
| { | ||
| if (_logger.IsTrace) | ||
| _logger.Trace( | ||
| $"SNAP - AddStorageRange failed, slots are out of bounds, startingHash:{request.StartingHash}"); | ||
| } | ||
| else if (result == AddRangeResult.EmptyRange) | ||
| { | ||
| if (_logger.IsTrace) | ||
| _logger.Trace( | ||
| $"SNAP - AddStorageRange failed, slots list is empty, startingHash:{request.StartingHash}"); |
There was a problem hiding this comment.
make it a switch something like (pseudocode)
| if (result == AddRangeResult.MissingRootHashInProofs) | |
| { | |
| _logger.Trace( | |
| $"SNAP - AddStorageRange failed, missing root hash {actualRootHash} in the proofs, startingHash:{request.StartingHash}"); | |
| } | |
| else if (accountIndex == 0 && request.Accounts.Count == 1) | |
| else if (result == AddRangeResult.DifferentRootHash) | |
| { | |
| _progressTracker.OnCompletedLargeStorage(pathWithAccount); | |
| _logger.Trace( | |
| $"SNAP - AddStorageRange failed, expected storage root hash:{pathWithAccount.Account.StorageRoot} but was {actualRootHash}, startingHash:{request.StartingHash}"); | |
| } | |
| else if (result == AddRangeResult.InvalidOrder) | |
| { | |
| if (_logger.IsTrace) | |
| _logger.Trace( | |
| $"SNAP - AddStorageRange failed, slots are not in sorted order, startingHash:{request.StartingHash}"); | |
| } | |
| else if (result == AddRangeResult.OutOfBounds) | |
| { | |
| if (_logger.IsTrace) | |
| _logger.Trace( | |
| $"SNAP - AddStorageRange failed, slots are out of bounds, startingHash:{request.StartingHash}"); | |
| } | |
| else if (result == AddRangeResult.EmptyRange) | |
| { | |
| if (_logger.IsTrace) | |
| _logger.Trace( | |
| $"SNAP - AddStorageRange failed, slots list is empty, startingHash:{request.StartingHash}"); | |
| if (_logger.IsTrace) _logger.Trace(result switch | |
| { | |
| AddRangeResult.MissingRootHashInProofs => $"SNAP - AddStorageRange failed, missing root hash {actualRootHash} in the proofs, startingHash:{request.StartingHash}, | |
| ... | |
| } |
|
|
||
| public static (AddRangeResult result, bool moreChildrenToRight, List<PathWithAccount> storageRoots, List<ValueHash256> codeHashes) AddAccountRange( | ||
| StateTree tree, | ||
| public static (AddRangeResult result, bool moreChildrenToRight, List<PathWithAccount> storageRoots, List<ValueHash256> codeHashes, Hash256 actualRootHash) AddAccountRange( |
There was a problem hiding this comment.
those tuples are getting ridiculous in this type (in multiple methods)
create custom types with better naming and some conversions and/or composition
| List<PathWithAccount> accountsWithStorage = new(); | ||
| List<ValueHash256> codeHashes = new(); |
There was a problem hiding this comment.
could they be ArrayPoolList or ArrayPoolListRef if the containing type would be ref struct?
|
|
||
| public byte[]? TryLoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => baseTrieStore.TryLoadRlp(in path, hash, flags); | ||
|
|
||
| public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256? address) => throw new NotSupportedException("Get storage trie node resolver not supported"); |
There was a problem hiding this comment.
could this be avoided? Multiple interfaces perhaps?
There was a problem hiding this comment.
Yes, I'm thinking so to. But that would cut through other critical class. So definitely not here.
| else | ||
| { | ||
| int length = rlpStream.PeekNextRlpLength(); | ||
| return rlpStream.Read(length).ToArray(); |
There was a problem hiding this comment.
you could read from original _rlp by using Position and length and keep the result as Span<byte>. Doesn't make sense if you still need to allocate an array later.
…bContext.cs Co-authored-by: Lukasz Rozmej <lukasz.rozmej@gmail.com>
…r.cs Co-authored-by: Lukasz Rozmej <lukasz.rozmej@gmail.com>
|
@copilot address comment |
…tor logging (#10497) * Initial plan * Move IncrementPath/DecrementPath to extension methods Co-authored-by: asdacap <1841324+asdacap@users.noreply.github.com> * Convert logging if-else chains to switch expressions Co-authored-by: asdacap <1841324+asdacap@users.noreply.github.com> * Add missing using directive for Hash256Extensions Co-authored-by: asdacap <1841324+asdacap@users.noreply.github.com> * Improve switch expressions to avoid logging null Co-authored-by: asdacap <1841324+asdacap@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: asdacap <1841324+asdacap@users.noreply.github.com>
…ix/inaccurate-snap-sync
Summary
ISnapTrieFactory/ISnapTreeabstractions enabling pluggable trie implementations for snap syncISnapServerContext,RemoteDbContext,IStateSyncTestOperationabstractionsTest plan
Nethermind.State.Flatin changed files🤖 Generated with Claude Code