Skip to content

Commit

Permalink
Perf/poolable triestore (#6022)
Browse files Browse the repository at this point in the history
* Progress

* Capped array

* Unit test passed

* Should work now

* Tracking all buffer

* Separate the pool from the trie resolver

* Cleanup

* fix wrong calculate root

* Micro optimize

* Cleanup and receipts root also

* More tests

* Directly store array if possible

* Cleanup

* Test pool

* Whitespace

* Minor optimization on peek

* Fix peek

* Skip and read position without property

* Reduced memory usage

* Fix missed tests

* Ensure non nullable

* More unit tests

* Cleanup

* More cleanup

* Minor optimization

* More cleanup

* Fix minor mistake
  • Loading branch information
asdacap authored Aug 23, 2023
1 parent a4b9e52 commit ccb6238
Show file tree
Hide file tree
Showing 34 changed files with 551 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ public class ReceiptTrieTests
public void Can_calculate_root_no_eip_658()
{
TxReceipt receipt = Build.A.Receipt.WithAllFieldsFilled.TestObject;
ReceiptTrie receiptTrie = new(MainnetSpecProvider.Instance.GetSpec((1, null)), new[] { receipt });
Assert.That(receiptTrie.RootHash.ToString(), Is.EqualTo("0xe51a2d9f986d68628990c9d65e45c36128ec7bb697bd426b0bb4d18a3f3321be"));
Keccak rootHash = ReceiptTrie.CalculateRoot(MainnetSpecProvider.Instance.GetSpec((1, null)), new[] { receipt });
Assert.That(rootHash.ToString(), Is.EqualTo("0xe51a2d9f986d68628990c9d65e45c36128ec7bb697bd426b0bb4d18a3f3321be"));
}

[Test, Timeout(Timeout.MaxTestTime)]
public void Can_calculate_root()
{
TxReceipt receipt = Build.A.Receipt.WithAllFieldsFilled.TestObject;
ReceiptTrie receiptTrie = new(MainnetSpecProvider.Instance.GetSpec((MainnetSpecProvider.MuirGlacierBlockNumber, null)), new[] { receipt });
Assert.That(receiptTrie.RootHash.ToString(), Is.EqualTo("0x2e6d89c5b539e72409f2e587730643986c2ef33db5e817a4223aa1bb996476d5"));
Keccak rootHash = ReceiptTrie.CalculateRoot(MainnetSpecProvider.Instance.GetSpec((MainnetSpecProvider.MuirGlacierBlockNumber, null)), new[] { receipt });
Assert.That(rootHash.ToString(), Is.EqualTo("0x2e6d89c5b539e72409f2e587730643986c2ef33db5e817a4223aa1bb996476d5"));
}

[Test, Timeout(Timeout.MaxTestTime)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ public TxTrieTests(bool useEip2718)
public void Can_calculate_root()
{
Block block = Build.A.Block.WithTransactions(Build.A.Transaction.TestObject).TestObject;
TxTrie txTrie = new(block.Transactions);
Keccak rootHash = TxTrie.CalculateRoot(block.Transactions);

if (_releaseSpec == Berlin.Instance)
{
Assert.That(txTrie.RootHash.ToString(), Is.EqualTo("0x29cc403075ed3d1d6af940d577125cc378ee5a26f7746cbaf87f1cf4a38258b5"));
Assert.That(rootHash.ToString(), Is.EqualTo("0x29cc403075ed3d1d6af940d577125cc378ee5a26f7746cbaf87f1cf4a38258b5"));
}
else
{
Assert.That(txTrie.RootHash.ToString(), Is.EqualTo("0x29cc403075ed3d1d6af940d577125cc378ee5a26f7746cbaf87f1cf4a38258b5"));
Assert.That(rootHash.ToString(), Is.EqualTo("0x29cc403075ed3d1d6af940d577125cc378ee5a26f7746cbaf87f1cf4a38258b5"));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Threading;
using Nethermind.Core;
using Nethermind.Core.Buffers;
using Nethermind.Core.Crypto;
using Nethermind.Db.FullPruning;
using Nethermind.Logging;
Expand Down Expand Up @@ -74,7 +75,7 @@ private void PersistNode(TrieNode node)
if (node.Keccak is not null)
{
// simple copy of nodes RLP
_pruningContext.Set(node.Keccak.Bytes, node.FullRlp, _writeFlags);
_pruningContext.Set(node.Keccak.Bytes, node.FullRlp.ToArray(), _writeFlags);
Interlocked.Increment(ref _persistedNodes);

// log message every 1 mln nodes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Nethermind.Core.Crypto;
using Nethermind.Core.Specs;
using Nethermind.State.Proofs;
using Nethermind.Trie;

namespace Nethermind.Blockchain.Receipts
{
Expand All @@ -29,15 +30,15 @@ Keccak SkipStateAndStatusReceiptsRoot()
txReceipts.SetSkipStateAndStatusInRlp(true);
try
{
return new ReceiptTrie(receiptSpec, txReceipts).RootHash;
return ReceiptTrie.CalculateRoot(receiptSpec, txReceipts);
}
finally
{
txReceipts.SetSkipStateAndStatusInRlp(false);
}
}

Keccak receiptsRoot = new ReceiptTrie(receiptSpec, txReceipts).RootHash;
Keccak receiptsRoot = ReceiptTrie.CalculateRoot(receiptSpec, txReceipts);
if (!receiptSpec.ValidateReceipts && receiptsRoot != suggestedRoot)
{
var skipStateAndStatusReceiptsRoot = SkipStateAndStatusReceiptsRoot();
Expand Down
54 changes: 54 additions & 0 deletions src/Nethermind/Nethermind.Core.Test/Buffers/CappedArrayTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Linq;
using FluentAssertions;
using Nethermind.Core.Buffers;
using NUnit.Framework;

namespace Nethermind.Core.Test.Buffers;

public class CappedArrayTests
{
[Test]
public void WhenGivenNullArray_IsNull_ShouldReturnTrue()
{
CappedArray<byte> array = new(null);
array.IsNull.Should().BeTrue();
}

[Test]
public void WhenGivenNullArray_AsSpan_ShouldReturnEmpty()
{
CappedArray<byte> array = new(null);
array.AsSpan().IsEmpty.Should().BeTrue();
array.AsSpan().Length.Should().Be(0);
array.Length.Should().Be(0);
}

[Test]
public void WhenGivenArray_AndLengthIsSame_ToArray_ShouldReturnSameArray()
{
int[] baseArray = Enumerable.Range(0, 10).ToArray();
CappedArray<int> array = new(baseArray);
array.IsUncapped.Should().BeTrue();
array.IsNull.Should().BeFalse();
array.IsNotNull.Should().BeTrue();
array.Length.Should().Be(10);
array.ToArray().Should().BeSameAs(baseArray);
}

[Test]
public void WhenGivenArray_AndLengthIsLess_ToArray_ShouldBeCapped()
{
int[] baseArray = Enumerable.Range(0, 10).ToArray();
CappedArray<int> array = new(baseArray, 5);
array.IsUncapped.Should().BeFalse();
array.IsNull.Should().BeFalse();
array.IsNotNull.Should().BeTrue();
array.Length.Should().Be(5);
array.ToArray().Should().BeEquivalentTo(baseArray[..5]);
array.AsSpan().Length.Should().Be(5);
array.AsSpan().ToArray().Should().BeEquivalentTo(baseArray[..5]);
}
}
69 changes: 69 additions & 0 deletions src/Nethermind/Nethermind.Core/Buffers/CappedArray.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using Nethermind.Core.Crypto;
using Nethermind.Core.Extensions;

namespace Nethermind.Core.Buffers;

/// <summary>
/// Basically like ArraySegment, but only contain length, which reduces it size from 16byte to 12byte. Useful for
/// polling memory where memory pool usually can't return exactly the same size of data. To conserve space, The
/// underlying array can be null and this struct is meant to be non nullable, checking the `IsNull` property to check
/// if it represent null.
/// </summary>
public struct CappedArray<T>
{
private T[]? _array = null;
private int _length = 0;

public CappedArray(T[]? array, int length)
{
_array = array;
_length = length;
}

public CappedArray(T[]? array)
{
if (array != null)
{
_array = array;
_length = array.Length;
}
}

public static implicit operator ReadOnlySpan<T>(CappedArray<T> array)
{
return array.AsSpan();
}

public static implicit operator CappedArray<T>(T[]? array)
{
if (array == null) return new CappedArray<T>(null);
return new CappedArray<T>(array);
}

public int Length
{
get => _length;
set => _length = value;
}

public T[]? Array => _array;
public bool IsUncapped => _length == _array?.Length;
public bool IsNull => _array is null;
public bool IsNotNull => _array is not null;

public Span<T> AsSpan()
{
return _array.AsSpan()[..Length];
}

public T[]? ToArray()
{
if (_array is null) return null;
if (_length == _array?.Length) return _array;
return AsSpan().ToArray();
}
}
25 changes: 25 additions & 0 deletions src/Nethermind/Nethermind.Core/Buffers/ICappedArrayPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

namespace Nethermind.Core.Buffers;

public interface ICappedArrayPool
{
CappedArray<byte> Rent(int size);

void Return(CappedArray<byte> buffer);
}

public static class BufferPoolExtensions
{
public static CappedArray<byte> SafeRentBuffer(this ICappedArrayPool? pool, int size)
{
if (pool == null) return new CappedArray<byte>(new byte[size]);
return pool.Rent(size);
}

public static void SafeReturnBuffer(this ICappedArrayPool? pool, CappedArray<byte> buffer)
{
pool?.Return(buffer);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ private static string ToHexViaLookup(ReadOnlySpan<byte> bytes, bool withZeroX, b
{
return ToHexStringWithEip55Checksum(bytes, withZeroX, skipLeadingZeros);
}
if (bytes.Length == 0) return "";

int leadingZeros = skipLeadingZeros ? Bytes.CountLeadingZeros(bytes) : 0;
int length = bytes.Length * 2 + (withZeroX ? 2 : 0) - leadingZeros;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using Nethermind.Core.Buffers;

namespace Nethermind.Serialization.Rlp
{
Expand All @@ -12,6 +13,11 @@ public static RlpStream AsRlpStream(this byte[]? bytes)
return new(bytes ?? Array.Empty<byte>());
}

public static RlpStream AsRlpStream(this CappedArray<byte> bytes)
{
return new(bytes.Array ?? Array.Empty<byte>());
}

public static Rlp.ValueDecoderContext AsRlpValueContext(this byte[]? bytes)
{
return new(bytes ?? Array.Empty<byte>());
Expand Down
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ public static Rlp Encode(Keccak? keccak)
return new Rlp(result);
}

public static int StartSequence(byte[] buffer, int position, int sequenceLength)
public static int StartSequence(Span<byte> buffer, int position, int sequenceLength)
{
byte prefix;
int beforeLength = position + 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.Generic;
using DotNetty.Buffers;
using Nethermind.Core.Buffers;

namespace Nethermind.Serialization.Rlp
{
Expand Down Expand Up @@ -92,6 +93,22 @@ public static NettyRlpStream EncodeToNewNettyStream<T>(this IRlpStreamDecoder<T>
return rlpStream;
}

public static CappedArray<byte> EncodeToCappedArray<T>(this IRlpStreamDecoder<T> decoder, T? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None, ICappedArrayPool? bufferPool = null)
{
int size = decoder.GetLength(item, rlpBehaviors);
CappedArray<byte> buffer = bufferPool.SafeRentBuffer(size);
decoder.Encode(buffer.AsRlpStream(), item, rlpBehaviors);

return buffer;
}

public static CappedArray<byte> EncodeToCappedArray(this int item, ICappedArrayPool? bufferPool = null)
{
CappedArray<byte> buffer = bufferPool.SafeRentBuffer(Rlp.LengthOf(item));
buffer.AsRlpStream().Encode(item);
return buffer;
}

public static Rlp Encode<T>(this IRlpObjectDecoder<T> decoder, IReadOnlyCollection<T?>? items, RlpBehaviors behaviors = RlpBehaviors.None)
{
if (items is null)
Expand Down
5 changes: 3 additions & 2 deletions src/Nethermind/Nethermind.State.Test/NodeTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using Nethermind.Core.Buffers;
using Nethermind.Core.Test.Builders;
using Nethermind.Db;
using Nethermind.Logging;
Expand Down Expand Up @@ -133,11 +134,11 @@ public void Child_and_value_store_encode()
private static ITrieNodeResolver BuildATreeFromNode(TrieNode node)
{
TrieNode.AllowBranchValues = true;
byte[] rlp = node.RlpEncode(null);
CappedArray<byte> rlp = node.RlpEncode(null);
node.ResolveKey(null, true);

MemDb memDb = new();
memDb[node.Keccak.Bytes] = rlp;
memDb[node.Keccak.Bytes] = rlp.ToArray();

// ITrieNodeResolver tree = new PatriciaTree(memDb, node.Keccak, false, true);
return new TrieStore(memDb, NullLogManager.Instance);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Linq;
using FluentAssertions;
using Nethermind.Core;
using Nethermind.Core.Buffers;
using Nethermind.Core.Crypto;
using Nethermind.Core.Extensions;
using Nethermind.Core.Test.Builders;
Expand Down Expand Up @@ -746,7 +747,7 @@ public void Chaotic_test()
}
}

node.Value.Should().BeEquivalentTo(new byte[] { 1 });
node.Value.ToArray().Should().BeEquivalentTo(new byte[] { 1 });
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Linq;
using Nethermind.Core;
using Nethermind.Core.Buffers;
using Nethermind.Core.Crypto;
using Nethermind.Int256;
using Nethermind.Serialization.Rlp;
Expand Down Expand Up @@ -241,13 +242,13 @@ private void AddProofItem(TrieNode node, TrieVisitContext trieVisitContext)
{
foreach (int storageIndex in value.StorageIndices)
{
_storageProofItems[storageIndex].Add(node.FullRlp);
_storageProofItems[storageIndex].Add(node.FullRlp.ToArray());
}
}
}
else
{
_accountProofItems.Add(node.FullRlp);
_accountProofItems.Add(node.FullRlp.ToArray());
}
}

Expand Down Expand Up @@ -282,13 +283,13 @@ public void VisitLeaf(TrieNode node, TrieVisitContext trieVisitContext, byte[] v
bool isPathMatched = IsPathMatched(node, thisStoragePath);
if (isPathMatched)
{
_accountProof.StorageProofs[storageIndex].Value = new RlpStream(node.Value).DecodeByteArray();
_accountProof.StorageProofs[storageIndex].Value = new RlpStream(node.Value.ToArray()).DecodeByteArray();
}
}
}
else
{
Account account = _accountDecoder.Decode(new RlpStream(node.Value));
Account account = _accountDecoder.Decode(new RlpStream(node.Value.ToArray()));
bool isPathMatched = IsPathMatched(node, _fullAccountPath);
if (isPathMatched)
{
Expand Down
5 changes: 3 additions & 2 deletions src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Nethermind.Core.Buffers;
using Nethermind.Db;
using Nethermind.Logging;
using Nethermind.Serialization.Rlp;
Expand All @@ -23,8 +24,8 @@ public abstract class PatriciaTrie<T> : PatriciaTree
/// <c>true</c> to maintain an in-memory database for proof computation;
/// otherwise, <c>false</c>.
/// </param>
public PatriciaTrie(IEnumerable<T>? list, bool canBuildProof)
: base(canBuildProof ? new MemDb() : NullDb.Instance, EmptyTreeHash, false, false, NullLogManager.Instance)
public PatriciaTrie(IEnumerable<T>? list, bool canBuildProof, ICappedArrayPool? bufferPool = null)
: base(canBuildProof ? new MemDb() : NullDb.Instance, EmptyTreeHash, false, false, NullLogManager.Instance, bufferPool: bufferPool)
{
CanBuildProof = canBuildProof;

Expand Down
Loading

0 comments on commit ccb6238

Please sign in to comment.