diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/ByteArrayExtensions.cs b/src/Nethermind/Nethermind.Serialization.Rlp/ByteArrayExtensions.cs index 62a7078448c..5dbbe5671cf 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/ByteArrayExtensions.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/ByteArrayExtensions.cs @@ -18,6 +18,16 @@ public static RlpStream AsRlpStream(in this CappedArray bytes) return new(in bytes.IsNotNull ? ref bytes : ref CappedArray.Empty); } + public static RlpFactory AsRlpFactory(this byte[]? bytes) + { + return new(bytes ?? Array.Empty()); + } + + public static RlpFactory AsRlpFactory(in this CappedArray bytes) + { + return new(in bytes.IsNotNull ? ref bytes : ref CappedArray.Empty); + } + public static Rlp.ValueDecoderContext AsRlpValueContext(this byte[]? bytes) { return new(bytes ?? Array.Empty()); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpFactory.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpFactory.cs new file mode 100644 index 00000000000..b55c16051e8 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpFactory.cs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Buffers; + +namespace Nethermind.Serialization.Rlp +{ + public class RlpFactory + { + public long MemorySize => MemorySizes.SmallObjectOverhead + + MemorySizes.Align(MemorySizes.ArrayOverhead + _data.Length) + + MemorySizes.Align(sizeof(int)); + + private readonly CappedArray _data; + + public ref readonly CappedArray Data => ref _data; + + public RlpFactory(in CappedArray data) + { + _data = data; + } + + public ValueRlpStream GetRlpStream() + { + return new(in _data); + } + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs index 6d945b8f904..12ca0b8c223 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs @@ -28,6 +28,7 @@ public class RlpStream private static readonly LogEntryDecoder _logEntryDecoder = LogEntryDecoder.Instance; private readonly CappedArray _data; + private int _position = 0; protected RlpStream() { @@ -52,11 +53,6 @@ public RlpStream(in CappedArray data) _data = data; } - public RlpStream Clone() - { - return new(_data); - } - public void Encode(Block value) { _blockDecoder.Encode(this, value); @@ -188,8 +184,6 @@ public virtual void Write(IReadOnlyList bytesToWrite) public ref readonly CappedArray Data => ref _data; - private int _position = 0; - public virtual int Position { get diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/ValueRlpStream.cs b/src/Nethermind/Nethermind.Serialization.Rlp/ValueRlpStream.cs new file mode 100644 index 00000000000..268940224dc --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/ValueRlpStream.cs @@ -0,0 +1,415 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers.Binary; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Nethermind.Core.Buffers; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; + +namespace Nethermind.Serialization.Rlp; + +public ref struct ValueRlpStream(in CappedArray data) +{ + private readonly ref readonly CappedArray _data = ref data; + private int _position = 0; + + internal string Description => + Data.AsSpan(0, Math.Min(Rlp.DebugMessageContentLength, Length)).ToHexString() ?? "0x"; + + public readonly ref readonly CappedArray Data => ref _data; + + public int Position + { + readonly get => _position; + set => _position = value; + } + + public readonly bool IsNull => Unsafe.IsNullRef(ref Unsafe.AsRef(in _data)); + public readonly bool IsNotNull => !IsNull; + public readonly int Length => Data.Length; + + public int PeekNumberOfItemsRemaining(int? beforePosition = null, int maxSearch = int.MaxValue) + { + int positionStored = Position; + int numberOfItems = 0; + while (Position < (beforePosition ?? Length)) + { + int prefix = ReadByte(); + if (prefix <= 128) + { + } + else if (prefix <= 183) + { + int length = prefix - 128; + SkipBytes(length); + } + else if (prefix < 192) + { + int lengthOfLength = prefix - 183; + int length = DeserializeLength(lengthOfLength); + if (length < 56) + { + throw new RlpException("Expected length greater or equal 56 and was {length}"); + } + + SkipBytes(length); + } + else + { + Position--; + int sequenceLength = ReadSequenceLength(); + SkipBytes(sequenceLength); + } + + numberOfItems++; + if (numberOfItems >= maxSearch) + { + break; + } + } + + Position = positionStored; + return numberOfItems; + } + + public void SkipLength() + { + SkipBytes(PeekPrefixAndContentLength().PrefixLength); + } + + public int PeekNextRlpLength() + { + (int a, int b) = PeekPrefixAndContentLength(); + return a + b; + } + + public (int PrefixLength, int ContentLength) PeekPrefixAndContentLength() + { + (int prefixLength, int contentLength) result; + int prefix = PeekByte(); + if (prefix <= 128) + { + result = (0, 1); + } + else if (prefix <= 183) + { + result = (1, prefix - 128); + } + else 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 length less or equal 4"); + } + + int length = PeekDeserializeLength(1, lengthOfLength); + if (length < 56) + { + throw new RlpException($"Expected length greater or equal 56 and was {length}"); + } + + result = (lengthOfLength + 1, length); + } + else if (prefix <= 247) + { + result = (1, prefix - 192); + } + else + { + int lengthOfContentLength = prefix - 247; + int contentLength = PeekDeserializeLength(1, lengthOfContentLength); + if (contentLength < 56) + { + throw new RlpException($"Expected length greater or equal 56 and got {contentLength}"); + } + + + result = (lengthOfContentLength + 1, contentLength); + } + + return result; + } + + public int ReadSequenceLength() + { + int prefix = ReadByte(); + if (prefix < 192) + { + throw new RlpException( + $"Expected a sequence prefix to be in the range of <192, 255> and got {prefix} at position {Position} in the message of length {Length} starting with {Description}"); + } + + if (prefix <= 247) + { + return prefix - 192; + } + + int lengthOfContentLength = prefix - 247; + int contentLength = DeserializeLength(lengthOfContentLength); + if (contentLength < 56) + { + throw new RlpException($"Expected length greater or equal 56 and got {contentLength}"); + } + + return contentLength; + } + + private int DeserializeLength(int lengthOfLength) + { + if (lengthOfLength == 0 || (uint)lengthOfLength > 4) + { + ThrowArgumentOutOfRangeException(lengthOfLength); + } + + // Will use Unsafe.ReadUnaligned as we know the length of the span is same + // as what we asked for and then explicitly check lengths, so can skip the + // additional bounds checking from BinaryPrimitives.ReadUInt16BigEndian etc + ref byte firstElement = ref MemoryMarshal.GetReference(Read(lengthOfLength)); + + return DeserializeLengthRef(ref firstElement, lengthOfLength); + } + + private int PeekDeserializeLength(int offset, int lengthOfLength) + { + if (lengthOfLength == 0 || (uint)lengthOfLength > 4) + { + ThrowArgumentOutOfRangeException(lengthOfLength); + } + + // Will use Unsafe.ReadUnaligned as we know the length of the span is same + // as what we asked for and then explicitly check lengths, so can skip the + // additional bounds checking from BinaryPrimitives.ReadUInt16BigEndian etc + ref byte firstElement = ref MemoryMarshal.GetReference(Peek(offset, lengthOfLength)); + + return DeserializeLengthRef(ref firstElement, lengthOfLength); + } + + private static int DeserializeLengthRef(ref byte firstElement, int lengthOfLength) + { + int result = firstElement; + if (result == 0) + { + ThrowInvalidData(); + } + + if (lengthOfLength == 1) + { + // Already read above + // result = span[0]; + } + else if (lengthOfLength == 2) + { + if (BitConverter.IsLittleEndian) + { + result = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref firstElement)); + } + else + { + result = Unsafe.ReadUnaligned(ref firstElement); + } + } + else if (lengthOfLength == 3) + { + if (BitConverter.IsLittleEndian) + { + result = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref Unsafe.Add(ref firstElement, 1))) + | (result << 16); + } + else + { + result = Unsafe.ReadUnaligned(ref Unsafe.Add(ref firstElement, 1)) + | (result << 16); + } + } + else + { + if (BitConverter.IsLittleEndian) + { + result = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref firstElement)); + } + else + { + result = Unsafe.ReadUnaligned(ref firstElement); + } + } + + return result; + + [DoesNotReturn] + static void ThrowInvalidData() + { + throw new RlpException("Length starts with 0"); + } + } + + [DoesNotReturn] + static void ThrowArgumentOutOfRangeException(int lengthOfLength) + { + throw new InvalidOperationException($"Invalid length of length = {lengthOfLength}"); + } + + public byte ReadByte() + { + return Data![_position++]; + } + + public readonly byte PeekByte() + { + return Data![_position]; + } + + private void SkipBytes(int length) + { + _position += length; + } + + public Span Read(int length) + { + Span data = Data.AsSpan(_position, length); + _position += length; + return data; + } + + public Hash256? DecodeKeccak() + { + int prefix = ReadByte(); + if (prefix == 128) + { + return null; + } + + if (prefix != 128 + 32) + { + throw new RlpException( + $"Unexpected prefix of {prefix} when decoding {nameof(Hash256)} at position {Position} in the message of length {Length} starting with {Description}"); + } + + Span keccakSpan = Read(32); + if (keccakSpan.SequenceEqual(Keccak.OfAnEmptyString.Bytes)) + { + return Keccak.OfAnEmptyString; + } + + if (keccakSpan.SequenceEqual(Keccak.EmptyTreeHash.Bytes)) + { + return Keccak.EmptyTreeHash; + } + + return new Hash256(keccakSpan); + } + + public bool DecodeValueKeccak(out ValueHash256 keccak) + { + Unsafe.SkipInit(out keccak); + int prefix = ReadByte(); + if (prefix == 128) + { + return false; + } + + if (prefix != 128 + 32) + { + throw new RlpException( + $"Unexpected prefix of {prefix} when decoding {nameof(Hash256)} at position {Position} in the message of length {Length} starting with {Description}"); + } + + Span keccakSpan = Read(32); + keccak = new ValueHash256(keccakSpan); + return true; + } + + public Span PeekNextItem() + { + int length = PeekNextRlpLength(); + return Peek(length); + } + + public Span Peek(int length) + { + return Peek(0, length); + } + + public readonly Span Peek(int offset, int length) + { + return Data.AsSpan(_position + offset, length); + } + + public byte[] DecodeByteArray() => DecodeByteArraySpan().ToArray(); + + public ReadOnlySpan DecodeByteArraySpan() + { + int prefix = ReadByte(); + if (prefix == 0) + { + return new byte[] { 0 }; + } + + if (prefix < 128) + { + return new[] { (byte)prefix }; + } + + if (prefix == 128) + { + return Array.Empty(); + } + + if (prefix <= 183) + { + int length = prefix - 128; + Span buffer = Read(length); + if (length == 1 && buffer[0] < 128) + { + throw new RlpException($"Unexpected byte value {buffer[0]}"); + } + + return buffer; + } + + 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 length less or equal 4"); + } + + int length = DeserializeLength(lengthOfLength); + if (length < 56) + { + throw new RlpException($"Expected length greater or equal 56 and was {length}"); + } + + return Read(length); + } + + throw new RlpException($"Unexpected prefix value of {prefix} when decoding a byte array."); + } + + public void SkipItem() + { + (int prefix, int content) = PeekPrefixAndContentLength(); + SkipBytes(prefix + content); + } + + public void Reset() + { + Position = 0; + } + + private const byte EmptyArrayByte = 128; + + private const byte EmptySequenceByte = 192; + + public override readonly string ToString() + { + return $"[{nameof(RlpStream)}|{Position}/{Length}]"; + } +} diff --git a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs index 786cb236c4f..2607b528127 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs @@ -673,9 +673,12 @@ public void ReadOnly_store_returns_copies(bool pruning) readOnlyNode.Should().NotBe(originalNode); readOnlyNode.Should().BeEquivalentTo(originalNode, eq => eq.Including(t => t.Keccak) - .Including(t => t.RlpStream) .Including(t => t.NodeType)); + var origRlp = originalNode.FullRlp; + var readOnlyRlp = readOnlyNode.FullRlp; + readOnlyRlp.Should().BeEquivalentTo(origRlp); + readOnlyNode.Key?.ToString().Should().Be(originalNode.Key?.ToString()); } } diff --git a/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs b/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs index 79f1bf471f2..6b532aa6f62 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs @@ -177,34 +177,36 @@ private static int GetChildrenRlpLength(ITrieNodeResolver tree, TrieNode item, I { int totalLength = 0; item.InitData(); - RlpStream? rlpStream = item.RlpStream; - item.SeekChild(rlpStream, 0); + ValueRlpStream rlpStream = item.RlpStream; + item.SeekChild(ref rlpStream, 0); for (int i = 0; i < BranchesCount; i++) { - if (rlpStream is not null && item._data![i] is null) + bool isRlp = rlpStream.IsNotNull; + object data = item._data[i]; + if (rlpStream.IsNotNull && data is null) { (int prefixLength, int contentLength) = rlpStream.PeekPrefixAndContentLength(); totalLength += prefixLength + contentLength; } else { - if (ReferenceEquals(item._data![i], _nullNode) || item._data[i] is null) + if (ReferenceEquals(data, _nullNode) || data is null) { totalLength++; } - else if (item._data[i] is Hash256) + else if (data is Hash256) { totalLength += Rlp.LengthOfKeccakRlp; } else { - TrieNode childNode = (TrieNode)item._data[i]; + TrieNode childNode = (TrieNode)data; childNode!.ResolveKey(tree, false, bufferPool: bufferPool); totalLength += childNode.Keccak is null ? childNode.FullRlp.Length : Rlp.LengthOfKeccakRlp; } } - rlpStream?.SkipItem(); + if (isRlp) rlpStream.SkipItem(); } return totalLength; @@ -214,11 +216,13 @@ private static void WriteChildrenRlp(ITrieNodeResolver tree, TrieNode item, Span { int position = 0; item.InitData(); - RlpStream? rlpStream = item.RlpStream; - item.SeekChild(rlpStream, 0); + ValueRlpStream rlpStream = item.RlpStream; + item.SeekChild(ref rlpStream, 0); for (int i = 0; i < BranchesCount; i++) { - if (rlpStream is not null && item._data![i] is null) + bool isRlp = rlpStream.IsNotNull; + object data = item._data[i]; + if (isRlp && data is null) { int length = rlpStream.PeekNextRlpLength(); Span nextItem = rlpStream.Data.AsSpan(rlpStream.Position, length); @@ -228,18 +232,18 @@ private static void WriteChildrenRlp(ITrieNodeResolver tree, TrieNode item, Span } else { - rlpStream?.SkipItem(); - if (ReferenceEquals(item._data![i], _nullNode) || item._data[i] is null) + if (isRlp) rlpStream.SkipItem(); + if (ReferenceEquals(data, _nullNode) || data is null) { destination[position++] = 128; } - else if (item._data[i] is Hash256) + else if (data is Hash256) { - position = Rlp.Encode(destination, position, (item._data[i] as Hash256)!.Bytes); + position = Rlp.Encode(destination, position, (data as Hash256)!.Bytes); } else { - TrieNode childNode = (TrieNode)item._data[i]; + TrieNode childNode = (TrieNode)data; childNode!.ResolveKey(tree, false, bufferPool: bufferPool); if (childNode.Keccak is null) { diff --git a/src/Nethermind/Nethermind.Trie/TrieNode.cs b/src/Nethermind/Nethermind.Trie/TrieNode.cs index b741a3733fc..8a5d8e249e4 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNode.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNode.cs @@ -35,7 +35,7 @@ public partial class TrieNode private static readonly TrieNodeDecoder _nodeDecoder = new(); private static readonly AccountDecoder _accountDecoder = new(); private static Action _markPersisted => tn => tn.IsPersisted = true; - private RlpStream? _rlpStream; + private RlpFactory? _rlp; private object?[]? _data; private int _isDirty; @@ -61,12 +61,23 @@ public ref readonly CappedArray FullRlp { get { - RlpStream rlpStream = _rlpStream; - return ref rlpStream is not null ? ref rlpStream.Data : ref CappedArray.Null; + RlpFactory rlp = _rlp; + return ref rlp is not null ? ref rlp.Data : ref CappedArray.Null; } } - public RlpStream? RlpStream => _rlpStream?.Clone(); + public ValueRlpStream RlpStream + { + get + { + RlpFactory rlp = _rlp; + if (rlp is null) + { + return default; + } + return rlp.GetRlpStream(); + } + } public NodeType NodeType { get; private set; } @@ -137,17 +148,17 @@ public CappedArray Value obj = _data![BranchesCount]; if (obj is null) { - RlpStream rlpStream = _rlpStream; - if (rlpStream is null) + RlpFactory rlp = _rlp; + if (rlp is null) { _data[BranchesCount] = Array.Empty(); return CappedArray.Empty; } else { - RlpStream rlpStreamCopy = rlpStream.Clone(); - SeekChild(rlpStreamCopy, BranchesCount); - byte[]? bArr = rlpStreamCopy.DecodeByteArray(); + ValueRlpStream rlpStream = rlp.GetRlpStream(); + SeekChild(ref rlpStream, BranchesCount); + byte[]? bArr = rlpStream.DecodeByteArray(); _data![BranchesCount] = bArr; return new CappedArray(bArr); } @@ -247,7 +258,7 @@ public TrieNode(NodeType nodeType, in CappedArray rlp, bool isDirty = fals NodeType = nodeType; _isDirty = isDirty ? 1 : 0; - _rlpStream = rlp.AsRlpStream(); + _rlp = rlp.AsRlpFactory(); } public TrieNode(NodeType nodeType, byte[]? rlp, bool isDirty = false) : this(nodeType, new CappedArray(rlp), isDirty) @@ -313,8 +324,8 @@ private void ResolveUnknownNode(ITrieNodeResolver tree, ReadFlags readFlags = Re { try { - RlpStream rplStream = _rlpStream; - if (rplStream is null) + RlpFactory rlp = _rlp; + if (rlp is null) { Hash256 keccak = Keccak; if (keccak is null) @@ -329,11 +340,11 @@ private void ResolveUnknownNode(ITrieNodeResolver tree, ReadFlags readFlags = Re ThrowNullRlp(); } - _rlpStream = rplStream = fullRlp.AsRlpStream(); + _rlp = rlp = fullRlp.AsRlpFactory(); IsPersisted = true; } - if (!DecodeRlp(rplStream.Clone(), bufferPool, out int numberOfItems)) + if (!DecodeRlp(rlp.GetRlpStream(), bufferPool, out int numberOfItems)) { ThrowUnexpectedNumberOfItems(numberOfItems); } @@ -379,7 +390,7 @@ public bool TryResolveNode(ITrieNodeResolver tree, ReadFlags readFlags = ReadFla { try { - RlpStream rplStream = _rlpStream; + RlpFactory rplStream = _rlp; if (NodeType == NodeType.Unknown) { if (rplStream is null) @@ -397,7 +408,7 @@ public bool TryResolveNode(ITrieNodeResolver tree, ReadFlags readFlags = ReadFla return false; } - _rlpStream = rplStream = fullRlp.AsRlpStream(); + _rlp = rplStream = fullRlp.AsRlpFactory(); IsPersisted = true; } } @@ -406,7 +417,7 @@ public bool TryResolveNode(ITrieNodeResolver tree, ReadFlags readFlags = ReadFla return true; } - return DecodeRlp(rplStream.Clone(), bufferPool, out _); + return DecodeRlp(rplStream.GetRlpStream(), bufferPool, out _); } catch (RlpException) { @@ -414,7 +425,7 @@ public bool TryResolveNode(ITrieNodeResolver tree, ReadFlags readFlags = ReadFla } } - private bool DecodeRlp(RlpStream rlpStream, ICappedArrayPool bufferPool, out int itemsCount) + private bool DecodeRlp(ValueRlpStream rlpStream, ICappedArrayPool bufferPool, out int itemsCount) { Metrics.TreeNodeRlpDecodings++; @@ -482,25 +493,25 @@ public void ResolveKey(ITrieNodeResolver tree, bool isRoot, ICappedArrayPool? bu return keccak; } - RlpStream rlpStream = _rlpStream; - if (rlpStream is null || IsDirty) + RlpFactory rlp = _rlp; + if (rlp is null || IsDirty) { - ref readonly CappedArray oldRlp = ref rlpStream is not null ? ref rlpStream.Data : ref CappedArray.Empty; + ref readonly CappedArray oldRlp = ref rlp is not null ? ref rlp.Data : ref CappedArray.Empty; CappedArray fullRlp = RlpEncode(tree, bufferPool); if (fullRlp.IsNotNullOrEmpty) { bufferPool.SafeReturnBuffer(oldRlp); } - _rlpStream = rlpStream = fullRlp.AsRlpStream(); + _rlp = rlp = fullRlp.AsRlpFactory(); } /* nodes that are descendants of other nodes are stored inline * if their serialized length is less than Keccak length * */ - if (rlpStream.Data.Length >= 32 || isRoot) + if (rlp.Data.Length >= 32 || isRoot) { Metrics.TreeNodeHashCalculations++; - return Nethermind.Core.Crypto.Keccak.Compute(rlpStream.Data.AsSpan()); + return Nethermind.Core.Crypto.Keccak.Compute(rlp.Data.AsSpan()); } return null; @@ -554,14 +565,14 @@ public object GetData(int index) public Hash256? GetChildHash(int i) { - RlpStream rlpStream = _rlpStream; - if (rlpStream is null) + RlpFactory rlp = _rlp; + if (rlp is null) { return null; } - rlpStream = rlpStream.Clone(); - SeekChild(rlpStream, i); + ValueRlpStream rlpStream = rlp.GetRlpStream(); + SeekChild(ref rlpStream, i); (int _, int length) = rlpStream.PeekPrefixAndContentLength(); return length == 32 ? rlpStream.DecodeKeccak() : null; } @@ -569,14 +580,14 @@ public object GetData(int index) public bool GetChildHashAsValueKeccak(int i, out ValueHash256 keccak) { Unsafe.SkipInit(out keccak); - RlpStream rlpStream = _rlpStream; - if (rlpStream is null) + RlpFactory rlp = _rlp; + if (rlp is null) { return false; } - rlpStream = rlpStream.Clone(); - SeekChild(rlpStream, i); + ValueRlpStream rlpStream = rlp.GetRlpStream(); + SeekChild(ref rlpStream, i); (_, int length) = rlpStream.PeekPrefixAndContentLength(); if (length == 32 && rlpStream.DecodeValueKeccak(out keccak)) { @@ -593,11 +604,11 @@ public bool IsChildNull(int i) ThrowNotABranch(); } - RlpStream rlpStream = _rlpStream; - if (rlpStream is not null && _data?[i] is null) + RlpFactory rlp = _rlp; + if (rlp is not null && _data?[i] is null) { - rlpStream = rlpStream.Clone(); - SeekChild(rlpStream, i); + ValueRlpStream rlpStream = rlp.GetRlpStream(); + SeekChild(ref rlpStream, i); return rlpStream.PeekNextRlpLength() == 1; } @@ -730,7 +741,7 @@ Keccak is null MemorySizes.RefSize + (FullRlp.IsNull ? 0 : MemorySizes.Align(FullRlp.UnderlyingLength + MemorySizes.ArrayOverhead)); long rlpStreamSize = - MemorySizes.RefSize + (_rlpStream?.MemorySize ?? 0) + MemorySizes.RefSize + (_rlp?.MemorySize ?? 0) - (FullRlp.IsNull ? 0 : MemorySizes.Align(FullRlp.UnderlyingLength + MemorySizes.ArrayOverhead)); long dataSize = MemorySizes.RefSize + @@ -804,10 +815,10 @@ public TrieNode Clone() } } - RlpStream rlpStream = _rlpStream; - if (rlpStream is not null) + RlpFactory rlp = _rlp; + if (rlp is not null) { - trieNode._rlpStream = rlpStream.Clone(); + trieNode._rlp = rlp; } return trieNode; @@ -998,17 +1009,17 @@ static void ThrowCannotResolveException() } } - private void SeekChild(RlpStream rlpStream, int itemToSetOn) + private void SeekChild(ref ValueRlpStream rlpStream, int itemToSetOn) { - if (rlpStream is null) + if (rlpStream.IsNull) { return; } - SeekChildNotNull(rlpStream, itemToSetOn); + SeekChildNotNull(ref rlpStream, itemToSetOn); } - private void SeekChildNotNull(RlpStream rlpStream, int itemToSetOn) + private void SeekChildNotNull(ref ValueRlpStream rlpStream, int itemToSetOn) { rlpStream.Reset(); rlpStream.SkipLength(); @@ -1027,8 +1038,8 @@ private void SeekChildNotNull(RlpStream rlpStream, int itemToSetOn) private object? ResolveChild(ITrieNodeResolver tree, int i) { object? childOrRef; - RlpStream rlpStream = _rlpStream; - if (rlpStream is null) + RlpFactory rlp = _rlp; + if (rlp is null) { childOrRef = _data?[i]; } @@ -1038,8 +1049,8 @@ private void SeekChildNotNull(RlpStream rlpStream, int itemToSetOn) if (_data![i] is null) { // Allows to load children in parallel - rlpStream = rlpStream.Clone(); - SeekChild(rlpStream, i); + ValueRlpStream rlpStream = rlp.GetRlpStream(); + SeekChild(ref rlpStream, i); int prefix = rlpStream.ReadByte(); switch (prefix)