Skip to content

Commit

Permalink
Refactor patricia tries
Browse files Browse the repository at this point in the history
  • Loading branch information
rubo committed Jan 6, 2023
1 parent 355be4f commit 8e7be8d
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 110 deletions.
59 changes: 59 additions & 0 deletions src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Collections.Generic;
using System.Linq;
using Nethermind.Db;
using Nethermind.Logging;
using Nethermind.Serialization.Rlp;
using Nethermind.State.Proofs;
using Nethermind.Trie;

namespace Nethermind.State.Trie;

/// <summary>
/// An abstract class that represents a Patricia trie built of a collection of <see cref="T"/>.
/// </summary>
/// <typeparam name="T">The type of the elements in the collection used to build the trie.</typeparam>
public abstract class PatriciaTrie<T> : PatriciaTree
{
/// <param name="list">The collection to build the trie of.</param>
/// <param name="canBuildProof">
/// <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)
{
CanBuildProof = canBuildProof;

if (list?.Any() ?? false)
{
Initialize(list);
UpdateRootHash();
}
}

/// <summary>
/// Computes the proofs for the index specified.
/// </summary>
/// <param name="index">The node index to compute the proof for.</param>
/// <returns>The array of the computed proofs.</returns>
/// <exception cref="NotSupportedException"></exception>
public virtual byte[][] BuildProof(int index)
{
if (!CanBuildProof)
throw new NotSupportedException("Building proofs not supported");

var proofCollector = new ProofCollector(Rlp.Encode(index).Bytes);

Accept(proofCollector, RootHash, new() { ExpectAccounts = false });

return proofCollector.BuildResult();
}

protected abstract void Initialize(IEnumerable<T> list);

protected virtual bool CanBuildProof { get; }
}
71 changes: 30 additions & 41 deletions src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,48 @@
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Collections.Generic;
using System.Linq;
using Nethermind.Core;
using Nethermind.Core.Extensions;
using Nethermind.Core.Specs;
using Nethermind.Db;
using Nethermind.Logging;
using Nethermind.Serialization.Rlp;
using Nethermind.Trie;
using Nethermind.State.Trie;

namespace Nethermind.State.Proofs
namespace Nethermind.State.Proofs;

/// <summary>
/// Represents a Patricia trie built of a collection of <see cref="TxReceipt"/>.
/// </summary>
public class ReceiptTrie : PatriciaTrie<TxReceipt>
{
public class ReceiptTrie : PatriciaTree
private static readonly ReceiptMessageDecoder _decoder = new();

/// <inheritdoc/>
/// <param name="receipts">The transaction receipts to build the trie of.</param>
public ReceiptTrie(IReceiptSpec spec, IEnumerable<TxReceipt> receipts, bool canBuildProof = false)
: base(null, canBuildProof)
{
private readonly bool _allowProofs;
private static readonly ReceiptMessageDecoder Decoder = new();
ArgumentNullException.ThrowIfNull(spec);
ArgumentNullException.ThrowIfNull(receipts);

public ReceiptTrie(IReceiptSpec receiptSpec, TxReceipt?[] txReceipts, bool allowProofs = false)
: base(allowProofs ? (IDb)new MemDb() : NullDb.Instance, EmptyTreeHash, false, false, NullLogManager.Instance)
if (receipts.Any())
{
_allowProofs = allowProofs;
if (txReceipts.Length == 0)
{
return;
}

// 3% allocations (2GB) on a Goerli 3M blocks fast sync due to calling receipt encoder hee
// avoiding it would require pooling byte arrays and passing them as Spans to temporary trees
// a temporary trie would be a trie that exists to create a state root only and then be disposed of
for (int i = 0; i < txReceipts.Length; i++)
{
TxReceipt? currentReceipt = txReceipts[i];
byte[] receiptRlp = Decoder.EncodeNew(currentReceipt,
(receiptSpec.IsEip658Enabled
? RlpBehaviors.Eip658Receipts
: RlpBehaviors.None) | RlpBehaviors.SkipTypedWrapping);


Set(Rlp.Encode(i).Bytes, receiptRlp);
}

// additional 3% 2GB is used here for trie nodes creation and root calculation
Initialize(receipts, spec);
UpdateRootHash();
}
}

public byte[][] BuildProof(int index)
private void Initialize(IEnumerable<TxReceipt> receipts, IReceiptSpec spec)
{
var behavior = (spec.IsEip658Enabled ? RlpBehaviors.Eip658Receipts : RlpBehaviors.None)
| RlpBehaviors.SkipTypedWrapping;
var key = 0;

foreach (var receipt in receipts)
{
if (!_allowProofs)
{
throw new InvalidOperationException("Cannot build proofs without underlying DB (for now?)");
}

ProofCollector proofCollector = new(Rlp.Encode(index).Bytes);
Accept(proofCollector, RootHash, new VisitingOptions { ExpectAccounts = false });
return proofCollector.BuildResult();
Set(Rlp.Encode(key++).Bytes, _decoder.EncodeNew(receipt, behavior));
}
}

protected override void Initialize(IEnumerable<TxReceipt> list) => throw new NotSupportedException();
}
65 changes: 21 additions & 44 deletions src/Nethermind/Nethermind.State/Proofs/TxTrie.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,35 @@
using System;
using System.Collections.Generic;
using Nethermind.Core;
using Nethermind.Core.Extensions;
using Nethermind.Core.Specs;
using Nethermind.Db;
using Nethermind.Logging;
using Nethermind.Serialization.Rlp;
using Nethermind.Trie;
using Nethermind.State.Trie;

