From 40c39560758a6aff58a9abf1169276cd5cccee8f Mon Sep 17 00:00:00 2001 From: benaadams Date: Fri, 29 Dec 2023 15:41:36 +0000 Subject: [PATCH] Speed up trie node traversal --- .../Nethermind.Trie/PatriciaTree.cs | 249 ++++++++++++------ .../Nethermind.Trie/TrieNode.Decoder.cs | 29 +- src/Nethermind/Nethermind.Trie/TrieNode.cs | 142 ++++++++-- 3 files changed, 312 insertions(+), 108 deletions(-) diff --git a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs index a66779f4a97..1cedadc144e 100644 --- a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs +++ b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs @@ -53,6 +53,9 @@ public class PatriciaTree private readonly bool _allowCommits; + private readonly bool _isTrace; + private readonly bool _isDebug; + private Hash256 _rootHash = Keccak.EmptyTreeHash; public TrieNode? RootRef { get; set; } @@ -116,6 +119,8 @@ public PatriciaTree( ICappedArrayPool? bufferPool = null) { _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + _isTrace = _logger.IsTrace; + _isDebug = _logger.IsDebug; TrieStore = trieStore ?? throw new ArgumentNullException(nameof(trieStore)); _parallelBranches = parallelBranches; _allowCommits = allowCommits; @@ -137,13 +142,12 @@ public void Commit(long blockNumber, bool skipRoot = false, WriteFlags writeFlag { if (_currentCommit is null) { - throw new InvalidAsynchronousStateException( - $"{nameof(_currentCommit)} is NULL when calling {nameof(Commit)}"); + ThrowInvalidAsynchronousStateException(); } if (!_allowCommits) { - throw new TrieException("Commits are not allowed on this trie."); + ThrowTrieException(); } if (RootRef is not null && RootRef.IsDirty) @@ -151,7 +155,7 @@ public void Commit(long blockNumber, bool skipRoot = false, WriteFlags writeFlag Commit(new NodeCommitInfo(RootRef), skipSelf: skipRoot); while (_currentCommit.TryDequeue(out NodeCommitInfo node)) { - if (_logger.IsTrace) _logger.Trace($"Committing {node} in {blockNumber}"); + if (_isTrace) Trace(blockNumber, node); TrieStore.CommitNode(blockNumber, node, writeFlags: writeFlags); } @@ -161,21 +165,46 @@ public void Commit(long blockNumber, bool skipRoot = false, WriteFlags writeFlag } TrieStore.FinishBlockCommit(TrieType, blockNumber, RootRef, writeFlags); - if (_logger.IsDebug) _logger.Debug($"Finished committing block {blockNumber}"); + + if (_isDebug) Debug(blockNumber); + + [MethodImpl(MethodImplOptions.NoInlining)] + void Trace(long blockNumber, in NodeCommitInfo node) + { + _logger.Trace($"Committing {node} in {blockNumber}"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void Debug(long blockNumber) + { + _logger.Debug($"Finished committing block {blockNumber}"); + } + + [DoesNotReturn] + [StackTraceHidden] + static void ThrowInvalidAsynchronousStateException() + { + throw new InvalidAsynchronousStateException($"{nameof(_currentCommit)} is NULL when calling {nameof(Commit)}"); + } + + [DoesNotReturn] + [StackTraceHidden] + static void ThrowTrieException() + { + throw new TrieException("Commits are not allowed on this trie."); + } } private void Commit(NodeCommitInfo nodeCommitInfo, bool skipSelf = false) { if (_currentCommit is null) { - throw new InvalidAsynchronousStateException( - $"{nameof(_currentCommit)} is NULL when calling {nameof(Commit)}"); + ThrowInvalidAsynchronousStateException(nameof(_currentCommit)); } if (_commitExceptions is null) { - throw new InvalidAsynchronousStateException( - $"{nameof(_commitExceptions)} is NULL when calling {nameof(Commit)}"); + ThrowInvalidAsynchronousStateException(nameof(_commitExceptions)); } TrieNode node = nodeCommitInfo.Node; @@ -192,20 +221,16 @@ private void Commit(NodeCommitInfo nodeCommitInfo, bool skipSelf = false) } else { - if (_logger.IsTrace) + if (_isTrace) { - TrieNode child = node.GetChild(TrieStore, i); - if (child is not null) - { - _logger.Trace($"Skipping commit of {child}"); - } + Trace(node, i); } } } } else { - List nodesToCommit = new(); + List nodesToCommit = new(16); for (int i = 0; i < 16; i++) { if (node.IsChildDirty(i)) @@ -214,13 +239,9 @@ private void Commit(NodeCommitInfo nodeCommitInfo, bool skipSelf = false) } else { - if (_logger.IsTrace) + if (_isTrace) { - TrieNode child = node.GetChild(TrieStore, i); - if (child is not null) - { - _logger.Trace($"Skipping commit of {child}"); - } + Trace(node, i); } } } @@ -242,7 +263,7 @@ private void Commit(NodeCommitInfo nodeCommitInfo, bool skipSelf = false) if (!_commitExceptions.IsEmpty) { - throw new AggregateException(_commitExceptions); + ThrowAggregateExceptions(); } } else @@ -259,7 +280,7 @@ private void Commit(NodeCommitInfo nodeCommitInfo, bool skipSelf = false) TrieNode extensionChild = node.GetChild(TrieStore, 0); if (extensionChild is null) { - throw new InvalidOperationException("An attempt to store an extension without a child."); + ThrowInvalidExtension(); } if (extensionChild.IsDirty) @@ -268,7 +289,7 @@ private void Commit(NodeCommitInfo nodeCommitInfo, bool skipSelf = false) } else { - if (_logger.IsTrace) _logger.Trace($"Skipping commit of {extensionChild}"); + if (_isTrace) TraceExtensionSkip(extensionChild); } } @@ -284,7 +305,50 @@ private void Commit(NodeCommitInfo nodeCommitInfo, bool skipSelf = false) } else { - if (_logger.IsTrace) _logger.Trace($"Skipping commit of an inlined {node}"); + if (_isTrace) TraceSkipInlineNode(node); + } + + [DoesNotReturn] + [StackTraceHidden] + void ThrowAggregateExceptions() + { + throw new AggregateException(_commitExceptions); + } + + [DoesNotReturn] + [StackTraceHidden] + static void ThrowInvalidExtension() + { + throw new InvalidOperationException("An attempt to store an extension without a child."); + } + + [DoesNotReturn] + [StackTraceHidden] + static void ThrowInvalidAsynchronousStateException(string param) + { + throw new InvalidAsynchronousStateException($"{param} is NULL when calling {nameof(Commit)}"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void Trace(TrieNode node, int i) + { + TrieNode child = node.GetChild(TrieStore, i); + if (child is not null) + { + _logger.Trace($"Skipping commit of {child}"); + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void TraceExtensionSkip(TrieNode extensionChild) + { + _logger.Trace($"Skipping commit of {extensionChild}"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void TraceSkipInlineNode(TrieNode node) + { + _logger.Trace($"Skipping commit of an inlined {node}"); } } @@ -320,15 +384,11 @@ private void SetRootHash(Hash256? value, bool resetObjects) : array = ArrayPool.Shared.Rent(nibblesCount)) [..nibblesCount]; // Slice to exact size; - try - { - Nibbles.BytesToNibbleBytes(rawKey, nibbles); - return Run(nibbles, nibblesCount, new CappedArray(Array.Empty()), false, startRootHash: rootHash).ToArray(); - } - finally - { - if (array is not null) ArrayPool.Shared.Return(array); - } + Nibbles.BytesToNibbleBytes(rawKey, nibbles); + var result = Run(nibbles, nibblesCount, new CappedArray(Array.Empty()), false, startRootHash: rootHash).ToArray(); + if (array is not null) ArrayPool.Shared.Return(array); + + return result; } catch (TrieException e) { @@ -337,6 +397,7 @@ private void SetRootHash(Hash256? value, bool resetObjects) } } + [MethodImpl(MethodImplOptions.NoInlining)] private static void EnhanceException(ReadOnlySpan rawKey, ValueHash256 rootHash, TrieException baseException) { static TrieNodeException? GetTrieNodeException(TrieException? exception) => @@ -367,8 +428,7 @@ public virtual void Set(ReadOnlySpan rawKey, byte[] value) [DebuggerStepThrough] public virtual void Set(ReadOnlySpan rawKey, CappedArray value) { - if (_logger.IsTrace) - _logger.Trace($"{(value.Length == 0 ? $"Deleting {rawKey.ToHexString()}" : $"Setting {rawKey.ToHexString()} = {value.AsSpan().ToHexString()}")}"); + if (_isTrace) Trace(in rawKey, in value); int nibblesCount = 2 * rawKey.Length; byte[] array = null; @@ -377,14 +437,14 @@ public virtual void Set(ReadOnlySpan rawKey, CappedArray value) : array = ArrayPool.Shared.Rent(nibblesCount)) [..nibblesCount]; // Slice to exact size - try - { - Nibbles.BytesToNibbleBytes(rawKey, nibbles); - Run(nibbles, nibblesCount, value, true); - } - finally + Nibbles.BytesToNibbleBytes(rawKey, nibbles); + Run(nibbles, nibblesCount, value, true); + + if (array is not null) ArrayPool.Shared.Return(array); + + void Trace(in ReadOnlySpan rawKey, in CappedArray value) { - if (array is not null) ArrayPool.Shared.Return(array); + _logger.Trace($"{(value.Length == 0 ? $"Deleting {rawKey.ToHexString()}" : $"Setting {rawKey.ToHexString()} = {value.AsSpan().ToHexString()}")}"); } } @@ -404,7 +464,7 @@ private CappedArray Run( { if (isUpdate && startRootHash is not null) { - throw new InvalidOperationException("Only reads can be done in parallel on the Patricia tree"); + ThrowNonConcurrentWrites(); } #if DEBUG @@ -426,7 +486,7 @@ private CappedArray Run( CappedArray result; if (startRootHash is not null) { - if (_logger.IsTrace) _logger.Trace($"Starting from {startRootHash} - {traverseContext.ToString()}"); + if (_isTrace) TraceStart(startRootHash, in traverseContext); TrieNode startNode = TrieStore.FindCachedOrUnknown(startRootHash); ResolveNode(startNode, in traverseContext); result = TraverseNode(startNode, in traverseContext); @@ -438,23 +498,50 @@ private CappedArray Run( { if (traverseContext.UpdateValue.IsNotNull) { - if (_logger.IsTrace) _logger.Trace($"Setting new leaf node with value {traverseContext.UpdateValue}"); + if (_isTrace) TraceNewLeaf(in traverseContext); byte[] key = updatePath[..nibblesCount].ToArray(); RootRef = TrieNodeFactory.CreateLeaf(key, traverseContext.UpdateValue); } - if (_logger.IsTrace) _logger.Trace($"Keeping the root as null in {traverseContext.ToString()}"); + if (_isTrace) TraceNull(in traverseContext); result = traverseContext.UpdateValue; } else { ResolveNode(RootRef, in traverseContext); - if (_logger.IsTrace) _logger.Trace($"{traverseContext.ToString()}"); + if (_isTrace) TraceNode(in traverseContext); result = TraverseNode(RootRef, in traverseContext); } } return result; + + [DoesNotReturn] + [StackTraceHidden] + static void ThrowNonConcurrentWrites() + { + throw new InvalidOperationException("Only reads can be done in parallel on the Patricia tree"); + } + + void TraceStart(Hash256 startRootHash, in TraverseContext traverseContext) + { + _logger.Trace($"Starting from {startRootHash} - {traverseContext.ToString()}"); + } + + void TraceNewLeaf(in TraverseContext traverseContext) + { + _logger.Trace($"Setting new leaf node with value {traverseContext.UpdateValue}"); + } + + void TraceNull(in TraverseContext traverseContext) + { + _logger.Trace($"Keeping the root as null in {traverseContext.ToString()}"); + } + + void TraceNode(in TraverseContext traverseContext) + { + _logger.Trace($"{traverseContext.ToString()}"); + } } private void ResolveNode(TrieNode node, in TraverseContext traverseContext) @@ -471,20 +558,36 @@ private void ResolveNode(TrieNode node, in TraverseContext traverseContext) private CappedArray TraverseNode(TrieNode node, in TraverseContext traverseContext) { - if (_logger.IsTrace) - _logger.Trace( - $"Traversing {node} to {(traverseContext.IsRead ? "READ" : traverseContext.IsDelete ? "DELETE" : "UPDATE")}"); + if (_isTrace) Trace(node, traverseContext); return node.NodeType switch { NodeType.Branch => TraverseBranch(node, in traverseContext), NodeType.Extension => TraverseExtension(node, in traverseContext), NodeType.Leaf => TraverseLeaf(node, in traverseContext), - NodeType.Unknown => throw new InvalidOperationException( - $"Cannot traverse unresolved node {node.Keccak}"), - _ => throw new NotSupportedException( - $"Unknown node type {node.NodeType}") + NodeType.Unknown => TraverseUnknown(node), + _ => ThrowNotSupported(node) }; + + [MethodImpl(MethodImplOptions.NoInlining)] + void Trace(TrieNode node, in TraverseContext traverseContext) + { + _logger.Trace($"Traversing {node} to {(traverseContext.IsRead ? "READ" : traverseContext.IsDelete ? "DELETE" : "UPDATE")}"); + } + + [DoesNotReturn] + [StackTraceHidden] + static CappedArray TraverseUnknown(TrieNode node) + { + throw new InvalidOperationException($"Cannot traverse unresolved node {node.Keccak}"); + } + + [DoesNotReturn] + [StackTraceHidden] + static CappedArray ThrowNotSupported(TrieNode node) + { + throw new NotSupportedException($"Unknown node type {node.NodeType}"); + } } private void ConnectNodes(TrieNode? node, in TraverseContext traverseContext) @@ -525,7 +628,7 @@ private void ConnectNodes(TrieNode? node, in TraverseContext traverseContext) // which is not possible in the Ethereum protocol where keys are of equal lengths // (it is possible in the more general trie definition) TrieNode leafFromBranch = TrieNodeFactory.CreateLeaf(Array.Empty(), node.Value); - if (_logger.IsTrace) _logger.Trace($"Converting {node} into {leafFromBranch}"); + if (_isTrace) _logger.Trace($"Converting {node} into {leafFromBranch}"); nextNode = leafFromBranch; } else @@ -567,7 +670,7 @@ L X - - - - - - - - - - - - - - */ { TrieNode extensionFromBranch = TrieNodeFactory.CreateExtension(new[] { (byte)childNodeIndex }, childNode); - if (_logger.IsTrace) + if (_isTrace) _logger.Trace( $"Extending child {childNodeIndex} {childNode} of {node} into {extensionFromBranch}"); @@ -603,7 +706,7 @@ L L - - - - - - - - - - - - - - */ byte[] newKey = Bytes.Concat((byte)childNodeIndex, childNode.Key); TrieNode extendedExtension = childNode.CloneWithChangedKey(newKey); - if (_logger.IsTrace) + if (_isTrace) _logger.Trace( $"Extending child {childNodeIndex} {childNode} of {node} into {extendedExtension}"); nextNode = extendedExtension; @@ -613,14 +716,14 @@ L L - - - - - - - - - - - - - - */ byte[] newKey = Bytes.Concat((byte)childNodeIndex, childNode.Key); TrieNode extendedLeaf = childNode.CloneWithChangedKey(newKey); - if (_logger.IsTrace) + if (_isTrace) _logger.Trace( $"Extending branch child {childNodeIndex} {childNode} into {extendedLeaf}"); - if (_logger.IsTrace) _logger.Trace($"Decrementing ref on a leaf extended up to eat a branch {childNode}"); + if (_isTrace) _logger.Trace($"Decrementing ref on a leaf extended up to eat a branch {childNode}"); if (node.IsSealed) { - if (_logger.IsTrace) _logger.Trace($"Decrementing ref on a branch replaced by a leaf {node}"); + if (_isTrace) _logger.Trace($"Decrementing ref on a branch replaced by a leaf {node}"); } nextNode = extendedLeaf; @@ -644,7 +747,7 @@ L L - - - - - - - - - - - - - - */ { byte[] newKey = Bytes.Concat(node.Key, nextNode.Key); TrieNode extendedLeaf = nextNode.CloneWithChangedKey(newKey); - if (_logger.IsTrace) + if (_isTrace) _logger.Trace($"Combining {node} and {nextNode} into {extendedLeaf}"); nextNode = extendedLeaf; @@ -678,7 +781,7 @@ L L - - - - - - - - - - - - - - */ byte[] newKey = Bytes.Concat(node.Key, nextNode.Key); TrieNode extendedExtension = nextNode.CloneWithChangedKey(newKey); - if (_logger.IsTrace) + if (_isTrace) _logger.Trace($"Combining {node} and {nextNode} into {extendedExtension}"); nextNode = extendedExtension; @@ -690,7 +793,7 @@ L L - - - - - - - - - - - - - - */ node = node.Clone(); } - if (_logger.IsTrace) _logger.Trace($"Connecting {node} with {nextNode}"); + if (_isTrace) _logger.Trace($"Connecting {node} with {nextNode}"); node.SetChild(0, nextNode); nextNode = node; } @@ -815,7 +918,7 @@ private CappedArray TraverseLeaf(TrieNode node, in TraverseContext travers longerPathValue = node.Value; } - int extensionLength = FindCommonPrefixLength(shorterPath, longerPath); + int extensionLength = shorterPath.CommonPrefixLength(longerPath); if (extensionLength == shorterPath.Length && extensionLength == longerPath.Length) { if (traverseContext.IsRead) @@ -893,7 +996,7 @@ private CappedArray TraverseExtension(TrieNode node, in TraverseContext tr TrieNode originalNode = node; ReadOnlySpan remaining = traverseContext.GetRemainingUpdatePath(); - int extensionLength = FindCommonPrefixLength(remaining, node.Key); + int extensionLength = remaining.CommonPrefixLength(node.Key); if (extensionLength == node.Key.Length) { if (traverseContext.IsUpdate) @@ -978,18 +1081,6 @@ private CappedArray TraverseNext(in TraverseContext traverseContext, int e return TraverseNode(next, in newContext); } - private static int FindCommonPrefixLength(ReadOnlySpan shorterPath, ReadOnlySpan longerPath) - { - int commonPrefixLength = 0; - int maxLength = Math.Min(shorterPath.Length, longerPath.Length); - for (int i = 0; i < maxLength && shorterPath[i] == longerPath[i]; i++, commonPrefixLength++) - { - // just finding the common part of the path - } - - return commonPrefixLength; - } - private readonly ref struct TraverseContext { public CappedArray UpdateValue { get; } diff --git a/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs b/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs index a8bf54f78ba..5a87b3c1e1c 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs @@ -4,7 +4,7 @@ using System; using System.Buffers; using System.Diagnostics; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; @@ -29,7 +29,7 @@ public static CappedArray Encode(ITrieNodeResolver tree, TrieNode? item, I if (item is null) { - throw new TrieException("An attempt was made to RLP encode a null node."); + ThrowNullNode(); } return item.NodeType switch @@ -37,8 +37,22 @@ public static CappedArray Encode(ITrieNodeResolver tree, TrieNode? item, I NodeType.Branch => RlpEncodeBranch(tree, item, bufferPool), NodeType.Extension => EncodeExtension(tree, item, bufferPool), NodeType.Leaf => EncodeLeaf(item, bufferPool), - _ => throw new TrieException($"An attempt was made to encode a trie node of type {item.NodeType}") + _ => ThrowUnhandledNodeType(item) }; + + [DoesNotReturn] + [StackTraceHidden] + static void ThrowNullNode() + { + throw new TrieException("An attempt was made to RLP encode a null node."); + } + + [DoesNotReturn] + [StackTraceHidden] + static CappedArray ThrowUnhandledNodeType(TrieNode item) + { + throw new TrieException($"An attempt was made to encode a trie node of type {item.NodeType}"); + } } [SkipLocalsInit] @@ -101,7 +115,7 @@ private static CappedArray EncodeLeaf(TrieNode node, ICappedArrayPool? poo { if (node.Key is null) { - throw new TrieException($"Hex prefix of a leaf node is null at node {node.Keccak}"); + ThrowNullKey(node); } byte[] hexPrefix = node.Key; @@ -130,6 +144,13 @@ private static CappedArray EncodeLeaf(TrieNode node, ICappedArrayPool? poo return data; } + [DoesNotReturn] + [StackTraceHidden] + private static void ThrowNullKey(TrieNode node) + { + throw new TrieException($"Hex prefix of a leaf node is null at node {node.Keccak}"); + } + private static CappedArray RlpEncodeBranch(ITrieNodeResolver tree, TrieNode item, ICappedArrayPool? pool) { int valueRlpLength = AllowBranchValues ? Rlp.LengthOf(item.Value.AsSpan()) : 1; diff --git a/src/Nethermind/Nethermind.Trie/TrieNode.cs b/src/Nethermind/Nethermind.Trie/TrieNode.cs index bb229a48678..fdd08dcacb8 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNode.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNode.cs @@ -3,8 +3,9 @@ using System; using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using System.Threading; using Nethermind.Core; using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; @@ -73,13 +74,19 @@ internal set { if (IsSealed) { - throw new InvalidOperationException( - $"{nameof(TrieNode)} {this} is already sealed when setting {nameof(Key)}."); + ThrowAlreadySealed(); } InitData(); _data![0] = value; Keccak = null; + + [DoesNotReturn] + [StackTraceHidden] + void ThrowAlreadySealed() + { + throw new InvalidOperationException($"{nameof(TrieNode)} {this} is already sealed when setting {nameof(Key)}."); + } } } @@ -144,8 +151,7 @@ public CappedArray Value { if (IsSealed) { - throw new InvalidOperationException( - $"{nameof(TrieNode)} {this} is already sealed when setting {nameof(Value)}."); + ThrowAlreadySealed(); } InitData(); @@ -170,6 +176,13 @@ public CappedArray Value } _data![IsLeaf ? 1 : BranchesCount] = value; + + [DoesNotReturn] + [StackTraceHidden] + void ThrowAlreadySealed() + { + throw new InvalidOperationException($"{nameof(TrieNode)} {this} is already sealed when setting {nameof(Value)}."); + } } } @@ -262,10 +275,17 @@ public void Seal() { if (IsSealed) { - throw new InvalidOperationException($"{nameof(TrieNode)} {this} is already sealed."); + ThrowAlreadySealed(); } IsDirty = false; + + [DoesNotReturn] + [StackTraceHidden] + void ThrowAlreadySealed() + { + throw new InvalidOperationException($"{nameof(TrieNode)} {this} is already sealed."); + } } /// @@ -281,7 +301,7 @@ public void ResolveNode(ITrieNodeResolver tree, ReadFlags readFlags = ReadFlags. { if (Keccak is null) { - throw new TrieException("Unable to resolve node without Keccak"); + ThrowMissingKeccak(); } FullRlp = tree.LoadRlp(Keccak, readFlags); @@ -289,7 +309,7 @@ public void ResolveNode(ITrieNodeResolver tree, ReadFlags readFlags = ReadFlags. if (FullRlp.IsNull) { - throw new TrieException($"Trie returned a NULL RLP for node {Keccak}"); + ThrowNullRlp(); } } } @@ -301,15 +321,51 @@ public void ResolveNode(ITrieNodeResolver tree, ReadFlags readFlags = ReadFlags. _rlpStream = FullRlp.AsRlpStream(); if (_rlpStream is null) { - throw new InvalidAsynchronousStateException($"{nameof(_rlpStream)} is null when {nameof(NodeType)} is {NodeType}"); + ThrowInvalidStateException(); + return; } if (!DecodeRlp(bufferPool, out int numberOfItems)) { - throw new TrieNodeException($"Unexpected number of items = {numberOfItems} when decoding a node from RLP ({FullRlp.AsSpan().ToHexString()})", Keccak ?? Nethermind.Core.Crypto.Keccak.Zero); + ThrowUnexpectedNumberOfItems(numberOfItems); } } catch (RlpException rlpException) + { + ThrowDecodingError(rlpException); + } + + [DoesNotReturn] + [StackTraceHidden] + static void ThrowMissingKeccak() + { + throw new TrieException("Unable to resolve node without Keccak"); + } + + [DoesNotReturn] + [StackTraceHidden] + void ThrowNullRlp() + { + throw new TrieException($"Trie returned a NULL RLP for node {Keccak}"); + } + + [DoesNotReturn] + [StackTraceHidden] + void ThrowInvalidStateException() + { + throw new InvalidAsynchronousStateException($"{nameof(_rlpStream)} is null when {nameof(NodeType)} is {NodeType}"); + } + + [DoesNotReturn] + [StackTraceHidden] + void ThrowUnexpectedNumberOfItems(int numberOfItems) + { + throw new TrieNodeException($"Unexpected number of items = {numberOfItems} when decoding a node from RLP ({FullRlp.AsSpan().ToHexString()})", Keccak ?? Nethermind.Core.Crypto.Keccak.Zero); + } + + [DoesNotReturn] + [StackTraceHidden] + void ThrowDecodingError(RlpException rlpException) { throw new TrieNodeException($"Error when decoding node {Keccak}", Keccak ?? Nethermind.Core.Crypto.Keccak.Zero, rlpException); } @@ -348,7 +404,7 @@ public bool TryResolveNode(ITrieNodeResolver tree, ReadFlags readFlags = ReadFla _rlpStream = FullRlp.AsRlpStream(); if (_rlpStream is null) { - throw new InvalidAsynchronousStateException($"{nameof(_rlpStream)} is null when {nameof(NodeType)} is {NodeType}"); + ThrowInvalidStateException(); } return DecodeRlp(bufferPool, out _); @@ -357,6 +413,13 @@ public bool TryResolveNode(ITrieNodeResolver tree, ReadFlags readFlags = ReadFla { return false; } + + [DoesNotReturn] + [StackTraceHidden] + void ThrowInvalidStateException() + { + throw new InvalidAsynchronousStateException($"{nameof(_rlpStream)} is null when {nameof(NodeType)} is {NodeType}"); + } } private bool DecodeRlp(ICappedArrayPool bufferPool, out int itemsCount) @@ -529,8 +592,7 @@ public bool IsChildNull(int i) { if (!IsBranch) { - throw new TrieException( - "An attempt was made to ask about whether a child is null on a non-branch node."); + ThrowNotABranch(); } if (_rlpStream is not null && _data?[i] is null) @@ -540,6 +602,13 @@ public bool IsChildNull(int i) } return _data?[i] is null || ReferenceEquals(_data[i], _nullNode); + + [DoesNotReturn] + [StackTraceHidden] + static void ThrowNotABranch() + { + throw new TrieException("An attempt was made to ask about whether a child is null on a non-branch node."); + } } public bool IsChildDirty(int i) @@ -598,9 +667,7 @@ public TrieNode? this[int i] { // we expect this to happen as a Trie traversal error (please see the stack trace above) // we need to investigate this case when it happens again - bool isKeccakCalculated = Keccak is not null && FullRlp.IsNotNull; - bool isKeccakCorrect = isKeccakCalculated && Keccak == Nethermind.Core.Crypto.Keccak.Compute(FullRlp.AsSpan()); - throw new TrieException($"Unexpected type found at position {childIndex} of {this} with {nameof(_data)} of length {_data?.Length}. Expected a {nameof(TrieNode)} or {nameof(Keccak)} but found {childOrRef?.GetType()} with a value of {childOrRef}. Keccak calculated? : {isKeccakCalculated}; Keccak correct? : {isKeccakCorrect}"); + ThrowUnexpectedTypeException(childIndex, childOrRef); } // pruning trick so we never store long persisted paths @@ -610,6 +677,15 @@ public TrieNode? this[int i] } return child; + + [DoesNotReturn] + [StackTraceHidden] + void ThrowUnexpectedTypeException(int childIndex, object childOrRef) + { + bool isKeccakCalculated = Keccak is not null && FullRlp.IsNotNull; + bool isKeccakCorrect = isKeccakCalculated && Keccak == Nethermind.Core.Crypto.Keccak.Compute(FullRlp.AsSpan()); + throw new TrieException($"Unexpected type found at position {childIndex} of {this} with {nameof(_data)} of length {_data?.Length}. Expected a {nameof(TrieNode)} or {nameof(Keccak)} but found {childOrRef?.GetType()} with a value of {childOrRef}. Keccak calculated? : {isKeccakCalculated}; Keccak correct? : {isKeccakCorrect}"); + } } public void ReplaceChildRef(int i, TrieNode child) @@ -628,14 +704,20 @@ public void SetChild(int i, TrieNode? node) { if (IsSealed) { - throw new InvalidOperationException( - $"{nameof(TrieNode)} {this} is already sealed when setting a child."); + ThrowAlreadySealed(); } InitData(); int index = IsExtension ? i + 1 : i; _data![index] = node ?? _nullNode; Keccak = null; + + [DoesNotReturn] + [StackTraceHidden] + void ThrowAlreadySealed() + { + throw new InvalidOperationException($"{nameof(TrieNode)} {this} is already sealed when setting a child."); + } } public long GetMemorySize(bool recursive) @@ -857,8 +939,6 @@ public void PrunePersistedRecursively(int maxLevelsDeep) // } } - #region private - private bool TryResolveStorageRoot(ITrieNodeResolver resolver, out TrieNode? storageRoot) { bool hasStorage = false; @@ -891,8 +971,8 @@ private void InitData() switch (NodeType) { case NodeType.Unknown: - throw new InvalidOperationException( - $"Cannot resolve children of an {nameof(NodeType.Unknown)} node"); + ThrowCannotResolveException(); + return; case NodeType.Branch: _data = new object[AllowBranchValues ? BranchesCount + 1 : BranchesCount]; break; @@ -901,6 +981,13 @@ private void InitData() break; } } + + [DoesNotReturn] + [StackTraceHidden] + static void ThrowCannotResolveException() + { + throw new InvalidOperationException($"Cannot resolve children of an {nameof(NodeType.Unknown)} node"); + } } private void SeekChild(int itemToSetOn) @@ -999,7 +1086,7 @@ private void UnresolveChild(int i) { if (!childNode.IsPersisted) { - throw new InvalidOperationException("Cannot unresolve a child that is not persisted yet."); + ThrowNotPersisted(); } else if (childNode.Keccak is not null) // if not by value node { @@ -1007,8 +1094,13 @@ private void UnresolveChild(int i) } } } - } - #endregion + [DoesNotReturn] + [StackTraceHidden] + static void ThrowNotPersisted() + { + throw new InvalidOperationException("Cannot unresolve a child that is not persisted yet."); + } + } } }