From 84178b8e03523abe1c1c7be4d31306f00a8bafda Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 4 Sep 2024 16:04:15 -0300 Subject: [PATCH 01/76] Initial `RpcLegacyTransaction` --- .../RpcTransaction/RpcLegacyTransaction.cs | 67 +++++++++++ .../RpcLegacyTransactionTests.cs | 105 ++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs new file mode 100644 index 00000000000..e208a1c4946 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Text.Json.Serialization; +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Int256; + +namespace Nethermind.Facade.Eth.RpcTransaction; + +public sealed class RpcLegacyTransaction +{ + public TxType Type { get; set; } + + public UInt256 Nonce { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public Address? To { get; set; } + + public long Gas { get; set; } + + public UInt256 Value { get; set; } + + public byte[] Input { get; set; } + + public UInt256 GasPrice { get; set; } + + public UInt256? ChainId { get; set; } + + public UInt256 V { get; set; } + + public UInt256 S { get; set; } + + public UInt256 R { get; set; } + + private RpcLegacyTransaction() { } + + public static RpcLegacyTransaction? FromTransaction(Transaction? transaction) + { + if (transaction is null) + { + return null; + } + + if (transaction.Type != TxType.Legacy) + { + throw new ArgumentException("Transaction type must be Legacy"); + } + + return new RpcLegacyTransaction + { + Type = transaction.Type, + Nonce = transaction.Nonce, + To = transaction.To, + Gas = transaction.GasLimit, + Value = transaction.Value, + Input = transaction.Data.AsArray() ?? [], + GasPrice = transaction.GasPrice, + ChainId = transaction.ChainId, + + R = new UInt256(transaction.Signature?.R ?? [], true), + S = new UInt256(transaction.Signature?.S ?? [], true), + V = transaction.Signature?.V ?? 0, + }; + } +} diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs new file mode 100644 index 00000000000..dabb8638191 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs @@ -0,0 +1,105 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json; +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Facade.Eth.RpcTransaction; +using Nethermind.Int256; +using Nethermind.Serialization.Json; +using NUnit.Framework; + +namespace Nethermind.JsonRpc.Test.Modules.RpcTransaction; + +public class RpcLegacyTransactionTests +{ + private readonly EthereumJsonSerializer _serializer = new(); + + private static TransactionBuilder BuildALegacyTransaction => Build.A.Transaction.WithType(TxType.Legacy); + + public static readonly Transaction[] LegacyTransactions = + [ + BuildALegacyTransaction.TestObject, + + BuildALegacyTransaction.WithNonce(UInt256.Zero).TestObject, + BuildALegacyTransaction.WithNonce((UInt256) 123).TestObject, + BuildALegacyTransaction.WithNonce(UInt256.MaxValue).TestObject, + + BuildALegacyTransaction.WithTo(null).TestObject, + BuildALegacyTransaction.WithTo(TestItem.AddressA).TestObject, + BuildALegacyTransaction.WithTo(TestItem.AddressB).TestObject, + BuildALegacyTransaction.WithTo(TestItem.AddressC).TestObject, + BuildALegacyTransaction.WithTo(TestItem.AddressD).TestObject, + BuildALegacyTransaction.WithTo(TestItem.AddressE).TestObject, + BuildALegacyTransaction.WithTo(TestItem.AddressF).TestObject, + + BuildALegacyTransaction.WithGasLimit(0).TestObject, + BuildALegacyTransaction.WithGasLimit(123).TestObject, + BuildALegacyTransaction.WithGasLimit(long.MaxValue).TestObject, + + BuildALegacyTransaction.WithValue(UInt256.Zero).TestObject, + BuildALegacyTransaction.WithValue((UInt256) 123).TestObject, + BuildALegacyTransaction.WithValue(UInt256.MaxValue).TestObject, + + BuildALegacyTransaction.WithData(TestItem.RandomDataA).TestObject, + BuildALegacyTransaction.WithData(TestItem.RandomDataB).TestObject, + BuildALegacyTransaction.WithData(TestItem.RandomDataC).TestObject, + BuildALegacyTransaction.WithData(TestItem.RandomDataD).TestObject, + + BuildALegacyTransaction.WithGasPrice(UInt256.Zero).TestObject, + BuildALegacyTransaction.WithGasPrice((UInt256) 123).TestObject, + BuildALegacyTransaction.WithGasPrice(UInt256.MaxValue).TestObject, + + BuildALegacyTransaction.WithChainId(BlockchainIds.Mainnet).TestObject, + BuildALegacyTransaction.WithChainId(BlockchainIds.Sepolia).TestObject, + BuildALegacyTransaction.WithChainId(0).TestObject, + BuildALegacyTransaction.WithChainId(ulong.MaxValue).TestObject, + + BuildALegacyTransaction.WithSignature(TestItemSignatures.RandomSignatureA).TestObject, + BuildALegacyTransaction.WithSignature(TestItemSignatures.RandomSignatureB).TestObject, + ]; + + [TestCaseSource(nameof(LegacyTransactions))] + public void Always_satisfies_schema(Transaction transaction) + { + var rpcTx = RpcLegacyTransaction.FromTransaction(transaction); + string serialized = _serializer.Serialize(rpcTx); + using var jsonDocument = JsonDocument.Parse(serialized); + var json = jsonDocument.RootElement; + + json.GetProperty("type").GetString().Should().MatchRegex("^0x0$"); + json.GetProperty("nonce").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("to").GetString()?.Should().MatchRegex("^0x[0-9a-fA-F]{40}$"); + json.GetProperty("gas").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("value").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("input").GetString().Should().MatchRegex("^0x[0-9a-f]*$"); + json.GetProperty("gasPrice").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + bool hasChainId = json.TryGetProperty("chainId", out var chainId); + if (hasChainId) + { + chainId.GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + } + json.GetProperty("v").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("r").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("s").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + } +} + +// TODO: We don't have a good source of "random" signatures for testing +public static class TestItemSignatures +{ + public static readonly Signature RandomSignatureA; + public static readonly Signature RandomSignatureB; + + static TestItemSignatures() + { + byte[] r = new byte[32]; + byte[] s = new byte[32]; + r[1] = 1; + s[2] = 2; + RandomSignatureA = new Signature(r, s, 27); + RandomSignatureB = new Signature(r, s, 28); + } +} From a2739f48748b9a627b2c24c1afa429dd4e7fa4ce Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Thu, 5 Sep 2024 12:51:49 -0300 Subject: [PATCH 02/76] Change field order --- .../Eth/RpcTransaction/RpcLegacyTransaction.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index e208a1c4946..ae8fc3bb583 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -30,9 +30,8 @@ public sealed class RpcLegacyTransaction public UInt256 V { get; set; } - public UInt256 S { get; set; } - public UInt256 R { get; set; } + public UInt256 S { get; set; } private RpcLegacyTransaction() { } From af3b042141e9d66d9f090b3b59e129038ddac155 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Thu, 5 Sep 2024 13:24:55 -0300 Subject: [PATCH 03/76] Support null as `ChainId` through `TransactionBuilder` --- .../Nethermind.Core.Test/Builders/TransactionBuilder.cs | 2 +- .../Modules/RpcTransaction/RpcLegacyTransactionTests.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs index cb8c9e984f3..58b6917866d 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs @@ -64,7 +64,7 @@ public TransactionBuilder WithCode(byte[] data) return this; } - public TransactionBuilder WithChainId(ulong chainId) + public TransactionBuilder WithChainId(ulong? chainId) { TestObjectInternal.ChainId = chainId; return this; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs index dabb8638191..2ef3aeca5b4 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs @@ -52,6 +52,7 @@ public class RpcLegacyTransactionTests BuildALegacyTransaction.WithGasPrice((UInt256) 123).TestObject, BuildALegacyTransaction.WithGasPrice(UInt256.MaxValue).TestObject, + BuildALegacyTransaction.WithChainId(null).TestObject, BuildALegacyTransaction.WithChainId(BlockchainIds.Mainnet).TestObject, BuildALegacyTransaction.WithChainId(BlockchainIds.Sepolia).TestObject, BuildALegacyTransaction.WithChainId(0).TestObject, From ec1a21e777e5d946746b019cee9ec8ac8c4fab3b Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Thu, 5 Sep 2024 15:18:17 -0300 Subject: [PATCH 04/76] Initial `RpcAccessListTransaction` --- .../RpcAccessListTransaction.cs | 86 ++++++++++++ .../RpcAccessListTransactionTests.cs | 126 ++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs new file mode 100644 index 00000000000..5430225642d --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Int256; + +namespace Nethermind.Facade.Eth.RpcTransaction; + +public sealed class RpcAccessListTransaction +{ + // HACK: To ensure that serialized Txs always have a `ChainId` we keep the last loaded `ChainSpec`. + // See: https://github.com/NethermindEth/nethermind/pull/6061#discussion_r1321634914 + public static UInt256? DefaultChainId { get; set; } + + public TxType Type { get; set; } + + public UInt256 Nonce { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public Address? To { get; set; } + + public long Gas { get; set; } + + public UInt256 Value { get; set; } + + public byte[] Input { get; set; } + + public UInt256 GasPrice { get; set; } + + public IEnumerable AccessList { get; set; } + + public UInt256 ChainId { get; set; } + + public UInt256 YParity { get; set; } + + /// + /// For backwards compatibility, v is optionally provided as an alternative to yParity. + /// This field is DEPRECATED and all use of it should migrate to yParity. + /// + public UInt256? V { get; set; } + + public UInt256 R { get; set; } + + public UInt256 S { get; set; } + + private RpcAccessListTransaction() { } + + public static RpcAccessListTransaction? FromTransaction(Transaction? transaction) + { + if (transaction is null) + { + return null; + } + + if (transaction.Type != TxType.AccessList) + { + throw new ArgumentException("Transaction type must be AccessList"); + } + + return new RpcAccessListTransaction + { + Type = transaction.Type, + Nonce = transaction.Nonce, + To = transaction.To, + Gas = transaction.GasLimit, + Value = transaction.Value, + Input = transaction.Data.AsArray() ?? [], + GasPrice = transaction.GasPrice, + AccessList = transaction.AccessList is null + ? Array.Empty() + : AccessListItemForRpc.FromAccessList(transaction.AccessList), + ChainId = transaction.ChainId + ?? DefaultChainId + ?? BlockchainIds.Mainnet, + + YParity = transaction.Signature?.RecoveryId ?? 0, + V = transaction.Signature?.RecoveryId ?? 0, + R = new UInt256(transaction.Signature?.R ?? [], true), + S = new UInt256(transaction.Signature?.S ?? [], true), + }; + } +} diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs new file mode 100644 index 00000000000..c599dfa87b4 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Linq; +using System.Text.Json; +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Eip2930; +using Nethermind.Core.Test.Builders; +using Nethermind.Facade.Eth.RpcTransaction; +using Nethermind.Int256; +using Nethermind.Serialization.Json; +using NUnit.Framework; + +namespace Nethermind.JsonRpc.Test.Modules.RpcTransaction; + +public class RpcAccessListTransactionTests +{ + private readonly EthereumJsonSerializer _serializer = new(); + + private static TransactionBuilder Build => Core.Test.Builders.Build.A.Transaction.WithType(TxType.AccessList); + + public static readonly Transaction[] LegacyTransactions = + [ + Build.TestObject, + + Build.WithNonce(UInt256.Zero).TestObject, + Build.WithNonce((UInt256) 123).TestObject, + Build.WithNonce(UInt256.MaxValue).TestObject, + + Build.WithTo(null).TestObject, + Build.WithTo(TestItem.AddressA).TestObject, + Build.WithTo(TestItem.AddressB).TestObject, + Build.WithTo(TestItem.AddressC).TestObject, + Build.WithTo(TestItem.AddressD).TestObject, + Build.WithTo(TestItem.AddressE).TestObject, + Build.WithTo(TestItem.AddressF).TestObject, + + Build.WithGasLimit(0).TestObject, + Build.WithGasLimit(123).TestObject, + Build.WithGasLimit(long.MaxValue).TestObject, + + Build.WithValue(UInt256.Zero).TestObject, + Build.WithValue((UInt256) 123).TestObject, + Build.WithValue(UInt256.MaxValue).TestObject, + + Build.WithData(TestItem.RandomDataA).TestObject, + Build.WithData(TestItem.RandomDataB).TestObject, + Build.WithData(TestItem.RandomDataC).TestObject, + Build.WithData(TestItem.RandomDataD).TestObject, + + Build.WithGasPrice(UInt256.Zero).TestObject, + Build.WithGasPrice((UInt256) 123).TestObject, + Build.WithGasPrice(UInt256.MaxValue).TestObject, + + Build.WithChainId(null).TestObject, + Build.WithChainId(BlockchainIds.Mainnet).TestObject, + Build.WithChainId(BlockchainIds.Sepolia).TestObject, + Build.WithChainId(0).TestObject, + Build.WithChainId(ulong.MaxValue).TestObject, + + Build.WithAccessList(new AccessList.Builder() + .AddAddress(TestItem.AddressA) + .AddStorage(1) + .AddStorage(2) + .AddStorage(3) + .Build()).TestObject, + + Build.WithAccessList(new AccessList.Builder() + .AddAddress(TestItem.AddressA) + .AddStorage(1) + .AddStorage(2) + .AddAddress(TestItem.AddressB) + .AddStorage(3) + .Build()).TestObject, + + Build.WithSignature(TestItemSignatures.RandomSignatureA).TestObject, + Build.WithSignature(TestItemSignatures.RandomSignatureB).TestObject, + ]; + + [SetUp] + public void SetUp() + { + RpcAccessListTransaction.DefaultChainId = BlockchainIds.Mainnet; + } + + [TestCaseSource(nameof(LegacyTransactions))] + public void Always_satisfies_schema(Transaction transaction) + { + var rpcTx = RpcAccessListTransaction.FromTransaction(transaction); + string serialized = _serializer.Serialize(rpcTx); + using var jsonDocument = JsonDocument.Parse(serialized); + var json = jsonDocument.RootElement; + + json.GetProperty("type").GetString().Should().MatchRegex("^0x1$"); + json.GetProperty("nonce").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("to").GetString()?.Should().MatchRegex("^0x[0-9a-fA-F]{40}$"); + json.GetProperty("gas").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("value").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("input").GetString().Should().MatchRegex("^0x[0-9a-f]*$"); + json.GetProperty("gasPrice").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + // Suprising inconsistency in `FluentAssertions` where `AllSatisfy` fails on empty collections. + // This requires wrapping the assertion in a condition. + // See: https://github.com/fluentassertions/fluentassertions/discussions/2143#discussioncomment-9677309 + var accessList = json.GetProperty("accessList").EnumerateArray(); + if (accessList.Any()) + { + accessList.Should().AllSatisfy(item => + { + item.GetProperty("address").GetString().Should().MatchRegex("^0x[0-9a-fA-F]{40}$"); + item.GetProperty("storageKeys").EnumerateArray().Should().AllSatisfy(key => + key.GetString().Should().MatchRegex("^0x[0-9a-f]{64}$") + ); + }); + } + json.GetProperty("chainId").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + var yParity = json.GetProperty("yParity").GetString(); + yParity.Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + if (json.TryGetProperty("v", out var v)) + { + v.GetString().Should().Be(yParity); + } + json.GetProperty("r").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("s").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + } +} From 88dd0e34ebc6f7563ebad55679e064b57d79505a Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Thu, 5 Sep 2024 15:28:02 -0300 Subject: [PATCH 05/76] Add `RandomSignature`s --- .../Nethermind.Core.Test/Builders/TestItem.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs index a653619f397..4cb2a3662df 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TestItem.cs @@ -38,6 +38,13 @@ static TestItem() Keccaks[i - 1] = Keccak.Compute(PublicKeys[i - 1].Bytes); ValueKeccaks[i - 1] = Keccaks[i - 1]; } + + byte[] r = new byte[32]; + byte[] s = new byte[32]; + r[1] = 1; + s[2] = 2; + RandomSignatureA = new Signature(r, s, 27); + RandomSignatureB = new Signature(r, s, 28); } public static Hash256 KeccakFromNumber(int i) @@ -92,6 +99,9 @@ public static Hash256 KeccakFromNumber(int i) public static Address AddressE = PublicKeyE.Address; public static Address AddressF = PublicKeyF.Address; + public static readonly Signature RandomSignatureA; + public static readonly Signature RandomSignatureB; + public static Withdrawal WithdrawalA_1Eth = new() { Address = AddressA, Index = 1, ValidatorIndex = 2001, AmountInGwei = 1_000_000_000 }; public static Withdrawal WithdrawalB_2Eth = new() { Address = AddressB, Index = 2, ValidatorIndex = 2002, AmountInGwei = 2_000_000_000 }; public static Withdrawal WithdrawalC_3Eth = new() { Address = AddressC, Index = 3, ValidatorIndex = 2003, AmountInGwei = 3_000_000_000 }; From e9994eaeaddabbda97b5dee441a96c3ca165539f Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Thu, 5 Sep 2024 15:28:13 -0300 Subject: [PATCH 06/76] Use `TestItem` random `Signature`s --- .../RpcAccessListTransactionTests.cs | 4 ++-- .../RpcLegacyTransactionTests.cs | 21 ++----------------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs index c599dfa87b4..7fb0123e0c4 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs @@ -74,8 +74,8 @@ public class RpcAccessListTransactionTests .AddStorage(3) .Build()).TestObject, - Build.WithSignature(TestItemSignatures.RandomSignatureA).TestObject, - Build.WithSignature(TestItemSignatures.RandomSignatureB).TestObject, + Build.WithSignature(TestItem.RandomSignatureA).TestObject, + Build.WithSignature(TestItem.RandomSignatureB).TestObject, ]; [SetUp] diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs index 2ef3aeca5b4..2a9e62d78d7 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs @@ -58,8 +58,8 @@ public class RpcLegacyTransactionTests BuildALegacyTransaction.WithChainId(0).TestObject, BuildALegacyTransaction.WithChainId(ulong.MaxValue).TestObject, - BuildALegacyTransaction.WithSignature(TestItemSignatures.RandomSignatureA).TestObject, - BuildALegacyTransaction.WithSignature(TestItemSignatures.RandomSignatureB).TestObject, + BuildALegacyTransaction.WithSignature(TestItem.RandomSignatureA).TestObject, + BuildALegacyTransaction.WithSignature(TestItem.RandomSignatureB).TestObject, ]; [TestCaseSource(nameof(LegacyTransactions))] @@ -87,20 +87,3 @@ public void Always_satisfies_schema(Transaction transaction) json.GetProperty("s").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); } } - -// TODO: We don't have a good source of "random" signatures for testing -public static class TestItemSignatures -{ - public static readonly Signature RandomSignatureA; - public static readonly Signature RandomSignatureB; - - static TestItemSignatures() - { - byte[] r = new byte[32]; - byte[] s = new byte[32]; - r[1] = 1; - s[2] = 2; - RandomSignatureA = new Signature(r, s, 27); - RandomSignatureB = new Signature(r, s, 28); - } -} From 1839a0dfc405f9d9e6e0c881928b9eb5d428e9b6 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Thu, 5 Sep 2024 15:53:18 -0300 Subject: [PATCH 07/76] Initial `RpcEIP1559Transaction` --- .../RpcTransaction/RpcEIP1559Transaction.cs | 96 +++++++++++++ .../RpcEIP1559TransactionTests.cs | 136 ++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcEIP1559TransactionTests.cs diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs new file mode 100644 index 00000000000..8605ab60d14 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Int256; + +namespace Nethermind.Facade.Eth.RpcTransaction; + +public sealed class RpcEIP1559Transaction +{ + // HACK: To ensure that serialized Txs always have a `ChainId` we keep the last loaded `ChainSpec`. + // See: https://github.com/NethermindEth/nethermind/pull/6061#discussion_r1321634914 + public static UInt256? DefaultChainId { get; set; } + + public TxType Type { get; set; } + + public UInt256 Nonce { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public Address? To { get; set; } + + public long Gas { get; set; } + + public UInt256 Value { get; set; } + + public byte[] Input { get; set; } + + public UInt256 MaxPriorityFeePerGas { get; set; } + + public UInt256 MaxFeePerGas { get; set; } + + /// + /// The effective gas price paid by the sender in wei. For transactions not yet included in a block, this value should be set equal to the max fee per gas. + /// This field is DEPRECATED, please transition to using effectiveGasPrice in the receipt object going forward. + /// + public UInt256 GasPrice { get; set; } + + public IEnumerable AccessList { get; set; } + + public UInt256 ChainId { get; set; } + + public UInt256 YParity { get; set; } + + /// + /// For backwards compatibility, v is optionally provided as an alternative to yParity. + /// This field is DEPRECATED and all use of it should migrate to yParity. + /// + public UInt256? V { get; set; } + + public UInt256 R { get; set; } + + public UInt256 S { get; set; } + + private RpcEIP1559Transaction() { } + + public static RpcEIP1559Transaction? FromTransaction(Transaction? transaction) + { + if (transaction is null) + { + return null; + } + + if (transaction.Type != TxType.EIP1559) + { + throw new ArgumentException("Transaction type must be EIP1559"); + } + + return new RpcEIP1559Transaction + { + Type = transaction.Type, + Nonce = transaction.Nonce, + To = transaction.To, + Gas = transaction.GasLimit, + Value = transaction.Value, + Input = transaction.Data.AsArray() ?? [], + MaxPriorityFeePerGas = transaction.MaxPriorityFeePerGas, + MaxFeePerGas = transaction.MaxFeePerGas, + GasPrice = transaction.GasPrice, + AccessList = transaction.AccessList is null + ? Array.Empty() + : AccessListItemForRpc.FromAccessList(transaction.AccessList), + ChainId = transaction.ChainId + ?? DefaultChainId + ?? BlockchainIds.Mainnet, + + YParity = transaction.Signature?.RecoveryId ?? 0, + V = transaction.Signature?.RecoveryId ?? 0, + R = new UInt256(transaction.Signature?.R ?? [], true), + S = new UInt256(transaction.Signature?.S ?? [], true), + }; + } +} diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcEIP1559TransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcEIP1559TransactionTests.cs new file mode 100644 index 00000000000..c1d2cb08bbb --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcEIP1559TransactionTests.cs @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Linq; +using System.Text.Json; +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Eip2930; +using Nethermind.Core.Test.Builders; +using Nethermind.Facade.Eth.RpcTransaction; +using Nethermind.Int256; +using Nethermind.Serialization.Json; +using NUnit.Framework; + +namespace Nethermind.JsonRpc.Test.Modules.RpcTransaction; + +public class RpcEIP1559TransactionTests +{ + private readonly EthereumJsonSerializer _serializer = new(); + + private static TransactionBuilder Build => Core.Test.Builders.Build.A.Transaction.WithType(TxType.EIP1559); + + public static readonly Transaction[] Transactions = + [ + Build.TestObject, + + Build.WithNonce(UInt256.Zero).TestObject, + Build.WithNonce(123).TestObject, + Build.WithNonce(UInt256.MaxValue).TestObject, + + Build.WithTo(null).TestObject, + Build.WithTo(TestItem.AddressA).TestObject, + Build.WithTo(TestItem.AddressB).TestObject, + Build.WithTo(TestItem.AddressC).TestObject, + Build.WithTo(TestItem.AddressD).TestObject, + Build.WithTo(TestItem.AddressE).TestObject, + Build.WithTo(TestItem.AddressF).TestObject, + + Build.WithGasLimit(0).TestObject, + Build.WithGasLimit(123).TestObject, + Build.WithGasLimit(long.MaxValue).TestObject, + + Build.WithValue(UInt256.Zero).TestObject, + Build.WithValue(123).TestObject, + Build.WithValue(UInt256.MaxValue).TestObject, + + Build.WithData(TestItem.RandomDataA).TestObject, + Build.WithData(TestItem.RandomDataB).TestObject, + Build.WithData(TestItem.RandomDataC).TestObject, + Build.WithData(TestItem.RandomDataD).TestObject, + + Build.WithMaxPriorityFeePerGas(UInt256.Zero).TestObject, + Build.WithMaxPriorityFeePerGas(123).TestObject, + Build.WithMaxPriorityFeePerGas(UInt256.MaxValue).TestObject, + + Build.WithMaxFeePerGas(UInt256.Zero).TestObject, + Build.WithMaxFeePerGas(123).TestObject, + Build.WithMaxFeePerGas(UInt256.MaxValue).TestObject, + + Build.WithGasPrice(UInt256.Zero).TestObject, + Build.WithGasPrice(123).TestObject, + Build.WithGasPrice(UInt256.MaxValue).TestObject, + + Build.WithChainId(null).TestObject, + Build.WithChainId(BlockchainIds.Mainnet).TestObject, + Build.WithChainId(BlockchainIds.Sepolia).TestObject, + Build.WithChainId(0).TestObject, + Build.WithChainId(ulong.MaxValue).TestObject, + + Build.WithAccessList(new AccessList.Builder() + .AddAddress(TestItem.AddressA) + .AddStorage(1) + .AddStorage(2) + .AddStorage(3) + .Build()).TestObject, + + Build.WithAccessList(new AccessList.Builder() + .AddAddress(TestItem.AddressA) + .AddStorage(1) + .AddStorage(2) + .AddAddress(TestItem.AddressB) + .AddStorage(3) + .Build()).TestObject, + + Build.WithSignature(TestItem.RandomSignatureA).TestObject, + Build.WithSignature(TestItem.RandomSignatureB).TestObject, + ]; + + [SetUp] + public void SetUp() + { + RpcAccessListTransaction.DefaultChainId = BlockchainIds.Mainnet; + } + + [TestCaseSource(nameof(Transactions))] + public void Always_satisfies_schema(Transaction transaction) + { + var rpcTx = RpcEIP1559Transaction.FromTransaction(transaction); + string serialized = _serializer.Serialize(rpcTx); + using var jsonDocument = JsonDocument.Parse(serialized); + var json = jsonDocument.RootElement; + + json.GetProperty("type").GetString().Should().MatchRegex("^0x2$"); + json.GetProperty("nonce").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("to").GetString()?.Should().MatchRegex("^0x[0-9a-fA-F]{40}$"); + json.GetProperty("gas").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("value").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("input").GetString().Should().MatchRegex("^0x[0-9a-f]*$"); + json.GetProperty("maxPriorityFeePerGas").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("maxFeePerGas").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("gasPrice").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + // Suprising inconsistency in `FluentAssertions` where `AllSatisfy` fails on empty collections. + // This requires wrapping the assertion in a condition. + // See: https://github.com/fluentassertions/fluentassertions/discussions/2143#discussioncomment-9677309 + var accessList = json.GetProperty("accessList").EnumerateArray(); + if (accessList.Any()) + { + accessList.Should().AllSatisfy(item => + { + item.GetProperty("address").GetString().Should().MatchRegex("^0x[0-9a-fA-F]{40}$"); + item.GetProperty("storageKeys").EnumerateArray().Should().AllSatisfy(key => + key.GetString().Should().MatchRegex("^0x[0-9a-f]{64}$") + ); + }); + } + json.GetProperty("chainId").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + var yParity = json.GetProperty("yParity").GetString(); + yParity.Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + if (json.TryGetProperty("v", out var v)) + { + v.GetString().Should().Be(yParity); + } + json.GetProperty("r").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("s").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + } +} From 2833a9e9c49294f9556682bc44c1a7923419e3c5 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Thu, 5 Sep 2024 15:53:58 -0300 Subject: [PATCH 08/76] Rename `TestCaseSource` --- .../Modules/RpcTransaction/RpcAccessListTransactionTests.cs | 4 ++-- .../Modules/RpcTransaction/RpcLegacyTransactionTests.cs | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs index 7fb0123e0c4..749dbd60cfd 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs @@ -20,7 +20,7 @@ public class RpcAccessListTransactionTests private static TransactionBuilder Build => Core.Test.Builders.Build.A.Transaction.WithType(TxType.AccessList); - public static readonly Transaction[] LegacyTransactions = + public static readonly Transaction[] Transactions = [ Build.TestObject, @@ -84,7 +84,7 @@ public void SetUp() RpcAccessListTransaction.DefaultChainId = BlockchainIds.Mainnet; } - [TestCaseSource(nameof(LegacyTransactions))] + [TestCaseSource(nameof(Transactions))] public void Always_satisfies_schema(Transaction transaction) { var rpcTx = RpcAccessListTransaction.FromTransaction(transaction); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs index 2a9e62d78d7..7427a35a0b6 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs @@ -4,7 +4,6 @@ using System.Text.Json; using FluentAssertions; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Int256; @@ -19,7 +18,7 @@ public class RpcLegacyTransactionTests private static TransactionBuilder BuildALegacyTransaction => Build.A.Transaction.WithType(TxType.Legacy); - public static readonly Transaction[] LegacyTransactions = + public static readonly Transaction[] Transactions = [ BuildALegacyTransaction.TestObject, @@ -62,7 +61,7 @@ public class RpcLegacyTransactionTests BuildALegacyTransaction.WithSignature(TestItem.RandomSignatureB).TestObject, ]; - [TestCaseSource(nameof(LegacyTransactions))] + [TestCaseSource(nameof(Transactions))] public void Always_satisfies_schema(Transaction transaction) { var rpcTx = RpcLegacyTransaction.FromTransaction(transaction); From 3caed48242121f80297dfb05ec25e23540ad184d Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Thu, 5 Sep 2024 16:30:29 -0300 Subject: [PATCH 09/76] Fix `DefaultChainId` --- .../Modules/RpcTransaction/RpcEIP1559TransactionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcEIP1559TransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcEIP1559TransactionTests.cs index c1d2cb08bbb..8054f9f2bb8 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcEIP1559TransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcEIP1559TransactionTests.cs @@ -89,7 +89,7 @@ public class RpcEIP1559TransactionTests [SetUp] public void SetUp() { - RpcAccessListTransaction.DefaultChainId = BlockchainIds.Mainnet; + RpcEIP1559Transaction.DefaultChainId = BlockchainIds.Mainnet; } [TestCaseSource(nameof(Transactions))] From 67ec2221ddb61d8b9dc074baa3d34bc5eb9781a5 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Thu, 5 Sep 2024 17:12:52 -0300 Subject: [PATCH 10/76] Guarantee Blob transaction invariants --- .../Builders/TransactionBuilder.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs index 58b6917866d..bb11b4f55e3 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs @@ -134,7 +134,7 @@ public TransactionBuilder WithSenderAddress(Address? address) return this; } - public TransactionBuilder WithMaxFeePerBlobGas(UInt256? maxFeePerBlobGas) + public TransactionBuilder WithMaxFeePerBlobGas(UInt256 maxFeePerBlobGas) { TestObjectInternal.MaxFeePerBlobGas = maxFeePerBlobGas; return this; @@ -265,6 +265,16 @@ public TransactionBuilder SignedAndResolved(PrivateKey? privateKey = null) protected override void BeforeReturn() { base.BeforeReturn(); + + // Since hash calculation requires certail values to be present in Blob transactions + // we initalize them to sane defaults here. + // TODO: This should be removed when we have a proper Transaction type hierarchy + if (TestObjectInternal.Type == TxType.Blob) + { + TestObjectInternal.BlobVersionedHashes ??= []; + TestObjectInternal.MaxFeePerBlobGas ??= 0; + } + if (TestObjectInternal.IsSigned) { TestObjectInternal.Hash = TestObjectInternal.CalculateHash(); From 0f997c99ff031c8938763916193ed16acb21ffbc Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Thu, 5 Sep 2024 17:13:03 -0300 Subject: [PATCH 11/76] Initial `RpcBlobTransaction` --- .../Eth/RpcTransaction/RpcBlobTransaction.cs | 90 +++++++++++ .../RpcTransaction/RpcBlobTransactionTests.cs | 146 ++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcBlobTransactionTests.cs diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs new file mode 100644 index 00000000000..b70217f257a --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs @@ -0,0 +1,90 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Int256; + +namespace Nethermind.Facade.Eth.RpcTransaction; + +public sealed class RpcBlobTransaction +{ + // HACK: To ensure that serialized Txs always have a `ChainId` we keep the last loaded `ChainSpec`. + // See: https://github.com/NethermindEth/nethermind/pull/6061#discussion_r1321634914 + public static UInt256? DefaultChainId { get; set; } + + public TxType Type { get; set; } + + public UInt256 Nonce { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public Address? To { get; set; } + + public long Gas { get; set; } + + public UInt256 Value { get; set; } + + public byte[] Input { get; set; } + + public UInt256 MaxPriorityFeePerGas { get; set; } + + public UInt256 MaxFeePerGas { get; set; } + + public UInt256 MaxFeePerBlobGas { get; set; } + + public IEnumerable AccessList { get; set; } + + // TODO: Each item should be a 32 byte array + // Currently we don't enforce this (hashes can have any length) + public byte[][] BlobVersionedHashes { get; set; } + + public UInt256 ChainId { get; set; } + + public UInt256 YParity { get; set; } + + public UInt256 R { get; set; } + + public UInt256 S { get; set; } + + private RpcBlobTransaction() { } + + public static RpcBlobTransaction? FromTransaction(Transaction? transaction) + { + if (transaction is null) + { + return null; + } + + if (transaction.Type != TxType.Blob) + { + throw new ArgumentException("Transaction type must be Blob"); + } + + return new RpcBlobTransaction + { + Type = transaction.Type, + Nonce = transaction.Nonce, + To = transaction.To, + Gas = transaction.GasLimit, + Value = transaction.Value, + Input = transaction.Data.AsArray() ?? [], + MaxPriorityFeePerGas = transaction.MaxPriorityFeePerGas, + MaxFeePerGas = transaction.MaxFeePerGas, + MaxFeePerBlobGas = transaction.MaxFeePerBlobGas ?? 0, + AccessList = transaction.AccessList is null + ? Array.Empty() + : AccessListItemForRpc.FromAccessList(transaction.AccessList), + BlobVersionedHashes = transaction.BlobVersionedHashes ?? [], + ChainId = transaction.ChainId + ?? DefaultChainId + ?? BlockchainIds.Mainnet, + + YParity = transaction.Signature?.RecoveryId ?? 0, + R = new UInt256(transaction.Signature?.R ?? [], true), + S = new UInt256(transaction.Signature?.S ?? [], true), + }; + } +} diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcBlobTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcBlobTransactionTests.cs new file mode 100644 index 00000000000..85d6603da80 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcBlobTransactionTests.cs @@ -0,0 +1,146 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Linq; +using System.Text.Json; +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Eip2930; +using Nethermind.Core.Test.Builders; +using Nethermind.Facade.Eth.RpcTransaction; +using Nethermind.Int256; +using Nethermind.Serialization.Json; +using NUnit.Framework; + +namespace Nethermind.JsonRpc.Test.Modules.RpcTransaction; + +public class RpcBlobTransactionTests +{ + private readonly EthereumJsonSerializer _serializer = new(); + + private static TransactionBuilder Build => Core.Test.Builders.Build.A.Transaction.WithType(TxType.Blob); + + public static readonly Transaction[] Transactions = + [ + Build.TestObject, + + Build.WithNonce(UInt256.Zero).TestObject, + Build.WithNonce(123).TestObject, + Build.WithNonce(UInt256.MaxValue).TestObject, + + Build.WithTo(null).TestObject, + Build.WithTo(TestItem.AddressA).TestObject, + Build.WithTo(TestItem.AddressB).TestObject, + Build.WithTo(TestItem.AddressC).TestObject, + Build.WithTo(TestItem.AddressD).TestObject, + Build.WithTo(TestItem.AddressE).TestObject, + Build.WithTo(TestItem.AddressF).TestObject, + + Build.WithGasLimit(0).TestObject, + Build.WithGasLimit(123).TestObject, + Build.WithGasLimit(long.MaxValue).TestObject, + + Build.WithValue(UInt256.Zero).TestObject, + Build.WithValue(123).TestObject, + Build.WithValue(UInt256.MaxValue).TestObject, + + Build.WithData(TestItem.RandomDataA).TestObject, + Build.WithData(TestItem.RandomDataB).TestObject, + Build.WithData(TestItem.RandomDataC).TestObject, + Build.WithData(TestItem.RandomDataD).TestObject, + + Build.WithMaxPriorityFeePerGas(UInt256.Zero).TestObject, + Build.WithMaxPriorityFeePerGas(123).TestObject, + Build.WithMaxPriorityFeePerGas(UInt256.MaxValue).TestObject, + + Build.WithMaxFeePerGas(UInt256.Zero).TestObject, + Build.WithMaxFeePerGas(123).TestObject, + Build.WithMaxFeePerGas(UInt256.MaxValue).TestObject, + + Build.WithMaxFeePerBlobGas(UInt256.Zero).TestObject, + Build.WithMaxFeePerBlobGas(123).TestObject, + Build.WithMaxFeePerBlobGas(UInt256.MaxValue).TestObject, + + Build.WithAccessList(new AccessList.Builder() + .AddAddress(TestItem.AddressA) + .AddStorage(1) + .AddStorage(2) + .AddStorage(3) + .Build()).TestObject, + + Build.WithAccessList(new AccessList.Builder() + .AddAddress(TestItem.AddressA) + .AddStorage(1) + .AddStorage(2) + .AddAddress(TestItem.AddressB) + .AddStorage(3) + .Build()).TestObject, + + // TODO: We want a similar API to `AccessList.Builder` + Build.WithBlobVersionedHashes(0).TestObject, + Build.WithBlobVersionedHashes(1).TestObject, + Build.WithBlobVersionedHashes(50).TestObject, + + Build.WithChainId(null).TestObject, + Build.WithChainId(BlockchainIds.Mainnet).TestObject, + Build.WithChainId(BlockchainIds.Sepolia).TestObject, + Build.WithChainId(0).TestObject, + Build.WithChainId(ulong.MaxValue).TestObject, + + Build.WithSignature(TestItem.RandomSignatureA).TestObject, + Build.WithSignature(TestItem.RandomSignatureB).TestObject, + ]; + + [SetUp] + public void SetUp() + { + RpcBlobTransaction.DefaultChainId = BlockchainIds.Mainnet; + } + + [TestCaseSource(nameof(Transactions))] + public void Always_satisfies_schema(Transaction transaction) + { + var rpcTx = RpcBlobTransaction.FromTransaction(transaction); + string serialized = _serializer.Serialize(rpcTx); + using var jsonDocument = JsonDocument.Parse(serialized); + var json = jsonDocument.RootElement; + + // NOTE: This does not look correct since we are not checking for the exact value (`0x2`). + // Essentially we're accepting all hex values `0x00 ... 0xFF`, including `0x` + json.GetProperty("type").GetString().Should().MatchRegex("^0x([0-9a-fA-F]?){1,2}$"); + + json.GetProperty("nonce").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("to").GetString()?.Should().MatchRegex("^0x[0-9a-fA-F]{40}$"); + json.GetProperty("gas").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("value").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("input").GetString().Should().MatchRegex("^0x[0-9a-f]*$"); + json.GetProperty("maxPriorityFeePerGas").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("maxFeePerGas").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("maxFeePerBlobGas").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + // Suprising inconsistency in `FluentAssertions` where `AllSatisfy` fails on empty collections. + // See: https://github.com/fluentassertions/fluentassertions/discussions/2143#discussioncomment-9677309 + var accessList = json.GetProperty("accessList").EnumerateArray(); + if (accessList.Any()) + { + accessList.Should().AllSatisfy(item => + { + item.GetProperty("address").GetString().Should().MatchRegex("^0x[0-9a-fA-F]{40}$"); + item.GetProperty("storageKeys").EnumerateArray().Should().AllSatisfy(key => + key.GetString().Should().MatchRegex("^0x[0-9a-f]{64}$") + ); + }); + } + var blobVersionedHashes = json.GetProperty("blobVersionedHashes").EnumerateArray(); + if (blobVersionedHashes.Any()) + { + blobVersionedHashes.Should().AllSatisfy(hash => + hash.GetString().Should().MatchRegex("^0x[0-9a-f]{64}$") + ); + } + json.GetProperty("chainId").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + var yParity = json.GetProperty("yParity").GetString(); + yParity.Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("r").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("s").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + } +} From 5ae394a741eb747338d2d659e595509a9d136afc Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Tue, 10 Sep 2024 16:12:14 -0300 Subject: [PATCH 12/76] Initial `IRpcTransaction` --- .../Eth/RpcTransaction/IRpcTransaction.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs new file mode 100644 index 00000000000..ee719abbe2d --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Nethermind.Facade.Eth.RpcTransaction; + +public interface IRpcTransaction +{ + public static readonly JsonConverter JsonConverter = new ConverterImpl(); + + private class ConverterImpl : JsonConverter + { + public override IRpcTransaction? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotSupportedException(); + public override void Write(Utf8JsonWriter writer, IRpcTransaction value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, value.GetType(), options); + } +} From 14d3cae898e67390cd3adf320f1782579e99bed1 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Tue, 10 Sep 2024 16:12:25 -0300 Subject: [PATCH 13/76] Initial `IRpcTransactionConverter` --- .../IRpcTransactionConverter.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransactionConverter.cs diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransactionConverter.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransactionConverter.cs new file mode 100644 index 00000000000..e2300fd4823 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransactionConverter.cs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; + +namespace Nethermind.Facade.Eth.RpcTransaction; + +public interface IRpcTransactionConverter +{ + IRpcTransaction FromTransaction(Transaction tx, TxReceipt receipt); +} + +public class ComposeTransactionConverter : IRpcTransactionConverter +{ + private readonly IRpcTransactionConverter?[] _converters = new IRpcTransactionConverter?[Transaction.MaxTxType + 1]; + + public ComposeTransactionConverter RegisterConverter(TxType txType, IRpcTransactionConverter converter) + { + _converters[(byte)txType] = converter; + return this; + } + + public IRpcTransaction FromTransaction(Transaction tx, TxReceipt receipt) + { + var converter = _converters[(byte)tx.Type] ?? throw new ArgumentException("No converter for transaction type"); + return converter.FromTransaction(tx, receipt); + } +} From 11c237e56a2779111bc0c884921c788f24a3f2ca Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Tue, 10 Sep 2024 16:16:50 -0300 Subject: [PATCH 14/76] Move `RpcXTransaction` to class hierarchy --- .../RpcAccessListTransaction.cs | 73 ++++------------ .../Eth/RpcTransaction/RpcBlobTransaction.cs | 79 ++--------------- .../RpcTransaction/RpcEIP1559Transaction.cs | 84 +++---------------- .../RpcTransaction/RpcLegacyTransaction.cs | 49 +++++------ 4 files changed, 57 insertions(+), 228 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs index 5430225642d..ad518e7405f 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs @@ -3,37 +3,20 @@ using System; using System.Collections.Generic; -using System.Text.Json.Serialization; using Nethermind.Core; -using Nethermind.Core.Extensions; using Nethermind.Int256; namespace Nethermind.Facade.Eth.RpcTransaction; -public sealed class RpcAccessListTransaction +public class RpcAccessListTransaction : RpcLegacyTransaction { // HACK: To ensure that serialized Txs always have a `ChainId` we keep the last loaded `ChainSpec`. // See: https://github.com/NethermindEth/nethermind/pull/6061#discussion_r1321634914 public static UInt256? DefaultChainId { get; set; } - public TxType Type { get; set; } - - public UInt256 Nonce { get; set; } - - [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public Address? To { get; set; } - - public long Gas { get; set; } - - public UInt256 Value { get; set; } - - public byte[] Input { get; set; } - - public UInt256 GasPrice { get; set; } - public IEnumerable AccessList { get; set; } - public UInt256 ChainId { get; set; } + public new UInt256 ChainId { get; set; } public UInt256 YParity { get; set; } @@ -41,46 +24,24 @@ public sealed class RpcAccessListTransaction /// For backwards compatibility, v is optionally provided as an alternative to yParity. /// This field is DEPRECATED and all use of it should migrate to yParity. /// - public UInt256? V { get; set; } + public new UInt256? V { get; set; } - public UInt256 R { get; set; } - - public UInt256 S { get; set; } - - private RpcAccessListTransaction() { } - - public static RpcAccessListTransaction? FromTransaction(Transaction? transaction) + public RpcAccessListTransaction(Transaction transaction) : base(transaction) { - if (transaction is null) - { - return null; - } - - if (transaction.Type != TxType.AccessList) - { - throw new ArgumentException("Transaction type must be AccessList"); - } + AccessList = transaction.AccessList is null + ? Array.Empty() + : AccessListItemForRpc.FromAccessList(transaction.AccessList); + ChainId = transaction.ChainId + ?? DefaultChainId + ?? BlockchainIds.Mainnet; + YParity = transaction.Signature?.RecoveryId ?? 0; + V = transaction.Signature?.RecoveryId; + } - return new RpcAccessListTransaction - { - Type = transaction.Type, - Nonce = transaction.Nonce, - To = transaction.To, - Gas = transaction.GasLimit, - Value = transaction.Value, - Input = transaction.Data.AsArray() ?? [], - GasPrice = transaction.GasPrice, - AccessList = transaction.AccessList is null - ? Array.Empty() - : AccessListItemForRpc.FromAccessList(transaction.AccessList), - ChainId = transaction.ChainId - ?? DefaultChainId - ?? BlockchainIds.Mainnet, + public new static readonly IRpcTransactionConverter Converter = new ConverterImpl(); - YParity = transaction.Signature?.RecoveryId ?? 0, - V = transaction.Signature?.RecoveryId ?? 0, - R = new UInt256(transaction.Signature?.R ?? [], true), - S = new UInt256(transaction.Signature?.S ?? [], true), - }; + private class ConverterImpl : IRpcTransactionConverter + { + public IRpcTransaction FromTransaction(Transaction transaction, TxReceipt receipt) => new RpcAccessListTransaction(transaction); } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs index b70217f257a..b93a202ae66 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs @@ -1,90 +1,29 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Generic; -using System.Text.Json.Serialization; using Nethermind.Core; -using Nethermind.Core.Extensions; using Nethermind.Int256; namespace Nethermind.Facade.Eth.RpcTransaction; -public sealed class RpcBlobTransaction +public class RpcBlobTransaction : RpcEIP1559Transaction { - // HACK: To ensure that serialized Txs always have a `ChainId` we keep the last loaded `ChainSpec`. - // See: https://github.com/NethermindEth/nethermind/pull/6061#discussion_r1321634914 - public static UInt256? DefaultChainId { get; set; } - - public TxType Type { get; set; } - - public UInt256 Nonce { get; set; } - - [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public Address? To { get; set; } - - public long Gas { get; set; } - - public UInt256 Value { get; set; } - - public byte[] Input { get; set; } - - public UInt256 MaxPriorityFeePerGas { get; set; } - - public UInt256 MaxFeePerGas { get; set; } - public UInt256 MaxFeePerBlobGas { get; set; } - public IEnumerable AccessList { get; set; } - // TODO: Each item should be a 32 byte array // Currently we don't enforce this (hashes can have any length) public byte[][] BlobVersionedHashes { get; set; } - public UInt256 ChainId { get; set; } - - public UInt256 YParity { get; set; } - - public UInt256 R { get; set; } - - public UInt256 S { get; set; } - - private RpcBlobTransaction() { } - - public static RpcBlobTransaction? FromTransaction(Transaction? transaction) + public RpcBlobTransaction(Transaction transaction) : base(transaction) { - if (transaction is null) - { - return null; - } - - if (transaction.Type != TxType.Blob) - { - throw new ArgumentException("Transaction type must be Blob"); - } + MaxFeePerBlobGas = transaction.MaxFeePerBlobGas ?? 0; + BlobVersionedHashes = transaction.BlobVersionedHashes ?? []; + } - return new RpcBlobTransaction - { - Type = transaction.Type, - Nonce = transaction.Nonce, - To = transaction.To, - Gas = transaction.GasLimit, - Value = transaction.Value, - Input = transaction.Data.AsArray() ?? [], - MaxPriorityFeePerGas = transaction.MaxPriorityFeePerGas, - MaxFeePerGas = transaction.MaxFeePerGas, - MaxFeePerBlobGas = transaction.MaxFeePerBlobGas ?? 0, - AccessList = transaction.AccessList is null - ? Array.Empty() - : AccessListItemForRpc.FromAccessList(transaction.AccessList), - BlobVersionedHashes = transaction.BlobVersionedHashes ?? [], - ChainId = transaction.ChainId - ?? DefaultChainId - ?? BlockchainIds.Mainnet, + public new static readonly IRpcTransactionConverter Converter = new ConverterImpl(); - YParity = transaction.Signature?.RecoveryId ?? 0, - R = new UInt256(transaction.Signature?.R ?? [], true), - S = new UInt256(transaction.Signature?.S ?? [], true), - }; + private class ConverterImpl : IRpcTransactionConverter + { + public IRpcTransaction FromTransaction(Transaction transaction, TxReceipt receipt) => new RpcBlobTransaction(transaction); } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs index 8605ab60d14..36116d6d762 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs @@ -1,34 +1,13 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Generic; -using System.Text.Json.Serialization; using Nethermind.Core; -using Nethermind.Core.Extensions; using Nethermind.Int256; namespace Nethermind.Facade.Eth.RpcTransaction; -public sealed class RpcEIP1559Transaction +public class RpcEIP1559Transaction : RpcAccessListTransaction { - // HACK: To ensure that serialized Txs always have a `ChainId` we keep the last loaded `ChainSpec`. - // See: https://github.com/NethermindEth/nethermind/pull/6061#discussion_r1321634914 - public static UInt256? DefaultChainId { get; set; } - - public TxType Type { get; set; } - - public UInt256 Nonce { get; set; } - - [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public Address? To { get; set; } - - public long Gas { get; set; } - - public UInt256 Value { get; set; } - - public byte[] Input { get; set; } - public UInt256 MaxPriorityFeePerGas { get; set; } public UInt256 MaxFeePerGas { get; set; } @@ -37,60 +16,19 @@ public sealed class RpcEIP1559Transaction /// The effective gas price paid by the sender in wei. For transactions not yet included in a block, this value should be set equal to the max fee per gas. /// This field is DEPRECATED, please transition to using effectiveGasPrice in the receipt object going forward. /// - public UInt256 GasPrice { get; set; } - - public IEnumerable AccessList { get; set; } - - public UInt256 ChainId { get; set; } - - public UInt256 YParity { get; set; } + public new UInt256 GasPrice { get; set; } - /// - /// For backwards compatibility, v is optionally provided as an alternative to yParity. - /// This field is DEPRECATED and all use of it should migrate to yParity. - /// - public UInt256? V { get; set; } - - public UInt256 R { get; set; } - - public UInt256 S { get; set; } - - private RpcEIP1559Transaction() { } - - public static RpcEIP1559Transaction? FromTransaction(Transaction? transaction) + public RpcEIP1559Transaction(Transaction transaction) : base(transaction) { - if (transaction is null) - { - return null; - } - - if (transaction.Type != TxType.EIP1559) - { - throw new ArgumentException("Transaction type must be EIP1559"); - } + MaxPriorityFeePerGas = transaction.MaxPriorityFeePerGas; + MaxFeePerGas = transaction.MaxFeePerGas; + GasPrice = transaction.GasPrice; + } - return new RpcEIP1559Transaction - { - Type = transaction.Type, - Nonce = transaction.Nonce, - To = transaction.To, - Gas = transaction.GasLimit, - Value = transaction.Value, - Input = transaction.Data.AsArray() ?? [], - MaxPriorityFeePerGas = transaction.MaxPriorityFeePerGas, - MaxFeePerGas = transaction.MaxFeePerGas, - GasPrice = transaction.GasPrice, - AccessList = transaction.AccessList is null - ? Array.Empty() - : AccessListItemForRpc.FromAccessList(transaction.AccessList), - ChainId = transaction.ChainId - ?? DefaultChainId - ?? BlockchainIds.Mainnet, + public new static readonly IRpcTransactionConverter Converter = new ConverterImpl(); - YParity = transaction.Signature?.RecoveryId ?? 0, - V = transaction.Signature?.RecoveryId ?? 0, - R = new UInt256(transaction.Signature?.R ?? [], true), - S = new UInt256(transaction.Signature?.S ?? [], true), - }; + private class ConverterImpl : IRpcTransactionConverter + { + public IRpcTransaction FromTransaction(Transaction transaction, TxReceipt receipt) => new RpcEIP1559Transaction(transaction); } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index ae8fc3bb583..3760c37e2d7 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Extensions; @@ -9,7 +8,7 @@ namespace Nethermind.Facade.Eth.RpcTransaction; -public sealed class RpcLegacyTransaction +public class RpcLegacyTransaction : IRpcTransaction { public TxType Type { get; set; } @@ -33,34 +32,26 @@ public sealed class RpcLegacyTransaction public UInt256 R { get; set; } public UInt256 S { get; set; } - private RpcLegacyTransaction() { } + public RpcLegacyTransaction(Transaction transaction) + { + Type = transaction.Type; + Nonce = transaction.Nonce; + To = transaction.To; + Gas = transaction.GasLimit; + Value = transaction.Value; + Input = transaction.Data.AsArray() ?? []; + GasPrice = transaction.GasPrice; + ChainId = transaction.ChainId; + + R = new UInt256(transaction.Signature?.R ?? [], true); + S = new UInt256(transaction.Signature?.S ?? [], true); + V = transaction.Signature?.V ?? 0; + } + + public static readonly IRpcTransactionConverter Converter = new ConverterImpl(); - public static RpcLegacyTransaction? FromTransaction(Transaction? transaction) + private class ConverterImpl : IRpcTransactionConverter { - if (transaction is null) - { - return null; - } - - if (transaction.Type != TxType.Legacy) - { - throw new ArgumentException("Transaction type must be Legacy"); - } - - return new RpcLegacyTransaction - { - Type = transaction.Type, - Nonce = transaction.Nonce, - To = transaction.To, - Gas = transaction.GasLimit, - Value = transaction.Value, - Input = transaction.Data.AsArray() ?? [], - GasPrice = transaction.GasPrice, - ChainId = transaction.ChainId, - - R = new UInt256(transaction.Signature?.R ?? [], true), - S = new UInt256(transaction.Signature?.S ?? [], true), - V = transaction.Signature?.V ?? 0, - }; + public IRpcTransaction FromTransaction(Transaction tx, TxReceipt receipt) => new RpcLegacyTransaction(tx); } } From 7580bc461729bb8b13746182d4d3720def1e3378 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Tue, 10 Sep 2024 16:17:55 -0300 Subject: [PATCH 15/76] Merge `RPCXTransactionTests` into a single test - Expose test values and `ValidateSchema` --- .../RpcAccessListTransactionTests.cs | 22 +------ .../RpcTransaction/RpcBlobTransactionTests.cs | 25 +------- .../RpcEIP1559TransactionTests.cs | 21 +------ .../RpcLegacyTransactionTests.cs | 16 +---- .../RpcTransaction/RpcTransactionTests.cs | 62 +++++++++++++++++++ 5 files changed, 70 insertions(+), 76 deletions(-) create mode 100644 src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs index 749dbd60cfd..9d65faf1b9a 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs @@ -7,19 +7,13 @@ using Nethermind.Core; using Nethermind.Core.Eip2930; using Nethermind.Core.Test.Builders; -using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Int256; -using Nethermind.Serialization.Json; -using NUnit.Framework; namespace Nethermind.JsonRpc.Test.Modules.RpcTransaction; -public class RpcAccessListTransactionTests +public static class RpcAccessListTransactionTests { - private readonly EthereumJsonSerializer _serializer = new(); - private static TransactionBuilder Build => Core.Test.Builders.Build.A.Transaction.WithType(TxType.AccessList); - public static readonly Transaction[] Transactions = [ Build.TestObject, @@ -78,20 +72,8 @@ public class RpcAccessListTransactionTests Build.WithSignature(TestItem.RandomSignatureB).TestObject, ]; - [SetUp] - public void SetUp() - { - RpcAccessListTransaction.DefaultChainId = BlockchainIds.Mainnet; - } - - [TestCaseSource(nameof(Transactions))] - public void Always_satisfies_schema(Transaction transaction) + public static void ValidateSchema(JsonElement json) { - var rpcTx = RpcAccessListTransaction.FromTransaction(transaction); - string serialized = _serializer.Serialize(rpcTx); - using var jsonDocument = JsonDocument.Parse(serialized); - var json = jsonDocument.RootElement; - json.GetProperty("type").GetString().Should().MatchRegex("^0x1$"); json.GetProperty("nonce").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); json.GetProperty("to").GetString()?.Should().MatchRegex("^0x[0-9a-fA-F]{40}$"); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcBlobTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcBlobTransactionTests.cs index 85d6603da80..831ef882dca 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcBlobTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcBlobTransactionTests.cs @@ -7,19 +7,13 @@ using Nethermind.Core; using Nethermind.Core.Eip2930; using Nethermind.Core.Test.Builders; -using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Int256; -using Nethermind.Serialization.Json; -using NUnit.Framework; namespace Nethermind.JsonRpc.Test.Modules.RpcTransaction; -public class RpcBlobTransactionTests +public static class RpcBlobTransactionTests { - private readonly EthereumJsonSerializer _serializer = new(); - private static TransactionBuilder Build => Core.Test.Builders.Build.A.Transaction.WithType(TxType.Blob); - public static readonly Transaction[] Transactions = [ Build.TestObject, @@ -76,7 +70,6 @@ public class RpcBlobTransactionTests .AddStorage(3) .Build()).TestObject, - // TODO: We want a similar API to `AccessList.Builder` Build.WithBlobVersionedHashes(0).TestObject, Build.WithBlobVersionedHashes(1).TestObject, Build.WithBlobVersionedHashes(50).TestObject, @@ -91,20 +84,8 @@ public class RpcBlobTransactionTests Build.WithSignature(TestItem.RandomSignatureB).TestObject, ]; - [SetUp] - public void SetUp() - { - RpcBlobTransaction.DefaultChainId = BlockchainIds.Mainnet; - } - - [TestCaseSource(nameof(Transactions))] - public void Always_satisfies_schema(Transaction transaction) + public static void ValidateSchema(JsonElement json) { - var rpcTx = RpcBlobTransaction.FromTransaction(transaction); - string serialized = _serializer.Serialize(rpcTx); - using var jsonDocument = JsonDocument.Parse(serialized); - var json = jsonDocument.RootElement; - // NOTE: This does not look correct since we are not checking for the exact value (`0x2`). // Essentially we're accepting all hex values `0x00 ... 0xFF`, including `0x` json.GetProperty("type").GetString().Should().MatchRegex("^0x([0-9a-fA-F]?){1,2}$"); @@ -117,8 +98,6 @@ public void Always_satisfies_schema(Transaction transaction) json.GetProperty("maxPriorityFeePerGas").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); json.GetProperty("maxFeePerGas").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); json.GetProperty("maxFeePerBlobGas").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); - // Suprising inconsistency in `FluentAssertions` where `AllSatisfy` fails on empty collections. - // See: https://github.com/fluentassertions/fluentassertions/discussions/2143#discussioncomment-9677309 var accessList = json.GetProperty("accessList").EnumerateArray(); if (accessList.Any()) { diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcEIP1559TransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcEIP1559TransactionTests.cs index 8054f9f2bb8..7112976a634 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcEIP1559TransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcEIP1559TransactionTests.cs @@ -7,19 +7,13 @@ using Nethermind.Core; using Nethermind.Core.Eip2930; using Nethermind.Core.Test.Builders; -using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Int256; -using Nethermind.Serialization.Json; -using NUnit.Framework; namespace Nethermind.JsonRpc.Test.Modules.RpcTransaction; -public class RpcEIP1559TransactionTests +public static class RpcEIP1559TransactionTests { - private readonly EthereumJsonSerializer _serializer = new(); - private static TransactionBuilder Build => Core.Test.Builders.Build.A.Transaction.WithType(TxType.EIP1559); - public static readonly Transaction[] Transactions = [ Build.TestObject, @@ -86,20 +80,9 @@ public class RpcEIP1559TransactionTests Build.WithSignature(TestItem.RandomSignatureB).TestObject, ]; - [SetUp] - public void SetUp() - { - RpcEIP1559Transaction.DefaultChainId = BlockchainIds.Mainnet; - } - [TestCaseSource(nameof(Transactions))] - public void Always_satisfies_schema(Transaction transaction) + public static void ValidateSchema(JsonElement json) { - var rpcTx = RpcEIP1559Transaction.FromTransaction(transaction); - string serialized = _serializer.Serialize(rpcTx); - using var jsonDocument = JsonDocument.Parse(serialized); - var json = jsonDocument.RootElement; - json.GetProperty("type").GetString().Should().MatchRegex("^0x2$"); json.GetProperty("nonce").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); json.GetProperty("to").GetString()?.Should().MatchRegex("^0x[0-9a-fA-F]{40}$"); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs index 7427a35a0b6..6179801bec3 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs @@ -5,19 +5,13 @@ using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Test.Builders; -using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Int256; -using Nethermind.Serialization.Json; -using NUnit.Framework; namespace Nethermind.JsonRpc.Test.Modules.RpcTransaction; -public class RpcLegacyTransactionTests +public static class RpcLegacyTransactionTests { - private readonly EthereumJsonSerializer _serializer = new(); - private static TransactionBuilder BuildALegacyTransaction => Build.A.Transaction.WithType(TxType.Legacy); - public static readonly Transaction[] Transactions = [ BuildALegacyTransaction.TestObject, @@ -61,14 +55,8 @@ public class RpcLegacyTransactionTests BuildALegacyTransaction.WithSignature(TestItem.RandomSignatureB).TestObject, ]; - [TestCaseSource(nameof(Transactions))] - public void Always_satisfies_schema(Transaction transaction) + public static void ValidateSchema(JsonElement json) { - var rpcTx = RpcLegacyTransaction.FromTransaction(transaction); - string serialized = _serializer.Serialize(rpcTx); - using var jsonDocument = JsonDocument.Parse(serialized); - var json = jsonDocument.RootElement; - json.GetProperty("type").GetString().Should().MatchRegex("^0x0$"); json.GetProperty("nonce").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); json.GetProperty("to").GetString()?.Should().MatchRegex("^0x[0-9a-fA-F]{40}$"); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs new file mode 100644 index 00000000000..b3ff787888c --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Text.Json; +using Nethermind.Core; +using Nethermind.Facade.Eth.RpcTransaction; +using Nethermind.Serialization.Json; +using NUnit.Framework; + +namespace Nethermind.JsonRpc.Test.Modules.RpcTransaction; + +public class PlaygroundTests +{ + private readonly IJsonSerializer _serializer = new EthereumJsonSerializer([IRpcTransaction.JsonConverter]); + + private readonly IRpcTransactionConverter _converter = new ComposeTransactionConverter() + .RegisterConverter(TxType.Legacy, RpcLegacyTransaction.Converter) + .RegisterConverter(TxType.AccessList, RpcAccessListTransaction.Converter) + .RegisterConverter(TxType.EIP1559, RpcEIP1559Transaction.Converter) + .RegisterConverter(TxType.Blob, RpcBlobTransaction.Converter); + + public static readonly Transaction[] Transactions = [ + .. RpcLegacyTransactionTests.Transactions, + .. RpcAccessListTransactionTests.Transactions, + .. RpcEIP1559TransactionTests.Transactions, + .. RpcBlobTransactionTests.Transactions, + ]; + + [SetUp] + public void SetUp() + { + RpcAccessListTransaction.DefaultChainId = BlockchainIds.Mainnet; + } + + [TestCaseSource(nameof(Transactions))] + public void Always_satisfies_schema(Transaction transaction) + { + IRpcTransaction rpcTransaction = _converter.FromTransaction(transaction, new TxReceipt()); + string serialized = _serializer.Serialize(rpcTransaction); + using var jsonDocument = JsonDocument.Parse(serialized); + JsonElement json = jsonDocument.RootElement; + + switch (transaction.Type) + { + case TxType.Legacy: + RpcLegacyTransactionTests.ValidateSchema(json); + break; + case TxType.AccessList: + RpcAccessListTransactionTests.ValidateSchema(json); + break; + case TxType.EIP1559: + RpcEIP1559TransactionTests.ValidateSchema(json); + break; + case TxType.Blob: + RpcBlobTransactionTests.ValidateSchema(json); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } +} From b073e50f59f7cb9ab8ec4dcfeec6d0e6ae030be0 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 11 Sep 2024 10:59:21 -0300 Subject: [PATCH 16/76] Rename test class - Forgot to change the name --- .../Modules/RpcTransaction/RpcTransactionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index b3ff787888c..17a252b96b6 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -10,7 +10,7 @@ namespace Nethermind.JsonRpc.Test.Modules.RpcTransaction; -public class PlaygroundTests +public class RpcTransactionTests { private readonly IJsonSerializer _serializer = new EthereumJsonSerializer([IRpcTransaction.JsonConverter]); From a13fcc0ff556e998eb0355d90708dde8a9d5834b Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 11 Sep 2024 13:37:35 -0300 Subject: [PATCH 17/76] Initial `RpcOptimismTransaction` - Follows https://github.com/ethereum-optimism/op-geth fields and tests --- .../Rpc/RpcOptimismTransactionTests.cs | 70 +++++++++++++++++++ .../Rpc/RpcOptimismTransaction.cs | 62 ++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs create mode 100644 src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs diff --git a/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs b/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs new file mode 100644 index 00000000000..1f95612c523 --- /dev/null +++ b/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json; +using NUnit.Framework; +using Nethermind.Serialization.Json; +using Nethermind.Optimism.Rpc; +using Nethermind.Facade.Eth.RpcTransaction; +using Nethermind.Core; +using FluentAssertions; +using Nethermind.Core.Test.Builders; +using Nethermind.Core.Crypto; + +namespace Nethermind.Optimism.Test.Rpc; + +public class RpcOptimismTransactionTests +{ + private readonly IJsonSerializer _serializer = new EthereumJsonSerializer([IRpcTransaction.JsonConverter]); + + private readonly IRpcTransactionConverter _converter = new ComposeTransactionConverter() + .RegisterConverter(TxType.DepositTx, RpcOptimismTransaction.Converter); + + private static TransactionBuilder Build => Core.Test.Builders.Build.A.Transaction.WithType(TxType.DepositTx); + public static readonly Transaction[] Transactions = [ + Build.TestObject, + Build + .With(s => s.IsOPSystemTransaction = true) + .With(s => s.Mint = 1234) + .TestObject, + Build + .WithGasLimit(0x1234) + .WithValue(0x1) + .WithData([0x61, 0x62, 0x63, 0x64, 0x65, 0x66]) + .WithSourceHash(Hash256.Zero) + .WithSenderAddress(Address.FromNumber(1)) + .WithHash(new Hash256("0xa4341f3db4363b7ca269a8538bd027b2f8784f84454ca917668642d5f6dffdf9")) + .TestObject + ]; + + [TestCaseSource(nameof(Transactions))] + public void Always_satisfies_schema(Transaction transaction) + { + IRpcTransaction rpcTransaction = _converter.FromTransaction(transaction, new OptimismTxReceipt()); + string serialized = _serializer.Serialize(rpcTransaction); + using var jsonDocument = JsonDocument.Parse(serialized); + JsonElement json = jsonDocument.RootElement; + ValidateSchema(json); + } + + private static void ValidateSchema(JsonElement json) + { + json.GetProperty("type").GetString().Should().MatchRegex("^0x7[eE]$"); + json.GetProperty("sourceHash").GetString().Should().MatchRegex("^0x[0-9a-fA-F]{64}$"); + json.GetProperty("from").GetString().Should().MatchRegex("^0x[0-9a-fA-F]{40}$"); + json.GetProperty("to").GetString()?.Should().MatchRegex("^0x[0-9a-fA-F]{40}$"); + var hasMint = json.TryGetProperty("mint", out var mint); + if (hasMint) + { + mint.GetString()?.Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + } + json.GetProperty("value").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("gas").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + var hasIsSystemTx = json.TryGetProperty("isSystemTx", out var isSystemTx); + if (hasIsSystemTx) + { + isSystemTx.GetBoolean(); + } + json.GetProperty("input").GetString().Should().MatchRegex("^0x[0-9a-f]*$"); + } +} diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs new file mode 100644 index 00000000000..29399e66e11 --- /dev/null +++ b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; +using Nethermind.Core; +using Nethermind.Int256; +using System.Text.Json.Serialization; +using Nethermind.Facade.Eth.RpcTransaction; + +namespace Nethermind.Optimism.Rpc; + +public class RpcOptimismTransaction : IRpcTransaction +{ + public TxType Type { get; set; } + + public Hash256 SourceHash { get; set; } + + public Address From { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public Address? To { get; set; } + + public UInt256? Mint { get; set; } + + public UInt256 Value { get; set; } + + public long Gas { get; set; } + + public bool? IsSystemTx { get; set; } + + public byte[] Input { get; set; } + + #region Nethermind specific fields + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public UInt256? DepositReceiptVersion { get; set; } + #endregion + + public RpcOptimismTransaction(Transaction transaction, OptimismTxReceipt? receipt) + { + Type = transaction.Type; + SourceHash = transaction.SourceHash ?? Hash256.Zero; + From = transaction.SenderAddress ?? Address.SystemUser; + To = transaction.To; + Mint = transaction.Mint; + Value = transaction.Value; + Gas = transaction.GasLimit; + IsSystemTx = transaction.IsOPSystemTransaction ? true : null; + Input = transaction.Data?.ToArray() ?? []; + + DepositReceiptVersion = receipt?.DepositReceiptVersion; + // NOTE: According to https://github.com/ethereum-optimism/op-geth/blob/8af19cf20261c0b62f98cc27da3a268f542822ee/core/types/deposit_tx.go#L79 `nonce == 0` + // Nonce = receipt?.DepositNonce; + } + + public static readonly IRpcTransactionConverter Converter = new ConverterImpl(); + + private class ConverterImpl : IRpcTransactionConverter + { + // TODO: Is there a design where we avoid casting? + public IRpcTransaction FromTransaction(Transaction tx, TxReceipt receipt) => new RpcOptimismTransaction(tx, receipt as OptimismTxReceipt); + } +} From 48326d57967dbb430369b9be364e7c52ab1a663c Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 11 Sep 2024 16:47:43 -0300 Subject: [PATCH 18/76] Add `WithSourceHash` to builder --- .../Nethermind.Core.Test/Builders/TransactionBuilder.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs index bb11b4f55e3..1a921445652 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs @@ -292,5 +292,11 @@ public TransactionBuilder WithIsServiceTransaction(bool isServiceTransaction) TestObjectInternal.IsServiceTransaction = isServiceTransaction; return this; } + + public TransactionBuilder WithSourceHash(Hash256? sourceHash) + { + TestObjectInternal.SourceHash = sourceHash; + return this; + } } } From 16765bebb147979d44b4ba3b3b2c90a2a22d5438 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 11 Sep 2024 16:48:21 -0300 Subject: [PATCH 19/76] Initial `RpcAccessList` - Replaces old `AccessListItemForRpc` with proper container --- .../Eth/RpcTransaction/RpcAccessList.cs | 74 +++++++++++++++++++ .../RpcAccessListTransaction.cs | 8 +- .../RpcTransaction/RpcTransactionTests.cs | 8 +- 3 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessList.cs diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessList.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessList.cs new file mode 100644 index 00000000000..75cf8ed2c47 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessList.cs @@ -0,0 +1,74 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Linq; +using Nethermind.Core; +using Nethermind.Core.Eip2930; +using Nethermind.Int256; +using Nethermind.Serialization.Json; +using System.Text.Json.Serialization; +using System.Text.Json; +using System; + +namespace Nethermind.Facade.Eth.RpcTransaction; + +public record RpcAccessList +{ + private readonly List _items; + + private RpcAccessList(List items) + { + _items = items; + } + + private class Item + { + public Address Address { get; set; } + + [JsonConverter(typeof(StorageCellIndexConverter))] + public IEnumerable StorageKeys { get; set; } + + public Item(Address address, List storageKeys) + { + Address = address; + StorageKeys = storageKeys; + } + } + + public static RpcAccessList FromAccessList(AccessList? accessList) => + accessList is null + ? new RpcAccessList([]) + : new RpcAccessList(accessList.Select(item => new Item(item.Address, [.. item.StorageKeys])).ToList()); + + public AccessList ToAccessList() + { + AccessList.Builder builder = new(); + foreach (Item item in _items) + { + builder.AddAddress(item.Address); + foreach (UInt256 index in item.StorageKeys) + { + builder.AddStorage(index); + } + } + + return builder.Build(); + } + + public static readonly JsonConverter JsonConverter = new JsonConverterImpl(); + + public class JsonConverterImpl : JsonConverter + { + public override RpcAccessList? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + List list = JsonSerializer.Deserialize>(ref reader, options); + return list is null ? null : new RpcAccessList(list); + } + + public override void Write(Utf8JsonWriter writer, RpcAccessList value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value._items, options); + } + } +} diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs index ad518e7405f..9cc2c9a921e 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Generic; using Nethermind.Core; using Nethermind.Int256; @@ -14,7 +12,7 @@ public class RpcAccessListTransaction : RpcLegacyTransaction // See: https://github.com/NethermindEth/nethermind/pull/6061#discussion_r1321634914 public static UInt256? DefaultChainId { get; set; } - public IEnumerable AccessList { get; set; } + public RpcAccessList AccessList { get; set; } public new UInt256 ChainId { get; set; } @@ -28,9 +26,7 @@ public class RpcAccessListTransaction : RpcLegacyTransaction public RpcAccessListTransaction(Transaction transaction) : base(transaction) { - AccessList = transaction.AccessList is null - ? Array.Empty() - : AccessListItemForRpc.FromAccessList(transaction.AccessList); + AccessList = RpcAccessList.FromAccessList(transaction.AccessList); ChainId = transaction.ChainId ?? DefaultChainId ?? BlockchainIds.Mainnet; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index 17a252b96b6..244af087113 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -12,7 +12,10 @@ namespace Nethermind.JsonRpc.Test.Modules.RpcTransaction; public class RpcTransactionTests { - private readonly IJsonSerializer _serializer = new EthereumJsonSerializer([IRpcTransaction.JsonConverter]); + private readonly IJsonSerializer _serializer = new EthereumJsonSerializer([ + IRpcTransaction.JsonConverter, + RpcAccessList.JsonConverter + ]); private readonly IRpcTransactionConverter _converter = new ComposeTransactionConverter() .RegisterConverter(TxType.Legacy, RpcLegacyTransaction.Converter) @@ -20,7 +23,8 @@ public class RpcTransactionTests .RegisterConverter(TxType.EIP1559, RpcEIP1559Transaction.Converter) .RegisterConverter(TxType.Blob, RpcBlobTransaction.Converter); - public static readonly Transaction[] Transactions = [ + public static readonly Transaction[] Transactions = + [ .. RpcLegacyTransactionTests.Transactions, .. RpcAccessListTransactionTests.Transactions, .. RpcEIP1559TransactionTests.Transactions, From f673490e7b598f14a80850053136141a67350680 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 11 Sep 2024 16:49:41 -0300 Subject: [PATCH 20/76] Annotate converter instead of registering --- .../Nethermind.Facade/Eth/RpcTransaction/RpcAccessList.cs | 1 + .../Modules/RpcTransaction/RpcTransactionTests.cs | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessList.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessList.cs index 75cf8ed2c47..4d849c9bec9 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessList.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessList.cs @@ -13,6 +13,7 @@ namespace Nethermind.Facade.Eth.RpcTransaction; +[JsonConverter(typeof(JsonConverterImpl))] public record RpcAccessList { private readonly List _items; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index 244af087113..a9310499d68 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -12,10 +12,7 @@ namespace Nethermind.JsonRpc.Test.Modules.RpcTransaction; public class RpcTransactionTests { - private readonly IJsonSerializer _serializer = new EthereumJsonSerializer([ - IRpcTransaction.JsonConverter, - RpcAccessList.JsonConverter - ]); + private readonly IJsonSerializer _serializer = new EthereumJsonSerializer([IRpcTransaction.JsonConverter]); private readonly IRpcTransactionConverter _converter = new ComposeTransactionConverter() .RegisterConverter(TxType.Legacy, RpcLegacyTransaction.Converter) From cdd4f23bd3a9d94004065d9e0fa16d60e6ab267f Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Wed, 11 Sep 2024 16:51:59 -0300 Subject: [PATCH 21/76] Use annotation for `IRpcTransaction` --- .../Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs | 5 +++-- .../Modules/RpcTransaction/RpcTransactionTests.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs index ee719abbe2d..b6460c01798 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs @@ -7,11 +7,12 @@ namespace Nethermind.Facade.Eth.RpcTransaction; +[JsonConverter(typeof(JsonConverterImpl))] public interface IRpcTransaction { - public static readonly JsonConverter JsonConverter = new ConverterImpl(); + public static readonly JsonConverter JsonConverter = new JsonConverterImpl(); - private class ConverterImpl : JsonConverter + private class JsonConverterImpl : JsonConverter { public override IRpcTransaction? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotSupportedException(); public override void Write(Utf8JsonWriter writer, IRpcTransaction value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, value.GetType(), options); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index a9310499d68..eccac3f09d0 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -12,7 +12,7 @@ namespace Nethermind.JsonRpc.Test.Modules.RpcTransaction; public class RpcTransactionTests { - private readonly IJsonSerializer _serializer = new EthereumJsonSerializer([IRpcTransaction.JsonConverter]); + private readonly IJsonSerializer _serializer = new EthereumJsonSerializer(); private readonly IRpcTransactionConverter _converter = new ComposeTransactionConverter() .RegisterConverter(TxType.Legacy, RpcLegacyTransaction.Converter) From a9f6277a1bc2835ff1503b1f6da21c932192681a Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Thu, 12 Sep 2024 13:45:32 -0300 Subject: [PATCH 22/76] Initial `RpcGenericTransaction` - A lot of questions/unknowns/design decisions --- .../RpcTransaction/RpcGenericTransaction.cs | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs new file mode 100644 index 00000000000..b29cda8f8db --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json.Serialization; +using Nethermind.Core; +using Nethermind.Int256; + +namespace Nethermind.Facade.Eth.RpcTransaction; + +/// +/// Transaction object generic to all types. +/// See: https://github.com/ethereum/execution-apis/blob/1d4f70e84191bb574286fd7cea6c48795bf73e78/src/schemas/transaction.yaml#L358 +/// +/// +/// This class is intended to be used ONLY as input for RPC calls, not for return values. +/// +public record class RpcGenericTransaction +{ + // TODO: To be discussed: this class is esentially a bag of "whatever" that could be part of any Transaction type. + // Adding new Transaction types (ex. Optimism `Deposit`) will require modifying this class to include the new fields. + // + // Another option would be to ignore the spec and instead take the incoming JSON `Type` and deserialize + // into specific classes (ex. `RpcLegacyTransaction`, `RpcAccessListTransaction`, etc). + // This involves other complications, for example: The `Blobs` fields can be part of the incoming JSON, but + // it's not part of the spec of `Blob` transactions, that is `RpcBlobTransaction` does not have a `Blobs` field. + + public TxType? Type { get; set; } + + public UInt256? Nonce { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public Address? To { get; set; } + + public Address? From { get; set; } + + public long? Gas { get; set; } + + public UInt256? Value { get; set; } + + public byte[]? Input { get; set; } + + public UInt256? GasPrice { get; set; } + + public UInt256? MaxPriorityFeePerGas { get; set; } + + public UInt256? MaxFeePerGas { get; set; } + + public UInt256? MaxFeePerBlobGas { get; set; } + + public RpcAccessList? AccessList { get; set; } + + // TODO: Each item should be a 32 byte array + // Currently we don't enforce this (hashes can have any length) + // See `RpcBlobTransaction` also + public byte[][]? BlobVersionedHashes { get; set; } + + // TODO: Does it match with `ShardBlobNetworkWrapper.Blobs` + // If so, what about `Commitments` and `Proofs`? + public byte[]? Blobs { get; set; } + + public ulong? ChainId { get; set; } + + // TODO: Missing Optimism specific fields + + // TODO: We'll replace this method with a proper factory class. + // Such factory will have a list of converters indexed by `TxType`, + // and each converter exposes a method to convert from `RpcGenericTransaction` to specific `Transaction` subclasses. + // Ex. `LegacyTransaction : Transaction` implements `FromRpcGenericTransaction(RpcGenericTransaction rpcTx)`. + public Transaction ToTransaction() + { + // TODO: What does the spec say about missing fields? + // We're currently "defaulting" everything to `0` or `null`. + + return new Transaction() + { + Type = Type ?? TxType.Legacy, + Nonce = Nonce ?? UInt256.Zero, + To = To, + SenderAddress = From ?? Address.Zero, + GasLimit = Gas ?? 0, + Value = Value ?? 0, + Data = Input, + GasPrice = GasPrice ?? 0, + DecodedMaxFeePerGas = MaxFeePerGas ?? 0, + MaxFeePerBlobGas = MaxFeePerBlobGas, + AccessList = AccessList?.ToAccessList(), + BlobVersionedHashes = BlobVersionedHashes, + ChainId = ChainId ?? 0, + // TODO: What is the point of `MaxPriorityFeePerGas` if it's internally always the same as `GasPrice` ? + // MaxPriorityFeePerGas = MaxPriorityFeePerGas ?? 0, + // TODO: How do we map `Blobs`? + // Blobs = Blobs, + }; + } +} From 7db803b27d749ddf475ec48cb3bf4c104c0e51d0 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 13:21:08 -0300 Subject: [PATCH 23/76] Initial JSON deserialization code --- .../Eth/RpcTransaction/IRpcTransaction.cs | 35 +++++++++++++++---- .../Eth/RpcTransaction/RpcAccessList.cs | 6 ++++ .../RpcAccessListTransaction.cs | 4 +++ .../Eth/RpcTransaction/RpcBlobTransaction.cs | 4 +++ .../RpcTransaction/RpcEIP1559Transaction.cs | 4 +++ .../RpcTransaction/RpcLegacyTransaction.cs | 3 ++ .../RpcTransaction/RpcTransactionTests.cs | 18 +++++++++- .../Rpc/RpcOptimismTransactionTests.cs | 5 ++- 8 files changed, 71 insertions(+), 8 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs index b6460c01798..b7da9638862 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs @@ -4,17 +4,40 @@ using System; using System.Text.Json; using System.Text.Json.Serialization; +using Nethermind.Core; namespace Nethermind.Facade.Eth.RpcTransaction; -[JsonConverter(typeof(JsonConverterImpl))] public interface IRpcTransaction { - public static readonly JsonConverter JsonConverter = new JsonConverterImpl(); - - private class JsonConverterImpl : JsonConverter + public class JsonConverter : JsonConverter { - public override IRpcTransaction? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => throw new NotSupportedException(); - public override void Write(Utf8JsonWriter writer, IRpcTransaction value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, value.GetType(), options); + private readonly Type[] _transactionTypes = new Type[Transaction.MaxTxType + 1]; + + public JsonConverter RegisterTransactionType(TxType type, Type @class) + { + _transactionTypes[(byte)type] = @class; + return this; + } + + public override IRpcTransaction? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using var jsonDocument = JsonDocument.ParseValue(ref reader); + + TxType discriminator = default; + if (jsonDocument.RootElement.TryGetProperty("type", out JsonElement typeProperty)) + { + discriminator = (TxType?)typeProperty.Deserialize(typeof(TxType), options) ?? default; + } + + Type concreteTxType = _transactionTypes[(byte)discriminator]; + + return (IRpcTransaction?)jsonDocument.Deserialize(concreteTxType, options); + } + + public override void Write(Utf8JsonWriter writer, IRpcTransaction value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, value.GetType(), options); + } } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessList.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessList.cs index 4d849c9bec9..23db2dfe58e 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessList.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessList.cs @@ -18,6 +18,9 @@ public record RpcAccessList { private readonly List _items; + [JsonConstructor] + public RpcAccessList() { } + private RpcAccessList(List items) { _items = items; @@ -30,6 +33,9 @@ private class Item [JsonConverter(typeof(StorageCellIndexConverter))] public IEnumerable StorageKeys { get; set; } + [JsonConstructor] + public Item() { } + public Item(Address address, List storageKeys) { Address = address; diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs index 9cc2c9a921e..939b4f419b6 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Int256; @@ -24,6 +25,9 @@ public class RpcAccessListTransaction : RpcLegacyTransaction /// public new UInt256? V { get; set; } + [JsonConstructor] + public RpcAccessListTransaction() { } + public RpcAccessListTransaction(Transaction transaction) : base(transaction) { AccessList = RpcAccessList.FromAccessList(transaction.AccessList); diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs index b93a202ae66..e8ecf4d853b 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Int256; @@ -14,6 +15,9 @@ public class RpcBlobTransaction : RpcEIP1559Transaction // Currently we don't enforce this (hashes can have any length) public byte[][] BlobVersionedHashes { get; set; } + [JsonConstructor] + public RpcBlobTransaction() { } + public RpcBlobTransaction(Transaction transaction) : base(transaction) { MaxFeePerBlobGas = transaction.MaxFeePerBlobGas ?? 0; diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs index 36116d6d762..6ab6d39760f 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Int256; @@ -18,6 +19,9 @@ public class RpcEIP1559Transaction : RpcAccessListTransaction /// public new UInt256 GasPrice { get; set; } + [JsonConstructor] + public RpcEIP1559Transaction() { } + public RpcEIP1559Transaction(Transaction transaction) : base(transaction) { MaxPriorityFeePerGas = transaction.MaxPriorityFeePerGas; diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index 3760c37e2d7..c3e5be2a0f4 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -32,6 +32,9 @@ public class RpcLegacyTransaction : IRpcTransaction public UInt256 R { get; set; } public UInt256 S { get; set; } + [JsonConstructor] + public RpcLegacyTransaction() { } + public RpcLegacyTransaction(Transaction transaction) { Type = transaction.Type; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index eccac3f09d0..d429b80c79d 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -3,6 +3,7 @@ using System; using System.Text.Json; +using FluentAssertions; using Nethermind.Core; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Serialization.Json; @@ -12,7 +13,13 @@ namespace Nethermind.JsonRpc.Test.Modules.RpcTransaction; public class RpcTransactionTests { - private readonly IJsonSerializer _serializer = new EthereumJsonSerializer(); + private readonly IJsonSerializer _serializer = new EthereumJsonSerializer([ + new IRpcTransaction.JsonConverter() + .RegisterTransactionType(TxType.Legacy, typeof(RpcLegacyTransaction)) + .RegisterTransactionType(TxType.AccessList, typeof(RpcAccessListTransaction)) + .RegisterTransactionType(TxType.EIP1559, typeof(RpcEIP1559Transaction)) + .RegisterTransactionType(TxType.Blob, typeof(RpcBlobTransaction)) + ]); private readonly IRpcTransactionConverter _converter = new ComposeTransactionConverter() .RegisterConverter(TxType.Legacy, RpcLegacyTransaction.Converter) @@ -60,4 +67,13 @@ public void Always_satisfies_schema(Transaction transaction) throw new ArgumentOutOfRangeException(); } } + + [TestCaseSource(nameof(Transactions))] + public void RPC_JSON_Roundtrip_same_type(Transaction tx) { + IRpcTransaction rpcTx = _converter.FromTransaction(tx, new TxReceipt()); + string serialized = _serializer.Serialize(rpcTx); + IRpcTransaction deserialized = _serializer.Deserialize(serialized); + + rpcTx.GetType().Should().Be(deserialized.GetType()); + } } diff --git a/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs b/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs index 1f95612c523..4289f5f31e2 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs @@ -15,7 +15,10 @@ namespace Nethermind.Optimism.Test.Rpc; public class RpcOptimismTransactionTests { - private readonly IJsonSerializer _serializer = new EthereumJsonSerializer([IRpcTransaction.JsonConverter]); + private readonly IJsonSerializer _serializer = new EthereumJsonSerializer([ + new IRpcTransaction.JsonConverter() + .RegisterTransactionType(TxType.DepositTx, typeof(RpcOptimismTransaction)) + ]); private readonly IRpcTransactionConverter _converter = new ComposeTransactionConverter() .RegisterConverter(TxType.DepositTx, RpcOptimismTransaction.Converter); From 0fee0cf23cecad38c341c4e530ffa6f81f679cdb Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 13:37:56 -0300 Subject: [PATCH 24/76] Remove `TxReceipt` for now --- .../Eth/RpcTransaction/IRpcTransactionConverter.cs | 6 +++--- .../Eth/RpcTransaction/RpcAccessListTransaction.cs | 2 +- .../Eth/RpcTransaction/RpcBlobTransaction.cs | 2 +- .../Eth/RpcTransaction/RpcEIP1559Transaction.cs | 2 +- .../Eth/RpcTransaction/RpcLegacyTransaction.cs | 2 +- .../Modules/RpcTransaction/RpcTransactionTests.cs | 13 +++++++++++-- .../Rpc/RpcOptimismTransactionTests.cs | 2 +- .../Rpc/RpcOptimismTransaction.cs | 9 +++++---- 8 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransactionConverter.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransactionConverter.cs index e2300fd4823..941da08c857 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransactionConverter.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransactionConverter.cs @@ -8,7 +8,7 @@ namespace Nethermind.Facade.Eth.RpcTransaction; public interface IRpcTransactionConverter { - IRpcTransaction FromTransaction(Transaction tx, TxReceipt receipt); + IRpcTransaction FromTransaction(Transaction tx); } public class ComposeTransactionConverter : IRpcTransactionConverter @@ -21,9 +21,9 @@ public ComposeTransactionConverter RegisterConverter(TxType txType, IRpcTransact return this; } - public IRpcTransaction FromTransaction(Transaction tx, TxReceipt receipt) + public IRpcTransaction FromTransaction(Transaction tx) { var converter = _converters[(byte)tx.Type] ?? throw new ArgumentException("No converter for transaction type"); - return converter.FromTransaction(tx, receipt); + return converter.FromTransaction(tx); } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs index 939b4f419b6..7d60180a470 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs @@ -42,6 +42,6 @@ public RpcAccessListTransaction(Transaction transaction) : base(transaction) private class ConverterImpl : IRpcTransactionConverter { - public IRpcTransaction FromTransaction(Transaction transaction, TxReceipt receipt) => new RpcAccessListTransaction(transaction); + public IRpcTransaction FromTransaction(Transaction transaction) => new RpcAccessListTransaction(transaction); } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs index e8ecf4d853b..6ef7e2dfd97 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs @@ -28,6 +28,6 @@ public RpcBlobTransaction(Transaction transaction) : base(transaction) private class ConverterImpl : IRpcTransactionConverter { - public IRpcTransaction FromTransaction(Transaction transaction, TxReceipt receipt) => new RpcBlobTransaction(transaction); + public IRpcTransaction FromTransaction(Transaction transaction) => new RpcBlobTransaction(transaction); } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs index 6ab6d39760f..5337da3782a 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs @@ -33,6 +33,6 @@ public RpcEIP1559Transaction(Transaction transaction) : base(transaction) private class ConverterImpl : IRpcTransactionConverter { - public IRpcTransaction FromTransaction(Transaction transaction, TxReceipt receipt) => new RpcEIP1559Transaction(transaction); + public IRpcTransaction FromTransaction(Transaction transaction) => new RpcEIP1559Transaction(transaction); } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index c3e5be2a0f4..6528b6d1eef 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -55,6 +55,6 @@ public RpcLegacyTransaction(Transaction transaction) private class ConverterImpl : IRpcTransactionConverter { - public IRpcTransaction FromTransaction(Transaction tx, TxReceipt receipt) => new RpcLegacyTransaction(tx); + public IRpcTransaction FromTransaction(Transaction tx) => new RpcLegacyTransaction(tx); } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index d429b80c79d..da4d9d1e9d5 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -3,6 +3,7 @@ using System; using System.Text.Json; +using System.Text.Json.Serialization; using FluentAssertions; using Nethermind.Core; using Nethermind.Facade.Eth.RpcTransaction; @@ -44,7 +45,7 @@ public void SetUp() [TestCaseSource(nameof(Transactions))] public void Always_satisfies_schema(Transaction transaction) { - IRpcTransaction rpcTransaction = _converter.FromTransaction(transaction, new TxReceipt()); + IRpcTransaction rpcTransaction = _converter.FromTransaction(transaction); string serialized = _serializer.Serialize(rpcTransaction); using var jsonDocument = JsonDocument.Parse(serialized); JsonElement json = jsonDocument.RootElement; @@ -70,10 +71,18 @@ public void Always_satisfies_schema(Transaction transaction) [TestCaseSource(nameof(Transactions))] public void RPC_JSON_Roundtrip_same_type(Transaction tx) { - IRpcTransaction rpcTx = _converter.FromTransaction(tx, new TxReceipt()); + IRpcTransaction rpcTx = _converter.FromTransaction(tx); string serialized = _serializer.Serialize(rpcTx); IRpcTransaction deserialized = _serializer.Deserialize(serialized); rpcTx.GetType().Should().Be(deserialized.GetType()); } + + /* + JSON -> IRpcTransaction (`IRpcTransaction.JsonConverter`, with a registry of [TxType => C# Type]) + IRpcTransaction -> Transaction (IRpcTransaction has `.ToTransaction`) + Transaction -> IRpcTransaction (IRpcTransactionConverter.FromTransaction, with a registry of [TxType => IRpcTransactionConverter]) + IRpcTransaction -> JSON (derived by `System.Text.JSON`) + */ } + diff --git a/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs b/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs index 4289f5f31e2..f130490a020 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs @@ -43,7 +43,7 @@ public class RpcOptimismTransactionTests [TestCaseSource(nameof(Transactions))] public void Always_satisfies_schema(Transaction transaction) { - IRpcTransaction rpcTransaction = _converter.FromTransaction(transaction, new OptimismTxReceipt()); + IRpcTransaction rpcTransaction = _converter.FromTransaction(transaction); string serialized = _serializer.Serialize(rpcTransaction); using var jsonDocument = JsonDocument.Parse(serialized); JsonElement json = jsonDocument.RootElement; diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs index 29399e66e11..296acbcf550 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs @@ -35,7 +35,7 @@ public class RpcOptimismTransaction : IRpcTransaction public UInt256? DepositReceiptVersion { get; set; } #endregion - public RpcOptimismTransaction(Transaction transaction, OptimismTxReceipt? receipt) + public RpcOptimismTransaction(Transaction transaction) { Type = transaction.Type; SourceHash = transaction.SourceHash ?? Hash256.Zero; @@ -47,7 +47,9 @@ public RpcOptimismTransaction(Transaction transaction, OptimismTxReceipt? receip IsSystemTx = transaction.IsOPSystemTransaction ? true : null; Input = transaction.Data?.ToArray() ?? []; - DepositReceiptVersion = receipt?.DepositReceiptVersion; + // TODO: Handle extra data + // DepositReceiptVersion = receipt?.DepositReceiptVersion; + // NOTE: According to https://github.com/ethereum-optimism/op-geth/blob/8af19cf20261c0b62f98cc27da3a268f542822ee/core/types/deposit_tx.go#L79 `nonce == 0` // Nonce = receipt?.DepositNonce; } @@ -56,7 +58,6 @@ public RpcOptimismTransaction(Transaction transaction, OptimismTxReceipt? receip private class ConverterImpl : IRpcTransactionConverter { - // TODO: Is there a design where we avoid casting? - public IRpcTransaction FromTransaction(Transaction tx, TxReceipt receipt) => new RpcOptimismTransaction(tx, receipt as OptimismTxReceipt); + public IRpcTransaction FromTransaction(Transaction tx) => new RpcOptimismTransaction(tx); } } From 19686a15b45bbfcc396d446a6900f2f935b56283 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 13:48:19 -0300 Subject: [PATCH 25/76] Test for member equality in JSON (de)serialization --- .../RpcTransaction/RpcTransactionTests.cs | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index da4d9d1e9d5..888eb0b7813 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -3,7 +3,6 @@ using System; using System.Text.Json; -using System.Text.Json.Serialization; using FluentAssertions; using Nethermind.Core; using Nethermind.Facade.Eth.RpcTransaction; @@ -22,7 +21,7 @@ public class RpcTransactionTests .RegisterTransactionType(TxType.Blob, typeof(RpcBlobTransaction)) ]); - private readonly IRpcTransactionConverter _converter = new ComposeTransactionConverter() + private readonly IRpcTransactionConverter _rpcConverter = new ComposeTransactionConverter() .RegisterConverter(TxType.Legacy, RpcLegacyTransaction.Converter) .RegisterConverter(TxType.AccessList, RpcAccessListTransaction.Converter) .RegisterConverter(TxType.EIP1559, RpcEIP1559Transaction.Converter) @@ -45,7 +44,7 @@ public void SetUp() [TestCaseSource(nameof(Transactions))] public void Always_satisfies_schema(Transaction transaction) { - IRpcTransaction rpcTransaction = _converter.FromTransaction(transaction); + IRpcTransaction rpcTransaction = _rpcConverter.FromTransaction(transaction); string serialized = _serializer.Serialize(rpcTransaction); using var jsonDocument = JsonDocument.Parse(serialized); JsonElement json = jsonDocument.RootElement; @@ -69,20 +68,19 @@ public void Always_satisfies_schema(Transaction transaction) } } + + // * JSON -> IRpcTransaction (`IRpcTransaction.JsonConverter`, with a registry of [TxType => C# Type]) + // * IRpcTransaction -> Transaction (IRpcTransaction has `.ToTransaction`) + // * Transaction -> IRpcTransaction (IRpcTransactionConverter.FromTransaction, with a registry of [TxType => IRpcTransactionConverter]) + // * IRpcTransaction -> JSON (derived by `System.Text.JSON`) + [TestCaseSource(nameof(Transactions))] - public void RPC_JSON_Roundtrip_same_type(Transaction tx) { - IRpcTransaction rpcTx = _converter.FromTransaction(tx); + public void RpcTransaction_JSON_roundtrip(Transaction tx) + { + IRpcTransaction rpcTx = _rpcConverter.FromTransaction(tx); string serialized = _serializer.Serialize(rpcTx); IRpcTransaction deserialized = _serializer.Deserialize(serialized); - rpcTx.GetType().Should().Be(deserialized.GetType()); + rpcTx.Should().BeEquivalentTo(deserialized, options => options.RespectingRuntimeTypes()); } - - /* - JSON -> IRpcTransaction (`IRpcTransaction.JsonConverter`, with a registry of [TxType => C# Type]) - IRpcTransaction -> Transaction (IRpcTransaction has `.ToTransaction`) - Transaction -> IRpcTransaction (IRpcTransactionConverter.FromTransaction, with a registry of [TxType => IRpcTransactionConverter]) - IRpcTransaction -> JSON (derived by `System.Text.JSON`) - */ } - From 38f2a1f3e541d353959f421f21d0b33a3b29da27 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 14:06:37 -0300 Subject: [PATCH 26/76] Introduce `ITransactionConverter` - Mimics `JsonConverter` from `System.Text.Json` --- .../Eth/RpcTransaction/IRpcTransaction.cs | 17 +++++++++++++++++ .../Eth/RpcTransaction/ITransactionConverter.cs | 11 +++++++++++ .../RpcTransaction/RpcAccessListTransaction.cs | 6 +++--- .../Eth/RpcTransaction/RpcBlobTransaction.cs | 6 +++--- .../Eth/RpcTransaction/RpcEIP1559Transaction.cs | 6 +++--- .../Eth/RpcTransaction/RpcLegacyTransaction.cs | 6 +++--- .../RpcTransaction/RpcTransactionTests.cs | 6 +++--- .../Rpc/RpcOptimismTransactionTests.cs | 2 +- .../Rpc/RpcOptimismTransaction.cs | 8 ++++---- 9 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/ITransactionConverter.cs diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs index b7da9638862..fc6e0f02992 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs @@ -40,4 +40,21 @@ public override void Write(Utf8JsonWriter writer, IRpcTransaction value, JsonSer JsonSerializer.Serialize(writer, value, value.GetType(), options); } } + + public class TransactionConverter : ITransactionConverter + { + private readonly ITransactionConverter?[] _converters = new ITransactionConverter?[Transaction.MaxTxType + 1]; + + public TransactionConverter RegisterConverter(TxType txType, ITransactionConverter converter) + { + _converters[(byte)txType] = converter; + return this; + } + + public IRpcTransaction FromTransaction(Transaction tx) + { + var converter = _converters[(byte)tx.Type] ?? throw new ArgumentException("No converter for transaction type"); + return converter.FromTransaction(tx); + } + } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/ITransactionConverter.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/ITransactionConverter.cs new file mode 100644 index 00000000000..411a59fb9e6 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/ITransactionConverter.cs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Facade.Eth.RpcTransaction; + +public interface ITransactionConverter +{ + T FromTransaction(Transaction tx); +} diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs index 7d60180a470..d96d533c998 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs @@ -38,10 +38,10 @@ public RpcAccessListTransaction(Transaction transaction) : base(transaction) V = transaction.Signature?.RecoveryId; } - public new static readonly IRpcTransactionConverter Converter = new ConverterImpl(); + public new static readonly ITransactionConverter Converter = new ConverterImpl(); - private class ConverterImpl : IRpcTransactionConverter + private class ConverterImpl : ITransactionConverter { - public IRpcTransaction FromTransaction(Transaction transaction) => new RpcAccessListTransaction(transaction); + public RpcAccessListTransaction FromTransaction(Transaction transaction) => new(transaction); } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs index 6ef7e2dfd97..b31c5d884f4 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs @@ -24,10 +24,10 @@ public RpcBlobTransaction(Transaction transaction) : base(transaction) BlobVersionedHashes = transaction.BlobVersionedHashes ?? []; } - public new static readonly IRpcTransactionConverter Converter = new ConverterImpl(); + public new static readonly ITransactionConverter Converter = new ConverterImpl(); - private class ConverterImpl : IRpcTransactionConverter + private class ConverterImpl : ITransactionConverter { - public IRpcTransaction FromTransaction(Transaction transaction) => new RpcBlobTransaction(transaction); + public RpcBlobTransaction FromTransaction(Transaction transaction) => new(transaction); } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs index 5337da3782a..0b0e55723fb 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs @@ -29,10 +29,10 @@ public RpcEIP1559Transaction(Transaction transaction) : base(transaction) GasPrice = transaction.GasPrice; } - public new static readonly IRpcTransactionConverter Converter = new ConverterImpl(); + public new static readonly ITransactionConverter Converter = new ConverterImpl(); - private class ConverterImpl : IRpcTransactionConverter + private class ConverterImpl : ITransactionConverter { - public IRpcTransaction FromTransaction(Transaction transaction) => new RpcEIP1559Transaction(transaction); + public RpcEIP1559Transaction FromTransaction(Transaction transaction) => new(transaction); } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index 6528b6d1eef..e8724bc2fcc 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -51,10 +51,10 @@ public RpcLegacyTransaction(Transaction transaction) V = transaction.Signature?.V ?? 0; } - public static readonly IRpcTransactionConverter Converter = new ConverterImpl(); + public static readonly ITransactionConverter Converter = new ConverterImpl(); - private class ConverterImpl : IRpcTransactionConverter + private class ConverterImpl : ITransactionConverter { - public IRpcTransaction FromTransaction(Transaction tx) => new RpcLegacyTransaction(tx); + public RpcLegacyTransaction FromTransaction(Transaction tx) => new(tx); } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index 888eb0b7813..ac5e5441643 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -21,7 +21,7 @@ public class RpcTransactionTests .RegisterTransactionType(TxType.Blob, typeof(RpcBlobTransaction)) ]); - private readonly IRpcTransactionConverter _rpcConverter = new ComposeTransactionConverter() + private readonly ITransactionConverter _converter = new IRpcTransaction.TransactionConverter() .RegisterConverter(TxType.Legacy, RpcLegacyTransaction.Converter) .RegisterConverter(TxType.AccessList, RpcAccessListTransaction.Converter) .RegisterConverter(TxType.EIP1559, RpcEIP1559Transaction.Converter) @@ -44,7 +44,7 @@ public void SetUp() [TestCaseSource(nameof(Transactions))] public void Always_satisfies_schema(Transaction transaction) { - IRpcTransaction rpcTransaction = _rpcConverter.FromTransaction(transaction); + IRpcTransaction rpcTransaction = _converter.FromTransaction(transaction); string serialized = _serializer.Serialize(rpcTransaction); using var jsonDocument = JsonDocument.Parse(serialized); JsonElement json = jsonDocument.RootElement; @@ -77,7 +77,7 @@ public void Always_satisfies_schema(Transaction transaction) [TestCaseSource(nameof(Transactions))] public void RpcTransaction_JSON_roundtrip(Transaction tx) { - IRpcTransaction rpcTx = _rpcConverter.FromTransaction(tx); + IRpcTransaction rpcTx = _converter.FromTransaction(tx); string serialized = _serializer.Serialize(rpcTx); IRpcTransaction deserialized = _serializer.Deserialize(serialized); diff --git a/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs b/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs index f130490a020..205b09a2ecc 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs @@ -20,7 +20,7 @@ public class RpcOptimismTransactionTests .RegisterTransactionType(TxType.DepositTx, typeof(RpcOptimismTransaction)) ]); - private readonly IRpcTransactionConverter _converter = new ComposeTransactionConverter() + private readonly ITransactionConverter _converter = new IRpcTransaction.TransactionConverter() .RegisterConverter(TxType.DepositTx, RpcOptimismTransaction.Converter); private static TransactionBuilder Build => Core.Test.Builders.Build.A.Transaction.WithType(TxType.DepositTx); diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs index 296acbcf550..f19e1bdaac3 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs @@ -49,15 +49,15 @@ public RpcOptimismTransaction(Transaction transaction) // TODO: Handle extra data // DepositReceiptVersion = receipt?.DepositReceiptVersion; - + // NOTE: According to https://github.com/ethereum-optimism/op-geth/blob/8af19cf20261c0b62f98cc27da3a268f542822ee/core/types/deposit_tx.go#L79 `nonce == 0` // Nonce = receipt?.DepositNonce; } - public static readonly IRpcTransactionConverter Converter = new ConverterImpl(); + public static readonly ITransactionConverter Converter = new ConverterImpl(); - private class ConverterImpl : IRpcTransactionConverter + private class ConverterImpl : ITransactionConverter { - public IRpcTransaction FromTransaction(Transaction tx) => new RpcOptimismTransaction(tx); + public RpcOptimismTransaction FromTransaction(Transaction transaction) => new(transaction); } } From 4e040bfeebc8351d405b1e0072533a53f99f38f3 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 14:07:54 -0300 Subject: [PATCH 27/76] Add `TODO` --- .../Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs index fc6e0f02992..585346033aa 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs @@ -10,6 +10,8 @@ namespace Nethermind.Facade.Eth.RpcTransaction; public interface IRpcTransaction { + // TODO: Should/can we merge `JsonConverter` and `ITransactionConverter`? + public class JsonConverter : JsonConverter { private readonly Type[] _transactionTypes = new Type[Transaction.MaxTxType + 1]; From 07690bec5fce609651e039f96dae6f99a7777ab2 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 14:09:55 -0300 Subject: [PATCH 28/76] Update inline docs --- .../Modules/RpcTransaction/RpcTransactionTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index ac5e5441643..790f19c2990 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -70,8 +70,8 @@ public void Always_satisfies_schema(Transaction transaction) // * JSON -> IRpcTransaction (`IRpcTransaction.JsonConverter`, with a registry of [TxType => C# Type]) - // * IRpcTransaction -> Transaction (IRpcTransaction has `.ToTransaction`) - // * Transaction -> IRpcTransaction (IRpcTransactionConverter.FromTransaction, with a registry of [TxType => IRpcTransactionConverter]) + // * TODO: IRpcTransaction -> Transaction (IRpcTransaction has `.ToTransaction`) + // * Transaction -> IRpcTransaction (IRpcTransaction.TransactionConverter with a registry of [TxType => ITransactionConverter]) // * IRpcTransaction -> JSON (derived by `System.Text.JSON`) [TestCaseSource(nameof(Transactions))] From 92394c1127ccc9503a71e4e9393b60ebd332d8a1 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 14:18:03 -0300 Subject: [PATCH 29/76] Whitespace --- .../Modules/RpcTransaction/RpcTransactionTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index 790f19c2990..b6708b71e0f 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -68,7 +68,6 @@ public void Always_satisfies_schema(Transaction transaction) } } - // * JSON -> IRpcTransaction (`IRpcTransaction.JsonConverter`, with a registry of [TxType => C# Type]) // * TODO: IRpcTransaction -> Transaction (IRpcTransaction has `.ToTransaction`) // * Transaction -> IRpcTransaction (IRpcTransaction.TransactionConverter with a registry of [TxType => ITransactionConverter]) From b7c42cf545c82b0544880fffa5309c800bdf9fc8 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 14:18:12 -0300 Subject: [PATCH 30/76] Delete unused classes --- .../IRpcTransactionConverter.cs | 29 ------ .../RpcTransaction/RpcGenericTransaction.cs | 95 ------------------- 2 files changed, 124 deletions(-) delete mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransactionConverter.cs delete mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransactionConverter.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransactionConverter.cs deleted file mode 100644 index 941da08c857..00000000000 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransactionConverter.cs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Core; - -namespace Nethermind.Facade.Eth.RpcTransaction; - -public interface IRpcTransactionConverter -{ - IRpcTransaction FromTransaction(Transaction tx); -} - -public class ComposeTransactionConverter : IRpcTransactionConverter -{ - private readonly IRpcTransactionConverter?[] _converters = new IRpcTransactionConverter?[Transaction.MaxTxType + 1]; - - public ComposeTransactionConverter RegisterConverter(TxType txType, IRpcTransactionConverter converter) - { - _converters[(byte)txType] = converter; - return this; - } - - public IRpcTransaction FromTransaction(Transaction tx) - { - var converter = _converters[(byte)tx.Type] ?? throw new ArgumentException("No converter for transaction type"); - return converter.FromTransaction(tx); - } -} diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs deleted file mode 100644 index b29cda8f8db..00000000000 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Text.Json.Serialization; -using Nethermind.Core; -using Nethermind.Int256; - -namespace Nethermind.Facade.Eth.RpcTransaction; - -/// -/// Transaction object generic to all types. -/// See: https://github.com/ethereum/execution-apis/blob/1d4f70e84191bb574286fd7cea6c48795bf73e78/src/schemas/transaction.yaml#L358 -/// -/// -/// This class is intended to be used ONLY as input for RPC calls, not for return values. -/// -public record class RpcGenericTransaction -{ - // TODO: To be discussed: this class is esentially a bag of "whatever" that could be part of any Transaction type. - // Adding new Transaction types (ex. Optimism `Deposit`) will require modifying this class to include the new fields. - // - // Another option would be to ignore the spec and instead take the incoming JSON `Type` and deserialize - // into specific classes (ex. `RpcLegacyTransaction`, `RpcAccessListTransaction`, etc). - // This involves other complications, for example: The `Blobs` fields can be part of the incoming JSON, but - // it's not part of the spec of `Blob` transactions, that is `RpcBlobTransaction` does not have a `Blobs` field. - - public TxType? Type { get; set; } - - public UInt256? Nonce { get; set; } - - [JsonIgnore(Condition = JsonIgnoreCondition.Never)] - public Address? To { get; set; } - - public Address? From { get; set; } - - public long? Gas { get; set; } - - public UInt256? Value { get; set; } - - public byte[]? Input { get; set; } - - public UInt256? GasPrice { get; set; } - - public UInt256? MaxPriorityFeePerGas { get; set; } - - public UInt256? MaxFeePerGas { get; set; } - - public UInt256? MaxFeePerBlobGas { get; set; } - - public RpcAccessList? AccessList { get; set; } - - // TODO: Each item should be a 32 byte array - // Currently we don't enforce this (hashes can have any length) - // See `RpcBlobTransaction` also - public byte[][]? BlobVersionedHashes { get; set; } - - // TODO: Does it match with `ShardBlobNetworkWrapper.Blobs` - // If so, what about `Commitments` and `Proofs`? - public byte[]? Blobs { get; set; } - - public ulong? ChainId { get; set; } - - // TODO: Missing Optimism specific fields - - // TODO: We'll replace this method with a proper factory class. - // Such factory will have a list of converters indexed by `TxType`, - // and each converter exposes a method to convert from `RpcGenericTransaction` to specific `Transaction` subclasses. - // Ex. `LegacyTransaction : Transaction` implements `FromRpcGenericTransaction(RpcGenericTransaction rpcTx)`. - public Transaction ToTransaction() - { - // TODO: What does the spec say about missing fields? - // We're currently "defaulting" everything to `0` or `null`. - - return new Transaction() - { - Type = Type ?? TxType.Legacy, - Nonce = Nonce ?? UInt256.Zero, - To = To, - SenderAddress = From ?? Address.Zero, - GasLimit = Gas ?? 0, - Value = Value ?? 0, - Data = Input, - GasPrice = GasPrice ?? 0, - DecodedMaxFeePerGas = MaxFeePerGas ?? 0, - MaxFeePerBlobGas = MaxFeePerBlobGas, - AccessList = AccessList?.ToAccessList(), - BlobVersionedHashes = BlobVersionedHashes, - ChainId = ChainId ?? 0, - // TODO: What is the point of `MaxPriorityFeePerGas` if it's internally always the same as `GasPrice` ? - // MaxPriorityFeePerGas = MaxPriorityFeePerGas ?? 0, - // TODO: How do we map `Blobs`? - // Blobs = Blobs, - }; - } -} From fe8e7e7bd3a6fae48e7aab070face43ac73de168 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 14:24:15 -0300 Subject: [PATCH 31/76] Define `RpcNethermindTransaction` as base class --- .../RpcNethermindTransaction.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs new file mode 100644 index 00000000000..11c87fda329 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json.Serialization; +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Facade.Eth.RpcTransaction; + +/// +/// Base class for all Nethermind RPC Transactions. +/// All fields are optional since they're not part of the Ethereum JSON RPC spec. +/// +public abstract class RpcNethermindTransaction : IRpcTransaction +{ + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public Hash256? Hash { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public long? TransactionIndex { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public Hash256? BlockHash { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public long? BlockNumber { get; set; } + + [JsonConstructor] + public RpcNethermindTransaction() { } + + public RpcNethermindTransaction(Transaction transaction) + { + Hash = transaction.Hash; + } +} From c76f72f63538676bccfdd358543c7633fc8cd569 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 14:24:27 -0300 Subject: [PATCH 32/76] Inherit from `RpcNethermindTransaction` - Includes Nethermind specific fields --- .../Eth/RpcTransaction/RpcLegacyTransaction.cs | 4 ++-- .../Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index e8724bc2fcc..b97ef54ca0d 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -8,7 +8,7 @@ namespace Nethermind.Facade.Eth.RpcTransaction; -public class RpcLegacyTransaction : IRpcTransaction +public class RpcLegacyTransaction : RpcNethermindTransaction { public TxType Type { get; set; } @@ -35,7 +35,7 @@ public class RpcLegacyTransaction : IRpcTransaction [JsonConstructor] public RpcLegacyTransaction() { } - public RpcLegacyTransaction(Transaction transaction) + public RpcLegacyTransaction(Transaction transaction) : base(transaction) { Type = transaction.Type; Nonce = transaction.Nonce; diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs index f19e1bdaac3..31c0ac0bdf7 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs @@ -9,7 +9,7 @@ namespace Nethermind.Optimism.Rpc; -public class RpcOptimismTransaction : IRpcTransaction +public class RpcOptimismTransaction : RpcNethermindTransaction { public TxType Type { get; set; } @@ -35,7 +35,7 @@ public class RpcOptimismTransaction : IRpcTransaction public UInt256? DepositReceiptVersion { get; set; } #endregion - public RpcOptimismTransaction(Transaction transaction) + public RpcOptimismTransaction(Transaction transaction) : base(transaction) { Type = transaction.Type; SourceHash = transaction.SourceHash ?? Hash256.Zero; From 629dda7ec15625a2014235689dff0f87e9b6b5bc Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 16:16:50 -0300 Subject: [PATCH 33/76] Add `TransactionConverterExtraData` - Allows setting Nethermind specific fields - In >= `EIP1559` transactions, compute `GasPrice` using `baseFee` - In Optimism transactions, store `DepositReceiptVersion` --- .../Eth/RpcTransaction/IRpcTransaction.cs | 2 +- .../Eth/RpcTransaction/ITransactionConverter.cs | 14 +++++++++++++- .../RpcTransaction/RpcAccessListTransaction.cs | 7 +++++-- .../Eth/RpcTransaction/RpcBlobTransaction.cs | 7 +++++-- .../Eth/RpcTransaction/RpcEIP1559Transaction.cs | 13 +++++++++---- .../Eth/RpcTransaction/RpcLegacyTransaction.cs | 7 +++++-- .../RpcTransaction/RpcNethermindTransaction.cs | 5 ++++- .../Modules/RpcTransaction/RpcTransactionTests.cs | 2 ++ .../Rpc/RpcOptimismTransaction.cs | 15 ++++++++------- 9 files changed, 52 insertions(+), 20 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs index 585346033aa..41e1d627980 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs @@ -53,7 +53,7 @@ public TransactionConverter RegisterConverter(TxType txType, ITransactionConvert return this; } - public IRpcTransaction FromTransaction(Transaction tx) + public IRpcTransaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) { var converter = _converters[(byte)tx.Type] ?? throw new ArgumentException("No converter for transaction type"); return converter.FromTransaction(tx); diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/ITransactionConverter.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/ITransactionConverter.cs index 411a59fb9e6..4a8e5461e50 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/ITransactionConverter.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/ITransactionConverter.cs @@ -2,10 +2,22 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Int256; namespace Nethermind.Facade.Eth.RpcTransaction; public interface ITransactionConverter { - T FromTransaction(Transaction tx); + T FromTransaction(Transaction tx, TransactionConverterExtraData extraData); + T FromTransaction(Transaction tx) => FromTransaction(tx, new TransactionConverterExtraData()); +} + +public readonly struct TransactionConverterExtraData +{ + public Hash256? BlockHash { get; } + public long? BlockNumber { get; } + public int? TxIndex { get; } + public UInt256? BaseFee { get; } + public TxReceipt Receipt { get; } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs index d96d533c998..a22c702adaa 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs @@ -3,6 +3,7 @@ using System.Text.Json.Serialization; using Nethermind.Core; +using Nethermind.Core.Crypto; using Nethermind.Int256; namespace Nethermind.Facade.Eth.RpcTransaction; @@ -28,7 +29,8 @@ public class RpcAccessListTransaction : RpcLegacyTransaction [JsonConstructor] public RpcAccessListTransaction() { } - public RpcAccessListTransaction(Transaction transaction) : base(transaction) + public RpcAccessListTransaction(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null) + : base(transaction, txIndex, blockHash, blockNumber) { AccessList = RpcAccessList.FromAccessList(transaction.AccessList); ChainId = transaction.ChainId @@ -42,6 +44,7 @@ public RpcAccessListTransaction(Transaction transaction) : base(transaction) private class ConverterImpl : ITransactionConverter { - public RpcAccessListTransaction FromTransaction(Transaction transaction) => new(transaction); + public RpcAccessListTransaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) + => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber); } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs index b31c5d884f4..97931065550 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs @@ -3,6 +3,7 @@ using System.Text.Json.Serialization; using Nethermind.Core; +using Nethermind.Core.Crypto; using Nethermind.Int256; namespace Nethermind.Facade.Eth.RpcTransaction; @@ -18,7 +19,8 @@ public class RpcBlobTransaction : RpcEIP1559Transaction [JsonConstructor] public RpcBlobTransaction() { } - public RpcBlobTransaction(Transaction transaction) : base(transaction) + public RpcBlobTransaction(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null, UInt256? baseFee = null) + : base(transaction, txIndex, blockHash, blockNumber) { MaxFeePerBlobGas = transaction.MaxFeePerBlobGas ?? 0; BlobVersionedHashes = transaction.BlobVersionedHashes ?? []; @@ -28,6 +30,7 @@ public RpcBlobTransaction(Transaction transaction) : base(transaction) private class ConverterImpl : ITransactionConverter { - public RpcBlobTransaction FromTransaction(Transaction transaction) => new(transaction); + public RpcBlobTransaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) + => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber, baseFee: extraData.BaseFee); } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs index 0b0e55723fb..bebb1afec07 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs @@ -3,6 +3,7 @@ using System.Text.Json.Serialization; using Nethermind.Core; +using Nethermind.Core.Crypto; using Nethermind.Int256; namespace Nethermind.Facade.Eth.RpcTransaction; @@ -22,17 +23,21 @@ public class RpcEIP1559Transaction : RpcAccessListTransaction [JsonConstructor] public RpcEIP1559Transaction() { } - public RpcEIP1559Transaction(Transaction transaction) : base(transaction) + public RpcEIP1559Transaction(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null, UInt256? baseFee = null) + : base(transaction, txIndex, blockHash, blockNumber) { - MaxPriorityFeePerGas = transaction.MaxPriorityFeePerGas; MaxFeePerGas = transaction.MaxFeePerGas; - GasPrice = transaction.GasPrice; + MaxPriorityFeePerGas = transaction.MaxPriorityFeePerGas; + GasPrice = baseFee is not null + ? transaction.CalculateEffectiveGasPrice(eip1559Enabled: true, baseFee.Value) + : transaction.MaxFeePerGas; } public new static readonly ITransactionConverter Converter = new ConverterImpl(); private class ConverterImpl : ITransactionConverter { - public RpcEIP1559Transaction FromTransaction(Transaction transaction) => new(transaction); + public RpcEIP1559Transaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) + => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber, baseFee: extraData.BaseFee); } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index b97ef54ca0d..32ec1815fcc 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -3,6 +3,7 @@ using System.Text.Json.Serialization; using Nethermind.Core; +using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -35,7 +36,8 @@ public class RpcLegacyTransaction : RpcNethermindTransaction [JsonConstructor] public RpcLegacyTransaction() { } - public RpcLegacyTransaction(Transaction transaction) : base(transaction) + public RpcLegacyTransaction(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null) + : base(transaction, txIndex, blockHash, blockNumber) { Type = transaction.Type; Nonce = transaction.Nonce; @@ -55,6 +57,7 @@ public RpcLegacyTransaction(Transaction transaction) : base(transaction) private class ConverterImpl : ITransactionConverter { - public RpcLegacyTransaction FromTransaction(Transaction tx) => new(tx); + public RpcLegacyTransaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) + => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber); } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs index 11c87fda329..3de77ae4380 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs @@ -28,8 +28,11 @@ public abstract class RpcNethermindTransaction : IRpcTransaction [JsonConstructor] public RpcNethermindTransaction() { } - public RpcNethermindTransaction(Transaction transaction) + public RpcNethermindTransaction(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null) { Hash = transaction.Hash; + TransactionIndex = txIndex; + BlockHash = blockHash; + BlockNumber = blockNumber; } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index b6708b71e0f..f12f4420b3f 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -66,6 +66,8 @@ public void Always_satisfies_schema(Transaction transaction) default: throw new ArgumentOutOfRangeException(); } + + // TODO: Test that implementors satisfy the schema for the base `RpcNethermindTransaction` } // * JSON -> IRpcTransaction (`IRpcTransaction.JsonConverter`, with a registry of [TxType => C# Type]) diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs index 31c0ac0bdf7..58493e59047 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs @@ -35,8 +35,12 @@ public class RpcOptimismTransaction : RpcNethermindTransaction public UInt256? DepositReceiptVersion { get; set; } #endregion - public RpcOptimismTransaction(Transaction transaction) : base(transaction) + public RpcOptimismTransaction(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null, OptimismTxReceipt? receipt = null) + : base(transaction, txIndex, blockHash, blockNumber) { + // NOTE: According to https://github.com/ethereum-optimism/op-geth/blob/8af19cf20261c0b62f98cc27da3a268f542822ee/core/types/deposit_tx.go#L79 `nonce == 0` + // Nonce = receipt?.DepositNonce; + Type = transaction.Type; SourceHash = transaction.SourceHash ?? Hash256.Zero; From = transaction.SenderAddress ?? Address.SystemUser; @@ -47,17 +51,14 @@ public RpcOptimismTransaction(Transaction transaction) : base(transaction) IsSystemTx = transaction.IsOPSystemTransaction ? true : null; Input = transaction.Data?.ToArray() ?? []; - // TODO: Handle extra data - // DepositReceiptVersion = receipt?.DepositReceiptVersion; - - // NOTE: According to https://github.com/ethereum-optimism/op-geth/blob/8af19cf20261c0b62f98cc27da3a268f542822ee/core/types/deposit_tx.go#L79 `nonce == 0` - // Nonce = receipt?.DepositNonce; + DepositReceiptVersion = receipt?.DepositReceiptVersion; } public static readonly ITransactionConverter Converter = new ConverterImpl(); private class ConverterImpl : ITransactionConverter { - public RpcOptimismTransaction FromTransaction(Transaction transaction) => new(transaction); + public RpcOptimismTransaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) + => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber, receipt: extraData.Receipt as OptimismTxReceipt); } } From e3c884af45be0065a985fe271d5fb43fe4c4347f Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 16:51:49 -0300 Subject: [PATCH 34/76] Add `ToTransaction` --- .../Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs index 41e1d627980..e72be887865 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs @@ -10,8 +10,9 @@ namespace Nethermind.Facade.Eth.RpcTransaction; public interface IRpcTransaction { - // TODO: Should/can we merge `JsonConverter` and `ITransactionConverter`? + Transaction ToTransaction(); + // TODO: Should/can we merge `JsonConverter` and `ITransactionConverter`? public class JsonConverter : JsonConverter { private readonly Type[] _transactionTypes = new Type[Transaction.MaxTxType + 1]; From 33213e53fe8726e5c2de17e686610c16dcf775f6 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 16:55:52 -0300 Subject: [PATCH 35/76] Implement `ToTransaction` --- .../RpcAccessListTransaction.cs | 7 +++++++ .../Eth/RpcTransaction/RpcBlobTransaction.cs | 8 ++++++++ .../RpcTransaction/RpcEIP1559Transaction.cs | 8 ++++++++ .../RpcTransaction/RpcLegacyTransaction.cs | 19 +++++++++++++++++++ .../RpcNethermindTransaction.cs | 2 ++ .../Rpc/RpcOptimismTransaction.cs | 16 ++++++++++++++++ 6 files changed, 60 insertions(+) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs index a22c702adaa..f68d014a25c 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs @@ -40,6 +40,13 @@ public RpcAccessListTransaction(Transaction transaction, int? txIndex = null, Ha V = transaction.Signature?.RecoveryId; } + public override Transaction ToTransaction() + { + var tx = base.ToTransaction(); + tx.AccessList = AccessList?.ToAccessList(); + return tx; + } + public new static readonly ITransactionConverter Converter = new ConverterImpl(); private class ConverterImpl : ITransactionConverter diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs index 97931065550..191d685d291 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs @@ -26,6 +26,14 @@ public RpcBlobTransaction(Transaction transaction, int? txIndex = null, Hash256? BlobVersionedHashes = transaction.BlobVersionedHashes ?? []; } + public override Transaction ToTransaction() + { + var tx = base.ToTransaction(); + tx.MaxFeePerBlobGas = MaxFeePerBlobGas; + tx.BlobVersionedHashes = BlobVersionedHashes; + return tx; + } + public new static readonly ITransactionConverter Converter = new ConverterImpl(); private class ConverterImpl : ITransactionConverter diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs index bebb1afec07..0079ba13028 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs @@ -33,6 +33,14 @@ public RpcEIP1559Transaction(Transaction transaction, int? txIndex = null, Hash2 : transaction.MaxFeePerGas; } + public override Transaction ToTransaction() + { + var tx = base.ToTransaction(); + tx.DecodedMaxFeePerGas = MaxFeePerGas; + tx.GasPrice = MaxPriorityFeePerGas; + return tx; + } + public new static readonly ITransactionConverter Converter = new ConverterImpl(); private class ConverterImpl : ITransactionConverter diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index 32ec1815fcc..529ff1fd3d6 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -53,6 +53,25 @@ public RpcLegacyTransaction(Transaction transaction, int? txIndex = null, Hash25 V = transaction.Signature?.V ?? 0; } + public override Transaction ToTransaction() + { + return new Transaction() + { + Type = Type, + Nonce = Nonce, + To = To, + GasLimit = Gas, + Value = Value, + Data = Input, + GasPrice = GasPrice, + + // TODO: Dangerous cast + ChainId = (ulong)ChainId, + // TODO: Get `From` + // SenderAddress = From, + }; + } + public static readonly ITransactionConverter Converter = new ConverterImpl(); private class ConverterImpl : ITransactionConverter diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs index 3de77ae4380..5210d259eae 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs @@ -35,4 +35,6 @@ public RpcNethermindTransaction(Transaction transaction, int? txIndex = null, Ha BlockHash = blockHash; BlockNumber = blockNumber; } + + public abstract Transaction ToTransaction(); } diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs index 58493e59047..e7f1f98c9b0 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs @@ -54,6 +54,22 @@ public RpcOptimismTransaction(Transaction transaction, int? txIndex = null, Hash DepositReceiptVersion = receipt?.DepositReceiptVersion; } + public override Transaction ToTransaction() + { + return new Transaction + { + Type = Type, + SourceHash = SourceHash, + SenderAddress = From, + To = To, + Mint = Mint ?? 0, + Value = Value, + GasLimit = Gas, + IsOPSystemTransaction = IsSystemTx ?? false, + Data = Input, + }; + } + public static readonly ITransactionConverter Converter = new ConverterImpl(); private class ConverterImpl : ITransactionConverter From 115dbfcd6f66dd545a200ad613c1751111705044 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 16:56:12 -0300 Subject: [PATCH 36/76] Update inline docs --- .../Modules/RpcTransaction/RpcTransactionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index f12f4420b3f..4a7b06bc157 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -71,7 +71,7 @@ public void Always_satisfies_schema(Transaction transaction) } // * JSON -> IRpcTransaction (`IRpcTransaction.JsonConverter`, with a registry of [TxType => C# Type]) - // * TODO: IRpcTransaction -> Transaction (IRpcTransaction has `.ToTransaction`) + // * IRpcTransaction -> Transaction (IRpcTransaction has `.ToTransaction`) // * Transaction -> IRpcTransaction (IRpcTransaction.TransactionConverter with a registry of [TxType => ITransactionConverter]) // * IRpcTransaction -> JSON (derived by `System.Text.JSON`) From fb917672d1ef7e6986f6f4867e5f28b7b688574f Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 17:10:10 -0300 Subject: [PATCH 37/76] Remove deprecated fields in Blob transactions --- .../Eth/RpcTransaction/RpcAccessListTransaction.cs | 4 ++-- .../Eth/RpcTransaction/RpcBlobTransaction.cs | 6 ++++++ .../Eth/RpcTransaction/RpcEIP1559Transaction.cs | 2 +- .../Eth/RpcTransaction/RpcLegacyTransaction.cs | 4 ++-- .../Modules/RpcTransaction/RpcBlobTransactionTests.cs | 7 +++++-- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs index f68d014a25c..996aaf810ce 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs @@ -24,7 +24,7 @@ public class RpcAccessListTransaction : RpcLegacyTransaction /// For backwards compatibility, v is optionally provided as an alternative to yParity. /// This field is DEPRECATED and all use of it should migrate to yParity. /// - public new UInt256? V { get; set; } + public override UInt256 V { get; set; } [JsonConstructor] public RpcAccessListTransaction() { } @@ -37,7 +37,7 @@ public RpcAccessListTransaction(Transaction transaction, int? txIndex = null, Ha ?? DefaultChainId ?? BlockchainIds.Mainnet; YParity = transaction.Signature?.RecoveryId ?? 0; - V = transaction.Signature?.RecoveryId; + V = YParity; } public override Transaction ToTransaction() diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs index 191d685d291..bdcd284fbae 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs @@ -16,6 +16,12 @@ public class RpcBlobTransaction : RpcEIP1559Transaction // Currently we don't enforce this (hashes can have any length) public byte[][] BlobVersionedHashes { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.Always)] + public override UInt256 GasPrice { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.Always)] + public override UInt256 V { get; set; } + [JsonConstructor] public RpcBlobTransaction() { } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs index 0079ba13028..51d83a5e6d4 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs @@ -18,7 +18,7 @@ public class RpcEIP1559Transaction : RpcAccessListTransaction /// The effective gas price paid by the sender in wei. For transactions not yet included in a block, this value should be set equal to the max fee per gas. /// This field is DEPRECATED, please transition to using effectiveGasPrice in the receipt object going forward. /// - public new UInt256 GasPrice { get; set; } + public override UInt256 GasPrice { get; set; } [JsonConstructor] public RpcEIP1559Transaction() { } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index 529ff1fd3d6..3e99a03b866 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -24,11 +24,11 @@ public class RpcLegacyTransaction : RpcNethermindTransaction public byte[] Input { get; set; } - public UInt256 GasPrice { get; set; } + public virtual UInt256 GasPrice { get; set; } public UInt256? ChainId { get; set; } - public UInt256 V { get; set; } + public virtual UInt256 V { get; set; } public UInt256 R { get; set; } public UInt256 S { get; set; } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcBlobTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcBlobTransactionTests.cs index 831ef882dca..f87c6474b30 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcBlobTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcBlobTransactionTests.cs @@ -117,9 +117,12 @@ public static void ValidateSchema(JsonElement json) ); } json.GetProperty("chainId").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); - var yParity = json.GetProperty("yParity").GetString(); - yParity.Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("yParity").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); json.GetProperty("r").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); json.GetProperty("s").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + + // Assert deprecated fields are no longer serialized + json.TryGetProperty("v", out _).Should().BeFalse(); + json.TryGetProperty("gasPrice", out _).Should().BeFalse(); } } From 90fe6faee68fd0daa1fa4fb77472fd2bcec28487 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 17:14:40 -0300 Subject: [PATCH 38/76] Retrofit `Data ~ Input` fix --- .../Eth/RpcTransaction/RpcLegacyTransaction.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index 3e99a03b866..fd7d7d40735 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -22,6 +22,13 @@ public class RpcLegacyTransaction : RpcNethermindTransaction public UInt256 Value { get; set; } + // Required for compatibility with some CLs like Prysm + // Accept during deserialization, ignore during serialization + // See: https://github.com/NethermindEth/nethermind/pull/6067 + [JsonPropertyName(nameof(Data))] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public byte[]? Data { set { Input = value; } private get { return null; } } + public byte[] Input { get; set; } public virtual UInt256 GasPrice { get; set; } From 121e057d4cf42d8ac6af6bac2ec9d140dd1d8f7a Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Fri, 13 Sep 2024 17:18:09 -0300 Subject: [PATCH 39/76] Move docs --- .../Eth/RpcTransaction/IRpcTransaction.cs | 9 +++++++++ .../Modules/RpcTransaction/RpcTransactionTests.cs | 5 ----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs index e72be887865..af942db3f76 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs @@ -8,10 +8,19 @@ namespace Nethermind.Facade.Eth.RpcTransaction; +// General flows: +// * JSON -> IRpcTransaction (`IRpcTransaction.JsonConverter`, with a registry of [TxType => C# Type]) +// * IRpcTransaction -> Transaction (IRpcTransaction has `.ToTransaction`) +// * Transaction -> IRpcTransaction (IRpcTransaction.TransactionConverter with a registry of [TxType => ITransactionConverter]) +// * IRpcTransaction -> JSON (derived by `System.Text.JSON`) + public interface IRpcTransaction { Transaction ToTransaction(); + // TODO: Implement + // Transaction ToTransactionWitDefaults(); + // TODO: Should/can we merge `JsonConverter` and `ITransactionConverter`? public class JsonConverter : JsonConverter { diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index 4a7b06bc157..52ee2953e5e 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -70,11 +70,6 @@ public void Always_satisfies_schema(Transaction transaction) // TODO: Test that implementors satisfy the schema for the base `RpcNethermindTransaction` } - // * JSON -> IRpcTransaction (`IRpcTransaction.JsonConverter`, with a registry of [TxType => C# Type]) - // * IRpcTransaction -> Transaction (IRpcTransaction has `.ToTransaction`) - // * Transaction -> IRpcTransaction (IRpcTransaction.TransactionConverter with a registry of [TxType => ITransactionConverter]) - // * IRpcTransaction -> JSON (derived by `System.Text.JSON`) - [TestCaseSource(nameof(Transactions))] public void RpcTransaction_JSON_roundtrip(Transaction tx) { From cbc3b7f5546b9d4acdeb2daccb890fbde5e042c3 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 12:14:00 -0300 Subject: [PATCH 40/76] Initial `ToTransactionWithDefaults` --- .../Eth/RpcTransaction/IRpcTransaction.cs | 3 +-- .../RpcTransaction/RpcLegacyTransaction.cs | 20 +++++++++++++++++++ .../RpcNethermindTransaction.cs | 2 ++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs index af942db3f76..821d1fd120c 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs @@ -18,8 +18,7 @@ public interface IRpcTransaction { Transaction ToTransaction(); - // TODO: Implement - // Transaction ToTransactionWitDefaults(); + Transaction ToTransactionWitDefaults(ulong chainId); // TODO: Should/can we merge `JsonConverter` and `ITransactionConverter`? public class JsonConverter : JsonConverter diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index fd7d7d40735..3fd40bceace 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -79,6 +79,26 @@ public override Transaction ToTransaction() }; } + public override Transaction ToTransactionWitDefaults(ulong chainId) + { + return new Transaction + { + Type = Type, + Nonce = Nonce, // TODO: here pick the last nonce? + To = To, + GasLimit = Gas, // ?? 90000, + Value = Value, + Data = Input, + GasPrice = GasPrice, // ?? 20.GWei(), + ChainId = chainId, + + // TODO: Get `From` + // SenderAddress = From, + // TODO: `WithDefaults` sets the hash, unlike `ToTransaction`. Is this intentional? + Hash = Hash + }; + } + public static readonly ITransactionConverter Converter = new ConverterImpl(); private class ConverterImpl : ITransactionConverter diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs index 5210d259eae..2ab6176b7c0 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs @@ -37,4 +37,6 @@ public RpcNethermindTransaction(Transaction transaction, int? txIndex = null, Ha } public abstract Transaction ToTransaction(); + + public abstract Transaction ToTransactionWitDefaults(ulong chainId); } From 3660df563d83cb774b76a30e1c1a18c426a82e80 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 12:14:45 -0300 Subject: [PATCH 41/76] Change `ChainId` from UInt256 to ulong in JSON DTO --- .../Eth/RpcTransaction/RpcLegacyTransaction.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index 3fd40bceace..4e199293918 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -33,7 +33,7 @@ public class RpcLegacyTransaction : RpcNethermindTransaction public virtual UInt256 GasPrice { get; set; } - public UInt256? ChainId { get; set; } + public ulong? ChainId { get; set; } public virtual UInt256 V { get; set; } @@ -71,9 +71,8 @@ public override Transaction ToTransaction() Value = Value, Data = Input, GasPrice = GasPrice, + ChainId = ChainId, - // TODO: Dangerous cast - ChainId = (ulong)ChainId, // TODO: Get `From` // SenderAddress = From, }; From 53118046a2343c1fad1222eb49d664db49ec705f Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 12:16:04 -0300 Subject: [PATCH 42/76] Update inline TODOs --- .../Eth/RpcTransaction/RpcLegacyTransaction.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index 4e199293918..a3101c2311c 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -85,10 +85,10 @@ public override Transaction ToTransactionWitDefaults(ulong chainId) Type = Type, Nonce = Nonce, // TODO: here pick the last nonce? To = To, - GasLimit = Gas, // ?? 90000, + GasLimit = Gas, // Default is `90000`, but field is not nullable. Value = Value, Data = Input, - GasPrice = GasPrice, // ?? 20.GWei(), + GasPrice = GasPrice, // Default is `20.GWei()` but field is not nullable. ChainId = chainId, // TODO: Get `From` From f8c04957017fe9723f84ea8709cba3a772add487 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 12:50:24 -0300 Subject: [PATCH 43/76] Implement remaining `ToTransactionWithDefaults` --- .../RpcAccessListTransaction.cs | 7 +++++++ .../Eth/RpcTransaction/RpcBlobTransaction.cs | 8 ++++++++ .../RpcTransaction/RpcEIP1559Transaction.cs | 8 ++++++++ .../RpcTransaction/RpcLegacyTransaction.cs | 6 ++---- .../Rpc/RpcOptimismTransaction.cs | 20 +++++++++++++++++++ 5 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs index 996aaf810ce..80efeb587b2 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs @@ -47,6 +47,13 @@ public override Transaction ToTransaction() return tx; } + public override Transaction ToTransactionWitDefaults(ulong chainId) + { + var tx = base.ToTransactionWitDefaults(chainId); + tx.AccessList = AccessList?.ToAccessList(); + return tx; + } + public new static readonly ITransactionConverter Converter = new ConverterImpl(); private class ConverterImpl : ITransactionConverter diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs index bdcd284fbae..453a6a34b80 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs @@ -40,6 +40,14 @@ public override Transaction ToTransaction() return tx; } + public override Transaction ToTransactionWitDefaults(ulong chainId) + { + var tx = base.ToTransactionWitDefaults(chainId); + tx.MaxFeePerBlobGas = MaxFeePerBlobGas; + tx.BlobVersionedHashes = BlobVersionedHashes; + return tx; + } + public new static readonly ITransactionConverter Converter = new ConverterImpl(); private class ConverterImpl : ITransactionConverter diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs index 51d83a5e6d4..c6d651ef2ce 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs @@ -41,6 +41,14 @@ public override Transaction ToTransaction() return tx; } + public override Transaction ToTransactionWitDefaults(ulong chainId) + { + var tx = base.ToTransactionWitDefaults(chainId); + tx.DecodedMaxFeePerGas = MaxFeePerGas; + tx.GasPrice = MaxPriorityFeePerGas; + return tx; + } + public new static readonly ITransactionConverter Converter = new ConverterImpl(); private class ConverterImpl : ITransactionConverter diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index a3101c2311c..f3a485916ba 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -85,16 +85,14 @@ public override Transaction ToTransactionWitDefaults(ulong chainId) Type = Type, Nonce = Nonce, // TODO: here pick the last nonce? To = To, - GasLimit = Gas, // Default is `90000`, but field is not nullable. + GasLimit = Gas, // Default is `90000` but field is not nullable. Value = Value, Data = Input, GasPrice = GasPrice, // Default is `20.GWei()` but field is not nullable. ChainId = chainId, // TODO: Get `From` - // SenderAddress = From, - // TODO: `WithDefaults` sets the hash, unlike `ToTransaction`. Is this intentional? - Hash = Hash + // tx.SenderAddress = From }; } diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs index e7f1f98c9b0..19b509d2003 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs @@ -6,6 +6,7 @@ using Nethermind.Int256; using System.Text.Json.Serialization; using Nethermind.Facade.Eth.RpcTransaction; +using Nethermind.Core.Extensions; namespace Nethermind.Optimism.Rpc; @@ -70,6 +71,25 @@ public override Transaction ToTransaction() }; } + public override Transaction ToTransactionWitDefaults(ulong chainId) + { + return new Transaction + { + Type = Type, + SourceHash = SourceHash, + SenderAddress = From, + To = To, + Mint = Mint ?? 0, + Value = Value, + GasLimit = Gas, // Default is `90000` but field is not nullable. + IsOPSystemTransaction = IsSystemTx ?? false, + Data = Input, + + GasPrice = 20.GWei(), + ChainId = chainId, + }; + } + public static readonly ITransactionConverter Converter = new ConverterImpl(); private class ConverterImpl : ITransactionConverter From dd43cf9fce6789e176fdff9069201d5495a36f0d Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 13:02:39 -0300 Subject: [PATCH 44/76] Add `From` field --- .../Eth/RpcTransaction/RpcLegacyTransaction.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index f3a485916ba..d2301ac7a7b 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -18,6 +18,8 @@ public class RpcLegacyTransaction : RpcNethermindTransaction [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public Address? To { get; set; } + public Address? From { get; set; } + public long Gas { get; set; } public UInt256 Value { get; set; } @@ -72,9 +74,7 @@ public override Transaction ToTransaction() Data = Input, GasPrice = GasPrice, ChainId = ChainId, - - // TODO: Get `From` - // SenderAddress = From, + SenderAddress = From, }; } @@ -90,9 +90,7 @@ public override Transaction ToTransactionWitDefaults(ulong chainId) Data = Input, GasPrice = GasPrice, // Default is `20.GWei()` but field is not nullable. ChainId = chainId, - - // TODO: Get `From` - // tx.SenderAddress = From + SenderAddress = From }; } From 34de464d38b99842e5deb1cb33b7bd2eb8ded0c0 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 13:21:55 -0300 Subject: [PATCH 45/76] Add defaults test - Added `ToTransactionWithDefaults_and_EnsureDefaults_same_GasLimit` --- .../Modules/Eth/EthRpcModuleTests.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs index c5b2ae5db1a..805f2675a27 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs @@ -1159,6 +1159,28 @@ public static void Should_handle_gasCap_as_max_if_null_or_zero(long? gasCap) Assert.That(rpcTx.Gas, Is.EqualTo(long.MaxValue), "Gas must be set to max if gasCap is null or 0"); } + [TestCase(null)] + [TestCase(0)] + public static void ToTransactionWithDefaults_and_EnsureDefaults_same_GasLimit(long? gasCap) + { + long toTransactionWitDefaultsGasLimit; + { + var rpcTx = new TransactionForRpc(); + Transaction tx = rpcTx.ToTransactionWithDefaults(); + toTransactionWitDefaultsGasLimit = tx.GasLimit; + } + + long ensureDefaultsGasLimit; + { + var rpcTx = new TransactionForRpc(); + rpcTx.EnsureDefaults(gasCap); + var tx = rpcTx.ToTransaction(); + ensureDefaultsGasLimit = tx.GasLimit; + } + + toTransactionWitDefaultsGasLimit.Should().Be(ensureDefaultsGasLimit); + } + [Test] public async Task eth_getBlockByNumber_should_return_withdrawals_correctly() { From f8283e42bf253b3bfea735359e8bac2fcfdcf789 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 13:29:30 -0300 Subject: [PATCH 46/76] Add `EnsureDefaults` --- .../RpcTransaction/RpcLegacyTransaction.cs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index d2301ac7a7b..3b0668af9bc 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -20,7 +21,8 @@ public class RpcLegacyTransaction : RpcNethermindTransaction public Address? From { get; set; } - public long Gas { get; set; } + // TODO: The spec says that `Gas` is required in all transactions, yet we have it as nullable. + public long? Gas { get; set; } public UInt256 Value { get; set; } @@ -69,12 +71,12 @@ public override Transaction ToTransaction() Type = Type, Nonce = Nonce, To = To, - GasLimit = Gas, + GasLimit = Gas ?? 0, Value = Value, Data = Input, GasPrice = GasPrice, ChainId = ChainId, - SenderAddress = From, + SenderAddress = From ?? Address.SystemUser, }; } @@ -85,15 +87,28 @@ public override Transaction ToTransactionWitDefaults(ulong chainId) Type = Type, Nonce = Nonce, // TODO: here pick the last nonce? To = To, - GasLimit = Gas, // Default is `90000` but field is not nullable. + GasLimit = Gas ?? 90_000, Value = Value, Data = Input, GasPrice = GasPrice, // Default is `20.GWei()` but field is not nullable. ChainId = chainId, - SenderAddress = From + SenderAddress = From ?? Address.SystemUser, }; } + // TODO: + // - `ToTransactionWithDefaults` uses `90_000` as default + // - `EnsureDefaultGas` uses `long.MaxValue` as default + public void EnsureDefaultGas(long? gasCap = 0) + { + if (gasCap is null || gasCap == 0) + gasCap = long.MaxValue; + + Gas = Gas == 0 + ? gasCap + : Math.Min(gasCap.Value, Gas.Value); + } + public static readonly ITransactionConverter Converter = new ConverterImpl(); private class ConverterImpl : ITransactionConverter From 0b63ce62ca9b4c87e296ed0820f03e31f9835db1 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 13:30:25 -0300 Subject: [PATCH 47/76] Add test ignore --- .../Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs index 805f2675a27..e1185c1dcac 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs @@ -1159,6 +1159,7 @@ public static void Should_handle_gasCap_as_max_if_null_or_zero(long? gasCap) Assert.That(rpcTx.Gas, Is.EqualTo(long.MaxValue), "Gas must be set to max if gasCap is null or 0"); } + [Ignore(reason: "Shows disparity across 'default' methods")] [TestCase(null)] [TestCase(0)] public static void ToTransactionWithDefaults_and_EnsureDefaults_same_GasLimit(long? gasCap) From eb98675d388fc8e9b157e0d6bbc47a3555e7a395 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 13:31:07 -0300 Subject: [PATCH 48/76] Forward `baseFee` --- .../Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs index 453a6a34b80..7d3150dde2e 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs @@ -26,7 +26,7 @@ public class RpcBlobTransaction : RpcEIP1559Transaction public RpcBlobTransaction() { } public RpcBlobTransaction(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null, UInt256? baseFee = null) - : base(transaction, txIndex, blockHash, blockNumber) + : base(transaction, txIndex, blockHash, blockNumber, baseFee) { MaxFeePerBlobGas = transaction.MaxFeePerBlobGas ?? 0; BlobVersionedHashes = transaction.BlobVersionedHashes ?? []; From 11b152231c734535d44a24434b8483660b264ddf Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 14:22:49 -0300 Subject: [PATCH 49/76] Use `RpcGenericTransaction` approach - Too many mismatches between inputs/outputs --- .../Eth/RpcTransaction/IFromTransaction.cs | 23 +++++++ .../Eth/RpcTransaction/IRpcTransaction.cs | 16 ++--- .../RpcAccessListTransaction.cs | 22 +------ .../Eth/RpcTransaction/RpcBlobTransaction.cs | 23 +------ .../RpcTransaction/RpcEIP1559Transaction.cs | 23 +------ .../RpcTransaction/RpcLegacyTransaction.cs | 63 +------------------ .../RpcNethermindTransaction.cs | 4 -- .../RpcTransaction/RpcTransactionTests.cs | 3 +- .../Rpc/RpcOptimismTransactionTests.cs | 2 +- .../Rpc/RpcOptimismTransaction.cs | 40 +----------- 10 files changed, 42 insertions(+), 177 deletions(-) create mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IFromTransaction.cs diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IFromTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IFromTransaction.cs new file mode 100644 index 00000000000..26cd2a6dcad --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IFromTransaction.cs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Int256; + +namespace Nethermind.Facade.Eth.RpcTransaction; + +public interface IFromTransaction +{ + T FromTransaction(Transaction tx, TransactionConverterExtraData extraData); + T FromTransaction(Transaction tx) => FromTransaction(tx, new TransactionConverterExtraData()); +} + +public readonly struct TransactionConverterExtraData +{ + public Hash256? BlockHash { get; } + public long? BlockNumber { get; } + public int? TxIndex { get; } + public UInt256? BaseFee { get; } + public TxReceipt Receipt { get; } +} diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs index 821d1fd120c..c5df73083bf 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs @@ -9,18 +9,14 @@ namespace Nethermind.Facade.Eth.RpcTransaction; // General flows: -// * JSON -> IRpcTransaction (`IRpcTransaction.JsonConverter`, with a registry of [TxType => C# Type]) -// * IRpcTransaction -> Transaction (IRpcTransaction has `.ToTransaction`) +// * JSON -> RpcGenericTransaction (derived by `System.Text.JSON`) +// * RpcGenericTransaction -> Transaction (RpcGenericTransaction.TransactionConverter with a registry of [TxType => ITransactionConverter]) // * Transaction -> IRpcTransaction (IRpcTransaction.TransactionConverter with a registry of [TxType => ITransactionConverter]) // * IRpcTransaction -> JSON (derived by `System.Text.JSON`) public interface IRpcTransaction { - Transaction ToTransaction(); - - Transaction ToTransactionWitDefaults(ulong chainId); - - // TODO: Should/can we merge `JsonConverter` and `ITransactionConverter`? + // TODO: Should/can we merge `JsonConverter` and `IFromTransaction`? public class JsonConverter : JsonConverter { private readonly Type[] _transactionTypes = new Type[Transaction.MaxTxType + 1]; @@ -52,11 +48,11 @@ public override void Write(Utf8JsonWriter writer, IRpcTransaction value, JsonSer } } - public class TransactionConverter : ITransactionConverter + public class TransactionConverter : IFromTransaction { - private readonly ITransactionConverter?[] _converters = new ITransactionConverter?[Transaction.MaxTxType + 1]; + private readonly IFromTransaction?[] _converters = new IFromTransaction?[Transaction.MaxTxType + 1]; - public TransactionConverter RegisterConverter(TxType txType, ITransactionConverter converter) + public TransactionConverter RegisterConverter(TxType txType, IFromTransaction converter) { _converters[(byte)txType] = converter; return this; diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs index 80efeb587b2..125e50ba489 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Int256; @@ -26,9 +25,6 @@ public class RpcAccessListTransaction : RpcLegacyTransaction /// public override UInt256 V { get; set; } - [JsonConstructor] - public RpcAccessListTransaction() { } - public RpcAccessListTransaction(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null) : base(transaction, txIndex, blockHash, blockNumber) { @@ -40,23 +36,9 @@ public RpcAccessListTransaction(Transaction transaction, int? txIndex = null, Ha V = YParity; } - public override Transaction ToTransaction() - { - var tx = base.ToTransaction(); - tx.AccessList = AccessList?.ToAccessList(); - return tx; - } - - public override Transaction ToTransactionWitDefaults(ulong chainId) - { - var tx = base.ToTransactionWitDefaults(chainId); - tx.AccessList = AccessList?.ToAccessList(); - return tx; - } - - public new static readonly ITransactionConverter Converter = new ConverterImpl(); + public new static readonly IFromTransaction Converter = new ConverterImpl(); - private class ConverterImpl : ITransactionConverter + private class ConverterImpl : IFromTransaction { public RpcAccessListTransaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber); diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs index 7d3150dde2e..d051feb3b3e 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs @@ -22,9 +22,6 @@ public class RpcBlobTransaction : RpcEIP1559Transaction [JsonIgnore(Condition = JsonIgnoreCondition.Always)] public override UInt256 V { get; set; } - [JsonConstructor] - public RpcBlobTransaction() { } - public RpcBlobTransaction(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null, UInt256? baseFee = null) : base(transaction, txIndex, blockHash, blockNumber, baseFee) { @@ -32,25 +29,9 @@ public RpcBlobTransaction(Transaction transaction, int? txIndex = null, Hash256? BlobVersionedHashes = transaction.BlobVersionedHashes ?? []; } - public override Transaction ToTransaction() - { - var tx = base.ToTransaction(); - tx.MaxFeePerBlobGas = MaxFeePerBlobGas; - tx.BlobVersionedHashes = BlobVersionedHashes; - return tx; - } - - public override Transaction ToTransactionWitDefaults(ulong chainId) - { - var tx = base.ToTransactionWitDefaults(chainId); - tx.MaxFeePerBlobGas = MaxFeePerBlobGas; - tx.BlobVersionedHashes = BlobVersionedHashes; - return tx; - } - - public new static readonly ITransactionConverter Converter = new ConverterImpl(); + public new static readonly IFromTransaction Converter = new ConverterImpl(); - private class ConverterImpl : ITransactionConverter + private class ConverterImpl : IFromTransaction { public RpcBlobTransaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber, baseFee: extraData.BaseFee); diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs index c6d651ef2ce..143946ed573 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs @@ -20,9 +20,6 @@ public class RpcEIP1559Transaction : RpcAccessListTransaction /// public override UInt256 GasPrice { get; set; } - [JsonConstructor] - public RpcEIP1559Transaction() { } - public RpcEIP1559Transaction(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null, UInt256? baseFee = null) : base(transaction, txIndex, blockHash, blockNumber) { @@ -33,25 +30,9 @@ public RpcEIP1559Transaction(Transaction transaction, int? txIndex = null, Hash2 : transaction.MaxFeePerGas; } - public override Transaction ToTransaction() - { - var tx = base.ToTransaction(); - tx.DecodedMaxFeePerGas = MaxFeePerGas; - tx.GasPrice = MaxPriorityFeePerGas; - return tx; - } - - public override Transaction ToTransactionWitDefaults(ulong chainId) - { - var tx = base.ToTransactionWitDefaults(chainId); - tx.DecodedMaxFeePerGas = MaxFeePerGas; - tx.GasPrice = MaxPriorityFeePerGas; - return tx; - } - - public new static readonly ITransactionConverter Converter = new ConverterImpl(); + public new static readonly IFromTransaction Converter = new ConverterImpl(); - private class ConverterImpl : ITransactionConverter + private class ConverterImpl : IFromTransaction { public RpcEIP1559Transaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber, baseFee: extraData.BaseFee); diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index 3b0668af9bc..8064302277b 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -19,20 +18,10 @@ public class RpcLegacyTransaction : RpcNethermindTransaction [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public Address? To { get; set; } - public Address? From { get; set; } - - // TODO: The spec says that `Gas` is required in all transactions, yet we have it as nullable. public long? Gas { get; set; } public UInt256 Value { get; set; } - // Required for compatibility with some CLs like Prysm - // Accept during deserialization, ignore during serialization - // See: https://github.com/NethermindEth/nethermind/pull/6067 - [JsonPropertyName(nameof(Data))] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public byte[]? Data { set { Input = value; } private get { return null; } } - public byte[] Input { get; set; } public virtual UInt256 GasPrice { get; set; } @@ -44,9 +33,6 @@ public class RpcLegacyTransaction : RpcNethermindTransaction public UInt256 R { get; set; } public UInt256 S { get; set; } - [JsonConstructor] - public RpcLegacyTransaction() { } - public RpcLegacyTransaction(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null) : base(transaction, txIndex, blockHash, blockNumber) { @@ -64,54 +50,9 @@ public RpcLegacyTransaction(Transaction transaction, int? txIndex = null, Hash25 V = transaction.Signature?.V ?? 0; } - public override Transaction ToTransaction() - { - return new Transaction() - { - Type = Type, - Nonce = Nonce, - To = To, - GasLimit = Gas ?? 0, - Value = Value, - Data = Input, - GasPrice = GasPrice, - ChainId = ChainId, - SenderAddress = From ?? Address.SystemUser, - }; - } - - public override Transaction ToTransactionWitDefaults(ulong chainId) - { - return new Transaction - { - Type = Type, - Nonce = Nonce, // TODO: here pick the last nonce? - To = To, - GasLimit = Gas ?? 90_000, - Value = Value, - Data = Input, - GasPrice = GasPrice, // Default is `20.GWei()` but field is not nullable. - ChainId = chainId, - SenderAddress = From ?? Address.SystemUser, - }; - } - - // TODO: - // - `ToTransactionWithDefaults` uses `90_000` as default - // - `EnsureDefaultGas` uses `long.MaxValue` as default - public void EnsureDefaultGas(long? gasCap = 0) - { - if (gasCap is null || gasCap == 0) - gasCap = long.MaxValue; - - Gas = Gas == 0 - ? gasCap - : Math.Min(gasCap.Value, Gas.Value); - } - - public static readonly ITransactionConverter Converter = new ConverterImpl(); + public static readonly IFromTransaction Converter = new FromTransactionImpl(); - private class ConverterImpl : ITransactionConverter + private class FromTransactionImpl : IFromTransaction { public RpcLegacyTransaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber); diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs index 2ab6176b7c0..3de77ae4380 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs @@ -35,8 +35,4 @@ public RpcNethermindTransaction(Transaction transaction, int? txIndex = null, Ha BlockHash = blockHash; BlockNumber = blockNumber; } - - public abstract Transaction ToTransaction(); - - public abstract Transaction ToTransactionWitDefaults(ulong chainId); } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index 52ee2953e5e..9953bf92221 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -21,7 +21,7 @@ public class RpcTransactionTests .RegisterTransactionType(TxType.Blob, typeof(RpcBlobTransaction)) ]); - private readonly ITransactionConverter _converter = new IRpcTransaction.TransactionConverter() + private readonly IFromTransaction _converter = new IRpcTransaction.TransactionConverter() .RegisterConverter(TxType.Legacy, RpcLegacyTransaction.Converter) .RegisterConverter(TxType.AccessList, RpcAccessListTransaction.Converter) .RegisterConverter(TxType.EIP1559, RpcEIP1559Transaction.Converter) @@ -71,6 +71,7 @@ public void Always_satisfies_schema(Transaction transaction) } [TestCaseSource(nameof(Transactions))] + [Ignore("todo: reimplement")] public void RpcTransaction_JSON_roundtrip(Transaction tx) { IRpcTransaction rpcTx = _converter.FromTransaction(tx); diff --git a/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs b/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs index 205b09a2ecc..d81b356ba4a 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs @@ -20,7 +20,7 @@ public class RpcOptimismTransactionTests .RegisterTransactionType(TxType.DepositTx, typeof(RpcOptimismTransaction)) ]); - private readonly ITransactionConverter _converter = new IRpcTransaction.TransactionConverter() + private readonly IFromTransaction _converter = new IRpcTransaction.TransactionConverter() .RegisterConverter(TxType.DepositTx, RpcOptimismTransaction.Converter); private static TransactionBuilder Build => Core.Test.Builders.Build.A.Transaction.WithType(TxType.DepositTx); diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs index 19b509d2003..e30abda232a 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs @@ -6,7 +6,6 @@ using Nethermind.Int256; using System.Text.Json.Serialization; using Nethermind.Facade.Eth.RpcTransaction; -using Nethermind.Core.Extensions; namespace Nethermind.Optimism.Rpc; @@ -55,44 +54,9 @@ public RpcOptimismTransaction(Transaction transaction, int? txIndex = null, Hash DepositReceiptVersion = receipt?.DepositReceiptVersion; } - public override Transaction ToTransaction() - { - return new Transaction - { - Type = Type, - SourceHash = SourceHash, - SenderAddress = From, - To = To, - Mint = Mint ?? 0, - Value = Value, - GasLimit = Gas, - IsOPSystemTransaction = IsSystemTx ?? false, - Data = Input, - }; - } - - public override Transaction ToTransactionWitDefaults(ulong chainId) - { - return new Transaction - { - Type = Type, - SourceHash = SourceHash, - SenderAddress = From, - To = To, - Mint = Mint ?? 0, - Value = Value, - GasLimit = Gas, // Default is `90000` but field is not nullable. - IsOPSystemTransaction = IsSystemTx ?? false, - Data = Input, - - GasPrice = 20.GWei(), - ChainId = chainId, - }; - } - - public static readonly ITransactionConverter Converter = new ConverterImpl(); + public static readonly IFromTransaction Converter = new ConverterImpl(); - private class ConverterImpl : ITransactionConverter + private class ConverterImpl : IFromTransaction { public RpcOptimismTransaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber, receipt: extraData.Receipt as OptimismTxReceipt); From f4dd96b146833c87a8de7faea2e9844670793372 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 14:23:35 -0300 Subject: [PATCH 50/76] Update flow docs --- .../Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs index c5df73083bf..f5b1d01fb99 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs @@ -10,8 +10,8 @@ namespace Nethermind.Facade.Eth.RpcTransaction; // General flows: // * JSON -> RpcGenericTransaction (derived by `System.Text.JSON`) -// * RpcGenericTransaction -> Transaction (RpcGenericTransaction.TransactionConverter with a registry of [TxType => ITransactionConverter]) -// * Transaction -> IRpcTransaction (IRpcTransaction.TransactionConverter with a registry of [TxType => ITransactionConverter]) +// * RpcGenericTransaction -> Transaction (RpcGenericTransaction.TransactionConverter with a registry of [TxType => IToTransaction]) +// * Transaction -> IRpcTransaction (IRpcTransaction.TransactionConverter with a registry of [TxType => IFromTransaction]) // * IRpcTransaction -> JSON (derived by `System.Text.JSON`) public interface IRpcTransaction From 8ee417ee8fb49c11e4fd6d59dc10159e608b1fae Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 15:16:19 -0300 Subject: [PATCH 51/76] Remove JSON constructor --- .../Eth/RpcTransaction/RpcNethermindTransaction.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs index 3de77ae4380..5a628e78d6b 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs @@ -25,9 +25,6 @@ public abstract class RpcNethermindTransaction : IRpcTransaction [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public long? BlockNumber { get; set; } - [JsonConstructor] - public RpcNethermindTransaction() { } - public RpcNethermindTransaction(Transaction transaction, int? txIndex = null, Hash256? blockHash = null, long? blockNumber = null) { Hash = transaction.Hash; From 422460b1449e50842676561ad776a84ef38f72c1 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 15:36:44 -0300 Subject: [PATCH 52/76] Add `IToTransaction` --- .../Eth/RpcTransaction/IToTransaction.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IToTransaction.cs diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IToTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IToTransaction.cs new file mode 100644 index 00000000000..8d63aa3af39 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IToTransaction.cs @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Facade.Eth.RpcTransaction; + +// TODO: We might want to lift this to `Core` +public interface IToTransaction +{ + Transaction ToTransaction(T t); + Transaction ToTransactionWithDefaults(T t, ulong chainId); +} From 416a21e1e68cca5de9b27fe0559e48684ca7b015 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 15:38:15 -0300 Subject: [PATCH 53/76] Add `RpcGenericTransaction` --- .../RpcTransaction/RpcGenericTransaction.cs | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs new file mode 100644 index 00000000000..8b1ded7e53f --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Text.Json.Serialization; +using Nethermind.Core; +using Nethermind.Int256; + +namespace Nethermind.Facade.Eth.RpcTransaction; + +/// +/// Transaction object generic to all types. +/// Used only for deserialization purposes. +/// Source: https://github.com/ethereum/execution-apis/blob/1d4f70e84191bb574286fd7cea6c48795bf73e78/src/schemas/transaction.yaml#L358 +/// +public class RpcGenericTransaction +{ + public TxType? Type { get; set; } + + public UInt256? Nonce { get; set; } + + public Address? To { get; set; } + + public Address? From { get; set; } + + public long? Gas { get; set; } + + public UInt256? Value { get; set; } + + // Required for compatibility with some CLs like Prysm + // See: https://github.com/NethermindEth/nethermind/pull/6067 + [JsonPropertyName(nameof(Data))] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public byte[]? Data { set { Input = value; } private get { return null; } } + + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public byte[]? Input { get; set; } + + public UInt256? GasPrice { get; set; } + + public UInt256? MaxPriorityFeePerGas { get; set; } + + public UInt256? MaxFeePerGas { get; set; } + + public UInt256? MaxFeePerBlobGas { get; set; } + + public RpcAccessList? AccessList { get; set; } + + public byte[][]? BlobVersionedHashes { get; set; } + + // TODO: Add missing field + // public byte[]? Blobs { get; set; } + + public UInt256? ChainId { get; set; } + + public class TransactionConverter : IToTransaction + { + private readonly IToTransaction?[] _converters = new IToTransaction?[Transaction.MaxTxType + 1]; + + public TransactionConverter RegisterConverter(TxType txType, IToTransaction converter) + { + _converters[(byte)txType] = converter; + return this; + } + + public Transaction ToTransaction(RpcGenericTransaction t) + { + if (t.Type == null) + throw new ArgumentException("Transaction type is not set."); + + byte txType = (byte)t.Type; + + if (txType < 0 || txType > Transaction.MaxTxType) + throw new ArgumentException($"Transaction type is out of range: {t.Type}"); + + if (_converters[txType] == null) + throw new ArgumentException($"Transaction type {t.Type} is not supported."); + + return _converters[txType].ToTransaction(t); + } + + public Transaction ToTransactionWithDefaults(RpcGenericTransaction tx, ulong chainId) + { + if (tx.Type == null) + throw new ArgumentException("Transaction type is not set."); + + byte txType = (byte)tx.Type; + + if (txType < 0 || txType > Transaction.MaxTxType) + throw new ArgumentException($"Transaction type is out of range: {tx.Type}"); + + if (_converters[txType] == null) + throw new ArgumentException($"Transaction type {tx.Type} is not supported."); + + return _converters[txType].ToTransactionWithDefaults(tx, chainId); + } + } +} From e2683f9b527cb1e1967520b71dbb5748ab0655e5 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 15:38:39 -0300 Subject: [PATCH 54/76] Implement `IToTransaction, IFromTransaction` --- .../RpcAccessListTransaction.cs | 20 +++++++++++-- .../Eth/RpcTransaction/RpcBlobTransaction.cs | 20 +++++++++++-- .../RpcTransaction/RpcEIP1559Transaction.cs | 20 +++++++++++-- .../RpcTransaction/RpcLegacyTransaction.cs | 30 +++++++++++++++++-- .../RpcTransaction/RpcTransactionTests.cs | 8 ++--- 5 files changed, 82 insertions(+), 16 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs index 125e50ba489..9e4df5a9483 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs @@ -1,8 +1,10 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Core.Eip2930; using Nethermind.Int256; namespace Nethermind.Facade.Eth.RpcTransaction; @@ -36,11 +38,23 @@ public RpcAccessListTransaction(Transaction transaction, int? txIndex = null, Ha V = YParity; } - public new static readonly IFromTransaction Converter = new ConverterImpl(); - - private class ConverterImpl : IFromTransaction + public new class Converter : IToTransaction, IFromTransaction { + private readonly RpcLegacyTransaction.Converter _baseConverter = new(); + public RpcAccessListTransaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber); + + public Transaction ToTransaction(RpcGenericTransaction rpcTx) + { + var tx = _baseConverter.ToTransaction(rpcTx); + tx.AccessList = rpcTx.AccessList?.ToAccessList() ?? Core.Eip2930.AccessList.Empty; + return tx; + } + + public Transaction ToTransactionWithDefaults(RpcGenericTransaction t, ulong chainId) + { + throw new NotImplementedException(); + } } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs index d051feb3b3e..5fd094f2f8e 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -29,11 +30,24 @@ public RpcBlobTransaction(Transaction transaction, int? txIndex = null, Hash256? BlobVersionedHashes = transaction.BlobVersionedHashes ?? []; } - public new static readonly IFromTransaction Converter = new ConverterImpl(); - - private class ConverterImpl : IFromTransaction + public new class Converter : IToTransaction, IFromTransaction { + private readonly RpcEIP1559Transaction.Converter _baseConverter = new(); + public RpcBlobTransaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber, baseFee: extraData.BaseFee); + + public Transaction ToTransaction(RpcGenericTransaction rpcTx) + { + var tx = _baseConverter.ToTransaction(rpcTx); + tx.MaxFeePerBlobGas = rpcTx.MaxFeePerBlobGas; + tx.BlobVersionedHashes = rpcTx.BlobVersionedHashes; + return tx; + } + + public Transaction ToTransactionWithDefaults(RpcGenericTransaction t, ulong chainId) + { + throw new NotImplementedException(); + } } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs index 143946ed573..d712b5a4072 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -30,11 +31,24 @@ public RpcEIP1559Transaction(Transaction transaction, int? txIndex = null, Hash2 : transaction.MaxFeePerGas; } - public new static readonly IFromTransaction Converter = new ConverterImpl(); - - private class ConverterImpl : IFromTransaction + public new class Converter : IToTransaction, IFromTransaction { + private readonly RpcAccessListTransaction.Converter _baseConverter = new(); + public RpcEIP1559Transaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber, baseFee: extraData.BaseFee); + + public Transaction ToTransaction(RpcGenericTransaction rpcTx) + { + var tx = _baseConverter.ToTransaction(rpcTx); + tx.GasPrice = rpcTx.MaxPriorityFeePerGas ?? 0; + tx.DecodedMaxFeePerGas = rpcTx.MaxFeePerGas ?? 0; + return tx; + } + + public Transaction ToTransactionWithDefaults(RpcGenericTransaction t, ulong chainId) + { + throw new NotImplementedException(); + } } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index 8064302277b..af47d39690f 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -50,11 +51,34 @@ public RpcLegacyTransaction(Transaction transaction, int? txIndex = null, Hash25 V = transaction.Signature?.V ?? 0; } - public static readonly IFromTransaction Converter = new FromTransactionImpl(); - - private class FromTransactionImpl : IFromTransaction + public class Converter : IToTransaction, IFromTransaction { public RpcLegacyTransaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber); + + public Transaction ToTransaction(RpcGenericTransaction rpcTx) + { + Transaction tx = new() + { + Type = (TxType)rpcTx.Type, + Nonce = rpcTx.Nonce ?? 0, // TODO: here pick the last nonce? + To = rpcTx.To, + GasLimit = rpcTx.Gas ?? 0, + Value = rpcTx.Value ?? 0, + Data = rpcTx.Input, + GasPrice = rpcTx.GasPrice ?? 0, + SenderAddress = rpcTx.From, + + // TODO: Unsafe cast + ChainId = (ulong?)rpcTx.ChainId, + }; + + return tx; + } + + public Transaction ToTransactionWithDefaults(RpcGenericTransaction t, ulong chainId) + { + throw new NotImplementedException(); + } } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index 9953bf92221..a9a36483bbc 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -22,10 +22,10 @@ public class RpcTransactionTests ]); private readonly IFromTransaction _converter = new IRpcTransaction.TransactionConverter() - .RegisterConverter(TxType.Legacy, RpcLegacyTransaction.Converter) - .RegisterConverter(TxType.AccessList, RpcAccessListTransaction.Converter) - .RegisterConverter(TxType.EIP1559, RpcEIP1559Transaction.Converter) - .RegisterConverter(TxType.Blob, RpcBlobTransaction.Converter); + .RegisterConverter(TxType.Legacy, new RpcLegacyTransaction.Converter()) + .RegisterConverter(TxType.AccessList, new RpcAccessListTransaction.Converter()) + .RegisterConverter(TxType.EIP1559, new RpcEIP1559Transaction.Converter()) + .RegisterConverter(TxType.Blob, new RpcBlobTransaction.Converter()); public static readonly Transaction[] Transactions = [ From 1bb457dfcef3d3440450cc17935ffd2994674af4 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 15:43:47 -0300 Subject: [PATCH 55/76] Implement `ToTransactionWithDefaults` --- .../RpcTransaction/RpcAccessListTransaction.cs | 8 ++++---- .../Eth/RpcTransaction/RpcBlobTransaction.cs | 8 +++++--- .../Eth/RpcTransaction/RpcEIP1559Transaction.cs | 9 +++++---- .../Eth/RpcTransaction/RpcLegacyTransaction.cs | 16 +++++++++++++--- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs index 9e4df5a9483..6045c078792 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs @@ -1,10 +1,8 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Core; using Nethermind.Core.Crypto; -using Nethermind.Core.Eip2930; using Nethermind.Int256; namespace Nethermind.Facade.Eth.RpcTransaction; @@ -52,9 +50,11 @@ public Transaction ToTransaction(RpcGenericTransaction rpcTx) return tx; } - public Transaction ToTransactionWithDefaults(RpcGenericTransaction t, ulong chainId) + public Transaction ToTransactionWithDefaults(RpcGenericTransaction rpcTx, ulong chainId) { - throw new NotImplementedException(); + var tx = _baseConverter.ToTransactionWithDefaults(rpcTx, chainId); + tx.AccessList = rpcTx.AccessList?.ToAccessList() ?? Core.Eip2930.AccessList.Empty; + return tx; } } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs index 5fd094f2f8e..1d6465504ba 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -45,9 +44,12 @@ public Transaction ToTransaction(RpcGenericTransaction rpcTx) return tx; } - public Transaction ToTransactionWithDefaults(RpcGenericTransaction t, ulong chainId) + public Transaction ToTransactionWithDefaults(RpcGenericTransaction rpcTx, ulong chainId) { - throw new NotImplementedException(); + var tx = _baseConverter.ToTransaction(rpcTx); + tx.MaxFeePerBlobGas = rpcTx.MaxFeePerBlobGas; + tx.BlobVersionedHashes = rpcTx.BlobVersionedHashes; + return tx; } } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs index d712b5a4072..1116420c375 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Int256; @@ -46,9 +44,12 @@ public Transaction ToTransaction(RpcGenericTransaction rpcTx) return tx; } - public Transaction ToTransactionWithDefaults(RpcGenericTransaction t, ulong chainId) + public Transaction ToTransactionWithDefaults(RpcGenericTransaction rpcTx, ulong chainId) { - throw new NotImplementedException(); + var tx = _baseConverter.ToTransaction(rpcTx); + tx.GasPrice = rpcTx.MaxPriorityFeePerGas ?? 0; + tx.DecodedMaxFeePerGas = rpcTx.MaxFeePerGas ?? 0; + return tx; } } } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index af47d39690f..1f7a2038163 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -76,9 +75,20 @@ public Transaction ToTransaction(RpcGenericTransaction rpcTx) return tx; } - public Transaction ToTransactionWithDefaults(RpcGenericTransaction t, ulong chainId) + public Transaction ToTransactionWithDefaults(RpcGenericTransaction rpcTx, ulong chainId) { - throw new NotImplementedException(); + return new Transaction() + { + Type = (TxType)rpcTx.Type, + Nonce = rpcTx.Nonce ?? 0, // TODO: here pick the last nonce? + To = rpcTx.To, + GasLimit = rpcTx.Gas ?? 90000, + Value = rpcTx.Value ?? 0, + Data = rpcTx.Input, + GasPrice = rpcTx.GasPrice ?? 20.GWei(), + SenderAddress = rpcTx.From, + ChainId = chainId, + }; } } } From aaf6d5399e1633e0d0555be39fa3c9c715ab518c Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 15:44:03 -0300 Subject: [PATCH 56/76] Remove deleted files --- .../RpcTransaction/ITransactionConverter.cs | 23 ------------------- 1 file changed, 23 deletions(-) delete mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/ITransactionConverter.cs diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/ITransactionConverter.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/ITransactionConverter.cs deleted file mode 100644 index 4a8e5461e50..00000000000 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/ITransactionConverter.cs +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Int256; - -namespace Nethermind.Facade.Eth.RpcTransaction; - -public interface ITransactionConverter -{ - T FromTransaction(Transaction tx, TransactionConverterExtraData extraData); - T FromTransaction(Transaction tx) => FromTransaction(tx, new TransactionConverterExtraData()); -} - -public readonly struct TransactionConverterExtraData -{ - public Hash256? BlockHash { get; } - public long? BlockNumber { get; } - public int? TxIndex { get; } - public UInt256? BaseFee { get; } - public TxReceipt Receipt { get; } -} From 443115cdbe33af9a531af0f01f890322f0d12da6 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 15:54:36 -0300 Subject: [PATCH 57/76] Implement `IToTransaction, IFromTransaction` for OP --- .../Rpc/RpcOptimismTransactionTests.cs | 2 +- .../Rpc/RpcOptimismTransaction.cs | 41 +++++++++++++++---- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs b/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs index d81b356ba4a..be8303dbb1a 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs @@ -21,7 +21,7 @@ public class RpcOptimismTransactionTests ]); private readonly IFromTransaction _converter = new IRpcTransaction.TransactionConverter() - .RegisterConverter(TxType.DepositTx, RpcOptimismTransaction.Converter); + .RegisterConverter(TxType.DepositTx, new RpcOptimismTransaction.Converter()); private static TransactionBuilder Build => Core.Test.Builders.Build.A.Transaction.WithType(TxType.DepositTx); public static readonly Transaction[] Transactions = [ diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs index e30abda232a..dfe8495813c 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs @@ -9,6 +9,9 @@ namespace Nethermind.Optimism.Rpc; +/// +/// Defined in https://github.com/ethereum-optimism/op-geth/blob/8af19cf20261c0b62f98cc27da3a268f542822ee/core/types/deposit_tx.go#L29-L46 +/// public class RpcOptimismTransaction : RpcNethermindTransaction { public TxType Type { get; set; } @@ -24,9 +27,9 @@ public class RpcOptimismTransaction : RpcNethermindTransaction public UInt256 Value { get; set; } - public long Gas { get; set; } + public ulong Gas { get; set; } - public bool? IsSystemTx { get; set; } + public bool IsSystemTx { get; set; } public byte[] Input { get; set; } @@ -47,18 +50,42 @@ public RpcOptimismTransaction(Transaction transaction, int? txIndex = null, Hash To = transaction.To; Mint = transaction.Mint; Value = transaction.Value; - Gas = transaction.GasLimit; - IsSystemTx = transaction.IsOPSystemTransaction ? true : null; + // TODO: Unsafe cast + Gas = (ulong)transaction.GasLimit; + IsSystemTx = transaction.IsOPSystemTransaction; Input = transaction.Data?.ToArray() ?? []; DepositReceiptVersion = receipt?.DepositReceiptVersion; } - public static readonly IFromTransaction Converter = new ConverterImpl(); - - private class ConverterImpl : IFromTransaction + public class Converter : IToTransaction, IFromTransaction { public RpcOptimismTransaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) => new(tx, txIndex: extraData.TxIndex, blockHash: extraData.BlockHash, blockNumber: extraData.BlockNumber, receipt: extraData.Receipt as OptimismTxReceipt); + + public Transaction ToTransaction(RpcOptimismTransaction rpcTx) + { + return new Transaction() + { + Type = rpcTx.Type, + SourceHash = rpcTx.SourceHash, + SenderAddress = rpcTx.From, + To = rpcTx.To, + Mint = rpcTx.Mint ?? 0, + Value = rpcTx.Value, + GasPrice = rpcTx.Gas, + // TODO: Unsafe cast + GasLimit = (long)rpcTx.Gas, + IsOPSystemTransaction = rpcTx.IsSystemTx, + Data = rpcTx.Input + }; + } + + public Transaction ToTransactionWithDefaults(RpcOptimismTransaction rpcTx, ulong chainId) + { + var tx = ToTransaction(rpcTx); + tx.ChainId = chainId; + return tx; + } } } From c431ad946ca2a19c45c81f9165ec4a6c6368d0e8 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 15:57:49 -0300 Subject: [PATCH 58/76] Add `EnsureDefaults` --- .../Eth/RpcTransaction/RpcGenericTransaction.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs index 8b1ded7e53f..f9626a4e557 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs @@ -53,6 +53,19 @@ public class RpcGenericTransaction public UInt256? ChainId { get; set; } + // TODO: `Gas` is set as default to to `long.MaxValue` here but to `90_000` in `ToTransactionWithDefaults` + public void EnsureDefaults(long? gasCap) + { + if (gasCap is null || gasCap == 0) + gasCap = long.MaxValue; + + Gas = Gas is null || Gas == 0 + ? gasCap + : Math.Min(gasCap.Value, Gas.Value); + + From ??= Address.SystemUser; + } + public class TransactionConverter : IToTransaction { private readonly IToTransaction?[] _converters = new IToTransaction?[Transaction.MaxTxType + 1]; From 6cda988d57afcdce0e939a7b6a9809d2a04ae779 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 15:58:49 -0300 Subject: [PATCH 59/76] Remove roundtrip tests - There is no longer "roundtrip" --- .../Modules/RpcTransaction/RpcTransactionTests.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index a9a36483bbc..f51ab5c74b0 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -69,15 +69,4 @@ public void Always_satisfies_schema(Transaction transaction) // TODO: Test that implementors satisfy the schema for the base `RpcNethermindTransaction` } - - [TestCaseSource(nameof(Transactions))] - [Ignore("todo: reimplement")] - public void RpcTransaction_JSON_roundtrip(Transaction tx) - { - IRpcTransaction rpcTx = _converter.FromTransaction(tx); - string serialized = _serializer.Serialize(rpcTx); - IRpcTransaction deserialized = _serializer.Deserialize(serialized); - - rpcTx.Should().BeEquivalentTo(deserialized, options => options.RespectingRuntimeTypes()); - } } From e2e76917b34dc42bd8cfa9da4b9982fba463c2dd Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 16:07:06 -0300 Subject: [PATCH 60/76] Test for Nethermind fields --- .../RpcTransaction/RpcTransactionTests.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index f51ab5c74b0..fe523622d15 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -42,7 +42,7 @@ public void SetUp() } [TestCaseSource(nameof(Transactions))] - public void Always_satisfies_schema(Transaction transaction) + public void Serialized_JSON_satisfies_schema(Transaction transaction) { IRpcTransaction rpcTransaction = _converter.FromTransaction(transaction); string serialized = _serializer.Serialize(rpcTransaction); @@ -66,7 +66,19 @@ public void Always_satisfies_schema(Transaction transaction) default: throw new ArgumentOutOfRangeException(); } + } + + [TestCaseSource(nameof(Transactions))] + public void Serialized_JSON_satisfies_Nethermind_fields_schema(Transaction transaction) + { + IRpcTransaction rpcTransaction = _converter.FromTransaction(transaction); + string serialized = _serializer.Serialize(rpcTransaction); + using var jsonDocument = JsonDocument.Parse(serialized); + JsonElement json = jsonDocument.RootElement; - // TODO: Test that implementors satisfy the schema for the base `RpcNethermindTransaction` + json.GetProperty("hash").GetString()?.Should().MatchRegex("^0x[0-9a-fA-F]{64}$"); + json.GetProperty("transactionIndex").GetString()?.Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); + json.GetProperty("blockHash").GetString()?.Should().MatchRegex("^0x[0-9a-fA-F]{64}$"); + json.GetProperty("blockNumber").GetString()?.Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); } } From 5ad50497faa4342a352f704ae76dc515e855ac30 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 16:10:23 -0300 Subject: [PATCH 61/76] Move files - Not specific to RPC --- .../Eth/{RpcTransaction => }/IFromTransaction.cs | 2 +- .../Eth/{RpcTransaction => }/IToTransaction.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/Nethermind/Nethermind.Facade/Eth/{RpcTransaction => }/IFromTransaction.cs (93%) rename src/Nethermind/Nethermind.Facade/Eth/{RpcTransaction => }/IToTransaction.cs (86%) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IFromTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/IFromTransaction.cs similarity index 93% rename from src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IFromTransaction.cs rename to src/Nethermind/Nethermind.Facade/Eth/IFromTransaction.cs index 26cd2a6dcad..35083cb33f3 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IFromTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/IFromTransaction.cs @@ -5,7 +5,7 @@ using Nethermind.Core.Crypto; using Nethermind.Int256; -namespace Nethermind.Facade.Eth.RpcTransaction; +namespace Nethermind.Facade.Eth; public interface IFromTransaction { diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IToTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/IToTransaction.cs similarity index 86% rename from src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IToTransaction.cs rename to src/Nethermind/Nethermind.Facade/Eth/IToTransaction.cs index 8d63aa3af39..fb5dee27dba 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IToTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/IToTransaction.cs @@ -3,7 +3,7 @@ using Nethermind.Core; -namespace Nethermind.Facade.Eth.RpcTransaction; +namespace Nethermind.Facade.Eth; // TODO: We might want to lift this to `Core` public interface IToTransaction From 825d455d1871d894b68790de7d2edfbfa1e75174 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 16:22:05 -0300 Subject: [PATCH 62/76] Proper inline docs --- .../Eth/RpcTransaction/IRpcTransaction.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs index f5b1d01fb99..5aadbe4fa7c 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs @@ -8,12 +8,15 @@ namespace Nethermind.Facade.Eth.RpcTransaction; -// General flows: -// * JSON -> RpcGenericTransaction (derived by `System.Text.JSON`) -// * RpcGenericTransaction -> Transaction (RpcGenericTransaction.TransactionConverter with a registry of [TxType => IToTransaction]) -// * Transaction -> IRpcTransaction (IRpcTransaction.TransactionConverter with a registry of [TxType => IFromTransaction]) -// * IRpcTransaction -> JSON (derived by `System.Text.JSON`) +/// +/// Input: +/// JSON -> (derived by System.Text.JSON) +/// -> ( with a registry of [ => ) +/// Output: +/// -> ( with a registry of [ => ) +/// -> JSON (derived by System.Text.JSON.) +/// public interface IRpcTransaction { // TODO: Should/can we merge `JsonConverter` and `IFromTransaction`? From 5f3e55385806aeba73f1e79e11cdc992da7bce26 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 16:22:25 -0300 Subject: [PATCH 63/76] Rename class --- .../Eth/RpcTransaction/RpcGenericTransaction.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs index f9626a4e557..59ddf5fca68 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs @@ -66,11 +66,11 @@ public void EnsureDefaults(long? gasCap) From ??= Address.SystemUser; } - public class TransactionConverter : IToTransaction + public class Converter : IToTransaction { private readonly IToTransaction?[] _converters = new IToTransaction?[Transaction.MaxTxType + 1]; - public TransactionConverter RegisterConverter(TxType txType, IToTransaction converter) + public Converter RegisterConverter(TxType txType, IToTransaction converter) { _converters[(byte)txType] = converter; return this; From 79761d06765c1d6aac65c2a9b09b299946054789 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 16:22:31 -0300 Subject: [PATCH 64/76] Fix import --- .../Modules/RpcTransaction/RpcTransactionTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index fe523622d15..b84008bf4ff 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -5,6 +5,7 @@ using System.Text.Json; using FluentAssertions; using Nethermind.Core; +using Nethermind.Facade.Eth; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Serialization.Json; using NUnit.Framework; From 1ff3356f7afd1b25db8dc2312507b2699a0ab99e Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 16:26:34 -0300 Subject: [PATCH 65/76] Simplify XML reference --- .../Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs index 5aadbe4fa7c..f0150fd5a64 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs @@ -14,7 +14,7 @@ namespace Nethermind.Facade.Eth.RpcTransaction; /// JSON -> (derived by System.Text.JSON) /// -> ( with a registry of [ => ) /// Output: -/// -> ( with a registry of [ => ) +/// -> ( with a registry of [ => ) /// -> JSON (derived by System.Text.JSON.) /// public interface IRpcTransaction From 80264f4160e1b2f8cfecaead08d6a39b080da654 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 16:28:26 -0300 Subject: [PATCH 66/76] Fix imports --- src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs index dfe8495813c..c6a5fe3b0b9 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs @@ -6,6 +6,7 @@ using Nethermind.Int256; using System.Text.Json.Serialization; using Nethermind.Facade.Eth.RpcTransaction; +using Nethermind.Facade.Eth; namespace Nethermind.Optimism.Rpc; From e07620d2107b1efe738aebfca2b9d6812bf24f89 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 16:37:39 -0300 Subject: [PATCH 67/76] Make fields `init` only --- .../Nethermind.Facade/Eth/IFromTransaction.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/IFromTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/IFromTransaction.cs index 35083cb33f3..0dfdc41e6ea 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/IFromTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/IFromTransaction.cs @@ -15,9 +15,9 @@ public interface IFromTransaction public readonly struct TransactionConverterExtraData { - public Hash256? BlockHash { get; } - public long? BlockNumber { get; } - public int? TxIndex { get; } - public UInt256? BaseFee { get; } - public TxReceipt Receipt { get; } + public Hash256? BlockHash { get; init; } + public long? BlockNumber { get; init; } + public int? TxIndex { get; init; } + public UInt256? BaseFee { get; init; } + public TxReceipt Receipt { get; init; } } From 70abd1b78a5c82cf354c53e5396da06c7a9ef0a4 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 16:45:47 -0300 Subject: [PATCH 68/76] Add transition `RpcGenericTransactionExtensions` - Includes global converter --- .../Eth/RpcTransaction/RpcGenericTransaction.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs index 59ddf5fca68..ee35e7316da 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs @@ -109,3 +109,20 @@ public Transaction ToTransactionWithDefaults(RpcGenericTransaction tx, ulong cha } } } + +public static class RpcGenericTransactionExtensions +{ + public static readonly RpcGenericTransaction.Converter GlobalConverter = new RpcGenericTransaction.Converter() + .RegisterConverter(TxType.Legacy, new RpcLegacyTransaction.Converter()) + .RegisterConverter(TxType.AccessList, new RpcAccessListTransaction.Converter()) + .RegisterConverter(TxType.EIP1559, new RpcEIP1559Transaction.Converter()) + .RegisterConverter(TxType.Blob, new RpcBlobTransaction.Converter()); + + /// + /// Intended to be used until proper DI is implemented. + /// + public static Transaction ToTransaction(this RpcGenericTransaction rpcTransaction) + { + return GlobalConverter.ToTransaction(rpcTransaction); + } +} From aba2970015afbb75da8b49a9f23fbe1b6888f783 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 16:48:53 -0300 Subject: [PATCH 69/76] Preserve nullability --- .../Nethermind.Core.Test/Builders/TransactionBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs index 1a921445652..4ffc4a20849 100644 --- a/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs +++ b/src/Nethermind/Nethermind.Core.Test/Builders/TransactionBuilder.cs @@ -134,7 +134,7 @@ public TransactionBuilder WithSenderAddress(Address? address) return this; } - public TransactionBuilder WithMaxFeePerBlobGas(UInt256 maxFeePerBlobGas) + public TransactionBuilder WithMaxFeePerBlobGas(UInt256? maxFeePerBlobGas) { TestObjectInternal.MaxFeePerBlobGas = maxFeePerBlobGas; return this; From 70b175c32e9621930dbac794f96e5da01a808f3a Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 16:52:33 -0300 Subject: [PATCH 70/76] Simplify accessor - We throw either way --- .../RpcTransaction/RpcGenericTransaction.cs | 33 +++---------------- 1 file changed, 4 insertions(+), 29 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs index ee35e7316da..e515b826df5 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Diagnostics; using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Int256; @@ -76,37 +77,11 @@ public Converter RegisterConverter(TxType txType, IToTransaction Transaction.MaxTxType) - throw new ArgumentException($"Transaction type is out of range: {t.Type}"); - - if (_converters[txType] == null) - throw new ArgumentException($"Transaction type {t.Type} is not supported."); - - return _converters[txType].ToTransaction(t); - } + public Transaction ToTransaction(RpcGenericTransaction tx) + => _converters[(byte)tx.Type!]!.ToTransaction(tx); public Transaction ToTransactionWithDefaults(RpcGenericTransaction tx, ulong chainId) - { - if (tx.Type == null) - throw new ArgumentException("Transaction type is not set."); - - byte txType = (byte)tx.Type; - - if (txType < 0 || txType > Transaction.MaxTxType) - throw new ArgumentException($"Transaction type is out of range: {tx.Type}"); - - if (_converters[txType] == null) - throw new ArgumentException($"Transaction type {tx.Type} is not supported."); - - return _converters[txType].ToTransactionWithDefaults(tx, chainId); - } + => _converters[(byte)tx.Type!]!.ToTransactionWithDefaults(tx, chainId); } } From 93f94a958375198352cbfaffb6fc78ebe277d7fc Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 16:56:10 -0300 Subject: [PATCH 71/76] Remove `chainId` from `ToTransactionWithDefaults` --- src/Nethermind/Nethermind.Facade/Eth/IToTransaction.cs | 2 +- .../Eth/RpcTransaction/RpcAccessListTransaction.cs | 4 ++-- .../Eth/RpcTransaction/RpcBlobTransaction.cs | 2 +- .../Eth/RpcTransaction/RpcEIP1559Transaction.cs | 2 +- .../Eth/RpcTransaction/RpcLegacyTransaction.cs | 3 +-- .../Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs | 3 +-- 6 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/IToTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/IToTransaction.cs index fb5dee27dba..5d469403e59 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/IToTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/IToTransaction.cs @@ -9,5 +9,5 @@ namespace Nethermind.Facade.Eth; public interface IToTransaction { Transaction ToTransaction(T t); - Transaction ToTransactionWithDefaults(T t, ulong chainId); + Transaction ToTransactionWithDefaults(T t); } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs index 6045c078792..bed8c87b604 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcAccessListTransaction.cs @@ -50,9 +50,9 @@ public Transaction ToTransaction(RpcGenericTransaction rpcTx) return tx; } - public Transaction ToTransactionWithDefaults(RpcGenericTransaction rpcTx, ulong chainId) + public Transaction ToTransactionWithDefaults(RpcGenericTransaction rpcTx) { - var tx = _baseConverter.ToTransactionWithDefaults(rpcTx, chainId); + var tx = _baseConverter.ToTransactionWithDefaults(rpcTx); tx.AccessList = rpcTx.AccessList?.ToAccessList() ?? Core.Eip2930.AccessList.Empty; return tx; } diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs index 1d6465504ba..0f4abb794c2 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcBlobTransaction.cs @@ -44,7 +44,7 @@ public Transaction ToTransaction(RpcGenericTransaction rpcTx) return tx; } - public Transaction ToTransactionWithDefaults(RpcGenericTransaction rpcTx, ulong chainId) + public Transaction ToTransactionWithDefaults(RpcGenericTransaction rpcTx) { var tx = _baseConverter.ToTransaction(rpcTx); tx.MaxFeePerBlobGas = rpcTx.MaxFeePerBlobGas; diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs index 1116420c375..f463fb7ea20 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcEIP1559Transaction.cs @@ -44,7 +44,7 @@ public Transaction ToTransaction(RpcGenericTransaction rpcTx) return tx; } - public Transaction ToTransactionWithDefaults(RpcGenericTransaction rpcTx, ulong chainId) + public Transaction ToTransactionWithDefaults(RpcGenericTransaction rpcTx) { var tx = _baseConverter.ToTransaction(rpcTx); tx.GasPrice = rpcTx.MaxPriorityFeePerGas ?? 0; diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index 1f7a2038163..e9185a0bd07 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -75,7 +75,7 @@ public Transaction ToTransaction(RpcGenericTransaction rpcTx) return tx; } - public Transaction ToTransactionWithDefaults(RpcGenericTransaction rpcTx, ulong chainId) + public Transaction ToTransactionWithDefaults(RpcGenericTransaction rpcTx) { return new Transaction() { @@ -87,7 +87,6 @@ public Transaction ToTransactionWithDefaults(RpcGenericTransaction rpcTx, ulong Data = rpcTx.Input, GasPrice = rpcTx.GasPrice ?? 20.GWei(), SenderAddress = rpcTx.From, - ChainId = chainId, }; } } diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs index c6a5fe3b0b9..250dfb98e70 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs @@ -82,10 +82,9 @@ public Transaction ToTransaction(RpcOptimismTransaction rpcTx) }; } - public Transaction ToTransactionWithDefaults(RpcOptimismTransaction rpcTx, ulong chainId) + public Transaction ToTransactionWithDefaults(RpcOptimismTransaction rpcTx) { var tx = ToTransaction(rpcTx); - tx.ChainId = chainId; return tx; } } From a9a06f4c1123e739d59c0d8400367d83c420f060 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 16:56:52 -0300 Subject: [PATCH 72/76] Add more fallback methods durin dev --- .../RpcTransaction/RpcGenericTransaction.cs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs index e515b826df5..ab8e232ccb7 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Diagnostics; using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Int256; @@ -80,11 +79,14 @@ public Converter RegisterConverter(TxType txType, IToTransaction _converters[(byte)tx.Type!]!.ToTransaction(tx); - public Transaction ToTransactionWithDefaults(RpcGenericTransaction tx, ulong chainId) - => _converters[(byte)tx.Type!]!.ToTransactionWithDefaults(tx, chainId); + public Transaction ToTransactionWithDefaults(RpcGenericTransaction tx) + => _converters[(byte)tx.Type!]!.ToTransactionWithDefaults(tx); } } +/// +/// Intended to be used until proper DI is implemented. +/// public static class RpcGenericTransactionExtensions { public static readonly RpcGenericTransaction.Converter GlobalConverter = new RpcGenericTransaction.Converter() @@ -93,11 +95,19 @@ public static class RpcGenericTransactionExtensions .RegisterConverter(TxType.EIP1559, new RpcEIP1559Transaction.Converter()) .RegisterConverter(TxType.Blob, new RpcBlobTransaction.Converter()); - /// - /// Intended to be used until proper DI is implemented. - /// - public static Transaction ToTransaction(this RpcGenericTransaction rpcTransaction) + public static Transaction ToTransaction(this RpcGenericTransaction rpcTransaction, ulong? chainId = null) { - return GlobalConverter.ToTransaction(rpcTransaction); + var tx = GlobalConverter.ToTransaction(rpcTransaction); + if (chainId.HasValue) + tx.ChainId = chainId.Value; + return tx; + } + + public static Transaction ToTransactionWithDefaults(this RpcGenericTransaction rpcTransaction, ulong? chainId = null) + { + var tx = GlobalConverter.ToTransactionWithDefaults(rpcTransaction); + if (chainId.HasValue) + tx.ChainId = chainId.Value; + return tx; } } From 4d88ba99d342379f2b6b6fd9c42f067b3a86c54e Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 17:32:29 -0300 Subject: [PATCH 73/76] Remove `IRpcTransaction` - Use `RpcNethermindTransaction` directly - Avoid additonal indirection - Have always known fields available --- .../Eth/RpcTransaction/IRpcTransaction.cs | 70 --------------- .../RpcTransaction/RpcLegacyTransaction.cs | 2 - .../RpcNethermindTransaction.cs | 89 ++++++++++++++++++- .../RpcTransaction/RpcTransactionTests.cs | 8 +- .../Rpc/RpcOptimismTransactionTests.cs | 7 +- .../Rpc/RpcOptimismTransaction.cs | 2 - 6 files changed, 94 insertions(+), 84 deletions(-) delete mode 100644 src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs deleted file mode 100644 index f0150fd5a64..00000000000 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/IRpcTransaction.cs +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; -using Nethermind.Core; - -namespace Nethermind.Facade.Eth.RpcTransaction; - - -/// -/// Input: -/// JSON -> (derived by System.Text.JSON) -/// -> ( with a registry of [ => ) -/// Output: -/// -> ( with a registry of [ => ) -/// -> JSON (derived by System.Text.JSON.) -/// -public interface IRpcTransaction -{ - // TODO: Should/can we merge `JsonConverter` and `IFromTransaction`? - public class JsonConverter : JsonConverter - { - private readonly Type[] _transactionTypes = new Type[Transaction.MaxTxType + 1]; - - public JsonConverter RegisterTransactionType(TxType type, Type @class) - { - _transactionTypes[(byte)type] = @class; - return this; - } - - public override IRpcTransaction? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - using var jsonDocument = JsonDocument.ParseValue(ref reader); - - TxType discriminator = default; - if (jsonDocument.RootElement.TryGetProperty("type", out JsonElement typeProperty)) - { - discriminator = (TxType?)typeProperty.Deserialize(typeof(TxType), options) ?? default; - } - - Type concreteTxType = _transactionTypes[(byte)discriminator]; - - return (IRpcTransaction?)jsonDocument.Deserialize(concreteTxType, options); - } - - public override void Write(Utf8JsonWriter writer, IRpcTransaction value, JsonSerializerOptions options) - { - JsonSerializer.Serialize(writer, value, value.GetType(), options); - } - } - - public class TransactionConverter : IFromTransaction - { - private readonly IFromTransaction?[] _converters = new IFromTransaction?[Transaction.MaxTxType + 1]; - - public TransactionConverter RegisterConverter(TxType txType, IFromTransaction converter) - { - _converters[(byte)txType] = converter; - return this; - } - - public IRpcTransaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) - { - var converter = _converters[(byte)tx.Type] ?? throw new ArgumentException("No converter for transaction type"); - return converter.FromTransaction(tx); - } - } -} diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs index e9185a0bd07..24a9c566cfe 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcLegacyTransaction.cs @@ -11,8 +11,6 @@ namespace Nethermind.Facade.Eth.RpcTransaction; public class RpcLegacyTransaction : RpcNethermindTransaction { - public TxType Type { get; set; } - public UInt256 Nonce { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.Never)] diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs index 5a628e78d6b..92a65ae6ae4 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcNethermindTransaction.cs @@ -1,18 +1,31 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Text.Json; using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Int256; namespace Nethermind.Facade.Eth.RpcTransaction; /// -/// Base class for all Nethermind RPC Transactions. -/// All fields are optional since they're not part of the Ethereum JSON RPC spec. +/// Base class for all output Nethermind RPC Transactions. +/// Several fields are non-spec compliant so are marked as optional /// -public abstract class RpcNethermindTransaction : IRpcTransaction +/// +/// Input: +/// JSON -> (derived by System.Text.JSON) +/// -> ( with a registry of [ => ) +/// Output: +/// -> ( with a registry of [ => ) +/// -> JSON (derived by System.Text.JSON.) +/// +public abstract class RpcNethermindTransaction { + public TxType Type { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public Hash256? Hash { get; set; } @@ -32,4 +45,74 @@ public RpcNethermindTransaction(Transaction transaction, int? txIndex = null, Ha BlockHash = blockHash; BlockNumber = blockNumber; } + + public class JsonConverter : JsonConverter + { + private readonly Type[] _transactionTypes = new Type[Transaction.MaxTxType + 1]; + + public JsonConverter RegisterTransactionType(TxType type, Type @class) + { + _transactionTypes[(byte)type] = @class; + return this; + } + + public override RpcNethermindTransaction? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using var jsonDocument = JsonDocument.ParseValue(ref reader); + + TxType discriminator = default; + if (jsonDocument.RootElement.TryGetProperty("type", out JsonElement typeProperty)) + { + discriminator = (TxType?)typeProperty.Deserialize(typeof(TxType), options) ?? default; + } + + Type concreteTxType = _transactionTypes[(byte)discriminator]; + + return (RpcNethermindTransaction?)jsonDocument.Deserialize(concreteTxType, options); + } + + public override void Write(Utf8JsonWriter writer, RpcNethermindTransaction value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, value.GetType(), options); + } + } + + public class TransactionConverter : IFromTransaction + { + private readonly IFromTransaction?[] _converters = new IFromTransaction?[Transaction.MaxTxType + 1]; + + public TransactionConverter RegisterConverter(TxType txType, IFromTransaction converter) + { + _converters[(byte)txType] = converter; + return this; + } + + public RpcNethermindTransaction FromTransaction(Transaction tx, TransactionConverterExtraData extraData) + { + var converter = _converters[(byte)tx.Type] ?? throw new ArgumentException("No converter for transaction type"); + return converter.FromTransaction(tx); + } + } + + #region Refactoring transition code + public static readonly TransactionConverter GlobalConverter = new TransactionConverter() + .RegisterConverter(TxType.Legacy, new RpcLegacyTransaction.Converter()) + .RegisterConverter(TxType.AccessList, new RpcAccessListTransaction.Converter()) + .RegisterConverter(TxType.EIP1559, new RpcEIP1559Transaction.Converter()) + .RegisterConverter(TxType.Blob, new RpcBlobTransaction.Converter()); + // TODO: Add Optimism: + // `.RegisterConverter(TxType.DepositTx, new RpcOptimismTransaction.Converter())` + + public static RpcNethermindTransaction FromTransaction(Transaction transaction, Hash256? blockHash = null, long? blockNumber = null, int? txIndex = null, UInt256? baseFee = null) + { + var extraData = new TransactionConverterExtraData + { + TxIndex = txIndex, + BlockHash = blockHash, + BlockNumber = blockNumber, + BaseFee = baseFee + }; + return GlobalConverter.FromTransaction(transaction, extraData); + } + #endregion } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs index b84008bf4ff..4f097eeac09 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcTransactionTests.cs @@ -15,14 +15,14 @@ namespace Nethermind.JsonRpc.Test.Modules.RpcTransaction; public class RpcTransactionTests { private readonly IJsonSerializer _serializer = new EthereumJsonSerializer([ - new IRpcTransaction.JsonConverter() + new RpcNethermindTransaction.JsonConverter() .RegisterTransactionType(TxType.Legacy, typeof(RpcLegacyTransaction)) .RegisterTransactionType(TxType.AccessList, typeof(RpcAccessListTransaction)) .RegisterTransactionType(TxType.EIP1559, typeof(RpcEIP1559Transaction)) .RegisterTransactionType(TxType.Blob, typeof(RpcBlobTransaction)) ]); - private readonly IFromTransaction _converter = new IRpcTransaction.TransactionConverter() + private readonly IFromTransaction _converter = new RpcNethermindTransaction.TransactionConverter() .RegisterConverter(TxType.Legacy, new RpcLegacyTransaction.Converter()) .RegisterConverter(TxType.AccessList, new RpcAccessListTransaction.Converter()) .RegisterConverter(TxType.EIP1559, new RpcEIP1559Transaction.Converter()) @@ -45,7 +45,7 @@ public void SetUp() [TestCaseSource(nameof(Transactions))] public void Serialized_JSON_satisfies_schema(Transaction transaction) { - IRpcTransaction rpcTransaction = _converter.FromTransaction(transaction); + RpcNethermindTransaction rpcTransaction = _converter.FromTransaction(transaction); string serialized = _serializer.Serialize(rpcTransaction); using var jsonDocument = JsonDocument.Parse(serialized); JsonElement json = jsonDocument.RootElement; @@ -72,7 +72,7 @@ public void Serialized_JSON_satisfies_schema(Transaction transaction) [TestCaseSource(nameof(Transactions))] public void Serialized_JSON_satisfies_Nethermind_fields_schema(Transaction transaction) { - IRpcTransaction rpcTransaction = _converter.FromTransaction(transaction); + RpcNethermindTransaction rpcTransaction = _converter.FromTransaction(transaction); string serialized = _serializer.Serialize(rpcTransaction); using var jsonDocument = JsonDocument.Parse(serialized); JsonElement json = jsonDocument.RootElement; diff --git a/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs b/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs index be8303dbb1a..63d7fde21e0 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/Rpc/RpcOptimismTransactionTests.cs @@ -10,17 +10,18 @@ using FluentAssertions; using Nethermind.Core.Test.Builders; using Nethermind.Core.Crypto; +using Nethermind.Facade.Eth; namespace Nethermind.Optimism.Test.Rpc; public class RpcOptimismTransactionTests { private readonly IJsonSerializer _serializer = new EthereumJsonSerializer([ - new IRpcTransaction.JsonConverter() + new RpcNethermindTransaction.JsonConverter() .RegisterTransactionType(TxType.DepositTx, typeof(RpcOptimismTransaction)) ]); - private readonly IFromTransaction _converter = new IRpcTransaction.TransactionConverter() + private readonly IFromTransaction _converter = new RpcNethermindTransaction.TransactionConverter() .RegisterConverter(TxType.DepositTx, new RpcOptimismTransaction.Converter()); private static TransactionBuilder Build => Core.Test.Builders.Build.A.Transaction.WithType(TxType.DepositTx); @@ -43,7 +44,7 @@ public class RpcOptimismTransactionTests [TestCaseSource(nameof(Transactions))] public void Always_satisfies_schema(Transaction transaction) { - IRpcTransaction rpcTransaction = _converter.FromTransaction(transaction); + RpcNethermindTransaction rpcTransaction = _converter.FromTransaction(transaction); string serialized = _serializer.Serialize(rpcTransaction); using var jsonDocument = JsonDocument.Parse(serialized); JsonElement json = jsonDocument.RootElement; diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs index 250dfb98e70..ceb322c414a 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/RpcOptimismTransaction.cs @@ -15,8 +15,6 @@ namespace Nethermind.Optimism.Rpc; /// public class RpcOptimismTransaction : RpcNethermindTransaction { - public TxType Type { get; set; } - public Hash256 SourceHash { get; set; } public Address From { get; set; } From e5d576d1e47b6fef9939ba1fcf4408d7608c254c Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 17:46:20 -0300 Subject: [PATCH 74/76] Mimic `RpcNethermindTransaction.TransactionConverter` --- .../Eth/RpcTransaction/RpcGenericTransaction.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs index ab8e232ccb7..26522c99e0a 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/RpcTransaction/RpcGenericTransaction.cs @@ -77,10 +77,16 @@ public Converter RegisterConverter(TxType txType, IToTransaction _converters[(byte)tx.Type!]!.ToTransaction(tx); + { + var converter = _converters[(byte)tx.Type] ?? throw new ArgumentException("No converter for transaction type"); + return converter.ToTransaction(tx); + } public Transaction ToTransactionWithDefaults(RpcGenericTransaction tx) - => _converters[(byte)tx.Type!]!.ToTransactionWithDefaults(tx); + { + var converter = _converters[(byte)tx.Type] ?? throw new ArgumentException("No converter for transaction type"); + return converter.ToTransactionWithDefaults(tx); + } } } From 1d13861e72f3421bc5da0699a45a1090d6f02eb9 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 18:00:30 -0300 Subject: [PATCH 75/76] Remove duplicated comment --- .../Modules/RpcTransaction/RpcEIP1559TransactionTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcEIP1559TransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcEIP1559TransactionTests.cs index 7112976a634..86b757fbba7 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcEIP1559TransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcEIP1559TransactionTests.cs @@ -92,9 +92,6 @@ public static void ValidateSchema(JsonElement json) json.GetProperty("maxPriorityFeePerGas").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); json.GetProperty("maxFeePerGas").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); json.GetProperty("gasPrice").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); - // Suprising inconsistency in `FluentAssertions` where `AllSatisfy` fails on empty collections. - // This requires wrapping the assertion in a condition. - // See: https://github.com/fluentassertions/fluentassertions/discussions/2143#discussioncomment-9677309 var accessList = json.GetProperty("accessList").EnumerateArray(); if (accessList.Any()) { From 04133eff28f4b780b0472835676d7d3302cb7ff9 Mon Sep 17 00:00:00 2001 From: Lautaro Emanuel Date: Mon, 16 Sep 2024 18:10:53 -0300 Subject: [PATCH 76/76] Fix whitespace --- .../Modules/RpcTransaction/RpcAccessListTransactionTests.cs | 6 +++--- .../Modules/RpcTransaction/RpcLegacyTransactionTests.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs index 9d65faf1b9a..a4a5278edf5 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcAccessListTransactionTests.cs @@ -19,7 +19,7 @@ public static class RpcAccessListTransactionTests Build.TestObject, Build.WithNonce(UInt256.Zero).TestObject, - Build.WithNonce((UInt256) 123).TestObject, + Build.WithNonce((UInt256)123).TestObject, Build.WithNonce(UInt256.MaxValue).TestObject, Build.WithTo(null).TestObject, @@ -35,7 +35,7 @@ public static class RpcAccessListTransactionTests Build.WithGasLimit(long.MaxValue).TestObject, Build.WithValue(UInt256.Zero).TestObject, - Build.WithValue((UInt256) 123).TestObject, + Build.WithValue((UInt256)123).TestObject, Build.WithValue(UInt256.MaxValue).TestObject, Build.WithData(TestItem.RandomDataA).TestObject, @@ -44,7 +44,7 @@ public static class RpcAccessListTransactionTests Build.WithData(TestItem.RandomDataD).TestObject, Build.WithGasPrice(UInt256.Zero).TestObject, - Build.WithGasPrice((UInt256) 123).TestObject, + Build.WithGasPrice((UInt256)123).TestObject, Build.WithGasPrice(UInt256.MaxValue).TestObject, Build.WithChainId(null).TestObject, diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs index 6179801bec3..300eff44cf2 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/RpcLegacyTransactionTests.cs @@ -17,7 +17,7 @@ public static class RpcLegacyTransactionTests BuildALegacyTransaction.TestObject, BuildALegacyTransaction.WithNonce(UInt256.Zero).TestObject, - BuildALegacyTransaction.WithNonce((UInt256) 123).TestObject, + BuildALegacyTransaction.WithNonce((UInt256)123).TestObject, BuildALegacyTransaction.WithNonce(UInt256.MaxValue).TestObject, BuildALegacyTransaction.WithTo(null).TestObject, @@ -33,7 +33,7 @@ public static class RpcLegacyTransactionTests BuildALegacyTransaction.WithGasLimit(long.MaxValue).TestObject, BuildALegacyTransaction.WithValue(UInt256.Zero).TestObject, - BuildALegacyTransaction.WithValue((UInt256) 123).TestObject, + BuildALegacyTransaction.WithValue((UInt256)123).TestObject, BuildALegacyTransaction.WithValue(UInt256.MaxValue).TestObject, BuildALegacyTransaction.WithData(TestItem.RandomDataA).TestObject, @@ -42,7 +42,7 @@ public static class RpcLegacyTransactionTests BuildALegacyTransaction.WithData(TestItem.RandomDataD).TestObject, BuildALegacyTransaction.WithGasPrice(UInt256.Zero).TestObject, - BuildALegacyTransaction.WithGasPrice((UInt256) 123).TestObject, + BuildALegacyTransaction.WithGasPrice((UInt256)123).TestObject, BuildALegacyTransaction.WithGasPrice(UInt256.MaxValue).TestObject, BuildALegacyTransaction.WithChainId(null).TestObject,