From 193c1c3e67f3ded4e7e6c1c0ea24d0298b8dcc8f Mon Sep 17 00:00:00 2001 From: Ruben Buniatyan <rubo@users.noreply.github.com> Date: Wed, 11 Jan 2023 13:44:56 +0100 Subject: [PATCH] Implement beacon chain push withdrawals (EIP-4895) (#4731) * More test fixes * Fix for init and meter * Make withdrawals decoding optional * include 4895 in chainspecParameters * introduce WithdrawalTimestamp * Fix `PayloadAttributes` handling when null * Fix withdrawals length calculation * Refactor and add checks for withdrawals for RPC methods of v1 * Add engine_getPayloadV2 * Format whitespaces * Refactor and remove redundant "V1" * started adding Enginge V2 tests - V2_processing_block_should_serialize_valid_responses * formatting * formatting * Implement withdrawal root validation * working on tests and fixes * fix block for processing * BlockValidator WithdrawalRoot * Fix withdrawal trie proof validation * add WithdrawalApplier * Remove redundant withdrawals hash check * Remove withdrawal check for null * Add withdrawals test chain spec * Reformat whitespaces * add withdrawalApplier to blockchainProcessor * add withdrawals to ExecutionPayload * test fixes * fix EVM.test * fix EthereumTests * fix Benchmarks.sln * Revise withdrawals representation according to the spec and refactor * Rename `IWithdrawalApplier` to `IWithdrawalProcessor` * Add tests for withdrawal encoding/decoding * Rename `Recipient` to `Address` * Reformat whitespaces * Revise withdrawals length calculation in block encoding * HasBody? * Remove redundant withdrawals hash check * fix withdrawals_test chainspec && added IReleaseSpec.WithdrawalsEnabled * Fixes * fix GenesisLoader * fix extra-data * Refactor withdrawal validation by implementing `IWithdrawalValidator` * Fix failing tests * Reformat whitespaces * fix withdrawalsTimestamp * temporary change validation * null handling? * Applying Marek's suggestion. Not sure about this one * Fix Ethereum tests * Fix test cases * Fix benchmark build * Fix payload attributes validation * Remove "V1" from `IForkchoiceUpdatedHandler` name * Update tests * hack AuRa tests for now * fix more tests * fix more tests * fix build * + fix one more tests * fix Synchronization tests * Update tests to 0aa59689101f64cab8fa1526d9cf6e647ddba946 * fixed withdrawal chainspec * withdrawals block validator tests * Implement Engine API tests * Reformat code * fix chainspec? * Fix withdrawals decoding * fix chainspec timestamp * Add and fix Engine API tests * Revise block decoder tests * Revise file headers * Revise file headers * Add missing file headers * fix AuRa test * adjusting comments * load genesis tests * Lukasz suggestions * formating * Fix build * withdrawals_hivetests.json + cosmetic * Add more unit tests * spacing * Try on fixing timestamp activation with same value as genesis timestamp * Fix gnosis and chiado ForkId Calculations * fix tests * Cleanup and more tests * Janky but working solution to very rare edge case? * Fix flakiness of caused by Parallelizable * adding engine tests * working on more tests * add loop in test * Can_apply_withdrawals_correctly test * more test cases * Update withdrawals hive tests configuration * Refactor and fix some null reference warnings * Expose withdrawals to JSON-RPC modules * Fix broken tests * Revise `ForkchoiceUpdatedHandler` string output * fix Can_apply_withdrawals_correctly * fix whitespaces * fix whitespaces * more whitespaces fixes * work on Withdrawals_transition test cases * adjust CustomSpecProvider * Introduce IEip1995Spec * small changes in tests * Fix license * add comments * Apply Marek Suggestion * Adjust Lic to Rubo * Marek Suggestions * Fix * fix withdrawals in ChainLevelHelper * fix hive sync tests * fix? * Revise withdrawals root hash encoding/decoding and its tests * fix PayloadAttributes ToString * add more temp logs to investigate hive tests * more logs * revert not needed logs * fix test * ignore incorrect tests * Fix tests broken by withdrawals decoding revision * Revise file headers * Final appraoch, removed GetSpec complexity * Forgotten changes * SecondsPerSlot to BlocksConfig * Minor + config changes * Typo fix * remove empty line * Move BlocksConfig to Nethermind.Config project * Benchmark build fix * Rename `ExecutionPayloadV1` to `ExecutionPayloadV2` * Revise withdrawals check * Refactor tests * Optimize withdrawals root hash decoding in block header * Implements ForkId tests that are ub EIP-6122 * Spacing * Fix build * Fix ForkId Test case * Removing 3675 and using MergeForkId Transition * Final test fix * Fix tests * Rename `ExecutionPayloadV2` to `ExecutionPayload` * add one more line in Nlog (temp) * Revert NLog for jsonRpc * fix missing body? * adjust TxPool logs * Add more test cases for `BlockBodiesMessageSerializer` * Add tests for `BlockHeader.HasBody` * Revise `null` handling for `BlockHeader.HasBody` * fix BlockBody empty * cosmetic * fix CI * Revise tests * Update null checks with pattern matching * Refactor block body initialization and add tests * Revise whitespaces * cosmetic * ForkId calculation polish (#5068) * Refactor - mainly ChainSpecBasedSpecProvider * fixes * Better warning message * Simplify sanity check * one more simplification * Revise Clique block production according to withdrawals rules * Add withdrawals to `eth_getBlockByNumber` tests * SecondsPerSlot move to BlocksConfig (#4944) Co-authored-by: MarekM25 <marekm2504@gmail.com> Co-authored-by: lukasz.rozmej <lukasz.rozmej@gmail.com> Resolves https://github.com/NethermindEth/nethermind/issues/4871 * fix build * removed duplicated NSubstitute * Add missing file headers * resolved some review comments * more review comments * more review related changes * fix Benchmarks build * Refactor tests * refactor tests - review comment * cosmetic * Revise suggested block validation messages * Refactor patricia tries * fix InvalidBlockInterceptor for Withdrawals * fix whitespaces * fix tests Co-authored-by: smartprogrammer <smartprogrammer@windowslive.com> Co-authored-by: MarekM25 <marekm2504@gmail.com> Co-authored-by: Lukasz Rozmej <lukasz.rozmej@gmail.com> Co-authored-by: Ahmad Bitar <33181301+smartprogrammer93@users.noreply.github.com> --- src/Nethermind/Chains/shandong.json | 1 + src/Nethermind/Chains/withdrawals_devnet.json | 892 +++++ .../Chains/withdrawals_hivetests.json | 200 ++ src/Nethermind/Chains/withdrawals_test.json | 69 + .../Ethereum.Test.Base/BlockchainTestBase.cs | 1 + ...sts.TestAccountAbstractionRpcBlockchain.cs | 1 + .../Executor/UserOperationTracer.cs | 2 - .../AuraBlockProcessorTests.cs | 2 + .../AuRaContractGasLimitOverrideTests.cs | 4 +- .../Transactions/TxCertifierFilterTests.cs | 4 +- .../Transactions/TxPermissionFilterTest.cs | 2 + .../Validators/ContractBasedValidatorTests.cs | 2 +- .../BlockProcessorTests.cs | 1 + .../GenesisLoaderTests.cs | 101 +- .../Nethermind.Blockchain.Test.csproj | 4 + .../Producers/DevBlockproducerTests.cs | 1 + .../Proofs/WithdrawalTrieTests.cs | 64 + .../Nethermind.Blockchain.Test/ReorgTests.cs | 1 + .../Specs/shanghai_from_genesis.json | 86 + .../Validators/TestBlockValidator.cs | 86 +- .../Validators/WithdrawalValidatorTests.cs | 69 + .../CliqueBlockProducerTests.cs | 1 + .../AuRaBlockProcessor.cs | 5 +- .../InitializeBlockchainAuRa.cs | 4 +- .../StartBlockProducerAuRa.cs | 2 + .../CliqueBlockProducer.cs | 21 +- .../CliquePlugin.cs | 6 +- .../NethDevPlugin.cs | 1 + .../Processing/BlockExtensions.cs | 4 +- .../Processing/BlockProcessor.cs | 12 +- .../Processing/ReadOnlyChainProcessingEnv.cs | 2 +- .../Processing/ReadOnlyTxProcessingEnv.cs | 1 + .../Producers/BlockProducerBase.cs | 2 +- .../Producers/BlockProducerEnvFactory.cs | 5 +- .../Producers/BlockToProduce.cs | 9 +- .../Producers/PayloadAttributes.cs | 48 +- .../Validators/AlwaysValid.cs | 136 +- .../Validators/BlockValidator.cs | 265 +- .../Validators/IBlockValidator.cs | 12 +- .../Validators/IWithdrawalValidator.cs | 32 + .../Validators/NeverValidBlockValidator.cs | 7 + .../BlockProductionWithdrawalProcessor.cs | 30 + .../Withdrawals/IWithdrawalProcessor.cs | 12 + .../Withdrawals/WithdrawalProcessor.cs | 52 + .../Nethermind.Core.Test/BlockHeaderTests.cs | 282 +- .../Nethermind.Core.Test/BlockTests.cs | 23 + .../Blockchain/TestBlockchain.cs | 1 + .../Builders/BlockBuilder.cs | 30 + .../Builders/BlockHeaderBuilder.cs | 282 +- .../Builders/Build.Withdrawal.cs | 9 + .../Nethermind.Core.Test/Builders/TestItem.cs | 8 + .../Builders/WithdrawalBuilder.cs | 39 + .../Encoding/BlockDecoderTests.cs | 142 +- .../Encoding/HeaderDecoderTests.cs | 219 +- .../Encoding/TxDecoderTests.cs | 1 - .../Encoding/WithdrawalDecoderTests.cs | 82 + .../Authentication/JwtAuthentication.cs | 2 +- src/Nethermind/Nethermind.Core/Block.cs | 191 +- src/Nethermind/Nethermind.Core/BlockBody.cs | 30 +- src/Nethermind/Nethermind.Core/BlockHeader.cs | 206 +- .../Nethermind.Core/Specs/IReleaseSpec.cs | 15 +- src/Nethermind/Nethermind.Core/Withdrawal.cs | 41 + .../BlockchainBridgeTests.cs | 2 - .../Nethermind.Facade/BlockchainBridge.cs | 11 +- .../Nethermind.Facade/Filters/LogFinder.cs | 2 +- .../Nethermind.Init/Steps/InitRlp.cs | 1 + .../Steps/InitializeBlockchain.cs | 1 + .../Nethermind.Init/Steps/MigrateConfigs.cs | 2 +- .../EthModuleBenchmarks.cs | 1 + .../JsonRpcServiceTests.cs | 35 + .../Modules/Eth/EthRpcModuleTests.cs | 39 + .../Modules/SubscribeModuleTests.cs | 6 +- .../Modules/Trace/ParityStyleTracerTests.cs | 1 + .../Nethermind.JsonRpc/IJsonRpcConfig.cs | 2 +- .../Nethermind.JsonRpc/JsonRpcConfig.cs | 8 +- .../Modules/Eth/BlockForRpc.cs | 179 +- .../Modules/Eth/EthRpcModule.cs | 2 +- .../AuRaMergePluginTests.cs | 58 +- .../AuRaMergeBlockProcessor.cs | 3 + .../AuRaMergeBlockProducerEnvFactory.cs | 16 +- .../AuRaPostMergeBlockProducer.cs | 3 + .../AuRaPostMergeBlockProducerFactory.cs | 3 + .../InitializeBlockchainAuRaMerge.cs | 2 + .../EngineModuleTest.V2.cs | 93 - .../EngineModuleTests.HelperFunctions.cs | 30 +- .../EngineModuleTests.RelayBuilder.cs | 108 +- .../EngineModuleTests.Setup.cs | 32 +- .../EngineModuleTests.Synchronization.cs | 97 +- .../EngineModuleTests.V1.PayloadProduction.cs | 48 +- .../EngineModuleTests.V1.cs | 3001 ++++++++--------- .../EngineModuleTests.V2.cs | 552 +++ .../ForkChoiceUpdatedRequestTests.cs | 1 - .../InvalidBlockInterceptorTest.cs | 18 + .../Boost/BoostBlockImprovementContext.cs | 6 +- .../Boost/BoostExecutionPayloadV1.cs | 4 +- .../PayloadPreparationService.cs | 4 +- .../BlockProduction/PostMergeBlockProducer.cs | 2 +- .../Data/ExecutionPayload.cs | 146 + .../{V1 => }/ExecutionPayloadBodyV1Result.cs | 2 +- .../Data/ForkchoiceStateV1.cs | 41 + .../{V1 => }/ForkchoiceUpdatedV1Result.cs | 27 +- .../Data/GetPayloadV2Result.cs | 22 + .../Data/{V1 => }/NewPayloadV1Result.cs | 2 +- .../Data/{V1 => }/PayloadStatusV1.cs | 2 +- .../Data/{V1 => }/Statuses.cs | 2 +- .../{V1 => }/TransitionConfigurationV1.cs | 2 +- .../Data/V1/ExecutionPayloadV1.cs | 128 - .../Data/V1/ForkchoiceStateV1.cs | 44 - .../Data/V2/GetPayloadV2Result.cs | 22 - .../EngineRpcModule.cs | 98 +- ...xchangeTransitionConfigurationV1Handler.cs | 4 +- .../Handlers/ForkchoiceUpdatedHandler.cs | 402 +++ .../GetPayloadBodiesByHashV1Handler.cs | 37 + .../GetPayloadBodiesByRangeV1Handler.cs | 5 +- .../Handlers/{V1 => }/GetPayloadV1Handler.cs | 11 +- .../Handlers/{V2 => }/GetPayloadV2Handler.cs | 7 +- .../Handlers/IForkchoiceUpdatedHandler.cs | 14 + .../Handlers/IForkchoiceUpdatedV1Handler.cs | 15 - .../IGetPayloadBodiesByRangeV1Handler.cs | 2 +- .../Handlers/NewPayloadHandler.cs | 460 +++ .../Handlers/V1/ForkchoiceUpdatedV1Handler.cs | 384 --- .../V1/GetPayloadBodiesByHashV1Handler.cs | 39 - .../Handlers/V1/NewPayloadV1Handler.cs | 449 --- .../IEngineRpcModule.cs | 23 +- .../InvalidBlockInterceptor.cs | 36 +- .../Nethermind.Merge.Plugin/MergePlugin.cs | 8 +- .../Synchronization/ChainLevelHelper.cs | 2 +- .../MevRpcModuleTests.TestMevRpcBlockchain.cs | 1 + .../V62/BlockBodiesMessageSerializerTests.cs | 83 +- .../Messages/BlockBodiesMessageSerializer.cs | 22 +- src/Nethermind/Nethermind.Runner/NLog.config | 3 + .../Properties/launchSettings.json | 14 + .../configs/withdrawals_devnet.cfg | 62 + .../configs/withdrawals_hivetests.cfg | 66 + .../BlockDecoder.cs | 80 +- .../HeaderDecoder.cs | 21 +- .../Nethermind.Serialization.Rlp/Rlp.cs | 6 +- .../RlpDecoderExtensions.cs | 4 +- .../Nethermind.Serialization.Rlp/RlpStream.cs | 3 + .../WithdrawalDecoder.cs | 83 + .../ChainSpecBasedSpecProviderTests.cs | 5 +- .../Nethermind.Specs.Test.csproj | 2 +- .../OverridableReleaseSpec.cs | 2 + .../ChainSpecStyle/ChainParameters.cs | 6 +- .../ChainSpecBasedSpecProvider.cs | 3 + .../ChainSpecStyle/ChainSpecLoader.cs | 13 +- .../Json/ChainSpecParamsJson.cs | 3 +- .../Nethermind.Specs/Forks/15_Shanghai.cs | 1 + .../Nethermind.Specs/ReleaseSpec.cs | 2 + .../SystemTransactionReleaseSpec.cs | 2 + .../Nethermind.State/Proofs/PatriciaTrieT.cs | 59 + .../Nethermind.State/Proofs/ReceiptTrie.cs | 71 +- .../Nethermind.State/Proofs/TxTrie.cs | 65 +- .../Nethermind.State/Proofs/WithdrawalTrie.cs | 33 + .../BlockDownloaderTests.cs | 9 +- .../SnapSync/ProgressTrackerTests.cs | 4 +- .../SyncThreadTests.cs | 1 + .../Blocks/BlockDownloadContext.cs | 2 +- .../Filters/AlreadyKnownTxFilter.cs | 8 +- src/Nethermind/Nethermind.TxPool/TxPool.cs | 2 +- 160 files changed, 7271 insertions(+), 4144 deletions(-) create mode 100644 src/Nethermind/Chains/withdrawals_devnet.json create mode 100644 src/Nethermind/Chains/withdrawals_hivetests.json create mode 100644 src/Nethermind/Chains/withdrawals_test.json create mode 100644 src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs create mode 100644 src/Nethermind/Nethermind.Blockchain.Test/Specs/shanghai_from_genesis.json create mode 100644 src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs create mode 100644 src/Nethermind/Nethermind.Consensus/Validators/IWithdrawalValidator.cs create mode 100644 src/Nethermind/Nethermind.Consensus/Withdrawals/BlockProductionWithdrawalProcessor.cs create mode 100644 src/Nethermind/Nethermind.Consensus/Withdrawals/IWithdrawalProcessor.cs create mode 100644 src/Nethermind/Nethermind.Consensus/Withdrawals/WithdrawalProcessor.cs create mode 100644 src/Nethermind/Nethermind.Core.Test/BlockTests.cs create mode 100644 src/Nethermind/Nethermind.Core.Test/Builders/Build.Withdrawal.cs create mode 100644 src/Nethermind/Nethermind.Core.Test/Builders/WithdrawalBuilder.cs create mode 100644 src/Nethermind/Nethermind.Core.Test/Encoding/WithdrawalDecoderTests.cs create mode 100644 src/Nethermind/Nethermind.Core/Withdrawal.cs delete mode 100644 src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTest.V2.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs rename src/Nethermind/Nethermind.Merge.Plugin/Data/{V1 => }/ExecutionPayloadBodyV1Result.cs (93%) create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceStateV1.cs rename src/Nethermind/Nethermind.Merge.Plugin/Data/{V1 => }/ForkchoiceUpdatedV1Result.cs (65%) create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV2Result.cs rename src/Nethermind/Nethermind.Merge.Plugin/Data/{V1 => }/NewPayloadV1Result.cs (96%) rename src/Nethermind/Nethermind.Merge.Plugin/Data/{V1 => }/PayloadStatusV1.cs (97%) rename src/Nethermind/Nethermind.Merge.Plugin/Data/{V1 => }/Statuses.cs (95%) rename src/Nethermind/Nethermind.Merge.Plugin/Data/{V1 => }/TransitionConfigurationV1.cs (95%) delete mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/V1/ExecutionPayloadV1.cs delete mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/V1/ForkchoiceStateV1.cs delete mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/V2/GetPayloadV2Result.cs rename src/Nethermind/Nethermind.Merge.Plugin/Handlers/{V1 => }/ExchangeTransitionConfigurationV1Handler.cs (97%) create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByHashV1Handler.cs rename src/Nethermind/Nethermind.Merge.Plugin/Handlers/{V1 => }/GetPayloadBodiesByRangeV1Handler.cs (92%) rename src/Nethermind/Nethermind.Merge.Plugin/Handlers/{V1 => }/GetPayloadV1Handler.cs (86%) rename src/Nethermind/Nethermind.Merge.Plugin/Handlers/{V2 => }/GetPayloadV2Handler.cs (88%) create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Handlers/IForkchoiceUpdatedHandler.cs delete mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Handlers/IForkchoiceUpdatedV1Handler.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs delete mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/ForkchoiceUpdatedV1Handler.cs delete mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/GetPayloadBodiesByHashV1Handler.cs delete mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/NewPayloadV1Handler.cs create mode 100644 src/Nethermind/Nethermind.Runner/configs/withdrawals_devnet.cfg create mode 100644 src/Nethermind/Nethermind.Runner/configs/withdrawals_hivetests.cfg create mode 100644 src/Nethermind/Nethermind.Serialization.Rlp/WithdrawalDecoder.cs create mode 100644 src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs create mode 100644 src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs diff --git a/src/Nethermind/Chains/shandong.json b/src/Nethermind/Chains/shandong.json index b242a3aaaea..c0815117d48 100644 --- a/src/Nethermind/Chains/shandong.json +++ b/src/Nethermind/Chains/shandong.json @@ -58,6 +58,7 @@ "eip3670TransitionTimestamp": "0x0", "eip3855TransitionTimestamp": "0x0", "eip3860TransitionTimestamp": "0x0", + "eip4895TransitionTimestamp": "0x0", "terminalTotalDifficulty": "0x0" }, "genesis": { diff --git a/src/Nethermind/Chains/withdrawals_devnet.json b/src/Nethermind/Chains/withdrawals_devnet.json new file mode 100644 index 00000000000..a32c2d50d1b --- /dev/null +++ b/src/Nethermind/Chains/withdrawals_devnet.json @@ -0,0 +1,892 @@ +{ + "name": "Testnet", + "engine": { + "Ethash": {} + }, + "params": { + "gasLimitBoundDivisor": "0x400", + "registrar": "0x0000000000000000000000000000000000000000", + "accountStartNonce": "0x0", + "maximumExtraDataSize": "0xffff", + "minGasLimit": "0x1388", + "networkID": "0x1469cd", + "MergeForkIdTransition": "0x0", + "maxCodeSize": "0x6000", + "maxCodeSizeTransition": "0x0", + "eip150Transition": "0x0", + "eip158Transition": "0x0", + "eip160Transition": "0x0", + "eip161abcTransition": "0x0", + "eip161dTransition": "0x0", + "eip155Transition": "0x0", + "eip140Transition": "0x0", + "eip211Transition": "0x0", + "eip214Transition": "0x0", + "eip658Transition": "0x0", + "eip145Transition": "0x0", + "eip1014Transition": "0x0", + "eip1052Transition": "0x0", + "eip1283Transition": "0x0", + "eip1283DisableTransition": "0x0", + "eip152Transition": "0x0", + "eip1108Transition": "0x0", + "eip1344Transition": "0x0", + "eip1884Transition": "0x0", + "eip2028Transition": "0x0", + "eip2200Transition": "0x0", + "eip2565Transition": "0x0", + "eip2929Transition": "0x0", + "eip2930Transition": "0x0", + "eip1559Transition": "0x0", + "eip3198Transition": "0x0", + "eip3529Transition": "0x0", + "eip3541Transition": "0x0", + "eip4895TransitionTimestamp": "0x63a474ec", + "eip3855TransitionTimestamp": "0x63a474ec", + "eip3651TransitionTimestamp": "0x63a474ec", + "eip3860TransitionTimestamp": "0x63a474ec", + "terminalTotalDifficulty": "0x0" + }, + "genesis": { + "seal": { + "ethereum": { + "nonce": "0x1234", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x01", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x63a438b0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "", + "gasLimit": "0x400000" + }, + "accounts": { + "0x0000000000000000000000000000000000000000": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000001": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000002": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000003": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000004": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000005": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000006": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000007": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000008": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000009": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000010": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000011": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000012": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000013": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000014": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000015": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000016": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000017": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000018": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000019": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000020": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000021": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000022": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000023": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000024": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000025": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000026": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000027": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000028": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000029": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000030": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000031": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000032": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000033": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000034": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000035": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000036": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000037": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000038": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000039": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000040": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000041": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000042": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000043": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000044": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000045": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000046": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000047": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000048": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000049": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000050": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000051": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000052": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000053": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000054": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000055": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000056": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000057": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000058": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000059": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000060": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000061": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000062": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000063": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000064": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000065": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000066": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000067": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000068": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000069": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000070": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000071": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000072": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000073": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000074": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000075": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000076": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000077": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000078": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000079": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000080": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000081": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000082": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000083": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000084": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000085": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000086": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000087": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000088": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000089": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000090": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000091": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000092": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000093": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000094": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000095": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000096": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000097": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000098": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000099": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009f": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000aa": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ab": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ac": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ad": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ae": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000af": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ba": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000bb": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000bc": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000bd": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000be": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000bf": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ca": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000cb": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000cc": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000cd": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ce": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000cf": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000da": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000db": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000dc": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000dd": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000de": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000df": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ea": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000eb": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ec": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ed": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ee": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ef": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fa": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fb": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fc": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fd": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fe": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ff": { + "balance": "1" + }, + "0x4242424242424242424242424242424242424242": { + "balance": "0", + "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a26469706673582212201dd26f37a621703009abf16e77e69c93dc50c79db7f6cc37543e3e0e3decdc9764736f6c634300060b0033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000022": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0x0000000000000000000000000000000000000000000000000000000000000023": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0x0000000000000000000000000000000000000000000000000000000000000024": "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + "0x0000000000000000000000000000000000000000000000000000000000000025": "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", + "0x0000000000000000000000000000000000000000000000000000000000000026": "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", + "0x0000000000000000000000000000000000000000000000000000000000000027": "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", + "0x0000000000000000000000000000000000000000000000000000000000000028": "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", + "0x0000000000000000000000000000000000000000000000000000000000000029": "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", + "0x000000000000000000000000000000000000000000000000000000000000002a": "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", + "0x000000000000000000000000000000000000000000000000000000000000002b": "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", + "0x000000000000000000000000000000000000000000000000000000000000002c": "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", + "0x000000000000000000000000000000000000000000000000000000000000002d": "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", + "0x000000000000000000000000000000000000000000000000000000000000002e": "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", + "0x000000000000000000000000000000000000000000000000000000000000002f": "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", + "0x0000000000000000000000000000000000000000000000000000000000000030": "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", + "0x0000000000000000000000000000000000000000000000000000000000000031": "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "0x0000000000000000000000000000000000000000000000000000000000000032": "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "0x0000000000000000000000000000000000000000000000000000000000000034": "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "0x0000000000000000000000000000000000000000000000000000000000000035": "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x0000000000000000000000000000000000000000000000000000000000000036": "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0x0000000000000000000000000000000000000000000000000000000000000037": "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0x0000000000000000000000000000000000000000000000000000000000000038": "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x0000000000000000000000000000000000000000000000000000000000000039": "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x000000000000000000000000000000000000000000000000000000000000003a": "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x000000000000000000000000000000000000000000000000000000000000003b": "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x000000000000000000000000000000000000000000000000000000000000003c": "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x000000000000000000000000000000000000000000000000000000000000003d": "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x000000000000000000000000000000000000000000000000000000000000003e": "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0x000000000000000000000000000000000000000000000000000000000000003f": "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x0000000000000000000000000000000000000000000000000000000000000040": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" + } + }, + "0x7bB4e40fC0Fc4cbA645b63717D996c394388D9b2": { + "balance": "1000000000000000000000000000" + }, + "0x368aA5d08abc4d62F0Ca81C045B41209449Ce1FD": { + "balance": "1000000000000000000000000000" + }, + "0x088f7C908A13417Ef367Cb89097CC11837e526Cb": { + "balance": "1000000000000000000000000000" + }, + "0x5Ef957B844bf9D340099ffeF89765D2826132c9E": { + "balance": "1000000000000000000000000000" + }, + "0x9Ebb4D26aF2a100419ea2a97d9Ff58e54897214B": { + "balance": "1000000000000000000000000000" + }, + "0xF8870c504B1BcF903A1b7B93c6f2c36e7dD306fF": { + "balance": "1000000000000000000000000000" + } + }, + "nodes": ["enode://d0411c2d31fdae12a532a8ab62f63670ca6d9cfdc3d56b9540c5d56ce6e0a63df36521bd824ced629ff4658ad081ac3bb000d4a58de6ac347b308aeb25294151@134.122.93.43:30303", + "enode://7ecb84ee11e49957d588f24f00d85fd65df6d4834c53b335e6aad5deff2d872be63611c8cb89bf0a371c3e5fcb094cb46e0f572c320d3710ce53152a5e4a55aa@178.128.203.61:30303", + "enode://b3d5a13f72271bccacfd9e9d42c19e9bfb2e5e6aec892cb74e99457047b3aa0c5922b301a7f597f7ca9de117c02ac08580256797db671d8b2f4c86d1e1bf6772@46.101.129.227:30303"] +} diff --git a/src/Nethermind/Chains/withdrawals_hivetests.json b/src/Nethermind/Chains/withdrawals_hivetests.json new file mode 100644 index 00000000000..0425dd6e993 --- /dev/null +++ b/src/Nethermind/Chains/withdrawals_hivetests.json @@ -0,0 +1,200 @@ +{ + "version": "1", + "engine": { + "clique": { + "params": { + "period": 1, + "epoch": 30000, + "blockReward": "0x0" + } + } + }, + "params": { + "eip150Transition": "0x0", + "eip160Transition": "0x0", + "eip161abcTransition": "0x0", + "eip161dTransition": "0x0", + "eip155Transition": "0x0", + "maxCodeSizeTransition": "0x0", + "maxCodeSize": 24576, + "maximumExtraDataSize": "0x400", + "eip140Transition": "0x0", + "eip211Transition": "0x0", + "eip214Transition": "0x0", + "eip658Transition": "0x0", + "eip145Transition": "0x0", + "eip1014Transition": "0x0", + "eip1052Transition": "0x0", + "eip1283Transition": "0x0", + "eip1283DisableTransition": "0x0", + "eip152Transition": "0x0", + "eip1108Transition": "0x0", + "eip1344Transition": "0x0", + "eip1884Transition": "0x0", + "eip2028Transition": "0x0", + "eip2200Transition": "0x0", + "eip2565Transition": "0x0", + "eip2718Transition": "0x0", + "eip2929Transition": "0x0", + "eip2930Transition": "0x0", + "eip1559Transition": "0x0", + "eip3238Transition": "0x0", + "eip3529Transition": "0x0", + "eip3541Transition": "0x0", + "eip3198Transition": "0x0", + "eip4895TransitionTimestamp": "0x1235", + "networkID": "0x7", + "chainID": "0x7" + }, + "genesis": { + "seal": { + "ethereum": { + "nonce": "0x0000000000000000" + } + }, + "difficulty": "0x30000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x1234", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000658bdf435d810c91414ec09147daa6db624063790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x2fefd8" + }, + "accounts": { + "0xcf49fda3be353c69b41ed96333cd24302da4556f": { + "balance": "0x123450000000000000000" + }, + "0x0161e041aad467a890839d5b08b138c1e6373072": { + "balance": "0x123450000000000000000" + }, + "0x87da6a8c6e9eff15d703fc2773e32f6af8dbe301": { + "balance": "0x123450000000000000000" + }, + "0xb97de4b8c857e4f6bc354f226dc3249aaee49209": { + "balance": "0x123450000000000000000" + }, + "0xc5065c9eeebe6df2c2284d046bfc906501846c51": { + "balance": "0x123450000000000000000" + }, + "0x0000000000000000000000000000000000000314": { + "balance": "0x0", + "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a223e05d1461006a578063abd1a0cf1461008d578063abfced1d146100d4578063e05c914a14610110578063e6768b451461014c575b610000565b346100005761007761019d565b6040518082815260200191505060405180910390f35b34610000576100be600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101a3565b6040518082815260200191505060405180910390f35b346100005761010e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101ed565b005b346100005761014a600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610236565b005b346100005761017960048080359060200190919080359060200190919080359060200190919050506103c4565b60405180848152602001838152602001828152602001935050505060405180910390f35b60005481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5050565b7f6031a8d62d7c95988fa262657cd92107d90ed96e08d8f867d32f26edfe85502260405180905060405180910390a17f47e2689743f14e97f7dcfa5eec10ba1dff02f83b3d1d4b9c07b206cbbda66450826040518082815260200191505060405180910390a1817fa48a6b249a5084126c3da369fbc9b16827ead8cb5cdc094b717d3f1dcd995e2960405180905060405180910390a27f7890603b316f3509577afd111710f9ebeefa15e12f72347d9dffd0d65ae3bade81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18073ffffffffffffffffffffffffffffffffffffffff167f7efef9ea3f60ddc038e50cccec621f86a0195894dc0520482abf8b5c6b659e4160405180905060405180910390a28181604051808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a05b5050565b6000600060008585859250925092505b935093509390505600a165627a7a72305820aaf842d0d0c35c45622c5263cbb54813d2974d3999c8c38551d7c613ea2bc1170029", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x1234", + "0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9": "0x01" + } + }, + "0x0000000000000000000000000000000000000315": { + "balance": "0x9999999999999999999999999999999", + "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef2769ca1461003e575b610000565b3461000057610078600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061007a565b005b8173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f1935050505015610106578173ffffffffffffffffffffffffffffffffffffffff167f30a3c50752f2552dcc2b93f5b96866280816a986c0c0408cb6778b9fa198288f826040518082815260200191505060405180910390a25b5b50505600a165627a7a72305820637991fabcc8abad4294bf2bb615db78fbec4edff1635a2647d3894e2daf6a610029" + }, + "0x0000000000000000000000000000000000000316": { + "balance": "0x0", + "code": "0x444355" + }, + "0x0000000000000000000000000000000000000317": { + "balance": "0x0", + "code": "0x600160003555" + }, + "0x0000000000000000000000000000000000000001": { + "builtin": { + "name": "ecrecover", + "pricing": { + "linear": { + "base": 3000, + "word": 0 + } + } + } + }, + "0x0000000000000000000000000000000000000002": { + "builtin": { + "name": "sha256", + "pricing": { + "linear": { + "base": 60, + "word": 12 + } + } + } + }, + "0x0000000000000000000000000000000000000003": { + "builtin": { + "name": "ripemd160", + "pricing": { + "linear": { + "base": 600, + "word": 120 + } + } + } + }, + "0x0000000000000000000000000000000000000004": { + "builtin": { + "name": "identity", + "pricing": { + "linear": { + "base": 15, + "word": 3 + } + } + } + }, + "0x0000000000000000000000000000000000000005": { + "builtin": { + "name": "modexp", + "activate_at": "0x0", + "pricing": { + "modexp": { + "divisor": 20 + } + } + } + }, + "0x0000000000000000000000000000000000000006": { + "builtin": { + "name": "alt_bn128_add", + "activate_at": "0x0", + "pricing": { + "linear": { + "base": 500, + "word": 0 + } + } + } + }, + "0x0000000000000000000000000000000000000007": { + "builtin": { + "name": "alt_bn128_mul", + "activate_at": "0x0", + "pricing": { + "linear": { + "base": 40000, + "word": 0 + } + } + } + }, + "0x0000000000000000000000000000000000000008": { + "builtin": { + "name": "alt_bn128_pairing", + "activate_at": "0x0", + "pricing": { + "alt_bn128_pairing": { + "base": 100000, + "pair": 80000 + } + } + } + }, + "0x0000000000000000000000000000000000000009": { + "builtin": { + "name": "blake2_f", + "activate_at": "0x0", + "pricing": { + "blake2_f": { + "gas_per_round": 1 + } + } + } + } + } +} diff --git a/src/Nethermind/Chains/withdrawals_test.json b/src/Nethermind/Chains/withdrawals_test.json new file mode 100644 index 00000000000..752c3a16496 --- /dev/null +++ b/src/Nethermind/Chains/withdrawals_test.json @@ -0,0 +1,69 @@ +{ + "name": "TheMerge_Devnet", + "engine": { + "clique": { + "params": { + "period": 5, + "epoch": 30000 + } + } + }, + "params": { + "gasLimitBoundDivisor": "0x400", + "accountStartNonce": "0x0", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID": 1, + "eip150Transition": "0x0", + "eip155Transition": "0x0", + "eip158Transition": "0x0", + "eip160Transition": "0x0", + "eip161abcTransition": "0x0", + "eip161dTransition": "0x0", + "eip140Transition": "0x0", + "eip211Transition": "0x0", + "eip214Transition": "0x0", + "eip658Transition": "0x0", + "eip145Transition": "0x0", + "eip1014Transition": "0x0", + "eip1052Transition": "0x0", + "eip1283Transition": "0x0", + "eip1283DisableTransition": "0x0", + "eip152Transition": "0x0", + "eip1108Transition": "0x0", + "eip1344Transition": "0x0", + "eip1884Transition": "0x0", + "eip2028Transition": "0x0", + "eip2200Transition": "0x0", + "eip2565Transition": "0x0", + "eip2929Transition": "0x0", + "eip2930Transition": "0x0", + "eip1559Transition": "0x0", + "eip3198Transition": "0x0", + "eip3529Transition": "0x0", + "eip3541Transition": "0x0", + "eip4895TransitionTimestamp": "0x0", + "terminalTotalDifficulty": "0x0" + }, + "genesis": { + "seal": { + "ethereum": { + "nonce": "0x42", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x400000000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData":"0x0000000000000000000000000000000000000000000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit":"0x1C9C380", + "baseFeePerGas":"0x7" + }, + "accounts": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": + { + "balance":"0x6d6172697573766477000000" + } + } +} diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index 6b7c9de1e07..8464b18af5e 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -17,6 +17,7 @@ using Nethermind.Consensus.Processing; using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; diff --git a/src/Nethermind/Nethermind.AccountAbstraction.Test/AccountAbstractionRpcModuleTests.TestAccountAbstractionRpcBlockchain.cs b/src/Nethermind/Nethermind.AccountAbstraction.Test/AccountAbstractionRpcModuleTests.TestAccountAbstractionRpcBlockchain.cs index d2b5a3a0667..d0ddfa41a83 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction.Test/AccountAbstractionRpcModuleTests.TestAccountAbstractionRpcBlockchain.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction.Test/AccountAbstractionRpcModuleTests.TestAccountAbstractionRpcBlockchain.cs @@ -21,6 +21,7 @@ using Nethermind.Consensus.Test; using Nethermind.Consensus.Transactions; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; diff --git a/src/Nethermind/Nethermind.AccountAbstraction/Executor/UserOperationTracer.cs b/src/Nethermind/Nethermind.AccountAbstraction/Executor/UserOperationTracer.cs index 570538d0b49..3cae279f99a 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/Executor/UserOperationTracer.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/Executor/UserOperationTracer.cs @@ -5,8 +5,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using Nethermind.Abi; -using Nethermind.AccountAbstraction.Data; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; diff --git a/src/Nethermind/Nethermind.AuRa.Test/AuraBlockProcessorTests.cs b/src/Nethermind/Nethermind.AuRa.Test/AuraBlockProcessorTests.cs index 289ba6e3dd8..a3cae929a64 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/AuraBlockProcessorTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/AuraBlockProcessorTests.cs @@ -11,6 +11,7 @@ using Nethermind.Consensus.Processing; using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Transactions; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -160,6 +161,7 @@ void Process(AuRaBlockProcessor auRaBlockProcessor, int blockNumber, Keccak stat NullReceiptStorage.Instance, LimboLogs.Instance, Substitute.For<IBlockTree>(), + new WithdrawalProcessor(stateProvider, LimboLogs.Instance), txFilter, contractRewriter: contractRewriter); diff --git a/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs index 872252cca45..279120022d6 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Contract/AuRaContractGasLimitOverrideTests.cs @@ -12,6 +12,7 @@ using Nethermind.Consensus.Processing; using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Logging; using Nethermind.Trie.Pruning; @@ -23,7 +24,7 @@ public class AuRaContractGasLimitOverrideTests { private const int CorrectHeadGasLimit = 100000000; - // TestContract: + // TestContract: // pragma solidity ^0.5.0; // contract TestValidatorSet { // function blockGasLimit() public view returns(uint256) { @@ -99,6 +100,7 @@ protected override BlockProcessor CreateBlockProcessor() ReceiptStorage, LimboLogs.Instance, BlockTree, + new WithdrawalProcessor(State, LogManager), null, GasLimitCalculator as AuRaContractGasLimitOverride); } diff --git a/src/Nethermind/Nethermind.AuRa.Test/Transactions/TxCertifierFilterTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Transactions/TxCertifierFilterTests.cs index f1a77c6315a..3933ac0eb31 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Transactions/TxCertifierFilterTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Transactions/TxCertifierFilterTests.cs @@ -13,6 +13,7 @@ using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Transactions; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; @@ -155,7 +156,8 @@ protected override BlockProcessor CreateBlockProcessor() Storage, ReceiptStorage, LimboLogs.Instance, - BlockTree); + BlockTree, + new WithdrawalProcessor(State, LogManager)); } protected override Task AddBlocksOnStart() => Task.CompletedTask; diff --git a/src/Nethermind/Nethermind.AuRa.Test/Transactions/TxPermissionFilterTest.cs b/src/Nethermind/Nethermind.AuRa.Test/Transactions/TxPermissionFilterTest.cs index f7c7a6ddd33..6faa8b9061c 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Transactions/TxPermissionFilterTest.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Transactions/TxPermissionFilterTest.cs @@ -14,6 +14,7 @@ using Nethermind.Consensus.Processing; using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Core.Crypto; @@ -299,6 +300,7 @@ protected override BlockProcessor CreateBlockProcessor() ReceiptStorage, LimboLogs.Instance, BlockTree, + new WithdrawalProcessor(State, LogManager), PermissionBasedTxFilter); } diff --git a/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs index 9a1d41724b6..ecc9df91f58 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs @@ -510,7 +510,7 @@ public void consecutive_initiate_change_gets_finalized_and_switch_validators(Con _block.Header.Bloom = new Bloom(txReceipts.SelectMany(r => r.Logs).ToArray()); - _blockTree.FindBlock(_block.Header.Hash, Arg.Any<BlockTreeLookupOptions>()).Returns(new Block(_block.Header.Clone(), BlockBody.Empty)); + _blockTree.FindBlock(_block.Header.Hash, Arg.Any<BlockTreeLookupOptions>()).Returns(new Block(_block.Header.Clone())); Action preProcess = () => validator.OnBlockProcessingStart(_block); preProcess.Should().NotThrow<InvalidOperationException>(test.TestName); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs index 3683bd75ab8..270b1eddf26 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs @@ -26,6 +26,7 @@ using FluentAssertions; using Nethermind.Consensus.Processing; using Nethermind.Consensus.Rewards; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core.Test.Blockchain; using Nethermind.Evm.TransactionProcessing; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/GenesisLoaderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/GenesisLoaderTests.cs index 86b5eb9f5b2..c59d37b9252 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/GenesisLoaderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/GenesisLoaderTests.cs @@ -3,9 +3,9 @@ using System.IO; using Nethermind.Core; +using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Db; -using Nethermind.Evm; using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; using Nethermind.Serialization.Json; @@ -16,56 +16,67 @@ using NSubstitute; using NUnit.Framework; -namespace Nethermind.Blockchain.Test +namespace Nethermind.Blockchain.Test; + +[Parallelizable(ParallelScope.All)] +[TestFixture] +public class GenesisLoaderTests { - [Parallelizable(ParallelScope.All)] - [TestFixture] - public class GenesisLoaderTests + [Test] + public void Can_load_genesis_with_emtpy_accounts_and_storage() { - [TestCase] - public void Can_load_genesis_with_emtpy_accounts_and_storage() - { - AssertBlockHash("0x61b2253366eab37849d21ac066b96c9de133b8c58a9a38652deae1dd7ec22e7b", "Specs/empty_accounts_and_storages.json"); - } + AssertBlockHash("0x61b2253366eab37849d21ac066b96c9de133b8c58a9a38652deae1dd7ec22e7b", "Specs/empty_accounts_and_storages.json"); + } - [Test] - public void Can_load_genesis_with_emtpy_accounts_and_code() - { - AssertBlockHash("0xfa3da895e1c2a4d2673f60dd885b867d60fb6d823abaf1e5276a899d7e2feca5", "Specs/empty_accounts_and_codes.json"); - } + [Test] + public void Can_load_genesis_with_emtpy_accounts_and_code() + { + AssertBlockHash("0xfa3da895e1c2a4d2673f60dd885b867d60fb6d823abaf1e5276a899d7e2feca5", "Specs/empty_accounts_and_codes.json"); + } - [Test] - public void Can_load_genesis_with_precompile_that_has_zero_balance() - { - AssertBlockHash("0x62839401df8970ec70785f62e9e9d559b256a9a10b343baf6c064747b094de09", "Specs/hive_zero_balance_test.json"); - } + [Test] + public void Can_load_genesis_with_precompile_that_has_zero_balance() + { + AssertBlockHash("0x62839401df8970ec70785f62e9e9d559b256a9a10b343baf6c064747b094de09", "Specs/hive_zero_balance_test.json"); + } - private void AssertBlockHash(string expectedHash, string chainspecFilePath) - { - string path = Path.Combine(TestContext.CurrentContext.WorkDirectory, chainspecFilePath); - ChainSpec chainSpec = LoadChainSpec(path); - IDb stateDb = new MemDb(); - IDb codeDb = new MemDb(); - TrieStore trieStore = new(stateDb, LimboLogs.Instance); - IStateProvider stateProvider = new StateProvider(trieStore, codeDb, LimboLogs.Instance); - ISpecProvider specProvider = Substitute.For<ISpecProvider>(); - specProvider.GetSpec(Arg.Any<BlockHeader>()).Returns(Berlin.Instance); - specProvider.GetSpec(Arg.Any<ForkActivation>()).Returns(Berlin.Instance); - StorageProvider storageProvider = new(trieStore, stateProvider, LimboLogs.Instance); - ITransactionProcessor transactionProcessor = Substitute.For<ITransactionProcessor>(); - GenesisLoader genesisLoader = new(chainSpec, specProvider, stateProvider, storageProvider, - transactionProcessor); - Block block = genesisLoader.Load(); - Assert.AreEqual(expectedHash, block.Hash!.ToString()); - } + [Test] + public void Can_load_withdrawals_with_empty_root() + { + Block block = GetGenesisBlock("Specs/shanghai_from_genesis.json"); + Assert.AreEqual("0x3c8aeea1d582ff43787ac297e08a25c58ee9609ab49139e64e0f7a8a56ba3a46", block.Hash!.ToString()); + } + private void AssertBlockHash(string expectedHash, string chainspecFilePath) + { + Block block = GetGenesisBlock(chainspecFilePath); + Assert.AreEqual(expectedHash, block.Hash!.ToString()); + } - private static ChainSpec LoadChainSpec(string path) - { - string data = File.ReadAllText(path); - ChainSpecLoader chainSpecLoader = new(new EthereumJsonSerializer()); - ChainSpec chainSpec = chainSpecLoader.Load(data); - return chainSpec; - } + private Block GetGenesisBlock(string chainspecPath) + { + string path = Path.Combine(TestContext.CurrentContext.WorkDirectory, chainspecPath); + ChainSpec chainSpec = LoadChainSpec(path); + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IStateProvider stateProvider = new StateProvider(trieStore, codeDb, LimboLogs.Instance); + ISpecProvider specProvider = Substitute.For<ISpecProvider>(); + specProvider.GetSpec(Arg.Any<BlockHeader>()).Returns(Berlin.Instance); + specProvider.GetSpec(Arg.Any<ForkActivation>()).Returns(Berlin.Instance); + StorageProvider storageProvider = new(trieStore, stateProvider, LimboLogs.Instance); + ITransactionProcessor transactionProcessor = Substitute.For<ITransactionProcessor>(); + GenesisLoader genesisLoader = new(chainSpec, specProvider, stateProvider, storageProvider, + transactionProcessor); + return genesisLoader.Load(); + } + + + private static ChainSpec LoadChainSpec(string path) + { + string data = File.ReadAllText(path); + ChainSpecLoader chainSpecLoader = new(new EthereumJsonSerializer()); + ChainSpec chainSpec = chainSpecLoader.Load(data); + return chainSpec; } } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Nethermind.Blockchain.Test.csproj b/src/Nethermind/Nethermind.Blockchain.Test/Nethermind.Blockchain.Test.csproj index 684eda70a3e..e5b0b1328ff 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Nethermind.Blockchain.Test.csproj +++ b/src/Nethermind/Nethermind.Blockchain.Test/Nethermind.Blockchain.Test.csproj @@ -56,6 +56,10 @@ <Content Include="Specs\hive_zero_balance_test.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> + <None Remove="Specs\shanghai_from_genesis.json" /> + <Content Include="Specs\shanghai_from_genesis.json"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </Content> </ItemGroup> <ItemGroup> <Folder Include="Specs" /> diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs index 6bb5ca49d04..dc5de210942 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs @@ -10,6 +10,7 @@ using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Transactions; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs new file mode 100644 index 00000000000..0212a19168a --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Serialization.Rlp; +using Nethermind.State.Proofs; +using NUnit.Framework; + +namespace Nethermind.Blockchain.Test.Proofs; + +public class WithdrawalTrieTests +{ + [Test] + public void Should_compute_hash_root() + { + var block = Build.A.Block.WithWithdrawals(10).TestObject; + var trie = new WithdrawalTrie(block.Withdrawals!); + + Assert.AreEqual( + "0xf3a83e722a656f6d1813498178b7c9490a7488de8c576144f8bd473c61c3239f", + trie.RootHash.ToString()); + } + + [Test] + public void Should_verify_proof() + { + var count = 10; + var block = Build.A.Block.WithWithdrawals(count).TestObject; + var trie = new WithdrawalTrie(block.Withdrawals!, true); + + for (int i = 0; i < count; i++) + { + Assert.IsTrue(VerifyProof(trie.BuildProof(i), trie.RootHash)); + } + } + + private static bool VerifyProof(byte[][] proof, Keccak root) + { + for (var i = proof.Length - 1; i >= 0; i--) + { + var p = proof[i]; + var hash = Keccak.Compute(p); + + if (i > 0) + { + var hex = p.Length < 32 ? p.ToHexString(false) : hash.ToString(false); + + if (!new Rlp(proof[i - 1]).ToString(false).Contains(hex, StringComparison.Ordinal)) + { + return false; + } + } + else if (hash != root) + { + return false; + } + } + + return true; + } +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs index 5786012b7ec..9f1b12eb063 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs @@ -11,6 +11,7 @@ using Nethermind.Consensus.Processing; using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Specs/shanghai_from_genesis.json b/src/Nethermind/Nethermind.Blockchain.Test/Specs/shanghai_from_genesis.json new file mode 100644 index 00000000000..5ecde9fa224 --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain.Test/Specs/shanghai_from_genesis.json @@ -0,0 +1,86 @@ +{ + "name": "shandong", + "engine": { + "Ethash": { + "params": { + "minimumDifficulty": "0x20000", + "difficultyBoundDivisor": "0x800", + "durationLimit": "0xd", + "blockReward": { + "0x0": "0x1BC16D674EC80000" + }, + "homesteadTransition": "0x0", + "eip100bTransition": "0x0", + "difficultyBombDelays": {} + } + } + }, + "params": { + "gasLimitBoundDivisor": "0x400", + "registrar": "0x0000000000000000000000000000000000000000", + "accountStartNonce": "0x0", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID": "0x00146A2E", + "MergeForkIdTransition": "0x0", + "maxCodeSize": "0x6000", + "maxCodeSizeTransition": "0x0", + "eip150Transition": "0x0", + "eip158Transition": "0x0", + "eip160Transition": "0x0", + "eip161abcTransition": "0x0", + "eip161dTransition": "0x0", + "eip155Transition": "0x0", + "eip140Transition": "0x0", + "eip211Transition": "0x0", + "eip214Transition": "0x0", + "eip658Transition": "0x0", + "eip145Transition": "0x0", + "eip1014Transition": "0x0", + "eip1052Transition": "0x0", + "eip1283Transition": "0x0", + "eip1283DisableTransition": "0x0", + "eip152Transition": "0x0", + "eip1108Transition": "0x0", + "eip1344Transition": "0x0", + "eip1884Transition": "0x0", + "eip2028Transition": "0x0", + "eip2200Transition": "0x0", + "eip2565Transition": "0x0", + "eip2929Transition": "0x0", + "eip2930Transition": "0x0", + "eip1559Transition": "0x0", + "eip3198Transition": "0x0", + "eip3529Transition": "0x0", + "eip3541Transition": "0x0", + "eip3540TransitionTimestamp": "0x0", + "eip3651TransitionTimestamp": "0x0", + "eip3670TransitionTimestamp": "0x0", + "eip3675TransitionTimestamp": "0x0", + "eip3855TransitionTimestamp": "0x0", + "eip3860TransitionTimestamp": "0x0", + "eip4895TransitionTimestamp": "0x0", + "terminalTotalDifficulty": "0x0" + }, + "genesis": { + "seal": { + "ethereum": { + "nonce": "0x1234", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x01", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x63585F88", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "", + "gasLimit": "0x400000" + }, + "accounts": { + "0x0000000000000000000000000000000000000000": { + "balance": "1" + } + }, + "nodes": [ + ] +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TestBlockValidator.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TestBlockValidator.cs index 603510880bd..11b632192f7 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TestBlockValidator.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TestBlockValidator.cs @@ -6,47 +6,53 @@ using Nethermind.Consensus.Validators; using Nethermind.Core; -namespace Nethermind.Blockchain.Test.Validators +namespace Nethermind.Blockchain.Test.Validators; + +public class TestBlockValidator : IBlockValidator { - public class TestBlockValidator : IBlockValidator + public static TestBlockValidator AlwaysValid = new(); + public static TestBlockValidator NeverValid = new(false, false); + private readonly Queue<bool> _processedValidationResults = null!; + private readonly Queue<bool> _suggestedValidationResults = null!; + private readonly bool? _alwaysSameResultForProcessed; + private readonly bool? _alwaysSameResultForSuggested; + + public TestBlockValidator(bool suggestedValidationResult = true, bool processedValidationResult = true) + { + _alwaysSameResultForSuggested = suggestedValidationResult; + _alwaysSameResultForProcessed = processedValidationResult; + } + + public TestBlockValidator(Queue<bool> suggestedValidationResults, Queue<bool> processedValidationResults) + { + _suggestedValidationResults = suggestedValidationResults ?? throw new ArgumentNullException(nameof(suggestedValidationResults)); + _processedValidationResults = processedValidationResults ?? throw new ArgumentNullException(nameof(processedValidationResults)); + } + + public bool Validate(BlockHeader header, BlockHeader? parent, bool isUncle) + { + return _alwaysSameResultForSuggested ?? _suggestedValidationResults.Dequeue(); + } + + public bool Validate(BlockHeader header, bool isUncle) { - public static TestBlockValidator AlwaysValid = new(); - public static TestBlockValidator NeverValid = new(false, false); - private readonly Queue<bool> _processedValidationResults = null!; - private readonly Queue<bool> _suggestedValidationResults = null!; - private readonly bool? _alwaysSameResultForProcessed; - private readonly bool? _alwaysSameResultForSuggested; - - public TestBlockValidator(bool suggestedValidationResult = true, bool processedValidationResult = true) - { - _alwaysSameResultForSuggested = suggestedValidationResult; - _alwaysSameResultForProcessed = processedValidationResult; - } - - public TestBlockValidator(Queue<bool> suggestedValidationResults, Queue<bool> processedValidationResults) - { - _suggestedValidationResults = suggestedValidationResults ?? throw new ArgumentNullException(nameof(suggestedValidationResults)); - _processedValidationResults = processedValidationResults ?? throw new ArgumentNullException(nameof(processedValidationResults)); - } - - public bool Validate(BlockHeader header, BlockHeader? parent, bool isUncle) - { - return _alwaysSameResultForSuggested ?? _suggestedValidationResults.Dequeue(); - } - - public bool Validate(BlockHeader header, bool isUncle) - { - return _alwaysSameResultForSuggested ?? _suggestedValidationResults.Dequeue(); - } - - public bool ValidateSuggestedBlock(Block block) - { - return _alwaysSameResultForSuggested ?? _suggestedValidationResults.Dequeue(); - } - - public bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, Block suggestedBlock) - { - return _alwaysSameResultForProcessed ?? _processedValidationResults.Dequeue(); - } + return _alwaysSameResultForSuggested ?? _suggestedValidationResults.Dequeue(); + } + + public bool ValidateSuggestedBlock(Block block) + { + return _alwaysSameResultForSuggested ?? _suggestedValidationResults.Dequeue(); + } + + public bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, Block suggestedBlock) + { + return _alwaysSameResultForProcessed ?? _processedValidationResults.Dequeue(); + } + + public bool ValidateWithdrawals(Block block, out string? error) + { + error = null; + + return _alwaysSameResultForSuggested ?? _suggestedValidationResults.Dequeue(); } } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs new file mode 100644 index 00000000000..dc510b0eb12 --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Consensus.Validators; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Logging; +using Nethermind.Specs.Forks; +using Nethermind.Specs.Test; +using Nethermind.State.Proofs; +using NUnit.Framework; + +namespace Nethermind.Blockchain.Test.Validators; + +public class WithdrawalValidatorTests +{ + [Test] + public void Not_null_withdrawals_are_invalid_pre_shanghai() + { + ISpecProvider specProvider = new CustomSpecProvider(((ForkActivation)0, London.Instance)); + BlockValidator blockValidator = new(Always.Valid, Always.Valid, Always.Valid, specProvider, LimboLogs.Instance); + bool isValid = blockValidator.ValidateSuggestedBlock(Build.A.Block.WithWithdrawals(new Withdrawal[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalB_2Eth }).TestObject); + Assert.False(isValid); + } + + [Test] + public void Null_withdrawals_are_invalid_post_shanghai() + { + ISpecProvider specProvider = new CustomSpecProvider(((ForkActivation)0, Shanghai.Instance)); + BlockValidator blockValidator = new(Always.Valid, Always.Valid, Always.Valid, specProvider, LimboLogs.Instance); + bool isValid = blockValidator.ValidateSuggestedBlock(Build.A.Block.TestObject); + Assert.False(isValid); + } + + [Test] + public void Withdrawals_with_incorrect_withdrawals_root_are_invalid() + { + ISpecProvider specProvider = new CustomSpecProvider(((ForkActivation)0, Shanghai.Instance)); + BlockValidator blockValidator = new(Always.Valid, Always.Valid, Always.Valid, specProvider, LimboLogs.Instance); + Withdrawal[] withdrawals = { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalB_2Eth }; + Keccak withdrawalRoot = new WithdrawalTrie(withdrawals).RootHash; + bool isValid = blockValidator.ValidateSuggestedBlock(Build.A.Block.WithWithdrawals(withdrawals).WithWithdrawalsRoot(TestItem.KeccakD).TestObject); + Assert.False(isValid); + } + + [Test] + public void Empty_withdrawals_are_valid_post_shanghai() + { + ISpecProvider specProvider = new CustomSpecProvider(((ForkActivation)0, Shanghai.Instance)); + BlockValidator blockValidator = new(Always.Valid, Always.Valid, Always.Valid, specProvider, LimboLogs.Instance); + Withdrawal[] withdrawals = { }; + Keccak withdrawalRoot = new WithdrawalTrie(withdrawals).RootHash; + bool isValid = blockValidator.ValidateSuggestedBlock(Build.A.Block.WithWithdrawals(withdrawals).WithWithdrawalsRoot(withdrawalRoot).TestObject); + Assert.True(isValid); + } + + [Test] + public void Correct_withdrawals_block_post_shanghai() + { + ISpecProvider specProvider = new CustomSpecProvider(((ForkActivation)0, Shanghai.Instance)); + BlockValidator blockValidator = new(Always.Valid, Always.Valid, Always.Valid, specProvider, LimboLogs.Instance); + Withdrawal[] withdrawals = { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalB_2Eth }; + Keccak withdrawalRoot = new WithdrawalTrie(withdrawals).RootHash; + bool isValid = blockValidator.ValidateSuggestedBlock(Build.A.Block.WithWithdrawals(withdrawals).WithWithdrawalsRoot(withdrawalRoot).TestObject); + Assert.True(isValid); + } +} diff --git a/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs b/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs index 33f7c398f89..b92329f7d24 100644 --- a/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs +++ b/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs @@ -17,6 +17,7 @@ using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Transactions; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockProcessor.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockProcessor.cs index ce4b89b63c6..c7647a6d065 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockProcessor.cs @@ -10,6 +10,7 @@ using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Transactions; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Crypto; @@ -42,6 +43,7 @@ public AuRaBlockProcessor( IReceiptStorage receiptStorage, ILogManager logManager, IBlockTree blockTree, + IWithdrawalProcessor withdrawalProcessor, ITxFilter? txFilter = null, AuRaContractGasLimitOverride? gasLimitOverride = null, ContractRewriter? contractRewriter = null) @@ -54,7 +56,8 @@ public AuRaBlockProcessor( storageProvider, receiptStorage, NullWitnessCollector.Instance, - logManager) + logManager, + withdrawalProcessor) { _specProvider = specProvider; _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs index b8b3f37f86f..6ddc0dc1ea7 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/InitializeBlockchainAuRa.cs @@ -20,6 +20,7 @@ using Nethermind.Consensus.Processing; using Nethermind.Consensus.Transactions; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Evm.TransactionProcessing; using Nethermind.Init.Steps; @@ -96,6 +97,7 @@ protected virtual BlockProcessor NewBlockProcessor(AuRaNethermindApi api, ITxFil _api.ReceiptStorage, _api.LogManager, _api.BlockTree, + new WithdrawalProcessor(_api.StateProvider, _api.LogManager), txFilter, GetGasLimitCalculator(), contractRewriter @@ -199,7 +201,7 @@ protected override void InitSealEngine() _auRaStepCalculator = auRaStepCalculator; } - // private IReadOnlyTransactionProcessorSource GetReadOnlyTransactionProcessorSource() => + // private IReadOnlyTransactionProcessorSource GetReadOnlyTransactionProcessorSource() => // _readOnlyTransactionProcessorSource ??= new ReadOnlyTxProcessorSource( // _api.DbProvider, _api.ReadOnlyTrieStore, _api.BlockTree, _api.SpecProvider, _api.LogManager); diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/StartBlockProducerAuRa.cs b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/StartBlockProducerAuRa.cs index e440ddd426e..9d2ab8c10b9 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/StartBlockProducerAuRa.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/StartBlockProducerAuRa.cs @@ -18,6 +18,7 @@ using Nethermind.Consensus.Processing; using Nethermind.Consensus.Producers; using Nethermind.Consensus.Transactions; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Crypto; @@ -158,6 +159,7 @@ private BlockProcessor CreateBlockProcessor(ReadOnlyTxProcessingEnv changeableTx _api.ReceiptStorage, _api.LogManager, changeableTxProcessingEnv.BlockTree, + new WithdrawalProcessor(_api.StateProvider!, _api.LogManager), auRaTxFilter, CreateGasLimitCalculator(constantContractTxProcessingEnv) as AuRaContractGasLimitOverride, contractRewriter) diff --git a/src/Nethermind/Nethermind.Consensus.Clique/CliqueBlockProducer.cs b/src/Nethermind/Nethermind.Consensus.Clique/CliqueBlockProducer.cs index e1b43054e30..98cfbaeea27 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/CliqueBlockProducer.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/CliqueBlockProducer.cs @@ -385,8 +385,13 @@ bool IBlockProducer.IsProducingBlocks(ulong? maxProducingInterval) } } + // Ensure the timestamp has the correct delay + header.Timestamp = Math.Max(parentBlock.Timestamp + _config.BlockPeriod, _timestamper.UnixTime.Seconds); + + var spec = _specProvider.GetSpec(header); + + header.BaseFeePerGas = BaseFeeCalculator.Calculate(parentHeader, spec); // Set the correct difficulty - header.BaseFeePerGas = BaseFeeCalculator.Calculate(parentHeader, _specProvider.GetSpec(header)); header.Difficulty = CalculateDifficulty(snapshot, _sealer.Address); header.TotalDifficulty = parentBlock.TotalDifficulty + header.Difficulty; if (_logger.IsDebug) @@ -414,17 +419,17 @@ bool IBlockProducer.IsProducingBlocks(ulong? maxProducingInterval) // Mix digest is reserved for now, set to empty header.MixHash = Keccak.Zero; - // Ensure the timestamp has the correct delay - header.Timestamp = parentBlock.Timestamp + _config.BlockPeriod; - if (header.Timestamp < _timestamper.UnixTime.Seconds) - { - header.Timestamp = _timestamper.UnixTime.Seconds; - } + header.WithdrawalsRoot = spec.WithdrawalsEnabled ? Keccak.EmptyTreeHash : null; _stateProvider.StateRoot = parentHeader.StateRoot!; IEnumerable<Transaction> selectedTxs = _txSource.GetTransactions(parentBlock.Header, header.GasLimit); - Block block = new BlockToProduce(header, selectedTxs, Array.Empty<BlockHeader>()); + Block block = new BlockToProduce( + header, + selectedTxs, + Array.Empty<BlockHeader>(), + spec.WithdrawalsEnabled ? Enumerable.Empty<Withdrawal>() : null + ); header.TxRoot = new TxTrie(block.Transactions).RootHash; block.Header.Author = _sealer.Address; return block; diff --git a/src/Nethermind/Nethermind.Consensus.Clique/CliquePlugin.cs b/src/Nethermind/Nethermind.Consensus.Clique/CliquePlugin.cs index 404912d4238..751bd138ec4 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/CliquePlugin.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/CliquePlugin.cs @@ -13,6 +13,7 @@ using Nethermind.Consensus.Producers; using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Transactions; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core.Attributes; using Nethermind.Db; using Nethermind.JsonRpc.Modules; @@ -59,7 +60,7 @@ public Task Init(INethermindApi nethermindApi) _snapshotManager, getFromApi.LogManager); - // both Clique and the merge provide no block rewards + // both Clique and the merge provide no block rewards setInApi.RewardCalculatorSource = NoBlockRewards.Instance; setInApi.BlockPreprocessor.AddLast(new AuthorRecoveryStep(_snapshotManager!)); @@ -109,7 +110,8 @@ public Task<IBlockProducer> InitBlockProducer(IBlockProductionTrigger? blockProd producerEnv.StorageProvider, // do not remove transactions from the pool when preprocessing NullReceiptStorage.Instance, NullWitnessCollector.Instance, - getFromApi.LogManager); + getFromApi.LogManager, + new BlockProductionWithdrawalProcessor(new WithdrawalProcessor(producerEnv.StateProvider, getFromApi.LogManager))); IBlockchainProcessor producerChainProcessor = new BlockchainProcessor( readOnlyBlockTree, diff --git a/src/Nethermind/Nethermind.Consensus.Ethash/NethDevPlugin.cs b/src/Nethermind/Nethermind.Consensus.Ethash/NethDevPlugin.cs index 4fb554d2c2f..bee0c1e1ca8 100644 --- a/src/Nethermind/Nethermind.Consensus.Ethash/NethDevPlugin.cs +++ b/src/Nethermind/Nethermind.Consensus.Ethash/NethDevPlugin.cs @@ -12,6 +12,7 @@ using Nethermind.Consensus.Producers; using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Transactions; +using Nethermind.Consensus.Withdrawals; using Nethermind.Db; using Nethermind.Logging; using Nethermind.State; diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockExtensions.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockExtensions.cs index 8678f55589b..e9d9643a978 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockExtensions.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockExtensions.cs @@ -12,8 +12,8 @@ internal static class BlockExtensions { public static Block CreateCopy(this Block block, BlockHeader header) => block is BlockToProduce blockToProduce - ? new BlockToProduce(header, blockToProduce.Transactions, blockToProduce.Uncles) - : new Block(header, block.Transactions, block.Uncles); + ? new BlockToProduce(header, blockToProduce.Transactions, blockToProduce.Uncles, blockToProduce.Withdrawals) + : new Block(header, block.Transactions, block.Uncles, block.Withdrawals); public static IEnumerable<Transaction> GetTransactions(this Block block) => block is BlockToProduce blockToProduce diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs index b7ecf989966..d09093032cd 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs @@ -8,6 +8,7 @@ using Nethermind.Blockchain.Receipts; using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -17,6 +18,7 @@ using Nethermind.Logging; using Nethermind.Specs.Forks; using Nethermind.State; +using Nethermind.State.Proofs; namespace Nethermind.Consensus.Processing { @@ -27,6 +29,7 @@ public partial class BlockProcessor : IBlockProcessor protected readonly IStateProvider _stateProvider; private readonly IReceiptStorage _receiptStorage; private readonly IWitnessCollector _witnessCollector; + private readonly IWithdrawalProcessor _withdrawalProcessor; private readonly IBlockValidator _blockValidator; private readonly IStorageProvider _storageProvider; private readonly IRewardCalculator _rewardCalculator; @@ -49,7 +52,8 @@ public BlockProcessor( IStorageProvider? storageProvider, IReceiptStorage? receiptStorage, IWitnessCollector? witnessCollector, - ILogManager? logManager) + ILogManager? logManager, + IWithdrawalProcessor? withdrawalProcessor = null) { _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); @@ -58,9 +62,11 @@ public BlockProcessor( _storageProvider = storageProvider ?? throw new ArgumentNullException(nameof(storageProvider)); _receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage)); _witnessCollector = witnessCollector ?? throw new ArgumentNullException(nameof(witnessCollector)); + _withdrawalProcessor = withdrawalProcessor ?? new WithdrawalProcessor(stateProvider, logManager); _rewardCalculator = rewardCalculator ?? throw new ArgumentNullException(nameof(rewardCalculator)); _blockTransactionsExecutor = blockTransactionsExecutor ?? throw new ArgumentNullException(nameof(blockTransactionsExecutor)); + _receiptsTracer = new BlockReceiptsTracer(); } @@ -226,6 +232,7 @@ protected virtual TxReceipt[] ProcessBlock( block.Header.ReceiptsRoot = receipts.GetReceiptsRoot(spec, block.ReceiptsRoot); ApplyMinerRewards(block, blockTracer, spec); + _withdrawalProcessor.ProcessWithdrawals(block, spec); _receiptsTracer.EndBlockTrace(); _stateProvider.Commit(spec); @@ -270,7 +277,8 @@ private Block PrepareBlockForProcessing(Block suggestedBlock) AuRaSignature = bh.AuRaSignature, ReceiptsRoot = bh.ReceiptsRoot, BaseFeePerGas = bh.BaseFeePerGas, - IsPostMerge = bh.IsPostMerge + IsPostMerge = bh.IsPostMerge, + WithdrawalsRoot = bh.WithdrawalsRoot }; return suggestedBlock.CreateCopy(headerForProcessing); diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyChainProcessingEnv.cs b/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyChainProcessingEnv.cs index c3eae0c0c55..ca963a9d692 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyChainProcessingEnv.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyChainProcessingEnv.cs @@ -5,7 +5,7 @@ using Nethermind.Blockchain.Receipts; using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Validators; -using Nethermind.Core; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core.Specs; using Nethermind.Db; using Nethermind.Logging; diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyTxProcessingEnv.cs b/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyTxProcessingEnv.cs index caa24eacc89..a6351f7393a 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyTxProcessingEnv.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/ReadOnlyTxProcessingEnv.cs @@ -3,6 +3,7 @@ using System; using Nethermind.Blockchain; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Db; diff --git a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs index 64b12fba0fa..9d679906d06 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs @@ -303,7 +303,7 @@ protected virtual Block PrepareBlock(BlockHeader parent, PayloadAttributes? payl BlockHeader header = PrepareBlockHeader(parent, payloadAttributes); IEnumerable<Transaction> transactions = GetTransactions(parent); - return new BlockToProduce(header, transactions, Array.Empty<BlockHeader>()); + return new BlockToProduce(header, transactions, Array.Empty<BlockHeader>(), payloadAttributes?.Withdrawals); } } } diff --git a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerEnvFactory.cs b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerEnvFactory.cs index 717ff7d035c..e5f60e49974 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerEnvFactory.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerEnvFactory.cs @@ -9,6 +9,7 @@ using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Transactions; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core.Specs; using Nethermind.Db; using Nethermind.Logging; @@ -148,6 +149,8 @@ protected virtual BlockProcessor CreateBlockProcessor(ReadOnlyTxProcessingEnv re readOnlyTxProcessingEnv.StorageProvider, receiptStorage, NullWitnessCollector.Instance, - logManager); + logManager, + new BlockProductionWithdrawalProcessor(new WithdrawalProcessor(readOnlyTxProcessingEnv.StateProvider, logManager))); + } } diff --git a/src/Nethermind/Nethermind.Consensus/Producers/BlockToProduce.cs b/src/Nethermind/Nethermind.Consensus/Producers/BlockToProduce.cs index f261a609307..ab7bb2ffa9a 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/BlockToProduce.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/BlockToProduce.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.CompilerServices; using Nethermind.Core; @@ -29,8 +30,12 @@ internal class BlockToProduce : Block } } - public BlockToProduce(BlockHeader blockHeader, IEnumerable<Transaction> transactions, IEnumerable<BlockHeader> uncles) - : base(blockHeader, Array.Empty<Transaction>(), uncles) + public BlockToProduce( + BlockHeader blockHeader, + IEnumerable<Transaction> transactions, + IEnumerable<BlockHeader> uncles, + IEnumerable<Withdrawal>? withdrawals = null) + : base(blockHeader, Array.Empty<Transaction>(), uncles, withdrawals) { Transactions = transactions; } diff --git a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs index bc8d803547b..ab4a8661e3f 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/PayloadAttributes.cs @@ -1,31 +1,47 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Collections.Generic; +using System.Text; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Int256; -namespace Nethermind.Consensus.Producers +namespace Nethermind.Consensus.Producers; + +public class PayloadAttributes { - public class PayloadAttributes - { - public ulong Timestamp { get; set; } + public ulong Timestamp { get; set; } + + public Keccak PrevRandao { get; set; } + + public Address SuggestedFeeRecipient { get; set; } - public Keccak PrevRandao { get; set; } + public IList<Withdrawal>? Withdrawals { get; set; } - public Address SuggestedFeeRecipient { get; set; } + /// <summary> + /// GasLimit + /// </summary> + /// <remarks> + /// Only used for MEV-Boost + /// </remarks> + public long? GasLimit { get; set; } - /// <summary> - /// GasLimit - /// </summary> - /// <remarks> - /// Only used for MEV-Boost - /// </remarks> - public long? GasLimit { get; set; } + public override string ToString() => ToString(string.Empty); - public override string ToString() + public string ToString(string indentation) + { + var sb = new StringBuilder($"{indentation}{nameof(PayloadAttributes)} {{") + .Append($"{nameof(Timestamp)}: {Timestamp}, ") + .Append($"{nameof(PrevRandao)}: {PrevRandao}, ") + .Append($"{nameof(SuggestedFeeRecipient)}: {SuggestedFeeRecipient}"); + + if (Withdrawals is not null) { - return $"PayloadAttributes: ({nameof(Timestamp)}: {Timestamp}, {nameof(PrevRandao)}: {PrevRandao}, {nameof(SuggestedFeeRecipient)}: {SuggestedFeeRecipient})"; + sb.Append($", {nameof(Withdrawals)} count: {Withdrawals.Count}"); } + + sb.Append('}'); + + return sb.ToString(); } } diff --git a/src/Nethermind/Nethermind.Consensus/Validators/AlwaysValid.cs b/src/Nethermind/Nethermind.Consensus/Validators/AlwaysValid.cs index 97fb02088a0..0dc77b6e60a 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/AlwaysValid.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/AlwaysValid.cs @@ -6,72 +6,78 @@ using Nethermind.Core.Specs; using Nethermind.TxPool; -namespace Nethermind.Consensus.Validators +namespace Nethermind.Consensus.Validators; + +public class Always : IBlockValidator, ISealValidator, IUnclesValidator, ITxValidator { - public class Always : IBlockValidator, ISealValidator, IUnclesValidator, ITxValidator + private readonly bool _result; + + private Always(bool result) + { + _result = result; + } + + // ReSharper disable once NotNullMemberIsNotInitialized + private static Always _valid; + + public static Always Valid + => LazyInitializer.EnsureInitialized(ref _valid, () => new Always(true)); + + // ReSharper disable once NotNullMemberIsNotInitialized + private static Always _invalid; + + public static Always Invalid + => LazyInitializer.EnsureInitialized(ref _invalid, () => new Always(false)); + + public bool ValidateHash(BlockHeader header) { - private readonly bool _result; - - private Always(bool result) - { - _result = result; - } - - // ReSharper disable once NotNullMemberIsNotInitialized - private static Always _valid; - - public static Always Valid - => LazyInitializer.EnsureInitialized(ref _valid, () => new Always(true)); - - // ReSharper disable once NotNullMemberIsNotInitialized - private static Always _invalid; - - public static Always Invalid - => LazyInitializer.EnsureInitialized(ref _invalid, () => new Always(false)); - - public bool ValidateHash(BlockHeader header) - { - return _result; - } - - public bool Validate(BlockHeader header, BlockHeader parent, bool isUncle = false) - { - return _result; - } - - public bool Validate(BlockHeader header, bool isUncle = false) - { - return _result; - } - - public bool ValidateSuggestedBlock(Block block) - { - return _result; - } - - public bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, Block suggestedBlock) - { - return _result; - } - - public bool ValidateParams(BlockHeader parent, BlockHeader header, bool isUncle = false) - { - return _result; - } - - public bool ValidateSeal(BlockHeader header, bool force) - { - return _result; - } - - public bool Validate(BlockHeader header, BlockHeader[] uncles) - { - return _result; - } - - public bool IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) - { - return _result; - } + return _result; + } + + public bool Validate(BlockHeader header, BlockHeader parent, bool isUncle = false) + { + return _result; + } + + public bool Validate(BlockHeader header, bool isUncle = false) + { + return _result; + } + + public bool ValidateSuggestedBlock(Block block) + { + return _result; + } + + public bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, Block suggestedBlock) + { + return _result; + } + + public bool ValidateParams(BlockHeader parent, BlockHeader header, bool isUncle = false) + { + return _result; + } + + public bool ValidateSeal(BlockHeader header, bool force) + { + return _result; + } + + public bool Validate(BlockHeader header, BlockHeader[] uncles) + { + return _result; + } + + public bool IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) + { + return _result; + } + + public bool ValidateWithdrawals(Block block, out string? error) + { + error = null; + + return _result; } } diff --git a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs index 5db93377ee9..1a14417d415 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs @@ -10,150 +10,209 @@ using Nethermind.State.Proofs; using Nethermind.TxPool; -namespace Nethermind.Consensus.Validators +namespace Nethermind.Consensus.Validators; + +public class BlockValidator : IBlockValidator { - public class BlockValidator : IBlockValidator + private readonly IHeaderValidator _headerValidator; + private readonly ITxValidator _txValidator; + private readonly IUnclesValidator _unclesValidator; + private readonly ISpecProvider _specProvider; + private readonly ILogger _logger; + + public BlockValidator( + ITxValidator? txValidator, + IHeaderValidator? headerValidator, + IUnclesValidator? unclesValidator, + ISpecProvider? specProvider, + ILogManager? logManager) + { + _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + _txValidator = txValidator ?? throw new ArgumentNullException(nameof(txValidator)); + _unclesValidator = unclesValidator ?? throw new ArgumentNullException(nameof(unclesValidator)); + _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); + _headerValidator = headerValidator ?? throw new ArgumentNullException(nameof(headerValidator)); + } + + public bool Validate(BlockHeader header, BlockHeader? parent, bool isUncle) + { + return _headerValidator.Validate(header, parent, isUncle); + } + + public bool Validate(BlockHeader header, bool isUncle) + { + return _headerValidator.Validate(header, isUncle); + } + + /// <summary> + /// Suggested block validation runs basic checks that can be executed before going through the expensive EVM processing. + /// </summary> + /// <param name="block">A block to validate</param> + /// <returns> + /// <c>true</c> if the <paramref name="block"/> is valid; otherwise, <c>false</c>. + /// </returns> + public bool ValidateSuggestedBlock(Block block) { - private readonly IHeaderValidator _headerValidator; - private readonly ITxValidator _txValidator; - private readonly IUnclesValidator _unclesValidator; - private readonly ISpecProvider _specProvider; - private readonly ILogger _logger; - - public BlockValidator( - ITxValidator? txValidator, - IHeaderValidator? headerValidator, - IUnclesValidator? unclesValidator, - ISpecProvider? specProvider, - ILogManager? logManager) + Transaction[] txs = block.Transactions; + IReleaseSpec spec = _specProvider.GetSpec(block.Header); + + for (int i = 0; i < txs.Length; i++) { - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - _txValidator = txValidator ?? throw new ArgumentNullException(nameof(txValidator)); - _unclesValidator = unclesValidator ?? throw new ArgumentNullException(nameof(unclesValidator)); - _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); - _headerValidator = headerValidator ?? throw new ArgumentNullException(nameof(headerValidator)); + if (!_txValidator.IsWellFormed(txs[i], spec)) + { + if (_logger.IsDebug) _logger.Debug($"{Invalid(block)} Invalid transaction {txs[i].Hash}"); + return false; + } } - public bool Validate(BlockHeader header, BlockHeader? parent, bool isUncle) + if (spec.MaximumUncleCount < block.Uncles.Length) { - return _headerValidator.Validate(header, parent, isUncle); + _logger.Debug($"{Invalid(block)} Uncle count of {block.Uncles.Length} exceeds the max limit of {spec.MaximumUncleCount}"); + return false; } - public bool Validate(BlockHeader header, bool isUncle) + if (!ValidateUnclesHashMatches(block, out var unclesHash)) { - return _headerValidator.Validate(header, isUncle); + _logger.Debug($"{Invalid(block)} Uncles hash mismatch: expected {block.Header.UnclesHash}, got {unclesHash}"); + return false; } - /// <summary> - /// Suggested block validation runs basic checks that can be executed before going through the expensive EVM processing. - /// </summary> - /// <param name="block">A block to validate</param> - /// <returns><value>True</value> if the <paramref name="block"/> is valid, otherwise <value>False</value></returns> - public bool ValidateSuggestedBlock(Block block) + if (!_unclesValidator.Validate(block.Header, block.Uncles)) { - Transaction[] txs = block.Transactions; - IReleaseSpec spec = _specProvider.GetSpec(block.Header); + _logger.Debug($"{Invalid(block)} Invalid uncles"); + return false; + } - for (int i = 0; i < txs.Length; i++) - { - if (!_txValidator.IsWellFormed(txs[i], spec)) - { - if (_logger.IsDebug) _logger.Debug($"Invalid block ({block.ToString(Block.Format.FullHashAndNumber)}) - invalid transaction ({txs[i].Hash})"); - return false; - } - } + bool blockHeaderValid = _headerValidator.Validate(block.Header); + if (!blockHeaderValid) + { + if (_logger.IsDebug) _logger.Debug($"{Invalid(block)} Invalid header"); + return false; + } - if (spec.MaximumUncleCount < block.Uncles.Length) - { - _logger.Debug($"Invalid block ({block.ToString(Block.Format.FullHashAndNumber)}) - uncle count is {block.Uncles.Length} (MAX: {spec.MaximumUncleCount})"); - return false; - } + if (!ValidateTxRootMatchesTxs(block, out Keccak txRoot)) + { + if (_logger.IsDebug) _logger.Debug($"{Invalid(block)} Transaction root hash mismatch: expected {block.Header.TxRoot}, got {txRoot}"); + return false; + } + + if (!ValidateWithdrawals(block, spec, out _)) + return false; + + return true; + } - if (!ValidateUnclesHashMatches(block)) + /// <summary> + /// Processed block validation is comparing the block hashes (which include all other results). + /// We only make exact checks on what is invalid if the hash is different. + /// </summary> + /// <param name="processedBlock">This should be the block processing result (after going through the EVM processing)</param> + /// <param name="receipts">List of tx receipts from the processed block (required only for better diagnostics when the receipt root is invalid).</param> + /// <param name="suggestedBlock">Block received from the network - unchanged.</param> + /// <returns><c>true</c> if the <paramref name="processedBlock"/> is valid; otherwise, <c>false</c>.</returns> + public bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, Block suggestedBlock) + { + bool isValid = processedBlock.Header.Hash == suggestedBlock.Header.Hash; + if (!isValid) + { + if (_logger.IsError) _logger.Error($"Processed block {processedBlock.ToString(Block.Format.Short)} is invalid:"); + if (_logger.IsError) _logger.Error($"- hash: expected {suggestedBlock.Hash}, got {processedBlock.Hash}"); + + if (processedBlock.Header.GasUsed != suggestedBlock.Header.GasUsed) { - _logger.Debug($"Invalid block ({block.ToString(Block.Format.FullHashAndNumber)}) - invalid uncles hash"); - return false; + if (_logger.IsError) _logger.Error($"- gas used: expected {suggestedBlock.Header.GasUsed}, got {processedBlock.Header.GasUsed} (diff: {processedBlock.Header.GasUsed - suggestedBlock.Header.GasUsed})"); } - if (!_unclesValidator.Validate(block.Header, block.Uncles)) + if (processedBlock.Header.Bloom != suggestedBlock.Header.Bloom) { - _logger.Debug($"Invalid block ({block.ToString(Block.Format.FullHashAndNumber)}) - invalid uncles"); - return false; + if (_logger.IsError) _logger.Error($"- bloom: expected {suggestedBlock.Header.Bloom}, got {processedBlock.Header.Bloom}"); } - bool blockHeaderValid = _headerValidator.Validate(block.Header); - if (!blockHeaderValid) + if (processedBlock.Header.ReceiptsRoot != suggestedBlock.Header.ReceiptsRoot) { - if (_logger.IsDebug) _logger.Debug($"Invalid block ({block.ToString(Block.Format.FullHashAndNumber)}) - invalid header"); - return false; + if (_logger.IsError) _logger.Error($"- receipts root: expected {suggestedBlock.Header.ReceiptsRoot}, got {processedBlock.Header.ReceiptsRoot}"); } - if (!ValidateTxRootMatchesTxs(block, out Keccak txRoot)) + if (processedBlock.Header.StateRoot != suggestedBlock.Header.StateRoot) { - if (_logger.IsDebug) _logger.Debug($"Invalid block ({block.ToString(Block.Format.FullHashAndNumber)}) tx root {txRoot} != stated tx root {block.Header.TxRoot}"); - return false; + if (_logger.IsError) _logger.Error($"- state root: expected {suggestedBlock.Header.StateRoot}, got {processedBlock.Header.StateRoot}"); } - return true; - } - - /// <summary> - /// Processed block validation is comparing the block hashes (which include all other results). - /// We only make exact checks on what is invalid if the hash is different. - /// </summary> - /// <param name="processedBlock">This should be the block processing result (after going through the EVM processing)</param> - /// <param name="receipts">List of tx receipts from the processed block (required only for better diagnostics when the receipt root is invalid).</param> - /// <param name="suggestedBlock">Block received from the network - unchanged.</param> - /// <returns><value>True</value> if the <paramref name="processedBlock"/> is valid, otherwise <value>False</value></returns> - public bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, Block suggestedBlock) - { - bool isValid = processedBlock.Header.Hash == suggestedBlock.Header.Hash; - if (!isValid) + for (int i = 0; i < processedBlock.Transactions.Length; i++) { - if (_logger.IsError) _logger.Error($"Processed block {processedBlock.ToString(Block.Format.Short)} is not valid"); - if (_logger.IsError) _logger.Error($" hash {processedBlock.Hash} != stated hash {suggestedBlock.Hash}"); - - if (processedBlock.Header.GasUsed != suggestedBlock.Header.GasUsed) + if (receipts[i].Error is not null && receipts[i].GasUsed == 0 && receipts[i].Error == "invalid") { - if (_logger.IsError) _logger.Error($" gas used {processedBlock.Header.GasUsed} != stated gas used {suggestedBlock.Header.GasUsed} ({processedBlock.Header.GasUsed - suggestedBlock.Header.GasUsed} difference)"); + if (_logger.IsError) _logger.Error($"- invalid transaction {i}"); } + } + } - if (processedBlock.Header.Bloom != suggestedBlock.Header.Bloom) - { - if (_logger.IsError) _logger.Error($" bloom {processedBlock.Header.Bloom} != stated bloom {suggestedBlock.Header.Bloom}"); - } + return isValid; + } - if (processedBlock.Header.ReceiptsRoot != suggestedBlock.Header.ReceiptsRoot) - { - if (_logger.IsError) _logger.Error($" receipts root {processedBlock.Header.ReceiptsRoot} != stated receipts root {suggestedBlock.Header.ReceiptsRoot}"); - } + public bool ValidateWithdrawals(Block block, out string? error) => + ValidateWithdrawals(block, _specProvider.GetSpec(block.Header), out error); - if (processedBlock.Header.StateRoot != suggestedBlock.Header.StateRoot) - { - if (_logger.IsError) _logger.Error($" state root {processedBlock.Header.StateRoot} != stated state root {suggestedBlock.Header.StateRoot}"); - } + private bool ValidateWithdrawals(Block block, IReleaseSpec spec, out string? error) + { + if (spec.WithdrawalsEnabled && block.Withdrawals is null) + { + error = $"Withdrawals cannot be null in block {block.Hash} when EIP-4895 activated."; - for (int i = 0; i < processedBlock.Transactions.Length; i++) - { - if (receipts[i].Error is not null && receipts[i].GasUsed == 0 && receipts[i].Error == "invalid") - { - if (_logger.IsError) _logger.Error($" invalid transaction {i}"); - } - } - } + if (_logger.IsWarn) _logger.Warn(error); - return isValid; + return false; } - public static bool ValidateTxRootMatchesTxs(Block block, out Keccak txRoot) + if (!spec.WithdrawalsEnabled && block.Withdrawals is not null) { - txRoot = new TxTrie(block.Transactions).RootHash; - return txRoot == block.Header.TxRoot; + error = $"Withdrawals must be null in block {block.Hash} when EIP-4895 not activated."; + + if (_logger.IsWarn) _logger.Warn(error); + + return false; } - public static bool ValidateUnclesHashMatches(Block block) + if (block.Withdrawals is not null) { - return block.Header.UnclesHash == UnclesHash.Calculate(block); + if (!ValidateWithdrawalsHashMatches(block, out Keccak withdrawalsRoot)) + { + error = $"Withdrawals root hash mismatch in block {block.ToString(Block.Format.FullHashAndNumber)}: expected {block.Header.WithdrawalsRoot}, got {withdrawalsRoot}"; + if (_logger.IsWarn) _logger.Warn($"Withdrawals root hash mismatch in block {block.ToString(Block.Format.FullHashAndNumber)}: expected {block.Header.WithdrawalsRoot}, got {withdrawalsRoot}"); + + return false; + } } + + error = null; + + return true; + } + + public static bool ValidateTxRootMatchesTxs(Block block, out Keccak txRoot) + { + txRoot = new TxTrie(block.Transactions).RootHash; + return txRoot == block.Header.TxRoot; + } + + public static bool ValidateUnclesHashMatches(Block block, out Keccak unclesHash) + { + unclesHash = UnclesHash.Calculate(block); + + return block.Header.UnclesHash == unclesHash; } + + public static bool ValidateWithdrawalsHashMatches(Block block, out Keccak? withdrawalsRoot) + { + withdrawalsRoot = null; + if (block.Withdrawals == null) + return block.Header.WithdrawalsRoot == null; + + withdrawalsRoot = new WithdrawalTrie(block.Withdrawals).RootHash; + + return block.Header.WithdrawalsRoot == withdrawalsRoot; + } + + private static string Invalid(Block block) => + $"Invalid block {block.ToString(Block.Format.FullHashAndNumber)}:"; } diff --git a/src/Nethermind/Nethermind.Consensus/Validators/IBlockValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/IBlockValidator.cs index 4f4e521f6a2..3aea6301417 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/IBlockValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/IBlockValidator.cs @@ -3,11 +3,11 @@ using Nethermind.Core; -namespace Nethermind.Consensus.Validators +namespace Nethermind.Consensus.Validators; + +public interface IBlockValidator : IHeaderValidator, IWithdrawalValidator { - public interface IBlockValidator : IHeaderValidator - { - bool ValidateSuggestedBlock(Block block); - bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, Block suggestedBlock); - } + bool ValidateSuggestedBlock(Block block); + + bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, Block suggestedBlock); } diff --git a/src/Nethermind/Nethermind.Consensus/Validators/IWithdrawalValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/IWithdrawalValidator.cs new file mode 100644 index 00000000000..3e5821e16e6 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Validators/IWithdrawalValidator.cs @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Consensus.Validators; + +public interface IWithdrawalValidator +{ + /// <summary> + /// Validates the block specified for withdrawals against + /// the <see href="https://eips.ethereum.org/EIPS/eip-4895">EIP-4895</see>. + /// </summary> + /// <param name="block">The block to validate.</param> + /// <returns> + /// <c>true</c> if <see cref="Block.Withdrawals"/> are not <c>null</c> when EIP-4895 is activated; + /// otherwise, <c>false</c>. + /// </returns> + bool ValidateWithdrawals(Block block) => ValidateWithdrawals(block, out _); + + /// <summary> + /// Validates the block specified for withdrawals against + /// the <see href="https://eips.ethereum.org/EIPS/eip-4895">EIP-4895</see>. + /// </summary> + /// <param name="block">The block to validate.</param> + /// <param name="error">The validation error message if any.</param> + /// <returns> + /// <c>true</c> if <see cref="Block.Withdrawals"/> are not <c>null</c> when EIP-4895 is activated; + /// otherwise, <c>false</c>. + /// </returns> + bool ValidateWithdrawals(Block block, out string? error); +} diff --git a/src/Nethermind/Nethermind.Consensus/Validators/NeverValidBlockValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/NeverValidBlockValidator.cs index c8a8355e5e8..649c8f7885b 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/NeverValidBlockValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/NeverValidBlockValidator.cs @@ -31,5 +31,12 @@ public bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, B { return false; } + + public bool ValidateWithdrawals(Block block, out string? error) + { + error = null; + + return false; + } } } diff --git a/src/Nethermind/Nethermind.Consensus/Withdrawals/BlockProductionWithdrawalProcessor.cs b/src/Nethermind/Nethermind.Consensus/Withdrawals/BlockProductionWithdrawalProcessor.cs new file mode 100644 index 00000000000..c03f0702002 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Withdrawals/BlockProductionWithdrawalProcessor.cs @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.State.Proofs; + +namespace Nethermind.Consensus.Withdrawals; + +public class BlockProductionWithdrawalProcessor : IWithdrawalProcessor +{ + private readonly IWithdrawalProcessor _processor; + + public BlockProductionWithdrawalProcessor(IWithdrawalProcessor processor) => + _processor = processor ?? throw new ArgumentNullException(nameof(processor)); + + public void ProcessWithdrawals(Block block, IReleaseSpec spec) + { + _processor.ProcessWithdrawals(block, spec); + + if (spec.WithdrawalsEnabled) + { + block.Header.WithdrawalsRoot = block.Withdrawals is null || block.Withdrawals.Length == 0 + ? Keccak.EmptyTreeHash + : new WithdrawalTrie(block.Withdrawals!).RootHash; + } + } +} diff --git a/src/Nethermind/Nethermind.Consensus/Withdrawals/IWithdrawalProcessor.cs b/src/Nethermind/Nethermind.Consensus/Withdrawals/IWithdrawalProcessor.cs new file mode 100644 index 00000000000..5909ba1f220 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Withdrawals/IWithdrawalProcessor.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Specs; + +namespace Nethermind.Consensus.Withdrawals; + +public interface IWithdrawalProcessor +{ + void ProcessWithdrawals(Block block, IReleaseSpec spec); +} diff --git a/src/Nethermind/Nethermind.Consensus/Withdrawals/WithdrawalProcessor.cs b/src/Nethermind/Nethermind.Consensus/Withdrawals/WithdrawalProcessor.cs new file mode 100644 index 00000000000..190ef8a9111 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Withdrawals/WithdrawalProcessor.cs @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Numerics; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Logging; +using Nethermind.State; + +namespace Nethermind.Consensus.Withdrawals; + +public class WithdrawalProcessor : IWithdrawalProcessor +{ + private readonly ILogger _logger; + private readonly IStateProvider _stateProvider; + + public WithdrawalProcessor(IStateProvider stateProvider, ILogManager logManager) + { + ArgumentNullException.ThrowIfNull(logManager); + + _logger = logManager.GetClassLogger(); + _stateProvider = stateProvider ?? throw new ArgumentNullException(nameof(stateProvider)); + } + + public void ProcessWithdrawals(Block block, IReleaseSpec spec) + { + if (!spec.WithdrawalsEnabled) + return; + + if (_logger.IsTrace) _logger.Trace($"Applying withdrawals for block {block}"); + + if (block.Withdrawals != null) + { + foreach (var withdrawal in block.Withdrawals) + { + if (_logger.IsTrace) _logger.Trace($" {(BigInteger)withdrawal.Amount / (BigInteger)Unit.Ether:N3}{Unit.EthSymbol} to account {withdrawal.Address}"); + + if (_stateProvider.AccountExists(withdrawal.Address)) + { + _stateProvider.AddToBalance(withdrawal.Address, withdrawal.Amount, spec); + } + else + { + _stateProvider.CreateAccount(withdrawal.Address, withdrawal.Amount); + } + } + } + + if (_logger.IsTrace) _logger.Trace($"Withdrawals applied for block {block}"); + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/BlockHeaderTests.cs b/src/Nethermind/Nethermind.Core.Test/BlockHeaderTests.cs index 2f55d79604b..e7333b83e87 100644 --- a/src/Nethermind/Nethermind.Core.Test/BlockHeaderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/BlockHeaderTests.cs @@ -3,11 +3,9 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; -using Nethermind.Consensus; +using FluentAssertions; using Nethermind.Core.Crypto; -using Nethermind.Core.Eip2930; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; @@ -17,141 +15,183 @@ using NSubstitute; using NUnit.Framework; -namespace Nethermind.Core.Test +namespace Nethermind.Core.Test; + +[TestFixture] +public class BlockHeaderTests { - [TestFixture] - public class BlockHeaderTests + [Test] + public void Hash_as_expected() { - [Test] - public void Hash_as_expected() + BlockHeader header = new() { - BlockHeader header = new(); - header.Bloom = new Bloom(Bytes.FromHexString("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")); - header.Beneficiary = new Address("0x8888f1f195afa192cfee860698584c030f4c9db1"); - header.Difficulty = Bytes.FromHexString("0x020000").ToUInt256(); - header.ExtraData = Array.Empty<byte>(); - header.GasLimit = (long)Bytes.FromHexString("0x2fefba").ToUnsignedBigInteger(); - header.GasUsed = (long)Bytes.FromHexString("0x5208").ToUnsignedBigInteger(); - header.MixHash = new Keccak(Bytes.FromHexString("0x00be1f287e0911ea2f070b3650a1a0346535895b6c919d7e992a0c255a83fc8b")); - header.Nonce = (ulong)Bytes.FromHexString("0xa0ddc06c6d7b9f48").ToUnsignedBigInteger(); - header.Number = (long)Bytes.FromHexString("0x01").ToUInt256(); - header.ParentHash = new Keccak(Bytes.FromHexString("0x5a39ed1020c04d4d84539975b893a4e7c53eab6c2965db8bc3468093a31bc5ae")); - header.ReceiptsRoot = new Keccak(Bytes.FromHexString("0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2")); - header.StateRoot = new Keccak(Bytes.FromHexString("0x5c2e5a51a79da58791cdfe572bcfa3dfe9c860bf7fad7d9738a1aace56ef9332")); - header.Timestamp = (ulong)Bytes.FromHexString("0x59d79f18").ToUnsignedBigInteger(); - header.TxRoot = new Keccak(Bytes.FromHexString("0x5c9151c2413d1cd25c51ffb4ac38948acc1359bf08c6b49f283660e9bcf0f516")); - header.UnclesHash = new Keccak(Bytes.FromHexString("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")); - - Assert.AreEqual(new Keccak(Bytes.FromHexString("0x19a24085f6b1fb174aee0463264cc7163a7ffa165af04d3f40431ab3c3b08b98")), header.CalculateHash()); - } + Bloom = new Bloom(Bytes.FromHexString("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")), + Beneficiary = new Address("0x8888f1f195afa192cfee860698584c030f4c9db1"), + Difficulty = Bytes.FromHexString("0x020000").ToUInt256(), + ExtraData = Array.Empty<byte>(), + GasLimit = (long)Bytes.FromHexString("0x2fefba").ToUnsignedBigInteger(), + GasUsed = (long)Bytes.FromHexString("0x5208").ToUnsignedBigInteger(), + MixHash = new Keccak(Bytes.FromHexString("0x00be1f287e0911ea2f070b3650a1a0346535895b6c919d7e992a0c255a83fc8b")), + Nonce = (ulong)Bytes.FromHexString("0xa0ddc06c6d7b9f48").ToUnsignedBigInteger(), + Number = (long)Bytes.FromHexString("0x01").ToUInt256(), + ParentHash = new Keccak(Bytes.FromHexString("0x5a39ed1020c04d4d84539975b893a4e7c53eab6c2965db8bc3468093a31bc5ae")), + ReceiptsRoot = new Keccak(Bytes.FromHexString("0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2")), + StateRoot = new Keccak(Bytes.FromHexString("0x5c2e5a51a79da58791cdfe572bcfa3dfe9c860bf7fad7d9738a1aace56ef9332")), + Timestamp = (ulong)Bytes.FromHexString("0x59d79f18").ToUnsignedBigInteger(), + TxRoot = new Keccak(Bytes.FromHexString("0x5c9151c2413d1cd25c51ffb4ac38948acc1359bf08c6b49f283660e9bcf0f516")), + UnclesHash = new Keccak(Bytes.FromHexString("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")) + }; + + Assert.AreEqual(new Keccak(Bytes.FromHexString("0x19a24085f6b1fb174aee0463264cc7163a7ffa165af04d3f40431ab3c3b08b98")), header.CalculateHash()); + } - [Test] - public void Hash_as_expected_2() + [Test] + public void Hash_as_expected_2() + { + BlockHeader header = new() { - BlockHeader header = new(); - header.Bloom = new Bloom( - Bytes.FromHexString("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")); - header.Beneficiary = new Address("0x8888f1f195afa192cfee860698584c030f4c9db1"); - header.Difficulty = Bytes.FromHexString("0x020080").ToUInt256(); - header.ExtraData = Array.Empty<byte>(); - header.GasLimit = (long)Bytes.FromHexString("0x2fefba").ToUnsignedBigInteger(); - header.GasUsed = (long)Bytes.FromHexString("0x5208").ToUnsignedBigInteger(); - header.MixHash = new Keccak(Bytes.FromHexString("0x615bbf44eb133eab3cb24d5766ae9617d9e45ee00e7a5667db30672b47d22149")); - header.Nonce = (ulong)Bytes.FromHexString("0x4c4f3d3e055cb264").ToUnsignedBigInteger(); - header.Number = (long)Bytes.FromHexString("0x03").ToUInt256(); - header.ParentHash = new Keccak(Bytes.FromHexString("0xde1457da701ef916533750d46c124e9ae50b974410bd590fbcf4c935a4d19465")); - header.ReceiptsRoot = new Keccak(Bytes.FromHexString("0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2")); - header.StateRoot = new Keccak(Bytes.FromHexString("0xfb4084a7f8b57e370fefe24a3da3aaea6c4dd8b6f6251916c32440336035160b")); - header.Timestamp = (ulong)Bytes.FromHexString("0x59d79f1c").ToUnsignedBigInteger(); - header.TxRoot = new Keccak(Bytes.FromHexString("0x1722b8a91bfc4f5614ce36ee77c7cce6620ab4af36d3c54baa66d7dbeb7bce1a")); - header.UnclesHash = new Keccak(Bytes.FromHexString("0xe676a42c388d2d24bb2927605d5d5d82fba50fb60d74d44b1cd7d1c4e4eee3c0")); - header.Hash = header.CalculateHash(); - - Assert.AreEqual(new Keccak(Bytes.FromHexString("0x1423c2875714c31049cacfea8450f66a73ecbd61d7a6ab13089406a491aa9fc2")), header.Hash); - } + Bloom = new Bloom( + Bytes.FromHexString("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")), + Beneficiary = new Address("0x8888f1f195afa192cfee860698584c030f4c9db1"), + Difficulty = Bytes.FromHexString("0x020080").ToUInt256(), + ExtraData = Array.Empty<byte>(), + GasLimit = (long)Bytes.FromHexString("0x2fefba").ToUnsignedBigInteger(), + GasUsed = (long)Bytes.FromHexString("0x5208").ToUnsignedBigInteger(), + MixHash = new Keccak(Bytes.FromHexString("0x615bbf44eb133eab3cb24d5766ae9617d9e45ee00e7a5667db30672b47d22149")), + Nonce = (ulong)Bytes.FromHexString("0x4c4f3d3e055cb264").ToUnsignedBigInteger(), + Number = (long)Bytes.FromHexString("0x03").ToUInt256(), + ParentHash = new Keccak(Bytes.FromHexString("0xde1457da701ef916533750d46c124e9ae50b974410bd590fbcf4c935a4d19465")), + ReceiptsRoot = new Keccak(Bytes.FromHexString("0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2")), + StateRoot = new Keccak(Bytes.FromHexString("0xfb4084a7f8b57e370fefe24a3da3aaea6c4dd8b6f6251916c32440336035160b")), + Timestamp = (ulong)Bytes.FromHexString("0x59d79f1c").ToUnsignedBigInteger(), + TxRoot = new Keccak(Bytes.FromHexString("0x1722b8a91bfc4f5614ce36ee77c7cce6620ab4af36d3c54baa66d7dbeb7bce1a")), + UnclesHash = new Keccak(Bytes.FromHexString("0xe676a42c388d2d24bb2927605d5d5d82fba50fb60d74d44b1cd7d1c4e4eee3c0")) + }; + header.Hash = header.CalculateHash(); + + Assert.AreEqual(new Keccak(Bytes.FromHexString("0x1423c2875714c31049cacfea8450f66a73ecbd61d7a6ab13089406a491aa9fc2")), header.Hash); + } + + [Test] + public void Author() + { + Address author = new("0x05a56e2d52c817161883f50c441c3228cfe54d9f"); - [Test] - public void Author() + BlockHeader header = new() { - Address author = new("0x05a56e2d52c817161883f50c441c3228cfe54d9f"); + Beneficiary = author + }; - BlockHeader header = new(); - header.Beneficiary = author; + Assert.AreEqual(author, header.GasBeneficiary); + } - Assert.AreEqual(author, header.GasBeneficiary); - } + [Test] + public void Eip_1559_CalculateBaseFee_should_returns_zero_when_eip1559_not_enabled() + { + IReleaseSpec releaseSpec = Substitute.For<IReleaseSpec>(); + releaseSpec.IsEip1559Enabled.Returns(false); + + BlockHeader blockHeader = Build.A.BlockHeader.TestObject; + blockHeader.Number = 2001; + blockHeader.GasLimit = 100; + UInt256 baseFee = BaseFeeCalculator.Calculate(blockHeader, releaseSpec); + Assert.AreEqual(UInt256.Zero, baseFee); + } - [Test] - public void Eip_1559_CalculateBaseFee_should_returns_zero_when_eip1559_not_enabled() - { - IReleaseSpec releaseSpec = Substitute.For<IReleaseSpec>(); - releaseSpec.IsEip1559Enabled.Returns(false); - - BlockHeader blockHeader = Build.A.BlockHeader.TestObject; - blockHeader.Number = 2001; - blockHeader.GasLimit = 100; - UInt256 baseFee = BaseFeeCalculator.Calculate(blockHeader, releaseSpec); - Assert.AreEqual(UInt256.Zero, baseFee); - } + [TestCase(100, 100, 88, 0)] + [TestCase(100, 300, 267, 10)] + [TestCase(500, 200, 185, 200)] + [TestCase(500, 0, 0, 200)] + [TestCase(21, 23, 23, 21)] + [TestCase(21, 23, 61, 300)] + [TestCase(500, 0, 10, 200, 10)] + [TestCase(100, 100, 88, 0, 80)] + [TestCase(100, 100, 110, 0, 110)] + public void Eip_1559_CalculateBaseFee(long gasTarget, long baseFee, long expectedBaseFee, long gasUsed, long? minimalBaseFee = null) + { + IReleaseSpec releaseSpec = Substitute.For<IReleaseSpec>(); + releaseSpec.IsEip1559Enabled.Returns(true); + releaseSpec.Eip1559BaseFeeMinValue.Returns((UInt256?)minimalBaseFee); + + BlockHeader blockHeader = Build.A.BlockHeader.TestObject; + blockHeader.Number = 2001; + blockHeader.GasLimit = gasTarget * Eip1559Constants.ElasticityMultiplier; + blockHeader.BaseFeePerGas = (UInt256)baseFee; + blockHeader.GasUsed = gasUsed; + UInt256 actualBaseFee = BaseFeeCalculator.Calculate(blockHeader, releaseSpec); + Assert.AreEqual((UInt256)expectedBaseFee, actualBaseFee); + } - [TestCase(100, 100, 88, 0)] - [TestCase(100, 300, 267, 10)] - [TestCase(500, 200, 185, 200)] - [TestCase(500, 0, 0, 200)] - [TestCase(21, 23, 23, 21)] - [TestCase(21, 23, 61, 300)] - [TestCase(500, 0, 10, 200, 10)] - [TestCase(100, 100, 88, 0, 80)] - [TestCase(100, 100, 110, 0, 110)] - public void Eip_1559_CalculateBaseFee(long gasTarget, long baseFee, long expectedBaseFee, long gasUsed, long? minimalBaseFee = null) - { - IReleaseSpec releaseSpec = Substitute.For<IReleaseSpec>(); - releaseSpec.IsEip1559Enabled.Returns(true); - releaseSpec.Eip1559BaseFeeMinValue.Returns((UInt256?)minimalBaseFee); - - BlockHeader blockHeader = Build.A.BlockHeader.TestObject; - blockHeader.Number = 2001; - blockHeader.GasLimit = gasTarget * Eip1559Constants.ElasticityMultiplier; - blockHeader.BaseFeePerGas = (UInt256)baseFee; - blockHeader.GasUsed = gasUsed; - UInt256 actualBaseFee = BaseFeeCalculator.Calculate(blockHeader, releaseSpec); - Assert.AreEqual((UInt256)expectedBaseFee, actualBaseFee); - } + [TestCaseSource(nameof(HasBodyTestSource))] + public void Should_have_empty_body_as_expected((BlockHeader Header, bool HasBody) fixture) => + fixture.Header.HasBody.Should().Be(fixture.HasBody); - [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")] - public class BaseFeeTestCases - { - public int ParentBaseFee { get; set; } - public int ParentGasUsed { get; set; } - public int ParentTargetGasUsed { get; set; } - public int ExpectedBaseFee { get; set; } - } + public class BaseFeeTestCases + { + public int ParentBaseFee { get; set; } + public int ParentGasUsed { get; set; } + public int ParentTargetGasUsed { get; set; } + public int ExpectedBaseFee { get; set; } + } - [TestCaseSource(nameof(TestCaseSource))] - public void Eip_1559_CalculateBaseFee_shared_test_cases((BaseFeeTestCases Info, string Description) testCase) + [TestCaseSource(nameof(Eip1559BaseFeeTestSource))] + public void Eip_1559_CalculateBaseFee_shared_test_cases((BaseFeeTestCases Info, string Description) testCase) + { + IReleaseSpec releaseSpec = Substitute.For<IReleaseSpec>(); + releaseSpec.IsEip1559Enabled.Returns(true); + + BlockHeader blockHeader = Build.A.BlockHeader.TestObject; + blockHeader.Number = 2001; + blockHeader.GasLimit = testCase.Info.ParentTargetGasUsed * Eip1559Constants.ElasticityMultiplier; + blockHeader.BaseFeePerGas = (UInt256)testCase.Info.ParentBaseFee; + blockHeader.GasUsed = testCase.Info.ParentGasUsed; + UInt256 actualBaseFee = BaseFeeCalculator.Calculate(blockHeader, releaseSpec); + Assert.AreEqual((UInt256)testCase.Info.ExpectedBaseFee, actualBaseFee, testCase.Description); + } + + private static IEnumerable<(BaseFeeTestCases, string)> Eip1559BaseFeeTestSource() + { + string testCases = File.ReadAllText("TestFiles/BaseFeeTestCases.json"); + BaseFeeTestCases[] deserializedTestCases = JsonConvert.DeserializeObject<BaseFeeTestCases[]>(testCases) ?? Array.Empty<BaseFeeTestCases>(); + + for (int i = 0; i < deserializedTestCases.Length; ++i) { - IReleaseSpec releaseSpec = Substitute.For<IReleaseSpec>(); - releaseSpec.IsEip1559Enabled.Returns(true); - - BlockHeader blockHeader = Build.A.BlockHeader.TestObject; - blockHeader.Number = 2001; - blockHeader.GasLimit = testCase.Info.ParentTargetGasUsed * Eip1559Constants.ElasticityMultiplier; - blockHeader.BaseFeePerGas = (UInt256)testCase.Info.ParentBaseFee; - blockHeader.GasUsed = testCase.Info.ParentGasUsed; - UInt256 actualBaseFee = BaseFeeCalculator.Calculate(blockHeader, releaseSpec); - Assert.AreEqual((UInt256)testCase.Info.ExpectedBaseFee, actualBaseFee, testCase.Description); + yield return (deserializedTestCases[i], $"Test case number {i}"); } + } - public static IEnumerable<(BaseFeeTestCases, string)> TestCaseSource() + private static IEnumerable<(BlockHeader, bool)> HasBodyTestSource() => + new[] { - string testCases = File.ReadAllText("TestFiles/BaseFeeTestCases.json"); - BaseFeeTestCases[] deserializedTestCases = JsonConvert.DeserializeObject<BaseFeeTestCases[]>(testCases) ?? Array.Empty<BaseFeeTestCases>(); + (new BlockHeader(), false), - for (int i = 0; i < deserializedTestCases.Length; ++i) + (new BlockHeader { - yield return (deserializedTestCases[i], $"Test case number {i}"); - } - } - } + TxRoot = Keccak.EmptyTreeHash, + UnclesHash = Keccak.OfAnEmptySequenceRlp, + WithdrawalsRoot = Keccak.EmptyTreeHash + }, false), + + (new BlockHeader + { + TxRoot = Keccak.Zero, + UnclesHash = Keccak.OfAnEmptySequenceRlp, + WithdrawalsRoot = Keccak.EmptyTreeHash + }, true), + + (new BlockHeader + { + UnclesHash = Keccak.Zero, + WithdrawalsRoot = Keccak.EmptyTreeHash + }, true), + + (new BlockHeader { WithdrawalsRoot = Keccak.Zero }, true), + + (new BlockHeader + { + TxRoot = Keccak.Zero, + UnclesHash = Keccak.Zero, + WithdrawalsRoot = Keccak.Zero + }, true) + }; } diff --git a/src/Nethermind/Nethermind.Core.Test/BlockTests.cs b/src/Nethermind/Nethermind.Core.Test/BlockTests.cs new file mode 100644 index 00000000000..5c4f33c3351 --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/BlockTests.cs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using FluentAssertions; +using Nethermind.Core.Crypto; +using NUnit.Framework; + +namespace Nethermind.Core.Test; + +internal class BlockTests +{ + [TestCaseSource(nameof(WithdrawalsTestCases))] + public void Should_init_withdrawals_in_body_as_expected((BlockHeader Header, int? Count) fixture) => + (new Block(fixture.Header).Body.Withdrawals?.Length).Should().Be(fixture.Count); + + private static IEnumerable<(BlockHeader, int?)> WithdrawalsTestCases() => + new[] + { + (new BlockHeader(), (int?)null), + (new BlockHeader { WithdrawalsRoot = Keccak.EmptyTreeHash }, 0) + }; +} diff --git a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs index 6f8364f932d..2be486510ee 100644 --- a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs +++ b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs @@ -15,6 +15,7 @@ using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Transactions; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs index 5675bba077b..10ba49164f6 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Collections.Generic; using System.Linq; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -185,6 +186,13 @@ public BlockBuilder WithStateRoot(Keccak stateRoot) return this; } + public BlockBuilder WithWithdrawalsRoot(Keccak? withdrawalsRoot) + { + TestObjectInternal.Header.WithdrawalsRoot = withdrawalsRoot; + + return this; + } + public BlockBuilder WithBloom(Bloom bloom) { TestObjectInternal.Header.Bloom = bloom; @@ -223,5 +231,27 @@ public BlockBuilder WithGasUsed(long gasUsed) TestObjectInternal.Header.GasUsed = gasUsed; return this; } + + public BlockBuilder WithWithdrawals(int count) + { + var withdrawals = new Withdrawal[count]; + + for (var i = 0; i < count; i++) + withdrawals[i] = new(); + + return WithWithdrawals(withdrawals); + } + + public BlockBuilder WithWithdrawals(Withdrawal[]? withdrawals) + { + TestObjectInternal = TestObjectInternal + .WithReplacedBody(TestObjectInternal.Body.WithChangedWithdrawals(withdrawals)); + + TestObjectInternal.Header.WithdrawalsRoot = withdrawals is null + ? null + : new WithdrawalTrie(withdrawals).RootHash; + + return this; + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/BlockHeaderBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/BlockHeaderBuilder.cs index 7fff98ecebd..97c71896463 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/BlockHeaderBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/BlockHeaderBuilder.cs @@ -5,170 +5,176 @@ using Nethermind.Crypto; using Nethermind.Int256; -namespace Nethermind.Core.Test.Builders +namespace Nethermind.Core.Test.Builders; + +public class BlockHeaderBuilder : BuilderBase<BlockHeader> { - public class BlockHeaderBuilder : BuilderBase<BlockHeader> - { - public static UInt256 DefaultDifficulty = 1_000_000; + public static UInt256 DefaultDifficulty = 1_000_000; - protected override void BeforeReturn() + protected override void BeforeReturn() + { + if (!_doNotCalculateHash) { - if (!_doNotCalculateHash) - { - TestObjectInternal.Hash = TestObjectInternal.CalculateHash(); - } - - base.BeforeReturn(); + TestObjectInternal.Hash = TestObjectInternal.CalculateHash(); } - public BlockHeaderBuilder() - { - TestObjectInternal = new BlockHeader( - Keccak.Compute("parent"), - Keccak.OfAnEmptySequenceRlp, - Address.Zero, - DefaultDifficulty, 0, - 4_000_000, - 1_000_000, - new byte[] { 1, 2, 3 }); - TestObjectInternal.Bloom = Bloom.Empty; - TestObjectInternal.MixHash = Keccak.Compute("mix_hash"); - TestObjectInternal.Nonce = 1000; - TestObjectInternal.ReceiptsRoot = Keccak.EmptyTreeHash; - TestObjectInternal.StateRoot = Keccak.EmptyTreeHash; - TestObjectInternal.TxRoot = Keccak.EmptyTreeHash; - } + base.BeforeReturn(); + } - public BlockHeaderBuilder WithParent(BlockHeader parentHeader) - { - TestObjectInternal.ParentHash = parentHeader.Hash; - TestObjectInternal.Number = parentHeader.Number + 1; - TestObjectInternal.GasLimit = parentHeader.GasLimit; - return this; - } + public BlockHeaderBuilder() + { + TestObjectInternal = new BlockHeader( + Keccak.Compute("parent"), + Keccak.OfAnEmptySequenceRlp, + Address.Zero, + DefaultDifficulty, 0, + 4_000_000, + 1_000_000, + new byte[] { 1, 2, 3 }); + TestObjectInternal.Bloom = Bloom.Empty; + TestObjectInternal.MixHash = Keccak.Compute("mix_hash"); + TestObjectInternal.Nonce = 1000; + TestObjectInternal.ReceiptsRoot = Keccak.EmptyTreeHash; + TestObjectInternal.StateRoot = Keccak.EmptyTreeHash; + TestObjectInternal.TxRoot = Keccak.EmptyTreeHash; + } - public BlockHeaderBuilder WithParentHash(Keccak parentHash) - { - TestObjectInternal.ParentHash = parentHash; - return this; - } + public BlockHeaderBuilder WithParent(BlockHeader parentHeader) + { + TestObjectInternal.ParentHash = parentHeader.Hash; + TestObjectInternal.Number = parentHeader.Number + 1; + TestObjectInternal.GasLimit = parentHeader.GasLimit; + return this; + } - public BlockHeaderBuilder WithHash(Keccak hash) - { - TestObjectInternal.Hash = hash; - _doNotCalculateHash = true; - return this; - } + public BlockHeaderBuilder WithParentHash(Keccak parentHash) + { + TestObjectInternal.ParentHash = parentHash; + return this; + } - private bool _doNotCalculateHash; + public BlockHeaderBuilder WithHash(Keccak hash) + { + TestObjectInternal.Hash = hash; + _doNotCalculateHash = true; + return this; + } - public BlockHeaderBuilder WithUnclesHash(Keccak unclesHash) - { - TestObjectInternal.UnclesHash = unclesHash; - return this; - } + private bool _doNotCalculateHash; - public BlockHeaderBuilder WithBeneficiary(Address beneficiary) - { - TestObjectInternal.Beneficiary = beneficiary; - return this; - } + public BlockHeaderBuilder WithUnclesHash(Keccak unclesHash) + { + TestObjectInternal.UnclesHash = unclesHash; + return this; + } - public BlockHeaderBuilder WithAuthor(Address address) - { - TestObjectInternal.Author = address; - return this; - } + public BlockHeaderBuilder WithBeneficiary(Address beneficiary) + { + TestObjectInternal.Beneficiary = beneficiary; + return this; + } - public BlockHeaderBuilder WithBloom(Bloom bloom) - { - TestObjectInternal.Bloom = bloom; - return this; - } + public BlockHeaderBuilder WithAuthor(Address address) + { + TestObjectInternal.Author = address; + return this; + } - public BlockHeaderBuilder WithBaseFee(UInt256 baseFee) - { - TestObjectInternal.BaseFeePerGas = baseFee; - return this; - } + public BlockHeaderBuilder WithBloom(Bloom bloom) + { + TestObjectInternal.Bloom = bloom; + return this; + } - public BlockHeaderBuilder WithStateRoot(Keccak stateRoot) - { - TestObjectInternal.StateRoot = stateRoot; - return this; - } + public BlockHeaderBuilder WithBaseFee(UInt256 baseFee) + { + TestObjectInternal.BaseFeePerGas = baseFee; + return this; + } - public BlockHeaderBuilder WithTransactionsRoot(Keccak transactionsRoot) - { - TestObjectInternal.TxRoot = transactionsRoot; - return this; - } + public BlockHeaderBuilder WithStateRoot(Keccak stateRoot) + { + TestObjectInternal.StateRoot = stateRoot; + return this; + } - public BlockHeaderBuilder WithReceiptsRoot(Keccak receiptsRoot) - { - TestObjectInternal.ReceiptsRoot = receiptsRoot; - return this; - } + public BlockHeaderBuilder WithTransactionsRoot(Keccak transactionsRoot) + { + TestObjectInternal.TxRoot = transactionsRoot; + return this; + } - public BlockHeaderBuilder WithDifficulty(UInt256 difficulty) - { - TestObjectInternal.Difficulty = difficulty; - return this; - } + public BlockHeaderBuilder WithReceiptsRoot(Keccak receiptsRoot) + { + TestObjectInternal.ReceiptsRoot = receiptsRoot; + return this; + } - public BlockHeaderBuilder WithNumber(long blockNumber) - { - TestObjectInternal.Number = blockNumber; - return this; - } + public BlockHeaderBuilder WithDifficulty(UInt256 difficulty) + { + TestObjectInternal.Difficulty = difficulty; + return this; + } - public BlockHeaderBuilder WithTotalDifficulty(long totalDifficulty) - { - TestObjectInternal.TotalDifficulty = (ulong)totalDifficulty; - return this; - } + public BlockHeaderBuilder WithNumber(long blockNumber) + { + TestObjectInternal.Number = blockNumber; + return this; + } - public BlockHeaderBuilder WithGasLimit(long gasLimit) - { - TestObjectInternal.GasLimit = gasLimit; - return this; - } + public BlockHeaderBuilder WithTotalDifficulty(long totalDifficulty) + { + TestObjectInternal.TotalDifficulty = (ulong)totalDifficulty; + return this; + } - public BlockHeaderBuilder WithGasUsed(long gasUsed) - { - TestObjectInternal.GasUsed = gasUsed; - return this; - } + public BlockHeaderBuilder WithGasLimit(long gasLimit) + { + TestObjectInternal.GasLimit = gasLimit; + return this; + } - public BlockHeaderBuilder WithTimestamp(ulong timestamp) - { - TestObjectInternal.Timestamp = timestamp; - return this; - } + public BlockHeaderBuilder WithGasUsed(long gasUsed) + { + TestObjectInternal.GasUsed = gasUsed; + return this; + } - public BlockHeaderBuilder WithExtraData(byte[] extraData) - { - TestObjectInternal.ExtraData = extraData; - return this; - } + public BlockHeaderBuilder WithTimestamp(ulong timestamp) + { + TestObjectInternal.Timestamp = timestamp; + return this; + } - public BlockHeaderBuilder WithMixHash(Keccak mixHash) - { - TestObjectInternal.MixHash = mixHash; - return this; - } + public BlockHeaderBuilder WithExtraData(byte[] extraData) + { + TestObjectInternal.ExtraData = extraData; + return this; + } - public BlockHeaderBuilder WithNonce(ulong nonce) - { - TestObjectInternal.Nonce = nonce; - return this; - } + public BlockHeaderBuilder WithMixHash(Keccak mixHash) + { + TestObjectInternal.MixHash = mixHash; + return this; + } - public BlockHeaderBuilder WithAura(long step, byte[]? signature = null) - { - TestObjectInternal.AuRaStep = step; - TestObjectInternal.AuRaSignature = signature; - return this; - } + public BlockHeaderBuilder WithNonce(ulong nonce) + { + TestObjectInternal.Nonce = nonce; + return this; + } + + public BlockHeaderBuilder WithAura(long step, byte[]? signature = null) + { + TestObjectInternal.AuRaStep = step; + TestObjectInternal.AuRaSignature = signature; + return this; + } + + public BlockHeaderBuilder WithWithdrawalsRoot(Keccak? root) + { + TestObjectInternal.WithdrawalsRoot = root; + + return this; } } diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/Build.Withdrawal.cs b/src/Nethermind/Nethermind.Core.Test/Builders/Build.Withdrawal.cs new file mode 100644 index 00000000000..9c10be9d4dd --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Builders/Build.Withdrawal.cs @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Core.Test.Builders; + +public partial class Build +{ + public WithdrawalBuilder Withdrawal => new(); +} diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs index 489c8497520..523e39be8d1 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs @@ -6,6 +6,7 @@ using System.Net; using System.Text.Json; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using Nethermind.Crypto; using Nethermind.Int256; using Nethermind.Serialization.Rlp; @@ -89,6 +90,13 @@ public static Keccak KeccakFromNumber(int i) public static Address AddressE = PublicKeyE.Address; public static Address AddressF = PublicKeyF.Address; + public static Withdrawal WithdrawalA_1Eth = new() { Address = AddressA, Index = 1, ValidatorIndex = 2001, Amount = 1.Ether() }; + public static Withdrawal WithdrawalB_2Eth = new() { Address = AddressB, Index = 2, ValidatorIndex = 2002, Amount = 2.Ether() }; + public static Withdrawal WithdrawalC_3Eth = new() { Address = AddressC, Index = 3, ValidatorIndex = 2003, Amount = 3.Ether() }; + public static Withdrawal WithdrawalD_4Eth = new() { Address = AddressD, Index = 4, ValidatorIndex = 2004, Amount = 4.Ether() }; + public static Withdrawal WithdrawalE_5Eth = new() { Address = AddressE, Index = 5, ValidatorIndex = 2005, Amount = 5.Ether() }; + public static Withdrawal WithdrawalF_6Eth = new() { Address = AddressF, Index = 6, ValidatorIndex = 2006, Amount = 6.Ether() }; + public static IPEndPoint IPEndPointA = IPEndPoint.Parse("10.0.0.1"); public static IPEndPoint IPEndPointB = IPEndPoint.Parse("10.0.0.2"); public static IPEndPoint IPEndPointC = IPEndPoint.Parse("10.0.0.3"); diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/WithdrawalBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/WithdrawalBuilder.cs new file mode 100644 index 00000000000..0c0c6c64063 --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Builders/WithdrawalBuilder.cs @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Int256; + +namespace Nethermind.Core.Test.Builders; + +public class WithdrawalBuilder : BuilderBase<Withdrawal> +{ + public WithdrawalBuilder() => TestObject = new(); + + public WithdrawalBuilder WithAmount(UInt256 amount) + { + TestObject.Amount = amount; + + return this; + } + + public WithdrawalBuilder WithIndex(ulong index) + { + TestObject.Index = index; + + return this; + } + + public WithdrawalBuilder WithRecipient(Address recipient) + { + TestObject.Address = recipient; + + return this; + } + + public WithdrawalBuilder WithValidatorIndex(ulong index) + { + TestObject.ValidatorIndex = index; + + return this; + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs index 61b5e5a1177..f694847e0dc 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs @@ -12,78 +12,104 @@ using Nethermind.Serialization.Rlp; using NUnit.Framework; -namespace Nethermind.Core.Test.Encoding +namespace Nethermind.Core.Test.Encoding; + +[TestFixture] +public class BlockDecoderTests { - [TestFixture] - public class BlockDecoderTests - { - private Block[] _scenarios; + private Block[] _scenarios; - public BlockDecoderTests() + public BlockDecoderTests() + { + var transactions = new Transaction[100]; + for (int i = 0; i < transactions.Length; i++) { - var transactions = new Transaction[100]; - for (int i = 0; i < 100; i++) - { - transactions[i] = Build.A.Transaction.WithData(new byte[] { (byte)i }).WithNonce((UInt256)i).WithValue((UInt256)i).Signed(new EthereumEcdsa(ChainId.Mainnet, LimboLogs.Instance), TestItem.PrivateKeyA, true).TestObject; - } - - _scenarios = new[] - { - Build.A.Block.WithNumber(1).TestObject, - Build.A.Block.WithNumber(1).WithTransactions(transactions).WithUncles(Build.A.BlockHeader.TestObject).WithMixHash(Keccak.EmptyTreeHash).TestObject - }; + transactions[i] = Build.A.Transaction + .WithData(new byte[] { (byte)i }) + .WithNonce((UInt256)i) + .WithValue((UInt256)i) + .Signed(new EthereumEcdsa(ChainId.Mainnet, LimboLogs.Instance), TestItem.PrivateKeyA, true) + .TestObject; } - [Test] - public void Can_do_roundtrip_null([Values(true, false)] bool valueDecoder) + var uncles = new BlockHeader[16]; + + for (var i = 0; i < uncles.Length; i++) { - BlockDecoder decoder = new(); - Rlp result = decoder.Encode(null); - Block decoded = valueDecoder ? Rlp.Decode<Block>(result.Bytes.AsSpan()) : Rlp.Decode<Block>(result); - Assert.IsNull(decoded); + uncles[i] = Build.A.BlockHeader + .WithWithdrawalsRoot(i % 3 == 0 ? null : Keccak.Compute(i.ToString())) + .TestObject; } - private string regression5644 = "f902cff9025aa05297f2a4a699ba7d038a229a8eb7ab29d0073b37376ff0311f2bd9c608411830a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0fe77dd4ad7c2a3fa4c11868a00e4d728adcdfef8d2e3c13b256b06cbdbb02ec9a00d0abe08c162e4e0891e7a45a8107a98ae44ed47195c2d041fe574de40272df0a0056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000182160c837a1200825208845c54648eb8613078366336393733363936653733366236390000000000000000000000000000f3ec96e458292ccea72a1e53e95f94c28051ab51880b7e03d933f7fa78c9692f635ae55ac3899c9c6999d33c758b5248a05894a3471282333bcd76067c5d391300a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f86ff86d80843b9aca008252089422ea9f6b28db76a7162054c05ed812deb2f519cd8a152d02c7e14af6800000802da0f67424c67d9f91a87b5437db1bdaa05e29bd020ab474b2f67f7be163c9f650dda02f90ab34b44165d776ae04449b15210076d6a72abe2bda2903d4b87f0d1ce541c0"; - - [Test] - public void Can_do_roundtrip_regression([Values(true, false)] bool valueDecoder) + _scenarios = new[] { - BlockDecoder decoder = new(); + Build.A.Block.WithNumber(1).TestObject, + Build.A.Block + .WithNumber(1) + .WithTransactions(transactions) + .WithUncles(Build.A.BlockHeader.TestObject) + .WithMixHash(Keccak.EmptyTreeHash) + .TestObject, + Build.A.Block + .WithNumber(1) + .WithTransactions(transactions) + .WithUncles(uncles) + .WithWithdrawals(8) + .WithMixHash(Keccak.EmptyTreeHash) + .WithTimestamp(HeaderDecoder.WithdrawalTimestamp) + .TestObject + }; + } - byte[] bytes = Bytes.FromHexString(regression5644); - Rlp.ValueDecoderContext valueDecoderContext = new(bytes); - Block? decoded = valueDecoder ? decoder.Decode(ref valueDecoderContext) : decoder.Decode(new RlpStream(bytes)); - Rlp encoded = decoder.Encode(decoded); - Assert.AreEqual(encoded.Bytes.ToHexString(), encoded.Bytes.ToHexString()); - } + [Test] + public void Can_do_roundtrip_null([Values(true, false)] bool valueDecoder) + { + BlockDecoder decoder = new(); + Rlp result = decoder.Encode(null); + Block decoded = valueDecoder ? Rlp.Decode<Block>(result.Bytes.AsSpan()) : Rlp.Decode<Block>(result); + Assert.IsNull(decoded); + } - [Test] - public void Can_do_roundtrip_scenarios([Values(true, false)] bool valueDecoder) - { - BlockDecoder decoder = new(); - foreach (Block block in _scenarios) - { - Rlp encoded = decoder.Encode(block); - Rlp.ValueDecoderContext valueDecoderContext = new(encoded.Bytes); - Block? decoded = valueDecoder ? decoder.Decode(ref valueDecoderContext) : decoder.Decode(new RlpStream(encoded.Bytes)); - Rlp encoded2 = decoder.Encode(decoded); - Assert.AreEqual(encoded.Bytes.ToHexString(), encoded2.Bytes.ToHexString()); - } - } + private string regression5644 = "f902cff9025aa05297f2a4a699ba7d038a229a8eb7ab29d0073b37376ff0311f2bd9c608411830a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0fe77dd4ad7c2a3fa4c11868a00e4d728adcdfef8d2e3c13b256b06cbdbb02ec9a00d0abe08c162e4e0891e7a45a8107a98ae44ed47195c2d041fe574de40272df0a0056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000182160c837a1200825208845c54648eb8613078366336393733363936653733366236390000000000000000000000000000f3ec96e458292ccea72a1e53e95f94c28051ab51880b7e03d933f7fa78c9692f635ae55ac3899c9c6999d33c758b5248a05894a3471282333bcd76067c5d391300a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f86ff86d80843b9aca008252089422ea9f6b28db76a7162054c05ed812deb2f519cd8a152d02c7e14af6800000802da0f67424c67d9f91a87b5437db1bdaa05e29bd020ab474b2f67f7be163c9f650dda02f90ab34b44165d776ae04449b15210076d6a72abe2bda2903d4b87f0d1ce541c0"; - [TestCase("0xf90265f901fda00a7e4c1b7404e89fd6b1bc19148594f98b472a87ca152938b242343296da619da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa01ac2883e8f3f17f58488c6933524298dec316fd596614832065748274a336391a07e2d13609f335a7caf015192b353ce5abec6d37a00726d862b9d287a98addb51a0a0e10907f175886de9bd8cd4ac2c21d1db4109a3a9fecf60f54015ee102803fdb90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001887fffffffffffffff830249f08203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f862f860800a830249f094100000000000000000000000000000000000000001801ba0f56f3b98c5ed3c38d0e4e1e3e499b6ba9bda60fcf0f6a811d7979fb5d81cec53a00be599284605e5223d1fc0a043f56e1a6a9ec2802406f664cbfea850323aeabfc0")] - [Ignore("The test is useful for debugging hive - shouldn't be executed on CI")] - public void Write_rlp_of_blocks_to_file(string rlp) - { - // the test is useful for debugging hive - File.WriteAllBytes("C:\\blocks\\00001.rlp", Bytes.FromHexString(rlp)); - } + [Test] + public void Can_do_roundtrip_regression([Values(true, false)] bool valueDecoder) + { + BlockDecoder decoder = new(); - [Test] - public void Get_length_null() + byte[] bytes = Bytes.FromHexString(regression5644); + Rlp.ValueDecoderContext valueDecoderContext = new(bytes); + Block? decoded = valueDecoder ? decoder.Decode(ref valueDecoderContext) : decoder.Decode(new RlpStream(bytes)); + Rlp encoded = decoder.Encode(decoded); + Assert.AreEqual(encoded.Bytes.ToHexString(), encoded.Bytes.ToHexString()); + } + + [Test] + public void Can_do_roundtrip_scenarios([Values(true, false)] bool valueDecoder) + { + BlockDecoder decoder = new(); + foreach (Block block in _scenarios) { - BlockDecoder decoder = new(); - Assert.AreEqual(1, decoder.GetLength(null, RlpBehaviors.None)); + Rlp encoded = decoder.Encode(block); + Rlp.ValueDecoderContext valueDecoderContext = new(encoded.Bytes); + Block? decoded = valueDecoder ? decoder.Decode(ref valueDecoderContext) : decoder.Decode(new RlpStream(encoded.Bytes)); + Rlp encoded2 = decoder.Encode(decoded); + Assert.AreEqual(encoded.Bytes.ToHexString(), encoded2.Bytes.ToHexString()); } } + + [TestCase("0xf90265f901fda00a7e4c1b7404e89fd6b1bc19148594f98b472a87ca152938b242343296da619da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa01ac2883e8f3f17f58488c6933524298dec316fd596614832065748274a336391a07e2d13609f335a7caf015192b353ce5abec6d37a00726d862b9d287a98addb51a0a0e10907f175886de9bd8cd4ac2c21d1db4109a3a9fecf60f54015ee102803fdb90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001887fffffffffffffff830249f08203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f862f860800a830249f094100000000000000000000000000000000000000001801ba0f56f3b98c5ed3c38d0e4e1e3e499b6ba9bda60fcf0f6a811d7979fb5d81cec53a00be599284605e5223d1fc0a043f56e1a6a9ec2802406f664cbfea850323aeabfc0")] + [Ignore("The test is useful for debugging hive - shouldn't be executed on CI")] + public void Write_rlp_of_blocks_to_file(string rlp) + { + // the test is useful for debugging hive + File.WriteAllBytes("C:\\blocks\\00001.rlp", Bytes.FromHexString(rlp)); + } + + [Test] + public void Get_length_null() + { + BlockDecoder decoder = new(); + Assert.AreEqual(1, decoder.GetLength(null, RlpBehaviors.None)); + } } diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/HeaderDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/HeaderDecoderTests.cs index 5645f9a25a4..2d9f83204d1 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/HeaderDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/HeaderDecoderTests.cs @@ -10,134 +10,135 @@ using Nethermind.Serialization.Rlp; using NUnit.Framework; -namespace Nethermind.Core.Test.Encoding +namespace Nethermind.Core.Test.Encoding; + +[TestFixture] +public class HeaderDecoderTests { - [TestFixture] - public class HeaderDecoderTests + [TestCase(true)] + [TestCase(false)] + public void Can_decode(bool hasWithdrawalsRoot) { - [Test] - public void Can_decode() - { - BlockHeader header = Build.A.BlockHeader - .WithMixHash(Keccak.Compute("mix_hash")) - .WithNonce(1000) - .TestObject; - - HeaderDecoder decoder = new(); - Rlp rlp = decoder.Encode(header); - Rlp.ValueDecoderContext decoderContext = new(rlp.Bytes); - BlockHeader decoded = decoder.Decode(ref decoderContext)!; - decoded.Hash = decoded.CalculateHash(); - - Assert.AreEqual(header.Hash, decoded.Hash, "hash"); - } + BlockHeader header = Build.A.BlockHeader + .WithMixHash(Keccak.Compute("mix_hash")) + .WithNonce(1000) + .WithWithdrawalsRoot(hasWithdrawalsRoot ? Keccak.EmptyTreeHash : null) + .TestObject; + + HeaderDecoder decoder = new(); + Rlp rlp = decoder.Encode(header); + Rlp.ValueDecoderContext decoderContext = new(rlp.Bytes); + BlockHeader? decoded = decoder.Decode(ref decoderContext); + decoded!.Hash = decoded.CalculateHash(); + + Assert.AreEqual(header.Hash, decoded.Hash, "hash"); + } - [Test] - public void Can_decode_tricky() - { - BlockHeader header = Build.A.BlockHeader - .WithMixHash(Keccak.Compute("mix_hash")) - .WithTimestamp(2730) - .WithNonce(1000) - .TestObject; + [Test] + public void Can_decode_tricky() + { + BlockHeader header = Build.A.BlockHeader + .WithMixHash(Keccak.Compute("mix_hash")) + .WithTimestamp(2730) + .WithNonce(1000) + .TestObject; - HeaderDecoder decoder = new(); - Rlp rlp = decoder.Encode(header); - rlp.Bytes[2]++; - string bytesWithAAA = rlp.Bytes.ToHexString(); - bytesWithAAA = bytesWithAAA.Replace("820aaa", "83000aaa"); + HeaderDecoder decoder = new(); + Rlp rlp = decoder.Encode(header); + rlp.Bytes[2]++; + string bytesWithAAA = rlp.Bytes.ToHexString(); + bytesWithAAA = bytesWithAAA.Replace("820aaa", "83000aaa"); - rlp = new Rlp(Bytes.FromHexString(bytesWithAAA)); + rlp = new Rlp(Bytes.FromHexString(bytesWithAAA)); - Rlp.ValueDecoderContext decoderContext = new(rlp.Bytes); - BlockHeader decoded = decoder.Decode(ref decoderContext)!; - decoded.Hash = decoded.CalculateHash(); + Rlp.ValueDecoderContext decoderContext = new(rlp.Bytes); + BlockHeader? decoded = decoder.Decode(ref decoderContext); + decoded!.Hash = decoded.CalculateHash(); - Assert.AreEqual(header.Hash, decoded.Hash, "hash"); - } + Assert.AreEqual(header.Hash, decoded.Hash, "hash"); + } - [Test] - public void Can_decode_aura() - { - var auRaSignature = new byte[64]; - new Random().NextBytes(auRaSignature); - BlockHeader header = Build.A.BlockHeader - .WithAura(100000000, auRaSignature) - .TestObject; - - HeaderDecoder decoder = new(); - Rlp rlp = decoder.Encode(header); - Rlp.ValueDecoderContext decoderContext = new(rlp.Bytes); - BlockHeader decoded = decoder.Decode(ref decoderContext)!; - decoded.Hash = decoded.CalculateHash(); - - Assert.AreEqual(header.Hash, decoded.Hash, "hash"); - } + [Test] + public void Can_decode_aura() + { + var auRaSignature = new byte[64]; + new Random().NextBytes(auRaSignature); + BlockHeader header = Build.A.BlockHeader + .WithAura(100000000, auRaSignature) + .TestObject; + + HeaderDecoder decoder = new(); + Rlp rlp = decoder.Encode(header); + Rlp.ValueDecoderContext decoderContext = new(rlp.Bytes); + BlockHeader? decoded = decoder.Decode(ref decoderContext); + decoded!.Hash = decoded.CalculateHash(); + + Assert.AreEqual(header.Hash, decoded.Hash, "hash"); + } - [Test] - public void Get_length_null() - { - HeaderDecoder decoder = new(); - Assert.AreEqual(1, decoder.GetLength(null, RlpBehaviors.None)); - } + [Test] + public void Get_length_null() + { + HeaderDecoder decoder = new(); + Assert.AreEqual(1, decoder.GetLength(null, RlpBehaviors.None)); + } + + [Test] + public void Can_handle_nulls() + { + Rlp rlp = Rlp.Encode((BlockHeader?)null); + BlockHeader decoded = Rlp.Decode<BlockHeader>(rlp); + Assert.Null(decoded); + } - [Test] - public void Can_handle_nulls() + [Test] + public void Can_encode_decode_with_base_fee() + { + try { - Rlp rlp = Rlp.Encode((BlockHeader)null!); - BlockHeader decoded = Rlp.Decode<BlockHeader>(rlp); - Assert.Null(decoded); + HeaderDecoder.Eip1559TransitionBlock = 0; + BlockHeader header = Build.A.BlockHeader.WithBaseFee(123).TestObject; + Rlp rlp = Rlp.Encode(header); + BlockHeader blockHeader = Rlp.Decode<BlockHeader>(rlp); + blockHeader.BaseFeePerGas.Should().Be(123); } - - [Test] - public void Can_encode_decode_with_base_fee() + finally { - try - { - HeaderDecoder.Eip1559TransitionBlock = 0; - BlockHeader header = Build.A.BlockHeader.WithBaseFee(123).TestObject; - Rlp rlp = Rlp.Encode(header); - BlockHeader blockHeader = Rlp.Decode<BlockHeader>(rlp); - blockHeader.BaseFeePerGas.Should().Be(123); - } - finally - { - HeaderDecoder.Eip1559TransitionBlock = long.MaxValue; - } + HeaderDecoder.Eip1559TransitionBlock = long.MaxValue; } + } - [TestCase(-1)] - [TestCase(long.MinValue)] - public void Can_encode_decode_with_negative_long_fields(long negativeLong) - { - BlockHeader header = Build.A.BlockHeader. - WithNumber(negativeLong). - WithGasUsed(negativeLong). - WithGasLimit(negativeLong).TestObject; + [TestCase(-1)] + [TestCase(long.MinValue)] + public void Can_encode_decode_with_negative_long_fields(long negativeLong) + { + BlockHeader header = Build.A.BlockHeader. + WithNumber(negativeLong). + WithGasUsed(negativeLong). + WithGasLimit(negativeLong).TestObject; - Rlp rlp = Rlp.Encode(header); - BlockHeader blockHeader = Rlp.Decode<BlockHeader>(rlp); + Rlp rlp = Rlp.Encode(header); + BlockHeader blockHeader = Rlp.Decode<BlockHeader>(rlp); - blockHeader.GasUsed.Should().Be(negativeLong); - blockHeader.Number.Should().Be(negativeLong); - blockHeader.GasLimit.Should().Be(negativeLong); - } + blockHeader.GasUsed.Should().Be(negativeLong); + blockHeader.Number.Should().Be(negativeLong); + blockHeader.GasLimit.Should().Be(negativeLong); + } - [TestCase(-1)] - [TestCase(long.MinValue)] - public void Can_encode_decode_with_negative_long_when_using_span(long negativeLong) - { - BlockHeader header = Build.A.BlockHeader. - WithNumber(negativeLong). - WithGasUsed(negativeLong). - WithGasLimit(negativeLong).TestObject; + [TestCase(-1)] + [TestCase(long.MinValue)] + public void Can_encode_decode_with_negative_long_when_using_span(long negativeLong) + { + BlockHeader header = Build.A.BlockHeader. + WithNumber(negativeLong). + WithGasUsed(negativeLong). + WithGasLimit(negativeLong).TestObject; - Rlp rlp = Rlp.Encode(header); - BlockHeader blockHeader = Rlp.Decode<BlockHeader>(rlp.Bytes.AsSpan()); + Rlp rlp = Rlp.Encode(header); + BlockHeader blockHeader = Rlp.Decode<BlockHeader>(rlp.Bytes.AsSpan()); - blockHeader.GasUsed.Should().Be(negativeLong); - blockHeader.Number.Should().Be(negativeLong); - blockHeader.GasLimit.Should().Be(negativeLong); - } + blockHeader.GasUsed.Should().Be(negativeLong); + blockHeader.Number.Should().Be(negativeLong); + blockHeader.GasLimit.Should().Be(negativeLong); } } diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/TxDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/TxDecoderTests.cs index a16b7d32757..358c76894a1 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/TxDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/TxDecoderTests.cs @@ -165,7 +165,6 @@ private void ValueDecoderContext_return_the_same_transaction_as_rlp_stream( Assert.AreEqual(encoded.Bytes, encodedWithDecodedByValueDecoderContext.Bytes); } - [TestCaseSource(nameof(TestCaseSource))] public void Rlp_encode_should_return_the_same_as_rlp_stream_encoding((Transaction Tx, string Description) testCase) { diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/WithdrawalDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/WithdrawalDecoderTests.cs new file mode 100644 index 00000000000..25e8deaa805 --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/WithdrawalDecoderTests.cs @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using FluentAssertions; +using Nethermind.Core.Extensions; +using Nethermind.Int256; +using Nethermind.Serialization.Rlp; +using NUnit.Framework; + +namespace Nethermind.Core.Test.Encoding; + +public class WithdrawalDecoderTests +{ + [Test] + public void Should_encode() + { + var withdrawal = new Withdrawal + { + Index = 1, + ValidatorIndex = 2, + Address = Address.SystemUser, + Amount = 3 + }; + var rlp = Rlp.Encode(withdrawal).Bytes; + + rlp.ToHexString().Should().BeEquivalentTo("d8010294fffffffffffffffffffffffffffffffffffffffe03"); + } + + [Test] + public void Should_decode() + { + var withdrawal = new Withdrawal + { + Index = 1, + ValidatorIndex = 2, + Address = new Address("0x773f86fb098bb19f228f441a7715daa13d10a751"), + Amount = 3 + }; + var rlp = Rlp.Encode(withdrawal).Bytes; + var decoded = Rlp.Decode<Withdrawal>(rlp); + + decoded.Should().BeEquivalentTo(withdrawal); + } + + [Test] + public void Should_decode_with_ValueDecoderContext() + { + var withdrawal = new Withdrawal + { + Index = long.MaxValue, + ValidatorIndex = int.MaxValue, + Address = new Address("0x773f86fb098bb19f228f441a7715daa13d10a751"), + Amount = UInt256.UInt128MaxValue + }; + var stream = new RlpStream(1024); + var codec = new WithdrawalDecoder(); + + codec.Encode(stream, withdrawal); + + var decoderContext = new Rlp.ValueDecoderContext(stream.Data.AsSpan()); + var decoded = codec.Decode(ref decoderContext); + + decoded.Should().BeEquivalentTo(withdrawal); + } + + [Test] + public void Should_encode_same_for_Rlp_Encode_and_WithdrawalDecoder_Encode() + { + var withdrawal = new Withdrawal + { + Index = long.MaxValue, + ValidatorIndex = int.MaxValue, + Address = new Address("0x7e24b8f924a82df020eef45c320deb224559f13e"), + Amount = UInt256.UInt128MaxValue + }; + var rlp1 = new WithdrawalDecoder().Encode(withdrawal).Bytes; + var rlp2 = Rlp.Encode(withdrawal).Bytes; + + rlp1.Should().BeEquivalentTo(rlp2); + } +} diff --git a/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs b/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs index d0993dec4c4..12186ce158d 100644 --- a/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs +++ b/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs @@ -173,7 +173,7 @@ private bool LifetimeValidator( SecurityToken securityToken, TokenValidationParameters validationParameters) { - if (expires == null) return true; + if (!expires.HasValue) return true; long exp = ((DateTimeOffset)expires).ToUnixTimeSeconds(); return _timestamper.UnixTime.SecondsLong < exp; } diff --git a/src/Nethermind/Nethermind.Core/Block.cs b/src/Nethermind/Nethermind.Core/Block.cs index c083216a890..4aea0172494 100644 --- a/src/Nethermind/Nethermind.Core/Block.cs +++ b/src/Nethermind/Nethermind.Core/Block.cs @@ -9,138 +9,151 @@ using Nethermind.Core.Crypto; using Nethermind.Int256; -namespace Nethermind.Core +namespace Nethermind.Core; + +[DebuggerDisplay("{Hash} ({Number})")] +public class Block { - [DebuggerDisplay("{Hash} ({Number})")] - public class Block + public Block(BlockHeader blockHeader, BlockBody body) { - public Block(BlockHeader blockHeader, BlockBody body) - { - Header = blockHeader; - Body = body; - } + Header = blockHeader; + Body = body; + } - public Block(BlockHeader blockHeader, IEnumerable<Transaction> transactions, IEnumerable<BlockHeader> uncles) - { - Header = blockHeader; - Body = new BlockBody(transactions.ToArray(), uncles.ToArray()); - } + public Block( + BlockHeader blockHeader, + IEnumerable<Transaction> transactions, + IEnumerable<BlockHeader> uncles, + IEnumerable<Withdrawal>? withdrawals = null) + { + Header = blockHeader; + Body = new(transactions.ToArray(), uncles.ToArray(), withdrawals?.ToArray()); + } - public Block(BlockHeader blockHeader) - : this(blockHeader, BlockBody.Empty) - { - } + public Block(BlockHeader blockHeader) : this( + blockHeader, + new(null, null, blockHeader.WithdrawalsRoot is null ? null : Array.Empty<Withdrawal>()) + ) + { } + + public Block WithReplacedHeader(BlockHeader newHeader) => new(newHeader, Body); + + public Block WithReplacedBody(BlockBody newBody) => new(Header, newBody); - public Block WithReplacedHeader(BlockHeader newHeader) => new(newHeader, Body); + public Block WithReplacedBodyCloned(BlockBody newBody) => new(Header.Clone(), newBody); - public Block WithReplacedBody(BlockBody newBody) => new(Header, newBody); + public BlockHeader Header { get; } - public Block WithReplacedBodyCloned(BlockBody newBody) => new(Header.Clone(), newBody); + public BlockBody Body { get; } - public BlockHeader Header { get; } + public bool IsGenesis => Header.IsGenesis; - public BlockBody Body { get; } + public Transaction[] Transactions + { + get => Body.Transactions; + protected set => Body.Transactions = value; // needed to produce blocks with unknown transaction count on start + } - public bool IsGenesis => Header.IsGenesis; + public BlockHeader[] Uncles => Body.Uncles; // do not add setter here - public Transaction[] Transactions { get => Body.Transactions; protected set => Body.Transactions = value; } // setter needed to produce blocks with unknown transaction count on start + public Withdrawal[]? Withdrawals => Body.Withdrawals; - public BlockHeader[] Uncles => Body.Uncles; // do not add setter here + public Keccak? Hash => Header.Hash; // do not add setter here - public Keccak? Hash => Header.Hash; // do not add setter here + public Keccak? ParentHash => Header.ParentHash; // do not add setter here - public Keccak? ParentHash => Header.ParentHash; // do not add setter here + public ulong Nonce => Header.Nonce; // do not add setter here - public ulong Nonce => Header.Nonce; // do not add setter here + public Keccak? MixHash => Header.MixHash; // do not add setter here - public Keccak? MixHash => Header.MixHash; // do not add setter here + public byte[]? ExtraData => Header.ExtraData; // do not add setter here - public byte[]? ExtraData => Header.ExtraData; // do not add setter here + public Bloom? Bloom => Header.Bloom; // do not add setter here - public Bloom? Bloom => Header.Bloom; // do not add setter here + public Keccak? UnclesHash => Header.UnclesHash; // do not add setter here - public Keccak? UnclesHash => Header.UnclesHash; // do not add setter here + public Address? Beneficiary => Header.Beneficiary; // do not add setter here - public Address? Beneficiary => Header.Beneficiary; // do not add setter here + public Address? Author => Header.Author; // do not add setter here - public Address? Author => Header.Author; // do not add setter here + public Keccak? StateRoot => Header.StateRoot; // do not add setter here - public Keccak? StateRoot => Header.StateRoot; // do not add setter here + public Keccak? TxRoot => Header.TxRoot; // do not add setter here - public Keccak? TxRoot => Header.TxRoot; // do not add setter here + public Keccak? ReceiptsRoot => Header.ReceiptsRoot; // do not add setter here - public Keccak? ReceiptsRoot => Header.ReceiptsRoot; // do not add setter here + public long GasLimit => Header.GasLimit; // do not add setter here - public long GasLimit => Header.GasLimit; // do not add setter here + public long GasUsed => Header.GasUsed; // do not add setter here - public long GasUsed => Header.GasUsed; // do not add setter here + public ulong Timestamp => Header.Timestamp; // do not add setter here - public ulong Timestamp => Header.Timestamp; // do not add setter here + public DateTime TimestampDate => Header.TimestampDate; // do not add setter here - public DateTime TimestampDate => Header.TimestampDate; // do not add setter here + public long Number => Header.Number; // do not add setter here - public long Number => Header.Number; // do not add setter here + public UInt256 Difficulty => Header.Difficulty; // do not add setter here - public UInt256 Difficulty => Header.Difficulty; // do not add setter here + public UInt256? TotalDifficulty => Header.TotalDifficulty; // do not add setter here - public UInt256? TotalDifficulty => Header.TotalDifficulty; // do not add setter here + public UInt256 BaseFeePerGas => Header.BaseFeePerGas; // do not add setter here - public UInt256 BaseFeePerGas => Header.BaseFeePerGas; // do not add setter here + public bool IsPostMerge => Header.IsPostMerge; // do not add setter here - public bool IsPostMerge => Header.IsPostMerge; // do not add setter here + public bool IsBodyMissing => Header.HasBody && Body.IsEmpty; - public bool IsBodyMissing => Header.HasBody && Body.IsEmpty; + public Keccak? WithdrawalsRoot => Header.WithdrawalsRoot; // do not add setter here - public override string ToString() - { - return ToString(Format.Short); - } + public override string ToString() => ToString(Format.Short); + + public string ToString(Format format) => format switch + { + Format.Full => ToFullString(), + Format.FullHashAndNumber => Hash is null ? $"{Number} null" : $"{Number} ({Hash})", + Format.HashNumberAndTx => Hash is null + ? $"{Number} null, tx count: {Body.Transactions.Length}" + : $"{Number} {TimestampDate:HH:mm:ss} ({Hash?.ToShortString()}), tx count: {Body.Transactions.Length}", + Format.HashNumberDiffAndTx => Hash is null + ? $"{Number} null, diff: {Difficulty}, tx count: {Body.Transactions.Length}" + : $"{Number} ({Hash?.ToShortString()}), diff: {Difficulty}, tx count: {Body.Transactions.Length}", + _ => Hash is null ? $"{Number} null" : $"{Number} ({Hash?.ToShortString()})" + }; + + private string ToFullString() + { + StringBuilder builder = new(); + builder.AppendLine($"Block {Number}"); + builder.AppendLine(" Header:"); + builder.Append(Header.ToString(" ")); - public string ToString(Format format) + builder.AppendLine(" Uncles:"); + foreach (BlockHeader uncle in Body.Uncles ?? Array.Empty<BlockHeader>()) { - return format switch - { - Format.Full => ToFullString(), - Format.FullHashAndNumber => Hash is null ? $"{Number} null" : $"{Number} ({Hash})", - Format.HashNumberAndTx => Hash is null - ? $"{Number} null, tx count: {Body.Transactions.Length}" - : $"{Number} {TimestampDate:HH:mm:ss} ({Hash?.ToShortString()}), tx count: {Body.Transactions.Length}", - Format.HashNumberDiffAndTx => Hash is null - ? $"{Number} null, diff: {Difficulty}, tx count: {Body.Transactions.Length}" - : $"{Number} ({Hash?.ToShortString()}), diff: {Difficulty}, tx count: {Body.Transactions.Length}", - _ => Hash is null ? $"{Number} null" : $"{Number} ({Hash?.ToShortString()})" - }; + builder.Append(uncle.ToString(" ")); } - private string ToFullString() + builder.AppendLine(" Transactions:"); + foreach (Transaction tx in Body?.Transactions ?? Array.Empty<Transaction>()) { - StringBuilder builder = new(); - builder.AppendLine($"Block {Number}"); - builder.AppendLine(" Header:"); - builder.Append($"{Header.ToString(" ")}"); - - builder.AppendLine(" Uncles:"); - foreach (BlockHeader uncle in Body.Uncles ?? Array.Empty<BlockHeader>()) - { - builder.Append($"{uncle.ToString(" ")}"); - } - - builder.AppendLine(" Transactions:"); - foreach (Transaction tx in Body?.Transactions ?? Array.Empty<Transaction>()) - { - builder.Append($"{tx.ToString(" ")}"); - } - - return builder.ToString(); + builder.Append(tx.ToString(" ")); } - public enum Format + builder.AppendLine(" Withdrawals:"); + + foreach (var w in Body?.Withdrawals ?? Array.Empty<Withdrawal>()) { - Full, - FullHashAndNumber, - HashNumberAndTx, - HashNumberDiffAndTx, - Short + builder.Append(w.ToString(" ")); } + + return builder.ToString(); + } + + public enum Format + { + Full, + FullHashAndNumber, + HashNumberAndTx, + HashNumberDiffAndTx, + Short } } diff --git a/src/Nethermind/Nethermind.Core/BlockBody.cs b/src/Nethermind/Nethermind.Core/BlockBody.cs index d19214b65cb..76658c6ab8a 100644 --- a/src/Nethermind/Nethermind.Core/BlockBody.cs +++ b/src/Nethermind/Nethermind.Core/BlockBody.cs @@ -7,37 +7,29 @@ namespace Nethermind.Core { public class BlockBody { - public BlockBody(Transaction[]? transactions, BlockHeader[]? uncles) + public BlockBody(Transaction[]? transactions, BlockHeader[]? uncles, Withdrawal[]? withdrawals = null) { Transactions = transactions ?? Array.Empty<Transaction>(); Uncles = uncles ?? Array.Empty<BlockHeader>(); + Withdrawals = withdrawals; } - public BlockBody() - : this(null, null) - { - } + public BlockBody() : this(null, null, null) { } - public BlockBody WithChangedTransactions(Transaction[] transactions) - { - return new(transactions, Uncles); - } + public BlockBody WithChangedTransactions(Transaction[] transactions) => new(transactions, Uncles, Withdrawals); - public BlockBody WithChangedUncles(BlockHeader[] uncles) - { - return new(Transactions, uncles); - } + public BlockBody WithChangedUncles(BlockHeader[] uncles) => new(Transactions, uncles, Withdrawals); - public static BlockBody WithOneTransactionOnly(Transaction tx) - { - return new(new[] { tx }, Array.Empty<BlockHeader>()); - } + public BlockBody WithChangedWithdrawals(Withdrawal[]? withdrawals) => new(Transactions, Uncles, withdrawals); + + public static BlockBody WithOneTransactionOnly(Transaction tx) => new(new[] { tx }, null, null); public Transaction[] Transactions { get; internal set; } + public BlockHeader[] Uncles { get; } - public static readonly BlockBody Empty = new(); + public Withdrawal[]? Withdrawals { get; } - public bool IsEmpty => Transactions.Length == 0 && Uncles.Length == 0; + public bool IsEmpty => Transactions.Length == 0 && Uncles.Length == 0 && (Withdrawals?.Length ?? 0) == 0; } } diff --git a/src/Nethermind/Nethermind.Core/BlockHeader.cs b/src/Nethermind/Nethermind.Core/BlockHeader.cs index 0239c0aea96..fb7bd23e82e 100644 --- a/src/Nethermind/Nethermind.Core/BlockHeader.cs +++ b/src/Nethermind/Nethermind.Core/BlockHeader.cs @@ -9,123 +9,117 @@ using Nethermind.Core.Extensions; using Nethermind.Int256; -namespace Nethermind.Core +namespace Nethermind.Core; + +[DebuggerDisplay("{Hash} ({Number})")] +public class BlockHeader { - [DebuggerDisplay("{Hash} ({Number})")] - public class BlockHeader - { - internal BlockHeader() { } + internal BlockHeader() { } - public BlockHeader( - Keccak parentHash, - Keccak unclesHash, - Address beneficiary, - in UInt256 difficulty, - long number, - long gasLimit, - ulong timestamp, - byte[] extraData) - { - ParentHash = parentHash; - UnclesHash = unclesHash; - Beneficiary = beneficiary; - Difficulty = difficulty; - Number = number; - GasLimit = gasLimit; - Timestamp = timestamp; - ExtraData = extraData; - } + public BlockHeader( + Keccak parentHash, + Keccak unclesHash, + Address beneficiary, + in UInt256 difficulty, + long number, + long gasLimit, + ulong timestamp, + byte[] extraData) + { + ParentHash = parentHash; + UnclesHash = unclesHash; + Beneficiary = beneficiary; + Difficulty = difficulty; + Number = number; + GasLimit = gasLimit; + Timestamp = timestamp; + ExtraData = extraData; + } - public WeakReference<BlockHeader>? MaybeParent { get; set; } + public WeakReference<BlockHeader>? MaybeParent { get; set; } + public bool IsGenesis => Number == 0L; + public Keccak? ParentHash { get; set; } + public Keccak? UnclesHash { get; set; } + public Address? Author { get; set; } + public Address? Beneficiary { get; set; } + public Address? GasBeneficiary => Author ?? Beneficiary; + public Keccak? StateRoot { get; set; } + public Keccak? TxRoot { get; set; } + public Keccak? ReceiptsRoot { get; set; } + public Bloom? Bloom { get; set; } + public UInt256 Difficulty { get; set; } + public long Number { get; set; } + public long GasUsed { get; set; } + public long GasLimit { get; set; } + public ulong Timestamp { get; set; } + public DateTime TimestampDate => DateTimeOffset.FromUnixTimeSeconds((long)Timestamp).LocalDateTime; + public byte[] ExtraData { get; set; } = Array.Empty<byte>(); + public Keccak? MixHash { get; set; } + public Keccak? Random => MixHash; + public ulong Nonce { get; set; } + public Keccak? Hash { get; set; } + public UInt256? TotalDifficulty { get; set; } + public byte[]? AuRaSignature { get; set; } + public long? AuRaStep { get; set; } + public UInt256 BaseFeePerGas { get; set; } + public Keccak? WithdrawalsRoot { get; set; } - public bool IsGenesis => Number == 0L; - public Keccak? ParentHash { get; set; } - public Keccak? UnclesHash { get; set; } - public Address? Author { get; set; } - public Address? Beneficiary { get; set; } - public Address? GasBeneficiary => Author ?? Beneficiary; - public Keccak? StateRoot { get; set; } - public Keccak? TxRoot { get; set; } - public Keccak? ReceiptsRoot { get; set; } - public Bloom? Bloom { get; set; } - public UInt256 Difficulty { get; set; } - public long Number { get; set; } - public long GasUsed { get; set; } - public long GasLimit { get; set; } - public ulong Timestamp { get; set; } - public DateTime TimestampDate => DateTimeOffset.FromUnixTimeSeconds((long)Timestamp).LocalDateTime; - public byte[] ExtraData { get; set; } = Array.Empty<byte>(); - public Keccak? MixHash { get; set; } - public Keccak? Random => MixHash; - public ulong Nonce { get; set; } - public Keccak? Hash { get; set; } - public UInt256? TotalDifficulty { get; set; } - public byte[]? AuRaSignature { get; set; } - public long? AuRaStep { get; set; } - public UInt256 BaseFeePerGas { get; set; } + public bool HasBody => (TxRoot is not null && TxRoot != Keccak.EmptyTreeHash) + || (UnclesHash is not null && UnclesHash != Keccak.OfAnEmptySequenceRlp) + || (WithdrawalsRoot is not null && WithdrawalsRoot != Keccak.EmptyTreeHash); - public bool HasBody => UnclesHash != Keccak.OfAnEmptySequenceRlp || TxRoot != Keccak.EmptyTreeHash; - public string SealEngineType { get; set; } = Nethermind.Core.SealEngineType.Ethash; + public string SealEngineType { get; set; } = Core.SealEngineType.Ethash; - // ToDo we need to set this flag after reading block from db - public bool IsPostMerge { get; set; } + // ToDo we need to set this flag after reading block from db + public bool IsPostMerge { get; set; } - public string ToString(string indent) - { - StringBuilder builder = new(); - builder.AppendLine($"{indent}Hash: {Hash}"); - builder.AppendLine($"{indent}Number: {Number}"); - builder.AppendLine($"{indent}Parent: {ParentHash}"); - builder.AppendLine($"{indent}Beneficiary: {Beneficiary}"); - builder.AppendLine($"{indent}Gas Limit: {GasLimit}"); - builder.AppendLine($"{indent}Gas Used: {GasUsed}"); - builder.AppendLine($"{indent}Timestamp: {Timestamp}"); - builder.AppendLine($"{indent}Extra Data: {ExtraData.ToHexString()}"); - builder.AppendLine($"{indent}Difficulty: {Difficulty}"); - builder.AppendLine($"{indent}Mix Hash: {MixHash}"); - builder.AppendLine($"{indent}Nonce: {Nonce}"); - builder.AppendLine($"{indent}Uncles Hash: {UnclesHash}"); - builder.AppendLine($"{indent}Tx Root: {TxRoot}"); - builder.AppendLine($"{indent}Receipts Root: {ReceiptsRoot}"); - builder.AppendLine($"{indent}State Root: {StateRoot}"); - builder.AppendLine($"{indent}BaseFeePerGas: {BaseFeePerGas}"); - builder.AppendLine($"{indent}IsPostMerge: {IsPostMerge}"); - builder.AppendLine($"{indent}TotalDifficulty: {TotalDifficulty}"); + public string ToString(string indent) + { + StringBuilder builder = new(); + builder.AppendLine($"{indent}Hash: {Hash}"); + builder.AppendLine($"{indent}Number: {Number}"); + builder.AppendLine($"{indent}Parent: {ParentHash}"); + builder.AppendLine($"{indent}Beneficiary: {Beneficiary}"); + builder.AppendLine($"{indent}Gas Limit: {GasLimit}"); + builder.AppendLine($"{indent}Gas Used: {GasUsed}"); + builder.AppendLine($"{indent}Timestamp: {Timestamp}"); + builder.AppendLine($"{indent}Extra Data: {ExtraData.ToHexString()}"); + builder.AppendLine($"{indent}Difficulty: {Difficulty}"); + builder.AppendLine($"{indent}Mix Hash: {MixHash}"); + builder.AppendLine($"{indent}Nonce: {Nonce}"); + builder.AppendLine($"{indent}Uncles Hash: {UnclesHash}"); + builder.AppendLine($"{indent}Tx Root: {TxRoot}"); + builder.AppendLine($"{indent}Receipts Root: {ReceiptsRoot}"); + builder.AppendLine($"{indent}State Root: {StateRoot}"); + builder.AppendLine($"{indent}BaseFeePerGas: {BaseFeePerGas}"); + builder.AppendLine($"{indent}Withdrawals Root: {WithdrawalsRoot}"); + builder.AppendLine($"{indent}IsPostMerge: {IsPostMerge}"); + builder.AppendLine($"{indent}TotalDifficulty: {TotalDifficulty}"); - return builder.ToString(); - } + return builder.ToString(); + } - public override string ToString() - { - return ToString(string.Empty); - } + public override string ToString() => ToString(string.Empty); - public string ToString(Format format) - { - switch (format) - { - case Format.Full: - return ToString(string.Empty); - case Format.FullHashAndNumber: - return Hash is null ? $"{Number} null" : $"{Number} ({Hash})"; - default: - return Hash is null ? $"{Number} null" : $"{Number} ({Hash.ToShortString()})"; - } - } + public string ToString(Format format) => format switch + { + Format.Full => ToString(string.Empty), + Format.FullHashAndNumber => Hash is null ? $"{Number} null" : $"{Number} ({Hash})", + _ => Hash is null ? $"{Number} null" : $"{Number} ({Hash.ToShortString()})", + }; - [Todo(Improve.Refactor, "Use IFormattable here")] - public enum Format - { - Full, - Short, - FullHashAndNumber - } + [Todo(Improve.Refactor, "Use IFormattable here")] + public enum Format + { + Full, + Short, + FullHashAndNumber + } - public BlockHeader Clone() - { - BlockHeader header = (BlockHeader)MemberwiseClone(); - header.Bloom = Bloom?.Clone() ?? new Bloom(); - return header; - } + public BlockHeader Clone() + { + BlockHeader header = (BlockHeader)MemberwiseClone(); + header.Bloom = Bloom?.Clone() ?? new Bloom(); + return header; } } diff --git a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs index 07982931547..7f5901818ce 100644 --- a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs @@ -220,7 +220,7 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec bool IsEip3529Enabled { get; } /// <summary> - /// Reject new contracts starting with the 0xEF byte + /// Reject new contracts starting with the 0xEF byte /// </summary> bool IsEip3541Enabled { get; } @@ -250,13 +250,22 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec /// </summary> bool IsEip3860Enabled { get; } + /// <summary> + /// Gets or sets a value indicating whether the + /// <see href="https://eips.ethereum.org/EIPS/eip-4895">EIP-4895</see> + /// validator withdrawals are enabled. + /// </summary> + bool IsEip4895Enabled { get; } + /// <summary> /// Should transactions be validated against chainId. /// </summary> /// <remarks>Backward compatibility for early Kovan blocks.</remarks> bool ValidateChainId => true; - // STATE related + public ulong WithdrawalTimestamp { get; } + + // STATE related public bool ClearEmptyAccountWhenTouched => IsEip158Enabled; // VM @@ -322,5 +331,7 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec public bool IncludePush0Instruction => IsEip3855Enabled; public bool TransientStorageEnabled => IsEip1153Enabled; + + public bool WithdrawalsEnabled => IsEip4895Enabled; } } diff --git a/src/Nethermind/Nethermind.Core/Withdrawal.cs b/src/Nethermind/Nethermind.Core/Withdrawal.cs new file mode 100644 index 00000000000..b0c47386bd4 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Withdrawal.cs @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text; +using Nethermind.Int256; + +namespace Nethermind.Core; + +/// <summary> +/// Represents a withdrawal that has been validated at the consensus layer. +/// </summary> +public class Withdrawal +{ + /// <summary> + /// Gets or sets the withdrawal unique id. + /// </summary> + public ulong Index { get; set; } + + /// <summary> + /// Gets or sets the validator index on the consensus layer the withdrawal corresponds to. + /// </summary> + public ulong ValidatorIndex { get; set; } + /// <summary> + /// Gets or sets the withdrawal address. + /// </summary> + public Address Address { get; set; } = Address.Zero; + + /// <summary> + /// Gets or sets the withdrawal amount in Wei. + /// </summary> + public UInt256 Amount { get; set; } + + public override string ToString() => ToString(string.Empty); + + public string ToString(string indentation) => new StringBuilder($"{indentation}{nameof(Withdrawal)} {{") + .Append($"{nameof(Index)}: {Index}, ") + .Append($"{nameof(ValidatorIndex)}: {ValidatorIndex}, ") + .Append($"{nameof(Address)}: {Address}, ") + .Append($"{nameof(Amount)}: {Amount}}}") + .ToString(); +} diff --git a/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs b/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs index 6bab56e6dd1..6ace9a200d0 100644 --- a/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs +++ b/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs @@ -14,12 +14,10 @@ using Nethermind.Consensus.Processing; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Db; -using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.Int256; diff --git a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs index a973c02cb27..ac326574dc9 100644 --- a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs @@ -257,9 +257,6 @@ private void CallAndRestore( blockHeader.GasLimit, Math.Max(blockHeader.Timestamp + 1, _timestamper.UnixTime.Seconds), Array.Empty<byte>()) - { - BaseFeePerGas = BaseFeeCalculator.Calculate(blockHeader, _specProvider.GetSpecFor1559(blockHeader.Number + 1)), - } : new( blockHeader.ParentHash!, blockHeader.UnclesHash!, @@ -268,11 +265,11 @@ private void CallAndRestore( blockHeader.Number, blockHeader.GasLimit, blockHeader.Timestamp, - blockHeader.ExtraData) - { - BaseFeePerGas = blockHeader.BaseFeePerGas, - }; + blockHeader.ExtraData); + callHeader.BaseFeePerGas = treatBlockHeaderAsParentBlock + ? BaseFeeCalculator.Calculate(blockHeader, _specProvider.GetSpec(callHeader)) + : blockHeader.BaseFeePerGas; callHeader.MixHash = blockHeader.MixHash; callHeader.IsPostMerge = blockHeader.Difficulty == 0; transaction.Hash = transaction.CalculateHash(); diff --git a/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs b/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs index 7db272175c9..d03a57d7f0c 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs @@ -188,7 +188,7 @@ private IEnumerable<FilterLog> FilterLogsIteratively(LogFilter filter, BlockHead } fromBlock = _blockFinder.FindHeader(fromBlock.Number + 1); - if (fromBlock == null) break; + if (fromBlock is null) break; count++; } diff --git a/src/Nethermind/Nethermind.Init/Steps/InitRlp.cs b/src/Nethermind/Nethermind.Init/Steps/InitRlp.cs index cb69a2d533e..c17b43a76b6 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitRlp.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitRlp.cs @@ -34,6 +34,7 @@ public virtual Task Execute(CancellationToken _) } HeaderDecoder.Eip1559TransitionBlock = _api.SpecProvider.GenesisSpec.Eip1559TransitionBlock; + HeaderDecoder.WithdrawalTimestamp = _api.SpecProvider.GenesisSpec.WithdrawalTimestamp; return Task.CompletedTask; } diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs index 22c5e6655e4..45aeb414894 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs @@ -17,6 +17,7 @@ using Nethermind.Consensus.Processing; using Nethermind.Consensus.Producers; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Attributes; using Nethermind.Core.Crypto; diff --git a/src/Nethermind/Nethermind.Init/Steps/MigrateConfigs.cs b/src/Nethermind/Nethermind.Init/Steps/MigrateConfigs.cs index 8b9b88835e6..b0cc8b59e5e 100644 --- a/src/Nethermind/Nethermind.Init/Steps/MigrateConfigs.cs +++ b/src/Nethermind/Nethermind.Init/Steps/MigrateConfigs.cs @@ -41,7 +41,7 @@ public static void MigrateBlocksConfig(IBlocksConfig? blocksConfig, IBlocksConfi //Loop over config properties checking mismaches and changing defaults //So that on given and current inner configs we would only have same values - if (propertyInfos == null) return; + if (propertyInfos is null) return; foreach (PropertyInfo? propertyInfo in propertyInfos) { diff --git a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs index 76e23fdde68..62fad66ffdd 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs @@ -34,6 +34,7 @@ using NSubstitute; using BlockTree = Nethermind.Blockchain.BlockTree; using Nethermind.Blockchain.Synchronization; +using Nethermind.Consensus.Withdrawals; using Nethermind.Config; namespace Nethermind.JsonRpc.Benchmark diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcServiceTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcServiceTests.cs index 0f06496feb1..7278ee8e78c 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcServiceTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcServiceTests.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; using System.IO.Abstractions; using System.Linq; @@ -8,6 +9,7 @@ using FluentAssertions; using Nethermind.Blockchain.Find; using Nethermind.Config; +using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; @@ -184,5 +186,38 @@ public void Web3ShaTest() JsonRpcSuccessResponse? response = TestRequest(web3RpcModule, "web3_sha3", "0x68656c6c6f20776f726c64") as JsonRpcSuccessResponse; Assert.AreEqual(TestItem.KeccakA, response?.Result); } + + [TestCaseSource(nameof(BlockForRpcTestSource))] + public void BlockForRpc_should_expose_withdrawals_if_any((bool Expected, Block Block) item) + { + var specProvider = Substitute.For<ISpecProvider>(); + var rpcBlock = new BlockForRpc(item.Block, false, specProvider); + + rpcBlock.WithdrawalsRoot.Should().BeEquivalentTo(item.Block.WithdrawalsRoot); + rpcBlock.Withdrawals.Should().BeEquivalentTo(item.Block.Withdrawals); + + var json = new EthereumJsonSerializer().Serialize(rpcBlock); + + json.Contains("withdrawals\"", StringComparison.Ordinal).Should().Be(item.Expected); + json.Contains("withdrawalsRoot", StringComparison.Ordinal).Should().Be(item.Expected); + } + + // With (Block, bool), tests don't run for some reason. Flipped to (bool, Block). + private static IEnumerable<(bool, Block)> BlockForRpcTestSource() => + new[] + { + (true, Build.A.Block + .WithWithdrawals(new[] + { + Build.A.Withdrawal + .WithAmount(1) + .WithRecipient(TestItem.AddressA) + .TestObject + }) + .TestObject + ), + + (false, Build.A.Block.WithWithdrawals(null).TestObject) + }; } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs index acefe84bb45..54d4f7743d8 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs @@ -23,6 +23,7 @@ using Nethermind.Facade.Filters; using Nethermind.Int256; using Nethermind.JsonRpc.Data; +using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Logging; using Nethermind.Serialization.Json; using Nethermind.Serialization.Rlp; @@ -1082,6 +1083,44 @@ public static void Should_handle_gasCap_as_max_if_null_or_zero(long? gasCap) Assert.AreEqual(long.MaxValue, rpcTx.Gas, "Gas must be set to max if gasCap is null or 0"); } + [Test] + public async Task eth_getBlockByNumber_should_return_withdrawals_correctly() + { + using Context ctx = await Context.Create(); + IBlockFinder blockFinder = Substitute.For<IBlockFinder>(); + IReceiptFinder receiptFinder = Substitute.For<IReceiptFinder>(); + + Block block = Build.A.Block.WithNumber(1) + .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")) + .WithTransactions(new[] { Build.A.Transaction.TestObject }) + .WithWithdrawals(new[] { Build.A.Withdrawal.WithAmount(1_000).TestObject }) + .TestObject; + + LogEntry[] entries = new[] + { + Build.A.LogEntry.TestObject, + Build.A.LogEntry.TestObject + }; + + TxReceipt receipt = Build.A.Receipt.WithBloom(new Bloom(entries, new Bloom())).WithAllFieldsFilled + .WithSender(TestItem.AddressE) + .WithLogs(entries).TestObject; + TxReceipt[] receiptsTab = { receipt }; + blockFinder.FindBlock(Arg.Any<BlockParameter>()).Returns(block); + receiptFinder.Get(Arg.Any<Block>()).Returns(receiptsTab); + receiptFinder.Get(Arg.Any<Keccak>()).Returns(receiptsTab); + + ctx.Test = await TestRpcBlockchain.ForTest(SealEngineType.NethDev).WithBlockFinder(blockFinder).WithReceiptFinder(receiptFinder).Build(); + string result = ctx.Test.TestEthRpc("eth_getBlockByNumber", TestItem.KeccakA.ToString(), "true"); + + result.Should().Be(new EthereumJsonSerializer().Serialize(new + { + jsonrpc = "2.0", + result = new BlockForRpc(block, true, Substitute.For<ISpecProvider>()), + id = 67 + })); + } + private static (byte[] ByteCode, AccessListItemForRpc[] AccessList) GetTestAccessList(long loads = 2, bool allowSystemUser = true) { AccessListItemForRpc[] accessList = allowSystemUser diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs index 525ad74137b..92546f74fb0 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs @@ -885,7 +885,7 @@ public void SyncingSubscription_on_NewBestSuggestedBlock_event_when_sync_no_chan SyncingSubscription syncingSubscription = GetSyncingSubscription(10042, 10024); BlockHeader blockHeader = Build.A.BlockHeader.WithNumber(10045).TestObject; - Block block = new(blockHeader, BlockBody.Empty); + Block block = new(blockHeader); BlockEventArgs blockEventArgs = new(block); _blockTree.FindBestSuggestedHeader().Returns(blockHeader); @@ -917,7 +917,7 @@ public void SyncingSubscription_on_NewBestSuggestedBlock_event_when_sync_changed SyncingSubscription syncingSubscription = GetSyncingSubscription(10042, 10024); BlockHeader blockHeader = Build.A.BlockHeader.WithNumber(10030).TestObject; - Block block = new(blockHeader, BlockBody.Empty); + Block block = new(blockHeader); BlockEventArgs blockEventArgs = new(block); _blockTree.FindBestSuggestedHeader().Returns(blockHeader); @@ -952,7 +952,7 @@ public void SyncingSubscription_on_NewBestSuggestedBlock_event_when_sync_changed SyncingSubscription syncingSubscription = GetSyncingSubscription(10042, 10040); BlockHeader blockHeader = Build.A.BlockHeader.WithNumber(10099).TestObject; - Block block = new(blockHeader, BlockBody.Empty); + Block block = new(blockHeader); BlockEventArgs blockEventArgs = new(block); _blockTree.FindBestSuggestedHeader().Returns(blockHeader); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs index 6626e27ed91..e0551fb6082 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs @@ -8,6 +8,7 @@ using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Tracing; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; diff --git a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs index 13f6f12387a..cf908867e09 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_forkchoiceUpdatedV1]")] + DefaultValue = "[engine_newPayloadV1, engine_newPayloadV2, 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 318110f09f1..f2e54921db6 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs @@ -39,7 +39,13 @@ public int WebSocketsPort public string JwtSecretFile { get; set; } = "keystore/jwt-secret"; public bool UnsecureDevNoRpcAuthentication { get; set; } public int? MaxLoggedRequestParametersCharacters { get; set; } = null; - public string[]? MethodsLoggingFiltering { get; set; } = { "engine_newPayloadV1", "engine_forkchoiceUpdatedV1" }; + public string[]? MethodsLoggingFiltering { get; set; } = + { + "engine_newPayloadV1", + "engine_newPayloadV2", + "engine_forkchoiceUpdatedV1", + "engine_forkchoiceUpdatedV2" + }; public string EngineHost { get; set; } = "127.0.0.1"; public int? EnginePort { get; set; } = null; public string[] EngineEnabledModules { get; set; } = ModuleType.DefaultEngineModules.ToArray(); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BlockForRpc.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BlockForRpc.cs index 99023f9750a..009984207e2 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BlockForRpc.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BlockForRpc.cs @@ -13,102 +13,109 @@ using Nethermind.Serialization.Rlp; using Newtonsoft.Json; -namespace Nethermind.JsonRpc.Modules.Eth +namespace Nethermind.JsonRpc.Modules.Eth; + +public class BlockForRpc { - public class BlockForRpc + private readonly BlockDecoder _blockDecoder = new(); + private readonly bool _isAuRaBlock; + + protected BlockForRpc() { - private readonly BlockDecoder _blockDecoder = new(); - private readonly bool _isAuRaBlock; - protected BlockForRpc() - { + } + public BlockForRpc(Block block, bool includeFullTransactionData, ISpecProvider specProvider) + { + _isAuRaBlock = block.Header.AuRaSignature is not null; + Author = block.Author ?? block.Beneficiary; + Difficulty = block.Difficulty; + ExtraData = block.ExtraData; + GasLimit = block.GasLimit; + GasUsed = block.GasUsed; + Hash = block.Hash; + LogsBloom = block.Bloom; + Miner = block.Beneficiary; + if (!_isAuRaBlock) + { + MixHash = block.MixHash; + Nonce = new byte[8]; + BinaryPrimitives.WriteUInt64BigEndian(Nonce, block.Nonce); } - - public BlockForRpc(Block block, bool includeFullTransactionData, ISpecProvider specProvider) + else { - _isAuRaBlock = block.Header.AuRaSignature is not null; - Author = block.Author ?? block.Beneficiary; - Difficulty = block.Difficulty; - ExtraData = block.ExtraData; - GasLimit = block.GasLimit; - GasUsed = block.GasUsed; - Hash = block.Hash; - LogsBloom = block.Bloom; - Miner = block.Beneficiary; - if (!_isAuRaBlock) - { - MixHash = block.MixHash; - Nonce = new byte[8]; - BinaryPrimitives.WriteUInt64BigEndian(Nonce, block.Nonce); - } - else - { - Step = block.Header.AuRaStep; - Signature = block.Header.AuRaSignature; - } + Step = block.Header.AuRaStep; + Signature = block.Header.AuRaSignature; + } - if (specProvider is not null) + if (specProvider is not null) + { + var spec = specProvider.GetSpec(block.Header); + if (spec.IsEip1559Enabled) { - var spec = specProvider.GetSpec(block.Header); - if (spec.IsEip1559Enabled) - { - BaseFeePerGas = block.Header.BaseFeePerGas; - } + BaseFeePerGas = block.Header.BaseFeePerGas; } - - Number = block.Number; - ParentHash = block.ParentHash; - ReceiptsRoot = block.ReceiptsRoot; - Sha3Uncles = block.UnclesHash; - Size = _blockDecoder.GetLength(block, RlpBehaviors.None); - StateRoot = block.StateRoot; - Timestamp = block.Timestamp; - TotalDifficulty = block.TotalDifficulty ?? 0; - Transactions = includeFullTransactionData ? block.Transactions.Select((t, idx) => new TransactionForRpc(block.Hash, block.Number, idx, t, block.BaseFeePerGas)).ToArray() : block.Transactions.Select(t => t.Hash).OfType<object>().ToArray(); - TransactionsRoot = block.TxRoot; - Uncles = block.Uncles.Select(o => o.Hash); } - public Address Author { get; set; } - public UInt256 Difficulty { get; set; } - public byte[] ExtraData { get; set; } - public long GasLimit { get; set; } - public long GasUsed { get; set; } - - [JsonProperty(NullValueHandling = NullValueHandling.Include)] - public Keccak Hash { get; set; } - - [JsonProperty(NullValueHandling = NullValueHandling.Include)] - public Bloom LogsBloom { get; set; } - public Address Miner { get; set; } - public Keccak MixHash { get; set; } - - public bool ShouldSerializeMixHash() => !_isAuRaBlock && MixHash is not null; - - [JsonProperty(NullValueHandling = NullValueHandling.Include)] - public byte[] Nonce { get; set; } - - public bool ShouldSerializeNonce() => !_isAuRaBlock; - - [JsonProperty(NullValueHandling = NullValueHandling.Include)] - public long? Number { get; set; } - public Keccak ParentHash { get; set; } - public Keccak ReceiptsRoot { get; set; } - public Keccak Sha3Uncles { get; set; } - public byte[] Signature { get; set; } - public bool ShouldSerializeSignature() => _isAuRaBlock; - public long Size { get; set; } - public Keccak StateRoot { get; set; } - [JsonConverter(typeof(NullableLongConverter), NumberConversion.Raw)] - public long? Step { get; set; } - public bool ShouldSerializeStep() => _isAuRaBlock; - public UInt256 TotalDifficulty { get; set; } - public UInt256 Timestamp { get; set; } - - public UInt256? BaseFeePerGas { get; set; } - public IEnumerable<object> Transactions { get; set; } - public Keccak TransactionsRoot { get; set; } - public IEnumerable<Keccak> Uncles { get; set; } + Number = block.Number; + ParentHash = block.ParentHash; + ReceiptsRoot = block.ReceiptsRoot; + Sha3Uncles = block.UnclesHash; + Size = _blockDecoder.GetLength(block, RlpBehaviors.None); + StateRoot = block.StateRoot; + Timestamp = block.Timestamp; + TotalDifficulty = block.TotalDifficulty ?? 0; + Transactions = includeFullTransactionData ? block.Transactions.Select((t, idx) => new TransactionForRpc(block.Hash, block.Number, idx, t, block.BaseFeePerGas)).ToArray() : block.Transactions.Select(t => t.Hash).OfType<object>().ToArray(); + TransactionsRoot = block.TxRoot; + Uncles = block.Uncles.Select(o => o.Hash); + Withdrawals = block.Withdrawals; + WithdrawalsRoot = block.Header.WithdrawalsRoot; } + + public Address Author { get; set; } + public UInt256 Difficulty { get; set; } + public byte[] ExtraData { get; set; } + public long GasLimit { get; set; } + public long GasUsed { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Include)] + public Keccak Hash { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Include)] + public Bloom LogsBloom { get; set; } + public Address Miner { get; set; } + public Keccak MixHash { get; set; } + + public bool ShouldSerializeMixHash() => !_isAuRaBlock && MixHash is not null; + + [JsonProperty(NullValueHandling = NullValueHandling.Include)] + public byte[] Nonce { get; set; } + + public bool ShouldSerializeNonce() => !_isAuRaBlock; + + [JsonProperty(NullValueHandling = NullValueHandling.Include)] + public long? Number { get; set; } + public Keccak ParentHash { get; set; } + public Keccak ReceiptsRoot { get; set; } + public Keccak Sha3Uncles { get; set; } + public byte[] Signature { get; set; } + public bool ShouldSerializeSignature() => _isAuRaBlock; + public long Size { get; set; } + public Keccak StateRoot { get; set; } + [JsonConverter(typeof(NullableLongConverter), NumberConversion.Raw)] + public long? Step { get; set; } + public bool ShouldSerializeStep() => _isAuRaBlock; + public UInt256 TotalDifficulty { get; set; } + public UInt256 Timestamp { get; set; } + + public UInt256? BaseFeePerGas { get; set; } + public IEnumerable<object> Transactions { get; set; } + public Keccak TransactionsRoot { get; set; } + public IEnumerable<Keccak> Uncles { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public IEnumerable<Withdrawal>? Withdrawals { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public Keccak? WithdrawalsRoot { get; set; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index 023b319d7e2..cbbf49f4994 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -529,7 +529,7 @@ private ResultWrapper<BlockForRpc> GetUncle(BlockParameter blockParameter, UInt2 } BlockHeader uncleHeader = block.Uncles[(int)positionIndex]; - return ResultWrapper<BlockForRpc>.Success(new BlockForRpc(new Block(uncleHeader, BlockBody.Empty), false, _specProvider)); + return ResultWrapper<BlockForRpc>.Success(new BlockForRpc(new Block(uncleHeader), false, _specProvider)); } public ResultWrapper<UInt256?> eth_newFilter(Filter filter) diff --git a/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergePluginTests.cs b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergePluginTests.cs index 8759e5b05a0..b10e467baf2 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergePluginTests.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergePluginTests.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using System.Threading.Tasks; using Nethermind.Blockchain.Synchronization; using Nethermind.Config; @@ -14,17 +15,14 @@ using Nethermind.Consensus.Rewards; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; -using Nethermind.Core.Test.Blockchain; +using Nethermind.Core.Specs; using Nethermind.Core.Timers; using Nethermind.Facade.Eth; -using Nethermind.Merge.AuRa.Test; using Nethermind.Merge.Plugin; using Nethermind.Merge.Plugin.BlockProduction; using Nethermind.Merge.Plugin.Handlers; using Nethermind.Merge.Plugin.Test; using Nethermind.Specs; -using Nethermind.Specs.Forks; using NSubstitute; using NUnit.Framework; @@ -40,34 +38,46 @@ protected override MergeTestBlockchain CreateBaseBlockChain( protected override Keccak ExpectedBlockHash => new("0x990d377b67dbffee4a60db6f189ae479ffb406e8abea16af55e0469b8524cf46"); + [TestCase(true)] + [TestCase(false)] + public override async Task executePayloadV1_accepts_already_known_block(bool throttleBlockProcessor) + { + await base.executePayloadV1_accepts_already_known_block(throttleBlockProcessor); + } + + // Override below tests for now, it fails when asserting the blockHash of produced block equals a hardcoded precomputed one. + // This happens because for this AuRa chain the blockHash includes AuRa specific fields, hence the hash for genesis is different + // causing all subsequent blocks to have a different blockHash. + // You can verify this by removing `SealEngineType = Nethermind.Core.SealEngineType.AuRa;` from the constructor of + // the test class above and rerunning the tests. + [TestCaseSource(nameof(GetWithdrawalValidationValues))] + public override async Task newPayloadV2_should_validate_withdrawals(( + IReleaseSpec Spec, + string ErrorMessage, + IEnumerable<Withdrawal>? Withdrawals, + string BlockHash + ) input) + { + await Task.CompletedTask; + } + [Test] - public override async Task processing_block_should_serialize_valid_responses() + public override async Task Should_process_block_as_expected_V2() { - // Override this test for now, it fails when asserting the blockHash of produced block equals a hardcoded precomputed one. - // This happens because for this AuRa chain the blockHash includes AuRa specific fields, hence the hash for genesis is different - // causing all subsequent blocks to have a different blockHash. - // You can verify this by removing `SealEngineType = Nethermind.Core.SealEngineType.AuRa;` from the constructor of - // the test class above and rerunning the tests. await Task.CompletedTask; } [Test] - public override async Task forkchoiceUpdatedV1_should_communicate_with_boost_relay_through_http() + public override async Task processing_block_should_serialize_valid_responses() { - // Override this test for now, it fails when asserting the blockHash of produced block equals a hardcoded precomputed one. - // This happens because for this AuRa chain the blockHash includes AuRa specific fields, hence the hash for genesis is different - // causing all subsequent blocks to have a different blockHash. - // You can verify this by removing `SealEngineType = Nethermind.Core.SealEngineType.AuRa;` from the constructor of - // the test class above and rerunning the tests. - // NOTE: This is the blockhash AuRa produces `0xb337e096b1540ade48f63104b653691af54bb87feb0944d7ec597baeb04f7e1b` await Task.CompletedTask; } - [TestCase(true)] - [TestCase(false)] - public override async Task executePayloadV1_accepts_already_known_block(bool throttleBlockProcessor) + [Test] + public override async Task forkchoiceUpdatedV1_should_communicate_with_boost_relay_through_http() { - await base.executePayloadV1_accepts_already_known_block(throttleBlockProcessor); + // NOTE: This is the blockhash AuRa produces `0xb337e096b1540ade48f63104b653691af54bb87feb0944d7ec597baeb04f7e1b` + await Task.CompletedTask; } class MergeAuRaTestBlockchain : MergeTestBlockchain @@ -75,12 +85,12 @@ class MergeAuRaTestBlockchain : MergeTestBlockchain public MergeAuRaTestBlockchain(IMergeConfig? mergeConfig = null, IPayloadPreparationService? mockedPayloadPreparationService = null) : base(mergeConfig, mockedPayloadPreparationService) { - SealEngineType = Nethermind.Core.SealEngineType.AuRa; + SealEngineType = Core.SealEngineType.AuRa; } protected override IBlockProducer CreateTestBlockProducer(TxPoolTxSource txPoolTxSource, ISealer sealer, ITransactionComparerProvider transactionComparerProvider) { - SealEngine = new MergeSealEngine(SealEngine, PoSSwitcher, SealValidator, LogManager); + SealEngine = new MergeSealEngine(SealEngine, PoSSwitcher, SealValidator!, LogManager); BlocksConfig blocksConfig = new() { MinGasPrice = 0 }; ISyncConfig syncConfig = new SyncConfig(); TargetAdjustedGasLimitCalculator targetAdjustedGasLimitCalculator = new(SpecProvider, blocksConfig); @@ -126,7 +136,7 @@ protected override IBlockProducer CreateTestBlockProducer(TxPoolTxSource txPoolT AuRaBlockProducer preMergeBlockProducer = new( txPoolTxSource, blockProducerEnvFactory.Create().ChainProcessor, - ((TestBlockchain)this).BlockProductionTrigger, + BlockProductionTrigger, State, sealer, BlockTree, diff --git a/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergeBlockProcessor.cs b/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergeBlockProcessor.cs index ebe61c530a0..31376c30368 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergeBlockProcessor.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergeBlockProcessor.cs @@ -9,6 +9,7 @@ using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Transactions; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Evm.Tracing; @@ -29,6 +30,7 @@ public AuRaMergeBlockProcessor( IReceiptStorage receiptStorage, ILogManager logManager, IBlockTree blockTree, + IWithdrawalProcessor withdrawalProcessor, ITxFilter? txFilter = null, AuRaContractGasLimitOverride? gasLimitOverride = null, ContractRewriter? contractRewriter = null @@ -42,6 +44,7 @@ public AuRaMergeBlockProcessor( receiptStorage, logManager, blockTree, + withdrawalProcessor, txFilter, gasLimitOverride, contractRewriter diff --git a/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergeBlockProducerEnvFactory.cs b/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergeBlockProducerEnvFactory.cs index bd341f384cb..3e2f07f9edc 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergeBlockProducerEnvFactory.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergeBlockProducerEnvFactory.cs @@ -1,25 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; using Nethermind.Blockchain; -using Nethermind.Blockchain.Data; using Nethermind.Blockchain.Receipts; using Nethermind.Config; -using Nethermind.Consensus; using Nethermind.Consensus.AuRa.Config; -using Nethermind.Consensus.AuRa.Contracts; -using Nethermind.Consensus.AuRa.Contracts.DataStore; using Nethermind.Consensus.AuRa.InitializationSteps; -using Nethermind.Consensus.AuRa.Transactions; using Nethermind.Consensus.Comparers; using Nethermind.Consensus.Processing; using Nethermind.Consensus.Producers; using Nethermind.Consensus.Rewards; -using Nethermind.Consensus.Transactions; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Db; using Nethermind.Logging; -using Nethermind.State; using Nethermind.Trie.Pruning; using Nethermind.TxPool; @@ -83,7 +80,10 @@ protected override BlockProcessor CreateBlockProcessor( readOnlyTxProcessingEnv.StorageProvider, receiptStorage, logManager, - _blockTree); + _blockTree, + new BlockProductionWithdrawalProcessor( + new WithdrawalProcessor(readOnlyTxProcessingEnv.StateProvider, logManager)) + ); } protected override TxPoolTxSource CreateTxPoolTxSource( diff --git a/src/Nethermind/Nethermind.Merge.AuRa/AuRaPostMergeBlockProducer.cs b/src/Nethermind/Nethermind.Merge.AuRa/AuRaPostMergeBlockProducer.cs index 88f907b0864..5aa4145c0c4 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa/AuRaPostMergeBlockProducer.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa/AuRaPostMergeBlockProducer.cs @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using Nethermind.Blockchain; using Nethermind.Config; using Nethermind.Consensus; diff --git a/src/Nethermind/Nethermind.Merge.AuRa/AuRaPostMergeBlockProducerFactory.cs b/src/Nethermind/Nethermind.Merge.AuRa/AuRaPostMergeBlockProducerFactory.cs index 89d4f14d5be..0664eb92eef 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa/AuRaPostMergeBlockProducerFactory.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa/AuRaPostMergeBlockProducerFactory.cs @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using Nethermind.Config; using Nethermind.Consensus; using Nethermind.Consensus.Producers; diff --git a/src/Nethermind/Nethermind.Merge.AuRa/InitializationSteps/InitializeBlockchainAuRaMerge.cs b/src/Nethermind/Nethermind.Merge.AuRa/InitializationSteps/InitializeBlockchainAuRaMerge.cs index f53b8297d31..9066e53d6cf 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa/InitializationSteps/InitializeBlockchainAuRaMerge.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa/InitializationSteps/InitializeBlockchainAuRaMerge.cs @@ -11,6 +11,7 @@ using Nethermind.Consensus.AuRa.Validators; using Nethermind.Consensus.AuRa.Transactions; using Nethermind.Consensus.Transactions; +using Nethermind.Consensus.Withdrawals; namespace Nethermind.Merge.AuRa.InitializationSteps { @@ -35,6 +36,7 @@ protected override BlockProcessor NewBlockProcessor(AuRaNethermindApi api, ITxFi _api.ReceiptStorage!, _api.LogManager, _api.BlockTree!, + new WithdrawalProcessor(_api.StateProvider!, _api.LogManager), txFilter, GetGasLimitCalculator(), contractRewriter diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTest.V2.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTest.V2.cs deleted file mode 100644 index 372e1949707..00000000000 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTest.V2.cs +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -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.Crypto; -using Nethermind.Int256; -using Nethermind.JsonRpc; -using Nethermind.Merge.Plugin.Data.V1; -using Nethermind.Merge.Plugin.Data.V2; -using Nethermind.State; -using NUnit.Framework; - -namespace Nethermind.Merge.Plugin.Test; - -public partial class EngineModuleTests -{ - [Test] - public async Task getPayloadV2_empty_block_should_have_zero_value() - { - 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<ResultWrapper<ForkchoiceUpdatedV1Result>> forkchoiceResponse = rpc.engine_forkchoiceUpdatedV1(forkchoiceState, payload); - - byte[] payloadId = Bytes.FromHexString(forkchoiceResponse.Result.Data.PayloadId!); - ResultWrapper<GetPayloadV2Result?> responseFirst = await rpc.engine_getPayloadV2(payloadId); - responseFirst.Should().NotBeNull(); - responseFirst.Result.ResultType.Should().Be(ResultType.Success); - responseFirst.Data!.BlockValue.Should().Be(0); - } - - [Test] - public async Task getPayloadV2_received_fees_should_be_equal_to_block_value_in_getPayload_result() - { - using SemaphoreSlim blockImprovementLock = new(0); - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - - Address feeRecipient = TestItem.AddressA; - - Keccak startingHead = chain.BlockTree.HeadHash; - uint count = 3; - int value = 10; - - PrivateKey sender = TestItem.PrivateKeyB; - 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 }) - .Result.Data.PayloadId!; - - UInt256 startingBalance = chain.StateReader.GetBalance(chain.State.StateRoot, feeRecipient); - - await blockImprovementLock.WaitAsync(10000); - GetPayloadV2Result getPayloadResult = (await rpc.engine_getPayloadV2(Bytes.FromHexString(payloadId))).Data!; - - ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult.ExecutionPayloadV1); - executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); - - UInt256 finalBalance = chain.StateReader.GetBalance(getPayloadResult.ExecutionPayloadV1.StateRoot, feeRecipient); - - (finalBalance - startingBalance).Should().Be(getPayloadResult.BlockValue); - } - - [Test] - public async Task getPayloadV2_request_unknown_payload() - { - using SemaphoreSlim blockImprovementLock = new(0); - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - - byte[] payloadId = Bytes.FromHexString("0x0"); - ResultWrapper<GetPayloadV2Result?> responseFirst = await rpc.engine_getPayloadV2(payloadId); - responseFirst.Should().NotBeNull(); - responseFirst.Result.ResultType.Should().Be(ResultType.Failure); - responseFirst.ErrorCode.Should().Be(-38001); - } -} diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs index 2255be2e593..3fb3038c430 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs @@ -2,12 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; -using System.Threading.Tasks; -using FluentAssertions; using Nethermind.Blockchain; using Nethermind.Blockchain.Find; using Nethermind.Core; @@ -21,8 +18,6 @@ using Nethermind.JsonRpc.Test.Modules; using Nethermind.Specs; using Nethermind.Specs.Forks; -using Nethermind.JsonRpc; -using Nethermind.Merge.Plugin.Data.V1; using Nethermind.State; namespace Nethermind.Merge.Plugin.Test @@ -42,7 +37,7 @@ private void AssertExecutionStatusChanged(IEngineRpcModule rpc, Keccak headBlock Assert.AreEqual(safeBlockHash, result.SafeBlockHash); } - private (UInt256, UInt256) AddTransactions(MergeTestBlockchain chain, ExecutionPayloadV1 executePayloadRequest, + private (UInt256, UInt256) AddTransactions(MergeTestBlockchain chain, ExecutionPayload 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); @@ -72,11 +67,11 @@ Transaction BuildTransaction(uint index, Account senderAccount) => return Enumerable.Range(0, (int)count).Select(i => BuildTransaction((uint)i, account)).ToArray(); } - private ExecutionPayloadV1 CreateParentBlockRequestOnHead(IBlockTree blockTree) + private ExecutionPayload CreateParentBlockRequestOnHead(IBlockTree blockTree) { Block? head = blockTree.Head; if (head is null) throw new NotSupportedException(); - return new ExecutionPayloadV1() + return new ExecutionPayload() { BlockNumber = head.Number, BlockHash = head.Hash!, @@ -87,9 +82,9 @@ private ExecutionPayloadV1 CreateParentBlockRequestOnHead(IBlockTree blockTree) }; } - private static ExecutionPayloadV1 CreateBlockRequest(ExecutionPayloadV1 parent, Address miner) + private static ExecutionPayload CreateBlockRequest(ExecutionPayload parent, Address miner, Withdrawal[]? withdrawals = null) { - ExecutionPayloadV1 blockRequest = new() + ExecutionPayload blockRequest = new() { ParentHash = parent.BlockHash, FeeRecipient = miner, @@ -100,6 +95,7 @@ private static ExecutionPayloadV1 CreateBlockRequest(ExecutionPayloadV1 parent, ReceiptsRoot = Keccak.EmptyTreeHash, LogsBloom = Bloom.Empty, Timestamp = parent.Timestamp + 1, + Withdrawals = withdrawals }; blockRequest.SetTransactions(Array.Empty<Transaction>()); @@ -108,10 +104,10 @@ private static ExecutionPayloadV1 CreateBlockRequest(ExecutionPayloadV1 parent, return blockRequest; } - private static ExecutionPayloadV1[] CreateBlockRequestBranch(ExecutionPayloadV1 parent, Address miner, int count) + private static ExecutionPayload[] CreateBlockRequestBranch(ExecutionPayload parent, Address miner, int count) { - ExecutionPayloadV1 currentBlock = parent; - ExecutionPayloadV1[] blockRequests = new ExecutionPayloadV1[count]; + ExecutionPayload currentBlock = parent; + ExecutionPayload[] blockRequests = new ExecutionPayload[count]; for (int i = 0; i < count; i++) { currentBlock = CreateBlockRequest(currentBlock, miner); @@ -135,18 +131,18 @@ private static ExecutionPayloadV1[] CreateBlockRequestBranch(ExecutionPayloadV1 } private static TestCaseData GetNewBlockRequestBadDataTestCase<T>( - Expression<Func<ExecutionPayloadV1, T>> propertyAccess, T wrongValue) + Expression<Func<ExecutionPayload, T>> propertyAccess, T wrongValue) { - Action<ExecutionPayloadV1, T> setter = propertyAccess.GetSetter(); + Action<ExecutionPayload, T> setter = propertyAccess.GetSetter(); // ReSharper disable once ConvertToLocalFunction - Action<ExecutionPayloadV1> wrongValueSetter = r => setter(r, wrongValue); + Action<ExecutionPayload> wrongValueSetter = r => setter(r, wrongValue); return new TestCaseData(wrongValueSetter) { TestName = $"executePayload_rejects_incorrect_{propertyAccess.GetName().ToLower()}({wrongValue?.ToString()})" }; } - private static bool TryCalculateHash(ExecutionPayloadV1 request, out Keccak hash) + private static bool TryCalculateHash(ExecutionPayload request, out Keccak hash) { if (request.TryGetBlock(out Block? block) && block is not null) { diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.RelayBuilder.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.RelayBuilder.cs index c17195f0d3d..3cf16c2529e 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.RelayBuilder.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.RelayBuilder.cs @@ -2,9 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.IO; +using System.Globalization; using System.Net.Http; -using System.Text; using System.Threading; using System.Threading.Tasks; using FluentAssertions; @@ -19,7 +18,7 @@ using Nethermind.JsonRpc; using Nethermind.Merge.Plugin.BlockProduction; using Nethermind.Merge.Plugin.BlockProduction.Boost; -using Nethermind.Merge.Plugin.Data.V1; +using Nethermind.Merge.Plugin.Data; using Nethermind.Serialization.Json; using NSubstitute; using NUnit.Framework; @@ -74,9 +73,9 @@ public async Task forkchoiceUpdatedV1_should_communicate_with_boost_relay() await wait.WaitOneAsync(100, CancellationToken.None); - ResultWrapper<ExecutionPayloadV1?> response = await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId)); + ResultWrapper<ExecutionPayload?> response = await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId)); - ExecutionPayloadV1 executionPayloadV1 = response.Data!; + ExecutionPayload executionPayloadV1 = response.Data!; executionPayloadV1.FeeRecipient.Should().Be(TestItem.AddressA); executionPayloadV1.PrevRandao.Should().Be(TestItem.KeccakA); executionPayloadV1.GasLimit.Should().Be(10_000_000L); @@ -102,57 +101,46 @@ public virtual async Task forkchoiceUpdatedV1_should_communicate_with_boost_rela .Respond("application/json", "{\"timestamp\":\"0x3e9\",\"prevRandao\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"suggestedFeeRecipient\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\"}"); //TODO: think about extracting an essely serialisable class, test its serializatoin sepratly, refactor with it similar methods like the one above - string expected_parentHash = "0x1c53bdbf457025f80c6971a9cf50986974eed02f0a9acaeeb49cafef10efd133"; - string expected_feeRecipient = "0xb7705ae4c6f81b66cdb323c65f4e8133690fc099"; - string expected_stateRoot = "0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f"; - string expected_receiptsRoot = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"; - string expected_logsBloom = "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; - string expected_prevRandao = "0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760"; - string expected_blockNumber = "0x1"; - string expected_gasLimit = "0x3d0900"; - string expected_gasUsed = "0x0"; - string expected_timestamp = "0x3e9"; - string expected_extraData = "0x4e65746865726d696e64"; // Nethermind - string expected_baseFeePerGas = "0x0"; - string expected_blockHash = "0x4ced29c819cf146b41ef448042773f958d5bbe297b0d6b82be677b65c85b436b"; - string expected_transactions = "[]"; - string expected_profit = "0x0"; - - - string expectedContent = "{\"block\":{\"parentHash\":\"" + - expected_parentHash + - "\",\"feeRecipient\":\"" + - expected_feeRecipient + - "\",\"stateRoot\":\"" + - expected_stateRoot + - "\",\"receiptsRoot\":\"" + - expected_receiptsRoot + - "\",\"logsBloom\":\"" + - expected_logsBloom + - "\",\"prevRandao\":\"" + - expected_prevRandao + - "\",\"blockNumber\":\"" + - expected_blockNumber + - "\",\"gasLimit\":\"" + - expected_gasLimit + - "\",\"gasUsed\":\"" + - expected_gasUsed + - "\",\"timestamp\":\"" + - expected_timestamp + - "\",\"extraData\":\"" + - expected_extraData + - "\",\"baseFeePerGas\":\"" + - expected_baseFeePerGas + - "\",\"blockHash\":\"" + - expected_blockHash + - "\",\"transactions\":" + - expected_transactions + - "},\"profit\":\"" + - expected_profit + - "\"}"; - - mockHttp.Expect(HttpMethod.Post, relayUrl + BoostRelay.SendPayloadPath) - .WithContent(expectedContent); + var expected_parentHash = "0x1c53bdbf457025f80c6971a9cf50986974eed02f0a9acaeeb49cafef10efd133"; + var expected_feeRecipient = "0xb7705ae4c6f81b66cdb323c65f4e8133690fc099"; + var expected_stateRoot = "0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f"; + var expected_receiptsRoot = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"; + var expected_logsBloom = "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + var expected_prevRandao = "0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760"; + var expected_blockNumber = 1; + var expected_gasLimit = 0x3d0900L; + var expected_gasUsed = 0; + var expected_timestamp = 0x3e9UL; + var expected_extraData = "0x4e65746865726d696e64"; // Nethermind + var expected_baseFeePerGas = (UInt256)0; + var expected_blockHash = "0x4ced29c819cf146b41ef448042773f958d5bbe297b0d6b82be677b65c85b436b"; + var expected_profit = "0x0"; + + var expected = new BoostExecutionPayloadV1 + { + Block = new ExecutionPayload + { + ParentHash = new(expected_parentHash), + FeeRecipient = new(expected_feeRecipient), + StateRoot = new(expected_stateRoot), + ReceiptsRoot = new(expected_receiptsRoot), + LogsBloom = new(Bytes.FromHexString(expected_logsBloom)), + PrevRandao = new(expected_prevRandao), + BlockNumber = expected_blockNumber, + GasLimit = expected_gasLimit, + GasUsed = expected_gasUsed, + Timestamp = expected_timestamp, + ExtraData = Bytes.FromHexString(expected_extraData), + BaseFeePerGas = expected_baseFeePerGas, + BlockHash = new(expected_blockHash), + Transactions = Array.Empty<byte[]>() + }, + Profit = UInt256.Parse(expected_profit[2..], NumberStyles.HexNumber) + }; + + mockHttp + .Expect(HttpMethod.Post, relayUrl + BoostRelay.SendPayloadPath) + .WithContent(serializer.Serialize(expected)); DefaultHttpClient defaultHttpClient = new(mockHttp.ToHttpClient(), serializer, chain.LogManager, 1, 100); BoostRelay boostRelay = new(defaultHttpClient, relayUrl); @@ -177,9 +165,9 @@ public virtual async Task forkchoiceUpdatedV1_should_communicate_with_boost_rela await wait.WaitOneAsync(100, CancellationToken.None); - ResultWrapper<ExecutionPayloadV1?> response = await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId)); + ResultWrapper<ExecutionPayload?> response = await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId)); - ExecutionPayloadV1 executionPayloadV1 = response.Data!; + ExecutionPayload executionPayloadV1 = response.Data!; executionPayloadV1.FeeRecipient.Should().Be(TestItem.AddressA); executionPayloadV1.PrevRandao.Should().Be(TestItem.KeccakA); @@ -223,9 +211,9 @@ public async Task forkchoiceUpdatedV1_should_ignore_gas_limit([Values(false, tru new PayloadAttributes { Timestamp = timestamp, SuggestedFeeRecipient = feeRecipient, PrevRandao = random, GasLimit = 10_000_000L }).Result.Data .PayloadId!; - ResultWrapper<ExecutionPayloadV1?> response = await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId)); + ResultWrapper<ExecutionPayload?> response = await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId)); - ExecutionPayloadV1 executionPayloadV1 = response.Data!; + ExecutionPayload executionPayloadV1 = response.Data!; executionPayloadV1.GasLimit.Should().Be(4_000_000L); } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs index e2845b951cc..34467d26c69 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Setup.cs @@ -15,6 +15,7 @@ using Nethermind.Consensus.Producers; using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; @@ -27,8 +28,6 @@ using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; using Nethermind.Merge.Plugin.Handlers; -using Nethermind.Merge.Plugin.Handlers.V1; -using Nethermind.Merge.Plugin.Handlers.V2; using Nethermind.Merge.Plugin.Synchronization; using Nethermind.Specs; using Nethermind.Specs.Forks; @@ -39,11 +38,17 @@ namespace Nethermind.Merge.Plugin.Test { public partial class EngineModuleTests { - protected virtual MergeTestBlockchain CreateBaseBlockChain(IMergeConfig mergeConfig = null, IPayloadPreparationService? mockedPayloadService = null) => + protected virtual MergeTestBlockchain CreateBaseBlockChain(IMergeConfig? mergeConfig = null, IPayloadPreparationService? mockedPayloadService = null) => new(mergeConfig, mockedPayloadService); - protected async Task<MergeTestBlockchain> CreateBlockChain(IMergeConfig mergeConfig = null, IPayloadPreparationService? mockedPayloadService = null) - => await CreateBaseBlockChain(mergeConfig, mockedPayloadService).Build(new SingleReleaseSpecProvider(London.Instance, 1)); + protected async Task<MergeTestBlockchain> CreateShanghaiBlockChain(IMergeConfig? mergeConfig = null, IPayloadPreparationService? mockedPayloadService = null) + => await CreateBlockChain(mergeConfig, mockedPayloadService, Shanghai.Instance); + + protected async Task<MergeTestBlockchain> CreateBlockChain(IMergeConfig? mergeConfig = null, IPayloadPreparationService? mockedPayloadService = null, IReleaseSpec? releaseSpec = null) + => await CreateBaseBlockChain(mergeConfig, mockedPayloadService).Build(new SingleReleaseSpecProvider(releaseSpec ?? London.Instance, 1)); + + protected async Task<MergeTestBlockchain> CreateBlockChain(ISpecProvider specProvider) + => await CreateBaseBlockChain(null, null).Build(specProvider); private IEngineRpcModule CreateEngineModule(MergeTestBlockchain chain, ISyncConfig? syncConfig = null, TimeSpan? newPayloadTimeout = null, int newPayloadCacheSize = 50) { @@ -65,7 +70,7 @@ private IEngineRpcModule CreateEngineModule(MergeTestBlockchain chain, ISyncConf new GetPayloadV2Handler( chain.PayloadPreparationService!, chain.LogManager), - new NewPayloadV1Handler( + new NewPayloadHandler( chain.BlockValidator, chain.BlockTree, new InitConfig(), @@ -81,7 +86,7 @@ private IEngineRpcModule CreateEngineModule(MergeTestBlockchain chain, ISyncConf chain.LogManager, newPayloadTimeout, newPayloadCacheSize), - new ForkchoiceUpdatedV1Handler( + new ForkchoiceUpdatedHandler( chain.BlockTree, chain.BlockFinalizationManager, chain.PoSSwitcher, @@ -92,6 +97,7 @@ private IEngineRpcModule CreateEngineModule(MergeTestBlockchain chain, ISyncConf chain.BeaconSync, chain.BeaconPivot, peerRefresher, + chain.SpecProvider, chain.LogManager), new ExecutionStatusHandler(chain.BlockTree), new GetPayloadBodiesByHashV1Handler(chain.BlockTree, chain.LogManager), @@ -108,13 +114,11 @@ public class MergeTestBlockchain : TestBlockchain public IPayloadPreparationService? PayloadPreparationService { get; set; } - public ISealValidator SealValidator { get; set; } - - public IManualBlockProductionTrigger BlockProductionTrigger { get; set; } = new BuildBlocksWhenRequested(); + public ISealValidator? SealValidator { get; set; } - public IBeaconPivot BeaconPivot { get; set; } + public IBeaconPivot? BeaconPivot { get; set; } - public BeaconSync BeaconSync { get; set; } + public BeaconSync? BeaconSync { get; set; } private int _blockProcessingThrottle = 0; @@ -139,11 +143,11 @@ public MergeTestBlockchain(IMergeConfig? mergeConfig = null, IPayloadPreparation public sealed override ILogManager LogManager { get; } = LimboLogs.Instance; - public IEthSyncingInfo EthSyncingInfo { get; protected set; } + public IEthSyncingInfo? EthSyncingInfo { get; protected set; } protected override IBlockProducer CreateTestBlockProducer(TxPoolTxSource txPoolTxSource, ISealer sealer, ITransactionComparerProvider transactionComparerProvider) { - SealEngine = new MergeSealEngine(SealEngine, PoSSwitcher, SealValidator, LogManager); + SealEngine = new MergeSealEngine(SealEngine, PoSSwitcher, SealValidator!, LogManager); IBlockProducer preMergeBlockProducer = base.CreateTestBlockProducer(txPoolTxSource, sealer, transactionComparerProvider); BlocksConfig blocksConfig = new() { MinGasPrice = 0 }; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs index f2508f3c10e..7faaf26c5e4 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs @@ -6,10 +6,8 @@ using System.Threading; using System.Threading.Tasks; using FluentAssertions; -using Jint; using Nethermind.Blockchain; using Nethermind.Blockchain.Synchronization; -using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; @@ -17,7 +15,6 @@ using Nethermind.Int256; using Nethermind.JsonRpc; using Nethermind.Merge.Plugin.Data; -using Nethermind.Merge.Plugin.Data.V1; using Nethermind.Merge.Plugin.Synchronization; using NUnit.Framework; @@ -45,7 +42,7 @@ public async Task forkChoiceUpdatedV1_unknown_block_initiates_syncing() .WithAuthor(Address.Zero) .WithPostMergeFlag(true) .TestObject; - await rpc.engine_newPayloadV1(new ExecutionPayloadV1(block)); + await rpc.engine_newPayloadV1(new ExecutionPayload(block)); // sync has not started yet chain.BeaconSync.IsBeaconSyncHeadersFinished().Should().BeTrue(); chain.BeaconSync.IsBeaconSyncFinished(block.Header).Should().BeTrue(); @@ -118,7 +115,7 @@ public async Task forkChoiceUpdatedV1_unknown_block_parent_while_syncing_initiat .WithPostMergeFlag(true) .TestObject; - await rpc.engine_newPayloadV1(new ExecutionPayloadV1(block)); + await rpc.engine_newPayloadV1(new ExecutionPayload(block)); // sync has not started yet chain.BeaconSync.IsBeaconSyncHeadersFinished().Should().BeTrue(); chain.BeaconSync.IsBeaconSyncFinished(block.Header).Should().BeTrue(); @@ -151,7 +148,7 @@ public async Task forkChoiceUpdatedV1_unknown_block_parent_while_syncing_initiat pointers.LowestInsertedHeader = block.Header; AssertBlockTreePointers(chain.BlockTree, pointers); - await rpc.engine_newPayloadV1(new ExecutionPayloadV1(nextUnconnectedBlock)); + await rpc.engine_newPayloadV1(new ExecutionPayload(nextUnconnectedBlock)); forkchoiceStateV1 = new(nextUnconnectedBlock.Hash!, startingHead, startingHead); forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should() @@ -189,7 +186,7 @@ public async Task should_return_invalid_lvh_null_on_invalid_blocks_during_the_sy .WithAuthor(Address.Zero) .WithPostMergeFlag(true) .TestObject; - ExecutionPayloadV1 startingNewPayload = new(block); + ExecutionPayload startingNewPayload = new(block); await rpc.engine_newPayloadV1(startingNewPayload); ForkchoiceStateV1 forkchoiceStateV1 = new(block.Hash!, startingHead, startingHead); @@ -198,20 +195,20 @@ public async Task should_return_invalid_lvh_null_on_invalid_blocks_during_the_sy forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should() .Be(nameof(PayloadStatusV1.Syncing).ToUpper()); - ExecutionPayloadV1[] requests = CreateBlockRequestBranch(startingNewPayload, TestItem.AddressD, 1); - foreach (ExecutionPayloadV1 r in requests) + ExecutionPayload[] requests = CreateBlockRequestBranch(startingNewPayload, TestItem.AddressD, 1); + foreach (ExecutionPayload r in requests) { ResultWrapper<PayloadStatusV1> payloadStatus = await rpc.engine_newPayloadV1(r); payloadStatus.Data.Status.Should().Be(nameof(PayloadStatusV1.Syncing).ToUpper()); } - ExecutionPayloadV1[] invalidRequests = CreateBlockRequestBranch(requests[0], TestItem.AddressD, 1); - foreach (ExecutionPayloadV1 r in invalidRequests) + ExecutionPayload[] invalidRequests = CreateBlockRequestBranch(requests[0], TestItem.AddressD, 1); + foreach (ExecutionPayload r in invalidRequests) { r.TryGetBlock(out Block? newBlock); newBlock!.Header.GasLimit = long.MaxValue; // incorrect gas limit newBlock.Header.Hash = newBlock.CalculateHash(); - ResultWrapper<PayloadStatusV1> payloadStatus = await rpc.engine_newPayloadV1(new ExecutionPayloadV1(newBlock)); + ResultWrapper<PayloadStatusV1> payloadStatus = await rpc.engine_newPayloadV1(new ExecutionPayload(newBlock)); payloadStatus.Data.Status.Should().Be(nameof(PayloadStatusV1.Invalid).ToUpper()); payloadStatus.Data.LatestValidHash.Should().BeNull(); } @@ -224,10 +221,10 @@ public async Task newPayloadV1_can_insert_blocks_from_cache_when_syncing() IEngineRpcModule rpc = CreateEngineModule(chain); Keccak startingHead = chain.BlockTree.HeadHash; - ExecutionPayloadV1 parentBlockRequest = new(Build.A.Block.WithNumber(2).TestObject); - ExecutionPayloadV1[] requests = CreateBlockRequestBranch(parentBlockRequest, Address.Zero, 7); + ExecutionPayload parentBlockRequest = new(Build.A.Block.WithNumber(2).TestObject); + ExecutionPayload[] requests = CreateBlockRequestBranch(parentBlockRequest, Address.Zero, 7); ResultWrapper<PayloadStatusV1> payloadStatus; - foreach (ExecutionPayloadV1 r in requests) + foreach (ExecutionPayload r in requests) { payloadStatus = await rpc.engine_newPayloadV1(r); payloadStatus.Data.Status.Should().Be(nameof(PayloadStatusV1.Syncing).ToUpper()); @@ -282,15 +279,15 @@ public async Task first_new_payload_set_beacon_main_chain() .WithAuthor(Address.Zero) .WithPostMergeFlag(true) .TestObject; - ExecutionPayloadV1 startingNewPayload = new(block); + ExecutionPayload startingNewPayload = new(block); await rpc.engine_newPayloadV1(startingNewPayload); ForkchoiceStateV1 forkchoiceStateV1 = new(block.Hash!, startingHead, startingHead); ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should() .Be(nameof(PayloadStatusV1.Syncing).ToUpper()); - ExecutionPayloadV1[] requests = CreateBlockRequestBranch(startingNewPayload, Address.Zero, 4); - foreach (ExecutionPayloadV1 r in requests) + ExecutionPayload[] requests = CreateBlockRequestBranch(startingNewPayload, Address.Zero, 4); + foreach (ExecutionPayload r in requests) { ResultWrapper<PayloadStatusV1> payloadStatus = await rpc.engine_newPayloadV1(r); payloadStatus.Data.Status.Should().Be(nameof(PayloadStatusV1.Syncing).ToUpper()); @@ -323,15 +320,15 @@ public async Task repeated_new_payloads_do_not_change_metadata() .WithAuthor(Address.Zero) .WithPostMergeFlag(true) .TestObject; - ExecutionPayloadV1 startingNewPayload = new(block); + ExecutionPayload startingNewPayload = new(block); await rpc.engine_newPayloadV1(startingNewPayload); ForkchoiceStateV1 forkchoiceStateV1 = new(block.Hash!, startingHead, startingHead); ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should() .Be(nameof(PayloadStatusV1.Syncing).ToUpper()); - ExecutionPayloadV1[] requests = CreateBlockRequestBranch(startingNewPayload, Address.Zero, 4); - foreach (ExecutionPayloadV1 r in requests) + ExecutionPayload[] requests = CreateBlockRequestBranch(startingNewPayload, Address.Zero, 4); + foreach (ExecutionPayload r in requests) { ResultWrapper<PayloadStatusV1> payloadStatus = await rpc.engine_newPayloadV1(r); payloadStatus.Data.Status.Should().Be(nameof(PayloadStatusV1.Syncing).ToUpper()); @@ -341,7 +338,7 @@ public async Task repeated_new_payloads_do_not_change_metadata() lvl!.BlockInfos[0].Metadata.Should().Be(BlockMetadata.BeaconBody | BlockMetadata.BeaconHeader | BlockMetadata.BeaconMainChain); } - foreach (ExecutionPayloadV1 r in requests) + foreach (ExecutionPayload r in requests) { ResultWrapper<PayloadStatusV1> payloadStatus = await rpc.engine_newPayloadV1(r); payloadStatus.Data.Status.Should().Be(nameof(PayloadStatusV1.Syncing).ToUpper()); @@ -381,7 +378,7 @@ public async Task Can_set_beacon_pivot_in_new_payload_if_null() newBlock2.CalculateHash(); chain.BeaconPivot.BeaconPivotExists().Should().BeFalse(); - ResultWrapper<PayloadStatusV1> result = await rpc.engine_newPayloadV1(new ExecutionPayloadV1(newBlock2)); + ResultWrapper<PayloadStatusV1> result = await rpc.engine_newPayloadV1(new ExecutionPayload(newBlock2)); result.Data.Status.Should().Be(PayloadStatus.Syncing); chain.BeaconPivot.BeaconPivotExists().Should().BeTrue(); } @@ -415,7 +412,7 @@ public async Task BeaconMainChain_is_correctly_set_when_block_was_not_processed( .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")).TestObject; newBlock2.CalculateHash(); - await rpc.engine_newPayloadV1(new ExecutionPayloadV1(newBlock2)); + await rpc.engine_newPayloadV1(new ExecutionPayload(newBlock2)); await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(newBlock2.Hash!, newBlock2.Hash!, newBlock2.Hash!), null); chain.BlockTree.FindLevel(10)!.BlockInfos[0].Metadata.Should().Be(BlockMetadata.None); @@ -453,7 +450,7 @@ public async Task Repeated_block_do_not_change_metadata() newBlock2.CalculateHash(); await chain.BlockTree.SuggestBlockAsync(newBlock2!, BlockTreeSuggestOptions.FillBeaconBlock); - await rpc.engine_newPayloadV1(new ExecutionPayloadV1(newBlock2)); + await rpc.engine_newPayloadV1(new ExecutionPayload(newBlock2)); Block? block = chain.BlockTree.FindBlock(newBlock2.GetOrCalculateHash(), BlockTreeLookupOptions.None); block.TotalDifficulty.Should().NotBe((UInt256)0); BlockInfo blockInfo = chain.BlockTree.FindLevel(newBlock2.Number!).BlockInfos[0]; @@ -481,15 +478,15 @@ public async Task second_new_payload_should_not_set_beacon_main_chain() .WithAuthor(Address.Zero) .WithPostMergeFlag(true) .TestObject; - ExecutionPayloadV1 startingNewPayload = new(block); + ExecutionPayload startingNewPayload = new(block); await rpc.engine_newPayloadV1(startingNewPayload); ForkchoiceStateV1 forkchoiceStateV1 = new(block.Hash!, startingHead, startingHead); ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should() .Be(nameof(PayloadStatusV1.Syncing).ToUpper()); - ExecutionPayloadV1[] requests = CreateBlockRequestBranch(startingNewPayload, Address.Zero, 4); - foreach (ExecutionPayloadV1 r in requests) + ExecutionPayload[] requests = CreateBlockRequestBranch(startingNewPayload, Address.Zero, 4); + foreach (ExecutionPayload r in requests) { ResultWrapper<PayloadStatusV1> payloadStatus = await rpc.engine_newPayloadV1(r); payloadStatus.Data.Status.Should().Be(nameof(PayloadStatusV1.Syncing).ToUpper()); @@ -499,8 +496,8 @@ public async Task second_new_payload_should_not_set_beacon_main_chain() lvl!.BlockInfos[0].Metadata.Should().Be(BlockMetadata.BeaconBody | BlockMetadata.BeaconHeader | BlockMetadata.BeaconMainChain); } - ExecutionPayloadV1[] secondNewPayloads = CreateBlockRequestBranch(startingNewPayload, TestItem.AddressD, 4); - foreach (ExecutionPayloadV1 r in secondNewPayloads) + ExecutionPayload[] secondNewPayloads = CreateBlockRequestBranch(startingNewPayload, TestItem.AddressD, 4); + foreach (ExecutionPayload r in secondNewPayloads) { ResultWrapper<PayloadStatusV1> payloadStatus = await rpc.engine_newPayloadV1(r); payloadStatus.Data.Status.Should().Be(nameof(PayloadStatusV1.Syncing).ToUpper()); @@ -541,18 +538,18 @@ public async Task should_reorg_during_the_sync(int initialChainPayloadsCount, in .WithAuthor(Address.Zero) .WithPostMergeFlag(true) .TestObject; - ExecutionPayloadV1 startingNewPayload = new(block); + ExecutionPayload startingNewPayload = new(block); await rpc.engine_newPayloadV1(startingNewPayload); ForkchoiceStateV1 forkchoiceStateV1 = new(block.Hash!, startingHead, startingHead); await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); - ExecutionPayloadV1[] initialBranchPayloads = CreateBlockRequestBranch(startingNewPayload, Address.Zero, initialChainPayloadsCount); - foreach (ExecutionPayloadV1 r in initialBranchPayloads) + ExecutionPayload[] initialBranchPayloads = CreateBlockRequestBranch(startingNewPayload, Address.Zero, initialChainPayloadsCount); + foreach (ExecutionPayload r in initialBranchPayloads) { await rpc.engine_newPayloadV1(r); } - ExecutionPayloadV1[] newBranchPayloads = CreateBlockRequestBranch(startingNewPayload, TestItem.AddressD, reorgedChainPayloadCount); - foreach (ExecutionPayloadV1 r in newBranchPayloads) + ExecutionPayload[] newBranchPayloads = CreateBlockRequestBranch(startingNewPayload, TestItem.AddressD, reorgedChainPayloadCount); + foreach (ExecutionPayload r in newBranchPayloads) { await rpc.engine_newPayloadV1(r); } @@ -561,9 +558,9 @@ public async Task should_reorg_during_the_sync(int initialChainPayloadsCount, in ForkchoiceStateV1 forkchoiceStateV1Reorg = new(lastHash, lastHash, lastHash); await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1Reorg); - IEnumerable<ExecutionPayloadV1> mainChainRequests = newBranchPayloads.Take(reorgToIndex + 1 ?? newBranchPayloads.Length); - ExecutionPayloadV1[] requestsToIterate = newBranchPayloads.Length >= initialBranchPayloads.Length ? newBranchPayloads : initialBranchPayloads; - foreach (ExecutionPayloadV1 r in requestsToIterate) + IEnumerable<ExecutionPayload> mainChainRequests = newBranchPayloads.Take(reorgToIndex + 1 ?? newBranchPayloads.Length); + ExecutionPayload[] requestsToIterate = newBranchPayloads.Length >= initialBranchPayloads.Length ? newBranchPayloads : initialBranchPayloads; + foreach (ExecutionPayload r in requestsToIterate) { ChainLevelInfo? lvl = chain.BlockTree.FindLevel(r.BlockNumber); foreach (BlockInfo blockInfo in lvl!.BlockInfos) @@ -582,7 +579,7 @@ public async Task Blocks_from_cache_inserted_when_fast_headers_sync_finish_befor using MergeTestBlockchain chain = await CreateBlockChain(); Keccak startingHead = chain.BlockTree.HeadHash; IEngineRpcModule rpc = CreateEngineModule(chain); - ExecutionPayloadV1[] requests = CreateBlockRequestBranch(new ExecutionPayloadV1(chain.BlockTree.Head!), Address.Zero, 7); + ExecutionPayload[] requests = CreateBlockRequestBranch(new ExecutionPayload(chain.BlockTree.Head!), Address.Zero, 7); ResultWrapper<PayloadStatusV1> payloadStatus; for (int i = 4; i < requests.Length - 1; i++) @@ -599,7 +596,7 @@ public async Task Blocks_from_cache_inserted_when_fast_headers_sync_finish_befor .Be(nameof(PayloadStatusV1.Syncing).ToUpper()); // complete headers sync BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.BeaconHeaderMetadata - | BlockTreeInsertHeaderOptions.TotalDifficultyNotNeeded; + | BlockTreeInsertHeaderOptions.TotalDifficultyNotNeeded; for (int i = 0; i < requests.Length - 2; i++) { requests[i].TryGetBlock(out Block? block); @@ -630,7 +627,7 @@ public async Task Maintain_correct_pointers_for_beacon_sync_in_archive_sync() Keccak startingHead = chain.BlockTree.HeadHash; // create 7 block gap int gap = 7; - ExecutionPayloadV1 headBlockRequest = new(chain.BlockTree.Head!); + ExecutionPayload headBlockRequest = new(chain.BlockTree.Head!); Block[] missingBlocks = new Block[gap]; for (int i = 0; i < gap; i++) { @@ -640,7 +637,7 @@ public async Task Maintain_correct_pointers_for_beacon_sync_in_archive_sync() } // setting up beacon pivot - ExecutionPayloadV1 pivotRequest = CreateBlockRequest(headBlockRequest, Address.Zero); + ExecutionPayload pivotRequest = CreateBlockRequest(headBlockRequest, Address.Zero); ResultWrapper<PayloadStatusV1> payloadStatus = await rpc.engine_newPayloadV1(pivotRequest); payloadStatus.Data.Status.Should().Be(nameof(PayloadStatusV1.Syncing).ToUpper()); pivotRequest.TryGetBlock(out Block? pivotBlock); @@ -662,13 +659,13 @@ public async Task Maintain_correct_pointers_for_beacon_sync_in_archive_sync() forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should() .Be(nameof(PayloadStatusV1.Syncing).ToUpper()); // trigger insertion of blocks in cache into block tree by adding new block - ExecutionPayloadV1 bestBeaconBlockRequest = CreateBlockRequest(pivotRequest, Address.Zero); + ExecutionPayload bestBeaconBlockRequest = CreateBlockRequest(pivotRequest, Address.Zero); payloadStatus = await rpc.engine_newPayloadV1(bestBeaconBlockRequest); payloadStatus.Data.Status.Should().Be(nameof(PayloadStatusV1.Syncing).ToUpper()); // simulate headers sync by inserting 3 headers from pivot backwards int filledNum = 3; BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.BeaconHeaderMetadata | - BlockTreeInsertHeaderOptions.TotalDifficultyNotNeeded; + BlockTreeInsertHeaderOptions.TotalDifficultyNotNeeded; for (int i = missingBlocks.Length; i-- > missingBlocks.Length - filledNum;) { chain.BlockTree.Insert(missingBlocks[i].Header, headerOptions); @@ -720,7 +717,7 @@ public async Task Maintain_correct_pointers_for_beacon_sync_in_archive_sync() Assert.That( () => rpc.engine_newPayloadV1(bestBeaconBlockRequest).Result.Data.Status, Is.EqualTo(PayloadStatus.Valid).After(1000, 100) - ); + ); chain.BeaconSync.ShouldBeInBeaconHeaders().Should().BeFalse(); chain.BeaconSync.IsBeaconSyncHeadersFinished().Should().BeTrue(); @@ -743,10 +740,10 @@ public async Task Maintain_correct_pointers_for_beacon_sync_in_fast_sync() IEngineRpcModule rpc = CreateEngineModule(chain, syncConfig); // create block gap from fast sync pivot int gap = 7; - ExecutionPayloadV1[] requests = - CreateBlockRequestBranch(new ExecutionPayloadV1(syncedBlockTree.Head!), Address.Zero, gap); + ExecutionPayload[] requests = + CreateBlockRequestBranch(new ExecutionPayload(syncedBlockTree.Head!), Address.Zero, gap); // setting up beacon pivot - ExecutionPayloadV1 pivotRequest = CreateBlockRequest(requests[^1], Address.Zero); + ExecutionPayload pivotRequest = CreateBlockRequest(requests[^1], Address.Zero); ResultWrapper<PayloadStatusV1> payloadStatus = await rpc.engine_newPayloadV1(pivotRequest); payloadStatus.Data.Status.Should().Be(nameof(PayloadStatusV1.Syncing).ToUpper()); pivotRequest.TryGetBlock(out Block? pivotBlock); @@ -769,12 +766,12 @@ public async Task Maintain_correct_pointers_for_beacon_sync_in_fast_sync() forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should() .Be(nameof(PayloadStatusV1.Syncing).ToUpper()); // trigger insertion of blocks in cache into block tree by adding new block - ExecutionPayloadV1 bestBeaconBlockRequest = CreateBlockRequest(pivotRequest, Address.Zero); + ExecutionPayload bestBeaconBlockRequest = CreateBlockRequest(pivotRequest, Address.Zero); payloadStatus = await rpc.engine_newPayloadV1(bestBeaconBlockRequest); payloadStatus.Data.Status.Should().Be(nameof(PayloadStatusV1.Syncing).ToUpper()); // fill in beacon headers until fast headers pivot BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.BeaconHeaderMetadata | - BlockTreeInsertHeaderOptions.TotalDifficultyNotNeeded; + BlockTreeInsertHeaderOptions.TotalDifficultyNotNeeded; for (int i = requests.Length; i-- > 0;) { requests[i].TryGetBlock(out Block? block); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.PayloadProduction.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.PayloadProduction.cs index 886a99702d0..25d4c54cd93 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.PayloadProduction.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.PayloadProduction.cs @@ -19,7 +19,7 @@ using Nethermind.JsonRpc; using Nethermind.JsonRpc.Test; using Nethermind.Merge.Plugin.BlockProduction; -using Nethermind.Merge.Plugin.Data.V1; +using Nethermind.Merge.Plugin.Data; using Nethermind.State; using NSubstitute; using NUnit.Framework; @@ -39,10 +39,10 @@ public async Task getPayloadV1_should_allow_asking_multiple_times_by_same_payloa PayloadAttributes payload = new() { Timestamp = Timestamper.UnixTime.Seconds, SuggestedFeeRecipient = Address.Zero, PrevRandao = Keccak.Zero }; Task<ResultWrapper<ForkchoiceUpdatedV1Result>> forkchoiceResponse = rpc.engine_forkchoiceUpdatedV1(forkchoiceState, payload); byte[] payloadId = Bytes.FromHexString(forkchoiceResponse.Result.Data.PayloadId!); - ResultWrapper<ExecutionPayloadV1?> responseFirst = await rpc.engine_getPayloadV1(payloadId); + ResultWrapper<ExecutionPayload?> responseFirst = await rpc.engine_getPayloadV1(payloadId); responseFirst.Should().NotBeNull(); responseFirst.Result.ResultType.Should().Be(ResultType.Success); - ResultWrapper<ExecutionPayloadV1?> responseSecond = await rpc.engine_getPayloadV1(payloadId); + ResultWrapper<ExecutionPayload?> responseSecond = await rpc.engine_getPayloadV1(payloadId); responseSecond.Should().NotBeNull(); responseSecond.Result.ResultType.Should().Be(ResultType.Success); responseSecond.Data!.BlockHash!.Should().Be(responseFirst.Data!.BlockHash!); @@ -74,7 +74,7 @@ public async Task getPayloadV1_should_return_error_if_called_after_cleanup_timer await Task.Delay(PayloadPreparationService.SlotsPerOldPayloadCleanup * 2 * timePerSlot + timePerSlot); - ResultWrapper<ExecutionPayloadV1?> response = await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId)); + ResultWrapper<ExecutionPayload?> response = await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId)); response.ErrorCode.Should().Be(MergeErrorCodes.UnknownPayload); } @@ -99,7 +99,7 @@ public async Task getPayloadV1_picks_transactions_from_pool_v1() .Result.Data.PayloadId!; await blockImprovementLock.WaitAsync(10000); - ExecutionPayloadV1 getPayloadResult = (await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId))).Data!; + ExecutionPayload getPayloadResult = (await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId))).Data!; getPayloadResult.StateRoot.Should().NotBe(chain.BlockTree.Genesis!.StateRoot!); @@ -153,7 +153,7 @@ public async Task<int> getPayloadV1_waits_for_block_production(TimeSpan delay) new PayloadAttributes { Timestamp = 100, PrevRandao = TestItem.KeccakA, SuggestedFeeRecipient = Address.Zero }) .Result.Data.PayloadId!; - ExecutionPayloadV1 getPayloadResult = (await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId))).Data!; + ExecutionPayload getPayloadResult = (await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId))).Data!; return getPayloadResult.Transactions.Length; } @@ -168,7 +168,7 @@ public async Task getPayloadV1_return_correct_block_values_for_empty_block() ulong 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); + ExecutionPayload getPayloadResult = await BuildAndGetPayloadResult(chain, rpc, payloadAttributes); getPayloadResult.ParentHash.Should().Be(startingHead); @@ -197,7 +197,7 @@ public async Task getPayloadV1_should_return_error_if_there_was_no_corresponding .PayloadId!; byte[] requestedPayloadId = Bytes.FromHexString("0x45bd36a8143d860d"); - ResultWrapper<ExecutionPayloadV1?> response = await rpc.engine_getPayloadV1(requestedPayloadId); + ResultWrapper<ExecutionPayload?> response = await rpc.engine_getPayloadV1(requestedPayloadId); response.ErrorCode.Should().Be(MergeErrorCodes.UnknownPayload); } @@ -216,9 +216,33 @@ public async Task getPayload_correctlyEncodeTransactions() 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}"); + Assert.AreEqual(result, + chain.JsonSerializer.Serialize(new + { + jsonrpc = "2.0", + result = new ExecutionPayload + { + BaseFeePerGas = 0, + BlockHash = new("0x5fd61518405272d77fd6cdc8a824a109d75343e32024ee4f6769408454b1823d"), + BlockNumber = 0, + ExtraData = Bytes.FromHexString("0x010203"), + FeeRecipient = Address.Zero, + GasLimit = 0x3d0900L, + GasUsed = 0, + LogsBloom = new Bloom(Bytes.FromHexString("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")), + ParentHash = new("0xff483e972a04a9a62bb4b7d04ae403c615604e4090521ecc5bb7af67f71be09c"), + PrevRandao = new("0x2ba5557a4c62a513c7e56d1bf13373e0da6bec016755483e91589fe1c6d212e2"), + ReceiptsRoot = new("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), + StateRoot = new("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), + Timestamp = 0xf4240UL, + Transactions = new[] { + Bytes.FromHexString("0xf85f800182520894475674cb523a0a2736b7f7534390288fce16982c018025a0634db2f18f24d740be29e03dd217eea5757ed7422680429bdd458c582721b6c2a02f0fa83931c9a99d3448a46b922261447d6a41d8a58992b5596089d15d521102"), + Bytes.FromHexString("0x02f8620180011482520894475674cb523a0a2736b7f7534390288fce16982c0180c001a0033e85439a128c42f2ba47ca278f1375ef211e61750018ff21bcd9750d1893f2a04ee981fe5261f8853f95c865232ffdab009abcc7858ca051fb624c49744bf18d") + }, + }, + id = 67 + })); } [Test] @@ -304,7 +328,7 @@ public async Task getPayloadV1_picks_transactions_from_pool_constantly_improving await blockImprovementLock.WaitAsync(100 * TestContext.CurrentContext.CurrentRepeatCount); - ExecutionPayloadV1 getPayloadResult = (await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId))).Data!; + ExecutionPayload getPayloadResult = (await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId))).Data!; List<int?> transactionsLength = improvementContextFactory.CreatedContexts .Select(c => @@ -357,7 +381,7 @@ public async Task getPayloadV1_doesnt_wait_for_improvement_when_block_is_not_emp await blockImprovementStartsLock.WaitAsync(100); // started improving block improvementContextFactory.CreatedContexts.Should().HaveCount(2); - ExecutionPayloadV1 getPayloadResult = (await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId))).Data!; + ExecutionPayload 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 8ed0c7b2917..12ea89c591a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -25,7 +25,6 @@ using Nethermind.JsonRpc.Test; using Nethermind.JsonRpc.Test.Modules; using Nethermind.Merge.Plugin.Data; -using Nethermind.Merge.Plugin.Data.V1; using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.State; @@ -33,1697 +32,1693 @@ using Newtonsoft.Json; using NUnit.Framework; -namespace Nethermind.Merge.Plugin.Test +namespace Nethermind.Merge.Plugin.Test; + +public partial class EngineModuleTests { - public partial class EngineModuleTests + [Test] + public virtual async Task processing_block_should_serialize_valid_responses() { - [Test] - public virtual async Task processing_block_should_serialize_valid_responses() - { - using MergeTestBlockchain chain = await CreateBlockChain(new MergeConfig() - { - TerminalTotalDifficulty = "0" - }); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - Keccak prevRandao = Keccak.Zero; - Address feeRecipient = TestItem.AddressC; - UInt256 timestamp = Timestamper.UnixTime.Seconds; - - - var forkChoiceUpdatedParams = new - { - headBlockHash = startingHead.ToString(), - safeBlockHash = startingHead.ToString(), - finalizedBlockHash = Keccak.Zero.ToString(), - }; - var preparePayloadParams = new - { - timestamp = timestamp.ToHexString(true), - prevRandao = prevRandao.ToString(), - suggestedFeeRecipient = feeRecipient.ToString(), - }; - string?[] parameters = - { - JsonConvert.SerializeObject(forkChoiceUpdatedParams), - JsonConvert.SerializeObject(preparePayloadParams) - }; - // prepare a payload - string result = RpcTest.TestSerializedRequest(rpc, "engine_forkchoiceUpdatedV1", parameters); - byte[] expectedPayloadId = Bytes.FromHexString("0x6454408c425ddd96"); - result.Should().Be($"{{\"jsonrpc\":\"2.0\",\"result\":{{\"payloadStatus\":{{\"status\":\"VALID\",\"latestValidHash\":\"0x1c53bdbf457025f80c6971a9cf50986974eed02f0a9acaeeb49cafef10efd133\",\"validationError\":null}},\"payloadId\":\"{expectedPayloadId.ToHexString(true)}\"}},\"id\":67}}"); - - Keccak blockHash = new("0xb1b3b07ef3832bd409a04fdea9bf2bfa83d7af0f537ff25f4a3d2eb632ebfb0f"); - var expectedPayload = new - { - parentHash = startingHead.ToString(), - feeRecipient = feeRecipient.ToString(), - stateRoot = chain.BlockTree.Head!.StateRoot!.ToString(), - receiptsRoot = chain.BlockTree.Head!.ReceiptsRoot!.ToString(), - logsBloom = Bloom.Empty.Bytes.ToHexString(true), - prevRandao = prevRandao.ToString(), - blockNumber = "0x1", - gasLimit = chain.BlockTree.Head!.GasLimit.ToHexString(true), - gasUsed = "0x0", - timestamp = timestamp.ToHexString(true), - extraData = "0x4e65746865726d696e64", // Nethermind - baseFeePerGas = "0x0", - blockHash = blockHash.ToString(), - transactions = Array.Empty<object>(), - }; - string expectedPayloadString = JsonConvert.SerializeObject(expectedPayload); - // get the payload - result = RpcTest.TestSerializedRequest(rpc, "engine_getPayloadV1", expectedPayloadId.ToHexString(true)); - result.Should().Be($"{{\"jsonrpc\":\"2.0\",\"result\":{expectedPayloadString},\"id\":67}}"); - // execute the payload - result = RpcTest.TestSerializedRequest(rpc, "engine_newPayloadV1", expectedPayloadString); - result.Should().Be($"{{\"jsonrpc\":\"2.0\",\"result\":{{\"status\":\"VALID\",\"latestValidHash\":\"{blockHash}\",\"validationError\":null}},\"id\":67}}"); - - forkChoiceUpdatedParams = new - { - headBlockHash = blockHash.ToString(true), - safeBlockHash = blockHash.ToString(true), - finalizedBlockHash = startingHead.ToString(true), - }; - parameters = new[] { JsonConvert.SerializeObject(forkChoiceUpdatedParams), null }; - // update the fork choice - result = RpcTest.TestSerializedRequest(rpc, "engine_forkchoiceUpdatedV1", parameters); - result.Should().Be("{\"jsonrpc\":\"2.0\",\"result\":{\"payloadStatus\":{\"status\":\"VALID\",\"latestValidHash\":\"" + - blockHash + - "\",\"validationError\":null},\"payloadId\":null},\"id\":67}"); - } - - [Test] - public async Task can_parse_forkchoiceUpdated_with_implicit_null_payloadAttributes() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - var forkChoiceUpdatedParams = new - { - headBlockHash = Keccak.Zero.ToString(), - safeBlockHash = Keccak.Zero.ToString(), - finalizedBlockHash = Keccak.Zero.ToString(), - }; - string[] parameters = new[] { JsonConvert.SerializeObject(forkChoiceUpdatedParams) }; - string? result = RpcTest.TestSerializedRequest(rpc, "engine_forkchoiceUpdatedV1", parameters); - result.Should().Be("{\"jsonrpc\":\"2.0\",\"result\":{\"payloadStatus\":{\"status\":\"SYNCING\",\"latestValidHash\":null,\"validationError\":null},\"payloadId\":null},\"id\":67}"); - } + using MergeTestBlockchain chain = await CreateBlockChain(new MergeConfig() + { + TerminalTotalDifficulty = "0" + }); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + Keccak prevRandao = Keccak.Zero; + Address feeRecipient = TestItem.AddressC; + UInt256 timestamp = Timestamper.UnixTime.Seconds; + var forkChoiceUpdatedParams = new + { + headBlockHash = startingHead.ToString(), + safeBlockHash = startingHead.ToString(), + finalizedBlockHash = Keccak.Zero.ToString(), + }; + var preparePayloadParams = new + { + timestamp = timestamp.ToHexString(true), + prevRandao = prevRandao.ToString(), + suggestedFeeRecipient = feeRecipient.ToString(), + }; + string?[] parameters = + { + JsonConvert.SerializeObject(forkChoiceUpdatedParams), + JsonConvert.SerializeObject(preparePayloadParams) + }; + // prepare a payload + string result = RpcTest.TestSerializedRequest(rpc, "engine_forkchoiceUpdatedV1", parameters); + byte[] expectedPayloadId = Bytes.FromHexString("0x6454408c425ddd96"); + result.Should().Be($"{{\"jsonrpc\":\"2.0\",\"result\":{{\"payloadStatus\":{{\"status\":\"VALID\",\"latestValidHash\":\"0x1c53bdbf457025f80c6971a9cf50986974eed02f0a9acaeeb49cafef10efd133\",\"validationError\":null}},\"payloadId\":\"{expectedPayloadId.ToHexString(true)}\"}},\"id\":67}}"); + + Keccak blockHash = new("0xb1b3b07ef3832bd409a04fdea9bf2bfa83d7af0f537ff25f4a3d2eb632ebfb0f"); + var expectedPayload = chain.JsonSerializer.Serialize(new ExecutionPayload + { + BaseFeePerGas = 0, + BlockHash = blockHash, + BlockNumber = 1, + ExtraData = Bytes.FromHexString("0x4e65746865726d696e64"), // Nethermind + FeeRecipient = feeRecipient, + GasLimit = chain.BlockTree.Head!.GasLimit, + GasUsed = 0, + LogsBloom = Bloom.Empty, + ParentHash = startingHead, + PrevRandao = prevRandao, + ReceiptsRoot = chain.BlockTree.Head!.ReceiptsRoot!, + StateRoot = chain.BlockTree.Head!.StateRoot!, + Timestamp = timestamp.ToUInt64(null), + Transactions = Array.Empty<byte[]>() + }); + // get the payload + result = RpcTest.TestSerializedRequest(rpc, "engine_getPayloadV1", expectedPayloadId.ToHexString(true)); + result.Should().Be($"{{\"jsonrpc\":\"2.0\",\"result\":{expectedPayload},\"id\":67}}"); + // execute the payload + result = RpcTest.TestSerializedRequest(rpc, "engine_newPayloadV1", expectedPayload); + result.Should().Be($"{{\"jsonrpc\":\"2.0\",\"result\":{{\"status\":\"VALID\",\"latestValidHash\":\"{blockHash}\",\"validationError\":null}},\"id\":67}}"); + + forkChoiceUpdatedParams = new + { + headBlockHash = blockHash.ToString(true), + safeBlockHash = blockHash.ToString(true), + finalizedBlockHash = startingHead.ToString(true), + }; + parameters = new[] { JsonConvert.SerializeObject(forkChoiceUpdatedParams), null }; + // update the fork choice + result = RpcTest.TestSerializedRequest(rpc, "engine_forkchoiceUpdatedV1", parameters); + result.Should().Be("{\"jsonrpc\":\"2.0\",\"result\":{\"payloadStatus\":{\"status\":\"VALID\",\"latestValidHash\":\"" + + blockHash + + "\",\"validationError\":null},\"payloadId\":null},\"id\":67}"); + } - [Test] - public async Task engine_forkchoiceUpdatedV1_with_payload_attributes_should_create_block_on_top_of_genesis_and_not_change_head() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - ulong timestamp = 30; - Keccak random = Keccak.Zero; - Address feeRecipient = TestItem.AddressD; - - ExecutionPayloadV1? executionPayloadV1 = await BuildAndGetPayloadResult(rpc, chain, startingHead, - Keccak.Zero, startingHead, timestamp, random, feeRecipient); - - ExecutionPayloadV1 expected = CreateParentBlockRequestOnHead(chain.BlockTree); - expected.GasLimit = 4000000L; - expected.BlockHash = ExpectedBlockHash; - expected.LogsBloom = Bloom.Empty; - expected.FeeRecipient = feeRecipient; - expected.BlockNumber = 1; - expected.PrevRandao = random; - expected.ParentHash = startingHead; - expected.SetTransactions(Array.Empty<Transaction>()); - expected.Timestamp = timestamp; - expected.PrevRandao = random; - expected.ExtraData = Encoding.UTF8.GetBytes("Nethermind"); - - executionPayloadV1.Should().BeEquivalentTo(expected); - Keccak actualHead = chain.BlockTree.HeadHash; - actualHead.Should().NotBe(expected.BlockHash); - actualHead.Should().Be(startingHead); - } + [Test] + public async Task can_parse_forkchoiceUpdated_with_implicit_null_payloadAttributes() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + var forkChoiceUpdatedParams = new + { + headBlockHash = Keccak.Zero.ToString(), + safeBlockHash = Keccak.Zero.ToString(), + finalizedBlockHash = Keccak.Zero.ToString(), + }; + string[] parameters = new[] { JsonConvert.SerializeObject(forkChoiceUpdatedParams) }; + string? result = RpcTest.TestSerializedRequest(rpc, "engine_forkchoiceUpdatedV1", parameters); + result.Should().Be("{\"jsonrpc\":\"2.0\",\"result\":{\"payloadStatus\":{\"status\":\"SYNCING\",\"latestValidHash\":null,\"validationError\":null},\"payloadId\":null},\"id\":67}"); + } - protected virtual Keccak ExpectedBlockHash => new("0x3accc4186d73f4826acf1a8da3f7c696f16c3863e4f76b1315d65daa88fe28ff"); + [Test] + public async Task engine_forkchoiceUpdatedV1_with_payload_attributes_should_create_block_on_top_of_genesis_and_not_change_head() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + ulong timestamp = 30; + Keccak random = Keccak.Zero; + Address feeRecipient = TestItem.AddressD; + + ExecutionPayload? executionPayloadV1 = await BuildAndGetPayloadResult(rpc, chain, startingHead, + Keccak.Zero, startingHead, timestamp, random, feeRecipient); + + ExecutionPayload expected = CreateParentBlockRequestOnHead(chain.BlockTree); + expected.GasLimit = 4000000L; + expected.BlockHash = ExpectedBlockHash; + expected.LogsBloom = Bloom.Empty; + expected.FeeRecipient = feeRecipient; + expected.BlockNumber = 1; + expected.PrevRandao = random; + expected.ParentHash = startingHead; + expected.SetTransactions(Array.Empty<Transaction>()); + expected.Timestamp = timestamp; + expected.PrevRandao = random; + expected.ExtraData = Encoding.UTF8.GetBytes("Nethermind"); + + executionPayloadV1.Should().BeEquivalentTo(expected); + Keccak actualHead = chain.BlockTree.HeadHash; + actualHead.Should().NotBe(expected.BlockHash); + actualHead.Should().Be(startingHead); + } - [Test] - public async Task getPayloadBodiesByHashV1_should_return_payload_bodies_in_order_of_request_block_hashes_and_nil_for_unknown_hashes() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - - ExecutionPayloadV1 executionPayloadV11 = await SendNewBlockV1(rpc, chain); - - PrivateKey from = TestItem.PrivateKeyA; - Address to = TestItem.AddressB; - Transaction[] txs = BuildTransactions(chain, executionPayloadV11.BlockHash, from, to, 3, 0, out _, out _); - chain.AddTransactions(txs); - ExecutionPayloadV1 executionPayloadV12 = await BuildAndSendNewBlockV1(rpc, chain, true); - Keccak?[] blockHashes = { executionPayloadV11.BlockHash, TestItem.KeccakA, executionPayloadV12.BlockHash }; - ExecutionPayloadBodyV1Result?[] payloadBodies = rpc.engine_getPayloadBodiesByHashV1(blockHashes).Result.Data; - ExecutionPayloadBodyV1Result?[] expected = { new(Array.Empty<Transaction>()), null, new(txs) }; - payloadBodies.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering()); - } + protected virtual Keccak ExpectedBlockHash => new("0x3accc4186d73f4826acf1a8da3f7c696f16c3863e4f76b1315d65daa88fe28ff"); - [Test] - public async Task getPayloadBodiesByRangeV1_should_return_payload_bodies_in_order_of_request_range_and_nil_for_unknown_indexes() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); + [Test] + public async Task getPayloadBodiesByHashV1_should_return_payload_bodies_in_order_of_request_block_hashes_and_null_for_unknown_hashes() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + + ExecutionPayload executionPayloadV11 = await SendNewBlockV1(rpc, chain); + + PrivateKey from = TestItem.PrivateKeyA; + Address to = TestItem.AddressB; + Transaction[] txs = BuildTransactions(chain, executionPayloadV11.BlockHash, from, to, 3, 0, out _, out _); + chain.AddTransactions(txs); + ExecutionPayload executionPayloadV12 = await BuildAndSendNewBlockV1(rpc, chain, true); + Keccak?[] blockHashes = { executionPayloadV11.BlockHash, TestItem.KeccakA, executionPayloadV12.BlockHash }; + ExecutionPayloadBodyV1Result?[] payloadBodies = rpc.engine_getPayloadBodiesByHashV1(blockHashes).Result.Data; + ExecutionPayloadBodyV1Result?[] expected = { new(Array.Empty<Transaction>()), null, new(txs) }; + payloadBodies.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering()); + } - ExecutionPayloadV1 executionPayloadV11 = await SendNewBlockV1(rpc, chain); + [Test] + public async Task getPayloadBodiesByRangeV1_should_return_payload_bodies_in_order_of_request_range_and_nil_for_unknown_indexes() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); - PrivateKey from = TestItem.PrivateKeyA; - Address to = TestItem.AddressB; - Transaction[] txs = BuildTransactions(chain, executionPayloadV11.BlockHash, from, to, 3, 0, out _, out _); - chain.AddTransactions(txs); - ExecutionPayloadV1 executionPayloadV12 = await BuildAndSendNewBlockV1(rpc, chain, true); + ExecutionPayload executionPayloadV11 = await SendNewBlockV1(rpc, chain); - await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(executionPayloadV12.BlockHash!, - executionPayloadV12.BlockHash!, executionPayloadV12.BlockHash!)); + PrivateKey from = TestItem.PrivateKeyA; + Address to = TestItem.AddressB; + Transaction[] txs = BuildTransactions(chain, executionPayloadV11.BlockHash, from, to, 3, 0, out _, out _); + chain.AddTransactions(txs); + ExecutionPayload executionPayloadV12 = await BuildAndSendNewBlockV1(rpc, chain, true); - ExecutionPayloadBodyV1Result?[] payloadBodies = rpc.engine_getPayloadBodiesByRangeV1(0, 3).Result.Data; - ExecutionPayloadBodyV1Result?[] expected = { new(Array.Empty<Transaction>()), new(txs), null }; - payloadBodies.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering()); - } + await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(executionPayloadV12.BlockHash!, + executionPayloadV12.BlockHash!, executionPayloadV12.BlockHash!)); - [Test] - public async Task getPayloadBodiesByRangeV1_empty_response() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); + ExecutionPayloadBodyV1Result?[] payloadBodies = rpc.engine_getPayloadBodiesByRangeV1(0, 3).Result.Data; + ExecutionPayloadBodyV1Result?[] expected = { new(Array.Empty<Transaction>()), new(txs), null }; + payloadBodies.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering()); + } - ExecutionPayloadBodyV1Result?[] payloadBodies = rpc.engine_getPayloadBodiesByRangeV1(0, 0).Result.Data; - ExecutionPayloadBodyV1Result?[] expected = { }; - payloadBodies.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering()); - } + [Test] + public async Task getPayloadBodiesByRangeV1_empty_response() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); - [Test] - public async Task getPayloadBodiesByRangeV1_should_fail_when_too_many_payloads_requested() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); + ExecutionPayloadBodyV1Result?[] payloadBodies = rpc.engine_getPayloadBodiesByRangeV1(0, 0).Result.Data; + ExecutionPayloadBodyV1Result?[] expected = { }; + payloadBodies.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering()); + } - var result = rpc.engine_getPayloadBodiesByRangeV1(0, 1025); - result.Result.ErrorCode.Should().Be(-32005); - } + [Test] + public async Task getPayloadBodiesByRangeV1_should_fail_when_too_many_payloads_requested() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); - [Test] - public async Task getPayloadBodiesByRangeV1_should_return_canonical() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); + var result = rpc.engine_getPayloadBodiesByRangeV1(0, 1025); + result.Result.ErrorCode.Should().Be(-32005); + } - ExecutionPayloadV1 executionPayloadV11 = await SendNewBlockV1(rpc, chain); + [Test] + public async Task getPayloadBodiesByRangeV1_should_return_canonical() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); - await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(executionPayloadV11.BlockHash!, - executionPayloadV11.BlockHash!, executionPayloadV11.BlockHash!)); + ExecutionPayload executionPayloadV11 = await SendNewBlockV1(rpc, chain); - Block head = chain.BlockTree.Head!; + await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(executionPayloadV11.BlockHash!, + executionPayloadV11.BlockHash!, executionPayloadV11.BlockHash!)); - PrivateKey from = TestItem.PrivateKeyA; + Block head = chain.BlockTree.Head!; - // First branch - { - Transaction[] txsA = - BuildTransactions(chain, executionPayloadV11.BlockHash!, from, TestItem.AddressA, 1, 0, out _, out _); - chain.AddTransactions(txsA); + PrivateKey from = TestItem.PrivateKeyA; - ExecutionPayloadV1 executionPayloadV12A = await BuildAndGetPayloadResult(rpc, chain, - head.Hash!, head.Hash!, head.Hash!, 1001, Keccak.Zero, Address.Zero); + // First branch + { + Transaction[] txsA = + BuildTransactions(chain, executionPayloadV11.BlockHash!, from, TestItem.AddressA, 1, 0, out _, out _); + chain.AddTransactions(txsA); - ResultWrapper<PayloadStatusV1> executePayloadResultA = - await rpc.engine_newPayloadV1(executionPayloadV12A); - executePayloadResultA.Data.Status.Should().Be(PayloadStatus.Valid); + ExecutionPayload executionPayloadV12A = await BuildAndGetPayloadResult(rpc, chain, + head.Hash!, head.Hash!, head.Hash!, 1001, Keccak.Zero, Address.Zero); - (await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(executionPayloadV12A.BlockHash!, - head.Hash!, head.Hash!))).Data.PayloadStatus.Status.Should() - .Be(PayloadStatus.Valid); + ResultWrapper<PayloadStatusV1> executePayloadResultA = + await rpc.engine_newPayloadV1(executionPayloadV12A); + executePayloadResultA.Data.Status.Should().Be(PayloadStatus.Valid); - ExecutionPayloadBodyV1Result?[] payloadBodiesA = rpc.engine_getPayloadBodiesByRangeV1(1, 3).Result.Data; - ExecutionPayloadBodyV1Result?[] expectedA = { new(Array.Empty<Transaction>()), new(txsA), null }; + (await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(executionPayloadV12A.BlockHash!, + head.Hash!, head.Hash!))).Data.PayloadStatus.Status.Should() + .Be(PayloadStatus.Valid); - payloadBodiesA.Should().BeEquivalentTo(expectedA, o => o.WithStrictOrdering()); - } + ExecutionPayloadBodyV1Result?[] payloadBodiesA = rpc.engine_getPayloadBodiesByRangeV1(1, 3).Result.Data; + ExecutionPayloadBodyV1Result?[] expectedA = { new(Array.Empty<Transaction>()), new(txsA), null }; - // Second branch - { - Block? newBlock = Build.A.Block - .WithNumber(head.Number + 1) - .WithParent(head) - .WithNonce(0) - .WithDifficulty(0) - .WithStateRoot(head.StateRoot!) - .WithBeneficiary(Build.An.Address.TestObject) - .TestObject; - - (await rpc.engine_newPayloadV1(new ExecutionPayloadV1(newBlock))).Data.Status.Should().Be(PayloadStatus.Valid); - - await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(newBlock.Hash!, - newBlock.Hash!, newBlock.Hash!)); - - ExecutionPayloadBodyV1Result?[] payloadBodiesB = rpc.engine_getPayloadBodiesByRangeV1(1, 3).Result.Data; - ExecutionPayloadBodyV1Result?[] expectedB = - { - new(Array.Empty<Transaction>()), new(Array.Empty<Transaction>()), null - }; - - payloadBodiesB.Should().BeEquivalentTo(expectedB, o => o.WithStrictOrdering()); - } + payloadBodiesA.Should().BeEquivalentTo(expectedA, o => o.WithStrictOrdering()); } - [Test] - public async Task forkchoiceUpdatedV1_should_not_create_block_or_change_head_with_unknown_parent() + // Second branch { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - Keccak notExistingHash = TestItem.KeccakH; - ulong timestamp = Timestamper.UnixTime.Seconds; - Keccak random = Keccak.Zero; - Address feeRecipient = Address.Zero; - - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedV1Response = await rpc.engine_forkchoiceUpdatedV1( - new ForkchoiceStateV1(notExistingHash, Keccak.Zero, notExistingHash), - new PayloadAttributes { Timestamp = timestamp, SuggestedFeeRecipient = feeRecipient, PrevRandao = random }); - - forkchoiceUpdatedV1Response.Data.PayloadStatus.Status.Should() - .Be(PayloadStatus.Syncing); // ToDo wait for final PostMerge sync - byte[] payloadId = Bytes.FromHexString("0x5d071947bfcc3e65"); - ResultWrapper<ExecutionPayloadV1?> getResponse = await rpc.engine_getPayloadV1(payloadId); - - getResponse.ErrorCode.Should().Be(MergeErrorCodes.UnknownPayload); - Keccak actualHead = chain.BlockTree.HeadHash; - actualHead.Should().NotBe(notExistingHash); - actualHead.Should().Be(startingHead); - } + Block? newBlock = Build.A.Block + .WithNumber(head.Number + 1) + .WithParent(head) + .WithNonce(0) + .WithDifficulty(0) + .WithStateRoot(head.StateRoot!) + .WithBeneficiary(Build.An.Address.TestObject) + .TestObject; - [Test] - public async Task executePayloadV1_accepts_previously_assembled_block_multiple_times([Values(1, 3)] int times) - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - BlockHeader startingBestSuggestedHeader = chain.BlockTree.BestSuggestedHeader!; - ExecutionPayloadV1 getPayloadResult = await BuildAndGetPayloadResult(chain, rpc); - getPayloadResult.ParentHash.Should().Be(startingHead); + (await rpc.engine_newPayloadV1(new ExecutionPayload(newBlock))).Data.Status.Should().Be(PayloadStatus.Valid); + await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(newBlock.Hash!, + newBlock.Hash!, newBlock.Hash!)); - for (int i = 0; i < times; i++) + ExecutionPayloadBodyV1Result?[] payloadBodiesB = rpc.engine_getPayloadBodiesByRangeV1(1, 3).Result.Data; + ExecutionPayloadBodyV1Result?[] expectedB = { - ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); - executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); - } + new(Array.Empty<Transaction>()), new(Array.Empty<Transaction>()), null + }; - Keccak bestSuggestedHeaderHash = chain.BlockTree.BestSuggestedHeader!.Hash!; - bestSuggestedHeaderHash.Should().Be(getPayloadResult.BlockHash); - bestSuggestedHeaderHash.Should().NotBe(startingBestSuggestedHeader!.Hash!); + payloadBodiesB.Should().BeEquivalentTo(expectedB, o => o.WithStrictOrdering()); } + } - [Test] - public async Task executePayloadV1_accepts_previously_prepared_block_multiple_times([Values(1, 3)] int times) - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - BlockHeader startingBestSuggestedHeader = chain.BlockTree.BestSuggestedHeader!; - ExecutionPayloadV1 getPayloadResult = await PrepareAndGetPayloadResultV1(chain, rpc); - getPayloadResult.ParentHash.Should().Be(startingHead); - + [Test] + public async Task forkchoiceUpdatedV1_should_not_create_block_or_change_head_with_unknown_parent() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + Keccak notExistingHash = TestItem.KeccakH; + ulong timestamp = Timestamper.UnixTime.Seconds; + Keccak random = Keccak.Zero; + Address feeRecipient = Address.Zero; + + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedV1Response = await rpc.engine_forkchoiceUpdatedV1( + new ForkchoiceStateV1(notExistingHash, Keccak.Zero, notExistingHash), + new PayloadAttributes { Timestamp = timestamp, SuggestedFeeRecipient = feeRecipient, PrevRandao = random }); + + forkchoiceUpdatedV1Response.Data.PayloadStatus.Status.Should() + .Be(PayloadStatus.Syncing); // ToDo wait for final PostMerge sync + byte[] payloadId = Bytes.FromHexString("0x5d071947bfcc3e65"); + ResultWrapper<ExecutionPayload?> getResponse = await rpc.engine_getPayloadV1(payloadId); + + getResponse.ErrorCode.Should().Be(MergeErrorCodes.UnknownPayload); + Keccak actualHead = chain.BlockTree.HeadHash; + actualHead.Should().NotBe(notExistingHash); + actualHead.Should().Be(startingHead); + } - for (int i = 0; i < times; i++) - { - ResultWrapper<PayloadStatusV1>? executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); - executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); - } + [Test] + public async Task executePayloadV1_accepts_previously_assembled_block_multiple_times([Values(1, 3)] int times) + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + BlockHeader startingBestSuggestedHeader = chain.BlockTree.BestSuggestedHeader!; + ExecutionPayload getPayloadResult = await BuildAndGetPayloadResult(chain, rpc); + getPayloadResult.ParentHash.Should().Be(startingHead); - Keccak bestSuggestedHeaderHash = chain.BlockTree.BestSuggestedHeader!.Hash!; - bestSuggestedHeaderHash.Should().Be(getPayloadResult.BlockHash); - bestSuggestedHeaderHash.Should().NotBe(startingBestSuggestedHeader!.Hash!); - } - [Test] - public async Task block_should_not_be_canonical_before_forkchoiceUpdatedV1() + for (int i = 0; i < times; i++) { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - - ExecutionPayloadV1 getPayloadResult = await BuildAndGetPayloadResult(chain, rpc); - Keccak newHead = getPayloadResult.BlockHash!; - - await rpc.engine_newPayloadV1(getPayloadResult); - chain.BlockTree.FindBlock(newHead, BlockTreeLookupOptions.RequireCanonical).Should().BeNull(); - chain.BlockTree.FindBlock(newHead, BlockTreeLookupOptions.None).Should().NotBeNull(); - - await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(newHead, Keccak.Zero, Keccak.Zero)); - chain.BlockTree.FindBlock(newHead, BlockTreeLookupOptions.RequireCanonical).Should().NotBeNull(); - chain.BlockTree.FindBlock(newHead, BlockTreeLookupOptions.None).Should().NotBeNull(); + ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); + executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); } - [Test] - public async Task block_should_not_be_canonical_after_reorg() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - Keccak finalizedHash = Keccak.Zero; - ulong timestamp = 30; - Keccak random = Keccak.Zero; - Address feeRecipientA = TestItem.AddressD; - Address feeRecipientB = TestItem.AddressE; - - ExecutionPayloadV1 getPayloadResultA = await BuildAndGetPayloadResult(rpc, chain, startingHead, - finalizedHash, startingHead, timestamp, random, feeRecipientA); - Keccak blochHashA = getPayloadResultA.BlockHash!; - - ExecutionPayloadV1 getPayloadResultB = await BuildAndGetPayloadResult(rpc, chain, startingHead, - finalizedHash, startingHead, timestamp, random, feeRecipientB); - Keccak blochHashB = getPayloadResultB.BlockHash!; - - await rpc.engine_newPayloadV1(getPayloadResultA); - chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.RequireCanonical).Should().BeNull(); - chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.RequireCanonical).Should().BeNull(); - chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.None).Should().NotBeNull(); - chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.None).Should().BeNull(); - - await rpc.engine_newPayloadV1(getPayloadResultB); - chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.RequireCanonical).Should().BeNull(); - chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.RequireCanonical).Should().BeNull(); - chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.None).Should().NotBeNull(); - chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.None).Should().NotBeNull(); - - await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(blochHashA, finalizedHash, startingHead)); - chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.RequireCanonical).Should().NotBeNull(); - chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.RequireCanonical).Should().BeNull(); - chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.None).Should().NotBeNull(); - chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.None).Should().NotBeNull(); - - await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(blochHashB, finalizedHash, startingHead)); - chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.RequireCanonical).Should().BeNull(); - chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.RequireCanonical).Should().NotBeNull(); - chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.None).Should().NotBeNull(); - chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.None).Should().NotBeNull(); - - await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(blochHashA, finalizedHash, startingHead)); - chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.RequireCanonical).Should().NotBeNull(); - chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.RequireCanonical).Should().BeNull(); - chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.None).Should().NotBeNull(); - chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.None).Should().NotBeNull(); - } + Keccak bestSuggestedHeaderHash = chain.BlockTree.BestSuggestedHeader!.Hash!; + bestSuggestedHeaderHash.Should().Be(getPayloadResult.BlockHash); + bestSuggestedHeaderHash.Should().NotBe(startingBestSuggestedHeader!.Hash!); + } - private async Task<ExecutionPayloadV1> PrepareAndGetPayloadResultV1(MergeTestBlockchain chain, - IEngineRpcModule rpc) - { - Keccak startingHead = chain.BlockTree.HeadHash; - ulong timestamp = Timestamper.UnixTime.Seconds; - Keccak random = Keccak.Zero; - Address feeRecipient = Address.Zero; - return await PrepareAndGetPayloadResultV1(rpc, startingHead, timestamp, random, feeRecipient); - } + [Test] + public async Task executePayloadV1_accepts_previously_prepared_block_multiple_times([Values(1, 3)] int times) + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + BlockHeader startingBestSuggestedHeader = chain.BlockTree.BestSuggestedHeader!; + ExecutionPayload getPayloadResult = await PrepareAndGetPayloadResultV1(chain, rpc); + getPayloadResult.ParentHash.Should().Be(startingHead); - private async Task<ExecutionPayloadV1> PrepareAndGetPayloadResultV1( - IEngineRpcModule rpc, Keccak currentHead, ulong timestamp, Keccak random, Address feeRecipient) - { - PayloadAttributes? payloadAttributes = new() - { - PrevRandao = random, - SuggestedFeeRecipient = feeRecipient, - Timestamp = timestamp - }; - ForkchoiceStateV1? forkchoiceStateV1 = new(currentHead, currentHead, currentHead); - ResultWrapper<ForkchoiceUpdatedV1Result>? forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1, payloadAttributes); - byte[] payloadId = Bytes.FromHexString(forkchoiceUpdatedResult.Data.PayloadId); - ResultWrapper<ExecutionPayloadV1?> getPayloadResult = await rpc.engine_getPayloadV1(payloadId); - return getPayloadResult.Data!; - } - public static IEnumerable WrongInputTestsV1 + for (int i = 0; i < times; i++) { - get - { - yield return GetNewBlockRequestBadDataTestCase(r => r.ReceiptsRoot, TestItem.KeccakD); - yield return GetNewBlockRequestBadDataTestCase(r => r.StateRoot, TestItem.KeccakD); - - Bloom bloom = new(); - bloom.Add(new[] - { - Build.A.LogEntry.WithAddress(TestItem.AddressA).WithTopics(TestItem.KeccakG).TestObject - }); - yield return GetNewBlockRequestBadDataTestCase(r => r.LogsBloom, bloom); - yield return GetNewBlockRequestBadDataTestCase(r => r.Transactions, new[] { new byte[] { 1 } }); - yield return GetNewBlockRequestBadDataTestCase(r => r.GasUsed, 1); - } + ResultWrapper<PayloadStatusV1>? executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); + executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); } - [Test] - public async Task executePayloadV1_unknown_parentHash_return_accepted() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - ExecutionPayloadV1 getPayloadResult = await BuildAndGetPayloadResult(chain, rpc); - Keccak blockHash = getPayloadResult.BlockHash; - getPayloadResult.ParentHash = TestItem.KeccakF; - if (blockHash == getPayloadResult.BlockHash && TryCalculateHash(getPayloadResult, out Keccak? hash)) - { - getPayloadResult.BlockHash = hash; - } + Keccak bestSuggestedHeaderHash = chain.BlockTree.BestSuggestedHeader!.Hash!; + bestSuggestedHeaderHash.Should().Be(getPayloadResult.BlockHash); + bestSuggestedHeaderHash.Should().NotBe(startingBestSuggestedHeader!.Hash!); + } - ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); - executePayloadResult.Data.Status.Should().Be(PayloadStatus.Syncing); - } + [Test] + public async Task block_should_not_be_canonical_before_forkchoiceUpdatedV1() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); - [TestCaseSource(nameof(WrongInputTestsV1))] - public async Task executePayloadV1_rejects_incorrect_input(Action<ExecutionPayloadV1> breakerAction) - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - ExecutionPayloadV1 getPayloadResult = await BuildAndGetPayloadResult(chain, rpc); - breakerAction(getPayloadResult); - if (TryCalculateHash(getPayloadResult, out Keccak? hash)) - { - getPayloadResult.BlockHash = hash; - } + ExecutionPayload getPayloadResult = await BuildAndGetPayloadResult(chain, rpc); + Keccak newHead = getPayloadResult.BlockHash!; - ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); - executePayloadResult.Data.Status.Should().Be(PayloadStatus.Invalid); - } + await rpc.engine_newPayloadV1(getPayloadResult); + chain.BlockTree.FindBlock(newHead, BlockTreeLookupOptions.RequireCanonical).Should().BeNull(); + chain.BlockTree.FindBlock(newHead, BlockTreeLookupOptions.None).Should().NotBeNull(); - [Test] - public async Task executePayloadV1_rejects_invalid_blockHash() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - ExecutionPayloadV1 getPayloadResult = await BuildAndGetPayloadResult(chain, rpc); - getPayloadResult.BlockHash = TestItem.KeccakC; + await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(newHead, Keccak.Zero, Keccak.Zero)); + chain.BlockTree.FindBlock(newHead, BlockTreeLookupOptions.RequireCanonical).Should().NotBeNull(); + chain.BlockTree.FindBlock(newHead, BlockTreeLookupOptions.None).Should().NotBeNull(); + } - ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); - executePayloadResult.Data.Status.Should().Be(PayloadStatus.InvalidBlockHash); - } + [Test] + public async Task block_should_not_be_canonical_after_reorg() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + Keccak finalizedHash = Keccak.Zero; + ulong timestamp = 30; + Keccak random = Keccak.Zero; + Address feeRecipientA = TestItem.AddressD; + Address feeRecipientB = TestItem.AddressE; + + ExecutionPayload getPayloadResultA = await BuildAndGetPayloadResult(rpc, chain, startingHead, + finalizedHash, startingHead, timestamp, random, feeRecipientA); + Keccak blochHashA = getPayloadResultA.BlockHash!; + + ExecutionPayload getPayloadResultB = await BuildAndGetPayloadResult(rpc, chain, startingHead, + finalizedHash, startingHead, timestamp, random, feeRecipientB); + Keccak blochHashB = getPayloadResultB.BlockHash!; + + await rpc.engine_newPayloadV1(getPayloadResultA); + chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.RequireCanonical).Should().BeNull(); + chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.RequireCanonical).Should().BeNull(); + chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.None).Should().NotBeNull(); + chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.None).Should().BeNull(); + + await rpc.engine_newPayloadV1(getPayloadResultB); + chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.RequireCanonical).Should().BeNull(); + chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.RequireCanonical).Should().BeNull(); + chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.None).Should().NotBeNull(); + chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.None).Should().NotBeNull(); + + await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(blochHashA, finalizedHash, startingHead)); + chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.RequireCanonical).Should().NotBeNull(); + chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.RequireCanonical).Should().BeNull(); + chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.None).Should().NotBeNull(); + chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.None).Should().NotBeNull(); + + await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(blochHashB, finalizedHash, startingHead)); + chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.RequireCanonical).Should().BeNull(); + chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.RequireCanonical).Should().NotBeNull(); + chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.None).Should().NotBeNull(); + chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.None).Should().NotBeNull(); + + await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(blochHashA, finalizedHash, startingHead)); + chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.RequireCanonical).Should().NotBeNull(); + chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.RequireCanonical).Should().BeNull(); + chain.BlockTree.FindBlock(blochHashA, BlockTreeLookupOptions.None).Should().NotBeNull(); + chain.BlockTree.FindBlock(blochHashB, BlockTreeLookupOptions.None).Should().NotBeNull(); + } - [Test] - public async Task executePayloadV1_rejects_block_with_invalid_timestamp() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - ExecutionPayloadV1 getPayloadResult = await BuildAndGetPayloadResult(chain, rpc); - getPayloadResult.Timestamp = (ulong)chain.BlockTree.Head!.Timestamp - 1; - getPayloadResult.TryGetBlock(out Block? block); - getPayloadResult.BlockHash = block!.Header.CalculateHash(); + private async Task<ExecutionPayload> PrepareAndGetPayloadResultV1(MergeTestBlockchain chain, + IEngineRpcModule rpc) + { + Keccak startingHead = chain.BlockTree.HeadHash; + ulong timestamp = Timestamper.UnixTime.Seconds; + Keccak random = Keccak.Zero; + Address feeRecipient = Address.Zero; + return await PrepareAndGetPayloadResultV1(rpc, startingHead, timestamp, random, feeRecipient); + } - ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); - executePayloadResult.Data.Status.Should().Be(PayloadStatus.Invalid); - } + private async Task<ExecutionPayload> PrepareAndGetPayloadResultV1( + IEngineRpcModule rpc, Keccak currentHead, ulong timestamp, Keccak random, Address feeRecipient) + { + PayloadAttributes? payloadAttributes = new() + { + PrevRandao = random, + SuggestedFeeRecipient = feeRecipient, + Timestamp = timestamp + }; + ForkchoiceStateV1? forkchoiceStateV1 = new(currentHead, currentHead, currentHead); + ResultWrapper<ForkchoiceUpdatedV1Result>? forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1, payloadAttributes); + byte[] payloadId = Bytes.FromHexString(forkchoiceUpdatedResult.Data.PayloadId); + ResultWrapper<ExecutionPayload?> getPayloadResult = await rpc.engine_getPayloadV1(payloadId); + return getPayloadResult.Data!; + } - [Test] - public async Task executePayloadV1_rejects_block_with_invalid_receiptsRoot() + public static IEnumerable WrongInputTestsV1 + { + get { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - ExecutionPayloadV1 getPayloadResult = await BuildAndGetPayloadResult(chain, rpc); - getPayloadResult.ReceiptsRoot = TestItem.KeccakA; - getPayloadResult.TryGetBlock(out Block? block); - getPayloadResult.BlockHash = block!.Header.CalculateHash(); + yield return GetNewBlockRequestBadDataTestCase(r => r.ReceiptsRoot, TestItem.KeccakD); + yield return GetNewBlockRequestBadDataTestCase(r => r.StateRoot, TestItem.KeccakD); - ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); - executePayloadResult.Data.Status.Should().Be(PayloadStatus.Invalid); - chain.BlockFinder.SearchForBlock(new BlockParameter(getPayloadResult.BlockHash)).IsError.Should().BeTrue(); + Bloom bloom = new(); + bloom.Add(new[] + { + Build.A.LogEntry.WithAddress(TestItem.AddressA).WithTopics(TestItem.KeccakG).TestObject + }); + yield return GetNewBlockRequestBadDataTestCase(r => r.LogsBloom, bloom); + yield return GetNewBlockRequestBadDataTestCase(r => r.Transactions, new[] { new byte[] { 1 } }); + yield return GetNewBlockRequestBadDataTestCase(r => r.GasUsed, 1); } + } - [Test] - public async Task executePayloadV1_result_is_fail_when_blockchainprocessor_report_exception() + [Test] + public async Task executePayloadV1_unknown_parentHash_return_syncing() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + ExecutionPayload getPayloadResult = await BuildAndGetPayloadResult(chain, rpc); + Keccak blockHash = getPayloadResult.BlockHash; + getPayloadResult.ParentHash = TestItem.KeccakF; + if (blockHash == getPayloadResult.BlockHash && TryCalculateHash(getPayloadResult, out Keccak? hash)) { - using MergeTestBlockchain chain = await CreateBaseBlockChain(null, null) - .Build(new SingleReleaseSpecProvider(London.Instance, 1)); - IEngineRpcModule rpc = CreateEngineModule(chain); - - ((TestBlockProcessorInterceptor)chain.BlockProcessor).ExceptionToThrow = - new Exception("unxpected exception"); - - ExecutionPayloadV1 executionPayload = CreateBlockRequest(CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD); - ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV1(executionPayload); - resultWrapper.Result.ResultType.Should().Be(ResultType.Failure); + getPayloadResult.BlockHash = hash; } + ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); + executePayloadResult.Data.Status.Should().Be(PayloadStatus.Syncing); + } - [TestCase(true)] - [TestCase(false)] - public virtual async Task executePayloadV1_accepts_already_known_block(bool throttleBlockProcessor) - { - using MergeTestBlockchain chain = await CreateBaseBlockChain() - .ThrottleBlockProcessor(throttleBlockProcessor ? 100 : 0) - .Build(new SingleReleaseSpecProvider(London.Instance, 1)); - - IEngineRpcModule rpc = CreateEngineModule(chain); - Block block = Build.A.Block.WithNumber(1).WithParent(chain.BlockTree.Head!).WithDifficulty(0).WithNonce(0) - .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")) - .TestObject; - block.Header.IsPostMerge = true; - block.Header.Hash = block.CalculateHash(); - using SemaphoreSlim bestBlockProcessed = new(0); - chain.BlockTree.NewHeadBlock += (s, e) => - { - if (e.Block.Hash == block!.Hash) - bestBlockProcessed.Release(1); - }; - await chain.BlockTree.SuggestBlockAsync(block!); - - await bestBlockProcessed.WaitAsync(); - ExecutionPayloadV1 blockRequest = new(block); - ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV1(blockRequest); - executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); - } - - [Test] - public async Task forkchoiceUpdatedV1_should_work_with_zero_keccak_for_finalization() + [TestCaseSource(nameof(WrongInputTestsV1))] + public async Task executePayloadV1_rejects_incorrect_input(Action<ExecutionPayload> breakerAction) + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + ExecutionPayload getPayloadResult = await BuildAndGetPayloadResult(chain, rpc); + breakerAction(getPayloadResult); + if (TryCalculateHash(getPayloadResult, out Keccak? hash)) { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - ExecutionPayloadV1 executionPayload = await SendNewBlockV1(rpc, chain); - - Keccak newHeadHash = executionPayload.BlockHash; - ForkchoiceStateV1 forkchoiceStateV1 = new(newHeadHash!, Keccak.Zero, startingHead); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); - forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - forkchoiceUpdatedResult.Data.PayloadId.Should().Be(null); - - Keccak actualHead = chain.BlockTree.HeadHash; - actualHead.Should().NotBe(startingHead); - actualHead.Should().Be(newHeadHash); - AssertExecutionStatusChanged(rpc, newHeadHash!, Keccak.Zero, startingHead); + getPayloadResult.BlockHash = hash; } - [Test] - public async Task forkchoiceUpdatedV1_should_update_finalized_block_hash() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - TestRpcBlockchain testRpc = await CreateTestRpc(chain); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - ExecutionPayloadV1 executionPayload = await SendNewBlockV1(rpc, chain); - - Keccak newHeadHash = executionPayload.BlockHash; - ForkchoiceStateV1 forkchoiceStateV1 = new(newHeadHash!, startingHead, startingHead!); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); - forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - forkchoiceUpdatedResult.Data.PayloadId.Should().Be(null); - - Keccak? actualFinalizedHash = chain.BlockTree.FinalizedHash; - actualFinalizedHash.Should().NotBeNull(); - actualFinalizedHash.Should().Be(startingHead); - - BlockForRpc blockForRpc = testRpc.EthRpcModule.eth_getBlockByNumber(BlockParameter.Finalized).Data; - blockForRpc.Should().NotBeNull(); - actualFinalizedHash = blockForRpc.Hash; - actualFinalizedHash.Should().NotBeNull(); - actualFinalizedHash.Should().Be(startingHead); - - Assert.AreEqual(actualFinalizedHash, chain.BlockFinalizationManager.LastFinalizedHash); - AssertExecutionStatusChanged(rpc, newHeadHash!, startingHead, startingHead); - } + ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); + executePayloadResult.Data.Status.Should().Be(PayloadStatus.Invalid); + } - [Test] - public async Task forkchoiceUpdatedV1_should_update_safe_block_hash() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - TestRpcBlockchain testRpc = await CreateTestRpc(chain); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - ExecutionPayloadV1 executionPayload = await SendNewBlockV1(rpc, chain); - - Keccak newHeadHash = executionPayload.BlockHash; - ForkchoiceStateV1 forkchoiceStateV1 = new(newHeadHash!, startingHead, startingHead!); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); - forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - forkchoiceUpdatedResult.Data.PayloadId.Should().Be(null); - - Keccak? actualSafeHash = chain.BlockTree.SafeHash; - actualSafeHash.Should().NotBeNull(); - actualSafeHash.Should().Be(startingHead); - - BlockForRpc blockForRpc = testRpc.EthRpcModule.eth_getBlockByNumber(BlockParameter.Safe).Data; - blockForRpc.Should().NotBeNull(); - actualSafeHash = blockForRpc.Hash; - actualSafeHash.Should().NotBeNull(); - actualSafeHash.Should().Be(startingHead); - - AssertExecutionStatusChanged(rpc, newHeadHash!, startingHead, startingHead); - } + [Test] + public async Task executePayloadV1_rejects_invalid_blockHash() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + ExecutionPayload getPayloadResult = await BuildAndGetPayloadResult(chain, rpc); + getPayloadResult.BlockHash = TestItem.KeccakC; + ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); + executePayloadResult.Data.Status.Should().Be(PayloadStatus.InvalidBlockHash); + } - [Test] - public async Task forkchoiceUpdatedV1_should_work_with_zero_keccak_as_safe_block() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - ExecutionPayloadV1 executionPayload = await SendNewBlockV1(rpc, chain); - - Keccak newHeadHash = executionPayload.BlockHash!; - ForkchoiceStateV1 forkchoiceStateV1 = new(newHeadHash, newHeadHash, Keccak.Zero); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); - forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - forkchoiceUpdatedResult.Data.PayloadId.Should().Be(null); - - Keccak actualHead = chain.BlockTree.HeadHash; - actualHead.Should().NotBe(startingHead); - actualHead.Should().Be(newHeadHash); - AssertExecutionStatusChanged(rpc, newHeadHash!, newHeadHash, Keccak.Zero); - } + [Test] + public async Task executePayloadV1_rejects_block_with_invalid_timestamp() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + ExecutionPayload getPayloadResult = await BuildAndGetPayloadResult(chain, rpc); + getPayloadResult.Timestamp = (ulong)chain.BlockTree.Head!.Timestamp - 1; + getPayloadResult.TryGetBlock(out Block? block); + getPayloadResult.BlockHash = block!.Header.CalculateHash(); + + ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); + executePayloadResult.Data.Status.Should().Be(PayloadStatus.Invalid); + } - [Test] - public async Task forkchoiceUpdatedV1_with_no_payload_attributes_should_change_head() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - ExecutionPayloadV1 executionPayload = await SendNewBlockV1(rpc, chain); - - Keccak newHeadHash = executionPayload.BlockHash!; - ForkchoiceStateV1 forkchoiceStateV1 = new(newHeadHash, startingHead, startingHead); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); - forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - forkchoiceUpdatedResult.Data.PayloadId.Should().Be(null); - - Keccak actualHead = chain.BlockTree.HeadHash; - actualHead.Should().NotBe(startingHead); - actualHead.Should().Be(newHeadHash); - AssertExecutionStatusChangedV1(rpc, newHeadHash, startingHead, startingHead); - } + [Test] + public async Task executePayloadV1_rejects_block_with_invalid_receiptsRoot() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + ExecutionPayload getPayloadResult = await BuildAndGetPayloadResult(chain, rpc); + getPayloadResult.ReceiptsRoot = TestItem.KeccakA; + getPayloadResult.TryGetBlock(out Block? block); + getPayloadResult.BlockHash = block!.Header.CalculateHash(); + + ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult); + executePayloadResult.Data.Status.Should().Be(PayloadStatus.Invalid); + chain.BlockFinder.SearchForBlock(new BlockParameter(getPayloadResult.BlockHash)).IsError.Should().BeTrue(); + } - [Test] - public async Task forkChoiceUpdatedV1_to_unknown_block_fails() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - ForkchoiceStateV1 forkchoiceStateV1 = new(TestItem.KeccakF, TestItem.KeccakF, TestItem.KeccakF); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); - forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be(nameof(PayloadStatus.Syncing).ToUpper()); // ToDo wait for final PostMerge sync - AssertExecutionStatusNotChangedV1(rpc, TestItem.KeccakF, TestItem.KeccakF, TestItem.KeccakF); - } + [Test] + public async Task executePayloadV1_result_is_fail_when_blockchainprocessor_report_exception() + { + using MergeTestBlockchain chain = await CreateBaseBlockChain(null, null) + .Build(new SingleReleaseSpecProvider(London.Instance, 1)); + IEngineRpcModule rpc = CreateEngineModule(chain); - [Test] - public async Task forkChoiceUpdatedV1_to_unknown_safeBlock_hash_should_fail() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - ExecutionPayloadV1 executionPayload = await SendNewBlockV1(rpc, chain); - - Keccak newHeadHash = executionPayload.BlockHash!; - ForkchoiceStateV1 forkchoiceStateV1 = new(newHeadHash, startingHead, TestItem.KeccakF); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1, null); - forkchoiceUpdatedResult.ErrorCode.Should().Be(MergeErrorCodes.InvalidForkchoiceState); - - Keccak actualHead = chain.BlockTree.HeadHash; - actualHead.Should().NotBe(newHeadHash); - } + ((TestBlockProcessorInterceptor)chain.BlockProcessor).ExceptionToThrow = + new Exception("unxpected exception"); - [Test] - public async Task forkChoiceUpdatedV1_no_common_branch_fails() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak? startingHead = chain.BlockTree.HeadHash; - Block parent = Build.A.Block.WithNumber(2).WithParentHash(TestItem.KeccakA).WithNonce(0).WithDifficulty(0).TestObject; - Block block = Build.A.Block.WithNumber(3).WithParent(parent).WithNonce(0).WithDifficulty(0).TestObject; + ExecutionPayload executionPayload = CreateBlockRequest(CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD); + ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV1(executionPayload); + resultWrapper.Result.ResultType.Should().Be(ResultType.Failure); + } - await rpc.engine_newPayloadV1(new ExecutionPayloadV1(parent)); - ForkchoiceStateV1 forkchoiceStateV1 = new(parent.Hash!, startingHead, startingHead); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); - forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be("SYNCING"); + [TestCase(true)] + [TestCase(false)] + public virtual async Task executePayloadV1_accepts_already_known_block(bool throttleBlockProcessor) + { + using MergeTestBlockchain chain = await CreateBaseBlockChain() + .ThrottleBlockProcessor(throttleBlockProcessor ? 100 : 0) + .Build(new SingleReleaseSpecProvider(London.Instance, 1)); + + IEngineRpcModule rpc = CreateEngineModule(chain); + Block block = Build.A.Block.WithNumber(1).WithParent(chain.BlockTree.Head!).WithDifficulty(0).WithNonce(0) + .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")) + .TestObject; + block.Header.IsPostMerge = true; + block.Header.Hash = block.CalculateHash(); + using SemaphoreSlim bestBlockProcessed = new(0); + chain.BlockTree.NewHeadBlock += (s, e) => + { + if (e.Block.Hash == block!.Hash) + bestBlockProcessed.Release(1); + }; + await chain.BlockTree.SuggestBlockAsync(block!); + + await bestBlockProcessed.WaitAsync(); + ExecutionPayload blockRequest = new(block); + ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV1(blockRequest); + executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); + } - await rpc.engine_newPayloadV1(new ExecutionPayloadV1(block)); + [Test] + public async Task forkchoiceUpdatedV1_should_work_with_zero_keccak_for_finalization() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + ExecutionPayload executionPayload = await SendNewBlockV1(rpc, chain); + + Keccak newHeadHash = executionPayload.BlockHash; + ForkchoiceStateV1 forkchoiceStateV1 = new(newHeadHash!, Keccak.Zero, startingHead); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); + forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + forkchoiceUpdatedResult.Data.PayloadId.Should().Be(null); + + Keccak actualHead = chain.BlockTree.HeadHash; + actualHead.Should().NotBe(startingHead); + actualHead.Should().Be(newHeadHash); + AssertExecutionStatusChanged(rpc, newHeadHash!, Keccak.Zero, startingHead); + } - ForkchoiceStateV1 forkchoiceStateV11 = new(parent.Hash!, startingHead, startingHead); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult_1 = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV11); - forkchoiceUpdatedResult_1.Data.PayloadStatus.Status.Should().Be("SYNCING"); + [Test] + public async Task forkchoiceUpdatedV1_should_update_finalized_block_hash() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + TestRpcBlockchain testRpc = await CreateTestRpc(chain); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + ExecutionPayload executionPayload = await SendNewBlockV1(rpc, chain); + + Keccak newHeadHash = executionPayload.BlockHash; + ForkchoiceStateV1 forkchoiceStateV1 = new(newHeadHash!, startingHead, startingHead!); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); + forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + forkchoiceUpdatedResult.Data.PayloadId.Should().Be(null); + + Keccak? actualFinalizedHash = chain.BlockTree.FinalizedHash; + actualFinalizedHash.Should().NotBeNull(); + actualFinalizedHash.Should().Be(startingHead); + + BlockForRpc blockForRpc = testRpc.EthRpcModule.eth_getBlockByNumber(BlockParameter.Finalized).Data; + blockForRpc.Should().NotBeNull(); + actualFinalizedHash = blockForRpc.Hash; + actualFinalizedHash.Should().NotBeNull(); + actualFinalizedHash.Should().Be(startingHead); + + Assert.AreEqual(actualFinalizedHash, chain.BlockFinalizationManager.LastFinalizedHash); + AssertExecutionStatusChanged(rpc, newHeadHash!, startingHead, startingHead); + } - AssertExecutionStatusNotChangedV1(rpc, block.Hash!, startingHead, startingHead); - } + [Test] + public async Task forkchoiceUpdatedV1_should_update_safe_block_hash() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + TestRpcBlockchain testRpc = await CreateTestRpc(chain); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + ExecutionPayload executionPayload = await SendNewBlockV1(rpc, chain); + + Keccak newHeadHash = executionPayload.BlockHash; + ForkchoiceStateV1 forkchoiceStateV1 = new(newHeadHash!, startingHead, startingHead!); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); + forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + forkchoiceUpdatedResult.Data.PayloadId.Should().Be(null); + + Keccak? actualSafeHash = chain.BlockTree.SafeHash; + actualSafeHash.Should().NotBeNull(); + actualSafeHash.Should().Be(startingHead); + + BlockForRpc blockForRpc = testRpc.EthRpcModule.eth_getBlockByNumber(BlockParameter.Safe).Data; + blockForRpc.Should().NotBeNull(); + actualSafeHash = blockForRpc.Hash; + actualSafeHash.Should().NotBeNull(); + actualSafeHash.Should().Be(startingHead); + + AssertExecutionStatusChanged(rpc, newHeadHash!, startingHead, startingHead); + } - [Test, NonParallelizable] - public async Task forkChoiceUpdatedV1_block_still_processing() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain, newPayloadTimeout: TimeSpan.FromMilliseconds(100)); - Keccak startingHead = chain.BlockTree.HeadHash; - Block blockTreeHead = chain.BlockTree.Head!; - Block block = Build.A.Block.WithNumber(blockTreeHead.Number + 1).WithParent(blockTreeHead).WithNonce(0).WithDifficulty(0).TestObject; + [Test] + public async Task forkchoiceUpdatedV1_should_work_with_zero_keccak_as_safe_block() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + ExecutionPayload executionPayload = await SendNewBlockV1(rpc, chain); + + Keccak newHeadHash = executionPayload.BlockHash!; + ForkchoiceStateV1 forkchoiceStateV1 = new(newHeadHash, newHeadHash, Keccak.Zero); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); + forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + forkchoiceUpdatedResult.Data.PayloadId.Should().Be(null); + + Keccak actualHead = chain.BlockTree.HeadHash; + actualHead.Should().NotBe(startingHead); + actualHead.Should().Be(newHeadHash); + AssertExecutionStatusChanged(rpc, newHeadHash!, newHeadHash, Keccak.Zero); + } - chain.ThrottleBlockProcessor(200); - ResultWrapper<PayloadStatusV1> newPayloadV1 = - await rpc.engine_newPayloadV1(new ExecutionPayloadV1(block)); - newPayloadV1.Data.Status.Should().Be("SYNCING"); + [Test] + public async Task forkchoiceUpdatedV1_with_no_payload_attributes_should_change_head() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + ExecutionPayload executionPayload = await SendNewBlockV1(rpc, chain); + + Keccak newHeadHash = executionPayload.BlockHash!; + ForkchoiceStateV1 forkchoiceStateV1 = new(newHeadHash, startingHead, startingHead); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); + forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + forkchoiceUpdatedResult.Data.PayloadId.Should().Be(null); + + Keccak actualHead = chain.BlockTree.HeadHash; + actualHead.Should().NotBe(startingHead); + actualHead.Should().Be(newHeadHash); + AssertExecutionStatusChangedV1(rpc, newHeadHash, startingHead, startingHead); + } - ForkchoiceStateV1 forkchoiceStateV1 = new(block.Hash!, startingHead, startingHead); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = - await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); - forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be("SYNCING"); + [Test] + public async Task forkChoiceUpdatedV1_to_unknown_block_fails() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + ForkchoiceStateV1 forkchoiceStateV1 = new(TestItem.KeccakF, TestItem.KeccakF, TestItem.KeccakF); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); + forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be(nameof(PayloadStatus.Syncing).ToUpper()); // ToDo wait for final PostMerge sync + AssertExecutionStatusNotChangedV1(rpc, TestItem.KeccakF, TestItem.KeccakF, TestItem.KeccakF); + } - AssertExecutionStatusNotChangedV1(rpc, block.Hash!, startingHead, startingHead); - } + [Test] + public async Task forkChoiceUpdatedV1_to_unknown_safeBlock_hash_should_fail() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + ExecutionPayload executionPayload = await SendNewBlockV1(rpc, chain); + + Keccak newHeadHash = executionPayload.BlockHash!; + ForkchoiceStateV1 forkchoiceStateV1 = new(newHeadHash, startingHead, TestItem.KeccakF); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1, null); + forkchoiceUpdatedResult.ErrorCode.Should().Be(MergeErrorCodes.InvalidForkchoiceState); + + Keccak actualHead = chain.BlockTree.HeadHash; + actualHead.Should().NotBe(newHeadHash); + } - [Test, NonParallelizable] - public async Task AlreadyKnown_not_cached_block_should_return_valid() - { - using MergeTestBlockchain? chain = await CreateBlockChain(); + [Test] + public async Task forkChoiceUpdatedV1_no_common_branch_fails() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak? startingHead = chain.BlockTree.HeadHash; + Block parent = Build.A.Block.WithNumber(2).WithParentHash(TestItem.KeccakA).WithNonce(0).WithDifficulty(0).TestObject; + Block block = Build.A.Block.WithNumber(3).WithParent(parent).WithNonce(0).WithDifficulty(0).TestObject; - IEngineRpcModule? rpc = CreateEngineModule(chain, newPayloadTimeout: TimeSpan.FromMilliseconds(100), newPayloadCacheSize: 0); - Block? head = chain.BlockTree.Head!; + await rpc.engine_newPayloadV1(new ExecutionPayload(parent)); - Block? b4 = Build.A.Block - .WithNumber(head.Number + 1) - .WithParent(head) - .WithNonce(0) - .WithDifficulty(0) - .WithStateRoot(head.StateRoot!) - .WithBeneficiary(Build.An.Address.TestObject) - .TestObject; + ForkchoiceStateV1 forkchoiceStateV1 = new(parent.Hash!, startingHead, startingHead); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); + forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be("SYNCING"); - (await rpc.engine_newPayloadV1(new ExecutionPayloadV1(b4))).Data.Status.Should().Be(PayloadStatus.Valid); + await rpc.engine_newPayloadV1(new ExecutionPayload(block)); - Block? b5 = Build.A.Block - .WithNumber(b4.Number + 1) - .WithParent(b4) - .WithNonce(0) - .WithDifficulty(0) - .WithStateRoot(b4.StateRoot!) - .TestObject; + ForkchoiceStateV1 forkchoiceStateV11 = new(parent.Hash!, startingHead, startingHead); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult_1 = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV11); + forkchoiceUpdatedResult_1.Data.PayloadStatus.Status.Should().Be("SYNCING"); - (await rpc.engine_newPayloadV1(new ExecutionPayloadV1(b5))).Data.Status.Should().Be(PayloadStatus.Valid); - (await rpc.engine_newPayloadV1(new ExecutionPayloadV1(b5))).Data.Status.Should().Be(PayloadStatus.Valid); - } + AssertExecutionStatusNotChangedV1(rpc, block.Hash!, startingHead, startingHead); + } - [Test, NonParallelizable] - public async Task Invalid_block_on_processing_wont_be_accepted_if_sent_twice_in_a_row_when_block_processing_queue_is_not_empty() - { - using MergeTestBlockchain? chain = await CreateBlockChain(); + [Test, NonParallelizable] + public async Task forkChoiceUpdatedV1_block_still_processing() + { + using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule? rpc = CreateEngineModule(chain, newPayloadTimeout: TimeSpan.FromMilliseconds(100), newPayloadCacheSize: 10); - Block? head = chain.BlockTree.Head!; + IEngineRpcModule rpc = CreateEngineModule(chain, newPayloadTimeout: TimeSpan.FromMilliseconds(100)); + Keccak startingHead = chain.BlockTree.HeadHash; + Block blockTreeHead = chain.BlockTree.Head!; + Block block = Build.A.Block.WithNumber(blockTreeHead.Number + 1).WithParent(blockTreeHead).WithNonce(0).WithDifficulty(0).TestObject; - // make sure AddressA has enough balance to send tx - chain.State.GetBalance(TestItem.AddressA).Should().BeGreaterThan(UInt256.One); + chain.ThrottleBlockProcessor(200); + ResultWrapper<PayloadStatusV1> newPayloadV1 = + await rpc.engine_newPayloadV1(new ExecutionPayload(block)); + newPayloadV1.Data.Status.Should().Be("SYNCING"); - // block is an invalid block, but it is impossible to detect until we process it. - // it is invalid because after you processs its transactions, the root of the state trie - // doesn't match the state root in the block - Block? block = Build.A.Block - .WithNumber(head.Number + 1) - .WithParent(head) - .WithNonce(0) - .WithDifficulty(0) - .WithTransactions( - Build.A.Transaction - .WithTo(TestItem.AddressD) - .WithValue(100.GWei()) - .SignedAndResolved(TestItem.PrivateKeyA) - .TestObject - ) - .WithGasUsed(21000) - .WithStateRoot(head.StateRoot!) // after processing transaction, this state root is wrong - .TestObject; + ForkchoiceStateV1 forkchoiceStateV1 = new(block.Hash!, startingHead, startingHead); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = + await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); + forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be("SYNCING"); - chain.ThrottleBlockProcessor(1000); // throttle the block processor enough so that the block processing queue is never empty - (await rpc.engine_newPayloadV1(new ExecutionPayloadV1(block))).Data.Status.Should().Be(PayloadStatus.Syncing); - (await rpc.engine_newPayloadV1(new ExecutionPayloadV1(block))).Data.Status.Should().BeOneOf(PayloadStatus.Syncing); - } + AssertExecutionStatusNotChangedV1(rpc, block.Hash!, startingHead, startingHead); + } - [Test] - public async Task forkchoiceUpdatedV1_should_change_head_when_all_parameters_are_the_newHeadHash() - { - 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<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = - await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1, null); - forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - forkchoiceUpdatedResult.Data.PayloadId.Should().Be(null); - AssertExecutionStatusChangedV1(rpc, newHeadHash, newHeadHash, newHeadHash); - } + [Test, NonParallelizable] + public async Task AlreadyKnown_not_cached_block_should_return_valid() + { + using MergeTestBlockchain? chain = await CreateBlockChain(); + + IEngineRpcModule? rpc = CreateEngineModule(chain, newPayloadTimeout: TimeSpan.FromMilliseconds(100), newPayloadCacheSize: 0); + Block? head = chain.BlockTree.Head!; + + Block? b4 = Build.A.Block + .WithNumber(head.Number + 1) + .WithParent(head) + .WithNonce(0) + .WithDifficulty(0) + .WithStateRoot(head.StateRoot!) + .WithBeneficiary(Build.An.Address.TestObject) + .TestObject; + + (await rpc.engine_newPayloadV1(new ExecutionPayload(b4))).Data.Status.Should().Be(PayloadStatus.Valid); + + Block? b5 = Build.A.Block + .WithNumber(b4.Number + 1) + .WithParent(b4) + .WithNonce(0) + .WithDifficulty(0) + .WithStateRoot(b4.StateRoot!) + .TestObject; + + (await rpc.engine_newPayloadV1(new ExecutionPayload(b5))).Data.Status.Should().Be(PayloadStatus.Valid); + (await rpc.engine_newPayloadV1(new ExecutionPayload(b5))).Data.Status.Should().Be(PayloadStatus.Valid); + } - [Test] - public async Task Can_transition_from_PoW_chain() - { - using MergeTestBlockchain chain = - await CreateBlockChain(new MergeConfig() { TerminalTotalDifficulty = "1000001" }); - IEngineRpcModule rpc = CreateEngineModule(chain); - - // adding PoW block - await chain.AddBlock(); - - // creating PoS block - Block? head = chain.BlockTree.Head; - ExecutionPayloadV1 executionPayload = await SendNewBlockV1(rpc, chain); - await rpc.engine_forkchoiceUpdatedV1( - new ForkchoiceStateV1(executionPayload.BlockHash, executionPayload.BlockHash, executionPayload.BlockHash)); - Assert.AreEqual(2, chain.BlockTree.Head!.Number); - } + [Test, NonParallelizable] + public async Task Invalid_block_on_processing_wont_be_accepted_if_sent_twice_in_a_row_when_block_processing_queue_is_not_empty() + { + using MergeTestBlockchain? chain = await CreateBlockChain(); + + IEngineRpcModule? rpc = CreateEngineModule(chain, newPayloadTimeout: TimeSpan.FromMilliseconds(100), newPayloadCacheSize: 10); + Block? head = chain.BlockTree.Head!; + + // make sure AddressA has enough balance to send tx + chain.State.GetBalance(TestItem.AddressA).Should().BeGreaterThan(UInt256.One); + + // block is an invalid block, but it is impossible to detect until we process it. + // it is invalid because after you processs its transactions, the root of the state trie + // doesn't match the state root in the block + Block? block = Build.A.Block + .WithNumber(head.Number + 1) + .WithParent(head) + .WithNonce(0) + .WithDifficulty(0) + .WithTransactions( + Build.A.Transaction + .WithTo(TestItem.AddressD) + .WithValue(100.GWei()) + .SignedAndResolved(TestItem.PrivateKeyA) + .TestObject + ) + .WithGasUsed(21000) + .WithStateRoot(head.StateRoot!) // after processing transaction, this state root is wrong + .TestObject; + + chain.ThrottleBlockProcessor(1000); // throttle the block processor enough so that the block processing queue is never empty + (await rpc.engine_newPayloadV1(new ExecutionPayload(block))).Data.Status.Should().Be(PayloadStatus.Syncing); + (await rpc.engine_newPayloadV1(new ExecutionPayload(block))).Data.Status.Should().BeOneOf(PayloadStatus.Syncing); + } - [TestCase(null)] - [TestCase(1000000000)] - [TestCase(1000001)] - public async Task executePayloadV1_should_not_accept_blocks_with_incorrect_ttd(long? terminalTotalDifficulty) - { - using MergeTestBlockchain chain = await CreateBlockChain(new MergeConfig() - { - TerminalTotalDifficulty = $"{terminalTotalDifficulty}" - }); - IEngineRpcModule rpc = CreateEngineModule(chain); - ExecutionPayloadV1 executionPayload = CreateBlockRequest(CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD); - ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV1(executionPayload); - resultWrapper.Data.Status.Should().Be(PayloadStatus.Invalid); - resultWrapper.Data.LatestValidHash.Should().Be(Keccak.Zero); - } + [Test] + public async Task forkchoiceUpdatedV1_should_change_head_when_all_parameters_are_the_newHeadHash() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + ExecutionPayload executionPayload = await SendNewBlockV1(rpc, chain); + Keccak newHeadHash = executionPayload.BlockHash; + ForkchoiceStateV1 forkchoiceStateV1 = new(newHeadHash, newHeadHash, newHeadHash); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult = + await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1, null); + forkchoiceUpdatedResult.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + forkchoiceUpdatedResult.Data.PayloadId.Should().Be(null); + AssertExecutionStatusChangedV1(rpc, newHeadHash, newHeadHash, newHeadHash); + } - [TestCase(null)] - [TestCase(1000000000)] - [TestCase(1000001)] - public async Task forkchoiceUpdatedV1_should_not_accept_blocks_with_incorrect_ttd(long? terminalTotalDifficulty) - { - using MergeTestBlockchain chain = await CreateBlockChain(new MergeConfig() - { - TerminalTotalDifficulty = $"{terminalTotalDifficulty}" - }); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak blockHash = chain.BlockTree.HeadHash; - ResultWrapper<ForkchoiceUpdatedV1Result> resultWrapper = await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(blockHash, blockHash, blockHash), null); - resultWrapper.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Invalid); - resultWrapper.Data.PayloadStatus.LatestValidHash.Should().Be(Keccak.Zero); - } + [Test] + public async Task Can_transition_from_PoW_chain() + { + using MergeTestBlockchain chain = + await CreateBlockChain(new MergeConfig() { TerminalTotalDifficulty = "1000001" }); + IEngineRpcModule rpc = CreateEngineModule(chain); + + // adding PoW block + await chain.AddBlock(); + + // creating PoS block + Block? head = chain.BlockTree.Head; + ExecutionPayload executionPayload = await SendNewBlockV1(rpc, chain); + await rpc.engine_forkchoiceUpdatedV1( + new ForkchoiceStateV1(executionPayload.BlockHash, executionPayload.BlockHash, executionPayload.BlockHash)); + Assert.AreEqual(2, chain.BlockTree.Head!.Number); + } - [Test] - public async Task executePayloadV1_on_top_of_terminal_block() - { - using MergeTestBlockchain chain = await CreateBlockChain(new MergeConfig() - { - TerminalTotalDifficulty = $"{1900000}" - }); - IEngineRpcModule rpc = CreateEngineModule(chain); - Block newBlock = Build.A.Block.WithNumber(chain.BlockTree.Head!.Number) - .WithParent(chain.BlockTree.Head!) - .WithNonce(0) - .WithDifficulty(900000) - .WithTotalDifficulty(1900000L) - .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")).TestObject; - newBlock.CalculateHash(); + [TestCase(null)] + [TestCase(1000000000)] + [TestCase(1000001)] + public async Task executePayloadV1_should_not_accept_blocks_with_incorrect_ttd(long? terminalTotalDifficulty) + { + using MergeTestBlockchain chain = await CreateBlockChain(new MergeConfig() + { + TerminalTotalDifficulty = $"{terminalTotalDifficulty}" + }); + IEngineRpcModule rpc = CreateEngineModule(chain); + ExecutionPayload executionPayload = CreateBlockRequest(CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD); + ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV1(executionPayload); + resultWrapper.Data.Status.Should().Be(PayloadStatus.Invalid); + resultWrapper.Data.LatestValidHash.Should().Be(Keccak.Zero); + } - using SemaphoreSlim bestBlockProcessed = new(0); - chain.BlockTree.NewHeadBlock += (s, e) => - { - if (e.Block.Hash == newBlock!.Hash) - bestBlockProcessed.Release(1); - }; - await chain.BlockTree.SuggestBlockAsync(newBlock); - (await bestBlockProcessed.WaitAsync(TimeSpan.FromSeconds(1))).Should().BeTrue(); + [TestCase(null)] + [TestCase(1000000000)] + [TestCase(1000001)] + public async Task forkchoiceUpdatedV1_should_not_accept_blocks_with_incorrect_ttd(long? terminalTotalDifficulty) + { + using MergeTestBlockchain chain = await CreateBlockChain(new MergeConfig() + { + TerminalTotalDifficulty = $"{terminalTotalDifficulty}" + }); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak blockHash = chain.BlockTree.HeadHash; + ResultWrapper<ForkchoiceUpdatedV1Result> resultWrapper = await rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(blockHash, blockHash, blockHash), null); + resultWrapper.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Invalid); + resultWrapper.Data.PayloadStatus.LatestValidHash.Should().Be(Keccak.Zero); + } - ExecutionPayloadV1 executionPayload = CreateBlockRequest(CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD); - ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV1(executionPayload); - resultWrapper.Data.Status.Should().Be(PayloadStatus.Valid); - new ExecutionPayloadV1(chain.BlockTree.BestSuggestedBody).Should().BeEquivalentTo(executionPayload); - } + [Test] + public async Task executePayloadV1_on_top_of_terminal_block() + { + using MergeTestBlockchain chain = await CreateBlockChain(new MergeConfig() + { + TerminalTotalDifficulty = $"{1900000}" + }); + IEngineRpcModule rpc = CreateEngineModule(chain); + Block newBlock = Build.A.Block.WithNumber(chain.BlockTree.Head!.Number) + .WithParent(chain.BlockTree.Head!) + .WithNonce(0) + .WithDifficulty(900000) + .WithTotalDifficulty(1900000L) + .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")).TestObject; + newBlock.CalculateHash(); + + using SemaphoreSlim bestBlockProcessed = new(0); + chain.BlockTree.NewHeadBlock += (s, e) => + { + if (e.Block.Hash == newBlock!.Hash) + bestBlockProcessed.Release(1); + }; + await chain.BlockTree.SuggestBlockAsync(newBlock); + (await bestBlockProcessed.WaitAsync(TimeSpan.FromSeconds(1))).Should().BeTrue(); + + ExecutionPayload executionPayload = CreateBlockRequest(CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD); + ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV1(executionPayload); + resultWrapper.Data.Status.Should().Be(PayloadStatus.Valid); + new ExecutionPayload(chain.BlockTree.BestSuggestedBody).Should().BeEquivalentTo(executionPayload); + } - [Test] - public async Task executePayloadV1_on_top_of_not_processed_invalid_terminal_block() - { - using MergeTestBlockchain chain = await CreateBlockChain(new MergeConfig() - { - TerminalTotalDifficulty = $"{1900000}" - }); - IEngineRpcModule rpc = CreateEngineModule(chain); - Block newBlock = Build.A.Block.WithNumber(chain.BlockTree.Head!.Number) - .WithParent(chain.BlockTree.Head!) - .WithNonce(0) - .WithDifficulty(1000000) - .WithTotalDifficulty(2000000L) - .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")).TestObject; - newBlock.CalculateHash(); - Block oneMoreTerminalBlock = Build.A.Block.WithNumber(chain.BlockTree.Head!.Number) - .WithParent(chain.BlockTree.Head!) - .WithNonce(0) - .WithDifficulty(900000) - .WithTotalDifficulty(1900000L) - .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bfba4ccf1702fabf02d8ad7a20b454edb6fd2f")).TestObject; //incorrect state root + [Test] + public async Task executePayloadV1_on_top_of_not_processed_invalid_terminal_block() + { + using MergeTestBlockchain chain = await CreateBlockChain(new MergeConfig() + { + TerminalTotalDifficulty = $"{1900000}" + }); + IEngineRpcModule rpc = CreateEngineModule(chain); + Block newBlock = Build.A.Block.WithNumber(chain.BlockTree.Head!.Number) + .WithParent(chain.BlockTree.Head!) + .WithNonce(0) + .WithDifficulty(1000000) + .WithTotalDifficulty(2000000L) + .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")).TestObject; + newBlock.CalculateHash(); + Block oneMoreTerminalBlock = Build.A.Block.WithNumber(chain.BlockTree.Head!.Number) + .WithParent(chain.BlockTree.Head!) + .WithNonce(0) + .WithDifficulty(900000) + .WithTotalDifficulty(1900000L) + .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bfba4ccf1702fabf02d8ad7a20b454edb6fd2f")).TestObject; //incorrect state root + + using SemaphoreSlim bestBlockProcessed = new(0); + chain.BlockTree.NewHeadBlock += (s, e) => + { + if (e.Block.Hash == newBlock!.Hash) + bestBlockProcessed.Release(1); + }; + await chain.BlockTree.SuggestBlockAsync(newBlock); + (await bestBlockProcessed.WaitAsync(TimeSpan.FromSeconds(5))).Should().Be(true); + + oneMoreTerminalBlock.CalculateHash(); + await chain.BlockTree.SuggestBlockAsync(oneMoreTerminalBlock); + + Block firstPoSBlock = Build.A.Block.WithParent(oneMoreTerminalBlock). + WithNumber(oneMoreTerminalBlock.Number + 1) + .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")) + .WithDifficulty(0).WithNonce(0).TestObject; + firstPoSBlock.CalculateHash(); + ExecutionPayload executionPayload = new(firstPoSBlock); + ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV1(executionPayload); + resultWrapper.Data.Status.Should().Be(PayloadStatus.Invalid); + resultWrapper.Data.LatestValidHash.Should().Be(Keccak.Zero); + } - using SemaphoreSlim bestBlockProcessed = new(0); - chain.BlockTree.NewHeadBlock += (s, e) => - { - if (e.Block.Hash == newBlock!.Hash) - bestBlockProcessed.Release(1); - }; - await chain.BlockTree.SuggestBlockAsync(newBlock); - (await bestBlockProcessed.WaitAsync(TimeSpan.FromSeconds(5))).Should().Be(true); - - oneMoreTerminalBlock.CalculateHash(); - await chain.BlockTree.SuggestBlockAsync(oneMoreTerminalBlock); - - Block firstPoSBlock = Build.A.Block.WithParent(oneMoreTerminalBlock). - WithNumber(oneMoreTerminalBlock.Number + 1) - .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")) - .WithDifficulty(0).WithNonce(0).TestObject; - firstPoSBlock.CalculateHash(); - ExecutionPayloadV1 executionPayload = new(firstPoSBlock); - ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV1(executionPayload); - resultWrapper.Data.Status.Should().Be(PayloadStatus.Invalid); - resultWrapper.Data.LatestValidHash.Should().Be(Keccak.Zero); - } + [Test] + public async Task executePayloadV1_on_top_of_not_processed_terminal_block() + { + using MergeTestBlockchain chain = await CreateBlockChain(new MergeConfig() + { + TerminalTotalDifficulty = $"{1900000}" + }); + IEngineRpcModule rpc = CreateEngineModule(chain); + Block newBlock = Build.A.Block.WithNumber(chain.BlockTree.Head!.Number) + .WithParent(chain.BlockTree.Head!) + .WithNonce(0) + .WithDifficulty(1000000) + .WithTotalDifficulty(2000000L) + .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")).TestObject; + newBlock.CalculateHash(); + Block oneMoreTerminalBlock = Build.A.Block.WithNumber(chain.BlockTree.Head!.Number) + .WithParent(chain.BlockTree.Head!) + .WithNonce(0) + .WithDifficulty(900000) + .WithTotalDifficulty(1900000L) + .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")).TestObject; + + using SemaphoreSlim bestBlockProcessed = new(0); + chain.BlockTree.NewHeadBlock += (s, e) => + { + if (e.Block.Hash == newBlock!.Hash) + bestBlockProcessed.Release(1); + }; + await chain.BlockTree.SuggestBlockAsync(newBlock); + (await bestBlockProcessed.WaitAsync(TimeSpan.FromSeconds(5))).Should().Be(true); + + oneMoreTerminalBlock.CalculateHash(); + await chain.BlockTree.SuggestBlockAsync(oneMoreTerminalBlock); + + Block firstPoSBlock = Build.A.Block.WithParent(oneMoreTerminalBlock). + WithNumber(oneMoreTerminalBlock.Number + 1) + .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")) + .WithDifficulty(0).WithNonce(0).TestObject; + firstPoSBlock.CalculateHash(); + ExecutionPayload executionPayload = new(firstPoSBlock); + ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV1(executionPayload); + resultWrapper.Data.Status.Should().Be(PayloadStatus.Valid); + new ExecutionPayload(chain.BlockTree.BestSuggestedBody).Should().BeEquivalentTo(executionPayload); + } - [Test] - public async Task executePayloadV1_on_top_of_not_processed_terminal_block() - { - using MergeTestBlockchain chain = await CreateBlockChain(new MergeConfig() - { - TerminalTotalDifficulty = $"{1900000}" - }); - IEngineRpcModule rpc = CreateEngineModule(chain); - Block newBlock = Build.A.Block.WithNumber(chain.BlockTree.Head!.Number) - .WithParent(chain.BlockTree.Head!) - .WithNonce(0) - .WithDifficulty(1000000) - .WithTotalDifficulty(2000000L) - .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")).TestObject; - newBlock.CalculateHash(); - Block oneMoreTerminalBlock = Build.A.Block.WithNumber(chain.BlockTree.Head!.Number) - .WithParent(chain.BlockTree.Head!) - .WithNonce(0) - .WithDifficulty(900000) - .WithTotalDifficulty(1900000L) - .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")).TestObject; + [Test] + public async Task executePayloadV1_accepts_first_block() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + ExecutionPayload executionPayload = CreateBlockRequest(CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD); + ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV1(executionPayload); + resultWrapper.Data.Status.Should().Be(PayloadStatus.Valid); + new ExecutionPayload(chain.BlockTree.BestSuggestedBody).Should().BeEquivalentTo(executionPayload); + } - using SemaphoreSlim bestBlockProcessed = new(0); - chain.BlockTree.NewHeadBlock += (s, e) => - { - if (e.Block.Hash == newBlock!.Hash) - bestBlockProcessed.Release(1); - }; - await chain.BlockTree.SuggestBlockAsync(newBlock); - (await bestBlockProcessed.WaitAsync(TimeSpan.FromSeconds(5))).Should().Be(true); - - oneMoreTerminalBlock.CalculateHash(); - await chain.BlockTree.SuggestBlockAsync(oneMoreTerminalBlock); - - Block firstPoSBlock = Build.A.Block.WithParent(oneMoreTerminalBlock). - WithNumber(oneMoreTerminalBlock.Number + 1) - .WithStateRoot(new Keccak("0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f")) - .WithDifficulty(0).WithNonce(0).TestObject; - firstPoSBlock.CalculateHash(); - ExecutionPayloadV1 executionPayload = new(firstPoSBlock); - ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV1(executionPayload); - resultWrapper.Data.Status.Should().Be(PayloadStatus.Valid); - new ExecutionPayloadV1(chain.BlockTree.BestSuggestedBody).Should().BeEquivalentTo(executionPayload); - } + [Test] + public async Task executePayloadV1_calculate_hash_for_cached_blocks() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + ExecutionPayload executionPayload = CreateBlockRequest( + CreateParentBlockRequestOnHead(chain.BlockTree), + TestItem.AddressD); + ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV1(executionPayload); + resultWrapper.Data.Status.Should().Be(PayloadStatus.Valid); + ResultWrapper<PayloadStatusV1> + resultWrapper2 = await rpc.engine_newPayloadV1(executionPayload); + resultWrapper2.Data.Status.Should().Be(PayloadStatus.Valid); + executionPayload.ParentHash = executionPayload.BlockHash!; + ResultWrapper<PayloadStatusV1> invalidBlockRequest = await rpc.engine_newPayloadV1(executionPayload); + invalidBlockRequest.Data.Status.Should().Be(PayloadStatus.InvalidBlockHash); + } - [Test] - public async Task executePayloadV1_accepts_first_block() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - ExecutionPayloadV1 executionPayload = CreateBlockRequest(CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD); - ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV1(executionPayload); - resultWrapper.Data.Status.Should().Be(PayloadStatus.Valid); - new ExecutionPayloadV1(chain.BlockTree.BestSuggestedBody).Should().BeEquivalentTo(executionPayload); - } + [TestCase(30)] + public async Task can_progress_chain_one_by_one_v1(int count) + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak lastHash = (await ProduceBranchV1(rpc, chain, count, CreateParentBlockRequestOnHead(chain.BlockTree), true)) + .LastOrDefault()?.BlockHash ?? Keccak.Zero; + chain.BlockTree.HeadHash.Should().Be(lastHash); + Block? last = RunForAllBlocksInBranch(chain.BlockTree, chain.BlockTree.HeadHash, b => b.IsGenesis, true); + last.Should().NotBeNull(); + last!.IsGenesis.Should().BeTrue(); + } - [Test] - public async Task executePayloadV1_calculate_hash_for_cached_blocks() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - ExecutionPayloadV1 executionPayload = CreateBlockRequest( - CreateParentBlockRequestOnHead(chain.BlockTree), - TestItem.AddressD); - ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV1(executionPayload); - resultWrapper.Data.Status.Should().Be(PayloadStatus.Valid); - ResultWrapper<PayloadStatusV1> - resultWrapper2 = await rpc.engine_newPayloadV1(executionPayload); - resultWrapper2.Data.Status.Should().Be(PayloadStatus.Valid); - executionPayload.ParentHash = executionPayload.BlockHash!; - ResultWrapper<PayloadStatusV1> invalidBlockRequest = await rpc.engine_newPayloadV1(executionPayload); - invalidBlockRequest.Data.Status.Should().Be(PayloadStatus.InvalidBlockHash); - } + [Test] + public async Task forkchoiceUpdatedV1_can_reorganize_to_last_block() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); - [TestCase(30)] - public async Task can_progress_chain_one_by_one_v1(int count) + async Task CanReorganizeToBlock(ExecutionPayload block, MergeTestBlockchain testChain) { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak lastHash = (await ProduceBranchV1(rpc, chain, count, CreateParentBlockRequestOnHead(chain.BlockTree), true)) - .LastOrDefault()?.BlockHash ?? Keccak.Zero; - chain.BlockTree.HeadHash.Should().Be(lastHash); - Block? last = RunForAllBlocksInBranch(chain.BlockTree, chain.BlockTree.HeadHash, b => b.IsGenesis, true); - last.Should().NotBeNull(); - last!.IsGenesis.Should().BeTrue(); + ForkchoiceStateV1 forkchoiceStateV1 = new(block.BlockHash, block.BlockHash, block.BlockHash); + ResultWrapper<ForkchoiceUpdatedV1Result> result = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1, null); + result.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + result.Data.PayloadId.Should().Be(null); + testChain.BlockTree.HeadHash.Should().Be(block.BlockHash); + testChain.BlockTree.Head!.Number.Should().Be(block.BlockNumber); + testChain.State.StateRoot.Should().Be(testChain.BlockTree.Head!.StateRoot!); } - [Test] - public async Task forkchoiceUpdatedV1_can_reorganize_to_last_block() + async Task CanReorganizeToLastBlock(MergeTestBlockchain testChain, + params IReadOnlyList<ExecutionPayload>[] branches) { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - - async Task CanReorganizeToBlock(ExecutionPayloadV1 block, MergeTestBlockchain testChain) + foreach (IReadOnlyList<ExecutionPayload>? branch in branches) { - ForkchoiceStateV1 forkchoiceStateV1 = new(block.BlockHash, block.BlockHash, block.BlockHash); - ResultWrapper<ForkchoiceUpdatedV1Result> result = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1, null); - result.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - result.Data.PayloadId.Should().Be(null); - testChain.BlockTree.HeadHash.Should().Be(block.BlockHash); - testChain.BlockTree.Head!.Number.Should().Be(block.BlockNumber); - testChain.State.StateRoot.Should().Be(testChain.BlockTree.Head!.StateRoot!); + await CanReorganizeToBlock(branch.Last(), testChain); } - - async Task CanReorganizeToLastBlock(MergeTestBlockchain testChain, - params IReadOnlyList<ExecutionPayloadV1>[] branches) - { - foreach (IReadOnlyList<ExecutionPayloadV1>? branch in branches) - { - await CanReorganizeToBlock(branch.Last(), testChain); - } - } - - IReadOnlyList<ExecutionPayloadV1> branch1 = await ProduceBranchV1(rpc, chain, 10, CreateParentBlockRequestOnHead(chain.BlockTree), true); - IReadOnlyList<ExecutionPayloadV1> branch2 = await ProduceBranchV1(rpc, chain, 6, branch1[3], true, TestItem.KeccakC); - - await CanReorganizeToLastBlock(chain, branch1, branch2); } - [Test] - public async Task forkchoiceUpdatedV1_head_block_after_reorg() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - - async Task CanReorganizeToBlock(ExecutionPayloadV1 block, MergeTestBlockchain testChain) - { - ForkchoiceStateV1 forkchoiceStateV1 = new(block.BlockHash, block.BlockHash, block.BlockHash); - ResultWrapper<ForkchoiceUpdatedV1Result> result = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1, null); - result.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - result.Data.PayloadId.Should().Be(null); - testChain.BlockTree.HeadHash.Should().Be(block.BlockHash); - testChain.BlockTree.Head!.Number.Should().Be(block.BlockNumber); - testChain.State.StateRoot.Should().Be(testChain.BlockTree.Head!.StateRoot!); - } + IReadOnlyList<ExecutionPayload> branch1 = await ProduceBranchV1(rpc, chain, 10, CreateParentBlockRequestOnHead(chain.BlockTree), true); + IReadOnlyList<ExecutionPayload> branch2 = await ProduceBranchV1(rpc, chain, 6, branch1[3], true, TestItem.KeccakC); - IReadOnlyList<ExecutionPayloadV1> branch1 = await ProduceBranchV1(rpc, chain, 10, CreateParentBlockRequestOnHead(chain.BlockTree), true); - IReadOnlyList<ExecutionPayloadV1> branch2 = await ProduceBranchV1(rpc, chain, 6, branch1[3], true, TestItem.KeccakC); + await CanReorganizeToLastBlock(chain, branch1, branch2); + } - await CanReorganizeToBlock(branch2.Last(), chain); - } + [Test] + public async Task forkchoiceUpdatedV1_head_block_after_reorg() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); - [Test] - public async Task newPayloadV1_should_return_accepted_for_side_branch() + async Task CanReorganizeToBlock(ExecutionPayload block, MergeTestBlockchain testChain) { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - ExecutionPayloadV1 executionPayload = CreateBlockRequest(CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD); - ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV1(executionPayload); - resultWrapper.Data.Status.Should().Be(PayloadStatus.Valid); - ForkchoiceStateV1 forkChoiceUpdatedRequest = new(executionPayload.BlockHash, executionPayload.BlockHash, executionPayload.BlockHash); - ResultWrapper<ForkchoiceUpdatedV1Result> fcu1 = (await rpc.engine_forkchoiceUpdatedV1(forkChoiceUpdatedRequest, - new PayloadAttributes() - { - PrevRandao = TestItem.KeccakA, - SuggestedFeeRecipient = Address.Zero, - Timestamp = executionPayload.Timestamp + 1 - })); - await rpc.engine_getPayloadV1(Bytes.FromHexString(fcu1.Data.PayloadId!)); + ForkchoiceStateV1 forkchoiceStateV1 = new(block.BlockHash, block.BlockHash, block.BlockHash); + ResultWrapper<ForkchoiceUpdatedV1Result> result = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1, null); + result.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + result.Data.PayloadId.Should().Be(null); + testChain.BlockTree.HeadHash.Should().Be(block.BlockHash); + testChain.BlockTree.Head!.Number.Should().Be(block.BlockNumber); + testChain.State.StateRoot.Should().Be(testChain.BlockTree.Head!.StateRoot!); } - [TestCase(false)] - [TestCase(true)] - public async Task executePayloadV1_processes_passed_transactions(bool moveHead) - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - IReadOnlyList<ExecutionPayloadV1> branch = await ProduceBranchV1(rpc, chain, 8, CreateParentBlockRequestOnHead(chain.BlockTree), moveHead); + IReadOnlyList<ExecutionPayload> branch1 = await ProduceBranchV1(rpc, chain, 10, CreateParentBlockRequestOnHead(chain.BlockTree), true); + IReadOnlyList<ExecutionPayload> branch2 = await ProduceBranchV1(rpc, chain, 6, branch1[3], true, TestItem.KeccakC); - foreach (ExecutionPayloadV1 block in branch) - { - uint count = 10; - ExecutionPayloadV1 executePayloadRequest = CreateBlockRequest(block, TestItem.AddressA); - PrivateKey from = TestItem.PrivateKeyB; - Address to = TestItem.AddressD; - (_, UInt256 toBalanceAfter) = AddTransactions(chain, executePayloadRequest, from, to, count, 1, out BlockHeader? parentHeader); - - executePayloadRequest.GasUsed = GasCostOf.Transaction * count; - executePayloadRequest.StateRoot = new Keccak("0x3d2e3ced6da0d1e94e65894dc091190480f045647610ef614e1cab4241ca66e0"); - executePayloadRequest.ReceiptsRoot = new Keccak("0xc538d36ed1acf6c28187110a2de3e5df707d6d38982f436eb0db7a623f9dc2cd"); - TryCalculateHash(executePayloadRequest, out Keccak? hash); - executePayloadRequest.BlockHash = hash; - ResultWrapper<PayloadStatusV1> result = await rpc.engine_newPayloadV1(executePayloadRequest); - result.Data.Status.Should().Be(PayloadStatus.Valid); - RootCheckVisitor rootCheckVisitor = new(); - chain.StateReader.RunTreeVisitor(rootCheckVisitor, executePayloadRequest.StateRoot); - rootCheckVisitor.HasRoot.Should().BeTrue(); - - chain.StateReader.GetBalance(executePayloadRequest.StateRoot, to).Should().Be(toBalanceAfter); - if (moveHead) - { - ForkchoiceStateV1 forkChoiceUpdatedRequest = new(executePayloadRequest.BlockHash, executePayloadRequest.BlockHash, executePayloadRequest.BlockHash); - await rpc.engine_forkchoiceUpdatedV1(forkChoiceUpdatedRequest); - chain.ReadOnlyState.StateRoot.Should().Be(executePayloadRequest.StateRoot); - chain.ReadOnlyState.StateRoot.Should().NotBe(parentHeader.StateRoot!); - } - } - } - - [Test] - public async Task executePayloadV1_transactions_produce_receipts() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - IEngineRpcModule rpc = CreateEngineModule(chain); - IReadOnlyList<ExecutionPayloadV1> branch = await ProduceBranchV1(rpc, chain, 1, CreateParentBlockRequestOnHead(chain.BlockTree), false); + await CanReorganizeToBlock(branch2.Last(), chain); + } - foreach (ExecutionPayloadV1 block in branch) + [Test] + public async Task newPayloadV1_should_return_accepted_for_side_branch() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + ExecutionPayload executionPayload = CreateBlockRequest(CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD); + ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV1(executionPayload); + resultWrapper.Data.Status.Should().Be(PayloadStatus.Valid); + ForkchoiceStateV1 forkChoiceUpdatedRequest = new(executionPayload.BlockHash, executionPayload.BlockHash, executionPayload.BlockHash); + ResultWrapper<ForkchoiceUpdatedV1Result> fcu1 = (await rpc.engine_forkchoiceUpdatedV1(forkChoiceUpdatedRequest, + new PayloadAttributes() { - uint count = 10; - ExecutionPayloadV1 executionPayload = CreateBlockRequest(block, TestItem.AddressA); - PrivateKey from = TestItem.PrivateKeyB; - Address to = TestItem.AddressD; - (_, UInt256 toBalanceAfter) = AddTransactions(chain, executionPayload, from, to, count, 1, out BlockHeader parentHeader); - - UInt256 fromBalance = chain.StateReader.GetBalance(parentHeader.StateRoot!, from.Address); - executionPayload.GasUsed = GasCostOf.Transaction * count; - executionPayload.StateRoot = - new Keccak("0x3d2e3ced6da0d1e94e65894dc091190480f045647610ef614e1cab4241ca66e0"); - executionPayload.ReceiptsRoot = - new Keccak("0xc538d36ed1acf6c28187110a2de3e5df707d6d38982f436eb0db7a623f9dc2cd"); - TryCalculateHash(executionPayload, out Keccak hash); - executionPayload.BlockHash = hash; - ResultWrapper<PayloadStatusV1> result = await rpc.engine_newPayloadV1(executionPayload); - - result.Data.Status.Should().Be(PayloadStatus.Valid); - RootCheckVisitor rootCheckVisitor = new(); - chain.StateReader.RunTreeVisitor(rootCheckVisitor, executionPayload.StateRoot); - rootCheckVisitor.HasRoot.Should().BeTrue(); - - UInt256 fromBalanceAfter = chain.StateReader.GetBalance(executionPayload.StateRoot, from.Address); - Assert.True(fromBalanceAfter < fromBalance - toBalanceAfter); - chain.StateReader.GetBalance(executionPayload.StateRoot, to).Should().Be(toBalanceAfter); - Block findBlock = chain.BlockTree.FindBlock(executionPayload.BlockHash, BlockTreeLookupOptions.None)!; - TxReceipt[]? receipts = chain.ReceiptStorage.Get(findBlock); - findBlock.Transactions.Select(t => t.Hash).Should().BeEquivalentTo(receipts.Select(r => r.TxHash)); - } - } + PrevRandao = TestItem.KeccakA, + SuggestedFeeRecipient = Address.Zero, + Timestamp = executionPayload.Timestamp + 1 + })); + await rpc.engine_getPayloadV1(Bytes.FromHexString(fcu1.Data.PayloadId!)); + } - private async Task<IReadOnlyList<ExecutionPayloadV1>> ProduceBranchV1(IEngineRpcModule rpc, - MergeTestBlockchain chain, - int count, ExecutionPayloadV1 startingParentBlock, bool setHead, Keccak? random = null) - { - List<ExecutionPayloadV1> blocks = new(); - ExecutionPayloadV1 parentBlock = startingParentBlock; - parentBlock.TryGetBlock(out Block? block); - UInt256? startingTotalDifficulty = block!.IsGenesis - ? block.Difficulty : chain.BlockFinder.FindHeader(block!.Header!.ParentHash!)!.TotalDifficulty; - BlockHeader parentHeader = block!.Header; - parentHeader.TotalDifficulty = startingTotalDifficulty + - parentHeader.Difficulty; - for (int i = 0; i < count; i++) + [TestCase(false)] + [TestCase(true)] + public async Task executePayloadV1_processes_passed_transactions(bool moveHead) + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + IReadOnlyList<ExecutionPayload> branch = await ProduceBranchV1(rpc, chain, 8, CreateParentBlockRequestOnHead(chain.BlockTree), moveHead); + + foreach (ExecutionPayload block in branch) + { + uint count = 10; + ExecutionPayload executePayloadRequest = CreateBlockRequest(block, TestItem.AddressA); + PrivateKey from = TestItem.PrivateKeyB; + Address to = TestItem.AddressD; + (_, UInt256 toBalanceAfter) = AddTransactions(chain, executePayloadRequest, from, to, count, 1, out BlockHeader? parentHeader); + + executePayloadRequest.GasUsed = GasCostOf.Transaction * count; + executePayloadRequest.StateRoot = new Keccak("0x3d2e3ced6da0d1e94e65894dc091190480f045647610ef614e1cab4241ca66e0"); + executePayloadRequest.ReceiptsRoot = new Keccak("0xc538d36ed1acf6c28187110a2de3e5df707d6d38982f436eb0db7a623f9dc2cd"); + TryCalculateHash(executePayloadRequest, out Keccak? hash); + executePayloadRequest.BlockHash = hash; + ResultWrapper<PayloadStatusV1> result = await rpc.engine_newPayloadV1(executePayloadRequest); + result.Data.Status.Should().Be(PayloadStatus.Valid); + RootCheckVisitor rootCheckVisitor = new(); + chain.StateReader.RunTreeVisitor(rootCheckVisitor, executePayloadRequest.StateRoot); + rootCheckVisitor.HasRoot.Should().BeTrue(); + + chain.StateReader.GetBalance(executePayloadRequest.StateRoot, to).Should().Be(toBalanceAfter); + if (moveHead) { - ExecutionPayloadV1? getPayloadResult = await BuildAndGetPayloadOnBranch(rpc, chain, parentHeader, - parentBlock.Timestamp + 12, - random ?? TestItem.KeccakA, Address.Zero); - PayloadStatusV1 payloadStatusResponse = (await rpc.engine_newPayloadV1(getPayloadResult)).Data; - payloadStatusResponse.Status.Should().Be(PayloadStatus.Valid); - if (setHead) - { - Keccak newHead = getPayloadResult!.BlockHash; - ForkchoiceStateV1 forkchoiceStateV1 = new(newHead, newHead, newHead); - ResultWrapper<ForkchoiceUpdatedV1Result> setHeadResponse = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); - setHeadResponse.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - setHeadResponse.Data.PayloadId.Should().Be(null); - } - - blocks.Add((getPayloadResult)); - parentBlock = getPayloadResult; - parentBlock.TryGetBlock(out block!); - block.Header.TotalDifficulty = parentHeader.TotalDifficulty + block.Header.Difficulty; - parentHeader = block.Header; + ForkchoiceStateV1 forkChoiceUpdatedRequest = new(executePayloadRequest.BlockHash, executePayloadRequest.BlockHash, executePayloadRequest.BlockHash); + await rpc.engine_forkchoiceUpdatedV1(forkChoiceUpdatedRequest); + chain.ReadOnlyState.StateRoot.Should().Be(executePayloadRequest.StateRoot); + chain.ReadOnlyState.StateRoot.Should().NotBe(parentHeader.StateRoot!); } - - return blocks; - } - - [Test] - public async Task ExecutionPayloadV1_set_and_get_transactions_roundtrip() - { - using MergeTestBlockchain chain = await CreateBlockChain(); - Keccak startingHead = chain.BlockTree.HeadHash; - uint count = 3; - int value = 10; - Address recipient = TestItem.AddressD; - PrivateKey sender = TestItem.PrivateKeyB; - - Transaction[] txsSource = - BuildTransactions(chain, startingHead, sender, recipient, count, value, out _, out _); - - ExecutionPayloadV1 executionPayload = new(); - executionPayload.SetTransactions(txsSource); - - Transaction[] txsReceived = executionPayload.GetTransactions(); - - txsReceived.Should().BeEquivalentTo(txsSource, options => options - .Excluding(t => t.ChainId) - .Excluding(t => t.SenderAddress) - .Excluding(t => t.Timestamp) - ); } + } - [Test] - public async Task payloadV1_no_suggestedFeeRecipient_in_config() - { - using MergeTestBlockchain chain = - await CreateBlockChain(new MergeConfig() { TerminalTotalDifficulty = "0" }); - IEngineRpcModule rpc = CreateEngineModule(chain); - Keccak startingHead = chain.BlockTree.HeadHash; - ulong timestamp = Timestamper.UnixTime.Seconds; - Keccak random = Keccak.Zero; - Address feeRecipient = TestItem.AddressC; - string payloadId = rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(startingHead, Keccak.Zero, startingHead), - new PayloadAttributes { Timestamp = timestamp, SuggestedFeeRecipient = feeRecipient, PrevRandao = random }).Result.Data - .PayloadId!; - (await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId))).Data!.FeeRecipient.Should() - .Be(TestItem.AddressC); + [Test] + public async Task executePayloadV1_transactions_produce_receipts() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + IReadOnlyList<ExecutionPayload> branch = await ProduceBranchV1(rpc, chain, 1, CreateParentBlockRequestOnHead(chain.BlockTree), false); + + foreach (ExecutionPayload block in branch) + { + uint count = 10; + ExecutionPayload executionPayload = CreateBlockRequest(block, TestItem.AddressA); + PrivateKey from = TestItem.PrivateKeyB; + Address to = TestItem.AddressD; + (_, UInt256 toBalanceAfter) = AddTransactions(chain, executionPayload, from, to, count, 1, out BlockHeader parentHeader); + + UInt256 fromBalance = chain.StateReader.GetBalance(parentHeader.StateRoot!, from.Address); + executionPayload.GasUsed = GasCostOf.Transaction * count; + executionPayload.StateRoot = + new Keccak("0x3d2e3ced6da0d1e94e65894dc091190480f045647610ef614e1cab4241ca66e0"); + executionPayload.ReceiptsRoot = + new Keccak("0xc538d36ed1acf6c28187110a2de3e5df707d6d38982f436eb0db7a623f9dc2cd"); + TryCalculateHash(executionPayload, out Keccak hash); + executionPayload.BlockHash = hash; + ResultWrapper<PayloadStatusV1> result = await rpc.engine_newPayloadV1(executionPayload); + + result.Data.Status.Should().Be(PayloadStatus.Valid); + RootCheckVisitor rootCheckVisitor = new(); + chain.StateReader.RunTreeVisitor(rootCheckVisitor, executionPayload.StateRoot); + rootCheckVisitor.HasRoot.Should().BeTrue(); + + UInt256 fromBalanceAfter = chain.StateReader.GetBalance(executionPayload.StateRoot, from.Address); + Assert.True(fromBalanceAfter < fromBalance - toBalanceAfter); + chain.StateReader.GetBalance(executionPayload.StateRoot, to).Should().Be(toBalanceAfter); + Block findBlock = chain.BlockTree.FindBlock(executionPayload.BlockHash, BlockTreeLookupOptions.None)!; + TxReceipt[]? receipts = chain.ReceiptStorage.Get(findBlock); + findBlock.Transactions.Select(t => t.Hash).Should().BeEquivalentTo(receipts.Select(r => r.TxHash)); } + } - [TestCase(0, "0x0000000000000000000000000000000000000000000000000000000000000000")] - [TestCase(1000001, "0x191dc9697d77129ee5b6f6d57074d2c854a38129913e3fdd3d9f0ebc930503a6")] - public async Task exchangeTransitionConfiguration_return_expected_results(long clTtd, string terminalBlockHash) - { - using MergeTestBlockchain chain = - await CreateBlockChain(new MergeConfig() { TerminalTotalDifficulty = "1000001", TerminalBlockHash = new Keccak("0x191dc9697d77129ee5b6f6d57074d2c854a38129913e3fdd3d9f0ebc930503a6").ToString(true), TerminalBlockNumber = 1 }); - IEngineRpcModule rpc = CreateEngineModule(chain); - - TransitionConfigurationV1 result = rpc.engine_exchangeTransitionConfigurationV1(new TransitionConfigurationV1() + private async Task<IReadOnlyList<ExecutionPayload>> ProduceBranchV1(IEngineRpcModule rpc, + MergeTestBlockchain chain, + int count, ExecutionPayload startingParentBlock, bool setHead, Keccak? random = null) + { + List<ExecutionPayload> blocks = new(); + ExecutionPayload parentBlock = startingParentBlock; + parentBlock.TryGetBlock(out Block? block); + UInt256? startingTotalDifficulty = block!.IsGenesis + ? block.Difficulty : chain.BlockFinder.FindHeader(block!.Header!.ParentHash!)!.TotalDifficulty; + BlockHeader parentHeader = block!.Header; + parentHeader.TotalDifficulty = startingTotalDifficulty + + parentHeader.Difficulty; + for (int i = 0; i < count; i++) + { + ExecutionPayload? getPayloadResult = await BuildAndGetPayloadOnBranch(rpc, chain, parentHeader, + parentBlock.Timestamp + 12, + random ?? TestItem.KeccakA, Address.Zero); + PayloadStatusV1 payloadStatusResponse = (await rpc.engine_newPayloadV1(getPayloadResult)).Data; + payloadStatusResponse.Status.Should().Be(PayloadStatus.Valid); + if (setHead) { - TerminalBlockNumber = 0, - TerminalBlockHash = new Keccak(terminalBlockHash), - TerminalTotalDifficulty = (UInt256)clTtd - }).Data; - - Assert.AreEqual((UInt256)1000001, result.TerminalTotalDifficulty); - Assert.AreEqual(1, result.TerminalBlockNumber); - Assert.AreEqual("0x191dc9697d77129ee5b6f6d57074d2c854a38129913e3fdd3d9f0ebc930503a6", result.TerminalBlockHash.ToString()); + Keccak newHead = getPayloadResult!.BlockHash; + ForkchoiceStateV1 forkchoiceStateV1 = new(newHead, newHead, newHead); + ResultWrapper<ForkchoiceUpdatedV1Result> setHeadResponse = await rpc.engine_forkchoiceUpdatedV1(forkchoiceStateV1); + setHeadResponse.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + setHeadResponse.Data.PayloadId.Should().Be(null); + } + + blocks.Add((getPayloadResult)); + parentBlock = getPayloadResult; + parentBlock.TryGetBlock(out block!); + block.Header.TotalDifficulty = parentHeader.TotalDifficulty + block.Header.Difficulty; + parentHeader = block.Header; } - [TestCase(0, "0x0000000000000000000000000000000000000000000000000000000000000000")] - [TestCase(1000001, "0x191dc9697d77129ee5b6f6d57074d2c854a38129913e3fdd3d9f0ebc930503a6")] - public async Task exchangeTransitionConfiguration_return_with_empty_Nethermind_configuration(long clTtd, string terminalBlockHash) - { - using MergeTestBlockchain chain = - await CreateBlockChain(new MergeConfig() { }); - IEngineRpcModule rpc = CreateEngineModule(chain); + return blocks; + } - TransitionConfigurationV1 result = rpc.engine_exchangeTransitionConfigurationV1(new TransitionConfigurationV1() - { - TerminalBlockNumber = 0, - TerminalBlockHash = new Keccak(terminalBlockHash), - TerminalTotalDifficulty = (UInt256)clTtd - }).Data; - - Assert.AreEqual(UInt256.Parse("115792089237316195423570985008687907853269984665640564039457584007913129638912"), result.TerminalTotalDifficulty); - Assert.AreEqual(0, result.TerminalBlockNumber); - Assert.AreEqual("0x0000000000000000000000000000000000000000000000000000000000000000", result.TerminalBlockHash.ToString()); - } + [Test] + public async Task ExecutionPayloadV1_set_and_get_transactions_roundtrip() + { + using MergeTestBlockchain chain = await CreateBlockChain(); + Keccak startingHead = chain.BlockTree.HeadHash; + uint count = 3; + int value = 10; + Address recipient = TestItem.AddressD; + PrivateKey sender = TestItem.PrivateKeyB; + + Transaction[] txsSource = + BuildTransactions(chain, startingHead, sender, recipient, count, value, out _, out _); + + ExecutionPayload executionPayload = new(); + executionPayload.SetTransactions(txsSource); + + Transaction[] txsReceived = executionPayload.GetTransactions(); + + txsReceived.Should().BeEquivalentTo(txsSource, options => options + .Excluding(t => t.ChainId) + .Excluding(t => t.SenderAddress) + .Excluding(t => t.Timestamp) + ); + } - private async Task<ExecutionPayloadV1> SendNewBlockV1(IEngineRpcModule rpc, MergeTestBlockchain chain) - { - ExecutionPayloadV1 executionPayload = CreateBlockRequest( - CreateParentBlockRequestOnHead(chain.BlockTree), - TestItem.AddressD); - ResultWrapper<PayloadStatusV1> executePayloadResult = - await rpc.engine_newPayloadV1(executionPayload); - executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); - return executionPayload; - } + [Test] + public async Task payloadV1_no_suggestedFeeRecipient_in_config() + { + using MergeTestBlockchain chain = + await CreateBlockChain(new MergeConfig() { TerminalTotalDifficulty = "0" }); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + ulong timestamp = Timestamper.UnixTime.Seconds; + Keccak random = Keccak.Zero; + Address feeRecipient = TestItem.AddressC; + string payloadId = rpc.engine_forkchoiceUpdatedV1(new ForkchoiceStateV1(startingHead, Keccak.Zero, startingHead), + new PayloadAttributes { Timestamp = timestamp, SuggestedFeeRecipient = feeRecipient, PrevRandao = random }).Result.Data + .PayloadId!; + (await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId))).Data!.FeeRecipient.Should() + .Be(TestItem.AddressC); + } - private async Task<ExecutionPayloadV1> BuildAndSendNewBlockV1(IEngineRpcModule rpc, MergeTestBlockchain chain, bool waitForBlockImprovement) - { - Keccak head = chain.BlockTree.HeadHash; - ulong timestamp = Timestamper.UnixTime.Seconds; - Keccak random = Keccak.Zero; - Address feeRecipient = Address.Zero; - ExecutionPayloadV1 executionPayload = await BuildAndGetPayloadResult(rpc, chain, head, - Keccak.Zero, head, timestamp, random, feeRecipient, waitForBlockImprovement); - ResultWrapper<PayloadStatusV1> executePayloadResult = - await rpc.engine_newPayloadV1(executionPayload); - executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); - return executionPayload; - } + [TestCase(0, "0x0000000000000000000000000000000000000000000000000000000000000000")] + [TestCase(1000001, "0x191dc9697d77129ee5b6f6d57074d2c854a38129913e3fdd3d9f0ebc930503a6")] + public async Task exchangeTransitionConfiguration_return_expected_results(long clTtd, string terminalBlockHash) + { + using MergeTestBlockchain chain = + await CreateBlockChain(new MergeConfig() { TerminalTotalDifficulty = "1000001", TerminalBlockHash = new Keccak("0x191dc9697d77129ee5b6f6d57074d2c854a38129913e3fdd3d9f0ebc930503a6").ToString(true), TerminalBlockNumber = 1 }); + IEngineRpcModule rpc = CreateEngineModule(chain); - private async Task<ExecutionPayloadV1> BuildAndGetPayloadOnBranch( - IEngineRpcModule rpc, MergeTestBlockchain chain, BlockHeader parentHeader, - ulong timestamp, Keccak random, Address feeRecipient) + TransitionConfigurationV1 result = rpc.engine_exchangeTransitionConfigurationV1(new TransitionConfigurationV1() { - PayloadAttributes payloadAttributes = - new() { Timestamp = timestamp, PrevRandao = random, SuggestedFeeRecipient = feeRecipient }; - - // we're using payloadService directly, because we can't use fcU for branch - string payloadId = chain.PayloadPreparationService!.StartPreparingPayload(parentHeader, payloadAttributes)!; + TerminalBlockNumber = 0, + TerminalBlockHash = new Keccak(terminalBlockHash), + TerminalTotalDifficulty = (UInt256)clTtd + }).Data; - ResultWrapper<ExecutionPayloadV1?> getPayloadResult = - await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId)); - return getPayloadResult.Data!; - } + Assert.AreEqual((UInt256)1000001, result.TerminalTotalDifficulty); + Assert.AreEqual(1, result.TerminalBlockNumber); + Assert.AreEqual("0x191dc9697d77129ee5b6f6d57074d2c854a38129913e3fdd3d9f0ebc930503a6", result.TerminalBlockHash.ToString()); + } + [TestCase(0, "0x0000000000000000000000000000000000000000000000000000000000000000")] + [TestCase(1000001, "0x191dc9697d77129ee5b6f6d57074d2c854a38129913e3fdd3d9f0ebc930503a6")] + public async Task exchangeTransitionConfiguration_return_with_empty_Nethermind_configuration(long clTtd, string terminalBlockHash) + { + using MergeTestBlockchain chain = + await CreateBlockChain(new MergeConfig() { }); + IEngineRpcModule rpc = CreateEngineModule(chain); - [Test] - public async Task repeat_the_same_payload_after_fcu_should_return_valid_and_be_ignored() + TransitionConfigurationV1 result = rpc.engine_exchangeTransitionConfigurationV1(new TransitionConfigurationV1() { - using MergeTestBlockchain chain = - await CreateBlockChain(new MergeConfig() { TerminalTotalDifficulty = "0" }); - IEngineRpcModule rpc = CreateEngineModule(chain); + TerminalBlockNumber = 0, + TerminalBlockHash = new Keccak(terminalBlockHash), + TerminalTotalDifficulty = (UInt256)clTtd + }).Data; - // Correct new payload - ExecutionPayloadV1 executionPayloadV11 = CreateBlockRequest( - CreateParentBlockRequestOnHead(chain.BlockTree), - TestItem.AddressA); - ResultWrapper<PayloadStatusV1> newPayloadResult1 = await rpc.engine_newPayloadV1(executionPayloadV11); - newPayloadResult1.Data.Status.Should().Be(PayloadStatus.Valid); - - // Fork choice updated with first np hash - ForkchoiceStateV1 forkChoiceState1 = new(executionPayloadV11.BlockHash, - executionPayloadV11.BlockHash, - executionPayloadV11.BlockHash); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult1 = - await rpc.engine_forkchoiceUpdatedV1(forkChoiceState1); - forkchoiceUpdatedResult1.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - - ResultWrapper<PayloadStatusV1> newPayloadResult2 = await rpc.engine_newPayloadV1(executionPayloadV11); - newPayloadResult2.Data.Status.Should().Be(PayloadStatus.Valid); - newPayloadResult2.Data.LatestValidHash.Should().Be(executionPayloadV11.BlockHash); - } + Assert.AreEqual(UInt256.Parse("115792089237316195423570985008687907853269984665640564039457584007913129638912"), result.TerminalTotalDifficulty); + Assert.AreEqual(0, result.TerminalBlockNumber); + Assert.AreEqual("0x0000000000000000000000000000000000000000000000000000000000000000", result.TerminalBlockHash.ToString()); + } - [Test] - public async Task payloadV1_invalid_parent_hash() - { - using MergeTestBlockchain chain = - await CreateBlockChain(new MergeConfig() { TerminalTotalDifficulty = "0" }); - IEngineRpcModule rpc = CreateEngineModule(chain); + private async Task<ExecutionPayload> SendNewBlockV1(IEngineRpcModule rpc, MergeTestBlockchain chain) + { + ExecutionPayload executionPayload = CreateBlockRequest( + CreateParentBlockRequestOnHead(chain.BlockTree), + TestItem.AddressD); + ResultWrapper<PayloadStatusV1> executePayloadResult = + await rpc.engine_newPayloadV1(executionPayload); + executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); + return executionPayload; + } - // Correct new payload - ExecutionPayloadV1 executionPayloadV11 = CreateBlockRequest( - CreateParentBlockRequestOnHead(chain.BlockTree), - TestItem.AddressA); - ResultWrapper<PayloadStatusV1> newPayloadResult1 = await rpc.engine_newPayloadV1(executionPayloadV11); - newPayloadResult1.Data.Status.Should().Be(PayloadStatus.Valid); - - // Fork choice updated with first np hash - ForkchoiceStateV1 forkChoiceState1 = new(executionPayloadV11.BlockHash, executionPayloadV11.BlockHash, - executionPayloadV11.BlockHash); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult1 = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState1); - forkchoiceUpdatedResult1.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - - // New payload unknown parent hash - ExecutionPayloadV1 executionPayloadV12A = CreateBlockRequest(executionPayloadV11, TestItem.AddressA); - executionPayloadV12A.ParentHash = TestItem.KeccakB; - TryCalculateHash(executionPayloadV12A, out Keccak? hash); - executionPayloadV12A.BlockHash = hash; - ResultWrapper<PayloadStatusV1> newPayloadResult2A = await rpc.engine_newPayloadV1(executionPayloadV12A); - newPayloadResult2A.Data.Status.Should().Be(PayloadStatus.Syncing); - - // Fork choice updated with unknown parent hash - ForkchoiceStateV1 forkChoiceState2A = new(executionPayloadV12A.BlockHash, - executionPayloadV12A.BlockHash, - executionPayloadV12A.BlockHash); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult2A = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState2A); - forkchoiceUpdatedResult2A.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Syncing); - - // New payload with correct parent hash - ExecutionPayloadV1 executionPayloadV12B = CreateBlockRequest(executionPayloadV11, TestItem.AddressA); - ResultWrapper<PayloadStatusV1> newPayloadResult2B = await rpc.engine_newPayloadV1(executionPayloadV12B); - newPayloadResult2B.Data.Status.Should().Be(PayloadStatus.Valid); - - // Fork choice updated with correct parent hash - ForkchoiceStateV1 forkChoiceState2B = new(executionPayloadV12B.BlockHash, executionPayloadV12B.BlockHash, - executionPayloadV12B.BlockHash); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult2B = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState2B); - forkchoiceUpdatedResult2B.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - - // New payload unknown parent hash - ExecutionPayloadV1 executionPayloadV13A = CreateBlockRequest(executionPayloadV12A, TestItem.AddressA); - ResultWrapper<PayloadStatusV1> newPayloadResult3A = await rpc.engine_newPayloadV1(executionPayloadV13A); - newPayloadResult3A.Data.Status.Should().Be(PayloadStatus.Syncing); - - // Fork choice updated with unknown parent hash - ForkchoiceStateV1 forkChoiceState3A = new(executionPayloadV13A.BlockHash, - executionPayloadV13A.BlockHash, - executionPayloadV13A.BlockHash); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult3A = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState3A); - forkchoiceUpdatedResult3A.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Syncing); - - ExecutionPayloadV1 executionPayloadV13B = CreateBlockRequest(executionPayloadV12B, TestItem.AddressA); - ResultWrapper<PayloadStatusV1> newPayloadResult3B = await rpc.engine_newPayloadV1(executionPayloadV13B); - newPayloadResult3B.Data.Status.Should().Be(PayloadStatus.Valid); - - // Fork choice updated with correct parent hash - ForkchoiceStateV1 forkChoiceState3B = new(executionPayloadV13B.BlockHash, executionPayloadV13B.BlockHash, - executionPayloadV13B.BlockHash); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult3B = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState3B); - forkchoiceUpdatedResult3B.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - } + private async Task<ExecutionPayload> BuildAndSendNewBlockV1(IEngineRpcModule rpc, MergeTestBlockchain chain, bool waitForBlockImprovement) + { + Keccak head = chain.BlockTree.HeadHash; + ulong timestamp = Timestamper.UnixTime.Seconds; + Keccak random = Keccak.Zero; + Address feeRecipient = Address.Zero; + ExecutionPayload executionPayload = await BuildAndGetPayloadResult(rpc, chain, head, + Keccak.Zero, head, timestamp, random, feeRecipient, waitForBlockImprovement); + ResultWrapper<PayloadStatusV1> executePayloadResult = + await rpc.engine_newPayloadV1(executionPayload); + executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); + return executionPayload; + } - [Test] - public async Task inconsistent_finalized_hash() - { - using MergeTestBlockchain chain = - await CreateBlockChain(new MergeConfig() { TerminalTotalDifficulty = "0" }); - IEngineRpcModule rpc = CreateEngineModule(chain); + private async Task<ExecutionPayload> BuildAndGetPayloadOnBranch( + IEngineRpcModule rpc, MergeTestBlockchain chain, BlockHeader parentHeader, + ulong timestamp, Keccak random, Address feeRecipient) + { + PayloadAttributes payloadAttributes = + new() { Timestamp = timestamp, PrevRandao = random, SuggestedFeeRecipient = feeRecipient }; - ExecutionPayloadV1 blockRequestResult1 = CreateBlockRequest( - CreateParentBlockRequestOnHead(chain.BlockTree), - TestItem.AddressA); - ResultWrapper<PayloadStatusV1> newPayloadResult1 = await rpc.engine_newPayloadV1(blockRequestResult1); - newPayloadResult1.Data.Status.Should().Be(PayloadStatus.Valid); - - ForkchoiceStateV1 forkChoiceState1 = new(blockRequestResult1.BlockHash, blockRequestResult1.BlockHash, - blockRequestResult1.BlockHash); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult1 = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState1); - forkchoiceUpdatedResult1.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - - ExecutionPayloadV1 blockRequestResult2A = CreateBlockRequest(blockRequestResult1, TestItem.AddressB); - ResultWrapper<PayloadStatusV1> newPayloadResult2A = await rpc.engine_newPayloadV1(blockRequestResult2A); - newPayloadResult2A.Data.Status.Should().Be(PayloadStatus.Valid); - - ExecutionPayloadV1 blockRequestResult2B = CreateBlockRequest(blockRequestResult1, TestItem.AddressA); - ResultWrapper<PayloadStatusV1> newPayloadResult2B = await rpc.engine_newPayloadV1(blockRequestResult2B); - newPayloadResult2B.Data.Status.Should().Be(PayloadStatus.Valid); - - ExecutionPayloadV1 blockRequestResult3B = CreateBlockRequest(blockRequestResult2B, TestItem.AddressA); - ResultWrapper<PayloadStatusV1> newPayloadResult3B = await rpc.engine_newPayloadV1(blockRequestResult3B); - newPayloadResult3B.Data.Status.Should().Be(PayloadStatus.Valid); - - ForkchoiceStateV1 forkChoiceState3 = new(blockRequestResult3B.BlockHash, blockRequestResult2A.BlockHash, - blockRequestResult3B.BlockHash); // finalized hash - inconsistent blockRequestResult2A - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult3 = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState3); - forkchoiceUpdatedResult3.ErrorCode.Should().Be(MergeErrorCodes.InvalidForkchoiceState); - } + // we're using payloadService directly, because we can't use fcU for branch + string payloadId = chain.PayloadPreparationService!.StartPreparingPayload(parentHeader, payloadAttributes)!; - [Test] - public async Task inconsistent_safe_hash() - { - using MergeTestBlockchain chain = - await CreateBlockChain(new MergeConfig() { TerminalTotalDifficulty = "0" }); - IEngineRpcModule rpc = CreateEngineModule(chain); + ResultWrapper<ExecutionPayload?> getPayloadResult = + await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId)); + return getPayloadResult.Data!; + } - ExecutionPayloadV1 blockRequestResult1 = CreateBlockRequest( - CreateParentBlockRequestOnHead(chain.BlockTree), - TestItem.AddressA); - ResultWrapper<PayloadStatusV1> newPayloadResult1 = await rpc.engine_newPayloadV1(blockRequestResult1); - newPayloadResult1.Data.Status.Should().Be(PayloadStatus.Valid); - - ForkchoiceStateV1 forkChoiceState1 = new(blockRequestResult1.BlockHash, blockRequestResult1.BlockHash, - blockRequestResult1.BlockHash); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult1 = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState1); - forkchoiceUpdatedResult1.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - - ExecutionPayloadV1 blockRequestResult2A = CreateBlockRequest(blockRequestResult1, TestItem.AddressB); - ResultWrapper<PayloadStatusV1> newPayloadResult2A = await rpc.engine_newPayloadV1(blockRequestResult2A); - newPayloadResult2A.Data.Status.Should().Be(PayloadStatus.Valid); - - ExecutionPayloadV1 blockRequestResult2B = CreateBlockRequest(blockRequestResult1, TestItem.AddressA); - ResultWrapper<PayloadStatusV1> newPayloadResult2B = await rpc.engine_newPayloadV1(blockRequestResult2B); - newPayloadResult2B.Data.Status.Should().Be(PayloadStatus.Valid); - - ExecutionPayloadV1 blockRequestResult3B = CreateBlockRequest(blockRequestResult2B, TestItem.AddressA); - ResultWrapper<PayloadStatusV1> newPayloadResult3B = await rpc.engine_newPayloadV1(blockRequestResult3B); - newPayloadResult3B.Data.Status.Should().Be(PayloadStatus.Valid); - - ForkchoiceStateV1 forkChoiceState3 = new(blockRequestResult3B.BlockHash, blockRequestResult3B.BlockHash, - blockRequestResult2A.BlockHash); // safe block hash - inconsistent blockRequestResult2A - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult3 = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState3); - forkchoiceUpdatedResult3.ErrorCode.Should().Be(MergeErrorCodes.InvalidForkchoiceState); - } + [Test] + public async Task repeat_the_same_payload_after_fcu_should_return_valid_and_be_ignored() + { + using MergeTestBlockchain chain = + await CreateBlockChain(new MergeConfig() { TerminalTotalDifficulty = "0" }); + IEngineRpcModule rpc = CreateEngineModule(chain); + + // Correct new payload + ExecutionPayload executionPayloadV11 = CreateBlockRequest( + CreateParentBlockRequestOnHead(chain.BlockTree), + TestItem.AddressA); + ResultWrapper<PayloadStatusV1> newPayloadResult1 = await rpc.engine_newPayloadV1(executionPayloadV11); + newPayloadResult1.Data.Status.Should().Be(PayloadStatus.Valid); + + // Fork choice updated with first np hash + ForkchoiceStateV1 forkChoiceState1 = new(executionPayloadV11.BlockHash, + executionPayloadV11.BlockHash, + executionPayloadV11.BlockHash); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult1 = + await rpc.engine_forkchoiceUpdatedV1(forkChoiceState1); + forkchoiceUpdatedResult1.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + + ResultWrapper<PayloadStatusV1> newPayloadResult2 = await rpc.engine_newPayloadV1(executionPayloadV11); + newPayloadResult2.Data.Status.Should().Be(PayloadStatus.Valid); + newPayloadResult2.Data.LatestValidHash.Should().Be(executionPayloadV11.BlockHash); + } - [Test] - public async Task payloadV1_latest_block_after_reorg() - { - using MergeTestBlockchain chain = - await CreateBlockChain(new MergeConfig() { TerminalTotalDifficulty = "0" }); - IEngineRpcModule rpc = CreateEngineModule(chain); + [Test] + public async Task payloadV1_invalid_parent_hash() + { + using MergeTestBlockchain chain = + await CreateBlockChain(new MergeConfig() { TerminalTotalDifficulty = "0" }); + IEngineRpcModule rpc = CreateEngineModule(chain); + + // Correct new payload + ExecutionPayload executionPayloadV11 = CreateBlockRequest( + CreateParentBlockRequestOnHead(chain.BlockTree), + TestItem.AddressA); + ResultWrapper<PayloadStatusV1> newPayloadResult1 = await rpc.engine_newPayloadV1(executionPayloadV11); + newPayloadResult1.Data.Status.Should().Be(PayloadStatus.Valid); + + // Fork choice updated with first np hash + ForkchoiceStateV1 forkChoiceState1 = new(executionPayloadV11.BlockHash, executionPayloadV11.BlockHash, + executionPayloadV11.BlockHash); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult1 = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState1); + forkchoiceUpdatedResult1.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + + // New payload unknown parent hash + ExecutionPayload executionPayloadV12A = CreateBlockRequest(executionPayloadV11, TestItem.AddressA); + executionPayloadV12A.ParentHash = TestItem.KeccakB; + TryCalculateHash(executionPayloadV12A, out Keccak? hash); + executionPayloadV12A.BlockHash = hash; + ResultWrapper<PayloadStatusV1> newPayloadResult2A = await rpc.engine_newPayloadV1(executionPayloadV12A); + newPayloadResult2A.Data.Status.Should().Be(PayloadStatus.Syncing); + + // Fork choice updated with unknown parent hash + ForkchoiceStateV1 forkChoiceState2A = new(executionPayloadV12A.BlockHash, + executionPayloadV12A.BlockHash, + executionPayloadV12A.BlockHash); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult2A = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState2A); + forkchoiceUpdatedResult2A.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Syncing); + + // New payload with correct parent hash + ExecutionPayload executionPayloadV12B = CreateBlockRequest(executionPayloadV11, TestItem.AddressA); + ResultWrapper<PayloadStatusV1> newPayloadResult2B = await rpc.engine_newPayloadV1(executionPayloadV12B); + newPayloadResult2B.Data.Status.Should().Be(PayloadStatus.Valid); + + // Fork choice updated with correct parent hash + ForkchoiceStateV1 forkChoiceState2B = new(executionPayloadV12B.BlockHash, executionPayloadV12B.BlockHash, + executionPayloadV12B.BlockHash); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult2B = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState2B); + forkchoiceUpdatedResult2B.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + + // New payload unknown parent hash + ExecutionPayload executionPayloadV13A = CreateBlockRequest(executionPayloadV12A, TestItem.AddressA); + ResultWrapper<PayloadStatusV1> newPayloadResult3A = await rpc.engine_newPayloadV1(executionPayloadV13A); + newPayloadResult3A.Data.Status.Should().Be(PayloadStatus.Syncing); + + // Fork choice updated with unknown parent hash + ForkchoiceStateV1 forkChoiceState3A = new(executionPayloadV13A.BlockHash, + executionPayloadV13A.BlockHash, + executionPayloadV13A.BlockHash); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult3A = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState3A); + forkchoiceUpdatedResult3A.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Syncing); + + ExecutionPayload executionPayloadV13B = CreateBlockRequest(executionPayloadV12B, TestItem.AddressA); + ResultWrapper<PayloadStatusV1> newPayloadResult3B = await rpc.engine_newPayloadV1(executionPayloadV13B); + newPayloadResult3B.Data.Status.Should().Be(PayloadStatus.Valid); + + // Fork choice updated with correct parent hash + ForkchoiceStateV1 forkChoiceState3B = new(executionPayloadV13B.BlockHash, executionPayloadV13B.BlockHash, + executionPayloadV13B.BlockHash); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult3B = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState3B); + forkchoiceUpdatedResult3B.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + } - Keccak prevRandao1 = TestItem.KeccakA; - Keccak prevRandao2 = TestItem.KeccakB; - Keccak prevRandao3 = TestItem.KeccakC; + [Test] + public async Task inconsistent_finalized_hash() + { + using MergeTestBlockchain chain = + await CreateBlockChain(new MergeConfig() { TerminalTotalDifficulty = "0" }); + IEngineRpcModule rpc = CreateEngineModule(chain); + + ExecutionPayload blockRequestResult1 = CreateBlockRequest( + CreateParentBlockRequestOnHead(chain.BlockTree), + TestItem.AddressA); + ResultWrapper<PayloadStatusV1> newPayloadResult1 = await rpc.engine_newPayloadV1(blockRequestResult1); + newPayloadResult1.Data.Status.Should().Be(PayloadStatus.Valid); + + ForkchoiceStateV1 forkChoiceState1 = new(blockRequestResult1.BlockHash, blockRequestResult1.BlockHash, + blockRequestResult1.BlockHash); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult1 = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState1); + forkchoiceUpdatedResult1.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + + ExecutionPayload blockRequestResult2A = CreateBlockRequest(blockRequestResult1, TestItem.AddressB); + ResultWrapper<PayloadStatusV1> newPayloadResult2A = await rpc.engine_newPayloadV1(blockRequestResult2A); + newPayloadResult2A.Data.Status.Should().Be(PayloadStatus.Valid); + + ExecutionPayload blockRequestResult2B = CreateBlockRequest(blockRequestResult1, TestItem.AddressA); + ResultWrapper<PayloadStatusV1> newPayloadResult2B = await rpc.engine_newPayloadV1(blockRequestResult2B); + newPayloadResult2B.Data.Status.Should().Be(PayloadStatus.Valid); + + ExecutionPayload blockRequestResult3B = CreateBlockRequest(blockRequestResult2B, TestItem.AddressA); + ResultWrapper<PayloadStatusV1> newPayloadResult3B = await rpc.engine_newPayloadV1(blockRequestResult3B); + newPayloadResult3B.Data.Status.Should().Be(PayloadStatus.Valid); + + ForkchoiceStateV1 forkChoiceState3 = new(blockRequestResult3B.BlockHash, blockRequestResult2A.BlockHash, + blockRequestResult3B.BlockHash); // finalized hash - inconsistent blockRequestResult2A + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult3 = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState3); + forkchoiceUpdatedResult3.ErrorCode.Should().Be(MergeErrorCodes.InvalidForkchoiceState); + } - { - ForkchoiceStateV1 forkChoiceStateGen = new(chain.BlockTree.Head!.Hash!, chain.BlockTree.Head!.Hash!, - chain.BlockTree.Head!.Hash!); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResultGen = - await rpc.engine_forkchoiceUpdatedV1(forkChoiceStateGen, - new PayloadAttributes() - { - Timestamp = Timestamper.UnixTime.Seconds, - PrevRandao = prevRandao1, - SuggestedFeeRecipient = Address.Zero - }); - forkchoiceUpdatedResultGen.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - } + [Test] + public async Task inconsistent_safe_hash() + { + using MergeTestBlockchain chain = + await CreateBlockChain(new MergeConfig() { TerminalTotalDifficulty = "0" }); + IEngineRpcModule rpc = CreateEngineModule(chain); + + ExecutionPayload blockRequestResult1 = CreateBlockRequest( + CreateParentBlockRequestOnHead(chain.BlockTree), + TestItem.AddressA); + ResultWrapper<PayloadStatusV1> newPayloadResult1 = await rpc.engine_newPayloadV1(blockRequestResult1); + newPayloadResult1.Data.Status.Should().Be(PayloadStatus.Valid); + + ForkchoiceStateV1 forkChoiceState1 = new(blockRequestResult1.BlockHash, blockRequestResult1.BlockHash, + blockRequestResult1.BlockHash); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult1 = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState1); + forkchoiceUpdatedResult1.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + + ExecutionPayload blockRequestResult2A = CreateBlockRequest(blockRequestResult1, TestItem.AddressB); + ResultWrapper<PayloadStatusV1> newPayloadResult2A = await rpc.engine_newPayloadV1(blockRequestResult2A); + newPayloadResult2A.Data.Status.Should().Be(PayloadStatus.Valid); + + ExecutionPayload blockRequestResult2B = CreateBlockRequest(blockRequestResult1, TestItem.AddressA); + ResultWrapper<PayloadStatusV1> newPayloadResult2B = await rpc.engine_newPayloadV1(blockRequestResult2B); + newPayloadResult2B.Data.Status.Should().Be(PayloadStatus.Valid); + + ExecutionPayload blockRequestResult3B = CreateBlockRequest(blockRequestResult2B, TestItem.AddressA); + ResultWrapper<PayloadStatusV1> newPayloadResult3B = await rpc.engine_newPayloadV1(blockRequestResult3B); + newPayloadResult3B.Data.Status.Should().Be(PayloadStatus.Valid); + + ForkchoiceStateV1 forkChoiceState3 = new(blockRequestResult3B.BlockHash, blockRequestResult3B.BlockHash, + blockRequestResult2A.BlockHash); // safe block hash - inconsistent blockRequestResult2A + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult3 = await rpc.engine_forkchoiceUpdatedV1(forkChoiceState3); + forkchoiceUpdatedResult3.ErrorCode.Should().Be(MergeErrorCodes.InvalidForkchoiceState); + } - // Add one block - ExecutionPayloadV1 executionPayloadV11 = CreateBlockRequest( - CreateParentBlockRequestOnHead(chain.BlockTree), - TestItem.AddressA); - executionPayloadV11.PrevRandao = prevRandao1; - TryCalculateHash(executionPayloadV11, out Keccak? hash1); - executionPayloadV11.BlockHash = hash1; + [Test] + public async Task payloadV1_latest_block_after_reorg() + { + using MergeTestBlockchain chain = + await CreateBlockChain(new MergeConfig() { TerminalTotalDifficulty = "0" }); + IEngineRpcModule rpc = CreateEngineModule(chain); - ResultWrapper<PayloadStatusV1> newPayloadResult1 = await rpc.engine_newPayloadV1(executionPayloadV11); - newPayloadResult1.Data.Status.Should().Be(PayloadStatus.Valid); + Keccak prevRandao1 = TestItem.KeccakA; + Keccak prevRandao2 = TestItem.KeccakB; + Keccak prevRandao3 = TestItem.KeccakC; - ForkchoiceStateV1 forkChoiceState1 = new(executionPayloadV11.BlockHash, - executionPayloadV11.BlockHash, executionPayloadV11.BlockHash); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult1 = - await rpc.engine_forkchoiceUpdatedV1(forkChoiceState1, + { + ForkchoiceStateV1 forkChoiceStateGen = new(chain.BlockTree.Head!.Hash!, chain.BlockTree.Head!.Hash!, + chain.BlockTree.Head!.Hash!); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResultGen = + await rpc.engine_forkchoiceUpdatedV1(forkChoiceStateGen, new PayloadAttributes() { Timestamp = Timestamper.UnixTime.Seconds, - PrevRandao = prevRandao2, + PrevRandao = prevRandao1, SuggestedFeeRecipient = Address.Zero }); - forkchoiceUpdatedResult1.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + forkchoiceUpdatedResultGen.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + } + // Add one block + ExecutionPayload executionPayloadV11 = CreateBlockRequest( + CreateParentBlockRequestOnHead(chain.BlockTree), + TestItem.AddressA); + executionPayloadV11.PrevRandao = prevRandao1; - { - ExecutionPayloadV1 executionPayloadV12 = CreateBlockRequest( - executionPayloadV11, - TestItem.AddressA); + TryCalculateHash(executionPayloadV11, out Keccak? hash1); + executionPayloadV11.BlockHash = hash1; + + ResultWrapper<PayloadStatusV1> newPayloadResult1 = await rpc.engine_newPayloadV1(executionPayloadV11); + newPayloadResult1.Data.Status.Should().Be(PayloadStatus.Valid); + + ForkchoiceStateV1 forkChoiceState1 = new(executionPayloadV11.BlockHash, + executionPayloadV11.BlockHash, executionPayloadV11.BlockHash); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult1 = + await rpc.engine_forkchoiceUpdatedV1(forkChoiceState1, + new PayloadAttributes() + { + Timestamp = Timestamper.UnixTime.Seconds, + PrevRandao = prevRandao2, + SuggestedFeeRecipient = Address.Zero + }); + forkchoiceUpdatedResult1.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - executionPayloadV12.PrevRandao = prevRandao3; - TryCalculateHash(executionPayloadV12, out Keccak? hash); - executionPayloadV12.BlockHash = hash; + { + ExecutionPayload executionPayloadV12 = CreateBlockRequest( + executionPayloadV11, + TestItem.AddressA); - ResultWrapper<PayloadStatusV1> newPayloadResult2 = await rpc.engine_newPayloadV1(executionPayloadV12); - newPayloadResult2.Data.Status.Should().Be(PayloadStatus.Valid); + executionPayloadV12.PrevRandao = prevRandao3; - ForkchoiceStateV1 forkChoiceState2 = new(executionPayloadV12.BlockHash, - executionPayloadV11.BlockHash, executionPayloadV11.BlockHash); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult2 = - await rpc.engine_forkchoiceUpdatedV1(forkChoiceState2); - forkchoiceUpdatedResult2.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + TryCalculateHash(executionPayloadV12, out Keccak? hash); + executionPayloadV12.BlockHash = hash; - Keccak currentBlockHash = chain.BlockTree.Head!.Hash!; - Assert.True(currentBlockHash == executionPayloadV12.BlockHash); - } + ResultWrapper<PayloadStatusV1> newPayloadResult2 = await rpc.engine_newPayloadV1(executionPayloadV12); + newPayloadResult2.Data.Status.Should().Be(PayloadStatus.Valid); - // re-org - { - ExecutionPayloadV1 executionPayloadV13 = CreateBlockRequest( - executionPayloadV11, - TestItem.AddressA); + ForkchoiceStateV1 forkChoiceState2 = new(executionPayloadV12.BlockHash, + executionPayloadV11.BlockHash, executionPayloadV11.BlockHash); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult2 = + await rpc.engine_forkchoiceUpdatedV1(forkChoiceState2); + forkchoiceUpdatedResult2.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); - executionPayloadV13.PrevRandao = prevRandao2; + Keccak currentBlockHash = chain.BlockTree.Head!.Hash!; + Assert.True(currentBlockHash == executionPayloadV12.BlockHash); + } + + // re-org + { + ExecutionPayload executionPayloadV13 = CreateBlockRequest( + executionPayloadV11, + TestItem.AddressA); - TryCalculateHash(executionPayloadV13, out Keccak? hash); - executionPayloadV13.BlockHash = hash; + executionPayloadV13.PrevRandao = prevRandao2; - ResultWrapper<PayloadStatusV1> newPayloadResult3 = await rpc.engine_newPayloadV1(executionPayloadV13); - newPayloadResult3.Data.Status.Should().Be(PayloadStatus.Valid); + TryCalculateHash(executionPayloadV13, out Keccak? hash); + executionPayloadV13.BlockHash = hash; - ForkchoiceStateV1 forkChoiceState3 = new(executionPayloadV13.BlockHash, - executionPayloadV11.BlockHash, executionPayloadV11.BlockHash); - ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult3 = - await rpc.engine_forkchoiceUpdatedV1(forkChoiceState3); - forkchoiceUpdatedResult3.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + ResultWrapper<PayloadStatusV1> newPayloadResult3 = await rpc.engine_newPayloadV1(executionPayloadV13); + newPayloadResult3.Data.Status.Should().Be(PayloadStatus.Valid); - Keccak currentBlockHash = chain.BlockTree.Head!.Hash!; - Assert.False(currentBlockHash != forkChoiceState3.HeadBlockHash || - currentBlockHash == forkChoiceState3.SafeBlockHash || - currentBlockHash == forkChoiceState3.FinalizedBlockHash); - } + ForkchoiceStateV1 forkChoiceState3 = new(executionPayloadV13.BlockHash, + executionPayloadV11.BlockHash, executionPayloadV11.BlockHash); + ResultWrapper<ForkchoiceUpdatedV1Result> forkchoiceUpdatedResult3 = + await rpc.engine_forkchoiceUpdatedV1(forkChoiceState3); + forkchoiceUpdatedResult3.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + + Keccak currentBlockHash = chain.BlockTree.Head!.Hash!; + Assert.False(currentBlockHash != forkChoiceState3.HeadBlockHash || + currentBlockHash == forkChoiceState3.SafeBlockHash || + currentBlockHash == forkChoiceState3.FinalizedBlockHash); } + } - private async Task<ExecutionPayloadV1> BuildAndGetPayloadResult( - IEngineRpcModule rpc, MergeTestBlockchain chain, Keccak headBlockHash, Keccak finalizedBlockHash, - Keccak safeBlockHash, - ulong timestamp, Keccak random, Address feeRecipient, bool waitForBlockImprovement = true) + private async Task<ExecutionPayload> BuildAndGetPayloadResult( + IEngineRpcModule rpc, MergeTestBlockchain chain, Keccak headBlockHash, Keccak finalizedBlockHash, + Keccak safeBlockHash, + ulong timestamp, Keccak random, Address feeRecipient, bool waitForBlockImprovement = true) + { + using SemaphoreSlim blockImprovementLock = new(0); + if (waitForBlockImprovement) { - using SemaphoreSlim blockImprovementLock = new(0); - if (waitForBlockImprovement) + chain.PayloadPreparationService!.BlockImproved += (s, e) => { - chain.PayloadPreparationService!.BlockImproved += (s, e) => - { - blockImprovementLock.Release(1); - }; - } - - ForkchoiceStateV1 forkchoiceState = new(headBlockHash, finalizedBlockHash, safeBlockHash); - PayloadAttributes payloadAttributes = - new() { Timestamp = timestamp, PrevRandao = random, SuggestedFeeRecipient = feeRecipient }; - string payloadId = rpc.engine_forkchoiceUpdatedV1(forkchoiceState, payloadAttributes).Result.Data.PayloadId; - if (waitForBlockImprovement) - await blockImprovementLock.WaitAsync(10000); - ResultWrapper<ExecutionPayloadV1?> getPayloadResult = - await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId)); - return getPayloadResult.Data!; + blockImprovementLock.Release(1); + }; } - private async Task<ExecutionPayloadV1> BuildAndGetPayloadResult(MergeTestBlockchain chain, - IEngineRpcModule rpc, PayloadAttributes payloadAttributes) - { - Keccak startingHead = chain.BlockTree.HeadHash; - Keccak parentHead = chain.BlockTree.Head!.ParentHash!; + ForkchoiceStateV1 forkchoiceState = new(headBlockHash, finalizedBlockHash, safeBlockHash); + PayloadAttributes payloadAttributes = + new() { Timestamp = timestamp, PrevRandao = random, SuggestedFeeRecipient = feeRecipient }; + string payloadId = rpc.engine_forkchoiceUpdatedV1(forkchoiceState, payloadAttributes).Result.Data.PayloadId; + if (waitForBlockImprovement) + await blockImprovementLock.WaitAsync(10000); + ResultWrapper<ExecutionPayload?> getPayloadResult = + await rpc.engine_getPayloadV1(Bytes.FromHexString(payloadId)); + return getPayloadResult.Data!; + } - return await BuildAndGetPayloadResult(rpc, chain, startingHead, parentHead, startingHead, - payloadAttributes.Timestamp, payloadAttributes.PrevRandao!, payloadAttributes.SuggestedFeeRecipient); - } + private async Task<ExecutionPayload> BuildAndGetPayloadResult(MergeTestBlockchain chain, + IEngineRpcModule rpc, PayloadAttributes payloadAttributes) + { + Keccak startingHead = chain.BlockTree.HeadHash; + Keccak parentHead = chain.BlockTree.Head!.ParentHash!; - private async Task<ExecutionPayloadV1> BuildAndGetPayloadResult(MergeTestBlockchain chain, - IEngineRpcModule rpc) - { - Keccak startingHead = chain.BlockTree.HeadHash; - Keccak parentHead = chain.BlockTree.Head!.ParentHash!; + return await BuildAndGetPayloadResult(rpc, chain, startingHead, parentHead, startingHead, + payloadAttributes.Timestamp, payloadAttributes.PrevRandao!, payloadAttributes.SuggestedFeeRecipient); + } - ulong timestamp = Timestamper.UnixTime.Seconds; - Keccak random = Keccak.Zero; - Address feeRecipient = Address.Zero; + private async Task<ExecutionPayload> BuildAndGetPayloadResult(MergeTestBlockchain chain, + IEngineRpcModule rpc) + { + Keccak startingHead = chain.BlockTree.HeadHash; + Keccak parentHead = chain.BlockTree.Head!.ParentHash!; - return await BuildAndGetPayloadResult(rpc, chain, startingHead, parentHead, startingHead, - timestamp, random, feeRecipient); - } + ulong timestamp = Timestamper.UnixTime.Seconds; + Keccak random = Keccak.Zero; + Address feeRecipient = Address.Zero; - private void AssertExecutionStatusChangedV1(IEngineRpcModule rpc, Keccak headBlockHash, - Keccak finalizedBlockHash, - Keccak confirmedBlockHash) - { - ExecutionStatusResult? result = rpc.engine_executionStatus().Data; - Assert.AreEqual(headBlockHash, result.HeadBlockHash); - Assert.AreEqual(finalizedBlockHash, result.FinalizedBlockHash); - Assert.AreEqual(confirmedBlockHash, result.SafeBlockHash); - } + return await BuildAndGetPayloadResult(rpc, chain, startingHead, parentHead, startingHead, + timestamp, random, feeRecipient); + } - private void AssertExecutionStatusNotChangedV1(IEngineRpcModule rpc, Keccak headBlockHash, - Keccak finalizedBlockHash, Keccak confirmedBlockHash) - { - ExecutionStatusResult? result = rpc.engine_executionStatus().Data; - Assert.AreNotEqual(headBlockHash, result.HeadBlockHash); - Assert.AreNotEqual(finalizedBlockHash, result.FinalizedBlockHash); - Assert.AreNotEqual(confirmedBlockHash, result.SafeBlockHash); - } + private void AssertExecutionStatusChangedV1(IEngineRpcModule rpc, Keccak headBlockHash, + Keccak finalizedBlockHash, + Keccak confirmedBlockHash) + { + ExecutionStatusResult? result = rpc.engine_executionStatus().Data; + Assert.AreEqual(headBlockHash, result.HeadBlockHash); + Assert.AreEqual(finalizedBlockHash, result.FinalizedBlockHash); + Assert.AreEqual(confirmedBlockHash, result.SafeBlockHash); + } + + private void AssertExecutionStatusNotChangedV1(IEngineRpcModule rpc, Keccak headBlockHash, + Keccak finalizedBlockHash, Keccak confirmedBlockHash) + { + ExecutionStatusResult? result = rpc.engine_executionStatus().Data; + Assert.AreNotEqual(headBlockHash, result.HeadBlockHash); + Assert.AreNotEqual(finalizedBlockHash, result.FinalizedBlockHash); + Assert.AreNotEqual(confirmedBlockHash, result.SafeBlockHash); } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs new file mode 100644 index 00000000000..6f8c012f8b3 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V2.cs @@ -0,0 +1,552 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +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.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Int256; +using Nethermind.JsonRpc; +using Nethermind.JsonRpc.Test; +using Nethermind.Merge.Plugin.Data; +using Nethermind.Specs.Forks; +using Nethermind.Specs.Test; +using Nethermind.State; +using NUnit.Framework; + +namespace Nethermind.Merge.Plugin.Test; + +public partial class EngineModuleTests +{ + [Test] + public virtual async Task Should_process_block_as_expected_V2() + { + using MergeTestBlockchain chain = await CreateShanghaiBlockChain(new MergeConfig { TerminalTotalDifficulty = "0" }); + IEngineRpcModule rpc = CreateEngineModule(chain); + Keccak startingHead = chain.BlockTree.HeadHash; + Keccak prevRandao = Keccak.Zero; + Address feeRecipient = TestItem.AddressC; + ulong timestamp = Timestamper.UnixTime.Seconds; + var fcuState = new + { + headBlockHash = startingHead.ToString(), + safeBlockHash = startingHead.ToString(), + finalizedBlockHash = Keccak.Zero.ToString() + }; + Withdrawal[] withdrawals = new[] + { + new Withdrawal { Index = 1, Amount = 3, Address = TestItem.AddressB, ValidatorIndex = 2 } + }; + var payloadAttrs = new + { + timestamp = timestamp.ToHexString(true), + prevRandao = prevRandao.ToString(), + suggestedFeeRecipient = feeRecipient.ToString(), + withdrawals + }; + string?[] @params = new string?[] + { + chain.JsonSerializer.Serialize(fcuState), + chain.JsonSerializer.Serialize(payloadAttrs) + }; + string expectedPayloadId = "0x6454408c425ddd96"; + + string response = RpcTest.TestSerializedRequest(rpc, "engine_forkchoiceUpdatedV2", @params!); + JsonRpcSuccessResponse? successResponse = chain.JsonSerializer.Deserialize<JsonRpcSuccessResponse>(response); + + successResponse.Should().NotBeNull(); + response.Should().Be(chain.JsonSerializer.Serialize(new JsonRpcSuccessResponse + { + Id = successResponse.Id, + Result = new ForkchoiceUpdatedV1Result + { + PayloadId = expectedPayloadId, + PayloadStatus = new PayloadStatusV1 + { + LatestValidHash = new("0x1c53bdbf457025f80c6971a9cf50986974eed02f0a9acaeeb49cafef10efd133"), + Status = PayloadStatus.Valid, + ValidationError = null + } + } + })); + + Keccak blockHash = new("0xed14029504c440624047d5d0223899fb2c8abc4550464ac21e8f42ccdbb472d3"); + Block block = new( + new( + startingHead, + Keccak.OfAnEmptySequenceRlp, + feeRecipient, + UInt256.Zero, + 1, + chain.BlockTree.Head!.GasLimit, + timestamp, + Bytes.FromHexString("0x4e65746865726d696e64") // Nethermind + ) + { + BaseFeePerGas = 0, + Bloom = Bloom.Empty, + GasUsed = 0, + Hash = blockHash, + MixHash = prevRandao, + ReceiptsRoot = chain.BlockTree.Head!.ReceiptsRoot!, + StateRoot = new("0xde9a4fd5deef7860dc840612c5e960c942b76a9b2e710504de9bab8289156491"), + }, + Array.Empty<Transaction>(), + Array.Empty<BlockHeader>(), + withdrawals + ); + GetPayloadV2Result expectedPayload = new(block, UInt256.Zero); + + response = RpcTest.TestSerializedRequest(rpc, "engine_getPayloadV2", expectedPayloadId); + successResponse = chain.JsonSerializer.Deserialize<JsonRpcSuccessResponse>(response); + + successResponse.Should().NotBeNull(); + response.Should().Be(chain.JsonSerializer.Serialize(new JsonRpcSuccessResponse + { + Id = successResponse.Id, + Result = expectedPayload + })); + + response = RpcTest.TestSerializedRequest(rpc, "engine_newPayloadV2", + chain.JsonSerializer.Serialize(new ExecutionPayload(block))); + successResponse = chain.JsonSerializer.Deserialize<JsonRpcSuccessResponse>(response); + + successResponse.Should().NotBeNull(); + response.Should().Be(chain.JsonSerializer.Serialize(new JsonRpcSuccessResponse + { + Id = successResponse.Id, + Result = new PayloadStatusV1 + { + LatestValidHash = blockHash, + Status = PayloadStatus.Valid, + ValidationError = null + } + })); + + fcuState = new + { + headBlockHash = blockHash.ToString(true), + safeBlockHash = blockHash.ToString(true), + finalizedBlockHash = startingHead.ToString(true) + }; + @params = new[] + { + chain.JsonSerializer.Serialize(fcuState), + null + }; + + response = RpcTest.TestSerializedRequest(rpc, "engine_forkchoiceUpdatedV2", @params!); + successResponse = chain.JsonSerializer.Deserialize<JsonRpcSuccessResponse>(response); + + successResponse.Should().NotBeNull(); + response.Should().Be(chain.JsonSerializer.Serialize(new JsonRpcSuccessResponse + { + Id = successResponse.Id, + Result = new ForkchoiceUpdatedV1Result + { + PayloadId = null, + PayloadStatus = new PayloadStatusV1 + { + LatestValidHash = blockHash, + Status = PayloadStatus.Valid, + ValidationError = null + } + } + })); + } + + [Test] + public virtual async Task forkchoiceUpdatedV1_should_fail_with_withdrawals() + { + using MergeTestBlockchain chain = await CreateBlockChain(new MergeConfig { TerminalTotalDifficulty = "0" }); + IEngineRpcModule rpcModule = CreateEngineModule(chain); + var fcuState = new + { + headBlockHash = Keccak.Zero.ToString(), + safeBlockHash = Keccak.Zero.ToString(), + finalizedBlockHash = Keccak.Zero.ToString() + }; + var payloadAttrs = new + { + timestamp = "0x0", + prevRandao = Keccak.Zero.ToString(), + suggestedFeeRecipient = Address.Zero.ToString(), + withdrawals = Enumerable.Empty<Withdrawal>() + }; + string[] @params = new[] + { + chain.JsonSerializer.Serialize(fcuState), + chain.JsonSerializer.Serialize(payloadAttrs) + }; + + string response = RpcTest.TestSerializedRequest(rpcModule, "engine_forkchoiceUpdatedV1", @params); + JsonRpcErrorResponse? errorResponse = chain.JsonSerializer.Deserialize<JsonRpcErrorResponse>(response); + + errorResponse.Should().NotBeNull(); + errorResponse!.Error.Should().NotBeNull(); + errorResponse!.Error!.Code.Should().Be(ErrorCodes.InvalidParams); + errorResponse!.Error!.Message.Should().Contain("Withdrawals not supported"); + } + + [TestCaseSource(nameof(GetWithdrawalValidationValues))] + public virtual async Task forkchoiceUpdatedV2_should_validate_withdrawals(( + IReleaseSpec Spec, + string ErrorMessage, + IEnumerable<Withdrawal>? Withdrawals, + string BlockHash + ) input) + { + using MergeTestBlockchain chain = await CreateBlockChain(null, null, input.Spec); + IEngineRpcModule rpcModule = CreateEngineModule(chain); + var fcuState = new + { + headBlockHash = chain.BlockTree.HeadHash.ToString(), + safeBlockHash = chain.BlockTree.HeadHash.ToString(), + finalizedBlockHash = Keccak.Zero.ToString() + }; + var payloadAttrs = new + { + timestamp = Timestamper.UnixTime.Seconds.ToHexString(true), + prevRandao = Keccak.Zero.ToString(), + suggestedFeeRecipient = TestItem.AddressA.ToString(), + withdrawals = input.Withdrawals + }; + string[] @params = new[] + { + chain.JsonSerializer.Serialize(fcuState), + chain.JsonSerializer.Serialize(payloadAttrs) + }; + + string response = RpcTest.TestSerializedRequest(rpcModule, "engine_forkchoiceUpdatedV2", @params); + JsonRpcErrorResponse? errorResponse = chain.JsonSerializer.Deserialize<JsonRpcErrorResponse>(response); + + errorResponse.Should().NotBeNull(); + errorResponse!.Error.Should().NotBeNull(); + errorResponse!.Error!.Code.Should().Be(MergeErrorCodes.InvalidPayloadAttributes); + errorResponse!.Error!.Message.Should().Be(string.Format(input.ErrorMessage, string.Empty)); + } + + [Test] + public async Task getPayloadV2_empty_block_should_have_zero_value() + { + 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<ResultWrapper<ForkchoiceUpdatedV1Result>> forkchoiceResponse = rpc.engine_forkchoiceUpdatedV1(forkchoiceState, payload); + + byte[] payloadId = Bytes.FromHexString(forkchoiceResponse.Result.Data.PayloadId!); + ResultWrapper<GetPayloadV2Result?> responseFirst = await rpc.engine_getPayloadV2(payloadId); + responseFirst.Should().NotBeNull(); + responseFirst.Result.ResultType.Should().Be(ResultType.Success); + responseFirst.Data!.BlockValue.Should().Be(0); + } + + [Test] + public async Task getPayloadV2_received_fees_should_be_equal_to_block_value_in_result() + { + using SemaphoreSlim blockImprovementLock = new(0); + using MergeTestBlockchain chain = await CreateBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + + Address feeRecipient = TestItem.AddressA; + + Keccak startingHead = chain.BlockTree.HeadHash; + uint count = 3; + int value = 10; + + PrivateKey sender = TestItem.PrivateKeyB; + 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 }) + .Result.Data.PayloadId!; + + UInt256 startingBalance = chain.StateReader.GetBalance(chain.State.StateRoot, feeRecipient); + + await blockImprovementLock.WaitAsync(10000); + GetPayloadV2Result getPayloadResult = (await rpc.engine_getPayloadV2(Bytes.FromHexString(payloadId))).Data!; + + ResultWrapper<PayloadStatusV1> executePayloadResult = await rpc.engine_newPayloadV1(getPayloadResult.ExecutionPayload); + executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); + + UInt256 finalBalance = chain.StateReader.GetBalance(getPayloadResult.ExecutionPayload.StateRoot, feeRecipient); + + (finalBalance - startingBalance).Should().Be(getPayloadResult.BlockValue); + } + + [Test] + public async Task getPayloadV2_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<GetPayloadV2Result?> responseFirst = await rpc.engine_getPayloadV2(payloadId); + responseFirst.Should().NotBeNull(); + responseFirst.Result.ResultType.Should().Be(ResultType.Failure); + responseFirst.ErrorCode.Should().Be(-38001); + } + + [Test] + public virtual async Task newPayloadV1_should_fail_with_withdrawals() + { + using MergeTestBlockchain chain = await CreateBlockChain(new MergeConfig { TerminalTotalDifficulty = "0" }); + IEngineRpcModule rpcModule = CreateEngineModule(chain); + ExecutionPayload expectedPayload = new() + { + BaseFeePerGas = 0, + BlockHash = Keccak.Zero, + BlockNumber = 1, + ExtraData = Array.Empty<byte>(), + FeeRecipient = Address.Zero, + GasLimit = 0, + GasUsed = 0, + LogsBloom = Bloom.Empty, + ParentHash = Keccak.Zero, + PrevRandao = Keccak.Zero, + ReceiptsRoot = Keccak.Zero, + StateRoot = Keccak.Zero, + Timestamp = 0, + Transactions = Array.Empty<byte[]>(), + Withdrawals = Enumerable.Empty<Withdrawal>() + }; + + string response = RpcTest.TestSerializedRequest(rpcModule, "engine_newPayloadV1", + chain.JsonSerializer.Serialize(expectedPayload)); + JsonRpcErrorResponse? errorResponse = chain.JsonSerializer.Deserialize<JsonRpcErrorResponse>(response); + + errorResponse.Should().NotBeNull(); + errorResponse!.Error.Should().NotBeNull(); + errorResponse!.Error!.Code.Should().Be(ErrorCodes.InvalidParams); + errorResponse!.Error!.Message.Should().Contain("Withdrawals not supported"); + } + + [TestCaseSource(nameof(GetWithdrawalValidationValues))] + public virtual async Task newPayloadV2_should_validate_withdrawals(( + IReleaseSpec Spec, + string ErrorMessage, + IEnumerable<Withdrawal>? Withdrawals, + string BlockHash + ) input) + { + using MergeTestBlockchain chain = await CreateBlockChain(null, null, input.Spec); + IEngineRpcModule rpcModule = CreateEngineModule(chain); + Keccak blockHash = new(input.BlockHash); + Keccak startingHead = chain.BlockTree.HeadHash; + Keccak prevRandao = Keccak.Zero; + Address feeRecipient = TestItem.AddressC; + ulong timestamp = Timestamper.UnixTime.Seconds; + ExecutionPayload expectedPayload = new() + { + BaseFeePerGas = 0, + BlockHash = blockHash, + BlockNumber = 1, + ExtraData = Bytes.FromHexString("0x4e65746865726d696e64"), // Nethermind + FeeRecipient = feeRecipient, + GasLimit = chain.BlockTree.Head!.GasLimit, + GasUsed = 0, + LogsBloom = Bloom.Empty, + ParentHash = startingHead, + PrevRandao = prevRandao, + ReceiptsRoot = chain.BlockTree.Head!.ReceiptsRoot!, + StateRoot = new("0xde9a4fd5deef7860dc840612c5e960c942b76a9b2e710504de9bab8289156491"), + Timestamp = timestamp, + Transactions = Array.Empty<byte[]>(), + Withdrawals = input.Withdrawals + }; + + string response = RpcTest.TestSerializedRequest(rpcModule, "engine_newPayloadV2", + chain.JsonSerializer.Serialize(expectedPayload)); + JsonRpcSuccessResponse? successResponse = chain.JsonSerializer.Deserialize<JsonRpcSuccessResponse>(response); + + successResponse.Should().NotBeNull(); + response.Should().Be(chain.JsonSerializer.Serialize(new JsonRpcSuccessResponse + { + Id = successResponse.Id, + Result = new PayloadStatusV1 + { + LatestValidHash = startingHead, + Status = PayloadStatus.Invalid, + ValidationError = string.Format(input.ErrorMessage, $"in block {blockHash} ") + } + })); + } + + protected static IEnumerable<( + IReleaseSpec spec, + string ErrorMessage, + IEnumerable<Withdrawal>? Withdrawals, + string blockHash + )> GetWithdrawalValidationValues() + { + yield return ( + Shanghai.Instance, + "Withdrawals cannot be null {0}when EIP-4895 activated.", + null, + "0x6817d4b48be0bc14f144cc242cdc47a5ccc40de34b9c3934acad45057369f576"); + yield return ( + London.Instance, + "Withdrawals must be null {0}when EIP-4895 not activated.", + Enumerable.Empty<Withdrawal>(), + "0xaa4aa15951a28e6adab430a795e36a84649bbafb1257eda23e38b9131cbd3b98"); + } + + [TestCaseSource(nameof(ZeroWithdrawalsTestCases))] + public async Task executePayloadV2_works_correctly_when_0_withdrawals_applied(( + IReleaseSpec ReleaseSpec, + Withdrawal[]? Withdrawals, + bool IsValid) input) + { + using MergeTestBlockchain chain = await CreateBlockChain(null, null, input.ReleaseSpec); + IEngineRpcModule rpc = CreateEngineModule(chain); + ExecutionPayload executionPayload = CreateBlockRequest(CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD, input.Withdrawals); + ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV2(executionPayload); + resultWrapper.Data.Status.Should().Be(input.IsValid ? PayloadStatus.Valid : PayloadStatus.Invalid); + } + + protected static IEnumerable<( + IReleaseSpec releaseSpec, + Withdrawal[]? Withdrawals, + bool isValid + )> ZeroWithdrawalsTestCases() + { + yield return (London.Instance, null, true); + yield return (Shanghai.Instance, null, false); + yield return (London.Instance, Array.Empty<Withdrawal>(), false); + yield return (Shanghai.Instance, Array.Empty<Withdrawal>(), true); + yield return (London.Instance, new[] { TestItem.WithdrawalA_1Eth, TestItem.WithdrawalB_2Eth }, false); + } + + [TestCaseSource(nameof(WithdrawalsTestCases))] + public async Task Can_apply_withdrawals_correctly((Withdrawal[][] Withdrawals, (Address Account, UInt256 BalanceIncrease)[] ExpectedAccountIncrease) input) + { + using MergeTestBlockchain chain = await CreateShanghaiBlockChain(); + IEngineRpcModule rpc = CreateEngineModule(chain); + + // get initial balances + List<UInt256> initialBalances = new(); + foreach ((Address Account, UInt256 BalanceIncrease) accountIncrease in input.ExpectedAccountIncrease) + { + 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!; + ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV2(payload!); + resultWrapper.Data.Status.Should().Be(PayloadStatus.Valid); + ResultWrapper<ForkchoiceUpdatedV1Result> resultFcu = await rpc.engine_forkchoiceUpdatedV2( + new(payload.BlockHash, payload.BlockHash, payload.BlockHash)); + resultFcu.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + } + + // check balance increase + 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); + currentBalance.Should().Be(accountIncrease.BalanceIncrease + initialBalances[index]); + } + } + + [Test] + public async Task Should_handle_withdrawals_transition_when_Shanghai_fork_activated() + { + // Shanghai fork, ForkActivation.Timestamp = 3 + 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); + ResultWrapper<PayloadStatusV1> resultWrapper = await rpc.engine_newPayloadV2(executionPayload); + resultWrapper.Data.Status.Should().Be(PayloadStatus.Valid); + + // Block with withdrawals, Timestamp = 3 + PayloadAttributes payloadAttributes = new() + { + Timestamp = chain.BlockTree.Head!.Timestamp + 2, + PrevRandao = TestItem.KeccakH, + SuggestedFeeRecipient = TestItem.AddressF, + Withdrawals = new[] { TestItem.WithdrawalA_1Eth } + }; + ExecutionPayload payloadWithWithdrawals = (await BuildAndGetPayloadResultV2(rpc, chain, payloadAttributes))?.ExecutionPayload!; + ResultWrapper<PayloadStatusV1> resultWithWithdrawals = await rpc.engine_newPayloadV2(payloadWithWithdrawals!); + + resultWithWithdrawals.Data.Status.Should().Be(PayloadStatus.Valid); + + ResultWrapper<ForkchoiceUpdatedV1Result> fcuResult = await rpc.engine_forkchoiceUpdatedV2( + new(payloadWithWithdrawals.BlockHash, payloadWithWithdrawals.BlockHash, payloadWithWithdrawals.BlockHash)); + + fcuResult.Data.PayloadStatus.Status.Should().Be(PayloadStatus.Valid); + } + + [Test] + public void Should_print_payload_attributes_as_expected() + { + PayloadAttributes attrs = new() + { + Timestamp = 1, + PrevRandao = TestItem.KeccakH, + SuggestedFeeRecipient = TestItem.AddressF, + Withdrawals = new[] { TestItem.WithdrawalA_1Eth } + }; + + attrs.ToString().Should().Be( + $"PayloadAttributes {{Timestamp: {attrs.Timestamp}, PrevRandao: {attrs.PrevRandao}, SuggestedFeeRecipient: {attrs.SuggestedFeeRecipient}, Withdrawals count: {attrs.Withdrawals.Count}}}"); + } + + private static async Task<GetPayloadV2Result> BuildAndGetPayloadResultV2( + IEngineRpcModule rpc, MergeTestBlockchain chain, PayloadAttributes payloadAttributes) + { + Keccak currentHeadHash = chain.BlockTree.HeadHash; + ForkchoiceStateV1 forkchoiceState = new(currentHeadHash, currentHeadHash, currentHeadHash); + string payloadId = rpc.engine_forkchoiceUpdatedV2(forkchoiceState, payloadAttributes).Result.Data.PayloadId!; + ResultWrapper<GetPayloadV2Result?> getPayloadResult = + await rpc.engine_getPayloadV2(Bytes.FromHexString(payloadId)); + return getPayloadResult.Data!; + } + + protected static IEnumerable<( + Withdrawal[][] Withdrawals, // withdrawals per payload + (Address, UInt256)[] expectedAccountIncrease)> WithdrawalsTestCases() + { + yield return (new[] { Array.Empty<Withdrawal>() }, 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.WithdrawalA_1Eth }, // 1st payload + new[] { TestItem.WithdrawalA_1Eth }, // 2nd payload + Array.Empty<Withdrawal>(), // 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()) }); + } +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/ForkChoiceUpdatedRequestTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/ForkChoiceUpdatedRequestTests.cs index af7e57e7c9e..cfe4b122bb7 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/ForkChoiceUpdatedRequestTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/ForkChoiceUpdatedRequestTests.cs @@ -4,7 +4,6 @@ using FluentAssertions; using Nethermind.Core.Test.Builders; using Nethermind.Merge.Plugin.Data; -using Nethermind.Merge.Plugin.Data.V1; using Nethermind.Serialization.Json; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/InvalidChainTracker/InvalidBlockInterceptorTest.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/InvalidChainTracker/InvalidBlockInterceptorTest.cs index 9a40ba59d42..a1f2aefaaa8 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/InvalidChainTracker/InvalidBlockInterceptorTest.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/InvalidChainTracker/InvalidBlockInterceptorTest.cs @@ -103,4 +103,22 @@ public void TestBlockWithNotMatchingTxShouldNotGetTracked() _tracker.DidNotReceive().OnInvalidBlock(block.Hash, block.ParentHash); } + [Test] + public void TestBlockWithIncorrectWithdrawalsShouldNotGetTracked() + { + Block block = Build.A.Block + .WithWithdrawals(10) + .TestObject; + + block = new Block(block.Header, block.Body.WithChangedWithdrawals( + block.Withdrawals.Take(8).ToArray() + )); + + _baseValidator.ValidateSuggestedBlock(block).Returns(false); + _invalidBlockInterceptor.ValidateSuggestedBlock(block); + + _tracker.DidNotReceive().SetChildParent(block.Hash, block.ParentHash); + _tracker.DidNotReceive().OnInvalidBlock(block.Hash, block.ParentHash); + } + } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostBlockImprovementContext.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostBlockImprovementContext.cs index 809179efde7..ed8d78b6067 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostBlockImprovementContext.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostBlockImprovementContext.cs @@ -2,16 +2,14 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Nethermind.Consensus.Producers; using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Evm.Tracing; -using Nethermind.Facade.Proxy; using Nethermind.Int256; -using Nethermind.Merge.Plugin.Data.V1; +using Nethermind.Merge.Plugin.Data; using Nethermind.State; namespace Nethermind.Merge.Plugin.BlockProduction.Boost; @@ -55,7 +53,7 @@ public BoostBlockImprovementContext(Block currentBestBlock, CurrentBestBlock = block; BlockFees = _feesTracer.Fees; UInt256 balanceAfter = _stateReader.GetAccount(block.StateRoot!, payloadAttributes.SuggestedFeeRecipient)?.Balance ?? UInt256.Zero; - await _boostRelay.SendPayload(new BoostExecutionPayloadV1 { Block = new ExecutionPayloadV1(block), Profit = balanceAfter - balanceBefore }, cancellationToken); + await _boostRelay.SendPayload(new BoostExecutionPayloadV1 { Block = new ExecutionPayload(block), Profit = balanceAfter - balanceBefore }, cancellationToken); } return CurrentBestBlock; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostExecutionPayloadV1.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostExecutionPayloadV1.cs index d0b7976e00e..64659783ab4 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostExecutionPayloadV1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/Boost/BoostExecutionPayloadV1.cs @@ -2,12 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Int256; -using Nethermind.Merge.Plugin.Data.V1; +using Nethermind.Merge.Plugin.Data; namespace Nethermind.Merge.Plugin.BlockProduction.Boost; public class BoostExecutionPayloadV1 { - public ExecutionPayloadV1 Block { get; init; } = null!; + public ExecutionPayload Block { get; init; } = null!; public UInt256 Profit { get; init; } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PayloadPreparationService.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PayloadPreparationService.cs index ca4fa60b30f..84b2c983a6c 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PayloadPreparationService.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PayloadPreparationService.cs @@ -14,12 +14,12 @@ using Nethermind.Core.Timers; using Nethermind.Int256; using Nethermind.Logging; -using Nethermind.Merge.Plugin.Handlers.V1; +using Nethermind.Merge.Plugin.Handlers; namespace Nethermind.Merge.Plugin.BlockProduction { /// <summary> - /// A cache of pending payloads. A payload is created whenever a consensus client requests a payload creation in <see cref="ForkchoiceUpdatedV1Handler"/>. + /// A cache of pending payloads. A payload is created whenever a consensus client requests a payload creation in <see cref="ForkchoiceUpdatedHandler"/>. /// <seealso cref="https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#engine_forkchoiceupdatedv1"/> /// Each payload is assigned a payloadId which can be used by the consensus client to retrieve payload later by calling a <see cref="GetPayloadV1Handler"/>. /// <seealso cref="https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#engine_getpayloadv1"/> diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PostMergeBlockProducer.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PostMergeBlockProducer.cs index 1838f708795..e0600ae90cf 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PostMergeBlockProducer.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PostMergeBlockProducer.cs @@ -55,7 +55,7 @@ public virtual Block PrepareEmptyBlock(BlockHeader parent, PayloadAttributes? pa blockHeader.TxRoot = Keccak.EmptyTreeHash; blockHeader.Bloom = Bloom.Empty; - return new(blockHeader, Array.Empty<Transaction>(), Array.Empty<BlockHeader>()); + return new(blockHeader, Array.Empty<Transaction>(), Array.Empty<BlockHeader>(), payloadAttributes?.Withdrawals); } protected override Block PrepareBlock(BlockHeader parent, PayloadAttributes? payloadAttributes = null) diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs new file mode 100644 index 00000000000..1f75a1746e9 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs @@ -0,0 +1,146 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Int256; +using Nethermind.Serialization.Rlp; +using Nethermind.State.Proofs; + +namespace Nethermind.Merge.Plugin.Data; + +/// <summary> +/// Represents an object mapping the <c>ExecutionPayload</c> structure of the beacon chain spec. +/// </summary> +public class ExecutionPayload +{ + public ExecutionPayload() { } // Needed for tests + + public ExecutionPayload(Block block) + { + BlockHash = block.Hash!; + ParentHash = block.ParentHash!; + FeeRecipient = block.Beneficiary!; + StateRoot = block.StateRoot!; + BlockNumber = block.Number; + GasLimit = block.GasLimit; + GasUsed = block.GasUsed; + ReceiptsRoot = block.ReceiptsRoot!; + LogsBloom = block.Bloom!; + PrevRandao = block.MixHash ?? Keccak.Zero; + ExtraData = block.ExtraData!; + Timestamp = block.Timestamp; + BaseFeePerGas = block.BaseFeePerGas; + Withdrawals = block.Withdrawals; + + SetTransactions(block.Transactions); + } + + public UInt256 BaseFeePerGas { get; set; } + + public Keccak BlockHash { get; set; } = Keccak.Zero; + + public long BlockNumber { get; set; } + + public byte[] ExtraData { get; set; } = Array.Empty<byte>(); + + public Address FeeRecipient { get; set; } = Address.Zero; + + public long GasLimit { get; set; } + + public long GasUsed { get; set; } + + public Bloom LogsBloom { get; set; } = Bloom.Empty; + + public Keccak ParentHash { get; set; } = Keccak.Zero; + + public Keccak PrevRandao { get; set; } = Keccak.Zero; + + public Keccak ReceiptsRoot { get; set; } = Keccak.Zero; + + public Keccak StateRoot { get; set; } = Keccak.Zero; + + public ulong Timestamp { get; set; } + + /// <summary> + /// Gets or sets an array of RLP-encoded transaction where each item is a byte list (data) + /// representing <c>TransactionType || TransactionPayload</c> or <c>LegacyTransaction</c> as defined in + /// <see href="https://eips.ethereum.org/EIPS/eip-2718">EIP-2718</see>. + /// </summary> + public byte[][] Transactions { get; set; } = Array.Empty<byte[]>(); + + /// <summary> + /// Gets or sets a collection of <see cref="Withdrawal"/> as defined in + /// <see href="https://eips.ethereum.org/EIPS/eip-4895">EIP-4895</see>. + /// </summary> + public IEnumerable<Withdrawal>? Withdrawals { get; set; } + + /// <summary> + /// Creates the execution block from payload. + /// </summary> + /// <param name="block">When this method returns, contains the execution block.</param> + /// <param name="totalDifficulty">A total difficulty of the block.</param> + /// <returns><c>true</c> if block created successfully; otherise, <c>false</c>.</returns> + public virtual bool TryGetBlock(out Block? block, UInt256? totalDifficulty = null) + { + try + { + var transactions = GetTransactions(); + var header = new BlockHeader( + ParentHash, + Keccak.OfAnEmptySequenceRlp, + FeeRecipient, + UInt256.Zero, + BlockNumber, + GasLimit, + Timestamp, + ExtraData) + { + Hash = BlockHash, + ReceiptsRoot = ReceiptsRoot, + StateRoot = StateRoot, + Bloom = LogsBloom, + GasUsed = GasUsed, + BaseFeePerGas = BaseFeePerGas, + Nonce = 0, + MixHash = PrevRandao, + Author = FeeRecipient, + IsPostMerge = true, + TotalDifficulty = totalDifficulty, + TxRoot = new TxTrie(transactions).RootHash, + WithdrawalsRoot = Withdrawals is null ? null : new WithdrawalTrie(Withdrawals).RootHash, + }; + + block = new(header, transactions, Array.Empty<BlockHeader>(), Withdrawals); + + return true; + } + catch (Exception) + { + block = null; + + return false; + } + } + + /// <summary> + /// Decodes and returns an array of <see cref="Transaction"/> from <see cref="Transactions"/>. + /// </summary> + /// <returns>An RLP-decoded array of <see cref="Transaction"/>.</returns> + public Transaction[] GetTransactions() => Transactions + .Select(t => Rlp.Decode<Transaction>(t, RlpBehaviors.SkipTypedWrapping)) + .ToArray(); + + /// <summary> + /// RLP-encodes and sets the transactions specified to <see cref="Transactions"/>. + /// </summary> + /// <param name="transactions">An array of transactions to encode.</param> + public void SetTransactions(params Transaction[] transactions) => Transactions = transactions + .Select(t => Rlp.Encode(t, RlpBehaviors.SkipTypedWrapping).Bytes) + .ToArray(); + + public override string ToString() => $"{BlockNumber} ({BlockHash})"; +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/ExecutionPayloadBodyV1Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV1Result.cs similarity index 93% rename from src/Nethermind/Nethermind.Merge.Plugin/Data/V1/ExecutionPayloadBodyV1Result.cs rename to src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV1Result.cs index 42ec2a0b167..17a06784a9a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/ExecutionPayloadBodyV1Result.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayloadBodyV1Result.cs @@ -5,7 +5,7 @@ using Nethermind.Core; using Nethermind.Serialization.Rlp; -namespace Nethermind.Merge.Plugin.Data.V1 +namespace Nethermind.Merge.Plugin.Data { public class ExecutionPayloadBodyV1Result diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceStateV1.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceStateV1.cs new file mode 100644 index 00000000000..02b2d061bcf --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceStateV1.cs @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; + +namespace Nethermind.Merge.Plugin.Data; + +/// <summary> +/// Arguments to engine_ForkChoiceUpdate +/// +/// <seealso cref="https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#forkchoicestatev1"/> +/// </summary> +public class ForkchoiceStateV1 +{ + public ForkchoiceStateV1(Keccak headBlockHash, Keccak finalizedBlockHash, Keccak safeBlockHash) + { + HeadBlockHash = headBlockHash; + FinalizedBlockHash = finalizedBlockHash; + SafeBlockHash = safeBlockHash; + } + + /// <summary> + /// Hash of the head of the canonical chain. + /// </summary> + public Keccak HeadBlockHash { get; set; } + + /// <summary> + /// Safe block hash of the canonical chain under certain synchrony and honesty assumptions. This value MUST be either equal to or an ancestor of headBlockHash. + /// </summary> + /// <remarks>Can be <see cref="Keccak.Zero"/> when transition block is not finalized yet.</remarks> + public Keccak SafeBlockHash { get; set; } + + /// <summary> + /// Hash of the most recent finalized block + /// </summary> + /// <remarks>Can be <see cref="Keccak.Zero"/> when transition block is not finalized yet.</remarks> + public Keccak FinalizedBlockHash { get; set; } + + public override string ToString() => + $"ForkchoiceState: {{{nameof(HeadBlockHash)}: {HeadBlockHash}, {nameof(SafeBlockHash)}: {SafeBlockHash}, {nameof(FinalizedBlockHash)}: {FinalizedBlockHash}}}"; +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/ForkchoiceUpdatedV1Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceUpdatedV1Result.cs similarity index 65% rename from src/Nethermind/Nethermind.Merge.Plugin/Data/V1/ForkchoiceUpdatedV1Result.cs rename to src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceUpdatedV1Result.cs index 697a1115a47..e42cb40795b 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/ForkchoiceUpdatedV1Result.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceUpdatedV1Result.cs @@ -6,7 +6,7 @@ using Nethermind.JsonRpc; using Newtonsoft.Json; -namespace Nethermind.Merge.Plugin.Data.V1 +namespace Nethermind.Merge.Plugin.Data { /// <summary> /// Result of engine_forkChoiceUpdate call. @@ -18,13 +18,28 @@ public class ForkchoiceUpdatedV1Result public static readonly ResultWrapper<ForkchoiceUpdatedV1Result> Syncing = ResultWrapper<ForkchoiceUpdatedV1Result>.Success(new ForkchoiceUpdatedV1Result { PayloadId = null, PayloadStatus = PayloadStatusV1.Syncing }); public static ResultWrapper<ForkchoiceUpdatedV1Result> Valid(string? payloadId, Keccak? latestValidHash) => - ResultWrapper<ForkchoiceUpdatedV1Result>.Success(new ForkchoiceUpdatedV1Result { PayloadId = payloadId, PayloadStatus = new PayloadStatusV1() { Status = Data.V1.PayloadStatus.Valid, LatestValidHash = latestValidHash } }); + ResultWrapper<ForkchoiceUpdatedV1Result>.Success( + new ForkchoiceUpdatedV1Result + { + PayloadId = payloadId, + PayloadStatus = new PayloadStatusV1 + { + Status = Data.PayloadStatus.Valid, + LatestValidHash = latestValidHash + } + }); public static ResultWrapper<ForkchoiceUpdatedV1Result> Invalid(Keccak? latestValidHash, string? validationError = null) => - ResultWrapper<ForkchoiceUpdatedV1Result>.Success(new ForkchoiceUpdatedV1Result - { - PayloadStatus = new PayloadStatusV1 { Status = Data.V1.PayloadStatus.Invalid, LatestValidHash = latestValidHash, ValidationError = validationError } - }); + ResultWrapper<ForkchoiceUpdatedV1Result>.Success( + new ForkchoiceUpdatedV1Result + { + PayloadStatus = new PayloadStatusV1 + { + Status = Data.PayloadStatus.Invalid, + LatestValidHash = latestValidHash, + ValidationError = validationError + } + }); public static ResultWrapper<ForkchoiceUpdatedV1Result> Error(string message, int errorCode) => ResultWrapper<ForkchoiceUpdatedV1Result>.Fail(message, errorCode); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV2Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV2Result.cs new file mode 100644 index 00000000000..31df7cbb3d9 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/GetPayloadV2Result.cs @@ -0,0 +1,22 @@ +// 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 GetPayloadV2Result +{ + public GetPayloadV2Result(Block block, UInt256 blockFees) + { + BlockValue = blockFees; + ExecutionPayload = new(block); + } + + public UInt256 BlockValue { get; } + + public ExecutionPayload ExecutionPayload { get; } + + public override string ToString() => $"{{ExecutionPayload: {ExecutionPayload}, Fees: {BlockValue}}}"; +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/NewPayloadV1Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/NewPayloadV1Result.cs similarity index 96% rename from src/Nethermind/Nethermind.Merge.Plugin/Data/V1/NewPayloadV1Result.cs rename to src/Nethermind/Nethermind.Merge.Plugin/Data/NewPayloadV1Result.cs index 4caa62beea4..f9ae11f84fa 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/NewPayloadV1Result.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/NewPayloadV1Result.cs @@ -4,7 +4,7 @@ using Nethermind.Core.Crypto; using Nethermind.JsonRpc; -namespace Nethermind.Merge.Plugin.Data.V1; +namespace Nethermind.Merge.Plugin.Data; /// <summary> /// Wraps <see cref="PayloadStatusV1"/> in <see cref="ResultWrapper{T}"/> for JSON RPC. diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/PayloadStatusV1.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/PayloadStatusV1.cs similarity index 97% rename from src/Nethermind/Nethermind.Merge.Plugin/Data/V1/PayloadStatusV1.cs rename to src/Nethermind/Nethermind.Merge.Plugin/Data/PayloadStatusV1.cs index d5799e5cc1b..e7e8f24a61e 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/PayloadStatusV1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/PayloadStatusV1.cs @@ -4,7 +4,7 @@ using Nethermind.Core.Crypto; using Newtonsoft.Json; -namespace Nethermind.Merge.Plugin.Data.V1 +namespace Nethermind.Merge.Plugin.Data { /// <summary> /// Result of engine_newPayloadV1 call. diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/Statuses.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/Statuses.cs similarity index 95% rename from src/Nethermind/Nethermind.Merge.Plugin/Data/V1/Statuses.cs rename to src/Nethermind/Nethermind.Merge.Plugin/Data/Statuses.cs index a238f61e602..0a6c60cc0fd 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/Statuses.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/Statuses.cs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -namespace Nethermind.Merge.Plugin.Data.V1 +namespace Nethermind.Merge.Plugin.Data { public static class PayloadStatus { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/TransitionConfigurationV1.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/TransitionConfigurationV1.cs similarity index 95% rename from src/Nethermind/Nethermind.Merge.Plugin/Data/V1/TransitionConfigurationV1.cs rename to src/Nethermind/Nethermind.Merge.Plugin/Data/TransitionConfigurationV1.cs index 7c40ee5c954..d42b1c6da50 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/TransitionConfigurationV1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/TransitionConfigurationV1.cs @@ -4,7 +4,7 @@ using Nethermind.Core.Crypto; using Nethermind.Int256; -namespace Nethermind.Merge.Plugin.Data.V1; +namespace Nethermind.Merge.Plugin.Data; /// <summary> /// Result of call. diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/ExecutionPayloadV1.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/ExecutionPayloadV1.cs deleted file mode 100644 index d6e4df74106..00000000000 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/ExecutionPayloadV1.cs +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Int256; -using Nethermind.Serialization.Rlp; -using Nethermind.State.Proofs; -using Newtonsoft.Json; - -namespace Nethermind.Merge.Plugin.Data.V1 -{ - /// <summary> - /// A data object representing a block as being sent from the execution layer to the consensus layer. - /// - /// <seealso cref="https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#executionpayloadv1"/> - /// </summary> - public class ExecutionPayloadV1 - { - public ExecutionPayloadV1() - { - BlockHash = Keccak.Zero; - ParentHash = Keccak.Zero; - FeeRecipient = Address.Zero; - StateRoot = Keccak.Zero; - ReceiptsRoot = Keccak.Zero; - LogsBloom = Bloom.Empty; - PrevRandao = Keccak.Zero; - ExtraData = Array.Empty<byte>(); - } - - public ExecutionPayloadV1(Block block) - { - BlockHash = block.Hash!; - ParentHash = block.ParentHash!; - FeeRecipient = block.Beneficiary!; - StateRoot = block.StateRoot!; - BlockNumber = block.Number; - GasLimit = block.GasLimit; - GasUsed = block.GasUsed; - ReceiptsRoot = block.ReceiptsRoot!; - LogsBloom = block.Bloom!; - PrevRandao = block.MixHash ?? Keccak.Zero; - SetTransactions(block.Transactions); - ExtraData = block.ExtraData!; - Timestamp = block.Timestamp; - BaseFeePerGas = block.BaseFeePerGas; - } - - public bool TryGetBlock(out Block? block, UInt256? totalDifficulty = null) - { - try - { - BlockHeader header = new(ParentHash, Keccak.OfAnEmptySequenceRlp, FeeRecipient, UInt256.Zero, BlockNumber, GasLimit, Timestamp, ExtraData) - { - Hash = BlockHash, - ReceiptsRoot = ReceiptsRoot, - StateRoot = StateRoot, - Bloom = LogsBloom, - GasUsed = GasUsed, - BaseFeePerGas = BaseFeePerGas, - Nonce = 0, - MixHash = PrevRandao, - Author = FeeRecipient - }; - Transaction[] transactions = GetTransactions(); - header.TxRoot = new TxTrie(transactions).RootHash; - header.IsPostMerge = true; - header.TotalDifficulty = totalDifficulty; - block = new Block(header, transactions, Array.Empty<BlockHeader>()); - return true; - } - catch (Exception) - { - block = null; - return false; - } - } - - public Keccak ParentHash { get; set; } = null!; - public Address FeeRecipient { get; set; } - public Keccak StateRoot { get; set; } = null!; - public Keccak ReceiptsRoot { get; set; } = null!; - - [JsonProperty(NullValueHandling = NullValueHandling.Include)] - public Bloom LogsBloom { get; set; } = Bloom.Empty; - public Keccak PrevRandao { get; set; } = Keccak.Zero; - - [JsonProperty(NullValueHandling = NullValueHandling.Include)] - public long BlockNumber { get; set; } - public long GasLimit { get; set; } - public long GasUsed { get; set; } - public ulong Timestamp { get; set; } - public byte[] ExtraData { get; set; } = Array.Empty<byte>(); - public UInt256 BaseFeePerGas { get; set; } - - [JsonProperty(NullValueHandling = NullValueHandling.Include)] - public Keccak? BlockHash { get; set; } = null!; - - /// <summary> - /// Array of transaction objects, each object is a byte list (DATA) representing TransactionType || TransactionPayload or LegacyTransaction as defined in EIP-2718 - /// </summary> - public byte[][] Transactions { get; set; } = Array.Empty<byte[]>(); - - public override string ToString() => BlockHash is null ? $"{BlockNumber} null" : $"{BlockNumber} ({BlockHash})"; - - public void SetTransactions(params Transaction[] transactions) - { - Transactions = new byte[transactions.Length][]; - for (int i = 0; i < Transactions.Length; i++) - { - Transactions[i] = Rlp.Encode(transactions[i], RlpBehaviors.SkipTypedWrapping).Bytes; - } - } - - public Transaction[] GetTransactions() - { - Transaction[] transactions = new Transaction[Transactions.Length]; - for (int i = 0; i < Transactions.Length; i++) - { - transactions[i] = Rlp.Decode<Transaction>(Transactions[i], RlpBehaviors.SkipTypedWrapping); - } - - return transactions; - } - } -} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/ForkchoiceStateV1.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/ForkchoiceStateV1.cs deleted file mode 100644 index 63c00cc7155..00000000000 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/V1/ForkchoiceStateV1.cs +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Core.Crypto; - -namespace Nethermind.Merge.Plugin.Data.V1 -{ - /// <summary> - /// Arguments to engine_ForkChoiceUpdate - /// - /// <seealso cref="https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#forkchoicestatev1"/> - /// </summary> - public class ForkchoiceStateV1 - { - public ForkchoiceStateV1(Keccak headBlockHash, Keccak finalizedBlockHash, Keccak safeBlockHash) - { - HeadBlockHash = headBlockHash; - FinalizedBlockHash = finalizedBlockHash; - SafeBlockHash = safeBlockHash; - } - - /// <summary> - /// Hash of the head of the canonical chain. - /// </summary> - public Keccak HeadBlockHash { get; set; } - - /// <summary> - /// Safe block hash of the canonical chain under certain synchrony and honesty assumptions. This value MUST be either equal to or an ancestor of headBlockHash. - /// </summary> - /// <remarks>Can be <see cref="Keccak.Zero"/> when transition block is not finalized yet.</remarks> - public Keccak SafeBlockHash { get; set; } - - /// <summary> - /// Hash of the most recent finalized block - /// </summary> - /// <remarks>Can be <see cref="Keccak.Zero"/> when transition block is not finalized yet.</remarks> - public Keccak FinalizedBlockHash { get; set; } - - public override string ToString() - { - return $"ForkchoiceState: ({nameof(HeadBlockHash)}: {HeadBlockHash}, {nameof(SafeBlockHash)}: {SafeBlockHash}, {nameof(FinalizedBlockHash)}: {FinalizedBlockHash})"; - } - } -} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/V2/GetPayloadV2Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/V2/GetPayloadV2Result.cs deleted file mode 100644 index fb9eeb84b25..00000000000 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/V2/GetPayloadV2Result.cs +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Core; -using Nethermind.Int256; -using Nethermind.Merge.Plugin.Data.V1; - -namespace Nethermind.Merge.Plugin.Data.V2; - -public class GetPayloadV2Result -{ - public ExecutionPayloadV1 ExecutionPayloadV1; - public UInt256 BlockValue; - - public GetPayloadV2Result(Block block, UInt256 blockFees) - { - ExecutionPayloadV1 = new(block); - BlockValue = blockFees; - } - - public override string ToString() => $"ExecutionPayloadV1: {ExecutionPayloadV1}, Fees: {BlockValue}"; -} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs index c4d0312c1d1..6d722c511be 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs @@ -10,32 +10,29 @@ using Nethermind.JsonRpc; using Nethermind.Logging; using Nethermind.Merge.Plugin.Data; -using Nethermind.Merge.Plugin.Data.V1; -using Nethermind.Merge.Plugin.Data.V2; using Nethermind.Merge.Plugin.Handlers; namespace Nethermind.Merge.Plugin { public class EngineRpcModule : IEngineRpcModule { - private readonly IAsyncHandler<byte[], ExecutionPayloadV1?> _getPayloadHandlerV1; + private readonly IAsyncHandler<byte[], ExecutionPayload?> _getPayloadHandlerV1; private readonly IAsyncHandler<byte[], GetPayloadV2Result?> _getPayloadHandlerV2; - private readonly IAsyncHandler<ExecutionPayloadV1, PayloadStatusV1> _newPayloadV1Handler; - private readonly IForkchoiceUpdatedV1Handler _forkchoiceUpdatedV1Handler; + private readonly IAsyncHandler<ExecutionPayload, PayloadStatusV1> _newPayloadV1Handler; + private readonly IForkchoiceUpdatedHandler _forkchoiceUpdatedV1Handler; private readonly IHandler<ExecutionStatusResult> _executionStatusHandler; private readonly IAsyncHandler<Keccak[], ExecutionPayloadBodyV1Result?[]> _executionGetPayloadBodiesByHashV1Handler; private readonly IGetPayloadBodiesByRangeV1Handler _executionGetPayloadBodiesByRangeV1Handler; private readonly IHandler<TransitionConfigurationV1, TransitionConfigurationV1> _transitionConfigurationHandler; - private readonly SemaphoreSlim _locker = new(1, 1); private readonly TimeSpan _timeout = TimeSpan.FromSeconds(8); private readonly ILogger _logger; public EngineRpcModule( - IAsyncHandler<byte[], ExecutionPayloadV1?> getPayloadHandlerV1, + IAsyncHandler<byte[], ExecutionPayload?> getPayloadHandlerV1, IAsyncHandler<byte[], GetPayloadV2Result?> getPayloadHandlerV2, - IAsyncHandler<ExecutionPayloadV1, PayloadStatusV1> newPayloadV1Handler, - IForkchoiceUpdatedV1Handler forkchoiceUpdatedV1Handler, + IAsyncHandler<ExecutionPayload, PayloadStatusV1> newPayloadV1Handler, + IForkchoiceUpdatedHandler forkchoiceUpdatedV1Handler, IHandler<ExecutionStatusResult> executionStatusHandler, IAsyncHandler<Keccak[], ExecutionPayloadBodyV1Result?[]> executionGetPayloadBodiesByHashV1Handler, IGetPayloadBodiesByRangeV1Handler executionGetPayloadBodiesByRangeV1Handler, @@ -53,70 +50,99 @@ public EngineRpcModule( _logger = logManager.GetClassLogger(); } - public ResultWrapper<ExecutionStatusResult> engine_executionStatus() - { - return _executionStatusHandler.Handle(); - } + public ResultWrapper<ExecutionStatusResult> engine_executionStatus() => _executionStatusHandler.Handle(); + + public Task<ResultWrapper<ExecutionPayload?>> engine_getPayloadV1(byte[] payloadId) => + _getPayloadHandlerV1.HandleAsync(payloadId); + + public async Task<ResultWrapper<GetPayloadV2Result?>> engine_getPayloadV2(byte[] payloadId) => + await _getPayloadHandlerV2.HandleAsync(payloadId); - public async Task<ResultWrapper<ExecutionPayloadV1?>> engine_getPayloadV1(byte[] payloadId) + public async Task<ResultWrapper<PayloadStatusV1>> engine_newPayloadV1(ExecutionPayload executionPayload) { - return await (_getPayloadHandlerV1.HandleAsync(payloadId)); + if (executionPayload.Withdrawals != null) + { + var error = $"Withdrawals not supported in {nameof(engine_newPayloadV1)}"; + + if (_logger.IsWarn) _logger.Warn(error); + + return ResultWrapper<PayloadStatusV1>.Fail(error, ErrorCodes.InvalidParams); + } + + return await NewPayload(executionPayload, nameof(engine_newPayloadV1)); } - public async Task<ResultWrapper<GetPayloadV2Result?>> engine_getPayloadV2(byte[] payloadId) + public Task<ResultWrapper<PayloadStatusV1>> engine_newPayloadV2(ExecutionPayload executionPayload) => + NewPayload(executionPayload, nameof(engine_newPayloadV2)); + + public async Task<ResultWrapper<ForkchoiceUpdatedV1Result>> engine_forkchoiceUpdatedV1(ForkchoiceStateV1 forkchoiceState, PayloadAttributes? payloadAttributes = null) { - return await (_getPayloadHandlerV2.HandleAsync(payloadId)); + if (payloadAttributes?.Withdrawals != null) + { + var error = $"Withdrawals not supported in {nameof(engine_forkchoiceUpdatedV1)}"; + + if (_logger.IsWarn) _logger.Warn(error); + + return ResultWrapper<ForkchoiceUpdatedV1Result>.Fail(error, ErrorCodes.InvalidParams); + } + + return await ForkchoiceUpdated(forkchoiceState, payloadAttributes, nameof(engine_forkchoiceUpdatedV1)); } - public async Task<ResultWrapper<PayloadStatusV1>> engine_newPayloadV1(ExecutionPayloadV1 executionPayload) + public Task<ResultWrapper<ForkchoiceUpdatedV1Result>> engine_forkchoiceUpdatedV2(ForkchoiceStateV1 forkchoiceState, PayloadAttributes? payloadAttributes = null) => + ForkchoiceUpdated(forkchoiceState, payloadAttributes, nameof(engine_forkchoiceUpdatedV2)); + + public ResultWrapper<TransitionConfigurationV1> engine_exchangeTransitionConfigurationV1( + TransitionConfigurationV1 beaconTransitionConfiguration) => _transitionConfigurationHandler.Handle(beaconTransitionConfiguration); + + private async Task<ResultWrapper<ForkchoiceUpdatedV1Result>> ForkchoiceUpdated(ForkchoiceStateV1 forkchoiceState, PayloadAttributes? payloadAttributes, string methodName) { if (await _locker.WaitAsync(_timeout)) { Stopwatch watch = Stopwatch.StartNew(); try { - return await _newPayloadV1Handler.HandleAsync(executionPayload); - } - catch (Exception exception) - { - if (_logger.IsError) _logger.Error($"{nameof(engine_newPayloadV1)} threw an unexpected exception. {exception}"); - return ResultWrapper<PayloadStatusV1>.Fail($"{nameof(engine_newPayloadV1)} threw an unexpected exception. {exception}"); + return await _forkchoiceUpdatedV1Handler.Handle(forkchoiceState, payloadAttributes); } finally { watch.Stop(); - Metrics.NewPayloadExecutionTime = watch.ElapsedMilliseconds; + Metrics.ForkchoiceUpdedExecutionTime = watch.ElapsedMilliseconds; _locker.Release(); } } else { - if (_logger.IsWarn) _logger.Warn($"{nameof(engine_newPayloadV1)} timeout."); - return ResultWrapper<PayloadStatusV1>.Fail($"{nameof(engine_newPayloadV1)} timeout.", ErrorCodes.Timeout); + if (_logger.IsWarn) _logger.Warn($"{methodName} timed out"); + return ResultWrapper<ForkchoiceUpdatedV1Result>.Fail("Timed out", ErrorCodes.Timeout); } } - - public async Task<ResultWrapper<ForkchoiceUpdatedV1Result>> engine_forkchoiceUpdatedV1(ForkchoiceStateV1 forkchoiceState, PayloadAttributes? payloadAttributes = null) + private async Task<ResultWrapper<PayloadStatusV1>> NewPayload(ExecutionPayload executionPayload, string methodName) { if (await _locker.WaitAsync(_timeout)) { Stopwatch watch = Stopwatch.StartNew(); try { - return await _forkchoiceUpdatedV1Handler.Handle(forkchoiceState, payloadAttributes); + return await _newPayloadV1Handler.HandleAsync(executionPayload); + } + catch (Exception exception) + { + if (_logger.IsError) _logger.Error($"{methodName} failed: {exception}"); + return ResultWrapper<PayloadStatusV1>.Fail(exception.Message); } finally { watch.Stop(); - Metrics.ForkchoiceUpdedExecutionTime = watch.ElapsedMilliseconds; + Metrics.NewPayloadExecutionTime = watch.ElapsedMilliseconds; _locker.Release(); } } else { - if (_logger.IsWarn) _logger.Warn($"{nameof(engine_forkchoiceUpdatedV1)} timeout."); - return ResultWrapper<ForkchoiceUpdatedV1Result>.Fail($"{nameof(engine_forkchoiceUpdatedV1)} timeout.", ErrorCodes.Timeout); + if (_logger.IsWarn) _logger.Warn($"{methodName} timed out"); + return ResultWrapper<PayloadStatusV1>.Fail("Timed out", ErrorCodes.Timeout); } } @@ -129,11 +155,5 @@ public async Task<ResultWrapper<ForkchoiceUpdatedV1Result>> engine_forkchoiceUpd { return await _executionGetPayloadBodiesByRangeV1Handler.Handle(start, count); } - - public ResultWrapper<TransitionConfigurationV1> engine_exchangeTransitionConfigurationV1( - TransitionConfigurationV1 beaconTransitionConfiguration) - { - return _transitionConfigurationHandler.Handle(beaconTransitionConfiguration); - } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/ExchangeTransitionConfigurationV1Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeTransitionConfigurationV1Handler.cs similarity index 97% rename from src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/ExchangeTransitionConfigurationV1Handler.cs rename to src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeTransitionConfigurationV1Handler.cs index 8275c9d6849..b79ce824abd 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/ExchangeTransitionConfigurationV1Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeTransitionConfigurationV1Handler.cs @@ -7,9 +7,9 @@ using Nethermind.Int256; using Nethermind.JsonRpc; using Nethermind.Logging; -using Nethermind.Merge.Plugin.Data.V1; +using Nethermind.Merge.Plugin.Data; -namespace Nethermind.Merge.Plugin.Handlers.V1; +namespace Nethermind.Merge.Plugin.Handlers; public class ExchangeTransitionConfigurationV1Handler : IHandler<TransitionConfigurationV1, TransitionConfigurationV1> { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs new file mode 100644 index 00000000000..c919f0d5ed7 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs @@ -0,0 +1,402 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Find; +using Nethermind.Consensus; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Producers; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Crypto; +using Nethermind.JsonRpc; +using Nethermind.Logging; +using Nethermind.Merge.Plugin.BlockProduction; +using Nethermind.Merge.Plugin.Data; +using Nethermind.Merge.Plugin.InvalidChainTracker; +using Nethermind.Merge.Plugin.Synchronization; + +namespace Nethermind.Merge.Plugin.Handlers; + +/// <summary> +/// Propagates the change in the fork choice to the execution client. May initiate creating new payload. +/// <see href="https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#engine_forkchoiceupdatedv2">engine_forkchoiceupdatedv2</see>. +/// </summary> +public class ForkchoiceUpdatedHandler : IForkchoiceUpdatedHandler +{ + private readonly IBlockTree _blockTree; + private readonly IManualBlockFinalizationManager _manualBlockFinalizationManager; + private readonly IPoSSwitcher _poSSwitcher; + private readonly IPayloadPreparationService _payloadPreparationService; + private readonly IBlockProcessingQueue _processingQueue; + private readonly IBlockCacheService _blockCacheService; + private readonly IInvalidChainTracker _invalidChainTracker; + private readonly IMergeSyncController _mergeSyncController; + private readonly IBeaconPivot _beaconPivot; + private readonly ILogger _logger; + private readonly IPeerRefresher _peerRefresher; + private readonly ISpecProvider _specProvider; + + public ForkchoiceUpdatedHandler( + IBlockTree blockTree, + IManualBlockFinalizationManager manualBlockFinalizationManager, + IPoSSwitcher poSSwitcher, + IPayloadPreparationService payloadPreparationService, + IBlockProcessingQueue processingQueue, + IBlockCacheService blockCacheService, + IInvalidChainTracker invalidChainTracker, + IMergeSyncController mergeSyncController, + IBeaconPivot beaconPivot, + IPeerRefresher peerRefresher, + ISpecProvider specProvider, + ILogManager logManager) + { + _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); + _manualBlockFinalizationManager = manualBlockFinalizationManager ?? throw new ArgumentNullException(nameof(manualBlockFinalizationManager)); + _poSSwitcher = poSSwitcher ?? throw new ArgumentNullException(nameof(poSSwitcher)); + _payloadPreparationService = payloadPreparationService; + _processingQueue = processingQueue; + _blockCacheService = blockCacheService; + _invalidChainTracker = invalidChainTracker; + _mergeSyncController = mergeSyncController; + _beaconPivot = beaconPivot; + _peerRefresher = peerRefresher; + _specProvider = specProvider; + _logger = logManager.GetClassLogger(); + } + + public Task<ResultWrapper<ForkchoiceUpdatedV1Result>> Handle(ForkchoiceStateV1 forkchoiceState, PayloadAttributes? payloadAttributes) + { + string requestStr = $"{forkchoiceState} {payloadAttributes}"; + if (_logger.IsInfo) _logger.Info($"Received: {requestStr}"); + + if (_invalidChainTracker.IsOnKnownInvalidChain(forkchoiceState.HeadBlockHash, out Keccak? lastValidHash)) + { + if (_logger.IsInfo) _logger.Info($" FCU - Invalid - {requestStr} {forkchoiceState.HeadBlockHash} is known to be a part of an invalid chain."); + return ForkchoiceUpdatedV1Result.Invalid(lastValidHash); + } + + Block? newHeadBlock = GetBlock(forkchoiceState.HeadBlockHash); + if (newHeadBlock is null) // if a head is unknown we are syncing + { + if (_blockCacheService.BlockCache.TryGetValue(forkchoiceState.HeadBlockHash, out Block? block)) + { + StartNewBeaconHeaderSync(forkchoiceState, block, requestStr); + } + else if (_logger.IsInfo) + { + _logger.Info($"Syncing... Unknown forkchoiceState head hash... Request: {requestStr}."); + } + + return ForkchoiceUpdatedV1Result.Syncing; + } + + BlockInfo? blockInfo = _blockTree.GetInfo(newHeadBlock.Number, newHeadBlock.GetOrCalculateHash()).Info; + if (blockInfo is null) + { + if (_logger.IsWarn) { _logger.Warn($"Block info for: {requestStr} wasn't found."); } + return ForkchoiceUpdatedV1Result.Syncing; + } + if (!blockInfo.WasProcessed) + { + BlockHeader? blockParent = _blockTree.FindHeader(newHeadBlock.ParentHash!); + if (blockParent is null) + { + if (_logger.IsInfo) + _logger.Info($"Parent of block {newHeadBlock} not available. Starting new beacon header. sync."); + + StartNewBeaconHeaderSync(forkchoiceState, newHeadBlock!, requestStr); + + return ForkchoiceUpdatedV1Result.Syncing; + } + + if (_beaconPivot.ShouldForceStartNewSync) + { + if (_logger.IsInfo) + _logger.Info($"Force starting new sync."); + + StartNewBeaconHeaderSync(forkchoiceState, newHeadBlock!, requestStr); + + return ForkchoiceUpdatedV1Result.Syncing; + } + + if (!blockInfo.IsBeaconMainChain && blockInfo.IsBeaconInfo) + ReorgBeaconChainDuringSync(newHeadBlock!, blockInfo); + + int processingQueueCount = _processingQueue.Count; + if (processingQueueCount == 0) + { + _peerRefresher.RefreshPeers(newHeadBlock!.Hash!, newHeadBlock.ParentHash!, forkchoiceState.FinalizedBlockHash); + _blockCacheService.FinalizedHash = forkchoiceState.FinalizedBlockHash; + _mergeSyncController.StopBeaconModeControl(); + + if (_logger.IsInfo) { _logger.Info($"Syncing beacon headers... Request: {requestStr}."); } + } + else + { + if (_logger.IsInfo) { _logger.Info($"Processing {_processingQueue.Count} blocks... Request: {requestStr}."); } + } + + _beaconPivot.ProcessDestination ??= newHeadBlock!.Header; + return ForkchoiceUpdatedV1Result.Syncing; + } + + if (_logger.IsInfo) _logger.Info($"FCU - block {newHeadBlock} was processed."); + + BlockHeader? finalizedHeader = ValidateBlockHash(forkchoiceState.FinalizedBlockHash, out string? finalizationErrorMsg); + if (finalizationErrorMsg is not null) + { + if (_logger.IsWarn) _logger.Warn($"Invalid finalized block hash {finalizationErrorMsg}. Request: {requestStr}."); + return ForkchoiceUpdatedV1Result.Error(finalizationErrorMsg, MergeErrorCodes.InvalidForkchoiceState); + } + + ValidateBlockHash(forkchoiceState.SafeBlockHash, out string? safeBlockErrorMsg); + if (safeBlockErrorMsg is not null) + { + if (_logger.IsWarn) _logger.Warn($"Invalid safe block hash {finalizationErrorMsg}. Request: {requestStr}."); + return ForkchoiceUpdatedV1Result.Error(safeBlockErrorMsg, MergeErrorCodes.InvalidForkchoiceState); + } + + if ((newHeadBlock.TotalDifficulty ?? 0) != 0 && (_poSSwitcher.MisconfiguredTerminalTotalDifficulty() || _poSSwitcher.BlockBeforeTerminalTotalDifficulty(newHeadBlock.Header))) + { + if (_logger.IsWarn) _logger.Warn($"Invalid terminal block. Nethermind TTD {_poSSwitcher.TerminalTotalDifficulty}, NewHeadBlock TD: {newHeadBlock.Header.TotalDifficulty}. Request: {requestStr}."); + + // https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#specification + // {status: INVALID, latestValidHash: 0x0000000000000000000000000000000000000000000000000000000000000000, validationError: errorMessage | null} if terminal block conditions are not satisfied + return ForkchoiceUpdatedV1Result.Invalid(Keccak.Zero); + } + + Block[]? blocks = EnsureNewHead(newHeadBlock, out string? setHeadErrorMsg); + if (setHeadErrorMsg is not null) + { + if (_logger.IsWarn) _logger.Warn($"Invalid new head block {setHeadErrorMsg}. Request: {requestStr}."); + return ForkchoiceUpdatedV1Result.Error(setHeadErrorMsg, ErrorCodes.InvalidParams); + } + + if (_blockTree.IsOnMainChainBehindHead(newHeadBlock)) + { + if (_logger.IsInfo) _logger.Info($"Valid. ForkchoiceUpdated ignored - already in canonical chain. Request: {requestStr}."); + return ForkchoiceUpdatedV1Result.Valid(null, forkchoiceState.HeadBlockHash); + } + + EnsureTerminalBlock(forkchoiceState, blocks); + + bool newHeadTheSameAsCurrentHead = _blockTree.Head!.Hash == newHeadBlock.Hash; + bool shouldUpdateHead = !newHeadTheSameAsCurrentHead && blocks is not null; + if (shouldUpdateHead) + { + _blockTree.UpdateMainChain(blocks!, true, true); + } + + if (IsInconsistent(forkchoiceState.FinalizedBlockHash)) + { + string errorMsg = $"Inconsistent forkchoiceState - finalized block hash. Request: {requestStr}"; + if (_logger.IsWarn) _logger.Warn(errorMsg); + return ForkchoiceUpdatedV1Result.Error(errorMsg, MergeErrorCodes.InvalidForkchoiceState); + } + + if (IsInconsistent(forkchoiceState.SafeBlockHash)) + { + string errorMsg = $"Inconsistent forkchoiceState - safe block hash. Request: {requestStr}"; + if (_logger.IsWarn) _logger.Warn(errorMsg); + return ForkchoiceUpdatedV1Result.Error(errorMsg, MergeErrorCodes.InvalidForkchoiceState); + } + + bool nonZeroFinalizedBlockHash = forkchoiceState.FinalizedBlockHash != Keccak.Zero; + // bool nonZeroSafeBlockHash = forkchoiceState.SafeBlockHash != Keccak.Zero; + if (nonZeroFinalizedBlockHash) + { + _manualBlockFinalizationManager.MarkFinalized(newHeadBlock.Header, finalizedHeader!); + } + + if (shouldUpdateHead) + { + _poSSwitcher.ForkchoiceUpdated(newHeadBlock.Header, forkchoiceState.FinalizedBlockHash); + if (_logger.IsInfo) _logger.Info($"Block {forkchoiceState.HeadBlockHash} was set as head."); + } + + string? payloadId = null; + if (payloadAttributes is not null) + { + if (newHeadBlock.Timestamp >= payloadAttributes.Timestamp) + { + var error = $"Payload timestamp {payloadAttributes.Timestamp} must be greater than block timestamp {newHeadBlock.Timestamp}."; + + if (_logger.IsWarn) _logger.Warn($"Invalid payload attributes: {error}"); + + return ForkchoiceUpdatedV1Result.Error(error, MergeErrorCodes.InvalidPayloadAttributes); + } + + var spec = _specProvider.GetSpec(newHeadBlock.Number + 1, payloadAttributes.Timestamp); + + if (spec.WithdrawalsEnabled && payloadAttributes.Withdrawals is null) + { + var error = "Withdrawals cannot be null when EIP-4895 activated."; + + if (_logger.IsInfo) _logger.Warn($"Invalid payload attributes: {error}"); + + return ForkchoiceUpdatedV1Result.Error(error, MergeErrorCodes.InvalidPayloadAttributes); + } + + if (!spec.WithdrawalsEnabled && payloadAttributes.Withdrawals is not null) + { + var error = "Withdrawals must be null when EIP-4895 not activated."; + + if (_logger.IsInfo) _logger.Warn($"Invalid payload attributes: {error}"); + + return ForkchoiceUpdatedV1Result.Error(error, MergeErrorCodes.InvalidPayloadAttributes); + } + + payloadAttributes.GasLimit = null; + payloadId = _payloadPreparationService.StartPreparingPayload(newHeadBlock.Header, payloadAttributes); + } + + if (_logger.IsInfo) _logger.Info($"Valid. Request: {requestStr}."); + + _blockTree.ForkChoiceUpdated(forkchoiceState.FinalizedBlockHash, forkchoiceState.SafeBlockHash); + return ForkchoiceUpdatedV1Result.Valid(payloadId, forkchoiceState.HeadBlockHash); + } + + private void StartNewBeaconHeaderSync(ForkchoiceStateV1 forkchoiceState, Block block, string requestStr) + { + _mergeSyncController.InitBeaconHeaderSync(block.Header); + _beaconPivot.ProcessDestination = block.Header; + _peerRefresher.RefreshPeers(block.Hash!, block.ParentHash!, forkchoiceState.FinalizedBlockHash); + _blockCacheService.FinalizedHash = forkchoiceState.FinalizedBlockHash; + + if (_logger.IsInfo) _logger.Info($"Start a new sync process... Request: {requestStr}."); + } + + // This method will detect reorg in terminal PoW block + private void EnsureTerminalBlock(ForkchoiceStateV1 forkchoiceState, Block[]? blocks) + { + // we can reorg terminal block only if we haven't finalized PoS yet and we're not finalizing PoS now + // https://github.com/ethereum/EIPs/blob/d896145678bd65d3eafd8749690c1b5228875c39/EIPS/eip-3675.md#ability-to-jump-between-terminal-pow-blocks + bool notFinalizingPoS = forkchoiceState.FinalizedBlockHash == Keccak.Zero; + bool notFinalizedPoS = _manualBlockFinalizationManager.LastFinalizedHash == Keccak.Zero; + if (notFinalizingPoS && notFinalizedPoS && blocks is not null) + { + for (int i = 0; i < blocks.Length; ++i) + { + if (blocks[i].Header.Difficulty != 0 && blocks[i].TotalDifficulty >= _poSSwitcher.TerminalTotalDifficulty) + { + if (_poSSwitcher.TryUpdateTerminalBlock(blocks[i].Header)) + { + if (_logger.IsInfo) _logger.Info($"Terminal block {blocks[i].Header} updated during the forkchoice"); + } + + break; + } + } + } + } + + private bool IsInconsistent(Keccak blockHash) => blockHash != Keccak.Zero && !_blockTree.IsMainChain(blockHash); + + private Block? GetBlock(Keccak headBlockHash) + { + Block? block = _blockTree.FindBlock(headBlockHash, BlockTreeLookupOptions.DoNotCreateLevelIfMissing); + if (block is null) + { + if (_logger.IsInfo) _logger.Info($"Syncing... Block {headBlockHash} not found."); + } + + return block; + } + + private Block[]? EnsureNewHead(Block newHeadBlock, out string? errorMessage) + { + errorMessage = null; + if (_blockTree.Head!.Hash == newHeadBlock.Hash) + { + return null; + } + + if (!TryGetBranch(newHeadBlock, out Block[] branchOfBlocks)) + { + errorMessage = $"Block's {newHeadBlock} main chain predecessor cannot be found and it will not be set as head."; + if (_logger.IsWarn) _logger.Warn(errorMessage); + } + + return branchOfBlocks; + } + + private BlockHeader? ValidateBlockHash(Keccak blockHash, out string? errorMessage, bool skipZeroHash = true) + { + errorMessage = null; + if (skipZeroHash && blockHash == Keccak.Zero) + { + return null; + } + + BlockHeader? blockHeader = _blockTree.FindHeader(blockHash, BlockTreeLookupOptions.DoNotCreateLevelIfMissing); + if (blockHeader is null) + { + errorMessage = $"Block {blockHash} not found."; + if (_logger.IsWarn) _logger.Warn(errorMessage); + } + return blockHeader; + } + + + private bool TryGetBranch(Block newHeadBlock, out Block[] blocks) + { + List<Block> blocksList = new() { newHeadBlock }; + Block? predecessor = newHeadBlock; + + while (true) + { + predecessor = _blockTree.FindParent(predecessor, BlockTreeLookupOptions.DoNotCreateLevelIfMissing); + if (predecessor is null) + { + blocks = Array.Empty<Block>(); + return false; + } + if (_blockTree.IsMainChain(predecessor.Header)) break; + blocksList.Add(predecessor); + } + + blocksList.Reverse(); + blocks = blocksList.ToArray(); + return true; + } + + private void ReorgBeaconChainDuringSync(Block newHeadBlock, BlockInfo newHeadBlockInfo) + { + if (_logger.IsInfo) _logger.Info("BeaconChain reorged during the sync or cache rebuilt"); + BlockInfo[] beaconMainChainBranch = GetBeaconChainBranch(newHeadBlock, newHeadBlockInfo); + _blockTree.UpdateBeaconMainChain(beaconMainChainBranch, Math.Max(_beaconPivot.ProcessDestination?.Number ?? 0, newHeadBlock.Number)); + _beaconPivot.ProcessDestination = newHeadBlock.Header; + } + + private BlockInfo[] GetBeaconChainBranch(Block newHeadBlock, BlockInfo newHeadBlockInfo) + { + newHeadBlockInfo.BlockNumber = newHeadBlock.Number; + List<BlockInfo> blocksList = new() { newHeadBlockInfo }; + Block? predecessor = newHeadBlock; + + while (true) + { + predecessor = _blockTree.FindParent(predecessor, BlockTreeLookupOptions.TotalDifficultyNotNeeded); + + if (predecessor is null) + { + break; + } + BlockInfo? predecessorInfo = _blockTree.GetInfo(predecessor.Number, predecessor.GetOrCalculateHash()).Info; + if (predecessorInfo is null) break; + predecessorInfo.BlockNumber = predecessor.Number; + if (predecessorInfo.IsBeaconMainChain || !predecessorInfo.IsBeaconInfo) break; + if (_logger.IsInfo) _logger.Info($"Reorged to beacon block ({predecessorInfo.BlockNumber}) {predecessorInfo.BlockHash} or cache rebuilt"); + blocksList.Add(predecessorInfo); + } + + blocksList.Reverse(); + + return blocksList.ToArray(); + } +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByHashV1Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByHashV1Handler.cs new file mode 100644 index 00000000000..4bf3aa851d2 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByHashV1Handler.cs @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading.Tasks; +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.JsonRpc; +using Nethermind.Logging; +using Nethermind.Merge.Plugin.Data; + +namespace Nethermind.Merge.Plugin.Handlers; + +public class GetPayloadBodiesByHashV1Handler : IAsyncHandler<Keccak[], ExecutionPayloadBodyV1Result?[]> +{ + private readonly IBlockTree _blockTree; + private readonly ILogger _logger; + + public GetPayloadBodiesByHashV1Handler(IBlockTree blockTree, ILogManager logManager) + { + _blockTree = blockTree; + _logger = logManager.GetClassLogger(); + } + + public Task<ResultWrapper<ExecutionPayloadBodyV1Result?[]>> HandleAsync(Keccak[] blockHashes) + { + var payloadBodies = new ExecutionPayloadBodyV1Result?[blockHashes.Length]; + for (int i = 0; i < blockHashes.Length; ++i) + { + Block? block = _blockTree.FindBlock(blockHashes[i]); + + payloadBodies[i] = block is not null ? new ExecutionPayloadBodyV1Result(block.Transactions) : null; + } + + return Task.FromResult(ResultWrapper<ExecutionPayloadBodyV1Result?[]>.Success(payloadBodies)); + } +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/GetPayloadBodiesByRangeV1Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByRangeV1Handler.cs similarity index 92% rename from src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/GetPayloadBodiesByRangeV1Handler.cs rename to src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByRangeV1Handler.cs index 706191897fe..1d8de6d1cd2 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/GetPayloadBodiesByRangeV1Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadBodiesByRangeV1Handler.cs @@ -1,15 +1,14 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Collections.Generic; using System.Threading.Tasks; using Nethermind.Blockchain; using Nethermind.Core; using Nethermind.JsonRpc; using Nethermind.Logging; -using Nethermind.Merge.Plugin.Data.V1; +using Nethermind.Merge.Plugin.Data; -namespace Nethermind.Merge.Plugin.Handlers.V1; +namespace Nethermind.Merge.Plugin.Handlers; public class GetPayloadBodiesByRangeV1Handler : IGetPayloadBodiesByRangeV1Handler { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/GetPayloadV1Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs similarity index 86% rename from src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/GetPayloadV1Handler.cs rename to src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs index e5861c6fe93..0c48d674eff 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/GetPayloadV1Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV1Handler.cs @@ -8,9 +8,8 @@ using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; using Nethermind.Merge.Plugin.Data; -using Nethermind.Merge.Plugin.Data.V1; -namespace Nethermind.Merge.Plugin.Handlers.V1 +namespace Nethermind.Merge.Plugin.Handlers { /// <summary> /// engine_getPayloadV1 @@ -25,7 +24,7 @@ namespace Nethermind.Merge.Plugin.Handlers.V1 /// 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. /// </remarks> - public class GetPayloadV1Handler : IAsyncHandler<byte[], ExecutionPayloadV1?> + public class GetPayloadV1Handler : IAsyncHandler<byte[], ExecutionPayload?> { private readonly IPayloadPreparationService _payloadPreparationService; private readonly ILogger _logger; @@ -36,7 +35,7 @@ public GetPayloadV1Handler(IPayloadPreparationService payloadPreparationService, _logger = logManager.GetClassLogger(); } - public async Task<ResultWrapper<ExecutionPayloadV1?>> HandleAsync(byte[] payloadId) + public async Task<ResultWrapper<ExecutionPayload?>> HandleAsync(byte[] payloadId) { string payloadStr = payloadId.ToHexString(true); Block? block = (await _payloadPreparationService.GetPayload(payloadStr))?.CurrentBestBlock; @@ -45,14 +44,14 @@ public GetPayloadV1Handler(IPayloadPreparationService payloadPreparationService, { // 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<ExecutionPayloadV1?>.Fail("unknown payload", MergeErrorCodes.UnknownPayload); + return ResultWrapper<ExecutionPayload?>.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<ExecutionPayloadV1?>.Success(new ExecutionPayloadV1(block)); + return ResultWrapper<ExecutionPayload?>.Success(new ExecutionPayload(block)); } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V2/GetPayloadV2Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs similarity index 88% rename from src/Nethermind/Nethermind.Merge.Plugin/Handlers/V2/GetPayloadV2Handler.cs rename to src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs index 7127f5deb82..d8c4d2ecb89 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V2/GetPayloadV2Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetPayloadV2Handler.cs @@ -7,10 +7,13 @@ using Nethermind.JsonRpc; using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; -using Nethermind.Merge.Plugin.Data.V2; +using Nethermind.Merge.Plugin.Data; -namespace Nethermind.Merge.Plugin.Handlers.V2 +namespace Nethermind.Merge.Plugin.Handlers { + /// <summary> + /// <see href="https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#engine_getpayloadv2">engine_getpayloadv22</see>. + /// </summary> public class GetPayloadV2Handler : IAsyncHandler<byte[], GetPayloadV2Result?> { private readonly IPayloadPreparationService _payloadPreparationService; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IForkchoiceUpdatedHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IForkchoiceUpdatedHandler.cs new file mode 100644 index 00000000000..b390eb798af --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IForkchoiceUpdatedHandler.cs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading.Tasks; +using Nethermind.Consensus.Producers; +using Nethermind.JsonRpc; +using Nethermind.Merge.Plugin.Data; + +namespace Nethermind.Merge.Plugin.Handlers; + +public interface IForkchoiceUpdatedHandler +{ + Task<ResultWrapper<ForkchoiceUpdatedV1Result>> Handle(ForkchoiceStateV1 forkchoiceState, PayloadAttributes? payloadAttributes); +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IForkchoiceUpdatedV1Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IForkchoiceUpdatedV1Handler.cs deleted file mode 100644 index 0c473e867ed..00000000000 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IForkchoiceUpdatedV1Handler.cs +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading.Tasks; -using Nethermind.Consensus.Producers; -using Nethermind.JsonRpc; -using Nethermind.Merge.Plugin.Data.V1; - -namespace Nethermind.Merge.Plugin.Handlers -{ - public interface IForkchoiceUpdatedV1Handler - { - Task<ResultWrapper<ForkchoiceUpdatedV1Result>> Handle(ForkchoiceStateV1 forkchoiceState, PayloadAttributes? payloadAttributes); - } -} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IGetPayloadBodiesByRangeV1Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IGetPayloadBodiesByRangeV1Handler.cs index eb01bae1860..e3655e04347 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IGetPayloadBodiesByRangeV1Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/IGetPayloadBodiesByRangeV1Handler.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; using Nethermind.JsonRpc; -using Nethermind.Merge.Plugin.Data.V1; +using Nethermind.Merge.Plugin.Data; namespace Nethermind.Merge.Plugin.Handlers; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs new file mode 100644 index 00000000000..7bc9a353774 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs @@ -0,0 +1,460 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Nethermind.Api; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Consensus; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Validators; +using Nethermind.Core; +using Nethermind.Core.Caching; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Crypto; +using Nethermind.Int256; +using Nethermind.JsonRpc; +using Nethermind.Logging; +using Nethermind.Merge.Plugin.Data; +using Nethermind.Merge.Plugin.InvalidChainTracker; +using Nethermind.Merge.Plugin.Synchronization; +using Nethermind.Synchronization; + +namespace Nethermind.Merge.Plugin.Handlers; + +/// <summary> +/// Defines a class that handles the execution payload according to the +/// <see href="https://eips.ethereum.org/EIPS/eip-3675">EIP-3675</see>. +/// <see href="https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#engine_newpayloadv2">engine_newpayloadv2</see>. +/// </summary> +public class NewPayloadHandler : IAsyncHandler<ExecutionPayload, PayloadStatusV1> +{ + private readonly IBlockValidator _blockValidator; + private readonly IBlockTree _blockTree; + private readonly ISyncConfig _syncConfig; + private readonly IPoSSwitcher _poSSwitcher; + private readonly IBeaconSyncStrategy _beaconSyncStrategy; + private readonly IBeaconPivot _beaconPivot; + private readonly IBlockCacheService _blockCacheService; + private readonly IBlockProcessingQueue _processingQueue; + private readonly IMergeSyncController _mergeSyncController; + private readonly ISpecProvider _specProvider; + private readonly IInvalidChainTracker _invalidChainTracker; + private readonly ILogger _logger; + private readonly LruCache<Keccak, bool>? _latestBlocks; + private readonly ProcessingOptions _defaultProcessingOptions; + private readonly TimeSpan _timeout; + + public NewPayloadHandler( + IBlockValidator blockValidator, + IBlockTree blockTree, + IInitConfig initConfig, + ISyncConfig syncConfig, + IPoSSwitcher poSSwitcher, + IBeaconSyncStrategy beaconSyncStrategy, + IBeaconPivot beaconPivot, + IBlockCacheService blockCacheService, + IBlockProcessingQueue processingQueue, + IInvalidChainTracker invalidChainTracker, + IMergeSyncController mergeSyncController, + ISpecProvider specProvider, + ILogManager logManager, + TimeSpan? timeout = null, + int cacheSize = 50) + { + _blockValidator = blockValidator ?? throw new ArgumentNullException(nameof(blockValidator)); + _blockTree = blockTree; + _syncConfig = syncConfig; + _poSSwitcher = poSSwitcher; + _beaconSyncStrategy = beaconSyncStrategy; + _beaconPivot = beaconPivot; + _blockCacheService = blockCacheService; + _processingQueue = processingQueue; + _invalidChainTracker = invalidChainTracker; + _mergeSyncController = mergeSyncController; + _specProvider = specProvider; + _logger = logManager.GetClassLogger(); + _defaultProcessingOptions = initConfig.StoreReceipts ? ProcessingOptions.EthereumMerge | ProcessingOptions.StoreReceipts : ProcessingOptions.EthereumMerge; + _timeout = timeout ?? TimeSpan.FromSeconds(7); + if (cacheSize > 0) + _latestBlocks = new LruCache<Keccak, bool>(cacheSize, 0, "LatestBlocks"); + } + + /// <summary> + /// Processes the execution payload and returns the <see cref="PayloadStatusV1"/> + /// and the hash of the last valid block. + /// </summary> + /// <param name="request">The execution payload to process.</param> + /// <returns></returns> + public async Task<ResultWrapper<PayloadStatusV1>> HandleAsync(ExecutionPayload request) + { + string requestStr = $"a new payload: {request}"; + if (_logger.IsInfo) { _logger.Info($"Received {requestStr}"); } + + if (!request.TryGetBlock(out Block? block, _poSSwitcher.FinalTotalDifficulty)) + { + if (_logger.IsWarn) _logger.Warn($"Invalid block. Result of {requestStr}."); + return NewPayloadV1Result.Invalid(null, $"Block {request} could not be parsed as a block"); + } + + if (!HeaderValidator.ValidateHash(block!.Header)) + { + if (_logger.IsWarn) _logger.Warn($"InvalidBlockHash. Result of {requestStr}."); + return NewPayloadV1Result.InvalidBlockHash; + } + + _invalidChainTracker.SetChildParent(block.Hash!, block.ParentHash!); + if (_invalidChainTracker.IsOnKnownInvalidChain(block.Hash!, out Keccak? lastValidHash)) + { + if (_logger.IsInfo) _logger.Info($"Invalid - block {request} is known to be a part of an invalid chain."); + return NewPayloadV1Result.Invalid(lastValidHash, $"Block {request} is known to be a part of an invalid chain."); + } + + if (!_blockValidator.ValidateWithdrawals(block, out var error)) + { + if (_logger.IsWarn) _logger.Warn($"Invalid: {error}"); + + return NewPayloadV1Result.Invalid(lastValidHash ?? block.ParentHash, error); + } + + if (block.Header.Number <= _syncConfig.PivotNumberParsed) + { + if (_logger.IsInfo) _logger.Info($"Pre-pivot block, ignored and returned Syncing. Result of {requestStr}."); + return NewPayloadV1Result.Syncing; + } + + block.Header.TotalDifficulty = _poSSwitcher.FinalTotalDifficulty; + + BlockHeader? parentHeader = _blockTree.FindHeader(block.ParentHash!, BlockTreeLookupOptions.DoNotCreateLevelIfMissing); + if (parentHeader is null) + { + // possible that headers sync finished before this was called, so blocks in cache weren't inserted + if (!_beaconSyncStrategy.IsBeaconSyncFinished(parentHeader)) + { + bool inserted = TryInsertDanglingBlock(block); + if (_logger.IsInfo) _logger.Info(inserted ? $"BeaconSync not finished - block {block} inserted" : $"BeaconSync not finished - block {block} added to cache."); + return NewPayloadV1Result.Syncing; + } + + if (_logger.IsInfo) _logger.Info($"Insert block into cache without parent {block}"); + _blockCacheService.BlockCache.TryAdd(block.Hash!, block); + return NewPayloadV1Result.Syncing; + } + + // we need to check if the head is greater than block.Number. In fast sync we could return Valid to CL without this if + if (_blockTree.IsOnMainChainBehindOrEqualHead(block)) + { + if (_logger.IsInfo) _logger.Info($"Valid... A new payload ignored. Block {block.ToString(Block.Format.FullHashAndNumber)} found in main chain."); + return NewPayloadV1Result.Valid(block.Hash); + } + + if (!ShouldProcessBlock(block, parentHeader, out ProcessingOptions processingOptions)) // we shouldn't process block + { + if (!_blockValidator.ValidateSuggestedBlock(block)) + { + if (_logger.IsInfo) _logger.Info($"Rejecting invalid block received during the sync, block: {block}"); + return NewPayloadV1Result.Invalid(null); + } + + BlockTreeInsertHeaderOptions insertHeaderOptions = BlockTreeInsertHeaderOptions.BeaconBlockInsert; + + if (block.Number <= Math.Max(_blockTree.BestKnownNumber, _blockTree.BestKnownBeaconNumber) && _blockTree.FindBlock(block.GetOrCalculateHash(), BlockTreeLookupOptions.TotalDifficultyNotNeeded) != null) + { + if (_logger.IsInfo) _logger.Info($"Syncing... Parent wasn't processed. Block already known in blockTree {block}."); + return NewPayloadV1Result.Syncing; + } + + if (_beaconPivot.ProcessDestination != null && _beaconPivot.ProcessDestination.Hash == block.ParentHash) + { + insertHeaderOptions |= BlockTreeInsertHeaderOptions.MoveToBeaconMainChain; // we're extending our beacon canonical chain + _beaconPivot.ProcessDestination = block.Header; + } + + _beaconPivot.EnsurePivot(block.Header, true); + _blockTree.Insert(block, BlockTreeInsertBlockOptions.SaveHeader | BlockTreeInsertBlockOptions.SkipCanAcceptNewBlocks, insertHeaderOptions); + + if (_logger.IsInfo) _logger.Info($"Syncing... Parent wasn't processed. Inserting block {block}."); + return NewPayloadV1Result.Syncing; + } + + if (_poSSwitcher.MisconfiguredTerminalTotalDifficulty()) + { + if (_logger.IsWarn) _logger.Warn($"Misconfigured terminal total difficulty."); + + return NewPayloadV1Result.Invalid(Keccak.Zero); + } + + if ((block.TotalDifficulty ?? 0) != 0 && _poSSwitcher.BlockBeforeTerminalTotalDifficulty(parentHeader)) + { + if (_logger.IsWarn) _logger.Warn($"Invalid terminal block. Nethermind TTD {_poSSwitcher.TerminalTotalDifficulty}, Parent TD: {parentHeader.TotalDifficulty}. Request: {requestStr}."); + + // {status: INVALID, latestValidHash: 0x0000000000000000000000000000000000000000000000000000000000000000, validationError: errorMessage | null} if terminal block conditions are not satisfied + return NewPayloadV1Result.Invalid(Keccak.Zero); + } + + // Otherwise, we can just process this block and we don't need to do BeaconSync anymore. + _mergeSyncController.StopSyncing(); + + // Try to execute block + (ValidationResult result, string? message) = await ValidateBlockAndProcess(block, parentHeader, processingOptions); + + if (result == ValidationResult.Invalid) + { + if (_logger.IsInfo) _logger.Info($"Invalid block found. Validation message: {message}. Result of {requestStr}."); + _invalidChainTracker.OnInvalidBlock(block.Hash!, block.ParentHash); + return ResultWrapper<PayloadStatusV1>.Success(BuildInvalidPayloadStatusV1(request, message)); + } + + if (result == ValidationResult.Syncing) + { + if (_logger.IsInfo) _logger.Info($"Processing queue wasn't empty added to queue {requestStr}."); + return NewPayloadV1Result.Syncing; + } + + if (_logger.IsInfo) _logger.Info($"Valid. Result of {requestStr}."); + return NewPayloadV1Result.Valid(block.Hash); + } + + /// <summary> + /// Decides if we should process the block or try syncing to it. It also returns what options to process the block with. + /// </summary> + /// <param name="block">Block</param> + /// <param name="parent">Parent header</param> + /// <param name="processingOptions">Options that should be used for processing</param> + /// <returns>Options which should be used for block processing. Null if we shouldn't process the block.</returns> + /// <remarks> + /// We decide to process blocks in two situations: + /// 1. The block parent was already processed. Then we process with ProcessingOptions.EthereumMerge with potentially also StoringReceipts. + /// This contains ProcessingOptions.IgnoreParentNotOnMainChain flag in order not to collect whole branch for processing, but only process this block directly on parent. + /// As parent was processed the state to process on should also be available. + /// + /// 2. If the parent wasn't processed, but it was a PoW block (terminal block) and we are not syncing PoW chain and are in the deep past. + /// In this case we remove ~ProcessingOptions.IgnoreParentNotOnMainChain flag in order to collect whole branch for processing. + /// If we didn't support this edge case then we couldn't process this block and would have to return Syncing, which is not desired during transition. + /// + /// Scenario 2 proved to be quite common on testnets which produced multiple transition blocks. + /// </remarks> + private bool ShouldProcessBlock(Block block, BlockHeader parent, out ProcessingOptions processingOptions) + { + processingOptions = _defaultProcessingOptions; + + BlockInfo? parentBlockInfo = _blockTree.GetInfo(parent.Number, parent.GetOrCalculateHash()).Info; + bool parentProcessed = parentBlockInfo is { WasProcessed: true }; + + // During the transition we can have a case of NP built over a transition block that wasn't processed. + // We want to force process the whole branch then, but not longer than few blocks. + // But we don't want this to trigger when we are in beacon sync. + // The last condition: !parentBlockInfo.IsBeaconInfo will be true for terminal blocks. + // Checking _posSwitcher.IsTerminal might not be the best, because we're loading parentHeader with DoNotCalculateTotalDifficulty option + bool weHaveOnlyFewBlocksToProcess = (_blockTree.Head?.Number ?? 0) + 8 >= block.Number; + bool parentIsPoWBlock = parent.Difficulty != UInt256.Zero; + bool processTerminalBlock = !_poSSwitcher.TransitionFinished // we haven't finished transition + && weHaveOnlyFewBlocksToProcess // we won't try to process too much blocks (if we are behind the transition block and still processing blocks) + && parentBlockInfo is { IsBeaconInfo: false } // we are not in beacon sync + && parentIsPoWBlock; // parent was PoW block -> so it was a transition block + + if (!parentProcessed && processTerminalBlock) // so if parent wasn't processed + { + if (_logger.IsInfo) _logger.Info($"Forced processing block {block}, block TD: {block.TotalDifficulty}, parent: {parent}, parent TD: {parent.TotalDifficulty}"); + + // if parent wasn't processed and we want to force processing terminal block then we need to allow to process whole branch, not just one block + // in all other cases when parent is processed ProcessingOptions.IgnoreParentNotOnMainChain allows us to process just this block ignoring that its not on Head + // this option is part of ProcessingOptions.EthereumMerge option + processingOptions &= ~ProcessingOptions.IgnoreParentNotOnMainChain; + } + + return parentProcessed || processTerminalBlock; + } + + private async Task<(ValidationResult, string?)> ValidateBlockAndProcess(Block block, BlockHeader parent, ProcessingOptions processingOptions) + { + ValidationResult TryCacheResult(ValidationResult result) + { + // notice that it is not correct to add information to the cache + // if we return SYNCING for example, and don't know yet whether + // the block is valid or invalid because we haven't processed it yet + if (result == ValidationResult.Valid || result == ValidationResult.Invalid) + _latestBlocks?.Set(block.GetOrCalculateHash(), result == ValidationResult.Valid); + return result; + } + + (ValidationResult? result, string? validationMessage) = (null, null); + + // If duplicate, reuse results + if (_latestBlocks is not null && _latestBlocks.TryGet(block.Hash!, out bool isValid)) + { + if (!isValid) + { + validationMessage = "Invalid block found in latestBlock cache."; + if (_logger.IsWarn) _logger.Warn(validationMessage); + } + + return (isValid ? ValidationResult.Valid : ValidationResult.Invalid, validationMessage); + } + + // Validate + if (!ValidateWithBlockValidator(block, parent)) + { + return (TryCacheResult(ValidationResult.Invalid), string.Empty); + } + + TaskCompletionSource<ValidationResult?> blockProcessedTaskCompletionSource = new(); + Task<ValidationResult?> blockProcessed = blockProcessedTaskCompletionSource.Task; + + void GetProcessingQueueOnBlockRemoved(object? o, BlockHashEventArgs e) + { + if (e.BlockHash == block.Hash) + { + _processingQueue.BlockRemoved -= GetProcessingQueueOnBlockRemoved; + + if (e.ProcessingResult == ProcessingResult.Exception) + { + BlockchainException? exception = new("Block processing threw exception.", e.Exception); + blockProcessedTaskCompletionSource.SetException(exception); + return; + } + + ValidationResult? validationResult = e.ProcessingResult switch + { + ProcessingResult.Success => ValidationResult.Valid, + ProcessingResult.ProcessingError => ValidationResult.Invalid, + _ => null + }; + + validationMessage = e.ProcessingResult switch + { + ProcessingResult.QueueException => "Block cannot be added to processing queue.", + ProcessingResult.MissingBlock => "Block wasn't found in tree.", + ProcessingResult.ProcessingError => "Block processing failed.", + _ => null + }; + + blockProcessedTaskCompletionSource.TrySetResult(validationResult); + } + } + + _processingQueue.BlockRemoved += GetProcessingQueueOnBlockRemoved; + try + { + Task timeoutTask = Task.Delay(_timeout); + + AddBlockResult addResult = await _blockTree + .SuggestBlockAsync(block, BlockTreeSuggestOptions.ForceDontSetAsMain) + .AsTask().TimeoutOn(timeoutTask); + + result = addResult switch + { + AddBlockResult.InvalidBlock => ValidationResult.Invalid, + // if the block is marked as AlreadyKnown by the block tree then it means it has already + // been suggested. there are three possibilities, either the block hasn't been processed yet, + // the block was processed and returned invalid but this wasn't saved anywhere or the block was + // processed and marked as valid. + // if marked as processed by the blocktree then return VALID, otherwise null so that it's process a few lines below + AddBlockResult.AlreadyKnown => _blockTree.WasProcessed(block.Number, block.Hash!) ? ValidationResult.Valid : null, + _ => null + }; + + validationMessage = addResult switch + { + AddBlockResult.InvalidBlock => "Block couldn't be added to the tree.", + AddBlockResult.AlreadyKnown => "Block was already known in the tree.", + _ => null + }; + + if (!result.HasValue) + { + // we don't know the result of processing the block, either because + // it is the first time we add it to the tree or it's AlreadyKnown in + // the tree but hasn't yet been processed. if it's the second case + // probably the block is already in the processing queue as a result + // of a previous newPayload or the block being discovered during syncing + // but add it to the processing queue just in case. + _processingQueue.Enqueue(block, processingOptions); + result = await blockProcessed.TimeoutOn(timeoutTask); + } + } + catch (TimeoutException) + { + // we timed out while processing the block, result will be null and we will return SYNCING below, no need to do anything + if (_logger.IsDebug) _logger.Debug($"Block {block.ToString(Block.Format.FullHashAndNumber)} timed out when processing. Assume Syncing."); + } + finally + { + _processingQueue.BlockRemoved -= GetProcessingQueueOnBlockRemoved; + } + + return (TryCacheResult(result ?? ValidationResult.Syncing), validationMessage); + } + + private bool ValidateWithBlockValidator(Block block, BlockHeader parent) + { + block.Header.TotalDifficulty ??= parent.TotalDifficulty + block.Difficulty; + block.Header.IsPostMerge = true; // I think we don't need to set it again here. + bool isValid = _blockValidator.ValidateSuggestedBlock(block); + if (!isValid && _logger.IsWarn) _logger.Warn($"Block validator rejected the block {block.ToString(Block.Format.FullHashAndNumber)}."); + return isValid; + } + + private PayloadStatusV1 BuildInvalidPayloadStatusV1(ExecutionPayload request, string? validationMessage) => + new() + { + Status = PayloadStatus.Invalid, + ValidationError = validationMessage, + LatestValidHash = _invalidChainTracker.IsOnKnownInvalidChain(request.BlockHash!, out Keccak? lastValidHash) + ? lastValidHash + : request.ParentHash + }; + + /// Pop blocks from cache up to ancestor on the beacon chain. Which is then inserted into the block tree + /// which I assume will switch the canonical chain. + /// Return false if no ancestor that is part of beacon chain found. + private bool TryInsertDanglingBlock(Block block) + { + BlockTreeInsertHeaderOptions insertHeaderOptions = BlockTreeInsertHeaderOptions.BeaconBlockInsert | BlockTreeInsertHeaderOptions.MoveToBeaconMainChain; + + if (!_blockTree.IsKnownBeaconBlock(block.Number, block.Hash ?? block.CalculateHash())) + { + // last block inserted is parent of current block, part of the same chain + Block? current = block; + Stack<Block> stack = new(); + while (current != null) + { + stack.Push(current); + Keccak currentHash = current.Hash!; + if (currentHash == _beaconPivot.PivotHash || _blockTree.IsKnownBeaconBlock(current.Number, currentHash)) + { + break; + } + + _blockCacheService.BlockCache.TryGetValue(current.ParentHash!, out Block? parentBlock); + current = parentBlock; + } + + if (current is null) + { + // block not part of beacon pivot chain, save in cache + _blockCacheService.BlockCache.TryAdd(block.Hash!, block); + return false; + } + + while (stack.TryPop(out Block? child)) + { + _blockTree.Insert(child, BlockTreeInsertBlockOptions.SaveHeader, insertHeaderOptions); + } + + _beaconPivot.ProcessDestination = block.Header; + } + + return true; + } + + private enum ValidationResult + { + Invalid, + Valid, + Syncing + } +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/ForkchoiceUpdatedV1Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/ForkchoiceUpdatedV1Handler.cs deleted file mode 100644 index 410ed693622..00000000000 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/ForkchoiceUpdatedV1Handler.cs +++ /dev/null @@ -1,384 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Nethermind.Blockchain; -using Nethermind.Blockchain.Find; -using Nethermind.Consensus; -using Nethermind.Consensus.Processing; -using Nethermind.Consensus.Producers; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Crypto; -using Nethermind.JsonRpc; -using Nethermind.Logging; -using Nethermind.Merge.Plugin.BlockProduction; -using Nethermind.Merge.Plugin.Data.V1; -using Nethermind.Merge.Plugin.InvalidChainTracker; -using Nethermind.Merge.Plugin.Synchronization; - -namespace Nethermind.Merge.Plugin.Handlers.V1 -{ - /// <summary> - /// Propagates the change in the fork choice to the execution client. May initiate creating new payload. - /// <seealso cref="http://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#engine_forkchoiceupdatedv1"/>. - /// </summary> - public class ForkchoiceUpdatedV1Handler : IForkchoiceUpdatedV1Handler - { - private readonly IBlockTree _blockTree; - private readonly IManualBlockFinalizationManager _manualBlockFinalizationManager; - private readonly IPoSSwitcher _poSSwitcher; - private readonly IPayloadPreparationService _payloadPreparationService; - private readonly IBlockProcessingQueue _processingQueue; - private readonly IBlockCacheService _blockCacheService; - private readonly IInvalidChainTracker _invalidChainTracker; - private readonly IMergeSyncController _mergeSyncController; - private readonly IBeaconPivot _beaconPivot; - private readonly ILogger _logger; - private readonly IPeerRefresher _peerRefresher; - - public ForkchoiceUpdatedV1Handler( - IBlockTree blockTree, - IManualBlockFinalizationManager manualBlockFinalizationManager, - IPoSSwitcher poSSwitcher, - IPayloadPreparationService payloadPreparationService, - IBlockProcessingQueue processingQueue, - IBlockCacheService blockCacheService, - IInvalidChainTracker invalidChainTracker, - IMergeSyncController mergeSyncController, - IBeaconPivot beaconPivot, - IPeerRefresher peerRefresher, - ILogManager logManager) - { - _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); - _manualBlockFinalizationManager = manualBlockFinalizationManager ?? throw new ArgumentNullException(nameof(manualBlockFinalizationManager)); - _poSSwitcher = poSSwitcher ?? throw new ArgumentNullException(nameof(poSSwitcher)); - _payloadPreparationService = payloadPreparationService; - _processingQueue = processingQueue; - _blockCacheService = blockCacheService; - _invalidChainTracker = invalidChainTracker; - _mergeSyncController = mergeSyncController; - _beaconPivot = beaconPivot; - _peerRefresher = peerRefresher; - _logger = logManager.GetClassLogger(); - } - - public Task<ResultWrapper<ForkchoiceUpdatedV1Result>> Handle(ForkchoiceStateV1 forkchoiceState, PayloadAttributes? payloadAttributes) - { - string requestStr = $"{forkchoiceState} {payloadAttributes}"; - if (_logger.IsInfo) _logger.Info($"Received: {requestStr}"); - - if (_invalidChainTracker.IsOnKnownInvalidChain(forkchoiceState.HeadBlockHash, out Keccak? lastValidHash)) - { - if (_logger.IsInfo) _logger.Info($" FCU - Invalid - {requestStr} {forkchoiceState.HeadBlockHash} is known to be a part of an invalid chain."); - return ForkchoiceUpdatedV1Result.Invalid(lastValidHash); - } - - Block? newHeadBlock = GetBlock(forkchoiceState.HeadBlockHash); - if (newHeadBlock is null) // if a head is unknown we are syncing - { - if (_blockCacheService.BlockCache.TryGetValue(forkchoiceState.HeadBlockHash, out Block? block)) - { - StartNewBeaconHeaderSync(forkchoiceState, block, requestStr); - - return ForkchoiceUpdatedV1Result.Syncing; - } - - if (_logger.IsInfo) - { - _logger.Info($"Syncing... Unknown forkchoiceState head hash... Request: {requestStr}."); - } - return ForkchoiceUpdatedV1Result.Syncing; - } - - BlockInfo? blockInfo = _blockTree.GetInfo(newHeadBlock.Number, newHeadBlock.GetOrCalculateHash()).Info; - if (blockInfo is null) - { - if (_logger.IsWarn) { _logger.Warn($"Block info for: {requestStr} wasn't found."); } - return ForkchoiceUpdatedV1Result.Syncing; - } - if (!blockInfo.WasProcessed) - { - BlockHeader? blockParent = _blockTree.FindHeader(newHeadBlock.ParentHash!); - if (blockParent is null) - { - if (_logger.IsInfo) - _logger.Info($"Parent of block {newHeadBlock} not available. Starting new beacon header. sync."); - - StartNewBeaconHeaderSync(forkchoiceState, newHeadBlock!, requestStr); - - return ForkchoiceUpdatedV1Result.Syncing; - } - - if (_beaconPivot.ShouldForceStartNewSync) - { - if (_logger.IsInfo) - _logger.Info($"Force starting new sync."); - - StartNewBeaconHeaderSync(forkchoiceState, newHeadBlock!, requestStr); - - return ForkchoiceUpdatedV1Result.Syncing; - } - - if (!blockInfo.IsBeaconMainChain && blockInfo.IsBeaconInfo) - ReorgBeaconChainDuringSync(newHeadBlock!, blockInfo); - - int processingQueueCount = _processingQueue.Count; - if (processingQueueCount == 0) - { - _peerRefresher.RefreshPeers(newHeadBlock!.Hash!, newHeadBlock.ParentHash!, forkchoiceState.FinalizedBlockHash); - _blockCacheService.FinalizedHash = forkchoiceState.FinalizedBlockHash; - _mergeSyncController.StopBeaconModeControl(); - - if (_logger.IsInfo) { _logger.Info($"Syncing beacon headers... Request: {requestStr}."); } - } - else - { - if (_logger.IsInfo) { _logger.Info($"Processing {_processingQueue.Count} blocks... Request: {requestStr}."); } - } - - _beaconPivot.ProcessDestination ??= newHeadBlock!.Header; - return ForkchoiceUpdatedV1Result.Syncing; - } - - if (_logger.IsInfo) _logger.Info($"FCU - block {newHeadBlock} was processed."); - - BlockHeader? finalizedHeader = ValidateBlockHash(forkchoiceState.FinalizedBlockHash, out string? finalizationErrorMsg); - if (finalizationErrorMsg is not null) - { - if (_logger.IsWarn) _logger.Warn($"Invalid finalized block hash {finalizationErrorMsg}. Request: {requestStr}."); - return ForkchoiceUpdatedV1Result.Error(finalizationErrorMsg, MergeErrorCodes.InvalidForkchoiceState); - } - - ValidateBlockHash(forkchoiceState.SafeBlockHash, out string? safeBlockErrorMsg); - if (safeBlockErrorMsg is not null) - { - if (_logger.IsWarn) _logger.Warn($"Invalid safe block hash {finalizationErrorMsg}. Request: {requestStr}."); - return ForkchoiceUpdatedV1Result.Error(safeBlockErrorMsg, MergeErrorCodes.InvalidForkchoiceState); - } - - if ((newHeadBlock.TotalDifficulty ?? 0) != 0 && (_poSSwitcher.MisconfiguredTerminalTotalDifficulty() || _poSSwitcher.BlockBeforeTerminalTotalDifficulty(newHeadBlock.Header))) - { - if (_logger.IsWarn) _logger.Warn($"Invalid terminal block. Nethermind TTD {_poSSwitcher.TerminalTotalDifficulty}, NewHeadBlock TD: {newHeadBlock.Header.TotalDifficulty}. Request: {requestStr}."); - - // https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#specification - // {status: INVALID, latestValidHash: 0x0000000000000000000000000000000000000000000000000000000000000000, validationError: errorMessage | null} if terminal block conditions are not satisfied - return ForkchoiceUpdatedV1Result.Invalid(Keccak.Zero); - } - - Block[]? blocks = EnsureNewHead(newHeadBlock, out string? setHeadErrorMsg); - if (setHeadErrorMsg is not null) - { - if (_logger.IsWarn) _logger.Warn($"Invalid new head block {setHeadErrorMsg}. Request: {requestStr}."); - return ForkchoiceUpdatedV1Result.Error(setHeadErrorMsg, ErrorCodes.InvalidParams); - } - - if (_blockTree.IsOnMainChainBehindHead(newHeadBlock)) - { - if (_logger.IsInfo) _logger.Info($"Valid. ForkchoiceUpdated ignored - already in canonical chain. Request: {requestStr}."); - return ForkchoiceUpdatedV1Result.Valid(null, forkchoiceState.HeadBlockHash); - } - - EnsureTerminalBlock(forkchoiceState, blocks); - - bool newHeadTheSameAsCurrentHead = _blockTree.Head!.Hash == newHeadBlock.Hash; - bool shouldUpdateHead = !newHeadTheSameAsCurrentHead && blocks is not null; - if (shouldUpdateHead) - { - _blockTree.UpdateMainChain(blocks!, true, true); - } - - if (IsInconsistent(forkchoiceState.FinalizedBlockHash)) - { - string errorMsg = $"Inconsistent forkchoiceState - finalized block hash. Request: {requestStr}"; - if (_logger.IsWarn) _logger.Warn(errorMsg); - return ForkchoiceUpdatedV1Result.Error(errorMsg, MergeErrorCodes.InvalidForkchoiceState); - } - - if (IsInconsistent(forkchoiceState.SafeBlockHash)) - { - string errorMsg = $"Inconsistent forkchoiceState - safe block hash. Request: {requestStr}"; - if (_logger.IsWarn) _logger.Warn(errorMsg); - return ForkchoiceUpdatedV1Result.Error(errorMsg, MergeErrorCodes.InvalidForkchoiceState); - } - - bool nonZeroFinalizedBlockHash = forkchoiceState.FinalizedBlockHash != Keccak.Zero; - // bool nonZeroSafeBlockHash = forkchoiceState.SafeBlockHash != Keccak.Zero; - if (nonZeroFinalizedBlockHash) - { - _manualBlockFinalizationManager.MarkFinalized(newHeadBlock.Header, finalizedHeader!); - } - - if (shouldUpdateHead) - { - _poSSwitcher.ForkchoiceUpdated(newHeadBlock.Header, forkchoiceState.FinalizedBlockHash); - if (_logger.IsInfo) _logger.Info($"Block {forkchoiceState.HeadBlockHash} was set as head."); - } - - string? payloadId = null; - if (payloadAttributes is not null) - { - payloadAttributes.GasLimit = null; - if (newHeadBlock.Timestamp >= payloadAttributes.Timestamp) - { - if (_logger.IsWarn) _logger.Warn($"Invalid payload attributes timestamp {payloadAttributes.Timestamp}, block timestamp {newHeadBlock.Timestamp}. Request: {requestStr}."); - - return ForkchoiceUpdatedV1Result.Error( - $"Invalid payload attributes timestamp {payloadAttributes.Timestamp}, block timestamp {newHeadBlock.Timestamp}. Request: {requestStr}", - MergeErrorCodes.InvalidPayloadAttributes); - } - else - { - payloadId = _payloadPreparationService.StartPreparingPayload(newHeadBlock.Header, payloadAttributes); - } - } - - if (_logger.IsInfo) _logger.Info($"Valid. Request: {requestStr}."); - - _blockTree.ForkChoiceUpdated(forkchoiceState.FinalizedBlockHash, forkchoiceState.SafeBlockHash); - return ForkchoiceUpdatedV1Result.Valid(payloadId, forkchoiceState.HeadBlockHash); - } - - private void StartNewBeaconHeaderSync(ForkchoiceStateV1 forkchoiceState, Block block, string requestStr) - { - _mergeSyncController.InitBeaconHeaderSync(block.Header); - _beaconPivot.ProcessDestination = block.Header; - _peerRefresher.RefreshPeers(block.Hash!, block.ParentHash!, forkchoiceState.FinalizedBlockHash); - _blockCacheService.FinalizedHash = forkchoiceState.FinalizedBlockHash; - - if (_logger.IsInfo) _logger.Info($"Start a new sync process... Request: {requestStr}."); - } - - // This method will detect reorg in terminal PoW block - private void EnsureTerminalBlock(ForkchoiceStateV1 forkchoiceState, Block[]? blocks) - { - // we can reorg terminal block only if we haven't finalized PoS yet and we're not finalizing PoS now - // https://github.com/ethereum/EIPs/blob/d896145678bd65d3eafd8749690c1b5228875c39/EIPS/eip-3675.md#ability-to-jump-between-terminal-pow-blocks - bool notFinalizingPoS = forkchoiceState.FinalizedBlockHash == Keccak.Zero; - bool notFinalizedPoS = _manualBlockFinalizationManager.LastFinalizedHash == Keccak.Zero; - if (notFinalizingPoS && notFinalizedPoS && blocks is not null) - { - for (int i = 0; i < blocks.Length; ++i) - { - if (blocks[i].Header.Difficulty != 0 && blocks[i].TotalDifficulty >= _poSSwitcher.TerminalTotalDifficulty) - { - if (_poSSwitcher.TryUpdateTerminalBlock(blocks[i].Header)) - { - if (_logger.IsInfo) _logger.Info($"Terminal block {blocks[i].Header} updated during the forkchoice"); - } - - break; - } - } - } - } - - private bool IsInconsistent(Keccak blockHash) => blockHash != Keccak.Zero && !_blockTree.IsMainChain(blockHash); - - private Block? GetBlock(Keccak headBlockHash) - { - Block? block = _blockTree.FindBlock(headBlockHash, BlockTreeLookupOptions.DoNotCreateLevelIfMissing); - if (block is null) - { - if (_logger.IsInfo) _logger.Info($"Syncing... Block {headBlockHash} not found."); - } - - return block; - } - - private Block[]? EnsureNewHead(Block newHeadBlock, out string? errorMessage) - { - errorMessage = null; - if (_blockTree.Head!.Hash == newHeadBlock.Hash) - { - return null; - } - - if (!TryGetBranch(newHeadBlock, out Block[] branchOfBlocks)) - { - errorMessage = $"Block's {newHeadBlock} main chain predecessor cannot be found and it will not be set as head."; - if (_logger.IsWarn) _logger.Warn(errorMessage); - } - - return branchOfBlocks; - } - - private BlockHeader? ValidateBlockHash(Keccak blockHash, out string? errorMessage, bool skipZeroHash = true) - { - errorMessage = null; - if (skipZeroHash && blockHash == Keccak.Zero) - { - return null; - } - - BlockHeader? blockHeader = _blockTree.FindHeader(blockHash, BlockTreeLookupOptions.DoNotCreateLevelIfMissing); - if (blockHeader is null) - { - errorMessage = $"Block {blockHash} not found."; - if (_logger.IsWarn) _logger.Warn(errorMessage); - } - return blockHeader; - } - - - private bool TryGetBranch(Block newHeadBlock, out Block[] blocks) - { - List<Block> blocksList = new() { newHeadBlock }; - Block? predecessor = newHeadBlock; - - while (true) - { - predecessor = _blockTree.FindParent(predecessor, BlockTreeLookupOptions.DoNotCreateLevelIfMissing); - if (predecessor is null) - { - blocks = Array.Empty<Block>(); - return false; - } - if (_blockTree.IsMainChain(predecessor.Header)) break; - blocksList.Add(predecessor); - } - - blocksList.Reverse(); - blocks = blocksList.ToArray(); - return true; - } - - private void ReorgBeaconChainDuringSync(Block newHeadBlock, BlockInfo newHeadBlockInfo) - { - if (_logger.IsInfo) _logger.Info("BeaconChain reorged during the sync or cache rebuilt"); - BlockInfo[] beaconMainChainBranch = GetBeaconChainBranch(newHeadBlock, newHeadBlockInfo); - _blockTree.UpdateBeaconMainChain(beaconMainChainBranch, Math.Max(_beaconPivot.ProcessDestination?.Number ?? 0, newHeadBlock.Number)); - _beaconPivot.ProcessDestination = newHeadBlock.Header; - } - - private BlockInfo[] GetBeaconChainBranch(Block newHeadBlock, BlockInfo newHeadBlockInfo) - { - newHeadBlockInfo.BlockNumber = newHeadBlock.Number; - List<BlockInfo> blocksList = new() { newHeadBlockInfo }; - Block? predecessor = newHeadBlock; - - while (true) - { - predecessor = _blockTree.FindParent(predecessor, BlockTreeLookupOptions.TotalDifficultyNotNeeded); - - if (predecessor is null) - { - break; - } - BlockInfo? predecessorInfo = _blockTree.GetInfo(predecessor.Number, predecessor.GetOrCalculateHash()).Info; - if (predecessorInfo is null) break; - predecessorInfo.BlockNumber = predecessor.Number; - if (predecessorInfo.IsBeaconMainChain || !predecessorInfo.IsBeaconInfo) break; - if (_logger.IsInfo) _logger.Info($"Reorged to beacon block ({predecessorInfo.BlockNumber}) {predecessorInfo.BlockHash} or cache rebuilt"); - blocksList.Add(predecessorInfo); - } - - blocksList.Reverse(); - - return blocksList.ToArray(); - } - - } -} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/GetPayloadBodiesByHashV1Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/GetPayloadBodiesByHashV1Handler.cs deleted file mode 100644 index 424d64057d1..00000000000 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/GetPayloadBodiesByHashV1Handler.cs +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using System.Threading.Tasks; -using Nethermind.Blockchain; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.JsonRpc; -using Nethermind.Logging; -using Nethermind.Merge.Plugin.Data.V1; - -namespace Nethermind.Merge.Plugin.Handlers.V1 -{ - public class GetPayloadBodiesByHashV1Handler : IAsyncHandler<Keccak[], ExecutionPayloadBodyV1Result?[]> - { - private readonly IBlockTree _blockTree; - private readonly ILogger _logger; - - public GetPayloadBodiesByHashV1Handler(IBlockTree blockTree, ILogManager logManager) - { - _blockTree = blockTree; - _logger = logManager.GetClassLogger(); - } - - public Task<ResultWrapper<ExecutionPayloadBodyV1Result?[]>> HandleAsync(Keccak[] blockHashes) - { - var payloadBodies = new ExecutionPayloadBodyV1Result?[blockHashes.Length]; - for (int i = 0; i < blockHashes.Length; ++i) - { - Block? block = _blockTree.FindBlock(blockHashes[i]); - - payloadBodies[i] = block is not null ? new ExecutionPayloadBodyV1Result(block.Transactions) : null; - } - - return Task.FromResult(ResultWrapper<ExecutionPayloadBodyV1Result?[]>.Success(payloadBodies)); - } - } -} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/NewPayloadV1Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/NewPayloadV1Handler.cs deleted file mode 100644 index a56f9272a2c..00000000000 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/V1/NewPayloadV1Handler.cs +++ /dev/null @@ -1,449 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Nethermind.Api; -using Nethermind.Blockchain; -using Nethermind.Blockchain.Synchronization; -using Nethermind.Consensus; -using Nethermind.Consensus.Processing; -using Nethermind.Consensus.Validators; -using Nethermind.Core; -using Nethermind.Core.Caching; -using Nethermind.Core.Crypto; -using Nethermind.Core.Specs; -using Nethermind.Crypto; -using Nethermind.Int256; -using Nethermind.JsonRpc; -using Nethermind.Logging; -using Nethermind.Merge.Plugin.Data.V1; -using Nethermind.Merge.Plugin.InvalidChainTracker; -using Nethermind.Merge.Plugin.Synchronization; -using Nethermind.Synchronization; - -namespace Nethermind.Merge.Plugin.Handlers.V1 -{ - /// <summary> - /// Verifies the payload according to the execution environment rule set (EIP-3675) and returns the <see cref="PayloadStatusV1"/> of the verification and the hash of the last valid block. - /// - /// <seealso cref="http://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#engine_newpayloadv1"/> - /// </summary> - public class NewPayloadV1Handler : IAsyncHandler<ExecutionPayloadV1, PayloadStatusV1> - { - private readonly IBlockValidator _blockValidator; - private readonly IBlockTree _blockTree; - private readonly ISyncConfig _syncConfig; - private readonly IPoSSwitcher _poSSwitcher; - private readonly IBeaconSyncStrategy _beaconSyncStrategy; - private readonly IBeaconPivot _beaconPivot; - private readonly IBlockCacheService _blockCacheService; - private readonly IBlockProcessingQueue _processingQueue; - private readonly IMergeSyncController _mergeSyncController; - private readonly ISpecProvider _specProvider; - private readonly IInvalidChainTracker _invalidChainTracker; - private readonly ILogger _logger; - private readonly LruCache<Keccak, bool>? _latestBlocks; - private readonly ProcessingOptions _defaultProcessingOptions; - private readonly TimeSpan _timeout; - - public NewPayloadV1Handler( - IBlockValidator blockValidator, - IBlockTree blockTree, - IInitConfig initConfig, - ISyncConfig syncConfig, - IPoSSwitcher poSSwitcher, - IBeaconSyncStrategy beaconSyncStrategy, - IBeaconPivot beaconPivot, - IBlockCacheService blockCacheService, - IBlockProcessingQueue processingQueue, - IInvalidChainTracker invalidChainTracker, - IMergeSyncController mergeSyncController, - ISpecProvider specProvider, - ILogManager logManager, - TimeSpan? timeout = null, - int cacheSize = 50) - { - _blockValidator = blockValidator ?? throw new ArgumentNullException(nameof(blockValidator)); - _blockTree = blockTree; - _syncConfig = syncConfig; - _poSSwitcher = poSSwitcher; - _beaconSyncStrategy = beaconSyncStrategy; - _beaconPivot = beaconPivot; - _blockCacheService = blockCacheService; - _processingQueue = processingQueue; - _invalidChainTracker = invalidChainTracker; - _mergeSyncController = mergeSyncController; - _specProvider = specProvider; - _logger = logManager.GetClassLogger(); - _defaultProcessingOptions = initConfig.StoreReceipts ? ProcessingOptions.EthereumMerge | ProcessingOptions.StoreReceipts : ProcessingOptions.EthereumMerge; - _timeout = timeout ?? TimeSpan.FromSeconds(7); - if (cacheSize > 0) - _latestBlocks = new LruCache<Keccak, bool>(cacheSize, 0, "LatestBlocks"); - } - - public async Task<ResultWrapper<PayloadStatusV1>> HandleAsync(ExecutionPayloadV1 request) - { - string requestStr = $"a new payload: {request}"; - if (_logger.IsInfo) { _logger.Info($"Received {requestStr}"); } - - if (!request.TryGetBlock(out Block? block, _poSSwitcher.FinalTotalDifficulty)) - { - if (_logger.IsWarn) _logger.Warn($"Invalid block. Result of {requestStr}."); - return NewPayloadV1Result.Invalid(null, $"Block {request} could not be parsed as a block"); - } - - if (!HeaderValidator.ValidateHash(block!.Header)) - { - if (_logger.IsWarn) _logger.Warn($"InvalidBlockHash. Result of {requestStr}."); - return NewPayloadV1Result.InvalidBlockHash; - } - - Keccak blockHash = block.Hash!; - _invalidChainTracker.SetChildParent(blockHash, request.ParentHash); - if (_invalidChainTracker.IsOnKnownInvalidChain(blockHash, out Keccak? lastValidHash)) - { - if (_logger.IsInfo) _logger.Info($"Invalid - block {request} is known to be a part of an invalid chain."); - return NewPayloadV1Result.Invalid(lastValidHash, $"Block {request} is known to be a part of an invalid chain."); - } - - if (block.Header.Number <= _syncConfig.PivotNumberParsed) - { - if (_logger.IsInfo) _logger.Info($"Pre-pivot block, ignored and returned Syncing. Result of {requestStr}."); - return NewPayloadV1Result.Syncing; - } - - block.Header.TotalDifficulty = _poSSwitcher.FinalTotalDifficulty; - - BlockHeader? parentHeader = _blockTree.FindHeader(request.ParentHash, BlockTreeLookupOptions.DoNotCreateLevelIfMissing); - if (parentHeader is null) - { - // possible that headers sync finished before this was called, so blocks in cache weren't inserted - if (!_beaconSyncStrategy.IsBeaconSyncFinished(parentHeader)) - { - bool inserted = TryInsertDanglingBlock(block); - if (_logger.IsInfo) _logger.Info(inserted ? $"BeaconSync not finished - block {block} inserted" : $"BeaconSync not finished - block {block} added to cache."); - return NewPayloadV1Result.Syncing; - } - - if (_logger.IsInfo) _logger.Info($"Insert block into cache without parent {block}"); - _blockCacheService.BlockCache.TryAdd(blockHash, block); - return NewPayloadV1Result.Syncing; - } - - // we need to check if the head is greater than block.Number. In fast sync we could return Valid to CL without this if - if (_blockTree.IsOnMainChainBehindOrEqualHead(block)) - { - if (_logger.IsInfo) _logger.Info($"Valid... A new payload ignored. Block {block.ToString(Block.Format.FullHashAndNumber)} found in main chain."); - return NewPayloadV1Result.Valid(block.Hash); - } - - if (!ShouldProcessBlock(block, parentHeader, out ProcessingOptions processingOptions)) // we shouldn't process block - { - if (!_blockValidator.ValidateSuggestedBlock(block)) - { - if (_logger.IsInfo) _logger.Info($"Rejecting invalid block received during the sync, block: {block}"); - return NewPayloadV1Result.Invalid(null); - } - - BlockTreeInsertHeaderOptions insertHeaderOptions = BlockTreeInsertHeaderOptions.BeaconBlockInsert; - - if (block.Number <= Math.Max(_blockTree.BestKnownNumber, _blockTree.BestKnownBeaconNumber) && _blockTree.FindBlock(block.GetOrCalculateHash(), BlockTreeLookupOptions.TotalDifficultyNotNeeded) is not null) - { - if (_logger.IsInfo) _logger.Info($"Syncing... Parent wasn't processed. Block already known in blockTree {block}."); - return NewPayloadV1Result.Syncing; - } - - if (_beaconPivot.ProcessDestination is not null && _beaconPivot.ProcessDestination.Hash == block.ParentHash) - { - insertHeaderOptions |= BlockTreeInsertHeaderOptions.MoveToBeaconMainChain; // we're extending our beacon canonical chain - _beaconPivot.ProcessDestination = block.Header; - } - - _beaconPivot.EnsurePivot(block.Header, true); - _blockTree.Insert(block, BlockTreeInsertBlockOptions.SaveHeader | BlockTreeInsertBlockOptions.SkipCanAcceptNewBlocks, insertHeaderOptions); - - if (_logger.IsInfo) _logger.Info($"Syncing... Parent wasn't processed. Inserting block {block}."); - return NewPayloadV1Result.Syncing; - } - - if (_poSSwitcher.MisconfiguredTerminalTotalDifficulty()) - { - if (_logger.IsWarn) _logger.Warn($"Misconfigured terminal total difficulty."); - - return NewPayloadV1Result.Invalid(Keccak.Zero); - } - - if ((block.TotalDifficulty ?? 0) != 0 && _poSSwitcher.BlockBeforeTerminalTotalDifficulty(parentHeader)) - { - if (_logger.IsWarn) _logger.Warn($"Invalid terminal block. Nethermind TTD {_poSSwitcher.TerminalTotalDifficulty}, Parent TD: {parentHeader.TotalDifficulty}. Request: {requestStr}."); - - // {status: INVALID, latestValidHash: 0x0000000000000000000000000000000000000000000000000000000000000000, validationError: errorMessage | null} if terminal block conditions are not satisfied - return NewPayloadV1Result.Invalid(Keccak.Zero); - } - - // Otherwise, we can just process this block and we don't need to do BeaconSync anymore. - _mergeSyncController.StopSyncing(); - - // Try to execute block - (ValidationResult result, string? message) = await ValidateBlockAndProcess(block, parentHeader, processingOptions); - - if (result == ValidationResult.Invalid) - { - if (_logger.IsInfo) _logger.Info($"Invalid block found. Validation message: {message}. Result of {requestStr}."); - _invalidChainTracker.OnInvalidBlock(blockHash, request.ParentHash); - return ResultWrapper<PayloadStatusV1>.Success(BuildInvalidPayloadStatusV1(request, message)); - } - - if (result == ValidationResult.Syncing) - { - if (_logger.IsInfo) _logger.Info($"Processing queue wasn't empty added to queue {requestStr}."); - return NewPayloadV1Result.Syncing; - } - - if (_logger.IsInfo) _logger.Info($"Valid. Result of {requestStr}."); - return NewPayloadV1Result.Valid(request.BlockHash); - } - - /// <summary> - /// Decides if we should process the block or try syncing to it. It also returns what options to process the block with. - /// </summary> - /// <param name="block">Block</param> - /// <param name="parent">Parent header</param> - /// <param name="processingOptions">Options that should be used for processing</param> - /// <returns>Options which should be used for block processing. Null if we shouldn't process the block.</returns> - /// <remarks> - /// We decide to process blocks in two situations: - /// 1. The block parent was already processed. Then we process with ProcessingOptions.EthereumMerge with potentially also StoringReceipts. - /// This contains ProcessingOptions.IgnoreParentNotOnMainChain flag in order not to collect whole branch for processing, but only process this block directly on parent. - /// As parent was processed the state to process on should also be available. - /// - /// 2. If the parent wasn't processed, but it was a PoW block (terminal block) and we are not syncing PoW chain and are in the deep past. - /// In this case we remove ~ProcessingOptions.IgnoreParentNotOnMainChain flag in order to collect whole branch for processing. - /// If we didn't support this edge case then we couldn't process this block and would have to return Syncing, which is not desired during transition. - /// - /// Scenario 2 proved to be quite common on testnets which produced multiple transition blocks. - /// </remarks> - private bool ShouldProcessBlock(Block block, BlockHeader parent, out ProcessingOptions processingOptions) - { - processingOptions = _defaultProcessingOptions; - - BlockInfo? parentBlockInfo = _blockTree.GetInfo(parent.Number, parent.GetOrCalculateHash()).Info; - bool parentProcessed = parentBlockInfo is { WasProcessed: true }; - - // During the transition we can have a case of NP built over a transition block that wasn't processed. - // We want to force process the whole branch then, but not longer than few blocks. - // But we don't want this to trigger when we are in beacon sync. - // The last condition: !parentBlockInfo.IsBeaconInfo will be true for terminal blocks. - // Checking _posSwitcher.IsTerminal might not be the best, because we're loading parentHeader with DoNotCalculateTotalDifficulty option - bool weHaveOnlyFewBlocksToProcess = (_blockTree.Head?.Number ?? 0) + 8 >= block.Number; - bool parentIsPoWBlock = parent.Difficulty != UInt256.Zero; - bool processTerminalBlock = !_poSSwitcher.TransitionFinished // we haven't finished transition - && weHaveOnlyFewBlocksToProcess // we won't try to process too much blocks (if we are behind the transition block and still processing blocks) - && parentBlockInfo is { IsBeaconInfo: false } // we are not in beacon sync - && parentIsPoWBlock; // parent was PoW block -> so it was a transition block - - if (!parentProcessed && processTerminalBlock) // so if parent wasn't processed - { - if (_logger.IsInfo) _logger.Info($"Forced processing block {block}, block TD: {block.TotalDifficulty}, parent: {parent}, parent TD: {parent.TotalDifficulty}"); - - // if parent wasn't processed and we want to force processing terminal block then we need to allow to process whole branch, not just one block - // in all other cases when parent is processed ProcessingOptions.IgnoreParentNotOnMainChain allows us to process just this block ignoring that its not on Head - // this option is part of ProcessingOptions.EthereumMerge option - processingOptions &= ~ProcessingOptions.IgnoreParentNotOnMainChain; - } - - return parentProcessed || processTerminalBlock; - } - - private async Task<(ValidationResult, string?)> ValidateBlockAndProcess(Block block, BlockHeader parent, ProcessingOptions processingOptions) - { - ValidationResult TryCacheResult(ValidationResult result) - { - // notice that it is not correct to add information to the cache - // if we return SYNCING for example, and don't know yet whether - // the block is valid or invalid because we haven't processed it yet - if (result == ValidationResult.Valid || result == ValidationResult.Invalid) - _latestBlocks?.Set(block.GetOrCalculateHash(), result == ValidationResult.Valid); - return result; - } - - (ValidationResult? result, string? validationMessage) = (null, null); - - // If duplicate, reuse results - if (_latestBlocks is not null && _latestBlocks.TryGet(block.Hash!, out bool isValid)) - { - if (!isValid) - { - validationMessage = "Invalid block found in latestBlock cache."; - if (_logger.IsWarn) _logger.Warn(validationMessage); - } - - return (isValid ? ValidationResult.Valid : ValidationResult.Invalid, validationMessage); - } - - // Validate - if (!ValidateWithBlockValidator(block, parent)) - { - return (TryCacheResult(ValidationResult.Invalid), string.Empty); - } - - TaskCompletionSource<ValidationResult?> blockProcessedTaskCompletionSource = new(); - Task<ValidationResult?> blockProcessed = blockProcessedTaskCompletionSource.Task; - - void GetProcessingQueueOnBlockRemoved(object? o, BlockHashEventArgs e) - { - if (e.BlockHash == block.Hash) - { - _processingQueue.BlockRemoved -= GetProcessingQueueOnBlockRemoved; - - if (e.ProcessingResult == ProcessingResult.Exception) - { - BlockchainException? exception = new("Block processing threw exception.", e.Exception); - blockProcessedTaskCompletionSource.SetException(exception); - return; - } - - ValidationResult? validationResult = e.ProcessingResult switch - { - ProcessingResult.Success => ValidationResult.Valid, - ProcessingResult.ProcessingError => ValidationResult.Invalid, - _ => null - }; - - validationMessage = e.ProcessingResult switch - { - ProcessingResult.QueueException => "Block cannot be added to processing queue.", - ProcessingResult.MissingBlock => "Block wasn't found in tree.", - ProcessingResult.ProcessingError => "Block processing failed.", - _ => null - }; - - blockProcessedTaskCompletionSource.TrySetResult(validationResult); - } - } - - _processingQueue.BlockRemoved += GetProcessingQueueOnBlockRemoved; - try - { - Task timeoutTask = Task.Delay(_timeout); - - AddBlockResult addResult = await _blockTree - .SuggestBlockAsync(block, BlockTreeSuggestOptions.ForceDontSetAsMain) - .AsTask().TimeoutOn(timeoutTask); - - result = addResult switch - { - AddBlockResult.InvalidBlock => ValidationResult.Invalid, - // if the block is marked as AlreadyKnown by the block tree then it means it has already - // been suggested. there are three possibilities, either the block hasn't been processed yet, - // the block was processed and returned invalid but this wasn't saved anywhere or the block was - // processed and marked as valid. - // if marked as processed by the blocktree then return VALID, otherwise null so that it's process a few lines below - AddBlockResult.AlreadyKnown => _blockTree.WasProcessed(block.Number, block.Hash!) ? ValidationResult.Valid : null, - _ => null - }; - - validationMessage = addResult switch - { - AddBlockResult.InvalidBlock => "Block couldn't be added to the tree.", - AddBlockResult.AlreadyKnown => "Block was already known in the tree.", - _ => null - }; - - if (!result.HasValue) - { - // we don't know the result of processing the block, either because - // it is the first time we add it to the tree or it's AlreadyKnown in - // the tree but hasn't yet been processed. if it's the second case - // probably the block is already in the processing queue as a result - // of a previous newPayload or the block being discovered during syncing - // but add it to the processing queue just in case. - _processingQueue.Enqueue(block, processingOptions); - result = await blockProcessed.TimeoutOn(timeoutTask); - } - } - catch (TimeoutException) - { - // we timed out while processing the block, result will be null and we will return SYNCING below, no need to do anything - if (_logger.IsDebug) _logger.Debug($"Block {block.ToString(Block.Format.FullHashAndNumber)} timed out when processing. Assume Syncing."); - } - finally - { - _processingQueue.BlockRemoved -= GetProcessingQueueOnBlockRemoved; - } - - return (TryCacheResult(result ?? ValidationResult.Syncing), validationMessage); - } - - private bool ValidateWithBlockValidator(Block block, BlockHeader parent) - { - block.Header.TotalDifficulty ??= parent.TotalDifficulty + block.Difficulty; - block.Header.IsPostMerge = true; // I think we don't need to set it again here. - bool isValid = _blockValidator.ValidateSuggestedBlock(block); - if (!isValid && _logger.IsWarn) _logger.Warn($"Block validator rejected the block {block.ToString(Block.Format.FullHashAndNumber)}."); - return isValid; - } - - private PayloadStatusV1 BuildInvalidPayloadStatusV1(ExecutionPayloadV1 request, string? validationMessage) => - new() - { - Status = PayloadStatus.Invalid, - ValidationError = validationMessage, - LatestValidHash = _invalidChainTracker.IsOnKnownInvalidChain(request.BlockHash!, out Keccak? lastValidHash) - ? lastValidHash - : request.ParentHash - }; - - /// Pop blocks from cache up to ancestor on the beacon chain. Which is then inserted into the block tree - /// which I assume will switch the canonical chain. - /// Return false if no ancestor that is part of beacon chain found. - private bool TryInsertDanglingBlock(Block block) - { - BlockTreeInsertHeaderOptions insertHeaderOptions = BlockTreeInsertHeaderOptions.BeaconBlockInsert | BlockTreeInsertHeaderOptions.MoveToBeaconMainChain; - - if (!_blockTree.IsKnownBeaconBlock(block.Number, block.Hash ?? block.CalculateHash())) - { - // last block inserted is parent of current block, part of the same chain - Block? current = block; - Stack<Block> stack = new(); - while (current is not null) - { - stack.Push(current); - Keccak currentHash = current.Hash!; - if (currentHash == _beaconPivot.PivotHash || _blockTree.IsKnownBeaconBlock(current.Number, currentHash)) - { - break; - } - - _blockCacheService.BlockCache.TryGetValue(current.ParentHash!, out Block? parentBlock); - current = parentBlock; - } - - if (current is null) - { - // block not part of beacon pivot chain, save in cache - _blockCacheService.BlockCache.TryAdd(block.Hash!, block); - return false; - } - - while (stack.TryPop(out Block? child)) - { - _blockTree.Insert(child, BlockTreeInsertBlockOptions.SaveHeader, insertHeaderOptions); - } - - _beaconPivot.ProcessDestination = block.Header; - } - - return true; - } - - private enum ValidationResult - { - Invalid, - Valid, - Syncing - } - } -} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.cs index 6c2f83e8f9c..5a2fc75c07b 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.cs @@ -3,14 +3,10 @@ using System.Threading.Tasks; using Nethermind.Consensus.Producers; -using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Int256; using Nethermind.JsonRpc; using Nethermind.JsonRpc.Modules; using Nethermind.Merge.Plugin.Data; -using Nethermind.Merge.Plugin.Data.V1; -using Nethermind.Merge.Plugin.Data.V2; namespace Nethermind.Merge.Plugin { @@ -28,11 +24,10 @@ public interface IEngineRpcModule : IRpcModule Description = "Returns the most recent version of an execution payload with respect to the transaction set contained by the mempool.", IsSharable = true, IsImplemented = true)] - Task<ResultWrapper<ExecutionPayloadV1?>> engine_getPayloadV1(byte[] payloadId); + Task<ResultWrapper<ExecutionPayload?>> engine_getPayloadV1(byte[] payloadId); [JsonRpcMethod( - Description = - "Returns the most recent version of an execution payload and fees with respect to the transaction set contained by the mempool.", + 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<ResultWrapper<GetPayloadV2Result?>> engine_getPayloadV2(byte[] payloadId); @@ -41,7 +36,13 @@ public interface IEngineRpcModule : IRpcModule 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<ResultWrapper<PayloadStatusV1>> engine_newPayloadV1(ExecutionPayloadV1 executionPayload); + Task<ResultWrapper<PayloadStatusV1>> engine_newPayloadV1(ExecutionPayload executionPayload); + + [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<ResultWrapper<PayloadStatusV1>> engine_newPayloadV2(ExecutionPayload executionPayload); [JsonRpcMethod( Description = "Verifies the payload according to the execution environment rules and returns the verification status and hash of the last valid block.", @@ -49,6 +50,12 @@ public interface IEngineRpcModule : IRpcModule IsImplemented = true)] Task<ResultWrapper<ForkchoiceUpdatedV1Result>> engine_forkchoiceUpdatedV1(ForkchoiceStateV1 forkchoiceState, PayloadAttributes? payloadAttributes = null); + [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<ResultWrapper<ForkchoiceUpdatedV1Result>> engine_forkchoiceUpdatedV2(ForkchoiceStateV1 forkchoiceState, PayloadAttributes? payloadAttributes = null); + [JsonRpcMethod( Description = "Returns an array of execution payload bodies for the list of provided block hashes.", IsSharable = true, diff --git a/src/Nethermind/Nethermind.Merge.Plugin/InvalidChainTracker/InvalidBlockInterceptor.cs b/src/Nethermind/Nethermind.Merge.Plugin/InvalidChainTracker/InvalidBlockInterceptor.cs index d022de5c386..aeb77e8cda5 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/InvalidChainTracker/InvalidBlockInterceptor.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/InvalidChainTracker/InvalidBlockInterceptor.cs @@ -99,13 +99,41 @@ private static bool ShouldNotTrackInvalidation(BlockHeader header) return !HeaderValidator.ValidateHash(header); } + public bool ValidateWithdrawals(Block block, out string? error) + { + bool result = _baseValidator.ValidateWithdrawals(block, out error); + + if (!result) + { + if (_logger.IsTrace) _logger.Trace($"Intercepted a bad block {block}"); + + if (ShouldNotTrackInvalidation(block.Header)) + { + if (_logger.IsDebug) _logger.Debug($"Block invalidation should not be tracked"); + + return false; + } + + _invalidChainTracker.OnInvalidBlock(block.Hash!, block.ParentHash); + } + + _invalidChainTracker.SetChildParent(block.Hash!, block.ParentHash!); + + return result; + } + private static bool ShouldNotTrackInvalidation(Block block) { - if (ShouldNotTrackInvalidation(block.Header)) return true; + if (ShouldNotTrackInvalidation(block.Header)) + return true; // Body does not match header, but it does not mean the hash that the header point to is invalid. - if (!BlockValidator.ValidateTxRootMatchesTxs(block, out Keccak _)) return true; - if (!BlockValidator.ValidateUnclesHashMatches(block)) return true; - return false; + if (!BlockValidator.ValidateTxRootMatchesTxs(block, out Keccak _)) + return true; + + if (!BlockValidator.ValidateUnclesHashMatches(block, out Keccak _)) + return true; + + return !BlockValidator.ValidateWithdrawalsHashMatches(block, out Keccak _); } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index 43d08720132..96dde7c7778 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -16,7 +16,6 @@ using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Validators; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Core.Exceptions; using Nethermind.Db; using Nethermind.Facade.Proxy; @@ -26,8 +25,6 @@ using Nethermind.Merge.Plugin.BlockProduction; using Nethermind.Merge.Plugin.BlockProduction.Boost; using Nethermind.Merge.Plugin.Handlers; -using Nethermind.Merge.Plugin.Handlers.V1; -using Nethermind.Merge.Plugin.Handlers.V2; using Nethermind.Merge.Plugin.InvalidChainTracker; using Nethermind.Merge.Plugin.Synchronization; using Nethermind.Synchronization.ParallelSync; @@ -307,7 +304,7 @@ public Task InitRpcModules() IEngineRpcModule engineRpcModule = new EngineRpcModule( new GetPayloadV1Handler(payloadPreparationService, _api.LogManager), new GetPayloadV2Handler(payloadPreparationService, _api.LogManager), - new NewPayloadV1Handler( + new NewPayloadHandler( _api.BlockValidator, _api.BlockTree, _api.Config<IInitConfig>(), @@ -321,7 +318,7 @@ public Task InitRpcModules() _beaconSync, _api.SpecProvider, _api.LogManager), - new ForkchoiceUpdatedV1Handler( + new ForkchoiceUpdatedHandler( _api.BlockTree, _blockFinalizationManager, _poSSwitcher, @@ -332,6 +329,7 @@ public Task InitRpcModules() _beaconSync, _beaconPivot, _peerRefresher, + _api.SpecProvider, _api.LogManager), new ExecutionStatusHandler(_api.BlockTree), new GetPayloadBodiesByHashV1Handler(_api.BlockTree, _api.LogManager), diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/ChainLevelHelper.cs b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/ChainLevelHelper.cs index c6dba1f3b89..12cd07b8936 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/ChainLevelHelper.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/ChainLevelHelper.cs @@ -159,7 +159,7 @@ public bool TrySetNextBlocks(int maxCount, BlockDownloadContext context) { Block? block = _blockTree.FindBlock(hashesToRequest[i], BlockTreeLookupOptions.None); if (block is null) return false; - BlockBody blockBody = new(block.Transactions, block.Uncles); + BlockBody blockBody = new(block.Transactions, block.Uncles, block?.Withdrawals); context.SetBody(i + offset, blockBody); } diff --git a/src/Nethermind/Nethermind.Mev.Test/MevRpcModuleTests.TestMevRpcBlockchain.cs b/src/Nethermind/Nethermind.Mev.Test/MevRpcModuleTests.TestMevRpcBlockchain.cs index 61a3183fd9a..8c4f1ef544c 100644 --- a/src/Nethermind/Nethermind.Mev.Test/MevRpcModuleTests.TestMevRpcBlockchain.cs +++ b/src/Nethermind/Nethermind.Mev.Test/MevRpcModuleTests.TestMevRpcBlockchain.cs @@ -16,6 +16,7 @@ using Nethermind.Consensus.Test; using Nethermind.Consensus.Transactions; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/BlockBodiesMessageSerializerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/BlockBodiesMessageSerializerTests.cs index ca3feb2fbbc..a826e001176 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/BlockBodiesMessageSerializerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/BlockBodiesMessageSerializerTests.cs @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Collections.Generic; using Nethermind.Core; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; @@ -8,31 +10,66 @@ using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; using NUnit.Framework; -namespace Nethermind.Network.Test.P2P.Subprotocols.Eth.V62 +namespace Nethermind.Network.Test.P2P.Subprotocols.Eth.V62; + +[TestFixture, Parallelizable(ParallelScope.All)] +public class BlockBodiesMessageSerializerTests { - [TestFixture, Parallelizable(ParallelScope.All)] - public class BlockBodiesMessageSerializerTests + [TestCaseSource(nameof(GetBlockBodyValues))] + public void Should_pass_roundtrip(BlockBody[] bodies) => SerializerTester.TestZero( + new BlockBodiesMessageSerializer(), + new BlockBodiesMessage { Bodies = bodies }); + + private static IEnumerable<BlockBody[]> GetBlockBodyValues() { - [Test] - public void Roundtrip() - { - BlockHeader header = Build.A.BlockHeader.TestObject; - Address to = Build.An.Address.FromNumber(1).TestObject; - Transaction tx = Build.A.Transaction.WithTo(to).SignedAndResolved(new EthereumEcdsa(ChainId.Ropsten, LimboLogs.Instance), TestItem.PrivateKeyA).TestObject; - tx.SenderAddress = null; - BlockBodiesMessage message = new(); - message.Bodies = new[] { new BlockBody(new[] { tx }, new[] { header }) }; - - var serializer = new BlockBodiesMessageSerializer(); - SerializerTester.TestZero(serializer, message); - } - - [Test] - public void Roundtrip_with_nulls() + var header = Build.A.BlockHeader.TestObject; + var tx = Build.A.Transaction + .WithTo(TestItem.AddressA) + .SignedAndResolved(new EthereumEcdsa(ChainId.Ropsten, LimboLogs.Instance), TestItem.PrivateKeyA) + .TestObject; + + tx.SenderAddress = null; + + // null body + yield return new BlockBody[] { null }; + + // body with null withdrawals + yield return new BlockBody[] { new(new[] { tx }, Array.Empty<BlockHeader>(), null) }; + + yield return new BlockBody[] { - BlockBodiesMessage message = new() { Bodies = new BlockBody[1] { null } }; - var serializer = new BlockBodiesMessageSerializer(); - SerializerTester.TestZero(serializer, message); - } + // body with emtpy withdrawals + new(new[] { tx }, new[] { header }, Array.Empty<Withdrawal>()), + // body with a single withdrawals + new(new[] { tx }, Array.Empty<BlockHeader>(), + new[] + { + Build.A.Withdrawal + .WithIndex(1) + .WithAmount(1) + .WithRecipient(TestItem.AddressA) + .TestObject + }), + // body with multiple withdrawals + new(new[] { tx }, new[] { header }, + new[] + { + Build.A.Withdrawal + .WithIndex(1) + .WithAmount(1) + .WithRecipient(TestItem.AddressA) + .TestObject, + Build.A.Withdrawal + .WithIndex(2) + .WithAmount(2) + .WithRecipient(TestItem.AddressB) + .TestObject, + Build.A.Withdrawal + .WithIndex(3) + .WithAmount(3) + .WithRecipient(TestItem.AddressC) + .TestObject + }) + }; } } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs index 321b68ab82d..e80913253a4 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/BlockBodiesMessageSerializer.cs @@ -10,14 +10,13 @@ namespace Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages { public class BlockBodiesMessageSerializer : IZeroInnerMessageSerializer<BlockBodiesMessage> { - public byte[] Serialize(BlockBodiesMessage message) - { - return Rlp.Encode(message.Bodies.Select(b => b is null + public byte[] Serialize(BlockBodiesMessage message) => + Rlp.Encode(message.Bodies.Select(b => b is null ? Rlp.OfEmptySequence - : Rlp.Encode( - Rlp.Encode(b.Transactions), - Rlp.Encode(b.Uncles))).ToArray()).Bytes; - } + : b.Withdrawals != null + ? Rlp.Encode(Rlp.Encode(b.Transactions), Rlp.Encode(b.Uncles), Rlp.Encode(b.Withdrawals)) + : Rlp.Encode(Rlp.Encode(b.Transactions), Rlp.Encode(b.Uncles)) + ).ToArray()).Bytes; public void Serialize(IByteBuffer byteBuffer, BlockBodiesMessage message) { @@ -45,6 +44,7 @@ public static BlockBodiesMessage Deserialize(RlpStream rlpStream) message.Bodies = rlpStream.DecodeArray(ctx => { int sequenceLength = rlpStream.ReadSequenceLength(); + int startingPosition = rlpStream.Position; if (sequenceLength == 0) { return null; @@ -54,7 +54,13 @@ public static BlockBodiesMessage Deserialize(RlpStream rlpStream) // (just on these delegates) Transaction[] transactions = rlpStream.DecodeArray(_ => Rlp.Decode<Transaction>(ctx)); BlockHeader[] uncles = rlpStream.DecodeArray(_ => Rlp.Decode<BlockHeader>(ctx)); - return new BlockBody(transactions, uncles); + Withdrawal[]? withdrawals = null; + if (rlpStream.ReadNumberOfItemsRemaining(startingPosition + sequenceLength, 1) > 0) + { + withdrawals = rlpStream.DecodeArray(_ => Rlp.Decode<Withdrawal>(ctx)); + } + + return new BlockBody(transactions, uncles, withdrawals); }, false); return message; diff --git a/src/Nethermind/Nethermind.Runner/NLog.config b/src/Nethermind/Nethermind.Runner/NLog.config index 72f451d4f18..411c0df4536 100644 --- a/src/Nethermind/Nethermind.Runner/NLog.config +++ b/src/Nethermind/Nethermind.Runner/NLog.config @@ -121,6 +121,9 @@ <!-- for a detailed pruning analysis --> <!-- <logger name="Trie.*" minlevel="Trace" writeTo="all" final="true" /> --> + <!-- for JsonRpc responses --> + <!-- <logger name="JsonRpc.JsonRpcService" minlevel="Trace" writeTo="all" final="true" /> --> + <!-- note: minLevel will get replaced by `Seq.MinLevel` --> <logger name="*" minlevel="Off" writeTo="seq" /> <logger name="*" minlevel="Info" writeTo="file-async" /> diff --git a/src/Nethermind/Nethermind.Runner/Properties/launchSettings.json b/src/Nethermind/Nethermind.Runner/Properties/launchSettings.json index 5c57389fdd6..10dc50360ca 100644 --- a/src/Nethermind/Nethermind.Runner/Properties/launchSettings.json +++ b/src/Nethermind/Nethermind.Runner/Properties/launchSettings.json @@ -273,6 +273,20 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } + }, + "Withdrawals (Devnet)": { + "commandName": "Project", + "commandLineArgs": "-c withdrawals_devnet -dd D:\\", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Withdrawals (Hive tests)": { + "commandName": "Project", + "commandLineArgs": "-c withdrawals_hivetests -dd %NETHERMIND_DATA_DIR% --Init.DiagnosticMode MemDb", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } } } } diff --git a/src/Nethermind/Nethermind.Runner/configs/withdrawals_devnet.cfg b/src/Nethermind/Nethermind.Runner/configs/withdrawals_devnet.cfg new file mode 100644 index 00000000000..36c428dd4d5 --- /dev/null +++ b/src/Nethermind/Nethermind.Runner/configs/withdrawals_devnet.cfg @@ -0,0 +1,62 @@ +{ + "Init": { + "IsMining": true, + "WebSocketsEnabled": true, + "StoreReceipts": true, + "ChainSpecPath": "chainspec/withdrawals_devnet.json", + "BaseDbPath": "nethermind_db/withdrawals_devnet", + "LogFileName": "withdrawals_devnet.log", + "MemoryHint": 768000000, + "EnableUnsecuredDevWallet": false + }, + "Network": { + "DiscoveryPort": 30303, + "P2PPort": 30303, + "EnableUPnP": true + }, + "TxPool": { + "Size": 2048 + }, + "JsonRpc": { + "Enabled": true, + "Timeout": 200000000, + "Host": "127.0.0.1", + "Port": 8545, + "EnabledModules": ["Eth", "Subscribe", "Trace", "TxPool", "Web3", "Personal", "Proof", "Net", "Parity", "Health", "Debug"], + "AdditionalRpcUrls": [ + "http://localhost:8551|http;ws|net;eth;subscribe;engine;web3;client|no-auth" + ] + }, + "Db": { + "CacheIndexAndFilterBlocks": false + }, + "Sync": { + "FastSync": false + }, + "EthStats": { + "Enabled": false, + "Server": "ws://localhost:3000/api", + "Name": "Nethermind shandong", + "Secret": "secret", + "Contact": "hello@nethermind.io" + }, + "Metrics": { + "NodeName": "WithdrawalTest", + "Enabled": false, + "PushGatewayUrl": "http://localhost:9091/metrics", + "IntervalSeconds": 5 + }, + "Bloom": { + "IndexLevelBucketSizes": [ + 16, + 16, + 16 + ] + }, + "Pruning": { + "Mode": "None" + }, + "Mining": { + "ExtraData": "" + } +} diff --git a/src/Nethermind/Nethermind.Runner/configs/withdrawals_hivetests.cfg b/src/Nethermind/Nethermind.Runner/configs/withdrawals_hivetests.cfg new file mode 100644 index 00000000000..5b73386ae51 --- /dev/null +++ b/src/Nethermind/Nethermind.Runner/configs/withdrawals_hivetests.cfg @@ -0,0 +1,66 @@ +{ + "Init": { + "IsMining": true, + "WebSocketsEnabled": true, + "StoreReceipts": true, + "ChainSpecPath": "chainspec/withdrawals_hivetests.json", + "BaseDbPath": "nethermind_db/withdrawals_hivetests", + "LogFileName": "withdrawals_hivetests.log", + "MemoryHint": 768000000, + "EnableUnsecuredDevWallet": false + }, + "Network": { + "DiscoveryPort": 30303, + "P2PPort": 30303, + "EnableUPnP": true + }, + "TxPool": { + "Size": 2048 + }, + "JsonRpc": { + "Enabled": true, + "Timeout": 20000, + "Host": "127.0.0.1", + "Port": 8545, + "EnabledModules": ["Eth", "Subscribe", "Trace", "TxPool", "Web3", "Personal", "Proof", "Net", "Parity", "Health"], + "AdditionalRpcUrls": [ + "http://localhost:8551|http;ws|net;eth;subscribe;engine;web3;client|no-auth" + ] + }, + "Db": { + "CacheIndexAndFilterBlocks": false + }, + "Sync": { + "FastSync": false + }, + "EthStats": { + "Enabled": false, + "Server": "ws://localhost:3000/api", + "Name": "Nethermind shandong", + "Secret": "secret", + "Contact": "hello@nethermind.io" + }, + "Metrics": { + "NodeName": "WithdrawalTest", + "Enabled": false, + "PushGatewayUrl": "http://localhost:9091/metrics", + "IntervalSeconds": 5 + }, + "Bloom": { + "IndexLevelBucketSizes": [ + 16, + 16, + 16 + ] + }, + "Pruning": { + "Mode": "None" + }, + "Merge": { + "Enabled": true, + "TerminalTotalDifficulty": "196608" + }, + "Mining": { + "ExtraData": "" + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs index 0c2dd4a9286..1a1b025a180 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; +using System.Linq; using Nethermind.Core; namespace Nethermind.Serialization.Rlp @@ -10,6 +11,7 @@ public class BlockDecoder : IRlpValueDecoder<Block>, IRlpStreamDecoder<Block> { private readonly HeaderDecoder _headerDecoder = new(); private readonly TxDecoder _txDecoder = new(); + private readonly WithdrawalDecoder _withdrawalDecoder = new(); public Block? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { @@ -49,15 +51,31 @@ public class BlockDecoder : IRlpValueDecoder<Block>, IRlpStreamDecoder<Block> rlpStream.Check(unclesCheck); + List<Withdrawal> withdrawals = null; + + if (header.Timestamp >= HeaderDecoder.WithdrawalTimestamp) + { + int withdrawalsLength = rlpStream.ReadSequenceLength(); + int withdrawalsCheck = rlpStream.Position + withdrawalsLength; + withdrawals = new(); + + while (rlpStream.Position < withdrawalsCheck) + { + withdrawals.Add(Rlp.Decode<Withdrawal>(rlpStream)); + } + + rlpStream.Check(withdrawalsCheck); + } + if ((rlpBehaviors & RlpBehaviors.AllowExtraData) != RlpBehaviors.AllowExtraData) { rlpStream.Check(blockCheck); } - return new Block(header, transactions, uncleHeaders); + return new(header, transactions, uncleHeaders, withdrawals); } - private (int Total, int Txs, int Uncles) GetContentLength(Block item, RlpBehaviors rlpBehaviors) + private (int Total, int Txs, int Uncles, int? Withdrawals) GetContentLength(Block item, RlpBehaviors rlpBehaviors) { int contentLength = _headerDecoder.GetLength(item.Header, rlpBehaviors); @@ -67,7 +85,16 @@ public class BlockDecoder : IRlpValueDecoder<Block>, IRlpStreamDecoder<Block> int unclesLength = GetUnclesLength(item, rlpBehaviors); contentLength += Rlp.LengthOfSequence(unclesLength); - return (contentLength, txLength, unclesLength); + int? withdrawalsLength = null; + if (item.Header.Timestamp >= HeaderDecoder.WithdrawalTimestamp) + { + withdrawalsLength = GetWithdrawalsLength(item, rlpBehaviors); + + if (withdrawalsLength.HasValue) + contentLength += Rlp.LengthOfSequence(withdrawalsLength.Value); + } + + return (contentLength, txLength, unclesLength, withdrawalsLength); } private int GetUnclesLength(Block item, RlpBehaviors rlpBehaviors) @@ -92,6 +119,21 @@ private int GetTxLength(Block item, RlpBehaviors rlpBehaviors) return txLength; } + private int? GetWithdrawalsLength(Block item, RlpBehaviors rlpBehaviors) + { + if (item.Withdrawals is null) + return null; + + var withdrawalLength = 0; + + for (int i = 0, count = item.Withdrawals.Length; i < count; i++) + { + withdrawalLength += _withdrawalDecoder.GetLength(item.Withdrawals[i], rlpBehaviors); + } + + return withdrawalLength; + } + public int GetLength(Block? item, RlpBehaviors rlpBehaviors) { if (item is null) @@ -135,12 +177,28 @@ public int GetLength(Block? item, RlpBehaviors rlpBehaviors) decoderContext.Check(unclesCheck); + List<Withdrawal> withdrawals = null; + + if (header.Timestamp >= HeaderDecoder.WithdrawalTimestamp) + { + int withdrawalsLength = decoderContext.ReadSequenceLength(); + int withdrawalsCheck = decoderContext.Position + withdrawalsLength; + withdrawals = new(); + + while (decoderContext.Position < withdrawalsCheck) + { + withdrawals.Add(Rlp.Decode<Withdrawal>(ref decoderContext)); + } + + decoderContext.Check(withdrawalsCheck); + } + if ((rlpBehaviors & RlpBehaviors.AllowExtraData) != RlpBehaviors.AllowExtraData) { decoderContext.Check(blockCheck); } - return new Block(header, transactions, uncleHeaders); + return new(header, transactions, uncleHeaders, withdrawals); } public Rlp Encode(Block? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) @@ -152,7 +210,7 @@ public Rlp Encode(Block? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) RlpStream rlpStream = new(GetLength(item, rlpBehaviors)); Encode(rlpStream, item, rlpBehaviors); - return new Rlp(rlpStream.Data); + return new(rlpStream.Data); } public void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) @@ -163,7 +221,7 @@ public void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehaviors = Rl return; } - (int contentLength, int txsLength, int unclesLength) = GetContentLength(item, rlpBehaviors); + (int contentLength, int txsLength, int unclesLength, int? withdrawalsLength) = GetContentLength(item, rlpBehaviors); stream.StartSequence(contentLength); stream.Encode(item.Header); stream.StartSequence(txsLength); @@ -177,6 +235,16 @@ public void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehaviors = Rl { stream.Encode(item.Uncles[i]); } + + if (withdrawalsLength.HasValue) + { + stream.StartSequence(withdrawalsLength.Value); + + for (int i = 0; i < item.Withdrawals.Length; i++) + { + stream.Encode(item.Withdrawals[i]); + } + } } } } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs index ab7fcb70312..ac6d1945024 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs @@ -14,6 +14,7 @@ public class HeaderDecoder : IRlpValueDecoder<BlockHeader>, IRlpStreamDecoder<Bl // This would help with EIP1559 as well and could generally setup proper coders automatically, hmm // but then RLP would have to be passed into so many places public static long Eip1559TransitionBlock = long.MaxValue; + public static ulong WithdrawalTimestamp = ulong.MaxValue; public BlockHeader? Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) @@ -75,6 +76,12 @@ public class HeaderDecoder : IRlpValueDecoder<BlockHeader>, IRlpStreamDecoder<Bl blockHeader.BaseFeePerGas = decoderContext.DecodeUInt256(); } + if (decoderContext.ReadNumberOfItemsRemaining(null, 1) > 0 && + decoderContext.PeekPrefixAndContentLength().ContentLength == Keccak.Size) + { + blockHeader.WithdrawalsRoot = decoderContext.DecodeKeccak(); + } + if ((rlpBehaviors & RlpBehaviors.AllowExtraData) != RlpBehaviors.AllowExtraData) { decoderContext.Check(headerCheck); @@ -143,6 +150,12 @@ public class HeaderDecoder : IRlpValueDecoder<BlockHeader>, IRlpStreamDecoder<Bl blockHeader.BaseFeePerGas = rlpStream.DecodeUInt256(); } + if (rlpStream.ReadNumberOfItemsRemaining(null, 1) > 0 && + rlpStream.PeekPrefixAndContentLength().ContentLength == Keccak.Size) + { + blockHeader.WithdrawalsRoot = rlpStream.DecodeKeccak(); + } + if ((rlpBehaviors & RlpBehaviors.AllowExtraData) != RlpBehaviors.AllowExtraData) { rlpStream.Check(headerCheck); @@ -194,6 +207,11 @@ public void Encode(RlpStream rlpStream, BlockHeader? header, RlpBehaviors rlpBeh { rlpStream.Encode(header.BaseFeePerGas); } + + if (header.WithdrawalsRoot is not null) + { + rlpStream.Encode(header.WithdrawalsRoot); + } } public Rlp Encode(BlockHeader? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) @@ -231,7 +249,8 @@ private static int GetContentLength(BlockHeader? item, RlpBehaviors rlpBehaviors + Rlp.LengthOf(item.GasUsed) + Rlp.LengthOf(item.Timestamp) + Rlp.LengthOf(item.ExtraData) - + (item.Number < Eip1559TransitionBlock ? 0 : Rlp.LengthOf(item.BaseFeePerGas)); + + (item.Number < Eip1559TransitionBlock ? 0 : Rlp.LengthOf(item.BaseFeePerGas)) + + (item.WithdrawalsRoot is null ? 0 : Rlp.LengthOf(item.WithdrawalsRoot)); if (notForSealing) { diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs index 01dd0fea0da..0bc0f813b14 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs @@ -749,7 +749,7 @@ public bool IsSequenceNext() return Data[Position] >= 192; } - public int ReadNumberOfItemsRemaining(int? beforePosition = null) + public int ReadNumberOfItemsRemaining(int? beforePosition = null, int maxSearch = int.MaxValue) { int positionStored = Position; int numberOfItems = 0; @@ -783,6 +783,10 @@ public int ReadNumberOfItemsRemaining(int? beforePosition = null) } numberOfItems++; + if (numberOfItems >= maxSearch) + { + break; + } } Position = positionStored; diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpDecoderExtensions.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpDecoderExtensions.cs index e0188ced7b5..d0614f39b90 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/RlpDecoderExtensions.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpDecoderExtensions.cs @@ -49,7 +49,7 @@ public static Rlp Encode<T>(this IRlpObjectDecoder<T> decoder, T?[]? items, RlpB public static Rlp Encode<T>(this IRlpObjectDecoder<T> decoder, IReadOnlyCollection<T?>? items, RlpBehaviors behaviors = RlpBehaviors.None) { - if (items == null) + if (items is null) { return Rlp.OfEmptySequence; } @@ -58,7 +58,7 @@ public static Rlp Encode<T>(this IRlpObjectDecoder<T> decoder, IReadOnlyCollecti int i = 0; foreach (T? item in items) { - rlpSequence[i++] = item == null ? Rlp.OfEmptySequence : decoder.Encode(item, behaviors); + rlpSequence[i++] = item is null ? Rlp.OfEmptySequence : decoder.Encode(item, behaviors); } return Rlp.Encode(rlpSequence); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs index ba570512ddf..e2ff117f7ea 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs @@ -19,6 +19,7 @@ public class RlpStream private static readonly BlockDecoder _blockDecoder = new(); private static readonly TxDecoder _txDecoder = new(); private static readonly ReceiptMessageDecoder _receiptDecoder = new(); + private static readonly WithdrawalDecoder _withdrawalDecoder = new(); private static readonly LogEntryDecoder _logEntryDecoder = LogEntryDecoder.Instance; protected RlpStream() @@ -59,6 +60,8 @@ public void Encode(TxReceipt value) _receiptDecoder.Encode(this, value); } + public void Encode(Withdrawal value) => _withdrawalDecoder.Encode(this, value); + public void Encode(LogEntry value) { _logEntryDecoder.Encode(this, value); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/WithdrawalDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/WithdrawalDecoder.cs new file mode 100644 index 00000000000..bf1fc751a0f --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/WithdrawalDecoder.cs @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Serialization.Rlp; + +public class WithdrawalDecoder : IRlpStreamDecoder<Withdrawal>, IRlpValueDecoder<Withdrawal> +{ + public Withdrawal? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (rlpStream.IsNextItemNull()) + { + rlpStream.ReadByte(); + + return null; + } + + rlpStream.ReadSequenceLength(); + + return new() + { + Index = rlpStream.DecodeULong(), + ValidatorIndex = rlpStream.DecodeULong(), + Address = rlpStream.DecodeAddress(), + Amount = rlpStream.DecodeUInt256() + }; + } + + public Withdrawal? Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (decoderContext.IsNextItemNull()) + { + decoderContext.ReadByte(); + + return null; + } + + decoderContext.ReadSequenceLength(); + + return new() + { + Index = decoderContext.DecodeULong(), + ValidatorIndex = decoderContext.DecodeULong(), + Address = decoderContext.DecodeAddress(), + Amount = decoderContext.DecodeUInt256() + }; + } + + public void Encode(RlpStream stream, Withdrawal? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + { + stream.EncodeNullObject(); + return; + } + + var contentLength = GetContentLength(item); + + stream.StartSequence(contentLength); + stream.Encode(item.Index); + stream.Encode(item.ValidatorIndex); + stream.Encode(item.Address); + stream.Encode(item.Amount); + } + + public Rlp Encode(Withdrawal? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + var stream = new RlpStream(GetLength(item, rlpBehaviors)); + + Encode(stream, item, rlpBehaviors); + + return new(stream.Data); + } + + private static int GetContentLength(Withdrawal item) => + Rlp.LengthOf(item.Index) + + Rlp.LengthOf(item.ValidatorIndex) + + Rlp.LengthOfAddressRlp + + Rlp.LengthOf(item.Amount); + + public int GetLength(Withdrawal item, RlpBehaviors _) => Rlp.LengthOfSequence(GetContentLength(item)); +} diff --git a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs index 47663fc8e68..424f6a85974 100644 --- a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs +++ b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs @@ -15,7 +15,6 @@ using Nethermind.Specs.ChainSpecStyle; using Nethermind.Specs.Forks; using NSubstitute; -using NSubstitute.Extensions; using NUnit.Framework; namespace Nethermind.Specs.Test.ChainSpecStyle @@ -339,7 +338,8 @@ private static void CompareSpecs(IReleaseSpec expectedSpec, IReleaseSpec ActualS p.Name != nameof(IReleaseSpec.DifficultyBombDelay)) .Where(p => isMainnet || checkDifficultyBomb || p.Name != nameof(IReleaseSpec.DifficultyBoundDivisor)) - .Where(p => p.Name != nameof(IReleaseSpec.Eip1559TransitionBlock))) + .Where(p => p.Name != nameof(IReleaseSpec.Eip1559TransitionBlock)) + .Where(p => p.Name != nameof(IReleaseSpec.WithdrawalTimestamp))) { Assert.AreEqual(propertyInfo.GetValue(expectedSpec), propertyInfo.GetValue(ActualSpec), activation + "." + propertyInfo.Name); @@ -611,6 +611,7 @@ void TestTransitions(ForkActivation activation, Action<ReleaseSpec> changes) r.Eip1559TransitionBlock = 15590L; r.IsTimeAdjustmentPostOlympic = true; r.MaximumUncleCount = 2; + r.WithdrawalTimestamp = ulong.MaxValue; }); TestTransitions((ForkActivation)1L, r => diff --git a/src/Nethermind/Nethermind.Specs.Test/Nethermind.Specs.Test.csproj b/src/Nethermind/Nethermind.Specs.Test/Nethermind.Specs.Test.csproj index d7162cffc28..b041db3be4c 100644 --- a/src/Nethermind/Nethermind.Specs.Test/Nethermind.Specs.Test.csproj +++ b/src/Nethermind/Nethermind.Specs.Test/Nethermind.Specs.Test.csproj @@ -8,8 +8,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="NSubstitute" Version="4.3.0" /> <PackageReference Include="FluentAssertions" Version="6.8.0" /> + <PackageReference Include="NSubstitute" Version="4.4.0" /> <PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit3TestAdapter" Version="4.3.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" /> diff --git a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs index e6ba18f1276..4487638c062 100644 --- a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs @@ -137,5 +137,7 @@ public Address? Eip1559FeeCollector public bool IsEip3651Enabled => _spec.IsEip3651Enabled; public bool IsEip3855Enabled => _spec.IsEip3855Enabled; public bool IsEip3860Enabled => _spec.IsEip3860Enabled; + public bool IsEip4895Enabled => _spec.IsEip4895Enabled; + public ulong WithdrawalTimestamp => _spec.WithdrawalTimestamp; } } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs index 31e3faab5fb..8e2d63b6691 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs @@ -108,15 +108,17 @@ public class ChainParameters public UInt256? TerminalTotalDifficulty { get; set; } /// <summary> - /// this feild will indicate the timestamp at which this EIP1153 will be enabled. + /// this field will indicate the timestamp at which this EIP1153 will be enabled. /// </summary> public ulong? Eip1153TransitionTimestamp { get; set; } /// <summary> - /// this feild will indicate the timestamp at which this EIP3651 will be enabled. + /// this field will indicate the timestamp at which this EIP3651 will be enabled. /// </summary> public ulong? Eip3651TransitionTimestamp { get; set; } public ulong? Eip3855TransitionTimestamp { get; set; } public ulong? Eip3860TransitionTimestamp { get; set; } + + public ulong? Eip4895TransitionTimestamp { get; set; } } } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs index 193d2f277c3..8781236f24d 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs @@ -228,6 +228,9 @@ private static ReleaseSpec CreateReleaseSpec(ChainSpec chainSpec, long releaseSt releaseSpec.IsEip3651Enabled = (chainSpec.Parameters.Eip3651TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; releaseSpec.IsEip3855Enabled = (chainSpec.Parameters.Eip3855TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; releaseSpec.IsEip3860Enabled = (chainSpec.Parameters.Eip3860TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; + releaseSpec.IsEip4895Enabled = (chainSpec.Parameters.Eip4895TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; + releaseSpec.WithdrawalTimestamp = chainSpec.Parameters.Eip4895TransitionTimestamp ?? ulong.MaxValue; + return releaseSpec; } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs index cba5ca82c31..f3e081e2984 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs @@ -19,7 +19,7 @@ namespace Nethermind.Specs.ChainSpecStyle { /// <summary> - /// This class can load a Parity-style chain spec file and build a <see cref="ChainSpec"/> out of it. + /// This class can load a Parity-style chain spec file and build a <see cref="ChainSpec"/> out of it. /// </summary> public class ChainSpecLoader : IChainSpecLoader { @@ -137,6 +137,7 @@ private void LoadParameters(ChainSpecJson chainSpecJson, ChainSpec chainSpec) Eip3651TransitionTimestamp = chainSpecJson.Params.Eip3651TransitionTimestamp, Eip3855TransitionTimestamp = chainSpecJson.Params.Eip3855TransitionTimestamp, Eip3860TransitionTimestamp = chainSpecJson.Params.Eip3860TransitionTimestamp, + Eip4895TransitionTimestamp = chainSpecJson.Params.Eip4895TransitionTimestamp, TransactionPermissionContract = chainSpecJson.Params.TransactionPermissionContract, TransactionPermissionContractTransition = chainSpecJson.Params.TransactionPermissionContractTransition, ValidateChainIdTransition = chainSpecJson.Params.ValidateChainIdTransition, @@ -352,7 +353,6 @@ private static void LoadGenesis(ChainSpecJson chainSpecJson, ChainSpec chainSpec ? (chainSpecJson.Genesis.BaseFeePerGas ?? Eip1559Constants.DefaultForkBaseFee) : UInt256.Zero; - BlockHeader genesisHeader = new( parentHash, Keccak.OfAnEmptySequenceRlp, @@ -372,12 +372,17 @@ private static void LoadGenesis(ChainSpecJson chainSpecJson, ChainSpec chainSpec genesisHeader.StateRoot = Keccak.EmptyTreeHash; genesisHeader.TxRoot = Keccak.EmptyTreeHash; genesisHeader.BaseFeePerGas = baseFee; + bool withdrawalsEnabled = chainSpecJson.Params.Eip4895TransitionTimestamp != null && genesisHeader.Timestamp >= chainSpecJson.Params.Eip4895TransitionTimestamp; + if (withdrawalsEnabled) + genesisHeader.WithdrawalsRoot = Keccak.EmptyTreeHash; genesisHeader.AuRaStep = step; genesisHeader.AuRaSignature = auRaSignature; - - chainSpec.Genesis = new Block(genesisHeader); + if (withdrawalsEnabled) + chainSpec.Genesis = new Block(genesisHeader, Array.Empty<Transaction>(), Array.Empty<BlockHeader>(), Array.Empty<Withdrawal>()); + else + chainSpec.Genesis = new Block(genesisHeader); } private static void LoadAllocations(ChainSpecJson chainSpecJson, ChainSpec chainSpec) diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs index a550fda673b..9fefc4c9b37 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs @@ -100,7 +100,7 @@ internal class ChainSpecParamsJson public long? Eip3541Transition { get; set; } - // We explicitly want this to be enabled by default on all the networks + // We explicitly want this to be enabled by default on all the networks // we can disable it if needed, but its expected not to cause issues public long? Eip3607Transition { get; set; } = 0; @@ -137,5 +137,6 @@ internal class ChainSpecParamsJson public ulong? Eip3651TransitionTimestamp { get; set; } public ulong? Eip3855TransitionTimestamp { get; set; } public ulong? Eip3860TransitionTimestamp { get; set; } + public ulong? Eip4895TransitionTimestamp { get; set; } } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/15_Shanghai.cs b/src/Nethermind/Nethermind.Specs/Forks/15_Shanghai.cs index 74e159921f6..04e27e3eb38 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/15_Shanghai.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/15_Shanghai.cs @@ -16,6 +16,7 @@ protected Shanghai() IsEip3651Enabled = true; IsEip3855Enabled = true; IsEip3860Enabled = true; + IsEip4895Enabled = true; } public new static IReleaseSpec Instance => LazyInitializer.EnsureInitialized(ref _instance, () => new Shanghai()); diff --git a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs index 08b2a4587ff..6f56bd217af 100644 --- a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs @@ -69,11 +69,13 @@ public ReleaseSpec Clone() public bool ValidateChainId { get; set; } public bool ValidateReceipts { get; set; } public long Eip1559TransitionBlock { get; set; } + public ulong WithdrawalTimestamp { get; set; } public Address Eip1559FeeCollector { get; set; } public UInt256? Eip1559BaseFeeMinValue { get; set; } public bool IsEip1153Enabled { get; set; } public bool IsEip3651Enabled { get; set; } public bool IsEip3855Enabled { get; set; } public bool IsEip3860Enabled { get; set; } + public bool IsEip4895Enabled { get; set; } } } diff --git a/src/Nethermind/Nethermind.Specs/SystemTransactionReleaseSpec.cs b/src/Nethermind/Nethermind.Specs/SystemTransactionReleaseSpec.cs index 38e4c405473..69becc7f9e5 100644 --- a/src/Nethermind/Nethermind.Specs/SystemTransactionReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs/SystemTransactionReleaseSpec.cs @@ -115,11 +115,13 @@ public bool IsEip158IgnoredAccount(Address address) } public long Eip1559TransitionBlock => _spec.Eip1559TransitionBlock; + public ulong WithdrawalTimestamp => _spec.WithdrawalTimestamp; public Address Eip1559FeeCollector => _spec.Eip1559FeeCollector; public bool IsEip1153Enabled => _spec.IsEip1153Enabled; public bool IsEip3651Enabled => _spec.IsEip3651Enabled; public bool IsEip3855Enabled => _spec.IsEip3855Enabled; public bool IsEip3860Enabled => _spec.IsEip3860Enabled; + public bool IsEip4895Enabled => _spec.IsEip4895Enabled; } } diff --git a/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs b/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs new file mode 100644 index 00000000000..5287eeeb44c --- /dev/null +++ b/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; +using Nethermind.Db; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; +using Nethermind.State.Proofs; +using Nethermind.Trie; + +namespace Nethermind.State.Trie; + +/// <summary> +/// An abstract class that represents a Patricia trie built of a collection of <see cref="T"/>. +/// </summary> +/// <typeparam name="T">The type of the elements in the collection used to build the trie.</typeparam> +public abstract class PatriciaTrie<T> : PatriciaTree +{ + /// <param name="list">The collection to build the trie of.</param> + /// <param name="canBuildProof"> + /// <c>true</c> to maintain an in-memory database for proof computation; + /// otherwise, <c>false</c>. + /// </param> + public PatriciaTrie(IEnumerable<T>? list, bool canBuildProof) + : base(canBuildProof ? new MemDb() : NullDb.Instance, EmptyTreeHash, false, false, NullLogManager.Instance) + { + CanBuildProof = canBuildProof; + + if (list?.Any() ?? false) + { + Initialize(list); + UpdateRootHash(); + } + } + + /// <summary> + /// Computes the proofs for the index specified. + /// </summary> + /// <param name="index">The node index to compute the proof for.</param> + /// <returns>The array of the computed proofs.</returns> + /// <exception cref="NotSupportedException"></exception> + public virtual byte[][] BuildProof(int index) + { + if (!CanBuildProof) + throw new NotSupportedException("Building proofs not supported"); + + var proofCollector = new ProofCollector(Rlp.Encode(index).Bytes); + + Accept(proofCollector, RootHash, new() { ExpectAccounts = false }); + + return proofCollector.BuildResult(); + } + + protected abstract void Initialize(IEnumerable<T> list); + + protected virtual bool CanBuildProof { get; } +} diff --git a/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs b/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs index e71ff09806e..14296d471b0 100644 --- a/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs @@ -2,59 +2,48 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; +using System.Linq; using Nethermind.Core; -using Nethermind.Core.Extensions; using Nethermind.Core.Specs; -using Nethermind.Db; -using Nethermind.Logging; using Nethermind.Serialization.Rlp; -using Nethermind.Trie; +using Nethermind.State.Trie; -namespace Nethermind.State.Proofs +namespace Nethermind.State.Proofs; + +/// <summary> +/// Represents a Patricia trie built of a collection of <see cref="TxReceipt"/>. +/// </summary> +public class ReceiptTrie : PatriciaTrie<TxReceipt> { - public class ReceiptTrie : PatriciaTree + private static readonly ReceiptMessageDecoder _decoder = new(); + + /// <inheritdoc/> + /// <param name="receipts">The transaction receipts to build the trie of.</param> + public ReceiptTrie(IReceiptSpec spec, IEnumerable<TxReceipt> receipts, bool canBuildProof = false) + : base(null, canBuildProof) { - private readonly bool _allowProofs; - private static readonly ReceiptMessageDecoder Decoder = new(); + ArgumentNullException.ThrowIfNull(spec); + ArgumentNullException.ThrowIfNull(receipts); - public ReceiptTrie(IReceiptSpec receiptSpec, TxReceipt?[] txReceipts, bool allowProofs = false) - : base(allowProofs ? (IDb)new MemDb() : NullDb.Instance, EmptyTreeHash, false, false, NullLogManager.Instance) + if (receipts.Any()) { - _allowProofs = allowProofs; - if (txReceipts.Length == 0) - { - return; - } - - // 3% allocations (2GB) on a Goerli 3M blocks fast sync due to calling receipt encoder hee - // avoiding it would require pooling byte arrays and passing them as Spans to temporary trees - // a temporary trie would be a trie that exists to create a state root only and then be disposed of - for (int i = 0; i < txReceipts.Length; i++) - { - TxReceipt? currentReceipt = txReceipts[i]; - byte[] receiptRlp = Decoder.EncodeNew(currentReceipt, - (receiptSpec.IsEip658Enabled - ? RlpBehaviors.Eip658Receipts - : RlpBehaviors.None) | RlpBehaviors.SkipTypedWrapping); - - - Set(Rlp.Encode(i).Bytes, receiptRlp); - } - - // additional 3% 2GB is used here for trie nodes creation and root calculation + Initialize(receipts, spec); UpdateRootHash(); } + } - public byte[][] BuildProof(int index) + private void Initialize(IEnumerable<TxReceipt> receipts, IReceiptSpec spec) + { + var behavior = (spec.IsEip658Enabled ? RlpBehaviors.Eip658Receipts : RlpBehaviors.None) + | RlpBehaviors.SkipTypedWrapping; + var key = 0; + + foreach (var receipt in receipts) { - if (!_allowProofs) - { - throw new InvalidOperationException("Cannot build proofs without underlying DB (for now?)"); - } - - ProofCollector proofCollector = new(Rlp.Encode(index).Bytes); - Accept(proofCollector, RootHash, new VisitingOptions { ExpectAccounts = false }); - return proofCollector.BuildResult(); + Set(Rlp.Encode(key++).Bytes, _decoder.EncodeNew(receipt, behavior)); } } + + protected override void Initialize(IEnumerable<TxReceipt> list) => throw new NotSupportedException(); } diff --git a/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs b/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs index 04dfd33e569..4c9d0b54d3b 100644 --- a/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs @@ -4,58 +4,35 @@ using System; using System.Collections.Generic; using Nethermind.Core; -using Nethermind.Core.Extensions; -using Nethermind.Core.Specs; -using Nethermind.Db; -using Nethermind.Logging; using Nethermind.Serialization.Rlp; -using Nethermind.Trie; +using Nethermind.State.Trie; -namespace Nethermind.State.Proofs -{ - public class TxTrie : PatriciaTree - { - private readonly bool _allowMerkleProofConstructions; - private static readonly TxDecoder _txDecoder = new(); +namespace Nethermind.State.Proofs; - /// <summary> - /// Helper class used for calculation of tx roots for block headers. - /// </summary> - /// <param name="txs">Transactions to build a trie from.</param> - /// <param name="allowMerkleProofConstructions">Some tries do not need to be used for proof constructions. - /// In such cases we can avoid maintaining any in-memory databases.</param> - public TxTrie(IReadOnlyList<Transaction>? txs, bool allowMerkleProofConstructions = false) - : base(allowMerkleProofConstructions ? (IDb)new MemDb() : NullDb.Instance, EmptyTreeHash, false, false, NullLogManager.Instance) - { - _allowMerkleProofConstructions = allowMerkleProofConstructions; - if ((txs?.Count ?? 0) == 0) - { - return; - } +/// <summary> +/// Represents a Patricia trie built of a collection of <see cref="Transaction"/>. +/// </summary> +public class TxTrie : PatriciaTrie<Transaction> +{ + private static readonly TxDecoder _txDecoder = new(); - // 3% allocations (2GB) on a Goerli 3M blocks fast sync due to calling transaction encoder here - // Avoiding it would require pooling byte arrays and passing them as Spans to temporary trees - // a temporary trie would be a trie that exists to create a state root only and then be disposed of - for (int i = 0; i < txs.Count; i++) - { - Rlp transactionRlp = _txDecoder.Encode(txs[i], RlpBehaviors.SkipTypedWrapping); - Set(Rlp.Encode(i).Bytes, transactionRlp.Bytes); - } + /// <inheritdoc/> + /// <param name="transactions">The transactions to build the trie of.</param> + public TxTrie(IEnumerable<Transaction> transactions, bool canBuildProof = false) + : base(transactions, canBuildProof) => ArgumentNullException.ThrowIfNull(transactions); - // additional 3% 2GB is used here for trie nodes creation and root calculation - UpdateRootHash(); - } + protected override void Initialize(IEnumerable<Transaction> list) + { + var key = 0; - public byte[][] BuildProof(int index) + // 3% allocations (2GB) on a Goerli 3M blocks fast sync due to calling transaction encoder here + // Avoiding it would require pooling byte arrays and passing them as Spans to temporary trees + // a temporary trie would be a trie that exists to create a state root only and then be disposed of + foreach (var transaction in list) { - if (!_allowMerkleProofConstructions) - { - throw new InvalidOperationException("Cannot build proofs without underlying DB (for now?)"); - } + Rlp transactionRlp = _txDecoder.Encode(transaction, RlpBehaviors.SkipTypedWrapping); - ProofCollector proofCollector = new(Rlp.Encode(index).Bytes); - Accept(proofCollector, RootHash, new VisitingOptions { ExpectAccounts = false }); - return proofCollector.BuildResult(); + Set(Rlp.Encode(key++).Bytes, transactionRlp.Bytes); } } } diff --git a/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs b/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs new file mode 100644 index 00000000000..baa0f7a6dcf --- /dev/null +++ b/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.Serialization.Rlp; +using Nethermind.State.Trie; + +namespace Nethermind.State.Proofs; + +/// <summary> +/// Represents a Patricia trie built of a collection of <see cref="Withdrawal"/>. +/// </summary> +public class WithdrawalTrie : PatriciaTrie<Withdrawal> +{ + private static readonly WithdrawalDecoder _codec = new(); + + /// <inheritdoc/> + /// <param name="withdrawals">The withdrawals to build the trie of.</param> + public WithdrawalTrie(IEnumerable<Withdrawal> withdrawals, bool canBuildProof = false) + : base(withdrawals, canBuildProof) => ArgumentNullException.ThrowIfNull(withdrawals); + + protected override void Initialize(IEnumerable<Withdrawal> withdrawals) + { + var key = 0; + + foreach (var withdrawal in withdrawals) + { + Set(Rlp.Encode(key++).Bytes, _codec.Encode(withdrawal).Bytes); + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization.Test/BlockDownloaderTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/BlockDownloaderTests.cs index 29349f2fec9..304444540e7 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/BlockDownloaderTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/BlockDownloaderTests.cs @@ -472,6 +472,13 @@ public bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, B Thread.Sleep(1000); return true; } + + public bool ValidateWithdrawals(Block block, out string? error) + { + Thread.Sleep(1000); + error = string.Empty; + return true; + } } [Test, MaxTime(7000)] @@ -913,7 +920,7 @@ public virtual IBlockTree BlockTree { get { - if (_blockTree == null) + if (_blockTree is null) { _blockTree = new BlockTree(new MemDb(), new MemDb(), _blockInfoDb, new ChainLevelInfoRepository(_blockInfoDb), SpecProvider, NullBloomStorage.Instance, LimboLogs.Instance); _blockTree.SuggestBlock(genesis); diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/ProgressTrackerTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/ProgressTrackerTests.cs index bc94892e208..9a0fbb4115c 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/ProgressTrackerTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/ProgressTrackerTests.cs @@ -1,5 +1,5 @@ -// Copyright 2022 Demerzel Solutions Limited -// Licensed under the LGPL-3.0. For full terms, see LICENSE-LGPL in the project root. +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only using System; using System.Threading.Tasks; diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs index fe4224573d7..c63f2cfc0b9 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncThreadTests.cs @@ -15,6 +15,7 @@ using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Transactions; using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; diff --git a/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloadContext.cs b/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloadContext.cs index 076f9464cc1..97aada6fc2f 100644 --- a/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloadContext.cs +++ b/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloadContext.cs @@ -56,7 +56,7 @@ public BlockDownloadContext(ISpecProvider specProvider, PeerInfo syncPeer, Block } else { - Blocks[i - 1] = new Block(header, BlockBody.Empty); + Blocks[i - 1] = new Block(header); } } } diff --git a/src/Nethermind/Nethermind.TxPool/Filters/AlreadyKnownTxFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/AlreadyKnownTxFilter.cs index 3186423befa..6cc38bb0ecf 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/AlreadyKnownTxFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/AlreadyKnownTxFilter.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core; +using Nethermind.Logging; namespace Nethermind.TxPool.Filters { @@ -14,16 +15,21 @@ namespace Nethermind.TxPool.Filters internal class AlreadyKnownTxFilter : IIncomingTxFilter { private readonly HashCache _hashCache; + private readonly ILogger _logger; - public AlreadyKnownTxFilter(HashCache hashCache) + public AlreadyKnownTxFilter( + HashCache hashCache, + ILogger logger) { _hashCache = hashCache; + _logger = logger; } public AcceptTxResult Accept(Transaction tx, TxFilteringState state, TxHandlingOptions handlingOptions) { if (_hashCache.Get(tx.Hash!)) { + if (_logger.IsTrace) _logger.Trace($"Found tx in _hashCache. TxHash: {tx?.Hash}, Tx: {tx}"); Metrics.PendingTransactionsKnown++; return AcceptTxResult.AlreadyKnown; } diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index b96336190f7..8aeb2ed697b 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -98,7 +98,7 @@ public TxPool( _headInfo.HeadChanged += OnHeadChange; _filterPipeline.Add(new NullHashTxFilter()); - _filterPipeline.Add(new AlreadyKnownTxFilter(_hashCache)); + _filterPipeline.Add(new AlreadyKnownTxFilter(_hashCache, _logger)); _filterPipeline.Add(new MalformedTxFilter(_specProvider, validator, _logger)); _filterPipeline.Add(new GasLimitTxFilter(_headInfo, txPoolConfig, _logger)); _filterPipeline.Add(new UnknownSenderFilter(ecdsa, _logger));