diff --git a/.github/workflows/build-tools.yml b/.github/workflows/build-tools.yml new file mode 100644 index 00000000000..1f25cb17a86 --- /dev/null +++ b/.github/workflows/build-tools.yml @@ -0,0 +1,33 @@ +name: Build tools + +on: + merge_group: + types: [checks_requested] + pull_request: + branches: [master] + push: + branches: [master] + +jobs: + build: + name: Build + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + config: [release] + project: + - DocGen/DocGen.sln + - HiveCompare/HiveCompare.sln + - HiveConsensusWorkflowGenerator/HiveConsensusWorkflowGenerator.csproj + - Nethermind.Tools.Kute/Nethermind.Tools.Kute.csproj + - SendBlobs/SendBlobs.sln + - TxParser/TxParser.csproj + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Set up .NET + uses: actions/setup-dotnet@v4 + - name: Build ${{ matrix.project }} + working-directory: tools + run: dotnet build ./${{ matrix.project }} -c ${{ matrix.config }} diff --git a/.github/workflows/nethermind-tests.yml b/.github/workflows/nethermind-tests.yml index b95d724b9ed..c5b1ee08652 100644 --- a/.github/workflows/nethermind-tests.yml +++ b/.github/workflows/nethermind-tests.yml @@ -64,6 +64,7 @@ jobs: - Nethermind.Overseer.Test - Nethermind.Runner.Test - Nethermind.Serialization.Ssz.Test + - Nethermind.Shutter.Test - Nethermind.Sockets.Test - Nethermind.Specs.Test - Nethermind.State.Test diff --git a/.github/workflows/sync-supported-chains.yml b/.github/workflows/sync-supported-chains.yml index 0d8363e0053..3ca8427a262 100644 --- a/.github/workflows/sync-supported-chains.yml +++ b/.github/workflows/sync-supported-chains.yml @@ -85,7 +85,7 @@ jobs: - name: Setup Go environment uses: actions/setup-go@v5 with: - go-version: '1.21.0' + go-version: 'stable' check-latest: true cache: true diff --git a/.github/workflows/update-docs.yml b/.github/workflows/update-docs.yml index e988db3e551..2d7ed8126a1 100644 --- a/.github/workflows/update-docs.yml +++ b/.github/workflows/update-docs.yml @@ -41,15 +41,15 @@ jobs: global-json-file: n/global.json - name: Build DocGen working-directory: n - run: dotnet build tools/docgen/DocGen.csproj -c release -o docgen + run: dotnet build tools/DocGen/DocGen.csproj -c release -o DocGen - name: Generate docs - run: n/docgen/DocGen $GITHUB_WORKSPACE/d --config --jsonrpc --metrics + run: n/DocGen/DocGen $GITHUB_WORKSPACE/d --config --jsonrpc --metrics - name: Tag a new version if: github.event_name == 'release' && !github.event.release.prerelease working-directory: d run: | npm i - npm run docusaurus docs:version v${{ github.event.release.tag_name }} + npm run docusaurus docs:version ${{ github.event.release.tag_name }} - name: Create a pull request working-directory: d env: diff --git a/src/Nethermind/Chains/base-mainnet.json b/src/Nethermind/Chains/base-mainnet.json index 1f34134952f..3098cc81864 100644 --- a/src/Nethermind/Chains/base-mainnet.json +++ b/src/Nethermind/Chains/base-mainnet.json @@ -47,7 +47,7 @@ "eip2930Transition": "0x0", "eip1559Transition": "0x0", "eip1559FeeCollectorTransition": "0x0", - "eip1559FeeCollector": "0x4200000000000000000000000000000000000019", + "feeCollector": "0x4200000000000000000000000000000000000019", "eip1559ElasticityMultiplier": "0x6", "eip1559BaseFeeMaxChangeDenominator": "0x32", "eip3198Transition": "0x0", diff --git a/src/Nethermind/Chains/base-sepolia.json b/src/Nethermind/Chains/base-sepolia.json index 79bcfc6c91d..1a682af75d0 100644 --- a/src/Nethermind/Chains/base-sepolia.json +++ b/src/Nethermind/Chains/base-sepolia.json @@ -47,7 +47,7 @@ "eip2930Transition": "0x0", "eip1559Transition": "0x0", "eip1559FeeCollectorTransition": "0x0", - "eip1559FeeCollector": "0x4200000000000000000000000000000000000019", + "feeCollector": "0x4200000000000000000000000000000000000019", "eip1559ElasticityMultiplier": "0xa", "eip1559BaseFeeMaxChangeDenominator": "0x32", "eip3198Transition": "0x0", diff --git a/src/Nethermind/Chains/chiado.json b/src/Nethermind/Chains/chiado.json index 5a61c9ef01e..4aae438e2d6 100644 --- a/src/Nethermind/Chains/chiado.json +++ b/src/Nethermind/Chains/chiado.json @@ -64,6 +64,7 @@ "eip3529Transition": "0x0", "eip3541Transition": "0x0", "eip1559Transition": "0x0", + "beaconChainGenesisTimestamp": "0x6343ee4c", "eip3651TransitionTimestamp": "0x646e0e4c", "eip3855TransitionTimestamp": "0x646e0e4c", "eip3860TransitionTimestamp": "0x646e0e4c", @@ -75,7 +76,7 @@ "eip6780TransitionTimestamp": "0x65ba8e4c", "eip1559BaseFeeMaxChangeDenominator": "0x8", "eip1559ElasticityMultiplier": "0x2", - "eip1559FeeCollector": "0x1559000000000000000000000000000000000000", + "feeCollector": "0x1559000000000000000000000000000000000000", "eip1559FeeCollectorTransition": 0, "eip4844BlobGasPriceUpdateFraction": "0x10fafa", "eip4844MaxBlobGasPerBlock": "0x40000", diff --git a/src/Nethermind/Chains/foundation.json b/src/Nethermind/Chains/foundation.json index 674e828bc20..aeff1d38644 100644 --- a/src/Nethermind/Chains/foundation.json +++ b/src/Nethermind/Chains/foundation.json @@ -181,6 +181,7 @@ "eip3198Transition": "0xC5D488", "eip3529Transition": "0xC5D488", "eip3541Transition": "0xC5D488", + "beaconChainGenesisTimestamp": "0x5fc63057", "eip3651TransitionTimestamp": "0x64373057", "eip3855TransitionTimestamp": "0x64373057", "eip3860TransitionTimestamp": "0x64373057", diff --git a/src/Nethermind/Chains/gnosis.json b/src/Nethermind/Chains/gnosis.json index 99da29a9b63..c316f9fcf3d 100644 --- a/src/Nethermind/Chains/gnosis.json +++ b/src/Nethermind/Chains/gnosis.json @@ -70,6 +70,7 @@ "eip3529Transition": 19040000, "eip3541Transition": 19040000, "eip1559Transition": 19040000, + "beaconChainGenesisTimestamp": "0x61b10dbc", "eip3651TransitionTimestamp": "0x64c8edbc", "eip3855TransitionTimestamp": "0x64c8edbc", "eip3860TransitionTimestamp": "0x64c8edbc", @@ -82,7 +83,7 @@ "eip1559BaseFeeMaxChangeDenominator": "0x8", "eip1559ElasticityMultiplier": "0x2", "eip1559BaseFeeInitialValue": "0x3b9aca00", - "eip1559FeeCollector": "0x6BBe78ee9e474842Dbd4AB4987b3CeFE88426A92", + "feeCollector": "0x6BBe78ee9e474842Dbd4AB4987b3CeFE88426A92", "eip1559FeeCollectorTransition": 19040000, "eip4844BlobGasPriceUpdateFraction": "0x10fafa", "eip4844MaxBlobGasPerBlock": "0x40000", diff --git a/src/Nethermind/Chains/hive.json b/src/Nethermind/Chains/hive.json index 805c6aaa175..0e0d1f800a6 100644 --- a/src/Nethermind/Chains/hive.json +++ b/src/Nethermind/Chains/hive.json @@ -26,7 +26,7 @@ "eip155Transition": "0x0", "maxCodeSizeTransition": "0x0", "maxCodeSize": 24576, - "maximumExtraDataSize": 102400, + "maximumExtraDataSize": "0x400", "eip140Transition": "0x0", "eip211Transition": "0x0", "eip214Transition": "0x0", @@ -51,57 +51,45 @@ "eip3529Transition": "0x0", "eip3541Transition": "0x0", "eip3198Transition": "0x0", - "MergeForkIdTransition": "0x64", - "networkID": "0x7", - "chainID": "0x7" + "eip3651TransitionTimestamp": "0x0", + "eip3855TransitionTimestamp": "0x0", + "eip3860TransitionTimestamp": "0x0", + "eip4895TransitionTimestamp": "0x0", + "eip4844TransitionTimestamp": "0x0", + "eip4788TransitionTimestamp": "0x0", + "eip1153TransitionTimestamp": "0x0", + "eip5656TransitionTimestamp": "0x0", + "eip6780TransitionTimestamp": "0x0", + "eip7702TransitionTimestamp": "0x0", + "chainID": "0x1" }, "genesis": { "seal": { "ethereum": { - "nonce": "0x0000000000000000" + "nonce": "0x0000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, - "difficulty": "0x30000", + "difficulty": "0x00", "author": "0x0000000000000000000000000000000000000000", - "timestamp": "0x1234", - "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000658bdf435d810c91414ec09147daa6db624063790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x2fefd8" + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x00", + "gasLimit": "0x016345785d8a0000", + "baseFeePerGas": "0x07" }, "accounts": { - "0xcf49fda3be353c69b41ed96333cd24302da4556f": { - "balance": "0x123450000000000000000" - }, - "0x0161e041aad467a890839d5b08b138c1e6373072": { - "balance": "0x123450000000000000000" - }, - "0x87da6a8c6e9eff15d703fc2773e32f6af8dbe301": { - "balance": "0x123450000000000000000" - }, - "0xb97de4b8c857e4f6bc354f226dc3249aaee49209": { - "balance": "0x123450000000000000000" - }, - "0xc5065c9eeebe6df2c2284d046bfc906501846c51": { - "balance": "0x123450000000000000000" - }, - "0x0000000000000000000000000000000000000314": { - "balance": "0x0", - "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a223e05d1461006a578063abd1a0cf1461008d578063abfced1d146100d4578063e05c914a14610110578063e6768b451461014c575b610000565b346100005761007761019d565b6040518082815260200191505060405180910390f35b34610000576100be600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101a3565b6040518082815260200191505060405180910390f35b346100005761010e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101ed565b005b346100005761014a600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610236565b005b346100005761017960048080359060200190919080359060200190919080359060200190919050506103c4565b60405180848152602001838152602001828152602001935050505060405180910390f35b60005481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5050565b7f6031a8d62d7c95988fa262657cd92107d90ed96e08d8f867d32f26edfe85502260405180905060405180910390a17f47e2689743f14e97f7dcfa5eec10ba1dff02f83b3d1d4b9c07b206cbbda66450826040518082815260200191505060405180910390a1817fa48a6b249a5084126c3da369fbc9b16827ead8cb5cdc094b717d3f1dcd995e2960405180905060405180910390a27f7890603b316f3509577afd111710f9ebeefa15e12f72347d9dffd0d65ae3bade81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18073ffffffffffffffffffffffffffffffffffffffff167f7efef9ea3f60ddc038e50cccec621f86a0195894dc0520482abf8b5c6b659e4160405180905060405180910390a28181604051808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a05b5050565b6000600060008585859250925092505b935093509390505600a165627a7a72305820aaf842d0d0c35c45622c5263cbb54813d2974d3999c8c38551d7c613ea2bc1170029", - "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000000": "0x1234", - "0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9": "0x01" - } - }, - "0x0000000000000000000000000000000000000315": { - "balance": "0x9999999999999999999999999999999", - "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef2769ca1461003e575b610000565b3461000057610078600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061007a565b005b8173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f1935050505015610106578173ffffffffffffffffffffffffffffffffffffffff167f30a3c50752f2552dcc2b93f5b96866280816a986c0c0408cb6778b9fa198288f826040518082815260200191505060405180910390a25b5b50505600a165627a7a72305820637991fabcc8abad4294bf2bb615db78fbec4edff1635a2647d3894e2daf6a610029" - }, - "0x0000000000000000000000000000000000000316": { - "balance": "0x0", - "code": "0x444355" - }, - "0x0000000000000000000000000000000000000317": { - "balance": "0x0", - "code": "0x600160003555" + "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": { + "nonce": "0x01", + "balance": "0x00", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", + "storage": {} + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "nonce": "0x00", + "balance": "0x1d6329f1c35ca4bfabb9f5610000000000", + "code": "0x", + "storage": {} }, "0x0000000000000000000000000000000000000001": { "builtin": { @@ -206,4 +194,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/Nethermind/Chains/holesky.json b/src/Nethermind/Chains/holesky.json index a5effdc1d96..5ca2e40da40 100644 --- a/src/Nethermind/Chains/holesky.json +++ b/src/Nethermind/Chains/holesky.json @@ -35,6 +35,7 @@ "eip3198Transition": "0x0", "eip3529Transition": "0x0", "eip3541Transition": "0x0", + "beaconChainGenesisTimestamp": "0x65156994", "eip3651TransitionTimestamp": "0x6516eac0", "eip3855TransitionTimestamp": "0x6516eac0", "eip3860TransitionTimestamp": "0x6516eac0", diff --git a/src/Nethermind/Chains/op-mainnet.json b/src/Nethermind/Chains/op-mainnet.json index dd908b17302..ee17aa826f7 100644 --- a/src/Nethermind/Chains/op-mainnet.json +++ b/src/Nethermind/Chains/op-mainnet.json @@ -52,7 +52,7 @@ "eip2930Transition": "0x3C45B0", "eip1559Transition": "0x645C277", "eip1559FeeCollectorTransition": "0x645C277", - "eip1559FeeCollector": "0x4200000000000000000000000000000000000019", + "feeCollector": "0x4200000000000000000000000000000000000019", "eip1559ElasticityMultiplier": "0x6", "eip1559BaseFeeMaxChangeDenominator": "0x32", "eip3198Transition": "0x645C277", diff --git a/src/Nethermind/Chains/op-sepolia.json b/src/Nethermind/Chains/op-sepolia.json index d1b9cefab31..038e570bdb1 100644 --- a/src/Nethermind/Chains/op-sepolia.json +++ b/src/Nethermind/Chains/op-sepolia.json @@ -47,7 +47,7 @@ "eip2930Transition": "0x0", "eip1559Transition": "0x0", "eip1559FeeCollectorTransition": "0x0", - "eip1559FeeCollector": "0x4200000000000000000000000000000000000019", + "feeCollector": "0x4200000000000000000000000000000000000019", "eip1559ElasticityMultiplier": "0x6", "eip1559BaseFeeMaxChangeDenominator": "0x32", "eip3198Transition": "0x0", diff --git a/src/Nethermind/Chains/poacore.json b/src/Nethermind/Chains/poacore.json index 6f9c9760d52..88cf6a7cb28 100644 --- a/src/Nethermind/Chains/poacore.json +++ b/src/Nethermind/Chains/poacore.json @@ -59,7 +59,7 @@ "eip1559BaseFeeInitialValue": "0x3b9aca00", "eip1559BaseFeeMinValue": "0x2540be400", "eip1559BaseFeeMinValueTransition": 24199500, - "eip1559FeeCollector": "0x517F3AcfF3aFC2fb45e574718bca6F919b798e10", + "feeCollector": "0x517F3AcfF3aFC2fb45e574718bca6F919b798e10", "eip1559FeeCollectorTransition": 24090200 }, "genesis": { diff --git a/src/Nethermind/Chains/sepolia.json b/src/Nethermind/Chains/sepolia.json index 957fdfb0f01..3fa415123d4 100644 --- a/src/Nethermind/Chains/sepolia.json +++ b/src/Nethermind/Chains/sepolia.json @@ -57,6 +57,7 @@ "eip3541Transition": "0x0", "terminalTotalDifficulty": "3C6568F12E8000", "mergeForkIdTransition": "0x1A7ACB", + "beaconChainGenesisTimestamp": "0x62b07d60", "eip4895TransitionTimestamp": "0x63FD7D60", "eip3855TransitionTimestamp": "0x63FD7D60", "eip3651TransitionTimestamp": "0x63FD7D60", diff --git a/src/Nethermind/Directory.Packages.props b/src/Nethermind/Directory.Packages.props index 99a2579947a..4b568b8184d 100644 --- a/src/Nethermind/Directory.Packages.props +++ b/src/Nethermind/Directory.Packages.props @@ -19,8 +19,8 @@ - - + + @@ -44,13 +44,14 @@ - + + diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueBlockChainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueBlockChainTests.cs new file mode 100644 index 00000000000..2474c0cfa2b --- /dev/null +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueBlockChainTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Blockchain.Pyspec.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +[Explicit("These tests are not ready yet")] +public class PragueBlockChainTests : BlockchainTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public async Task Test(BlockchainTest test) => await RunTest(test); + + private static IEnumerable LoadTests() + { + TestsSourceLoader loader = new(new LoadPyspecTestsStrategy(), $"fixtures/blockchain_tests/prague"); + return loader.LoadTests().OfType(); + } +} diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueStateTests.cs new file mode 100644 index 00000000000..107b4e249f1 --- /dev/null +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueStateTests.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Linq; +using Ethereum.Test.Base; +using FluentAssertions; +using NUnit.Framework; + +namespace Ethereum.Blockchain.Pyspec.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +[Explicit("These tests are not ready yet")] +public class PragueStateTests : GeneralStateTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) => RunTest(test).Pass.Should().BeTrue(); + + private static IEnumerable LoadTests() + { + TestsSourceLoader loader = new(new LoadPyspecTestsStrategy(), $"fixtures/state_tests/prague"); + return loader.LoadTests().Cast(); + } +} diff --git a/src/Nethermind/Ethereum.Test.Base/AuthorizationListJson.cs b/src/Nethermind/Ethereum.Test.Base/AuthorizationListJson.cs new file mode 100644 index 00000000000..a2b8b6cfcbc --- /dev/null +++ b/src/Nethermind/Ethereum.Test.Base/AuthorizationListJson.cs @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Ethereum.Test.Base; +public class AuthorizationListJson +{ + public ulong ChainId { get; set; } + public Address Address { get; set; } + public ulong Nonce { get; set; } + public ulong V { get; set; } + public byte[] R { get; set; } + public byte[] S { get; set; } + public Address Signer { get; set; } +} diff --git a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs index 120c146c6d3..47487e7e212 100644 --- a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs @@ -137,12 +137,16 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer) Block block = Build.A.Block.WithTransactions(test.Transaction).WithHeader(header).TestObject; - bool isValid = _txValidator.IsWellFormed(test.Transaction, spec) && IsValidBlock(block, specProvider); + ValidationResult txIsValid = _txValidator.IsWellFormed(test.Transaction, spec); - if (isValid) + if (txIsValid) { transactionProcessor.Execute(test.Transaction, new BlockExecutionContext(header), txTracer); } + else + { + _logger.Info($"Skipping invalid tx with error: {txIsValid.Error}"); + } stopwatch.Stop(); diff --git a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs index 8ac509a9672..7d41065cd52 100644 --- a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs @@ -127,6 +127,7 @@ public static Transaction Convert(PostStateJson postStateJson, TransactionJson t { Transaction transaction = new(); + transaction.Type = transactionJson.Type; transaction.Value = transactionJson.Value[postStateJson.Indexes.Value]; transaction.GasLimit = transactionJson.GasLimit[postStateJson.Indexes.Gas]; transaction.GasPrice = transactionJson.GasPrice ?? transactionJson.MaxPriorityFeePerGas ?? 0; @@ -157,6 +158,23 @@ public static Transaction Convert(PostStateJson postStateJson, TransactionJson t if (transaction.BlobVersionedHashes?.Length > 0) transaction.Type = TxType.Blob; + if (transactionJson.AuthorizationList is not null) + { + transaction.AuthorizationList = + transactionJson.AuthorizationList + .Select(i => new AuthorizationTuple( + i.ChainId, + i.Address, + i.Nonce, + i.V, + i.R, + i.S)).ToArray(); + if (transaction.AuthorizationList.Any()) + { + transaction.Type = TxType.SetCode; + } + } + return transaction; } @@ -291,6 +309,7 @@ public static IEnumerable Convert(string json) List tests = new(); foreach (KeyValuePair namedTest in testsInFile) { + Console.WriteLine($"Loading {namedTest.Key}\n {namedTest.Value.Post}"); tests.AddRange(Convert(namedTest.Key, namedTest.Value)); } diff --git a/src/Nethermind/Ethereum.Test.Base/LegacyTransactionJson.cs b/src/Nethermind/Ethereum.Test.Base/LegacyTransactionJson.cs index db932e48913..0d93af017d5 100644 --- a/src/Nethermind/Ethereum.Test.Base/LegacyTransactionJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/LegacyTransactionJson.cs @@ -14,6 +14,7 @@ public class LegacyTransactionJson public UInt256 Nonce { get; set; } public Address To { get; set; } public UInt256 Value { get; set; } + public string Sender { get; set; } public byte[] R { get; set; } public byte[] S { get; set; } public ulong V { get; set; } diff --git a/src/Nethermind/Ethereum.Test.Base/TransactionJson.cs b/src/Nethermind/Ethereum.Test.Base/TransactionJson.cs index 59658f8e876..83c4884f835 100644 --- a/src/Nethermind/Ethereum.Test.Base/TransactionJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/TransactionJson.cs @@ -8,6 +8,8 @@ namespace Ethereum.Test.Base { public class TransactionJson { + public TxType Type { get; set; } + public Address Sender { get; set; } public byte[][]? Data { get; set; } public long[]? GasLimit { get; set; } public UInt256? GasPrice { get; set; } @@ -19,6 +21,7 @@ public class TransactionJson public byte[]? SecretKey { get; set; } public AccessListItemJson[]?[]? AccessLists { get; set; } public AccessListItemJson[]? AccessList { get; set; } + public AuthorizationListJson[]? AuthorizationList { get; set; } public byte[]?[]? BlobVersionedHashes { get; set; } public UInt256? MaxFeePerBlobGas { get; set; } } diff --git a/src/Nethermind/Nethermind.Api/Extensions/IConsensusWrapperPlugin.cs b/src/Nethermind/Nethermind.Api/Extensions/IConsensusWrapperPlugin.cs index b81722632bc..becb062362d 100644 --- a/src/Nethermind/Nethermind.Api/Extensions/IConsensusWrapperPlugin.cs +++ b/src/Nethermind/Nethermind.Api/Extensions/IConsensusWrapperPlugin.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Threading.Tasks; using Nethermind.Consensus; using Nethermind.Consensus.Transactions; diff --git a/src/Nethermind/Nethermind.Api/Extensions/IPluginConfig.cs b/src/Nethermind/Nethermind.Api/Extensions/IPluginConfig.cs index d519e3d3b5a..370b9ae0dd0 100644 --- a/src/Nethermind/Nethermind.Api/Extensions/IPluginConfig.cs +++ b/src/Nethermind/Nethermind.Api/Extensions/IPluginConfig.cs @@ -8,6 +8,6 @@ namespace Nethermind.Api.Extensions; [ConfigCategory(DisabledForCli = false, HiddenFromDocs = true)] public interface IPluginConfig : IConfig { - [ConfigItem(Description = "Order of plugin initialization", DefaultValue = "[Clique, Aura, Ethash, Optimism, AuRaMerge, Merge, MEV, HealthChecks, Hive]")] + [ConfigItem(Description = "Order of plugin initialization", DefaultValue = "[Clique, Aura, Ethash, Optimism, Shutter, AuRaMerge, Merge, MEV, HealthChecks, Hive]")] string[] PluginOrder { get; set; } } diff --git a/src/Nethermind/Nethermind.Api/Extensions/PluginConfig.cs b/src/Nethermind/Nethermind.Api/Extensions/PluginConfig.cs index 43b49b27342..b5ffe466585 100644 --- a/src/Nethermind/Nethermind.Api/Extensions/PluginConfig.cs +++ b/src/Nethermind/Nethermind.Api/Extensions/PluginConfig.cs @@ -5,5 +5,5 @@ namespace Nethermind.Api.Extensions; public class PluginConfig : IPluginConfig { - public string[] PluginOrder { get; set; } = { "Clique", "Aura", "Ethash", "Optimism", "AuRaMerge", "Merge", "MEV", "HealthChecks", "Hive" }; + public string[] PluginOrder { get; set; } = { "Clique", "Aura", "Ethash", "Optimism", "Shutter", "AuRaMerge", "Merge", "MEV", "HealthChecks", "Hive" }; } diff --git a/src/Nethermind/Nethermind.Api/IApiWithBlockchain.cs b/src/Nethermind/Nethermind.Api/IApiWithBlockchain.cs index a3a510e7c89..61bceb25ae4 100644 --- a/src/Nethermind/Nethermind.Api/IApiWithBlockchain.cs +++ b/src/Nethermind/Nethermind.Api/IApiWithBlockchain.cs @@ -88,6 +88,7 @@ public interface IApiWithBlockchain : IApiWithStores, IBlockchainBridgeFactory IGasLimitCalculator? GasLimitCalculator { get; set; } IBlockProducerEnvFactory? BlockProducerEnvFactory { get; set; } + IBlockImprovementContextFactory? BlockImprovementContextFactory { get; set; } IGasPriceOracle? GasPriceOracle { get; set; } diff --git a/src/Nethermind/Nethermind.Api/IApiWithStores.cs b/src/Nethermind/Nethermind.Api/IApiWithStores.cs index f6e02313adc..fa2911c2ebe 100644 --- a/src/Nethermind/Nethermind.Api/IApiWithStores.cs +++ b/src/Nethermind/Nethermind.Api/IApiWithStores.cs @@ -8,6 +8,7 @@ using Nethermind.Consensus; using Nethermind.Crypto; using Nethermind.Db.Blooms; +using Nethermind.Facade.Find; using Nethermind.State.Repositories; using Nethermind.TxPool; using Nethermind.Wallet; diff --git a/src/Nethermind/Nethermind.Api/NethermindApi.cs b/src/Nethermind/Nethermind.Api/NethermindApi.cs index e250c18b01a..bcf296dda37 100644 --- a/src/Nethermind/Nethermind.Api/NethermindApi.cs +++ b/src/Nethermind/Nethermind.Api/NethermindApi.cs @@ -58,6 +58,7 @@ using Nethermind.Trie; using Nethermind.Evm.Config; using Nethermind.Consensus.Processing.CensorshipDetector; +using Nethermind.Facade.Find; namespace Nethermind.Api { @@ -216,6 +217,7 @@ public ISealEngine SealEngine public IGasLimitCalculator? GasLimitCalculator { get; set; } public IBlockProducerEnvFactory? BlockProducerEnvFactory { get; set; } + public IBlockImprovementContextFactory? BlockImprovementContextFactory { get; set; } public IGasPriceOracle? GasPriceOracle { get; set; } public IEthSyncingInfo? EthSyncingInfo { get; set; } diff --git a/src/Nethermind/Nethermind.Api/PluginPriorities.cs b/src/Nethermind/Nethermind.Api/PluginPriorities.cs index 1b0eaec2880..2a6af5a1065 100644 --- a/src/Nethermind/Nethermind.Api/PluginPriorities.cs +++ b/src/Nethermind/Nethermind.Api/PluginPriorities.cs @@ -6,4 +6,5 @@ namespace Nethermind.Api; public class PluginPriorities { public const int Merge = 1000; + public const int Shutter = 2000; } diff --git a/src/Nethermind/Nethermind.Benchmark/Core/RecoverSignaturesBenchmark.cs b/src/Nethermind/Nethermind.Benchmark/Core/RecoverSignaturesBenchmark.cs new file mode 100644 index 00000000000..0b7655985fe --- /dev/null +++ b/src/Nethermind/Nethermind.Benchmark/Core/RecoverSignaturesBenchmark.cs @@ -0,0 +1,203 @@ +using BenchmarkDotNet.Attributes; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Core; +using Nethermind.Crypto; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; +using Nethermind.Specs; +using System; +using System.Collections.Generic; +using Nethermind.Consensus.Processing; +using Nethermind.TxPool; +using Nethermind.Int256; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; + +namespace Nethermind.Benchmarks.Core +{ + [MemoryDiagnoser] + public class RecoverSignaturesBenchmark + { + private ISpecProvider _specProvider = MainnetSpecProvider.Instance; + + private static EthereumEcdsa _ethereumEcdsa; + private static RecoverSignatures _sut; + + private Block _block100TxWith100AuthSigs; + private Block _block100TxWith10AuthSigs; + private Block _block100TxWith1AuthSigs; + private Block _block3TxWith1AuthSigs; + private Block _block10TxWith0AuthSigs; + private Block _block10TxWith10AuthSigs; + + private static PrivateKey[] _privateKeys = Enumerable.Range(0, 1000) + .Select(i => Build.A.PrivateKey.TestObject) + .ToArray(); + + [GlobalSetup] + public void GlobalSetup() + { + _ethereumEcdsa = new(_specProvider.ChainId); + _sut = new(_ethereumEcdsa, NullTxPool.Instance, _specProvider, NullLogManager.Instance); + + var rnd = new Random(); + + _block100TxWith100AuthSigs = Build.A.Block + .WithHeader(new BlockHeader() + { + Timestamp = ulong.MaxValue, + Number = long.MaxValue + }) + .WithTransactions(CreateTransactions(100, 100)) + .TestObject; + _block100TxWith10AuthSigs = Build.A.Block + .WithHeader(new BlockHeader() + { + Timestamp = ulong.MaxValue, + Number = long.MaxValue + }) + .WithTransactions(CreateTransactions(100, 10)) + .TestObject; + + _block100TxWith1AuthSigs = Build.A.Block + .WithHeader(new BlockHeader() + { + Timestamp = ulong.MaxValue, + Number = long.MaxValue + }) + .WithTransactions(CreateTransactions(100, 1)) + .TestObject; + + _block10TxWith10AuthSigs = Build.A.Block + .WithHeader(new BlockHeader() + { + Timestamp = ulong.MaxValue, + Number = long.MaxValue + }) + .WithTransactions(CreateTransactions(10, 10)) + .TestObject; + + _block3TxWith1AuthSigs = Build.A.Block + .WithHeader(new BlockHeader() + { + Timestamp = ulong.MaxValue, + Number = long.MaxValue + }) + .WithTransactions(CreateTransactions(3, 1)) + .TestObject; + + _block10TxWith0AuthSigs = Build.A.Block + .WithHeader(new BlockHeader() + { + Timestamp = ulong.MaxValue, + Number = long.MaxValue + }) + .WithTransactions(CreateTransactions(10, 0)) + .TestObject; + + Transaction[] CreateTransactions(int txCount, int authPerTx) + { + var list = new List(); + for (int i = 0; i < txCount; i++) + { + PrivateKey signer = _privateKeys[i]; + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithAuthorizationCode( + Enumerable.Range(0, authPerTx).Select(y => + { + PrivateKey authority = _privateKeys[i + y + _privateKeys.Length / 2]; + return CreateAuthorizationTuple( + authority, + (ulong)rnd.NextInt64(), + Address.Zero, + (ulong)rnd.NextInt64()); + }).ToArray() + ) + .SignedAndResolved(signer) + .WithSenderAddress(null) + .TestObject; + list.Add(tx); + } + return list.ToArray(); + } + + static AuthorizationTuple CreateAuthorizationTuple(PrivateKey signer, ulong chainId, Address codeAddress, ulong nonce) + { + AuthorizationTupleDecoder decoder = new(); + RlpStream rlp = decoder.EncodeWithoutSignature(chainId, codeAddress, nonce); + Span code = stackalloc byte[rlp.Length + 1]; + code[0] = Eip7702Constants.Magic; + rlp.Data.AsSpan().CopyTo(code.Slice(1)); + + Signature sig = _ethereumEcdsa.Sign(signer, Keccak.Compute(code)); + + return new AuthorizationTuple(chainId, codeAddress, nonce, sig); + } + } + + [IterationCleanup] + public void IterationCleanup() + { + ResetSigs(_block100TxWith100AuthSigs); + ResetSigs(_block100TxWith10AuthSigs); + ResetSigs(_block100TxWith1AuthSigs); + ResetSigs(_block10TxWith10AuthSigs); + ResetSigs(_block10TxWith0AuthSigs); + ResetSigs(_block3TxWith1AuthSigs); + + void ResetSigs(Block block) + { + Parallel.ForEach(block.Transactions, (t) => + { + t.SenderAddress = null; + t.Hash = null; + Parallel.ForEach(t.AuthorizationList, (tuple) => + { + tuple.Authority = null; + }); + }); + } + } + + [Benchmark] + public void Recover100TxSignatureswith100AuthoritySignatures() + { + _sut.RecoverData(_block100TxWith100AuthSigs); + } + + [Benchmark] + public void Recover100TxSignatureswith10AuthoritySignatures() + { + _sut.RecoverData(_block100TxWith10AuthSigs); + } + + [Benchmark] + public void Recover100TxSignaturesWith1AuthoritySignatures() + { + _sut.RecoverData(_block100TxWith1AuthSigs); + } + + [Benchmark] + public void Recover10TxSignaturesWith10AuthoritySignatures() + { + _sut.RecoverData(_block10TxWith10AuthSigs); + } + + [Benchmark] + public void Recover3TxSignaturesWith1AuthoritySignatures() + { + _sut.RecoverData(_block3TxWith1AuthSigs); + } + + [Benchmark] + public void Recover10TxSignaturesWith0AuthoritySignatures() + { + _sut.RecoverData(_block10TxWith0AuthSigs); + } + } +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs index a270e0c9d2c..1fa64a27c27 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs @@ -20,6 +20,7 @@ using Nethermind.Logging; using Nethermind.Db.Blooms; using Nethermind.Facade.Filters; +using Nethermind.Facade.Find; using NSubstitute; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.BaseFee.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.BaseFee.cs index 18ca8be5352..3df241870a2 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.BaseFee.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.BaseFee.cs @@ -54,7 +54,7 @@ private async Task CreateTestBlockchainAsync(long gasLimit) { IsEip1559Enabled = _eip1559Enabled, Eip1559TransitionBlock = _eip1559TransitionBlock, - Eip1559FeeCollector = _eip1559FeeCollector, + FeeCollector = _feeCollector, IsEip155Enabled = true }); BlockBuilder blockBuilder = Build.A.Block.Genesis.WithGasLimit(gasLimit); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.FeeCollector.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.FeeCollector.cs index 7d688f7875e..f88685c675f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.FeeCollector.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.FeeCollector.cs @@ -16,11 +16,11 @@ public static partial class BaseFeeTestScenario { public partial class ScenarioBuilder { - private Address _eip1559FeeCollector = null!; + private Address _feeCollector = null!; - public ScenarioBuilder WithEip1559FeeCollector(Address address) + public ScenarioBuilder WithFeeCollector(Address address) { - _eip1559FeeCollector = address; + _feeCollector = address; return this; } @@ -33,9 +33,9 @@ public ScenarioBuilder AssertNewBlockFeeCollected(UInt256 expectedFeeCollected, private async Task AssertNewBlockFeeCollectedAsync(UInt256 expectedFeeCollected, params Transaction[] transactions) { await ExecuteAntecedentIfNeeded(); - UInt256 balanceBefore = _testRpcBlockchain.State.GetBalance(_eip1559FeeCollector); + UInt256 balanceBefore = _testRpcBlockchain.State.GetBalance(_feeCollector); await _testRpcBlockchain.AddBlock(transactions); - UInt256 balanceAfter = _testRpcBlockchain.State.GetBalance(_eip1559FeeCollector); + UInt256 balanceAfter = _testRpcBlockchain.State.GetBalance(_feeCollector); Assert.That(balanceAfter - balanceBefore, Is.EqualTo(expectedFeeCollected)); return this; @@ -49,7 +49,7 @@ public async Task FeeCollector_should_collect_burned_fees_when_eip1559_and_fee_c long gasTarget = 3000000; BaseFeeTestScenario.ScenarioBuilder scenario = BaseFeeTestScenario.GoesLikeThis() .WithEip1559TransitionBlock(6) - .WithEip1559FeeCollector(TestItem.AddressE) + .WithFeeCollector(TestItem.AddressE) .CreateTestBlockchain(gasTarget) .DeployContract() .BlocksBeforeTransitionShouldHaveZeroBaseFee() @@ -65,7 +65,7 @@ public async Task FeeCollector_should_not_collect_burned_fees_when_eip1559_is_no { long gasTarget = 3000000; BaseFeeTestScenario.ScenarioBuilder scenario = BaseFeeTestScenario.GoesLikeThis() - .WithEip1559FeeCollector(TestItem.AddressE) + .WithFeeCollector(TestItem.AddressE) .CreateTestBlockchain(gasTarget) .DeployContract() .BlocksBeforeTransitionShouldHaveZeroBaseFee() @@ -82,7 +82,7 @@ public async Task FeeCollector_should_not_collect_burned_fees_when_transaction_i long gasTarget = 3000000; BaseFeeTestScenario.ScenarioBuilder scenario = BaseFeeTestScenario.GoesLikeThis() .WithEip1559TransitionBlock(6) - .WithEip1559FeeCollector(TestItem.AddressE) + .WithFeeCollector(TestItem.AddressE) .CreateTestBlockchain(gasTarget) .DeployContract() .BlocksBeforeTransitionShouldHaveZeroBaseFee() diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs index 840c63e6f9f..88274cfd17b 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs @@ -48,16 +48,16 @@ public void Setup() .WithSpecProvider(specProvider) .TestObject; + CodeInfoRepository codeInfoRepository = new(); TxPool.TxPool txPool = new( ecdsa, new BlobTxStorage(), - new ChainHeadInfoProvider(specProvider, _blockTree, stateProvider), + new ChainHeadInfoProvider(specProvider, _blockTree, stateProvider, codeInfoRepository), new TxPoolConfig(), new TxValidator(specProvider.ChainId), LimboLogs.Instance, transactionComparerProvider.GetDefaultComparer()); BlockhashProvider blockhashProvider = new(_blockTree, specProvider, stateProvider, LimboLogs.Instance); - CodeInfoRepository codeInfoRepository = new(); VirtualMachine virtualMachine = new( blockhashProvider, specProvider, diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs index c7cd07b95e1..f52adce9ba7 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Numerics; using FluentAssertions; +using Nethermind.Consensus.Messages; using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -58,7 +59,7 @@ public void Zero_r_is_not_valid() txValidator.IsWellFormed(tx, MuirGlacier.Instance).AsBool().Should().BeFalse(); } - private static byte CalculateV() => (byte)EthereumEcdsa.CalculateV(TestBlockchainIds.ChainId); + private static byte CalculateV() => (byte)EthereumEcdsaExtensions.CalculateV(TestBlockchainIds.ChainId); [Test, MaxTime(Timeout.MaxTestTime)] public void Zero_s_is_not_valid() @@ -522,6 +523,129 @@ public void IsWellFormed_BlobTxHasProofOverTheSizeLimit_ReturnFalse() Assert.That(txValidator.IsWellFormed(tx, Cancun.Instance).AsBool(), Is.False); } + [Test] + public void IsWellFormed_CreateTxInSetCode_ReturnsFalse() + { + TransactionBuilder txBuilder = Build.A.Transaction + .WithType(TxType.SetCode) + .WithAuthorizationCode(new AuthorizationTuple(0, TestItem.AddressA, 0, 0, [], [])) + .WithMaxFeePerGas(100000) + .WithGasLimit(1000000) + .WithChainId(TestBlockchainIds.ChainId) + .SignedAndResolved() + .WithTo(null); + + Transaction tx = txBuilder.TestObject; + TxValidator txValidator = new(TestBlockchainIds.ChainId); + + Assert.Multiple(() => + { + ValidationResult validationResult = txValidator.IsWellFormed(tx, Prague.Instance); + Assert.That(validationResult.AsBool(), Is.False); + Assert.That(validationResult.Error, Is.EqualTo(TxErrorMessages.NotAllowedCreateTransaction)); + }); + } + + [Test] + public void IsWellFormed_AuthorizationListTxInPragueSpec_ReturnsTrue() + { + TransactionBuilder txBuilder = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(TestItem.AddressA) + .WithAuthorizationCode(new AuthorizationTuple(0, TestItem.AddressA, 0, 0, [], [])) + .WithMaxFeePerGas(100000) + .WithGasLimit(1000000) + .WithChainId(TestBlockchainIds.ChainId) + .SignedAndResolved(); + + Transaction tx = txBuilder.TestObject; + TxValidator txValidator = new(TestBlockchainIds.ChainId); + + Assert.That(txValidator.IsWellFormed(tx, Prague.Instance).AsBool, Is.True); + } + + [Test] + public void IsWellFormed_EmptyAuthorizationList_ReturnsFalse() + { + TransactionBuilder txBuilder = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(TestItem.AddressA) + .WithAuthorizationCode([]) + .WithMaxFeePerGas(100000) + .WithGasLimit(1000000) + .WithChainId(TestBlockchainIds.ChainId) + .SignedAndResolved(); + + Transaction tx = txBuilder.TestObject; + TxValidator txValidator = new(TestBlockchainIds.ChainId); + + Assert.That(txValidator.IsWellFormed(tx, Prague.Instance).AsBool, Is.False); + } + [Test] + public void IsWellFormed_NullAuthorizationList_ReturnsFalse() + { + TransactionBuilder txBuilder = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(TestItem.AddressA) + .WithAuthorizationCode((AuthorizationTuple[])null!) + .WithMaxFeePerGas(100000) + .WithGasLimit(1000000) + .WithChainId(TestBlockchainIds.ChainId) + .SignedAndResolved(); + + Transaction tx = txBuilder.TestObject; + TxValidator txValidator = new(TestBlockchainIds.ChainId); + + Assert.That(txValidator.IsWellFormed(tx, Prague.Instance).AsBool, Is.False); + } + + private static object[] BadSignatures = + { + new object[] { 1ul, (UInt256)1, Secp256K1Curve.HalfNPlusOne, false}, + new object[] { 1ul, UInt256.Zero, Secp256K1Curve.HalfN, true }, + new object[] { 0ul, UInt256.Zero, UInt256.Zero, true }, + }; + [TestCaseSource(nameof(BadSignatures))] + public void IsWellFormed_AuthorizationTupleHasBadSignature_ReturnsFalse(ulong yParity, UInt256 r, UInt256 s, bool expected) + { + TransactionBuilder txBuilder = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(TestItem.AddressA) + .WithAuthorizationCode(new AuthorizationTuple(0, Address.Zero, 0, new Signature(r, s, yParity + Signature.VOffset))) + .WithMaxFeePerGas(100000) + .WithGasLimit(1000000) + .WithChainId(TestBlockchainIds.ChainId) + .SignedAndResolved(); + + Transaction tx = txBuilder.TestObject; + TxValidator txValidator = new(TestBlockchainIds.ChainId); + + Assert.That(txValidator.IsWellFormed(tx, Prague.Instance).AsBool, Is.EqualTo(expected)); + } + + private static IEnumerable NonSetCodeTypes() => + Enum.GetValues().Where(t => t != TxType.SetCode && t != TxType.DepositTx); + + [TestCaseSource(nameof(NonSetCodeTypes))] + public void IsWellFormed_NonSetCodeTxHasAuthorizationList_ReturnsFalse(TxType type) + { + var x = Enum.GetValues().Where(t => t != TxType.SetCode); + TransactionBuilder txBuilder = Build.A.Transaction + .WithType(type) + .WithTo(TestItem.AddressA) + .WithMaxFeePerGas(100000) + .WithGasLimit(1000000) + .WithChainId(TestBlockchainIds.ChainId) + .WithShardBlobTxTypeAndFieldsIfBlobTx() + .WithAuthorizationCode(new AuthorizationTuple(TestBlockchainIds.ChainId, TestItem.AddressA, 0, new Signature(new byte[65]))) + .SignedAndResolved(); + + Transaction tx = txBuilder.TestObject; + TxValidator txValidator = new(TestBlockchainIds.ChainId); + + Assert.That(txValidator.IsWellFormed(tx, Prague.Instance).Error, Is.EqualTo(TxErrorMessages.NotAllowedAuthorizationList)); + } + private static byte[] MakeArray(int count, params byte[] elements) => elements.Take(Math.Min(count, elements.Length)) .Concat(new byte[Math.Max(0, count - elements.Length)]) diff --git a/src/Nethermind/Nethermind.Blockchain/ChainHeadInfoProvider.cs b/src/Nethermind/Nethermind.Blockchain/ChainHeadInfoProvider.cs index 8a0b721db9f..3c90e3dc736 100644 --- a/src/Nethermind/Nethermind.Blockchain/ChainHeadInfoProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/ChainHeadInfoProvider.cs @@ -17,28 +17,31 @@ namespace Nethermind.Blockchain { public class ChainHeadInfoProvider : IChainHeadInfoProvider { - public ChainHeadInfoProvider(ISpecProvider specProvider, IBlockTree blockTree, IStateReader stateReader) - : this(new ChainHeadSpecProvider(specProvider, blockTree), blockTree, new ChainHeadReadOnlyStateProvider(blockTree, stateReader)) + public ChainHeadInfoProvider(ISpecProvider specProvider, IBlockTree blockTree, IStateReader stateReader, ICodeInfoRepository codeInfoRepository) + : this(new ChainHeadSpecProvider(specProvider, blockTree), blockTree, new ChainHeadReadOnlyStateProvider(blockTree, stateReader), codeInfoRepository) { } - public ChainHeadInfoProvider(ISpecProvider specProvider, IBlockTree blockTree, IAccountStateProvider stateProvider) - : this(new ChainHeadSpecProvider(specProvider, blockTree), blockTree, stateProvider) + public ChainHeadInfoProvider(ISpecProvider specProvider, IBlockTree blockTree, IReadOnlyStateProvider stateProvider, ICodeInfoRepository codeInfoRepository) + : this(new ChainHeadSpecProvider(specProvider, blockTree), blockTree, stateProvider, codeInfoRepository) { } - public ChainHeadInfoProvider(IChainHeadSpecProvider specProvider, IBlockTree blockTree, IAccountStateProvider stateProvider) + public ChainHeadInfoProvider(IChainHeadSpecProvider specProvider, IBlockTree blockTree, IReadOnlyStateProvider stateProvider, ICodeInfoRepository codeInfoRepository) { SpecProvider = specProvider; - AccountStateProvider = stateProvider; + ReadOnlyStateProvider = stateProvider; HeadNumber = blockTree.BestKnownNumber; + CodeInfoRepository = codeInfoRepository; blockTree.BlockAddedToMain += OnHeadChanged; } public IChainHeadSpecProvider SpecProvider { get; } - public IAccountStateProvider AccountStateProvider { get; } + public IReadOnlyStateProvider ReadOnlyStateProvider { get; } + + public ICodeInfoRepository CodeInfoRepository { get; } public long HeadNumber { get; private set; } @@ -46,7 +49,7 @@ public ChainHeadInfoProvider(IChainHeadSpecProvider specProvider, IBlockTree blo public UInt256 CurrentBaseFee { get; private set; } - public UInt256 CurrentPricePerBlobGas { get; internal set; } + public UInt256 CurrentFeePerBlobGas { get; internal set; } public event EventHandler? HeadChanged; @@ -55,9 +58,9 @@ private void OnHeadChanged(object? sender, BlockReplacementEventArgs e) HeadNumber = e.Block.Number; BlockGasLimit = e.Block!.GasLimit; CurrentBaseFee = e.Block.Header.BaseFeePerGas; - CurrentPricePerBlobGas = - BlobGasCalculator.TryCalculateBlobGasPricePerUnit(e.Block.Header, out UInt256 currentPricePerBlobGas) - ? currentPricePerBlobGas + CurrentFeePerBlobGas = + BlobGasCalculator.TryCalculateFeePerBlobGas(e.Block.Header, out UInt256 currentFeePerBlobGas) + ? currentFeePerBlobGas : UInt256.Zero; HeadChanged?.Invoke(sender, e); } diff --git a/src/Nethermind/Nethermind.Blockchain/Contracts/LogEntryAddressAndTopicsMatchTemplateEqualityComparer.cs b/src/Nethermind/Nethermind.Blockchain/Contracts/LogEntryAddressAndTopicsMatchTemplateEqualityComparer.cs index 72a90023c3a..795a0b8d754 100644 --- a/src/Nethermind/Nethermind.Blockchain/Contracts/LogEntryAddressAndTopicsMatchTemplateEqualityComparer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Contracts/LogEntryAddressAndTopicsMatchTemplateEqualityComparer.cs @@ -26,7 +26,7 @@ public bool Equals(LogEntry logEntry, LogEntry searchedEntryTemplate) Hash256[] matchEntryTopics = searchedEntryTemplate?.Topics ?? Array.Empty(); return ReferenceEquals(logEntry, searchedEntryTemplate) || ( logEntry is not null - && logEntry.LoggersAddress == searchedEntryTemplate?.LoggersAddress + && logEntry.Address == searchedEntryTemplate?.Address && logEntry.Topics.Length >= matchEntryTopics.Length && logEntry.Topics.Take(matchEntryTopics.Length).SequenceEqual(matchEntryTopics) ); @@ -34,7 +34,7 @@ logEntry is not null public int GetHashCode(LogEntry obj) { - return obj.Topics.Aggregate(obj.LoggersAddress.GetHashCode(), (i, keccak) => i ^ keccak.GetHashCode()); + return obj.Topics.Aggregate(obj.Address.GetHashCode(), (i, keccak) => i ^ keccak.GetHashCode()); } } } diff --git a/src/Nethermind/Nethermind.Blockchain/FullPruning/CopyTreeVisitor.cs b/src/Nethermind/Nethermind.Blockchain/FullPruning/CopyTreeVisitor.cs index ee41061fcad..5d868bf60fb 100644 --- a/src/Nethermind/Nethermind.Blockchain/FullPruning/CopyTreeVisitor.cs +++ b/src/Nethermind/Nethermind.Blockchain/FullPruning/CopyTreeVisitor.cs @@ -53,7 +53,7 @@ public CopyTreeVisitor( public void VisitTree(in TContext nodeContext, Hash256 rootHash, TrieVisitContext trieVisitContext) { _stopwatch.Start(); - if (_logger.IsWarn) _logger.Warn($"Full Pruning Started on root hash {rootHash}: do not close the node until finished or progress will be lost."); + if (_logger.IsInfo) _logger.Info($"Full Pruning Started on root hash {rootHash}: do not close the node until finished or progress will be lost."); } [DoesNotReturn] diff --git a/src/Nethermind/Nethermind.Blockchain/Spec/ChainHeadSpecProvider.cs b/src/Nethermind/Nethermind.Blockchain/Spec/ChainHeadSpecProvider.cs index f1d38e94778..05680546eb9 100644 --- a/src/Nethermind/Nethermind.Blockchain/Spec/ChainHeadSpecProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/Spec/ChainHeadSpecProvider.cs @@ -34,6 +34,8 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public long? DaoBlockNumber => _specProvider.DaoBlockNumber; + public ulong? BeaconChainGenesisTimestamp => _specProvider.BeaconChainGenesisTimestamp; + public ulong NetworkId => _specProvider.NetworkId; public ulong ChainId => _specProvider.ChainId; diff --git a/src/Nethermind/Nethermind.Cli/NodeManager.cs b/src/Nethermind/Nethermind.Cli/NodeManager.cs index c392a499e59..75cfe3df20f 100644 --- a/src/Nethermind/Nethermind.Cli/NodeManager.cs +++ b/src/Nethermind/Nethermind.Cli/NodeManager.cs @@ -69,11 +69,9 @@ public async Task PostJint(string method, params object[] parameters) } else { - Stopwatch stopwatch = new(); - stopwatch.Start(); + long startTime = Stopwatch.GetTimestamp(); object? result = await _currentClient.Post(method, parameters); - stopwatch.Stop(); - decimal totalMicroseconds = stopwatch.ElapsedTicks * (1_000_000m / Stopwatch.Frequency); + decimal totalMicroseconds = (decimal)Stopwatch.GetElapsedTime(startTime).TotalMicroseconds; Colorful.Console.WriteLine($"Request complete in {totalMicroseconds}μs"); if (result is bool boolResult) @@ -121,11 +119,9 @@ public async Task PostJint(string method, params object[] parameters) } else { - Stopwatch stopwatch = new(); - stopwatch.Start(); + long starting = Stopwatch.GetTimestamp(); result = await _currentClient.Post(method, parameters); - stopwatch.Stop(); - decimal totalMicroseconds = stopwatch.ElapsedTicks * (1_000_000m / Stopwatch.Frequency); + decimal totalMicroseconds = (decimal)Stopwatch.GetElapsedTime(starting).TotalMicroseconds; Colorful.Console.WriteLine($"Request complete in {totalMicroseconds}μs"); } } diff --git a/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs b/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs index 2769e365735..83e2f3b5372 100644 --- a/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs +++ b/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs @@ -107,9 +107,10 @@ public On CreateNode(PrivateKey privateKey, bool withGenesisAlreadyProcessed = f ITransactionComparerProvider transactionComparerProvider = new TransactionComparerProvider(specProvider, blockTree); + CodeInfoRepository codeInfoRepository = new(); TxPool.TxPool txPool = new(_ethereumEcdsa, new BlobTxStorage(), - new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(GoerliSpecProvider.Instance), blockTree, stateProvider), + new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(GoerliSpecProvider.Instance), blockTree, stateProvider, codeInfoRepository), new TxPoolConfig(), new TxValidator(goerliSpecProvider.ChainId), _logManager, @@ -127,7 +128,6 @@ public On CreateNode(PrivateKey privateKey, bool withGenesisAlreadyProcessed = f _genesis.Header.Hash = _genesis.Header.CalculateHash(); _genesis3Validators.Header.Hash = _genesis3Validators.Header.CalculateHash(); - CodeInfoRepository codeInfoRepository = new(); TransactionProcessor transactionProcessor = new(goerliSpecProvider, stateProvider, new VirtualMachine(blockhashProvider, specProvider, codeInfoRepository, nodeLogManager), codeInfoRepository, @@ -424,9 +424,8 @@ private void WaitForNumber(PrivateKey nodeKey, long number) { if (_logger.IsInfo) _logger.Info($"WAITING ON {nodeKey.Address} FOR BLOCK {number}"); SpinWait spinWait = new(); - Stopwatch stopwatch = new(); - stopwatch.Start(); - while (stopwatch.ElapsedMilliseconds < _timeout) + long startTime = Stopwatch.GetTimestamp(); + while (Stopwatch.GetElapsedTime(startTime).TotalMilliseconds < _timeout) { spinWait.SpinOnce(); if (_blockTrees[nodeKey].Head.Number >= number) diff --git a/src/Nethermind/Nethermind.Clique.Test/SnapshotDecoderTests.cs b/src/Nethermind/Nethermind.Clique.Test/SnapshotDecoderTests.cs index 567fc487a96..8d9fc9cb4d7 100644 --- a/src/Nethermind/Nethermind.Clique.Test/SnapshotDecoderTests.cs +++ b/src/Nethermind/Nethermind.Clique.Test/SnapshotDecoderTests.cs @@ -67,8 +67,7 @@ private Snapshot GenerateSnapshot(Hash256 hash, long number, Address candidate) tally[candidate].Votes = 2; tally[_signer2] = new Tally(false); tally[_signer2].Votes = 1; - Snapshot snapshot = new(number, hash, signers, tally); - snapshot.Votes = votes; + Snapshot snapshot = new(number, hash, signers, tally) { Votes = votes }; return snapshot; } } diff --git a/src/Nethermind/Nethermind.Config/IBlocksConfig.cs b/src/Nethermind/Nethermind.Config/IBlocksConfig.cs index f7a929174d6..72b5446a97d 100644 --- a/src/Nethermind/Nethermind.Config/IBlocksConfig.cs +++ b/src/Nethermind/Nethermind.Config/IBlocksConfig.cs @@ -37,10 +37,10 @@ public interface IBlocksConfig : IConfig [ConfigItem(Description = "Whether to pre-warm the state when processing blocks. This can lead to an up to 2x speed-up in the main loop block processing.", DefaultValue = "True")] bool PreWarmStateOnBlockProcessing { get; set; } - [ConfigItem(Description = "Block Production timeout, in milliseconds.", DefaultValue = "4000")] + [ConfigItem(Description = "The block production timeout, in milliseconds.", DefaultValue = "4000")] int BlockProductionTimeoutMs { get; set; } - [ConfigItem(Description = "Genesis block load timeout, in milliseconds.", DefaultValue = "40000")] + [ConfigItem(Description = "The genesis block load timeout, in milliseconds.", DefaultValue = "40000")] int GenesisTimeoutMs { get; set; } byte[] GetExtraDataBytes(); diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/Config/IAuraConfig.cs b/src/Nethermind/Nethermind.Consensus.AuRa/Config/IAuraConfig.cs index fcd368af8e3..c8e0fb45a12 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/Config/IAuraConfig.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/Config/IAuraConfig.cs @@ -18,7 +18,6 @@ public interface IAuraConfig : IConfig [ConfigItem(Description = "The address of the transaction priority contract to use when selecting transactions from the transaction pool.", DefaultValue = "null")] - string TxPriorityContractAddress { get; set; } [ConfigItem(Description = "The path to the transaction priority rules file to use when selecting transactions from the transaction pool.", diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs index cf7dbf413fc..d2a3952e8f0 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs @@ -24,6 +24,7 @@ using Nethermind.Consensus.Validators; using Nethermind.Consensus.Withdrawals; using Nethermind.Core; +using Nethermind.Evm; using Nethermind.Init.Steps; using Nethermind.Logging; using Nethermind.State; @@ -256,24 +257,25 @@ private IComparer CreateTxPoolTxComparer(TxPriorityContract? txPrio return CreateTxPoolTxComparer(); } - protected override TxPool.TxPool CreateTxPool() + protected override TxPool.TxPool CreateTxPool(CodeInfoRepository codeInfoRepository) { // This has to be different object than the _processingReadOnlyTransactionProcessorSource as this is in separate thread - var txPriorityContract = TxAuRaFilterBuilders.CreateTxPrioritySources(_api); - var localDataSource = _api.TxPriorityContractLocalDataSource; + TxPriorityContract txPriorityContract = TxAuRaFilterBuilders.CreateTxPrioritySources(_api); + TxPriorityContract.LocalDataSource? localDataSource = _api.TxPriorityContractLocalDataSource; ReportTxPriorityRules(txPriorityContract, localDataSource); - var minGasPricesContractDataStore = TxAuRaFilterBuilders.CreateMinGasPricesDataStore(_api, txPriorityContract, localDataSource); + DictionaryContractDataStore? minGasPricesContractDataStore + = TxAuRaFilterBuilders.CreateMinGasPricesDataStore(_api, txPriorityContract, localDataSource); ITxFilter txPoolFilter = TxAuRaFilterBuilders.CreateAuRaTxFilterForProducer(_api, minGasPricesContractDataStore); return new TxPool.TxPool( - _api.EthereumEcdsa, + _api.EthereumEcdsa!, _api.BlobTxStorage ?? NullBlobTxStorage.Instance, - new ChainHeadInfoProvider(_api.SpecProvider, _api.BlockTree, _api.StateReader), + new ChainHeadInfoProvider(_api.SpecProvider!, _api.BlockTree!, _api.StateReader!, codeInfoRepository), NethermindApi.Config(), - _api.TxValidator, + _api.TxValidator!, _api.LogManager, CreateTxPoolTxComparer(txPriorityContract, localDataSource), _api.TxGossipPolicy, diff --git a/src/Nethermind/Nethermind.Consensus.Clique/CliqueBlockProducer.cs b/src/Nethermind/Nethermind.Consensus.Clique/CliqueBlockProducer.cs index 8ed9f2ec739..f85842a83ca 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/CliqueBlockProducer.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/CliqueBlockProducer.cs @@ -100,6 +100,8 @@ public void ProduceOnTopOf(Hash256 hash) _signalsQueue.Add(_blockTree.FindBlock(hash, BlockTreeLookupOptions.None)); } + public IReadOnlyDictionary GetProposals() => _blockProducer.Proposals.ToDictionary(); + private void TimerOnElapsed(object sender, ElapsedEventArgs e) { try @@ -499,7 +501,7 @@ ILogManager logManager selectedTxs, Array.Empty(), spec.WithdrawalsEnabled ? Enumerable.Empty() : null, - spec.ConsensusRequestsEnabled ? Enumerable.Empty() : null + spec.RequestsEnabled ? Enumerable.Empty() : null ); header.TxRoot = TxTrie.CalculateRoot(block.Transactions); block.Header.Author = _sealer.Address; diff --git a/src/Nethermind/Nethermind.Consensus.Clique/CliqueRpcModule.cs b/src/Nethermind/Nethermind.Consensus.Clique/CliqueRpcModule.cs index 07142a25b7a..cc5ec1da3af 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/CliqueRpcModule.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/CliqueRpcModule.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using System.Linq; using Nethermind.Blockchain; using Nethermind.Blockchain.Find; @@ -11,50 +12,46 @@ namespace Nethermind.Consensus.Clique { - public class CliqueRpcModule : ICliqueRpcModule + public class CliqueRpcModule( + ICliqueBlockProducerRunner? cliqueBlockProducer, + ISnapshotManager snapshotManager, + IBlockFinder blockTree) + : ICliqueRpcModule { private const string CannotVoteOnNonValidatorMessage = "Not a signer node - cannot vote"; - private readonly ICliqueBlockProducerRunner? _cliqueBlockProducer; - private readonly ISnapshotManager _snapshotManager; - private readonly IBlockFinder _blockTree; - - public CliqueRpcModule(ICliqueBlockProducerRunner? cliqueBlockProducer, ISnapshotManager snapshotManager, IBlockFinder blockTree) - { - _cliqueBlockProducer = cliqueBlockProducer; - _snapshotManager = snapshotManager ?? throw new ArgumentNullException(nameof(snapshotManager)); - _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); - } + private readonly ISnapshotManager _snapshotManager = snapshotManager ?? throw new ArgumentNullException(nameof(snapshotManager)); + private readonly IBlockFinder _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); public bool ProduceBlock(Hash256 parentHash) { - if (_cliqueBlockProducer is null) + if (cliqueBlockProducer is null) { return false; } - _cliqueBlockProducer?.ProduceOnTopOf(parentHash); + cliqueBlockProducer?.ProduceOnTopOf(parentHash); return true; } public void CastVote(Address signer, bool vote) { - if (_cliqueBlockProducer is null) + if (cliqueBlockProducer is null) { throw new InvalidOperationException(CannotVoteOnNonValidatorMessage); } - _cliqueBlockProducer.CastVote(signer, vote); + cliqueBlockProducer.CastVote(signer, vote); } public void UncastVote(Address signer) { - if (_cliqueBlockProducer is null) + if (cliqueBlockProducer is null) { throw new InvalidOperationException(CannotVoteOnNonValidatorMessage); } - _cliqueBlockProducer.UncastVote(signer); + cliqueBlockProducer.UncastVote(signer); } public Snapshot GetSnapshot() @@ -103,45 +100,24 @@ public string[] GetSignersAnnotated(Hash256 hash) .Select(s => string.Concat(s.Key, $" ({KnownAddresses.GetDescription(s.Key)})")).ToArray(); } - public ResultWrapper clique_produceBlock(Hash256 parentHash) - { - return ResultWrapper.Success(ProduceBlock(parentHash)); - } + public ResultWrapper clique_produceBlock(Hash256 parentHash) => ResultWrapper.Success(ProduceBlock(parentHash)); - public ResultWrapper clique_getSnapshot() - { - return ResultWrapper.Success(GetSnapshot()); - } + public ResultWrapper> clique_proposals() => + ResultWrapper>.Success(cliqueBlockProducer?.GetProposals() ?? new Dictionary()); - public ResultWrapper clique_getSnapshotAtHash(Hash256 hash) - { - return ResultWrapper.Success(GetSnapshot(hash)); - } + public ResultWrapper clique_getSnapshot() => ResultWrapper.Success(GetSnapshot()); - public ResultWrapper clique_getSigners() - { - return ResultWrapper.Success(GetSigners().ToArray()); - } + public ResultWrapper clique_getSnapshotAtHash(Hash256 hash) => ResultWrapper.Success(GetSnapshot(hash)); - public ResultWrapper clique_getSignersAtHash(Hash256 hash) - { - return ResultWrapper.Success(GetSigners(hash).ToArray()); - } + public ResultWrapper clique_getSigners() => ResultWrapper.Success(GetSigners().ToArray()); - public ResultWrapper clique_getSignersAtNumber(long number) - { - return ResultWrapper.Success(GetSigners(number).ToArray()); - } + public ResultWrapper clique_getSignersAtHash(Hash256 hash) => ResultWrapper.Success(GetSigners(hash).ToArray()); - public ResultWrapper clique_getSignersAnnotated() - { - return ResultWrapper.Success(GetSignersAnnotated().ToArray()); - } + public ResultWrapper clique_getSignersAtNumber(long number) => ResultWrapper.Success(GetSigners(number).ToArray()); - public ResultWrapper clique_getSignersAtHashAnnotated(Hash256 hash) - { - return ResultWrapper.Success(GetSignersAnnotated(hash).ToArray()); - } + public ResultWrapper clique_getSignersAnnotated() => ResultWrapper.Success(GetSignersAnnotated().ToArray()); + + public ResultWrapper clique_getSignersAtHashAnnotated(Hash256 hash) => ResultWrapper.Success(GetSignersAnnotated(hash).ToArray()); public ResultWrapper clique_getBlockSigner(Hash256? hash) { diff --git a/src/Nethermind/Nethermind.Consensus.Clique/ICliqueBlockProducer.cs b/src/Nethermind/Nethermind.Consensus.Clique/ICliqueBlockProducer.cs index e2fbc43492e..9604b49ce71 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/ICliqueBlockProducer.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/ICliqueBlockProducer.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Collections.Generic; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -11,5 +12,6 @@ public interface ICliqueBlockProducerRunner : IBlockProducerRunner void CastVote(Address signer, bool vote); void UncastVote(Address signer); void ProduceOnTopOf(Hash256 hash); + IReadOnlyDictionary GetProposals(); } } diff --git a/src/Nethermind/Nethermind.Consensus.Clique/ICliqueRpcModule.cs b/src/Nethermind/Nethermind.Consensus.Clique/ICliqueRpcModule.cs index dba1116bbb7..53440be3fd9 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/ICliqueRpcModule.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/ICliqueRpcModule.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Collections.Generic; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.JsonRpc; @@ -43,5 +44,8 @@ public interface ICliqueRpcModule : IRpcModule [JsonRpcMethod(Description = "Forces Clique block producer to produce a new block", IsImplemented = true)] ResultWrapper clique_produceBlock(Hash256 parentHash); + + [JsonRpcMethod(Description = "Retrieves the current proposals the node is voting on.", IsImplemented = true)] + ResultWrapper> clique_proposals(); } } diff --git a/src/Nethermind/Nethermind.Consensus.Clique/Snapshot.cs b/src/Nethermind/Nethermind.Consensus.Clique/Snapshot.cs index d98f6157ba8..d2f07492a65 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/Snapshot.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/Snapshot.cs @@ -13,8 +13,7 @@ public class Snapshot : ICloneable public long Number { get; set; } public Hash256 Hash { get; set; } public SortedList Signers { get; } - - public List Votes; + public List Votes { get; init; } internal Dictionary Tally { get; } internal Snapshot(long number, Hash256 hash, SortedList signers, Dictionary tally) @@ -31,12 +30,14 @@ internal Snapshot(long number, Hash256 hash, SortedList signers) { } - public object Clone() - { - Snapshot clone = new Snapshot(Number, Hash, new SortedList(Signers, AddressComparer.Instance), new Dictionary(Tally)); - clone.Votes = new List(Votes); - return clone; - } + public object Clone() => + new Snapshot(Number, + Hash, + new SortedList(Signers, AddressComparer.Instance), + new Dictionary(Tally)) + { + Votes = [.. Votes] + }; public long SignerLimit => Signers.Count / 2 + 1; } diff --git a/src/Nethermind/Nethermind.Consensus.Clique/SnapshotDecoder.cs b/src/Nethermind/Nethermind.Consensus.Clique/SnapshotDecoder.cs index 87156c1e43b..eeb86589ee5 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/SnapshotDecoder.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/SnapshotDecoder.cs @@ -25,8 +25,7 @@ public Snapshot Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehav List votes = DecodeVotes(rlpStream); // Tally Dictionary tally = DecodeTally(rlpStream); - Snapshot snapshot = new Snapshot(number, hash, signers, tally); - snapshot.Votes = votes; + Snapshot snapshot = new(number, hash, signers, tally) { Votes = votes }; return snapshot; } diff --git a/src/Nethermind/Nethermind.Consensus.Test/CensorshipDetectorTests.cs b/src/Nethermind/Nethermind.Consensus.Test/CensorshipDetectorTests.cs index 4e8ba989451..a3ead8f5cff 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/CensorshipDetectorTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/CensorshipDetectorTests.cs @@ -16,6 +16,7 @@ using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Db; +using Nethermind.Evm; using Nethermind.Logging; using Nethermind.Specs; using Nethermind.Specs.Forks; @@ -256,7 +257,7 @@ private TxPool.TxPool CreatePool(bool eip1559Enabled = true) return new( _ethereumEcdsa, new BlobTxStorage(), - new ChainHeadInfoProvider(_specProvider, _blockTree, _stateProvider), + new ChainHeadInfoProvider(_specProvider, _blockTree, _stateProvider, new CodeInfoRepository()), new TxPoolConfig(), new TxValidator(_specProvider.ChainId), _logManager, diff --git a/src/Nethermind/Nethermind.Consensus.Test/RecoverSignaturesTest.cs b/src/Nethermind/Nethermind.Consensus.Test/RecoverSignaturesTest.cs new file mode 100644 index 00000000000..8b9825e0c94 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus.Test/RecoverSignaturesTest.cs @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Test.Builders; +using Nethermind.Core; +using NUnit.Framework; +using Nethermind.Consensus.Processing; +using NSubstitute; +using Nethermind.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Logging; +using Nethermind.TxPool; +using System.Linq; +using Nethermind.Core.Crypto; + +namespace Nethermind.Consensus.Test; +[TestFixture] +public class RecoverSignaturesTest +{ + private static readonly IEthereumEcdsa _ecdsa = new EthereumEcdsa(BlockchainIds.GenericNonRealNetwork); + + [Test] + public void RecoverData_SenderIsNotRecoveredAndNotInPool_SenderAndAuthorityIsRecovered() + { + PrivateKey signer = TestItem.PrivateKeyA; + PrivateKey authority = TestItem.PrivateKeyB; + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithAuthorizationCode(_ecdsa.Sign(authority, 0, Address.Zero, 0)) + .SignedAndResolved(signer) + .WithSenderAddress(null) + .TestObject; + + Block block = Build.A.Block + .WithTransactions([tx]) + .TestObject; + + ISpecProvider specProvider = Substitute.For(); + IReleaseSpec releaseSpec = Substitute.For(); + releaseSpec.IsAuthorizationListEnabled.Returns(true); + specProvider.GetSpec(Arg.Any()).Returns(releaseSpec); + RecoverSignatures sut = new( + _ecdsa, + NullTxPool.Instance, + specProvider, + Substitute.For()); + + sut.RecoverData(block); + + Assert.That(tx.SenderAddress, Is.EqualTo(signer.Address)); + Assert.That(tx.AuthorizationList.First().Authority, Is.EqualTo(authority.Address)); + } + + [Test] + public void RecoverData_TxIsInPool_SenderAndAuthoritiesIsSetToSameAsInPool() + { + PrivateKey signer = TestItem.PrivateKeyA; + PrivateKey poolSender = TestItem.PrivateKeyB; + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithAuthorizationCode + ([ + new AuthorizationTuple(0, Address.Zero, 0, new Signature(new byte[65]), null), + new AuthorizationTuple(0, Address.Zero, 0, new Signature(new byte[65]), null), + ]) + .SignedAndResolved(signer) + .WithSenderAddress(null) + .TestObject; + + Block block = Build.A.Block + .WithTransactions([tx]) + .TestObject; + + Transaction txInPool = Build.A.Transaction + .WithType(TxType.SetCode) + .WithAuthorizationCode + ([ + new AuthorizationTuple(0, Address.Zero, 0, new Signature(new byte[65]), poolSender.Address), + new AuthorizationTuple(0, Address.Zero, 0, new Signature(new byte[65]), poolSender.Address), + ]) + .SignedAndResolved(poolSender) + .TestObject; + ITxPool txPool = Substitute.For(); + txPool + .TryGetPendingTransaction(Arg.Any(), out Arg.Any()) + .Returns(x => + { + x[1] = txInPool; + return true; + }); + ISpecProvider specProvider = Substitute.For(); + IReleaseSpec releaseSpec = Substitute.For(); + releaseSpec.IsAuthorizationListEnabled.Returns(true); + specProvider.GetSpec(Arg.Any()).Returns(releaseSpec); + RecoverSignatures sut = new( + _ecdsa, + txPool, + specProvider, + Substitute.For()); + + sut.RecoverData(block); + + Assert.That(tx.SenderAddress, Is.EqualTo(poolSender.Address)); + Assert.That(tx.AuthorizationList.Select(a => a.Authority), Is.All.EqualTo(poolSender.Address)); + } +} diff --git a/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs b/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs index 2f96501987d..e049e77b6e2 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only +using FluentAssertions; using Nethermind.Config; using Nethermind.Core; using Nethermind.Core.Test.Builders; @@ -29,5 +30,21 @@ public void Is_bump_on_1559_eip_block() long actualValue = targetedAdjustedGasLimitCalculator.GetGasLimit(header); Assert.That(actualValue, Is.EqualTo(gasLimit * Eip1559Constants.DefaultElasticityMultiplier)); } + + [TestCase(30_000_000, 100_000_000, 30029295)] + public void Is_calculating_correct_gasLimit(long currentGasLimit, long targetGasLimit, long expectedGasLimit) + { + int blockNumber = 20_000_000; + long gasLimit = currentGasLimit; + TestSpecProvider specProvider = new(Prague.Instance); + TargetAdjustedGasLimitCalculator targetedAdjustedGasLimitCalculator = new(specProvider, + new BlocksConfig() + { + TargetBlockGasLimit = targetGasLimit + }); + BlockHeader header = Build.A.BlockHeader.WithNumber(blockNumber - 1).WithGasLimit(gasLimit).TestObject; + long actualValue = targetedAdjustedGasLimitCalculator.GetGasLimit(header); + actualValue.Should().Be(expectedGasLimit); + } } } diff --git a/src/Nethermind/Nethermind.Consensus/ReadOnlyChain.cs b/src/Nethermind/Nethermind.Consensus/BlockProducerEnv.cs similarity index 100% rename from src/Nethermind/Nethermind.Consensus/ReadOnlyChain.cs rename to src/Nethermind/Nethermind.Consensus/BlockProducerEnv.cs diff --git a/src/Nethermind/Nethermind.Consensus/GC/GCScheduler.cs b/src/Nethermind/Nethermind.Consensus/GC/GCScheduler.cs index c66daea0de1..ddb2d5caee4 100644 --- a/src/Nethermind/Nethermind.Consensus/GC/GCScheduler.cs +++ b/src/Nethermind/Nethermind.Consensus/GC/GCScheduler.cs @@ -6,6 +6,8 @@ using System.Runtime; using System.Threading; using System.Threading.Tasks; +using Nethermind.Core.Extensions; +using Nethermind.Core.Memory; namespace Nethermind.Consensus; @@ -180,7 +182,8 @@ public bool GCCollect(int generation, GCCollectionMode mode, bool blocking, bool // Reset the block counter after GC _countToGC = MaxBlocksWithoutGC; System.GC.Collect(generation, mode, blocking: blocking, compacting: compacting); - + // Also trim native memory used by Db + MallocHelper.Instance.MallocTrim((uint)1.MiB()); // Indicate that GC has finished MarkGCResumed(); diff --git a/src/Nethermind/Nethermind.Consensus/Messages/TxErrorMessages.cs b/src/Nethermind/Nethermind.Consensus/Messages/TxErrorMessages.cs index 8eab24494cf..2a8082895ff 100644 --- a/src/Nethermind/Nethermind.Consensus/Messages/TxErrorMessages.cs +++ b/src/Nethermind/Nethermind.Consensus/Messages/TxErrorMessages.cs @@ -16,6 +16,8 @@ public static string InvalidTxType(string name) => $"InvalidTxType: Transaction type in {name} is not supported."; public const string IntrinsicGasTooLow = "IntrinsicGasTooLow: Gas limit is too low."; + public const string TxMissingTo = + "TxMissingTo: Must be set."; public const string InvalidTxSignature = "InvalidTxSignature: Signature is invalid."; @@ -37,8 +39,8 @@ public static string InvalidTxChainId(ulong expected, ulong? actual) => public const string InvalidTransaction = $"InvalidTransaction: Cannot be {nameof(ShardBlobNetworkWrapper)}."; - public const string TxMissingTo = - "TxMissingTo: Must be set."; + public const string NotAllowedCreateTransaction = + "NotAllowedCreateTransaction: To must be set."; public const string BlobTxMissingMaxFeePerBlobGas = "BlobTxMissingMaxFeePerBlobGas: Must be set."; @@ -70,6 +72,12 @@ public static string InvalidTxChainId(ulong expected, ulong? actual) => public static readonly string InvalidBlobProofSize = $"InvalidBlobProofSize: Cannot be more than {Ckzg.Ckzg.BytesPerProof}."; + public const string NotAllowedAuthorizationList = $"NotAllowedAuthorizationList: Only transactions with type {nameof(TxType.SetCode)} can have authorization_list."; + + public const string MissingAuthorizationList = "MissingAuthorizationList: Must be set."; + + public const string InvalidAuthoritySignature = "InvalidAuthoritySignature: Invalid signature in authorization list."; + public const string InvalidBlobCommitmentHash = "InvalidBlobCommitmentHash: Commitment hash does not match."; diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs index e5c1c8d1543..e1989bbde26 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs @@ -108,10 +108,10 @@ private static bool HasEnoughFounds(Transaction transaction, in UInt256 senderBa } if (transaction.SupportsBlobs && ( - !BlobGasCalculator.TryCalculateBlobGasPrice(block.Header, transaction, out UInt256 blobGasPrice) || - senderBalance < (maxFee += blobGasPrice))) + !BlobGasCalculator.TryCalculateBlobBaseFee(block.Header, transaction, out UInt256 blobBaseFee) || + senderBalance < (maxFee += blobBaseFee))) { - e.Set(TxAction.Skip, $"{maxFee} is higher than sender balance ({senderBalance}), MaxFeePerGas: ({transaction.MaxFeePerGas}), GasLimit {transaction.GasLimit}, BlobGasPrice: {blobGasPrice}"); + e.Set(TxAction.Skip, $"{maxFee} is higher than sender balance ({senderBalance}), MaxFeePerGas: ({transaction.MaxFeePerGas}), GasLimit {transaction.GasLimit}, BlobBaseFee: {blobBaseFee}"); return false; } } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/CensorshipDetector/CensorshipDetectorConfig.cs b/src/Nethermind/Nethermind.Consensus/Processing/CensorshipDetector/CensorshipDetectorConfig.cs index c350c8bf8b4..b4e3bb243fd 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/CensorshipDetector/CensorshipDetectorConfig.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/CensorshipDetector/CensorshipDetectorConfig.cs @@ -5,7 +5,7 @@ namespace Nethermind.Consensus.Processing.CensorshipDetector; public class CensorshipDetectorConfig : ICensorshipDetectorConfig { - public bool Enabled { get; set; } = true; + public bool Enabled { get; set; } = false; public uint BlockCensorshipThreshold { get; set; } = 2; public string[]? AddressesForCensorshipDetection { get; set; } = null; } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/CensorshipDetector/ICensorshipDetectorConfig.cs b/src/Nethermind/Nethermind.Consensus/Processing/CensorshipDetector/ICensorshipDetectorConfig.cs index 29dec2ddf4d..e71ffff02e1 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/CensorshipDetector/ICensorshipDetectorConfig.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/CensorshipDetector/ICensorshipDetectorConfig.cs @@ -7,15 +7,13 @@ namespace Nethermind.Consensus.Processing.CensorshipDetector; public interface ICensorshipDetectorConfig : IConfig { - [ConfigItem(DefaultValue = "true", - Description = "Enabling censorship detection feature")] + [ConfigItem(DefaultValue = "false", Description = "Whether to enable censorship detection.")] bool Enabled { get; set; } [ConfigItem(DefaultValue = "2", - Description = "Number of consecutive blocks with detected potential censorship to report censorship attempt")] + Description = "The number of the consecutive blocks with detected potential censorship to report.")] uint BlockCensorshipThreshold { get; set; } - [ConfigItem(DefaultValue = "null", - Description = "The addresses for which censorship is being detected.")] + [ConfigItem(DefaultValue = "null", Description = "The addresses to detect censorship for.")] string[]? AddressesForCensorshipDetection { get; set; } } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs index 72b73241995..48845fb904d 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs @@ -165,7 +165,7 @@ void IThreadPoolWorkItem.Execute() if (chunkBlocks > 1) { - _logger.Info($"Processed {block.Number - chunkBlocks + 1,10}...{block.Number,9} | {chunkMs,9:N2} ms | slot {runMs,7:N0} ms |{blockGas}"); + _logger.Info($"Processed {block.Number - chunkBlocks + 1,10}...{block.Number,9} | {chunkMs,10:N1} ms | slot {runMs,7:N0} ms |{blockGas}"); } else { @@ -192,7 +192,7 @@ void IThreadPoolWorkItem.Execute() < 2000 => orangeText, _ => redText }; - _logger.Info($"Processed {block.Number,10} | {chunkColor}{chunkMs,9:N2}{resetColor} ms | slot {runMs,7:N0} ms |{blockGas}"); + _logger.Info($"Processed {block.Number,10} | {chunkColor}{chunkMs,10:N1}{resetColor} ms | slot {runMs,7:N0} ms |{blockGas}"); } string mgasPerSecondColor = (mgasPerSecond / (block.GasLimit / 1_000_000.0)) switch @@ -236,20 +236,20 @@ void IThreadPoolWorkItem.Execute() var recoveryQueue = Metrics.RecoveryQueueSize; var processingQueue = Metrics.ProcessingQueueSize; - _logger.Info($"- Block{(chunkBlocks > 1 ? $"s {chunkBlocks,-9:N0}" : " ")}{(chunkBlocks == 1 ? mgasColor : "")} {chunkMGas,9:F2}{resetColor} MGas | {chunkTx,6:N0} txs | calls {callsColor}{chunkCalls,6:N0}{resetColor} {darkGreyText}({chunkEmptyCalls,3:N0}){resetColor} | sload {chunkSload,7:N0} | sstore {sstoreColor}{chunkSstore,6:N0}{resetColor} | create {createsColor}{chunkCreates,3:N0}{resetColor}{(currentSelfDestructs - _lastSelfDestructs > 0 ? $"{darkGreyText}({-(currentSelfDestructs - _lastSelfDestructs),3:N0}){resetColor}" : "")}"); + _logger.Info($"- Block{(chunkBlocks > 1 ? $"s {chunkBlocks,-9:N0}" : " ")}{(chunkBlocks == 1 ? mgasColor : "")} {chunkMGas,9:F2}{resetColor} MGas | {chunkTx,8:N0} txs | calls {callsColor}{chunkCalls,6:N0}{resetColor} {darkGreyText}({chunkEmptyCalls,3:N0}){resetColor} | sload {chunkSload,7:N0} | sstore {sstoreColor}{chunkSstore,6:N0}{resetColor} | create {createsColor}{chunkCreates,3:N0}{resetColor}{(currentSelfDestructs - _lastSelfDestructs > 0 ? $"{darkGreyText}({-(currentSelfDestructs - _lastSelfDestructs),3:N0}){resetColor}" : "")}"); if (recoveryQueue > 0 || processingQueue > 0) { - _logger.Info($"- Block throughput {mgasPerSecondColor}{mgasPerSecond,9:F2}{resetColor} MGas/s{(mgasPerSecond > 1000 ? "🔥" : " ")}| {txps,9:F2} t/s | {bps,7:F2} Blk/s | recover {recoveryQueue,5:N0} | process {processingQueue,5:N0}"); + _logger.Info($"- Block throughput {mgasPerSecondColor}{mgasPerSecond,9:F2}{resetColor} MGas/s{(mgasPerSecond > 1000 ? "🔥" : " ")}| {txps,10:N1} tps | {bps,7:F2} Blk/s | recover {recoveryQueue,5:N0} | process {processingQueue,5:N0}"); } else { - _logger.Info($"- Block throughput {mgasPerSecondColor}{mgasPerSecond,9:F2}{resetColor} MGas/s{(mgasPerSecond > 1000 ? "🔥" : " ")}| {txps,9:F2} t/s | {bps,7:F2} Blk/s | exec code {resetColor} from cache {cachedContractsUsed,7:N0} |{resetColor} new {contractsAnalysed,6:N0}"); + _logger.Info($"- Block throughput {mgasPerSecondColor}{mgasPerSecond,9:F2}{resetColor} MGas/s{(mgasPerSecond > 1000 ? "🔥" : " ")}| {txps,10:N1} tps | {bps,7:F2} Blk/s | exec code {resetColor} from cache {cachedContractsUsed,7:N0} |{resetColor} new {contractsAnalysed,6:N0}"); } // Only output the total throughput in debug mode if (_logger.IsDebug) { - _logger.Debug($"- Total throughput {totalMgasPerSecond,9:F2} MGas/s | {totalTxPerSecond,9:F2} t/s | {totalBlocksPerSecond,7:F2} Blk/s |⛽ Gas gwei: {Evm.Metrics.MinGasPrice:N2} .. {Math.Max(Evm.Metrics.MinGasPrice, Evm.Metrics.EstMedianGasPrice):N2} ({Evm.Metrics.AveGasPrice:N2}) .. {Evm.Metrics.MaxGasPrice:N2}"); + _logger.Debug($"- Total throughput {totalMgasPerSecond,9:F2} MGas/s | {totalTxPerSecond,9:F2} tps | {totalBlocksPerSecond,7:F2} Blk/s |⛽ Gas gwei: {Evm.Metrics.MinGasPrice:N2} .. {Math.Max(Evm.Metrics.MinGasPrice, Evm.Metrics.EstMedianGasPrice):N2} ({Evm.Metrics.AveGasPrice:N2}) .. {Evm.Metrics.MaxGasPrice:N2}"); } } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/RecoverSignature.cs b/src/Nethermind/Nethermind.Consensus/Processing/RecoverSignature.cs index 64b4dcd0cbb..c748ecae850 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/RecoverSignature.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/RecoverSignature.cs @@ -2,12 +2,14 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Crypto; using Nethermind.Logging; +using Nethermind.Serialization.Rlp; using Nethermind.TxPool; namespace Nethermind.Consensus.Processing @@ -60,7 +62,7 @@ public void RecoverData(Block block) // Don't access txPool in Parallel loop as increases contention foreach (Transaction tx in txs) { - if (!ShouldRecoverSender(tx)) + if (!ShouldRecoverSignatures(tx)) continue; Transaction? poolTx = null; @@ -84,22 +86,38 @@ public void RecoverData(Block block) { recoverFromEcdsa++; } + + if (poolTx is not null && tx.HasAuthorizationList) + { + for (int i = 0; i < tx.AuthorizationList.Length; i++) + { + if (poolTx.AuthorizationList[i].Authority is not null) + { + tx.AuthorizationList[i].Authority = poolTx.AuthorizationList[i].Authority; + } + else if (tx.AuthorizationList[i].Authority is null) + { + recoverFromEcdsa++; + } + } + } } if (recoverFromEcdsa == 0) return; - bool useSignatureChainId = !_specProvider.GetSpec(block.Header).ValidateChainId; + IReleaseSpec releaseSpec = _specProvider.GetSpec(block.Header); + bool useSignatureChainId = !releaseSpec.ValidateChainId; if (recoverFromEcdsa > 3) { // Recover ecdsa in Parallel Parallel.For(0, txs.Length, i => { Transaction tx = txs[i]; - if (!ShouldRecoverSender(tx)) return; - - tx.SenderAddress = _ecdsa.RecoverAddress(tx, useSignatureChainId); + if (!ShouldRecoverSignatures(tx)) return; + tx.SenderAddress ??= _ecdsa.RecoverAddress(tx, useSignatureChainId); + RecoverAuthorities(tx); if (_logger.IsTrace) _logger.Trace($"Recovered {tx.SenderAddress} sender for {tx.Hash}"); }); } @@ -107,17 +125,41 @@ public void RecoverData(Block block) { foreach (Transaction tx in txs) { - if (!ShouldRecoverSender(tx)) continue; - - tx.SenderAddress = _ecdsa.RecoverAddress(tx, useSignatureChainId); + if (!ShouldRecoverSignatures(tx)) continue; + tx.SenderAddress ??= _ecdsa.RecoverAddress(tx, useSignatureChainId); + RecoverAuthorities(tx); if (_logger.IsTrace) _logger.Trace($"Recovered {tx.SenderAddress} sender for {tx.Hash}"); } } + + void RecoverAuthorities(Transaction tx) + { + if (!releaseSpec.IsAuthorizationListEnabled + || !tx.HasAuthorizationList) + { + return; + } + + if (tx.AuthorizationList.Length > 3) + { + Parallel.ForEach(tx.AuthorizationList.Where(t => t.Authority is null), (tuple) => + { + tuple.Authority = _ecdsa.RecoverAddress(tuple); + }); + } + else + { + foreach (AuthorizationTuple tuple in tx.AuthorizationList.AsSpan()) + { + tuple.Authority ??= _ecdsa.RecoverAddress(tuple); + } + } + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool ShouldRecoverSender(Transaction tx) - => tx.IsSigned && tx.SenderAddress is null; + private static bool ShouldRecoverSignatures(Transaction tx) + => tx.IsSigned && (tx.SenderAddress is null || (tx.HasAuthorizationList && tx.AuthorizationList.Any(a => a.Authority is null))); } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockImprovementContext.cs b/src/Nethermind/Nethermind.Consensus/Producers/IBlockImprovementContext.cs similarity index 87% rename from src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockImprovementContext.cs rename to src/Nethermind/Nethermind.Consensus/Producers/IBlockImprovementContext.cs index 156de674473..56bff79d6cb 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockImprovementContext.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/IBlockImprovementContext.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Nethermind.Core; -namespace Nethermind.Merge.Plugin.BlockProduction; +namespace Nethermind.Consensus.Producers; public interface IBlockImprovementContext : IBlockProductionContext, IDisposable { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockImprovementContextFactory.cs b/src/Nethermind/Nethermind.Consensus/Producers/IBlockImprovementContextFactory.cs similarity index 81% rename from src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockImprovementContextFactory.cs rename to src/Nethermind/Nethermind.Consensus/Producers/IBlockImprovementContextFactory.cs index 05ce33721f1..daba38c45d7 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockImprovementContextFactory.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/IBlockImprovementContextFactory.cs @@ -2,10 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using Nethermind.Consensus.Producers; using Nethermind.Core; -namespace Nethermind.Merge.Plugin.BlockProduction; +namespace Nethermind.Consensus.Producers; public interface IBlockImprovementContextFactory { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockProductionContext.cs b/src/Nethermind/Nethermind.Consensus/Producers/IBlockProductionContext.cs similarity index 83% rename from src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockProductionContext.cs rename to src/Nethermind/Nethermind.Consensus/Producers/IBlockProductionContext.cs index 404b650c458..207f55eb738 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockProductionContext.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/IBlockProductionContext.cs @@ -4,7 +4,7 @@ using Nethermind.Core; using Nethermind.Int256; -namespace Nethermind.Merge.Plugin.BlockProduction; +namespace Nethermind.Consensus.Producers; public interface IBlockProductionContext { diff --git a/src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs b/src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs index 6bf0215c54a..70793885fd3 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs @@ -124,7 +124,7 @@ private void SelectBlobTransactions(IEnumerable blobTransactions, B int checkedBlobTransactions = 0; int selectedBlobTransactions = 0; UInt256 blobGasCounter = 0; - UInt256 blobGasPrice = UInt256.Zero; + UInt256 feePerBlobGas = UInt256.Zero; foreach (Transaction blobTx in blobTransactions) { @@ -143,13 +143,13 @@ private void SelectBlobTransactions(IEnumerable blobTransactions, B continue; } - if (blobGasPrice.IsZero && !TryUpdateBlobGasPrice(blobTx, parent, spec, out blobGasPrice)) + if (feePerBlobGas.IsZero && !TryUpdateFeePerBlobGas(blobTx, parent, spec, out feePerBlobGas)) { if (_logger.IsTrace) _logger.Trace($"Declining {blobTx.ToShortString()}, failed to get full version of this blob tx from TxPool."); continue; } - if (blobGasPrice > blobTx.MaxFeePerBlobGas) + if (feePerBlobGas > blobTx.MaxFeePerBlobGas) { if (_logger.IsTrace) _logger.Trace($"Declining {blobTx.ToShortString()}, data gas fee is too low."); continue; @@ -186,20 +186,20 @@ private bool TryGetFullBlobTx(Transaction blobTx, [NotNullWhen(true)] out Transa return blobTx.Hash is not null && _transactionPool.TryGetPendingBlobTransaction(blobTx.Hash, out fullBlobTx); } - private bool TryUpdateBlobGasPrice(Transaction lightBlobTx, BlockHeader parent, IReleaseSpec spec, out UInt256 blobGasPrice) + private bool TryUpdateFeePerBlobGas(Transaction lightBlobTx, BlockHeader parent, IReleaseSpec spec, out UInt256 feePerBlobGas) { ulong? excessDataGas = BlobGasCalculator.CalculateExcessBlobGas(parent, spec); if (excessDataGas is null) { if (_logger.IsTrace) _logger.Trace($"Declining {lightBlobTx.ToShortString()}, the specification is not configured to handle shard blob transactions."); - blobGasPrice = UInt256.Zero; + feePerBlobGas = UInt256.Zero; return false; } - if (!BlobGasCalculator.TryCalculateBlobGasPricePerUnit(excessDataGas.Value, out blobGasPrice)) + if (!BlobGasCalculator.TryCalculateFeePerBlobGas(excessDataGas.Value, out feePerBlobGas)) { if (_logger.IsTrace) _logger.Trace($"Declining {lightBlobTx.ToShortString()}, failed to calculate data gas price."); - blobGasPrice = UInt256.Zero; + feePerBlobGas = UInt256.Zero; return false; } diff --git a/src/Nethermind/Nethermind.Consensus/Requests/ConsensusRequestsProcessor.cs b/src/Nethermind/Nethermind.Consensus/Requests/ConsensusRequestsProcessor.cs index d44a33466a1..cbc155d1d9c 100644 --- a/src/Nethermind/Nethermind.Consensus/Requests/ConsensusRequestsProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Requests/ConsensusRequestsProcessor.cs @@ -16,22 +16,24 @@ namespace Nethermind.Consensus.Requests; public class ConsensusRequestsProcessor(ITransactionProcessor transactionProcessor) : IConsensusRequestsProcessor { + private readonly ConsolidationRequestsProcessor _consolidationRequestsProcessor = new(transactionProcessor); private readonly WithdrawalRequestsProcessor _withdrawalRequestsProcessor = new(transactionProcessor); private readonly IDepositsProcessor _depositsProcessor = new DepositsProcessor(); public void ProcessRequests(Block block, IWorldState state, TxReceipt[] receipts, IReleaseSpec spec) { - if (spec.DepositsEnabled || spec.WithdrawalRequestsEnabled) - { - using ArrayPoolList requestsList = new(receipts.Length * 2); + if (!spec.RequestsEnabled) + return; - requestsList.AddRange(_depositsProcessor.ProcessDeposits(block, receipts, spec)); - requestsList.AddRange(_withdrawalRequestsProcessor.ReadWithdrawalRequests(block, state, spec)); + using ArrayPoolList requestsList = new(receipts.Length * 2); - ConsensusRequest[] requests = requestsList.ToArray(); - Hash256 root = new RequestsTrie(requests).RootHash; - block.Body.Requests = requests; - block.Header.RequestsRoot = root; - } + requestsList.AddRange(_depositsProcessor.ProcessDeposits(block, receipts, spec)); + requestsList.AddRange(_withdrawalRequestsProcessor.ReadRequests(block, state, spec)); + requestsList.AddRange(_consolidationRequestsProcessor.ReadRequests(block, state, spec)); + + ConsensusRequest[] requests = requestsList.ToArray(); + Hash256 root = new RequestsTrie(requests).RootHash; + block.Body.Requests = requests; + block.Header.RequestsRoot = root; } } diff --git a/src/Nethermind/Nethermind.Consensus/Requests/ConsolidationRequestProcessor.cs b/src/Nethermind/Nethermind.Consensus/Requests/ConsolidationRequestProcessor.cs new file mode 100644 index 00000000000..9d18024b080 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Requests/ConsolidationRequestProcessor.cs @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.Core.ConsensusRequests; +using Nethermind.Core.Specs; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.State; + +namespace Nethermind.Consensus.Requests; + +// https://eips.ethereum.org/EIPS/eip-7251#block-processing +public class ConsolidationRequestsProcessor(ITransactionProcessor transactionProcessor) : RequestProcessor(transactionProcessor), IRequestProcessor +{ + private const int SizeOfClass = 20 + 48 + 48; + + public IEnumerable ReadRequests(Block block, IWorldState state, IReleaseSpec spec) + { + return base.ReadRequests(block, state, spec, spec.Eip7251ContractAddress); + } + protected override bool IsEnabledInSpec(IReleaseSpec spec) + { + return spec.ConsolidationRequestsEnabled; + } + protected override IEnumerable ParseResult(Memory result) + { + int count = result.Length / SizeOfClass; + + for (int i = 0; i < count; ++i) + { + int offset = i * SizeOfClass; + ConsolidationRequest request = new() + { + SourceAddress = new Address(result.Slice(offset, 20).ToArray()), + SourcePubkey = result.Slice(offset + 20, 48), + TargetPubkey = result.Slice(offset + 68, 48) + }; + + yield return request; + } + } +} diff --git a/src/Nethermind/Nethermind.Consensus/Requests/DepositsProcessor.cs b/src/Nethermind/Nethermind.Consensus/Requests/DepositsProcessor.cs index 79e37ef79cc..a9619bba946 100644 --- a/src/Nethermind/Nethermind.Consensus/Requests/DepositsProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Requests/DepositsProcessor.cs @@ -30,7 +30,7 @@ public IEnumerable ProcessDeposits(Block block, TxReceipt[] receipts, I for (var j = 0; j < logEntries.Length; j++) { LogEntry log = logEntries[j]; - if (log.LoggersAddress == spec.DepositContractAddress) + if (log.Address == spec.DepositContractAddress) { yield return DecodeDeposit(log); } diff --git a/src/Nethermind/Nethermind.Consensus/Requests/IWithdrawalRequestsProcessor.cs b/src/Nethermind/Nethermind.Consensus/Requests/IRequestProcessor.cs similarity index 56% rename from src/Nethermind/Nethermind.Consensus/Requests/IWithdrawalRequestsProcessor.cs rename to src/Nethermind/Nethermind.Consensus/Requests/IRequestProcessor.cs index ba396ed1441..6f33453fdfb 100644 --- a/src/Nethermind/Nethermind.Consensus/Requests/IWithdrawalRequestsProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Requests/IRequestProcessor.cs @@ -1,15 +1,14 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Collections.Generic; using Nethermind.Core; -using Nethermind.Core.ConsensusRequests; using Nethermind.Core.Specs; using Nethermind.State; +using System.Collections.Generic; namespace Nethermind.Consensus.Requests; -public interface IWithdrawalRequestsProcessor +public interface IRequestProcessor { - IEnumerable ReadWithdrawalRequests(Block block, IWorldState state, IReleaseSpec spec); + IEnumerable ReadRequests(Block block, IWorldState state, IReleaseSpec spec); } diff --git a/src/Nethermind/Nethermind.Consensus/Requests/RequestProcessor.cs b/src/Nethermind/Nethermind.Consensus/Requests/RequestProcessor.cs new file mode 100644 index 00000000000..c9c40c8dcd1 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Requests/RequestProcessor.cs @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Crypto; +using Nethermind.Evm; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Int256; +using Nethermind.State; + +namespace Nethermind.Consensus.Requests; + +public abstract class RequestProcessor(ITransactionProcessor transactionProcessor) +{ + private const long GasLimit = 30_000_000L; + + public IEnumerable ReadRequests(Block block, IWorldState state, IReleaseSpec spec, Address contractAddress) + { + if (!IsEnabledInSpec(spec)) + return Array.Empty(); + + if (!state.AccountExists(contractAddress)) + return Array.Empty(); + + CallOutputTracer tracer = new(); + + Transaction? transaction = new() + { + Value = UInt256.Zero, + Data = Array.Empty(), + To = contractAddress, + SenderAddress = Address.SystemUser, + GasLimit = GasLimit, + GasPrice = UInt256.Zero, + }; + transaction.Hash = transaction.CalculateHash(); + + transactionProcessor.Execute(transaction, new BlockExecutionContext(block.Header), tracer); + var result = tracer.ReturnValue; + if (result == null || result.Length == 0) + return Enumerable.Empty(); + return ParseResult(tracer.ReturnValue); + } + + + protected abstract bool IsEnabledInSpec(IReleaseSpec spec); + protected abstract IEnumerable ParseResult(Memory result); +} diff --git a/src/Nethermind/Nethermind.Consensus/Requests/WithdrawalRequestsProcessor.cs b/src/Nethermind/Nethermind.Consensus/Requests/WithdrawalRequestsProcessor.cs index 564f2573f5d..86483add1a0 100644 --- a/src/Nethermind/Nethermind.Consensus/Requests/WithdrawalRequestsProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Requests/WithdrawalRequestsProcessor.cs @@ -4,68 +4,43 @@ using System; using System.Buffers.Binary; using System.Collections.Generic; -using System.Linq; using Nethermind.Core; using Nethermind.Core.ConsensusRequests; -using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; -using Nethermind.Crypto; -using Nethermind.Evm; -using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; -using Nethermind.Int256; using Nethermind.State; namespace Nethermind.Consensus.Requests; // https://eips.ethereum.org/EIPS/eip-7002#block-processing -public class WithdrawalRequestsProcessor(ITransactionProcessor transactionProcessor) : IWithdrawalRequestsProcessor +public class WithdrawalRequestsProcessor(ITransactionProcessor transactionProcessor) : RequestProcessor(transactionProcessor), IRequestProcessor { - private const long GasLimit = 30_000_000L; private const int SizeOfClass = 20 + 48 + 8; - public IEnumerable ReadWithdrawalRequests(Block block, IWorldState state, IReleaseSpec spec) + public IEnumerable ReadRequests(Block block, IWorldState state, IReleaseSpec spec) { - if (!spec.IsEip7002Enabled || !state.AccountExists(spec.Eip7002ContractAddress)) - { - yield break; - } + return ReadRequests(block, state, spec, spec.Eip7002ContractAddress); + } - byte[]? result = ExecuteTransaction(block, spec); - if (result?.Length > 0) - { - int count = result.Length / SizeOfClass; - for (int i = 0; i < count; ++i) - { - Memory memory = result.AsMemory(i * SizeOfClass, SizeOfClass); - WithdrawalRequest request = new() - { - SourceAddress = new Address(memory.Slice(0, 20).AsArray()), - ValidatorPubkey = memory.Slice(20, 48), - Amount = BinaryPrimitives.ReadUInt64BigEndian(memory.Slice(68, 8).Span) - }; - yield return request; - } - } + protected override bool IsEnabledInSpec(IReleaseSpec spec) + { + return spec.WithdrawalRequestsEnabled; } - private byte[]? ExecuteTransaction(Block block, IReleaseSpec spec) + protected override IEnumerable ParseResult(Memory result) { - CallOutputTracer tracer = new(); - Transaction transaction = new() + int count = result.Length / SizeOfClass; + for (int i = 0; i < count; ++i) { - Value = UInt256.Zero, - Data = Array.Empty(), - To = spec.Eip7002ContractAddress, - SenderAddress = Address.SystemUser, - GasLimit = GasLimit, - GasPrice = UInt256.Zero, - }; - transaction.Hash = transaction.CalculateHash(); - - transactionProcessor.Execute(transaction, new BlockExecutionContext(block.Header), tracer); - - return tracer.ReturnValue; + int offset = i * SizeOfClass; + WithdrawalRequest request = new() + { + SourceAddress = new Address(result.Slice(offset, 20).AsArray()), + ValidatorPubkey = result.Slice(offset + 20, 48), + Amount = BinaryPrimitives.ReadUInt64BigEndian(result.Slice(offset + 68, 8).Span) + }; + yield return request; + } } } diff --git a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs index 5c75c77549c..1e3aa152eed 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs @@ -288,7 +288,7 @@ public bool ValidateRequests(Block block, out string? error) => private bool ValidateRequests(Block block, IReleaseSpec spec, out string? error) { - if (spec.ConsensusRequestsEnabled && block.Requests is null) + if (spec.RequestsEnabled && block.Requests is null) { error = BlockErrorMessages.MissingRequests; @@ -297,7 +297,7 @@ private bool ValidateRequests(Block block, IReleaseSpec spec, out string? error) return false; } - if (!spec.ConsensusRequestsEnabled && block.Requests is not null) + if (!spec.RequestsEnabled && block.Requests is not null) { error = BlockErrorMessages.RequestsNotEnabled; @@ -371,7 +371,7 @@ private bool ValidateEip4844Fields(Block block, IReleaseSpec spec, out string? e } int blobsInBlock = 0; - UInt256 blobGasPrice = UInt256.Zero; + UInt256 feePerBlobGas = UInt256.Zero; Transaction[] transactions = block.Transactions; for (int txIndex = 0; txIndex < transactions.Length; txIndex++) @@ -383,9 +383,9 @@ private bool ValidateEip4844Fields(Block block, IReleaseSpec spec, out string? e continue; } - if (blobGasPrice.IsZero) + if (feePerBlobGas.IsZero) { - if (!BlobGasCalculator.TryCalculateBlobGasPricePerUnit(block.Header, out blobGasPrice)) + if (!BlobGasCalculator.TryCalculateFeePerBlobGas(block.Header, out feePerBlobGas)) { error = BlockErrorMessages.BlobGasPriceOverflow; if (_logger.IsDebug) _logger.Debug($"{Invalid(block)} {error}."); @@ -393,10 +393,10 @@ private bool ValidateEip4844Fields(Block block, IReleaseSpec spec, out string? e } } - if (transaction.MaxFeePerBlobGas < blobGasPrice) + if (transaction.MaxFeePerBlobGas < feePerBlobGas) { error = BlockErrorMessages.InsufficientMaxFeePerBlobGas; - if (_logger.IsDebug) _logger.Debug($"{Invalid(block)} Transaction at index {txIndex} has insufficient {nameof(transaction.MaxFeePerBlobGas)} to cover current blob gas fee: {transaction.MaxFeePerBlobGas} < {blobGasPrice}."); + if (_logger.IsDebug) _logger.Debug($"{Invalid(block)} Transaction at index {txIndex} has insufficient {nameof(transaction.MaxFeePerBlobGas)} to cover current blob gas fee: {transaction.MaxFeePerBlobGas} < {feePerBlobGas}."); return false; } diff --git a/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs index 5207135b109..070ab579360 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs @@ -11,6 +11,7 @@ using Nethermind.Crypto; using Nethermind.Evm; using Nethermind.Int256; +using System.Linq; namespace Nethermind.Consensus.Validators; @@ -25,6 +26,7 @@ public TxValidator(ulong chainId) new LegacySignatureTxValidator(chainId), ContractSizeTxValidator.Instance, NonBlobFieldsTxValidator.Instance, + NonSetCodeFieldsTxValidator.Instance ])); RegisterValidator(TxType.AccessList, new CompositeTxValidator([ new ReleaseSpecTxValidator(static spec => spec.IsEip2930Enabled), @@ -33,6 +35,7 @@ public TxValidator(ulong chainId) new ExpectedChainIdTxValidator(chainId), ContractSizeTxValidator.Instance, NonBlobFieldsTxValidator.Instance, + NonSetCodeFieldsTxValidator.Instance ])); RegisterValidator(TxType.EIP1559, new CompositeTxValidator([ new ReleaseSpecTxValidator(static spec => spec.IsEip1559Enabled), @@ -42,6 +45,7 @@ public TxValidator(ulong chainId) GasFieldsTxValidator.Instance, ContractSizeTxValidator.Instance, NonBlobFieldsTxValidator.Instance, + NonSetCodeFieldsTxValidator.Instance ])); RegisterValidator(TxType.Blob, new CompositeTxValidator([ new ReleaseSpecTxValidator(static spec => spec.IsEip4844Enabled), @@ -51,7 +55,19 @@ public TxValidator(ulong chainId) GasFieldsTxValidator.Instance, ContractSizeTxValidator.Instance, BlobFieldsTxValidator.Instance, - MempoolBlobTxValidator.Instance + MempoolBlobTxValidator.Instance, + NonSetCodeFieldsTxValidator.Instance + ])); + RegisterValidator(TxType.SetCode, new CompositeTxValidator([ + new ReleaseSpecTxValidator(static spec => spec.IsEip7702Enabled), + IntrinsicGasTxValidator.Instance, + SignatureTxValidator.Instance, + new ExpectedChainIdTxValidator(chainId), + GasFieldsTxValidator.Instance, + ContractSizeTxValidator.Instance, + NonBlobFieldsTxValidator.Instance, + NoContractCreationTxValidator.Instance, + AuthorizationListTxValidator.Instance, ])); } @@ -150,6 +166,18 @@ private NonBlobFieldsTxValidator() { } }; } +public sealed class NonSetCodeFieldsTxValidator : ITxValidator +{ + public static readonly NonSetCodeFieldsTxValidator Instance = new(); + private NonSetCodeFieldsTxValidator() { } + + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) => transaction switch + { + { AuthorizationList: not null } => TxErrorMessages.NotAllowedAuthorizationList, + _ => ValidationResult.Success + }; +} + public sealed class BlobFieldsTxValidator : ITxValidator { public static readonly BlobFieldsTxValidator Instance = new(); @@ -282,3 +310,33 @@ public sealed class SignatureTxValidator : BaseSignatureTxValidator public static readonly SignatureTxValidator Instance = new(); private SignatureTxValidator() { } } + +public sealed class NoContractCreationTxValidator : ITxValidator +{ + public static readonly NoContractCreationTxValidator Instance = new(); + private NoContractCreationTxValidator() { } + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) => + transaction.IsContractCreation ? TxErrorMessages.NotAllowedCreateTransaction : ValidationResult.Success; +} + +public sealed class AuthorizationListTxValidator : ITxValidator +{ + public static readonly AuthorizationListTxValidator Instance = new(); + private AuthorizationListTxValidator() { } + + public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) => + transaction.AuthorizationList switch + { + null or { Length: 0 } => TxErrorMessages.MissingAuthorizationList, + var authorizationList when authorizationList.Any(a => !ValidateAuthoritySignature(a.AuthoritySignature)) => + TxErrorMessages.InvalidAuthoritySignature, + _ => ValidationResult.Success + }; + + private bool ValidateAuthoritySignature(Signature signature) + { + UInt256 sValue = new(signature.SAsSpan, isBigEndian: true); + + return sValue < Secp256K1Curve.HalfNPlusOne && signature.RecoveryId is 0 or 1; + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs index 335b6f02331..2af6892ee13 100644 --- a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs +++ b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs @@ -30,6 +30,7 @@ using Nethermind.Db.Blooms; using Nethermind.Evm; using Nethermind.Evm.TransactionProcessing; +using Nethermind.Facade.Find; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Serialization.Json; @@ -37,6 +38,7 @@ using Nethermind.Specs.Test; using Nethermind.State; using Nethermind.State.Repositories; +using Nethermind.Synchronization; using Nethermind.Trie; using Nethermind.Trie.Pruning; using Nethermind.TxPool; @@ -180,15 +182,16 @@ protected virtual async Task Build(ISpecProvider? specProvider = ReadOnlyState = new ChainHeadReadOnlyStateProvider(BlockTree, StateReader); TransactionComparerProvider = new TransactionComparerProvider(SpecProvider, BlockTree); - TxPool = CreateTxPool(); + CodeInfoRepository codeInfoRepository = new(); + TxPool = CreateTxPool(codeInfoRepository); IChainHeadInfoProvider chainHeadInfoProvider = - new ChainHeadInfoProvider(SpecProvider, BlockTree, StateReader); + new ChainHeadInfoProvider(SpecProvider, BlockTree, StateReader, codeInfoRepository); - NonceManager = new NonceManager(chainHeadInfoProvider.AccountStateProvider); + NonceManager = new NonceManager(chainHeadInfoProvider.ReadOnlyStateProvider); _trieStoreWatcher = new TrieStoreBoundaryWatcher(WorldStateManager, BlockTree, LogManager); - CodeInfoRepository codeInfoRepository = new(); + ReceiptStorage = new InMemoryReceiptStorage(blockTree: BlockTree); VirtualMachine virtualMachine = new(new BlockhashProvider(BlockTree, SpecProvider, State, LogManager), SpecProvider, codeInfoRepository, LogManager); TxProcessor = new TransactionProcessor(SpecProvider, State, virtualMachine, codeInfoRepository, LogManager); @@ -314,12 +317,12 @@ protected virtual IBlockProducerRunner CreateBlockProducerRunner() public virtual ILogManager LogManager { get; set; } = LimboLogs.Instance; - protected virtual TxPool.TxPool CreateTxPool() => + protected virtual TxPool.TxPool CreateTxPool(CodeInfoRepository codeInfoRepository) => new( EthereumEcdsa, new BlobTxStorage(), - new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(SpecProvider), BlockTree, ReadOnlyState), - new TxPoolConfig() { BlobsSupport = BlobsSupportMode.InMemory }, + new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(SpecProvider), BlockTree, ReadOnlyState, codeInfoRepository), + new TxPoolConfig { BlobsSupport = BlobsSupportMode.InMemory }, new TxValidator(SpecProvider.ChainId), LogManager, TransactionComparerProvider.GetDefaultComparer()); @@ -361,8 +364,11 @@ protected virtual Block GetGenesisBlock() genesisBlockBuilder.WithParentBeaconBlockRoot(Keccak.Zero); } - if (SpecProvider.GenesisSpec.ConsensusRequestsEnabled) + if (SpecProvider.GenesisSpec.RequestsEnabled) + { genesisBlockBuilder.WithConsensusRequests(0); + } + genesisBlockBuilder.WithStateRoot(State.StateRoot); return genesisBlockBuilder.TestObject; diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs index a43039ef8af..4b5e591949b 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs @@ -281,7 +281,7 @@ public BlockBuilder WithConsensusRequests(int count) var consensusRequests = new ConsensusRequest[count]; for (var i = 0; i < count; i++) - consensusRequests[i] = new(); + consensusRequests[i] = new Deposit(); return WithConsensusRequests(consensusRequests); } diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/ConsolidationRequestBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/ConsolidationRequestBuilder.cs new file mode 100644 index 00000000000..63628ef305b --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Builders/ConsolidationRequestBuilder.cs @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Core.Test.Builders; +using Nethermind.Core.ConsensusRequests; + +public class ConsolidationRequestBuilder : BuilderBase +{ + public ConsolidationRequestBuilder() => TestObject = new(); + + public ConsolidationRequestBuilder WithSourceAddress(Address sourceAddress) + { + TestObject.SourceAddress = sourceAddress; + + return this; + } + + public ConsolidationRequestBuilder WithSourcePubkey(byte[] SourcePubkey) + { + TestObject.SourcePubkey = SourcePubkey; + + return this; + } + + public ConsolidationRequestBuilder WithTargetPubkey(byte[] TargetPubkey) + { + TestObject.TargetPubkey = TargetPubkey; + + return this; + } + +} diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs index bae4135790c..ac8cf8e0313 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs @@ -115,6 +115,13 @@ public static Hash256 KeccakFromNumber(int i) public static WithdrawalRequest WithdrawalRequestE = new() { SourceAddress = AddressE, ValidatorPubkey = PublicKeyE.Bytes }; public static WithdrawalRequest WithdrawalRequestF = new() { SourceAddress = AddressF, ValidatorPubkey = PublicKeyF.Bytes }; + public static ConsolidationRequest ConsolidationRequestA = new() { SourceAddress = AddressA, SourcePubkey = PublicKeyA.Bytes, TargetPubkey = PublicKeyB.Bytes }; + public static ConsolidationRequest ConsolidationRequestB = new() { SourceAddress = AddressB, SourcePubkey = PublicKeyB.Bytes, TargetPubkey = PublicKeyC.Bytes }; + public static ConsolidationRequest ConsolidationRequestC = new() { SourceAddress = AddressC, SourcePubkey = PublicKeyC.Bytes, TargetPubkey = PublicKeyD.Bytes }; + public static ConsolidationRequest ConsolidationRequestD = new() { SourceAddress = AddressD, SourcePubkey = PublicKeyD.Bytes, TargetPubkey = PublicKeyE.Bytes }; + public static ConsolidationRequest ConsolidationRequestE = new() { SourceAddress = AddressE, SourcePubkey = PublicKeyE.Bytes, TargetPubkey = PublicKeyF.Bytes }; + public static ConsolidationRequest ConsolidationRequestF = new() { SourceAddress = AddressF, SourcePubkey = PublicKeyF.Bytes, TargetPubkey = PublicKeyA.Bytes }; + public static IPEndPoint IPEndPointA = IPEndPoint.Parse("10.0.0.1"); public static IPEndPoint IPEndPointB = IPEndPoint.Parse("10.0.0.2"); public static IPEndPoint IPEndPointC = IPEndPoint.Parse("10.0.0.3"); diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs index cb8c9e984f3..cf81f10a348 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs @@ -7,7 +7,6 @@ using Nethermind.Core.Eip2930; using Nethermind.Crypto; using Nethermind.Int256; -using Nethermind.Logging; namespace Nethermind.Core.Test.Builders { @@ -218,6 +217,22 @@ public TransactionBuilder WithShardBlobTxTypeAndFields(int blobCount = 1, boo return this; } + public TransactionBuilder WithAuthorizationCodeIfAuthorizationListTx() + { + return TestObjectInternal.Type == TxType.SetCode ? WithAuthorizationCode(new AuthorizationTuple(0, Address.Zero, 0, new Signature(new byte[64], 0))) : this; + } + + public TransactionBuilder WithAuthorizationCode(AuthorizationTuple authTuple) + { + TestObjectInternal.AuthorizationList = TestObjectInternal.AuthorizationList is not null ? [.. TestObjectInternal.AuthorizationList, authTuple] : [authTuple]; + return this; + } + public TransactionBuilder WithAuthorizationCode(AuthorizationTuple[] authList) + { + TestObjectInternal.AuthorizationList = authList; + return this; + } + public TransactionBuilder With(Action anyChange) { anyChange(TestObjectInternal); @@ -282,5 +297,11 @@ public TransactionBuilder WithIsServiceTransaction(bool isServiceTransaction) TestObjectInternal.IsServiceTransaction = isServiceTransaction; return this; } + + public TransactionBuilder From(T item) + { + TestObjectInternal = item; + return this; + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Crypto/BlsSignerTests.cs b/src/Nethermind/Nethermind.Core.Test/Crypto/BlsSignerTests.cs new file mode 100644 index 00000000000..8fd58d46c4c --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Crypto/BlsSignerTests.cs @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using FluentAssertions; +using Nethermind.Crypto; +using NUnit.Framework; + +namespace Nethermind.Core.Test.Crypto; + +using G1 = Bls.P1; + +[TestFixture] +public class BlsTests +{ + private static readonly byte[] SkBytes = [0x2c, 0xd4, 0xba, 0x40, 0x6b, 0x52, 0x24, 0x59, 0xd5, 0x7a, 0x0b, 0xed, 0x51, 0xa3, 0x97, 0x43, 0x5c, 0x0b, 0xb1, 0x1d, 0xd5, 0xf3, 0xca, 0x11, 0x52, 0xb3, 0x69, 0x4b, 0xb9, 0x1d, 0x7c, 0x22]; + private static readonly byte[] MsgBytes = [0x3e, 0x00, 0xef, 0x2f, 0x89, 0x5f, 0x40, 0xd6, 0x7f, 0x5b, 0xb8, 0xe8, 0x1f, 0x09, 0xa5, 0xa1, 0x2c, 0x84, 0x0e, 0xc3, 0xce, 0x9a, 0x7f, 0x3b, 0x18, 0x1b, 0xe1, 0x88, 0xef, 0x71, 0x1a, 0x1e]; + + [Test] + public void Calculate_signature() + { + byte[] expected = [0xa5, 0xa0, 0x0d, 0xe9, 0x9d, 0x8f, 0xee, 0x7e, 0x28, 0x81, 0x1b, 0x2c, 0x08, 0xe0, 0xa7, 0xfc, 0x00, 0xa1, 0x10, 0x0c, 0x3d, 0x0f, 0x80, 0x51, 0x9d, 0x43, 0x24, 0x67, 0x1c, 0x29, 0x36, 0xb1, 0xe5, 0xa5, 0x87, 0x7d, 0x46, 0x7a, 0x6d, 0xc6, 0xf5, 0x92, 0xb2, 0x40, 0x7b, 0xcb, 0x12, 0x61, 0x0c, 0x18, 0x8a, 0x6c, 0xdf, 0x57, 0xd1, 0x77, 0x92, 0x00, 0x0f, 0xf7, 0x56, 0xf8, 0x0e, 0xbe, 0xd8, 0x00, 0x88, 0xab, 0x22, 0x9a, 0xa7, 0xe2, 0xc3, 0x24, 0x09, 0xec, 0xfe, 0x5a, 0x8d, 0x44, 0x73, 0xe9, 0x12, 0xfa, 0x19, 0x9e, 0xee, 0xa1, 0x8f, 0x3c, 0x79, 0x8d, 0xc5, 0x28, 0x64, 0x7d]; + BlsSigner.Signature s = BlsSigner.Sign(new(SkBytes, Bls.ByteOrder.LittleEndian), MsgBytes); + s.Bytes.ToArray().Should().Equal(expected); + } + + [Test] + public void Verify_signature() + { + Bls.SecretKey sk = new(SkBytes, Bls.ByteOrder.LittleEndian); + BlsSigner.Signature s = BlsSigner.Sign(sk, MsgBytes); + G1 publicKey = new(); + publicKey.FromSk(sk); + Assert.That(BlsSigner.Verify(publicKey.ToAffine(), s, MsgBytes)); + } + + [Test] + public void Rejects_bad_signature() + { + Bls.SecretKey sk = new(SkBytes, Bls.ByteOrder.LittleEndian); + BlsSigner.Signature s = BlsSigner.Sign(sk, MsgBytes); + Span bytes = stackalloc byte[96]; + s.Bytes.CopyTo(bytes); + bytes[34] += 1; + BlsSigner.Signature bad = new(bytes); + + G1 publicKey = new(); + publicKey.FromSk(sk); + Assert.That(BlsSigner.Verify(publicKey.ToAffine(), bad, MsgBytes), Is.False); + } + + [Test] + public void Public_key_from_private_key() + { + byte[] expected = [0x95, 0x39, 0x27, 0x35, 0x0c, 0x35, 0x31, 0xb0, 0xbc, 0x58, 0x64, 0xcd, 0x9c, 0x5f, 0xe1, 0x34, 0x74, 0xca, 0x0c, 0x9b, 0x59, 0x99, 0x51, 0xa7, 0x76, 0xc4, 0xb9, 0x8d, 0xf6, 0x6a, 0x0e, 0x62, 0x07, 0xa8, 0x5c, 0x7f, 0x7a, 0x85, 0x1a, 0x0c, 0x02, 0x2a, 0x87, 0xc0, 0x29, 0xc3, 0x65, 0x61]; + + G1 publicKey = new(); + publicKey.FromSk(new(SkBytes, Bls.ByteOrder.LittleEndian)); + + Assert.That(publicKey.Compress(), Is.EqualTo(expected)); + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/Crypto/EthereumEcdsaTests.cs b/src/Nethermind/Nethermind.Core.Test/Crypto/EthereumEcdsaTests.cs index a11dc55dd69..64d98d18dea 100644 --- a/src/Nethermind/Nethermind.Core.Test/Crypto/EthereumEcdsaTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Crypto/EthereumEcdsaTests.cs @@ -1,10 +1,15 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; +using System.Net; +using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; +using Nethermind.Int256; using Nethermind.Logging; +using Nethermind.Serialization.Rlp; using NUnit.Framework; namespace Nethermind.Core.Test.Crypto @@ -73,5 +78,23 @@ public void Sign_generic_network() Address? address = ecdsa.RecoverAddress(tx); Assert.That(address, Is.EqualTo(key.Address)); } + + [Test] + [Repeat(3)] + public void RecoverAddress_AuthorizationTupleOfDifferentSize_RecoversAddressCorrectly() + { + PrivateKey signer = Build.A.PrivateKey.TestObject; + AuthorizationTuple authorizationTuple = new EthereumEcdsa(BlockchainIds.GenericNonRealNetwork) + .Sign(signer, + TestContext.CurrentContext.Random.NextULong(), + Build.A.Address.TestObjectInternal, + TestContext.CurrentContext.Random.NextULong()); + + EthereumEcdsa ecdsa = new(BlockchainIds.GenericNonRealNetwork); + + Address? authority = ecdsa.RecoverAddress(authorizationTuple); + + Assert.That(authority, Is.EqualTo(signer.Address)); + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/AuthorizationTupleDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/AuthorizationTupleDecoderTests.cs new file mode 100644 index 00000000000..0d243601940 --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/AuthorizationTupleDecoderTests.cs @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Int256; +using Nethermind.Serialization.Rlp; +using NSubstitute.ExceptionExtensions; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nethermind.Core.Test.Encoding; + +[TestFixture] +public class AuthorizationTupleDecoderTests +{ + public static IEnumerable AuthorizationTupleEncodeCases() + { + yield return new AuthorizationTuple(0, Address.Zero, 0, new Signature(new byte[64], 0)); + yield return new AuthorizationTuple(0, Address.Zero, 0, new Signature(new byte[64], 0)); + yield return new AuthorizationTuple( + ulong.MaxValue, + new Address(Enumerable.Range(0, 20).Select(i => (byte)0xff).ToArray()), + ulong.MaxValue, + new Signature(Enumerable.Range(0, 64).Select(i => (byte)0xff).ToArray(), 1)); + } + + [TestCaseSource(nameof(AuthorizationTupleEncodeCases))] + public void Encode_TupleHasValues_TupleCanBeDecodedToEquivalentTuple(AuthorizationTuple item) + { + AuthorizationTupleDecoder sut = new(); + + RlpStream result = sut.Encode(item); + result.Position = 0; + + sut.Decode(result).Should().BeEquivalentTo(item); + } + + [Test] + public void DecodeValueDecoderContext_CodeAddressIsNull_ThrowsRlpException() + { + RlpStream stream = TupleRlpStreamWithNull(); + + AuthorizationTupleDecoder sut = new(); + Assert.That(() => + { + Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(stream.Data); + sut.Decode(ref decoderContext, RlpBehaviors.None); + } + , Throws.TypeOf()); + } + + private static RlpStream TupleRlpStreamWithNull() + { + Address? codeAddress = null; + Signature sig = new(new byte[64], 0); + int length = + +Rlp.LengthOf(1) + + Rlp.LengthOf(codeAddress) + + Rlp.LengthOf(0) + + Rlp.LengthOf(sig.RecoveryId) + + Rlp.LengthOf(sig.R) + + Rlp.LengthOf(sig.S); + RlpStream stream = new RlpStream(Rlp.LengthOfSequence(length)); + stream.StartSequence(length); + stream.Encode(1); + stream.Encode(codeAddress); + stream.Encode(0); + stream.Encode(sig.RecoveryId); + stream.Encode(sig.R); + stream.Encode(sig.S); + stream.Position = 0; + return stream; + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/ConsensusRequestDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/ConsensusRequestDecoderTests.cs index 3fc4c35588c..c311fb36a7f 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/ConsensusRequestDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/ConsensusRequestDecoderTests.cs @@ -50,6 +50,26 @@ public void Roundtrip_withdrawalRequest() decoded.Should().BeEquivalentTo(withdrawalRequest); } + [Test] + public void Roundtrip_consolidationRequest() + { + byte[] SourcePubkey = new byte[48]; + SourcePubkey[11] = 11; + byte[] TargetPubkey = new byte[48]; + TargetPubkey[22] = 22; + ConsensusRequest consolidationRequest = new ConsolidationRequest() + { + SourceAddress = TestItem.AddressA, + SourcePubkey = SourcePubkey, + TargetPubkey = TargetPubkey + }; + + byte[] rlp = Rlp.Encode(consolidationRequest).Bytes; + ConsensusRequest decoded = Rlp.Decode(rlp); + + decoded.Should().BeEquivalentTo(consolidationRequest); + } + [Test] public void Should_decode_deposit_with_ValueDecoderContext() { @@ -62,7 +82,7 @@ public void Should_decode_deposit_with_ValueDecoderContext() Amount = int.MaxValue }; RlpStream stream = new(1024); - ConsensusRequestDecoder codec = new(); + ConsensusRequestDecoder codec = ConsensusRequestDecoder.Instance; codec.Encode(stream, deposit); @@ -82,7 +102,7 @@ public void Should_decode_withdrawalRequest_with_ValueDecoderContext() Amount = int.MaxValue }; RlpStream stream = new(1024); - ConsensusRequestDecoder codec = new(); + ConsensusRequestDecoder codec = ConsensusRequestDecoder.Instance; codec.Encode(stream, withdrawalRequest); @@ -93,7 +113,27 @@ public void Should_decode_withdrawalRequest_with_ValueDecoderContext() } [Test] - public void Should_encode_deposit_same_for_Rlp_Encode_and_DepositDecoder_Encode() + public void Should_decode_consolidationRequest_with_ValueDecoderContext() + { + ConsensusRequest consolidationRequest = new ConsolidationRequest() + { + SourceAddress = TestItem.AddressA, + SourcePubkey = KeccakTests.KeccakOfAnEmptyString.ToBytes(), + TargetPubkey = KeccakTests.KeccakOfAnEmptyString.ToBytes() + }; + RlpStream stream = new(1024); + ConsensusRequestDecoder codec = ConsensusRequestDecoder.Instance; + + codec.Encode(stream, consolidationRequest); + + Rlp.ValueDecoderContext decoderContext = new(stream.Data.AsSpan()); + ConsolidationRequest? decoded = (ConsolidationRequest?)codec.Decode(ref decoderContext); + + decoded.Should().BeEquivalentTo(consolidationRequest); + } + + [Test] + public void Should_encode_deposit_same_for_Rlp_Encode_and_ConsensusRequestDecoder_Encode() { ConsensusRequest deposit = new Deposit() { @@ -103,14 +143,14 @@ public void Should_encode_deposit_same_for_Rlp_Encode_and_DepositDecoder_Encode( WithdrawalCredentials = KeccakTests.KeccakOfAnEmptyString.ToBytes(), Amount = int.MaxValue }; - byte[] rlp1 = new ConsensusRequestDecoder().Encode(deposit).Bytes; + byte[] rlp1 = ConsensusRequestDecoder.Instance.Encode(deposit).Bytes; byte[] rlp2 = Rlp.Encode(deposit).Bytes; rlp1.Should().BeEquivalentTo(rlp2); } [Test] - public void Should_encode_withdrawalRequest_same_for_Rlp_Encode_and_DepositDecoder_Encode() + public void Should_encode_withdrawalRequest_same_for_Rlp_Encode_and_ConsensusRequestDecoder_Encode() { ConsensusRequest withdrawalRequest = new WithdrawalRequest() { @@ -118,12 +158,27 @@ public void Should_encode_withdrawalRequest_same_for_Rlp_Encode_and_DepositDecod ValidatorPubkey = KeccakTests.KeccakOfAnEmptyString.ToBytes(), Amount = int.MaxValue }; - byte[] rlp1 = new ConsensusRequestDecoder().Encode(withdrawalRequest).Bytes; + byte[] rlp1 = ConsensusRequestDecoder.Instance.Encode(withdrawalRequest).Bytes; byte[] rlp2 = Rlp.Encode(withdrawalRequest).Bytes; rlp1.Should().BeEquivalentTo(rlp2); } + [Test] + public void Should_encode_consolidationRequest_same_for_Rlp_Encode_and_ConsensusRequestDecoder_Encode() + { + ConsensusRequest consolidationRequest = new ConsolidationRequest() + { + SourceAddress = TestItem.AddressA, + SourcePubkey = KeccakTests.KeccakOfAnEmptyString.ToBytes(), + TargetPubkey = KeccakTests.KeccakOfAnEmptyString.ToBytes() + }; + byte[] rlp1 = ConsensusRequestDecoder.Instance.Encode(consolidationRequest).Bytes; + byte[] rlp2 = Rlp.Encode(consolidationRequest).Bytes; + + rlp1.Should().BeEquivalentTo(rlp2); + } + [Test] public void Should_encode_ConsensusRequests_Array() { @@ -142,12 +197,18 @@ public void Should_encode_ConsensusRequests_Array() SourceAddress = TestItem.AddressA, ValidatorPubkey = KeccakTests.KeccakOfAnEmptyString.ToBytes(), Amount = int.MaxValue + }, + new ConsolidationRequest() + { + SourceAddress = TestItem.AddressA, + SourcePubkey = KeccakTests.KeccakOfAnEmptyString.ToBytes(), + TargetPubkey = KeccakTests.KeccakOfAnEmptyString.ToBytes() } }; byte[] rlp = Rlp.Encode(requests).Bytes; RlpStream rlpStream = new(rlp); - ConsensusRequest[] decoded = Rlp.DecodeArray(rlpStream, new ConsensusRequestDecoder()); + ConsensusRequest[] decoded = Rlp.DecodeArray(rlpStream, ConsensusRequestDecoder.Instance); decoded.Should().BeEquivalentTo(requests); } } diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/LogEntryDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/LogEntryDecoderTests.cs index bc6188fedfe..9f3f323fe9e 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/LogEntryDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/LogEntryDecoderTests.cs @@ -22,7 +22,7 @@ public void Can_do_roundtrip(bool valueDecode) LogEntry decoded = valueDecode ? Rlp.Decode(rlp.Bytes.AsSpan()) : Rlp.Decode(rlp); Assert.That(decoded.Data, Is.EqualTo(logEntry.Data), "data"); - Assert.That(decoded.LoggersAddress, Is.EqualTo(logEntry.LoggersAddress), "address"); + Assert.That(decoded.Address, Is.EqualTo(logEntry.Address), "address"); Assert.That(decoded.Topics, Is.EqualTo(logEntry.Topics), "topics"); } @@ -35,7 +35,7 @@ public void Can_do_roundtrip_ref_struct() LogEntryDecoder.DecodeStructRef(ref valueDecoderContext, RlpBehaviors.None, out LogEntryStructRef decoded); Assert.That(Bytes.AreEqual(logEntry.Data, decoded.Data), "data"); - Assert.That(logEntry.LoggersAddress == decoded.LoggersAddress, "address"); + Assert.That(logEntry.Address == decoded.Address, "address"); Span buffer = stackalloc byte[32]; KeccaksIterator iterator = new(decoded.TopicsRlp, buffer); @@ -64,7 +64,7 @@ public void Can_do_roundtrip_rlp_stream() LogEntry deserialized = decoder.Decode(new RlpStream(encoded.Bytes))!; Assert.That(deserialized.Data, Is.EqualTo(logEntry.Data), "data"); - Assert.That(deserialized.LoggersAddress, Is.EqualTo(logEntry.LoggersAddress), "address"); + Assert.That(deserialized.Address, Is.EqualTo(logEntry.Address), "address"); Assert.That(deserialized.Topics, Is.EqualTo(logEntry.Topics), "topics"); } diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/ShardBlobTxDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/ShardBlobTxDecoderTests.cs index bf70a7b46f9..ced26306e3a 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/ShardBlobTxDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/ShardBlobTxDecoderTests.cs @@ -8,6 +8,7 @@ using FluentAssertions; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Logging; using Nethermind.Serialization.Rlp; @@ -24,7 +25,7 @@ public partial class ShardBlobTxDecoderTests public static Task SetUp() => KzgPolynomialCommitments.InitializeAsync(); public static IEnumerable<(Transaction, string)> TestCaseSource() => - TxDecoderTests.TestObjectsSource().Select(tos => (tos.Item1 + TxDecoderTests.TestCaseSource().Select(tos => (Build.A.Transaction.From(tos.Item1) .WithChainId(TestBlockchainIds.ChainId) .WithShardBlobTxTypeAndFields(2, false) .SignedAndResolved() diff --git a/src/Nethermind/Nethermind.Core.Test/FixedBlockChainHeadSpecProvider.cs b/src/Nethermind/Nethermind.Core.Test/FixedBlockChainHeadSpecProvider.cs index 7030287b6ef..f742288c1b4 100644 --- a/src/Nethermind/Nethermind.Core.Test/FixedBlockChainHeadSpecProvider.cs +++ b/src/Nethermind/Nethermind.Core.Test/FixedBlockChainHeadSpecProvider.cs @@ -27,6 +27,8 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public long? DaoBlockNumber => specProvider.DaoBlockNumber; + public ulong? BeaconChainGenesisTimestamp => specProvider.BeaconChainGenesisTimestamp; + public ulong NetworkId => specProvider.NetworkId; public ulong ChainId => specProvider.ChainId; diff --git a/src/Nethermind/Nethermind.Core/AccountStateProviderExtensions.cs b/src/Nethermind/Nethermind.Core/AccountStateProviderExtensions.cs index 45a8c09be5f..b07d3e9ebdd 100644 --- a/src/Nethermind/Nethermind.Core/AccountStateProviderExtensions.cs +++ b/src/Nethermind/Nethermind.Core/AccountStateProviderExtensions.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core.Specs; +using System; namespace Nethermind.Core { @@ -9,8 +10,5 @@ public static class AccountStateProviderExtensions { public static bool HasCode(this IAccountStateProvider stateProvider, Address address) => stateProvider.TryGetAccount(address, out AccountStruct account) && account.HasCode; - - public static bool IsInvalidContractSender(this IAccountStateProvider stateProvider, IReleaseSpec spec, Address address) => - spec.IsEip3607Enabled && stateProvider.HasCode(address); } } diff --git a/src/Nethermind/Nethermind.Core/Address.cs b/src/Nethermind/Nethermind.Core/Address.cs index f09c9e5b76d..01507842989 100644 --- a/src/Nethermind/Nethermind.Core/Address.cs +++ b/src/Nethermind/Nethermind.Core/Address.cs @@ -3,6 +3,7 @@ using System; using System.ComponentModel; +using System.Diagnostics; using System.Globalization; using System.Numerics; using System.Runtime.CompilerServices; @@ -19,6 +20,7 @@ namespace Nethermind.Core { [JsonConverter(typeof(AddressConverter))] [TypeConverter(typeof(AddressTypeConverter))] + [DebuggerDisplay("{ToString()}")] public class Address : IEquatable
, IComparable
{ public const int Size = 20; @@ -26,6 +28,8 @@ public class Address : IEquatable
, IComparable
private const int PrefixedHexCharsCount = 2 + HexCharsCount; // 0x5a4eab120fb44eb6684e5e32785702ff45ea344d public static Address Zero { get; } = new(new byte[Size]); + public static Address MaxValue { get; } = new("0xffffffffffffffffffffffffffffffffffffffff"); + public const string SystemUserHex = "0xfffffffffffffffffffffffffffffffffffffffe"; public static Address SystemUser { get; } = new(SystemUserHex); diff --git a/src/Nethermind/Nethermind.Core/AddressConverter.cs b/src/Nethermind/Nethermind.Core/AddressConverter.cs index 808e383218f..9fcab4431b3 100644 --- a/src/Nethermind/Nethermind.Core/AddressConverter.cs +++ b/src/Nethermind/Nethermind.Core/AddressConverter.cs @@ -2,10 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Serialization; using Nethermind.Core; +using Nethermind.Core.Extensions; namespace Nethermind.Serialization.Json; @@ -27,4 +29,17 @@ public override void Write( { ByteArrayConverter.Convert(writer, address.Bytes, skipLeadingZeros: false); } + + [SkipLocalsInit] + public override void WriteAsPropertyName(Utf8JsonWriter writer, + Address value, + JsonSerializerOptions options) + { + Span addressBytes = stackalloc byte[Address.Size * 2 + 2]; + addressBytes[0] = (byte)'0'; + addressBytes[1] = (byte)'x'; + Span hex = addressBytes.Slice(2); + value.Bytes.AsSpan().OutputBytesToByteHex(hex, false); + writer.WritePropertyName(addressBytes); + } } diff --git a/src/Nethermind/Nethermind.Core/AuthorizationTuple.cs b/src/Nethermind/Nethermind.Core/AuthorizationTuple.cs new file mode 100644 index 00000000000..69fd3b53768 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/AuthorizationTuple.cs @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; +using System; + +namespace Nethermind.Core; +public class AuthorizationTuple( + ulong chainId, + Address codeAddress, + ulong nonce, + Signature sig, + Address? authority = null) +{ + public AuthorizationTuple( + ulong chainId, + Address codeAddress, + ulong nonce, + ulong yParity, + byte[] r, + byte[] s, + Address? authority = null) : this(chainId, codeAddress, nonce, new Signature(r, s, yParity + Signature.VOffset), authority) + { } + + public ulong ChainId { get; } = chainId; + public Address CodeAddress { get; protected set; } = codeAddress; + public ulong Nonce { get; } = nonce; + public Signature AuthoritySignature { get; protected set; } = sig; + + /// + /// may be recovered at a later point. + /// + public Address? Authority { get; set; } = authority; + + public override string ToString() => $"Delegation authorization from {Authority} to {CodeAddress} on chain {ChainId} with Nonce {Nonce}"; +} diff --git a/src/Nethermind/Nethermind.Core/Bloom.cs b/src/Nethermind/Nethermind.Core/Bloom.cs index 05d3f4e5d9f..1cd212b0606 100644 --- a/src/Nethermind/Nethermind.Core/Bloom.cs +++ b/src/Nethermind/Nethermind.Core/Bloom.cs @@ -121,7 +121,7 @@ public void Add(LogEntry[] logEntries, Bloom? blockBloom = null) for (int entryIndex = 0; entryIndex < logEntries.Length; entryIndex++) { LogEntry logEntry = logEntries[entryIndex]; - byte[] addressBytes = logEntry.LoggersAddress.Bytes; + byte[] addressBytes = logEntry.Address.Bytes; Set(addressBytes, blockBloom); Hash256[] topics = logEntry.Topics; for (int topicIndex = 0; topicIndex < topics.Length; topicIndex++) @@ -144,7 +144,7 @@ public void Accumulate(Bloom? bloom) public bool Matches(LogEntry logEntry) { - if (Matches(logEntry.LoggersAddress)) + if (Matches(logEntry.Address)) { Hash256[] topics = logEntry.Topics; for (int topicIndex = 0; topicIndex < topics.Length; topicIndex++) @@ -279,7 +279,7 @@ public override readonly bool Equals(object? obj) public readonly bool Matches(LogEntry logEntry) { - if (Matches(logEntry.LoggersAddress)) + if (Matches(logEntry.Address)) { Hash256[] topics = logEntry.Topics; for (int topicIndex = 0; topicIndex < topics.Length; topicIndex++) diff --git a/src/Nethermind/Nethermind.Core/ByteArrayConverter.cs b/src/Nethermind/Nethermind.Core/ByteArrayConverter.cs index 9e5c9b2cd3c..e02067a2cc7 100644 --- a/src/Nethermind/Nethermind.Core/ByteArrayConverter.cs +++ b/src/Nethermind/Nethermind.Core/ByteArrayConverter.cs @@ -15,7 +15,7 @@ namespace Nethermind.Serialization.Json; public class ByteArrayConverter : JsonConverter { - private readonly static ushort _hexPrefix = MemoryMarshal.Cast("0x"u8)[0]; + private static readonly ushort _hexPrefix = MemoryMarshal.Cast("0x"u8)[0]; public override byte[]? Read( ref Utf8JsonReader reader, diff --git a/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs b/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs index c43e1ee0b0a..827c592d5e6 100644 --- a/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs +++ b/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs @@ -363,5 +363,6 @@ public void Dispose() public Span AsSpan() => _array.AsSpan(0, Count); - public ReadOnlyMemory AsMemory() => new(_array, 0, Count); + public Memory AsMemory() => new(_array, 0, Count); + public ReadOnlyMemory AsReadOnlyMemory() => new(_array, 0, Count); } diff --git a/src/Nethermind/Nethermind.Core/Collections/EnumerableExtensions.cs b/src/Nethermind/Nethermind.Core/Collections/EnumerableExtensions.cs index 1ee3db6092e..8d51f46b482 100644 --- a/src/Nethermind/Nethermind.Core/Collections/EnumerableExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Collections/EnumerableExtensions.cs @@ -11,9 +11,26 @@ public static class EnumerableExtensions { public static void ForEach(this IEnumerable list, Action action) { - list.ToList().ForEach(action); + foreach (T element in list) + { + action(element); + } } public static bool NullableSequenceEqual(this IEnumerable? first, IEnumerable? second) => first is not null ? second is not null && first.SequenceEqual(second) : second is null; + + public static bool ContainsDuplicates(this IEnumerable list, int? count = null, IEqualityComparer? comparer = null) + { + HashSet hashSet = count is null ? new HashSet(comparer) : new HashSet(count.Value, comparer); + foreach (T element in list) + { + if (!hashSet.Add(element)) + { + return true; + } + } + + return false; + } } diff --git a/src/Nethermind/Nethermind.Core/Collections/EnumerableWithCount.cs b/src/Nethermind/Nethermind.Core/Collections/EnumerableWithCount.cs new file mode 100644 index 00000000000..1fdebf464b3 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Collections/EnumerableWithCount.cs @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections; +using System.Collections.Generic; + +namespace Nethermind.Core.Collections; + +public readonly record struct EnumerableWithCount(IEnumerable Enumerable, int Count) : IEnumerable +{ + // ReSharper disable once NotDisposedResourceIsReturned + public IEnumerator GetEnumerator() => Enumerable.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} + diff --git a/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsensusRequest.cs b/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsensusRequest.cs index b5cbf9f8e34..e9e5f6d38e1 100644 --- a/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsensusRequest.cs +++ b/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsensusRequest.cs @@ -10,39 +10,23 @@ namespace Nethermind.Core.ConsensusRequests; public enum ConsensusRequestsType : byte { Deposit = 0, - WithdrawalRequest = 1 + WithdrawalRequest = 1, + ConsolidationRequest = 2 } -public class ConsensusRequest +public abstract class ConsensusRequest { [JsonIgnore] public ConsensusRequestsType Type { get; protected set; } - - [JsonIgnore] - public ulong AmountField { get; protected set; } - - [JsonIgnore] - public Address? SourceAddressField { get; protected set; } - - [JsonIgnore] - public Memory? PubKeyField { get; set; } - - [JsonIgnore] - public byte[]? WithdrawalCredentialsField { get; protected set; } - - [JsonIgnore] - public byte[]? SignatureField { get; protected set; } - - [JsonIgnore] - public ulong? IndexField { get; protected set; } } public static class ConsensusRequestExtensions { - public static (int depositCount, int withdrawalRequestCount) GetTypeCounts(this ConsensusRequest[]? requests) + public static (int depositCount, int withdrawalRequestCount, int consolidationRequestCount) GetTypeCounts(this ConsensusRequest[]? requests) { int depositCount = 0; int withdrawalRequestCount = 0; + int consolidationRequestCount = 0; int length = requests?.Length ?? 0; for (int i = 0; i < length; i++) { @@ -50,35 +34,45 @@ public static (int depositCount, int withdrawalRequestCount) GetTypeCounts(this { depositCount++; } - else + else if (requests[i].Type == ConsensusRequestsType.WithdrawalRequest) { withdrawalRequestCount++; } + else + { + consolidationRequestCount++; + } } - return (depositCount, withdrawalRequestCount); + return (depositCount, withdrawalRequestCount, consolidationRequestCount); } - public static (Deposit[]? deposits, WithdrawalRequest[]? withdrawalRequests) SplitRequests(this ConsensusRequest[]? requests) + public static (Deposit[]? deposits, WithdrawalRequest[]? withdrawalRequests, ConsolidationRequest[]? consolidationRequests) SplitRequests(this ConsensusRequest[]? requests) { - if (requests is null) return (null, null); - (int depositCount, int withdrawalRequestCount) = requests.GetTypeCounts(); + if (requests is null) return (null, null, null); + (int depositCount, int withdrawalRequestCount, int consolidationRequestCount) = requests.GetTypeCounts(); Deposit[] deposits = new Deposit[depositCount]; WithdrawalRequest[] withdrawalRequests = new WithdrawalRequest[withdrawalRequestCount]; + ConsolidationRequest[] consolidationRequests = new ConsolidationRequest[consolidationRequestCount]; int depositIndex = 0; int withdrawalRequestIndex = 0; + int consolidationRequestIndex = 0; for (int i = 0; i < requests.Length; i++) { if (requests[i].Type == ConsensusRequestsType.Deposit) { deposits[depositIndex++] = (Deposit)requests[i]; } - else + else if (requests[i].Type == ConsensusRequestsType.WithdrawalRequest) { withdrawalRequests[withdrawalRequestIndex++] = (WithdrawalRequest)requests[i]; } + else + { + consolidationRequests[consolidationRequestIndex++] = (ConsolidationRequest)requests[i]; + } } - return (deposits, withdrawalRequests); + return (deposits, withdrawalRequests, consolidationRequests); } } diff --git a/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsolidationRequest.cs b/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsolidationRequest.cs new file mode 100644 index 00000000000..abf79b924a1 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/ConsensusRequests/ConsolidationRequest.cs @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only +using System; +using Nethermind.Core.Extensions; + +namespace Nethermind.Core.ConsensusRequests; + +/// +/// Represents a Deposit that has been validated at the consensus layer. +/// +public class ConsolidationRequest : ConsensusRequest +{ + public ConsolidationRequest() + { + Type = ConsensusRequestsType.ConsolidationRequest; + } + public Address? SourceAddress { get; set; } + public Memory? SourcePubkey { get; set; } + + public Memory? TargetPubkey { get; set; } + + public override string ToString() => ToString(string.Empty); + + public string ToString(string indentation) => @$"{indentation}{nameof(ConsolidationRequest)} + {{ {nameof(SourceAddress)}: {SourceAddress}, + {nameof(SourcePubkey)}: {SourcePubkey?.Span.ToHexString()}, + {nameof(TargetPubkey)}: {TargetPubkey?.Span.ToHexString()}, + }}"; + + +} diff --git a/src/Nethermind/Nethermind.Core/ConsensusRequests/Deposit.cs b/src/Nethermind/Nethermind.Core/ConsensusRequests/Deposit.cs index 09da1d1f8a8..50b46fc27c2 100644 --- a/src/Nethermind/Nethermind.Core/ConsensusRequests/Deposit.cs +++ b/src/Nethermind/Nethermind.Core/ConsensusRequests/Deposit.cs @@ -3,7 +3,6 @@ using System; using Nethermind.Core.Extensions; -using System.Text; namespace Nethermind.Core.ConsensusRequests; @@ -17,34 +16,13 @@ public Deposit() Type = ConsensusRequestsType.Deposit; Amount = 0; } - public Memory? Pubkey - { - get { return PubKeyField; } - set { PubKeyField = value; } - } + public Memory? Pubkey { get; set; } + public byte[]? WithdrawalCredentials { get; set; } - public byte[]? WithdrawalCredentials - { - get { return WithdrawalCredentialsField; } - set { WithdrawalCredentialsField = value; } - } + public ulong Amount { get; set; } - public ulong Amount - { - get { return AmountField; } - set { AmountField = value; } - } - - public byte[]? Signature - { - get { return SignatureField; } - set { SignatureField = value; } - } - public ulong? Index - { - get { return IndexField; } - set { IndexField = value; } - } + public byte[]? Signature { get; set; } + public ulong? Index { get; set; } public override string ToString() => ToString(string.Empty); public string ToString(string indentation) => @$"{indentation}{nameof(Deposit)} diff --git a/src/Nethermind/Nethermind.Core/ConsensusRequests/WithdrawalRequest.cs b/src/Nethermind/Nethermind.Core/ConsensusRequests/WithdrawalRequest.cs index 355205d5174..f9d79f127c7 100644 --- a/src/Nethermind/Nethermind.Core/ConsensusRequests/WithdrawalRequest.cs +++ b/src/Nethermind/Nethermind.Core/ConsensusRequests/WithdrawalRequest.cs @@ -17,23 +17,11 @@ public WithdrawalRequest() Type = ConsensusRequestsType.WithdrawalRequest; Amount = 0; } - public Address? SourceAddress - { - get { return SourceAddressField; } - set { SourceAddressField = value; } - } + public Address? SourceAddress { get; set; } - public Memory? ValidatorPubkey - { - get { return PubKeyField; } - set { PubKeyField = value; } - } + public Memory? ValidatorPubkey { get; set; } - public ulong Amount - { - get { return AmountField; } - set { AmountField = value; } - } + public ulong Amount { get; set; } public override string ToString() => ToString(string.Empty); public string ToString(string indentation) => @$"{indentation}{nameof(WithdrawalRequest)} diff --git a/src/Nethermind/Nethermind.Core/Eip2930/AccessList.cs b/src/Nethermind/Nethermind.Core/Eip2930/AccessList.cs index 86bc94d0809..9372639ce52 100644 --- a/src/Nethermind/Nethermind.Core/Eip2930/AccessList.cs +++ b/src/Nethermind/Nethermind.Core/Eip2930/AccessList.cs @@ -26,6 +26,7 @@ private AccessList(List<(Address address, int count)> addresses, List k public static AccessList Empty { get; } = new(new List<(Address, int)>(), new List()); public bool IsEmpty => _addresses.Count == 0; + public (int AddressesCount, int StorageKeysCount) Count => (_addresses.Count, _keys.Count); public class Builder { @@ -72,26 +73,21 @@ public AccessList Build() IEnumerator<(Address Address, StorageKeysEnumerable StorageKeys)> IEnumerable<(Address Address, StorageKeysEnumerable StorageKeys)>.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public struct Enumerator : IEnumerator<(Address Address, StorageKeysEnumerable StorageKeys)>, IEnumerator<(Address Address, IEnumerable StorageKeys)> + public struct Enumerator(AccessList accessList) : IEnumerator<(Address Address, StorageKeysEnumerable StorageKeys)>, + IEnumerator<(Address Address, IEnumerable StorageKeys)> { - private readonly AccessList _accessList; private int _index = -1; private int _keysIndex = 0; - public Enumerator(AccessList accessList) - { - _accessList = accessList; - } - public bool MoveNext() { _index++; if (_index > 0) { - _keysIndex += CollectionsMarshal.AsSpan(_accessList._addresses)[_index - 1].count; + _keysIndex += CollectionsMarshal.AsSpan(accessList._addresses)[_index - 1].count; } - return _index < _accessList._addresses.Count; + return _index < accessList._addresses.Count; } public void Reset() @@ -104,8 +100,8 @@ public readonly (Address Address, StorageKeysEnumerable StorageKeys) Current { get { - ref readonly var addressCount = ref CollectionsMarshal.AsSpan(_accessList._addresses)[_index]; - return (addressCount.address, new StorageKeysEnumerable(_accessList, _keysIndex, addressCount.count)); + ref readonly var addressCount = ref CollectionsMarshal.AsSpan(accessList._addresses)[_index]; + return (addressCount.address, new StorageKeysEnumerable(accessList, _keysIndex, addressCount.count)); } } diff --git a/src/Nethermind/Nethermind.Core/Eip7251Constants.cs b/src/Nethermind/Nethermind.Core/Eip7251Constants.cs new file mode 100644 index 00000000000..4fc30bd2601 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Eip7251Constants.cs @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Core; + +public static class Eip7251Constants +{ + public static readonly Address ConsolidationRequestPredeployAddress = new("0x00b42dbF2194e931E80326D950320f7d9Dbeac02"); +} diff --git a/src/Nethermind/Nethermind.Core/Eip7702Constants.cs b/src/Nethermind/Nethermind.Core/Eip7702Constants.cs new file mode 100644 index 00000000000..d54ff84f4b4 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Eip7702Constants.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Core; +public static class Eip7702Constants +{ + public const byte Magic = 0x05; + public static ReadOnlySpan DelegationHeader => [0xef, 0x01, 0x00]; + private static readonly int HeaderLength = DelegationHeader.Length; + public static bool IsDelegatedCode(ReadOnlySpan code) => + code.Length == HeaderLength + Address.Size + && DelegationHeader.SequenceEqual(code.Slice(0, DelegationHeader.Length)); +} diff --git a/src/Nethermind/Nethermind.Core/Extensions/Bytes.Vector.cs b/src/Nethermind/Nethermind.Core/Extensions/Bytes.Vector.cs index 5f06d3e2d7a..6e732853342 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/Bytes.Vector.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/Bytes.Vector.cs @@ -38,18 +38,18 @@ public static void Avx2Reverse256InPlace(Span bytes) } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - public static void Or(this Span thisSpam, Span valueSpam) + public static void Or(this Span thisSpan, ReadOnlySpan valueSpan) { - var length = thisSpam.Length; - if (length != valueSpam.Length) + var length = thisSpan.Length; + if (length != valueSpan.Length) { throw new ArgumentException("Both byte spans has to be same length."); } int i = 0; - fixed (byte* thisPtr = thisSpam) - fixed (byte* valuePtr = valueSpam) + fixed (byte* thisPtr = thisSpan) + fixed (byte* valuePtr = valueSpan) { if (Avx2.IsSupported) { @@ -73,7 +73,47 @@ public static void Or(this Span thisSpam, Span valueSpam) for (; i < length; i++) { - thisSpam[i] |= valueSpam[i]; + thisSpan[i] |= valueSpan[i]; + } + } + + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static void Xor(this Span thisSpan, ReadOnlySpan valueSpan) + { + var length = thisSpan.Length; + if (length != valueSpan.Length) + { + throw new ArgumentException("Both byte spans has to be same length."); + } + + int i = 0; + + fixed (byte* thisPtr = thisSpan) + fixed (byte* valuePtr = valueSpan) + { + if (Avx2.IsSupported) + { + for (; i < length - (Vector256.Count - 1); i += Vector256.Count) + { + Vector256 b1 = Avx2.LoadVector256(thisPtr + i); + Vector256 b2 = Avx2.LoadVector256(valuePtr + i); + Avx2.Store(thisPtr + i, Avx2.Xor(b1, b2)); + } + } + else if (Sse2.IsSupported) + { + for (; i < length - (Vector128.Count - 1); i += Vector128.Count) + { + Vector128 b1 = Sse2.LoadVector128(thisPtr + i); + Vector128 b2 = Sse2.LoadVector128(valuePtr + i); + Sse2.Store(thisPtr + i, Sse2.Xor(b1, b2)); + } + } + } + + for (; i < length; i++) + { + thisSpan[i] ^= valueSpan[i]; } } diff --git a/src/Nethermind/Nethermind.Core/Extensions/EnumerableExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/EnumerableExtensions.cs index 19eff3af5c4..216af63604b 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/EnumerableExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/EnumerableExtensions.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; using System.Linq; using Nethermind.Core.Collections; @@ -14,5 +15,17 @@ public static ISet AsSet(this IEnumerable enumerable) => public static ArrayPoolList ToPooledList(this IEnumerable enumerable, int count) => new(count, enumerable); public static ArrayPoolList ToPooledList(this IReadOnlyCollection collection) => new(collection.Count, collection); + + public static IEnumerable Shuffle(this IEnumerable enumerable, Random rng, int maxSize = 100) + { + using ArrayPoolList buffer = new(maxSize, enumerable); + for (int i = 0; i < buffer.Count; i++) + { + int j = rng.Next(i, buffer.Count); + yield return buffer[j]; + + buffer[j] = buffer[i]; + } + } } } diff --git a/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs index 1d6f49d57ee..bc45c174eb3 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs @@ -55,7 +55,7 @@ public static string ToHexString(this in Span span, bool withZeroX, bool n } [DebuggerStepThrough] - private unsafe static string ToHexViaLookup(ReadOnlySpan bytes, bool withZeroX, bool skipLeadingZeros, bool withEip55Checksum) + private static unsafe string ToHexViaLookup(ReadOnlySpan bytes, bool withZeroX, bool skipLeadingZeros, bool withEip55Checksum) { if (withEip55Checksum) { @@ -82,7 +82,7 @@ private unsafe static string ToHexViaLookup(ReadOnlySpan bytes, bool withZ } } - unsafe readonly struct StringParams(byte* input, int inputLength, int leadingZeros, bool withZeroX) + readonly unsafe struct StringParams(byte* input, int inputLength, int leadingZeros, bool withZeroX) { private readonly byte* _input = input; public readonly int InputLength = inputLength; diff --git a/src/Nethermind/Nethermind.Core/ILogEntry.cs b/src/Nethermind/Nethermind.Core/ILogEntry.cs new file mode 100644 index 00000000000..147e0bab78c --- /dev/null +++ b/src/Nethermind/Nethermind.Core/ILogEntry.cs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; + +namespace Nethermind.Core +{ + public interface ILogEntry + { + Address Address { get; } + Hash256[] Topics { get; } + byte[] Data { get; } + } +} diff --git a/src/Nethermind/Nethermind.Core/ITimestamp.cs b/src/Nethermind/Nethermind.Core/ITimestamper.cs similarity index 100% rename from src/Nethermind/Nethermind.Core/ITimestamp.cs rename to src/Nethermind/Nethermind.Core/ITimestamper.cs diff --git a/src/Nethermind/Nethermind.Core/LogEntry.cs b/src/Nethermind/Nethermind.Core/LogEntry.cs index 94e22c8323d..1946a102f8e 100644 --- a/src/Nethermind/Nethermind.Core/LogEntry.cs +++ b/src/Nethermind/Nethermind.Core/LogEntry.cs @@ -6,16 +6,16 @@ namespace Nethermind.Core { - public class LogEntry + public class LogEntry : ILogEntry { public LogEntry(Address address, byte[] data, Hash256[] topics) { - LoggersAddress = address; + Address = address; Data = data; Topics = topics; } - public Address LoggersAddress { get; } + public Address Address { get; } public Hash256[] Topics { get; } public byte[] Data { get; } } @@ -24,17 +24,17 @@ public ref struct LogEntryStructRef { public LogEntryStructRef(AddressStructRef address, ReadOnlySpan data, ReadOnlySpan topicsRlp) { - LoggersAddress = address; + Address = address; Data = data; TopicsRlp = topicsRlp; Topics = null; } - public AddressStructRef LoggersAddress; + public AddressStructRef Address; public LogEntryStructRef(LogEntry logEntry) { - LoggersAddress = logEntry.LoggersAddress.ToStructRef(); + Address = logEntry.Address.ToStructRef(); Data = logEntry.Data; Topics = logEntry.Topics; TopicsRlp = default; diff --git a/src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs b/src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs index 31261d6a34d..cfb3541fa4b 100644 --- a/src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs +++ b/src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs @@ -16,6 +16,7 @@ public CompositePublisher(params IPublisher[] publishers) public async Task PublishAsync(T data) where T : class { + // TODO: .Net 9 stackalloc Task[] tasks = new Task[_publishers.Length]; for (int i = 0; i < _publishers.Length; i++) { diff --git a/src/Nethermind/Nethermind.Core/Specs/IEip1559Spec.cs b/src/Nethermind/Nethermind.Core/Specs/IEip1559Spec.cs index 0d343448551..811a4510a63 100644 --- a/src/Nethermind/Nethermind.Core/Specs/IEip1559Spec.cs +++ b/src/Nethermind/Nethermind.Core/Specs/IEip1559Spec.cs @@ -15,7 +15,8 @@ public interface IEip1559Spec /// bool IsEip1559Enabled { get; } public long Eip1559TransitionBlock { get; } - public Address? Eip1559FeeCollector => null; + // Collects for both EIP-1559 and EIP-4844-Pectra + public Address? FeeCollector => null; public UInt256? Eip1559BaseFeeMinValue => null; public UInt256 ForkBaseFee { get; } public UInt256 BaseFeeMaxChangeDenominator { get; } diff --git a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs index dcd68614ec8..0189f27f4fa 100644 --- a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs @@ -283,6 +283,15 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec bool WithdrawalRequestsEnabled => IsEip7002Enabled; Address Eip7002ContractAddress { get; } + + /// + /// EIP-7251: triggered consolidations + /// + bool IsEip7251Enabled { get; } + bool ConsolidationRequestsEnabled => IsEip7251Enabled; + Address Eip7251ContractAddress { get; } + + /// /// Save historical block hashes in state /// @@ -299,6 +308,17 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec /// bool IsEip6780Enabled { get; } + /// + /// Transactions that allows code delegation for EOA + /// + bool IsEip7702Enabled { get; } + + /// + /// Blob base fee collection for Gnosis + /// + bool IsEip4844FeeCollectorEnabled { get; } + + /// /// Secp256r1 precompile /// bool IsRip7212Enabled { get; } @@ -390,6 +410,8 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec public bool BlobBaseFeeEnabled => IsEip4844Enabled; - public bool ConsensusRequestsEnabled => WithdrawalRequestsEnabled || DepositsEnabled; + bool IsAuthorizationListEnabled => IsEip7702Enabled; + + public bool RequestsEnabled => ConsolidationRequestsEnabled || WithdrawalRequestsEnabled || DepositsEnabled; } } diff --git a/src/Nethermind/Nethermind.Core/Specs/ISpecProvider.cs b/src/Nethermind/Nethermind.Core/Specs/ISpecProvider.cs index 15b13965d91..67bb4619bd8 100644 --- a/src/Nethermind/Nethermind.Core/Specs/ISpecProvider.cs +++ b/src/Nethermind/Nethermind.Core/Specs/ISpecProvider.cs @@ -51,6 +51,8 @@ public interface ISpecProvider /// long? DaoBlockNumber { get; } + ulong? BeaconChainGenesisTimestamp { get; } + /// /// Unique identifier of the chain that allows to sign messages for the specified chain only. /// It is also used when verifying if sync peers are on the same chain. diff --git a/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs b/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs index 86041606fc4..01c65e977f9 100644 --- a/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs +++ b/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs @@ -71,13 +71,17 @@ public class ReleaseSpecDecorator(IReleaseSpec spec) : IReleaseSpec public Address DepositContractAddress => spec.DepositContractAddress; public bool IsEip7002Enabled => spec.IsEip7002Enabled; public Address Eip7002ContractAddress => spec.Eip7002ContractAddress; + public bool IsEip7251Enabled => spec.IsEip7251Enabled; + public Address Eip7251ContractAddress => spec.Eip7251ContractAddress; public virtual bool IsEip2935Enabled => spec.IsEip2935Enabled; public virtual bool IsEip7709Enabled => spec.IsEip7709Enabled; public virtual Address Eip2935ContractAddress => spec.Eip2935ContractAddress; public virtual bool IsEip6780Enabled => spec.IsEip6780Enabled; + public bool IsEip7702Enabled => spec.IsEip7702Enabled; public virtual bool IsRip7212Enabled => spec.IsRip7212Enabled; public virtual bool IsOpGraniteEnabled => spec.IsOpGraniteEnabled; public virtual ulong WithdrawalTimestamp => spec.WithdrawalTimestamp; public virtual ulong Eip4844TransitionTimestamp => spec.Eip4844TransitionTimestamp; public virtual bool IsEip158IgnoredAccount(Address address) => spec.IsEip158IgnoredAccount(address); + public bool IsEip4844FeeCollectorEnabled => spec.IsEip4844FeeCollectorEnabled; } diff --git a/src/Nethermind/Nethermind.Core/Timestamper.cs b/src/Nethermind/Nethermind.Core/Timestamper.cs index bbb6986a17e..ed15978f5f6 100644 --- a/src/Nethermind/Nethermind.Core/Timestamper.cs +++ b/src/Nethermind/Nethermind.Core/Timestamper.cs @@ -14,6 +14,12 @@ public Timestamper(DateTime? constantDate = null) _constantDate = constantDate; } + public Timestamper(long timestamp) + { + var blockTime = DateTimeOffset.FromUnixTimeSeconds(timestamp); + _constantDate = blockTime.UtcDateTime; + } + public DateTime UtcNow => _constantDate ?? DateTime.UtcNow; public static readonly ITimestamper Default = new Timestamper(); diff --git a/src/Nethermind/Nethermind.Core/Transaction.cs b/src/Nethermind/Nethermind.Core/Transaction.cs index ec162f85c88..987af864482 100644 --- a/src/Nethermind/Nethermind.Core/Transaction.cs +++ b/src/Nethermind/Nethermind.Core/Transaction.cs @@ -4,6 +4,8 @@ using System; using System.Buffers; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Text.Json.Serialization; @@ -46,6 +48,7 @@ public class Transaction public bool SupportsAccessList => Type >= TxType.AccessList && Type != TxType.DepositTx; public bool Supports1559 => Type >= TxType.EIP1559 && Type != TxType.DepositTx; public bool SupportsBlobs => Type == TxType.Blob && Type != TxType.DepositTx; + public bool SupportsAuthorizationList => Type == TxType.SetCode && Type != TxType.DepositTx; public long GasLimit { get; set; } public Address? To { get; set; } public UInt256 Value { get; set; } @@ -56,6 +59,12 @@ public class Transaction public bool IsContractCreation => To is null; public bool IsMessageCall => To is not null; + [MemberNotNullWhen(true, nameof(AuthorizationList))] + public bool HasAuthorizationList => + Type == TxType.SetCode && + AuthorizationList is not null && + AuthorizationList.Length > 0; + private Hash256? _hash; [JsonIgnore] @@ -161,6 +170,12 @@ private void ClearPreHashInternal() public object? NetworkWrapper { get; set; } + /// + /// List of EOA code authorizations. + /// https://eips.ethereum.org/EIPS/eip-7702 + /// + public AuthorizationTuple[]? AuthorizationList { get; set; } + /// /// Service transactions are free. The field added to handle baseFee validation after 1559 /// @@ -263,6 +278,7 @@ public bool Return(Transaction obj) obj.IsServiceTransaction = default; obj.PoolIndex = default; obj._size = default; + obj.AuthorizationList = default; return true; } @@ -293,6 +309,7 @@ public void CopyTo(Transaction tx) tx.IsServiceTransaction = IsServiceTransaction; tx.PoolIndex = PoolIndex; tx._size = _size; + tx.AuthorizationList = AuthorizationList; } } @@ -304,7 +321,10 @@ public class GeneratedTransaction : Transaction { } /// /// System transaction that is to be executed by the node without including in the block. /// - public class SystemTransaction : Transaction { } + public class SystemTransaction : Transaction + { + private new const long GasLimit = 30_000_000L; + } /// /// Used inside Transaction::GetSize to calculate encoded transaction size diff --git a/src/Nethermind/Nethermind.Core/TxType.cs b/src/Nethermind/Nethermind.Core/TxType.cs index b642ca78121..8b9d2f65b92 100644 --- a/src/Nethermind/Nethermind.Core/TxType.cs +++ b/src/Nethermind/Nethermind.Core/TxType.cs @@ -9,6 +9,8 @@ public enum TxType : byte AccessList = 1, EIP1559 = 2, Blob = 3, - DepositTx = 0x7E + SetCode = 4, + + DepositTx = 0x7E, } } diff --git a/src/Nethermind/Nethermind.Crypto/BlsSigner.cs b/src/Nethermind/Nethermind.Crypto/BlsSigner.cs new file mode 100644 index 00000000000..77e3baebc47 --- /dev/null +++ b/src/Nethermind/Nethermind.Crypto/BlsSigner.cs @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Runtime.CompilerServices; +using System.Text; +using Nethermind.Core.Collections; + +namespace Nethermind.Crypto; + +using G1Affine = Bls.P1Affine; +using G2 = Bls.P2; +using G2Affine = Bls.P2Affine; +using GT = Bls.PT; + +public static class BlsSigner +{ + private static readonly byte[] Cryptosuite = Encoding.UTF8.GetBytes("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"); + private const int InputLength = 64; + + [SkipLocalsInit] + public static Signature Sign(Bls.SecretKey sk, ReadOnlySpan message) + { + G2 p = new(stackalloc long[G2.Sz]); + p.HashTo(message, Cryptosuite); + p.SignWith(sk); + return new(p.Compress()); + } + + [SkipLocalsInit] + public static bool Verify(G1Affine publicKey, Signature signature, ReadOnlySpan message) + { + int len = 2 * GT.Sz; + using ArrayPoolList buf = new(len, len); + try + { + G2Affine sig = new(stackalloc long[G2Affine.Sz]); + sig.Decode(signature.Bytes); + GT p1 = new(buf.AsSpan()[..GT.Sz]); + p1.MillerLoop(sig, G1Affine.Generator(stackalloc long[G1Affine.Sz])); + + G2 m = new(stackalloc long[G2.Sz]); + m.HashTo(message, Cryptosuite); + GT p2 = new(buf.AsSpan()[GT.Sz..]); + p2.MillerLoop(m.ToAffine(), publicKey); + + return GT.FinalVerify(p1, p2); + } + catch (Bls.BlsException) + { + // point not on curve + return false; + } + } + + // Compressed G2 point + public readonly ref struct Signature() + { + public readonly ReadOnlySpan Bytes; + + public Signature(ReadOnlySpan s) : this() + { + if (s.Length != 96) + { + throw new Bls.BlsException(Bls.ERROR.BADENCODING); + } + Bytes = s; + } + } +} diff --git a/src/Nethermind/Nethermind.Crypto/EthereumEcdsa.cs b/src/Nethermind/Nethermind.Crypto/EthereumEcdsa.cs index 7b27a145d43..deecd2c9d11 100644 --- a/src/Nethermind/Nethermind.Crypto/EthereumEcdsa.cs +++ b/src/Nethermind/Nethermind.Crypto/EthereumEcdsa.cs @@ -5,8 +5,10 @@ using System.Globalization; using System.IO; using System.Numerics; +using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Serialization.Rlp; @@ -26,81 +28,15 @@ public class EthereumEcdsa : Ecdsa, IEthereumEcdsa BigInteger.Parse("00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", NumberStyles.HexNumber); + private readonly AuthorizationTupleDecoder _tupleDecoder = AuthorizationTupleDecoder.Instance; private readonly ulong _chainIdValue; + public ulong ChainId => _chainIdValue; public EthereumEcdsa(ulong chainId) { _chainIdValue = chainId; } - public void Sign(PrivateKey privateKey, Transaction tx, bool isEip155Enabled) - { - if (tx.Type != TxType.Legacy) - { - tx.ChainId = _chainIdValue; - } - - Hash256 hash = Keccak.Compute(Rlp.Encode(tx, true, isEip155Enabled, _chainIdValue).Bytes); - tx.Signature = Sign(privateKey, hash); - - if (tx.Type == TxType.Legacy && isEip155Enabled) - { - tx.Signature.V = tx.Signature.V + 8 + 2 * _chainIdValue; - } - } - - /// - /// - /// - /// - /// - /// - public bool Verify(Address sender, Transaction tx) - { - Address? recovered = RecoverAddress(tx); - return recovered?.Equals(sender) ?? false; - } - - /// - /// - /// - /// - /// - /// - public Address? RecoverAddress(Transaction tx, bool useSignatureChainId = false) - { - if (tx.Signature is null) - { - throw new InvalidDataException("Cannot recover sender address from a transaction without a signature."); - } - - useSignatureChainId &= tx.Signature.ChainId.HasValue; - - // feels like it is the same check twice - bool applyEip155 = useSignatureChainId - || tx.Signature.V == CalculateV(_chainIdValue, false) - || tx.Signature.V == CalculateV(_chainIdValue, true); - - ulong chainId; - switch (tx.Type) - { - case TxType.Legacy when useSignatureChainId: - chainId = tx.Signature.ChainId.Value; - break; - case TxType.Legacy: - chainId = _chainIdValue; - break; - default: - chainId = tx.ChainId!.Value; - break; - } - Hash256 hash = Keccak.Compute(Rlp.Encode(tx, true, applyEip155, chainId).Bytes); - - return RecoverAddress(tx.Signature, hash); - } - - public static ulong CalculateV(ulong chainId, bool addParity = true) => chainId * 2 + 35ul + (addParity ? 1u : 0u); - public Address? RecoverAddress(Signature signature, Hash256 message) { return RecoverAddress(signature.BytesWithRecovery, message); diff --git a/src/Nethermind/Nethermind.Crypto/EthereumEcdsaExtensions.cs b/src/Nethermind/Nethermind.Crypto/EthereumEcdsaExtensions.cs new file mode 100644 index 00000000000..3b7d939c936 --- /dev/null +++ b/src/Nethermind/Nethermind.Crypto/EthereumEcdsaExtensions.cs @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.IO; +using System.Runtime.CompilerServices; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; + +namespace Nethermind.Crypto +{ + public static class EthereumEcdsaExtensions + { + public static AuthorizationTuple Sign(this IEthereumEcdsa ecdsa, PrivateKey signer, ulong chainId, Address codeAddress, ulong nonce) + { + using NettyRlpStream rlp = AuthorizationTupleDecoder.Instance.EncodeWithoutSignature(chainId, codeAddress, nonce); + Span preImage = stackalloc byte[rlp.Length + 1]; + preImage[0] = Eip7702Constants.Magic; + rlp.AsSpan().CopyTo(preImage.Slice(1)); + Signature sig = ecdsa.Sign(signer, Keccak.Compute(preImage)); + return new AuthorizationTuple(chainId, codeAddress, nonce, sig); + } + + public static void Sign(this IEthereumEcdsa ecdsa, PrivateKey privateKey, Transaction tx, bool isEip155Enabled = true) + { + if (tx.Type != TxType.Legacy) + { + tx.ChainId = ecdsa.ChainId; + } + + Hash256 hash = Keccak.Compute(Rlp.Encode(tx, true, isEip155Enabled, ecdsa.ChainId).Bytes); + tx.Signature = ecdsa.Sign(privateKey, hash); + + if (tx.Type == TxType.Legacy && isEip155Enabled) + { + tx.Signature.V = tx.Signature.V + 8 + 2 * ecdsa.ChainId; + } + } + + /// + /// + /// + /// + /// + /// + public static bool Verify(this IEthereumEcdsa ecdsa, Address sender, Transaction tx) + { + Address? recovered = ecdsa.RecoverAddress(tx); + return recovered?.Equals(sender) ?? false; + } + + /// + /// + /// + /// + /// + /// + public static Address? RecoverAddress(this IEthereumEcdsa ecdsa, Transaction tx, bool useSignatureChainId = false) + { + if (tx.Signature is null) + { + throw new InvalidDataException("Cannot recover sender address from a transaction without a signature."); + } + + useSignatureChainId &= tx.Signature.ChainId.HasValue; + + // feels like it is the same check twice + bool applyEip155 = useSignatureChainId + || tx.Signature.V == CalculateV(ecdsa.ChainId, false) + || tx.Signature.V == CalculateV(ecdsa.ChainId, true); + + ulong chainId; + switch (tx.Type) + { + case TxType.Legacy when useSignatureChainId: + chainId = tx.Signature.ChainId.Value; + break; + case TxType.Legacy: + chainId = ecdsa.ChainId; + break; + default: + chainId = tx.ChainId!.Value; + break; + } + Hash256 hash = Keccak.Compute(Rlp.Encode(tx, true, applyEip155, chainId).Bytes); + + return ecdsa.RecoverAddress(tx.Signature, hash); + } + + public static ulong CalculateV(ulong chainId, bool addParity = true) => chainId * 2 + 35ul + (addParity ? 1u : 0u); + + [SkipLocalsInit] + public static Address? RecoverAddress(this IEthereumEcdsa ecdsa, AuthorizationTuple tuple) + { + Span buffer = stackalloc byte[128]; + buffer[0] = Eip7702Constants.Magic; + using NettyRlpStream stream = AuthorizationTupleDecoder.Instance.EncodeWithoutSignature(tuple.ChainId, tuple.CodeAddress, tuple.Nonce); + stream.AsSpan().CopyTo(buffer.Slice(1)); + return ecdsa.RecoverAddress(tuple.AuthoritySignature, Keccak.Compute(buffer.Slice(0, stream.Length + 1))); + } + } +} diff --git a/src/Nethermind/Nethermind.Crypto/IEthereumEcdsa.cs b/src/Nethermind/Nethermind.Crypto/IEthereumEcdsa.cs index 2c72540a705..67f17f74d5b 100644 --- a/src/Nethermind/Nethermind.Crypto/IEthereumEcdsa.cs +++ b/src/Nethermind/Nethermind.Crypto/IEthereumEcdsa.cs @@ -4,15 +4,14 @@ using System; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Int256; namespace Nethermind.Crypto { public interface IEthereumEcdsa : IEcdsa { - void Sign(PrivateKey privateKey, Transaction tx, bool isEip155Enabled = true); - Address? RecoverAddress(Transaction tx, bool useSignatureChainId = false); + ulong ChainId { get; } Address? RecoverAddress(Signature signature, Hash256 message); Address? RecoverAddress(Span signatureBytes, Hash256 message); - bool Verify(Address sender, Transaction tx); } } diff --git a/src/Nethermind/Nethermind.Crypto/Nethermind.Crypto.csproj b/src/Nethermind/Nethermind.Crypto/Nethermind.Crypto.csproj index cbd9d76af1a..fa60784a7af 100644 --- a/src/Nethermind/Nethermind.Crypto/Nethermind.Crypto.csproj +++ b/src/Nethermind/Nethermind.Crypto/Nethermind.Crypto.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Nethermind/Nethermind.Crypto/NullEthereumEcdsa.cs b/src/Nethermind/Nethermind.Crypto/NullEthereumEcdsa.cs index fade56407c5..78ff585916f 100644 --- a/src/Nethermind/Nethermind.Crypto/NullEthereumEcdsa.cs +++ b/src/Nethermind/Nethermind.Crypto/NullEthereumEcdsa.cs @@ -4,6 +4,7 @@ using System; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Int256; namespace Nethermind.Crypto { @@ -11,6 +12,8 @@ public class NullEthereumEcdsa : IEthereumEcdsa { public static NullEthereumEcdsa Instance { get; } = new(); + public ulong ChainId => 0; + private NullEthereumEcdsa() { } @@ -30,11 +33,6 @@ public CompressedPublicKey RecoverCompressedPublicKey(Signature signature, Hash2 throw new InvalidOperationException($"{nameof(NullEthereumEcdsa)} does not expect any calls"); } - public void Sign(PrivateKey privateKey, Transaction tx, bool _) - { - throw new InvalidOperationException($"{nameof(NullEthereumEcdsa)} does not expect any calls"); - } - public Address RecoverAddress(Transaction tx, bool useSignatureChainId = false) { throw new InvalidOperationException($"{nameof(NullEthereumEcdsa)} does not expect any calls"); diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs index fd31df6fa0c..be98ea39aab 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs @@ -55,7 +55,7 @@ public void GlobalSetup() codeInfo: new CodeInfo(ByteCode), value: 0, transferValue: 0, - txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, null), + txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, null, codeInfoRepository), inputData: default ); diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs b/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs index 3a45a79026b..3b3853d5fc5 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs @@ -87,7 +87,7 @@ public void GlobalSetup() codeInfo: new CodeInfo(_bytecode.Concat(_bytecode).Concat(_bytecode).Concat(_bytecode).ToArray()), value: 0, transferValue: 0, - txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, null), + txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, null, codeInfoRepository), inputData: default ); diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs index c0f3e229c4f..12c12742ed7 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs @@ -98,7 +98,7 @@ public void GlobalSetup() codeInfo: new CodeInfo(Bytecode), value: 0, transferValue: 0, - txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, null), + txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, null, codeInfoRepository), inputData: default ); diff --git a/src/Nethermind/Nethermind.Evm.Test/BlobGasCalculatorTests.cs b/src/Nethermind/Nethermind.Evm.Test/BlobGasCalculatorTests.cs index bb3ea55c503..39302bb37dd 100644 --- a/src/Nethermind/Nethermind.Evm.Test/BlobGasCalculatorTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/BlobGasCalculatorTests.cs @@ -42,27 +42,27 @@ void Test(IReleaseSpec spec, bool areBlobsEnabled) } [TestCaseSource(nameof(BlobGasCostTestCaseSource))] - public void Blob_gas_price_is_calculated_properly( + public void Blob_base_fee_is_calculated_properly( (Transaction tx, ulong excessBlobGas, UInt256 expectedCost) testCase) { BlockHeader header = Build.A.BlockHeader.WithExcessBlobGas(testCase.excessBlobGas).TestObject; - bool success = BlobGasCalculator.TryCalculateBlobGasPrice(header, testCase.tx, out UInt256 blobGasPrice); + bool success = BlobGasCalculator.TryCalculateBlobBaseFee(header, testCase.tx, out UInt256 blobBaseFee); Assert.That(success, Is.True); - Assert.That(blobGasPrice, Is.EqualTo(testCase.expectedCost)); + Assert.That(blobBaseFee, Is.EqualTo(testCase.expectedCost)); } [Test] - public void Blob_gas_price_may_overflow() + public void Blob_base_fee_may_overflow() { var tx = Build.A.Transaction.WithType(TxType.Blob).WithBlobVersionedHashes(1000).TestObject; BlockHeader header = Build.A.BlockHeader.WithExcessBlobGas(ulong.MaxValue).TestObject; - bool success = BlobGasCalculator.TryCalculateBlobGasPrice(header, tx, out UInt256 blobGasPrice); + bool success = BlobGasCalculator.TryCalculateBlobBaseFee(header, tx, out UInt256 blobBaseFee); Assert.That(success, Is.False); - Assert.That(blobGasPrice, Is.EqualTo(UInt256.MaxValue)); + Assert.That(blobBaseFee, Is.EqualTo(UInt256.MaxValue)); } public static IEnumerable<(ulong parentExcessBlobGas, int parentBlobsCount, ulong expectedExcessBlobGas)> ExcessBlobGasTestCaseSource() diff --git a/src/Nethermind/Nethermind.Evm.Test/BlsAddG1PrecompileTests.cs b/src/Nethermind/Nethermind.Evm.Test/BlsG1AddPrecompileTests.cs similarity index 100% rename from src/Nethermind/Nethermind.Evm.Test/BlsAddG1PrecompileTests.cs rename to src/Nethermind/Nethermind.Evm.Test/BlsG1AddPrecompileTests.cs diff --git a/src/Nethermind/Nethermind.Evm.Test/BlsMultiMulG1PrecompileTests.cs b/src/Nethermind/Nethermind.Evm.Test/BlsG1MSMPrecompileTests.cs similarity index 99% rename from src/Nethermind/Nethermind.Evm.Test/BlsMultiMulG1PrecompileTests.cs rename to src/Nethermind/Nethermind.Evm.Test/BlsG1MSMPrecompileTests.cs index 627ebd8c53c..9626fa7238e 100644 --- a/src/Nethermind/Nethermind.Evm.Test/BlsMultiMulG1PrecompileTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/BlsG1MSMPrecompileTests.cs @@ -12,15 +12,15 @@ namespace Nethermind.Evm.Test; -public class BlsMultiMulG1PrecompileTests +public class BlsG1MSMPrecompileTests { [Test] public void Test() { foreach ((byte[] input, ReadOnlyMemory expectedResult) in Inputs) { - IPrecompile precompile = G1MultiMulPrecompile.Instance; - (ReadOnlyMemory output, bool success) = precompile.Run(input, MuirGlacier.Instance); + IPrecompile precompile = G1MSMPrecompile.Instance; + (ReadOnlyMemory output, bool success) = precompile.Run(input, Prague.Instance); output.ToArray().Should().BeEquivalentTo(expectedResult.ToArray()); success.Should().BeTrue(); } diff --git a/src/Nethermind/Nethermind.Evm.Test/BlsAddG2PrecompileTests.cs b/src/Nethermind/Nethermind.Evm.Test/BlsG2AddPrecompileTests.cs similarity index 99% rename from src/Nethermind/Nethermind.Evm.Test/BlsAddG2PrecompileTests.cs rename to src/Nethermind/Nethermind.Evm.Test/BlsG2AddPrecompileTests.cs index 4c6dabd96a0..be2609d84ef 100644 --- a/src/Nethermind/Nethermind.Evm.Test/BlsAddG2PrecompileTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/BlsG2AddPrecompileTests.cs @@ -12,7 +12,7 @@ namespace Nethermind.Evm.Test; -public class BlsAddG2PrecompileTests +public class BlsG2AddPrecompileTests { [Test] public void Test() diff --git a/src/Nethermind/Nethermind.Evm.Test/BlsMultiMulG2PrecompileTests.cs b/src/Nethermind/Nethermind.Evm.Test/BlsG2MSMPrecompileTests.cs similarity index 99% rename from src/Nethermind/Nethermind.Evm.Test/BlsMultiMulG2PrecompileTests.cs rename to src/Nethermind/Nethermind.Evm.Test/BlsG2MSMPrecompileTests.cs index 3914ae8e860..c9310444b3b 100644 --- a/src/Nethermind/Nethermind.Evm.Test/BlsMultiMulG2PrecompileTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/BlsG2MSMPrecompileTests.cs @@ -8,19 +8,19 @@ using Nethermind.Evm.Precompiles; using Nethermind.Evm.Precompiles.Bls; using NUnit.Framework; -using static Nethermind.Specs.Forks.MuirGlacier; +using Nethermind.Specs.Forks; namespace Nethermind.Evm.Test; -public class BlsMultiMulG2PrecompileTests +public class BlsG2MSMPrecompileTests { [Test] public void Test() { foreach ((byte[] input, ReadOnlyMemory expectedResult) in Inputs) { - IPrecompile precompile = G2MultiMulPrecompile.Instance; - (ReadOnlyMemory output, bool success) = precompile.Run(input, Instance); + IPrecompile precompile = G2MSMPrecompile.Instance; + (ReadOnlyMemory output, bool success) = precompile.Run(input, Prague.Instance); output.ToArray().Should().BeEquivalentTo(expectedResult.ToArray()); success.Should().BeTrue(); } diff --git a/src/Nethermind/Nethermind.Evm.Test/BlsMapToG2Tests.cs b/src/Nethermind/Nethermind.Evm.Test/BlsMapFp2ToG2Tests.cs similarity index 99% rename from src/Nethermind/Nethermind.Evm.Test/BlsMapToG2Tests.cs rename to src/Nethermind/Nethermind.Evm.Test/BlsMapFp2ToG2Tests.cs index 6122b9ac2a6..61982b51754 100644 --- a/src/Nethermind/Nethermind.Evm.Test/BlsMapToG2Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/BlsMapFp2ToG2Tests.cs @@ -12,14 +12,14 @@ namespace Nethermind.Evm.Test; -public class BlsMapToG2Tests +public class BlsMapFp2ToG2Tests { [Test] public void Test() { foreach ((byte[] input, ReadOnlyMemory expectedResult) in Inputs) { - IPrecompile precompile = MapToG2Precompile.Instance; + IPrecompile precompile = MapFp2ToG2Precompile.Instance; (ReadOnlyMemory output, bool success) = precompile.Run(input, MuirGlacier.Instance); output.ToArray().Should().BeEquivalentTo(expectedResult.ToArray()); diff --git a/src/Nethermind/Nethermind.Evm.Test/BlsMapToG1Tests.cs b/src/Nethermind/Nethermind.Evm.Test/BlsMapFpToG1Tests.cs similarity index 99% rename from src/Nethermind/Nethermind.Evm.Test/BlsMapToG1Tests.cs rename to src/Nethermind/Nethermind.Evm.Test/BlsMapFpToG1Tests.cs index 35f44f4151e..801f1fdc387 100644 --- a/src/Nethermind/Nethermind.Evm.Test/BlsMapToG1Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/BlsMapFpToG1Tests.cs @@ -12,14 +12,14 @@ namespace Nethermind.Evm.Test; -public class BlsMapToG1Tests +public class BlsMapFpToG1Tests { [Test] public void Test() { foreach ((byte[] input, ReadOnlyMemory expectedResult) in Inputs) { - IPrecompile precompile = MapToG1Precompile.Instance; + IPrecompile precompile = MapFpToG1Precompile.Instance; (ReadOnlyMemory output, bool success) = precompile.Run(input, MuirGlacier.Instance); output.ToArray().Should().BeEquivalentTo(expectedResult.ToArray()); diff --git a/src/Nethermind/Nethermind.Evm.Test/BlsPairingPrecompileTests.cs b/src/Nethermind/Nethermind.Evm.Test/BlsPairingCheckPrecompileTests.cs similarity index 99% rename from src/Nethermind/Nethermind.Evm.Test/BlsPairingPrecompileTests.cs rename to src/Nethermind/Nethermind.Evm.Test/BlsPairingCheckPrecompileTests.cs index f717785b44f..ee490e4513e 100644 --- a/src/Nethermind/Nethermind.Evm.Test/BlsPairingPrecompileTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/BlsPairingCheckPrecompileTests.cs @@ -12,14 +12,14 @@ namespace Nethermind.Evm.Test; -public class BlsPairingPrecompileTests +public class BlsPairingCheckPrecompileTests { [Test] public void Test() { foreach ((byte[] input, ReadOnlyMemory expectedResult) in Inputs) { - IPrecompile precompile = PairingPrecompile.Instance; + IPrecompile precompile = PairingCheckPrecompile.Instance; (ReadOnlyMemory output, bool success) = precompile.Run(input, MuirGlacier.Instance); output.ToArray().Should().BeEquivalentTo(expectedResult.ToArray()); diff --git a/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs b/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs new file mode 100644 index 00000000000..f83b9b9846d --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs @@ -0,0 +1,205 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; +using Nethermind.Core; +using Nethermind.Crypto; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; +using NSubstitute; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using Nethermind.Core.Test.Builders; +using FluentAssertions; +using Nethermind.State; +using Nethermind.Core.Specs; +using Nethermind.Evm.CodeAnalysis; +using Nethermind.Core.Extensions; +using Nethermind.Db; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Evm.Test; + +[TestFixture, Parallelizable] +public class CodeInfoRepositoryTests +{ + public static IEnumerable NotDelegationCodeCases() + { + byte[] rndAddress = new byte[20]; + TestContext.CurrentContext.Random.NextBytes(rndAddress); + //Change first byte of the delegation header + byte[] code = [.. Eip7702Constants.DelegationHeader, .. rndAddress]; + code[0] = TestContext.CurrentContext.Random.NextByte(0xee); + yield return new object[] + { + code + }; + //Change second byte of the delegation header + code = [.. Eip7702Constants.DelegationHeader, .. rndAddress]; + code[1] = TestContext.CurrentContext.Random.NextByte(0x2, 0xff); + yield return new object[] + { + code + }; + //Change third byte of the delegation header + code = [.. Eip7702Constants.DelegationHeader, .. rndAddress]; + code[2] = TestContext.CurrentContext.Random.NextByte(0x1, 0xff); + yield return new object[] + { + code + }; + code = [.. Eip7702Constants.DelegationHeader, .. new byte[21]]; + yield return new object[] + { + code + }; + code = [.. Eip7702Constants.DelegationHeader, .. new byte[19]]; + yield return new object[] + { + code + }; + } + [TestCaseSource(nameof(NotDelegationCodeCases))] + public void TryGetDelegation_CodeIsNotDelegation_ReturnsFalse(byte[] code) + { + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + CodeInfoRepository sut = new(); + + sut.TryGetDelegation(stateProvider, TestItem.AddressA, out _).Should().Be(false); + } + + + public static IEnumerable DelegationCodeCases() + { + byte[] address = new byte[20]; + byte[] code = [.. Eip7702Constants.DelegationHeader, .. address]; + yield return new object[] + { + code + }; + TestContext.CurrentContext.Random.NextBytes(address); + code = [.. Eip7702Constants.DelegationHeader, .. address]; + yield return new object[] + { + code + }; + } + [TestCaseSource(nameof(DelegationCodeCases))] + public void TryGetDelegation_CodeTryGetDelegation_ReturnsTrue(byte[] code) + { + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + CodeInfoRepository sut = new(); + + sut.TryGetDelegation(stateProvider, TestItem.AddressA, out _).Should().Be(true); + } + + [TestCaseSource(nameof(DelegationCodeCases))] + public void TryGetDelegation_CodeTryGetDelegation_CorrectDelegationAddressIsSet(byte[] code) + { + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + CodeInfoRepository sut = new(); + + Address result; + sut.TryGetDelegation(stateProvider, TestItem.AddressA, out result); + + result.Should().Be(new Address(code.Slice(3, Address.Size))); + } + + [TestCaseSource(nameof(DelegationCodeCases))] + public void GetExecutableCodeHash_CodeTryGetDelegation_ReturnsHashOfDelegated(byte[] code) + { + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + Address delegationAddress = new Address(code.Slice(3, Address.Size)); + byte[] delegationCode = new byte[32]; + stateProvider.CreateAccount(delegationAddress, 0); + stateProvider.InsertCode(delegationAddress, delegationCode, Substitute.For()); + + CodeInfoRepository sut = new(); + + sut.GetExecutableCodeHash(stateProvider, TestItem.AddressA).Should().Be(Keccak.Compute(delegationCode).ValueHash256); + } + + [TestCaseSource(nameof(NotDelegationCodeCases))] + public void GetExecutableCodeHash_CodeIsNotDelegation_ReturnsCodeHashOfAddress(byte[] code) + { + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + + CodeInfoRepository sut = new(); + + sut.GetExecutableCodeHash(stateProvider, TestItem.AddressA).Should().Be(Keccak.Compute(code).ValueHash256); + } + + [TestCaseSource(nameof(DelegationCodeCases))] + public void GetCachedCodeInfo_CodeTryGetDelegation_ReturnsCodeOfDelegation(byte[] code) + { + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + Address delegationAddress = new Address(code.Slice(3, Address.Size)); + stateProvider.CreateAccount(delegationAddress, 0); + byte[] delegationCode = new byte[32]; + stateProvider.InsertCode(delegationAddress, delegationCode, Substitute.For()); + CodeInfoRepository sut = new(); + + CodeInfo result = sut.GetCachedCodeInfo(stateProvider, TestItem.AddressA, Substitute.For()); + result.MachineCode.ToArray().Should().BeEquivalentTo(delegationCode); + } + + [TestCaseSource(nameof(NotDelegationCodeCases))] + public void GetCachedCodeInfo_CodeIsNotDelegation_ReturnsCodeOfAddress(byte[] code) + { + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + + CodeInfoRepository sut = new(); + + sut.GetCachedCodeInfo(stateProvider, TestItem.AddressA, Substitute.For()).Should().BeEquivalentTo(new CodeInfo(code)); + } + + private static AuthorizationTuple CreateAuthorizationTuple(PrivateKey signer, ulong chainId, Address codeAddress, ulong nonce) + { + AuthorizationTupleDecoder decoder = new(); + using NettyRlpStream rlp = decoder.EncodeWithoutSignature(chainId, codeAddress, nonce); + Span code = stackalloc byte[rlp.Length + 1]; + code[0] = Eip7702Constants.Magic; + rlp.AsSpan().CopyTo(code.Slice(1)); + EthereumEcdsa ecdsa = new(1); + Signature sig = ecdsa.Sign(signer, Keccak.Compute(code)); + + return new AuthorizationTuple(chainId, codeAddress, nonce, sig, signer.Address); + } +} diff --git a/src/Nethermind/Nethermind.Evm.Test/ConsolidationRequestProcessorTest.cs b/src/Nethermind/Nethermind.Evm.Test/ConsolidationRequestProcessorTest.cs new file mode 100644 index 00000000000..99973b8bc23 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Test/ConsolidationRequestProcessorTest.cs @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Linq; +using FluentAssertions; +using Nethermind.Consensus.Requests; +using Nethermind.Core; +using Nethermind.Core.ConsensusRequests; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Db; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Specs; +using Nethermind.State; +using Nethermind.Trie.Pruning; +using NSubstitute; +using NUnit.Framework; + + +namespace Nethermind.Evm.Test; + +public class ConsolidationRequestProcessorTests +{ + + private ISpecProvider _specProvider; + private ITransactionProcessor _transactionProcessor; + private IWorldState _stateProvider; + + private ICodeInfoRepository _codeInfoRepository; + + private static readonly UInt256 AccountBalance = 1.Ether(); + + private readonly Address eip7251Account = Eip7251Constants.ConsolidationRequestPredeployAddress; + + [SetUp] + public void Setup() + { + _specProvider = MainnetSpecProvider.Instance; + MemDb stateDb = new(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + _stateProvider = new WorldState(trieStore, new MemDb(), LimboLogs.Instance); + _stateProvider.CreateAccount(eip7251Account, AccountBalance); + _stateProvider.Commit(_specProvider.GenesisSpec); + _stateProvider.CommitTree(0); + + _codeInfoRepository = new CodeInfoRepository(); + + VirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, _codeInfoRepository, LimboLogs.Instance); + + _transactionProcessor = Substitute.For(); + + _transactionProcessor.Execute(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(ci => + { + CallOutputTracer tracer = ci.Arg(); + tracer.ReturnValue = Bytes.FromHexString("a94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002ffffffffffffffffa94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004ffffffffffffffffa94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006ffffffffffffffffa94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000070000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008ffffffffffffffffa94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000affffffffffffffffa94f5374fce5edbc8e2a8697c15331677e6ebf0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b0000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cffffffffffffffffa94f5374fce5edbc8e2a8697c15331677e6ebf0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d0000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000effffffffffffffffa94f5374fce5edbc8e2a8697c15331677e6ebf0b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010ffffffffffffffff"); + return new TransactionResult(); + }); + } + + + [Test] + public void ShouldProcessConsolidationRequest() + { + + IReleaseSpec spec = Substitute.For(); + spec.ConsolidationRequestsEnabled.Returns(true); + spec.Eip7251ContractAddress.Returns(eip7251Account); + + Block block = Build.A.Block.TestObject; + + ConsolidationRequestsProcessor ConsolidationRequestsProcessor = new(transactionProcessor: _transactionProcessor); + + var ConsolidationRequest = new ConsolidationRequest() + { + SourceAddress = new Address(Bytes.FromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")), + SourcePubkey = Bytes.FromHexString("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"), + TargetPubkey = Bytes.FromHexString("0000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000") + }; + + var ConsolidationRequests = ConsolidationRequestsProcessor.ReadRequests(block, _stateProvider, spec).ToList(); + + Assert.That(ConsolidationRequests, Has.Count.EqualTo(10)); + ConsolidationRequest ConsolidationRequestResult = ConsolidationRequests[0]; + + ConsolidationRequestResult.Should().BeEquivalentTo(ConsolidationRequest, options => options + .Using>(ctx => ctx.Subject.Span.SequenceEqual(ctx.Expectation.Span).Should().BeTrue()) + .WhenTypeIs>()); + } +} diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip1153Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip1153Tests.cs index 97d509a3b57..0f2e4bed9ff 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip1153Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip1153Tests.cs @@ -80,7 +80,6 @@ public void tload_uninitialized_returns_zero() [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; @@ -93,11 +92,10 @@ public void transient_storage_performance_test() byte[] code = prepare.Done; - stopwatch.Start(); + long startTime = Stopwatch.GetTimestamp(); TestAllTracerWithOutput result = Execute((MainnetSpecProvider.GrayGlacierBlockNumber, Timestamp), blockGasLimit, code, blockGasLimit); Assert.That(result.StatusCode, Is.EqualTo(StatusCode.Success)); - stopwatch.Stop(); - Assert.That(stopwatch.ElapsedMilliseconds < 5000, Is.True); + Assert.That(Stopwatch.GetElapsedTime(startTime).TotalMilliseconds < 5000, Is.True); } /// diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip7516BlobBaseFeeTests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip7516BlobBaseFeeTests.cs index f62d8c595f6..7bc096c4dc4 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip7516BlobBaseFeeTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip7516BlobBaseFeeTests.cs @@ -41,10 +41,10 @@ public void Blob_Base_fee_opcode_should_return_expected_results(bool eip7516Enab TestAllTracerWithOutput tracer = CreateTracer(); _processor.Execute(transaction, block.Header, tracer); - _ = BlobGasCalculator.TryCalculateBlobGasPricePerUnit(excessBlobGas, out UInt256 expectedGasPrice); + _ = BlobGasCalculator.TryCalculateFeePerBlobGas(excessBlobGas, out UInt256 expectedFeePerBlobGas); if (eip7516Enabled) { - AssertStorage((UInt256)0, expectedGasPrice); + AssertStorage((UInt256)0, expectedFeePerBlobGas); } else { diff --git a/src/Nethermind/Nethermind.Evm.Test/IntrinsicGasCalculatorTests.cs b/src/Nethermind/Nethermind.Evm.Test/IntrinsicGasCalculatorTests.cs index 126efa7438b..d1c068694da 100644 --- a/src/Nethermind/Nethermind.Evm.Test/IntrinsicGasCalculatorTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/IntrinsicGasCalculatorTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using FluentAssertions; +using MathNet.Numerics.Random; using Nethermind.Core; using Nethermind.Core.Eip2930; using Nethermind.Core.Extensions; @@ -115,5 +116,89 @@ void Test(IReleaseSpec spec, bool isAfterRepricing) Test(Shanghai.Instance, true); Test(Cancun.Instance, true); } + public static IEnumerable<(AuthorizationTuple[] contractCode, long expectedCost)> AuthorizationListTestCaseSource() + { + yield return ( + [], 0); + yield return ( + [new AuthorizationTuple( + TestContext.CurrentContext.Random.NextULong(), + new Address(TestContext.CurrentContext.Random.NextBytes(20)), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextBytes(10), + TestContext.CurrentContext.Random.NextBytes(10)) + ], + GasCostOf.NewAccount); + yield return ( + [new AuthorizationTuple( + TestContext.CurrentContext.Random.NextULong(), + new Address(TestContext.CurrentContext.Random.NextBytes(20)), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextBytes(10), + TestContext.CurrentContext.Random.NextBytes(10)), + new AuthorizationTuple( + TestContext.CurrentContext.Random.NextULong(), + new Address(TestContext.CurrentContext.Random.NextBytes(20)), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextBytes(10), + TestContext.CurrentContext.Random.NextBytes(10)) + ], + GasCostOf.NewAccount * 2); + yield return ( + [new AuthorizationTuple( + TestContext.CurrentContext.Random.NextULong(), + new Address(TestContext.CurrentContext.Random.NextBytes(20)), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextBytes(10), + TestContext.CurrentContext.Random.NextBytes(10)), + new AuthorizationTuple( + TestContext.CurrentContext.Random.NextULong(), + new Address(TestContext.CurrentContext.Random.NextBytes(20)), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextBytes(10), + TestContext.CurrentContext.Random.NextBytes(10)), + new AuthorizationTuple( + TestContext.CurrentContext.Random.NextULong(), + new Address(TestContext.CurrentContext.Random.NextBytes(20)), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextBytes(10), + TestContext.CurrentContext.Random.NextBytes(10)) + ], + GasCostOf.NewAccount * 3); + } + [TestCaseSource(nameof(AuthorizationListTestCaseSource))] + public void Calculate_TxHasAuthorizationList_ReturnsExpectedCostOfTx((AuthorizationTuple[] AuthorizationList, long ExpectedCost) testCase) + { + Transaction tx = Build.A.Transaction.SignedAndResolved() + .WithAuthorizationCode(testCase.AuthorizationList) + .TestObject; + + IntrinsicGasCalculator.Calculate(tx, Prague.Instance) + .Should().Be(GasCostOf.Transaction + (testCase.ExpectedCost)); + } + + [Test] + public void Calculate_TxHasAuthorizationListBeforePrague_ThrowsInvalidDataException() + { + Transaction tx = Build.A.Transaction.SignedAndResolved() + .WithAuthorizationCode( + new AuthorizationTuple( + 0, + TestItem.AddressF, + 0, + TestContext.CurrentContext.Random.NextULong(), + TestContext.CurrentContext.Random.NextBytes(10), + TestContext.CurrentContext.Random.NextBytes(10)) + ) + .TestObject; + + Assert.That(() => IntrinsicGasCalculator.Calculate(tx, Cancun.Instance), Throws.InstanceOf()); + } } } diff --git a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip7702Tests.cs b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip7702Tests.cs new file mode 100644 index 00000000000..4fe6ab74f20 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip7702Tests.cs @@ -0,0 +1,613 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Db; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Logging; +using Nethermind.Specs.Forks; +using Nethermind.State; +using Nethermind.Trie.Pruning; +using NUnit.Framework; +using System.Collections.Generic; +using Nethermind.Core.Crypto; +using System; +using System.Linq; +using Nethermind.Int256; + +namespace Nethermind.Evm.Test; + +[TestFixture] +internal class TransactionProcessorEip7702Tests +{ + private ISpecProvider _specProvider; + private IEthereumEcdsa _ethereumEcdsa; + private TransactionProcessor _transactionProcessor; + private IWorldState _stateProvider; + + [SetUp] + public void Setup() + { + MemDb stateDb = new(); + _specProvider = new TestSpecProvider(Prague.Instance); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + _stateProvider = new WorldState(trieStore, new MemDb(), LimboLogs.Instance); + CodeInfoRepository codeInfoRepository = new(); + VirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, codeInfoRepository, LimboLogs.Instance); + _transactionProcessor = new TransactionProcessor(_specProvider, _stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); + _ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId); + } + + [Test] + public void Execute_TxHasAuthorizationWithCodeThatSavesCallerAddress_ExpectedAddressIsSaved() + { + PrivateKey sender = TestItem.PrivateKeyA; + PrivateKey signer = TestItem.PrivateKeyB; + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + //Save caller in storage slot 0 + byte[] code = Prepare.EvmCode + .Op(Instruction.CALLER) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done; + DeployCode(codeSource, code); + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(100_000) + .WithAuthorizationCode(_ethereumEcdsa.Sign(signer, _specProvider.ChainId, codeSource, 0)) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(10000000).TestObject; + + _transactionProcessor.Execute(tx, block.Header, NullTxTracer.Instance); + + ReadOnlySpan cell = _stateProvider.Get(new StorageCell(signer.Address, 0)); + + Assert.That(new Address(cell.ToArray()), Is.EqualTo(sender.Address)); + } + + public static IEnumerable DelegatedAndNotDelegatedCodeCases() + { + byte[] delegatedCode = new byte[23]; + Eip7702Constants.DelegationHeader.CopyTo(delegatedCode); + yield return new object[] { delegatedCode, true }; + yield return new object[] { Prepare.EvmCode.Op(Instruction.GAS).Done, false }; + } + [TestCaseSource(nameof(DelegatedAndNotDelegatedCodeCases))] + public void Execute_TxHasAuthorizationCodeButAuthorityHasCode_OnlyInsertIfExistingCodeIsDelegated(byte[] authorityCode, bool shouldInsert) + { + PrivateKey sender = TestItem.PrivateKeyA; + PrivateKey signer = TestItem.PrivateKeyB; + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + //Save caller in storage slot 0 + byte[] code = Prepare.EvmCode + .Op(Instruction.CALLER) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done; + DeployCode(codeSource, code); + DeployCode(signer.Address, authorityCode); + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(60_000) + .WithAuthorizationCode(_ethereumEcdsa.Sign(signer, _specProvider.ChainId, codeSource, 0)) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(10000000).TestObject; + + _transactionProcessor.Execute(tx, block.Header, NullTxTracer.Instance); + + ReadOnlySpan signerCode = _stateProvider.GetCode(signer.Address); + + byte[] expectedCode = shouldInsert ? [.. Eip7702Constants.DelegationHeader, .. codeSource.Bytes] : authorityCode; + + Assert.That(signerCode.ToArray(), Is.EquivalentTo(expectedCode)); + } + + public static IEnumerable SenderSignerCases() + { + yield return new object[] { TestItem.PrivateKeyA, TestItem.PrivateKeyB, 0ul }; + yield return new object[] { TestItem.PrivateKeyA, TestItem.PrivateKeyA, 1ul }; + } + [TestCaseSource(nameof(SenderSignerCases))] + public void Execute_SenderAndSignerIsTheSameOrNotWithCodeThatSavesCallerAddress_SenderAddressIsSaved(PrivateKey sender, PrivateKey signer, ulong nonce) + { + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + //Save caller in storage slot 0 + byte[] code = Prepare.EvmCode + .Op(Instruction.CALLER) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done; + DeployCode(codeSource, code); + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(600_000) + .WithAuthorizationCode(_ethereumEcdsa.Sign(signer, _specProvider.ChainId, codeSource, nonce)) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(10000000).TestObject; + + _transactionProcessor.Execute(tx, block.Header, NullTxTracer.Instance); + + ReadOnlySpan cellValue = _stateProvider.Get(new StorageCell(signer.Address, 0)); + + Assert.That(cellValue.ToArray(), Is.EqualTo(sender.Address.Bytes)); + } + public static IEnumerable DifferentCommitValues() + { + //Base case + yield return new object[] { 1ul, 0ul, TestItem.AddressA.Bytes }; + //Wrong nonce + yield return new object[] { 1ul, 1ul, new[] { (byte)0x0 } }; + //Wrong chain id + yield return new object[] { 2ul, 0ul, new[] { (byte)0x0 } }; + } + + [TestCaseSource(nameof(DifferentCommitValues))] + public void Execute_CommitMessageHasDifferentData_ExpectedAddressIsSavedInStorageSlot(ulong chainId, ulong nonce, byte[] expectedStorageValue) + { + PrivateKey sender = TestItem.PrivateKeyA; + PrivateKey signer = TestItem.PrivateKeyB; + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + //Save caller in storage slot 0 + byte[] code = Prepare.EvmCode + .Op(Instruction.CALLER) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done; + DeployCode(codeSource, code); + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(100_000) + .WithAuthorizationCode(_ethereumEcdsa.Sign(signer, chainId, codeSource, nonce)) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(10000000).TestObject; + + _transactionProcessor.Execute(tx, block.Header, NullTxTracer.Instance); + + var actual = _stateProvider.Get(new StorageCell(signer.Address, 0)).ToArray(); + Assert.That(actual, Is.EqualTo(expectedStorageValue)); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(10)] + [TestCase(99)] + public void Execute_TxHasDifferentAmountOfAuthorizedCode_UsedGasIsExpected(int count) + { + PrivateKey sender = TestItem.PrivateKeyA; + PrivateKey signer = TestItem.PrivateKeyB; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(GasCostOf.Transaction + GasCostOf.NewAccount * count) + .WithAuthorizationCode(Enumerable.Range(0, count) + .Select(i => _ethereumEcdsa.Sign( + signer, + _specProvider.ChainId, + TestItem.AddressC, + 0)).ToArray()) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(100000000).TestObject; + + CallOutputTracer tracer = new(); + + _transactionProcessor.Execute(tx, block.Header, tracer); + + Assert.That(tracer.GasSpent, Is.EqualTo(GasCostOf.Transaction + GasCostOf.NewAccount * count)); + } + + private static IEnumerable EvmExecutionErrorCases() + { + byte[] runOutOfGasCode = Prepare.EvmCode + .Op(Instruction.CALLER) + .Op(Instruction.BALANCE) + .Op(Instruction.PUSH0) + .Op(Instruction.JUMP) + .Done; + yield return new object[] { runOutOfGasCode }; + byte[] revertExecution = Prepare.EvmCode + .Op(Instruction.REVERT) + .Done; + yield return new object[] { revertExecution }; + } + [TestCaseSource(nameof(EvmExecutionErrorCases))] + public void Execute_TxWithDelegationRunsOutOfGas_DelegationRefundIsStillApplied(byte[] executionErrorCode) + { + PrivateKey sender = TestItem.PrivateKeyA; + Address codeSource = TestItem.AddressB; + + _stateProvider.CreateAccount(codeSource, 0); + _stateProvider.InsertCode(codeSource, executionErrorCode, Prague.Instance); + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + const long gasLimit = 10_000_000; + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(codeSource) + .WithGasLimit(gasLimit) + .WithAuthorizationCode( + _ethereumEcdsa.Sign( + sender, + _specProvider.ChainId, + Address.Zero, + 1) + ) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(long.MaxValue).TestObject; + + CallOutputTracer tracer = new(); + + _transactionProcessor.Execute(tx, block.Header, tracer); + + Assert.That(tracer.GasSpent, Is.EqualTo(gasLimit - GasCostOf.NewAccount + GasCostOf.PerAuthBaseCost)); + } + + [Test] + public void Execute_TxAuthorizationListWithBALANCE_WarmAccountReadGasIsCharged() + { + PrivateKey sender = TestItem.PrivateKeyA; + PrivateKey signer = TestItem.PrivateKeyB; + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + byte[] code = Prepare.EvmCode + .PushData(signer.Address) + .Op(Instruction.BALANCE) + .Done; + DeployCode(codeSource, code); + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(60_000) + .WithAuthorizationCode( + _ethereumEcdsa.Sign( + signer, + _specProvider.ChainId, + codeSource, + 0)) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(100000000).TestObject; + + CallOutputTracer tracer = new(); + + _transactionProcessor.Execute(tx, block.Header, tracer); + //Tx should only be charged for warm state read + Assert.That(tracer.GasSpent, Is.EqualTo(GasCostOf.Transaction + + GasCostOf.NewAccount + + Prague.Instance.GetBalanceCost() + + GasCostOf.WarmStateRead + + GasCostOf.VeryLow)); + } + + [TestCase(2)] + [TestCase(1)] + public void Execute_AuthorizationListHasSameAuthorityButDifferentCode_OnlyLastInstanceIsUsed(int expectedStoredValue) + { + PrivateKey sender = TestItem.PrivateKeyA; + PrivateKey signer = TestItem.PrivateKeyB; + Address firstCodeSource = TestItem.AddressC; + Address secondCodeSource = TestItem.AddressD; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + byte[] firstCode = Prepare.EvmCode + .PushData(0) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done; + DeployCode(firstCodeSource, firstCode); + + byte[] secondCode = Prepare.EvmCode + .PushData(expectedStoredValue) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done; + DeployCode(secondCodeSource, secondCode); + + AuthorizationTuple[] authList = [ + _ethereumEcdsa.Sign( + signer, + _specProvider.ChainId, + firstCodeSource, + 0), + _ethereumEcdsa.Sign( + signer, + _specProvider.ChainId, + secondCodeSource, + 1), + ]; + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(100_000) + .WithAuthorizationCode(authList) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(10000000).TestObject; + + _transactionProcessor.Execute(tx, block.Header, NullTxTracer.Instance); + + Assert.That(_stateProvider.Get(new StorageCell(signer.Address, 0)).ToArray(), Is.EquivalentTo(new[] { expectedStoredValue })); + } + + [TestCase] + public void Execute_FirstTxHasAuthorizedCodeThatIncrementsAndSecondDoesNot_StorageSlotIsOnlyIncrementedOnce() + { + PrivateKey sender = TestItem.PrivateKeyA; + PrivateKey signer = TestItem.PrivateKeyB; + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + //Increment 1 everytime it's called + byte[] code = Prepare.EvmCode + .Op(Instruction.PUSH0) + .Op(Instruction.SLOAD) + .PushData(1) + .Op(Instruction.ADD) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done; + DeployCode(codeSource, code); + + Transaction tx1 = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(60_000) + .WithAuthorizationCode(_ethereumEcdsa.Sign( + signer, + _specProvider.ChainId, + codeSource, + 0)) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Transaction tx2 = Build.A.Transaction + .WithType(TxType.SetCode) + .WithNonce(1) + .WithTo(signer.Address) + .WithGasLimit(60_000) + .WithAuthorizationCode([]) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx1, tx2) + .WithGasLimit(10000000).TestObject; + + _transactionProcessor.Execute(tx1, block.Header, NullTxTracer.Instance); + _transactionProcessor.Execute(tx2, block.Header, NullTxTracer.Instance); + + Assert.That(_stateProvider.Get(new StorageCell(signer.Address, 0)).ToArray(), Is.EquivalentTo(new[] { 1 })); + } + + public static IEnumerable OpcodesWithEXT() + { + //EXTCODESIZE should return the size of the delegated code + yield return new object[] { + Prepare.EvmCode + .PushData(TestItem.AddressA) + .Op(Instruction.EXTCODESIZE) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done, + new byte[]{ 2 + 22 } }; + //EXTCODEHASH should return the HASH of the delegated code + yield return new object[] { + Prepare.EvmCode + .PushData(TestItem.AddressA) + .Op(Instruction.EXTCODEHASH) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done, + Keccak.Compute( + Prepare.EvmCode + .PushData(TestItem.AddressA) + .Op(Instruction.EXTCODEHASH) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Done).Bytes.ToArray() + }; + //EXTCOPYCODE should copy the delegated code + byte[] code = Prepare.EvmCode + .PushData(TestItem.AddressA) + .Op(Instruction.DUP1) + .Op(Instruction.EXTCODESIZE) + .Op(Instruction.PUSH0) + .Op(Instruction.PUSH0) + .Op(Instruction.DUP4) + .Op(Instruction.EXTCODECOPY) + .Op(Instruction.PUSH0) + .Op(Instruction.MLOAD) + .Op(Instruction.PUSH0) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + yield return new object[] + { + code, + code + }; + } + [TestCaseSource(nameof(OpcodesWithEXT))] + public void Execute_DelegatedCodeUsesEXTOPCODES_StoresExpectedValue(byte[] code, byte[] expectedValue) + { + PrivateKey signer = TestItem.PrivateKeyA; + PrivateKey sender = TestItem.PrivateKeyB; + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + DeployCode(codeSource, code); + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(signer.Address) + .WithGasLimit(100_000) + .WithAuthorizationCode(_ethereumEcdsa.Sign( + signer, + _specProvider.ChainId, + codeSource, + 0)) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(10000000).TestObject; + + TransactionResult result = _transactionProcessor.Execute(tx, block.Header, NullTxTracer.Instance); + Assert.That(_stateProvider.Get(new StorageCell(signer.Address, 0)).ToArray(), Is.EquivalentTo(expectedValue)); + } + + public static IEnumerable CountsAsAccessedCases() + { + EthereumEcdsa ethereumEcdsa = new(BlockchainIds.GenericNonRealNetwork); + + yield return new object[] + { + new AuthorizationTuple[] + { + ethereumEcdsa.Sign(TestItem.PrivateKeyA, 1, TestItem.AddressF, 0), + ethereumEcdsa.Sign(TestItem.PrivateKeyB, 1, TestItem.AddressF, 0), + }, + new Address[] + { + TestItem.AddressA, + TestItem.AddressB + } + }; + yield return new object[] + { + new AuthorizationTuple[] + { + ethereumEcdsa.Sign(TestItem.PrivateKeyA, 1, TestItem.AddressF, 0), + ethereumEcdsa.Sign(TestItem.PrivateKeyB, 2, TestItem.AddressF, 0), + }, + new Address[] + { + TestItem.AddressA, + } + }; + yield return new object[] + { + new AuthorizationTuple[] + { + ethereumEcdsa.Sign(TestItem.PrivateKeyA, 1, TestItem.AddressF, 0), + //Bad signature + new AuthorizationTuple(1, TestItem.AddressF, 0, new Signature(new byte[65]), TestItem.AddressA) + }, + new Address[] + { + TestItem.AddressA, + } + }; + } + + [TestCaseSource(nameof(CountsAsAccessedCases))] + public void Execute_CombinationOfValidAndInvalidTuples_AddsTheCorrectAddressesToAccessedAddresses(AuthorizationTuple[] tuples, Address[] shouldCountAsAccessed) + { + PrivateKey sender = TestItem.PrivateKeyA; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(TestItem.AddressB) + .WithGasLimit(100_000) + .WithAuthorizationCode(tuples) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(10000000).TestObject; + + AccessTxTracer txTracer = new AccessTxTracer(); + TransactionResult result = _transactionProcessor.Execute(tx, block.Header, txTracer); + Assert.That(txTracer.AccessList.Select(a => a.Address), Is.SupersetOf(shouldCountAsAccessed)); + } + + [TestCase(true)] + [TestCase(false)] + public void Execute_AuthorityAccountExistsOrNot_NonceIsIncrementedByOne(bool accountExists) + { + PrivateKey authority = TestItem.PrivateKeyA; + PrivateKey sender = TestItem.PrivateKeyB; + + if (accountExists) + _stateProvider.CreateAccount(authority.Address, 0); + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + AuthorizationTuple[] tuples = + { + _ethereumEcdsa.Sign(authority, 1, sender.Address, 0), + }; + + Transaction tx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithTo(TestItem.AddressB) + .WithGasLimit(100_000) + .WithAuthorizationCode(tuples) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithTransactions(tx) + .WithGasLimit(10000000).TestObject; + TransactionResult result = _transactionProcessor.Execute(tx, block.Header, NullTxTracer.Instance); + + Assert.That(_stateProvider.GetNonce(authority.Address), Is.EqualTo((UInt256)1)); + } + + private void DeployCode(Address codeSource, byte[] code) + { + _stateProvider.CreateAccountIfNotExists(codeSource, 0); + _stateProvider.InsertCode(codeSource, ValueKeccak.Compute(code), code, _specProvider.GetSpec(MainnetSpecProvider.PragueActivation)); + } +} diff --git a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs index 574dcdbc171..1fcc3537729 100644 --- a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs @@ -13,11 +13,12 @@ using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; using Nethermind.Specs; -using Nethermind.Specs.Forks; using Nethermind.Specs.Test; using Nethermind.State; using Nethermind.Trie.Pruning; +using Nethermind.Int256; using NUnit.Framework; +using Nethermind.Specs.GnosisForks; namespace Nethermind.Evm.Test; @@ -32,7 +33,7 @@ public class TransactionProcessorFeeTests [SetUp] public void Setup() { - _spec = new(London.Instance); + _spec = new(PragueGnosis.Instance); _specProvider = new TestSpecProvider(_spec); TrieStore trieStore = new(new MemDb(), LimboLogs.Instance); @@ -56,7 +57,7 @@ public void Check_fees_with_fee_collector(bool isTransactionEip1559, bool withFe { if (withFeeCollector) { - _spec.Eip1559FeeCollector = TestItem.AddressC; + _spec.FeeCollector = TestItem.AddressC; } Transaction tx = Build.A.Transaction @@ -84,7 +85,7 @@ public void Check_paid_fees_multiple_transactions(bool withFeeCollector) { if (withFeeCollector) { - _spec.Eip1559FeeCollector = TestItem.AddressC; + _spec.FeeCollector = TestItem.AddressC; } Transaction tx1 = Build.A.Transaction @@ -107,6 +108,40 @@ public void Check_paid_fees_multiple_transactions(bool withFeeCollector) tracer.BurntFees.Should().Be(84000); } + [TestCase(false)] + [TestCase(true)] + public void Check_paid_fees_with_blob(bool withFeeCollector) + { + UInt256 initialBalance = 0; + if (withFeeCollector) + { + _spec.FeeCollector = TestItem.AddressC; + initialBalance = _stateProvider.GetBalance(TestItem.AddressC); + } + + BlockHeader header = Build.A.BlockHeader.WithExcessBlobGas(0).TestObject; + + Transaction tx = Build.A.Transaction + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).WithType(TxType.Blob) + .WithBlobVersionedHashes(1).WithMaxFeePerBlobGas(1).TestObject; + + Block block = Build.A.Block.WithNumber(0).WithBaseFeePerGas(1) + .WithBeneficiary(TestItem.AddressB).WithTransactions(tx).WithGasLimit(21000).WithHeader(header).TestObject; + + FeesTracer tracer = new(); + ExecuteAndTrace(block, tracer); + + tracer.Fees.Should().Be(0); + + block.GasUsed.Should().Be(21000); + tracer.BurntFees.Should().Be(131072); + + if (withFeeCollector) + { + UInt256 currentBalance = _stateProvider.GetBalance(TestItem.AddressC); + (currentBalance - initialBalance).Should().Be(131072); + } + } [Test] public void Check_paid_fees_with_byte_code() diff --git a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs index 39987fba6e3..9f1b257b914 100644 --- a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs +++ b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs @@ -126,10 +126,22 @@ protected TestAllTracerWithOutput Execute(ForkActivation activation, params byte return tracer; } + protected TestAllTracerWithOutput Execute(ForkActivation activation, Transaction tx) + { + (Block block, _) = PrepareTx(activation, 100000, null); + TestAllTracerWithOutput tracer = CreateTracer(); + _processor.Execute(tx, block.Header, tracer); + return tracer; + } + protected TestAllTracerWithOutput Execute(params byte[] code) { return Execute(Activation, code); } + protected TestAllTracerWithOutput Execute(Transaction tx) + { + return Execute(Activation, tx); + } protected virtual TestAllTracerWithOutput CreateTracer() => new(); @@ -199,7 +211,8 @@ protected TestAllTracerWithOutput Execute(ForkActivation activation, long gasLim int value = 1, long blockGasLimit = DefaultBlockGasLimit, byte[][]? blobVersionedHashes = null, - ulong excessBlobGas = 0) + ulong excessBlobGas = 0, + Transaction transaction = null) { senderRecipientAndMiner ??= SenderRecipientAndMiner.Default; @@ -229,7 +242,7 @@ protected TestAllTracerWithOutput Execute(ForkActivation activation, long gasLim TestState.CommitTree(0); GetLogManager().GetClassLogger().Debug("Committed initial tree"); - Transaction transaction = Build.A.Transaction + transaction ??= Build.A.Transaction .WithGasLimit(gasLimit) .WithGasPrice(1) .WithValue(value) diff --git a/src/Nethermind/Nethermind.Evm.Test/WithdrawalRequestsProcessorTests.cs b/src/Nethermind/Nethermind.Evm.Test/WithdrawalRequestsProcessorTests.cs index 820809bf95f..279b668e424 100644 --- a/src/Nethermind/Nethermind.Evm.Test/WithdrawalRequestsProcessorTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/WithdrawalRequestsProcessorTests.cs @@ -32,11 +32,8 @@ public class WithdrawalRequestProcessorTests private IEthereumEcdsa _ethereumEcdsa; private ITransactionProcessor _transactionProcessor; private IWorldState _stateProvider; - private ICodeInfoRepository _codeInfoRepository; - private static readonly UInt256 AccountBalance = 1.Ether(); - private readonly Address eip7002Account = Eip7002Constants.WithdrawalRequestPredeployAddress; [SetUp] @@ -72,7 +69,7 @@ public void Setup() public void ShouldProcessWithdrawalRequest() { IReleaseSpec spec = Substitute.For(); - spec.IsEip7002Enabled.Returns(true); + spec.WithdrawalRequestsEnabled.Returns(true); spec.Eip7002ContractAddress.Returns(eip7002Account); Block block = Build.A.Block.TestObject; @@ -86,7 +83,7 @@ public void ShouldProcessWithdrawalRequest() Amount = 0 }; - var withdrawalRequests = withdrawalRequestsProcessor.ReadWithdrawalRequests(block, _stateProvider, spec).ToList(); + var withdrawalRequests = withdrawalRequestsProcessor.ReadRequests(block, _stateProvider, spec).ToList(); Assert.That(withdrawalRequests, Has.Count.EqualTo(16)); diff --git a/src/Nethermind/Nethermind.Evm/BlobGasCalculator.cs b/src/Nethermind/Nethermind.Evm/BlobGasCalculator.cs index db129966dc7..5b240a865f4 100644 --- a/src/Nethermind/Nethermind.Evm/BlobGasCalculator.cs +++ b/src/Nethermind/Nethermind.Evm/BlobGasCalculator.cs @@ -29,32 +29,32 @@ public static ulong CalculateBlobGas(Transaction[] transactions) return CalculateBlobGas(blobCount); } - public static bool TryCalculateBlobGasPrice(BlockHeader header, Transaction transaction, out UInt256 blobGasPrice) + public static bool TryCalculateBlobBaseFee(BlockHeader header, Transaction transaction, out UInt256 blobBaseFee) { - if (!TryCalculateBlobGasPricePerUnit(header.ExcessBlobGas.Value, out UInt256 blobGasPricePerUnit)) + if (!TryCalculateFeePerBlobGas(header.ExcessBlobGas.Value, out UInt256 feePerBlobGas)) { - blobGasPrice = UInt256.MaxValue; + blobBaseFee = UInt256.MaxValue; return false; } - return !UInt256.MultiplyOverflow(CalculateBlobGas(transaction), blobGasPricePerUnit, out blobGasPrice); + return !UInt256.MultiplyOverflow(CalculateBlobGas(transaction), feePerBlobGas, out blobBaseFee); } - public static bool TryCalculateBlobGasPricePerUnit(BlockHeader header, out UInt256 blobGasPricePerUnit) + public static bool TryCalculateFeePerBlobGas(BlockHeader header, out UInt256 feePerBlobGas) { - blobGasPricePerUnit = UInt256.MaxValue; + feePerBlobGas = UInt256.MaxValue; return header.ExcessBlobGas is not null - && TryCalculateBlobGasPricePerUnit(header.ExcessBlobGas.Value, out blobGasPricePerUnit); + && TryCalculateFeePerBlobGas(header.ExcessBlobGas.Value, out feePerBlobGas); } - public static bool TryCalculateBlobGasPricePerUnit(ulong excessBlobGas, out UInt256 blobGasPricePerUnit) + public static bool TryCalculateFeePerBlobGas(ulong excessBlobGas, out UInt256 feePerBlobGas) { - static bool FakeExponentialOverflow(UInt256 factor, UInt256 num, UInt256 denominator, out UInt256 blobGasPricePerUnit) + static bool FakeExponentialOverflow(UInt256 factor, UInt256 num, UInt256 denominator, out UInt256 feePerBlobGas) { UInt256 output = UInt256.Zero; if (UInt256.MultiplyOverflow(factor, denominator, out UInt256 numAccum)) { - blobGasPricePerUnit = UInt256.MaxValue; + feePerBlobGas = UInt256.MaxValue; return true; } @@ -62,30 +62,30 @@ static bool FakeExponentialOverflow(UInt256 factor, UInt256 num, UInt256 denomin { if (UInt256.AddOverflow(output, numAccum, out output)) { - blobGasPricePerUnit = UInt256.MaxValue; + feePerBlobGas = UInt256.MaxValue; return true; } if (UInt256.MultiplyOverflow(numAccum, num, out UInt256 updatedNumAccum)) { - blobGasPricePerUnit = UInt256.MaxValue; + feePerBlobGas = UInt256.MaxValue; return true; } if (UInt256.MultiplyOverflow(i, denominator, out UInt256 multipliedDeniminator)) { - blobGasPricePerUnit = UInt256.MaxValue; + feePerBlobGas = UInt256.MaxValue; return true; } numAccum = updatedNumAccum / multipliedDeniminator; } - blobGasPricePerUnit = output / denominator; + feePerBlobGas = output / denominator; return false; } - return !FakeExponentialOverflow(Eip4844Constants.MinBlobGasPrice, excessBlobGas, Eip4844Constants.BlobGasPriceUpdateFraction, out blobGasPricePerUnit); + return !FakeExponentialOverflow(Eip4844Constants.MinBlobGasPrice, excessBlobGas, Eip4844Constants.BlobGasPriceUpdateFraction, out feePerBlobGas); } public static ulong? CalculateExcessBlobGas(BlockHeader? parentBlockHeader, IReleaseSpec releaseSpec) diff --git a/src/Nethermind/Nethermind.Evm/BlockExecutionContext.cs b/src/Nethermind/Nethermind.Evm/BlockExecutionContext.cs index a4ed924c6c4..2bd0b7456b9 100644 --- a/src/Nethermind/Nethermind.Evm/BlockExecutionContext.cs +++ b/src/Nethermind/Nethermind.Evm/BlockExecutionContext.cs @@ -17,11 +17,11 @@ public BlockExecutionContext(BlockHeader blockHeader) Header = blockHeader; if (blockHeader?.ExcessBlobGas is not null) { - if (!BlobGasCalculator.TryCalculateBlobGasPricePerUnit(blockHeader.ExcessBlobGas.Value, out UInt256 blobBaseFeeResult)) + if (!BlobGasCalculator.TryCalculateFeePerBlobGas(blockHeader.ExcessBlobGas.Value, out UInt256 feePerBlobGas)) { throw new OverflowException("Blob gas price calculation led to overflow."); } - BlobBaseFee = blobBaseFeeResult; + BlobBaseFee = feePerBlobGas; } } diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs index 0617b9e9070..4b6bf0d238b 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs @@ -10,6 +10,7 @@ using Nethermind.Core.Crypto; using Nethermind.Evm.Config; using Nethermind.Logging; +using ILMode = int; namespace Nethermind.Evm.CodeAnalysis { @@ -31,7 +32,7 @@ public async void NoticeExecution(IVMConfig vmConfig, ILogger logger) Interlocked.Increment(ref _callCount); // use Interlocked just in case of concurrent execution to run it only once - var mode = + ILMode mode = vmConfig.IsPatternMatchingEnabled && _callCount == vmConfig.PatternMatchingThreshold ? IlInfo.ILMode.PAT_MODE : vmConfig.IsJitEnabled && _callCount == vmConfig.JittingThreshold @@ -39,7 +40,7 @@ public async void NoticeExecution(IVMConfig vmConfig, ILogger logger) : IlInfo.ILMode.NO_ILVM; if (mode == IlInfo.ILMode.NO_ILVM) - return; + return; await IlAnalyzer.StartAnalysis(this, mode, logger).ConfigureAwait(false); } diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILCompiler.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILCompiler.cs index 564d3c7197e..03ba0ec1a5b 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILCompiler.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILCompiler.cs @@ -23,6 +23,14 @@ namespace Nethermind.Evm.CodeAnalysis.IL; internal class ILCompiler { public delegate void ExecuteSegment(ref ILEvmState vmstate, IBlockhashProvider blockhashProvider, IWorldState worldState, ICodeInfoRepository codeInfoRepository, IReleaseSpec spec, byte[][] immediatesData); + + private const int VMSTATE_INDEX = 0; + private const int BLOCKHASH_PROVIDER_INDEX = 1; + private const int WORLD_STATE_INDEX = 2; + private const int CODE_INFO_REPOSITORY_INDEX = 3; + private const int SPEC_INDEX = 4; + private const int IMMEDIATES_DATA_INDEX = 5; + public class SegmentExecutionCtx { public string Name => PrecompiledSegment.Method.Name; @@ -81,6 +89,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co using Local int64A = method.DeclareLocal(typeof(long)); using Local int64B = method.DeclareLocal(typeof(long)); using Local byte8A = method.DeclareLocal(typeof(byte)); + using Local lbool = method.DeclareLocal(typeof(bool)); using Local byte8B = method.DeclareLocal(typeof(byte)); using Local buffer = method.DeclareLocal(typeof(byte*)); @@ -106,7 +115,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co // allocate stack - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX); method.Duplicate(); method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Stack))); method.Call(GetCastMethodInfo()); @@ -115,13 +124,13 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.StoreLocal(head); // set gas to local - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.GasAvailable))); method.Convert(); method.StoreLocal(gasAvailable); // set pc to local - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ProgramCounter))); method.StoreLocal(programCounter); @@ -149,7 +158,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co } // check if opcode is activated in current spec - method.LoadArgument(4); + method.LoadArgument(SPEC_INDEX); method.LoadConstant((byte)op.Operation); method.Call(typeof(InstructionExtensions).GetMethod(nameof(InstructionExtensions.IsEnabled))); method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.BadInstruction]); @@ -197,7 +206,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co break; case Instruction.STOP: { - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadConstant(true); method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ShouldStop))); method.FakeBranch(ret); @@ -207,7 +216,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co { method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ChainId))); method.Call(Word.SetULong0); method.StackPush(head); @@ -296,7 +305,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.Load(stack, head); // we load the span of bytes - method.LoadArgument(5); + method.LoadArgument(IMMEDIATES_DATA_INDEX); method.LoadConstant(op.Arguments.Value); method.LoadElement(); method.Call(Word.SetArray); @@ -479,7 +488,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co // load spec method.LoadLocal(gasAvailable); - method.LoadArgument(4); + method.LoadArgument(SPEC_INDEX); method.Call(typeof(ReleaseSpecExtensions).GetMethod(nameof(ReleaseSpecExtensions.GetExpByteCost))); method.LoadConstant((long)32); method.LoadLocal(uint64A); @@ -636,7 +645,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co { method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.BlkCtx))); method.Call(GetPropertyInfo(typeof(BlockExecutionContext), nameof(BlockExecutionContext.Header), false, out _)); method.Call(GetPropertyInfo(nameof(BlockHeader.GasBeneficiary), false, out _)); @@ -648,7 +657,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co { method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.BlkCtx))); method.Call(GetPropertyInfo(typeof(BlockExecutionContext), nameof(BlockExecutionContext.Header), false, out _)); method.Call(GetPropertyInfo(nameof(BlockHeader.Timestamp), false, out _)); @@ -660,7 +669,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co { method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.BlkCtx))); method.Call(GetPropertyInfo(typeof(BlockExecutionContext), nameof(BlockExecutionContext.Header), false, out _)); method.Call(GetPropertyInfo(nameof(BlockHeader.Number), false, out _)); @@ -672,7 +681,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co { method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.BlkCtx))); method.Call(GetPropertyInfo(typeof(BlockExecutionContext), nameof(BlockExecutionContext.Header), false, out _)); method.Call(GetPropertyInfo(nameof(BlockHeader.GasLimit), false, out _)); @@ -684,7 +693,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co { method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Env))); method.LoadField(GetFieldInfo(typeof(ExecutionEnvironment), nameof(ExecutionEnvironment.Caller))); method.Call(Word.SetAddress); @@ -695,7 +704,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co { method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Env))); method.LoadField(GetFieldInfo(typeof(ExecutionEnvironment), nameof(ExecutionEnvironment.ExecutingAccount))); method.Call(Word.SetAddress); @@ -706,7 +715,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co { method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.TxCtx))); method.Call(GetPropertyInfo(typeof(TxExecutionContext), nameof(TxExecutionContext.Origin), false, out _)); method.Call(Word.SetAddress); @@ -717,7 +726,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co { method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Env))); method.LoadField(GetFieldInfo(typeof(ExecutionEnvironment), nameof(ExecutionEnvironment.Value))); method.Call(Word.SetUInt256); @@ -728,7 +737,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co { method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.TxCtx))); method.Call(GetPropertyInfo(typeof(TxExecutionContext), nameof(TxExecutionContext.GasPrice), false, out _)); method.Call(Word.SetUInt256); @@ -752,7 +761,8 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.LoadLocal(gasAvailable); method.LoadLocalAddress(uint256C); - method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling))); + method.LoadLocalAddress(lbool); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling), [typeof(UInt256).MakeByRefType(), typeof(bool).MakeByRefType()])); method.LoadConstant(GasCostOf.Memory); method.Multiply(); method.Subtract(); @@ -765,8 +775,8 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.Call(typeof(UInt256).GetProperty(nameof(UInt256.IsZero)).GetMethod!); method.BranchIfTrue(endOfOpcode); - method.LoadArgument(0); - method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadArgument(VMSTATE_INDEX);; + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); method.LoadLocalAddress(gasAvailable); method.LoadLocalAddress(uint256A); method.LoadLocalAddress(uint256C); @@ -774,7 +784,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.InputBuffer))); method.LoadObject(typeof(ReadOnlyMemory)); method.LoadLocalAddress(uint256B); @@ -785,11 +795,11 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.Call(typeof(ByteArrayExtensions).GetMethod(nameof(ByteArrayExtensions.SliceWithZeroPadding), [typeof(ReadOnlyMemory), typeof(UInt256).MakeByRefType(), typeof(int), typeof(PadDirection)])); method.StoreLocal(localZeroPaddedSpan); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); method.LoadLocalAddress(uint256A); method.LoadLocalAddress(localZeroPaddedSpan); - method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Save), [typeof(UInt256).MakeByRefType(), typeof(ZeroPaddedSpan).MakeByRefType()])); + method.CallVirtual(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Save), [typeof(UInt256).MakeByRefType(), typeof(ZeroPaddedSpan).MakeByRefType()])); method.MarkLabel(endOfOpcode); } @@ -804,7 +814,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.InputBuffer))); method.LoadObject(typeof(ReadOnlyMemory)); @@ -821,7 +831,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co { method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.InputBuffer))); method.Call(GetPropertyInfo>(nameof(ReadOnlyMemory.Length), false, out _)); method.Call(Word.SetInt0); @@ -833,7 +843,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); method.Call(GetPropertyInfo(nameof(EvmPooledMemory.Size), false, out _)); method.Call(Word.SetULong0); @@ -850,8 +860,8 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.StoreLocal(uint256B); method.StackPop(head, 2); - method.LoadArgument(0); - method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadArgument(VMSTATE_INDEX);; + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); method.LoadLocalAddress(gasAvailable); method.LoadLocalAddress(uint256A); method.LoadConstant(Word.Size); @@ -861,7 +871,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); method.LoadLocalAddress(uint256A); method.LoadLocalAddress(uint256B); @@ -881,8 +891,8 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.StoreLocal(byte8A); method.StackPop(head, 2); - method.LoadArgument(0); - method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadArgument(VMSTATE_INDEX);; + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); method.LoadLocalAddress(gasAvailable); method.LoadLocalAddress(uint256A); method.LoadConstant(1); @@ -892,7 +902,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); method.LoadLocalAddress(uint256A); method.LoadLocalAddress(uint256B); @@ -911,15 +921,15 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.StoreLocal(uint256A); method.StackPop(head, 1); - method.LoadArgument(0); - method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadArgument(VMSTATE_INDEX);; + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); method.LoadLocalAddress(gasAvailable); method.LoadLocalAddress(uint256A); method.LoadFieldAddress(GetFieldInfo(typeof(VirtualMachine), nameof(VirtualMachine.BigInt32))); method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); method.LoadLocalAddress(uint256A); method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.LoadSpan), [typeof(UInt256).MakeByRefType()])); @@ -953,7 +963,8 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.LoadLocal(gasAvailable); method.LoadLocalAddress(uint256C); - method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling))); + method.LoadLocalAddress(lbool); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling), [typeof(UInt256).MakeByRefType(), typeof(bool).MakeByRefType()])); method.LoadConstant((long)1); method.Add(); method.LoadConstant(GasCostOf.VeryLow); @@ -964,8 +975,8 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.LoadConstant((long)0); method.BranchIfLess(evmExceptionLabels[EvmExceptionType.OutOfGas]); - method.LoadArgument(0); - method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadArgument(VMSTATE_INDEX);; + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); method.LoadLocalAddress(gasAvailable); method.LoadLocalAddress(uint256A); method.LoadLocalAddress(uint256B); @@ -976,10 +987,10 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); method.LoadLocalAddress(uint256A); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); method.LoadLocalAddress(uint256B); method.LoadLocalAddress(uint256C); @@ -999,7 +1010,8 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.LoadLocal(gasAvailable); method.LoadLocalAddress(uint256B); - method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling))); + method.LoadLocalAddress(lbool); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling), [typeof(UInt256).MakeByRefType(), typeof(bool).MakeByRefType()])); method.LoadConstant(GasCostOf.Sha3Word); method.Multiply(); method.Subtract(); @@ -1008,8 +1020,8 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.LoadConstant((long)0); method.BranchIfLess(evmExceptionLabels[EvmExceptionType.OutOfGas]); - method.LoadArgument(0); - method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadArgument(VMSTATE_INDEX);; + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); method.LoadLocalAddress(gasAvailable); method.LoadLocalAddress(uint256A); method.LoadLocalAddress(uint256B); @@ -1019,7 +1031,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); method.LoadLocalAddress(uint256A); method.LoadLocalAddress(uint256B); @@ -1089,7 +1101,8 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.LoadLocal(gasAvailable); method.LoadLocalAddress(uint256C); - method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling))); + method.LoadLocalAddress(lbool); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling), [typeof(UInt256).MakeByRefType(), typeof(bool).MakeByRefType()])); method.LoadConstant(GasCostOf.Memory); method.Multiply(); method.Subtract(); @@ -1102,15 +1115,15 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.Call(typeof(UInt256).GetProperty(nameof(UInt256.IsZero)).GetMethod!); method.BranchIfTrue(endOfOpcode); - method.LoadArgument(0); - method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadArgument(VMSTATE_INDEX);; + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); method.LoadLocalAddress(gasAvailable); method.LoadLocalAddress(uint256A); method.LoadLocalAddress(uint256C); method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.MachineCode))); method.StoreLocal(localReadOnlyMemory); @@ -1122,7 +1135,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.Call(typeof(ByteArrayExtensions).GetMethod(nameof(ByteArrayExtensions.SliceWithZeroPadding), [typeof(ReadOnlyMemory), typeof(UInt256).MakeByRefType(), typeof(int), typeof(PadDirection)])); method.StoreLocal(localZeroPaddedSpan); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); method.LoadLocalAddress(uint256A); method.LoadLocalAddress(localZeroPaddedSpan); @@ -1144,7 +1157,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co case Instruction.RETURNDATASIZE: method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ReturnBuffer))); method.Call(GetPropertyInfo>(nameof(ReadOnlyMemory.Length), false, out _)); method.Call(Word.SetInt0); @@ -1172,7 +1185,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.LoadLocalAddress(tempResult); method.Call(typeof(UInt256).GetMethod(nameof(UInt256.AddOverflow))); method.LoadLocalAddress(tempResult); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ReturnBuffer))); method.Call(typeof(ReadOnlyMemory).GetProperty(nameof(ReadOnlyMemory.Length)).GetMethod!); method.Call(typeof(UInt256).GetMethod("op_GreaterThan", new[] { typeof(UInt256).MakeByRefType(), typeof(int) })); @@ -1182,7 +1195,8 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.LoadLocal(gasAvailable); method.LoadLocalAddress(uint256C); - method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling))); + method.LoadLocalAddress(lbool); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling), [typeof(UInt256).MakeByRefType(), typeof(bool).MakeByRefType()])); method.LoadConstant(GasCostOf.Memory); method.Multiply(); method.Subtract(); @@ -1197,15 +1211,15 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.Call(typeof(UInt256).GetProperty(nameof(UInt256.IsZero)).GetMethod!); method.BranchIfTrue(endOfOpcode); - method.LoadArgument(0); - method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadArgument(VMSTATE_INDEX);; + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); method.LoadLocalAddress(gasAvailable); method.LoadLocalAddress(uint256A); method.LoadLocalAddress(uint256C); method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ReturnBuffer))); method.LoadObject(typeof(ReadOnlyMemory)); method.LoadLocalAddress(uint256B); @@ -1215,7 +1229,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.Call(typeof(ByteArrayExtensions).GetMethod(nameof(ByteArrayExtensions.SliceWithZeroPadding), [typeof(ReadOnlyMemory), typeof(UInt256).MakeByRefType(), typeof(int), typeof(PadDirection)])); method.StoreLocal(localZeroPaddedSpan); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); method.LoadLocalAddress(uint256A); method.LoadLocalAddress(localZeroPaddedSpan); @@ -1234,24 +1248,24 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.StoreLocal(uint256B); method.StackPop(head, 2); - method.LoadArgument(0); - method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadArgument(VMSTATE_INDEX);; + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); method.LoadLocalAddress(gasAvailable); method.LoadLocalAddress(uint256A); method.LoadLocalAddress(uint256B); method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.ReturnBuffer))); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); method.LoadLocalAddress(uint256A); method.LoadLocalAddress(uint256B); method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Load), [typeof(UInt256).MakeByRefType(), typeof(UInt256).MakeByRefType()])); method.StoreObject>(); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadConstant(true); switch (op.Operation) { @@ -1268,7 +1282,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co { method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.BlkCtx))); method.Call(GetPropertyInfo(typeof(BlockExecutionContext), nameof(BlockExecutionContext.Header), false, out _)); method.Call(GetPropertyInfo(typeof(BlockHeader), nameof(BlockHeader.BaseFeePerGas), false, out _)); @@ -1281,7 +1295,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co using Local uint256Nullable = method.DeclareLocal(typeof(UInt256?)); method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.BlkCtx))); method.Call(GetPropertyInfo(typeof(BlockExecutionContext), nameof(BlockExecutionContext.BlobBaseFee), false, out _)); method.StoreLocal(uint256Nullable); @@ -1298,7 +1312,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.BlkCtx))); method.Call(GetPropertyInfo(typeof(BlockExecutionContext), nameof(BlockExecutionContext.Header), false, out _)); method.Duplicate(); @@ -1331,20 +1345,20 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.StoreLocal(uint32A); method.StackPop(head, 1); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.TxCtx))); method.Call(GetPropertyInfo(typeof(TxExecutionContext), nameof(TxExecutionContext.BlobVersionedHashes), false, out _)); method.LoadNull(); method.BranchIfEqual(blobVersionedHashNotFound); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.TxCtx))); method.Call(GetPropertyInfo(typeof(TxExecutionContext), nameof(TxExecutionContext.BlobVersionedHashes), false, out _)); method.Call(GetPropertyInfo(typeof(byte[][]), nameof(Array.Length), false, out _)); method.LoadLocal(uint32A); method.BranchIfLessOrEqual(blobVersionedHashNotFound); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.TxCtx))); method.Call(GetPropertyInfo(typeof(TxExecutionContext), nameof(TxExecutionContext.BlobVersionedHashes), false, out _)); method.LoadLocal(uint32A); @@ -1379,8 +1393,8 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.StoreLocal(int64A); method.StackPop(head, 1); - method.LoadArgument(1); - method.LoadArgument(0); + method.LoadArgument(BLOCKHASH_PROVIDER_INDEX);; + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.BlkCtx))); method.Call(GetPropertyInfo(typeof(BlockExecutionContext), nameof(BlockExecutionContext.Header), false, out _)); method.LoadLocalAddress(int64A); @@ -1485,7 +1499,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co break; case Instruction.TSTORE: { - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); method.Call(GetPropertyInfo(typeof(EvmState), nameof(EvmState.IsStatic), false, out _)); method.BranchIfTrue(evmExceptionLabels[EvmExceptionType.StaticCallViolation]); @@ -1500,14 +1514,14 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.StackPop(head, 2); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Env))); method.LoadField(GetFieldInfo(typeof(ExecutionEnvironment), nameof(ExecutionEnvironment.ExecutingAccount))); method.LoadLocalAddress(uint256A); method.NewObject(typeof(StorageCell), [typeof(Address), typeof(UInt256).MakeByRefType()]); method.StoreLocal(storageCell); - method.LoadArgument(2); + method.LoadArgument(WORLD_STATE_INDEX); method.LoadLocalAddress(storageCell); method.LoadLocal(localArray); method.CallVirtual(typeof(IWorldState).GetMethod(nameof(IWorldState.SetTransientState), [typeof(StorageCell).MakeByRefType(), typeof(byte[])])); @@ -1520,14 +1534,14 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.StoreLocal(uint256A); method.StackPop(head, 1); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Env))); method.LoadField(GetFieldInfo(typeof(ExecutionEnvironment), nameof(ExecutionEnvironment.ExecutingAccount))); method.LoadLocalAddress(uint256A); method.NewObject(typeof(StorageCell), [typeof(Address), typeof(UInt256).MakeByRefType()]); method.StoreLocal(storageCell); - method.LoadArgument(2); + method.LoadArgument(WORLD_STATE_INDEX); method.LoadLocalAddress(storageCell); method.CallVirtual(typeof(IWorldState).GetMethod(nameof(IWorldState.GetTransientState), [typeof(StorageCell).MakeByRefType()])); method.StoreLocal(localReadonOnlySpan); @@ -1549,13 +1563,13 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.StoreLocal(localReadonOnlySpan); method.StackPop(head, 2); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); - method.LoadArgument(2); + method.LoadArgument(WORLD_STATE_INDEX); method.LoadLocalAddress(gasAvailable); method.LoadLocalAddress(uint256A); method.LoadLocalAddress(localReadonOnlySpan); - method.LoadArgument(4); + method.LoadArgument(SPEC_INDEX); method.Call(GetPropertyInfo(nameof(NullTxTracer.Instance), false, out _)); MethodInfo sstoreMethod = @@ -1570,7 +1584,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.LoadConstant((int)EvmExceptionType.None); method.BranchIfEqual(endOfOpcode); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadLocal(uint32A); method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmException))); method.Branch(exit); @@ -1581,7 +1595,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co case Instruction.SLOAD: { method.LoadLocal(gasAvailable); - method.LoadArgument(4); + method.LoadArgument(SPEC_INDEX); method.Call(typeof(ReleaseSpecExtensions).GetMethod(nameof(ReleaseSpecExtensions.GetSLoadCost))); method.Subtract(); method.Duplicate(); @@ -1594,7 +1608,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.StoreLocal(uint256A); method.StackPop(head, 1); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Env))); method.LoadField(GetFieldInfo(typeof(ExecutionEnvironment), nameof(ExecutionEnvironment.ExecutingAccount))); method.LoadLocalAddress(uint256A); @@ -1602,16 +1616,16 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.StoreLocal(storageCell); method.LoadLocalAddress(gasAvailable); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); method.LoadLocalAddress(storageCell); method.LoadConstant((int)VirtualMachine.StorageAccessType.SLOAD); - method.LoadArgument(4); + method.LoadArgument(SPEC_INDEX); method.Call(GetPropertyInfo(nameof(NullTxTracer.Instance), false, out _)); method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.ChargeStorageAccessGas), BindingFlags.Static | BindingFlags.NonPublic)); method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); - method.LoadArgument(2); + method.LoadArgument(WORLD_STATE_INDEX); method.LoadLocalAddress(storageCell); method.CallVirtual(typeof(IWorldState).GetMethod(nameof(IWorldState.Get), [typeof(StorageCell).MakeByRefType()])); method.StoreLocal(localReadonOnlySpan); @@ -1626,7 +1640,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co case Instruction.EXTCODESIZE: { method.LoadLocal(gasAvailable); - method.LoadArgument(4); + method.LoadArgument(SPEC_INDEX); method.Call(typeof(ReleaseSpecExtensions).GetMethod(nameof(ReleaseSpecExtensions.GetExtCodeCost))); method.Subtract(); method.Duplicate(); @@ -1640,10 +1654,12 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.StackPop(head, 1); method.LoadLocalAddress(gasAvailable); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); method.LoadLocal(address); - method.LoadArgument(4); + method.LoadConstant(true); + method.LoadArgument(WORLD_STATE_INDEX); + method.LoadArgument(SPEC_INDEX); method.Call(GetPropertyInfo(nameof(NullTxTracer.Instance), false, out _)); method.LoadConstant(true); method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.ChargeAccountAccessGas))); @@ -1652,11 +1668,11 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(3); - method.LoadArgument(2); + method.LoadArgument(CODE_INFO_REPOSITORY_INDEX); + method.LoadArgument(WORLD_STATE_INDEX); method.LoadLocal(address); - method.LoadArgument(4); - method.CallVirtual(typeof(ICodeInfoRepository).GetMethod(nameof(ICodeInfoRepository.GetCachedCodeInfo), [typeof(IWorldState), typeof(Address), typeof(IReleaseSpec)])); + method.LoadArgument(SPEC_INDEX); + method.Call(typeof(CodeInfoRepositoryExtensions).GetMethod(nameof(CodeInfoRepositoryExtensions.GetCachedCodeInfo), [typeof(ICodeInfoRepository), typeof(IWorldState), typeof(Address), typeof(IReleaseSpec)])); method.Call(GetPropertyInfo(nameof(CodeInfo.MachineCode), false, out _)); method.StoreLocal(localReadOnlyMemory); method.LoadLocalAddress(localReadOnlyMemory); @@ -1671,7 +1687,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co Label endOfOpcode = method.DefineLabel(); method.LoadLocal(gasAvailable); - method.LoadArgument(4); + method.LoadArgument(SPEC_INDEX); method.Call(typeof(ReleaseSpecExtensions).GetMethod(nameof(ReleaseSpecExtensions.GetExtCodeCost))); method.Subtract(); method.Duplicate(); @@ -1695,7 +1711,8 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.LoadLocal(gasAvailable); method.LoadLocalAddress(uint256C); - method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling))); + method.LoadLocalAddress(lbool); + method.Call(typeof(EvmPooledMemory).GetMethod(nameof(EvmPooledMemory.Div32Ceiling), [typeof(UInt256).MakeByRefType(), typeof(bool).MakeByRefType()])); method.LoadConstant(GasCostOf.Memory); method.Multiply(); method.Subtract(); @@ -1705,10 +1722,12 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.BranchIfLess(evmExceptionLabels[EvmExceptionType.OutOfGas]); method.LoadLocalAddress(gasAvailable); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); method.LoadLocal(address); - method.LoadArgument(4); + method.LoadConstant(true); + method.LoadArgument(WORLD_STATE_INDEX); + method.LoadArgument(SPEC_INDEX); method.Call(GetPropertyInfo(nameof(NullTxTracer.Instance), false, out _)); method.LoadConstant(true); method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.ChargeAccountAccessGas))); @@ -1718,19 +1737,19 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.Call(typeof(UInt256).GetProperty(nameof(UInt256.IsZero)).GetMethod!); method.BranchIfTrue(endOfOpcode); - method.LoadArgument(0); - method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + method.LoadArgument(VMSTATE_INDEX);; + method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); method.LoadLocalAddress(gasAvailable); method.LoadLocalAddress(uint256A); method.LoadLocalAddress(uint256C); method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.UpdateMemoryCost))); method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); - method.LoadArgument(3); - method.LoadArgument(2); + method.LoadArgument(CODE_INFO_REPOSITORY_INDEX); + method.LoadArgument(WORLD_STATE_INDEX); method.LoadLocal(address); - method.LoadArgument(4); - method.CallVirtual(typeof(ICodeInfoRepository).GetMethod(nameof(ICodeInfoRepository.GetCachedCodeInfo), [typeof(IWorldState), typeof(Address), typeof(IReleaseSpec)])); + method.LoadArgument(SPEC_INDEX); + method.Call(typeof(CodeInfoRepositoryExtensions).GetMethod(nameof(CodeInfoRepositoryExtensions.GetCachedCodeInfo), [typeof(ICodeInfoRepository), typeof(IWorldState), typeof(Address), typeof(IReleaseSpec)])); method.Call(GetPropertyInfo(nameof(CodeInfo.MachineCode), false, out _)); method.LoadLocalAddress(uint256B); @@ -1741,7 +1760,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.Call(typeof(ByteArrayExtensions).GetMethod(nameof(ByteArrayExtensions.SliceWithZeroPadding), [typeof(ReadOnlyMemory), typeof(UInt256).MakeByRefType(), typeof(int), typeof(PadDirection)])); method.StoreLocal(localZeroPaddedSpan); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); method.LoadLocalAddress(uint256A); method.LoadLocalAddress(localZeroPaddedSpan); @@ -1755,7 +1774,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co Label endOfOpcode = method.DefineLabel(); method.LoadLocal(gasAvailable); - method.LoadArgument(4); + method.LoadArgument(SPEC_INDEX); method.Call(typeof(ReleaseSpecExtensions).GetMethod(nameof(ReleaseSpecExtensions.GetExtCodeHashCost))); method.Subtract(); method.Duplicate(); @@ -1769,21 +1788,23 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.StackPop(head, 1); method.LoadLocalAddress(gasAvailable); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); method.LoadLocal(address); - method.LoadArgument(4); + method.LoadConstant(true); + method.LoadArgument(WORLD_STATE_INDEX); + method.LoadArgument(SPEC_INDEX); method.Call(GetPropertyInfo(nameof(NullTxTracer.Instance), false, out _)); method.LoadConstant(true); method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.ChargeAccountAccessGas))); method.BranchIfFalse(evmExceptionLabels[EvmExceptionType.OutOfGas]); - method.LoadArgument(2); + method.LoadArgument(WORLD_STATE_INDEX); method.LoadLocal(address); method.CallVirtual(typeof(IReadOnlyStateProvider).GetMethod(nameof(IWorldState.AccountExists))); method.LoadConstant(false); method.CompareEqual(); - method.LoadArgument(2); + method.LoadArgument(WORLD_STATE_INDEX); method.LoadLocal(address); method.CallVirtual(typeof(IReadOnlyStateProvider).GetMethod(nameof(IWorldState.IsDeadAccount))); method.Or(); @@ -1791,7 +1812,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(2); + method.LoadArgument(WORLD_STATE_INDEX); method.LoadLocal(address); method.CallVirtual(typeof(IAccountStateProvider).GetMethod(nameof(IWorldState.GetCodeHash))); method.Call(Word.SetKeccak); @@ -1803,8 +1824,8 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co { method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(2); - method.LoadArgument(0); + method.LoadArgument(WORLD_STATE_INDEX); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Env))); method.LoadField(GetFieldInfo(typeof(ExecutionEnvironment), nameof(ExecutionEnvironment.ExecutingAccount))); method.CallVirtual(typeof(IAccountStateProvider).GetMethod(nameof(IWorldState.GetBalance))); @@ -1815,7 +1836,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co case Instruction.BALANCE: { method.LoadLocal(gasAvailable); - method.LoadArgument(4); + method.LoadArgument(SPEC_INDEX); method.Call(typeof(ReleaseSpecExtensions).GetMethod(nameof(ReleaseSpecExtensions.GetBalanceCost))); method.Subtract(); method.Duplicate(); @@ -1829,10 +1850,12 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.StackPop(head, 1); method.LoadLocalAddress(gasAvailable); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); method.LoadLocal(address); - method.LoadArgument(4); + method.LoadConstant(false); + method.LoadArgument(WORLD_STATE_INDEX); + method.LoadArgument(SPEC_INDEX); method.Call(GetPropertyInfo(nameof(NullTxTracer.Instance), false, out _)); method.LoadConstant(true); method.Call(typeof(VirtualMachine).GetMethod(nameof(VirtualMachine.ChargeAccountAccessGas))); @@ -1840,7 +1863,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.CleanWord(stack, head); method.Load(stack, head); - method.LoadArgument(2); + method.LoadArgument(WORLD_STATE_INDEX); method.LoadLocal(address); method.CallVirtual(typeof(IAccountStateProvider).GetMethod(nameof(IWorldState.GetBalance))); method.Call(Word.SetUInt256); @@ -1861,18 +1884,18 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co // check if returnState is null method.MarkLabel(ret); // we get stack size - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadLocal(head); method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.StackHead))); // set stack - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadLocal(stack); method.Call(GetCastMethodInfo()); method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Stack))); // set gas available - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadLocal(gasAvailable); method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.GasAvailable))); @@ -1880,7 +1903,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.LoadLocal(isEphemeralJump); method.BranchIfTrue(skipProgramCounterSetting); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadLocal(programCounter); method.LoadConstant(1); method.Add(); @@ -1890,7 +1913,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co // set exception - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadConstant((int)EvmExceptionType.None); method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmException))); @@ -1935,7 +1958,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co method.Branch(jumpIsLocal); method.MarkLabel(jumpIsNotLocal); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.Duplicate(); method.LoadConstant(true); method.StoreLocal(isEphemeralJump); @@ -1994,7 +2017,7 @@ private static void EmitSegmentBody(Emit method, OpcodeInfo[] co foreach (var kvp in evmExceptionLabels) { method.MarkLabel(kvp.Value); - method.LoadArgument(0); + method.LoadArgument(VMSTATE_INDEX);; method.LoadConstant((int)kvp.Key); method.StoreField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmException))); method.Branch(exit); @@ -2375,7 +2398,7 @@ private static void EmitLogMethod( il.StackPop(stack.idx, 2); // UpdateMemoryCost il.LoadArgument(0); - il.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.Memory))); + il.LoadField(GetFieldInfo(typeof(ILEvmState), nameof(ILEvmState.EvmState))); il.LoadLocalAddress(gasAvailable); il.LoadLocalAddress(uint256A); // position il.LoadLocalAddress(uint256B); // length @@ -2446,42 +2469,35 @@ private static Dictionary BuildCostLookup(ReadOnlySpan co int costStart = code[0].ProgramCounter; long coststack = 0; - for (int pc = 0; pc < code.Length; pc++) - { - - OpcodeInfo op = code[pc]; - Debug.WriteLine(op); - switch (op.Operation) - { - case Instruction.JUMPDEST: - costs[costStart] = coststack; // remember the stack chain of opcodes - costStart = op.ProgramCounter; - coststack = op.Metadata.GasCost; - break; - case Instruction.GAS: - case Instruction.JUMPI: - case Instruction.JUMP: - coststack += op.Metadata.GasCost; - costs[costStart] = coststack; // remember the stack chain of opcodes - costStart = op.ProgramCounter + 1; // start with the next again - coststack = 0; - break; - default: - coststack += op.Metadata.GasCost; - break; - } - } + for (int pc = 0; pc < code.Length; pc++) + { - if (coststack > 0) + OpcodeInfo op = code[pc]; + switch (op.Operation) { - costs[costStart] = coststack; + case Instruction.JUMPDEST: + costs[costStart] = coststack; // remember the stack chain of opcodes + costStart = op.ProgramCounter; + coststack = op.Metadata.GasCost; + break; + case Instruction.GAS: + case Instruction.JUMPI: + case Instruction.JUMP: + coststack += op.Metadata.GasCost; + costs[costStart] = coststack; // remember the stack chain of opcodes + costStart = op.ProgramCounter + 1; // start with the next again + coststack = 0; + break; + default: + coststack += op.Metadata.GasCost; + break; } - return costs; - } catch (Exception ex) + } + + if (coststack > 0) { - string codeStr = string.Join(", ", code.ToArray().Select(c => c.Operation.ToString())); - System.IO.File.WriteAllText($"./costLookUp.{ex.GetHashCode()}", codeStr); - throw; + costs[costStart] = coststack; } + return costs; } } diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILExtensions.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILExtensions.cs index ce5e6f2bb73..7df6a9a633a 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILExtensions.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/ILExtensions.cs @@ -233,7 +233,6 @@ public static void LoadArray(this Emit il, ReadOnlySpan value) il.Call(typeof(ReadOnlySpan).GetMethod("op_Implicit", new[] { typeof(byte[]) })); } - public static void FakeBranch(this Emit il, Sigil.Label label) { il.LoadConstant(true); diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/IlAnalyzer.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/IlAnalyzer.cs index 73525b08179..a2c9f966394 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/IlAnalyzer.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/IlAnalyzer.cs @@ -13,6 +13,9 @@ using System.Threading; using System.Threading.Tasks; using static Nethermind.Evm.CodeAnalysis.IL.ILCompiler; +using static Nethermind.Evm.CodeAnalysis.IL.IlInfo; +using ILMode = int; + namespace Nethermind.Evm.CodeAnalysis.IL; using Mode = int; @@ -58,7 +61,7 @@ public static void Initialize() /// Starts the analyzing in a background task and outputs the value in the . /// thou /// The destination output. - internal static Task StartAnalysis(CodeInfo codeInfo, Mode mode, ILogger logger) + internal static Task StartAnalysis(CodeInfo codeInfo, ILMode mode, ILogger logger) { Metrics.IlvmContractsAnalyzed++; @@ -101,7 +104,7 @@ public static (OpcodeInfo[], byte[][]) StripByteCode(ReadOnlySpan machineC /// /// For now, return null always to default to EVM. /// - internal static void Analysis(CodeInfo codeInfo, Mode mode, ILogger logger) + private static void Analysis(CodeInfo codeInfo, ILMode mode, ILogger logger) { ReadOnlyMemory machineCode = codeInfo.MachineCode; @@ -125,6 +128,7 @@ static void SegmentCode(CodeInfo codeInfo, (OpcodeInfo[], byte[][]) codeData, Il } } + long segmentAvgSize = 0; for (int i = -1; i <= j; i++) { int start = i == -1 ? 0 : statefulOpcodeindex[i] + 1; @@ -143,6 +147,9 @@ static void SegmentCode(CodeInfo codeInfo, (OpcodeInfo[], byte[][]) codeData, Il var lastOp = segment[^1]; var segmentName = GenerateName(firstOp.ProgramCounter..(lastOp.ProgramCounter + lastOp.Metadata.AdditionalBytes)); + segmentAvgSize += segment.Length; + ilinfo.Segments.GetOrAdd((ushort)segment[0].ProgramCounter, CompileSegment(segmentName, segment, codeData.Item2)); + } List pcKeys = [segment[0].ProgramCounter]; for (int k = 0; k < segment.Length; k++) @@ -153,6 +160,8 @@ static void SegmentCode(CodeInfo codeInfo, (OpcodeInfo[], byte[][]) codeData, Il } } + Interlocked.Or(ref ilinfo.Mode, IlInfo.ILMode.JIT_MODE); + } var segmentExecutionCtx = CompileSegment(segmentName, segment, codeData.Item2); foreach (ushort pc in pcKeys) { @@ -190,15 +199,12 @@ static void CheckPatterns(ReadOnlyMemory machineCode, IlInfo ilinfo) switch (mode) { case IlInfo.ILMode.PAT_MODE: - if (logger.IsInfo) logger.Info($"Starting Analysis of patterns of code {codeInfo.CodeHash}"); + if (logger.IsInfo) logger.Info($"Analyzing patterns of code {codeInfo.CodeHash}"); CheckPatterns(machineCode, codeInfo.IlInfo); - if (logger.IsInfo) logger.Info($"Finished Analysis of patterns of code {codeInfo.CodeHash}"); break; case IlInfo.ILMode.JIT_MODE: - if (logger.IsInfo) logger.Info($"Starting Precompilation of segments of code {codeInfo.CodeHash}"); - System.IO.File.WriteAllText($"./{codeInfo.CodeHash}.evm", machineCode.ToArray().ToHexString()); + if (logger.IsInfo) logger.Info($"Precompiling of segments of code {codeInfo.CodeHash}"); SegmentCode(codeInfo, StripByteCode(machineCode.Span), codeInfo.IlInfo); - if (logger.IsInfo) logger.Info($"Finished Precompiling of segments of code {codeInfo.CodeHash}"); break; } } diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/IlInfo.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/IlInfo.cs index 76fe8232597..81337cd3ff9 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/IlInfo.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/IlInfo.cs @@ -75,13 +75,13 @@ public bool TryExecute(ILogger logger, EvmState vmState, u try { - var executionResult = new ILChunkExecutionResult(); - if (Mode.HasFlag(ILMode.JIT_MODE) && Segments.TryGetValue((ushort)programCounter, out SegmentExecutionCtx ctx)) - { - Metrics.IlvmPrecompiledSegmentsExecutions++; - if (typeof(TTracingInstructions) == typeof(IsTracing)) - StartTracingSegment(in vmState, in stack, tracer, programCounter, gasAvailable, ctx); - if (logger.IsInfo) logger.Info($"Executing segment {ctx.Name} at {programCounter} on code {vmState.Env.CodeInfo.CodeHash}"); + var executionResult = new ILChunkExecutionResult(); + if (Mode.HasFlag(ILMode.JIT_MODE) && Segments.TryGetValue((ushort)programCounter, out SegmentExecutionCtx ctx)) + { + Metrics.IlvmPrecompiledSegmentsExecutions++; + if (typeof(TTracingInstructions) == typeof(IsTracing)) + StartTracingSegment(in vmState, in stack, tracer, programCounter, gasAvailable, ctx); + if (logger.IsInfo) logger.Info($"Executing segment {ctx.Name} at {programCounter}"); vmState.DataStackHead = stack.Head; var ilvmState = new ILEvmState(chainId, vmState, EvmExceptionType.None, (ushort)programCounter, gasAvailable, ref outputBuffer); @@ -100,12 +100,12 @@ public bool TryExecute(ILogger logger, EvmState vmState, u stack.Head = ilvmState.StackHead; - if (typeof(TTracingInstructions) == typeof(IsTracing)) - tracer.ReportOperationRemainingGas(gasAvailable); - } - else if (Mode.HasFlag(ILMode.PAT_MODE) && Chunks.TryGetValue((ushort)programCounter, out InstructionChunk chunk)) - { - Metrics.IlvmPredefinedPatternsExecutions++; + if (typeof(TTracingInstructions) == typeof(IsTracing)) + tracer.ReportOperationRemainingGas(gasAvailable); + } + else if (Mode.HasFlag(ILMode.PAT_MODE) && Chunks.TryGetValue((ushort)programCounter, out InstructionChunk chunk)) + { + Metrics.IlvmPredefinedPatternsExecutions++; if (typeof(TTracingInstructions) == typeof(IsTracing)) StartTracingSegment(in vmState, in stack, tracer, programCounter, gasAvailable, chunk); diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/PredefinedPatterns.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/PredefinedPatterns.cs index 7a1be172761..567e45dfc0d 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/PredefinedPatterns.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/IL/PredefinedPatterns.cs @@ -40,7 +40,7 @@ public void Invoke(EvmState vmState, IBlockhashProvider blockhashProvider, IW byte value = vmState.Env.CodeInfo.MachineCode.Span[programCounter + 1]; byte location = vmState.Env.CodeInfo.MachineCode.Span[programCounter + 3]; - VirtualMachine.UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, location, 32); + VirtualMachine.UpdateMemoryCost(vmState, ref gasAvailable, location, 32); vmState.Memory.SaveByte(location, value); stack.PushUInt256(vmState.Env.Value); stack.PushUInt256(vmState.Env.Value); @@ -74,7 +74,7 @@ public void Invoke(EvmState vmState, IBlockhashProvider blockhashProvider, IW Address address = stack.PopAddress(); - if (!VirtualMachine.ChargeAccountAccessGas(ref gasAvailable, vmState, address, spec, NullTxTracer.Instance)) + if (!VirtualMachine.ChargeAccountAccessGas(ref gasAvailable, vmState, address, false, worldState, spec, NullTxTracer.Instance, true)) result.ExceptionType = EvmExceptionType.OutOfGas; int contractCodeSize = codeInfoRepository.GetCachedCodeInfo(worldState, address, spec).MachineCode.Length; diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs index 599d05425da..79e8124e29a 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs @@ -16,49 +16,12 @@ using Nethermind.Evm.Precompiles.Bls; using Nethermind.Evm.Precompiles.Snarks; using Nethermind.State; +using Nethermind.Crypto; namespace Nethermind.Evm; public class CodeInfoRepository : ICodeInfoRepository { - internal sealed class CodeLruCache - { - private const int CacheCount = 16; - private const int CacheMax = CacheCount - 1; - private readonly ClockCache[] _caches; - - public CodeLruCache() - { - _caches = new ClockCache[CacheCount]; - for (int i = 0; i < _caches.Length; i++) - { - // Cache per nibble to reduce contention as TxPool is very parallel - _caches[i] = new ClockCache(MemoryAllowance.CodeCacheSize / CacheCount); - } - } - - public CodeInfo? Get(in ValueHash256 codeHash) - { - ClockCache cache = _caches[GetCacheIndex(codeHash)]; - return cache.Get(codeHash); - } - - public bool Set(in ValueHash256 codeHash, CodeInfo codeInfo) - { - ClockCache cache = _caches[GetCacheIndex(codeHash)]; - return cache.Set(codeHash, codeInfo); - } - - private static int GetCacheIndex(in ValueHash256 codeHash) => codeHash.Bytes[^1] & CacheMax; - - public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out CodeInfo? codeInfo) - { - codeInfo = Get(codeHash); - return codeInfo is not null; - } - } - - private static readonly FrozenDictionary _precompiles = InitializePrecompiledContracts(); private static readonly CodeLruCache _codeCache = new(); private readonly FrozenDictionary _localPrecompiles; @@ -81,13 +44,13 @@ private static FrozenDictionary InitializePrecompiledCon [G1AddPrecompile.Address] = new(G1AddPrecompile.Instance), [G1MulPrecompile.Address] = new(G1MulPrecompile.Instance), - [G1MultiMulPrecompile.Address] = new(G1MultiMulPrecompile.Instance), + [G1MSMPrecompile.Address] = new(G1MSMPrecompile.Instance), [G2AddPrecompile.Address] = new(G2AddPrecompile.Instance), [G2MulPrecompile.Address] = new(G2MulPrecompile.Instance), - [G2MultiMulPrecompile.Address] = new(G2MultiMulPrecompile.Instance), - [PairingPrecompile.Address] = new(PairingPrecompile.Instance), - [MapToG1Precompile.Address] = new(MapToG1Precompile.Instance), - [MapToG2Precompile.Address] = new(MapToG2Precompile.Instance), + [G2MSMPrecompile.Address] = new(G2MSMPrecompile.Instance), + [PairingCheckPrecompile.Address] = new(PairingCheckPrecompile.Instance), + [MapFpToG1Precompile.Address] = new(MapFpToG1Precompile.Instance), + [MapFp2ToG2Precompile.Address] = new(MapFp2ToG2Precompile.Instance), [PointEvaluationPrecompile.Address] = new(PointEvaluationPrecompile.Instance), @@ -102,13 +65,26 @@ public CodeInfoRepository(ConcurrentDictionary kvp.Key, kvp => CreateCachedPrecompile(kvp, precompileCache)); } - public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec) + public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress) { + delegationAddress = null; if (codeSource.IsPrecompile(vmSpec)) { return _localPrecompiles[codeSource]; } + CodeInfo cachedCodeInfo = InternalGetCachedCode(worldState, codeSource); + + if (TryGetDelegatedAddress(cachedCodeInfo.MachineCode.Span, out delegationAddress)) + { + cachedCodeInfo = InternalGetCachedCode(worldState, delegationAddress); + } + + return cachedCodeInfo; + } + + private static CodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, Address codeSource) + { CodeInfo? cachedCodeInfo = null; ValueHash256 codeHash = worldState.GetCodeHash(codeSource); if (codeHash == Keccak.OfAnEmptyString.ValueHash256) @@ -145,27 +121,61 @@ static void MissingCode(Address codeSource, in ValueHash256 codeHash) } } - public CodeInfo GetOrAdd(ValueHash256 codeHash, ReadOnlySpan initCode) + public void InsertCode(IWorldState state, ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec) { - if (!_codeCache.TryGet(codeHash, out CodeInfo? codeInfo)) - { - codeInfo = new(initCode.ToArray(), codeHash); + CodeInfo codeInfo = new(code); + codeInfo.AnalyseInBackgroundIfRequired(); - // Prime the code cache as likely to be used by more txs - _codeCache.Set(codeHash, codeInfo); - } + ValueHash256 codeHash = code.Length == 0 ? ValueKeccak.OfAnEmptyString : ValueKeccak.Compute(code.Span); + state.InsertCode(codeOwner, codeHash, code, spec); + _codeCache.Set(codeHash, codeInfo); + } - return codeInfo; + public void SetDelegation(IWorldState state, Address codeSource, Address authority, IReleaseSpec spec) + { + byte[] authorizedBuffer = new byte[Eip7702Constants.DelegationHeader.Length + Address.Size]; + Eip7702Constants.DelegationHeader.CopyTo(authorizedBuffer); + codeSource.Bytes.CopyTo(authorizedBuffer, Eip7702Constants.DelegationHeader.Length); + ValueHash256 codeHash = ValueKeccak.Compute(authorizedBuffer); + state.InsertCode(authority, codeHash, authorizedBuffer.AsMemory(), spec); + _codeCache.Set(codeHash, new CodeInfo(authorizedBuffer)); } + /// + /// Retrieves code hash of delegation if delegated. Otherwise code hash of . + /// + /// + /// + public ValueHash256 GetExecutableCodeHash(IWorldState worldState, Address address) + { + ValueHash256 codeHash = worldState.GetCodeHash(address); + if (codeHash == Keccak.OfAnEmptyString.ValueHash256) + { + return Keccak.OfAnEmptyString.ValueHash256; + } - public void InsertCode(IWorldState state, ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec) + CodeInfo codeInfo = InternalGetCachedCode(worldState, address); + return codeInfo.IsEmpty + ? Keccak.OfAnEmptyString.ValueHash256 + : TryGetDelegatedAddress(codeInfo.MachineCode.Span, out Address? delegationAddress) + ? worldState.GetCodeHash(delegationAddress) + : codeHash; + } + + /// + /// Parses delegation code to extract the contained address. + /// Assumes is delegation code! + /// + private static bool TryGetDelegatedAddress(ReadOnlySpan code, [NotNullWhen(true)] out Address? address) { - Hash256 codeHash = code.Length == 0 ? Keccak.OfAnEmptyString : Keccak.Compute(code.Span); - CodeInfo codeInfo = new(code, codeHash); - codeInfo.AnalyseInBackgroundIfRequired(); - state.InsertCode(codeOwner, codeHash, code, spec); - _codeCache.Set(codeHash, codeInfo); + if (Eip7702Constants.IsDelegatedCode(code)) + { + address = new Address(code.Slice(Eip7702Constants.DelegationHeader.Length).ToArray()); + return true; + } + + address = null; + return false; } private CodeInfo CreateCachedPrecompile( @@ -173,6 +183,9 @@ private CodeInfo CreateCachedPrecompile( ConcurrentDictionary, bool)> cache) => new(new CachedPrecompile(originalPrecompile.Key.Value, originalPrecompile.Value.Precompile!, cache)); + public bool TryGetDelegation(IReadOnlyStateProvider worldState, Address address, [NotNullWhen(true)] out Address? delegatedAddress) => + TryGetDelegatedAddress(InternalGetCachedCode(worldState, address).MachineCode.Span, out delegatedAddress); + private class CachedPrecompile( Address address, IPrecompile precompile, @@ -198,4 +211,42 @@ private class CachedPrecompile( return result; } } + + private sealed class CodeLruCache + { + private const int CacheCount = 16; + private const int CacheMax = CacheCount - 1; + private readonly ClockCache[] _caches; + + public CodeLruCache() + { + _caches = new ClockCache[CacheCount]; + for (int i = 0; i < _caches.Length; i++) + { + // Cache per nibble to reduce contention as TxPool is very parallel + _caches[i] = new ClockCache(MemoryAllowance.CodeCacheSize / CacheCount); + } + } + + public CodeInfo? Get(in ValueHash256 codeHash) + { + ClockCache cache = _caches[GetCacheIndex(codeHash)]; + return cache.Get(codeHash); + } + + public bool Set(in ValueHash256 codeHash, CodeInfo codeInfo) + { + ClockCache cache = _caches[GetCacheIndex(codeHash)]; + return cache.Set(codeHash, codeInfo); + } + + private static int GetCacheIndex(in ValueHash256 codeHash) => codeHash.Bytes[^1] & CacheMax; + + public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out CodeInfo? codeInfo) + { + codeInfo = Get(codeHash); + return codeInfo is not null; + } + } } + diff --git a/src/Nethermind/Nethermind.Evm/EvmPooledMemory.cs b/src/Nethermind/Nethermind.Evm/EvmPooledMemory.cs index 306e8d135d1..01e12e32c96 100644 --- a/src/Nethermind/Nethermind.Evm/EvmPooledMemory.cs +++ b/src/Nethermind/Nethermind.Evm/EvmPooledMemory.cs @@ -61,6 +61,18 @@ public void Save(in UInt256 location, Span value) value.CopyTo(_memory.AsSpan((int)location, value.Length)); } + private static void CheckMemoryAccessViolation(in UInt256 location, in UInt256 length, out ulong newLength, out bool outOfGas) + { + if (location.IsLargerThanULong() || length.IsLargerThanULong()) + { + outOfGas = true; + newLength = 0; + return; + } + + CheckMemoryAccessViolationInner(location.u0, length.u0, out newLength, out outOfGas); + } + private static void CheckMemoryAccessViolation(in UInt256 location, in UInt256 length, out ulong newLength) { if (location.IsLargerThanULong() || length.IsLargerThanULong()) @@ -68,7 +80,11 @@ private static void CheckMemoryAccessViolation(in UInt256 location, in UInt256 l ThrowOutOfGasException(); } - CheckMemoryAccessViolation(location.u0, length.u0, out newLength); + CheckMemoryAccessViolationInner(location.u0, length.u0, out newLength, out bool outOfGas); + if (outOfGas) + { + ThrowOutOfGasException(); + } } private static void CheckMemoryAccessViolation(in UInt256 location, ulong length, out ulong newLength) @@ -78,17 +94,24 @@ private static void CheckMemoryAccessViolation(in UInt256 location, ulong length ThrowOutOfGasException(); } - CheckMemoryAccessViolation(location.u0, length, out newLength); + CheckMemoryAccessViolationInner(location.u0, length, out newLength, out bool outOfGas); + if (outOfGas) + { + ThrowOutOfGasException(); + } } - private static void CheckMemoryAccessViolation(ulong location, ulong length, out ulong newLength) + private static void CheckMemoryAccessViolationInner(ulong location, ulong length, out ulong newLength, out bool outOfGas) { ulong totalSize = location + length; if (totalSize < location || totalSize > long.MaxValue) { - ThrowOutOfGasException(); + outOfGas = true; + newLength = 0; + return; } + outOfGas = false; newLength = totalSize; } @@ -197,19 +220,23 @@ private void ClearForTracing(ulong size) } } - public long CalculateMemoryCost(in UInt256 location, in UInt256 length) + public long CalculateMemoryCost(in UInt256 location, in UInt256 length, out bool outOfGas) { + outOfGas = false; if (length.IsZero) { return 0L; } - CheckMemoryAccessViolation(in location, in length, out ulong newSize); + CheckMemoryAccessViolation(in location, in length, out ulong newSize, out outOfGas); + if (outOfGas) return 0; if (newSize > Size) { - long newActiveWords = Div32Ceiling(newSize); - long activeWords = Div32Ceiling(Size); + long newActiveWords = Div32Ceiling(newSize, out outOfGas); + if (outOfGas) return 0; + long activeWords = Div32Ceiling(Size, out outOfGas); + if (outOfGas) return 0; // TODO: guess it would be well within ranges but this needs to be checked and comment need to be added with calculations ulong cost = (ulong) @@ -230,6 +257,17 @@ public long CalculateMemoryCost(in UInt256 location, in UInt256 length) return 0L; } + public long CalculateMemoryCost(in UInt256 location, in UInt256 length) + { + long result = CalculateMemoryCost(in location, in length, out bool outOfGas); + if (outOfGas) + { + throw new OutOfGasException(); + } + + return result; + } + public TraceMemory GetTrace() { ulong size = Size; @@ -247,11 +285,12 @@ public void Dispose() } } - public static long Div32Ceiling(in UInt256 length) + public static long Div32Ceiling(in UInt256 length, out bool outOfGas) { if (length.IsLargerThanULong()) { - ThrowOutOfGasException(); + outOfGas = true; + return 0; } ulong result = length.u0; @@ -264,12 +303,25 @@ public static long Div32Ceiling(in UInt256 length) if (result > int.MaxValue) { - ThrowOutOfGasException(); + outOfGas = true; + return 0; } + outOfGas = false; return (long)result; } + public static long Div32Ceiling(in UInt256 length) + { + long result = Div32Ceiling(in length, out bool outOfGas); + if (outOfGas) + { + ThrowOutOfGasException(); + } + + return result; + } + private void UpdateSize(in UInt256 location, in UInt256 length, bool rentIfNeeded = true) { UpdateSize((ulong)(location + length), rentIfNeeded); diff --git a/src/Nethermind/Nethermind.Evm/GasCostOf.cs b/src/Nethermind/Nethermind.Evm/GasCostOf.cs index 68dc8cae654..e67770b53e8 100644 --- a/src/Nethermind/Nethermind.Evm/GasCostOf.cs +++ b/src/Nethermind/Nethermind.Evm/GasCostOf.cs @@ -62,5 +62,6 @@ public static class GasCostOf public const long AccessStorageListEntry = 1900; // eip-2930 public const long TLoad = WarmStateRead; // eip-1153 public const long TStore = WarmStateRead; // eip-1153 + public const long PerAuthBaseCost = 2500; // eip-7702 } } diff --git a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs index 6fde3cbfe7f..b2f0a4a176d 100644 --- a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -12,7 +14,15 @@ namespace Nethermind.Evm; public interface ICodeInfoRepository { - CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec); - CodeInfo GetOrAdd(ValueHash256 codeHash, ReadOnlySpan initCode); + CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress); + ValueHash256 GetExecutableCodeHash(IWorldState worldState, Address address); void InsertCode(IWorldState state, ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec); + void SetDelegation(IWorldState state, Address codeSource, Address authority, IReleaseSpec spec); + bool TryGetDelegation(IReadOnlyStateProvider worldState, Address address, [NotNullWhen(true)] out Address? delegatedAddress); +} + +public static class CodeInfoRepositoryExtensions +{ + public static CodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, IWorldState worldState, Address codeSource, IReleaseSpec vmSpec) + => codeInfoRepository.GetCachedCodeInfo(worldState, codeSource, vmSpec, out _); } diff --git a/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs b/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs index 8099d6d7b6c..a5ca480a980 100644 --- a/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs @@ -1,11 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Core.Specs; -using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.Tracing; using Nethermind.State; diff --git a/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs b/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs index 7742bcfc5ba..a8e5dc5c86f 100644 --- a/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs +++ b/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs @@ -2,52 +2,39 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Numerics; using Nethermind.Core; using Nethermind.Core.Eip2930; using Nethermind.Core.Specs; using Nethermind.Int256; -using System.Runtime.Intrinsics; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; using Nethermind.Core.Extensions; namespace Nethermind.Evm; public static class IntrinsicGasCalculator { - public static long Calculate(Transaction transaction, IReleaseSpec releaseSpec) - { - long result = GasCostOf.Transaction; - result += DataCost(transaction, releaseSpec); - result += CreateCost(transaction, releaseSpec); - result += AccessListCost(transaction, releaseSpec); - return result; - } - - private static long CreateCost(Transaction transaction, IReleaseSpec releaseSpec) - { - long createCost = 0; - if (transaction.IsContractCreation && releaseSpec.IsEip2Enabled) - { - createCost += GasCostOf.TxCreate; - } + public static long Calculate(Transaction transaction, IReleaseSpec releaseSpec) => + GasCostOf.Transaction + + DataCost(transaction, releaseSpec) + + CreateCost(transaction, releaseSpec) + + AccessListCost(transaction, releaseSpec) + + AuthorizationListCost(transaction, releaseSpec); - return createCost; - } + private static long CreateCost(Transaction transaction, IReleaseSpec releaseSpec) => + transaction.IsContractCreation && releaseSpec.IsEip2Enabled ? GasCostOf.TxCreate : 0; private static long DataCost(Transaction transaction, IReleaseSpec releaseSpec) { - long txDataNonZeroGasCost = - releaseSpec.IsEip2028Enabled ? GasCostOf.TxDataNonZeroEip2028 : GasCostOf.TxDataNonZero; + long txDataNonZeroGasCost = releaseSpec.IsEip2028Enabled ? GasCostOf.TxDataNonZeroEip2028 : GasCostOf.TxDataNonZero; Span data = transaction.Data.GetValueOrDefault().Span; int totalZeros = data.CountZeros(); - var baseDataCost = (transaction.IsContractCreation && releaseSpec.IsEip3860Enabled + long baseDataCost = transaction.IsContractCreation && releaseSpec.IsEip3860Enabled ? EvmPooledMemory.Div32Ceiling((UInt256)data.Length) * GasCostOf.InitCodeWord - : 0); + : 0; return baseDataCost + totalZeros * GasCostOf.TxDataZero + @@ -57,27 +44,48 @@ private static long DataCost(Transaction transaction, IReleaseSpec releaseSpec) private static long AccessListCost(Transaction transaction, IReleaseSpec releaseSpec) { AccessList? accessList = transaction.AccessList; - long accessListCost = 0; if (accessList is not null) { if (!releaseSpec.UseTxAccessLists) { - throw new InvalidDataException( - $"Transaction with an access list received within the context of {releaseSpec.Name}. Eip-2930 is not enabled."); + ThrowInvalidDataException(releaseSpec); } - if (accessList.IsEmpty) return accessListCost; + (int addressesCount, int storageKeysCount) = accessList.Count; + return addressesCount * GasCostOf.AccessAccountListEntry + storageKeysCount * GasCostOf.AccessStorageListEntry; + } + + return 0; + + [DoesNotReturn] + [StackTraceHidden] + static void ThrowInvalidDataException(IReleaseSpec releaseSpec) + { + throw new InvalidDataException($"Transaction with an authorization list received within the context of {releaseSpec.Name}. Eip-7702 is not enabled."); + } + } + + private static long AuthorizationListCost(Transaction transaction, IReleaseSpec releaseSpec) + { + AuthorizationTuple[]? transactionAuthorizationList = transaction.AuthorizationList; - foreach ((Address address, AccessList.StorageKeysEnumerable storageKeys) entry in accessList) + if (transactionAuthorizationList is not null) + { + if (!releaseSpec.IsAuthorizationListEnabled) { - accessListCost += GasCostOf.AccessAccountListEntry; - foreach (UInt256 _ in entry.storageKeys) - { - accessListCost += GasCostOf.AccessStorageListEntry; - } + ThrowInvalidDataException(releaseSpec); } + + return transactionAuthorizationList.Length * GasCostOf.NewAccount; } - return accessListCost; + return 0; + + [DoesNotReturn] + [StackTraceHidden] + static void ThrowInvalidDataException(IReleaseSpec releaseSpec) + { + throw new InvalidDataException($"Transaction with an authorization list received within the context of {releaseSpec.Name}. Eip-7702 is not enabled."); + } } } diff --git a/src/Nethermind/Nethermind.Evm/Metrics.cs b/src/Nethermind/Nethermind.Evm/Metrics.cs index d4a5d19c640..3cb984d49bd 100644 --- a/src/Nethermind/Nethermind.Evm/Metrics.cs +++ b/src/Nethermind/Nethermind.Evm/Metrics.cs @@ -67,6 +67,33 @@ public class Metrics [Description("Number of BN254_PAIRING precompile calls.")] public static long Bn254PairingPrecompile { get; set; } + [Description("Number of BLS12_G1ADD precompile calls.")] + public static long BlsG1AddPrecompile { get; set; } + + [Description("Number of BLS12_G1MUL precompile calls.")] + public static long BlsG1MulPrecompile { get; set; } + + [Description("Number of BLS12_G1MSM precompile calls.")] + public static long BlsG1MSMPrecompile { get; set; } + + [Description("Number of BLS12_G2ADD precompile calls.")] + public static long BlsG2AddPrecompile { get; set; } + + [Description("Number of BLS12_G2MUL precompile calls.")] + public static long BlsG2MulPrecompile { get; set; } + + [Description("Number of BLS12_G2MSM precompile calls.")] + public static long BlsG2MSMPrecompile { get; set; } + + [Description("Number of BLS12_PAIRING_CHECK precompile calls.")] + public static long BlsPairingCheckPrecompile { get; set; } + + [Description("Number of BLS12_MAP_FP_TO_G1 precompile calls.")] + public static long BlsMapFpToG1Precompile { get; set; } + + [Description("Number of BLS12_MAP_FP2_TO_G2 precompile calls.")] + public static long BlsMapFp2ToG2Precompile { get; set; } + [Description("Number of EC_RECOVERY precompile calls.")] public static long EcRecoverPrecompile { get; set; } diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/BlsConst.cs b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/BlsConst.cs index 6587e354ee4..cd378705202 100644 --- a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/BlsConst.cs +++ b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/BlsConst.cs @@ -8,6 +8,8 @@ namespace Nethermind.Evm.Precompiles.Bls; public static class BlsConst { + public const bool DisableConcurrency = false; + public const bool DisableSubgroupChecks = false; public const int LenFr = 32; public const int LenFp = 64; public const int LenFpTrimmed = 48; @@ -16,6 +18,7 @@ public static class BlsConst public const int LenG1Trimmed = 2 * LenFpTrimmed; public const int LenG2 = 4 * LenFp; public const int LenG2Trimmed = 4 * LenFpTrimmed; + public static readonly byte[] BaseFieldOrder = [0x1a, 0x01, 0x11, 0xea, 0x39, 0x7f, 0xe6, 0x9a, 0x4b, 0x1b, 0xa7, 0xb6, 0x43, 0x4b, 0xac, 0xd7, 0x64, 0x77, 0x4b, 0x84, 0xf3, 0x85, 0x12, 0xbf, 0x67, 0x30, 0xd2, 0xa0, 0xf6, 0xb0, 0xf6, 0x24, 0x1e, 0xab, 0xff, 0xfe, 0xb1, 0x53, 0xff, 0xff, 0xb9, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xaa, 0xab]; public static readonly ReadOnlyMemory G1Inf = Enumerable.Repeat(0, 128).ToArray(); public static readonly ReadOnlyMemory G2Inf = Enumerable.Repeat(0, 256).ToArray(); diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/BlsExtensions.cs b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/BlsExtensions.cs index e81aa564d78..ab8d862d60d 100644 --- a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/BlsExtensions.cs +++ b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/BlsExtensions.cs @@ -21,7 +21,7 @@ public static bool TryDecodeRaw(this G1 p, ReadOnlySpan raw) return false; } - p.Point.Clear(); + p.Zero(); ReadOnlySpan fp0 = raw[BlsConst.LenFpPad..BlsConst.LenFp]; ReadOnlySpan fp1 = raw[(BlsConst.LenFp + BlsConst.LenFpPad)..]; @@ -41,20 +41,6 @@ public static bool TryDecodeRaw(this G1 p, ReadOnlySpan raw) return true; } - public static ReadOnlyMemory EncodeRaw(this G1 p) - { - if (p.IsInf()) - { - return BlsConst.G1Inf; - } - - byte[] raw = new byte[BlsConst.LenG1]; - ReadOnlySpan trimmed = p.Serialize(); - trimmed[..BlsConst.LenFpTrimmed].CopyTo(raw.AsSpan()[BlsConst.LenFpPad..BlsConst.LenFp]); - trimmed[BlsConst.LenFpTrimmed..].CopyTo(raw.AsSpan()[(BlsConst.LenFp + BlsConst.LenFpPad)..]); - return raw; - } - public static bool TryDecodeRaw(this G2 p, ReadOnlySpan raw) { if (raw.Length != BlsConst.LenG2) @@ -70,7 +56,7 @@ public static bool TryDecodeRaw(this G2 p, ReadOnlySpan raw) return false; } - p.Point.Clear(); + p.Zero(); ReadOnlySpan fp0 = raw[BlsConst.LenFpPad..BlsConst.LenFp]; ReadOnlySpan fp1 = raw[(BlsConst.LenFp + BlsConst.LenFpPad)..(2 * BlsConst.LenFp)]; @@ -96,6 +82,20 @@ public static bool TryDecodeRaw(this G2 p, ReadOnlySpan raw) return true; } + public static ReadOnlyMemory EncodeRaw(this G1 p) + { + if (p.IsInf()) + { + return BlsConst.G1Inf; + } + + byte[] raw = new byte[BlsConst.LenG1]; + ReadOnlySpan trimmed = p.Serialize(); + trimmed[..BlsConst.LenFpTrimmed].CopyTo(raw.AsSpan()[BlsConst.LenFpPad..BlsConst.LenFp]); + trimmed[BlsConst.LenFpTrimmed..].CopyTo(raw.AsSpan()[(BlsConst.LenFp + BlsConst.LenFpPad)..]); + return raw; + } + public static ReadOnlyMemory EncodeRaw(this G2 p) { if (p.IsInf()) @@ -128,4 +128,52 @@ public static bool ValidRawFp(ReadOnlySpan fp) // check that fp < base field order return fp[BlsConst.LenFpPad..].SequenceCompareTo(BlsConst.BaseFieldOrder.AsSpan()) < 0; } + + public static bool TryDecodeG1ToBuffer(ReadOnlyMemory inputData, Memory pointBuffer, Memory scalarBuffer, int dest, int index) + => TryDecodePointToBuffer(inputData, pointBuffer, scalarBuffer, dest, index, BlsConst.LenG1, G1MSMPrecompile.ItemSize, DecodeAndCheckG1); + + public static bool TryDecodeG2ToBuffer(ReadOnlyMemory inputData, Memory pointBuffer, Memory scalarBuffer, int dest, int index) + => TryDecodePointToBuffer(inputData, pointBuffer, scalarBuffer, dest, index, BlsConst.LenG2, G2MSMPrecompile.ItemSize, DecodeAndCheckG2); + + private static bool DecodeAndCheckG1(ReadOnlyMemory rawPoint, Memory pointBuffer, int dest) + { + G1 p = new(pointBuffer.Span[(dest * G1.Sz)..]); + return p.TryDecodeRaw(rawPoint.Span) && (BlsConst.DisableSubgroupChecks || p.InGroup()); + } + + private static bool DecodeAndCheckG2(ReadOnlyMemory rawPoint, Memory pointBuffer, int dest) + { + G2 p = new(pointBuffer.Span[(dest * G2.Sz)..]); + return p.TryDecodeRaw(rawPoint.Span) && (BlsConst.DisableSubgroupChecks || p.InGroup()); + } + + private static bool TryDecodePointToBuffer( + ReadOnlyMemory inputData, + Memory pointBuffer, + Memory scalarBuffer, + int dest, + int index, + int pointLen, + int itemSize, + Func, Memory, int, bool> decodeAndCheckPoint) + { + if (dest == -1) + { + return true; + } + + int offset = index * itemSize; + ReadOnlyMemory rawPoint = inputData[offset..(offset + pointLen)]; + ReadOnlyMemory reversedScalar = inputData[(offset + pointLen)..(offset + itemSize)]; + + if (!decodeAndCheckPoint(rawPoint, pointBuffer, dest)) + { + return false; + } + + int destOffset = dest * 32; + reversedScalar.CopyTo(scalarBuffer[destOffset..]); + scalarBuffer[destOffset..(destOffset + 32)].Span.Reverse(); + return true; + } } diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G1AddPrecompile.cs b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G1AddPrecompile.cs index 87d7806d1f4..53df7f426df 100644 --- a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G1AddPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G1AddPrecompile.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Specs; @@ -26,8 +27,11 @@ private G1AddPrecompile() public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0L; + [SkipLocalsInit] public (ReadOnlyMemory, bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { + Metrics.BlsG1AddPrecompile++; + const int expectedInputLength = 2 * BlsConst.LenG1; if (inputData.Length != expectedInputLength) { diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G1MultiMulPrecompile.cs b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G1MSMPrecompile.cs similarity index 61% rename from src/Nethermind/Nethermind.Evm/Precompiles/Bls/G1MultiMulPrecompile.cs rename to src/Nethermind/Nethermind.Evm/Precompiles/Bls/G1MSMPrecompile.cs index 4e34d7e8e49..8f3f734f37f 100644 --- a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G1MultiMulPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G1MSMPrecompile.cs @@ -8,17 +8,18 @@ using Nethermind.Core.Collections; using G1 = Nethermind.Crypto.Bls.P1; +using System.Runtime.CompilerServices; namespace Nethermind.Evm.Precompiles.Bls; /// /// https://eips.ethereum.org/EIPS/eip-2537 /// -public class G1MultiMulPrecompile : IPrecompile +public class G1MSMPrecompile : IPrecompile { - public static readonly G1MultiMulPrecompile Instance = new(); + public static readonly G1MSMPrecompile Instance = new(); - private G1MultiMulPrecompile() + private G1MSMPrecompile() { } @@ -32,10 +33,13 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec return 12000L * k * Discount.For(k) / 1000; } - private const int ItemSize = 160; + public const int ItemSize = 160; + [SkipLocalsInit] public (ReadOnlyMemory, bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { + Metrics.BlsG1MSMPrecompile++; + if (inputData.Length % ItemSize > 0 || inputData.Length == 0) { return IPrecompile.Failure; @@ -43,7 +47,7 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec int nItems = inputData.Length / ItemSize; - using ArrayPoolList rawPoints = new(nItems * 18, nItems * 18); + using ArrayPoolList rawPoints = new(nItems * G1.Sz, nItems * G1.Sz); using ArrayPoolList rawScalars = new(nItems * 32, nItems * 32); using ArrayPoolList pointDestinations = new(nItems); @@ -64,34 +68,39 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec } bool fail = false; - Parallel.ForEach(pointDestinations, (dest, state, i) => + +#pragma warning disable CS0162 // Unreachable code detected + if (BlsConst.DisableConcurrency) { - if (dest != -1) + for (int i = 0; i < pointDestinations.Count; i++) { - int offset = (int)i * ItemSize; - ReadOnlySpan rawPoint = inputData[offset..(offset + BlsConst.LenG1)].Span; - ReadOnlySpan rawScalar = inputData[(offset + BlsConst.LenG1)..(offset + ItemSize)].Span; - - G1 p = new(rawPoints.AsSpan()[(dest * 18)..]); - - if (!p.TryDecodeRaw(rawPoint) || !p.InGroup()) + if (!BlsExtensions.TryDecodeG1ToBuffer(inputData, rawPoints.AsMemory(), rawScalars.AsMemory(), pointDestinations[i], i)) { fail = true; - state.Break(); + break; } - - int destOffset = dest * 32; - rawScalar.CopyTo(rawScalars.AsSpan()[destOffset..]); - rawScalars.AsSpan()[destOffset..(destOffset + 32)].Reverse(); } - }); + } + else + { + Parallel.ForEach(pointDestinations, (dest, state, i) => + { + int index = (int)i; + if (!BlsExtensions.TryDecodeG1ToBuffer(inputData, rawPoints.AsMemory(), rawScalars.AsMemory(), dest, index)) + { + fail = true; + state.Break(); + } + }); + } +#pragma warning restore CS0162 // Unreachable code detected if (fail) { return IPrecompile.Failure; } - G1 res = new G1().MultiMult(rawPoints.AsSpan(), rawScalars.AsSpan(), npoints); + G1 res = new G1(stackalloc long[G1.Sz]).MultiMult(rawPoints.AsSpan(), rawScalars.AsSpan(), npoints); return (res.EncodeRaw(), true); } } diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G1MulPrecompile.cs b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G1MulPrecompile.cs index b0f520e15ae..18eb5889913 100644 --- a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G1MulPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G1MulPrecompile.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Specs; using G1 = Nethermind.Crypto.Bls.P1; @@ -25,8 +26,11 @@ private G1MulPrecompile() public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0L; + [SkipLocalsInit] public (ReadOnlyMemory, bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { + Metrics.BlsG1MulPrecompile++; + const int expectedInputLength = BlsConst.LenG1 + BlsConst.LenFr; if (inputData.Length != expectedInputLength) { @@ -34,7 +38,7 @@ private G1MulPrecompile() } G1 x = new(stackalloc long[G1.Sz]); - if (!x.TryDecodeRaw(inputData[..BlsConst.LenG1].Span) || !x.InGroup()) + if (!x.TryDecodeRaw(inputData[..BlsConst.LenG1].Span) || !(BlsConst.DisableSubgroupChecks || x.InGroup())) { return IPrecompile.Failure; } diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G2AddPrecompile.cs b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G2AddPrecompile.cs index 6b6779f91f3..710954f3a3b 100644 --- a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G2AddPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G2AddPrecompile.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Specs; @@ -26,8 +27,11 @@ private G2AddPrecompile() public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0L; + [SkipLocalsInit] public (ReadOnlyMemory, bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { + Metrics.BlsG2AddPrecompile++; + const int expectedInputLength = 2 * BlsConst.LenG2; if (inputData.Length != expectedInputLength) { diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G2MultiMulPrecompile.cs b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G2MSMPrecompile.cs similarity index 58% rename from src/Nethermind/Nethermind.Evm/Precompiles/Bls/G2MultiMulPrecompile.cs rename to src/Nethermind/Nethermind.Evm/Precompiles/Bls/G2MSMPrecompile.cs index ddd1eea799b..495e5c91a59 100644 --- a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G2MultiMulPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G2MSMPrecompile.cs @@ -8,17 +8,18 @@ using System.Threading.Tasks; using G2 = Nethermind.Crypto.Bls.P2; +using System.Runtime.CompilerServices; namespace Nethermind.Evm.Precompiles.Bls; /// /// https://eips.ethereum.org/EIPS/eip-2537 /// -public class G2MultiMulPrecompile : IPrecompile +public class G2MSMPrecompile : IPrecompile { - public static readonly G2MultiMulPrecompile Instance = new(); + public static readonly G2MSMPrecompile Instance = new(); - private G2MultiMulPrecompile() + private G2MSMPrecompile() { } @@ -32,10 +33,13 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec return 45000L * k * Discount.For(k) / 1000; } - private const int ItemSize = 288; + public const int ItemSize = 288; + [SkipLocalsInit] public (ReadOnlyMemory, bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { + Metrics.BlsG2MSMPrecompile++; + if (inputData.Length % ItemSize > 0 || inputData.Length == 0) { return IPrecompile.Failure; @@ -43,8 +47,8 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec int nItems = inputData.Length / ItemSize; - using ArrayPoolList rawPoints = new(nItems * 36, nItems * 36); - using ArrayPoolList rawScalars = new(nItems * 32, nItems * 32); + using ArrayPoolList pointBuffer = new(nItems * G2.Sz, nItems * G2.Sz); + using ArrayPoolList scalarBuffer = new(nItems * 32, nItems * 32); using ArrayPoolList pointDestinations = new(nItems); int npoints = 0; @@ -64,34 +68,39 @@ public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec } bool fail = false; - Parallel.ForEach(pointDestinations, (dest, state, i) => + +#pragma warning disable CS0162 // Unreachable code detected + if (BlsConst.DisableConcurrency) { - if (dest != -1) + for (int i = 0; i < pointDestinations.Count; i++) { - int offset = (int)i * ItemSize; - ReadOnlySpan rawPoint = inputData[offset..(offset + BlsConst.LenG2)].Span; - ReadOnlySpan rawScalar = inputData[(offset + BlsConst.LenG2)..(offset + ItemSize)].Span; - - G2 p = new(rawPoints.AsSpan()[(dest * 36)..]); - - if (!p.TryDecodeRaw(rawPoint) || !p.InGroup()) + if (!BlsExtensions.TryDecodeG2ToBuffer(inputData, pointBuffer.AsMemory(), scalarBuffer.AsMemory(), pointDestinations[i], i)) { fail = true; - state.Break(); + break; } - - int destOffset = dest * 32; - rawScalar.CopyTo(rawScalars.AsSpan()[destOffset..]); - rawScalars.AsSpan()[destOffset..(destOffset + 32)].Reverse(); } - }); + } + else + { + Parallel.ForEach(pointDestinations, (dest, state, i) => + { + int index = (int)i; + if (!BlsExtensions.TryDecodeG2ToBuffer(inputData, pointBuffer.AsMemory(), scalarBuffer.AsMemory(), dest, index)) + { + fail = true; + state.Break(); + } + }); + } +#pragma warning restore CS0162 // Unreachable code detected if (fail) { return IPrecompile.Failure; } - G2 res = new G2().MultiMult(rawPoints.AsSpan(), rawScalars.AsSpan(), npoints); + G2 res = new G2(stackalloc long[G2.Sz]).MultiMult(pointBuffer.AsSpan(), scalarBuffer.AsSpan(), npoints); return (res.EncodeRaw(), true); } diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G2MulPrecompile.cs b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G2MulPrecompile.cs index 151d20b4e3f..fbe99e32867 100644 --- a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G2MulPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/G2MulPrecompile.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Specs; @@ -26,8 +27,11 @@ private G2MulPrecompile() public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0L; + [SkipLocalsInit] public (ReadOnlyMemory, bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { + Metrics.BlsG2MulPrecompile++; + const int expectedInputLength = BlsConst.LenG2 + BlsConst.LenFr; if (inputData.Length != expectedInputLength) @@ -36,7 +40,7 @@ private G2MulPrecompile() } G2 x = new(stackalloc long[G2.Sz]); - if (!x.TryDecodeRaw(inputData[..BlsConst.LenG2].Span) || !x.InGroup()) + if (!x.TryDecodeRaw(inputData[..BlsConst.LenG2].Span) || !(BlsConst.DisableSubgroupChecks || x.InGroup())) { return IPrecompile.Failure; } diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/MapToG2Precompile.cs b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/MapFp2ToG2Precompile.cs similarity index 82% rename from src/Nethermind/Nethermind.Evm/Precompiles/Bls/MapToG2Precompile.cs rename to src/Nethermind/Nethermind.Evm/Precompiles/Bls/MapFp2ToG2Precompile.cs index 87a77d75bf0..5190c58be57 100644 --- a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/MapToG2Precompile.cs +++ b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/MapFp2ToG2Precompile.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Specs; @@ -12,11 +13,11 @@ namespace Nethermind.Evm.Precompiles.Bls; /// /// https://eips.ethereum.org/EIPS/eip-2537 /// -public class MapToG2Precompile : IPrecompile +public class MapFp2ToG2Precompile : IPrecompile { - public static readonly MapToG2Precompile Instance = new(); + public static readonly MapFp2ToG2Precompile Instance = new(); - private MapToG2Precompile() + private MapFp2ToG2Precompile() { } @@ -26,8 +27,11 @@ private MapToG2Precompile() public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0L; + [SkipLocalsInit] public (ReadOnlyMemory, bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { + Metrics.BlsMapFp2ToG2Precompile++; + const int expectedInputLength = 2 * BlsConst.LenFp; if (inputData.Length != expectedInputLength) { diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/MapToG1Precompile.cs b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/MapFpToG1Precompile.cs similarity index 79% rename from src/Nethermind/Nethermind.Evm/Precompiles/Bls/MapToG1Precompile.cs rename to src/Nethermind/Nethermind.Evm/Precompiles/Bls/MapFpToG1Precompile.cs index 63517445eab..eb1032ea1f5 100644 --- a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/MapToG1Precompile.cs +++ b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/MapFpToG1Precompile.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Specs; using G1 = Nethermind.Crypto.Bls.P1; @@ -11,11 +12,11 @@ namespace Nethermind.Evm.Precompiles.Bls; /// /// https://eips.ethereum.org/EIPS/eip-2537 /// -public class MapToG1Precompile : IPrecompile +public class MapFpToG1Precompile : IPrecompile { - public static readonly MapToG1Precompile Instance = new MapToG1Precompile(); + public static readonly MapFpToG1Precompile Instance = new MapFpToG1Precompile(); - private MapToG1Precompile() + private MapFpToG1Precompile() { } @@ -25,8 +26,11 @@ private MapToG1Precompile() public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 0L; + [SkipLocalsInit] public (ReadOnlyMemory, bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { + Metrics.BlsMapFpToG1Precompile++; + const int expectedInputLength = BlsConst.LenFp; if (inputData.Length != expectedInputLength) { diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/PairingPrecompile.cs b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/PairingCheckPrecompile.cs similarity index 72% rename from src/Nethermind/Nethermind.Evm/Precompiles/Bls/PairingPrecompile.cs rename to src/Nethermind/Nethermind.Evm/Precompiles/Bls/PairingCheckPrecompile.cs index febe5c7b7ab..6be90e9054a 100644 --- a/src/Nethermind/Nethermind.Evm/Precompiles/Bls/PairingPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm/Precompiles/Bls/PairingCheckPrecompile.cs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Runtime.CompilerServices; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Specs; using G1 = Nethermind.Crypto.Bls.P1; @@ -14,12 +16,12 @@ namespace Nethermind.Evm.Precompiles.Bls; /// /// https://eips.ethereum.org/EIPS/eip-2537 /// -public class PairingPrecompile : IPrecompile +public class PairingCheckPrecompile : IPrecompile { private const int PairSize = 384; - public static readonly PairingPrecompile Instance = new(); + public static readonly PairingCheckPrecompile Instance = new(); - private PairingPrecompile() { } + private PairingCheckPrecompile() { } public static Address Address { get; } = Address.FromNumber(0x11); @@ -27,8 +29,11 @@ private PairingPrecompile() { } public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => 43000L * (inputData.Length / PairSize); + [SkipLocalsInit] public (ReadOnlyMemory, bool) Run(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) { + Metrics.BlsPairingCheckPrecompile++; + if (inputData.Length % PairSize > 0 || inputData.Length == 0) { return IPrecompile.Failure; @@ -37,15 +42,18 @@ private PairingPrecompile() { } G1 x = new(stackalloc long[G1.Sz]); G2 y = new(stackalloc long[G2.Sz]); - var acc = GT.One(); + using ArrayPoolList buf = new(GT.Sz * 2, GT.Sz * 2); + var acc = GT.One(buf.AsSpan()); + GT p = new(buf.AsSpan()[GT.Sz..]); + for (int i = 0; i < inputData.Length / PairSize; i++) { int offset = i * PairSize; if (!x.TryDecodeRaw(inputData[offset..(offset + BlsConst.LenG1)].Span) || - !x.InGroup() || + !(BlsConst.DisableSubgroupChecks || x.InGroup()) || !y.TryDecodeRaw(inputData[(offset + BlsConst.LenG1)..(offset + PairSize)].Span) || - !y.InGroup()) + !(BlsConst.DisableSubgroupChecks || y.InGroup())) { return IPrecompile.Failure; } @@ -56,7 +64,8 @@ private PairingPrecompile() { } continue; } - acc.Mul(new GT(y, x)); + p.MillerLoop(y, x); + acc.Mul(p); } bool verified = acc.FinalExp().IsOne(); diff --git a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs index cbb08962d9a..e2b6aba3dd2 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs @@ -104,7 +104,7 @@ public override void ReportLog(LogEntry log) NativeCallTracerCallFrame callFrame = _callStack[^1]; NativeCallTracerLogEntry callLog = new( - log.LoggersAddress, + log.Address, log.Data, log.Topics, (ulong)callFrame.Calls.Count); diff --git a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/Custom/Native/Call/NativeCallTracerCallFrameConverter.cs b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/Custom/Native/Call/NativeCallTracerCallFrameConverter.cs index 37f181cee8e..bc89734d8c5 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/Custom/Native/Call/NativeCallTracerCallFrameConverter.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/Custom/Native/Call/NativeCallTracerCallFrameConverter.cs @@ -49,13 +49,13 @@ public override void Write(Utf8JsonWriter writer, NativeCallTracerCallFrame valu } else { - JsonSerializer.Serialize(writer, value.Input.AsMemory(), options); + JsonSerializer.Serialize(writer, value.Input.AsReadOnlyMemory(), options); } if (value.Output?.Count > 0) { writer.WritePropertyName("output"u8); - JsonSerializer.Serialize(writer, value.Output.AsMemory(), options); + JsonSerializer.Serialize(writer, value.Output.AsReadOnlyMemory(), options); } if (value.Error is not null) diff --git a/src/Nethermind/Nethermind.Evm/Tracing/TxTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/TxTracer.cs index d88b3928f39..e32e4993d9c 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/TxTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/TxTracer.cs @@ -10,7 +10,7 @@ namespace Nethermind.Evm.Tracing; -public class TxTracer : ITxTracer +public abstract class TxTracer : ITxTracer { [SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")] protected TxTracer() diff --git a/src/Nethermind/Nethermind.Evm/TransactionExtensions.cs b/src/Nethermind/Nethermind.Evm/TransactionExtensions.cs index 831fdd67d64..0bb04c021fd 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionExtensions.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionExtensions.cs @@ -25,13 +25,13 @@ public static TxGasInfo GetGasInfo(this Transaction tx, bool is1559Enabled, Bloc throw new ArgumentException($"Block that contains Shard Blob Transactions should have {nameof(header.ExcessBlobGas)} set.", nameof(header.ExcessBlobGas)); } - if (!BlobGasCalculator.TryCalculateBlobGasPricePerUnit(header, out UInt256 blobGasPrice)) + if (!BlobGasCalculator.TryCalculateFeePerBlobGas(header, out UInt256 feePerBlobGas)) { throw new OverflowException("Blob gas price calculation led to overflow."); } ulong blobGas = BlobGasCalculator.CalculateBlobGas(tx); - return new(effectiveGasPrice, blobGasPrice, blobGas); + return new(effectiveGasPrice, feePerBlobGas, blobGas); } return new(effectiveGasPrice, null, null); @@ -42,10 +42,10 @@ public struct TxGasInfo { public TxGasInfo() { } - public TxGasInfo(UInt256? effectiveGasPrice, UInt256? blobGasPrice, ulong? blobGasUsed) + public TxGasInfo(UInt256? effectiveGasPrice, UInt256? feePerBlobGas, ulong? blobGasUsed) { EffectiveGasPrice = effectiveGasPrice; - BlobGasPrice = blobGasPrice; + BlobGasPrice = feePerBlobGas; BlobGasUsed = blobGasUsed; } diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/SystemTransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/SystemTransactionProcessor.cs index 1110dd289a1..e84a21f60c3 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/SystemTransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/SystemTransactionProcessor.cs @@ -52,7 +52,7 @@ protected override TransactionResult Execute(Transaction tx, in BlockExecutionCo protected override void DecrementNonce(Transaction tx) { } - protected override void PayFees(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, in TransactionSubstate substate, in long spentGas, in UInt256 premiumPerGas, in byte statusCode) { } + protected override void PayFees(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, in TransactionSubstate substate, in long spentGas, in UInt256 premiumPerGas, in UInt256 blobBaseFee, in byte statusCode) { } protected override void PayValue(Transaction tx, IReleaseSpec spec, ExecutionOptions opts) { diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs index 71d47c7a2b5..3ebf373ab25 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -17,7 +18,6 @@ using Nethermind.Logging; using Nethermind.State; using Nethermind.State.Tracing; -using static Nethermind.Core.Extensions.MemoryExtensions; using static Nethermind.Evm.VirtualMachine; @@ -41,6 +41,7 @@ public abstract class TransactionProcessorBase : ITransactionProcessor private readonly ICodeInfoRepository _codeInfoRepository; private SystemTransactionProcessor? _systemTransactionProcessor; private readonly ILogManager _logManager; + private readonly HashSet
_accessedAddresses = []; [Flags] protected enum ExecutionOptions @@ -144,16 +145,19 @@ protected virtual TransactionResult Execute(Transaction tx, in BlockExecutionCon bool deleteCallerAccount = RecoverSenderIfNeeded(tx, spec, opts, effectiveGasPrice); if (!(result = ValidateSender(tx, header, spec, tracer, opts))) return result; - if (!(result = BuyGas(tx, header, spec, tracer, opts, effectiveGasPrice, out UInt256 premiumPerGas, out UInt256 senderReservedGasPayment))) return result; + if (!(result = BuyGas(tx, header, spec, tracer, opts, effectiveGasPrice, out UInt256 premiumPerGas, out UInt256 senderReservedGasPayment, out UInt256 blobBaseFee))) return result; if (!(result = IncrementNonce(tx, header, spec, tracer, opts))) return result; if (commit) WorldState.Commit(spec, tracer.IsTracingState ? tracer : NullTxTracer.Instance, commitStorageRoots: false); - ExecutionEnvironment env = BuildExecutionEnvironment(tx, in blCtx, spec, effectiveGasPrice); + _accessedAddresses.Clear(); + int delegationRefunds = ProcessDelegations(tx, spec, _accessedAddresses); + + ExecutionEnvironment env = BuildExecutionEnvironment(tx, in blCtx, spec, effectiveGasPrice, _codeInfoRepository, _accessedAddresses); long gasAvailable = tx.GasLimit - intrinsicGas; - ExecuteEvmCall(tx, header, spec, tracer, opts, gasAvailable, env, out TransactionSubstate? substate, out long spentGas, out byte statusCode); - PayFees(tx, header, spec, tracer, substate, spentGas, premiumPerGas, statusCode); + ExecuteEvmCall(tx, header, spec, tracer, opts, delegationRefunds, _accessedAddresses, gasAvailable, env, out TransactionSubstate? substate, out long spentGas, out byte statusCode); + PayFees(tx, header, spec, tracer, substate, spentGas, premiumPerGas, blobBaseFee, statusCode); // Finalize if (restore) @@ -168,6 +172,7 @@ protected virtual TransactionResult Execute(Transaction tx, in BlockExecutionCon if (!opts.HasFlag(ExecutionOptions.NoValidation)) WorldState.AddToBalance(tx.SenderAddress!, senderReservedGasPayment, spec); DecrementNonce(tx); + WorldState.Commit(spec); } } @@ -200,6 +205,74 @@ protected virtual TransactionResult Execute(Transaction tx, in BlockExecutionCon return TransactionResult.Ok; } + private int ProcessDelegations(Transaction tx, IReleaseSpec spec, HashSet
accessedAddresses) + { + int refunds = 0; + if (spec.IsEip7702Enabled && tx.HasAuthorizationList) + { + foreach (AuthorizationTuple authTuple in tx.AuthorizationList) + { + authTuple.Authority ??= Ecdsa.RecoverAddress(authTuple); + + if (!IsValidForExecution(authTuple, accessedAddresses, out _)) + { + if (Logger.IsDebug) Logger.Debug($"Delegation {authTuple} is invalid"); + } + else + { + if (!WorldState.AccountExists(authTuple.Authority!)) + { + WorldState.CreateAccount(authTuple.Authority, 0, 1); + } + else + { + refunds++; + WorldState.IncrementNonce(authTuple.Authority); + } + + _codeInfoRepository.SetDelegation(WorldState, authTuple.CodeAddress, authTuple.Authority, spec); + } + } + + } + + return refunds; + + bool IsValidForExecution( + AuthorizationTuple authorizationTuple, + ISet
accessedAddresses, + [NotNullWhen(false)] out string? error) + { + if (authorizationTuple.Authority is null) + { + error = "Bad signature."; + return false; + } + if (authorizationTuple.ChainId != 0 && SpecProvider.ChainId != authorizationTuple.ChainId) + { + error = $"Chain id ({authorizationTuple.ChainId}) does not match."; + return false; + } + + accessedAddresses.Add(authorizationTuple.Authority); + + if (WorldState.HasCode(authorizationTuple.Authority) && !_codeInfoRepository.TryGetDelegation(WorldState, authorizationTuple.Authority, out _)) + { + error = $"Authority ({authorizationTuple.Authority}) has code deployed."; + return false; + } + UInt256 authNonce = WorldState.GetNonce(authorizationTuple.Authority); + if (authNonce != authorizationTuple.Nonce) + { + error = $"Skipping tuple in authorization_list because nonce is set to {authorizationTuple.Nonce}, but authority ({authorizationTuple.Authority}) has {authNonce}."; + return false; + } + + error = null; + return true; + } + } + protected virtual IReleaseSpec GetSpec(Transaction tx, BlockHeader header) => SpecProvider.GetSpec(header); private static void UpdateMetrics(ExecutionOptions opts, UInt256 effectiveGasPrice) @@ -346,10 +419,11 @@ protected virtual TransactionResult ValidateSender(Transaction tx, BlockHeader h } protected virtual TransactionResult BuyGas(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts, - in UInt256 effectiveGasPrice, out UInt256 premiumPerGas, out UInt256 senderReservedGasPayment) + in UInt256 effectiveGasPrice, out UInt256 premiumPerGas, out UInt256 senderReservedGasPayment, out UInt256 blobBaseFee) { premiumPerGas = UInt256.Zero; senderReservedGasPayment = UInt256.Zero; + blobBaseFee = UInt256.Zero; bool validate = !opts.HasFlag(ExecutionOptions.NoValidation); if (validate) @@ -376,6 +450,7 @@ protected virtual TransactionResult BuyGas(Transaction tx, BlockHeader header, I TraceLogInvalidTx(tx, $"INSUFFICIENT_MAX_FEE_PER_GAS_FOR_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}, MAX_FEE_PER_GAS: {tx.MaxFeePerGas}"); return "insufficient MaxFeePerGas for sender balance"; } + if (tx.SupportsBlobs) { overflows = UInt256.MultiplyOverflow(BlobGasCalculator.CalculateBlobGas(tx), (UInt256)tx.MaxFeePerBlobGas!, out UInt256 maxBlobGasFee); @@ -390,10 +465,10 @@ protected virtual TransactionResult BuyGas(Transaction tx, BlockHeader header, I overflows = UInt256.MultiplyOverflow((UInt256)tx.GasLimit, effectiveGasPrice, out senderReservedGasPayment); if (!overflows && tx.SupportsBlobs) { - overflows = !BlobGasCalculator.TryCalculateBlobGasPrice(header, tx, out UInt256 blobGasFee); + overflows = !BlobGasCalculator.TryCalculateBlobBaseFee(header, tx, out blobBaseFee); if (!overflows) { - overflows = UInt256.AddOverflow(senderReservedGasPayment, blobGasFee, out senderReservedGasPayment); + overflows = UInt256.AddOverflow(senderReservedGasPayment, blobBaseFee, out senderReservedGasPayment); } } @@ -430,16 +505,24 @@ private ExecutionEnvironment BuildExecutionEnvironment( Transaction tx, in BlockExecutionContext blCtx, IReleaseSpec spec, - in UInt256 effectiveGasPrice) + in UInt256 effectiveGasPrice, + ICodeInfoRepository codeInfoRepository, + HashSet
accessedAddresses) { Address recipient = tx.GetRecipient(tx.IsContractCreation ? WorldState.GetNonce(tx.SenderAddress!) : 0); if (recipient is null) ThrowInvalidDataException("Recipient has not been resolved properly before tx execution"); - TxExecutionContext executionContext = new(in blCtx, tx.SenderAddress!, effectiveGasPrice, tx.BlobVersionedHashes!); + accessedAddresses.Add(recipient); + accessedAddresses.Add(tx.SenderAddress!); + TxExecutionContext executionContext = new(in blCtx, tx.SenderAddress, effectiveGasPrice, tx.BlobVersionedHashes, codeInfoRepository); + Address? delegationAddress = null; CodeInfo codeInfo = tx.IsContractCreation ? new(tx.Data ?? Memory.Empty) - : _codeInfoRepository.GetCachedCodeInfo(WorldState, recipient, spec); + : codeInfoRepository.GetCachedCodeInfo(WorldState, recipient, spec, out delegationAddress); + + if (delegationAddress is not null) + accessedAddresses.Add(delegationAddress); codeInfo.AnalyseInBackgroundIfRequired(); @@ -466,6 +549,8 @@ protected virtual void ExecuteEvmCall( IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts, + int delegationRefunds, + IEnumerable
accessedAddresses, in long gasAvailable, in ExecutionEnvironment env, out TransactionSubstate? substate, @@ -489,28 +574,17 @@ protected virtual void ExecuteEvmCall( if (tx.IsContractCreation) { // if transaction is a contract creation then recipient address is the contract deployment address - PrepareAccountForContractDeployment(env.ExecutingAccount, spec); + if (!PrepareAccountForContractDeployment(env.ExecutingAccount, _codeInfoRepository, spec)) + { + goto Fail; + } } ExecutionType executionType = tx.IsContractCreation ? ExecutionType.CREATE : ExecutionType.TRANSACTION; using (EvmState state = new(unspentGas, env, executionType, true, snapshot, false)) { - if (spec.UseTxAccessLists) - { - state.WarmUp(tx.AccessList); // eip-2930 - } - - if (spec.UseHotAndColdStorage) - { - state.WarmUp(tx.SenderAddress!); // eip-2929 - state.WarmUp(env.ExecutingAccount); // eip-2929 - } - - if (spec.AddCoinbaseToTxAccessList) - { - state.WarmUp(header.GasBeneficiary!); - } + WarmUp(tx, header, spec, state, accessedAddresses); substate = !tracer.IsTracingActions ? VirtualMachine.Run(state, WorldState, tracer) @@ -570,7 +644,7 @@ protected virtual void ExecuteEvmCall( statusCode = StatusCode.Success; } - spentGas = Refund(tx, header, spec, opts, substate, unspentGas, env.TxExecutionContext.GasPrice); + spentGas = Refund(tx, header, spec, opts, substate, unspentGas, env.TxExecutionContext.GasPrice, delegationRefunds); goto Complete; } catch (Exception ex) when (ex is EvmException or OverflowException) // TODO: OverflowException? still needed? hope not @@ -591,32 +665,61 @@ protected virtual void PayValue(Transaction tx, IReleaseSpec spec, ExecutionOpti WorldState.SubtractFromBalance(tx.SenderAddress!, tx.Value, spec); } - protected virtual void PayFees(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, in TransactionSubstate substate, in long spentGas, in UInt256 premiumPerGas, in byte statusCode) + private void WarmUp(Transaction tx, BlockHeader header, IReleaseSpec spec, EvmState state, IEnumerable
accessedAddresses) + { + if (spec.UseTxAccessLists) + { + state.WarmUp(tx.AccessList); // eip-2930 + } + + if (spec.UseHotAndColdStorage) // eip-2929 + { + foreach (Address accessed in accessedAddresses) + { + state.WarmUp(accessed); + } + } + + if (spec.AddCoinbaseToTxAccessList) + { + state.WarmUp(header.GasBeneficiary!); + } + } + + protected virtual void PayFees(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, in TransactionSubstate substate, in long spentGas, in UInt256 premiumPerGas, in UInt256 blobBaseFee, in byte statusCode) { bool gasBeneficiaryNotDestroyed = substate?.DestroyList.Contains(header.GasBeneficiary) != true; if (statusCode == StatusCode.Failure || gasBeneficiaryNotDestroyed) { UInt256 fees = (UInt256)spentGas * premiumPerGas; - UInt256 burntFees = !tx.IsFree() ? (UInt256)spentGas * header.BaseFeePerGas : 0; + UInt256 eip1559Fees = !tx.IsFree() ? (UInt256)spentGas * header.BaseFeePerGas : UInt256.Zero; + UInt256 collectedFees = spec.IsEip1559Enabled ? eip1559Fees : UInt256.Zero; + + if (tx.SupportsBlobs && spec.IsEip4844FeeCollectorEnabled) + { + collectedFees += blobBaseFee; + } WorldState.AddToBalanceAndCreateIfNotExists(header.GasBeneficiary!, fees, spec); - if (spec.IsEip1559Enabled && spec.Eip1559FeeCollector is not null && !burntFees.IsZero) - WorldState.AddToBalanceAndCreateIfNotExists(spec.Eip1559FeeCollector, burntFees, spec); + if (spec.FeeCollector is not null && !collectedFees.IsZero) + WorldState.AddToBalanceAndCreateIfNotExists(spec.FeeCollector, collectedFees, spec); if (tracer.IsTracingFees) - tracer.ReportFees(fees, burntFees); + tracer.ReportFees(fees, eip1559Fees + blobBaseFee); } } - protected void PrepareAccountForContractDeployment(Address contractAddress, IReleaseSpec spec) + protected bool PrepareAccountForContractDeployment(Address contractAddress, ICodeInfoRepository codeInfoRepository, IReleaseSpec spec) { - if (WorldState.AccountExists(contractAddress) && contractAddress.IsNonZeroAccount(spec, _codeInfoRepository, WorldState)) + if (WorldState.AccountExists(contractAddress) && contractAddress.IsNonZeroAccount(spec, codeInfoRepository, WorldState)) { if (Logger.IsTrace) Logger.Trace($"Contract collision at {contractAddress}"); - ThrowTransactionCollisionException(); + return false; } + + return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -626,22 +729,36 @@ protected void TraceLogInvalidTx(Transaction transaction, string reason) } protected virtual long Refund(Transaction tx, BlockHeader header, IReleaseSpec spec, ExecutionOptions opts, - in TransactionSubstate substate, in long unspentGas, in UInt256 gasPrice) + in TransactionSubstate substate, in long unspentGas, in UInt256 gasPrice, int codeInsertRefunds) { long spentGas = tx.GasLimit; + var codeInsertRefund = (GasCostOf.NewAccount - GasCostOf.PerAuthBaseCost) * codeInsertRefunds; + if (!substate.IsError) { spentGas -= unspentGas; - long refund = substate.ShouldRevert - ? 0 - : RefundHelper.CalculateClaimableRefund(spentGas, - substate.Refund + substate.DestroyList.Count * RefundOf.Destroy(spec.IsEip3529Enabled), spec); + + long totalToRefund = codeInsertRefund; + if (!substate.ShouldRevert) + totalToRefund += substate.Refund + substate.DestroyList.Count * RefundOf.Destroy(spec.IsEip3529Enabled); + long actualRefund = RefundHelper.CalculateClaimableRefund(spentGas, totalToRefund, spec); if (Logger.IsTrace) - Logger.Trace("Refunding unused gas of " + unspentGas + " and refund of " + refund); + Logger.Trace("Refunding unused gas of " + unspentGas + " and refund of " + actualRefund); // If noValidation we didn't charge for gas, so do not refund if (!opts.HasFlag(ExecutionOptions.NoValidation)) - WorldState.AddToBalance(tx.SenderAddress!, (ulong)(unspentGas + refund) * gasPrice, spec); + WorldState.AddToBalance(tx.SenderAddress!, (ulong)(unspentGas + actualRefund) * gasPrice, spec); + spentGas -= actualRefund; + } + else if (codeInsertRefund > 0) + { + long refund = RefundHelper.CalculateClaimableRefund(spentGas, codeInsertRefund, spec); + + if (Logger.IsTrace) + Logger.Trace("Refunding delegations only: " + refund); + // If noValidation we didn't charge for gas, so do not refund + if (!opts.HasFlag(ExecutionOptions.NoValidation)) + WorldState.AddToBalance(tx.SenderAddress!, (ulong)refund * gasPrice, spec); spentGas -= refund; } @@ -651,10 +768,6 @@ protected virtual long Refund(Transaction tx, BlockHeader header, IReleaseSpec s [DoesNotReturn] [StackTraceHidden] private static void ThrowInvalidDataException(string message) => throw new InvalidDataException(message); - - [DoesNotReturn] - [StackTraceHidden] - private static void ThrowTransactionCollisionException() => throw new TransactionCollisionException(); } public readonly struct TransactionResult(string? error) diff --git a/src/Nethermind/Nethermind.Evm/TxExecutionContext.cs b/src/Nethermind/Nethermind.Evm/TxExecutionContext.cs index 637c6fc0258..e1aa68c2b4e 100644 --- a/src/Nethermind/Nethermind.Evm/TxExecutionContext.cs +++ b/src/Nethermind/Nethermind.Evm/TxExecutionContext.cs @@ -6,19 +6,17 @@ namespace Nethermind.Evm { - public readonly struct TxExecutionContext + public readonly struct TxExecutionContext( + in BlockExecutionContext blockExecutionContext, + Address origin, + in UInt256 gasPrice, + byte[][] blobVersionedHashes, + ICodeInfoRepository codeInfoRepository) { - public readonly BlockExecutionContext BlockExecutionContext; - public Address Origin { get; } - public UInt256 GasPrice { get; } - public byte[][]? BlobVersionedHashes { get; } - - public TxExecutionContext(in BlockExecutionContext blockExecutionContext, Address origin, in UInt256 gasPrice, byte[][] blobVersionedHashes) - { - BlockExecutionContext = blockExecutionContext; - Origin = origin; - GasPrice = gasPrice; - BlobVersionedHashes = blobVersionedHashes; - } + public readonly BlockExecutionContext BlockExecutionContext = blockExecutionContext; + public Address Origin { get; } = origin; + public UInt256 GasPrice { get; } = gasPrice; + public byte[][]? BlobVersionedHashes { get; } = blobVersionedHashes; + public ICodeInfoRepository CodeInfoRepository { get; } = codeInfoRepository; } } diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index 5aa29a4570f..cce85d56927 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -28,10 +27,10 @@ [assembly: InternalsVisibleTo("Nethermind.Evm.Test")] namespace Nethermind.Evm; - using Int256; using Nethermind.Evm.Config; + public class VirtualMachine : IVirtualMachine { public const int MaxCallDepth = 1024; @@ -58,6 +57,9 @@ public class VirtualMachine : IVirtualMachine 255, 255, 255, 255, 255, 255, 255, 255 }; + internal static readonly PrecompileExecutionFailureException PrecompileExecutionFailureException = new(); + internal static readonly OutOfGasException PrecompileOutOfGasException = new(); + private readonly IVirtualMachine _evm; private IVMConfig _config; @@ -71,8 +73,8 @@ public VirtualMachine( ILogger logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); _config = vmConfig ?? new VMConfig(); _evm = logger.IsTrace - ? new VirtualMachine(blockhashProvider, specProvider, codeInfoRepository, _config, logger) - : new VirtualMachine(blockhashProvider, specProvider, codeInfoRepository, _config, logger); + ? new VirtualMachine(blockhashProvider, specProvider, _config, logger) + : new VirtualMachine(blockhashProvider, specProvider, _config, logger); } public TransactionSubstate Run(EvmState state, IWorldState worldState, ITxTracer txTracer) @@ -142,25 +144,22 @@ internal sealed class VirtualMachine : IVirtualMachine where TLogger : private readonly IBlockhashProvider _blockhashProvider; private readonly ISpecProvider _specProvider; private readonly ILogger _logger; - private IWorldState _state; + private IWorldState _state = null!; private readonly Stack _stateStack = new(); private (Address Address, bool ShouldDelete) _parityTouchBugAccount = (Address.FromNumber(3), false); private ReadOnlyMemory _returnDataBuffer = Array.Empty(); private ITxTracer _txTracer = NullTxTracer.Instance; - private readonly ICodeInfoRepository _codeInfoRepository; private readonly IVMConfig _vmConfig; public VirtualMachine( IBlockhashProvider? blockhashProvider, ISpecProvider? specProvider, - ICodeInfoRepository codeInfoRepository, - IVMConfig VMC, + IVMConfig vmConfig, ILogger? logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _blockhashProvider = blockhashProvider ?? throw new ArgumentNullException(nameof(blockhashProvider)); _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); - _codeInfoRepository = codeInfoRepository ?? throw new ArgumentNullException(nameof(codeInfoRepository)); _chainId = ((UInt256)specProvider.ChainId).ToBigEndian(); _vmConfig = new VMConfig { @@ -177,7 +176,9 @@ public TransactionSubstate Run(EvmState state, IWorldState worl _txTracer = txTracer; _state = worldState; - IReleaseSpec spec = _specProvider.GetSpec(state.Env.TxExecutionContext.BlockExecutionContext.Header.Number, state.Env.TxExecutionContext.BlockExecutionContext.Header.Timestamp); + ref readonly TxExecutionContext txExecutionContext = ref state.Env.TxExecutionContext; + ICodeInfoRepository codeInfoRepository = txExecutionContext.CodeInfoRepository; + IReleaseSpec spec = _specProvider.GetSpec(txExecutionContext.BlockExecutionContext.Header.Number, txExecutionContext.BlockExecutionContext.Header.Timestamp); EvmState currentState = state; ReadOnlyMemory? previousCallResult = null; ZeroPaddedSpan previousCallOutput = ZeroPaddedSpan.Empty; @@ -186,6 +187,7 @@ public TransactionSubstate Run(EvmState state, IWorldState worl while (true) { + Exception? failure = null; if (!currentState.IsContinuation) { _returnDataBuffer = Array.Empty(); @@ -205,11 +207,16 @@ public TransactionSubstate Run(EvmState state, IWorldState worl if (!callResult.PrecompileSuccess.Value) { + if (callResult.IsException) + { + failure = VirtualMachine.PrecompileOutOfGasException; + goto Failure; + } if (currentState.IsPrecompile && currentState.IsTopLevel) { - Metrics.EvmExceptions++; + failure = VirtualMachine.PrecompileExecutionFailureException; // TODO: when direct / calls are treated same we should not need such differentiation - throw new PrecompileExecutionFailureException(); + goto Failure; } // TODO: testing it as it seems the way to pass zkSNARKs tests @@ -330,102 +337,112 @@ public TransactionSubstate Run(EvmState state, IWorldState worl } Address callCodeOwner = currentState.Env.ExecutingAccount; - using EvmState previousState = currentState; - currentState = _stateStack.Pop(); - currentState.IsContinuation = true; - currentState.GasAvailable += previousState.GasAvailable; - bool previousStateSucceeded = true; - - if (!callResult.ShouldRevert) + using (EvmState previousState = currentState) { - long gasAvailableForCodeDeposit = previousState.GasAvailable; // TODO: refactor, this is to fix 61363 Ropsten - if (previousState.ExecutionType.IsAnyCreate()) - { - previousCallResult = callCodeOwner.Bytes; - previousCallOutputDestination = UInt256.Zero; - _returnDataBuffer = Array.Empty(); - previousCallOutput = ZeroPaddedSpan.Empty; + currentState = _stateStack.Pop(); + currentState.IsContinuation = true; + currentState.GasAvailable += previousState.GasAvailable; + bool previousStateSucceeded = true; - long codeDepositGasCost = CodeDepositHandler.CalculateCost(callResult.Output.Length, spec); - bool invalidCode = CodeDepositHandler.CodeIsInvalid(spec, callResult.Output); - if (gasAvailableForCodeDeposit >= codeDepositGasCost && !invalidCode) + if (!callResult.ShouldRevert) + { + long gasAvailableForCodeDeposit = previousState.GasAvailable; // TODO: refactor, this is to fix 61363 Ropsten + if (previousState.ExecutionType.IsAnyCreate()) { - ReadOnlyMemory code = callResult.Output; - _codeInfoRepository.InsertCode(_state, code, callCodeOwner, spec); + previousCallResult = callCodeOwner.Bytes; + previousCallOutputDestination = UInt256.Zero; + _returnDataBuffer = Array.Empty(); + previousCallOutput = ZeroPaddedSpan.Empty; - currentState.GasAvailable -= codeDepositGasCost; + long codeDepositGasCost = CodeDepositHandler.CalculateCost(callResult.Output.Length, spec); + bool invalidCode = CodeDepositHandler.CodeIsInvalid(spec, callResult.Output); + if (gasAvailableForCodeDeposit >= codeDepositGasCost && !invalidCode) + { + ReadOnlyMemory code = callResult.Output; + codeInfoRepository.InsertCode(_state, code, callCodeOwner, spec); - if (typeof(TTracingActions) == typeof(IsTracing)) + currentState.GasAvailable -= codeDepositGasCost; + + if (typeof(TTracingActions) == typeof(IsTracing)) + { + _txTracer.ReportActionEnd(previousState.GasAvailable - codeDepositGasCost, callCodeOwner, callResult.Output); + } + } + else if (spec.FailOnOutOfGasCodeDeposit || invalidCode) { - _txTracer.ReportActionEnd(previousState.GasAvailable - codeDepositGasCost, callCodeOwner, callResult.Output); + currentState.GasAvailable -= gasAvailableForCodeDeposit; + worldState.Restore(previousState.Snapshot); + if (!previousState.IsCreateOnPreExistingAccount) + { + _state.DeleteAccount(callCodeOwner); + } + + previousCallResult = BytesZero; + previousStateSucceeded = false; + + if (typeof(TTracingActions) == typeof(IsTracing)) + { + _txTracer.ReportActionError(invalidCode ? EvmExceptionType.InvalidCode : EvmExceptionType.OutOfGas); + } + } + else if (typeof(TTracingActions) == typeof(IsTracing)) + { + _txTracer.ReportActionEnd(0L, callCodeOwner, callResult.Output); } } - else if (spec.FailOnOutOfGasCodeDeposit || invalidCode) + else { - currentState.GasAvailable -= gasAvailableForCodeDeposit; - worldState.Restore(previousState.Snapshot); - if (!previousState.IsCreateOnPreExistingAccount) + _returnDataBuffer = callResult.Output; + previousCallResult = callResult.PrecompileSuccess.HasValue ? (callResult.PrecompileSuccess.Value ? StatusCode.SuccessBytes : StatusCode.FailureBytes) : StatusCode.SuccessBytes; + previousCallOutput = callResult.Output.Span.SliceWithZeroPadding(0, Math.Min(callResult.Output.Length, (int)previousState.OutputLength)); + previousCallOutputDestination = (ulong)previousState.OutputDestination; + if (previousState.IsPrecompile) { - _state.DeleteAccount(callCodeOwner); + // parity induced if else for vmtrace + if (_txTracer.IsTracingInstructions) + { + _txTracer.ReportMemoryChange(previousCallOutputDestination, previousCallOutput); + } } - previousCallResult = BytesZero; - previousStateSucceeded = false; - if (typeof(TTracingActions) == typeof(IsTracing)) { - _txTracer.ReportActionError(invalidCode ? EvmExceptionType.InvalidCode : EvmExceptionType.OutOfGas); + _txTracer.ReportActionEnd(previousState.GasAvailable, _returnDataBuffer); } } - else if (typeof(TTracingActions) == typeof(IsTracing)) + + if (previousStateSucceeded) { - _txTracer.ReportActionEnd(0L, callCodeOwner, callResult.Output); + previousState.CommitToParent(currentState); } } else { + worldState.Restore(previousState.Snapshot); _returnDataBuffer = callResult.Output; - previousCallResult = callResult.PrecompileSuccess.HasValue ? (callResult.PrecompileSuccess.Value ? StatusCode.SuccessBytes : StatusCode.FailureBytes) : StatusCode.SuccessBytes; + previousCallResult = StatusCode.FailureBytes; previousCallOutput = callResult.Output.Span.SliceWithZeroPadding(0, Math.Min(callResult.Output.Length, (int)previousState.OutputLength)); previousCallOutputDestination = (ulong)previousState.OutputDestination; - if (previousState.IsPrecompile) - { - // parity induced if else for vmtrace - if (_txTracer.IsTracingInstructions) - { - _txTracer.ReportMemoryChange(previousCallOutputDestination, previousCallOutput); - } - } + if (typeof(TTracingActions) == typeof(IsTracing)) { - _txTracer.ReportActionEnd(previousState.GasAvailable, _returnDataBuffer); + _txTracer.ReportActionRevert(previousState.GasAvailable, callResult.Output); } } - - if (previousStateSucceeded) - { - previousState.CommitToParent(currentState); - } - } - else - { - worldState.Restore(previousState.Snapshot); - _returnDataBuffer = callResult.Output; - previousCallResult = StatusCode.FailureBytes; - previousCallOutput = callResult.Output.Span.SliceWithZeroPadding(0, Math.Min(callResult.Output.Length, (int)previousState.OutputLength)); - previousCallOutputDestination = (ulong)previousState.OutputDestination; - - - if (typeof(TTracingActions) == typeof(IsTracing)) - { - _txTracer.ReportActionRevert(previousState.GasAvailable, callResult.Output); - } } } catch (Exception ex) when (ex is EvmException or OverflowException) { - if (typeof(TLogger) == typeof(IsTracing)) _logger.Trace($"exception ({ex.GetType().Name}) in {currentState.ExecutionType} at depth {currentState.Env.CallDepth} - restoring snapshot"); + failure = ex; + goto Failure; + } + + continue; + + Failure: + { + if (typeof(TLogger) == typeof(IsTracing)) _logger.Trace($"exception ({failure.GetType().Name}) in {currentState.ExecutionType} at depth {currentState.Env.CallDepth} - restoring snapshot"); _state.Restore(currentState.Snapshot); @@ -434,18 +451,18 @@ public TransactionSubstate Run(EvmState state, IWorldState worl if (txTracer.IsTracingInstructions) { txTracer.ReportOperationRemainingGas(0); - txTracer.ReportOperationError(ex is EvmException evmException ? evmException.ExceptionType : EvmExceptionType.Other); + txTracer.ReportOperationError(failure is EvmException evmException ? evmException.ExceptionType : EvmExceptionType.Other); } if (typeof(TTracingActions) == typeof(IsTracing)) { - EvmException evmException = ex as EvmException; + EvmException evmException = failure as EvmException; _txTracer.ReportActionError(evmException?.ExceptionType ?? EvmExceptionType.Other); } if (currentState.IsTopLevel) { - return new TransactionSubstate(ex is OverflowException ? EvmExceptionType.Other : (ex as EvmException).ExceptionType, isTracing); + return new TransactionSubstate(failure is OverflowException ? EvmExceptionType.Other : (failure as EvmException).ExceptionType, isTracing); } previousCallResult = StatusCode.FailureBytes; @@ -489,13 +506,22 @@ public static void UpdateGasUp(long refund, ref long gasAvailable) gasAvailable += refund; } - public static bool ChargeAccountAccessGas(ref long gasAvailable, EvmState vmState, Address address, IReleaseSpec spec, ITxTracer tracer, bool chargeForWarm = true) + public static bool ChargeAccountAccessGas(ref long gasAvailable, EvmState vmState, Address address, bool chargeForDelegation, IWorldState state, IReleaseSpec spec, ITxTracer tracer, bool chargeForWarm = true) { - // Console.WriteLine($"Accessing {address}"); + if (!spec.UseHotAndColdStorage) + { + return true; + } + bool notOutOfGas = ChargeAccountGas(ref gasAvailable, vmState, address, spec); + return notOutOfGas + && chargeForDelegation + && vmState.Env.TxExecutionContext.CodeInfoRepository.TryGetDelegation(state, address, out Address delegated) + ? ChargeAccountGas(ref gasAvailable, vmState, delegated, spec) + : notOutOfGas; - bool result = true; - if (spec.UseHotAndColdStorage) + bool ChargeAccountGas(ref long gasAvailable, EvmState vmState, Address address, IReleaseSpec spec) { + bool result = true; if (tracer.IsTracingAccess) // when tracing access we want cost as if it was warmed up from access list { vmState.WarmUp(address); @@ -510,9 +536,8 @@ public static bool ChargeAccountAccessGas(ref long gasAvailable, EvmState vmStat { result = UpdateGas(GasCostOf.WarmStateRead, ref gasAvailable); } + return result; } - - return result; } internal enum StorageAccessType @@ -593,8 +618,7 @@ private CallResult ExecutePrecompile(EvmState state, IReleaseSpec spec) if (!UpdateGas(checked(baseGasCost + blobGasCost), ref gasAvailable)) { - Metrics.EvmExceptions++; - throw new OutOfGasException(); + return new(default, false, true, EvmExceptionType.OutOfGas); } state.GasAvailable = gasAvailable; @@ -673,7 +697,7 @@ private CallResult ExecuteCall(EvmState vmState, ReadOnlyM if (previousCallOutput.Length > 0) { UInt256 localPreviousDest = previousCallOutputDestination; - if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in localPreviousDest, (ulong)previousCallOutput.Length)) + if (!UpdateMemoryCost(vmState, ref gasAvailable, in localPreviousDest, (ulong)previousCallOutput.Length)) { goto OutOfGas; } @@ -740,7 +764,7 @@ private CallResult ExecuteCode externalCode = _codeInfoRepository.GetCachedCodeInfo(_state, address, spec).MachineCode; + ReadOnlyMemory externalCode = txCtx.CodeInfoRepository.GetCachedCodeInfo(_state, address, spec).MachineCode; slice = externalCode.SliceWithZeroPadding(b, (int)result); vmState.Memory.Save(in a, in slice); if (typeof(TTracingInstructions) == typeof(IsTracing)) @@ -1439,7 +1467,8 @@ private CallResult ExecuteCode _returnDataBuffer.Length) { @@ -1448,7 +1477,7 @@ private CallResult ExecuteCode [SkipLocalsInit] [MethodImpl(MethodImplOptions.NoInlining)] - private void InstructionExtCodeSize(Address address, ref EvmStack stack, IReleaseSpec spec) where TTracingInstructions : struct, IIsTracing + private void InstructionExtCodeSize(Address address, ref EvmStack stack, ICodeInfoRepository codeInfoRepository, IReleaseSpec spec) where TTracingInstructions : struct, IIsTracing { - ReadOnlyMemory accountCode = _codeInfoRepository.GetCachedCodeInfo(_state, address, spec).MachineCode; + ReadOnlyMemory accountCode = codeInfoRepository.GetCachedCodeInfo(_state, address, spec).MachineCode; UInt256 result = (UInt256)accountCode.Span.Length; stack.PushUInt256(in result); } @@ -2146,7 +2178,7 @@ private EvmExceptionType InstructionCall( Address codeSource = stack.PopAddress(); if (codeSource is null) return EvmExceptionType.StackUnderflow; - if (!ChargeAccountAccessGas(ref gasAvailable, vmState, codeSource, spec, _txTracer)) return EvmExceptionType.OutOfGas; + if (!ChargeAccountAccessGas(ref gasAvailable, vmState, codeSource, true, _state, spec, _txTracer)) return EvmExceptionType.OutOfGas; UInt256 callValue; switch (instruction) @@ -2171,6 +2203,7 @@ private EvmExceptionType InstructionCall( if (vmState.IsStatic && !transferValue.IsZero && instruction != Instruction.CALLCODE) return EvmExceptionType.StaticCallViolation; Address caller = instruction == Instruction.DELEGATECALL ? env.Caller : env.ExecutingAccount; + Address target = instruction == Instruction.CALL || instruction == Instruction.STATICCALL ? codeSource : env.ExecutingAccount; @@ -2197,11 +2230,11 @@ private EvmExceptionType InstructionCall( } if (!UpdateGas(spec.GetCallCost(), ref gasAvailable) || - !UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in dataOffset, dataLength) || - !UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in outputOffset, outputLength) || + !UpdateMemoryCost(vmState, ref gasAvailable, in dataOffset, dataLength) || + !UpdateMemoryCost(vmState, ref gasAvailable, in outputOffset, outputLength) || !UpdateGas(gasExtra, ref gasAvailable)) return EvmExceptionType.OutOfGas; - CodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(_state, codeSource, spec); + CodeInfo codeInfo = vmState.Env.TxExecutionContext.CodeInfoRepository.GetCachedCodeInfo(_state, codeSource, spec); codeInfo.AnalyseInBackgroundIfRequired(); if (spec.Use63Over64Rule) @@ -2220,8 +2253,7 @@ private EvmExceptionType InstructionCall( gasLimitUl += GasCostOf.CallStipend; } - if (env.CallDepth >= MaxCallDepth || - !transferValue.IsZero && _state.GetBalance(env.ExecutingAccount) < transferValue) + if (env.CallDepth >= MaxCallDepth || !transferValue.IsZero && _state.GetBalance(env.ExecutingAccount) < transferValue) { _returnDataBuffer = Array.Empty(); stack.PushZero(); @@ -2328,7 +2360,7 @@ private static EvmExceptionType InstructionRevert(EvmState vmState, re !stack.PopUInt256(out UInt256 length)) return EvmExceptionType.StackUnderflow; - if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in position, in length)) + if (!UpdateMemoryCost(vmState, ref gasAvailable, in position, in length)) { return EvmExceptionType.OutOfGas; } @@ -2347,7 +2379,7 @@ private static EvmExceptionType InstructionReturn(EvmState vmState, re !stack.PopUInt256(out UInt256 length)) return EvmExceptionType.StackUnderflow; - if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in position, in length)) + if (!UpdateMemoryCost(vmState, ref gasAvailable, in position, in length)) { return EvmExceptionType.OutOfGas; } @@ -2365,7 +2397,7 @@ private EvmExceptionType InstructionSelfDestruct(EvmState vmState, ref Address inheritor = stack.PopAddress(); if (inheritor is null) return EvmExceptionType.StackUnderflow; - if (!ChargeAccountAccessGas(ref gasAvailable, vmState, inheritor, spec, _txTracer, false)) return EvmExceptionType.OutOfGas; + if (!ChargeAccountAccessGas(ref gasAvailable, vmState, inheritor, false, _state, spec, _txTracer, false)) return EvmExceptionType.OutOfGas; Address executingAccount = vmState.Env.ExecutingAccount; bool createInSameTx = vmState.CreateList.Contains(executingAccount); @@ -2430,15 +2462,15 @@ private EvmExceptionType InstructionSelfDestruct(EvmState vmState, ref if (initCodeLength > spec.MaxInitCodeSize) return (EvmExceptionType.OutOfGas, null); } + bool outOfGas = false; long gasCost = GasCostOf.Create + - (spec.IsEip3860Enabled ? GasCostOf.InitCodeWord * EvmPooledMemory.Div32Ceiling(initCodeLength) : 0) + + (spec.IsEip3860Enabled ? GasCostOf.InitCodeWord * EvmPooledMemory.Div32Ceiling(initCodeLength, out outOfGas) : 0) + (instruction == Instruction.CREATE2 - ? GasCostOf.Sha3Word * EvmPooledMemory.Div32Ceiling(initCodeLength) + ? GasCostOf.Sha3Word * EvmPooledMemory.Div32Ceiling(initCodeLength, out outOfGas) : 0); + if (outOfGas || !UpdateGas(gasCost, ref gasAvailable)) return (EvmExceptionType.OutOfGas, null); - if (!UpdateGas(gasCost, ref gasAvailable)) return (EvmExceptionType.OutOfGas, null); - - if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in memoryPositionOfInitCode, initCodeLength)) return (EvmExceptionType.OutOfGas, null); + if (!UpdateMemoryCost(vmState, ref gasAvailable, in memoryPositionOfInitCode, initCodeLength)) return (EvmExceptionType.OutOfGas, null); // TODO: copy pasted from CALL / DELEGATECALL, need to move it outside? if (env.CallDepth >= MaxCallDepth) // TODO: fragile ordering / potential vulnerability for different clients @@ -2490,7 +2522,7 @@ private EvmExceptionType InstructionSelfDestruct(EvmState vmState, ref bool accountExists = _state.AccountExists(contractAddress); - if (accountExists && contractAddress.IsNonZeroAccount(spec, _codeInfoRepository, _state)) + if (accountExists && contractAddress.IsNonZeroAccount(spec, env.TxExecutionContext.CodeInfoRepository, _state)) { /* we get the snapshot before this as there is a possibility with that we will touch an empty account and remove it even if the REVERT operation follows */ if (typeof(TLogger) == typeof(IsTracing)) _logger.Trace($"Contract collision at {contractAddress}"); @@ -2547,7 +2579,7 @@ private EvmExceptionType InstructionLog(EvmState vmState, ref EvmStack if (!stack.PopUInt256(out UInt256 position)) return EvmExceptionType.StackUnderflow; if (!stack.PopUInt256(out UInt256 length)) return EvmExceptionType.StackUnderflow; long topicsCount = instruction - Instruction.LOG0; - if (!UpdateMemoryCost(ref vmState.Memory, ref gasAvailable, in position, length)) return EvmExceptionType.OutOfGas; + if (!UpdateMemoryCost(vmState, ref gasAvailable, in position, length)) return EvmExceptionType.OutOfGas; if (!UpdateGas( GasCostOf.Log + topicsCount * GasCostOf.LogTopic + length.ToLong() * GasCostOf.LogData, ref gasAvailable)) return EvmExceptionType.OutOfGas; @@ -2772,18 +2804,11 @@ private static void UpdateCurrentState(EvmState state, int pc, long gas, int sta state.DataStackHead = stackHead; } - public static bool UpdateMemoryCost(ref EvmPooledMemory memory, ref long gasAvailable, in UInt256 position, in UInt256 length) + public static bool UpdateMemoryCost(EvmState vmState, ref long gasAvailable, in UInt256 position, in UInt256 length) { - long memoryCost = memory.CalculateMemoryCost(in position, length); - if (memoryCost != 0L) - { - if (!UpdateGas(memoryCost, ref gasAvailable)) - { - return false; - } - } - - return true; + long memoryCost = vmState.Memory.CalculateMemoryCost(in position, length, out bool outOfGas); + if (outOfGas) return false; + return memoryCost == 0L || UpdateGas(memoryCost, ref gasAvailable); } private static bool Jump(in UInt256 jumpDest, ref int programCounter, in ExecutionEnvironment env) @@ -2838,30 +2863,13 @@ private void EndInstructionTraceError(long gasAvailable, EvmExceptionType evmExc _txTracer.ReportOperationError(evmExceptionType); } - private static ExecutionType GetCallExecutionType(Instruction instruction, bool isPostMerge = false) - { - ExecutionType executionType; - if (instruction == Instruction.CALL) + private static ExecutionType GetCallExecutionType(Instruction instruction, bool isPostMerge = false) => + instruction switch { - executionType = ExecutionType.CALL; - } - else if (instruction == Instruction.DELEGATECALL) - { - executionType = ExecutionType.DELEGATECALL; - } - else if (instruction == Instruction.STATICCALL) - { - executionType = ExecutionType.STATICCALL; - } - else if (instruction == Instruction.CALLCODE) - { - executionType = ExecutionType.CALLCODE; - } - else - { - throw new NotSupportedException($"Execution type is undefined for {instruction.GetName(isPostMerge)}"); - } - - return executionType; - } + Instruction.CALL => ExecutionType.CALL, + Instruction.DELEGATECALL => ExecutionType.DELEGATECALL, + Instruction.STATICCALL => ExecutionType.STATICCALL, + Instruction.CALLCODE => ExecutionType.CALLCODE, + _ => throw new NotSupportedException($"Execution type is undefined for {instruction.GetName(isPostMerge)}") + }; } diff --git a/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs b/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs index 77fa79f865b..c698909a89d 100644 --- a/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs +++ b/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs @@ -29,6 +29,7 @@ using NUnit.Framework; using Nethermind.Config; using Nethermind.Evm; +using Nethermind.Facade.Find; using Nethermind.Facade.Simulate; using Nethermind.State; diff --git a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs index b95f0646548..50621ebb666 100644 --- a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs @@ -26,6 +26,7 @@ using Nethermind.State; using Nethermind.Core.Extensions; using Nethermind.Config; +using Nethermind.Facade.Find; using Nethermind.Facade.Proxy.Models.Simulate; using Nethermind.Facade.Simulate; using Transaction = Nethermind.Core.Transaction; diff --git a/src/Nethermind/Nethermind.Facade/Eth/AuthorizationTupleForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/AuthorizationTupleForRpc.cs new file mode 100644 index 00000000000..c35c97b1d56 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/AuthorizationTupleForRpc.cs @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Int256; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; + +namespace Nethermind.Facade.Eth +{ + public struct AuthorizationTupleForRpc + { + [JsonConstructor] + public AuthorizationTupleForRpc() + { + } + public AuthorizationTupleForRpc(UInt256 chainId, ulong nonce, Address address, UInt256? yParity, UInt256? s, UInt256? r) + { + ChainId = chainId; + Nonce = nonce; + Address = address; + YParity = yParity; + S = s; + R = r; + } + + public UInt256 ChainId { get; set; } + public ulong Nonce { get; set; } + public Address Address { get; set; } + public UInt256? YParity { get; set; } + public UInt256? S { get; set; } + public UInt256? R { get; set; } + + public static IEnumerable FromAuthorizationList(AuthorizationTuple[] authorizationList) => + authorizationList.Select(tuple => new AuthorizationTupleForRpc(tuple.ChainId, + tuple.Nonce, + tuple.CodeAddress, + tuple.AuthoritySignature.RecoveryId, + new UInt256(tuple.AuthoritySignature.S), + new UInt256(tuple.AuthoritySignature.R))); + } +} diff --git a/src/Nethermind/Nethermind.Facade/Eth/TransactionForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/TransactionForRpc.cs index d51ed363156..5b054f48106 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/TransactionForRpc.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/TransactionForRpc.cs @@ -61,6 +61,11 @@ public TransactionForRpc(Hash256? blockHash, long? blockNumber, int? txIndex, Tr { AccessList = null; } + AuthorizationList = transaction.SupportsAuthorizationList + ? transaction.AuthorizationList is null + ? Array.Empty() + : AuthorizationTupleForRpc.FromAuthorizationList(transaction.AuthorizationList) + : null; MaxFeePerBlobGas = transaction.MaxFeePerBlobGas; BlobVersionedHashes = transaction.BlobVersionedHashes; @@ -126,6 +131,8 @@ public TransactionForRpc() { } public TxType Type { get; set; } public IEnumerable? AccessList { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public IEnumerable? AuthorizationList { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public UInt256? MaxFeePerBlobGas { get; set; } // eip4844 diff --git a/src/Nethermind/Nethermind.Facade/Filters/FilterLog.cs b/src/Nethermind/Nethermind.Facade/Filters/FilterLog.cs index 3d3f003c6fe..c9c775ba479 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/FilterLog.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/FilterLog.cs @@ -6,7 +6,7 @@ namespace Nethermind.Facade.Filters { - public class FilterLog + public class FilterLog : ILogEntry { public Address Address { get; } public Hash256 BlockHash { get; } @@ -27,7 +27,7 @@ public FilterLog(long logIndex, long transactionLogIndex, TxReceipt txReceipt, L txReceipt.BlockHash, txReceipt.Index, txReceipt.TxHash, - logEntry.LoggersAddress, + logEntry.Address, logEntry.Data, logEntry.Topics, removed) diff --git a/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs index 645f7de9c08..d50981f9388 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs @@ -23,12 +23,12 @@ public LogFilter(int id, BlockParameter fromBlock, BlockParameter toBlock, TopicsFilter = topicsFilter; } - public bool Accepts(LogEntry logEntry) => AddressFilter.Accepts(logEntry.LoggersAddress) && TopicsFilter.Accepts(logEntry); + public bool Accepts(LogEntry logEntry) => AddressFilter.Accepts(logEntry.Address) && TopicsFilter.Accepts(logEntry); public bool Matches(Core.Bloom bloom) => AddressFilter.Matches(bloom) && TopicsFilter.Matches(bloom); public bool Matches(ref BloomStructRef bloom) => AddressFilter.Matches(ref bloom) && TopicsFilter.Matches(ref bloom); - public bool Accepts(ref LogEntryStructRef logEntry) => AddressFilter.Accepts(ref logEntry.LoggersAddress) && TopicsFilter.Accepts(ref logEntry); + public bool Accepts(ref LogEntryStructRef logEntry) => AddressFilter.Accepts(ref logEntry.Address) && TopicsFilter.Accepts(ref logEntry); } } diff --git a/src/Nethermind/Nethermind.Facade/Filters/ILogFinder.cs b/src/Nethermind/Nethermind.Facade/Find/ILogFinder.cs similarity index 93% rename from src/Nethermind/Nethermind.Facade/Filters/ILogFinder.cs rename to src/Nethermind/Nethermind.Facade/Find/ILogFinder.cs index a9a2455c78b..a80e2545465 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/ILogFinder.cs +++ b/src/Nethermind/Nethermind.Facade/Find/ILogFinder.cs @@ -7,7 +7,7 @@ using Nethermind.Core; using Nethermind.Facade.Filters; -namespace Nethermind.Blockchain.Find +namespace Nethermind.Facade.Find { public interface ILogFinder { diff --git a/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs b/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs similarity index 98% rename from src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs rename to src/Nethermind/Nethermind.Facade/Find/LogFinder.cs index a2a807d78ed..0f3749977f9 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs +++ b/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs @@ -5,16 +5,18 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using Nethermind.Blockchain; using Nethermind.Blockchain.Filters; +using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Logging; -using Nethermind.Serialization.Rlp; using Nethermind.Db.Blooms; using Nethermind.Facade.Filters; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; -namespace Nethermind.Blockchain.Find +namespace Nethermind.Facade.Find { public class LogFinder : ILogFinder { @@ -254,7 +256,7 @@ private static IEnumerable FilterLogsInBlockLowMemoryAllocation(LogFi receipt.BlockHash.ToCommitment(), receipt.Index, receipt.TxHash.ToCommitment(), - log.LoggersAddress.ToAddress(), + log.Address.ToAddress(), log.Data.ToArray(), topics)); } diff --git a/src/Nethermind/Nethermind.Facade/Find/LogScanner.cs b/src/Nethermind/Nethermind.Facade/Find/LogScanner.cs new file mode 100644 index 00000000000..769727a168e --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Find/LogScanner.cs @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using Nethermind.Blockchain.Filters; +using Nethermind.Blockchain.Filters.Topics; +using Nethermind.Blockchain.Find; +using Nethermind.Core; +using Nethermind.Facade.Filters; +using Nethermind.Logging; + +namespace Nethermind.Facade.Find +{ + public abstract class LogScanner(ILogFinder logFinder, AddressFilter addressFilter, TopicsFilter topicsFilter, ILogManager logManager) + { + private const long LogScanChunkSize = 16; + private const int LogScanCutoffChunks = 128; + private readonly ILogger _logger = logManager.GetClassLogger(); + + public IEnumerable ScanLogs(long headBlockNumber, Predicate shouldStopScanning) + { + BlockParameter end = new(headBlockNumber); + + for (int i = 0; i < LogScanCutoffChunks; i++) + { + bool atGenesis = false; + long startBlockNumber = end.BlockNumber!.Value - LogScanChunkSize; + if (startBlockNumber < 0) + { + atGenesis = true; + startBlockNumber = 0; + } + + BlockParameter start = new(startBlockNumber); + LogFilter logFilter = new(0, start, end, addressFilter, topicsFilter); + + IEnumerable logs = logFinder.FindLogs(logFilter); + int count = 0; + T first = default; + foreach (FilterLog log in logs) + { + T @event = ParseEvent(log); + if (count == 0) + { + first = @event; + } + + yield return @event; + count++; + } + + if (_logger.IsDebug) _logger.Debug($"{GetType().Name} found {count} events from logs in block range {logFilter.FromBlock} - {logFilter.ToBlock}"); + + if (atGenesis || (count != 0 && shouldStopScanning(first))) + { + yield break; + } + + end = new BlockParameter(startBlockNumber - 1); + } + } + + public IEnumerable ScanReceipts(long blockNumber, TxReceipt[] receipts) + { + int count = 0; + + foreach (TxReceipt receipt in receipts) + { + foreach (LogEntry log in receipt.Logs!) + { + if (addressFilter.Accepts(log.Address) && topicsFilter.Accepts(log)) + { + T e = ParseEvent(log); + count++; + yield return e; + } + } + } + + if (_logger.IsDebug) _logger.Debug($"{GetType().Name} found {count} events events in block {blockNumber}."); + } + + protected abstract T ParseEvent(ILogEntry log); + } +} diff --git a/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs index 1fd72314d77..412d2e2c242 100644 --- a/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs @@ -9,6 +9,7 @@ using Nethermind.Core.Crypto; using Nethermind.Evm; using Nethermind.Facade.Filters; +using Nethermind.Facade.Find; using Nethermind.Facade.Simulate; using Nethermind.Facade.Proxy.Models.Simulate; using Nethermind.Int256; diff --git a/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs b/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs index dbe43ddddc7..082d5ad57fe 100644 --- a/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -16,17 +17,17 @@ public class OverridableCodeInfoRepository(ICodeInfoRepository codeInfoRepositor { private readonly Dictionary _codeOverwrites = new(); - public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec) => - _codeOverwrites.TryGetValue(codeSource, out CodeInfo result) + public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress) + { + delegationAddress = null; + return _codeOverwrites.TryGetValue(codeSource, out CodeInfo result) ? result : codeInfoRepository.GetCachedCodeInfo(worldState, codeSource, vmSpec); - - public CodeInfo GetOrAdd(ValueHash256 codeHash, ReadOnlySpan initCode) => codeInfoRepository.GetOrAdd(codeHash, initCode); + } public void InsertCode(IWorldState state, ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec) => codeInfoRepository.InsertCode(state, code, codeOwner, spec); - public void SetCodeOverwrite( IWorldState worldState, IReleaseSpec vmSpec, @@ -36,9 +37,18 @@ public void SetCodeOverwrite( { if (redirectAddress is not null) { - _codeOverwrites[redirectAddress] = GetCachedCodeInfo(worldState, key, vmSpec); + _codeOverwrites[redirectAddress] = this.GetCachedCodeInfo(worldState, key, vmSpec); } _codeOverwrites[key] = value; } + + public void SetDelegation(IWorldState state, Address codeSource, Address authority, IReleaseSpec spec) => + codeInfoRepository.SetDelegation(state, codeSource, authority, spec); + + public bool TryGetDelegation(IReadOnlyStateProvider worldState, Address address, [NotNullWhen(true)] out Address? delegatedAddress) => + codeInfoRepository.TryGetDelegation(worldState, address, out delegatedAddress); + + public ValueHash256 GetExecutableCodeHash(IWorldState worldState, Address address) => + codeInfoRepository.GetExecutableCodeHash(worldState, address); } diff --git a/src/Nethermind/Nethermind.Facade/Proxy/DefaultHttpClient.cs b/src/Nethermind/Nethermind.Facade/Proxy/DefaultHttpClient.cs index aac11f47041..53765e36759 100644 --- a/src/Nethermind/Nethermind.Facade/Proxy/DefaultHttpClient.cs +++ b/src/Nethermind/Nethermind.Facade/Proxy/DefaultHttpClient.cs @@ -79,7 +79,7 @@ private async Task ProcessRequestAsync(Method method, string endpoint, str string methodType = method.ToString(); string json = payload is null ? "{}" : _jsonSerializer.Serialize(payload); if (_logger.IsTrace) _logger.Trace($"Sending HTTP {methodType} request to: {endpoint} [id: {requestId}]{(method == Method.Get ? "." : $": {json}")}"); - Stopwatch stopWatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); HttpResponseMessage response; switch (method) { @@ -94,9 +94,7 @@ private async Task ProcessRequestAsync(Method method, string endpoint, str if (_logger.IsError) _logger.Error($"Unsupported HTTP method: {methodType}."); return default; } - - stopWatch.Stop(); - if (_logger.IsTrace) _logger.Trace($"Received HTTP {methodType} response from: {endpoint} [id: {requestId}, elapsed: {stopWatch.ElapsedMilliseconds} ms]: {response}"); + if (_logger.IsTrace) _logger.Trace($"Received HTTP {methodType} response from: {endpoint} [id: {requestId}, elapsed: {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0} ms]: {response}"); if (!response.IsSuccessStatusCode) { return default; diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateTxMutatorTracer.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateTxMutatorTracer.cs index d5e78e34023..0f49c9494c1 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateTxMutatorTracer.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateTxMutatorTracer.cs @@ -65,7 +65,7 @@ public override void MarkAsSuccess(Address recipient, long gasSpent, byte[] outp Status = StatusCode.Success, Logs = logs.Select((entry, i) => new Log { - Address = entry.LoggersAddress, + Address = entry.Address, Topics = entry.Topics, Data = entry.Data, LogIndex = (ulong)i, diff --git a/src/Nethermind/Nethermind.Init/InitializeStateDb.cs b/src/Nethermind/Nethermind.Init/InitializeStateDb.cs index 8ca20a82750..2b5e642d17b 100644 --- a/src/Nethermind/Nethermind.Init/InitializeStateDb.cs +++ b/src/Nethermind/Nethermind.Init/InitializeStateDb.cs @@ -16,6 +16,7 @@ using Nethermind.Core.Extensions; using Nethermind.Db; using Nethermind.Db.FullPruning; +using Nethermind.Db.Rocks.Config; using Nethermind.Init.Steps; using Nethermind.JsonRpc.Converters; using Nethermind.Logging; @@ -100,10 +101,27 @@ public Task Execute(CancellationToken cancellationToken) persistenceStrategy = persistenceStrategy.Or(triggerPersistenceStrategy); } - if ((_api.NodeStorageFactory.CurrentKeyScheme != INodeStorage.KeyScheme.Hash || initConfig.StateDbKeyScheme == INodeStorage.KeyScheme.HalfPath) - && pruningConfig.CacheMb > 2000) + // On a 7950x (32 logical coree), assuming write buffer is large enough, the pruning time is about 3 second + // with 8GB of pruning cache. Lets assume that this is a safe estimate as the ssd can be a limitation also. + long maximumCacheMb = Environment.ProcessorCount * 250; + // It must be at least 1GB as on mainnet at least 500MB will remain to support snap sync. So pruning cache only drop to about 500MB after pruning. + maximumCacheMb = Math.Max(1000, maximumCacheMb); + if (pruningConfig.CacheMb > maximumCacheMb) { - if (_logger.IsWarn) _logger.Warn($"Detected {pruningConfig.CacheMb}MB of pruning cache config. Pruning cache more than 2000MB is not recommended as it may cause long memory pruning time which affect attestation."); + // The user can also change `--Db.StateDbWriteBufferSize`. + // Which may or may not be better as each read will need to go through eacch write buffer. + // So having less of them is probably better.. + if (_logger.IsWarn) _logger.Warn($"Detected {pruningConfig.CacheMb}MB of pruning cache config. Pruning cache more than {maximumCacheMb}MB is not recommended with {Environment.ProcessorCount} logical core as it may cause long memory pruning time which affect attestation."); + } + + var dbConfig = _api.Config(); + var totalWriteBufferMb = dbConfig.StateDbWriteBufferNumber * dbConfig.StateDbWriteBufferSize / (ulong)1.MB(); + var minimumWriteBufferMb = 0.2 * pruningConfig.CacheMb; + if (totalWriteBufferMb < minimumWriteBufferMb) + { + int minimumWriteBufferNumber = (int)Math.Ceiling((minimumWriteBufferMb * 1.MB()) / dbConfig.StateDbWriteBufferSize); + + if (_logger.IsWarn) _logger.Warn($"Detected {totalWriteBufferMb}MB of maximum write buffer size. Write buffer size should be at least 20% of pruning cache MB or memory pruning may slow down. Try setting `--Db.{nameof(dbConfig.WriteBufferNumber)} {minimumWriteBufferNumber}`."); } pruningStrategy = Prune diff --git a/src/Nethermind/Nethermind.Init/Steps/EthereumStepsManager.cs b/src/Nethermind/Nethermind.Init/Steps/EthereumStepsManager.cs index 4912b4ba817..337729eb705 100644 --- a/src/Nethermind/Nethermind.Init/Steps/EthereumStepsManager.cs +++ b/src/Nethermind/Nethermind.Init/Steps/EthereumStepsManager.cs @@ -147,14 +147,14 @@ private void RunOneRoundOfInitialization(CancellationToken cancellationToken) private async Task ExecuteStep(IStep step, StepInfo stepInfo, CancellationToken cancellationToken) { - Stopwatch stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); try { await step.Execute(cancellationToken); if (_logger.IsDebug) _logger.Debug( - $"Step {step.GetType().Name.PadRight(24)} executed in {stopwatch.ElapsedMilliseconds}ms"); + $"Step {step.GetType().Name.PadRight(24)} executed in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); stepInfo.Stage = StepInitializationStage.Complete; } @@ -164,7 +164,7 @@ private async Task ExecuteStep(IStep step, StepInfo stepInfo, CancellationToken { if (_logger.IsError) _logger.Error( - $"Step {step.GetType().Name.PadRight(24)} failed after {stopwatch.ElapsedMilliseconds}ms", + $"Step {step.GetType().Name.PadRight(24)} failed after {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms", exception); stepInfo.Stage = StepInitializationStage.Failed; @@ -174,13 +174,12 @@ private async Task ExecuteStep(IStep step, StepInfo stepInfo, CancellationToken if (_logger.IsWarn) { _logger.Warn( - $"Step {step.GetType().Name.PadRight(24)} failed after {stopwatch.ElapsedMilliseconds}ms {exception}"); + $"Step {step.GetType().Name.PadRight(24)} failed after {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms {exception}"); } stepInfo.Stage = StepInitializationStage.Complete; } finally { - stopwatch.Stop(); _autoResetEvent.Set(); if (_logger.IsDebug) _logger.Debug($"{step.GetType().Name.PadRight(24)} complete"); diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockTree.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockTree.cs index d0a6abb5c8e..0cd10de51f0 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockTree.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockTree.cs @@ -15,6 +15,7 @@ using Nethermind.Core; using Nethermind.Db; using Nethermind.Db.Blooms; +using Nethermind.Facade.Find; using Nethermind.Serialization.Rlp; using Nethermind.State.Repositories; diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs index cbfc2536448..28e0b18d612 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs @@ -59,7 +59,9 @@ protected virtual Task InitBlockchain() IReceiptConfig receiptConfig = getApi.Config(); IStateReader stateReader = setApi.StateReader!; - ITxPool txPool = _api.TxPool = CreateTxPool(); + PreBlockCaches? preBlockCaches = (_api.WorldState as IPreBlockCaches)?.Caches; + CodeInfoRepository codeInfoRepository = new(preBlockCaches?.PrecompileCache); + ITxPool txPool = _api.TxPool = CreateTxPool(codeInfoRepository); ReceiptCanonicalityMonitor receiptCanonicalityMonitor = new(getApi.ReceiptStorage, _api.LogManager); getApi.DisposeStack.Push(receiptCanonicalityMonitor); @@ -68,8 +70,7 @@ protected virtual Task InitBlockchain() _api.BlockPreprocessor.AddFirst( new RecoverSignatures(getApi.EthereumEcdsa, txPool, getApi.SpecProvider, getApi.LogManager)); - PreBlockCaches? preBlockCaches = (_api.WorldState as IPreBlockCaches)?.Caches; - CodeInfoRepository codeInfoRepository = new(preBlockCaches?.PrecompileCache); + VirtualMachine virtualMachine = CreateVirtualMachine(codeInfoRepository); _api.TransactionProcessor = CreateTransactionProcessor(codeInfoRepository, virtualMachine); @@ -81,17 +82,17 @@ protected virtual Task InitBlockchain() setApi.BlockValidator = CreateBlockValidator(); IChainHeadInfoProvider chainHeadInfoProvider = - new ChainHeadInfoProvider(getApi.SpecProvider!, getApi.BlockTree!, stateReader); + new ChainHeadInfoProvider(getApi.SpecProvider!, getApi.BlockTree!, stateReader, codeInfoRepository); // TODO: can take the tx sender from plugin here maybe ITxSigner txSigner = new WalletTxSigner(getApi.Wallet, getApi.SpecProvider!.ChainId); TxSealer nonceReservingTxSealer = new(txSigner, getApi.Timestamper); - INonceManager nonceManager = new NonceManager(chainHeadInfoProvider.AccountStateProvider); + INonceManager nonceManager = new NonceManager(chainHeadInfoProvider.ReadOnlyStateProvider); setApi.NonceManager = nonceManager; setApi.TxSender = new TxPoolSender(txPool, nonceReservingTxSealer, nonceManager, getApi.EthereumEcdsa!); - setApi.TxPoolInfoProvider = new TxPoolInfoProvider(chainHeadInfoProvider.AccountStateProvider, txPool); + setApi.TxPoolInfoProvider = new TxPoolInfoProvider(chainHeadInfoProvider.ReadOnlyStateProvider, txPool); setApi.GasPriceOracle = new GasPriceOracle(getApi.BlockTree!, getApi.SpecProvider, _api.LogManager, blocksConfig.MinGasPrice); BlockCachePreWarmer? preWarmer = blocksConfig.PreWarmStateOnBlockProcessing ? new(new(_api.WorldStateManager!, _api.BlockTree!, _api.SpecProvider, _api.LogManager, _api.WorldState), _api.SpecProvider!, _api.LogManager, preBlockCaches) @@ -207,10 +208,10 @@ protected virtual IHealthHintService CreateHealthHintService() => protected virtual IBlockProductionPolicy CreateBlockProductionPolicy() => new BlockProductionPolicy(_api.Config()); - protected virtual TxPool.TxPool CreateTxPool() => - new(_api.EthereumEcdsa!, + protected virtual ITxPool CreateTxPool(CodeInfoRepository codeInfoRepository) => + new TxPool.TxPool(_api.EthereumEcdsa!, _api.BlobTxStorage ?? NullBlobTxStorage.Instance, - new ChainHeadInfoProvider(_api.SpecProvider!, _api.BlockTree!, _api.StateReader!), + new ChainHeadInfoProvider(_api.SpecProvider!, _api.BlockTree!, _api.StateReader!, codeInfoRepository), _api.Config(), _api.TxValidator!, _api.LogManager, diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializePlugins.cs b/src/Nethermind/Nethermind.Init/Steps/InitializePlugins.cs index 31143a8f43e..122ba122348 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializePlugins.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializePlugins.cs @@ -30,11 +30,10 @@ public async Task Execute(CancellationToken cancellationToken) try { if (logger.IsInfo) logger.Info($" {plugin.Name} by {plugin.Author}"); - Stopwatch stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); await plugin.Init(_api); - stopwatch.Stop(); if (logger.IsInfo) - logger.Info($" {plugin.Name} by {plugin.Author} initialized in {stopwatch.ElapsedMilliseconds}ms"); + logger.Info($" {plugin.Name} by {plugin.Author} initialized in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); } catch (Exception e) { diff --git a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs index fc14ad0f119..885496c6812 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs @@ -37,6 +37,7 @@ using BlockTree = Nethermind.Blockchain.BlockTree; using Nethermind.Blockchain.Synchronization; using Nethermind.Config; +using Nethermind.Facade.Find; using Nethermind.Facade.Simulate; using Nethermind.Synchronization.ParallelSync; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs index 2bb1b14a1de..e5dd0d4cd59 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs @@ -17,7 +17,7 @@ namespace Nethermind.JsonRpc.Test; -[Parallelizable(ParallelScope.All)] +[Parallelizable(ParallelScope.Self)] [TestFixture(true)] [TestFixture(false)] public class JsonRpcProcessorTests(bool returnErrors) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcUrlTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcUrlTests.cs index cb88348b86e..bc2335a3d99 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcUrlTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcUrlTests.cs @@ -7,7 +7,7 @@ namespace Nethermind.JsonRpc.Test; -[Parallelizable(ParallelScope.All)] +[Parallelizable(ParallelScope.Self)] public class JsonRpcUrlTests { [TestCase("http://127.0.0.1:1234|http|eth;web3;net", "http", "127.0.0.1", 1234, RpcEndpoint.Http, new string[] { "eth", "web3", "net" })] diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs index 076e792e566..843406aad94 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs @@ -36,7 +36,7 @@ namespace Nethermind.JsonRpc.Test.Modules.Eth; -[Parallelizable(ParallelScope.All)] +[Parallelizable(ParallelScope.Self)] [SetCulture("en-US")] public partial class EthRpcModuleTests { @@ -1201,6 +1201,101 @@ public async Task eth_getBlockByNumber_should_return_withdrawals_correctly() })), Is.EqualTo(result)); } + + [Test] + public async Task eth_sendRawTransaction_sender_with_non_delegated_code_is_rejected() + { + var specProvider = new TestSpecProvider(Prague.Instance); + specProvider.AllowTestChainOverride = false; + + TestRpcBlockchain Test = await TestRpcBlockchain.ForTest(SealEngineType.NethDev).Build(specProvider); + + Transaction testTx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithNonce(Test.State.GetNonce(TestItem.AddressA)) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(GasCostOf.Transaction + GasCostOf.NewAccount) + .WithAuthorizationCodeIfAuthorizationListTx() + .WithTo(TestItem.AddressA) + .SignedAndResolved(TestItem.PrivateKeyA).TestObject; + + string result = await Test.TestEthRpc("eth_sendRawTransaction", Bytes.ToHexString(Rlp.Encode(testTx).Bytes)); + + JsonRpcErrorResponse actual = new EthereumJsonSerializer().Deserialize(result); + Assert.That(actual.Error!.Message, Is.EqualTo(nameof(AcceptTxResult.SenderIsContract))); + } + + + [Test] + public async Task eth_sendRawTransaction_sender_with_delegated_code_is_accepted() + { + var specProvider = new TestSpecProvider(Prague.Instance); + specProvider.AllowTestChainOverride = false; + + TestRpcBlockchain test = await TestRpcBlockchain.ForTest(SealEngineType.NethDev).Build(specProvider); + Transaction setCodeTx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithNonce(test.State.GetNonce(TestItem.AddressB)) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(GasCostOf.Transaction + GasCostOf.NewAccount) + .WithAuthorizationCode(test.EthereumEcdsa.Sign(TestItem.PrivateKeyB, 0, Address.Zero, (ulong)test.State.GetNonce(TestItem.AddressB) + 1)) + .WithTo(TestItem.AddressA) + .SignedAndResolved(TestItem.PrivateKeyB).TestObject; + + await test.AddBlock(setCodeTx); + + var code = test.State.GetCode(TestItem.AddressB); + + Assert.That(code!.Slice(0, 3), Is.EquivalentTo(Eip7702Constants.DelegationHeader.ToArray())); + + Transaction normalTx = Build.A.Transaction + .WithNonce(test.State.GetNonce(TestItem.AddressB)) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(GasCostOf.Transaction) + .WithTo(TestItem.AddressA) + .SignedAndResolved(TestItem.PrivateKeyB).TestObject; + + string result = await test.TestEthRpc("eth_sendRawTransaction", Bytes.ToHexString(Rlp.Encode(normalTx).Bytes)); + + JsonRpcSuccessResponse actual = new EthereumJsonSerializer().Deserialize(result); + Assert.That(actual.Result, Is.Not.Null); + } + + [Test] + public async Task eth_sendRawTransaction_returns_correct_error_if_AuthorityTuple_has_null_value() + { + var specProvider = new TestSpecProvider(Prague.Instance); + specProvider.AllowTestChainOverride = false; + + TestRpcBlockchain test = await TestRpcBlockchain.ForTest(SealEngineType.NethDev).Build(specProvider); + Transaction invalidSetCodeTx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithNonce(test.State.GetNonce(TestItem.AddressB)) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(GasCostOf.Transaction + GasCostOf.NewAccount) + .WithAuthorizationCode(new AllowNullAuthorizationTuple(0, null, 0, new Signature(new byte[65]))) + .WithTo(TestItem.AddressA) + .SignedAndResolved(TestItem.PrivateKeyB).TestObject; + + string result = await test.TestEthRpc("eth_sendRawTransaction", Bytes.ToHexString(Rlp.Encode(invalidSetCodeTx).Bytes)); + + JsonRpcErrorResponse actual = new EthereumJsonSerializer().Deserialize(result); + Assert.That(actual.Error!.Code, Is.EqualTo(ErrorCodes.TransactionRejected)); + } + public class AllowNullAuthorizationTuple : AuthorizationTuple + { + public AllowNullAuthorizationTuple(ulong chainId, Address? codeAddress, ulong nonce, Signature? sig) + : base(chainId, Address.Zero, nonce, new Signature(new byte[65])) + { + CodeAddress = codeAddress!; + AuthoritySignature = sig!; + } + } + private static (byte[] ByteCode, AccessListItemForRpc[] AccessList) GetTestAccessList(long loads = 2, bool allowSystemUser = true) { AccessListItemForRpc[] accessList = allowSystemUser diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/ParityRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/ParityRpcModuleTests.cs index 3fd4d6dbac0..708dd5575c1 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/ParityRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/ParityRpcModuleTests.cs @@ -35,6 +35,7 @@ using NSubstitute; using NUnit.Framework; using System; +using Nethermind.Evm; namespace Nethermind.JsonRpc.Test.Modules { @@ -75,7 +76,7 @@ public void Initialize() _txPool = new TxPool.TxPool(_ethereumEcdsa, new BlobTxStorage(), - new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(specProvider), _blockTree, stateProvider), + new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(specProvider), _blockTree, stateProvider, new CodeInfoRepository()), new TxPoolConfig(), new TxValidator(specProvider.ChainId), LimboLogs.Instance, diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/TxTraceFilterTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/TxTraceFilterTests.cs index 370f349e1f6..6f974ebae85 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/TxTraceFilterTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/TxTraceFilterTests.cs @@ -8,7 +8,7 @@ namespace Nethermind.JsonRpc.Test.Modules.Trace; -[Parallelizable(ParallelScope.All)] +[Parallelizable(ParallelScope.Self)] [TestFixture] public class TxTraceFilterTests { diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/LogEntryForRpc.cs b/src/Nethermind/Nethermind.JsonRpc/Data/LogEntryForRpc.cs index e4020806510..04a2182abd2 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Data/LogEntryForRpc.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Data/LogEntryForRpc.cs @@ -20,7 +20,7 @@ public LogEntryForRpc(TxReceipt receipt, LogEntry logEntry, int index) TransactionHash = receipt.TxHash; BlockHash = receipt.BlockHash; BlockNumber = receipt.BlockNumber; - Address = logEntry.LoggersAddress; + Address = logEntry.Address; Data = logEntry.Data; Topics = logEntry.Topics; } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs index bcc2a257cab..b684ee14b23 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs @@ -119,7 +119,7 @@ private ArrayPoolList DeserializeArray(JsonElement element) => public async IAsyncEnumerable ProcessAsync(PipeReader reader, JsonRpcContext context) { reader = await RecordRequest(reader); - Stopwatch stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); CancellationTokenSource timeoutSource = new(_jsonRpcConfig.Timeout); // Handles general exceptions during parsing and validation. @@ -130,8 +130,7 @@ JsonRpcResult GetParsingError(string error, Exception? exception = null) if (_logger.IsError) _logger.Error(error, exception); JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ParseError, "Incorrect message"); TraceResult(response); - stopwatch.Stop(); - return JsonRpcResult.Single(RecordResponse(response, new RpcReport("# parsing error #", stopwatch.ElapsedMicroseconds(), false))); + return JsonRpcResult.Single(RecordResponse(response, new RpcReport("# parsing error #", (long)Stopwatch.GetElapsedTime(startTime).TotalMicroseconds, false))); } // Initializes a buffer to store the data read from the reader. @@ -219,9 +218,6 @@ JsonRpcResult GetParsingError(string error, Exception? exception = null) yield return deserializationFailureResult.Value; break; } - - // Stops the stopwatch and yields the batch processing result. - stopwatch.Stop(); JsonRpcBatchResult jsonRpcBatchResult = new((e, c) => IterateRequest(collection, context, e).GetAsyncEnumerator(c)); jsonRpcBatchResult.AddDisposable(() => collection.Dispose()); yield return JsonRpcResult.Collection(jsonRpcBatchResult); @@ -235,9 +231,8 @@ JsonRpcResult GetParsingError(string error, Exception? exception = null) errorResponse.AddDisposable(() => jsonDocument.Dispose()); TraceResult(errorResponse); - stopwatch.Stop(); - if (_logger.IsDebug) _logger.Debug($" Failed request handled in {stopwatch.Elapsed.TotalMilliseconds}ms"); - deserializationFailureResult = JsonRpcResult.Single(RecordResponse(errorResponse, new RpcReport("# parsing error #", stopwatch.ElapsedMicroseconds(), false))); + if (_logger.IsDebug) _logger.Debug($" Failed request handled in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); + deserializationFailureResult = JsonRpcResult.Single(RecordResponse(errorResponse, new RpcReport("# parsing error #", (long)Stopwatch.GetElapsedTime(startTime).TotalMilliseconds, false))); yield return deserializationFailureResult.Value; break; } @@ -304,7 +299,7 @@ static bool HasNonWhitespace(ReadOnlySpan span) { try { - Stopwatch stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); int requestIndex = 0; for (int index = 0; index < requests.Count; index++) { @@ -325,7 +320,7 @@ static bool HasNonWhitespace(ReadOnlySpan span) yield return RecordResponse(response); } - if (_logger.IsDebug) _logger.Debug($" {requests.Count} requests handled in {stopwatch.Elapsed.TotalMilliseconds}ms"); + if (_logger.IsDebug) _logger.Debug($" {requests.Count} requests handled in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); } finally { @@ -336,7 +331,7 @@ static bool HasNonWhitespace(ReadOnlySpan span) private async Task HandleSingleRequest(JsonRpcRequest request, JsonRpcContext context) { Metrics.JsonRpcRequests++; - Stopwatch stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); JsonRpcResponse response = await _jsonRpcService.SendRequestAsync(request, context); JsonRpcErrorResponse localErrorResponse = response as JsonRpcErrorResponse; @@ -355,11 +350,10 @@ static bool HasNonWhitespace(ReadOnlySpan span) Metrics.JsonRpcSuccesses++; } - stopwatch.Stop(); - if (_logger.IsDebug) _logger.Debug($" {request} handled in {stopwatch.Elapsed.TotalMilliseconds}ms"); + if (_logger.IsDebug) _logger.Debug($" {request} handled in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); - JsonRpcResult.Entry result = new(response, new RpcReport(request.Method, stopwatch.ElapsedMicroseconds(), isSuccess)); + JsonRpcResult.Entry result = new(response, new RpcReport(request.Method, (long)Stopwatch.GetElapsedTime(startTime).TotalMicroseconds, isSuccess)); TraceResult(result); return result; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index 54b3db43ab0..2a56b3ce014 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -110,12 +110,12 @@ public ResultWrapper
eth_coinbase() { return ResultWrapper.Success(UInt256.Zero); } - if (!BlobGasCalculator.TryCalculateBlobGasPricePerUnit(_blockFinder.Head?.Header?.ExcessBlobGas ?? 0, - out UInt256 blobGasPricePerUnit)) + if (!BlobGasCalculator.TryCalculateFeePerBlobGas(_blockFinder.Head?.Header?.ExcessBlobGas ?? 0, + out UInt256 feePerBlobGas)) { return ResultWrapper.Fail("Unable to calculate the current blob base fee"); } - return ResultWrapper.Success(blobGasPricePerUnit); + return ResultWrapper.Success(feePerBlobGas); } public ResultWrapper eth_maxPriorityFeePerGas() diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/FeeHistory/FeeHistoryOracle.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/FeeHistory/FeeHistoryOracle.cs index 6d319ab4f9c..28fe3a15d93 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/FeeHistory/FeeHistoryOracle.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/FeeHistory/FeeHistoryOracle.cs @@ -108,12 +108,12 @@ private readonly record struct BlockFeeHistorySearchInfo( { BlockFeeHistorySearchInfo BlockFeeHistorySearchInfoFromBlock(Block b) { - BlobGasCalculator.TryCalculateBlobGasPricePerUnit(b.Header, out UInt256 blobGas); + BlobGasCalculator.TryCalculateFeePerBlobGas(b.Header, out UInt256 feePerBlobGas); return new( b.Number, b.BaseFeePerGas, BaseFeeCalculator.Calculate(b.Header, _specProvider.GetSpecFor1559(b.Number + 1)), - blobGas == UInt256.MaxValue ? 0 : blobGas, + feePerBlobGas == UInt256.MaxValue ? UInt256.Zero : feePerBlobGas, b.GasUsed / (double)b.GasLimit, (b.BlobGasUsed ?? 0) / (double)Eip4844Constants.MaxBlobGasPerBlock, b.ParentHash, diff --git a/src/Nethermind/Nethermind.JsonRpc/WebSockets/JsonRpcSocketsClient.cs b/src/Nethermind/Nethermind.JsonRpc/WebSockets/JsonRpcSocketsClient.cs index 3cf54185a5e..ddf29f87e36 100644 --- a/src/Nethermind/Nethermind.JsonRpc/WebSockets/JsonRpcSocketsClient.cs +++ b/src/Nethermind/Nethermind.JsonRpc/WebSockets/JsonRpcSocketsClient.cs @@ -58,7 +58,6 @@ public override void Dispose() public override async Task ProcessAsync(ArraySegment data) { - Stopwatch stopwatch = Stopwatch.StartNew(); IncrementBytesReceivedMetric(data.Count); PipeReader request = PipeReader.Create(new ReadOnlySequence(data.Array!, data.Offset, data.Count)); int allResponsesSize = 0; @@ -67,23 +66,21 @@ public override async Task ProcessAsync(ArraySegment data) { using (result) { - stopwatch.Restart(); - int singleResponseSize = await SendJsonRpcResult(result); allResponsesSize += singleResponseSize; + long startTime = Stopwatch.GetTimestamp(); + if (result.IsCollection) { - long handlingTimeMicroseconds = stopwatch.ElapsedMicroseconds(); + long handlingTimeMicroseconds = (long)Stopwatch.GetElapsedTime(startTime).TotalMicroseconds; _ = _jsonRpcLocalStats.ReportCall(new RpcReport("# collection serialization #", handlingTimeMicroseconds, true), handlingTimeMicroseconds, singleResponseSize); } else { - long handlingTimeMicroseconds = stopwatch.ElapsedMicroseconds(); + long handlingTimeMicroseconds = (long)Stopwatch.GetElapsedTime(startTime).TotalMicroseconds; _ = _jsonRpcLocalStats.ReportCall(result.Report!.Value, handlingTimeMicroseconds, singleResponseSize); } - - stopwatch.Restart(); } } diff --git a/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs index 13c7e2c616f..b2d1e4c26ab 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs @@ -17,6 +17,7 @@ using Nethermind.Consensus.Producers; using Nethermind.Consensus.Requests; using Nethermind.Consensus.Rewards; +using Nethermind.Consensus.Transactions; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -33,7 +34,6 @@ using Nethermind.Serialization.Json; using Nethermind.Specs; using Nethermind.Specs.ChainSpecStyle; -using Nethermind.State; using Nethermind.Synchronization.ParallelSync; using NSubstitute; using NUnit.Framework; @@ -47,7 +47,7 @@ protected override MergeTestBlockchain CreateBaseBlockchain( IPayloadPreparationService? mockedPayloadService = null, ILogManager? logManager = null, IConsensusRequestsProcessor? mockedConsensusRequestsProcessor = null) - => new MergeAuRaTestBlockchain(mergeConfig, mockedPayloadService, logManager, mockedConsensusRequestsProcessor); + => new MergeAuRaTestBlockchain(mergeConfig, mockedPayloadService, null, logManager, mockedConsensusRequestsProcessor); protected override Hash256 ExpectedBlockHash => new("0x990d377b67dbffee4a60db6f189ae479ffb406e8abea16af55e0469b8524cf46"); @@ -99,15 +99,17 @@ public override Task Can_apply_withdrawals_correctly((Withdrawal[][] Withdrawals return base.Can_apply_withdrawals_correctly(input); } - class MergeAuRaTestBlockchain : MergeTestBlockchain + public class MergeAuRaTestBlockchain : MergeTestBlockchain { private AuRaNethermindApi? _api; + protected ITxSource? _additionalTxSource; - public MergeAuRaTestBlockchain(IMergeConfig? mergeConfig = null, IPayloadPreparationService? mockedPayloadPreparationService = null, ILogManager? logManager = null, IConsensusRequestsProcessor? mockedConsensusRequestsProcessor = null) + public MergeAuRaTestBlockchain(IMergeConfig? mergeConfig = null, IPayloadPreparationService? mockedPayloadPreparationService = null, ITxSource? additionalTxSource = null, ILogManager? logManager = null, IConsensusRequestsProcessor? mockedConsensusRequestsProcessor = null) : base(mergeConfig, mockedPayloadPreparationService, logManager, mockedConsensusRequestsProcessor) { ConsensusRequestsProcessor = mockedConsensusRequestsProcessor; SealEngineType = Core.SealEngineType.AuRa; + _additionalTxSource = additionalTxSource; } protected override Task Build(ISpecProvider? specProvider = null, UInt256? initialValues = null, bool addBlockOnStart = true) @@ -194,12 +196,12 @@ protected override IBlockProducer CreateTestBlockProducer(TxPoolTxSource txPoolT ConsensusRequestsProcessor); - BlockProducerEnv blockProducerEnv = blockProducerEnvFactory.Create(); + BlockProducerEnv blockProducerEnv = blockProducerEnvFactory.Create(_additionalTxSource); PostMergeBlockProducer postMergeBlockProducer = blockProducerFactory.Create(blockProducerEnv); PostMergeBlockProducer = postMergeBlockProducer; PayloadPreparationService ??= new PayloadPreparationService( postMergeBlockProducer, - new BlockImprovementContextFactory(PostMergeBlockProducer, TimeSpan.FromSeconds(MergeConfig.SecondsPerSlot)), + CreateBlockImprovementContextFactory(PostMergeBlockProducer), TimerFactory.Default, LogManager, TimeSpan.FromSeconds(MergeConfig.SecondsPerSlot), @@ -227,5 +229,8 @@ protected override IBlockProducer CreateTestBlockProducer(TxPoolTxSource txPoolT return new MergeBlockProducer(preMergeBlockProducer, postMergeBlockProducer, PoSSwitcher); } + + protected virtual IBlockImprovementContextFactory CreateBlockImprovementContextFactory(IBlockProducer blockProducer) + => new BlockImprovementContextFactory(blockProducer, TimeSpan.FromSeconds(MergeConfig.SecondsPerSlot)); } } diff --git a/src/Nethermind/Nethermind.Merge.AuRa/Nethermind.Merge.AuRa.csproj b/src/Nethermind/Nethermind.Merge.AuRa/Nethermind.Merge.AuRa.csproj index 0d012660e8a..6cb68cd5b6d 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa/Nethermind.Merge.AuRa.csproj +++ b/src/Nethermind/Nethermind.Merge.AuRa/Nethermind.Merge.AuRa.csproj @@ -1,4 +1,4 @@ - + Nethermind.Merge.AuRa diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/ConsensusRequestsProcessorMock.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/ConsensusRequestsProcessorMock.cs index 386989099f8..14b1b8881c1 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/ConsensusRequestsProcessorMock.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/ConsensusRequestsProcessorMock.cs @@ -20,7 +20,9 @@ public class ConsensusRequestsProcessorMock : IConsensusRequestsProcessor TestItem.DepositA_1Eth, TestItem.DepositB_2Eth, TestItem.WithdrawalRequestA, - TestItem.WithdrawalRequestB + TestItem.WithdrawalRequestB, + TestItem.ConsolidationRequestA, + TestItem.ConsolidationRequestB ]; public void ProcessRequests(Block block, IWorldState state, TxReceipt[] receipts, IReleaseSpec spec) diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs index 1fa8cc0d5d4..2de5457185a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs @@ -84,7 +84,7 @@ Transaction BuildTransaction(uint index, AccountStruct senderAccount) return Enumerable.Range(0, (int)count).Select(i => BuildTransaction((uint)i, account)).ToArray(); } - private ExecutionPayload CreateParentBlockRequestOnHead(IBlockTree blockTree) + protected ExecutionPayload CreateParentBlockRequestOnHead(IBlockTree blockTree) { Block? head = blockTree.Head; if (head is null) throw new NotSupportedException(); @@ -119,8 +119,15 @@ private static ExecutionPayload CreateBlockRequest(MergeTestBlockchain chain, Ex return blockRequest; } - private static ExecutionPayloadV3 CreateBlockRequestV3(MergeTestBlockchain chain, ExecutionPayload parent, Address miner, Withdrawal[]? withdrawals = null, - ulong? blobGasUsed = null, ulong? excessBlobGas = null, Transaction[]? transactions = null, Hash256? parentBeaconBlockRoot = null) + private static ExecutionPayloadV3 CreateBlockRequestV3( + MergeTestBlockchain chain, + ExecutionPayload parent, + Address miner, + Withdrawal[]? withdrawals = null, + ulong? blobGasUsed = null, + ulong? excessBlobGas = null, + Transaction[]? transactions = null, + Hash256? parentBeaconBlockRoot = null) { ExecutionPayloadV3 blockRequestV3 = CreateBlockRequestInternal(parent, miner, withdrawals, blobGasUsed, excessBlobGas, transactions: transactions, parentBeaconBlockRoot: parentBeaconBlockRoot); blockRequestV3.TryGetBlock(out Block? block); @@ -170,9 +177,11 @@ private static T CreateBlockRequestInternal(ExecutionPayload parent, Address { Deposit[]? deposits = null; WithdrawalRequest[]? withdrawalRequests = null; + ConsolidationRequest[]? consolidationRequests = null; + if (requests is not null) { - (deposits, withdrawalRequests) = requests.SplitRequests(); + (deposits, withdrawalRequests, consolidationRequests) = requests.SplitRequests(); } T blockRequest = new() @@ -191,7 +200,8 @@ private static T CreateBlockRequestInternal(ExecutionPayload parent, Address ExcessBlobGas = excessBlobGas, ParentBeaconBlockRoot = parentBeaconBlockRoot, DepositRequests = deposits, - WithdrawalRequests = withdrawalRequests + WithdrawalRequests = withdrawalRequests, + ConsolidationRequests = consolidationRequests, }; blockRequest.SetTransactions(transactions ?? Array.Empty()); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs index fb20a057b19..a95d1a9479b 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs @@ -76,7 +76,7 @@ protected async Task CreateBlockchain(ISpecProvider specPro ILogManager? logManager = null) => await CreateBaseBlockchain(logManager: logManager).Build(specProvider); - private IEngineRpcModule CreateEngineModule(MergeTestBlockchain chain, ISyncConfig? syncConfig = null, TimeSpan? newPayloadTimeout = null, int newPayloadCacheSize = 50) + protected IEngineRpcModule CreateEngineModule(MergeTestBlockchain chain, ISyncConfig? syncConfig = null, TimeSpan? newPayloadTimeout = null, int newPayloadCacheSize = 50) { IPeerRefresher peerRefresher = Substitute.For(); var synchronizationConfig = syncConfig ?? new SyncConfig(); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs index da2e62ef01a..93e0c019c8a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs @@ -257,6 +257,7 @@ public async Task forkChoiceUpdatedV1_unknown_block_parent_while_syncing_initiat pointers.LowestInsertedBeaconHeader = block.Header; pointers.BestKnownBeaconBlock = block.Number; pointers.LowestInsertedHeader = block.Header; + AssertBlockTreePointers(chain.BlockTree, pointers); await rpc.engine_newPayloadV1(ExecutionPayload.Create(nextUnconnectedBlock)); @@ -273,6 +274,7 @@ public async Task forkChoiceUpdatedV1_unknown_block_parent_while_syncing_initiat nextUnconnectedBlock.Header.TotalDifficulty = 0; pointers.LowestInsertedBeaconHeader = nextUnconnectedBlock.Header; pointers.BestKnownBeaconBlock = nextUnconnectedBlock.Number; + AssertBlockTreePointers(chain.BlockTree, pointers); AssertExecutionStatusNotChangedV1(chain.BlockFinder, block.Hash!, startingHead, startingHead); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index 83af60a8eff..899a499d91c 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -1086,9 +1086,10 @@ public async Task executePayloadV1_transactions_produce_receipts() } } - private async Task> ProduceBranchV1(IEngineRpcModule rpc, + protected async Task> ProduceBranchV1(IEngineRpcModule rpc, MergeTestBlockchain chain, - int count, ExecutionPayload startingParentBlock, bool setHead, Hash256? random = null) + int count, ExecutionPayload startingParentBlock, bool setHead, Hash256? random = null, + ulong slotLength = 12) { List blocks = new(); ExecutionPayload parentBlock = startingParentBlock; @@ -1101,7 +1102,7 @@ private async Task> ProduceBranchV1(IEngineRpcMo for (int i = 0; i < count; i++) { ExecutionPayload? getPayloadResult = await BuildAndGetPayloadOnBranch(rpc, chain, parentHeader, - parentBlock.Timestamp + 12, + parentBlock.Timestamp + slotLength, random ?? TestItem.KeccakA, Address.Zero); PayloadStatusV1 payloadStatusResponse = (await rpc.engine_newPayloadV1(getPayloadResult)).Data; payloadStatusResponse.Status.Should().Be(PayloadStatus.Valid); @@ -1232,7 +1233,7 @@ private async Task BuildAndSendNewBlockV1(IEngineRpcModule rpc return executionPayload; } - private async Task BuildAndGetPayloadOnBranch( + protected async Task BuildAndGetPayloadOnBranch( IEngineRpcModule rpc, MergeTestBlockchain chain, BlockHeader parentHeader, ulong timestamp, Hash256 random, Address feeRecipient) { diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V4.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V4.cs index 3c3874b2f8f..7918db73e3c 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V4.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V4.cs @@ -167,6 +167,42 @@ public virtual async Task Should_process_block_as_expected_V4(string latestValid })); } + + [Test] + public async Task NewPayloadV4_reject_payload_with_bad_authorization_list_rlp() + { + ConsensusRequestsProcessorMock consensusRequestsProcessorMock = new(); + using MergeTestBlockchain chain = await CreateBlockchain(Prague.Instance, null, null, null, consensusRequestsProcessorMock); + IEngineRpcModule rpc = CreateEngineModule(chain); + Hash256 lastHash = (await ProduceBranchV4(rpc, chain, 10, CreateParentBlockRequestOnHead(chain.BlockTree), true)) + .LastOrDefault()?.BlockHash ?? Keccak.Zero; + + Transaction invalidSetCodeTx = Build.A.Transaction + .WithType(TxType.SetCode) + .WithNonce(0) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(100_000) + .WithAuthorizationCode(new JsonRpc.Test.Modules.Eth.EthRpcModuleTests.AllowNullAuthorizationTuple(0, null, 0, new Signature(new byte[65]))) + .WithTo(TestItem.AddressA) + .SignedAndResolved(TestItem.PrivateKeyB).TestObject; + + Block invalidBlock = Build.A.Block + .WithNumber(chain.BlockTree.Head!.Number + 1) + .WithTimestamp(chain.BlockTree.Head!.Timestamp + 12) + .WithTransactions([invalidSetCodeTx]) + .WithParentBeaconBlockRoot(chain.BlockTree.Head!.ParentBeaconBlockRoot) + .WithBlobGasUsed(0) + .WithExcessBlobGas(0) + .TestObject; + + ExecutionPayloadV4 executionPayload = ExecutionPayloadV4.Create(invalidBlock); + + var response = await rpc.engine_newPayloadV4(executionPayload, [], invalidBlock.ParentBeaconBlockRoot); + + Assert.That(response.Data.Status, Is.EqualTo("INVALID")); + } + [TestCase(30)] public async Task can_progress_chain_one_by_one_v4(int count) { @@ -319,9 +355,11 @@ public virtual async Task { Deposit[]? deposits = null; WithdrawalRequest[]? withdrawalRequests = null; + ConsolidationRequest[]? consolidationRequests = null; + if (requests is not null) { - (deposits, withdrawalRequests) = requests.SplitRequests(); + (deposits, withdrawalRequests, consolidationRequests) = requests.SplitRequests(); } ConsensusRequestsProcessorMock consensusRequestsProcessorMock = new(); using MergeTestBlockchain chain = await CreateBlockchain(Prague.Instance, null, null, null, consensusRequestsProcessorMock); @@ -332,9 +370,9 @@ public virtual async Task IEnumerable payloadBodies = rpc.engine_getPayloadBodiesByHashV2(blockHashes).Data; ExecutionPayloadBodyV2Result?[] expected = { - new (Array.Empty(), Array.Empty() , deposits, withdrawalRequests), + new (Array.Empty(), Array.Empty() , deposits, withdrawalRequests, consolidationRequests), null, - new (Array.Empty(), Array.Empty(), deposits, withdrawalRequests), + new (Array.Empty(), Array.Empty(), deposits, withdrawalRequests,consolidationRequests), }; payloadBodies.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering()); } @@ -346,9 +384,11 @@ public virtual async Task { Deposit[]? deposits = null; WithdrawalRequest[]? withdrawalRequests = null; + ConsolidationRequest[]? consolidationRequests = null; + if (requests is not null) { - (deposits, withdrawalRequests) = requests.SplitRequests(); + (deposits, withdrawalRequests, consolidationRequests) = requests.SplitRequests(); } ConsensusRequestsProcessorMock consensusRequestsProcessorMock = new(); @@ -363,7 +403,7 @@ await rpc.engine_forkchoiceUpdatedV3(new ForkchoiceStateV1(executionPayload2.Blo rpc.engine_getPayloadBodiesByRangeV2(1, 3).Result.Data; ExecutionPayloadBodyV2Result?[] expected = { - new (Array.Empty(), Array.Empty() , deposits, withdrawalRequests), + new (Array.Empty(), Array.Empty() , deposits, withdrawalRequests, consolidationRequests), }; payloadBodies.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering()); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/NoBlockImprovementContext.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/NoBlockImprovementContext.cs index 9f76e9791a0..dc23c669140 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/NoBlockImprovementContext.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/NoBlockImprovementContext.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; using Nethermind.Core; +using Nethermind.Consensus.Producers; using Nethermind.Int256; namespace Nethermind.Merge.Plugin.BlockProduction; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PayloadPreparationService.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PayloadPreparationService.cs index 667eee43b37..3f89495b3b0 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PayloadPreparationService.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PayloadPreparationService.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Nethermind.Consensus.Producers; using Nethermind.Core; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs index aeb2bda72fd..61201fbaabc 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs @@ -89,6 +89,12 @@ public byte[][] Transactions /// public virtual WithdrawalRequest[]? WithdrawalRequests { get; set; } + /// + /// Gets or sets a collection of as defined in + /// EIP-7251. + /// + public virtual ConsolidationRequest[]? ConsolidationRequests { get; set; } + /// /// Gets or sets as defined in @@ -226,7 +232,7 @@ public ValidationResult ValidateParams(IReleaseSpec spec, int version, out strin return ValidationResult.Fail; } - if (spec.ConsensusRequestsEnabled) + if (spec.RequestsEnabled) { error = "ExecutionPayloadV4 expected"; return ValidationResult.Fail; @@ -246,7 +252,7 @@ public ValidationResult ValidateParams(IReleaseSpec spec, int version, out strin private int GetExecutionPayloadVersion() => this switch { - { DepositRequests: not null, WithdrawalRequests: not null } => 4, + { DepositRequests: not null, WithdrawalRequests: not null, ConsolidationRequests: not null } => 4, { BlobGasUsed: not null } or { ExcessBlobGas: not null } or { ParentBeaconBlockRoot: not null } => 3, { Withdrawals: not null } => 2, _ => 1 diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV1Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV1Result.cs index 2b20e438649..f6733147ad1 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV1Result.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV1Result.cs @@ -13,7 +13,7 @@ namespace Nethermind.Merge.Plugin.Data; public class ExecutionPayloadBodyV1Result { - public ExecutionPayloadBodyV1Result(IList transactions, IList? withdrawals) + public ExecutionPayloadBodyV1Result(IReadOnlyList transactions, IReadOnlyList? withdrawals) { ArgumentNullException.ThrowIfNull(transactions); @@ -28,8 +28,8 @@ public ExecutionPayloadBodyV1Result(IList transactions, IList Transactions { get; set; } + public IReadOnlyList Transactions { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public IList? Withdrawals { get; set; } + public IReadOnlyList? Withdrawals { get; set; } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV2Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV2Result.cs index 508f900fc55..d929fcf6fd8 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV2Result.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV2Result.cs @@ -11,26 +11,34 @@ namespace Nethermind.Merge.Plugin.Data; public class ExecutionPayloadBodyV2Result : ExecutionPayloadBodyV1Result { public ExecutionPayloadBodyV2Result( - IList transactions, - IList? withdrawals, - IList? deposits, - IList? withdrawalsRequests + IReadOnlyList transactions, + IReadOnlyList? withdrawals, + IReadOnlyList? deposits, + IReadOnlyList? withdrawalsRequests, + IReadOnlyList? consolidationRequests ) : base(transactions, withdrawals) { DepositRequests = deposits; WithdrawalRequests = withdrawalsRequests; + ConsolidationRequests = consolidationRequests; } /// /// Deposit requests . /// [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public IList? DepositRequests { get; set; } + public IReadOnlyList? DepositRequests { get; set; } /// /// Withdrawal requests . /// [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public IList? WithdrawalRequests { get; set; } + public IReadOnlyList? WithdrawalRequests { get; set; } + + /// + /// Consolidation requests . + /// + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public IReadOnlyList? ConsolidationRequests { get; set; } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV4.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV4.cs index cef9b04c3fb..e0d65da1215 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV4.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadV4.cs @@ -25,7 +25,7 @@ public class ExecutionPayloadV4 : ExecutionPayloadV3, IExecutionPayloadFactory(block); ConsensusRequest[]? blockRequests = block.Requests; - (executionPayload.DepositRequests, executionPayload.WithdrawalRequests) = blockRequests?.SplitRequests() ?? ([], []); + (executionPayload.DepositRequests, executionPayload.WithdrawalRequests, executionPayload.ConsolidationRequests) = blockRequests?.SplitRequests() ?? ([], [], []); return executionPayload; } @@ -40,7 +40,8 @@ public override bool TryGetBlock([NotNullWhen(true)] out Block? block, UInt256? var depositsLength = DepositRequests?.Length ?? 0; var withdrawalRequestsLength = WithdrawalRequests?.Length ?? 0; - var requestsCount = depositsLength + withdrawalRequestsLength; + var consolidationRequestsLength = ConsolidationRequests?.Length ?? 0; + var requestsCount = depositsLength + withdrawalRequestsLength + consolidationRequestsLength; if (requestsCount > 0) { var requests = new ConsensusRequest[requestsCount]; @@ -50,11 +51,16 @@ public override bool TryGetBlock([NotNullWhen(true)] out Block? block, UInt256? requests[i] = DepositRequests![i]; } - for (; i < requestsCount; ++i) + for (; i < depositsLength + withdrawalRequestsLength; ++i) { requests[i] = WithdrawalRequests![i - depositsLength]; } + for (; i < requestsCount; ++i) + { + requests[i] = ConsolidationRequests![i - depositsLength - withdrawalRequestsLength]; + } + block.Body.Requests = requests; block.Header.RequestsRoot = new RequestsTrie(requests).RootHash; } @@ -69,7 +75,8 @@ public override bool TryGetBlock([NotNullWhen(true)] out Block? block, UInt256? public override bool ValidateFork(ISpecProvider specProvider) => specProvider.GetSpec(BlockNumber, Timestamp).DepositsEnabled - && specProvider.GetSpec(BlockNumber, Timestamp).WithdrawalRequestsEnabled; + && specProvider.GetSpec(BlockNumber, Timestamp).WithdrawalRequestsEnabled + && specProvider.GetSpec(BlockNumber, Timestamp).ConsolidationRequestsEnabled; /// /// Gets or sets as defined in @@ -84,4 +91,11 @@ public override bool ValidateFork(ISpecProvider specProvider) => /// [JsonRequired] public sealed override WithdrawalRequest[]? WithdrawalRequests { get; set; } + + /// + /// Gets or sets as defined in + /// EIP-7251. + /// + [JsonRequired] + public sealed override ConsolidationRequest[]? ConsolidationRequests { get; set; } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Paris.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Paris.cs index f5c0616b826..4cf84584a76 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Paris.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Paris.cs @@ -41,15 +41,14 @@ protected async Task> ForkchoiceUpdated { if (await _locker.WaitAsync(_timeout)) { - Stopwatch watch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); try { return await _forkchoiceUpdatedV1Handler.Handle(forkchoiceState, payloadAttributes, version); } finally { - watch.Stop(); - Metrics.ForkchoiceUpdedExecutionTime = watch.ElapsedMilliseconds; + Metrics.ForkchoiceUpdedExecutionTime = (long)Stopwatch.GetElapsedTime(startTime).TotalMilliseconds; _locker.Release(); } } @@ -82,7 +81,7 @@ protected async Task> NewPayload(IExecutionPayloa if (await _locker.WaitAsync(_timeout)) { - Stopwatch watch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); try { using IDisposable region = _gcKeeper.TryStartNoGCRegion(); @@ -95,8 +94,7 @@ protected async Task> NewPayload(IExecutionPayloa } finally { - watch.Stop(); - Metrics.NewPayloadExecutionTime = watch.ElapsedMilliseconds; + Metrics.NewPayloadExecutionTime = (long)Stopwatch.GetElapsedTime(startTime).TotalMilliseconds; _locker.Release(); } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/GC/GCKeeper.cs b/src/Nethermind/Nethermind.Merge.Plugin/GC/GCKeeper.cs index e2d3a48957d..e1bbd1eb58c 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/GC/GCKeeper.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/GC/GCKeeper.cs @@ -8,7 +8,6 @@ using FastEnumUtility; using Nethermind.Consensus; using Nethermind.Core.Extensions; -using Nethermind.Core.Memory; using Nethermind.Logging; namespace Nethermind.Merge.Plugin.GC; @@ -150,8 +149,8 @@ private async Task ScheduleGCInternal() { // This should give time to finalize response in Engine API // Normally we should get block every 12s (5s on some chains) - // Lets say we process block in 2s, then delay 500ms, then invoke GC - await Task.Delay(500); + // Lets say we process block in 2s, then delay 125ms, then invoke GC + await Task.Delay(125); if (GCSettings.LatencyMode != GCLatencyMode.NoGCRegion) { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs index e69773391e4..57d1ab5f5bd 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs @@ -49,10 +49,17 @@ public EngineRpcCapabilitiesProvider(ISpecProvider specProvider) #endregion #region Prague - _capabilities[nameof(IEngineRpcModule.engine_getPayloadV4)] = (spec.ConsensusRequestsEnabled, spec.ConsensusRequestsEnabled); - _capabilities[nameof(IEngineRpcModule.engine_newPayloadV4)] = (spec.ConsensusRequestsEnabled, spec.ConsensusRequestsEnabled); - _capabilities[nameof(IEngineRpcModule.engine_getPayloadBodiesByHashV2)] = (spec.ConsensusRequestsEnabled, false); - _capabilities[nameof(IEngineRpcModule.engine_getPayloadBodiesByRangeV2)] = (spec.ConsensusRequestsEnabled, false); + _capabilities[nameof(IEngineRpcModule.engine_getPayloadV4)] = (spec.RequestsEnabled, spec.RequestsEnabled); + _capabilities[nameof(IEngineRpcModule.engine_newPayloadV4)] = (spec.RequestsEnabled, spec.RequestsEnabled); + _capabilities[nameof(IEngineRpcModule.engine_getPayloadBodiesByHashV2)] = (spec.RequestsEnabled, false); + _capabilities[nameof(IEngineRpcModule.engine_getPayloadBodiesByRangeV2)] = (spec.RequestsEnabled, false); + #endregion + + #region Prague + _capabilities[nameof(IEngineRpcModule.engine_getPayloadV4)] = (spec.RequestsEnabled, spec.RequestsEnabled); + _capabilities[nameof(IEngineRpcModule.engine_newPayloadV4)] = (spec.RequestsEnabled, spec.RequestsEnabled); + _capabilities[nameof(IEngineRpcModule.engine_getPayloadBodiesByHashV2)] = (spec.RequestsEnabled, false); + _capabilities[nameof(IEngineRpcModule.engine_getPayloadBodiesByRangeV2)] = (spec.RequestsEnabled, false); #endregion } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByHashV2Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByHashV2Handler.cs index ff64739b948..0c95bbd88b0 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByHashV2Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByHashV2Handler.cs @@ -31,8 +31,8 @@ public class GetPayloadBodiesByHashV2Handler(IBlockTree blockTree, ILogManager l for (int i = 0; i < blockHashes.Count; i++) { Block? block = _blockTree.FindBlock(blockHashes[i]); - (Deposit[]? deposits, WithdrawalRequest[]? withdrawalRequests) = block?.Requests?.SplitRequests() ?? (null, null); - yield return block is null ? null : new ExecutionPayloadBodyV2Result(block.Transactions, block.Withdrawals, deposits, withdrawalRequests); + (Deposit[]? deposits, WithdrawalRequest[]? withdrawalRequests, ConsolidationRequest[]? consolidationRequests) = block?.Requests?.SplitRequests() ?? (null, null, null); + yield return block is null ? null : new ExecutionPayloadBodyV2Result(block.Transactions, block.Withdrawals, deposits, withdrawalRequests, consolidationRequests); } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByRangeV2Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByRangeV2Handler.cs index 4f97a0b800a..f1a3107457d 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByRangeV2Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByRangeV2Handler.cs @@ -42,8 +42,8 @@ public class GetPayloadBodiesByRangeV2Handler(IBlockTree blockTree, ILogManager continue; } - (Deposit[]? deposits, WithdrawalRequest[]? withdrawalRequests) = block!.Requests?.SplitRequests() ?? (null, null); - yield return new ExecutionPayloadBodyV2Result(block.Transactions, block.Withdrawals, deposits, withdrawalRequests); + (Deposit[]? deposits, WithdrawalRequest[]? withdrawalRequests, ConsolidationRequest[]? consolidationRequests) = block!.Requests?.SplitRequests() ?? (null, null, null); + yield return new ExecutionPayloadBodyV2Result(block.Transactions, block.Withdrawals, deposits, withdrawalRequests, consolidationRequests); } yield break; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadHandlerBase.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadHandlerBase.cs index ce3cf321e23..2a85e372d24 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadHandlerBase.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadHandlerBase.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Threading.Tasks; +using Nethermind.Consensus.Producers; using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs index 2cd2defba3e..4c2a3ad5719 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Consensus.Producers; using Nethermind.Core.Specs; using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs index 6d2bdc932b5..49eaad78a0d 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Consensus.Producers; using Nethermind.Core.Specs; using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV3Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV3Handler.cs index 4d0a2f727ba..2b7214b0f81 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV3Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV3Handler.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Consensus.Producers; using System.Linq; using Nethermind.Core.Specs; using Nethermind.Logging; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV4Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV4Handler.cs index 94a16c9ff70..f56e0fef2d5 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV4Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV4Handler.cs @@ -7,6 +7,7 @@ using Nethermind.Merge.Plugin.BlockProduction; using Nethermind.Merge.Plugin.Data; using Nethermind.Consensus.Processing.CensorshipDetector; +using Nethermind.Consensus.Producers; namespace Nethermind.Merge.Plugin.Handlers; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.BlockProducer.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.BlockProducer.cs index d9523f91164..d17d39b5599 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.BlockProducer.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.BlockProducer.cs @@ -44,7 +44,8 @@ public virtual IBlockProducer InitBlockProducer(IBlockProducerFactory baseBlockP ? baseBlockProducerFactory.InitBlockProducer(txSource) : null; _manualTimestamper ??= new ManualTimestamper(); - BlockProducerEnv blockProducerEnv = _api.BlockProducerEnvFactory.Create(); + + BlockProducerEnv blockProducerEnv = _api.BlockProducerEnvFactory.Create(txSource); _api.SealEngine = new MergeSealEngine(_api.SealEngine, _poSSwitcher, _api.SealValidator, _api.LogManager); _api.Sealer = _api.SealEngine; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index cb00c3fafc2..9d387b5d200 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -13,6 +13,7 @@ using Nethermind.Blockchain.Synchronization; using Nethermind.Config; using Nethermind.Consensus; +using Nethermind.Consensus.Producers; using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Validators; using Nethermind.Core; @@ -299,19 +300,20 @@ public Task InitRpcModules() } Thread.Sleep(5000); - IBlockImprovementContextFactory improvementContextFactory; - if (string.IsNullOrEmpty(_mergeConfig.BuilderRelayUrl)) - { - improvementContextFactory = new BlockImprovementContextFactory(_api.BlockProducer!, TimeSpan.FromSeconds(_blocksConfig.SecondsPerSlot)); - } - else + IBlockImprovementContextFactory CreateBlockImprovementContextFactory() { + if (string.IsNullOrEmpty(_mergeConfig.BuilderRelayUrl)) + { + return new BlockImprovementContextFactory(_api.BlockProducer!, TimeSpan.FromSeconds(_blocksConfig.SecondsPerSlot)); + } + DefaultHttpClient httpClient = new(new HttpClient(), _api.EthereumJsonSerializer, _api.LogManager, retryDelayMilliseconds: 100); IBoostRelay boostRelay = new BoostRelay(httpClient, _mergeConfig.BuilderRelayUrl); - BoostBlockImprovementContextFactory boostBlockImprovementContextFactory = new(_api.BlockProducer!, TimeSpan.FromSeconds(_blocksConfig.SecondsPerSlot), boostRelay, _api.StateReader); - improvementContextFactory = boostBlockImprovementContextFactory; + return new BoostBlockImprovementContextFactory(_api.BlockProducer!, TimeSpan.FromSeconds(_blocksConfig.SecondsPerSlot), boostRelay, _api.StateReader); } + IBlockImprovementContextFactory improvementContextFactory = _api.BlockImprovementContextFactory ??= CreateBlockImprovementContextFactory(); + PayloadPreparationService payloadPreparationService = new( _postMergeBlockProducer, improvementContextFactory, diff --git a/src/Nethermind/Nethermind.Merge.Plugin/PoSSwitcher.cs b/src/Nethermind/Nethermind.Merge.Plugin/PoSSwitcher.cs index c71a9663b7b..ff0758cc01b 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/PoSSwitcher.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/PoSSwitcher.cs @@ -161,7 +161,6 @@ public void ForkchoiceUpdated(BlockHeader newHeadHash, Hash256 finalizedHash) { if (_finalizedBlockHash == Keccak.Zero) { - if (_logger.IsInfo) _logger.Info($"Reached the first finalized PoS block FinalizedHash: {finalizedHash}, NewHeadHash: {newHeadHash}"); _blockTree.NewHeadBlock -= CheckIfTerminalBlockReached; } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/MergeBlockDownloader.cs b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/MergeBlockDownloader.cs index 1091473ca26..f0768e39168 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/MergeBlockDownloader.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/MergeBlockDownloader.cs @@ -179,7 +179,7 @@ bool HasMoreToSync(out BlockHeader[]? headers, out int headersToRequest) if (cancellation.IsCancellationRequested) return blocksSynced; // check before every heavy operation - Stopwatch sw = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); await RequestBodies(bestPeer, cancellation, context); if (downloadReceipts) @@ -189,7 +189,7 @@ bool HasMoreToSync(out BlockHeader[]? headers, out int headersToRequest) await RequestReceipts(bestPeer, cancellation, context); } - AdjustSyncBatchSize(sw.Elapsed); + AdjustSyncBatchSize(Stopwatch.GetElapsedTime(startTime)); blocks = context.Blocks; receipts = context.ReceiptsForBlocks; diff --git a/src/Nethermind/Nethermind.Merkleization/Merkle.cs b/src/Nethermind/Nethermind.Merkleization/Merkle.cs index c0494f3eeb1..3567aca3f97 100644 --- a/src/Nethermind/Nethermind.Merkleization/Merkle.cs +++ b/src/Nethermind/Nethermind.Merkleization/Merkle.cs @@ -157,13 +157,13 @@ public static void Ize(out UInt256 root, Root value) } } - public static void Ize(out UInt256 root, Span value) + public static void Ize(out UInt256 root, ReadOnlySpan value) { const int typeSize = 1; int partialChunkLength = value.Length % (32 / typeSize); if (partialChunkLength > 0) { - Span fullChunks = value[..^partialChunkLength]; + ReadOnlySpan fullChunks = value[..^partialChunkLength]; Span lastChunk = stackalloc bool[32 / typeSize]; value[^partialChunkLength..].CopyTo(lastChunk); Ize(out root, MemoryMarshal.Cast(fullChunks), MemoryMarshal.Cast(lastChunk)); @@ -174,13 +174,13 @@ public static void Ize(out UInt256 root, Span value) } } - public static void Ize(out UInt256 root, Span value) + public static void Ize(out UInt256 root, ReadOnlySpan value) { const int typeSize = 1; int partialChunkLength = value.Length % (32 / typeSize); if (partialChunkLength > 0) { - Span fullChunks = value[..^partialChunkLength]; + ReadOnlySpan fullChunks = value[..^partialChunkLength]; Span lastChunk = stackalloc byte[32 / typeSize]; value[^partialChunkLength..].CopyTo(lastChunk); Ize(out root, MemoryMarshal.Cast(fullChunks), MemoryMarshal.Cast(lastChunk)); @@ -288,13 +288,13 @@ private static int ResetLastBit(ref byte lastByte) return 8; } - public static void Ize(out UInt256 root, Span value) + public static void Ize(out UInt256 root, ReadOnlySpan value) { const int typeSize = 2; int partialChunkLength = value.Length % (32 / typeSize); if (partialChunkLength > 0) { - Span fullChunks = value[..^partialChunkLength]; + ReadOnlySpan fullChunks = value[..^partialChunkLength]; Span lastChunk = stackalloc ushort[32 / typeSize]; value[^partialChunkLength..].CopyTo(lastChunk); Ize(out root, MemoryMarshal.Cast(fullChunks), MemoryMarshal.Cast(lastChunk)); @@ -305,13 +305,13 @@ public static void Ize(out UInt256 root, Span value) } } - public static void Ize(out UInt256 root, Span value) + public static void Ize(out UInt256 root, ReadOnlySpan value) { const int typeSize = 4; int partialChunkLength = value.Length % (32 / typeSize); if (partialChunkLength > 0) { - Span fullChunks = value[..^partialChunkLength]; + ReadOnlySpan fullChunks = value[..^partialChunkLength]; Span lastChunk = stackalloc uint[32 / typeSize]; value[^partialChunkLength..].CopyTo(lastChunk); Ize(out root, MemoryMarshal.Cast(fullChunks), MemoryMarshal.Cast(lastChunk)); @@ -322,14 +322,14 @@ public static void Ize(out UInt256 root, Span value) } } - public static void Ize(out UInt256 root, Span value, ulong maxLength = 0U) + public static void Ize(out UInt256 root, ReadOnlySpan value, ulong maxLength = 0U) { const int typeSize = sizeof(ulong); ulong limit = (maxLength * typeSize + 31) / 32; int partialChunkLength = value.Length % (32 / typeSize); if (partialChunkLength > 0) { - Span fullChunks = value[..^partialChunkLength]; + ReadOnlySpan fullChunks = value[..^partialChunkLength]; Span lastChunk = stackalloc ulong[32 / typeSize]; value[^partialChunkLength..].CopyTo(lastChunk); Ize(out root, MemoryMarshal.Cast(fullChunks), MemoryMarshal.Cast(lastChunk), limit); @@ -340,13 +340,13 @@ public static void Ize(out UInt256 root, Span value, ulong maxLength = 0U } } - public static void Ize(out UInt256 root, Span value) + public static void Ize(out UInt256 root, ReadOnlySpan value) { const int typeSize = 16; int partialChunkLength = value.Length % (32 / typeSize); if (partialChunkLength > 0) { - Span fullChunks = value[..^partialChunkLength]; + ReadOnlySpan fullChunks = value[..^partialChunkLength]; Span lastChunk = stackalloc UInt128[32 / typeSize]; value[^partialChunkLength..].CopyTo(lastChunk); Ize(out root, MemoryMarshal.Cast(fullChunks), MemoryMarshal.Cast(lastChunk)); diff --git a/src/Nethermind/Nethermind.Merkleization/Merkleizer.cs b/src/Nethermind/Nethermind.Merkleization/Merkleizer.cs index 1713b34da38..edf48702878 100644 --- a/src/Nethermind/Nethermind.Merkleization/Merkleizer.cs +++ b/src/Nethermind/Nethermind.Merkleization/Merkleizer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Int256; @@ -126,6 +127,25 @@ public void FeedBitlist(BitArray bitArray, ulong maximumBitlistLength) Feed(_chunks[^1]); } + public void Feed(IEnumerable>? value, ulong maxLength) + { + if (value is null) + { + return; + } + + using ArrayPoolList subRoots = new ArrayPoolList((int)maxLength); + foreach (ReadOnlyMemory memory in value) + { + Merkle.Ize(out UInt256 root, memory.Span); + subRoots.Add(root); + } + + Merkle.Ize(out _chunks[^1], subRoots.AsSpan(), maxLength); + Merkle.MixIn(ref _chunks[^1], subRoots.Count); + Feed(_chunks[^1]); + } + //public void Feed(BlsPublicKey? value) //{ // if (value is null) diff --git a/src/Nethermind/Nethermind.Mev.Test/MevMegabundleTests.cs b/src/Nethermind/Nethermind.Mev.Test/MevMegabundleTests.cs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/Nethermind/Nethermind.Mev/MevPlugin.cs b/src/Nethermind/Nethermind.Mev/MevPlugin.cs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/Nethermind/Nethermind.Mev/Source/BundlePool.cs b/src/Nethermind/Nethermind.Mev/Source/BundlePool.cs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs b/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs index 2baef78b6d2..cf9e055a421 100644 --- a/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs +++ b/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs @@ -116,6 +116,7 @@ public void Register_and_update_metrics_should_not_throw_exception() typeof(Synchronization.Metrics), typeof(Trie.Metrics), typeof(Trie.Pruning.Metrics), + typeof(Shutter.Metrics) }; MetricsController metricsController = new(metricsConfig); MonitoringService monitoringService = new(metricsController, metricsConfig, LimboLogs.Instance); diff --git a/src/Nethermind/Nethermind.Network.Benchmark/Eth62ProtocolHandlerBenchmarks.cs b/src/Nethermind/Nethermind.Network.Benchmark/Eth62ProtocolHandlerBenchmarks.cs index 5700d30de35..538ae8f1ff6 100644 --- a/src/Nethermind/Nethermind.Network.Benchmark/Eth62ProtocolHandlerBenchmarks.cs +++ b/src/Nethermind/Nethermind.Network.Benchmark/Eth62ProtocolHandlerBenchmarks.cs @@ -16,6 +16,7 @@ using Nethermind.Core.Timers; using Nethermind.Crypto; using Nethermind.Db; +using Nethermind.Evm; using Nethermind.Logging; using Nethermind.Network.P2P; using Nethermind.Network.P2P.Analyzers; @@ -58,7 +59,7 @@ public void SetUp() TxPool.TxPool txPool = new TxPool.TxPool( ecdsa, new BlobTxStorage(), - new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(MainnetSpecProvider.Instance), tree, stateProvider), + new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(MainnetSpecProvider.Instance), tree, stateProvider, new CodeInfoRepository()), new TxPoolConfig(), new TxValidator(TestBlockchainIds.ChainId), LimboLogs.Instance, diff --git a/src/Nethermind/Nethermind.Network.Discovery.Test/DiscoveryManagerTests.cs b/src/Nethermind/Nethermind.Network.Discovery.Test/DiscoveryManagerTests.cs index f6d36407232..7921d946c1c 100644 --- a/src/Nethermind/Nethermind.Network.Discovery.Test/DiscoveryManagerTests.cs +++ b/src/Nethermind/Nethermind.Network.Discovery.Test/DiscoveryManagerTests.cs @@ -200,7 +200,7 @@ public async Task RateLimitOutgoingMessage() MaxOutgoingMessagePerSecond = 5 }); - Stopwatch sw = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); FindNodeMsg msg = new(_publicKey, 0, Array.Empty()); await _discoveryManager.SendMessageAsync(msg); await _discoveryManager.SendMessageAsync(msg); @@ -208,7 +208,7 @@ public async Task RateLimitOutgoingMessage() await _discoveryManager.SendMessageAsync(msg); await _discoveryManager.SendMessageAsync(msg); await _discoveryManager.SendMessageAsync(msg); - sw.Elapsed.Should().BeGreaterOrEqualTo(TimeSpan.FromSeconds(0.9)); + Stopwatch.GetElapsedTime(startTime).Should().BeGreaterOrEqualTo(TimeSpan.FromSeconds(0.9)); } } } diff --git a/src/Nethermind/Nethermind.Network.Dns.Test/EnrDiscoveryTests.cs b/src/Nethermind/Nethermind.Network.Dns.Test/EnrDiscoveryTests.cs index fb20bb43b14..0c5dc4143ed 100644 --- a/src/Nethermind/Nethermind.Network.Dns.Test/EnrDiscoveryTests.cs +++ b/src/Nethermind/Nethermind.Network.Dns.Test/EnrDiscoveryTests.cs @@ -33,10 +33,10 @@ public async Task Test_enr_discovery(string url) INetworkConfig config = new NetworkConfig(); config.DiscoveryDns = url; EnrDiscovery enrDiscovery = new(new EnrRecordParser(singer), config, testErrorLogManager); - Stopwatch stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); List addedRecords = enrDiscovery.DiscoverNodes(default).ToBlockingEnumerable().ToList(); - await TestContext.Out.WriteLineAsync($"Actually added {addedRecords.Count} in {stopwatch.Elapsed:g}"); + await TestContext.Out.WriteLineAsync($"Actually added {addedRecords.Count} in {Stopwatch.GetElapsedTime(startTime):g}"); foreach (TestErrorLogManager.Error error in testErrorLogManager.Errors) { await TestContext.Out.WriteLineAsync(error.Text); diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/PacketSenderTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/PacketSenderTests.cs index 785f9ef49bc..7d7aa9c3165 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/PacketSenderTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/PacketSenderTests.cs @@ -38,7 +38,6 @@ public void Does_send_on_active_channel() PacketSender packetSender = new(serializer, LimboLogs.Instance, TimeSpan.Zero); packetSender.HandlerAdded(context); packetSender.Enqueue(testMessage); - testMessage.WasDisposed.Should().BeTrue(); context.Received(1).WriteAndFlushAsync(Arg.Any()); } @@ -62,7 +61,6 @@ public void Does_not_try_to_send_on_inactive_channel() PacketSender packetSender = new(serializer, LimboLogs.Instance, TimeSpan.Zero); packetSender.HandlerAdded(context); packetSender.Enqueue(testMessage); - testMessage.WasDisposed.Should().BeTrue(); context.Received(0).WriteAndFlushAsync(Arg.Any()); } @@ -88,7 +86,6 @@ public async Task Send_after_delay_if_specified() PacketSender packetSender = new(serializer, LimboLogs.Instance, delay); packetSender.HandlerAdded(context); packetSender.Enqueue(testMessage); - testMessage.WasDisposed.Should().BeTrue(); await context.Received(0).WriteAndFlushAsync(Arg.Any()); @@ -101,13 +98,6 @@ private class TestMessage : P2PMessage { public override int PacketType { get; } = 0; public override string Protocol { get; } = ""; - - public bool WasDisposed { get; set; } - public override void Dispose() - { - base.Dispose(); - WasDisposed = true; - } } } } diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/SessionTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/SessionTests.cs index 95108ed1533..a9078b29656 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/SessionTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/SessionTests.cs @@ -15,7 +15,6 @@ using Nethermind.Network.Rlpx; using Nethermind.Stats.Model; using NSubstitute; -using NSubstitute.ReceivedExtensions; using NUnit.Framework; namespace Nethermind.Network.Test.P2P; @@ -452,9 +451,11 @@ public void Can_deliver_messages() session.AddProtocolHandler(bbb); session.AddProtocolHandler(ccc); - _packetSender.Enqueue(PingMessage.Instance).Returns(10); - session.DeliverMessage(PingMessage.Instance); - _packetSender.Received().Enqueue(PingMessage.Instance); + var message = new TestMessage(); + _packetSender.Enqueue(message).Returns(10); + session.DeliverMessage(message); + _packetSender.Received().Enqueue(message); + message.WasDisposed.Should().BeTrue(); Metrics.P2PBytesSent.Should().Be(10); } @@ -462,10 +463,12 @@ public void Can_deliver_messages() [Test] public void Cannot_deliver_before_initialized() { + var message = new TestMessage(); Session session = new(30312, new Node(TestItem.PublicKeyA, "127.0.0.1", 8545), _channel, NullDisconnectsAnalyzer.Instance, LimboLogs.Instance); - Assert.Throws(() => session.DeliverMessage(PingMessage.Instance)); + Assert.Throws(() => session.DeliverMessage(message)); session.Handshake(TestItem.PublicKeyA); - Assert.Throws(() => session.DeliverMessage(PingMessage.Instance)); + Assert.Throws(() => session.DeliverMessage(message)); + message.WasDisposed.Should().BeTrue(); session.Init(5, _channelHandlerContext, _packetSender); IProtocolHandler p2p = BuildHandler("p2p", 10); session.AddProtocolHandler(p2p); @@ -494,8 +497,10 @@ public void Stops_delivering_messages_after_disconnect() session.InitiateDisconnect(DisconnectReason.Other); - session.DeliverMessage(PingMessage.Instance); - _packetSender.DidNotReceive().Enqueue(Arg.Any()); + var message = new TestMessage(); + session.DeliverMessage(message); + _packetSender.DidNotReceive().Enqueue(Arg.Any()); + message.WasDisposed.Should().BeTrue(); } [Test] @@ -538,10 +543,11 @@ public void Protocol_handler_can_send_message_on_disconnect() IProtocolHandler p2p = BuildHandler("p2p", 10); session.AddProtocolHandler(p2p); + var message = new TestMessage(); p2p.When(it => it.DisconnectProtocol(Arg.Any(), Arg.Any())) .Do((_) => { - session.DeliverMessage(PingMessage.Instance); + session.DeliverMessage(message); }); session.Init(5, _channelHandlerContext, _packetSender); @@ -549,7 +555,9 @@ public void Protocol_handler_can_send_message_on_disconnect() _packetSender .Received() - .Enqueue(PingMessage.Instance); + .Enqueue(message); + + message.WasDisposed.Should().BeTrue(); } [Test, Retry(3)] @@ -619,4 +627,17 @@ public void Updates_local_and_remote_metrics_on_disconnects() Assert.That(afterLocal, Is.EqualTo(beforeLocal)); Assert.That(afterRemote, Is.EqualTo(beforeRemote + 1)); } + + private class TestMessage : P2PMessage + { + public override int PacketType => P2PMessageCode.Ping; + public override string Protocol => "p2p"; + + public bool WasDisposed { get; set; } + public override void Dispose() + { + base.Dispose(); + WasDisposed = true; + } + } } diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/ReceiptsMessageSerializerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/ReceiptsMessageSerializerTests.cs index 25b26f580bc..568a7b3b652 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/ReceiptsMessageSerializerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/ReceiptsMessageSerializerTests.cs @@ -34,7 +34,7 @@ public void RoundTrip() txReceipt.GasUsedTotal.Should().Be(1); txReceipt.Bloom.Should().Be(Bloom.Empty); - txReceipt.Logs[0].LoggersAddress.Should().BeEquivalentTo(new Address("0x0000000000000000000000000000000000000011")); + txReceipt.Logs[0].Address.Should().BeEquivalentTo(new Address("0x0000000000000000000000000000000000000011")); txReceipt.Logs[0].Topics[0].Should().BeEquivalentTo(new Hash256("0x000000000000000000000000000000000000000000000000000000000000dead")); txReceipt.Logs[0].Topics[1].Should().BeEquivalentTo(new Hash256("0x000000000000000000000000000000000000000000000000000000000000beef")); txReceipt.Logs[0].Data.Should().BeEquivalentTo(Bytes.FromHexString("0x0100ff")); diff --git a/src/Nethermind/Nethermind.Network/CompositeNodeSource.cs b/src/Nethermind/Nethermind.Network/CompositeNodeSource.cs index a56329f4731..1402ebcf725 100644 --- a/src/Nethermind/Nethermind.Network/CompositeNodeSource.cs +++ b/src/Nethermind/Nethermind.Network/CompositeNodeSource.cs @@ -20,7 +20,8 @@ public async IAsyncEnumerable DiscoverNodes([EnumeratorCancellation] Cance { Channel ch = Channel.CreateBounded(1); - Task[] feedTasks = _nodeSources.Select(async (innerSource) => + // TODO: .Net 9 stackalloc + Task[] feedTasks = _nodeSources.Select(async innerSource => { await foreach (Node node in innerSource.DiscoverNodes(cancellationToken)) { diff --git a/src/Nethermind/Nethermind.Network/P2P/PacketSender.cs b/src/Nethermind/Nethermind.Network/P2P/PacketSender.cs index 2201fb73fd2..3248eb98115 100644 --- a/src/Nethermind/Nethermind.Network/P2P/PacketSender.cs +++ b/src/Nethermind/Nethermind.Network/P2P/PacketSender.cs @@ -27,20 +27,12 @@ public PacketSender(IMessageSerializationService messageSerializationService, IL public int Enqueue(T message) where T : P2PMessage { - IByteBuffer buffer; - try + if (!_context.Channel.IsWritable || !_context.Channel.Active) { - if (!_context.Channel.IsWritable || !_context.Channel.Active) - { - return 0; - } - - buffer = _messageSerializationService.ZeroSerialize(message); - } - finally - { - message.Dispose(); + return 0; } + + IByteBuffer buffer = _messageSerializationService.ZeroSerialize(message); int length = buffer.ReadableBytes; // Running in background diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/P2PProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/P2PProtocolHandler.cs index e5a3db6b9d6..b0c3c5d1615 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/P2PProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/P2PProtocolHandler.cs @@ -271,7 +271,7 @@ public async Task SendPing() if (Logger.IsTrace) Logger.Trace($"{Session} P2P sending ping on {Session.RemotePort} ({RemoteClientId})"); Send(PingMessage.Instance); _nodeStatsManager.ReportEvent(Session.Node, NodeStatsEventType.P2PPingOut); - Stopwatch stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); CancellationTokenSource delayCancellation = new(); try @@ -286,7 +286,7 @@ public async Task SendPing() return false; } - long latency = stopwatch.ElapsedMilliseconds; + long latency = (long)Stopwatch.GetElapsedTime(startTime).TotalMilliseconds; _nodeStatsManager.ReportTransferSpeedEvent(Session.Node, TransferSpeedType.Latency, latency); return true; } diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs index b47db24a5ac..e930beb17dc 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs @@ -283,7 +283,7 @@ private void SendMessage(IOwnedReadOnlyList txsToSend) protected async Task Handle(GetBlockHeadersMessage getBlockHeadersMessage, CancellationToken cancellationToken) { using GetBlockHeadersMessage message = getBlockHeadersMessage; - Stopwatch stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); if (Logger.IsTrace) { Logger.Trace($"Received headers request from {Session.Node:c}:"); @@ -309,8 +309,7 @@ protected async Task Handle(GetBlockHeadersMessage getBlock // } BlockHeadersMessage resp = await FulfillBlockHeadersRequest(message, cancellationToken); - stopwatch.Stop(); - if (Logger.IsTrace) Logger.Trace($"OUT {Counter:D5} BlockHeaders to {Node:c} in {stopwatch.Elapsed.TotalMilliseconds}ms"); + if (Logger.IsTrace) Logger.Trace($"OUT {Counter:D5} BlockHeaders to {Node:c} in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); return resp; } @@ -343,12 +342,11 @@ protected async Task Handle(GetBlockBodiesMessage request, C Logger.Trace($"Received bodies request of length {message.BlockHashes.Count} from {Session.Node:c}:"); } - Stopwatch stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); Interlocked.Increment(ref Counter); BlockBodiesMessage resp = await FulfillBlockBodiesRequest(message, cancellationToken); - stopwatch.Stop(); - if (Logger.IsTrace) Logger.Trace($"OUT {Counter:D5} BlockBodies to {Node:c} in {stopwatch.Elapsed.TotalMilliseconds}ms"); + if (Logger.IsTrace) Logger.Trace($"OUT {Counter:D5} BlockBodies to {Node:c} in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); return resp; } @@ -391,10 +389,9 @@ protected async Task Handle(GetReceiptsMessage msg, Cancellatio throw new EthSyncException("Incoming receipts request for more than 512 blocks"); } - Stopwatch stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); ReceiptsMessage resp = await FulfillReceiptsRequest(message, cancellationToken); - stopwatch.Stop(); - if (Logger.IsTrace) Logger.Trace($"OUT {Counter:D5} Receipts to {Node:c} in {stopwatch.Elapsed.TotalMilliseconds}ms"); + if (Logger.IsTrace) Logger.Trace($"OUT {Counter:D5} Receipts to {Node:c} in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); return resp; } diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs index 8bf24cc1ac3..9a4e07a900e 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs @@ -52,29 +52,39 @@ CancellationToken token ) { Task task = request.CompletionSource.Task; - using CancellationTokenSource delayCancellation = new(); - using CancellationTokenSource compositeCancellation = - CancellationTokenSource.CreateLinkedTokenSource(token, delayCancellation.Token); - Task firstTask = await Task.WhenAny(task, Task.Delay(Timeouts.Eth, compositeCancellation.Token)); - if (firstTask.IsCanceled) + bool success = false; + try { - token.ThrowIfCancellationRequested(); - } + using CancellationTokenSource delayCancellation = new(); + using CancellationTokenSource compositeCancellation = CancellationTokenSource.CreateLinkedTokenSource(token, delayCancellation.Token); + Task firstTask = await Task.WhenAny(task, Task.Delay(Timeouts.Eth, compositeCancellation.Token)); + if (firstTask.IsCanceled) + { + token.ThrowIfCancellationRequested(); + } - if (firstTask == task) - { - delayCancellation.Cancel(); - long elapsed = request.FinishMeasuringTime(); - long bytesPerMillisecond = (long)((decimal)request.ResponseSize / Math.Max(1, elapsed)); - if (Logger.IsTrace) Logger.Trace($"{this} speed is {request.ResponseSize}/{elapsed} = {bytesPerMillisecond}"); - StatsManager.ReportTransferSpeedEvent(Session.Node, speedType, bytesPerMillisecond); + if (firstTask == task) + { + await delayCancellation.CancelAsync(); + long elapsed = request.FinishMeasuringTime(); + long bytesPerMillisecond = (long)((decimal)request.ResponseSize / Math.Max(1, elapsed)); + if (Logger.IsTrace) Logger.Trace($"{this} speed is {request.ResponseSize}/{elapsed} = {bytesPerMillisecond}"); + StatsManager.ReportTransferSpeedEvent(Session.Node, speedType, bytesPerMillisecond); - return await task; - } + success = true; + return await task; + } - CleanupTimeoutTask(task); - StatsManager.ReportTransferSpeedEvent(Session.Node, speedType, 0L); - throw new TimeoutException($"{Session} Request timeout in {describeRequestFunc(request.Message)}"); + StatsManager.ReportTransferSpeedEvent(Session.Node, speedType, 0L); + throw new TimeoutException($"{Session} Request timeout in {describeRequestFunc(request.Message)}"); + } + finally + { + if (!success) + { + CleanupTimeoutTask(task); + } + } } private static void CleanupTimeoutTask(Task task) diff --git a/src/Nethermind/Nethermind.Network/P2P/Session.cs b/src/Nethermind/Nethermind.Network/P2P/Session.cs index f092fd41ce2..2efaa613de7 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Session.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Session.cs @@ -207,31 +207,38 @@ public void ReceiveMessage(ZeroPacket zeroPacket) public int DeliverMessage(T message) where T : P2PMessage { - lock (_sessionStateLock) + try { - if (State < SessionState.Initialized) + lock (_sessionStateLock) { - throw new InvalidOperationException($"{nameof(DeliverMessage)} called {this}"); - } + if (State < SessionState.Initialized) + { + throw new InvalidOperationException($"{nameof(DeliverMessage)} called {this}"); + } - // Must allow sending out packet when `DisconnectingProtocols` so that we can send out disconnect reason - // and hello (part of protocol) - if (IsClosed) - { - return 1; + // Must allow sending out packet when `DisconnectingProtocols` so that we can send out disconnect reason + // and hello (part of protocol) + if (IsClosed) + { + return 1; + } } - } - if (_logger.IsTrace) _logger.Trace($"P2P to deliver {message.Protocol}.{message.PacketType} on {this}"); + if (_logger.IsTrace) _logger.Trace($"P2P to deliver {message.Protocol}.{message.PacketType} on {this}"); - message.AdaptivePacketType = _resolver.ResolveAdaptiveId(message.Protocol, message.PacketType); - int size = _packetSender.Enqueue(message); + message.AdaptivePacketType = _resolver.ResolveAdaptiveId(message.Protocol, message.PacketType); + int size = _packetSender.Enqueue(message); - RecordOutgoingMessageMetric(message, size); + RecordOutgoingMessageMetric(message, size); - Interlocked.Add(ref Metrics.P2PBytesSent, size); + Interlocked.Add(ref Metrics.P2PBytesSent, size); - return size; + return size; + } + finally + { + message.Dispose(); + } } public void ReceiveMessage(Packet packet) diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs index 65fd4af43d8..c037fe6b16e 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs @@ -59,7 +59,7 @@ private class BlockBodyDecoder : IRlpValueDecoder private readonly TxDecoder _txDecoder = TxDecoder.Instance; private readonly HeaderDecoder _headerDecoder = new(); private readonly WithdrawalDecoder _withdrawalDecoderDecoder = new(); - private readonly ConsensusRequestDecoder _requestsDecoder = new(); + private readonly ConsensusRequestDecoder _requestsDecoder = ConsensusRequestDecoder.Instance; public int GetLength(BlockBody item, RlpBehaviors rlpBehaviors) { diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Eth63ProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Eth63ProtocolHandler.cs index be1d225effb..a3f07a11fa4 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Eth63ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Eth63ProtocolHandler.cs @@ -106,10 +106,9 @@ private async Task Handle(GetNodeDataMessage msg, CancellationT { using var message = msg; - Stopwatch stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); NodeDataMessage response = await FulfillNodeDataRequest(message, cancellationToken); - stopwatch.Stop(); - if (Logger.IsTrace) Logger.Trace($"OUT {Counter:D5} NodeData to {Node:c} in {stopwatch.Elapsed.TotalMilliseconds}ms"); + if (Logger.IsTrace) Logger.Trace($"OUT {Counter:D5} NodeData to {Node:c} in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); return response; } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Eth65ProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Eth65ProtocolHandler.cs index a15a71193f5..b322f6905ed 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Eth65ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Eth65ProtocolHandler.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; @@ -97,15 +98,14 @@ protected virtual void Handle(NewPooledTransactionHashesMessage msg) using var _ = msg; AddNotifiedTransactions(msg.Hashes); - Stopwatch stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); TxPool.Metrics.PendingTransactionsHashesReceived += msg.Hashes.Count; _pooledTxsRequestor.RequestTransactions(Send, msg.Hashes); - stopwatch.Stop(); if (Logger.IsTrace) Logger.Trace($"OUT {Counter:D5} {nameof(NewPooledTransactionHashesMessage)} to {Node:c} " + - $"in {stopwatch.Elapsed.TotalMilliseconds}ms"); + $"in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); } protected void AddNotifiedTransactions(IReadOnlyList hashes) @@ -119,12 +119,11 @@ protected void AddNotifiedTransactions(IReadOnlyList hashes) private async ValueTask Handle(GetPooledTransactionsMessage msg, CancellationToken cancellationToken) { using var message = msg; - var stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); Send(await FulfillPooledTransactionsRequest(message, cancellationToken)); - stopwatch.Stop(); if (Logger.IsTrace) Logger.Trace($"OUT {Counter:D5} {nameof(GetPooledTransactionsMessage)} to {Node:c} " + - $"in {stopwatch.Elapsed.TotalMilliseconds}ms"); + $"in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); } internal Task FulfillPooledTransactionsRequest(GetPooledTransactionsMessage msg, CancellationToken cancellationToken) diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Eth66ProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Eth66ProtocolHandler.cs index 95595b3b5c0..265feceb2c0 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Eth66ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Eth66ProtocolHandler.cs @@ -194,15 +194,14 @@ protected override void Handle(NewPooledTransactionHashesMessage msg) { using var message = msg; bool isTrace = Logger.IsTrace; - Stopwatch? stopwatch = isTrace ? Stopwatch.StartNew() : null; + long startTime = Stopwatch.GetTimestamp(); TxPool.Metrics.PendingTransactionsHashesReceived += message.Hashes.Count; _pooledTxsRequestor.RequestTransactionsEth66(_sendAction, message.Hashes); - stopwatch?.Stop(); if (isTrace) Logger.Trace($"OUT {Counter:D5} {nameof(NewPooledTransactionHashesMessage)} to {Node:c} " + - $"in {stopwatch.Elapsed.TotalMilliseconds}ms"); + $"in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"); } protected override async Task> SendRequest(V62.Messages.GetBlockHeadersMessage message, CancellationToken token) diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandler.cs index 15c0ace7a19..4dae6ba58b9 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandler.cs @@ -94,13 +94,11 @@ private void Handle(NewPooledTransactionHashesMessage68 msg) AddNotifiedTransactions(message.Hashes); - Stopwatch? stopwatch = isTrace ? Stopwatch.StartNew() : null; + long startTime = isTrace ? Stopwatch.GetTimestamp() : 0; _pooledTxsRequestor.RequestTransactionsEth68(_sendAction, message.Hashes, message.Sizes, message.Types); - stopwatch?.Stop(); - - if (isTrace) Logger.Trace($"OUT {Counter:D5} {nameof(NewPooledTransactionHashesMessage68)} to {Node:c} in {stopwatch.Elapsed.TotalMilliseconds}ms"); + if (isTrace) Logger.Trace($"OUT {Counter:D5} {nameof(NewPooledTransactionHashesMessage68)} to {Node:c} in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds}ms"); } protected override void SendNewTransactionCore(Transaction tx) diff --git a/src/Nethermind/Nethermind.Optimism/InitializeBlockchainOptimism.cs b/src/Nethermind/Nethermind.Optimism/InitializeBlockchainOptimism.cs index 32eaa382de9..d4bbd47eed1 100644 --- a/src/Nethermind/Nethermind.Optimism/InitializeBlockchainOptimism.cs +++ b/src/Nethermind/Nethermind.Optimism/InitializeBlockchainOptimism.cs @@ -15,6 +15,7 @@ using Nethermind.Evm.TransactionProcessing; using Nethermind.Init.Steps; using Nethermind.Merge.Plugin.InvalidChainTracker; +using Nethermind.TxPool; namespace Nethermind.Optimism; @@ -103,4 +104,7 @@ protected override IHealthHintService CreateHealthHintService() => new ManualHealthHintService(_blocksConfig.SecondsPerSlot * 6, HealthHintConstants.InfinityHint); protected override IBlockProductionPolicy CreateBlockProductionPolicy() => AlwaysStartBlockProductionPolicy.Instance; + + protected override ITxPool CreateTxPool(CodeInfoRepository codeInfoRepository) => + api.Config().SequencerUrl is not null ? NullTxPool.Instance : base.CreateTxPool(codeInfoRepository); } diff --git a/src/Nethermind/Nethermind.Optimism/OptimismEthereumEcdsa.cs b/src/Nethermind/Nethermind.Optimism/OptimismEthereumEcdsa.cs index f70db353bf7..16c2e6b8cf8 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismEthereumEcdsa.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismEthereumEcdsa.cs @@ -12,25 +12,13 @@ public class OptimismEthereumEcdsa : Ecdsa, IEthereumEcdsa { private readonly IEthereumEcdsa _ethereumEcdsa; + public ulong ChainId => _ethereumEcdsa.ChainId; + public OptimismEthereumEcdsa(IEthereumEcdsa ethereumEcdsa) { _ethereumEcdsa = ethereumEcdsa; } - - public void Sign(PrivateKey privateKey, Transaction tx, bool isEip155Enabled = true) => _ethereumEcdsa.Sign(privateKey, tx, isEip155Enabled); - - public Address? RecoverAddress(Transaction tx, bool useSignatureChainId = false) - { - if (tx.Signature is null && tx.IsOPSystemTransaction) - { - return Address.Zero; - } - return _ethereumEcdsa.RecoverAddress(tx, useSignatureChainId); - } - public Address? RecoverAddress(Signature signature, Hash256 message) => _ethereumEcdsa.RecoverAddress(signature, message); public Address? RecoverAddress(Span signatureBytes, Hash256 message) => _ethereumEcdsa.RecoverAddress(signatureBytes, message); - - public bool Verify(Address sender, Transaction tx) => _ethereumEcdsa.Verify(sender, tx); } diff --git a/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs b/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs index d21a3350ed3..58dab4bab85 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs @@ -65,10 +65,11 @@ protected override TransactionResult Execute(Transaction tx, in BlockExecutionCo } protected override TransactionResult BuyGas(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts, - in UInt256 effectiveGasPrice, out UInt256 premiumPerGas, out UInt256 senderReservedGasPayment) + in UInt256 effectiveGasPrice, out UInt256 premiumPerGas, out UInt256 senderReservedGasPayment, out UInt256 blobBaseFee) { premiumPerGas = UInt256.Zero; senderReservedGasPayment = UInt256.Zero; + blobBaseFee = UInt256.Zero; bool validate = !opts.HasFlag(ExecutionOptions.NoValidation); @@ -136,12 +137,12 @@ protected override TransactionResult ValidateSender(Transaction tx, BlockHeader tx.IsDeposit() ? TransactionResult.Ok : base.ValidateSender(tx, header, spec, tracer, opts); protected override void PayFees(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, - in TransactionSubstate substate, in long spentGas, in UInt256 premiumPerGas, in byte statusCode) + in TransactionSubstate substate, in long spentGas, in UInt256 premiumPerGas, in UInt256 blobGasFee, in byte statusCode) { if (!tx.IsDeposit()) { // Skip coinbase payments for deposit tx in Regolith - base.PayFees(tx, header, spec, tracer, substate, spentGas, premiumPerGas, statusCode); + base.PayFees(tx, header, spec, tracer, substate, spentGas, premiumPerGas, blobGasFee, statusCode); if (opSpecHelper.IsBedrock(header)) { @@ -152,7 +153,7 @@ protected override void PayFees(Transaction tx, BlockHeader header, IReleaseSpec } protected override long Refund(Transaction tx, BlockHeader header, IReleaseSpec spec, ExecutionOptions opts, - in TransactionSubstate substate, in long unspentGas, in UInt256 gasPrice) + in TransactionSubstate substate, in long unspentGas, in UInt256 gasPrice, int refunds) { // if deposit: skip refunds, skip tipping coinbase // Regolith changes this behaviour to report the actual gasUsed instead of always reporting all gas used. @@ -163,6 +164,6 @@ protected override long Refund(Transaction tx, BlockHeader header, IReleaseSpec return tx.IsOPSystemTransaction ? 0 : tx.GasLimit; } - return base.Refund(tx, header, spec, opts, substate, unspentGas, gasPrice); + return base.Refund(tx, header, spec, opts, substate, unspentGas, gasPrice, refunds); } } diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/RegisterOptimismRpcModules.cs b/src/Nethermind/Nethermind.Optimism/Rpc/RegisterOptimismRpcModules.cs index 04da5fca22e..a53257f1ec3 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/RegisterOptimismRpcModules.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/RegisterOptimismRpcModules.cs @@ -51,13 +51,12 @@ protected override void RegisterEthRpcModule(IRpcModuleProvider rpcModuleProvide if (_config.SequencerUrl is null && _logger.IsWarn) { - _logger.Warn($"SequencerUrl is not set. Nethermind will behave as a Sequencer"); + _logger.Warn("SequencerUrl is not set. Nethermind will behave as a Sequencer"); } - BasicJsonRpcClient? sequencerJsonRpcClient = _config.SequencerUrl is null - ? null - : new(new Uri(_config.SequencerUrl), _api.EthereumJsonSerializer, _api.LogManager); - ModuleFactoryBase ethModuleFactory = CreateEthModuleFactory(); + BasicJsonRpcClient? sequencerJsonRpcClient = _config.SequencerUrl is not null + ? new(new Uri(_config.SequencerUrl), _api.EthereumJsonSerializer, _api.LogManager) + : null; ITxSigner txSigner = new WalletTxSigner(_api.Wallet, _api.SpecProvider.ChainId); TxSealer sealer = new(txSigner, _api.Timestamper); diff --git a/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG1AddBenchmark.cs b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG1AddBenchmark.cs new file mode 100644 index 00000000000..26733025c3d --- /dev/null +++ b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG1AddBenchmark.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Evm.Precompiles; +using Nethermind.Evm.Precompiles.Bls; + +namespace Nethermind.Precompiles.Benchmark; + +public class BlsG1AddBenchmark : PrecompileBenchmarkBase +{ + protected override IEnumerable Precompiles => new[] + { + G1AddPrecompile.Instance + }; + + protected override string InputsDirectory => "blsg1add"; +} diff --git a/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG1MSMBenchmark.cs b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG1MSMBenchmark.cs new file mode 100644 index 00000000000..ebd52dddb05 --- /dev/null +++ b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG1MSMBenchmark.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Evm.Precompiles; +using Nethermind.Evm.Precompiles.Bls; + +namespace Nethermind.Precompiles.Benchmark; + +public class BlsG1MSMBenchmark : PrecompileBenchmarkBase +{ + protected override IEnumerable Precompiles => new[] + { + G1MSMPrecompile.Instance + }; + + protected override string InputsDirectory => "blsg1msm"; +} diff --git a/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG1MulBenchmark.cs b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG1MulBenchmark.cs new file mode 100644 index 00000000000..7d1adc95338 --- /dev/null +++ b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG1MulBenchmark.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Evm.Precompiles; +using Nethermind.Evm.Precompiles.Bls; + +namespace Nethermind.Precompiles.Benchmark; + +public class BlsG1MulBenchmark : PrecompileBenchmarkBase +{ + protected override IEnumerable Precompiles => new[] + { + G1MulPrecompile.Instance + }; + + protected override string InputsDirectory => "blsg1mul"; +} diff --git a/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG2AddBenchmark.cs b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG2AddBenchmark.cs new file mode 100644 index 00000000000..d89edf3e771 --- /dev/null +++ b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG2AddBenchmark.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Evm.Precompiles; +using Nethermind.Evm.Precompiles.Bls; + +namespace Nethermind.Precompiles.Benchmark; + +public class BlsG2AddBenchmark : PrecompileBenchmarkBase +{ + protected override IEnumerable Precompiles => new[] + { + G2AddPrecompile.Instance + }; + + protected override string InputsDirectory => "blsg2add"; +} diff --git a/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG2MSMBenchmark.cs b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG2MSMBenchmark.cs new file mode 100644 index 00000000000..59efd92b36f --- /dev/null +++ b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG2MSMBenchmark.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Evm.Precompiles; +using Nethermind.Evm.Precompiles.Bls; + +namespace Nethermind.Precompiles.Benchmark; + +public class BlsG2MSMBenchmark : PrecompileBenchmarkBase +{ + protected override IEnumerable Precompiles => new[] + { + G2MSMPrecompile.Instance + }; + + protected override string InputsDirectory => "blsg2msm"; +} diff --git a/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG2MulBenchmark.cs b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG2MulBenchmark.cs new file mode 100644 index 00000000000..6df853a9778 --- /dev/null +++ b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsG2MulBenchmark.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Evm.Precompiles; +using Nethermind.Evm.Precompiles.Bls; + +namespace Nethermind.Precompiles.Benchmark; + +public class BlsG2MulBenchmark : PrecompileBenchmarkBase +{ + protected override IEnumerable Precompiles => new[] + { + G2MulPrecompile.Instance + }; + + protected override string InputsDirectory => "blsg2mul"; +} diff --git a/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsMapFp2ToG2Benchmark.cs b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsMapFp2ToG2Benchmark.cs new file mode 100644 index 00000000000..ff903b6e1e7 --- /dev/null +++ b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsMapFp2ToG2Benchmark.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Evm.Precompiles; +using Nethermind.Evm.Precompiles.Bls; + +namespace Nethermind.Precompiles.Benchmark; + +public class BlsMapFp2ToG2Benchmark : PrecompileBenchmarkBase +{ + protected override IEnumerable Precompiles => new[] + { + MapFp2ToG2Precompile.Instance + }; + + protected override string InputsDirectory => "blsmapfp2tog2"; +} diff --git a/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsMapFpToG1Benchmark.cs b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsMapFpToG1Benchmark.cs new file mode 100644 index 00000000000..0f2fc9bad0b --- /dev/null +++ b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsMapFpToG1Benchmark.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Evm.Precompiles; +using Nethermind.Evm.Precompiles.Bls; + +namespace Nethermind.Precompiles.Benchmark; + +public class BlsMapFpToG1Benchmark : PrecompileBenchmarkBase +{ + protected override IEnumerable Precompiles => new[] + { + MapFpToG1Precompile.Instance + }; + + protected override string InputsDirectory => "blsmapfptog1"; +} diff --git a/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsPairingCheckBenchmark.cs b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsPairingCheckBenchmark.cs new file mode 100644 index 00000000000..23c7933af1f --- /dev/null +++ b/src/Nethermind/Nethermind.Precompiles.Benchmark/BlsPairingCheckBenchmark.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Evm.Precompiles; +using Nethermind.Evm.Precompiles.Bls; + +namespace Nethermind.Precompiles.Benchmark; + +public class BlsPairingCheckBenchmark : PrecompileBenchmarkBase +{ + protected override IEnumerable Precompiles => new[] + { + PairingCheckPrecompile.Instance + }; + + protected override string InputsDirectory => "blspairingcheck"; +} diff --git a/src/Nethermind/Nethermind.Precompiles.Benchmark/Nethermind.Precompiles.Benchmark.csproj b/src/Nethermind/Nethermind.Precompiles.Benchmark/Nethermind.Precompiles.Benchmark.csproj index c7d79384acf..1fa7275f368 100644 --- a/src/Nethermind/Nethermind.Precompiles.Benchmark/Nethermind.Precompiles.Benchmark.csproj +++ b/src/Nethermind/Nethermind.Precompiles.Benchmark/Nethermind.Precompiles.Benchmark.csproj @@ -18,10 +18,6 @@ %(RecursiveDir)%(FileName)%(Extension) PreserveNewest - - - PreserveNewest - diff --git a/src/Nethermind/Nethermind.Precompiles.Benchmark/modexp/current/file.json b/src/Nethermind/Nethermind.Precompiles.Benchmark/modexp/current/file.json deleted file mode 100644 index a7fc3a30973..00000000000 --- a/src/Nethermind/Nethermind.Precompiles.Benchmark/modexp/current/file.json +++ /dev/null @@ -1,142 +0,0 @@ -[ - { - "Input": "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002003fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", - "Expected": "0000000000000000000000000000000000000000000000000000000000000001", - "Name": "eip_example1", - "Gas": 13056, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", - "Expected": "0000000000000000000000000000000000000000000000000000000000000000", - "Name": "eip_example2", - "Gas": 13056, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb502fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", - "Expected": "60008f1614cc01dcfb6bfb09c625cf90b47d4468db81b5f8b7a39d42f332eab9b2da8f2d95311648a8f243f4bb13cfb3d8f7f2a3c014122ebb3ed41b02783adc", - "Name": "nagydani-1-square", - "Gas": 204, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb503fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", - "Expected": "4834a46ba565db27903b1c720c9d593e84e4cbd6ad2e64b31885d944f68cd801f92225a8961c952ddf2797fa4701b330c85c4b363798100b921a1a22a46a7fec", - "Name": "nagydani-1-qube", - "Gas": 204, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000040e09ad9675465c53a109fac66a445c91b292d2bb2c5268addb30cd82f80fcb0033ff97c80a5fc6f39193ae969c6ede6710a6b7ac27078a06d90ef1c72e5c85fb5010001fc9e1f6beb81516545975218075ec2af118cd8798df6e08a147c60fd6095ac2bb02c2908cf4dd7c81f11c289e4bce98f3553768f392a80ce22bf5c4f4a248c6b", - "Expected": "c36d804180c35d4426b57b50c5bfcca5c01856d104564cd513b461d3c8b8409128a5573e416d0ebe38f5f736766d9dc27143e4da981dfa4d67f7dc474cbee6d2", - "Name": "nagydani-1-pow0x10001", - "Gas": 3276, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5102e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", - "Expected": "981dd99c3b113fae3e3eaa9435c0dc96779a23c12a53d1084b4f67b0b053a27560f627b873e3f16ad78f28c94f14b6392def26e4d8896c5e3c984e50fa0b3aa44f1da78b913187c6128baa9340b1e9c9a0fd02cb78885e72576da4a8f7e5a113e173a7a2889fde9d407bd9f06eb05bc8fc7b4229377a32941a02bf4edcc06d70", - "Name": "nagydani-2-square", - "Gas": 665, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf5103e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", - "Expected": "d89ceb68c32da4f6364978d62aaa40d7b09b59ec61eb3c0159c87ec3a91037f7dc6967594e530a69d049b64adfa39c8fa208ea970cfe4b7bcd359d345744405afe1cbf761647e32b3184c7fbe87cee8c6c7ff3b378faba6c68b83b6889cb40f1603ee68c56b4c03d48c595c826c041112dc941878f8c5be828154afd4a16311f", - "Name": "nagydani-2-qube", - "Gas": 665, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000080cad7d991a00047dd54d3399b6b0b937c718abddef7917c75b6681f40cc15e2be0003657d8d4c34167b2f0bbbca0ccaa407c2a6a07d50f1517a8f22979ce12a81dcaf707cc0cebfc0ce2ee84ee7f77c38b9281b9822a8d3de62784c089c9b18dcb9a2a5eecbede90ea788a862a9ddd9d609c2c52972d63e289e28f6a590ffbf51010001e6d893b80aeed5e6e9ce9afa8a5d5675c93a32ac05554cb20e9951b2c140e3ef4e433068cf0fb73bc9f33af1853f64aa27a0028cbf570d7ac9048eae5dc7b28c87c31e5810f1e7fa2cda6adf9f1076dbc1ec1238560071e7efc4e9565c49be9e7656951985860a558a754594115830bcdb421f741408346dd5997bb01c287087", - "Expected": "ad85e8ef13fd1dd46eae44af8b91ad1ccae5b7a1c92944f92a19f21b0b658139e0cabe9c1f679507c2de354bf2c91ebd965d1e633978a830d517d2f6f8dd5fd58065d58559de7e2334a878f8ec6992d9b9e77430d4764e863d77c0f87beede8f2f7f2ab2e7222f85cc9d98b8467f4bb72e87ef2882423ebdb6daf02dddac6db2", - "Name": "nagydani-2-pow0x10001", - "Gas": 10649, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb02d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", - "Expected": "affc7507ea6d84751ec6b3f0d7b99dbcc263f33330e450d1b3ff0bc3d0874320bf4edd57debd587306988157958cb3cfd369cc0c9c198706f635c9e0f15d047df5cb44d03e2727f26b083c4ad8485080e1293f171c1ed52aef5993a5815c35108e848c951cf1e334490b4a539a139e57b68f44fee583306f5b85ffa57206b3ee5660458858534e5386b9584af3c7f67806e84c189d695e5eb96e1272d06ec2df5dc5fabc6e94b793718c60c36be0a4d031fc84cd658aa72294b2e16fc240aef70cb9e591248e38bd49c5a554d1afa01f38dab72733092f7555334bbef6c8c430119840492380aa95fa025dcf699f0a39669d812b0c6946b6091e6e235337b6f8", - "Name": "nagydani-3-square", - "Gas": 1894, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb03d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", - "Expected": "1b280ecd6a6bf906b806d527c2a831e23b238f89da48449003a88ac3ac7150d6a5e9e6b3be4054c7da11dd1e470ec29a606f5115801b5bf53bc1900271d7c3ff3cd5ed790d1c219a9800437a689f2388ba1a11d68f6a8e5b74e9a3b1fac6ee85fc6afbac599f93c391f5dc82a759e3c6c0ab45ce3f5d25d9b0c1bf94cf701ea6466fc9a478dacc5754e593172b5111eeba88557048bceae401337cd4c1182ad9f700852bc8c99933a193f0b94cf1aedbefc48be3bc93ef5cb276d7c2d5462ac8bb0c8fe8923a1db2afe1c6b90d59c534994a6a633f0ead1d638fdc293486bb634ff2c8ec9e7297c04241a61c37e3ae95b11d53343d4ba2b4cc33d2cfa7eb705e", - "Name": "nagydani-3-qube", - "Gas": 1894, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000100c9130579f243e12451760976261416413742bd7c91d39ae087f46794062b8c239f2a74abf3918605a0e046a7890e049475ba7fbb78f5de6490bd22a710cc04d30088179a919d86c2da62cf37f59d8f258d2310d94c24891be2d7eeafaa32a8cb4b0cfe5f475ed778f45907dc8916a73f03635f233f7a77a00a3ec9ca6761a5bbd558a2318ecd0caa1c5016691523e7e1fa267dd35e70c66e84380bdcf7c0582f540174e572c41f81e93da0b757dff0b0fe23eb03aa19af0bdec3afb474216febaacb8d0381e631802683182b0fe72c28392539850650b70509f54980241dc175191a35d967288b532a7a8223ce2440d010615f70df269501944d4ec16fe4a3cb010001d7a85909174757835187cb52e71934e6c07ef43b4c46fc30bbcd0bc72913068267c54a4aabebb493922492820babdeb7dc9b1558fcf7bd82c37c82d3147e455b623ab0efa752fe0b3a67ca6e4d126639e645a0bf417568adbb2a6a4eef62fa1fa29b2a5a43bebea1f82193a7dd98eb483d09bb595af1fa9c97c7f41f5649d976aee3e5e59e2329b43b13bea228d4a93f16ba139ccb511de521ffe747aa2eca664f7c9e33da59075cc335afcd2bf3ae09765f01ab5a7c3e3938ec168b74724b5074247d200d9970382f683d6059b94dbc336603d1dfee714e4b447ac2fa1d99ecb4961da2854e03795ed758220312d101e1e3d87d5313a6d052aebde75110363d", - "Expected": "37843d7c67920b5f177372fa56e2a09117df585f81df8b300fba245b1175f488c99476019857198ed459ed8d9799c377330e49f4180c4bf8e8f66240c64f65ede93d601f957b95b83efdee1e1bfde74169ff77002eaf078c71815a9220c80b2e3b3ff22c2f358111d816ebf83c2999026b6de50bfc711ff68705d2f40b753424aefc9f70f08d908b5a20276ad613b4ab4309a3ea72f0c17ea9df6b3367d44fb3acab11c333909e02e81ea2ed404a712d3ea96bba87461720e2d98723e7acd0520ac1a5212dbedcd8dc0c1abf61d4719e319ff4758a774790b8d463cdfe131d1b2dcfee52d002694e98e720cb6ae7ccea353bc503269ba35f0f63bf8d7b672a76", - "Name": "nagydani-3-pow0x10001", - "Gas": 30310, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8102df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", - "Expected": "8a5aea5f50dcc03dc7a7a272b5aeebc040554dbc1ffe36753c4fc75f7ed5f6c2cc0de3a922bf96c78bf0643a73025ad21f45a4a5cadd717612c511ab2bff1190fe5f1ae05ba9f8fe3624de1de2a817da6072ddcdb933b50216811dbe6a9ca79d3a3c6b3a476b079fd0d05f04fb154e2dd3e5cb83b148a006f2bcbf0042efb2ae7b916ea81b27aac25c3bf9a8b6d35440062ad8eae34a83f3ffa2cc7b40346b62174a4422584f72f95316f6b2bee9ff232ba9739301c97c99a9ded26c45d72676eb856ad6ecc81d36a6de36d7f9dafafee11baa43a4b0d5e4ecffa7b9b7dcefd58c397dd373e6db4acd2b2c02717712e6289bed7c813b670c4a0c6735aa7f3b0f1ce556eae9fcc94b501b2c8781ba50a8c6220e8246371c3c7359fe4ef9da786ca7d98256754ca4e496be0a9174bedbecb384bdf470779186d6a833f068d2838a88d90ef3ad48ff963b67c39cc5a3ee123baf7bf3125f64e77af7f30e105d72c4b9b5b237ed251e4c122c6d8c1405e736299c3afd6db16a28c6a9cfa68241e53de4cd388271fe534a6a9b0dbea6171d170db1b89858468885d08fecbd54c8e471c3e25d48e97ba450b96d0d87e00ac732aaa0d3ce4309c1064bd8a4c0808a97e0143e43a24cfa847635125cd41c13e0574487963e9d725c01375db99c31da67b4cf65eff555f0c0ac416c727ff8d438ad7c42030551d68c2e7adda0abb1ca7c10", - "Name": "nagydani-4-square", - "Gas": 5580, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b8103df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", - "Expected": "5a2664252aba2d6e19d9600da582cdd1f09d7a890ac48e6b8da15ae7c6ff1856fc67a841ac2314d283ffa3ca81a0ecf7c27d89ef91a5a893297928f5da0245c99645676b481b7e20a566ee6a4f2481942bee191deec5544600bb2441fd0fb19e2ee7d801ad8911c6b7750affec367a4b29a22942c0f5f4744a4e77a8b654da2a82571037099e9c6d930794efe5cdca73c7b6c0844e386bdca8ea01b3d7807146bb81365e2cdc6475f8c23e0ff84463126189dc9789f72bbce2e3d2d114d728a272f1345122de23df54c922ec7a16e5c2a8f84da8871482bd258c20a7c09bbcd64c7a96a51029bbfe848736a6ba7bf9d931a9b7de0bcaf3635034d4958b20ae9ab3a95a147b0421dd5f7ebff46c971010ebfc4adbbe0ad94d5498c853e7142c450d8c71de4b2f84edbf8acd2e16d00c8115b150b1c30e553dbb82635e781379fe2a56360420ff7e9f70cc64c00aba7e26ed13c7c19622865ae07248daced36416080f35f8cc157a857ed70ea4f347f17d1bee80fa038abd6e39b1ba06b97264388b21364f7c56e192d4b62d9b161405f32ab1e2594e86243e56fcf2cb30d21adef15b9940f91af681da24328c883d892670c6aa47940867a81830a82b82716895db810df1b834640abefb7db2092dd92912cb9a735175bc447be40a503cf22dfe565b4ed7a3293ca0dfd63a507430b323ee248ec82e843b673c97ad730728cebc", - "Name": "nagydani-4-qube", - "Gas": 5580, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000200db34d0e438249c0ed685c949cc28776a05094e1c48691dc3f2dca5fc3356d2a0663bd376e4712839917eb9a19c670407e2c377a2de385a3ff3b52104f7f1f4e0c7bf7717fb913896693dc5edbb65b760ef1b00e42e9d8f9af17352385e1cd742c9b006c0f669995cb0bb21d28c0aced2892267637b6470d8cee0ab27fc5d42658f6e88240c31d6774aa60a7ebd25cd48b56d0da11209f1928e61005c6eb709f3e8e0aaf8d9b10f7d7e296d772264dc76897ccdddadc91efa91c1903b7232a9e4c3b941917b99a3bc0c26497dedc897c25750af60237aa67934a26a2bc491db3dcc677491944bc1f51d3e5d76b8d846a62db03dedd61ff508f91a56d71028125035c3a44cbb041497c83bf3e4ae2a9613a401cc721c547a2afa3b16a2969933d3626ed6d8a7428648f74122fd3f2a02a20758f7f693892c8fd798b39abac01d18506c45e71432639e9f9505719ee822f62ccbf47f6850f096ff77b5afaf4be7d772025791717dbe5abf9b3f40cff7d7aab6f67e38f62faf510747276e20a42127e7500c444f9ed92baf65ade9e836845e39c4316d9dce5f8e2c8083e2c0acbb95296e05e51aab13b6b8f53f06c9c4276e12b0671133218cc3ea907da3bd9a367096d9202128d14846cc2e20d56fc8473ecb07cecbfb8086919f3971926e7045b853d85a69d026195c70f9f7a823536e2a8f4b3e12e94d9b53a934353451094b81010001df3143a0057457d75e8c708b6337a6f5a4fd1a06727acf9fb93e2993c62f3378b37d56c85e7b1e00f0145ebf8e4095bd723166293c60b6ac1252291ef65823c9e040ddad14969b3b340a4ef714db093a587c37766d68b8d6b5016e741587e7e6bf7e763b44f0247e64bae30f994d248bfd20541a333e5b225ef6a61199e301738b1e688f70ec1d7fb892c183c95dc543c3e12adf8a5e8b9ca9d04f9445cced3ab256f29e998e69efaa633a7b60e1db5a867924ccab0a171d9d6e1098dfa15acde9553de599eaa56490c8f411e4985111f3d40bddfc5e301edb01547b01a886550a61158f7e2033c59707789bf7c854181d0c2e2a42a93cf09209747d7082e147eb8544de25c3eb14f2e35559ea0c0f5877f2f3fc92132c0ae9da4e45b2f6c866a224ea6d1f28c05320e287750fbc647368d41116e528014cc1852e5531d53e4af938374daba6cee4baa821ed07117253bb3601ddd00d59a3d7fb2ef1f5a2fbba7c429f0cf9a5b3462410fd833a69118f8be9c559b1000cc608fd877fb43f8e65c2d1302622b944462579056874b387208d90623fcdaf93920ca7a9e4ba64ea208758222ad868501cc2c345e2d3a5ea2a17e5069248138c8a79c0251185d29ee73e5afab5354769142d2bf0cb6712727aa6bf84a6245fcdae66e4938d84d1b9dd09a884818622080ff5f98942fb20acd7e0c916c2d5ea7ce6f7e173315384518f", - "Expected": "bed8b970c4a34849fc6926b08e40e20b21c15ed68d18f228904878d4370b56322d0da5789da0318768a374758e6375bfe4641fca5285ec7171828922160f48f5ca7efbfee4d5148612c38ad683ae4e3c3a053d2b7c098cf2b34f2cb19146eadd53c86b2d7ccf3d83b2c370bfb840913ee3879b1057a6b4e07e110b6bcd5e958bc71a14798c91d518cc70abee264b0d25a4110962a764b364ac0b0dd1ee8abc8426d775ec0f22b7e47b32576afaf1b5a48f64573ed1c5c29f50ab412188d9685307323d990802b81dacc06c6e05a1e901830ba9fcc67688dc29c5e27bde0a6e845ca925f5454b6fb3747edfaa2a5820838fb759eadf57f7cb5cec57fc213ddd8a4298fa079c3c0f472b07fb15aa6a7f0a3780bd296ff6a62e58ef443870b02260bd4fd2bbc98255674b8e1f1f9f8d33c7170b0ebbea4523b695911abbf26e41885344823bd0587115fdd83b721a4e8457a31c9a84b3d3520a07e0e35df7f48e5a9d534d0ec7feef1ff74de6a11e7f93eab95175b6ce22c68d78a642ad642837897ec11349205d8593ac19300207572c38d29ca5dfa03bc14cdbc32153c80e5cc3e739403d34c75915e49beb43094cc6dcafb3665b305ddec9286934ae66ec6b777ca528728c851318eb0f207b39f1caaf96db6eeead6b55ed08f451939314577d42bcc9f97c0b52d0234f88fd07e4c1d7780fdebc025cfffcb572cb27a8c33963", - "Name": "nagydani-4-pow0x10001", - "Gas": 89292, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf02e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", - "Expected": "d61fe4e3f32ac260915b5b03b78a86d11bfc41d973fce5b0cc59035cf8289a8a2e3878ea15fa46565b0d806e2f85b53873ea20ed653869b688adf83f3ef444535bf91598ff7e80f334fb782539b92f39f55310cc4b35349ab7b278346eda9bc37c0d8acd3557fae38197f412f8d9e57ce6a76b7205c23564cab06e5615be7c6f05c3d05ec690cba91da5e89d55b152ff8dd2157dc5458190025cf94b1ad98f7cbe64e9482faba95e6b33844afc640892872b44a9932096508f4a782a4805323808f23e54b6ff9b841dbfa87db3505ae4f687972c18ea0f0d0af89d36c1c2a5b14560c153c3fee406f5cf15cfd1c0bb45d767426d465f2f14c158495069d0c5955a00150707862ecaae30624ebacdd8ac33e4e6aab3ff90b6ba445a84689386b9e945d01823a65874444316e83767290fcff630d2477f49d5d8ffdd200e08ee1274270f86ed14c687895f6caf5ce528bd970c20d2408a9ba66216324c6a011ac4999098362dbd98a038129a2d40c8da6ab88318aa3046cb660327cc44236d9e5d2163bd0959062195c51ed93d0088b6f92051fc99050ece2538749165976233697ab4b610385366e5ce0b02ad6b61c168ecfbedcdf74278a38de340fd7a5fead8e588e294795f9b011e2e60377a89e25c90e145397cdeabc60fd32444a6b7642a611a83c464d8b8976666351b4865c37b02e6dc21dbcdf5f930341707b618cc0f03c3122646b3385c9df9f2ec730eec9d49e7dfc9153b6e6289da8c4f0ebea9ccc1b751948e3bb7171c9e4d57423b0eeeb79095c030cb52677b3f7e0b45c30f645391f3f9c957afa549c4e0b2465b03c67993cd200b1af01035962edbc4c9e89b31c82ac121987d6529dafdeef67a132dc04b6dc68e77f22862040b75e2ceb9ff16da0fca534e6db7bd12fa7b7f51b6c08c1e23dfcdb7acbd2da0b51c87ffbced065a612e9b1c8bba9b7e2d8d7a2f04fcc4aaf355b60d764879a76b5e16762d5f2f55d585d0c8e82df6940960cddfb72c91dfa71f6b4e1c6ca25dfc39a878e998a663c04fe29d5e83b9586d047b4d7ff70a9f0d44f127e7d741685ca75f11629128d916a0ffef4be586a30c4b70389cc746e84ebf177c01ee8a4511cfbb9d1ecf7f7b33c7dd8177896e10bbc82f838dcd6db7ac67de62bf46b6a640fb580c5d1d2708f3862e3d2b645d0d18e49ef088053e3a220adc0e033c2afcfe61c90e32151152eb3caaf746c5e377d541cafc6cbb0cc0fa48b5caf1728f2e1957f5addfc234f1a9d89e40d49356c9172d0561a695fce6dab1d412321bbf407f63766ffd7b6b3d79bcfa07991c5a9709849c1008689e3b47c50d613980bec239fb64185249d055b30375ccb4354d71fe4d05648fbf6c80634dfc3575f2f24abb714c1e4c95e8896763bf4316e954c7ad19e5780ab7a040ca6fb9271f90a8b22ae738daf6cb", - "Name": "nagydani-5-square", - "Gas": 17868, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf03e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", - "Expected": "5f9c70ec884926a89461056ad20ac4c30155e817f807e4d3f5bb743d789c83386762435c3627773fa77da5144451f2a8aad8adba88e0b669f5377c5e9bad70e45c86fe952b613f015a9953b8a5de5eaee4566acf98d41e327d93a35bd5cef4607d025e58951167957df4ff9b1627649d3943805472e5e293d3efb687cfd1e503faafeb2840a3e3b3f85d016051a58e1c9498aab72e63b748d834b31eb05d85dcde65e27834e266b85c75cc4ec0135135e0601cb93eeeb6e0010c8ceb65c4c319623c5e573a2c8c9fbbf7df68a930beb412d3f4dfd146175484f45d7afaa0d2e60684af9b34730f7c8438465ad3e1d0c3237336722f2aa51095bd5759f4b8ab4dda111b684aa3dac62a761722e7ae43495b7709933512c81c4e3c9133a51f7ce9f2b51fcec064f65779666960b4e45df3900f54311f5613e8012dd1b8efd359eda31a778264c72aa8bb419d862734d769076bce2810011989a45374e5c5d8729fec21427f0bf397eacbb4220f603cf463a4b0c94efd858ffd9768cd60d6ce68d755e0fbad007ce5c2223d70c7018345a102e4ab3c60a13a9e7794303156d4c2063e919f2153c13961fb324c80b240742f47773a7a8e25b3e3fb19b00ce839346c6eb3c732fbc6b888df0b1fe0a3d07b053a2e9402c267b2d62f794d8a2840526e3ade15ce2264496ccd7519571dfde47f7a4bb16292241c20b2be59f3f8fb4f6383f232d838c5a22d8c95b6834d9d2ca493f5a505ebe8899503b0e8f9b19e6e2dd81c1628b80016d02097e0134de51054c4e7674824d4d758760fc52377d2cad145e259aa2ffaf54139e1a66b1e0c1c191e32ac59474c6b526f5b3ba07d3e5ec286eddf531fcd5292869be58c9f22ef91026159f7cf9d05ef66b4299f4da48cc1635bf2243051d342d378a22c83390553e873713c0454ce5f3234397111ac3fe3207b86f0ed9fc025c81903e1748103692074f83824fda6341be4f95ff00b0a9a208c267e12fa01825054cc0513629bf3dbb56dc5b90d4316f87654a8be18227978ea0a8a522760cad620d0d14fd38920fb7321314062914275a5f99f677145a6979b156bd82ecd36f23f8e1273cc2759ecc0b2c69d94dad5211d1bed939dd87ed9e07b91d49713a6e16ade0a98aea789f04994e318e4ff2c8a188cd8d43aeb52c6daa3bc29b4af50ea82a247c5cd67b573b34cbadcc0a376d3bbd530d50367b42705d870f2e27a8197ef46070528bfe408360faa2ebb8bf76e9f388572842bcb119f4d84ee34ae31f5cc594f23705a49197b181fb78ed1ec99499c690f843a4d0cf2e226d118e9372271054fbabdcc5c92ae9fefaef0589cd0e722eaf30c1703ec4289c7fd81beaa8a455ccee5298e31e2080c10c366a6fcf56f7d13582ad0bcad037c612b710fc595b70fbefaaca23623b60c6c39b11beb8e5843b6b3dac60f", - "Name": "nagydani-5-qube", - "Gas": 17868, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000400c5a1611f8be90071a43db23cc2fe01871cc4c0e8ab5743f6378e4fef77f7f6db0095c0727e20225beb665645403453e325ad5f9aeb9ba99bf3c148f63f9c07cf4fe8847ad5242d6b7d4499f93bd47056ddab8f7dee878fc2314f344dbee2a7c41a5d3db91eff372c730c2fdd3a141a4b61999e36d549b9870cf2f4e632c4d5df5f024f81c028000073a0ed8847cfb0593d36a47142f578f05ccbe28c0c06aeb1b1da027794c48db880278f79ba78ae64eedfea3c07d10e0562668d839749dc95f40467d15cf65b9cfc52c7c4bcef1cda3596dd52631aac942f146c7cebd46065131699ce8385b0db1874336747ee020a5698a3d1a1082665721e769567f579830f9d259cec1a836845109c21cf6b25da572512bf3c42fd4b96e43895589042ab60dd41f497db96aec102087fe784165bb45f942859268fd2ff6c012d9d00c02ba83eace047cc5f7b2c392c2955c58a49f0338d6fc58749c9db2155522ac17914ec216ad87f12e0ee95574613942fa615898c4d9e8a3be68cd6afa4e7a003dedbdf8edfee31162b174f965b20ae752ad89c967b3068b6f722c16b354456ba8e280f987c08e0a52d40a2e8f3a59b94d590aeef01879eb7a90b3ee7d772c839c85519cbeaddc0c193ec4874a463b53fcaea3271d80ebfb39b33489365fc039ae549a17a9ff898eea2f4cb27b8dbee4c17b998438575b2b8d107e4a0d66ba7fca85b41a58a8d51f191a35c856dfbe8aef2b00048a694bbccff832d23c8ca7a7ff0b6c0b3011d00b97c86c0628444d267c951d9e4fb8f83e154b8f74fb51aa16535e498235c5597dac9606ed0be3173a3836baa4e7d756ffe1e2879b415d3846bccd538c05b847785699aefde3e305decb600cd8fb0e7d8de5efc26971a6ad4e6d7a2d91474f1023a0ac4b78dc937da0ce607a45974d2cac1c33a2631ff7fe6144a3b2e5cf98b531a9627dea92c1dc82204d09db0439b6a11dd64b484e1263aa45fd9539b6020b55e3baece3986a8bffc1003406348f5c61265099ed43a766ee4f93f5f9c5abbc32a0fd3ac2b35b87f9ec26037d88275bd7dd0a54474995ee34ed3727f3f97c48db544b1980193a4b76a8a3ddab3591ce527f16d91882e67f0103b5cda53f7da54d489fc4ac08b6ab358a5a04aa9daa16219d50bd672a7cb804ed769d218807544e5993f1c27427104b349906a0b654df0bf69328afd3013fbe430155339c39f236df5557bf92f1ded7ff609a8502f49064ec3d1dbfb6c15d3a4c11a4f8acd12278cbf68acd5709463d12e3338a6eddb8c112f199645e23154a8e60879d2a654e3ed9296aa28f134168619691cd2c6b9e2eba4438381676173fc63c2588a3c5910dc149cf3760f0aa9fa9c3f5faa9162b0bf1aac9dd32b706a60ef53cbdb394b6b40222b5bc80eea82ba8958386672564cae3794f977871ab62337cf010001e30049201ec12937e7ce79d0f55d9c810e20acf52212aca1d3888949e0e4830aad88d804161230eb89d4d329cc83570fe257217d2119134048dd2ed167646975fc7d77136919a049ea74cf08ddd2b896890bb24a0ba18094a22baa351bf29ad96c66bbb1a598f2ca391749620e62d61c3561a7d3653ccc8892c7b99baaf76bf836e2991cb06d6bc0514568ff0d1ec8bb4b3d6984f5eaefb17d3ea2893722375d3ddb8e389a8eef7d7d198f8e687d6a513983df906099f9a2d23f4f9dec6f8ef2f11fc0a21fac45353b94e00486f5e17d386af42502d09db33cf0cf28310e049c07e88682aeeb00cb833c5174266e62407a57583f1f88b304b7c6e0c84bbe1c0fd423072d37a5bd0aacf764229e5c7cd02473460ba3645cd8e8ae144065bf02d0dd238593d8e230354f67e0b2f23012c23274f80e3ee31e35e2606a4a3f31d94ab755e6d163cff52cbb36b6d0cc67ffc512aeed1dce4d7a0d70ce82f2baba12e8d514dc92a056f994adfb17b5b9712bd5186f27a2fda1f7039c5df2c8587fdc62f5627580c13234b55be4df3056050e2d1ef3218f0dd66cb05265fe1acfb0989d8213f2c19d1735a7cf3fa65d88dad5af52dc2bba22b7abf46c3bc77b5091baab9e8f0ddc4d5e581037de91a9f8dcbc69309be29cc815cf19a20a7585b8b3073edf51fc9baeb3e509b97fa4ecfd621e0fd57bd61cac1b895c03248ff12bdbc57509250df3517e8a3fe1d776836b34ab352b973d932ef708b14f7418f9eceb1d87667e61e3e758649cb083f01b133d37ab2f5afa96d6c84bcacf4efc3851ad308c1e7d9113624fce29fab460ab9d2a48d92cdb281103a5250ad44cb2ff6e67ac670c02fdafb3e0f1353953d6d7d5646ca1568dea55275a050ec501b7c6250444f7219f1ba7521ba3b93d089727ca5f3bbe0d6c1300b423377004954c5628fdb65770b18ced5c9b23a4a5a6d6ef25fe01b4ce278de0bcc4ed86e28a0a68818ffa40970128cf2c38740e80037984428c1bd5113f40ff47512ee6f4e4d8f9b8e8e1b3040d2928d003bd1c1329dc885302fbce9fa81c23b4dc49c7c82d29b52957847898676c89aa5d32b5b0e1c0d5a2b79a19d67562f407f19425687971a957375879d90c5f57c857136c17106c9ab1b99d80e69c8c954ed386493368884b55c939b8d64d26f643e800c56f90c01079d7c534e3b2b7ae352cefd3016da55f6a85eb803b85e2304915fd2001f77c74e28746293c46e4f5f0fd49cf988aafd0026b8e7a3bab2da5cdce1ea26c2e29ec03f4807fac432662b2d6c060be1c7be0e5489de69d0a6e03a4b9117f9244b34a0f1ecba89884f781c6320412413a00c4980287409a2a78c2cd7e65cecebbe4ec1c28cac4dd95f6998e78fc6f1392384331c9436aa10e10e2bf8ad2c4eafbcf276aa7bae64b74428911b3269c749338b0fc5075ad", - "Expected": "5a0eb2bdf0ac1cae8e586689fa16cd4b07dfdedaec8a110ea1fdb059dd5253231b6132987598dfc6e11f86780428982d50cf68f67ae452622c3b336b537ef3298ca645e8f89ee39a26758206a5a3f6409afc709582f95274b57b71fae5c6b74619ae6f089a5393c5b79235d9caf699d23d88fb873f78379690ad8405e34c19f5257d596580c7a6a7206a3712825afe630c76b31cdb4a23e7f0632e10f14f4e282c81a66451a26f8df2a352b5b9f607a7198449d1b926e27036810368e691a74b91c61afa73d9d3b99453e7c8b50fd4f09c039a2f2feb5c419206694c31b92df1d9586140cb3417b38d0c503c7b508cc2ed12e813a1c795e9829eb39ee78eeaf360a169b491a1d4e419574e712402de9d48d54c1ae5e03739b7156615e8267e1fb0a897f067afd11fb33f6e24182d7aaaaa18fe5bc1982f20d6b871e5a398f0f6f718181d31ec225cfa9a0a70124ed9a70031bdf0c1c7829f708b6e17d50419ef361cf77d99c85f44607186c8d683106b8bd38a49b5d0fb503b397a83388c5678dcfcc737499d84512690701ed621a6f0172aecf037184ddf0f2453e4053024018e5ab2e30d6d5363b56e8b41509317c99042f517247474ab3abc848e00a07f69c254f46f2a05cf6ed84e5cc906a518fdcfdf2c61ce731f24c5264f1a25fc04934dc28aec112134dd523f70115074ca34e3807aa4cb925147f3a0ce152d323bd8c675ace446d0fd1ae30c4b57f0eb2c23884bc18f0964c0114796c5b6d080c3d89175665fbf63a6381a6a9da39ad070b645c8bb1779506da14439a9f5b5d481954764ea114fac688930bc68534d403cff4210673b6a6ff7ae416b7cd41404c3d3f282fcd193b86d0f54d0006c2a503b40d5c3930da980565b8f9630e9493a79d1c03e74e5f93ac8e4dc1a901ec5e3b3e57049124c7b72ea345aa359e782285d9e6a5c144a378111dd02c40855ff9c2be9b48425cb0b2fd62dc8678fd151121cf26a65e917d65d8e0dacfae108eb5508b601fb8ffa370be1f9a8b749a2d12eeab81f41079de87e2d777994fa4d28188c579ad327f9957fb7bdecec5c680844dd43cb57cf87aeb763c003e65011f73f8c63442df39a92b946a6bd968a1c1e4d5fa7d88476a68bd8e20e5b70a99259c7d3f85fb1b65cd2e93972e6264e74ebf289b8b6979b9b68a85cd5b360c1987f87235c3c845d62489e33acf85d53fa3561fe3a3aee18924588d9c6eba4edb7a4d106b31173e42929f6f0c48c80ce6a72d54eca7c0fe870068b7a7c89c63cdda593f5b32d3cb4ea8a32c39f00ab449155757172d66763ed9527019d6de6c9f2416aa6203f4d11c9ebee1e1d3845099e55504446448027212616167eb36035726daa7698b075286f5379cd3e93cb3e0cf4f9cb8d017facbb5550ed32d5ec5400ae57e47e2bf78d1eaeff9480cc765ceff39db500", - "Name": "nagydani-5-pow0x10001", - "Gas": 285900, - "NoBenchmark": false - }, - { - "Input": "0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000409aaf0cd43ee868a92194c346bac7d5551e97439fb92163e38fbd2699ece0e817a8d70b67a7d39c975e9490645464d21a60e59eb4c1bc00784c294581c1cfbd41acab9ee2bb6dae4afaac402591ff8e2aeb0b3806413456ebd6931b7e8e1bd58ed74eb7a8c6ef5ff33754b9147bdbe74d9a1a96b597b6ff7855b20c285230bbded6b14e4247e664fbac45cfed7a4170fbabcee373851ff7204f3f5313e236a00798db3ec98ca406b4b69a0951e712fd1341117c844c4859b6ba2df30792501876", - "Expected": "97e3edb32d968a33cae520a37fe5cada129980885801e54cc5b45ae2f544711074408dd98174ddfe5b920222091dfc672e59f5001ef526f315726c9597a2f7397", - "Name": "random-1", - "Gas": 10901, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100a562755a0b5a3c0dce5d2f99a3dadc496848d9894565eda1ebbe03a1219a253b561718ba823e8781bff41af6ba2910e9dc98ab32c866674075d46e4d579aeb3337f3438bc121c72ac058ca504bf7e6c2ad19c588eff273efb0d845aecf3a2a2408e6bfc6e58528bad18072709b16f57dedac9f3dd56343448053dae9dc195a862da647643820dc28f10ab76f80025499e2c7c0239bed4fa5a37a4592aa3fdeb725eb43f5ce9145cdcf774ccaabd3c37eff4006145937ea4056a2c0bdda4a7db22cff4994be5ecb5181f35423a085f9cfb7c7926938c95eee01039f2cd088f284f99a9dac8a2ebfa075b49266e3c69fd1a8f10613b8137962a05b93bbbf9e81f900cf2906e74d458807f1f38337f80b3a63ce83de82decce6203a0b2e9a03e1d7339965139c98e95bab7d0cc61ff52ba84f5957f11aa277210243e5ad4116ed03101c509d92f79dcbe5273304d206c62a0f63df41acefbc9022441159b33b1a958f08b502a58d1557bb8c0cf26f724efb7dba9ede37ab572160bf7b4ee64418a90d9d75ca51c385814d87861fe3c921aff515dbf89d3b5de3b093a2e611c671777463e5d1fd84aab9351c90b025383e969dcb59d1681f0bd1409119bd209e6f86150fab7fe450b2706b22c280aaacbd78550b48456da00e545713906b6aec9896ad099a168dbeb24d19e866465c8a2793d3a0681556291975005628d0bf46067e892cd3a012a7eb9758fb2c843ac8f7ccecfd98ca84834083f025153695e28ed2e2e7bc30b786812cb5d0bc5afd953b385e5be4c1ae3bfa06eb50b0ec91b401766c5aa2520bc715667b6c14b3c7e5865ae9ddd839fdcc24a6edb64467d5949a00cf45eea3f3e82d96467d464227463f06c95603b4dcc7789f35fb655583ad91e6d114e481f7e36700af41f38086e27fa6773e9d7f91e62407cc4e6df783d81398ec5f701261bd0ee6ee02049ad4f819537bbedd44101255eaefddb5a876707dd72e5a9327716c45b92fb6908fe28a33d335197bc5face88da098522840866707e852c6b7a65f1e20e7924155981c50865aae1735df4b7dd5fd1b91deb4ac1b1c9", - "Expected": "4aa808af385b8f843aaa63bff77929bcfab802cd8725c696668f00bc1e339438c2613892f1a9469d9b35c18234f33a6c6de477f0e716dcbc763fd2ff134a970576d92b587d0451f92bc47b6fcb0f7f67ddcfc7888868d8df3bf0126556cdbd262b334a15a661116882e211e8a22414172c6b016296b578b246fd967be5b1b2ec2a3154b77c16b55179f08dde77142421cd816649dfca87280e41cd65f8083e407dbc49d08a584b243443f574562965ae489a42076c721e06bd761af6b5a14bcc6082f5b6217519380a0b67af3f3dfde94affc20eec3d7afb97c46ee622f7a24193fe03b8d66850e745215a70989cdb5c4e2eb6eee34ecc050737155ffa228f04", - "Name": "random-2", - "Gas": 695978, - "NoBenchmark": false - }, - { - "Input": "000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000300fca65d7df0af69e7e334361bce8b5493cc610c06045ed7e1d86a097f5de3162cf7cf4ad5b48e2e75dc1a9aaea306eaffa967d2bc3e9b3d9ecf7ca585a5ea89001d0924fe417fdf3e33962ae9c3283dd17f0ad6f6115aed5b92ab0537e9a8cff2919a909ae2ebbd29889489a77154daa63fd3059b1393eec21cc86c9679a7558ac737582997a3ef944cad6d7dd145ea3dd05ff816a334defdabce7e197e27afe03ffee039fcb10e9ce3714e662671f0d6de2fc51aaf3ee793595b7ec924d61525fd8c3ed365b1411f505c3231e577d73adda73b4f61953100174ddfa03245770c5b296999331d712ceda6c9520cb5ade28344eb7d9ac5c3675b3dbb17d1a0ec0ae525d5bb0d3d06bf360e62bacbdff4629ffd1cee59f9729665013df562595b4cbabe6de6dff2d4463d2c468c62099c7c495877d383fdb1ebb33f550c00115dddb316000c710e83ad29d6b12db3e5335c65c1ac09e187c8bc0d81329f9cc8462bce69d84c08900a3bd5c9aaad4c8338ce0bf9a73e6553fb2c4d44fef0dbe3116190865e4a0ce654d4b7703417dc1b708a496493a32007c67b6dacffc391195ff1b8491cc625edc125a4d287f41ee44f32d474a777506e4089bfe3dd5fe790439d11435f93ffa9645ba26efe4e80d15c885dcdef1c72e7a1490893064210613e138eeae3db85bb322a0f4bd5e170f108ee86540219a1262a29a2158a6ba3224a4130eac096ed8103a19ac40d2612bfd9178a17bbbb5f247267bfba396f30c786ec0193b1863172010451607fdd190316e23c73e18c668ee20b7758d632489370d33d20257f0d53e51716b76afea5954ebba45633bce5fa8572b1a60ed89ccebcbdefab9ff8de97d5e98dc975af24e365e2091a8ef260b4ed429ab3cd67450c7b48fbac0ef9f556dd0d81492e1c85d64dcd402bcf813fca5c3bbe5d5d2bc96ea4804e2db477d56cedb33d81711785d1bdc465ff26193dfa083150d23b98f8b14312a5adce5822d6f8d43d1b43be5945b6ed9f99c6ca478a4353248227023155bb8c3d5e72d70264a28e0752db22b649d80a1c4df85feb79cb1a6a7a339faef6a2e860b780fad3dfea3bf637c2f4ae209ddbaa1de5e35c5ca0c492576c883103e011aff3c1ffc8dd363c63bd68b3b6d66b49dc58f49110bf8c0416bc687b736209f257b144bb5f9e12cde6a9809ff3ed0ebceb22abd90f4e00f066bde80c75bac900b3755c0491b0ea3e4626ab673fec1029aeab9050b630e308b15454a1b505582bdf7feb7d90092a3b376d92e8a399b07aacbf5ec92a794a829253533c5b498840cd7f88c0009398a4815418499dfa6517a3daf4f6a78f00653469ea3db6ede0eee4521a2311f0791927b947d66001e597ee3b98805592993e7f228b97a85bee93742a644bdf4106b361dacfed472a053384534e7a545f9852b9dca4ef65e170cbcbb362369c99b7ac35e51f7ac2abf6541d5713550c743f14911191039d164e7f6a032293c7ce96ea9ba2446f401e8f0ee54fd10dce1bbf57744ea58b08b40f1e979f10bf88638c4ec129ba6a2fd0d47281831d4d4541b14f44cb1f79290be2e79225d7a00ed08582c2834ca4605527a47a32098496c2d956eec17bbd431325b547d2d709f83364b1fe9b8ac1f40ceeeea47afff6025d50d04c81b24d55c73eaf5d965d8e7d1dab46cd5786fa371761e1d28c19c9132eb2e98206bd7681175c365ddb635daf607186ceb929aad756993420d07ac8ae759c2fe3730b765f35c5b6fb4f9d5cb30331721b93fa5a0508122b6d8956b09c9388e73e34793b54fa2699f472ae87b83c801c9e07ad1b7b412adb61e008d6de141968c7b51d7f89f61e216c606693598a733109391195e56c6cfd0a9eda73f150bbdf5846bf021f0093a8e88cae1adb9781d66dd7cca40384825b492075eb1caa2f1ca5863a13ca701208a669ac59bd5121731c03061a8316dcd4c364b9fe8ba88c3d5c9275545c6e0cfcc5c4fe99d7299df7fe01ce19a315988eaa664f65b6137dfedd6efc2bdccc16aef60a55bf1842fc9ac7c20089d9729a47f9e6a9f51ea645b146ffbc682c721a31ea1f2299c0d9766869b49ef413f3246ad4b431f523208f50c4a3781885946e55a7ce9eba500087206f418c6a08509e53da52937740250ec990d1d41d605ee3d546484d44546a19d42d1085e29425487304253d3200f61481bfd4297e2b6abf115db623bf0359fe7038bf4a2f9f2c356a8961183b20b0d55033cb8480419ba8c3507484de018870ab27cdf941abe5e2939fbef5ca5184f0ace1d28c37d47e2f9d1686de2ba7576900f06c1353b694a44ee9f1e082a5de71cc83d7fc2ffc38d6a99d5b2a2506b7eb4855f5d9e5719cf54d2e3d4ee39dacf8371512a7d05a3a03b7966ab65b697f483205972cb0058c5ae6e1af00bfeaf30bf063f210f30f1bee1bf1214e06e69ae3aef51cdd0580473288b7c0efaa3944cda25fc5f7766f5422eaa911bee33d540046cc51c9ce1505701bc0873b4e2c43e2ad54aca621a36c3fa1f9f35b6f884e4b63c0a8dee77aff8d5954dbb6e818d4bd662e118f91addea35f1228d421a90193713bd88e8364fa32541e4dcdc7b657882535f54077bfbab5e1057fd5522a6bb0524844ac40453a0133de3a726011dba8ba9fafebb3c11bb28123ac7ee525e02ab29c7a7f71ab48dffbf40c98844755d02ad61ab27a7ffc912d922a1d99bb1b0dfd10bfc3975b370f57cff0bfb4822d39cddc540e0b2eaf33a0e852de802b463fd8fe9b74879138ce9f0a5aab8f3cddaa64f14fbc670d02cf608c23ae09588f2c89b0e7b5e7b9b13ca3866f00d304a3630ba7798edc89d8cbfccb14fb6d8ac4467b2e21df5527c2ab3f1218e19d01fb5be9c0a261de004fffd71bb91ba5b6ce19c12985a5e2c40de4e5ce4fc970f72efad0b811ade763677ade2107396da6068843ff20747c567924b2be7c10b0868fe0edbe4beda420315ec10ac6c0bdd3763ec8c78571d7b97e793eedfb29e657c95892264cae5b19e323cdc1b2718fb867de473e48e9001d093340d1aeb725087769416e2d9687c7e2aee462a315070cea86851ce1a80fe057751c186e896a2bfdc5494930218ad057805b99059d7af42c33e727320826cf596016b87a6f595943b865decbbddcc8fcb4c5614c76dbbafa28eb3aa143f9f49e79efcf0ad156e82601554c389c286e571e2b7d43c583e3855cbe51831eb700bf703f51ce0cbd2a5edd3a4a208e815eff7ce2", - "Expected": "322095f875760be6e68ff243035d11bf24ba5efe065d6f1036756cb9dc71e8c889cc87f614f7e16e694cda1bb12357cb866f7dbd9917c7da6bbd0dcbdc0d923e79d35255a6fba56ae769e8acd80bb6fb55227021649ec00f67c476e88d582dd474100bb9c5e834b13e62d680812a8564e63e94347eb755a129da1e0a7fa33c82eaffcf2faaed039c28ce512df21f987621fe86a48d5a6be2a69f95c3385a8a70ba18a142446f13e1a5d617c84cf5f0d901545eaab402ae8dd15c91216624e67687fd7a2594a3cd8b0e6ed7ba56002a77a6e917561923a503c65fdbc34268daee52d4ec98fa92a0908c0bc5f04bde3a8dac2400d3284b4cf77f3f0929c6352fd242ceaab08afe7b1966afed11ad364bd76b31461559be396fdac25a2bcd8334fa3c43889b5514714e06088a3a3d2b368b8e594cb674a4df2627e2fda6514ddd0c622d627a1f87d14386906da0126c35bb4490f87d71f75d47038096de6a89b9209509385a15bb5dcff3e0d9d493159fdd206df13d34f778aeac9b69e9f2897c93ff587b86e10937c1d6cd7f640c30fe17e0716d74eeea25e18ab1b1aa08e63590261dcab0f247d1bc090dda80e8665109e9ca95f47a5dcecfa0e2bed9864e4dac15afb99d4d1d776d2f65362acb5ca14726d2f3a712f4ed43a1fad18a22f96f470b0ee4877f4820288587e01fd6ef81703776ac27081e9cdf0fba3ca5878f368dc6e06de9fdd2ccc10c9b1da1a3947a9cb87df4740db06fabc21f5082b5c580f1675c48834ab4cc0ff49e8c134f0a0c76b5d0820bc5f2691e7e3ce48a327c311921a8b746fc8192dd2abae3aff0c153af09367f49cbbc39c02984c8c077ddf8dcaf786fac7cc2f3aef8daec0a700172abcdbee72d830c6eaf5def5442658f6f878b581f213565ec085685804b658360366e024dae2de41c49ceb78399de7997d3a6bea66fff501ffd08695c9941a91a1d129d10c1d61b720e0f1f1f9d7c3d06e1304e8d8b7fc243cc9f361af4f75d007c6a7a8580f5af4e898c6644435697f70abf307ea519ef9a140668f145b6741fe1b3e0f14f3638158c6bb81c54b524c122", - "Name": "random-3", - "Gas": 18868224, - "NoBenchmark": false - } -] \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs b/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs index 73c0c925c45..ef3c4c9a68a 100644 --- a/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs +++ b/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs @@ -46,6 +46,7 @@ using Nethermind.Trie; using NSubstitute; using Nethermind.Blockchain.Blocks; +using Nethermind.Facade.Find; namespace Nethermind.Runner.Test.Ethereum { diff --git a/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs b/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs index f3c29b1fed8..7e970cda02d 100644 --- a/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs +++ b/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs @@ -163,7 +163,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IJsonRpc if (jsonRpcUrl.MaxRequestBodySize is not null) ctx.Features.Get().MaxRequestBodySize = jsonRpcUrl.MaxRequestBodySize; - Stopwatch stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); CountingPipeReader request = new(ctx.Request.BodyReader); try { @@ -243,7 +243,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IJsonRpc await ctx.Response.CompleteAsync(); } - long handlingTimeMicroseconds = stopwatch.ElapsedMicroseconds(); + long handlingTimeMicroseconds = (long)Stopwatch.GetElapsedTime(startTime).TotalMicroseconds; _ = jsonRpcLocalStats.ReportCall(result.IsCollection ? new RpcReport("# collection serialization #", handlingTimeMicroseconds, true) : result.Report.Value, handlingTimeMicroseconds, resultWriter.WrittenCount); diff --git a/src/Nethermind/Nethermind.Runner/NLog.config b/src/Nethermind/Nethermind.Runner/NLog.config index d99f39d9003..b9e3d620738 100644 --- a/src/Nethermind/Nethermind.Runner/NLog.config +++ b/src/Nethermind/Nethermind.Runner/NLog.config @@ -66,7 +66,7 @@ - + diff --git a/src/Nethermind/Nethermind.Runner/Nethermind.Runner.csproj b/src/Nethermind/Nethermind.Runner/Nethermind.Runner.csproj index a4b822a4fd9..613b0e330ee 100644 --- a/src/Nethermind/Nethermind.Runner/Nethermind.Runner.csproj +++ b/src/Nethermind/Nethermind.Runner/Nethermind.Runner.csproj @@ -57,6 +57,7 @@ + @@ -90,11 +91,8 @@ - - - - - + + diff --git a/src/Nethermind/Nethermind.Runner/Program.cs b/src/Nethermind/Nethermind.Runner/Program.cs index e6d0b5780a0..fef352adc11 100644 --- a/src/Nethermind/Nethermind.Runner/Program.cs +++ b/src/Nethermind/Nethermind.Runner/Program.cs @@ -412,11 +412,10 @@ private static IConfigProvider BuildConfigProvider( else { _logger.Info($"Loading standard NLog.config file from {"NLog.config".GetApplicationResourcePath()}."); - Stopwatch stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); LogManager.Configuration = new XmlLoggingConfiguration("NLog.config".GetApplicationResourcePath()); - stopwatch.Stop(); - _logger.Info($"NLog.config loaded in {stopwatch.ElapsedMilliseconds}ms."); + _logger.Info($"NLog.config loaded in {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms."); } // TODO: dynamically switch log levels from CLI! diff --git a/src/Nethermind/Nethermind.Runner/configs/base-mainnet.cfg b/src/Nethermind/Nethermind.Runner/configs/base-mainnet.cfg index 5deb04a53aa..9c32c0f7d8c 100644 --- a/src/Nethermind/Nethermind.Runner/configs/base-mainnet.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/base-mainnet.cfg @@ -15,8 +15,8 @@ "AncientBodiesBarrier": 105235063, "AncientReceiptsBarrier": 105235063, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 20380000, - "PivotHash": "0x32dfa61d16a0e99c4e5edd1f791cae912e982d9d33531e6f0337fe230a19606b", + "PivotNumber": 20680000, + "PivotHash": "0xf6a3f8571f1641e83c531e264dd1a805f8a1ecfe974a5d6cdf211fe16d35fad4", "PivotTotalDifficulty": "0", "MaxAttemptsToUpdatePivot": 0 }, diff --git a/src/Nethermind/Nethermind.Runner/configs/base-sepolia.cfg b/src/Nethermind/Nethermind.Runner/configs/base-sepolia.cfg index b737a1e5d63..e369d49a0bd 100644 --- a/src/Nethermind/Nethermind.Runner/configs/base-sepolia.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/base-sepolia.cfg @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 15890000, - "PivotHash": "0x237da61c2eb0fd52eaca16caded258771df08e8618540f6f1b525110b4f96d4a", + "PivotNumber": 16190000, + "PivotHash": "0x8a04494563509fbd72ca62cc947b265d3bfa8803a0dc4d1450672853efbc6817", "PivotTotalDifficulty": "0", "MaxAttemptsToUpdatePivot": 0 }, diff --git a/src/Nethermind/Nethermind.Runner/configs/chiado.cfg b/src/Nethermind/Nethermind.Runner/configs/chiado.cfg index fe8cd712f81..2caeddda453 100644 --- a/src/Nethermind/Nethermind.Runner/configs/chiado.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/chiado.cfg @@ -16,8 +16,8 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 12020000, - "PivotHash": "0x6552eb5964d70decaabff32b2e0f66d1139447b6f680dea854c56101150c039a", + "PivotNumber": 12130000, + "PivotHash": "0x906cdf95d8d77c3bca1cca7c6f832cf15b3b39da3bb9c2c9308c79e4f5bde2cf", "PivotTotalDifficulty": "231708131825107706987652208063906496124457284", "FastSyncCatchUpHeightDelta": "10000000000", "UseGethLimitsInFastBlocks": false @@ -31,6 +31,20 @@ "TxPriorityContractAddress": "0x4100000000000000000000000000000000000000", "ForceSealing": true }, + "Shutter": { + "ValidatorRegistryContractAddress": "0xa9289A3Dd14FEBe10611119bE81E5d35eAaC3084", + "SequencerContractAddress": "0x2aD8E2feB0ED5b2EC8e700edB725f120576994ed", + "KeyBroadcastContractAddress": "0x9D31865BEffcE842FBd36CDA587aDDA8bef804B7", + "KeyperSetManagerContractAddress": "0xC4DE9FAf4ec882b33dA0162CBE628B0D8205D0c0", + "BootnodeP2PAddresses": [ + "/ip4/157.230.104.246/tcp/23003/p2p/12D3KooWFUYoPd3bdPuRi6FXkEQRSw7FRf2e23NAypjfDVYuvBAV", + "/ip4/134.209.225.234/tcp/23003/p2p/12D3KooWAsBKAj1NEtvu7wcLiEVU49N6Z9GPK3tZ87m17tFdWdNE", + "/ip4/157.230.114.117/tcp/23003/p2p/12D3KooWEDk8XJdxHjCHh9wTGVRXtpyCvCP4N4Jztr8zTJd4rMVX", + "/ip4/64.225.104.2/tcp/23003/p2p/12D3KooWMXTYrwEz4v5aGa7chYHjVVpjzzkq9JSjZzxbdh9YgAQS", + "/ip4/157.230.111.142/tcp/23003/p2p/12D3KooWA3FPqxV8whaFPbLzwbyDEWML4y73D6RJqb2mn7SHz6fg" + ], + "InstanceID": "102000" + }, "EthStats": { "Name": "Nethermind Chiado" }, diff --git a/src/Nethermind/Nethermind.Runner/configs/energyweb.cfg b/src/Nethermind/Nethermind.Runner/configs/energyweb.cfg index 5180c59d20a..85a3d9a092b 100644 --- a/src/Nethermind/Nethermind.Runner/configs/energyweb.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/energyweb.cfg @@ -11,9 +11,9 @@ }, "Sync": { "FastSync": true, - "PivotNumber": 32070000, - "PivotHash": "0x85a2e57de94ae39aedb255f664ed0ccfec14a4e68513a50bedbc639defc40b01", - "PivotTotalDifficulty": "10912855507154496523270423660336806541016479581", + "PivotNumber": 32180000, + "PivotHash": "0xc962101318876bc4728b6c491a0a29117dfce9c2d27800008bd3d74d11fd1aa5", + "PivotTotalDifficulty": "10950286567515799754251394867154301044276410309", "UseGethLimitsInFastBlocks": false, "FastSyncCatchUpHeightDelta": 10000000000 }, diff --git a/src/Nethermind/Nethermind.Runner/configs/exosama.cfg b/src/Nethermind/Nethermind.Runner/configs/exosama.cfg index 1a291cc97b7..d8ef1140f00 100644 --- a/src/Nethermind/Nethermind.Runner/configs/exosama.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/exosama.cfg @@ -11,9 +11,9 @@ }, "Sync": { "FastSync": true, - "PivotNumber": 12220000, - "PivotHash": "0xadecf2c44195620a00fbfa193d5a11716a521d930bd8f1c1dc1f923ca8e13237", - "PivotTotalDifficulty": "4158250523773868023522437702816207543634733285", + "PivotNumber": 12340000, + "PivotHash": "0xab44a093519fd72562c9a93fe5cdaaa8d5f171b90edeca811003324eff6964a3", + "PivotTotalDifficulty": "4199084407804380639138042655708019729009213285", "UseGethLimitsInFastBlocks": false, "FastSyncCatchUpHeightDelta": 10000000000 }, diff --git a/src/Nethermind/Nethermind.Runner/configs/gnosis.cfg b/src/Nethermind/Nethermind.Runner/configs/gnosis.cfg index ecb8b47333e..f7bcfab9c47 100644 --- a/src/Nethermind/Nethermind.Runner/configs/gnosis.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/gnosis.cfg @@ -13,8 +13,8 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 36230000, - "PivotHash": "0x228ed2253106c75745623fca648cfe98a91f5f68de3556a0e8bc6a0a11edc2d3", + "PivotNumber": 36340000, + "PivotHash": "0x9fefe19997f398430c979a36b70d734997db87461b24084f303444430a7d99ac", "PivotTotalDifficulty": "8626000110427538733349499292577475819600160930", "UseGethLimitsInFastBlocks": false, "FastSyncCatchUpHeightDelta": 10000000000 diff --git a/src/Nethermind/Nethermind.Runner/configs/hive.cfg b/src/Nethermind/Nethermind.Runner/configs/hive.cfg index 4bbfac20a03..d0d51c733ce 100644 --- a/src/Nethermind/Nethermind.Runner/configs/hive.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/hive.cfg @@ -9,7 +9,12 @@ }, "JsonRpc": { "Enabled": true, - "Host": "0.0.0.0" + "Timeout": 20000, + "Host": "127.0.0.1", + "Port": 8545, + "EngineHost": "127.0.0.1", + "EnginePort": 8551, + "EngineEnabledModules": "net,eth,subscribe,engine,web3,client" }, "Network": { "ExternalIp": "127.0.0.1" diff --git a/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.cfg b/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.cfg index 0266879f4a4..d60775812e4 100644 --- a/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.cfg @@ -11,9 +11,9 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 13590000, - "PivotHash": "0x4d55f48b5939543fa48ccf9714cc3b113e7934a9fc813287feec283d833afb27", - "PivotTotalDifficulty": "26928974" + "PivotNumber": 13710000, + "PivotHash": "0x10c7117af99c830c485c9dee83fea59ef8bc9fe74fe93ce393ef1ae06e8ddb50", + "PivotTotalDifficulty": "27138394" }, "Metrics": { "NodeName": "JOC-Mainnet" diff --git a/src/Nethermind/Nethermind.Runner/configs/joc-testnet.cfg b/src/Nethermind/Nethermind.Runner/configs/joc-testnet.cfg index 9c81c2a1730..6fe47a3e5d7 100644 --- a/src/Nethermind/Nethermind.Runner/configs/joc-testnet.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/joc-testnet.cfg @@ -11,9 +11,9 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 7190000, - "PivotHash": "0xbe62f0118609603a1d59472bd8c2bfdff92b6bf13579a0438561daa523f90da9", - "PivotTotalDifficulty": "13364665" + "PivotNumber": 7310000, + "PivotHash": "0xe277d991a85ff41c7f1c45940a871a00ded49407b446ce6ab337b4820f6c903a", + "PivotTotalDifficulty": "13535348" }, "Metrics": { "NodeName": "JOC-Testnet" diff --git a/src/Nethermind/Nethermind.Runner/configs/mainnet.cfg b/src/Nethermind/Nethermind.Runner/configs/mainnet.cfg index 1e80477c618..74b1b6f68d2 100644 --- a/src/Nethermind/Nethermind.Runner/configs/mainnet.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/mainnet.cfg @@ -9,8 +9,8 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 20851000, - "PivotHash": "0xb583bd6ecd1473667bb8183de0cb09d172361b56fbfd9c9638158d4d47854e15", + "PivotNumber": 20901000, + "PivotHash": "0xfe2488ddbad3900b2e647598014b1eb5d3389e8ad7111cb612573a8a3bc1e834", "PivotTotalDifficulty": "58750003716598352816469", "FastSyncCatchUpHeightDelta": "10000000000" }, diff --git a/src/Nethermind/Nethermind.Runner/configs/op-mainnet.cfg b/src/Nethermind/Nethermind.Runner/configs/op-mainnet.cfg index f9a6095da50..a507ab52511 100644 --- a/src/Nethermind/Nethermind.Runner/configs/op-mainnet.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/op-mainnet.cfg @@ -16,9 +16,9 @@ "AncientBodiesBarrier": 105235063, "AncientReceiptsBarrier": 105235063, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 125970000, - "PivotHash": "0xc77d1d26b1d3511a1440cb4f55222a30d79bc7ac9d806e30de5399c5415ecfb0", - "PivotTotalDifficulty": "210470125", + "PivotNumber": 126270000, + "PivotHash": "0xd3404c9404e47cbbcd688c1620119495cad1dab37631b556cbbb6d4ce0cea54f", + "PivotTotalDifficulty": "0", "MaxAttemptsToUpdatePivot": 0 }, "Discovery": { diff --git a/src/Nethermind/Nethermind.Runner/configs/op-sepolia.cfg b/src/Nethermind/Nethermind.Runner/configs/op-sepolia.cfg index 40b495db2d9..9b5b7ed0aca 100644 --- a/src/Nethermind/Nethermind.Runner/configs/op-sepolia.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/op-sepolia.cfg @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 17870000, - "PivotHash": "0x12e517c71480f3940b29449df4df244aec04f25e1e42c96ef4976af37a713b94", + "PivotNumber": 18170000, + "PivotHash": "0xd006b425a97e05178570f69ec445e513ed076e07bdc7f929c0ccbef0ac6981ac", "PivotTotalDifficulty": "0", "MaxAttemptsToUpdatePivot": 0 }, diff --git a/src/Nethermind/Nethermind.Runner/configs/sepolia.cfg b/src/Nethermind/Nethermind.Runner/configs/sepolia.cfg index cfd90fdb7c7..99442bdcb2f 100644 --- a/src/Nethermind/Nethermind.Runner/configs/sepolia.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/sepolia.cfg @@ -17,8 +17,8 @@ "FastSync": true, "SnapSync": true, "UseGethLimitsInFastBlocks": true, - "PivotNumber": 6777000, - "PivotHash": "0x1a8de8f7b09a32a7e0fe2d3bedcc5ff75118507c9e75c822ce2338e591e79f97", + "PivotNumber": 6821000, + "PivotHash": "0x7c5a0544e9eb44f09d977808fef54a3c7bdcd3d88105117f70cc890bcb32f723", "PivotTotalDifficulty": "17000018015853232", "FastSyncCatchUpHeightDelta": "10000000000" }, diff --git a/src/Nethermind/Nethermind.Runner/configs/volta.cfg b/src/Nethermind/Nethermind.Runner/configs/volta.cfg index 52c8440ed52..cd6b665a3f1 100644 --- a/src/Nethermind/Nethermind.Runner/configs/volta.cfg +++ b/src/Nethermind/Nethermind.Runner/configs/volta.cfg @@ -15,9 +15,9 @@ }, "Sync": { "FastSync": true, - "PivotNumber": 29450000, - "PivotHash": "0xa190daa8205c8021ee58048781a0bd408faebeb434f05447a8ece2986169668f", - "PivotTotalDifficulty": "10021315705821637748996382188865573827004386297", + "PivotNumber": 29530000, + "PivotHash": "0xd4c57f73fbea59f81e88d3325afe1c8f09e095e477b901c92fad7ea6beb9c595", + "PivotTotalDifficulty": "10048538295175312826073452157460115283920659540", "UseGethLimitsInFastBlocks": false, "FastSyncCatchUpHeightDelta": 10000000000 }, diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs index 3c6eba08339..0530f141fd8 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs @@ -14,7 +14,7 @@ public class BlockDecoder : IRlpValueDecoder, IRlpStreamDecoder private readonly HeaderDecoder _headerDecoder = new(); private readonly TxDecoder _txDecoder = TxDecoder.Instance; private readonly WithdrawalDecoder _withdrawalDecoder = new(); - private readonly ConsensusRequestDecoder _consensusRequestsDecoder = new(); + private readonly ConsensusRequestDecoder _consensusRequestsDecoder = ConsensusRequestDecoder.Instance; public Block? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/CompactLogEntryDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/CompactLogEntryDecoder.cs index 65646dae530..b1bf684d685 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/CompactLogEntryDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/CompactLogEntryDecoder.cs @@ -115,7 +115,7 @@ public static void Encode(RlpStream rlpStream, LogEntry? item, RlpBehaviors rlpB var (total, topics) = GetContentLength(item); rlpStream.StartSequence(total); - rlpStream.Encode(item.LoggersAddress); + rlpStream.Encode(item.Address); rlpStream.StartSequence(topics); for (var i = 0; i < item.Topics.Length; i++) @@ -147,7 +147,7 @@ private static (int Total, int Topics) GetContentLength(LogEntry? item) return (contentLength, 0); } - contentLength += Rlp.LengthOf(item.LoggersAddress); + contentLength += Rlp.LengthOf(item.Address); int topicsLength = GetTopicsLength(item); contentLength += Rlp.LengthOfSequence(topicsLength); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/ConsensusRequestDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/ConsensusRequestDecoder.cs index e06c135bd9b..91ac5675f87 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/ConsensusRequestDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/ConsensusRequestDecoder.cs @@ -8,12 +8,16 @@ namespace Nethermind.Serialization.Rlp; public class ConsensusRequestDecoder : IRlpStreamDecoder, IRlpValueDecoder, IRlpObjectDecoder { - private readonly WithdrawalRequestDecoder _withdrawalRequestDecoder = new(); + public static ConsensusRequestDecoder Instance { get; } = new(); + private readonly DepositDecoder _depositDecoder = DepositDecoder.Instance; + private readonly WithdrawalRequestDecoder _withdrawalRequestDecoder = WithdrawalRequestDecoder.Instance; + private readonly ConsolidationRequestDecoder _consolidationRequestDecoder = ConsolidationRequestDecoder.Instance; public int GetContentLength(ConsensusRequest item, RlpBehaviors rlpBehaviors) { int length = item.Type switch { + ConsensusRequestsType.ConsolidationRequest => _consolidationRequestDecoder.GetContentLength((ConsolidationRequest)item, rlpBehaviors), ConsensusRequestsType.WithdrawalRequest => _withdrawalRequestDecoder.GetContentLength((WithdrawalRequest)item, rlpBehaviors), ConsensusRequestsType.Deposit => _depositDecoder.GetContentLength((Deposit)item, rlpBehaviors), _ => throw new RlpException($"Unsupported consensus request type {item.Type}") @@ -25,6 +29,7 @@ public int GetLength(ConsensusRequest item, RlpBehaviors rlpBehaviors) { int length = item.Type switch { + ConsensusRequestsType.ConsolidationRequest => _consolidationRequestDecoder.GetLength((ConsolidationRequest)item, rlpBehaviors), ConsensusRequestsType.WithdrawalRequest => _withdrawalRequestDecoder.GetLength((WithdrawalRequest)item, rlpBehaviors), ConsensusRequestsType.Deposit => _depositDecoder.GetLength((Deposit)item, rlpBehaviors), _ => throw new RlpException($"Unsupported consensus request type {item.Type}") @@ -55,6 +60,7 @@ public int GetLength(ConsensusRequest item, RlpBehaviors rlpBehaviors) ConsensusRequest result = consensusRequestsType switch { + ConsensusRequestsType.ConsolidationRequest => _consolidationRequestDecoder.Decode(rlpStream, rlpBehaviors), ConsensusRequestsType.WithdrawalRequest => _withdrawalRequestDecoder.Decode(rlpStream, rlpBehaviors), ConsensusRequestsType.Deposit => _depositDecoder.Decode(rlpStream, rlpBehaviors), @@ -81,6 +87,7 @@ public ConsensusRequest Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBe ConsensusRequest result = consensusRequestsType switch { + ConsensusRequestsType.ConsolidationRequest => _consolidationRequestDecoder.Decode(ref decoderContext, rlpBehaviors), ConsensusRequestsType.WithdrawalRequest => _withdrawalRequestDecoder.Decode(ref decoderContext, rlpBehaviors), ConsensusRequestsType.Deposit => _depositDecoder.Decode(ref decoderContext, rlpBehaviors), _ => throw new RlpException($"Unsupported consensus request type {consensusRequestsType}") @@ -102,6 +109,9 @@ public void Encode(RlpStream stream, ConsensusRequest item, RlpBehaviors rlpBeha stream.WriteByte((byte)item.Type); switch (item.Type) { + case ConsensusRequestsType.ConsolidationRequest: + _consolidationRequestDecoder.Encode(stream, (ConsolidationRequest)item, rlpBehaviors); + break; case ConsensusRequestsType.WithdrawalRequest: _withdrawalRequestDecoder.Encode(stream, (WithdrawalRequest)item, rlpBehaviors); break; diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/ConsolidationRequestDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/ConsolidationRequestDecoder.cs new file mode 100644 index 00000000000..99b2f3e49e8 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/ConsolidationRequestDecoder.cs @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Core.ConsensusRequests; + +namespace Nethermind.Serialization.Rlp; + +public class ConsolidationRequestDecoder : IRlpStreamDecoder, IRlpValueDecoder +{ + public static ConsolidationRequestDecoder Instance { get; } = new(); + public int GetLength(ConsolidationRequest item, RlpBehaviors rlpBehaviors) => + Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); + + public int GetContentLength(ConsolidationRequest item, RlpBehaviors rlpBehaviors) => + Rlp.LengthOf(item.SourceAddress) + Rlp.LengthOf(item.SourcePubkey) + + Rlp.LengthOf(item.TargetPubkey); + + public ConsolidationRequest Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + int _ = rlpStream.ReadSequenceLength(); + Address sourceAddress = rlpStream.DecodeAddress(); + ArgumentNullException.ThrowIfNull(sourceAddress); + byte[] SourcePubkey = rlpStream.DecodeByteArray(); + byte[] TargetPubkey = rlpStream.DecodeByteArray(); + return new ConsolidationRequest() + { + SourceAddress = sourceAddress, + SourcePubkey = SourcePubkey, + TargetPubkey = TargetPubkey + }; + } + + public ConsolidationRequest Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + int _ = decoderContext.ReadSequenceLength(); + Address sourceAddress = decoderContext.DecodeAddress(); + ArgumentNullException.ThrowIfNull(sourceAddress); + byte[] SourcePubkey = decoderContext.DecodeByteArray(); + byte[] TargetPubkey = decoderContext.DecodeByteArray(); + return new ConsolidationRequest() + { + SourceAddress = sourceAddress, + SourcePubkey = SourcePubkey, + TargetPubkey = TargetPubkey + }; + } + + public void Encode(RlpStream stream, ConsolidationRequest item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + int contentLength = GetContentLength(item, rlpBehaviors); + stream.StartSequence(contentLength); + stream.Encode(item.SourceAddress); + stream.Encode(item.SourcePubkey); + stream.Encode(item.TargetPubkey); + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Eip7702/AuthorizationTupleDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7702/AuthorizationTupleDecoder.cs new file mode 100644 index 00000000000..b3e7dfcc80d --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip7702/AuthorizationTupleDecoder.cs @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using DotNetty.Buffers; +using Nethermind.Core; +using Nethermind.Int256; +using Nethermind.Serialization.Rlp.Eip2930; +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace Nethermind.Serialization.Rlp; + +public class AuthorizationTupleDecoder : IRlpStreamDecoder, IRlpValueDecoder +{ + public static readonly AuthorizationTupleDecoder Instance = new(); + + public AuthorizationTuple Decode(RlpStream stream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + int length = stream.ReadSequenceLength(); + int check = length + stream.Position; + ulong chainId = stream.DecodeULong(); + Address? codeAddress = stream.DecodeAddress(); + ulong nonce = stream.DecodeULong(); + ulong yParity = stream.DecodeULong(); + byte[] r = stream.DecodeByteArray(); + byte[] s = stream.DecodeByteArray(); + + if (!rlpBehaviors.HasFlag(RlpBehaviors.AllowExtraBytes)) + { + stream.Check(check); + } + + if (codeAddress is null) + { + ThrowMissingCodeAddressException(); + } + + return new AuthorizationTuple(chainId, codeAddress, nonce, yParity, r, s); + } + + public AuthorizationTuple Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + int length = decoderContext.ReadSequenceLength(); + int check = length + decoderContext.Position; + ulong chainId = decoderContext.DecodeULong(); + Address? codeAddress = decoderContext.DecodeAddress(); + ulong nonce = decoderContext.DecodeULong(); + ulong yParity = decoderContext.DecodeULong(); + byte[] r = decoderContext.DecodeByteArray(); + byte[] s = decoderContext.DecodeByteArray(); + + if (!rlpBehaviors.HasFlag(RlpBehaviors.AllowExtraBytes)) + { + decoderContext.Check(check); + } + + if (codeAddress is null) + { + ThrowMissingCodeAddressException(); + } + + return new AuthorizationTuple(chainId, codeAddress, nonce, yParity, r, s); + } + + public RlpStream Encode(AuthorizationTuple item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + RlpStream stream = new(GetLength(item, rlpBehaviors)); + Encode(stream, item, rlpBehaviors); + return stream; + } + + public void Encode(RlpStream stream, AuthorizationTuple item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + int contentLength = GetContentLength(item); + stream.StartSequence(contentLength); + stream.Encode(item.ChainId); + stream.Encode(item.CodeAddress); + stream.Encode(item.Nonce); + stream.Encode(item.AuthoritySignature.RecoveryId); + stream.Encode(new UInt256(item.AuthoritySignature.R, true)); + stream.Encode(new UInt256(item.AuthoritySignature.S, true)); + } + + public NettyRlpStream EncodeWithoutSignature(ulong chainId, Address codeAddress, ulong nonce) + { + int contentLength = GetContentLengthWithoutSig(chainId, codeAddress, nonce); + var totalLength = Rlp.LengthOfSequence(contentLength); + IByteBuffer byteBuffer = PooledByteBufferAllocator.Default.Buffer(totalLength); + NettyRlpStream stream = new(byteBuffer); + EncodeWithoutSignature(stream, chainId, codeAddress, nonce); + return stream; + } + + public void EncodeWithoutSignature(RlpStream stream, ulong chainId, Address codeAddress, ulong nonce) + { + int contentLength = GetContentLengthWithoutSig(chainId, codeAddress, nonce); + stream.StartSequence(contentLength); + stream.Encode(chainId); + stream.Encode(codeAddress ?? throw new RlpException($"Invalid tx {nameof(AuthorizationTuple)} format - address is null")); + stream.Encode(nonce); + } + + public int GetLength(AuthorizationTuple item, RlpBehaviors rlpBehaviors) => Rlp.LengthOfSequence(GetContentLength(item)); + + private static int GetContentLength(AuthorizationTuple tuple) => + GetContentLengthWithoutSig(tuple.ChainId, tuple.CodeAddress, tuple.Nonce) + + Rlp.LengthOf(tuple.AuthoritySignature.RecoveryId) + + Rlp.LengthOf(new UInt256(tuple.AuthoritySignature.R.AsSpan(), true)) + + Rlp.LengthOf(new UInt256(tuple.AuthoritySignature.S.AsSpan(), true)); + + private static int GetContentLengthWithoutSig(ulong chainId, Address codeAddress, ulong nonce) => + Rlp.LengthOf(chainId) + + Rlp.LengthOf(codeAddress) + + Rlp.LengthOf(nonce); + + [DoesNotReturn] + [StackTraceHidden] + private static void ThrowMissingCodeAddressException() => throw new RlpException("Missing code address for Authorization"); +} diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/LogEntryDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/LogEntryDecoder.cs index bb98f193cba..9dad2038672 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/LogEntryDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/LogEntryDecoder.cs @@ -77,7 +77,7 @@ public void Encode(RlpStream rlpStream, LogEntry? item, RlpBehaviors rlpBehavior var (total, topics) = GetContentLength(item); rlpStream.StartSequence(total); - rlpStream.Encode(item.LoggersAddress); + rlpStream.Encode(item.Address); rlpStream.StartSequence(topics); for (var i = 0; i < item.Topics.Length; i++) @@ -106,7 +106,7 @@ private static (int Total, int Topics) GetContentLength(LogEntry? item) return (contentLength, 0); } - contentLength += Rlp.LengthOf(item.LoggersAddress); + contentLength += Rlp.LengthOf(item.Address); int topicsLength = GetTopicsLength(item); contentLength += Rlp.LengthOfSequence(topicsLength); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs index 6672a485297..5c1919959ef 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs @@ -27,7 +27,7 @@ public class RlpStream private static readonly BlockInfoDecoder _blockInfoDecoder = new(); private static readonly TxDecoder _txDecoder = TxDecoder.Instance; private static readonly WithdrawalDecoder _withdrawalDecoder = new(); - private static readonly ConsensusRequestDecoder _requestsDecoder = new(); + private static readonly ConsensusRequestDecoder _requestsDecoder = ConsensusRequestDecoder.Instance; private static readonly LogEntryDecoder _logEntryDecoder = LogEntryDecoder.Instance; private readonly CappedArray _data; @@ -56,6 +56,23 @@ public RlpStream(in CappedArray data) _data = data; } + public void EncodeArray(T?[]? items, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (items is null) + { + WriteByte(Rlp.NullObjectByte); + return; + } + IRlpStreamDecoder decoder = Rlp.GetStreamDecoder(); + int contentLength = decoder.GetContentLength(items); + + StartSequence(contentLength); + + foreach (var item in items) + { + decoder.Encode(this, item, rlpBehaviors); + } + } public void Encode(Block value) { _blockDecoder.Encode(this, value); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs index 6a720d6e5e5..0c16876e02f 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs @@ -40,6 +40,7 @@ protected TxDecoder(Func? transactionFactory = null) RegisterDecoder(new AccessListTxDecoder(factory)); RegisterDecoder(new EIP1559TxDecoder(factory)); RegisterDecoder(new BlobTxDecoder(factory)); + RegisterDecoder(new SetCodeTxDecoder(factory)); } public void RegisterDecoder(ITxDecoder decoder) => _decoders[(int)decoder.Type] = decoder; diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BaseTxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BaseTxDecoder.cs index ab72eef75e7..a8918382c1d 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BaseTxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BaseTxDecoder.cs @@ -155,7 +155,7 @@ protected virtual void DecodeGasPrice(Transaction transaction, ref Rlp.ValueDeco transaction.GasPrice = decoderContext.DecodeUInt256(); } - private Signature? DecodeSignature(Transaction transaction, RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected Signature? DecodeSignature(Transaction transaction, RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { ulong v = rlpStream.DecodeULong(); ReadOnlySpan rBytes = rlpStream.DecodeByteArraySpan(); @@ -163,7 +163,7 @@ protected virtual void DecodeGasPrice(Transaction transaction, ref Rlp.ValueDeco return DecodeSignature(v, rBytes, sBytes, transaction.Signature, rlpBehaviors); } - private Signature? DecodeSignature(Transaction transaction, ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + protected Signature? DecodeSignature(Transaction transaction, ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { ulong v = decoderContext.DecodeULong(); ReadOnlySpan rBytes = decoderContext.DecodeByteArraySpan(); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/SetCodeTxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/SetCodeTxDecoder.cs new file mode 100644 index 00000000000..ffc110f998b --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/SetCodeTxDecoder.cs @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; + +namespace Nethermind.Serialization.Rlp.TxDecoders; + +public sealed class SetCodeTxDecoder(Func? transactionFactory = null) + : BaseEIP1559TxDecoder(TxType.SetCode, transactionFactory) where T : Transaction, new() +{ + private AuthorizationTupleDecoder _authTupleDecoder = new(); + + protected override void DecodePayload(Transaction transaction, RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + base.DecodePayload(transaction, rlpStream, rlpBehaviors); + transaction.AuthorizationList = rlpStream.DecodeArray((s) => _authTupleDecoder.Decode(s, rlpBehaviors)); + } + + protected override void DecodePayload(Transaction transaction, ref Rlp.ValueDecoderContext decoderContext, + RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + base.DecodePayload(transaction, ref decoderContext, rlpBehaviors); + transaction.AuthorizationList = decoderContext.DecodeArray(_authTupleDecoder); + } + + protected override void EncodePayload(Transaction transaction, RlpStream stream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + base.EncodePayload(transaction, stream, rlpBehaviors); + stream.EncodeArray(transaction.AuthorizationList, rlpBehaviors); + } + + protected override int GetPayloadLength(Transaction transaction) + { + return base.GetPayloadLength(transaction) + + (transaction.AuthorizationList is null ? 1 : Rlp.LengthOfSequence(_authTupleDecoder.GetContentLength(transaction.AuthorizationList, RlpBehaviors.None))); + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/WithdrawalRequestDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/WithdrawalRequestDecoder.cs index 4a0de9f3288..450a842cc9c 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/WithdrawalRequestDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/WithdrawalRequestDecoder.cs @@ -9,6 +9,7 @@ namespace Nethermind.Serialization.Rlp; public class WithdrawalRequestDecoder : IRlpStreamDecoder, IRlpValueDecoder, IRlpObjectDecoder { + public static WithdrawalRequestDecoder Instance { get; } = new(); public int GetLength(WithdrawalRequest item, RlpBehaviors rlpBehaviors) => Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); diff --git a/src/Nethermind/Nethermind.Shutter.Test/AssertionsSetup.cs b/src/Nethermind/Nethermind.Shutter.Test/AssertionsSetup.cs new file mode 100644 index 00000000000..3f2d3e0dd28 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter.Test/AssertionsSetup.cs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using NUnit.Framework; +using FluentAssertions; +using Nethermind.Core; + +namespace Nethermind; + +/// +/// Global settings for the fluent assertions, works for the current assembly only. +/// +[SetUpFixture] +public class AssertionsSetup +{ + [OneTimeSetUp] + public void RunBeforeAnyTests() + { + AssertionOptions.AssertEquivalencyUsing(options => options.Excluding(c => c.Name == nameof(BlockHeader.MaybeParent))); + } +} diff --git a/src/Nethermind/Nethermind.Shutter.Test/Nethermind.Shutter.Test.csproj b/src/Nethermind/Nethermind.Shutter.Test/Nethermind.Shutter.Test.csproj new file mode 100644 index 00000000000..47c068e1faf --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter.Test/Nethermind.Shutter.Test.csproj @@ -0,0 +1,30 @@ + + + + enable + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterApiSimulator.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterApiSimulator.cs new file mode 100644 index 00000000000..c1709d2879c --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterApiSimulator.cs @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Nethermind.Abi; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Find; +using Nethermind.Blockchain.Receipts; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Facade.Find; +using Nethermind.Logging; +using Nethermind.Shutter.Config; +using Nethermind.State; +using NSubstitute; + +namespace Nethermind.Shutter.Test; + +public class ShutterApiSimulator( + ShutterEventSimulator eventSimulator, + IAbiEncoder abiEncoder, + IBlockTree blockTree, + IEthereumEcdsa ecdsa, + ILogFinder logFinder, + IReceiptStorage receiptStorage, + ILogManager logManager, + ISpecProvider specProvider, + ITimestamper timestamper, + IWorldStateManager worldStateManager, + IShutterConfig cfg, + Dictionary validatorsInfo, + Random rnd + ) : ShutterApi(abiEncoder, blockTree, ecdsa, logFinder, receiptStorage, + logManager, specProvider, timestamper, worldStateManager, cfg, validatorsInfo, ShutterTestsCommon.SlotLength) +{ + public int EonUpdateCalled = 0; + public int KeysValidated = 0; + public ShutterTransactions? LoadedTransactions; + + private readonly Random _rnd = rnd; + private readonly IReceiptStorage _receiptStorage = receiptStorage; + + public (List events, Dto.DecryptionKeys keys) AdvanceSlot(int eventCount, int? keyCount = null) + { + (List events, Dto.DecryptionKeys keys) x = eventSimulator.AdvanceSlot(eventCount, keyCount); + LogEntry[] logs = x.events.Select(e => e.LogEntry).ToArray(); + InsertShutterReceipts(_readOnlyBlockTree.Head ?? Build.A.Block.TestObject, logs); + TriggerKeysReceived(x.keys); + return x; + } + + public void TriggerNewHeadBlock(BlockEventArgs e) + => _blockTree.NewHeadBlock += Raise.EventWith(this, e); + + public void TriggerKeysReceived(Dto.DecryptionKeys keys) + => _ = OnKeysReceived(keys); + + public void NextEon() + => eventSimulator.NextEon(); + + public void InsertShutterReceipts(Block block, in LogEntry[] logs) + { + var receipts = new TxReceipt[logs.Length]; + block.Header.Bloom = new(logs); + + // one log per receipt + for (int i = 0; i < logs.Length; i++) + { + var h = new byte[32]; + _rnd.NextBytes(h); + receipts[i] = Build.A.Receipt + .WithLogs([logs[i]]) + .WithTransactionHash(new(h)) + .WithBlockHash(block.Hash) + .WithBlockNumber(block.Number) + .TestObject; + } + + _receiptStorage.Insert(block, receipts); + TxLoader.LoadFromReceipts(block, receipts, eventSimulator.GetCurrentEonInfo().Eon); + } + + protected override async Task OnKeysReceived(Dto.DecryptionKeys decryptionKeys) + { + IShutterKeyValidator.ValidatedKeys? keys = KeyValidator.ValidateKeys(decryptionKeys); + + if (keys is null) + { + return; + } + + KeysValidated++; + Metrics.ShutterTxPointer = keys.Value.TxPointer; + + // wait for latest block before loading transactions + Block? head = (await BlockHandler.WaitForBlockInSlot(keys.Value.Slot - 1, new())) ?? _readOnlyBlockTree.Head; + BlockHeader? header = head?.Header; + BlockHeader parentHeader = header is not null + ? _readOnlyBlockTree.FindParentHeader(header, BlockTreeLookupOptions.None)! + : _readOnlyBlockTree.FindLatestHeader()!; + + // store transactions to check in tests + LoadedTransactions = TxSource.LoadTransactions(head, parentHeader, keys.Value); + } + + + // fake out P2P module + protected override void InitP2P(IShutterConfig cfg, ILogManager logManager) + { + P2P = Substitute.For(); + } + + protected override IShutterEon InitEon() + { + IShutterEon eon = Substitute.For(); + eon.GetCurrentEonInfo().Returns(_ => eventSimulator.GetCurrentEonInfo()); + eon.When(x => x.Update(Arg.Any())).Do((_) => EonUpdateCalled++); + return eon; + } + + // set genesis unix timestamp to 1 + protected override ShutterTime InitTime(ISpecProvider specProvider, ITimestamper timestamper) + { + return new(1000, timestamper, _slotLength, _blockUpToDateCutoff); + } +} diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterBlockHandlerTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterBlockHandlerTests.cs new file mode 100644 index 00000000000..f1224ebcb42 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterBlockHandlerTests.cs @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using NUnit.Framework; +using Nethermind.Core.Test.Builders; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Core; +using System; +using Nethermind.Merge.Plugin.Test; + +namespace Nethermind.Shutter.Test; + +[TestFixture] +class ShutterBlockHandlerTests : EngineModuleTests +{ + [Test] + public void Can_wait_for_valid_block() + { + Random rnd = new(ShutterTestsCommon.Seed); + Timestamper timestamper = ShutterTestsCommon.InitTimestamper(ShutterTestsCommon.InitialSlotTimestamp, 0); + ShutterApiSimulator api = ShutterTestsCommon.InitApi(rnd, timestamper); + IShutterBlockHandler blockHandler = api.BlockHandler; + + CancellationTokenSource source = new(); + Task waitTask = blockHandler.WaitForBlockInSlot(ShutterTestsCommon.InitialSlot, source.Token); + Block result = Build.A.Block.WithTimestamp(ShutterTestsCommon.InitialSlotTimestamp).TestObject; + api.TriggerNewHeadBlock(new(result)); + + Assert.That(result, Is.EqualTo(waitTask.Result)); + } + + [Test] + public void Wait_times_out_at_cutoff() + { + Random rnd = new(ShutterTestsCommon.Seed); + Timestamper timestamper = ShutterTestsCommon.InitTimestamper(ShutterTestsCommon.InitialSlotTimestamp, 0); + ShutterApiSimulator api = ShutterTestsCommon.InitApi(rnd, timestamper); + + using CancellationTokenSource source = new(); + using CancellationTokenSource timeoutSource = new(); + Task waitTask = api.BlockHandler.WaitForBlockInSlot(ShutterTestsCommon.InitialSlot, source.Token, (int waitTime) => + { + Assert.That(waitTime, Is.EqualTo((int)api.BlockWaitCutoff.TotalMilliseconds)); + return timeoutSource; + }); + + Assert.That(waitTask.IsCompleted, Is.False); + timeoutSource.Cancel(); + Assert.That(waitTask.IsCompletedSuccessfully); + } + + [Test] + public void Does_not_wait_after_cutoff() + { + const ulong blockWaitCutoff = 1333; + Random rnd = new(ShutterTestsCommon.Seed); + Timestamper timestamper = ShutterTestsCommon.InitTimestamper(ShutterTestsCommon.InitialSlotTimestamp, 2 * blockWaitCutoff); + ShutterApiSimulator api = ShutterTestsCommon.InitApi(rnd, timestamper); + + using CancellationTokenSource source = new(); + Task waitTask = api.BlockHandler.WaitForBlockInSlot(ShutterTestsCommon.InitialSlot, source.Token); + + Assert.That(waitTask.IsCompletedSuccessfully); + } + + [Test] + public void Ignores_outdated_block() + { + Random rnd = new(ShutterTestsCommon.Seed); + Timestamper timestamper = ShutterTestsCommon.InitTimestamper(ShutterTestsCommon.InitialSlotTimestamp, 2 * (ulong)ShutterTestsCommon.BlockUpToDateCutoff.TotalMilliseconds); + ShutterApiSimulator api = ShutterTestsCommon.InitApi(rnd, timestamper); + + // not triggered on outdated block + api.TriggerNewHeadBlock(new(Build.A.Block.WithTimestamp(ShutterTestsCommon.InitialSlotTimestamp).TestObject)); + Assert.That(api.EonUpdateCalled, Is.EqualTo(0)); + + // triggered on up to date block + ulong upToDateTimestamp = ShutterTestsCommon.InitialSlotTimestamp + 2 * (ulong)ShutterTestsCommon.BlockUpToDateCutoff.TotalSeconds; + api.TriggerNewHeadBlock(new(Build.A.Block.WithTimestamp(upToDateTimestamp).TestObject)); + Assert.That(api.EonUpdateCalled, Is.EqualTo(1)); + } + +} diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterCryptoTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterCryptoTests.cs new file mode 100644 index 00000000000..53b25b0fe96 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterCryptoTests.cs @@ -0,0 +1,211 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; +using Nethermind.Int256; +using NUnit.Framework; +using Nethermind.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Core.Extensions; + +namespace Nethermind.Shutter.Test; + +using G1 = Bls.P1; +using G2 = Bls.P2; +using GT = Bls.PT; +using EncryptedMessage = ShutterCrypto.EncryptedMessage; + +[TestFixture] +class ShutterCryptoTests +{ + [Test] + public void Pairing_holds() + { + UInt256 sk = 123456789; + UInt256 r = 4444444444; + G1 identity = G1.Generator().Mult(3261443); + G2 eonKey = G2.Generator().Mult(sk.ToLittleEndian()); + G1 key = identity.Dup().Mult(sk.ToLittleEndian()); + + GT p1 = new(key, G2.Generator().Mult(r.ToLittleEndian())); + Span h1 = ShutterCrypto.Hash2(p1); + GT p2 = new(identity, eonKey); + ShutterCrypto.GTExp(ref p2, r); + Span h2 = ShutterCrypto.Hash2(p2); + + Assert.That(h1.ToArray(), Is.EqualTo(h2.ToArray())); + } + + [Test] + [TestCase("f869820243849502f900825208943834a349678ef446bae07e2aeffc01054184af008203e880824fd4a0510c063afbe5b8b8875b680e96a1778c99c765cc0df263f10f8d9707cfa0f114a02590b2ce6dbce6532da17c52a2a7f2eb6155f23404128fca5fb72dc852ce64c6")] + [TestCase("08825208943834a349678ef446bae07e2aeffc01054184af008203e880824fd4a02356f869820246849502f900825208943834a349678ef446bae07e2aeffc01054184af008203e880824fd4a02356b138904ed89a72a1fa913aa651c3b4144a5b47aa0cbf6a6cf9956d896bc0a0825208943834a349678ef446bae07e2aeffc01054184af008203e880824fd4a023560825208943834a349678ef446bae07e2aeffc01054184af008203e880824fd4a0235607e1364d24a98ac1cdb3f0af8c5c0cf164528df11dd766aa368d4136651ceb55e")] + public void Can_encrypt_then_decrypt(string msgHex) + { + byte[] msg = Convert.FromHexString(msgHex); + UInt256 sk = 123456789; + G1 identity = G1.Generator().Mult(3261443); + G2 eonKey = G2.Generator().Mult(sk.ToLittleEndian()); + Span sigma = new([0x12, 0x15, 0xaa, 0xbb, 0x33, 0xfd, 0x66, 0x55, 0x15, 0xaa, 0xbb, 0x33, 0xfd, 0x66, 0x55, 0x15, 0xaa, 0xbb, 0x33, 0xfd, 0x66, 0x55, 0x15, 0xaa, 0xbb, 0x33, 0xfd, 0x66, 0x55, 0x22, 0x88, 0x45]); + + TestContext.Out.WriteLine("eon key for " + sk + ": " + Convert.ToHexString(eonKey.Compress())); + + EncryptedMessage encryptedMessage = ShutterCrypto.Encrypt(msg, identity, eonKey, sigma); + G1 key = identity.Dup().Mult(sk.ToLittleEndian()); + + ShutterCrypto.RecoverSigma(out Span recoveredSigma, encryptedMessage, key.ToAffine()); + Assert.That(recoveredSigma.ToArray(), Is.EqualTo(sigma.ToArray())); + + Span decryptedMessage = stackalloc byte[ShutterCrypto.GetDecryptedDataLength(encryptedMessage)]; + ShutterCrypto.Decrypt(ref decryptedMessage, encryptedMessage, key); + Assert.That(msg.SequenceEqual(decryptedMessage.ToArray())); + + EncryptedMessage decoded = ShutterCrypto.DecodeEncryptedMessage(ShutterCrypto.EncodeEncryptedMessage(encryptedMessage)); + Assert.That(encryptedMessage.C1.IsEqual(decoded.C1)); + Assert.That(encryptedMessage.C2.ToArray(), Is.EqualTo(decoded.C2.ToArray())); + Assert.That(encryptedMessage.C3.ToArray(), Is.EqualTo(decoded.C3.ToArray())); + } + + [Test] + [TestCase( + true, + "9555a85e8f6f91b2c986c36e4047ed0613c5cd7ae5d9651d0d465a3d37a1a15dcf3102762a913e34bc5769c8c4a7fbd1", + "ac7ac24084236ddd6f4bee47e4a10086ff02345cce206e12befcc328425704b179ad21b50a69434a6c851286959382291513f6a856da9166cc0cd8c03a09f0dfa34876a88e7c664aeee6edfd5159b9f31e79f8ec9db7797d57c5086b1ff695e1", + "6baafa8f22cf1f3d32e8b1a13d6859eb92085c346fbed66f8def1dd1659ee555a925487f1fc87750627e9a1659b31adcac0157b3" + )] + [TestCase( + false, + "a7b225dad05e856fb58bd87bd805ed67466f731421de8fa6127ffd16964c2ac06c4dc40d27f71d4cb81edf9f1de42ff8", + "87c0cbe2e20645dcf0d8805e7afae882f6d483765e7616f6e7454c015e0f34dcc1b4c9ed05169b053dbe12f2223a423606e51afdf59e3536f5bbc49555a67ed2d7fd7be3a893ba15e80f20415a51156a4844066583e98ad17c273ae921f28bdf", + "c6af2324331998d25b488fafe9e7e25bc3a2e3d45b75e47dd8a4da5435db152c728403a7e2911eb21daeeee0c1342710c3e2d5b2" + )] + public void Can_check_decryption_keys(bool expected, string dkHex, string eonKeyHex, string identityPreimageHex) + { + G1 dk = new(Convert.FromHexString(dkHex)); + G2 eonKey = new(Convert.FromHexString(eonKeyHex)); + byte[] identityPreimage = Convert.FromHexString(identityPreimageHex); + G1 identity = new(); + ShutterCrypto.ComputeIdentity(identity, identityPreimage); + + Assert.That(ShutterCrypto.CheckDecryptionKey(dk.ToAffine(), eonKey.ToAffine(), identity.ToAffine()), Is.EqualTo(expected)); + } + + [Test] + // encryption 4 + [TestCase( + "a24b9d6554912ef6874486ad8c42d3c0b0817997059d4c763c512bed3ccfd7fc", + "87c1c4c78a302e3ba808ffa76bd555c1aa9fa2846fc24ff2424a41586a3d8ab2a60003cb00e1cea6858eb0fed14d9e8c91741948", + "ac7ac24084236ddd6f4bee47e4a10086ff02345cce206e12befcc328425704b179ad21b50a69434a6c851286959382291513f6a856da9166cc0cd8c03a09f0dfa34876a88e7c664aeee6edfd5159b9f31e79f8ec9db7797d57c5086b1ff695e1", + "184172a7457952a02255adc9b723be43e29f50c3e074b118f185434153698835", + "038cad884b3457b04e2c1796722b7ed6e1f1da6a8290e0a013564bf139e167b11a114f70cb84e002292e517419a67265c10cf1d79a1e79bd8c2cc4fc56a408b2d54149e25ac53573f2c0006286271f70c456f0e10d4c5243b2004b529c03d8e3533c0a93a4510f6781ea682913b5b522ff47b1d8b3c5bd815409595e1d324cdcacda0e738d1a712698ae475549c684ff5f4289eca61d133764f1919b4d4bdd9081a19992bff0f40e056bae02dbd87cc71e482bc2efac89766c401c00fa1f708ae2" + )] + // encryption 9 + [TestCase( + "41206d657373616765", + "4609799139506408307ff4cd8933dc35d52e5e9d5c72b58b20ecb9cce93457b9e9f15dbb9683503def21bc5a8502978692764974", + "ac7ac24084236ddd6f4bee47e4a10086ff02345cce206e12befcc328425704b179ad21b50a69434a6c851286959382291513f6a856da9166cc0cd8c03a09f0dfa34876a88e7c664aeee6edfd5159b9f31e79f8ec9db7797d57c5086b1ff695e1", + "9bfb46b6ae9c1054a51eb8c639ce204e452ddf8eb75661c5ca34ca6861458584", + "03a33b390fdb98612a13dbfe05490aa6027e350ca3b204e57e0cef8fccd7ef57928850efc82d061702b889d3dbc02bd8f415dcb15c802d86733f217d8509f9ad6c666e9caac3cc45c63efa7b9dadeaf426254e4c41a69f7fccecf8f4820aecd0c419fdae1dd00353b498a510c25ebcaa902fc03b84254dc386bf9e51a8e77b47597a9063e1fe10ba563a13cd88ff4e66ee8135c029475de5ff559fdcc1aca86e3c" + )] + // encryption 2 + [TestCase( + "c1", + "1e3432a664e7674f99c4bebda3de85af745db21d20f53a8d7adefc9ce46c39721b73797a3dfd72fd7c45ac01a15e011acc9e187b", + "ac7ac24084236ddd6f4bee47e4a10086ff02345cce206e12befcc328425704b179ad21b50a69434a6c851286959382291513f6a856da9166cc0cd8c03a09f0dfa34876a88e7c664aeee6edfd5159b9f31e79f8ec9db7797d57c5086b1ff695e1", + "0608fb237111797214a33cd1ab072f0a576fc0b3016b3de669a42642c9cae165", + "03b902dcba95b9d0fd5b9000fbb79a840516ec62a8082beed4fcf725035e32282d7efe4209c50a5a578eef1be47cc30cc30b0edfb3b61e24af605d70616ac0bed3f79baca921492e88f72ccc8091d053f6c392bfe3c8e202e0a025e50aa4b404c9decdea2289f11fe0a377286cdaa460752096318210010b75dd6ee47bca1b44e121d4be9fe788c1ed2cf9f885c8bd6939baa64fa0add349ff864a61ae4a18586c" + )] + public void Can_encrypt_data(string msgHex, string identityPreimageHex, string eonKeyHex, string sigmaHex, string expectedHex) + { + byte[] msg = Convert.FromHexString(msgHex); + byte[] expected = Convert.FromHexString(expectedHex); + + G1 identity = new(); + ShutterCrypto.ComputeIdentity(identity, Convert.FromHexString(identityPreimageHex)); + G2 eonKey = new(Convert.FromHexString(eonKeyHex)); + Span sigma = Convert.FromHexString(sigmaHex).AsSpan(); + + EncryptedMessage c = ShutterCrypto.Encrypt(msg, identity, eonKey, sigma); + + Span encoded = ShutterCrypto.EncodeEncryptedMessage(c); + TestContext.Out.WriteLine("encrypted msg: " + Convert.ToHexString(encoded)); + Assert.That(encoded.ToArray(), Is.EqualTo(expected)); + } + + [Test] + // cryptotests decryption 4 + [TestCase( + "038cad884b3457b04e2c1796722b7ed6e1f1da6a8290e0a013564bf139e167b11a114f70cb84e002292e517419a67265c10cf1d79a1e79bd8c2cc4fc56a408b2d54149e25ac53573f2c0006286271f70c456f0e10d4c5243b2004b529c03d8e3533c0a93a4510f6781ea682913b5b522ff47b1d8b3c5bd815409595e1d324cdcacda0e738d1a712698ae475549c684ff5f4289eca61d133764f1919b4d4bdd9081a19992bff0f40e056bae02dbd87cc71e482bc2efac89766c401c00fa1f708ae2", + "95e84d8032618db55029bc7b60dbd0b0dbdae5da3fb89e38aa53bf5b84ea1b77cc6ba3ebd9eab5da54e7971fa79c816d", + "a24b9d6554912ef6874486ad8c42d3c0b0817997059d4c763c512bed3ccfd7fc" + )] + // decryption 6 + [TestCase( + "03882ff5ca64d6afcdde711841b969377f4d8fe0f9f5cebe519a4d064fda6842d8ab01fd858cb3714083169a78c7cb8dbd047acf11e2ed461a4af515714ca0318ec445cb3b400778320fa936c19455e6a4d205a6e9c1dafea0628918e5355b07588ed9b53cf49f8678d3099d0cb335c2b3b673effc30605faabf6af6f5751592e70a5b4e7332b18a08add17e36aac5db2c8c3ed19e3a96021ff4f73bb7ca0662b7b575c2775335eb56e20ad9216b7376df2a0ff80d12a9892dbbc969e5df8acfd89f15648700d1f1e14a6c57d376a6ddc661dcd047a8803604c06bacb24162a08d5a3587f35c307c44be7465b9b982cf1c40f2f90b11d8f4623013a2d516949cf1d820b91f6a80f7bcc1f0a04dd68f3dbd992a4866e33e17fd09b44b1b4e36f891754b23d2928f5106df445b065527235a8a6ebdb811a1f7325ba38ee2db602041bea9777065ec36a6f7ba0fcb897bf29cccf383883e7e571b772ca62199e4d56fc324219c55ae7ae92e5b2d84c03af19f78fae1a400f88a3adbce5ddc77ae2963a4746e629a5e5d5eb082488d382a3c1a16727cdb1f3849e4490ff754a88fd0260ce4d476e2c96c67b207bc83657a4f90560a314573a5e369090df1766d96f5cd", + "90adf61b0bbca0a477f12e6429c53735966ae10c77844e8989355cec7c2ef8e1cbe0f162463014034688bcebf98f5e9e", + "1e366ff59c0e891e094e1a10a2bf2c8a8d7091bb0a2c725d324984a9131304b16a7fba17d65ec729c010c5741711747a3ee5cc59a0730bd2eac0b538a8cfa9430beebe984758067e9e555283dcbeb1495444a36b0d52384a08f29b638ffd30006ed13130ada213e693defe568a3f2b9d251ce996fcc4dd1d07e83a23b4251358d80b81ccf1a5936b747fbe6d4f2fca241d1f149267d592717b4302aa06ed48c4f098f0ca782e229b619dfe390490b64d38712fbaae8edf044d0f78f696bf7c522dec976d5f219f7254ad5ac0f8346481064710f02c4db0f1aa2164afd0b6153de841070444e013876e032adfa3e3b0dab8a337f6414701b48fdafcc527535622688a06b490851463f60ae0385e218858d1f3a3a9418eeef8f931c50ec13c23bf8543889dfa22d891bee0d6efc2cce322517bd6ba429b5647bed0254cf42eed" + )] + public void Can_decrypt_data(string cipherTextHex, string decryptionKeyHex, string expectedHex) + { + EncryptedMessage c = ShutterCrypto.DecodeEncryptedMessage(Convert.FromHexString(cipherTextHex)); + G1 decryptionKey = new(Convert.FromHexString(decryptionKeyHex)); + + // recover sigma + GT p = new(decryptionKey, c.C1); + Span sigma = ShutterCrypto.Hash2(p); // key + sigma.Xor(c.C2); + + int len = ShutterCrypto.GetDecryptedDataLength(c); + Span decryptedMessage = stackalloc byte[len]; + ShutterCrypto.Decrypt(ref decryptedMessage, c, decryptionKey); + TestContext.Out.WriteLine("decrypted msg: " + Convert.ToHexString(decryptedMessage)); + + Assert.That(decryptedMessage.SequenceEqual(Convert.FromHexString(expectedHex))); + } + + [Test] + [TestCase( + "0000000000000027d806bfddbebe11f7ee8a39fc7dc24498de85c8afca0000000000000000000000000000000001", + "846b23860007d8d735a46364087f4fe90cfd2d129af7c593079f04186d2a71826c3ed4fefd7403eb452c4160d23c4d6c0d4f04d46333117a39aacc2c59e5384c5d5455bb6887e7979454948962afcb41e0f928c64a75001a71883f5ed7d02d81", + "afc199da35e82d41a92b2f06cc05377f394cdce127394a4c49bc18e90c90b4d93cb1c2c6c232d7b8fbc7573ec724c0d5" + )] + [TestCase( + "0000000000000027d806bfddbebe11f7ee8a39fc7dc24498de85c8afca00000000000001f4000000000000000001", + "a4a2e5b38f761fe45084fb2ae14878873d5a3b99133aa86db36abc3318e7cd1ef12974420ffed2fa3cda382b8a51e6f304104887f2393c45cca95d9e0f9ddf6e18c054f2c4c755a03e601dff74a4bcc55210e13c134e3cf983c20515aafb5a71", + "90dcd8a9b9b86b9df077e3fda45a92647422b00d70eb37372200703ef20e2d8a9eba57e9e09ae77d6c54ff8ded3b92b3" + )] + public void Can_verify_validator_registration_signature(string msgHex, string sigHex, string pkHex) + { + Assert.That(ShutterCrypto.CheckValidatorRegistrySignature( + Convert.FromHexString(pkHex), + Convert.FromHexString(sigHex), + Convert.FromHexString(msgHex) + )); + } + + [Test] + [TestCase( + 7649174914161947266ul, + 16729082666370017565ul, + 8333205535599204084ul, + 17223499624376311426ul, + "0x932552E9df00550E4c59fA4C233B440743e85974", + "a9358a3e475e373d4749b9bce38df386e90b5b84742d77881448a6ce0db07e3077f8652d0133488962b7543b642c1025066904fb5c4278b91be6892b86c314c400", + new string[] { "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334" } + )] + [TestCase( + 60ul, + 1ul, + 10457208ul, + 619ul, + "0xcb770a9b31ac28b0c90d0357f8df7c1c1cd660be", + "C3A9E42322917542E56FCEB963612161ECE4751E5BB4232CE030E6C1FDD8AA9B01551565037C355874858CC595FCE595368F071572F7CE6F4BDD9DEA3A7514C800", + new string[] { "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009F9078" } + )] + public void Can_verify_decryption_key_signatures(ulong instanceId, ulong eon, ulong slot, ulong txPointer, string keyperAddress, string sigHex, string[] identityPreimagesHex) + { + IEnumerable> identityPreimages = identityPreimagesHex.Select(Convert.FromHexString).Select(b => (ReadOnlyMemory)b); + Assert.That(ShutterCrypto.CheckSlotDecryptionIdentitiesSignature(instanceId, eon, slot, txPointer, identityPreimages, Convert.FromHexString(sigHex), new(keyperAddress))); + } +} diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterEventSimulator.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterEventSimulator.cs new file mode 100644 index 00000000000..06460d48b08 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterEventSimulator.cs @@ -0,0 +1,286 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Int256; +using System.Linq; +using Google.Protobuf; +using Nethermind.Core.Extensions; +using Nethermind.Core.Crypto; +using Nethermind.Crypto; +using Nethermind.Core; +using System.Collections.Generic; +using Nethermind.Shutter.Dto; +using Nethermind.Serialization.Rlp; +using Nethermind.Core.Test.Builders; +using Nethermind.Abi; + +using G1 = Nethermind.Crypto.Bls.P1; +using G2 = Nethermind.Crypto.Bls.P2; +using EncryptedMessage = Nethermind.Shutter.ShutterCrypto.EncryptedMessage; +using Nethermind.Shutter.Contracts; + +namespace Nethermind.Shutter.Test; + +public class ShutterEventSimulator +{ + private const ulong DefaultGasLimit = 21000; + private readonly int _defaultMaxKeyCount; + private readonly Random _rnd; + private readonly ulong _chainId; + private readonly ulong _threshold; + private readonly IAbiEncoder _abiEncoder; + private readonly Address _sequencerContractAddress; + private readonly AbiEncodingInfo _transactionSubmittedAbi; + private ulong _slot; + protected ulong _eon; + private ulong _txPointer; // for tracking which keys are released + private readonly IEnumerable _eventSource; + private readonly Queue<(byte[] IdentityPreimage, byte[] Key)>[] _keys = new Queue<(byte[] IdentityPreimage, byte[] Key)>[10]; + private readonly EonData[] _eonData = new EonData[10]; + + public ShutterEventSimulator( + Random rnd, + ulong chainId, + ulong threshold, + ulong slot, + IAbiEncoder abiEncoder, + Address sequencerContractAddress + ) + { + _rnd = rnd; + _chainId = chainId; + _eon = 0; + _slot = slot; + _txPointer = 0; + _threshold = threshold; + _abiEncoder = abiEncoder; + _sequencerContractAddress = sequencerContractAddress; + _transactionSubmittedAbi = new SequencerContract(sequencerContractAddress).TransactionSubmittedAbi; + _defaultMaxKeyCount = (int)Math.Floor((decimal)ShutterTestsCommon.Cfg.EncryptedGasLimit / DefaultGasLimit); + + _eventSource = EmitEvents(); + + for (ulong i = 0; i < 10; i++) + { + _eonData[i] = new EonData(_rnd, i); + _keys[i] = []; + } + } + + public struct Event + { + public byte[] EncryptedTransaction; + public byte[] IdentityPreimage; + public byte[] Key; + public LogEntry LogEntry; + public byte[] Transaction; + public ulong Eon; + } + + public List GetEvents(int c) + { + return _eventSource.Take(c).ToList(); + } + + public (List events, DecryptionKeys keys) AdvanceSlot(int eventCount, int? keyCount) + { + var events = _eventSource.Take(eventCount).ToList(); + foreach (Event e in events) + { + _keys[e.Eon].Enqueue((e.IdentityPreimage, e.Key)); + } + + keyCount ??= Math.Min(_keys[_eon].Count, _defaultMaxKeyCount); + + List<(byte[] IdentityPreimage, byte[] Key)> keys = []; + for (int i = 0; i < keyCount; i++) + { + keys.Add(_keys[_eon].Dequeue()); + } + DecryptionKeys decryptionKeys = ToDecryptionKeys(keys, _txPointer, (int)_threshold); + + _slot++; + _txPointer += (ulong)keyCount; + return (events, decryptionKeys); + } + + protected virtual IEnumerable EmitEvents() + { + return EmitEvents(EmitDefaultEons(), EmitDefaultTransactions()); + } + + protected IEnumerable EmitEvents(IEnumerable eons, IEnumerable transactions) + { + foreach ((ulong eon, Transaction tx) in eons.Zip(transactions)) + { + byte[] identityPreimage = new byte[52]; + byte[] sigma = new byte[32]; + _rnd.NextBytes(identityPreimage); + _rnd.NextBytes(sigma); + + ulong txIndex = _eonData[eon].TxIndex++; + G1 identity = new(); + ShutterCrypto.ComputeIdentity(identity, identityPreimage); + G1 key = identity.Dup().Mult(_eonData[eon].SecretKey.ToLittleEndian()); + + byte[] encodedTx = Rlp.Encode(tx, RlpBehaviors.SkipTypedWrapping).Bytes; + EncryptedMessage encryptedMessage = ShutterCrypto.Encrypt(encodedTx, identity, new(_eonData[eon].Key), new(sigma)); + byte[] encryptedTx = ShutterCrypto.EncodeEncryptedMessage(encryptedMessage).ToArray(); + + yield return new() + { + EncryptedTransaction = encryptedTx, + IdentityPreimage = identityPreimage, + Key = key.Compress(), + LogEntry = EncodeShutterLog(encryptedTx, identityPreimage, eon, txIndex, DefaultGasLimit), + Transaction = encodedTx, + Eon = eon + }; + } + } + + protected IEnumerable EmitDefaultEons() + { + while (true) + { + yield return _eon; + } + } + + protected IEnumerable EmitDefaultTransactions() + { + ulong nonce = 0; + // alternate legacy and type 2 transactions + bool type2 = false; + while (true) + { + TransactionBuilder txBuilder = Build.A.Transaction + .WithNonce(nonce++) + .WithChainId(_chainId) + .WithSenderAddress(TestItem.AddressA) + .WithTo(TestItem.AddressA) + .WithValue(100); + + if (type2) + { + txBuilder = txBuilder + .WithType(TxType.EIP1559) + .WithMaxFeePerGas(4) + .WithGasLimit(21000); + + type2 = false; + } + else + { + type2 = true; + } + + yield return txBuilder.Signed(TestItem.PrivateKeyA).TestObject; + } + } + + public void NextEon() + { + _eon++; + _txPointer = 0; + } + + public IShutterEon.Info GetCurrentEonInfo() + { + EonData eonData = _eonData.ElementAt((int)_eon); + return new() + { + Eon = _eon, + Key = new G2(eonData.Key.AsSpan()).Compress(), + Threshold = _threshold, + Addresses = TestItem.Addresses + }; + } + + private LogEntry EncodeShutterLog( + ReadOnlySpan encryptedTransaction, + ReadOnlySpan identityPreimage, + ulong eon, + ulong txIndex, + UInt256 gasLimit) + { + byte[] logData = _abiEncoder.Encode(_transactionSubmittedAbi, [ + eon, + txIndex, + identityPreimage[..32].ToArray(), + new Address(identityPreimage[32..].ToArray()), + encryptedTransaction.ToArray(), + gasLimit + ]); + + return Build.A.LogEntry + .WithAddress(_sequencerContractAddress) + .WithTopics(_transactionSubmittedAbi.Signature.Hash) + .WithData(logData) + .TestObject; + } + + private DecryptionKeys ToDecryptionKeys(List<(byte[] IdentityPreimage, byte[] Key)> rawKeys, ulong txPointer, int signatureCount) + { + rawKeys.Sort((a, b) => Bytes.BytesComparer.Compare(a.IdentityPreimage, b.IdentityPreimage)); + rawKeys.Insert(0, ([], [])); + + var keys = rawKeys.Select(k => new Key() + { + Identity = ByteString.CopyFrom(k.IdentityPreimage), + Key_ = ByteString.CopyFrom(k.Key), + }).ToList(); + + IEnumerable> identityPreimages = rawKeys.Select(k => (ReadOnlyMemory)k.IdentityPreimage); + List randomIndices = Enumerable.Range(0, TestItem.PublicKeys.Length).Shuffle(_rnd).ToList(); + + List signerIndices = []; + List signatures = []; + + for (int i = 0; i < signatureCount; i++) + { + ulong index = (ulong)randomIndices[i]; + PrivateKey sk = TestItem.PrivateKeys[index]; + Hash256 h = ShutterCrypto.GenerateHash(ShutterTestsCommon.Cfg.InstanceID, _eon, _slot, txPointer, identityPreimages); + byte[] sig = ShutterTestsCommon.Ecdsa.Sign(sk, h).BytesWithRecovery; + signerIndices.Add(index); + signatures.Add(ByteString.CopyFrom(sig)); + } + + GnosisDecryptionKeysExtra gnosis = new() + { + Slot = _slot, + TxPointer = txPointer, + SignerIndices = { signerIndices }, + Signatures = { signatures } + }; + + return new() + { + InstanceID = ShutterTestsCommon.Cfg.InstanceID, + Eon = _eon, + Keys = { keys }, + Gnosis = gnosis + }; + } + + private struct EonData + { + public ulong Eon { get; } + public byte[] Key { get; } + public UInt256 SecretKey { get; } + public ulong TxIndex { get; set; } + + public EonData(Random rnd, ulong eon) + { + byte[] sk = new byte[32]; + rnd.NextBytes(sk); + + Eon = eon; + SecretKey = new(sk); + Key = G2.Generator().Mult(SecretKey.ToLittleEndian()).Compress(); + TxIndex = 0; + } + } +} diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs new file mode 100644 index 00000000000..0dee393917f --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Nethermind.Merge.Plugin; +using Nethermind.Merge.Plugin.Data; +using Nethermind.Consensus.Producers; +using Nethermind.Core; +using System.Threading; +using Nethermind.Merge.Plugin.Test; + +namespace Nethermind.Shutter.Test; + +[TestFixture] +public class ShutterIntegrationTests : EngineModuleTests +{ + private const int BuildingSlot = (int)ShutterTestsCommon.InitialSlot; + private const ulong BuildingSlotTimestamp = ShutterTestsCommon.InitialSlotTimestamp; + + [Test] + public async Task Can_load_when_previous_block_arrives_late() + { + Random rnd = new(ShutterTestsCommon.Seed); + Timestamper timestamper = ShutterTestsCommon.InitTimestamper(BuildingSlotTimestamp - 5, 0); + PayloadAttributes payloadAttributes = new() + { + Timestamp = BuildingSlotTimestamp + }; + + using var chain = (ShutterTestBlockchain)await new ShutterTestBlockchain(rnd, timestamper).Build(ShutterTestsCommon.SpecProvider); + IEngineRpcModule rpc = CreateEngineModule(chain); + IReadOnlyList executionPayloads = await ProduceBranchV1(rpc, chain, BuildingSlot - 2, CreateParentBlockRequestOnHead(chain.BlockTree), true, null, 5); + ExecutionPayload lastPayload = executionPayloads[^1]; + + // keys arrive 5 seconds before slot start + // waits for previous block to timeout then loads txs + chain.Api!.AdvanceSlot(20); + + // no events loaded initially + var txs = chain.Api.TxSource.GetTransactions(chain.BlockTree!.Head!.Header, 0, payloadAttributes).ToList(); + Assert.That(txs, Has.Count.EqualTo(0)); + + // after timeout they should be loaded + using CancellationTokenSource cts = new(); + await chain.Api.TxSource.WaitForTransactions((ulong)BuildingSlot, cts.Token); + txs = chain.Api.TxSource.GetTransactions(chain.BlockTree!.Head!.Header, 0, payloadAttributes).ToList(); + Assert.That(txs, Has.Count.EqualTo(20)); + + // late block arrives, then next block should contain loaded transactions + IReadOnlyList payloads = await ProduceBranchV1(rpc, chain, 2, lastPayload, true, null, 5); + lastPayload = payloads[^1]; + lastPayload.TryGetBlock(out Block? b); + Assert.That(b!.Transactions, Has.Length.EqualTo(20)); + } + + + [Test] + public async Task Can_load_when_block_arrives_before_keys() + { + Random rnd = new(ShutterTestsCommon.Seed); + Timestamper timestamper = ShutterTestsCommon.InitTimestamper(BuildingSlotTimestamp, 0); + PayloadAttributes payloadAttributes = new() + { + Timestamp = BuildingSlotTimestamp + }; + + using var chain = (ShutterTestBlockchain)await new ShutterTestBlockchain(rnd, timestamper).Build(ShutterTestsCommon.SpecProvider); + IEngineRpcModule rpc = CreateEngineModule(chain); + IReadOnlyList executionPayloads = await ProduceBranchV1(rpc, chain, BuildingSlot - 2, CreateParentBlockRequestOnHead(chain.BlockTree), true, null, 5); + ExecutionPayload lastPayload = executionPayloads[executionPayloads.Count - 1]; + + // no events loaded initially + var txs = chain.Api!.TxSource.GetTransactions(chain.BlockTree!.Head!.Header, 0, payloadAttributes).ToList(); + Assert.That(txs, Has.Count.EqualTo(0)); + + chain.Api.AdvanceSlot(20); + + IReadOnlyList payloads = await ProduceBranchV1(rpc, chain, 1, lastPayload, true, null, 5); + lastPayload = payloads[0]; + + txs = chain.Api.TxSource.GetTransactions(chain.BlockTree!.Head!.Header, 0, payloadAttributes).ToList(); + Assert.That(txs, Has.Count.EqualTo(20)); + + payloads = await ProduceBranchV1(rpc, chain, 1, lastPayload, true, null, 5); + lastPayload = payloads[0]; + lastPayload.TryGetBlock(out Block? b); + Assert.That(b!.Transactions, Has.Length.EqualTo(20)); + } + + [Test] + [NonParallelizable] + public async Task Can_increment_metric_on_missed_keys() + { + Random rnd = new(ShutterTestsCommon.Seed); + long time = 1; + Timestamper timestamper = new(time); + + Metrics.ShutterKeysMissed = 0; + + using var chain = (ShutterTestBlockchain)await new ShutterTestBlockchain(rnd, timestamper).Build(ShutterTestsCommon.SpecProvider); + IEngineRpcModule rpc = CreateEngineModule(chain); + + ExecutionPayload lastPayload = CreateParentBlockRequestOnHead(chain.BlockTree); + for (int i = 0; i < 5; i++) + { + // KeysMissed will be incremented when get_payload is called + lastPayload = (await ProduceBranchV1(rpc, chain, 1, lastPayload, true, null, 5))[0]; + + time += (long)ShutterTestsCommon.SlotLength.TotalSeconds; + } + + Assert.That(Metrics.ShutterKeysMissed, Is.EqualTo(5)); + } + +} diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterKeyValidatorTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterKeyValidatorTests.cs new file mode 100644 index 00000000000..e7b2f65c826 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterKeyValidatorTests.cs @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace Nethermind.Shutter.Test; + +[TestFixture] +class ShutterKeyValidatorTests +{ + [Test] + public void Accepts_valid_decryption_keys() + { + Random rnd = new(ShutterTestsCommon.Seed); + ShutterApiSimulator api = ShutterTestsCommon.InitApi(rnd); + + Assert.That(api.KeysValidated, Is.EqualTo(0)); + + api.AdvanceSlot(5); + + Assert.That(api.KeysValidated, Is.EqualTo(1)); + } + + [Test] + public void Rejects_outdated_decryption_keys() + { + Random rnd = new(ShutterTestsCommon.Seed); + ShutterApiSimulator api = ShutterTestsCommon.InitApi(rnd); + + (List _, Dto.DecryptionKeys keys) = api.AdvanceSlot(5); + + Assert.That(api.KeysValidated, Is.EqualTo(1)); + + // should ignore more keys from the same slot + api.TriggerKeysReceived(keys); + + Assert.That(api.KeysValidated, Is.EqualTo(1)); + } +} diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterTestBlockchain.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterTestBlockchain.cs new file mode 100644 index 00000000000..b006dfdb4a6 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterTestBlockchain.cs @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Consensus; +using Nethermind.Consensus.Comparers; +using Nethermind.Consensus.Producers; +using Nethermind.Core; +using static Nethermind.Merge.AuRa.Test.AuRaMergeEngineModuleTests; + +namespace Nethermind.Shutter.Test; + +public class ShutterTestBlockchain(Random rnd, ITimestamper? timestamper = null, ShutterEventSimulator? eventSimulator = null) : MergeAuRaTestBlockchain(null, null) +{ + public ShutterApiSimulator? Api { get => _api; } + private ShutterApiSimulator? _api; + protected readonly Random _rnd = rnd; + protected readonly ITimestamper? _timestamper = timestamper; + + protected virtual ShutterApiSimulator CreateShutterApi() + => ShutterTestsCommon.InitApi(_rnd, this, _timestamper, eventSimulator); + + protected override IBlockProducer CreateTestBlockProducer(TxPoolTxSource txPoolTxSource, ISealer sealer, ITransactionComparerProvider transactionComparerProvider) + { + _api = CreateShutterApi(); + _additionalTxSource = _api.TxSource; + return base.CreateTestBlockProducer(txPoolTxSource, sealer, transactionComparerProvider); + } + + protected override IBlockImprovementContextFactory CreateBlockImprovementContextFactory(IBlockProducer blockProducer) + => _api!.GetBlockImprovementContextFactory(blockProducer); +} diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterTestsCommon.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterTestsCommon.cs new file mode 100644 index 00000000000..03b157963df --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterTestsCommon.cs @@ -0,0 +1,86 @@ + +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using Nethermind.Abi; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Receipts; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Crypto; +using Nethermind.Facade.Find; +using Nethermind.Logging; +using Nethermind.Shutter.Config; +using Nethermind.Specs; +using Nethermind.State; +using NSubstitute; + +using static Nethermind.Merge.Plugin.Test.EngineModuleTests; + +namespace Nethermind.Shutter.Test; +class ShutterTestsCommon +{ + public const int Seed = 100; + public const ulong InitialSlot = 21; + public const ulong InitialSlotTimestamp = 1 + 21 * 5; + public const ulong Threshold = 10; + public const int ChainId = BlockchainIds.Chiado; + public const ulong GenesisTimestamp = 1; + public static readonly TimeSpan SlotLength = TimeSpan.FromSeconds(5); + public static readonly TimeSpan BlockUpToDateCutoff = TimeSpan.FromSeconds(5); + public static readonly ISpecProvider SpecProvider = ChiadoSpecProvider.Instance; + public static readonly IEthereumEcdsa Ecdsa = new EthereumEcdsa(ChainId); + public static readonly ILogManager LogManager = LimboLogs.Instance; + public static readonly AbiEncoder AbiEncoder = new(); + public static readonly ShutterConfig Cfg = new() + { + InstanceID = 0, + ValidatorRegistryContractAddress = Address.Zero.ToString(), + ValidatorRegistryMessageVersion = 0, + KeyBroadcastContractAddress = Address.Zero.ToString(), + KeyperSetManagerContractAddress = Address.Zero.ToString(), + SequencerContractAddress = Address.Zero.ToString(), + EncryptedGasLimit = 21000 * 20, + Validator = true + }; + + public static ShutterApiSimulator InitApi(Random rnd, ITimestamper? timestamper = null, ShutterEventSimulator? eventSimulator = null) + { + IWorldStateManager worldStateManager = Substitute.For(); + ILogFinder logFinder = Substitute.For(); + IBlockTree blockTree = Substitute.For(); + IReceiptStorage receiptStorage = Substitute.For(); + return new( + eventSimulator ?? InitEventSimulator(rnd), + AbiEncoder, blockTree, Ecdsa, logFinder, receiptStorage, + LogManager, SpecProvider, timestamper ?? Substitute.For(), + worldStateManager, Cfg, [], rnd + ); + } + + public static ShutterApiSimulator InitApi(Random rnd, MergeTestBlockchain chain, ITimestamper? timestamper = null, ShutterEventSimulator? eventSimulator = null) + => new( + eventSimulator ?? InitEventSimulator(rnd), + AbiEncoder, chain.BlockTree.AsReadOnly(), chain.EthereumEcdsa, chain.LogFinder, chain.ReceiptStorage, + chain.LogManager, chain.SpecProvider, timestamper ?? chain.Timestamper, chain.WorldStateManager, Cfg, [], rnd + ); + + public static ShutterEventSimulator InitEventSimulator(Random rnd) + => new( + rnd, + ChainId, + Threshold, + InitialSlot, + AbiEncoder, + new(Cfg.SequencerContractAddress!) + ); + + public static Timestamper InitTimestamper(ulong slotTimestamp, ulong offsetMs) + { + ulong timestampMs = slotTimestamp * 1000 + offsetMs; + var blockTime = DateTimeOffset.FromUnixTimeMilliseconds((long)timestampMs); + return new(blockTime.UtcDateTime); + } +} diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterTxFilterTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterTxFilterTests.cs new file mode 100644 index 00000000000..e3e0d7c3aa3 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterTxFilterTests.cs @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using NUnit.Framework; +using Nethermind.Logging; +using Nethermind.Specs; +using Nethermind.Core.Test.Builders; + +namespace Nethermind.Shutter.Test; + +[TestFixture] +class ShutterTxFilterTests +{ + [Test] + public void Accepts_valid_txs() + { + Transaction tx0 = Build.A.Transaction + .WithChainId(BlockchainIds.Chiado) + .WithSenderAddress(TestItem.AddressA) + .WithTo(TestItem.AddressA) + .WithValue(100) + .Signed(TestItem.PrivateKeyA).TestObject; + + Transaction tx1 = Build.A.Transaction + .WithChainId(BlockchainIds.Chiado) + .WithSenderAddress(TestItem.AddressA) + .WithTo(TestItem.AddressA) + .WithValue(100) + .WithType(TxType.EIP1559) + .WithMaxFeePerGas(4) + .WithGasLimit(21000) + .Signed(TestItem.PrivateKeyA).TestObject; + + Assert.Multiple(() => + { + Assert.That(IsAllowed(tx0)); + Assert.That(IsAllowed(tx1)); + }); + } + + [Test] + public void Rejects_blob_tx() + { + Transaction tx = Build.A.Transaction + .WithChainId(BlockchainIds.GenericNonRealNetwork) + .WithSenderAddress(TestItem.AddressA) + .WithTo(TestItem.AddressA) + .WithType(TxType.Blob) + .WithMaxFeePerBlobGas(2) + .WithBlobVersionedHashes(0) + .Signed(TestItem.PrivateKeyA).TestObject; + + Assert.That(IsAllowed(tx), Is.False); + } + + [Test] + public void Rejects_wrong_chain_id() + { + Transaction tx = Build.A.Transaction + .WithChainId(BlockchainIds.GenericNonRealNetwork) + .WithSenderAddress(TestItem.AddressA) + .WithTo(TestItem.AddressA) + .WithValue(100) + .Signed(TestItem.PrivateKeyA).TestObject; + + Assert.That(IsAllowed(tx), Is.False); + } + + [Test] + public void Rejects_bad_signature() + { + Core.Crypto.Signature sig = new(new byte[65]); + + Transaction tx = Build.A.Transaction + .WithChainId(BlockchainIds.Chiado) + .WithSenderAddress(TestItem.AddressA) + .WithTo(TestItem.AddressA) + .WithValue(100) + .WithSignature(sig).TestObject; + + Assert.That(IsAllowed(tx), Is.False); + } + + private static bool IsAllowed(Transaction tx) + { + ShutterTxFilter txFilter = new(ChiadoSpecProvider.Instance, LimboLogs.Instance); + return txFilter.IsAllowed(tx, Build.A.BlockHeader.TestObject); + } +} diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterTxLoaderTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterTxLoaderTests.cs new file mode 100644 index 00000000000..fa428ea8694 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterTxLoaderTests.cs @@ -0,0 +1,230 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using Nethermind.Core; +using NUnit.Framework; +using System.Threading.Tasks; +using Nethermind.Merge.Plugin; +using Nethermind.Merge.Plugin.Data; +using Nethermind.Abi; +using Nethermind.Core.Test.Builders; +using Nethermind.Merge.Plugin.Test; + +namespace Nethermind.Shutter.Test; + +[TestFixture] +class ShutterTxLoaderTests : EngineModuleTests +{ + private class ShutterEventSimulatorHalfInvalid(Random rnd, ulong chainId, ulong threshold, ulong slot, IAbiEncoder abiEncoder, Address sequencerContractAddress) : ShutterEventSimulator(rnd, chainId, threshold, slot, abiEncoder, sequencerContractAddress) + { + private readonly Transaction _validTx = Build.A.Transaction.WithChainId(chainId).Signed().TestObject; + private readonly Transaction _invalidTx = Build.A.Transaction.TestObject; + protected override IEnumerable EmitEvents() + { + IEnumerable EmitHalfInvalid() + { + bool valid = false; + while (true) + { + valid = !valid; + yield return valid ? _validTx : _invalidTx; + } + } + + return EmitEvents(EmitDefaultEons(), EmitHalfInvalid()); + } + } + + private class ShutterEventSimulatorHalfNextEon(Random rnd, ulong chainId, ulong threshold, ulong slot, IAbiEncoder abiEncoder, Address sequencerContractAddress) : ShutterEventSimulator(rnd, chainId, threshold, slot, abiEncoder, sequencerContractAddress) + { + protected override IEnumerable EmitEvents() + { + IEnumerable EmitHalfNextEon() + { + bool next = false; + while (true) + { + next = !next; + yield return next ? _eon + 1 : _eon; + } + } + + return EmitEvents(EmitHalfNextEon(), EmitDefaultTransactions()); + } + } + + [Test] + public async Task Can_load_transactions_over_slots() + { + Random rnd = new(ShutterTestsCommon.Seed); + + using var chain = (ShutterTestBlockchain)await new ShutterTestBlockchain(rnd).Build(ShutterTestsCommon.SpecProvider); + IEngineRpcModule rpc = CreateEngineModule(chain); + IReadOnlyList executionPayloads = await ProduceBranchV1(rpc, chain, 20, CreateParentBlockRequestOnHead(chain.BlockTree), true, null, 5); + ExecutionPayload lastPayload = executionPayloads[executionPayloads.Count - 1]; + + for (int i = 0; i < 20; i++) + { + chain.Api!.AdvanceSlot(20); + + Assert.That(chain.Api.LoadedTransactions!.Value.Transactions, Has.Length.EqualTo(20)); + + IReadOnlyList payloads = await ProduceBranchV1(rpc, chain, 1, lastPayload, true, null, 5); + lastPayload = payloads[0]; + } + } + + [Test] + public async Task Can_load_and_filter_transactions() + { + Random rnd = new(ShutterTestsCommon.Seed); + ShutterEventSimulatorHalfNextEon eventSimulator = new( + rnd, + ShutterTestsCommon.ChainId, + ShutterTestsCommon.Threshold, + ShutterTestsCommon.InitialSlot, + ShutterTestsCommon.AbiEncoder, + new(ShutterTestsCommon.Cfg.SequencerContractAddress!) + ); + + using var chain = (ShutterTestBlockchain)await new ShutterTestBlockchain(rnd, null, eventSimulator).Build(ShutterTestsCommon.SpecProvider); + IEngineRpcModule rpc = CreateEngineModule(chain); + IReadOnlyList executionPayloads = await ProduceBranchV1(rpc, chain, 20, CreateParentBlockRequestOnHead(chain.BlockTree), true, null, 5); + ExecutionPayload lastPayload = executionPayloads[executionPayloads.Count - 1]; + + chain.Api!.AdvanceSlot(20); + + // half of transactions were invalid, should have been filtered + Assert.That(chain.Api.LoadedTransactions!.Value.Transactions, Has.Length.EqualTo(10)); + } + + [Test] + public async Task Can_load_up_to_gas_limit() + { + Random rnd = new(ShutterTestsCommon.Seed); + + using var chain = (ShutterTestBlockchain)await new ShutterTestBlockchain(rnd).Build(ShutterTestsCommon.SpecProvider); + IEngineRpcModule rpc = CreateEngineModule(chain); + IReadOnlyList executionPayloads = await ProduceBranchV1(rpc, chain, 20, CreateParentBlockRequestOnHead(chain.BlockTree), true, null, 5); + ExecutionPayload lastPayload = executionPayloads[executionPayloads.Count - 1]; + + chain.Api!.AdvanceSlot(40); + + Assert.Multiple(() => + { + Assert.That(chain.Api.LoadedTransactions!.Value.Slot, Is.EqualTo(ShutterTestsCommon.InitialSlot)); + Assert.That(chain.Api.LoadedTransactions.Value.Transactions, Has.Length.EqualTo(20)); + }); + + + IReadOnlyList payloads = await ProduceBranchV1(rpc, chain, 1, lastPayload, true, null, 5); + lastPayload = payloads[0]; + + chain.Api.AdvanceSlot(0); + + Assert.Multiple(() => + { + Assert.That(chain.Api.LoadedTransactions!.Value.Slot, Is.EqualTo(ShutterTestsCommon.InitialSlot + 1)); + Assert.That(chain.Api.LoadedTransactions.Value.Transactions, Has.Length.EqualTo(20)); + }); + + + payloads = await ProduceBranchV1(rpc, chain, 1, lastPayload, true, null, 5); + lastPayload = payloads[0]; + + chain.Api.AdvanceSlot(0); + + Assert.Multiple(() => + { + Assert.That(chain.Api.LoadedTransactions!.Value.Slot, Is.EqualTo(ShutterTestsCommon.InitialSlot + 2)); + Assert.That(chain.Api.LoadedTransactions.Value.Transactions, Has.Length.EqualTo(0)); + }); + + } + + [Test] + public async Task Can_load_transactions_over_eons() + { + Random rnd = new(ShutterTestsCommon.Seed); + + using var chain = (ShutterTestBlockchain)await new ShutterTestBlockchain(rnd).Build(ShutterTestsCommon.SpecProvider); + IEngineRpcModule rpc = CreateEngineModule(chain); + IReadOnlyList executionPayloads = await ProduceBranchV1(rpc, chain, 20, CreateParentBlockRequestOnHead(chain.BlockTree), true, null, 5); + ExecutionPayload lastPayload = executionPayloads[executionPayloads.Count - 1]; + + chain.Api!.AdvanceSlot(5); + + Assert.Multiple(() => + { + Assert.That(chain.Api.LoadedTransactions!.Value.Slot, Is.EqualTo(ShutterTestsCommon.InitialSlot)); + Assert.That(chain.Api.LoadedTransactions.Value.Transactions, Has.Length.EqualTo(5)); + }); + + + IReadOnlyList payloads = await ProduceBranchV1(rpc, chain, 1, lastPayload, true, null, 5); + lastPayload = payloads[0]; + + chain.Api.NextEon(); + chain.Api.AdvanceSlot(5); + + Assert.Multiple(() => + { + Assert.That(chain.Api.LoadedTransactions!.Value.Slot, Is.EqualTo(ShutterTestsCommon.InitialSlot + 1)); + Assert.That(chain.Api.LoadedTransactions.Value.Transactions, Has.Length.EqualTo(5)); + }); + } + + [Test] + public async Task Can_scan_logs_to_genesis() + { + Random rnd = new(ShutterTestsCommon.Seed); + + using var chain = (ShutterTestBlockchain)await new ShutterTestBlockchain(rnd).Build(ShutterTestsCommon.SpecProvider); + IEngineRpcModule rpc = CreateEngineModule(chain); + IReadOnlyList executionPayloads = await ProduceBranchV1(rpc, chain, 20, CreateParentBlockRequestOnHead(chain.BlockTree), true, null, 5); + ExecutionPayload lastPayload = executionPayloads[executionPayloads.Count - 1]; + + Assert.DoesNotThrow(() => chain.Api!.AdvanceSlot(0)); + } + + [Test] + public async Task Can_load_transactions_with_overlapping_eons() + { + Random rnd = new(ShutterTestsCommon.Seed); + ShutterEventSimulatorHalfNextEon eventSimulator = new( + rnd, + ShutterTestsCommon.ChainId, + ShutterTestsCommon.Threshold, + ShutterTestsCommon.InitialSlot, + ShutterTestsCommon.AbiEncoder, + new(ShutterTestsCommon.Cfg.SequencerContractAddress!) + ); + + using var chain = (ShutterTestBlockchain)await new ShutterTestBlockchain(rnd, null, eventSimulator).Build(ShutterTestsCommon.SpecProvider); + IEngineRpcModule rpc = CreateEngineModule(chain); + IReadOnlyList executionPayloads = await ProduceBranchV1(rpc, chain, 20, CreateParentBlockRequestOnHead(chain.BlockTree), true, null, 5); + ExecutionPayload lastPayload = executionPayloads[executionPayloads.Count - 1]; + + chain.Api!.AdvanceSlot(20); + + Assert.Multiple(() => + { + Assert.That(chain.Api.LoadedTransactions!.Value.Slot, Is.EqualTo(ShutterTestsCommon.InitialSlot)); + Assert.That(chain.Api.LoadedTransactions.Value.Transactions, Has.Length.EqualTo(10)); + }); + + IReadOnlyList payloads = await ProduceBranchV1(rpc, chain, 1, lastPayload, true, null, 5); + lastPayload = payloads[0]; + + chain.Api.NextEon(); + chain.Api.AdvanceSlot(0); + + Assert.Multiple(() => + { + Assert.That(chain.Api.LoadedTransactions!.Value.Slot, Is.EqualTo(ShutterTestsCommon.InitialSlot + 1)); + Assert.That(chain.Api.LoadedTransactions.Value.Transactions, Has.Length.EqualTo(10)); + }); + } +} diff --git a/src/Nethermind/Nethermind.Shutter/Config/IShutterConfig.cs b/src/Nethermind/Nethermind.Shutter/Config/IShutterConfig.cs new file mode 100644 index 00000000000..7bc477ae4c8 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Config/IShutterConfig.cs @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.IO; +using Multiformats.Address; +using Nethermind.Config; +using Nethermind.Core; + +namespace Nethermind.Shutter.Config; + +public interface IShutterConfig : IConfig +{ + // todo: replace with bootnodes when peer discovery added + private const string DefaultP2PAddresses = +@"/ip4/139.59.130.109/tcp/23003/p2p/12D3KooWRZoofMsnpsjkgvfPQUyGXZQnn7EVnb4tw4ghNfwMnnsj, +/ip4/167.71.169.248/tcp/23003/p2p/12D3KooWGH3VxoSQXZ6wUuCmsv5caGQnhwfGejbkXH6uS2r7sehA, +/ip4/139.59.130.109/tcp/23003/p2p/12D3KooWNxTiw7CvD1fuyye5P8qPhKTTrRBW6wwZwMdqdTxjYF2H, +/ip4/178.128.192.239/tcp/23003/p2p/12D3KooWCdpkipTiuzVMfkV7yLLgqbFeAL8WmEP78hCoBGBYLugN, +/ip4/45.55.192.248/tcp/23003/p2p/12D3KooWMPuubKqksfMxvLwEBDScaopTdvPLr5J5SMmBEo2zkcMz, +/ip4/178.128.126.237/tcp/23003/p2p/12D3KooWAg1pGUDAfFWSZftpN3JjBfLUCGLQcZApJHv2VntdMS9U"; + + [ConfigItem(Description = "Whether to enable Shutter.", DefaultValue = "false")] + bool Enabled { get; set; } + + [ConfigItem(Description = "The filepath of the validator info json file.", + DefaultValue = "null")] + string? ValidatorInfoFile { get; set; } + + [ConfigItem(Description = "The address of the Shutter sequencer contract.", + DefaultValue = "0xc5C4b277277A1A8401E0F039dfC49151bA64DC2E")] + string? SequencerContractAddress { get; set; } + + [ConfigItem(Description = "The address of the Shutter validator registry contract.", + DefaultValue = "0xefCC23E71f6bA9B22C4D28F7588141d44496A6D6")] + string? ValidatorRegistryContractAddress { get; set; } + + [ConfigItem(Description = "The address of the Shutter key broadcast contract.", + DefaultValue = "0x626dB87f9a9aC47070016A50e802dd5974341301")] + string? KeyBroadcastContractAddress { get; set; } + + [ConfigItem(Description = "The address of the Shutter keyper set manager contract.", + DefaultValue = "0x7C2337f9bFce19d8970661DA50dE8DD7d3D34abb")] + string? KeyperSetManagerContractAddress { get; set; } + + [ConfigItem(Description = "The p2p addresses of the Shutter Keyper network bootnodes.", + DefaultValue = DefaultP2PAddresses)] + string[]? BootnodeP2PAddresses { get; set; } + + [ConfigItem(Description = "Instance ID of Shutter keyper set.", + DefaultValue = "1000")] + ulong InstanceID { get; set; } + + [ConfigItem(Description = "The port to connect to Shutter P2P network with.", + DefaultValue = "23102")] + int P2PPort { get; set; } + + [ConfigItem(Description = "The Shutter P2P protocol version.", + DefaultValue = "/shutter/0.1.0", HiddenFromDocs = true)] + string? P2PProtocolVersion { get; set; } + + [ConfigItem(Description = "The Shutter P2P agent version.", + DefaultValue = "github.com/shutter-network/rolling-shutter/rolling-shutter", + HiddenFromDocs = true)] + string? P2PAgentVersion { get; set; } + + [ConfigItem(Description = "The Shutter validator registry message version.", + DefaultValue = "0", HiddenFromDocs = true)] + ulong ValidatorRegistryMessageVersion { get; set; } + + [ConfigItem(Description = "The maximum amount of gas to use on Shutter transactions.", + DefaultValue = "10000000", HiddenFromDocs = true)] + int EncryptedGasLimit { get; set; } + + [ConfigItem(Description = "Maximum amount of milliseconds into the slot to wait for Shutter keys before building block.", + DefaultValue = "1666", HiddenFromDocs = true)] + ushort MaxKeyDelay { get; } + + [ConfigItem(Description = "Whether to build Shutter blocks or just give metrics on Shutter transactions.", + DefaultValue = "true", HiddenFromDocs = true)] + bool Validator { get; set; } + + public void Validate() + { + if (Validator && ValidatorInfoFile is null) + { + throw new ArgumentException($"Must set Shutter.ValidatorInfoFile to a valid json file."); + } + + if (ValidatorInfoFile is not null && !File.Exists(ValidatorInfoFile)) + { + throw new ArgumentException($"Shutter validator info file \"{ValidatorInfoFile}\" does not exist."); + } + + if (SequencerContractAddress is null || !Address.TryParse(SequencerContractAddress, out _)) + { + throw new ArgumentException("Must set Shutter sequencer contract address to valid address."); + } + + if (ValidatorRegistryContractAddress is null || !Address.TryParse(ValidatorRegistryContractAddress, out _)) + { + throw new ArgumentException("Must set Shutter validator registry contract address to valid address."); + } + + if (KeyBroadcastContractAddress is null || !Address.TryParse(KeyBroadcastContractAddress, out _)) + { + throw new ArgumentException("Must set Shutter key broadcast contract address to valid address."); + } + + if (KeyperSetManagerContractAddress is null || !Address.TryParse(KeyperSetManagerContractAddress, out _)) + { + throw new ArgumentException("Must set Shutter keyper set manager contract address to valid address."); + } + + if (P2PAgentVersion is null) + { + throw new ArgumentNullException(nameof(P2PAgentVersion)); + } + + if (P2PProtocolVersion is null) + { + throw new ArgumentNullException(nameof(P2PProtocolVersion)); + } + + if (BootnodeP2PAddresses is null) + { + throw new ArgumentNullException(nameof(BootnodeP2PAddresses)); + } + + foreach (string addr in BootnodeP2PAddresses) + { + try + { + Multiaddress.Decode(addr); + } + catch (NotSupportedException) + { + throw new ArgumentException($"Could not decode Shutter keyper p2p address \"{addr}\"."); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Shutter/Config/ShutterConfig.cs b/src/Nethermind/Nethermind.Shutter/Config/ShutterConfig.cs new file mode 100644 index 00000000000..1e0e9c662f3 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Config/ShutterConfig.cs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Shutter.Config +{ + public class ShutterConfig : IShutterConfig + { + public bool Enabled { get; set; } + public bool Validator { get; set; } = true; + public string? SequencerContractAddress { get; set; } + public string? ValidatorRegistryContractAddress { get; set; } + public string? KeyBroadcastContractAddress { get; set; } + public string? KeyperSetManagerContractAddress { get; set; } + public string[]? BootnodeP2PAddresses { get; set; } = []; + public int P2PPort { get; set; } = 23102; + public string? ValidatorInfoFile { get; set; } + public string? P2PProtocolVersion { get; set; } = "/shutter/0.1.0"; + public string? P2PAgentVersion { get; set; } = "github.com/shutter-network/rolling-shutter/rolling-shutter"; + public ulong ValidatorRegistryMessageVersion { get; set; } = 0; + public ulong InstanceID { get; set; } = 0; + public int EncryptedGasLimit { get; set; } = 10000000; + public ushort MaxKeyDelay { get; set; } = 1666; + } +} diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/IKeyBroadcastContract.cs b/src/Nethermind/Nethermind.Shutter/Contracts/IKeyBroadcastContract.cs new file mode 100644 index 00000000000..83bd618dfaf --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Contracts/IKeyBroadcastContract.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Shutter.Contracts; + +public interface IKeyBroadcastContract +{ + /// + /// Retrieves the public key for an eon. + /// + /// + byte[] GetEonKey(BlockHeader blockHeader, in ulong eon); +} diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/IKeyperSetContract.cs b/src/Nethermind/Nethermind.Shutter/Contracts/IKeyperSetContract.cs new file mode 100644 index 00000000000..a8980fc181d --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Contracts/IKeyperSetContract.cs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Shutter.Contracts; + +public interface IKeyperSetContract +{ + /// + /// Check if the keyper set contract has been finalized + /// + bool IsFinalized(BlockHeader blockHeader); + + /// + /// Gets the keyper set threshold + /// + ulong GetThreshold(BlockHeader blockHeader); + + /// + /// Gets the members of the keyper set + /// + Address[] GetMembers(BlockHeader blockHeader); +} diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/IKeyperSetManagerContract.cs b/src/Nethermind/Nethermind.Shutter/Contracts/IKeyperSetManagerContract.cs new file mode 100644 index 00000000000..9bc605671bb --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Contracts/IKeyperSetManagerContract.cs @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Shutter.Contracts; + +public interface IKeyperSetManagerContract +{ + /// + /// Gets the keyper set contract address from index (eon). + /// + /// + Address GetKeyperSetAddress(BlockHeader blockHeader, in ulong index); + + /// + /// Gets the current eon. + /// + ulong GetNumKeyperSets(BlockHeader blockHeader); + + + /// + /// Gets the keyper set contract address from block number. + /// + /// + ulong GetKeyperSetIndexByBlock(BlockHeader blockHeader, in ulong blockNumber); +} diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/ISequencerContract.cs b/src/Nethermind/Nethermind.Shutter/Contracts/ISequencerContract.cs new file mode 100644 index 00000000000..110af58395f --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Contracts/ISequencerContract.cs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Abi; +using Nethermind.Core; +using Nethermind.Int256; + +namespace Nethermind.Shutter.Contracts; + +public interface ISequencerContract +{ + AbiEncodingInfo TransactionSubmittedAbi { get; } + + struct TransactionSubmitted + { + public ulong Eon; + public ulong TxIndex; + public Bytes32 IdentityPrefix; + public Address Sender; + public byte[] EncryptedTransaction; + public UInt256 GasLimit; + } +} diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/IValidatorRegistryContract.cs b/src/Nethermind/Nethermind.Shutter/Contracts/IValidatorRegistryContract.cs new file mode 100644 index 00000000000..01c99f531f4 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Contracts/IValidatorRegistryContract.cs @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.Int256; +using Update = (byte[] Message, byte[] Signature); + +namespace Nethermind.Shutter.Contracts; + +public interface IValidatorRegistryContract +{ + /// + /// Check if validator is registered. + /// + /// + /// + bool IsRegistered(BlockHeader header, in Dictionary validatorsInfo, out HashSet unregistered); + + /// + /// Returns the number of previous updates to the registry. + /// + /// + UInt256 GetNumUpdates(BlockHeader header); + + /// + /// Retrieves the ith update to the registry. + /// + /// + /// + Update GetUpdate(BlockHeader header, in UInt256 i); +} diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/KeyBroadcastContract.cs b/src/Nethermind/Nethermind.Shutter/Contracts/KeyBroadcastContract.cs new file mode 100644 index 00000000000..b73ff06897e --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Contracts/KeyBroadcastContract.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Abi; +using Nethermind.Blockchain.Contracts; +using Nethermind.Core; +using Nethermind.Evm.TransactionProcessing; + +namespace Nethermind.Shutter.Contracts; + +public class KeyBroadcastContract(ITransactionProcessor transactionProcessor, IAbiEncoder abiEncoder, Address contractAddress) : CallableContract(transactionProcessor, abiEncoder, contractAddress), IKeyBroadcastContract +{ + public byte[] GetEonKey(BlockHeader blockHeader, in ulong eon) + { + return (byte[])Call(blockHeader, nameof(GetEonKey), Address.Zero, [eon])[0]; + } +} diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/KeyBroadcastContract.json b/src/Nethermind/Nethermind.Shutter/Contracts/KeyBroadcastContract.json new file mode 100644 index 00000000000..5a47ec75a5e --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Contracts/KeyBroadcastContract.json @@ -0,0 +1,21 @@ +[ + { + "inputs": [ + { + "internalType": "uint64", + "name": "eon", + "type": "uint64" + } + ], + "name": "getEonKey", + "outputs": [ + { + "internaltype": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/KeyperSetContract.cs b/src/Nethermind/Nethermind.Shutter/Contracts/KeyperSetContract.cs new file mode 100644 index 00000000000..aba64940166 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Contracts/KeyperSetContract.cs @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Abi; +using Nethermind.Blockchain.Contracts; +using Nethermind.Core; +using Nethermind.Evm.TransactionProcessing; + +namespace Nethermind.Shutter.Contracts; + +public class KeyperSetContract(ITransactionProcessor transactionProcessor, IAbiEncoder abiEncoder, Address contractAddress) : CallableContract(transactionProcessor, abiEncoder, contractAddress), IKeyperSetContract +{ + public bool IsFinalized(BlockHeader blockHeader) + { + return (bool)Call(blockHeader, nameof(IsFinalized), Address.Zero, [])[0]; + } + + public ulong GetThreshold(BlockHeader blockHeader) + { + return (ulong)Call(blockHeader, nameof(GetThreshold), Address.Zero, [])[0]; + } + + public Address[] GetMembers(BlockHeader blockHeader) + { + return (Address[])Call(blockHeader, nameof(GetMembers), Address.Zero, [])[0]; + } +} diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/KeyperSetContract.json b/src/Nethermind/Nethermind.Shutter/Contracts/KeyperSetContract.json new file mode 100644 index 00000000000..f5955de26d8 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Contracts/KeyperSetContract.json @@ -0,0 +1,41 @@ +[ + { + "inputs": [], + "name": "isFinalized", + "outputs": [ + { + "name": "", + "type": "bool", + "internaltype": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getThreshold", + "outputs": [ + { + "name": "", + "type": "uint64", + "internaltype": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMembers", + "outputs": [ + { + "name": "", + "type": "address[]", + "internaltype": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/KeyperSetManagerContract.cs b/src/Nethermind/Nethermind.Shutter/Contracts/KeyperSetManagerContract.cs new file mode 100644 index 00000000000..8f49d1fc81a --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Contracts/KeyperSetManagerContract.cs @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Abi; +using Nethermind.Blockchain.Contracts; +using Nethermind.Core; +using Nethermind.Evm.TransactionProcessing; + +namespace Nethermind.Shutter.Contracts; + +public class KeyperSetManagerContract(ITransactionProcessor transactionProcessor, IAbiEncoder abiEncoder, Address contractAddress) : CallableContract(transactionProcessor, abiEncoder, contractAddress), IKeyperSetManagerContract +{ + public Address GetKeyperSetAddress(BlockHeader blockHeader, in ulong index) + { + return (Address)Call(blockHeader, nameof(GetKeyperSetAddress), Address.Zero, [index])[0]; + } + + public ulong GetNumKeyperSets(BlockHeader blockHeader) + { + return (ulong)Call(blockHeader, nameof(GetNumKeyperSets), Address.Zero, [])[0]; + } + + public ulong GetKeyperSetIndexByBlock(BlockHeader blockHeader, in ulong blockNumber) + { + return (ulong)Call(blockHeader, nameof(GetKeyperSetIndexByBlock), Address.Zero, [blockNumber])[0]; + } +} diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/KeyperSetManagerContract.json b/src/Nethermind/Nethermind.Shutter/Contracts/KeyperSetManagerContract.json new file mode 100644 index 00000000000..05a6092140b --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Contracts/KeyperSetManagerContract.json @@ -0,0 +1,53 @@ +[ + { + "inputs": [ + { + "internalType": "uint64", + "name": "index", + "type": "uint64" + } + ], + "name": "getKeyperSetAddress", + "outputs": [ + { + "internaltype": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNumKeyperSets", + "outputs": [ + { + "name": "", + "type": "uint64", + "internaltype": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "blockNumber", + "type": "uint64" + } + ], + "name": "getKeyperSetIndexByBlock", + "outputs": [ + { + "internaltype": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/SequencerContract.cs b/src/Nethermind/Nethermind.Shutter/Contracts/SequencerContract.cs new file mode 100644 index 00000000000..115b27d9b2e --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Contracts/SequencerContract.cs @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Abi; +using Nethermind.Blockchain.Contracts; +using Nethermind.Core; + +namespace Nethermind.Shutter.Contracts; + +public class SequencerContract : Contract, ISequencerContract +{ + public AbiEncodingInfo TransactionSubmittedAbi { get => _transactionSubmittedAbi; } + private readonly AbiEncodingInfo _transactionSubmittedAbi; + + public SequencerContract(Address address) + : base(null, address) + { + _transactionSubmittedAbi = AbiDefinition.GetEvent(nameof(ISequencerContract.TransactionSubmitted)).GetCallInfo(AbiEncodingStyle.None); + } +} diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/SequencerContract.json b/src/Nethermind/Nethermind.Shutter/Contracts/SequencerContract.json new file mode 100644 index 00000000000..348e591f5e0 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Contracts/SequencerContract.json @@ -0,0 +1,45 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "eon", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "txIndex", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "identityPrefix", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "encryptedTransaction", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + } + ], + "name": "TransactionSubmitted", + "type": "event" + } +] \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/ValidatorRegistryContract.cs b/src/Nethermind/Nethermind.Shutter/Contracts/ValidatorRegistryContract.cs new file mode 100644 index 00000000000..791f59cf13e --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Contracts/ValidatorRegistryContract.cs @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers.Binary; +using Nethermind.Abi; +using Nethermind.Blockchain.Contracts; +using Nethermind.Core; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Int256; +using Nethermind.Logging; +using System.Collections.Generic; +using Nethermind.Core.Extensions; +using Update = (byte[] Message, byte[] Signature); + +namespace Nethermind.Shutter.Contracts; + +public class ValidatorRegistryContract( + ITransactionProcessor transactionProcessor, + IAbiEncoder abiEncoder, + Address contractAddress, + ILogManager logManager, + ulong chainId, + ulong messageVersion) + : CallableContract(transactionProcessor, abiEncoder, contractAddress), IValidatorRegistryContract +{ + private readonly ILogger _logger = logManager.GetClassLogger(); + + public UInt256 GetNumUpdates(BlockHeader header) => (UInt256)Call(header, nameof(GetNumUpdates), Address.Zero, [])[0]; + + public Update GetUpdate(BlockHeader header, in UInt256 i) + => (Update)Call(header, nameof(GetUpdate), Address.Zero, [i])[0]; + + public bool IsRegistered(BlockHeader header, in Dictionary validatorsInfo, out HashSet unregistered) + { + Dictionary nonces = []; + unregistered = []; + foreach (KeyValuePair validatorInfo in validatorsInfo) + { + nonces.Add(validatorInfo.Key, null); + unregistered.Add(validatorInfo.Key); + } + + uint updates = (uint)GetNumUpdates(header); + for (uint i = 0; i < updates; i++) + { + Update update = GetUpdate(header, updates - i - 1); + Message msg = new(update.Message.AsSpan()[..46]); + + // skip untracked validators + if (!validatorsInfo.ContainsKey(msg.ValidatorIndex)) + { + continue; + } + + if (msg.Version != messageVersion) + { + if (_logger.IsDebug) _logger.Debug($"Registration message has wrong version ({msg.Version}) should be {messageVersion}"); + continue; + } + + if (msg.ChainId != chainId) + { + if (_logger.IsDebug) _logger.Debug($"Registration message has incorrect chain ID ({msg.ChainId}) should be {chainId}"); + continue; + } + + if (!msg.ContractAddress.SequenceEqual(ContractAddress!.Bytes)) + { + if (_logger.IsDebug) _logger.Debug($"Registration message contains an invalid contract address ({msg.ContractAddress.ToHexString()}) should be {ContractAddress}"); + continue; + } + + if (nonces[msg.ValidatorIndex].HasValue && msg.Nonce <= nonces[msg.ValidatorIndex]) + { + if (_logger.IsDebug) _logger.Debug($"Registration message has incorrect nonce ({msg.Nonce}) should be {nonces[msg.ValidatorIndex]}"); + continue; + } + + if (!ShutterCrypto.CheckValidatorRegistrySignature(validatorsInfo[msg.ValidatorIndex], update.Signature, update.Message)) + { + if (_logger.IsDebug) _logger.Debug("Registration message has invalid signature."); + continue; + } + + // message is valid + nonces[msg.ValidatorIndex] = msg.Nonce; + + if (msg.IsRegistration) + { + unregistered.Remove(msg.ValidatorIndex); + } + else + { + unregistered.Add(msg.ValidatorIndex); + } + } + + return unregistered.Count == 0; + } + + private readonly ref struct Message + { + public readonly byte Version; + public readonly ulong ChainId; + public readonly ReadOnlySpan ContractAddress; + public readonly ulong ValidatorIndex; + public readonly ulong Nonce; + public readonly bool IsRegistration; + + public Message(Span encodedMessage) + { + if (encodedMessage.Length != 46) + { + throw new ArgumentException("Validator registry contract message was wrong length."); + } + + Version = encodedMessage[0]; + ChainId = BinaryPrimitives.ReadUInt64BigEndian(encodedMessage[1..]); + ContractAddress = encodedMessage[9..29]; + ValidatorIndex = BinaryPrimitives.ReadUInt64BigEndian(encodedMessage[29..]); + Nonce = BinaryPrimitives.ReadUInt64BigEndian(encodedMessage[37..]); + IsRegistration = encodedMessage[45] == 1; + } + } +} diff --git a/src/Nethermind/Nethermind.Shutter/Contracts/ValidatorRegistryContract.json b/src/Nethermind/Nethermind.Shutter/Contracts/ValidatorRegistryContract.json new file mode 100644 index 00000000000..46a5f7c95f4 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Contracts/ValidatorRegistryContract.json @@ -0,0 +1,83 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes", + "name": "message", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "Updated", + "type": "event" + }, + { + "inputs": [], + "name": "getNumUpdates", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "i", + "type": "uint256" + } + ], + "name": "getUpdate", + "outputs": [ + { + "components": [ + { + "internaltype": "bytes", + "name": "message", + "type": "bytes" + }, + { + "internaltype": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "internaltype": "struct ivalidatorregistrycontract.update", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "message", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "update", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Shutter/Dto/Gossip.cs b/src/Nethermind/Nethermind.Shutter/Dto/Gossip.cs new file mode 100644 index 00000000000..866696d838d --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Dto/Gossip.cs @@ -0,0 +1,3485 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: Gossip.proto +// +#pragma warning disable 1591, 0612, 3021, 8981 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Nethermind.Shutter.Dto { + + /// Holder for reflection information generated from Gossip.proto + public static partial class GossipReflection { + + #region Descriptor + /// File descriptor for Gossip.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static GossipReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "CgxHb3NzaXAucHJvdG8SBnAycG1zZxoJYW55LnByb3RvInoKEURlY3J5cHRp", + "b25UcmlnZ2VyEhIKCmluc3RhbmNlSUQYASABKAQSDwoHZXBvY2hJRBgCIAEo", + "DBITCgtibG9ja051bWJlchgDIAEoBBIYChB0cmFuc2FjdGlvbnNIYXNoGAQg", + "ASgMEhEKCXNpZ25hdHVyZRgFIAEoDCIqCghLZXlTaGFyZRIPCgdlcG9jaElE", + "GAEgASgMEg0KBXNoYXJlGAIgASgMIlUKHkdub3Npc0RlY3J5cHRpb25LZXlT", + "aGFyZXNFeHRyYRIMCgRzbG90GAEgASgEEhIKCnR4X3BvaW50ZXIYAiABKAQS", + "EQoJc2lnbmF0dXJlGAMgASgMIiIKIE9wdGltaXNtRGVjcnlwdGlvbktleVNo", + "YXJlc0V4dHJhIu4BChNEZWNyeXB0aW9uS2V5U2hhcmVzEhIKCmluc3RhbmNl", + "SUQYASABKAQSCwoDZW9uGAQgASgEEhMKC2tleXBlckluZGV4GAUgASgEEiAK", + "BnNoYXJlcxgJIAMoCzIQLnAycG1zZy5LZXlTaGFyZRI4CgZnbm9zaXMYCiAB", + "KAsyJi5wMnBtc2cuR25vc2lzRGVjcnlwdGlvbktleVNoYXJlc0V4dHJhSAAS", + "PAoIb3B0aW1pc20YCyABKAsyKC5wMnBtc2cuT3B0aW1pc21EZWNyeXB0aW9u", + "S2V5U2hhcmVzRXh0cmFIAEIHCgVleHRyYSIkCgNLZXkSEAoIaWRlbnRpdHkY", + "ASABKAwSCwoDa2V5GAIgASgMImgKGUdub3Npc0RlY3J5cHRpb25LZXlzRXh0", + "cmESDAoEc2xvdBgBIAEoBBISCgp0eF9wb2ludGVyGAIgASgEEhUKDXNpZ25l", + "ckluZGljZXMYAyADKAQSEgoKc2lnbmF0dXJlcxgEIAMoDCIdChtPcHRpbWlz", + "bURlY3J5cHRpb25LZXlzRXh0cmEiwwEKDkRlY3J5cHRpb25LZXlzEhIKCmlu", + "c3RhbmNlSUQYASABKAQSCwoDZW9uGAIgASgEEhkKBGtleXMYAyADKAsyCy5w", + "MnBtc2cuS2V5EjMKBmdub3NpcxgEIAEoCzIhLnAycG1zZy5Hbm9zaXNEZWNy", + "eXB0aW9uS2V5c0V4dHJhSAASNwoIb3B0aW1pc20YBSABKAsyIy5wMnBtc2cu", + "T3B0aW1pc21EZWNyeXB0aW9uS2V5c0V4dHJhSABCBwoFZXh0cmEiiQEKDEVv", + "blB1YmxpY0tleRISCgppbnN0YW5jZUlEGAEgASgEEhEKCXB1YmxpY0tleRgC", + "IAEoDBIXCg9hY3RpdmF0aW9uQmxvY2sYAyABKAQSGQoRa2V5cGVyQ29uZmln", + "SW5kZXgYBiABKAQSCwoDZW9uGAcgASgEEhEKCXNpZ25hdHVyZRgFIAEoDCJX", + "CgxUcmFjZUNvbnRleHQSDwoHdHJhY2VJRBgBIAEoDBIOCgZzcGFuSUQYAiAB", + "KAwSEgoKdHJhY2VGbGFncxgDIAEoDBISCgp0cmFjZVN0YXRlGAQgASgJInYK", + "CEVudmVsb3BlEg8KB3ZlcnNpb24YASABKAkSJQoHbWVzc2FnZRgCIAEoCzIU", + "Lmdvb2dsZS5wcm90b2J1Zi5BbnkSKAoFdHJhY2UYAyABKAsyFC5wMnBtc2cu", + "VHJhY2VDb250ZXh0SACIAQFCCAoGX3RyYWNlQihaCS4vO3AycG1zZ6oCGk5l", + "dGhlcm1pbmQuTGlicDJwLkNvcmUuRHRvYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { global::Google.Protobuf.WellKnownTypes.AnyReflection.Descriptor, }, + new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] { + new pbr::GeneratedClrTypeInfo(typeof(global::Nethermind.Shutter.Dto.DecryptionTrigger), global::Nethermind.Shutter.Dto.DecryptionTrigger.Parser, new[]{ "InstanceID", "EpochID", "BlockNumber", "TransactionsHash", "Signature" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Nethermind.Shutter.Dto.KeyShare), global::Nethermind.Shutter.Dto.KeyShare.Parser, new[]{ "EpochID", "Share" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Nethermind.Shutter.Dto.GnosisDecryptionKeySharesExtra), global::Nethermind.Shutter.Dto.GnosisDecryptionKeySharesExtra.Parser, new[]{ "Slot", "TxPointer", "Signature" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Nethermind.Shutter.Dto.OptimismDecryptionKeySharesExtra), global::Nethermind.Shutter.Dto.OptimismDecryptionKeySharesExtra.Parser, null, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Nethermind.Shutter.Dto.DecryptionKeyShares), global::Nethermind.Shutter.Dto.DecryptionKeyShares.Parser, new[]{ "InstanceID", "Eon", "KeyperIndex", "Shares", "Gnosis", "Optimism" }, new[]{ "Extra" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Nethermind.Shutter.Dto.Key), global::Nethermind.Shutter.Dto.Key.Parser, new[]{ "Identity", "Key_" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Nethermind.Shutter.Dto.GnosisDecryptionKeysExtra), global::Nethermind.Shutter.Dto.GnosisDecryptionKeysExtra.Parser, new[]{ "Slot", "TxPointer", "SignerIndices", "Signatures" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Nethermind.Shutter.Dto.OptimismDecryptionKeysExtra), global::Nethermind.Shutter.Dto.OptimismDecryptionKeysExtra.Parser, null, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Nethermind.Shutter.Dto.DecryptionKeys), global::Nethermind.Shutter.Dto.DecryptionKeys.Parser, new[]{ "InstanceID", "Eon", "Keys", "Gnosis", "Optimism" }, new[]{ "Extra" }, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Nethermind.Shutter.Dto.EonPublicKey), global::Nethermind.Shutter.Dto.EonPublicKey.Parser, new[]{ "InstanceID", "PublicKey", "ActivationBlock", "KeyperConfigIndex", "Eon", "Signature" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Nethermind.Shutter.Dto.TraceContext), global::Nethermind.Shutter.Dto.TraceContext.Parser, new[]{ "TraceID", "SpanID", "TraceFlags", "TraceState" }, null, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::Nethermind.Shutter.Dto.Envelope), global::Nethermind.Shutter.Dto.Envelope.Parser, new[]{ "Version", "Message", "Trace" }, new[]{ "Trace" }, null, null, null) + })); + } + #endregion + + } + #region Messages + public sealed partial class DecryptionTrigger : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new DecryptionTrigger()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::Nethermind.Shutter.Dto.GossipReflection.Descriptor.MessageTypes[0]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public DecryptionTrigger() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public DecryptionTrigger(DecryptionTrigger other) : this() { + instanceID_ = other.instanceID_; + epochID_ = other.epochID_; + blockNumber_ = other.blockNumber_; + transactionsHash_ = other.transactionsHash_; + signature_ = other.signature_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public DecryptionTrigger Clone() { + return new DecryptionTrigger(this); + } + + /// Field number for the "instanceID" field. + public const int InstanceIDFieldNumber = 1; + private ulong instanceID_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ulong InstanceID { + get { return instanceID_; } + set { + instanceID_ = value; + } + } + + /// Field number for the "epochID" field. + public const int EpochIDFieldNumber = 2; + private pb::ByteString epochID_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pb::ByteString EpochID { + get { return epochID_; } + set { + epochID_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "blockNumber" field. + public const int BlockNumberFieldNumber = 3; + private ulong blockNumber_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ulong BlockNumber { + get { return blockNumber_; } + set { + blockNumber_ = value; + } + } + + /// Field number for the "transactionsHash" field. + public const int TransactionsHashFieldNumber = 4; + private pb::ByteString transactionsHash_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pb::ByteString TransactionsHash { + get { return transactionsHash_; } + set { + transactionsHash_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "signature" field. + public const int SignatureFieldNumber = 5; + private pb::ByteString signature_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pb::ByteString Signature { + get { return signature_; } + set { + signature_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as DecryptionTrigger); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(DecryptionTrigger other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (InstanceID != other.InstanceID) return false; + if (EpochID != other.EpochID) return false; + if (BlockNumber != other.BlockNumber) return false; + if (TransactionsHash != other.TransactionsHash) return false; + if (Signature != other.Signature) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (InstanceID != 0UL) hash ^= InstanceID.GetHashCode(); + if (EpochID.Length != 0) hash ^= EpochID.GetHashCode(); + if (BlockNumber != 0UL) hash ^= BlockNumber.GetHashCode(); + if (TransactionsHash.Length != 0) hash ^= TransactionsHash.GetHashCode(); + if (Signature.Length != 0) hash ^= Signature.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (InstanceID != 0UL) { + output.WriteRawTag(8); + output.WriteUInt64(InstanceID); + } + if (EpochID.Length != 0) { + output.WriteRawTag(18); + output.WriteBytes(EpochID); + } + if (BlockNumber != 0UL) { + output.WriteRawTag(24); + output.WriteUInt64(BlockNumber); + } + if (TransactionsHash.Length != 0) { + output.WriteRawTag(34); + output.WriteBytes(TransactionsHash); + } + if (Signature.Length != 0) { + output.WriteRawTag(42); + output.WriteBytes(Signature); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (InstanceID != 0UL) { + output.WriteRawTag(8); + output.WriteUInt64(InstanceID); + } + if (EpochID.Length != 0) { + output.WriteRawTag(18); + output.WriteBytes(EpochID); + } + if (BlockNumber != 0UL) { + output.WriteRawTag(24); + output.WriteUInt64(BlockNumber); + } + if (TransactionsHash.Length != 0) { + output.WriteRawTag(34); + output.WriteBytes(TransactionsHash); + } + if (Signature.Length != 0) { + output.WriteRawTag(42); + output.WriteBytes(Signature); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (InstanceID != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(InstanceID); + } + if (EpochID.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(EpochID); + } + if (BlockNumber != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(BlockNumber); + } + if (TransactionsHash.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(TransactionsHash); + } + if (Signature.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Signature); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(DecryptionTrigger other) { + if (other == null) { + return; + } + if (other.InstanceID != 0UL) { + InstanceID = other.InstanceID; + } + if (other.EpochID.Length != 0) { + EpochID = other.EpochID; + } + if (other.BlockNumber != 0UL) { + BlockNumber = other.BlockNumber; + } + if (other.TransactionsHash.Length != 0) { + TransactionsHash = other.TransactionsHash; + } + if (other.Signature.Length != 0) { + Signature = other.Signature; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + InstanceID = input.ReadUInt64(); + break; + } + case 18: { + EpochID = input.ReadBytes(); + break; + } + case 24: { + BlockNumber = input.ReadUInt64(); + break; + } + case 34: { + TransactionsHash = input.ReadBytes(); + break; + } + case 42: { + Signature = input.ReadBytes(); + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 8: { + InstanceID = input.ReadUInt64(); + break; + } + case 18: { + EpochID = input.ReadBytes(); + break; + } + case 24: { + BlockNumber = input.ReadUInt64(); + break; + } + case 34: { + TransactionsHash = input.ReadBytes(); + break; + } + case 42: { + Signature = input.ReadBytes(); + break; + } + } + } + } + #endif + + } + + public sealed partial class KeyShare : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new KeyShare()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::Nethermind.Shutter.Dto.GossipReflection.Descriptor.MessageTypes[1]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public KeyShare() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public KeyShare(KeyShare other) : this() { + epochID_ = other.epochID_; + share_ = other.share_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public KeyShare Clone() { + return new KeyShare(this); + } + + /// Field number for the "epochID" field. + public const int EpochIDFieldNumber = 1; + private pb::ByteString epochID_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pb::ByteString EpochID { + get { return epochID_; } + set { + epochID_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "share" field. + public const int ShareFieldNumber = 2; + private pb::ByteString share_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pb::ByteString Share { + get { return share_; } + set { + share_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as KeyShare); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(KeyShare other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (EpochID != other.EpochID) return false; + if (Share != other.Share) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (EpochID.Length != 0) hash ^= EpochID.GetHashCode(); + if (Share.Length != 0) hash ^= Share.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (EpochID.Length != 0) { + output.WriteRawTag(10); + output.WriteBytes(EpochID); + } + if (Share.Length != 0) { + output.WriteRawTag(18); + output.WriteBytes(Share); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (EpochID.Length != 0) { + output.WriteRawTag(10); + output.WriteBytes(EpochID); + } + if (Share.Length != 0) { + output.WriteRawTag(18); + output.WriteBytes(Share); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (EpochID.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(EpochID); + } + if (Share.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Share); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(KeyShare other) { + if (other == null) { + return; + } + if (other.EpochID.Length != 0) { + EpochID = other.EpochID; + } + if (other.Share.Length != 0) { + Share = other.Share; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + EpochID = input.ReadBytes(); + break; + } + case 18: { + Share = input.ReadBytes(); + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 10: { + EpochID = input.ReadBytes(); + break; + } + case 18: { + Share = input.ReadBytes(); + break; + } + } + } + } + #endif + + } + + public sealed partial class GnosisDecryptionKeySharesExtra : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GnosisDecryptionKeySharesExtra()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::Nethermind.Shutter.Dto.GossipReflection.Descriptor.MessageTypes[2]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public GnosisDecryptionKeySharesExtra() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public GnosisDecryptionKeySharesExtra(GnosisDecryptionKeySharesExtra other) : this() { + slot_ = other.slot_; + txPointer_ = other.txPointer_; + signature_ = other.signature_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public GnosisDecryptionKeySharesExtra Clone() { + return new GnosisDecryptionKeySharesExtra(this); + } + + /// Field number for the "slot" field. + public const int SlotFieldNumber = 1; + private ulong slot_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ulong Slot { + get { return slot_; } + set { + slot_ = value; + } + } + + /// Field number for the "tx_pointer" field. + public const int TxPointerFieldNumber = 2; + private ulong txPointer_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ulong TxPointer { + get { return txPointer_; } + set { + txPointer_ = value; + } + } + + /// Field number for the "signature" field. + public const int SignatureFieldNumber = 3; + private pb::ByteString signature_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pb::ByteString Signature { + get { return signature_; } + set { + signature_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as GnosisDecryptionKeySharesExtra); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(GnosisDecryptionKeySharesExtra other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Slot != other.Slot) return false; + if (TxPointer != other.TxPointer) return false; + if (Signature != other.Signature) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (Slot != 0UL) hash ^= Slot.GetHashCode(); + if (TxPointer != 0UL) hash ^= TxPointer.GetHashCode(); + if (Signature.Length != 0) hash ^= Signature.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (Slot != 0UL) { + output.WriteRawTag(8); + output.WriteUInt64(Slot); + } + if (TxPointer != 0UL) { + output.WriteRawTag(16); + output.WriteUInt64(TxPointer); + } + if (Signature.Length != 0) { + output.WriteRawTag(26); + output.WriteBytes(Signature); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (Slot != 0UL) { + output.WriteRawTag(8); + output.WriteUInt64(Slot); + } + if (TxPointer != 0UL) { + output.WriteRawTag(16); + output.WriteUInt64(TxPointer); + } + if (Signature.Length != 0) { + output.WriteRawTag(26); + output.WriteBytes(Signature); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (Slot != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Slot); + } + if (TxPointer != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(TxPointer); + } + if (Signature.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Signature); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(GnosisDecryptionKeySharesExtra other) { + if (other == null) { + return; + } + if (other.Slot != 0UL) { + Slot = other.Slot; + } + if (other.TxPointer != 0UL) { + TxPointer = other.TxPointer; + } + if (other.Signature.Length != 0) { + Signature = other.Signature; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + Slot = input.ReadUInt64(); + break; + } + case 16: { + TxPointer = input.ReadUInt64(); + break; + } + case 26: { + Signature = input.ReadBytes(); + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 8: { + Slot = input.ReadUInt64(); + break; + } + case 16: { + TxPointer = input.ReadUInt64(); + break; + } + case 26: { + Signature = input.ReadBytes(); + break; + } + } + } + } + #endif + + } + + public sealed partial class OptimismDecryptionKeySharesExtra : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new OptimismDecryptionKeySharesExtra()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::Nethermind.Shutter.Dto.GossipReflection.Descriptor.MessageTypes[3]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public OptimismDecryptionKeySharesExtra() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public OptimismDecryptionKeySharesExtra(OptimismDecryptionKeySharesExtra other) : this() { + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public OptimismDecryptionKeySharesExtra Clone() { + return new OptimismDecryptionKeySharesExtra(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as OptimismDecryptionKeySharesExtra); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(OptimismDecryptionKeySharesExtra other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(OptimismDecryptionKeySharesExtra other) { + if (other == null) { + return; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + } + } + } + #endif + + } + + public sealed partial class DecryptionKeyShares : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new DecryptionKeyShares()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::Nethermind.Shutter.Dto.GossipReflection.Descriptor.MessageTypes[4]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public DecryptionKeyShares() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public DecryptionKeyShares(DecryptionKeyShares other) : this() { + instanceID_ = other.instanceID_; + eon_ = other.eon_; + keyperIndex_ = other.keyperIndex_; + shares_ = other.shares_.Clone(); + switch (other.ExtraCase) { + case ExtraOneofCase.Gnosis: + Gnosis = other.Gnosis.Clone(); + break; + case ExtraOneofCase.Optimism: + Optimism = other.Optimism.Clone(); + break; + } + + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public DecryptionKeyShares Clone() { + return new DecryptionKeyShares(this); + } + + /// Field number for the "instanceID" field. + public const int InstanceIDFieldNumber = 1; + private ulong instanceID_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ulong InstanceID { + get { return instanceID_; } + set { + instanceID_ = value; + } + } + + /// Field number for the "eon" field. + public const int EonFieldNumber = 4; + private ulong eon_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ulong Eon { + get { return eon_; } + set { + eon_ = value; + } + } + + /// Field number for the "keyperIndex" field. + public const int KeyperIndexFieldNumber = 5; + private ulong keyperIndex_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ulong KeyperIndex { + get { return keyperIndex_; } + set { + keyperIndex_ = value; + } + } + + /// Field number for the "shares" field. + public const int SharesFieldNumber = 9; + private static readonly pb::FieldCodec _repeated_shares_codec + = pb::FieldCodec.ForMessage(74, global::Nethermind.Shutter.Dto.KeyShare.Parser); + private readonly pbc::RepeatedField shares_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pbc::RepeatedField Shares { + get { return shares_; } + } + + /// Field number for the "gnosis" field. + public const int GnosisFieldNumber = 10; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public global::Nethermind.Shutter.Dto.GnosisDecryptionKeySharesExtra Gnosis { + get { return extraCase_ == ExtraOneofCase.Gnosis ? (global::Nethermind.Shutter.Dto.GnosisDecryptionKeySharesExtra) extra_ : null; } + set { + extra_ = value; + extraCase_ = value == null ? ExtraOneofCase.None : ExtraOneofCase.Gnosis; + } + } + + /// Field number for the "optimism" field. + public const int OptimismFieldNumber = 11; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public global::Nethermind.Shutter.Dto.OptimismDecryptionKeySharesExtra Optimism { + get { return extraCase_ == ExtraOneofCase.Optimism ? (global::Nethermind.Shutter.Dto.OptimismDecryptionKeySharesExtra) extra_ : null; } + set { + extra_ = value; + extraCase_ = value == null ? ExtraOneofCase.None : ExtraOneofCase.Optimism; + } + } + + private object extra_; + /// Enum of possible cases for the "extra" oneof. + public enum ExtraOneofCase { + None = 0, + Gnosis = 10, + Optimism = 11, + } + private ExtraOneofCase extraCase_ = ExtraOneofCase.None; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ExtraOneofCase ExtraCase { + get { return extraCase_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void ClearExtra() { + extraCase_ = ExtraOneofCase.None; + extra_ = null; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as DecryptionKeyShares); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(DecryptionKeyShares other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (InstanceID != other.InstanceID) return false; + if (Eon != other.Eon) return false; + if (KeyperIndex != other.KeyperIndex) return false; + if(!shares_.Equals(other.shares_)) return false; + if (!object.Equals(Gnosis, other.Gnosis)) return false; + if (!object.Equals(Optimism, other.Optimism)) return false; + if (ExtraCase != other.ExtraCase) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (InstanceID != 0UL) hash ^= InstanceID.GetHashCode(); + if (Eon != 0UL) hash ^= Eon.GetHashCode(); + if (KeyperIndex != 0UL) hash ^= KeyperIndex.GetHashCode(); + hash ^= shares_.GetHashCode(); + if (extraCase_ == ExtraOneofCase.Gnosis) hash ^= Gnosis.GetHashCode(); + if (extraCase_ == ExtraOneofCase.Optimism) hash ^= Optimism.GetHashCode(); + hash ^= (int) extraCase_; + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (InstanceID != 0UL) { + output.WriteRawTag(8); + output.WriteUInt64(InstanceID); + } + if (Eon != 0UL) { + output.WriteRawTag(32); + output.WriteUInt64(Eon); + } + if (KeyperIndex != 0UL) { + output.WriteRawTag(40); + output.WriteUInt64(KeyperIndex); + } + shares_.WriteTo(output, _repeated_shares_codec); + if (extraCase_ == ExtraOneofCase.Gnosis) { + output.WriteRawTag(82); + output.WriteMessage(Gnosis); + } + if (extraCase_ == ExtraOneofCase.Optimism) { + output.WriteRawTag(90); + output.WriteMessage(Optimism); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (InstanceID != 0UL) { + output.WriteRawTag(8); + output.WriteUInt64(InstanceID); + } + if (Eon != 0UL) { + output.WriteRawTag(32); + output.WriteUInt64(Eon); + } + if (KeyperIndex != 0UL) { + output.WriteRawTag(40); + output.WriteUInt64(KeyperIndex); + } + shares_.WriteTo(ref output, _repeated_shares_codec); + if (extraCase_ == ExtraOneofCase.Gnosis) { + output.WriteRawTag(82); + output.WriteMessage(Gnosis); + } + if (extraCase_ == ExtraOneofCase.Optimism) { + output.WriteRawTag(90); + output.WriteMessage(Optimism); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (InstanceID != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(InstanceID); + } + if (Eon != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Eon); + } + if (KeyperIndex != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(KeyperIndex); + } + size += shares_.CalculateSize(_repeated_shares_codec); + if (extraCase_ == ExtraOneofCase.Gnosis) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Gnosis); + } + if (extraCase_ == ExtraOneofCase.Optimism) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Optimism); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(DecryptionKeyShares other) { + if (other == null) { + return; + } + if (other.InstanceID != 0UL) { + InstanceID = other.InstanceID; + } + if (other.Eon != 0UL) { + Eon = other.Eon; + } + if (other.KeyperIndex != 0UL) { + KeyperIndex = other.KeyperIndex; + } + shares_.Add(other.shares_); + switch (other.ExtraCase) { + case ExtraOneofCase.Gnosis: + if (Gnosis == null) { + Gnosis = new global::Nethermind.Shutter.Dto.GnosisDecryptionKeySharesExtra(); + } + Gnosis.MergeFrom(other.Gnosis); + break; + case ExtraOneofCase.Optimism: + if (Optimism == null) { + Optimism = new global::Nethermind.Shutter.Dto.OptimismDecryptionKeySharesExtra(); + } + Optimism.MergeFrom(other.Optimism); + break; + } + + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + InstanceID = input.ReadUInt64(); + break; + } + case 32: { + Eon = input.ReadUInt64(); + break; + } + case 40: { + KeyperIndex = input.ReadUInt64(); + break; + } + case 74: { + shares_.AddEntriesFrom(input, _repeated_shares_codec); + break; + } + case 82: { + global::Nethermind.Shutter.Dto.GnosisDecryptionKeySharesExtra subBuilder = new global::Nethermind.Shutter.Dto.GnosisDecryptionKeySharesExtra(); + if (extraCase_ == ExtraOneofCase.Gnosis) { + subBuilder.MergeFrom(Gnosis); + } + input.ReadMessage(subBuilder); + Gnosis = subBuilder; + break; + } + case 90: { + global::Nethermind.Shutter.Dto.OptimismDecryptionKeySharesExtra subBuilder = new global::Nethermind.Shutter.Dto.OptimismDecryptionKeySharesExtra(); + if (extraCase_ == ExtraOneofCase.Optimism) { + subBuilder.MergeFrom(Optimism); + } + input.ReadMessage(subBuilder); + Optimism = subBuilder; + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 8: { + InstanceID = input.ReadUInt64(); + break; + } + case 32: { + Eon = input.ReadUInt64(); + break; + } + case 40: { + KeyperIndex = input.ReadUInt64(); + break; + } + case 74: { + shares_.AddEntriesFrom(ref input, _repeated_shares_codec); + break; + } + case 82: { + global::Nethermind.Shutter.Dto.GnosisDecryptionKeySharesExtra subBuilder = new global::Nethermind.Shutter.Dto.GnosisDecryptionKeySharesExtra(); + if (extraCase_ == ExtraOneofCase.Gnosis) { + subBuilder.MergeFrom(Gnosis); + } + input.ReadMessage(subBuilder); + Gnosis = subBuilder; + break; + } + case 90: { + global::Nethermind.Shutter.Dto.OptimismDecryptionKeySharesExtra subBuilder = new global::Nethermind.Shutter.Dto.OptimismDecryptionKeySharesExtra(); + if (extraCase_ == ExtraOneofCase.Optimism) { + subBuilder.MergeFrom(Optimism); + } + input.ReadMessage(subBuilder); + Optimism = subBuilder; + break; + } + } + } + } + #endif + + } + + public sealed partial class Key : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Key()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::Nethermind.Shutter.Dto.GossipReflection.Descriptor.MessageTypes[5]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public Key() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public Key(Key other) : this() { + identity_ = other.identity_; + key_ = other.key_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public Key Clone() { + return new Key(this); + } + + /// Field number for the "identity" field. + public const int IdentityFieldNumber = 1; + private pb::ByteString identity_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pb::ByteString Identity { + get { return identity_; } + set { + identity_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "key" field. + public const int Key_FieldNumber = 2; + private pb::ByteString key_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pb::ByteString Key_ { + get { return key_; } + set { + key_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as Key); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(Key other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Identity != other.Identity) return false; + if (Key_ != other.Key_) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (Identity.Length != 0) hash ^= Identity.GetHashCode(); + if (Key_.Length != 0) hash ^= Key_.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (Identity.Length != 0) { + output.WriteRawTag(10); + output.WriteBytes(Identity); + } + if (Key_.Length != 0) { + output.WriteRawTag(18); + output.WriteBytes(Key_); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (Identity.Length != 0) { + output.WriteRawTag(10); + output.WriteBytes(Identity); + } + if (Key_.Length != 0) { + output.WriteRawTag(18); + output.WriteBytes(Key_); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (Identity.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Identity); + } + if (Key_.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Key_); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(Key other) { + if (other == null) { + return; + } + if (other.Identity.Length != 0) { + Identity = other.Identity; + } + if (other.Key_.Length != 0) { + Key_ = other.Key_; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + Identity = input.ReadBytes(); + break; + } + case 18: { + Key_ = input.ReadBytes(); + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 10: { + Identity = input.ReadBytes(); + break; + } + case 18: { + Key_ = input.ReadBytes(); + break; + } + } + } + } + #endif + + } + + public sealed partial class GnosisDecryptionKeysExtra : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new GnosisDecryptionKeysExtra()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::Nethermind.Shutter.Dto.GossipReflection.Descriptor.MessageTypes[6]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public GnosisDecryptionKeysExtra() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public GnosisDecryptionKeysExtra(GnosisDecryptionKeysExtra other) : this() { + slot_ = other.slot_; + txPointer_ = other.txPointer_; + signerIndices_ = other.signerIndices_.Clone(); + signatures_ = other.signatures_.Clone(); + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public GnosisDecryptionKeysExtra Clone() { + return new GnosisDecryptionKeysExtra(this); + } + + /// Field number for the "slot" field. + public const int SlotFieldNumber = 1; + private ulong slot_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ulong Slot { + get { return slot_; } + set { + slot_ = value; + } + } + + /// Field number for the "tx_pointer" field. + public const int TxPointerFieldNumber = 2; + private ulong txPointer_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ulong TxPointer { + get { return txPointer_; } + set { + txPointer_ = value; + } + } + + /// Field number for the "signerIndices" field. + public const int SignerIndicesFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_signerIndices_codec + = pb::FieldCodec.ForUInt64(26); + private readonly pbc::RepeatedField signerIndices_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pbc::RepeatedField SignerIndices { + get { return signerIndices_; } + } + + /// Field number for the "signatures" field. + public const int SignaturesFieldNumber = 4; + private static readonly pb::FieldCodec _repeated_signatures_codec + = pb::FieldCodec.ForBytes(34); + private readonly pbc::RepeatedField signatures_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pbc::RepeatedField Signatures { + get { return signatures_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as GnosisDecryptionKeysExtra); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(GnosisDecryptionKeysExtra other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Slot != other.Slot) return false; + if (TxPointer != other.TxPointer) return false; + if(!signerIndices_.Equals(other.signerIndices_)) return false; + if(!signatures_.Equals(other.signatures_)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (Slot != 0UL) hash ^= Slot.GetHashCode(); + if (TxPointer != 0UL) hash ^= TxPointer.GetHashCode(); + hash ^= signerIndices_.GetHashCode(); + hash ^= signatures_.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (Slot != 0UL) { + output.WriteRawTag(8); + output.WriteUInt64(Slot); + } + if (TxPointer != 0UL) { + output.WriteRawTag(16); + output.WriteUInt64(TxPointer); + } + signerIndices_.WriteTo(output, _repeated_signerIndices_codec); + signatures_.WriteTo(output, _repeated_signatures_codec); + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (Slot != 0UL) { + output.WriteRawTag(8); + output.WriteUInt64(Slot); + } + if (TxPointer != 0UL) { + output.WriteRawTag(16); + output.WriteUInt64(TxPointer); + } + signerIndices_.WriteTo(ref output, _repeated_signerIndices_codec); + signatures_.WriteTo(ref output, _repeated_signatures_codec); + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (Slot != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Slot); + } + if (TxPointer != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(TxPointer); + } + size += signerIndices_.CalculateSize(_repeated_signerIndices_codec); + size += signatures_.CalculateSize(_repeated_signatures_codec); + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(GnosisDecryptionKeysExtra other) { + if (other == null) { + return; + } + if (other.Slot != 0UL) { + Slot = other.Slot; + } + if (other.TxPointer != 0UL) { + TxPointer = other.TxPointer; + } + signerIndices_.Add(other.signerIndices_); + signatures_.Add(other.signatures_); + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + Slot = input.ReadUInt64(); + break; + } + case 16: { + TxPointer = input.ReadUInt64(); + break; + } + case 26: + case 24: { + signerIndices_.AddEntriesFrom(input, _repeated_signerIndices_codec); + break; + } + case 34: { + signatures_.AddEntriesFrom(input, _repeated_signatures_codec); + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 8: { + Slot = input.ReadUInt64(); + break; + } + case 16: { + TxPointer = input.ReadUInt64(); + break; + } + case 26: + case 24: { + signerIndices_.AddEntriesFrom(ref input, _repeated_signerIndices_codec); + break; + } + case 34: { + signatures_.AddEntriesFrom(ref input, _repeated_signatures_codec); + break; + } + } + } + } + #endif + + } + + public sealed partial class OptimismDecryptionKeysExtra : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new OptimismDecryptionKeysExtra()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::Nethermind.Shutter.Dto.GossipReflection.Descriptor.MessageTypes[7]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public OptimismDecryptionKeysExtra() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public OptimismDecryptionKeysExtra(OptimismDecryptionKeysExtra other) : this() { + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public OptimismDecryptionKeysExtra Clone() { + return new OptimismDecryptionKeysExtra(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as OptimismDecryptionKeysExtra); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(OptimismDecryptionKeysExtra other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(OptimismDecryptionKeysExtra other) { + if (other == null) { + return; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + } + } + } + #endif + + } + + public sealed partial class DecryptionKeys : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new DecryptionKeys()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::Nethermind.Shutter.Dto.GossipReflection.Descriptor.MessageTypes[8]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public DecryptionKeys() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public DecryptionKeys(DecryptionKeys other) : this() { + instanceID_ = other.instanceID_; + eon_ = other.eon_; + keys_ = other.keys_.Clone(); + switch (other.ExtraCase) { + case ExtraOneofCase.Gnosis: + Gnosis = other.Gnosis.Clone(); + break; + case ExtraOneofCase.Optimism: + Optimism = other.Optimism.Clone(); + break; + } + + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public DecryptionKeys Clone() { + return new DecryptionKeys(this); + } + + /// Field number for the "instanceID" field. + public const int InstanceIDFieldNumber = 1; + private ulong instanceID_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ulong InstanceID { + get { return instanceID_; } + set { + instanceID_ = value; + } + } + + /// Field number for the "eon" field. + public const int EonFieldNumber = 2; + private ulong eon_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ulong Eon { + get { return eon_; } + set { + eon_ = value; + } + } + + /// Field number for the "keys" field. + public const int KeysFieldNumber = 3; + private static readonly pb::FieldCodec _repeated_keys_codec + = pb::FieldCodec.ForMessage(26, global::Nethermind.Shutter.Dto.Key.Parser); + private readonly pbc::RepeatedField keys_ = new pbc::RepeatedField(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pbc::RepeatedField Keys { + get { return keys_; } + } + + /// Field number for the "gnosis" field. + public const int GnosisFieldNumber = 4; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public global::Nethermind.Shutter.Dto.GnosisDecryptionKeysExtra Gnosis { + get { return extraCase_ == ExtraOneofCase.Gnosis ? (global::Nethermind.Shutter.Dto.GnosisDecryptionKeysExtra) extra_ : null; } + set { + extra_ = value; + extraCase_ = value == null ? ExtraOneofCase.None : ExtraOneofCase.Gnosis; + } + } + + /// Field number for the "optimism" field. + public const int OptimismFieldNumber = 5; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public global::Nethermind.Shutter.Dto.OptimismDecryptionKeysExtra Optimism { + get { return extraCase_ == ExtraOneofCase.Optimism ? (global::Nethermind.Shutter.Dto.OptimismDecryptionKeysExtra) extra_ : null; } + set { + extra_ = value; + extraCase_ = value == null ? ExtraOneofCase.None : ExtraOneofCase.Optimism; + } + } + + private object extra_; + /// Enum of possible cases for the "extra" oneof. + public enum ExtraOneofCase { + None = 0, + Gnosis = 4, + Optimism = 5, + } + private ExtraOneofCase extraCase_ = ExtraOneofCase.None; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ExtraOneofCase ExtraCase { + get { return extraCase_; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void ClearExtra() { + extraCase_ = ExtraOneofCase.None; + extra_ = null; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as DecryptionKeys); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(DecryptionKeys other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (InstanceID != other.InstanceID) return false; + if (Eon != other.Eon) return false; + if(!keys_.Equals(other.keys_)) return false; + if (!object.Equals(Gnosis, other.Gnosis)) return false; + if (!object.Equals(Optimism, other.Optimism)) return false; + if (ExtraCase != other.ExtraCase) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (InstanceID != 0UL) hash ^= InstanceID.GetHashCode(); + if (Eon != 0UL) hash ^= Eon.GetHashCode(); + hash ^= keys_.GetHashCode(); + if (extraCase_ == ExtraOneofCase.Gnosis) hash ^= Gnosis.GetHashCode(); + if (extraCase_ == ExtraOneofCase.Optimism) hash ^= Optimism.GetHashCode(); + hash ^= (int) extraCase_; + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (InstanceID != 0UL) { + output.WriteRawTag(8); + output.WriteUInt64(InstanceID); + } + if (Eon != 0UL) { + output.WriteRawTag(16); + output.WriteUInt64(Eon); + } + keys_.WriteTo(output, _repeated_keys_codec); + if (extraCase_ == ExtraOneofCase.Gnosis) { + output.WriteRawTag(34); + output.WriteMessage(Gnosis); + } + if (extraCase_ == ExtraOneofCase.Optimism) { + output.WriteRawTag(42); + output.WriteMessage(Optimism); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (InstanceID != 0UL) { + output.WriteRawTag(8); + output.WriteUInt64(InstanceID); + } + if (Eon != 0UL) { + output.WriteRawTag(16); + output.WriteUInt64(Eon); + } + keys_.WriteTo(ref output, _repeated_keys_codec); + if (extraCase_ == ExtraOneofCase.Gnosis) { + output.WriteRawTag(34); + output.WriteMessage(Gnosis); + } + if (extraCase_ == ExtraOneofCase.Optimism) { + output.WriteRawTag(42); + output.WriteMessage(Optimism); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (InstanceID != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(InstanceID); + } + if (Eon != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Eon); + } + size += keys_.CalculateSize(_repeated_keys_codec); + if (extraCase_ == ExtraOneofCase.Gnosis) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Gnosis); + } + if (extraCase_ == ExtraOneofCase.Optimism) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Optimism); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(DecryptionKeys other) { + if (other == null) { + return; + } + if (other.InstanceID != 0UL) { + InstanceID = other.InstanceID; + } + if (other.Eon != 0UL) { + Eon = other.Eon; + } + keys_.Add(other.keys_); + switch (other.ExtraCase) { + case ExtraOneofCase.Gnosis: + if (Gnosis == null) { + Gnosis = new global::Nethermind.Shutter.Dto.GnosisDecryptionKeysExtra(); + } + Gnosis.MergeFrom(other.Gnosis); + break; + case ExtraOneofCase.Optimism: + if (Optimism == null) { + Optimism = new global::Nethermind.Shutter.Dto.OptimismDecryptionKeysExtra(); + } + Optimism.MergeFrom(other.Optimism); + break; + } + + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + InstanceID = input.ReadUInt64(); + break; + } + case 16: { + Eon = input.ReadUInt64(); + break; + } + case 26: { + keys_.AddEntriesFrom(input, _repeated_keys_codec); + break; + } + case 34: { + global::Nethermind.Shutter.Dto.GnosisDecryptionKeysExtra subBuilder = new global::Nethermind.Shutter.Dto.GnosisDecryptionKeysExtra(); + if (extraCase_ == ExtraOneofCase.Gnosis) { + subBuilder.MergeFrom(Gnosis); + } + input.ReadMessage(subBuilder); + Gnosis = subBuilder; + break; + } + case 42: { + global::Nethermind.Shutter.Dto.OptimismDecryptionKeysExtra subBuilder = new global::Nethermind.Shutter.Dto.OptimismDecryptionKeysExtra(); + if (extraCase_ == ExtraOneofCase.Optimism) { + subBuilder.MergeFrom(Optimism); + } + input.ReadMessage(subBuilder); + Optimism = subBuilder; + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 8: { + InstanceID = input.ReadUInt64(); + break; + } + case 16: { + Eon = input.ReadUInt64(); + break; + } + case 26: { + keys_.AddEntriesFrom(ref input, _repeated_keys_codec); + break; + } + case 34: { + global::Nethermind.Shutter.Dto.GnosisDecryptionKeysExtra subBuilder = new global::Nethermind.Shutter.Dto.GnosisDecryptionKeysExtra(); + if (extraCase_ == ExtraOneofCase.Gnosis) { + subBuilder.MergeFrom(Gnosis); + } + input.ReadMessage(subBuilder); + Gnosis = subBuilder; + break; + } + case 42: { + global::Nethermind.Shutter.Dto.OptimismDecryptionKeysExtra subBuilder = new global::Nethermind.Shutter.Dto.OptimismDecryptionKeysExtra(); + if (extraCase_ == ExtraOneofCase.Optimism) { + subBuilder.MergeFrom(Optimism); + } + input.ReadMessage(subBuilder); + Optimism = subBuilder; + break; + } + } + } + } + #endif + + } + + /// + /// EonPublicKey is sent by the keypers to publish the EonPublicKey for a certain + /// eon. For those that observe it, e.g. the collator, it's a candidate until + /// the observer has seen at least threshold messages. + /// + public sealed partial class EonPublicKey : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new EonPublicKey()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::Nethermind.Shutter.Dto.GossipReflection.Descriptor.MessageTypes[9]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public EonPublicKey() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public EonPublicKey(EonPublicKey other) : this() { + instanceID_ = other.instanceID_; + publicKey_ = other.publicKey_; + activationBlock_ = other.activationBlock_; + keyperConfigIndex_ = other.keyperConfigIndex_; + eon_ = other.eon_; + signature_ = other.signature_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public EonPublicKey Clone() { + return new EonPublicKey(this); + } + + /// Field number for the "instanceID" field. + public const int InstanceIDFieldNumber = 1; + private ulong instanceID_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ulong InstanceID { + get { return instanceID_; } + set { + instanceID_ = value; + } + } + + /// Field number for the "publicKey" field. + public const int PublicKeyFieldNumber = 2; + private pb::ByteString publicKey_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pb::ByteString PublicKey { + get { return publicKey_; } + set { + publicKey_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "activationBlock" field. + public const int ActivationBlockFieldNumber = 3; + private ulong activationBlock_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ulong ActivationBlock { + get { return activationBlock_; } + set { + activationBlock_ = value; + } + } + + /// Field number for the "keyperConfigIndex" field. + public const int KeyperConfigIndexFieldNumber = 6; + private ulong keyperConfigIndex_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ulong KeyperConfigIndex { + get { return keyperConfigIndex_; } + set { + keyperConfigIndex_ = value; + } + } + + /// Field number for the "eon" field. + public const int EonFieldNumber = 7; + private ulong eon_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public ulong Eon { + get { return eon_; } + set { + eon_ = value; + } + } + + /// Field number for the "signature" field. + public const int SignatureFieldNumber = 5; + private pb::ByteString signature_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pb::ByteString Signature { + get { return signature_; } + set { + signature_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as EonPublicKey); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(EonPublicKey other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (InstanceID != other.InstanceID) return false; + if (PublicKey != other.PublicKey) return false; + if (ActivationBlock != other.ActivationBlock) return false; + if (KeyperConfigIndex != other.KeyperConfigIndex) return false; + if (Eon != other.Eon) return false; + if (Signature != other.Signature) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (InstanceID != 0UL) hash ^= InstanceID.GetHashCode(); + if (PublicKey.Length != 0) hash ^= PublicKey.GetHashCode(); + if (ActivationBlock != 0UL) hash ^= ActivationBlock.GetHashCode(); + if (KeyperConfigIndex != 0UL) hash ^= KeyperConfigIndex.GetHashCode(); + if (Eon != 0UL) hash ^= Eon.GetHashCode(); + if (Signature.Length != 0) hash ^= Signature.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (InstanceID != 0UL) { + output.WriteRawTag(8); + output.WriteUInt64(InstanceID); + } + if (PublicKey.Length != 0) { + output.WriteRawTag(18); + output.WriteBytes(PublicKey); + } + if (ActivationBlock != 0UL) { + output.WriteRawTag(24); + output.WriteUInt64(ActivationBlock); + } + if (Signature.Length != 0) { + output.WriteRawTag(42); + output.WriteBytes(Signature); + } + if (KeyperConfigIndex != 0UL) { + output.WriteRawTag(48); + output.WriteUInt64(KeyperConfigIndex); + } + if (Eon != 0UL) { + output.WriteRawTag(56); + output.WriteUInt64(Eon); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (InstanceID != 0UL) { + output.WriteRawTag(8); + output.WriteUInt64(InstanceID); + } + if (PublicKey.Length != 0) { + output.WriteRawTag(18); + output.WriteBytes(PublicKey); + } + if (ActivationBlock != 0UL) { + output.WriteRawTag(24); + output.WriteUInt64(ActivationBlock); + } + if (Signature.Length != 0) { + output.WriteRawTag(42); + output.WriteBytes(Signature); + } + if (KeyperConfigIndex != 0UL) { + output.WriteRawTag(48); + output.WriteUInt64(KeyperConfigIndex); + } + if (Eon != 0UL) { + output.WriteRawTag(56); + output.WriteUInt64(Eon); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (InstanceID != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(InstanceID); + } + if (PublicKey.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(PublicKey); + } + if (ActivationBlock != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(ActivationBlock); + } + if (KeyperConfigIndex != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(KeyperConfigIndex); + } + if (Eon != 0UL) { + size += 1 + pb::CodedOutputStream.ComputeUInt64Size(Eon); + } + if (Signature.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Signature); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(EonPublicKey other) { + if (other == null) { + return; + } + if (other.InstanceID != 0UL) { + InstanceID = other.InstanceID; + } + if (other.PublicKey.Length != 0) { + PublicKey = other.PublicKey; + } + if (other.ActivationBlock != 0UL) { + ActivationBlock = other.ActivationBlock; + } + if (other.KeyperConfigIndex != 0UL) { + KeyperConfigIndex = other.KeyperConfigIndex; + } + if (other.Eon != 0UL) { + Eon = other.Eon; + } + if (other.Signature.Length != 0) { + Signature = other.Signature; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 8: { + InstanceID = input.ReadUInt64(); + break; + } + case 18: { + PublicKey = input.ReadBytes(); + break; + } + case 24: { + ActivationBlock = input.ReadUInt64(); + break; + } + case 42: { + Signature = input.ReadBytes(); + break; + } + case 48: { + KeyperConfigIndex = input.ReadUInt64(); + break; + } + case 56: { + Eon = input.ReadUInt64(); + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 8: { + InstanceID = input.ReadUInt64(); + break; + } + case 18: { + PublicKey = input.ReadBytes(); + break; + } + case 24: { + ActivationBlock = input.ReadUInt64(); + break; + } + case 42: { + Signature = input.ReadBytes(); + break; + } + case 48: { + KeyperConfigIndex = input.ReadUInt64(); + break; + } + case 56: { + Eon = input.ReadUInt64(); + break; + } + } + } + } + #endif + + } + + public sealed partial class TraceContext : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new TraceContext()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::Nethermind.Shutter.Dto.GossipReflection.Descriptor.MessageTypes[10]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public TraceContext() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public TraceContext(TraceContext other) : this() { + traceID_ = other.traceID_; + spanID_ = other.spanID_; + traceFlags_ = other.traceFlags_; + traceState_ = other.traceState_; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public TraceContext Clone() { + return new TraceContext(this); + } + + /// Field number for the "traceID" field. + public const int TraceIDFieldNumber = 1; + private pb::ByteString traceID_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pb::ByteString TraceID { + get { return traceID_; } + set { + traceID_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "spanID" field. + public const int SpanIDFieldNumber = 2; + private pb::ByteString spanID_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pb::ByteString SpanID { + get { return spanID_; } + set { + spanID_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "traceFlags" field. + public const int TraceFlagsFieldNumber = 3; + private pb::ByteString traceFlags_ = pb::ByteString.Empty; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public pb::ByteString TraceFlags { + get { return traceFlags_; } + set { + traceFlags_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "traceState" field. + public const int TraceStateFieldNumber = 4; + private string traceState_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string TraceState { + get { return traceState_; } + set { + traceState_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as TraceContext); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(TraceContext other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (TraceID != other.TraceID) return false; + if (SpanID != other.SpanID) return false; + if (TraceFlags != other.TraceFlags) return false; + if (TraceState != other.TraceState) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (TraceID.Length != 0) hash ^= TraceID.GetHashCode(); + if (SpanID.Length != 0) hash ^= SpanID.GetHashCode(); + if (TraceFlags.Length != 0) hash ^= TraceFlags.GetHashCode(); + if (TraceState.Length != 0) hash ^= TraceState.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (TraceID.Length != 0) { + output.WriteRawTag(10); + output.WriteBytes(TraceID); + } + if (SpanID.Length != 0) { + output.WriteRawTag(18); + output.WriteBytes(SpanID); + } + if (TraceFlags.Length != 0) { + output.WriteRawTag(26); + output.WriteBytes(TraceFlags); + } + if (TraceState.Length != 0) { + output.WriteRawTag(34); + output.WriteString(TraceState); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (TraceID.Length != 0) { + output.WriteRawTag(10); + output.WriteBytes(TraceID); + } + if (SpanID.Length != 0) { + output.WriteRawTag(18); + output.WriteBytes(SpanID); + } + if (TraceFlags.Length != 0) { + output.WriteRawTag(26); + output.WriteBytes(TraceFlags); + } + if (TraceState.Length != 0) { + output.WriteRawTag(34); + output.WriteString(TraceState); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (TraceID.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(TraceID); + } + if (SpanID.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(SpanID); + } + if (TraceFlags.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(TraceFlags); + } + if (TraceState.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(TraceState); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(TraceContext other) { + if (other == null) { + return; + } + if (other.TraceID.Length != 0) { + TraceID = other.TraceID; + } + if (other.SpanID.Length != 0) { + SpanID = other.SpanID; + } + if (other.TraceFlags.Length != 0) { + TraceFlags = other.TraceFlags; + } + if (other.TraceState.Length != 0) { + TraceState = other.TraceState; + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + TraceID = input.ReadBytes(); + break; + } + case 18: { + SpanID = input.ReadBytes(); + break; + } + case 26: { + TraceFlags = input.ReadBytes(); + break; + } + case 34: { + TraceState = input.ReadString(); + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 10: { + TraceID = input.ReadBytes(); + break; + } + case 18: { + SpanID = input.ReadBytes(); + break; + } + case 26: { + TraceFlags = input.ReadBytes(); + break; + } + case 34: { + TraceState = input.ReadString(); + break; + } + } + } + } + #endif + + } + + public sealed partial class Envelope : pb::IMessage + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + , pb::IBufferMessage + #endif + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Envelope()); + private pb::UnknownFieldSet _unknownFields; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pb::MessageParser Parser { get { return _parser; } } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public static pbr::MessageDescriptor Descriptor { + get { return global::Nethermind.Shutter.Dto.GossipReflection.Descriptor.MessageTypes[11]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + pbr::MessageDescriptor pb::IMessage.Descriptor { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public Envelope() { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public Envelope(Envelope other) : this() { + version_ = other.version_; + message_ = other.message_ != null ? other.message_.Clone() : null; + trace_ = other.trace_ != null ? other.trace_.Clone() : null; + _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public Envelope Clone() { + return new Envelope(this); + } + + /// Field number for the "version" field. + public const int VersionFieldNumber = 1; + private string version_ = ""; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public string Version { + get { return version_; } + set { + version_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); + } + } + + /// Field number for the "message" field. + public const int MessageFieldNumber = 2; + private global::Google.Protobuf.WellKnownTypes.Any message_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public global::Google.Protobuf.WellKnownTypes.Any Message { + get { return message_; } + set { + message_ = value; + } + } + + /// Field number for the "trace" field. + public const int TraceFieldNumber = 3; + private global::Nethermind.Shutter.Dto.TraceContext trace_; + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public global::Nethermind.Shutter.Dto.TraceContext Trace { + get { return trace_; } + set { + trace_ = value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override bool Equals(object other) { + return Equals(other as Envelope); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public bool Equals(Envelope other) { + if (ReferenceEquals(other, null)) { + return false; + } + if (ReferenceEquals(other, this)) { + return true; + } + if (Version != other.Version) return false; + if (!object.Equals(Message, other.Message)) return false; + if (!object.Equals(Trace, other.Trace)) return false; + return Equals(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override int GetHashCode() { + int hash = 1; + if (Version.Length != 0) hash ^= Version.GetHashCode(); + if (message_ != null) hash ^= Message.GetHashCode(); + if (trace_ != null) hash ^= Trace.GetHashCode(); + if (_unknownFields != null) { + hash ^= _unknownFields.GetHashCode(); + } + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public override string ToString() { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void WriteTo(pb::CodedOutputStream output) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + output.WriteRawMessage(this); + #else + if (Version.Length != 0) { + output.WriteRawTag(10); + output.WriteString(Version); + } + if (message_ != null) { + output.WriteRawTag(18); + output.WriteMessage(Message); + } + if (trace_ != null) { + output.WriteRawTag(26); + output.WriteMessage(Trace); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(output); + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) { + if (Version.Length != 0) { + output.WriteRawTag(10); + output.WriteString(Version); + } + if (message_ != null) { + output.WriteRawTag(18); + output.WriteMessage(Message); + } + if (trace_ != null) { + output.WriteRawTag(26); + output.WriteMessage(Trace); + } + if (_unknownFields != null) { + _unknownFields.WriteTo(ref output); + } + } + #endif + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public int CalculateSize() { + int size = 0; + if (Version.Length != 0) { + size += 1 + pb::CodedOutputStream.ComputeStringSize(Version); + } + if (message_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Message); + } + if (trace_ != null) { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Trace); + } + if (_unknownFields != null) { + size += _unknownFields.CalculateSize(); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(Envelope other) { + if (other == null) { + return; + } + if (other.Version.Length != 0) { + Version = other.Version; + } + if (other.message_ != null) { + if (message_ == null) { + Message = new global::Google.Protobuf.WellKnownTypes.Any(); + } + Message.MergeFrom(other.Message); + } + if (other.trace_ != null) { + if (trace_ == null) { + Trace = new global::Nethermind.Shutter.Dto.TraceContext(); + } + Trace.MergeFrom(other.Trace); + } + _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + public void MergeFrom(pb::CodedInputStream input) { + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + input.ReadRawMessage(this); + #else + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); + break; + case 10: { + Version = input.ReadString(); + break; + } + case 18: { + if (message_ == null) { + Message = new global::Google.Protobuf.WellKnownTypes.Any(); + } + input.ReadMessage(Message); + break; + } + case 26: { + if (trace_ == null) { + Trace = new global::Nethermind.Shutter.Dto.TraceContext(); + } + input.ReadMessage(Trace); + break; + } + } + } + #endif + } + + #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)] + void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) { + uint tag; + while ((tag = input.ReadTag()) != 0) { + switch(tag) { + default: + _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); + break; + case 10: { + Version = input.ReadString(); + break; + } + case 18: { + if (message_ == null) { + Message = new global::Google.Protobuf.WellKnownTypes.Any(); + } + input.ReadMessage(Message); + break; + } + case 26: { + if (trace_ == null) { + Trace = new global::Nethermind.Shutter.Dto.TraceContext(); + } + input.ReadMessage(Trace); + break; + } + } + } + } + #endif + + } + + #endregion + +} + +#endregion Designer generated code diff --git a/src/Nethermind/Nethermind.Shutter/Dto/gossip.proto b/src/Nethermind/Nethermind.Shutter/Dto/gossip.proto new file mode 100644 index 00000000000..271f9f6c625 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Dto/gossip.proto @@ -0,0 +1,89 @@ +syntax = "proto3"; +package p2pmsg; + +// import "any.proto"; + +option go_package = "./;p2pmsg"; +option csharp_namespace = "Nethermind.Shutter.Dto"; + +message DecryptionTrigger { + uint64 instanceID = 1; + bytes epochID = 2; + uint64 blockNumber = 3; + bytes transactionsHash = 4; + bytes signature = 5; +} + +message KeyShare { + bytes epochID = 1; + bytes share = 2; +} + +message GnosisDecryptionKeySharesExtra { + uint64 slot = 1; + uint64 tx_pointer = 2; + bytes signature = 3; +} + +message OptimismDecryptionKeySharesExtra {} + +message DecryptionKeyShares { + uint64 instanceID = 1; + uint64 eon = 4; + uint64 keyperIndex = 5; + repeated KeyShare shares = 9; + oneof extra { + GnosisDecryptionKeySharesExtra gnosis = 10; + OptimismDecryptionKeySharesExtra optimism = 11; + } +} + +message Key { + bytes identity = 1; + bytes key = 2; +} + +message GnosisDecryptionKeysExtra { + uint64 slot = 1; + uint64 tx_pointer = 2; + repeated uint64 signerIndices = 3; + repeated bytes signatures = 4; +} + +message OptimismDecryptionKeysExtra {} + +message DecryptionKeys { + uint64 instanceID = 1; + uint64 eon = 2; + repeated Key keys = 3; + oneof extra { + GnosisDecryptionKeysExtra gnosis = 4; + OptimismDecryptionKeysExtra optimism = 5; + } +} + +// EonPublicKey is sent by the keypers to publish the EonPublicKey for a certain +// eon. For those that observe it, e.g. the collator, it's a candidate until +// the observer has seen at least threshold messages. +message EonPublicKey { + uint64 instanceID = 1; + bytes publicKey= 2; + uint64 activationBlock = 3; + uint64 keyperConfigIndex = 6; + uint64 eon = 7; + bytes signature = 5; +} + + +message TraceContext { + bytes traceID = 1; + bytes spanID = 2; + bytes traceFlags = 3; + string traceState = 4; +} + +message Envelope { + string version = 1 ; + google.protobuf.Any message = 2; + optional TraceContext trace = 3; +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Shutter/IShutterApi.cs b/src/Nethermind/Nethermind.Shutter/IShutterApi.cs new file mode 100644 index 00000000000..c6ed5544372 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/IShutterApi.cs @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Consensus; + +namespace Nethermind.Shutter; + +public interface IShutterApi +{ + ShutterTxSource TxSource { get; } + Task StartP2P(CancellationTokenSource? cancellationTokenSource = null); + ShutterBlockImprovementContextFactory GetBlockImprovementContextFactory(IBlockProducer blockProducer); + ValueTask DisposeAsync(); +} diff --git a/src/Nethermind/Nethermind.Shutter/IShutterBlockHandler.cs b/src/Nethermind/Nethermind.Shutter/IShutterBlockHandler.cs new file mode 100644 index 00000000000..8db86be349a --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/IShutterBlockHandler.cs @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Core; + +namespace Nethermind.Shutter; +public interface IShutterBlockHandler : IDisposable +{ + Task WaitForBlockInSlot(ulong slot, CancellationToken cancellationToken, Func? initTimeoutSource = null); +} diff --git a/src/Nethermind/Nethermind.Shutter/IShutterEon.cs b/src/Nethermind/Nethermind.Shutter/IShutterEon.cs new file mode 100644 index 00000000000..b697725bb2a --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/IShutterEon.cs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Shutter; + +public interface IShutterEon +{ + Info? GetCurrentEonInfo(); + + void Update(BlockHeader header); + + readonly struct Info + { + public ulong Eon { get; init; } + public byte[] Key { get; init; } + public ulong Threshold { get; init; } + public Address[] Addresses { get; init; } + } +} diff --git a/src/Nethermind/Nethermind.Shutter/IShutterKeyValidator.cs b/src/Nethermind/Nethermind.Shutter/IShutterKeyValidator.cs new file mode 100644 index 00000000000..8826ae6cd8f --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/IShutterKeyValidator.cs @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core.Collections; + +namespace Nethermind.Shutter; + +public interface IShutterKeyValidator +{ + ValidatedKeys? ValidateKeys(Dto.DecryptionKeys decryptionKeys); + + struct ValidatedKeys + { + public ulong Slot; + public ulong Eon; + public ulong TxPointer; + public EnumerableWithCount<(ReadOnlyMemory IdentityPreimage, ReadOnlyMemory Key)> Keys; + } +} diff --git a/src/Nethermind/Nethermind.Shutter/IShutterP2P.cs b/src/Nethermind/Nethermind.Shutter/IShutterP2P.cs new file mode 100644 index 00000000000..43f6bdb38e1 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/IShutterP2P.cs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Nethermind.Shutter; + +public interface IShutterP2P +{ + Task Start(Func onKeysReceived, CancellationTokenSource? cts = null); + ValueTask DisposeAsync(); +} diff --git a/src/Nethermind/Nethermind.Shutter/IShutterTxSignal.cs b/src/Nethermind/Nethermind.Shutter/IShutterTxSignal.cs new file mode 100644 index 00000000000..edb5cc510c7 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/IShutterTxSignal.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading; +using System.Threading.Tasks; + +namespace Nethermind.Shutter; +public interface IShutterTxSignal +{ + Task WaitForTransactions(ulong slot, CancellationToken cancellationToken); + bool HaveTransactionsArrived(ulong slot); +} diff --git a/src/Nethermind/Nethermind.Shutter/Metrics.cs b/src/Nethermind/Nethermind.Shutter/Metrics.cs new file mode 100644 index 00000000000..6669857787c --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Metrics.cs @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.ComponentModel; +using Nethermind.Core.Attributes; + +namespace Nethermind.Shutter; + +public class Metrics +{ + [CounterMetric] + [Description("Number of keys not received.")] + public static ulong ShutterKeysMissed { get; set; } + + [Description("Eon of the latest block.")] + public static ulong ShutterEon { get; set; } + + [Description("Size of keyper set in current eon.")] + public static int ShutterKeypers { get; set; } + + [Description("Number of keypers assumed to be honest and online for current eon.")] + public static int ShutterThreshold { get; set; } + + [Description("Number of transactions since Shutter genesis.")] + public static ulong ShutterTxPointer { get; set; } + + [GaugeMetric] + [Description("Relative time offset (ms) from slot boundary that keys were received.")] + public static long ShutterKeysReceivedTimeOffset { get; set; } + + [GaugeMetric] + [Description("Number of transactions included.")] + public static uint ShutterTransactions { get; set; } + + [GaugeMetric] + [Description("Number of invalid transactions that could not be included.")] + public static uint ShutterBadTransactions { get; set; } + + [GaugeMetric] + [Description("Amount of encrypted gas used.")] + public static ulong ShutterEncryptedGasUsed { get; set; } +} diff --git a/src/Nethermind/Nethermind.Shutter/Nethermind.Shutter.csproj b/src/Nethermind/Nethermind.Shutter/Nethermind.Shutter.csproj new file mode 100644 index 00000000000..5f7acfe37b8 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Nethermind.Shutter.csproj @@ -0,0 +1,48 @@ + + + + true + Nethermind.Shutter + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Nethermind/Nethermind.Shutter/ShutterApi.cs b/src/Nethermind/Nethermind.Shutter/ShutterApi.cs new file mode 100644 index 00000000000..91b53f6a17d --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterApi.cs @@ -0,0 +1,153 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Abi; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Find; +using Nethermind.Blockchain.Receipts; +using Nethermind.Consensus; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Crypto; +using Nethermind.Facade.Find; +using Nethermind.Logging; +using Nethermind.Shutter.Config; +using Nethermind.State; + +namespace Nethermind.Shutter; + +public class ShutterApi : IShutterApi +{ + public virtual TimeSpan BlockWaitCutoff { get => _blockWaitCutoff; } + + public readonly ShutterBlockHandler BlockHandler; + public readonly IShutterKeyValidator KeyValidator; + public readonly IShutterEon Eon; + public readonly ShutterTxLoader TxLoader; + public readonly ShutterTime Time; + public ShutterTxSource TxSource { get; } + public IShutterP2P? P2P; + public ShutterBlockImprovementContextFactory? BlockImprovementContextFactory; + + protected readonly TimeSpan _slotLength; + protected readonly TimeSpan _blockUpToDateCutoff; + protected readonly IReadOnlyBlockTree _readOnlyBlockTree; + protected readonly IBlockTree _blockTree; + private readonly ReadOnlyTxProcessingEnvFactory _txProcessingEnvFactory; + private readonly IAbiEncoder _abiEncoder; + private readonly ILogManager _logManager; + private readonly IShutterConfig _cfg; + private readonly TimeSpan _blockWaitCutoff; + + public ShutterApi( + IAbiEncoder abiEncoder, + IBlockTree blockTree, + IEthereumEcdsa ecdsa, + ILogFinder logFinder, + IReceiptFinder receiptFinder, + ILogManager logManager, + ISpecProvider specProvider, + ITimestamper timestamper, + IWorldStateManager worldStateManager, + IShutterConfig cfg, + Dictionary validatorsInfo, + TimeSpan slotLength + ) + { + _cfg = cfg; + _blockTree = blockTree; + _readOnlyBlockTree = blockTree.AsReadOnly(); + _abiEncoder = abiEncoder; + _logManager = logManager; + _slotLength = slotLength; + _blockUpToDateCutoff = slotLength; + _blockWaitCutoff = _slotLength / 3; + + _txProcessingEnvFactory = new(worldStateManager, blockTree, specProvider, logManager); + + Time = InitTime(specProvider, timestamper); + TxLoader = new(logFinder, _cfg, Time, specProvider, ecdsa, abiEncoder, logManager); + Eon = InitEon(); + BlockHandler = new ShutterBlockHandler( + specProvider.ChainId, + _cfg, + _txProcessingEnvFactory, + blockTree, + abiEncoder, + receiptFinder, + validatorsInfo, + Eon, + TxLoader, + Time, + logManager, + _slotLength, + BlockWaitCutoff); + + TxSource = new ShutterTxSource(TxLoader, _cfg, Time, logManager); + + KeyValidator = new ShutterKeyValidator(_cfg, Eon, logManager); + + InitP2P(_cfg, logManager); + } + + public Task StartP2P(CancellationTokenSource? cancellationTokenSource = null) + => P2P!.Start(OnKeysReceived, cancellationTokenSource); + + public ShutterBlockImprovementContextFactory GetBlockImprovementContextFactory(IBlockProducer blockProducer) + { + BlockImprovementContextFactory ??= new( + blockProducer, + TxSource, + _cfg, + Time, + _logManager, + _slotLength + ); + return BlockImprovementContextFactory; + } + + public async ValueTask DisposeAsync() + { + TxSource.Dispose(); + BlockHandler.Dispose(); + await (P2P?.DisposeAsync() ?? default); + } + + protected virtual async Task OnKeysReceived(Dto.DecryptionKeys decryptionKeys) + { + IShutterKeyValidator.ValidatedKeys? keys = KeyValidator.ValidateKeys(decryptionKeys); + + if (keys is null) + { + return; + } + + Metrics.ShutterTxPointer = keys.Value.TxPointer; + + // wait for latest block before loading transactions + Block? head = (await BlockHandler.WaitForBlockInSlot(keys.Value.Slot - 1, new())) ?? _readOnlyBlockTree.Head; + BlockHeader? header = head?.Header; + BlockHeader parentHeader = header is not null + ? _readOnlyBlockTree.FindParentHeader(header, BlockTreeLookupOptions.None)! + : _readOnlyBlockTree.FindLatestHeader()!; + TxSource.LoadTransactions(head, parentHeader, keys.Value); + } + + protected virtual void InitP2P(IShutterConfig cfg, ILogManager logManager) + { + P2P = new ShutterP2P(cfg, logManager); + } + + protected virtual IShutterEon InitEon() + => new ShutterEon(_readOnlyBlockTree, _txProcessingEnvFactory, _abiEncoder, _cfg, _logManager); + + protected virtual ShutterTime InitTime(ISpecProvider specProvider, ITimestamper timestamper) + { + return new(specProvider.BeaconChainGenesisTimestamp!.Value * 1000, timestamper, _slotLength, _blockUpToDateCutoff); + } +} diff --git a/src/Nethermind/Nethermind.Shutter/ShutterBlockHandler.cs b/src/Nethermind/Nethermind.Shutter/ShutterBlockHandler.cs new file mode 100644 index 00000000000..3e1be55af16 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterBlockHandler.cs @@ -0,0 +1,228 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Shutter.Contracts; +using Nethermind.Logging; +using Nethermind.Abi; +using Nethermind.Blockchain.Receipts; +using System.Threading.Tasks; +using System.Threading; +using Nethermind.Core.Caching; +using Nethermind.Core.Crypto; +using Nethermind.Blockchain; +using Nethermind.Core.Collections; +using Nethermind.Shutter.Config; + +namespace Nethermind.Shutter; + +public class ShutterBlockHandler : IShutterBlockHandler +{ + private readonly ILogger _logger; + private readonly ShutterTime _time; + private readonly IShutterEon _eon; + private readonly IReceiptFinder _receiptFinder; + private readonly ShutterTxLoader _txLoader; + private readonly Dictionary _validatorsInfo; + private readonly ILogManager _logManager; + private readonly IAbiEncoder _abiEncoder; + private readonly IBlockTree _blockTree; + private readonly ReadOnlyBlockTree _readOnlyBlockTree; + private readonly ulong _chainId; + private readonly IShutterConfig _cfg; + private readonly TimeSpan _slotLength; + private readonly TimeSpan _blockWaitCutoff; + private readonly ReadOnlyTxProcessingEnvFactory _envFactory; + private bool _haveCheckedRegistered = false; + private ulong _blockWaitTaskId = 0; + private readonly Dictionary> _blockWaitTasks = []; + private readonly LruCache _slotToBlockHash = new(5, "Slot to block hash mapping"); + private readonly object _syncObject = new(); + + public ShutterBlockHandler( + ulong chainId, + IShutterConfig cfg, + ReadOnlyTxProcessingEnvFactory envFactory, + IBlockTree blockTree, + IAbiEncoder abiEncoder, + IReceiptFinder receiptFinder, + Dictionary validatorsInfo, + IShutterEon eon, + ShutterTxLoader txLoader, + ShutterTime time, + ILogManager logManager, + TimeSpan slotLength, + TimeSpan blockWaitCutoff) + { + _chainId = chainId; + _cfg = cfg; + _logger = logManager.GetClassLogger(); + _time = time; + _validatorsInfo = validatorsInfo; + _eon = eon; + _receiptFinder = receiptFinder; + _txLoader = txLoader; + _blockTree = blockTree; + _readOnlyBlockTree = blockTree.AsReadOnly(); + _abiEncoder = abiEncoder; + _logManager = logManager; + _envFactory = envFactory; + _slotLength = slotLength; + _blockWaitCutoff = blockWaitCutoff; + + _blockTree.NewHeadBlock += OnNewHeadBlock; + } + + public async Task WaitForBlockInSlot(ulong slot, CancellationToken cancellationToken, Func? initTimeoutSource = null) + { + TaskCompletionSource? tcs = null; + lock (_syncObject) + { + if (_slotToBlockHash.TryGet(slot, out Hash256? blockHash)) + { + return _readOnlyBlockTree.FindBlock(blockHash!, BlockTreeLookupOptions.None); + } + + if (_logger.IsDebug) _logger.Debug($"Waiting for block in {slot} to get Shutter transactions."); + + tcs = new(); + + long offset = _time.GetCurrentOffsetMs(slot); + long waitTime = (long)_blockWaitCutoff.TotalMilliseconds - offset; + if (waitTime <= 0) + { + if (_logger.IsDebug) _logger.Debug($"Shutter no longer waiting for block in slot {slot}, offset of {offset}ms is after cutoff of {(int)_blockWaitCutoff.TotalMilliseconds}ms."); + return null; + } + waitTime = Math.Min(waitTime, 2 * (long)_slotLength.TotalMilliseconds); + + ulong taskId = _blockWaitTaskId++; + CancellationTokenSource timeoutSource = initTimeoutSource is null ? new CancellationTokenSource((int)waitTime) : initTimeoutSource((int)waitTime); + CancellationTokenRegistration ctr = cancellationToken.Register(() => CancelWaitForBlock(slot, taskId, false)); + CancellationTokenRegistration timeoutCtr = timeoutSource.Token.Register(() => CancelWaitForBlock(slot, taskId, true)); + + if (!_blockWaitTasks.ContainsKey(slot)) + { + _blockWaitTasks.Add(slot, []); + } + + Dictionary slotWaitTasks = _blockWaitTasks.GetValueOrDefault(slot)!; + slotWaitTasks.Add(taskId, new BlockWaitTask() + { + Tcs = tcs, + TimeoutSource = timeoutSource, + CancellationRegistration = ctr, + TimeoutCancellationRegistration = timeoutCtr + }); + } + return await tcs.Task; + } + + public void Dispose() + { + _blockTree.NewHeadBlock -= OnNewHeadBlock; + _blockWaitTasks.ForEach(x => x.Value.ForEach(waitTask => + { + waitTask.Value.CancellationRegistration.Dispose(); + waitTask.Value.TimeoutCancellationRegistration.Dispose(); + })); + } + + private void CancelWaitForBlock(ulong slot, ulong taskId, bool timeout) + { + if (_blockWaitTasks.TryGetValue(slot, out Dictionary? slotWaitTasks)) + { + if (slotWaitTasks.TryGetValue(taskId, out BlockWaitTask waitTask)) + { + if (timeout) + { + waitTask.Tcs.TrySetResult(null); + } + else + { + waitTask.Tcs.SetException(new OperationCanceledException()); + } + waitTask.CancellationRegistration.Dispose(); + waitTask.TimeoutCancellationRegistration.Dispose(); + } + slotWaitTasks.Remove(taskId); + } + } + + private void OnNewHeadBlock(object? _, BlockEventArgs e) + { + Block head = e.Block; + if (_time.IsBlockUpToDate(head)) + { + if (_logger.IsDebug) _logger.Debug($"Shutter block handler {head.Number}"); + + if (!_haveCheckedRegistered) + { + CheckAllValidatorsRegistered(head.Header, _validatorsInfo); + _haveCheckedRegistered = true; + } + _eon.Update(head.Header); + _txLoader.LoadFromReceipts(head, _receiptFinder.Get(head), _eon.GetCurrentEonInfo()!.Value.Eon); + + lock (_syncObject) + { + ulong slot = _time.GetSlot(head.Timestamp * 1000); + _slotToBlockHash.Set(slot, head.Hash); + + if (_blockWaitTasks.Remove(slot, out Dictionary? waitTasks)) + { + waitTasks.ForEach(waitTask => + { + waitTask.Value.Tcs.TrySetResult(head); + waitTask.Value.Dispose(); + }); + } + } + } + else if (_logger.IsDebug) + { + _logger.Warn($"Shutter block handler not running, outdated block {head.Number}"); + } + } + + + private void CheckAllValidatorsRegistered(BlockHeader parent, Dictionary validatorsInfo) + { + if (validatorsInfo.Count == 0) + { + return; + } + + IReadOnlyTxProcessingScope scope = _envFactory.Create().Build(parent.StateRoot!); + ITransactionProcessor processor = scope.TransactionProcessor; + + ValidatorRegistryContract validatorRegistryContract = new(processor, _abiEncoder, new(_cfg.ValidatorRegistryContractAddress!), _logManager, _chainId, _cfg.ValidatorRegistryMessageVersion!); + if (validatorRegistryContract.IsRegistered(parent, validatorsInfo, out HashSet unregistered)) + { + if (_logger.IsInfo) _logger.Info($"All Shutter validator keys are registered."); + } + else if (_logger.IsError) + { + _logger.Error($"Validators not registered to Shutter with the following indices: [{string.Join(", ", unregistered)}]"); + } + } + + private readonly struct BlockWaitTask : IDisposable + { + public TaskCompletionSource Tcs { get; init; } + public CancellationTokenSource TimeoutSource { get; init; } + public CancellationTokenRegistration CancellationRegistration { get; init; } + public CancellationTokenRegistration TimeoutCancellationRegistration { get; init; } + + public void Dispose() + { + TimeoutSource.Dispose(); + CancellationRegistration.Dispose(); + TimeoutCancellationRegistration.Dispose(); + } + } +} diff --git a/src/Nethermind/Nethermind.Shutter/ShutterBlockImprovementContext.cs b/src/Nethermind/Nethermind.Shutter/ShutterBlockImprovementContext.cs new file mode 100644 index 00000000000..3868bea2497 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterBlockImprovementContext.cs @@ -0,0 +1,176 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Consensus; +using Nethermind.Shutter.Config; +using Nethermind.Consensus.Producers; +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Int256; +using Nethermind.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Nethermind.Shutter; + +public class ShutterBlockImprovementContextFactory( + IBlockProducer blockProducer, + ShutterTxSource shutterTxSource, + IShutterConfig shutterConfig, + ShutterTime time, + ILogManager logManager, + TimeSpan slotLength) : IBlockImprovementContextFactory +{ + public IBlockImprovementContext StartBlockImprovementContext( + Block currentBestBlock, + BlockHeader parentHeader, + PayloadAttributes payloadAttributes, + DateTimeOffset startDateTime) => + new ShutterBlockImprovementContext(blockProducer, + shutterTxSource, + shutterConfig, + time, + currentBestBlock, + parentHeader, + payloadAttributes, + startDateTime, + slotLength, + logManager); +} + +public class ShutterBlockImprovementContext : IBlockImprovementContext +{ + public Task ImprovementTask { get; } + + public Block? CurrentBestBlock { get; private set; } + + public bool Disposed { get; private set; } + public DateTimeOffset StartDateTime { get; } + + public UInt256 BlockFees => 0; + + private CancellationTokenSource? _cancellationTokenSource; + private readonly ILogger _logger; + private readonly IBlockProducer _blockProducer; + private readonly IShutterTxSignal _txSignal; + private readonly IShutterConfig _shutterConfig; + private readonly ShutterTime _time; + private readonly BlockHeader _parentHeader; + private readonly PayloadAttributes _payloadAttributes; + private readonly ulong _slotTimestampMs; + private readonly TimeSpan _slotLength; + + internal ShutterBlockImprovementContext( + IBlockProducer blockProducer, + IShutterTxSignal shutterTxSignal, + IShutterConfig shutterConfig, + ShutterTime time, + Block currentBestBlock, + BlockHeader parentHeader, + PayloadAttributes payloadAttributes, + DateTimeOffset startDateTime, + TimeSpan slotLength, + ILogManager logManager) + { + if (slotLength == TimeSpan.Zero) + { + throw new ArgumentException("Cannot be zero.", nameof(slotLength)); + } + + _slotTimestampMs = payloadAttributes.Timestamp * 1000; + + _cancellationTokenSource = new CancellationTokenSource(); + CurrentBestBlock = currentBestBlock; + StartDateTime = startDateTime; + _logger = logManager.GetClassLogger(); + _blockProducer = blockProducer; + _txSignal = shutterTxSignal; + _shutterConfig = shutterConfig; + _time = time; + _parentHeader = parentHeader; + _payloadAttributes = payloadAttributes; + _slotLength = slotLength; + + ImprovementTask = Task.Run(ImproveBlock); + } + + public void Dispose() + { + Disposed = true; + CancellationTokenExtensions.CancelDisposeAndClear(ref _cancellationTokenSource); + } + + private async Task ImproveBlock() + { + if (_logger.IsDebug) _logger.Debug("Running Shutter block improvement."); + + ulong slot; + long offset; + try + { + (slot, offset) = _time.GetBuildingSlotAndOffset(_slotTimestampMs); + } + catch (ShutterTime.ShutterSlotCalulationException e) + { + if (_logger.IsWarn) _logger.Warn($"Could not calculate Shutter building slot: {e}"); + await BuildBlock(); + return CurrentBestBlock; + } + + bool includedShutterTxs = await TryBuildShutterBlock(slot); + if (includedShutterTxs) + { + return CurrentBestBlock; + } + + long waitTime = _shutterConfig.MaxKeyDelay - offset; + if (waitTime <= 0) + { + if (_logger.IsWarn) _logger.Warn($"Cannot await Shutter decryption keys for slot {slot}, offset of {offset}ms is too late."); + return CurrentBestBlock; + } + waitTime = Math.Min(waitTime, 2 * (long)_slotLength.TotalMilliseconds); + + if (_logger.IsDebug) _logger.Debug($"Awaiting Shutter decryption keys for {slot} at offset {offset}ms. Timeout in {waitTime}ms..."); + + ObjectDisposedException.ThrowIf(_cancellationTokenSource is null, this); + + using var txTimeout = new CancellationTokenSource((int)waitTime); + using var source = CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource!.Token, txTimeout.Token); + + try + { + await _txSignal.WaitForTransactions(slot, source.Token); + } + catch (OperationCanceledException) + { + Metrics.ShutterKeysMissed++; + if (_logger.IsWarn) _logger.Warn($"Shutter decryption keys not received in time for slot {slot}."); + + return CurrentBestBlock; + } + + // should succeed after waiting for transactions + await TryBuildShutterBlock(slot); + + return CurrentBestBlock; + } + + private async Task TryBuildShutterBlock(ulong slot) + { + bool hasShutterTxs = _txSignal.HaveTransactionsArrived(slot); + await BuildBlock(); + return hasShutterTxs; + } + + private async Task BuildBlock() + { + Block? result = await _blockProducer.BuildBlock(_parentHeader, null, _payloadAttributes, _cancellationTokenSource!.Token); + if (result is not null) + { + CurrentBestBlock = result; + } + } + +} diff --git a/src/Nethermind/Nethermind.Shutter/ShutterCrypto.cs b/src/Nethermind/Nethermind.Shutter/ShutterCrypto.cs new file mode 100644 index 00000000000..540f147fe94 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterCrypto.cs @@ -0,0 +1,376 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Int256; +using Nethermind.Crypto; +using Nethermind.Merkleization; +using System.Text; +using Nethermind.Core.Collections; +using System.Runtime.CompilerServices; + +namespace Nethermind.Shutter; + +using G1 = Bls.P1; +using G1Affine = Bls.P1Affine; +using G2 = Bls.P2; +using G2Affine = Bls.P2Affine; +using GT = Bls.PT; + +public static class ShutterCrypto +{ + private static readonly byte[] DST = Encoding.UTF8.GetBytes("SHUTTER_V01_BLS12381G1_XMD:SHA-256_SSWU_RO_"); + private const byte CryptoVersion = 0x3; + private static readonly UInt256 BlsSubgroupOrder = new((byte[])[0x73, 0xed, 0xa7, 0x53, 0x29, 0x9d, 0x7d, 0x48, 0x33, 0x39, 0xd8, 0x08, 0x09, 0xa1, 0xd8, 0x05, 0x53, 0xbd, 0xa4, 0x02, 0xff, 0xfe, 0x5b, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01], true); + + public readonly ref struct EncryptedMessage + { + public readonly byte VersionId { get; init; } + public readonly G2 C1 { get; init; } + public readonly ReadOnlySpan C2 { get; init; } + public readonly ReadOnlySpan C3 { get; init; } + } + + public class ShutterCryptoException(string message, Exception? innerException = null) : Exception(message, innerException); + + [SkipLocalsInit] + public static void ComputeIdentity(G1 p, scoped ReadOnlySpan identityPrefix, in Address sender) + { + Span preimage = stackalloc byte[52]; + identityPrefix.CopyTo(preimage); + sender.Bytes.CopyTo(preimage[32..]); + ComputeIdentity(p, preimage); + } + + public static void Decrypt(ref Span res, EncryptedMessage encryptedMessage, scoped G1 key) + { + RecoverSigma(out Span sigma, encryptedMessage, key.ToAffine()); + ComputeBlockKeys(res, sigma, encryptedMessage.C3.Length / 32); + + res.Xor(encryptedMessage.C3); + + UnpadAndJoin(ref res); + + ComputeR(sigma, res, out UInt256 r); + + G2 expectedC1 = ComputeC1(r); + if (!expectedC1.IsEqual(encryptedMessage.C1)) + { + throw new ShutterCryptoException("Could not decrypt message."); + } + } + + public static int GetDecryptedDataLength(EncryptedMessage encryptedMessage) + => encryptedMessage.C3.Length; + + public static EncryptedMessage DecodeEncryptedMessage(ReadOnlySpan bytes) + { + if (bytes[0] != CryptoVersion) + { + throw new ShutterCryptoException($"Encrypted message had wrong Shutter crypto version. Expected version {CryptoVersion}, found {bytes[0]}."); + } + + ReadOnlySpan c3 = bytes[(1 + 96 + 32)..]; + + if (c3.Length % 32 != 0) + { + throw new ShutterCryptoException("Encrypted Shutter message had invalid c3"); + } + + try + { + G2 c1 = new(bytes[1..(1 + 96)]); + return new() + { + VersionId = bytes[0], + C1 = c1, + C2 = bytes[(1 + 96)..(1 + 96 + 32)], + C3 = c3 + }; + } + catch (Bls.BlsException e) + { + throw new ShutterCryptoException("Encrypted Shutter message had invalid c1", e); + } + } + + public static void RecoverSigma(out Span sigma, EncryptedMessage encryptedMessage, G1Affine decryptionKey) + { + using ArrayPoolList buf = new(GT.Sz, GT.Sz); + GT p = new(buf.AsSpan()); + p.MillerLoop(encryptedMessage.C1.ToAffine(), decryptionKey); + sigma = Hash2(p); // key + sigma.Xor(encryptedMessage.C2); + } + + [SkipLocalsInit] + public static void ComputeBlockKeys(Span blockKeys, ReadOnlySpan sigma, int n) + { + if (blockKeys.Length != 32 * n) + { + throw new ArgumentException("Block keys buffer was the wrong size."); + } + + Span preimageBuf = stackalloc byte[36]; + Span suffix = stackalloc byte[4]; + + for (int i = 0; i < n; i++) + { + BinaryPrimitives.WriteUInt32BigEndian(suffix, (uint)i); + int suffixLength = 4; + for (int j = 0; j < 3; j++) + { + if (suffix[j] != 0) + { + break; + } + suffixLength--; + } + Span preimage = preimageBuf[..(32 + suffixLength)]; + sigma.CopyTo(preimage); + suffix[(4 - suffixLength)..4].CopyTo(preimage[32..]); + + Hash4(preimage).Bytes.CopyTo(blockKeys[(i * 32)..]); + } + } + + // Hash1 in spec + public static void ComputeIdentity(G1 p, scoped ReadOnlySpan bytes) + { + int len = bytes.Length + 1; + using ArrayPoolList buf = new(len, len); + Span preimage = buf.AsSpan(); + preimage[0] = 0x1; + bytes.CopyTo(preimage[1..]); + p.HashTo(preimage, DST); + } + + public static Span Hash2(GT p) + { + using ArrayPoolList buf = new(577, 577); + Span preimage = buf.AsSpan(); + preimage[0] = 0x2; + p.FinalExp().ToBendian().CopyTo(preimage[1..]); + return HashBytesToBlock(preimage); + } + + public static void GTExp(ref GT x, UInt256 exp) + { + if (exp == 0) + { + return; + } + exp -= 1; + + using ArrayPoolList buf = new(GT.Sz, GT.Sz); + x.Fp12.CopyTo(buf.AsSpan()); + GT a = new(buf.AsSpan()); + for (; exp > 0; exp >>= 1) + { + if ((exp & 1) == 1) + { + x.Mul(a); + } + a.Sqr(); + } + } + + [SkipLocalsInit] + public static bool CheckDecryptionKey(G1Affine decryptionKey, G2Affine eonPublicKey, G1Affine identity) + { + int len = GT.Sz * 2; + using ArrayPoolList buf = new(len, len); + GT p1 = new(buf.AsSpan()[..GT.Sz]); + p1.MillerLoop(G2Affine.Generator(stackalloc long[G2Affine.Sz]), decryptionKey); + GT p2 = new(buf.AsSpan()[GT.Sz..]); + p2.MillerLoop(eonPublicKey, identity); + return GT.FinalVerify(p1, p2); + } + + public static bool CheckSlotDecryptionIdentitiesSignature( + ulong instanceId, + ulong eon, + ulong slot, + ulong txPointer, + IEnumerable> identityPreimages, + ReadOnlySpan signatureBytes, + Address keyperAddress) + { + ValueHash256 hash = GenerateHash(instanceId, eon, slot, txPointer, identityPreimages); + + // check recovery_id + if (signatureBytes[64] > 3) + { + return false; + } + + Signature signature = new(signatureBytes[..64], signatureBytes[64]); + + PublicKey? expectedPubkey = _ecdsa.RecoverPublicKey(signature, hash); + + return expectedPubkey is not null && keyperAddress == expectedPubkey.Address; + } + + [SkipLocalsInit] + public static bool CheckValidatorRegistrySignature(ReadOnlySpan pkBytes, ReadOnlySpan sigBytes, ReadOnlySpan msgBytes) + { + BlsSigner.Signature sig = new(sigBytes); + ValueHash256 h = ValueKeccak.Compute(msgBytes); + + G1Affine pk = new(stackalloc long[G1Affine.Sz]); + pk.Decode(pkBytes); + return BlsSigner.Verify(pk, sig, h.Bytes); + } + + public static EncryptedMessage Encrypt(ReadOnlySpan msg, G1 identity, G2 eonKey, ReadOnlySpan sigma) + { + int len = PaddedLength(msg); + using ArrayPoolList buf = new(len, len); + ComputeR(sigma, msg, out UInt256 r); + ReadOnlySpan msgBlocks = PadAndSplit(buf.AsSpan(), msg); + Span c3 = new byte[msgBlocks.Length]; + ComputeC3(c3, msgBlocks, sigma); + + return new() + { + VersionId = CryptoVersion, + C1 = ComputeC1(r), + C2 = ComputeC2(sigma, r, identity, eonKey), + C3 = c3 + }; + } + + public static Span EncodeEncryptedMessage(EncryptedMessage encryptedMessage) + { + Span encoded = new byte[1 + 96 + 32 + encryptedMessage.C3.Length]; + + encoded[0] = encryptedMessage.VersionId; + encryptedMessage.C1.Compress().CopyTo(encoded[1..]); + encryptedMessage.C2.CopyTo(encoded[(1 + 96)..]); + encryptedMessage.C3.CopyTo(encoded[(1 + 96 + 32)..]); + + return encoded; + } + + private static readonly Ecdsa _ecdsa = new(); + + private static G2 ComputeC1(UInt256 r) + => G2.Generator().Mult(r.ToLittleEndian()); + + private static Span HashBytesToBlock(scoped ReadOnlySpan bytes) + => Keccak.Compute(bytes).Bytes; + + private static void UnpadAndJoin(ref Span blocks) + { + if (blocks.Length == 0) + { + return; + } + + byte n = blocks[^1]; + + if (n == 0 || n > 32) + { + throw new ShutterCryptoException("Invalid padding length"); + } + + blocks = blocks[..(blocks.Length - n)]; + } + + private static Span ComputeC2(scoped ReadOnlySpan sigma, UInt256 r, G1 identity, G2 eonKey) + { + using ArrayPoolList buf = new(GT.Sz, GT.Sz); + GT p = new(buf.AsSpan()); + p.MillerLoop(eonKey, identity); + GTExp(ref p, r); + Span c2 = Hash2(p); //key + c2.Xor(sigma); + return c2; + } + + private static void ComputeC3(Span c3, scoped ReadOnlySpan msgBlocks, ReadOnlySpan sigma) + { + // c3 = keys ^ msgs + ComputeBlockKeys(c3, sigma, msgBlocks.Length / 32); + c3.Xor(msgBlocks); + } + + private static int PaddedLength(ReadOnlySpan bytes) + => bytes.Length + (32 - (bytes.Length % 32)); + + private static Span PadAndSplit(Span paddedBytes, scoped ReadOnlySpan bytes) + { + int n = 32 - (bytes.Length % 32); + bytes.CopyTo(paddedBytes); + paddedBytes[bytes.Length..].Fill((byte)n); + return paddedBytes; + } + + private static void ComputeR(scoped ReadOnlySpan sigma, scoped ReadOnlySpan msg, out UInt256 r) + { + int len = 32 + msg.Length; + using ArrayPoolList buf = new(len, len); + Span preimage = buf.AsSpan(); + sigma.CopyTo(preimage); + msg.CopyTo(preimage[32..]); + Hash3(preimage, out r); + } + + internal static ValueHash256 GenerateHash(ulong instanceId, ulong eon, ulong slot, ulong txPointer, IEnumerable> identityPreimages) + { + SlotDecryptionIdentites container = new() + { + InstanceID = instanceId, + Eon = eon, + Slot = slot, + TxPointer = txPointer, + IdentityPreimages = identityPreimages + }; + + Merkleizer merkleizer = new(Merkle.NextPowerOfTwoExponent(5)); + merkleizer.Feed(container.InstanceID); + merkleizer.Feed(container.Eon); + merkleizer.Feed(container.Slot); + merkleizer.Feed(container.TxPointer); + merkleizer.Feed(container.IdentityPreimages, 1024); + merkleizer.CalculateRoot(out UInt256 root); + + return new(root.ToLittleEndian()); + } + + private static void Hash3(ReadOnlySpan bytes, out UInt256 res) + { + int len = bytes.Length + 1; + using ArrayPoolList buf = new(len, len); + Span preimage = buf.AsSpan(); + preimage[0] = 0x3; + bytes.CopyTo(preimage[1..]); + ReadOnlySpan hash = ValueKeccak.Compute(preimage).Bytes; + UInt256.Mod(new UInt256(hash, true), BlsSubgroupOrder, out res); + } + + private static ValueHash256 Hash4(ReadOnlySpan bytes) + { + int len = bytes.Length + 1; + using ArrayPoolList buf = new(len, len); + Span preimage = buf.AsSpan(); + preimage[0] = 0x4; + bytes.CopyTo(preimage[1..]); + return ValueKeccak.Compute(preimage); + } + + private readonly struct SlotDecryptionIdentites + { + public readonly ulong InstanceID { get; init; } + public readonly ulong Eon { get; init; } + public readonly ulong Slot { get; init; } + public readonly ulong TxPointer { get; init; } + public readonly IEnumerable> IdentityPreimages { get; init; } + } + +} diff --git a/src/Nethermind/Nethermind.Shutter/ShutterEon.cs b/src/Nethermind/Nethermind.Shutter/ShutterEon.cs new file mode 100644 index 00000000000..b330d36be26 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterEon.cs @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Crypto; +using Nethermind.Core; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Abi; +using Nethermind.Shutter.Contracts; +using Nethermind.Logging; +using Nethermind.Shutter.Config; +using Nethermind.Consensus.Processing; +using Nethermind.Core.Crypto; + +namespace Nethermind.Shutter; + +public class ShutterEon( + IReadOnlyBlockTree blockTree, + ReadOnlyTxProcessingEnvFactory envFactory, + IAbiEncoder abiEncoder, + IShutterConfig shutterConfig, + ILogManager logManager) : IShutterEon +{ + private IShutterEon.Info? _currentInfo; + private readonly Address _keyBroadcastContractAddress = new(shutterConfig.KeyBroadcastContractAddress!); + private readonly Address _keyperSetManagerContractAddress = new(shutterConfig.KeyperSetManagerContractAddress!); + private readonly ILogger _logger = logManager.GetClassLogger(); + + public IShutterEon.Info? GetCurrentEonInfo() => _currentInfo; + + public void Update(BlockHeader header) + { + Hash256 stateRoot = blockTree.Head!.StateRoot!; + IReadOnlyTxProcessingScope scope = envFactory.Create().Build(stateRoot); + ITransactionProcessor processor = scope.TransactionProcessor; + + try + { + KeyperSetManagerContract keyperSetManagerContract = new(processor, abiEncoder, _keyperSetManagerContractAddress); + ulong eon = keyperSetManagerContract.GetKeyperSetIndexByBlock(header, (ulong)header.Number + 1); + + if (_currentInfo is null || _currentInfo.Value.Eon != eon) + { + Address keyperSetContractAddress = keyperSetManagerContract.GetKeyperSetAddress(header, eon); + KeyperSetContract keyperSetContract = new(processor, abiEncoder, keyperSetContractAddress); + + if (keyperSetContract.IsFinalized(header)) + { + ulong threshold = keyperSetContract.GetThreshold(header); + Address[] addresses = keyperSetContract.GetMembers(header); + + KeyBroadcastContract keyBroadcastContract = new(processor, abiEncoder, _keyBroadcastContractAddress); + byte[] eonKey = keyBroadcastContract.GetEonKey(blockTree.Head!.Header, eon); + + // update atomically + _currentInfo = new() + { + Eon = eon, + Key = eonKey, + Threshold = threshold, + Addresses = addresses + }; + + Metrics.ShutterEon = eon; + Metrics.ShutterThreshold = (int)threshold; + Metrics.ShutterKeypers = addresses.Length; + + if (_logger.IsInfo) _logger.Info($"Shutter eon={_currentInfo.Value.Eon} threshold={_currentInfo.Value.Threshold} #keypers={_currentInfo.Value.Addresses.Length}"); + } + else if (_logger.IsError) + { + _logger.Error("Cannot use unfinalised Shutter keyper set contract."); + } + } + } + catch (AbiException e) + { + if (_logger.IsError) _logger.Error($"Error when calling Shutter Keyper contracts.", e); + } + catch (Bls.BlsException e) + { + if (_logger.IsError) _logger.Error($"Invalid Shutter Eon key ", e); + } + } +} diff --git a/src/Nethermind/Nethermind.Shutter/ShutterEventQueue.cs b/src/Nethermind/Nethermind.Shutter/ShutterEventQueue.cs new file mode 100644 index 00000000000..7392b33e124 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterEventQueue.cs @@ -0,0 +1,128 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Core.Collections; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Shutter.Contracts; + +namespace Nethermind.Shutter; + +public class ShutterEventQueue(int encryptedGasLimit, ILogManager logManager) +{ + private const int MaxQueueSize = 10000; + private ulong? _eon; + private ulong? _txIndex; + private ulong _nextEonTxIndex = 0; + private Queue _events = []; + private Queue _nextEonEvents = []; + private readonly ILogger _logger = logManager.GetClassLogger(); + + public int Count { get => _events.Count; } + + public void EnqueueEvents(IEnumerable events, ulong eon) + => events.ForEach(e => EnqueueEvent(e, eon)); + + public void EnqueueEvent(ISequencerContract.TransactionSubmitted e, ulong eon) + { + if (_eon is null || eon > _eon) + { + SetEon(eon); + } + + if (e.Eon == _eon) + { + if (_logger.IsDebug && _txIndex is not null && e.TxIndex != _txIndex) + { + _logger.Warn($"Loading unexpected Shutter event with index {e.TxIndex} in eon {_eon}, expected {_txIndex}."); + } + + _txIndex = e.TxIndex + 1; + + if (_events.Count < MaxQueueSize) + { + _events.Enqueue(e); + } + else + { + if (_logger.IsError) _logger.Error($"Shutter queue for eon {_eon} is full, cannot load events."); + return; + } + } + else if (e.Eon == _eon + 1) + { + if (_logger.IsDebug && e.TxIndex != _nextEonTxIndex) + { + _logger.Warn($"Loading unexpected Shutter event with index {e.TxIndex} in eon {_eon + 1}, expected {_nextEonTxIndex}."); + } + + _nextEonTxIndex = e.TxIndex + 1; + + if (_nextEonEvents.Count < MaxQueueSize) + { + _nextEonEvents.Enqueue(e); + } + else + { + if (_logger.IsError) _logger.Error($"Shutter queue for eon {_eon + 1} is full, cannot load events."); + return; + } + } + else if (_logger.IsDebug) + { + _logger.Warn($"Ignoring Shutter event with future eon {e.Eon}."); + } + } + + public IEnumerable DequeueToGasLimit(ulong eon, ulong txPointer) + { + UInt256 totalGas = 0; + + if (_eon is null || eon > _eon) + { + SetEon(eon); + } + + if (eon != _eon) + { + if (_logger.IsError) _logger.Error($"Cannot load Shutter transactions for eon {eon}, expected {_eon}."); + yield break; + } + + while (_events.TryPeek(out ISequencerContract.TransactionSubmitted e)) + { + if (e.TxIndex < txPointer) + { + // skip and delete outdated events + _events.Dequeue(); + continue; + } + + if (totalGas + e.GasLimit > encryptedGasLimit) + { + if (_logger.IsDebug) _logger.Debug("Shutter gas limit reached."); + yield break; + } + + _events.Dequeue(); + totalGas += e.GasLimit; + yield return e; + } + + Metrics.ShutterEncryptedGasUsed = (ulong)totalGas; + } + + private void SetEon(ulong eon) + { + if (eon == _eon + 1) + { + _txIndex = _nextEonTxIndex; + _events = _nextEonEvents; + _nextEonTxIndex = 0; + _nextEonEvents = []; + } + + _eon = eon; + } +} diff --git a/src/Nethermind/Nethermind.Shutter/ShutterKeyValidator.cs b/src/Nethermind/Nethermind.Shutter/ShutterKeyValidator.cs new file mode 100644 index 00000000000..f76ca05a323 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterKeyValidator.cs @@ -0,0 +1,155 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; +using Nethermind.Core; +using Nethermind.Crypto; +using Nethermind.Shutter.Config; +using Nethermind.Logging; +using Google.Protobuf; +using Nethermind.Core.Collections; +using System.Runtime.CompilerServices; + +namespace Nethermind.Shutter; + +using G1 = Bls.P1; +using G1Affine = Bls.P1Affine; +using G2Affine = Bls.P2Affine; + +public class ShutterKeyValidator( + IShutterConfig shutterConfig, + IShutterEon eon, + ILogManager logManager) : IShutterKeyValidator +{ + private ulong? _highestValidatedSlot; + private readonly ILogger _logger = logManager.GetClassLogger(); + private readonly ulong _instanceId = shutterConfig.InstanceID; + private readonly object _lockObject = new(); + + public IShutterKeyValidator.ValidatedKeys? ValidateKeys(Dto.DecryptionKeys decryptionKeys) + { + lock (_lockObject) + { + if (_highestValidatedSlot is not null && decryptionKeys.Gnosis.Slot <= _highestValidatedSlot) + { + if (_logger.IsDebug) _logger.Debug($"Skipping Shutter decryption keys from slot {decryptionKeys.Gnosis.Slot}, keys currently stored for slot {_highestValidatedSlot}."); + return null; + } + + IShutterEon.Info? eonInfo = eon.GetCurrentEonInfo(); + if (eonInfo is null) + { + if (_logger.IsDebug) _logger.Debug("Cannot check Shutter decryption keys, eon info was not found."); + return null; + } + + if (_logger.IsDebug) _logger.Debug($"Checking Shutter decryption keys instanceID: {decryptionKeys.InstanceID} eon: {decryptionKeys.Eon} #keys: {decryptionKeys.Keys.Count} #sig: {decryptionKeys.Gnosis.Signatures.Count()} #txpointer: {decryptionKeys.Gnosis.TxPointer} #slot: {decryptionKeys.Gnosis.Slot}"); + + if (CheckDecryptionKeys(decryptionKeys, eonInfo.Value)) + { + if (_logger.IsInfo) _logger.Info($"Validated Shutter decryption keys for slot {decryptionKeys.Gnosis.Slot}."); + _highestValidatedSlot = decryptionKeys.Gnosis.Slot; + return new() + { + Eon = decryptionKeys.Eon, + Slot = decryptionKeys.Gnosis.Slot, + TxPointer = decryptionKeys.Gnosis.TxPointer, + Keys = ExtractKeys(decryptionKeys) + }; + } + else + { + return null; + } + } + } + + [SkipLocalsInit] + private bool CheckDecryptionKeys(in Dto.DecryptionKeys decryptionKeys, in IShutterEon.Info eonInfo) + { + if (decryptionKeys.InstanceID != _instanceId) + { + if (_logger.IsDebug) _logger.Debug($"Invalid Shutter decryption keys received: instanceID {decryptionKeys.InstanceID} did not match expected value {_instanceId}."); + return false; + } + + if (decryptionKeys.Eon != eonInfo.Eon) + { + if (_logger.IsDebug) _logger.Debug($"Invalid Shutter decryption keys received: eon {decryptionKeys.Eon} did not match expected value {eonInfo.Eon}."); + return false; + } + + if (decryptionKeys.Keys.Count == 0) + { + if (_logger.IsDebug) _logger.Error("Invalid Shutter decryption keys received: expected placeholder key."); + return false; + } + + G1Affine dk = new(stackalloc long[G1Affine.Sz]); + G1 identity = new(stackalloc long[G1.Sz]); + G2Affine eonKey = new(stackalloc long[G2Affine.Sz]); + + // skip placeholder transaction + foreach (Dto.Key key in decryptionKeys.Keys.AsEnumerable().Skip(1)) + { + try + { + dk.Decode(key.Key_.Span); + ShutterCrypto.ComputeIdentity(identity, key.Identity.Span); + } + catch (Bls.BlsException e) + { + if (_logger.IsDebug) _logger.Error("Invalid Shutter decryption keys received.", e); + return false; + } + + eonKey.Decode(eonInfo.Key.AsSpan()); + if (!ShutterCrypto.CheckDecryptionKey(dk, eonKey, identity.ToAffine())) + { + if (_logger.IsDebug) _logger.Debug("Invalid Shutter decryption keys received: decryption key did not match eon key."); + return false; + } + } + + int signerIndicesCount = decryptionKeys.Gnosis.SignerIndices.Count; + + if (decryptionKeys.Gnosis.SignerIndices.ContainsDuplicates(signerIndicesCount)) + { + if (_logger.IsDebug) _logger.Debug("Invalid Shutter decryption keys received: incorrect number of signer indices."); + return false; + } + + if (decryptionKeys.Gnosis.Signatures.Count != signerIndicesCount) + { + if (_logger.IsDebug) _logger.Debug("Invalid Shutter decryption keys received: incorrect number of signatures."); + return false; + } + + if (signerIndicesCount != (int)eonInfo.Threshold) + { + if (_logger.IsDebug) _logger.Debug($"Invalid Shutter decryption keys received: signer indices did not match threshold."); + return false; + } + + IEnumerable> identityPreimages = decryptionKeys.Keys.Select(key => key.Identity.Memory); + + foreach ((ulong signerIndex, ByteString signature) in decryptionKeys.Gnosis.SignerIndices.Zip(decryptionKeys.Gnosis.Signatures)) + { + Address keyperAddress = eonInfo.Addresses[signerIndex]; + + if (!ShutterCrypto.CheckSlotDecryptionIdentitiesSignature(_instanceId, eonInfo.Eon, decryptionKeys.Gnosis.Slot, decryptionKeys.Gnosis.TxPointer, identityPreimages, signature.Span, keyperAddress)) + { + if (_logger.IsDebug) _logger.Debug($"Invalid Shutter decryption keys received: bad signature."); + return false; + } + } + + return true; + } + + private static EnumerableWithCount<(ReadOnlyMemory, ReadOnlyMemory)> ExtractKeys(in Dto.DecryptionKeys decryptionKeys) + // remove placeholder + => new(decryptionKeys.Keys.Skip(1).Select(x => (x.Identity.Memory, x.Key_.Memory)), decryptionKeys.Keys.Count - 1); +} diff --git a/src/Nethermind/Nethermind.Shutter/ShutterLogScanner.cs b/src/Nethermind/Nethermind.Shutter/ShutterLogScanner.cs new file mode 100644 index 00000000000..7619461482e --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterLogScanner.cs @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + + +using System.Runtime.CompilerServices; +using Nethermind.Abi; +using Nethermind.Blockchain.Filters; +using Nethermind.Blockchain.Filters.Topics; +using Nethermind.Core; +using Nethermind.Facade.Find; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Shutter.Contracts; + +namespace Nethermind.Shutter; + +public class ShutterLogScanner( + SequencerContract sequencerContract, + ILogFinder logFinder, + ILogManager logManager, + IAbiEncoder abiEncoder) + : LogScanner( + logFinder, + new AddressFilter(sequencerContract.ContractAddress!), + new SequenceTopicsFilter(new SpecificTopic(sequencerContract.TransactionSubmittedAbi.Signature.Hash)), + logManager) +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override ISequencerContract.TransactionSubmitted ParseEvent(ILogEntry log) + { + object[] decodedEvent = abiEncoder.Decode(AbiEncodingStyle.None, sequencerContract.TransactionSubmittedAbi.Signature, log.Data); + return new ISequencerContract.TransactionSubmitted + { + Eon = (ulong)decodedEvent[0], + TxIndex = (ulong)decodedEvent[1], + IdentityPrefix = new Bytes32((byte[])decodedEvent[2]), + Sender = (Address)decodedEvent[3], + EncryptedTransaction = (byte[])decodedEvent[4], + GasLimit = (UInt256)decodedEvent[5] + }; + } +} diff --git a/src/Nethermind/Nethermind.Shutter/ShutterP2P.cs b/src/Nethermind/Nethermind.Shutter/ShutterP2P.cs new file mode 100644 index 00000000000..6bd04185038 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterP2P.cs @@ -0,0 +1,177 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Libp2p.Core; +using Nethermind.Libp2p.Core.Discovery; +using Nethermind.Libp2p.Protocols.Pubsub; +using Nethermind.Libp2p.Stack; +using Nethermind.Libp2p.Protocols; +using System; +using System.Threading.Tasks; +using System.Threading; +using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; +using Multiformats.Address; +using Nethermind.Shutter.Config; +using Nethermind.Logging; +using Nethermind.Core.Extensions; +using ILogger = Nethermind.Logging.ILogger; +using System.Threading.Channels; +using Google.Protobuf; + +namespace Nethermind.Shutter; + +public class ShutterP2P : IShutterP2P +{ + private readonly ILogger _logger; + private readonly IShutterConfig _cfg; + private readonly Channel _msgQueue = Channel.CreateBounded(1000); + private readonly PubsubRouter _router; + private readonly ILocalPeer _peer; + private readonly ServiceProvider _serviceProvider; + private CancellationTokenSource? _cts; + private static readonly TimeSpan DisconnectionLogTimeout = TimeSpan.FromMinutes(5); + + public class ShutterP2PException(string message, Exception? innerException = null) : Exception(message, innerException); + + + public ShutterP2P(IShutterConfig shutterConfig, ILogManager logManager) + { + _logger = logManager.GetClassLogger(); + _cfg = shutterConfig; + _serviceProvider = new ServiceCollection() + .AddLibp2p(builder => builder) + .AddSingleton(new IdentifyProtocolSettings + { + ProtocolVersion = shutterConfig.P2PProtocolVersion, + AgentVersion = shutterConfig.P2PAgentVersion + }) + // pubsub settings + .AddSingleton(new Settings() + { + ReconnectionAttempts = int.MaxValue, + Degree = 3, + LowestDegree = 2, + HighestDegree = 6, + LazyDegree = 3 + }) + //.AddSingleton(new NethermindLoggerFactory(logManager)) + // .AddLogging(builder => + // builder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace) + // .AddSimpleConsole(l => + // { + // l.SingleLine = true; + // l.TimestampFormat = "[HH:mm:ss.FFF]"; + // }) + // ) + .BuildServiceProvider(); + + IPeerFactory peerFactory = _serviceProvider!.GetService()!; + _peer = peerFactory.Create(new Identity(), "/ip4/0.0.0.0/tcp/" + _cfg.P2PPort); + _router = _serviceProvider!.GetService()!; + ITopic topic = _router.Subscribe("decryptionKeys"); + + topic.OnMessage += (byte[] msg) => + { + _msgQueue.Writer.TryWrite(msg); + if (_logger.IsTrace) _logger.Trace("Received Shutter P2P message."); + }; + } + + public async Task Start(Func onKeysReceived, CancellationTokenSource? cts = null) + { + MyProto proto = new(); + _cts = cts ?? new(); + _ = _router!.RunAsync(_peer, proto, token: _cts.Token); + proto.SetupFinished().GetAwaiter().GetResult(); + ConnectToPeers(proto, _cfg.BootnodeP2PAddresses!); + + if (_logger.IsInfo) _logger.Info($"Started Shutter P2P: {_peer.Address}"); + + long lastMessageProcessed = DateTimeOffset.Now.ToUnixTimeSeconds(); + while (true) + { + try + { + using var timeoutSource = new CancellationTokenSource(DisconnectionLogTimeout); + using var source = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token, timeoutSource.Token); + + byte[] msg = await _msgQueue.Reader.ReadAsync(source.Token); + lastMessageProcessed = DateTimeOffset.Now.ToUnixTimeSeconds(); + ProcessP2PMessage(msg, onKeysReceived); + } + catch (OperationCanceledException) + { + if (_cts.IsCancellationRequested) + { + if (_logger.IsInfo) _logger.Info($"Shutting down Shutter P2P..."); + break; + } + else if (_logger.IsWarn) + { + long delta = DateTimeOffset.Now.ToUnixTimeSeconds() - lastMessageProcessed; + _logger.Warn($"Not receiving Shutter messages ({delta / 60}m)..."); + } + } + catch (Exception e) + { + if (_logger.IsError) _logger.Error("Shutter processing thread error", e); + throw new ShutterP2PException("Shutter processing thread error", e); + } + } + } + + public async ValueTask DisposeAsync() + { + _router?.UnsubscribeAll(); + await (_serviceProvider?.DisposeAsync() ?? default); + await (_cts?.CancelAsync() ?? Task.CompletedTask); + } + + private class MyProto : IDiscoveryProtocol + { + private readonly TaskCompletionSource taskCompletionSource = new(); + public Func? OnAddPeer { get; set; } + public Func? OnRemovePeer { get; set; } + + public Task SetupFinished() => taskCompletionSource.Task; + + public Task DiscoverAsync(Multiaddress localPeerAddr, CancellationToken token = default) + { + taskCompletionSource.TrySetResult(); + return Task.CompletedTask; + } + } + + private void ProcessP2PMessage(byte[] msg, Func onKeysReceived) + { + if (_logger.IsTrace) _logger.Trace("Processing Shutter P2P message."); + + try + { + Dto.Envelope envelope = Dto.Envelope.Parser.ParseFrom(msg); + if (envelope.Message.TryUnpack(out Dto.DecryptionKeys decryptionKeys)) + { + _ = onKeysReceived(decryptionKeys); + } + else if (_logger.IsDebug) + { + _logger.Debug($"Could not parse Shutter decryption keys: protobuf type names did not match."); + } + } + catch (InvalidProtocolBufferException e) + { + if (_logger.IsDebug) _logger.Warn($"Could not parse Shutter decryption keys: {e}"); + } + } + + private static void ConnectToPeers(MyProto proto, IEnumerable p2pAddresses) + { + // shuffle peers to connect to random subset of keypers + int seed = (int)(DateTimeOffset.Now.ToUnixTimeSeconds() % int.MaxValue); + foreach (string addr in p2pAddresses.Shuffle(new Random(seed))) + { + proto.OnAddPeer?.Invoke([addr]); + } + } +} diff --git a/src/Nethermind/Nethermind.Shutter/ShutterPlugin.cs b/src/Nethermind/Nethermind.Shutter/ShutterPlugin.cs new file mode 100644 index 00000000000..04b6a444699 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterPlugin.cs @@ -0,0 +1,137 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Nethermind.Api; +using Nethermind.Api.Extensions; +using Nethermind.Consensus; +using Nethermind.Consensus.Transactions; +using Nethermind.Core; +using Nethermind.Shutter.Config; +using Nethermind.Merge.Plugin; +using Nethermind.Logging; +using System.IO; +using Nethermind.Serialization.Json; +using System.Threading; +using Nethermind.Config; + +namespace Nethermind.Shutter; + +public class ShutterPlugin : IConsensusWrapperPlugin, IInitializationPlugin +{ + public string Name => "Shutter"; + public string Description => "Shutter plugin for AuRa post-merge chains"; + public string Author => "Nethermind"; + public bool Enabled => ShouldRunSteps(_api!); + public int Priority => PluginPriorities.Shutter; + + private INethermindApi? _api; + private IMergeConfig? _mergeConfig; + private IShutterConfig? _shutterConfig; + private IBlocksConfig? _blocksConfig; + private ShutterApi? _shutterApi; + private ILogger _logger; + private readonly CancellationTokenSource _cts = new(); + + public class ShutterLoadingException(string message, Exception? innerException = null) : Exception(message, innerException); + + public Task Init(INethermindApi nethermindApi) + { + _api = nethermindApi; + _blocksConfig = _api.Config(); + _mergeConfig = _api.Config(); + _shutterConfig = _api.Config(); + _logger = _api.LogManager.GetClassLogger(); + if (_logger.IsInfo) _logger.Info($"Initializing Shutter plugin."); + return Task.CompletedTask; + } + + public Task InitRpcModules() + { + if (Enabled) + { + if (_api!.BlockProducer is null) throw new ArgumentNullException(nameof(_api.BlockProducer)); + + if (_logger.IsInfo) _logger.Info("Initializing Shutter block improvement."); + _api.BlockImprovementContextFactory = _shutterApi!.GetBlockImprovementContextFactory(_api.BlockProducer); + } + return Task.CompletedTask; + } + + public IBlockProducer InitBlockProducer(IBlockProducerFactory consensusPlugin, ITxSource? txSource) + { + if (Enabled) + { + if (_api!.BlockTree is null) throw new ArgumentNullException(nameof(_api.BlockTree)); + if (_api.EthereumEcdsa is null) throw new ArgumentNullException(nameof(_api.SpecProvider)); + if (_api.LogFinder is null) throw new ArgumentNullException(nameof(_api.LogFinder)); + if (_api.SpecProvider is null) throw new ArgumentNullException(nameof(_api.SpecProvider)); + if (_api.ReceiptFinder is null) throw new ArgumentNullException(nameof(_api.ReceiptFinder)); + if (_api.WorldStateManager is null) throw new ArgumentNullException(nameof(_api.WorldStateManager)); + + if (_logger.IsInfo) _logger.Info("Initializing Shutter block producer."); + + try + { + _shutterConfig!.Validate(); + } + catch (ArgumentException e) + { + throw new ShutterLoadingException("Invalid Shutter config", e); + } + + Dictionary validatorsInfo = []; + if (_shutterConfig!.ValidatorInfoFile is not null) + { + try + { + validatorsInfo = LoadValidatorInfo(_shutterConfig!.ValidatorInfoFile); + } + catch (Exception e) + { + throw new ShutterLoadingException("Could not load Shutter validator info file", e); + } + } + + _shutterApi = new ShutterApi( + _api.AbiEncoder, + _api.BlockTree, + _api.EthereumEcdsa, + _api.LogFinder, + _api.ReceiptFinder, + _api.LogManager, + _api.SpecProvider, + _api.Timestamper, + _api.WorldStateManager, + _shutterConfig, + validatorsInfo, + TimeSpan.FromSeconds(_blocksConfig!.SecondsPerSlot) + ); + + _ = _shutterApi.StartP2P(_cts); + } + + return consensusPlugin.InitBlockProducer(_shutterApi is null ? txSource : _shutterApi.TxSource.Then(txSource)); + } + + public bool ShouldRunSteps(INethermindApi api) + { + _shutterConfig = api.Config(); + _mergeConfig = api.Config(); + return _shutterConfig!.Enabled && _mergeConfig!.Enabled && api.ChainSpec.SealEngineType is SealEngineType.AuRa; + } + + public async ValueTask DisposeAsync() + { + _cts.Dispose(); + await (_shutterApi?.DisposeAsync() ?? default); + } + + private static Dictionary LoadValidatorInfo(string fp) + { + FileStream fstream = new(fp, FileMode.Open, FileAccess.Read, FileShare.None); + return new EthereumJsonSerializer().Deserialize>(fstream); + } +} diff --git a/src/Nethermind/Nethermind.Shutter/ShutterTime.cs b/src/Nethermind/Nethermind.Shutter/ShutterTime.cs new file mode 100644 index 00000000000..d7872fbdc25 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterTime.cs @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; + +namespace Nethermind.Shutter; + +public class ShutterTime(ulong genesisTimestampMs, ITimestamper timestamper, TimeSpan slotLength, TimeSpan blockUpToDateCutoff) +{ + public class ShutterSlotCalulationException(string message, Exception? innerException = null) : Exception(message, innerException); + + public readonly ulong GenesisTimestampMs = genesisTimestampMs; + + public ulong GetSlotTimestampMs(ulong slot) + => GenesisTimestampMs + slot * (ulong)slotLength.TotalMilliseconds; + + public long GetCurrentOffsetMs(ulong slot, ulong? slotTimestampMs = null) + => timestamper.UtcNowOffset.ToUnixTimeMilliseconds() - (long)(slotTimestampMs ?? GetSlotTimestampMs(slot)); + + public bool IsBlockUpToDate(Block head) + => timestamper.UtcNowOffset.ToUnixTimeSeconds() - (long)head.Header.Timestamp < blockUpToDateCutoff.TotalSeconds; + + public ulong GetSlot(ulong slotTimestampMs) + { + long slotTimeSinceGenesis = (long)slotTimestampMs - (long)GenesisTimestampMs; + if (slotTimeSinceGenesis < 0) + { + throw new ShutterSlotCalulationException($"Slot timestamp {slotTimestampMs}ms was before than genesis timestamp {GenesisTimestampMs}ms."); + } + + return (ulong)slotTimeSinceGenesis / (ulong)slotLength.TotalMilliseconds; + } + + public (ulong slot, long slotOffset) GetBuildingSlotAndOffset(ulong slotTimestampMs) + { + ulong buildingSlot = GetSlot(slotTimestampMs); + long offset = GetCurrentOffsetMs(buildingSlot, slotTimestampMs); + + return (buildingSlot, offset); + } +} diff --git a/src/Nethermind/Nethermind.Shutter/ShutterTransactions.cs b/src/Nethermind/Nethermind.Shutter/ShutterTransactions.cs new file mode 100644 index 00000000000..956c7cb0bbe --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterTransactions.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Shutter; + +public readonly struct ShutterTransactions +{ + public Transaction[] Transactions { get; init; } + public ulong Slot { get; init; } +} diff --git a/src/Nethermind/Nethermind.Shutter/ShutterTxFilter.cs b/src/Nethermind/Nethermind.Shutter/ShutterTxFilter.cs new file mode 100644 index 00000000000..8ca9fc7992b --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterTxFilter.cs @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Logging; +using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Transactions; +using Nethermind.TxPool; + +namespace Nethermind.Shutter; + +public class ShutterTxFilter( + ISpecProvider specProvider, + ILogManager logManager) : ITxFilter +{ + private readonly TxValidator _txValidator = new(specProvider.ChainId); + private readonly ILogger _logger = logManager.GetClassLogger(); + + public AcceptTxResult IsAllowed(Transaction tx, BlockHeader parentHeader) + { + if (tx.Type == TxType.Blob) + { + if (_logger.IsDebug) _logger.Debug("Decrypted Shutter transaction was blob, cannot include."); + return AcceptTxResult.Invalid; + } + + IReleaseSpec releaseSpec = specProvider.GetSpec(parentHeader); + ValidationResult wellFormed = _txValidator.IsWellFormed(tx, releaseSpec); + + if (_logger.IsDebug && !wellFormed) _logger.Debug($"Decrypted Shutter transaction was not well-formed: {wellFormed}"); + + return wellFormed ? AcceptTxResult.Accepted : AcceptTxResult.Invalid; + } +} diff --git a/src/Nethermind/Nethermind.Shutter/ShutterTxLoader.cs b/src/Nethermind/Nethermind.Shutter/ShutterTxLoader.cs new file mode 100644 index 00000000000..0e8d2f814b3 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterTxLoader.cs @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Crypto; +using Nethermind.Int256; +using Nethermind.Serialization.Rlp; +using Nethermind.Logging; +using Nethermind.Shutter.Contracts; +using Nethermind.Shutter.Config; +using Nethermind.Core.Collections; +using Nethermind.Core.Extensions; +using System.IO; +using Nethermind.Abi; +using Nethermind.Facade.Find; +using System.Runtime.CompilerServices; + +namespace Nethermind.Shutter; + +using G1 = Bls.P1; + +public class ShutterTxLoader( + ILogFinder logFinder, + IShutterConfig cfg, + ShutterTime time, + ISpecProvider specProvider, + IEthereumEcdsa ecdsa, + IAbiEncoder abiEncoder, + ILogManager logManager) +{ + private readonly ShutterLogScanner _logScanner = new( + new(new Address(cfg.SequencerContractAddress!)), + logFinder, + logManager, + abiEncoder); + + private readonly ShutterEventQueue _events = new(cfg.EncryptedGasLimit, logManager); + private ulong _txPointer = ulong.MaxValue; + private bool _loadFromReceipts = false; + private readonly int _maxTransactions = cfg.EncryptedGasLimit / 21000; + private readonly ShutterTxFilter _txFilter = new(specProvider, logManager); + private readonly ILogger _logger = logManager.GetClassLogger(); + + public ShutterTransactions LoadTransactions(Block? head, BlockHeader parentHeader, IShutterKeyValidator.ValidatedKeys keys) + { + using ArrayPoolList sequencedTransactions = new(_maxTransactions, GetNextTransactions(keys.Eon, keys.TxPointer, head?.Number ?? 0)); + + long offset = time.GetCurrentOffsetMs(keys.Slot); + Metrics.ShutterKeysReceivedTimeOffset = offset; + string offsetText = offset < 0 ? $"{-offset}ms before" : $"{offset}ms fter"; + if (_logger.IsInfo) _logger.Info($"Got {sequencedTransactions.Count} encrypted transactions from Shutter sequencer contract for slot {keys.Slot} at time {offsetText} slot start..."); + + using ArrayPoolList? decrypted = DecryptSequencedTransactions(sequencedTransactions, keys.Keys); + + if (_logger.IsDebug && decrypted is not null) _logger.Debug($"Decrypted Shutter transactions:{Environment.NewLine}{string.Join(Environment.NewLine, decrypted.Select(tx => tx.ToShortString()))}"); + + Transaction[] filtered = decrypted is null ? [] : FilterTransactions(decrypted, parentHeader); + + ShutterTransactions shutterTransactions = new() + { + Transactions = filtered, + Slot = keys.Slot + }; + + Metrics.ShutterTransactions = (uint)filtered.Length; + Metrics.ShutterBadTransactions = (uint)(sequencedTransactions.Count - filtered.Length); + + if (_logger.IsDebug && shutterTransactions.Transactions.Length > 0) _logger.Debug($"Filtered Shutter transactions:{Environment.NewLine}{string.Join(Environment.NewLine, shutterTransactions.Transactions.Select(tx => tx.ToShortString()))}"); + return shutterTransactions; + } + + public void LoadFromReceipts(Block? head, TxReceipt[] receipts, ulong eon) + { + lock (_events) + { + if (_loadFromReceipts && head is not null) + { + IEnumerable events = _logScanner.ScanReceipts(head.Number, receipts); + _events.EnqueueEvents(events, eon); + } + } + } + + private Transaction[] FilterTransactions(IEnumerable transactions, BlockHeader parentHeader) + => transactions.Where(tx => _txFilter.IsAllowed(tx, parentHeader) == TxPool.AcceptTxResult.Accepted).ToArray(); + + private ArrayPoolList? DecryptSequencedTransactions(ArrayPoolList sequencedTransactions, EnumerableWithCount<(ReadOnlyMemory IdentityPreimage, ReadOnlyMemory Key)> decryptionKeys) + { + int txCount = sequencedTransactions.Count; + int keyCount = decryptionKeys.Count; + + if (txCount < keyCount) + { + if (_logger.IsError) _logger.Error($"Could not decrypt Shutter transactions: found {txCount} transactions but received {keyCount} keys (excluding placeholder)."); + return null; + } + + if (txCount > keyCount) + { + if (_logger.IsWarn) _logger.Warn($"Could not decrypt all Shutter transactions: found {txCount} transactions but received {keyCount} keys (excluding placeholder)."); + sequencedTransactions.ReduceCount(txCount - keyCount); + txCount = keyCount; + } + + using ArrayPoolList sortedIndexes = sequencedTransactions.ToPooledList(); + sortedIndexes.Sort((a, b) => Bytes.BytesComparer.Compare(a.IdentityPreimage, b.IdentityPreimage)); + + using ArrayPoolList sortedKeyIndexes = new(txCount, txCount); + int keyIndex = 0; + foreach (SequencedTransaction index in sortedIndexes) + { + sortedKeyIndexes[index.Index] = keyIndex++; + } + + using var decryptionKeysList = new ArrayPoolList<(ReadOnlyMemory IdentityPreimage, ReadOnlyMemory Key)>(keyCount, decryptionKeys); + + return sequencedTransactions + .AsParallel() + .AsOrdered() + // ReSharper disable AccessToDisposedClosure + .Select((tx, i) => DecryptSequencedTransaction(tx, decryptionKeysList[sortedKeyIndexes[i]])) + // ReSharper restore AccessToDisposedClosure + .OfType() + .ToPooledList(sequencedTransactions.Count); + } + + [SkipLocalsInit] + private Transaction? DecryptSequencedTransaction(SequencedTransaction sequencedTransaction, (ReadOnlyMemory IdentityPreimage, ReadOnlyMemory Key) decryptionKey) + { + try + { + ShutterCrypto.EncryptedMessage encryptedMessage = ShutterCrypto.DecodeEncryptedMessage(sequencedTransaction.EncryptedTransaction); + G1 key = new(stackalloc long[G1.Sz]); + key.Decode(decryptionKey.Key.Span); + G1 identity = new(stackalloc long[G1.Sz]); + ShutterCrypto.ComputeIdentity(identity, decryptionKey.IdentityPreimage.Span); + G1 sequencedIdentity = new(stackalloc long[G1.Sz]); + sequencedIdentity.Decode(sequencedTransaction.Identity.AsSpan()); + + if (!identity.IsEqual(sequencedIdentity)) + { + if (_logger.IsDebug) _logger.Debug("Could not decrypt Shutter transaction: Transaction identity did not match decryption key."); + return null; + } + + int len = ShutterCrypto.GetDecryptedDataLength(encryptedMessage); + using ArrayPoolList buf = new(len, len); + Span encodedTransaction = buf.AsSpan(); + ShutterCrypto.Decrypt(ref encodedTransaction, encryptedMessage, key); + + if (_logger.IsDebug) _logger.Debug($"Decrypted Shutter transaction, got encoded transaction data: {Convert.ToHexString(encodedTransaction)}"); + + return DecodeTransaction(encodedTransaction); + } + catch (ShutterCrypto.ShutterCryptoException e) + { + if (_logger.IsDebug) _logger.Error($"Could not decode encrypted Shutter transaction", e); + } + catch (Bls.BlsException e) + { + if (_logger.IsDebug) _logger.Error("Could not decrypt Shutter transaction with invalid key", e); + } + catch (RlpException e) + { + if (_logger.IsDebug) _logger.Error("Could not decode decrypted Shutter transaction", e); + } + catch (ArgumentException e) + { + if (_logger.IsDebug) _logger.Error("Could not recover Shutter transaction sender address", e); + } + catch (InvalidDataException e) + { + if (_logger.IsDebug) _logger.Error("Decrypted Shutter transaction had no signature", e); + } + + return null; + } + + private Transaction DecodeTransaction(ReadOnlySpan encoded) + { + Transaction tx = TxDecoder.Instance.Decode(encoded, RlpBehaviors.SkipTypedWrapping); + tx.SenderAddress = ecdsa.RecoverAddress(tx, true); + return tx; + } + + private IEnumerable GetNextTransactions(ulong eon, ulong txPointer, long headBlockNumber) + { + lock (_events) + { + if (_loadFromReceipts) + { + if (_logger.IsDebug) _logger.Debug($"Found {_events.Count} Shutter events in recent blocks up to {headBlockNumber}, local tx pointer is {_txPointer}."); + } + else + { + LoadFromScanningLogs(eon, txPointer, headBlockNumber); + _loadFromReceipts = true; + } + + IEnumerable events = _events.DequeueToGasLimit(eon, txPointer); + + int index = 0; + foreach (ISequencerContract.TransactionSubmitted e in events) + { + yield return EventToSequencedTransaction(e, index++, eon); + } + } + } + + [SkipLocalsInit] + private static SequencedTransaction EventToSequencedTransaction(ISequencerContract.TransactionSubmitted e, int index, ulong eon) + { + byte[] identityPreimage = new byte[52]; + e.IdentityPrefix.AsSpan().CopyTo(identityPreimage.AsSpan()); + e.Sender.Bytes.CopyTo(identityPreimage.AsSpan()[32..]); + + G1 identity = new(stackalloc long[G1.Sz]); + ShutterCrypto.ComputeIdentity(identity, identityPreimage.AsSpan()); + + return new() + { + Index = index, + Eon = eon, + EncryptedTransaction = e.EncryptedTransaction, + GasLimit = e.GasLimit, + Identity = identity.Compress(), + IdentityPreimage = identityPreimage + }; + } + + private void LoadFromScanningLogs(ulong eon, ulong txPointer, long headBlockNumber) + { + _txPointer = txPointer; + + IEnumerable events = _logScanner.ScanLogs(headBlockNumber, (ISequencerContract.TransactionSubmitted e) => e.Eon == eon && e.TxIndex <= _txPointer); + + int count = 0; + foreach (ISequencerContract.TransactionSubmitted e in events) + { + _events.EnqueueEvent(e, eon); + count++; + } + + if (_logger.IsDebug) _logger.Debug($"Found {count} Shutter events from scanning logs up to block {headBlockNumber}, local tx pointer is {_txPointer}."); + } + + + private struct SequencedTransaction + { + public int Index; + public ulong Eon; + public byte[] EncryptedTransaction; + public UInt256 GasLimit; + public byte[] Identity; + public byte[] IdentityPreimage; + } + + private readonly struct DecryptedTransactions : IDisposable + { + public ArrayPoolList Transactions { get; init; } + public ArrayPoolList SortedKeyIndexes { get; init; } + + public void Dispose() + { + SortedKeyIndexes.Dispose(); + Transactions.Dispose(); + } + } +} diff --git a/src/Nethermind/Nethermind.Shutter/ShutterTxSource.cs b/src/Nethermind/Nethermind.Shutter/ShutterTxSource.cs new file mode 100644 index 00000000000..4736d45dc46 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterTxSource.cs @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Consensus.Transactions; +using Nethermind.Core; +using Nethermind.Consensus.Producers; +using Nethermind.Shutter.Config; +using Nethermind.Logging; +using System; +using Nethermind.Core.Caching; +using System.Threading.Tasks; +using System.Threading; +using Nethermind.Core.Collections; + +namespace Nethermind.Shutter; + +public class ShutterTxSource( + ShutterTxLoader txLoader, + IShutterConfig shutterConfig, + ShutterTime shutterTime, + ILogManager logManager) + : ITxSource, IShutterTxSignal +{ + private readonly LruCache _txCache = new(5, "Shutter tx cache"); + private readonly ILogger _logger = logManager.GetClassLogger(); + private ulong _keyWaitTaskId = 0; + private readonly Dictionary> _keyWaitTasks = []; + private readonly object _syncObject = new(); + + public IEnumerable GetTransactions(BlockHeader parent, long gasLimit, PayloadAttributes? payloadAttributes) + { + if (!shutterConfig.Validator) + { + if (_logger.IsDebug) _logger.Debug($"Not building Shutterized block since running in non-validator mode."); + return []; + } + + ulong buildingSlot; + try + { + (buildingSlot, _) = shutterTime.GetBuildingSlotAndOffset(payloadAttributes!.Timestamp * 1000); + } + catch (ShutterTime.ShutterSlotCalulationException e) + { + if (_logger.IsDebug) _logger.Warn($"Could not calculate Shutter building slot: {e}"); + return []; + } + + ShutterTransactions? shutterTransactions = _txCache.Get(buildingSlot); + if (shutterTransactions is null) + { + if (_logger.IsDebug) _logger.Debug($"No Shutter transactions currently loaded for slot {buildingSlot}."); + return []; + } + + int txCount = shutterTransactions.Value.Transactions.Length; + if (_logger.IsInfo) _logger.Info($"Preparing Shutterized block {parent.Number + 1} for slot {buildingSlot} with {txCount} decrypted transactions."); + return shutterTransactions.Value.Transactions; + } + + public Task WaitForTransactions(ulong slot, CancellationToken cancellationToken) + { + TaskCompletionSource? tcs = null; + lock (_syncObject) + { + if (_txCache.Contains(slot)) + { + return Task.CompletedTask; + } + + ulong taskId = _keyWaitTaskId++; + tcs = new(); + CancellationTokenRegistration ctr = cancellationToken.Register(() => CancelWaitForTransactions(slot, taskId)); + + if (!_keyWaitTasks.ContainsKey(slot)) + { + _keyWaitTasks.Add(slot, []); + } + Dictionary slotWaitTasks = _keyWaitTasks.GetValueOrDefault(slot)!; + slotWaitTasks!.Add(taskId, (tcs, ctr)); + } + return tcs.Task; + } + + private void CancelWaitForTransactions(ulong slot, ulong taskId) + { + if (_keyWaitTasks.TryGetValue(slot, out Dictionary? slotWaitTasks)) + { + if (slotWaitTasks.TryGetValue(taskId, out (TaskCompletionSource Tcs, CancellationTokenRegistration Ctr) waitTask)) + { + waitTask.Tcs.TrySetException(new OperationCanceledException()); + waitTask.Ctr.Dispose(); + } + slotWaitTasks.Remove(taskId); + } + } + + public bool HaveTransactionsArrived(ulong slot) + { + return _txCache.Contains(slot); + } + + public ShutterTransactions LoadTransactions(Block? head, BlockHeader parentHeader, IShutterKeyValidator.ValidatedKeys keys) + { + ShutterTransactions transactions = txLoader.LoadTransactions(head, parentHeader, keys); + _txCache.Set(keys.Slot, transactions); + + lock (_syncObject) + { + if (_keyWaitTasks.Remove(keys.Slot, out Dictionary? slotWaitTasks)) + { + slotWaitTasks.ForEach(waitTask => + { + waitTask.Value.Tcs.TrySetResult(); + waitTask.Value.Ctr.Dispose(); + }); + } + } + + return transactions; + } + + public void Dispose() + => _keyWaitTasks.ForEach(x => x.Value.ForEach(waitTask => waitTask.Value.Item2.Dispose())); +} diff --git a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs index 734b84f298f..ba794b320b1 100644 --- a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs +++ b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs @@ -149,7 +149,7 @@ public void Sepolia_loads_properly(ForkActivation forkActivation) Assert.That(provider.NetworkId, Is.EqualTo(BlockchainIds.Sepolia)); GetTransitionTimestamps(chainSpec.Parameters).Should().AllSatisfy( - t => ValidateSlotByTimestamp(t, SepoliaSpecProvider.BeaconChainGenesisTimestamp).Should().BeTrue()); + t => ValidateSlotByTimestamp(t, SepoliaSpecProvider.BeaconChainGenesisTimestampConst).Should().BeTrue()); } public static IEnumerable HoleskyActivations @@ -216,7 +216,7 @@ public void Chiado_loads_properly(ForkActivation forkActivation) VerifyGnosisShanghaiSpecifics(preShanghaiSpec, postShanghaiSpec); VerifyGnosisCancunSpecifics(); GetTransitionTimestamps(chainSpec.Parameters).Should().AllSatisfy( - t => ValidateSlotByTimestamp(t, ChiadoSpecProvider.BeaconChainGenesisTimestamp, GnosisBlockTime).Should().BeTrue()); + t => ValidateSlotByTimestamp(t, ChiadoSpecProvider.BeaconChainGenesisTimestampConst, GnosisBlockTime).Should().BeTrue()); } public static IEnumerable GnosisActivations @@ -263,7 +263,7 @@ public void Gnosis_loads_properly(ForkActivation forkActivation) VerifyGnosisShanghaiSpecifics(preShanghaiSpec, postShanghaiSpec); VerifyGnosisCancunSpecifics(); GetTransitionTimestamps(chainSpec.Parameters).Should().AllSatisfy( - t => ValidateSlotByTimestamp(t, GnosisSpecProvider.BeaconChainGenesisTimestamp, GnosisBlockTime).Should().BeTrue()); + t => ValidateSlotByTimestamp(t, GnosisSpecProvider.BeaconChainGenesisTimestampConst, GnosisBlockTime).Should().BeTrue()); } private static void VerifyGnosisCancunSpecifics() @@ -375,7 +375,7 @@ public void Mainnet_loads_properly(ForkActivation forkActivation) Assert.That(provider.NetworkId, Is.EqualTo(BlockchainIds.Mainnet)); GetTransitionTimestamps(chainSpec.Parameters).Should().AllSatisfy( - t => ValidateSlotByTimestamp(t, MainnetSpecProvider.BeaconChainGenesisTimestamp).Should().BeTrue()); + t => ValidateSlotByTimestamp(t, MainnetSpecProvider.BeaconChainGenesisTimestampConst).Should().BeTrue()); } [Flags] @@ -694,7 +694,7 @@ public void Eip_transitions_loaded_correctly() Eip2930Transition = 29300L, Eip1559Transition = 15590L, Eip1559FeeCollectorTransition = 15591L, - Eip1559FeeCollector = Address.SystemUser, + FeeCollector = Address.SystemUser, Eip1559BaseFeeMinValueTransition = 15592L, Eip1559BaseFeeMinValue = UInt256.UInt128MaxValue, Eip3198Transition = 31980L, @@ -708,7 +708,9 @@ public void Eip_transitions_loaded_correctly() Eip3855TransitionTimestamp = 1000000012, Eip3860TransitionTimestamp = 1000000012, Eip1153TransitionTimestamp = 1000000024, - Eip2537TransitionTimestamp = 1000000024 + Eip2537TransitionTimestamp = 1000000024, + + Eip7702TransitionTimestamp = 1000000032, } }; @@ -765,7 +767,7 @@ void TestTransitions(ForkActivation activation, Action changes) TestTransitions((ForkActivation)12831L, r => { r.IsEip1283Enabled = false; }); TestTransitions((ForkActivation)13440L, r => { r.IsEip1344Enabled = true; }); TestTransitions((ForkActivation)15590L, r => { r.IsEip1559Enabled = true; }); - TestTransitions((ForkActivation)15591L, r => { r.Eip1559FeeCollector = Address.SystemUser; }); + TestTransitions((ForkActivation)15591L, r => { r.FeeCollector = Address.SystemUser; }); TestTransitions((ForkActivation)15592L, r => { r.Eip1559BaseFeeMinValue = UInt256.UInt128MaxValue; }); TestTransitions((ForkActivation)18840L, r => { r.IsEip1884Enabled = true; }); TestTransitions((ForkActivation)20280L, r => { r.IsEip2028Enabled = true; }); @@ -787,6 +789,7 @@ void TestTransitions(ForkActivation activation, Action changes) r.IsEip3860Enabled = true; }); TestTransitions((40001L, 1000000024), r => { r.IsEip1153Enabled = r.IsEip2537Enabled = true; }); + TestTransitions((40001L, 1000000032), r => { r.IsEip7702Enabled = true; }); } [TestCaseSource(nameof(BlockNumbersAndTimestampsNearForkActivations))] diff --git a/src/Nethermind/Nethermind.Specs.Test/CustomSpecProvider.cs b/src/Nethermind/Nethermind.Specs.Test/CustomSpecProvider.cs index 6b405e37354..b00d58fbde8 100644 --- a/src/Nethermind/Nethermind.Specs.Test/CustomSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs.Test/CustomSpecProvider.cs @@ -55,5 +55,6 @@ public long? DaoBlockNumber } } + public ulong? BeaconChainGenesisTimestamp { get; set; } } diff --git a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs index 39c6d3c977f..5394d1c475d 100644 --- a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs @@ -124,11 +124,11 @@ public long Eip1559TransitionBlock set => _overridenEip1559TransitionBlock = value; } - private Address? _overridenEip1559FeeCollector; - public Address? Eip1559FeeCollector + private Address? _overridenFeeCollector; + public Address? FeeCollector { - get => _overridenEip1559FeeCollector ?? _spec.Eip1559FeeCollector; - set => _overridenEip1559FeeCollector = value; + get => _overridenFeeCollector ?? _spec.FeeCollector; + set => _overridenFeeCollector = value; } private ulong? _overridenEip4844TransitionTimeStamp; @@ -153,12 +153,16 @@ public ulong Eip4844TransitionTimestamp public bool IsEip5656Enabled => _spec.IsEip5656Enabled; public bool IsEip6780Enabled => _spec.IsEip6780Enabled; public bool IsEip4788Enabled => _spec.IsEip4788Enabled; + public bool IsEip4844FeeCollectorEnabled => _spec.IsEip4844FeeCollectorEnabled; public Address? Eip4788ContractAddress => _spec.Eip4788ContractAddress; public bool IsEip7002Enabled => _spec.IsEip7002Enabled; public Address Eip7002ContractAddress => _spec.Eip7002ContractAddress; + public bool IsEip7251Enabled => _spec.IsEip7251Enabled; + public Address Eip7251ContractAddress => _spec.Eip7251ContractAddress; public bool IsEip2935Enabled => _spec.IsEip2935Enabled; public bool IsEip7709Enabled => _spec.IsEip7709Enabled; public Address Eip2935ContractAddress => _spec.Eip2935ContractAddress; + public bool IsEip7702Enabled => _spec.IsEip7702Enabled; public UInt256 ForkBaseFee => _spec.ForkBaseFee; public UInt256 BaseFeeMaxChangeDenominator => _spec.BaseFeeMaxChangeDenominator; public long ElasticityMultiplier => _spec.ElasticityMultiplier; diff --git a/src/Nethermind/Nethermind.Specs.Test/OverridableSpecProvider.cs b/src/Nethermind/Nethermind.Specs.Test/OverridableSpecProvider.cs index fa2fbe346a2..9a3d5803207 100644 --- a/src/Nethermind/Nethermind.Specs.Test/OverridableSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs.Test/OverridableSpecProvider.cs @@ -35,7 +35,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public IReleaseSpec GetSpec(ForkActivation forkActivation) => _overrideAction(_specProvider.GetSpec(forkActivation)); public long? DaoBlockNumber => _specProvider.DaoBlockNumber; - + public ulong? BeaconChainGenesisTimestamp => _specProvider.BeaconChainGenesisTimestamp; public ulong NetworkId => _specProvider.NetworkId; public ulong ChainId => _specProvider.ChainId; public string SealEngine => _specProvider.SealEngine; diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs index 4fd2f92c584..4252264aab1 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs @@ -90,7 +90,7 @@ public class ChainParameters /// /// Optional, address where burnt EIP-1559 fees will go /// - public Address Eip1559FeeCollector { get; set; } + public Address FeeCollector { get; set; } /// /// Block from which EIP1559 base fee cannot drop below @@ -107,6 +107,7 @@ public class ChainParameters public long? TerminalPoWBlockNumber { get; set; } public UInt256? TerminalTotalDifficulty { get; set; } + public ulong? BeaconChainGenesisTimestamp { get; set; } public ulong? Eip3651TransitionTimestamp { get; set; } public ulong? Eip3855TransitionTimestamp { get; set; } public ulong? Eip3860TransitionTimestamp { get; set; } @@ -121,9 +122,12 @@ public class ChainParameters public Address DepositContractAddress { get; set; } public ulong? Eip7002TransitionTimestamp { get; set; } public Address Eip7002ContractAddress { get; set; } + public ulong? Eip7251TransitionTimestamp { get; set; } + public Address Eip7251ContractAddress { get; set; } public ulong? Eip2935TransitionTimestamp { get; set; } public Address Eip2935ContractAddress { get; set; } public ulong? Rip7212TransitionTimestamp { get; set; } + public ulong? Eip7702TransitionTimestamp { get; set; } public ulong? OpGraniteTransitionTimestamp { get; set; } #region EIP-4844 parameters @@ -150,5 +154,10 @@ public class ChainParameters /// EIP-4844. /// public ulong? Eip4844TargetBlobGasPerBlock { get; set; } + + /// + /// Enables blob gas fee collection for Gnosis chain + /// + public ulong? Eip4844FeeCollectorTransitionTimestamp { get; set; } #endregion } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs index f72ef942ed0..73f2e45726e 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs @@ -11,7 +11,7 @@ namespace Nethermind.Specs.ChainSpecStyle { /// /// https://github.com/ethereum/wiki/wiki/Ethereum-Chain-Spec-Format - /// https://wiki.parity.io/Chain-specification + /// https://openethereum.github.io/Chain-specification /// [DebuggerDisplay("{Name}, ChainId = {ChainId}")] public class ChainSpec diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs index a19948b5b58..bb7c9a7011e 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs @@ -207,7 +207,10 @@ private static ReleaseSpec CreateReleaseSpec(ChainSpec chainSpec, long releaseSt 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; + bool eip1559FeeCollector = releaseSpec.IsEip1559Enabled && (chainSpec.Parameters.Eip1559FeeCollectorTransition ?? long.MaxValue) <= releaseStartBlock; + bool eip4844FeeCollector = releaseSpec.IsEip4844Enabled && (chainSpec.Parameters.Eip4844FeeCollectorTransitionTimestamp ?? long.MaxValue) <= releaseStartTimestamp; + releaseSpec.FeeCollector = (eip1559FeeCollector || eip4844FeeCollector) ? chainSpec.Parameters.FeeCollector : null; + releaseSpec.Eip1559BaseFeeMinValue = releaseSpec.IsEip1559Enabled && (chainSpec.Parameters.Eip1559BaseFeeMinValueTransition ?? long.MaxValue) <= releaseStartBlock ? chainSpec.Parameters.Eip1559BaseFeeMinValue : null; releaseSpec.ElasticityMultiplier = chainSpec.Parameters.Eip1559ElasticityMultiplier ?? Eip1559Constants.DefaultElasticityMultiplier; releaseSpec.ForkBaseFee = chainSpec.Parameters.Eip1559BaseFeeInitialValue ?? Eip1559Constants.DefaultForkBaseFee; @@ -256,12 +259,17 @@ private static ReleaseSpec CreateReleaseSpec(ChainSpec chainSpec, long releaseSt releaseSpec.IsEip2935Enabled = (chainSpec.Parameters.Eip2935TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; releaseSpec.Eip2935ContractAddress = chainSpec.Parameters.Eip2935ContractAddress; + releaseSpec.IsEip7702Enabled = (chainSpec.Parameters.Eip7702TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; + releaseSpec.IsEip6110Enabled = (chainSpec.Parameters.Eip6110TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; releaseSpec.DepositContractAddress = chainSpec.Parameters.DepositContractAddress; releaseSpec.IsEip7002Enabled = (chainSpec.Parameters.Eip7002TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; releaseSpec.Eip7002ContractAddress = chainSpec.Parameters.Eip7002ContractAddress; + releaseSpec.IsEip7251Enabled = (chainSpec.Parameters.Eip7251TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; + releaseSpec.Eip7251ContractAddress = chainSpec.Parameters.Eip7251ContractAddress; + return releaseSpec; } @@ -284,6 +292,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public UInt256? TerminalTotalDifficulty { get; private set; } public long? DaoBlockNumber => _chainSpec.DaoForkBlockNumber; + public ulong? BeaconChainGenesisTimestamp => _chainSpec.Parameters.BeaconChainGenesisTimestamp; public ulong NetworkId => _chainSpec.NetworkId; public ulong ChainId => _chainSpec.ChainId; diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs index d56d62f80c5..1f45f5c83c3 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs @@ -133,6 +133,7 @@ bool GetForInnerPathExistence(KeyValuePair o) => Eip3541Transition = chainSpecJson.Params.Eip3541Transition, Eip3529Transition = chainSpecJson.Params.Eip3529Transition, Eip3607Transition = chainSpecJson.Params.Eip3607Transition, + BeaconChainGenesisTimestamp = chainSpecJson.Params.BeaconChainGenesisTimestamp, Eip1153TransitionTimestamp = chainSpecJson.Params.Eip1153TransitionTimestamp, Eip3651TransitionTimestamp = chainSpecJson.Params.Eip3651TransitionTimestamp, Eip3855TransitionTimestamp = chainSpecJson.Params.Eip3855TransitionTimestamp, @@ -145,6 +146,7 @@ bool GetForInnerPathExistence(KeyValuePair o) => Rip7212TransitionTimestamp = chainSpecJson.Params.Rip7212TransitionTimestamp, OpGraniteTransitionTimestamp = chainSpecJson.Params.OpGraniteTransitionTimestamp, Eip4788TransitionTimestamp = chainSpecJson.Params.Eip4788TransitionTimestamp, + Eip7702TransitionTimestamp = chainSpecJson.Params.Eip7702TransitionTimestamp, Eip4788ContractAddress = chainSpecJson.Params.Eip4788ContractAddress ?? Eip4788Constants.BeaconRootsAddress, Eip2935TransitionTimestamp = chainSpecJson.Params.Eip2935TransitionTimestamp, Eip2935ContractAddress = chainSpecJson.Params.Eip2935ContractAddress ?? Eip2935Constants.BlockHashHistoryAddress, @@ -161,7 +163,9 @@ bool GetForInnerPathExistence(KeyValuePair o) => DepositContractAddress = chainSpecJson.Params.DepositContractAddress ?? Eip6110Constants.MainnetDepositContractAddress, Eip7002TransitionTimestamp = chainSpecJson.Params.Eip7002TransitionTimestamp, Eip7002ContractAddress = chainSpecJson.Params.Eip7002ContractAddress ?? Eip7002Constants.WithdrawalRequestPredeployAddress, - Eip1559FeeCollector = chainSpecJson.Params.Eip1559FeeCollector, + Eip7251TransitionTimestamp = chainSpecJson.Params.Eip7251TransitionTimestamp, + Eip7251ContractAddress = chainSpecJson.Params.Eip7251ContractAddress ?? Eip7251Constants.ConsolidationRequestPredeployAddress, + FeeCollector = chainSpecJson.Params.FeeCollector, Eip1559FeeCollectorTransition = chainSpecJson.Params.Eip1559FeeCollectorTransition, Eip1559BaseFeeMinValueTransition = chainSpecJson.Params.Eip1559BaseFeeMinValueTransition, Eip1559BaseFeeMinValue = chainSpecJson.Params.Eip1559BaseFeeMinValue, @@ -169,6 +173,7 @@ bool GetForInnerPathExistence(KeyValuePair o) => Eip4844MaxBlobGasPerBlock = chainSpecJson.Params.Eip4844MaxBlobGasPerBlock, Eip4844MinBlobGasPrice = chainSpecJson.Params.Eip4844MinBlobGasPrice, Eip4844TargetBlobGasPerBlock = chainSpecJson.Params.Eip4844TargetBlobGasPerBlock, + Eip4844FeeCollectorTransitionTimestamp = chainSpecJson.Params.Eip4844FeeCollectorTransitionTimestamp, MergeForkIdTransition = chainSpecJson.Params.MergeForkIdTransition, TerminalTotalDifficulty = chainSpecJson.Params.TerminalTotalDifficulty, TerminalPoWBlockNumber = chainSpecJson.Params.TerminalPoWBlockNumber @@ -423,10 +428,11 @@ private static void LoadGenesis(ChainSpecJson chainSpecJson, ChainSpec chainSpec bool withdrawalsEnabled = chainSpecJson.Params.Eip4895TransitionTimestamp is not null && genesisHeader.Timestamp >= chainSpecJson.Params.Eip4895TransitionTimestamp; bool depositsEnabled = chainSpecJson.Params.Eip6110TransitionTimestamp is not null && genesisHeader.Timestamp >= chainSpecJson.Params.Eip6110TransitionTimestamp; bool withdrawalRequestsEnabled = chainSpecJson.Params.Eip7002TransitionTimestamp is not null && genesisHeader.Timestamp >= chainSpecJson.Params.Eip7002TransitionTimestamp; + bool consolidationRequestsEnabled = chainSpecJson.Params.Eip7251TransitionTimestamp is not null && genesisHeader.Timestamp >= chainSpecJson.Params.Eip7251TransitionTimestamp; if (withdrawalsEnabled) genesisHeader.WithdrawalsRoot = Keccak.EmptyTreeHash; - bool requestsEnabled = depositsEnabled || withdrawalRequestsEnabled; + var requestsEnabled = depositsEnabled || withdrawalRequestsEnabled || consolidationRequestsEnabled; if (requestsEnabled) genesisHeader.RequestsRoot = Keccak.EmptyTreeHash; ; diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs index 791c9f5d648..f905249c4e8 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs @@ -118,7 +118,7 @@ internal class ChainSpecParamsJson public long? Eip1559FeeCollectorTransition { get; set; } - public Address Eip1559FeeCollector { get; set; } + public Address FeeCollector { get; set; } public long? Eip1559BaseFeeMinValueTransition { get; set; } @@ -129,6 +129,7 @@ internal class ChainSpecParamsJson public UInt256? TerminalTotalDifficulty { get; set; } public long? TerminalPoWBlockNumber { get; set; } + public ulong? BeaconChainGenesisTimestamp { get; set; } public ulong? Eip1153TransitionTimestamp { get; set; } public ulong? Eip3651TransitionTimestamp { get; set; } @@ -147,10 +148,14 @@ internal class ChainSpecParamsJson public ulong? Eip4844MaxBlobGasPerBlock { get; set; } public UInt256? Eip4844MinBlobGasPrice { get; set; } public ulong? Eip4844TargetBlobGasPerBlock { get; set; } + public ulong? Eip4844FeeCollectorTransitionTimestamp { get; set; } public ulong? Eip6110TransitionTimestamp { get; set; } public Address DepositContractAddress { get; set; } public ulong? Eip7002TransitionTimestamp { get; set; } public Address Eip7002ContractAddress { get; set; } + public ulong? Eip7251TransitionTimestamp { get; set; } + public Address Eip7251ContractAddress { get; set; } public ulong? Rip7212TransitionTimestamp { get; set; } + public ulong? Eip7702TransitionTimestamp { get; set; } public ulong? OpGraniteTransitionTimestamp { get; set; } } diff --git a/src/Nethermind/Nethermind.Specs/ChiadoSpecProvider.cs b/src/Nethermind/Nethermind.Specs/ChiadoSpecProvider.cs index 023778e5096..de2f45dfa0f 100644 --- a/src/Nethermind/Nethermind.Specs/ChiadoSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/ChiadoSpecProvider.cs @@ -5,14 +5,17 @@ using Nethermind.Core.Specs; using Nethermind.Int256; using Nethermind.Specs.Forks; +using Nethermind.Specs.GnosisForks; namespace Nethermind.Specs; public class ChiadoSpecProvider : ISpecProvider { - public const ulong BeaconChainGenesisTimestamp = 0x6343ee4c; + public const ulong BeaconChainGenesisTimestampConst = 0x6343ee4c; public const ulong ShanghaiTimestamp = 0x646e0e4c; public const ulong CancunTimestamp = 0x65ba8e4c; + //TODO correct this timestamp! + public const ulong PragueTimestamp = ulong.MaxValue - 2; private ChiadoSpecProvider() { } @@ -22,7 +25,8 @@ private ChiadoSpecProvider() { } { null or < ShanghaiTimestamp => GenesisSpec, < CancunTimestamp => Shanghai.Instance, - _ => Cancun.Instance + < PragueTimestamp => Cancun.Instance, + _ => PragueGnosis.Instance } }; @@ -40,6 +44,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public UInt256? TerminalTotalDifficulty { get; private set; } = UInt256.Parse("231707791542740786049188744689299064356246512"); public IReleaseSpec GenesisSpec => London.Instance; public long? DaoBlockNumber => null; + public ulong? BeaconChainGenesisTimestamp => BeaconChainGenesisTimestampConst; public ulong NetworkId => BlockchainIds.Chiado; public ulong ChainId => BlockchainIds.Chiado; public string SealEngine => SealEngineType.AuRa; diff --git a/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs b/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs index 2aaffc45998..d456bde53be 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs @@ -28,6 +28,7 @@ protected Olympic() ValidateReceipts = true; // The below addresses are added for all forks, but the given EIPs can be enabled at a specific timestamp or block. + Eip7251ContractAddress = Eip7251Constants.ConsolidationRequestPredeployAddress; Eip7002ContractAddress = Eip7002Constants.WithdrawalRequestPredeployAddress; DepositContractAddress = Eip6110Constants.MainnetDepositContractAddress; } diff --git a/src/Nethermind/Nethermind.Specs/Forks/18_Prague.cs b/src/Nethermind/Nethermind.Specs/Forks/18_Prague.cs index d88d482ea47..7fd7e8c348d 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/18_Prague.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/18_Prague.cs @@ -16,8 +16,10 @@ protected Prague() Name = "Prague"; IsEip2537Enabled = true; IsEip2935Enabled = true; + IsEip7702Enabled = true; IsEip6110Enabled = true; IsEip7002Enabled = true; + IsEip7251Enabled = true; IsRip7212Enabled = true; Eip2935ContractAddress = Eip2935Constants.BlockHashHistoryAddress; } diff --git a/src/Nethermind/Nethermind.Specs/FrontierSpecProvider.cs b/src/Nethermind/Nethermind.Specs/FrontierSpecProvider.cs index 7d48cdb6292..f172aba8b0e 100644 --- a/src/Nethermind/Nethermind.Specs/FrontierSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/FrontierSpecProvider.cs @@ -28,6 +28,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public IReleaseSpec GetSpec(ForkActivation forkActivation) => Frontier.Instance; public long? DaoBlockNumber { get; } = null; + public ulong? BeaconChainGenesisTimestamp => null; public ulong NetworkId => Core.BlockchainIds.Mainnet; public ulong ChainId => NetworkId; diff --git a/src/Nethermind/Nethermind.Specs/GnosisForks/18_PragueGnosis.cs b/src/Nethermind/Nethermind.Specs/GnosisForks/18_PragueGnosis.cs new file mode 100644 index 00000000000..cae610ae0da --- /dev/null +++ b/src/Nethermind/Nethermind.Specs/GnosisForks/18_PragueGnosis.cs @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading; +using Nethermind.Core.Specs; + +namespace Nethermind.Specs.GnosisForks; + +public class PragueGnosis : Forks.Prague +{ + private static IReleaseSpec _instance; + + protected PragueGnosis() : base() + { + IsEip4844FeeCollectorEnabled = true; + } + + public new static IReleaseSpec Instance => LazyInitializer.EnsureInitialized(ref _instance, () => new PragueGnosis()); +} diff --git a/src/Nethermind/Nethermind.Specs/GnosisSpecProvider.cs b/src/Nethermind/Nethermind.Specs/GnosisSpecProvider.cs index 38ce4fd2552..e65dee2bf54 100644 --- a/src/Nethermind/Nethermind.Specs/GnosisSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/GnosisSpecProvider.cs @@ -5,6 +5,7 @@ using Nethermind.Core.Specs; using Nethermind.Int256; using Nethermind.Specs.Forks; +using Nethermind.Specs.GnosisForks; namespace Nethermind.Specs; @@ -15,9 +16,11 @@ public class GnosisSpecProvider : ISpecProvider public const long IstanbulBlockNumber = 7_298_030; public const long BerlinBlockNumber = 16_101_500; public const long LondonBlockNumber = 19_040_000; - public const ulong BeaconChainGenesisTimestamp = 0x61b10dbc; + public const ulong BeaconChainGenesisTimestampConst = 0x61b10dbc; public const ulong ShanghaiTimestamp = 0x64c8edbc; public const ulong CancunTimestamp = 0x65ef4dbc; + //TODO correct this timestamp! + public const ulong PragueTimestamp = ulong.MaxValue - 2; private GnosisSpecProvider() { } @@ -34,7 +37,8 @@ public IReleaseSpec GetSpec(ForkActivation forkActivation) { null or < ShanghaiTimestamp => London.Instance, < CancunTimestamp => Shanghai.Instance, - _ => Cancun.Instance + < PragueTimestamp => Cancun.Instance, + _ => PragueGnosis.Instance } }; } @@ -53,6 +57,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public UInt256? TerminalTotalDifficulty { get; private set; } = UInt256.Parse("8626000000000000000000058750000000000000000000"); public IReleaseSpec GenesisSpec => Byzantium.Instance; public long? DaoBlockNumber => null; + public ulong? BeaconChainGenesisTimestamp => BeaconChainGenesisTimestampConst; public ulong NetworkId => BlockchainIds.Gnosis; public ulong ChainId => BlockchainIds.Gnosis; public string SealEngine => SealEngineType.AuRa; diff --git a/src/Nethermind/Nethermind.Specs/GoerliSpecProvider.cs b/src/Nethermind/Nethermind.Specs/GoerliSpecProvider.cs index 4408c603aa5..c4179a1442c 100644 --- a/src/Nethermind/Nethermind.Specs/GoerliSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/GoerliSpecProvider.cs @@ -13,7 +13,7 @@ public class GoerliSpecProvider : ISpecProvider public const long IstanbulBlockNumber = 1_561_651; public const long BerlinBlockNumber = 4_460_644; public const long LondonBlockNumber = 5_062_605; - public const ulong BeaconChainGenesisTimestamp = 0x6059f460; + public const ulong BeaconChainGenesisTimestampConst = 0x6059f460; public const ulong ShanghaiTimestamp = 0x6410f460; public const ulong CancunTimestamp = 0x65A77460; @@ -47,6 +47,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public ulong ChainId => BlockchainIds.Goerli; public string SealEngine => SealEngineType.Clique; public long? DaoBlockNumber => null; + public ulong? BeaconChainGenesisTimestamp => BeaconChainGenesisTimestampConst; public ForkActivation? MergeBlockNumber { get; private set; } public ulong TimestampFork => ShanghaiTimestamp; public UInt256? TerminalTotalDifficulty { get; private set; } = 10790000; diff --git a/src/Nethermind/Nethermind.Specs/HoleskySpecProvider.cs b/src/Nethermind/Nethermind.Specs/HoleskySpecProvider.cs index 16730cb30f3..56680b6699b 100644 --- a/src/Nethermind/Nethermind.Specs/HoleskySpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/HoleskySpecProvider.cs @@ -37,6 +37,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public ulong NetworkId => BlockchainIds.Holesky; public ulong ChainId => NetworkId; public long? DaoBlockNumber => null; + public ulong? BeaconChainGenesisTimestamp => GenesisTimestamp; public ForkActivation? MergeBlockNumber { get; private set; } = (0, GenesisTimestamp); public ulong TimestampFork => ShanghaiTimestamp; public UInt256? TerminalTotalDifficulty { get; private set; } = 0; diff --git a/src/Nethermind/Nethermind.Specs/MainnetSpecProvider.cs b/src/Nethermind/Nethermind.Specs/MainnetSpecProvider.cs index 6470a8a26b7..4aa6c8d8c63 100644 --- a/src/Nethermind/Nethermind.Specs/MainnetSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/MainnetSpecProvider.cs @@ -23,7 +23,7 @@ public class MainnetSpecProvider : ISpecProvider public const long GrayGlacierBlockNumber = 15_050_000; public const long ParisBlockNumber = 15_537_393; public const ulong GenesisBlockTimestamp = 0x55ba4215; - public const ulong BeaconChainGenesisTimestamp = 0x5fc63057; + public const ulong BeaconChainGenesisTimestampConst = 0x5fc63057; public const ulong ShanghaiBlockTimestamp = 0x64373057; public const ulong CancunBlockTimestamp = 0x65F1B057; //TODO correct this timestamp! @@ -64,6 +64,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public ulong NetworkId { get; } = Core.BlockchainIds.Mainnet; public ulong ChainId => NetworkId; public long? DaoBlockNumber => DaoBlockNumberConst; + public ulong? BeaconChainGenesisTimestamp => BeaconChainGenesisTimestampConst; public ForkActivation? MergeBlockNumber { get; private set; } = null; public ulong TimestampFork { get; } = ShanghaiBlockTimestamp; public UInt256? TerminalTotalDifficulty { get; private set; } = UInt256.Parse("58750000000000000000000"); diff --git a/src/Nethermind/Nethermind.Specs/MordenSpecProvider.cs b/src/Nethermind/Nethermind.Specs/MordenSpecProvider.cs index 19a8c3b6f49..aa572aad154 100644 --- a/src/Nethermind/Nethermind.Specs/MordenSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/MordenSpecProvider.cs @@ -33,6 +33,7 @@ public IReleaseSpec GetSpec(ForkActivation forkActivation) => _ => SpuriousDragon.Instance }; public long? DaoBlockNumber => null; + public ulong? BeaconChainGenesisTimestamp => null; public ulong NetworkId => BlockchainIds.Morden; public ulong ChainId => NetworkId; diff --git a/src/Nethermind/Nethermind.Specs/OlympicSpecProvider.cs b/src/Nethermind/Nethermind.Specs/OlympicSpecProvider.cs index 54967e746c2..b3556fa01aa 100644 --- a/src/Nethermind/Nethermind.Specs/OlympicSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/OlympicSpecProvider.cs @@ -27,6 +27,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public IReleaseSpec GetSpec(ForkActivation forkActivation) => Olympic.Instance; public long? DaoBlockNumber => 0L; + public ulong? BeaconChainGenesisTimestamp => null; public ulong NetworkId => Core.BlockchainIds.Olympic; public ulong ChainId => NetworkId; diff --git a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs index de10ff6190b..f138b747d3b 100644 --- a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs @@ -70,7 +70,7 @@ public bool IsEip1559Enabled public long Eip1559TransitionBlock { get; set; } public ulong WithdrawalTimestamp { get; set; } public ulong Eip4844TransitionTimestamp { get; set; } - public Address Eip1559FeeCollector { get; set; } + public Address FeeCollector { get; set; } public UInt256? Eip1559BaseFeeMinValue { get; set; } public UInt256 ForkBaseFee { get; set; } = Eip1559Constants.DefaultForkBaseFee; public UInt256 BaseFeeMaxChangeDenominator { get; set; } = Eip1559Constants.DefaultBaseFeeMaxChangeDenominator; @@ -86,8 +86,17 @@ public bool IsEip1559Enabled public bool IsEip5656Enabled { get; set; } public bool IsEip6780Enabled { get; set; } public bool IsEip4788Enabled { get; set; } + public bool IsEip7702Enabled { get; set; } + public bool IsEip4844FeeCollectorEnabled { get; set; } public bool IsEip7002Enabled { get; set; } + public bool IsEip7251Enabled { get; set; } + private Address _eip7251ContractAddress; + public Address Eip7251ContractAddress + { + get => IsEip7251Enabled ? _eip7251ContractAddress : null; + set => _eip7251ContractAddress = value; + } private Address _eip7002ContractAddress; public Address Eip7002ContractAddress { @@ -121,7 +130,5 @@ public Address Eip2935ContractAddress get => IsEip2935Enabled ? _eip2935ContractAddress : null; set => _eip2935ContractAddress = value; } - - } } diff --git a/src/Nethermind/Nethermind.Specs/SepoliaSpecProvider.cs b/src/Nethermind/Nethermind.Specs/SepoliaSpecProvider.cs index 561ec1728c0..f5df258248e 100644 --- a/src/Nethermind/Nethermind.Specs/SepoliaSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/SepoliaSpecProvider.cs @@ -10,7 +10,7 @@ namespace Nethermind.Specs; public class SepoliaSpecProvider : ISpecProvider { - public const ulong BeaconChainGenesisTimestamp = 0x62b07d60; + public const ulong BeaconChainGenesisTimestampConst = 0x62b07d60; public const ulong ShanghaiTimestamp = 0x63fd7d60; public const ulong CancunTimestamp = 0x65B97D60; @@ -36,6 +36,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public ulong ChainId => NetworkId; public string SealEngine => SealEngineType.Clique; public long? DaoBlockNumber => null; + public ulong? BeaconChainGenesisTimestamp => BeaconChainGenesisTimestampConst; public ForkActivation? MergeBlockNumber { get; private set; } = null; public ulong TimestampFork => ISpecProvider.TimestampForkNever; public UInt256? TerminalTotalDifficulty { get; private set; } = 17000000000000000; diff --git a/src/Nethermind/Nethermind.Specs/SingleReleaseSpecProvider.cs b/src/Nethermind/Nethermind.Specs/SingleReleaseSpecProvider.cs index ff963a3f420..75fb5a087fd 100644 --- a/src/Nethermind/Nethermind.Specs/SingleReleaseSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/SingleReleaseSpecProvider.cs @@ -45,6 +45,7 @@ public SingleReleaseSpecProvider(IReleaseSpec releaseSpec, ulong networkId, ulon public IReleaseSpec GetSpec(ForkActivation forkActivation) => _releaseSpec; public long? DaoBlockNumber { get; } + public ulong? BeaconChainGenesisTimestamp { get; } public string SealEngine { get; set; } = SealEngineType.Ethash; } diff --git a/src/Nethermind/Nethermind.Specs/TestSpecProvider.cs b/src/Nethermind/Nethermind.Specs/TestSpecProvider.cs index 0ff4bca3f0b..1dff3bd8bcc 100644 --- a/src/Nethermind/Nethermind.Specs/TestSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/TestSpecProvider.cs @@ -36,6 +36,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public IReleaseSpec SpecToReturn { get; set; } public long? DaoBlockNumber { get; set; } + public ulong? BeaconChainGenesisTimestamp { get; set; } public ulong? _networkId; public ulong NetworkId { get { return _networkId ?? TestBlockchainIds.NetworkId; } set { _networkId = value; } } diff --git a/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs b/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs index f1e5d5eaad1..e47aab57922 100644 --- a/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StateProviderTests.cs @@ -182,7 +182,7 @@ public void Restore_in_the_middle() provider.CreateAccount(_address1, 1); provider.AddToBalance(_address1, 1, Frontier.Instance); provider.IncrementNonce(_address1); - provider.InsertCode(_address1, new byte[] { 1 }, Frontier.Instance); + provider.InsertCode(_address1, new byte[] { 1 }, Frontier.Instance, false); provider.UpdateStorageRoot(_address1, Hash2); Assert.That(provider.GetNonce(_address1), Is.EqualTo(UInt256.One)); diff --git a/src/Nethermind/Nethermind.State.Test/StateReaderTests.cs b/src/Nethermind/Nethermind.State.Test/StateReaderTests.cs index 5e56dd22f44..bedccef85ce 100644 --- a/src/Nethermind/Nethermind.State.Test/StateReaderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StateReaderTests.cs @@ -248,5 +248,114 @@ public void Can_collect_stats() var stats = stateReader.CollectStats(provider.StateRoot, new MemDb(), Logger); stats.AccountCount.Should().Be(1); } + + [Test] + public void IsInvalidContractSender_AccountHasCode_ReturnsTrue() + { + IReleaseSpec releaseSpec = Substitute.For(); + releaseSpec.IsEip3607Enabled.Returns(true); + releaseSpec.IsEip7702Enabled.Returns(true); + TrieStore trieStore = new TrieStore(new MemDb(), Logger); + WorldState sut = new(trieStore, new MemDb(), Logger); + sut.CreateAccount(TestItem.AddressA, 0); + sut.InsertCode(TestItem.AddressA, ValueKeccak.Compute(new byte[1]), new byte[1], releaseSpec, false); + sut.Commit(MuirGlacier.Instance); + sut.CommitTree(0); + + bool result = sut.IsInvalidContractSender(releaseSpec, TestItem.AddressA); + + Assert.That(result, Is.True); + } + + [Test] + public void IsInvalidContractSender_AccountHasNoCode_ReturnsFalse() + { + IReleaseSpec releaseSpec = Substitute.For(); + releaseSpec.IsEip3607Enabled.Returns(true); + releaseSpec.IsEip7702Enabled.Returns(true); + TrieStore trieStore = new TrieStore(new MemDb(), Logger); + WorldState sut = new(trieStore, new MemDb(), Logger); + sut.CreateAccount(TestItem.AddressA, 0); + sut.Commit(MuirGlacier.Instance); + sut.CommitTree(0); + + bool result = sut.IsInvalidContractSender(releaseSpec, TestItem.AddressA); + + Assert.That(result, Is.False); + } + + [Test] + public void IsInvalidContractSender_AccountHasDelegatedCode_ReturnsFalse() + { + IReleaseSpec releaseSpec = Substitute.For(); + releaseSpec.IsEip3607Enabled.Returns(true); + releaseSpec.IsEip7702Enabled.Returns(true); + TrieStore trieStore = new TrieStore(new MemDb(), Logger); + WorldState sut = new(trieStore, new MemDb(), Logger); + sut.CreateAccount(TestItem.AddressA, 0); + byte[] code = [.. Eip7702Constants.DelegationHeader, .. new byte[20]]; + sut.InsertCode(TestItem.AddressA, ValueKeccak.Compute(code), code, releaseSpec, false); + sut.Commit(MuirGlacier.Instance); + sut.CommitTree(0); + + bool result = sut.IsInvalidContractSender(releaseSpec, TestItem.AddressA); + + Assert.That(result, Is.False); + } + + [Test] + public void IsInvalidContractSender_AccountHasCodeButDelegateReturnsTrue_ReturnsFalse() + { + IReleaseSpec releaseSpec = Substitute.For(); + releaseSpec.IsEip3607Enabled.Returns(true); + releaseSpec.IsEip7702Enabled.Returns(true); + TrieStore trieStore = new TrieStore(new MemDb(), Logger); + WorldState sut = new(trieStore, new MemDb(), Logger); + sut.CreateAccount(TestItem.AddressA, 0); + byte[] code = new byte[20]; + sut.InsertCode(TestItem.AddressA, ValueKeccak.Compute(code), code, releaseSpec, false); + sut.Commit(MuirGlacier.Instance); + sut.CommitTree(0); + + bool result = sut.IsInvalidContractSender(releaseSpec, TestItem.AddressA, () => true); + + Assert.That(result, Is.False); + } + + [Test] + public void IsInvalidContractSender_AccountHasDelegatedCodeBut7702IsNotEnabled_ReturnsTrue() + { + IReleaseSpec releaseSpec = Substitute.For(); + releaseSpec.IsEip3607Enabled.Returns(true); + TrieStore trieStore = new TrieStore(new MemDb(), Logger); + WorldState sut = new(trieStore, new MemDb(), Logger); + sut.CreateAccount(TestItem.AddressA, 0); + byte[] code = [.. Eip7702Constants.DelegationHeader, .. new byte[20]]; + sut.InsertCode(TestItem.AddressA, ValueKeccak.Compute(code), code, releaseSpec, false); + sut.Commit(MuirGlacier.Instance); + sut.CommitTree(0); + + bool result = sut.IsInvalidContractSender(releaseSpec, TestItem.AddressA); + + Assert.That(result, Is.True); + } + + [Test] + public void IsInvalidContractSender_AccountHasDelegatedCodeBut3807IsNotEnabled_ReturnsFalse() + { + IReleaseSpec releaseSpec = Substitute.For(); + releaseSpec.IsEip7702Enabled.Returns(true); + TrieStore trieStore = new TrieStore(new MemDb(), Logger); + WorldState sut = new(trieStore, new MemDb(), Logger); + sut.CreateAccount(TestItem.AddressA, 0); + byte[] code = [.. Eip7702Constants.DelegationHeader, .. new byte[20]]; + sut.InsertCode(TestItem.AddressA, ValueKeccak.Compute(code), code, releaseSpec, false); + sut.Commit(MuirGlacier.Instance); + sut.CommitTree(0); + + bool result = sut.IsInvalidContractSender(releaseSpec, TestItem.AddressA); + + Assert.That(result, Is.False); + } } } diff --git a/src/Nethermind/Nethermind.State/IReadOnlyStateProviderExtensions.cs b/src/Nethermind/Nethermind.State/IReadOnlyStateProviderExtensions.cs new file mode 100644 index 00000000000..2fdf091a047 --- /dev/null +++ b/src/Nethermind/Nethermind.State/IReadOnlyStateProviderExtensions.cs @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Specs; +using System; + +namespace Nethermind.State +{ + public static class IReadOnlyStateProviderExtensions + { + public static byte[] GetCode(this IReadOnlyStateProvider stateProvider, Address address) + { + stateProvider.TryGetAccount(address, out AccountStruct account); + return !account.HasCode ? Array.Empty() : stateProvider.GetCode(account.CodeHash) ?? Array.Empty(); + } + /// + /// Checks if has code that is not a delegation, according to the rules of eip-3607 and eip-7702. + /// Where possible a cache for code lookup should be used, since the fallback will read from . + /// + /// + /// + /// + /// + /// + public static bool IsInvalidContractSender( + this IReadOnlyStateProvider stateProvider, + IReleaseSpec spec, + Address sender, + Func? isDelegatedCode = null) => + spec.IsEip3607Enabled + && stateProvider.HasCode(sender) + && (!spec.IsEip7702Enabled + || (!isDelegatedCode?.Invoke() ?? !Eip7702Constants.IsDelegatedCode(GetCode(stateProvider, sender)))); + } + +} diff --git a/src/Nethermind/Nethermind.State/IWorldState.cs b/src/Nethermind/Nethermind.State/IWorldState.cs index 21ac37a2a64..ac5bf705ab6 100644 --- a/src/Nethermind/Nethermind.State/IWorldState.cs +++ b/src/Nethermind/Nethermind.State/IWorldState.cs @@ -87,7 +87,7 @@ public interface IWorldState : IJournal, IReadOnlyStateProvider void CreateAccount(Address address, in UInt256 balance, in UInt256 nonce = default); void CreateAccountIfNotExists(Address address, in UInt256 balance, in UInt256 nonce = default); - void InsertCode(Address address, Hash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false); + void InsertCode(Address address, in ValueHash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false); void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec); diff --git a/src/Nethermind/Nethermind.State/IWorldStateExtensions.cs b/src/Nethermind/Nethermind.State/IWorldStateExtensions.cs index f6ffa4250d1..2cdeadddb5e 100644 --- a/src/Nethermind/Nethermind.State/IWorldStateExtensions.cs +++ b/src/Nethermind/Nethermind.State/IWorldStateExtensions.cs @@ -13,15 +13,9 @@ namespace Nethermind.State { public static class WorldStateExtensions { - public static byte[] GetCode(this IWorldState stateProvider, Address address) - { - stateProvider.TryGetAccount(address, out AccountStruct account); - return !account.HasCode ? Array.Empty() : stateProvider.GetCode(account.CodeHash) ?? Array.Empty(); - } - public static void InsertCode(this IWorldState worldState, Address address, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false) { - Hash256 codeHash = code.Length == 0 ? Keccak.OfAnEmptyString : Keccak.Compute(code.Span); + ValueHash256 codeHash = code.Length == 0 ? ValueKeccak.OfAnEmptyString : ValueKeccak.Compute(code.Span); worldState.InsertCode(address, codeHash, code, spec, isGenesis); } diff --git a/src/Nethermind/Nethermind.State/Proofs/RequestsTrie.cs b/src/Nethermind/Nethermind.State/Proofs/RequestsTrie.cs index f33d484ef48..d69e1d0c8d7 100644 --- a/src/Nethermind/Nethermind.State/Proofs/RequestsTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/RequestsTrie.cs @@ -14,7 +14,7 @@ namespace Nethermind.State.Proofs; public class RequestsTrie(ConsensusRequest[]? requests, bool canBuildProof = false, ICappedArrayPool? bufferPool = null) : PatriciaTrie(requests, canBuildProof, bufferPool) { - private static readonly ConsensusRequestDecoder _codec = new(); + private static readonly ConsensusRequestDecoder _codec = ConsensusRequestDecoder.Instance; protected override void Initialize(ConsensusRequest[] requests) { diff --git a/src/Nethermind/Nethermind.State/StateProvider.cs b/src/Nethermind/Nethermind.State/StateProvider.cs index 0afd1fdef0c..c4689da5d04 100644 --- a/src/Nethermind/Nethermind.State/StateProvider.cs +++ b/src/Nethermind/Nethermind.State/StateProvider.cs @@ -29,7 +29,7 @@ internal class StateProvider // Note: // False negatives are fine as they will just result in a overwrite set // False positives would be problematic as the code _must_ be persisted - private readonly ClockKeyCacheNonConcurrent _codeInsertFilter = new(1_024); + private readonly ClockKeyCacheNonConcurrent _codeInsertFilter = new(1_024); private readonly Dictionary _blockCache = new(4_096); private readonly ConcurrentDictionary? _preBlockCache; @@ -37,7 +37,7 @@ internal class StateProvider private readonly ILogger _logger; private readonly IKeyValueStore _codeDb; - private List _changes = new(Resettable.StartCapacity); + private readonly List _changes = new(Resettable.StartCapacity); internal readonly StateTree _tree; private readonly Func _getStateFromTrie; @@ -116,7 +116,7 @@ public UInt256 GetBalance(Address address) return account?.Balance ?? UInt256.Zero; } - public void InsertCode(Address address, Hash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false) + public void InsertCode(Address address, in ValueHash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false) { _needsStateRootUpdate = true; @@ -149,7 +149,7 @@ public void InsertCode(Address address, Hash256 codeHash, ReadOnlyMemory c throw new InvalidOperationException($"Account {address} is null when updating code hash"); } - if (account.CodeHash != codeHash) + if (account.CodeHash.ValueHash256 != codeHash) { if (_logger.IsDebug) _logger.Debug($" Update {address} C {account.CodeHash} -> {codeHash}"); Account changedAccount = account.WithChangedCodeHash(codeHash); diff --git a/src/Nethermind/Nethermind.State/WorldState.cs b/src/Nethermind/Nethermind.State/WorldState.cs index fa3753df79c..c18fb219381 100644 --- a/src/Nethermind/Nethermind.State/WorldState.cs +++ b/src/Nethermind/Nethermind.State/WorldState.cs @@ -143,7 +143,7 @@ public void CreateAccount(Address address, in UInt256 balance, in UInt256 nonce { _stateProvider.CreateAccount(address, balance, nonce); } - public void InsertCode(Address address, Hash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false) + public void InsertCode(Address address, in ValueHash256 codeHash, ReadOnlyMemory code, IReleaseSpec spec, bool isGenesis = false) { _stateProvider.InsertCode(address, codeHash, code, spec, isGenesis); } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs index e3540377184..95cde2d1a4e 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs @@ -702,8 +702,11 @@ public void GetNodeData_returns_cached_trie_nodes() Hash256 nodeKey = TestItem.KeccakA; TrieNode node = new(NodeType.Leaf, nodeKey, TestItem.KeccakB.Bytes); IScopedTrieStore scopedTrieStore = trieStore.GetTrieStore(null); - scopedTrieStore.CommitNode(1, new NodeCommitInfo(node, TreePath.Empty)); - scopedTrieStore.FinishBlockCommit(TrieType.State, 1, node); + using (ICommitter committer = scopedTrieStore.BeginCommit(TrieType.State, 1, node)) + { + TreePath path = TreePath.Empty; + committer.CommitNode(ref path, new NodeCommitInfo(node)); + } stateDb.KeyExists(nodeKey).Should().BeFalse(); ctx.SyncServer.GetNodeData(new[] { nodeKey }, CancellationToken.None, NodeDataType.All).Should().BeEquivalentTo(new[] { TestItem.KeccakB.BytesToArray() }); diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs index fb3f5927289..c86dfbb240b 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs @@ -269,15 +269,15 @@ private SyncTestContext CreateSyncManager(int index) ITransactionComparerProvider transactionComparerProvider = new TransactionComparerProvider(specProvider, tree); + CodeInfoRepository codeInfoRepository = new(); TxPool.TxPool txPool = new(ecdsa, new BlobTxStorage(), - new ChainHeadInfoProvider(specProvider, tree, stateReader), + new ChainHeadInfoProvider(specProvider, tree, stateReader, codeInfoRepository), new TxPoolConfig(), new TxValidator(specProvider.ChainId), logManager, transactionComparerProvider.GetDefaultComparer()); BlockhashProvider blockhashProvider = new(tree, specProvider, stateProvider, LimboLogs.Instance); - CodeInfoRepository codeInfoRepository = new(); VirtualMachine virtualMachine = new(blockhashProvider, specProvider, codeInfoRepository, logManager); Always sealValidator = Always.Valid; diff --git a/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloader.cs b/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloader.cs index 765c0f7e7a4..77f78aa7420 100644 --- a/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloader.cs +++ b/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloader.cs @@ -167,7 +167,7 @@ bool HasMoreToSync() } if (_logger.IsDebug) _logger.Debug($"Headers request {currentNumber}+{headersToRequest} to peer {bestPeer} with {bestPeer.HeadNumber} blocks. Got {currentNumber} and asking for {headersToRequest} more."); - Stopwatch sw = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); using IOwnedReadOnlyList headers = await RequestHeaders(bestPeer, cancellation, currentNumber, headersToRequest); Hash256? startHeaderHash = headers[0]?.Hash; @@ -188,7 +188,7 @@ bool HasMoreToSync() } ancestorLookupLevel = 0; - AdjustSyncBatchSize(sw.Elapsed); + AdjustSyncBatchSize(Stopwatch.GetElapsedTime(startTime)); for (int i = 1; i < headers.Count; i++) { diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/PendingSyncItems.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/PendingSyncItems.cs index 2cd79d10912..cfc36ce4509 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastSync/PendingSyncItems.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/PendingSyncItems.cs @@ -202,7 +202,7 @@ public List TakeBatch(int maxSize) [MethodImpl(MethodImplOptions.Synchronized)] public string RecalculatePriorities() { - Stopwatch stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); string reviewMessage = $"Node sync queues review ({LevelsDescription}):" + Environment.NewLine; reviewMessage += $" before {Description}" + Environment.NewLine; @@ -225,8 +225,7 @@ public string RecalculatePriorities() reviewMessage += $" after {Description}" + Environment.NewLine; - stopwatch.Stop(); - reviewMessage += $" time spent in review: {stopwatch.ElapsedMilliseconds}ms"; + reviewMessage += $" time spent in review: {Stopwatch.GetElapsedTime(startTime).TotalMilliseconds:N0}ms"; return reviewMessage; } } diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/TreeSync.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/TreeSync.cs index 4cc449cb47b..0c4b7b35568 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastSync/TreeSync.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/TreeSync.cs @@ -167,7 +167,7 @@ void AddAgainAllItems() if (_logger.IsDebug) _logger.Debug(reviewMessage); } - Stopwatch handleWatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); bool isMissingRequestData = batch.RequestedNodes is null; if (isMissingRequestData) @@ -289,24 +289,24 @@ shorter than the request */ _data.DisplayProgressReport(_pendingRequests.Count, _branchProgress, _logger); - handleWatch.Stop(); - long total = handleWatch.ElapsedMilliseconds + _networkWatch.ElapsedMilliseconds; + long elapsedTime = (long)Stopwatch.GetElapsedTime(startTime).TotalMilliseconds; + long total = elapsedTime + _networkWatch.ElapsedMilliseconds; if (total != 0) { // calculate averages if (_logger.IsTrace) _logger.Trace( - $"Prepare batch {_networkWatch.ElapsedMilliseconds}ms ({(decimal)_networkWatch.ElapsedMilliseconds / total:P0}) - Handle {handleWatch.ElapsedMilliseconds}ms ({(decimal)handleWatch.ElapsedMilliseconds / total:P0})"); + $"Prepare batch {_networkWatch.ElapsedMilliseconds}ms ({(decimal)_networkWatch.ElapsedMilliseconds / total:P0}) - Handle {elapsedTime:N0}ms ({(decimal)elapsedTime / total:P0})"); } - if (handleWatch.ElapsedMilliseconds > 250) + if (Stopwatch.GetElapsedTime(startTime).TotalMilliseconds > 250) { if (_logger.IsDebug) _logger.Debug( - $"Handle watch {handleWatch.ElapsedMilliseconds}, DB reads {_data.DbChecks - _data.LastDbReads}, ratio {(decimal)handleWatch.ElapsedMilliseconds / Math.Max(1, _data.DbChecks - _data.LastDbReads)}"); + $"Handle watch {elapsedTime:N0}, DB reads {_data.DbChecks - _data.LastDbReads}, ratio {(decimal)elapsedTime / Math.Max(1, _data.DbChecks - _data.LastDbReads)}"); } - Interlocked.Add(ref _handleWatch, handleWatch.ElapsedMilliseconds); + Interlocked.Add(ref _handleWatch, (long)Stopwatch.GetElapsedTime(startTime).TotalMilliseconds); _data.LastDbReads = _data.DbChecks; _data.AverageTimeInHandler = _handleWatch / (decimal)_data.ProcessedRequestsCount; diff --git a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs index d127da5f076..7d78cc8443f 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs @@ -70,7 +70,11 @@ public void Memory_with_one_node_is_288() using TrieStore fullTrieStore = CreateTrieStore(pruningStrategy: new TestPruningStrategy(true)); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); - trieStore.CommitNode(1234, new NodeCommitInfo(trieNode, TreePath.Empty)); + TreePath emptyPath = TreePath.Empty; + using (ICommitter? committer = trieStore.BeginCommit(TrieType.State, 1234, null)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode)); + } fullTrieStore.MemoryUsedByDirtyCache.Should().Be( trieNode.GetMemorySize(false) + ExpectedPerNodeKeyMemorySize); } @@ -85,10 +89,16 @@ public void Pruning_off_cache_should_not_change_commit_node() using TrieStore fullTrieStore = CreateTrieStore(); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); - trieStore.CommitNode(1234, new NodeCommitInfo(trieNode, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, 1234, trieNode); - trieStore.CommitNode(124, new NodeCommitInfo(trieNode2, TreePath.Empty)); - trieStore.CommitNode(11234, new NodeCommitInfo(trieNode3, TreePath.Empty)); + TreePath emptyPath = TreePath.Empty; + using (ICommitter? committer = trieStore.BeginCommit(TrieType.State, 1234, trieNode)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode)); + } + using (ICommitter? committer = trieStore.BeginCommit(TrieType.State, 1235, trieNode)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode2)); + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode3)); + } fullTrieStore.MemoryUsedByDirtyCache.Should().Be(0); } @@ -101,8 +111,12 @@ public void When_commit_forward_write_flag_if_available() using TrieStore fullTrieStore = CreateTrieStore(kvStore: testMemDb); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); - trieStore.CommitNode(1234, new NodeCommitInfo(trieNode, TreePath.Empty), WriteFlags.LowPriority); - trieStore.FinishBlockCommit(TrieType.State, 1234, trieNode, WriteFlags.LowPriority); + + TreePath emptyPath = TreePath.Empty; + using (ICommitter? committer = trieStore.BeginCommit(TrieType.State, 1234, trieNode, WriteFlags.LowPriority)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode)); + } if (_scheme == INodeStorage.KeyScheme.HalfPath) { @@ -123,13 +137,13 @@ public void Should_always_announce_block_number_when_pruning_disabled_and_persis using TrieStore fullTrieStore = CreateTrieStore(persistenceStrategy: Archive.Instance); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); fullTrieStore.ReorgBoundaryReached += (_, e) => reorgBoundaryCount += e.BlockNumber; - trieStore.FinishBlockCommit(TrieType.State, 1, trieNode); + trieStore.BeginCommit(TrieType.State, 1, trieNode).Dispose(); reorgBoundaryCount.Should().Be(0); - trieStore.FinishBlockCommit(TrieType.State, 2, trieNode); + trieStore.BeginCommit(TrieType.State, 2, trieNode).Dispose(); reorgBoundaryCount.Should().Be(1); - trieStore.FinishBlockCommit(TrieType.State, 3, trieNode); + trieStore.BeginCommit(TrieType.State, 3, trieNode).Dispose(); reorgBoundaryCount.Should().Be(3); - trieStore.FinishBlockCommit(TrieType.State, 4, trieNode); + trieStore.BeginCommit(TrieType.State, 4, trieNode).Dispose(); reorgBoundaryCount.Should().Be(6); } @@ -142,10 +156,10 @@ public void Should_always_announce_zero_when_not_persisting() using TrieStore fullTrieStore = CreateTrieStore(); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); fullTrieStore.ReorgBoundaryReached += (_, e) => reorgBoundaryCount += e.BlockNumber; - trieStore.FinishBlockCommit(TrieType.State, 1, trieNode); - trieStore.FinishBlockCommit(TrieType.State, 2, trieNode); - trieStore.FinishBlockCommit(TrieType.State, 3, trieNode); - trieStore.FinishBlockCommit(TrieType.State, 4, trieNode); + trieStore.BeginCommit(TrieType.State, 1, trieNode).Dispose(); + trieStore.BeginCommit(TrieType.State, 2, trieNode).Dispose(); + trieStore.BeginCommit(TrieType.State, 3, trieNode).Dispose(); + trieStore.BeginCommit(TrieType.State, 4, trieNode).Dispose(); reorgBoundaryCount.Should().Be(0L); } @@ -189,8 +203,12 @@ public void Memory_with_two_nodes_is_correct() using TrieStore fullTrieStore = CreateTrieStore(pruningStrategy: new TestPruningStrategy(true)); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); - trieStore.CommitNode(1234, new NodeCommitInfo(trieNode1, TreePath.Empty)); - trieStore.CommitNode(1234, new NodeCommitInfo(trieNode2, TreePath.Empty)); + TreePath emptyPath = TreePath.Empty; + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, 1234, null)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode1)); + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode2)); + } fullTrieStore.MemoryUsedByDirtyCache.Should().Be( trieNode1.GetMemorySize(false) + ExpectedPerNodeKeyMemorySize + trieNode2.GetMemorySize(false) + ExpectedPerNodeKeyMemorySize); @@ -206,11 +224,18 @@ public void Memory_with_two_times_two_nodes_is_correct() using TrieStore fullTrieStore = CreateTrieStore(pruningStrategy: new TestPruningStrategy(true)); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); - trieStore.CommitNode(1234, new NodeCommitInfo(trieNode1, TreePath.Empty)); - trieStore.CommitNode(1234, new NodeCommitInfo(trieNode2, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, 1234, trieNode2); - trieStore.CommitNode(1235, new NodeCommitInfo(trieNode3, TreePath.Empty)); - trieStore.CommitNode(1235, new NodeCommitInfo(trieNode4, TreePath.Empty)); + TreePath emptyPath = TreePath.Empty; + using (ICommitter? committer = trieStore.BeginCommit(TrieType.State, 1234, trieNode2)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode1)); + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode2)); + } + + using (ICommitter? committer = trieStore.BeginCommit(TrieType.State, 1235, trieNode2)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode3)); + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode4)); + } // depending on whether the node gets resolved it gives different values here in debugging and run // needs some attention @@ -236,13 +261,21 @@ public void Dispatcher_will_try_to_clear_memory() using TrieStore fullTrieStore = CreateTrieStore(pruningStrategy: new MemoryLimit(640)); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); - trieStore.CommitNode(1234, new NodeCommitInfo(trieNode1, TreePath.Empty)); - trieStore.CommitNode(1234, new NodeCommitInfo(trieNode2, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, 1234, trieNode2); - trieStore.CommitNode(1235, new NodeCommitInfo(trieNode3, TreePath.Empty)); - trieStore.CommitNode(1235, new NodeCommitInfo(trieNode4, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, 1235, trieNode2); - trieStore.FinishBlockCommit(TrieType.State, 1236, trieNode2); + + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, 1234, trieNode2)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode1)); + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode2)); + } + + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, 1235, trieNode2)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode3)); + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode4)); + } + + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 1236, trieNode2)) { } + fullTrieStore.MemoryUsedByDirtyCache.Should().Be( trieNode1.GetMemorySize(false) + ExpectedPerNodeKeyMemorySize + trieNode2.GetMemorySize(false) + ExpectedPerNodeKeyMemorySize + @@ -267,11 +300,19 @@ public void Dispatcher_will_try_to_clear_memory_the_soonest_possible() using TrieStore fullTrieStore = CreateTrieStore(pruningStrategy: new MemoryLimit(512)); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); - trieStore.CommitNode(1234, new NodeCommitInfo(trieNode1, TreePath.Empty)); - trieStore.CommitNode(1234, new NodeCommitInfo(trieNode2, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, 1234, trieNode2); - trieStore.CommitNode(1235, new NodeCommitInfo(trieNode3, TreePath.Empty)); - trieStore.CommitNode(1235, new NodeCommitInfo(trieNode4, TreePath.Empty)); + + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, 1234, trieNode2)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode1)); + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode2)); + } + + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, 1235, trieNode2)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode3)); + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode4)); + } + fullTrieStore.MemoryUsedByDirtyCache.Should().Be( trieNode1.GetMemorySize(false) + ExpectedPerNodeKeyMemorySize + trieNode2.GetMemorySize(false) + ExpectedPerNodeKeyMemorySize + @@ -287,16 +328,17 @@ public void Dispatcher_will_always_try_to_clear_memory() TreePath emptyPath = TreePath.Empty; for (int i = 0; i < 1024; i++) { - for (int j = 0; j < 1 + i % 3; j++) - { - TrieNode trieNode = new(NodeType.Leaf, new byte[0]); // 192B - trieNode.ResolveKey(NullTrieNodeResolver.Instance, ref emptyPath, true); - trieStore.CommitNode(i, new NodeCommitInfo(trieNode, TreePath.Empty)); - } - TrieNode fakeRoot = new(NodeType.Leaf, new byte[0]); // 192B fakeRoot.ResolveKey(NullTrieNodeResolver.Instance, ref emptyPath, true); - trieStore.FinishBlockCommit(TrieType.State, i, fakeRoot); + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, i, fakeRoot)) + { + for (int j = 0; j < 1 + i % 3; j++) + { + TrieNode trieNode = new(NodeType.Leaf, new byte[0]); // 192B + trieNode.ResolveKey(NullTrieNodeResolver.Instance, ref emptyPath, true); + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode)); + } + } } fullTrieStore.MemoryUsedByDirtyCache.Should().BeLessThan(512 * 2); @@ -318,12 +360,14 @@ public void Dispatcher_will_save_to_db_everything_from_snapshot_blocks() persistenceStrategy: new ConstantInterval(4)); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); - trieStore.CommitNode(0, new NodeCommitInfo(a, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, 0, a); - trieStore.FinishBlockCommit(TrieType.State, 1, a); - trieStore.FinishBlockCommit(TrieType.State, 2, a); - trieStore.FinishBlockCommit(TrieType.State, 3, a); - trieStore.FinishBlockCommit(TrieType.State, 4, a); + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, 0, a)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(a)); + } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 1, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 2, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 3, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 4, a)) { } storage.Get(null, TreePath.Empty, a.Keccak).Should().NotBeNull(); fullTrieStore.IsNodeCached(null, TreePath.Empty, a.Keccak).Should().BeTrue(); @@ -342,11 +386,13 @@ public void Stays_in_memory_until_persisted() using TrieStore fullTrieStore = CreateTrieStore(pruningStrategy: new MemoryLimit(16.MB())); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); - trieStore.CommitNode(0, new NodeCommitInfo(a, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, 0, a); - trieStore.FinishBlockCommit(TrieType.State, 1, a); - trieStore.FinishBlockCommit(TrieType.State, 2, a); - trieStore.FinishBlockCommit(TrieType.State, 3, a); + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, 0, a)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(a)); + } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 1, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 2, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 3, a)) { } // <- do not persist in this test storage.Get(null, TreePath.Empty, a.Keccak).Should().BeNull(); @@ -381,16 +427,18 @@ public void Will_get_persisted_on_snapshot_if_referenced() ); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); - trieStore.FinishBlockCommit(TrieType.State, 0, null); - trieStore.CommitNode(1, new NodeCommitInfo(a, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, 1, a); - trieStore.FinishBlockCommit(TrieType.State, 2, a); - trieStore.FinishBlockCommit(TrieType.State, 3, a); - trieStore.FinishBlockCommit(TrieType.State, 4, a); - trieStore.FinishBlockCommit(TrieType.State, 5, a); - trieStore.FinishBlockCommit(TrieType.State, 6, a); - trieStore.FinishBlockCommit(TrieType.State, 7, a); - trieStore.FinishBlockCommit(TrieType.State, 8, a); + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 0, null)) { } + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, 1, a)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(a)); + } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 2, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 3, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 4, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 5, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 6, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 7, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 8, a)) { } storage.Get(null, TreePath.Empty, a.Keccak).Should().NotBeNull(); fullTrieStore.IsNodeCached(null, TreePath.Empty, a.Keccak).Should().BeTrue(); @@ -416,17 +464,21 @@ public void Will_not_get_dropped_on_snapshot_if_unreferenced_in_later_blocks() IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); - trieStore.FinishBlockCommit(TrieType.State, 0, null); - trieStore.CommitNode(1, new NodeCommitInfo(a, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, 1, a); - trieStore.FinishBlockCommit(TrieType.State, 2, a); - trieStore.FinishBlockCommit(TrieType.State, 3, a); - trieStore.FinishBlockCommit(TrieType.State, 4, a); - trieStore.FinishBlockCommit(TrieType.State, 5, a); - trieStore.FinishBlockCommit(TrieType.State, 6, a); - trieStore.CommitNode(7, new NodeCommitInfo(b, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, 7, b); - trieStore.FinishBlockCommit(TrieType.State, 8, b); + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 0, null)) { } + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, 1, a)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(a)); + } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 2, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 3, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 4, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 5, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 6, a)) { } + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, 7, a)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(b)); + } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 8, a)) { } nodeStorage.Get(null, TreePath.Empty, a.Keccak).Should().NotBeNull(); fullTrieStore.IsNodeCached(null, TreePath.Empty, a.Keccak).Should().BeTrue(); @@ -451,17 +503,21 @@ public void Will_get_dropped_on_snapshot_if_it_was_a_transient_node() IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); - trieStore.FinishBlockCommit(TrieType.State, 0, null); - trieStore.CommitNode(1, new NodeCommitInfo(a, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, 1, a); - trieStore.FinishBlockCommit(TrieType.State, 2, a); - trieStore.CommitNode(3, new NodeCommitInfo(b, TreePath.Empty)); // <- new root - trieStore.FinishBlockCommit(TrieType.State, 3, b); - trieStore.FinishBlockCommit(TrieType.State, 4, b); // should be 'a' to test properly - trieStore.FinishBlockCommit(TrieType.State, 5, b); // should be 'a' to test properly - trieStore.FinishBlockCommit(TrieType.State, 6, b); // should be 'a' to test properly - trieStore.FinishBlockCommit(TrieType.State, 7, b); // should be 'a' to test properly - trieStore.FinishBlockCommit(TrieType.State, 8, b); // should be 'a' to test properly + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 0, null)) { } + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, 1, a)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(a)); + } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 2, a)) { } + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, 3, a)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(b)); // <- new root + } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 4, b)) { } // should be 'a' to test properly + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 5, b)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 6, b)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 7, b)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 8, b)) { } memDb[a.Keccak!.Bytes].Should().BeNull(); fullTrieStore.IsNodeCached(null, TreePath.Empty, a.Keccak).Should().BeTrue(); @@ -544,21 +600,24 @@ public void Will_store_storage_on_snapshot() persistenceStrategy: new ConstantInterval(4)); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); - trieStore.FinishBlockCommit(TrieType.State, 0, null); - trieStore.CommitNode(1, new NodeCommitInfo(a, TreePath.Empty)); - fullTrieStore.GetTrieStore(TestItem.KeccakA) - .CommitNode(1, new NodeCommitInfo(storage1, TreePath.Empty)); - fullTrieStore.GetTrieStore(TestItem.KeccakA) - .FinishBlockCommit(TrieType.Storage, 1, storage1); - trieStore.FinishBlockCommit(TrieType.Storage, 1, storage1); - trieStore.FinishBlockCommit(TrieType.State, 1, a); - trieStore.FinishBlockCommit(TrieType.State, 2, a); - trieStore.FinishBlockCommit(TrieType.State, 3, a); - trieStore.FinishBlockCommit(TrieType.State, 4, a); - trieStore.FinishBlockCommit(TrieType.State, 5, a); - trieStore.FinishBlockCommit(TrieType.State, 6, a); - trieStore.FinishBlockCommit(TrieType.State, 7, a); - trieStore.FinishBlockCommit(TrieType.State, 8, a); + + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 0, null)) { } + using (ICommitter committer = fullTrieStore.GetTrieStore(TestItem.KeccakA).BeginCommit(TrieType.Storage, 1, storage1)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(storage1)); + } + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, 1, a)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(a)); + } + + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 2, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 3, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 4, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 5, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 6, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 7, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 8, a)) { } asStorage.Get(null, TreePath.Empty, a.Keccak).Should().NotBeNull(); asStorage.Get(TestItem.KeccakA, TreePath.Empty, storage1.Keccak).Should().NotBeNull(); @@ -591,19 +650,27 @@ public void Will_drop_transient_storage() IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); - trieStore.FinishBlockCommit(TrieType.State, 0, null); - trieStore.CommitNode(1, new NodeCommitInfo(a, TreePath.Empty)); - trieStore.CommitNode(1, new NodeCommitInfo(storage1, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.Storage, 1, storage1); - trieStore.FinishBlockCommit(TrieType.State, 1, a); - trieStore.FinishBlockCommit(TrieType.State, 2, a); - trieStore.CommitNode(3, new NodeCommitInfo(b, TreePath.Empty)); // <- new root - trieStore.FinishBlockCommit(TrieType.State, 3, b); - trieStore.FinishBlockCommit(TrieType.State, 4, b); // should be 'a' to test properly - trieStore.FinishBlockCommit(TrieType.State, 5, b); // should be 'a' to test properly - trieStore.FinishBlockCommit(TrieType.State, 6, b); // should be 'a' to test properly - trieStore.FinishBlockCommit(TrieType.State, 7, b); // should be 'a' to test properly - trieStore.FinishBlockCommit(TrieType.State, 8, b); // should be 'a' to test properly + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 0, null)) { } + + using (ICommitter committer = trieStore.BeginCommit(TrieType.Storage, 1, storage1)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(a)); + committer.CommitNode(ref emptyPath, new NodeCommitInfo(storage1)); + } + + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 1, a)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 2, a)) { } + + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, 2, b)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(b)); // <- new root + } + + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 4, b)) { } // Should be 'a' to test properly + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 5, b)) { } // Should be 'a' to test properly + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 6, b)) { } // Should be 'a' to test properly + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 7, b)) { } // Should be 'a' to test properly + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 8, b)) { } // Should be 'a' to test properly memDb[a.Keccak!.Bytes].Should().BeNull(); memDb[storage1.Keccak!.Bytes].Should().BeNull(); @@ -653,27 +720,32 @@ public void Will_combine_same_storage() IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); - trieStore.FinishBlockCommit(TrieType.State, 0, null); + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 0, null)) { } + + using (ICommitter committer = fullTrieStore.GetTrieStore(new Hash256(Nibbles.ToBytes(storage1Nib))).BeginCommit(TrieType.Storage, 1, storage1)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(storage1)); + } - IScopedTrieStore storageTrieStore = fullTrieStore.GetTrieStore(new Hash256(Nibbles.ToBytes(storage1Nib))); - storageTrieStore.CommitNode(1, new NodeCommitInfo(storage1, TreePath.Empty)); - storageTrieStore.FinishBlockCommit(TrieType.Storage, 1, storage1); + using (ICommitter committer = fullTrieStore.GetTrieStore(new Hash256(Nibbles.ToBytes(storage2Nib))).BeginCommit(TrieType.Storage, 1, storage2)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(storage2)); + } - storageTrieStore = fullTrieStore.GetTrieStore(new Hash256(Nibbles.ToBytes(storage2Nib))); - storageTrieStore.CommitNode(1, new NodeCommitInfo(storage2, TreePath.Empty)); - storageTrieStore.FinishBlockCommit(TrieType.Storage, 1, storage2); + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, 1, branch)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(a)); + committer.CommitNode(ref emptyPath, new NodeCommitInfo(b)); + committer.CommitNode(ref emptyPath, new NodeCommitInfo(branch)); + } - trieStore.CommitNode(1, new NodeCommitInfo(a, TreePath.Empty)); - trieStore.CommitNode(1, new NodeCommitInfo(b, TreePath.Empty)); - trieStore.CommitNode(1, new NodeCommitInfo(branch, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, 1, branch); - trieStore.FinishBlockCommit(TrieType.State, 2, branch); - trieStore.FinishBlockCommit(TrieType.State, 3, branch); - trieStore.FinishBlockCommit(TrieType.State, 4, branch); - trieStore.FinishBlockCommit(TrieType.State, 5, branch); - trieStore.FinishBlockCommit(TrieType.State, 6, branch); - trieStore.FinishBlockCommit(TrieType.State, 7, branch); - trieStore.FinishBlockCommit(TrieType.State, 8, branch); + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 2, branch)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 3, branch)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 4, branch)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 5, branch)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 6, branch)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 7, branch)) { } + using (ICommitter _ = trieStore.BeginCommit(TrieType.State, 8, branch)) { } storage.Get(null, TreePath.FromNibble(new byte[] { 0 }), a.Keccak).Should().NotBeNull(); storage.Get(new Hash256(Nibbles.ToBytes(storage1Nib)), TreePath.Empty, storage1.Keccak).Should().NotBeNull(); @@ -702,7 +774,10 @@ public async Task Read_only_trie_store_is_allowing_many_thread_to_work_with_the_ IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); TreePath emptyPath = TreePath.Empty; trieNode.ResolveKey(trieStore, ref emptyPath, false); - trieStore.CommitNode(1, new NodeCommitInfo(trieNode, TreePath.Empty)); + using (ICommitter? committer = trieStore.BeginCommit(TrieType.State, 0, trieNode)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(trieNode)); + } if (beThreadSafe) { @@ -755,8 +830,12 @@ public void ReadOnly_store_returns_copies(bool pruning) using TrieStore fullTrieStore = CreateTrieStore(pruningStrategy: new TestPruningStrategy(pruning)); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); - trieStore.CommitNode(0, new NodeCommitInfo(node, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, 0, node); + + using (ICommitter? committer = trieStore.BeginCommit(TrieType.State, 0, node)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(node)); + } + var originalNode = trieStore.FindCachedOrUnknown(TreePath.Empty, node.Keccak); IReadOnlyTrieStore readOnlyTrieStore = fullTrieStore.AsReadOnly(); @@ -774,7 +853,7 @@ public void ReadOnly_store_returns_copies(bool pruning) readOnlyNode.Key?.ToString().Should().Be(originalNode.Key?.ToString()); } - private long ExpectedPerNodeKeyMemorySize => _scheme == INodeStorage.KeyScheme.Hash ? 0 : TrieStore.DirtyNodesCache.Key.MemoryUsage; + private long ExpectedPerNodeKeyMemorySize => _scheme == INodeStorage.KeyScheme.Hash ? 0 : TrieStoreDirtyNodesCache.Key.MemoryUsage; [Test] public void After_commit_should_have_has_root() @@ -807,12 +886,15 @@ public async Task Will_RemovePastKeys_OnSnapshot() persistenceStrategy: No.Persistence); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); + TreePath emptyPath = TreePath.Empty; for (int i = 0; i < 64; i++) { TrieNode node = new(NodeType.Leaf, TestItem.Keccaks[i], new byte[2]); - trieStore.CommitNode(i, new NodeCommitInfo(node, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, i, node); + using (ICommitter? committer = trieStore.BeginCommit(TrieType.State, i, node)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(node)); + } // Pruning is done in background await Task.Delay(TimeSpan.FromMilliseconds(10)); @@ -842,12 +924,15 @@ public async Task Will_Trigger_ReorgBoundaryEvent_On_Prune() fullTrieStore.ReorgBoundaryReached += (sender, reached) => reorgBoundary = reached.BlockNumber; IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); + TreePath emptyPath = TreePath.Empty; for (int i = 0; i < 64; i++) { TrieNode node = new(NodeType.Leaf, TestItem.Keccaks[i], new byte[2]); - trieStore.CommitNode(i, new NodeCommitInfo(node, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, i, node); + using (ICommitter? committer = trieStore.BeginCommit(TrieType.State, i, node)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(node)); + } if (i > 4) { @@ -875,12 +960,15 @@ public async Task Will_Not_RemovePastKeys_OnSnapshot_DuringFullPruning() persistenceStrategy: isPruningPersistenceStrategy); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); + TreePath emptyPath = TreePath.Empty; for (int i = 0; i < 64; i++) { TrieNode node = new(NodeType.Leaf, TestItem.Keccaks[i], new byte[2]); - trieStore.CommitNode(i, new NodeCommitInfo(node, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, i, node); + using (ICommitter? committer = trieStore.BeginCommit(TrieType.State, i, node)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(node)); + } // Pruning is done in background await Task.Delay(TimeSpan.FromMilliseconds(10)); @@ -902,13 +990,15 @@ public async Task Will_NotRemove_ReCommittedNode() persistenceStrategy: No.Persistence); IScopedTrieStore trieStore = fullTrieStore.GetTrieStore(null); + TreePath emptyPath = TreePath.Empty; for (int i = 0; i < 64; i++) { TrieNode node = new(NodeType.Leaf, TestItem.Keccaks[i % 4], new byte[2]); - trieStore.CommitNode(i, new NodeCommitInfo(node, TreePath.Empty)); - node = trieStore.FindCachedOrUnknown(TreePath.Empty, node.Keccak); - trieStore.FinishBlockCommit(TrieType.State, i, node); + using (ICommitter committer = trieStore.BeginCommit(TrieType.State, i, node)) + { + committer.CommitNode(ref emptyPath, new NodeCommitInfo(node)); + } // Pruning is done in background await Task.Delay(TimeSpan.FromMilliseconds(10)); diff --git a/src/Nethermind/Nethermind.Trie.Test/TrieNodeTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrieNodeTests.cs index 6ee564db88a..001be11d7d7 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrieNodeTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrieNodeTests.cs @@ -933,15 +933,20 @@ public void Rlp_is_cloned_when_cloning() TreePath emptyPath = TreePath.Empty; leaf1.ResolveKey(trieStore, ref emptyPath, false); leaf1.Seal(); - trieStore.CommitNode(0, new NodeCommitInfo(leaf1, TreePath.Empty)); TrieNode leaf2 = new(NodeType.Leaf); leaf2.Key = Bytes.FromHexString("abd"); leaf2.Value = new byte[222]; leaf2.ResolveKey(trieStore, ref emptyPath, false); leaf2.Seal(); - trieStore.CommitNode(0, new NodeCommitInfo(leaf2, TreePath.Empty)); - trieStore.FinishBlockCommit(TrieType.State, 0, leaf2); + + TreePath path = TreePath.Empty; + + using (ICommitter? committer = trieStore.BeginCommit(TrieType.State, 0, leaf2)) + { + committer.CommitNode(ref path, new NodeCommitInfo(leaf1)); + committer.CommitNode(ref path, new NodeCommitInfo(leaf2)); + } TrieNode trieNode = new(NodeType.Branch); trieNode.SetChild(1, leaf1); diff --git a/src/Nethermind/Nethermind.Trie/BatchedTrieVisitor.cs b/src/Nethermind/Nethermind.Trie/BatchedTrieVisitor.cs index ef0706a09cf..99bff648354 100644 --- a/src/Nethermind/Nethermind.Trie/BatchedTrieVisitor.cs +++ b/src/Nethermind/Nethermind.Trie/BatchedTrieVisitor.cs @@ -142,11 +142,12 @@ public void Start( try { + // TODO: .Net 9 stackalloc Task[]? tasks = Enumerable.Range(0, trieVisitContext.MaxDegreeOfParallelism) - .Select((_) => Task.Run(BatchedThread)) + .Select(_ => Task.Run(BatchedThread)) .ToArray(); - Task.WhenAll(tasks).Wait(); + Task.WaitAll(tasks); } catch (Exception) { diff --git a/src/Nethermind/Nethermind.Trie/CachedTrieStore.cs b/src/Nethermind/Nethermind.Trie/CachedTrieStore.cs index d9a1769c5aa..5b23ba6ad92 100644 --- a/src/Nethermind/Nethermind.Trie/CachedTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/CachedTrieStore.cs @@ -20,46 +20,25 @@ public class CachedTrieStore(IScopedTrieStore @base) : IScopedTrieStore { private readonly NonBlocking.ConcurrentDictionary<(TreePath path, Hash256 hash), TrieNode> _cachedNode = new(); - public TrieNode FindCachedOrUnknown(in TreePath path, Hash256 hash) - { - return _cachedNode.GetOrAdd((path, hash), (key) => @base.FindCachedOrUnknown(key.path, key.hash)); - } + public TrieNode FindCachedOrUnknown(in TreePath path, Hash256 hash) => + _cachedNode.GetOrAdd((path, hash), (key) => @base.FindCachedOrUnknown(key.path, key.hash)); - public byte[]? LoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) - { - return @base.LoadRlp(in path, hash, flags); - } + public byte[]? LoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => + @base.LoadRlp(in path, hash, flags); - public byte[]? TryLoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) - { - return @base.TryLoadRlp(in path, hash, flags); - } + public byte[]? TryLoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => + @base.TryLoadRlp(in path, hash, flags); - public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256? address) - { + public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256? address) => throw new InvalidOperationException("unsupported"); - } public INodeStorage.KeyScheme Scheme => @base.Scheme; - public void CommitNode(long blockNumber, NodeCommitInfo nodeCommitInfo, WriteFlags writeFlags = WriteFlags.None) - { - @base.CommitNode(blockNumber, nodeCommitInfo, writeFlags); - } + public ICommitter BeginCommit(TrieType trieType, long blockNumber, TrieNode? root, WriteFlags writeFlags = WriteFlags.None) => + @base.BeginCommit(trieType, blockNumber, root, writeFlags); - public void FinishBlockCommit(TrieType trieType, long blockNumber, TrieNode? root, WriteFlags writeFlags = WriteFlags.None) - { - @base.FinishBlockCommit(trieType, blockNumber, root, writeFlags); - } + public bool IsPersisted(in TreePath path, in ValueHash256 keccak) => @base.IsPersisted(in path, in keccak); - public bool IsPersisted(in TreePath path, in ValueHash256 keccak) - { - return @base.IsPersisted(in path, in keccak); - } - - public void Set(in TreePath path, in ValueHash256 keccak, byte[] rlp) - { - @base.Set(in path, in keccak, rlp); - } + public void Set(in TreePath path, in ValueHash256 keccak, byte[] rlp) => @base.Set(in path, in keccak, rlp); } diff --git a/src/Nethermind/Nethermind.Trie/NodeCommitInfo.cs b/src/Nethermind/Nethermind.Trie/NodeCommitInfo.cs index bf85db2709d..265d81631fb 100644 --- a/src/Nethermind/Nethermind.Trie/NodeCommitInfo.cs +++ b/src/Nethermind/Nethermind.Trie/NodeCommitInfo.cs @@ -1,37 +1,35 @@ +using System.Diagnostics.CodeAnalysis; + namespace Nethermind.Trie { public readonly struct NodeCommitInfo { public NodeCommitInfo( - TrieNode node, - in TreePath path + TrieNode node ) { ChildPositionAtParent = 0; Node = node; - Path = path; NodeParent = null; } public NodeCommitInfo( TrieNode node, TrieNode nodeParent, - in TreePath path, int childPositionAtParent) { ChildPositionAtParent = childPositionAtParent; Node = node; - Path = path; NodeParent = nodeParent; } public TrieNode? Node { get; } - public readonly TreePath Path; public TrieNode? NodeParent { get; } public int ChildPositionAtParent { get; } + [MemberNotNullWhen(false, nameof(Node))] public bool IsEmptyBlockMarker => Node is null; public bool IsRoot => !IsEmptyBlockMarker && NodeParent is null; diff --git a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs index 3ab8c9d1127..88b443a3983 100644 --- a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs +++ b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs @@ -8,13 +8,14 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; using Nethermind.Core; using Nethermind.Core.Buffers; -using Nethermind.Core.Cpu; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Logging; @@ -39,10 +40,6 @@ public class PatriciaTree public TrieType TrieType { get; init; } private Stack? _nodeStack; - - private ConcurrentQueue? _commitExceptions; - private ConcurrentQueue? _currentCommit; - public IScopedTrieStore TrieStore { get; } public ICappedArrayPool? _bufferPool; @@ -56,6 +53,9 @@ public class PatriciaTree public TrieNode? RootRef { get; set; } + // Used to estimate if parallelization is needed during commit + private long _writeBeforeCommit = 0; + /// /// Only used in EthereumTests /// @@ -138,37 +138,30 @@ public void Commit(long blockNumber, bool skipRoot = false, WriteFlags writeFlag ThrowReadOnlyTrieException(); } - if (RootRef is not null && RootRef.IsDirty) + int maxLevelForConcurrentCommit = _writeBeforeCommit switch { - Commit(new NodeCommitInfo(RootRef, TreePath.Empty), skipSelf: skipRoot); - while (TryDequeueCommit(out NodeCommitInfo node)) - { - if (_logger.IsTrace) Trace(blockNumber, node); - TrieStore.CommitNode(blockNumber, node, writeFlags: writeFlags); - } - - // reset objects - TreePath path = TreePath.Empty; - RootRef!.ResolveKey(TrieStore, ref path, true, bufferPool: _bufferPool); - SetRootHash(RootRef.Keccak!, true); - } - - TrieStore.FinishBlockCommit(TrieType, blockNumber, RootRef, writeFlags); + > 64 * 16 => 1, // we separate at two top levels + > 64 => 0, // we separate at top level + _ => -1 + }; - if (_logger.IsDebug) Debug(blockNumber); + _writeBeforeCommit = 0; - bool TryDequeueCommit(out NodeCommitInfo value) + using (ICommitter committer = TrieStore.BeginCommit(TrieType, blockNumber, RootRef, writeFlags)) { - Unsafe.SkipInit(out value); - return _currentCommit?.TryDequeue(out value) ?? false; - } + if (RootRef is not null && RootRef.IsDirty) + { + TreePath path = TreePath.Empty; + Commit(committer, ref path, new NodeCommitInfo(RootRef), skipSelf: skipRoot, maxLevelForConcurrentCommit: maxLevelForConcurrentCommit); - [MethodImpl(MethodImplOptions.NoInlining)] - void Trace(long blockNumber, in NodeCommitInfo node) - { - _logger.Trace($"Committing {node} in {blockNumber}"); + // reset objects + RootRef!.ResolveKey(TrieStore, ref path, true, bufferPool: _bufferPool); + SetRootHash(RootRef.Keccak!, true); + } } + if (_logger.IsDebug) Debug(blockNumber); + [MethodImpl(MethodImplOptions.NoInlining)] void Debug(long blockNumber) { @@ -176,7 +169,7 @@ void Debug(long blockNumber) } } - private void Commit(NodeCommitInfo nodeCommitInfo, bool skipSelf = false) + private void Commit(ICommitter committer, ref TreePath path, NodeCommitInfo nodeCommitInfo, int maxLevelForConcurrentCommit, bool skipSelf = false) { if (!_allowCommits) { @@ -184,18 +177,18 @@ private void Commit(NodeCommitInfo nodeCommitInfo, bool skipSelf = false) } TrieNode node = nodeCommitInfo.Node; - TreePath path = nodeCommitInfo.Path; if (node!.IsBranch) { - // idea from EthereumJ - testing parallel branches - if (!_parallelBranches || !nodeCommitInfo.IsRoot) + if (path.Length > maxLevelForConcurrentCommit) { for (int i = 0; i < 16; i++) { if (node.IsChildDirty(i)) { - TreePath childPath = node.GetChildPath(nodeCommitInfo.Path, i); - Commit(new NodeCommitInfo(node.GetChildWithChildPath(TrieStore, ref childPath, i)!, node, childPath, i)); + path.AppendMut(i); + TrieNode childNode = node.GetChildWithChildPath(TrieStore, ref path, i); + Commit(committer, ref path, new NodeCommitInfo(childNode!, node, i), maxLevelForConcurrentCommit); + path.TruncateOne(); } else { @@ -208,13 +201,33 @@ private void Commit(NodeCommitInfo nodeCommitInfo, bool skipSelf = false) } else { - List nodesToCommit = new(16); + Task CreateTaskForPath(TreePath childPath, TrieNode childNode, int idx) => Task.Run(() => + { + Commit(committer, ref childPath, new NodeCommitInfo(childNode!, node, idx), maxLevelForConcurrentCommit); + committer.ReturnConcurrencyQuota(); + }); + + // TODO: .Net 9 stackalloc + ArrayPoolList? childTasks = null; + for (int i = 0; i < 16; i++) { if (node.IsChildDirty(i)) { - TreePath childPath = node.GetChildPath(nodeCommitInfo.Path, i); - nodesToCommit.Add(new NodeCommitInfo(node.GetChildWithChildPath(TrieStore, ref childPath, i)!, node, childPath, i)); + if (i < 15 && committer.CanSpawnTask()) + { + childTasks ??= new ArrayPoolList(15); + TreePath childPath = path.Append(i); + TrieNode childNode = node.GetChildWithChildPath(TrieStore, ref childPath, i); + childTasks.Add(CreateTaskForPath(childPath, childNode, i)); + } + else + { + path.AppendMut(i); + TrieNode childNode = node.GetChildWithChildPath(TrieStore, ref path, i); + Commit(committer, ref path, new NodeCommitInfo(childNode!, node, i), maxLevelForConcurrentCommit); + path.TruncateOne(); + } } else { @@ -225,39 +238,17 @@ private void Commit(NodeCommitInfo nodeCommitInfo, bool skipSelf = false) } } - if (nodesToCommit.Count >= 4) - { - ClearExceptions(); - Parallel.For(0, nodesToCommit.Count, RuntimeInformation.ParallelOptionsLogicalCores, i => - { - try - { - Commit(nodesToCommit[i]); - } - catch (Exception e) - { - AddException(e); - } - }); - - if (WereExceptions()) - { - ThrowAggregateExceptions(); - } - } - else + if (childTasks is not null) { - for (int i = 0; i < nodesToCommit.Count; i++) - { - Commit(nodesToCommit[i]); - } + Task.WaitAll(childTasks.ToArray()); + childTasks.Dispose(); } } } else if (node.NodeType == NodeType.Extension) { - TreePath childPath = node.GetChildPath(nodeCommitInfo.Path, 0); - TrieNode extensionChild = node.GetChildWithChildPath(TrieStore, ref childPath, 0); + int previousPathLength = node.AppendChildPath(ref path, 0); + TrieNode extensionChild = node.GetChildWithChildPath(TrieStore, ref path, 0); if (extensionChild is null) { ThrowInvalidExtension(); @@ -265,12 +256,13 @@ private void Commit(NodeCommitInfo nodeCommitInfo, bool skipSelf = false) if (extensionChild.IsDirty) { - Commit(new NodeCommitInfo(extensionChild, node, childPath, 0)); + Commit(committer, ref path, new NodeCommitInfo(extensionChild, node, 0), maxLevelForConcurrentCommit); } else { if (_logger.IsTrace) TraceExtensionSkip(extensionChild); } + path.TruncateMut(previousPathLength); } node.ResolveKey(TrieStore, ref path, nodeCommitInfo.IsRoot, bufferPool: _bufferPool); @@ -280,7 +272,7 @@ private void Commit(NodeCommitInfo nodeCommitInfo, bool skipSelf = false) { if (!skipSelf) { - EnqueueCommit(nodeCommitInfo); + committer.CommitNode(ref path, nodeCommitInfo); } } else @@ -288,37 +280,6 @@ private void Commit(NodeCommitInfo nodeCommitInfo, bool skipSelf = false) if (_logger.IsTrace) TraceSkipInlineNode(node); } - void EnqueueCommit(in NodeCommitInfo value) - { - ConcurrentQueue queue = Volatile.Read(ref _currentCommit); - // Allocate queue if first commit made - queue ??= CreateQueue(ref _currentCommit); - queue.Enqueue(value); - } - - void ClearExceptions() => _commitExceptions?.Clear(); - bool WereExceptions() => _commitExceptions?.IsEmpty == false; - - void AddException(Exception value) - { - ConcurrentQueue queue = Volatile.Read(ref _commitExceptions); - // Allocate queue if first exception thrown - queue ??= CreateQueue(ref _commitExceptions); - queue.Enqueue(value); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - ConcurrentQueue CreateQueue(ref ConcurrentQueue queueRef) - { - ConcurrentQueue queue = new(); - ConcurrentQueue current = Interlocked.CompareExchange(ref queueRef, queue, null); - return (current is null) ? queue : current; - } - - [DoesNotReturn] - [StackTraceHidden] - void ThrowAggregateExceptions() => throw new AggregateException(_commitExceptions); - [DoesNotReturn] [StackTraceHidden] static void ThrowInvalidExtension() => throw new InvalidOperationException("An attempt to store an extension without a child."); @@ -493,6 +454,8 @@ public virtual void Set(ReadOnlySpan rawKey, in CappedArray value) ThrowNonConcurrentWrites(); } + _writeBeforeCommit++; + try { int nibblesCount = 2 * rawKey.Length; diff --git a/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs b/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs index efb9d242748..82d83a3e8f3 100644 --- a/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs @@ -34,14 +34,9 @@ public void Dispose() _inner.Dispose(); } - public void CommitNode(long blockNumber, Hash256? address, in NodeCommitInfo nodeCommitInfo, WriteFlags writeFlags = WriteFlags.None) + public ICommitter BeginCommit(TrieType trieType, long blockNumber, Hash256? address, TrieNode? root, WriteFlags writeFlags) { - _inner.CommitNode(blockNumber, address, in nodeCommitInfo, writeFlags); - } - - public void FinishBlockCommit(TrieType trieType, long blockNumber, Hash256? address, TrieNode? root, WriteFlags writeFlags = WriteFlags.None) - { - _inner.FinishBlockCommit(trieType, blockNumber, address, root, writeFlags); + return _inner.BeginCommit(trieType, blockNumber, address, root, writeFlags); } public bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 keccak) diff --git a/src/Nethermind/Nethermind.Trie/Pruning/HashAndTinyPath.cs b/src/Nethermind/Nethermind.Trie/Pruning/HashAndTinyPath.cs new file mode 100644 index 00000000000..09138b330be --- /dev/null +++ b/src/Nethermind/Nethermind.Trie/Pruning/HashAndTinyPath.cs @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Runtime.InteropServices; +using Nethermind.Core.Crypto; + +namespace Nethermind.Trie.Pruning; + +[StructLayout(LayoutKind.Auto)] +internal readonly struct HashAndTinyPath : IEquatable +{ + public readonly ValueHash256 addr; + public readonly TinyTreePath path; + + public HashAndTinyPath(Hash256? hash, in TinyTreePath path) + { + addr = hash ?? default; + this.path = path; + } + public HashAndTinyPath(in ValueHash256 hash, in TinyTreePath path) + { + addr = hash; + this.path = path; + } + + public bool Equals(HashAndTinyPath other) => addr == other.addr && path.Equals(in other.path); + public override bool Equals(object? obj) => obj is HashAndTinyPath other && Equals(other); + public override int GetHashCode() + { + var addressHash = addr != default ? addr.GetHashCode() : 1; + return path.GetHashCode() ^ addressHash; + } +} diff --git a/src/Nethermind/Nethermind.Trie/Pruning/HashAndTinyPathAndHash.cs b/src/Nethermind/Nethermind.Trie/Pruning/HashAndTinyPathAndHash.cs new file mode 100644 index 00000000000..56f3c2baff0 --- /dev/null +++ b/src/Nethermind/Nethermind.Trie/Pruning/HashAndTinyPathAndHash.cs @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Runtime.InteropServices; +using Nethermind.Core.Crypto; + +namespace Nethermind.Trie.Pruning; + +[StructLayout(LayoutKind.Auto)] +internal readonly struct HashAndTinyPathAndHash : IEquatable +{ + public readonly ValueHash256 hash; + public readonly TinyTreePath path; + public readonly ValueHash256 valueHash; + + public HashAndTinyPathAndHash(Hash256? hash, in TinyTreePath path, in ValueHash256 valueHash) + { + this.hash = hash ?? default; + this.path = path; + this.valueHash = valueHash; + } + public HashAndTinyPathAndHash(in ValueHash256 hash, in TinyTreePath path, in ValueHash256 valueHash) + { + this.hash = hash; + this.path = path; + this.valueHash = valueHash; + } + + public bool Equals(HashAndTinyPathAndHash other) => hash == other.hash && path.Equals(in other.path) && valueHash.Equals(in other.valueHash); + public override bool Equals(object? obj) => obj is HashAndTinyPath other && Equals(other); + public override int GetHashCode() + { + var hashHash = hash != default ? hash.GetHashCode() : 1; + return valueHash.GetChainedHashCode((uint)path.GetHashCode()) ^ hashHash; + } +} diff --git a/src/Nethermind/Nethermind.Trie/Pruning/IScopedTrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/IScopedTrieStore.cs index f8d2384fd0e..3dec5d49e25 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/IScopedTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/IScopedTrieStore.cs @@ -13,11 +13,7 @@ namespace Nethermind.Trie.Pruning; /// public interface IScopedTrieStore : ITrieNodeResolver { - // TODO: Commit and FinishBlockCommit is unnecessary. Geth just compile the changes and return it in a batch, - // which get committed in a single call. - void CommitNode(long blockNumber, NodeCommitInfo nodeCommitInfo, WriteFlags writeFlags = WriteFlags.None); - - void FinishBlockCommit(TrieType trieType, long blockNumber, TrieNode? root, WriteFlags writeFlags = WriteFlags.None); + ICommitter BeginCommit(TrieType trieType, long blockNumber, TrieNode? root, WriteFlags writeFlags = WriteFlags.None); // Only used by snap provider, so ValueHash instead of Hash bool IsPersisted(in TreePath path, in ValueHash256 keccak); @@ -25,3 +21,11 @@ public interface IScopedTrieStore : ITrieNodeResolver // Used for trie node recovery void Set(in TreePath path, in ValueHash256 keccak, byte[] rlp); } + +public interface ICommitter : IDisposable +{ + void CommitNode(ref TreePath path, NodeCommitInfo nodeCommitInfo); + + bool CanSpawnTask() => false; + void ReturnConcurrencyQuota() { } +} diff --git a/src/Nethermind/Nethermind.Trie/Pruning/ITrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/ITrieStore.cs index a7ec0699ec1..f9f2f59d55e 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/ITrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/ITrieStore.cs @@ -13,10 +13,6 @@ namespace Nethermind.Trie.Pruning /// public interface ITrieStore : IDisposable { - void CommitNode(long blockNumber, Hash256? address, in NodeCommitInfo nodeCommitInfo, WriteFlags writeFlags = WriteFlags.None); - - void FinishBlockCommit(TrieType trieType, long blockNumber, Hash256? address, TrieNode? root, WriteFlags writeFlags = WriteFlags.None); - bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 keccak); IReadOnlyTrieStore AsReadOnly(INodeStorage? keyValueStore = null); @@ -37,6 +33,7 @@ public interface ITrieStore : IDisposable byte[]? LoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None); byte[]? TryLoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None); INodeStorage.KeyScheme Scheme { get; } + ICommitter BeginCommit(TrieType trieType, long blockNumber, Hash256? address, TrieNode? root, WriteFlags writeFlags); } public interface IPruningTrieStore diff --git a/src/Nethermind/Nethermind.Trie/Pruning/NullTrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/NullTrieStore.cs index 4982d7166b8..585050f7150 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/NullTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/NullTrieStore.cs @@ -15,27 +15,27 @@ private NullTrieStore() { } public static NullTrieStore Instance { get; } = new(); - public void CommitNode(long blockNumber, NodeCommitInfo nodeCommitInfo, WriteFlags flags = WriteFlags.None) { } - - public void FinishBlockCommit(TrieType trieType, long blockNumber, TrieNode? root, WriteFlags flags = WriteFlags.None) { } - public TrieNode FindCachedOrUnknown(in TreePath treePath, Hash256 hash) => new(NodeType.Unknown, hash); - public byte[] LoadRlp(in TreePath treePath, Hash256 hash, ReadFlags flags = ReadFlags.None) => Array.Empty(); + public byte[] LoadRlp(in TreePath treePath, Hash256 hash, ReadFlags flags = ReadFlags.None) => []; + + public byte[]? TryLoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => []; - public byte[]? TryLoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => Array.Empty(); + public ICommitter BeginCommit(TrieType trieType, long blockNumber, TrieNode? root, WriteFlags writeFlags = WriteFlags.None) => new NullCommitter(); public bool IsPersisted(in TreePath path, in ValueHash256 keccak) => true; - public void Set(in TreePath path, in ValueHash256 keccak, byte[] rlp) - { - } + public void Set(in TreePath path, in ValueHash256 keccak, byte[] rlp) { } - public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256 storageRoot) - { - return this; - } + public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256 storageRoot) => this; public INodeStorage.KeyScheme Scheme => INodeStorage.KeyScheme.HalfPath; + + internal class NullCommitter : ICommitter + { + public void Dispose() { } + + public void CommitNode(ref TreePath path, NodeCommitInfo nodeCommitInfo) { } + } } } diff --git a/src/Nethermind/Nethermind.Trie/Pruning/ReadOnlyTrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/ReadOnlyTrieStore.cs index 0ae9f2160e9..4bb2c8d537f 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/ReadOnlyTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/ReadOnlyTrieStore.cs @@ -12,36 +12,25 @@ namespace Nethermind.Trie.Pruning /// /// Safe to be reused for the same wrapped store. /// - public class ReadOnlyTrieStore : IReadOnlyTrieStore + public class ReadOnlyTrieStore(TrieStore trieStore, INodeStorage? readOnlyStore) : IReadOnlyTrieStore { - private readonly TrieStore _trieStore; - private readonly INodeStorage? _readOnlyStore; + private readonly TrieStore _trieStore = trieStore ?? throw new ArgumentNullException(nameof(trieStore)); public INodeStorage.KeyScheme Scheme => _trieStore.Scheme; - public ReadOnlyTrieStore(TrieStore trieStore, INodeStorage? readOnlyStore) - { - _trieStore = trieStore ?? throw new ArgumentNullException(nameof(trieStore)); - _readOnlyStore = readOnlyStore; - } - public TrieNode FindCachedOrUnknown(Hash256? address, in TreePath treePath, Hash256 hash) => _trieStore.FindCachedOrUnknown(address, treePath, hash, true); public byte[] LoadRlp(Hash256? address, in TreePath treePath, Hash256 hash, ReadFlags flags) => - _trieStore.LoadRlp(address, treePath, hash, _readOnlyStore, flags); + _trieStore.LoadRlp(address, treePath, hash, readOnlyStore, flags); public byte[]? TryLoadRlp(Hash256? address, in TreePath treePath, Hash256 hash, ReadFlags flags) => - _trieStore.TryLoadRlp(address, treePath, hash, _readOnlyStore, flags); + _trieStore.TryLoadRlp(address, treePath, hash, readOnlyStore, flags); public bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 keccak) => _trieStore.IsPersisted(address, path, keccak); - public IReadOnlyTrieStore AsReadOnly(INodeStorage nodeStore) - { - return new ReadOnlyTrieStore(_trieStore, nodeStore); - } - - public void CommitNode(long blockNumber, Hash256? address, in NodeCommitInfo nodeCommitInfo, WriteFlags flags = WriteFlags.None) { } + public IReadOnlyTrieStore AsReadOnly(INodeStorage nodeStore) => new ReadOnlyTrieStore(_trieStore, nodeStore); - public void FinishBlockCommit(TrieType trieType, long blockNumber, Hash256? address, TrieNode? root, WriteFlags flags = WriteFlags.None) { } + public ICommitter BeginCommit(TrieType trieType, long blockNumber, Hash256? address, TrieNode? root, WriteFlags writeFlags) => + new NullTrieStore.NullCommitter(); public event EventHandler ReorgBoundaryReached { @@ -51,78 +40,36 @@ public event EventHandler ReorgBoundaryReached public IReadOnlyKeyValueStore TrieNodeRlpStore => _trieStore.TrieNodeRlpStore; - public void Set(Hash256? address, in TreePath path, in ValueHash256 keccak, byte[] rlp) - { - } + public void Set(Hash256? address, in TreePath path, in ValueHash256 keccak, byte[] rlp) { } - public IScopedTrieStore GetTrieStore(Hash256? address) - { - return new ScopedReadOnlyTrieStore(this, address); - } + public IScopedTrieStore GetTrieStore(Hash256? address) => new ScopedReadOnlyTrieStore(this, address); + public bool HasRoot(Hash256 stateRoot) => _trieStore.HasRoot(stateRoot); - public void PersistCache(CancellationToken cancellationToken) - { - _trieStore.PersistCache(cancellationToken); - } + public void Dispose() { } - public bool HasRoot(Hash256 stateRoot) + private class ScopedReadOnlyTrieStore(ReadOnlyTrieStore fullTrieStore, Hash256? address) : IScopedTrieStore { - return _trieStore.HasRoot(stateRoot); - } + public TrieNode FindCachedOrUnknown(in TreePath path, Hash256 hash) => + fullTrieStore.FindCachedOrUnknown(address, path, hash); - public void Dispose() { } + public byte[]? LoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => + fullTrieStore.LoadRlp(address, path, hash, flags); - private class ScopedReadOnlyTrieStore : IScopedTrieStore - { - private readonly ReadOnlyTrieStore _trieStoreImplementation; - private readonly Hash256? _address; - - public ScopedReadOnlyTrieStore(ReadOnlyTrieStore fullTrieStore, Hash256? address) - { - _trieStoreImplementation = fullTrieStore; - _address = address; - } - - public TrieNode FindCachedOrUnknown(in TreePath path, Hash256 hash) - { - return _trieStoreImplementation.FindCachedOrUnknown(_address, path, hash); - } - - public byte[]? LoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) - { - return _trieStoreImplementation.LoadRlp(_address, path, hash, flags); - } - - public byte[]? TryLoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) - { - return _trieStoreImplementation.TryLoadRlp(_address, path, hash, flags); - } - - public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256? address) - { - if (address == _address) return this; - return new ScopedReadOnlyTrieStore(_trieStoreImplementation, address); - } - - public INodeStorage.KeyScheme Scheme => _trieStoreImplementation.Scheme; - - public void CommitNode(long blockNumber, NodeCommitInfo nodeCommitInfo, WriteFlags writeFlags = WriteFlags.None) - { - } - - public void FinishBlockCommit(TrieType trieType, long blockNumber, TrieNode? root, WriteFlags writeFlags = WriteFlags.None) - { - } - - public bool IsPersisted(in TreePath path, in ValueHash256 keccak) - { - return _trieStoreImplementation.IsPersisted(_address, path, in keccak); - } - - public void Set(in TreePath path, in ValueHash256 keccak, byte[] rlp) - { - } + public byte[]? TryLoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => fullTrieStore.TryLoadRlp(address, path, hash, flags); + + public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256? address1) => + address1 == address ? this : new ScopedReadOnlyTrieStore(fullTrieStore, address1); + + public INodeStorage.KeyScheme Scheme => fullTrieStore.Scheme; + + public ICommitter BeginCommit(TrieType trieType, long blockNumber, TrieNode? root, WriteFlags writeFlags = WriteFlags.None) => + new NullTrieStore.NullCommitter(); + + public bool IsPersisted(in TreePath path, in ValueHash256 keccak) => + fullTrieStore.IsPersisted(address, path, in keccak); + + public void Set(in TreePath path, in ValueHash256 keccak, byte[] rlp) { } } } } diff --git a/src/Nethermind/Nethermind.Trie/Pruning/ScopedTrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/ScopedTrieStore.cs index 02dc3e4d85e..650ee399086 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/ScopedTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/ScopedTrieStore.cs @@ -6,57 +6,28 @@ namespace Nethermind.Trie.Pruning; -public sealed class ScopedTrieStore : IScopedTrieStore +public sealed class ScopedTrieStore(ITrieStore fullTrieStore, Hash256? address) : IScopedTrieStore { - private readonly ITrieStore _trieStoreImplementation; - private readonly Hash256? _address; - - public ScopedTrieStore(ITrieStore fullTrieStore, Hash256? address) - { - _trieStoreImplementation = fullTrieStore; - _address = address; - } - - public TrieNode FindCachedOrUnknown(in TreePath path, Hash256 hash) - { - return _trieStoreImplementation.FindCachedOrUnknown(_address, path, hash); - } - - public byte[]? LoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) - { - return _trieStoreImplementation.LoadRlp(_address, path, hash, flags); - } - - public byte[]? TryLoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) - { - return _trieStoreImplementation.TryLoadRlp(_address, path, hash, flags); - } - - public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256? address) - { - if (address == _address) return this; - return new ScopedTrieStore(_trieStoreImplementation, address); - } - - public INodeStorage.KeyScheme Scheme => _trieStoreImplementation.Scheme; - - public void CommitNode(long blockNumber, NodeCommitInfo nodeCommitInfo, WriteFlags writeFlags = WriteFlags.None) - { - _trieStoreImplementation.CommitNode(blockNumber, _address, nodeCommitInfo, writeFlags); - } - - public void FinishBlockCommit(TrieType trieType, long blockNumber, TrieNode? root, WriteFlags writeFlags = WriteFlags.None) - { - _trieStoreImplementation.FinishBlockCommit(trieType, blockNumber, _address, root, writeFlags); - } - - public bool IsPersisted(in TreePath path, in ValueHash256 keccak) - { - return _trieStoreImplementation.IsPersisted(_address, path, in keccak); - } - - public void Set(in TreePath path, in ValueHash256 keccak, byte[] rlp) - { - _trieStoreImplementation.Set(_address, path, keccak, rlp); - } + public TrieNode FindCachedOrUnknown(in TreePath path, Hash256 hash) => + fullTrieStore.FindCachedOrUnknown(address, path, hash); + + public byte[]? LoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => + fullTrieStore.LoadRlp(address, path, hash, flags); + + public byte[]? TryLoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => + fullTrieStore.TryLoadRlp(address, path, hash, flags); + + public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256? address1) => + address1 == address ? this : new ScopedTrieStore(fullTrieStore, address1); + + public INodeStorage.KeyScheme Scheme => fullTrieStore.Scheme; + + public ICommitter BeginCommit(TrieType trieType, long blockNumber, TrieNode? root, WriteFlags writeFlags = WriteFlags.None) => + fullTrieStore.BeginCommit(trieType, blockNumber, address, root, writeFlags); + + public bool IsPersisted(in TreePath path, in ValueHash256 keccak) => + fullTrieStore.IsPersisted(address, path, in keccak); + + public void Set(in TreePath path, in ValueHash256 keccak, byte[] rlp) => + fullTrieStore.Set(address, path, keccak, rlp); } diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TreePath.cs b/src/Nethermind/Nethermind.Trie/Pruning/TreePath.cs index ba768f5844a..36306f16241 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TreePath.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TreePath.cs @@ -27,7 +27,7 @@ public struct TreePath : IEquatable public const int MemorySize = 36; public ValueHash256 Path; - public static TreePath Empty => new TreePath(); + public static TreePath Empty => new(); public readonly Span Span => Path.BytesAsSpan; @@ -38,7 +38,7 @@ public TreePath(in ValueHash256 path, int length) Length = length; } - public int Length { get; internal set; } + public int Length { get; private set; } public static TreePath FromPath(ReadOnlySpan pathHash) { @@ -46,7 +46,7 @@ public static TreePath FromPath(ReadOnlySpan pathHash) if (pathHash.Length == 32) return new TreePath(new ValueHash256(pathHash), 64); // Some of the test passes path directly to PatriciaTrie, but its not 32 byte. - TreePath newTreePath = new TreePath(); + TreePath newTreePath = new(); pathHash.CopyTo(newTreePath.Span); newTreePath.Length = pathHash.Length * 2; return newTreePath; diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs index 2b7a1eca32e..6ae6ca164fc 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs @@ -3,17 +3,13 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; -using System.Threading.Tasks.Dataflow; using Nethermind.Core; -using Nethermind.Core.Caching; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -21,290 +17,29 @@ namespace Nethermind.Trie.Pruning { - using Nethermind.Core.Cpu; - using CollectionExtensions = Core.Collections.CollectionExtensions; - /// /// Trie store helps to manage trie commits block by block. /// If persistence and pruning are needed they have a chance to execute their behaviour on commits. /// public class TrieStore : ITrieStore, IPruningTrieStore { - internal class DirtyNodesCache - { - private readonly TrieStore _trieStore; - private readonly bool _storeByHash; - private readonly ConcurrentDictionary _byKeyObjectCache; - private readonly ConcurrentDictionary _byHashObjectCache; - - public readonly long KeyMemoryUsage; - - public DirtyNodesCache(TrieStore trieStore) - { - _trieStore = trieStore; - // If the nodestore indicated that path is not required, - // we will use a map with hash as its key instead of the full Key to reduce memory usage. - _storeByHash = !trieStore._nodeStorage.RequirePath; - int initialBuckets = HashHelpers.GetPrime(Math.Max(31, Environment.ProcessorCount * 16)); - if (_storeByHash) - { - _byHashObjectCache = new(CollectionExtensions.LockPartitions, initialBuckets); - } - else - { - _byKeyObjectCache = new(CollectionExtensions.LockPartitions, initialBuckets); - } - KeyMemoryUsage = _storeByHash ? 0 : Key.MemoryUsage; // 0 because previously it was not counted. - } - - public void SaveInCache(in Key key, TrieNode node) - { - Debug.Assert(node.Keccak is not null, "Cannot store in cache nodes without resolved key."); - if (TryAdd(key, node)) - { - Metrics.CachedNodesCount = Interlocked.Increment(ref _count); - _trieStore.MemoryUsedByDirtyCache += node.GetMemorySize(false) + KeyMemoryUsage; - } - } - - public TrieNode FindCachedOrUnknown(in Key key) - { - if (TryGetValue(key, out TrieNode trieNode)) - { - Metrics.LoadedFromCacheNodesCount++; - } - else - { - trieNode = new TrieNode(NodeType.Unknown, key.Keccak); - if (_trieStore._logger.IsTrace) Trace(trieNode); - SaveInCache(key, trieNode); - } - - return trieNode; - - [MethodImpl(MethodImplOptions.NoInlining)] - void Trace(TrieNode trieNode) - { - _trieStore._logger.Trace($"Creating new node {trieNode}"); - } - } - - public TrieNode FromCachedRlpOrUnknown(in Key key) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (TryGetValue(key, out TrieNode trieNode)) - { - if (trieNode!.FullRlp.IsNull) - { - // // this happens in SyncProgressResolver - // throw new InvalidAsynchronousStateException("Read only trie store is trying to read a transient node."); - return new TrieNode(NodeType.Unknown, key.Keccak); - } - - // we returning a copy to avoid multithreaded access - trieNode = new TrieNode(NodeType.Unknown, key.Keccak, trieNode.FullRlp); - trieNode.ResolveNode(_trieStore.GetTrieStore(key.AddressAsHash256), key.Path); - trieNode.Keccak = key.Keccak; - - Metrics.LoadedFromCacheNodesCount++; - } - else - { - trieNode = new TrieNode(NodeType.Unknown, key.Keccak); - } - - if (_trieStore._logger.IsTrace) Trace(trieNode); - return trieNode; - - [MethodImpl(MethodImplOptions.NoInlining)] - void Trace(TrieNode trieNode) - { - _trieStore._logger.Trace($"Creating new node {trieNode}"); - } - } - - public bool IsNodeCached(in Key key) - { - if (_storeByHash) return _byHashObjectCache.ContainsKey(key.Keccak); - return _byKeyObjectCache.ContainsKey(key); - } - - public IEnumerable> AllNodes - { - get - { - if (_storeByHash) - { - return _byHashObjectCache.Select( - pair => new KeyValuePair(new Key(null, TreePath.Empty, pair.Key.Value), pair.Value)); - } - - return _byKeyObjectCache; - } - } - - public bool TryGetValue(in Key key, out TrieNode node) - { - if (_storeByHash) - { - return _byHashObjectCache.TryGetValue(key.Keccak, out node); - } - return _byKeyObjectCache.TryGetValue(key, out node); - } - - public bool TryAdd(in Key key, TrieNode node) - { - if (_storeByHash) - { - return _byHashObjectCache.TryAdd(key.Keccak, node); - } - return _byKeyObjectCache.TryAdd(key, node); - } - - public void Remove(in Key key) - { - if (_storeByHash) - { - if (_byHashObjectCache.Remove(key.Keccak, out _)) - { - Metrics.CachedNodesCount = Interlocked.Decrement(ref _count); - } - - return; - } - if (_byKeyObjectCache.Remove(key, out _)) - { - Metrics.CachedNodesCount = Interlocked.Decrement(ref _count); - } - } - - public MapLock AcquireMapLock() - { - if (_storeByHash) - { - return new MapLock() - { - _storeByHash = _storeByHash, - _byHashLock = _byHashObjectCache.AcquireLock() - }; - } - return new MapLock() - { - _storeByHash = _storeByHash, - _byKeyLock = _byKeyObjectCache.AcquireLock() - }; - } - - private int _count = 0; - - public int Count => _count; - - public void Dump() - { - if (_trieStore._logger.IsTrace) - { - _trieStore._logger.Trace($"Trie node dirty cache ({Count})"); - foreach (KeyValuePair keyValuePair in AllNodes) - { - _trieStore._logger.Trace($" {keyValuePair.Value}"); - } - } - } - - public void Clear() - { - _byHashObjectCache.NoResizeClear(); - _byKeyObjectCache.NoResizeClear(); - Interlocked.Exchange(ref _count, 0); - Metrics.CachedNodesCount = 0; - _trieStore.MemoryUsedByDirtyCache = 0; - } - - internal readonly struct Key : IEquatable - { - internal const long MemoryUsage = 8 + 36 + 8; // (address (probably shared), path, keccak pointer (shared with TrieNode)) - public readonly ValueHash256 Address; - public Hash256? AddressAsHash256 => Address == default ? null : Address.ToCommitment(); - // Direct member rather than property for large struct, so members are called directly, - // rather than struct copy through the property. Could also return a ref through property. - public readonly TreePath Path; - public Hash256 Keccak { get; } - - public Key(Hash256? address, in TreePath path, Hash256 keccak) - { - Address = address ?? default; - Path = path; - Keccak = keccak; - } - public Key(in ValueHash256 address, in TreePath path, Hash256 keccak) - { - Address = address; - Path = path; - Keccak = keccak; - } - - [SkipLocalsInit] - public override int GetHashCode() - { - var addressHash = Address != default ? Address.GetHashCode() : 1; - return Keccak.ValueHash256.GetChainedHashCode((uint)Path.GetHashCode()) ^ addressHash; - } - - public bool Equals(Key other) - { - return other.Keccak == Keccak && other.Path == Path && other.Address == Address; - } - - public override bool Equals(object? obj) - { - return obj is Key other && Equals(other); - } - } - - internal ref struct MapLock - { - public bool _storeByHash; - public ConcurrentDictionaryLock.Lock _byHashLock; - public ConcurrentDictionaryLock.Lock _byKeyLock; - - public readonly void Dispose() - { - if (_storeByHash) - { - _byHashLock.Dispose(); - } - else - { - _byKeyLock.Dispose(); - } - } - } - } + private const int ShardedDirtyNodeCount = 256; private int _isFirst; private INodeStorage.WriteBatch? _currentBatch = null; - private DirtyNodesCache? _dirtyNodes; - private DirtyNodesCache DirtyNodes => _dirtyNodes ?? CreateCacheAtomic(ref _dirtyNodes); + private readonly TrieStoreDirtyNodesCache[] _dirtyNodes = []; + private readonly Task[] _dirtyNodesTasks = []; + private readonly ConcurrentDictionary[] _persistedHashes = []; + private readonly Action _persistedNodeRecorder; + private readonly Task[] _disposeTasks = new Task[Environment.ProcessorCount]; - [MethodImpl(MethodImplOptions.NoInlining)] - private DirtyNodesCache CreateCacheAtomic(ref DirtyNodesCache val) - { - DirtyNodesCache instance = new(this); - DirtyNodesCache? prior = Interlocked.CompareExchange(ref val, instance, null); - return prior ?? instance; - } - - // Track some of the persisted path hash. Used to be able to remove keys when it is replaced. - // If null, disable removing key. - private readonly ClockCache? _pastPathHash; + // This seems to attempt prevent multiple block processing at the same time and along with pruning at the same time. + private readonly object _dirtyNodesLock = new object(); - // Track ALL of the recently re-committed persisted nodes. This is so that we don't accidentally remove - // recommitted persisted nodes (which will not get re-persisted). - private readonly ConcurrentDictionary? _persistedLastSeen; + private readonly bool _livePruningEnabled = false; - private ConcurrentDictionary? _wasPersisted; private bool _lastPersistedReachedReorgBoundary; private Task _pruningTask = Task.CompletedTask; private readonly CancellationTokenSource _pruningTaskCancellationTokenSource = new(); @@ -338,22 +73,28 @@ public TrieStore( _pruningStrategy = pruningStrategy ?? throw new ArgumentNullException(nameof(pruningStrategy)); _persistenceStrategy = persistenceStrategy ?? throw new ArgumentNullException(nameof(persistenceStrategy)); _publicStore = new TrieKeyValueStore(this); + _persistedNodeRecorder = PersistedNodeRecorder; if (pruningStrategy.PruningEnabled) { - _persistedLastSeen = new(CollectionExtensions.LockPartitions, 4 * 4096); - if (pruningStrategy.TrackedPastKeyCount > 0 && nodeStorage.RequirePath) + _dirtyNodes = new TrieStoreDirtyNodesCache[ShardedDirtyNodeCount]; + _dirtyNodesTasks = new Task[ShardedDirtyNodeCount]; + _persistedHashes = new ConcurrentDictionary[ShardedDirtyNodeCount]; + for (int i = 0; i < ShardedDirtyNodeCount; i++) { - _pastPathHash = new(pruningStrategy.TrackedPastKeyCount); + _dirtyNodes[i] = new TrieStoreDirtyNodesCache(this, _pruningStrategy.TrackedPastKeyCount / ShardedDirtyNodeCount, !_nodeStorage.RequirePath, _logger); + _persistedHashes[i] = new ConcurrentDictionary(); } } - } - public IScopedTrieStore GetTrieStore(Hash256? address) - { - return new ScopedTrieStore(this, address); + if (pruningStrategy.PruningEnabled && pruningStrategy.TrackedPastKeyCount > 0 && nodeStorage.RequirePath) + { + _livePruningEnabled = true; + } } + public IScopedTrieStore GetTrieStore(Hash256? address) => new ScopedTrieStore(this, address); + public long LastPersistedBlockNumber { get => _latestPersistedBlockNumber; @@ -371,7 +112,7 @@ private set public long MemoryUsedByDirtyCache { get => _memoryUsedByDirtyCache; - private set + set { Metrics.MemoryUsedByCache = value; _memoryUsedByDirtyCache = value; @@ -402,43 +143,39 @@ public int CachedNodesCount { get { - int count = DirtyNodes.Count; + int count = DirtyNodesCount(); Metrics.CachedNodesCount = count; return count; } } - public void CommitNode(long blockNumber, Hash256? address, in NodeCommitInfo nodeCommitInfo, WriteFlags writeFlags = WriteFlags.None) + private void CommitNode(long blockNumber, Hash256? address, ref TreePath path, in NodeCommitInfo nodeCommitInfo, WriteFlags writeFlags = WriteFlags.None) { - ArgumentOutOfRangeException.ThrowIfNegative(blockNumber); - EnsureCommitSetExistsForBlock(blockNumber); - if (_logger.IsTrace) Trace(blockNumber, in nodeCommitInfo); if (!nodeCommitInfo.IsEmptyBlockMarker && !nodeCommitInfo.Node.IsBoundaryProofNode) { - TrieNode node = nodeCommitInfo.Node!; + TrieNode node = nodeCommitInfo.Node; - if (node!.Keccak is null) + if (node.Keccak is null) { ThrowUnknownHash(node); } - if (CurrentPackage is null) + if (node.LastSeen >= 0) { - ThrowUnknownPackage(blockNumber, node); + ThrowNodeHasBeenSeen(blockNumber, node); } - if (node!.LastSeen >= 0) + if (_pruningStrategy.PruningEnabled) { - ThrowNodeHasBeenSeen(blockNumber, node); + node = SaveOrReplaceInDirtyNodesCache(address, ref path, nodeCommitInfo, node); } - node = SaveOrReplaceInDirtyNodesCache(address, nodeCommitInfo, node); node.LastSeen = Math.Max(blockNumber, node.LastSeen); if (!_pruningStrategy.PruningEnabled) { - PersistNode(address, nodeCommitInfo.Path, node, blockNumber, writeFlags); + PersistNode(address, path, node, blockNumber, writeFlags); } CommittedNodesCount++; @@ -452,58 +189,75 @@ void Trace(long blockNumber, in NodeCommitInfo nodeCommitInfo) [DoesNotReturn] [StackTraceHidden] - static void ThrowUnknownHash(TrieNode node) - { - throw new TrieStoreException($"The hash of {node} should be known at the time of committing."); - } + static void ThrowUnknownHash(TrieNode node) => throw new TrieStoreException($"The hash of {node} should be known at the time of committing."); [DoesNotReturn] [StackTraceHidden] - static void ThrowUnknownPackage(long blockNumber, TrieNode node) - { - throw new TrieStoreException($"{nameof(CurrentPackage)} is NULL when committing {node} at {blockNumber}."); - } + static void ThrowNodeHasBeenSeen(long blockNumber, TrieNode node) => throw new TrieStoreException($"{nameof(TrieNode.LastSeen)} set on {node} committed at {blockNumber}."); + } - [DoesNotReturn] - [StackTraceHidden] - static void ThrowNodeHasBeenSeen(long blockNumber, TrieNode node) + private int GetNodeShardIdx(in TreePath path, Hash256 hash) => + // When enabled, the shard have dictionaries for tracking past path hash also. + // So the same path need to be in the same shard for the remove logic to work. + // Using the address first byte however, causes very uneven distribution. So the path is used. + _livePruningEnabled ? path.Path.Bytes[0] : hash.Bytes[0]; + + private int GetNodeShardIdx(in TrieStoreDirtyNodesCache.Key key) => GetNodeShardIdx(key.Path, key.Keccak); + + private TrieStoreDirtyNodesCache GetDirtyNodeShard(in TrieStoreDirtyNodesCache.Key key) => _dirtyNodes[GetNodeShardIdx(key)]; + + private int DirtyNodesCount() + { + int count = 0; + foreach (TrieStoreDirtyNodesCache dirtyNode in _dirtyNodes) { - throw new TrieStoreException($"{nameof(TrieNode.LastSeen)} set on {node} committed at {blockNumber}."); + count += dirtyNode.Count; } + return count; } - private TrieNode SaveOrReplaceInDirtyNodesCache(Hash256? address, NodeCommitInfo nodeCommitInfo, TrieNode node) + private bool DirtyNodesTryGetValue(in TrieStoreDirtyNodesCache.Key key, out TrieNode node) => + GetDirtyNodeShard(key).TryGetValue(key, out node); + + private void DirtyNodesSaveInCache(in TrieStoreDirtyNodesCache.Key key, TrieNode node) => + GetDirtyNodeShard(key).SaveInCache(key, node); + + private bool DirtyNodesIsNodeCached(TrieStoreDirtyNodesCache.Key key) => + GetDirtyNodeShard(key).IsNodeCached(key); + + private TrieNode DirtyNodesFromCachedRlpOrUnknown(TrieStoreDirtyNodesCache.Key key) => + GetDirtyNodeShard(key).FromCachedRlpOrUnknown(key); + + private TrieNode DirtyNodesFindCachedOrUnknown(TrieStoreDirtyNodesCache.Key key) => + GetDirtyNodeShard(key).FindCachedOrUnknown(key); + + private TrieNode SaveOrReplaceInDirtyNodesCache(Hash256? address, ref TreePath path, NodeCommitInfo nodeCommitInfo, TrieNode node) { - if (_pruningStrategy.PruningEnabled) + TrieStoreDirtyNodesCache.Key key = new(address, path, node.Keccak); + if (DirtyNodesTryGetValue(in key, out TrieNode cachedNodeCopy)) { - DirtyNodesCache.Key key = new DirtyNodesCache.Key(address, nodeCommitInfo.Path, node.Keccak); - DirtyNodesCache cache = DirtyNodes; - if (cache.TryGetValue(in key, out TrieNode cachedNodeCopy)) + Metrics.LoadedFromCacheNodesCount++; + if (!ReferenceEquals(cachedNodeCopy, node)) { - Metrics.LoadedFromCacheNodesCount++; - if (!ReferenceEquals(cachedNodeCopy, node)) + if (_logger.IsTrace) Trace(node, cachedNodeCopy); + cachedNodeCopy.ResolveKey(GetTrieStore(address), ref path, nodeCommitInfo.IsRoot); + if (node.Keccak != cachedNodeCopy.Keccak) { - if (_logger.IsTrace) Trace(node, cachedNodeCopy); - TreePath path = nodeCommitInfo.Path; - cachedNodeCopy.ResolveKey(GetTrieStore(address), ref path, nodeCommitInfo.IsRoot); - if (node.Keccak != cachedNodeCopy.Keccak) - { - ThrowNodeIsNotSame(node, cachedNodeCopy); - } - - if (!nodeCommitInfo.IsRoot) - { - nodeCommitInfo.NodeParent!.ReplaceChildRef(nodeCommitInfo.ChildPositionAtParent, cachedNodeCopy); - } + ThrowNodeIsNotSame(node, cachedNodeCopy); + } - node = cachedNodeCopy; - Metrics.ReplacedNodesCount++; + if (!nodeCommitInfo.IsRoot) + { + nodeCommitInfo.NodeParent!.ReplaceChildRef(nodeCommitInfo.ChildPositionAtParent, cachedNodeCopy); } + + node = cachedNodeCopy; + Metrics.ReplacedNodesCount++; } - else - { - cache.SaveInCache(key, node); - } + } + else + { + DirtyNodesSaveInCache(key, node); } return node; @@ -516,18 +270,24 @@ void Trace(TrieNode node, TrieNode cachedNodeCopy) [DoesNotReturn] [StackTraceHidden] - static void ThrowNodeIsNotSame(TrieNode node, TrieNode cachedNodeCopy) - { + static void ThrowNodeIsNotSame(TrieNode node, TrieNode cachedNodeCopy) => throw new InvalidOperationException($"The hash of replacement node {cachedNodeCopy} is not the same as the original {node}."); - } - } - public void FinishBlockCommit(TrieType trieType, long blockNumber, Hash256? address, TrieNode? root, WriteFlags writeFlags = WriteFlags.None) + public ICommitter BeginCommit(TrieType trieType, long blockNumber, Hash256? address, TrieNode? root, WriteFlags writeFlags) { ArgumentOutOfRangeException.ThrowIfNegative(blockNumber); EnsureCommitSetExistsForBlock(blockNumber); + int concurrency = _pruningStrategy.PruningEnabled + ? Environment.ProcessorCount + : 0; // The write batch when pruning is not enabled is not concurrent safe + + return new TrieStoreCommitter(this, trieType, blockNumber, address, root, writeFlags, concurrency); + } + + private void FinishBlockCommit(TrieType trieType, long blockNumber, Hash256? address, TrieNode? root, WriteFlags writeFlags = WriteFlags.None) + { try { if (trieType == TrieType.State) // storage tries happen before state commits @@ -563,10 +323,9 @@ public void FinishBlockCommit(TrieType trieType, long blockNumber, Hash256? addr } CurrentPackage = null; - DirtyNodesCache cache = DirtyNodes; - if (_pruningStrategy.PruningEnabled && Monitor.IsEntered(cache)) + if (_pruningStrategy.PruningEnabled && Monitor.IsEntered(_dirtyNodesLock)) { - Monitor.Exit(cache); + Monitor.Exit(_dirtyNodesLock); } } } @@ -633,7 +392,7 @@ public virtual bool IsPersisted(Hash256? address, in TreePath path, in ValueHash public IReadOnlyTrieStore AsReadOnly(INodeStorage? store) => new ReadOnlyTrieStore(this, store); - public bool IsNodeCached(Hash256? address, in TreePath path, Hash256? hash) => DirtyNodes.IsNodeCached(new DirtyNodesCache.Key(address, path, hash)); + public bool IsNodeCached(Hash256? address, in TreePath path, Hash256? hash) => DirtyNodesIsNodeCached(new TrieStoreDirtyNodesCache.Key(address, path, hash)); public virtual TrieNode FindCachedOrUnknown(Hash256? address, in TreePath path, Hash256? hash) => FindCachedOrUnknown(address, path, hash, false); @@ -647,16 +406,23 @@ internal TrieNode FindCachedOrUnknown(Hash256? address, in TreePath path, Hash25 return new TrieNode(NodeType.Unknown, hash); } - DirtyNodesCache.Key key = new DirtyNodesCache.Key(address, path, hash); + TrieStoreDirtyNodesCache.Key key = new TrieStoreDirtyNodesCache.Key(address, path, hash); return FindCachedOrUnknown(key, isReadOnly); } - private TrieNode FindCachedOrUnknown(DirtyNodesCache.Key key, bool isReadOnly) + private TrieNode FindCachedOrUnknown(TrieStoreDirtyNodesCache.Key key, bool isReadOnly) { - return isReadOnly ? DirtyNodes.FromCachedRlpOrUnknown(key) : DirtyNodes.FindCachedOrUnknown(key); + return isReadOnly ? DirtyNodesFromCachedRlpOrUnknown(key) : DirtyNodesFindCachedOrUnknown(key); } - public void Dump() => DirtyNodes.Dump(); + // Used only in tests + public void Dump() + { + foreach (TrieStoreDirtyNodesCache? dirtyNode in _dirtyNodes) + { + dirtyNode.Dump(); + } + } public void Prune() { @@ -666,37 +432,41 @@ public void Prune() { try { - DirtyNodesCache cache = DirtyNodes; - lock (cache) + // Flush ahead of time so that memtable is empty which prevent stalling when writing nodes. + // Note, the WriteBufferSize * WriteBufferNumber need to be more than about 20% of pruning cache + // otherwise, it may not fit the whole dirty cache. + // Additionally, if (WriteBufferSize * (WriteBufferNumber - 1)) is already more than 20% of pruning + // cache, it is likely that there are enough space for it on most time, except for syncing maybe. + _nodeStorage.Flush(); + lock (_dirtyNodesLock) { - using (cache.AcquireMapLock()) + long start = Stopwatch.GetTimestamp(); + if (_logger.IsDebug) _logger.Debug($"Locked {nameof(TrieStore)} for pruning."); + + long memoryUsedByDirtyCache = MemoryUsedByDirtyCache; + if (!_pruningTaskCancellationTokenSource.IsCancellationRequested && _pruningStrategy.ShouldPrune(memoryUsedByDirtyCache)) { - Stopwatch sw = Stopwatch.StartNew(); - if (_logger.IsDebug) _logger.Debug($"Locked {nameof(TrieStore)} for pruning."); - - long memoryUsedByDirtyCache = MemoryUsedByDirtyCache; - if (!_pruningTaskCancellationTokenSource.IsCancellationRequested && _pruningStrategy.ShouldPrune(memoryUsedByDirtyCache)) - { - // Most of the time in memory pruning is on `PrunePersistedRecursively`. So its - // usually faster to just SaveSnapshot causing most of the entry to be persisted. - // Not saving snapshot just save about 5% of memory at most of the time, causing - // an elevated pruning a few blocks after making it not very effective especially - // on constant block processing such as during forward sync where it can take up to - // 30% of the total time on halfpath as the block processing portion got faster. - // - // With halfpath's live pruning, there is a slight complication, the currently loaded - // persisted node have a pretty good hit rate and tend to conflict with the persisted - // nodes (address,path) entry on second PruneCache. So pruning them ahead of time - // really helps increase nodes that can be removed. - PruneCache(skipRecalculateMemory: true); - - SaveSnapshot(); - - PruneCache(); - - Metrics.PruningTime = sw.ElapsedMilliseconds; - if (_logger.IsInfo) _logger.Info($"Executed memory prune. Took {sw.Elapsed.TotalSeconds:0.##} seconds. From {memoryUsedByDirtyCache / 1.MiB()}MB to {MemoryUsedByDirtyCache / 1.MiB()}MB"); - } + // Most of the time in memory pruning is on `PrunePersistedRecursively`. So its + // usually faster to just SaveSnapshot causing most of the entry to be persisted. + // Not saving snapshot just save about 5% of memory at most of the time, causing + // an elevated pruning a few blocks after making it not very effective especially + // on constant block processing such as during forward sync where it can take up to + // 30% of the total time on halfpath as the block processing portion got faster. + // + // With halfpath's live pruning, there is a slight complication, the currently loaded + // persisted node have a pretty good hit rate and tend to conflict with the persisted + // nodes (address,path) entry on second PruneCache. So pruning them ahead of time + // really helps increase nodes that can be removed. + PruneCache(skipRecalculateMemory: true); + + SaveSnapshot(); + + PruneCache(); + + TimeSpan sw = Stopwatch.GetElapsedTime(start); + long ms = (long)sw.TotalMilliseconds; + Metrics.PruningTime = ms; + if (_logger.IsInfo) _logger.Info($"Executed memory prune. Took {ms:0.##} ms. From {memoryUsedByDirtyCache / 1.MiB()}MB to {MemoryUsedByDirtyCache / 1.MiB()}MB"); } } @@ -744,39 +514,49 @@ private bool SaveSnapshot() _commitSetQueue.Enqueue(toAddBack[index]); } + bool shouldDeletePersistedNode = // Its disabled - _pastPathHash is not null && + _livePruningEnabled && // Full pruning need to visit all node, so can't delete anything. !_persistenceStrategy.IsFullPruning && // If more than one candidate set, its a reorg, we can't remove node as persisted node may not be canonical candidateSets.Count == 1; - Dictionary? persistedHashes = - shouldDeletePersistedNode - ? new Dictionary() - : null; + Action? persistedNodeRecorder = shouldDeletePersistedNode ? _persistedNodeRecorder : null; - INodeStorage.WriteBatch writeBatch = _nodeStorage.StartWriteBatch(); for (int index = 0; index < candidateSets.Count; index++) { BlockCommitSet blockCommitSet = candidateSets[index]; if (_logger.IsDebug) _logger.Debug($"Elevated pruning for candidate {blockCommitSet.BlockNumber}"); - PersistBlockCommitSet(null, blockCommitSet, writeBatch, persistedHashes: persistedHashes); + ParallelPersistBlockCommitSet(null, blockCommitSet, persistedNodeRecorder); } - // Run in parallel. Reduce time by about 30%. - Task deleteTask = Task.Run(() => RemovePastKeys(persistedHashes)); + Task deleteTask = shouldDeletePersistedNode ? RemovePastKeys() : Task.CompletedTask; + + Task RemovePastKeys() + { + for (int index = 0; index < _dirtyNodes.Length; index++) + { + int i = index; + _dirtyNodesTasks[index] = Task.Run(() => + { + _dirtyNodes[i].RemovePastKeys(_persistedHashes[i], _nodeStorage); + _persistedHashes[i].NoResizeClear(); + }); + } + + return Task.WhenAll(_dirtyNodesTasks); + } - writeBatch.Dispose(); AnnounceReorgBoundaries(); deleteTask.Wait(); - foreach (KeyValuePair keyValuePair in _persistedLastSeen) + if (_livePruningEnabled) { - if (IsNoLongerNeeded(keyValuePair.Value)) + foreach (TrieStoreDirtyNodesCache dirtyNode in _dirtyNodes) { - _persistedLastSeen.Remove(keyValuePair.Key, out _); + dirtyNode.CleanObsoletePersistedLastSeen(); } } @@ -792,70 +572,15 @@ _pastPathHash is not null && return false; } - private void RemovePastKeys(Dictionary? persistedHashes) + private void PersistedNodeRecorder(TreePath treePath, Hash256 address, TrieNode tn) { - if (persistedHashes is null) return; - - bool CanRemove(in ValueHash256 address, TinyTreePath path, in TreePath fullPath, in ValueHash256 keccak, Hash256? currentlyPersistingKeccak) + if (treePath.Length <= TinyTreePath.MaxNibbleLength) { - // Multiple current hash that we don't keep track for simplicity. Just ignore this case. - if (currentlyPersistingKeccak is null) return false; - - // The persisted hash is the same as currently persisting hash. Do nothing. - if (currentlyPersistingKeccak == keccak) return false; - - // We have it in cache and it is still needed. - if (DirtyNodes.TryGetValue(new DirtyNodesCache.Key(address, fullPath, keccak.ToCommitment()), out TrieNode node) && - !IsNoLongerNeeded(node)) return false; - - // We don't have it in cache, but we know it was re-committed, so if it is still needed, don't remove - if (_persistedLastSeen.TryGetValue(new(address, in path, in keccak), out long commitBlock) && - !IsNoLongerNeeded(commitBlock)) return false; + int shardIdx = GetNodeShardIdx(treePath, tn.Keccak); - return true; - } - - ActionBlock actionBlock = - new ActionBlock(static (batch) => batch.Dispose()); - - INodeStorage.WriteBatch writeBatch = _nodeStorage.StartWriteBatch(); - try - { - int round = 0; - foreach (KeyValuePair keyValuePair in persistedHashes) - { - HashAndTinyPath key = keyValuePair.Key; - if (_pastPathHash.TryGet(key, out ValueHash256 prevHash)) - { - TreePath fullPath = key.path.ToTreePath(); // Micro op to reduce double convert - if (CanRemove(key.addr, key.path, fullPath, prevHash, keyValuePair.Value)) - { - Metrics.RemovedNodeCount++; - Hash256? address = key.addr == default ? null : key.addr.ToCommitment(); - writeBatch.Set(address, fullPath, prevHash, default, WriteFlags.DisableWAL); - round++; - } - } + HashAndTinyPath key = new(address, new TinyTreePath(treePath)); - // Batches of 256 - if (round > 256) - { - actionBlock.Post(writeBatch); - writeBatch = _nodeStorage.StartWriteBatch(); - round = 0; - } - } - } - catch (Exception ex) - { - if (_logger.IsError) _logger.Error($"Failed to remove past keys. {ex}"); - } - finally - { - writeBatch.Dispose(); - actionBlock.Complete(); - actionBlock.Completion.Wait(); - _nodeStorage.Compact(); + _persistedHashes[shardIdx].AddOrUpdate(key, _ => tn.Keccak, (_, _) => null); } } @@ -864,7 +589,7 @@ bool CanRemove(in ValueHash256 address, TinyTreePath path, in TreePath fullPath, /// private void PruneCurrentSet() { - Stopwatch stopwatch = Stopwatch.StartNew(); + long start = Stopwatch.GetTimestamp(); // We assume that the most recent package very likely resolved many persisted nodes and only replaced // some top level branches. Any of these persisted nodes are held in cache now so we just prune them here @@ -873,8 +598,7 @@ private void PruneCurrentSet() // Note that currently the TrieNode ResolveChild un-resolves any persisted child immediately which // may make this call unnecessary. CurrentPackage?.Root?.PrunePersistedRecursively(2); - stopwatch.Stop(); - Metrics.DeepPruningTime = stopwatch.ElapsedMilliseconds; + Metrics.DeepPruningTime = (long)Stopwatch.GetElapsedTime(start).TotalMilliseconds; } /// @@ -882,106 +606,42 @@ private void PruneCurrentSet() /// removing ones that are either no longer referenced or already persisted. /// /// - private void PruneCache(bool skipRecalculateMemory = false, KeyValuePair[]? allNodes = null) + private void PruneCache(bool skipRecalculateMemory = false) { if (_logger.IsDebug) _logger.Debug($"Pruning nodes {MemoryUsedByDirtyCache / 1.MB()} MB , last persisted block: {LastPersistedBlockNumber} current: {LatestCommittedBlockNumber}."); - Stopwatch stopwatch = Stopwatch.StartNew(); - - // Run in parallel - bool shouldTrackPersistedNode = _pastPathHash is not null && !_persistenceStrategy.IsFullPruning; - ActionBlock<(DirtyNodesCache.Key key, TrieNode node)>? trackNodesAction = shouldTrackPersistedNode - ? new ActionBlock<(DirtyNodesCache.Key key, TrieNode node)>( - entry => TrackPrunedPersistedNodes(entry.key, entry.node)) - : null; + long start = Stopwatch.GetTimestamp(); long newMemory = 0; - DirtyNodesCache cache = DirtyNodes; - ActionBlock pruneAndRecalculateAction = - new ActionBlock(node => - { - node.PrunePersistedRecursively(1); - Interlocked.Add(ref newMemory, node.GetMemorySize(false) + cache.KeyMemoryUsage); - }); - foreach ((DirtyNodesCache.Key key, TrieNode node) in (allNodes ?? cache.AllNodes)) + for (int index = 0; index < _dirtyNodes.Length; index++) { - if (node.IsPersisted) - { - if (_logger.IsTrace) _logger.Trace($"Removing persisted {node} from memory."); - - trackNodesAction?.Post((key, node)); - - Hash256? keccak = node.Keccak; - if (keccak is null) - { - TreePath path2 = key.Path; - keccak = node.GenerateKey(this.GetTrieStore(key.AddressAsHash256), ref path2, isRoot: true); - if (keccak != key.Keccak) - { - throw new InvalidOperationException($"Persisted {node} {key} != {keccak}"); - } - - node.Keccak = keccak; - } - cache.Remove(key); - - Metrics.PrunedPersistedNodesCount++; - } - else if (IsNoLongerNeeded(node)) + TrieStoreDirtyNodesCache dirtyNode = _dirtyNodes[index]; + _dirtyNodesTasks[index] = Task.Run(() => { - if (_logger.IsTrace) _logger.Trace($"Removing {node} from memory (no longer referenced)."); - if (node.Keccak is null) - { - throw new InvalidOperationException($"Removed {node}"); - } - cache.Remove(key); - - Metrics.PrunedTransientNodesCount++; - } - else if (!skipRecalculateMemory) - { - pruneAndRecalculateAction.Post(node); - } + long shardSize = dirtyNode.PruneCache(skipRecalculateMemory); + Interlocked.Add(ref newMemory, shardSize); + }); } - pruneAndRecalculateAction.Complete(); - trackNodesAction?.Complete(); - pruneAndRecalculateAction.Completion.Wait(); - trackNodesAction?.Completion.Wait(); + Task.WaitAll(_dirtyNodesTasks); - if (!skipRecalculateMemory) MemoryUsedByDirtyCache = newMemory + (_persistedLastSeen?.Count ?? 0) * 48; - Metrics.CachedNodesCount = cache.Count; + if (!skipRecalculateMemory) MemoryUsedByDirtyCache = newMemory; + _ = CachedNodesCount; // Setter also update the count - stopwatch.Stop(); - if (_logger.IsDebug) _logger.Debug($"Finished pruning nodes in {stopwatch.ElapsedMilliseconds}ms {MemoryUsedByDirtyCache / 1.MB()} MB, last persisted block: {LastPersistedBlockNumber} current: {LatestCommittedBlockNumber}."); + if (_logger.IsDebug) _logger.Debug($"Finished pruning nodes in {(long)Stopwatch.GetElapsedTime(start).TotalMilliseconds}ms {MemoryUsedByDirtyCache / 1.MB()} MB, last persisted block: {LastPersistedBlockNumber} current: {LatestCommittedBlockNumber}."); } - private void TrackPrunedPersistedNodes(in DirtyNodesCache.Key key, TrieNode node) + /// + /// This method is here to support testing. + /// + public void ClearCache() { - if (key.Path.Length > TinyTreePath.MaxNibbleLength) return; - TinyTreePath treePath = new(key.Path); - // Persisted node with LastSeen is a node that has been re-committed, likely due to processing - // recalculated to the same hash. - if (node.LastSeen >= 0) + foreach (TrieStoreDirtyNodesCache dirtyNode in _dirtyNodes) { - // Update _persistedLastSeen to later value. - _persistedLastSeen.AddOrUpdate( - new(key.Address, in treePath, key.Keccak), - (_, newValue) => newValue, - (_, newValue, currentLastSeen) => Math.Max(newValue, currentLastSeen), - node.LastSeen); + dirtyNode.Clear(); } - - // This persisted node is being removed from cache. Keep it in mind in case of an update to the same - // path. - _pastPathHash.Set(new(key.Address, in treePath), key.Keccak); } - /// - /// This method is here to support testing. - /// - public void ClearCache() => DirtyNodes.Clear(); - public void Dispose() { if (_logger.IsDebug) _logger.Debug("Disposing trie"); @@ -1062,43 +722,125 @@ private void PersistBlockCommitSet( Hash256? address, BlockCommitSet commitSet, INodeStorage.WriteBatch writeBatch, - Dictionary? persistedHashes = null, + Action? persistedNodeRecorder = null, WriteFlags writeFlags = WriteFlags.None ) { void PersistNode(TrieNode tn, Hash256? address2, TreePath path) { - if (persistedHashes is not null && path.Length <= TinyTreePath.MaxNibbleLength) - { - HashAndTinyPath key = new(address2, new TinyTreePath(path)); - ref Hash256? hash = ref CollectionsMarshal.GetValueRefOrAddDefault(persistedHashes, key, out bool exists); - if (exists) - { - // Null mark that there are multiple saved hash for this path. So we don't attempt to remove anything. - // Otherwise this would have to be a list, which is such a rare case that its not worth it to have a list. - hash = null; - } - else - { - hash = tn.Keccak; - } - } + persistedNodeRecorder?.Invoke(path, address2, tn); this.PersistNode(address2, path, tn, commitSet.BlockNumber, writeFlags, writeBatch); } if (_logger.IsDebug) _logger.Debug($"Persisting from root {commitSet.Root} in {commitSet.BlockNumber}"); - Stopwatch stopwatch = Stopwatch.StartNew(); + long start = Stopwatch.GetTimestamp(); TreePath path = TreePath.Empty; commitSet.Root?.CallRecursively(PersistNode, address, ref path, GetTrieStore(null), true, _logger); - stopwatch.Stop(); - Metrics.SnapshotPersistenceTime = stopwatch.ElapsedMilliseconds; + long elapsedMilliseconds = (long)Stopwatch.GetElapsedTime(start).TotalMilliseconds; + Metrics.SnapshotPersistenceTime = elapsedMilliseconds; - if (_logger.IsDebug) _logger.Debug($"Persisted trie from {commitSet.Root} at {commitSet.BlockNumber} in {stopwatch.ElapsedMilliseconds}ms (cache memory {MemoryUsedByDirtyCache})"); + if (_logger.IsDebug) _logger.Debug($"Persisted trie from {commitSet.Root} at {commitSet.BlockNumber} in {elapsedMilliseconds}ms (cache memory {MemoryUsedByDirtyCache})"); LastPersistedBlockNumber = commitSet.BlockNumber; } + private void ParallelPersistBlockCommitSet( + Hash256? address, + BlockCommitSet commitSet, + Action? persistedNodeRecorder = null, + WriteFlags writeFlags = WriteFlags.None + ) + { + INodeStorage.WriteBatch topLevelWriteBatch = _nodeStorage.StartWriteBatch(); + const int parallelBoundaryPathLength = 2; + + using ArrayPoolList<(TrieNode trieNode, Hash256? address2, TreePath path)> parallelStartNodes = new(ShardedDirtyNodeCount); + + void TopLevelPersist(TrieNode tn, Hash256? address2, TreePath path) + { + if (path.Length < parallelBoundaryPathLength) + { + persistedNodeRecorder?.Invoke(path, address2, tn); + PersistNode(address2, path, tn, commitSet.BlockNumber, writeFlags, topLevelWriteBatch); + } + else + { + parallelStartNodes.Add((tn, address2, path)); + } + } + + if (_logger.IsDebug) _logger.Debug($"Persisting from root {commitSet.Root} in {commitSet.BlockNumber}"); + + long start = Stopwatch.GetTimestamp(); + + // The first CallRecursive stop at two level, yielding 256 node in parallelStartNodes, which is run concurrently + TreePath path = TreePath.Empty; + commitSet.Root?.CallRecursively(TopLevelPersist, address, ref path, GetTrieStore(null), true, _logger, maxPathLength: parallelBoundaryPathLength); + + // The amount of change in the subtrees are not balanced at all. So their writes ares buffered here + // which get disposed in parallel instead of being disposed in `PersistNodeStartingFrom`. + // This unfortunately is not atomic + // However, anything that we are trying to persist here should still be in dirty cache. + // So parallel read should go there first instead of to the database for these dataset, + // so it should be fine for these to be non atomic. + using BlockingCollection disposeQueue = new BlockingCollection(4); + + for (int index = 0; index < _disposeTasks.Length; index++) + { + _disposeTasks[index] = Task.Run(() => + { + while (disposeQueue.TryTake(out INodeStorage.WriteBatch disposable, Timeout.Infinite)) + { + disposable.Dispose(); + } + }); + } + + Task.WaitAll(parallelStartNodes.Select(entry => Task.Run(() => + { + (TrieNode trieNode, Hash256? address2, TreePath path2) = entry; + PersistNodeStartingFrom(trieNode, address2, path2, commitSet, persistedNodeRecorder, writeFlags, disposeQueue); + })).ToArray()); + + disposeQueue.CompleteAdding(); + Task.WaitAll(_disposeTasks); + + // Dispose top level last in case something goes wrong, at least the root wont be stored + topLevelWriteBatch.Dispose(); + + long elapsedMilliseconds = (long)Stopwatch.GetElapsedTime(start).TotalMilliseconds; + Metrics.SnapshotPersistenceTime = elapsedMilliseconds; + + if (_logger.IsDebug) _logger.Debug($"Persisted trie from {commitSet.Root} at {commitSet.BlockNumber} in {elapsedMilliseconds}ms (cache memory {MemoryUsedByDirtyCache})"); + + LastPersistedBlockNumber = commitSet.BlockNumber; + } + + private void PersistNodeStartingFrom(TrieNode tn, Hash256 address2, TreePath path, BlockCommitSet commitSet, + Action? persistedNodeRecorder, + WriteFlags writeFlags, BlockingCollection disposeQueue) + { + long persistedNodeCount = 0; + INodeStorage.WriteBatch writeBatch = _nodeStorage.StartWriteBatch(); + + void DoPersist(TrieNode node, Hash256? address3, TreePath path2) + { + persistedNodeRecorder?.Invoke(path2, address3, node); + PersistNode(address3, path2, node, commitSet.BlockNumber, writeFlags, writeBatch); + + persistedNodeCount++; + if (persistedNodeCount % 512 == 0) + { + disposeQueue.Add(writeBatch); + writeBatch = _nodeStorage.StartWriteBatch(); + } + } + + tn.CallRecursively(DoPersist, address2, ref path, GetTrieStore(address2), true, _logger); + disposeQueue.Add(writeBatch); + } + private void PersistNode(Hash256? address, in TreePath path, TrieNode currentNode, long blockNumber, WriteFlags writeFlags = WriteFlags.None, INodeStorage.WriteBatch? writeBatch = null) { writeBatch ??= _currentBatch ??= _nodeStorage.StartWriteBatch(); @@ -1125,12 +867,12 @@ private void PersistNode(Hash256? address, in TreePath path, TrieNode currentNod } } - private bool IsNoLongerNeeded(TrieNode node) + public bool IsNoLongerNeeded(TrieNode node) { return IsNoLongerNeeded(node.LastSeen); } - private bool IsNoLongerNeeded(long lastSeen) + public bool IsNoLongerNeeded(long lastSeen) { Debug.Assert(lastSeen >= 0, $"Any node that is cache should have {nameof(TrieNode.LastSeen)} set."); return lastSeen < LastPersistedBlockNumber @@ -1159,10 +901,9 @@ private void EnsureCommitSetExistsForBlock(long blockNumber) { if (CurrentPackage is null) { - DirtyNodesCache cache = DirtyNodes; - if (_pruningStrategy.PruningEnabled && !Monitor.IsEntered(cache)) + if (_pruningStrategy.PruningEnabled && !Monitor.IsEntered(_dirtyNodesLock)) { - Monitor.Enter(cache); + Monitor.Enter(_dirtyNodesLock); } CreateCommitSet(blockNumber); @@ -1254,96 +995,78 @@ private void PersistOnShutdown() public void PersistCache(CancellationToken cancellationToken) { - if (_logger.IsInfo) _logger.Info($"Full Pruning Persist Cache started."); + if (_logger.IsInfo) _logger.Info("Full Pruning Persist Cache started."); - int commitSetCount = 0; - Stopwatch stopwatch = Stopwatch.StartNew(); - // We persist all sealed Commitset causing PruneCache to almost completely clear the cache. Any new block that - // need existing node will have to read back from db causing copy-on-read mechanism to copy the node. - void ClearCommitSetQueue() + lock (_dirtyNodesLock) { - while (_commitSetQueue.TryPeek(out BlockCommitSet commitSet) && commitSet.IsSealed) + int commitSetCount = 0; + long start = Stopwatch.GetTimestamp(); + // We persist all sealed Commitset causing PruneCache to almost completely clear the cache. Any new block that + // need existing node will have to read back from db causing copy-on-read mechanism to copy the node. + void ClearCommitSetQueue() { - if (!_commitSetQueue.TryDequeue(out commitSet)) break; - if (!commitSet.IsSealed) + while (_commitSetQueue.TryPeek(out BlockCommitSet commitSet) && commitSet.IsSealed) { - // Oops - _commitSetQueue.Enqueue(commitSet); - break; + if (!_commitSetQueue.TryDequeue(out commitSet)) break; + if (!commitSet.IsSealed) + { + // Oops + _commitSetQueue.Enqueue(commitSet); + break; + } + + commitSetCount++; + using INodeStorage.WriteBatch writeBatch = _nodeStorage.StartWriteBatch(); + PersistBlockCommitSet(null, commitSet, writeBatch); } + PruneCurrentSet(); + } - commitSetCount++; - using INodeStorage.WriteBatch writeBatch = _nodeStorage.StartWriteBatch(); - PersistBlockCommitSet(null, commitSet, writeBatch); + if (!(_commitSetQueue?.IsEmpty ?? true)) + { + // We persist outside of lock first. + ClearCommitSetQueue(); } - PruneCurrentSet(); - } - if (!(_commitSetQueue?.IsEmpty ?? true)) - { - // We persist outside of lock first. + if (_logger.IsInfo) _logger.Info($"Saving all commit set took {Stopwatch.GetElapsedTime(start)} for {commitSetCount} commit sets."); + + start = Stopwatch.GetTimestamp(); + + // Double check ClearCommitSetQueue(); - } + if (cancellationToken.IsCancellationRequested) return; - if (_logger.IsInfo) _logger.Info($"Saving all commit set took {stopwatch.Elapsed} for {commitSetCount} commit sets."); + // This should clear most nodes. For some reason, not all. + PruneCache(skipRecalculateMemory: true); + if (cancellationToken.IsCancellationRequested) return; - stopwatch.Restart(); - ConcurrentDictionary wasPersisted; - DirtyNodesCache cache = DirtyNodes; - lock (cache) - { - using (cache.AcquireMapLock()) + for (int index = 0; index < _dirtyNodes.Length; index++) { - // Double check - ClearCommitSetQueue(); + TrieStoreDirtyNodesCache dirtyNode = _dirtyNodes[index]; + _dirtyNodesTasks[index] = Task.Run(() => + { + dirtyNode.PersistAll(_nodeStorage, cancellationToken); + }); + } - // This should clear most nodes. For some reason, not all. - PruneCache(skipRecalculateMemory: true); - KeyValuePair[] nodesCopy = cache.AllNodes.ToArray(); + Task.WaitAll(_dirtyNodesTasks); - wasPersisted = Interlocked.Exchange(ref _wasPersisted, null) ?? - new(CollectionExtensions.LockPartitions, nodesCopy.Length); + if (cancellationToken.IsCancellationRequested) return; - void PersistNode(TrieNode n, Hash256? address, TreePath path) - { - if (n.Keccak is null) return; - DirtyNodesCache.Key key = new DirtyNodesCache.Key(address, path, n.Keccak); - if (wasPersisted.TryAdd(key, true)) - { - _nodeStorage.Set(address, path, n.Keccak, n.FullRlp); - n.IsPersisted = true; - } - } - Parallel.For(0, nodesCopy.Length, RuntimeInformation.ParallelOptionsPhysicalCores, i => - { - if (cancellationToken.IsCancellationRequested) return; - DirtyNodesCache.Key key = nodesCopy[i].Key; - TreePath path = key.Path; - Hash256? address = key.AddressAsHash256; - nodesCopy[i].Value.CallRecursively(PersistNode, address, ref path, GetTrieStore(address), false, _logger, false); - }); - PruneCache(allNodes: nodesCopy); + PruneCache(); - if (cache.Count != 0) - { - if (_logger.IsWarn) _logger.Warn($"{cache.Count} cache entry remains."); - } + int dirtyNodesCount = DirtyNodesCount(); + if (dirtyNodesCount != 0) + { + if (_logger.IsWarn) _logger.Warn($"{dirtyNodesCount} cache entry remains."); } - } - - _persistedLastSeen.NoResizeClear(); - _pastPathHash?.Clear(); - if (_logger.IsInfo) _logger.Info($"Clear cache took {stopwatch.Elapsed}."); - if (wasPersisted is not null) - { - // Clear in background outside of lock to not block - Task.Run(() => + foreach (TrieStoreDirtyNodesCache dirtyNode in _dirtyNodes) { - wasPersisted.NoResizeClear(); - // Set back to be reused - _wasPersisted = wasPersisted; - }); + dirtyNode.ClearLivePruningTracking(); + } + + if (_logger.IsInfo) _logger.Info($"Clear cache took {Stopwatch.GetElapsedTime(start)}."); } } @@ -1352,7 +1075,7 @@ void PersistNode(TrieNode n, Hash256? address, TreePath path) { Hash256 asHash = new Hash256(key); return _pruningStrategy.PruningEnabled - && DirtyNodes.TryGetValue(new DirtyNodesCache.Key(null, TreePath.Empty, asHash), out TrieNode? trieNode) + && DirtyNodesTryGetValue(new TrieStoreDirtyNodesCache.Key(null, TreePath.Empty, asHash), out TrieNode? trieNode) && trieNode is not null && trieNode.NodeType != NodeType.Unknown && trieNode.FullRlp.IsNotNull @@ -1361,6 +1084,7 @@ void PersistNode(TrieNode n, Hash256? address, TreePath path) } public IReadOnlyKeyValueStore TrieNodeRlpStore => _publicStore; + public bool IsCurrentlyFullPruning => _persistenceStrategy.IsFullPruning; public void Set(Hash256? address, in TreePath path, in ValueHash256 keccak, byte[] rlp) { @@ -1391,61 +1115,50 @@ public bool HasRoot(Hash256 stateRoot) return true; } - [StructLayout(LayoutKind.Auto)] - private readonly struct HashAndTinyPath : IEquatable + private class TrieStoreCommitter( + TrieStore trieStore, + TrieType trieType, + long blockNumber, + Hash256? address, + TrieNode? root, + WriteFlags writeFlags, + int concurrency + ) : ICommitter { - public readonly ValueHash256 addr; - public readonly TinyTreePath path; + private readonly bool _needToResetRoot = root is not null && root.IsDirty; + private int _concurrency = concurrency; + private TrieNode? _root = root; - public HashAndTinyPath(Hash256? hash, in TinyTreePath path) + public void Dispose() { - addr = hash ?? default; - this.path = path; - } - public HashAndTinyPath(in ValueHash256 hash, in TinyTreePath path) - { - addr = hash; - this.path = path; - } + if (_needToResetRoot) + { + // During commit it PatriciaTrie, the root may get resolved to an existing node (same keccak). + // This ensure that the root that we use here is the same. + _root = trieStore.FindCachedOrUnknown(address, TreePath.Empty, _root?.Keccak); + } - public bool Equals(HashAndTinyPath other) => addr == other.addr && path.Equals(in other.path); - public override bool Equals(object? obj) => obj is HashAndTinyPath other && Equals(other); - public override int GetHashCode() - { - var addressHash = addr != default ? addr.GetHashCode() : 1; - return path.GetHashCode() ^ addressHash; + trieStore.FinishBlockCommit(trieType, blockNumber, address, _root, writeFlags); } - } - [StructLayout(LayoutKind.Auto)] - private readonly struct HashAndTinyPathAndHash : IEquatable - { - public readonly ValueHash256 hash; - public readonly TinyTreePath path; - public readonly ValueHash256 valueHash; + public void CommitNode(ref TreePath path, NodeCommitInfo nodeCommitInfo) => + trieStore.CommitNode(blockNumber, address, ref path, nodeCommitInfo, writeFlags: writeFlags); - public HashAndTinyPathAndHash(Hash256? hash, in TinyTreePath path, in ValueHash256 valueHash) + public bool CanSpawnTask() { - this.hash = hash ?? default; - this.path = path; - this.valueHash = valueHash; - } - public HashAndTinyPathAndHash(in ValueHash256 hash, in TinyTreePath path, in ValueHash256 valueHash) - { - this.hash = hash; - this.path = path; - this.valueHash = valueHash; - } + if (Interlocked.Decrement(ref _concurrency) >= 0) + { + return true; + } - public bool Equals(HashAndTinyPathAndHash other) => hash == other.hash && path.Equals(in other.path) && valueHash.Equals(in other.valueHash); - public override bool Equals(object? obj) => obj is HashAndTinyPath other && Equals(other); - public override int GetHashCode() - { - var hashHash = hash != default ? hash.GetHashCode() : 1; - return valueHash.GetChainedHashCode((uint)path.GetHashCode()) ^ hashHash; + ReturnConcurrencyQuota(); + return false; } + + public void ReturnConcurrencyQuota() => Interlocked.Increment(ref _concurrency); } + internal static class HashHelpers { private const int HashPrime = 101; diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStoreDirtyNodesCache.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStoreDirtyNodesCache.cs new file mode 100644 index 00000000000..57d3d7f83da --- /dev/null +++ b/src/Nethermind/Nethermind.Trie/Pruning/TrieStoreDirtyNodesCache.cs @@ -0,0 +1,481 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using Nethermind.Core; +using Nethermind.Core.Caching; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Logging; +using CollectionExtensions = Nethermind.Core.Collections.CollectionExtensions; + +namespace Nethermind.Trie.Pruning; + +internal class TrieStoreDirtyNodesCache +{ + private readonly TrieStore _trieStore; + private int _count = 0; + private readonly ILogger _logger; + private readonly bool _storeByHash; + private readonly ConcurrentDictionary _byKeyObjectCache; + private readonly ConcurrentDictionary _byHashObjectCache; + + // Track some of the persisted path hash. Used to be able to remove keys when it is replaced. + // If null, disable removing key. + private readonly ClockCache? _pastPathHash; + + // Track ALL of the recently re-committed persisted nodes. This is so that we don't accidentally remove + // recommitted persisted nodes (which will not get re-persisted). + private Dictionary? _persistedLastSeen; + + public readonly long KeyMemoryUsage; + + public TrieStoreDirtyNodesCache(TrieStore trieStore, int trackedPastKeyCount, bool storeByHash, ILogger logger) + { + _trieStore = trieStore; + _logger = logger; + // If the nodestore indicated that path is not required, + // we will use a map with hash as its key instead of the full Key to reduce memory usage. + _storeByHash = storeByHash; + int initialBuckets = TrieStore.HashHelpers.GetPrime(Math.Max(31, Environment.ProcessorCount * 16)); + if (_storeByHash) + { + _byHashObjectCache = new(CollectionExtensions.LockPartitions, initialBuckets); + } + else + { + _byKeyObjectCache = new(CollectionExtensions.LockPartitions, initialBuckets); + } + KeyMemoryUsage = _storeByHash ? 0 : Key.MemoryUsage; // 0 because previously it was not counted. + + if (trackedPastKeyCount > 0 && !storeByHash) + { + _persistedLastSeen = new(); + _pastPathHash = new(trackedPastKeyCount); + } + } + + public void SaveInCache(in Key key, TrieNode node) + { + Debug.Assert(node.Keccak is not null, "Cannot store in cache nodes without resolved key."); + if (TryAdd(key, node)) + { + Metrics.CachedNodesCount = Interlocked.Increment(ref _count); + _trieStore.MemoryUsedByDirtyCache += node.GetMemorySize(false) + KeyMemoryUsage; + } + } + + public TrieNode FindCachedOrUnknown(in Key key) + { + if (TryGetValue(key, out TrieNode trieNode)) + { + Metrics.LoadedFromCacheNodesCount++; + } + else + { + trieNode = new TrieNode(NodeType.Unknown, key.Keccak); + if (_logger.IsTrace) Trace(trieNode); + SaveInCache(key, trieNode); + } + + return trieNode; + + [MethodImpl(MethodImplOptions.NoInlining)] + void Trace(TrieNode trieNode) + { + _logger.Trace($"Creating new node {trieNode}"); + } + } + + public TrieNode FromCachedRlpOrUnknown(in Key key) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (TryGetValue(key, out TrieNode trieNode)) + { + if (trieNode!.FullRlp.IsNull) + { + // // this happens in SyncProgressResolver + // throw new InvalidAsynchronousStateException("Read only trie store is trying to read a transient node."); + return new TrieNode(NodeType.Unknown, key.Keccak); + } + + // we returning a copy to avoid multithreaded access + trieNode = new TrieNode(NodeType.Unknown, key.Keccak, trieNode.FullRlp); + trieNode.ResolveNode(_trieStore.GetTrieStore(key.AddressAsHash256), key.Path); + trieNode.Keccak = key.Keccak; + + Metrics.LoadedFromCacheNodesCount++; + } + else + { + trieNode = new TrieNode(NodeType.Unknown, key.Keccak); + } + + if (_logger.IsTrace) Trace(trieNode); + return trieNode; + + [MethodImpl(MethodImplOptions.NoInlining)] + void Trace(TrieNode trieNode) + { + _logger.Trace($"Creating new node {trieNode}"); + } + } + + public bool IsNodeCached(in Key key) + { + if (_storeByHash) return _byHashObjectCache.ContainsKey(key.Keccak); + return _byKeyObjectCache.ContainsKey(key); + } + + public IEnumerable> AllNodes + { + get + { + if (_storeByHash) + { + return _byHashObjectCache.Select( + pair => new KeyValuePair(new Key(null, TreePath.Empty, pair.Key.Value), pair.Value)); + } + + return _byKeyObjectCache; + } + } + + public bool TryGetValue(in Key key, out TrieNode node) + { + if (_storeByHash) + { + return _byHashObjectCache.TryGetValue(key.Keccak, out node); + } + return _byKeyObjectCache.TryGetValue(key, out node); + } + + public bool TryAdd(in Key key, TrieNode node) + { + if (_storeByHash) + { + return _byHashObjectCache.TryAdd(key.Keccak, node); + } + return _byKeyObjectCache.TryAdd(key, node); + } + + public void Remove(in Key key) + { + if (_storeByHash) + { + if (_byHashObjectCache.Remove(key.Keccak, out _)) + { + Metrics.CachedNodesCount = Interlocked.Decrement(ref _count); + } + + return; + } + if (_byKeyObjectCache.Remove(key, out _)) + { + Metrics.CachedNodesCount = Interlocked.Decrement(ref _count); + } + } + + private MapLock AcquireMapLock() + { + if (_storeByHash) + { + return new MapLock() + { + _storeByHash = _storeByHash, + _byHashLock = _byHashObjectCache.AcquireLock() + }; + } + return new MapLock() + { + _storeByHash = _storeByHash, + _byKeyLock = _byKeyObjectCache.AcquireLock() + }; + } + + public int Count => _count; + + /// + /// This method is responsible for reviewing the nodes that are directly in the cache and + /// removing ones that are either no longer referenced or already persisted. + /// + /// + public long PruneCache(bool skipRecalculateMemory = false) + { + bool shouldTrackPersistedNode = _pastPathHash is not null && !_trieStore.IsCurrentlyFullPruning; + long newMemory = 0; + + using (AcquireMapLock()) + { + foreach ((Key key, TrieNode node) in AllNodes) + { + if (node.IsPersisted) + { + if (_logger.IsTrace) _logger.Trace($"Removing persisted {node} from memory."); + + if (shouldTrackPersistedNode) + { + TrackPersistedNode(key, node); + } + + Hash256? keccak = node.Keccak; + if (keccak is null) + { + TreePath path2 = key.Path; + keccak = node.GenerateKey(_trieStore.GetTrieStore(key.AddressAsHash256), ref path2, isRoot: true); + if (keccak != key.Keccak) + { + throw new InvalidOperationException($"Persisted {node} {key} != {keccak}"); + } + + node.Keccak = keccak; + } + Remove(key); + + Metrics.PrunedPersistedNodesCount++; + } + else if (_trieStore.IsNoLongerNeeded(node)) + { + if (_logger.IsTrace) _logger.Trace($"Removing {node} from memory (no longer referenced)."); + if (node.Keccak is null) + { + throw new InvalidOperationException($"Removed {node}"); + } + Remove(key); + + Metrics.PrunedTransientNodesCount++; + } + else if (!skipRecalculateMemory) + { + node.PrunePersistedRecursively(1); + newMemory += node.GetMemorySize(false) + KeyMemoryUsage; + } + } + } + + return newMemory + (_persistedLastSeen?.Count ?? 0) * 48; + + void TrackPersistedNode(in TrieStoreDirtyNodesCache.Key key, TrieNode node) + { + if (key.Path.Length > TinyTreePath.MaxNibbleLength) return; + TinyTreePath treePath = new(key.Path); + // Persisted node with LastSeen is a node that has been re-committed, likely due to processing + // recalculated to the same hash. + if (node.LastSeen >= 0) + { + // Update _persistedLastSeen to later value. + HashAndTinyPathAndHash plsKey = new(key.Address, in treePath, key.Keccak); + if (!_persistedLastSeen.TryGetValue(plsKey, out var currentLastSeen) || currentLastSeen <= node.LastSeen) + { + _persistedLastSeen[plsKey] = node.LastSeen; + } + } + + // This persisted node is being removed from cache. Keep it in mind in case of an update to the same + // path. + _pastPathHash.Set(new(key.Address, in treePath), key.Keccak); + } + } + + + public void RemovePastKeys(ConcurrentDictionary persistedHashes, INodeStorage nodeStorage) + { + bool CanRemove(in ValueHash256 address, TinyTreePath path, in TreePath fullPath, in ValueHash256 keccak, Hash256? currentlyPersistingKeccak) + { + // Multiple current hash that we don't keep track for simplicity. Just ignore this case. + if (currentlyPersistingKeccak is null) return false; + + // The persisted hash is the same as currently persisting hash. Do nothing. + if ((ValueHash256)currentlyPersistingKeccak == keccak) return false; + + // We have it in cache and it is still needed. + if (TryGetValue(new TrieStoreDirtyNodesCache.Key(address, fullPath, keccak.ToCommitment()), out TrieNode node) && + !_trieStore.IsNoLongerNeeded(node)) return false; + + // We don't have it in cache, but we know it was re-committed, so if it is still needed, don't remove + if (_persistedLastSeen.TryGetValue(new(address, in path, in keccak), out long commitBlock) && + !_trieStore.IsNoLongerNeeded(commitBlock)) return false; + + return true; + } + + using (AcquireMapLock()) + { + INodeStorage.WriteBatch writeBatch = nodeStorage.StartWriteBatch(); + try + { + int round = 0; + foreach (KeyValuePair keyValuePair in persistedHashes) + { + HashAndTinyPath key = keyValuePair.Key; + if (_pastPathHash.TryGet(key, out ValueHash256 prevHash)) + { + TreePath fullPath = key.path.ToTreePath(); // Micro op to reduce double convert + if (CanRemove(key.addr, key.path, fullPath, prevHash, keyValuePair.Value)) + { + Metrics.RemovedNodeCount++; + Hash256? address = key.addr == default ? null : key.addr.ToCommitment(); + writeBatch.Set(address, fullPath, prevHash, default, WriteFlags.DisableWAL); + round++; + } + } + + // Batches of 256 + if (round > 256) + { + writeBatch.Dispose(); + writeBatch = nodeStorage.StartWriteBatch(); + round = 0; + } + } + } + catch (Exception ex) + { + if (_logger.IsError) _logger.Error($"Failed to remove past keys. {ex}"); + } + finally + { + writeBatch.Dispose(); + } + } + } + + public void CleanObsoletePersistedLastSeen() + { + Dictionary? persistedLastSeen = _persistedLastSeen; + + // The amount of nodes that is no longer needed is so high that creating a new dictionary is faster. + Dictionary newPersistedLastSeen = new(); + + foreach (KeyValuePair keyValuePair in persistedLastSeen) + { + if (!_trieStore.IsNoLongerNeeded(keyValuePair.Value)) + { + newPersistedLastSeen.Add(keyValuePair.Key, keyValuePair.Value); + } + } + + _persistedLastSeen = newPersistedLastSeen; + } + + public void PersistAll(INodeStorage nodeStorage, CancellationToken cancellationToken) + { + ConcurrentDictionary wasPersisted = new(); + + void PersistNode(TrieNode n, Hash256? address, TreePath path) + { + if (n.Keccak is null) return; + TrieStoreDirtyNodesCache.Key key = new TrieStoreDirtyNodesCache.Key(address, path, n.Keccak); + if (wasPersisted.TryAdd(key, true)) + { + nodeStorage.Set(address, path, n.Keccak, n.FullRlp); + n.IsPersisted = true; + } + } + + using (AcquireMapLock()) + { + foreach (KeyValuePair kv in AllNodes) + { + if (cancellationToken.IsCancellationRequested) return; + TrieStoreDirtyNodesCache.Key key = kv.Key; + TreePath path = key.Path; + Hash256? address = key.AddressAsHash256; + kv.Value.CallRecursively(PersistNode, address, ref path, _trieStore.GetTrieStore(address), false, _logger, resolveStorageRoot: false); + } + } + } + + public void Dump() + { + if (_logger.IsTrace) + { + _logger.Trace($"Trie node dirty cache ({Count})"); + foreach (KeyValuePair keyValuePair in AllNodes) + { + _logger.Trace($" {keyValuePair.Value}"); + } + } + } + + public void ClearLivePruningTracking() + { + _persistedLastSeen.Clear(); + _pastPathHash?.Clear(); + } + + public void Clear() + { + _byHashObjectCache.NoResizeClear(); + _byKeyObjectCache.NoResizeClear(); + Interlocked.Exchange(ref _count, 0); + Metrics.CachedNodesCount = 0; + _trieStore.MemoryUsedByDirtyCache = 0; + } + + internal readonly struct Key : IEquatable + { + internal const long MemoryUsage = 8 + 36 + 8; // (address (probably shared), path, keccak pointer (shared with TrieNode)) + public readonly ValueHash256 Address; + public Hash256? AddressAsHash256 => Address == default ? null : Address.ToCommitment(); + // Direct member rather than property for large struct, so members are called directly, + // rather than struct copy through the property. Could also return a ref through property. + public readonly TreePath Path; + public Hash256 Keccak { get; } + + public Key(Hash256? address, in TreePath path, Hash256 keccak) + { + Address = address ?? default; + Path = path; + Keccak = keccak; + } + public Key(in ValueHash256 address, in TreePath path, Hash256 keccak) + { + Address = address; + Path = path; + Keccak = keccak; + } + + [SkipLocalsInit] + public override int GetHashCode() + { + var addressHash = Address != default ? Address.GetHashCode() : 1; + return Keccak.ValueHash256.GetChainedHashCode((uint)Path.GetHashCode()) ^ addressHash; + } + + public bool Equals(Key other) + { + return other.Keccak == Keccak && other.Path == Path && other.Address == Address; + } + + public override bool Equals(object? obj) + { + return obj is Key other && Equals(other); + } + } + + internal ref struct MapLock + { + public bool _storeByHash; + public ConcurrentDictionaryLock.Lock _byHashLock; + public ConcurrentDictionaryLock.Lock _byKeyLock; + + public readonly void Dispose() + { + if (_storeByHash) + { + _byHashLock.Dispose(); + } + else + { + _byKeyLock.Dispose(); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Trie/TrieNode.cs b/src/Nethermind/Nethermind.Trie/TrieNode.cs index fd738105008..fe6bdfec066 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNode.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNode.cs @@ -881,6 +881,7 @@ public void CallRecursively( ITrieNodeResolver resolver, bool skipPersisted, in ILogger logger, + int maxPathLength = Int32.MaxValue, bool resolveStorageRoot = true) { if (skipPersisted && IsPersisted) @@ -889,6 +890,12 @@ public void CallRecursively( return; } + if (currentPath.Length >= maxPathLength) + { + action(this, storageAddress, currentPath); + return; + } + if (!IsLeaf) { if (_data is not null) @@ -900,7 +907,7 @@ public void CallRecursively( { if (logger.IsTrace) logger.Trace($"Persist recursively on child {i} {child} of {this}"); int previousLength = AppendChildPath(ref currentPath, i); - child.CallRecursively(action, storageAddress, ref currentPath, resolver, skipPersisted, logger); + child.CallRecursively(action, storageAddress, ref currentPath, resolver, skipPersisted, logger, maxPathLength, resolveStorageRoot); currentPath.TruncateMut(previousLength); } } diff --git a/src/Nethermind/Nethermind.Trie/TrieStoreWithReadFlags.cs b/src/Nethermind/Nethermind.Trie/TrieStoreWithReadFlags.cs index 557e519a36e..e93f1096d06 100644 --- a/src/Nethermind/Nethermind.Trie/TrieStoreWithReadFlags.cs +++ b/src/Nethermind/Nethermind.Trie/TrieStoreWithReadFlags.cs @@ -7,32 +7,15 @@ namespace Nethermind.Trie; -public class TrieStoreWithReadFlags : TrieNodeResolverWithReadFlags, IScopedTrieStore +public class TrieStoreWithReadFlags(IScopedTrieStore implementation, ReadFlags flags) + : TrieNodeResolverWithReadFlags(implementation, flags), IScopedTrieStore { - private readonly IScopedTrieStore _baseImplementation; + public ICommitter BeginCommit(TrieType trieType, long blockNumber, TrieNode? root, WriteFlags writeFlags = WriteFlags.None) => + implementation.BeginCommit(trieType, blockNumber, root, writeFlags); - public TrieStoreWithReadFlags(IScopedTrieStore implementation, ReadFlags flags) : base(implementation, flags) - { - _baseImplementation = implementation; - } + public bool IsPersisted(in TreePath path, in ValueHash256 keccak) => + implementation.IsPersisted(in path, in keccak); - public void CommitNode(long blockNumber, NodeCommitInfo nodeCommitInfo, WriteFlags writeFlags = WriteFlags.None) - { - _baseImplementation.CommitNode(blockNumber, nodeCommitInfo, writeFlags); - } - - public void FinishBlockCommit(TrieType trieType, long blockNumber, TrieNode? root, WriteFlags writeFlags = WriteFlags.None) - { - _baseImplementation.FinishBlockCommit(trieType, blockNumber, root, writeFlags); - } - - public bool IsPersisted(in TreePath path, in ValueHash256 keccak) - { - return _baseImplementation.IsPersisted(in path, in keccak); - } - - public void Set(in TreePath path, in ValueHash256 keccak, byte[] rlp) - { - _baseImplementation.Set(in path, in keccak, rlp); - } + public void Set(in TreePath path, in ValueHash256 keccak, byte[] rlp) => + implementation.Set(in path, in keccak, rlp); } diff --git a/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs b/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs index 574d7b7e0c0..a78fcb377b2 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs @@ -11,6 +11,7 @@ using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Db; +using Nethermind.Evm; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Specs; @@ -42,8 +43,8 @@ public void Setup() _blockTree.Head.Returns(block); _blockTree.FindBestSuggestedHeader().Returns(Build.A.BlockHeader.WithNumber(10000000).TestObject); - _headInfo = new ChainHeadInfoProvider(_specProvider, _blockTree, _stateProvider); - _nonceManager = new NonceManager(_headInfo.AccountStateProvider); + _headInfo = new ChainHeadInfoProvider(_specProvider, _blockTree, _stateProvider, new CodeInfoRepository()); + _nonceManager = new NonceManager(_headInfo.ReadOnlyStateProvider); } [Test] diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs index 1a6db97812a..43269050404 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs @@ -411,13 +411,13 @@ public void should_not_pick_1559_txs_with_MaxFeePerGas_lower_than_CurrentBaseFee } [Test] - public void should_not_pick_blob_txs_with_MaxFeePerBlobGas_lower_than_CurrentPricePerBlobGas([Values(1, 2, 99, 100, 101, 1000)] int threshold) + public void should_not_pick_blob_txs_with_MaxFeePerBlobGas_lower_than_CurrentFeePerBlobGas([Values(1, 2, 99, 100, 101, 1000)] int threshold) { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - const int currentPricePerBlobGasInGwei = 250; - _headInfo.CurrentPricePerBlobGas.Returns(currentPricePerBlobGasInGwei.GWei()); + const int currentFeePerBlobGas = 250; + _headInfo.CurrentFeePerBlobGas.Returns(currentFeePerBlobGas.GWei()); // add 256 transactions with MaxFeePerBlobGas 0-255 int addedTxsCount = TestItem.PrivateKeys.Length; @@ -437,7 +437,7 @@ public void should_not_pick_blob_txs_with_MaxFeePerBlobGas_lower_than_CurrentPri _broadcaster.GetSnapshot().Length.Should().Be(addedTxsCount); // count number of expected hashes to broadcast - int expectedCount = Math.Min(addedTxsCount * threshold / 100 + 1, addedTxsCount - currentPricePerBlobGasInGwei); + int expectedCount = Math.Min(addedTxsCount * threshold / 100 + 1, addedTxsCount - currentFeePerBlobGas); // prepare list of expected hashes to broadcast List expectedTxs = new(); @@ -453,7 +453,7 @@ public void should_not_pick_blob_txs_with_MaxFeePerBlobGas_lower_than_CurrentPri pickedHashes.Count.Should().Be(expectedCount); // check if number of hashes to broadcast (with MaxFeePerBlobGas >= current) is correct - expectedTxs.Count(t => t.MaxFeePerBlobGas >= (UInt256)currentPricePerBlobGasInGwei).Should().Be(expectedCount); + expectedTxs.Count(t => t.MaxFeePerBlobGas >= (UInt256)currentFeePerBlobGas).Should().Be(expectedCount); } [Test] diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs index 42fdaf69b59..b2e21635f62 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs @@ -12,6 +12,7 @@ using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Evm; +using Nethermind.Evm.Tracing.GethStyle.Custom.JavaScript; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.TxPool.Collections; @@ -346,7 +347,7 @@ public void should_dump_GasBottleneck_of_blob_tx_to_zero_if_MaxFeePerBlobGas_is_ _txPool = CreatePool(txPoolConfig, GetCancunSpecProvider()); EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); - _headInfo.CurrentPricePerBlobGas = UInt256.MaxValue; + _headInfo.CurrentFeePerBlobGas = UInt256.MaxValue; Transaction tx = Build.A.Transaction .WithType(isBlob ? TxType.Blob : TxType.EIP1559) @@ -679,6 +680,65 @@ public void should_index_blobs_when_adding_txs([Values(true, false)] bool isPers } } + [Test] + [Repeat(3)] + public void should_handle_indexing_blobs_when_adding_txs_in_parallel([Values(true, false)] bool isPersistentStorage) + { + const int txsPerSender = 10; + int poolSize = TestItem.PrivateKeys.Length * txsPerSender; + TxPoolConfig txPoolConfig = new() + { + BlobsSupport = isPersistentStorage ? BlobsSupportMode.Storage : BlobsSupportMode.InMemory, + PersistentBlobStorageSize = isPersistentStorage ? poolSize : 0, + InMemoryBlobPoolSize = isPersistentStorage ? 0 : poolSize + }; + + IComparer comparer = new TransactionComparerProvider(_specProvider, _blockTree).GetDefaultComparer(); + + BlobTxDistinctSortedPool blobPool = isPersistentStorage + ? new PersistentBlobTxDistinctSortedPool(new BlobTxStorage(), txPoolConfig, comparer, LimboLogs.Instance) + : new BlobTxDistinctSortedPool(txPoolConfig.InMemoryBlobPoolSize, comparer, LimboLogs.Instance); + + byte[] expectedBlobVersionedHash = null; + + foreach (PrivateKey privateKey in TestItem.PrivateKeys) + { + EnsureSenderBalance(privateKey.Address, UInt256.MaxValue); + } + + // adding, getting and removing txs in parallel + Parallel.ForEach(TestItem.PrivateKeys, privateKey => + { + for (int i = 0; i < txsPerSender; i++) + { + Transaction tx = Build.A.Transaction + .WithNonce((UInt256)i) + .WithShardBlobTxTypeAndFields() + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithMaxFeePerBlobGas(1000.Wei()) + .SignedAndResolved(_ethereumEcdsa, privateKey).TestObject; + + expectedBlobVersionedHash ??= tx.BlobVersionedHashes[0]!; + + blobPool.TryInsert(tx.Hash, tx, out _).Should().BeTrue(); + + for (int j = 0; j < 100; j++) + { + blobPool.TryGetBlobAndProof(expectedBlobVersionedHash.ToBytes(), out _, out _).Should().BeTrue(); + } + + // removing 50% of txs + if (i % 2 == 0) blobPool.TryRemove(tx.Hash, out _).Should().BeTrue(); + } + }); + + // we expect index to have 1 key with poolSize/2 values (50% of txs were removed) + blobPool.BlobIndex.Count.Should().Be(1); + blobPool.BlobIndex.TryGetValue(expectedBlobVersionedHash, out List values).Should().BeTrue(); + values.Count.Should().Be(poolSize / 2); + } + private Transaction GetTx(PrivateKey sender) { return Build.A.Transaction diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs index bdb1a26a5a6..4e7f10b33b2 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs @@ -1660,7 +1660,7 @@ public void Should_correctly_add_tx_to_local_pool_when_underpaid([Values] TxType // No need to check for deposit tx if (txType == TxType.DepositTx) return; - ISpecProvider specProvider = GetCancunSpecProvider(); + ISpecProvider specProvider = GetPragueSpecProvider(); TxPoolConfig txPoolConfig = new TxPoolConfig { Size = 30, PersistentBlobStorageSize = 0 }; _txPool = CreatePool(txPoolConfig, specProvider); @@ -1684,8 +1684,10 @@ public void Should_correctly_add_tx_to_local_pool_when_underpaid([Values] TxType .WithNonce(0) .WithType(txType) .WithShardBlobTxTypeAndFieldsIfBlobTx() + .WithAuthorizationCodeIfAuthorizationListTx() .WithMaxFeePerGas(9.GWei()) .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(txType != TxType.SetCode ? GasCostOf.Transaction : GasCostOf.Transaction + GasCostOf.NewAccount) .WithTo(TestItem.AddressB) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; @@ -1698,6 +1700,35 @@ public void Should_correctly_add_tx_to_local_pool_when_underpaid([Values] TxType _txPool.GetPendingTransactions().Should().NotContain(testTx); } + static IEnumerable<(byte[], AcceptTxResult)> CodeCases() + { + yield return (new byte[16], AcceptTxResult.SenderIsContract); + //Delegation code + yield return ([.. Eip7702Constants.DelegationHeader, .. new byte[20]], AcceptTxResult.Accepted); + } + [TestCaseSource(nameof(CodeCases))] + public void SubmitTx_CodeIsNotDelegationAndDelegation_DelegationIsAccepted((byte[] code, AcceptTxResult expected) testCase) + { + ISpecProvider specProvider = GetPragueSpecProvider(); + TxPoolConfig txPoolConfig = new TxPoolConfig { Size = 30, PersistentBlobStorageSize = 0 }; + _txPool = CreatePool(txPoolConfig, specProvider); + + Transaction testTx = Build.A.Transaction + .WithNonce(0) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(100_000) + .WithTo(TestItem.AddressB) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; + + EnsureSenderBalance(TestItem.PrivateKeyA.Address, UInt256.MaxValue); + + _stateProvider.InsertCode(TestItem.PrivateKeyA.Address, testCase.code, Prague.Instance); + + AcceptTxResult result = _txPool.SubmitTx(testTx, TxHandlingOptions.PersistentBroadcast); + result.Should().Be(testCase.expected); + } + private IDictionary GetPeers(int limit = 100) { var peers = new Dictionary(); @@ -1726,7 +1757,7 @@ private TxPool CreatePool( txStorage ??= new BlobTxStorage(); _headInfo = chainHeadInfoProvider; - _headInfo ??= new ChainHeadInfoProvider(specProvider, _blockTree, _stateProvider); + _headInfo ??= new ChainHeadInfoProvider(specProvider, _blockTree, _stateProvider, new CodeInfoRepository()); return new TxPool( _ethereumEcdsa, @@ -1763,6 +1794,13 @@ private static ISpecProvider GetCancunSpecProvider() return specProvider; } + private static ISpecProvider GetPragueSpecProvider() + { + var specProvider = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(Prague.Instance); + return specProvider; + } + private Transaction[] AddTransactionsToPool(bool sameTransactionSenderPerPeer = true, bool sameNoncePerPeer = false, int transactionsPerPeer = 10) { var transactions = GetTransactions(GetPeers(transactionsPerPeer), sameTransactionSenderPerPeer, sameNoncePerPeer); diff --git a/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs b/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs index d447cf4af9f..e924b9e5733 100644 --- a/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs +++ b/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -17,7 +17,7 @@ public class BlobTxDistinctSortedPool(int capacity, IComparer compa { protected override string ShortPoolName => "BlobPool"; - internal readonly ConcurrentDictionary> BlobIndex = new(Bytes.EqualityComparer); + internal readonly Dictionary> BlobIndex = new(Bytes.EqualityComparer); protected override IComparer GetReplacementComparer(IComparer comparer) => comparer.GetBlobReplacementComparer(); @@ -26,19 +26,24 @@ public bool TryGetBlobAndProof(byte[] requestedBlobVersionedHash, [NotNullWhen(true)] out byte[]? blob, [NotNullWhen(true)] out byte[]? proof) { - if (BlobIndex.TryGetValue(requestedBlobVersionedHash, out List? txHashes) - && txHashes[0] is not null - && TryGetValue(txHashes[0], out Transaction? blobTx) - && blobTx.BlobVersionedHashes?.Length > 0) + using var lockRelease = Lock.Acquire(); + + if (BlobIndex.TryGetValue(requestedBlobVersionedHash, out List? txHashes)) { - for (int indexOfBlob = 0; indexOfBlob < blobTx.BlobVersionedHashes.Length; indexOfBlob++) + foreach (Hash256 hash in CollectionsMarshal.AsSpan(txHashes)) { - if (Bytes.AreEqual(blobTx.BlobVersionedHashes[indexOfBlob], requestedBlobVersionedHash) - && blobTx.NetworkWrapper is ShardBlobNetworkWrapper wrapper) + if (TryGetValueNonLocked(hash, out Transaction? blobTx) && blobTx.BlobVersionedHashes?.Length > 0) { - blob = wrapper.Blobs[indexOfBlob]; - proof = wrapper.Proofs[indexOfBlob]; - return true; + for (int indexOfBlob = 0; indexOfBlob < blobTx.BlobVersionedHashes.Length; indexOfBlob++) + { + if (Bytes.AreEqual(blobTx.BlobVersionedHashes[indexOfBlob], requestedBlobVersionedHash) + && blobTx.NetworkWrapper is ShardBlobNetworkWrapper wrapper) + { + blob = wrapper.Blobs[indexOfBlob]; + proof = wrapper.Proofs[indexOfBlob]; + return true; + } + } } } } @@ -48,18 +53,18 @@ public bool TryGetBlobAndProof(byte[] requestedBlobVersionedHash, return false; } - public override bool TryInsert(ValueHash256 hash, Transaction blobTx, out Transaction? removed) + protected override bool InsertCore(ValueHash256 key, Transaction value, AddressAsKey groupKey) { - if (base.TryInsert(blobTx.Hash, blobTx, out removed)) + if (base.InsertCore(key, value, groupKey)) { - AddToBlobIndex(blobTx); + AddToBlobIndex(value); return true; } return false; } - protected void AddToBlobIndex(Transaction blobTx) + private void AddToBlobIndex(Transaction blobTx) { if (blobTx.BlobVersionedHashes?.Length > 0) { @@ -67,13 +72,9 @@ protected void AddToBlobIndex(Transaction blobTx) { if (blobVersionedHash?.Length == KzgPolynomialCommitments.BytesPerBlobVersionedHash) { - BlobIndex.AddOrUpdate(blobVersionedHash, - k => [blobTx.Hash!], - (k, b) => - { - b.Add(blobTx.Hash!); - return b; - }); + ref List? list = ref CollectionsMarshal.GetValueRefOrAddDefault(BlobIndex, blobVersionedHash, out _); + list ??= new List(); + list.Add(blobTx.Hash!); } } } @@ -99,8 +100,7 @@ private void RemoveFromBlobIndex(Transaction blobTx) { foreach (var blobVersionedHash in blobTx.BlobVersionedHashes) { - if (blobVersionedHash is not null - && BlobIndex.TryGetValue(blobVersionedHash, out List? txHashes)) + if (blobVersionedHash is not null && BlobIndex.TryGetValue(blobVersionedHash, out List? txHashes)) { if (txHashes.Count < 2) { @@ -119,5 +119,5 @@ private void RemoveFromBlobIndex(Transaction blobTx) /// For tests only - to test sorting /// internal void TryGetBlobTxSortingEquivalent(Hash256 hash, out Transaction? lightBlobTx) - => base.TryGetValue(hash, out lightBlobTx); + => base.TryGetValueNonLocked(hash, out lightBlobTx); } diff --git a/src/Nethermind/Nethermind.TxPool/Collections/DistinctValueSortedPool.cs b/src/Nethermind/Nethermind.TxPool/Collections/DistinctValueSortedPool.cs index c37a14346d0..86821529b2d 100644 --- a/src/Nethermind/Nethermind.TxPool/Collections/DistinctValueSortedPool.cs +++ b/src/Nethermind/Nethermind.TxPool/Collections/DistinctValueSortedPool.cs @@ -45,16 +45,20 @@ protected DistinctValueSortedPool( protected virtual IComparer GetReplacementComparer(IComparer comparer) => comparer; - protected override void InsertCore(TKey key, TValue value, TGroupKey groupKey) + protected override bool InsertCore(TKey key, TValue value, TGroupKey groupKey) { if (_distinctDictionary.TryGetValue(value, out KeyValuePair oldKvp)) { TryRemoveNonLocked(oldKvp.Key, evicted: false, out _, out _); } - base.InsertCore(key, value, groupKey); + if (base.InsertCore(key, value, groupKey)) + { + _distinctDictionary[value] = new KeyValuePair(key, value); + return true; + } - _distinctDictionary[value] = new KeyValuePair(key, value); + return false; } protected override bool Remove(TKey key, out TValue? value) diff --git a/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs b/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs index d0f7eb34d92..f17b0f51866 100644 --- a/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs +++ b/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs @@ -33,12 +33,12 @@ private void RecreateLightTxCollectionAndCache(ITxStorage blobTxStorage) if (_logger.IsDebug) _logger.Debug("Recreating light collection of blob transactions and cache"); int numberOfTxsInDb = 0; int numberOfBlobsInDb = 0; - Stopwatch stopwatch = Stopwatch.StartNew(); + long startTime = Stopwatch.GetTimestamp(); foreach (LightTransaction lightBlobTx in blobTxStorage.GetAll()) { - if (base.TryInsert(lightBlobTx.Hash, lightBlobTx, out _)) + if (lightBlobTx.SenderAddress is not null + && base.InsertCore(lightBlobTx.Hash, lightBlobTx, lightBlobTx.SenderAddress)) { - AddToBlobIndex(lightBlobTx); numberOfTxsInDb++; numberOfBlobsInDb += lightBlobTx.BlobVersionedHashes?.Length ?? 0; } @@ -46,16 +46,15 @@ private void RecreateLightTxCollectionAndCache(ITxStorage blobTxStorage) if (_logger.IsInfo && numberOfTxsInDb != 0) { - long loadingTime = stopwatch.ElapsedMilliseconds; - _logger.Info($"Loaded {numberOfTxsInDb} blob txs from persistent db, containing {numberOfBlobsInDb} blobs, in {loadingTime}ms"); + long loadingTime = (long)Stopwatch.GetElapsedTime(startTime).TotalMilliseconds; + _logger.Info($"Loaded {numberOfTxsInDb} blob txs from persistent db, containing {numberOfBlobsInDb} blobs, in {loadingTime:N0}ms"); _logger.Info($"There are {BlobIndex.Count} unique blobs indexed"); } - stopwatch.Stop(); } - public override bool TryInsert(ValueHash256 hash, Transaction fullBlobTx, out Transaction? removed) + protected override bool InsertCore(ValueHash256 hash, Transaction fullBlobTx, AddressAsKey groupKey) { - if (base.TryInsert(fullBlobTx.Hash, new LightTransaction(fullBlobTx), out removed)) + if (base.InsertCore(hash, new LightTransaction(fullBlobTx), groupKey)) { _blobTxCache.Set(fullBlobTx.Hash, fullBlobTx); _blobTxStorage.Add(fullBlobTx); @@ -65,11 +64,11 @@ public override bool TryInsert(ValueHash256 hash, Transaction fullBlobTx, out Tr return false; } - public override bool TryGetValue(ValueHash256 hash, [NotNullWhen(true)] out Transaction? fullBlobTx) + protected override bool TryGetValueNonLocked(ValueHash256 hash, [NotNullWhen(true)] out Transaction? fullBlobTx) { // Firstly check if tx is present in in-memory collection of light blob txs (without actual blobs). // If not, just return false - if (base.TryGetValue(hash, out Transaction? lightTx)) + if (base.TryGetValueNonLocked(hash, out Transaction? lightTx)) { // tx is present in light collection. Try to get full blob tx from cache if (_blobTxCache.TryGet(hash, out fullBlobTx)) diff --git a/src/Nethermind/Nethermind.TxPool/Collections/SortedPool.cs b/src/Nethermind/Nethermind.TxPool/Collections/SortedPool.cs index 98a32ce04e3..f5ebedda4d4 100644 --- a/src/Nethermind/Nethermind.TxPool/Collections/SortedPool.cs +++ b/src/Nethermind/Nethermind.TxPool/Collections/SortedPool.cs @@ -336,13 +336,15 @@ public bool ContainsKey(TKey key) /// Key to be returned. /// Returned element or null. /// If element retrieval succeeded. True if element was present in pool. - public virtual bool TryGetValue(TKey key, [NotNullWhen(true)] out TValue? value) + public bool TryGetValue(TKey key, [NotNullWhen(true)] out TValue? value) { using var lockRelease = Lock.Acquire(); - return _cacheMap.TryGetValue(key, out value) && value is not null; + return TryGetValueNonLocked(key, out value); } + protected virtual bool TryGetValueNonLocked(TKey key, [NotNullWhen(true)] out TValue? value) => _cacheMap.TryGetValue(key, out value) && value is not null; + /// /// Tries to insert element. /// @@ -350,7 +352,7 @@ public virtual bool TryGetValue(TKey key, [NotNullWhen(true)] out TValue? value) /// Element to insert. /// Element removed because of exceeding capacity /// If element was inserted. False if element was already present in pool. - public virtual bool TryInsert(TKey key, TValue value, out TValue? removed) + public bool TryInsert(TKey key, TValue value, out TValue? removed) { using var lockRelease = Lock.Acquire(); @@ -360,7 +362,7 @@ public virtual bool TryInsert(TKey key, TValue value, out TValue? removed) if (group is not null) { - InsertCore(key, value, group); + bool inserted = InsertCore(key, value, group); if (_cacheMap.Count > _capacity) { @@ -372,11 +374,11 @@ public virtual bool TryInsert(TKey key, TValue value, out TValue? removed) RemoveLast(out removed); } - return true; + return inserted; } removed = default; - return true; + return inserted; } } @@ -430,7 +432,7 @@ protected virtual bool CanInsert(TKey key, TValue value) /// /// Actual insert mechanism. /// - protected virtual void InsertCore(TKey key, TValue value, TGroupKey groupKey) + protected virtual bool InsertCore(TKey key, TValue value, TGroupKey groupKey) { if (!_buckets.TryGetValue(groupKey, out EnhancedSortedSet? bucket)) { @@ -445,7 +447,10 @@ protected virtual void InsertCore(TKey key, TValue value, TGroupKey groupKey) UpdateSortedValues(bucket, last); _snapshot = null; Inserted?.Invoke(this, new SortedPoolEventArgs(key, value)); + return true; } + + return false; } private void UpdateSortedValues(EnhancedSortedSet bucket, TValue? previousLast) diff --git a/src/Nethermind/Nethermind.TxPool/Filters/DeployedCodeFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/DeployedCodeFilter.cs index 12d6e5a7035..1eb06f8dcc8 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/DeployedCodeFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/DeployedCodeFilter.cs @@ -3,23 +3,21 @@ using Nethermind.Core; using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.State; namespace Nethermind.TxPool.Filters { /// /// Filters out transactions that sender has any code deployed. If is enabled. /// - internal sealed class DeployedCodeFilter : IIncomingTxFilter + internal sealed class DeployedCodeFilter(IReadOnlyStateProvider worldState, ICodeInfoRepository codeInfoRepository, IChainHeadSpecProvider specProvider) : IIncomingTxFilter { - private readonly IChainHeadSpecProvider _specProvider; - - public DeployedCodeFilter(IChainHeadSpecProvider specProvider) - { - _specProvider = specProvider; - } public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions txHandlingOptions) { - return _specProvider.GetCurrentHeadSpec().IsEip3607Enabled && state.SenderAccount.HasCode + return worldState.IsInvalidContractSender(specProvider.GetCurrentHeadSpec(), + tx.SenderAddress!, + () => codeInfoRepository.TryGetDelegation(worldState, tx.SenderAddress!, out _)) ? AcceptTxResult.SenderIsContract : AcceptTxResult.Accepted; } diff --git a/src/Nethermind/Nethermind.TxPool/Filters/IsValidTxSender.cs b/src/Nethermind/Nethermind.TxPool/Filters/IsValidTxSender.cs new file mode 100644 index 00000000000..f9077d6c966 --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool/Filters/IsValidTxSender.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.State; + +namespace Nethermind.TxPool.Filters; +public class IsValidTxSender(IWorldState worldState, ICodeInfoRepository codeInfoRepository, IChainHeadSpecProvider specProvider) +{ + public bool IsValid(Address sender) + { + return specProvider.GetCurrentHeadSpec().IsEip3607Enabled + && worldState.HasCode(sender) + && (!specProvider.GetCurrentHeadSpec().IsEip7702Enabled || !codeInfoRepository.TryGetDelegation(worldState, sender, out _)); + } +} diff --git a/src/Nethermind/Nethermind.TxPool/Filters/RecoverAuthorityFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/RecoverAuthorityFilter.cs new file mode 100644 index 00000000000..8075d9d14dd --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool/Filters/RecoverAuthorityFilter.cs @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Crypto; + +namespace Nethermind.TxPool.Filters +{ + /// + /// Will recover authority from transactions with authority_list + /// /// + internal sealed class RecoverAuthorityFilter(IEthereumEcdsa ecdsa) : IIncomingTxFilter + { + public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions handlingOptions) + { + if (tx.HasAuthorizationList) + { + foreach (AuthorizationTuple tuple in tx.AuthorizationList) + { + tuple.Authority ??= ecdsa.RecoverAddress(tuple); + } + } + + return AcceptTxResult.Accepted; + } + } +} diff --git a/src/Nethermind/Nethermind.TxPool/IChainHeadInfoProvider.cs b/src/Nethermind/Nethermind.TxPool/IChainHeadInfoProvider.cs index 457322785a4..ca89bd82aec 100644 --- a/src/Nethermind/Nethermind.TxPool/IChainHeadInfoProvider.cs +++ b/src/Nethermind/Nethermind.TxPool/IChainHeadInfoProvider.cs @@ -4,7 +4,9 @@ using System; using Nethermind.Core; using Nethermind.Core.Specs; +using Nethermind.Evm; using Nethermind.Int256; +using Nethermind.State; namespace Nethermind.TxPool { @@ -12,15 +14,17 @@ public interface IChainHeadInfoProvider { IChainHeadSpecProvider SpecProvider { get; } - IAccountStateProvider AccountStateProvider { get; } + IReadOnlyStateProvider ReadOnlyStateProvider { get; } - public long HeadNumber { get; } + ICodeInfoRepository CodeInfoRepository { get; } - public long? BlockGasLimit { get; } + long HeadNumber { get; } - public UInt256 CurrentBaseFee { get; } + long? BlockGasLimit { get; } - public UInt256 CurrentPricePerBlobGas { get; } + UInt256 CurrentBaseFee { get; } + + public UInt256 CurrentFeePerBlobGas { get; } event EventHandler HeadChanged; } diff --git a/src/Nethermind/Nethermind.TxPool/Metrics.cs b/src/Nethermind/Nethermind.TxPool/Metrics.cs index 53721d957fe..d825ad8060b 100644 --- a/src/Nethermind/Nethermind.TxPool/Metrics.cs +++ b/src/Nethermind/Nethermind.TxPool/Metrics.cs @@ -122,6 +122,10 @@ public static class Metrics [Description("Ratio of 1559-type transactions in the block.")] public static float Eip1559TransactionsRatio { get; set; } + [GaugeMetric] + [Description("Ratio of 7702-type transactions in the block.")] + public static long Eip7702TransactionsInBlock { get; set; } + [GaugeMetric] [Description("Number of blob transactions in the block.")] public static long BlobTransactionsInBlock { get; set; } diff --git a/src/Nethermind/Nethermind.TxPool/Nethermind.TxPool.csproj b/src/Nethermind/Nethermind.TxPool/Nethermind.TxPool.csproj index 2f6f825141f..7c667ed34e8 100644 --- a/src/Nethermind/Nethermind.TxPool/Nethermind.TxPool.csproj +++ b/src/Nethermind/Nethermind.TxPool/Nethermind.TxPool.csproj @@ -13,6 +13,8 @@ + + diff --git a/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs b/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs index 0e61bb15b55..937f0cc83d3 100644 --- a/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs +++ b/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs @@ -246,7 +246,7 @@ internal void BroadcastPersistentTxs() } else { - if (!tx.CanPayForBlobGas(_headInfo.CurrentPricePerBlobGas)) + if (!tx.CanPayForBlobGas(_headInfo.CurrentFeePerBlobGas)) { continue; } diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index b79fce3ce91..aea6460a8ff 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -103,7 +103,7 @@ public TxPool(IEthereumEcdsa ecdsa, _headInfo = chainHeadInfoProvider ?? throw new ArgumentNullException(nameof(chainHeadInfoProvider)); _txPoolConfig = txPoolConfig; _blobReorgsSupportEnabled = txPoolConfig.BlobsSupport.SupportsReorgs(); - _accounts = _accountCache = new AccountCache(_headInfo.AccountStateProvider); + _accounts = _accountCache = new AccountCache(_headInfo.ReadOnlyStateProvider); _specProvider = _headInfo.SpecProvider; MemoryAllowance.MemPoolSize = txPoolConfig.Size; @@ -124,34 +124,36 @@ public TxPool(IEthereumEcdsa ecdsa, _headInfo.HeadChanged += OnHeadChange; - _preHashFilters = new IIncomingTxFilter[] - { + _preHashFilters = + [ new NotSupportedTxFilter(txPoolConfig, _logger), new GasLimitTxFilter(_headInfo, txPoolConfig, _logger), new PriorityFeeTooLowFilter(_logger), new FeeTooLowFilter(_headInfo, _transactions, _blobTransactions, thereIsPriorityContract, _logger), new MalformedTxFilter(_specProvider, validator, _logger) - }; + ]; - List postHashFilters = new() - { + List postHashFilters = + [ new NullHashTxFilter(), // needs to be first as it assigns the hash new AlreadyKnownTxFilter(_hashCache, _logger), new UnknownSenderFilter(ecdsa, _logger), - new TxTypeTxFilter(_transactions, _blobTransactions), // has to be after UnknownSenderFilter as it uses sender + new TxTypeTxFilter(_transactions, + _blobTransactions), // has to be after UnknownSenderFilter as it uses sender new BalanceZeroFilter(thereIsPriorityContract, _logger), new BalanceTooLowFilter(_transactions, _blobTransactions, _logger), new LowNonceFilter(_logger), // has to be after UnknownSenderFilter as it uses sender new FutureNonceFilter(txPoolConfig), new GapNonceFilter(_transactions, _blobTransactions, _logger), - }; + new RecoverAuthorityFilter(ecdsa) + ]; if (incomingTxFilter is not null) { postHashFilters.Add(incomingTxFilter); } - postHashFilters.Add(new DeployedCodeFilter(_specProvider)); + postHashFilters.Add(new DeployedCodeFilter(chainHeadInfoProvider.ReadOnlyStateProvider, chainHeadInfoProvider.CodeInfoRepository, _specProvider)); _postHashFilters = postHashFilters.ToArray(); @@ -304,6 +306,7 @@ private void RemoveProcessedTransactions(Block block) long discoveredForPendingTxs = 0; long discoveredForHashCache = 0; long eip1559Txs = 0; + long eip7702Txs = 0; long blobTxs = 0; long blobs = 0; @@ -333,6 +336,11 @@ private void RemoveProcessedTransactions(Block block) } } + if (blockTx.Type == TxType.SetCode) + { + eip7702Txs++; + } + if (!IsKnown(txHash)) { discoveredForHashCache++; @@ -355,6 +363,7 @@ private void RemoveProcessedTransactions(Block block) Metrics.DarkPoolRatioLevel1 = (float)discoveredForHashCache / transactionsInBlock; Metrics.DarkPoolRatioLevel2 = (float)discoveredForPendingTxs / transactionsInBlock; Metrics.Eip1559TransactionsRatio = (float)eip1559Txs / transactionsInBlock; + Metrics.Eip7702TransactionsInBlock = eip7702Txs; Metrics.BlobTransactionsInBlock = blobTxs; Metrics.BlobsInBlock = blobs; } @@ -558,7 +567,7 @@ private void UpdateGasBottleneck( _headInfo.CurrentBaseFee, balance); // it is not affecting non-blob txs - for them MaxFeePerBlobGas is null so check is skipped - if (tx.MaxFeePerBlobGas < _headInfo.CurrentPricePerBlobGas) + if (tx.MaxFeePerBlobGas < _headInfo.CurrentFeePerBlobGas) { gasBottleneck = UInt256.Zero; } @@ -905,6 +914,7 @@ private static void WriteTxPoolReport(in ILogger logger) ------------------------------------------------ Ratios in last block: * Eip1559 Transactions: {Metrics.Eip1559TransactionsRatio,24:P5} +* Eip7702 Transactions: {Metrics.Eip7702TransactionsInBlock,24:P5} * DarkPool Level1: {Metrics.DarkPoolRatioLevel1,24:P5} * DarkPool Level2: {Metrics.DarkPoolRatioLevel2,24:P5} Amounts: diff --git a/src/Nethermind/Nethermind.sln b/src/Nethermind/Nethermind.sln index a0ac7f0e84c..68a7e1f8b66 100644 --- a/src/Nethermind/Nethermind.sln +++ b/src/Nethermind/Nethermind.sln @@ -218,6 +218,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Signer", "Signer", "{89311B EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nethermind.ExternalSigner.Plugin", "Nethermind.ExternalSigner.Plugin\Nethermind.ExternalSigner.Plugin.csproj", "{6528010D-7DCE-4935-9785-5270FF515F3E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nethermind.Shutter", "Nethermind.Shutter\Nethermind.Shutter.csproj", "{F38037D2-98EA-4263-887A-4B383635F605}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nethermind.Shutter.Test", "Nethermind.Shutter.Test\Nethermind.Shutter.Test.csproj", "{CEA1C413-A96C-4339-AC1C-839B603DECC8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -600,6 +604,14 @@ Global {6528010D-7DCE-4935-9785-5270FF515F3E}.Debug|Any CPU.Build.0 = Debug|Any CPU {6528010D-7DCE-4935-9785-5270FF515F3E}.Release|Any CPU.ActiveCfg = Release|Any CPU {6528010D-7DCE-4935-9785-5270FF515F3E}.Release|Any CPU.Build.0 = Release|Any CPU + {F38037D2-98EA-4263-887A-4B383635F605}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F38037D2-98EA-4263-887A-4B383635F605}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F38037D2-98EA-4263-887A-4B383635F605}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F38037D2-98EA-4263-887A-4B383635F605}.Release|Any CPU.Build.0 = Release|Any CPU + {CEA1C413-A96C-4339-AC1C-839B603DECC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CEA1C413-A96C-4339-AC1C-839B603DECC8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CEA1C413-A96C-4339-AC1C-839B603DECC8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CEA1C413-A96C-4339-AC1C-839B603DECC8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/bench_precompiles b/src/bench_precompiles index d083d17d367..c1089350ef0 160000 --- a/src/bench_precompiles +++ b/src/bench_precompiles @@ -1 +1 @@ -Subproject commit d083d17d3679b82585877a2d18829535d972546a +Subproject commit c1089350ef0daa3f0bd437ce9d1626de973f9a93 diff --git a/tools/docgen/.editorconfig b/tools/DocGen/.editorconfig similarity index 100% rename from tools/docgen/.editorconfig rename to tools/DocGen/.editorconfig diff --git a/tools/docgen/ConfigGenerator.cs b/tools/DocGen/ConfigGenerator.cs similarity index 81% rename from tools/docgen/ConfigGenerator.cs rename to tools/DocGen/ConfigGenerator.cs index 15e329b4e7a..fd529ad2350 100644 --- a/tools/docgen/ConfigGenerator.cs +++ b/tools/DocGen/ConfigGenerator.cs @@ -88,13 +88,7 @@ private static void WriteMarkdown(StreamWriter file, Type configType) var moduleName = configType.Name[1..].Replace("Config", null); file.WriteLine($""" -
- - - #### {moduleName} - - -

+ ### {moduleName} """); @@ -107,10 +101,32 @@ private static void WriteMarkdown(StreamWriter file, Type configType) var description = itemAttr.Description.Replace("\n", "\n ").TrimEnd(' '); - file.Write($""" - - **`--{moduleName}.{prop.Name} `** `NETHERMIND_{moduleName.ToUpperInvariant()}CONFIG_{prop.Name.ToUpperInvariant()}` - - {description} + file.Write($$""" + - #### `{{moduleName}}.{{prop.Name}}` \{#{{moduleName.ToLowerInvariant()}}-{{prop.Name.ToLowerInvariant()}}\} + + + + ``` + --{{moduleName}}.{{prop.Name}} + ``` + + + ``` + NETHERMIND_{{moduleName.ToUpperInvariant()}}CONFIG_{{prop.Name.ToUpperInvariant()}}= + ``` + + + ```json + { + "{{moduleName}}": { + "{{prop.Name}}": + } + } + ``` + + + + {{description}} """); var startsFromNewLine = WriteAllowedValues(file, prop.PropertyType) || description.EndsWith('\n'); @@ -121,11 +137,7 @@ private static void WriteMarkdown(StreamWriter file, Type configType) file.WriteLine(); } - file.WriteLine(""" -

-
- - """); + file.WriteLine(); } private static bool WriteAllowedValues(StreamWriter file, Type type) diff --git a/tools/docgen/DBSizeGenerator.cs b/tools/DocGen/DBSizeGenerator.cs similarity index 78% rename from tools/docgen/DBSizeGenerator.cs rename to tools/DocGen/DBSizeGenerator.cs index 761cad555ea..f78ce87ae20 100644 --- a/tools/docgen/DBSizeGenerator.cs +++ b/tools/DocGen/DBSizeGenerator.cs @@ -8,7 +8,6 @@ namespace Nethermind.DocGen; internal static class DBSizeGenerator { - private const string _chainSizesDir = "chainSizes"; private const string _startMark = ""; private const string _endMark = ""; @@ -23,7 +22,7 @@ internal static class DBSizeGenerator "blobTransactions" ]; - internal static void Generate(string path) + internal static void Generate(string docsPath, string? dbSizeSourcePath) { IList chainOrder = [ @@ -36,9 +35,10 @@ internal static void Generate(string path) "volta" ]; - var chainSizesPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, _chainSizesDir); + dbSizeSourcePath ??= AppDomain.CurrentDomain.BaseDirectory; + var chains = Directory - .GetFiles(chainSizesPath) + .GetFiles(dbSizeSourcePath) .Select(Path.GetFileNameWithoutExtension) .OrderBy(c => { @@ -48,14 +48,14 @@ internal static void Generate(string path) }) .ToList(); - GenerateFile(Path.Join(path, "docs", "fundamentals"), chains!); - GenerateFile(Path.Join(path, "versioned_docs", $"version-{GetLatestVersion(path)}", "fundamentals"), chains!); + GenerateFile(Path.Join(docsPath, "docs", "fundamentals"), dbSizeSourcePath, chains!); + GenerateFile(Path.Join(docsPath, "versioned_docs", $"version-{GetLatestVersion(docsPath)}", "fundamentals"), dbSizeSourcePath, chains!); } - private static void GenerateFile(string path, IList chains) + private static void GenerateFile(string docsPath, string dbSizeSourcePath, IList chains) { - var fileName = Path.Join(path, "database.md"); - var tempFileName = Path.Join(path, "~database.md"); + var fileName = Path.Join(docsPath, "database.md"); + var tempFileName = Path.Join(docsPath, "~database.md"); // Delete the temp file if it exists File.Delete(tempFileName); @@ -77,7 +77,7 @@ private static void GenerateFile(string path, IList chains) writeStream.WriteLine(); - WriteMarkdown(writeStream, chains!); + WriteMarkdown(writeStream, dbSizeSourcePath, chains!); var skip = true; @@ -103,12 +103,12 @@ private static void GenerateFile(string path, IList chains) AnsiConsole.MarkupLine($"[green]Updated[/] {fileName}"); } - private static void WriteMarkdown(StreamWriter file, IList chains) + private static void WriteMarkdown(StreamWriter file, string dbSizeSourcePath, IList chains) { file.WriteLine(""); foreach (var chain in chains) - WriteChainSize(file, chain); + WriteChainSize(file, dbSizeSourcePath, chain); file.WriteLine(""" @@ -116,9 +116,10 @@ private static void WriteMarkdown(StreamWriter file, IList chains) """); } - private static void WriteChainSize(StreamWriter file, string chain) + private static void WriteChainSize(StreamWriter file, string dbSizeSourcePath, string chain) { - using var json = JsonDocument.Parse(File.ReadAllText($"{_chainSizesDir}/{chain}.json")); + var path = Path.Join(dbSizeSourcePath, $"{chain}.json"); + using var json = JsonDocument.Parse(File.ReadAllText(path)); if (json.RootElement.ValueKind != JsonValueKind.Object) return; diff --git a/tools/docgen/DocGen.csproj b/tools/DocGen/DocGen.csproj similarity index 100% rename from tools/docgen/DocGen.csproj rename to tools/DocGen/DocGen.csproj diff --git a/tools/docgen/DocGen.sln b/tools/DocGen/DocGen.sln similarity index 100% rename from tools/docgen/DocGen.sln rename to tools/DocGen/DocGen.sln diff --git a/tools/docgen/JsonRpcGenerator.cs b/tools/DocGen/JsonRpcGenerator.cs similarity index 100% rename from tools/docgen/JsonRpcGenerator.cs rename to tools/DocGen/JsonRpcGenerator.cs diff --git a/tools/docgen/MetricsGenerator.cs b/tools/DocGen/MetricsGenerator.cs similarity index 91% rename from tools/docgen/MetricsGenerator.cs rename to tools/DocGen/MetricsGenerator.cs index 95e7f859851..697b0381772 100644 --- a/tools/docgen/MetricsGenerator.cs +++ b/tools/DocGen/MetricsGenerator.cs @@ -85,13 +85,7 @@ private static void WriteMarkdown(StreamWriter file, Type metricsType) return; file.WriteLine($""" -
- - - #### {GetNamespace(metricsType.FullName)} - - -

+ ### {GetNamespace(metricsType.FullName)} """); @@ -100,7 +94,7 @@ private static void WriteMarkdown(StreamWriter file, Type metricsType) var attr = prop.GetCustomAttribute(); var param = _regex.Replace(prop.Name, m => $"_{m.Value.ToLowerInvariant()}"); - file.WriteLine($"- **`nethermind{param}`**"); + file.WriteLine($"- #### `nethermind{param}` \\{{#{param[1..]}\\}}"); if (!string.IsNullOrWhiteSpace(attr?.Description)) file.WriteLine($""" @@ -110,12 +104,7 @@ private static void WriteMarkdown(StreamWriter file, Type metricsType) """); } - file.WriteLine(""" - -

-
- - """); + file.WriteLine(); } private static string? GetNamespace(string? fullTypeName) diff --git a/tools/docgen/Program.cs b/tools/DocGen/Program.cs similarity index 87% rename from tools/docgen/Program.cs rename to tools/DocGen/Program.cs index 63fc55f1bf0..80a62ef36ca 100644 --- a/tools/docgen/Program.cs +++ b/tools/DocGen/Program.cs @@ -30,7 +30,7 @@ public override int Execute(CommandContext context, AppSettings settings) ConfigGenerator.Generate(settings.DocsPath); if (settings.GenerateDBSize) - DBSizeGenerator.Generate(settings.DocsPath); + DBSizeGenerator.Generate(settings.DocsPath, settings.DBSizeSourcePath); if (settings.GenerateJsonRpc) JsonRpcGenerator.Generate(settings.DocsPath); @@ -44,6 +44,10 @@ public override int Execute(CommandContext context, AppSettings settings) public sealed class AppSettings : CommandSettings { + [Description("Path to the directory with DB size files")] + [CommandOption("--dbsize-src")] + public string? DBSizeSourcePath { get; init; } + [Description("Path to the docs")] [CommandArgument(0, "[docspath]")] public string? DocsPath { get; init; } diff --git a/tools/docgen/Properties/launchSettings.json b/tools/DocGen/Properties/launchSettings.json similarity index 100% rename from tools/docgen/Properties/launchSettings.json rename to tools/DocGen/Properties/launchSettings.json diff --git a/tools/docgen/nuget.config b/tools/DocGen/nuget.config similarity index 100% rename from tools/docgen/nuget.config rename to tools/DocGen/nuget.config diff --git a/tools/SendBlobs/AccountException.cs b/tools/SendBlobs/AccountException.cs index 624e8754a86..00f66e3966f 100644 --- a/tools/SendBlobs/AccountException.cs +++ b/tools/SendBlobs/AccountException.cs @@ -22,8 +22,4 @@ public AccountException(string? message) : base(message) public AccountException(string? message, Exception? innerException) : base(message, innerException) { } - - protected AccountException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } } diff --git a/tools/SendBlobs/BlobSender.cs b/tools/SendBlobs/BlobSender.cs index 4b4d0889ced..008523c85e5 100644 --- a/tools/SendBlobs/BlobSender.cs +++ b/tools/SendBlobs/BlobSender.cs @@ -16,7 +16,7 @@ namespace SendBlobs; internal class BlobSender { - private static readonly TxDecoder txDecoder = new(); + private static readonly TxDecoder txDecoder = TxDecoder.Instance; private INodeManager _nodeManager; private readonly ILogger _logger; @@ -85,7 +85,7 @@ public async Task SendRandomBlobs( signers.Add(new(new Signer(chainId, privateKey, _logManager), nonce)); } - TxDecoder txDecoder = new(); + TxDecoder txDecoder = TxDecoder.Instance; Random random = new(); int signerIndex = -1; @@ -283,7 +283,7 @@ public async Task SendData( if (defaultMaxFeePerBlobGas is null) { ulong excessBlobsReserve = 2 * Eip4844Constants.TargetBlobGasPerBlock; - BlobGasCalculator.TryCalculateBlobGasPricePerUnit( + BlobGasCalculator.TryCalculateFeePerBlobGas( (block.ExcessBlobGas ?? 0) + excessBlobs * Eip4844Constants.MaxBlobGasPerBlock + excessBlobsReserve, diff --git a/tools/SendBlobs/FundsDistributor.cs b/tools/SendBlobs/FundsDistributor.cs index 6356d91d345..d794abbfeff 100644 --- a/tools/SendBlobs/FundsDistributor.cs +++ b/tools/SendBlobs/FundsDistributor.cs @@ -83,7 +83,7 @@ public async Task> DitributeFunds(Signer distributeFrom, uin List txHash = new List(); - TxDecoder txDecoder = new(); + TxDecoder txDecoder = TxDecoder.Instance; StreamWriter? keyWriter = null; if (!string.IsNullOrWhiteSpace(_keyFilePath)) @@ -147,7 +147,7 @@ public async Task> ReclaimFunds(Address beneficiary, UInt256 ILogger log = _logManager.GetClassLogger(); List txHashes = new List(); - TxDecoder txDecoder = new(); + TxDecoder txDecoder = TxDecoder.Instance; foreach (var signer in privateSigners) { diff --git a/tools/TxParser/Program.cs b/tools/TxParser/Program.cs index 409c755ef97..cd2baab450c 100644 --- a/tools/TxParser/Program.cs +++ b/tools/TxParser/Program.cs @@ -5,7 +5,6 @@ using Nethermind.Core.Extensions; using Nethermind.Serialization.Rlp; using Nethermind.Crypto; -using Nethermind.Logging; using Nethermind.Consensus.Validators; using Nethermind.Specs.Forks; @@ -21,7 +20,7 @@ TxValidator txValidator = new TxValidator(BlockchainIds.Mainnet); if (txValidator.IsWellFormed(tx, GrayGlacier.Instance)) { - EthereumEcdsa ecdsa = new(BlockchainIds.Mainnet, SimpleConsoleLogManager.Instance); + EthereumEcdsa ecdsa = new(BlockchainIds.Mainnet); Address? sender = ecdsa.RecoverAddress(tx); if (sender is null) {