diff --git a/src/Nethermind/Nethermind.State.Test/PatriciaTreeBulkSetterTests.cs b/src/Nethermind/Nethermind.State.Test/PatriciaTreeBulkSetterTests.cs index 54e6827808c..e11940237cb 100644 --- a/src/Nethermind/Nethermind.State.Test/PatriciaTreeBulkSetterTests.cs +++ b/src/Nethermind/Nethermind.State.Test/PatriciaTreeBulkSetterTests.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using FluentAssertions; +using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Test; @@ -90,6 +91,12 @@ public static IEnumerable NewBranchesGen() (new Hash256("cccccccc00000000000000000000000000000000000000000000000000000000"), MakeRandomValue(rng)), (new Hash256("cccc000000000000000000000000000000000000000000000000000000000000"), MakeRandomValue(rng)), }).SetName("deep value"); + + yield return new TestCaseData(new List<(Hash256 key, byte[] value)>() + { + (new Hash256("3333333333333333333333333333333333333333333333333333333333333333"), MakeRandomValue(rng)), + (new Hash256("3333333332222222222222222222222222222222222222222222222222222222"), MakeRandomValue(rng)), + }).SetName("matching long extension"); } public static IEnumerable PreExistingDataGen() @@ -109,6 +116,13 @@ public static IEnumerable PreExistingDataGen() (new Hash256("3322222222222222222222222222222222222222222222222222222222222222"), MakeRandomValue(rng)), }).SetName("one extension"); + yield return new TestCaseData(new List<(Hash256 key, byte[] value)>() + { + (new Hash256("3333333332222222222222222222222222222222222222222222222222222222"), MakeRandomValue(rng)), + (new Hash256("3333333333333333333333333333333333333333333333333333333333333333"), MakeRandomValue(rng)), + (new Hash256("3333333344444444444444444444444444444444444444444444444444444444"), MakeRandomValue(rng)), + }).SetName("long extension with branch child"); + yield return new TestCaseData(GenRandomOfLength(1000)).SetName("random 1000"); } @@ -122,6 +136,10 @@ public static IEnumerable BulkSetTestGen() { yield return new TestCaseData(existingData.Arguments[0], testCaseData.Arguments[0]).SetName(existingData.TestName + " and " + testCaseData.TestName); } + + List<(Hash256 key, byte[] value)> originalSet = (List<(Hash256 key, byte[] value)>)existingData.Arguments[0]; + List<(Hash256 key, byte[] value)> removal = originalSet.Select((kv) => (kv.key, (byte[])null)).ToList(); + yield return new TestCaseData(existingData.Arguments[0], removal).SetName(existingData.TestName + " and remove self completely "); } yield return new TestCaseData( @@ -255,7 +273,7 @@ public void BulkSet(List<(Hash256 key, byte[] value)> existingItems, List<(Hash2 long newWriteCount = 0; { TestMemDb db = new TestMemDb(); - IScopedTrieStore trieStore = new RawScopedTrieStore(db); + IScopedTrieStore trieStore = new StrictRawScopedTrieStore(new RawScopedTrieStore(db)); PatriciaTree pTree = new PatriciaTree(trieStore, LimboLogs.Instance); pTree.RootHash = Keccak.EmptyTreeHash; @@ -306,7 +324,7 @@ public void BulkSetRootHashUpdated(List<(Hash256 key, byte[] value)> existingIte (Hash256 root, TimeSpan baselineTime, long baselineWriteCount, string originalDump) = CalculateBaseline(existingItems, items, recordDump); TestMemDb db = new TestMemDb(); - IScopedTrieStore trieStore = new RawScopedTrieStore(db); + IScopedTrieStore trieStore = new StrictRawScopedTrieStore(new RawScopedTrieStore(db)); PatriciaTree pTree = new PatriciaTree(trieStore, LimboLogs.Instance); pTree.RootHash = Keccak.EmptyTreeHash; @@ -338,7 +356,7 @@ public void BulkSetPreSorted(List<(Hash256 key, byte[] value)> existingItems, Li long preSortedWriteCount; { TestMemDb db = new TestMemDb(); - IScopedTrieStore trieStore = new RawScopedTrieStore(db); + IScopedTrieStore trieStore = new StrictRawScopedTrieStore(new RawScopedTrieStore(db)); PatriciaTree pTree = new PatriciaTree(trieStore, LimboLogs.Instance); pTree.RootHash = Keccak.EmptyTreeHash; @@ -396,7 +414,7 @@ public void BulkSetOneByOne(List<(Hash256 key, byte[] value)> existingItems, Lis { // Just the bulk set one stack TestMemDb db = new TestMemDb(); - IScopedTrieStore trieStore = new RawScopedTrieStore(db); + IScopedTrieStore trieStore = new StrictRawScopedTrieStore(new RawScopedTrieStore(db)); PatriciaTree pTree = new PatriciaTree(trieStore, LimboLogs.Instance); pTree.RootHash = Keccak.EmptyTreeHash; @@ -448,7 +466,7 @@ private static (Hash256, TimeSpan, long, string originalDump) CalculateBaseline( long baselineWriteCount = 0; { TestMemDb db = new TestMemDb(); - IScopedTrieStore trieStore = new RawScopedTrieStore(db); + IScopedTrieStore trieStore = new StrictRawScopedTrieStore(new RawScopedTrieStore(db)); PatriciaTree pTree = new PatriciaTree(trieStore, LimboLogs.Instance); pTree.RootHash = Keccak.EmptyTreeHash; @@ -484,7 +502,7 @@ private static (Hash256, TimeSpan, long, string originalDump) CalculateBaseline( [Test] public void BulkSet_ShouldThrowOnNonUniqueEntries() { - IScopedTrieStore trieStore = new RawScopedTrieStore(new TestMemDb()); + IScopedTrieStore trieStore = new StrictRawScopedTrieStore(new RawScopedTrieStore(new TestMemDb())); PatriciaTree pTree = new PatriciaTree(trieStore, LimboLogs.Instance); pTree.RootHash = Keccak.EmptyTreeHash; @@ -715,4 +733,30 @@ public void HexarySearch(int nibIndex, List paths, List baseTrieStore.LoadRlp(in path, hash, flags); + + public byte[] TryLoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => baseTrieStore.TryLoadRlp(in path, hash, flags); + + public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256 address) => baseTrieStore.GetStorageTrieNodeResolver(address); + + public INodeStorage.KeyScheme Scheme => baseTrieStore.Scheme; + + public ICommitter BeginCommit(TrieNode root, WriteFlags writeFlags = WriteFlags.None) => baseTrieStore.BeginCommit(root, writeFlags); + + public bool IsPersisted(in TreePath path, in ValueHash256 keccak) => baseTrieStore.IsPersisted(in path, in keccak); + } } diff --git a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs index 65e57990dd1..9abe356a42e 100644 --- a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs +++ b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs @@ -141,13 +141,19 @@ public void Commit(bool skipRoot = false, WriteFlags writeFlags = WriteFlags.Non _writeBeforeCommit = 0; - using ICommitter committer = TrieStore.BeginCommit(RootRef, writeFlags); - if (RootRef is not null && RootRef.IsDirty) + TrieNode? newRoot = RootRef; + using (ICommitter committer = TrieStore.BeginCommit(RootRef, writeFlags)) { - TreePath path = TreePath.Empty; - RootRef = Commit(committer, ref path, RootRef, skipSelf: skipRoot, maxLevelForConcurrentCommit: maxLevelForConcurrentCommit); + if (RootRef is not null && RootRef.IsDirty) + { + TreePath path = TreePath.Empty; + newRoot = Commit(committer, ref path, RootRef, skipSelf: skipRoot, maxLevelForConcurrentCommit: maxLevelForConcurrentCommit); + } } + // Need to be after committer dispose so that it can find it in trie store properly + RootRef = newRoot; + // Sometimes RootRef is set to null, so we still need to reset roothash to empty tree hash. SetRootHash(RootRef?.Keccak, true); } @@ -770,7 +776,9 @@ internal bool ShouldUpdateChild(TrieNode? parent, TrieNode? oldChild, TrieNode? byte[] extensionKey = [(byte)onlyChildIdx]; if (originalNode is not null && originalNode.IsExtension && Bytes.AreEqual(extensionKey, originalNode.Key)) { + path.AppendMut(onlyChildIdx); TrieNode? originalChild = originalNode.GetChildWithChildPath(TrieStore, ref path, 0); + path.TruncateOne(); if (!ShouldUpdateChild(originalNode, originalChild, onlyChildNode)) { return originalNode; @@ -790,8 +798,11 @@ internal bool ShouldUpdateChild(TrieNode? parent, TrieNode? oldChild, TrieNode? { if (Bytes.AreEqual(newKey, originalNode.Key)) { + int originalLength = path.Length; + path.AppendMut(newKey); TrieNode? originalChild = originalNode.GetChildWithChildPath(TrieStore, ref path, 0); TrieNode? newChild = onlyChildNode.GetChildWithChildPath(TrieStore, ref path, 0); + path.TruncateMut(originalLength); if (!ShouldUpdateChild(originalNode, originalChild, newChild)) { return originalNode;