namespace Nethermind.State.Proofs
{
public class TxTrie : PatriciaTree
{
private readonly bool _allowMerkleProofConstructions;
private static readonly TxDecoder _txDecoder = new();
namespace Nethermind.State.Proofs;

/// <summary>
/// Helper class used for calculation of tx roots for block headers.
/// </summary>
/// <param name="txs">Transactions to build a trie from.</param>
/// <param name="allowMerkleProofConstructions">Some tries do not need to be used for proof constructions.
/// In such cases we can avoid maintaining any in-memory databases.</param>
public TxTrie(IReadOnlyList<Transaction>? txs, bool allowMerkleProofConstructions = false)
: base(allowMerkleProofConstructions ? (IDb)new MemDb() : NullDb.Instance, EmptyTreeHash, false, false, NullLogManager.Instance)
{
_allowMerkleProofConstructions = allowMerkleProofConstructions;
if ((txs?.Count ?? 0) == 0)
{
return;
}
/// <summary>
/// Represents a Patricia trie built of a collection of <see cref="Transaction"/>.
/// </summary>
public class TxTrie : PatriciaTrie<Transaction>
{
private static readonly TxDecoder _txDecoder = new();

// 3% allocations (2GB) on a Goerli 3M blocks fast sync due to calling transaction encoder here
// Avoiding it would require pooling byte arrays and passing them as Spans to temporary trees
// a temporary trie would be a trie that exists to create a state root only and then be disposed of
for (int i = 0; i < txs.Count; i++)
{
Rlp transactionRlp = _txDecoder.Encode(txs[i], RlpBehaviors.SkipTypedWrapping);
Set(Rlp.Encode(i).Bytes, transactionRlp.Bytes);
}
/// <inheritdoc/>
/// <param name="transactions">The transactions to build the trie of.</param>
public TxTrie(IEnumerable<Transaction> transactions, bool canBuildProof = false)
: base(transactions, canBuildProof) => ArgumentNullException.ThrowIfNull(transactions);

// additional 3% 2GB is used here for trie nodes creation and root calculation
UpdateRootHash();
}
protected override void Initialize(IEnumerable<Transaction> list)
{
var key = 0;

public byte[][] BuildProof(int index)
// 3% allocations (2GB) on a Goerli 3M blocks fast sync due to calling transaction encoder here
// Avoiding it would require pooling byte arrays and passing them as Spans to temporary trees
// a temporary trie would be a trie that exists to create a state root only and then be disposed of
foreach (var transaction in list)
{
if (!_allowMerkleProofConstructions)
{
throw new InvalidOperationException("Cannot build proofs without underlying DB (for now?)");
}
Rlp transactionRlp = _txDecoder.Encode(transaction, RlpBehaviors.SkipTypedWrapping);

ProofCollector proofCollector = new(Rlp.Encode(index).Bytes);
Accept(proofCollector, RootHash, new VisitingOptions { ExpectAccounts = false });
return proofCollector.BuildResult();
Set(Rlp.Encode(key++).Bytes, transactionRlp.Bytes);
}
}
}
35 changes: 10 additions & 25 deletions src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,30 @@
using System;
using System.Collections.Generic;
using Nethermind.Core;
using Nethermind.Db;
using Nethermind.Logging;
using Nethermind.Serialization.Rlp;
using Nethermind.Trie;
using Nethermind.State.Trie;

namespace Nethermind.State.Proofs;

public class WithdrawalTrie : PatriciaTree
/// <summary>
/// Represents a Patricia trie built of a collection of <see cref="Withdrawal"/>.
/// </summary>
public class WithdrawalTrie : PatriciaTrie<Withdrawal>
{
private static readonly WithdrawalDecoder _codec = new();

private readonly bool _allowsMerkleProofs;
/// <inheritdoc/>
/// <param name="withdrawals">The withdrawals to build the trie of.</param>
public WithdrawalTrie(IEnumerable<Withdrawal> withdrawals, bool canBuildProof = false)
: base(withdrawals, canBuildProof) => ArgumentNullException.ThrowIfNull(withdrawals);

public WithdrawalTrie(IEnumerable<Withdrawal> withdrawals, bool allowsProofs = false)
: base(allowsProofs ? new MemDb() : NullDb.Instance, EmptyTreeHash, false, false, NullLogManager.Instance)
protected override void Initialize(IEnumerable<Withdrawal> withdrawals)
{
ArgumentNullException.ThrowIfNull(withdrawals);

_allowsMerkleProofs = allowsProofs;

var key = 0;

foreach (var withdrawal in withdrawals)
{
Set(Rlp.Encode(key++).Bytes, _codec.Encode(withdrawal).Bytes);
}

UpdateRootHash();
}

public byte[][] BuildProof(int index)
{
if (!_allowsMerkleProofs)
throw new InvalidOperationException("Merkle proofs not allowed");

var proofCollector = new ProofCollector(Rlp.Encode(index).Bytes);

Accept(proofCollector, RootHash, new() { ExpectAccounts = false });

return proofCollector.BuildResult();
}
}

0 comments on commit 8e7be8d

Please sign in to comment.