diff --git a/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs b/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs index 7da9a763000..2688f2942f0 100644 --- a/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs @@ -441,6 +441,66 @@ public void Selfdestruct_clears_cache() provider.Get(nonAccessedStorageCell).ToArray().Should().BeEquivalentTo(StorageTree.ZeroBytes); } + [Test] + public void Set_empty_value_for_storage_cell_without_read_clears_data() + { + IWorldState worldState = new WorldState(TestTrieStoreFactory.Build(new MemDb(), LimboLogs.Instance), Substitute.For(), LogManager); + + using var disposable = worldState.BeginScope(IWorldState.PreGenesis); + worldState.CreateAccount(TestItem.AddressA, 1); + worldState.Commit(Prague.Instance); + worldState.CommitTree(0); + Hash256 emptyHash = worldState.StateRoot; + + worldState.Set(new StorageCell(TestItem.AddressA, 1), _values[11]); + worldState.Set(new StorageCell(TestItem.AddressA, 2), _values[12]); + worldState.Commit(Prague.Instance); + worldState.CommitTree(1); + + var fullHash = worldState.StateRoot; + fullHash.Should().NotBe(emptyHash); + + worldState.Set(new StorageCell(TestItem.AddressA, 1), [0]); + worldState.Set(new StorageCell(TestItem.AddressA, 2), [0]); + worldState.Commit(Prague.Instance); + worldState.CommitTree(2); + + var clearedHash = worldState.StateRoot; + + clearedHash.Should().Be(emptyHash); + } + + [Test] + public void Set_empty_value_for_storage_cell_with_read_clears_data() + { + IWorldState worldState = new WorldState(TestTrieStoreFactory.Build(new MemDb(), LimboLogs.Instance), Substitute.For(), LogManager); + + using var disposable = worldState.BeginScope(IWorldState.PreGenesis); + worldState.CreateAccount(TestItem.AddressA, 1); + worldState.Commit(Prague.Instance); + worldState.CommitTree(0); + Hash256 emptyHash = worldState.StateRoot; + + worldState.Set(new StorageCell(TestItem.AddressA, 1), _values[11]); + worldState.Set(new StorageCell(TestItem.AddressA, 2), _values[12]); + worldState.Commit(Prague.Instance); + worldState.CommitTree(1); + + var fullHash = worldState.StateRoot; + fullHash.Should().NotBe(emptyHash); + + worldState.Get(new StorageCell(TestItem.AddressA, 1)); + worldState.Get(new StorageCell(TestItem.AddressA, 2)); + worldState.Set(new StorageCell(TestItem.AddressA, 1), [0]); + worldState.Set(new StorageCell(TestItem.AddressA, 2), [0]); + worldState.Commit(Prague.Instance); + worldState.CommitTree(2); + + var clearedHash = worldState.StateRoot; + + clearedHash.Should().Be(emptyHash); + } + private class Context { public WorldState StateProvider { get; } diff --git a/src/Nethermind/Nethermind.State/PartialStorageProviderBase.cs b/src/Nethermind/Nethermind.State/PartialStorageProviderBase.cs index 69fd96b9d41..57c3253791e 100644 --- a/src/Nethermind/Nethermind.State/PartialStorageProviderBase.cs +++ b/src/Nethermind/Nethermind.State/PartialStorageProviderBase.cs @@ -163,10 +163,12 @@ public ChangeTrace(byte[]? after) { After = after ?? StorageTree.ZeroBytes; Before = StorageTree.ZeroBytes; + IsInitialValue = true; } public byte[] Before; public byte[] After; + public bool IsInitialValue; } /// diff --git a/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs b/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs index 0382fa11c8b..db28e3660fd 100644 --- a/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs +++ b/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs @@ -631,7 +631,8 @@ private byte[] LoadFromTreeStorage(StorageCell storageCell) foreach (var kvp in BlockChange) { byte[] after = kvp.Value.After; - if (!Bytes.AreEqual(kvp.Value.Before, after)) + if (!Bytes.AreEqual(kvp.Value.Before, after) + || kvp.Value.IsInitialValue) // IsInitialValue is so that it does not skip change if it does not know existing value. { BlockChange[kvp.Key] = new(after, after); StorageTree.Set(kvp.Key, after);