diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.DelayBlockImprovementContextFactory.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.DelayBlockImprovementContextFactory.cs index a7442ec542a..837c43264dd 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.DelayBlockImprovementContextFactory.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.DelayBlockImprovementContextFactory.cs @@ -32,33 +32,34 @@ private class DelayBlockImprovementContextFactory : IBlockImprovementContextFact { private readonly IManualBlockProductionTrigger _productionTrigger; private readonly TimeSpan _timeout; - private readonly int _delay; + private readonly TimeSpan _delay; - public DelayBlockImprovementContextFactory(IManualBlockProductionTrigger productionTrigger, TimeSpan timeout, int delay) + public DelayBlockImprovementContextFactory(IManualBlockProductionTrigger productionTrigger, TimeSpan timeout, TimeSpan delay) { _productionTrigger = productionTrigger; _timeout = timeout; _delay = delay; } - public IBlockImprovementContext StartBlockImprovementContext(Block currentBestBlock, BlockHeader parentHeader, PayloadAttributes payloadAttributes) => - new DelayBlockImprovementContext(currentBestBlock, _productionTrigger, _timeout, parentHeader, payloadAttributes, _delay); + public IBlockImprovementContext StartBlockImprovementContext(Block currentBestBlock, BlockHeader parentHeader, PayloadAttributes payloadAttributes, DateTimeOffset startDateTime) => + new DelayBlockImprovementContext(currentBestBlock, _productionTrigger, _timeout, parentHeader, payloadAttributes, _delay, startDateTime); } private class DelayBlockImprovementContext : IBlockImprovementContext { private CancellationTokenSource? _cancellationTokenSource; - public DelayBlockImprovementContext( - Block currentBestBlock, + public DelayBlockImprovementContext(Block currentBestBlock, IManualBlockProductionTrigger blockProductionTrigger, TimeSpan timeout, BlockHeader parentHeader, PayloadAttributes payloadAttributes, - int delay) + TimeSpan delay, + DateTimeOffset startDateTime) { _cancellationTokenSource = new CancellationTokenSource(timeout); CurrentBestBlock = currentBestBlock; + StartDateTime = startDateTime; ImprovementTask = BuildBlock(blockProductionTrigger, parentHeader, payloadAttributes, delay, _cancellationTokenSource.Token); } @@ -66,7 +67,7 @@ public DelayBlockImprovementContext( IManualBlockProductionTrigger blockProductionTrigger, BlockHeader parentHeader, PayloadAttributes payloadAttributes, - int delay, + TimeSpan delay, CancellationToken cancellationToken) { Block? block = await blockProductionTrigger.BuildBlock(parentHeader, cancellationToken, NullBlockTracer.Instance, payloadAttributes); @@ -80,11 +81,13 @@ public DelayBlockImprovementContext( } public Task ImprovementTask { get; } - public Block? CurrentBestBlock { get; private set; } + public bool Disposed { get; private set; } + public DateTimeOffset StartDateTime { get; } public void Dispose() { + Disposed = true; CancellationTokenExtensions.CancelDisposeAndClear(ref _cancellationTokenSource); } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs index 37edd793482..acdb1e4a7bc 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs @@ -1,19 +1,19 @@ // Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. -// +// // The Nethermind library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // The Nethermind library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. -// +// // You should have received a copy of the GNU Lesser General Public License // along with the Nethermind. If not, see . -// +// using System; using System.Collections.Generic; @@ -59,12 +59,10 @@ private void AssertExecutionStatusChanged(IEngineRpcModule rpc, Keccak headBlock private (UInt256, UInt256) AddTransactions(MergeTestBlockchain chain, ExecutionPayloadV1 executePayloadRequest, PrivateKey from, Address to, uint count, int value, out BlockHeader parentHeader) { - Transaction[] transactions = BuildTransactions(chain, executePayloadRequest.ParentHash, from, to, count, value, - out Account accountFrom, out parentHeader); + Transaction[] transactions = BuildTransactions(chain, executePayloadRequest.ParentHash, from, to, count, value, out Account accountFrom, out parentHeader); executePayloadRequest.SetTransactions(transactions); UInt256 totalValue = ((int)(count * value)).GWei(); - return (accountFrom.Balance - totalValue, - chain.StateReader.GetBalance(parentHeader.StateRoot!, to) + totalValue); + return (accountFrom.Balance - totalValue, chain.StateReader.GetBalance(parentHeader.StateRoot!, to) + totalValue); } private Transaction[] BuildTransactions(MergeTestBlockchain chain, Keccak parentHash, PrivateKey from, @@ -82,11 +80,10 @@ Transaction BuildTransaction(uint index, Account senderAccount) => .TestObject; parentHeader = chain.BlockTree.FindHeader(parentHash, BlockTreeLookupOptions.None)!; - Account account = chain.StateReader.GetAccount(parentHeader.StateRoot!, @from.Address)!; + Account account = chain.StateReader.GetAccount(parentHeader.StateRoot!, from.Address)!; accountFrom = account; - return Enumerable.Range(0, (int)count) - .Select(i => BuildTransaction((uint)i, account)).ToArray(); + return Enumerable.Range(0, (int)count).Select(i => BuildTransaction((uint)i, account)).ToArray(); } private ExecutionPayloadV1 CreateParentBlockRequestOnHead(IBlockTree blockTree) @@ -141,8 +138,7 @@ private static ExecutionPayloadV1[] CreateBlockRequestBranch(ExecutionPayloadV1 private Block? RunForAllBlocksInBranch(IBlockTree blockTree, Keccak blockHash, Func shouldStop, bool requireCanonical) { - BlockTreeLookupOptions options = - requireCanonical ? BlockTreeLookupOptions.RequireCanonical : BlockTreeLookupOptions.None; + BlockTreeLookupOptions options = requireCanonical ? BlockTreeLookupOptions.RequireCanonical : BlockTreeLookupOptions.None; Block? current = blockTree.FindBlock(blockHash, options); while (current is not null && !shouldStop(current)) { @@ -160,8 +156,7 @@ private static TestCaseData GetNewBlockRequestBadDataTestCase( Action wrongValueSetter = r => setter(r, wrongValue); return new TestCaseData(wrongValueSetter) { - TestName = - $"executePayload_rejects_incorrect_{propertyAccess.GetName().ToLower()}({wrongValue?.ToString()})" + TestName = $"executePayload_rejects_incorrect_{propertyAccess.GetName().ToLower()}({wrongValue?.ToString()})" }; } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.MockBlockImprovementContextFactory.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.MockBlockImprovementContextFactory.cs new file mode 100644 index 00000000000..9bf61848368 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.MockBlockImprovementContextFactory.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Consensus.Producers; +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Evm.Tracing; +using Nethermind.Merge.Plugin.BlockProduction; + +namespace Nethermind.Merge.Plugin.Test; + +public partial class EngineModuleTests +{ + private class MockBlockImprovementContextFactory : IBlockImprovementContextFactory + { + public IBlockImprovementContext StartBlockImprovementContext(Block currentBestBlock, BlockHeader parentHeader, PayloadAttributes payloadAttributes, DateTimeOffset startDateTime) => + new MockBlockImprovementContext(currentBestBlock, startDateTime); + } + + private class MockBlockImprovementContext : IBlockImprovementContext + { + public MockBlockImprovementContext(Block currentBestBlock, DateTimeOffset startDateTime) + { + CurrentBestBlock = currentBestBlock; + StartDateTime = startDateTime; + ImprovementTask = Task.FromResult((Block?)currentBestBlock); + } + + public void Dispose() => Disposed = true; + public Task ImprovementTask { get; } + public Block? CurrentBestBlock { get; } + public bool Disposed { get; private set; } + public DateTimeOffset StartDateTime { get; } + } +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.StoringBlockImprovementContextFactory.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.StoringBlockImprovementContextFactory.cs new file mode 100644 index 00000000000..2fbb607e307 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.StoringBlockImprovementContextFactory.cs @@ -0,0 +1,62 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Consensus.Producers; +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Evm.Tracing; +using Nethermind.Merge.Plugin.BlockProduction; + +namespace Nethermind.Merge.Plugin.Test; + +public partial class EngineModuleTests +{ + private class StoringBlockImprovementContextFactory : IBlockImprovementContextFactory + { + private readonly IBlockImprovementContextFactory _blockImprovementContextFactory; + public IList CreatedContexts { get; } = new List(); + + public event EventHandler? ImprovementStarted; + + public StoringBlockImprovementContextFactory(IBlockImprovementContextFactory blockImprovementContextFactory) + { + _blockImprovementContextFactory = blockImprovementContextFactory; + } + + public IBlockImprovementContext StartBlockImprovementContext(Block currentBestBlock, BlockHeader parentHeader, PayloadAttributes payloadAttributes, DateTimeOffset startDateTime) + { + IBlockImprovementContext blockImprovementContext = _blockImprovementContextFactory.StartBlockImprovementContext(currentBestBlock, parentHeader, payloadAttributes, startDateTime); + CreatedContexts.Add(blockImprovementContext); + Task.Run(() => ImprovementStarted?.Invoke(this, new ImprovementStartedEventArgs(blockImprovementContext))); + return blockImprovementContext; + } + } + + private class ImprovementStartedEventArgs : EventArgs + { + public IBlockImprovementContext BlockImprovementContext { get; } + + public ImprovementStartedEventArgs(IBlockImprovementContext blockImprovementContext) + { + BlockImprovementContext = blockImprovementContext; + } + } +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.PayloadProduction.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.PayloadProduction.cs new file mode 100644 index 00000000000..a9df78b7738 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.PayloadProduction.cs @@ -0,0 +1,369 @@ +// Copyright (c) 2021 Demerzel Solutions Limited +// This file is part of the Nethermind library. +// +// The Nethermind library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The Nethermind library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the Nethermind. If not, see . +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Nethermind.Consensus.Producers; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Core.Timers; +using Nethermind.Crypto; +using Nethermind.Int256; +using Nethermind.JsonRpc; +using Nethermind.JsonRpc.Test; +using Nethermind.Merge.Plugin.BlockProduction; +using Nethermind.Merge.Plugin.Data.V1; +using Nethermind.State; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Merge.Plugin.Test; + +public partial class EngineModuleTests +{ + [Test] + public async Task getPayloadV1_should_allow_asking_multiple_times_by_same_payload_id() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + + Keccak startingHead = chain.BlockTree.HeadHash; + ForkchoiceStateV1 forkchoiceState = new(startingHead, Keccak.Zero, startingHead); + PayloadAttributes payload = new() { Timestamp = Timestamper.UnixTime.Seconds, SuggestedFeeRecipient = Address.Zero, PrevRandao = Keccak.Zero }; + Task> forkchoiceResponse = rpc.engine_forkchoiceUpdatedV1(forkchoiceState, payload); + byte[] payloadId = Bytes.FromHexString(forkchoiceResponse.Result.Data.PayloadId!); + ResultWrapper responseFirst = await rpc.engine_getPayloadV1(payloadId); + responseFirst.Should().NotBeNull(); + responseFirst.Result.ResultType.Should().Be(ResultType.Success); + ResultWrapper responseSecond = await rpc.engine_getPayloadV1(payloadId); + responseSecond.Should().NotBeNull(); + responseSecond.Result.ResultType.Should().Be(ResultType.Success); + responseSecond.Data!.BlockHash!.Should().Be(responseFirst.Data!.BlockHash!); + } + + [Test] + public async Task getPayloadV1_should_return_error_if_called_after_cleanup_timer() + { + MergeConfig mergeConfig = new() { Enabled = true, SecondsPerSlot = 1, TerminalTotalDifficulty = "0" }; + using MergeTestBlockchain chain = await CreateBlockChain(mergeConfig); + BlockImprovementContextFactory improvementContextFactory = new(chain.BlockProductionTrigger, TimeSpan.FromSeconds(1)); + TimeSpan timePerSlot = TimeSpan.FromMilliseconds(10); + chain.PayloadPreparationService = new PayloadPreparationService( + chain.PostMergeBlockProducer!, + improvementContextFactory, + TimerFactory.Default, + chain.LogManager, + timePerSlot); + + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + UInt256 timestamp = Timestamper.UnixTime.Seconds; + Keccak random = Keccak.Zero; + Address feeRecipient = Address.Zero; + + string payloadId = rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(startingHead, Keccak.Zero, startingHead), + new PayloadAttributes { Timestamp = timestamp, SuggestedFeeRecipient = feeRecipient, PrevRandao = random }).Result.Data + .PayloadId!; + + await Task.Delay(PayloadPreparationService.SlotsPerOldPayloadCleanup * 2 * timePerSlot + timePerSlot); + + ResultWrapper response = await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId)); + + response.ErrorCode.Should().Be(MergeErrorCodes.UnknownPayload); + } + + [Test] + public async Task getPayloadV1_picks_transactions_from_pool_v1() + { + using SemaphoreSlim blockImprovementLock = new(0); + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + uint count = 3; + int value = 10; + Address recipient = TestItem.AddressF; + PrivateKey sender = TestItem.PrivateKeyB; + Transaction[] transactions = BuildTransactions(chain, startingHead, sender, recipient, count, value, out _, out _); + chain.AddTransactions(transactions); + chain.PayloadPreparationService!.BlockImproved += (_, _) => { blockImprovementLock.Release(1); }; + string? payloadId = rpc.engine_forkchoiceUpdatedV1( + new ForkchoiceStateV1(startingHead, Keccak.Zero, startingHead), + new PayloadAttributes() { Timestamp = 100, PrevRandao = TestItem.KeccakA, SuggestedFeeRecipient = Address.Zero }) + .Result.Data.PayloadId!; + + await blockImprovementLock.WaitAsync(10000); + ExecutionPayloadV1 getPayloadResult = (await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId))).Data!; + + getPayloadResult.StateRoot.Should().NotBe(chain.BlockTree.Genesis!.StateRoot!); + + Transaction[] transactionsInBlock = getPayloadResult.GetTransactions(); + transactionsInBlock.Should().BeEquivalentTo(transactions, o => o + .Excluding(t => t.ChainId) + .Excluding(t => t.SenderAddress) + .Excluding(t => t.Timestamp) + .Excluding(t => t.PoolIndex) + .Excluding(t => t.GasBottleneck)); + + ResultWrapper executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); + executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); + + UInt256 totalValue = ((int)(count * value)).GWei(); + chain.StateReader.GetBalance(getPayloadResult.StateRoot, recipient).Should().Be(totalValue); + } + + public static IEnumerable WaitTestCases + { + get + { + yield return new TestCaseData(PayloadPreparationService.GetPayloadWaitForFullBlockMillisecondsDelay / 10) { ExpectedResult = 3, TestName = "Production manages to finish" }; + yield return new TestCaseData(PayloadPreparationService.GetPayloadWaitForFullBlockMillisecondsDelay * 2) { ExpectedResult = 0, TestName = "Production takes too long" }; + } + } + + [TestCaseSource(nameof(WaitTestCases))] + public async Task getPayloadV1_waits_for_block_production(TimeSpan delay) + { + using MergeTestBlockchain chain = await CreateBlockChain(); + + DelayBlockImprovementContextFactory improvementContextFactory = new(chain.BlockProductionTrigger, TimeSpan.FromSeconds(10), delay); + chain.PayloadPreparationService = new PayloadPreparationService( + chain.PostMergeBlockProducer!, + improvementContextFactory, + TimerFactory.Default, + chain.LogManager, + TimeSpan.FromSeconds(10)); + + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + uint count = 3; + int value = 10; + Address recipient = TestItem.AddressF; + PrivateKey sender = TestItem.PrivateKeyB; + Transaction[] transactions = BuildTransactions(chain, startingHead, sender, recipient, count, value, out _, out _); + chain.AddTransactions(transactions); + string payloadId = rpc.engine_forkchoiceUpdatedV1( + new ForkchoiceStateV1(startingHead, Keccak.Zero, startingHead), + new PayloadAttributes { Timestamp = 100, PrevRandao = TestItem.KeccakA, SuggestedFeeRecipient = Address.Zero }) + .Result.Data.PayloadId!; + + ExecutionPayloadV1 getPayloadResult = (await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId))).Data!; + + return getPayloadResult.Transactions.Length; + } + + [Test] + public async Task getPayloadV1_return_correct_block_values_for_empty_block() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + Keccak? random = TestItem.KeccakF; + UInt256 timestamp = chain.BlockTree.Head!.Timestamp + 5; + Address? suggestedFeeRecipient = TestItem.AddressC; + PayloadAttributes? payloadAttributes = new() { PrevRandao = random, Timestamp = timestamp, SuggestedFeeRecipient = suggestedFeeRecipient }; + ExecutionPayloadV1 getPayloadResult = await BuildAndGetPayloadResult(chain, rpc, payloadAttributes); + getPayloadResult.ParentHash.Should().Be(startingHead); + + + ResultWrapper executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); + executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); + + BlockHeader? currentHeader = chain.BlockTree.BestSuggestedHeader!; + + Assert.AreEqual("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", currentHeader.UnclesHash!.ToString()); + Assert.AreEqual((UInt256)0, currentHeader.Difficulty); + Assert.AreEqual(0, currentHeader.Nonce); + Assert.AreEqual(random, currentHeader.MixHash); + } + + [Test] + public async Task getPayloadV1_should_return_error_if_there_was_no_corresponding_prepare_call() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + UInt256 timestamp = Timestamper.UnixTime.Seconds; + Keccak random = Keccak.Zero; + Address feeRecipient = Address.Zero; + string _ = rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(startingHead, Keccak.Zero, startingHead), + new PayloadAttributes { Timestamp = timestamp, SuggestedFeeRecipient = feeRecipient, PrevRandao = random }).Result.Data + .PayloadId!; + + byte[] requestedPayloadId = Bytes.FromHexString("0x45bd36a8143d860d"); + ResultWrapper response = await rpc.engine_getPayloadV1(requestedPayloadId); + + response.ErrorCode.Should().Be(MergeErrorCodes.UnknownPayload); + } + + [Test] + public async Task getPayload_correctlyEncodeTransactions() + { + byte[] payload = new byte[0]; + IPayloadPreparationService payloadPreparationService = Substitute.For(); + Block block = Build.A.Block.WithTransactions( + Build.A.Transaction.WithTo(TestItem.AddressD).SignedAndResolved(TestItem.PrivateKeyA).TestObject, + Build.A.Transaction.WithTo(TestItem.AddressD).WithType(TxType.EIP1559).WithMaxFeePerGas(20).SignedAndResolved(TestItem.PrivateKeyA).TestObject).TestObject; + payloadPreparationService.GetPayload(Arg.Any()).Returns(block); + using MergeTestBlockchain chain = await CreateBlockChain(null, payloadPreparationService); + + IEngineRpcModule rpc = CreateEngineModule(chain); + + string result = RpcTest.TestSerializedRequest(rpc, "engine_getPayloadV1", payload.ToHexString(true)); + Assert.AreEqual(result, "{\"jsonrpc\":\"2.0\",\"result\":{\"parentHash\":\"0xff483e972a04a9a62bb4b7d04ae403c615604e4090521ecc5bb7af67f71be09c\",\"feeRecipient\":\"0x0000000000000000000000000000000000000000\",\"stateRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"receiptsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"prevRandao\":\"0x2ba5557a4c62a513c7e56d1bf13373e0da6bec016755483e91589fe1c6d212e2\",\"blockNumber\":\"0x0\",\"gasLimit\":\"0x3d0900\",\"gasUsed\":\"0x0\",\"timestamp\":\"0xf4240\",\"extraData\":\"0x010203\",\"baseFeePerGas\":\"0x0\",\"blockHash\":\"0x5fd61518405272d77fd6cdc8a824a109d75343e32024ee4f6769408454b1823d\",\"transactions\":[\"0xf85f800182520894475674cb523a0a2736b7f7534390288fce16982c018025a0634db2f18f24d740be29e03dd217eea5757ed7422680429bdd458c582721b6c2a02f0fa83931c9a99d3448a46b922261447d6a41d8a58992b5596089d15d521102\",\"0x02f8620180011482520894475674cb523a0a2736b7f7534390288fce16982c0180c001a0033e85439a128c42f2ba47ca278f1375ef211e61750018ff21bcd9750d1893f2a04ee981fe5261f8853f95c865232ffdab009abcc7858ca051fb624c49744bf18d\"]},\"id\":67}"); + } + + [Test] + public async Task getPayload_should_serialize_unknown_payload_response_properly() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + byte[] payloadId = Bytes.FromHexString("0x1111111111111111"); + + string parameters = payloadId.ToHexString(true); + string result = RpcTest.TestSerializedRequest(rpc, "engine_getPayloadV1", parameters); + result.Should().Be("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-38001,\"message\":\"unknown payload\"},\"id\":67}"); + } + + [Test] + public async Task consecutive_blockImprovements_should_be_disposed() + { + MergeConfig mergeConfig = new() { Enabled = true, SecondsPerSlot = 1, TerminalTotalDifficulty = "0" }; + using MergeTestBlockchain chain = await CreateBlockChain(mergeConfig); + StoringBlockImprovementContextFactory improvementContextFactory = new(new MockBlockImprovementContextFactory()); + TimeSpan delay = TimeSpan.FromMilliseconds(10); + TimeSpan timePerSlot = 10 * delay; + chain.PayloadPreparationService = new PayloadPreparationService( + chain.PostMergeBlockProducer!, + improvementContextFactory, + TimerFactory.Default, + chain.LogManager, + timePerSlot, + improvementDelay: delay, + minTimeForProduction: delay); + + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + UInt256 timestamp = Timestamper.UnixTime.Seconds; + Keccak random = Keccak.Zero; + Address feeRecipient = Address.Zero; + + string payloadId = rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(startingHead, Keccak.Zero, startingHead), + new PayloadAttributes { Timestamp = timestamp, SuggestedFeeRecipient = feeRecipient, PrevRandao = random }).Result.Data.PayloadId!; + + await Task.Delay(timePerSlot / 2); + + improvementContextFactory.CreatedContexts.Count.Should().BeInRange(3, 5); + improvementContextFactory.CreatedContexts.Take(improvementContextFactory.CreatedContexts.Count - 1).Should().OnlyContain(i => i.Disposed); + + await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId)); + + improvementContextFactory.CreatedContexts.Should().OnlyContain(i => i.Disposed); + } + + [Test] + public async Task getPayloadV1_picks_transactions_from_pool_constantly_improving_blocks() + { + using SemaphoreSlim blockImprovementLock = new(0); + using MergeTestBlockchain chain = await CreateBlockChain(); + TimeSpan delay = TimeSpan.FromMilliseconds(10); + TimeSpan timePerSlot = 10 * delay; + StoringBlockImprovementContextFactory improvementContextFactory = new(new BlockImprovementContextFactory(chain.BlockProductionTrigger, TimeSpan.FromSeconds(chain.MergeConfig.SecondsPerSlot))); + chain.PayloadPreparationService = new PayloadPreparationService( + chain.PostMergeBlockProducer!, + improvementContextFactory, + TimerFactory.Default, + chain.LogManager, + timePerSlot, + improvementDelay: delay, + minTimeForProduction: delay); + + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + chain.AddTransactions(BuildTransactions(chain, startingHead, TestItem.PrivateKeyB, TestItem.AddressF, 3, 10, out _, out _)); + chain.PayloadPreparationService!.BlockImproved += (_, _) => { blockImprovementLock.Release(1); }; + string? payloadId = rpc.engine_forkchoiceUpdatedV1( + new ForkchoiceStateV1(startingHead, Keccak.Zero, startingHead), + new PayloadAttributes { Timestamp = 100, PrevRandao = TestItem.KeccakA, SuggestedFeeRecipient = Address.Zero }) + .Result.Data.PayloadId!; + + await blockImprovementLock.WaitAsync(100); + chain.AddTransactions(BuildTransactions(chain, startingHead, TestItem.PrivateKeyC, TestItem.AddressA, 3, 10, out _, out _)); + + await blockImprovementLock.WaitAsync(100); + chain.AddTransactions(BuildTransactions(chain, startingHead, TestItem.PrivateKeyA, TestItem.AddressC, 5, 10, out _, out _)); + + await blockImprovementLock.WaitAsync(100); + + ExecutionPayloadV1 getPayloadResult = (await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId))).Data!; + + improvementContextFactory.CreatedContexts.Select(c => c.CurrentBestBlock?.Transactions.Length).Should().Equal(3, 6, 11); + getPayloadResult.GetTransactions().Should().HaveCount(11); + } + + [Test] + public async Task getPayloadV1_doesnt_wait_for_improvement_when_block_is_not_empty() + { + using SemaphoreSlim blockImprovementLock = new(0); + using MergeTestBlockchain chain = await CreateBlockChain(); + TimeSpan delay = TimeSpan.FromMilliseconds(10); + TimeSpan timePerSlot = 10 * delay; + StoringBlockImprovementContextFactory improvementContextFactory = new(new DelayBlockImprovementContextFactory(chain.BlockProductionTrigger, TimeSpan.FromSeconds(chain.MergeConfig.SecondsPerSlot), 3 * delay)); + chain.PayloadPreparationService = new PayloadPreparationService( + chain.PostMergeBlockProducer!, + improvementContextFactory, + TimerFactory.Default, + chain.LogManager, + timePerSlot, + improvementDelay: delay, + minTimeForProduction: delay); + + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + chain.AddTransactions(BuildTransactions(chain, startingHead, TestItem.PrivateKeyB, TestItem.AddressF, 3, 10, out _, out _)); + chain.PayloadPreparationService!.BlockImproved += (_, _) => { blockImprovementLock.Release(1); }; + string? payloadId = rpc.engine_forkchoiceUpdatedV1( + new ForkchoiceStateV1(startingHead, Keccak.Zero, startingHead), + new PayloadAttributes { Timestamp = 100, PrevRandao = TestItem.KeccakA, SuggestedFeeRecipient = Address.Zero }) + .Result.Data.PayloadId!; + + await blockImprovementLock.WaitAsync(100); + chain.AddTransactions(BuildTransactions(chain, startingHead, TestItem.PrivateKeyC, TestItem.AddressA, 3, 10, out _, out _)); + + using SemaphoreSlim blockImprovementStartsLock = new(0); + IBlockImprovementContext? cancelledContext = null; + improvementContextFactory.ImprovementStarted += (_, e) => + { + blockImprovementStartsLock.Release(1); + cancelledContext = e.BlockImprovementContext; + }; + + await blockImprovementStartsLock.WaitAsync(100); // started improving block + improvementContextFactory.CreatedContexts.Should().HaveCount(2); + + ExecutionPayloadV1 getPayloadResult = (await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId))).Data!; + + getPayloadResult.GetTransactions().Should().HaveCount(3); + cancelledContext?.Disposed.Should().BeTrue(); + } +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index 05665ff3cef..f1e0ca103f6 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -19,7 +19,6 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -42,15 +41,11 @@ using Nethermind.Merge.Plugin.BlockProduction; using Nethermind.Merge.Plugin.Data; using Nethermind.Merge.Plugin.Data.V1; -using Nethermind.Merge.Plugin.Handlers; -using Nethermind.Merge.Plugin.Handlers.V1; -using Nethermind.Merge.Plugin.Synchronization; using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.State; using Nethermind.Trie; using Newtonsoft.Json; -using NLog; using NSubstitute; using NUnit.Framework; @@ -58,29 +53,6 @@ namespace Nethermind.Merge.Plugin.Test { public partial class EngineModuleTests { - [Test] - public async Task getPayload_correctlyEncodeTransactions() - { - byte[] payload = new byte[0]; - IPayloadPreparationService payloadPreparationService = Substitute.For(); - Block block = Build.A.Block.WithTransactions( - new[] - { - Build.A.Transaction.WithTo(TestItem.AddressD) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject, - Build.A.Transaction.WithTo(TestItem.AddressD).WithType(TxType.EIP1559).WithMaxFeePerGas(20) - .SignedAndResolved(TestItem.PrivateKeyA).TestObject - }).TestObject; - payloadPreparationService.GetPayload(Arg.Any()).Returns(block); - using MergeTestBlockchain chain = await CreateBlockChain(null, payloadPreparationService); - - IEngineRpcModule rpc = CreateEngineModule(chain); - - string result = RpcTest.TestSerializedRequest(rpc, "engine_getPayloadV1", payload.ToHexString(true)); - Assert.AreEqual(result, - "{\"jsonrpc\":\"2.0\",\"result\":{\"parentHash\":\"0xff483e972a04a9a62bb4b7d04ae403c615604e4090521ecc5bb7af67f71be09c\",\"feeRecipient\":\"0x0000000000000000000000000000000000000000\",\"stateRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"receiptsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"prevRandao\":\"0x2ba5557a4c62a513c7e56d1bf13373e0da6bec016755483e91589fe1c6d212e2\",\"blockNumber\":\"0x0\",\"gasLimit\":\"0x3d0900\",\"gasUsed\":\"0x0\",\"timestamp\":\"0xf4240\",\"extraData\":\"0x010203\",\"baseFeePerGas\":\"0x0\",\"blockHash\":\"0x5fd61518405272d77fd6cdc8a824a109d75343e32024ee4f6769408454b1823d\",\"transactions\":[\"0xf85f800182520894475674cb523a0a2736b7f7534390288fce16982c018025a0634db2f18f24d740be29e03dd217eea5757ed7422680429bdd458c582721b6c2a02f0fa83931c9a99d3448a46b922261447d6a41d8a58992b5596089d15d521102\",\"0x02f8620180011482520894475674cb523a0a2736b7f7534390288fce16982c0180c001a0033e85439a128c42f2ba47ca278f1375ef211e61750018ff21bcd9750d1893f2a04ee981fe5261f8853f95c865232ffdab009abcc7858ca051fb624c49744bf18d\"]},\"id\":67}"); - } - [Test] public virtual async Task processing_block_should_serialize_valid_responses() { @@ -172,19 +144,6 @@ public async Task can_parse_forkchoiceUpdated_with_implicit_null_payloadAttribut result.Should().Be("{\"jsonrpc\":\"2.0\",\"result\":{\"payloadStatus\":{\"status\":\"SYNCING\",\"latestValidHash\":null,\"validationError\":null},\"payloadId\":null},\"id\":67}"); } - [Test] - public async Task getPayload_should_serialize_unknown_payload_response_properly() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - byte[] payloadId = Bytes.FromHexString("0x1111111111111111"); - ; - - string parameters = payloadId.ToHexString(true); - string result = RpcTest.TestSerializedRequest(rpc, "engine_getPayloadV1", parameters); - result.Should().Be("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-38001,\"message\":\"unknown payload\"},\"id\":67}"); - } - [Test] public async Task engine_forkchoiceUpdatedV1_with_payload_attributes_should_create_block_on_top_of_genesis_and_not_change_head() { @@ -219,83 +178,6 @@ public async Task engine_forkchoiceUpdatedV1_with_payload_attributes_should_crea protected virtual Keccak ExpectedBlockHash => new("0x3ee80ba456bac700bfaf5b2827270406134e2392eb03ec50f6c23de28dd08811"); - [Test] - public async Task getPayloadV1_should_return_error_if_there_was_no_corresponding_prepare_call() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - UInt256 timestamp = Timestamper.UnixTime.Seconds; - Keccak random = Keccak.Zero; - Address feeRecipient = Address.Zero; - string _ = rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(startingHead, Keccak.Zero, startingHead), - new PayloadAttributes { Timestamp = timestamp, SuggestedFeeRecipient = feeRecipient, PrevRandao = random }).Result.Data - .PayloadId!; - - byte[] requestedPayloadId = Bytes.FromHexString("0x45bd36a8143d860d"); - ResultWrapper response = await rpc.engine_getPayloadV1(requestedPayloadId); - - response.ErrorCode.Should().Be(MergeErrorCodes.UnknownPayload); - } - - [Test] - public async Task getPayloadV1_should_allow_asking_multiple_times_by_same_payload_id() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - - Keccak startingHead = chain.BlockTree.HeadHash; - ForkchoiceStateV1 forkchoiceState = new(startingHead, Keccak.Zero, startingHead); - PayloadAttributes payload = new() - { - Timestamp = Timestamper.UnixTime.Seconds, - SuggestedFeeRecipient = Address.Zero, - PrevRandao = Keccak.Zero - }; - Task> forkchoiceResponse = rpc.engine_forkchoiceUpdatedV1(forkchoiceState, payload); - byte[] payloadId = Bytes.FromHexString(forkchoiceResponse.Result.Data.PayloadId!); - ResultWrapper responseFirst = await rpc.engine_getPayloadV1(payloadId); - responseFirst.Should().NotBeNull(); - responseFirst.Result.ResultType.Should().Be(ResultType.Success); - ResultWrapper responseSecond = await rpc.engine_getPayloadV1(payloadId); - responseSecond.Should().NotBeNull(); - responseSecond.Result.ResultType.Should().Be(ResultType.Success); - - responseSecond.Data!.BlockHash!.Should().Be(responseFirst.Data!.BlockHash!); - } - - [Test] - public async Task getPayloadV1_should_return_error_if_called_after_cleanup_timer() - { - MergeConfig mergeConfig = new() { Enabled = true, SecondsPerSlot = 1, TerminalTotalDifficulty = "0" }; - using MergeTestBlockchain chain = await CreateBlockChain(mergeConfig); - BlockImprovementContextFactory improvementContextFactory = new(chain.BlockProductionTrigger, TimeSpan.FromSeconds(1)); - TimeSpan timePerSlot = TimeSpan.FromMilliseconds(10); - chain.PayloadPreparationService = new PayloadPreparationService( - chain.PostMergeBlockProducer!, - improvementContextFactory, - TimerFactory.Default, - chain.LogManager, - timePerSlot); - - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - UInt256 timestamp = Timestamper.UnixTime.Seconds; - Keccak random = Keccak.Zero; - Address feeRecipient = Address.Zero; - - string payloadId = rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(startingHead, Keccak.Zero, startingHead), - new PayloadAttributes { Timestamp = timestamp, SuggestedFeeRecipient = feeRecipient, PrevRandao = random }).Result.Data - .PayloadId!; - - - await Task.Delay(PayloadPreparationService.SlotsPerOldPayloadCleanup * 2 * timePerSlot + timePerSlot); - - Assert.That( - () => rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId)).Result.ErrorCode, - Is.EqualTo(MergeErrorCodes.UnknownPayload).After(10000, 500)); - } - [Test] public async Task getPayloadBodiesV1_should_return_payload_bodies_in_order_of_request_block_hashes_and_skip_unknown_hashes() { @@ -602,7 +484,7 @@ public virtual async Task executePayloadV1_accepts_already_known_block(bool thro .TestObject; block.Header.IsPostMerge = true; block.Header.Hash = block.CalculateHash(); - SemaphoreSlim bestBlockProcessed = new(0); + using SemaphoreSlim bestBlockProcessed = new(0); chain.BlockTree.NewHeadBlock += (s, e) => { if (e.Block.Hash == block!.Hash) @@ -882,7 +764,6 @@ public async Task forkchoiceUpdatedV1_should_change_head_when_all_parameters_are using MergeTestBlockchain chain = await CreateBlockChain(); IEngineRpcModule rpc = CreateEngineModule(chain); ExecutionPayloadV1 executionPayload = await SendNewBlockV1(rpc, chain); - Keccak newHeadHash = executionPayload.BlockHash; ForkchoiceStateV1 forkchoiceStateV1 = new(newHeadHash, newHeadHash, newHeadHash); ResultWrapper forkchoiceUpdatedResult = @@ -961,7 +842,7 @@ public async Task executePayloadV1_on_top_of_terminal_block() .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")).TestObject; newBlock.CalculateHash(); - SemaphoreSlim bestBlockProcessed = new(0); + using SemaphoreSlim bestBlockProcessed = new(0); chain.BlockTree.NewHeadBlock += (s, e) => { if (e.Block.Hash == newBlock!.Hash) @@ -999,7 +880,7 @@ public async Task executePayloadV1_on_top_of_not_processed_invalid_terminal_bloc .WithTotalDifficulty(1900000L) .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bfba4ccf1702fabf02d8ad7a20b454edb6fd2f")).TestObject; //incorrect state root - SemaphoreSlim bestBlockProcessed = new(0); + using SemaphoreSlim bestBlockProcessed = new(0); chain.BlockTree.NewHeadBlock += (s, e) => { if (e.Block.Hash == newBlock!.Hash) @@ -1045,7 +926,7 @@ public async Task executePayloadV1_on_top_of_not_processed_terminal_block() .WithTotalDifficulty(1900000L) .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")).TestObject; - SemaphoreSlim bestBlockProcessed = new(0); + using SemaphoreSlim bestBlockProcessed = new(0); chain.BlockTree.NewHeadBlock += (s, e) => { if (e.Block.Hash == newBlock!.Hash) @@ -1261,110 +1142,6 @@ public async Task executePayloadV1_transactions_produce_receipts() } } - [Test] - public async Task getPayloadV1_picks_transactions_from_pool_v1() - { - SemaphoreSlim blockImprovementLock = new(0); - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - uint count = 3; - int value = 10; - Address recipient = TestItem.AddressF; - PrivateKey sender = TestItem.PrivateKeyB; - Transaction[] transactions = BuildTransactions(chain, startingHead, sender, recipient, count, value, out _, out _); - chain.AddTransactions(transactions); - chain.PayloadPreparationService!.BlockImproved += (_, _) => { blockImprovementLock.Release(1); }; - string? payloadId = rpc.engine_forkchoiceUpdatedV1( - new ForkchoiceStateV1(startingHead, Keccak.Zero, startingHead), - new PayloadAttributes() { Timestamp = 100, PrevRandao = TestItem.KeccakA, SuggestedFeeRecipient = Address.Zero }) - .Result.Data.PayloadId!; - - await blockImprovementLock.WaitAsync(10000); - ExecutionPayloadV1 getPayloadResult = (await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId))).Data!; - - getPayloadResult.StateRoot.Should().NotBe(chain.BlockTree.Genesis!.StateRoot!); - - Transaction[] transactionsInBlock = getPayloadResult.GetTransactions(); - transactionsInBlock.Should().BeEquivalentTo(transactions, o => o - .Excluding(t => t.ChainId) - .Excluding(t => t.SenderAddress) - .Excluding(t => t.Timestamp) - .Excluding(t => t.PoolIndex) - .Excluding(t => t.GasBottleneck)); - - ResultWrapper executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); - executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); - - UInt256 totalValue = ((int)(count * value)).GWei(); - chain.StateReader.GetBalance(getPayloadResult.StateRoot, recipient).Should().Be(totalValue); - } - - [TestCase(PayloadPreparationService.GetPayloadWaitForFullBlockMillisecondsDelay / 10, ExpectedResult = 3)] - [TestCase(PayloadPreparationService.GetPayloadWaitForFullBlockMillisecondsDelay * 2, ExpectedResult = 0)] - public async Task getPayloadV1_waits_for_block_production(int delay) - { - using MergeTestBlockchain chain = await CreateBlockChain(); - - DelayBlockImprovementContextFactory improvementContextFactory = new(chain.BlockProductionTrigger, TimeSpan.FromSeconds(10), delay); - chain.PayloadPreparationService = new PayloadPreparationService( - chain.PostMergeBlockProducer!, - improvementContextFactory, - TimerFactory.Default, - chain.LogManager, - TimeSpan.FromSeconds(10)); - - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - uint count = 3; - int value = 10; - Address recipient = TestItem.AddressF; - PrivateKey sender = TestItem.PrivateKeyB; - Transaction[] transactions = BuildTransactions(chain, startingHead, sender, recipient, count, value, out _, out _); - chain.AddTransactions(transactions); - string payloadId = rpc.engine_forkchoiceUpdatedV1( - new ForkchoiceStateV1(startingHead, Keccak.Zero, startingHead), - new PayloadAttributes { Timestamp = 100, PrevRandao = TestItem.KeccakA, SuggestedFeeRecipient = Address.Zero }) - .Result.Data.PayloadId!; - - ExecutionPayloadV1 getPayloadResult = (await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId))).Data!; - - return getPayloadResult.Transactions.Length; - } - - [Test] - public async Task getPayloadV1_return_correct_block_values_for_empty_block() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - Keccak? random = TestItem.KeccakF; - UInt256 timestamp = chain.BlockTree.Head!.Timestamp + 5; - Address? suggestedFeeRecipient = TestItem.AddressC; - PayloadAttributes? payloadAttributes = new() - { - PrevRandao = random, - Timestamp = timestamp, - SuggestedFeeRecipient = suggestedFeeRecipient - }; - ExecutionPayloadV1 getPayloadResult = await BuildAndGetPayloadResult(chain, rpc, payloadAttributes); - getPayloadResult.ParentHash.Should().Be(startingHead); - - - ResultWrapper executePayloadResult = - await rpc.engine_newPayloadV1(getPayloadResult); - executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); - - BlockHeader? currentHeader = chain.BlockTree.BestSuggestedHeader!; - - Assert.AreEqual("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - currentHeader.UnclesHash!.ToString()); - Assert.AreEqual((UInt256)0, currentHeader.Difficulty); - Assert.AreEqual(0, currentHeader.Nonce); - Assert.AreEqual(random, currentHeader.MixHash); - } - - private async Task> ProduceBranchV1(IEngineRpcModule rpc, MergeTestBlockchain chain, int count, ExecutionPayloadV1 startingParentBlock, bool setHead, Keccak? random = null) @@ -1800,7 +1577,7 @@ private async Task BuildAndGetPayloadResult( Keccak safeBlockHash, UInt256 timestamp, Keccak random, Address feeRecipient, bool waitForBlockImprovement = true) { - SemaphoreSlim blockImprovementLock = new(0); + using SemaphoreSlim blockImprovementLock = new(0); if (waitForBlockImprovement) { chain.PayloadPreparationService!.BlockImproved += (s, e) => diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/BlockImprovementContext.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/BlockImprovementContext.cs index e31dd17fd65..c33dfea62fa 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/BlockImprovementContext.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/BlockImprovementContext.cs @@ -29,15 +29,16 @@ public class BlockImprovementContext : IBlockImprovementContext { private CancellationTokenSource? _cancellationTokenSource; - public BlockImprovementContext( - Block currentBestBlock, + public BlockImprovementContext(Block currentBestBlock, IManualBlockProductionTrigger blockProductionTrigger, TimeSpan timeout, BlockHeader parentHeader, - PayloadAttributes payloadAttributes) + PayloadAttributes payloadAttributes, + DateTimeOffset startDateTime) { _cancellationTokenSource = new CancellationTokenSource(timeout); CurrentBestBlock = currentBestBlock; + StartDateTime = startDateTime; ImprovementTask = blockProductionTrigger .BuildBlock(parentHeader, _cancellationTokenSource.Token, NullBlockTracer.Instance, payloadAttributes) .ContinueWith(SetCurrentBestBlock, _cancellationTokenSource.Token); @@ -60,8 +61,12 @@ public BlockImprovementContext( return task.Result; } + public bool Disposed { get; private set; } + public DateTimeOffset StartDateTime { get; } + public void Dispose() { + Disposed = true; CancellationTokenExtensions.CancelDisposeAndClear(ref _cancellationTokenSource); } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/BlockImprovementContextFactory.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/BlockImprovementContextFactory.cs index 58b04a92bfe..82056eec5a5 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/BlockImprovementContextFactory.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/BlockImprovementContextFactory.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. -// +// // The Nethermind library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // The Nethermind library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. -// +// // You should have received a copy of the GNU Lesser General Public License // along with the Nethermind. If not, see . -// +// using System; using Nethermind.Consensus.Producers; @@ -36,6 +36,7 @@ public BlockImprovementContextFactory(IManualBlockProductionTrigger blockProduct public IBlockImprovementContext StartBlockImprovementContext( Block currentBestBlock, BlockHeader parentHeader, - PayloadAttributes payloadAttributes) => - new BlockImprovementContext(currentBestBlock, _blockProductionTrigger, _timeout, parentHeader, payloadAttributes); + PayloadAttributes payloadAttributes, + DateTimeOffset startDateTime) => + new BlockImprovementContext(currentBestBlock, _blockProductionTrigger, _timeout, parentHeader, payloadAttributes, startDateTime); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostBlockImprovementContext.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostBlockImprovementContext.cs index ecf2d22c723..b3f786002a5 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostBlockImprovementContext.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostBlockImprovementContext.cs @@ -36,19 +36,20 @@ public class BoostBlockImprovementContext : IBlockImprovementContext private readonly IStateReader _stateReader; private CancellationTokenSource? _cancellationTokenSource; - public BoostBlockImprovementContext( - Block currentBestBlock, + public BoostBlockImprovementContext(Block currentBestBlock, IManualBlockProductionTrigger blockProductionTrigger, TimeSpan timeout, BlockHeader parentHeader, PayloadAttributes payloadAttributes, IBoostRelay boostRelay, - IStateReader stateReader) + IStateReader stateReader, + DateTimeOffset startDateTime) { _boostRelay = boostRelay; _stateReader = stateReader; _cancellationTokenSource = new CancellationTokenSource(timeout); CurrentBestBlock = currentBestBlock; + StartDateTime = startDateTime; ImprovementTask = StartImprovingBlock(blockProductionTrigger, parentHeader, payloadAttributes, _cancellationTokenSource.Token); } @@ -73,11 +74,13 @@ public BoostBlockImprovementContext( } public Task ImprovementTask { get; } - public Block? CurrentBestBlock { get; private set; } + public bool Disposed { get; private set; } + public DateTimeOffset StartDateTime { get; } public void Dispose() { + Disposed = true; CancellationTokenExtensions.CancelDisposeAndClear(ref _cancellationTokenSource); } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostBlockImprovementContextFactory.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostBlockImprovementContextFactory.cs index 96140c6fce1..a9706d403c7 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostBlockImprovementContextFactory.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostBlockImprovementContextFactory.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. -// +// // The Nethermind library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // The Nethermind library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. -// +// // You should have received a copy of the GNU Lesser General Public License // along with the Nethermind. If not, see . -// +// using System; using System.Net.Http; @@ -40,8 +40,9 @@ public BoostBlockImprovementContextFactory(IManualBlockProductionTrigger blockPr } public IBlockImprovementContext StartBlockImprovementContext( - Block currentBestBlock, + Block currentBestBlock, BlockHeader parentHeader, - PayloadAttributes payloadAttributes) => - new BoostBlockImprovementContext(currentBestBlock, _blockProductionTrigger, _timeout, parentHeader, payloadAttributes, _boostRelay, _stateReader); + PayloadAttributes payloadAttributes, + DateTimeOffset startDateTime) => + new BoostBlockImprovementContext(currentBestBlock, _blockProductionTrigger, _timeout, parentHeader, payloadAttributes, _boostRelay, _stateReader, startDateTime); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockImprovementContext.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockImprovementContext.cs index 7622d47fdee..d0671394101 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockImprovementContext.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockImprovementContext.cs @@ -1,19 +1,19 @@ // Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. -// +// // The Nethermind library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // The Nethermind library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. -// +// // You should have received a copy of the GNU Lesser General Public License // along with the Nethermind. If not, see . -// +// using System; using System.Threading.Tasks; @@ -25,4 +25,6 @@ public interface IBlockImprovementContext : IDisposable { Task ImprovementTask { get; } Block? CurrentBestBlock { get; } + bool Disposed { get; } + DateTimeOffset StartDateTime { get; } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockImprovementContextFactory.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockImprovementContextFactory.cs index 4e4429109ce..5ddb5dec1c1 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockImprovementContextFactory.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/IBlockImprovementContextFactory.cs @@ -1,20 +1,21 @@ -// Copyright (c) 2021 Demerzel Solutions Limited +// Copyright (c) 2021 Demerzel Solutions Limited // This file is part of the Nethermind library. -// +// // The Nethermind library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. -// +// // The Nethermind library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. -// +// // You should have received a copy of the GNU Lesser General Public License // along with the Nethermind. If not, see . -// +// +using System; using Nethermind.Consensus.Producers; using Nethermind.Core; @@ -25,5 +26,6 @@ public interface IBlockImprovementContextFactory IBlockImprovementContext StartBlockImprovementContext( Block currentBestBlock, BlockHeader parentHeader, - PayloadAttributes payloadAttributes); + PayloadAttributes payloadAttributes, + DateTimeOffset startDateTime); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PayloadPreparationService.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PayloadPreparationService.cs index 20ea21e1fc8..623dbb30bb3 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PayloadPreparationService.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PayloadPreparationService.cs @@ -18,16 +18,14 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Threading; +using System.Linq; using System.Threading.Tasks; -using Nethermind.Consensus; using Nethermind.Consensus.Producers; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Timers; using Nethermind.Logging; -using Nethermind.Merge.Plugin.Handlers; using Nethermind.Merge.Plugin.Handlers.V1; namespace Nethermind.Merge.Plugin.BlockProduction @@ -47,8 +45,22 @@ public class PayloadPreparationService : IPayloadPreparationService // by default we will cleanup the old payload once per six slot. There is no need to fire it more often public const int SlotsPerOldPayloadCleanup = 6; - public const int GetPayloadWaitForFullBlockMillisecondsDelay = 500; + public static readonly TimeSpan GetPayloadWaitForFullBlockMillisecondsDelay = TimeSpan.FromMilliseconds(500); + public static readonly TimeSpan DefaultImprovementDelay = TimeSpan.FromMilliseconds(3000); + public static readonly TimeSpan DefaultMinTimeForProduction = TimeSpan.FromMilliseconds(500); + + /// + /// Delay between block improvements + /// + private readonly TimeSpan _improvementDelay; + + /// + /// Minimal time to try to improve block + /// + private readonly TimeSpan _minTimeForProduction; + private readonly TimeSpan _cleanupOldPayloadDelay; + private readonly TimeSpan _timePerSlot; // first ExecutionPayloadV1 is empty (without txs), second one is the ideal one private readonly ConcurrentDictionary _payloadStorage = new(); @@ -59,12 +71,17 @@ public PayloadPreparationService( ITimerFactory timerFactory, ILogManager logManager, TimeSpan timePerSlot, - int slotsPerOldPayloadCleanup = SlotsPerOldPayloadCleanup) + int slotsPerOldPayloadCleanup = SlotsPerOldPayloadCleanup, + TimeSpan? improvementDelay = null, + TimeSpan? minTimeForProduction = null) { _blockProducer = blockProducer; _blockImprovementContextFactory = blockImprovementContextFactory; + _timePerSlot = timePerSlot; TimeSpan timeout = timePerSlot; - _cleanupOldPayloadDelay = 2 * timePerSlot; // 2 * slots time + _cleanupOldPayloadDelay = 3 * timePerSlot; // 3 * slots time + _improvementDelay = improvementDelay ?? DefaultImprovementDelay; + _minTimeForProduction = minTimeForProduction ?? DefaultMinTimeForProduction; ITimer timer = timerFactory.CreateTimer(slotsPerOldPayloadCleanup * timeout); timer.Elapsed += CleanupOldPayloads; timer.Start(); @@ -78,7 +95,7 @@ public string StartPreparingPayload(BlockHeader parentHeader, PayloadAttributes if (!_payloadStorage.ContainsKey(payloadId)) { Block emptyBlock = ProduceEmptyBlock(payloadId, parentHeader, payloadAttributes); - ImproveBlock(payloadId, parentHeader, payloadAttributes, emptyBlock); + ImproveBlock(payloadId, parentHeader, payloadAttributes, emptyBlock, DateTimeOffset.UtcNow); } else if (_logger.IsInfo) _logger.Info($"Payload with the same parameters has already started. PayloadId: {payloadId}"); @@ -93,27 +110,66 @@ private Block ProduceEmptyBlock(string payloadId, BlockHeader parentHeader, Payl return emptyBlock; } - private void ImproveBlock(string payloadId, BlockHeader parentHeader, PayloadAttributes payloadAttributes, Block emptyBlock) + private void ImproveBlock(string payloadId, BlockHeader parentHeader, PayloadAttributes payloadAttributes, Block currentBestBlock, DateTimeOffset startDateTime) => + _payloadStorage.AddOrUpdate(payloadId, + id => CreateBlockImprovementContext(id, parentHeader, payloadAttributes, currentBestBlock, startDateTime), + (id, currentContext) => + { + // if there is payload improvement and its not yet finished leave it be + if (!currentContext.ImprovementTask.IsCompleted) + { + if (_logger.IsTrace) _logger.Trace($"Block for payload {payloadId} with parent {parentHeader.ToString(BlockHeader.Format.FullHashAndNumber)} won't be improved, previous improvement hasn't finished"); + return currentContext; + } + + IBlockImprovementContext newContext = CreateBlockImprovementContext(id, parentHeader, payloadAttributes, currentBestBlock, startDateTime); + currentContext.Dispose(); + return newContext; + }); + + + private IBlockImprovementContext CreateBlockImprovementContext(string payloadId, BlockHeader parentHeader, PayloadAttributes payloadAttributes, Block currentBestBlock, DateTimeOffset startDateTime) { - if (_logger.IsTrace) _logger.Trace($"Start improving block from payload {payloadId} with parent {parentHeader}"); - IBlockImprovementContext blockImprovementContext = _blockImprovementContextFactory.StartBlockImprovementContext(emptyBlock, parentHeader, payloadAttributes); + if (_logger.IsTrace) _logger.Trace($"Start improving block from payload {payloadId} with parent {parentHeader.ToString(BlockHeader.Format.FullHashAndNumber)}"); + IBlockImprovementContext blockImprovementContext = _blockImprovementContextFactory.StartBlockImprovementContext(currentBestBlock, parentHeader, payloadAttributes, startDateTime); blockImprovementContext.ImprovementTask.ContinueWith(LogProductionResult); - if (!_payloadStorage.TryAdd(payloadId, blockImprovementContext)) + blockImprovementContext.ImprovementTask.ContinueWith(async _ => { - blockImprovementContext.Dispose(); - } + // if after delay we still have time to try producing the block in this slot + DateTimeOffset whenWeCouldFinishNextProduction = DateTimeOffset.UtcNow + _improvementDelay + _minTimeForProduction; + DateTimeOffset slotFinished = startDateTime + _timePerSlot; + if (whenWeCouldFinishNextProduction < slotFinished) + { + if (_logger.IsTrace) _logger.Trace($"Block for payload {payloadId} with parent {parentHeader.ToString(BlockHeader.Format.FullHashAndNumber)} will be improved in {_improvementDelay.TotalMilliseconds}ms"); + await Task.Delay(_improvementDelay); + if (!blockImprovementContext.Disposed) // if GetPayload wasn't called for this item or it wasn't cleared + { + Block newBestBlock = blockImprovementContext.CurrentBestBlock ?? currentBestBlock; + ImproveBlock(payloadId, parentHeader, payloadAttributes, newBestBlock, startDateTime); + } + else + { + if (_logger.IsTrace) _logger.Trace($"Block for payload {payloadId} with parent {parentHeader.ToString(BlockHeader.Format.FullHashAndNumber)} won't be improved, it was retrieved"); + } + } + else + { + if (_logger.IsTrace) _logger.Trace($"Block for payload {payloadId} with parent {parentHeader.ToString(BlockHeader.Format.FullHashAndNumber)} won't be improved, no more time in slot"); + } + }); + + return blockImprovementContext; } private void CleanupOldPayloads(object? sender, EventArgs e) { - if (_logger.IsTrace) _logger.Trace($"Started old payloads cleanup"); - UnixTime utcNow = new(DateTimeOffset.Now); - + if (_logger.IsTrace) _logger.Trace("Started old payloads cleanup"); foreach (KeyValuePair payload in _payloadStorage) { - if (payload.Value.CurrentBestBlock is not null && payload.Value.CurrentBestBlock.Timestamp + (uint)_cleanupOldPayloadDelay.Seconds <= utcNow.Seconds) + DateTimeOffset now = DateTimeOffset.UtcNow; + if (payload.Value.StartDateTime + _cleanupOldPayloadDelay <= now) { - if (_logger.IsInfo) _logger.Info($"A new payload to remove: {payload.Key}, Current time {utcNow.Seconds}, Payload timestamp: {payload.Value.CurrentBestBlock.Timestamp}"); + if (_logger.IsDebug) _logger.Info($"A new payload to remove: {payload.Key}, Current time {now:t}, Payload timestamp: {payload.Value.CurrentBestBlock?.Timestamp}"); _payloadsToRemove.Add(payload.Key); } } @@ -123,7 +179,7 @@ private void CleanupOldPayloads(object? sender, EventArgs e) if (_payloadStorage.TryRemove(payloadToRemove, out IBlockImprovementContext? context)) { context.Dispose(); - if (_logger.IsInfo) _logger.Info($"Cleaned up payload with id={payloadToRemove} as it was not requested"); + if (_logger.IsDebug) _logger.Info($"Cleaned up payload with id={payloadToRemove} as it was not requested"); } } @@ -138,16 +194,16 @@ private void CleanupOldPayloads(object? sender, EventArgs e) if (t.Result != null) { BlockImproved?.Invoke(this, new BlockEventArgs(t.Result)); - if (_logger.IsInfo) _logger.Info($"Produced post-merge block {t.Result.ToString(Block.Format.HashNumberDiffAndTx)}"); + if (_logger.IsInfo) _logger.Info($"Improved post-merge block {t.Result.ToString(Block.Format.HashNumberDiffAndTx)}"); } else { - if (_logger.IsInfo) _logger.Info($"Failed to produce post-merge block"); + if (_logger.IsInfo) _logger.Info("Failed to improve post-merge block"); } } else if (t.IsFaulted) { - if (_logger.IsError) _logger.Error("Post merge block producing failed", t.Exception); + if (_logger.IsError) _logger.Error("Post merge block improvement failed", t.Exception); } else if (t.IsCanceled) { @@ -163,7 +219,8 @@ private void CleanupOldPayloads(object? sender, EventArgs e) { using (blockContext) { - if (!blockContext.ImprovementTask.IsCompleted) + bool currentBestBlockIsEmpty = blockContext.CurrentBestBlock?.Transactions.Any() != true; + if (currentBestBlockIsEmpty && !blockContext.ImprovementTask.IsCompleted) { await Task.WhenAny(blockContext.ImprovementTask, Task.Delay(GetPayloadWaitForFullBlockMillisecondsDelay)); } diff --git a/src/Nethermind/Nethermind.sln b/src/Nethermind/Nethermind.sln index 67fef231ca6..ef4b9fb28f6 100644 --- a/src/Nethermind/Nethermind.sln +++ b/src/Nethermind/Nethermind.sln @@ -230,6 +230,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nethermind.Merge.AuRa.Test" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nethermind.Merge.AuRa", "Nethermind.Merge.AuRa\Nethermind.Merge.AuRa.csproj", "{F166365D-6E31-401B-9753-59EADB800E2A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MathGmp.Native", "..\Math.Gmp.Native\MathGmp.Native\MathGmp.Native.csproj", "{16E039D0-8944-4EFB-8624-FC9EB9FF75DB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -600,6 +602,10 @@ Global {F166365D-6E31-401B-9753-59EADB800E2A}.Debug|Any CPU.Build.0 = Debug|Any CPU {F166365D-6E31-401B-9753-59EADB800E2A}.Release|Any CPU.ActiveCfg = Release|Any CPU {F166365D-6E31-401B-9753-59EADB800E2A}.Release|Any CPU.Build.0 = Release|Any CPU + {16E039D0-8944-4EFB-8624-FC9EB9FF75DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {16E039D0-8944-4EFB-8624-FC9EB9FF75DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {16E039D0-8944-4EFB-8624-FC9EB9FF75DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {16E039D0-8944-4EFB-8624-FC9EB9FF75DB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE