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/src/Nethermind/Chains/chiado.json b/src/Nethermind/Chains/chiado.json index b692dfa2ecf..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", 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 79ff843ce6e..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", 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/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 db504de060a..253c598a39d 100644 --- a/src/Nethermind/Directory.Packages.props +++ b/src/Nethermind/Directory.Packages.props @@ -19,8 +19,8 @@ - - + + @@ -51,6 +51,7 @@ + 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 ceee96957d1..56193492d01 100644 --- a/src/Nethermind/Nethermind.Api/NethermindApi.cs +++ b/src/Nethermind/Nethermind.Api/NethermindApi.cs @@ -57,6 +57,7 @@ using Nethermind.Sockets; using Nethermind.Trie; using Nethermind.Consensus.Processing.CensorshipDetector; +using Nethermind.Facade.Find; namespace Nethermind.Api { @@ -214,6 +215,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/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 f843345bc38..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; } 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.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/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/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/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..1799871edd7 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 (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 + { + 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/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 d5dcd925af1..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; 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..26ad8ee1554 --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Crypto/BlsSignerTests.cs @@ -0,0 +1,52 @@ +// 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; + +[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); + Assert.That(BlsSigner.Verify(BlsSigner.GetPublicKey(sk).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); + + Assert.That(BlsSigner.Verify(BlsSigner.GetPublicKey(sk).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]; + Assert.That(BlsSigner.GetPublicKey(new(SkBytes, Bls.ByteOrder.LittleEndian)).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/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/Specs/IReleaseSpec.cs b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs index 0516fc9f958..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,11 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec /// bool IsEip6780Enabled { get; } + /// + /// Transactions that allows code delegation for EOA + /// + bool IsEip7702Enabled { get; } + /// /// Blob base fee collection for Gnosis /// @@ -396,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 8cd33749e43..01c65e977f9 100644 --- a/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs +++ b/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs @@ -71,10 +71,13 @@ 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; 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..adcaf19f5fd --- /dev/null +++ b/src/Nethermind/Nethermind.Crypto/BlsSigner.cs @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Text; +using Nethermind.Core.Collections; + +namespace Nethermind.Crypto; + +using G1 = Bls.P1; +using G1Affine = Bls.P1Affine; +using G2 = Bls.P2; +using G2Affine = Bls.P2Affine; +using GT = Bls.PT; + +public class BlsSigner +{ + private static readonly byte[] Cryptosuite = Encoding.UTF8.GetBytes("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"); + private const int InputLength = 64; + + 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()); + } + + public static void GetPublicKey(G1 res, Bls.SecretKey sk) + => res.FromSk(sk); + + public static G1 GetPublicKey(Bls.SecretKey sk) + { + G1 p = new(); + GetPublicKey(p, sk); + return p; + } + + 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/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/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/VirtualMachineTestsBase.cs b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs index 6df49e3f481..6960cc8b18c 100644 --- a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs +++ b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs @@ -124,10 +124,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(); @@ -197,7 +209,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; @@ -227,7 +240,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/CodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs index 2cbcd1c047b..fe631b3fb10 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; @@ -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,28 +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()); + 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) { - CodeInfo codeInfo = new(code); - codeInfo.AnalyseInBackgroundIfRequired(); + if (Eip7702Constants.IsDelegatedCode(code)) + { + address = new Address(code.Slice(Eip7702Constants.DelegationHeader.Length).ToArray()); + return true; + } - Hash256 codeHash = code.Length == 0 ? Keccak.OfAnEmptyString : Keccak.Compute(code.Span); - state.InsertCode(codeOwner, codeHash, code, spec); - _codeCache.Set(codeHash, codeInfo); + address = null; + return false; } private CodeInfo CreateCachedPrecompile( @@ -174,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, @@ -199,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/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/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/TxTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/TxTracer.cs index 439427fb1c2..dee699912cc 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/TransactionProcessing/TransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs index b21e56d310d..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 @@ -149,10 +150,13 @@ protected virtual TransactionResult Execute(Transaction tx, in BlockExecutionCon 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); + 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 @@ -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) @@ -377,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); @@ -431,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(); @@ -467,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, @@ -490,7 +574,7 @@ protected virtual void ExecuteEvmCall( if (tx.IsContractCreation) { // if transaction is a contract creation then recipient address is the contract deployment address - if (!PrepareAccountForContractDeployment(env.ExecutingAccount, spec)) + if (!PrepareAccountForContractDeployment(env.ExecutingAccount, _codeInfoRepository, spec)) { goto Fail; } @@ -500,21 +584,7 @@ protected virtual void ExecuteEvmCall( 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) @@ -574,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 @@ -595,6 +665,27 @@ protected virtual void PayValue(Transaction tx, IReleaseSpec spec, ExecutionOpti WorldState.SubtractFromBalance(tx.SenderAddress!, tx.Value, spec); } + 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; @@ -619,9 +710,9 @@ protected virtual void PayFees(Transaction tx, BlockHeader header, IReleaseSpec } } - protected bool 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}"); @@ -638,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 " + actualRefund); + // If noValidation we didn't charge for gas, so do not refund + if (!opts.HasFlag(ExecutionOptions.NoValidation)) + 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 unused gas of " + unspentGas + " and refund of " + refund); + 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)(unspentGas + refund) * gasPrice, spec); + WorldState.AddToBalance(tx.SenderAddress!, (ulong)refund * gasPrice, spec); spentGas -= refund; } 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 c1b9ab1af67..b5ca0aa4a7d 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; @@ -27,9 +26,9 @@ [assembly: InternalsVisibleTo("Nethermind.Evm.Test")] namespace Nethermind.Evm; - using Int256; + public class VirtualMachine : IVirtualMachine { public const int MaxCallDepth = 1024; @@ -69,8 +68,8 @@ public VirtualMachine( { ILogger logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); _evm = logger.IsTrace - ? new VirtualMachine(blockhashProvider, specProvider, codeInfoRepository, logger) - : new VirtualMachine(blockhashProvider, specProvider, codeInfoRepository, logger); + ? new VirtualMachine(blockhashProvider, specProvider, logger) + : new VirtualMachine(blockhashProvider, specProvider, logger); } public TransactionSubstate Run(EvmState state, IWorldState worldState, ITxTracer txTracer) @@ -140,23 +139,20 @@ 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; public VirtualMachine( IBlockhashProvider? blockhashProvider, ISpecProvider? specProvider, - ICodeInfoRepository codeInfoRepository, 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(); } @@ -166,7 +162,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; @@ -347,7 +345,7 @@ public TransactionSubstate Run(EvmState state, IWorldState worl if (gasAvailableForCodeDeposit >= codeDepositGasCost && !invalidCode) { ReadOnlyMemory code = callResult.Output; - _codeInfoRepository.InsertCode(_state, code, callCodeOwner, spec); + codeInfoRepository.InsertCode(_state, code, callCodeOwner, spec); currentState.GasAvailable -= codeDepositGasCost; @@ -494,13 +492,22 @@ private static void UpdateGasUp(long refund, ref long gasAvailable) gasAvailable += refund; } - private bool ChargeAccountAccessGas(ref long gasAvailable, EvmState vmState, Address address, IReleaseSpec spec, bool chargeForWarm = true) + private bool ChargeAccountAccessGas(ref long gasAvailable, EvmState vmState, Address address, bool chargeForDelegation, IReleaseSpec spec, 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 (_txTracer.IsTracingAccess) // when tracing access we want cost as if it was warmed up from access list { vmState.WarmUp(address); @@ -515,9 +522,8 @@ private bool ChargeAccountAccessGas(ref long gasAvailable, EvmState vmState, Add { result = UpdateGas(GasCostOf.WarmStateRead, ref gasAvailable); } + return result; } - - return result; } private enum StorageAccessType @@ -1170,7 +1176,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)) @@ -1914,15 +1920,17 @@ 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); } @@ -2092,7 +2100,7 @@ private EvmExceptionType InstructionCall( Address codeSource = stack.PopAddress(); if (codeSource is null) return EvmExceptionType.StackUnderflow; - if (!ChargeAccountAccessGas(ref gasAvailable, vmState, codeSource, spec)) return EvmExceptionType.OutOfGas; + if (!ChargeAccountAccessGas(ref gasAvailable, vmState, codeSource, true, spec)) return EvmExceptionType.OutOfGas; UInt256 callValue; switch (instruction) @@ -2117,6 +2125,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; @@ -2147,7 +2156,7 @@ private EvmExceptionType InstructionCall( !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) @@ -2166,8 +2175,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(); @@ -2311,7 +2319,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, false)) return EvmExceptionType.OutOfGas; + if (!ChargeAccountAccessGas(ref gasAvailable, vmState, inheritor, false, spec, false)) return EvmExceptionType.OutOfGas; Address executingAccount = vmState.Env.ExecutingAccount; bool createInSameTx = vmState.CreateList.Contains(executingAccount); @@ -2436,7 +2444,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}"); @@ -2777,30 +2785,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 1c7d9f22a29..62754cb2fb2 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs @@ -57,7 +57,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); @@ -66,8 +68,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); @@ -79,17 +80,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) @@ -199,10 +200,10 @@ protected virtual IHealthHintService CreateHealthHintService() => protected virtual IBlockProductionPolicy CreateBlockProductionPolicy() => new BlockProductionPolicy(_api.Config()); - protected virtual TxPool.TxPool CreateTxPool() => + protected virtual TxPool.TxPool CreateTxPool(CodeInfoRepository codeInfoRepository) => new(_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/Modules/Eth/EthRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs index 914c3ebf8d8..843406aad94 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs @@ -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/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/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/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/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/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/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/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 481c655d757..58dab4bab85 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs @@ -153,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. @@ -164,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.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..2e34e8434b8 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/Nethermind.Shutter.csproj @@ -0,0 +1,47 @@ + + + + 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..0a3c0750b97 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterCrypto.cs @@ -0,0 +1,372 @@ +// 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; + +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); + + 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 res, EncryptedMessage encryptedMessage, G1Affine decryptionKey) + { + using ArrayPoolList buf = new(GT.Sz, GT.Sz); + GT p = new(buf.AsSpan()); + p.MillerLoop(encryptedMessage.C1.ToAffine(), decryptionKey); + res = Hash2(p); // key + res.Xor(encryptedMessage.C2); + } + + public static void ComputeBlockKeys(Span res, ReadOnlySpan sigma, int n) + { + if (res.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(res[(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); + } + + // res should be intialised to one + 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(); + } + } + + 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); + } + + private static readonly Ecdsa _ecdsa = new(); + + 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; + } + + 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 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 res = Hash2(p); //key + res.Xor(sigma); + return res; + } + + private static void ComputeC3(Span res, scoped ReadOnlySpan msgBlocks, ReadOnlySpan sigma) + { + // res = keys ^ msgs + ComputeBlockKeys(res, sigma, msgBlocks.Length / 32); + res.Xor(msgBlocks); + } + + private static int PaddedLength(ReadOnlySpan bytes) + => bytes.Length + (32 - (bytes.Length % 32)); + + private static Span PadAndSplit(Span res, scoped ReadOnlySpan bytes) + { + int n = 32 - (bytes.Length % 32); + bytes.CopyTo(res); + res[bytes.Length..].Fill((byte)n); + return res; + } + + private static void ComputeR(scoped ReadOnlySpan sigma, scoped ReadOnlySpan msg, out UInt256 res) + { + 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 res); + } + + 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..22978b32b92 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterKeyValidator.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.Linq; +using Nethermind.Core; +using Nethermind.Crypto; +using Nethermind.Shutter.Config; +using Nethermind.Logging; +using Google.Protobuf; +using Nethermind.Core.Collections; + +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; + } + } + } + + 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..eee65b30253 --- /dev/null +++ b/src/Nethermind/Nethermind.Shutter/ShutterTxLoader.cs @@ -0,0 +1,270 @@ +// 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; + +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); + } + + 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); + } + } + } + + 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 114869c071a..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] @@ -708,7 +708,9 @@ public void Eip_transitions_loaded_correctly() Eip3855TransitionTimestamp = 1000000012, Eip3860TransitionTimestamp = 1000000012, Eip1153TransitionTimestamp = 1000000024, - Eip2537TransitionTimestamp = 1000000024 + Eip2537TransitionTimestamp = 1000000024, + + Eip7702TransitionTimestamp = 1000000032, } }; @@ -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 0c329498934..5394d1c475d 100644 --- a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs @@ -157,9 +157,12 @@ public ulong Eip4844TransitionTimestamp 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 a01fd386fbe..4252264aab1 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs @@ -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 diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs index e1c45005436..bb7c9a7011e 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs @@ -259,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; } @@ -287,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 bf0832b0e57..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,6 +163,8 @@ bool GetForInnerPathExistence(KeyValuePair o) => DepositContractAddress = chainSpecJson.Params.DepositContractAddress ?? Eip6110Constants.MainnetDepositContractAddress, Eip7002TransitionTimestamp = chainSpecJson.Params.Eip7002TransitionTimestamp, Eip7002ContractAddress = chainSpecJson.Params.Eip7002ContractAddress ?? Eip7002Constants.WithdrawalRequestPredeployAddress, + Eip7251TransitionTimestamp = chainSpecJson.Params.Eip7251TransitionTimestamp, + Eip7251ContractAddress = chainSpecJson.Params.Eip7251ContractAddress ?? Eip7251Constants.ConsolidationRequestPredeployAddress, FeeCollector = chainSpecJson.Params.FeeCollector, Eip1559FeeCollectorTransition = chainSpecJson.Params.Eip1559FeeCollectorTransition, Eip1559BaseFeeMinValueTransition = chainSpecJson.Params.Eip1559BaseFeeMinValueTransition, @@ -424,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 6cd97f25e19..f905249c4e8 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs @@ -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; } @@ -152,6 +153,9 @@ internal class ChainSpecParamsJson 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 7b9996d5806..de2f45dfa0f 100644 --- a/src/Nethermind/Nethermind.Specs/ChiadoSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/ChiadoSpecProvider.cs @@ -11,7 +11,7 @@ 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! @@ -44,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/GnosisSpecProvider.cs b/src/Nethermind/Nethermind.Specs/GnosisSpecProvider.cs index 17d4ffcfd02..e65dee2bf54 100644 --- a/src/Nethermind/Nethermind.Specs/GnosisSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/GnosisSpecProvider.cs @@ -16,7 +16,7 @@ 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! @@ -57,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 3bda8e6eada..f138b747d3b 100644 --- a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs @@ -86,9 +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 { @@ -122,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/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..3a1677afd69 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs @@ -774,7 +774,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() 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/ReadOnlyTrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/ReadOnlyTrieStore.cs index 0ae9f2160e9..3152aad59cd 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/ReadOnlyTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/ReadOnlyTrieStore.cs @@ -60,12 +60,6 @@ public IScopedTrieStore GetTrieStore(Hash256? address) return new ScopedReadOnlyTrieStore(this, address); } - - public void PersistCache(CancellationToken cancellationToken) - { - _trieStore.PersistCache(cancellationToken); - } - public bool HasRoot(Hash256 stateRoot) { return _trieStore.HasRoot(stateRoot); diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs index 2b7a1eca32e..50d71a17d5f 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; - } + // 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 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; + private readonly bool _livePruningEnabled = false; - // 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 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,7 +143,7 @@ public int CachedNodesCount { get { - int count = DirtyNodes.Count; + int count = DirtyNodesCount(); Metrics.CachedNodesCount = count; return count; } @@ -452,33 +193,58 @@ 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 ThrowUnknownPackage(long blockNumber, TrieNode node) => throw new TrieStoreException($"{nameof(CurrentPackage)} is NULL when committing {node} at {blockNumber}."); [DoesNotReturn] [StackTraceHidden] - static void ThrowNodeHasBeenSeen(long blockNumber, TrieNode node) + static void ThrowNodeHasBeenSeen(long blockNumber, TrieNode node) => throw new TrieStoreException($"{nameof(TrieNode.LastSeen)} set on {node} committed at {blockNumber}."); + } + + 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 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, NodeCommitInfo nodeCommitInfo, TrieNode node) { if (_pruningStrategy.PruningEnabled) { - DirtyNodesCache.Key key = new DirtyNodesCache.Key(address, nodeCommitInfo.Path, node.Keccak); - DirtyNodesCache cache = DirtyNodes; - if (cache.TryGetValue(in key, out TrieNode cachedNodeCopy)) + TrieStoreDirtyNodesCache.Key key = new TrieStoreDirtyNodesCache.Key(address, nodeCommitInfo.Path, node.Keccak); + if (DirtyNodesTryGetValue(in key, out TrieNode cachedNodeCopy)) { Metrics.LoadedFromCacheNodesCount++; if (!ReferenceEquals(cachedNodeCopy, node)) @@ -502,7 +268,7 @@ private TrieNode SaveOrReplaceInDirtyNodesCache(Hash256? address, NodeCommitInfo } else { - cache.SaveInCache(key, node); + DirtyNodesSaveInCache(key, node); } } @@ -516,11 +282,8 @@ 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) @@ -563,10 +326,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 +395,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 +409,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 +435,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 +517,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 +575,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; - - return true; - } + int shardIdx = GetNodeShardIdx(treePath, tn.Keccak); - ActionBlock actionBlock = - new ActionBlock(static (batch) => batch.Dispose()); + HashAndTinyPath key = new(address, new TinyTreePath(treePath)); - 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) - { - 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 +592,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 +601,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 +609,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 +725,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 +870,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 +904,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 +998,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 +1078,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 +1087,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 +1118,6 @@ public bool HasRoot(Hash256 stateRoot) return true; } - [StructLayout(LayoutKind.Auto)] - private 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; - } - } - - [StructLayout(LayoutKind.Auto)] - private 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; - } - } - 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.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/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/PersistentBlobTxDistinctSortedPool.cs b/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs index d0f7eb34d92..d768df97d28 100644 --- a/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs +++ b/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs @@ -33,7 +33,7 @@ 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 _)) @@ -46,11 +46,10 @@ 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) 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 2387ac91427..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,13 +14,15 @@ 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; } + + UInt256 CurrentBaseFee { get; } public UInt256 CurrentFeePerBlobGas { get; } 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/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 956e7a55397..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; } @@ -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/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) {