diff --git a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs index ad8ff933da1..b24bf603112 100644 --- a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs @@ -250,6 +250,11 @@ public interface IReleaseSpec /// bool IsEip3607Enabled { get; } + /// + /// Transient storage + /// + bool IsEip1153Enabled { get; } + /// /// Should transactions be validated against chainId. /// @@ -327,5 +332,7 @@ public interface IReleaseSpec public Address? Eip1559FeeCollector => null; public UInt256? Eip1559BaseFeeMinValue => null; + + public bool TransientStorageEnabled => IsEip1153Enabled; } } diff --git a/src/Nethermind/Nethermind.Evm.Test/CoinbaseTests.cs b/src/Nethermind/Nethermind.Evm.Test/CoinbaseTests.cs index c8eb7b2321d..e937b0b5d43 100644 --- a/src/Nethermind/Nethermind.Evm.Test/CoinbaseTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/CoinbaseTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -28,10 +28,10 @@ public class CoinbaseTests : VirtualMachineTestsBase private bool _setAuthor; - protected override Block BuildBlock(long blockNumber, SenderRecipientAndMiner senderRecipientAndMiner, Transaction transaction) + protected override Block BuildBlock(long blockNumber, SenderRecipientAndMiner senderRecipientAndMiner, Transaction transaction, long blockGasLimit = DefaultBlockGasLimit) { senderRecipientAndMiner ??= new SenderRecipientAndMiner(); - Block block = base.BuildBlock(blockNumber, senderRecipientAndMiner, transaction); + Block block = base.BuildBlock(blockNumber, senderRecipientAndMiner, transaction, blockGasLimit); if(_setAuthor) block.Header.Author = TestItem.AddressC; block.Header.Beneficiary = senderRecipientAndMiner.Recipient; return block; diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip1153Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip1153Tests.cs new file mode 100644 index 00000000000..988fc1af92a --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Test/Eip1153Tests.cs @@ -0,0 +1,850 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . + +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Specs; +using Nethermind.Core.Test.Builders; +using NUnit.Framework; +using System.Diagnostics; + +namespace Nethermind.Evm.Test +{ + /// + /// Tests functionality of Transient Storage + /// + internal class Eip1153Tests : VirtualMachineTestsBase + { + protected override long BlockNumber => MainnetSpecProvider.ShanghaiBlockNumber; + protected override ISpecProvider SpecProvider => MainnetSpecProvider.Instance; + + /// + /// Transient storage should be activated after Shanghai hardfork + /// + [Test] + public void after_shanghai_can_call_tstore_tload() + { + byte[] code = Prepare.EvmCode + .StoreDataInTransientStorage(1, 8) + .LoadDataFromTransientStorage(1) + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + Assert.AreEqual(StatusCode.Success, result.StatusCode); + } + + /// + /// Transient storage should not be activated until after Shanghai hardfork + /// + [Test] + public void before_shanghai_can_not_call_tstore_tload() + { + byte[] code = Prepare.EvmCode + .StoreDataInTransientStorage(1, 8) + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber - 1, 100000, code); + Assert.AreEqual(StatusCode.Failure, result.StatusCode); + + code = Prepare.EvmCode + .LoadDataFromTransientStorage(1) + .Done; + + result = Execute(MainnetSpecProvider.ShanghaiBlockNumber - 1, 100000, code); + Assert.AreEqual(StatusCode.Failure, result.StatusCode); + } + + /// + /// Uninitialized transient storage is zero + /// + [Test] + public void tload_uninitialized_returns_zero() + { + byte[] code = Prepare.EvmCode + .LoadDataFromTransientStorage(1) + .DataOnStackToMemory(0) + .Return(32, 0) + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + Assert.AreEqual(StatusCode.Success, result.StatusCode); + + // Should be 0 since it's not yet set + Assert.AreEqual(0, (int)result.ReturnValue.ToUInt256()); + } + + /// + /// Simple performance test + /// + [Explicit("Depends on hardware")] + [Test] + public void transient_storage_performance_test() + { + Stopwatch stopwatch = new Stopwatch(); + long blockGasLimit = 30000000; + long numOfOps = (long)(blockGasLimit * .95) / (GasCostOf.TLoad + GasCostOf.TStore + GasCostOf.VeryLow * 4); + Prepare prepare = Prepare.EvmCode; + for (long i = 0; i < numOfOps; i++) + { + prepare.StoreDataInTransientStorage(1, 8); + prepare.LoadDataFromTransientStorage(1); + prepare.Op(Instruction.POP); + } + + byte[] code = prepare.Done; + + stopwatch.Start(); + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, blockGasLimit, code, blockGasLimit); + Assert.AreEqual(StatusCode.Success, result.StatusCode); + stopwatch.Stop(); + Assert.IsTrue(stopwatch.ElapsedMilliseconds < 5000); + } + + /// + /// Simple functionality test + /// + [Test] + public void tload_after_tstore() + { + byte[] code = Prepare.EvmCode + .StoreDataInTransientStorage(1, 8) + .LoadDataFromTransientStorage(1) + .DataOnStackToMemory(0) + .Return(32, 0) + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + Assert.AreEqual(StatusCode.Success, result.StatusCode); + + Assert.AreEqual(8, (int)result.ReturnValue.ToUInt256()); + } + + /// + /// Testing transient data store/load from different locations + /// + /// Location + [TestCase(2)] + [TestCase(3)] + [TestCase(4)] + [TestCase(5)] + [TestCase(6)] + public void tload_after_tstore_from_different_locations(int loadLocation) + { + byte[] code = Prepare.EvmCode + .StoreDataInTransientStorage(1, 8) + .LoadDataFromTransientStorage(loadLocation) + .DataOnStackToMemory(0) + .Return(32, 0) + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + Assert.AreEqual(StatusCode.Success, result.StatusCode); + + Assert.AreEqual(0, (int)result.ReturnValue.ToUInt256()); + } + + /// + /// Contracts have separate transient storage + /// + [Test] + public void tload_from_different_contract() + { + // TLOAD and RETURN the resulting value + byte[] contractCode = Prepare.EvmCode + .PushData(1) + .Op(Instruction.TLOAD) + .DataOnStackToMemory(0) + .PushData(32) + .PushData(0) + .Op(Instruction.RETURN) + .Done; + + TestState.CreateAccount(TestItem.AddressD, 1.Ether()); + Keccak contractCodeHash = TestState.UpdateCode(contractCode); + TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec); + + // Store 8 at index 1 and call contract from above + // Return the result received from the contract + byte[] code = Prepare.EvmCode + .StoreDataInTransientStorage(1, 8) + .Call(TestItem.AddressD, 50000) + .ReturnInnerCallResult() + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + + // If transient state was not isolated, the return value would be 8 + Assert.AreEqual(0, (int)result.ReturnValue.ToUInt256()); + } + + /// + /// Reentrant calls access the same transient storage + /// + [Test] + public void tload_from_reentrant_call() + { + // If caller is self, TLOAD and return value (break recursion) + // Else, TSTORE and call self, return the response + byte[] contractCode = Prepare.EvmCode + // Check if caller is self + .Op(Instruction.CALLER) + .PushData(TestItem.AddressD) + .Op(Instruction.EQ) + .PushData(78) + .Op(Instruction.JUMPI) + + // Non-reentrant, call self after TSTORE + .StoreDataInTransientStorage(1, 8) + .Call(TestItem.AddressD, 50000) + // Return the response + .ReturnInnerCallResult() + + // Reentrant, TLOAD and return value + .Op(Instruction.JUMPDEST) + .LoadDataFromTransientStorage(1) + .DataOnStackToMemory(0) + .PushData(32) + .PushData(0) + .Op(Instruction.RETURN) + .Done; + + TestState.CreateAccount(TestItem.AddressD, 1.Ether()); + Keccak contractCodeHash = TestState.UpdateCode(contractCode); + TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec); + + // Return the result received from the contract + byte[] code = Prepare.EvmCode + .Call(TestItem.AddressD, 50000) + .ReturnInnerCallResult() + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + + Assert.AreEqual(8, (int)result.ReturnValue.ToUInt256()); + } + + /// + /// Reentrant calls can manipulate the same transient storage + /// + [Test] + public void tstore_from_reentrant_call() + { + // If caller is self, TLOAD and return value (break recursion) + // Else, TSTORE and call self, return the response + byte[] contractCode = Prepare.EvmCode + // Check if caller is self + .Op(Instruction.CALLER) + .PushData(TestItem.AddressD) + .Op(Instruction.EQ) + .PushData(78) + .Op(Instruction.JUMPI) + + // Non-reentrant, call self after TSTORE + .StoreDataInTransientStorage(1, 8) + .Call(TestItem.AddressD, 50000) + // Return the response + .ReturnInnerCallResult() + + // Reentrant, TLOAD and return value + .Op(Instruction.JUMPDEST) // PC = 78 + .StoreDataInTransientStorage(1, 9) + .LoadDataFromTransientStorage(1) + .DataOnStackToMemory(0) + .PushData(32) + .PushData(0) + .Op(Instruction.RETURN) + .Done; + + TestState.CreateAccount(TestItem.AddressD, 1.Ether()); + Keccak contractCodeHash = TestState.UpdateCode(contractCode); + TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec); + + // Return the result received from the contract + byte[] code = Prepare.EvmCode + .Call(TestItem.AddressD, 50000) + .ReturnInnerCallResult() + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + + Assert.AreEqual(9, (int)result.ReturnValue.ToUInt256()); + } + + /// + /// Successfully returned calls do not revert transient storage writes + /// + [Test] + public void tstore_from_reentrant_call_read_by_caller() + { + // If caller is self, TLOAD and return value (break recursion) + // Else, TSTORE and call self, return the response + byte[] contractCode = Prepare.EvmCode + // Check if caller is self + .Op(Instruction.CALLER) + .PushData(TestItem.AddressD) + .Op(Instruction.EQ) + .PushData(77) + .Op(Instruction.JUMPI) + + // Non-reentrant, call self after TSTORE + .StoreDataInTransientStorage(1, 8) + .Call(TestItem.AddressD, 50000) + // TLOAD and return value (should be 9) + .LoadDataFromTransientStorage(1) + .DataOnStackToMemory(0) + .PushData(32) + .PushData(0) + .Op(Instruction.RETURN) + + // Reentrant, TSTORE 9 + .Op(Instruction.JUMPDEST) // PC = 77 + .StoreDataInTransientStorage(1, 9) + .Done; + + TestState.CreateAccount(TestItem.AddressD, 1.Ether()); + Keccak contractCodeHash = TestState.UpdateCode(contractCode); + TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec); + + // Return the result received from the contract + byte[] code = Prepare.EvmCode + .Call(TestItem.AddressD, 50000) + .ReturnInnerCallResult() + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + + Assert.AreEqual(9, (int)result.ReturnValue.ToUInt256()); + } + + /// + /// Revert undoes the transient storage write from the failed call + /// + [Test] + public void revert_resets_transient_state() + { + // If caller is self, TLOAD and return value (break recursion) + // Else, TSTORE and call self, return the response + byte[] contractCode = Prepare.EvmCode + // Check if caller is self + .Op(Instruction.CALLER) + .PushData(TestItem.AddressD) + .Op(Instruction.EQ) + .PushData(77) + .Op(Instruction.JUMPI) + + // Non-reentrant, call self after TSTORE + .StoreDataInTransientStorage(1, 8) + .Call(TestItem.AddressD, 50000) + // TLOAD and return value + .LoadDataFromTransientStorage(1) + .DataOnStackToMemory(0) + .PushData(32) + .PushData(0) + .Op(Instruction.RETURN) + + // Reentrant, TSTORE 9 but REVERT + .Op(Instruction.JUMPDEST) // PC = 77 + .StoreDataInTransientStorage(1, 9) + .Op(Instruction.REVERT) + .Done; + + TestState.CreateAccount(TestItem.AddressD, 1.Ether()); + Keccak contractCodeHash = TestState.UpdateCode(contractCode); + TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec); + + // Return the result received from the contract + byte[] code = Prepare.EvmCode + .Call(TestItem.AddressD, 50000) + .ReturnInnerCallResult() + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + + // Should be original TSTORE value + Assert.AreEqual(8, (int)result.ReturnValue.ToUInt256()); + } + + /// + /// Revert undoes all the transient storage writes to the same key from the failed call + /// + [Test] + public void revert_resets_all_transient_state() + { + // If caller is self, TLOAD and return value (break recursion) + // Else, TSTORE and call self, return the response + byte[] contractCode = Prepare.EvmCode + // Check if caller is self + .Op(Instruction.CALLER) + .PushData(TestItem.AddressD) + .Op(Instruction.EQ) + .PushData(77) + .Op(Instruction.JUMPI) + + // Non-reentrant, call self after TSTORE + .StoreDataInTransientStorage(1, 8) + .Call(TestItem.AddressD, 50000) + // TLOAD and return value + .LoadDataFromTransientStorage(1) + .DataOnStackToMemory(0) + .PushData(32) + .PushData(0) + .Op(Instruction.RETURN) + + // Reentrant, TSTORE 9 but REVERT + .Op(Instruction.JUMPDEST) // PC = 77 + .StoreDataInTransientStorage(1, 9) + .StoreDataInTransientStorage(1, 10) + .Op(Instruction.REVERT) + .Done; + + TestState.CreateAccount(TestItem.AddressD, 1.Ether()); + Keccak contractCodeHash = TestState.UpdateCode(contractCode); + TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec); + + // Return the result received from the contract + byte[] code = Prepare.EvmCode + .Call(TestItem.AddressD, 50000) + .ReturnInnerCallResult() + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + + // Should be original TSTORE value + Assert.AreEqual(8, (int)result.ReturnValue.ToUInt256()); + } + + /// + /// Revert undoes transient storage writes from inner calls that successfully returned + /// + [Test] + public void revert_resets_transient_state_from_succesful_calls() + { + // If caller is self, TLOAD and return value (break recursion) + // Else, TSTORE and call self, return the response + byte[] contractCode = Prepare.EvmCode + // Check call depth + .PushData(0) + .Op(Instruction.CALLDATALOAD) + // Store input in mem and reload it to stack + .DataOnStackToMemory(5) + .PushData(5) + .Op(Instruction.MLOAD) + + // See if we're at call depth 1 + .PushData(1) + .Op(Instruction.EQ) + .PushData(84) + .Op(Instruction.JUMPI) + + // See if we're at call depth 2 + .PushData(5) + .Op(Instruction.MLOAD) + .PushData(2) + .Op(Instruction.EQ) + .PushData(135) + .Op(Instruction.JUMPI) + + // Call depth = 0, call self after TSTORE 8 + .StoreDataInTransientStorage(1, 8) + + // Recursive call with input + // Depth++ + .PushData(5) + .Op(Instruction.MLOAD) + .PushData(1) + .Op(Instruction.ADD) + + .CallWithInput(TestItem.AddressD, 50000) + + // TLOAD and return value + .LoadDataFromTransientStorage(1) + .DataOnStackToMemory(0) + .PushData(32) + .PushData(0) + .Op(Instruction.RETURN) + + // Call depth 1, TSTORE 9 but REVERT after recursion + .Op(Instruction.JUMPDEST) // PC = 84 + .StoreDataInTransientStorage(1, 9) + + // Recursive call with input + // Depth++ + .PushData(5) + .Op(Instruction.MLOAD) + .PushData(1) + .Op(Instruction.ADD) + + .CallWithInput(TestItem.AddressD, 50000) + + .Op(Instruction.REVERT) + + // Call depth 2, TSTORE 10 and complete + .Op(Instruction.JUMPDEST) // PC = 135 + .StoreDataInTransientStorage(1, 10) + .Done; + + TestState.CreateAccount(TestItem.AddressD, 1.Ether()); + Keccak contractCodeHash = TestState.UpdateCode(contractCode); + TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec); + + // Return the result received from the contract + byte[] code = Prepare.EvmCode + .CallWithInput(TestItem.AddressD, 50000, new byte[32]) + .ReturnInnerCallResult() + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + + // Should be original TSTORE value + Assert.AreEqual(8, (int)result.ReturnValue.ToUInt256()); + } + + /// + /// Transient storage cannot be manipulated in a static context + /// + [TestCase(Instruction.CALL, 1)] + [TestCase(Instruction.STATICCALL, 0)] + public void tstore_in_staticcall(Instruction callType, int expectedResult) + { + byte[] contractCode = Prepare.EvmCode + .StoreDataInTransientStorage(1, 8) + .PushData(1) + .DataOnStackToMemory(0) + .PushData(32) + .PushData(0) + .Op(Instruction.RETURN) + .Done; + + TestState.CreateAccount(TestItem.AddressD, 1.Ether()); + Keccak contractCodeHash = TestState.UpdateCode(contractCode); + TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec); + + // Return the result received from the contract (1 if successful) + byte[] code = Prepare.EvmCode + .StoreDataInTransientStorage(1, 7) + .DynamicCallWithInput(callType, TestItem.AddressD, 50000, new byte[32]) + .ReturnInnerCallResult() + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + + Assert.AreEqual(expectedResult, (int)result.ReturnValue.ToUInt256()); + } + + /// + /// Transient storage cannot be manipulated in a static context when calling self + /// + [TestCase(Instruction.CALL, 9)] + [TestCase(Instruction.STATICCALL, 8)] + public void tstore_from_static_reentrant_call(Instruction callType, int expectedResult) + { + // If caller is self, TSTORE 9 and break recursion + // Else, TSTORE 8 and call self, return the result of TLOAD + byte[] contractCode = Prepare.EvmCode + // Check if caller is self + .Op(Instruction.CALLER) + .PushData(TestItem.AddressD) + .Op(Instruction.EQ) + .PushData(113) + .Op(Instruction.JUMPI) + + // Non-reentrant, call self after TSTORE 8 + .StoreDataInTransientStorage(1, 8) + .DynamicCallWithInput(callType, TestItem.AddressD, 50000, new byte[32]) + // Return the TLOAD value + // Should be 8 if call fails, 9 if success + .LoadDataFromTransientStorage(1) + .DataOnStackToMemory(0) + .PushData(32) + .PushData(0) + .Op(Instruction.RETURN) + + // Reentrant, TSTORE 9 + .Op(Instruction.JUMPDEST) // PC = 113 + .StoreDataInTransientStorage(1, 9) + .Done; + + TestState.CreateAccount(TestItem.AddressD, 1.Ether()); + Keccak contractCodeHash = TestState.UpdateCode(contractCode); + TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec); + + // Return the result received from the contract + byte[] code = Prepare.EvmCode + .Call(TestItem.AddressD, 50000) + .ReturnInnerCallResult() + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + + Assert.AreEqual(expectedResult, (int)result.ReturnValue.ToUInt256()); + } + + /// + /// Transient storage cannot be manipulated in a nested static context + /// + [TestCase(Instruction.CALL, 10)] + [TestCase(Instruction.STATICCALL, 8)] + public void tstore_from_nonstatic_reentrant_call_with_static_intermediary(Instruction callType, int expectedResult) + { + // If caller is self, TLOAD and return value (break recursion) + // Else, TSTORE and call self, return the response + byte[] contractCode = Prepare.EvmCode + // Check call depth + .PushData(0) + .Op(Instruction.CALLDATALOAD) + // Store input in mem and reload it to stack + .DataOnStackToMemory(5) + .PushData(5) + .Op(Instruction.MLOAD) + + // See if we're at call depth 1 + .PushData(1) + .Op(Instruction.EQ) + .PushData(84) + .Op(Instruction.JUMPI) + + // See if we're at call depth 2 + .PushData(5) + .Op(Instruction.MLOAD) + .PushData(2) + .Op(Instruction.EQ) + .PushData(140) + .Op(Instruction.JUMPI) + + // Call depth = 0, call self after TSTORE 8 + .StoreDataInTransientStorage(1, 8) + + // Recursive call with input + // Depth++ + .PushData(5) + .Op(Instruction.MLOAD) + .PushData(1) + .Op(Instruction.ADD) + + .DynamicCallWithInput(callType, TestItem.AddressD, 50000) + + // TLOAD and return value + .LoadDataFromTransientStorage(1) + .DataOnStackToMemory(0) + .PushData(32) + .PushData(0) + .Op(Instruction.RETURN) + + // Call depth 1, TSTORE 9 but REVERT after recursion + .Op(Instruction.JUMPDEST) // PC = 84 + + // Recursive call with input + // Depth++ + .PushData(5) + .Op(Instruction.MLOAD) + .PushData(1) + .Op(Instruction.ADD) + .CallWithInput(TestItem.AddressD, 50000) + + // TLOAD and return value + .LoadDataFromTransientStorage(1) + .DataOnStackToMemory(0) + .PushData(32) + .PushData(0) + .Op(Instruction.RETURN) + + // Call depth 2, TSTORE 10 and complete + .Op(Instruction.JUMPDEST) // PC = 140 + .StoreDataInTransientStorage(1, 10) // This will fail + .Done; + + TestState.CreateAccount(TestItem.AddressD, 1.Ether()); + Keccak contractCodeHash = TestState.UpdateCode(contractCode); + TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec); + + // Return the result received from the contract + byte[] code = Prepare.EvmCode + .CallWithInput(TestItem.AddressD, 50000, new byte[32]) + .ReturnInnerCallResult() + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + + // Should be original TSTORE value + Assert.AreEqual(expectedResult, (int)result.ReturnValue.ToUInt256()); + } + + /// + /// Delegatecall manipulates transient storage in the context of the current address + /// + [TestCase(Instruction.CALL, 7)] + [TestCase(Instruction.DELEGATECALL, 8)] + public void tstore_in_delegatecall(Instruction callType, int expectedResult) + { + byte[] contractCode = Prepare.EvmCode + .StoreDataInTransientStorage(1, 8) + .Done; + + TestState.CreateAccount(TestItem.AddressD, 1.Ether()); + Keccak contractCodeHash = TestState.UpdateCode(contractCode); + TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec); + + byte[] code = Prepare.EvmCode + .StoreDataInTransientStorage(1, 7) + .DynamicCallWithInput(callType, TestItem.AddressD, 50000, new byte[32]) + // TLOAD and return value + .LoadDataFromTransientStorage(1) + .DataOnStackToMemory(0) + .PushData(32) + .PushData(0) + .Op(Instruction.RETURN) + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + + Assert.AreEqual(expectedResult, (int)result.ReturnValue.ToUInt256()); + } + + /// + /// Delegatecall reads transient storage in the context of the current address + /// + [TestCase(Instruction.CALL, 0)] + [TestCase(Instruction.DELEGATECALL, 7)] + public void tload_in_delegatecall(Instruction callType, int expectedResult) + { + byte[] contractCode = Prepare.EvmCode + .LoadDataFromTransientStorage(1) + .DataOnStackToMemory(0) + .PushData(32) + .PushData(0) + .Op(Instruction.RETURN) + .Done; + + TestState.CreateAccount(TestItem.AddressD, 1.Ether()); + Keccak contractCodeHash = TestState.UpdateCode(contractCode); + TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec); + + byte[] code = Prepare.EvmCode + .StoreDataInTransientStorage(1, 7) + .DynamicCallWithInput(callType, TestItem.AddressD, 50000, new byte[32]) + // Return response from nested call + .ReturnInnerCallResult() + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + + Assert.AreEqual(expectedResult, (int)result.ReturnValue.ToUInt256()); + } + + /// + /// Zeroing out a transient storage slot does not result in gas refund + /// + [Test] + public void tstore_does_not_result_in_gasrefund() + { + byte[] code = Prepare.EvmCode + .StoreDataInTransientStorage(1, 7) + .StoreDataInTransientStorage(1, 0) + .Done; + + TestAllTracerWithOutput receipt = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + Assert.AreEqual(GasCostOf.Transaction + GasCostOf.VeryLow * 4 + GasCostOf.TStore * 2, receipt.GasSpent, "gas"); + } + + /// + /// Transient storage does not persist beyond a single transaction + /// + [Test] + public void transient_state_not_persisted_across_txs() + { + // Return the result received from the contract + byte[] code = Prepare.EvmCode + .LoadDataFromTransientStorage(1) + // See if we're at call depth 1 + .PushData(1) + .Op(Instruction.EQ) + .PushData(24) + .Op(Instruction.JUMPI) + + // TSTORE 1 and Return 1 + .StoreDataInTransientStorage(1, 1) + .PushData(1) + .DataOnStackToMemory(0) + .PushData(32) + .PushData(0) + .Op(Instruction.RETURN) + + // Return 0 + .Op(Instruction.JUMPDEST) // PC = 24 + .PushData(0) + .DataOnStackToMemory(0) + .PushData(32) + .PushData(0) + .Op(Instruction.RETURN) + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + Assert.AreEqual(1, (int)result.ReturnValue.ToUInt256()); + + // If transient state persisted across txs, calling again would return 0 + result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + Assert.AreEqual(1, (int)result.ReturnValue.ToUInt256()); + } + + + /// + /// Transient storage can be accessed in a static context when calling self + /// + [TestCase(Instruction.CALL, 8)] + [TestCase(Instruction.STATICCALL, 8)] + public void tload_from_static_reentrant_call(Instruction callType, int expectedResult) + { + // If caller is self, TLOAD and break recursion + // Else, TSTORE 8 and call self, return the result of the inner call + byte[] contractCode = Prepare.EvmCode + // Check if caller is self + .Op(Instruction.CALLER) + .PushData(TestItem.AddressD) + .Op(Instruction.EQ) + .PushData(114) + .Op(Instruction.JUMPI) + + // Non-reentrant, call self after TSTORE 8 + .StoreDataInTransientStorage(1, 8) + .DynamicCallWithInput(callType, TestItem.AddressD, 50000, new byte[32]) + .ReturnInnerCallResult() + + // Reentrant, TLOAD and return + .Op(Instruction.JUMPDEST) // PC = 114 + .LoadDataFromTransientStorage(1) + .DataOnStackToMemory(0) + .PushData(32) + .PushData(0) + .Op(Instruction.RETURN) + .Done; + + TestState.CreateAccount(TestItem.AddressD, 1.Ether()); + Keccak contractCodeHash = TestState.UpdateCode(contractCode); + TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec); + + // Return the result received from the contract + byte[] code = Prepare.EvmCode + .Call(TestItem.AddressD, 50000) + .ReturnInnerCallResult() + .Done; + + TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + + Assert.AreEqual(expectedResult, (int)result.ReturnValue.ToUInt256()); + } + } +} diff --git a/src/Nethermind/Nethermind.Evm.Test/EvmStateTests.cs b/src/Nethermind/Nethermind.Evm.Test/EvmStateTests.cs index 9f47b465921..71c5cd6a8ee 100644 --- a/src/Nethermind/Nethermind.Evm.Test/EvmStateTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/EvmStateTests.cs @@ -250,13 +250,13 @@ parentEvmState is null new ExecutionEnvironment(), ExecutionType.Call, true, - new Snapshot(Snapshot.EmptyPosition, Snapshot.EmptyPosition), + Snapshot.Empty, isContinuation) : new EvmState(10000, new ExecutionEnvironment(), ExecutionType.Call, false, - new Snapshot(Snapshot.EmptyPosition, Snapshot.EmptyPosition), + Snapshot.Empty, 0, 0, false, diff --git a/src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs b/src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs index 22f1a93affb..f95ef1b4417 100644 --- a/src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -120,6 +120,22 @@ public class InvalidOpcodeTests : VirtualMachineTestsBase ) ))))).ToArray(); + private static readonly Instruction[] ShanghaiInstructions = + FrontierInstructions.Union( + HomesteadInstructions.Union( + ByzantiumInstructions.Union( + ConstantinopleFixInstructions.Union( + IstanbulInstructions.Union( + BerlinInstructions.Union( + LondonInstructions.Union( + new Instruction[] + { + Instruction.TLOAD, + Instruction.TSTORE + } + ) + )))))).ToArray(); + private Dictionary _validOpcodes = new() { @@ -133,7 +149,8 @@ private Dictionary _validOpcodes {MainnetSpecProvider.MuirGlacierBlockNumber, IstanbulInstructions}, {MainnetSpecProvider.BerlinBlockNumber, BerlinInstructions}, {MainnetSpecProvider.LondonBlockNumber, LondonInstructions}, - {long.MaxValue, LondonInstructions} + {MainnetSpecProvider.ShanghaiBlockNumber, ShanghaiInstructions}, + {long.MaxValue, ShanghaiInstructions} }; private const string InvalidOpCodeErrorMessage = "BadInstruction"; @@ -157,6 +174,7 @@ protected override ILogManager GetLogManager() [TestCase(MainnetSpecProvider.BerlinBlockNumber)] [TestCase(MainnetSpecProvider.BerlinBlockNumber)] [TestCase(MainnetSpecProvider.LondonBlockNumber)] + [TestCase(MainnetSpecProvider.ShanghaiBlockNumber)] [TestCase(long.MaxValue)] public void Test(long blockNumber) { diff --git a/src/Nethermind/Nethermind.Evm.Test/Sha3Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Sha3Tests.cs index 1b78f3b639c..2cb2500b901 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Sha3Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Sha3Tests.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -29,9 +29,9 @@ public class Sha3Tests : VirtualMachineTestsBase private bool _setAuthor; - protected override Block BuildBlock(long blockNumber, SenderRecipientAndMiner senderRecipientAndMiner, Transaction transaction) + protected override Block BuildBlock(long blockNumber, SenderRecipientAndMiner senderRecipientAndMiner, Transaction transaction, long blockGasLimit = DefaultBlockGasLimit) { - Block block = base.BuildBlock(blockNumber, senderRecipientAndMiner, transaction); + Block block = base.BuildBlock(blockNumber, senderRecipientAndMiner, transaction, blockGasLimit); if(_setAuthor) block.Header.Author = TestItem.AddressC; block.Header.Beneficiary = TestItem.AddressB; return block; diff --git a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTests.cs b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTests.cs index f1998e937bf..5e1654cbbb7 100644 --- a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -19,9 +19,9 @@ using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Evm.Tracing.GethStyle; -using Nethermind.State; using Nethermind.Int256; using NUnit.Framework; +using Nethermind.Specs; namespace Nethermind.Evm.Test { @@ -425,6 +425,37 @@ public void Sstore_twice_0_same_storage_should_refund_only_once() Assert.AreEqual(BigInteger.Zero.ToBigEndianByteArray(), Storage.Get(new StorageCell(Recipient, 0)), "storage"); } + /// + /// TLoad gas cost check + /// + [Test] + public void Tload() + { + byte[] code = Prepare.EvmCode + .PushData(96) + .Op(Instruction.TLOAD) + .Done; + + TestAllTracerWithOutput receipt = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + Assert.AreEqual(GasCostOf.Transaction + GasCostOf.VeryLow * 1 + GasCostOf.TLoad, receipt.GasSpent, "gas"); + } + + /// + /// TStore gas cost check + /// + [Test] + public void Tstore() + { + byte[] code = Prepare.EvmCode + .PushData(96) + .PushData(64) + .Op(Instruction.TSTORE) + .Done; + + TestAllTracerWithOutput receipt = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code); + Assert.AreEqual(GasCostOf.Transaction + GasCostOf.VeryLow * 2 + GasCostOf.TStore, receipt.GasSpent, "gas"); + } + [Test] [Ignore("Not yet implemented")] public void Ropsten_attack_contract_test() diff --git a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs index 5b27f00a280..60761ab726f 100644 --- a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs +++ b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs @@ -40,6 +40,7 @@ public class VirtualMachineTestsBase protected const string SampleHexData1 = "a01234"; protected const string SampleHexData2 = "b15678"; protected const string HexZero = "00"; + protected const long DefaultBlockGasLimit = 8000000; private IEthereumEcdsa _ethereumEcdsa; protected ITransactionProcessor _processor; @@ -116,9 +117,9 @@ protected T Execute(T tracer, params byte[] code) where T : ITxTracer return tracer; } - protected TestAllTracerWithOutput Execute(long blockNumber, long gasLimit, byte[] code) + protected TestAllTracerWithOutput Execute(long blockNumber, long gasLimit, byte[] code, long blockGasLimit = DefaultBlockGasLimit) { - (Block block, Transaction transaction) = PrepareTx(blockNumber, gasLimit, code); + (Block block, Transaction transaction) = PrepareTx(blockNumber, gasLimit, code, blockGasLimit: blockGasLimit); TestAllTracerWithOutput tracer = CreateTracer(); _processor.Execute(transaction, block.Header, tracer); return tracer; @@ -129,7 +130,8 @@ protected TestAllTracerWithOutput Execute(long blockNumber, long gasLimit, byte[ long gasLimit, byte[] code, SenderRecipientAndMiner senderRecipientAndMiner = null, - int value = 1) + int value = 1, + long blockGasLimit = DefaultBlockGasLimit) { senderRecipientAndMiner ??= SenderRecipientAndMiner.Default; TestState.CreateAccount(senderRecipientAndMiner.Sender, 100.Ether()); @@ -152,7 +154,7 @@ protected TestAllTracerWithOutput Execute(long blockNumber, long gasLimit, byte[ .SignedAndResolved(_ethereumEcdsa, senderRecipientAndMiner.SenderKey) .TestObject; - Block block = BuildBlock(blockNumber, senderRecipientAndMiner, transaction); + Block block = BuildBlock(blockNumber, senderRecipientAndMiner, transaction, blockGasLimit); return (block, transaction); } @@ -202,10 +204,10 @@ protected Block BuildBlock(long blockNumber, SenderRecipientAndMiner senderRecip return BuildBlock(blockNumber, senderRecipientAndMiner, null); } - protected virtual Block BuildBlock(long blockNumber, SenderRecipientAndMiner senderRecipientAndMiner, Transaction tx) + protected virtual Block BuildBlock(long blockNumber, SenderRecipientAndMiner senderRecipientAndMiner, Transaction tx, long blockGasLimit = DefaultBlockGasLimit) { senderRecipientAndMiner ??= SenderRecipientAndMiner.Default; - return Build.A.Block.WithNumber(blockNumber).WithTransactions(tx == null ? new Transaction[0] : new[] {tx}).WithGasLimit(8000000).WithBeneficiary(senderRecipientAndMiner.Miner).TestObject; + return Build.A.Block.WithNumber(blockNumber).WithTransactions(tx == null ? new Transaction[0] : new[] {tx}).WithGasLimit(blockGasLimit).WithBeneficiary(senderRecipientAndMiner.Miner).TestObject; } protected void AssertGas(TestAllTracerWithOutput receipt, long gas) diff --git a/src/Nethermind/Nethermind.Evm/ByteCodeBuilder.cs b/src/Nethermind/Nethermind.Evm/ByteCodeBuilder.cs index 6db7c50e6af..17520644947 100644 --- a/src/Nethermind/Nethermind.Evm/ByteCodeBuilder.cs +++ b/src/Nethermind/Nethermind.Evm/ByteCodeBuilder.cs @@ -22,6 +22,9 @@ namespace Nethermind.Evm { + /// + /// A utility class to easily construct common patterns of EVM byte code + /// public class Prepare { private readonly List _byteCode = new(); @@ -115,12 +118,20 @@ public Prepare CallWithInput(Address address, long gasLimit, string input) return CallWithInput(address, gasLimit, Bytes.FromHexString(input)); } - public Prepare CallWithInput(Address address, long gasLimit, byte[] input) + public Prepare CallWithInput(Address address, long gasLimit, byte[]? input = null) { - StoreDataInMemory(0, input); + if (input != null) + { + StoreDataInMemory(0, input); + } + else + { + // Use top of stack as input + DataOnStackToMemory(0); + } PushData(0); PushData(0); - PushData(input.Length); + PushData(input != null ? input.Length : 32); PushData(0); PushData(0); PushData(address); @@ -179,6 +190,43 @@ public Prepare StaticCall(Address address, long gasLimit) return this; } + /// + /// Call the address with the specified callType + /// + /// CALL, STATICCALL, DELEGATECALL + /// Address of the contract + /// Gas limit of the call + /// Optional 32 byte input + /// Prepare with call bytecode + /// Throws exception if callType is incorrect + public Prepare DynamicCallWithInput(Instruction callType, Address address, long gasLimit, byte[]? input = null) + { + if (callType != Instruction.CALL && + callType != Instruction.STATICCALL && + callType != Instruction.DELEGATECALL) + { + throw new Exception($"Unexpected call type {callType}"); + } + if (input != null) + { + StoreDataInMemory(0, input); + } + else + { + // Use top of stack as input + DataOnStackToMemory(0); + } + PushData(0); + PushData(0); + PushData(input != null ? input.Length : 32); + PushData(0); + PushData(0); + PushData(address); + PushData(gasLimit); + Op(callType); + return this; + } + public Prepare PushData(Address address) { PushData(address.Bytes); @@ -271,5 +319,74 @@ private Prepare StoreDataInMemory(int position, byte[] data) return this; } + + /// + /// Take the data already on stack and store it in memory + /// at specified position + /// + /// Memory position + /// Prepare with requested bytecode + public Prepare DataOnStackToMemory(int position) + { + PushData(position); + Op(Instruction.MSTORE); + return this; + } + + /// + /// Store input value at specified key in transient storage + /// + /// Storage key + /// Value to store + /// Prepare with requested bytecode + public Prepare StoreDataInTransientStorage(int key, int value) + { + PushData(value); + PushData(key); + Op(Instruction.TSTORE); + return this; + } + + /// + /// Load value from specified key in transient storage + /// + /// Storage key + /// Prepare with requested bytecode + public Prepare LoadDataFromTransientStorage(int key) + { + PushData(key); + Op(Instruction.TLOAD); + return this; + } + + /// + /// Return the data in memory at position + /// + /// Data size + /// Memory position + /// Prepare with requested bytecode + public Prepare Return(int size, int position) + { + PushData(size); + PushData(position); + Op(Instruction.RETURN); + return this; + } + + /// + /// Returns the result from a call made immediately prior + /// + /// Prepare with requested bytecode + public Prepare ReturnInnerCallResult() + { + PushData(32); + PushData(0); + PushData(0); + Op(Instruction.RETURNDATACOPY); + PushData(32); + PushData(0); + Op(Instruction.RETURN); + return this; + } } } diff --git a/src/Nethermind/Nethermind.Evm/GasCostOf.cs b/src/Nethermind/Nethermind.Evm/GasCostOf.cs index fa9576eb9e5..c43d4469ae7 100644 --- a/src/Nethermind/Nethermind.Evm/GasCostOf.cs +++ b/src/Nethermind/Nethermind.Evm/GasCostOf.cs @@ -70,5 +70,7 @@ public static class GasCostOf public const long AccessAccountListEntry = 2400; // eip-2930 public const long AccessStorageListEntry = 1900; // eip-2930 + public const long TLoad = WarmStateRead; // eip-1153 + public const long TStore = WarmStateRead; // eip-1153 } } diff --git a/src/Nethermind/Nethermind.Evm/Instruction.cs b/src/Nethermind/Nethermind.Evm/Instruction.cs index 959327bfaef..d162919ffb3 100644 --- a/src/Nethermind/Nethermind.Evm/Instruction.cs +++ b/src/Nethermind/Nethermind.Evm/Instruction.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -167,6 +167,10 @@ public enum Instruction : byte LOG3 = 0xa3, LOG4 = 0xa4, + // EIP-1153 + TLOAD = 0xb3, + TSTORE = 0xb4, + CREATE = 0xf0, CALL = 0xf1, CALLCODE = 0xf2, diff --git a/src/Nethermind/Nethermind.Evm/Metrics.cs b/src/Nethermind/Nethermind.Evm/Metrics.cs index b5514c7a54c..e60bdafe38e 100644 --- a/src/Nethermind/Nethermind.Evm/Metrics.cs +++ b/src/Nethermind/Nethermind.Evm/Metrics.cs @@ -34,7 +34,13 @@ public class Metrics [Description("Number of SSTORE opcodes executed.")] public static long SstoreOpcode { get; set; } - + + [Description("Number of TLOAD opcodes executed.")] + public static long TloadOpcode { get; set; } + + [Description("Number of TSTORE opcodes executed.")] + public static long TstoreOpcode { get; set; } + [Description("Number of MODEXP precompiles executed.")] public static long ModExpOpcode { get; set; } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/ITxTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/ITxTracer.cs index 09b29a81abe..58e8933e5be 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/ITxTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/ITxTracer.cs @@ -127,8 +127,10 @@ void ReportMemoryChange(long offset, in ZeroPaddedMemory data) void ReportStorageChange(in ReadOnlySpan key, in ReadOnlySpan value); void SetOperationStorage(Address address, UInt256 storageIndex, ReadOnlySpan newValue, ReadOnlySpan currentValue); + void SetOperationTransientStorage(Address storageCellAddress, UInt256 storageIndex, Span newValue, byte[] currentValue) { } void LoadOperationStorage(Address address, UInt256 storageIndex, ReadOnlySpan value); + void LoadOperationTransientStorage(Address storageCellAddress, UInt256 storageIndex, byte[] value) { } void ReportSelfDestruct(Address address, UInt256 balance, Address refundAddress); diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index 9f03c1f6fbf..7c1f805f2c4 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -2017,6 +2017,80 @@ void UpdateMemoryCost(in UInt256 position, in UInt256 length) break; } + case Instruction.TLOAD: + { + Metrics.TloadOpcode++; + if (!spec.TransientStorageEnabled) + { + EndInstructionTraceError(EvmExceptionType.BadInstruction); + return CallResult.InvalidInstructionException; + } + var gasCost = GasCostOf.TLoad; + + if (!UpdateGas(gasCost, ref gasAvailable)) + { + EndInstructionTraceError(EvmExceptionType.OutOfGas); + return CallResult.OutOfGasException; + } + + stack.PopUInt256(out UInt256 storageIndex); + StorageCell storageCell = new(env.ExecutingAccount, storageIndex); + + byte[] value = _storage.GetTransientState(storageCell); + stack.PushBytes(value); + + if (_txTracer.IsTracingOpLevelStorage) + { + _txTracer.LoadOperationTransientStorage(storageCell.Address, storageIndex, value); + } + + break; + } + case Instruction.TSTORE: + { + Metrics.TstoreOpcode++; + if (!spec.TransientStorageEnabled) + { + EndInstructionTraceError(EvmExceptionType.BadInstruction); + return CallResult.InvalidInstructionException; + } + + if (vmState.IsStatic) + { + EndInstructionTraceError(EvmExceptionType.StaticCallViolation); + return CallResult.StaticCallViolationException; + } + + long gasCost = GasCostOf.TStore; + if (!UpdateGas(gasCost, ref gasAvailable)) + { + EndInstructionTraceError(EvmExceptionType.OutOfGas); + return CallResult.OutOfGasException; + } + + stack.PopUInt256(out UInt256 storageIndex); + Span newValue = stack.PopBytes(); + bool newIsZero = newValue.IsZero(); + if (!newIsZero) + { + newValue = newValue.WithoutLeadingZeros().ToArray(); + } + else + { + newValue = BytesZero; + } + + StorageCell storageCell = new(env.ExecutingAccount, storageIndex); + byte[] currentValue = newValue.ToArray(); + _storage.SetTransientState(storageCell, currentValue); + + if (_txTracer.IsTracingOpLevelStorage) + { + _txTracer.SetOperationTransientStorage(storageCell.Address, storageIndex, newValue, currentValue); + } + + break; + } case Instruction.JUMP: { if (!UpdateGas(GasCostOf.Mid, ref gasAvailable)) diff --git a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs index 4dd2c5c76f3..00f877adc10 100644 --- a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs +++ b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify diff --git a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecLoaderTests.cs b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecLoaderTests.cs index 0c7cc4b9a56..d141b7d3339 100644 --- a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecLoaderTests.cs +++ b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecLoaderTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify diff --git a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs index 52fd930b8e2..3bbc6b9b32b 100644 --- a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -147,5 +147,6 @@ public long Eip1559TransitionBlock } public Address? Eip1559FeeCollector => _spec.Eip1559FeeCollector; + public bool IsEip1153Enabled => _spec.IsEip1153Enabled; } } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs index 92fb77df2eb..af04c641235 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs @@ -113,11 +113,13 @@ public class ChainParameters /// Optional, minimal value of EIP1559 base fee /// public UInt256? Eip1559BaseFeeMinValue { get; set; } - + public long? MergeForkIdTransition { get; set; } - + public long? TerminalPowBlockNumber { get; set; } - + public UInt256? TerminalTotalDifficulty { get; set; } + + public long? Eip1153Transition { get; set; } } } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs index 1858cf23bed..bffe8b3f35c 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs @@ -87,5 +87,7 @@ public class ChainSpec public long? TerminalPoWBlockNumber { get; set; } public UInt256? TerminalTotalDifficulty { get; set; } + + public long? ShanghaiBlockNumber { get; set; } } } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs index 386dded0717..36dc2f8d614 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -139,6 +139,7 @@ private void BuildTransitions() releaseSpec.IsEip3541Enabled = (_chainSpec.Parameters.Eip3541Transition ?? long.MaxValue) <= releaseStartBlock; releaseSpec.IsEip3529Enabled = (_chainSpec.Parameters.Eip3529Transition ?? long.MaxValue) <= releaseStartBlock; releaseSpec.IsEip3607Enabled = (_chainSpec.Parameters.Eip3607Transition ?? long.MaxValue) <= releaseStartBlock; + releaseSpec.IsEip1153Enabled = (_chainSpec.Parameters.Eip1153Transition ?? long.MaxValue) <= releaseStartBlock; releaseSpec.ValidateChainId = (_chainSpec.Parameters.ValidateChainIdTransition ?? 0) <= releaseStartBlock; releaseSpec.ValidateReceipts = ((_chainSpec.Parameters.ValidateReceiptsTransition > 0) ? Math.Max(_chainSpec.Parameters.ValidateReceiptsTransition ?? 0, _chainSpec.Parameters.Eip658Transition ?? 0) : 0) <= releaseStartBlock; releaseSpec.Eip1559FeeCollector = releaseSpec.IsEip1559Enabled && (_chainSpec.Parameters.Eip1559FeeCollectorTransition ?? long.MaxValue) <= releaseStartBlock ? _chainSpec.Parameters.Eip1559FeeCollector : null; diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs index a6d10c9d30e..8be3765f451 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -146,6 +146,7 @@ private void LoadParameters(ChainSpecJson chainSpecJson, ChainSpec chainSpec) Eip3541Transition = chainSpecJson.Params.Eip3541Transition, Eip3529Transition = chainSpecJson.Params.Eip3529Transition, Eip3607Transition = chainSpecJson.Params.Eip3607Transition, + Eip1153Transition = chainSpecJson.Params.Eip1153Transition, TransactionPermissionContract = chainSpecJson.Params.TransactionPermissionContract, TransactionPermissionContractTransition = chainSpecJson.Params.TransactionPermissionContractTransition, ValidateChainIdTransition = chainSpecJson.Params.ValidateChainIdTransition, @@ -222,7 +223,8 @@ private static void LoadTransitions(ChainSpecJson chainSpecJson, ChainSpec chain chainSpec.GrayGlacierBlockNumber = chainSpec.Ethash?.DifficultyBombDelays.Count > 5 ? chainSpec.Ethash?.DifficultyBombDelays.Keys.ToArray()[5] : null; - + chainSpec.ShanghaiBlockNumber = chainSpec.Parameters.Eip1153Transition ?? (long.MaxValue - 1); + // TheMerge parameters chainSpec.MergeForkIdBlockNumber = chainSpec.Parameters.MergeForkIdTransition; chainSpec.TerminalPoWBlockNumber = chainSpec.Parameters.TerminalPowBlockNumber; diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs index f989111f5d6..07b306ef789 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -138,11 +138,13 @@ internal class ChainSpecParamsJson public long? Eip1559BaseFeeMinValueTransition { get; set; } public UInt256? Eip1559BaseFeeMinValue { get; set; } - + public long? MergeForkIdTransition { get; set; } - + public UInt256? TerminalTotalDifficulty { get; set; } - + public long? TerminalPoWBlockNumber { get; set; } + + public long? Eip1153Transition { get; set; } } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs b/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs index c9a6870e473..f9bc8cf4b34 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs @@ -83,5 +83,6 @@ private Olympic() public bool IsEip3541Enabled => false; public bool IsEip3607Enabled => true; public long Eip1559TransitionBlock => long.MaxValue; + public bool IsEip1153Enabled => false; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/01_Frontier.cs b/src/Nethermind/Nethermind.Specs/Forks/01_Frontier.cs index 12f3a70139d..8ea42d2f0f1 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/01_Frontier.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/01_Frontier.cs @@ -81,5 +81,6 @@ private Frontier() public bool IsEip3541Enabled => false; public bool IsEip3607Enabled => true; public bool IsEip158IgnoredAccount(Address address) => false; + public bool IsEip1153Enabled => false; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/02_Homestead.cs b/src/Nethermind/Nethermind.Specs/Forks/02_Homestead.cs index 910b627cff3..9ed0a4435a4 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/02_Homestead.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/02_Homestead.cs @@ -79,5 +79,6 @@ private Homestead() { } public bool IsEip3541Enabled => false; public bool IsEip3607Enabled => true; public bool IsEip158IgnoredAccount(Address address) => false; + public bool IsEip1153Enabled => false; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/03_Dao.cs b/src/Nethermind/Nethermind.Specs/Forks/03_Dao.cs index 88586f050bf..04765ccbd7e 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/03_Dao.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/03_Dao.cs @@ -81,5 +81,6 @@ private Dao() public bool IsEip3541Enabled => false; public bool IsEip3607Enabled => true; public bool IsEip158IgnoredAccount(Address address) => false; + public bool IsEip1153Enabled => false; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/04_TangerineWhistle.cs b/src/Nethermind/Nethermind.Specs/Forks/04_TangerineWhistle.cs index 33bb970a7aa..ecf3f7fd058 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/04_TangerineWhistle.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/04_TangerineWhistle.cs @@ -81,5 +81,6 @@ private TangerineWhistle() public bool IsEip3541Enabled => false; public bool IsEip3607Enabled => true; public long Eip1559TransitionBlock => long.MaxValue; + public bool IsEip1153Enabled => false; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/05_SpuriousDragon.cs b/src/Nethermind/Nethermind.Specs/Forks/05_SpuriousDragon.cs index 34105adc895..a50e7c7ba13 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/05_SpuriousDragon.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/05_SpuriousDragon.cs @@ -81,5 +81,6 @@ private SpuriousDragon() public bool IsEip3541Enabled => false; public bool IsEip3607Enabled => true; public bool IsEip158IgnoredAccount(Address address) => false; + public bool IsEip1153Enabled => false; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/06_Byzantium.cs b/src/Nethermind/Nethermind.Specs/Forks/06_Byzantium.cs index 1427783cf2a..c66c133a242 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/06_Byzantium.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/06_Byzantium.cs @@ -81,5 +81,6 @@ private Byzantium() public bool IsEip3541Enabled => false; public bool IsEip3607Enabled => true; public bool IsEip158IgnoredAccount(Address address) => false; + public bool IsEip1153Enabled => false; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/07_Constantinople.cs b/src/Nethermind/Nethermind.Specs/Forks/07_Constantinople.cs index ef0d4bfe894..673d294897a 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/07_Constantinople.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/07_Constantinople.cs @@ -81,5 +81,6 @@ private Constantinople() public bool IsEip3541Enabled => false; public bool IsEip3607Enabled => true; public long Eip1559TransitionBlock => long.MaxValue; + public bool IsEip1153Enabled => false; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/08_ConstantinopleFix.cs b/src/Nethermind/Nethermind.Specs/Forks/08_ConstantinopleFix.cs index a1a9f2c7a83..141f3422422 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/08_ConstantinopleFix.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/08_ConstantinopleFix.cs @@ -83,5 +83,6 @@ private ConstantinopleFix() public bool IsEip3541Enabled => false; public bool IsEip3607Enabled => true; public long Eip1559TransitionBlock => long.MaxValue; + public bool IsEip1153Enabled => false; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/09_Istanbul.cs b/src/Nethermind/Nethermind.Specs/Forks/09_Istanbul.cs index 88d6a734d1f..86af18f96f8 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/09_Istanbul.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/09_Istanbul.cs @@ -81,5 +81,6 @@ private Istanbul() public bool IsEip3541Enabled => false; public bool IsEip3607Enabled => true; public long Eip1559TransitionBlock => long.MaxValue; + public bool IsEip1153Enabled => false; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/10_MuirGlacier.cs b/src/Nethermind/Nethermind.Specs/Forks/10_MuirGlacier.cs index 310792cba08..aac60115e00 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/10_MuirGlacier.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/10_MuirGlacier.cs @@ -79,5 +79,6 @@ private MuirGlacier() { } public bool IsEip3541Enabled => false; public bool IsEip3607Enabled => true; public long Eip1559TransitionBlock => long.MaxValue; + public bool IsEip1153Enabled => false; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/11_Berlin.cs b/src/Nethermind/Nethermind.Specs/Forks/11_Berlin.cs index 3bdb331966f..0b9eceeeda8 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/11_Berlin.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/11_Berlin.cs @@ -81,5 +81,6 @@ private Berlin() public bool IsEip3541Enabled => false; public bool IsEip3607Enabled => true; public long Eip1559TransitionBlock => long.MaxValue; + public bool IsEip1153Enabled => false; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/12_London.cs b/src/Nethermind/Nethermind.Specs/Forks/12_London.cs index 273d646ae4c..a60a6437330 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/12_London.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/12_London.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -82,5 +82,6 @@ private London() public bool IsEip3541Enabled => true; public bool IsEip3607Enabled => true; public long Eip1559TransitionBlock => 12965000; + public bool IsEip1153Enabled => false; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/13_ArrowGlacier.cs b/src/Nethermind/Nethermind.Specs/Forks/13_ArrowGlacier.cs index e1d20cc86ff..04c041529c0 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/13_ArrowGlacier.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/13_ArrowGlacier.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -82,5 +82,6 @@ private ArrowGlacier() public bool IsEip3541Enabled => true; public bool IsEip3607Enabled => true; public long Eip1559TransitionBlock => 12965000; + public bool IsEip1153Enabled => false; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/14_GrayGlacier.cs b/src/Nethermind/Nethermind.Specs/Forks/14_GrayGlacier.cs index 2bff0d592f2..5bfdb3a38d1 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/14_GrayGlacier.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/14_GrayGlacier.cs @@ -80,5 +80,6 @@ private GrayGlacier() { } public bool IsEip3541Enabled => true; public bool IsEip3607Enabled => true; public long Eip1559TransitionBlock => 12965000; + public bool IsEip1153Enabled => false; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/15_Shanghai.cs b/src/Nethermind/Nethermind.Specs/Forks/15_Shanghai.cs new file mode 100644 index 00000000000..27982049251 --- /dev/null +++ b/src/Nethermind/Nethermind.Specs/Forks/15_Shanghai.cs @@ -0,0 +1,88 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System.Threading; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Int256; + +namespace Nethermind.Specs.Forks +{ + public class Shanghai : IReleaseSpec + { + private static IReleaseSpec _instance; + + private Shanghai() + { + } + + public static IReleaseSpec Instance => LazyInitializer.EnsureInitialized(ref _instance, () => new Shanghai()); + + public string Name => "Shanghai"; + public long MaximumExtraDataSize => 32; + public long MaxCodeSize => 24576; + public long MinGasLimit => 5000; + public long GasLimitBoundDivisor => 0x0400; + public UInt256 BlockReward { get; } = UInt256.Parse("2000000000000000000"); + public long DifficultyBombDelay => 10700000L; + public long DifficultyBoundDivisor => 0x0800; + public long? FixedDifficulty => null; + public int MaximumUncleCount => 2; + public bool IsTimeAdjustmentPostOlympic => true; + public bool IsEip2Enabled => true; + public bool IsEip7Enabled => true; + public bool IsEip100Enabled => true; + public bool IsEip140Enabled => true; + public bool IsEip150Enabled => true; + public bool IsEip155Enabled => true; + public bool IsEip158Enabled => true; + public bool IsEip160Enabled => true; + public bool IsEip170Enabled => true; + public bool IsEip196Enabled => true; + public bool IsEip197Enabled => true; + public bool IsEip198Enabled => true; + public bool IsEip211Enabled => true; + public bool IsEip214Enabled => true; + public bool IsEip649Enabled => true; + public bool IsEip658Enabled => true; + public bool IsEip145Enabled => true; + public bool IsEip1014Enabled => true; + public bool IsEip1052Enabled => true; + public bool IsEip1283Enabled => false; + public bool IsEip1234Enabled => true; + public bool IsEip1344Enabled => true; + public bool IsEip2028Enabled => true; + public bool IsEip152Enabled => true; + public bool IsEip1108Enabled => true; + public bool IsEip1884Enabled => true; + public bool IsEip2200Enabled => true; + public bool IsEip2315Enabled => false; + public bool IsEip2537Enabled => false; + public bool IsEip2565Enabled => true; + public bool IsEip2929Enabled => true; + public bool IsEip2930Enabled => true; + public bool IsEip158IgnoredAccount(Address address) => false; + public bool IsEip1559Enabled => true; + public bool IsEip3198Enabled => true; + public bool IsEip3529Enabled => true; + public bool IsEip3541Enabled => true; + public bool IsEip3607Enabled => true; + public bool IsEip3675Enabled => false; + public long Eip1559TransitionBlock => 12965000; + public bool IsEip1153Enabled => true; + } +} diff --git a/src/Nethermind/Nethermind.Specs/MainNetSpecProvider.cs b/src/Nethermind/Nethermind.Specs/MainNetSpecProvider.cs index c9af9c904c6..bc1752d4107 100644 --- a/src/Nethermind/Nethermind.Specs/MainNetSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/MainNetSpecProvider.cs @@ -51,7 +51,8 @@ public IReleaseSpec GetSpec(long blockNumber) => < LondonBlockNumber => Berlin.Instance, < ArrowGlacierBlockNumber => London.Instance, < GrayGlacierBlockNumber => ArrowGlacier.Instance, - _ => GrayGlacier.Instance + < ShanghaiBlockNumber => GrayGlacier.Instance, + _ => Shanghai.Instance }; public const long HomesteadBlockNumber = 1_150_000; @@ -78,7 +79,7 @@ public IReleaseSpec GetSpec(long blockNumber) => { HomesteadBlockNumber, DaoBlockNumberConst, TangerineWhistleBlockNumber, SpuriousDragonBlockNumber, ByzantiumBlockNumber, ConstantinopleFixBlockNumber, IstanbulBlockNumber, MuirGlacierBlockNumber, - BerlinBlockNumber, LondonBlockNumber, ArrowGlacierBlockNumber, GrayGlacierBlockNumber + BerlinBlockNumber, LondonBlockNumber, ArrowGlacierBlockNumber, GrayGlacierBlockNumber, ShanghaiBlockNumber }; private MainnetSpecProvider() { } diff --git a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs index 1945ecd5fd2..1e59143ceac 100644 --- a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs @@ -76,5 +76,6 @@ public class ReleaseSpec : IReleaseSpec public long Eip1559TransitionBlock { get; set; } public Address Eip1559FeeCollector { get; set; } public UInt256? Eip1559BaseFeeMinValue { get; set; } + public bool IsEip1153Enabled { get; set; } } } diff --git a/src/Nethermind/Nethermind.Specs/SystemTransactionReleaseSpec.cs b/src/Nethermind/Nethermind.Specs/SystemTransactionReleaseSpec.cs index 7cf3120c27f..6cc2cce2d82 100644 --- a/src/Nethermind/Nethermind.Specs/SystemTransactionReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs/SystemTransactionReleaseSpec.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -130,5 +130,6 @@ public bool IsEip158IgnoredAccount(Address address) public long Eip1559TransitionBlock => _spec.Eip1559TransitionBlock; public Address Eip1559FeeCollector => _spec.Eip1559FeeCollector; + public bool IsEip1153Enabled => _spec.IsEip1153Enabled; } } diff --git a/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs b/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs index 6569adfafc5..2769c197740 100644 --- a/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -15,6 +15,7 @@ // along with the Nethermind. If not, see . using System; +using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -57,7 +58,7 @@ public void Empty_commit_restore() Context ctx = new(); StorageProvider provider = BuildStorageProvider(ctx); provider.Commit(); - provider.Restore(-1); + provider.Restore(Snapshot.Storage.Empty); } private StorageProvider BuildStorageProvider(Context ctx) @@ -236,6 +237,193 @@ public void Can_commit_when_exactly_at_capacity_regression() Assert.AreEqual(_values[(Resettable.StartCapacity + 1) % 2], valueAfter); } + /// + /// Transient storage should be zero if uninitialized + /// + [Test] + public void Can_tload_uninitialized_locations() + { + Context ctx = new(); + StorageProvider provider = BuildStorageProvider(ctx); + // Should be 0 if not set + Assert.True(provider.GetTransientState(new StorageCell(ctx.Address1, 1)).IsZero()); + + // Should be 0 if loading from the same contract but different index + provider.SetTransientState(new StorageCell(ctx.Address1, 2), _values[1]); + Assert.True(provider.GetTransientState(new StorageCell(ctx.Address1, 1)).IsZero()); + + // Should be 0 if loading from the same index but different contract + Assert.True(provider.GetTransientState(new StorageCell(ctx.Address2, 1)).IsZero()); + } + + /// + /// Simple transient storage test + /// + [Test] + public void Can_tload_after_tstore() + { + Context ctx = new(); + StorageProvider provider = BuildStorageProvider(ctx); + + provider.SetTransientState(new StorageCell(ctx.Address1, 2), _values[1]); + Assert.AreEqual(_values[1], provider.GetTransientState(new StorageCell(ctx.Address1, 2))); + } + + /// + /// Transient storage can be updated and restored + /// + /// Snapshot to restore to + [TestCase(-1)] + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void Tload_same_address_same_index_different_values_restore(int snapshot) + { + Context ctx = new(); + StorageProvider provider = BuildStorageProvider(ctx); + Snapshot.Storage[] snapshots = new Snapshot.Storage[4]; + snapshots[0] = ((IStorageProvider)provider).TakeSnapshot(); + provider.SetTransientState(new StorageCell(ctx.Address1, 1), _values[1]); + snapshots[1] = ((IStorageProvider)provider).TakeSnapshot(); + provider.SetTransientState(new StorageCell(ctx.Address1, 1), _values[2]); + snapshots[2] = ((IStorageProvider)provider).TakeSnapshot(); + provider.SetTransientState(new StorageCell(ctx.Address1, 1), _values[3]); + snapshots[3] = ((IStorageProvider)provider).TakeSnapshot(); + + Assert.AreEqual(snapshots[snapshot + 1].TransientStorageSnapshot, snapshot); + // Persistent storage is unimpacted by transient storage + Assert.AreEqual(snapshots[snapshot + 1].PersistentStorageSnapshot, -1); + provider.Restore(snapshots[snapshot + 1]); + + Assert.AreEqual(_values[snapshot + 1], provider.GetTransientState(new StorageCell(ctx.Address1, 1))); + } + + /// + /// Commit will reset transient state + /// + [Test] + public void Commit_resets_transient_state() + { + Context ctx = new(); + StorageProvider provider = BuildStorageProvider(ctx); + + provider.SetTransientState(new StorageCell(ctx.Address1, 2), _values[1]); + Assert.AreEqual(_values[1], provider.GetTransientState(new StorageCell(ctx.Address1, 2))); + + provider.Commit(); + Assert.True(provider.GetTransientState(new StorageCell(ctx.Address1, 2)).IsZero()); + } + + /// + /// Reset will reset transient state + /// + [Test] + public void Reset_resets_transient_state() + { + Context ctx = new(); + StorageProvider provider = BuildStorageProvider(ctx); + + provider.SetTransientState(new StorageCell(ctx.Address1, 2), _values[1]); + Assert.AreEqual(_values[1], provider.GetTransientState(new StorageCell(ctx.Address1, 2))); + + provider.Reset(); + Assert.True(provider.GetTransientState(new StorageCell(ctx.Address1, 2)).IsZero()); + } + + /// + /// Transient state does not impact persistent state + /// + /// Snapshot to restore to + [TestCase(-1)] + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void Transient_state_restores_independent_of_persistent_state(int snapshot) + { + Context ctx = new(); + StorageProvider provider = BuildStorageProvider(ctx); + Snapshot.Storage[] snapshots = new Snapshot.Storage[4]; + + // No updates + snapshots[0] = ((IStorageProvider)provider).TakeSnapshot(); + + // Only update transient + provider.SetTransientState(new StorageCell(ctx.Address1, 1), _values[1]); + snapshots[1] = ((IStorageProvider)provider).TakeSnapshot(); + + // Update both + provider.SetTransientState(new StorageCell(ctx.Address1, 1), _values[2]); + provider.Set(new StorageCell(ctx.Address1, 1), _values[9]); + snapshots[2] = ((IStorageProvider)provider).TakeSnapshot(); + + // Only update persistent + provider.Set(new StorageCell(ctx.Address1, 1), _values[8]); + snapshots[3] = ((IStorageProvider)provider).TakeSnapshot(); + + provider.Restore(snapshots[snapshot + 1]); + + // Since we didn't update transient on the 3rd snapshot + if (snapshot == 2) + { + snapshot--; + } + + snapshots.Should().Equal( + Snapshot.Storage.Empty, + new Snapshot.Storage(Snapshot.EmptyPosition, 0), + new Snapshot.Storage(0, 1), + new Snapshot.Storage(1, 1)); + + _values[snapshot + 1].Should().BeEquivalentTo(provider.GetTransientState(new StorageCell(ctx.Address1, 1))); + } + + /// + /// Persistent state does not impact transient state + /// + /// Snapshot to restore to + [TestCase(-1)] + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void Persistent_state_restores_independent_of_transient_state(int snapshot) + { + Context ctx = new(); + StorageProvider provider = BuildStorageProvider(ctx); + Snapshot.Storage[] snapshots = new Snapshot.Storage[4]; + + // No updates + snapshots[0] = ((IStorageProvider)provider).TakeSnapshot(); + + // Only update persistent + provider.Set(new StorageCell(ctx.Address1, 1), _values[1]); + snapshots[1] = ((IStorageProvider)provider).TakeSnapshot(); + + // Update both + provider.Set(new StorageCell(ctx.Address1, 1), _values[2]); + provider.SetTransientState(new StorageCell(ctx.Address1, 1), _values[9]); + snapshots[2] = ((IStorageProvider)provider).TakeSnapshot(); + + // Only update transient + provider.SetTransientState(new StorageCell(ctx.Address1, 1), _values[8]); + snapshots[3] = ((IStorageProvider)provider).TakeSnapshot(); + + provider.Restore(snapshots[snapshot + 1]); + + // Since we didn't update persistent on the 3rd snapshot + if (snapshot == 2) + { + snapshot--; + } + + snapshots.Should().Equal( + Snapshot.Storage.Empty, + new Snapshot.Storage(0, Snapshot.EmptyPosition), + new Snapshot.Storage(1, 0), + new Snapshot.Storage(1, 1)); + + _values[snapshot + 1].Should().BeEquivalentTo(provider.Get(new StorageCell(ctx.Address1, 1))); + } + private class Context { public IStateProvider StateProvider { get; } diff --git a/src/Nethermind/Nethermind.State.Test/WorldStateTests.cs b/src/Nethermind/Nethermind.State.Test/WorldStateTests.cs index 0d44082d6eb..a68a70703f8 100644 --- a/src/Nethermind/Nethermind.State.Test/WorldStateTests.cs +++ b/src/Nethermind/Nethermind.State.Test/WorldStateTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -48,7 +48,7 @@ public void When_taking_a_snapshot_return_the_same_value_as_both() Snapshot snapshot = worldState.TakeSnapshot(); snapshot.StateSnapshot.Should().Be(0); - snapshot.StorageSnapshot.Should().Be(0); + snapshot.StorageSnapshot.PersistentStorageSnapshot.Should().Be(0); } [Test] @@ -60,11 +60,12 @@ public void When_taking_a_snapshot_can_return_non_zero_snapshot_value() WorldState worldState = new(stateProvider, storageProvider); stateProvider.TakeSnapshot().Returns(1); - storageProvider.TakeSnapshot().Returns(2); + storageProvider.TakeSnapshot().Returns(new Snapshot.Storage(2, 3)); Snapshot snapshot = worldState.TakeSnapshot(); snapshot.StateSnapshot.Should().Be(1); - snapshot.StorageSnapshot.Should().Be(2); + snapshot.StorageSnapshot.PersistentStorageSnapshot.Should().Be(2); + snapshot.StorageSnapshot.TransientStorageSnapshot.Should().Be(3); } [Test] @@ -85,9 +86,9 @@ public void Can_restore_snapshot() IStorageProvider storageProvider = Substitute.For(); WorldState worldState = new(stateProvider, storageProvider); - worldState.Restore(new Snapshot(1, 2)); + worldState.Restore(new Snapshot(1, new Snapshot.Storage(2, 1))); stateProvider.Received().Restore(1); - storageProvider.Received().Restore(2); + storageProvider.Received().Restore(new Snapshot.Storage(2, 1)); } } } diff --git a/src/Nethermind/Nethermind.State/IStorageProvider.cs b/src/Nethermind/Nethermind.State/IStorageProvider.cs index 0c0d1ab281f..eb2f45cb5f1 100644 --- a/src/Nethermind/Nethermind.State/IStorageProvider.cs +++ b/src/Nethermind/Nethermind.State/IStorageProvider.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -18,20 +18,67 @@ namespace Nethermind.State { - public interface IStorageProvider : IJournal + /// + /// Interface for the StorageProvider + /// Includes both persistent and transient storage + /// + public interface IStorageProvider : IJournal { + /// + /// Return the original persistent storage value from the storage cell + /// + /// + /// byte[] GetOriginal(StorageCell storageCell); + /// + /// Get the persistent storage value at the specified storage cell + /// + /// Storage location + /// Value at cell byte[] Get(StorageCell storageCell); + /// + /// Set the provided value to persistent storage at the specified storage cell + /// + /// Storage location + /// Value to store void Set(StorageCell storageCell, byte[] newValue); + /// + /// Get the transient storage value at the specified storage cell + /// + /// Storage location + /// Value at cell + byte[] GetTransientState(StorageCell storageCell); + + /// + /// Set the provided value to transient storage at the specified storage cell + /// + /// Storage location + /// Value to store + void SetTransientState(StorageCell storageCell, byte[] newValue); + + /// + /// Reset all storage + /// void Reset(); + /// + /// Commit persisent storage trees + /// + /// Current block number void CommitTrees(long blockNumber); + /// + /// Commit persistent storage + /// void Commit(); + /// + /// Commit persistent storage + /// + /// State tracer void Commit(IStorageTracer stateTracer); /// @@ -43,10 +90,14 @@ public interface IStorageProvider : IJournal /// If is true and there are already changes in then next call to /// will use changes before this snapshot as original values for this new transaction. /// - int TakeSnapshot(bool newTransactionStart = false); + Snapshot.Storage TakeSnapshot(bool newTransactionStart = false); - int IJournal.TakeSnapshot() => TakeSnapshot(); + Snapshot.Storage IJournal.TakeSnapshot() => TakeSnapshot(); + /// + /// Clear all storage at specified address + /// + /// Contract address void ClearStorage(Address address); } } diff --git a/src/Nethermind/Nethermind.State/PartialStorageProviderBase.cs b/src/Nethermind/Nethermind.State/PartialStorageProviderBase.cs new file mode 100644 index 00000000000..823472d1436 --- /dev/null +++ b/src/Nethermind/Nethermind.State/PartialStorageProviderBase.cs @@ -0,0 +1,328 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . + +using System; +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Resettables; +using Nethermind.Logging; + +namespace Nethermind.State +{ + /// + /// Contains common code for both Persistent and Transient storage providers + /// + public abstract class PartialStorageProviderBase + { + protected readonly ResettableDictionary> _intraBlockCache = new(); + + protected readonly ILogger _logger; + + private const int StartCapacity = Resettable.StartCapacity; + private int _capacity = StartCapacity; + protected Change?[] _changes = new Change[StartCapacity]; + protected int _currentPosition = Resettable.EmptyPosition; + + // stack of snapshot indexes on changes for start of each transaction + // this is needed for OriginalValues for new transactions + protected readonly Stack _transactionChangesSnapshots = new(); + + protected static readonly byte[] _zeroValue = {0}; + + protected PartialStorageProviderBase(ILogManager? logManager) + { + _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + } + + /// + /// Get the storage value at the specified storage cell + /// + /// Storage location + /// Value at cell + public byte[] Get(StorageCell storageCell) + { + return GetCurrentValue(storageCell); + } + + /// + /// Set the provided value to storage at the specified storage cell + /// + /// Storage location + /// Value to store + public void Set(StorageCell storageCell, byte[] newValue) + { + PushUpdate(storageCell, newValue); + } + + /// + /// Creates a restartable snapshot. + /// + /// Indicates new transaction will start here. + /// Snapshot index + public int TakeSnapshot(bool newTransactionStart) + { + if (_logger.IsTrace) _logger.Trace($"Storage snapshot {_currentPosition}"); + if (newTransactionStart && _currentPosition != Resettable.EmptyPosition) + { + _transactionChangesSnapshots.Push(_currentPosition); + } + + return _currentPosition; + } + + /// + /// Restore the state to the provided snapshot + /// + /// Snapshot index + /// Throws exception if snapshot is invalid + public void Restore(int snapshot) + { + if (_logger.IsTrace) _logger.Trace($"Restoring storage snapshot {snapshot}"); + + if (snapshot > _currentPosition) + { + throw new InvalidOperationException($"{GetType().Name} tried to restore snapshot {snapshot} beyond current position {_currentPosition}"); + } + + if (snapshot == _currentPosition) + { + return; + } + + List keptInCache = new(); + + for (int i = 0; i < _currentPosition - snapshot; i++) + { + Change change = _changes[_currentPosition - i]; + if (_intraBlockCache[change!.StorageCell].Count == 1) + { + if (_changes[_intraBlockCache[change.StorageCell].Peek()]!.ChangeType == ChangeType.JustCache) + { + int actualPosition = _intraBlockCache[change.StorageCell].Pop(); + if (actualPosition != _currentPosition - i) + { + throw new InvalidOperationException($"Expected actual position {actualPosition} to be equal to {_currentPosition} - {i}"); + } + + keptInCache.Add(change); + _changes[actualPosition] = null; + continue; + } + } + + int forAssertion = _intraBlockCache[change.StorageCell].Pop(); + if (forAssertion != _currentPosition - i) + { + throw new InvalidOperationException($"Expected checked value {forAssertion} to be equal to {_currentPosition} - {i}"); + } + + _changes[_currentPosition - i] = null; + + if (_intraBlockCache[change.StorageCell].Count == 0) + { + _intraBlockCache.Remove(change.StorageCell); + } + } + + _currentPosition = snapshot; + foreach (Change kept in keptInCache) + { + _currentPosition++; + _changes[_currentPosition] = kept; + _intraBlockCache[kept.StorageCell].Push(_currentPosition); + } + + while (_transactionChangesSnapshots.TryPeek(out int lastOriginalSnapshot) && lastOriginalSnapshot > snapshot) + { + _transactionChangesSnapshots.Pop(); + } + + } + + /// + /// Commit persistent storage + /// + public void Commit() + { + Commit(NullStorageTracer.Instance); + } + + protected readonly struct ChangeTrace + { + public ChangeTrace(byte[]? before, byte[]? after) + { + After = after ?? _zeroValue; + Before = before ?? _zeroValue; + } + + public ChangeTrace(byte[]? after) + { + After = after ?? _zeroValue; + Before = _zeroValue; + } + + public byte[] Before { get; } + public byte[] After { get; } + } + + /// + /// Commit persistent storage + /// + /// State tracer + public void Commit(IStorageTracer tracer) + { + if (_currentPosition == Snapshot.EmptyPosition) + { + if (_logger.IsTrace) _logger.Trace("No storage changes to commit"); + } + else + { + CommitCore(tracer); + } + } + + /// + /// Called by Commit + /// Used for storage-specific logic + /// + /// Storage tracer + protected virtual void CommitCore(IStorageTracer tracer) + { + Resettable.Reset(ref _changes, ref _capacity, ref _currentPosition); + _intraBlockCache.Reset(); + _transactionChangesSnapshots.Clear(); + } + + /// + /// Reset the storage state + /// + public virtual void Reset() + { + if (_logger.IsTrace) _logger.Trace("Resetting storage"); + + _intraBlockCache.Clear(); + _transactionChangesSnapshots.Clear(); + _currentPosition = -1; + Array.Clear(_changes, 0, _changes.Length); + } + + /// + /// Attempt to get the current value at the storage cell + /// + /// Storage location + /// Resulting value + /// True if value has been set + protected bool TryGetCachedValue(StorageCell storageCell, out byte[]? bytes) + { + if (_intraBlockCache.TryGetValue(storageCell, out StackList stack)) + { + int lastChangeIndex = stack.Peek(); + { + bytes = _changes[lastChangeIndex]!.Value; + return true; + } + } + + bytes = null; + return false; + } + + /// + /// Get the current value at the specified location + /// + /// Storage location + /// Value at location + protected abstract byte[] GetCurrentValue(StorageCell storageCell); + + /// + /// Update the storage cell with provided value + /// + /// Storage location + /// Value to set + private void PushUpdate(StorageCell cell, byte[] value) + { + SetupRegistry(cell); + IncrementChangePosition(); + _intraBlockCache[cell].Push(_currentPosition); + _changes[_currentPosition] = new Change(ChangeType.Update, cell, value); + } + + /// + /// Increment position and size (if needed) of _changes + /// + protected void IncrementChangePosition() + { + Resettable.IncrementPosition(ref _changes, ref _capacity, ref _currentPosition); + } + + /// + /// Initialize the StackList at the storage cell position if needed + /// + /// + protected void SetupRegistry(StorageCell cell) + { + if (!_intraBlockCache.ContainsKey(cell)) + { + _intraBlockCache[cell] = new StackList(); + } + } + + /// + /// Clear all storage at specified address + /// + /// Contract address + public virtual void ClearStorage(Address address) + { + // We are setting cached values to zero so we do not use previously set values + // when the contract is revived with CREATE2 inside the same block + foreach (KeyValuePair> cellByAddress in _intraBlockCache) + { + if (cellByAddress.Key.Address == address) + { + Set(cellByAddress.Key, _zeroValue); + } + } + } + + /// + /// Used for tracking each change to storage + /// + protected class Change + { + public Change(ChangeType changeType, StorageCell storageCell, byte[] value) + { + StorageCell = storageCell; + Value = value; + ChangeType = changeType; + } + + public ChangeType ChangeType { get; } + public StorageCell StorageCell { get; } + public byte[] Value { get; } + } + + /// + /// Type of change to track + /// + protected enum ChangeType + { + JustCache, + Update, + Destroy, + } + } +} diff --git a/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs b/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs new file mode 100644 index 00000000000..1a61a142a77 --- /dev/null +++ b/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs @@ -0,0 +1,297 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . + +using System; +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Resettables; +using Nethermind.Logging; +using Nethermind.Trie.Pruning; + +namespace Nethermind.State +{ + /// + /// Manages persistent storage allowing for snapshotting and restoring + /// Persists data to ITrieStore + /// + public class PersistentStorageProvider : PartialStorageProviderBase + { + private readonly ITrieStore _trieStore; + private readonly IStateProvider _stateProvider; + private readonly ILogManager? _logManager; + private readonly ResettableDictionary _storages = new(); + /// + /// EIP-1283 + /// + private readonly ResettableDictionary _originalValues = new(); + private readonly ResettableHashSet _committedThisRound = new(); + + public PersistentStorageProvider(ITrieStore? trieStore, IStateProvider? stateProvider, ILogManager? logManager) + : base(logManager) + { + _trieStore = trieStore ?? throw new ArgumentNullException(nameof(trieStore)); + _stateProvider = stateProvider ?? throw new ArgumentNullException(nameof(stateProvider)); + _logManager = logManager ?? throw new ArgumentNullException(nameof(logManager)); + } + + /// + /// Reset the storage state + /// + public override void Reset() + { + base.Reset(); + _storages.Reset(); + _originalValues.Clear(); + _committedThisRound.Clear(); + } + + /// + /// Get the current value at the specified location + /// + /// Storage location + /// Value at location + protected override byte[] GetCurrentValue(StorageCell storageCell) => + TryGetCachedValue(storageCell, out byte[]? bytes) ? bytes! : LoadFromTree(storageCell); + + /// + /// Return the original persistent storage value from the storage cell + /// + /// + /// + public byte[] GetOriginal(StorageCell storageCell) + { + if (!_originalValues.ContainsKey(storageCell)) + { + throw new InvalidOperationException("Get original should only be called after get within the same caching round"); + } + + if (_transactionChangesSnapshots.TryPeek(out int snapshot)) + { + if (_intraBlockCache.TryGetValue(storageCell, out StackList stack)) + { + if (stack.TryGetSearchedItem(snapshot, out int lastChangeIndexBeforeOriginalSnapshot)) + { + return _changes[lastChangeIndexBeforeOriginalSnapshot]!.Value; + } + } + } + + return _originalValues[storageCell]; + } + + + /// + /// Called by Commit + /// Used for persistent storage specific logic + /// + /// Storage tracer + protected override void CommitCore(IStorageTracer tracer) + { + if (_logger.IsTrace) _logger.Trace("Committing storage changes"); + + if (_changes[_currentPosition] is null) + { + throw new InvalidOperationException($"Change at current position {_currentPosition} was null when commiting {nameof(PartialStorageProviderBase)}"); + } + + if (_changes[_currentPosition + 1] != null) + { + throw new InvalidOperationException($"Change after current position ({_currentPosition} + 1) was not null when commiting {nameof(PartialStorageProviderBase)}"); + } + + HashSet
toUpdateRoots = new(); + + bool isTracing = tracer.IsTracingStorage; + Dictionary? trace = null; + if (isTracing) + { + trace = new Dictionary(); + } + + for (int i = 0; i <= _currentPosition; i++) + { + Change change = _changes[_currentPosition - i]; + if (!isTracing && change!.ChangeType == ChangeType.JustCache) + { + continue; + } + + if (_committedThisRound.Contains(change!.StorageCell)) + { + if (isTracing && change.ChangeType == ChangeType.JustCache) + { + trace![change.StorageCell] = new ChangeTrace(change.Value, trace[change.StorageCell].After); + } + + continue; + } + + if (isTracing && change.ChangeType == ChangeType.JustCache) + { + tracer!.ReportStorageRead(change.StorageCell); + } + + _committedThisRound.Add(change.StorageCell); + + if (change.ChangeType == ChangeType.Destroy) + { + continue; + } + + int forAssertion = _intraBlockCache[change.StorageCell].Pop(); + if (forAssertion != _currentPosition - i) + { + throw new InvalidOperationException($"Expected checked value {forAssertion} to be equal to {_currentPosition} - {i}"); + } + + switch (change.ChangeType) + { + case ChangeType.Destroy: + break; + case ChangeType.JustCache: + break; + case ChangeType.Update: + if (_logger.IsTrace) + { + _logger.Trace($" Update {change.StorageCell.Address}_{change.StorageCell.Index} V = {change.Value.ToHexString(true)}"); + } + + StorageTree tree = GetOrCreateStorage(change.StorageCell.Address); + Db.Metrics.StorageTreeWrites++; + toUpdateRoots.Add(change.StorageCell.Address); + tree.Set(change.StorageCell.Index, change.Value); + if (isTracing) + { + trace![change.StorageCell] = new ChangeTrace(change.Value); + } + + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + // TODO: it seems that we are unnecessarily recalculating root hashes all the time in storage? + foreach (Address address in toUpdateRoots) + { + // since the accounts could be empty accounts that are removing (EIP-158) + if (_stateProvider.AccountExists(address)) + { + Keccak root = RecalculateRootHash(address); + + // _logger.Warn($"Recalculating storage root {address}->{root} ({toUpdateRoots.Count})"); + _stateProvider.UpdateStorageRoot(address, root); + } + } + + base.CommitCore(tracer); + _originalValues.Reset(); + _committedThisRound.Reset(); + + if (isTracing) + { + ReportChanges(tracer!, trace!); + } + } + + /// + /// Commit persisent storage trees + /// + /// Current block number + public void CommitTrees(long blockNumber) + { + // _logger.Warn($"Storage block commit {blockNumber}"); + foreach (KeyValuePair storage in _storages) + { + storage.Value.Commit(blockNumber); + } + + // TODO: maybe I could update storage roots only now? + + // only needed here as there is no control over cached storage size otherwise + _storages.Reset(); + } + + private StorageTree GetOrCreateStorage(Address address) + { + if (!_storages.ContainsKey(address)) + { + StorageTree storageTree = new(_trieStore, _stateProvider.GetStorageRoot(address), _logManager); + return _storages[address] = storageTree; + } + + return _storages[address]; + } + + private byte[] LoadFromTree(StorageCell storageCell) + { + StorageTree tree = GetOrCreateStorage(storageCell.Address); + + Db.Metrics.StorageTreeReads++; + byte[] value = tree.Get(storageCell.Index); + PushToRegistryOnly(storageCell, value); + return value; + } + + private void PushToRegistryOnly(StorageCell cell, byte[] value) + { + SetupRegistry(cell); + IncrementChangePosition(); + _intraBlockCache[cell].Push(_currentPosition); + _originalValues[cell] = value; + _changes[_currentPosition] = new Change(ChangeType.JustCache, cell, value); + } + + private static void ReportChanges(IStorageTracer tracer, Dictionary trace) + { + foreach ((StorageCell address, ChangeTrace change) in trace) + { + byte[] before = change.Before; + byte[] after = change.After; + + if (!Bytes.AreEqual(before, after)) + { + tracer.ReportStorageChange(address, before, after); + } + } + } + + private Keccak RecalculateRootHash(Address address) + { + StorageTree storageTree = GetOrCreateStorage(address); + storageTree.UpdateRootHash(); + return storageTree.RootHash; + } + + /// + /// Clear all storage at specified address + /// + /// Contract address + public override void ClearStorage(Address address) + { + base.ClearStorage(address); + + // here it is important to make sure that we will not reuse the same tree when the contract is revived + // by means of CREATE 2 - notice that the cached trie may carry information about items that were not + // touched in this block, hence were not zeroed above + // TODO: how does it work with pruning? + _storages[address] = new StorageTree(_trieStore, Keccak.EmptyTreeHash, _logManager); + } + } +} diff --git a/src/Nethermind/Nethermind.State/Snapshot.cs b/src/Nethermind/Nethermind.State/Snapshot.cs index 11945148fa2..a6429daef46 100644 --- a/src/Nethermind/Nethermind.State/Snapshot.cs +++ b/src/Nethermind/Nethermind.State/Snapshot.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -26,15 +26,33 @@ namespace Nethermind.State ///
public readonly struct Snapshot { - public Snapshot(int stateSnapshot, int storageSnapshot) + public static readonly Snapshot Empty = new(EmptyPosition, Storage.Empty); + + public Snapshot(int stateSnapshot, Storage storageSnapshot) { StateSnapshot = stateSnapshot; StorageSnapshot = storageSnapshot; } + + /// + /// Tracks snapshot positions for Persistent and Transient storage + /// + public readonly struct Storage + { + public static readonly Storage Empty = new(EmptyPosition, EmptyPosition); + + public int PersistentStorageSnapshot { get; } + public int TransientStorageSnapshot { get; } + + public Storage(int storageSnapshot, int transientStorageSnapshot) + { + PersistentStorageSnapshot = storageSnapshot; + TransientStorageSnapshot = transientStorageSnapshot; + } + } public int StateSnapshot { get; } - - public int StorageSnapshot { get; } + public Storage StorageSnapshot { get; } public const int EmptyPosition = -1; } diff --git a/src/Nethermind/Nethermind.State/StorageProvider.cs b/src/Nethermind/Nethermind.State/StorageProvider.cs index 387ca0f6bed..ace96841820 100644 --- a/src/Nethermind/Nethermind.State/StorageProvider.cs +++ b/src/Nethermind/Nethermind.State/StorageProvider.cs @@ -14,13 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the Nethermind. If not, see . -using System; -using System.Collections.Generic; using Nethermind.Core; -using Nethermind.Core.Collections; -using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; -using Nethermind.Core.Resettables; using Nethermind.Logging; using Nethermind.Trie.Pruning; @@ -28,436 +22,90 @@ namespace Nethermind.State { public class StorageProvider : IStorageProvider { - private readonly ResettableDictionary> _intraBlockCache = new(); - - /// - /// EIP-1283 - /// - private readonly ResettableDictionary _originalValues = new(); - - private readonly ResettableHashSet _committedThisRound = new(); - - private readonly ILogger _logger; - - private readonly ITrieStore _trieStore; - - private readonly IStateProvider _stateProvider; - private readonly ILogManager _logManager; - - private readonly ResettableDictionary _storages = new(); - - private const int StartCapacity = Resettable.StartCapacity; - private int _capacity = StartCapacity; - private Change?[] _changes = new Change[StartCapacity]; - private int _currentPosition = Resettable.EmptyPosition; - - // stack of snapshot indexes on changes for start of each transaction - // this is needed for OriginalValues for new transactions - private readonly Stack _transactionChangesSnapshots = new(); + PersistentStorageProvider _persistentStorageProvider; + TransientStorageProvider _transientStorageProvider; public StorageProvider(ITrieStore? trieStore, IStateProvider? stateProvider, ILogManager? logManager) { - _logManager = logManager ?? throw new ArgumentNullException(nameof(logManager)); - _logger = logManager.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - _trieStore = trieStore ?? throw new ArgumentNullException(nameof(trieStore)); - _stateProvider = stateProvider ?? throw new ArgumentNullException(nameof(stateProvider)); - } - - public byte[] GetOriginal(StorageCell storageCell) - { - if (!_originalValues.ContainsKey(storageCell)) - { - throw new InvalidOperationException("Get original should only be called after get within the same caching round"); - } - - if (_transactionChangesSnapshots.TryPeek(out int snapshot)) - { - if (_intraBlockCache.TryGetValue(storageCell, out StackList stack)) - { - if (stack.TryGetSearchedItem(snapshot, out int lastChangeIndexBeforeOriginalSnapshot)) - { - return _changes[lastChangeIndexBeforeOriginalSnapshot]!.Value; - } - } - } - - return _originalValues[storageCell]; - } - - public byte[] Get(StorageCell storageCell) - { - return GetCurrentValue(storageCell); - } - - public void Set(StorageCell storageCell, byte[] newValue) - { - PushUpdate(storageCell, newValue); - } - - private Keccak RecalculateRootHash(Address address) - { - StorageTree storageTree = GetOrCreateStorage(address); - storageTree.UpdateRootHash(); - return storageTree.RootHash; + _persistentStorageProvider = new PersistentStorageProvider(trieStore, stateProvider, logManager); + _transientStorageProvider = new TransientStorageProvider(logManager); } - int IStorageProvider.TakeSnapshot(bool newTransactionStart) - { - if (_logger.IsTrace) _logger.Trace($"Storage snapshot {_currentPosition}"); - if (newTransactionStart && _currentPosition != Resettable.EmptyPosition) - { - _transactionChangesSnapshots.Push(_currentPosition); - } - return _currentPosition; - } - - public void Restore(int snapshot) + public void ClearStorage(Address address) { - if (_logger.IsTrace) _logger.Trace($"Restoring storage snapshot {snapshot}"); - - if (snapshot > _currentPosition) - { - throw new InvalidOperationException($"{nameof(StorageProvider)} tried to restore snapshot {snapshot} beyond current position {_currentPosition}"); - } - - if (snapshot == _currentPosition) - { - return; - } - - List keptInCache = new(); - - for (int i = 0; i < _currentPosition - snapshot; i++) - { - Change change = _changes[_currentPosition - i]; - if (_intraBlockCache[change!.StorageCell].Count == 1) - { - if (_changes[_intraBlockCache[change.StorageCell].Peek()]!.ChangeType == ChangeType.JustCache) - { - int actualPosition = _intraBlockCache[change.StorageCell].Pop(); - if (actualPosition != _currentPosition - i) - { - throw new InvalidOperationException($"Expected actual position {actualPosition} to be equal to {_currentPosition} - {i}"); - } - - keptInCache.Add(change); - _changes[actualPosition] = null; - continue; - } - } - - int forAssertion = _intraBlockCache[change.StorageCell].Pop(); - if (forAssertion != _currentPosition - i) - { - throw new InvalidOperationException($"Expected checked value {forAssertion} to be equal to {_currentPosition} - {i}"); - } - - _changes[_currentPosition - i] = null; - - if (_intraBlockCache[change.StorageCell].Count == 0) - { - _intraBlockCache.Remove(change.StorageCell); - } - } - - _currentPosition = snapshot; - foreach (Change kept in keptInCache) - { - _currentPosition++; - _changes[_currentPosition] = kept; - _intraBlockCache[kept.StorageCell].Push(_currentPosition); - } - - while (_transactionChangesSnapshots.TryPeek(out int lastOriginalSnapshot) && lastOriginalSnapshot > snapshot) - { - _transactionChangesSnapshots.Pop(); - } - + _persistentStorageProvider.ClearStorage(address); + _transientStorageProvider.ClearStorage(address); } public void Commit() { - Commit(NullStorageTracer.Instance); + _persistentStorageProvider.Commit(); + _transientStorageProvider.Commit(); } - private static readonly byte[] _zeroValue = {0}; - - private readonly struct ChangeTrace + public void Commit(IStorageTracer stateTracer) { - public ChangeTrace(byte[]? before, byte[]? after) - { - After = after ?? _zeroValue; - Before = before ?? _zeroValue; - } - - public ChangeTrace(byte[]? after) - { - After = after ?? _zeroValue; - Before = _zeroValue; - } - - public byte[] Before { get; } - public byte[] After { get; } - } - - public void Commit(IStorageTracer tracer) - { - if (_currentPosition == -1) - { - if (_logger.IsTrace) _logger.Trace("No storage changes to commit"); - return; - } - - if (_logger.IsTrace) _logger.Trace("Committing storage changes"); - - if (_changes[_currentPosition] is null) - { - throw new InvalidOperationException($"Change at current position {_currentPosition} was null when commiting {nameof(StorageProvider)}"); - } - - if (_changes[_currentPosition + 1] != null) - { - throw new InvalidOperationException($"Change after current position ({_currentPosition} + 1) was not null when commiting {nameof(StorageProvider)}"); - } - - HashSet
toUpdateRoots = new(); - - bool isTracing = tracer.IsTracingStorage; - Dictionary? trace = null; - if (isTracing) - { - trace = new Dictionary(); - } - - for (int i = 0; i <= _currentPosition; i++) - { - Change change = _changes[_currentPosition - i]; - if (!isTracing && change!.ChangeType == ChangeType.JustCache) - { - continue; - } - - if (_committedThisRound.Contains(change!.StorageCell)) - { - if (isTracing && change.ChangeType == ChangeType.JustCache) - { - trace![change.StorageCell] = new ChangeTrace(change.Value, trace[change.StorageCell].After); - } - - continue; - } - - if (isTracing && change.ChangeType == ChangeType.JustCache) - { - tracer!.ReportStorageRead(change.StorageCell); - } - - _committedThisRound.Add(change.StorageCell); - - if (change.ChangeType == ChangeType.Destroy) - { - continue; - } - - int forAssertion = _intraBlockCache[change.StorageCell].Pop(); - if (forAssertion != _currentPosition - i) - { - throw new InvalidOperationException($"Expected checked value {forAssertion} to be equal to {_currentPosition} - {i}"); - } - - switch (change.ChangeType) - { - case ChangeType.Destroy: - break; - case ChangeType.JustCache: - break; - case ChangeType.Update: - if (_logger.IsTrace) - { - _logger.Trace($" Update {change.StorageCell.Address}_{change.StorageCell.Index} V = {change.Value.ToHexString(true)}"); - } - - StorageTree tree = GetOrCreateStorage(change.StorageCell.Address); - Db.Metrics.StorageTreeWrites++; - toUpdateRoots.Add(change.StorageCell.Address); - tree.Set(change.StorageCell.Index, change.Value); - if (isTracing) - { - trace![change.StorageCell] = new ChangeTrace(change.Value); - } - - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - // TODO: it seems that we are unnecessarily recalculating root hashes all the time in storage? - foreach (Address address in toUpdateRoots) - { - // since the accounts could be empty accounts that are removing (EIP-158) - if (_stateProvider.AccountExists(address)) - { - Keccak root = RecalculateRootHash(address); - - // _logger.Warn($"Recalculating storage root {address}->{root} ({toUpdateRoots.Count})"); - _stateProvider.UpdateStorageRoot(address, root); - } - } - - Resettable.Reset(ref _changes, ref _capacity, ref _currentPosition, StartCapacity); - _committedThisRound.Reset(); - _intraBlockCache.Reset(); - _originalValues.Reset(); - _transactionChangesSnapshots.Clear(); - - if (isTracing) - { - ReportChanges(tracer!, trace!); - } - } - - private static void ReportChanges(IStorageTracer tracer, Dictionary trace) - { - foreach ((StorageCell address, ChangeTrace change) in trace) - { - byte[] before = change.Before; - byte[] after = change.After; - - if (!Bytes.AreEqual(before, after)) - { - tracer.ReportStorageChange(address, before, after); - } - } - } - - public void Reset() - { - if (_logger.IsTrace) _logger.Trace("Resetting storage"); - - _intraBlockCache.Clear(); - _originalValues.Clear(); - _transactionChangesSnapshots.Clear(); - _currentPosition = -1; - _committedThisRound.Clear(); - Array.Clear(_changes, 0, _changes.Length); - _storages.Reset(); + _persistentStorageProvider.Commit(stateTracer); + _transientStorageProvider.Commit(stateTracer); } public void CommitTrees(long blockNumber) { - // _logger.Warn($"Storage block commit {blockNumber}"); - foreach (KeyValuePair storage in _storages) - { - storage.Value.Commit(blockNumber); - } - - // TODO: maybe I could update storage roots only now? - - // only needed here as there is no control over cached storage size otherwise - _storages.Reset(); + _persistentStorageProvider.CommitTrees(blockNumber); } - private StorageTree GetOrCreateStorage(Address address) + public byte[] Get(StorageCell storageCell) { - if (!_storages.ContainsKey(address)) - { - StorageTree storageTree = new(_trieStore, _stateProvider.GetStorageRoot(address), _logManager); - return _storages[address] = storageTree; - } - - return _storages[address]; + return _persistentStorageProvider.Get(storageCell); } - private byte[] GetCurrentValue(StorageCell storageCell) + public byte[] GetOriginal(StorageCell storageCell) { - if (_intraBlockCache.TryGetValue(storageCell, out StackList stack)) - { - int lastChangeIndex = stack.Peek(); - return _changes[lastChangeIndex]!.Value; - } - - return LoadFromTree(storageCell); + return _persistentStorageProvider.GetOriginal(storageCell); } - private byte[] LoadFromTree(StorageCell storageCell) + public byte[] GetTransientState(StorageCell storageCell) { - StorageTree tree = GetOrCreateStorage(storageCell.Address); - - Db.Metrics.StorageTreeReads++; - byte[] value = tree.Get(storageCell.Index); - PushToRegistryOnly(storageCell, value); - return value; + return _transientStorageProvider.Get(storageCell); } - private void PushToRegistryOnly(StorageCell cell, byte[] value) + public void Reset() { - SetupRegistry(cell); - IncrementChangePosition(); - _intraBlockCache[cell].Push(_currentPosition); - _originalValues[cell] = value; - _changes[_currentPosition] = new Change(ChangeType.JustCache, cell, value); + _persistentStorageProvider.Reset(); + _transientStorageProvider.Reset(); } - private void PushUpdate(StorageCell cell, byte[] value) + /// + /// Convenience for test cases + /// + /// + internal void Restore(int snapshot) { - SetupRegistry(cell); - IncrementChangePosition(); - _intraBlockCache[cell].Push(_currentPosition); - _changes[_currentPosition] = new Change(ChangeType.Update, cell, value); + Restore(new Snapshot.Storage(snapshot, Snapshot.EmptyPosition)); } - private void IncrementChangePosition() + public void Restore(Snapshot.Storage snapshot) { - Resettable.IncrementPosition(ref _changes, ref _capacity, ref _currentPosition); + _persistentStorageProvider.Restore(snapshot.PersistentStorageSnapshot); + _transientStorageProvider.Restore(snapshot.TransientStorageSnapshot); } - private void SetupRegistry(StorageCell cell) + public void Set(StorageCell storageCell, byte[] newValue) { - if (!_intraBlockCache.ContainsKey(cell)) - { - _intraBlockCache[cell] = new StackList(); - } + _persistentStorageProvider.Set(storageCell, newValue); } - private class Change + public void SetTransientState(StorageCell storageCell, byte[] newValue) { - public Change(ChangeType changeType, StorageCell storageCell, byte[] value) - { - StorageCell = storageCell; - Value = value; - ChangeType = changeType; - } - - public ChangeType ChangeType { get; } - public StorageCell StorageCell { get; } - public byte[] Value { get; } + _transientStorageProvider.Set(storageCell, newValue); } - public void ClearStorage(Address address) + Snapshot.Storage IStorageProvider.TakeSnapshot(bool newTransactionStart) { - /* we are setting cached values to zero so we do not use previously set values - when the contract is revived with CREATE2 inside the same block */ - foreach (var cellByAddress in _intraBlockCache) - { - if (cellByAddress.Key.Address == address) - { - Set(cellByAddress.Key, _zeroValue); - } - } + int persistentSnapshot = _persistentStorageProvider.TakeSnapshot(newTransactionStart); + int transientSnapshot = _transientStorageProvider.TakeSnapshot(newTransactionStart); - /* here it is important to make sure that we will not reuse the same tree when the contract is revived - by means of CREATE 2 - notice that the cached trie may carry information about items that were not - touched in this block, hence were not zeroed above */ - // TODO: how does it work with pruning? - _storages[address] = new StorageTree(_trieStore, Keccak.EmptyTreeHash, _logManager); - } - - private enum ChangeType - { - JustCache, - Update, - Destroy, + return new Snapshot.Storage(persistentSnapshot, transientSnapshot); } } } diff --git a/src/Nethermind/Nethermind.State/TransientStorageProvider.cs b/src/Nethermind/Nethermind.State/TransientStorageProvider.cs new file mode 100644 index 00000000000..73cdd945f7f --- /dev/null +++ b/src/Nethermind/Nethermind.State/TransientStorageProvider.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . + +using Nethermind.Core; +using Nethermind.Logging; + +namespace Nethermind.State +{ + /// + /// EIP-1153 provides a transient store for contracts that doesn't persist + /// storage across calls. Reverts will rollback any transient state changes. + /// + public class TransientStorageProvider : PartialStorageProviderBase + { + public TransientStorageProvider(ILogManager? logManager) + : base(logManager) { } + + /// + /// Get the storage value at the specified storage cell + /// + /// Storage location + /// Value at cell + protected override byte[] GetCurrentValue(StorageCell storageCell) => + TryGetCachedValue(storageCell, out byte[]? bytes) ? bytes! : _zeroValue; + } +} diff --git a/src/Nethermind/Nethermind.State/WorldState.cs b/src/Nethermind/Nethermind.State/WorldState.cs index 628a25c9f36..37ab5b1d0e2 100644 --- a/src/Nethermind/Nethermind.State/WorldState.cs +++ b/src/Nethermind/Nethermind.State/WorldState.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. // // The Nethermind library is free software: you can redistribute it and/or modify @@ -25,7 +25,8 @@ public class WorldState : IWorldState public Snapshot TakeSnapshot(bool newTransactionStart = false) { - return new (StateProvider.TakeSnapshot(), StorageProvider.TakeSnapshot(newTransactionStart)); + Snapshot.Storage storageSnapshot = StorageProvider.TakeSnapshot(newTransactionStart); + return new (StateProvider.TakeSnapshot(), storageSnapshot); } public void Restore(Snapshot snapshot)