Skip to content

Commit

Permalink
Add shard blob tx gas calculations (#5596)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* 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 <[email protected]>
Co-authored-by: Nikita Mescheryakov <[email protected]>
Co-authored-by: Nikita Mescheryakov <[email protected]>
  • Loading branch information
4 people authored Jun 30, 2023
1 parent 54e54be commit 578290e
Show file tree
Hide file tree
Showing 62 changed files with 1,137 additions and 323 deletions.
4 changes: 3 additions & 1 deletion src/Nethermind/Chains/eip4844_local.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@
}
},
"timestamp": 0,
"baseFeePerGas": "0x7"
"baseFeePerGas": "0x7",
"dataGasUsed": "0x0",
"excessDataGas": "0x0"
},
"accounts": {
"0x8A04d14125D0FDCDc742F4A05C051De07232EDa4": {
Expand Down
4 changes: 2 additions & 2 deletions src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
using Nethermind.State;
using NSubstitute;
using NUnit.Framework;
using NUnit.Framework.Internal.Execution;

namespace Nethermind.Blockchain.Test.Producers
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -240,9 +249,13 @@ void SetAccountStates(IEnumerable<Address> 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<Transaction> 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());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,57 +1,105 @@
// 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;
using Nethermind.Core.Test.Builders;
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<IBlockTree>(), 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<TestCaseData> 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
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)]
Expand All @@ -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)
Expand Down Expand Up @@ -407,36 +406,41 @@ private static IEnumerable<TestCaseData> 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
};
}
}

Expand Down Expand Up @@ -494,19 +498,19 @@ static TransactionBuilder<Transaction> 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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down
Loading

0 comments on commit 578290e

Please sign in to comment.