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; + } +}