Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement t8n execution #7695

Merged
merged 7 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer)
return testResult;
}

private static void InitializeTestState(Dictionary<Address, AccountState> preState, WorldState stateProvider, ISpecProvider specProvider)
public static void InitializeTestState(Dictionary<Address, AccountState> preState, WorldState stateProvider, ISpecProvider specProvider)
{
foreach (KeyValuePair<Address, AccountState> accountState in preState)
{
Expand Down
23 changes: 23 additions & 0 deletions tools/Evm/T8n/Errors/GethErrorMappings.cs
Original file line number Diff line number Diff line change
@@ -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
};
}
}
12 changes: 6 additions & 6 deletions tools/Evm/T8n/JsonTypes/EnvJson.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
7 changes: 6 additions & 1 deletion tools/Evm/T8n/JsonTypes/InputData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Transaction> transactions = [];
if (TxRlp is not null)
Expand All @@ -29,14 +29,19 @@ 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();
transaction.SenderAddress = null; // t8n does not accept SenderAddress from input, so need to reset senderAddress

SignTransaction(transaction, TransactionMetaDataList[i], (LegacyTransactionForRpc) Txs[i]);

transaction.ChainId ??= chainId;
transaction.SenderAddress ??= ecdsa.RecoverAddress(transaction);
transaction.Hash = transaction.CalculateHash();

transactions.Add(transaction);
}
}
Expand Down
6 changes: 1 addition & 5 deletions tools/Evm/T8n/JsonTypes/Ommer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
6 changes: 6 additions & 0 deletions tools/Evm/T8n/JsonTypes/RejectedTx.cs
Original file line number Diff line number Diff line change
@@ -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);
129 changes: 129 additions & 0 deletions tools/Evm/T8n/JsonTypes/T8nResult.cs
Original file line number Diff line number Diff line change
@@ -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<Address, AccountState> 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<TxReceipt>.CalculateRoot(receiptSpec,
txReport.SuccessfulTransactionReceipts.ToArray(), new ReceiptMessageDecoder());
LogEntry[] logEntries = txReport.SuccessfulTransactionReceipts
.SelectMany(receipt => receipt.Logs ?? Enumerable.Empty<LogEntry>())
.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<Address, AccountState> CollectAccounts(T8nTest test, WorldState stateProvider, StorageTxTracer storageTracer, Block block)
{
Dictionary<Address, AccountState?> 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;
}
}
49 changes: 0 additions & 49 deletions tools/Evm/T8n/JsonTypes/T8nTest.cs

This file was deleted.

15 changes: 15 additions & 0 deletions tools/Evm/T8n/JsonTypes/TransactionExecutionReport.cs
Original file line number Diff line number Diff line change
@@ -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<RejectedTx> RejectedTransactionReceipts { get; set; } = [];
public List<Transaction> ValidTransactions { get; set; } = [];
public List<Transaction> SuccessfulTransactions { get; set; } = [];
public List<TxReceipt> SuccessfulTransactionReceipts { get; set; } = [];
}
6 changes: 1 addition & 5 deletions tools/Evm/T8n/JsonTypes/TransactionMetaData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
42 changes: 42 additions & 0 deletions tools/Evm/T8n/StorageTxTracer.cs
Original file line number Diff line number Diff line change
@@ -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<Address, Dictionary<UInt256, byte[]>> _storages = new();
public bool IsTracingRewards => false;
public override bool IsTracingOpLevelStorage => true;

public override void SetOperationStorage(Address address, UInt256 storageIndex, ReadOnlySpan<byte> newValue,
ReadOnlySpan<byte> currentValue)
{
if (!_storages.TryGetValue(address, out _))
{
_storages[address] = [];
}

_storages[address][storageIndex] = newValue.ToArray();
}

public Dictionary<UInt256, byte[]>? GetStorage(Address address)
{
_storages.TryGetValue(address, out Dictionary<UInt256, byte[]>? 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() { }
}
33 changes: 33 additions & 0 deletions tools/Evm/T8n/T8nBlockHashProvider.cs
Original file line number Diff line number Diff line change
@@ -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<long, Hash256?> _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;
}
}
Loading