From c20eaa7ddca8942f81eae027ee1d334d8f76c104 Mon Sep 17 00:00:00 2001 From: Ruben Buniatyan Date: Wed, 25 Jan 2023 22:32:29 +0100 Subject: [PATCH 1/8] Implement `engine_getCapabilities` method --- .../EngineModuleTests.V1.cs | 16 ++++++++++++++++ .../Nethermind.Merge.Plugin/EngineRpcModule.cs | 13 +++++++++++++ .../Nethermind.Merge.Plugin/IEngineRpcModule.cs | 7 +++++++ 3 files changed, 36 insertions(+) diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index 25085549198..f5a3bdadadc 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -1661,6 +1661,22 @@ await rpc.engine_forkchoiceUpdatedV1(forkChoiceState1, } } + [Test] + public async Task Should_return_capabilities() + { + using var chain = await CreateBlockChain(); + var rpcModule = CreateEngineModule(chain); + + var expected = typeof(IEngineRpcModule).GetMethods() + .Select(m => m.Name) + .Where(m => !m.Equals(nameof(IEngineRpcModule.engine_getCapabilities), StringComparison.Ordinal)) + .Order(); + + var result = rpcModule.engine_getCapabilities(); + + result.Data.Should().BeEquivalentTo(expected); + } + private async Task BuildAndGetPayloadResult( IEngineRpcModule rpc, MergeTestBlockchain chain, Keccak headBlockHash, Keccak finalizedBlockHash, Keccak safeBlockHash, diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs index e1bce7b7cfb..fb30e656df8 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -14,6 +16,7 @@ namespace Nethermind.Merge.Plugin; public partial class EngineRpcModule : IEngineRpcModule { + private static IEnumerable? _capabilities; private readonly IHandler _executionStatusHandler; private readonly IAsyncHandler _executionGetPayloadBodiesByHashV1Handler; private readonly IGetPayloadBodiesByRangeV1Handler _executionGetPayloadBodiesByRangeV1Handler; @@ -46,6 +49,16 @@ public EngineRpcModule( public ResultWrapper engine_executionStatus() => _executionStatusHandler.Handle(); + public ResultWrapper> engine_getCapabilities() + { + _capabilities ??= typeof(IEngineRpcModule).GetMethods() + .Select(m => m.Name) + .Where(m => !m.Equals(nameof(engine_getCapabilities), StringComparison.Ordinal)) + .Order(); + + return ResultWrapper>.Success(_capabilities); + } + public async Task> engine_getPayloadBodiesByHashV1(Keccak[] blockHashes) { return await _executionGetPayloadBodiesByHashV1Handler.HandleAsync(blockHashes); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.cs index 4c80609f7c5..626e0e692c6 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Collections.Generic; using System.Threading.Tasks; using Nethermind.Core.Crypto; using Nethermind.JsonRpc; @@ -19,6 +20,12 @@ public partial interface IEngineRpcModule : IRpcModule IsImplemented = true)] ResultWrapper engine_executionStatus(); + [JsonRpcMethod( + Description = "Returns the currently supported list of Engine API methods.", + IsSharable = true, + IsImplemented = true)] + ResultWrapper> engine_getCapabilities(); + [JsonRpcMethod( Description = "Returns an array of execution payload bodies for the list of provided block hashes.", IsSharable = true, From f0da7bcea982edfd4a5352f68d4602c49a90c4fd Mon Sep 17 00:00:00 2001 From: Ruben Buniatyan Date: Thu, 26 Jan 2023 11:59:06 +0100 Subject: [PATCH 2/8] Update `engine_exchangeCapabilities` implementation --- .../EngineModuleTests.V1.cs | 19 ++++++-- .../EngineRpcModule.cs | 48 +++++++++++++++---- .../IEngineRpcModule.cs | 10 ++-- 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index f5a3bdadadc..1485cd95ab2 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -1667,12 +1667,21 @@ public async Task Should_return_capabilities() using var chain = await CreateBlockChain(); var rpcModule = CreateEngineModule(chain); - var expected = typeof(IEngineRpcModule).GetMethods() - .Select(m => m.Name) - .Where(m => !m.Equals(nameof(IEngineRpcModule.engine_getCapabilities), StringComparison.Ordinal)) - .Order(); + var expected = new[] + { + "engine_exchangeTransitionConfigurationV1", + "engine_executionStatus", + "engine_forkchoiceUpdatedV1", + "engine_forkchoiceUpdatedV2", + "engine_getPayloadBodiesByHashV1", + "engine_getPayloadBodiesByRangeV1", + "engine_getPayloadV1", + "engine_getPayloadV2", + "engine_newPayloadV1", + "engine_newPayloadV2" + }; - var result = rpcModule.engine_getCapabilities(); + var result = await rpcModule.engine_exchangeCapabilities(expected); result.Data.Should().BeEquivalentTo(expected); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs index fb30e656df8..83cc460fe16 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Nethermind.Core.Crypto; @@ -47,18 +48,49 @@ public EngineRpcModule( _logger = logManager.GetClassLogger(); } - public ResultWrapper engine_executionStatus() => _executionStatusHandler.Handle(); - - public ResultWrapper> engine_getCapabilities() + public async Task>> engine_exchangeCapabilities(IEnumerable methods) { - _capabilities ??= typeof(IEngineRpcModule).GetMethods() - .Select(m => m.Name) - .Where(m => !m.Equals(nameof(engine_getCapabilities), StringComparison.Ordinal)) - .Order(); + ArgumentNullException.ThrowIfNull(methods); + + if (await _locker.WaitAsync(1_000)) + { + var watch = Stopwatch.StartNew(); + + try + { + _capabilities ??= typeof(IEngineRpcModule).GetMethods() + .Select(m => m.Name) + .Where(m => !m.Equals(nameof(engine_exchangeCapabilities), StringComparison.Ordinal)) + .Order(); + + var unsupported = methods.Except(_capabilities); + + if (unsupported.Any()) + { + if (_logger.IsWarn) _logger.Warn($"Unsupported capabilities: {string.Join(", ", unsupported)}"); + } - return ResultWrapper>.Success(_capabilities); + return ResultWrapper>.Success(_capabilities); + } + finally + { + watch.Stop(); + + Metrics.ForkchoiceUpdedExecutionTime = watch.ElapsedMilliseconds; + + _locker.Release(); + } + } + else + { + if (_logger.IsWarn) _logger.Warn($"{nameof(engine_exchangeCapabilities)} timed out"); + + return ResultWrapper>.Fail("Timed out", ErrorCodes.Timeout); + } } + public ResultWrapper engine_executionStatus() => _executionStatusHandler.Handle(); + public async Task> engine_getPayloadBodiesByHashV1(Keccak[] blockHashes) { return await _executionGetPayloadBodiesByHashV1Handler.HandleAsync(blockHashes); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.cs index 626e0e692c6..2bbd78111e8 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.cs @@ -14,17 +14,17 @@ namespace Nethermind.Merge.Plugin; public partial interface IEngineRpcModule : IRpcModule { [JsonRpcMethod( - Description = - "Responds with information on the state of the execution client to either engine_consensusStatus or any other call if consistency failure has occurred.", + Description = "Returns the currently supported list of Engine API methods.", IsSharable = true, IsImplemented = true)] - ResultWrapper engine_executionStatus(); + Task>> engine_exchangeCapabilities(IEnumerable methods); [JsonRpcMethod( - Description = "Returns the currently supported list of Engine API methods.", + Description = + "Responds with information on the state of the execution client to either engine_consensusStatus or any other call if consistency failure has occurred.", IsSharable = true, IsImplemented = true)] - ResultWrapper> engine_getCapabilities(); + ResultWrapper engine_executionStatus(); [JsonRpcMethod( Description = "Returns an array of execution payload bodies for the list of provided block hashes.", From d0e0f011bc73f687ce3989fa8d6842e171b05a08 Mon Sep 17 00:00:00 2001 From: Ruben Buniatyan Date: Thu, 26 Jan 2023 14:01:12 +0100 Subject: [PATCH 3/8] Add capabilities warning test --- .../Blockchain/TestBlockchain.cs | 663 +++++++++--------- .../EngineModuleTests.Setup.cs | 2 +- .../EngineModuleTests.V1.cs | 19 +- 3 files changed, 348 insertions(+), 336 deletions(-) diff --git a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs index bd40861ae50..e8e5d5fb974 100644 --- a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs +++ b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs @@ -15,7 +15,6 @@ using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Transactions; using Nethermind.Consensus.Validators; -using Nethermind.Consensus.Withdrawals; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; @@ -31,399 +30,395 @@ using Nethermind.State.Repositories; using Nethermind.Db.Blooms; using Nethermind.Evm.TransactionProcessing; -using Nethermind.Synchronization.FastSync; using Nethermind.Specs.Test; -using Nethermind.Trie; using Nethermind.Trie.Pruning; using Nethermind.TxPool; -using NUnit.Framework; using BlockTree = Nethermind.Blockchain.BlockTree; using Nethermind.Config; -namespace Nethermind.Core.Test.Blockchain +namespace Nethermind.Core.Test.Blockchain; + +public class TestBlockchain : IDisposable { - public class TestBlockchain : IDisposable + public const int DefaultTimeout = 4000; + public IStateReader StateReader { get; private set; } = null!; + public IEthereumEcdsa EthereumEcdsa { get; private set; } = null!; + public TransactionProcessor TxProcessor { get; set; } = null!; + public IStorageProvider Storage { get; set; } = null!; + public IReceiptStorage ReceiptStorage { get; set; } = null!; + public ITxPool TxPool { get; set; } = null!; + public IDb CodeDb => DbProvider.CodeDb; + public IBlockProcessor BlockProcessor { get; set; } = null!; + public IBlockchainProcessor BlockchainProcessor { get; set; } = null!; + + public IBlockPreprocessorStep BlockPreprocessorStep { get; set; } = null!; + + public IBlockProcessingQueue BlockProcessingQueue { get; set; } = null!; + public IBlockTree BlockTree { get; set; } = null!; + + public IBlockFinder BlockFinder { - public const int DefaultTimeout = 4000; - public IStateReader StateReader { get; private set; } = null!; - public IEthereumEcdsa EthereumEcdsa { get; private set; } = null!; - public TransactionProcessor TxProcessor { get; set; } = null!; - public IStorageProvider Storage { get; set; } = null!; - public IReceiptStorage ReceiptStorage { get; set; } = null!; - public ITxPool TxPool { get; set; } = null!; - public IDb CodeDb => DbProvider.CodeDb; - public IBlockProcessor BlockProcessor { get; set; } = null!; - public IBlockchainProcessor BlockchainProcessor { get; set; } = null!; - - public IBlockPreprocessorStep BlockPreprocessorStep { get; set; } = null!; - - public IBlockProcessingQueue BlockProcessingQueue { get; set; } = null!; - public IBlockTree BlockTree { get; set; } = null!; - - public IBlockFinder BlockFinder - { - get => _blockFinder ?? BlockTree; - set => _blockFinder = value; - } + get => _blockFinder ?? BlockTree; + set => _blockFinder = value; + } - public ILogFinder LogFinder { get; private set; } = null!; - public IJsonSerializer JsonSerializer { get; set; } = null!; - public IStateProvider State { get; set; } = null!; - public IReadOnlyStateProvider ReadOnlyState { get; private set; } = null!; - public IDb StateDb => DbProvider.StateDb; - public TrieStore TrieStore { get; set; } = null!; - public IBlockProducer BlockProducer { get; private set; } = null!; - public IDbProvider DbProvider { get; set; } = null!; - public ISpecProvider SpecProvider { get; set; } = null!; + public ILogFinder LogFinder { get; private set; } = null!; + public IJsonSerializer JsonSerializer { get; set; } = null!; + public IStateProvider State { get; set; } = null!; + public IReadOnlyStateProvider ReadOnlyState { get; private set; } = null!; + public IDb StateDb => DbProvider.StateDb; + public TrieStore TrieStore { get; set; } = null!; + public IBlockProducer BlockProducer { get; private set; } = null!; + public IDbProvider DbProvider { get; set; } = null!; + public ISpecProvider SpecProvider { get; set; } = null!; - public ISealEngine SealEngine { get; set; } = null!; + public ISealEngine SealEngine { get; set; } = null!; - public ITransactionComparerProvider TransactionComparerProvider { get; set; } = null!; + public ITransactionComparerProvider TransactionComparerProvider { get; set; } = null!; - public IPoSSwitcher PoSSwitcher { get; set; } = null!; + public IPoSSwitcher PoSSwitcher { get; set; } = null!; - protected TestBlockchain() - { - } + protected TestBlockchain() + { + } - public string SealEngineType { get; set; } = null!; + public string SealEngineType { get; set; } = null!; - public static Address AccountA = TestItem.AddressA; - public static Address AccountB = TestItem.AddressB; - public static Address AccountC = TestItem.AddressC; - public SemaphoreSlim _resetEvent = null!; - private ManualResetEvent _suggestedBlockResetEvent = null!; - private AutoResetEvent _oneAtATime = new(true); - private IBlockFinder _blockFinder = null!; + public static Address AccountA = TestItem.AddressA; + public static Address AccountB = TestItem.AddressB; + public static Address AccountC = TestItem.AddressC; + public SemaphoreSlim _resetEvent = null!; + private ManualResetEvent _suggestedBlockResetEvent = null!; + private AutoResetEvent _oneAtATime = new(true); + private IBlockFinder _blockFinder = null!; - public static readonly UInt256 InitialValue = 1000.Ether(); - private TrieStoreBoundaryWatcher _trieStoreWatcher = null!; - public IHeaderValidator HeaderValidator { get; set; } = null!; + public static readonly UInt256 InitialValue = 1000.Ether(); + private TrieStoreBoundaryWatcher _trieStoreWatcher = null!; + public IHeaderValidator HeaderValidator { get; set; } = null!; - public IBlockValidator BlockValidator { get; set; } = null!; - public BuildBlocksWhenRequested BlockProductionTrigger { get; } = new(); + public IBlockValidator BlockValidator { get; set; } = null!; + public BuildBlocksWhenRequested BlockProductionTrigger { get; } = new(); - public IReadOnlyTrieStore ReadOnlyTrieStore { get; private set; } = null!; + public IReadOnlyTrieStore ReadOnlyTrieStore { get; private set; } = null!; - public ManualTimestamper Timestamper { get; protected set; } = null!; + public ManualTimestamper Timestamper { get; protected set; } = null!; - public ProducedBlockSuggester Suggester { get; protected set; } = null!; + public ProducedBlockSuggester Suggester { get; protected set; } = null!; - public static TransactionBuilder BuildSimpleTransaction => Builders.Build.A.Transaction.SignedAndResolved(TestItem.PrivateKeyA).To(AccountB); + public static TransactionBuilder BuildSimpleTransaction => Builders.Build.A.Transaction.SignedAndResolved(TestItem.PrivateKeyA).To(AccountB); - protected virtual async Task Build(ISpecProvider? specProvider = null, UInt256? initialValues = null) + protected virtual async Task Build(ISpecProvider? specProvider = null, UInt256? initialValues = null) + { + Timestamper = new ManualTimestamper(new DateTime(2020, 2, 15, 12, 50, 30, DateTimeKind.Utc)); + JsonSerializer = new EthereumJsonSerializer(); + SpecProvider = CreateSpecProvider(specProvider ?? MainnetSpecProvider.Instance); + EthereumEcdsa = new EthereumEcdsa(SpecProvider.ChainId, LogManager); + DbProvider = await CreateDbProvider(); + TrieStore = new TrieStore(StateDb, LogManager); + State = new StateProvider(TrieStore, DbProvider.CodeDb, LogManager); + State.CreateAccount(TestItem.AddressA, (initialValues ?? InitialValue)); + State.CreateAccount(TestItem.AddressB, (initialValues ?? InitialValue)); + State.CreateAccount(TestItem.AddressC, (initialValues ?? InitialValue)); + byte[] code = Bytes.FromHexString("0xabcd"); + Keccak codeHash = Keccak.Compute(code); + State.UpdateCode(code); + State.UpdateCodeHash(TestItem.AddressA, codeHash, SpecProvider.GenesisSpec); + + Storage = new StorageProvider(TrieStore, State, LogManager); + Storage.Set(new StorageCell(TestItem.AddressA, UInt256.One), Bytes.FromHexString("0xabcdef")); + Storage.Commit(); + + State.Commit(SpecProvider.GenesisSpec); + State.CommitTree(0); + + ReadOnlyTrieStore = TrieStore.AsReadOnly(StateDb); + StateReader = new StateReader(ReadOnlyTrieStore, CodeDb, LogManager); + + IDb blockDb = new MemDb(); + IDb headerDb = new MemDb(); + IDb blockInfoDb = new MemDb(); + BlockTree = new BlockTree(blockDb, headerDb, blockInfoDb, new ChainLevelInfoRepository(blockInfoDb), SpecProvider, NullBloomStorage.Instance, LimboLogs.Instance); + ReadOnlyState = new ChainHeadReadOnlyStateProvider(BlockTree, StateReader); + TransactionComparerProvider = new TransactionComparerProvider(SpecProvider, BlockTree); + TxPool = CreateTxPool(); + + _trieStoreWatcher = new TrieStoreBoundaryWatcher(TrieStore, BlockTree, LogManager); + + ReceiptStorage = new InMemoryReceiptStorage(); + VirtualMachine virtualMachine = new(new BlockhashProvider(BlockTree, LogManager), SpecProvider, LogManager); + TxProcessor = new TransactionProcessor(SpecProvider, State, Storage, virtualMachine, LogManager); + BlockPreprocessorStep = new RecoverSignatures(EthereumEcdsa, TxPool, SpecProvider, LogManager); + HeaderValidator = new HeaderValidator(BlockTree, Always.Valid, SpecProvider, LogManager); + + new ReceiptCanonicalityMonitor(BlockTree, ReceiptStorage, LogManager); + + BlockValidator = new BlockValidator( + new TxValidator(SpecProvider.ChainId), + HeaderValidator, + Always.Valid, + SpecProvider, + LogManager); + + PoSSwitcher = NoPoS.Instance; + ISealer sealer = new NethDevSealEngine(TestItem.AddressD); + SealEngine = new SealEngine(sealer, Always.Valid); + + BloomStorage bloomStorage = new(new BloomConfig(), new MemDb(), new InMemoryDictionaryFileStoreFactory()); + ReceiptsRecovery receiptsRecovery = new(new EthereumEcdsa(SpecProvider.ChainId, LimboLogs.Instance), SpecProvider); + LogFinder = new LogFinder(BlockTree, ReceiptStorage, ReceiptStorage, bloomStorage, LimboLogs.Instance, receiptsRecovery); + BlockProcessor = CreateBlockProcessor(); + + BlockchainProcessor chainProcessor = new(BlockTree, BlockProcessor, BlockPreprocessorStep, StateReader, LogManager, Consensus.Processing.BlockchainProcessor.Options.Default); + BlockchainProcessor = chainProcessor; + BlockProcessingQueue = chainProcessor; + chainProcessor.Start(); + + TxPoolTxSource txPoolTxSource = CreateTxPoolTxSource(); + ITransactionComparerProvider transactionComparerProvider = new TransactionComparerProvider(SpecProvider, BlockFinder); + BlockProducer = CreateTestBlockProducer(txPoolTxSource, sealer, transactionComparerProvider); + await BlockProducer.Start(); + Suggester = new ProducedBlockSuggester(BlockTree, BlockProducer); + + _resetEvent = new SemaphoreSlim(0); + _suggestedBlockResetEvent = new ManualResetEvent(true); + BlockTree.NewHeadBlock += OnNewHeadBlock; + BlockProducer.BlockProduced += (s, e) => { - Timestamper = new ManualTimestamper(new DateTime(2020, 2, 15, 12, 50, 30, DateTimeKind.Utc)); - JsonSerializer = new EthereumJsonSerializer(); - SpecProvider = CreateSpecProvider(specProvider ?? MainnetSpecProvider.Instance); - EthereumEcdsa = new EthereumEcdsa(SpecProvider.ChainId, LogManager); - DbProvider = await CreateDbProvider(); - TrieStore = new TrieStore(StateDb, LogManager); - State = new StateProvider(TrieStore, DbProvider.CodeDb, LogManager); - State.CreateAccount(TestItem.AddressA, (initialValues ?? InitialValue)); - State.CreateAccount(TestItem.AddressB, (initialValues ?? InitialValue)); - State.CreateAccount(TestItem.AddressC, (initialValues ?? InitialValue)); - byte[] code = Bytes.FromHexString("0xabcd"); - Keccak codeHash = Keccak.Compute(code); - State.UpdateCode(code); - State.UpdateCodeHash(TestItem.AddressA, codeHash, SpecProvider.GenesisSpec); - - Storage = new StorageProvider(TrieStore, State, LogManager); - Storage.Set(new StorageCell(TestItem.AddressA, UInt256.One), Bytes.FromHexString("0xabcdef")); - Storage.Commit(); - - State.Commit(SpecProvider.GenesisSpec); - State.CommitTree(0); - - ReadOnlyTrieStore = TrieStore.AsReadOnly(StateDb); - StateReader = new StateReader(ReadOnlyTrieStore, CodeDb, LogManager); - - IDb blockDb = new MemDb(); - IDb headerDb = new MemDb(); - IDb blockInfoDb = new MemDb(); - BlockTree = new BlockTree(blockDb, headerDb, blockInfoDb, new ChainLevelInfoRepository(blockInfoDb), SpecProvider, NullBloomStorage.Instance, LimboLogs.Instance); - ReadOnlyState = new ChainHeadReadOnlyStateProvider(BlockTree, StateReader); - TransactionComparerProvider = new TransactionComparerProvider(SpecProvider, BlockTree); - TxPool = CreateTxPool(); - - _trieStoreWatcher = new TrieStoreBoundaryWatcher(TrieStore, BlockTree, LogManager); - - ReceiptStorage = new InMemoryReceiptStorage(); - VirtualMachine virtualMachine = new(new BlockhashProvider(BlockTree, LogManager), SpecProvider, LogManager); - TxProcessor = new TransactionProcessor(SpecProvider, State, Storage, virtualMachine, LogManager); - BlockPreprocessorStep = new RecoverSignatures(EthereumEcdsa, TxPool, SpecProvider, LogManager); - HeaderValidator = new HeaderValidator(BlockTree, Always.Valid, SpecProvider, LogManager); - - new ReceiptCanonicalityMonitor(BlockTree, ReceiptStorage, LogManager); - - BlockValidator = new BlockValidator( - new TxValidator(SpecProvider.ChainId), - HeaderValidator, - Always.Valid, - SpecProvider, - LogManager); - - PoSSwitcher = NoPoS.Instance; - ISealer sealer = new NethDevSealEngine(TestItem.AddressD); - SealEngine = new SealEngine(sealer, Always.Valid); - - BloomStorage bloomStorage = new(new BloomConfig(), new MemDb(), new InMemoryDictionaryFileStoreFactory()); - ReceiptsRecovery receiptsRecovery = new(new EthereumEcdsa(SpecProvider.ChainId, LimboLogs.Instance), SpecProvider); - LogFinder = new LogFinder(BlockTree, ReceiptStorage, ReceiptStorage, bloomStorage, LimboLogs.Instance, receiptsRecovery); - BlockProcessor = CreateBlockProcessor(); - - BlockchainProcessor chainProcessor = new(BlockTree, BlockProcessor, BlockPreprocessorStep, StateReader, LogManager, Consensus.Processing.BlockchainProcessor.Options.Default); - BlockchainProcessor = chainProcessor; - BlockProcessingQueue = chainProcessor; - chainProcessor.Start(); - - TxPoolTxSource txPoolTxSource = CreateTxPoolTxSource(); - ITransactionComparerProvider transactionComparerProvider = new TransactionComparerProvider(SpecProvider, BlockFinder); - BlockProducer = CreateTestBlockProducer(txPoolTxSource, sealer, transactionComparerProvider); - await BlockProducer.Start(); - Suggester = new ProducedBlockSuggester(BlockTree, BlockProducer); - - _resetEvent = new SemaphoreSlim(0); - _suggestedBlockResetEvent = new ManualResetEvent(true); - BlockTree.NewHeadBlock += OnNewHeadBlock; - BlockProducer.BlockProduced += (s, e) => - { - _suggestedBlockResetEvent.Set(); - }; - - Block? genesis = GetGenesisBlock(); - BlockTree.SuggestBlock(genesis); - - await WaitAsync(_resetEvent, "Failed to process genesis in time."); - await AddBlocksOnStart(); - return this; - } + _suggestedBlockResetEvent.Set(); + }; - private static ISpecProvider CreateSpecProvider(ISpecProvider specProvider) - { - return specProvider is TestSpecProvider { AllowTestChainOverride: false } - ? specProvider - : new OverridableSpecProvider(specProvider, s => new OverridableReleaseSpec(s) { IsEip3607Enabled = false }); - } + Block? genesis = GetGenesisBlock(); + BlockTree.SuggestBlock(genesis); - private void OnNewHeadBlock(object? sender, BlockEventArgs e) - { - _resetEvent.Release(1); - } + await WaitAsync(_resetEvent, "Failed to process genesis in time."); + await AddBlocksOnStart(); + return this; + } - protected virtual Task CreateDbProvider() => TestMemDbProvider.InitAsync(); + private static ISpecProvider CreateSpecProvider(ISpecProvider specProvider) + { + return specProvider is TestSpecProvider { AllowTestChainOverride: false } + ? specProvider + : new OverridableSpecProvider(specProvider, s => new OverridableReleaseSpec(s) { IsEip3607Enabled = false }); + } - private async Task WaitAsync(SemaphoreSlim semaphore, string error, int timeout = DefaultTimeout) - { - if (!await semaphore.WaitAsync(timeout)) - { - throw new InvalidOperationException(error); - } - } + private void OnNewHeadBlock(object? sender, BlockEventArgs e) + { + _resetEvent.Release(1); + } - private async Task WaitAsync(EventWaitHandle eventWaitHandle, string error, int timeout = DefaultTimeout) + protected virtual Task CreateDbProvider() => TestMemDbProvider.InitAsync(); + + private async Task WaitAsync(SemaphoreSlim semaphore, string error, int timeout = DefaultTimeout) + { + if (!await semaphore.WaitAsync(timeout)) { - if (!await eventWaitHandle.WaitOneAsync(timeout, CancellationToken.None)) - { - throw new InvalidOperationException(error); - } + throw new InvalidOperationException(error); } + } - protected virtual IBlockProducer CreateTestBlockProducer(TxPoolTxSource txPoolTxSource, ISealer sealer, ITransactionComparerProvider transactionComparerProvider) + private async Task WaitAsync(EventWaitHandle eventWaitHandle, string error, int timeout = DefaultTimeout) + { + if (!await eventWaitHandle.WaitOneAsync(timeout, CancellationToken.None)) { - BlocksConfig blocksConfig = new(); - - BlockProducerEnvFactory blockProducerEnvFactory = new( - DbProvider, - BlockTree, - ReadOnlyTrieStore, - SpecProvider, - BlockValidator, - NoBlockRewards.Instance, - ReceiptStorage, - BlockPreprocessorStep, - TxPool, - transactionComparerProvider, - blocksConfig, - LogManager); - - BlockProducerEnv env = blockProducerEnvFactory.Create(txPoolTxSource); - return new TestBlockProducer( - env.TxSource, - env.ChainProcessor, - env.ReadOnlyStateProvider, - sealer, - BlockTree, - BlockProductionTrigger, - Timestamper, - SpecProvider, - LogManager, - blocksConfig); + throw new InvalidOperationException(error); } + } - public virtual ILogManager LogManager { get; } = LimboLogs.Instance; + protected virtual IBlockProducer CreateTestBlockProducer(TxPoolTxSource txPoolTxSource, ISealer sealer, ITransactionComparerProvider transactionComparerProvider) + { + BlocksConfig blocksConfig = new(); + + BlockProducerEnvFactory blockProducerEnvFactory = new( + DbProvider, + BlockTree, + ReadOnlyTrieStore, + SpecProvider, + BlockValidator, + NoBlockRewards.Instance, + ReceiptStorage, + BlockPreprocessorStep, + TxPool, + transactionComparerProvider, + blocksConfig, + LogManager); + + BlockProducerEnv env = blockProducerEnvFactory.Create(txPoolTxSource); + return new TestBlockProducer( + env.TxSource, + env.ChainProcessor, + env.ReadOnlyStateProvider, + sealer, + BlockTree, + BlockProductionTrigger, + Timestamper, + SpecProvider, + LogManager, + blocksConfig); + } + + public virtual ILogManager LogManager { get; set; } = LimboLogs.Instance; - protected virtual TxPool.TxPool CreateTxPool() => - new( - EthereumEcdsa, - new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(SpecProvider), BlockTree, ReadOnlyState), - new TxPoolConfig(), - new TxValidator(SpecProvider.ChainId), - LogManager, - TransactionComparerProvider.GetDefaultComparer()); + protected virtual TxPool.TxPool CreateTxPool() => + new( + EthereumEcdsa, + new ChainHeadInfoProvider(new FixedForkActivationChainHeadSpecProvider(SpecProvider), BlockTree, ReadOnlyState), + new TxPoolConfig(), + new TxValidator(SpecProvider.ChainId), + LogManager, + TransactionComparerProvider.GetDefaultComparer()); - protected virtual TxPoolTxSource CreateTxPoolTxSource() + protected virtual TxPoolTxSource CreateTxPoolTxSource() + { + BlocksConfig blocksConfig = new() { - BlocksConfig blocksConfig = new() - { - MinGasPrice = 0 - }; - ITxFilterPipeline txFilterPipeline = TxFilterPipelineBuilder.CreateStandardFilteringPipeline(LimboLogs.Instance, - SpecProvider, blocksConfig); - return new TxPoolTxSource(TxPool, SpecProvider, TransactionComparerProvider, LogManager, txFilterPipeline); - } + MinGasPrice = 0 + }; + ITxFilterPipeline txFilterPipeline = TxFilterPipelineBuilder.CreateStandardFilteringPipeline(LimboLogs.Instance, + SpecProvider, blocksConfig); + return new TxPoolTxSource(TxPool, SpecProvider, TransactionComparerProvider, LogManager, txFilterPipeline); + } - public BlockBuilder GenesisBlockBuilder { get; set; } = null!; + public BlockBuilder GenesisBlockBuilder { get; set; } = null!; - protected virtual Block GetGenesisBlock() + protected virtual Block GetGenesisBlock() + { + BlockBuilder genesisBlockBuilder = Builders.Build.A.Block.Genesis; + if (GenesisBlockBuilder is not null) { - BlockBuilder genesisBlockBuilder = Builders.Build.A.Block.Genesis; - if (GenesisBlockBuilder is not null) - { - genesisBlockBuilder = GenesisBlockBuilder; - } - - genesisBlockBuilder.WithStateRoot(State.StateRoot); - if (SealEngineType == Nethermind.Core.SealEngineType.AuRa) - { - genesisBlockBuilder.WithAura(0, new byte[65]); - } - - return genesisBlockBuilder.TestObject; + genesisBlockBuilder = GenesisBlockBuilder; } - protected virtual async Task AddBlocksOnStart() + genesisBlockBuilder.WithStateRoot(State.StateRoot); + if (SealEngineType == Nethermind.Core.SealEngineType.AuRa) { - await AddBlock(); - await AddBlock(BuildSimpleTransaction.WithNonce(0).TestObject); - await AddBlock(BuildSimpleTransaction.WithNonce(1).TestObject, BuildSimpleTransaction.WithNonce(2).TestObject); + genesisBlockBuilder.WithAura(0, new byte[65]); } - protected virtual IBlockProcessor CreateBlockProcessor() => - new BlockProcessor( - SpecProvider, - BlockValidator, - NoBlockRewards.Instance, - new BlockProcessor.BlockValidationTransactionsExecutor(TxProcessor, State), - State, - Storage, - ReceiptStorage, - NullWitnessCollector.Instance, - LogManager); - - public async Task WaitForNewHead() - { - await WaitAsync(_resetEvent, "Failed to produce new head in time."); - _suggestedBlockResetEvent.Reset(); - } + return genesisBlockBuilder.TestObject; + } - public async Task AddBlock(params Transaction[] transactions) - { - await AddBlockInternal(transactions); + protected virtual async Task AddBlocksOnStart() + { + await AddBlock(); + await AddBlock(BuildSimpleTransaction.WithNonce(0).TestObject); + await AddBlock(BuildSimpleTransaction.WithNonce(1).TestObject, BuildSimpleTransaction.WithNonce(2).TestObject); + } - await WaitAsync(_resetEvent, "Failed to produce new head in time."); - _suggestedBlockResetEvent.Reset(); - _oneAtATime.Set(); - } + protected virtual IBlockProcessor CreateBlockProcessor() => + new BlockProcessor( + SpecProvider, + BlockValidator, + NoBlockRewards.Instance, + new BlockProcessor.BlockValidationTransactionsExecutor(TxProcessor, State), + State, + Storage, + ReceiptStorage, + NullWitnessCollector.Instance, + LogManager); + + public async Task WaitForNewHead() + { + await WaitAsync(_resetEvent, "Failed to produce new head in time."); + _suggestedBlockResetEvent.Reset(); + } - public async Task AddBlock(bool shouldWaitForHead = true, params Transaction[] transactions) - { - await AddBlockInternal(transactions); - - if (shouldWaitForHead) - { - await WaitAsync(_resetEvent, "Failed to produce new head in time."); - } - else - { - await WaitAsync(_suggestedBlockResetEvent, "Failed to produce new suggested block in time."); - } - - _oneAtATime.Set(); - } + public async Task AddBlock(params Transaction[] transactions) + { + await AddBlockInternal(transactions); + + await WaitAsync(_resetEvent, "Failed to produce new head in time."); + _suggestedBlockResetEvent.Reset(); + _oneAtATime.Set(); + } - private async Task AddBlockInternal(params Transaction[] transactions) + public async Task AddBlock(bool shouldWaitForHead = true, params Transaction[] transactions) + { + await AddBlockInternal(transactions); + + if (shouldWaitForHead) { - // we want it to be last event, so lets re-register - BlockTree.NewHeadBlock -= OnNewHeadBlock; - BlockTree.NewHeadBlock += OnNewHeadBlock; - - await WaitAsync(_oneAtATime, "Multiple block produced at once."); - AcceptTxResult[] txResults = transactions.Select(t => TxPool.SubmitTx(t, TxHandlingOptions.None)).ToArray(); - Timestamper.Add(TimeSpan.FromSeconds(1)); - await BlockProductionTrigger.BuildBlock(); - return txResults; + await WaitAsync(_resetEvent, "Failed to produce new head in time."); } - - public void AddTransactions(params Transaction[] txs) + else { - for (int i = 0; i < txs.Length; i++) - { - TxPool.SubmitTx(txs[i], TxHandlingOptions.None); - } + await WaitAsync(_suggestedBlockResetEvent, "Failed to produce new suggested block in time."); } - public virtual void Dispose() + _oneAtATime.Set(); + } + + private async Task AddBlockInternal(params Transaction[] transactions) + { + // we want it to be last event, so lets re-register + BlockTree.NewHeadBlock -= OnNewHeadBlock; + BlockTree.NewHeadBlock += OnNewHeadBlock; + + await WaitAsync(_oneAtATime, "Multiple block produced at once."); + AcceptTxResult[] txResults = transactions.Select(t => TxPool.SubmitTx(t, TxHandlingOptions.None)).ToArray(); + Timestamper.Add(TimeSpan.FromSeconds(1)); + await BlockProductionTrigger.BuildBlock(); + return txResults; + } + + public void AddTransactions(params Transaction[] txs) + { + for (int i = 0; i < txs.Length; i++) { - BlockProducer?.StopAsync(); - CodeDb?.Dispose(); - StateDb?.Dispose(); - _trieStoreWatcher?.Dispose(); - DbProvider?.Dispose(); + TxPool.SubmitTx(txs[i], TxHandlingOptions.None); } + } - /// - /// Creates a simple transfer transaction with value defined by - /// from a rich account to - /// - /// Address to add funds to - /// Value of ether to add to the account - /// - public async Task AddFunds(Address address, UInt256 ether) => - await AddBlock(GetFundsTransaction(address, ether)); + public virtual void Dispose() + { + BlockProducer?.StopAsync(); + CodeDb?.Dispose(); + StateDb?.Dispose(); + _trieStoreWatcher?.Dispose(); + DbProvider?.Dispose(); + } - public async Task AddFunds(params (Address address, UInt256 ether)[] funds) => - await AddBlock(funds.Select((f, i) => GetFundsTransaction(f.address, f.ether, (uint)i)).ToArray()); + /// + /// Creates a simple transfer transaction with value defined by + /// from a rich account to + /// + /// Address to add funds to + /// Value of ether to add to the account + /// + public async Task AddFunds(Address address, UInt256 ether) => + await AddBlock(GetFundsTransaction(address, ether)); - public async Task AddFundsAfterLondon(params (Address address, UInt256 ether)[] funds) => - await AddBlock(funds.Select((f, i) => GetFunds1559Transaction(f.address, f.ether, (uint)i)).ToArray()); + public async Task AddFunds(params (Address address, UInt256 ether)[] funds) => + await AddBlock(funds.Select((f, i) => GetFundsTransaction(f.address, f.ether, (uint)i)).ToArray()); - private Transaction GetFundsTransaction(Address address, UInt256 ether, uint index = 0) - { - UInt256 nonce = StateReader.GetNonce(BlockTree.Head!.StateRoot!, TestItem.AddressA); - Transaction tx = Builders.Build.A.Transaction - .SignedAndResolved(TestItem.PrivateKeyA) - .To(address) - .WithNonce(nonce + index) - .WithValue(ether) - .TestObject; - return tx; - } + public async Task AddFundsAfterLondon(params (Address address, UInt256 ether)[] funds) => + await AddBlock(funds.Select((f, i) => GetFunds1559Transaction(f.address, f.ether, (uint)i)).ToArray()); - private Transaction GetFunds1559Transaction(Address address, UInt256 ether, uint index = 0) - { - UInt256 nonce = StateReader.GetNonce(BlockTree.Head!.StateRoot!, TestItem.AddressA); - Transaction tx = Builders.Build.A.Transaction - .SignedAndResolved(TestItem.PrivateKeyA) - .To(address) - .WithNonce(nonce + index) - .WithMaxFeePerGas(20.GWei()) - .WithMaxPriorityFeePerGas(5.GWei()) - .WithType(TxType.EIP1559) - .WithValue(ether) - .WithChainId(MainnetSpecProvider.Instance.ChainId) - .TestObject; - return tx; - } + private Transaction GetFundsTransaction(Address address, UInt256 ether, uint index = 0) + { + UInt256 nonce = StateReader.GetNonce(BlockTree.Head!.StateRoot!, TestItem.AddressA); + Transaction tx = Builders.Build.A.Transaction + .SignedAndResolved(TestItem.PrivateKeyA) + .To(address) + .WithNonce(nonce + index) + .WithValue(ether) + .TestObject; + return tx; + } + + private Transaction GetFunds1559Transaction(Address address, UInt256 ether, uint index = 0) + { + UInt256 nonce = StateReader.GetNonce(BlockTree.Head!.StateRoot!, TestItem.AddressA); + Transaction tx = Builders.Build.A.Transaction + .SignedAndResolved(TestItem.PrivateKeyA) + .To(address) + .WithNonce(nonce + index) + .WithMaxFeePerGas(20.GWei()) + .WithMaxPriorityFeePerGas(5.GWei()) + .WithType(TxType.EIP1559) + .WithValue(ether) + .WithChainId(MainnetSpecProvider.Instance.ChainId) + .TestObject; + return tx; } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs index bddfbaeb6a9..d8513ad68e0 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs @@ -139,7 +139,7 @@ public MergeTestBlockchain(IMergeConfig? mergeConfig = null, IPayloadPreparation protected override Task AddBlocksOnStart() => Task.CompletedTask; - public sealed override ILogManager LogManager { get; } = LimboLogs.Instance; + public sealed override ILogManager LogManager { get; set; } = LimboLogs.Instance; public IEthSyncingInfo? EthSyncingInfo { get; protected set; } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index 1485cd95ab2..bcc3036d862 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -24,12 +24,14 @@ using Nethermind.JsonRpc.Modules.Eth; using Nethermind.JsonRpc.Test; using Nethermind.JsonRpc.Test.Modules; +using Nethermind.Logging; using Nethermind.Merge.Plugin.Data; using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.State; using Nethermind.Trie; using Newtonsoft.Json; +using NSubstitute; using NUnit.Framework; namespace Nethermind.Merge.Plugin.Test; @@ -1666,7 +1668,6 @@ public async Task Should_return_capabilities() { using var chain = await CreateBlockChain(); var rpcModule = CreateEngineModule(chain); - var expected = new[] { "engine_exchangeTransitionConfigurationV1", @@ -1686,6 +1687,22 @@ public async Task Should_return_capabilities() result.Data.Should().BeEquivalentTo(expected); } + [Test] + public async Task Should_warn_for_missing_capabilities() + { + using var chain = await CreateBlockChain(); + chain.LogManager = Substitute.For(); + chain.LogManager.GetClassLogger().IsWarn.Returns(true); + + var rpcModule = CreateEngineModule(chain); + var unsupportedMethod = "unsupportedMethod"; + + var result = await rpcModule.engine_exchangeCapabilities(new[] { unsupportedMethod }); + + chain.LogManager.GetClassLogger().Received().Warn( + Arg.Is(a => a.Contains(unsupportedMethod, StringComparison.Ordinal))); + } + private async Task BuildAndGetPayloadResult( IEngineRpcModule rpc, MergeTestBlockchain chain, Keccak headBlockHash, Keccak finalizedBlockHash, Keccak safeBlockHash, From 23ec12d6d29a2b27fed01e83ddcb2f95366c16f1 Mon Sep 17 00:00:00 2001 From: Ruben Buniatyan Date: Fri, 27 Jan 2023 00:11:22 +0100 Subject: [PATCH 4/8] Extract capabilities handling --- .../EngineModuleTests.Setup.cs | 1 + .../EngineModuleTests.V1.cs | 10 +-- .../EngineRpcModule.cs | 61 +++-------------- .../Handlers/ExchangeCapabilitiesHandler.cs | 67 +++++++++++++++++++ .../Nethermind.Merge.Plugin/MergePlugin.cs | 1 + 5 files changed, 85 insertions(+), 55 deletions(-) create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs index d8513ad68e0..e777df983ce 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs @@ -100,6 +100,7 @@ private IEngineRpcModule CreateEngineModule(MergeTestBlockchain chain, ISyncConf new GetPayloadBodiesByHashV1Handler(chain.BlockTree, chain.LogManager), new GetPayloadBodiesByRangeV1Handler(chain.BlockTree, chain.LogManager), new ExchangeTransitionConfigurationV1Handler(chain.PoSSwitcher, chain.LogManager), + new ExchangeCapabilitiesHandler(chain.LogManager), chain.SpecProvider, chain.LogManager); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index bcc3036d862..97b2a055f35 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -1695,12 +1695,14 @@ public async Task Should_warn_for_missing_capabilities() chain.LogManager.GetClassLogger().IsWarn.Returns(true); var rpcModule = CreateEngineModule(chain); - var unsupportedMethod = "unsupportedMethod"; + var list = new[] { "missing" }; - var result = await rpcModule.engine_exchangeCapabilities(new[] { unsupportedMethod }); + var result = await rpcModule.engine_exchangeCapabilities(list); - chain.LogManager.GetClassLogger().Received().Warn( - Arg.Is(a => a.Contains(unsupportedMethod, StringComparison.Ordinal))); + chain.LogManager.GetClassLogger().Received(2).Warn( + Arg.Is(a => + a.Contains(result.Data.First(), StringComparison.Ordinal) || + a.Contains(list.First(), StringComparison.Ordinal))); } private async Task BuildAndGetPayloadResult( diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs index 83cc460fe16..0bc62e599f3 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; using System.Threading.Tasks; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -17,7 +15,8 @@ namespace Nethermind.Merge.Plugin; public partial class EngineRpcModule : IEngineRpcModule { - private static IEnumerable? _capabilities; + + private readonly IAsyncHandler, IEnumerable> _capabilitiesHandler; private readonly IHandler _executionStatusHandler; private readonly IAsyncHandler _executionGetPayloadBodiesByHashV1Handler; private readonly IGetPayloadBodiesByRangeV1Handler _executionGetPayloadBodiesByRangeV1Handler; @@ -33,9 +32,11 @@ public EngineRpcModule( IAsyncHandler executionGetPayloadBodiesByHashV1Handler, IGetPayloadBodiesByRangeV1Handler executionGetPayloadBodiesByRangeV1Handler, IHandler transitionConfigurationHandler, + IAsyncHandler, IEnumerable> capabilitiesHandler, ISpecProvider specProvider, ILogManager logManager) { + _capabilitiesHandler = capabilitiesHandler ?? throw new ArgumentNullException(nameof(capabilitiesHandler)); _getPayloadHandlerV1 = getPayloadHandlerV1; _getPayloadHandlerV2 = getPayloadHandlerV2; _newPayloadV1Handler = newPayloadV1Handler; @@ -48,56 +49,14 @@ public EngineRpcModule( _logger = logManager.GetClassLogger(); } - public async Task>> engine_exchangeCapabilities(IEnumerable methods) - { - ArgumentNullException.ThrowIfNull(methods); - - if (await _locker.WaitAsync(1_000)) - { - var watch = Stopwatch.StartNew(); - - try - { - _capabilities ??= typeof(IEngineRpcModule).GetMethods() - .Select(m => m.Name) - .Where(m => !m.Equals(nameof(engine_exchangeCapabilities), StringComparison.Ordinal)) - .Order(); - - var unsupported = methods.Except(_capabilities); - - if (unsupported.Any()) - { - if (_logger.IsWarn) _logger.Warn($"Unsupported capabilities: {string.Join(", ", unsupported)}"); - } - - return ResultWrapper>.Success(_capabilities); - } - finally - { - watch.Stop(); - - Metrics.ForkchoiceUpdedExecutionTime = watch.ElapsedMilliseconds; - - _locker.Release(); - } - } - else - { - if (_logger.IsWarn) _logger.Warn($"{nameof(engine_exchangeCapabilities)} timed out"); - - return ResultWrapper>.Fail("Timed out", ErrorCodes.Timeout); - } - } + public Task>> engine_exchangeCapabilities(IEnumerable methods) + => _capabilitiesHandler.HandleAsync(methods); public ResultWrapper engine_executionStatus() => _executionStatusHandler.Handle(); - public async Task> engine_getPayloadBodiesByHashV1(Keccak[] blockHashes) - { - return await _executionGetPayloadBodiesByHashV1Handler.HandleAsync(blockHashes); - } + public Task> engine_getPayloadBodiesByHashV1(Keccak[] blockHashes) + => _executionGetPayloadBodiesByHashV1Handler.HandleAsync(blockHashes); - public async Task> engine_getPayloadBodiesByRangeV1(long start, long count) - { - return await _executionGetPayloadBodiesByRangeV1Handler.Handle(start, count); - } + public Task> engine_getPayloadBodiesByRangeV1(long start, long count) + => _executionGetPayloadBodiesByRangeV1Handler.Handle(start, count); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs new file mode 100644 index 00000000000..8f8c0702f4f --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2023 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.Core; +using Nethermind.JsonRpc; +using Nethermind.Logging; + +namespace Nethermind.Merge.Plugin.Handlers; + +public class ExchangeCapabilitiesHandler : IAsyncHandler, IEnumerable> +{ + private static IEnumerable? _capabilities; + private static readonly TimeSpan _timeout = TimeSpan.FromSeconds(1); + + private readonly ILogger _logger; + + public ExchangeCapabilitiesHandler(ILogManager logManager) + { + ArgumentNullException.ThrowIfNull(logManager); + + _logger = logManager.GetClassLogger(); + } + + public async Task>> HandleAsync(IEnumerable methods) + { + var task = Task.Run(() => CheckCapabilitiesAsync(methods)); + + try + { + await task.WaitAsync(_timeout); + + return ResultWrapper>.Success(_capabilities!); + } + catch (TimeoutException) + { + if (_logger.IsWarn) _logger.Warn($"{nameof(IEngineRpcModule.engine_exchangeCapabilities)} timed out"); + + return ResultWrapper>.Fail("Timed out", ErrorCodes.Timeout); + } + } + + private void CheckCapabilitiesAsync(IEnumerable methods) + { + _capabilities ??= typeof(IEngineRpcModule).GetMethods() + .Select(m => m.Name) + .Where(m => !m.Equals(nameof(IEngineRpcModule.engine_exchangeCapabilities), StringComparison.Ordinal)) + .Order(); + + var missing = methods.Except(_capabilities); + + if (missing.Any()) + { + if (_logger.IsWarn) _logger.Warn($"{ProductInfo.Name} missing capabilities: {string.Join(", ", missing)}"); + } + + missing = _capabilities.Except(methods); + + if (missing.Any()) + { + if (_logger.IsWarn) _logger.Warn($"Consensus client missing capabilities: {string.Join(", ", missing)}"); + } + } +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index 5599300a179..aca5bd3974a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -333,6 +333,7 @@ public Task InitRpcModules() new GetPayloadBodiesByHashV1Handler(_api.BlockTree, _api.LogManager), new GetPayloadBodiesByRangeV1Handler(_api.BlockTree, _api.LogManager), new ExchangeTransitionConfigurationV1Handler(_poSSwitcher, _api.LogManager), + new ExchangeCapabilitiesHandler(_api.LogManager), _api.SpecProvider, _api.LogManager); From e70fcd80d7f6e3d99ed726cc26ccb1970e1f58d3 Mon Sep 17 00:00:00 2001 From: Ruben Buniatyan Date: Fri, 27 Jan 2023 10:08:58 +0100 Subject: [PATCH 5/8] Revise the method name --- .../Handlers/ExchangeCapabilitiesHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs index 8f8c0702f4f..51df2c6d9a1 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs @@ -27,7 +27,7 @@ public ExchangeCapabilitiesHandler(ILogManager logManager) public async Task>> HandleAsync(IEnumerable methods) { - var task = Task.Run(() => CheckCapabilitiesAsync(methods)); + var task = Task.Run(() => CheckCapabilities(methods)); try { @@ -43,7 +43,7 @@ public async Task>> HandleAsync(IEnumerable methods) + private void CheckCapabilities(IEnumerable methods) { _capabilities ??= typeof(IEngineRpcModule).GetMethods() .Select(m => m.Name) From a57e3038e7e362cbed7b0883c3267348fd0ce134 Mon Sep 17 00:00:00 2001 From: Ruben Buniatyan Date: Fri, 27 Jan 2023 11:31:10 +0100 Subject: [PATCH 6/8] Revise warning logic --- .../EngineModuleTests.Setup.cs | 2 +- .../EngineModuleTests.V1.cs | 23 +++------ .../Handlers/ExchangeCapabilitiesHandler.cs | 49 +++++++++++++------ .../Nethermind.Merge.Plugin/MergePlugin.cs | 2 +- 4 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs index e777df983ce..12b14ddba12 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs @@ -100,7 +100,7 @@ private IEngineRpcModule CreateEngineModule(MergeTestBlockchain chain, ISyncConf new GetPayloadBodiesByHashV1Handler(chain.BlockTree, chain.LogManager), new GetPayloadBodiesByRangeV1Handler(chain.BlockTree, chain.LogManager), new ExchangeTransitionConfigurationV1Handler(chain.PoSSwitcher, chain.LogManager), - new ExchangeCapabilitiesHandler(chain.LogManager), + new ExchangeCapabilitiesHandler(chain.SpecProvider, chain.LogManager), chain.SpecProvider, chain.LogManager); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index 97b2a055f35..cacfeaeb561 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -1668,19 +1668,10 @@ public async Task Should_return_capabilities() { using var chain = await CreateBlockChain(); var rpcModule = CreateEngineModule(chain); - var expected = new[] - { - "engine_exchangeTransitionConfigurationV1", - "engine_executionStatus", - "engine_forkchoiceUpdatedV1", - "engine_forkchoiceUpdatedV2", - "engine_getPayloadBodiesByHashV1", - "engine_getPayloadBodiesByRangeV1", - "engine_getPayloadV1", - "engine_getPayloadV2", - "engine_newPayloadV1", - "engine_newPayloadV2" - }; + var expected = typeof(IEngineRpcModule).GetMethods() + .Select(m => m.Name) + .Where(m => !m.Equals(nameof(IEngineRpcModule.engine_exchangeCapabilities), StringComparison.Ordinal)) + .Order(); var result = await rpcModule.engine_exchangeCapabilities(expected); @@ -1699,10 +1690,8 @@ public async Task Should_warn_for_missing_capabilities() var result = await rpcModule.engine_exchangeCapabilities(list); - chain.LogManager.GetClassLogger().Received(2).Warn( - Arg.Is(a => - a.Contains(result.Data.First(), StringComparison.Ordinal) || - a.Contains(list.First(), StringComparison.Ordinal))); + chain.LogManager.GetClassLogger().Received().Warn( + Arg.Is(a => a.Contains(nameof(IEngineRpcModule.engine_forkchoiceUpdatedV1), StringComparison.Ordinal))); } private async Task BuildAndGetPayloadResult( diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs index 51df2c6d9a1..a706014f28f 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Nethermind.Core; +using Nethermind.Core.Specs; using Nethermind.JsonRpc; using Nethermind.Logging; @@ -13,16 +13,33 @@ namespace Nethermind.Merge.Plugin.Handlers; public class ExchangeCapabilitiesHandler : IAsyncHandler, IEnumerable> { - private static IEnumerable? _capabilities; + private static IDictionary _capabilities = new Dictionary(); private static readonly TimeSpan _timeout = TimeSpan.FromSeconds(1); private readonly ILogger _logger; - public ExchangeCapabilitiesHandler(ILogManager logManager) + public ExchangeCapabilitiesHandler(ISpecProvider specProvider, ILogManager logManager) { + ArgumentNullException.ThrowIfNull(specProvider); ArgumentNullException.ThrowIfNull(logManager); _logger = logManager.GetClassLogger(); + + if (_capabilities.Count == 0) + { + var spec = specProvider.GetSpec((long.MaxValue, ulong.MaxValue)); + + _capabilities[nameof(IEngineRpcModule.engine_exchangeTransitionConfigurationV1)] = true; + _capabilities[nameof(IEngineRpcModule.engine_executionStatus)] = true; + _capabilities[nameof(IEngineRpcModule.engine_forkchoiceUpdatedV1)] = true; + _capabilities[nameof(IEngineRpcModule.engine_forkchoiceUpdatedV2)] = spec.WithdrawalsEnabled; + _capabilities[nameof(IEngineRpcModule.engine_getPayloadBodiesByHashV1)] = spec.WithdrawalsEnabled; + _capabilities[nameof(IEngineRpcModule.engine_getPayloadBodiesByRangeV1)] = spec.WithdrawalsEnabled; + _capabilities[nameof(IEngineRpcModule.engine_getPayloadV1)] = true; + _capabilities[nameof(IEngineRpcModule.engine_getPayloadV2)] = spec.WithdrawalsEnabled; + _capabilities[nameof(IEngineRpcModule.engine_newPayloadV1)] = true; + _capabilities[nameof(IEngineRpcModule.engine_newPayloadV2)] = spec.WithdrawalsEnabled; + } } public async Task>> HandleAsync(IEnumerable methods) @@ -33,7 +50,7 @@ public async Task>> HandleAsync(IEnumerable>.Success(_capabilities!); + return ResultWrapper>.Success(_capabilities.Keys); } catch (TimeoutException) { @@ -45,19 +62,23 @@ public async Task>> HandleAsync(IEnumerable methods) { - _capabilities ??= typeof(IEngineRpcModule).GetMethods() - .Select(m => m.Name) - .Where(m => !m.Equals(nameof(IEngineRpcModule.engine_exchangeCapabilities), StringComparison.Ordinal)) - .Order(); - - var missing = methods.Except(_capabilities); + var missing = new List(); - if (missing.Any()) + foreach (var capability in _capabilities) { - if (_logger.IsWarn) _logger.Warn($"{ProductInfo.Name} missing capabilities: {string.Join(", ", missing)}"); - } + var found = false; - missing = _capabilities.Except(methods); + foreach (var method in methods) + if (method.Equals(capability.Key, StringComparison.Ordinal)) + { + found = true; + break; + } + + // Warn if not found and capability activated + if (!found && capability.Value) + missing.Add(capability.Key); + } if (missing.Any()) { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index aca5bd3974a..d6d58c3bed5 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -333,7 +333,7 @@ public Task InitRpcModules() new GetPayloadBodiesByHashV1Handler(_api.BlockTree, _api.LogManager), new GetPayloadBodiesByRangeV1Handler(_api.BlockTree, _api.LogManager), new ExchangeTransitionConfigurationV1Handler(_poSSwitcher, _api.LogManager), - new ExchangeCapabilitiesHandler(_api.LogManager), + new ExchangeCapabilitiesHandler(_api.SpecProvider, _api.LogManager), _api.SpecProvider, _api.LogManager); From 78ea32039206d1790ee2800e8ebc456993945a56 Mon Sep 17 00:00:00 2001 From: Ruben Buniatyan Date: Fri, 27 Jan 2023 11:56:44 +0100 Subject: [PATCH 7/8] Improve warning test --- .../EngineModuleTests.V1.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index cacfeaeb561..9e16fbbfb38 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -1686,12 +1686,18 @@ public async Task Should_warn_for_missing_capabilities() chain.LogManager.GetClassLogger().IsWarn.Returns(true); var rpcModule = CreateEngineModule(chain); - var list = new[] { "missing" }; + var list = new[] + { + nameof(IEngineRpcModule.engine_forkchoiceUpdatedV1), + nameof(IEngineRpcModule.engine_forkchoiceUpdatedV2) + }; var result = await rpcModule.engine_exchangeCapabilities(list); chain.LogManager.GetClassLogger().Received().Warn( - Arg.Is(a => a.Contains(nameof(IEngineRpcModule.engine_forkchoiceUpdatedV1), StringComparison.Ordinal))); + Arg.Is(a => + a.Contains(nameof(IEngineRpcModule.engine_getPayloadV1), StringComparison.Ordinal) && + !a.Contains(nameof(IEngineRpcModule.engine_getPayloadV2), StringComparison.Ordinal))); } private async Task BuildAndGetPayloadResult( From dcc4d73715677366028c1dde4eaf03fcdf186092 Mon Sep 17 00:00:00 2001 From: Ruben Buniatyan Date: Fri, 27 Jan 2023 14:34:35 +0100 Subject: [PATCH 8/8] Update capabilities order --- .../Handlers/ExchangeCapabilitiesHandler.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs index a706014f28f..d623a202382 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeCapabilitiesHandler.cs @@ -29,16 +29,21 @@ public ExchangeCapabilitiesHandler(ISpecProvider specProvider, ILogManager logMa { var spec = specProvider.GetSpec((long.MaxValue, ulong.MaxValue)); + #region The Merge _capabilities[nameof(IEngineRpcModule.engine_exchangeTransitionConfigurationV1)] = true; _capabilities[nameof(IEngineRpcModule.engine_executionStatus)] = true; _capabilities[nameof(IEngineRpcModule.engine_forkchoiceUpdatedV1)] = true; + _capabilities[nameof(IEngineRpcModule.engine_getPayloadV1)] = true; + _capabilities[nameof(IEngineRpcModule.engine_newPayloadV1)] = true; + #endregion + + #region Shanghai _capabilities[nameof(IEngineRpcModule.engine_forkchoiceUpdatedV2)] = spec.WithdrawalsEnabled; _capabilities[nameof(IEngineRpcModule.engine_getPayloadBodiesByHashV1)] = spec.WithdrawalsEnabled; _capabilities[nameof(IEngineRpcModule.engine_getPayloadBodiesByRangeV1)] = spec.WithdrawalsEnabled; - _capabilities[nameof(IEngineRpcModule.engine_getPayloadV1)] = true; _capabilities[nameof(IEngineRpcModule.engine_getPayloadV2)] = spec.WithdrawalsEnabled; - _capabilities[nameof(IEngineRpcModule.engine_newPayloadV1)] = true; _capabilities[nameof(IEngineRpcModule.engine_newPayloadV2)] = spec.WithdrawalsEnabled; + #endregion } }