From 578290ec8d857973711a8cbd90511f2bc8bb3e57 Mon Sep 17 00:00:00 2001 From: Alexey Date: Fri, 30 Jun 2023 16:27:02 +0300 Subject: [PATCH] Add shard blob tx gas calculations (#5596) * Add data gas calculations * ChainSpecs/BlockProcessing: Fix syncing on Genesis block due to ExcessDataGas mismatch. * TxValidator: Move KZG version check into network wrapper verification. * ExcessData gas on genesis block * Check versioned hash earlier * Remake gas, preview * Fix recursion * Add tests, refactor * Get rid of parent block header finder * Clean up * Clean up checks * Fix tests * Fix header mapping to rpc * Fix data gas calculations * Add safety checks * Fix and improve * Blob gas info in receipts (#5767) * Blob gas info in receipts * Fix gas price * Fix spaces * Add tests * Supports blobs * Ignore on null * Fix suggestions * Fix build --------- Co-authored-by: Nikita Mescheryakov * Increase data gas limits * Clean up * Add more blob space * Fix tests; clean up * Add tests, refacator * Fix MaxFeePerDataGas affects consensus on low balance * Improve max fee per data gas fix * Fix receipts * Add overflow checks * Fix tests * Fix encoding; improve text; clean up * Add gas fields to the local 4844 network genesis * Refactor BlockValidator * Add additional check --------- Co-authored-by: spencer-tb Co-authored-by: Nikita Mescheryakov Co-authored-by: Nikita Mescheryakov --- src/Nethermind/Chains/eip4844_local.json | 4 +- .../Ethereum.Test.Base/GeneralTestBase.cs | 4 +- .../Validators/ContractBasedValidatorTests.cs | 1 - .../Producers/BlockProducerBaseTests.cs | 1 - .../TransactionSelectorTests.cs | 25 ++- .../ShardBlobBlockValidatorTests.cs | 100 +++++++++--- .../Validators/TxValidatorTests.cs | 28 ++-- .../CliqueBlockProducerTests.cs | 6 +- ...cessor.BlockProductionTransactionPicker.cs | 16 +- .../Processing/BlockProcessor.cs | 10 ++ .../Producers/BlockProducerBase.cs | 15 +- .../Producers/TxPoolTxSource.cs | 31 +++- .../Validators/BlockValidator.cs | 100 +++++++++--- .../Validators/HeaderValidator.cs | 50 +++++- .../Validators/TxValidator.cs | 2 +- .../Blockchain/TestBlockchain.cs | 8 +- .../Builders/BlockBuilder.cs | 10 +- .../Builders/BlockHeaderBuilder.cs | 11 +- .../Builders/Build.BlockHeader.cs | 1 + .../Encoding/BlockDecoderTests.cs | 30 ++++ .../Encoding/HeaderDecoderTests.cs | 50 +++--- src/Nethermind/Nethermind.Core/Block.cs | 4 +- src/Nethermind/Nethermind.Core/BlockHeader.cs | 15 +- .../Nethermind.Core/Eip4844Constants.cs | 11 +- src/Nethermind/Nethermind.Core/Transaction.cs | 3 +- .../DataGasCalculatorTests.cs | 100 ++++++++++++ .../Eip3198BaseFeeTests.cs | 3 +- .../Eip3529RefundsTests.cs | 7 - .../IntrinsicGasCalculatorTests.cs | 5 +- .../Tracing/GasEstimationTests.cs | 70 +++++--- .../TransactionProcessorEip4844Tests.cs | 149 ++++++++++++++++++ .../TransactionProcessorFeeTests.cs | 4 +- .../VirtualMachineTestsBase.cs | 16 +- .../Nethermind.Evm/DataGasCalculator.cs | 108 +++++++++++++ .../Nethermind.Evm/IntrinsicGasCalculator.cs | 121 +++++++------- .../Nethermind.Evm/TransactionExtensions.cs | 40 +++++ .../TransactionProcessor.cs | 37 ++++- .../BlockchainBridgeTests.cs | 45 ++++-- .../Nethermind.Facade.Test.csproj | 1 + .../Nethermind.Facade/BlockchainBridge.cs | 17 +- .../Nethermind.Facade/Eth/EthSyncingInfo.cs | 2 +- .../Nethermind.Facade/IBlockchainBridge.cs | 5 +- .../Proxy/Models/BlockModel.cs | 3 +- .../Data/ReceiptsForRpcTests.cs | 2 +- .../Modules/Eth/EthRpcModuleTests.cs | 18 ++- .../Converters/TxReceiptConverter.cs | 2 +- .../Nethermind.JsonRpc/Data/ReceiptForRpc.cs | 14 +- .../Modules/Eth/BlockForRpc.cs | 6 +- .../Modules/Eth/EthRpcModule.cs | 7 +- .../Modules/Parity/ParityRpcModule.cs | 6 +- .../Modules/Proof/ProofRpcModule.cs | 7 +- .../Modules/Trace/TraceRpcModule.cs | 4 +- .../AssertionsSetup.cs | 22 +++ .../AssertionsSetup.cs | 22 +++ .../EngineModuleTests.HelperFunctions.cs | 3 +- .../EngineModuleTests.V3.cs | 9 +- .../BlockProduction/PostMergeBlockProducer.cs | 17 +- .../Data/ExecutionPayload.cs | 16 +- .../HeaderDecoder.cs | 25 +-- .../ChainSpecStyle/ChainSpec.cs | 2 + .../ChainSpecStyle/ChainSpecLoader.cs | 6 +- .../Json/ChainSpecGenesisJson.cs | 3 + 62 files changed, 1137 insertions(+), 323 deletions(-) create mode 100644 src/Nethermind/Nethermind.Evm.Test/DataGasCalculatorTests.cs create mode 100644 src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip4844Tests.cs create mode 100644 src/Nethermind/Nethermind.Evm/DataGasCalculator.cs create mode 100644 src/Nethermind/Nethermind.Merge.AuRa.Test/AssertionsSetup.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin.Test/AssertionsSetup.cs diff --git a/src/Nethermind/Chains/eip4844_local.json b/src/Nethermind/Chains/eip4844_local.json index 5c4844d4f6f..b42da9d106e 100644 --- a/src/Nethermind/Chains/eip4844_local.json +++ b/src/Nethermind/Chains/eip4844_local.json @@ -56,7 +56,9 @@ } }, "timestamp": 0, - "baseFeePerGas": "0x7" + "baseFeePerGas": "0x7", + "dataGasUsed": "0x0", + "excessDataGas": "0x0" }, "accounts": { "0x8A04d14125D0FDCDc742F4A05C051De07232EDa4": { diff --git a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs index fc6382a4137..fad444b47ce 100644 --- a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs @@ -89,8 +89,8 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer) header.MixHash = test.CurrentRandom; Stopwatch stopwatch = Stopwatch.StartNew(); - var txValidator = new TxValidator((MainnetSpecProvider.Instance.ChainId)); - var spec = specProvider.GetSpec((ForkActivation)test.CurrentNumber); + TxValidator? txValidator = new((MainnetSpecProvider.Instance.ChainId)); + IReleaseSpec? spec = specProvider.GetSpec((ForkActivation)test.CurrentNumber); if (test.Transaction.ChainId == null) test.Transaction.ChainId = MainnetSpecProvider.Instance.ChainId; bool isValid = txValidator.IsWellFormed(test.Transaction, spec); diff --git a/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs index 5511d9db936..cd20ec0da8a 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs @@ -7,7 +7,6 @@ using FluentAssertions; using Nethermind.Abi; using Nethermind.Blockchain; -using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; using Nethermind.Consensus; using Nethermind.Consensus.AuRa; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.cs index 4a3f401c9a8..4b8b6ef14b7 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.cs @@ -17,7 +17,6 @@ using Nethermind.State; using NSubstitute; using NUnit.Framework; -using NUnit.Framework.Internal.Execution; namespace Nethermind.Blockchain.Test.Producers { diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs index 6d53ca6520d..2bdf521fd74 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs @@ -151,26 +151,35 @@ public static IEnumerable EnoughShardBlobTransactionsSelectedTestCases { get { - ProperTransactionsSelectedTestCase maxTransactionsSelected = ProperTransactionsSelectedTestCase.Default; + ProperTransactionsSelectedTestCase maxTransactionsSelected = ProperTransactionsSelectedTestCase.Eip1559Default; + maxTransactionsSelected.ReleaseSpec = Cancun.Instance; + maxTransactionsSelected.BaseFee = 1; maxTransactionsSelected.Transactions.ForEach(tx => { tx.Type = TxType.Blob; tx.BlobVersionedHashes = new byte[1][]; + tx.MaxFeePerDataGas = 1; }); maxTransactionsSelected.Transactions[1].BlobVersionedHashes = - new byte[Eip4844Constants.MaxBlobsPerBlock - 1][]; + new byte[Eip4844Constants.MaxDataGasPerTransaction / Eip4844Constants.DataGasPerBlob - 1][]; maxTransactionsSelected.ExpectedSelectedTransactions.AddRange( maxTransactionsSelected.Transactions.OrderBy(t => t.Nonce).Take(2)); yield return new TestCaseData(maxTransactionsSelected).SetName("Enough transactions selected"); ProperTransactionsSelectedTestCase enoughTransactionsSelected = - ProperTransactionsSelectedTestCase.Default; + ProperTransactionsSelectedTestCase.Eip1559Default; + enoughTransactionsSelected.ReleaseSpec = Cancun.Instance; + enoughTransactionsSelected.BaseFee = 1; + Transaction[] expectedSelectedTransactions = enoughTransactionsSelected.Transactions.OrderBy(t => t.Nonce).ToArray(); expectedSelectedTransactions[0].Type = TxType.Blob; - expectedSelectedTransactions[0].BlobVersionedHashes = new byte[Eip4844Constants.MaxBlobsPerBlock][]; + expectedSelectedTransactions[0].BlobVersionedHashes = + new byte[Eip4844Constants.MaxDataGasPerTransaction / Eip4844Constants.DataGasPerBlob][]; + expectedSelectedTransactions[0].MaxFeePerDataGas = 1; expectedSelectedTransactions[1].Type = TxType.Blob; expectedSelectedTransactions[1].BlobVersionedHashes = new byte[1][]; + expectedSelectedTransactions[1].MaxFeePerDataGas = 1; enoughTransactionsSelected.ExpectedSelectedTransactions.AddRange( expectedSelectedTransactions.Where((tx, index) => index != 1)); yield return new TestCaseData(enoughTransactionsSelected).SetName( @@ -240,9 +249,13 @@ void SetAccountStates(IEnumerable
missingAddresses) TxPoolTxSource poolTxSource = new(transactionPool, specProvider, transactionComparerProvider, LimboLogs.Instance, txFilterPipeline); - + BlockHeaderBuilder parentHeader = Build.A.BlockHeader.WithStateRoot(stateProvider.StateRoot).WithBaseFee(testCase.BaseFee); + if (spec.IsEip4844Enabled) + { + parentHeader = parentHeader.WithExcessDataGas(0); + } IEnumerable selectedTransactions = - poolTxSource.GetTransactions(Build.A.BlockHeader.WithStateRoot(stateProvider.StateRoot).WithBaseFee(testCase.BaseFee).TestObject, + poolTxSource.GetTransactions(parentHeader.TestObject, testCase.GasLimit); selectedTransactions.Should() .BeEquivalentTo(testCase.ExpectedSelectedTransactions, o => o.WithStrictOrdering()); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs index 7518bf3303c..ad361daadea 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Collections.Generic; +using System.Linq; +using Nethermind.Blockchain.Find; using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Specs; @@ -8,50 +11,95 @@ using Nethermind.Logging; using Nethermind.Specs.Forks; using Nethermind.Specs.Test; +using NSubstitute; using NUnit.Framework; namespace Nethermind.Blockchain.Test.Validators; public class ShardBlobBlockValidatorTests { - [Test] - public void Not_null_ExcessDataGas_is_invalid_pre_cancun() + [TestCaseSource(nameof(DataGasFieldsPerForkTestCases))] + public static bool Data_gas_fields_should_be_set(IReleaseSpec spec, ulong? dataGasUsed, ulong? excessDataGas) { - 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 - .WithWithdrawalsRoot(TestItem.KeccakA) - .WithWithdrawals(TestItem.WithdrawalA_1Eth) - .WithExcessDataGas(1).TestObject); - Assert.False(isValid); - } - - [Test] - public void Null_ExcessDataGas_is_invalid_post_cancun() - { - ISpecProvider specProvider = new CustomSpecProvider(((ForkActivation)0, Cancun.Instance)); - BlockValidator blockValidator = new(Always.Valid, Always.Valid, Always.Valid, specProvider, LimboLogs.Instance); - bool isValid = blockValidator.ValidateSuggestedBlock(Build.A.Block + ISpecProvider specProvider = new CustomSpecProvider(((ForkActivation)0, spec)); + HeaderValidator headerValidator = new(Substitute.For(), Always.Valid, specProvider, TestLogManager.Instance); + BlockValidator blockValidator = new(Always.Valid, headerValidator, Always.Valid, specProvider, TestLogManager.Instance); + return blockValidator.ValidateSuggestedBlock(Build.A.Block + .WithDataGasUsed(dataGasUsed) + .WithExcessDataGas(excessDataGas) .WithWithdrawalsRoot(TestItem.KeccakA) .WithWithdrawals(TestItem.WithdrawalA_1Eth) + .WithParent(Build.A.BlockHeader.TestObject) .TestObject); - Assert.False(isValid); } - [TestCase(0, ExpectedResult = true)] - [TestCase(Eip4844Constants.MaxBlobsPerBlock - 1, ExpectedResult = true)] - [TestCase(Eip4844Constants.MaxBlobsPerBlock, ExpectedResult = true)] - [TestCase(Eip4844Constants.MaxBlobsPerBlock + 1, ExpectedResult = false)] - public bool Blobs_per_block_count_is_valid(int blobsCount) + [TestCase(0ul, ExpectedResult = true)] + [TestCase(Eip4844Constants.MaxDataGasPerBlock - Eip4844Constants.DataGasPerBlob, ExpectedResult = true)] + [TestCase(Eip4844Constants.MaxDataGasPerBlock, ExpectedResult = true)] + [TestCase(Eip4844Constants.MaxDataGasPerBlock + Eip4844Constants.DataGasPerBlob, ExpectedResult = false)] + public bool Blobs_per_block_count_is_valid(ulong dataGasUsed) { ISpecProvider specProvider = new CustomSpecProvider(((ForkActivation)0, Cancun.Instance)); - BlockValidator blockValidator = new(Always.Valid, Always.Valid, Always.Valid, specProvider, LimboLogs.Instance); + BlockValidator blockValidator = new(Always.Valid, Always.Valid, Always.Valid, specProvider, TestLogManager.Instance); return blockValidator.ValidateSuggestedBlock( Build.A.Block .WithWithdrawalsRoot(TestItem.KeccakA) .WithWithdrawals(TestItem.WithdrawalA_1Eth) - .WithExcessDataGas(1) - .WithTransactions(Build.A.Transaction.WithBlobVersionedHashes(blobsCount).TestObject) + .WithDataGasUsed(dataGasUsed) + .WithExcessDataGas(0) + .WithTransactions(Enumerable.Range(0, (int)(dataGasUsed / Eip4844Constants.DataGasPerBlob)) + .Select(i => Build.A.Transaction.WithType(TxType.Blob) + .WithMaxFeePerDataGas(ulong.MaxValue) + .WithBlobVersionedHashes(1).TestObject).ToArray()) .TestObject); } + + public static IEnumerable DataGasFieldsPerForkTestCases + { + get + { + yield return new TestCaseData(Shanghai.Instance, null, null) + { + TestName = "Data gas fields are not set pre-Cancun", + ExpectedResult = true + }; + yield return new TestCaseData(Shanghai.Instance, 0ul, null) + { + TestName = "DataGasUsed is set pre-Cancun", + ExpectedResult = false + }; + yield return new TestCaseData(Shanghai.Instance, null, 0ul) + { + TestName = "ExcessDataGas is set pre-Cancun", + ExpectedResult = false + }; + yield return new TestCaseData(Shanghai.Instance, 0ul, 0ul) + { + TestName = "Data gas fields are set pre-Cancun", + ExpectedResult = false + }; + + + yield return new TestCaseData(Cancun.Instance, null, null) + { + TestName = "Data gas fields are not set post-Cancun", + ExpectedResult = false + }; + yield return new TestCaseData(Cancun.Instance, 0ul, null) + { + TestName = "Just DataGasUsed is set post-Cancun", + ExpectedResult = false + }; + yield return new TestCaseData(Cancun.Instance, null, 0ul) + { + TestName = "Just ExcessDataGas is set post-Cancun", + ExpectedResult = false + }; + yield return new TestCaseData(Cancun.Instance, 0ul, 0ul) + { + TestName = "Data gas fields are set post-Cancun", + ExpectedResult = true + }; + } + } } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs index 4ea1a2998b3..59dca3b99a3 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs @@ -336,7 +336,7 @@ public void ShardBlobTransactions_should_have_destination_set() .WithChainId(TestBlockchainIds.ChainId) .SignedAndResolved().TestObject; - Transaction txtxWithTo = Build.A.Transaction + Transaction txWithTo = Build.A.Transaction .WithType(TxType.Blob) .WithTimestamp(ulong.MaxValue) .WithTo(TestItem.AddressA) @@ -347,7 +347,7 @@ public void ShardBlobTransactions_should_have_destination_set() .SignedAndResolved().TestObject; Assert.That(txValidator.IsWellFormed(txWithoutTo, Cancun.Instance), Is.False); - Assert.That(txValidator.IsWellFormed(txtxWithTo, Cancun.Instance)); + Assert.That(txValidator.IsWellFormed(txWithTo, Cancun.Instance)); } [Timeout(Timeout.MaxTestTime)] @@ -371,7 +371,6 @@ public bool MaxFeePerDataGas_should_be_set_for_blob_tx_only(TxType txType, bool return txValidator.IsWellFormed(tx, Cancun.Instance); } - [TestCaseSource(nameof(BlobVersionedHashInvalidTestCases))] [TestCaseSource(nameof(BlobVersionedHashValidTestCases))] public bool BlobVersionedHash_should_be_correct(byte[] hash) @@ -407,36 +406,41 @@ private static IEnumerable BlobVersionedHashInvalidTestCases { yield return new TestCaseData(null) { TestName = "Null hash", ExpectedResult = false }; yield return new TestCaseData(MakeArray(0)) { TestName = "Empty hash", ExpectedResult = false }; - yield return new TestCaseData(MakeArray(1, 1, 0)) + yield return new TestCaseData(MakeArray(1, 1)) { TestName = "Correct version, incorrect length", ExpectedResult = false }; - yield return new TestCaseData(MakeArray(31, 1, 0)) + yield return new TestCaseData(MakeArray(31, 1)) { TestName = "Correct version, incorrect length", ExpectedResult = false }; - yield return new TestCaseData(MakeArray(33, 1, 0)) + yield return new TestCaseData(MakeArray(33, 1)) { TestName = "Correct version, incorrect length", ExpectedResult = false }; - yield return new TestCaseData(MakeArray(32, 0, 0)) + yield return new TestCaseData(MakeArray(32, 0)) { TestName = "Incorrect version, correct length", ExpectedResult = false }; - yield return new TestCaseData(MakeArray(32, KzgPolynomialCommitments.KzgBlobHashVersionV1 - 1, 0)) + yield return new TestCaseData(MakeArray(32, KzgPolynomialCommitments.KzgBlobHashVersionV1 - 1)) { TestName = "Incorrect version, correct length", ExpectedResult = false }; - yield return new TestCaseData(MakeArray(32, KzgPolynomialCommitments.KzgBlobHashVersionV1 + 1, 0)) + yield return new TestCaseData(MakeArray(32, KzgPolynomialCommitments.KzgBlobHashVersionV1 + 1)) { TestName = "Incorrect version, correct length", ExpectedResult = false }; + yield return new TestCaseData(MakeArray(32, KzgPolynomialCommitments.KzgBlobHashVersionV1)) + { + TestName = "Correct version, correct length", + ExpectedResult = true + }; } } @@ -494,19 +498,19 @@ static TransactionBuilder MakeTestObject(int blobCount = 1) => Buil TestName = "More than minimum BlobVersionedHashes", ExpectedResult = true }; - yield return new TestCaseData(MakeTestObject(Eip4844Constants.MaxBlobsPerTransaction - 1) + yield return new TestCaseData(MakeTestObject((int)(Eip4844Constants.MaxDataGasPerBlock / Eip4844Constants.DataGasPerBlob - 1)) .SignedAndResolved().TestObject) { TestName = "Less than maximum BlobVersionedHashes", ExpectedResult = true }; - yield return new TestCaseData(MakeTestObject(Eip4844Constants.MaxBlobsPerTransaction) + yield return new TestCaseData(MakeTestObject((int)(Eip4844Constants.MaxDataGasPerBlock / Eip4844Constants.DataGasPerBlob)) .SignedAndResolved().TestObject) { TestName = "Maximum BlobVersionedHashes", ExpectedResult = true }; - yield return new TestCaseData(MakeTestObject(Eip4844Constants.MaxBlobsPerTransaction + 1) + yield return new TestCaseData(MakeTestObject((int)(Eip4844Constants.MaxDataGasPerBlock / Eip4844Constants.DataGasPerBlob + 1)) .SignedAndResolved().TestObject) { TestName = "Too many BlobVersionedHashes", diff --git a/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs b/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs index 1a8ab589711..b905d8e6a49 100644 --- a/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs +++ b/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs @@ -17,7 +17,6 @@ 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; @@ -37,7 +36,6 @@ using Nethermind.Trie.Pruning; using Nethermind.TxPool; using NUnit.Framework; -using BlockTree = Nethermind.Blockchain.BlockTree; using Nethermind.Config; namespace Nethermind.Clique.Test @@ -123,7 +121,9 @@ public On CreateNode(PrivateKey privateKey, bool withGenesisAlreadyProcessed = f _genesis.Header.Hash = _genesis.Header.CalculateHash(); _genesis3Validators.Header.Hash = _genesis3Validators.Header.CalculateHash(); - TransactionProcessor transactionProcessor = new(goerliSpecProvider, stateProvider, new VirtualMachine(blockhashProvider, specProvider, nodeLogManager), nodeLogManager); + TransactionProcessor transactionProcessor = new(goerliSpecProvider, stateProvider, + new VirtualMachine(blockhashProvider, specProvider, nodeLogManager), + nodeLogManager); BlockProcessor blockProcessor = new( goerliSpecProvider, Always.Valid, diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs index f5a2e6bc030..48c14c7c03f 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionPicker.cs @@ -7,8 +7,6 @@ using Nethermind.Core.Specs; using Nethermind.Evm; using Nethermind.Int256; -using Nethermind.Logging; -using Nethermind.Specs; using Nethermind.State; namespace Nethermind.Consensus.Processing @@ -92,9 +90,19 @@ private bool HasEnoughFounds(Transaction transaction, in UInt256 senderBalance, return false; } - if (eip1559Enabled && !transaction.IsServiceTransaction && senderBalance < (UInt256)transaction.GasLimit * transaction.MaxFeePerGas + transaction.Value) + UInt256 maxFee = 0; + + if (eip1559Enabled && !transaction.IsServiceTransaction && senderBalance < (maxFee = (UInt256)transaction.GasLimit * transaction.MaxFeePerGas + transaction.Value)) + { + e.Set(TxAction.Skip, $"{maxFee} is higher than sender balance ({senderBalance}), MaxFeePerGas: ({transaction.MaxFeePerGas}), GasLimit {transaction.GasLimit}"); + return false; + } + + if (releaseSpec.IsEip4844Enabled && !transaction.IsServiceTransaction && ( + !DataGasCalculator.TryCalculateDataGasPrice(block.Header, transaction, out UInt256 dataGasPrice) || + senderBalance < (maxFee = (UInt256)transaction.GasLimit * transaction.MaxFeePerGas + dataGasPrice + transaction.Value))) { - e.Set(TxAction.Skip, $"MaxFeePerGas ({transaction.MaxFeePerGas}) times GasLimit {transaction.GasLimit} is higher than sender balance ({senderBalance})"); + e.Set(TxAction.Skip, $"{maxFee} is higher than sender balance ({senderBalance}), MaxFeePerGas: ({transaction.MaxFeePerGas}), GasLimit {transaction.GasLimit}, DataGasPrice: {dataGasPrice}"); return false; } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs index dfdecd8f4d0..7ff421dc73c 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Numerics; using Nethermind.Blockchain; using Nethermind.Blockchain.Receipts; @@ -13,11 +14,13 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Crypto; +using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Specs.Forks; using Nethermind.State; +using Metrics = Nethermind.Blockchain.Metrics; namespace Nethermind.Consensus.Processing; @@ -221,8 +224,14 @@ protected virtual TxReceipt[] ProcessBlock( _receiptsTracer.SetOtherTracer(blockTracer); _receiptsTracer.StartNewBlockTrace(block); + TxReceipt[] receipts = _blockTransactionsExecutor.ProcessTransactions(block, options, _receiptsTracer, spec); + if (spec.IsEip4844Enabled) + { + block.Header.DataGasUsed = DataGasCalculator.CalculateDataGas(block.Transactions); + } + block.Header.ReceiptsRoot = receipts.GetReceiptsRoot(spec, block.ReceiptsRoot); ApplyMinerRewards(block, blockTracer, spec); _withdrawalProcessor.ProcessWithdrawals(block, spec); @@ -258,6 +267,7 @@ private Block PrepareBlockForProcessing(Block suggestedBlock) bh.GasLimit, bh.Timestamp, bh.ExtraData, + bh.DataGasUsed, bh.ExcessDataGas) { Bloom = Bloom.Empty, diff --git a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs index f3f54fb7849..d4e5accf15e 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerBase.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Text; using System.Threading; using System.Threading.Tasks; using Nethermind.Blockchain; @@ -12,8 +11,8 @@ using Nethermind.Consensus.Transactions; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; using Nethermind.Core.Specs; +using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Int256; using Nethermind.Logging; @@ -44,7 +43,7 @@ public abstract class BlockProducerBase : IBlockProducer private IWorldState StateProvider { get; } private readonly IGasLimitCalculator _gasLimitCalculator; private readonly IDifficultyCalculator _difficultyCalculator; - private readonly ISpecProvider _specProvider; + protected readonly ISpecProvider _specProvider; private readonly ITxSource _txSource; private readonly IBlockProductionTrigger _trigger; private bool _isRunning; @@ -292,7 +291,7 @@ protected virtual BlockHeader PrepareBlockHeader(BlockHeader parent, _blocksConfig.GetExtraDataBytes()) { Author = blockAuthor, - MixHash = payloadAttributes?.PrevRandao + MixHash = payloadAttributes?.PrevRandao, }; UInt256 difficulty = _difficultyCalculator.Calculate(header, parent); @@ -300,7 +299,9 @@ protected virtual BlockHeader PrepareBlockHeader(BlockHeader parent, header.TotalDifficulty = parent.TotalDifficulty + difficulty; if (Logger.IsDebug) Logger.Debug($"Setting total difficulty to {parent.TotalDifficulty} + {difficulty}."); + header.BaseFeePerGas = BaseFeeCalculator.Calculate(parent, _specProvider.GetSpec(header)); + return header; } @@ -310,12 +311,6 @@ protected virtual Block PrepareBlock(BlockHeader parent, PayloadAttributes? payl IEnumerable transactions = GetTransactions(parent); - if (_specProvider.GetSpec(header).IsEip4844Enabled) - { - // TODO: Calculate ExcessDataGas depending on parent ExcessDataGas and number of blobs in txs - header.ExcessDataGas = 0; - } - return new BlockToProduce(header, transactions, Array.Empty(), payloadAttributes?.Withdrawals); } } diff --git a/src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs b/src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs index adaccb99e94..ae5ef686772 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs @@ -11,6 +11,7 @@ using Nethermind.Core.Collections; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; +using Nethermind.Evm; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.TxPool; @@ -60,6 +61,8 @@ public IEnumerable GetTransactions(BlockHeader parent, long gasLimi // TODO: removing transactions from TX pool here seems to be a bad practice since they will // not come back if the block is ignored? int blobsCounter = 0; + UInt256 dataGasPrice = UInt256.Zero; + foreach (Transaction tx in transactions) { i++; @@ -74,10 +77,33 @@ public IEnumerable GetTransactions(BlockHeader parent, long gasLimi bool success = _txFilterPipeline.Execute(tx, parent); if (!success) continue; - if (tx.Type == TxType.Blob) + if (tx.SupportsBlobs) { + if (dataGasPrice == UInt256.Zero) + { + ulong? excessDataGas = DataGasCalculator.CalculateExcessDataGas(parent, _specProvider.GetSpec(parent)); + if (excessDataGas is null) + { + if (_logger.IsTrace) _logger.Trace($"Declining {tx.ToShortString()}, the specification is not configured to handle shard blob transactions."); + continue; + } + if (!DataGasCalculator.TryCalculateDataGasPricePerUnit(excessDataGas.Value, out dataGasPrice)) + { + if (_logger.IsTrace) _logger.Trace($"Declining {tx.ToShortString()}, failed to calculate data gas price."); + continue; + } + } + int txAmountOfBlobs = tx.BlobVersionedHashes?.Length ?? 0; - if ((blobsCounter + txAmountOfBlobs) > Eip4844Constants.MaxBlobsPerBlock) + + if (dataGasPrice > tx.MaxFeePerDataGas) + { + if (_logger.IsTrace) _logger.Trace($"Declining {tx.ToShortString()}, data gas fee is too low."); + continue; + } + + if (DataGasCalculator.CalculateDataGas(blobsCounter + txAmountOfBlobs) > + Eip4844Constants.MaxDataGasPerBlock) { if (_logger.IsTrace) _logger.Trace($"Declining {tx.ToShortString()}, no more blob space."); continue; @@ -97,7 +123,6 @@ public IEnumerable GetTransactions(BlockHeader parent, long gasLimi } if (_logger.IsDebug) _logger.Debug($"Potentially selected {selectedTransactions} out of {i} pending transactions checked."); - } protected virtual IEnumerable GetOrderedTransactions(IDictionary pendingTransactions, IComparer comparer) => diff --git a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs index 82858e12c3f..5c0bf211ccf 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs @@ -2,11 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Linq; using Nethermind.Blockchain; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.Int256; using Nethermind.Logging; using Nethermind.State.Proofs; using Nethermind.TxPool; @@ -54,16 +55,18 @@ public bool Validate(BlockHeader header, bool isUncle) /// public bool ValidateSuggestedBlock(Block block) { - Transaction[] txs = block.Transactions; IReleaseSpec spec = _specProvider.GetSpec(block.Header); - for (int i = 0; i < txs.Length; i++) + if (!ValidateTransactions(block, spec, out int blobsInBlock, out string error)) { - if (!_txValidator.IsWellFormed(txs[i], spec)) - { - if (_logger.IsDebug) _logger.Debug($"{Invalid(block)} Invalid transaction {txs[i].Hash}"); - return false; - } + if (_logger.IsDebug) _logger.Debug($"{Invalid(block)} {error}"); + return false; + } + + if (!ValidateDataGasUsed(block, spec, blobsInBlock, out string dataGasError)) + { + if (_logger.IsDebug) _logger.Debug($"{Invalid(block)} {dataGasError}"); + return false; } if (spec.MaximumUncleCount < block.Uncles.Length) @@ -100,9 +103,6 @@ public bool ValidateSuggestedBlock(Block block) if (!ValidateWithdrawals(block, spec, out _)) return false; - if (!ValidateBlobs(block, spec, out _)) - return false; - return true; } @@ -142,6 +142,16 @@ public bool ValidateProcessedBlock(Block processedBlock, TxReceipt[] receipts, B if (_logger.IsError) _logger.Error($"- state root: expected {suggestedBlock.Header.StateRoot}, got {processedBlock.Header.StateRoot}"); } + if (processedBlock.Header.DataGasUsed != suggestedBlock.Header.DataGasUsed) + { + if (_logger.IsError) _logger.Error($"- data gas used: expected {suggestedBlock.Header.DataGasUsed}, got {processedBlock.Header.DataGasUsed}"); + } + + if (processedBlock.Header.ExcessDataGas != suggestedBlock.Header.ExcessDataGas) + { + if (_logger.IsError) _logger.Error($"- excess data gas: expected {suggestedBlock.Header.ExcessDataGas}, got {processedBlock.Header.ExcessDataGas}"); + } + for (int i = 0; i < processedBlock.Transactions.Length; i++) { if (receipts[i].Error is not null && receipts[i].GasUsed == 0 && receipts[i].Error == "invalid") @@ -193,32 +203,70 @@ private bool ValidateWithdrawals(Block block, IReleaseSpec spec, out string? err return true; } - private bool ValidateBlobs(Block block, IReleaseSpec spec, out string? error) + private bool ValidateTransactions(Block block, IReleaseSpec spec, out int blobsInBlock, out string? error) { - if (spec.IsEip4844Enabled && block.ExcessDataGas is null) + blobsInBlock = 0; + + UInt256 dataGasPrice = UInt256.Zero; + + Transaction[] transactions = block.Transactions; + + for (int txIndex = 0; txIndex < transactions.Length; txIndex++) { - error = "ExcessDataGas field is not set."; - if (_logger.IsWarn) _logger.Warn(error); - return false; + Transaction transaction = transactions[txIndex]; + + if (!_txValidator.IsWellFormed(transaction, spec)) + { + error = $"{Invalid(block)} Invalid transaction {transaction.Hash}"; + return false; + } + + if (!transaction.SupportsBlobs) + { + continue; + } + + if (dataGasPrice == UInt256.Zero) + { + if (!DataGasCalculator.TryCalculateDataGasPricePerUnit(block.Header, out dataGasPrice)) + { + error = $"{nameof(dataGasPrice)} overflow."; + return false; + } + } + + if (transaction.MaxFeePerDataGas < dataGasPrice) + { + error = $"A transaction has unsufficient {nameof(transaction.MaxFeePerDataGas)} to cover current data gas fee: {transaction.MaxFeePerDataGas} < {dataGasPrice}."; + return false; + } + + blobsInBlock += transaction.BlobVersionedHashes!.Length; } - if (!spec.IsEip4844Enabled && block.ExcessDataGas is not null) + error = null; + return true; + } + + private bool ValidateDataGasUsed(Block block, IReleaseSpec spec, in int blobsInBlock, out string? error) + { + if (!spec.IsEip4844Enabled) { - error = "ExcessDataGas field should not have value."; - if (_logger.IsWarn) _logger.Warn(error); - return false; + error = null; + return true; } - int? blobsInBlock = 0; - for (int txIndex = block.Transactions.Length - 1; txIndex >= 0; txIndex--) + ulong dataGasUsed = DataGasCalculator.CalculateDataGas(blobsInBlock); + + if (dataGasUsed > Eip4844Constants.MaxDataGasPerBlock) { - blobsInBlock += block.Transactions[txIndex].BlobVersionedHashes?.Length ?? 0; + error = $"A block cannot have more than {Eip4844Constants.MaxDataGasPerBlock} data gas."; + return false; } - if (spec.IsEip4844Enabled && blobsInBlock > Eip4844Constants.MaxBlobsPerBlock) + if (dataGasUsed != block.Header.DataGasUsed) { - error = $"A block cannot contain more than {Eip4844Constants.MaxBlobsPerBlock} blobs."; - if (_logger.IsWarn) _logger.Warn(error); + error = $"{nameof(BlockHeader.DataGasUsed)} declared in the block header does not match actual data gas used: {block.Header.DataGasUsed} != {dataGasUsed}."; return false; } diff --git a/src/Nethermind/Nethermind.Consensus/Validators/HeaderValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/HeaderValidator.cs index ee708ce6aff..66d77ad4694 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/HeaderValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/HeaderValidator.cs @@ -2,15 +2,13 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; -using System.Linq; using Nethermind.Blockchain; using Nethermind.Blockchain.Find; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Crypto; +using Nethermind.Evm; using Nethermind.Int256; using Nethermind.Logging; @@ -123,6 +121,8 @@ public virtual bool Validate(BlockHeader header, BlockHeader? parent, bool isUnc } } + bool eip4844Valid = ValidateDataGasFields(header, parent, spec); + return totalDifficultyCorrect && gasUsedBelowLimit && @@ -133,7 +133,8 @@ public virtual bool Validate(BlockHeader header, BlockHeader? parent, bool isUnc numberIsParentPlusOne && hashAsExpected && extraDataValid && - eip1559Valid; + eip1559Valid && + eip4844Valid; } private bool ValidateFieldLimit(BlockHeader blockHeader) @@ -277,5 +278,46 @@ private bool ValidateGenesis(BlockHeader header) header.Bloom is not null && header.ExtraData.Length <= _specProvider.GenesisSpec.MaximumExtraDataSize; } + + private bool ValidateDataGasFields(BlockHeader header, BlockHeader parentHeader, IReleaseSpec spec) + { + if (!spec.IsEip4844Enabled) + { + if (header.DataGasUsed is not null) + { + if (_logger.IsWarn) _logger.Warn($"DataGasUsed field should not have value."); + return false; + } + + if (header.ExcessDataGas is not null) + { + if (_logger.IsWarn) _logger.Warn($"ExcessDataGas field should not have value."); + return false; + } + + return true; + } + + if (header.DataGasUsed is null) + { + if (_logger.IsWarn) _logger.Warn($"DataGasUsed field is not set."); + return false; + } + + if (header.ExcessDataGas is null) + { + if (_logger.IsWarn) _logger.Warn($"ExcessDataGas field is not set."); + return false; + } + + UInt256? expectedExcessDataGas = DataGasCalculator.CalculateExcessDataGas(parentHeader, spec); + + if (header.ExcessDataGas != expectedExcessDataGas) + { + if (_logger.IsWarn) _logger.Warn($"ExcessDataGas field is incorrect: {header.ExcessDataGas}, should be {expectedExcessDataGas}."); + return false; + } + return true; + } } } diff --git a/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs index 14904972294..21fd0dcc1c0 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs @@ -119,7 +119,7 @@ transaction.BlobVersionedHashes is null && if (transaction.To is null || transaction.MaxFeePerDataGas is null || transaction.BlobVersionedHashes is null || - transaction.BlobVersionedHashes!.Length > Eip4844Constants.MaxBlobsPerTransaction || + DataGasCalculator.CalculateDataGas(transaction.BlobVersionedHashes!.Length) > Eip4844Constants.MaxDataGasPerTransaction || transaction.BlobVersionedHashes!.Length < Eip4844Constants.MinBlobsPerTransaction) { return false; diff --git a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs index 28ab3ba80fa..59ab6390e1e 100644 --- a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs +++ b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs @@ -293,11 +293,17 @@ protected virtual Block GetGenesisBlock() } genesisBlockBuilder.WithStateRoot(State.StateRoot); - if (SealEngineType == Nethermind.Core.SealEngineType.AuRa) + if (SealEngineType == Core.SealEngineType.AuRa) { genesisBlockBuilder.WithAura(0, new byte[65]); } + if (SpecProvider.GenesisSpec.IsEip4844Enabled) + { + genesisBlockBuilder.WithDataGasUsed(0); + genesisBlockBuilder.WithExcessDataGas(0); + } + return genesisBlockBuilder.TestObject; } diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/BlockBuilder.cs index f25730883f4..c26ac138b54 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; using System.Collections.Generic; using System.Linq; using Nethermind.Core.Crypto; @@ -56,7 +57,13 @@ public BlockBuilder WithTimestamp(ulong timestamp) return this; } - public BlockBuilder WithExcessDataGas(UInt256 excessDataGas) + public BlockBuilder WithDataGasUsed(ulong? dataGasUsed) + { + TestObjectInternal.Header.DataGasUsed = dataGasUsed; + return this; + } + + public BlockBuilder WithExcessDataGas(ulong? excessDataGas) { TestObjectInternal.Header.ExcessDataGas = excessDataGas; return this; @@ -159,6 +166,7 @@ public BlockBuilder WithParent(BlockHeader blockHeader) TestObjectInternal.Header.Number = blockHeader?.Number + 1 ?? 0; TestObjectInternal.Header.Timestamp = blockHeader?.Timestamp + 1 ?? 0; TestObjectInternal.Header.ParentHash = blockHeader is null ? Keccak.Zero : blockHeader.Hash; + TestObjectInternal.Header.MaybeParent = blockHeader is null ? null : new WeakReference(blockHeader); return this; } diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/BlockHeaderBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/BlockHeaderBuilder.cs index 3b301cf9f1a..dfc12b82659 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/BlockHeaderBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/BlockHeaderBuilder.cs @@ -30,8 +30,7 @@ public BlockHeaderBuilder() DefaultDifficulty, 0, 4_000_000, 1_000_000, - new byte[] { 1, 2, 3 }, - null); + new byte[] { 1, 2, 3 }); TestObjectInternal.Bloom = Bloom.Empty; TestObjectInternal.MixHash = Keccak.Compute("mix_hash"); TestObjectInternal.Nonce = 1000; @@ -179,7 +178,13 @@ public BlockHeaderBuilder WithWithdrawalsRoot(Keccak? root) return this; } - public BlockHeaderBuilder WithExcessDataGas(UInt256? excessDataGas) + public BlockHeaderBuilder WithDataGasUsed(ulong? dataGasUsed) + { + TestObjectInternal.DataGasUsed = dataGasUsed; + return this; + } + + public BlockHeaderBuilder WithExcessDataGas(ulong? excessDataGas) { TestObjectInternal.ExcessDataGas = excessDataGas; return this; diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/Build.BlockHeader.cs b/src/Nethermind/Nethermind.Core.Test/Builders/Build.BlockHeader.cs index 09204ba42a9..2a272f2296c 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/Build.BlockHeader.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/Build.BlockHeader.cs @@ -6,5 +6,6 @@ namespace Nethermind.Core.Test.Builders public partial class Build { public BlockHeaderBuilder BlockHeader => new(); + public BlockHeader EmptyBlockHeader => BlockHeader.TestObject; } } diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs index de6326845e3..20ac09f5235 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs @@ -56,6 +56,36 @@ public BlockDecoderTests() .WithUncles(uncles) .WithWithdrawals(8) .WithMixHash(Keccak.EmptyTreeHash) + .TestObject, + Build.A.Block + .WithNumber(1) + .WithBaseFeePerGas(1) + .WithTransactions(transactions) + .WithUncles(uncles) + .WithWithdrawals(8) + .WithDataGasUsed(0) + .WithExcessDataGas(0) + .WithMixHash(Keccak.EmptyTreeHash) + .TestObject, + Build.A.Block + .WithNumber(1) + .WithBaseFeePerGas(1) + .WithTransactions(transactions) + .WithUncles(uncles) + .WithWithdrawals(8) + .WithDataGasUsed(0xff) + .WithExcessDataGas(0xff) + .WithMixHash(Keccak.EmptyTreeHash) + .TestObject, + Build.A.Block + .WithNumber(1) + .WithBaseFeePerGas(1) + .WithTransactions(transactions) + .WithUncles(uncles) + .WithWithdrawals(8) + .WithDataGasUsed(ulong.MaxValue) + .WithExcessDataGas(ulong.MaxValue) + .WithMixHash(Keccak.EmptyTreeHash) .TestObject }; } diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/HeaderDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/HeaderDecoderTests.cs index efe802ca17a..eb6b1be1130 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/HeaderDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/HeaderDecoderTests.cs @@ -128,30 +128,6 @@ public void If_withdrawals_are_null_should_not_encode() Convert.ToHexString(rlp.Bytes).ToLower().Should().Be("f901f7a0ff483e972a04a9a62bb4b7d04ae403c615604e4090521ecc5bb7af67f71be09ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008080833d090080830f424083010203a02ba5557a4c62a513c7e56d1bf13373e0da6bec016755483e91589fe1c6d212e288000000000000000001"); } - [TestCaseSource(nameof(ExcessDataGasCaseSource))] - public void Can_encode_decode_with_excessDataGas(UInt256? excessDataGas) - { - BlockHeader header = Build.A.BlockHeader - .WithTimestamp(ulong.MaxValue) - .WithBaseFee(1) - .WithWithdrawalsRoot(Keccak.Zero) - .WithExcessDataGas(excessDataGas).TestObject; - - Rlp rlp = Rlp.Encode(header); - BlockHeader blockHeader = Rlp.Decode(rlp.Bytes.AsSpan()); - - blockHeader.ExcessDataGas.Should().Be(excessDataGas); - } - - public static IEnumerable ExcessDataGasCaseSource() - { - yield return null; - yield return UInt256.Zero; - yield return new UInt256(1); - yield return UInt256.UInt128MaxValue; - yield return UInt256.MaxValue; - } - [TestCase(-1)] [TestCase(long.MinValue)] public void Can_encode_decode_with_negative_long_fields(long negativeLong) @@ -185,4 +161,30 @@ public void Can_encode_decode_with_negative_long_when_using_span(long negativeLo blockHeader.Number.Should().Be(negativeLong); blockHeader.GasLimit.Should().Be(negativeLong); } + + [TestCaseSource(nameof(ExcessDataGasCaseSource))] + public void Can_encode_decode_with_excessDataGas(ulong? dataGasUsed, ulong? excessDataGas) + { + BlockHeader header = Build.A.BlockHeader + .WithTimestamp(ulong.MaxValue) + .WithBaseFee(1) + .WithWithdrawalsRoot(Keccak.Zero) + .WithDataGasUsed(dataGasUsed) + .WithExcessDataGas(excessDataGas).TestObject; + + Rlp rlp = Rlp.Encode(header); + BlockHeader blockHeader = Rlp.Decode(rlp.Bytes.AsSpan()); + + blockHeader.DataGasUsed.Should().Be(dataGasUsed); + blockHeader.ExcessDataGas.Should().Be(excessDataGas); + } + + public static IEnumerable ExcessDataGasCaseSource() + { + yield return new object?[] { null, null }; + yield return new object?[] { 0ul, 0ul }; + yield return new object?[] { 1ul, 2ul }; + yield return new object?[] { ulong.MaxValue / 2, ulong.MaxValue }; + yield return new object?[] { ulong.MaxValue, ulong.MaxValue / 2 }; + } } diff --git a/src/Nethermind/Nethermind.Core/Block.cs b/src/Nethermind/Nethermind.Core/Block.cs index fe2d08fdb8e..59e9ed916bb 100644 --- a/src/Nethermind/Nethermind.Core/Block.cs +++ b/src/Nethermind/Nethermind.Core/Block.cs @@ -98,7 +98,9 @@ public Transaction[] Transactions public UInt256 BaseFeePerGas => Header.BaseFeePerGas; // do not add setter here - public UInt256? ExcessDataGas => Header.ExcessDataGas; // do not add setter here + public ulong? DataGasUsed => Header.DataGasUsed; // do not add setter here + + public ulong? ExcessDataGas => Header.ExcessDataGas; // do not add setter here public bool IsPostMerge => Header.IsPostMerge; // do not add setter here diff --git a/src/Nethermind/Nethermind.Core/BlockHeader.cs b/src/Nethermind/Nethermind.Core/BlockHeader.cs index 5c0bbcfd529..d9fd16d2183 100644 --- a/src/Nethermind/Nethermind.Core/BlockHeader.cs +++ b/src/Nethermind/Nethermind.Core/BlockHeader.cs @@ -25,7 +25,8 @@ public BlockHeader( long gasLimit, ulong timestamp, byte[] extraData, - UInt256? excessDataGas = null) + ulong? dataGasUsed = null, + ulong? excessDataGas = null) { ParentHash = parentHash; UnclesHash = unclesHash; @@ -35,6 +36,7 @@ public BlockHeader( GasLimit = gasLimit; Timestamp = timestamp; ExtraData = extraData; + DataGasUsed = dataGasUsed; ExcessDataGas = excessDataGas; } @@ -65,11 +67,11 @@ public BlockHeader( public long? AuRaStep { get; set; } public UInt256 BaseFeePerGas { get; set; } public Keccak? WithdrawalsRoot { get; set; } - public UInt256? ExcessDataGas { get; set; } - + public ulong? DataGasUsed { get; set; } + public ulong? ExcessDataGas { 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); + || (UnclesHash is not null && UnclesHash != Keccak.OfAnEmptySequenceRlp) + || (WithdrawalsRoot is not null && WithdrawalsRoot != Keccak.EmptyTreeHash); public bool HasTransactions => (TxRoot is not null && TxRoot != Keccak.EmptyTreeHash); @@ -99,8 +101,9 @@ public string ToString(string indent) { builder.AppendLine($"{indent}WithdrawalsRoot: {WithdrawalsRoot}"); } - if (ExcessDataGas is not null) + if (DataGasUsed is not null || ExcessDataGas is not null) { + builder.AppendLine($"{indent}DataGasUsed: {DataGasUsed}"); builder.AppendLine($"{indent}ExcessDataGas: {ExcessDataGas}"); } builder.AppendLine($"{indent}IsPostMerge: {IsPostMerge}"); diff --git a/src/Nethermind/Nethermind.Core/Eip4844Constants.cs b/src/Nethermind/Nethermind.Core/Eip4844Constants.cs index 4ef6c219633..0df02f30d78 100644 --- a/src/Nethermind/Nethermind.Core/Eip4844Constants.cs +++ b/src/Nethermind/Nethermind.Core/Eip4844Constants.cs @@ -1,14 +1,19 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Nethermind.Core.Extensions; using Nethermind.Int256; namespace Nethermind.Core; public class Eip4844Constants { - public const int MaxBlobsPerBlock = 4; - public const int MaxBlobsPerTransaction = MaxBlobsPerBlock; public const int MinBlobsPerTransaction = 1; + + public const ulong DataGasPerBlob = 1 << 17; + public const ulong TargetDataGasPerBlock = DataGasPerBlob * 3; + public const ulong MaxDataGasPerBlock = DataGasPerBlob * 6; + public const ulong MaxDataGasPerTransaction = MaxDataGasPerBlock; + + public static readonly UInt256 DataGasUpdateFraction = 3338477; + public static readonly UInt256 MinDataGasPrice = 1; } diff --git a/src/Nethermind/Nethermind.Core/Transaction.cs b/src/Nethermind/Nethermind.Core/Transaction.cs index ca3e57ce5cb..2acea9cc206 100644 --- a/src/Nethermind/Nethermind.Core/Transaction.cs +++ b/src/Nethermind/Nethermind.Core/Transaction.cs @@ -173,7 +173,8 @@ public string ToString(string indent) if (SupportsBlobs) { - builder.AppendLine($"{indent}BlobVersionedHashes: {BlobVersionedHashes?.Length}"); + builder.AppendLine($"{indent}{nameof(MaxFeePerDataGas)}: {MaxFeePerDataGas}"); + builder.AppendLine($"{indent}{nameof(BlobVersionedHashes)}: {BlobVersionedHashes?.Length}"); } return builder.ToString(); diff --git a/src/Nethermind/Nethermind.Evm.Test/DataGasCalculatorTests.cs b/src/Nethermind/Nethermind.Evm.Test/DataGasCalculatorTests.cs new file mode 100644 index 00000000000..3490bf44bde --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Test/DataGasCalculatorTests.cs @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Int256; +using Nethermind.Specs.Forks; +using NUnit.Framework; + +namespace Nethermind.Evm.Test; + +[TestFixture] +public class DataGasCalculatorTests +{ + [TestCaseSource(nameof(ExcessDataGasTestCaseSource))] + public void Excess_data_gas_is_calculated_properly((ulong parentExcessDataGas, int parentBlobsCount, ulong expectedExcessDataGas) testCase) + { + void Test(IReleaseSpec spec, bool areBlobsEnabled) + { + BlockHeader parentHeader = Build.A.BlockHeader + .WithDataGasUsed(DataGasCalculator.CalculateDataGas(testCase.parentBlobsCount)) + .WithExcessDataGas(testCase.parentExcessDataGas).TestObject; + + Assert.That(DataGasCalculator.CalculateExcessDataGas(parentHeader, spec), Is.EqualTo(areBlobsEnabled ? testCase.expectedExcessDataGas : null)); + } + + Test(Homestead.Instance, false); + Test(Frontier.Instance, false); + Test(SpuriousDragon.Instance, false); + Test(TangerineWhistle.Instance, false); + Test(Byzantium.Instance, false); + Test(Constantinople.Instance, false); + Test(ConstantinopleFix.Instance, false); + Test(Istanbul.Instance, false); + Test(MuirGlacier.Instance, false); + Test(Berlin.Instance, false); + Test(GrayGlacier.Instance, false); + Test(Shanghai.Instance, false); + Test(Cancun.Instance, true); + } + + [TestCaseSource(nameof(BlobDataGasCostTestCaseSource))] + public void Data_gas_price_is_calculated_properly( + (Transaction tx, ulong excessDataGas, UInt256 expectedCost) testCase) + { + BlockHeader header = Build.A.BlockHeader.WithExcessDataGas(testCase.excessDataGas).TestObject; + + bool success = DataGasCalculator.TryCalculateDataGasPrice(header, testCase.tx, out UInt256 dataGasPrice); + + Assert.That(success, Is.True); + Assert.That(dataGasPrice, Is.EqualTo(testCase.expectedCost)); + } + + [Test] + public void Data_gas_price_may_overflow() + { + var tx = Build.A.Transaction.WithType(TxType.Blob).WithBlobVersionedHashes(1000).TestObject; + BlockHeader header = Build.A.BlockHeader.WithExcessDataGas(ulong.MaxValue).TestObject; + + bool success = DataGasCalculator.TryCalculateDataGasPrice(header, tx, out UInt256 dataGasPrice); + + Assert.That(success, Is.False); + Assert.That(dataGasPrice, Is.EqualTo(UInt256.MaxValue)); + } + + public static IEnumerable<(ulong parentExcessDataGas, int parentBlobsCount, ulong expectedExcessDataGas)> ExcessDataGasTestCaseSource() + { + yield return (0, 0, 0); + yield return (0, (int)(Eip4844Constants.TargetDataGasPerBlock / Eip4844Constants.DataGasPerBlob) - 1, 0); + yield return (0, (int)(Eip4844Constants.TargetDataGasPerBlock / Eip4844Constants.DataGasPerBlob), 0); + yield return (100000, (int)(Eip4844Constants.TargetDataGasPerBlock / Eip4844Constants.DataGasPerBlob), 100000); + yield return (0, (int)(Eip4844Constants.TargetDataGasPerBlock / Eip4844Constants.DataGasPerBlob) + 1, Eip4844Constants.DataGasPerBlob * 1); + yield return (Eip4844Constants.TargetDataGasPerBlock, 1, Eip4844Constants.DataGasPerBlob * 1); + yield return (Eip4844Constants.TargetDataGasPerBlock, 0, 0); + yield return (Eip4844Constants.TargetDataGasPerBlock, 2, Eip4844Constants.DataGasPerBlob * 2); + yield return (Eip4844Constants.MaxDataGasPerBlock, 1, Eip4844Constants.TargetDataGasPerBlock + Eip4844Constants.DataGasPerBlob * 1); + yield return ( + Eip4844Constants.MaxDataGasPerBlock, + (int)(Eip4844Constants.TargetDataGasPerBlock / Eip4844Constants.DataGasPerBlob), + Eip4844Constants.MaxDataGasPerBlock); + yield return ( + Eip4844Constants.MaxDataGasPerBlock, + (int)(Eip4844Constants.MaxDataGasPerBlock / Eip4844Constants.DataGasPerBlob), + Eip4844Constants.MaxDataGasPerBlock * 2 - Eip4844Constants.TargetDataGasPerBlock + ); + } + + public static IEnumerable<(Transaction tx, ulong excessDataGas, UInt256 expectedCost)> BlobDataGasCostTestCaseSource() + { + yield return (Build.A.Transaction.TestObject, 0, 0); + yield return (Build.A.Transaction.TestObject, 1000, 0); + yield return (Build.A.Transaction.WithType(TxType.Blob).WithBlobVersionedHashes(0).TestObject, 1000, 0); + yield return (Build.A.Transaction.WithType(TxType.Blob).WithBlobVersionedHashes(1).TestObject, 0, 131072); + yield return (Build.A.Transaction.WithType(TxType.Blob).WithBlobVersionedHashes(1).TestObject, 10000000, 2490368); + yield return (Build.A.Transaction.WithType(TxType.Blob).WithBlobVersionedHashes(1000).TestObject, 0, 131072000); + yield return (Build.A.Transaction.WithType(TxType.Blob).WithBlobVersionedHashes(1000).TestObject, 10000000, 2490368000); + } +} diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs index 218f7a27a55..64d8b5810e3 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs @@ -4,13 +4,12 @@ using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Specs; +using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; using Nethermind.Evm.TransactionProcessing; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Specs; -using Nethermind.Specs.Forks; -using NSubstitute; using NUnit.Framework; namespace Nethermind.Evm.Test diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip3529RefundsTests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip3529RefundsTests.cs index 80aa0346126..fe3e7a58e32 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip3529RefundsTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip3529RefundsTests.cs @@ -2,22 +2,15 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using FluentAssertions; 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.Evm.Tracing; -using Nethermind.Evm.Tracing.GethStyle; using Nethermind.Evm.Tracing.ParityStyle; using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; -using Nethermind.Serialization.Json; using Nethermind.Specs; using Nethermind.Specs.Forks; -using NSubstitute; using NUnit.Framework; namespace Nethermind.Evm.Test diff --git a/src/Nethermind/Nethermind.Evm.Test/IntrinsicGasCalculatorTests.cs b/src/Nethermind/Nethermind.Evm.Test/IntrinsicGasCalculatorTests.cs index 64bfc890bba..f830b2483cc 100644 --- a/src/Nethermind/Nethermind.Evm.Test/IntrinsicGasCalculatorTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/IntrinsicGasCalculatorTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Collections.Immutable; using System.IO; using FluentAssertions; using Nethermind.Core; @@ -41,7 +40,6 @@ public class IntrinsicGasCalculatorTests yield return (new byte[] { 1, 1, 0 }, 140, 36); yield return (new byte[] { 0, 0, 1, 1 }, 144, 40); } - [TestCaseSource(nameof(TestCaseSource))] public void Intrinsic_cost_is_calculated_properly((Transaction Tx, long Cost, string Description) testCase) { @@ -113,6 +111,9 @@ void Test(IReleaseSpec spec, bool isAfterRepricing) Test(Istanbul.Instance, true); Test(MuirGlacier.Instance, true); Test(Berlin.Instance, true); + Test(GrayGlacier.Instance, true); + Test(Shanghai.Instance, true); + Test(Cancun.Instance, true); } } } diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs index b7c0f6d2f32..eb9021f38f8 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs @@ -7,18 +7,16 @@ using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; +using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Db; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; -using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Specs; -using Nethermind.Specs.Forks; using Nethermind.State; using Nethermind.Trie.Pruning; -using NSubstitute; using NUnit.Framework; namespace Nethermind.Evm.Test.Tracing @@ -42,9 +40,12 @@ public void Does_not_take_into_account_precompiles() Transaction tx = Build.A.Transaction.WithGasLimit(1000).TestObject; Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).TestObject; - testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), ExecutionType.Transaction, false); - testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), ExecutionType.Call, true); - testEnvironment.tracer.ReportActionEnd(400, Array.Empty()); // this would not happen but we want to ensure that precompiles are ignored + testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), + ExecutionType.Transaction, false); + testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), + ExecutionType.Call, true); + testEnvironment.tracer.ReportActionEnd(400, + Array.Empty()); // this would not happen but we want to ensure that precompiles are ignored testEnvironment.tracer.ReportActionEnd(600, Array.Empty()); testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer).Should().Be(0); @@ -72,7 +73,8 @@ public void Handles_well_top_level() Transaction tx = Build.A.Transaction.WithGasLimit(1000).TestObject; Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).TestObject; - testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), ExecutionType.Transaction, false); + testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), + ExecutionType.Transaction, false); testEnvironment.tracer.ReportActionEnd(600, Array.Empty()); testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer).Should().Be(0); @@ -85,10 +87,13 @@ public void Handles_well_serial_calls() Transaction tx = Build.A.Transaction.WithGasLimit(1000).TestObject; Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).TestObject; - testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), ExecutionType.Transaction, false); - testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), _executionType, false); + testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), + ExecutionType.Transaction, false); + testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), + _executionType, false); testEnvironment.tracer.ReportActionEnd(400, Array.Empty()); - testEnvironment.tracer.ReportAction(400, 0, Address.Zero, Address.Zero, Array.Empty(), _executionType, false); + testEnvironment.tracer.ReportAction(400, 0, Address.Zero, Address.Zero, Array.Empty(), _executionType, + false); if (_executionType.IsAnyCreate()) { testEnvironment.tracer.ReportActionEnd(200, Address.Zero, Array.Empty()); @@ -110,9 +115,12 @@ public void Handles_well_errors() Transaction tx = Build.A.Transaction.WithGasLimit(1000).TestObject; Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).TestObject; - testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), ExecutionType.Transaction, false); - testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), _executionType, false); - testEnvironment.tracer.ReportAction(400, 0, Address.Zero, Address.Zero, Array.Empty(), _executionType, false); + testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), + ExecutionType.Transaction, false); + testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), + _executionType, false); + testEnvironment.tracer.ReportAction(400, 0, Address.Zero, Address.Zero, Array.Empty(), _executionType, + false); if (_executionType.IsAnyCreate()) { @@ -139,11 +147,14 @@ public void Handles_well_revert() Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).TestObject; long gasLeft = gasLimit - 22000; - testEnvironment.tracer.ReportAction(gasLeft, 0, Address.Zero, Address.Zero, Array.Empty(), ExecutionType.Transaction, false); + testEnvironment.tracer.ReportAction(gasLeft, 0, Address.Zero, Address.Zero, Array.Empty(), + ExecutionType.Transaction, false); gasLeft = 63 * gasLeft / 64; - testEnvironment.tracer.ReportAction(gasLeft, 0, Address.Zero, Address.Zero, Array.Empty(), _executionType, false); + testEnvironment.tracer.ReportAction(gasLeft, 0, Address.Zero, Address.Zero, Array.Empty(), + _executionType, false); gasLeft = 63 * gasLeft / 64; - testEnvironment.tracer.ReportAction(gasLeft, 0, Address.Zero, Address.Zero, Array.Empty(), _executionType, false); + testEnvironment.tracer.ReportAction(gasLeft, 0, Address.Zero, Address.Zero, Array.Empty(), + _executionType, false); if (_executionType.IsAnyCreate()) { @@ -168,8 +179,10 @@ public void Easy_one_level_case() Transaction tx = Build.A.Transaction.WithGasLimit(128).TestObject; Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).TestObject; - testEnvironment.tracer.ReportAction(128, 0, Address.Zero, Address.Zero, Array.Empty(), ExecutionType.Transaction, false); - testEnvironment.tracer.ReportAction(100, 0, Address.Zero, Address.Zero, Array.Empty(), _executionType, false); + testEnvironment.tracer.ReportAction(128, 0, Address.Zero, Address.Zero, Array.Empty(), + ExecutionType.Transaction, false); + testEnvironment.tracer.ReportAction(100, 0, Address.Zero, Address.Zero, Array.Empty(), _executionType, + false); testEnvironment.tracer.ReportActionEnd(63, Array.Empty()); // second level testEnvironment.tracer.ReportActionEnd(65, Array.Empty()); @@ -184,9 +197,12 @@ public void Handles_well_nested_calls_where_most_nested_defines_excess() Transaction tx = Build.A.Transaction.WithGasLimit(1000).TestObject; Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).TestObject; - testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), ExecutionType.Transaction, false); - testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), _executionType, false); - testEnvironment.tracer.ReportAction(400, 0, Address.Zero, Address.Zero, Array.Empty(), _executionType, false); + testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), + ExecutionType.Transaction, false); + testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), + _executionType, false); + testEnvironment.tracer.ReportAction(400, 0, Address.Zero, Address.Zero, Array.Empty(), _executionType, + false); if (_executionType.IsAnyCreate()) { @@ -211,9 +227,12 @@ public void Handles_well_nested_calls_where_least_nested_defines_excess() Transaction tx = Build.A.Transaction.WithGasLimit(1000).TestObject; Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).TestObject; - testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), ExecutionType.Transaction, false); - testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), _executionType, false); - testEnvironment.tracer.ReportAction(400, 0, Address.Zero, Address.Zero, Array.Empty(), _executionType, false); + testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), + ExecutionType.Transaction, false); + testEnvironment.tracer.ReportAction(1000, 0, Address.Zero, Address.Zero, Array.Empty(), + _executionType, false); + testEnvironment.tracer.ReportAction(400, 0, Address.Zero, Address.Zero, Array.Empty(), _executionType, + false); if (_executionType.IsAnyCreate()) { @@ -251,7 +270,8 @@ public TestEnvironment() _stateProvider.CommitTree(0); VirtualMachine virtualMachine = new(TestBlockhashProvider.Instance, _specProvider, LimboLogs.Instance); - _transactionProcessor = new TransactionProcessor(_specProvider, _stateProvider, virtualMachine, LimboLogs.Instance); + _transactionProcessor = new TransactionProcessor(_specProvider, _stateProvider, + virtualMachine, LimboLogs.Instance); _ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId, LimboLogs.Instance); tracer = new(); diff --git a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip4844Tests.cs b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip4844Tests.cs new file mode 100644 index 00000000000..77498c8bf64 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip4844Tests.cs @@ -0,0 +1,149 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Db; +using Nethermind.Int256; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Logging; +using Nethermind.Specs.Forks; +using Nethermind.State; +using Nethermind.Trie.Pruning; +using NUnit.Framework; +using System.Collections.Generic; + +namespace Nethermind.Evm.Test; + +[TestFixture] +internal class TransactionProcessorEip4844Tests +{ + private ISpecProvider _specProvider; + private IEthereumEcdsa _ethereumEcdsa; + private TransactionProcessor _transactionProcessor; + private IWorldState _stateProvider; + + [SetUp] + public void Setup() + { + MemDb stateDb = new(); + _specProvider = new TestSpecProvider(Cancun.Instance); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + _stateProvider = new WorldState(trieStore, new MemDb(), LimboLogs.Instance); + VirtualMachine virtualMachine = new(TestBlockhashProvider.Instance, _specProvider, LimboLogs.Instance); + _transactionProcessor = new TransactionProcessor(_specProvider, _stateProvider, virtualMachine, LimboLogs.Instance); + _ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId, LimboLogs.Instance); + } + + + [TestCaseSource(nameof(BalanceIsAffectedByDataGasTestCaseSource))] + [TestCaseSource(nameof(BalanceIsNotAffectedWhenNotEnoughFunds))] + public UInt256 Balance_is_affected_by_data_gas_on_execution(UInt256 balance, int blobCount, + ulong maxFeePerDataGas, ulong excessDataGas, ulong value) + { + _stateProvider.CreateAccount(TestItem.AddressA, balance); + _stateProvider.Commit(_specProvider.GenesisSpec); + _stateProvider.CommitTree(0); + + long gasLimit = GasCostOf.Transaction; + Transaction blobTx = Build.A.Transaction + .WithValue(value) + .WithGasPrice(1) + .WithMaxFeePerGas(1) + .WithMaxFeePerDataGas(maxFeePerDataGas) + .WithGasLimit(gasLimit) + .WithShardBlobTxTypeAndFields(blobCount) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA) + .TestObject; + + Block block = Build.A.Block + .WithNumber(1) + .WithTransactions(blobTx) + .WithGasLimit(gasLimit) + .WithExcessDataGas(excessDataGas) + .WithBaseFeePerGas(1) + .TestObject; + + _transactionProcessor.CallAndRestore(blobTx, block.Header, NullTxTracer.Instance); + UInt256 deltaBalance = balance - _stateProvider.GetBalance(TestItem.PrivateKeyA.Address); + Assert.That(deltaBalance, Is.EqualTo(UInt256.Zero)); + + _transactionProcessor.Execute(blobTx, block.Header, NullTxTracer.Instance); + deltaBalance = balance - _stateProvider.GetBalance(TestItem.PrivateKeyA.Address); + + return deltaBalance; + } + + public static IEnumerable BalanceIsAffectedByDataGasTestCaseSource() + { + yield return new TestCaseData((UInt256)(GasCostOf.Transaction + Eip4844Constants.DataGasPerBlob), 1, 1ul, 0ul, 0ul) + { + TestName = "Data gas consumed for 1 blob, minimal balance", + ExpectedResult = (UInt256)(GasCostOf.Transaction + Eip4844Constants.DataGasPerBlob), + }; + yield return new TestCaseData(1.Ether(), 1, 1ul, 0ul, 0ul) + { + TestName = "Data gas consumed for 1 blob", + ExpectedResult = (UInt256)(GasCostOf.Transaction + Eip4844Constants.DataGasPerBlob), + }; + yield return new TestCaseData(1.Ether(), 2, 1ul, 0ul, 0ul) + { + TestName = "Data gas consumed for 2 blobs", + ExpectedResult = (UInt256)(GasCostOf.Transaction + 2 * Eip4844Constants.DataGasPerBlob), + }; + yield return new TestCaseData(1.Ether(), (int)(Eip4844Constants.MaxDataGasPerTransaction / Eip4844Constants.DataGasPerBlob), 1ul, 0ul, 0ul) + { + TestName = "Data gas consumed for max blobs", + ExpectedResult = (UInt256)(GasCostOf.Transaction + Eip4844Constants.MaxDataGasPerTransaction), + }; + yield return new TestCaseData(1.Ether(), 1, 10ul, 0ul, 0ul) + { + TestName = $"Data gas consumed for 1 blob, with {nameof(Transaction.MaxFeePerDataGas)} more than needed", + ExpectedResult = (UInt256)(GasCostOf.Transaction + Eip4844Constants.DataGasPerBlob), + }; + yield return new TestCaseData(1.Ether(), 1, 10ul, (ulong)Eip4844Constants.DataGasUpdateFraction, 0ul) + { + TestName = $"Data gas consumed for 1 blob, with data gas price hiking", + ExpectedResult = (UInt256)(GasCostOf.Transaction + Eip4844Constants.DataGasPerBlob * 2), + }; + yield return new TestCaseData(1.Ether(), 1, 10ul, (ulong)Eip4844Constants.DataGasUpdateFraction, 2ul) + { + TestName = $"Data gas consumed for 1 blob, with data gas price hiking and some {nameof(Transaction.Value)}", + ExpectedResult = (UInt256)(GasCostOf.Transaction + Eip4844Constants.DataGasPerBlob * 2 + 2), + }; + } + + public static IEnumerable BalanceIsNotAffectedWhenNotEnoughFunds() + { + yield return new TestCaseData((UInt256)(GasCostOf.Transaction + Eip4844Constants.DataGasPerBlob - 1), 1, 1ul, 0ul, 0ul) + { + TestName = $"Rejected if balance is not enough, all funds are returned", + ExpectedResult = UInt256.Zero, + }; + yield return new TestCaseData((UInt256)(GasCostOf.Transaction + Eip4844Constants.DataGasPerBlob + 41), 1, 1ul, 0ul, 42ul) + { + TestName = $"Rejected if balance is not enough to cover {nameof(Transaction.Value)} also, all funds are returned", + ExpectedResult = UInt256.Zero, + }; + yield return new TestCaseData((UInt256)(GasCostOf.Transaction + Eip4844Constants.DataGasPerBlob), 1, 10ul, (ulong)Eip4844Constants.DataGasUpdateFraction, 0ul) + { + TestName = $"Rejected if balance is not enough due to data gas price hiking, all funds are returned", + ExpectedResult = UInt256.Zero, + }; + yield return new TestCaseData((UInt256)(GasCostOf.Transaction + Eip4844Constants.DataGasPerBlob), 1, 2ul, 0ul, 0ul) + { + TestName = $"Rejected if balance does not cover {nameof(Transaction.MaxFeePerDataGas)}, all funds are returned", + ExpectedResult = UInt256.Zero, + }; + yield return new TestCaseData((UInt256)(GasCostOf.Transaction + 2 * Eip4844Constants.DataGasPerBlob + 41), 1, 2ul, 0ul, 42ul) + { + TestName = $"Rejected if balance does not cover {nameof(Transaction.MaxFeePerDataGas)} + {nameof(Transaction.Value)}, all funds are returned", + ExpectedResult = UInt256.Zero, + }; + } +} diff --git a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs index 0ac0d44fa5d..e64c27bfcea 100644 --- a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs @@ -6,6 +6,7 @@ using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Extensions; +using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Db; @@ -43,8 +44,7 @@ public void Setup() _stateProvider.CommitTree(0); VirtualMachine virtualMachine = new(TestBlockhashProvider.Instance, _specProvider, LimboLogs.Instance); - _transactionProcessor = new TransactionProcessor(_specProvider, _stateProvider, virtualMachine, - LimboLogs.Instance); + _transactionProcessor = new TransactionProcessor(_specProvider, _stateProvider, virtualMachine, LimboLogs.Instance); _ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId, LimboLogs.Instance); } diff --git a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs index 6624658940b..95c5cca1c54 100644 --- a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs +++ b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs @@ -3,10 +3,12 @@ using System; using System.Numerics; +using Nethermind.Blockchain.Find; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; +using Nethermind.Core.Test; using Nethermind.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; @@ -107,9 +109,11 @@ protected T Execute(T tracer, byte[] code, ForkActivation? forkActivation = n return tracer; } - protected TestAllTracerWithOutput Execute(long blockNumber, long gasLimit, byte[] code, long blockGasLimit = DefaultBlockGasLimit, ulong timestamp = 0, byte[][] blobVersionedHashes = null) + protected TestAllTracerWithOutput Execute(long blockNumber, long gasLimit, byte[] code, + long blockGasLimit = DefaultBlockGasLimit, ulong timestamp = 0, byte[][] blobVersionedHashes = null) { - (Block block, Transaction transaction) = PrepareTx(blockNumber, gasLimit, code, blockGasLimit: blockGasLimit, timestamp: timestamp, blobVersionedHashes: blobVersionedHashes); + (Block block, Transaction transaction) = PrepareTx(blockNumber, gasLimit, code, + blockGasLimit: blockGasLimit, timestamp: timestamp, blobVersionedHashes: blobVersionedHashes); TestAllTracerWithOutput tracer = CreateTracer(); _processor.Execute(transaction, block.Header, tracer); return tracer; @@ -167,7 +171,8 @@ protected TestAllTracerWithOutput Execute(long blockNumber, long gasLimit, byte[ return (block, transaction); } - protected (Block block, Transaction transaction) PrepareTx(long blockNumber, long gasLimit, byte[] code, byte[] input, UInt256 value, SenderRecipientAndMiner senderRecipientAndMiner = null) + protected (Block block, Transaction transaction) PrepareTx(long blockNumber, long gasLimit, byte[] code, + byte[] input, UInt256 value, SenderRecipientAndMiner senderRecipientAndMiner = null) { senderRecipientAndMiner ??= SenderRecipientAndMiner.Default; @@ -202,7 +207,8 @@ protected TestAllTracerWithOutput Execute(long blockNumber, long gasLimit, byte[ return (block, transaction); } - protected (Block block, Transaction transaction) PrepareInitTx(long blockNumber, long gasLimit, byte[] code, SenderRecipientAndMiner senderRecipientAndMiner = null) + protected (Block block, Transaction transaction) PrepareInitTx(long blockNumber, long gasLimit, byte[] code, + SenderRecipientAndMiner senderRecipientAndMiner = null) { senderRecipientAndMiner ??= SenderRecipientAndMiner.Default; TestState.CreateAccount(senderRecipientAndMiner.Sender, 100.Ether()); @@ -234,6 +240,8 @@ protected virtual Block BuildBlock(long blockNumber, SenderRecipientAndMiner sen .WithTransactions(tx is null ? new Transaction[0] : new[] { tx }) .WithGasLimit(blockGasLimit) .WithBeneficiary(senderRecipientAndMiner.Miner) + .WithDataGasUsed(0) + .WithExcessDataGas(0) .WithTimestamp(timestamp) .TestObject; } diff --git a/src/Nethermind/Nethermind.Evm/DataGasCalculator.cs b/src/Nethermind/Nethermind.Evm/DataGasCalculator.cs new file mode 100644 index 00000000000..444b695f8e2 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/DataGasCalculator.cs @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Int256; + +namespace Nethermind.Evm; + +public static class DataGasCalculator +{ + public static ulong CalculateDataGas(int blobCount) => + (ulong)blobCount * Eip4844Constants.DataGasPerBlob; + + public static ulong CalculateDataGas(Transaction transaction) => + CalculateDataGas(transaction.BlobVersionedHashes?.Length ?? 0); + + public static ulong CalculateDataGas(Transaction[] transactions) + { + int blobCount = 0; + foreach (Transaction tx in transactions) + { + if (tx.SupportsBlobs) + { + blobCount += tx.BlobVersionedHashes!.Length; + } + } + + return CalculateDataGas(blobCount); + } + + public static bool TryCalculateDataGasPrice(BlockHeader header, Transaction transaction, out UInt256 dataGasPrice) + { + if (!TryCalculateDataGasPricePerUnit(header.ExcessDataGas.Value, out UInt256 dataGasPricePerUnit)) + { + dataGasPrice = UInt256.MaxValue; + return false; + } + return !UInt256.MultiplyOverflow(CalculateDataGas(transaction), dataGasPricePerUnit, out dataGasPrice); + } + + public static bool TryCalculateDataGasPricePerUnit(BlockHeader header, out UInt256 dataGasPricePerUnit) => + header.ExcessDataGas is null + ? throw new ArgumentException(nameof(BlockHeader.ExcessDataGas)) + : TryCalculateDataGasPricePerUnit(header.ExcessDataGas.Value, out dataGasPricePerUnit); + + public static bool TryCalculateDataGasPricePerUnit(ulong excessDataGas, out UInt256 dataGasPricePerUnit) + { + static bool FakeExponentialOverflow(UInt256 factor, UInt256 num, UInt256 denominator, out UInt256 dataGasPricePerUnit) + { + UInt256 output = UInt256.Zero; + + if (UInt256.MultiplyOverflow(factor, denominator, out UInt256 numAccum)) + { + dataGasPricePerUnit = UInt256.MaxValue; + return true; + } + + for (UInt256 i = 1; numAccum > 0; i++) + { + if (UInt256.AddOverflow(output, numAccum, out output)) + { + dataGasPricePerUnit = UInt256.MaxValue; + return true; + } + + if (UInt256.MultiplyOverflow(numAccum, num, out UInt256 updatedNumAccum)) + { + dataGasPricePerUnit = UInt256.MaxValue; + return true; + } + + if (UInt256.MultiplyOverflow(i, denominator, out UInt256 multipliedDeniminator)) + { + dataGasPricePerUnit = UInt256.MaxValue; + return true; + } + + numAccum = updatedNumAccum / multipliedDeniminator; + } + + dataGasPricePerUnit = output / denominator; + return false; + } + + return !FakeExponentialOverflow(Eip4844Constants.MinDataGasPrice, excessDataGas, Eip4844Constants.DataGasUpdateFraction, out dataGasPricePerUnit); + } + + public static ulong? CalculateExcessDataGas(BlockHeader? parentBlockHeader, IReleaseSpec releaseSpec) + { + if (!releaseSpec.IsEip4844Enabled) + { + return null; + } + + if (parentBlockHeader is null) + { + return 0; + } + + ulong excessDataGas = parentBlockHeader.ExcessDataGas ?? 0; + excessDataGas += parentBlockHeader.DataGasUsed ?? 0; + return excessDataGas < Eip4844Constants.TargetDataGasPerBlock + ? 0 + : (excessDataGas - Eip4844Constants.TargetDataGasPerBlock); + } +} diff --git a/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs b/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs index fc7459c9c56..b030b204bb2 100644 --- a/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs +++ b/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs @@ -9,89 +9,88 @@ using Nethermind.Core.Specs; using Nethermind.Int256; -namespace Nethermind.Evm +namespace Nethermind.Evm; + +public static class IntrinsicGasCalculator { - public static class IntrinsicGasCalculator + public static long Calculate(Transaction transaction, IReleaseSpec releaseSpec) { - public static long Calculate(Transaction transaction, IReleaseSpec releaseSpec) + long result = GasCostOf.Transaction; + result += DataCost(transaction, releaseSpec); + result += CreateCost(transaction, releaseSpec); + result += AccessListCost(transaction, releaseSpec); + return result; + } + + private static long CreateCost(Transaction transaction, IReleaseSpec releaseSpec) + { + long createCost = 0; + if (transaction.IsContractCreation && releaseSpec.IsEip2Enabled) { - long result = GasCostOf.Transaction; - result += DataCost(transaction, releaseSpec); - result += CreateCost(transaction, releaseSpec); - result += AccessListCost(transaction, releaseSpec); - return result; + createCost += GasCostOf.TxCreate; } - private static long CreateCost(Transaction transaction, IReleaseSpec releaseSpec) + return createCost; + } + + private static long DataCost(Transaction transaction, IReleaseSpec releaseSpec) + { + long txDataNonZeroGasCost = + releaseSpec.IsEip2028Enabled ? GasCostOf.TxDataNonZeroEip2028 : GasCostOf.TxDataNonZero; + long dataCost = 0; + if (transaction.Data is not null) { - long createCost = 0; - if (transaction.IsContractCreation && releaseSpec.IsEip2Enabled) + Span data = transaction.Data.Value.Span; + for (int i = 0; i < transaction.DataLength; i++) { - createCost += GasCostOf.TxCreate; + dataCost += data[i] == 0 ? GasCostOf.TxDataZero : txDataNonZeroGasCost; } - - return createCost; } - private static long DataCost(Transaction transaction, IReleaseSpec releaseSpec) + if (transaction.IsContractCreation && releaseSpec.IsEip3860Enabled) { - long txDataNonZeroGasCost = - releaseSpec.IsEip2028Enabled ? GasCostOf.TxDataNonZeroEip2028 : GasCostOf.TxDataNonZero; - long dataCost = 0; - if (transaction.Data is not null) - { - Span data = transaction.Data.Value.Span; - for (int i = 0; i < transaction.DataLength; i++) - { - dataCost += data[i] == 0 ? GasCostOf.TxDataZero : txDataNonZeroGasCost; - } - } - - if (transaction.IsContractCreation && releaseSpec.IsEip3860Enabled) - { - dataCost += EvmPooledMemory.Div32Ceiling((UInt256)transaction.DataLength) * GasCostOf.InitCodeWord; - } - - return dataCost; + dataCost += EvmPooledMemory.Div32Ceiling((UInt256)transaction.DataLength) * GasCostOf.InitCodeWord; } - private static long AccessListCost(Transaction transaction, IReleaseSpec releaseSpec) + return dataCost; + } + + private static long AccessListCost(Transaction transaction, IReleaseSpec releaseSpec) + { + AccessList? accessList = transaction.AccessList; + long accessListCost = 0; + if (accessList is not null) { - AccessList? accessList = transaction.AccessList; - long accessListCost = 0; - if (accessList is not null) + if (releaseSpec.UseTxAccessLists) { - if (releaseSpec.UseTxAccessLists) + if (accessList.IsNormalized) { - if (accessList.IsNormalized) - { - accessListCost += accessList.Data.Count * GasCostOf.AccessAccountListEntry; - accessListCost += accessList.Data.Sum(d => d.Value.Count) * - GasCostOf.AccessStorageListEntry; - } - else + accessListCost += accessList.Data.Count * GasCostOf.AccessAccountListEntry; + accessListCost += accessList.Data.Sum(d => d.Value.Count) * + GasCostOf.AccessStorageListEntry; + } + else + { + foreach (object o in accessList.OrderQueue!) { - foreach (object o in accessList.OrderQueue!) + if (o is Address) + { + accessListCost += GasCostOf.AccessAccountListEntry; + } + else { - if (o is Address) - { - accessListCost += GasCostOf.AccessAccountListEntry; - } - else - { - accessListCost += GasCostOf.AccessStorageListEntry; - } + accessListCost += GasCostOf.AccessStorageListEntry; } } } - else - { - throw new InvalidDataException( - $"Transaction with an access list received within the context of {releaseSpec.Name}. Eip-2930 is not enabled."); - } } - - return accessListCost; + else + { + throw new InvalidDataException( + $"Transaction with an access list received within the context of {releaseSpec.Name}. Eip-2930 is not enabled."); + } } + + return accessListCost; } } diff --git a/src/Nethermind/Nethermind.Evm/TransactionExtensions.cs b/src/Nethermind/Nethermind.Evm/TransactionExtensions.cs index a5f85fe7075..1a5b800f7e0 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionExtensions.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionExtensions.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using Nethermind.Core; using Nethermind.Int256; @@ -14,5 +15,44 @@ tx.To is not null : tx.IsSystem() ? tx.SenderAddress : ContractAddress.From(tx.SenderAddress, nonce > 0 ? nonce - 1 : nonce); + + public static TxGasInfo GetGasInfo(this Transaction tx, bool is1559Enabled, BlockHeader header) + { + UInt256 effectiveGasPrice = tx.CalculateEffectiveGasPrice(is1559Enabled, header.BaseFeePerGas); + + if (tx.SupportsBlobs) + { + if (!DataGasCalculator.TryCalculateDataGasPricePerUnit(header, out UInt256 dataGasPrice)) + { + throw new ArgumentException(nameof(dataGasPrice)); + } + ulong dataGas = DataGasCalculator.CalculateDataGas(tx); + + return new(effectiveGasPrice, dataGasPrice, dataGas); + } + + return new(effectiveGasPrice, null, null); + } + } + + public struct TxGasInfo + { + public TxGasInfo() { } + + public TxGasInfo(UInt256? effectiveGasPrice, UInt256? dataGasPrice, ulong? dataGasUsed) + { + EffectiveGasPrice = effectiveGasPrice; + DataGasPrice = dataGasPrice; + DataGasUsed = dataGasUsed; + } + + public TxGasInfo(UInt256? effectiveGasPrice) + { + EffectiveGasPrice = effectiveGasPrice; + } + + public UInt256? EffectiveGasPrice { get; private set; } + public UInt256? DataGasPrice { get; private set; } + public ulong? DataGasUsed { get; private set; } } } diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs index 9c2e952bd83..0bcbd0438cc 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs @@ -17,7 +17,6 @@ using Nethermind.State; using Nethermind.State.Tracing; using static Nethermind.Core.Extensions.MemoryExtensions; -using Transaction = Nethermind.Core.Transaction; using static Nethermind.Evm.VirtualMachine; @@ -330,6 +329,7 @@ private bool RecoverSenderIfNeeded(Transaction tx, IReleaseSpec spec, ExecutionO return deleteCallerAccount; } + protected virtual bool ValidateSender(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts) { bool validate = !opts.HasFlag(ExecutionOptions.NoValidation); @@ -349,7 +349,6 @@ private bool BuyGas(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTr { premiumPerGas = UInt256.Zero; senderReservedGasPayment = UInt256.Zero; - bool validate = !opts.HasFlag(ExecutionOptions.NoValidation); if (!tx.IsSystem() && validate) @@ -369,15 +368,39 @@ private bool BuyGas(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTr return false; } - bool overflows = UInt256.MultiplyOverflow((UInt256)tx.GasLimit, tx.MaxFeePerGas, out UInt256 maxGasFee); - if (spec.IsEip1559Enabled && !tx.IsFree() && (overflows || balanceLeft < maxGasFee)) + + bool overflows; + if (spec.IsEip1559Enabled && !tx.IsFree()) { - TraceLogInvalidTx(tx, $"INSUFFICIENT_MAX_FEE_PER_GAS_FOR_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}, MAX_FEE_PER_GAS: {tx.MaxFeePerGas}"); - QuickFail(tx, header, spec, tracer, "insufficient MaxFeePerGas for sender balance"); - return false; + overflows = UInt256.MultiplyOverflow((UInt256)tx.GasLimit, tx.MaxFeePerGas, out UInt256 maxGasFee); + if (overflows || balanceLeft < maxGasFee) + { + TraceLogInvalidTx(tx, $"INSUFFICIENT_MAX_FEE_PER_GAS_FOR_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}, MAX_FEE_PER_GAS: {tx.MaxFeePerGas}"); + QuickFail(tx, header, spec, tracer, "insufficient MaxFeePerGas for sender balance"); + return false; + } + if (tx.SupportsBlobs) + { + overflows = UInt256.MultiplyOverflow(DataGasCalculator.CalculateDataGas(tx), (UInt256)tx.MaxFeePerDataGas, out UInt256 maxDataGasFee); + if (overflows || UInt256.AddOverflow(maxGasFee, maxDataGasFee, out UInt256 multidimGasFee) || multidimGasFee > balanceLeft) + { + TraceLogInvalidTx(tx, $"INSUFFICIENT_MAX_FEE_PER_DATA_GAS_FOR_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}"); + QuickFail(tx, header, spec, tracer, "insufficient sender balance"); + return false; + } + } } overflows = UInt256.MultiplyOverflow((UInt256)tx.GasLimit, effectiveGasPrice, out senderReservedGasPayment); + if (!overflows && tx.SupportsBlobs) + { + overflows = !DataGasCalculator.TryCalculateDataGasPrice(header, tx, out UInt256 dataGasFee); + if (!overflows) + { + overflows = UInt256.AddOverflow(senderReservedGasPayment, dataGasFee, out senderReservedGasPayment); + } + } + if (overflows || senderReservedGasPayment > balanceLeft) { TraceLogInvalidTx(tx, $"INSUFFICIENT_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}"); diff --git a/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs b/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs index 6ace9a200d0..0752d82f079 100644 --- a/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs +++ b/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs @@ -28,6 +28,7 @@ using NSubstitute; using NUnit.Framework; using Nethermind.Config; +using Nethermind.Evm; namespace Nethermind.Facade.Test { @@ -231,20 +232,34 @@ public void Bridge_head_is_correct(long headNumber) _blockchainBridge.HeadBlock.Should().Be(head); } - [TestCase(true)] - [TestCase(false)] - public void GetReceiptAndEffectiveGasPrice_returns_correct_results(bool isCanonical) + [TestCase(true, true)] + [TestCase(false, true)] + [TestCase(true, false)] + [TestCase(false, false)] + public void GetReceiptAndGasInfo_returns_correct_results(bool isCanonical, bool postEip4844) { Keccak txHash = TestItem.KeccakA; Keccak blockHash = TestItem.KeccakB; UInt256 effectiveGasPrice = 123; - Transaction tx = Build.A.Transaction - .WithGasPrice(effectiveGasPrice) - .TestObject; - Block block = Build.A.Block - .WithTransactions(tx) - .TestObject; + Transaction tx = postEip4844 + ? Build.A.Transaction + .WithGasPrice(effectiveGasPrice) + .WithType(TxType.Blob) + .WithMaxFeePerDataGas(2) + .WithBlobVersionedHashes(2) + .TestObject + : Build.A.Transaction + .WithGasPrice(effectiveGasPrice) + .TestObject; + Block block = postEip4844 + ? Build.A.Block + .WithTransactions(tx) + .WithExcessDataGas(2) + .TestObject + : Build.A.Block + .WithTransactions(tx) + .TestObject; TxReceipt receipt = Build.A.Receipt .WithBlockHash(blockHash) .WithTransactionHash(txHash) @@ -255,8 +270,16 @@ public void GetReceiptAndEffectiveGasPrice_returns_correct_results(bool isCanoni _receiptStorage.FindBlockHash(txHash).Returns(blockHash); _receiptStorage.Get(block).Returns(new[] { receipt }); - (TxReceipt Receipt, UInt256? EffectiveGasPrice, int LogIndexStart) result = isCanonical ? (receipt, effectiveGasPrice, 0) : (null, null, 0); - _blockchainBridge.GetReceiptAndEffectiveGasPrice(txHash).Should().BeEquivalentTo(result); + (TxReceipt? Receipt, TxGasInfo? GasInfo, int LogIndexStart) result = postEip4844 + ? (receipt, new(effectiveGasPrice, 1, 262144), 0) + : (receipt, new(effectiveGasPrice), 0); + + if (!isCanonical) + { + result = (null, null, 0); + } + + _blockchainBridge.GetReceiptAndGasInfo(txHash).Should().BeEquivalentTo(result); } } } diff --git a/src/Nethermind/Nethermind.Facade.Test/Nethermind.Facade.Test.csproj b/src/Nethermind/Nethermind.Facade.Test/Nethermind.Facade.Test.csproj index 15608eea2e8..5feafc0ae10 100644 --- a/src/Nethermind/Nethermind.Facade.Test/Nethermind.Facade.Test.csproj +++ b/src/Nethermind/Nethermind.Facade.Test/Nethermind.Facade.Test.csproj @@ -3,6 +3,7 @@ net7.0 true + annotations diff --git a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs index 1b461d2d70c..50402f61ff7 100644 --- a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs @@ -83,7 +83,7 @@ public Block? HeadBlock public bool IsMining { get; } - public (TxReceipt Receipt, UInt256? EffectiveGasPrice, int LogIndexStart) GetReceiptAndEffectiveGasPrice(Keccak txHash) + public (TxReceipt? Receipt, TxGasInfo? GasInfo, int LogIndexStart) GetReceiptAndGasInfo(Keccak txHash) { Keccak blockHash = _receiptFinder.FindBlockHash(txHash); if (blockHash is not null) @@ -96,15 +96,14 @@ public Block? HeadBlock int logIndexStart = txReceipts.GetBlockLogFirstIndex(txReceipt.Index); Transaction tx = block.Transactions[txReceipt.Index]; bool is1559Enabled = _specProvider.GetSpecFor1559(block.Number).IsEip1559Enabled; - UInt256 effectiveGasPrice = tx.CalculateEffectiveGasPrice(is1559Enabled, block.Header.BaseFeePerGas); - return (txReceipt, effectiveGasPrice, logIndexStart); + return (txReceipt, tx.GetGasInfo(is1559Enabled, block.Header), logIndexStart); } } return (null, null, 0); } - public (TxReceipt Receipt, Transaction Transaction, UInt256? baseFee) GetTransaction(Keccak txHash) + public (TxReceipt? Receipt, Transaction Transaction, UInt256? baseFee) GetTransaction(Keccak txHash) { Keccak blockHash = _receiptFinder.FindBlockHash(txHash); if (blockHash is not null) @@ -253,8 +252,7 @@ private void CallAndRestore( blockHeader.Number + 1, blockHeader.GasLimit, Math.Max(blockHeader.Timestamp + 1, _timestamper.UnixTime.Seconds), - Array.Empty(), - null) + Array.Empty()) : new( blockHeader.ParentHash!, blockHeader.UnclesHash!, @@ -263,8 +261,7 @@ private void CallAndRestore( blockHeader.Number, blockHeader.GasLimit, blockHeader.Timestamp, - blockHeader.ExtraData, - blockHeader.ExcessDataGas); + blockHeader.ExtraData); IReleaseSpec releaseSpec = _specProvider.GetSpec(callHeader); callHeader.BaseFeePerGas = treatBlockHeaderAsParentBlock @@ -273,9 +270,9 @@ private void CallAndRestore( if (releaseSpec.IsEip4844Enabled) { - // TODO: Calculate ExcessDataGas depending on parent ExcessDataGas and number of blobs in txs + callHeader.DataGasUsed = DataGasCalculator.CalculateDataGas(transaction); callHeader.ExcessDataGas = treatBlockHeaderAsParentBlock - ? 0 + ? DataGasCalculator.CalculateExcessDataGas(blockHeader, releaseSpec) : blockHeader.ExcessDataGas; } callHeader.MixHash = blockHeader.MixHash; diff --git a/src/Nethermind/Nethermind.Facade/Eth/EthSyncingInfo.cs b/src/Nethermind/Nethermind.Facade/Eth/EthSyncingInfo.cs index a2c0e152e7e..d2843b54013 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/EthSyncingInfo.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/EthSyncingInfo.cs @@ -35,7 +35,7 @@ public EthSyncingInfo( public SyncingResult GetFullInfo() { - long bestSuggestedNumber = _blockTree.FindBestSuggestedHeader().Number; + long bestSuggestedNumber = _blockTree.FindBestSuggestedHeader()?.Number ?? 0; long headNumberOrZero = _blockTree.Head?.Number ?? 0; bool isSyncing = bestSuggestedNumber > headNumberOrZero + 8; SyncMode syncMode = _syncModeSelector.Current; diff --git a/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs index ad6c4ff52d2..48b1058460d 100644 --- a/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs @@ -7,6 +7,7 @@ using Nethermind.Blockchain.Find; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Evm; using Nethermind.Facade.Filters; using Nethermind.Int256; using Nethermind.Trie; @@ -21,8 +22,8 @@ public interface IBlockchainBridge : ILogFinder void RecoverTxSenders(Block block); Address? RecoverTxSender(Transaction tx); TxReceipt GetReceipt(Keccak txHash); - (TxReceipt Receipt, UInt256? EffectiveGasPrice, int LogIndexStart) GetReceiptAndEffectiveGasPrice(Keccak txHash); - (TxReceipt Receipt, Transaction Transaction, UInt256? baseFee) GetTransaction(Keccak txHash); + (TxReceipt? Receipt, TxGasInfo? GasInfo, int LogIndexStart) GetReceiptAndGasInfo(Keccak txHash); + (TxReceipt? Receipt, Transaction Transaction, UInt256? baseFee) GetTransaction(Keccak txHash); BlockchainBridge.CallOutput Call(BlockHeader header, Transaction tx, CancellationToken cancellationToken); BlockchainBridge.CallOutput EstimateGas(BlockHeader header, Transaction tx, CancellationToken cancellationToken); BlockchainBridge.CallOutput CreateAccessList(BlockHeader header, Transaction tx, CancellationToken cancellationToken, bool optimize); diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/BlockModel.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/BlockModel.cs index 8346128af3a..656e5c9f6ed 100644 --- a/src/Nethermind/Nethermind.Facade/Proxy/Models/BlockModel.cs +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/BlockModel.cs @@ -28,12 +28,11 @@ public class BlockModel public UInt256 TotalDifficulty { get; set; } public List Transactions { get; set; } public Keccak TransactionsRoot { get; set; } - public UInt256? ExcessDataGas { get; set; } public Block ToBlock() { Block block = new(new BlockHeader(ParentHash, Sha3Uncles, Miner, Difficulty, (long)Number, - (long)GasLimit, Timestamp, ExtraData, ExcessDataGas)); + (long)GasLimit, Timestamp, ExtraData)); block.Header.StateRoot = StateRoot; block.Header.GasUsed = (long)GasUsed; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Data/ReceiptsForRpcTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Data/ReceiptsForRpcTests.cs index 06e71db2bf5..2ab81efce34 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Data/ReceiptsForRpcTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Data/ReceiptsForRpcTests.cs @@ -38,7 +38,7 @@ public void Are_log_indexes_unique() }; UInt256 effectiveGasPrice = new(5526); - ReceiptForRpc receiptForRpc = new(txHash, receipt1, effectiveGasPrice); + ReceiptForRpc receiptForRpc = new(txHash, receipt1, new(effectiveGasPrice)); long?[] indexes = receiptForRpc.Logs.Select(log => log.LogIndex).ToArray(); long?[] expected = { 0, 1, 2 }; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs index 5a3bf224497..cc7488c1b5f 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs @@ -818,8 +818,9 @@ public async Task Eth_get_block_by_number_with_recovering_sender_from_receipts() Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":{\"author\":\"0x0000000000000000000000000000000000000000\",\"difficulty\":\"0xf4240\",\"extraData\":\"0x010203\",\"gasLimit\":\"0x3d0900\",\"gasUsed\":\"0x0\",\"hash\":\"0xe3026a6708b90d5cb25557ac38ddc3f5ef550af10f31e1cf771524da8553fa1c\",\"logsBloom\":\"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"miner\":\"0x0000000000000000000000000000000000000000\",\"mixHash\":\"0x2ba5557a4c62a513c7e56d1bf13373e0da6bec016755483e91589fe1c6d212e2\",\"nonce\":\"0x00000000000003e8\",\"number\":\"0x1\",\"parentHash\":\"0xff483e972a04a9a62bb4b7d04ae403c615604e4090521ecc5bb7af67f71be09c\",\"receiptsRoot\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"sha3Uncles\":\"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347\",\"size\":\"0x221\",\"stateRoot\":\"0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f\",\"totalDifficulty\":\"0x0\",\"timestamp\":\"0xf4240\",\"transactions\":[{\"nonce\":\"0x0\",\"blockHash\":\"0xe3026a6708b90d5cb25557ac38ddc3f5ef550af10f31e1cf771524da8553fa1c\",\"blockNumber\":\"0x1\",\"transactionIndex\":\"0x0\",\"from\":\"0x2d36e6c27c34ea22620e7b7c45de774599406cf3\",\"to\":\"0x0000000000000000000000000000000000000000\",\"value\":\"0x1\",\"gasPrice\":\"0x1\",\"gas\":\"0x5208\",\"data\":\"0x\",\"input\":\"0x\",\"type\":\"0x0\"}],\"transactionsRoot\":\"0x29cc403075ed3d1d6af940d577125cc378ee5a26f7746cbaf87f1cf4a38258b5\",\"uncles\":[]},\"id\":67}")); } - [Test] - public async Task Eth_get_transaction_receipt() + [TestCase(false)] + [TestCase(true)] + public async Task Eth_get_transaction_receipt(bool postEip4844) { using Context ctx = await Context.Create(); IBlockchainBridge blockchainBridge = Substitute.For(); @@ -840,7 +841,9 @@ public async Task Eth_get_transaction_receipt() .WithLogs(entries).TestObject; TxReceipt[] receiptsTab = { receipt }; - blockchainBridge.GetReceiptAndEffectiveGasPrice(Arg.Any()).Returns((receipt, UInt256.One, 0)); + + blockchainBridge.GetReceiptAndGasInfo(Arg.Any()) + .Returns((receipt, postEip4844 ? new(UInt256.One, 2, 3) : new(UInt256.One), 0)); blockFinder.FindBlock(Arg.Any()).Returns(block); receiptFinder.Get(Arg.Any()).Returns(receiptsTab); receiptFinder.Get(Arg.Any()).Returns(receiptsTab); @@ -848,7 +851,10 @@ public async Task Eth_get_transaction_receipt() ctx.Test = await TestRpcBlockchain.ForTest(SealEngineType.NethDev).WithBlockFinder(blockFinder).WithReceiptFinder(receiptFinder).WithBlockchainBridge(blockchainBridge).Build(); string serialized = ctx.Test.TestEthRpc("eth_getTransactionReceipt", TestItem.KeccakA.ToString()); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":{\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"transactionIndex\":\"0x2\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"cumulativeGasUsed\":\"0x3e8\",\"gasUsed\":\"0x64\",\"effectiveGasPrice\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"contractAddress\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"logs\":[{\"removed\":false,\"logIndex\":\"0x0\",\"transactionIndex\":\"0x2\",\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"address\":\"0x0000000000000000000000000000000000000000\",\"data\":\"0x\",\"topics\":[\"0x0000000000000000000000000000000000000000000000000000000000000000\"]},{\"removed\":false,\"logIndex\":\"0x1\",\"transactionIndex\":\"0x2\",\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"address\":\"0x0000000000000000000000000000000000000000\",\"data\":\"0x\",\"topics\":[\"0x0000000000000000000000000000000000000000000000000000000000000000\"]}],\"logsBloom\":\"0x00000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000\",\"root\":\"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111\",\"status\":\"0x1\",\"error\":\"error\",\"type\":\"0x0\"},\"id\":67}")); + if (postEip4844) + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":{\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"transactionIndex\":\"0x2\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"cumulativeGasUsed\":\"0x3e8\",\"gasUsed\":\"0x64\",\"dataGasUsed\":\"0x3\",\"dataGasPrice\":\"0x2\",\"effectiveGasPrice\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"contractAddress\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"logs\":[{\"removed\":false,\"logIndex\":\"0x0\",\"transactionIndex\":\"0x2\",\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"address\":\"0x0000000000000000000000000000000000000000\",\"data\":\"0x\",\"topics\":[\"0x0000000000000000000000000000000000000000000000000000000000000000\"]},{\"removed\":false,\"logIndex\":\"0x1\",\"transactionIndex\":\"0x2\",\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"address\":\"0x0000000000000000000000000000000000000000\",\"data\":\"0x\",\"topics\":[\"0x0000000000000000000000000000000000000000000000000000000000000000\"]}],\"logsBloom\":\"0x00000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000\",\"root\":\"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111\",\"status\":\"0x1\",\"error\":\"error\",\"type\":\"0x0\"},\"id\":67}")); + else + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":{\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"transactionIndex\":\"0x2\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"cumulativeGasUsed\":\"0x3e8\",\"gasUsed\":\"0x64\",\"effectiveGasPrice\":\"0x1\",\"from\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"to\":\"0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358\",\"contractAddress\":\"0x76e68a8696537e4141926f3e528733af9e237d69\",\"logs\":[{\"removed\":false,\"logIndex\":\"0x0\",\"transactionIndex\":\"0x2\",\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"address\":\"0x0000000000000000000000000000000000000000\",\"data\":\"0x\",\"topics\":[\"0x0000000000000000000000000000000000000000000000000000000000000000\"]},{\"removed\":false,\"logIndex\":\"0x1\",\"transactionIndex\":\"0x2\",\"transactionHash\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"blockHash\":\"0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72\",\"blockNumber\":\"0x2\",\"address\":\"0x0000000000000000000000000000000000000000\",\"data\":\"0x\",\"topics\":[\"0x0000000000000000000000000000000000000000000000000000000000000000\"]}],\"logsBloom\":\"0x00000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000\",\"root\":\"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111\",\"status\":\"0x1\",\"error\":\"error\",\"type\":\"0x0\"},\"id\":67}")); } @@ -903,7 +909,7 @@ public async Task Eth_get_transaction_receipt_when_block_has_few_receipts() Logs = logEntries }; - blockchainBridge.GetReceiptAndEffectiveGasPrice(Arg.Any()).Returns((receipt2, UInt256.One, 2)); + blockchainBridge.GetReceiptAndGasInfo(Arg.Any()).Returns((receipt2, new(UInt256.One), 2)); TxReceipt[] receipts = { receipt1, receipt2 }; @@ -959,7 +965,7 @@ public async Task Eth_getTransactionReceipt_return_info_about_mined_tx() blockFinder.FindBlock(Arg.Any()).Returns(block); receiptFinder.Get(Arg.Any()).Returns(receiptsTab); receiptFinder.Get(Arg.Any()).Returns(receiptsTab); - blockchainBridge.GetReceiptAndEffectiveGasPrice(Arg.Any()).Returns((receipt, UInt256.One, 0)); + blockchainBridge.GetReceiptAndGasInfo(Arg.Any()).Returns((receipt, new(UInt256.One), 0)); ctx.Test = await TestRpcBlockchain.ForTest(SealEngineType.NethDev).WithBlockFinder(blockFinder).WithReceiptFinder(receiptFinder).WithBlockchainBridge(blockchainBridge).Build(); string serialized = ctx.Test.TestEthRpc("eth_getTransactionReceipt", tx.Hash!.ToString()); diff --git a/src/Nethermind/Nethermind.JsonRpc/Converters/TxReceiptConverter.cs b/src/Nethermind/Nethermind.JsonRpc/Converters/TxReceiptConverter.cs index 682247df55b..34da8afada2 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Converters/TxReceiptConverter.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Converters/TxReceiptConverter.cs @@ -13,7 +13,7 @@ public class TxReceiptConverter : JsonConverter { public override void WriteJson(JsonWriter writer, TxReceipt value, JsonSerializer serializer) { - serializer.Serialize(writer, new ReceiptForRpc(value.TxHash!, value, UInt256.Zero)); + serializer.Serialize(writer, new ReceiptForRpc(value.TxHash!, value, new(UInt256.Zero))); } public override TxReceipt ReadJson(JsonReader reader, Type objectType, TxReceipt existingValue, bool hasExistingValue, JsonSerializer serializer) diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/ReceiptForRpc.cs b/src/Nethermind/Nethermind.JsonRpc/Data/ReceiptForRpc.cs index 00de50c6f1b..d571e1c2194 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Data/ReceiptForRpc.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Data/ReceiptForRpc.cs @@ -1,10 +1,10 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Linq; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Evm; using Nethermind.Int256; using Newtonsoft.Json; @@ -16,7 +16,7 @@ public ReceiptForRpc() { } - public ReceiptForRpc(Keccak txHash, TxReceipt receipt, UInt256? effectiveGasPrice, int logIndexStart = 0) + public ReceiptForRpc(Keccak txHash, TxReceipt receipt, TxGasInfo gasInfo, int logIndexStart = 0) { TransactionHash = txHash; TransactionIndex = receipt.Index; @@ -24,7 +24,9 @@ public ReceiptForRpc(Keccak txHash, TxReceipt receipt, UInt256? effectiveGasPric BlockNumber = receipt.BlockNumber; CumulativeGasUsed = receipt.GasUsedTotal; GasUsed = receipt.GasUsed; - EffectiveGasPrice = effectiveGasPrice; + EffectiveGasPrice = gasInfo.EffectiveGasPrice; + DataGasUsed = gasInfo.DataGasUsed; + DataGasPrice = gasInfo.DataGasPrice; From = receipt.Sender; To = receipt.Recipient; ContractAddress = receipt.ContractAddress; @@ -43,6 +45,12 @@ public ReceiptForRpc(Keccak txHash, TxReceipt receipt, UInt256? effectiveGasPric public long CumulativeGasUsed { get; set; } public long GasUsed { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public ulong? DataGasUsed { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public UInt256? DataGasPrice { get; set; } + public UInt256? EffectiveGasPrice { get; set; } public Address From { get; set; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BlockForRpc.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BlockForRpc.cs index f2f8eda9a44..6e9a82cf353 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BlockForRpc.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/BlockForRpc.cs @@ -58,6 +58,7 @@ public BlockForRpc(Block block, bool includeFullTransactionData, ISpecProvider s if (spec.IsEip4844Enabled) { + DataGasUsed = block.Header.DataGasUsed; ExcessDataGas = block.Header.ExcessDataGas; } } @@ -125,5 +126,8 @@ public BlockForRpc(Block block, bool includeFullTransactionData, ISpecProvider s public Keccak? WithdrawalsRoot { get; set; } [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public UInt256? ExcessDataGas { get; set; } + public ulong? DataGasUsed { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public ulong? ExcessDataGas { get; set; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index 16a47323852..b60d9552cbf 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -14,6 +14,7 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; +using Nethermind.Evm; using Nethermind.Facade; using Nethermind.Facade.Eth; using Nethermind.Facade.Filters; @@ -474,14 +475,14 @@ public ResultWrapper eth_getTransactionByBlockNumberAndIndex( public Task> eth_getTransactionReceipt(Keccak txHash) { - (TxReceipt receipt, UInt256? effectiveGasPrice, int logIndexStart) = _blockchainBridge.GetReceiptAndEffectiveGasPrice(txHash); - if (receipt is null) + (TxReceipt? receipt, TxGasInfo? gasInfo, int logIndexStart) = _blockchainBridge.GetReceiptAndGasInfo(txHash); + if (receipt is null || gasInfo is null) { return Task.FromResult(ResultWrapper.Success(null)); } if (_logger.IsTrace) _logger.Trace($"eth_getTransactionReceipt request {txHash}, result: {txHash}"); - return Task.FromResult(ResultWrapper.Success(new(txHash, receipt, effectiveGasPrice, logIndexStart))); + return Task.FromResult(ResultWrapper.Success(new(txHash, receipt, gasInfo.Value, logIndexStart))); } public ResultWrapper eth_getUncleByBlockHashAndIndex(Keccak blockHash, UInt256 positionIndex) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityRpcModule.cs index 07680e96e90..7e0b3a92a38 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityRpcModule.cs @@ -11,7 +11,7 @@ using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Crypto; -using Nethermind.Int256; +using Nethermind.Evm; using Nethermind.JsonRpc.Data; using Nethermind.KeyStore; using Nethermind.Serialization.Rlp; @@ -77,7 +77,9 @@ public ResultWrapper parity_getBlockReceipts(BlockParameter blo bool isEip1559Enabled = _specProvider.GetSpec(block.Header).IsEip1559Enabled; IEnumerable result = receipts .Zip(block.Transactions, (r, t) => - new ReceiptForRpc(t.Hash, r, t.CalculateEffectiveGasPrice(isEip1559Enabled, block.BaseFeePerGas), receipts.GetBlockLogFirstIndex(r.Index))); + { + return new ReceiptForRpc(t.Hash, r, t.GetGasInfo(isEip1559Enabled, block.Header), receipts.GetBlockLogFirstIndex(r.Index)); + }); ReceiptForRpc[] resultAsArray = result.ToArray(); return ResultWrapper.Success(resultAsArray); } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofRpcModule.cs index 05427d8a01b..0ead70e46c1 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofRpcModule.cs @@ -4,18 +4,16 @@ using System; using System.Collections.Generic; using System.Linq; -using Nethermind.Blockchain; using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; using Nethermind.Consensus.Tracing; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Crypto; +using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Evm.Tracing.Proofs; -using Nethermind.Facade; using Nethermind.JsonRpc.Data; using Nethermind.Logging; using Nethermind.Serialization.Rlp; @@ -159,7 +157,8 @@ public ResultWrapper proof_getTransactionReceipt(Keccak txHash Transaction? tx = txs.FirstOrDefault(x => x.Hash == txHash); int logIndexStart = _receiptFinder.Get(block).GetBlockLogFirstIndex(receipt.Index); - receiptWithProof.Receipt = new ReceiptForRpc(txHash, receipt, tx?.CalculateEffectiveGasPrice(isEip1559Enabled, block.BaseFeePerGas), logIndexStart); + + receiptWithProof.Receipt = new ReceiptForRpc(txHash, receipt, tx?.GetGasInfo(isEip1559Enabled, block.Header) ?? new(), logIndexStart); receiptWithProof.ReceiptProof = BuildReceiptProofs(block.Header, receipts, receipt.Index); receiptWithProof.TxProof = BuildTxProofs(txs, _specProvider.GetSpec(block.Header), receipt.Index); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceRpcModule.cs index ebaf494751c..92799bf6d51 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Trace/TraceRpcModule.cs @@ -129,7 +129,9 @@ private SearchResult SearchBlockHeaderForTraceCall(BlockParameter b header.Number + 1, header.GasLimit, header.Timestamp + 1, - header.ExtraData); + header.ExtraData, + header.DataGasUsed, + header.ExcessDataGas); header.TotalDifficulty = 2 * header.Difficulty; header.BaseFeePerGas = baseFee; diff --git a/src/Nethermind/Nethermind.Merge.AuRa.Test/AssertionsSetup.cs b/src/Nethermind/Nethermind.Merge.AuRa.Test/AssertionsSetup.cs new file mode 100644 index 00000000000..4ea6730bd46 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.AuRa.Test/AssertionsSetup.cs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using NUnit.Framework; +using NUnit.Framework.Internal; +using FluentAssertions; +using Nethermind.Core; + +namespace Nethermind; + +/// +/// Global settings for the fluent assertions, works for the current assembly only. +/// +[SetUpFixture] +public class AssertionsSetup +{ + [OneTimeSetUp] + public void RunBeforeAnyTests() + { + AssertionOptions.AssertEquivalencyUsing(options => options.Excluding(c => c.Name == nameof(BlockHeader.MaybeParent))); + } +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/AssertionsSetup.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/AssertionsSetup.cs new file mode 100644 index 00000000000..4ea6730bd46 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/AssertionsSetup.cs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using NUnit.Framework; +using NUnit.Framework.Internal; +using FluentAssertions; +using Nethermind.Core; + +namespace Nethermind; + +/// +/// Global settings for the fluent assertions, works for the current assembly only. +/// +[SetUpFixture] +public class AssertionsSetup +{ + [OneTimeSetUp] + public void RunBeforeAnyTests() + { + AssertionOptions.AssertEquivalencyUsing(options => options.Excluding(c => c.Name == nameof(BlockHeader.MaybeParent))); + } +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs index 5a91099f04d..36459b7444e 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs @@ -84,7 +84,7 @@ private ExecutionPayload CreateParentBlockRequestOnHead(IBlockTree blockTree) }; } - private static ExecutionPayload CreateBlockRequest(ExecutionPayload parent, Address miner, IList? withdrawals = null, UInt256? excessDataGas = null, Transaction[]? transactions = null) + private static ExecutionPayload CreateBlockRequest(ExecutionPayload parent, Address miner, IList? withdrawals = null, ulong? dataGasUsed = null, ulong? excessDataGas = null, Transaction[]? transactions = null) { ExecutionPayload blockRequest = new() { @@ -98,6 +98,7 @@ private static ExecutionPayload CreateBlockRequest(ExecutionPayload parent, Addr LogsBloom = Bloom.Empty, Timestamp = parent.Timestamp + 1, Withdrawals = withdrawals, + DataGasUsed = dataGasUsed, ExcessDataGas = excessDataGas, }; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs index 1753a038036..45ac44b43cc 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs @@ -13,6 +13,7 @@ using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; +using Nethermind.Evm; using Nethermind.JsonRpc; using Nethermind.Logging; using Nethermind.Merge.Plugin.Data; @@ -59,8 +60,10 @@ public async Task GetPayloadV3_should_fail_on_unknown_payload() public async Task PayloadV3_should_return_all_the_blobs(int blobTxCount) { (IEngineRpcModule rpcModule, string payloadId) = await BuildAndGetPayloadV3Result(Cancun.Instance, blobTxCount); - BlobsBundleV1 getPayloadResultBlobsBundle = - (await rpcModule.engine_getPayloadV3(Bytes.FromHexString(payloadId))).Data!.BlobsBundle!; + var result = await rpcModule.engine_getPayloadV3(Bytes.FromHexString(payloadId)); + BlobsBundleV1 getPayloadResultBlobsBundle = result.Data!.BlobsBundle!; + Assert.That(result.Data.ExecutionPayload.DataGasUsed, Is.EqualTo(DataGasCalculator.CalculateDataGas(blobTxCount))); + Assert.That(result.Data.ExecutionPayload.ExcessDataGas, Is.Not.Null); Assert.That(getPayloadResultBlobsBundle.Blobs!.Length, Is.EqualTo(blobTxCount)); Assert.That(getPayloadResultBlobsBundle.Commitments!.Length, Is.EqualTo(blobTxCount)); Assert.That(getPayloadResultBlobsBundle.Proofs!.Length, Is.EqualTo(blobTxCount)); @@ -223,7 +226,7 @@ public static IEnumerable BlobVersionedHashesDoNotMatchTestSource private async Task SendNewBlockV3(IEngineRpcModule rpc, MergeTestBlockchain chain, IList? withdrawals) { ExecutionPayload executionPayload = CreateBlockRequest( - CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD, withdrawals, 0); + CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD, withdrawals, 0, 0); ResultWrapper executePayloadResult = await rpc.engine_newPayloadV3(executionPayload, Array.Empty()); executePayloadResult.Data.Status.Should().Be(PayloadStatus.Valid); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PostMergeBlockProducer.cs b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PostMergeBlockProducer.cs index eebd64ff0c3..6bc92ec14c2 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PostMergeBlockProducer.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/BlockProduction/PostMergeBlockProducer.cs @@ -11,11 +11,9 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; -using Nethermind.Crypto; -using Nethermind.Core.Extensions; -using System.Threading; using Nethermind.Logging; using Nethermind.State; +using Nethermind.Evm; namespace Nethermind.Merge.Plugin.BlockProduction { @@ -79,22 +77,29 @@ public virtual Block PrepareEmptyBlock(BlockHeader parent, PayloadAttributes? pa protected override Block PrepareBlock(BlockHeader parent, PayloadAttributes? payloadAttributes = null) { Block block = base.PrepareBlock(parent, payloadAttributes); - AmendHeader(block.Header); + AmendHeader(block.Header, parent); return block; } protected override BlockHeader PrepareBlockHeader(BlockHeader parent, PayloadAttributes? payloadAttributes = null) { BlockHeader blockHeader = base.PrepareBlockHeader(parent, payloadAttributes); - AmendHeader(blockHeader); + AmendHeader(blockHeader, parent); return blockHeader; } // TODO: this seems to me that it should be done in the Eth2 seal engine? - private void AmendHeader(BlockHeader blockHeader) + private void AmendHeader(BlockHeader blockHeader, BlockHeader parent) { blockHeader.ExtraData = _blocksConfig.GetExtraDataBytes(); blockHeader.IsPostMerge = true; + IReleaseSpec spec = _specProvider.GetSpec(blockHeader); + + if (spec.IsEip4844Enabled) + { + blockHeader.DataGasUsed = 0; + blockHeader.ExcessDataGas = DataGasCalculator.CalculateExcessDataGas(parent, spec); + } } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs index 2b097f8058f..2c24119198f 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs @@ -39,6 +39,7 @@ public ExecutionPayload(Block block) Timestamp = block.Timestamp; BaseFeePerGas = block.BaseFeePerGas; Withdrawals = block.Withdrawals; + DataGasUsed = block.DataGasUsed; ExcessDataGas = block.ExcessDataGas; SetTransactions(block.Transactions); @@ -93,12 +94,20 @@ public byte[][] Transactions /// public IEnumerable? Withdrawals { get; set; } + /// + /// Gets or sets as defined in + /// EIP-4844. + /// + [JsonProperty(ItemConverterType = typeof(NullableUInt256Converter), NullValueHandling = NullValueHandling.Ignore)] + public ulong? DataGasUsed { get; set; } + /// /// Gets or sets as defined in /// EIP-4844. /// [JsonProperty(ItemConverterType = typeof(NullableUInt256Converter), NullValueHandling = NullValueHandling.Ignore)] - public UInt256? ExcessDataGas { get; set; } + public ulong? ExcessDataGas { get; set; } + /// /// Creates the execution block from payload. @@ -119,7 +128,9 @@ public virtual bool TryGetBlock(out Block? block, UInt256? totalDifficulty = nul BlockNumber, GasLimit, Timestamp, - ExtraData) + ExtraData, + DataGasUsed, + ExcessDataGas) { Hash = BlockHash, ReceiptsRoot = ReceiptsRoot, @@ -134,7 +145,6 @@ public virtual bool TryGetBlock(out Block? block, UInt256? totalDifficulty = nul TotalDifficulty = totalDifficulty, TxRoot = new TxTrie(transactions).RootHash, WithdrawalsRoot = Withdrawals is null ? null : new WithdrawalTrie(Withdrawals).RootHash, - ExcessDataGas = ExcessDataGas, }; block = new(header, transactions, Array.Empty(), Withdrawals); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs index 2208172d938..057e70c4c9b 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs @@ -10,7 +10,6 @@ namespace Nethermind.Serialization.Rlp { public class HeaderDecoder : IRlpValueDecoder, IRlpStreamDecoder { - public BlockHeader? Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { @@ -72,15 +71,16 @@ public class HeaderDecoder : IRlpValueDecoder, IRlpStreamDecoder 0 && decoderContext.PeekPrefixAndContentLength().ContentLength == Keccak.Size) { blockHeader.WithdrawalsRoot = decoderContext.DecodeKeccak(); - if (itemsRemaining == 2 && decoderContext.Position != headerCheck) + if (itemsRemaining == 3 && decoderContext.Position != headerCheck) { - blockHeader.ExcessDataGas = decoderContext.DecodeUInt256(); + blockHeader.DataGasUsed = decoderContext.DecodeULong(allowLeadingZeroBytes: false); + blockHeader.ExcessDataGas = decoderContext.DecodeULong(allowLeadingZeroBytes: false); } } @@ -152,15 +152,16 @@ public class HeaderDecoder : IRlpValueDecoder, IRlpStreamDecoder 0 && rlpStream.PeekPrefixAndContentLength().ContentLength == Keccak.Size) { blockHeader.WithdrawalsRoot = rlpStream.DecodeKeccak(); - if (itemsRemaining == 2 && rlpStream.Position != headerCheck) + if (itemsRemaining == 3 && rlpStream.Position != headerCheck) { - blockHeader.ExcessDataGas = rlpStream.DecodeUInt256(); + blockHeader.DataGasUsed = rlpStream.DecodeUlong(allowLeadingZeroBytes: false); + blockHeader.ExcessDataGas = rlpStream.DecodeUlong(allowLeadingZeroBytes: false); } } @@ -216,14 +217,15 @@ public void Encode(RlpStream rlpStream, BlockHeader? header, RlpBehaviors rlpBeh rlpStream.Encode(header.BaseFeePerGas); } - if (header.WithdrawalsRoot is not null || header.ExcessDataGas is not null) + if (header.WithdrawalsRoot is not null || header.ExcessDataGas is not null || header.DataGasUsed is not null) { rlpStream.Encode(header.WithdrawalsRoot ?? Keccak.Zero); } - if (header.ExcessDataGas is not null) + if (header.DataGasUsed is not null || header.ExcessDataGas is not null) { - rlpStream.Encode(header.ExcessDataGas.Value); + rlpStream.Encode(header.DataGasUsed.GetValueOrDefault()); + rlpStream.Encode(header.ExcessDataGas.GetValueOrDefault()); } } @@ -263,7 +265,8 @@ private static int GetContentLength(BlockHeader? item, RlpBehaviors rlpBehaviors + Rlp.LengthOf(item.Timestamp) + Rlp.LengthOf(item.ExtraData) + (item.BaseFeePerGas.IsZero ? 0 : Rlp.LengthOf(item.BaseFeePerGas)) - + (item.WithdrawalsRoot is null && item.ExcessDataGas is null ? 0 : Rlp.LengthOfKeccakRlp) + + (item.WithdrawalsRoot is null && item.DataGasUsed is null && item.ExcessDataGas is null ? 0 : Rlp.LengthOfKeccakRlp) + + (item.DataGasUsed is null ? 0 : Rlp.LengthOf(item.DataGasUsed.Value)) + (item.ExcessDataGas is null ? 0 : Rlp.LengthOf(item.ExcessDataGas.Value)); if (notForSealing) diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs index bd2074c021c..8dc6b19e959 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs @@ -78,5 +78,7 @@ public class ChainSpec public UInt256? TerminalTotalDifficulty { get; set; } public ulong? ShanghaiTimestamp { get; set; } + + public ulong? CancunTimestamp { get; set; } } } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs index 38df2fb805e..2410861f17f 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs @@ -217,6 +217,7 @@ chainSpec.Parameters.Eip1283DisableTransition is null chainSpec.ArrowGlacierBlockNumber = difficultyBombDelaysBlockNumbers?.Skip(4).FirstOrDefault(); chainSpec.GrayGlacierBlockNumber = difficultyBombDelaysBlockNumbers?.Skip(5).FirstOrDefault(); chainSpec.ShanghaiTimestamp = chainSpec.Parameters.Eip3651TransitionTimestamp; + chainSpec.CancunTimestamp = chainSpec.Parameters.Eip4844TransitionTimestamp; // TheMerge parameters chainSpec.MergeForkIdBlockNumber = chainSpec.Parameters.MergeForkIdTransition; @@ -381,7 +382,10 @@ private static void LoadGenesis(ChainSpecJson chainSpecJson, ChainSpec chainSpec bool isEip4844Enabled = chainSpecJson.Params.Eip4844TransitionTimestamp != null && genesisHeader.Timestamp >= chainSpecJson.Params.Eip4844TransitionTimestamp; if (isEip4844Enabled) - genesisHeader.ExcessDataGas ??= 0; + { + genesisHeader.DataGasUsed = chainSpecJson.Genesis.DataGasUsed; + genesisHeader.ExcessDataGas = chainSpecJson.Genesis.ExcessDataGas; + } genesisHeader.AuRaStep = step; genesisHeader.AuRaSignature = auRaSignature; diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecGenesisJson.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecGenesisJson.cs index f1f9d63845f..36b80f2e38d 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecGenesisJson.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecGenesisJson.cs @@ -22,5 +22,8 @@ internal class ChainSpecGenesisJson public UInt256? BaseFeePerGas { get; set; } public Keccak StateRoot { get; set; } + + public ulong? DataGasUsed { get; set; } + public ulong? ExcessDataGas { get; set; } } }