From 5555fcb56abeca568240d5d56ea605894f0c735e Mon Sep 17 00:00:00 2001 From: Alexey Date: Wed, 26 Apr 2023 20:46:36 +0300 Subject: [PATCH] Add JSON-RPC endpoints for EIP-4844 (#5558) Add JSON-RPC endpoints for EIP-4844 --- src/Nethermind/Directory.Packages.props | 2 +- .../Validators/BlockValidator.cs | 11 +- .../Validators/TxValidator.cs | 2 +- .../Builders/TransactionBuilder.cs | 32 ++ .../Nethermind.Core/Extensions/Bytes.cs | 43 +++ .../KzgPolynomialCommitments.cs | 14 +- .../PointEvaluationPrecompileTests.cs | 2 +- .../Precompiles/PointEvaluationPrecompile.cs | 2 +- .../Steps/InitializePrecompiles.cs | 2 +- .../Nethermind.JsonRpc/IJsonRpcConfig.cs | 2 +- .../Nethermind.JsonRpc/JsonRpcConfig.cs | 1 + .../EngineModuleTests.HelperFunctions.cs | 15 +- .../EngineModuleTests.Setup.cs | 27 +- .../EngineModuleTests.V2.cs | 273 ++++++++++-------- .../EngineModuleTests.V3.cs | 115 ++++++++ .../Data/BlobsBundleV1.cs | 53 ++++ .../Data/ExecutionPayload.cs | 8 +- .../Data/GetPayloadV2Result.cs | 1 + .../Data/GetPayloadV3Result.cs | 20 ++ .../EngineRpcModule.Cancun.cs | 24 ++ .../EngineRpcModule.cs | 2 + .../Handlers/EngineRpcCapabilitiesProvider.cs | 5 + .../Handlers/GetPayloadHandlerBase.cs | 48 +++ .../Handlers/GetPayloadV1Handler.cs | 69 ++--- .../Handlers/GetPayloadV2Handler.cs | 49 +--- .../Handlers/GetPayloadV3Handler.cs | 23 ++ .../Handlers/NewPayloadHandler.cs | 4 +- .../IEngineRpcModule.Cancun.cs | 25 ++ .../Nethermind.Merge.Plugin/MergePlugin.cs | 1 + .../Nethermind.Merge.Plugin/Metrics.cs | 6 - .../EthereumJsonSerializer.cs | 6 +- .../MemoryByteConverter.cs | 34 +++ .../Nethermind.Serialization.Rlp/TxDecoder.cs | 8 + 33 files changed, 695 insertions(+), 234 deletions(-) create mode 100644 src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsBundleV1.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV3Result.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Cancun.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadHandlerBase.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV3Handler.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Cancun.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Json/MemoryByteConverter.cs diff --git a/src/Nethermind/Directory.Packages.props b/src/Nethermind/Directory.Packages.props index fa0a002d21e..ad377cafbb2 100644 --- a/src/Nethermind/Directory.Packages.props +++ b/src/Nethermind/Directory.Packages.props @@ -9,7 +9,7 @@ - + diff --git a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs index 869815883f6..82858e12c3f 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs @@ -195,9 +195,16 @@ private bool ValidateWithdrawals(Block block, IReleaseSpec spec, out string? err private bool ValidateBlobs(Block block, IReleaseSpec spec, out string? error) { - if (spec.IsEip4844Enabled ^ block.ExcessDataGas is not null) + if (spec.IsEip4844Enabled && block.ExcessDataGas is null) { - error = $"ExcessDataGas cannot be null in block {block.Hash} when EIP-4844 activated."; + error = "ExcessDataGas field is not set."; + if (_logger.IsWarn) _logger.Warn(error); + return false; + } + + if (!spec.IsEip4844Enabled && block.ExcessDataGas is not null) + { + error = "ExcessDataGas field should not have value."; if (_logger.IsWarn) _logger.Warn(error); return false; } diff --git a/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs index 4a714122cc5..38421c25b13 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs @@ -146,7 +146,7 @@ transaction.BlobKzgs is not null || i < transaction.BlobVersionedHashes!.Length; i++, n += Ckzg.Ckzg.BytesPerCommitment) { - if (!KzgPolynomialCommitments.TryComputeCommitmentV1( + if (!KzgPolynomialCommitments.TryComputeCommitmentHashV1( commitements[n..(n + Ckzg.Ckzg.BytesPerCommitment)], hash) || !hash.SequenceEqual(transaction.BlobVersionedHashes![i])) { diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs index 3b5347f96d3..bf139928b06 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs @@ -89,6 +89,9 @@ public TransactionBuilder WithMaxFeePerGas(UInt256 feeCap) return this; } + public TransactionBuilder WithMaxFeePerGasIfSupports1559(UInt256 feeCap) => + TestObjectInternal.Supports1559 ? WithMaxFeePerGas(feeCap) : this; + public TransactionBuilder WithMaxPriorityFeePerGas(UInt256 maxPriorityFeePerGas) { TestObjectInternal.GasPrice = maxPriorityFeePerGas; @@ -166,6 +169,35 @@ public TransactionBuilder WithBlobs(byte[] blobs) return this; } + + public TransactionBuilder WithShardBlobTxTypeAndFields(int blobCount) + { + if (blobCount is 0) + { + return this; + } + + TestObjectInternal.Type = TxType.Blob; + TestObjectInternal.MaxFeePerDataGas ??= 1; + TestObjectInternal.Blobs = new byte[Ckzg.Ckzg.BytesPerBlob * blobCount]; + TestObjectInternal.BlobKzgs = new byte[Ckzg.Ckzg.BytesPerCommitment * blobCount]; + TestObjectInternal.BlobProofs = new byte[Ckzg.Ckzg.BytesPerProof * blobCount]; + TestObjectInternal.BlobVersionedHashes = new byte[blobCount][]; + for (int i = 0; i < blobCount; i++) + { + TestObjectInternal.BlobVersionedHashes[i] = new byte[32]; + TestObjectInternal.Blobs[Ckzg.Ckzg.BytesPerBlob * i] = 1; + KzgPolynomialCommitments.KzgifyBlob( + TestObjectInternal.Blobs.AsSpan(Ckzg.Ckzg.BytesPerBlob * i, Ckzg.Ckzg.BytesPerBlob * (i + 1)), + TestObjectInternal.BlobKzgs.AsSpan(Ckzg.Ckzg.BytesPerCommitment * i, Ckzg.Ckzg.BytesPerCommitment * (i + 1)), + TestObjectInternal.BlobProofs.AsSpan(Ckzg.Ckzg.BytesPerProof * i, Ckzg.Ckzg.BytesPerProof * (i + 1)), + TestObjectInternal.BlobVersionedHashes[i].AsSpan()); + } + + + return this; + } + public TransactionBuilder WithBlobKzgs(byte[] blobKzgs) { TestObjectInternal.BlobKzgs = blobKzgs; diff --git a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs index 20ccabbd2aa..718024991ec 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs @@ -518,6 +518,18 @@ public StateSmall(byte[] bytes, bool withZeroX) public readonly bool WithZeroX; } + private readonly struct StateSmallMemory + { + public StateSmallMemory(Memory bytes, bool withZeroX) + { + Bytes = bytes; + WithZeroX = withZeroX; + } + + public readonly Memory Bytes; + public readonly bool WithZeroX; + } + private struct StateOld { public StateOld(byte[] bytes, int leadingZeros, bool withZeroX, bool withEip55Checksum) @@ -579,6 +591,37 @@ public static string ByteArrayToHexViaLookup32Safe(byte[] bytes, bool withZeroX) }); } + [DebuggerStepThrough] + public static string ByteArrayToHexViaLookup32Safe(Memory bytes, bool withZeroX) + { + if (bytes.Length == 0) + { + return withZeroX ? "0x" : string.Empty; + } + + int length = bytes.Length * 2 + (withZeroX ? 2 : 0); + StateSmallMemory stateToPass = new(bytes, withZeroX); + + return string.Create(length, stateToPass, static (chars, state) => + { + ref char charsRef = ref MemoryMarshal.GetReference(chars); + + Memory bytes = state.Bytes; + if (bytes.Length == 0) + { + if (state.WithZeroX) + { + chars[1] = 'x'; + chars[0] = '0'; + } + + return; + } + + OutputBytesToCharHex(ref bytes.Span[0], state.Bytes.Length, ref charsRef, state.WithZeroX, leadingZeros: 0); + }); + } + [DebuggerStepThrough] private static string ByteArrayToHexViaLookup32(byte[] bytes, bool withZeroX, bool skipLeadingZeros, bool withEip55Checksum) diff --git a/src/Nethermind/Nethermind.Crypto/KzgPolynomialCommitments.cs b/src/Nethermind/Nethermind.Crypto/KzgPolynomialCommitments.cs index a08db72e4aa..f1814f276ad 100644 --- a/src/Nethermind/Nethermind.Crypto/KzgPolynomialCommitments.cs +++ b/src/Nethermind/Nethermind.Crypto/KzgPolynomialCommitments.cs @@ -27,7 +27,7 @@ public static class KzgPolynomialCommitments private static Task? _initializeTask; - public static Task Initialize(ILogger? logger = null) => _initializeTask ??= Task.Run(() => + public static Task InitializeAsync(ILogger? logger = null) => _initializeTask ??= Task.Run(() => { if (_ckzgSetup != IntPtr.Zero) return; @@ -52,7 +52,7 @@ public static Task Initialize(ILogger? logger = null) => _initializeTask ??= Tas /// Holds the output, can safely contain any data before the call. /// Result of the attempt /// - public static bool TryComputeCommitmentV1(ReadOnlySpan commitment, Span hashBuffer) + public static bool TryComputeCommitmentHashV1(ReadOnlySpan commitment, Span hashBuffer) { if (commitment.Length != Ckzg.Ckzg.BytesPerCommitment) { @@ -98,4 +98,14 @@ public static bool AreProofsValid(byte[] blobs, byte[] commitments, byte[] proof return false; } } + + /// + /// Method to genereate correct data for tests only, not safe + /// + public static void KzgifyBlob(ReadOnlySpan blob, Span commitment, Span proof, Span hashV1) + { + Ckzg.Ckzg.BlobToKzgCommitment(commitment, blob, _ckzgSetup); + Ckzg.Ckzg.ComputeBlobKzgProof(proof, blob, commitment, _ckzgSetup); + TryComputeCommitmentHashV1(commitment, hashV1); + } } diff --git a/src/Nethermind/Nethermind.Evm.Test/PointEvaluationPrecompileTests.cs b/src/Nethermind/Nethermind.Evm.Test/PointEvaluationPrecompileTests.cs index e4316eb96e6..f6b146f7238 100644 --- a/src/Nethermind/Nethermind.Evm.Test/PointEvaluationPrecompileTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/PointEvaluationPrecompileTests.cs @@ -24,7 +24,7 @@ public class PointEvaluationPrecompileTests private static readonly byte[] _predefinedFailureAnswer = Array.Empty(); [OneTimeSetUp] - public Task OneTimeSetUp() => KzgPolynomialCommitments.Initialize(); + public Task OneTimeSetUp() => KzgPolynomialCommitments.InitializeAsync(); [TestCaseSource(nameof(OutputTests))] public bool Test_PointEvaluationPrecompile_Produces_Correct_Outputs(byte[] input) diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/PointEvaluationPrecompile.cs b/src/Nethermind/Nethermind.Evm/Precompiles/PointEvaluationPrecompile.cs index be64187704f..f15c1d5aa67 100644 --- a/src/Nethermind/Nethermind.Evm/Precompiles/PointEvaluationPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm/Precompiles/PointEvaluationPrecompile.cs @@ -43,7 +43,7 @@ static bool IsValid(in ReadOnlyMemory inputData) ReadOnlySpan proof = inputDataSpan[144..192]; Span hash = stackalloc byte[32]; - return KzgPolynomialCommitments.TryComputeCommitmentV1(commitment, hash) + return KzgPolynomialCommitments.TryComputeCommitmentHashV1(commitment, hash) && hash.SequenceEqual(versionedHash) && KzgPolynomialCommitments.VerifyProof(commitment, z, y, proof); } diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializePrecompiles.cs b/src/Nethermind/Nethermind.Init/Steps/InitializePrecompiles.cs index b68e8c7fa52..9213e46a354 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializePrecompiles.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializePrecompiles.cs @@ -27,7 +27,7 @@ public async Task Execute(CancellationToken cancellationToken) try { - await KzgPolynomialCommitments.Initialize(logger); + await KzgPolynomialCommitments.InitializeAsync(logger); } catch (Exception e) { diff --git a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs index d4225f2ea92..de57492245c 100644 --- a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs +++ b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs @@ -108,7 +108,7 @@ public interface IJsonRpcConfig : IConfig [ConfigItem( Description = "Defines method names of Json RPC service requests to NOT log. Example: {\"eth_blockNumber\"} will not log \"eth_blockNumber\" requests.", - DefaultValue = "[engine_newPayloadV1, engine_newPayloadV2, engine_forkchoiceUpdatedV1, engine_forkchoiceUpdatedV2]")] + DefaultValue = "[engine_newPayloadV1, engine_newPayloadV2, engine_newPayloadV3, engine_forkchoiceUpdatedV1, engine_forkchoiceUpdatedV2]")] public string[]? MethodsLoggingFiltering { get; set; } [ConfigItem( diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs index 555c0fa407d..96a7326b854 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs @@ -44,6 +44,7 @@ public int WebSocketsPort { "engine_newPayloadV1", "engine_newPayloadV2", + "engine_newPayloadV3", "engine_forkchoiceUpdatedV1", "engine_forkchoiceUpdatedV2" }; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs index 26f23ef3af0..4d6364345c6 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs @@ -47,7 +47,7 @@ private void AssertExecutionStatusChanged(IBlockFinder blockFinder, Keccak headB } private Transaction[] BuildTransactions(MergeTestBlockchain chain, Keccak parentHash, PrivateKey from, - Address to, uint count, int value, out Account accountFrom, out BlockHeader parentHeader) + Address to, uint count, int value, out Account accountFrom, out BlockHeader parentHeader, int blobCountPerTx = 0) { Transaction BuildTransaction(uint index, Account senderAccount) => Build.A.Transaction.WithNonce(senderAccount.Nonce + index) @@ -57,8 +57,9 @@ Transaction BuildTransaction(uint index, Account senderAccount) => .WithGasPrice(1.GWei()) .WithChainId(chain.SpecProvider.ChainId) .WithSenderAddress(from.Address) - .SignedAndResolved(from) - .TestObject; + .WithShardBlobTxTypeAndFields(blobCountPerTx) + .WithMaxFeePerGasIfSupports1559(1.GWei()) + .SignedAndResolved(from).TestObject; parentHeader = chain.BlockTree.FindHeader(parentHash, BlockTreeLookupOptions.None)!; Account account = chain.StateReader.GetAccount(parentHeader.StateRoot!, from.Address)!; @@ -78,11 +79,12 @@ private ExecutionPayload CreateParentBlockRequestOnHead(IBlockTree blockTree) StateRoot = head.StateRoot!, ReceiptsRoot = head.ReceiptsRoot!, GasLimit = head.GasLimit, - Timestamp = head.Timestamp + Timestamp = head.Timestamp, + BaseFeePerGas = head.BaseFeePerGas, }; } - private static ExecutionPayload CreateBlockRequest(ExecutionPayload parent, Address miner, IList? withdrawals = null) + private static ExecutionPayload CreateBlockRequest(ExecutionPayload parent, Address miner, IList? withdrawals = null, UInt256? excessDataGas = null) { ExecutionPayload blockRequest = new() { @@ -95,7 +97,8 @@ private static ExecutionPayload CreateBlockRequest(ExecutionPayload parent, Addr ReceiptsRoot = Keccak.EmptyTreeHash, LogsBloom = Bloom.Empty, Timestamp = parent.Timestamp + 1, - Withdrawals = withdrawals + Withdrawals = withdrawals, + ExcessDataGas = excessDataGas, }; blockRequest.SetTransactions(Array.Empty()); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs index 40efaf1d653..84b46854baa 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs @@ -20,6 +20,7 @@ using Nethermind.Core.Specs; using Nethermind.Core.Test.Blockchain; using Nethermind.Core.Timers; +using Nethermind.Crypto; using Nethermind.Db; using Nethermind.Evm.Tracing; using Nethermind.Facade.Eth; @@ -34,21 +35,34 @@ using Nethermind.Specs.Forks; using Nethermind.State; using NSubstitute; +using NUnit.Framework; namespace Nethermind.Merge.Plugin.Test; public partial class EngineModuleTests { - protected virtual MergeTestBlockchain CreateBaseBlockChain(IMergeConfig? mergeConfig = null, IPayloadPreparationService? mockedPayloadService = null, ILogManager? logManager = null) => + [SetUp] + public Task Setup() + { + return KzgPolynomialCommitments.InitializeAsync(); + } + + protected virtual MergeTestBlockchain CreateBaseBlockChain(IMergeConfig? mergeConfig = null, + IPayloadPreparationService? mockedPayloadService = null, ILogManager? logManager = null) => new(mergeConfig, mockedPayloadService, logManager); - protected async Task CreateShanghaiBlockChain(IMergeConfig? mergeConfig = null, IPayloadPreparationService? mockedPayloadService = null) + protected async Task CreateShanghaiBlockChain(IMergeConfig? mergeConfig = null, + IPayloadPreparationService? mockedPayloadService = null) => await CreateBlockChain(mergeConfig, mockedPayloadService, Shanghai.Instance); - protected async Task CreateBlockChain(IMergeConfig? mergeConfig = null, IPayloadPreparationService? mockedPayloadService = null, IReleaseSpec? releaseSpec = null) - => await CreateBaseBlockChain(mergeConfig, mockedPayloadService).Build(new TestSingleReleaseSpecProvider(releaseSpec ?? London.Instance)); - protected async Task CreateBlockChain(ISpecProvider specProvider, ILogManager? logManager = null) + protected async Task CreateBlockChain(IMergeConfig? mergeConfig = null, + IPayloadPreparationService? mockedPayloadService = null, IReleaseSpec? releaseSpec = null) + => await CreateBaseBlockChain(mergeConfig, mockedPayloadService) + .Build(new TestSingleReleaseSpecProvider(releaseSpec ?? London.Instance)); + + protected async Task CreateBlockChain(ISpecProvider specProvider, + ILogManager? logManager = null) => await CreateBaseBlockChain(null, null, logManager).Build(specProvider); private IEngineRpcModule CreateEngineModule(MergeTestBlockchain chain, ISyncConfig? syncConfig = null, TimeSpan? newPayloadTimeout = null, int newPayloadCacheSize = 50) @@ -72,6 +86,9 @@ private IEngineRpcModule CreateEngineModule(MergeTestBlockchain chain, ISyncConf new GetPayloadV2Handler( chain.PayloadPreparationService!, chain.LogManager), + new GetPayloadV3Handler( + chain.PayloadPreparationService!, + chain.LogManager), new NewPayloadHandler( chain.BlockValidator, chain.BlockTree, diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs index 6ef16f03897..573bfbedbb7 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs @@ -35,9 +35,11 @@ public partial class EngineModuleTests "0x6d8a107ccab7a785de89f58db49064ee091df5d2b6306fe55db666e75a0e9f68", "0x03e662d795ee2234c492ca4a08de03b1d7e3e0297af81a76582e16de75cdfc51", "0x5009aaf2fdcd600e")] - public virtual async Task Should_process_block_as_expected_V2(string latestValidHash, string blockHash, string stateRoot, string payloadId) + public virtual async Task Should_process_block_as_expected_V2(string latestValidHash, string blockHash, + string stateRoot, string payloadId) { - using MergeTestBlockchain chain = await CreateShanghaiBlockChain(new MergeConfig { TerminalTotalDifficulty = "0" }); + using MergeTestBlockchain chain = + await CreateShanghaiBlockChain(new MergeConfig { TerminalTotalDifficulty = "0" }); IEngineRpcModule rpc = CreateEngineModule(chain); Keccak startingHead = chain.BlockTree.HeadHash; Keccak prevRandao = Keccak.Zero; @@ -62,8 +64,7 @@ public virtual async Task Should_process_block_as_expected_V2(string latestValid }; string?[] @params = new string?[] { - chain.JsonSerializer.Serialize(fcuState), - chain.JsonSerializer.Serialize(payloadAttrs) + chain.JsonSerializer.Serialize(fcuState), chain.JsonSerializer.Serialize(payloadAttrs) }; string expectedPayloadId = payloadId; @@ -97,7 +98,7 @@ public virtual async Task Should_process_block_as_expected_V2(string latestValid chain.BlockTree.Head!.GasLimit, timestamp, Bytes.FromHexString("0x4e65746865726d696e64") // Nethermind - ) + ) { BaseFeePerGas = 0, Bloom = Bloom.Empty, @@ -145,11 +146,7 @@ public virtual async Task Should_process_block_as_expected_V2(string latestValid safeBlockHash = expectedBlockHash.ToString(true), finalizedBlockHash = startingHead.ToString(true) }; - @params = new[] - { - chain.JsonSerializer.Serialize(fcuState), - null - }; + @params = new[] { chain.JsonSerializer.Serialize(fcuState), null }; response = RpcTest.TestSerializedRequest(rpc, "engine_forkchoiceUpdatedV2", @params!); successResponse = chain.JsonSerializer.Deserialize(response); @@ -191,8 +188,7 @@ public virtual async Task forkchoiceUpdatedV1_should_fail_with_withdrawals() }; string[] @params = new[] { - chain.JsonSerializer.Serialize(fcuState), - chain.JsonSerializer.Serialize(payloadAttrs) + chain.JsonSerializer.Serialize(fcuState), chain.JsonSerializer.Serialize(payloadAttrs) }; string response = RpcTest.TestSerializedRequest(rpcModule, "engine_forkchoiceUpdatedV1", @params); @@ -229,8 +225,7 @@ string BlockHash }; string[] @params = new[] { - chain.JsonSerializer.Serialize(fcuState), - chain.JsonSerializer.Serialize(payloadAttrs) + chain.JsonSerializer.Serialize(fcuState), chain.JsonSerializer.Serialize(payloadAttrs) }; string response = RpcTest.TestSerializedRequest(rpcModule, "engine_forkchoiceUpdatedV2", @params); @@ -251,8 +246,14 @@ public virtual async Task getPayloadV2_empty_block_should_have_zero_value() 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); + 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_getPayloadV2(payloadId); @@ -275,14 +276,20 @@ public virtual async Task getPayloadV2_received_fees_should_be_equal_to_block_va int value = 10; PrivateKey sender = TestItem.PrivateKeyB; - Transaction[] transactions = BuildTransactions(chain, startingHead, sender, Address.Zero, count, value, out _, out _); + Transaction[] transactions = + BuildTransactions(chain, startingHead, sender, Address.Zero, 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 = feeRecipient }) + new PayloadAttributes() + { + Timestamp = 100, + PrevRandao = TestItem.KeccakA, + SuggestedFeeRecipient = feeRecipient + }) .Result.Data.PayloadId!; UInt256 startingBalance = chain.StateReader.GetBalance(chain.State.StateRoot, feeRecipient); @@ -290,7 +297,8 @@ public virtual async Task getPayloadV2_received_fees_should_be_equal_to_block_va await blockImprovementLock.WaitAsync(10000); GetPayloadV2Result getPayloadResult = (await rpc.engine_getPayloadV2(Bytes.FromHexString(payloadId))).Data!; - ResultWrapper executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult.ExecutionPayload); + ResultWrapper executePayloadResult = + await rpc.engine_newPayloadV1(getPayloadResult.ExecutionPayload); executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); UInt256 finalBalance = chain.StateReader.GetBalance(getPayloadResult.ExecutionPayload.StateRoot, feeRecipient); @@ -313,57 +321,55 @@ public virtual async Task getPayloadV2_should_fail_on_unknown_payload() } [TestCaseSource(nameof(GetPayloadWithdrawalsTestCases))] - public virtual async Task getPayloadBodiesByHashV1_should_return_payload_bodies_in_order_of_request_block_hashes_and_null_for_unknown_hashes( - IList withdrawals) + public virtual async Task + getPayloadBodiesByHashV1_should_return_payload_bodies_in_order_of_request_block_hashes_and_null_for_unknown_hashes( + IList withdrawals) { - using var chain = await CreateShanghaiBlockChain(); - var rpc = CreateEngineModule(chain); - var executionPayload1 = await SendNewBlockV2(rpc, chain, withdrawals); - var txs = BuildTransactions( + using MergeTestBlockchain chain = await CreateShanghaiBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + ExecutionPayload executionPayload1 = await SendNewBlockV2(rpc, chain, withdrawals); + Transaction[] txs = BuildTransactions( chain, executionPayload1.BlockHash, TestItem.PrivateKeyA, TestItem.AddressB, 3, 0, out _, out _); chain.AddTransactions(txs); - var executionPayload2 = await BuildAndSendNewBlockV2(rpc, chain, true, withdrawals); - var blockHashes = new Keccak[] + ExecutionPayload executionPayload2 = await BuildAndSendNewBlockV2(rpc, chain, true, withdrawals); + Keccak[] blockHashes = new Keccak[] { - executionPayload1.BlockHash, TestItem.KeccakA, - executionPayload2.BlockHash + executionPayload1.BlockHash, TestItem.KeccakA, executionPayload2.BlockHash }; - var payloadBodies = rpc.engine_getPayloadBodiesByHashV1(blockHashes).Result.Data; - var expected = new ExecutionPayloadBodyV1Result?[] + IEnumerable payloadBodies = + rpc.engine_getPayloadBodiesByHashV1(blockHashes).Result.Data; + ExecutionPayloadBodyV1Result[] expected = new ExecutionPayloadBodyV1Result?[] { - new(Array.Empty(), withdrawals), - null, - new(txs, withdrawals) + new(Array.Empty(), withdrawals), null, new(txs, withdrawals) }; payloadBodies.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering()); } [TestCaseSource(nameof(GetPayloadWithdrawalsTestCases))] - public virtual async Task getPayloadBodiesByRangeV1_should_return_payload_bodies_in_order_of_request_range_and_null_for_unknown_indexes( - IList withdrawals) + public virtual async Task + getPayloadBodiesByRangeV1_should_return_payload_bodies_in_order_of_request_range_and_null_for_unknown_indexes( + IList withdrawals) { - using var chain = await CreateShanghaiBlockChain(); - var rpc = CreateEngineModule(chain); - var executionPayload1 = await SendNewBlockV2(rpc, chain, withdrawals); - var txs = BuildTransactions( + using MergeTestBlockchain chain = await CreateShanghaiBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + ExecutionPayload executionPayload1 = await SendNewBlockV2(rpc, chain, withdrawals); + Transaction[] txs = BuildTransactions( chain, executionPayload1.BlockHash, TestItem.PrivateKeyA, TestItem.AddressB, 3, 0, out _, out _); chain.AddTransactions(txs); await BuildAndSendNewBlockV2(rpc, chain, true, withdrawals); - var executionPayload2 = await BuildAndSendNewBlockV2(rpc, chain, true, withdrawals); + ExecutionPayload executionPayload2 = await BuildAndSendNewBlockV2(rpc, chain, true, withdrawals); await rpc.engine_forkchoiceUpdatedV2(new ForkchoiceStateV1(executionPayload2.BlockHash!, executionPayload2.BlockHash!, executionPayload2.BlockHash!)); - var payloadBodies = rpc.engine_getPayloadBodiesByRangeV1(1, 3).Result.Data; - var expected = new ExecutionPayloadBodyV1Result?[] - { - new(txs, withdrawals) - }; + IEnumerable payloadBodies = + rpc.engine_getPayloadBodiesByRangeV1(1, 3).Result.Data; + ExecutionPayloadBodyV1Result[] expected = new ExecutionPayloadBodyV1Result?[] { new(txs, withdrawals) }; payloadBodies.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering()); } @@ -371,10 +377,11 @@ await rpc.engine_forkchoiceUpdatedV2(new ForkchoiceStateV1(executionPayload2.Blo [Test] public async Task getPayloadBodiesByRangeV1_empty_response() { - using var chain = await CreateBlockChain(); - var rpc = CreateEngineModule(chain); - var payloadBodies = rpc.engine_getPayloadBodiesByRangeV1(1, 1).Result.Data; - var expected = Array.Empty(); + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + IEnumerable payloadBodies = + rpc.engine_getPayloadBodiesByRangeV1(1, 1).Result.Data; + ExecutionPayloadBodyV1Result[] expected = Array.Empty(); payloadBodies.Should().BeEquivalentTo(expected); } @@ -382,9 +389,10 @@ public async Task getPayloadBodiesByRangeV1_empty_response() [Test] public async Task getPayloadBodiesByRangeV1_should_fail_when_too_many_payloads_requested() { - using var chain = await CreateBlockChain(); - var rpc = CreateEngineModule(chain); - var result = rpc.engine_getPayloadBodiesByRangeV1(1, 1025); + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Task>> result = + rpc.engine_getPayloadBodiesByRangeV1(1, 1025); result.Result.ErrorCode.Should().Be(MergeErrorCodes.TooLargeRequest); } @@ -392,10 +400,11 @@ public async Task getPayloadBodiesByRangeV1_should_fail_when_too_many_payloads_r [Test] public async Task getPayloadBodiesByHashV1_should_fail_when_too_many_payloads_requested() { - using var chain = await CreateBlockChain(); - var rpc = CreateEngineModule(chain); - var hashes = Enumerable.Repeat(TestItem.KeccakA, 1025).ToArray(); - var result = rpc.engine_getPayloadBodiesByHashV1(hashes); + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak[] hashes = Enumerable.Repeat(TestItem.KeccakA, 1025).ToArray(); + Task>> result = + rpc.engine_getPayloadBodiesByHashV1(hashes); result.Result.ErrorCode.Should().Be(MergeErrorCodes.TooLargeRequest); } @@ -403,9 +412,10 @@ public async Task getPayloadBodiesByHashV1_should_fail_when_too_many_payloads_re [Test] public async Task getPayloadBodiesByRangeV1_should_fail_when_params_below_1() { - using var chain = await CreateBlockChain(); - var rpc = CreateEngineModule(chain); - var result = rpc.engine_getPayloadBodiesByRangeV1(0, 1); + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Task>> result = + rpc.engine_getPayloadBodiesByRangeV1(0, 1); result.Result.ErrorCode.Should().Be(ErrorCodes.InvalidParams); @@ -417,38 +427,38 @@ public async Task getPayloadBodiesByRangeV1_should_fail_when_params_below_1() [TestCaseSource(nameof(GetPayloadWithdrawalsTestCases))] public virtual async Task getPayloadBodiesByRangeV1_should_return_canonical(IList withdrawals) { - using var chain = await CreateShanghaiBlockChain(); - var rpc = CreateEngineModule(chain); - var executionPayload1 = await SendNewBlockV2(rpc, chain, withdrawals); + using MergeTestBlockchain chain = await CreateShanghaiBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + ExecutionPayload executionPayload1 = await SendNewBlockV2(rpc, chain, withdrawals); await rpc.engine_forkchoiceUpdatedV2(new ForkchoiceStateV1(executionPayload1.BlockHash!, executionPayload1.BlockHash!, executionPayload1.BlockHash!)); - var head = chain.BlockTree.Head!; + Block head = chain.BlockTree.Head!; // First branch { - var txsA = BuildTransactions( + Transaction[] txsA = BuildTransactions( chain, executionPayload1.BlockHash!, TestItem.PrivateKeyA, TestItem.AddressA, 1, 0, out _, out _); chain.AddTransactions(txsA); - var executionPayload2 = await BuildAndGetPayloadResultV2( + ExecutionPayload executionPayload2 = await BuildAndGetPayloadResultV2( rpc, chain, head.Hash!, head.Hash!, head.Hash!, 1001, Keccak.Zero, Address.Zero, withdrawals); - var execResult = await rpc.engine_newPayloadV2(executionPayload2); + ResultWrapper execResult = await rpc.engine_newPayloadV2(executionPayload2); execResult.Data.Status.Should().Be(PayloadStatus.Valid); - var fcuResult = await rpc.engine_forkchoiceUpdatedV2( + ResultWrapper fcuResult = await rpc.engine_forkchoiceUpdatedV2( new ForkchoiceStateV1(executionPayload2.BlockHash!, head.Hash!, head.Hash!)); fcuResult.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - var payloadBodies = rpc.engine_getPayloadBodiesByRangeV1(1, 3).Result.Data; - var expected = new ExecutionPayloadBodyV1Result?[] + IEnumerable payloadBodies = + rpc.engine_getPayloadBodiesByRangeV1(1, 3).Result.Data; + ExecutionPayloadBodyV1Result[] expected = { - new(Array.Empty(), withdrawals), - new(txsA, withdrawals) + new(Array.Empty(), withdrawals), new(txsA, withdrawals) }; payloadBodies.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering()); @@ -456,7 +466,7 @@ await rpc.engine_forkchoiceUpdatedV2(new ForkchoiceStateV1(executionPayload1.Blo // Second branch { - var newBlock = Build.A.Block + Block newBlock = Build.A.Block .WithNumber(head.Number + 1) .WithParent(head) .WithNonce(0) @@ -466,18 +476,18 @@ await rpc.engine_forkchoiceUpdatedV2(new ForkchoiceStateV1(executionPayload1.Blo .WithWithdrawals(withdrawals.ToArray()) .TestObject; - var fcuResult = await rpc.engine_newPayloadV2(new ExecutionPayload(newBlock)); + ResultWrapper fcuResult = await rpc.engine_newPayloadV2(new ExecutionPayload(newBlock)); fcuResult.Data.Status.Should().Be(PayloadStatus.Valid); await rpc.engine_forkchoiceUpdatedV2( new ForkchoiceStateV1(newBlock.Hash!, newBlock.Hash!, newBlock.Hash!)); - var payloadBodies = rpc.engine_getPayloadBodiesByRangeV1(1, 3).Result.Data; - var expected = new ExecutionPayloadBodyV1Result?[] + IEnumerable payloadBodies = + rpc.engine_getPayloadBodiesByRangeV1(1, 3).Result.Data; + ExecutionPayloadBodyV1Result[] expected = { - new(Array.Empty(), withdrawals), - new(Array.Empty(), withdrawals) + new(Array.Empty(), withdrawals), new(Array.Empty(), withdrawals) }; payloadBodies.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering()); @@ -487,18 +497,19 @@ await rpc.engine_forkchoiceUpdatedV2( [TestCaseSource(nameof(PayloadBodiesByRangeNullTrimTestCases))] public async Task getPayloadBodiesByRangeV1_should_trim_trailing_null_bodies( (Func Impl, - IEnumerable Outcome) input) + IEnumerable Outcome) input) { - var blockTree = Substitute.For(); + IBlockTree? blockTree = Substitute.For(); blockTree.Head.Returns(Build.A.Block.WithNumber(5).TestObject); blockTree.FindBlock(Arg.Any()).Returns(input.Impl); - using var chain = await CreateShanghaiBlockChain(); + using MergeTestBlockchain chain = await CreateShanghaiBlockChain(); chain.BlockTree = blockTree; - var rpc = CreateEngineModule(chain); - var payloadBodies = rpc.engine_getPayloadBodiesByRangeV1(1, 5).Result.Data; + IEngineRpcModule rpc = CreateEngineModule(chain); + IEnumerable payloadBodies = + rpc.engine_getPayloadBodiesByRangeV1(1, 5).Result.Data; payloadBodies.Should().BeEquivalentTo(input.Outcome); } @@ -506,17 +517,18 @@ public async Task getPayloadBodiesByRangeV1_should_trim_trailing_null_bodies( [Test] public async Task getPayloadBodiesByRangeV1_should_return_up_to_best_body_number() { - var blockTree = Substitute.For(); + IBlockTree? blockTree = Substitute.For(); blockTree.FindBlock(Arg.Any()) .Returns(i => Build.A.Block.WithNumber(i.ArgAt(0)).TestObject); blockTree.Head.Returns(Build.A.Block.WithNumber(5).TestObject); - using var chain = await CreateShanghaiBlockChain(); + using MergeTestBlockchain chain = await CreateShanghaiBlockChain(); chain.BlockTree = blockTree; - var rpc = CreateEngineModule(chain); - var payloadBodies = rpc.engine_getPayloadBodiesByRangeV1(1, 7).Result.Data; + IEngineRpcModule rpc = CreateEngineModule(chain); + IEnumerable payloadBodies = + rpc.engine_getPayloadBodiesByRangeV1(1, 7).Result.Data; payloadBodies.Count().Should().Be(5); } @@ -626,7 +638,8 @@ public async Task executePayloadV2_works_correctly_when_0_withdrawals_applied(( { using MergeTestBlockchain chain = await CreateBlockChain(null, null, input.ReleaseSpec); IEngineRpcModule rpc = CreateEngineModule(chain); - ExecutionPayload executionPayload = CreateBlockRequest(CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD, input.Withdrawals); + ExecutionPayload executionPayload = CreateBlockRequest(CreateParentBlockRequestOnHead(chain.BlockTree), + TestItem.AddressD, input.Withdrawals); ResultWrapper resultWrapper = await rpc.engine_newPayloadV2(executionPayload); if (input.IsValid) @@ -636,7 +649,8 @@ public async Task executePayloadV2_works_correctly_when_0_withdrawals_applied(( } [TestCaseSource(nameof(WithdrawalsTestCases))] - public virtual async Task Can_apply_withdrawals_correctly((Withdrawal[][] Withdrawals, (Address Account, UInt256 BalanceIncrease)[] ExpectedAccountIncrease) input) + public virtual async Task Can_apply_withdrawals_correctly( + (Withdrawal[][] Withdrawals, (Address Account, UInt256 BalanceIncrease)[] ExpectedAccountIncrease) input) { using MergeTestBlockchain chain = await CreateShanghaiBlockChain(); IEngineRpcModule rpc = CreateEngineModule(chain); @@ -645,14 +659,22 @@ public virtual async Task Can_apply_withdrawals_correctly((Withdrawal[][] Withdr List initialBalances = new(); foreach ((Address Account, UInt256 BalanceIncrease) accountIncrease in input.ExpectedAccountIncrease) { - UInt256 initialBalance = chain.StateReader.GetBalance(chain.BlockTree.Head!.StateRoot!, accountIncrease.Account); + UInt256 initialBalance = + chain.StateReader.GetBalance(chain.BlockTree.Head!.StateRoot!, accountIncrease.Account); initialBalances.Add(initialBalance); } foreach (Withdrawal[] withdrawal in input.Withdrawals) { - PayloadAttributes payloadAttributes = new() { Timestamp = chain.BlockTree.Head!.Timestamp + 1, PrevRandao = TestItem.KeccakH, SuggestedFeeRecipient = TestItem.AddressF, Withdrawals = withdrawal }; - ExecutionPayload payload = (await BuildAndGetPayloadResultV2(rpc, chain, payloadAttributes))?.ExecutionPayload!; + PayloadAttributes payloadAttributes = new() + { + Timestamp = chain.BlockTree.Head!.Timestamp + 1, + PrevRandao = TestItem.KeccakH, + SuggestedFeeRecipient = TestItem.AddressF, + Withdrawals = withdrawal + }; + ExecutionPayload payload = + (await BuildAndGetPayloadResultV2(rpc, chain, payloadAttributes))?.ExecutionPayload!; ResultWrapper resultWrapper = await rpc.engine_newPayloadV2(payload!); resultWrapper.Data.Status.Should().Be(PayloadStatus.Valid); ResultWrapper resultFcu = await rpc.engine_forkchoiceUpdatedV2( @@ -664,7 +686,8 @@ public virtual async Task Can_apply_withdrawals_correctly((Withdrawal[][] Withdr for (int index = 0; index < input.ExpectedAccountIncrease.Length; index++) { (Address Account, UInt256 BalanceIncrease) accountIncrease = input.ExpectedAccountIncrease[index]; - UInt256 currentBalance = chain.StateReader.GetBalance(chain.BlockTree.Head!.StateRoot!, accountIncrease.Account); + UInt256 currentBalance = + chain.StateReader.GetBalance(chain.BlockTree.Head!.StateRoot!, accountIncrease.Account); currentBalance.Should().Be(accountIncrease.BalanceIncrease + initialBalances[index]); } } @@ -676,14 +699,15 @@ public virtual async Task Should_handle_withdrawals_transition_when_Shanghai_for CustomSpecProvider specProvider = new( (new ForkActivation(0, null), ArrowGlacier.Instance), (new ForkActivation(0, 3), Shanghai.Instance) - ); + ); // Genesis, Timestamp = 1 using MergeTestBlockchain chain = await CreateBlockChain(specProvider); IEngineRpcModule rpc = CreateEngineModule(chain); // Block without withdrawals, Timestamp = 2 - ExecutionPayload executionPayload = CreateBlockRequest(CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD); + ExecutionPayload executionPayload = + CreateBlockRequest(CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD); ResultWrapper resultWrapper = await rpc.engine_newPayloadV2(executionPayload); resultWrapper.Data.Status.Should().Be(PayloadStatus.Valid); @@ -695,7 +719,8 @@ public virtual async Task Should_handle_withdrawals_transition_when_Shanghai_for SuggestedFeeRecipient = TestItem.AddressF, Withdrawals = new[] { TestItem.WithdrawalA_1Eth } }; - ExecutionPayload payloadWithWithdrawals = (await BuildAndGetPayloadResultV2(rpc, chain, payloadAttributes))?.ExecutionPayload!; + ExecutionPayload payloadWithWithdrawals = + (await BuildAndGetPayloadResultV2(rpc, chain, payloadAttributes))?.ExecutionPayload!; ResultWrapper resultWithWithdrawals = await rpc.engine_newPayloadV2(payloadWithWithdrawals!); resultWithWithdrawals.Data.Status.Should().Be(PayloadStatus.Valid); @@ -754,25 +779,35 @@ private static async Task BuildAndGetPayloadResultV2( (Address, UInt256)[] expectedAccountIncrease)> WithdrawalsTestCases() { yield return (new[] { Array.Empty() }, Array.Empty<(Address, UInt256)>()); - yield return (new[] { new[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalB_2Eth } }, new[] { (TestItem.AddressA, 1.Ether()), (TestItem.AddressB, 2.Ether()) }); - yield return (new[] { new[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalA_1Eth } }, new[] { (TestItem.AddressA, 2.Ether()), (TestItem.AddressB, 0.Ether()) }); - yield return (new[] { new[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalA_1Eth }, new[] { TestItem.WithdrawalA_1Eth } }, new[] { (TestItem.AddressA, 3.Ether()), (TestItem.AddressB, 0.Ether()) }); + yield return (new[] { new[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalB_2Eth } }, + new[] { (TestItem.AddressA, 1.Ether()), (TestItem.AddressB, 2.Ether()) }); + yield return (new[] { new[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalA_1Eth } }, + new[] { (TestItem.AddressA, 2.Ether()), (TestItem.AddressB, 0.Ether()) }); + yield return ( + new[] + { + new[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalA_1Eth }, new[] { TestItem.WithdrawalA_1Eth } + }, new[] { (TestItem.AddressA, 3.Ether()), (TestItem.AddressB, 0.Ether()) }); yield return (new[] - { - new[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalA_1Eth }, // 1st payload - new[] { TestItem.WithdrawalA_1Eth }, // 2nd payload - Array.Empty(), // 3rd payload - new[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalC_3Eth }, // 4th payload - new[] { TestItem.WithdrawalB_2Eth, TestItem.WithdrawalF_6Eth }, // 5th payload - }, new[] { (TestItem.AddressA, 4.Ether()), (TestItem.AddressB, 2.Ether()), (TestItem.AddressC, 3.Ether()), (TestItem.AddressF, 6.Ether()) }); + { + new[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalA_1Eth }, // 1st payload + new[] { TestItem.WithdrawalA_1Eth }, // 2nd payload + Array.Empty(), // 3rd payload + new[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalC_3Eth }, // 4th payload + new[] { TestItem.WithdrawalB_2Eth, TestItem.WithdrawalF_6Eth }, // 5th payload + }, + new[] + { + (TestItem.AddressA, 4.Ether()), (TestItem.AddressB, 2.Ether()), (TestItem.AddressC, 3.Ether()), + (TestItem.AddressF, 6.Ether()) + }); } protected static IEnumerable> GetPayloadWithdrawalsTestCases() { yield return new[] { - new Withdrawal { Index = 1, ValidatorIndex = 1 }, - new Withdrawal { Index = 2, ValidatorIndex = 2 } + new Withdrawal { Index = 1, ValidatorIndex = 1 }, new Withdrawal { Index = 2, ValidatorIndex = 2 } }; } @@ -788,7 +823,7 @@ private async Task BuildAndGetPayloadResultV2( IList? withdrawals, bool waitForBlockImprovement = true) { - using var blockImprovementLock = new SemaphoreSlim(0); + using SemaphoreSlim blockImprovementLock = new SemaphoreSlim(0); if (waitForBlockImprovement) { @@ -798,20 +833,21 @@ private async Task BuildAndGetPayloadResultV2( }; } - var forkchoiceState = new ForkchoiceStateV1(headBlockHash, finalizedBlockHash, safeBlockHash); - var payloadAttributes = new PayloadAttributes + ForkchoiceStateV1 forkchoiceState = new ForkchoiceStateV1(headBlockHash, finalizedBlockHash, safeBlockHash); + PayloadAttributes payloadAttributes = new PayloadAttributes { Timestamp = timestamp, PrevRandao = random, SuggestedFeeRecipient = feeRecipient, Withdrawals = withdrawals }; - var payloadId = rpc.engine_forkchoiceUpdatedV2(forkchoiceState, payloadAttributes).Result.Data.PayloadId; + string? payloadId = rpc.engine_forkchoiceUpdatedV2(forkchoiceState, payloadAttributes).Result.Data.PayloadId; if (waitForBlockImprovement) await blockImprovementLock.WaitAsync(10000); - var getPayloadResult = await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId!)); + ResultWrapper getPayloadResult = + await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId!)); return getPayloadResult.Data!; } @@ -834,11 +870,12 @@ private async Task BuildAndSendNewBlockV2( return executionPayload; } - private async Task SendNewBlockV2(IEngineRpcModule rpc, MergeTestBlockchain chain, IList? withdrawals) + private async Task SendNewBlockV2(IEngineRpcModule rpc, MergeTestBlockchain chain, + IList? withdrawals) { - var executionPayload = CreateBlockRequest( + ExecutionPayload executionPayload = CreateBlockRequest( CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD, withdrawals); - var executePayloadResult = await rpc.engine_newPayloadV2(executionPayload); + ResultWrapper executePayloadResult = await rpc.engine_newPayloadV2(executionPayload); executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); @@ -863,8 +900,8 @@ bool isValid IEnumerable )> PayloadBodiesByRangeNullTrimTestCases() { - var block = Build.A.Block.TestObject; - var result = new ExecutionPayloadBodyV1Result(Array.Empty(), null); + Block block = Build.A.Block.TestObject; + ExecutionPayloadBodyV1Result result = new ExecutionPayloadBodyV1Result(Array.Empty(), null); yield return ( new Func(i => null), diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs new file mode 100644 index 00000000000..bd5f4699f52 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs @@ -0,0 +1,115 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +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.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.JsonRpc; +using Nethermind.Merge.Plugin.Data; +using Nethermind.Specs.Forks; +using NUnit.Framework; +using Transaction = Nethermind.Core.Transaction; + +namespace Nethermind.Merge.Plugin.Test; + +public partial class EngineModuleTests +{ + [TestCaseSource(nameof(ExcessDataGasInGetPayloadV3ForDifferentSpecTestSource))] + public async Task ExccessDataGas_should_present_in_cancun_only((IReleaseSpec Spec, bool IsExcessDataGasSet) input) + { + (IEngineRpcModule rpcModule, string payloadId) = await BuildAndGetPayloadV3Result(input.Spec); + ResultWrapper getPayloadResult = + await rpcModule.engine_getPayloadV3(Bytes.FromHexString(payloadId)); + Assert.That(getPayloadResult.Data!.ExecutionPayload.ExcessDataGas.HasValue, + Is.EqualTo(input.IsExcessDataGasSet)); + } + + [Test] + public async Task GetPayloadV3_should_fail_on_unknown_payload() + { + using SemaphoreSlim blockImprovementLock = new(0); + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + + byte[] payloadId = Bytes.FromHexString("0x0"); + ResultWrapper responseFirst = await rpc.engine_getPayloadV3(payloadId); + responseFirst.Should().NotBeNull(); + responseFirst.Result.ResultType.Should().Be(ResultType.Failure); + responseFirst.ErrorCode.Should().Be(MergeErrorCodes.UnknownPayload); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + [TestCase(4)] + public async Task PayloadV3_should_return_all_the_blobs(int blobTxCount) + { + (IEngineRpcModule rpcModule, string payloadId) = await BuildAndGetPayloadV3Result(Cancun.Instance, blobTxCount); + BlobsBundleV1 getPayloadResultBlobsBundle = + (await rpcModule.engine_getPayloadV3(Bytes.FromHexString(payloadId))).Data!.BlobsBundle!; + Assert.That(getPayloadResultBlobsBundle.Blobs!.Length, Is.EqualTo(blobTxCount)); + Assert.That(getPayloadResultBlobsBundle.Commitments!.Length, Is.EqualTo(blobTxCount)); + Assert.That(getPayloadResultBlobsBundle.Proofs!.Length, Is.EqualTo(blobTxCount)); + } + + private async Task SendNewBlockV3(IEngineRpcModule rpc, MergeTestBlockchain chain, IList? withdrawals) + { + ExecutionPayload executionPayload = CreateBlockRequest( + CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD, withdrawals, 0); + ResultWrapper executePayloadResult = await rpc.engine_newPayloadV3(executionPayload); + + executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); + + return executionPayload; + } + + private async Task<(IEngineRpcModule, string)> BuildAndGetPayloadV3Result( + IReleaseSpec spec, int transactionCount = 0) + { + MergeTestBlockchain chain = await CreateBlockChain(releaseSpec: spec); + IEngineRpcModule rpcModule = CreateEngineModule(chain); + if (transactionCount is not 0) + { + using SemaphoreSlim blockImprovementLock = new(0); + + ExecutionPayload executionPayload1 = await SendNewBlockV3(rpcModule, chain, new List()); + Transaction[] txs = BuildTransactions( + chain, executionPayload1.BlockHash, TestItem.PrivateKeyA, TestItem.AddressB, (uint)transactionCount, 0, out _, out _, 1); + chain.AddTransactions(txs); + + EventHandler onBlockImprovedHandler = (_, _) => blockImprovementLock.Release(1); + + chain.PayloadPreparationService!.BlockImproved += onBlockImprovedHandler; + await blockImprovementLock.WaitAsync(10000); + chain.PayloadPreparationService!.BlockImproved -= onBlockImprovedHandler; + } + + PayloadAttributes payloadAttributes = new() + { + Timestamp = chain.BlockTree.Head!.Timestamp + 1, + PrevRandao = TestItem.KeccakH, + SuggestedFeeRecipient = TestItem.AddressF, + Withdrawals = new List { TestItem.WithdrawalA_1Eth } + }; + Keccak currentHeadHash = chain.BlockTree.HeadHash; + ForkchoiceStateV1 forkchoiceState = new(currentHeadHash, currentHeadHash, currentHeadHash); + string payloadId = rpcModule.engine_forkchoiceUpdatedV2(forkchoiceState, payloadAttributes).Result.Data + .PayloadId!; + return (rpcModule, payloadId); + } + + protected static IEnumerable<(IReleaseSpec Spec, bool IsExcessDataGasSet)> ExcessDataGasInGetPayloadV3ForDifferentSpecTestSource() + { + yield return (Shanghai.Instance, false); + yield return (Cancun.Instance, true); + } +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsBundleV1.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsBundleV1.cs new file mode 100644 index 00000000000..8686e83e51e --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsBundleV1.cs @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; + +namespace Nethermind.Merge.Plugin.Data; + +/// +/// Holds blobs of a block. +/// +/// See BlobsBundleV1 +/// +public class BlobsBundleV1 +{ + public BlobsBundleV1(Block block) + { + int blobsCount = 0; + foreach (Transaction? tx in block.Transactions) + { + blobsCount += tx?.BlobVersionedHashes?.Length ?? 0; + } + + Commitments = new Memory[blobsCount]; + Blobs = new Memory[blobsCount]; + Proofs = new Memory[blobsCount]; + int i = 0; + + foreach (Transaction? tx in block.Transactions) + { + if (tx.Type is not TxType.Blob || tx.BlobKzgs is null || tx.Blobs is null) + { + continue; + } + + for (int cc = 0, bc = 0, pc = 0; + cc < tx.BlobKzgs.Length; + i++, + cc += Ckzg.Ckzg.BytesPerCommitment, + bc += Ckzg.Ckzg.BytesPerBlob, + pc += Ckzg.Ckzg.BytesPerProof) + { + Commitments[i] = tx.BlobKzgs.AsMemory(cc, Ckzg.Ckzg.BytesPerCommitment); + Blobs[i] = tx.Blobs.AsMemory(bc, Ckzg.Ckzg.BytesPerBlob); + Proofs[i] = tx.BlobProofs.AsMemory(pc, Ckzg.Ckzg.BytesPerProof); + } + } + } + + public Memory[] Commitments { get; } + public Memory[] Blobs { get; } + public Memory[] Proofs { get; } +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs index 99b8ab4246f..f5ea4cc7f2f 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs @@ -83,7 +83,11 @@ public ExecutionPayload(Block block) /// public IEnumerable? Withdrawals { get; set; } - [JsonConverter(typeof(NullableUInt256Converter))] + /// + /// Gets or sets as defined in + /// EIP-4844. + /// + [JsonProperty(ItemConverterType = typeof(NullableUInt256Converter), NullValueHandling = NullValueHandling.Ignore)] public UInt256? ExcessDataGas { get; set; } /// @@ -91,7 +95,7 @@ public ExecutionPayload(Block block) /// /// When this method returns, contains the execution block. /// A total difficulty of the block. - /// true if block created successfully; otherise, false. + /// true if block created successfully; otherwise, false. public virtual bool TryGetBlock(out Block? block, UInt256? totalDifficulty = null) { try diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV2Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV2Result.cs index 31df7cbb3d9..1d261ca65d2 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV2Result.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV2Result.cs @@ -3,6 +3,7 @@ using Nethermind.Core; using Nethermind.Int256; +using Newtonsoft.Json; namespace Nethermind.Merge.Plugin.Data; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV3Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV3Result.cs new file mode 100644 index 00000000000..b3730948fad --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV3Result.cs @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Int256; + +namespace Nethermind.Merge.Plugin.Data; + +public class GetPayloadV3Result : GetPayloadV2Result +{ + public GetPayloadV3Result(Block block, UInt256 blockFees, BlobsBundleV1 blobsBundle) : base(block, blockFees) + { + BlobsBundle = blobsBundle; + } + + public BlobsBundleV1 BlobsBundle { get; } + + public override string ToString() => + $"{{ExecutionPayload: {ExecutionPayload}, Fees: {BlockValue}, BlobsBundle blobs count: {BlobsBundle.Blobs.Length}}}"; +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Cancun.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Cancun.cs new file mode 100644 index 00000000000..63a66aa6116 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Cancun.cs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading.Tasks; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.JsonRpc; +using Nethermind.Logging; +using Nethermind.Merge.Plugin.Data; +using Nethermind.Merge.Plugin.Handlers; + +namespace Nethermind.Merge.Plugin; + +public partial class EngineRpcModule : IEngineRpcModule +{ + private readonly IAsyncHandler _getPayloadHandlerV3; + + public Task> engine_newPayloadV3(ExecutionPayload executionPayload) => + NewPayload(executionPayload, 3); + + public async Task> engine_getPayloadV3(byte[] payloadId) => + await _getPayloadHandlerV3.HandleAsync(payloadId); +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs index 275a764b01e..dadc30c8391 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs @@ -23,6 +23,7 @@ public partial class EngineRpcModule : IEngineRpcModule public EngineRpcModule( IAsyncHandler getPayloadHandlerV1, IAsyncHandler getPayloadHandlerV2, + IAsyncHandler getPayloadHandlerV3, IAsyncHandler newPayloadV1Handler, IForkchoiceUpdatedHandler forkchoiceUpdatedV1Handler, IAsyncHandler, IEnumerable> executionGetPayloadBodiesByHashV1Handler, @@ -36,6 +37,7 @@ public EngineRpcModule( _capabilitiesHandler = capabilitiesHandler ?? throw new ArgumentNullException(nameof(capabilitiesHandler)); _getPayloadHandlerV1 = getPayloadHandlerV1; _getPayloadHandlerV2 = getPayloadHandlerV2; + _getPayloadHandlerV3 = getPayloadHandlerV3; _newPayloadV1Handler = newPayloadV1Handler; _forkchoiceUpdatedV1Handler = forkchoiceUpdatedV1Handler; _executionGetPayloadBodiesByHashV1Handler = executionGetPayloadBodiesByHashV1Handler; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs index c6dda695c41..25bbf8c8ef3 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs @@ -39,6 +39,11 @@ public IReadOnlyDictionary GetEngineCapabilities() _capabilities[nameof(IEngineRpcModule.engine_getPayloadV2)] = spec.WithdrawalsEnabled; _capabilities[nameof(IEngineRpcModule.engine_newPayloadV2)] = spec.WithdrawalsEnabled; #endregion + + #region Cancun + _capabilities[nameof(IEngineRpcModule.engine_getPayloadV3)] = spec.IsEip4844Enabled; + _capabilities[nameof(IEngineRpcModule.engine_newPayloadV3)] = spec.IsEip4844Enabled; + #endregion } return _capabilities; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadHandlerBase.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadHandlerBase.cs new file mode 100644 index 00000000000..501a6816707 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadHandlerBase.cs @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.JsonRpc; +using Nethermind.Logging; +using Nethermind.Merge.Plugin.BlockProduction; +using Nethermind.Merge.Plugin.Data; + +namespace Nethermind.Merge.Plugin.Handlers; + +public abstract class GetPayloadHandlerBase : IAsyncHandler +{ + private readonly int _apiVersion; + private readonly IPayloadPreparationService _payloadPreparationService; + private readonly ILogger _logger; + + protected GetPayloadHandlerBase(int apiVersion, IPayloadPreparationService payloadPreparationService, ILogManager logManager) + { + _apiVersion = apiVersion; + _payloadPreparationService = payloadPreparationService; + _logger = logManager.GetClassLogger(); + } + + public async Task> HandleAsync(byte[] payloadId) + { + string payloadStr = payloadId.ToHexString(true); + IBlockProductionContext? blockContext = await _payloadPreparationService.GetPayload(payloadStr); + Block? block = blockContext?.CurrentBestBlock; + + if (blockContext is null || block is null) + { + // The call MUST return -38001: Unknown payload error if the build process identified by the payloadId does not exist. + if (_logger.IsWarn) _logger.Warn($"Block production for payload with id={payloadId.ToHexString()} failed - unknown payload."); + return ResultWrapper.Fail("unknown payload", MergeErrorCodes.UnknownPayload); + } + + if (_logger.IsInfo) _logger.Info($"GetPayloadV{_apiVersion} result: {block.Header.ToString(BlockHeader.Format.Full)}."); + + Metrics.GetPayloadRequests++; + Metrics.NumberOfTransactionsInGetPayload = block.Transactions.Length; + return ResultWrapper.Success(GetPayloadResultFromBlock(blockContext)); + } + + protected abstract TGetPayloadResult GetPayloadResultFromBlock(IBlockProductionContext blockProductionContext); +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs index 0c48d674eff..295859ae19a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs @@ -1,57 +1,34 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Threading.Tasks; -using Nethermind.Core; -using Nethermind.Core.Extensions; -using Nethermind.JsonRpc; using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; using Nethermind.Merge.Plugin.Data; -namespace Nethermind.Merge.Plugin.Handlers +namespace Nethermind.Merge.Plugin.Handlers; + +/// +/// engine_getPayloadV1 +/// +/// Given a 8 byte payload_id, it returns the most recent version of an execution payload that is available by the time of the call or responds with an error. +/// +/// +/// +/// +/// This call must be responded immediately. An exception would be the case when no version of the payload is ready yet +/// and in this case there might be a slight delay before the response is done. +/// Execution client should create a payload with empty transaction set to be able to respond as soon as possible. +/// If there were no prior engine_preparePayload call with the corresponding payload_id or the process of building +/// a payload has been cancelled due to the timeout then execution client must respond with error message. +/// Execution client may stop the building process with the corresponding payload_id value after serving this call. +/// +public class GetPayloadV1Handler : GetPayloadHandlerBase { - /// - /// engine_getPayloadV1 - /// - /// Given a 8 byte payload_id, it returns the most recent version of an execution payload that is available by the time of the call or responds with an error. - /// - /// - /// - /// - /// This call must be responded immediately. An exception would be the case when no version of the payload is ready yet and in this case there might be a slight delay before the response is done. - /// Execution client should create a payload with empty transaction set to be able to respond as soon as possible. - /// If there were no prior engine_preparePayload call with the corresponding payload_id or the process of building a payload has been cancelled due to the timeout then execution client must respond with error message. - /// Execution client may stop the building process with the corresponding payload_id value after serving this call. - /// - public class GetPayloadV1Handler : IAsyncHandler + public GetPayloadV1Handler(IPayloadPreparationService payloadPreparationService, ILogManager logManager) : base( + 1, payloadPreparationService, logManager) { - private readonly IPayloadPreparationService _payloadPreparationService; - private readonly ILogger _logger; - - public GetPayloadV1Handler(IPayloadPreparationService payloadPreparationService, ILogManager logManager) - { - _payloadPreparationService = payloadPreparationService; - _logger = logManager.GetClassLogger(); - } - - public async Task> HandleAsync(byte[] payloadId) - { - string payloadStr = payloadId.ToHexString(true); - Block? block = (await _payloadPreparationService.GetPayload(payloadStr))?.CurrentBestBlock; - - if (block is null) - { - // The call MUST return -38001: Unknown payload error if the build process identified by the payloadId does not exist. - if (_logger.IsWarn) _logger.Warn($"Block production for payload with id={payloadId.ToHexString()} failed - unknown payload."); - return ResultWrapper.Fail("unknown payload", MergeErrorCodes.UnknownPayload); - } - - if (_logger.IsInfo) _logger.Info($"GetPayloadV1 result: {block.Header.ToString(BlockHeader.Format.Full)}."); - - Metrics.GetPayloadRequests++; - Metrics.NumberOfTransactionsInGetPayload = block.Transactions.Length; - return ResultWrapper.Success(new ExecutionPayload(block)); - } } + + protected override ExecutionPayload GetPayloadResultFromBlock(IBlockProductionContext context) => + new(context.CurrentBestBlock!); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs index d8c4d2ecb89..2932fe02cc0 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs @@ -1,48 +1,23 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Threading.Tasks; -using Nethermind.Core; -using Nethermind.Core.Extensions; -using Nethermind.JsonRpc; using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; using Nethermind.Merge.Plugin.Data; -namespace Nethermind.Merge.Plugin.Handlers +namespace Nethermind.Merge.Plugin.Handlers; + +/// +/// +/// engine_getpayloadv2. +/// +public class GetPayloadV2Handler : GetPayloadHandlerBase { - /// - /// engine_getpayloadv22. - /// - public class GetPayloadV2Handler : IAsyncHandler + public GetPayloadV2Handler(IPayloadPreparationService payloadPreparationService, ILogManager logManager) : base( + 2, payloadPreparationService, logManager) { - private readonly IPayloadPreparationService _payloadPreparationService; - private readonly ILogger _logger; - - public GetPayloadV2Handler(IPayloadPreparationService payloadPreparationService, ILogManager logManager) - { - _payloadPreparationService = payloadPreparationService; - _logger = logManager.GetClassLogger(); - } - - public async Task> HandleAsync(byte[] payloadId) - { - string payloadStr = payloadId.ToHexString(true); - IBlockProductionContext? blockContext = await _payloadPreparationService.GetPayload(payloadStr); - Block? block = blockContext?.CurrentBestBlock; - - if (block is null) - { - // The call MUST return -38001: Unknown payload error if the build process identified by the payloadId does not exist. - if (_logger.IsWarn) _logger.Warn($"Block production for payload with id={payloadId.ToHexString()} failed - unknown payload."); - return ResultWrapper.Fail("unknown payload", MergeErrorCodes.UnknownPayload); - } - - if (_logger.IsInfo) _logger.Info($"GetPayloadV2 result: {block.Header.ToString(BlockHeader.Format.Full)}."); - - Metrics.GetPayloadRequests++; - Metrics.NumberOfTransactionsInGetPayload = block.Transactions.Length; - return ResultWrapper.Success(new GetPayloadV2Result(block, blockContext!.BlockFees)); - } } + + protected override GetPayloadV2Result GetPayloadResultFromBlock(IBlockProductionContext context) => + new(context.CurrentBestBlock!, context.BlockFees); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV3Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV3Handler.cs new file mode 100644 index 00000000000..dd1f0ba962c --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV3Handler.cs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Logging; +using Nethermind.Merge.Plugin.BlockProduction; +using Nethermind.Merge.Plugin.Data; + +namespace Nethermind.Merge.Plugin.Handlers; + +/// +/// +/// engine_getpayloadv3 +/// +public class GetPayloadV3Handler : GetPayloadHandlerBase +{ + public GetPayloadV3Handler(IPayloadPreparationService payloadPreparationService, ILogManager logManager) : base( + 3, payloadPreparationService, logManager) + { + } + + protected override GetPayloadV3Result GetPayloadResultFromBlock(IBlockProductionContext context) => + new(context.CurrentBestBlock!, context.BlockFees, new BlobsBundleV1(context.CurrentBestBlock!)); +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs index 659484e9068..0bb839ee47b 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs @@ -26,8 +26,8 @@ namespace Nethermind.Merge.Plugin.Handlers; /// /// Provides an execution payload handler as defined in Engine API -/// -/// Shanghai specification. +/// +/// Shanghai specification. /// public class NewPayloadHandler : IAsyncHandler { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Cancun.cs b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Cancun.cs new file mode 100644 index 00000000000..f05a3824266 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Cancun.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Threading.Tasks; +using Nethermind.JsonRpc; +using Nethermind.JsonRpc.Modules; +using Nethermind.Merge.Plugin.Data; + +namespace Nethermind.Merge.Plugin; + +public partial interface IEngineRpcModule : IRpcModule +{ + [JsonRpcMethod( + Description = "Verifies the payload according to the execution environment rules and returns the verification status and hash of the last valid block.", + IsSharable = true, + IsImplemented = true)] + Task> engine_newPayloadV3(ExecutionPayload executionPayload); + + [JsonRpcMethod( + Description = "Returns the most recent version of an execution payload and fees with respect to the transaction set contained by the mempool.", + IsSharable = true, + IsImplemented = true)] + public Task> engine_getPayloadV3(byte[] payloadId); +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index 159af0d2942..df64702c315 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -309,6 +309,7 @@ public Task InitRpcModules() IEngineRpcModule engineRpcModule = new EngineRpcModule( new GetPayloadV1Handler(payloadPreparationService, _api.LogManager), new GetPayloadV2Handler(payloadPreparationService, _api.LogManager), + new GetPayloadV3Handler(payloadPreparationService, _api.LogManager), new NewPayloadHandler( _api.BlockValidator, _api.BlockTree, diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Metrics.cs b/src/Nethermind/Nethermind.Merge.Plugin/Metrics.cs index 31c641363e0..fcaf7335942 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Metrics.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Metrics.cs @@ -1,12 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Nethermind.Core.Attributes; namespace Nethermind.Merge.Plugin @@ -28,6 +23,5 @@ public static class Metrics [GaugeMetric] [Description("Number of Transactions included in the Last GetPayload Request")] public static int NumberOfTransactionsInGetPayload { get; set; } - } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs b/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs index 6ab7b5ecd2f..2eaba4cb364 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs @@ -46,7 +46,8 @@ public EthereumJsonSerializer(int? maxDepth = null, params JsonConverter[] conve new BigIntegerConverter(), new NullableBigIntegerConverter(), new PublicKeyConverter(), - new TxTypeConverter() + new TxTypeConverter(), + new MemoryByteConverter(), }); public IList BasicConverters { get; } = CommonConverters.ToList(); @@ -66,7 +67,8 @@ public EthereumJsonSerializer(int? maxDepth = null, params JsonConverter[] conve new BigIntegerConverter(NumberConversion.Decimal), new NullableBigIntegerConverter(NumberConversion.Decimal), new PublicKeyConverter(), - new TxTypeConverter() + new TxTypeConverter(), + new MemoryByteConverter(), }; public T Deserialize(Stream stream) diff --git a/src/Nethermind/Nethermind.Serialization.Json/MemoryByteConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/MemoryByteConverter.cs new file mode 100644 index 00000000000..c408db7c3bd --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/MemoryByteConverter.cs @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core.Extensions; +using Newtonsoft.Json; + +namespace Nethermind.Serialization.Json; + +public class MemoryByteConverter : JsonConverter> +{ + public override void WriteJson(JsonWriter writer, Memory value, JsonSerializer serializer) + { + if (value.IsEmpty) + { + writer.WriteNull(); + } + else + { + writer.WriteValue(Bytes.ByteArrayToHexViaLookup32Safe(value, true)); + } + } + + public override Memory ReadJson(JsonReader reader, Type objectType, Memory existingValue, bool hasExistingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + { + return null; + } + + string s = (string)reader.Value; + return Bytes.FromHexString(s); + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs index fb1388f681b..4daec7891d6 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs @@ -73,6 +73,8 @@ public class TxDecoder : DecodeAccessListPayloadWithoutSig(transaction, rlpStream, rlpBehaviors); break; case TxType.EIP1559: + // TODO: Replace with SSZ encoding + case TxType.Blob: DecodeEip1559PayloadWithoutSig(transaction, rlpStream, rlpBehaviors); break; } @@ -254,6 +256,8 @@ private void EncodeEip1559PayloadWithoutPayload(T item, RlpStream stream, RlpBeh DecodeAccessListPayloadWithoutSig(transaction, ref decoderContext, rlpBehaviors); break; case TxType.EIP1559: + // TODO: Replace with SSZ encoding + case TxType.Blob: DecodeEip1559PayloadWithoutSig(transaction, ref decoderContext, rlpBehaviors); break; } @@ -411,6 +415,8 @@ private void EncodeTx(RlpStream stream, T? item, RlpBehaviors rlpBehaviors = Rlp EncodeAccessListPayloadWithoutPayload(item, stream, rlpBehaviors); break; case TxType.EIP1559: + // TODO: Replace with SSZ encoding + case TxType.Blob: EncodeEip1559PayloadWithoutPayload(item, stream, rlpBehaviors); break; } @@ -490,6 +496,8 @@ private int GetContentLength(T item, bool forSigning, bool isEip155Enabled = fal contentLength = GetAccessListContentLength(item); break; case TxType.EIP1559: + // TODO: Replace with SSZ encoding + case TxType.Blob: contentLength = GetEip1559ContentLength(item); break; }