From e348961d5966f680db4b8754b88bd09462deab18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= <43241881+kamilchodola@users.noreply.github.com> Date: Mon, 11 Dec 2023 12:40:12 +0100 Subject: [PATCH] Kch/rlp refactor (#6343) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * RLP refactor/cleanup (#6243) Co-authored-by: Kamil Chodoła * Remove allowLeadingZeros * Fix Keccak -> Hash256 * fix for optimism --------- Co-authored-by: Kamil Chodoła Co-authored-by: Marcin Sobczak --- src/Nethermind/Ethereum.Rlp.Test/RlpTests.cs | 5 +- .../Encoding/HeaderDecoderTests.cs | 24 -- .../V62/Messages/StatusMessageSerializer.cs | 2 +- .../Eip2930/AccessListDecoder.cs | 12 +- .../HeaderDecoder.cs | 16 +- .../ReceiptMessageDecoder.cs | 2 +- .../Nethermind.Serialization.Rlp/Rlp.cs | 205 ++++++++---------- .../Nethermind.Serialization.Rlp/RlpStream.cs | 146 +++++-------- .../SignatureDecoder.cs | 39 ---- .../Nethermind.Serialization.Rlp/TxDecoder.cs | 170 ++++++++------- 10 files changed, 259 insertions(+), 362 deletions(-) delete mode 100644 src/Nethermind/Nethermind.Serialization.Rlp/SignatureDecoder.cs diff --git a/src/Nethermind/Ethereum.Rlp.Test/RlpTests.cs b/src/Nethermind/Ethereum.Rlp.Test/RlpTests.cs index 12284626909..2a1369a1ed6 100644 --- a/src/Nethermind/Ethereum.Rlp.Test/RlpTests.cs +++ b/src/Nethermind/Ethereum.Rlp.Test/RlpTests.cs @@ -8,6 +8,7 @@ using Ethereum.Test.Base; using Nethermind.Core; using Nethermind.Core.Extensions; +using Nethermind.Int256; using Nethermind.Serialization.Rlp; using NUnit.Framework; @@ -116,14 +117,14 @@ public void TestCast() Assert.That(Nethermind.Serialization.Rlp.Rlp.Encode(1UL).Bytes, Is.EqualTo(expected), "ulong bytes"); byte[] expectedNonce = new byte[] { 136, 0, 0, 0, 0, 0, 0, 0, 1 }; - Assert.That(Nethermind.Serialization.Rlp.Rlp.EncodeNonce(1UL).Bytes, Is.EqualTo(expectedNonce), "nonce bytes"); + Assert.That(Nethermind.Serialization.Rlp.Rlp.Encode((UInt256)1UL, HeaderDecoder.NonceLength).Bytes, Is.EqualTo(expectedNonce), "nonce bytes"); } [Test] public void TestNonce() { byte[] expected = { 136, 0, 0, 0, 0, 0, 0, 0, 42 }; - Assert.That(Nethermind.Serialization.Rlp.Rlp.EncodeNonce(42UL).Bytes, Is.EqualTo(expected)); + Assert.That(Nethermind.Serialization.Rlp.Rlp.Encode((UInt256)42UL, HeaderDecoder.NonceLength).Bytes, Is.EqualTo(expected)); } //[Ignore("placeholder for various rlp tests")] diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/HeaderDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/HeaderDecoderTests.cs index a780b344599..380e9a66f9a 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/HeaderDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/HeaderDecoderTests.cs @@ -36,30 +36,6 @@ public void Can_decode(bool hasWithdrawalsRoot) Assert.That(decoded.Hash, Is.EqualTo(header.Hash), "hash"); } - [Test] - public void Can_decode_tricky() - { - BlockHeader header = Build.A.BlockHeader - .WithMixHash(Keccak.Compute("mix_hash")) - .WithTimestamp(2730) - .WithNonce(1000) - .TestObject; - - HeaderDecoder decoder = new(); - Rlp rlp = decoder.Encode(header); - rlp.Bytes[2]++; - string bytesWithAAA = rlp.Bytes.ToHexString(); - bytesWithAAA = bytesWithAAA.Replace("820aaa", "83000aaa"); - - rlp = new Rlp(Bytes.FromHexString(bytesWithAAA)); - - Rlp.ValueDecoderContext decoderContext = new(rlp.Bytes); - BlockHeader? decoded = decoder.Decode(ref decoderContext); - decoded!.Hash = decoded.CalculateHash(); - - Assert.That(decoded.Hash, Is.EqualTo(header.Hash), "hash"); - } - [Test] public void Can_decode_aura() { diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/StatusMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/StatusMessageSerializer.cs index bd2d56069af..f9139463fc9 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/StatusMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Messages/StatusMessageSerializer.cs @@ -78,7 +78,7 @@ private static StatusMessage Deserialize(RlpStream rlpStream) if (rlpStream.Position < rlpStream.Length) { rlpStream.ReadSequenceLength(); - uint forkHash = rlpStream.DecodeUInt(); + uint forkHash = (uint)rlpStream.DecodeUInt256(ForkHashLength - 1); ulong next = rlpStream.DecodeUlong(); ForkId forkId = new(forkHash, next); statusMessage.ForkId = forkId; diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Eip2930/AccessListDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Eip2930/AccessListDecoder.cs index bd3a668cc2a..142326c3431 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/Eip2930/AccessListDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Eip2930/AccessListDecoder.cs @@ -11,6 +11,8 @@ namespace Nethermind.Serialization.Rlp.Eip2930 { public class AccessListDecoder : IRlpStreamDecoder, IRlpValueDecoder { + private const int IndexLength = 32; + /// /// We pay a high code quality tax for the performance optimization on RLP. /// Adding more RLP decoders is costly (time wise) but the path taken saves a lot of allocations and GC. @@ -48,8 +50,8 @@ public class AccessListDecoder : IRlpStreamDecoder, IRlpValueDecode int storagesCheck = rlpStream.Position + storagesLength; while (rlpStream.Position < storagesCheck) { - int storageItemCheck = rlpStream.Position + 33; - UInt256 index = rlpStream.DecodeUInt256(); + int storageItemCheck = rlpStream.Position + IndexLength + 1; + UInt256 index = rlpStream.DecodeUInt256(IndexLength); accessListBuilder.AddStorage(index); if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) { @@ -113,8 +115,8 @@ public class AccessListDecoder : IRlpStreamDecoder, IRlpValueDecode int storagesCheck = decoderContext.Position + storagesLength; while (decoderContext.Position < storagesCheck) { - int storageItemCheck = decoderContext.Position + 33; - UInt256 index = decoderContext.DecodeUInt256(); + int storageItemCheck = decoderContext.Position + IndexLength + 1; + UInt256 index = decoderContext.DecodeUInt256(IndexLength); accessListBuilder.AddStorage(index); if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) { @@ -167,7 +169,7 @@ public void Encode(RlpStream stream, AccessList? item, RlpBehaviors rlpBehaviors foreach (UInt256 index in storageKeys) { // storage indices are encoded as 32 bytes data arrays - stream.Encode(index, 32); + stream.Encode(index, IndexLength); } } } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs index d2c12440067..0cbc8dd0020 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/HeaderDecoder.cs @@ -10,6 +10,8 @@ namespace Nethermind.Serialization.Rlp { public class HeaderDecoder : IRlpValueDecoder, IRlpStreamDecoder { + public const int NonceLength = 8; + public BlockHeader? Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { @@ -57,7 +59,7 @@ public class HeaderDecoder : IRlpValueDecoder, IRlpStreamDecoder, IRlpStreamDecoder= 3 && decoderContext.Position != headerCheck) { - blockHeader.BlobGasUsed = decoderContext.DecodeULong(allowLeadingZeroBytes: false); - blockHeader.ExcessBlobGas = decoderContext.DecodeULong(allowLeadingZeroBytes: false); + blockHeader.BlobGasUsed = decoderContext.DecodeULong(); + blockHeader.ExcessBlobGas = decoderContext.DecodeULong(); } if (itemsRemaining == 4 && decoderContext.Position != headerCheck) @@ -145,7 +147,7 @@ public class HeaderDecoder : IRlpValueDecoder, IRlpStreamDecoder, IRlpStreamDecoder= 3 && rlpStream.Position != headerCheck) { - blockHeader.BlobGasUsed = rlpStream.DecodeUlong(allowLeadingZeroBytes: false); - blockHeader.ExcessBlobGas = rlpStream.DecodeUlong(allowLeadingZeroBytes: false); + blockHeader.BlobGasUsed = rlpStream.DecodeUlong(); + blockHeader.ExcessBlobGas = rlpStream.DecodeUlong(); } if (itemsRemaining == 4 && rlpStream.Position != headerCheck) @@ -219,7 +221,7 @@ public void Encode(RlpStream rlpStream, BlockHeader? header, RlpBehaviors rlpBeh else { rlpStream.Encode(header.MixHash); - rlpStream.EncodeNonce(header.Nonce); + rlpStream.Encode(header.Nonce, NonceLength); } } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptMessageDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptMessageDecoder.cs index 2e26fa352ea..b7da5faaedb 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptMessageDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptMessageDecoder.cs @@ -36,7 +36,7 @@ public TxReceipt Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBeha txReceipt.StatusCode = firstItem[0]; txReceipt.GasUsedTotal = (long)rlpStream.DecodeUBigInt(); } - else if (firstItem.Length >= 1 && firstItem.Length <= 4) + else if (firstItem.Length is >= 1 and <= 4) { txReceipt.GasUsedTotal = (long)firstItem.ToUnsignedBigInteger(); txReceipt.SkipStateAndStatusInRlp = true; diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs index 1974bcc556e..d3d92737c79 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs @@ -284,6 +284,21 @@ public static Rlp Encode(BigInteger bigInteger, int outputLength = -1) return bigInteger == 0 ? OfEmptyByteArray : Encode(bigInteger.ToBigEndianByteArray(outputLength)); } + public static Rlp Encode(in UInt256 value, int length = -1) + { + if (value.IsZero && length == -1) + { + return OfEmptyByteArray; + } + else + { + Span bytes = stackalloc byte[32]; + value.ToBigEndian(bytes); + return Encode(length != -1 ? bytes.Slice(bytes.Length - length, length) : bytes.WithoutLeadingZeros()); + } + } + + public static int Encode(Span buffer, int position, byte[]? input) { if (input is null || input.Length == 0) @@ -354,18 +369,6 @@ public static Rlp Encode(Span input) } } - /// - /// Special case for nonce - /// - /// - /// - public static Rlp EncodeNonce(ulong value) - { - Span bytes = stackalloc byte[8]; - BinaryPrimitives.WriteUInt64BigEndian(bytes, value); - return Encode(bytes); - } - public static Rlp Encode(byte[]? input) { return input is null ? OfEmptyByteArray : Encode(input.AsSpan()); @@ -963,26 +966,34 @@ public void DecodeAddressStructRef(out AddressStructRef address) } } - public UInt256 DecodeUInt256(bool allowLeadingZeroBytes = true) + public UInt256 DecodeUInt256(int length = -1) { Span byteSpan = DecodeByteArraySpan(); if (byteSpan.Length > 32) { - throw new ArgumentException(); + throw new RlpException("UInt256 cannot be longer than 32 bytes"); } - if (!allowLeadingZeroBytes && byteSpan.Length > 1 && byteSpan[0] == 0) + if (length == -1) { - throw new RlpException($"Non-canonical UInt256 (leading zero bytes) at position {Position}"); + if (byteSpan.Length > 1 && byteSpan[0] == 0) + { + throw new RlpException($"Non-canonical UInt256 (leading zero bytes) at position {Position}"); + } } + else if (byteSpan.Length != length) + { + throw new RlpException($"Invalid length at position {Position}"); + } + return new UInt256(byteSpan, true); } - public BigInteger DecodeUBigInt(bool allowLeadingZeroBytes = true) + public BigInteger DecodeUBigInt() { ReadOnlySpan bytes = DecodeByteArraySpan(); - if (!allowLeadingZeroBytes && bytes.Length > 1 && bytes[0] == 0) + if (bytes.Length > 1 && bytes[0] == 0) { throw new RlpException($"Non-canonical UBigInt (leading zero bytes) at position {Position}"); } @@ -1059,23 +1070,18 @@ public bool IsNextItemNull() return Data[Position] == 192; } - public int DecodeInt(bool allowLeadingZeroBytes = true) + public int DecodeInt() { int prefix = ReadByte(); - if (!allowLeadingZeroBytes && prefix == 0) - { - throw new RlpException($"Non-canonical integer (leading zero bytes) at position {Position}"); - } - - if (prefix < 128) - { - return prefix; - } - - if (prefix == 128) + switch (prefix) { - return 0; + case 0: + throw new RlpException($"Non-canonical integer (leading zero bytes) at position {Position}"); + case < 128: + return prefix; + case 128: + return 0; } int length = prefix - 128; @@ -1091,7 +1097,7 @@ public int DecodeInt(bool allowLeadingZeroBytes = true) if (i <= length) { result |= Data[Position + length - i]; - if (!allowLeadingZeroBytes && result == 0) + if (result == 0) { throw new RlpException($"Non-canonical integer (leading zero bytes) at position {Position}"); } @@ -1103,18 +1109,11 @@ public int DecodeInt(bool allowLeadingZeroBytes = true) return result; } - public byte[] DecodeByteArray(bool allowLeadingZeroBytes = true) - { - return DecodeByteArraySpan(allowLeadingZeroBytes).ToArray(); - } + public byte[] DecodeByteArray() => DecodeByteArraySpan().ToArray(); - public Span DecodeByteArraySpan(bool allowLeadingZeroBytes = true) + public Span DecodeByteArraySpan() { int prefix = ReadByte(); - if (!allowLeadingZeroBytes && prefix == 0) - { - throw new RlpException($"Non-canonical ulong (leading zero bytes) at position {Position}"); - } if (prefix < 128) { @@ -1144,7 +1143,7 @@ public Span DecodeByteArraySpan(bool allowLeadingZeroBytes = true) if (lengthOfLength > 4) { // strange but needed to pass tests - seems that spec gives int64 length and tests int32 length - throw new RlpException("Expected length of lenth less or equal 4"); + throw new RlpException("Expected length of length less or equal 4"); } int length = DeserializeLength(lengthOfLength); @@ -1159,7 +1158,7 @@ public Span DecodeByteArraySpan(bool allowLeadingZeroBytes = true) throw new RlpException($"Unexpected prefix value of {prefix} when decoding a byte array."); } - public Memory? DecodeByteArrayMemory(bool allowLeadingZeroBytes = true) + public Memory? DecodeByteArrayMemory() { if (!_sliceMemory) { @@ -1172,53 +1171,45 @@ public Span DecodeByteArraySpan(bool allowLeadingZeroBytes = true) } int prefix = ReadByte(); - if (!allowLeadingZeroBytes && prefix == 0) - { - throw new RlpException($"Non-canonical ulong (leading zero bytes) at position {Position}"); - } - if (prefix < 128) - { - return Memory.Value.Slice(Position - 1, 1); - } - - if (prefix == 128) + switch (prefix) { - return Array.Empty(); - } - - if (prefix <= 183) - { - int length = prefix - 128; - Memory buffer = ReadSlicedMemory(length); - Span asSpan = buffer.Span; - if (length == 1 && asSpan[0] < 128) - { - throw new RlpException($"Unexpected byte value {asSpan[0]}"); - } - - return buffer; - } + case < 128: + return Memory.Value.Slice(Position - 1, 1); + case 128: + return Array.Empty(); + case <= 183: + { + int length = prefix - 128; + Memory buffer = ReadSlicedMemory(length); + Span asSpan = buffer.Span; + if (length == 1 && asSpan[0] < 128) + { + throw new RlpException($"Unexpected byte value {asSpan[0]}"); + } - if (prefix < 192) - { - int lengthOfLength = prefix - 183; - if (lengthOfLength > 4) - { - // strange but needed to pass tests - seems that spec gives int64 length and tests int32 length - throw new RlpException("Expected length of lenth less or equal 4"); - } + return buffer; + } + case < 192: + { + int lengthOfLength = prefix - 183; + if (lengthOfLength > 4) + { + // strange but needed to pass tests - seems that spec gives int64 length and tests int32 length + throw new RlpException("Expected length of lenth less or equal 4"); + } - int length = DeserializeLength(lengthOfLength); - if (length < 56) - { - throw new RlpException("Expected length greater or equal 56 and was {length}"); - } + int length = DeserializeLength(lengthOfLength); + if (length < 56) + { + throw new RlpException("Expected length greater or equal 56 and was {length}"); + } - return ReadSlicedMemory(length); + return ReadSlicedMemory(length); + } + default: + throw new RlpException($"Unexpected prefix value of {prefix} when decoding a byte array."); } - - throw new RlpException($"Unexpected prefix value of {prefix} when decoding a byte array."); } public void SkipItem() @@ -1299,23 +1290,18 @@ public string DecodeString() return Encoding.UTF8.GetString(bytes); } - public long DecodeLong(bool allowLeadingZeroBytes = true) + public long DecodeLong() { int prefix = ReadByte(); - if (!allowLeadingZeroBytes && prefix == 0) - { - throw new RlpException($"Non-canonical long (leading zero bytes) at position {Position}"); - } - - if (prefix < 128) - { - return prefix; - } - - if (prefix == 128) + switch (prefix) { - return 0; + case 0: + throw new RlpException($"Non-canonical long (leading zero bytes) at position {Position}"); + case < 128: + return prefix; + case 128: + return 0; } int length = prefix - 128; @@ -1331,7 +1317,7 @@ public long DecodeLong(bool allowLeadingZeroBytes = true) if (i <= length) { result |= PeekByte(length - i); - if (!allowLeadingZeroBytes && result == 0) + if (result == 0) { throw new RlpException($"Non-canonical long (leading zero bytes) at position {Position}"); } @@ -1343,23 +1329,18 @@ public long DecodeLong(bool allowLeadingZeroBytes = true) return result; } - public ulong DecodeULong(bool allowLeadingZeroBytes = true) + public ulong DecodeULong() { int prefix = ReadByte(); - if (!allowLeadingZeroBytes && prefix == 0) - { - throw new RlpException($"Non-canonical ulong (leading zero bytes) at position {Position}"); - } - - if (prefix < 128) - { - return (ulong)prefix; - } - - if (prefix == 128) + switch (prefix) { - return 0; + case 0: + throw new RlpException($"Non-canonical ulong (leading zero bytes) at position {Position}"); + case < 128: + return (ulong)prefix; + case 128: + return 0; } int length = prefix - 128; @@ -1375,7 +1356,7 @@ public ulong DecodeULong(bool allowLeadingZeroBytes = true) if (i <= length) { result |= PeekByte(length - i); - if (!allowLeadingZeroBytes && result == 0) + if (result == 0) { throw new RlpException($"Non-canonical ulong (leading zero bytes) at position {Position}"); } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs index 4c8a453f1a6..c12a7233e14 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs @@ -382,14 +382,6 @@ public void Encode(int value) Encode((long)value); } - public void Encode(BigInteger bigInteger, int outputLength = -1) - { - Rlp rlp = bigInteger == 0 - ? Rlp.OfEmptyByteArray - : Rlp.Encode(bigInteger.ToBigEndianByteArray(outputLength)); - Write(rlp.Bytes); - } - public void Encode(long value) { if (value == 0L) @@ -496,12 +488,15 @@ public void Encode(long value) return; } - Encode(new BigInteger(value), 8); + Encode(value, 8); } - public void EncodeNonce(ulong value) + private void Encode(BigInteger bigInteger, int outputLength = -1) { - Encode((UInt256)value, 8); + Rlp rlp = bigInteger == 0 + ? Rlp.OfEmptyByteArray + : Rlp.Encode(bigInteger.ToBigEndianByteArray(outputLength)); + Write(rlp.Bytes); } public void Encode(ulong value) @@ -519,14 +514,7 @@ public void Encode(in UInt256 value, int length = -1) { Span bytes = stackalloc byte[32]; value.ToBigEndian(bytes); - if (length != -1) - { - Encode(bytes.Slice(bytes.Length - length, length)); - } - else - { - Encode(bytes.WithoutLeadingZeros()); - } + Encode(length != -1 ? bytes.Slice(bytes.Length - length, length) : bytes.WithoutLeadingZeros()); } } @@ -972,11 +960,11 @@ public bool DecodeValueKeccak(out ValueHash256 keccak) return new Address(buffer); } - public UInt256 DecodeUInt256(bool allowLeadingZeroBytes = true) + public UInt256 DecodeUInt256(int length = -1) { byte byteValue = PeekByte(); - if (!allowLeadingZeroBytes && byteValue == 0) + if (byteValue == 0) { throw new RlpException($"Non-canonical UInt256 (leading zero bytes) at position {Position}"); } @@ -991,32 +979,28 @@ public UInt256 DecodeUInt256(bool allowLeadingZeroBytes = true) if (byteSpan.Length > 32) { - throw new ArgumentException(); + throw new RlpException("UInt256 cannot be longer than 32 bytes"); } - if (!allowLeadingZeroBytes && byteSpan.Length > 1 && byteSpan[0] == 0) + if (length == -1) { - throw new RlpException($"Non-canonical UInt256 (leading zero bytes) at position {Position}"); + if (byteSpan.Length > 1 && byteSpan[0] == 0) + { + throw new RlpException($"Non-canonical UInt256 (leading zero bytes) at position {Position}"); + } } - - return new UInt256(byteSpan, true); - } - - public UInt256? DecodeNullableUInt256(bool allowLeadingZeroBytes = true) - { - if (PeekByte() == 0) + else if (byteSpan.Length != length) { - Position++; - return null; + throw new RlpException($"Invalid length at position {Position}"); } - return DecodeUInt256(allowLeadingZeroBytes); + return new UInt256(byteSpan, true); } - public BigInteger DecodeUBigInt(bool allowLeadingZeroBytes = true) + public BigInteger DecodeUBigInt() { ReadOnlySpan bytes = DecodeByteArraySpan(); - if (!allowLeadingZeroBytes && bytes.Length > 1 && bytes[0] == 0) + if (bytes.Length > 1 && bytes[0] == 0) { throw new RlpException($"Non-canonical UBigInt (leading zero bytes) at position {Position}"); } @@ -1045,7 +1029,7 @@ public BigInteger DecodeUBigInt(bool allowLeadingZeroBytes = true) if (bloomBytes.Length != 256) { - throw new InvalidOperationException("Incorrect bloom RLP"); + throw new RlpException("Incorrect bloom RLP"); } return bloomBytes.SequenceEqual(Bloom.Empty.Bytes) ? Bloom.Empty : new Bloom(bloomBytes.ToArray()); @@ -1167,23 +1151,18 @@ public byte DecodeByte() : bytes[1]; } - public int DecodeInt(bool allowLeadingZeroBytes = true) + public int DecodeInt() { int prefix = ReadByte(); - if (!allowLeadingZeroBytes && prefix == 0) - { - throw new RlpException($"Non-canonical integer (leading zero bytes) at position {Position}"); - } - - if (prefix < 128) - { - return prefix; - } - - if (prefix == 128) + switch (prefix) { - return 0; + case 0: + throw new RlpException($"Non-canonical integer (leading zero bytes) at position {Position}"); + case < 128: + return prefix; + case 128: + return 0; } int length = prefix - 128; @@ -1199,7 +1178,7 @@ public int DecodeInt(bool allowLeadingZeroBytes = true) if (i <= length) { result |= PeekByte(length - i); - if (!allowLeadingZeroBytes && result == 0) + if (result == 0) { throw new RlpException($"Non-canonical integer (leading zero bytes) at position {Position}"); } @@ -1211,33 +1190,28 @@ public int DecodeInt(bool allowLeadingZeroBytes = true) return result; } - public uint DecodeUInt(bool allowLeadingZeroBytes = true) + public uint DecodeUInt() { ReadOnlySpan bytes = DecodeByteArraySpan(); - if (!allowLeadingZeroBytes && bytes.Length > 1 && bytes[0] == 0) + if (bytes.Length > 1 && bytes[0] == 0) { throw new RlpException($"Non-canonical UInt (leading zero bytes) at position {Position}"); } return bytes.Length == 0 ? 0 : bytes.ReadEthUInt32(); } - public long DecodeLong(bool allowLeadingZeroBytes = true) + public long DecodeLong() { int prefix = ReadByte(); - if (!allowLeadingZeroBytes && prefix == 0) + switch (prefix) { - throw new RlpException($"Non-canonical long (leading zero bytes) at position {Position}"); - } - - if (prefix < 128) - { - return prefix; - } - - if (prefix == 128) - { - return 0; + case 0: + throw new RlpException($"Non-canonical long (leading zero bytes) at position {Position}"); + case < 128: + return prefix; + case 128: + return 0; } int length = prefix - 128; @@ -1253,7 +1227,7 @@ public long DecodeLong(bool allowLeadingZeroBytes = true) if (i <= length) { result |= PeekByte(length - i); - if (!allowLeadingZeroBytes && result == 0) + if (result == 0) { throw new RlpException($"Non-canonical long (leading zero bytes) at position {Position}"); } @@ -1265,23 +1239,18 @@ public long DecodeLong(bool allowLeadingZeroBytes = true) return result; } - public ulong DecodeULong(bool allowLeadingZeroBytes = true) + public ulong DecodeULong() { int prefix = ReadByte(); - if (!allowLeadingZeroBytes && prefix == 0) - { - throw new RlpException($"Non-canonical ulong (leading zero bytes) at position {Position}"); - } - - if (prefix < 128) - { - return (ulong)prefix; - } - - if (prefix == 128) + switch (prefix) { - return 0; + case 0: + throw new RlpException($"Non-canonical ulong (leading zero bytes) at position {Position}"); + case < 128: + return (ulong)prefix; + case 128: + return 0; } int length = prefix - 128; @@ -1297,7 +1266,7 @@ public ulong DecodeULong(bool allowLeadingZeroBytes = true) if (i <= length) { result |= PeekByte(length - i); - if (!allowLeadingZeroBytes && result == 0) + if (result == 0) { throw new RlpException($"Non-canonical ulong (leading zero bytes) at position {Position}"); } @@ -1309,30 +1278,23 @@ public ulong DecodeULong(bool allowLeadingZeroBytes = true) return result; } - public ulong DecodeUlong(bool allowLeadingZeroBytes = true) + public ulong DecodeUlong() { ReadOnlySpan bytes = DecodeByteArraySpan(); - if (!allowLeadingZeroBytes && bytes.Length > 1 && bytes[0] == 0) + if (bytes.Length > 1 && bytes[0] == 0) { throw new RlpException($"Non-canonical ulong (leading zero bytes) at position {Position}"); } return bytes.Length == 0 ? 0L : bytes.ReadEthUInt64(); } - public byte[] DecodeByteArray(bool allowLeadingZeroBytes = true) - { - return DecodeByteArraySpan(allowLeadingZeroBytes).ToArray(); - } + public byte[] DecodeByteArray() => DecodeByteArraySpan().ToArray(); - public ReadOnlySpan DecodeByteArraySpan(bool allowLeadingZeroBytes = true) + public ReadOnlySpan DecodeByteArraySpan() { int prefix = ReadByte(); if (prefix == 0) { - if (!allowLeadingZeroBytes) - { - throw new RlpException($"Non-canonical ulong (leading zero bytes) at position {Position}"); - } return new byte[] { 0 }; } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/SignatureDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/SignatureDecoder.cs deleted file mode 100644 index ceb2beb4395..00000000000 --- a/src/Nethermind/Nethermind.Serialization.Rlp/SignatureDecoder.cs +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; - -namespace Nethermind.Serialization.Rlp -{ - public static class SignatureDecoder - { - public static Signature DecodeSignature(RlpStream rlpStream) - { - ReadOnlySpan vBytes = rlpStream.DecodeByteArraySpan(); - ReadOnlySpan rBytes = rlpStream.DecodeByteArraySpan(); - ReadOnlySpan sBytes = rlpStream.DecodeByteArraySpan(); - - if (vBytes[0] == 0 || rBytes[0] == 0 || sBytes[0] == 0) - { - throw new RlpException("VRS starting with 0"); - } - - if (rBytes.Length > 32 || sBytes.Length > 32) - { - throw new RlpException("R and S lengths expected to be less or equal 32"); - } - - ulong v = vBytes.ReadEthUInt64(); - - if (rBytes.SequenceEqual(Bytes.Zero32) && sBytes.SequenceEqual(Bytes.Zero32)) - { - throw new RlpException("Both 'r' and 's' are zero when decoding a transaction."); - } - - Signature signature = new(rBytes, sBytes, v); - return signature; - } - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs index 4ff29b2c28e..3c67f6bd0f0 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using FastEnumUtility; using Microsoft.Extensions.ObjectPool; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -39,7 +40,7 @@ public class TxDecoder : where T : Transaction, new() { private readonly AccessListDecoder _accessListDecoder = new(); - private bool _lazyHash; + private readonly bool _lazyHash; protected TxDecoder(bool lazyHash = true) { @@ -59,27 +60,10 @@ protected virtual T NewTx() return null; } - Span transactionSequence = rlpStream.PeekNextItem(); + Span transactionSequence = DecodeTxTypeAndGetSequence(rlpStream, rlpBehaviors, out TxType txType); T transaction = NewTx(); - if ((rlpBehaviors & RlpBehaviors.SkipTypedWrapping) == RlpBehaviors.SkipTypedWrapping) - { - byte firstByte = rlpStream.PeekByte(); - if (firstByte <= 0x7f) // it is typed transactions - { - transactionSequence = rlpStream.Peek(rlpStream.Length); - transaction.Type = (TxType)rlpStream.ReadByte(); - } - } - else - { - if (!rlpStream.IsSequenceNext()) - { - (int _, int contentLength) = rlpStream.ReadPrefixAndContentLength(); - transactionSequence = rlpStream.Peek(contentLength); - transaction.Type = (TxType)rlpStream.ReadByte(); - } - } + transaction.Type = txType; int positionAfterNetworkWrapper = 0; if ((rlpBehaviors & RlpBehaviors.InMempoolForm) == RlpBehaviors.InMempoolForm && transaction.MayHaveNetworkForm) @@ -152,6 +136,38 @@ protected virtual T NewTx() return transaction; } + private static Span DecodeTxTypeAndGetSequence(RlpStream rlpStream, RlpBehaviors rlpBehaviors, out TxType txType) + { + static Span DecodeTxType(RlpStream rlpStream, int length, out TxType txType) + { + Span sequence = rlpStream.Peek(length); + txType = (TxType)rlpStream.ReadByte(); + return txType == TxType.Legacy + ? throw new RlpException("Legacy transactions are not allowed in EIP-2718 Typed Transaction Envelope.") + : sequence; + } + + Span transactionSequence = rlpStream.PeekNextItem(); + txType = TxType.Legacy; + if ((rlpBehaviors & RlpBehaviors.SkipTypedWrapping) == RlpBehaviors.SkipTypedWrapping) + { + byte firstByte = rlpStream.PeekByte(); + if (firstByte <= 0x7f) // it is typed transactions + { + transactionSequence = DecodeTxType(rlpStream, rlpStream.Length, out txType); + } + } + else if (!rlpStream.IsSequenceNext()) + { + transactionSequence = DecodeTxType(rlpStream, rlpStream.ReadPrefixAndContentLength().ContentLength, out txType); + } + + + return transactionSequence; + + + } + private static Hash256 CalculateHashForNetworkPayloadForm(TxType type, Span transactionSequence) { KeccakHash hash = KeccakHash.Create(); @@ -164,51 +180,51 @@ private static Hash256 CalculateHashForNetworkPayloadForm(TxType type, Span vBytes = rlpStream.DecodeByteArraySpan(allowLeadingZeroBytes: false); + ulong v = rlpStream.DecodeULong(); ReadOnlySpan rBytes = rlpStream.DecodeByteArraySpan(); ReadOnlySpan sBytes = rlpStream.DecodeByteArraySpan(); - if (!(vBytes.IsEmpty && rBytes.IsEmpty && sBytes.IsEmpty)) + if (!(v == 0 && rBytes.IsEmpty && sBytes.IsEmpty)) { - ApplySignature(transaction, vBytes, rBytes, sBytes, rlpBehaviors); + ApplySignature(transaction, v, rBytes, sBytes, rlpBehaviors); } } @@ -519,18 +532,18 @@ private static void DecodeSignature( RlpBehaviors rlpBehaviors, T transaction) { - ReadOnlySpan vBytes = decoderContext.DecodeByteArraySpan(allowLeadingZeroBytes: false); + ulong v = decoderContext.DecodeULong(); ReadOnlySpan rBytes = decoderContext.DecodeByteArraySpan(); ReadOnlySpan sBytes = decoderContext.DecodeByteArraySpan(); - if (!(vBytes.IsEmpty && rBytes.IsEmpty && sBytes.IsEmpty)) + if (!(v == 0 && rBytes.IsEmpty && sBytes.IsEmpty)) { - ApplySignature(transaction, vBytes, rBytes, sBytes, rlpBehaviors); + ApplySignature(transaction, v, rBytes, sBytes, rlpBehaviors); } } private static void ApplySignature( T transaction, - ReadOnlySpan vBytes, + ulong v, ReadOnlySpan rBytes, ReadOnlySpan sBytes, RlpBehaviors rlpBehaviors) @@ -538,7 +551,7 @@ private static void ApplySignature( bool allowUnsigned = (rlpBehaviors & RlpBehaviors.AllowUnsigned) == RlpBehaviors.AllowUnsigned; bool isSignatureOk = true; string signatureError = null; - if (vBytes == null || rBytes == null || sBytes == null) + if (rBytes == null || sBytes == null) { isSignatureOk = false; signatureError = "VRS null when decoding Transaction"; @@ -566,8 +579,7 @@ private static void ApplySignature( if (isSignatureOk) { - ulong v = vBytes.ReadEthUInt64(); - if (transaction.Type != TxType.Legacy && v < Signature.VOffset) + if (transaction.Type != TxType.Legacy) { v += Signature.VOffset; }