From bd369fee454d384811a6fe632a7d69166001576d Mon Sep 17 00:00:00 2001 From: Diptanshu Kakwani Date: Mon, 6 Jan 2025 11:45:20 +0530 Subject: [PATCH] add nibbleset in RlpMemo to remember the order --- src/Paprika.Tests/Merkle/RlpMemoTests.cs | 114 +++++++------ src/Paprika/Merkle/CommitExtensions.cs | 1 - src/Paprika/Merkle/ComputeMerkleBehavior.cs | 63 +++----- src/Paprika/Merkle/NibbleSet.cs | 11 ++ src/Paprika/Merkle/RlpMemo.cs | 169 +++++++++++--------- 5 files changed, 189 insertions(+), 169 deletions(-) diff --git a/src/Paprika.Tests/Merkle/RlpMemoTests.cs b/src/Paprika.Tests/Merkle/RlpMemoTests.cs index 04bdad7d..75ae90e1 100644 --- a/src/Paprika.Tests/Merkle/RlpMemoTests.cs +++ b/src/Paprika.Tests/Merkle/RlpMemoTests.cs @@ -13,7 +13,6 @@ public class RlpMemoTests private enum RlpMemoOperation { Set, - SetRaw, Clear, Delete, Insert @@ -25,11 +24,17 @@ public void Random_delete() Span raw = stackalloc byte[RlpMemo.MaxSize]; var children = new NibbleSet(); - for (var i = 0; i < RlpMemo.MaxSize; i++) + for (var i = 0; i < RlpMemo.MaxSize - NibbleSet.MaxByteSize; i++) { raw[i] = (byte)(i & 0xFF); } + // Set all the index bits at the end. + for (var i = RlpMemo.MaxSize - 1; i >= RlpMemo.MaxSize - NibbleSet.MaxByteSize; i--) + { + raw[i] = 0xFF; + } + for (var i = 0; i < NibbleSet.NibbleCount; i++) { children[(byte)i] = true; @@ -48,11 +53,15 @@ public void Random_delete() } children[child] = false; - memo = RlpMemo.Delete(memo, child, children, raw); + memo = RlpMemo.Delete(memo, child, raw); - memo.Length.Should().Be(RlpMemo.MaxSize - (i + 1) * Keccak.Size); - memo.Exists(child, children).Should().BeFalse(); - memo.TryGetKeccak(child, out var keccak, children).Should().BeFalse(); + var expectedLength = (i != NibbleSet.NibbleCount - 1) + ? RlpMemo.MaxSize - (i + 1) * Keccak.Size + NibbleSet.MaxByteSize + : 0; + + memo.Length.Should().Be(expectedLength); + memo.Exists(child).Should().BeFalse(); + memo.TryGetKeccak(child, out var keccak).Should().BeFalse(); keccak.IsEmpty.Should().BeTrue(); } @@ -87,11 +96,15 @@ public void Random_insert() } children[child] = true; - memo = RlpMemo.Insert(memo, child, children, keccak, workingMemory); + memo = RlpMemo.Insert(memo, child, keccak, workingMemory); + + var expectedLength = (i != NibbleSet.NibbleCount - 1) + ? (i + 1) * Keccak.Size + NibbleSet.MaxByteSize + : RlpMemo.MaxSize; - memo.Length.Should().Be((i + 1) * Keccak.Size); - memo.Exists(child, children).Should().BeTrue(); - memo.TryGetKeccak(child, out var k, children).Should().BeTrue(); + memo.Length.Should().Be(expectedLength); + memo.Exists(child).Should().BeTrue(); + memo.TryGetKeccak(child, out var k).Should().BeTrue(); k.SequenceEqual(keccak).Should().BeTrue(); } @@ -102,29 +115,39 @@ public void Random_insert() public void In_place_update() { Span raw = stackalloc byte[RlpMemo.MaxSize]; + var children = new NibbleSet(); - for (var i = 0; i < RlpMemo.MaxSize; i++) + for (var i = 0; i < RlpMemo.MaxSize - NibbleSet.MaxByteSize; i++) { raw[i] = (byte)(i & 0xFF); } - var memo = new RlpMemo(raw); + // Set all the index bits at the end. + for (var i = RlpMemo.MaxSize - 1; i >= RlpMemo.MaxSize - NibbleSet.MaxByteSize; i--) + { + raw[i] = 0xFF; + } - var children = new NibbleSet(); for (var i = 0; i < NibbleSet.NibbleCount; i++) { children[(byte)i] = true; } + var memo = new RlpMemo(raw); + // Delete each child and the corresponding keccak for (byte i = 0; i < NibbleSet.NibbleCount; i++) { children[i] = false; - memo = RlpMemo.Delete(memo, i, children, raw); + memo = RlpMemo.Delete(memo, i, raw); + + var expectedLength = (i != NibbleSet.NibbleCount - 1) + ? RlpMemo.MaxSize - (i + 1) * Keccak.Size + NibbleSet.MaxByteSize + : 0; - memo.Length.Should().Be(RlpMemo.MaxSize - (i + 1) * Keccak.Size); - memo.Exists(i, children).Should().BeFalse(); - memo.TryGetKeccak(i, out var k, children).Should().BeFalse(); + memo.Length.Should().Be(expectedLength); + memo.Exists(i).Should().BeFalse(); + memo.TryGetKeccak(i, out var k).Should().BeFalse(); k.IsEmpty.Should().BeTrue(); } @@ -137,11 +160,15 @@ public void In_place_update() for (byte i = 0; i < NibbleSet.NibbleCount; i++) { children[i] = true; - memo = RlpMemo.Insert(memo, i, children, keccak, raw); + memo = RlpMemo.Insert(memo, i, keccak, raw); - memo.Length.Should().Be((i + 1) * Keccak.Size); - memo.Exists(i, children).Should().BeTrue(); - memo.TryGetKeccak(i, out var k, children).Should().BeTrue(); + var expectedLength = (i != NibbleSet.NibbleCount - 1) + ? (i + 1) * Keccak.Size + NibbleSet.MaxByteSize + : RlpMemo.MaxSize; + + memo.Length.Should().Be(expectedLength); + memo.Exists(i).Should().BeTrue(); + memo.TryGetKeccak(i, out var k).Should().BeTrue(); k.SequenceEqual(keccak).Should().BeTrue(); } @@ -173,14 +200,19 @@ public void Large_random_operations(int numOperations) var children = new NibbleSet(); Span keccak = new byte[Keccak.Size]; - keccak.Fill(0xFF); - KeccakOrRlp.FromSpan(keccak, out var keccakOrRlp); + keccak.Fill(0xFF); - for (var i = 0; i < RlpMemo.MaxSize; i++) + for (var i = 0; i < RlpMemo.MaxSize - NibbleSet.MaxByteSize; i++) { raw[i] = (byte)(i & 0xFF); } + // Set all the index bits at the end. + for (var i = RlpMemo.MaxSize - 1; i >= RlpMemo.MaxSize - NibbleSet.MaxByteSize; i--) + { + raw[i] = 0xFF; + } + for (var i = 0; i < NibbleSet.NibbleCount; i++) { children[(byte)i] = true; @@ -197,53 +229,43 @@ public void Large_random_operations(int numOperations) switch (op) { case RlpMemoOperation.Set: - if (memo.Exists(child, children)) - { - memo.Set(keccakOrRlp, child, children); - - memo.TryGetKeccak(child, out var k, children).Should().BeTrue(); - k.SequenceEqual(keccakOrRlp.Span).Should().BeTrue(); - } - - break; - case RlpMemoOperation.SetRaw: - if (memo.Exists(child, children)) + if (memo.Exists(child)) { - memo.SetRaw(keccak, child, children); + memo.Set(keccak, child); - memo.TryGetKeccak(child, out var k, children).Should().BeTrue(); + memo.TryGetKeccak(child, out var k).Should().BeTrue(); k.SequenceEqual(keccak).Should().BeTrue(); } break; case RlpMemoOperation.Clear: - if (memo.Exists(child, children)) + if (memo.Exists(child)) { - memo.Clear(child, children); + memo.Clear(child); - memo.TryGetKeccak(child, out var k, children).Should().BeFalse(); + memo.TryGetKeccak(child, out var k).Should().BeFalse(); k.IsEmpty.Should().BeTrue(); } break; case RlpMemoOperation.Delete: - if (memo.Exists(child, children)) + if (memo.Exists(child)) { children[child] = false; - memo = RlpMemo.Delete(memo, child, children, raw); + memo = RlpMemo.Delete(memo, child, raw); - memo.TryGetKeccak(child, out var k, children).Should().BeFalse(); + memo.TryGetKeccak(child, out var k).Should().BeFalse(); k.IsEmpty.Should().BeTrue(); } break; case RlpMemoOperation.Insert: - if (!memo.Exists(child, children)) + if (!memo.Exists(child)) { children[child] = true; - memo = RlpMemo.Insert(memo, child, children, keccak, raw); + memo = RlpMemo.Insert(memo, child, keccak, raw); - memo.TryGetKeccak(child, out var k, children).Should().BeTrue(); + memo.TryGetKeccak(child, out var k).Should().BeTrue(); k.SequenceEqual(keccak).Should().BeTrue(); } diff --git a/src/Paprika/Merkle/CommitExtensions.cs b/src/Paprika/Merkle/CommitExtensions.cs index 9cee5e0a..61a5d3eb 100644 --- a/src/Paprika/Merkle/CommitExtensions.cs +++ b/src/Paprika/Merkle/CommitExtensions.cs @@ -42,7 +42,6 @@ public static void SetBranch(this ICommit commit, in Key key, NibbleSet.Readonly EntryType type = EntryType.Persistent) { var branch = new Branch(children); - Debug.Assert(rlp.Length == 0 || rlp.Length == (children.SetCount * Keccak.Size)); commit.Set(key, branch.WriteTo(stackalloc byte[Branch.MaxByteLength]), rlp, type); } diff --git a/src/Paprika/Merkle/ComputeMerkleBehavior.cs b/src/Paprika/Merkle/ComputeMerkleBehavior.cs index 690f3ede..3d483858 100644 --- a/src/Paprika/Merkle/ComputeMerkleBehavior.cs +++ b/src/Paprika/Merkle/ComputeMerkleBehavior.cs @@ -277,39 +277,19 @@ private BuildStorageTriesItem[] GetStorageWorkItems(ICommitWithStats commit, Cac public ReadOnlySpan InspectBeforeApply(in Key key, ReadOnlySpan data, Span workingSet) { - if (data.IsEmpty) - return data; - - if (key.Type != DataType.Merkle) - return data; - - var node = Node.Header.Peek(data).NodeType; - - if (node != Node.Type.Branch) + if (data.IsEmpty || key.Type != DataType.Merkle) { - // Return data as is, either the node is not a branch or the memoization is not set for branches. return data; } - if (key.Path.Length < SkipRlpMemoizationForTopLevelsCount) + if (key.Path.Length < SkipRlpMemoizationForTopLevelsCount && + Node.Header.Peek(data).NodeType == Node.Type.Branch) { // For State branches, omit top levels of RLP memoization return Node.Branch.GetOnlyBranchData(data); } - var memoizedRlp = Node.Branch.ReadFrom(data, out var branch); - if (memoizedRlp.Length == 0) - { - // no RLP of children memoized, return - return data; - } - - // Copy the memoized RLPs - var dataLength = data.Length - memoizedRlp.Length; - data[..dataLength].CopyTo(workingSet); - memoizedRlp.CopyTo(workingSet[dataLength..]); - - return workingSet[..data.Length]; + return data; } public Keccak RootHash { get; private set; } @@ -511,7 +491,7 @@ private void EncodeBranch(scoped in Key key, scoped in ComputeContext ctx, scope { if (branch.Children[i]) { - if (memoize && memo.TryGetKeccak(i, out var keccak, branch.Children)) + if (memoize && memo.TryGetKeccak(i, out var keccak)) { // keccak from cache stream.Encode(keccak); @@ -544,13 +524,13 @@ private void EncodeBranch(scoped in Key key, scoped in ComputeContext ctx, scope { memoizedUpdated = true; - if (memo.Exists(i, branch.Children)) + if (memo.Exists(i)) { - memo.Set(keccakOrRlp, i, branch.Children); + memo.Set(keccakOrRlp.Span, i); } else if (keccakOrRlp.DataType == KeccakOrRlp.Type.Keccak) { - memo = RlpMemo.Insert(memo, i, branch.Children, keccakOrRlp.Span, rlpMemoization); + memo = RlpMemo.Insert(memo, i, keccakOrRlp.Span, rlpMemoization); } } } @@ -558,9 +538,9 @@ private void EncodeBranch(scoped in Key key, scoped in ComputeContext ctx, scope { stream.EncodeEmptyArray(); - if (memoize && memo.Exists(i, branch.Children)) + if (memoize && memo.Exists(i)) { - memo = RlpMemo.Delete(memo, i, branch.Children, rlpMemoization); + memo = RlpMemo.Delete(memo, i, rlpMemoization); memoizedUpdated = true; } } @@ -608,18 +588,18 @@ private void EncodeBranch(scoped in Key key, scoped in ComputeContext ctx, scope if (value.Length == Keccak.Size) { - if (memo.Exists(i, branch.Children)) + if (memo.Exists(i)) { - memo.SetRaw(value, i, branch.Children); + memo.Set(value, i); } else { - memo = RlpMemo.Insert(memo, i, branch.Children, value, rlpMemoization); + memo = RlpMemo.Insert(memo, i, value, rlpMemoization); } } - else if (memo.Exists(i, branch.Children)) + else if (memo.Exists(i)) { - memo.Clear(i, branch.Children); + memo = RlpMemo.Delete(memo, i, rlpMemoization); } } } @@ -834,7 +814,6 @@ private static DeleteStatus Delete(in NibblePath path, int at, ICommit commit, C case Node.Type.Branch: { var nibble = path[at]; - Debug.Assert(leftover.Length == 0 || leftover.Length == (branch.Children.SetCount * Keccak.Size)); if (!branch.Children[nibble]) { // no such child @@ -939,18 +918,18 @@ static void UpdateBranchOnDelete(ICommit commit, in Node.Branch branch, NibbleSe } // If this child still exists, only clear the memo. Otherwise, delete it from the memo. - if (memo.Exists(nibble, children)) + if (children[nibble] && memo.Exists(nibble)) { - memo.Clear(nibble, children); + memo.Clear(nibble); } - else if (leftover.Length > 0) + else if (memo.Exists(nibble)) { if (rlpWorkingSet == null) { rlpWorkingSet = ArrayPool.Shared.Rent(leftover.Length - Keccak.Size); } - memo = RlpMemo.Delete(memo, nibble, children, rlpWorkingSet); + memo = RlpMemo.Delete(memo, nibble, rlpWorkingSet); } var shouldUpdate = !branch.Children.Equals(children); @@ -1218,11 +1197,11 @@ private static void MarkPathDirty(in NibblePath path, in Span rlpMemoWorki // If this path does not exist, insert an empty Keccak. Otherwise, clear it in the memo. if (createLeaf) { - memo = RlpMemo.Insert(memo, nibble, children, Keccak.Zero.Span, rlpMemoWorkingSet); + memo = RlpMemo.Insert(memo, nibble, Keccak.Zero.Span, rlpMemoWorkingSet); } else { - memo.Clear(nibble, children); + memo.Clear(nibble); } if (shouldUpdateBranch || childRlpRequiresUpdate) diff --git a/src/Paprika/Merkle/NibbleSet.cs b/src/Paprika/Merkle/NibbleSet.cs index 86019147..4c0952a1 100644 --- a/src/Paprika/Merkle/NibbleSet.cs +++ b/src/Paprika/Merkle/NibbleSet.cs @@ -55,6 +55,13 @@ public bool this[byte nibble] public int SetCount => BitOperations.PopCount(_value); + public int SetCountBefore(byte nibble) + { + // Compute the number of set bits before `nibble` (including itself). + var leftChildren = (ushort)(_value & ((1U << (nibble + 1)) - 1)); + return BitOperations.PopCount(leftChildren); + } + public byte SmallestNibbleSet => (byte)BitOperations.TrailingZeroCount(_value); public byte BiggestNibbleSet => (byte)(31 - BitOperations.LeadingZeroCount((uint)_value)); @@ -102,10 +109,14 @@ public Readonly(ushort value) public static Readonly AllWithout(byte nibble) => new((ushort)(AllSetValue & ~(1 << nibble))); + public static Readonly None => new(0); + public bool AllSet => _value == AllSetValue; public int SetCount => new NibbleSet(_value).SetCount; + public int SetCountBefore(byte nibble) => new NibbleSet(_value).SetCountBefore(nibble); + public byte SmallestNibbleSet => new NibbleSet(_value).SmallestNibbleSet; public byte SmallestNibbleNotSet => new NibbleSet(_value).SmallestNibbleNotSet; diff --git a/src/Paprika/Merkle/RlpMemo.cs b/src/Paprika/Merkle/RlpMemo.cs index 0a26ea3e..d45e36e9 100644 --- a/src/Paprika/Merkle/RlpMemo.cs +++ b/src/Paprika/Merkle/RlpMemo.cs @@ -25,36 +25,17 @@ public RlpMemo(Span buffer) public int Length => _buffer.Length; - public void SetRaw(ReadOnlySpan keccak, byte nibble, NibbleSet.Readonly children) + public void Set(ReadOnlySpan keccak, byte nibble) { - var span = GetAtNibble(nibble, children); + var span = GetAtNibble(nibble); Debug.Assert(!span.IsEmpty); - Debug.Assert(_buffer.Length == (children.SetCount * Keccak.Size)); keccak.CopyTo(span); } - public void Set(in KeccakOrRlp keccakOrRlp, byte nibble, NibbleSet.Readonly children) + public void Clear(byte nibble) { - var span = GetAtNibble(nibble, children); - Debug.Assert(!span.IsEmpty); - Debug.Assert(_buffer.Length == (children.SetCount * Keccak.Size)); - - if (keccakOrRlp.DataType == KeccakOrRlp.Type.Keccak) - { - keccakOrRlp.Span.CopyTo(span); - } - else - { - // on rlp, memoize none - span.Clear(); - } - } - - public void Clear(byte nibble, NibbleSet.Readonly children) - { - var span = GetAtNibble(nibble, children); - Debug.Assert(_buffer.Length == 0 || _buffer.Length == (children.SetCount * Keccak.Size)); + var span = GetAtNibble(nibble); if (!span.IsEmpty) { @@ -62,10 +43,9 @@ public void Clear(byte nibble, NibbleSet.Readonly children) } } - public bool TryGetKeccak(byte nibble, out ReadOnlySpan keccak, NibbleSet.Readonly children) + public bool TryGetKeccak(byte nibble, out ReadOnlySpan keccak) { - var span = GetAtNibble(nibble, children); - Debug.Assert(_buffer.Length == 0 || _buffer.Length == (children.SetCount * Keccak.Size)); + var span = GetAtNibble(nibble); if (span.IndexOfAnyExcept((byte)0) >= 0) { @@ -77,29 +57,53 @@ public bool TryGetKeccak(byte nibble, out ReadOnlySpan keccak, NibbleSet.R return false; } - public bool Exists(byte nibble, NibbleSet.Readonly children) + public bool Exists(byte nibble) { - // Check if the element exists - if (_buffer.Length == 0 || ((ushort)children & (1U << nibble)) == 0) + if (_buffer.Length == 0) { return false; } - return true; + GetIndex(out var index); + + return index[nibble]; } - private Span GetAtNibble(byte nibble, NibbleSet.Readonly children) + private Span GetAtNibble(byte nibble) { - var leftChildren = (ushort)((ushort)children & ((1U << (nibble + 1)) - 1)); + if (_buffer.Length == 0) + { + return []; + } + + GetIndex(out var index); // Check if the element exists - if (_buffer.Length == 0 || (leftChildren & (1U << nibble)) == 0) + if (!index[nibble]) { return []; } - var index = BitOperations.PopCount(leftChildren) - 1; - return _buffer.Slice(index * Keccak.Size, Keccak.Size); + var nibbleIndex = index.SetCountBefore(nibble) - 1; + return _buffer.Slice(nibbleIndex * Keccak.Size, Keccak.Size); + } + + private void GetIndex(out NibbleSet.Readonly index) + { + // Extract the index bits. + var indexLength = _buffer.Length % Keccak.Size; + + if (indexLength != 0) + { + var bits = _buffer[^indexLength..]; + NibbleSet.Readonly.ReadFrom(bits, out index); + } + else + { + Debug.Assert(_buffer.Length is 0 or MaxSize); + + index = _buffer.IsEmpty ? NibbleSet.Readonly.None : NibbleSet.Readonly.All; + } } public static RlpMemo Copy(ReadOnlySpan from, scoped in Span to) @@ -109,86 +113,91 @@ public static RlpMemo Copy(ReadOnlySpan from, scoped in Span to) return new RlpMemo(span); } - public static RlpMemo Insert(RlpMemo memo, byte nibble, NibbleSet.Readonly children, - ReadOnlySpan keccak, scoped in Span workingSet) + public static RlpMemo Insert(RlpMemo memo, byte nibble, ReadOnlySpan keccak, scoped in Span workingSet) { - // Compute the destination size for copying. - var size = children.SetCount * Keccak.Size; + memo.GetIndex(out var index); + // Ensure that this element doesn't already exist. + Debug.Assert(!index[nibble]); + + // Compute the destination size for copying. + var size = (index.SetCount < NibbleSet.NibbleCount - 1) + ? (index.SetCount + 1) * Keccak.Size + NibbleSet.MaxByteSize + : MaxSize; Debug.Assert(workingSet.Length >= size); - var span = workingSet[..size]; - // Ensure that this element already exists in the list of children - var leftChildren = (ushort)((ushort)children & ((1U << (nibble + 1)) - 1)); - Debug.Assert((leftChildren & (1U << nibble)) != 0); + var span = workingSet[..size]; - // Find the index of this nibble in the memo - var insertIndex = BitOperations.PopCount(leftChildren) - 1; + // Compute the index of this nibble in the memo + var insertIndex = index.SetCountBefore(nibble); var insertOffset = insertIndex * Keccak.Size; - if (memo.Length != 0) + // Copy all the elements before the new element + if (insertOffset > 0) { - // Copy all the elements before the new element - if (insertOffset > 0) - { - memo._buffer.Slice(0, insertOffset).CopyTo(span); - } - - // Copy all the elements after the new element - var remainingBytes = (children.SetCount - insertIndex - 1) * Keccak.Size; - if (remainingBytes > 0) - { - memo._buffer.Slice(insertOffset, remainingBytes) - .CopyTo(span.Slice(insertOffset + Keccak.Size)); - } + memo._buffer[..insertOffset].CopyTo(span); } - else + + // Copy all the elements after the new element (except the index) + if (memo.Length > insertOffset) { - // Insert empty keccak for all the existing children - span.Clear(); + memo._buffer[insertOffset..^NibbleSet.MaxByteSize].CopyTo(span[(insertOffset + Keccak.Size)..]); } - keccak.CopyTo(span.Slice(insertOffset)); + keccak.CopyTo(span[insertOffset..]); + + // Update the index. + index = index.Set(nibble); + + if (size != MaxSize) + { + index.WriteToWithLeftover(span[^NibbleSet.MaxByteSize..]); + } return new RlpMemo(span); } - public static RlpMemo Delete(RlpMemo memo, byte nibble, NibbleSet.Readonly children, - scoped in Span workingSet) + public static RlpMemo Delete(RlpMemo memo, byte nibble, scoped in Span workingSet) { + memo.GetIndex(out var index); + + // Ensure that this element isn't already deleted. + Debug.Assert(index[nibble]); + // Compute the destination size for copying. - var size = memo.Length - Keccak.Size; - if (size < 0) + var size = (index.SetCount < NibbleSet.NibbleCount) + ? memo.Length - Keccak.Size + : memo.Length - Keccak.Size + NibbleSet.MaxByteSize; + + if (size <= NibbleSet.MaxByteSize) { - // Memo is already empty, nothing to delete. + // Empty RlpMemo after this delete operation. size = 0; return new RlpMemo(workingSet[..size]); } var span = workingSet[..size]; - // Ensure that this element does not exist in the list of children - var leftChildren = (ushort)((ushort)children & ((1U << (nibble + 1)) - 1)); - Debug.Assert((leftChildren & (1U << nibble)) == 0); - - // Find the index of this nibble in the memo - var deleteIndex = BitOperations.PopCount(leftChildren); + // Compute the index of this nibble in the memo + var deleteIndex = index.SetCountBefore(nibble) - 1; var deleteOffset = deleteIndex * Keccak.Size; // Copy all the elements before the deleted element if (deleteOffset > 0) { - memo._buffer.Slice(0, deleteOffset).CopyTo(span); + memo._buffer[..deleteOffset].CopyTo(span); } - // Copy all the elements after the deleted element - var remainingBytes = (children.SetCount - deleteIndex) * Keccak.Size; - if (remainingBytes > 0) + // Copy all the elements after the deleted element (except the index) + if (memo.Length > (deleteOffset + Keccak.Size)) { - memo._buffer.Slice(deleteOffset + Keccak.Size, remainingBytes) - .CopyTo(span.Slice(deleteOffset)); + memo._buffer[(deleteOffset + Keccak.Size)..^NibbleSet.MaxByteSize].CopyTo(span[deleteOffset..]); } + // Update the index. + index = index.Remove(nibble); + index.WriteToWithLeftover(span[^NibbleSet.MaxByteSize..]); + return new RlpMemo(span); } }