diff --git a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs
index d3db46da9c8..9943a961241 100644
--- a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs
+++ b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs
@@ -167,7 +167,7 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer)
return testResult;
}
- private static void InitializeTestState(Dictionary
preState, WorldState stateProvider, ISpecProvider specProvider)
+ public static void InitializeTestState(Dictionary preState, WorldState stateProvider, ISpecProvider specProvider)
{
foreach (KeyValuePair accountState in preState)
{
diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs
index c975c529b83..f910880585f 100644
--- a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs
+++ b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs
@@ -122,7 +122,7 @@ void IThreadPoolWorkItem.Execute()
if (block is null) return;
Transaction[] txs = block.Transactions;
- Address beneficiary = block.Header.GasBeneficiary;
+ Address beneficiary = block.Header.GasBeneficiary ?? Address.Zero;
Transaction lastTx = txs.Length > 0 ? txs[^1] : null;
bool isMev = false;
if (lastTx is not null && (lastTx.SenderAddress == beneficiary || _alternateMevPayees.Contains(lastTx.SenderAddress)))
diff --git a/tools/Evm/T8n/Errors/GethErrorMappings.cs b/tools/Evm/T8n/Errors/GethErrorMappings.cs
new file mode 100644
index 00000000000..3a1d61d39bf
--- /dev/null
+++ b/tools/Evm/T8n/Errors/GethErrorMappings.cs
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+namespace Evm.T8n.Errors;
+
+public class GethErrorMappings
+{
+ private const string WrongTransactionNonceError = "wrong transaction nonce";
+ private const string WrongTransactionNonceGethError = "nonce too low: address {0}, tx: {1} state: {2}";
+
+ private const string MissingTxToError = "TxMissingTo: Must be set.";
+ private const string MissingTxToGethError = "rlp: input string too short for common.Address, decoding into (types.Transaction)(types.BlobTx).To";
+
+ public static string GetErrorMapping(string error, params object[] arguments)
+ {
+ return error switch
+ {
+ WrongTransactionNonceError => string.Format(WrongTransactionNonceGethError, arguments),
+ MissingTxToError => string.Format(MissingTxToGethError),
+ _ => error
+ };
+ }
+}
diff --git a/tools/Evm/T8n/JsonTypes/EnvJson.cs b/tools/Evm/T8n/JsonTypes/EnvJson.cs
index 4c5973265dc..7a830b840bb 100644
--- a/tools/Evm/T8n/JsonTypes/EnvJson.cs
+++ b/tools/Evm/T8n/JsonTypes/EnvJson.cs
@@ -7,14 +7,14 @@
namespace Evm.T8n.JsonTypes;
-public class EnvJson
+public class EnvJson(Address currentCoinbase, long currentGasLimit, long currentNumber, ulong currentTimestamp)
{
- public Address? CurrentCoinbase { get; set; }
- public long CurrentGasLimit { get; set; }
- public long CurrentNumber { get; set; }
- public ulong CurrentTimestamp { get; set; }
- public Withdrawal[]? Withdrawals { get; set; }
+ public Address CurrentCoinbase { get; set; } = currentCoinbase;
+ public long CurrentGasLimit { get; set; } = currentGasLimit;
+ public long CurrentNumber { get; set; } = currentNumber;
+ public ulong CurrentTimestamp { get; set; } = currentTimestamp;
+ public Withdrawal[]? Withdrawals { get; set; }
public UInt256? CurrentRandom { get; set; }
public ulong ParentTimestamp { get; set; }
public UInt256? ParentDifficulty { get; set; }
diff --git a/tools/Evm/T8n/JsonTypes/InputData.cs b/tools/Evm/T8n/JsonTypes/InputData.cs
index 2cfbf99cbb9..2922eef750d 100644
--- a/tools/Evm/T8n/JsonTypes/InputData.cs
+++ b/tools/Evm/T8n/JsonTypes/InputData.cs
@@ -19,7 +19,7 @@ public class InputData
public TransactionMetaData[]? TransactionMetaDataList { get; set; }
public string? TxRlp { get; set; }
- public Transaction[] GetTransactions(TxDecoder decoder)
+ public Transaction[] GetTransactions(TxDecoder decoder, ulong chainId)
{
List transactions = [];
if (TxRlp is not null)
@@ -29,6 +29,8 @@ public Transaction[] GetTransactions(TxDecoder decoder)
}
else if (Txs is not null && TransactionMetaDataList is not null)
{
+ var ecdsa = new EthereumEcdsa(chainId);
+
for (int i = 0; i < Txs.Length; i++)
{
var transaction = Txs[i].ToTransaction();
@@ -36,7 +38,10 @@ public Transaction[] GetTransactions(TxDecoder decoder)
SignTransaction(transaction, TransactionMetaDataList[i], (LegacyTransactionForRpc) Txs[i]);
+ transaction.ChainId ??= chainId;
+ transaction.SenderAddress ??= ecdsa.RecoverAddress(transaction);
transaction.Hash = transaction.CalculateHash();
+
transactions.Add(transaction);
}
}
diff --git a/tools/Evm/T8n/JsonTypes/Ommer.cs b/tools/Evm/T8n/JsonTypes/Ommer.cs
index 96e3928547d..ddbf217521d 100644
--- a/tools/Evm/T8n/JsonTypes/Ommer.cs
+++ b/tools/Evm/T8n/JsonTypes/Ommer.cs
@@ -5,8 +5,4 @@
namespace Evm.T8n.JsonTypes;
-public class Ommer(int delta, Address address)
-{
- public int Delta { get; set; } = delta;
- public Address Address { get; set; } = address;
-}
+public readonly record struct Ommer(int Delta, Address Address);
diff --git a/tools/Evm/T8n/JsonTypes/RejectedTx.cs b/tools/Evm/T8n/JsonTypes/RejectedTx.cs
new file mode 100644
index 00000000000..ef0b5570321
--- /dev/null
+++ b/tools/Evm/T8n/JsonTypes/RejectedTx.cs
@@ -0,0 +1,6 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+namespace Evm.T8n.JsonTypes;
+
+public readonly record struct RejectedTx(int Index, string Error);
diff --git a/tools/Evm/T8n/JsonTypes/T8nResult.cs b/tools/Evm/T8n/JsonTypes/T8nResult.cs
new file mode 100644
index 00000000000..59759551759
--- /dev/null
+++ b/tools/Evm/T8n/JsonTypes/T8nResult.cs
@@ -0,0 +1,129 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using System.Text.Json.Serialization;
+using Ethereum.Test.Base;
+using Nethermind.Core;
+using Nethermind.Core.Collections;
+using Nethermind.Core.Crypto;
+using Nethermind.Core.Specs;
+using Nethermind.Evm;
+using Nethermind.Evm.Tracing;
+using Nethermind.Int256;
+using Nethermind.Serialization.Rlp;
+using Nethermind.State;
+using Nethermind.State.Proofs;
+
+namespace Evm.T8n.JsonTypes;
+
+public class T8nResult
+{
+ public Hash256? StateRoot { get; set; }
+ public Hash256? TxRoot { get; set; }
+ public Hash256? ReceiptsRoot { get; set; }
+ public Hash256? WithdrawalsRoot { get; set; }
+ public Hash256? LogsHash { get; set; }
+ public Bloom? LogsBloom { get; set; }
+ public TxReceipt[]? Receipts { get; set; }
+ public RejectedTx[]? Rejected { get; set; }
+ [JsonIgnore(Condition = JsonIgnoreCondition.Never)]
+ public UInt256? CurrentDifficulty { get; set; }
+ public UInt256? GasUsed { get; set; }
+ public UInt256? CurrentBaseFee { get; set; }
+ public UInt256? CurrentExcessBlobGas { get; set; }
+ public UInt256? BlobGasUsed { get; set; }
+ public Dictionary Accounts { get; set; } = [];
+ public byte[] TransactionsRlp { get; set; } = [];
+
+
+ public static T8nResult ConstructT8nResult(WorldState stateProvider,
+ Block block,
+ T8nTest test,
+ StorageTxTracer storageTracer,
+ BlockReceiptsTracer blockReceiptsTracer,
+ ISpecProvider specProvider,
+ TransactionExecutionReport txReport)
+ {
+ IReceiptSpec receiptSpec = specProvider.GetSpec(block.Header);
+ Hash256 txRoot = TxTrie.CalculateRoot(txReport.SuccessfulTransactions.ToArray());
+ Hash256 receiptsRoot = ReceiptTrie.CalculateRoot(receiptSpec,
+ txReport.SuccessfulTransactionReceipts.ToArray(), new ReceiptMessageDecoder());
+ LogEntry[] logEntries = txReport.SuccessfulTransactionReceipts
+ .SelectMany(receipt => receipt.Logs ?? Enumerable.Empty())
+ .ToArray();
+ var bloom = new Bloom(logEntries);
+ var gasUsed = blockReceiptsTracer.TxReceipts.Count == 0 ? 0 : (ulong)blockReceiptsTracer.LastReceipt.GasUsedTotal;
+ ulong? blobGasUsed = test.Spec.IsEip4844Enabled ? BlobGasCalculator.CalculateBlobGas(txReport.ValidTransactions.ToArray()) : null;
+
+ T8nResult t8NResult = new()
+ {
+ StateRoot = stateProvider.StateRoot,
+ TxRoot = txRoot,
+ ReceiptsRoot = receiptsRoot,
+ LogsBloom = bloom,
+ LogsHash = Keccak.Compute(Rlp.OfEmptySequence.Bytes),
+ Receipts = txReport.SuccessfulTransactionReceipts.ToArray(),
+ Rejected = txReport.RejectedTransactionReceipts.Count == 0
+ ? null
+ : txReport.RejectedTransactionReceipts.ToArray(),
+ CurrentDifficulty = test.CurrentDifficulty,
+ GasUsed = new UInt256(gasUsed),
+ CurrentBaseFee = test.CurrentBaseFee,
+ WithdrawalsRoot = block.WithdrawalsRoot,
+ CurrentExcessBlobGas = block.ExcessBlobGas,
+ BlobGasUsed = blobGasUsed,
+ TransactionsRlp = Rlp.Encode(txReport.SuccessfulTransactions.ToArray()).Bytes,
+ Accounts = CollectAccounts(test, stateProvider, storageTracer, block),
+ };
+
+ return t8NResult;
+ }
+
+ private static Dictionary CollectAccounts(T8nTest test, WorldState stateProvider, StorageTxTracer storageTracer, Block block)
+ {
+ Dictionary accounts = test.Alloc.Keys.ToDictionary(address => address,
+ address => GetAccountState(address, stateProvider, storageTracer));
+
+ accounts.AddRange(test.Ommers.ToDictionary(ommer => ommer.Address,
+ ommer => GetAccountState(ommer.Address, stateProvider, storageTracer)));
+
+ if (block.Beneficiary is not null)
+ {
+ accounts[block.Beneficiary] = GetAccountState(block.Beneficiary, stateProvider, storageTracer);
+ }
+
+ foreach (Transaction tx in test.Transactions)
+ {
+ if (tx.To is not null && !accounts.ContainsKey(tx.To))
+ {
+ accounts[tx.To] = GetAccountState(tx.To, stateProvider, storageTracer);
+ }
+ if (tx.SenderAddress is not null && !accounts.ContainsKey(tx.SenderAddress))
+ {
+ accounts[tx.SenderAddress] = GetAccountState(tx.SenderAddress, stateProvider, storageTracer);
+ }
+ }
+
+ return accounts
+ .Where(addressAndAccount => addressAndAccount.Value is not null)
+ .ToDictionary(addressAndAccount => addressAndAccount.Key, addressAndAccount => addressAndAccount.Value!);
+ }
+
+ private static AccountState? GetAccountState(Address address, WorldState stateProvider, StorageTxTracer storageTxTracer)
+ {
+ if (!stateProvider.AccountExists(address)) return null;
+
+ Account account = stateProvider.GetAccount(address);
+ var code = stateProvider.GetCode(address);
+ var accountState = new AccountState
+ {
+ Nonce = account.Nonce,
+ Balance = account.Balance,
+ Code = code
+ };
+
+ accountState.Storage = storageTxTracer.GetStorage(address) ?? [];
+
+ return accountState;
+ }
+}
diff --git a/tools/Evm/T8n/JsonTypes/T8nTest.cs b/tools/Evm/T8n/JsonTypes/T8nTest.cs
deleted file mode 100644
index 462dd26440f..00000000000
--- a/tools/Evm/T8n/JsonTypes/T8nTest.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
-// SPDX-License-Identifier: LGPL-3.0-only
-
-using Ethereum.Test.Base;
-using Nethermind.Core;
-using Nethermind.Core.Crypto;
-using Nethermind.Core.Specs;
-using Nethermind.Evm.Tracing.GethStyle;
-using Nethermind.Int256;
-using Nethermind.Specs;
-
-namespace Evm.T8n.JsonTypes;
-
-public class T8nTest(IReleaseSpec spec, ISpecProvider specProvider)
-{
- public IReleaseSpec Spec { get; set; } = spec;
- public ISpecProvider SpecProvider { get; set; } = specProvider;
- public Address? CurrentCoinbase { get; set; }
- public UInt256? CurrentDifficulty { get; set; }
-
- public UInt256? CurrentBaseFee { get; set; }
- public long CurrentGasLimit { get; set; }
- public long CurrentNumber { get; set; }
- public ulong CurrentTimestamp { get; set; }
- public Hash256? PreviousHash { get; set; }
- public Dictionary Alloc { get; set; } = [];
- public Hash256? PostHash { get; set; }
- public Transaction[] Transactions { get; set; } = [];
- public Hash256? CurrentRandom { get; set; }
- public Hash256? CurrentBeaconRoot { get; set; }
- public Hash256? CurrentWithdrawalsRoot { get; set; }
- public ulong? CurrentExcessBlobGas { get; set; }
- public UInt256? ParentBlobGasUsed { get; set; }
- public UInt256? ParentExcessBlobGas { get; set; }
-
- public Withdrawal[]? Withdrawals { get; set; }
- public ulong ParentTimestamp { get; set; }
- public UInt256? ParentDifficulty { get; set; }
- public Hash256? ParentUncleHash { get; set; }
- public Hash256? ParentBeaconBlockRoot { get; set; }
- public UInt256? ParentBaseFee { get; set; }
- public long ParentGasUsed { get; set; }
- public long ParentGasLimit { get; set; }
- public Dictionary BlockHashes { get; set; } = [];
- public Ommer[] Ommers { get; set; } = [];
- public ulong StateChainId { get; set; } = MainnetSpecProvider.Instance.ChainId;
- public GethTraceOptions GethTraceOptions { get; set; } = GethTraceOptions.Default;
- public bool IsTraceEnabled { get; set; } = false;
-}
diff --git a/tools/Evm/T8n/JsonTypes/TransactionExecutionReport.cs b/tools/Evm/T8n/JsonTypes/TransactionExecutionReport.cs
new file mode 100644
index 00000000000..ec7726c903e
--- /dev/null
+++ b/tools/Evm/T8n/JsonTypes/TransactionExecutionReport.cs
@@ -0,0 +1,15 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Core;
+
+namespace Evm.T8n.JsonTypes;
+
+
+public class TransactionExecutionReport
+{
+ public List RejectedTransactionReceipts { get; set; } = [];
+ public List ValidTransactions { get; set; } = [];
+ public List SuccessfulTransactions { get; set; } = [];
+ public List SuccessfulTransactionReceipts { get; set; } = [];
+}
diff --git a/tools/Evm/T8n/JsonTypes/TransactionMetaData.cs b/tools/Evm/T8n/JsonTypes/TransactionMetaData.cs
index 737a052e715..82c0c8894b0 100644
--- a/tools/Evm/T8n/JsonTypes/TransactionMetaData.cs
+++ b/tools/Evm/T8n/JsonTypes/TransactionMetaData.cs
@@ -3,8 +3,4 @@
namespace Evm.T8n.JsonTypes;
-public class TransactionMetaData
-{
- public bool? Protected { get; set; }
- public byte[]? SecretKey { get; set; }
-}
+public readonly record struct TransactionMetaData(bool? Protected, byte[]? SecretKey);
diff --git a/tools/Evm/T8n/StorageTxTracer.cs b/tools/Evm/T8n/StorageTxTracer.cs
new file mode 100644
index 00000000000..0e995dbcab8
--- /dev/null
+++ b/tools/Evm/T8n/StorageTxTracer.cs
@@ -0,0 +1,42 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Nethermind.Core;
+using Nethermind.Evm.Tracing;
+using Nethermind.Int256;
+
+namespace Evm.T8n;
+
+public class StorageTxTracer : TxTracer, IBlockTracer
+{
+ private readonly Dictionary> _storages = new();
+ public bool IsTracingRewards => false;
+ public override bool IsTracingOpLevelStorage => true;
+
+ public override void SetOperationStorage(Address address, UInt256 storageIndex, ReadOnlySpan newValue,
+ ReadOnlySpan currentValue)
+ {
+ if (!_storages.TryGetValue(address, out _))
+ {
+ _storages[address] = [];
+ }
+
+ _storages[address][storageIndex] = newValue.ToArray();
+ }
+
+ public Dictionary? GetStorage(Address address)
+ {
+ _storages.TryGetValue(address, out Dictionary? storage);
+ return storage;
+ }
+
+ public void ReportReward(Address author, string rewardType, UInt256 rewardValue) { }
+
+ public void StartNewBlockTrace(Block block) { }
+
+ public ITxTracer StartNewTxTrace(Transaction? tx) => this;
+
+ public void EndTxTrace() { }
+
+ public void EndBlockTrace() { }
+}
diff --git a/tools/Evm/T8n/T8nBlockHashProvider.cs b/tools/Evm/T8n/T8nBlockHashProvider.cs
new file mode 100644
index 00000000000..40a048e28d7
--- /dev/null
+++ b/tools/Evm/T8n/T8nBlockHashProvider.cs
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Evm.T8n.Errors;
+using Nethermind.Core;
+using Nethermind.Core.Crypto;
+using Nethermind.Evm;
+
+namespace Evm.T8n;
+
+public class T8nBlockHashProvider : IBlockhashProvider
+{
+ private readonly Dictionary _blockHashes = new();
+ private static readonly int _maxDepth = 256;
+
+ public Hash256? GetBlockhash(BlockHeader currentBlock, in long number)
+ {
+ long current = currentBlock.Number;
+ if (number >= current || number < current - Math.Min(current, _maxDepth))
+ {
+ return null;
+ }
+
+ return _blockHashes.GetValueOrDefault(number, null) ??
+ throw new T8nException($"BlockHash for block {number} not provided",
+ T8nErrorCodes.ErrorMissingBlockhash);
+ }
+
+ public void Insert(Hash256 blockHash, long number)
+ {
+ _blockHashes[number] = blockHash;
+ }
+}
diff --git a/tools/Evm/T8n/T8nExecutor.cs b/tools/Evm/T8n/T8nExecutor.cs
index 307f1c83afa..a1ed85939f9 100644
--- a/tools/Evm/T8n/T8nExecutor.cs
+++ b/tools/Evm/T8n/T8nExecutor.cs
@@ -1,14 +1,169 @@
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only
+using System.IO.Abstractions;
+using Ethereum.Test.Base;
+using Evm.T8n.Errors;
using Evm.T8n.JsonTypes;
+using Nethermind.Blockchain.BeaconBlockRoot;
+using Nethermind.Consensus.Rewards;
+using Nethermind.Consensus.Validators;
+using Nethermind.Consensus.Withdrawals;
+using Nethermind.Core;
+using Nethermind.Core.Crypto;
+using Nethermind.Core.Specs;
+using Nethermind.Crypto;
+using Nethermind.Db;
+using Nethermind.Evm;
+using Nethermind.Evm.Tracing;
+using Nethermind.Evm.Tracing.GethStyle;
+using Nethermind.Evm.TransactionProcessing;
+using Nethermind.Logging;
+using Nethermind.State;
+using Nethermind.Trie.Pruning;
namespace Evm.T8n;
public static class T8nExecutor
{
- public static void Execute(T8nCommandArguments arguments)
+ private static ILogManager _logManager = LimboLogs.Instance;
+
+ public static T8nResult Execute(T8nCommandArguments arguments)
+ {
+ T8nTest test = T8nInputProcessor.ProcessInputAndConvertToT8nTest(arguments);
+
+ KzgPolynomialCommitments.InitializeAsync();
+
+ IDb stateDb = new MemDb();
+ IDb codeDb = new MemDb();
+
+ TrieStore trieStore = new(stateDb, _logManager);
+ WorldState stateProvider = new(trieStore, codeDb, _logManager);
+ CodeInfoRepository codeInfoRepository = new();
+ IBlockhashProvider blockhashProvider = ConstructBlockHashProvider(test);
+
+ IVirtualMachine virtualMachine = new VirtualMachine(
+ blockhashProvider,
+ test.SpecProvider,
+ codeInfoRepository,
+ _logManager);
+ TransactionProcessor transactionProcessor = new(
+ test.SpecProvider,
+ stateProvider,
+ virtualMachine,
+ codeInfoRepository,
+ _logManager);
+
+ stateProvider.CreateAccount(test.CurrentCoinbase, 0);
+ GeneralStateTestBase.InitializeTestState(test.Alloc, stateProvider, test.SpecProvider);
+
+ Block block = test.ConstructBlock();
+ var withdrawalProcessor = new WithdrawalProcessor(stateProvider, _logManager);
+ withdrawalProcessor.ProcessWithdrawals(block, test.Spec);
+
+ ApplyRewards(block, stateProvider, test.Spec, test.SpecProvider);
+
+ CompositeBlockTracer compositeBlockTracer = new();
+
+ StorageTxTracer storageTxTracer = new();
+ compositeBlockTracer.Add(storageTxTracer);
+ if (test.IsTraceEnabled)
+ {
+ compositeBlockTracer.Add(new GethLikeBlockFileTracer(block, test.GethTraceOptions, new FileSystem()));
+ }
+
+ BlockReceiptsTracer blockReceiptsTracer = new();
+ blockReceiptsTracer.SetOtherTracer(compositeBlockTracer);
+ blockReceiptsTracer.StartNewBlockTrace(block);
+
+ BeaconBlockRootHandler beaconBlockRootHandler = new(transactionProcessor, stateProvider);
+ if (test.ParentBeaconBlockRoot is not null)
+ {
+ beaconBlockRootHandler.StoreBeaconRoot(block, test.Spec, storageTxTracer);
+ }
+
+ int txIndex = 0;
+ TransactionExecutionReport transactionExecutionReport = new();
+ var txValidator = new TxValidator(test.StateChainId);
+
+ foreach (Transaction transaction in test.Transactions)
+ {
+ ValidationResult txIsValid = txValidator.IsWellFormed(transaction, test.Spec);
+
+ if (!txIsValid)
+ {
+ if (txIsValid.Error is not null)
+ {
+ var error = GethErrorMappings.GetErrorMapping(txIsValid.Error);
+ transactionExecutionReport.RejectedTransactionReceipts.Add(new RejectedTx(txIndex, error));
+ }
+ continue;
+ }
+
+ blockReceiptsTracer.StartNewTxTrace(transaction);
+ TransactionResult transactionResult = transactionProcessor
+ .Execute(transaction, new BlockExecutionContext(block.Header), blockReceiptsTracer);
+ blockReceiptsTracer.EndTxTrace();
+
+ transactionExecutionReport.ValidTransactions.Add(transaction);
+
+ if (transactionResult.Success)
+ {
+ transactionExecutionReport.SuccessfulTransactions.Add(transaction);
+ blockReceiptsTracer.LastReceipt.PostTransactionState = null;
+ blockReceiptsTracer.LastReceipt.BlockHash = null;
+ blockReceiptsTracer.LastReceipt.BlockNumber = 0;
+ transactionExecutionReport.SuccessfulTransactionReceipts.Add(blockReceiptsTracer.LastReceipt);
+ }
+ else if (transactionResult.Error is not null && transaction.SenderAddress is not null)
+ {
+ var error = GethErrorMappings.GetErrorMapping(transactionResult.Error,
+ transaction.SenderAddress.ToString(true),
+ transaction.Nonce, stateProvider.GetNonce(transaction.SenderAddress));
+
+ transactionExecutionReport.RejectedTransactionReceipts.Add(new RejectedTx(txIndex, error));
+ stateProvider.Reset();
+ }
+
+ txIndex++;
+ }
+
+ blockReceiptsTracer.EndBlockTrace();
+
+ stateProvider.Commit(test.SpecProvider.GetSpec((ForkActivation)1));
+ stateProvider.CommitTree(test.CurrentNumber);
+
+ return T8nResult.ConstructT8nResult(stateProvider, block, test, storageTxTracer,
+ blockReceiptsTracer, test.SpecProvider, transactionExecutionReport);
+ }
+
+ private static IBlockhashProvider ConstructBlockHashProvider(T8nTest test)
+ {
+ var t8NBlockHashProvider = new T8nBlockHashProvider();
+
+ foreach (KeyValuePair blockHash in test.BlockHashes)
+ {
+ t8NBlockHashProvider.Insert(blockHash.Value, long.Parse(blockHash.Key));
+ }
+
+ return t8NBlockHashProvider;
+ }
+
+ private static void ApplyRewards(Block block, WorldState stateProvider, IReleaseSpec spec, ISpecProvider specProvider)
{
- T8nTest t8nTest = T8nInputProcessor.ProcessInputAndConvertToT8nTest(arguments);
+ var rewardCalculator = new RewardCalculator(specProvider);
+ BlockReward[] rewards = rewardCalculator.CalculateRewards(block);
+
+ foreach (BlockReward reward in rewards)
+ {
+ if (!stateProvider.AccountExists(reward.Address))
+ {
+ stateProvider.CreateAccount(reward.Address, reward.Value);
+ }
+ else
+ {
+ stateProvider.AddToBalance(reward.Address, reward.Value, spec);
+ }
+ }
}
}
diff --git a/tools/Evm/T8n/T8nInputProcessor.cs b/tools/Evm/T8n/T8nInputProcessor.cs
index 45abf4c104c..da35520d8f5 100644
--- a/tools/Evm/T8n/T8nInputProcessor.cs
+++ b/tools/Evm/T8n/T8nInputProcessor.cs
@@ -37,11 +37,10 @@ public static T8nTest ProcessInputAndConvertToT8nTest(T8nCommandArguments argume
DisableStack = arguments.TraceNoStack
};
- T8nTest test = new(spec, specProvider)
+ T8nTest test = new(spec, specProvider, inputData.Env.CurrentCoinbase)
{
Alloc = inputData.Alloc ?? [],
- Transactions = inputData.GetTransactions(TxDecoder),
- CurrentCoinbase = inputData.Env.CurrentCoinbase,
+ Transactions = inputData.GetTransactions(TxDecoder, specProvider.ChainId),
CurrentGasLimit = inputData.Env.CurrentGasLimit,
CurrentTimestamp = inputData.Env.CurrentTimestamp,
CurrentNumber = inputData.Env.CurrentNumber,
diff --git a/tools/Evm/T8n/T8nTest.cs b/tools/Evm/T8n/T8nTest.cs
new file mode 100644
index 00000000000..6d88aae73b4
--- /dev/null
+++ b/tools/Evm/T8n/T8nTest.cs
@@ -0,0 +1,95 @@
+// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
+// SPDX-License-Identifier: LGPL-3.0-only
+
+using Ethereum.Test.Base;
+using Evm.T8n.JsonTypes;
+using Nethermind.Core;
+using Nethermind.Core.Crypto;
+using Nethermind.Core.Specs;
+using Nethermind.Core.Test.Builders;
+using Nethermind.Crypto;
+using Nethermind.Evm;
+using Nethermind.Evm.Tracing.GethStyle;
+using Nethermind.Int256;
+using Nethermind.Specs;
+using Nethermind.Specs.Forks;
+
+namespace Evm.T8n;
+
+public class T8nTest(IReleaseSpec spec, ISpecProvider specProvider, Address currentCoinbase)
+{
+ public IReleaseSpec Spec { get; set; } = spec;
+ public ISpecProvider SpecProvider { get; set; } = specProvider;
+ public Dictionary Alloc { get; set; } = [];
+ public Transaction[] Transactions { get; set; } = [];
+
+ public Address CurrentCoinbase { get; set; } = currentCoinbase;
+ public UInt256? CurrentDifficulty { get; set; }
+ public UInt256? CurrentBaseFee { get; set; }
+ public long CurrentGasLimit { get; set; }
+ public long CurrentNumber { get; set; }
+ public ulong CurrentTimestamp { get; set; }
+ public Hash256? CurrentRandom { get; set; }
+ public ulong? CurrentExcessBlobGas { get; set; }
+ public UInt256? ParentBlobGasUsed { get; set; }
+ public UInt256? ParentExcessBlobGas { get; set; }
+ public Withdrawal[]? Withdrawals { get; set; }
+ public ulong ParentTimestamp { get; set; }
+ public UInt256? ParentDifficulty { get; set; }
+ public Hash256? ParentUncleHash { get; set; }
+ public Hash256? ParentBeaconBlockRoot { get; set; }
+ public UInt256? ParentBaseFee { get; set; }
+ public long ParentGasUsed { get; set; }
+ public long ParentGasLimit { get; set; }
+ public Dictionary BlockHashes { get; set; } = [];
+ public Ommer[] Ommers { get; set; } = [];
+ public ulong StateChainId { get; set; } = MainnetSpecProvider.Instance.ChainId;
+ public GethTraceOptions GethTraceOptions { get; set; } = GethTraceOptions.Default;
+ public bool IsTraceEnabled { get; set; } = false;
+
+ private BlockHeader ConstructBlockHeader()
+ {
+ BlockHeader header = Build.A.BlockHeader
+ .WithTimestamp(CurrentTimestamp)
+ .WithGasLimit(CurrentGasLimit)
+ .WithDifficulty(CurrentDifficulty ?? UInt256.Zero)
+ .WithBeneficiary(CurrentCoinbase)
+ .WithNumber(CurrentNumber)
+ .TestObject;
+
+ if (CurrentRandom is not null) header.MixHash = CurrentRandom;
+ if (CurrentBaseFee.HasValue) header.BaseFeePerGas = CurrentBaseFee.Value;
+ if (ParentExcessBlobGas.HasValue && ParentBlobGasUsed.HasValue)
+ {
+ BlockHeader parentHeader = Build.A.BlockHeader
+ .WithExcessBlobGas((ulong)ParentExcessBlobGas)
+ .WithBlobGasUsed((ulong)ParentBlobGasUsed)
+ .TestObject;
+ header.ExcessBlobGas = BlobGasCalculator.CalculateExcessBlobGas(parentHeader, Spec);
+ }
+ header.BlobGasUsed = BlobGasCalculator.CalculateBlobGas(Transactions);
+ header.IsPostMerge = Spec is Paris;
+ header.Hash = header.CalculateHash();
+
+ return header;
+ }
+
+ public Block ConstructBlock()
+ {
+
+ BlockHeader[] uncles = Ommers
+ .Select(ommer => Build.A.BlockHeader
+ .WithNumber(CurrentNumber - ommer.Delta)
+ .WithBeneficiary(ommer.Address)
+ .TestObject)
+ .ToArray();
+
+ BlockHeader header = ConstructBlockHeader();
+ return Build.A.Block
+ .WithHeader(header)
+ .WithTransactions(Transactions)
+ .WithWithdrawals(Withdrawals)
+ .WithUncles(uncles)
+ .TestObject;
+ }
+}