From f90ae860e2aa781806aa0cc9f7c303901e8a20c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Thu, 5 Feb 2026 14:25:48 +0100 Subject: [PATCH 01/13] perf(evm): optimize extcodesize handling --- .../Instructions/EvmInstructions.CodeCopy.cs | 16 +----- .../VirtualMachine.ExtCodeSizeCache.cs | 50 +++++++++++++++++++ .../Nethermind.Evm/VirtualMachine.cs | 1 + 3 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeSizeCache.cs diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs index 658511bca8c4..fdc47d3227c7 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs @@ -293,20 +293,8 @@ public static EvmExceptionType InstructionExtCodeSize( } } - // No optimization applied: load the account's code from storage. - ReadOnlySpan accountCode = vm.CodeInfoRepository - .GetCachedCodeInfo(address, followDelegation: false, spec, out _) - .CodeSpan; - // If EOF is enabled and the code is an EOF contract, push a fixed size (2). - if (spec.IsEofEnabled && EofValidator.IsEof(accountCode, out _)) - { - stack.PushUInt32(2); - } - else - { - // Otherwise, push the actual code length. - stack.PushUInt32((uint)accountCode.Length); - } + uint codeSize = vm.GetExtCodeSizeCached(address, spec); + stack.PushUInt32(codeSize); return EvmExceptionType.None; // Jump forward to be unpredicted by the branch predictor. OutOfGas: diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeSizeCache.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeSizeCache.cs new file mode 100644 index 000000000000..a21de5fc3da9 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeSizeCache.cs @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Evm.EvmObjectFormat; + +namespace Nethermind.Evm; + +public unsafe partial class VirtualMachine +{ + private const int MaxExtCodeSizeCacheEntries = 256; + private Dictionary? _extCodeSizeCache; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal uint GetExtCodeSizeCached(Address address, IReleaseSpec spec) + { + _extCodeSizeCache ??= new Dictionary(8); + AddressAsKey key = address; + + ValueHash256 codeHash = _worldState.GetCodeHash(address); + if (_extCodeSizeCache.TryGetValue(key, out ExtCodeSizeCacheEntry entry) && entry.CodeHash == codeHash) + { + return entry.CodeSize; + } + + ReadOnlySpan code = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _).CodeSpan; + uint codeSize = spec.IsEofEnabled && EofValidator.IsEof(code, out _) ? 2u : (uint)code.Length; + + if (_extCodeSizeCache.Count >= MaxExtCodeSizeCacheEntries) + { + _extCodeSizeCache.Clear(); + } + _extCodeSizeCache[key] = new ExtCodeSizeCacheEntry(codeHash, codeSize); + return codeSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ResetExtCodeSizeCache() + { + // Cache is keyed by code hash to remain correct when code changes mid-transaction. + _extCodeSizeCache?.Clear(); + } + + private readonly record struct ExtCodeSizeCacheEntry(ValueHash256 CodeHash, uint CodeSize); +} diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index 45611c2385ef..cefa17a1eda2 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -164,6 +164,7 @@ public virtual TransactionSubstate ExecuteTransaction( OpCodeCount = 0; // Initialize the code repository and set up the initial execution state. _codeInfoRepository = TxExecutionContext.CodeInfoRepository; + ResetExtCodeSizeCache(); _currentState = vmState; _previousCallResult = null; _previousCallOutputDestination = UInt256.Zero; From 4eff00a58892fe380bf41e293fb669d66e33de59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Thu, 5 Feb 2026 14:28:55 +0100 Subject: [PATCH 02/13] perf(evm): reuse extcode cache for extcodehash/copy --- .../Instructions/EvmInstructions.CodeCopy.cs | 3 +- .../EvmInstructions.Environment.cs | 23 +++++----- .../VirtualMachine.ExtCodeSizeCache.cs | 43 +++++++++++++++---- 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs index fdc47d3227c7..9b2b0ee32539 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs @@ -166,8 +166,7 @@ public static EvmExceptionType InstructionExtCodeCopy( if (!TGasPolicy.UpdateMemoryCost(ref gas, in a, result, vm.VmState)) goto OutOfGas; - ICodeInfo codeInfo = vm.CodeInfoRepository - .GetCachedCodeInfo(address, followDelegation: false, spec, out _); + ICodeInfo codeInfo = vm.GetExtCodeInfoCached(address, spec); // Get the external code from the repository. ReadOnlySpan externalCode = codeInfo.CodeSpan; diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs index 7e770d5ec07b..9b6fe99dac53 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs @@ -6,6 +6,7 @@ using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Core.Crypto; +using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.EvmObjectFormat; using Nethermind.Evm.GasPolicy; using Nethermind.Evm.State; @@ -674,20 +675,20 @@ public static EvmExceptionType InstructionExtCodeHashEof(); } - else - { - Memory code = state.GetCode(address); - // If the code passes EOF validation, push the EOF-specific hash. - if (EofValidator.IsEof(code, out _)) - { - stack.PushBytes(EofHash256); - } else { - // Otherwise, push the standard code hash. - stack.PushBytes(state.GetCodeHash(address).Bytes); + ICodeInfo codeInfo = vm.GetExtCodeInfoCached(address, spec); + // If the code is EOF, push the EOF-specific hash. + if (codeInfo is EofCodeInfo) + { + stack.PushBytes(EofHash256); + } + else + { + // Otherwise, push the standard code hash. + stack.PushBytes(state.GetCodeHash(address).Bytes); + } } - } return EvmExceptionType.None; // Jump forward to be unpredicted by the branch predictor. diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeSizeCache.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeSizeCache.cs index a21de5fc3da9..b47679b55b6e 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeSizeCache.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeSizeCache.cs @@ -7,6 +7,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; +using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.EvmObjectFormat; namespace Nethermind.Evm; @@ -28,14 +29,10 @@ internal uint GetExtCodeSizeCached(Address address, IReleaseSpec spec) return entry.CodeSize; } - ReadOnlySpan code = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _).CodeSpan; - uint codeSize = spec.IsEofEnabled && EofValidator.IsEof(code, out _) ? 2u : (uint)code.Length; + ICodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + uint codeSize = codeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)codeInfo.CodeSpan.Length; - if (_extCodeSizeCache.Count >= MaxExtCodeSizeCacheEntries) - { - _extCodeSizeCache.Clear(); - } - _extCodeSizeCache[key] = new ExtCodeSizeCacheEntry(codeHash, codeSize); + StoreCacheEntry(key, codeHash, codeSize, codeInfo); return codeSize; } @@ -46,5 +43,35 @@ private void ResetExtCodeSizeCache() _extCodeSizeCache?.Clear(); } - private readonly record struct ExtCodeSizeCacheEntry(ValueHash256 CodeHash, uint CodeSize); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ICodeInfo GetExtCodeInfoCached(Address address, IReleaseSpec spec) + { + _extCodeSizeCache ??= new Dictionary(8); + AddressAsKey key = address; + + ValueHash256 codeHash = _worldState.GetCodeHash(address); + if (_extCodeSizeCache.TryGetValue(key, out ExtCodeSizeCacheEntry entry) + && entry.CodeHash == codeHash + && entry.CodeInfo is not null) + { + return entry.CodeInfo; + } + + ICodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + uint codeSize = codeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)codeInfo.CodeSpan.Length; + StoreCacheEntry(key, codeHash, codeSize, codeInfo); + return codeInfo; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void StoreCacheEntry(AddressAsKey key, in ValueHash256 codeHash, uint codeSize, ICodeInfo? codeInfo) + { + if (_extCodeSizeCache!.Count >= MaxExtCodeSizeCacheEntries) + { + _extCodeSizeCache.Clear(); + } + _extCodeSizeCache[key] = new ExtCodeSizeCacheEntry(codeHash, codeSize, codeInfo); + } + + private readonly record struct ExtCodeSizeCacheEntry(ValueHash256 CodeHash, uint CodeSize, ICodeInfo? CodeInfo); } From dc7833f742c67f4ff345440887cc523bc522f454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Thu, 5 Feb 2026 14:44:46 +0100 Subject: [PATCH 03/13] perf(evm): rename extcode cache and make size configurable --- .../Nethermind.Config/BlocksConfig.cs | 2 + .../Nethermind.Config/IBlocksConfig.cs | 3 + .../VirtualMachine.ExtCodeCache.cs | 111 ++++++++++++++++++ .../VirtualMachine.ExtCodeSizeCache.cs | 77 ------------ .../Nethermind.Evm/VirtualMachine.cs | 2 +- .../Modules/BlockProcessingModule.cs | 2 + 6 files changed, 119 insertions(+), 78 deletions(-) create mode 100644 src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs delete mode 100644 src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeSizeCache.cs diff --git a/src/Nethermind/Nethermind.Config/BlocksConfig.cs b/src/Nethermind/Nethermind.Config/BlocksConfig.cs index 1eeba56cc523..193c44d5ce6a 100644 --- a/src/Nethermind/Nethermind.Config/BlocksConfig.cs +++ b/src/Nethermind/Nethermind.Config/BlocksConfig.cs @@ -61,6 +61,8 @@ private static string GetDefaultVersionExtraData() public bool CachePrecompilesOnBlockProcessing { get; set; } = true; + public int ExtCodeCacheEntries { get; set; } = 1024; + public int PreWarmStateConcurrency { get; set; } = 0; public int BlockProductionTimeoutMs { get; set; } = 4_000; diff --git a/src/Nethermind/Nethermind.Config/IBlocksConfig.cs b/src/Nethermind/Nethermind.Config/IBlocksConfig.cs index 5706b1b458fe..3c0d5ccb0da7 100644 --- a/src/Nethermind/Nethermind.Config/IBlocksConfig.cs +++ b/src/Nethermind/Nethermind.Config/IBlocksConfig.cs @@ -43,6 +43,9 @@ public interface IBlocksConfig : IConfig [ConfigItem(Description = "Whether to cache precompile results when processing blocks.", DefaultValue = "True", HiddenFromDocs = true)] bool CachePrecompilesOnBlockProcessing { get; set; } + [ConfigItem(Description = "The max entries in the per-transaction EXTCODE* cache. Set to 0 to disable.", DefaultValue = "1024", HiddenFromDocs = true)] + int ExtCodeCacheEntries { get; set; } + [ConfigItem(Description = "Specify pre-warm state concurrency. Default is logical processor - 1.", DefaultValue = "0", HiddenFromDocs = true)] int PreWarmStateConcurrency { get; set; } diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs new file mode 100644 index 000000000000..aa8afa797162 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Evm.CodeAnalysis; +using Nethermind.Evm.EvmObjectFormat; + +namespace Nethermind.Evm; + +public unsafe partial class VirtualMachine +{ + private const int DefaultMaxExtCodeCacheEntries = 1024; + private static int _maxExtCodeCacheEntries = DefaultMaxExtCodeCacheEntries; + private Dictionary? _extCodeCache; + + /// + /// Gets the maximum number of entries kept in the per-transaction EXTCODE* cache. + /// + public static int MaxExtCodeCacheEntries => _maxExtCodeCacheEntries; + + /// + /// Sets the maximum number of entries kept in the per-transaction EXTCODE* cache. + /// Use 0 to disable the cache. + /// + /// The maximum number of cache entries. Must be non-negative. + /// Thrown when is negative. + public static void SetMaxExtCodeCacheEntries(int maxEntries) + { + ArgumentOutOfRangeException.ThrowIfNegative(maxEntries); + _maxExtCodeCacheEntries = maxEntries; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal uint GetExtCodeSizeCached(Address address, IReleaseSpec spec) + { + if (_maxExtCodeCacheEntries == 0) + { + ICodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + return codeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)codeInfo.CodeSpan.Length; + } + + _extCodeCache ??= new Dictionary(8); + AddressAsKey key = address; + + ValueHash256 codeHash = _worldState.GetCodeHash(address); + if (_extCodeCache.TryGetValue(key, out ExtCodeCacheEntry entry) && entry.CodeHash == codeHash) + { + return entry.CodeSize; + } + + ICodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + uint codeSize = codeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)codeInfo.CodeSpan.Length; + + StoreCacheEntry(key, codeHash, codeSize, codeInfo); + return codeSize; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ResetExtCodeCache() + { + // Cache is keyed by code hash to remain correct when code changes mid-transaction. + _extCodeCache?.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ICodeInfo GetExtCodeInfoCached(Address address, IReleaseSpec spec) + { + if (_maxExtCodeCacheEntries == 0) + { + return _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + } + + _extCodeCache ??= new Dictionary(8); + AddressAsKey key = address; + + ValueHash256 codeHash = _worldState.GetCodeHash(address); + if (_extCodeCache.TryGetValue(key, out ExtCodeCacheEntry entry) + && entry.CodeHash == codeHash + && entry.CodeInfo is not null) + { + return entry.CodeInfo; + } + + ICodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + uint codeSize = codeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)codeInfo.CodeSpan.Length; + StoreCacheEntry(key, codeHash, codeSize, codeInfo); + return codeInfo; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void StoreCacheEntry(AddressAsKey key, in ValueHash256 codeHash, uint codeSize, ICodeInfo? codeInfo) + { + if (_maxExtCodeCacheEntries == 0) + { + return; + } + + if (_extCodeCache!.Count >= _maxExtCodeCacheEntries) + { + _extCodeCache.Clear(); + } + _extCodeCache[key] = new ExtCodeCacheEntry(codeHash, codeSize, codeInfo); + } + + private readonly record struct ExtCodeCacheEntry(ValueHash256 CodeHash, uint CodeSize, ICodeInfo? CodeInfo); +} diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeSizeCache.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeSizeCache.cs deleted file mode 100644 index b47679b55b6e..000000000000 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeSizeCache.cs +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Core.Specs; -using Nethermind.Evm.CodeAnalysis; -using Nethermind.Evm.EvmObjectFormat; - -namespace Nethermind.Evm; - -public unsafe partial class VirtualMachine -{ - private const int MaxExtCodeSizeCacheEntries = 256; - private Dictionary? _extCodeSizeCache; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal uint GetExtCodeSizeCached(Address address, IReleaseSpec spec) - { - _extCodeSizeCache ??= new Dictionary(8); - AddressAsKey key = address; - - ValueHash256 codeHash = _worldState.GetCodeHash(address); - if (_extCodeSizeCache.TryGetValue(key, out ExtCodeSizeCacheEntry entry) && entry.CodeHash == codeHash) - { - return entry.CodeSize; - } - - ICodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); - uint codeSize = codeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)codeInfo.CodeSpan.Length; - - StoreCacheEntry(key, codeHash, codeSize, codeInfo); - return codeSize; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ResetExtCodeSizeCache() - { - // Cache is keyed by code hash to remain correct when code changes mid-transaction. - _extCodeSizeCache?.Clear(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ICodeInfo GetExtCodeInfoCached(Address address, IReleaseSpec spec) - { - _extCodeSizeCache ??= new Dictionary(8); - AddressAsKey key = address; - - ValueHash256 codeHash = _worldState.GetCodeHash(address); - if (_extCodeSizeCache.TryGetValue(key, out ExtCodeSizeCacheEntry entry) - && entry.CodeHash == codeHash - && entry.CodeInfo is not null) - { - return entry.CodeInfo; - } - - ICodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); - uint codeSize = codeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)codeInfo.CodeSpan.Length; - StoreCacheEntry(key, codeHash, codeSize, codeInfo); - return codeInfo; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void StoreCacheEntry(AddressAsKey key, in ValueHash256 codeHash, uint codeSize, ICodeInfo? codeInfo) - { - if (_extCodeSizeCache!.Count >= MaxExtCodeSizeCacheEntries) - { - _extCodeSizeCache.Clear(); - } - _extCodeSizeCache[key] = new ExtCodeSizeCacheEntry(codeHash, codeSize, codeInfo); - } - - private readonly record struct ExtCodeSizeCacheEntry(ValueHash256 CodeHash, uint CodeSize, ICodeInfo? CodeInfo); -} diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index cefa17a1eda2..9d8aeca2f5c0 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -164,7 +164,7 @@ public virtual TransactionSubstate ExecuteTransaction( OpCodeCount = 0; // Initialize the code repository and set up the initial execution state. _codeInfoRepository = TxExecutionContext.CodeInfoRepository; - ResetExtCodeSizeCache(); + ResetExtCodeCache(); _currentState = vmState; _previousCallResult = null; _previousCallOutputDestination = UInt256.Zero; diff --git a/src/Nethermind/Nethermind.Init/Modules/BlockProcessingModule.cs b/src/Nethermind/Nethermind.Init/Modules/BlockProcessingModule.cs index 9ad13948399b..4165e4fba354 100644 --- a/src/Nethermind/Nethermind.Init/Modules/BlockProcessingModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/BlockProcessingModule.cs @@ -38,6 +38,8 @@ public class BlockProcessingModule(IInitConfig initConfig, IBlocksConfig blocksC { protected override void Load(ContainerBuilder builder) { + VirtualMachine.SetMaxExtCodeCacheEntries(blocksConfig.ExtCodeCacheEntries); + builder // Validators .AddSingleton((spec) => new TxValidator(spec.ChainId)) From 3325ebf087b4d8ea63fc3a7163eede232b131c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Thu, 5 Feb 2026 15:20:04 +0100 Subject: [PATCH 04/13] perf(evm): scope extcode cache per block --- .../Nethermind.Config/IBlocksConfig.cs | 2 +- .../VirtualMachine.ExtCodeCache.cs | 23 +++++++++++++++---- .../Nethermind.Evm/VirtualMachine.cs | 2 +- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Nethermind/Nethermind.Config/IBlocksConfig.cs b/src/Nethermind/Nethermind.Config/IBlocksConfig.cs index 3c0d5ccb0da7..44d26cbee88d 100644 --- a/src/Nethermind/Nethermind.Config/IBlocksConfig.cs +++ b/src/Nethermind/Nethermind.Config/IBlocksConfig.cs @@ -43,7 +43,7 @@ public interface IBlocksConfig : IConfig [ConfigItem(Description = "Whether to cache precompile results when processing blocks.", DefaultValue = "True", HiddenFromDocs = true)] bool CachePrecompilesOnBlockProcessing { get; set; } - [ConfigItem(Description = "The max entries in the per-transaction EXTCODE* cache. Set to 0 to disable.", DefaultValue = "1024", HiddenFromDocs = true)] + [ConfigItem(Description = "The max entries in the EXTCODE* cache. Set to 0 to disable.", DefaultValue = "1024", HiddenFromDocs = true)] int ExtCodeCacheEntries { get; set; } [ConfigItem(Description = "Specify pre-warm state concurrency. Default is logical processor - 1.", DefaultValue = "0", HiddenFromDocs = true)] diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs index aa8afa797162..60eff6b6993e 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs @@ -17,14 +17,15 @@ public unsafe partial class VirtualMachine private const int DefaultMaxExtCodeCacheEntries = 1024; private static int _maxExtCodeCacheEntries = DefaultMaxExtCodeCacheEntries; private Dictionary? _extCodeCache; + private long _extCodeCacheBlockNumber = long.MinValue; /// - /// Gets the maximum number of entries kept in the per-transaction EXTCODE* cache. + /// Gets the maximum number of entries kept in the EXTCODE* cache. /// public static int MaxExtCodeCacheEntries => _maxExtCodeCacheEntries; /// - /// Sets the maximum number of entries kept in the per-transaction EXTCODE* cache. + /// Sets the maximum number of entries kept in the EXTCODE* cache. /// Use 0 to disable the cache. /// /// The maximum number of cache entries. Must be non-negative. @@ -40,8 +41,8 @@ internal uint GetExtCodeSizeCached(Address address, IReleaseSpec spec) { if (_maxExtCodeCacheEntries == 0) { - ICodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); - return codeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)codeInfo.CodeSpan.Length; + ICodeInfo uncachedCodeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + return uncachedCodeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)uncachedCodeInfo.CodeSpan.Length; } _extCodeCache ??= new Dictionary(8); @@ -67,12 +68,24 @@ private void ResetExtCodeCache() _extCodeCache?.Clear(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void ResetExtCodeCacheForBlock() + { + long blockNumber = BlockExecutionContext.Header.Number; + if (_extCodeCacheBlockNumber != blockNumber) + { + _extCodeCacheBlockNumber = blockNumber; + ResetExtCodeCache(); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ICodeInfo GetExtCodeInfoCached(Address address, IReleaseSpec spec) { if (_maxExtCodeCacheEntries == 0) { - return _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + ICodeInfo uncachedCodeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + return uncachedCodeInfo; } _extCodeCache ??= new Dictionary(8); diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index 9d8aeca2f5c0..e26a5ea777f0 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -164,7 +164,7 @@ public virtual TransactionSubstate ExecuteTransaction( OpCodeCount = 0; // Initialize the code repository and set up the initial execution state. _codeInfoRepository = TxExecutionContext.CodeInfoRepository; - ResetExtCodeCache(); + ResetExtCodeCacheForBlock(); _currentState = vmState; _previousCallResult = null; _previousCallOutputDestination = UInt256.Zero; From bd8098c67fe9c97d8e1014f436db774cea72ba94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Tue, 17 Feb 2026 17:29:27 +0100 Subject: [PATCH 05/13] Merge branch 'kch/gas-benchmarks-bdn' into perf/extcodesize-cache-bdn # Conflicts: # src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs --- .github/workflows/build-tools.yml | 1 + .github/workflows/ci-surge.yml | 167 +++ .github/workflows/ci-taiko.yml | 62 +- .github/workflows/gas-benchmarks-bdn.yml | 161 +++ .github/workflows/pr-labeler.yml | 201 +++ .gitignore | 4 + .gitmodules | 4 + AGENTS.md | 108 +- Directory.Packages.props | 49 +- Dockerfile | 4 +- Dockerfile.chiseled | 4 +- README.md | 2 +- cspell.json | 113 ++ scripts/benchmarks/compare_bdn_results.py | 156 +++ scripts/benchmarks/merge_bdn_results.py | 53 + scripts/build/Dockerfile | 2 +- .../Chains/arena-z-mainnet.json.zst | Bin 86216 -> 87959 bytes .../Chains/arena-z-sepolia.json.zst | Bin 82744 -> 84295 bytes .../Chains/automata-mainnet.json.zst | Bin 78345 -> 79847 bytes src/Nethermind/Chains/base-mainnet.json.zst | Bin 37232 -> 38338 bytes src/Nethermind/Chains/base-sepolia.json.zst | Bin 45456 -> 46230 bytes src/Nethermind/Chains/bob-mainnet.json.zst | Bin 69168 -> 70308 bytes src/Nethermind/Chains/boba-mainnet.json.zst | Bin 1502 -> 2564 bytes src/Nethermind/Chains/boba-sepolia.json.zst | Bin 1500 -> 2538 bytes src/Nethermind/Chains/camp-sepolia.json.zst | Bin 73911 -> 75484 bytes .../Chains/celo-sep-sepolia.json.zst | Bin 86242 -> 87789 bytes src/Nethermind/Chains/cyber-mainnet.json.zst | Bin 72230 -> 73511 bytes src/Nethermind/Chains/cyber-sepolia.json.zst | Bin 72241 -> 73487 bytes src/Nethermind/Chains/dictionary | Bin 65536 -> 65536 bytes .../Chains/ethernity-mainnet.json.zst | Bin 70753 -> 72175 bytes .../Chains/ethernity-sepolia.json.zst | Bin 74028 -> 75578 bytes .../Chains/fraxtal-mainnet.json.zst | Bin 143082 -> 144497 bytes src/Nethermind/Chains/funki-mainnet.json.zst | Bin 78337 -> 79866 bytes src/Nethermind/Chains/funki-sepolia.json.zst | Bin 72257 -> 73514 bytes .../Chains/hashkeychain-mainnet.json.zst | Bin 81902 -> 83435 bytes src/Nethermind/Chains/ink-mainnet.json.zst | Bin 86144 -> 87925 bytes src/Nethermind/Chains/ink-sepolia.json.zst | Bin 86245 -> 87884 bytes src/Nethermind/Chains/lisk-mainnet.json.zst | Bin 69115 -> 70230 bytes src/Nethermind/Chains/lisk-sepolia.json.zst | Bin 70141 -> 71731 bytes src/Nethermind/Chains/lyra-mainnet.json.zst | Bin 36671 -> 37934 bytes src/Nethermind/Chains/metal-mainnet.json.zst | Bin 69180 -> 70318 bytes src/Nethermind/Chains/metal-sepolia.json.zst | Bin 36972 -> 38283 bytes src/Nethermind/Chains/mint-mainnet.json.zst | Bin 69143 -> 70307 bytes src/Nethermind/Chains/mode-mainnet.json.zst | Bin 37229 -> 38311 bytes src/Nethermind/Chains/mode-sepolia.json.zst | Bin 41330 -> 42379 bytes src/Nethermind/Chains/op-mainnet.json.zst | Bin 1587 -> 2676 bytes src/Nethermind/Chains/op-sepolia.json.zst | Bin 48781 -> 49842 bytes .../Chains/orderly-mainnet.json.zst | Bin 37213 -> 38301 bytes src/Nethermind/Chains/ozean-sepolia.json.zst | Bin 70705 -> 72077 bytes .../Chains/pivotal-sepolia.json.zst | Bin 73882 -> 75415 bytes .../Chains/polynomial-mainnet.json.zst | Bin 69011 -> 70295 bytes src/Nethermind/Chains/race-mainnet.json.zst | Bin 70604 -> 72014 bytes src/Nethermind/Chains/race-sepolia.json.zst | Bin 70667 -> 72083 bytes .../Chains/redstone-mainnet.json.zst | Bin 69018 -> 70143 bytes .../Chains/settlus-mainnet-mainnet.json.zst | Bin 82794 -> 84357 bytes .../Chains/settlus-sepolia-sepolia.json.zst | Bin 86117 -> 87753 bytes src/Nethermind/Chains/shape-mainnet.json.zst | Bin 69036 -> 70130 bytes src/Nethermind/Chains/shape-sepolia.json.zst | Bin 72315 -> 73439 bytes .../Chains/soneium-mainnet.json.zst | Bin 86239 -> 87970 bytes .../Chains/soneium-minato-sepolia.json.zst | Bin 78497 -> 80019 bytes src/Nethermind/Chains/sseed-mainnet.json.zst | Bin 70707 -> 72166 bytes src/Nethermind/Chains/swan-mainnet.json.zst | Bin 67884 -> 69279 bytes src/Nethermind/Chains/swell-mainnet.json.zst | Bin 82890 -> 84459 bytes src/Nethermind/Chains/tbn-mainnet.json.zst | Bin 81789 -> 83494 bytes src/Nethermind/Chains/tbn-sepolia.json.zst | Bin 73978 -> 75515 bytes .../Chains/unichain-mainnet.json.zst | Bin 116381 -> 118352 bytes .../Chains/unichain-sepolia.json.zst | Bin 101586 -> 103133 bytes .../Chains/worldchain-mainnet.json.zst | Bin 72303 -> 73618 bytes .../Chains/worldchain-sepolia.json.zst | Bin 72376 -> 73622 bytes .../Chains/xterio-eth-mainnet.json.zst | Bin 72282 -> 73537 bytes src/Nethermind/Chains/zora-mainnet.json.zst | Bin 41317 -> 42378 bytes src/Nethermind/Chains/zora-sepolia.json.zst | Bin 37098 -> 38329 bytes src/Nethermind/Nethermind.Api/IBasicApi.cs | 2 +- src/Nethermind/Nethermind.Api/IInitConfig.cs | 2 +- src/Nethermind/Nethermind.Api/InitConfig.cs | 2 +- .../Nethermind.Api/NethermindApi.cs | 4 +- .../Core/FastHashBenchmarks.cs | 47 + .../Core/SeqlockCacheBenchmarks.cs | 345 +++++ .../BackgroundTaskSchedulerBenchmarks.cs | 181 +++ .../BeaconBlockRootHandlerTests.cs | 2 + .../BlockFinderExtensionsTests.cs | 1 + .../BlockProcessorTests.cs | 1 + .../BlockTreeSuggestPacerTests.cs | 1 + .../BlockTreeTests.cs | 2 + .../BlockchainProcessorTests.cs | 35 +- .../BlockhashProviderTests.cs | 1 + .../Blocks/BadBlockStoreTests.cs | 1 + .../Blocks/BlockStoreTests.cs | 1 + .../Blocks/HeaderStoreTests.cs | 2 +- .../Builders/FilterBuilder.cs | 9 +- .../CachedCodeInfoRepositoryTests.cs | 51 +- .../ChainHeadReadOnlyStateProviderTests.cs | 1 + .../Consensus/ClefSignerTests.cs | 1 + .../Consensus/CompositeTxSourceTests.cs | 1 + .../Consensus/NethDevSealEngineTests.cs | 1 + .../Consensus/NullSealEngineTests.cs | 1 + .../Consensus/NullSignerTests.cs | 1 + .../Consensus/OneByOneTxSourceTests.cs | 1 + .../Consensus/SealEngineExceptionTests.cs | 1 + .../Consensus/SignerTests.cs | 1 + .../Consensus/SinglePendingTxSelectorTests.cs | 1 + .../DaoDataTests.cs | 1 + .../Data/FileLocalDataSourceTests.cs | 1 + .../ExitOnBlocknumberHandlerTests.cs | 1 + .../Filters/AddressFilterTests.cs | 1 + .../Filters/FilterManagerTests.cs | 17 +- .../Filters/FilterStoreTests.cs | 3 + .../Filters/LogFilterTests.cs | 2 + .../Filters/LogIndexFilterVisitorTests.cs | 413 ++++++ .../Filters/TestProcessingContext.cs | 55 + .../Find/LogFinderTests.cs | 137 +- .../FullPruning/CopyTreeVisitorTests.cs | 19 +- .../FullPruning/FullPrunerTests.cs | 22 +- .../FullPruning/FullPruningDiskTest.cs | 34 +- .../PruningTriggerPruningStrategyTests.cs | 3 +- .../BuildBlockOnEachPendingTxTests.cs | 1 + .../Producers/BuildBlockRegularlyTests.cs | 1 + .../BuildBlocksWhenRequestedTests.cs | 1 + .../CompositeBlockProductionTriggerTests.cs | 1 + .../Producers/DevBlockproducerTests.cs | 1 + .../Producers/IfPoolIsNotEmptyTests.cs | 1 + .../Proofs/ReceiptTrieTests.cs | 23 + .../Proofs/TxTrieTests.cs | 52 +- .../Proofs/WithdrawalTrieTests.cs | 1 + .../ReadOnlyBlockTreeTests.cs | 2 + .../Receipts/KeccaksIteratorTests.cs | 1 + .../Receipts/PersistentReceiptStorageTests.cs | 16 +- .../Receipts/ReceiptsIteratorTests.cs | 3 +- .../Receipts/ReceiptsRecoveryTests.cs | 2 + .../Receipts/ReceiptsRootTests.cs | 1 + .../ReorgDepthFinalizedStateProviderTests.cs | 3 +- .../Nethermind.Blockchain.Test/ReorgTests.cs | 5 +- .../Rewards/NoBlockRewardsTests.cs | 1 + .../Rewards/RewardCalculatorTests.cs | 1 + .../Services/HealthHintServiceTests.cs | 1 + .../TransactionGasPriceComparisonTests.cs | 1 + .../TransactionProcessorEip7702Tests.cs | 2 + .../TransactionProcessorTests.cs | 3 +- .../TransactionSelectorTests.cs | 1 + .../TransactionsExecutorTests.cs | 1 + .../Utils/LastNStateRootTrackerTests.cs | 1 + .../Validators/BlockValidatorTests.cs | 2 + .../Validators/HeaderValidatorTests.cs | 2 + .../ShardBlobBlockValidatorTests.cs | 1 + .../Validators/TxValidatorTests.cs | 5 +- .../Validators/UnclesValidatorTests.cs | 2 + .../Validators/WithdrawalValidatorTests.cs | 1 + .../Visitors/DbBlocksLoaderTests.cs | 1 + .../Visitors/StartupTreeFixerTests.cs | 1 + .../BlockhashProvider.cs | 10 +- .../CachedCodeInfoRepository.cs | 10 +- .../EthereumPrecompileProvider.cs | 6 +- .../JavaScript/GethLikeJavaScriptTxTracer.cs | 3 +- .../Custom/Native/Call/NativeCallTracer.cs | 14 +- .../Tracing/GethStyle/GethTraceOptions.cs | 12 +- .../Tracing/ParityStyle/ParityLikeTxTracer.cs | 3 +- .../AuRaBlockFinalizationManager.cs | 1 + .../BlockHeaderExtensions.cs | 19 - .../Scheduler/BackgroundTaskSchedulerTests.cs | 175 +++ .../Processing/BlockCachePreWarmer.cs | 2 +- .../Processing/BlockchainProcessor.cs | 12 +- .../IOverridableTxProcessingScope.cs | 0 .../IOverridableTxProcessorSource.cs | 0 .../Processing/IProcessingStats.cs | 36 + .../OverridableTxProcessingScope.cs | 0 .../Processing/ProcessingStats.cs | 36 +- .../Scheduler/BackgroundTaskScheduler.cs | 30 +- .../Tracing/GethStyleTracer.cs | 4 +- .../Nethermind.Core.Test/BytesTests.cs | 142 +- .../Collections/SeqlockCacheTests.cs | 428 ++++++ .../Encoding/BlockDecoderTests.cs | 88 +- .../Json/ByteArrayConverterTests.cs | 65 + .../Json/Hash256ConverterTests.cs | 63 + .../Json/LongConverterTests.cs | 42 + .../Json/NullableUlongConverterTests.cs | 27 + .../Json/UInt256ConverterTests.cs | 135 ++ .../Nethermind.Core.Test/MCSLockTests.cs | 1 + .../Nethermind.Core.Test/RlpTests.cs | 10 + .../Nethermind.Core.Test/TestMemDb.cs | 66 +- src/Nethermind/Nethermind.Core/Address.cs | 12 +- .../Authentication/JwtAuthentication.cs | 367 +++-- src/Nethermind/Nethermind.Core/Block.cs | 7 + src/Nethermind/Nethermind.Core/Bloom.cs | 2 + .../Buffers/ArrayMemoryManager.cs | 22 + .../{ => Buffers}/DbSpanMemoryManager.cs | 25 +- .../Nethermind.Core/Caching/ISpanCache.cs | 36 - .../CappedArrayMemoryManager.cs | 45 - .../Collections/ArrayListCore.cs | 14 +- .../Collections/DictionaryExtensions.cs | 53 +- .../Collections/HashHelpers.cs | 132 -- .../Nethermind.Core/Collections/IHash64bit.cs | 23 + .../Collections/InsertionBehavior.cs | 9 - .../Collections/SeqlockCache.cs | 400 ++++++ .../Collections/SortedRealList.cs | 80 -- .../Nethermind.Core/Crypto/Hash256.cs | 4 + .../Nethermind.Core/Crypto/Signature.cs | 4 + .../Nethermind.Core/Extensions/Bytes.cs | 55 +- .../Extensions/DictionaryExtensions.cs | 30 - .../Extensions/IntExtensions.cs | 17 +- .../Extensions/SpanExtensions.cs | 431 ++++-- .../Nethermind.Core/IKeyValueStore.cs | 23 +- .../JsonConverters/ByteArrayConverter.cs | 109 +- .../KeyValueStoreExtensions.cs | 181 +-- .../PubSub/CompositePublisher.cs | 30 - src/Nethermind/Nethermind.Core/StorageCell.cs | 46 +- .../Utils/ConcurrentWriteBatcher.cs | 85 -- .../Nethermind.Db.Rocks/ColumnDb.cs | 116 +- .../Nethermind.Db.Rocks/ColumnsDb.cs | 21 +- .../Nethermind.Db.Rocks/Config/DbConfig.cs | 15 + .../Nethermind.Db.Rocks/Config/IDbConfig.cs | 15 + .../Nethermind.Db.Rocks/DbOnTheRocks.cs | 328 ++--- .../MergeOperatorAdapter.cs | 52 +- src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs | 96 +- .../CompressingStoreTests.cs | 11 +- .../LogIndex/LogIndexStorageCompactorTests.cs | 184 +++ .../LogIndexStorageIntegrationTests.cs | 1110 +++++++++++++++ .../LogIndex/LogIndexStorageTestExtensions.cs | 44 + .../LogIndex/MergeOperatorTests.cs | 247 ++++ src/Nethermind/Nethermind.Db/CompressingDb.cs | 101 +- src/Nethermind/Nethermind.Db/DbNames.cs | 1 + .../FullPruning/FullPruningDb.cs | 14 + src/Nethermind/Nethermind.Db/IColumnsDb.cs | 1 + .../Nethermind.Db/IMergeOperator.cs | 4 + .../Nethermind.Db/InMemoryColumnBatch.cs | 9 + .../Nethermind.Db/LogIndex/AverageStats.cs | 31 + .../Nethermind.Db/LogIndex/BlockReceipts.cs | 8 + .../Nethermind.Db/LogIndex/CompactingStats.cs | 14 + .../Nethermind.Db/LogIndex/Compactor.cs | 203 +++ .../LogIndex/CompressionAlgorithm.cs | 58 + .../Nethermind.Db/LogIndex/Compressor.cs | 208 +++ .../LogIndex/DisabledLogIndexStorage.cs | 38 + .../Nethermind.Db/LogIndex/ExecTimeStats.cs | 44 + .../Nethermind.Db/LogIndex/ILogIndexConfig.cs | 106 ++ .../LogIndex/ILogIndexStorage.cs | 56 + .../LogIndex/LogIndexAggregate.cs | 33 + .../Nethermind.Db/LogIndex/LogIndexColumns.cs | 14 + .../Nethermind.Db/LogIndex/LogIndexConfig.cs | 33 + .../LogIndex/LogIndexEnumerator.cs | 153 +++ .../LogIndex/LogIndexStateException.cs | 15 + .../Nethermind.Db/LogIndex/LogIndexStorage.cs | 1006 ++++++++++++++ .../LogIndex/LogIndexUpdateStats.cs | 114 ++ .../Nethermind.Db/LogIndex/MergeOperator.cs | 174 +++ .../Nethermind.Db/LogIndex/MergeOps.cs | 94 ++ .../LogIndex/PostMergeProcessingStats.cs | 29 + src/Nethermind/Nethermind.Db/MemDb.cs | 42 +- .../Nethermind.Db/Nethermind.Db.csproj | 1 + src/Nethermind/Nethermind.Db/ReadOnlyDb.cs | 20 +- .../Nethermind.Db/RocksDbSettings.cs | 12 +- .../Nethermind.Db/SimpleFilePublicKeyDb.cs | 63 +- .../GasBenchmarks/BlockBenchmarkHelper.cs | 232 ++++ .../GasBenchmarkColumnProvider.cs | 88 ++ .../GasBenchmarks/GasBenchmarkConfig.cs | 36 + .../GasBenchmarks/GasBlockBenchmarks.cs | 112 ++ .../GasBlockBuildingBenchmarks.cs | 321 +++++ .../GasBenchmarks/GasBlockOneBenchmarks.cs | 97 ++ .../GasBenchmarks/GasNewPayloadBenchmarks.cs | 1187 +++++++++++++++++ .../GasNewPayloadMeasuredBenchmarks.cs | 679 ++++++++++ .../GasBenchmarks/GasPayloadBenchmarks.cs | 258 ++++ .../GasPayloadExecuteBenchmarks.cs | 121 ++ .../GasBenchmarks/PayloadLoader.cs | 457 +++++++ .../Nethermind.Evm.Benchmark.csproj | 8 + .../Nethermind.Evm.Benchmark/Program.cs | 347 +++++ .../Properties/launchSettings.json | 8 + .../CodeInfoRepositoryTests.cs | 2 +- .../Nethermind.Evm/BadInstructionException.cs | 9 - .../BlockOverride.cs | 2 +- src/Nethermind/Nethermind.Evm/CallResult.cs | 4 +- .../Nethermind.Evm/CodeAnalysis/CodeInfo.cs | 90 +- .../CodeAnalysis/JumpDestinationAnalyzer.cs | 6 +- .../Nethermind.Evm/CodeInfoFactory.cs | 4 +- .../Nethermind.Evm/CodeInfoRepository.cs | 34 +- .../EvmObjectFormat/EofCodeInfo.cs | 13 +- .../Nethermind.Evm/ExecutionEnvironment.cs | 4 +- src/Nethermind/Nethermind.Evm/ICodeInfo.cs | 67 - .../Nethermind.Evm/ICodeInfoRepository.cs | 6 +- .../IOverridableCodeInfoRepository.cs | 2 +- .../Nethermind.Evm/IPrecompileProvider.cs | 2 +- .../Instructions/EvmInstructions.Call.cs | 2 +- .../Instructions/EvmInstructions.CodeCopy.cs | 2 +- .../Instructions/EvmInstructions.Create.cs | 2 +- .../EvmInstructions.Environment.cs | 2 +- .../Instructions/EvmInstructions.Eof.cs | 42 +- .../Nethermind.Evm/InvalidCodeException.cs | 10 - src/Nethermind/Nethermind.Evm/Metrics.cs | 17 + .../Precompiles/PrecompileInfo.cs | 17 - .../TransactionCollisionException.cs | 10 - .../TransactionProcessor.cs | 2 +- .../Nethermind.Evm/TransactionSubstate.cs | 4 +- .../VirtualMachine.ExtCodeCache.cs | 14 +- .../Nethermind.Evm/VirtualMachine.cs | 20 +- .../LogIndexBuilderTests.cs | 273 ++++ .../Eth/FacadeJsonContext.cs | 19 + .../Nethermind.Facade/Filters/LogFilter.cs | 7 +- .../Filters/LogIndexFilterVisitor.cs | 187 +++ .../Filters/Topics/AnyTopic.cs | 2 + .../Filters/Topics/AnyTopicsFilter.cs | 5 + .../Filters/Topics/OrExpression.cs | 5 + .../Filters/Topics/SequenceTopicsFilter.cs | 5 + .../Filters/Topics/SpecificTopic.cs | 3 + .../Filters/Topics/TopicExpression.cs | 2 + .../Filters/Topics/TopicsFilter.cs | 4 + .../Find/ILogIndexBuilder.cs | 21 + .../Find/IndexedLogFinder.cs | 92 ++ .../Nethermind.Facade/Find/LogFinder.cs | 101 +- .../Nethermind.Facade/Find/LogIndexBuilder.cs | 495 +++++++ .../Modules/BlockProcessingModule.cs | 1 + .../Modules/BlockTreeModule.cs | 32 +- .../Modules/BuiltInStepsModule.cs | 1 + .../Nethermind.Init/Modules/DbModule.cs | 1 - .../Modules/MainProcessingContext.cs | 26 +- .../Modules/NethermindModule.cs | 3 +- .../Nethermind.Init/Modules/RpcModules.cs | 3 + .../PruningTrieStateFactory.cs | 5 + .../Steps/InitializeBlockchain.cs | 1 - .../Nethermind.Init/Steps/StartLogIndex.cs | 28 + .../EthModuleBenchmarks.cs | 2 + .../JsonRpcProcessorTests.cs | 60 + .../Modules/BoundedModulePoolTests.cs | 4 +- .../Modules/Eth/EthRpcModuleTests.cs | 4 +- .../Modules/SingletonModulePoolTests.cs | 4 +- .../Modules/TestRpcBlockchain.cs | 5 +- .../Data/EthRpcJsonContext.cs | 38 + .../Nethermind.JsonRpc/ErrorCodes.cs | 55 +- .../Nethermind.JsonRpc/IJsonRpcResult.cs | 10 - .../Nethermind.JsonRpc/IStreamableResult.cs | 17 + .../JsonRpcConfigExtension.cs | 46 +- .../Nethermind.JsonRpc/JsonRpcContext.cs | 25 +- .../Nethermind.JsonRpc/JsonRpcProcessor.cs | 74 +- .../JsonRpcResponseJsonContext.cs | 15 + .../Nethermind.JsonRpc/JsonRpcService.cs | 14 +- .../Modules/DebugModule/TransactionBundle.cs | 1 - .../Modules/Eth/EthModuleFactory.cs | 5 +- .../Modules/Eth/EthRpcModule.cs | 33 + .../Nethermind.JsonRpc/Modules/Eth/Filter.cs | 16 +- .../Modules/LogIndex/ILogIndexRpcModule.cs | 17 + .../Modules/LogIndex/LogIndexRpcModule.cs | 54 + .../Modules/LogIndex/LogIndexStatus.cs | 18 + .../Nethermind.JsonRpc/Modules/ModuleType.cs | 2 +- .../EngineModuleTests.Synchronization.cs | 3 +- .../EngineModuleTests.V1.cs | 27 +- .../EngineModuleTests.V3.cs | 31 + .../EngineModuleTests.V5.cs | 68 + .../Nethermind.Merge.Plugin.Test/JwtTest.cs | 135 +- .../MergePluginTests.cs | 22 + .../TestBlockProcessorInterceptor.Setup.cs | 3 + .../Data/BlobsV1DirectResponse.cs | 68 + .../Data/BlobsV2DirectResponse.cs | 89 ++ .../Data/EngineApiJsonContext.cs | 35 + .../Data/ExecutionPayload.cs | 9 +- .../Handlers/GetBlobsHandler.cs | 41 +- .../Handlers/GetBlobsHandlerV2.cs | 44 +- .../Nethermind.Merge.Plugin/MergePlugin.cs | 2 + .../MetricsTests.cs | 41 + .../Metrics/MetricsController.cs | 8 +- .../P2P/Protocol.cs | 4 - .../P2P/ProtocolParser.cs | 3 - .../IDiscoveryMsgSerializersProvider.cs | 9 - .../MessageQueueTests.cs | 168 +++ .../P2P/ProtocolParserTests.cs | 19 - .../PeerManagerTests.cs | 86 +- .../Nethermind.Network/P2P/MessageQueue.cs | 11 +- .../AddCapabilityMessageSerializer.cs | 2 +- .../SyncPeerProtocolHandlerBase.cs | 3 - .../ZeroProtocolHandlerBase.cs | 18 +- .../PeerEqualityComparer.cs | 22 - src/Nethermind/Nethermind.Network/Timeouts.cs | 5 - .../Rpc/OptimismEthRpcModuleTest.cs | 4 +- .../CL/L1Bridge/EthereumL1Bridge.cs | 8 - .../Rpc/OptimismEthModuleFactory.cs | 8 +- .../Rpc/OptimismEthRpcModule.cs | 3 + .../Ethereum/ContextWithMocks.cs | 2 +- .../Steps/EthereumStepsManagerTests.cs | 3 +- .../Ethereum/Api/ApiBuilder.cs | 4 +- .../Ethereum/EthereumRunner.cs | 15 - .../Ethereum/JsonRpcRunner.cs | 4 +- .../Modules/NethermindRunnerModule.cs | 3 +- .../Nethermind.Runner/JsonRpc/Bootstrap.cs | 4 +- .../Nethermind.Runner/JsonRpc/Startup.cs | 482 +++++-- src/Nethermind/Nethermind.Runner/Program.cs | 18 +- .../configs/base-mainnet.json | 4 +- .../configs/base-sepolia.json | 4 +- .../Nethermind.Runner/configs/chiado.json | 4 +- .../Nethermind.Runner/configs/gnosis.json | 4 +- .../configs/joc-mainnet.json | 6 +- .../configs/joc-testnet.json | 6 +- .../configs/linea-mainnet.json | 6 +- .../configs/linea-sepolia.json | 4 +- .../Nethermind.Runner/configs/mainnet.json | 4 +- .../Nethermind.Runner/configs/op-mainnet.json | 4 +- .../Nethermind.Runner/configs/op-sepolia.json | 4 +- .../Nethermind.Runner/configs/sepolia.json | 4 +- .../Nethermind.Runner/configs/spaceneth.json | 1 - .../configs/spaceneth_persistent.json | 1 - .../configs/worldchain-mainnet.json | 4 +- .../configs/worldchain-sepolia.json | 4 +- .../Nethermind.Runner/packages.lock.json | 240 ++-- .../CountingTextReader.cs | 83 -- .../CountingTextWriter.cs | 42 - .../EthereumJsonSerializer.cs | 206 ++- .../ForcedNumberConversion.cs | 28 +- .../Hash256Converter.cs | 30 +- .../HexWriter.cs | 428 ++++++ .../LongConverter.cs | 215 ++- .../NullableULongConverter.cs | 52 +- .../PubSub/LogPublisher.cs | 31 - .../UInt256Converter.cs | 56 +- .../ULongConverter.cs | 161 ++- .../ValueHash256Converter.cs | 4 +- .../BlockDecoder.cs | 32 +- .../Nethermind.Serialization.Rlp/Rlp.cs | 3 +- .../Nethermind.Serialization.Rlp/TxDecoder.cs | 19 + .../Ssz.BasicTypes.cs | 2 + .../Ssz.Containers.cs | 18 - .../ShutterIntegrationTests.cs | 4 +- .../StorageProviderTests.cs | 23 +- .../TrieStoreScopeProviderTests.cs | 22 + .../OverridableCodeInfoRepository.cs | 10 +- .../PersistentStorageProvider.cs | 1 - .../Nethermind.State/PreBlockCaches.cs | 18 +- .../PrewarmerScopeProvider.cs | 74 +- .../Nethermind.State/Proofs/PatriciaTrieT.cs | 6 +- .../Nethermind.State/Proofs/ReceiptTrie.cs | 16 +- .../Nethermind.State/Proofs/TxTrie.cs | 33 +- .../Nethermind.State/Proofs/WithdrawalTrie.cs | 2 +- .../TrieStoreScopeProvider.cs | 17 +- .../FastSync/StateSyncFeedHealingTests.cs | 1 + .../SyncProgressResolverTests.cs | 22 +- .../SyncServerTests.cs | 2 +- .../ParallelSync/SyncProgressResolver.cs | 8 +- .../ClientTypeStrategy.cs | 44 - .../AllocationStrategies/NullStrategy.cs | 26 - .../AllocationStrategies/StaticStrategy.cs | 24 - .../StrategySelectionType.cs | 11 - .../SyncPeerEventArgs.cs | 20 - .../Nethermind.Taiko/Tdx/TdxsClient.cs | 4 +- .../StateTestTxTracer.cs | 3 +- .../Nethermind.Trie.Test/CacheTests.cs | 1 + .../Nethermind.Trie.Test/HexPrefixTests.cs | 1 + .../Nethermind.Trie.Test/NibbleTests.cs | 1 + .../NodeStorageFactoryTests.cs | 1 + .../Nethermind.Trie.Test/NodeStorageTests.cs | 34 +- .../OverlayTrieStoreTests.cs | 1 + .../MaxBlockInCachePruneStrategyTests.cs | 3 +- .../MinBlockInCachePruneStrategyTests.cs | 3 +- .../Pruning/TreeStoreTests.cs | 34 +- .../PruningScenariosTests.cs | 3 +- .../Nethermind.Trie.Test/RawTrieStoreTests.cs | 1 + .../Nethermind.Trie.Test/TinyTreePathTests.cs | 1 + .../TrackingCappedArrayPoolTests.cs | 1 + .../Nethermind.Trie.Test/TreePathTests.cs | 1 + .../TrieNodeResolverWithReadFlagsTests.cs | 1 + .../Nethermind.Trie.Test/TrieTests.cs | 4 + .../Nethermind.Trie.Test/VisitingTests.cs | 1 + .../VisitorProgressTrackerTests.cs | 1 + .../Nethermind.Trie/NodeStorageCache.cs | 13 +- .../Nethermind.Trie/PatriciaTree.cs | 22 - .../Nethermind.Trie/PreCachedTrieStore.cs | 37 +- .../Nethermind.Trie/Pruning/TrieStore.cs | 5 - .../TrackingCappedArrayPool.cs | 64 +- .../Nethermind.Trie/TrieNode.Decoder.cs | 23 +- src/Nethermind/Nethermind.Trie/TrieNode.cs | 1 - .../Nethermind.Trie/VisitContext.cs | 1 - .../BlobTxStorageTests.cs | 100 ++ .../Collections/SortedPoolTests.cs | 2 + ...mpetingTransactionEqualityComparerTests.cs | 1 + .../DelegatedAccountFilterTest.cs | 17 +- .../NonceManagerTests.cs | 2 + .../ReceiptStorageTests.cs | 2 + .../Nethermind.TxPool.Test/RetryCacheTests.cs | 83 +- .../Nethermind.TxPool.Test/TestBlockTree.cs | 108 ++ .../TestChainHeadInfoProvider.cs | 35 + .../TransactionExtensionsTests.cs | 1 + .../TransactionPoolInfoProviderTests.cs | 2 + .../TxBroadcasterTests.cs | 40 +- .../TxPoolTests.Blobs.cs | 154 ++- .../Nethermind.TxPool.Test/TxPoolTests.cs | 137 +- .../Nethermind.TxPool/BlobTxStorage.cs | 40 + .../Collections/BlobTxDistinctSortedPool.cs | 42 +- .../PersistentBlobTxDistinctSortedPool.cs | 126 ++ src/Nethermind/Nethermind.TxPool/ITxPool.cs | 3 +- .../Nethermind.TxPool/ITxStorage.cs | 3 + .../Nethermind.TxPool/NullBlobTxStorage.cs | 2 + .../Nethermind.TxPool/NullTxPool.cs | 3 +- .../Nethermind.TxPool/RetryCache.cs | 6 +- .../Nethermind.TxPool/TxBroadcaster.cs | 9 +- .../TxNonceTxPoolReserveSealer.cs | 0 .../Nethermind.TxPool/TxPool.NonceInfo.cs | 0 src/Nethermind/Nethermind.TxPool/TxPool.cs | 7 +- .../Nethermind.Xdc.Test/BlockInfoTests.cs | 1 - .../Helpers/XdcTestBlockchain.cs | 6 +- .../ModuleTests/ProposedBlockTests.cs | 1 - .../ModuleTests/RewardTests.cs | 16 +- .../ModuleTests/SyncInfoDecoderTests.cs | 175 +++ .../ModuleTests/XdcReorgModuleTests.cs | 1 - .../Errors/QuorumCertificateException.cs | 12 - src/Nethermind/Nethermind.Xdc/IXdcSealer.cs | 11 - .../Nethermind.Xdc/RLP/SyncInfoDecoder.cs | 93 ++ .../Nethermind.Xdc/RLP/VoteDecoder.cs | 2 +- src/Nethermind/Nethermind.Xdc/XdcDbNames.cs | 12 - src/Nethermind/Nethermind.Xdc/XdcModule.cs | 5 +- .../XdcStateSyncAllocationStrategyFactory.cs | 34 + src/Nethermind/Nethermind.slnx | 1 + tools/JitAsm/DisassemblyParser.cs | 112 ++ tools/JitAsm/InstructionAnnotator.cs | 441 ++++++ tools/JitAsm/InstructionDb.cs | 156 +++ tools/JitAsm/InstructionDbBuilder.cs | 265 ++++ tools/JitAsm/JitAsm.csproj | 12 + tools/JitAsm/JitAsm.slnx | 3 + tools/JitAsm/JitRunner.cs | 269 ++++ tools/JitAsm/MethodResolver.cs | 393 ++++++ tools/JitAsm/Program.cs | 581 ++++++++ tools/JitAsm/README.md | 494 +++++++ tools/JitAsm/StaticCtorDetector.cs | 175 +++ tools/Kute/Nethermind.Tools.Kute/Program.cs | 14 +- tools/gas-benchmarks | 1 + 515 files changed, 21839 insertions(+), 3713 deletions(-) create mode 100644 .github/workflows/ci-surge.yml create mode 100644 .github/workflows/gas-benchmarks-bdn.yml create mode 100644 .github/workflows/pr-labeler.yml create mode 100644 scripts/benchmarks/compare_bdn_results.py create mode 100644 scripts/benchmarks/merge_bdn_results.py create mode 100644 src/Nethermind/Nethermind.Benchmark/Core/FastHashBenchmarks.cs create mode 100644 src/Nethermind/Nethermind.Benchmark/Core/SeqlockCacheBenchmarks.cs create mode 100644 src/Nethermind/Nethermind.Benchmark/Scheduler/BackgroundTaskSchedulerBenchmarks.cs create mode 100644 src/Nethermind/Nethermind.Blockchain.Test/Filters/LogIndexFilterVisitorTests.cs create mode 100644 src/Nethermind/Nethermind.Blockchain.Test/Filters/TestProcessingContext.cs delete mode 100644 src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessingScope.cs delete mode 100644 src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessorSource.cs create mode 100644 src/Nethermind/Nethermind.Consensus/Processing/IProcessingStats.cs delete mode 100644 src/Nethermind/Nethermind.Consensus/Processing/OverridableTxProcessingScope.cs create mode 100644 src/Nethermind/Nethermind.Core.Test/Collections/SeqlockCacheTests.cs create mode 100644 src/Nethermind/Nethermind.Core/Buffers/ArrayMemoryManager.cs rename src/Nethermind/Nethermind.Core/{ => Buffers}/DbSpanMemoryManager.cs (58%) delete mode 100644 src/Nethermind/Nethermind.Core/Caching/ISpanCache.cs delete mode 100644 src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs delete mode 100644 src/Nethermind/Nethermind.Core/Collections/HashHelpers.cs create mode 100644 src/Nethermind/Nethermind.Core/Collections/IHash64bit.cs delete mode 100644 src/Nethermind/Nethermind.Core/Collections/InsertionBehavior.cs create mode 100644 src/Nethermind/Nethermind.Core/Collections/SeqlockCache.cs delete mode 100644 src/Nethermind/Nethermind.Core/Collections/SortedRealList.cs delete mode 100644 src/Nethermind/Nethermind.Core/Extensions/DictionaryExtensions.cs delete mode 100644 src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs delete mode 100644 src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs create mode 100644 src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageCompactorTests.cs create mode 100644 src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageIntegrationTests.cs create mode 100644 src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageTestExtensions.cs create mode 100644 src/Nethermind/Nethermind.Db.Test/LogIndex/MergeOperatorTests.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/AverageStats.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/BlockReceipts.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/CompactingStats.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/Compactor.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/CompressionAlgorithm.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/Compressor.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/DisabledLogIndexStorage.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/ExecTimeStats.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/ILogIndexConfig.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/ILogIndexStorage.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexAggregate.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexColumns.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexConfig.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexEnumerator.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexStateException.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexStorage.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexUpdateStats.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/MergeOperator.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/MergeOps.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/PostMergeProcessingStats.cs create mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/BlockBenchmarkHelper.cs create mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBenchmarkColumnProvider.cs create mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBenchmarkConfig.cs create mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockBenchmarks.cs create mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockBuildingBenchmarks.cs create mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockOneBenchmarks.cs create mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasNewPayloadBenchmarks.cs create mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasNewPayloadMeasuredBenchmarks.cs create mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasPayloadBenchmarks.cs create mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasPayloadExecuteBenchmarks.cs create mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/PayloadLoader.cs create mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/Program.cs create mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/Properties/launchSettings.json delete mode 100644 src/Nethermind/Nethermind.Evm/BadInstructionException.cs rename src/Nethermind/{Nethermind.Facade/Proxy/Models/Simulate => Nethermind.Evm}/BlockOverride.cs (96%) delete mode 100644 src/Nethermind/Nethermind.Evm/ICodeInfo.cs delete mode 100644 src/Nethermind/Nethermind.Evm/InvalidCodeException.cs delete mode 100644 src/Nethermind/Nethermind.Evm/Precompiles/PrecompileInfo.cs delete mode 100644 src/Nethermind/Nethermind.Evm/TransactionCollisionException.cs create mode 100644 src/Nethermind/Nethermind.Facade.Test/LogIndexBuilderTests.cs create mode 100644 src/Nethermind/Nethermind.Facade/Eth/FacadeJsonContext.cs create mode 100644 src/Nethermind/Nethermind.Facade/Filters/LogIndexFilterVisitor.cs create mode 100644 src/Nethermind/Nethermind.Facade/Find/ILogIndexBuilder.cs create mode 100644 src/Nethermind/Nethermind.Facade/Find/IndexedLogFinder.cs create mode 100644 src/Nethermind/Nethermind.Facade/Find/LogIndexBuilder.cs create mode 100644 src/Nethermind/Nethermind.Init/Steps/StartLogIndex.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc/Data/EthRpcJsonContext.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/IJsonRpcResult.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc/IStreamableResult.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc/JsonRpcResponseJsonContext.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/ILogIndexRpcModule.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexRpcModule.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexStatus.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV1DirectResponse.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV2DirectResponse.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/EngineApiJsonContext.cs delete mode 100644 src/Nethermind/Nethermind.Network.Discovery/Serializers/IDiscoveryMsgSerializersProvider.cs create mode 100644 src/Nethermind/Nethermind.Network.Test/MessageQueueTests.cs delete mode 100644 src/Nethermind/Nethermind.Network/PeerEqualityComparer.cs delete mode 100644 src/Nethermind/Nethermind.Serialization.Json/CountingTextReader.cs delete mode 100644 src/Nethermind/Nethermind.Serialization.Json/CountingTextWriter.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Json/HexWriter.cs delete mode 100644 src/Nethermind/Nethermind.Serialization.Json/PubSub/LogPublisher.cs delete mode 100644 src/Nethermind/Nethermind.Serialization.Ssz/Ssz.Containers.cs delete mode 100644 src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/ClientTypeStrategy.cs delete mode 100644 src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/NullStrategy.cs delete mode 100644 src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StaticStrategy.cs delete mode 100644 src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StrategySelectionType.cs delete mode 100644 src/Nethermind/Nethermind.Synchronization/SyncPeerEventArgs.cs create mode 100644 src/Nethermind/Nethermind.TxPool.Test/TestBlockTree.cs create mode 100644 src/Nethermind/Nethermind.TxPool.Test/TestChainHeadInfoProvider.cs delete mode 100644 src/Nethermind/Nethermind.TxPool/TxNonceTxPoolReserveSealer.cs delete mode 100644 src/Nethermind/Nethermind.TxPool/TxPool.NonceInfo.cs create mode 100644 src/Nethermind/Nethermind.Xdc.Test/ModuleTests/SyncInfoDecoderTests.cs delete mode 100644 src/Nethermind/Nethermind.Xdc/Errors/QuorumCertificateException.cs delete mode 100644 src/Nethermind/Nethermind.Xdc/IXdcSealer.cs create mode 100644 src/Nethermind/Nethermind.Xdc/RLP/SyncInfoDecoder.cs delete mode 100644 src/Nethermind/Nethermind.Xdc/XdcDbNames.cs create mode 100644 src/Nethermind/Nethermind.Xdc/XdcStateSyncAllocationStrategyFactory.cs create mode 100644 tools/JitAsm/DisassemblyParser.cs create mode 100644 tools/JitAsm/InstructionAnnotator.cs create mode 100644 tools/JitAsm/InstructionDb.cs create mode 100644 tools/JitAsm/InstructionDbBuilder.cs create mode 100644 tools/JitAsm/JitAsm.csproj create mode 100644 tools/JitAsm/JitAsm.slnx create mode 100644 tools/JitAsm/JitRunner.cs create mode 100644 tools/JitAsm/MethodResolver.cs create mode 100644 tools/JitAsm/Program.cs create mode 100644 tools/JitAsm/README.md create mode 100644 tools/JitAsm/StaticCtorDetector.cs create mode 160000 tools/gas-benchmarks diff --git a/.github/workflows/build-tools.yml b/.github/workflows/build-tools.yml index 8038f514a80f..4369e74a07b4 100644 --- a/.github/workflows/build-tools.yml +++ b/.github/workflows/build-tools.yml @@ -21,6 +21,7 @@ jobs: - Evm/Evm.slnx - HiveCompare/HiveCompare.slnx - HiveConsensusWorkflowGenerator/HiveConsensusWorkflowGenerator.slnx + - JitAsm/JitAsm.slnx - Kute/Kute.slnx # - SchemaGenerator/SchemaGenerator.slnx - SendBlobs/SendBlobs.slnx diff --git a/.github/workflows/ci-surge.yml b/.github/workflows/ci-surge.yml new file mode 100644 index 000000000000..87ff8b8c4688 --- /dev/null +++ b/.github/workflows/ci-surge.yml @@ -0,0 +1,167 @@ +name: "Surge Integration Tests" + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths: + - "src/Nethermind/Nethermind.Taiko/**" + - ".github/workflows/ci-surge.yml" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + integration_tests: + if: >- + ${{ github.event.pull_request.draft == false + && !startsWith(github.head_ref, 'release-please') }} + name: Integration tests + runs-on: [ubuntu-latest] + timeout-minutes: 45 + env: + SURGE_TAIKO_MONO_DIR: surge-taiko-mono + PACAYA_FORK_TAIKO_MONO_DIR: pacaya-fork-taiko-mono + SHASTA_FORK_TAIKO_MONO_DIR: shasta-fork-taiko-mono + + steps: + - name: Checkout Nethermind + uses: actions/checkout@v6 + + - name: Checkout surge-taiko-mono + uses: actions/checkout@v6 + with: + repository: NethermindEth/surge-taiko-mono + path: ${{ env.SURGE_TAIKO_MONO_DIR }} + ref: surge-shasta + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: ${{ env.SURGE_TAIKO_MONO_DIR }}/go.mod + cache: true + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + run_install: false + + - name: Install Node.js + uses: actions/setup-node@v5 + with: + node-version: 20 + cache: pnpm + cache-dependency-path: ${{ env.SURGE_TAIKO_MONO_DIR }}/pnpm-lock.yaml + + - name: Install dependencies + working-directory: ${{ env.SURGE_TAIKO_MONO_DIR }} + shell: bash + run: pnpm install + + - name: Checkout Pacaya fork + uses: actions/checkout@v6 + with: + repository: NethermindEth/surge-taiko-mono + path: >- + ${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, + env.PACAYA_FORK_TAIKO_MONO_DIR) }} + ref: jmadibekov/pacaya-dummy-verifiers-fix + + - name: Checkout Shasta fork + uses: actions/checkout@v6 + with: + repository: NethermindEth/surge-taiko-mono + path: >- + ${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, + env.SHASTA_FORK_TAIKO_MONO_DIR) }} + ref: surge-alethia-protocol-v3.0.0 + + - name: Install pnpm dependencies for pacaya fork taiko-mono + working-directory: >- + ${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, + env.PACAYA_FORK_TAIKO_MONO_DIR) }} + run: cd ./packages/protocol && pnpm install + + - name: Install pnpm dependencies for shasta fork taiko-mono + working-directory: >- + ${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, + env.SHASTA_FORK_TAIKO_MONO_DIR) }} + run: cd ./packages/protocol && pnpm install + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker Build Nethermind Client + run: | + image_name="nethermindeth/nethermind" + image_tag="${GITHUB_SHA:0:8}" + full_image="${image_name}:${image_tag}" + + echo "Building Docker image: ${full_image}" + + docker buildx build . \ + --platform linux/amd64 \ + -f Dockerfile \ + -t "${full_image}" \ + --load \ + --build-arg BUILD_CONFIG=release \ + --build-arg CI=true \ + --build-arg COMMIT_HASH=${{ github.sha }} \ + --build-arg SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) + + echo "IMAGE_TAG=${full_image}" >> $GITHUB_ENV + + echo "Verifying image exists locally:" + docker images | grep "${image_name}" | grep "${image_tag}" || (echo "Error: Image not found locally" && exit 1) + + - name: Install yq + run: | + sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + sudo chmod +x /usr/local/bin/yq + yq --version + + - name: Update taiko-client docker-compose.yml with new image + working-directory: >- + ${{ env.SURGE_TAIKO_MONO_DIR }}/packages/taiko-client/internal/docker/nodes + run: | + docker_compose_file="docker-compose.yml" + if [ -f "$docker_compose_file" ]; then + echo "Current image in docker-compose.yml:" + yq eval '.services.l2_nmc.image' "$docker_compose_file" + + echo "Updating docker-compose.yml with image: ${IMAGE_TAG}" + yq eval '.services.l2_nmc.image = "'"${IMAGE_TAG}"'"' -i "$docker_compose_file" + + yq eval '.services.l2_nmc.pull_policy = "never"' -i "$docker_compose_file" + + echo "Updated image in docker-compose.yml:" + yq eval '.services.l2_nmc.image' "$docker_compose_file" + + echo "Pull policy set to:" + yq eval '.services.l2_nmc.pull_policy' "$docker_compose_file" + else + echo "Warning: docker-compose.yml not found at expected path" + exit 1 + fi + + - name: Run integration tests + working-directory: >- + ${{ env.SURGE_TAIKO_MONO_DIR }}/packages/taiko-client + env: + L2_NODE: l2_nmc + run: >- + SHASTA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} + PACAYA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} + make test + + - name: Codecov.io + uses: codecov/codecov-action@v5 + with: + files: ${{ env.SURGE_TAIKO_MONO_DIR }}/packages/taiko-client/coverage.out + flags: taiko-client + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/ci-taiko.yml b/.github/workflows/ci-taiko.yml index d01e20692415..2d92a38090aa 100644 --- a/.github/workflows/ci-taiko.yml +++ b/.github/workflows/ci-taiko.yml @@ -1,4 +1,4 @@ -name: "Nethermind/Ethereum Taiko Client CI Tests" +name: "Taiko Integration Tests" on: pull_request: @@ -18,25 +18,22 @@ jobs: && !startsWith(github.head_ref, 'release-please') }} name: Integration tests runs-on: [ubuntu-latest] - timeout-minutes: 30 + timeout-minutes: 45 env: - OLD_FORK_TAIKO_MONO_DIR: old-fork-taiko-mono - TAIKO_MONO_MAIN_DIR: taiko-mono-main + TAIKO_MONO_DIR: taiko-mono PACAYA_FORK_TAIKO_MONO_DIR: pacaya-fork-taiko-mono SHASTA_FORK_TAIKO_MONO_DIR: shasta-fork-taiko-mono - strategy: - matrix: - execution_node: [l2_nmc] - steps: - - uses: actions/checkout@v6 + - name: Checkout Nethermind + uses: actions/checkout@v6 - - uses: actions/checkout@v6 + - name: Checkout taiko-mono + uses: actions/checkout@v6 with: - repository: NethermindEth/surge-taiko-mono - path: ${{ env.TAIKO_MONO_MAIN_DIR }} - ref: surge-shasta + repository: taikoxyz/taiko-mono + path: ${{ env.TAIKO_MONO_DIR }} + ref: main - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 @@ -44,14 +41,9 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version-file: ${{ env.TAIKO_MONO_MAIN_DIR }}/go.mod + go-version-file: ${{ env.TAIKO_MONO_DIR }}/go.mod cache: true - - name: Set up Git to use HTTPS - shell: bash - run: | - git config --global url."https://github.com/".insteadOf "git@github.com:" - - name: Install pnpm uses: pnpm/action-setup@v4 with: @@ -63,38 +55,40 @@ jobs: with: node-version: 20 cache: pnpm - cache-dependency-path: ${{ env.TAIKO_MONO_MAIN_DIR }}/pnpm-lock.yaml + cache-dependency-path: ${{ env.TAIKO_MONO_DIR }}/pnpm-lock.yaml - name: Install dependencies - working-directory: ${{ env.TAIKO_MONO_MAIN_DIR }} + working-directory: ${{ env.TAIKO_MONO_DIR }} shell: bash run: pnpm install - - uses: actions/checkout@v6 + - name: Checkout Pacaya fork + uses: actions/checkout@v6 with: repository: taikoxyz/taiko-mono path: >- - ${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, + ${{ format('{0}/{1}', env.TAIKO_MONO_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} ref: taiko-alethia-protocol-v2.3.0-devnet-shasta-test - - uses: actions/checkout@v6 + - name: Checkout Shasta fork + uses: actions/checkout@v6 with: repository: taikoxyz/taiko-mono path: >- - ${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, + ${{ format('{0}/{1}', env.TAIKO_MONO_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} ref: taiko-alethia-protocol-v3.0.0 - name: Install pnpm dependencies for pacaya fork taiko-mono working-directory: >- - ${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, + ${{ format('{0}/{1}', env.TAIKO_MONO_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} run: cd ./packages/protocol && pnpm install - name: Install pnpm dependencies for shasta fork taiko-mono working-directory: >- - ${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, + ${{ format('{0}/{1}', env.TAIKO_MONO_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} run: cd ./packages/protocol && pnpm install @@ -132,7 +126,7 @@ jobs: - name: Update taiko-client docker-compose.yml with new image working-directory: >- - ${{ env.TAIKO_MONO_MAIN_DIR }}/packages/taiko-client/internal/docker/nodes + ${{ env.TAIKO_MONO_DIR }}/packages/taiko-client/internal/docker/nodes run: | docker_compose_file="docker-compose.yml" if [ -f "$docker_compose_file" ]; then @@ -154,20 +148,20 @@ jobs: exit 1 fi - - name: Run Tests on ${{ matrix.execution_node }} execution engine + - name: Run integration tests working-directory: >- - ${{ env.TAIKO_MONO_MAIN_DIR }}/packages/taiko-client + ${{ env.TAIKO_MONO_DIR }}/packages/taiko-client env: - L2_NODE: ${{ matrix.execution_node }} + L2_NODE: l2_nmc run: >- - SHASTA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} - PACAYA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} + SHASTA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.TAIKO_MONO_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} + PACAYA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.TAIKO_MONO_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} make test - name: Codecov.io uses: codecov/codecov-action@v5 with: - files: ${{ env.TAIKO_MONO_MAIN_DIR }}/packages/taiko-client/coverage.out + files: ${{ env.TAIKO_MONO_DIR }}/packages/taiko-client/coverage.out flags: taiko-client env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/gas-benchmarks-bdn.yml b/.github/workflows/gas-benchmarks-bdn.yml new file mode 100644 index 000000000000..86c2aacabbba --- /dev/null +++ b/.github/workflows/gas-benchmarks-bdn.yml @@ -0,0 +1,161 @@ +name: Gas Benchmarks (BDN) + +on: + push: + branches: [master] + paths: ['src/Nethermind/**'] + pull_request: + branches: [master] + types: [labeled] + workflow_dispatch: + inputs: + mode: + description: 'Benchmark mode (Block or EVM)' + required: false + default: 'Block' + type: choice + options: + - Block + - EVM + filter: + description: 'BDN filter pattern (optional, e.g. *MULMOD*)' + required: false + default: '' + +env: + DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: "1" + TERM: xterm + +jobs: + resolve: + if: >- + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'pull_request' && github.event.label.name == 'performance is good') + runs-on: ubuntu-slim + outputs: + mode: ${{ steps.check.outputs.mode }} + filter: ${{ steps.check.outputs.filter }} + steps: + - name: Resolve inputs + id: check + shell: bash + run: | + set -euo pipefail + + mode="Block" + filter="" + + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + mode="${{ inputs.mode }}" + filter="${{ inputs.filter }}" + fi + + { + echo "mode=${mode}" + echo "filter=${filter}" + } >> "$GITHUB_OUTPUT" + + benchmark: + needs: resolve + if: always() && !cancelled() && needs.resolve.result == 'success' + runs-on: [self-hosted, Linux, X64, benchmark] + strategy: + fail-fast: false + matrix: + chunk: [1, 2, 3, 4, 5] + timeout-minutes: 240 + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + lfs: true + submodules: true + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: Build benchmarks + run: dotnet build src/Nethermind/Nethermind.Evm.Benchmark -c Release + + - name: Run benchmarks (chunk ${{ matrix.chunk }}/5) + shell: bash + run: | + MODE="${{ needs.resolve.outputs.mode }}" + FILTER="${{ needs.resolve.outputs.filter }}" + + ARGS="--inprocess --mode=$MODE --chunk ${{ matrix.chunk }}/5" + if [[ -n "$FILTER" ]]; then + ARGS="$ARGS --filter $FILTER" + fi + + dotnet run --project src/Nethermind/Nethermind.Evm.Benchmark -c Release --no-build -- $ARGS + + - name: Upload results + if: always() + uses: actions/upload-artifact@v4 + with: + name: bdn-results-${{ matrix.chunk }} + path: BenchmarkDotNet.Artifacts/results/*-full.json + retention-days: 90 + + collect: + needs: [resolve, benchmark] + if: always() && !cancelled() && needs.resolve.result == 'success' && needs.benchmark.result == 'success' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Download all results + uses: actions/download-artifact@v4 + with: + pattern: bdn-results-* + path: results/ + merge-multiple: true + + - name: Merge chunk results + run: python3 scripts/benchmarks/merge_bdn_results.py results/ pr-results.json + + - name: Upload merged results + uses: actions/upload-artifact@v4 + with: + name: bdn-results-merged + path: pr-results.json + retention-days: 90 + + # On master push: cache results as baseline for future PR comparisons + - name: Cache master baseline + if: github.ref == 'refs/heads/master' + run: cp pr-results.json baseline.json + + - name: Save master cache + if: github.ref == 'refs/heads/master' + uses: actions/cache/save@v4 + with: + path: baseline.json + key: gas-benchmark-bdn-${{ github.sha }} + + # On PR: restore master baseline and compare + - name: Restore master baseline + if: github.event_name == 'pull_request' + id: cache-restore + uses: actions/cache/restore@v4 + with: + path: baseline.json + key: gas-benchmark-bdn- + restore-keys: gas-benchmark-bdn- + + - name: Compare against master + if: github.event_name == 'pull_request' && steps.cache-restore.outputs.cache-hit == 'true' + run: python3 scripts/benchmarks/compare_bdn_results.py baseline.json pr-results.json > comparison.md + + - name: Post comparison to PR + if: github.event_name == 'pull_request' && steps.cache-restore.outputs.cache-hit == 'true' + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: gas-benchmark-bdn + path: comparison.md + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml new file mode 100644 index 000000000000..9ded94bebe46 --- /dev/null +++ b/.github/workflows/pr-labeler.yml @@ -0,0 +1,201 @@ +name: PR labeler + +on: + pull_request_target: + types: [opened, edited, ready_for_review, synchronize] + +jobs: + label: + name: Label PR by type + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Apply labels from PR template checkboxes + uses: actions/github-script@v7 + with: + script: | + // --- Checkbox rules --- + const checkboxToLabel = { + 'Bugfix (a non-breaking change that fixes an issue)': 'bug fix + reliability', + 'New feature (a non-breaking change that adds functionality)': 'new feature', + 'Breaking change (a change that causes existing functionality not to work as expected)': 'BREAKING', + 'Optimization': 'performance is good', + 'Refactoring': 'refactoring', + 'Documentation update': 'docs', + 'Build-related changes': 'build changes', + }; + + // --- Title prefix rules (conventional commits) --- + const prefixToLabel = { + 'fix': 'bug fix + reliability', + 'feat': 'new feature', + 'perf': 'performance is good', + 'refactor': 'refactoring', + 'chore': 'minor', + 'docs': 'docs', + 'test': 'test', + 'ci': 'build changes', + 'build': 'build changes', + }; + + // --- Path rules --- + const pathToLabel = [ + { pattern: 'src/Nethermind/Nethermind.Optimism', label: 'optimism' }, + { pattern: 'src/Nethermind/Nethermind.Taiko', label: 'taiko' }, + { pattern: 'src/Nethermind/Nethermind.Xdc', label: 'XDC' }, + { pattern: 'src/Nethermind/Nethermind.Network', label: 'network' }, + { pattern: 'src/Nethermind/Nethermind.Evm', label: 'evm' }, + { pattern: 'src/Nethermind/Nethermind.Trie', label: 'trie' }, + { pattern: 'src/Nethermind/Nethermind.State', label: 'state+storage' }, + { pattern: 'src/Nethermind/Nethermind.Synchronization', label: 'sync' }, + { pattern: 'src/Nethermind/Nethermind.JsonRpc', label: 'rpc' }, + { pattern: 'src/Nethermind/Nethermind.Db.Rocks', label: 'rocksdb' }, + { pattern: 'src/Nethermind/Nethermind.Db', label: 'database' }, + { pattern: 'src/Nethermind/Nethermind.Init/Modules/DbModule.cs', label: 'database' }, + { pattern: 'src/Nethermind/Nethermind.Runner/configs', label: 'configuration' }, + { pattern: 'src/Nethermind/Nethermind.Config', label: 'configuration' }, + { pattern: 'README.md', label: 'docs' }, + { pattern: 'AGENTS.md', label: 'agentic 🤖' }, + { pattern: '.github/', label: 'devops' }, + { pattern: 'Directory.Packages.props', label: 'dependencies' }, + { pattern: 'tools/', label: 'tools' }, + ]; + + // test label: applied when ALL changed files are in a .Test project + const testOnlyLabel = 'test'; + + const checkboxLabels = new Set(Object.values(checkboxToLabel)); + const prefixLabels = new Set(Object.values(prefixToLabel)); + const pathLabels = new Set(pathToLabel.map(r => r.label)); + const managedLabels = new Set([...checkboxLabels, ...prefixLabels, ...pathLabels, testOnlyLabel, 'cleanup', 'snap sync']); + + const body = context.payload.pull_request.body || ''; + const title = context.payload.pull_request.title || ''; + + const desiredLabels = new Set(); + + // Evaluate checkboxes + for (const [text, label] of Object.entries(checkboxToLabel)) { + const escaped = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + if (new RegExp(`-\\s*\\[\\s*[xX]\\s*\\]\\s*${escaped}`).test(body)) { + desiredLabels.add(label); + } + } + + // Evaluate "Other" checkbox with keyword matching + const otherMatch = body.match(/-\s*\[\s*[xX]\s*\]\s*Other:\s*(.+)/); + if (otherMatch) { + const otherText = otherMatch[1].toLowerCase(); + if (/\btest/.test(otherText)) desiredLabels.add('test'); + if (/\btool/.test(otherText)) desiredLabels.add('tools'); + if (/\bagent/.test(otherText)) desiredLabels.add('agentic 🤖'); + if (/\bdoc/.test(otherText)) desiredLabels.add('docs'); + } + + // Evaluate title prefix: supports "fix:", "fix(scope):", "(fix)", "[fix]" + const prefixMatch = title.match(/^[(\[]?(\w+)[)\]]?[\s(:/]/) + if (prefixMatch) { + const prefix = prefixMatch[1].toLowerCase(); + if (prefixToLabel[prefix]) { + desiredLabels.add(prefixToLabel[prefix]); + } + } + + // Evaluate title for EIP mentions (eip-1234, EIP 1234, eip1234, etc.) + if (/eip[-\s]?\d+/i.test(title)) { + desiredLabels.add('eip'); + } + + // Evaluate title for optimization keyword + if (/\boptimiz/i.test(title)) { + desiredLabels.add('performance is good'); + } + + // Evaluate changed file paths + const files = await github.paginate(github.rest.pulls.listFiles, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + per_page: 100, + }); + + for (const { pattern, label } of pathToLabel) { + if (files.some(f => f.filename.startsWith(pattern))) { + desiredLabels.add(label); + } + } + + // SnapSync in path + if (files.some(f => /SnapSync/i.test(f.filename))) { + desiredLabels.add('snap sync'); + } + + // Dockerfile changes + if (files.some(f => /(?:^|\/)[Dd]ockerfile/.test(f.filename))) { + desiredLabels.add('devops'); + } + + // Chain config files with integration keywords + const chainKeywordToLabel = { + 'taiko': 'taiko', + 'optimism': 'optimism', + 'op-': 'optimism', + 'xdc': 'XDC', + }; + for (const f of files) { + if (/^src\/Nethermind\/Chains\//.test(f.filename)) { + const name = f.filename.toLowerCase(); + for (const [keyword, label] of Object.entries(chainKeywordToLabel)) { + if (name.includes(keyword)) { + desiredLabels.add(label); + } + } + } + } + + // Apply test label when all changed files are in Test projects + if (files.length > 0 && files.every(f => /\.Test[s]?\//.test(f.filename))) { + desiredLabels.add(testOnlyLabel); + } + + // Apply cleanup label when PR only removes code + if (files.length > 0 && files.every(f => f.additions === 0 && f.deletions > 0)) { + desiredLabels.add('cleanup'); + } + + // Diff against current labels + const currentLabels = new Set( + context.payload.pull_request.labels.map(l => l.name) + ); + + const toAdd = [...desiredLabels].filter(l => !currentLabels.has(l)); + const toRemove = [...currentLabels].filter(l => managedLabels.has(l) && !desiredLabels.has(l)); + + if (toAdd.length > 0) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: toAdd, + }); + core.info(`Added labels: ${toAdd.join(', ')}`); + } + + for (const label of toRemove) { + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name: label, + }); + core.info(`Removed label: ${label}`); + } catch (e) { + if (e.status !== 404) throw e; + } + } + + if (toAdd.length === 0 && toRemove.length === 0) { + core.info('No label changes needed'); + } diff --git a/.gitignore b/.gitignore index 6f14b7b2d897..5f3e510becd3 100644 --- a/.gitignore +++ b/.gitignore @@ -358,6 +358,10 @@ paket-files/ #tools/** #!tools/packages.config +# JitAsm uops.info database (110MB, download separately) +tools/JitAsm/instructions.xml +tools/JitAsm/instructions.db + # Tabs Studio *.tss diff --git a/.gitmodules b/.gitmodules index f5c94ba7c690..c674b7d49e06 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,7 @@ [submodule "src/bench_precompiles"] path = src/bench_precompiles url = https://github.com/shamatar/bench_precompiles.git +[submodule "tools/gas-benchmarks"] + path = tools/gas-benchmarks + url = https://github.com/NethermindEth/gas-benchmarks + branch = main diff --git a/AGENTS.md b/AGENTS.md index 350f984894d6..62ce5f7ef4fc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,11 +23,18 @@ This guide helps to get started with the Nethermind Ethereum execution client re - Use the `ArgumentNullException.ThrowIfNull` method for null checks and other similar methods - Use the `ObjectDisposedException.ThrowIf` method for disposal checks - Use documentation comments for all public APIs with proper structure +- Avoid `var` when declaring variables, the only acceptable exceptions are very long type names e.g. nested generic types - Consider performance implications in high-throughput paths - Trust null annotations, do not add redundant null checks +- When fixing a bug, always add a regression test that fails without the fix and passes with it - Add tests to existing test files rather than creating new ones +- When adding multiple, similar tests write one test with test cases +- When adding a test, check if previous tests can be reused with new test case - Code comments must explain _why_, not _what_ -- Do not suggest using LINQ when a simple loop would suffice +- **NEVER suggest using LINQ (`.Select()`, `.Where()`, `.Any()`, etc.) when a simple `foreach` or `for` loop would work.** LINQ has overhead and is less readable for simple iterations. Use LINQ only for complex queries where the declarative syntax significantly improves clarity. +- Keep changes minimal and focused: do not rename variables, reformat surrounding code, or refactor unrelated logic as part of a fix. Touch only what is necessary to solve the problem. +- Follow DRY: after making changes, review the result for duplicated logic. Extract repeated blocks (roughly 5+ lines) into shared methods, but do not over-extract trivial one-liners into their own methods. +- In generic types, move methods that do not depend on the type parameter to a non-generic base class or static helper to avoid redundant JIT instantiations per closed type. - Do not use the `#region` and `#endregion` pragmas - Do not alter anything in the [src/bench_precompiles](./src/bench_precompiles/) and [src/tests](./src/tests/) directories @@ -106,8 +113,105 @@ Before creating a pull request: ```bash dotnet format whitespace src/Nethermind/ --folder ``` -- Use [pull_request_template.md](.github/pull_request_template.md) +- Follow the [pull_request_template.md](.github/pull_request_template.md) format: fill in the changes section, tick the appropriate type-of-change checkboxes, and complete the testing/documentation sections. The checkboxes drive automatic PR labeling. ## Prerequisites See [global.json](./global.json) for the required .NET SDK version. + +## EVM Gas Benchmarks + +The [Nethermind.Evm.Benchmark](./src/Nethermind/Nethermind.Evm.Benchmark/) project includes BenchmarkDotNet benchmarks that replay real `engine_newPayloadV4` payload files from the [gas-benchmarks](https://github.com/NethermindEth/gas-benchmarks) submodule. It is the primary tool for validating performance impact of EVM, block processing, block building, and newPayload path changes. + +**When to use:** After any change to the following projects, run the relevant gas benchmarks to verify there is no throughput regression: +- [Nethermind.Evm](./src/Nethermind/Nethermind.Evm/) - opcode implementations, `VirtualMachine`, instruction handling +- [Nethermind.Evm.Precompiles](./src/Nethermind/Nethermind.Evm.Precompiles/) - precompiled contracts +- [Nethermind.State](./src/Nethermind/Nethermind.State/) - world state, storage access, `WorldState` +- [Nethermind.Trie](./src/Nethermind/Nethermind.Trie/) - Merkle Patricia trie, trie store +- [Nethermind.Blockchain](./src/Nethermind/Nethermind.Blockchain/) - block processing and transaction processing paths +- [Nethermind.Merge.Plugin](./src/Nethermind/Nethermind.Merge.Plugin/) - newPayload handler flow +- [Nethermind.Specs](./src/Nethermind/Nethermind.Specs/) - gas cost changes, hard fork rules + +### Setup (one-time) + +```bash +git lfs install && git submodule update --init tools/gas-benchmarks +``` + +On Windows, if you get "Filename too long" errors, enable long paths first: + +```bash +git config --global core.longpaths true +``` + +If the submodule was already cloned without LFS installed (genesis file shows as ~130 bytes instead of ~53MB): + +```bash +git lfs install && cd tools/gas-benchmarks && git lfs pull +``` + +### Benchmark modes (current) + +Use `--mode=` to select one path. Do not use legacy `--mode=EVM`. + +| Mode | Benchmark class | What it measures | Typical use | +|---|---|---|---| +| `EVMExecute` | `GasPayloadExecuteBenchmarks` | Transaction execution via `TransactionProcessor.Execute` | Node-like import tx execution cost | +| `EVMBuildUp` | `GasPayloadBenchmarks` | Transaction execution via `TransactionProcessor.BuildUp` | Block-building tx execution cost | +| `BlockOne` | `GasBlockOneBenchmarks` | `BlockProcessor.ProcessOne` | Block-level processing without branch-level overhead | +| `Block` | `GasBlockBenchmarks` | `BranchProcessor.Process` | Full import branch processing overhead | +| `BlockBuilding` | `GasBlockBuildingBenchmarks` | Producer path with `ProcessingOptions.ProducingBlock` | Default block production behavior | +| `BlockBuildingMainState` | `GasBlockBuildingBenchmarks` | Producer path with `BuildBlocksOnMainState=true` | Main-state production behavior | +| `NewPayload` | `GasNewPayloadBenchmarks` | `NewPayloadHandler` path | Real handler-side newPayload flow | +| `NewPayloadMeasured` | `GasNewPayloadMeasuredBenchmarks` | Instrumented near-handler path | Detailed stage timing and breakdown | + +### Running benchmarks + +```bash +# List all benchmark scenarios +dotnet run --project src/Nethermind/Nethermind.Evm.Benchmark -c Release -- --list flat --filter "*Gas*" + +# Run one mode + one scenario pattern +dotnet run --project src/Nethermind/Nethermind.Evm.Benchmark -c Release -- --inprocess --mode=EVMExecute --filter "*MULMOD*" +dotnet run --project src/Nethermind/Nethermind.Evm.Benchmark -c Release -- --inprocess --mode=Block --filter "*a_to_a*" +dotnet run --project src/Nethermind/Nethermind.Evm.Benchmark -c Release -- --inprocess --mode=NewPayloadMeasured --filter "*a_to_a*" + +# Run from pre-built executable (no build at all) +dotnet build src/Nethermind/Nethermind.Evm.Benchmark -c Release +./src/Nethermind/artifacts/bin/Nethermind.Evm.Benchmark/release/Nethermind.Evm.Benchmark --inprocess --mode=EVMExecute --filter "*MULMOD*" + +# Run all 8 modes for the same scenario (PowerShell) +$modes = @('EVMExecute','EVMBuildUp','BlockOne','Block','BlockBuilding','BlockBuildingMainState','NewPayload','NewPayloadMeasured') +foreach ($mode in $modes) { + dotnet run --project src/Nethermind/Nethermind.Evm.Benchmark -c Release -- --inprocess --mode=$mode --filter "*a_to_a*" +} + +# Quick diagnostic mode (single payload, no BDN harness, debuggable) +dotnet run --project src/Nethermind/Nethermind.Evm.Benchmark -c Release -- --diag "opcode_MULMOD-mod_bits_63" +``` + +### Reading results + +The output includes custom columns: +- **MGas/s**: `100M gas / mean_seconds / 1M` - higher is better +- **CI-Lower / CI-Upper**: 99% confidence interval bounds for MGas/s + +A regression is a drop in MGas/s outside the confidence interval. If CI intervals of before/after overlap, the difference is not statistically significant. + +For `NewPayload` and `NewPayloadMeasured`, timing breakdown reports are emitted at the end of the run and persisted to files under `BenchmarkDotNet.Artifacts/results/`. + +### Workflow for performance changes + +1. Pick modes based on the code path you changed: +`EVMExecute` + `EVMBuildUp` for tx execution changes, `BlockOne` + `Block` for import flow changes, `BlockBuilding*` for producer flow changes, `NewPayload*` for engine API flow changes. +2. Run baseline using fixed BDN settings for comparability: +`--inprocess --warmupCount 10 --iterationCount 10 --launchCount 1`. +3. Apply your change. +4. Rerun the same command(s) with the same `--mode` and `--filter`. +5. Compare mean time, MGas/s, and allocations. +6. Add before/after numbers to your PR description. +7. Investigate any statistically meaningful drop before merge. + +To run only scenarios related to your change, use `--filter` with a pattern matching opcode or scenario name. Use `--list flat --filter "*Gas*"` to discover exact names. + +Test scenarios are auto-discovered from `tools/gas-benchmarks/eest_tests/testing/`. New tests added to the gas-benchmarks submodule appear automatically after `git submodule update --remote tools/gas-benchmarks`. diff --git a/Directory.Packages.props b/Directory.Packages.props index 2112ae3eadde..0c968919dcc0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,7 +11,7 @@ - + @@ -19,15 +19,15 @@ - - + + - + - - - + + + @@ -36,18 +36,18 @@ - - + + - - - - + + + + - - + + @@ -57,8 +57,9 @@ - + + @@ -67,25 +68,25 @@ - + - + - + - + - - - + + + - + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 8ecddbd54135..a2eef01c8319 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.102-noble@sha256:25d14b400b75fa4e89d5bd4487a92a604a4e409ab65becb91821e7dc4ac7f81f AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.103-noble@sha256:0a506ab0c8aa077361af42f82569d364ab1b8741e967955d883e3f23683d473a AS build ARG BUILD_CONFIG=release ARG CI=true @@ -25,7 +25,7 @@ RUN arch=$([ "$TARGETARCH" = "amd64" ] && echo "x64" || echo "$TARGETARCH") && \ # A temporary symlink to support the old executable name RUN ln -sr /publish/nethermind /publish/Nethermind.Runner -FROM mcr.microsoft.com/dotnet/aspnet:10.0.2-noble@sha256:1aacc8154bc3071349907dae26849df301188be1a2e1f4560b903fb6275e481a +FROM mcr.microsoft.com/dotnet/aspnet:10.0.3-noble@sha256:52dcfb4225fda614c38ba5997a4ec72cbd5260a624125174416e547ff9eb9b8c WORKDIR /nethermind diff --git a/Dockerfile.chiseled b/Dockerfile.chiseled index bf96919ae532..60b5b36ff752 100644 --- a/Dockerfile.chiseled +++ b/Dockerfile.chiseled @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.102-noble@sha256:25d14b400b75fa4e89d5bd4487a92a604a4e409ab65becb91821e7dc4ac7f81f AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.103-noble@sha256:0a506ab0c8aa077361af42f82569d364ab1b8741e967955d883e3f23683d473a AS build ARG BUILD_CONFIG=release ARG CI=true @@ -28,7 +28,7 @@ RUN cd /publish && \ mkdir logs && \ mkdir nethermind_db -FROM mcr.microsoft.com/dotnet/aspnet:10.0.2-noble-chiseled@sha256:cc6a8adc9402e9c2c84423ee1a4c58a3098511ed5399804df0659eeafb0ae0cb +FROM mcr.microsoft.com/dotnet/aspnet:10.0.3-noble-chiseled@sha256:3b0bd0fa83c55a73d85007ac6896b9e5ac61255d651be135b7d70622af56af78 WORKDIR /nethermind diff --git a/README.md b/README.md index e7443d757229..7888790426b4 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [![GitHub Discussions](https://img.shields.io/github/discussions/nethermindeth/nethermind?style=social)](https://github.com/nethermindeth/nethermind/discussions) [![GitPOAPs](https://public-api.gitpoap.io/v1/repo/NethermindEth/nethermind/badge)](https://www.gitpoap.io/gh/NethermindEth/nethermind) -The Nethermind Ethereum execution client, built on .NET, delivers industry-leading performance in syncing and tip-of-chain processing. With its modular design and plugin system, it offers extensibility and features for new chains. As one of the most adopted execution clients on Ethereum, Nethermind plays a crucial role in enhancing the diversity and resilience of the Ethereum ecosystem. +Nethermind is an Ethereum execution client built on .NET. Nethermind is the fastest client in terms of throughput, block processing, transactions per second (TPS), and syncing new nodes. With its modular design and plugin system, it offers extensibility and features for new chains. As one of the most adopted execution clients on Ethereum, Nethermind plays a crucial role in enhancing the diversity and resilience of the Ethereum ecosystem. ## Documentation diff --git a/cspell.json b/cspell.json index 313bcb6496c2..c65fe84f9159 100644 --- a/cspell.json +++ b/cspell.json @@ -35,11 +35,13 @@ "adab", "addmod", "affinitize", + "agen", "akhunov", "alethia", "alexey", "analysed", "apikey", + "archs", "argjson", "asmv", "aspnet", @@ -48,6 +50,7 @@ "autofac", "autogen", "auxdata", + "backpressure", "badreq", "barebone", "baseblock", @@ -85,6 +88,9 @@ "blocknr", "blocksconfig", "blocksdb", + "blockbuilding", + "blockbuildingmainstate", + "blockone", "blocktest", "blocktree", "blom", @@ -105,6 +111,7 @@ "buildtransitive", "bulkset", "bursty", + "bword", "buterin", "bylica", "bytecodes", @@ -115,17 +122,41 @@ "callf", "callme", "callvalue", + "callvirt", "cand", "canonicality", "castagnoli", + "cctor", + "cctors", "chainid", "chainspec", "chiado", "cipherparams", "ciphertext", "ckzg", + "classinit", "cloneable", "cmix", + "cmov", + "cmova", + "cmovae", + "cmovb", + "cmovbe", + "cmove", + "cmovg", + "cmovge", + "cmovl", + "cmovle", + "cmovna", + "cmovnb", + "cmovnbe", + "cmovne", + "cmovng", + "cmovnge", + "cmovnl", + "cmovnle", + "cmovnz", + "cmovz", "codecopy", "codehash", "codesection", @@ -133,7 +164,9 @@ "coef", "collectd", "colour", + "CORINFO", "commitset", + "compactable", "comparand", "concurrenc", "configurer", @@ -142,6 +175,7 @@ "containersection", "contentfiles", "corechart", + "corinfo", "cpufrequency", "crummey", "cryptosuite", @@ -151,6 +185,7 @@ "dataloadn", "datasection", "datasize", + "dasm", "dbdir", "dbsize", "deadlined", @@ -158,6 +193,7 @@ "debhelper", "decommit", "decompiled", + "dedup", "decompiler", "deconfigure", "deconfigured's", @@ -169,13 +205,17 @@ "deque", "deserialised", "dests", + "devirtualization", "devirtualize", + "devirtualized", "devnet", "devnets", "devp2p", "diagnoser", "diagnosers", + "diffable", "disappearer's", + "disasm", "discontiguous", "discport", "discv", @@ -190,11 +230,14 @@ "dpapi", "dpkg", "dupn", + "dynamicclass", "ecies", "ecrec", "edgecase", "efbbbf", + "eest", "eips", + "eliminable", "emojize", "emptish", "emptystep", @@ -226,8 +269,11 @@ "ethrex", "ethstats", "ethxx", + "evmbuildup", "evmdis", + "evmexecute", "ewasm", + "evex", "extcall", "extcode", "extcodecopy", @@ -254,17 +300,22 @@ "forkhash", "forkid", "fusaka", + "fullopts", "gasrefund", "gbps", + "gcstatic", "gcdump", "geoff", + "genesisfiles", "getblobs", "getnull", "getpayloadv", "getrlimit", + "getshared", "gettrie", "gopherium", "gwat", + "gword", "halfpath", "hardfork", "hardforks", @@ -287,11 +338,13 @@ "highbits", "hiveon", "hmac", + "hmacsha", "holesky", "hoodi", "hostnames", "hotstuff", "hyperthreading", + "idiv", "idxs", "iface", "ikvp", @@ -301,12 +354,14 @@ "initcodes", "inited", "initialized", + "inprocess", "insens", "insize", "installdeb", "instart", "internaltype", "interp", + "interruptible", "invalidblockhash", "isnull", "iszero", @@ -314,9 +369,13 @@ "ivle", "jemalloc", "jimbojones", + "jitasm", "jitted", "jitting", "jmps", + "jnbe", + "jnge", + "jnle", "jsonrpcconfig", "jumpdest", "jumpdestpush", @@ -356,8 +415,10 @@ "liskov", "logicalcpu", "longdate", + "longpaths", "lookback", "lukasz", + "lzcnt", "machdep", "machinename", "madv", @@ -366,10 +427,12 @@ "mallopt", "mapg", "marshallers", + "marocchino", "maskz", "masternode", "masternodes", "masterodes", + "materialisation", "maxcandidatepeercount", "maxcandidatepercount", "maxfee", @@ -395,14 +458,20 @@ "merkleization", "merkleize", "merkleizer", + "mgit", "mgas", + "microarchitecture", + "microbenchmark", + "microbenchmarks", "microsecs", "midnib", "millis", "mingas", "minlevel", + "minopt", "mintable", "misbehaviour", + "mispredictions", "mklink", "mload", "mmap", @@ -410,6 +479,7 @@ "modexpprecompile", "morden", "movbe", + "movsxd", "movzx", "mres", "mscorlib", @@ -449,7 +519,10 @@ "networkconfig", "networkconfigmaxcandidatepeercount", "networkid", + "newpayload", + "newpayloadmeasured", "newtonsoft", + "nint", "nito", "nlog", "nodedata", @@ -458,6 +531,7 @@ "nodetype", "nofile", "nonposdao", + "nongcstatic", "nonstring", "nops", "nostack", @@ -466,6 +540,7 @@ "npushes", "nsubstitute", "nugettest", + "nuint", "numfiles", "oand", "offchain", @@ -493,10 +568,12 @@ "permissioned", "pgrep", "physicalcpu", + "pipefail", "piechart", "pinnable", "pinnableslice", "pkcs", + "plinq", "pmsg", "poacore", "poaps", @@ -523,6 +600,7 @@ "prioritise", "protoc", "prysm", + "pshufb", "ptree", "pushgateway", "pwas", @@ -551,9 +629,12 @@ "redownloading", "reencoding", "refint", + "regs", + "relbr", "refstruct", "regenesis", "reitwiessner", + "reorgable", "reorganisation", "reorganisations", "reorganised", @@ -586,6 +667,7 @@ "samplenewpayload", "sankey", "sbrk", + "sbyte", "scopable", "sdiv", "secp", @@ -596,7 +678,23 @@ "seqlock", "serialised", "setcode", + "setb", + "setbe", "sete", + "setg", + "setge", + "setl", + "setle", + "setna", + "setnb", + "setnbe", + "setne", + "setnge", + "setnl", + "setnle", + "setng", + "setnz", + "setz", "shamir", "shlibs", "shouldly", @@ -607,6 +705,7 @@ "signextend", "sizeinbase", "skiplastn", + "skylake", "slnx", "sload", "smod", @@ -616,6 +715,7 @@ "sparkline", "spinlocks", "squarify", + "srcset", "ssse", "sstfiles", "sstore", @@ -631,6 +731,7 @@ "stfld", "stoppables", "storagefuzz", + "streamable", "stree", "strs", "stylesheet", @@ -715,24 +816,32 @@ "unreferred", "unrequested", "unresolve", + "unshifted", "unsub", "unsubscription", "unsynchronized", "unvote", + "uops", "upnp", "upto", "upvoting", "vbmi", "vitalik", + "vmovdqu", "vmovups", "vmtrace", "vote₁", "vote₂", "vote₃", "voteₙ", + "vpaddd", "vpcbr", + "vpermb", + "vpermi", "vpor", + "vpshufb", "vptest", + "vpxor", "vzeroupper", "wamp", "warmcoinbase", @@ -751,14 +860,18 @@ "wycheproof", "xdai", "xdcx", + "xmmword", "xmlstarlet", "xnpool", + "xvcj", "yellowpaper", "ymmword", "yparity", "zcompressor", "zdecompressor", "zhizhu", + "zmmword", + "zkevmgenesis", "zstandard", "zstd", "zwcm" diff --git a/scripts/benchmarks/compare_bdn_results.py b/scripts/benchmarks/compare_bdn_results.py new file mode 100644 index 000000000000..8b5c1d81b87f --- /dev/null +++ b/scripts/benchmarks/compare_bdn_results.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +"""Compare BDN gas benchmark results between master (baseline) and PR branch. + +Reads two merged BDN JSON files, computes MGas/s from mean execution time, +and outputs a Markdown comparison table suitable for GitHub PR comments. +""" +import json +import sys + +GAS_PER_BENCHMARK = 100_000_000 # 100M gas per scenario +SIGNIFICANT_CHANGE_THRESHOLD = 3.0 # % change to flag as regression/improvement + + +def extract_scenario(benchmark): + """Extract a short scenario key from the benchmark parameters.""" + params = benchmark.get("Parameters", "") + # Format: "Scenario=name[params]" + if params.startswith("Scenario="): + return params[9:] + return params or benchmark.get("Method", "unknown") + + +def mgas_per_second(mean_ns): + """Convert mean nanoseconds to MGas/s.""" + if mean_ns <= 0: + return 0.0 + return GAS_PER_BENCHMARK / (mean_ns * 1e-9) / 1e6 + + +def load_results(filepath): + """Load BDN JSON and return {scenario: {mgas, mean_ns, ...}}.""" + with open(filepath) as f: + report = json.load(f) + + results = {} + for b in report.get("Benchmarks", []): + scenario = extract_scenario(b) + stats = b.get("Statistics", {}) + mean_ns = stats.get("Mean", 0) + results[scenario] = { + "mean_ns": mean_ns, + "mgas": mgas_per_second(mean_ns), + "stddev_ns": stats.get("StandardDeviation", 0), + } + return results + + +def main(): + if len(sys.argv) != 3: + print("Usage: compare_bdn_results.py ", file=sys.stderr) + sys.exit(1) + + baseline = load_results(sys.argv[1]) + pr = load_results(sys.argv[2]) + + all_scenarios = sorted(set(baseline.keys()) | set(pr.keys())) + + if not all_scenarios: + print("No benchmarks to compare.", file=sys.stderr) + sys.exit(1) + + # Determine regressions and improvements + regressions = [] + improvements = [] + neutral = [] + + for scenario in all_scenarios: + b = baseline.get(scenario) + p = pr.get(scenario) + + if b is None or p is None: + neutral.append((scenario, b, p)) + continue + + if b["mgas"] > 0: + change_pct = ((p["mgas"] - b["mgas"]) / b["mgas"]) * 100 + else: + change_pct = 0.0 + + entry = (scenario, b, p, change_pct) + if change_pct < -SIGNIFICANT_CHANGE_THRESHOLD: + regressions.append(entry) + elif change_pct > SIGNIFICANT_CHANGE_THRESHOLD: + improvements.append(entry) + else: + neutral.append(entry) + + # Output Markdown + lines = [] + lines.append("## Gas Benchmark Results (BDN)") + lines.append("") + + has_regressions = len(regressions) > 0 + has_improvements = len(improvements) > 0 + + if has_regressions: + lines.append("### :warning: Regressions (>{:.0f}% slower)".format(SIGNIFICANT_CHANGE_THRESHOLD)) + lines.append("") + lines.append("| Scenario | Master (MGas/s) | PR (MGas/s) | Change |") + lines.append("|----------|----------------:|------------:|-------:|") + for scenario, b, p, change_pct in sorted(regressions, key=lambda x: x[3]): + lines.append("| {} | {:.1f} | {:.1f} | **{:+.1f}%** |".format( + scenario, b["mgas"], p["mgas"], change_pct)) + lines.append("") + + if has_improvements: + lines.append("### :rocket: Improvements (>{:.0f}% faster)".format(SIGNIFICANT_CHANGE_THRESHOLD)) + lines.append("") + lines.append("| Scenario | Master (MGas/s) | PR (MGas/s) | Change |") + lines.append("|----------|----------------:|------------:|-------:|") + for scenario, b, p, change_pct in sorted(improvements, key=lambda x: -x[3]): + lines.append("| {} | {:.1f} | {:.1f} | **{:+.1f}%** |".format( + scenario, b["mgas"], p["mgas"], change_pct)) + lines.append("") + + if not has_regressions and not has_improvements: + lines.append(":white_check_mark: No significant changes detected (all within +/-{:.0f}%).".format(SIGNIFICANT_CHANGE_THRESHOLD)) + lines.append("") + + # Summary table (collapsed for large sets) + if len(all_scenarios) > 20: + lines.append("
") + lines.append("Full results ({} scenarios)".format(len(all_scenarios))) + lines.append("") + + lines.append("| Scenario | Master (MGas/s) | PR (MGas/s) | Change |") + lines.append("|----------|----------------:|------------:|-------:|") + + for scenario in all_scenarios: + b = baseline.get(scenario) + p = pr.get(scenario) + + master_str = "{:.1f}".format(b["mgas"]) if b else "N/A" + pr_str = "{:.1f}".format(p["mgas"]) if p else "N/A" + + if b and p and b["mgas"] > 0: + change_pct = ((p["mgas"] - b["mgas"]) / b["mgas"]) * 100 + change_str = "{:+.1f}%".format(change_pct) + elif b is None: + change_str = "NEW" + elif p is None: + change_str = "REMOVED" + else: + change_str = "N/A" + + lines.append("| {} | {} | {} | {} |".format(scenario, master_str, pr_str, change_str)) + + if len(all_scenarios) > 20: + lines.append("") + lines.append("
") + + print("\n".join(lines)) + + +if __name__ == "__main__": + main() diff --git a/scripts/benchmarks/merge_bdn_results.py b/scripts/benchmarks/merge_bdn_results.py new file mode 100644 index 000000000000..c3e417e5e77b --- /dev/null +++ b/scripts/benchmarks/merge_bdn_results.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""Merge BenchmarkDotNet JSON results from multiple chunks into a single file.""" +import json +import os +import sys + + +def main(): + if len(sys.argv) != 3: + print("Usage: merge_bdn_results.py ", file=sys.stderr) + sys.exit(1) + + results_dir = sys.argv[1] + output_file = sys.argv[2] + + all_benchmarks = [] + base_report = None + + for filename in sorted(os.listdir(results_dir)): + if not filename.endswith("-full.json"): + continue + + filepath = os.path.join(results_dir, filename) + with open(filepath) as f: + report = json.load(f) + + if base_report is None: + base_report = report + + benchmarks = report.get("Benchmarks", []) + all_benchmarks.extend(benchmarks) + + if base_report is None: + print("No BDN result files found in {}".format(results_dir), file=sys.stderr) + sys.exit(1) + + # Deduplicate by full benchmark name (in case of overlapping chunks) + seen = {} + for b in all_benchmarks: + key = b.get("FullName", b.get("DisplayInfo", "")) + seen[key] = b + + base_report["Benchmarks"] = list(seen.values()) + base_report["Benchmarks"].sort(key=lambda b: b.get("FullName", "")) + + with open(output_file, "w") as f: + json.dump(base_report, f, indent=2) + + print("Merged {} benchmarks into {}".format(len(base_report["Benchmarks"]), output_file)) + + +if __name__ == "__main__": + main() diff --git a/scripts/build/Dockerfile b/scripts/build/Dockerfile index bb2f165f6116..57fad31cfd91 100644 --- a/scripts/build/Dockerfile +++ b/scripts/build/Dockerfile @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM mcr.microsoft.com/dotnet/sdk:10.0.102-noble@sha256:25d14b400b75fa4e89d5bd4487a92a604a4e409ab65becb91821e7dc4ac7f81f +FROM mcr.microsoft.com/dotnet/sdk:10.0.103-noble@sha256:0a506ab0c8aa077361af42f82569d364ab1b8741e967955d883e3f23683d473a ARG COMMIT_HASH ARG SOURCE_DATE_EPOCH diff --git a/src/Nethermind/Chains/arena-z-mainnet.json.zst b/src/Nethermind/Chains/arena-z-mainnet.json.zst index fef9920bc9192b35e375e370fb7cac36df95bb39..10503dbf9dbf43f30b371e38139fa85e5d97b8c3 100644 GIT binary patch delta 19943 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 delta 18203 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 diff --git a/src/Nethermind/Chains/arena-z-sepolia.json.zst b/src/Nethermind/Chains/arena-z-sepolia.json.zst index 3a5412fe44e0bc72478dfb8981f5386a5f49841d..0051877e65c12f082beaacee49bdbec72aeaf2b9 100644 GIT binary patch delta 16233 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 KSANh@sQ(ArF(c&w delta 14670 zcmXxpLvSSwvw-2)_6aApZQHhO8)stMwvCBx+nLz5Gs*oH_uoJDuDZIqs>hNb+ruD% zRDT%%foqLaHbDAyf=1n_3zPnek9q*NN<>a#bsS&)Hte;@G*+?dpyay)k-JcQS6iA+*H2U}9$l_nVv=3h%-QJ2NKn;7|9Jyw6=-S6BV$OWnzi>7RxlVx=?Gn;*~1k zNElrQuR{NDTrhFBNZY9}v$D%3e8tctd=0PCDK93J;i${}@QdxO3^x~bxFMqgz1Z(- ztQd;JVY;)tG}y2gL?I01`Dn9b&xpb5De^F9LVY9pt0OJNT{j3x?raXU7xY^G-=<&v zKY{!o>G&V1`5(FXU&ZJDE1CZzJEBK?T597!sBrtG%k4tm8-(TXz#>*5h`nkj*pu*I zV$bVK38>46+e76P84NyF%&B34jZ279k**5_EjWzBR?4zGHi=4ds%qu)ahTY6%Elfe z*l$H*@o)zsVVvP8?q(|{*y1f*s=0`&Y`!t=mNndiK}^4cJpDX2OxYu>^g?m90(EVG zVHRFy;?M3*XpPdLkbuG3;*W;*7#6xpceREuGgD$Ml2Cy75LVlJ@M#vmn+g#@07%~~#X&V1u5KSDP~7=$-Gpe9fcJWgmVxSI4HW569_nl5g6=AFgja-5Zdh(9c$zGcS|uWF2YhxdSYVU zf3yNjB~ZSf-HAy!7#fKx0R*1U3I&(1!iYeqKt2!xdC+N3COQ_G!rs5`t))OJz3dH^PU2hX7xo>H65Pj_-{! zRN?V>PAEkI{&*7 zWCGB>5JUvGoq&ad3sK3Be1KK zj6!q3&3DOvCQp34qBbX@gpAM|cqoLBHgFrbOW@U?$B!el*ALz<6e89n6Yox!cR=m0 z4&RAZnq!Gb8Y-&IgaCx1g+Syh4TP*E9WDSB7f72rar;Hr;3t%!FwpQD0*L|#dD^QX zILIkcD4{RUYcGh_qdw{Dq1ZwRP?Jt!S3Q&whSrY7)4_NM7e zW}reo4D_?4Q!nB(wOZFgz0$cH4~8~;g}I(}W>iD0j!mbnw6Z>cB5$CF{Ib{52e5eT zqu?129_thgdHj6o{{&`LcE_1o991G(lp;q7`pp`DTh0EEJ*2$9p@tw zzN*sqG*RP5@|S6aCI(FgG3EtZz<>+93#^;@0Ao@bxKEV}0f{dnhgRn>zT(XWlatvN z7y9hX^+_t++8*E1+M$a+`hOxXaJy{qnmbAGo^feumA`rdlHo6=HXwW2sv6x|PH2iP zM$WdM@{$J6`jlbC`^+oHg{add3z1k|QxsB!BNR-EHZm+9s2M|DK1AE+mTg2h?U6O5F(#C;9jmk(3vIbp+vMT%taibp3DaXD1Us@7sG^Vt6B8@OX${$#)lZ^oR0(+MPDOHv z#M+&^r@oq%eZ$>0rDB)s@w3(5&!%~Ya@f(>W7{SH^u-wRa)8p=wUrRp=*djP7mey1bcMwozc)rbcsX14S0mNsM^F1~CzOLaEk-1f9_u7?e7gCTVLu(XLasK{?)k?A zH(71eFUFaZLr}hJg0D;?swK7sI3sSko&bxNl@GI75j#Om{Ug2D*oj-+Qj&r_AwGIV zXvq}oAXYY}_qeo)i|hYPD{K|RPFX59&u2F zGou(Apq)5DApek zBSkl(5$6_e1VbEAK*Heze0LX3xPfPJ$uWG3{5!6boKt8#l`x+LfpGZH_2n`tt1rMV zG2K?^?!7a8>m`v_q;$1e!SBMuc9T!4&t0LsygE{6ooxJSVwisjPrpXB@dzJ9t8jir zGoAZu`>9|XF_Q^P42n<^)hx&2pVZ8Ra79o~HcglUgtG;z824%`&{-uG?T1&lR4Nwl zDy=KcKu%3{luley{-LjU`Rk9>LzaL3Y<7T~+XIdH+`#AU&`Z7&UiZ0Qn}BYg%a#_Z z$mxYPeBQxC5oV8+Hd=yJOaYqbUz7En8nM0A@aR!m)JT=ks`B#^b%nBJU}|Zg0P-8} zsk+s)p=x?tqueqtu;(utd%g#&OmT6~`}o)G#1E%9E3IecI#u7-nkGXP`EiC#V!8nf zxhp%fc;_*VjWI&D=PJ_sU4mgj6$`6(B}W_*zD$n?`Q-hAhyl$JrBp;fKn?AYL?m9_ zy~_Bn=-;m+mw8+8EI|QW2-<2ed{r05BFL;$i?$iuxX1ACKy#Rf?A~8}m_xu)azj~) z(E`NWX-;YCRGr3J0(x}8k$I+iqh#V`@J5COttvf|sq(xcaBYaG3YJ0P9z07b(%JSn zWeVE>Zj}Je9s`rQaaz>cy0Ljg_QGHNDI;}GJUJZ+DGu{{bpIT6av9N9 zMv-ZuOrnWn-ZOJp4Vadbm4n{&fL57UGCW?#&P<63tW}D=IMI%^IConv(s!cS$r~`w9I&gGMY2fa&fB` zR20v)0A+Omz5WE1YJY;Uc$mGOC62C0t{BY4q;sI^SRFh^pz&S9{Qy?eJq;(TvMgXS zbr%H_gG{hTM!mG)6Drjf28%9cIRtM~{TDINj!#A-1b(K2r$}d}U2NVebbgyioMweo zEv+z=VRnY=&jRYjEAs$WM~B|6W3WIY!fnF^8Fjv(ByU-{5wWBe z3?eFf)R6YVw0M;25$pwv?Sf9ueIgBwX3tX=?Zp7k3pJh(H-s#)+!z8`S-OYeL+=hy zD)IqG#FjCb#w9yfP9H4NDl(xVSbSQGg_5)_ZwH(_TuBe!ra9Tc*0k6mxwQ#hxvGex zCDXXX)Qh7iQ5@-k9KML2gvA&kuxX3Gs;Lkh9>7eZH!MfLB}GdRT|r(;*{S9QbzDAd zH(x$!HGhk1H*X2!vt9A)%(O{v{n{SLf}~45yAgv`HxZq!lwYEKqRQ{ysUNfEm5xn1 zZ&oM|t&-?%0OKY-GO*P>A;XmWWE{3Ir$n2c5#TX7_S6-o_fgm+^sx;k9iZlRo-Wf@ zsz%bI_L@aPEm6JC&Q!xmQBa6rr$1W{g zGLxyr%*7e8Zl+UY(^4#tHeosxD;6wSpB-yfnP#MZE?W`R*nBLW$`kxdPC31J|Lq449MVn%0OBCUjpkl_%##&Tg(X(%@|2q+hmOBu~f)F~*G?rYD4|$cj%$a1c zNzceh$z4z76+o7bS56eqr`L4$>S*AR9=8rVHzog;@K^lrnWT$Vj70k1cYToxwJ8;S zbap~pQzZo$9tv)k+;HHEGKqVDDMs8`MGIpbW@lO`LL>+b1jsLYSM=Y%s8A3hLQEDC zv?v$_F+iXL?C1gn*y6)YoVhTNAej-sg%O585Wqk{f`m|{giES8PzW4akPkhHMipUw zAiMBYgB(%({@IPHWgZt|-uXg=^Brhmm8NI#ss~Cg&l|k-{TT%oYJO!x;=LR7@6HRGA5K#%u+K^Bl{`It6k1s~!K#O`6Zs z243EO;YB6LfQcjnmdRb@2sko}Ti!;Xq`SYE8qJ8j5Yl-_vb^WKAP7QRO|y#H3uYS% zP(lCPC2|KxqG$nQa2?RZowukgC^e}*E?ooNY-+*vnmpO*Pthb%VOx4bTodW-?{d6h zDVsn8zMXc8Kkrbj{j!_Mvlu=krsnieVr$PhUaXUN6BK%VYKWminfnJ{p|Q!tDQ3Et z)2~~PHTH5VcF$2BiR|#9ZYC6WV=BLq?!>cU#vFQWHAR4;`@l~v>Nk?42NzPOy_R67 zK{pKRq9J&3z`5LV1a{4r#0a?VwmQ`1w{}% z7LvL@9$Sb|%R28n=&|c1YUQd|cF4Ynhy(1Ty8UpgKUeKbfuEmi7Use9o%;LlaeSg+ z2<+b!UFI`-Bn)|jN?kPP(Oj`VLmHEn{^-nO+mjsUlq(-Fun$xc1>!I9l96nAT$ zSpp?(KLMqUzsLI|#^(@UDcPvQ@lRasw*D7b*ayF2-SI)C#eG}S$07I!9-W<6|>3>j1 z7H9%)>Y$p zw(V*rEsS zKc0WEzYx0QYXaQJw=i;uP>o0XkK>b$xPa!vSGDuDqK{>T(^@#^uK85^- zpCrH!Er~|s(VpONA$>|ghZHejmOm}yP#+uFkvimhBJNFgDVaKyzDE^^7T2OkyRIgO zXN|280>tZFtj0&x1xzBop6%)+B$t46HUxr`4*1fuo-3aOJmq#%$eUb!YoPW6N%jLf zs<^V{MUwEkM93x?t7u{iSctSfv*jdUM|>O_ai~Hysc#8{Td^P}0hgks|Fjo>6SS67 zy6qYNQ0Z56y8+8OT`+UQyo`5_e|at;WAYB2s!95qg2BSs!S;SU1lX&6`BVa#$0xPZ zu$uJbD?7KGcLs%@nr_&IFJT4t)FWQD^kbM6^==a5vV28Aq+7yRX;ux|Nd+Uyi*unv z8H5cGZI#*sJLEPA=^=tt?4*R=lTMlrt?)(CvgKj116eyH4L48~c}+MFt{Cc7B!aqh zRlZLb;7TwN!ia41oX2@-ULS!M8h~ReN}L<+iRs>Mc6Lfrg2~9wkF{=46`+m$LyZ39 z>67fBH@#Q=xtDXYLpeJiiHABA(Mk^YKq3z~lCJ$4><1G?3)#rs-JywZKWC6=S|O^& z@A7!2e^?%+MvQdvsley*2NE&C;juVgXI&PJ6(b~yI6!vm=vnC=@)-ja;q_&i-@=DY?d4gXfN zgm#SL_k^75QW7BvM@R7QJM|#M*UB0@pGe7?jKyz#8p?t;clUpd_GzN#MdwxC;Ab2A z*Nv&R`(}Ry_yuT3z)*p_+*FE4;Fg#&$q19A)s6dM6+FUcpsCLeN-pzpnr9oF$zsvOlgJ}O$iiH zfVuEYYzVzLNw+!RWS>_wCK#um*x8zpr+8JJ;+KH&9DTuexYe0jBuS~L_J11}d5dK0 zcIi0#p`i3`f2e^D8m~A#l%hJzm+Q~N>oMHlzp%2t1RKDmvtOg}{i&@`x<(CPk#QrJ zH5wR6!NuxY!4}@rYWr`iGqv0go(i%JAgduwYW!)w>27`6bEFUj>Vk^-!1Jm?EZR5+ zx6@lJd+Jcw@6TUXkr6+pi!YcNIE{bqUFS3o?k5HG0z8a#u@~O7qusWy+9)Pz#bQ6R z8l2Rh(1AeyND}hCa&Zc}xGUh))UQJWQ@KatHRr0HO}STNtqyVS?35R`*pISq?Mjp7 zqep#RkS5CI{gyMSiLAV2>ZK0DtXY$irdn(W2W*+09-~YHI^#(2Q2gYaL3NGWP};Xj zlXCW6+nKJB3=Pwhm!#7Z&lMG+ViCg{+4A4TcNIW2tCa7{#D#+SictmMo$-z78+2^N z`UhXb&oujBQ2h?gX=pQLvQl)K1%^_3Tt3zv>wrhlKOxX^(`U+)*wxOgS)7l1%loZK zyylE3&pUTNU1$aM1oXo~;rmoYZ!li<7Fu$}kFy6rO{LC3Y^>5DTS9fXNZR{NfBhe* zE+-&wnl-gPG=!LLZ=g&>k$iw1Q-Ez6NkP8;Nme)a=D2-uEq#QVrfT6!Bu6ZIX(ItF z-}mzl|B7g;xTk-LTYnO*6>ie4I!hz;#PtM!*6tH?Km*%p2>v)}2G$uXjIt|8aWDP> zp?D;>V!Cn?C zw7Ar3if+)E;y(OK1d9c2e;&SrQkVlo`HWnrGY??+kuonVnC5YxgN8k*Id%z@xw3y%G_Hp*^ z)^?OON8=l}#+o)cPsZ^EG=ghI&~z6#U!xa4m6*I<)krnhB>YV9Q)6t^Dcr;AxA(Imy>N?IMH`_ zWp>oN95W%1A^Xpv8kwPV|WyR?}tcT2yh_yn(n4S@povow6a zIT0s4{!Y80@a|Yt|DHA%Ib3b^qH72a$M@upXx{xu@e}0EF_dr#s95HYyv0;`Yp7qZ zN~PQVrAkoJv&En61)0J&x~jAiTO&6`^BI2Phnr7|HdS0=iD_FdnXl8?Ueb62nUpC{ zX#P?Tit~thHaCXf_Zp_$;1Uj;Nejjs>nW@$*@=fL2ucKMep~5!=bwMA$(q7dan0nU zsO`&aM<{#+#x9D_o~+B`e8h}svY^(64z1y^F)s^mIjFB1oM328RH{&TI8oehk~Fbd zh6GB+!}2n1#XBUfn&a4eBjp0VzuXM11^YZ@G7jz*(@f#0UtQ@HxCelXbq9R=JPZK{(?Ac6*!XaCO4~`o9i#O&FRHt0Bu8 z@Zd3z%_#vh> zwwlvXxbBi4a*|I0mDE5y8W?|?f6GSU;=jLtgT;vuT}g2d!Z3iDI#m)%*0OH6DFF2C zY3X>OH;65z2dSx(9(fh=U1gHP@Nsgog86q`PAs%U6tw$#r$rQS{fHDUsSP!S8`y_G zPT-&0erGD(7q|w;uOMaqJa`e{I_;SLQ`mbMgfS^E3TRz)Y~=*fM|{JE;=^?%UiHp= z&f4vd%;bn=iXU`{#qt{wDmZp+54+CMQ9Z6H7t-WmOoUcZi@2SUr8YMoucn-_iOo3+ z^OG>k!p7uFuH7GhJa3Tp;?B+nzR00v*N^s2kXQ^UqPC6YUXx9nTUD#!|Dzt0vu&vQ zn;>6_$dC;3$r=Q7A4p|!-00hM0%SIL?pw~N!Zi?l*P^TpK48t(-$(H>^(K%Sh;~B! z!_S!`CmF1US1ull9(rkEYFS-|+O^z5{==tBz;0$iF}ZkwA0rrrlxf&e_mHQfSe~V< z=%*RqpX;vfSoQqN%iQgp3u_i@X}(?2K39CYA9LaBY5W41yjPf79!G#2{+<20%6wz- zm|0p&2XqPW5jWm~A?@(1qI)u*?lY3RwH{vbY>;RI99S>H5?!0`9R_GW z2>%8?40EN2PtmEn)utFTrn!ffK6Y=T24O>RVj+m3@pr%DpyyPZUm6FFVmE*5HO9C# zU~M)pa3U%8NJv2KD^f5w6Dt=#V$eC&Mr!qMtCLU+T)fQicB9h3k%B{}lGKck@+t(u zo{M|1+yNldIW1KbjclVcIq1?ePm2m3CSQOUC~ro%bU`D7Vc8g(mR|Pm7`GjOj?f4( zqw1S@G{KR@0I!mVi*v?tD!Y!@_)t3WDM2IM^M<13NkV(109zemi6bt+0CHf2;(kfX zgMJ_%@wJlrBr5e^Fu!^*@z}wNZW3z(Z32AcqOgZd{aXR$`nALPp#I7WA#?8fFLR*V zxy3Ew+STl>FD+{K*XchRJcV962)HbqfVsGiX>y5PzH-s9=V89Ext*m;(zy>pr@V#R zq8Kd4jYL>hzBWO^i|8ahn>-v{1za!aZAj?g)UF*L%??uwV<(%Fd3_e3*guyT(C_Dra@C)`Q8hnm;P}Ru{u;IxMrjJ4Z}qyU^qUwwLwM3 zP3nHbAd?R(a;$r5uh>@)(EpR+E$W@Um&rM^&n&;Kjk`8u4G*{+tWxbmwd@5lnmNy@ z-@0PdH;UimR&z7dHwn$X`RDId6|X~F@c3y$M>X$eu6;1WhLA!%UK%`U!*z4cE6ZYJlH6vwEis=v0$FUVVdG@-%V6IPs6jat%GxTLwzni#uE#BhYM5ACog#fpA-4=V^_doj%UJy;)dap ze*E(I3pYvSS%aY!Hsqr@7(~9JxtGt&`Vc^$nZ^2;i>xL=l{cb_1pXo*p8I3)t^=o! z=FMR0c^Dw5^R!Dx@Uo$_{QRzYUJA}PZl^480`GmeU z?&QHTuE^Lho4`aCABQ-i6SrE71%#w?~xv-oF z6VmoEyURG>K^YCVjug=NBB~Ny=q|rC-`C-wh9ETf=w|!l`vr6*@U(?P8lPL2<&=jN zzNrf#Hddy&?4BNVT@c*DauJ zg-ll4YzKHB`Wg<#8^1g|BdJeBm*bFtTgfXtrm}3Zx4>Qw*|e~_V~mI;(&7b0VrIjx z-As}*#8F3%?^v00?@23U%S-u&L*DQd3a)+EfMkqwi#?_3W9JWf@=@3 z`<=miWNZXdQ|Qs)B7JzJPd8?4SAkr#QepYs89oyVb!6;jqZ z-X1JoUEqYNu6*#)Q(HHBMrUm%h5yfU{*3E~4IC49WPY?o2fact*Iu_qT77UfM{{kO z=GjPi)MQ^as9=Neal|1C`~cmRhHKbo$SO^0qsj}Qx{&~t|9I&K!uuqO`_rZ%uo8|L z>+2OA5veTNU{h%^5~Ncb6n_KS`j~a#xBESI9&qt~>Sn5iHdn$sT4F+8iYel5_LHhz z7-U*R^A}82&i)B#DBqvp8x@h|dQQ>}24jFK?B<#ly$y32H!DG#qR#OWq=AI^<(W;H z(FL3EPUK*n=>+aUh$*roolNx1AEJza-RDV1kZJ0NvGd=PV#fg`u(R>HIHu7vgR3(O zkHE->wo}qKT=P9)m^z_BrJ;hdiArZ2_E&bTv>?w>u5 zGzjBbRrnwF(_3fv&yKs4%h5D+NdZ>dja9rfv!lW|#XiVz`m4T(+;AvHZgp1J`J-R( zqL&$IKwdBH?zgvfE%ipk3EhKh;mrX*;Z?kRl5UF_MLJOr6pLT%g!6^j?T)+OV?Z4k z5%EdhB94+v7!RccDCCub#_x&k7)S9+Z;78jV;}{^HiB61p42+k(<*AmFIp}0K8D_* z&r_RB$NY4ol=QTpN+{R$@)KCw%;lxMp^(4k$rm$w-A3tr?frHOu+osCEr+dT)u*a| zQ?K{>q^3}FJ2`XpAW_@@1?^NHjRUrstxi%WrUn_T4K91MLQ6Ue3BZel@+;l$&w_DT zJUo28xeF4~)-r3)QaNC=G9k95k7=M9;M4BW0#iU^qIL<*GUXFSC2>@oayChwhSO^txDok&89!@HjslD*pY=l z4zKn~`%!hwH!@PiFl|3jJ$p9ON*O&xQEpk%j<~FPiOQ2DF1*!2s32X-NH?VIl9K$l z6CXDefReq4pZ{-2b^P>RN{)(!M$NUdv-npg#tGN~wYel|WoDKUsopi{w~5K|Z9I#4?nIPENjCw}2h9C);QqYf1U_%#tt7|Pl4w#>LKfQ%kkg0@BxRW71I zS2%LKMa>!s{u!wzd$=)*xxt7jO}k>gArpCW#Tb~gYFMRmXMvA44@%^uS2D0x{^5?= zoQ#>gV}th1a>S?F)mZ`2}oC|V3QJfFctD`^ADut z<4MVC_`5GIr27a#Sh`*@YCd$oXD+iiqlrsvbmmU6fA6*kh>mkIHLe&)&VkV0FlS6{ zUC>nE7irzXb9mQlJD^p*^<3)jxRLNp_tg8Mv9P}t*qfJzCMB|2`c$~rw57`?=J)pP z9et7HpI^n8qt%+;9z=4+1A52cZDGn0BjzbWCe1G;ldMm|OkGD_BKY!`6P{WC_S^{f zf`?KToiXwYh~(_Sakb;>ZXe;Ln^G6dh5E{R5Tk&T$Xh<423VnfPkm;>14f)q1jMg? z?+s@t9@|t^^Go38vB`uq`XWz7?LK!Cjl|D5{Yeq@ESnz#z?+?aNcD4y7q<+Ennd#+ z@-aex=R`N|gSxNLor3215l)E8kOe@3Nz1r9B?W*3>u=jHXWZSH=YL@Grfp%IUZ~M_ z$jhb?SdBtj&Bab~^ocKSz!}S;5lCC?QaVkE7lmczSMWt|)S}7|jK}rS@eN7cm z%`A}}k|sE{SF=+{Dhq4wU1!AR!Fq7(&Q)m|=~Xg+XR6VYg>5p|i9(-B@cZDrOSmpz zjt<6MaDVNHokY17D!#&*>Qp=EI>(&FtI4ewd9$+ayt^ZwXs@p9*W#&(?Y5J&mbrJC z{@m9TJbY=BJtHtYOJHzn31s6RB6=s#C57*2Wxq@Vg;Zc!7dIso;u@o z7%*_j__y(%jbjk@2(mj!f9){$#EU+u&wMOzeyJv2xwxJ@O;$k0W$@b#E)NLSQ5FAd zjKzl{Zo548#|G!nkTDyEAxy|L7sP=mV@zYZGV{Bw)H`Rgj zYVIz@ev8y8xlK6cW)yNeW(GxV&((Sd;n*DJzpse5(vsiB&p{l}1P+d%>E6_K?X$_e zWM?Ak@#!r<9-l;}=#_i9uQ-R(niNvj65xJZP`u{9`!h@oZd|1&7ijqMCH)xp`4~{{ zzM?N8f+o~jL8r9t6zY}qi$say`??iPd@JCJnKJE~wf~+;B+BY*VEl$UP$!S-?}L>N z=K~CKhAUl?BEKAP*GneW`oU=)QULBUtOId&M4N1w;CG7b{VG0E`}lh@Qf18bfbUgb zwU&G6cqu4(C#dbdVeT$(A$ZHf`xEZ1?!)ZV+`U*v@(}yWCcPlbx&bJV*<~!Vmtq>8 z=hkR@)MCq2$S>I9f$LCnOMZ>Z#G}z(bj{Kf4FLxD(0dAG3%HYL;P)~_rYptUQ+LB5 zUddS(%r9r;rugd5L*|-c#E3kPSaJVXgS-86m5p_S7^?U(tY0?mckCHJ zkoYQ*ZsYhb+-nsr^R$E%-@{%`9Tn?O%_X+%5)QhT?9x{gK42ruG?x3#VY6jgnp2m~ z--UP?dJP}=7MbVOodz25IUGK)8)i)MO1($E?Hnn&eaBUbvc0}X&##2*FK!*vcMfHC zss??150x88Eyv?$)v&>OYR@D=L?_=cJNRa?k0kI5K;YjnA~RJa+UmB{F2@?{qt8ZL zQzW*OLG`z3wIsRR^d=F%u)=lS;bvh0XILJB8gUEw!`+7)Ll?PhN2X)}ENV(jp*j$&bDpmk5Mt#JB5%Z{WcB_euNE(gkLppjU6^|37HT&Mm`Qi2 zxi59Fv{9l5RXW%!?MigF&?%k|wzyC_G-lJV0})^ltSw9YJ@|?Pz+HwIohEWowdU0w zShDpxvTKmxBk;s}Gabz5mEEhR={()EZY2Wh^9~~W?meWa%)n3#pj#GrRR-M0V8b7b z5N%@?r|VkUctT`ln@b33zw(9|(E%7`9nHgV``hoHuyao4%Wspl!bW>&)&1}w!79Sj zvSUgofI#;R(-FO~&#b8ywn#-6-%=03rh=FHrK9T#Iz|Lbck+;#BLRtPQHQn0h%Vqu za`)*h^|X1@>tJS(llX9^BP*^!duLmLuw>0Pha--gtK-NPp)p4@-qbIP%V=}mX~!}a#PC<$ z`dH%w3+8Ptev|7~@2P_65qC!)wPs-2N(L0G?JoV6NdWU7Yp%cby|_bSOupx%7pS&v zGrCab40XOs#TLx)rg$ydFD4sUkJ*)SL;C^GD>xK`S7o?chq|)0J8Uqn&Li1`?9tjd zTLl)c+bmioyE2ZrNcYwCqfDq4oGa^uZCI^dTSi&7mpcy`8#9CJQ0!f|X#+r#0L~dU z{LWop=GI82?DNrwh(CWuwxC!!HNA%pXEatvncB7e+G?=wyR6HOl>+X!GbpOGW!cNd zYW`gBaAb6}Z{iVhR%^!^qfGB~ng5B{m1m_7e~7*vWm9g>7+fc`r?v%mkR(ye0E57Gs!TicX-6R>p@%nB4y{OLhYtX(b!IecdJ0?faPIZCo? zt`$ZJOWN&1d}39HcML_ki|Y635F@#W4tOtf(S}0glxo$+r&Ybb^h-p*Ci#G@D9F7Br)^Xb zwf%o)N||d^{t8bZds4rBV|^IQ*)g6fS;}%N@MrhcLz5ZmzPN}kCBnVz{;0sH>ye+n zpTsH4r>X~lrZY9jjG3uZsinGlVERW1{Ufv@Fjf7E{su=GU=|DNd=dunMgLUJ$2e8d zqv_RmXxwGkuXKvEa+C&MNK>3sL{XY1)pdrGz^0{HMd^omU2G==Gu|!xym?@|*29(TA}v+IGWJlbUPw+Nuh0z?fg?bL?Mv-6~_)F6Dr=3WZhwBnQ5C_+nQ# zw}a<`mz_QZ73>H@*d2gRlW-rNz)52bDU_w^dVlGLs=*2+{QN5!N4&!}OqqoxT6<{u zX?UTra8`Q|23i^7a-n|igl9dw`~3;(HICYM0CxoHy@}55yXeZ>=)5LB8lgmO*)T_b z{h8?BMTzBV5OyGH`p^SjbVmFE=Yrd}n5K5^csBtKnRFGs^d0HomnmZjqez_W&0rSf z?)*m&{qvthbP&M+n3LO=AD1EDo;2o)6RkUnshiQA;$L*|AVZ^wUL0(!q@+ut5GVTT zHr*#TW%J1qo)&-nWQ@(+3K9{wGW!3DN?EK@)5K|(MVtU#z0=1(7eyg%>?%NS(q06? znW1E;0z<}Pc+iBDP#{83xFG6Lm|zA`j37+$gHfpPh3%vwh>!w^#EOLI5D`${aM!!N zF2Q+Q8mgeQ08R{amQT_SAQ*j%AeTe0#$p%|MS(1+o)K&yrL&>x0@z{`C|poW{Wzk5 z1;n@7MpD2G3+!&qr7_|OKK1zGKxGE7F%Vp)V+NrnNpRH{J*#7K6gGMt$+#IDm3TUP zA1+*~V-&8i`QIuIft4sU1Z(rGJrIG7i-iMqRa_BJLG4CpToUDhjVj1yP?B8)8E}p* ztYtY-B~1Xvl`)yxJ8nGH-(h5br57Z_paftkc7mdDl3OB;nmse8QQJ0k259*C3LZa6c-LR%E)Eo11a7P zbQ?ljbZwE21ngVrFG)z!%WPs;X3y7c1zb~r2SJ@3m!uLi2@bBGN)`5TcMv$MG8yA< z;`8aQ%suTIVmQ)y7m+1Bc`c_Irm8X!T7w~Dx*#~kh}!5O#CyA1mO5&S4vhG*A!5fY zJ8>{(Thy>}7gQ;6S$Em+;CrbiII6Q zThL)|0~i7rJJ6Fsv9ix*R5FmQ`4P1MJCffZ)f>=FHp+y?mc~l;@<@=-nT|*@*g;&R z)Ueh0Z@Z8jP=RFIYuXkjiC zpp65NBw7e;a|nTugiyx;pomOH906JSQj!mrzetivli!L`SRswY8-yWJSSiv0y8Kt? z5AjQa4Zs^n#sETCwlN^ko?SR0d5onR2;YiD%3inzrg0!CHur&w#r_&LP`LAw6_9E4 zEbfsY(}X|>3?m3}9x6G>Sctufx1c<3FOKbM2BO~(m_|zN#&n>#!1;mLcd;!-aE>6L zuxzU(<3z#T{Xz2unx%_Gp(95i%vh|vJ)8W3ObaC9|(P?(DT z5)Gq&-3kDI8Bh=kMrZ@YvFtCEL_$DuL;ChSM?WePlpkuw4;f`(`W`q32d z_JhJij5Qd+!Ft9l8)@}pn>FB=B@8%26ocjoK@!;B2Cnh}K_LYodIuw!A%S4v-$DuX zqcRO}1ON#I0O{QhK=BzN+6n<9bOa-~gJOXJ0f8W4(*;5n2g5S}f#eAR0TBgYL;OdE z>URJ^6oG^W3IGCO0_6G9P1_E-icW(MoZIKkb+)8!Me;D1xE7Tp(ksR<%q2`Q7bIr! zbvg(IBJ%APfFcgvgZ~0*1{T>JfW!s)?gu3*um~3dVS@q!0!xrKM9`2E8o7rM=_m6H z@Q+#8Wx(1A^bQ2aP(qLu2fdX{-ll<=+XdMOcr{`~8h{|#1t7cv!Sx4lEM?B&;Wo86 zj)2?k>xro9gD*(YG}lXYzJl$m+OMG$M~a;y`g$0vKapgz=Tp9$vot|i-8ArHj|C4q z#oC)<6Qfr(tLT9#7HmYMk@Y(4rYa+8X_=0V#K`R-bz0|b$-#;^2bsm_`DF*qW#J`u z>NPnwvz=&Z0i3(!z;lzR{W4Riu$32PqL`C$xFD7ZDH1M*~ z_{=n=#6cN%c<&?h;zAhnyFMW_T?scP!}RpYAWVS}ssh0k8-YMUwHyQc`&mGtACYdM z`F3|fa|Ui9Cy9|P29AOeJcAK!iL^kbAh;ko2z(px0k*rqh&;P+TA=us5KJ9LA^o5T zY##q|O8~Wv$hGSfgrEV27}F0t7>opmfctNHA@I!sh4+J!toDQA`rZaYx;6wt@E9S& z8F7vo39b&{yEY7P00Dgrc=rb*N*Dz~wgm%~5NTzN)3C0W6z_?S7Bal~n45NN)|1Fs z__IX-tW-+S?KsW0xHeRmAg^wd6*~_Pmyh%-WEw9|Bw1J=VAZIt+3Kr4^J1#i225vz zHDTJp_K>tm(cljda;Jz(U?pSVf``hKzi}ifWsfStSnjBhvfzhHH_hN>N&U+A=^CEg zh-w#!>}xu>yGL%+S?JcJmn(j{k1Ky@_Xd6dnMR%W(*`Pg9x}2zMHj!}tV-N*d8uN4 zB#s(-T(HvhaevGOBx5V@c4FaZNYgd*oCs1qIiyu_QivfVV<0eoJiG=KHr4bdpzo4# zQsv}he>*+meG6%t`mh)xz{}HwA5nXNl%j~%8xW(GS`({uy3)J1v4)z!y_xD>%KWj1jJJ9&q*Q@W*I4oi;3?d~jUjW0IL(Cot5 z(~99NIt#5vL1xBHdjCrE1jf;}s)Ysm$K*OJJ30@XS$`d9DPqNg?R0Az>je z99agJ!Y(DlXsvaql2*r5*5SA{Syj|+g^J?zw-+KRY34-+Vk&u6TF2y8mHOlHb$e79 zC6%0$#v}J7CBoeUgx^1lhhr*$r3HytZzZgJVx0PR`9@`8oP597PW}| zVQB0tl=HY#WzpfeC7Qx5yiB(S&hSb%&Q4O+GC2Y%r;VV9v?4XrW2gzBlQIe>nCE$C zUOHwvSblN#=%)ziL`*F-9WtIsnBQF1Z8uyG7ImUy z;6_(*qpR&SN@!6@VNaB@`F71Cz=a!Ifr3O@P-I_FWgPzxUvCM(s`S(KcFaM&^_Qx> zeOeJoa8*(aKPTh*PwvFjxn0x$e4PD%JzD7htRD7%&01%~{|ueT1ClZOU5zCOt}|eP zYrdnF%y4ms39!|R5H{1q$Mos`z|=Z0_0*+uWlw$6&~cZ zG2>>YkcjG+M26$Sf6U2pp->x0$mR*NkGT7{g_GCYS+^?#_V1z`8gvz887Zo$P17<} zf`5(xmXdG3N;RJScpP}Rd6JVcdw#y*pLdi#>Te+cMfHxSpZT?hqPRCmMJ`V@k{K|` z;=JC8O=3jIvjsRFg^(NDHDOS-n1r#KMa;^@Mn&gVJk%Q8UbQLj^<=W(UoLNYQ;MPA z2XEC3^ofrEtM)=Rp7*iOoAEx&v?G@_OxriVF@WEZ%H%BCa#9jxb?0Leb=Q`rvMlNp z$4!lS=O>uDdSqsg8ZNeW zd+az|%Ijam1#&vmjBAf5E|Lb3mXb&dGA4aIxq;=@Fr$^cPJuq5xlL7Ve;XVPz{5r^ z+t8>1xgw|^V!FjP$dMA<`+HyZVm55`@9G0GR?<<@VZX|H5^D0&;uy{=sm=XgMb4=v zvQQ~I-pB^tq^b^;RiackdnWLjO9V$VBvdBf**nSLn(^DbcW^C^T;>l;*|^+;!$JiG z`1nOKC)jFmmr!$v-^U{V!uWAUL@pQDz*pV_P_?}zW63Z{CvD`Xq|tGFrWI|LPHns& zaIuqIMxo-R4hM=$SA_ZkUvhT#c0W02DW~11BpgA%EX+guADF_A|7s}faNRnZQ4+K= z`LEK==87BJ#h}Y>h5cn$A?>rVrYv()tdB%kH0i8iB1?-b(@0UuSm6Iiu#oklu&6-- z;E0z^$G51w%UOWs8ipNIzBr$&ai{o1{fbSCwY`{fICCzPG);8kj@kb}L=fF=YA8ka z+Su!02b^|VJZvShaR84)JoPV^KBz^Cga4sW`HL&W3zP7RJY4z}=@4ss_eFXGOk zn~lWzs9eQl*CF1E`T7z_T8;bV;A$c;inIk zk2@j;xy8L&O)W18NzLDNK&oYSItp(eYt`$%w`rVklToKpuPZ^3IN~g(v?faf;D}8? zQTM@Q-lrIr=`p=fC!;9k(cNN2$`~Q)eHKO$>r7n^s@4Rb9mBg95UD^$rQnGibl@e0 zm~TR%jMTR1!)ZrLpuNmYvB}IM&r{2YX;TU|8&7OGrJ08Ol^=2=@m)YjwIm888IF=; z`aJ_?i|m%fTtWK=S{|oQH~;|yK(O^iplG_IX-_F!fBsH&98qp)pjO?%==2#{EC7`lOa_rn| z=+}m`4XvS%ROb8rZ`~GrPe7b(gt+!xju*H_FkNh^`>7uTWeNK33%ixOM3R*WXrAK&rmziLqn6tcY&~&9ADD5dElT`7^m51Q?P}3k4(D6}Xuij0#U)xuy*E;P zxhDIYP~5k)%r&qx)6!d6ooV9FF{nE9Qm}a`VR+9L3!L;&rkUA5q<$BK%8?g}5=SZXm#NgOe8%+kVma~bVcwKFzym2-K{$Ar}9&(o&ne9@$Qxa9}a zEIhKb*l?DjvhpIn%7tL8%%qd0hmGHOPJfvGYRwiV1xr@}gf+vl9%z%dWc6Vmx|LkBOi~J z8)oDyMbW>1eH@s3oc+4|42tzMu(y`FX0aDDA?tjQoj6)8HLt9T1UE1(!$anAZk%9O zIT5)`JNuLYh=o!l&D=-@pNB2HO`m<{t?*r(_4XF)ke6{#J=F-|VOSJlZkp36RPJ?- zoUDzQ)i4xh1^8(49=Ejoa6i1+AD6%N&5uw3Bg%s^os8J{j zq5(npIWvR^F(rhP7&GC)6=Q`35efaj|HcE3O7zGN0ip-#el zO-KP5!=*8F6Te&cheTm$=yW{c{|wzOR|UC3d|c7KpRQ?D>|}@c!>qe+@_yqk0+cG& z#)jr}?eCt9_6f6vPM|yxH+K$jHadWi3YM?Od5qYr6a&MgXKCeYrsos1OTzYd1xOLZ z_0Nd@R6taS9qx4z9YBdGbq=1-EB@<%GESAYCC$qt+PgEp51;ce@mNy8>*;#c2&JA&>6z{xHsbL z&r}@q-LUA@SW4u}ZH~X!)Zx+CSx8ri9HJlwG8>aQej)t)6?+G5Oq(SV=6fnPq!H74}l+)|Pvtl3dykti0S zdJ4R1bt2T(Tm3V=p6qso2!RBYyfur(?oAQ`$SbZU@Ns|-3pq&vY$LUNaRh(~t+B_k z>#9*=A6-AK2baPQ(q8oLbB2jCS)mxa;kS^SxGT7e%4H3VSn_8FqaGbblTDO_xLKw` zYfvNfsn^0E{GPTT4d}p)T{Q{mav-*Ij^ePR8|p_hpJ(cmjl2!)Ww*gyN#SY%;W-|U zfeYXnDho5nbC!3Nqaq2fNmRgvPhoPE?xVgWYyz3cxQvG~Z}kHP&+SLh??-h}Mv z9_8k!HtP1}Nu-Yb5QCR*b(jqi!|WaQ00b1q*LV`#Zxvafmwy#db|+!XZSc9T%NbJm zlP51tq54`X>CgxT#Uhu_T92@Ig5W7OY)g!!O6lTqjb6X#Cei1qEGPiyQnfneN{NMR z5Xl9oB$ftc213HJhPueXtc}{FzQ13nJqx;0aeVYNas^C5cCxs}uaF~WM}lC+P{W7k zKOGFq!*k~OZz)FWS#3O5tHS7l{dM15Ib?J)o0>7X!SbmF&OZ4(!{V9;8b&kg-4Pb; zv3IqZ6hjaC9p4K13=V+i^=S@7#B)@2oa6B7p$&gW^7-{ZzVv?A+a~xFjmVH{aM`Yp zjLe8)9_W&9riJta@vV}UUy4Jh*4N-Cw0?*~v8-KHX_5ii*k_$3B|S@hQBD#bWfKM6 ztD+QA+ngK(BR}KjqqHbX$a4whe-P5V66iQ*4kG|lxszjopdSG*ksyr* zmO?L)vHRZTft##x#zXrF1_2Du+pL+76dS8(AiV)Xo;3kbhJX&T!9u%Ho!=)mqez08 z@Oh#kn3&7qe>=UDBQ*>OIe^xie83g%yh<v4*~w=lJ!C;)?zgqm86z z*8Da(f1FBqPsn_A*U4C3moRE7GR>KyvGB-3N}nNqqVWo>D*#4NYk8hfGx{^IS?o<1 zu-e8P!Mb3-kGC6}OyzlBt+6&49NMvWWMypXwLkAplez)7rdrLY@;76DQE^uBnlcwo zJ5Jyxe@DDg%#~Fh5ned{mb_}>it`YS@K?1522*o(v3fuU1rO$q4CMh0(rX;AyuC%$ z!5%?+bsFZ~RSpK7B~}p$_(dsqfcd1hh0^z&bo(d9)2#Et`-u$2sPOaeaDabQ(EXn2 zyTrgs+S4AFq&kNqO_Q}stA0kioOD~ZR7X+#0gE(bHrj_fd^K>oB25r@a$ z{_~kMSd`XwSq$fyV|C=uV$RzraOKnjZU9YoIR9;$iO0X;`Pd;t8*t-O(e>)MjmZ4K)$R z{{0icw1U(2_9n&Uhavv7wu8FA2pGMBo5v4-woVS!Da=s5p#QlY-~Jh3xZ)O@`OQIy zm`lw{BzKo{V_8J2&a%+64iFYn~aeipYJc(V)wF2 z51kbxt}2wFjpOR-_PBr(LaHHJhcrfk-wQnU5YwAmDNwdOz-$gxDLTP}U2Hc!WhcO& z4QH?kN;do>WB9O|Wb`e!%zh?6u4)HJ{D>ya^q&ldg`Ud?Ganu2B|$Kpld}o&i7g6`xvI;cvFKPyy- zno$&`Vnpm3s%f&<)jHjY@=*akz6rLx9Kr zGsO#i6{66ANO&$e*pA8$iMD<3$7N0MA$n+7(8YK_^c(876P>aED&pV#&;I^NJE=Vw z^dbMe*c+|td7P@QX-M)!9Y8M+rY=L@EDpK`l}NFMX#Q10>2i(5`P6|@3hE{) z?3*4s5Ce8R5c1Cr#Fta|l1Se2E6cISs`WhlK}Gnuk`4^RJhaheC2-4paxsTtZE?4~ zpP9Qg)T+;l4(pFWqrnIG*5_@X%{Y3MoNXeJOlAYQL8cgf<;^-0ln2QRNGkgR_psYn zPqy%C<8#A27qgIqTa!l#dslwysgG{gzK&$UBYxwl;--Gq3QnXBwD0id!PXH%EvOTG z3DPqIPdmU{Wp%a{mL6xL$U8`G;V~ELS<1K1G2k?VjF=8O2wejr1y<0O8@W;rQ7 zA`PHhw$P_KKX|1y!O zzLHcA2K-*=#a{)`{uZll^f?Q=p{Z1eF+4Ybbyib8Lc&O$5h0_7XqM$_FH(!mX zw0ADer8NUH5*Us1P>pI?R$dYz!0oJBoZNNTnvn?D>?}{($DTZ}%a32t*vY0>hgg}# zgN@%a;VyTjUjND;!QVpg#ggB%mtS)(AxR(zDl~9yK~DyJHDuErhKf6wTaTU|8Dy2u zb}aMkR5)ISve){aiC8pSejIMJz5+oi@ogIQ?&Vqd%&`|vh)(7-ZOvv5gLKrBDf~)u zZFgLqj#At{d%_HYWevwD7+_w8uPD$aQGd6TZ9-axe7`bX}i%W*v+2W(XX z{NY_N!Y7)`3g5ksq1{&D01w8(uGn?bcKj;E@(TbpcDU@W3mhy(J#VbR8zm8~iniKY z?fZp?>l{nkv{=w@!P00qV`4b24KqZtlNKx8!dnkQBUuyuS5bZ;68-sK=FNHcoiH=C zZ*S_&!GxG!Lc@Pz!L@eRiSPQBzIhG_mzLr03vMwATWDaIF%#y(nZ>1k8#`CbK_R_b zL+bzpF1rlx_!h64gQ(aKv+u|^F(Sv{y>kfr3N!I86#4ka+F33d71QAiiw;HZ?YFgF z14;^i`+ehO+7>liWamH;{MM!k9a8kowZ7R};%-v^^Msd@E1eEOke*!wM)!&J`;3c8 zJ0be8R)y#mAT(GpVEri?Mqq)O95C;gA!7mvC`=t66C&iWnk24S=_d$r7(+v` zuvA1J)=VdIDWi@`e_gu_>p`y8(D_`=OLVc$Tz>Hi)O@!}F3-h|W#W*sZja{Ft$7Hz z+Gf!zHU^yE-3dMQy%@fBym(I+x@bk+gOqG+A=zAA1vZPn`H|{+D6KcsalRC1yhy)+ zc5ctR@x;V1&F@owtrSL3$hI8~nOPeQf6)EN3#b&AOe>i}m%#WijX3=V((hKY9MI|; zj1u&=G2+@*ud2qmb2!om=#oG`D)%=q*tS#(On z#!K?9qy3n%m$Ii!X4o=+NTN@R)gEa@5IeztZ1&hK`Kx3re+mQI2*B*qi@Cn;%H_}~ zl@MYrqx-CQ-D~MaW&u|G6;^m=LS)S{ zdsM><>=^XJSad;VNU|=%VHC-&?F3?Per1#WH3vn;se&YI)l}|9=tN#EzW^+a2{kL2 z?d{X$?zxG$oo;}?76#z3-dPC^?XPy;O#HHLO57Vp(Lo*bC<%L=A!qGPqv-rb^zL}UZsaevcKt&?fsp~$!@NNq^jX_2d- z!RZz`9yzuj(oi-ML zaK*mf#wylBt%eAgd&Ml7#figv^s=nphaGH?f*)_@k(J!A7^TjXCZ^4@@ol`UJo$R4 zr2Pj^uWmeK=MKqN&zl1TOK!cG-Lb-A&s6J-bdj>3k*`!bX+%IV2t&P`O+5{L!~|Xv z$mmEb4h*kDddKM*`f>d8XKBZF+ci*mK0-alHKF}E?hkkq%wFR+f2V)It8N`K!DT?e zW#?0Yhp6Q?cH~WPpgyJ5ko!s|X{p=%(vvrn(<7r9vLo=b*C#v16sh7Py>Duuci5)@ zq59}8g_7Hmcmm)Th4(LOf=aRsWl*IDvsa=*3MP@D6ZIF-3E3DzGqmI&pu}zcADL3b zNU$X&HmLG4@`matMh(!5j)4*wu_f4;Weu8qE4Q-ig*z{p+?+2RcZDpdG5XnDX>R6dx+*Q!Tsow{su!7BKK zUQTIzvr?nmO)pz-pINKf>Poea={$N1cA4=IF#`0ry-^ger7tvN*R38P|6}xsF}$5d z{J2OvHJW<955;AH<8G9Y>-D(9*Z(v$(rvWuE%%Y5V-rq4c8l@}xOr$u{fJ93YaCnJ z2D?y8<^ix<08>wf-ivHW+(rBg$q+HVaS=+P7lnc*0jH$=l_OC*}G zNu~L-`GyCic27AEPHg(tg-_ZU^?PuTSSqX~QmbWNGoYuoU1yJjcQVR4*6Na0#;nC- zxH6g_E_?dz{w`b>)>OFwSPVxCH?0&Vx}tF3jsO?L`7! zCRkD5cT%5x6iz04jW=aa0j-?p?YrdcLYu*fzCOF~Xg1SJ%Pi||sHh$3kxSJ~-g2Ei zD10?~Z3Lg!P96_JDRYKG-eLKLT=4Yt3x5+(6mhP;k}|TRzh=Q&c+tBn3hrywmm4`F zng`&tzqW^EEJ~HIK^1TJH3bQ#g2{e0?%)m3AO|Nsj(?4+JOh-jdOV z6}h~Sx?qWpj;9MNy)9G5KYdb{+YhL)lm9{(3zd@Z?zg6(EYI^e1kaCLZ|Blvquayc znw5AX0S33UR7Ec>aS1NGg}wncVN?D_gf^kFcxd=;~lxHB7NPyP65zHyK ztur*Lo(2Ol=+hSiIAcz1@GCLlCwJ9I0-oT9{pAjvsa+z&u1u^sW%E7p0IqnMM0K?d z8(7klKmz|ti@EVI`89sl@8adE&4J71RD`mlcU&}c{$hhqx>)vqB`t7>g}DPff5_f> z__JW-5#BHa^Xi%-YTF`;VF4@QYn?Y>8HDm0iiD6sso@5OYVmO)=j&!%8>tA3va9z} zwh+aE(Ht#0X;8feqc-HRwd(^#%&=+k39R6=J*f1EGJPw1DWAOH@&P!FsEL6wG4GK{ zi?o%}H(?N~I?3V{VrG`N@vjyPZ_KxI?qirQBnDrl7`F=!&Xe?hFMuyU-B87-$dqOT zu{oVDuTL~8+o@A61qD}Ax#K@^xQiDQJtcBN@58{utu0Y)O>uq`lvU&L+B;VmFAZFy&)e#s(>^#Vwjp=h6P)r9AD7v=21*tJXj_Jl9F6 zcAV?>VehW2TyC--ivSiNQ&1Dh*r}+Go4aD2(rV=^ohPtC!Bj?_nPnZXjbUTavHt3K z=N+4i1gi=T9u7ROicK}qB`F1y;)wUO`dPsMZ_`@S6tdj-K($hZh?QY7gTqpK4c);i zod&<9D!+7)tdsr`9nAdEMnCBr?<3xa0o5;pY!m@%C?xG@TL5CNhNVM_sIVmi_$9(9 z0BIokoWsW}Su?8-tUO`duF#CE*}SNRX<@7q?@;1Z06mGP3_J=aRu8Sh~{J-*KK zjnEirf4b|K)d4T`zdJ$+bv|(j9D*GrO1SukpCu@=&$H+414PeF9@t2Dl?u`sl1-)8 zy(Z^S9gg%&^-a``%VO=K4)-lFFIl zHnGH6=*(?MA#a|wpkL&T#Inis#~i2=BQj26GmuRk7y)Q{Iw1(-&9<~brYrgaXC+Xr z7z}k+yV5$yf+qc^%w7Y_nNTS-N|ssRRyFLQoQ_oRP^U^BtO&K|%dK*Lv=~l5gm|F?p>^ck+rprpK)( zKF|+(;eb6nmpt%c-@^nlofFrSG+eGtTst(@3klHfFmiej1K$LKv7x`<#6E+L2gXDc zemm1F@FF*=&Tv$@Ro<;4C0e zDZtaDw$=}Mj`H{N3TKH9I&Pr!41VSZeu(CZ76CL}bSA8}%K6YvoF`>@oWEU>c%pwR zeAu^|(LdBX&r1?)IiV)%)IQtRKhHJ3oMR|v$-0!PyviZ`W!X3aANnj*fG_2m^j%$`hNMlgjczY)wXyVC^;G1FFKr!>iGVN3!xd5B1xg z&jDmayjIvHqz2iC2YjyR5+$NlY5^+r+==sexkOe4 z6*CNa(VY|{1EbTSW7D9Dprhi5Z`6UIagmbA0)$cMyLI_BeU9{|x=7ngqZPKPKTSQD zzzJ1?WDAGibN-rer@*3KLBKm{(MN(Az60zX(u3ju6ah8q;g@v6hV5uu-G4oNMx7$l z$HH|2zdR{g9yEibv=EQTE;6cIsC)(|J%{Y6-Gp9uB(+TtvXGVC3U!d$HM9*m`kre+ zn3ucwL;0k0ci+o=Z?v;A8A`G8ffGq4S@3@Kaz|P3s~fP2UJ?I*%=o>Do8wxk$^&>= zlXV|c^X@6-%{>T{!@6|Hewd=qRERmpgsU0A9nFiFV};_T-QAc?#e03+?xSQUg-%R} zJ==;}^Xsz9$B?-GJ(IGk;90J-HKX-QJO(|(jYhdGEX&aQlhF^!tW=;f`WA4C)vch> z@x8j87d|#RtozQj&B)P7HlaQ@Ee`-4YW+L$U`f(GEl`yzlx@-11o3AXCb=`Wc2s_1 zGh!L=6rcqvPAhz|W;WQPq-{=^vNa|d>LSo2@*^8*_-E#LR8VWg#X7}?y&QA&5Zr9) zxFaNc?qRfl0NCjVOHEf(Q!0MjF?r!$i%I8Lyxl{kW0oqsh~Wfd3DxA)T?x=c2-|3F zcsG9jNYup=e3H#cY^0qaAk@~ZFUpnd53RjrX3<*WkppDlbn1a5v8H1viXdN8pYNl3 znOE+pFjj`e3xt%TQ%J|OR>=>?h`kH3XCd+HYM{04L|?Lg43&XT4J;r|-8>Nw>M^3gUObbZ0rQ0+wA|rz6ksI@|=%xpO8*k>@bqvJbT%Q5Hw4u-HSr!7u5n zS;1Eq;T_2I1^JkSJGvLX`N_@a%QS%?6kIJ|`N8-6On61c$=1jOMCL#0F-gvee0C1e6$@eT4aA&LpPhyQdW zSY=8rV+xsLup~!6Zn};U_57c9IKB-@FSp-l+GIk9udSr<+r;2wd-d#T9B>x>v0q8c zhMoktX<$L~ntR||LZSf1CX^+BEV4N6rZ*)&2N}jdk(fyHOH@G=S)8#XLRbnKNJ`oTm0ZkO!@OCPu3+ zZ91IL*(mW+4syDJZ)f!IWSQ*Gsh&4t7*>Cb#P=+r?2-`)C9?bqwxo{r? zCVIQ-RC3D*J*|ZHXCf!(1U?QD=W(BM)PQl$GP=4i$A4<;Ta`?B`v zGur;!9t!p_o+%D1aBU;N7raMJo{xBN>dB2Cw4ocxXhIJpQ9u4 z)He4q#{hsd3*fDK{2lq0=@gg}@|KkCEyb(H{FIiV28M(M^P{e?gh9x;stuBPJYZFU zW*<6YhM7?Pl76raw=fn7?F7%BlYYu(p%Vc4?1#HKhz&(~v^C)usH^0ipL~b=%PYIw8kPvXy8V~APvZ|Wb3c(gsCPuWa z;_K6S{!40f@3Ek`(~+Mi%8P`%vAyAOMF}de%Gpg#{i|5s5~z@oj^%Wq`N7k}YNx%` z5@f={i}#{+!+nhfTO9Lz&HgWHI)w1a7)!mY=ujH8v_7Z9k%Ku=^WdH_P~w(G-y3|3 z?Hr(?oQNq_ofB1-kVZx3YL;EfL^|P+)Qf#W)(;|)JNP`vg|Xb}0o!NFKh@WHrpmYn zci$^FJG1k=vIa34`Sb^6ahH_liLEj4Q0APdHTHN<)V`A;>bp3qlu_!-^zmYysOepK zi$+-L2)viMJ$pl5*-5gPXt6^`dHM0_MF_C}4n2PPE{?7nsD^FZ<&HwpJ+9O>kXKMh!xqc&ah0$76USJUSFcSmo#mk?ix ziFzCL_h42Or`P1h?`%dE6uQ=gZ#bw0e79#W+P-)N5E9E}H)si6wz?ZgWg-~cJ1>CH z3r*RVErem$^{>Jl3njMz&a^?t6eayqyOLWQ49UGwg8O=O+M*mca$1ySJa>*=kKZD! zFAJ;5XSNIV;@q0#5Df7~AuO4OoI%Vz;d7-r)jwNVYh*6xr|%oyXbqcdZ#x|wmsgV4 zJBa8esi8#|dkDrFbeE+kp3jVnu}}b)oU`=@rvq#4EMs51u@mbzFjwe2N=gr|T|z(D z#>Q}2{#tMV&hG~|UGPN37%s0r$=Hon!Zm^f zon7{tZh8oR-*b4wBJabi)Iy9W5v}6#hvyElOxeVeQcdL)Nm>6AnNoZw7_XZ zH7&?W=7}yTOI&by3eyPFLSw4&#yjwB;CZRv=$zh?rrx(&SS zpZotKy zrFRUrXsXO3;4#({Bd!BQ9I1^AW@@TT+y5tYB+Sd1`!eXEsj~kCk2N_T>1vplH^#$c zilUC429L6)8R;naN7!O=bXCUe1xawWGIt5h%iHN;vQ+`7V{ZOWVHlz+W75E+imHsc z0g+%0WoB5aC}SH5#a&(+IS>(NPi5BY|0isN<8G;qbOg+2Ev)J_P?d3vgklY&jdU3m zXH0NmvUsavPlBUwEsb;n6=$t`Vlv^VVjToY*uflh^%rF<{3EPYac@Kl#>`;icFs5piRf|G(g7T%0|~rPpv;X7n#}TPZOv!o`^j zSpP{2PBtvsvf6(VmpQS@VC$-i{RceOzNBo95N=XdxLj3`5D!gZa44h((=9kXIh;_! ziViXa?1xU4&d-tnfIT~?p*-|Qr13F*Fimo$foo4-;c&Eiw|;>* zkkvOS5`X!XELcJd?H3o7!z(R3Ak40U@jaa#PL#clr zrj&zM$9;+DN^(qL8-BmvAIIY~;78rFTngrG7?C!fp)*X=x;3@hcU3rV2H}I@>pBi$ z1t1`?R}jhrFONYZtInQUtshM^i8(|Mci$EGV_J-43i=~YrQI;3%`j}oe0jjhg(t*> zQiCaCo`zZp5We;iD1i8DoVzCJNUT{<_u=VYg$Tjq;>Gr};IT0|zapFyRCFI!5?s7M zTr$b<_%06V{d{?wF3J0BLqG|1LlD=<28?)#4PS2`^$w&-B*;hY2bnnrpcxJ>83G6?ZXq-)`V z_QPW@Z+3__I&d(s2$yt?pdks+y>OsSp2@#U`y>3(7q!>saL-Cw|7qE|-|EA!M8Z4t zy7mbQXai~Gy5e_OD647fv~0Gklp=4c5{a~ajcEAz4P-3a{5CEsJI8cwSc_Ozc1Ntx zLd;wSq}oXE**xb4DuL7x?wM4%#7JQ#l&ouX=5x?@HcQ;^^NwNhv16_&BsW)d!1a13 zHQh^LAQdX(s8_q=Yo|pC6-P8uA$OxR`y;{uz_=C?=f@WCgCN92CUez|MJOjmUSmMt zZ!zUgh`&=9?TRL5mP>&v zKr-TYcUR_Nwru&_a-w|V$`dj%FBuHupsCDz`@@l;!;TxKFz>R zvq7T}lR(NO$(lVfEmw>jGtbw?3mLPCoo zewBp60*N9Lg+fabfkFX^BT9z?#1eiBM*{l1f~QZWVx uUpxOs_LZ$#?R*LBPO(UX9uQ7wrFw<~c5CP!K2e<_%tk=l=)wOs+5h delta 14629 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 diff --git a/src/Nethermind/Chains/base-mainnet.json.zst b/src/Nethermind/Chains/base-mainnet.json.zst index aaaea720746b40fc02e899e2fb65d8354d88fb23..450c06d9d0d3c46a0c5f1a42bfdf04dc243974a7 100644 GIT binary patch delta 10460 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 delta 9369 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 delta 9583 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 Gy!d}={}L|% delta 7954 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 Dwc$OI diff --git a/src/Nethermind/Chains/boba-mainnet.json.zst b/src/Nethermind/Chains/boba-mainnet.json.zst index 5921651083973e0880f7846a40def096e8dded8e..b9ada0eee6339f51e5dc150d23940e44f1bf2ba6 100644 GIT binary patch literal 2564 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 aIb<*D-BVlTa<}EX*HHr6Ll%`W35Ww}W9YH~ literal 1502 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 A^8f$< literal 1500 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 Z!`#13#uB8de`4~5gM+uM_huEl`G2hr8TJ4G delta 10812 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 delta 15538 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 E2a6-`YybcN diff --git a/src/Nethermind/Chains/cyber-mainnet.json.zst b/src/Nethermind/Chains/cyber-mainnet.json.zst index ca178a08100ec2071325b6f64cce0862369c2a01..46cb6963559929dd5c15f375a909e4aefeccb430 100644 GIT binary patch delta 12641 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 delta 11488 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 delta 11442 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 afB!!xD8B+V{;7aBWq_BsMUrjm>VE;_nUoy> diff --git a/src/Nethermind/Chains/dictionary b/src/Nethermind/Chains/dictionary index 3bb35bbf9d5552c79f1b3906976fef2c89f3f123..9617a6705bd58bb65bb20cb4e6875390026735c5 100644 GIT binary patch literal 65536 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 RlnFnTc3``|eo=iD{|`)ZI^qBT literal 65536 zcmeI5%a7gHme&J)Te@oXpa(6~LLj;`V$e9h_v4P@M>)B9wQmyVg&6#vPQ`JRW0&JN zS64z}z@#N6Oj;lw5)%eUAi=y50ttx;`~xsSOc)|DgwJ=ay?^I-PF30GxNJeIZ#k(t z=l9r;wbxpEy?6c3lK=Vd|M|cC&A<9P|Lxv9 zCx7xM|M89g@sr>9$zT8JZ~f-K`-6Xa=MVqN-}@hb|F8YzpZwc@@Rxt_p4 z18?W`n2yO%_NnHOOgy2hLsR!Ut6WR zS|nAx+LTi}lzufE!qutm^0dqRYF(~pur(U%p`HD$u`KFg?rVE1@3U~V8j?O6ig2~t zZL3R*f^ii-hoSMemaEo76-L`-+0Yl2k2bGcf3|O$v6#onduW)$2Hsl`wYIXt-)hUT zX?wc0t3@BKjziYY-QZWte7CJE@0&Uc>yWaZ2HRq;hGCd$n?nlbZeLXoSr?#GGSp%$ z6YC)x0+gz}%veUBLpFz1ufg^v%flE`)^~em%V?~JIzTC#>!KXW)_Z6Il!~k=vMC8Y zu;e>!8Tx1Q;BycZ1-s>{^^nJ_Z8>#ypL-8Qn3>2Y8G(>LI_o(I)9U4{hi8k@mpW zddQ*LqW5v7V}NK<&Eq&Fp@$577YB$`1U%EBvfpVg%BROH%PQj&U^lZfjsYd6BG|NS zM5_s`hBY$^O`D{JI$G$KE38kF)iWJj>4#pHe(QYR^QUDbX|)-l<~3$vE?GA7y_maA z%YMIQW}0>1#wQc;v#jVg5E&c}uprh`BjbK$PnF~HRGv)XUI|y_sU$I2H)RoF)q(~Q ze%||bYYiMNtU{hQ;a&~cW1o$BZtKlcOP}?;%+yELZN|T8s~L9M>Nn%hmRUBV@$!PP zG)ZHhbNDSZlr%+>3SuquXmU8AuCV&x0BEbZ4GG`aG4z{fggM;HxT;z3OdiV(LVIiS3K6?Vik*we;s&qYlQPHp)Vjtda77-qGi*?yRlV zSXn|FtCsS`V+F>Md|GI-?)M~sbC0FaHe;M4WXk?3ZM9`vFwOW+IGH3Jo7Do_a|779 zkXE^KqB?~WIsbrLGy{837pHviU&p)5B!ULaV;4knVEqeyE8$1QT-Vi9rBKFDm3har zhz@+_Tb=O@lH&JN!SS=CE~=&~`R;G=1S1V?RY}F_K!p{2*{QPXOYjnIkTxB%g>i?? zwH9fDYx~1UIL^8O1yP6QkpQV|`6hvh9f;phLfI71Sk@rCl|@^C@9cj~-PCDS@4$ZZ zbzcBCg}7;2i;Bt;m^$SnFw#@n=KP;EWt+8WJ?Cj&Oihj@G&+Q98~|Y`NAzm+M0=vyDw$_d zdlP2GE^87>Jwr>gw#rdRlrgJ38w(JIiL$KxtEzge<}`1R(~_)A(G({1tO4SgiyoN+ z3T1Dze}RgS6PaCuXn{nS0f=PSmdvx91(9wAe_`L}=0bOo{x16~?BRcg%uZ@6Bl^?> zIGN8qLQCCr`G5>r!&{raL(xI}EBfx@p5FY`um14&-~6M8XU7kYA3wPH!J9w({a?QM z&U7+8K7Rbkv&U!CgPW%(^YJ~qexo}ZzMdXkAIC?0e>3@E?3?6h=<=qzezVTHvMkeN z%JZAI+ncxRGS8=)rKzT)H`T}Y)1$AaZuB?ToZ;}#{h$^<8M<$~Fp|UDSl;MZ+C=TW z(>?wGFq|>M$l#}K^0mdy&DNDMc&$^<)YEaj8S0oVm+>T&b+9yL>b?uM} z7y(g!e$t=*AZ(c)2gsp-SM`mm=)jnQ7ucK18Qd(#ZW?BB$!|dT( zX?6GbN%rpb2Tk|M-MRSW>}mb*{r8T3e0<~hi_hoJ@_WxdzxBg=&mMf0e({Ts@{_al zUUTn>`gqG3;Wz*})P(_llS(DbbL1IQoIn~V7SkANlEj&OV$Z@LwLHaUz^14tDi+)TU}%Pl}kT_s7~4=(*C~Vc)Ul>2H?v zEKj6!Z(;#0L#hzHjHVJ?P<311{^_wT5R-BQ6-g}^JD6iubyc3#qU*k@+qNF2B0-qR zhNkJD@uci(Huj7SC&g4UCeko8dcgtcnUZZ-;2jdC%G!F3&LD-;(CDkZz>ia->>@I3A zP##aV)k1k}L(4c?v^Y%WqAnyu_y%v$imhVO^fCzv@*2I&@h&Ph4LX2he`9~=3Y`F* z5jxb9XaV}0&#jC?te&O?^|A*7Ng!*+pZ$ebB*~t!h~62g)LMSSrGN*@X*aVaV-p** zq3cm4lBsIuqylY{dV<{-ZHq;>t!8KfC8q1JIE>gD>$X7Ju2@a5h_?96ItsQ>x6zP& zx`fWfd%y@E58^y{7~lkQ2tUl^ibyZWK{4RFkSiY&SX(xt{Q(MDp53}4YjQryKjfCI zd_>}wHJCRtp$+ye``0lobbEW7dnMMr{Q>Eg3r!J+6N_Mo#3!VipqYv&(8y$s?ck0D zI?(t$mSHSdBo8V&^5=u27r+rC-s9(af`gV^Gk(!1WCtlr_6ZixVCL|ZJ_Q@W{EC)V zRc2g&UU!$?3$`8>8Kl!j=FDKx^I=Mh6$<>4+KfkotpGiW|AW;5jf#Pyl_prNO0=sI zs}{25?vJ+m1qETGV&+1mOcqSjfMrG1hH>S288bj6(c)hq}29q zP=k(*EoZXqvsq>MQ$-!DVCgnUm^7kJ-eYGo+5zfna}qxbbk22Ff`5llx3(-4?zBh{ zlx)KnONOaM$RGfeHQq724;L)VUYFuF$hKxcwTT-TXQKy#mG1476Mo?9Qtf&9&I?HV45;((gg3rjK3!|EQ^&bZj#)*fw+ zQUb7MnfFIrRGXW4Df5RcJ1ILLB!gDh5$Wcs7-32Suo;C zUJB0Q1M9}2pYXJEizTQlS)X9sw${BM*QWB7&@Z~mstq$78)VX1nl^I*#n>%?l13X? zGBXUHVH+}i+IwNM(Q_a^q8m&FdE4gCcmN5lq)@YdYx@y#BR!i&CU(32CkAZ)hpPr> z+fL-_6#;2PQKGfEq@_6-P446F+~9B5SOh!u&NX`G8m@K7$D~BAp>1%t(Ph4s$aZ{_ z#I1FcqfzUrwNHaNTr#wM_YDDwK4^bhXUT@>LtTj=>)YD){UxBrTJ>?uv1$D{mH?$P zj6vJe`k$cSmW^=r67w!{0CYrnKv7|>LS`x2av*Z2&k*4W4t!N8<(JanUZF?HEWL>JSX1W` znj#$~i9tr=g!i%Ng!f#Hp*YJ`TRmAx-~sFh}m|EsV2e*hTQoIu=5_!ZDc?RqambXrw5#W1CaHw z9TX*hYq@GYDD*_5MOVV+8Nz4MEClG_5JZB_Jh%zb)}Mtt_XQCOKE^We!N9j@D;ZJ( z8Y&O3Vi)wc6v~lwA*cq<6|PPQw-p2pQ-t`8>>>J%f3LtKRpdF2U)5PK#vr~)ugZ-X(|h@5KkcA?;5jcjTMcUQqKr2C4R8*9?-$u zXF|=$^+{sQE}q40+5c|qaciv8#sXt9FEB8|@FN{Y%V$vI^^NYl-RAz^B;VrC#g#!| zluY<9^#gBbWNC+)1l{CSjS7)-O7r$oY5zq-O=WUnTv#bs1dS`k-M#3|%kIB|nc>bc zW~eb8$XVbk7>zAvLVrER32TPOV~O(^zY$LiRvUS!#Z~0RNBoe;f=6Fsl_Y#-lh1Y6 z;-0SvtLtz#<4R>qEfM`;$%;F|gAe%d-j{twkHsL}X=<=aA%@EoW;(aVRhvfF9Hfgj zFzd^?*!CTVoAo0-i~g7~RGUJz2SSobss#+uw8XZcNnv`#%*={3*&$=Hr$HB1!OXuV z@Q(^3q2&R?y?pU04rmkR>bdWW8o*5~1P1z?i55w)1Q*%|bVw0!i?yc=-i zVRJxOni*DEPFY&B)#epFHe*eUf}H0=JA)`VSEdYUWf+J7qIHoLDiyFEQT0SMO?7%AuRfD?RB=xVxDp69DoaM6T12w0zA!5f%+Y6ExO9e!9B273VuZL zI&`&~$%wg@zqYzz-awyEqAP{I=ouoR^&3Su8KF7g#5YX#&<>vnW49}4%}^6^GB`4M z8-gfuo!ZlWO+Ps_Z>{q=r7?Y$0xj&)XH>4`vs6Fe3*K-?J+X{(2p|qDYXRhShrLU` zN199LsLsQNq zZUR41Win3ksOqo5i>MNPCho&w7U86;;~?k?>mn@mCZS1YzCZ#49!R40ZPUA}nR>sC zm`VZyV~6DQl;f{KbxIW3rjakO;=KvwZ-db<4LL9ku9QrK>=~~X|5Z0pF0r*0FHH$BCqc1GPfQmU9Q8%-n6rbeboDIu0=~0gH;+ zw`2uC4988<^vIBMMdCh{Em2lfDUUg!UzhEhFS;%%m{}L>EVH?6#3Lr08el?(Qljz8 zR#*}iVHn1CC5f7+=A1Zr8K?9)ld3>c1QHF=7g(cS)bXYFwU_RX^$iReDkHDP<$T4j zY8S=?qqur3^YO2^gGEuHi;K?q4Ug7rG4bGoR;y#)aYO{V5k;&guxi5YPEz}Gb zvJ&H#GZM2+N1Y0mYQYxi;^k^`Cp+p)#-D zcfO_fu}g=R3XeiIEr_)@{LvG>+Ys+(bKl6I+w#%A-zFK*2xpbC&E3Q)+*taY^j2kT zsZZ`Os+C3uL~OR&EkvCe(QG_%K0;AXRY;2Zr{`wV@xc42A4Z)otw*(Gt!?+`PiPJD zqNX|V{}hADYt;}_Y*i~;LT=tSGfQJnkTro&L!yQuQ+`_HU!@VC-*E?sq4A+VN*8pN z0wlL=2@z)(oP)yPF(${!mq7UEYm9}yAfEpG{IZ&8X6B>_O#U)2ndYa8SZI0Z(Q)O? zBRUPp%)9f`3Qf+Nz%DnVx&d^?27&IiH?5le#1{QEulKg?=o~zV&P9wd7zBp~FpxGc zxDXiN>l}Yy4iE7XK?l1fxSqrP?21!4yIJ&^%4;iajVFPi_8*=DN3^HFg>V*djpr4| zFZ-O=U(ro;*H@%hqF7I4 zg${%?PYpr!iF84frz|1X1DUkNe?3kkia6$59<55UUNTH^;yyz^U~Ml_;*3*bTQcPQ z8b3M8EP>Y;;>lzfr<`5BD2i!NEPe}$mD$_K1=rf-TFx-5>iqb+0SN|o_Ehv33$t-9 zvK)gJK821vGGK`q!ox}Elq8Y4&Dd&j zmLS2WQ%jsUv9ohC_|c(Dnq9x3=kOg7^(~MSYcdn=ag{3zGTG-St4#T!D3BNHl_}8hY7K&5l|Xv2f=$H zP+xe1?;ZOu0W@s71hXqQyiJZvV(PF+fDq2v$FmeXK=8T z1mTqFQ1qB<(F(EJv^f?V0_~A?1j3xrwe__!y@rf=et^Oz#YxnBC>4nd=c(_fVrC z5NnkK45mK!7+Vx$ne)nKjiHeX04RNp#Y5#A2oqBptlYXi>(*y2yESeKg2#y5tnq{3 zj2;*!H|UBOPWER-d_g$f%)D9zni=%KkPSD%iH10VmkYE}4k30HCJ&ssOlAx09rH}f z5+~QH;u`GiT5|ykqB2&bc+AMSkkky0oAQr1IUxVkbw71PA}BNpn{d_QlEKWbWa}Pp z{@iqk3n+W|KuAOza{N%(jo5Cn01*Hnlgq^TEj<~|USWxY^Z_1xyvP4KH7sy@t%L=( zaKZ~{xSiM~GVC=39iY`gsu+9_*+@TaEcOPtf|w&Yw%y!NKmdre4GwQGekMYT%*m`6 zEA&hS>9G{7I`Li|PF`}JQAUUXEOCb+Hh_F3XaS3chKQA~iDw-Q0V`1nG+buq5fFZb za53ZqW*~y_(ER!gxXt96q06xeq%1XCZJ%NzVPlcY4LZ*Hksy5W9mdEO-L-`Xg)N|K z8q8*h(QLEW%%Hync3gns=%^@31Gf1Ny3DZBOZAdX-L_X3!NEerIDRXbfgKbfSsq=0 zhgcj8D%cj>fS?W4{KY)Mw#}x^liGAW?8}l^A$2K%F8t|fJ!#MQ_D&0WbG7l?b-g16 zQop4*nt3u&3cg0{iIFu z&crW8SMW?o5#DF*LiEAz3VNOZYi{c%(P~*i2vgS?8{47QIf~ti3fbI?-?Z*3Y9MH< zjjJklv(z|Y7xOoa!SuC*F<6@;mI!8W7h6BU9qT{BV@}NiLGc{sq!uj3b3K-YxC)nI zPD6Mb^a}Rm7$h7wCiZ8;4Pc=~zaUeQWg{k5PD@5aNW6@O;L$n2NR<~9FKq?ZB&@;? zwsI>nMPjWg3=l8(6GU>Etid1AGLV+}K!KMMcYQ7GE}wZ$Y7pa8+^`Jm#Ea;!qCgG_ z*;5j)=-5q5GSnG{A*ZTOLYL9ZVDw7q)tqsFW2u8^WGJ2!QkH7srIeyg!UGaqIgqqb z5z<7GkyfUJv|-+m640l}%FvFuRIv6*#Q=}t{6wF1&IR3UA@xA=hG8-d`%EK0WK^s# zhDA(_hZxa#9@z-Pu43XbAfsP{t@HuB7z-+o(G}2)4lJV|!I|d}npA+6Nq@VK8q8xw<6~$QjonC2$KqJXA#k)03t2~DM~paprpYd##o+KRF&9;+0T)Q>Z%*b zKt+Zc5P&iJF)G5lC4Ev_r$7!wI7X`?7l3G$3h}ATN_0+XMg;p|Btb_*Sj2sW&_97@ z#MvV=gTCzZh=fUxkBh8ON;;stFfF>A5wn`&TAd*#v<%R7xvc@>2%~C2%`oxV)dObF zY(OEIkl*=$_>g0a8rq>CYX|v?sC`^}01Pt)+6XR9P<+hETd(s1d8#IYp4LdNQ=|jl zf{}?(dtze0Y6dDlB$(V0G0SwUaV>V40GOePplYUdZDP#U_!O3f*$47zdkC&o=BtvE zCn^$=AebU3<4>zm6xoFUpJJ!j%^|{+Y(dJsAgzvw%&aH&C6z8vCKA(V(DM+zN>Txk zQ%A6_lQ@iJvKUbuCK75HaTymly%vP5t?`$9H^(x1qQ7;udAkU0eb&#mknmKZ42u+Nin+js(GJ7- z(5j*qvNgOOhCnqV1RhjwFat*|;l?GURVFyYRQ1>>=9*Q;;z2weOr+^a;74~so0A!b z1Rz*hhh(QZMg(yZ`~$Dy{kbLzT!pq0B7PAWD+GNid({9Hv-jL7a-@skgL;wS&V!HQ z5p-Y5w@vB=NQ664`PYTCbQlAH*q8yZcgWU*S6S6tD1d=oLBnF($>$uoW`b`rD5BCS z^`%4^Y7oGe@Q%Zdu*K9pDl4Q$k~cd78x$$IHF!M3UPv2-d}|B}sju%AfzT*E379yc z5LMM-j|YUPMKj@Wa1hJ^Y<4(YNjJxyfB@Rzj!g?FrWnx4Fec#`AnP4whxUevqbO={ zMz-;JCUY1?Gozv#k=`hP=#@2ipzvWC(cm%+FsT_KY9<8i0NffMF#^=8$faLEv-sn( zN&KMd`W_l+z!zjPY8~-Dsh2%SO%6WW3Y!T*0cfWa>L?Z%uoI$Z%?D~N;QxcbNG`zG z0jjWV;cLS?EzNn7C~}mgVDUOO?i)6fPlZtdvwFQ4#-afsB1BED2^gbIU+tvkdOE`0J>70D9uj3><$L_AJ-Kwf6 z#UDixe?0Tu03hnIyp}Di#6M6J6Bp*Kwnp`43xc?yCRjnA!nugGKx6~iEs}!B z?sKz%;+VO?;C0V}j8l|6bKQUdDMGYzBZveUU{>G|&*cK_9r$SD! zLWl*8{y=!1f84Bf5|p&QCThQ)m_UrSyB?nz4-WwY_&12iKo-S%$j&5tfO$gX`!HY7 zmTw@$l`bBin!TRqF(p_6L~H?tCwhloL@6-7#e3v2G7!nyBNsh$T(*B;{{9{oY)-7K z)T{;9OsGXJf!8Bu)7l{(8zzRuVZR?mH7MdDjykUIfL&OW@*>Dwc3*-iD~G)P;veRS z5kK~bT7`1K%nRj7O1ZNC;Z0Dvls$M$sLV#wK3?e16b1{*hl{(~sx~m4ZF^PN%r|%8 zpIGOm%&ByefAY}%u#Ry(mz9CvqVT+UPVos0MuA5%8fE9aUJ4R_4wwoLgkI1^mCY!B zXp66KyIvRO%@Aoz^`+=h=2PO=A9@~OFZ@)LsqxCIBLDR|y_2W zHY{2KvqFPcYp-sdh%4J!Cq&qDrIxS|5o&(f^-J^Q&`VLJp8r4C*Ib&Tqp>Oe!7MWu z<;fxOq12Id!xQLu9=N2!Q4JObzlw-$nJJhT6v*$jLlO&EgV8TnJ(NgcY+O~Q|3omG zg+AeL3s-sCL{obfd)#e0<-Iu~|oKa0JdySwpg`ltHs(M&*fzSvk zFT;a&74^w_Kt*Gj;Mg`AZ5(R1Mjyw$7E0x+H1Kx66i>$Nj&5jeVT!l665Qq(VM3Nu zECNg#1$uOR7SY+BB_J7Vbr*`>s_u=fsc~*gSQUQCb>FhWVfh#0kbxA#vUtci0#}Jf z6cud+n@RRqjep&S@_B`cY?=gz8!w>^JbARS83S9*x%cbx^uxOZ7kqf{es^;8Nq1IV zfB5YEyX-V~-}}m{40-(`OkC)nIAcqzUWE9NrQ79;s%P)s`hHBGeD|#V{LU9ozfMof z`sUe#XLmk0Z5}_lca**Vta=OXyJwGnxbyUf z;$eL^@4o)(?5M2Y|8W{lo;~@lze|m-2R}Znzp1KsKP$dmHH=b9bU>S4YqLulE*_nx zr_EPiKdru)zxmcj~X7+{~YR^(;F&o<~UK?nlLYlzDpe?v1(cW+6|U^KD+aH)39tf*qX58 zq?fc?*LUtb8SkB;_I~+MdF#vL{LYsjzjyTMJimYPX*It4PWr*qdwq5G#qme?HtjYv z$p3PqXQB2KZnrezjSQ7qE^lZ%IZ|NGB=@!bdSeevVNPrrEju>0MSsi>l<7eAhUaeU*);o%40-M{_G{fGA+-neu2!;c?7 z{rH=s;ohg?%^Pq2EfUf1JUad1+4b?k@yQ=OJ&BdqiVm?lg9qQb-Q2|C(XiQ4@?!3e zhVuIL`W6u!)pcZUjq8K-le6w*>>iCb9vzSOCTq|W)AJkHh;G~_vGQg%9>Lg?s=irY zzusf%IVz8C5$kev<9cb9#aRFF&grAG?%P!(GHl|ZATWuO0jA<+c0^nS5@6Q$N7qq& z5Q%!2{7sEzT`qCe1Sv0=>-v_$fVYz5)-41mtOmExmya-Q5psf`>^iY9YU0-Me>-i+Ni60q1fS#(f>HAD@O&ig+x1Gkcc>T@_PQuNXqr=zRLc#2!AzQfJTA5VVQP1u`lsGCErhMo6tu2&4WRs~b_x|E@f;uHl`mh`> z-ZHBn_67JM=`kd#C~gi}3ezygd@3^$$it$J*fpss%IxY1bR+-*6EguK#BB8# z`>}H15+jI;d0?<6CmTT&sg?O)>?Um-9~IVMit~)fs{jYPw*On^qG)YdS1G^X>W?Bl z^)LR7PdX$~r(^(K{`pW+7dx(n8z|~nRtTIG z&VZNpNuCJolNbU_*>rW+-QmEEkOc|;SNRcd@R`Z<03=^+d*-YUE4GH(s=EF2;pCK$ z4+PKch5tlPz=wi#95X=XRrrpJ0?mmsRv{MylO-h~utZ=7$97JnC;`3rXdo~QngsG; zEQf6rIM&8`8reAs(aGRuHPMQA;a(%B2wVa~0)8-pnb{!=bYj8**&CZS#|42eknmZY ztOP!GI_LzIhq^RGIpeCJ)D5OQoV(5q1Pzre!sTzOmCln8k6Y*&U@a82wLY{8?qM>q zU5DT2>|K#kiQ-T`5UxzzlkAdkD4T`)VW)xj$XH`NMyf}8wsCM>rX-wO(7JjO6^T!( z-#9L_>C#dRsw4CT?NwAFi?>q?H#L)xS!-<)4?!B3~nDjyBA0Ka9t({pX7&=*0*^NID0DXw-GRAkmNVaY6|*j_=8z!|u-o& z^@o8@>qviw*;tQgHH2Zl+-{{-iE2@o!8$yP&D2n*lqQsGWwSHXi+A3#z(tQ zKI^0qHp4G^1%|Y(CmgS=E(+Yizp3(_#>4Z<;o{9O3zR&b2pnExhLI@ymnW8f0;j@9 ze-2|bGkF%pn_?&*@e88a#_nPq2Q$k9=O5tKM@M+8kk$8l_>jK55?W zs|s?Rs^n&=dj9-%$5kjY~w1&uXbf=m3O&u=4X0kF|(70E2Bw z%fTQplkP28(k_COygNQVUnm8(8nd^FdAq9iYuAF;LY2T_t4!Pwl@THeq6QGUpkT(> z9!5T7weutQD#ZaNAcX9+k|pJiM6QnSU(6yHwVtq9Gh>1SjN}5*&?AQ9^?oY!V7ZoA zzLF8!rY59g1Qqpv&y8D*)}o9+f8ataRGgJDbzy;K0gqSo>w*O;*aXu(4>OTbn7P5t zJVdNGP-mUCpe}DdEvrlie+jj*8LNL{2MHR3Otc*@m8BVycFhVhTMkt@AQF-uG5>;ltJJvx@fY%0l+X}AU zH!T|L7gJX6aaJ0If+iF`w5FQpeHI3*J*n#(RZJLU5C+Q3K`|~AH+BADcfeoB5Xd{W zVcRo|gV_V-b$=4NkQ0a0v6IcCm%BM0al8Q~PyS%N~=I&ntzzBb052Z|p?k&0b znO72a-2lVRD?JNNh)DeII?jYNFal+f=MXoEuT(@9K6yo+sKg{_#Wy&E0{4mIQc+sZ z)2B?4&i$gjC1!DF=5n)>IfIpD1lFJThs|LK4)DvoW!^%U6D1 zEZUz9g|G@EHQ0)!$SKW2S|q82h)3uil-4D=R2DDdwXq2IN|mMDE>R%_ZW9x3pg{J< zzpD&C?QP_mN`96uFKJO5jx4u1IkY$_rk+r5OkHfNEF&T8@E`1(oMJ*?bxQ;^>CB`a zBA-xKh?-P{#;TOfG+_b1GL4aeb7>pTTmSVvUwPJ2rTr}tC#uuK97Aq!QocYqh2!j@+<3^i` z!DE$X_B!~VY;CJJy9@XqGOG4mNkc@|!Q&|28KPGCPJXVjJipFd7oJ7-En7TqGd=gJX5E(XNq>QYCZHRmZqy-*kt5d+NF;`V`h=no;rxjcF0a@`?mCjHY z9}|MzSz97q2QE9ObS`o<$Q3kG!mE?1P%ti*sTh5M>q&DnnGOetwjBKf0hFs}-%R)6 zS+1GsSMq4ktU@*y?SqOEmg5XUo7O(eYQJ{Hz$v7RO~_13#{LPqH+Yl7WAIj(azTQS zzOiIpe3Zpy$+Q_>^kcJe&5-d-a=vI|k&G!#L7E*y*Zg$igd}k!TUe`PN03Eu1YSxoFMxC6cl2Ny=v!t5P56G_-;YiFF*wUSaQhOS0g#%h!9qfrg^36w>A| zb1JA-7&RS@fRcBZHx3^G1|eK#+H#l+dCfY=*=PbbK~LyCC}6@TBp?}#<+*R*gc6h} zVt}ecb`|m<$@`d;$>GBOuM#i#H({(SIy(?%RnoMR4wD?P2+_GFmE)ywCv-Nl`Z~GM-+X3yGA>0lGakG%dB-tFH%4mL@`#~4G z+z$j7H5$_t(GX$5Q(~dU<@ijbJps>PJw76~QUcUTVR8|4m;1@IFACE7$yVfBjMmXz zbjB~Glbd!#W-VjY8(yBTAg>2U24+kBFFK`ui7c(Q7R(Em2(Z>ELHN@<%5U)cOa4f6 zC;tc`P8?NRB)uso)x-mmobL%qxJk>5kS)#=r z2}rV*dNWN-h>gtiN;Di+hUJTPYkGvJC4B(YwI2hW4-BGi6duHjIr|HjQRpnGM;6ds7$9q1Jg!ENVY8#oDiS=-OMF@!zKX2`)Q2oct%O}G zF&Cnv-VR4k44h$zMS(yP+_l6qvFkAf^dxChm^dWaK^Jl6(pK2DY&M)WAg>~0Ciqs{ zBZf4+C$o&fOn9Ps$mnH43<}3qZNqREGo$wxdfR;;oMne8ISttibxy!Q&#{Fdl*Kmj zf)CN?qg5An39}3Xf{eVh%-TwdmhiGkfERHq+t=1jTfZ>>rBAUp<1uCtGH?mEk2oec zsC|b_z!9+%!I8a#v;Yo6LjS;bZr4njr?`&gg(0@Y?*Y!Lh{g($kZCK#7QOzi7TBIC zN&yeW$!Yb|Opa$25#hq9E;;%F9b)bV$grglK8QV1cZp=wfwEulA9io$iV+1wa$j3> zx)D|#_#f#nWDHZtqTm1Mi(mcfmv5dB@}|(PpP$@U=+{V4*{x#>NIO2c|2H0RtnJBU zVWiW^z2g%-`pf0iV*9d+cY8G5JN@?f>}wqe`}nMT@Ng9~28!x*Sc^eh&ch`@%>vGb z?&R6&$wjY?)3;~cMK`A5^z8Ixy6D#Y&FQ1@q8s-(arXG^!gszt<;Yu3U*36*iqxdE z&o;>8$7f$Zc=CAniG~th2(AL&H>ckpce~TT_(+6VVh{K{Z*-3*f)aIl?!D<2x!*+X z=>%gz3*1mk-=;k9@iZJiJf2Q;a4wJ{X&9wgMY57)$wK}qKReD2JuQ(|f zLovpQo)X_|P8=XatwjQ{FfFlvk_=7S0W}9Oc)%Wz1If>z1_$y#1?;e~&)D0hw@bx& zU(2c4c0s*6s!fqlE(2ggDdmwcIzsx0kt}ACJrGRs>1E`pP>_r&thkY}f4~3 zwl&x~6QKtD)FZ7CkbupWI0b4GFgj{zK_eWa1Q(=A7zLuJy+F||j5`BWzK9oTsX>5v zOYpT4tuLtMMXiTi$EXek3=h&FZcfJ?cHKmYv0238CUz~OY+T8y-gw%YoYdJF zSoS2%px~=edry`M(mn8HW1s{b=>>2Iiu3`!mhv^!cp&MyBtfJg4;o`9Hc{&FX?#im zulfIiT3)(0$oq5hl(dZr2?@cD|p&LCmMVypUv=$k? z4GB_25vx6aOY*Qd9Dxy0Og85OHAd7MHX|vPxL8!C0fExgr_eL!9>wpL@~6X^*yB9w z3P$D65}CODlq{0rsSt-jKP@Rip>1oc!;!iE-iLa9a1@F5J|2nk2)c;!KKoat%9#io zOsWX9s1++?t?B)^?lMfO#3Es}!$HL-Otn@@zz(P48pax~7w8WMNhb?KSW@dsb4BLZ6nl~O;T9P`OcAA{NM~z z?Tl`8o|rO1U-Umf4$cNC$lm|!!7s#sgYZiNsU0XR_o!T*Uc_2!A(!b2zpzcob}#Id zohrIb!8bWVSfLg=DsTF;Acqd(^{YAy)LJ0Qg=)v%8>)<7Ey&T~EG|D|5ksqA<&;VC zkI`1tZcP~^Yr%XhRJF6K96;<>i6A0`7{;n+DLU*|IVFG2vcj9A zt6nZ%nkc>r%75t^g^Sk^_M=BE+?M$LtKuDs49r^*dGxJ*&NXNiM$G_e&sgOP@n8kU|n3NfeOSGs{EBQ;U!tgXpsHgpi4c(zr+Jd?9w)}$ff8%D3@5=_n zQ-q6qZx%9*?a8DVqV5#VhR?zx>Eb(@1@E9_f~D?lTi#&AL}Gy)E=8dfGIOHwL(NnY zH82fR=8?EbcnNj`{C_+F2S!V$U2$?1(POuQ=D9=23u2R=Mp@N?4I{V(x5sQ|UO+fB zl*RNYhPbrJ+ej%Y(?U#nsbH7QJ#<5!rg%PW>ojHJC6<<~U+oZjo^fc0q1-+@2xc+D zEQm3s+F`;S?H4TP_C51$gi+aZ>Pfv&L`Jhh<=y+o_<^-0Zycnf3mEfXfo16Trx`ytqIhw&%-d56=0Hwk< zQV`R~2rt&%zg3wW^gC(EgQx&%g|LJpPF9N^<%CEOAEbt=X@CR&y+meT$_iQUNI zD?heM_aiOnX&ptT6_5AnpM3!pMi{ISLE|V8(J*a)^{(w6o8LAs)u+Yr2cgRyg8>EV zv*&D=v1>ZA!tR<2j<#{D_|x(ivp(WBdNSNM*m6yHVb5yQwf_N^)($wEQ^8utRClgN zN-L_$q_~i^>&5WAEmlWjv$=k@TiQPGDUK1paRa=R46<&^4CJ=ba8{tLLYyoE|5F!} zsR$ERCxq^>GJRsI5_U%LY$g*E5= zYtbXp6&SMhtf+NJ~$R|F8&ps8+5II#t<-oLC4l~c$c?8 z8BJRu#p*B?TLsvBXdgca>A`-L!v)lG__pkc%)w?Lda^YMYe^;j02w*+tX*R8G-sn2 zgczd@C6|K-!9GJREit_dI9jS)-+PlnO;}exEuFVK(vTR`WKut92(KC97$W>sb zuwS47C&x7@`Bwam->j*Ao5X8R`@U`C^GI#Gib=axhO6VNUbOKFwHXcQTluK9GRsPs zQ_5PW+_3&~FJLVz2AnuBjo*WQvqO&#Q*7dzA8sIigDO;#rB~-OzW;FIg{}xQ#FMbk zZ@%eNYdtM-Q`NNB@1?i{{T{ic{>gf!A8M)+lchD3KD&esgJNOzO!9@U%?GN;6!=vL z-4j2KaxjM?K!*yx;t3oAF8@)JhhojDU#F|e4wYm|5*|B`k_&ZJ0>;)=lNAl$d6r78 zlt3mi$F34WZqFjqcbsJwt}1RpFx%8JTKe633$RQn&R1`Xid)_b0D?57y6wre`9Z-92% z^R;h=?q<4O``mqV;2t?_e}P;7$oG?0#)ma}n)L!o3fdlJR7b$BdElTfz1)Sm#ihL;^0g3EDhqy=M z5g12|a`102^b8j+h^_iF!vW#*JF|mn``W*sRtY0f{#5x>r$Ut1u3oePh$`NN4!>B$RJ`98NG}(hV6!vmlu*%ywqdDr@DI{otFG?Rj!El;5jiSL|*UlLmk>*F|aK! zeG+0%_G%%kJyKxpgAgf5{{lqjiU!#^bRlmyLl!H9|Dq&3YAsx&F1HFZI(hkbjvbLh3B^$aZfxiTydRuU?gSPX5rb?`gv?^y#8}Lp(s4i&S^_a& zRNe;(5{~5|tR8zlT7_H{;&&J9I&PA(Wm8!oH3$OWL*}0u9f+uA9O8g*1{zzp<3?R4 z4DnFc;l$ZQDC$q{1LK&8{Kj3(cqY@1T=RmS&g~4mmr+D6?1-cj8F_}2DGf378PQtM z*53J=^e|dq>0!zwMkhxD;|zCp{fyrng{o5?Of!=vMke} zV~(QotZ_rFpyNRYJVnq9d;nYq8uFy*XX_;P*oJB?*|X+UzZ^OS7wld3Pf(gLM+xJx zyc-HOFX?+_rwsp}L7hLoi@5K^X`osn`Z0TekU^l(&jdEw7#KgHF(o*|@2u zPX}PJ00$$nZ%`hWcnS--UE#c0EdU2>i+JoAVC~j_MJ@BRKM7Z7gSGDqifYk#C3r1F zF1G8d6^fMj5{U%$l*CaGaVu4Y*?{~Q6s%v8za%s0vt)+##(~Ic*Qw&_tpY6`^Y=Kp z3#P*+X8nf4q^!jnm~HM*3eXuufFQUe51p$L_PiN6!Y6Bu=xcvRvtXo53z1*&v<*}S zNrw^S6vS}dkDeF8+znzzZXhWRf3V3s6N=ks9i$9!nPc%3MW;DIqNaTK+{97iC!b$D zr(3%rnDso1T#<{6u9L~a_>WIVABVQF`*AWU8xUW4}?3^#*5ZN zhh=FjzTP+(1Y5zt*tSL#J!RB~V@#aEXK9gm&6x?KZzsTvFuTW+}mOAiQCTD_< zz*Tu0(O0#CY+`X%(5bGFxvTz&xVJ*8D17qkt>8dhs>7P|9oBC+j%(Fy>;~`W!kTiu zurQ9aqt26!rN`ImEqYXlt-IYJ63FnJjVI6?{=OgzDG?#1z6Fd?M|1_&%@~a5IK4Br zg#&5hHbI6>8JSNeW{efMFWq>otHcrVO_3cJY{DCECSDX)iK7W^l~!q1S}j4IkqCD+ z51bn^pb67_;}WF^ViE}jH6MM%bmIry7r#>L-j@DGC=lBsZOYmv>(8MFd$6&{8DMvi zA)JDA#;vZ%UL%}Zo+ttr5?yUp?;Pb9I>W=;)xREVg7!3=|Ntqtr zM_Au(za1G|E7os3YuGIY5Cr0KPhC$zf*7_R7f|vyqdD z=FfJd3)PdaNunUI%SNLfWTLe>l;mUeq+nZH2$^9t1azWAdiWswL%7-!ftw75ZEOSD z2)5>np3VV*Xs0$`A+=z)Tvfkhd;3)q)5%8*h7XlDuxH^o9N!wk4naY`s)Gkm1Uzif zLL9gIRd}Z~VVeUvSpF8?Ryb)`Ukk0W(a6q?P1c_EQ{0w0n2|)d4?o=BQY_h)`ZB}B zp}+%YYLZ>naJnuk0ip0jle3mpN?|V2)j3p`T*Q`hcDos?4{@oYy@49-LHiIRjI5gn z%_8i2$zmmj-C&2uI}3?wpKJXL>BxnQKRa&$CiX^^yL^5m+K zaLAdgJ&{U;FDk5?qju5X5uQDgcatv-+~~;*;=vj%Jj={FdEa{QVk?eGcav6&eMlfo3OKa<5Z268C0p=oE}KTya6Nw`Bm=IUt+ zQj-97kR@HBkZ<48DI{L%{oQNYxPUV@Di=w%)&sBjdK`L=v=@Yk&8Qhw9CeN$eaWbI zd)*$1He5hsKeVQe5Ff&heO#LfQ1WdhK0}bpy2thW(EYUON!w7sjW(23FNzvb$!PkC zp(T!U4kxXuXo|9y+g=66D2(vm2m<8FMlS;9lr4+CV46`&Lah+itgYF^QD%S@|3WVU zgVLCAiHqM^FBYE16{^@O7z;vCd+yPYpyjrGy@Sy#3L(N)o^mUbuCo3zN`|L37HeH1 zAj*|vy0*HwmZ(KG0#jWWtpz>e$pD%r?4tpyhng@#?W}q)j6CAGt<-C`{KGy}+H>(W zS+6d38BTM^iZDi_)@rH@rme%;LMKQ#UTwTdfJOVkvI08BRG0y+MA#V?7}c&kNfz_9 zdQZXL7kG;zv`7?Q_reHqO95poC8`y?V4ZbBv#qn^k)B$w^RBOFgRK#BEufW36${=H zLcbnGgJ&O;w&2Gp%?25CWc3rUn7}gKYZ1=jS=;v%GRi;UDXkd3gJJ&7d}t`^_U(C4 z!Cbb2q52}cpaUv|qG=ofDugQGd{mA%XO;7tfD^-WO1(N)3%Dsi7Z_?Y5#=GQZ>>4f zj8lM~xVmQw;`4IF1Vp)-YV^0@D0Ce9+kaOEhC$g@n^LoNpo3$dzAJ>#;0~rUm&NMm z-d>*e>xBQAxx~Mv1Tg{?s&X5BSg0{ooQy&!!5d&GoR9y&{R75~F~(;q90XfU=`qOD zGlzd`M^n#)CTopD&-5ie4`%r&H*>*0xtZfdfzKGVUezI?+U<=mqKCo-j0=lToI?-I zLGu)(`K>ymP&cs%%OS4{qye9)wV>laIbYj&rh?uVJYBfTh3(68FN&kK0S66=|HwZa zom46H4EP?5WylT)&g^Q4+CWQ5s)+0$hrdNqkAF&M1>@%va!!1T=s&-QQFi+&4huT} z6zAlIr!Xec7Eyznr~Ir9uBwKXw;G(5LS*#g`K!y2-s0i_Psp|^Ahr9ZyFfM3bl!{nJytoFb)oD*fm0vd81ydv5 zTlq6#@fB*&AjQGGY?osf_kB0v9Qp_^hy6gzODI1UI(b}*?GgK|f*7Ppxk93fYN6t); zVYO)&$zL(G(69U|e4h9KglxAUSz}jb@aIXDQG%A9rvhwPl~M)eycKfhOy&r!TtxHq zN<|*XhrZ}fyStA{x(ILz&}Q$mxjXD57Vl3Nc5E;_%`shp?80?nzRw%_;GVn?`aZL< z_3QIr63)Fc^aZyB1)VYHE0tI3t;~ILTFm&`@Lfgk zC6)X<_19b*uo1#m7-=vXLKWIek%Tq&sF8}(9{^L^lUAIDU>_AESDQ+f0+O5y2PEZX zFI3l8TEG@2@kA%DJwOcD!|_32l=h0!Q;2N;IQwp5UiS}Z}Kn}(0JLVE%QP4QX^2TS<;7u;(CtU z2)Z@z0@d7gLI4N!%qhVO6!_72Ug}55;=C-O95z6WHH$NmuR~3OHmL75S&4*cMv+Ub zw<2;rE9hE;%EIw1O6o);VaTF?slo7)TB3lLHsYKteV8AAar9wx&OBHpIWPq8X>lHM>^!Uw8u?fBK$8g2k5{}qBQ6j=WLv5`5@{XDM zK$U-r&x^s6%4bGFoFRy@A~2RVFi?_vxF2h8s@-#P+rhqNdes_Y5>(=J*f#jiK~$`e ze~;J2RC!($iugq#H82dgnkT~)qbrZ6V`bPKBtB#OvaJ?7{y2f7MYRc zs_0Q>z;j`?d`K3m!6)H6Hq%guUpP+y3~?dnei&(JOIr-_W&{>r?T0nc z@#?~Xa7v3q&jkgY^u810;d(869e1Jcu$M5VLe0i0<(V)6(uK4Aw-*^AQBjSr zITvAVH~fSihy~+!$NE!HMzjk!jmN;lrIu!;D@}~b_>$ejjDRA{6E;euNW&VB8Ge1z zhzfFV+>RS*#AOf|U}mQ;#N>4aN?7@l8a0M{)HerRED>Fj%LXZkh}(l%p2Mou1<5m! zMaxP6u<#-aDEluJh_NLJuv}toi##KlJ2E!p8MA4>Mp)DD9#h)P; zDzivIDGLFjGx5Xs{A2 zsoqk-MI6Yp!Sf=i?tt}2jn!s}l!30SC`>?a=aN{1AIxZp7_oy3)x0Hi}(Rp#p zz}Kl4j$foeHX%hYpCl<7iw6fsKMnJMG0+|}-bkayEH*f3`ld8bn1Q9(dN_&jGzd=! zT?hlneMrzqmTbhM2OEptfDHr3YI*RYuU=TOFEUANBw8uaiGO3AOq604;0of6_iXRU zO0*LU#GDwC!_4$s6Rb@LGPb$cZkZ8<9+e)qb%V1->xtM{!*`4w9oYm>m#cU z@ryPfKv*kWqEsFLMu*V~aTc5_MhuF=uH;1#ly#V6u&QbmSUu@G%E({@^21o1GF~-R zSr*^cU7cZyt#5Nz)#F26rVwkVMYf*UqF^bA2}`gdfT?jk_=bi{IdOwNss4Zh`-wDv z>Am)(t0U5L^&W2skMN7xdf6)YDV^{R!xmpNEBHX`_E$c@rL322Ztebi#Gf5ni^*3CMWAPj%73R>_|SvpS_^q@?G7rSuE&#}WkJ2_{4J-+ zX0ASzVWqWR?*Z2HI_Hf*jBVPM$Pd&En_ita1S2_gVekd(SKbiO#(F$hP}bv-3UZ-G z!;6{VtB46l&YM_IAg(!US6JoBt)n&IVQSapP}_av-=|kvGxY?+Riq1HFRg$ZS6>@b z=;03psj&|U@3cz# z-Hw+4M>&uNTajj<5o-r>s9DbouW+}B#}%4UA|RhbwViefH|zwk-YB3J}gz^lY> zH*hjKSjmOpn=p<&VVf5c4`q>M#sh_o&b5R;hNZrS;3X>?*IRHAMhlA#?syo5zYM}k zfe3a%!U7@&IT>fb9mGBhv)&%3$(aG49A0aCx=ef;HCTO)5aH;+!~0h89D8n&RJ3k3 zZ$Zdm7P!1-Ydvw@-RzmJ*g#kIteuKDn@TqqRkw-xn94d-(M+9Dwzg+s z5R!#a9?qBf*|leheq3t{ni5i%=`C=}uxrCvwSHg&HH>!SVL3!(6%R}FoE5#6zV#}q zY3#qU*bxqjeU5dabn@m_ov7U!FriLVz)Kem6%MZ~;h2_)9AQ$*-ts#Z3iIpqLp_jIOT5%8sFJnFeXKN0>J{#FSKc*E zCom{Ui`}1Vu=`*rFnt`RD;+|5BUVp~DVJ69HP*^9cq)A2fbp6w%B=5|3ZR-qX4><{ z4ybEB6L~K}ut+s+#r(5^n`&7hYBXrZyo=p)mE%CQEQ|QleT$1G`WTqWZqAtpqJC)x z)+3gP7gzuZ-dm3v?8Y2nmJgu6vHNTeR!-L1Rkf@w_}CM&E#cWh zvxyl4L8~$CCTKPGBvOfNG$x-|yKXC%Epo9**LL6m);8I=z#7Go1}lYd$UXxrO43T3 z(tB>dtgSS%trCzvBxlA<#^%^^V+hmP3NEA*j7!0E$grf?%DC@J+n>8KSR`*=>Mw&z z7Oz;eN`t!_uh^fzGFav_hpL}n2Ft>3^we|8U|r&S+xRHWYA$UuTK=#!5_T`HRjZne&*LIB;RKebV-w$?;{|mS-QVak9 diff --git a/src/Nethermind/Chains/ethernity-mainnet.json.zst b/src/Nethermind/Chains/ethernity-mainnet.json.zst index 278fc5701e55d43799a05b75429d7f9351c7d4b4..3cbad235b874c0b690b22370fa464f0e6bb8977b 100644 GIT binary patch delta 8780 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 KHFhuxT>TH|3;JpR diff --git a/src/Nethermind/Chains/ethernity-sepolia.json.zst b/src/Nethermind/Chains/ethernity-sepolia.json.zst index 0a365fae323be2b13e81d7ef666e6e26b59c65d2..1ce360a87a87fc35912331a547b2b7a432cd898b 100644 GIT binary patch delta 12482 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 delta 10920 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 YQz+-Ym&lC@Z22%SHg1i7*yV5kA60&BNB{r; diff --git a/src/Nethermind/Chains/fraxtal-mainnet.json.zst b/src/Nethermind/Chains/fraxtal-mainnet.json.zst index 869705de89cb1ea0f3970b6e2034d4a21f60f24a..debfe6488fec103a535938b3b47d162d54ef2f67 100644 GIT binary patch delta 8405 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 delta 6980 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 BMoRzy diff --git a/src/Nethermind/Chains/funki-mainnet.json.zst b/src/Nethermind/Chains/funki-mainnet.json.zst index 3f45d457365b68268ac7bdc86425df8a3a0ff9ed..9c0e92a9b5b5f5c33675498c64680bf3e59c2dd8 100644 GIT binary patch delta 16217 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 delta 14685 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 diff --git a/src/Nethermind/Chains/hashkeychain-mainnet.json.zst b/src/Nethermind/Chains/hashkeychain-mainnet.json.zst index 28dabe61bf0513a4fd977af1557285813af1f2b2..ffdccc305982f99b8131ea689fbd79debfd07657 100644 GIT binary patch delta 16237 zcmXxLV~{RP&ow%>ZQHhO+qUg%Y;`d@4N!3>I;ND;^VESW7UD;k5PyGyLPzLgRv>$qyEMT=1x#<2?u-L(xsI$=m z%VBW+l~I=Bgcl;=d&@(O1+$%r#9r+8RcHDDEglIP=Xz9Ab(bm)DQ`uTo~Y?W@c_vN zo4A@D%gSwRW-XIzF$WjL9z42HX}dehuVnq2daR7EXOws0k^Y3jRj36i2_xiUhLNI z{4yD7`Q$wubB6yRN=BmC_1XN*Jh`+JSr#3Mxc0+ebg)i!H_v%xkt1Vb5~~}yiy5I; zxGS)_bqW`#`QvCJahUe_Tmk8e$jCZ?$tVwnfH~QtowCIWy(f6mc;gfcF&X9J%CYbP z>$^M>{ZUtrr?^&WO%v3B*&Op5ygZ(M4Ol$^zDNi(CNXR3+&KsQl3k&Qgm`CFu3ZDB z@JpyVXAEz;wB7}^FHE{KMZb9}Ek~>+IfDL?MFjn)TL}#)1;yatr&Y zXPk`PU3F+pg&4vhE@zJ)K5zhxhdeACrZCFOt0G(D_0m|`imKJ7wX)LxKczWn9f%SL zmrTjZD={z#Ozm!t35S5n2pX}4R?Z7cJUA2@wpeJbQ<1(}0MRi#HC)ZH_>n}I2^=1- zSd8=EKYhgOr^PjDwp3ElJ7vHYC*x6;z=JAIuvC|Kfe}HRc`iupTw(mDx#ZO z9M)}L|10n|wvsiWT96VJnusB%0G+5HNxeG5Z}-5IY!&QBK|i)>f3TJ}@c) z6KfGLF+2?MS0Eb|930>)FenHX)R_vIOU}v*s|r8_1WVA74qL4gzj0j-xOR<{QX6^Z18kU2zWb{kA1V#OrG zFfoULq!i1L8D-4c7z90#4>W2g9NmDEqsSRrR@2DAt89JD*$=oJaP=MYY;@B!xsoF* z*j6y4!XX{*=j7Wn4hD;W4v7gN%|nAir{Hr66EOycTkB-a148`IIdJ}J1o>U0Q9mdW z9g!Z18WpxyUoa$*mQuKEN7XT$3IZ;ZkW(HK9X_2I;87RXn6C~L28W6Y!5j!miwYy0 zQv({dxtZ)i8BirnIB&k2B;Is&!{;WTPypj_Zj4)0X>oxGjtqW#6OrEFGd$mlTOeWc zXPg;fTx6opr08>E4hwa~hDV?T)pY$FZJ+B)Aj4+vU*{0;pCYZRhGldx3hk|gQogc9blh^(qi%tae4=SMZZ%rRz)6t z)37kW^@#%ioJuVs-CX>^b;L413vnfNc3ePGhG1Lk#=~q*z%7UftsW6aue2W|6aryn zfO;^}CrqUu3>pmvG!2pgeT4muk1G`d2ELH%zx-u5vF!)p<4|PoDPcu}A>bMJ_lt&r zY97!XhiC}IZ?QDDmZf+8}3Kw+>@X%b0D_}HW}iG<6UfWhHW+4z*S0!2f> z5Q@3D*zUl9oUHbs3WH68pwOH`As_;bA|T@H?u5XPfsm<;K;-%mV0n%7toS)mb>skDGR*;bK0peLR2J_Q$vs_^Eo@fJ%MR!^aaWX7R zk#z;ha_MU0nJq=aYKb^inm$)WxGZjaiJ(b}F5$v+|!37ivtleZ402X70D2Q*g_=swM_q;fKegi+Mo!zFt3{CQ@K_PF&_q)5sUI}N3L8qLhuK?C1tlIT7MlFe#x$97&X_s1tFm6fDea;I`u%);KhBP8fSCB3o8E~Wmj}ctPk|GYr^*gh!O%oh=wcYu zX^wZy0m!t4#f_DuvtTPB8smUsPNTA$3pg_6s)|@zNR4QUW)R~+cn4Ze|AYjqo3dTg z7~ROcw5=`#;IZa?G+TFh__n4bz*9}qS`+VJZN07;SBJ2kgQrQLgSDx{gp-FBP`H^K zkC7bDLBll?dN;azV5>fU{Z-l`Cb^tY5)#c`7BJMWrcQ%zmd;jNTvn{bO=dMoL4*1& zRmQ0xm|pgvNvU7D48R-a&;Ru@e`K^sJXzbp2zymOZ4A#?cC%+)&xcGg{zmshEljL*J}A{S8Myyv?n?q_Zjd$JBq= zRmIjFP+Lz+W32Iyzp; zBUL^+F}5wn97zR9vC8WLu`oH8WB% zgGKh?%%(8pmzsA|C+uqgjR!7$ZtRFmYbZu)iA!owLlW75^KWFO$F0Z;O#`BqTTY2# zJ3-6u{{3n=LE7u$@0waPpw{fzeBw~uzt$ou09+@7O4Ym|ywe<(BPTHik)gOUwUH;n?bOvrJeCJgQraR|&`g`Nyp+GJ!c(<^ixU?^Q6k27lP-2gkd!xblPvx& zGj%knH(o{MnbYX%y~x8}Qrft^y{opkaZeoLQPm+@B}wTPp%GUBh;2!wx$72tT>zr1%P_S^Tmqml3_$yUbbl}d~^uop-P=#;ap|+Kg0aO>t#wziD}=%Br{{FK}xKKM@ZAQru|InbUj{U1A1?fCy`n&|%&*8eG_|5FD4 z$N$#<$N#MU$NyN=WOcHeC3e+hwR4TN$3r(qct68s%4e{62t;EEW5TmF+^qoDR_CoB z9=$Xl-=ypIC=8p1M*{mjO8FIH=Q3pbV+y+B-L=X9tyCPf?D^i6L_+cQ07pq#RqKfG zQ=UwXTenWzHc7f(>o}Z3`c>6JQwlw)(=yUz9s~OEgytsR3uysS8;D7w63q?8^YrXR zM8p*rY*RQEj}xoQNz<+bQ!BtTry@^0siCI9i<12P^q8_h?fv_yxH^R7etxW*X7I#@ zpk`4aQMN7eWV@rK17{Vh*OvMm^fg$|803iAU_(=q`avt=&wNN zwpC6D`)yHHs_olNmrkI|dkyZYr#Vo$)>~461qoLajrB8c{y5T*;&$mpHUv;-H9U^z9?PwAm`OT+ zXVl-#rx?}jVM6Yig?B_aPcH5J*Od&Q%x5{pG^ zk&)V21kMPJg6G9Y>>@RLR(6cRo)#5Q86AbSsOu^@NS@4iiK56d6jZ>LFi79IX{%Op z=2>*MbhC)9+W_e5U*+rh&{;nFYiV+TY`tRj+c+`a zGjEXNm=rG-hG8zEYGs9?VrAc35Kne5BPl1t(~zCK!@NCD7Q=&rn2^M%7Jas`$!kY1 zZGvJiUzM9&>Qc&Btr47nM4-2wu_|H8Hd^aod*QD77>BVG5|dLr*MzIz%{`z}`sYpL zw(Y4{MjC)x;95q2Z!2GVA0Da2Q{mNZ!X}kgWTAY+BMs*92?xL+iqz#25 zkuP=@C7bcbR8Nh>`pUy<3wu4_gu*T-$FD52$^@9PBT$%Uhb7Z%M#4x>oI1)>L@Pv} zm9cwkJvJ1a!eb`kn9v^_mRut9V$lT z%WhPTA}jmYn}GU_a$!?(OCHwS^(8s7?kuuPAdoY_vsqDpgi{&mb|X<~FCl$3NwelI_e^ z4$bY=3{49sWYI{4Fr}n|*JlQ9nKZCJYNhybDpClKJ&hgKo?cH#W|2tPgd%2iHD~J> zGkSjv$n1Gdl4ZJ|WOAk$DwmQxVga=7G(`quMD9hjMIJ|qN$O-#jq(G93#746N1V&G z*351V6obbcNo!GW%1ec4qh$#WDnC%W=3+~>f@OO6jJ7e<8D)d$R2Ogf6VeS!XVy`t z4wqx)UB&93%pKF!%_=SNBGzekId=4M=ogMJ4C=Gwbgl92g3SZw>?B(W+l}xF%U7m%Vh2@Glp7doN23!172dbLtWAoaQ z>|6}Y)p?zx{EMG#$LOa#nQ--6V+soXL4sHDtycpWoe_NX`XE7^#xYw zc?D;0F6kFf9Jzno4->l00|42HbIN7q$(f544`+7zh*XUU1+n{A$yQZ}D5dE<6n(*h zMVDElWcHUxxw226R26=PnFP#6_dHJI```BTqDqKQs@8cm2lnd1dsQ zdN&ed3JlJT>sH)c*GB|GhGsc+eqs8Jqc+>}ItTuLwO6fLAtm~X8-Pw+#zujcIl{ux z*%J0#A%#X;2DUppeyfZvfclK*TE-()#a5G!5j!;AV=%VEFF5{@fbO$-E{qZbLftlLP7Tp|YcwnuagWCl(v*xMp-M*v*LaY6d;fPkX%5O4Ju z5iI=GwKJ^|oNKG12Qi%Tf`u-XG!is%NgO&=+8kxw3i*!Xa-!pWWfTT7blbBM2c^j| zB+*vg?}~s(03BUw!lJb9S)*2yWn#inSy8P$`2a>ZTv8+u5D<|)kSb~*5D^+2RA|9| znHGhzAQ}(^0N}zDEWnf)L1N5=2Umy{9z+CTj06D;1oYopM6{%a3)Qo}E0Fo?&tzD9 zi5%)GCRdL1%%vWSh*EDz%@lT%J{S>KdA6fSy_y!43bW_EG354Hw6t^QB%xzi$5P%N`@1&Xs{(Fh8yK+*Mn4&f` z=1yoUuDoGi$3pXQYAc#g;~YR$ic;<4ht*pIuLqYe6@bv_3Gv2L3P_kEgc4rWq3jns z`h2rq8bHMP0^#~YEz{k**`=vJQTgwm;!ITkR0~~FhexL{`|iKXOF;52r*+BhQ7?n74x6u=?)y( zqbh9Zmd>~mv!LDIZ0S(9(XH3;9n|VQl;$UtsTUvu!z~sV&cxGWT;0N4jZ=$++jmvJ>#}xYuW8vpzCLtD7Pzo zFkEJm?8)kNY^c=V!awU9FjxaJbNq8W)hq8L$psn8q2>JnP zO4#-+GUhhA-CMV1Whx%2&?#<8Xzq^bUoc&xAb|*m5N&NynX{4asj|!F2Ugrtdfr%; zHXN}q2`)f=K~`RdUZm;T#F^~rO4G)_0xf-WN5q`$eZZg3ikDViH+i(W2H`o8+UR6O zL-n1D?Oz?N^)4|UV_-Kv)2sCW$aoPLlHyxj%|*cJ3;N+&m7@+IB)+*|& zYOnTa5HW%Foz)zTB1vUKY&~}yeJD7xcP(u!a4;fXP^#nXN6<>M=G_%NY%E?tqo3NO1VgcONP$ayl*U>p{-w8La9lH`@a)WFDKWC;eGN$(Ag#6KKbTO|kvCzz8yOLDX44FTodQkG04UjF#&sGjCm``|=v1HGLD7x`=vo7Cq4L3IojYc~gUAl~QmoYDDaEy=W?N8ZFjR|69;O=UYE({qM`U@qJ z11wXscC_JD23r_TP)bSxkJu`i&VI_U)H}xjdxqikT>3Z3jA2U8Iq@CZx9ZRzjPM20-57se!De) zLyT9vrYCdYu{D~V;y^^1Zk>#wv4nL-&YSlgO90=a$*TDRsQr|r!-cRs$dkk^cgVba zrih`0L;D0&<%Rv*oZqH0AaDOBj`+np!;u)tjm{GWeg<3*V;5BPkLOcpD(u*MmukB4c5WhHD`@F@ zx53Jr-mN5++Tf3sb~`+x90ZH+35`_MRWJzC{~fI=G^N+zUflFT@@M}g4F+4SWDM3LawE|FcSww-S^A2u_A?!%-&9%qS-9V|}wPm({OC z2nZXdZ+z~3Q1coJVVXKV$uh2gRmP3woGUDs0pn#!V#u~qpP9H4|MC$8?4_n1iu<`p z|CU-1#tSfFK6+KM8z0-^+o{Cw&CTDKjE3L8^eCJ>|!OT&}M$G z-x_Es?4fKeL*J4|_W5S4({t$sVT_Wz4)q5RYkQyuV@%nM%JsxEZtLO1ei@P*$3mGi z-)ku}*(E?cUNab!y|}!8RhEa}>Nxw(=hW*0#GDMA!?py*!UJId$$=y!ev}g54qtF> zN(9j(^-gP|6j_sluYOzJ@=!0fXwU53tN%z0x5NJPLk4-h<6eY%y{M!@U zq_mKm<}uMMnrDRSFfO$BC?izS@E&kJs+0Ar0+enb41u`fkq@lW0)%%UcJ<-C#QTFP zDLGwHwnZqc9*>t2(7a5kY(9T%O=&EVg?~u%fn?zt1ysaI58VS#doDY{ZpMuPe|MV* z1*`mz1GHQNoAT((Yz?FH|DgJU_B~cqWC6rs1I8{JJ@N!oc0smaY^%k7E zvyw##W%+HFRRF#kue^he(^KnTX_05fbeJZCx?2h9zHo0xFfPLRSK-k@XuJ4GbM;Ys zL95I-1<@@vdh`Zo{^H?hrkWJ;G+=14OlakmSz0jd-=<7$r~H-{ip793z}KcY`QNi< zJB!!e=Z9<1C+NG=)Xp>57|GN-QuYHA2Pws*@I{`C(2G(UDkEk9%I6l4)a zNA!19t4DV+2e@j<;%8dQG1lgx^{xa}1*mk$UDITw%I(A~>DJ3b>dl zY8Aqqbk4pqncxLEzybi4W#j5NwEiF2s}=pi%RmE)$EFIk-VgbVV>IzijnGatq}$p4 z*STW@V{pw{A-XqA1kzaX@AwI9`QaLz|I!IVM--kuK>bpY)DohH-YK4J8}j!aIIn}5 z>e?XlgXCji8gW=atMzME(~~IFu3RH}{s71W5WiaYR5Pv!h{put?0;q}*FE9HDLb*R z4>Oj5lgWkFCKRUV|7QJawLA;}*y7`sl~U*LKph(rU?6AgCzsH_nvjWRZ-f}Dw$*Ih zALSzsLg3qEz<0G38_MeXSYV>qi(fk*{Fb^kGvrnRqg`JGJ^gK>-I`Mq(!#i{DRPR6h};e-y22w~KL(=f!gp*XK)yC!I~ zVZu!wN5SD}u^AGeF<>9nj)tEt!3Y^r7`0)v&St#RMy z+eJB4Si(>hJ8}|V@XNr@4c}Kyh?D&Jo&O4(>gJ9^Se{k=O0bH(k<_+L!sCLoz4-&b zJI(*k9KW3~Y@1RP;mAaCHDz!N4)q#*u@^B6P(X*Cjk?(Cv%Guy=?G#ImunAkW|f!+ zYSPs5+FWLXi^&C-M{74A!AObS(xK*)>HICp_@gd%0gErpms6(1FBEet&#h99uqQ(5RqCuS zzix7C{u45)@4F!4`sBF*iG*LMc9dZjm6)Rz_P_*@;V%O_y=K%C3|c*G{dtapvT{#Y z8l@U`MI6^aIlYnfvWi#gQd%>hC>fYE^DD{P-QRD5JDkO@kh=J_JfWPrTE}Y*zB@`z z8*31XOE^OYG=9DnMMqv~;!vQM*|H9V2JE9l$Np2fc0&&4a@yOjc8ziZUji<+YwI^m zvqv!JKJz+d-kGp53kvB~_}79zRvTHV>CM@_6N&4gpU`+28O?HFFrl^Ont30L4=OP3evP65| z0kKE?=j5AygtW_e1c(eUn~N+fI!5hfmK4J6Mok=dUBf!s?hd^={bSiw z$TwD2x&}Av%YtD2>WYJEL0#@z42j87_1j_VWsmPXYMF#Q1fE5+Umn z(VK)`OUrVhQj|0fAJB5Ekra4TmYG#eP_4KwBIC?^e!B^O+L-L_H-t7q#^!xHmlxq z%*bcRj7zKIHd4dtxrqHiG zN`WI%H4!O4=IR|7=k5UC%Kz)}Xk-T4;k^X7j}GPyP@g3$cCHxS(p6+rKbJ~=g?7oX z&+ONe^AtT|@P~)Q1k^2{!j%0HkE)(s`#V)IUFXT?{#*i}8eI@e9O#Wgus5Am+S!iP z36k?jS84h1=L4iV{dlHT$E;&;Y+?u8JC@WMSGu_2}d zn7R}I!SJH_xpc;=uzP-itp=Nn7Zwp@QXB{tMij&Zip>6QMMoN`&op2EmgmPuJT+5g zEe!Iu8VUfx>4P`G^k4e{C;)aVTn_A>B$90NgCq^w@c!jhLeD#Fau`kpDS5~Dn+fGw z$tI{Rfu(C%TC4;&3HcZYSI>vLg0bi)MQe|LycA}UB*zC4FC8I&XsHiW50OXp)4UX4 z!3b;isdbxIw~NJ${ zp|c!LcS*&WZ1vM2XTx3zV?XYgAVu_MvV|Ew^gbv6%8`*108PpcLq(EDsD=%vlgU;M5!_w z6W!18*8U>NJ|)Pyh%}KGk5kbsbzq=u$jAuD6Ex+FmD|f7Bk(=SOuG&(t5HyL-xARW z5UXqbEoG;(LrC3_6!wRYvC1AsXP>=$cH%h^w`QcYI1m z9Nzzug%0_9XTD)G8WG(+5STeVtBw7yytzvDB$N!CUxkc`4s=xs7!-$QGZSSp42KI( zVW>$D!SS9+miL8Q7|h0)Gw`kl=c5Q<%R5Sl&RYHb`}A4i=nP6$YSoq=-{;&`EEENM z!HH;6eLH0m7W`!u_dQs789r`g%`+BZ{d9y(TE;6eGYc#*bB3Eo#|Ni0hDhq~5WH#ZJUZ;F{UX*@h{3*=Oazr{ zQe>F_2~!3_Sd2}+ah-`x3`4nYJyZ4YG~IUK*^b@(V|K9pk?Bx+FJMMe#R&4&bNui* zQTC>$A=#OYY#o3?*fr))*HZx~3m?kkqQhF!T8>WZ$@2#kX|^q|!g(s=oG(zboF&0A ztbMk9*G^TACDsCat|ny+jP0(KXF1GI6K&Be)|v0GjC2#ICowX>%sD!wZHuE9fy3FA zFwD04v0wHQZbm$FvuYBO$FDNbEy3+rUAY$J_haWPqbjr~q1gEM^!Nk7xZ)e+U(uV4jtj0dr({J*h??uv0>m(NxZ8^k4_a9gIsMTV@`K73is3a^>~nin{BII9c6y)Q&M_VIgQ z#T+>Go3urax#=X@TEF&yc_CCzM#+y31NM-WK)qK)dwY1mSid!;N;1LVOG}cxT3S+4 zsOYOH>P|lX*q$urID6&wiFu^Vq03D>!{-Kc$_R&OysIC^$0MMik>2ixOOzhj`0|M~ zMQb#}NJ5I^Fl|`-Im|}Z!36u8pHBJ@C)Srt_m2Y z3^>#H%R73M{Te>G`&RLW7Ivux(-cE1){B!Hnn39VUwmqdvz3o9PJgxoe;YcQh(PVi_F7`P@IqC`~ z5W#ob-+aCAiY7o|FWkXmjFB>~YupJH${W^Ev=5y>VvPf> zLh#qMKAZ!;WSx1dS@PB{UemKFTG_ijiJNh|D@vjz`@`-CWWn2a_*W6;)b$xOx?-y* ztNNGRhykEpT0CEdNqXn75taG=Ek804f#WW7{T}g#2@&)i_$zn0zDM6)TAqS`l2uBy z)1031$W*JZi;E+UKF;@P&5@xG^g0j8jw0=t-smz|Wh0!6(6p z`|kSKIBh$(bz@NOuwMDvC%T;$^v72iC)gAj5j?9jAu+&`pXk7%jTKxU;q*Eh$qIbT z&lN7t6SHvUNCb;#9)Vk*rF1MONey^y;RKLwIZoQBw;$f%kEVz*W*I=u z8gXaJ7Job^k#25MwoS0__f;8z8GSktdh`WEVkjR`A4{Cw zY+A0ePT=%h{{5|M|L>3-@D~t;pDkcr8%RbqfvIb-51yqrcH|Ux)Df{YXhLdUz^{2j zbs^+O5-6q*FD=3~?VMVM$2I!cSDd?$|3xc-S71zRfhw_VFNMD8S(+x?hC~3UIP78G zloH*&3uf0kkO$kY#x5YC+3)^eV~R>GAX{#W3}e*0tv%`$+Kpa~-sc&_CmPU7vkt$_ zF`x^E?VvW@dy?n*;hNf;u)E-nov&e1L%ulC1$4}y9g-}jcO@l4oKq8np_M64bAVB@ zHaim1HA>Snm*z)&qEf;rpO`0O64`e_>=LJxnav1-?!`rbmAI|%GNXsgW*xkwLL`E2 zA{0DvR{ZnC?ve^;6auOACy>c>=ScyhE zdHDTinKP6jz+DGxXid$7P4|~<-Gxr>H7|+bC>~{muSbDh6DVJxdzP4Eiz1ECC-?0^ z+W}?)3d>X%VN)XHLweqo6L6lG>k4AklIE?`vj+?lR|JgJa7xc=(zz$qF`9xn8Mw($rvnw z=N6NVfTYASad*P1)BJb;z(U7p&Vebzj3ZeYIs73I1YBek1ZN2al7yQ4P4kWie)RK+AGt4~DjTnp^{k;S)@N<67Tm&EnZzdemmAM3k;oQOV zVb$f4IIbXmuXlNK!*u1CAtKQP`x=qg3PK+3Ii;G5?%?iw&6y@RV!}@a#28LgM!gA~ z1T?q2)QgJuL6?kIi!k~Lu?_KDw31*bvD)LeKe191Ji7+47s&|%#eJ|2mkVx?VfUOT zM5_WA`lFv)`2Y+|d;^XaezlII7c@wmu_PN%e~t!wcBbx71jr5mK6-YsAM4Y|M41SF zgHZ)=Ta!T7>{`Tin`8NYmfJs?Lft$ed>&6h^l@Q5T>VA;WO1blAC(k^GTL{(PL_EU zjMHf-Rr^0^Dgn7N?6asGPur+IpOK7B!HqBhuK~rD%O(o2CJ==CRX8^5u>vQ9q_{NG=QnhAMmiU+n|LfLr_4tDUiKlf~A*Rvr`|y6$ zV>j%jF!IR5iR_owYy7W~lu3lb#5}jmMx#DQToqtL)Esjn{CK2Ngd!xhuDh!YD>bBf zD(e~CmhQ_sQDRM@$=$%?XMziiNI3I?SG}f zr1xb6aeG+G;ng1{!%q2UOaFc0n2_$!6iqSx!OzJ-+D485QBvh6(55Jq(Zd_!!U_C3 z-;HFG*M(<@{=QgQ+H24=n2g6a1IWU|63p!-DK@_%R%{&ke)eP&Wr%rFc4nm#nTluQE zAphW<)W~lP7fdUoN?Jxk42x92&;U%Eyv2@ky^w+YB;3%0qTgbrFr@R+<{}fAl4<^i zhZVLl`e-cCNlU1@TOVvNo6HrBw{?{lpZr%DlhKoVP~Jr8y#({b0G4ZOQ$Wumpirc% zF9rt5DB4*fW8taynboart@#WFV3vQOw7bY?rSSvE!KZn~rr>bu$PM|~^Dk(38}5xBccm1xn^W|YFN^4`p#9fIN%~`O{d!(5?9(zZus=s z*csy@YUj|~9ST>jS6uTMrP(X{BI&G1P4J~c1u+?r0#Nh%PUgl+JFav1-kCq ze<5tHG#?vRt}S>F*s$xajfhrR{1;Q*@@4}EpB=jHdM;#@iR`9#+4BFzRIj7E93N3- z0=s#ixNK*?NdRbxrnf)M*RYLgzE4!Qv&SW9si3zT1=XleyY}v*v9Esl&u{hmf6$xL zu6@R6TI%o-wX5luS3)#e(f`YZO^d~U(3dq@E{QA6DmQ$_F6_*G5p|YV><@t}*OUJb zslB!?qE_60xgf4={13VnyKZw1WK{#b&8=}|E1=n)--!LcWJFZWV6@x=QIn~J_YZFV zjte3|!bmgYr*6nj1|1hYNG7nwgNmdB@F=ncIufF+IW?T_Ih4VKC{SO{?JeIIOlyPd?U(+IukHlxX?u(k=cy)FNdSiXQTt^rnW6X zan+jlHZ$<(^JY}np`D~4CZO3s(;IN2ap~ljPlrs^c3s}VDVm ztn9E%?EY3m3&WgqMXdULeg6ObC07XpGBYzLz<2Xo@#KWr9zdlH}H`h_5o#*kO~$3P{Ut5yQVv44ZH6^$5u#W zZ?N6^gM-+Xup(esAxK~dz(-<`0%kz3+Qs|sUYR!ck$X@lkxo4hTao1nh9WZO$L z4@><$4kWLHacf~gOQC{BjI)pAY6k+^jCeh9rl1Z=SGW8_#<*`tGH6EW0PrgxoGyG+ zBwyqGLT_2yV{HjESZ$D^;j@uLZ>J9k&9}KS410p`&9tE7qn;k4c5)X2wWA`t(edwQ z6W7-l*wK}Obj5=$gCEqSR_|?1$WPjxFUKc|i9v2evoBWIE=}}S4~}Tp@Ej=V{60G` zfi}pFBAt6%m@fF2E#!{P_22x3Q`X*=|C7`B!Wc8OnPL5*R!Ji3W2vhtvEl%x^agNx zljT;`{F)B#ULX5qT2L~*d5*&q(U3KX+ zE7H65k-GZaT_WDDaJhN5LNdhu4`EXQ*8m`w0BaQrE~PN(PoMx40)S}R8<7rX{O`AN zvQM#*IKdx5X@fHa*_{Pys>CB=dpJLp;p|3d+8y-B@O-Dd_aTv|O1uuNSsIVnS*hcc zYFn1a1D|W#8j7xvkj)1AGY(%>eQ4i)s9i$ouM;%KBd-kcMZjYT>JUTP3lp0b3&;%{ zSDo;zF*7EZ@5D?VJis2-j{sJywB5mJLfq`09EFKyTOAtU8&tjU#0{M>8BW=LTCy8k z@?Pg|V|}FtS$BY;LYzC$#LR&MEE?_OAJ znISOCvVCVb8g>p3P-X}jD(BHLFBBmmBq%UpD5U>BhfqKw14+h^K*EZULim)+fsJ$m2nED$A?Nyh!z5F-x*6r4|&v*X+e7VcalbPJ)CYhW^ zLtcGD094J4pWs@q#J? z0AB33)m98e;m}=KUK(uabD|Ii@_e*eaL>qrnrO38{~Gwkc9(}5i`uP_Q2xJN|1Wv^ zKjQLVqV-?0{9p3;e}v_~q$_;PyRISu0dcczwNupdFYvQs7EIhQ0IE-O4|NJjIO43n zoS3u%wg;@%3@}NCg<}cPm3%aI#x~bdIj=B!9Ul>?6N6=q3Zr+p|1rz%d$O$*@d1V@fjsj2 zVLFCmXsMQ0m8;P7+84`<8eaYiejAM1kmwZd@Q0J9AEqfh{yrJgvjkqQOrza#i~#ZA zK*THG)brQy8*lUE#7}SZQ(GGQsNv9BglXgv7E>h#3KJR&Ei2r!XA{yuIe5U?Leco}`5 zG&g<1PB%he=OEyh&;(w{Vc>{-bw)8zHXf|R`1rYeGnrF|V`>)46XD=ps7`$#M=|5W zXV#kEQkju`@21R9@Ki=ja7^Wos<1LsfRzy)OiX_ew+%52yo^db`xp^9jtvc8=P#7m60Ne^! zt4}T95RT6g)RT128@UYkERF|pd#~G} z7tbL!bOaB-V+fB4>ItEnmC!oPsgvoe%W%@XQU}|Zu!T{`lAd65o#fU*N9voS(x?Bk+_d#<&uKao!@(8P>uKjqGs&|+RLz{H zz&}FT+I{S(1kaT&K&RKJE^@ugR{0+^Ba+<*oJON4N0xD54CYDzAMsQT)twts7v81x1Kjh_S3u`*?47G`P!FJv#8zGMa}C}t(Os&?m zP%d=N$AW(tK0{vuGtP`^2)|>~X)CR)_o2w^=pjGswe+!BJob=rjRy`{{$)lXv|AK* z3W%Brwutt)3X3b`4Y(P%I?sVc=1dP~CljzzI*KitGPGlK#&0P$KTMQakv(OYU`W8y z!1Z_`W)Z``9KIVRpE9cq{L7IHwGi8Yi!ClCy}+3Ep{8{NvMN27hq`2Bj?C6yDa|ot z&fS+Os%$T+?5ED6?3Qe+tHo}g-{cu87%hK3Y*&pe%xC?M$-*f1T=bQJE&i4uNB>MK zB>^whBM9Z0no{Obh9c&PjI=P#8!zudpEyMtp3Hr9WT5}`MOt>NdZ3mVWsql3_Yq^m zK+&7y7WOd!+|^Dp8oJ=1hDQk`9ttLAXLGp}$)%~`Bu>#L{zdPYC>`W)ayMIZDD;tA zw0n*Gm7uFo2bU%rC5u3=DM{MHq@c9XoK)f5O?yjUsjZJg=hFCumXg9k^j!aOh7+Z1 zJ)0gK7tLsZ<}gvir|EfPDK1e&^5@s`kXj~fgM#}B(3S`1?2o)Ra?5Z(D{+axU`@)d z((6K}F2Hj?aNUg1u3kk*3Sx(&L$p<1qMf~*4@+`b^EEaFr^M`N7BCw~CW+fxHOF(ZY9Q~kO$ zsgflJNXr&0{YCbIz9l+En4)Z9M{gvJH8t+Lg7c*hArO-SEx6lKYelEN1UpJM=nbmR zF+iP~Z+l?c!u&%cT?ye}dpXKlP6hj(d1PztgYA;yK0}9kfn$N=XRfg*g`Jf@iB&O0 zdC3Q*^yryQ^+J-00ohM%FYXy5oUI7C`1XJ0Kr=hr+nHw29G0zuOlH_C-h^q^j#grX z#c9F}lBK`u3rti7ohgenNh#Q5m)w1pcjl)f-SJn6v)h!oqnRwSC=a(!vPdt>=v=Cj zRGUfX0IvCl8C0ZP83Z=-CNfy8oMISRY6-`(txNBlAdMB$+qsiCKNRTDBWEwItBrM<$AgC*gv006T{Phej>RR%@Fnu^m`-v|q48wG zTowfU!CmKv%Y>}H06T}oRBNHT_x98^;3bh)q;#=S!SBMuc9l=6&t0Lsv@%?4ooxJU zVwisbN54w7eh(K#t8jWjGnGrU^-!>dkjaE821Ou=VwPj^DK$MVToII$O%tX7;cS5- z#=X+wtP+d*&8u4~6^nb3)|qA?r=~hWCoU;}*IT^w(QI{><(EH`?eFGxM`Jz<^uJ#Z zKIJRncAfre70~T<+0;T2IX=^d%iA9>!swRLMoqAaDM0o7YqGXoEw;N79z8;f5~&he zReoBcu28lFq?QH>AieM&t6NPOs;0Nr%PsMC|3zib_h6MNF7AFE`?wze<`ie8^<2N3 z#K1jD|9+bgZ!W=8rN3K{EiWPV<^x45S8K(% z1$4$_Lq8y3=4a#GL;q4a53(D;Vw<7d^muK#`U*?TA%zo1!v;#K^UIwWTWI?ruu2yH zBJPD3_Fw#Y*lP0fr|HmkdSX>rMcLAzZ?taABG)Eh!4>UK#n7uDr@$- zr0PZ#ZG^2Z!BydT0B=5oy~;dcHq9tGab315JB8s*UW9>Hk~-w?&I6{nJTzL_>BQ#~ zoQtS#UxUrkM&3y;TRt~n<9}qCvf6+aIzljyGyS`|$|PxTbPTU^d2wN0e!jrRM#ksU z+<(N`1{N*JiONCR$)A!8$RwVn5^c~|7V0%*WNa+eY5A=5= zkAxQHPwFGa*ccXwY%}R8a+%*2(fA@IN287NQCsMHslrQgdarx*aU06n4R_rug?BkB zsWPee6(1H4VyaQ1h?MFRtVcoEH|tnVM9PqMfzg6XL?Y3d_=3Y(@|=!!nOr5X@YK{H zL(V@a$B~_bLWgUb0DgGwa0z(I>nXWnvZCtboh>`4qpWH!S!M~V1^I^op84(xsS2|c zYNN3_l0lAI))@Li1riANqqe?E(^bgqJ|?fV7bCcJuhi_!ic)y<+7B(Yw8L@|_0*s% zT(w$atvV(ZGzTipz>vCIDs2=7w&Sx18}h_l{S`qK_-@?Da!2GsN*5BgH1CFO31{=`kDOtJT`?Yp1A3WrGg~7d3_um*FC!_RUa%;Pd28b z2Ejfi1fR|X7)j#BZhw3rpyCeM*HMZd3t@(saCokm2t%YZ8;Wp3VQsb}t#P9(N^8nH zQQ5;z$wzGz&Zj9B?zOEG&L=)`FAge*A7?RcZ5`*bE9J6e)oG((bP(}NvdC0?Tsrk& zHaO5Xh!IK-<-nH`|L09SM~?Bs@t%UV-)$irTS8D2sL?ZdTc;*myvA`k4W9rz^~5E6 zRh}<3r1?y9NllVsT;?yUC}9sz`T<> zbVyADnwlPdTQp5BZ61o7ADZ0FNH~Vn0Sk_Y;F9cuH-}uKX>tC=gOVbN{_47}V#E!J z2$v9{+XA51>f%94$umxM2_^BOy(JArXt}&U^c#}SQU{t$S2qtoEo^8=lkaHU$b=v0 zI;aqGDO7E0PKKxyvdz7V`h9rx(SxxmaXyKGVGn`4R1<&cO3oUBhJ`#r{il!h{1QIR zS;x5&qI$J@TdDy=Qq1YXIn{aQ%7q{&co>mRJlC^Vdqagb;&}Eng)D(knLH-xRCjd< z=3bU@{r1%C9sTe_=*T0SEU7{~(v*xSdAQRmn_7-d&8tJP$QdI^>@X3Fj05SpS+FNS z?hIF&@iHwvJ2^)aolh`nGIlXul7K<`;hmGQQ&#dK$KlL1sz|9$ z+F4e`trl*ui^v@Fc;Y!ydkmO=zWgX&yoZYf$FN zAavYF#UYXdc>`uBd?W}61V{jPFiZyF;`s_8b+G(}91h!{pb$ zu2hm35#~E~<@oo0!%f_9<_k-{>MhF0H0;5aCj_S$MsRW_v}`AMRiWIXPJJfegYENs zq}xX&``%CR&>T*v58l~8@fQJmV_QnfkHIr98`gp=ndIBL`|q`Cj|KBK6-y^RrSR_o zx4Rl0+Kd$8dRXjHL1Y;uVNWqi_Z<({ExzqDFG;ntqq^Ten8D;XIOD%}IXTGPLZx0$ zWyKYGqd%y3K)$e4R-*^{v#T-y4||T(+tzM=bcBiLraEa@XMx`DCL<^CoI4-iih&Wo zt3~Zd-u1}9O{H>MbONC3`|c6{7vuyJC8=uR#)jmdLXJSyieHI>>EV`N-Qxe5AtRqY z(n$03lEUXBXMK~Q_TUfm?j28pmy0gBsF?fjv2{y}m72*^^R)_IfnpF~G3!*ZKum)G z`ZSNx)VmR+jnoq={lN#>N?u36Ni04LMNfpo4MT_+1hi=wB!{{Vwd5;S8*47!+tK1$ zfGcHgLCsUrOdP+2!PC;2?}3qFiak0bNKm}fw=*>F+mpsv!=Qa3CKogIq3dZL$y?Y$ z^CMDi(Rww#FWXc*LvI_nNg4aCI^e}WX6DXO(_ZQ|qfztM5gm`TD}HgSm!?6CkTmP7 z3U(uJi<%3xZQ@t}ms7Ap+%4-RC#|$YR^hy*w>8yEF=P8ceUYv+23e|GoWB(fzP&m{ zpBa1Z6ftL+{73_YpmMPGQoZga-1{v$;z$M{$_PTK_b)x` zM7%g7Llo9$g0f(-CURjj=yg_(!~6bRm592-&~35%_HW+T=q!H7HCz_B|J21GkhJ_dw?r*c6EVs-qKVK?SYbvX@Ix2!bFxC}5_O0z1_$Sz7a}0{3 zHF`S`p7jj08SGzlVN@sxyv^@t_!>Gn!_OgSRq(9pPl5s(pixET2DkGosa%`kDw+TM zz&mzM{rzh{<0?a+MQ|spc=zGr!)=dl4mI4`&wo{wugYm$4aFuKbj+38-n!wN->#ok zhI*UiHAO{~GWm+IyOUI%d`Glmo!lxet?I&2^Ul9Su}B1t=l|LF4MA#Wsqib}o7`+x zO}Rl7SEvLS<|u3L`M1C}po(GQmwLs9DLJ>}xU3)lYS)!@A#G3=apJ5Ayc7A-R%*^s z&Cw#X`4tJ}7znz?xW(6K^_XADt9{-nU`eN6B%a>;`_6f3QXSv2+dA*x(9$LqhuZ18 zUzct(&hXIqWDzP1o8*S6=pXvh)!)yR^k&`lk=+Aw^~acb3lvO;i5U$=7Qtcy_*WJ& ze;8~VxLm_IgQ1iyb*tWVuKNzfTSAltmCp49|90+?g^zrVxEMuk#r(VjgU_8Q9$3i9 z(so3Fq}xZyRc}MLx7IW3lP9w%ve=Wua{mrUf?}|@o33YM54#7=fTQ~`7Z-qeM1b*l zDQpMC4zjZohAom$NRXMHt5fkmn?x(ZK(~)5$Luscqf2+K+rY-Rc5Xyf$z}jacOQdk z^%4FF;Y8m!ga*cm44#I1#N?7~Y#ax#wS1mtE>WhVr~u5uPRy2C>xs_csNtJKki_X7dbzGNV1kW-XWAs}AC*oWo<-7FF~NV2n*=u*Af z|F&wYE;gHm_~~_l=Y7uFWzht>xJ>KFe@`XFju`i}>wg{Yo?bf{Fn%GcVYUeV&C4~s zsdaiNA6j_MoJeILC&W;;o&8}0UbX-IUVZXLj&|X zys9~9xBKv`05VOUx0wOE=Vu9~xCqf60cDyog9X%`o2G`ff$`3~*FG9@$fO=H^*roI zIRv~842Su#m`2%@*kDjUn&vtJ&7dwziw2!owC%JBbaeOzYse}gJ6rw{zvupQL~ns< zi`msj3oga%zcAiqQSek_fiP5*A^_q@->CQ(RB%UQwzkR-z(O3Ogh?9Q5fGBYxFch3tz12M{YA0;eX6-P?vQPR@V6TUGL}llXRvu^{K7zC)MumP!*CO_mclLp zrRQ8Lo*5AmQs1aOwc;nplV{rjT1um>Y^9u;KCiI6q$m%%to=MJ*@goePY)ni+E6&- zWO6F^h7OayXn0Gy6oHJ>ImCrh;~p+06#PRIX=<$@PL%h6F+xmD^N&b@H`lGXYzyNIle5Y`NWvHM#G;QiyC}1EbDU#i22nH3 z_g)@`*b>$a<#P=vv&Ss=D*!+bi<3q-sb!hGlU(E?D8fkl^??92ppwM@I)U24te|Ek zqaF-h&i(o69{4-Aj&o&R8>c4e*3{6t*9MM+_U1{;Dr31qWI@}_);8fjE|UkjyrUhH z1o#lGf!Xa+N|PRSIdVDXIWPNA(AqmdK@A$>bHqEIV4uacH!oadcyj2W#91)6a}Qm*R2!NqD=F4 zk~9+LU(qKsR4tYoHTUfWQdAtQr~YmC>ml@>Vl$%?no^vuU>2OguyfUPPdzQ{2`-A!E1iRJ1c+?C0#S+qQsegT3 zMII`hV?1%WyrnoHx{xb+=i$c*B3U}*cT8ug3D4fh-@fwL>wT_tTxZ2l5)d)L*2j!h z;c+BAuQVX}$!ixEW<{6~CT2Jx5iin~(I7}N&4h%Uv#s%#_yvF_K4Ch&{|TTPQhvdb z^57zwmH-X^tsh~%*4r4h4ZC6J8B~-q>8)Iv(102r^(T7DNb(}i+zLLO)@kL4wv9AQl_H86)VV5NoYK(~=e2Fj1_EW?1}rAKuSEG>f}^bO=PktsTu;G9KyAEvH%|X49F>wH5b6mbB_x@f^=WWE;3;hunDTO9BP9Ia@q*R6Vu>rOJ6ovK5MD>odr6} zM=NlWBCc#TK~QrE34(LezB*K~Bl96KTSM#nnPcoqD)^b>PnRDU1L@;uVnO0g=)_%( zYRRe21(pp;Hd5EbhO+SgSKLmoi3j*+MqjfRmjwa}C2E+|O! zsgdm`T!l<1_u~oA)eBE}gcyBmsrUoQ^kqw(G;^D(^yJLwudw%^#QVO^eNo%siO=2- znr%D8KvQSuz@%hWD>QTUDGR1O8lwvzNbH%yNu8Lnaxcf%n+@-G^T>fWyPP81N_D4F!sul)0m4LCz5e94C4Kaj>B z44da3@oEV{pV(OGv6HInHaK<)TW+nJ6`P||1>)c=>^Qxj8;6H_9x~6ddlEI zTN}c=QMBdCJcO8-xBN$0nb)y5mq^(0odBJR39*WR2B6q5$ zlYf9S8Jfk#iJk$Mk;T!^vZKT~*O}&4m4GVkoO7*FvLtRjWft6WFE}5b3(_^TUaNtz zS=H6&w85hNyF$iADoN7a+D0_CFHhaf0duP<##m&vLS6QT?t%Ue6#2&REE!MAY-^0* zIaoRY?#NoWQCm^BL#rR%=9MlN778OoHZcIbd}HL+44GCdbVPRkz|CQA0v6enr;eLH z_F6TM{j8RhK$jtPU~8~z8JGWt)F^Y-#Z{!LRUIC!pyC8#Vd10TJFav;J+%MS2~T^( zNq}_zXthNt2bL{%Oy(D1Ak5U?r^Vp|EkiMKJvqBG3JeoX3--=X=d??&)G*tzv^d~L zO(mOt;eujG*bW#tdOKAYx9>9}^~@HO5pB>AB{R)p^&74|Z6KfukHCg)+ zCA0v94Cy7Ry|2qzpKRniZLp-B@a7Fj6HwcPro9cY2(be2b4gK#rS7<}2pMNkK? zsY-c7ZZXDYFjr8o!5`o_oSb)$b^AaYjMPbeL!OQk2Dk3w3oDwof1E2U1`-EO&a{Wr z9pjG`Vl{N!?3X5}5C@^CKM#vhmfr6cUOKKfox8Jz0(7JHYh7l&_{uhXkA5eSume0UKhn3`_f5&)7C|1Mk&Hw&g|?i6Q|j7qjJwtvbAa8{9XYe{QT|` z($C}HduMo36J%w*I3d||G29@X>U#km;< z48=7Q=7I|Z= z@ge_QmT3OF=`p^}O*wp=gw+fY)~_Dc&;RQ`6~|Vo=&(Q_p{9}Hrwj_zPBXO^V2NUONAz$!HS?|2g3 zA_cwQoJ_Vh`tBzw-KHn#UQ{Sdt3e~H-Dl1GWvARWRy?!u5t={~yws3yu;O#Y8*}hW z?ud5MY)H?Gm5RblbF$^=3k0Px;(drkx1XD@@wNluxO+sM zAfy8ktSJn_+Y;zvb2I?22btE0R5OKK=d7GYiq!NRJLM8zs)bX`gkbnxdT176gx)oQ zp~9D4vtqH)rH7d>JJo``M?FLzWX+WXC5l#(JUdgceP(sp{^AqGC)-!kk#4ZX7Jtu< zDPvjpMKN5h$}ZsH9F+!IzcMs9A*S}U+TGe{4O1|dV+N3sCa|%MN7vHrG>@TOgr5ce zd_BYL>#W*lse1XuAY4Itf1#7|j>-0n{U+-YHW z+`HS6wz;DUygF?hHh*?9Y(5ypd%8yzlrL=@^m*V*)0{QM+n@qF6fe8nUXw1o! z%BTrf2x-AU?e-Wnk@7pXS$c%%g{=y){4VV0X^YZG`p#{B@ua;?vK3^#+}U(InOXPy z-6GeFtL8P#Nl#E^aT5^>DB7RlZ1FmV3>ab*H3Ce9vpvpZo9K*Q&RlcbI*~U4U;YjF z;6okuy8RB;hfSv2=Y3YYx3YL0JzWx^nEUj&fL303*JyThqSBMS@jZ3(2?ffh*8Ogk00pY3r}XaA1S=A zIiIDT&IBfcBAfi6cTlr_M}pdFy#~HMZ9s8E%4opTJ!~`xo#}|iVZ>q4u}%j76aTi* zFzRnY$Dw_+i*4HDve7v)zxy>W=vZ=Dd^6ebOF{)CoKH7*t^pdSe!~;z4KX$e7oCLrPby_!OkHH_8+Mq;GNFN*!)vrB)p(B&A5eQ-q; zOCOdzJ#Kmi{n+TAOTRf)XQ_plQ+h?mI0(H5S2Snx+)88)-0;ApoPbZ{+&n%>z6f-m z#-D0`>8u>8_xqr0oggCU_dG?Vmb9g=VRP$Xn`HI#lw=4ce3WCX{>T6 za-Asy>~+x&Ab&x9**zTpjx0Nb2N?WHe{-2j-}Pv(KOjAkZtE;^q!(v91)G;?2!HIy)6+yh2Bgn4*`1NH{4$`$*% zh~fr+6UiixZV=}AySjaG8aYMvS@CGG+%^(g3w`{AsjYdNin~7o9}It0eVpH$SX<64 zzKs8izwW4TLFdPSU(m`TpsVsEA0XG*F1Q{4CxWLc9=u+EJaJv80RLUFWK&dPZCE}%d3J!dB!-U+00YAwH{29=gC_EZ`W8HfpL z)Sgun!=%3uFFX7-M8=sdBhg_6q13SEjnF{Pl19(c{NXVBb&lV(7N{bKrki`-MkpiZ zs$rh&aDCz}MKcMEUVNqltBNU60?ss)-EH_SV+M#r0b5n^+=R%BydZ@f!o!xiGS(OVS+v`fc ztqk2`)ef)mL_W>zaSEdJp7!c~@6$-|poP$Q&<)hjh&d;GkuLJs&u%VvLHT@F*<{v< zCsbDZWP%}CZ4?>G;Y3kGaKj;?0;rWPSpa$5{>4qmvSLb-w8$7CvqH|Ok`G3W zZ+d$?bFtMim7s1<(JS(nXfh!ZlddFln?4TArqKgO+;QNqOPf(2vBqF`OGLef-H+05cFnDC7cz;omzc9t>d{qhPR<@)&VPfkkc5`q z_-BAU+!;IH^+F%^1BRr@&u^-mnV!}R5mZIgpH)Dupf{Qvv$8+>fZJ=5&rqPulvnWN z>_OY@?wGH5SE|F&uB&zjn?5C3b@irH9C$WZ9LbFTqwlNL!L+PgG`7l_BSLPBbH-!u zCB3blfILQd_ltL|Euy_vo4d+_*>f_hgfZ!FsLDlG-b!A=>EHp86};&uvGe6<`N{HH zlm4J^E;xlo18#?}*ma&u@sidES!QYo~+C`%kJD|f7 zmF+N2d=nI3)`I4*wb0wr8p%MlG<#9dP4E4l?3vW^EmS_2wG{8Yj9S(rf_XBWnHtTj zEw;geK{1$I?zH|ktQ{qj?j~D@4qG6gORlPGsh0EwJtI&d8$y;PCQd1w`D8P#2v(lQ z_J>nLOi4(RwGp8k;(8W8bzq@y2axYJ{R_~kQBIebsFbUIY6-qA-+Q~8xcZIVEg7qh zAm^NrH?|f{Znt7Bvn;dq%q8=0m7%%uL$W}ak%?eZa@4z9tliMj9#_Q(HS-ZWNiFEN zr=T!Qm7arMlN5f)L~pHcuNxgQx2{bwB+zUeeHC^#bdHOQU_O6&-=Lgz0CLMepGf+& zU))F%ID`$;_n)ZA563uK$i``;e3&{5gV1GcbEcp5#u2TOY zHdp?06a0fg6Uj@g^29Dut>JRpA<^93Q{|VDS+?vv&{w=>>G0jLUDFC1b{xGv-dAe#Y}zzVX?_Uq5GB zW}ws&VjuaFY$64aL zyP7D=CRdwX8|5!nPnHfk@{kA}C3@5jFN#-QxelZ4uV*ukRLWh3_3GwagPX{5$y5FSY7D2 zQb_{6C{Bf``>zV5ja~Og6|x>S7{;W{wf*JMetO@FDY;K>LA1h~=tzP&zw7SQ& zAfi_pY~6J(bNOTY%z*oJZU14;5(rx2RgKW*%1~E=-JWk`iYE}K8{ltj&FK~&2 zi1S70-!+(rnC5x&a(xmohdeeNcJ-C-lg4-xG>Xjc=C9d6YG0!u!V2S7zD$;vy>+C3RXlNHqQ~5 z(JC#nOyTv-%Rn6tqCsU2x&U4-+@dM6frvg>4)ig>we?5{(@v`s|4Vwe$S2Yn-XeTTmfm-UXt#{SESAWPs;h3iC& z^PD(p*89Lz_h!ft2#|=S;;4_ouWOHdWE3lS*X*1A1z}CUsI#v$uuu>pxi;R6c1cs& zFhfit&j#EWO$H3Tl;c4LifA7T+>LQdw5#~$4L?3%!1*z@+`2%MC5TA;Lh+;#4GT!~}sV1ligg+j(Dk%cn$ z;sK|A^w8i*OHJBX8zaI|5U#qT#sD$FO;i#S8jt}SGVW-3f?SHJDg>RVWs)o&7*L*O zoU;Og=-$DhuMoiUQ7w)ha45V#)1{_iuu?kw?o`g(8Q5T_BHmP*;Kbmoio{XIN%-r` zV*ho49_Gwcoo2w1K8hZkrSjq|Jmr+bm$=4 zpTK`MkON{r{fvu-iE@aat?4*&!tuBc2{LfhJIuD2lE)a80w`NeqS!!963&-kOo=@- z%zMV5xCjg`d=6fs_Z;zJUk}OvNx+{7#*kl*o4f?FdnocOpZbbfvRG(7$o479zK1+EYt9jBeQ%A5KgqrB|f&oz0ThcY&+ zf&94D3+5aIu&Gk7zS%06WmmpkB-@91@(*3=J28G~t8#iayz%mn8M+D*9A2Eoj9giD z$oXOd|jl6_U-Ey57P#h@P1F(qsXvMpmKUCG=VTv zZJhG3!}sYW`pC9=zv~%h03n=u^n9VgEqQ~o_E^6Dqq0rOp+3wq?VeTn1*+P?(Hx6U z2Ibg>e@jn=5Q(emG{FqI#E-6HMlu(Yae~1w`=6=HmcSj3-b#Pfz*T#Q2d--Pj>pD$ zW6O^+n-?!k;Zv~EW0?Z%hm?;(DFq+dA2q+t`$fe3rIXaJ>tlaV zEcU2Y{LKm3#QqZq#Bd37qo8zWgqGOESLj;l?)0Gurj$*cg|3lkwLF^6Ag3Yy#BwLH zYp2yc)yK8^f8(W zgLWV`Y*|t(39(+355h{}(u5+7S)DhM@jxE=*8_y$f)y8lYv(1_Q>N++LT;^I{p!JL8!AA%tw)SfglsHeK8n^3M@o3+XtVu7r;k!&|*7JZ#~i!q@- z#+$GSyK#hLY_h*{rIE!xV3uVid&k?S+MJrSrSAaNFkPdHFIcG*wTFLcxRM>ynXoAu zcL(V#xL?(G5O>uxNz^y_A~upOek?9vPT+)dPbW>?M~G)y7%I4ixI^93U@FIn^G;yA zV^1g3St9@Dp3ui+4RO>3=IM`eM75`pzqB)m*Z&ceYfoqq*q(;k<%{B(XM1~3lf*P; zsB!}n1i{|0zf)JM;{L@9$L_-rl<5lyR$*T_qTT}FNoC>*ZE%g-FdZ7$rDDu{fNwLO#3%S^GBp=68k$0h@RadB7(gcyG6A+6wZ_UX2#>=?*G?+y)=GT(rZ3E8lz@zTGJ3!wKnuM=ve|pEosjPr}9{fcgK>8em z&Jl?XL>!8-13-~tf-H!F@I#v+M|7e-UmVw;xtSRhkPHtL9eAR z9V~*u!J6PeSfNq4I7C@lS^oFQOMe-CU6YVkCL}pQn`ApvJ#vm_vEO4^coVgq%zSRG zM~H?>G?KdLRga*|*LRR562Ki*jO=3M7c+vft++jfg~fiF6^4?b3JfW?<3;UbLJkQg zf`Aq&Ac7W6Kqi7Ph2VrAKq12uwwH<_?1W+NfhwcIdJ`Jl!SQPIIvGUJMkvWifD^=C zBEu`_^JOD?5PmLvXA(w_C;=s@7x!Q@N}UW<^#>bO!VxN!pF_q20bcwPJX&a99#B%E z=$65)15%WBu|lE5GGP*`%Rp(ke~Mv4N9Z%ukp5x~yU=K5=BGi>I)%X#T?|NV=~H`1 zkK^Ry=pG8qT2MYNQE(wbh_Et*#Ur%}sF3@?GKhDP6(Cn?pKjO@xVKemJV}#H9FnG` zk1lXI2|bsRlVfHJW#N-BU=Sr3_kB&4hFvnhVWW3O0YF! zfl7=8hm`xiqP*ll8pRDrC}j5$*g=52Sa5M0o0yuJTd*>-u(GjpaQ-)E`+q@9lz8lD z{bS?aobYY)$6BLb!Sizi(&~=Dl;B0Cz(WI)^5>=K>{DU7-`bAAgB_vIdid8GB zq`-wEp-&4^_k}3%KF8`z{W~x;BJVWV;Hj|J?_~XdOFu7z^Dadh-vi!AVIeTwzqTa4SBJyrYc zfI`TpI!J)JmFW{)6L}s9spJ&2i|w~8?Os9!Ir0~_9Lg@jkM%IyT0tUe)pwFJI?_uF zH~u^DzB(LJW^d^eNeVtD*MrH7q#YJM5x@rpx`Z6z!Pj&|*+YMPd!Yr41 zX==de$|Rg{oj7`&QHXfZSTvlmIKnKPMG_tBIog=v`#hhSFvm`kZ#He8x@83=u9RM! zMMUQ%etJg;xwS;Rdpx655ZQz&D$QilNzIS-+M@30I5bjZcvN8*3q=p8E(=4>fEkWN z(FjHj2pZ*ax5q~*WeKqmOx{)II~uY~i)etQ*}ju5LN?DV4VHvEvyBlMEq}rx$dcsb zs)uff$F1}i3T1HIpo8VXsWU~y(Sv2LRgK@&H_a~$i~Se6%*g&=|3`mp>V#QudnidL8HPH7@wEQ(v_3_nS{lJ^IBJZ za+tb+ePNn~nG=gOQQl8BcQ}NjaTLJU7yI5YkP#`m!AWUQj>=!n8mj zxTH`Q+K)XjIK z1BFJ$;!1%bl?X|xpO|=GrUpI+$Nn0kt zVFG;#0=;-wJwhzAQEk~FD_rE;LQU){zEqK3WMZ0lMH~vERuXOt<4BFSv9;qe3SNs# zI*v^Vn`nN#cGD&;KZBhcW%~DI9T+qT7E9;-3+`pVlmy*8=u}Ah2P*;5rKe!!ry6 z!NNo#l_4OJ5zq)IsWB;42Yv?b2M&SZ;ZRyqmlg&FQbGJTdk6@~uYq*)h5@i!w*ydM zB8U@Hdg1#IKNY-lA&{922lV!A~;25*Sw*sjq+1o1Eb(s@x zg@%S_A3RhYw{2dkfRFQPpoOO7!?Sq0{o|U0GLDI#p>F6}6w|x)g-ljDfjE+edlXw( zU|Pao73f7HGyN+;PU~)|*dIC$&peZk-^-v(o6>Y1Rgy}q4mQx!c-kBiv&uPig6p!ioVp0+lCo)A`!=mRSc6j`{>M?wt{aAv zF4;Gog=ISFpQh{5E&d3$j)5@Kp(qFm;Vf zalpdNRcCQQWN|?pax4So>s80MmbNqZdpYg2;%&iIu@Pz|o-3nDx-@t?>9`f66@xAu z!fr#%tcV!)>G##1y$hmF@_Nd75K8KYOY58IaTu{f zlY8vpu#=_lsL@OU`Ms~qbCGhTuYk0=ei@(s%8PZ{sYZcYB#RoQb%#!y;Sy};>kn{v zICGLXjTqEsN8V}|Nsl^>k%|WkZ|iq5?e0Y_IYMoQ(qZe~!i5e?W9gAYdnt$;RM_)N zMy-_k5!t@!)afYaHE!N?&RP`{Z=OHfpK|QAgv2bCV^zE(-mW4ZmMayLmViuW(gUf< zCK@pjJs-n{?v4{R+|g(>A!(Mjbc&mXhvey5qxuG1%>>;UW5`z{Y53`@_+|`g2wa~g znbSwYM?oX*UEPass=0_1MTN*Y9;C2V|<7-tv;uyje^SI*rrmM%;@K7?HNHj zMi$wPJ^ICuPkD>*sO{RqZlKA(4HzLL-66L6AEkA{;|6J<&qWE8{!f(rPZa-86#Y+Z z{ePs(zY+=CTN!CCigzdZe?Ump{r`b5SWmK~RLka3t~KQdH!W4R!>*^dsYXL+6vT3N(i&9k*2xq&TcVE^WUy5wOCck8{6i;pC=+6qxa=P#1UN* z*u60?U=nQ>{2Cb@8lI3Ixu6dYwnrf>;He9UsYBYXJjXeTe={LU`?BsTi!EQ>O@5^ zA(#1%yfRqO$cR~(j9wEgK`1vo|dXErZBD<0u4*dF_(0J;Mfs^u@!xjWOZ8@W#J zxc1KS($K}Ob$?Zh&UaoP@8Xo=cD(d|1QMB6w%+IxG^( zE}o{(t{A2QlIPvB#~QRr(C$R3F(WRL7#h`QuSR}qY6z&oaaS4X@ib~l>9W~jJjGZiAJjO&{t#r>M z5+saLF9Q3GZT(`FU;SjifO7Zz+4ZC&#J=OZm9;AI6FzkGc*$n*>RtXDY8`LwZ0&TW z(npoyXd9WS&W`p{Z(3^C$W*-j0jX6RxkW2iiN+S!JxaPP+HI>(X&ATFyv=@XTZm2* z=N`|m(ky*$2Ya0qS4l4`FZzrQO>Wbw^stoZWHGh!L9N3|*62=dfGo6E#3N2r4z;@Z z*u^cXH%q!p#g&t%Zb3iSAu&!Y6w6FR#nKW>+0w4RD31J2T0&NurztCGhk1K}Jemg$ zIX;m|HR^Qn53enQlrfr}Tuok5xpO&Ft$I*A3X$%1`kJ^2+gQE5&AFS(LoC*EaCC0z zd<(u_FZYmgdG@OaaMSTvDlLUx9!`Iqav1coJPii;61Y70!J9;LVwnsv;$K2eh;WQMQ2#EeI3%4ie#SelY7 zMR)a=>Y=MarVBwJl`C};C7<=fR!fP%`6$5Y2z@!=gu$%@CADW6z5U~^SjOh;|DsOVQ7Ws}y?FBw`n*}N|IBLz3A<_Dl z)LSo6?A8{eRE$uc7Y^lkN1?xSjYM7Pl}jZpydN^ad$K-1gjiI>8$+a{WVRWm9E8DI zB@%xPg~kX1^%+0s+*~2%obDwv*D5DmWdG9t9?dv0pyM@Y+GgJVw;ETFOSW)uxsp?d z!y|S|AXhQ#7FYEkb#&o6+Zr>Hq{h)CUhGWX|p=rqBFsk~X9c#=VcT|$^Z0z@XQXqXbJi9_1xKyy(h z35B$I-Hqe;h}w%>{_$;OsZ?A+hR;JsLjH)Mb~Ap#&b~Ia$qhrg+VsNI#;QgUX!fY|W;E&q z+@?P(F&V2H)86I_50w8%6ZXno;4@u{bX`5-X|$wF^oaVzX;g!lUk~o zB7;TFIvV1n!J=(f7hCO+bTpl&AIZ`$Ce1@u} z%DCJWCDk*TdFF}e6?=IDwUPSsP|r-&dB^-i`;g-mLD!BRX*pQJR##xEsB#u)JJYPF z^()2WuWTqYp;1R5h6RzB;Q~sSI#cJX2J#OLvgR z=u?)q`n~9Ti$&J;$eeMN7RS+S!ftaG(Q2F?%0LL49=BHCcX=^0(_+Ihu4&7DH(tVW zZ7wMNC-rU-Q={)*WUG>*)^1XgoD2_rsP!see-fT_!RaYVx+t98z>D8Ej@<%4#js zD%H-1S;EIAJrr7EjJUmwXpM_&eCl3pu!4LS9~ye^#A5~ThZX&dAkqq&09H{_Z$~kN z6^4Ks0rKCm193qQ00DkrAi#tK?Kh~FC<0c|1_?1Hgp(R^5g-)fga!V9G6I46 zuiOX+ri6M~6(8p8)PcC~TfjFT?Ga{(o3v_O(X4fbCb-F=I`hZ&1$JZnzX6j^@K84nWrCy`Pu<~+4`X!SeZb^TDpYcSsZs~I@XpdC zO@jQr7ba>e_Z`#hvLeE+#8_vA1$xSOQ``@$HmYc9=HyfrpSc^~c!LgP-3&$Yh|mo) zie09k7ZT0})Yr2|Au%Y(G{BxV%F}rCffAcqWjl+if^T|H;--fB0#ISMO{LzN`Vp7hlG!Hv~f4A zYnc?JjdGjLX6?E$P;p8|n$)or{CnrG0Oh}p^S~9&h@8)6mvlV4ewjW>7}Mb+G*xWT zVVIqe5edv6-H7-kHK3;8)S}DVI|bN-wp`G|3|b=1vS+4`o0BI`7fo7mDa=WJ+6IWNpn@ z)0K39!MyCQQbMlk&xz{UOiKmaqF#hWtlCG=7p8a)d*6>35wHVcW9fd-Y+R83&8~r{f0ohU=Lk22UA%m;n&oes^ibymCz5opn&_9?uhhf&_Fd^-DTZ zfySlOS?3^yTxmSbf9iY4mG&5=TyuMh1>zwP{v@yR$XPV~e{MM(V9(pD*`Sa${-j|Z zf^YxHc9z+@^PJ`2Ql?l`=E)NKz%??P#bZzFaCRl!jeRtaoXaDqdzyyNZ2L&}-2yyR z^%xuk(|Eg_V(}4oPQ&bm{Tvd=^!74Qx_K*O3a(s2LIXlm>g1Yh+8NpSa?*R~ zyb3MsxBLrc6J6mmg5PgnBEcH<=Y_7(@kgKJ!8;rX4r8BC0t)C>^<}cr1n1Y;n)?ET zd{+Z7_C6e?1_qni?Re+c;*35PSO6xS5Hg&F@KvRUBHH>Ke@{^CkmMG5rJ(JST`ap! zQ8pfSy66rqw_GT1!>14U7paO5;Os-}Yl4M58&e;7(SS6vsowHeYt}c?>&Y_&{#y5e zR9)_uxVE9R&KUf!LhQUxraW11$xUbP2+1!I^L<4;P)IFTS1HE~ujw5NNxRew=l zmCnJaev;oz1ZP@zV*ZOFcoK_eABJKB^*WF5D-Q~)rBAlS*83}I?vz=u0;io~zhS2` zOIpq`;@>Op8W>VAHma~!pR?mZze6*G;zkif+t7{H;F0^Q#m~u&S%7xP@39HQu~D0% zDO|zqW!cz45vy0~HpNxX&j|n&#@PmEQ3+ko9p800qx6T`6|m-{kEo0lbTuaax$Vhf z#!41<72^IZBE}+`OK2Vv;3dW3dn9V1^X({9Ak|6Bk%|~_#5WDJT^RBoS8gEaL3Cuj zj@grqRT=_x;K9~~;I>JW89f48;gvxV|eSL0kp(2b!p>?%O|2fT5yX^x8t$iMYkjrhU)- zhH{s2l@Y1J{>l_9+ca{fh$-a<{*J1J*|XV$9{f+}!RJhC6Y~>hm2FH21!G@v^KFc| zTKz&nUqeyZql2sJTP(kM>#A@59$eeSb ziJY_|2HZBLs}hsSf3?qE?;Lh#rS7@uEP8P55^KFq=&kWKhbu)zGQte;2=cJmxC|oh zkNtHj8Q>*?Q-NSM>_Z={i6=39<-5Lb7D>{C%l1aq3Oln{`;F#MVS=(jsaHu(3*MNbV!OmC&_zP+wiP1|Ue0C`a9y z>jH+#_KlR*qG&{p;2AyWJL8h5w-`0?;uk;S*zl|sdA$co%SK3*bk;C^{XU|^c{~Gv zzkU1N5HAxn*}Gzdf>{@K?=Ilyvw3+W!IxeZm3xZfn*a2?Ap*q9jsqIQ->--uM?#bi zu-666KJr=d{KFMtKrCdNUXEu0mySd!og4SrkSd7^D z%k0r>@MjotbZq7;`03o*?4U>Z4a5|{B26)Y9wQE7XHbkvhBS^!?lDRm7>u-@t64mt zK&m%fk5q_yWwP99o8!kIzYt8PDe7_0nc`I$1|*c&x3kES8yZxR`z&B_b_?Und+ z&_i4{atwy|YJN0z2sZ!xhjo8^3K57uoaUdp;9EYgwqMLricj&JTaN~I1G-tHvn>7#m_q# zh3%2rA4_S=C^<2ywbu6X%f?~>FoAta1_e2?W8ev~%=U)tUf{Lj#xgIL)$}G{2bJ%_ zOJpEr)qNGdar9jYDzD!ui}@esOu+(qFcZYb^$D7D$qBkh6Uu)_CY^!kgY|i}Z6&A{ zw_)bE;#?Md%vDXIF#$TwY;@mq7BfeMiN;fM+*$5Vn{Ox^wQDB#OCEphi1;&}Ka90W z1&+hLg~8V0WZ_0ntb%q@47RRxtvqFN7mE<7pZ)l#(ar8Cx)yWNZ0oShZc;w z48v=^D5r3vqvrYn9=875m$4gGnzM|TOfO}}h^>L|4e4TXS6lcu#gn>jaikFc+?+P2 zn}}d6u1R5GN z)rNYY(LK~~vxHW>aUspnVKECYY{sZ$?@}+OC!_JNL`H!^>C3o`i;5BPS_x; z>!o7{!%Bi>j^G9Uq|Lg;00_)7dH1tU$>2M<=JN$&@ZsjVq^gzEvN!whB@8!|Y}YK@ zfPY(pV%LO2FQlCmelG=K1pQ>h^R5gTmirWxtZrSnk4I-tOuAUrseJi#T?@d*C2~3u zJ%E*Cupes850-XsP$g^2ufM~XK;cFpTdAGHL5RnA0j#>)>T4nJeiuH0^tq%VCOu&~ zf@&!}`-KjD;Y5ssRnA%19&rotP$(m#8j!$b=Wv;-X*o8gFubfxmrUGKJ%Ry=WQbMq zFkG)*5=SiW0sl^JZO$)FyF6$SrVnqpf+4<0l+x$ zwjNDb9?GuD)H<*deE8UYqe0NL0|7qR>A(A)-C@Je<^kd8pN;q2q2Pwo6-^60bF9d* ziM;m?jR({LL5jBVwM-PeZq8HPx@h1#hm{2E6V4%N}l*j=D{&#S*{Z6lX%*pWc*`LptVjBt`#2V}TU3 zYUVjMAG{F+6k>;`H_nOHH=3n!t+hU=9iQVWzeLvXSmq6Eq++||(VXOVa+f9q5zKDwS{T*Wp(@Lll z>DD%#Dy+u%A@w9sNHL82r#gBgjCfFE##vt^B2o(SSp~-}{@e za){gpKOwisx+t&d$Vi-+&9q$bDTL%tGGs+11Q`*`YVe!S_%n%B(oFEb9I2$?a(ls| z^iIsf@@UW8RN(Zlte3{Oj5O|yAK=!rHuMQ9Qw|vs6INHyQe8pcUsEXY zLq2+O$QL8AZ76bxc}&!@zZvCh5tN~XI_)t2VnG6ZU!PVbOF}7dP+O?4Hs{@{P0izA z($+@WLotkrT~MOlT+=zuE4=TOzh+@nOMVz_G4Rhl#zo6g1L{Xbh0X&cgE;>ouUJjy zC~)QPz<9qPT{@LAidStk`8AaMj|hOke~UW99L9(_`qVw^9IW}2pi?Cv4Nx5f>DwPE z7T*}j9$pYau_)@273aRBw$M)A`3mi1M!)1B~A=nwMfy90e!9dQrixFd*YQNu~ zy(rcXK}ji~;xZCGjDf1qVdc)v&EXg;O}n?kK$_RQQ%zlHx3)UYPNhd8U`|VkO(g%d z>=T&Q?m`FoRO1$Ucio&8XU_E1C)#;o(6Jy97emmJ(`c2T~(c7f6KYxDj`6Bu`EJ* zy^Ohrx-4Y{DyA5!;Vsd4`%eY?=l<#X(-T#e%Z=ti7^^^b5=W55Ex#S}vhAF$vl@1W zGN1%Mb1+*C0#@7Psrv0y1$FYd7@9sdq3uL|+puEhyCkvk+{)~TFM@P2AcU#GDKC{^kVI|9A5tEYx8-1E=1@DMZW z;yqsR@44J71_A-)5Um)3DYb@w_ND2D2q@H0TGE3ghn>Lug3FHH_x*BEE5!WC_gui%~j zuu6U~O;Rt}xrScY3^`E?v8Q>|l2)XHA+i`uvi*tFD$fbCj+&Yrz^I!sKLiD8p0oL9 z5WW0oCcOw8$1c}l?Xhu(gYtksmC#T0%$W*^n&$zLJ6c@Pv7nGrdY%PDQ^6^WKnOG| z_+hRho!Ao4hsy;~WmpT3yd4#PO{rb0qd*qtuVodXo!b;c*C!4KHZGh#9U8QOtS^irhe-hkc#41I}rF>;Px{2@x`so-y$K$oAHI%yp@vbs~mtUuZU6p^?3|NSKy|T zKs9vh=n@Wcz>%rNvVU!^AsbQ2dr$GhMS@-MrCxdARwXBmWuVGG#u1qrG&|)7cr$;N zDoW8NhhqRMq^_-|qa$8?lle)*xZ= z1`siVQWdm>PjQAp?$Xqq?a;}2J1YAHd2)G*(LF@MiW8j|4iQ0@uabPUX_{gdq3Ekc zg2YLg_!I}oLArGe8uoyfcL%AO3`UaK_kIAk#!Cf7N76cuU|yd-ga2-X)Iy_d&Yun3 zE2AKR1$zZsBSa%SobPJuXOh3dcDXd^7x;YAF-zw=vjxj@C)=Voi{sbDw;%)WRL2Nf ziv3{S0~OkTyf`^6X}L~TX1Ke!Z`FGbh&N7 zs<4i1=u!+)h5zpCBpvqYrDB_hLK_{qyR_Z{vh++L)O4HGOI}A9X94xr0+x2z9omDj z8H=0?zAdRo@WfjUsK=u}!)}E^cr|NW1eSTX&Q0&m=icC7>-~F{%Mnj{lhZ~1>hAL_ z*!ab!v-LHkCEQ#VyMn*Ko@=bmL)At!_BXSh%BkTeb`B`VJjJ+D`UhN0aFkZ*9Yw$b z?OmPY;fvEYc)X3ip4&YgIOvZK4E@Hg-%0cYO3%nJO#Aj#VDs5RhESvT&_?DLPN5C;sGnt?ai524L_F}`Y>xBkqPIZ8oDH}aJ82`t>Ap`MDv%W*ZhHqFVj^9QFV#C*kxd67&ek$^K9UM5tS z=(UnM7>5MPFrZ2ZHWFUlZ6oEyVncSFUio!!yB;N+Ao-$eOn>!zddK z-DRqQ7FzfW&!LE=*G4MqoBtDXt&yWR$v%ojk~x9xRLzF!xD+pV9DVuu_SaXJ=W0N> z>&yZNg>>}LvRd{8jQHtUQ=58gJUXw`y(Hp3R z=N!3?6a6apQDiCpoIa-YH6KxaysH5_Ob9AVkkkL(oa!2iOfAm92U5d5oPmF>$`Jtm z+Y?cn(k}s5>R}I0E%q*lf1U+EFt^RvUbZqGpg4c8f+?onArpC3!n*X=3aJ^Ao;VH zrf1uy4F+CC0x`CWVCUTc{pq_FruMbQTs@5E{#xt&PjwM^=(!Z_=im3S&v{+veXJHm za^fQUErr+q2_J?-jeLhCYRpA$Jsgy;t;_^CE3z{jBsf-qaB?K1ek_#YL}~BGw9zZb z?&WXe)ULUb;_FJ8>e1dGnaU5%F=>z#+M;FNF#2>C1fKgg!R?MW>&Nq|&?()Ny1vYS z$<?zYtYsy*iT+~H>Un07XznT04rGU&c@tsdB05+fa z`OX4l2W>kxGPzK|*gw7Nbbf#7o~Qc>c^kV}70}2+GGmg4PNUrhH<^=T1d;thknH4D zea1!d2WS*fT{B*i7lzK_#R@8dTjrl1sKM!5cLcD7-2Qvu6z5sagBohViBSfRs^?@w zHZ1b{CDRA(mbA-4rj`Bl(wx4x6*}|H{BQpzulGMAosF66sW;cLi_e-#k-|AYIx`Nu zbEd!R%YQ9qud@8mhWZ0G&cC9_vw7A`!`^)YLMmfAMOcs1H4c3#ohesj#URY#5%(o` zjK967w4!p9o^sQ7ph$5Np3djh85K-?zGq(gq!~6cYE8isMF^hlCduVi_>+b2PM-%5 ztj;=R+xRS1@iy;d`RfbMGVW%;w_zrJF!z$mXoZd+pEYMat6)g=mX?5n#4HiC>k#A2l*%kB06-Q_DDo^Dm?^RN1ZITIr2{6Hp z`0m&lE#(m`o>adxn|%^C3y-b@X^aj4X@r<@@RC}pS52~8!m9(pH*HED&Z!q%Z>Z$U zZ_gE9)FlLpB=$uK_im+lS)mvw%4e&YT;N~l*(`K<0cOZ4-HX+Jhs3@lo4P)pscAY*gE}oYg|UMiHxEd;{KHcNwc4dNNjM{0IU~8*4YJ_rC&tEHvN`lYgfL zt41j){{}Pj-zk48H74wlBe2SK35c=~ruaDCyQdeL->#hs1Xa^J%vHVl2U`q)KjSNk zhCp9njTblvn(kynBiS>P(_o|@uJO_?!m08v4r!96jv?|RD#@wlt)0^n$O(Z&Ot~5g z`)Ycp3PnV8kKk86%ahJM?Z1GzvM}==>Yu%4;gKNd_mZ{*4CG7XdA%qMW`nmii78HQ zxl5}brsehXEv2JX6#u$d(0e{?N?|t4g;31vOa%hrKzTvv8;D-u+r^ z%-MKdWCu?DTeRQb*H4f4wEuHn7gC$gW|&Fpb%L)xj7U{Hk{v#5j8%Y{m{QI;X!@Di zKGv}qp6&QP&Ujp$ZG`MPV!8%x!OY)$jF*^#8M7a9h8V2>pe~ljtEUY)uK#stOdGl% z|4dx?G-ZE28ZKgnUi)`rQp{LTr8|D%Z|Qx+*{C!<{<7MbsF5*ox&08hmbGEa+CC!w z0AAg^DHi~DvCWxL9h5!DW7(LUQHRCrQu_2AyJ%h{ifR+C> zzRUDD^|Yz?cg=wHzed;TL(=;9jmH@Wk>ae*n_36(dKQ;vyYP6R*B+|btPeKk7+T$; zKR(7-UT?RbpQVaB^EUzIa~1n$-!~-(Cg=SH%`H`g+j_y;Sbr)KEi;3X=t{KE5ayAE zWMjGnB}up7A%+lflhhjHzSftoBkc*CstN|p&*?K1PRkz8)=w}{78-VC;>w#tJLiN2 zl3e<7`Sn<#nLmpFGUf+yBh!)eVl)=h?)9TGKUt|q!AbU0&*TvusOS=W?+HFIp)sj3 z*H4^k=!*Como(iDW{-fFFW=81rw=zobL;#Q)?1<@y4=HsvigB+`x8y>Um5v7;#Id` zoag*kmo%7Q2zu*q>LN7Qjy+Lqv<$EGq9Zi)0<$4}c9MQT9b^sb%$0bp4a}e;6B?~WW5=rfuLvjUh>IFzdJj~j$}XYF$~(4mS!UHKd)c&m7^2M=>bQyFYMFx z@g`P5Uroi}e#;jRLvF&g{{G1**=YE;@rZF$*iAj6*#Z@Xp0T`E1n2#*$?$3&gWsK* zVa~0=Ax2)KfepmMg!C z^y6p;@-*~u)O?Va9&tNA@%BG7v$RPyM5#&K*3tf9t&>TJ9t9r9V3e>6c+}*>zR>e| z@V8#v8~T2Oc7%2jnynKf;OCJqp~Mg)2vY327<>4*a$LZBX@ca=Z@s=Sy&`lk9?-}e zRx=8a0KVSr3EJ`;tTcUph>7jk3p0P^^i1dQHb$yT1~ zF!G55P9YGVc%kKKo?l&d3j%lg_rsnCz}u+|^2+@`i&Th*JE?iJw&DCgYw%zADoHs#0?I$!o9RYQxS>emfn>Gq+O$ zcxr5p>0i3Ya!xh|&%z&f?J?`v>OBz9NzsaUC0~6diaF^NSOXZfo23hOJjS()!u|su zz2A@(5?C<$pc&}lk^Q3$M}@Wv5{pPqJ=X))pf~O^SxK74{%k@vNaI_|rfl7Iz8Rx2Xb_w`?SiLnphlwY`kGzYhGp1_S$D&s@{c4XFkI7al=M z9+$m!h*{|ey^~2*zLqJ(o>^mVS zZ?3Z{K~bc1n3(XQ2`QpLi3XqmIVd$KKOr!L!%(QnLs7^f2!v~3BJRMC>)#zqkO`}n z2ZkY$W>yD|LlIQK)!uX)RK85-!7)6s5nBZ21ug|XBEbUL8X_5*iUMm}bPHI>9AioY zX^Dfvd>cW}qu}o4XUdVW7W9<)Q=qh^AmczS2U6%klg#iE?w$8jvXK`6nYK4J|J9_` zL>1PskZL5^_(D)R*gz8$JQNDljHsYeGBinfCQu_S9$T5DQ&cvTg8@26gksQ|B*O6+ zq~in0%!kr*s{*f#{b|(oZW-yvFTbr?(O3#0(T^xxhX5-fN>n4#eJ}4~-z`eCtJT@F zU;o8bYe5Y~8jzR7ZlD)|fGYTS%B7HkSXj-`(k7&}HRBOIkScX*c*KSy(3VFdF1e+& z={@HAtw1PfNG7`R%Q^<;l%FX-F-{^d5>eYUknx3+-mkC^4k}kGywIV; z$b$B%1d5Dm7are4R8n=dBFD8ztKP^7PPtyll_tm-ny?p&Yqj72JH=59x0g1lDRpaq znOB16hB$A-lHq`KraAYA-^LjbmTj}7Cm4q=18GnM(FF;M4b2OIV0(_1XB<%jR6u-U zuD1pqt!_duso%2I8OAaJmgRq8{ApPi&4NfQt4&S(B%~zKXpH}Ba(UQG7c9m_ij)QKlEHrqe=4!F06b}_XS5?s&_{ATFh9DkPiQGq0`3KOBh zRYsR4eKJ?px1ln#+tl1q=t{PSk%uXeg6Imt^5H@umGU1{e43Jc8H`1dX+C{V$wGNj z+AAYplaZ8EA%=oyK(Q}|>bH8mqJE>K-<0VOc-4V!2My?kgiSx%0tA@Sa<&9E?8OA% z*ekUfzS|eG^3W}4&hOxS95$Yk609K)8FLuaOjQ|1PCp-!+*s>46r+l6i5v;9I`+~ zO9-n#mP4OCVtDj=J^kQ+-2CwLWmp57OOvge3t2mjW#GyBt|$N9L7r{? zN1;Rs*Y@m372mYFW-G77fEY#+YW-Ze+o~O%;6zT%pVPBtomY2tbq*3sd@lMf7#L7; z)@r>-;(uNG!E}5Tyz8&$W;s1lBTG2Ol=JB*!>h^m*juQvXl75s80x9a&1p}ndbmx{ zIR-StAado(#(qx9_Oz%W>GAmZAl}mip9ER7McWn4Zb9$#*Vi2=K4`;{S}kfZRvA#N+} zINlqEsv$(b?fHRt@8CV+rA-LzrHn@I=zk-EXPjB5Ozn!~hn%Ka6h#bQ^!uscCmtw0 z8FAPEp2%Q7btH&vA(_p-6fG}FB7ki9+sOKbMQ;~sH8+KkcpX*5sGfifj{pwE2s(Nk zJ_GOzPLHL8*;S7thBAGv4rXl1CZ}~ip{cYlGgY@HhdRkw(6rtkaP7XJ>@zP{1%IAP z4~*uap9k9rl393G`V}G+%MS-dMJNaYF`x>!l@0`5Ril=fxZE6ZV{_phd?ZkxbYO*u z00rH6CeXNa2Did>I`3$hv#$*L0z*yP?*_-EsWqzgzYr41Aal<$LTh1o&CuGWIm zMi^gOi{Xc$w9tWwtRT8uYW@?0pntTF3dYcpH2Bt<5Z;CPoyug&D!8+J&}2eLCWZ+R z`KZ|2o%6<7-p(xwMjeN@k~cnnz641(2juQ2VQNX#=z|Mg0r4V8K$`+%eauAuq zHg_Zcq9W!qC`rc_4+WuSW^X5Lv`pQSciaeG0W+rm7TJ--;NH6fQ4l#JuYZ}wJKdd` zcNeGk9WG6c5Tcl!UrU<|&IzbI%r_vX>_Wau1@P_ko}HXTiA7xbC@}xFFkHQE8xqrDZcN``KKM7lU6M!@G|+%H zV2lMWRy^g zqPRRN@?@89YW(p^#(#d~_uJnol(|1{4&iC)G)O$@=>V)ZrPrgxZudATP-JIov>C23 zrQajuLX56_6NbYHTLw%r$286T| z@72L;=o(x)t>6Va+5^Q9F(BYXc7DgV0g*P)czI$!MSpt;kd#A1<)El{fWTc) z;}g#}g7phTnlVCUQVBh{H|WXGIP&-TkWl^BDaMh9~BxQbT6?}ty@W%!d&W8FX)vonA7 zh?S#cuObduReygAW($HOI))fyzdJ$A{740y`OZSns!2xtLx&NLREmX@_&U@$H^EjECz!98of5iYUY6v z%oWOxfS$x8?SK&97hpRJ=Cz2^E%R-f#vS#gv;>I#l)0u&SEq$cQ0vtPlSttuk z#(Qor6hM%)Uw5a9I-*%vHs+ znVDRfD58!`9$J{(N@7b6N*YT_N~%hPri6r*M3+Pkj7AWS$PA5~*d;|Kqb>q~>8K!c zS~)QVgb-qoA;cg>0+A0Apzv4-=s<*lFa!}q2oVrMke~>RfEYpqiXc-WfdnECNq@HY zp}i03ePHjyejnodfbYY3AHw&+--r7?WPKmz9~LIOY(b6?n5PF@=+{>f9+K`uavw7H zf!&AcK1BCH+=tP9=QTEM&Za z0RwE-^AcPLXL!M=!i_zkL=!Mv80qmJEfdBh(MsjZUO+CUBD?4=-Lo`7aa`za)(7~OQoLS(3V+>RA^>3} z>S}Dz+f?A+C)S4>A)sFMRNYMX1`wMZzc&fE5G>WrBaf%$apdT*`REWS>jVNR4lvll zAkRho2cv;R)_gQmeTTqH)XA*Rg2GNfoD}zr^K@FB!cSF+agT6(3;KU%?Z0rBV{G{; zP}nol%mq@-KOpf5+1>~&?|*uvl!&ehZMVL{I3Hpu$tqiMrr7}+bozn)KY_inc_}59 zoHdU@G9SirYpB)eBn@S^da;95H5I3KGmba<)s&U%+Vsn6snHk{6QQLNLgCaFxqM(7 zd(?CIB?h`*=28|!75jK{N{h3PjD#@|WTLl#k#@t#&ts{>zPu$6DpXLcWIbl)mFC$P z3e0*648H~J&RMj4r4~3}uC7<%0%<795J_Z4CgcfzHGx*55Q<7+Aa+5<@R=*Z@l0Ti=DMvnplL8OD#NVnBU0jmeD4lfe` delta 18153 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 diff --git a/src/Nethermind/Chains/ink-sepolia.json.zst b/src/Nethermind/Chains/ink-sepolia.json.zst index 7e317838c1ece1d6105f79698e8982f48f17992c..100be96e49e14e1484ee4498e46fe7878fd15be9 100644 GIT binary patch delta 19892 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 a1@A*Yq6)L|Mj`?Nk(+}RNw*bA0ni6(ECvDq delta 18257 zcmXt?!X zp4Qit^=|ww{DTsF1ssJ*mPPWvHA^fVg?V4;K&Dq~8=6nL!p|s`#PVtl-8;GQbZkwJ zG4YaQi4;{+nE2+i)MI;yi)%8Bs-UsS9A+Qm-?6;%NWajs)u-gA(a430;mCy=IOI~; zOK2oh7XV~bPmbFfE5-C%O{gnhYHf7s3a+}Z z?+VQ@0?(qmloY2+jrihkCv^s6AnCnnavHr)5n7m0?ERau8#h zkn9@AHm;r+v0#@V-{46bkrY>}5K415EvpcRP)Eg&AV=Iz)mS9V@uEVIp0OA~&V~S! zUT9q#CQaH{5}2nffPlJ}l#}n!lwuI8$ihcy7Nf z@ev|nKm{{e)#KluQpKbuJcM7PwJ;os<7PL z&6VX;D8v%{H$7EO1$4cB!Yq(M7(xtGUSzPNqI0CG29N{+Ar64X#73!-tD)l9|S}7oZAHp6yXAgM0^5;K(wmIG!hPk838xuH95_l z4kef&Rpdl3ag zpi!{<08j`>N05HsInZDj^4pmi)&_iLY%}0*A~@6_`-Qn#3DtkJu`qKzEH@M)KJ))* zfF0`thYIWmhqnUc%oE2yh-b zGQ^Bgj)W zTVONR!;^@r3PwTuNSoG9MyTpWS;Uv8ktL3lbB?gCL%|fp0YPu(DqwHawCx8xDir}l z6~+3CD7$un%R1yT5Ov6#G~-}81myGe1>vy&z{r_}gZzw$>H}be12n;qG7u8F0|B5P zTw|Y|kGchikQurrnzDbt3v207OeC^?ZEX)8PYkl$cWl#&*CmFfX1$LFt3JXLA(1JW zdWNd)OpUb;#G;VFw?AS{#R5kl5UdbZ@j37ntmt1rf*)E$g0$vYL)@?$e`%eKh*nX` zeT{L6BQ>l=zY#?MxRMjG$OR08BOsg@f7HlB1OrQt3A0FsjT2B17+h#H5=?*^G6?|& zfk^1Mo5VZxi;;UUD(&Vl%>9{}>hi8=LD6aalD-}RS9sTm83Y9MJ-WXL>>8$(ox~yA zp_%o$Re#R*w=t%34_%ro4=zcIrN?k7gr~6>4zr5*%1b>-4sG#@Mv$6WJ1O(MPvqlixq-Gn&cdt;~bxH zVYPhG(ZYwF&lO{Oa$1hGX>i0O*Yk+#)Z&yeE!*Fc;*UZ+{d~j9l8_xbyjN%B{Mirb zxdIu~W&_1r<5^{o=0UmS6X5VjfL~8tRZ_bwY|7uCd{CW+%q3OJUuj6g2HQkvNSkI8 zE`-z!mN6@k&XrXnfiZ_T2{Kfn_=6)Z?&!u|EYou+#6r}GhAaGTL3+oUdoYK9zoss; zS&=7a&C$g@*6^wth6(j7gX0NEDi>6Zia|XfvM?TVGxLnXH%v-91v;--*+LhkuP8N9 zqQAwXz7-jmgtV2xb+GL1KQgP*BD$ElE~Z9v%|HX&Tm1 zjS|!}i#z|&eyDjV=q|LpK$l&VxMmuqflX6E|FX|x6~?@qd=sotHYyF9?Lvr|g=)>j z9+wtZuf={*TGMS^wylAFPfL@UY<`lTqCx$8RU^BiwxPO}1*oQLRHu0@;OZ#& zlVdRFQ;jPpWn0T?qZzZUa6>~A^M#kH{GgW>g^=Rxg>lZzCUqu`6LLyUnHB3rnSZBB zT`q*dt+*%r+_DwwLTio7$ ze=L%fCsmUC9*`yH3axcMb430{fYYj1LAc1S=TAy>n1+5CbdqC(GaV{=v*r_mq7-4w&sX5imRhn@z>txb@sFoMJ5Af2{#JiJW@2K+7@Z*p zi^g$OjT!-O?TKg(k$AgP_vA;Dich%fU+LJTI{a+)SKxGtZ%~I!Wzn-bXP#Adekeud z+}A*XyR9N;-G~XJ+lGxlb_-mp7BzY|oIjKr8C@)ShHo4hu`VOEWRLW4qLKBMHu=;7 zp*hewi?r^cJe`DZSXGhP}lc0053!yfvB&(@@C6}1G zacEl0&@d%M$E*v?7~yV3$j5gam$z`RznyCZ0dv^4in5tuulN(DSv%T^5f*0&Gsu?y zYA>)+8T6*CGNh$olil+7Szei+4)iBqB~GqW5)Ni^C}O-k-pQi9tfO>$Wu!=mu;PU--Kw@F!+iED%<=TKJn2NXXF!2hq!;#H?T7) z0Jd*UoJp>?MDd0S6zDDwq-1og9mUo}YRgldOI<25xTodiB^AYD5#M=f9Bo^zbQnTe zMePxuD0dU_%2`Nga!sjZTO`<`Fr*a`u|NH{P)go|V(?FX_>*!qu8><=t+A3fQ-Th* ze=zpu+^ME3#iHK-vNFiyy64MFET_ik0yu5rbC6=+<6VymBvtf&bd@QDZAJU}#W6MO z7n{v8QP*;pHdQlw1i3B-yK|WhXrthG;!7krWj&H{P(c^MIonrZ^P&!AN}wERZ;Z%r z$bQ_|rIK>k*Xv#Ddb6k~;51C=NX=!0i&%nWH=Ux5eVWh$^@td8>%_@rF{|>*k~MiU zRlhaTpx*d*Z*sH>SpWrt?GaX$n+IpPCEvf()Uk0-$7J`tRNp%o(>0r=CdD6jhK3y# zX(^^v5K=OFwWAdD?(Z9fNdSV!$C3szzO-ERp8;Obq0~nzRd~gaIdgvMfpS* za2+c9VYb~EtYf6Rez%<`PeCySL`Y&-m|*c$-WhX)Q;iR}7IBiVqW(BRA7xBq4#WQp zt;biBB3Z1&sO}Y1WlJ^fY^5fLlvvoO=+?|&9))Y<*wbj!$L=c2Pld7i0aBi-7)Rxs z5uAw#wp!Da@hro*lpTe-^bEQ?S#dgUX}ZYIb61GGs>paXk3G)^C_aL!zq)K@j&kmL zX^MrR%O4Z7)OF@maIpeK99ddA8pGs+@rgX{)#atd*+n9cODPX;lOHh$OBj^od&-+h zyB~_utYUd8VP;)<_CF(Y0QFcMIF{Iww8xzML*`e5jNj*e8V?z%vl7YaNJw#*Un6^G zs1r+wwz5jh^JS8aoO2$TL+U_UPF4vn9R1=122<4VIQw{o7bxeq? zWF}Y`0v)T}0q@`%+BvzXvQ`{?4zIgGJuE_Scr0>jLabItW~Ad}=w2n<5dH2#kSbK(RF!7Z6ekBo#9!do<9*T*>ZX#!=CPF9T8I!P=NcwX`4eHEK zNkpkV7!kauAokz~m7bHRcw^}zC@Eg)HPZ9 z^~=hX{L%;&U`v}CANyVYEIdwvu^OZ+P8ma@?A|4%!Rcy5LM9m!ol$9yXI*NF+EVf; zo(?x#@R5>%_@<77_+}7{_=X@-hldH_C!Yq})uS+COgGGvJwL$V<%E8o%s%N`Rj$|; zeT#c@X9AH$-+U%a%Q_1m$XLnkna!mGTb3+1#r%c?*c_oP&MLlU_^&3&38`aA_ihl6 z$44=Ks-Cr_$d;s^6RN2%;EJ8K`;p*amE$%K+dVM$U>%|_=c$PcBVT< zme{2M=q@ZQ)Md>xsyQV8NKK3$=fmqk9r%*zO+?0D{^&#R(BXZHMIwTgc+>bbjT$;8 z$=QsK;r$NLt%V6Is=zzgFPbEX^qDjfrRaK%?>a79r3JSbXW)?hoLyNNBR^3+mk2T6 zbXvsjnk8S^QiNX2{VVM#66)g2RRzH!$Lb**z}R^8)QoziDBW)M9+UO)cU`}?1d{Ib z1$sr2gN9UxYYSn3SZ(%jQ6-7d5?S~G7IK&?=c&q*Hb0)dF#Z*7Uh6juTCV|ejTH^l zp^pi25A&mAk5v*LQIn1^N>%Dn7>Y8JMbXZxtm^5fR6ZV+BDOV^u)+kKV^6e)rT_sT z`Au>`uIuRNnEV72ayI_Z`QYIoF)@DW2B-&{-m5oh@4DS)1Y?o^~f#8CbZJLG!HR?EhzJJ z@U#(?Fz%va3E3XAF(VWK_P-tvq<{x-K@T9NMnQ-OG1*Jdrc@Hb0D<JdTWI+HIK?os62mk>I5=MzoDXHQ_(f{W_G_xa^(~I^1H_+h*Dk(!OOlYnNoI1pO zGEmd?3@zClX5-^OkMC*{hR5w&^4l$?FDV)$tn>5Asvdsu`I2yo>^ZQ5uU`XiR*#G7 zw}YO`g`DAs{*RNIt%}cMEo==wOLqIJSOka5w|17q$~0#X7GFxwc*1TvL1*4Y46-MN zo9Kfw8@U=LTiRy#q(uDPqknAcl1*6qTpp={1-EgiA36Um2ZLhQuhLT?-E?^vS#h7Tzf*#`FgDm0ly5{Fvh*y zGTlgNouGKfa~oEwaEHT{|J9d_OP%7}=h98G#-ruQ@-euPpd*+8jB}MJEFcVCC=H;UrXUtrFz~87crHPh7{NY5 zuF}+UR-q{2@iUkFwFcK_>>}ct>PRc}tTrntcWWcXj~~c&A^JQnvZ=1LzsMb%sOOn;e+2h?F=~ZOK1Hq0G5YA?Z=jMKHoDv3=t4hr%BaDPJDsV^BBpf z23jG`q81|Y=#dXXv+o=gM!I7uv2Zx;kq-v{ors!vZC*9Xiv{MJ0~jWt{BvkRMv>W8 zmglsR;7wPq{0YTrbsA!IRR_xJaOA{!?9I0D44QyK{uQ4}(7sdxOJ3YgbB>NPxdpfR zo9KZ@uIqcUO&qW^t9}RlGv7%?KfY={l<$SVVT#R60v?I`N928d)@|thy!~8F9f`2q zKJZY{@o~~P?xR}qBb)W0KWu?7qZZ50yJ(}^!CE#LKNZtW?SVP<@Gkly3^HRYV04ua zQ$M(&y~#>?IWPP|CTX!JY0FwlsM<;U`O9nk8DBKJab$uJr7s_=WCyM8T z{6mYazRLHR#L_Mxn{DFVuF&0SRV4E2sPNA>UsAVA5%InDFe>y**r&6%Rmi(2+==Z8 zMEkLanQ351E4MMFiE4w@1R}Px@0E##5u2=XJFC|A#tn^!-NIaq1NV=5}&Cq~L6cr}0^%@&u|6{M{1df9j%3kvQ>+ko)x2`w|)cc+==e z_W{y|(?=PO6(sjFl&sMxY8~ajFAEmqY&c@P(}hGwthN|5Ip;p$N;-S@!t=J;j|q-G zyJxEC1t*-Md}b)k4E0zZT}%lQ_j=Sax0!2&2=*W8r6x#3%ZA(-HVqaNzspt?qm9+C zX7Fdns8lL>MoP-U60=rjX$VVrB9H9;wEkD;>iz_Oe93CrC|(UJs0zkAJ73mXyVYW?I)A1?dB1r2@E+-U(zGf4n6*)r8p`9H zADrwv+u>mo+vT$rd4q-MB=!cg;kRGhB{b{V)quC&9} z@C|n*^o!zeeUL(;NJ^A5wM|xLOAxYiqjaNmQ6edg{j17-LI%kn zA9P`rgM~5f;!r=;xU5l_+85wk1&7zPXst@RkshGV#3=TAl{{wWarxyva9>X!jd#Er zYEh1>$}MqsQ-`QO9@5hsr?_af5JNPa>Q3E8+z6(GA04QpK}`-Sm0b^o(}v`I;(2r2 z8U11Eq`|)1p^P;YZ$3^jvP14260lhHyl|3VY2!H_h4*zjW8#ybeggC^KZy|g5E+)2 zU(xfk6e%8@B>(<6*RUH~IPa4jF7@)qKI&TcQ9!9c`F!Ho`&)HJRpQC&E$iPdK}r*s z4anh%gzZgB-#|?kkN5Dhy}V92Px^6&TDyIIE9fTE4BSC(Ww(MnQavnNFJvZp@KW6& zblRvGlu3X8wpJ*HrvPGFLC7O@%eaEd8q)MRtBY{%D*i3bmvd;YWC77EVIxyVNsH5U zi#5cIp+6zhKjceYd5!H+K3CW0AM&e$F1?5u&U*_hDLL&EcfNX*j3@J4K{?d)%sv~f zN*8cc*X&)GTJw*XmW&_4;o6bpy&o#f81s<127Ydc36~~&ksqRKaw&L zXoKLdRg5B)8Ny7->n?XcI}$hUTC0dvRTSJXsEgS5E5hB?HzdsJo@b<&19u)#D~>79 zuotAOHD%zj$$@GrDmVY&-Iu)vLh2G?s4-^Z>LjgcP~!MA;k^nWpwCbL#R42>)rbv& z6&)Q%m4}s=Cv+j#0zAiDxHT)|RLW;(v(@dp$Pj<=fxp~vE*{f}`y%RZHxuE;HFzU~ z3MZqkeRXD6_nmDAP2>55qN%kHoF_MYrDV`O?W~XaY~1D6f4}wwK^{Q%K!Uj@~nQ%HSFHGD3{XJ=H?>jGq`LES={s zfp1;+pxxAxpy!IE5RmNopgP?e$!#twAm_>I{^OU4p6FxeNmGi!q~TXzEW+iu)nvgB zs;@oMLx7QrkQx`(r_wLnraxbLUD&g|R9EW`@nUtQ)ZY>Q8h)awa;gOU1wltiyIlOvlG{md6fk(OF;R4Yn2r1TYqJYRyL@|b6!$Loo2v# zMe3DKl|9CxbJEDAxmbgI>wY~)xmBkRjvH$-hr~tzN!I!aVQtExZ=9L+Ufo2uEPtK? zzx}j)+Opd}MgpeG2Pc`lMii>q5Az%@vWY_KHt$d~x3!+?Ycv(B zYk*NT?~l~8U*=jeIi#Ad@Wx&b&vw6l(a)%_M_=Gyz=WA0nU&K&V%(4ToxQI%jNY6v zD|&8g)E`;>j^Jj`T)I8A^EUd_TL1pi{3(!I{jq=@Oq+&~QmDy|#!mVIWYoH*HtL9i z#UokX`pG=BJMwo7QjB=bt!bNwFLFX2RRIEmf*q7;Jjo$jjd$70Vf|ecgI>)0RLx@t zta&o74Y`ST{@6VWs6nH_@=n3cPRpj3TcOd5DMliCOiJ09K|$~N2h{pB^ka@w88|8^ zci4SVOp$Um4o@qrXPIL{UxZ^idSk<;`i4<-`fheVH|QY>32l2rK|?kI@4~NAtAWYK zjj8#p@i@$^4{^J1ta-4>a0W&8=clA1PVA%WArn>$9_|6?0yuxxvbyUUy_CS`NkhJ1WoB`=@dQCkG(*+*)G3z8ptEg>? zG_j=|=92{QcU3xZ#9RDA@V<00IKVJ}fC+l~FEae&(zFJOeeBV&4$apy6_YwY^D;4c ztJq2JkaoDeDexerf(WM(!P+WUC718_m_4n{1`#C66E4b2=}~w8Q@g6dlZ{1XlXMe(3%mJ?;Iu3JYx>#Q2pWvkBZKd3w|S<;o9td)Oz>X2QgrQh-ytaB%51 zva4@{r~-FlCU78=e5D$C(@f&mi&y8qDXW0BYHy7OT-{e8fiI#aIo{n+JLn2|!Y5AX z^_>__N=&A2g1c;GBr~4sLS%}AH*^Hup`Y=+_@S)VA;FqHHEJEta`4Q?tczAucMNjF z{K2xiPcE8Fw*UL{%##@7EU*Gf)WV8>;_ea{BP2$BBB1|c!~n6TjJAd<=(p6+T<0CZ zinAuj0J8@^Qn^v_0Z5wlV*3t`ZQ)Hm3EyCYJM@X%Q(tu5)w zrw$a;OG)|2?w`oTSPSgv@+gyZ5x<&YzkGe)m~1I)G?zOfNhCV{9Kd6bgsTX|Iu!#i z@l((ZC{B6VePTG_C%5H=T^cJ*PQOE18nSC$d8=QHlRgB`wNd3mn{&qPbJ~A)bfKba zb0gTI^pk~RX6@kK$`8-*z_gNSa8s6>{=(JvLdtE>Bq0h?i z9WgVUP`O5%siQ*+*Z(3Ao4vM0-b*IvgrPTu8-O+W0D*UAQ5?zg;Tf9* zpoG~*<{(EV0W%#k{sKRLp~Y?eI8Ig>r_OK; z?B6cY{L0(oTm<9Lw4lfXViPxsley~cxi6#;krmU4s$ z`q4CZ6=0W|E9JaQcl6;Nu0M$8t}92DK{=#!Kz*M&;V%xkA6OYxJQFD*NtLrx6Z0QO z+#ci4X3~g@`7RZKn(W8cZ_y_R669ekGlyi3Qi}n09 zh0mk)+F~i(Gn-Qtdu%QHlM8tGaVijd0~$gp!4iB%f3hi`H)Ak9_q#82V`SeD9C6jPZ%n+e9**>sN zaEam*jbUGHJ+(jrE#!>m%W0uK_u-K9R_e#*c(=y_t%@$p=_(SID_8HtEuEp@^$91p z^vs~kjoc2uZ^UO*vk%E@4(qd9>~Nn8`Zdu0kmr0e?DlxFPHB=MrEXUOKN|f9Q|B8E z>(g8!_H*caU^5BE>GX4gd)X}x>M5Qvj_sSpo!&_(+n!0>eZ%L4?^mQu9?uM6x|pGh zU{QFI4dL-LOt)as(&NTUZ==HpYaXgRSnaj9i3NnXu_*PeXN(~e!p|Qrt>_L3KW71| z+eHb$5_MNoML-N+O%&cIm-1ZRdvGVsY3JO|LPV7lwK-f>hX#!TXHO4eoKSJl65(+8 zkecM7ryA_p;+{GBCAdbSjk>@Zk_0>+$I(Esx~J>o#aF<9#h%v%s6+RwHh!QLO(9POHY8~Yd9)FEFFgVHhcfdKe%cLp!# zb0ACVOmDNb<>ODMSEFEAJoBnzoD@)Q2SSv;j-Z%0)B+b`jyYK`t-xxlzO9dK>G{rb zx#cpL-1=Ge`%_aV>=D^B@&gchC^MLSv2-j_N%t|_cJQ?ZV{R5I)2;jf!Zxkj?hl%@ zDEGc!_Ae3otP4K;h7w4`vjH)T^c0Irwvm4-$3C;J^-s>I)5Z`j-OEU?czMAqjA6bF zJLcb5NXX7A0hwsnMcU1G9gPnh8a72V-p?&m&Dx?jY9M2TnUfw-y*bmLf7DR~Vi%JW zy=hCe5UH$hN7tp$wG6$T2S2AGlA-sJWDtyIezh>$xAge3o*5OccL4G~P@3KvQjG30 zA`6kP{1dAZ3|q_UFegTE^w8YFD2fpGVk}4==??dlm6=V2_Tq&QiriUmyisiTOzGBd zG;a76_^SEljW)7Qa$g}|*C7;ZjzN>}CtV6FJ%?W%G5P;ZY(sSpn^!zSMC2hw^#-Ws z*CAR~7m|hZ8!ifp_yDpTIW)G|)njq^s4fHmR?kdj+yh*$<%C`C6i#xd|#~XwWGI@DA`cB9XD)w{$;*m<#s5+O5(#wy6&4%b>QzV3q zl(p)GL?UZU1i*Hx`iQqV>`*a=WwN)0HfH9m?~rAqIsdh*wORLr5={c5Oi*K_$vf*3 zDJ$LHZv#YQ6^sV;k1tXuO6PL5Qm1kECYV-8+gK|KLf?;9YlYvv+?u-|&1GRU{Y?bS zy-n4tljc9{bk=@9;n+(rl~YlE5m@6liO+F;ZFFxhEv*JTB%Jd&SyEfB9h#oTlvi7~1AuXoDij7;Co7 zcYYgj9Z+Lx2()}(vw%3OKM}wmF@*l5wIo>nZJEQ{|WPw{w`A1GK)Gm7RhtD|r*Z#Z%8RMWUaG3IVls!^RMfwpmSS+4{i zeUID8HN8R48g`M@RgG9}v8(lC4>lDbz6k=6L|O1^4`xn{#FxvT=_CqGE|I@xn+P63 zus1B5Zqf*e*-wS11118bhexFw7mgMi6<|5b0mO~(5-aSBy^hM5_%aR3U$jaHw1O5l zf_AWSP;C{{mk7``uL1@^UKr@pWr&hX@??JGu^pPJlJE*vKWneN?^u?%NCqaj<7axK zPDcrRFxh*~p2@q{Q#NA?_P{~Hdnj>YS>%WfxKoQieQ{)s^%PBNC-Ls$K}%>3Ub^w} zfNr6>8`cKns*2;V^V%k1Sn#6gQ7wl=oUe_m_I#Dosp&DdG*JNeY+xWXa9};uh;pWW z=^_k^_ZG9>d`iYYPnOh7pyNQ1my$^=%&&mq+=l6Jw5h`RLRzE6i3(m2afE_W=p}u% zg7*gu;hcp!36phaQgqha{``K=z+XfjpzHeuXJD$&&+-cV;g^Pst5n0ELIGu+CCA~{ zvgA8|__(=k^5ix#_$X|Z?8371xQ_kR{|f z1NF}dTlhy5_X9>oq3-dpF$NrgDy}f11VsAp)yH2W&t*jVe&+5L^^x$H0-s+j($8>6 zN=4(#l?C=Sk9|iCXXYlkj@LpI_t$M0#bx%*lg?5p)x+m?e6l#q0&WnnWsNv*y zOwe`5&hze2=w|)bkbIT40)oL2Qz4R^ejd*hp7}sbrwW$6HY&% zZ_enG`X80VDyVIZwia3}fygzj^qCP`*OH*(hm#lcwDC1<@ zmNI;Mg-RHmXCtok4@?!tN~W@O<~jSj-sE}uH3^1hQq1}T&>ZaUN~u+^6&%4;gA=~T zg`FYekw=o}C@9(!y9yY=Xr0<-a}JK^Wu1i>UfwXh#7(eZj#gWM^#v-06E#*2D-rx( zdemV6dQ2tRk{yHs+BbgO06{SC1{cGOs6VEWY zsuberhd@yuPOMkBk2X4C3Ak(#Y^V`%RNvPL*`azSZzS*1&-b99e+JpzkvW$}U z+1e-c)b{PPlnUDH7F1Dv5=T}llYm6Wq83s1tkF;w&7#y%Ng9DUSP$|-t+dD+`zz7! zU35;O$;^#(0z&UYEWhZljT$WNK(yKRCqyP()XddFPx@IdsBy@tz<5`ZOL+&|L+4u>#@8%c{N<11on4#aFY00zXYH>UZ{*uDi{OvO$N>C6= zfxJ?YK?;@NOR!=%FGixKcwDJfRXh}T&95$8y9Jq2Sed4mCR`$62C~rbLPK z#>EfeXv(34X}&Bt*@exs^V( zX6S72zHd6E=`X(E3vTn}tQf2b>2^PXi>6zlkh`6GkW=Z;otyV`m?NMTm+-OoPny7t z6!W%2v~0(Q!&s#WhVS+X>qg9%Ib9%B!Cw&q`U4?PkLFczjH2}D4yJ)go7KqVmVajg zpDnO41*0DB>T_|&)(eLua%sd0(6!;t3e&y)eN%SE=3TcRBn;Gxe?e_u2ajz5X3EGS zB%QXeu#T{cuw+hbr^#=Rfb>JKZ;5x6?v57FY>%?JeT^7%|X-W_Hlg;}ILC3Rr6@L)k-GbvoL5HJ+v4n4gKOB)w za(FVvfxRxR-orl%@D&NqC!~_23Fiz-^cC6>Zv0ccE?1%k#vg>m_1`N2tLNOE=S|-$ zeXQtEB+b>JU_ETFcuA}XA|tGC6Af#-+1ZfD|0?Zqm&>N}uSF2o8Bo{4xA8q?q5u90 z@|Q`T3g8Lxu(rW%aC<|&YVC5oBj&N42J_mW?R#UPfmyXKz`e({2a zGtci9_FK&Owvl}MVT*|X_3)dh{i6VJOU z#T3dj;yR^9l8VgCee$IpY+<5dRA&#N9+;d9}^Mmiy)E$nzCm-l9g`F4Juy+HftJywjJa7LnH@|{NIqI zK*8W%z;Y+@$AMbjX772D@0^ct+WXjlk7@?td_>Z)0Oa2h%|R)4p&#NO98;P%{cthn zw>W$>nWnyGGk4CqKN^bD>=+SBxIee}=k>xj*ET+F%*cXJ(P(_w4>7h0SIaOOI93=z zE%tXkJPuN4JWWM_Rtp1_V<`@-&3`R9Uk1bKFppszXj9b;)d<4(IE$?}U>n-KN9%7H z+<_nX?IS~6LUNsBv|1N`wOS23-Jh`ZVopjMvl2st7S99dFB}h)yD$=BcMe$nh5N$_ z{ypGw^Nj>pEqUS-^yAkBcOS*rAdd}-WjfYM=d%u&=-@pA%5k@t;V`b(L%e}C`k+eJ zJxMWq+ON-WHc-B3<{CP`9|n~aEb&r(wpb&H@@Q6i115MXvna3}F6b?C#53 zYW*$!c9jR%^h8X$AH&e?kQ_LtGV%2g&1C~utD7aAU79*3Py9KjyR$Z9%H9`c*Bk1N z?2qlArimy4)3~y%L2`c+OrMt}`Rv~Wg15z$(}N`zoXhz&ONxK82B+E!9X`#_TsWuk zDX%Q6Hx?}|>^VJNH)k_62`O2MD?g1(-Jh0orj```ybYR76qxIaT%FLCdK*yQ@QVp==96%3l_XpVsE?*V!FYo6|2V$jKT1 zi8fXh(p%2+wV`b49JKtHYrZo>W6M59ru${NGi_~Qf1TYcZ$FcviIn^@Uv;&o#+JRD z>-%3hQk(uy^<1-mBO2ek1rpnp#m=WStBZYBhy1l=jV5w_#(d4zo|;g$O`g0BrP`;l z`Q=>OK2<1#`R=(_p&sk8T*|N%j>_rYjsJNsrT{NjS$s0}w7==sib)qV!Ck)F(*5+= z4aMIwpX_tSe%X(Cfd_AIJtn!Ai?}KW^*=zuR>tIqE>p2BvPA-we%4$<*BL(iB0_*v ztzrPfOm~1}IJUedyd2!U0U|}8Y^P@yRG%#&5(mK{q2P;Opt2XZI~M8q=Ht+3P57?9 z{HQwkVI*?ETGMt99?F@T?9)B{PiTI9!b~{^&ylPN!+*wVr>Xe~`WP6gQC+N{@rFRR znZaDDhmpQ3D#fxM4rU~lv(+zZuiZmOtMa5BDXujRK?c3yEsW)vLhce8<^Nv3-uKvA5#C2eP75^SrRkJDY{lx)`*o#5VS;R7OhzLqP?2*)2g(P zqYu3g$D1?WJct&8^N2;Ks|O0RPYgixnX233%gGi?&&@=I#2LT#ZW|@0HR%Kuv}bX6 zAJcYA35OCf{MGOh*SA57!qSxK5{xn51=&6AUoM!I=#n=*Df&+hq(-~IC^xa^>nn$w zRNo<99v}`YuHy`8eR{Ul?2l?Xz5!-yjga07yRYFMLwJ$xFL4pDYT>fHWC7LhEi^a> z1J^?d-mW>_`?dwY3e!l>hUkhknIiBoYAEv}wb|j%C+rLRsSpHT-f8Z<@)kn=1o#^N zyFl3BQtTxdT)B2m!wkNU5~oSNh_3F&moVaKc9|6 z5UPj;T@(InVfw4B)sf+WMFCsA^u#+bNIg!PIcg17+szwP4qO{J4jWgY&lb7(*0cwo z_LJ*dpoCM9W^f>;l8Bz1Vr*<=i((Ml8q?z4Y;*`-rtezI$T-CulkHmWQPdesxQt~H z$1Yyjq%i&Zq&ITNsB2*66}N6J-SduntjcI40FfUrGkNCaon)5@_nQ6nH6eJ+cWAbCEDU zB6~z(!-6oK0%O2}bD``Mq&gvFTp&BKxki9lK2(J`qvO#J;&V|e0v=9N7}wC@2P5`` z1zN|L5>RzYFf|z!UI1;LuZLWvd!fNKTpeEmoW#TkG7go!kA@DK)dlTe7`c;ns-e0G zxIx!sbLNDFW&^iodg%V^CL|(>o2n=g6RvV2u{`<5PtNG?+kDt~kBx$mg@EV1mN1ap z!l!-;|94-a<{x53Siw3lk;QYMtrCUE2rRCcQ^%G0*zyV>@PPOfObkdQNDPR~>=Zq! zz$;|)CbRw*do5yK(o>AwU5u2cRU-RbuGASaVaRK#D7+EQH8_!hgL*>Rqtt2!Y zJydEK)GAek9N_yW!9bRFXw~`uPZc7n{7-(7!G5b^D!NgS)ZXsu&48MT$y|ydY3}#sy(7RD4!5v!9HJDGyhxS zRlG$~VO>@RwF8lX(#4+vD_CBRdIM8{T@1MlGqq5C|3Ftg5i~031T{$AS1SnbUjkABrIe zB*m!VC{mJo`g89RniA&w%sFN?p*y0tA+x*8Ns!L<2W+A1=NsQPjx3cZqmA{a?55;Rxli#XQ-s~O4vn-P=3Ab9uvSvUAYJY>FStN1y_H9GUX=x(ZectudjK_ zM7y>p#SaRo?m*4a)%3t*wQ^`C3}{qkX{d!Q*WbhHU_n(C`z1-Zs0yl@rE{PQuqa>< zBG@rO%uyuLC|_s=!YWbFB5;EVtYQD9Ge|;E0%6Ee5~Xd-LQ2R9z(J#&P4KCB7~KFG z%41yZJ&K-774!CmdMhQb8_tH@YcE63?{rXR+47;^^nM{jW&ik3b#yfF_r6}_vbL6P zs?8n>wONo9R{R)=Gzi{Wq0l2|nmOZ-moCuJmyEj+g&4HBxvUpe;Md=9(peDrm3vF} zGeN8ry7>t?XuHqAdk@g#@u+yoHr9ZI0CGE{bH|2e)r%%w7YYp(riYU~NGBZN87FOH ze2EPs(PbHFOPDJ;jan#hWSA4L(<}$_XCR@P#$qbA-5hq`fLLFfHLaGP6PdzhhWO#vD*;<#W}Zu_iMTuk+lAbl-C*G|efm7OvaMxYJ^E zg;-7xvb7L2MOA1C4w*T!1?u}P^>O8ScOI zZUJtNn4&SO5UNMDi&G^H#<#F13IgAI zZhM!__6HG1eOnpRsgfLvOn7?4;CVwhi$Am}f1nId?BkRyb9_WugO~tXgh?Du@rv&l zP-N&_VpiBtPbzyZv9UpA2?DK=8XO~0rt-YuXjKb;s!ka+GenT8@)5sB?5`wn(g9pj zmFha&y!y%=Yi<9aUm+CcoV?>DH@_1eps63t#bvMkI7|n%U69CGp&Daz&R#{ zjus771``aORt%jmi-sg@doD2xv(=ya5{tro4y_I8H&l6Lr1ec&FNp-wR5_}KJ{A;s zdT^2{JbV_>a!>UI(q9s+ow7ev58=UAP26Y?1L7#&K(@lwnN&gaE z>DZ?nJ@Nxoq|R0vZr>?8z1NixUwgA?cePvAObwt(GVeVMzFiT`I)cY6)#hcB zC3mukB#L<9hrbU^4G;zb=V%DniD~O`$RgFLdC4GAEQ^-!{e+}S!ik~;Ue2G-6Knn90(=oN2tqRz zPiLCjAJTi}kgAalL=85xD!3S8D}C04)}IEV`8sM%Rwxl+V6k|Ag&7=OwvZ?K;Fu)Q zZ{aDUNR$Br=3NfvU|blSio4)b7J-k(Eb$1{_*8$Z6e8tl+$C;>SMExaGGE5rPFjP% zV7h}V38BXLkfC9odDm&A%7<~t2?>3yDkFM(U~rpx@LZ74ei!Mq16yh4|1a?d4*4M+ zZ0=|<3(aZPO}Axj!4c&(2nqS)_gQlTl(XR7RRMx#5o7GDe@Gz37|)YFfU&GXwK^EP zUc$34emGNrKn%MT>Ob-t&d5Nxkro2O{{+MV%1v3K^+RuSfO3eQ>@LCj50lG>s8LG) zZr@=mscdF3`->Yo!PYE8EhdNMBBb=8rB7LIKMtHoHVEog#1ueMSv)j?KgK!p0>G%I zJ57Jr!=*mof945Cz?)B=^K!=s3got`9Fg<4?8igu%O4a_3DP@v`0_tCHk!H>G(Q5A zDS*8w3RoTe?%-)5P|T>2lc3K|koyIb08<9Iv1dRgtPcdOk?nj@r4d<$RN}!EomK<8ep5EQLa>8-N)L7f9?L{0f2iMAG>>l#tP9Fing<}{g(6!z zM=|wa57Yp{FM)D#4UPn;zQGpRhL;$0m*{sg{B4oLAOTcqAIRsaas~-p2yFyk=7n6_ zBIfORO))&pi(Dr|0K^_a;e&$AJp83e_HpUYagh$4>j(C*L_I05495c$Tjei zLfX)cRs#zWqtu74`=E9oYTAd6`#{}&sJRcG_8~1L;^@`_oFM=f(IyhWB^~eFZe&03 z@Rl;ABoqJ786yB803ZNEA9^VP7}7~chDU}srbsZf3&Y{?GzVdb5~Z$YDW^b}&n6@_ ze_Lh=5Tzg`ZA7owB1siy*hYbKDrb6pIjRmL(#Pln591ezAdUaiC%{466i=#y-RGnS z#Y4`dVcKWX%^}lvwr$(C>v4hYwr$(CZJ-p&D2ykJbVNq7mjliTPLhE}s4+e(N(T`I zgb+fEA;us?1R@_2pdew2=Rk&{C}W5re?&+G#1H`?L`aAs#26Y95s{z=)aKa-$bG=y z2b6uF-3R`CK-dTBJ`mprfY^tYFk1A1b^$Xh(%*vrk1?^y1C=A52jix&W1qpIBZ>|3 zHGpp9Jc;biwwN92mf~X{28}8?$-@RHB8P*}KB(DHA^$5(EUT?GEw z3|M*Sz4Q{0&LI-RanZ@P1ehx>Q0g`*El!sRXcK=aEZdn#6yXjM;NxeMkj7hjRrn&dVBMtK`e!{DUb@Vs4lMj{ zpdrpsR=6Jk|7%@|z=S`>e@xj>FOK)sM6k-!-Ko{a;~l1)xwJlGy*$2Ewz~G@%MIb! zwi(MS2N&C?xu#4|4pKm;$6j3uaQZJ5^^@GVXU(YG(FjV7L8eMlA^KqqQ^>M{u=)#- zG+Re{4jR#PN(W9`$*vz8YH328a1*mRHlYy-Hs_gfMncq{xK=Z%e-y~hynu6>vurjAXKCT<2uqZUF=u_D_ge&{35H@C=ru* z1LUTWgU@1im!)xHvHK#}W+xlkpz3oaprigx5S$bQ0QfgHlkrR)2OuEv>)!$LlW|iW zlVD~EldfVBll@u;9sTV?&-S60eduo=OwZvk|InMw_Mw-3=x;<7vH`PLO(FsU@tuQw KPq%ze0ni6hc=hW5 diff --git a/src/Nethermind/Chains/lisk-mainnet.json.zst b/src/Nethermind/Chains/lisk-mainnet.json.zst index 598598dfa3be25257d7e54795fd950f1a5c9a438..6c902a374cad8da5e88f7668c8ffe7eb5f63347b 100644 GIT binary patch delta 9016 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 delta 7892 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 k9B59&J(umC2hM?ne4NavbAr*h$R@&Et; diff --git a/src/Nethermind/Chains/lisk-sepolia.json.zst b/src/Nethermind/Chains/lisk-sepolia.json.zst index 88a36f768fde1fcb8d452e85e63a42a190b83339..2d100695c7669bf34681e021a45a5e64e49aaecc 100644 GIT binary patch delta 12122 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 diff --git a/src/Nethermind/Chains/lyra-mainnet.json.zst b/src/Nethermind/Chains/lyra-mainnet.json.zst index c2f409b139a9d2e9cd3406c94874e65b6eff139f..7dc989ec6092f02e8d2df3e350911d6dae2a61db 100644 GIT binary patch delta 10041 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 b{J&WS#7`?JDN`#V6HJ(61}ixvWS##7jl%G- delta 8714 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 diff --git a/src/Nethermind/Chains/metal-mainnet.json.zst b/src/Nethermind/Chains/metal-mainnet.json.zst index c0d9dc63466d8d721e5042a7db1db42a9080988c..f422775e3f91beee582a1980673426c679cf9476 100644 GIT binary patch delta 9205 zcmXw;Q&i@U_qMZb+qNg`$#zXPW^z;0CqCJ>ZF{n($##=%*Sx>~TJQJlI=c2g*a!Ez zcXu{SZ3zs((8B%!t%p8?hE|ONDP)nr#eD578;W)d5#22gV5|zI(@0dy6RHPr_%rN~ zQ`TDYujYW!6ivCWQADm_C!tTFHlg$|Bgq#LhWRlX7{rHyATXmKrCA~ssBy5GG9x0o zs%+$&)4|>KL^=RRniI2toTy!%g>Fntgaz-DlptZG z-5bRDy|lH|^TaXi)65+PHrNRl4Rv?coD5feNNZf$BF^?VYTttkM+Qns&I=Aem&cE{|Qkg{koG zBO)Eq*QpFVuGyePBPW%_AV};e_;A=0T?+V5QW`vL)*DO&c?fC^D5RNsaY*PpTEjx} zOrHPE3xu`w{s$dV1_AYd3k41UOc-?0LTZhv8nztpRT{)rQO2w!>>?D{H5d|@yOjPA zXe@ehiZtC@7ICVR5qfoaQw*L%(o@T537O{@1w@bIUrXb>IkPwmZ!!ckGa}B#*qst3 z!~Xg@8?80C8Exn7=cNUS$sCTP^w9F`I}n(mZda>=mPMK+?(e%%dhAbu1)JLm-6yu# z29Wt*UY~_wxZSFnjKYG|`K`aKbMwSpnVtO}nxpOl{EoJ*veDL#ty@oWQksL|N#N#^ z-|4KxZx#cIiq*yScuR@wgCZ^5xqcyVDw zWHdx1OUN`caKeyky>~d^4StRAM+`#b!N#ZySBwAJ*9-l{l$DN164DIv2IUH(ker1f zT9pxO3k!>fxl&ygg8>-=kBy1*!Cm!Yc~kFQFLumLkAuM~!Tpzio)!<20lgtzFa9bb z>vJT*eRg@-<1^5cao@G z&tU(92m$MT3vAO?j#v4~W3gCXSknE;x0bH{TJW%Y6D|`5k6U)WvayGjrh(LOS;JXy zGd{R=P+`JCGO5|EYpwepS0|;jJj`eNV=giB3=WrZ{BY&4xL@;EXX~$?-;FY%ITr~i75=WWEhv}j@2?kF{v(>OKGf(*k_i`9^5kezrgUPTVvx=d(Vhxv zImn4VTpVgDWc&|aZdGLO?DzC%y?yA5e0XoFZ2|fJNcKPI>`t2P&h*ydG1?lpHBlB} z&Sbr^y&;o{prO6egUiim;Ih{?FX;pIPreP33(bLW&b)0hNA>~Y($=#n^=Win|2ImIpgnlTDRDN8D}I$1is z@MBo?dPTiwej`QMIT5O-O`wv5?>iog=8&WuZIL!ViGYg|01eQXwn+@#z3_W2!OqVr zp-&7cWy#m@YK;?{uXhgAXJ~tGR9&aiRU(h^oaL~H_O4c_78=%y8CJTgg`%%gqfJR@ z=+hdW$r}ChK)_G|cJI^8JQEm2p zZtF@Tzu{g70YH1tT!r(btE!Hs@GR=Kmpxmg?UpdORjfM)hl_%y3N%rK+RG(uQrt83 zbL_{iD`R5S!9=6momx$Fs?h7MQi`=ewoA7{Yq21eOz;9QPsyP@>I5R0X@`JQy+OLdt&cx@+7kv^l|$G`NLc569a5 zdr*J|u$GBE7t1@LZ%RMX8EU=c+-|OYHdpKV;50p0JNZ!Rxs)_zH;u;EoH#5nR*nhI zabHsS5VvETIv_HM%UA|7?#E5BtD?Jhm zheuU2^)MB&OHgmw=sE9BvF^_Q;NA8878G#>{M>zDKPGSwaao2Yx!;SJMY;!*;Ka5`leBnbX7F%vu}DO=x4qaI~ zq3mg_h-AB)lu5s&>k(cEG9QfS_Hmn7{pzrHe%oF5RhC&v2OJ|} z6SGU!Mn7n35{XNUWjFs8P+5|@J0;`>T}o9qHqaWXOskESOB~cbbYfh270R>} zr==GaC6|Q0X&RM!nn=~XMWZ7#iYkVT*%i+#w8Aq^z5GHN7s00&ZjsknTQi8zZ|1L& z>S)u>3HGc<+23Me9fZ^Oq!m81P+1dOIvkxJMVZbGlAfrKHi0j_| zy*YtTWHk^O)o*L|@bK^&9Oji%@(xFo2r52|#5mo@ydxC&EX|=|w8)yQ!?`}w&QwXmFI=6maZ{kq^RVJ3bsStadHw8BOt>&K%b&IdRF2cC@9kGf&P11f z)wU_+dKnXSD5w*hs9?=4hPzl^pctt$r|HqNb&9$r_@Yv@{gJky-o8I1KY?Q;$pRm`QLQEI}aN9&1fRU-8`z;GdB_`X8}m2 zH0GRUC6c5?_Ddt@Hn!|-keEH=dfa~$)5^(2R%j^-^B88OWl5(W`DaGXl~zW&u3T^! z%x*CsKR$@qm4pU4;j6NSSqqe-4tU737Jm^m#@ zzIk$n%Hx)a0%VG*W`5(r#r)$6zYB+yMTFPlqX}y;Xm1j63%h-QLg`zn_Yir0eui

%E5tZxxp$%gSKE3Jxw7*d3RuYJ+$!w!APFGzZL_gmjRtue-6dt_{v2 zZ8ZTh-b()#woofABpjfYLh3atH1nCMb^8jBklas@aaThI& zkgWS@sn3Qe*r?F&)DLn7jixmCX1h7EyVP&X$)`Dkym|yjm;y%W9(>(2RO@TQ*ld#v z|5$iP6|j;1F+HCQ+m~B(-hDN2Q{-Hh(r48gtZ4C6y%HYj^zs8XZH)YQrb9EhPf~z{ zp9FSU?B|Jr;x*@S^FeXHnQjs#Ykvt{KOU=IGB1~dBT1Vt^TAJ^y^dBUI@?J^pMO`J zj+WQzFGCMzh3z!oACtLbfBvR7Bd8heG0LQcp;G!({AOo|2eg!vT9oI>`}KJi@V|fh zBETjb$Uqgr2did2C(n`a#Eh{7@#6t9wf3$I$*djkgCI~(-K_EXUCPj}pTnY}H5W?* z;lp*7_=TJdm^ti4yI^aoOcmP&-_#;}R1lx>>gMj6=>}n4|Mcw)sNB_VAvM$&96dMm zu5629d5ze;6=P|pwcCi}KKDdx;Dd~9zo+WPaA0KyPhDMf9@jkUMa~31sRdpMjM)1n zs2zQW^A$ZG>mq%PcPkc2UkC7^U%pPe^hK+v*mtL9NbzbX&5R_C;E;#4Ct%lj4c^=4 z6|1!w4BCYToa&3Y3Ye4ro@U#sdpYfbK+a!66|$WIpM9^ElbI`noOluXYx?z)vLt&= zZv${_AN!GA#egORwC!RUIRoM18X;A^@WSe|JZ^eFxpTE01Xtnjph#vi;%oKFogv+z zG6C{#esv1H2)IjS^CD9jqI%*O3pM+1+8K~=D`CE2g+s>P7T8sPouC?CAcxi^#v3Cf zjQ=%(Lq7p){D&*4b&}xmwBjB=k0PjCThJfj9G(GRr%MRXuQ( z&o0{Q?Tr}<^RdFJ!A<1~LtL=X5p}GKc|!aPG1M&69{ywjHv=aXh+ux|sNwQJhn~Fm z?PtVB$=RPa(X)_Ec)%&bsq4+Rv~u39NVG$hA&n2XB3S#r17QkflT%yi3t!5vPQ06k zHa*osqN=-d@J)>{v{gYX!oY2X6FBvE6dc^ye!W`D(0zSBH+sJSynC`CEpo6Pr)$b8`I0ysB0&}vEl^Ubz(}Z znp+9%^jS%I`>?!dX)te6T{;Upar?}&VJnV;lG|Zhs6997dZS0Iv!szLvc7e^zeeFd zT|s`0!fn#NT$w23VJ-we!l*hR3HxLl%bZG)kuw>!0wiHG5Wz#msZW=1R$O8>=0BF@ zy8H;J;*aaRedJC4;pfAa3>4MPkQ};a=BDZK)lU`>Tucryxz+i!HfFGoXa-$HTt4z+ z`{&!0r^{T-ts+;t&P3;1Ua?KSxyWkCxk?rq`>g4os_VI~`%JZp=HN-@avNx=^6!;~ zQ)e$O0DUdS$DNmaP<%f-Gvf@5J=y$5@k(~VoZ}^;RyO;0vklZf*(`^z&`VUm0?`wp zaEJW8p}1+L7Z0%dW%ch1*AyMdM*fKY_;;Q63or|=BkZa+FTq+_6|hs8u19c<2|BU#}B|3X(^IF7$I(!h9jR-OP zxx>fhrQO$QjU^I~Q57<%;zHX~12->lKih;42=wtE3!T|-jD-ovL{oD#-X>-H^Gv8?Ui6h}wn zweRnz37|Gyv46+={+wlThMTDMj+sjxC^lgeGh~_4wBA`582j5Uk7;du_NQI2!uaFJ z@KZE5jcp!*Q{@ziUN=KH(v4eLZDMuw0%SW{#$S|&j6Fx?pGif1@x@@%qP^gl)SJ?? zBwNR>6i(X|Iiiis)Dw1k{v*{SFf2R(vcodXzDFAjiXqrjgMl~H;vW_jZ<_Q1t{D*5 z3#=?{gXIvjf@;58GM1u|uSObh;6^(~;HIt$z>^T?C!!%jqvmxv78nuEOKr0gzD**< zp{gJby`(%LvyIxi&x_0Q zJNI_(~+o*H(Nk5H7a!h=5+{&(c|T}ca1k5h!0Lk8|(f)5o)mwN6B{NQYeab z9NcwuA$EU*&%-yr3S~98BzSe)?O7Ui_zUe#AifzEGL^SIAZKFGrX<3@AlF{|Vfrk&I<5{0r&^;&EC-Y5pM1 z8!1Xyll081y7D4mik@t`A~o&tNefMPgZ!pWp2W2kpE&UN}3AOT6NATW_JZMN+gOV^5NXlNOq6HPjd~%xGNO=2q?*i zn#@p_BT808O6k&IJFNJ=F zsAMS8^VOB8aROo0k2k4RZgmu5_ocBloyQV(oH@*IpQUFJR>g=?L{u@^zc_tmeFG5s z_;X&jvV(RAWhD@^xsXA2 z6>r_-1wOFiMb5(0ljeDH_N&m^bSzzSNs3c-uTf)OJ0{+8p3vz%X^`g#a?~2&zro~z zI{2*D;*0dho~#xq7|z20Z;1FmE`8+c;Whk}z1253xn)Nhr4bA9U_qp%ERezvvQsAg zND4i*dr6S)3_7U|ydR@(>C@Ubyl%0^b@3826YL# zimI=2${^Ez?w9_W{XUHLwe^wq7rw%l7{a~w^0@a>Zj1>~kJVduI{plach1;FbtUGk z`uCgTNQmFCHwBt%s{f!)>*!VCv;lGCEHWNbT=Yse?j4&|OcSwH>j1m7gsJip=MpSg zgO;tTZKVk~VW-HQ-P8EP?dh5+m#(rUj9++@QpYwI{Q zBiz-L^8DN;D{<8)BEguCoD9l???2=DUl~D&n+EvXG9MQFv+r~EyN>BCrPoM=Z1oA{ z1lsqi;_}@CwMw4Pu6H2dp8-MygEYdnn;tF>(h=aJUKLiaeT{!t@_1C!>kSV>S~dBZ z)tF_b3CUzN)L)1GE5Q_ljIuSLx-}Qv}GOQT7OGpaU zld5OIncjr7XoCj4-kHYA24UaYcXB>)U3gsI%>(~>hb+3(@(sW0hIYsDxfenUV|}lp zo7Ox#PF7XFWcmKbh6@xRm|q7p!c0^e~=l4vio#GD!mo9oSGP!^|ot#^>ibz zoE8Gkd+X+K<@dv+gX4AGZJcCMj(mrhhXJ7K<|W(d!kE;39(UBvBL7+OWim~FXsm0@ z&gu?(vOnx86Bb zSIy4HQ!RF^U>2Ofyr(@TNFF$dR$fN@YhuFe(PzY8&8K8fJErloi$`!+6OGiLY~wbR{!@&? zr(B$(*oL;B-mXNq2~H+**&>xH63&l+&Ps`pJF+E}5qaJ&~A?9{^?VU$H;;i3X0 zvXvBLBh>a1uK%hp(kPy`UNbGhEC!f~t+di~KAD(Odb(9WTeM$x7T)!TUjt^`CpANy z{fg6T#+b}O4DZXJd87@x5^RYFG1Fl`L*^C8@YV+LC90fd|T6?!*a?K((veb=2Erd1x*%W!j_8-J&|pn12hIg1&y zwsET(`nIiD*Zo|#OQGk_UT+?jn%2`U%Tp0-SZ_qH!&^O1Sr@HtWD(0+Te~b!WV@)J zbfEXt+Z)6kb*i01&(kqHOe@N5H8?I(>^i^BwCG`NILM;Ts=i)#0+v(P9yaD%S&zpV z$vq7(I#efHe|n4+;d;L0L@AsTgJ{S?x>UBPgB>dqyQL&l&{OH^; zWzw}Omy-It`#aT%tYc@*xvR&$b$!mS^>W3!>f_O=#LA!Krgaf2FL_u&+iZbP{^KE( zlMG^5U5Yf7c%Zm^9*9y3DnD{Va9k@!puFM4#w|g=DW=E_YS!ZnsN9fYFvS19X0Wz9 zW7qpG;YJB*&dAvthx7AVr6bQb++?yJy961gC+5Bbij>En(ZIaA7u8wZ?D}DsXSINJhwput#DK>`%y)U&>bAl;VW(K@ z{Vx2OP2%I-U!#rNn!jQLQ~q`3vEc?n`bQZ&6#Qm2Z}SVsfqJv9w^b^XXkGECK-@FQ z+FL3OwcHJ0Ky&i2HRExTNmK+qyYFD+=_nc3j0^htK2D7Wyu-BzLfk-lR9PY%>lvT| z&Q+ERoNH4?B>-n@V+Xz4+n3h*PxO3L{fJMgplc{@vRpQS--|_om}VhnI^X^w`J4Ed zl|283S%8JsTY=#(Sv|VSPp6bLo&6TFxE)Ln?}PD!K(L+bE}Iid@2qn{LzUcgLW6ED z=pCYNB5kR=erbkgGaRL*?5eL@kx}ba8NujYiMq3UO98+f=Y8Kr#E=XY6me8cG?U5<0?(@|)NgdmF}RQ6aqScGK1* zP<{g4=^RVSe&?HtaT}E*4u{Dy-1it|^qtX?fUPIfN*#Cc;ujmM_n`dKlVqZ5QmSsr z8HRj;!XE&PahkrVe0g%N{{2W`148q2S60g|=BsJOPnZo+EDQtqsWY}?v`ax2S$zvD z0qLt{>?U{b7nDPYkLPV0i#WgGHhrh(F1wa4(!rti6AeO6<3#{b>58iXxxacR(-=o9 zRCA|j|E)TSF86s)>(PgH2VoGO{OiKPE+v!#0RVgf5#d!&3Tj!UjA2Wt1W|xS1Y4nk zh-N5`a-o{=*-hf{d;c|_Zd3QtA~Mgd&dn|Ogu2U&NBt4pTHCOsL z(mR_8#QGw0OY@~#{;Uy~mpualTU>ok&N`*EDYRReq*E2#U?cp8 z0t%pqqmH3_QrZ|iQJiwDP>4&7@VB4i@=4r=CEK04kFO$L}t-$ z7m(}eW-3|<*^Ft49W@^!a|?07jGd8=JDr!Iw00(-kPVep(643_de%lpdiaO)ji3B9 zwIkxkxNVG@N>b=UaNAsSa1x=flQhJr{uofs1W^Pg@Yp4o9b$wgWjkp?EX;{-Ik6%c zKH|}NyJ-mW7PKJv3g_cGa8|yLeW7xXXIjDs#gjqOY4EqNxH(Al_@YJ%T?*0p;4P7{ z1`Dqli>{F3>*vW~J=^}ffz&`d>R!PA;(IXSIr5Be(p^uCf#|6@1oQ@Pwgsc{k1>D} z>|HBP{9rkL9m+tet`o@R^nfg$* z6R&+N1%5)t3b#`_`rhLAez6Y^`$312DVC#lwbw}Kq2#-Ak$dm|7EOkmO;rXhf(j9= zNbrAOOgtTJZEZ}<80SNor}t?(5TLO1*NM>BYNS)WlO;oPqRxKqx7uM}Lva11H}iga zc88&QVX!+M+d$S7@zlUcDz6`hTZ0@GTW0k;_i3<)wTzZDkLCyorpw;`B+8ImdH?sh zpyGNlcuKfPc?K-XtOu+B3I>!>3?3LjDgZ+qhCni(17!e%0|S9F9}@Ew60q+=??iVe z|3U6d{wbO+9cW5~fmsLli?RH85cP=nx>X&VPPDQ;R@JpZ4;ibbRnaK!sqV$jNd70*t!~nsQ6SOX#=@;k%n80s{X>DS&N9<&$s} zI#hN6Z5k2#+t#IoWc!QVH#3hwIza^!Yx&OKTuQSLOf7-+4$5cY(%fKi@&tU;Vo2>) zsL>ssZ?xMSjizrgWr5nKP@~@OP#^43#8(U_W$&|5RoPn) zsf19{pN644EHDty+}7NjNtN&~MmO(U-b7+TN}&w@$0Y}8GWLP3r0T<-BBcKm9U2$oTC9(6pG;08!67qT@Am5VVnHJq|8Qf7OZTEsB!(#V z&Pv1M#36= zjW)T(l0Y%N-VK3^K#oEt5XO)iN+o6QuX%?YmE?WU%gq2RgqmaY%7nsD5Fq^1;IPo_ zm?=q!AdGuID;*5C;;7Q#2ikSWzjv;)AV+E7Dj{^i@bU|D|8u@U8lH*-VPF7$v!J0z zx-Xmr6B7+*Sbz0ort^}lm$VC0@ zRAd%pG&oq?2K-lP$S37hawnQVG#Uh$P;6mXBtSm|E{0SZMxek7PgO%VtuM6CuJ&{- zj@67zACALGHn=(iYRGRkg|W2_0%V3P)P&p&TKlw z%G~YtRJxEThPnDvPaKXDF6cdI%4eRjb}Y+j9q_cF%S7T>t@EyD-QDVy2AjS7D6yzxyfkY zC}w-HEkhN$jEhYS;=zNIv<&B{$!T-g5VRs-JMkf}W4Nn3jX=z~; zy=atz#q(AwWGKE==n7XL=V5R)w89vw5+VX-{#{XHNJJ? zV#c?#Mg6wQ$mrR=(eUqnNU`5e${nAR%+I@2_PfchfX(*XyR)_U`Njma#-jPc`PQ`g zs@XG^ty_sSVzkFvJUH41RJgTZVy?%;kWehlQ1#);$?@x(7AwjB&M*oZjz=TU z5?5VJ(a_YU;CXH;lAV_ojPSE-i0D*}jIgm*1#3{K>0um`O6s$(flAQ<0arG%u^%O8 zvfIfKjsd94Y?QAhTJthF#hPk0bzQe=oYs^Z#tdKD;kdr~-L;Qg84fsyPQ#&MOMxG4 z<+G|CPJi9ne)KwHQjte`${_W%7`^z&!x|=0G^FzfIH_W-)eG>PHofFKIpc!_#E2Gm zdp!ky*b0oAw8b$Ah(z!Pd=h8e7IGSk+FG01OxNEV2z)QHx5RSB9;(Uc<0G(es8<~w zg+%E3D5!tNCbN9X$WZpCH;v77S|}hBS{?dXul8^<_ecH5Ur{%9U&JdW)>xOkJo-|I zBxe$z(DW>)*pA02rc79>7@=!!g&)Pz%1T)A9oPDNtYTC z&vnS>+Bi3dV1Nb2LiTmGeG#nBVbYl%{5a-(bu*^{`bwurZhMCcC1DNwS_Y23nzSIu zZ}}#7?X)eCo;6*2ws+xhR#TCYysm~FlZ$FZF8AUYP%QUSEGAb?E~sE@+aw|^=*ep; z6((eQ>-1o+;>gbi3_x;;j)@fIp$Ob>zy&0GDCP#l~pYQI;_TN`~p`<@eb-N zRjKnct03zlQier*IX-<8%QDHa8eJl9T;DG1>v6jSD}}POs7a-Xu6T^Ij9s*;#-?He z-tfAb8f9Lcs#+t4q8P@anjc-xDo{%RjP~}prQE%gF0%sQ@-nz|S%WCOBT?56bc#t% zO2Ivcf;PBQU^TezV@AH;1M<~E^<_qvXvW{fijp-8UG2Ts)FfPo?O%V1cP-f~F~K7E zO*7wuY+O!o46E2Z&-iVsTLW%5MLpZ}Q~Fs_50NHTkmgWHUpTduTw{_`Qmzvflq0o} zmQo>&iif}tSA(SA-wjXv(Fna+@(<<=c?ai=){_oh5th3DREey8*3lunF%;FVRPtsr zs`h5}DOx$+FRA)HScW!oF|Y3v@nH8Ight3N8)TVQaVx)?+P;#VaKn%tNPcxoP&^V( z8YC-)NT?VWH>sMImH_I}^%)wk!D0H4ND|#az^?&tPH}z=WaH|9Zns7g*P`D1<&!+N zv8}x*(X1H{bnr}6Pu8b+W_*WR;s~PW{Rz^2IN4s1D-jaX)B9@KPVeG0_{rv=D%Gb- zY0dgd0VV14?>N5Jq!ty-8zO#;nV8YwJ1iBuU>P#sI@Muf~7Dcan~d|2Db&j2<`SghU@SJ zD0Wm+PglrR&xhF7ThC3`JLo-0u39u_(|bM!(j-4k#-=8@i!@d8IgFx_8&)wq8Qv6J z(js?u8?LaxZMF?Lr~+E4`mrJ|x?ECoy8;(b=ip~mW*19Shf0|(CCgl)I6{`BIkQc( z{4zH`^EJ!8|JcS-+-BkQ&qXw`2<4@S2LP6RU;=#Tix8YItd0nv?W2y`E&3q)N(0&{Elx7=U`Yy@cM^c} zR8jHdfkOXNi`oz-*tTN9rYp}H?JEQVVNg!Sx+Uo4W+uP_gXlt)7?f0{@Ox$A7=LQ8 zV?aW22BlHLX;9#KFv4h1!e}u5nR)k;8OLB1Pnvt9Vltkq8MOr?<@EEtjK_KNgVM-~ zj;w~Rkl+_WM3pGIcP)3e-(eOiB7YX zy0{)wYR)tCPHR%0D zY#T$+O&st}??CYBDnb*h^m$z1>+DnNa^_qF>XbRpE7?vxp?CwaEx}i(73#99E>19u{1Df1(U9#?T^};Zq;=G zfRFynAmn+V5u5iZU7gXxe?36$Hv@}?b+Y}&SZ;prp-aKK`hAvNG3?Szy?6M|vBi+k zuY#A`|C3f}J$Cr$iBU-A(ooro3l?YersZ3IUr0 zdj=o$OV}9zhjq9GIa7jPr=&^m4;*=n#l<)_!C7UyZAZb!Xesr(a~;-qZ{|XhUJdtQ zA$6I#)*e5-IVysH6&0mau19~jGk<2@*>!RbdCkW#*xq-QHJeoPTk<5+3Bc+a6YwkF zXO9?<@8l+)wZy1oQ}iIppc1sX!)_->Sa~)`@?>5~ z$n5={|7~?KR$bW>Q&j^=#e$8+qT&~On0oEuzENAF>z7Xv>4XkaWC?z8BRivJG_4Kr ze(!q#n6RO%p%X8ul1q`IJfoILW0FCekefM8w5o?)In0M&^{4@vjm#$X_Qm%^(yc#w zU@3y_YA-SNQ$kROxRG(a2$D`V?!yST$Fl_sj{hFyNN>I0ZYmKax>vYUWXnabZ$(k+MXUdAxxZv&cw~&&l;HerJFF7OPn~{AsMwR8s>dV3yB2H7lDD z_#*d4Hy!FB#Qe>nVSEQ#r~UC{S`-o7o<4_y%*O7%IGQGHmGn;bYzrf++rn~yX{p-_ zoa@(IiOv0fw~q^zfLMYT4K$Ix>c4AVbXr+q#Kd61W#is*-$!;R)Ixwc$+BqCDMn3) zlaif1UH3~AdnInWnk^Yk=*vidKwudN7S(U>1S97$2_*X(;jtqo%1BCd|=RbvW1{)%*(o@Pyv~nVo{?Z%8V09k={JDo~dS z2|CR!xsQB4j>8vlN6`Kt4X@(CkV({g`u+<83l&3bvI$Er0o9KZ>`M_UHimMRgQb;^ zik=?XdCILXIMEAH-~@?BC1@N8;Nsj51*d6S15wG`Ibm zCclWM^upK|8&u85K{Dyy0X35XOZDQ_!OUU#^TfHYo*IIx6Ci z2jpnMsmtB2v~Kd}{-E_^^^vAfdT}a_y#vTpMjuI&xO02bDAzm8o(i2r?qMReWP5@J zHZ|~Ao{KL-F?6&;N-^*n3tqm0`fcwXB*9WwV?SEz%V@JmNT)*Z(-5^yU28&eZ}Tan zlV>C)WY~Gz&3cM**|u4JSp-J^ zcE!iCn?EasV~MN_Qxfl71=SY33 z(L(YzG;K)Mxmnj+$BXb~;kGv*XAh*1Z&BJGUqLqEN#(-dQeJR-SOrAAO!oq61d1yV z63ksZSS)Jme$>&Fxp}k7-!LBG9ZB>zNK~}Xnq4y6S8dM(H&0`Z>+0E%NEo{Wtd}UI z!Sao>IK!-zt$$(=a{TQ|dT{x;=T37V&XGJniWZ2b-R*q@J{pdm_ zx>sRAaUyc)aC4PoOCliqN-N;_8;gx`JJT`bTS0FMg zFSbV)aWL|R-A<5(1&=RG@f77-I(KcBBIGs!uE~!N|JMW$W5%JVH1+@nAF~eh^%o

NQTEx{_wjG=6G&OpmjqA9G1fk&f};j-cYY8oa149d!Na7v}CS#pv+L#*`LLt0IEo zuNJANE$?Cinq3Aw;!}lx$2i}K@}+eA{794-6G#l*yF(-{9(Bh=R_C#Um zJh4xERp?2*ZRO{j>eYe87{FBCo z);qy6cGyqg$dY2ESoY+98XQ5g%`q-XaQES%D$Gj49q**g!!68r06+)bc1ja0@-6MC zmW=w6%URuhxi?HtuTU-A9m7B8I>}X_ISzNE1U(I-DTXg1&%S#?`LxV&lcK%MF?U?^ z#gz^{2nN7rt5Yta$u5lqU+ehFx9lITjS9+AG{BtY5=^t@b(|_ZswQG-{Bf*Nl z1fop2`jmlCU~xVkx_R4oSM%d`Q?BsJ&;a%3q_wjj))=t z5p!&^y!Z8qaJR4+HvIQPNyZoI${&c3F!92RmRd^wDA%$LZqkJg1CH@itdG;w+bJ7+ zS7Y94l-j&L-DIs&fo@CGdUZn?!t5>sbHG*CDo79RIOXf3;QAK-ftq2ipR}Y)Tgk9l zfXiEC0^RW!f#`q}6AWCTpy>dsP}d0F&xQ}NR23URgI+_ycqXoE3|_F;WxmW?s{%rB zYvDhkFn@m|rFsa*jj}4{aO5Y{O^QglOxphRVkgMD7!^kAFi@k10U;QDlt?vS6#;y| zr5)2xd?D~J7}GfLqukSzn*|TazpV@bJX1o9x1pJ;LlfO6 z(B`RJL1@_#!4ZeFB%v_DNIh*;79w7)MDM&+e4m*yg#^m(-EJ~uKThF3HKUyS1mR}5 zpFO@yeoQrFKVHnNh0=Tx~|gJsn_KQxIq>!rL_>vU*xv2!ydsiB6wKuEo7p?34o=CAoOM7 z47E7SFMTbc=!E-z2iUBeqhkvh3m)sDFy!MSDpy`bmyhEyRB*4hc_r>HSE*oZcZ$Yd ziu{|6tSTv^Z6St-UTzL-Zq){Y3Co{M_-z#SCY9oLawvLuk!BPp|*OrKZiDoD{yvVIM`4a2;TyCLLsZV^f(q!Z0K((Q7 zAZpag?$u2ak4Mfr=Wsj)r-Dr!EN1U<-Ct5v{5;UAsiTKxLx@jsgZL^@Ob?uDIc0U$VEfaC~Bmt_= ze^vwjW^z6M4PM=b61el6PS~LoGc`=)J##Wg2xARQ@-sFB#X@Cs?F0miKdvx6G2pl=5oIXExN zd)5{G!?W}5=s$jbd9nym#fzR7Ium!bT=u5?<7d-DD;R_Fez{;nGQ(>lJn0`ln}>By z6#fp&GCdr^r){D1|M=;cDc$t@UorpRl)(RqX@~Y0FPF`nMAUGN*K&h=4`Ra?A|5ei z@e!LNPMwRTSAT|_X~go}y%TJE?)PZzAUpJjP!4Ix30?khyK~}{xankfd(*(aJ3&W1 zgX=Uj@bijpB3>*_8i22@FQ4aVpEE!hM7Sv!80AH3S;Yrn?r_k;oxQ`K(HmaiP%3XP zWXj&U!k2?1RJ5t0cHp*N&=Rj{*tzkAFUGGLTyO^}VxZ?b%hB(8kNomkm?!Bb?~G^6 z29M21@tNBXJ&5u1jB08?uph51-c?3_mE?B#Tc4bDA9P`Dcg*UodL_Z}j9HWHv;`$d z{Gxbr7k>nFn!A?E^*Dk^h8v3%mT(Xq;jSK)`w%`hds-9>O{7=3W~unZslYh)^O7>F z+X=oKZDT?uCn^*Rk9yS-%-J!N#R*+p5H8|r6!rZ4ZPkKPS2WxuEGL8O31IvB_eh&B z&wCG%&!_J!@)g|P5WsZhg8tKE9SMg-ae{jf?miTta`wiR25)iVTn=$mIBR^d&?(Em zn9S{Zzu9AjYP!WkHVFOXT*Y<%^L?D@raF0$pY}z)sfuLdEOhKv{)y5+?vcx(7N+q` zifa|^w%uOE`dp`hHL_AV6EaW6*p|xG=5I>y^_HIh@sJ)~+y{+ivb2lP>I`RFVAOY+ zCx8)9AD7+Wp>Vi1h)c5A++E|;V@6hR6mUJprTf9S{0bmq9zNu1{>{QvvEan(H;)2R zds*P0O_Y~4)8aNiPqW&y)Ri>NT|HfAlkywgf3!$;T_4rS~BD#_Jp=myy5%=4C4oeopTM@Blz}2sn_DtyD6eZoM zhm{TkA3q=6js<;AWbDnnFRXJ#pECoZvN?dog9K4x##{R8kJn2>5@mrR8ipUn2Nwyz z$e|Fxp%tQ#pq1cshM+(ly0O1uJ5u{oJ5e7=`cc~r!i3txbYfE`EcI?6=~0Ib#Aq#r zX!mzqug!^!p)h0_lIcGu2Au@yze7Wq4+*OXK*dzGW*8-rhgbdFAwB+H=F|*cA5U7u z!`vJre5Prtn)L-ynLD2)MiPEvXcFgf!Gc~IsI94@0>S1Z zLcPxqO(6q3B2Pk@O?092mTl@{$h~-IRyGm3<2T)`0B1)3ECcdVU)_(m_*3?|Ugq-G zXj8gP)pH2$P_Zg!L=#>e5;yJ~EHh7pS)G@Y+29jqVW=J_V!}^jcwb_8IeI{ql~Y8W zS~t&;xUy{$hUg98wAV&}`NLT|I&r1;|AyUt5&rg2X!|ldA$iw^eZD3+DRm-G{k#FryOiU4ncb1RbE5WN1ON9D z<9}|+0WY(BbIjGDQjk1wX$=w)-GeH%zkN-QF|0N`%pZ{y96tKyS9%0^I8N1OIHsEO F{{TjSakKyc diff --git a/src/Nethermind/Chains/metal-sepolia.json.zst b/src/Nethermind/Chains/metal-sepolia.json.zst index 2806a8338b5d72e33cb5774deb02a0977952f83e..d8656b6d2ce939ef068dfacbe3fff9868337fcea 100644 GIT binary patch delta 10216 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 delta 8878 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 diff --git a/src/Nethermind/Chains/mode-mainnet.json.zst b/src/Nethermind/Chains/mode-mainnet.json.zst index fda25cd5316b51a2ca8b5f64b3c69368388b8453..19d3cdf32984a03cec6ca5dcab5c502b094afb3e 100644 GIT binary patch delta 10526 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 delta 9350 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 JUxX8E{|_DWw@d&4 delta 12816 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 delta 12241 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 delta 7319 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 delta 10775 zcmXxqWl$VVv@l>~fdv+KcXxMpmq2iLf(D1c;_mJa!Lvwk$l~sV-~@LK8tlux_x+x# z>FMgO>N+()x~9+RtSLZl9{|kwo#Pc=f2bOQAo~-h6ZeUZj`9;P0$CUdZzxcAX5w4@ zJCy-F;*;_3G7lMCIjv)ycsxl=m~RbmLU4R=`8$`sU4x-@(TzMr-}J^bqNS9n`N>Zf zR;p@3=$*=Ag1Abl9kIQi4V336zun<*mxCRA01Q6kg7s#Gt~u3ZI6Giom01Y!$k-N7 zuI=5(jdABGVLqX>u-$%&{fo^>@3trMEm{0Pc4-Fyj|qp4xGFwmiSP&ng9Tn4Ar$#C z{()$$(QV)8F=Mz4^}Csu#s`7vnbB8HA`jQ3)AG^Xtn;iu_dmvSgj-S!@M;fWDqfoty;44W(o^AZw)fbV9`N{GS> zLL^5MEDq^|ZM^!C427a0<3gbzR#u2P?<3oj{vZvmYW>IhKnC4~&Qf{g)EUG+RANHK z;91D5Bpkwk9M1KX4qv^CJcdeNf4eS8SLZ4d0%{p~GSV_kI~ZDQUh;2Yc-dkLDOO%y zCCN^Txaqn6WmC2-pe=W6A*p3K~xIep0=tIj@f>moE3-8 zse$=Xsyj233a(!Y)JKUz4}!%+35Fs~!Tb6tyw-_rw(Y|%w?q(NMlM%1P0r$Z(q-U~>A@QOXs!|Wh$Vf=G*#}Fq zG_uOFQ{MCR^<%kCBWFFc)+!E*Rn34(O3cu?S~tQB_f~UlKE>iWs8tA|U$7$7&5h7O5(dRLgeoL7B^W^1P6Z3VN5|y7SfDZf4Yj=>@3x4~ zjsQO!6x~@VD|B-2>SA%kd|N;Am`=ApSGM8PwLkR&B@EtnF=54#sJL#@IR1U15K8}M zNNYD*HE}Q4A?i28L^L_EqO)MgwG-;?SQAre7{M#QshC4u%T8)2vLJ3q+9X>Ny}+`U zYj`r=8E3^(!?Nf?uW-en@K9H$nY%XDr71?u~q1n{QS^qRBy>=BAhcZR3N3RD_RKV z>F^WL*7@o+w9jXhCWUv-MKKr}nQfx0;;75aAIUqmHH}Y|T26(ogv_%YTSFVIb@loa zb-vN;b9r7(r=uyiuT5zXT0ug)a-5@XgX+HB+x!0+A+(C;8vdVVedGT`U`hk2!vCk~ z?D{`BIXU2ePfm`{e{}!D^B=7LTweU2$^X>+C;C6O{~5luH>f6Idvv|d8M%RSL8#zs zxvF4q>stICfZHw-zK%d8_BgQP`k#jDuRDuu+{FW6o0JNr-FM4+kM)r*_jAr^2+!6R7mlx8&k!^cy0VT%M1&ODaZ8wYGY^&XXnQ zCE0j9zLC!L63UcdD( zP&PGm@Ha%o6qK<7jj8Qi@=Hv-d-sz`LIF%*ALdPARFmp4*TV-cnKnWMj<=8Rf2*(^ zO}zuO={k%2;>2idLs_F_+Csy+TAKs&^rq98?JTSIS|1*d7)zB?$`W|~h6jQ0`o`s{ zdBs$}*x{EwmVYM;wfl3?vFPcdOJJw>!A@={ebL2V^GtPlm2kCajf8BKVuQ$V1HvT? z7D=1xYs^Wz{)^Rpoom;uVPsflKqP4SDsj6F1U#-2} zR9@{TV6PLPtW}X7G)&KoGeucaNSNDqLdmB`b^MlH2k+j))+TT2cHto6}sv$f)_@rMk;`}0Nl=E+3yDKO^)w!kKL zX<}zT!1<;|3v@d_u=SawIr?CLUcTG7%)}reYH%qt_G~0mDi3d|V`zbfMr1e&=SQk; zvQ2sx6VgmggSCkr$$9OB&dN_WpED>1&45)^&YvQa+{oI>n8~CUx4G`z`SgKJ@Ig(f zo2>eeBMRz96Q0u6tHXk*xbDflTRf5GD#d1yO@m_-i2gJBu0>rxcl-}4n{+2VRXy9h zkBBi#vZZyg-!&caDr=c(s;Lzu>=_+OzINl*UMt)=G4-IPF_G;1ZMb}J5Hs7^F%g@3S!8+C-_?mfA z7{^SPL1?L8u*$K%kRJ@(lPz+0$*S_?=n&;Ty>FZDa?7lX`VC`E0I_HtvWP8t>kta@ z=AsGP{w8hqmG$p*L1qv&)l6KJ^lLSOv_4#sFhWw zP#g+B&;7psvkhcEcKC!(WSthKtlp1m5wpqb#a269S z^B1Fbis-?|BKNX>w;N6^4S6Lk_rarySQI7H70jKIKI{{2JnY;IE-;zAJFf{>zTKmF zqcu(j`(k&!J?(R94XU3CcEdTH{x?s~$iAww$nIhr<&=NB9j%g-2RvReB_%E>N>(~W zk#Sf>0w=g{LT+1&6%+->6dghckirC7Vgf8M|8*jzD5I})c^%M8%T$=+5V>VGMI29A z2ez135#M+M)XJE_Bp=nu6O28YKh7W!ghrZc@;e-b5jmJ|D9r(JkO_La=Wk}h`=loX z7^$ey7M&?v+V}v#PB$-#a+jSp4Ls!4xNY_p!VdZ9zqIn28gFPe&+*0Wtr%TrVH*!6 z7vsjXz{3-AQ>(em5szK^V^pFsSI53Q=$%!}&c0+)Kvc@ezX-DgA#|Em9lO`59xYd_7w z*~4kiw+Is)p=AT|md&THZ-J?@RHP2&P`>9yxfRUWd_rOH4@Uem7hL57KX%~U$1L&p zW}+nIZ5>B5QJD-(V zQrhHSbHydnS}c#IThP*0U%?>6%Py1&h~^ zR`Ny1@MOq}-%*vnRWMS|^_WSq15%DeXlghn3VrX zQe1)#N4f<)W%3{Gu%R4!sU%Bm)AI~H7aWtJRWNjTH+!Pw8*Bm^Uo}r{j*6Hlw*<2PVpjoB+W^Jh?`GYF{3+g?iMn<%B1+pz#iG2`8dx< zHA6`4bteTKCYUw@>nwd|&y5G`q2YYFV^H&-OCk6++C{t%R5J~9_xWq>ndjA%DH~&A zyn$-5?s1KG_Hp*(aiG%FqDHV3Q>`mAuJ+Z^2QFCvXv!7xyP1Eq16F%DaQmaXL?%7u zS22Q}2+Ho&!tf8u07m-Rs0y-o>z!1`JpI%!D5>4k7sU)iooXJD0q7sWXfYFak5!nd ztNHOJ+sl0vpQ&bF3{7pF)ynRh`-Y_zjUO3_nvnayPYj+5)mWX?9DsPln_>xgFZ5q< zOw3$NYZOgJgzvAQ%aP6aSCE$dOOzP}3Tqz0A3HR}i9Ae6AvKfY{7y}?FnqzO*|kKI z`x6Zk4~!&*A1|bRgdI1)%)2Oj1_Tn>b*~_sR9|ctJ8Rjy1L}=uN&GF7oB70PdVHTt z?rw@^Y0-QJqP{Jx4;&L?mUrdVvCVbDYFMngy^qC1wQ|y|!W}GD;{kb;QC;f|HyTGD z-)4q~J%b6PwOZb1N2CKfWzq6GHCl4hWSd5!iILNgClk8$XtS=ssO%abE6AZcqsVvS z=@2rJac1tKAuKUpnsbU9vECj|Dx%n~F5gG(r*A%R!YArN2)KQXU3Kq;L*hXfto$)c zALDVY%(TXb>R0VIg6d8hdZ>BS!xMbGCVEkxX!(BiH3D&u>oh9ey;dd89SH$JjP6g3 z93)a(Ox3i+w1yqvH~i18^xY3Qsb6MFf5A$Bln){2dqI7>&+N6iPHN_!k?Q!+2bj_R zwKJr0Z~w_G!+E`>z%5s5Rg!U+5s6ECO@{_H!q-zpP}%TdW#N6cCsKKiII$mq0OHBf z2LDnDOiaz5@6~ZrJvfIYVD!CotjgZeI%lj&hZX4}k0eG%n*h;|0$BYth3$FJW0-D>W-j3 zycSYgHqVd%OYiSa+~(@k2P$^ODcojf>NEp_SAu>Jp9*xS?gDSO;`j8)#Vu@GdtJ2# zP$rd(!+f&9Y@d*X3X92;cJ9X~q48D>8>q<}yEv9Dr1UeG5muSVBjcgk#(^_8wCPcu z!c+hvkJhHAQH=pHr>D{RH|asNR(VLkUdV-Iv-MT9Pf;|up>pmY}74zu_K(l}pLIEUXW;lkrvF{t16FI~q}b9X!uq!zQ^ zl~}1X+xVIPA_8$e0V!E(yMuk$GFC4|<{)E{dVRWg1BOTd@zf5K-2&j6-*lV8a7Z3N zXR8Zt8vE|0aLV&}3=~vO-?^dJ_2Nfzi%V+fN}P1Z^^Rf7Qsgd|i>ov~X#*?o|8)Y( z^E%8o;`><{)9L`S>C4pi$JfG>&ENR?j*$T6IT{bM!m~%3-%I>CN%LqVrgK+_4mT&u<|40F+1gVX|au$5{OYidcXi=3ly1#LviF>h>aC6pE zCX1Miv^T|;4g#Rn!t2udmBZLI+zNj={jY1e?mfkujsnJ{Kt3^A6r4D77&E1h%_N)? zdwrj86lVgOXM4*krR5iY%BUE&?en9Vc6?blb@@{Ly`#RA{hjpXSiub1fy>)c-aAwQ z9$rjtA-oyZjIlIrOy*75;~<2&6q(zSH81~tS>2Ts-fFA6FwA#N<=;3&l4%U`wX^0* zg}h$BKMFHE>iT!$1>;|t*>sCA7aTTgBkz3{_w>GY<2OT}c;bjvCM#)dpVG4x0$+Rf zmVW&qFr&9IMFDgCWr5hgP(wIw?3V%%%(R6F<0rt_Xp{a^;y9?mJkQ`S_ZOa@Nr9Tk z#ra$BBK+fBdM>71YdWf9!#RT=6su0g7X&Rjg*XE2-*_(`! zAWs$0Xi{mV?|dD^P2BiQOed=qAQQg^X+MBsUU4f~L+$T>3ODd;tiJbZ`MH&30;#c^w z5HvX=Z>EBurv4T;0|2q)76^so`D)crqQ^siCvRf`10U`|@|=9)SQ9b{_#63eFjtBI z|8Ws_hG*YK>MNOH?HccU|7)Pu(L10-Q?vQNvnu8pa|cn9?$aycAGVY2z^~Wr9qff7 zj_+BasQC4K3>@>es;uM14Q%60f)WklWo{;@%7m&TB{A^Sjcd&0-Ir4#Xuz+o_*OMA zmwmo!TCx_IJW<%0^q0JP;Obv|J`q|BRtS-4nPU8|h2r~#jjPe!`J3LT^`|6-*isic z!-ZRvaN@CU$NYLlayr$v@nqjec2=bx4Avxr4F%y3vYuI2%hPw`R$TSfcN-ttLTU+$ z@5xIr%!V7~Eeq?14HH#V9J$`x+KfOecv=qxX(JIh(`Hn(GJfaY;*?u6 zL!5djW){6mzQi)xt7M{ZgGzFLWjZx((!We%QeC$ocy*(oUTK1v@+odc>C^T^n3Lih zKQ9ZVQeJmq)JQfrc*U(E3Li;$%&a)6rH0Pv3ndhkUh8N@i83w(lH5=NdZ0Arjry_XX^E>MvB(0ThGZ#B^@9^grxhhcb8^VjoWaG$3z{cWVjXQ5xm9eYf*5b~^6gIs4@T>P?^#bpSvpijr}3K!%&2}R zzhKvbimExB_m=}2EC;FEjF7xzYQ0m(LYuU><4^D6&n-1=4L=fYDELh2+8Y0=Dko8v^F-R$Yi9{QM&pyNDYCyz3!>VPt&# zmk_Lh3liS-xn9^rlDtz$qlFKM;XuTE9nuwvCemsJ z*^N99;dOxq4nzvK3M^w;{UPIZIqHQSAY0>GZfpb#p_2$4^_Fa-pOKtZHTp#*5PEMF zZztZPxvei7M5MO!^13cK)b!R|(G@Hs6vl7*28@NS`T zN$>GFbcDU5i`<=UHZml>K73Y|-HUs1YT=(Q5~s<JxjrL*tuP&{LTs5J?Z}s?F>py|=dt0)v}u`j9{gQ~YA!dK z{z(q;F5k{ZB3<;}$LgjJ68iu}#5?Ll2sBHY;05ukPQc2$XEgB6SFI>t>CpAS9 zIh6z!w>O`be+s)Q->%_wX=2g^t%H}+d>3FEM8zZJw3-MjPOXUuP(mg~VOdGPG@vcs zu4QPEqfw+a-YwG~ooY34HITO1nx1%H$iSRi;(P)`+lPvP{Y#u9Gw`-{8#0`8M8PEz zn)w%5W#-}=WF*FCngtdiCns*mZKvmJX@sIM-UY2CVNIF)Te6lP}a}^ND4ffM@D~n_thUzc)X}-(Fsmy@J43&laB!E*ypa? zSXuXBmk-kn10+qjk*RqE-36{bX2atECm_sj6y|{3AIEttJS;5aSGT#zE--Vte>UM) zuJZnB+)?pB89>p&At7-|PPEVci7Jc4{RTZ!fP4eEJAwS6WW0yTT9-E*R=KL=`23wD z1re9S)C=2M@F;`$IkUmad!ZRzAy(0d z4eJQR++*f~fyFLy@W%Eq;8>$0R4fP`1}M00 z1FAWZO06_aiw@|3>4fQq`P@PesTJDYEyGqSLm`CyXsi^JJgl8-Jhu{AP%#DjMvoSI z>}wFfv9^($n4wh)kd{Igl$WJV!R6{{S|&X{gx?!XV-TQ$@gJn#O^d>9wfBK-1^kq- zHcp5Duh%)MAqA6wt24*Bw#;6mP;+zlnR+boa#NXBO-!CmS!4`Pr3c_=2l~w|a9ymU zOLpaNRQX115lzBLhf+Sk7tqk&padtv1!E_4IalhU(a9;xiooDeUDw%P7$74|>f=(Y zDI5YWGQxAope;JbaHgCZiW20A%aQF=*df$-xQNE>zY`h3?lgP>R!&VRUaZMgEhMs% z`#5$Hu^6$dIA!;oV<>#RV+IisH)ZA}%cDsq)}LY%c39Lpwn3e=&OXKW^73W;i!j%x zaMAFlRf1K^*cfcK5$WEI7I1Qkl@93K(#uh7jlLt}3CqOAJ-GM}KCg(8{ztOiAc8beRoa4jWqL3!Qn(>Kt31VMsPipql#CAz5DQ9a6p3n_9_%^tV^4HO zSDbG|(wApgF!61-7xyhRD-7e6px|b26)_UGB96uO;5+?dU@YA}Pz zVrpbmlhZK{fH=79u(Sx;w%M>8>!}bP8lQ3n1&qL)t~Tm)nm4!;>BwRWJ=ww`=jlH> z%`O=_BV@9&vbca6I&2ZUA(Qn1vVjfzt-x0PGq`1r$(U-Y;*EhbekK{*<}9(|Nml_~ z@?7w8TcIywtXG^r-jcJIdrwV+&ev)dNs18iboD=|IKV^2KU-*;gmRe%ijITYc4uMHKY;oa5n?}eJP)XI@W^_taF!Tm!)bh#sBa{*g3+D!dNe! zZya9S7(=1HJ(WzQRJ`V2??-_Z-b^vI65(KUTxd*KVO40I4mY+^YN~Jq`aJUgWKMx) zl<}pQaDZ(L>|jj;5Rz1EX}VN6;eREp|EWt6=j4=YlCnvzA@QEUk336SQPeq8AdWmI zlUDqk9t;Z0Xk6^{S*C}Szz^O$vTgEM6z9e3IHO*ot3pkJjjq=DOXG={!T8)fKB6kt z3k{IP3q#NzF{od} zrRhYdf9|C*0{J()6fY1#dEMuW&>8s!xVnC^@pd&z5_A%+EjhI+E?6NG|r z6IJkE=oa1DtS@tnr z$}o!5O)>8wi#k7r+#Wg;X&Qg-vxfnQ5BtbmsvU3m$GUY53ncC#7HMOl-z~InuVcC| zI=I0rZ$p&u*-&MlJe6^MK?D@0IwQ|pkr5#Wg-4Hj0qO|a%F*X~;PUOZ_M_vFU=o{| z=zm8ps{F!Mlw@@#x`qf!(psmk&pzL<`{%YdV@?}4%cU1o1Am(=R@~d_VkYkQ-8hv*pUTi zcWKuDpkpULor5TK3^p50M&SW8$Vsclkcg9qJXRZE-`hg#m{Q1$XE#6ec50aCjjwJ4 zDro@rk?*4`^J@}X>pDwlH)kX^)TZMe?italH{$9k(H<4Dk@VV0&dft`_K<6M<93r> zhsMaw%VnXz`tWOJ9FCm=;sdd@D^#$1S$SIRatFwg>3wbs;N@V+^u37Hdyi@KFTqo% z_a2fMxMXlRdW3&7q&!eeC+3xf{+K?cGiLFZ*$=G39xv(kKe5VEGCPr4#X1QgA9XQ= z31})H+?&VV@YskA;d}&{QwN0PuR*&kZ`^`12=5uxuKm@!c(WQ&Qp=vJT zK`&>cVIT1kz{ooDC$!?9)3L{X|5Sus+%a)=@aSIF_XM^(VXjg?bSNHIu}obQMMtm}OEPHzzagIpC zDsgc*N5Q-D+kyM~uZ4eE8AKD>oUjkqM5qX>+{(hgQgFYA#)@guTqrjv48(dzXL_KJ ze;Yr8*`5^2)^U(Y>1`%T9m6Lu3|J0SHqkpgX{6?0e~PplU$rG7VXhgJE3c2I{C%#}9AoHjB)`)+y7atV)A}L7&owwT zGIQ^IXSZV#B>~a8d+19JcY}p}e%_yEq?~Ln_4Zupc}oPhw^-fIK3`$i zy2)-Sm24?I;#AImBGhQ7!HY7))AXyc^b}W5*%MNuXGF127Vp!e)VO_ugrNlL9FoF?C9BZX5 zoQ2(;_H5aIX*KM)V#EiZqW2?SE#Lk_`|_59`JG%!O#Jt`42B+;A;Yi17rZodyQVq? zJ$$BlPP>*>MRPVzEut19CU&w+Jg8W^A#vc&ht6Ube>!r5t>(n6M3R5_9jMs^<);5ktCoKuUg|p2Z0TOunL0!R4 z8MROYF$2gLZ2{$IHvi0f4LQcY@6@Aiqt>5~qeds#o6u6A%x?vx*0HB0ZqV74uR*S) zc}H|rsj1Eh@9fH4k>Gik1MnSqo!p_OOhIpAuwjyf7ry*2aCG-&M z#))0y`ikUqWIS(zqV}ju6|6q1aLG@xP;p3BXL{%#{cLZH%6mv5a|lc}<3}fPlqg(o z0>s)hePA_SVCG?EesyuG6SV2mZ^lY24oU=^uPBizTRR1%C`U%|H%EqAf?#sR%Z!5n zs?Vfq%oNAHs&Ov0o4{oN;$Q%Ks1im(#G2=}&^bphnrv~mj)FSjf+y8IyeL-HyDQUj zSX`~Iihgo(&-;_pjNLI+~7}3_e5p@Jq)9kUUhZYqYCwd~NiAUOzk1U)* zUCE9Q52!nU=J$^FgH=8WWy{r?5CwNsXWPWiG2zs8au;x%!!m!1dZHfXhCMuVupYv*UiO~O5O$4XU~5Eg{fHgL;b*l0&m zDm~$i^0U*4F0V0_883`=S1Fh)W$NRf_?>c@e#>nZ1^7i0gOe$90J`*+o>c@&g=q0V zHQ=a-z6D?o+29TrKTwJ#z!1B`HHHaVB6ki*sb5CkEl8V;H8;_@oDZk}Ue2ur=!Kpf zH=;U|X^&S0!)+`9TeZGr^}#n_k^2HReX-BDyQ60@sw+4*^M>?`NChyOA-h@D37l>~ z{atd6o;G7-N)NB|pW7Y{a|9>-rYe)i@1OH~I zunr92QL$QYVW*5Lxk(XWXyJHx{}&%0pS7SRAMd~S|HuCyF3M{}?Du@~e-z9c6OF7k!OXzi(M~STBng(F!L7Q-kaC8uTXh55Re2QYo4!Yz(5eyy~SaKjFA_Yzw zVNOO=R7bTYcW$sthqSwEl^F%ij5rC63my%JiAz+W5hfHsr67VIlty5K{gHrzfw@bs z;O>7-X>ldz=-_jEEu)~Q;8Ux8NYAU+->9zS&BJ3?63NdiZ7ChH^HcV&a5am14_-~e z+$;Dmi3MROE-Ac98g*~C2?^vgvPJOQY*VHUZ4S0h#Pf13tPwQYt8oM?{YqEp@D zyx>;XufaR3iw^ zf7^mfTfg$Uc!#~QUp@&oqL2)$AA~`{=KhR~hM9DYj;=BghC~Jv3?@J%>j|NTBX|Z= z0PZj_GRT6WhVEc28Zh`S5F_wv-Fs)?3JZKxcz@SJ55m|mhZ*T?I^-C#Z2yC`rD{c) zI>bn5;L2#!>AKp`ILd9WH>$x#Uj&{Gqv6)XBa1*16;!Zy$9ltHfc=NfA}Hz}UeO4H zc)r2a4}(SV-*LB~PB>+A>;Vu4%r-1AH}g>{yssaQW;uZ~kK^sve5*QqD_F4-VJC!G zi;7}d3IPrO(;zMtEN0Bw^-#4}O&$H8C`(n66AgaWX5-1ri13N1qWaNyYUdyF=?c^L zw3c_53A6u}H1Mp>hn9ML8!1ctd;9tYDECYiqT0~;@M(9eoD!^6TWt#&S9M~6R^AF> zMx17E?ADv1+CX-CCZ#-h7k}+oK24mWWb8KUHl3VS-fR-;GkV-+auM#8E|$8LnpGB` zZ|%w0Dc^BxNy()R)09qKju20sW11A@Sqe^Pn`=eoBRp;ut>KlwSdYmeG8@E&09Hq# z{2wUQp;zIv=Gr8pQM3Ia{_b4>m5J64r+bp*4&gE7Gi#I+*X`^z8XnP*$hO1}1wl{$ zZNvy<4D`NhTwX2&OfshVL|yX%coYg2^KHVpAwmosGN#$9fFYy^#Ck58_n2dRGA6Fx zft>*cb!E8U5)F1#zgFqj_FbEL{ zU(AuGxCV-L;E*xUvii|6ovEZpXBAX11?Vk3tWKtbWK?NKpFbVc5R3;9`>g!!PHy%L z;L%o6`DpWBIpAMv)s5>YB(JH*I-6^&pRLwNpZmL2p_0t)@&HC9z+lfd-YisiJ{Ug1 z_f%!~vDsL{cN7!*#j*yBq>w#uZ#u|1S74EURlvR3SQY&)CnmTn6gngmD08d-nEY)< z&Ui`JP+OQ)uRLam$2Lh_G3Zpr{vs(YYrs&WTzQy$&Pa>c7)X)Y+>ojb<@IA>ZS`x1 zx~BP9kD~3OU|*4)_!CaoMBB;ITJ^*pE)BI5CsSS(2k_n0+7AJv-S(!cqO2s?dnUXM z3eVC zh$-f0n=|5ZfBM~1M^;|Mu*9Kix8PTBr-4c}Iq9!0<=<#9ZDyT^wAM6id^d_{eNN`qo;C-O^;+ zm}MJ0T~PqZpKonekUx)kA2(CoH2&1@?!~+s>-N;GVtx1Zl^Qiyvo9tBmRzN^^Wa4? zyTLxf8&Yt5ks8z`#U{U!$gJ-!M4&p1v8)+YoIXsEqP;}rhlGIJ52jw`k|#vjghW0s zqP*#%%0X&ph4tkM88XIfQ%%M#OXY3^(`b`BwKAY$SVs`i@oZ@`uSM?_t*h$m?x$Dq z-5<2-gf5}%pHy9sD9EShsXx0NX1*S~1URtTETvbxqZJnm;(4*EBZOyFednR3T_aW$ z*@BqZd&eDESu!le`?K3NtA2cVI^T7#RegLCNRaBaI^NU2L^0hE6_Z!O_A1$Z?U0#S z|5^l?_+m_E21$jciQv2ChbO#MmC$=mOR;1*6=qi#qvAQI2R6}~_+cS`9T4SlYqA?D z%#Io(uVcC#y8u6?-IY#c`=_rzQc%9rRO9io&~}f3M>(G zB}5`SXfy0;b=6xFQ&n{}l{r#cQo}gfkf_kAYHb$d3|@I<4do`yN(TTpJ^H__{D?Mf z?(?n^UrOr02Vsg4QGdEiCW}92n&F6fUOx~NtHg|Byg$@$?S{M>k?0ZouE~~2%u^XGgi|WaoSujBB zChr=UZawBwzccG?q_es{cidOg%`z(HZqopu87W_Ad&xsHcI)$49P2KM#?7Z6OG|&9 z&g8#rOxinWP-tQtPE8#n|EfAa0p?868y=|%Y3+{m5A4HhpU{-LVtyx8$^Y2f*TOI5 z9Wvc_wM4aG9IqN9+Ot$SgVsM1v9EsoeZOEJq?2iA@ItK#rf{fYA&BQ&WXH3!)R6^u zB?#i|vD2PbvZ~B7G>{CJWKtxsw||x8rR3+{J$Ui8;iJGY5-FnzGG9YY0LmN7jk}14 zc2AO%^9u&Z)X21Bw8keU)57%lg6PkL*X&EWRMJh*{Qyd&_}6-d8?%u#7*xx}-? z%^=y+yKVLXkL6TTe0`T5Vf zX6lyZqugRS%4qAnx_#m|`HWp)b$R)Gf&$yJ$^(h_V%dsd@8d6G6NUM$-hYX^d zIyku_;8qmmJonLiZ{Q* zR4M-z8u(aX|FLnCh@HGs^N#V8>!So$uG*;GN3`Sxw~xuBeoWS%`9=i0u{CH~82o7i z`9|U=AvmpsZ|rK`t9AKezK$amOgh3C4b+fwQypGYnJ~kYNO~<@-hdynrp3cFO3Kp{ z56Oqjgg4uRK=NEIwzf)YQQvqx6f&>-pcm!4IRB`=(;k1kot1Eooo9f}AQN`*Aj`tT zpV+xHd*r6QtWI&5lrRyMQe3nfWzs}QLBd8&LvqfO{=JwRLN4dQYjQJl_m?fhxp+Q2 zryc)uGiOy-l7-@rw(sy~=@<#6^lkWTC7PKe8k)c3$xG2F&-Whj<90FNtDhLc549qv; z7Q8|jDm)A%nGa3DOmguOcrb`g?i}udY(z0Ka25(=C{S2)NiHnJumN)z7;`2>IPMs4 z65?Poz$OAF!(py1L``(HQZx?!o+}+a>w;+Nja7!c>aC{=q$yo4*9k2Sjv>jax91!d z$jew|;aYQeY3uWA!i^rTzs)8pg!^app^n!*s1DY=`1iNRB7vwHRMvS(H>OYVPQBta z)}Vq8hSR3+v~bWi%!gGZ*9*{h)@*!uJvPx+;5tp7j&pUklt}G!s;p*-uoxWCxd{5G z0^|H#;o!)1OQ6d6Uuxbp&TTusUR9A(tXrL9GW`I1;y7`Rx}tymn9V0;Az_tsD}fle zxt5!aNi#;KyFt?0liK-3QV5|x7}ClujPA)iD8*Vcx9SW^ovta{H=aYfbgvkngxk_QM3vz zrLR=0Ea}W(JN7F7>r=4lT?-Ng>fU{YPA;!!*Yj{OeVb+M&pv%#UF?X@1k zmCfQtW4%wBHO7w672yl>1Y%!~ODy>oK>x*oYoH97FJzMN0H-WgaIN-F-7j*8e*?_M zSE0-*Y#OY z8W?W;TNV9{7wxa@(Z1~nQ3>tcq{GOt%$;a#fFc#lvRm>u?k+WREJo;OQFf|^b^yxG zd|X{w=ScY%UeqF9j(qv)*9NjT<%>=5)a0X>#;$})rmu(M+QN7H#{gT&;j5tAr{(rM zShUzO4-BOicuwe_KB#4yX>uF^zSaF*VsxTB`#m(cTmG3)^ZbzHK7LzK`;(??SR=Lp z@{?`I!<3EMSU^%Lnc4@sV4SZ0G@yanQCKf-G_qTlUH$Y1#ekYbFTAhQTdyz(yYVC> zr#F{W$X)?vAzcEo-I&|TQ+0<$ZUGliN@p}MwQiZ8(Z-`|U#y+R(OSL@1 zl%zN4|8TG7j}SH?h%nwaOT8f`PM<1*Q}9#O?<=d8bowK9q~5uE@kjU+A1D@k#Dd$s z{0kufS$7=iJ^Pp@&~A2+?~5!EG2i^k+O23`o0o}o&3cqh#T(LP^)mF>*mK4g+NbYc zadsWcio7?u4nA;}_WSg^P>;e&#S)+_Nx60pR`qtkI)<#o0g!~tvbvl$G);Y=Viq3eo zG9aHYk2ob{8W!$;_jAuacDzqe2WQL4UR5eO2}x}PJoa2cf(uGr{n>-~b_LAS)@knY zH^rApUV)caG#BxX)9IPj&otMDQyP5Ll^7qNK$0wL7>{{0E^EW{z9s9dTa>B8x3oIUhAgJ! zq#lqgJ#mEs8bth=t>Tz3s$^V36HgR-JB|7p;?g7R=C}s~<&;oP4UCbZCEuMh0nc!K z43Se2%>0PwdqZ)9DLHEiDWYUGhkaJP|n@l@=bWUL`j&t!m42B zxT(4x-pLAhyNtAM$ZWn3|B%fQ9}+sv@*|=(3foe;Sjs3fJP^3OZt$)or3;xG`!x@; zWh{c!Ww*hTUunxlG}m+Kl=#2uO~M9YF$xkF9`AIO^NnE`8(>7p_tV{5{+^M$O1a6q zInG+T2HNs;fC^a~v=V}9qib95)k|vgTMVAVQ2Modj08Aj^TlYmDc~Yt9%@>8=WBBg# z#q|aYJ)K=?3@L;n$A3#Pa4>s~Yk3HlCUo;Oh}~oI3M!RE7S;g;X$g*9IibYQ)iQqA z!@g$u*D<^w*iB~`E~-Vd=#iimgVX4?!$(=+>a#Me{#`R(F{P<`_~v#O*G6XRattSD zHg}4Yz$6d#Xyc8EEs<6c{L}gU!KYwfmC@9L&c8BmTW3$wB3~I2uSiTSalQCFI2!7B zOkd>(NV<@%D90sMDo)InsSdD=${J^yE}gyn782u$Xc`qq9mQtQ?%n=5y_)S{$*+4x zauaTlPcxNq$0cE+=GH`cgx%H!{)v6mOmV&V2{gEv{@@lvNJD`CfPh8)+jpa$iYc&( zv;40WY@N*;5Y)0qTjCkFbYO_ruf(>A@e7qa7JjjJ;uXYZskx3JL*<`qf4U}T-tMNS z(zz3fGPpUt{#t6H1bR4_tkOS}gufb_Jft;117Aj;CZ32*_(gRu zM-K&+baVcAQ0PyYlFbm)_gIKgz#dz`?&A&w|lxjp%yq4mjl< zC7osR_&%@3RMJ_QcUVU28)yiL!WBqL{6RN>7RLop_BJWyPBbGk4+OtAC{5Dv{dPCX!yeO(1?_Pe7$svBg?)RL zP&mHrTzW#}hqq?+p?JC#O0H%=Gq(>m8Bh_vu!4H8--pu|Qqy4OfY-e{na{W0SDK6V2%7=G7%bxiy@7NIWhSHxzp%IiO8M*#kQWm zq~Ax^@QUyJp&D3$m3k?k<{Fmf*{~1e&V^ANe5^EL>%(@zH>{~A6Em~H@i}t;UWy*A zM>F;382pqwTt00*PqI!0;;0>;(MhbRWhDAF~$E}1djUW#OR+nI5i`@bm1EHE> zDkW|pVQtBk&Zc{8%Z^gj#khX@dT00G=Dk&Ej6xm-xQz;M%2Oty_iqkDIP;^;}Yg^Afbe7XHwM zWHl6-Vmdz2>S+P^NwSPJY=;+F>XQm+h%&56>nkXflHxR1sn;hhW=lk?2x1CV3Z(ZpsNBHnTR8;?+oA#JUuJkk`n@FU3;g9_F zF#-O}-x~#sIcHDBE3Zp%VjG@m$aKvf8;vFp_)eJOwH*aOA?w9E&%}a{!n8e4gSG_x zN@tb5FQsrK?(J|Z87tBAqnocU6 ztzS031}~0d0SAv!=CV;+3sZPFm`7f7BvOL=PiMgG`zwgHw-SU?kv(~+8UPxs!lE97 z+EAKk(Khu9@SMPA`;7km3|-nnH4g*BrGA;e6-YM+PnjfPTuWdQ!6;8GA34{ZsnHn* zVkjWFUuw!^qBZ61qTovIJ+$KAw*aYLJbZTL#{6tmWzK^00a+SiooB^=9>$y9aw_t( zx`8*^ST%Y?l#k2g!7B{U+W#(`cvcssXyD%7p_^j&>09;&D;L}%%^|I5kb@7u`LaD& zs(Z@1^Iw9i;%%EYPiDQKU)~+m3M_LbDxPpz_KTIbOn(t1ohuVoGiOi3pmIpyi^I6( z1Z3xx_z)Su9bQ$2&19_nyH2KnVFAsz;{u@0uvo&rbMEKCL-EXQrg+AwmcnaX!59So z6PnBzt%|lrn@1z&+FMYEi)DD~bo5tk)Urn>3g=tLa1>EVp(4J2i8jO&l|lI(cWMt- z%$O~)v}L3-%pBp9opfS$xyd~vXH;IUdfr(Dj7j{@d-B?GKfpHVs~lGD6~Q2iMnIdi zB8fF>gxZWM`l3l5Q+dqbBef6*^_u6wxNvxyW@&S7OfGlx3kM_XWN&lnn8^=E+5=2& zYW}BY4VF0&!d<{`gmZ_nFBr^i8~n5mR3BxYlqftb2f7m)H=-QUFOqu_;Y2`OqMYha zw}~C3|IIq5-ZD>}rHgJ>CU2KlT7Yslp+US?a6iuCBwqFdvD;S#6^TAZ@gTkY1>}?xGs^!0NQVMBZ%$hp`PcwLhkf8{vrG` z-)YDS6E25^u8VXnk>8g1Bp^rZlESWs-nL{PbkJel#+*9EyHr^-@|bNmZqL|Ml;IGO z#pCr;lr)alqL}m>F<*Z2?RmF7G@W6YXUSttbxOc3r7E9ofI}-j=W@N595;@CK4q=k zAk@L;aJ_w{HJKsHWm-e4HrwvEjGDH~xbp&9+vN~hkP1KF1bug{72xkMa^B^Xf?vX( zdr?jQD?ZyvZBmuUq9t1w&1*Qv#7mWbwN<(0+jn)=k@c3}#mRCxtp_<4-$>h90@w|0 zESp!#NqvSpJ|7x#O)!(LuuQvasjl;FA=0X3ZGUsI8d+uQ{pPvrII+g;By)c0g%@%^ zH3U^``NDY)t}ckC{swsCDnTFr@@nz(3#Nu~&)LGd&1p*g`x=p{z%v(^01>muteoG3 z>rdV_e@wQg>`OU14X^im%$scr<-9unhPV9mSGgX?^4-N#i)znE;Js&*a=#B7ar{|M zX_WP+$emo)wvB1#<@|LBKH%YP}Pz7$B0?PkE@+Oepc6NN*c@h1}x z_P_OCXc^#cZZO)mBK;kpT~`(}$i~={h)@;|+$l<*ewbUYb&9NqI6q z1XziY^+gwjeB1?O7AiQLT?+JecYFnAuMmkh)Rr`lf5MN3zmbl`7!nV2ob)KxIGKXrwMwYT^vtf%8_ z{2>O5U!(2cE_p|7zPOvHEat;ZCu;f3^jnf9u^f2K+K>Uyx77}YP^2$f{8<{Y1P+MD z{=$+utNHr~P$9xG9XLYLxT#cJVK>NClw6M+W|yjL{FJl%4CI<2w@P2{Rb8pTXU_~N##7zV&dc`_MT#&`Gwx)=YuYwsM>r)gWtJ2=OEbIK;PmTU z{#ennR1er%iOIvVu-itaSQ%AVy|09HL4 zys$>xa>&qy+p9iMCeyP-MnbSHnoSZ=*P#gC;-aw5vlHedAWfp+g9z*M-_Ft$CJ@1& z4ZyrB5_~pa(yGy#sLS(SN886W-HrltVVBN#Oak~lE+I)Bbv6xw;*n zH8cwWyRklKgx5J;*Eqf0MtxxDkcO&3v& z2+%D$@DHCyS%u{2ANwcKVKo@GvU$}qWRxT2(faAl%>AcjR1;0(?7_IxxPHDdJ8UbG z(#0f-{)9sIn8ouA=I+B=PS3>PVDpe>PD? z@HQEL95!o26K)!FRnib&K>G}*WBmmCBQV`snlYr$#WF*-Z;Jgl$68W5ncn2o*S7@6 z^nNAAS!~k&sRZn!BV{w4VIdP=m!wT+P>WS??um~S)}-9^Lg$nhh_sFNjgd^Z^rkP$ z`Ccga`>kceukqORCEKtCmH3P}R+z!gG|59Y0`6@Lv6CwBXGGGW^6c9c?8$J11|Wr= zFPk@FOS@@p<5e+8Eeb>6ZG9(|O~6_3{^|%xus1t_somIlYn(~;B|4H9NG{D-^>Vsh zFxB_$xime3zJ5bz$%{3DM?;4JP2m0iU(J)K|JF#i{h z7T-#2S4EqDLfD8=SVhpIs6ZS^bt-te2r6oIuo{>OE;d*iD-_X!D1wTH8Ll@N{N5Nq zX-)Zw(x1|qa!TA>CIk!J-qH}Xh1c<55fR9D{mo*~5B=P2jLWH?m6;IWnD~hSg+ZXu z07+98Vy+=IVuCfO5)k*J>eGy2Z0Ow4BthjW@FbqcRFs%uu7?JYU_^y+FxjAe z@Ob(!3bz7eyPxYzNg}Uz{zovWj2Q>fPO&;7$$3~R`jQBwF2ke^%exn)xu)P)@k4K zy>uVp#Vi9&r%SEq+RIEWv46N5#0C%ZEMN=a=7I3?@e2qFS+wv`lz?D$TUMyM&i)^0 CCM1>s delta 7795 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 diff --git a/src/Nethermind/Chains/race-mainnet.json.zst b/src/Nethermind/Chains/race-mainnet.json.zst index c9d5738c277eee9adbfad8b6b44ee7113ec130cc..c4259e7c81976d440d71530c1827e414a0986947 100644 GIT binary patch delta 8654 zcmXw;Ra6^*(yehq3GVLh9;CRtQ{0`>07Z%gcXx;2?oiy_tylwvqQ#+59Pat=I%n2A zerwOX%zl_XQ-L(sj09nB<9bE>gtdT)8TSsStPJI3hWk=kif9gULU<=@-sD%|{gtY3 z14ehb=?|uZm(#*W!&<0(e}Bhfi$ttNC`J&BG>!ZvxRSZeivLc(ta4%#wXID<+AyMi zg&EEGd$-ETXe*8~BRs;bM$pT%O^1+F-c*uv0MlX^2^|9qj~N0k3WLxEKW}3#ZzuB0HfpoBe@-d`sG{Y z!-?AAinXKga-J+L(15Gw}8u%-ZCDIkrTy*$xSF~?D0;$lse)JiX9z1qNHRO zHhB>OI_6a{Rn*P^Ueo~mRQN#f2gD#rIKWE~)j|+}R+AkbNmpu;pZEjq0^o2EiaQVl zh(lu5wFpAzmMjVZoWNrR1Ngfu zm%JgUW;(ede>?bPr15e%#+{}*JDtd_-H>9#Z14!!b7 zSq#|q(CV8WtpFVg?T76xM+$3C{O56XlUe7&SK?8R2M6-QccCyN)%6<3#9L}D4|?Zf zfy4p_gW~0-ypue|i6yy(KAaX&zdwaQZTzOrSbUrAvz^kkvAR!kwn%{^%XkP(+m zLq<}%|2FPseH0>Chpjk-O*S}K2@0Yf91M*BPf-^i4pkNnA)$u`k0BqxG4NSIzz7IVFro_F?jX4GN)w7Ip-OBPjQG7sQVOAK2$@RyKc|64 zKub)-thyix-5m@;ASa~-6XU~i=Wy|JUm-q&!44Lvgt9?9Mez9R!BQv)w8Ap-ruuQ+ z8it)>-_miHbvEALH*}XbDcz(*^-XD3OYpj+YvlFhMaSaPSa{1hcpyU|;W8?WnL_I^ z(%;-hK6I96bTwa`d_ep7ah26Lm_hjDeb}+wpcI?Jyg~_!M)xX805_DbV40qgwmcQG zA@MmszC-zwqt>5VlWbPp>3M2sN=#Mi>LyK9!Jv^=xymTTqOlH%2^D>4Pq#*G2){S$ zPgbw*VXkSOHV_oI@F<9P!foXDogX7D#mVox96kcqwGK8fSA;z|KKlH>C3=t>7z+sE zZ?V1%)?kAiwLn??F+m!(wWOVXV0Pq>*$vY-2p`;UD^ zyt?+WS;n49Yr!uD*Y7gjVH3kroQkfbD{6>ajuuQovqH%nBZ&~Qq3OuP7|i(6HJr{y z%W+mld3mOmGflfy3U9>Z0QZSzyr-l;r_I#Yjq_Px3h|yo?}J)x1Cg*6A=A*#)sZ(h z2AG*{UQYhBsZqZAeU;YV_zxrm=gnp}lXie#G_@jzFf&HCDw#)K@GUnQE8 zj^zEK_D=FT-6FhLZa5-g4o=#HRX(S%r#P>I@&1Bw74^@;IX6tI`^{Ce5)Gg+rTVwD zMcPjDtbM*OS~E9A*-;Y|b<7tN=YSZNBc=3~(b*s8&=d%pNnBzAZM6$2K?*qmNG5L~ z5xP|C@*kIoxQm^!I@2)vxhR(FJay`~G)T~pP!v9wll!J9oW;ji!t|-4xkAD3&TJ^MN5cN-(}9EW z>L^gTD`staMRI6wU+1&7Z_{FDE_0YB{bbd|yHBE{>1p-DVll;^oInw_iW6-%l|518 zD@BNkUeocK=J&>VU%fJ9(~3l2lZhyilXkSt{qzCKE(aHx3=0s1=F_=(-BOA|*YO1+~HHpYp1weo;U)ub!6xj6(Fv z5vHK(w!>rkSU?Oc2yn; zu>KQrAB7a~mPkHjKufp$F;D04=>LqPw8EUn@zAbVtf2Gxi}CAVNZ6Jr))y2$gR>c4 z))3YZ+bd0N_axp+om5-yWDjC2Yr<>+3JSIsn{KPF=C%-hlxRX*4TILH4own%D1^0! zw~{s0Nlf{PrKAK;A>Ff&>Bz#>uOCJcqt!|Dy;kwDSoYMnJ*F#7;|AM^cVsafppen; zSpEDqGxm*wBc0)oL2_N!=#Z=ce{YG_)S1u8*YtaH0hem|CGLAoo?!<#q62sa=RE+`j5%<&+UUWP5zFggGZL)FsavG zS=H1QrF3Za_jevn85zdeRD0~7P1I;=bkX1{nr->wdz%ey{sMw%KvgX47X+FVUGpx{ zoy?hRh}VPD`_$hrMkhtKBo+o*tiop1eo>!I#4l4GzxTz-=DDjbzNRHzNfBeuZZ4k0 zph}3di!ZXu=}Ax{R0I{aS6hcW^k?V;2{0|`L_p<*R!H{x zn_&)(J@UyhFNZwn0=FbWkWIm~Na4~F-r`b+N>DGGU|1xIN>mhzT=taNVpcY02`I-E z>xe}B6Z=NjlJRUfLA;H*_FcyJQZE9tS>LkI!q(FE5L?~qXa?3x|5O#j@eiCS3a~M} zJxcQx(t3xluJmFraRr5K_3Mby;zn2|D8gFfuzaT0g~ z@H2M1mh1_8%u!GXF~AZ#%n}<(>i;@S3VW0o9M3olf8VW$Mu^IbC>+@e$vzEexffk< z@dtUdA8&=*_uU+I6==xSjG&OF%++OF$w%P09v34-s$@qov6aytEt#W{6aGwV^~g}AKlXBekLa}^z_F5Ib3oT!e= z^J2rm>QPO@Dxn&B!_)>ceXbv{r|_=Ej3ZFRA{;$D?Y6r9Y_tM6&I5gdoyf3Gy9ED{ z;+rDMfg*C~7Y*{H(Sf-?`eIAp)Wv=mPljSms;5$5!(k}nPP45_TnQFNMVBmzbEGG^ zjTq|IKrrf|l3I2_!GAw_MmrP3Oqw9c-nQETwv(Cv^g83_myaW@%Q!A!Us^-4v*9IP z+APi~zw;1?0C&qvD(D(iLb0B<1{%)%rY{ZSrTooBP`r{hpOXg3+UX6~s>gUn=M(6QIhbyD{ z>~XC<(S0edPW*{e^!9Muas3~oBBYS;b0zm7>(2%txB4U>1Uvn#vSr1=r95QUs^N{y z!T4n9G}75I0Oqi?b!=_GAl7Fr{+Vh22TH2XuL`%i0<^1 zjx4_knwsqJ9!mRX>4k$lWW^5wcSpq!$vFgdla6cR0GcBS5dvlM83Y2K z;3F@|F$6GPx zJFU?XfMws8xm7;T;@(CMDf_lO-y7s6t}w}U9HpygNxQrIGz%MslOiXfkjRI8J{2D* zL*rOgGW`|xqj2}#`&PhFU=y3mPjShwIrU&OuV8{XnV(k>^8T0AD`|-IPf@ZCfaTJ)*zAW#&|wy&zihZl)@ZX zF6do-Os3+zT-4+Bu$w1EwTx}xyC4EtPiB@5+v|Q2khjpjW!rXki_+~)!|t^XYf^z7 z?#;D?<4dVv<^ZkOzTP;|$9S>+KCTJ_!5I9Nq*?Zh2+9BrEk?*{_1Ujd8a6^l(@eOG z6-%`JWL2!)cImhb(o8y{b5@YvsLwYVq2a|)@}soyfde_w^RLG?G1xxRp164Dwpxe} z@kM1{dLJd5V!iD^&>is*;C*+x8JKn?+*cw)edbLR%sSp%#kq)a* zqEp6@TFLWA7uq3;L*ggGSZcJ@wU(|%Zp{yKhaAZ{+YZxp%HpfRlFH@!s||DU)%i)0 zqTV}-P`iM$n(Kql_S_wYD;TOvF`Jg;cCZD-Rna9#j0)(;@Rjw6^sHMP)JE`=Agp9< zD7R<#(}N>PhAUUxtyd#?jc2sdH3qh4D`E_I+})e^8~G65O@*Tj_;eD41AE@Y6JFW*S*+|I_fzSk%Aa`XWjm)-zt z%kj_8^&xj|-gI^k7 zIutUihY_aLU&+SM*M_I=p%f7@PSSR`Ksi*^Rvi{n79mi7$X*LQ zfeT@QD-1H)31qX7jJZs)9g!M9MuDB*FTy5%R<8I6`jU;E3$ti#?*!X6jBHSSQr_MT zof5te?tc2mfyC16etheR+988qwcAZwOZ1d(F>@aaezlb!lNS#5e;x9)gpuT{kRe?_ zVj(-6o9r3{YE^a0?6Iy$yOO^j(5iKxGp_wfn7*sKr^DGUbq}Q3t*QEm+wznl2(c;d zdVFcpX00qaiD;cmb;fhRoxr)F1xZVkbj7Pa<0Me)T{%5!JtD^>uF@e)X9&Kii+;lt zsybkfxzv!Uno^oPx|-;A?X@mD;9speiGT>u;ny6T7=@N<(^s3J2JGj5muTK}I@H|b zR+ZE`@?|Vkv|FAHx7OFH?$Y)#t^zMV$&MEm0kw!q6hZ`Vd_vOSk?(OxxWNObO zIu#YQN=Bp@nxIz@kV$;oq4I1V-2(z?!RhZhFsKn>$4%>={1x5a<-Tj}biCIo@kh%? z*JRqN<8lXb;c@TO@cZrJ<2`iik9a&j?sLaw>>qIfbnkmPF%9llz~%DmMF81)2f{NB z4h;@ezS7`R?qkmg?DkT->fzykbwj;rhL~ulC3dY1#bd2ZZ(qaQXQC|*`3YtrTdCC;B%SjUYTaa%osM@u zFfo;2O+`naINrA((|kjYBrb|! zFWgFnNSA2Zd^!gQKH5i;xq?zj90MA2CNgoD%EHdjpP&3L=i*5ltY#KZS&?@1S;6mw z1mP5Rt;7n$CeN0X6cy~e!s`jOYT}!R4dL?MvIl<-F4eu>?y(uyk*0Q<0A( z5e;K#6abv5`?+02oZW3Mr$f~Cq9-`vu4kD>u3Y#>h!2CN&Rc9{)yok3+UfN8O!Aps zsK2{p`4){c6~X(xnVde-E*~ogSjMgNk>D%MYkpzHMRkZ`B&7UL(-V>xAEJ$kd6BUP%+_DDDy|nwdr6JtBB^zi`tX# zePnlK_#P_r_7L=`DpjoIq~yf;Kfxz4%n7Nw{*pc%T>EF|>5harFZBNghd!kgH(Ss!LB19mc|PcZoBF|I zC`p@s5C-vZE3GXN+VbB{qYEEpTFguwU@=NjVM+o5RR%PS3Bzc?`RNhFy*ADM^C}nA zt>W>?yKZy3_2BtrUNNruOGa zA=?rDFC7vA)t@M7e5p!=koYT{unnR=;XvsVV0(|5|Igp?A2G8mhJYHXK6?XH5cY{t zNU;m??_pxBmxsMz4D~F_kuV`*Sid<|K6oFLOu%-I`hDi_M5;lc z;o%fW=J5QBlz7M0Fdo?)vBfw!mg?0|B-2y%9K6RF@lGgCAn1BG9{D26x=6`1>%6Ms zJLTqSeK2AC?XqT}>)#@(XA1ts((Hm9$n7e$|IO&QIN62hWEzzC@_3Z2rk-s(lje=` z`2AOcf8!8_413)506B#GVJDm-!J#@2{w-@S7?ta#aU`4vW#)Q3NWAUpr%vu1sl%EH zmfG!7MTL0XY_R(o*>Op7pvc{yy6}q}=LK1=?5(OyZ_16M0;!C+n`v(I3$dFo5I-{B z!7!47tgEqLiMLNXAvl7-MFTg=hwE`fvOMnLD6}uYtMLPEq(Ee&rS&xwvr}Zl9PZf< zev`b+_>Pxux$=Y3QE!O}vd6T$ilaa22tY&EXt{OtZXz!XH7s6#)|11&ev_bH9O3k$ z$(K-n5l;|l-P2B|UkWEMT~*N;fb7O_rOFk}|MbxsNOF#Q*?1vuCy=>t4O`v(wqIxM zgqU_JCX7>XC|8MQ26zj981T5alV2@d*Q#W2E=WVNYC>QV#8yw$q!)mN;wp`*RMRE=MXt%#hyds*<=oxUgV}FHQ}?g*rnoz4XGJ$EeQf}1Fx_$Q3u&b z3>OBD#4=xi>LZIAT)}GIV8=EQf(h>RaDxO;~|^EttddLQpZnqh|?vmpxCewJ@mIdyTXDwmO4Rv+l-Nhc14yWWiqBb#FhUU#)@sWm&dA;s_G?pYH0*xD>mzETj`htAem{yN^ln_)Mk8nJ>UVSli6UN?$Zw;Ui9nIoG`June zogU~REnJoE0Sq3HCnc@Svea^~o%~&yESTT0GcpN+A@?To#eytyL1Y6PZ(DWakj18l zH8ebdr1 zbezkrBaU2y=AK?QkNH_5-G|`2%fuwF--b#0^7+OJCGvEE4@2*! z58?ASR@IOT>!;56!;z#>z4!ygBW(uR^S1Z9sf1#Zu}1 z?pxp#lT^)BTpm@o(0mkcPlFu959E5Uopae1ydN$c2_^l?pdqL~s9peYc;lG=Kc=Jn z&%1H)KRI1b%|z&9>W@&EV?nnjAcoj0Bs@2PY*x0XiR4L&k~X?@Brc(htjeG_NpCR; z38Ge+e4tWh`%4RjemlkKBW@HoF>nwfGl0#FL^6mmf(;^;YS?*zx23Y9>cZ;5x;0vAh$ zp&;QI+Z4fTqezWOl4@>rf)8YwDF(oO)>?nhZU>h~jDM6kN*?SFrN<8NB%o+VgqRio z2ttUmTe`+!J#c+XphVY{z#$V~6mb`{j=CPH`~y$=NthZ8TpVB{3a(l=6DiuU^i`TY zS-NgiV(|rGq$!fngX50Lf#J+9sYPT=@Sdp?WUL<>B?68&#BZCiUe9Ib|5d} zb)oXzhr6#9sXv4*uqIJi-u WZ)Kp12=`!{+QdoMQcT5q`~LtBzm8A< delta 7218 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 M&P2l%v%%&60YMkqqW}N^ diff --git a/src/Nethermind/Chains/race-sepolia.json.zst b/src/Nethermind/Chains/race-sepolia.json.zst index be45c6ea1ddd541e7f0efd523a7be2fd12afb561..f2bef6a125e741ab5e3e7925d1d7b3398c633dec 100644 GIT binary patch delta 8721 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 Ge*G6w$ws#T delta 7303 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 IJ5K?Q2aJl=IsgCw diff --git a/src/Nethermind/Chains/shape-mainnet.json.zst b/src/Nethermind/Chains/shape-mainnet.json.zst index 94111ce00e0e241cd7dfa168b22bdde5adca1699..c7b02685b219a3c73229d4c8106be4fd5e23431a 100644 GIT binary patch delta 9058 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%0D?GPBoCAdyjgAl1TCQs0=lC>6x`n!76mT@44te% zAC}A+ClDMAU<~sHikN{CbO=jP0B<{YfGNKK3$(6)U?BlZ2Z6-K83=|(=`a=*C=ezA zLqO3V0D%A}C=fL~5DCVFrD#x9_CWyNjuhktx;YQbxQElcl2M2Wq2@Z!NfPq9nDiFrQ^9F;)c0PxK zf>95Yb}k5ljzGDWiZBiev8E9QM+PWcFhmlra>{MbV->eRpTSbq947scuId73;L=m8 zDH)jl?SFf*glHfjc{>*@9I#{vJ42wL4F(T`>ycn$Gz9j3g290d20;r z6lmk@X**!q+R1#Y)W{I4O!WcDt6Q&c`P~H-i(r^&hq=1cXIGeE$l&&NktxmpMiuWk z>ekx9zp^S)+pr~Qr@NY4$H3Ct@(P9|R1nW@S@#-KZ>8ec*eG}?aF}nhTmMFBzKpCK zR9WV*K2tTVNnAn*5VW1Nt$uE4@d=n1NkZJ*bF^=(lfClO&?}vlynX@}ZgvlL+0l|q zR+dHF)EE46OHtrm5~)C9oUVFu8>24FL|#jnpA?jmB`PK9-2Ph-Pyrq#JR)lCoC}3S z#t~=?00giUIR@z;2p59+2pG#^5GG21MdS|x zhY|b@cL**9u9u-)Yk>;f4jzVpvvKxz#t9{+N$4vG3NI)KhCqOZR~UgnM!+DTLV?H7 zH!cYLQy2_}heM?g0#Z4Eu5au;fV@-k=MR{RF!+q{U?EuI!vF6}7X;!HObDmQy^>=Y z`3w3znB}3vrykPl_+&nMYwTBVuGI_EQ%vMbbUG-j zj}vb7>$fV7=bu+zq*^>no3nUZbY;Uep2H-Midtw<oi5U19H}&^i zXy8spqX}&l7=aEJyQ_p~zP>dYZD4kfTcrtQz?unC-o8Yx5SYSX<~3;op8Y;zv*XHpf;~09cyEIqb+fDA{BFTDD#Nr1X(}1POD_4>wJ-?)rfIqD~0QY z-{T1h&s^JJ|2qYIXYgpp_qyCMG%mDIoFRFaar}U7DXPL3!v~f$3Unt~9M3co{j$Z3 zP-sVV$JAptg_*KxpY!6CWf?q!l(^otzQ*FBq1-Fe-_emsV7Cb;cd)?qZ-(lq>O{<< zdP!1(VVGEmcyvq$NrX8VtCvJvk4UqI0PDOY5`1QvJ_Sr8at`%oDOJ*O_7VLw{Pd1c z3R@{$Mhka7e5n@-RN9FbH%*STqM{U0_Y@Q#=?ci%T#;Se^Sq#35U%J*0#OWCad*y( ztMiLw1F4lEUWA}Cjpzpb*15Qo^)3^22?iTIaEc=BvFXD&A=wF4egI&`Ps@^CP;$^yW zev3`s&bfm}rSeo&wIaDt(IJ4KpRgjt?5kCa%iR1EnAAdji2Yk~Tsr{2nU)kqDQ2^V zL{uWqIexh51w$Mqs8rdBR`x!vJ4B0HsXozl?2b1AP?z{Gk-bGOaWrWrQ6wZqf^Lrs znK6KhNHCrx0Iex|vkV}m2-Ls1s-xqyB*4a$BTX$!@`*kqhncmsytS5c5o#yKs6Q>A z+a%!yb{36(%0^YVE_hf!(%p;iD3gj?H8d?E7@*)wm}4E$$k@q20iWvb<2l=_qW29H zzHckymg)c_jm;)@+%2vSqvs1#i@AlXsNIV?xVvfYEeY-zNMq6%IEuCTk zgA_bHw9d3Kb~e$E!$jq7)ZK){jU^PFjWb7}JEn(Bc;Bcjf~Bq?Q+l)R;iBE^t#<5ipT+i7t; zucBEmqTI|1_{deesm!HRm5duCXVe%47Bz)+{XVj6geU~BjMB6o`p62cFb?GY$>Ejm7?;4i60JR?|5 ziBQo$i|VkDm?D!!*qtjZ7FboH61AJ)Xv0~YP`eGeoTAYnCU`4a$32lsjZ)XE=JZmt z<@Qx@Y@1@Je>^RIq3f|J{j)#>tgT2CgPu&LRc9o5B?(0Lj(Q-GO(deb zh{-L*F(zH4#>C9nr=^YcnVs>C#o}{F9;JtHRY~QLaP5Fgp-#y;SlopwVAsZ~$nwy> zf38nC(&^MreWZpq<#S*uNhF{ts1g<7CdS$n`3~L^NPKB6eST|MbmYea`4W4lca-#2 zSIkbm$63Xf*CwgmPl=03Oeju^$|qWID8}{}^pJwAN*SYSmJnU16I?_*T=@}>#Yz-9)s%qV1cK73u_cXD?whJ2FI2)-Dr7rD&F7v!MRlZO zWN{fEbCKk;YD|Yi;rVuJ(3mz0Wi*@SL7~hx)v}%uZ7WMrYNV*Zm&GFmxhUixzTvQQ zZG#eNtEp2~c+TtOZX~`Fle}kMVoC|I(fD0fHhQlMFhM zT${?KSnY4j#A8F%8`jYz$?9{w8ZBQ2897@%ZOPIs?V`sz?#`6dnu8+R2)=vq2;vHg z+XTp@3`)`r+(1wuXkZX{GDZOi4F(dlxM*P@!&s~k1_+d+Gm|rb0VhhFOhuXmE)>*Q zm<9z3+yn;%1P8`gIEE}&5{d-6_+M)ce0fZQt4V*7YnPvOrjGvDu4!=3?P!=!TW+FP zg*}AtxUu&Y-sP6nH`0X$tA`x`1xK5cc#+kn!-$WVbWh zU*x>uH>G%uZmv}Gg;zBt4cx(+RVoIFUDnzL65W)77z;A4qK8-Q=a^C7PMAzz@^I zVlj8AY>%m8_*vTSn9q}o&n%;b27px>i(|$UYjE5Xz)$^b18gsCS85{hBcX1UW0bDv zsvaEZcd(y(^)V;fkytcs?$J#1%YBe@aXm3ApLosGx^ApBTM&6PpE*pS=`M2e*+^yj})5JFUk@`CFfz1@E#j58y5+76$m+>|qtV zw*4#9=JX^$$@mLeBtK3>f7+9IilN6N>A4@c=Iwf&ZEjfz2}bZ)8u{{2@W$-ZFXlAY zE}DpI{3&)gY>e&uZ)YJI)8}jdQt-W~OUA_xW52*soa^J)(-}`=;?v3OG+}tMQX96) zT=GfwCh3uE)PodJfyBAG4^@6pmYj?bZphJwU2*Q7Eolj^7i$AseF=5zK3b zi5s?due`Wyz`?f}ho8HsK*aH`gIj(lDG9^4dX*JeYn&SYn>>6nKm^|Y;q7p~AD#l9&gvYXit8?8 zdS~>o0fsSdBZ}zK3`?6#IZiO2EF$$060^(U9?TX}Cy`Bh>ierjkYhuIa8^F&O7MYy zjMH9k3b6i7CrXLyo#WBO8S(Ym&>I2v5XAw)9T^*(Y6IfL5|^k7vP*%9Q^E$kjjosB z8Rz7BU~oc@7)2&QLXOH&Sn1j!rbP968I%}}u5X`Z#rfKNk+Xbx_Y*vGDd?^~9hv1g zp_@|)P+wFeydZXCklww{;WeJdyitg=`@FhAn)V%X(M8-^?@Idvo!ep=l9;RY-rQ4B zrL@qN_JEC1qv=l}$fz;yUXBfP60A3B3jLF1h$%z2L=4{u9l=L@n2pK+H#v@*S{+R2bj&7W8yhTVPo#7L!ulw4{$1 z5Pg;i!2^)Z3p`wNq$G|ML76}1c)+0*EC{~FGQocLS+d-|CO2WEAy;(p*2+agxx?Q} z0Jzu|pgf0>qvWlNCCgoKn#TsOH--R7T;iVs{# z{q7x9B)-mXS-8$}vxCEl@>zDf=%Gm%e`OdC84Wa=S& zV*}H^O0E)(CLn2Hr=Dc=wY#InlEfCS5dKEpQ!%JF+w8ES12S$@~|G?Kx0oxK)i<2*r zUGPW+R6Mwvf%(s=#Xo5zUh6So+g8S9Oz+}X`kGKX2Iuw}w)XWkLN3Pf)Gnf?yhxbl zZP53pMq{ALO?7-)IPv4Rjr3%%^>vyRR4BOY=iRVaLUw_26@rLt*B+j*HIe`TvN*-P zN~g{2vYD!r)^NVrPjRihqXEQ6!ai~qOhSI~`aU0ahh%K(+{YUbe1nTaLTbm6+rPD? zHpB@sGa~UqtZ{N?E2-4mIQx$4U!Rk|*m>ND>4lmZm9yweAF zBLgWtZ;Cnv3^l@-hkiFaY?O0QhR|urj1&`{heB9E1!Q{$9m#@&ySk>6g~SK@Hy;F{ zP;{!o!H%(wi#1_QiKAncq$EBd5s`g-p zSX^e{)`CDYl&VO*lt-FnSqT2Nq^maDQw8Usom>X`=gVLo>OV$9ZdD91)_)aypvfa1 zMD=z_(a?HGwb}HC(#l+~8p^T=Cp`q+L{zM3MPAv#F-M@MXJ+xf+Z=0T^4Pp~9s>pY zptWss==`f30qjd%x-c#-iObRASzgL_7lr6FwK@i@xBL<8MH~F{B-Bl2LmOqF=hNmC z)NzyDNApGmpqe^pBB0BB9J>BL*p4+KaZQvtD2tkxYz#eDyvmd;9vE%ezbTl@-pEw={aMa~WF zv1(X;&}uxB#HUaB6IYbp$aY_0Mrk557p>Jc!DF5NbpOyBHCdQ|F|im?&7JvcXZxpk zrT_%0*?ORd%*0k1zp@m3aetZK4#Pb&$x-sW=I#%_PsT422z>=wp<^r5_)ws);@5$7 z;dObui<+`+EGuk-2jm3Xdi5wDSai)r%`1a{-MZljvs?Bc-u zb(?)7{0;N_m;3n1=aH$~YW4IPRy|n0dFe&BF-e+3r=-a79v^v2FQ;yli*;r&cuO@Q zZ}E&~MHvPPwMXsO*8mV9d?^d(`L)9~H!@VUwKpZb2>Q+v_Q+8N#_Q>QV}Z%ai^a8# z$X-9Pi4}vTjXJu|3E?WhKYfl=wodfl^}1NFl9ld0h`7WxW0^)qX@QR4I)9i>`fvRS zQby5FI{zSbphtrExr*kWC9Fvg zSXs9D!SIk24nut<*C$-m=6Uht-)(HlV+#|l8W}#wH*-v2TKq%_S>7G!B+T@0_W+1fhDL*Ik#*3uCJc|8P$D>P-o^Pc@>wh4X`^e* zVkRRhy|O5wOvGPaKdHR=f-g^*p~=GR>E%e(|Y)0LeTsH2(NXcXisPirzxl^+H)@S+aUvh1WUX8#k_?IsO@5NGj{c^^4^+ja+ zbbe=Iiylypgl3fC2WAhAsqe`wIIoJPYoT;5m7Vo!{ zpp2Y$WSBp*ULin1Ef{M%*=scw{=vq`@%eZG%FHJ^(b3KCql0M@5HMON$!^l&^CK{# zGHPX%Cx?L;bWb3qsXbJdJAVV*3V10~0Lj5J)xyRKp4qU9nfUtWS0^?CbJy(^db-UL z2ScJ$zD!R%YQ86q-S?+Ff~)Q*;cB5U)jx7$Lkc4=syM(-JTY!E9~aKxgrvBY zwFz-w^P_Jo(z9K73C)P!q{yKzKd)XbTyH~{9RLuaEJ`@Jtwz_vf4q{RGnL!pdb}L@ zNBwq+jO?|^05kInUG2bM+>_rz^i;~ft!G2dh4frK(Ta^{U0n~{Jn$EplhYoxf#}4` z6}@VpEO%n-xBmD*Zbfv};%oOF-)vk4pQ0&z?Jd$b{ZLKD(|g! ze^&AN`y=lP^ly+ke{2+1R|y-T{{G+9kOrAJeY29j_Xz(9kOm>xVAX$ z-4=T(-B{gDrfqtgyEUymNE=5ZVLFbwS5t98 zx7w>Wx|RU5ZGqua9-H=UuCeZ>c~amt#zmPy`%ISSzUiw%=1p4eoE~>Qsb5Z$Lzv!L z*9Pl^0sSt=rf!NIB}^4ePTT-|DUl*}oar#%Ymv6z!t`=bQt~%=j3A?^v;=X6%+BFI zY5B7}OsQbQ6v-J3S-}>_PbT8V9A*7=0PeuPOvnv6C~qA+aXIfX2&Hx89t@DQn@-?( z0I{a;i*WPCja)$1-=LP6i<}uRWMFUH$Vr@%bw2h3nC|gB`NsSykq+}(4zcn^-qefY zE`c19U=qM@xfg*G5UmfSn~&J!BHFfO(fMeq^`L=}1y3$fLzS!M;MjU{c7g&Y+#BZR zzhN;#bQvHm8Rk{z3`MZq8v+Uqt5Qz-jX4x%pHjIkt((J_ZE*Y`hhSaPeW!(Y{7zX| zvL$7|Y#U-c)^OsnE)|i^zat0xSLxL%^8TqKj_(ia6-S8U5Q=3Ib==;(@^81>??8kin>j2yc%7 zrr#jQo!H<-;Mz(dMDwGwz-0#>j_P$aC?bwi30zLTyv832nVELDM90TQ*t56 zpo#glsRp}s877g(3d5`V$V@?+2aZERHI#SD-WBUpZ!R&I3DDmolhMlSlW)?a6|9(Z zc>ZLmWlD^FiFb468Ud53qYvPKMC=kog`qZ^eja*`D0l7+!!8p0t3NZQEp^Imdyim% zbUp2ZVYc(}F$7eA{YNIr1Sc7dJC4IRVvW+Z(wAisJ~MkLdNFheM&({m`!? zzb4)B6=w>~oE*JYPL}*D5sL)JB!Zg(jxU+D?|xISEo#%=3}D$C&Ot}c0quoTFC8Y6 zGURYGBj_oP;*D!n04q_Cu?A?kN69l*s80JZ}>{TL6R??d}8+zr-w3Ii#Six^QV#}*!_tGDqm*uiZ#p`@%$i{zTpZTY_Suael2vZ<82JEnDA9Zo!iaJ!c;$8Q!#_~) zJAbISXC=?69X&fG-#V%Q6Ht5CB`CHw`RG6kS0F5JC+hgaradiVeX)I)Zr6owx_N9k~^`4|x}Q^FLjO+zm(4paA8; zCCH#g@lZi-L!aVe{CbCXfwg3>vE<9-nZjg=w`ID zxSjPqs03OXP#ekGq6R1X?L%|}CKD{>L29aCLq^$as21#DDNJS2lFNZKES0C=eUiPykRQ$K)Jr#||L6x(V=z4JV*2&n5tc<+Pat*H)00|0Hb>v~lsK)ROWt zv%cvX-eXbD-Zpr9CB50$qhpRdkC4^cB6>sa^2!Fg}srWKM}@nk@40W zbCOqAyy$e5;Rcye>VIVb{0U<&w=zXZQohV|L2Z__Eh1{H61%+)OHHX+`*4SD8^gSp zL5WDW=%~r4(<49=< z`3N{BCMKkP4zMe-heFz>ld4@g)I8f0QPW|u&bz1ch^mITf_X3~hx0x@c0u>3CW4wk z#JT^(1_KWdsx}^{5D&rSPO3NopKPCgV$E0GP( z{2GZIQWYFAoB^o3h&Du*aI)|kgG1E&7wb+7N39+HNdW{GIe^wj3aWPamr z7FjOXStzbuP9;QEO%aoJ&u~yaxG!ogKIC|Z+(*VA^hXo1m@GX&#z)j<3Ff;Y#Ug*J z2L@^*orhgEoee$7o!Z{bA^@L`y<1^6SNUAFEj*FBp$V7?MjKIw!a&S~p-THtWVD=i zGJQ}RBlC3mHKk^fT0&f09A6szm~|(xjecNDHrYVA6d7A1t#VcTp_AWAabQwGNm)s} z?)hHGC#Po%kTH=Zz^a9b$Qm(=GAxhE3XqRyWlIXjqB2PG#?ezPpum@tb@Zmx&~Qyo zPiROwPXaDXNRUANiI`b{!IX=Vm&~2j4Lt}yc+H*3HLw4DA&^@Z5q3Z5=+sUv)@gIo z`r>DDKO}AnfsYgz&WeHu7H7GVv_r}t^=uo5e2N;4R_5vpzzDgoP%Y7J!bO!)RaKXU z9)d*)6~T!pqKPR+0fYDhjaG1P977`$Rg}USh5{6g2t*NuAq|a)h)9?~k&)qr3Ti{P z+X|n585|2Zu)7v>T>+0vlUY1xZ=5Wb9fQM;gkw;7>NA9awsiHi&cx`sxys(RHIp!B zFBbmia`(XQ+K2~%8SVHQr6KrUlI$N;rCZ!X1Mu>iJI^Uczcfv$Gc)ZBB91%n&%>L4wceu7fjXrsUUG7hggZ z*e;jH5LJxw8o|UKk^0D0m?dYQlX`?Bm20Db^ktfp;23VFV2~rR89XVXChDz9AtEqW z-ZMM8Beg_dF~M2VtmpU?t!6VgJ`Vi*(m?HB*#RjLAAKuC9AXBHG~5_sS%foKP2f>^ zsAV@X+}U*{9|4~4$*Fnuvs2Ne>B|_zLBkT* zDYxMy%Hy)Q!&&1%0EUNLoj06YMDIlYCYaAC;cVHa)PrRkg22VzYbm>Ii{d}$^|ck> zBv8Lz9?os$Gd^@U|EOWRC{7wlWR11#3mi}-UfUgu!1ZlmpULQLQUbCWhPi{TzHvb% zrUlL#4a9pP$4VU!fWIce?g4)>gkb0|g6 z6AtXZ4MO+j)IWSW${&A2cT_P-%cf!3=v2uyl=+;Ck-Krv2V&s{NG~FF&*xofq0C(8 z3}J2ACgHm~gaP?D=3-K2=S_}?`%f9T@VXg^hUvm~d@S|!NcHrMo1S4G7b;ZoEXWpY zRpGrweAj{V97I;^NYW!SP+)Bzm==Uva~4$&@>dHlUf$d-Q+>aPmAl$DcKb=odpT$X zu==->^oPVI?LzIdq;E>IRZbkr%A(ap9~~iJG%F!+1@k@gHqZR<&#kFu507_xWd&kp zN2QVH9|P@cUnl@Mku~zwxT^3kFqq|9h%8@eD8tP1S)PmD9k4GZbMekf^WfZGUaF-< z{yM2>C{*z`Xn)4gYsRmdI7~D#b45Ld@KB);ct$u)*rKK3^_&+4%%Y-;pcb<-Mjt71 zn5@W@%SH_1;3nBR_tzzN%0!PjIH$?9({Dc14uOrvMk$WzImZoc39!hQT(C#CQ>_mF zHKSauuBst51MYtJ7x&1%qY;HcU!uks-Vca=hHFuga^o2QH$U92v$)4A9} zHGqFdQ#l%F^ZKcTsQhEWs=BAxt-OB3Hh`Gyk@!7AR}S=RQC%&2rL^W{m%5SHJ^_f? zfU?>lhjsO%san#KO{zVa5iSaoPi!(zWBlR}&QG}NphipnNE_{>%noPF5teZ|>7q5Q z`!x2VWQcdA$yyo>{@}lRTRW(;rp~&l&A_I5i?%FGJ<4ZFcC|<+in5Lr=Jrnhiw9L88A#QUe3XHgEw2fl(OPlH9XmEP5i1%b#s#hh zjNPh~ebYz>SJo@5x40TaQBVx66O=*wc;pOa^j-@nyr&f!b>aFui~)t!$2jwTsSecWJTj9d=|$g~Sl?TVR02>Ndr6H(4k9wlOq`B*yVdS2FF-I`pj zrkJPaflv=IyHoX!CrCMg8lIXkwrU&te!jCJU=j^dxk$eVEzol8+CZj&kWSMLmz$qk z8z~nb5w+A0X!{g4+)8wLx(UrR$v~5P<7;avLV2_m9b?nMSRvT;5iFW#!su1iwgQ@Q z;Ry7<>7#r4_Al&%g?=Jx>7k=IpjBupZx4D9Uy{!}%ab^98SZ9sPd3jJ4G zKiy{3L4su+Dvv-M+f{}{bi<-J5%YZ@2n2A~*^IYJ@t%#?1llovfutMp7#PPndRU9# ziVmGT(ghJu0I+II>P2FvMDw9R$=1_0RJTwwyep;Fa#&yYO?C{Jb`ps#yf z?Fmph1Z{M^3)(Bbh?@NTp$d?8BL?h+b6tz!y%sJ96tp6*wjk%;h{b*ejtM>+6WeZv zbKHQB2Ux?i9;$-Fqqd)m>24;BwLmaHfV{CL&+`(&kdQ3C-kDN7B|EbbiTkx7_g;$u zKNp$<&Mk;Pw!`yoLhdYxPl>I8e?VgOh+l5R=3ffa1J0d@ecOXsM}E1Q07oWjs3h#`(M`$iacs#iASGFbPJ9gZ^&qU^h$L? z+l{h1IZ7VCY<#LCXEHuj82YPujpY=V5EdQ!?804cor^$Zl@EN6Dh*2yTK7#(;JmSU z3qn5rzCzMFn#Hd4H)Y|Q`N>fb?<6K?<&fj`LsYTOTb)44IX{RlByLK_lbR4nYjfb$ zhx!THWi7V+Bq}*35e{N>&gBB}Ly8RK+_Md#=3Hw?IWg-!;rbR`9f_T3t(Z;b@6l{*Y2zelQ)7A+WfHhOGDqq*Bnaa7Z4Oh9a!59Mqhb{Q=ONwNN3j z_)(y48nQ@_pn$UZ^2UooYhA55deQ4A^3>kI09bf5WB>)17g?*v2oj|T8X6ibAt0cs zSdjtlcQV9pwl>ky`m*1{S`KOD2l7KkK}AMGK}ODnL_-eP&V%HkKjqNAB$pEJhYDiD zy|+IvOioUANw^G>ksZ;f(lF=b)BxQ zRe4|r;M*=;J^awgiP5=|bq|J%s13zjX#yUCp%G>Ul8YFIw05u2@j6nFa85*GjXvP} zjx(#E;GskeVKV)om_;B55zs|Y=+gqhY4K^nK(JTc9MhP5@&a9_WEs+w4Qb_1Sq}EG zyUFbFcWY2H@znYIz9j))?qT2$_%uWxI&55g3Qx!jTvACniAlE^+N#lX+u_sBX;XQt zxzc)=V)E&}BTZgbMi9EMto3$}xe3xqGbK8F3Bn8)vJ^BfCvOxCA({Yw7zSJzEDAg- z5EUgLXx#cv$qmu-V+#g_G$bGtZWjW?`tMppjr^cPpy^meV%qv(?5@jIk#A@EH=T&; z!w&jsr8@Kq^mU>`2NseePKSr+?z3?gS_fyTsJ;w^Vs$5I#+m&}VF3LpuQt`UAUdlU z*I1ta;eOgv;+R9a4F%$Me%L}W8DL%m{ybNbsq@by5!6I@$olL{M#a_paP$}Dn(TFYlE0()3G$1|gPpU#oX%Xh9afzip;_B!|rsj(abr{%h86>4>Cg|hE% zP4My5D2_66sR&vcG@?-f7c~ZPF>eV}>l*W#HwnE=STuoCQhG&_jf`5w*BrunoHexB zIMJ_Yc#{?37LruEp^*pk1NHeBAKUq!TvwHr60bkN9qArlIcNTd1#qG-{{z4DOWs)T z=zzoC|CdSlPra@GAP@gR-u@R+`wuexALK^dv{ZUrZr!pMi2Ngv44U#sA<>=`h0V1n znrxSbcYo8nl?mmO+!SO97{zl;7zZ_pW^VT)&3nWhiAR<2i%IH>dhsWMQ)_-^{qRrj z(VTK}1oAZ-$N50qa-OjUjJHH&#pTDerlxMq{Xu5uipBK21^NTFtCaWg^;2fs6LQ)g z^0s?Kgp*V9ob6&h6G?|opdsnEwVUMZ5b+9WgbrJv1^vLBL0Viq213=u5yOS32{RcM zN16SA4O^lep~fy;JMU}bD5)Uf$LPa%P|SYy)|-SGcyiXc5F0#9<8&}Yuwr)j4Yid* zCeEp3r>cHhc^QL@7MC=cHjCJVXI4?wK67fTJjZ)J1)Z&>^fCArP|ER)6r3>)U=9@z z4Qy|!|CymRmB?seT)Nlv@OVU@FBe}B&G8)Mi;UANH`_npZq62=th-9#!F`b}uXy;? z)zOX1^{a-0>{&5ra4M)OI`6Hu6P))3&dQrdAu6cnSSBXU;3KFZ>m{o3RKjx5OcDCIN;Q^XYswv4*ygi?kugug z^9U-d+0=7PzcNL(g?-sz_b}shVy9A<7dJ07+mH&WRvCuTvW!V0nM@#XF*8$4C{wdr zr^L|Y#4x+txBGTN_VPvJvim~mq^b+KEhdhS#&MU0ijXP>*tqy(&rT(**Wh(yy)YAt zHENtt+2jH{OYdZpO%ba0^l~xP|1B%T1yjWDFp6E-^uVnpcL~9#XgguE-jdhKKL2Td zA;uzZ`P)|Js0fJ$$&M>UCxe`lQlB^>4UQ6lhC1aCY0^uVndI1ob>3eFjkzM91ZkU5 zbZG}G>EvzB z9E)1Y_!F$+FZp^T?9DNG`)+r;9Z4Ip*N><_t;;%0z?U-Y3ig~8;=M#_+|VvrS(sx& zaF_ybe3D?>!!7#p1wl2~lO}}6#4TUaw2Y}DBe1@WiMOInh$d>P zSOQO{h#(cGUqqURmL4K;GvKTlf~pFz=dj;7Pt*$o5?X?U|`6q#U1Qo_ps6f z=%}F*l<_k3G77?2J)#q|+p5eF{UY$jB*ij1KQ(bT`?&sf zOCLfNPCyS90Tw!fTXg&}wRj~F;$p%YDFkq`=opP(L1Ds-fL^T@f=ro|k)#|-{E z8(z|O1dsE`v{0cp*~w{OTM~M74-Pcel8JZ<40zicYiyU5pf3mD9XdcBh0C(Wp;$~V zHS&hJyR`Ue(Q%}!tdkgRJ$>((ccA%AhWiQ<(6`JsX$#si0gl*G(dazo|I8BV9p~s&1e7B zzmXc|V!`-`SJ21wWqvm^#-A)G+9uy+1=||plf2RjCGgasKW)}omU#B@v}dfB;z;d2zG7UBs=G@B(M{ateO zMra{dxKCNRcsbKM9hsKE?Y2>{Hqz*Kf@p8M6}Q4b@@T?cNBp8#$^?JJS9+9j;VZoo z+8XQzIU7KF_E%BXf0Af#w|dPq8d={4v;L2Ti`)qrk|o8;1I)l9+BE1T{XDo|;F+Wf z%>80BN5)XMm<|5%i|`HDG`)MyFG1n=SAr>Kx6((MGqK>HfoY`zACU9RW1S^c@gM_D{S@@m=`lRUucc78X)i^C?|5 zUmCK4`{|_bwCZSiJ$Ln9kD9FYNcT%dt)N9_#z1aArbTxb7cSa3H|Jlf24V#V#R^qw3{zr_EB&xV(c2%3-*DWW^WT2{!Zq>DV`v2E5#%1+Np78rpDQ6-_9}}XG72$ z{L664QIDVOn{@wt$*;l*MSi2yxN%BTWxpZL@|t7UMw5C{0s~4DwNeLLnK~g z>@I(*F%cX(Sh}U)1f4*ramRd9mrsPjH+Z#jdZ7ODZmCX;>J46FuW-{l3H%QcG~rXqtmFrq_>rr#p0$=*AYQF@_Tn?W9t?DGRMuwCCd+Ld&=l z$r$6m-_RHgWj9=E3moecxW7!Ym|6@abeSzSznlSpLre_@s*9{H{VqcN(9=|a}mPW&OPH$>WY)Xhjyd|)&=g}nJCrbQ^ z(xcCiYZYc6h>bh4bq;LUldb-gol6tfvU_VaVQ9<`zZf~Gled)j1#EIxUXiZ8XcU`K zrQ28meC`e;Eb>*;Y%~~%iWrGEXodW&?VHJQ5ao!Zb^3sVd*B*e5W|5^kSuGhQ8s@q zM62fj6U%@Zwu?N=DrZ}u!fXzSM#<^Sxk)x##->17_PUvKU?NSM#h3{g=(`^=7g*hM(;*!3GLegWx(Jhp+ zeqs01`OymGo~GMK*K8vh#sL!KGFknlQ|CZQUz?c{@fc;cCQONO&U}{eras4+{2lzg z#+v$ZSH5|luwQt903g>mBe8mwI|{dVkg9&K?HzFp!SLSJ$0k5|myNL6@Pl*_FEt;4 zU`3w77~mJ3`^3}1sHoZ~SU~;6EaUX0eK!d^fg?-#wDq2{(0?V=G$Sthz%;=vmc{jm zGX<_LB(*VUym79X=mXU;v22W7fda#jZ78Fl&C{3$NS#dtUH6AtY{yRc>93SuyV*gP z5g7ysduuygyW71KoR$%=ucbQ5xhDX3yExWuX#U423=1fgH20@?F@>j!e_|c`#V^mX zeq}+z)pr*ul@7$lMNW6g(cp_+coRBL>#+tFi4Mh2bW}?nQcLEvQ>%=A8LY?pH31Vt zy_kYipe<`x4*RLX4W~EfJ(8WkY19DTeYBF@d~-F`>G8=e=`d(f8gjL@!r?mr@0@>D zqLKV@+-7a)y+CPziSv{BAn-|6alt<5Fj?;qk8GSRif_t9+YNDo&(q~nviB&2qU33- z$}@nt{&timBsWUyj^_!I{&rGW7y+T$hQMbV>+)nT2|eJ9G=X(^-(HS1op?EQXCp%!mgF2(a-_ zIx=-D2E{5vbJsQf@FT1t5#$UdHnK2IJEw%d({eQ&uB&o_PF3q>Y4eB2s`!P`PKrWp z_^+K>(8*dWoILYdDl1NVH_{N_iX-0PKVqB5txz!Ex?l&5HUQxG6<)b{9pOVamDIh2 zYlwPk>xw@gg4m+jir7F7#x26XXiijje9q#$$LcV(VG@Clpr!8>T3C#1+mS{tY^J+Dlxl)s6W!B?j;YEu7Nj*fWYiRMKOC>$U93;ym zJQcj4n@1EwOrE#VHf{}blR1Md6_#zj-q-ncR{ZP@{i^0vJB_>GVjBNApA4|Ck;iF3 z8@%*dhZ;ao$Xws-XAL`W2Sr!HJef45HLqw>%m>QOIRZJKG5tmu5k~qY^wOw>Lo-8a zg6+}8B-1Q6^Ho0A4yI4{_FW;_+Fk|Khvpnf>>qo|G;owZCCRQYl=}w3k`~P{|M9@4 zv8UeL+t7L=giMcBM6tf+TwdRw8+K>;9o*F7h^wh7>a7iuQWRvs9Xr!09}DB3w~{?4 zb_87~1py8x2L&~Yli|H1J9+TqT}P4c@o+R%o5K`#3f$DX;-uf23&^w=1OQCAWF#4g zhEydXdQN)Z6DptWQ60hEtjT&B1LQY2jn5&Ul6Ve|={vDfg<`mU=B1ff)_@-+#7R{sUAkTR2_x)1K9Qx%ZT;@77?aI^zLM zsvoIN`?}8RkZg zPL4_DyWJX~D`mJhRR_7Pwvxs&-ZG1)`mawep~Kp!SLtPf2ei~20}64pgKJ#AqKRS= z-~j|^xTJ^W+uYvyih=@c53WYiEq(VY-Xg?H<-T{p7}_Ch3#Cir_o0D9Py#NT1&iz0 zRKJ(7bwNevvqO?SrbruZNB35Mo8Z1gMijJCkC8p*j*q_X6iVXhCm%LQLW$xb4Qj|2-px zl}n-cwKBDX^4Thq*vbfUE^P4iR{zBJsFi4=-5EszR7W?noUD)$?!h09%IG+{Ipjo$ zE0w)zy?vM^T!seiX@<4GOw+C%6Fd;Iu7;(nKn)Ux`JTr<1Ok7JRofKTv4E>cn3QLZPxO{0-3Xaj9JAqdEY7H|o<#@zKCC&-*uPDN#Rw_Z(Ok5qm% zYb!R|c^sY}FW(1EQVTuJjfTps&G}EGt8rXjsg7Y$*9VU}?8Z`D5$k%@R%jv(fXE!| zI5@b%A%FjVRkadlc$obnIV1u3XJ{S;Yu2AN7xV6eYtY7>OmW-#u!_?yfgBj{BtsrK z0SVjmO4%In_qeJKpZ?}-zE2q%Ap9C=VR*rP@HRUO@jRkoPX4*S(QIEVr0tDjqi*XL zVR~O_Puwu6Qb-yKN)u})6QNXm8ew)m^{v64a!?z^aM9W?Hy2WcU6OV<1;q@M>#WkV z1xSY!l?vWhMd|qkn0B~8m<8avK`^v8lyIrF?u?MKB9m5R=f05OYG@rg1NF?8L}X_dX;^#8%!~>Mt3k>3>Xj}yCni){b zoM4Inn(6C4Gp&G_nrS5QxpG*8XsL-ZsoOz$i$0fM4>6VE-- zh5aosCG4pX+2xgaD@mb>se%mFmJkC{q5{P+;A~$$&o2gHx=K#Mt@dfB);Zl`v+*ovnTK{&-8zPQVm8^ zj!Azo>>{Xst=*gW7J7EuF2j)}sB_UH%%kvRHvNMq_6#hoWgjibHU$Cr>Kw23J93bw zB`$yU)p6R%5_fy!Hf{>ESm3>m&7gVZwrwFoJA1fVL7`HP;u!fXTz-GNj8RXT!Lb_r z9XHeSF!!e~b^?H%%{?QR|K(7)|HYgtr7U_zK|?JKMh-M4k)|N|*^R|fy-?bsZ~E55 zvf6ucUe`o-L0lGF8go7KRIYf{X1Q_=wdzvZePY^Wm2r5mEk4HH3`DVlJv9FnQqPM1 zMxglydJKuV9ph$rWf8`sCW#r1OdT6;av6e#xlu6gvIV$13MYEdp3kb#rQIqu;=>*$ z?h>99NsGXJJJ*wf9!$e4FTaS!YeCN91zj1NxKMVJGEUqtmccv?PPz^<)da%K#2QS> zVUS-r&lyASk;hEO9GcRd*tEX_ja#E7)v8IDi8M4bnVHxs*R=QwjZvyLF!BK{Bx2&c zqD^1N@PL&M@sr;A`BbW=tPuKx@8Aj4Fa*d7x#>%@;t1z@)HtydG6Bnu%(o)fe%&zf z?(+tWCSsPNcxl8o{VgxZU_5s|M5|+a3d=w*WS!sdJ?1cNsK;a^w3HBr`iIs!WN(uv zm_{DAH}hjRQ-%{ojnY8=PsHh$cZ=&1)>g66NT7cBTV^MK<<6&?=}C665C!;Xdi`=tn~RhMlq|Ej5Lte_Tnwfw1SqENq9 zqX$JM_H)~2w|{<$Z0iuXE4}!-pkfjnPR4uA;a=Emf4CRBHY(f~65XNCe4HfuMWvMU z9!Pgj8e4zn*9~&1oH&zFgf7`&yk6Mh8zd5yZP-0*f#cb!f^_2{zm`5lg4m73|8M^v z0|z5M?!OHGj*74b_hsJlVT>;L-}qH@q@9np1)e$_lBPeXRd4IEV|nBG9+jdj;$J-; zV(H$4!jAM+-we9jHwjKM{;;}~o5=$dcTK_*aceQ>Fryc7n(-6UrY5C%p19c7r(AH3 za8ztm;3X>%rU?3$2s{)9ISgX2IJ~Hs$iEc^WUvYh{Gb@TflL@#uM1fNS~J=Ps2*(% zbO9ZVPo4?a@dD9riE`=?_gu?Y$y)(e>M|9KgJ$4FAj3J!iT-m-=uwE60zj0Q7Ll9l z%!boj#xzCFhg4(7Q#od!Cn5Hi51^;PdmnOpL}Y0zkJ>flVs6(GNBoo#C}f5I%e%N$ zUQqy}`eBH<{#&^et(*>20>S)EVGR#4n#gbvs~P{scfBsbk&exhK*&$wq#3bri*`<^ zedpL2dmccoO<|%1KM`T=FMwX;h@&9}Tb0<%Su{zi=M$?E`YK5{g(l3e7=2|CUlUG4 zxoMb~I*6yR9)vkF2;F~45Ngjpq+JH^Eg4&O1_A_1hDd@)hH&X%o~>h-D}WFah2)jO zmcW$7m}QEbj1QTj+Z#xv*TLNCws4j!?0Kn}*T_JRzD=|r5tz*9V+e)4TE4D8!_b1VVx<} zqt8|oCo+v3k0W<4Mi0M8agS&bkTqbJakNPd=Ms5yKsnbM6L&-T+9-JjVuzSi{U z0d~6!Zcd6SWFRvAhrsmKadZ?@A0niLgajkJj7&ibf(;y`Jkwcu9-?-ph7N76F69-k zMZe%05^;d|%XxbhiV?s{R}AInHp|Mv);@`4=5 z1L$J#6hZ%{f95s@VOpMs6Wq2TnSDK4<4S1L21wzr5Vf?`n`Ig{+^|?QJS|#B=2xy% ze|Tm9?3ty*#CVicVUva(lw36mh0eBcE|*JwOx4DR_0RYDM3Wnl^4E7Pztd!+Mby@P zHBVC{Ll~#9riiUyvUmi2)VTk+5asrIG?2RWanUZiRY$Sx`S#*sC8Ki<;(c<>k56P- zI@@D}&@g(^G`TC`VBhW z*ON9iW0lU)y2S+VCTW&He8a^)D*!)h3cVyS$v;1o%M;aHRh3U4E>5ce&2YSQ2a2f% zgX-P>0DUh*xH}6^w#kglNl5PfN7Oi5xBz>J!t9sS6{F|EV`t}dV;91|u-6_@GVM9Q zY8G5StlxnLPEJFO*DG;YY}0&yz8t5glx5-!Nd-|?W#ydHD-~z0K-=VPl+%sl>EOI} zly|t6oU@Vs2@hfEuc-D6sB|8Z>=-jg8Y8fT7F>k~U~IXp81vVizXRX$9NJ(74`+B? z2EOw1N=Bwkk#GoCZl)ZuavRn*?}049Ny#i!z#vD_z>ApNB*neyr#l(<=%hI@$c^Qd zRInQNeA)hWJC~|F3F9LqIi~yenC&milKAA_#A$9}>Jc;-7r09X{F)tzy&AVMj=U(2 zaWkgL-?Ai^{;;&yQy!lMFo9Mo*zXpajvsBHx0%J zYK!4HN-($`VnD01Ey9|W?xH6wGSJ#}9#pfem7mSZ+hLkcE3{sH&2+T~E9uW6)c^!bcBpRIY6w&Sh z%NEsN&ry-bUk`Ld_o+DdyP_f6AS<7YlywFFOgBXkTMrCYQXhv-lq%o%3)4pp#Nry2=&2n@q~Wy);$+#{&Y%hLd)Ir9a{thQ3&%}!_+ z0ZjHvBj*xxh+W&+Sha{FIOuRn=WIil?(y%+(;YHqB;CM<2&1WL!}63bP-vFDX|Ako zt`lvV%G#?al$!{?ujHXvqH>uE)I3(!QA^w7M-Q>*;B?_bgjFf zc>wfOq4(JK88-1I!!JH1Yio&C#B(Fm%`dd?*27W1Hp_v^;&t#zKvi2tx7Q*d9q9-u z$u4WWL`0I}7jB3-zp&7T>JH1%CiKuD>}}J*ov}PVkc2V1F7xD6Sk96EY0ON6_kohe zTR$^BTR^&6)qz@G_;{RM(e_R1%3E+ymi0Bcwf{4Ax|@+@pCWlK%?D^jRPZ&8%(08L z;Nwiqc^5#iylN|Tc$l7WjpyKq#I4Be9*g z9<>DGhBACDO#Me!mtCBOkpj4vh!S(XKtukgzl2_(eUlsu>tQFJzbPhfN1f@oPc7G3 zY+W;T4E$_Tt<8Dw+*yCPFb3_aaiQrVVdo*zVk;nOCWCISOMTpBi#V$vKGV$?Y{79u zdo>TIwo~GR%8~7AM*mmYqAS2IBOuDBM~8|XhVDdO)7hAXv7%0x*2lTtoqc!Ui_7Z} z+3O2Z03o~OhhZ{a&t6To+9i0rwExLkg%r+I;rFEejBpqD7fRc4tR#hO?)P(@N)B4gz+DO%y^19AE0(-VrV=#A4(uI7NruOw@W=U|*#xliLq$h&C>`V%yF<0`c z_n@?ssfdDfXl~Xg2OD~}nV}2o4 z>NnHYhY6m}R9K68i98qSlNHxEld>YVLa^UxB91JTUiCJEE3tdEitvyIBcSP0F+2cD ze!SEyCWQylCjLvCb0*UQQJu03+r!c*KECQL58htY$f5;a--9H~_H-jwN$r*Wwbj(hR+E&z& z-Oc!#Qv|)LBL2@DCV0C(4#;KH*zy3$M_G<^MlHKJ2yd6{-3xqFV{G-DxJ-D~GkL?}Rs|3v(b;X%i__hd1p_n)cyS~i5 zlP(1QV@0{(=3r@ZhF@exC#}FJ9Guf*1eA%bky3Tu%3}XAAL8AU+M4o=?=) zk)Mjw*M+#GGtdfC)bUe^jO?H(q-~h)gK5PQ$hEu^ZjE$=p1q*^h^foBFNNRyRzWuH zKU0BRO|m&JGRr!&rnS(d_ijK;JI@eV^lN2_FMR zZ0ONcp^-2w7eH2x+W_P)+AS;uHWVA@{|qiJE>m7(PA+a9UOppZ6H_yDZfF1(yTeFp4Zp9sG)P*T{v*>{SS}Ha)3LAzE3L&xmG* zPP!0i>a*8_Z@{orssVj}CjQ?Qmo|lNZOLwbqrLr?Ao}@b`upq0Fx)LP#3SGTJ#Zoh zFRqCQq!{eGke@G^NH<#UCTRKs`se}j_66ekC&b(TPY!(ot^fUh@?|fg8vpun@g|~+ SkksF?VnIPB*CTK&{QWTxwr$(CIkBBgoSE=@|9|gYeX_dhu658ySM}X% zuMo1R9ulBwW&Q-$L0&*YGPnSpVxLl=yK`=%K>oVla51f?P~^0&jrX2n^rz8>K%F%z zWS|w(ao_<)(0@y`6@jKh`a`%u1Z$(uM+vQmA`DhXT1ygNiG}U2jx-l3dytCX*&XUG zhIb{9dUM{_oIf&Iz!)hC(EDJg#U;uaT#x9a5&+ehYP-q5oXprQ6892o^opzh{I>Iw zLP*o$Pt4v@2LBs!m?YBs`XBeXddL;r9T<(s}IfFTpP<141AxHz-@?aPuiVdR8 z=XOm6OU6IcBV?niulFo*L(m>)Yo%e>4NZtKDb3ERqT*iky1Xi#(5Y|3R=44jO#d|1 z&;fN?S}?AhSZQ!1Fqt z8KSOL(lZ9L;JPCOh@(rV(~H(fhcB26NlqY8x_2ppb8b~6s4g;9Q)gdGCJ36Y&K|~D zqNV#xj$U5VmL?Ip}%-S}3XawgeXgE|cREGzZc6vMY;U3kV-CdjM z5MgTg8Hf#FIAk&=-Vr82U`*a7I0(p?&cC3y&H-HvX%}bDtk=c)KRG;M5rC5k6hDGAc+Rtd2o~sMbL^)eIuza|J{6iH0*NZn z-my}q86CFLg+Lw^l~O+yl(ND8cQp|Tr0-dq$&{0%aH})~HIw;)X&R1L0{1P(oRK#(_bj1E^sy zga-r`+>Bl@=m?MtDG*R>8Jj?4g251|zCZ}Lf^8U@fgligNF5W%v;lC60+D+N)BB=q(BhZ%AjzV8gdj+ddSrA zeI_0g*_MU}zALl)xfHJyQ~gTR|K* zIux0MmljZ33v4Of1_KhlW>DxwlR#(?5KuJq5TRZ3wAg=ay{q7nD}XJbEk`mQ_b)}7 zyj`PmW{lXx!+l(myM2_xK~rZ{Z!ozamcfG{Y7AHen}OY4P$+MZL2!_a%A z5^oR?6ci?O1sYrv5Y^BuJcm>dm?KY^A=(8F5h_HCiF0ts8Q3v!dW``UQCk`anE|mz zmYzmAor+q@#CS8sHz0!PJ&vKs((A17(FIM@SsrbUHB^V?oi^{+qNf0O^0vbok(!#(121EfE6!gachR%Qr9syZF zAU?^t2*XN3&$b7S5b|Tm`ac?4Ji37ExYUE?#;}NUfM&}5CF+Nj&lssqS20QeY3O*8 zqefDp)Gy3bi>4B}u@*|iqTm&i-X(Kt#>yKL+*CWW$SMA&$Ez*Oj=_%aoTlw6ohwNX6ejZwlV!nSYw+%ZwDFhx_FqV^*q>d5)Svj8dXe%vmTx?w9gyT%vFA)J&tL9)h&CVeUf|pHo3{?nE^xr=iy{C5utFz0c6I{ps z@I~>|C4Q%}x5y_t>Yh#&h)$7VI>92}FqX0_0oTU^xHF^E>iV+~tni76TQcNlaM+Tu zw664PF78X4X@h$`_Fs*sS|0b^)s=gMtE!ur;XIyP4mV+I(lxP2wehzIwRIb@ zz}(b%TS;+fNpUP(94qO^efwWMO;`T!O2$Ri=i-Y3GsG%l7Z%M_Ip|EvS$kSLS}i2l z&8EmH31PI$*yVW2s*hFKb=zlRX&2>lVIy4sA74j)6*IzSO4?CA_}hiLo-bJvy1L1y z%&YUtqcibtI@9zHvJSs@i$c29om$&Df%gcdaffEd2oJVLl%!{ zFAJwS#`ifZdOLfxo%C*uj8o!M=3n2>P*?6RPd4>gDJ0ZveEq^}yqm1@Bk{=+K)A+y zHOYa?LnV^hWh0v_w<~B zORTu&#wvEG$N+*^bXH=@3Vw%$JZ&&(Q8k|p{gEl`#fjfz5$V-1MKxpPjjhatw0NPZ zgZ4=1De|x6SjK)rzW3&N=-G1jKx$2o!q1+nKdTIj^&(ejmQ@NX&g~9^W$3Q=qEJ{E zGhzhwIOJ9bo?2%Kw|+|X5vphNPb)9dEuN*#S$r*ea^b5!;>9kDBPkK%J4rCRfAh;ob<}ap4{8I-?Q!X_=PMM;xxS?o>S&hl+KZA0X$7yM_O%H zBMSxn!{Lz7a?0#!ek94l9`8nR33d6%rrS#-SV6%xI{hHdpOibH3&L8*D1>`5*{Z)( z2C?la;)s)oIk?D$l6*7WH_^Bb=}b!{OZPh2G%51F5>;eJudHltZ0$aD_@~?}JBWKZ zbB~h5+>Un~%?L~OF0NG?fo@KB?X~%r_zv-rck-M6V>hq=Ww&zo|AV;y6W0GHr2kKZ zcmFTnH5*Q#^kjK!JuUt(=)GBBvq98Tw{^US zVw&|oH#W9(Hw$+L(94&(KfDlMBTl$$Rn1#YeC*t3e!*W5cu1&?S5y9LO!Jf$oV1ov z#I^Y0Q~H_mqmuXY&V(_Yb&SN^IdH@&O|a zOJ@wCDH$)yikeS1Nj|uuPOQe9ch6H#)yts%yfQ_34>a@lZS8n>mi)UL3g;)2Gn?i3 z$hx})C`K|SEFvW>G9znT0{4(}loQ@$vdYoEbGlxQ+Y*&EN(~vPP89K~mb=Cuz-#I$ zdM%0N>;7iI4^NMCCCzafV1@Xx;YoX$(@~)HOQ^l1MMCaP_T%2#ML4OKm@)Bz2aj=b z2{3!te!`?OZ+ST6M70QBiIR0o+l;B9lbC`jvQdiztSusT41yCb!dO5?2U_ zG6e}CGm(?tn3DgcMjZ#`N>h^G=WHlW@Px?_HXFd1nMase5p#4A+)p0^xtjsDC|I>T znZq_Vm~cqpAF6R+pG`&Ht3{uBsGQnIFcD}h)0{Qh|JH# zWnE7;w3d%TH6OiBa!qg9rxXnakEfPEy1*rD-7YF)TXfQ7MK$-azqRR*c(61)lnvui zNjY`tu=Mhcox=Ex!^qtP{Q1n~)42a;jcXfM=_2UnCq|S_;apv8i$|f2Jp0(e<_awrfN^j+?wc z%yf$D*I1@Mn{|v%LZW46jE6+WrVm$mUP^FvNvgQy;IS9*uwvx~tV0M9T;WJ&>R(O@{-D3%b9Dng5yz$40h7jrOY|T>wnl^dZ<6fVy%Qk z=aw$C;2ZYx4yly`*?-0UbUc;H$)XpzRS*%{E0*7fMdahKq6(}#Uj%=u$$rbOU;6yS7(z8-P|Fu0YJgw+)`85W#KRF+wx zNsQVNaI#ZpPI6UIN>S$(oIiCQn~Kg5u@mvk7!D&U|K#v237nAq5c)7^7OKeLsN8m8J=`JYT^?jI*DEL86pk6jMl;Ta?Jdvb+xW})_cl>m+$i*Ndb5@> z#lvB8i(ywg=Ttp}D*bZjI@1z$nx>?KHr&+bkX{F-L-N(rRwCscGT~k+ebT3u*tf{k6c)ad+ z2$A&0WQl^qM&dH9w!E@U)?1N0NyQPaCz9I>bHz)Ns$iX`JUZr9auMTmUNQLGHZfz& z(2f|mE=3zGxIB6^%&1qr=v*Z>jxo0ZjhmIC=91QY?0q z{b_v#&|H?Ti`CAays+8aYLaNyhZ*Vu8;`S%sz0boLFC zqHFJP`+MfAhLr7Ov~y|itfy;RIire4D21vd7QH?*@yew_WNVZY#;VC6J@z$s*?M_D zp;$zq;1Y{jFw~v@#+oz8HllFkH%pT1eX`I7E-gHDOXFJ14Jy@1pT(dm$qh+c$-`r6 z$YgUI(TGxsOiQTyD+3gx3zIoqlt$uML|g z5Y~yPj{i|nN-C97uJ$s(USv;-+Q(Ii~s2az0)cjGi`akhBPC8wf@%VY72py9xXrW z=pONo5-x=pXNa8pXN1zUV2JG6202^d?UkzBb4Q2tslivGQf^g`bs}B$Jl3kL$8}9y zk(_aGoUBoer`uZAOCbqDPzJxR@hwL%(`1 z8_G#2pO}=jtTa>jTYvkvSl4oiT-uUYeHl47U92%&mr{2UVd=^y=CnLiVet-2F;jj4 zd0))Z%DvxDiSpaB6w&eC+~PdFT|>iNEH4f=nV6QiVYAW@y9y+qKABhtvYqClrX;ve zDpYrd`r?A46r?u3uMcXw_M1q6g>ZDSBOoEh?oK>F(~WcLDISTA@UrseVK#C|TaI>C zCkuJkOd?$fs(EK=(aN*NijlBU>$<6-l>A`n{%tkgzqd2fa~29Wx|WJ4Q)6uBTH3ao z&8X{eO4}%czqO=Q5jWC8!d$GQ?Q>cFrzizqosp`nruw>jS$tW1oJJgQIiZ?JNEn@t zRC``tx;G!s;lWaGrwUVigojT!ty4@GtC1%|uBnlwU8VM`=v*em(yo-!I0 zfJ1I5w8RW?X9dv?7uW3Ev)XtS`93~0^udMC7TzB#`UOF(wWL(n{z|!$B|esN z2rCQ;H39?#gv0?P1U(P{A;Ca^`4xOnp;4kDi~#}_;K~#%#F7wBX39c

d56L;__B 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

MT$BV}uh5}%_bWYXD zE0KK(9)ukhJ=|47LW1M*JHSi(m%UVm_&%3G4Ye2QUnS$s5 zwh<7>uOk2O{cjMPzGu)EQ5)iC?!TnH0SkRJyx2|m7(643e`r1YLnOeD<17EYK7u;@ zu@ek}%1XfIky_|WQUXnI!=ss)3a0gsKBau5DU$-LRJwp!h$6L4r;G$f7Dec({Y@zl zqZVjcFymt@<=&si4z&KWBN4DO1lU zHK#=$6!)j%H#_MCjsQ%UD1OiJ z5;xFuB3!}JiOb^tr2arp+YHodxg&ZP1js)w9$sS;Q!{f5_Wy!|lZ*TR&K&OlNI1}POJ=WRkkud%8scqi>6Hv*jvjS*>qu;1yr(B=PJ8k%!8_~SXh_kWDXA7LPg XU<&`)CtltGcMF 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 IGnn4~5Boy15C8xG diff --git a/src/Nethermind/Chains/swan-mainnet.json.zst b/src/Nethermind/Chains/swan-mainnet.json.zst index 8c1d0552d6ad8717e91ee0a42504ccc7c36d07a4..3b5d192de95f35c2e70490c5383ef58bb6e192d3 100644 GIT binary patch delta 8420 zcmXw;RZtv&&bDEJ1&X`76u08O_~P#F?rw`O?oiy_-Cc?mmtqBqdvPd~@}D#RcV=?& zTqN%#lbg(&x>WeWe0T^$JNrAFF2({ny6_W>zGOEm(@kO(Ee7$;mpuyai3-`RYnrA+ zC+s>bIcn=$6hb4su5LCmN{L0SXi5n`0@(?&w|n&S{ZhtV=Fgm; zh7`tV>#8d6dH-kQaY0!uAzuQnn>;LO92U+mgm={SjWX$y=-w_AHq5SKGT$6`ZM$k!^0ZPo1&i7w_;1$|I!6y38g>K!TvQC1KDF*?MPMtleX z7yl-d%6D%FH+c_sI$|vmnhXCdFaFShz zVOd-;Ls4iMkkXOlii%+o2z^~);8iD4gvCwi07$xY5KZ!MA>q-cST$ptXN- zK&a<^xG^O<=?xqrdKSMeA|P}$A__W;hBOokT~ZweA#jn(A>dJB>JDLO!U0^1h9wbj zq9dXZr6~WiMc8mSG#FFh1psHWm_+niS5I&~PykB|_jia8X7{YG);!SgXZiAv|X^@*6&u?U_IGp>q>p++jMab(ya<*{=v7 zso~zY;)pt9xAZNgS}&(xqTkMFm4Mh6>WQccoMzHW6E>LGkEMDf^#qqvz1TFY_0aUwTWic9O$OWuZI zMTsPJ86J7T!Xd6}%R#727GI@9NuU@l9?DO7H&iy#6*(UcXIHeq5eWur>HYeVFCbM$s^4)GOE!u)Rn&Q;hAF~Ua%PTL<-Yc%jXXYSSng(<7Ti&n8O;%eL4js2@QJq_5_p zhp`>NhfB|gGa&aor*OzU7~dTs&h4%7>m&vjdN66fjOQ?U=&C$T&WqjyYceIeyELgs zCYg4{Ocn&mjleDA$OrtbXe{to$&Xn@0^P*uj`i-ElG6UnYoqhH7#yEbduRB7brd~q zJRKTlDU$>V{wOSBWb)5UoAD6BJgm7(nlI&iA0;| zZWcZ^H)QZiBA5R3sW)%pC74=0`Rb(3mPJ~cG4hmw>MmIoB}XW{=gWc!TraF6K8`>H z!(sHP{nC%c<n8gond zo0L~<6w8unnTSeU#-a|n?eMeJOR=zb>>TQuqm9$%$RNe1;;z$ah)*u$aKw&`#uPl> z=2JLk{iMF&XnmaMjr;8J2D-HmKF>odNn_UUYb=XqLNtQXHv&|~(C{QYfy^Yc3kc0> zQr4V_scjvWH+;W8edv1B_PqRDUHP4O&TthgNh(Cr;VsGhLpvUVUQhayNS~~kBtK)( z2`q~OmgS`8|EBbE;Bwnu6D0Ie?Q~J~y6CCe3|vj(&#I9xFSP;R>&NQHW=Mu*I+k!P zCPnxkJXpj~Lo&*k?H(y)-F6=+8skOzKHQ9@jwv`RnWg*Dt`}&90%ox-G5}i_TuUH4%Uo@$rR3?2GsarB$=nnb zoMW*Y-aUB`)N)%nd!WTxZ`CXvOcc+fC`t#6O}s;{5pV5BCxb@Nd&8S9qwFo%#()@=Emn-tpqr;?*5LmiCj*V1I10)JU%PZ zphX`2Z0_WtCPDt6qKXql%0Wl4wpy~b+*4QD#g>~*J{SA8%b;UVgc?MrKm|^398)a# z-fn}?%`71Fl~20z+k|y`z4cm z@yYW~0`qh7PF!kQ+D&o;Z=Q#y2DOY^KJ$Ji9%CE>r>77!@kvuG8>yaZdEH!!Mx`60 zhPf1?8RWVbXKa&Bp~a-`RKK=un3h}5HB;NC^Gm6H$<;PVdu6gX+(wS{4|WX8mrI_X zT8dLbQw8HY`+|sAhm$c~#dLNP_)`z^GS)KIyb1~yHk-}&WC!2Sh)Zv0KqRX)%9teA zp`}Pi#Pg6PlE+zvSk^3&Kl48dx7vHbfK?<0+4_l*yPD9la>7`Lr$XGs=6#*8;O;3XRm(BG6$Dxd7`Z`i)5B*Z4GlVt&U-5 z`1DVe*I6PED1o*^KTqPZl&raq4;_a>BA|5hyK2K;jX!Je4s=Y8WJ#6|CrB&@4t+W~ z-2%^S-S9HQ1al-BpMqzNN+6EQqT@J=6vr17T9C~(%XgZNSTf=j_F_@HW#R6{t#kF_ zbS<5HnP91LH~wHDRm6!GhepTA$tOBHl`-~D35m_Gyw(~Llm Z36{(qH5dsMQr? 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 delta 7014 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 X{e7EtD{A{LBOf~IQ9BW9#`%8$VxwAa diff --git a/src/Nethermind/Chains/swell-mainnet.json.zst b/src/Nethermind/Chains/swell-mainnet.json.zst index b0e270c4f8aaf6993756f4bd341b90d974bddffd..d928fac7e89699d57d9da3f918547cd95a2927c1 100644 GIT binary patch delta 16356 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 X++<`ihNf3`Ul^DHlO}Jj;QRjrr#d?@ diff --git a/src/Nethermind/Chains/unichain-mainnet.json.zst b/src/Nethermind/Chains/unichain-mainnet.json.zst index 0c645ad0e4d6776499cc586c24f238d7dc4ff78b..4569abcb8c625652bf64b3f78fb67daf434fb9df 100644 GIT binary patch delta 47216 zcmXt(G)YO(5QEbA z2D$m^Li1|rA`D$&Ch4(+!FohIaXNlFt<0Y|i35nef@a%NW#69adxlDF{X=G#rWdl5 zrxY+zL+Ui2q6+2;(;tHCp_3w{p98GA@CSwVj?Q2~vQFH{xbCp(GNIHCvP(7XH}7Ag zLnA=sF)RZda@KV0d_2WF&6Ph{lZWU1RM z(@U~h8k369@t+#%KAb0~7BsZ16|@uUu8%?NUW?ziY8D!f$upuME5l7i9d3`v#vH)= z8Y{+aelab1e!8qFD?-v5$~=r2adspW4YawaQ`D?*Ky{?0xa-C#$)L@l_JUrsS44f5 zrv`hv-Tzp~?EfJh|A*B4A7b-AB=rAo$^1WL_kW69K!Q@_j3zyzmdqi}Xy`u3zq?K9 zZJ>2vT*+dpTMbRqHh_Yr4bQEiI9uwmR&JRaBA-!<%VGfn!CWu{2gRGgQ*z@c1Qj>HObe-xH8b~N!2n#m>UeXy@?b&PEk z&Yux7R%bwtg)WLP{8q@p`8I^gLefqWuTxGx$v7zZC3jSq^A1jDwyF}qYK(0#A{WGr zO6USc$pU2LPXK9%#SydBou`aHBloz41U2IpFa;J1vz>0TqpQ+x^fzA|eW$5|FD;+@ ziACR09dpu{CfR;w3S20R&S~n`}Q)t~+Eng~nI8Mfmkc+8l5gQ$kmBg2<^P1jfB?^lN+|7)9zZH`kmJ zb0gowEbEz-brJ19N{N_y9hx5m6dH*aL>PuJAqbH$0-7MfNH_rG8pUot&WgC zvcLjsae;p`bNqPRte5%(KB%7DGXNBL5;Jk4q1g5QN#>47SdW16l0JeHlU=4|@?3cV z>bC4+1;6*t)iiG)2NGNFt_;3@a6^+dm@fc;<)mo6G$BQig4$#?kZIsl3kL<7J75pR zde;;(Fk@igl*LhEo|2Vj|pk1r6;WMvla&4XJ{^cp#S+aPJz;(6EJtEPQ*NSi0s4wIU(>pA%IFh2)a>1Ai7fk^E}Rce=wX> z0T={x0Vw`H3~z4Bj*K`PZF5rd!;gJ8KS1tD-lAkl+>tqKKS_k)6g zfPhBe=NbhU4g|wT1kfRafB=l}h2}sY@gNW|oeFhctq#hl@N}-GF@adc^FFOg)Vi~q zPU~YL1P5$d7~uG`yjfDFHF2v%RlJz@-6x4R<6Q+!K ziFw!Ir0xPBYCx%g{h-hV2=0{ZV`20RInKh_-0Aabnt|*|lUH;qjK2b3Q*>L0tw>Sc z!T0pF)O(~XX8uO{XU5bBZ}QnGLOmEc>lJTlNl1lW)SzPxpq+6RQApq7d77g`V6I{} zvXH5Ai~Y&6 zHjKD;#8hPBZ#3x!9VZ~C@4i1o6=94~LLzNos4VnP{fpx#O(#2;Drf8)z}OjkX(Z9a z@(P#;2qx_hD%WxffTwR4!tX}~hj~JAg5~_r7BK@(&{Jf{76YZ=2(SOS1VZ({Oa}K` z1S4t$Lz4j4g}_GxcnStQ_Cc`569`v~h%$`8k&Y1rrImt^-3q`_`2ipx5Xb>c(d2~D z2txJ3(7c`n|5X9p6avq)Ksy+LcU|bt0|bnaX5L5~APhrH%!xg)KOcxl7+?gBcn1&> z3W6r_oCk$QncoM6W;gn;L!j}3KvVWXp|%EW?tn&o#DrE(fygybpjyI;Q@C3MQ^m^j zw@_bsp+|y?${23=SUBYLVcicr-i2zE4c#^P=Z(lB@P(wDGHV!zUudjZTs*UipQ~B( z_2yDm@({4SIm0LEOUP3&28lkV62@AW>3?UKrpJ>d3fEguT6oGwzTOi5nAzaBxHs5t zmH^kk*4L?CPi7S7MV00uZG{@9w|T#8}q&L$Fk&NHXg5mkJ~+? zAB2`4SjRi6ci8aRpN)5p=6ggEUd=i05MU*l+t``k55rm&#d5VK4~zq&h1(T2rb zkcGB40gGhufv7)C$Y+`2xTAzdoFYQsVp1Ii9=}yh@*5>hsYv=haRnV8j9o>8;5^dWzY2TMz{ehZhgvPi`T&xv`hOtTU5_E8dd5bdWl<^xX00gw$xd^CKhhp zpr?V2Iwht`CqpfFp64gkRLYOWrU8g0T{@N2q4A?+3z}sbx?lF;aj(Lk6c}len38C9 zKW%gCm@jS^Z^xIo|AG!DanfF2jOf00+Qtn!v*!^jT+=~w8`ryy0-lh|9+7Vuf3^PP zK%Q!4+~Ul|#4$#VA+tsD44P5;ZV^VfN+fjYA(wZ*JE57{G&=cRw@nD4;N_vy>tM%-QziDT|bPb^wc@Yjrqq3W9KMg2`fJwP(vG0AhdGCFwgrE5^ zX>@y66d3;H%_S6_4qfopB_4Qu)G_((1L9Nj=oRE zqKGEBYbLTxT#cAw@61kmQXY}xowzlzZ~-wA@~a&|viSrNhpS7e>dO_jDj{v7fadtewDWKmerIm{&X4ZNp=`GMhCI3@gO#?NG8pV@+EC3&Y|Uxi4F zwyw%b%VvDD5-9COp-t+_9W~%R9y2}WqOnM^a*&c+o{mXWK8)~-uh0vRz){IMA~Y$t zODgk!(b_OfOheG2!y{E&es!-SU2qRoGbaIXEC{eIR7Vzzg@D72;%E%khsFY`fZd0k zA36~n4aTOB zo0!Gp>3LQ*6lUjX3UM`UiP@JtpeK7g$u#wt%$D_8J$pk~FIdnn1(oVR#a2n}6{*yp#*g{t z;fk%zd97iW1Qt^q5h(atU^!RuF>>6caI8652K3~g-o#Exb&Y0#sKqpLI*Te^%#w^3 zN=wPJM7j(zewixnQu$;;hJUdL_EI=>ZaDT6-qA#(aa$XXQ&8$*4nkW<)@M7+vo!Cx zZ&kTM8{Iy~uKs*;0YAxEQdDqT2iFR~Z86%oIG=<@lM%9U$tlnusF@fIo=GmqOJ^a| zH=HGU4diaPlHbTrR1pX)~TH>heabQt1Xj}RHkKr z=r&W_^^3|lXFAMgb3BWG4zXA>U8DN_?(|)zPkD)D5nftwwIttRQE^%ORARINjsOBRd~NqDB<>;*FOlVDDZV%bYst6hFnNsjf|n6680;N#$3D)D3nspEQnt|!`i@)5{|cU zK8qY)rw~6VCsMS)q>`qXl4|NNUCFgGFRHQmSUffBk0|ZWsx}vI?UJCyNjMpil^69; zB7?89{_mME`3Dnb_}Rh_Kz9n9ALuZH$*p2>m z#?s>V-|4?ynfa%WF09b1bmE&5o2OiMqMUYClM=BSV}Gn1q`i8!DOpbDBc4Pm-mL9s z;!EEzG=t~CR*7*Z*{A;^FXZu^0m>HCL z&2&$H1IO@OfQ#Idt_w$YD5g?W9bD2+bjjM9Vk8G~*Qkzm$;OlDB&Kv?$Uh6?PgZ48 zD$t71l z^XbOp-W;z2czAnI)!;m%V~QTM{NeKloA5!XlElK_30uMk%M@_ zgLtY{xAU?+Kk-B0;Wxfd(!a#a#1+T-ATCz1Q zz%R`zBClV}r6~~xv)`A^K9N?FhqcIs*MCtb2K2GG^Gzfp@UL+hx)tzdSP)q{8SKR~ zQFDssh;?ippuVUeA`40rMKj6Xe7$>`n@~kBBtEMtScLoRalgJ9#1qEi z%#z8?QbS~C>x$A;_okD6SVJ?t;kQ9%5}zN2a+VUZNXMBHAM#%me)aum}IP zc68EFb+?CJvQByLC?Z+lG)Arz508tWELEGTmFm4XQ^6Ixw#E#*Q)yB%GNDUhvg1T5 z<|GWzmqmu~c&Q+7AdVq!-%%Ykdwd=k72Ro->$mv1}l>mJqg4+SH4$;W1;BnjDIdn`#&{JGmJU z3&}sp!=FtO@2DPe5&8WAlvWbkw11($eGsFYaABoEQ56qX@;6f*k;^A-tbE_jk((IS z>ry?5KWHC>AV8XEvMQkDkOpb5uOQMyOG$81 zw(^!r!G^G!;f9ujok#_9gDsL?-a1U8GK=CPI^l-A?}@cfHV;s!CVD za^M|HzB3YDkLx}MLOLu;FCuH=hYhd0)Z+@^d&!Wj(waQ*a8C2xi%?6(!`p_De<_Jf zt3>p#3^~{d6@L7A4!yU>FJ=fYR*ll}l{C2;6$l@DR!7K97;df!v)E*X4FB_Dj^6~! znq^AJQ>CR0@Ze_AsQHWvWVJ3I7#ZQ|+Mun*H6Kx})hM$7axn~)nsZ#Nn3!AW+k3m4 zOHd&3^?d+huRd+^Xe?MUJtxiwy5M^d`j#VwzbXNBfWAiFRcqp~rK$s99|kef@- zeu{}J>Vrb1R;d|>IJ%VU$5f`m3GPf1 zbh=o7MzJdZ?)Q)WH6q1x)SQ}FY@kgA+5vWIWl>K}HGAe7DYFNrkJ?7IoPirk=JBJ;O z9PrWPo(cdSlc=a4_%oZh=dgIjstGgHQm-||I@Vb6Z;|J7!*HrS< zj80D)=xOOZp&UhWI`j#Kjv%+?92B?nuN^b8a=E|dPR5MuQHP7hvFCQ3WTa_j8E1&F zq<7Ly@p5{%0zJ;L85o=RYRUZKjzm|6|CAwH*oe<0K#|~n5kK*8A}v1Rio`7VzzHXF z<6v6@_xoP?2a&#cZ~77|M9365Xl}Df|0UoWX!ofnRxh_IH#Jz8@o>Ii`ke7v=W0N* zWD{JY7ImgLWPJ>4K6QrY*iU+8>{?TEa*FFJ;V8TLKqaCsT#|l0P00#3XOjI+d`c0| zO~)1va{g<6eirqX?4aW61eV2Jfu2x{(U{@}L_`4j|6vCrie^MijE)EhExK>ltNG)P z4gwY6#uy>SnG|jkCG z=Qxz{V;*Tr*R}8EaW?>*&1oz&ajH#Ghjr)nKA`Z%Z_yr(4_Ln8Ey+6UTUC{f0zs}| zVuWxrooTGbMuT|G&Z1ZqT)HEjc3^FCwEoG;xXt{1Rg&co5r+40dshERtfkBv1sk&7$#9-=l;gQ! zq4tKoyoQS#A^3FW5NNwpyU|{p0Mh|9rf~bKHssl;)!Z2EEpwXv58L_b;Cy#?P>=!< zxAZ0IUK=@(P9GaGR-d+#zDscSytwOM6+zWfzOsWTEeV=VIylGjNmGFdu31;7{JncJGtW-dhzFY) zq+Ip-=3sD+j7__Pq?Q`B1~DBI3|B&-S~6k^PzZ9+xDlIaZsLXloSf6Q`Yy5=)CyDR z>qU-z(V}B=COxMj5S`-hO#KDI`}FQ>{0cNe>i2L7|L@uP=cliH;0xtNsJ=l20iE@i zFiN-)PsmBOoUP0Ic=nqpWHB^>Bj6PtFWKgAaROU}gCol{brBjPwwhjo%+_r?4q>VP zd)pb$ud`xgboEsrkgdprolx1~9);(*QuELFTJxqRKsB>u#=jUUu8=xW%N{#GpAf4T zWJY%W0i_$80WAlDE%SlIevpOf_Lv%X?$wG3y+Ta(S(d4qy2{CO`QD92>|v2{N~L>- zmon}G>{5VwI%T^f!aZxT4vA-OHV!rV$2R$(z-SjP9xKob7>)S0fCa0z!+Cr>{ykkj z%9cQE9NojbGTpK;;42uUqwp=X7obd?7*9w2+6?{6({U#fw(IqDE+_`Z`;%!^mW6-Y z1UCy<#7o3mrf@!GR*MOztN-3zks-0S*N*4CH%6fWIiL{vJ~UUyUYzHP_?uE5KmM@L zzX$EEM@py~n4xTQR}1&&|BTA_6NrWMqDa zf;gB}2My9vlRs<1gvfisW=_mJRik)wqki=VGJ{Yq_zthO&wE8@`Xu@HsqszWA&NHKbUiwVE5{e@f&o1UkU0=t_FpOooyzcjO>`A>?ay zIEJM4{UIl}TCYV&k^QSQs|*&p9QnuOP0dxG&RoJ~Jz-B&R=To6eWRpHL9CB#SccX_BDP z`)9*X6*SZm3_q*^zWFIw7i+HS0SgoHr?*PKPSgvs9gX_g{`%8zU?mJy&}fh=LgjfE z7--?FKHlt0=Hm-6kjsW>xdLhSZj$8Qoo1E1FHaIIex7YQPUrTzGgba%$tPd8-z0rI zMmG|Y^HvmvBs6DFe*HB6+&-3Us5>ZI%dI*WNyx+)m_L`8rbF0vebbM$Ez~LwPUsO* zFNxFoaxXji1mi)2g>DiTJacZT`_b zf&vDDohASN9d4Rp?S!)kAYKZO^4+?I5AGjVV%HP-II$I?V^ktU8Uy3sb_{xy3EkI} zvSMglj@R*quSnJShryiF|KIno1IYqZe**G&ZH@U@U^zxwN9Dm+@7kd91ySk(IO6ti zZ4M_C=OO@)mO!tJGLYReY!>J9%rNGr!977t7WWuCSp-ryKN|)Jo}{@L=cW+$0dzBC zb$kzL>i!Dq$(nLC3Y<4+(1F(#_})Ivgk26>Y^0yb3nyob2R9R;Ajl;5nm^u)BgZko z)gCJTQz`VN2kj|EH@Lk&I^xO(>|mTW>U|mMPKqPNrV~UXu~_}U#OE3b@PKvkW~5$z zZwm4>x(+N<=(Sk>_Krh4>#z^=`fl)s)JPKGSZ?gBxOe**Kq>4jFL;+#U#;5qlSwMlg?^eH z$3J#WgZkJ&kx*yrdLDOym`9F(V-q$CS&rtQ;Zon-4d>Ua1wNq}glf_ri4Pe}#mcab z+Zu0NLC#vtGix3Ibb*9$p>C*bKZq;NYaC(6N);(#fSodE2-tC8v*pWXv=FSgPFt6g zro&UulgtBmZ^sZB)6Q5D9IOsSgg@Eng4$F4(O9u1_D3y#;m3wy8FI^v&QUL)O>+fkU)iYhL!s-hJ^6vMJxzlFR2U?fcHSH$k%-?c(HGO z$U266w<#yBg*GYW=4R5jI0NWbiq!pTxBdc>f_gZJ;iiv3HqR} zW^R9#v77p@9~4(#2+-IR6h%XeB9O;;+1soG^mpUsSTQwq5@$bm6{F+mhi8z_W5%>z zkHfCcd*=Am?pN7xU_@?dPcT|rOp{er6VJG};?n{SSx%(A{e$d$c%pV!WcEm3_@S7P zFl~k(Pc$_`u%4}!K)c+-7dCtKf}NZ}mw{TUKIvt#`>r|7dQ;oSw$Xo&lL@UZb@eOz zLiwkNzApGf?`a7mljEHnJ&zeoy#)QmLC2~Xyht-&mUT0(^LaT)E*dOoZDj7MYN)%a zd#4H{sbS-Y2!kFQFknJT#T#L+hwqTED4;UT6pB8&njCfDCTASO{co^$hBqxrSf7*b zD*<|!Q~jC{Xxu3FH9aL;#*pK$Da}40(j1=oFNq3SRheEWY{l!*hPKS(j8gIzCO*aD z=-(FTgmd%VUBE>pJR)?P(GWXitw;)2NKZBp;{bmeWd$|6|wZl-d&6P^L)uua2#2ITF4SWJsR2*zmT4D-WClUrI zu6X|L*f$e(S}GaVY}@a+N|ve~IfU%t^|qeT(IO6RqJF&`PCYNTZn=0F9=V+!){I$v zWyyD!3RuVQjs77@)Os=GF-BNFOS*KpGb$|5by97qrDr&%t(ii<>e&g5!~O>%8;)m) z2M1M0(<)5J(xVf3PePqYoxwh-UkHFK2GqJ7oJX+{oO{5i5JJf*8gI`ZSn5N_$OBk75r;KaGTetq(~9O!ODTEL6O<6Rb4)J__`U);e-18? zf=sdDt|t|I>(lwDls#4l`R`Kt&HbQ#y7U$(7Ssl+MUpL^5ZkQD-M5)^t$6B`IZx#* z+#CnV_?7&>4ucr004r{GpkZ>JDYd*h|g*)Nxh;FXzW(?c>z83=E zOHTkC9yE~ReE<&yHy9v={2pnIh2%IT-{Mv8(Q+DL0rte~)uNg?lDKG{RQMY4&Q!#x zsYDQJ{w{X~F>grxVN-$@F=L$!M~*~q`Y+UuJ$y@?i~Iw{73gWIRji7Y4hQ8Ys@eqW z-zQY#80h8+6}4T5k`0OryTsVqU^4q-qf}t9T$(VjZgMmY2)eeqs)F)p_^bMiL8dhFl53wItoL7g_{tmH3vQMW zX&KqM+*FZdM#bk7U*G4Mf_6lUKoiGy+}Yf6J>5sE?-OU*7a@w^kc5f0mA^ry)?&0v zP@@ioprZJDe1KUAo3$eK+&*R(!0{+#^`jWMetUr%F&T)b%`5o;S^nXeaY`0Ed?Y-4 z=qnE{arM*QgJF5uKnq3gs%Tw6Y&GlkuGiW+61G*FiKK+>M{VyRL(qw*^21Iy*_gNm zXhe6QIc=Gtl&{4pN@w*hcKrqIvnLqozK#mV_biF?5Kv}NkRUwKRHZzqNssvlt4j-r zQZ$B78vqnb0Ihqk4d6`XNUgpbOLIzVrs)xq`#G19&R-Qe7kr-sn8B&OQ-pHNj!rj=T4xOBgm`20#*|x%~qE zklun#6{(2a7{~{d$Wyx1uRl1q_UtXBFmtcpNCI=yW*wayoPz-0*lkIxf@Dt~(U~ZC zE9C%Cno_MLnl)wCFzXQH%zu#PbK}}GDe}kS0%59a4=3Okl_2dP39vKIhv{~I?S7Fb zBv3_xFnStxet9Q4teHqDU%sXAz({t+F~VaYp;;l|Nv<(KbzuRDEg{}De&dAsK*x$` zIRbs^NclAVAPVP%jcC8W5{kUh_PeG-xPpuH1cqlt6QOac4f< zB(sI*eRpA{JI(hg#|8L6`B9m2HqMXwTY&D`8e_)A*a4<*X9(Yph?7oq+~e|x;sOE7 z#ErxuA-rCFLLsSb2iX^AOHXpxKiz%W7dw1o{T`aqvWY-L=Cy2$JiORJwzCJ`7{{Xk$`1>7%{&4Cq+Z$S1p z-VL`eb1BQlZQq1K!kYoYj8FV*F*%=c^=seau=JU~dcn4V)BmIu1~7hoE$zL*TRZao zn8vkGS0HcfxqQ~I(8K$IfTb}wjTsJ*g3Hh)yptbbc>hud#87>DRH`HeJT}0r>eRbP zZ9Trf6}Do1uY@VnqaFa=;3IXGa-y!VfRNL=(yMH7OJhNNaJstcFC-kpB(a(B& z=$tBK(y@@#Ffs+x2C2Ukh{z0ZyQ!OgoGaBj=6YGHR9ps z^jaasixdW)&iKyo$vW`xOLQ3RYWnlL8nnt`|H0_K$TzfJVJsMt3>Ew?S^|>(E^`O~ zZJGCCfE&c^s=6KSKFr$qyZOr010}SIw*NJlgv`m?Z*A!mgh+|GsF<}S9;xuKa7=7K z`Q!AGQ!y&O5iI~eA11m2bM*t!KJ;nDCGphR=V)F(XOOhQG5Z_Ly$w6B*mJ6z-R5oJ z{n=BOTEo(k_1$>&^_nZv>;c$X2&c}5m>~tmarPo5th(ocFo;e`kDi|uqsEwh71C*Y zaY_^^!T%R8r9|H=;*q809NcqUlC5lrs=w;#!kMe`lXzO*Y4)rZC^?F)5FTY%)9?E4w2lf=qMQBHD6Bj^ zmrpEyIS6+Jdi&?i({CWwpm<998>am?!=)x7D>%%9#FZk@>CK1TN~0}1B{X@k_ge7c z_p%YE|LInEW%vwIB_5$y8LN;Ezsq{VXH*%-7wKK@{Ge)a)-3l7(cf$SZ!nu|b)N4U zYvH9Uso^Jx4(+uvDT(syX7I=d2glXlV7E*?t<;lRdvumdbwu1N4U$}A4$@2ezV|SVwMH5Hyf)JHxvf)hyJR!{QTY5s;Zg& z{CND@|4nU7oYMM4u}URgR1_eStL-UHB`6u|^3t2rtg8;h%>7Hfh$Q;%O6g{}n)jIn zg>u5?t;t0P^aFus2JdVJvY=|(YG{Ar_O3$SZ#FK=^|-|2e>Qww~B-*p3Swdq+? zNY-rF>d@zQ_bW6nIi-7_iPM<&TcB5*@5G9wV=3zXbe7oSv^tClV7Ef zT72^sfe)sR<7{ zjVufiE=jcTeM2@D-FYlW%P0t2FY@%9W>kUil)w3pBo_^r+Az#g#=Jjfo)?OVibG7r2~H&F;$@{hF7Rv z@U3Kf3So36_1i1S%m zD>2{ z3JPR1Xf%dxPXy-)^Hh(we{d-6v+K1}NY6LnGyd6yHJj#v7}U#K;F+6?i8=~(3-_g2 zu%n5`a^i3qdy+4VQBJj4;9Ie4(W+4+(GLl_%n)#DlG-=7-13y7GE;4I32QnV-{S^9>jm9lAapQz1MOGaLV^A)#PkV}Ze{}Q9jkz@sQx}_Tl z&Ou#9QJg7)5VlBCB-wBl@*wqI-bu0-+2Fr!?A|Fz{LVO^%W?XqNa)ltA!BLCqQH1q zlzrHmUtk}7h~&BFBB|od@HRnbK&KN8Wo*hYNC(F+^XuUh~?2ec#LKaX^oJDM5Bo6U@t zr25`^XhRd?&6U~R$6gS*X*9qYEaCV#J|aSMaJwSF(`{4Ao`ySAMBmny)-XemC zS$FI2;I&$UGd>``y8NuuyOjoRfHP#B< zoSc{#SD*rq%&WE8A9iFuynx_czdf1DP^>qX5z};ZANlB&US-m7$AME-Fa2XKV|+@s zc_HxvMA-4V#`3?y?i?m1XW^P}K!uRs(9#NDb5&d{`4u; zst8`@{=t3)6(4bjWI{nUHKa@r6%2Q?sWu<o~}kG0^->G?8n$e&N{WYP{( zhaI7E4$jlRf&*kHCUE{jpp4Ve^-=op5pU1xLoU*V<1e(?tXXizDBt>B=CT^GnucO` z^^(mckvz9&fslW59nJTzp_cik;KHr8yc~m%g5puBf>N0Ho=l+`AcLeWZ{+5;Asok* zbBX-9+-3jLCIu~4{;Gv&<%a3$&i8^spf3aL6R=jDp(e z&Z$xo7q{f{zN0JkFa&H#aU8UOt+K-vwzUuxVGi`*tS=oF4<|74X_SmnE-52Z<#lK~ z@4kW({+uv@&lW@6CqjH%;SNgd4H)`h4>YyGPZ+L;->}Z@Vm3^f!PInFn z*$DD}kJ|P5hN_p*(J1WTIsDo1!MeAq9&D2Mpovpks1^LZaN3mZ$!8aKB{=5WU9}c`<9T6(xzpuL^=_eRY`D=)B|}VNIDIBmM=>B1vj9RrzcyS=C+y$2CD_3<4F# zdzEJIfMDRRAB>QB{LcSK7d$!6Gi$79>$AWVKTcYqKW;m1r4_7zLwG<6tI;lP=jWW> zqxBW>?831EIDfI-IcaTOS}Yvix^(^J7$DK8q(I;yR>{ku5gPh_r6BnFugtF*B+!u) z^skD<7LD=Nrb*11`)vQn=q&CGcq8S?1*Y09+Dt13t zEejco!@MQK7xTJ<{)I9JmR0Mk0^Ul<$U}t0z0z$EG)tLL>&Hu3SPK_YaCfNZHeeQO z_{mV(JZ}ui&X~Dpzb;fbq$r|UfS^+bA@#*Z8AaYt638UmVYZ8Ab>0qcgqE6;nowa; zf#d7?E{Tb|&qvX@-@imgzYV}^w*OYX7_DaPNYFmC;ctOo&~YGmk@t57y}w?a0b>Q^ z$xHpMnKS6*oo!R_rn45dD8Gs|gMb62eyL`CzO<$#Y3xC)bMl8H&ib{l-^S!8@h~pN ztOfP=*Kdo|2KKxDM|msG`4L|ZJuw-zBBBA z)iFm*9W!w=s2}S-E4Ij18=#1iq_r+5fHTLC_k&wp%kq7xw|3Ox%Lh~^ZS!HEWr+_@ zF4_-|!aUY~#s+OG*HN`40PtrLF?OnlCJV_&_{-}UxX%jHFW&u?bXI8gM|@cnT>XhqZ2gp0~o*j_h%qNQAPb$ zNWckSa=d_s-*L_`KfId7ERxWjcD67@PEaL{5m4=%601zsQgk|J@epbc<*={+<;W!9 zg{PP82s#71jSrWN23;ZIjlH4NVYJRWXM8Yg=e;k8ou&X{Zb6R!Xb zp?G*&qi!=r1#x6;k(VT3ABOzP6IY-|z2vNR(nw{@@48dT2zZO^weu@|Y~!xN%(Y9{ zRvrFVFM|KOy$)MoOPL)mUjCHz6O*jnHBG0#?@Fo4yKoyF2NT#<`wgD{EH`d8?oHEd z9@X|j!M|+9Y9>mYQ9TvRR0CdX=3lHdM}d5)HSJqz0Z`t3_#v(T?K@29De$JIL-z$e zn<3?N2hce+!U2D+%tLK2?f%n7o-+-`?vF&|jlZ)I8)bi3Pd4Zv>1-5Q=PalGP(G?q z$Et4ibe$lGPn5DhmZvl8s-T+DXKNOIy-()w<|*3`CFxqN ziXi_#im6=J(f6?nEpLt58rK_Nj4MVdNfmxTYy9gDH1!ScHL`%s7}xJXda&Ng9T^8+ zrAPXKFM8lMI!r)tBz9&Acb>piR7vMwG3Ch`{rmlJOK!Uss?ZuN-}TmgKDo?i=qdE3 zn%tNNh2O6fW>r}Oz@$hKdZKPkh$LB`JRf$@?rGAFH8A)82Q5I-zdAj?@|w@wGWY?T z{JBxqKGQbsRF`LVLW>;Z`^z4Wfr(eJNk5f;J&PxITez|Oe|n7X`P6nJAL{dQWvHb0 zZ07>!2Xap6qyG<;Hz4?}RS0;dHuOM_)3@n8!=1Y)s>s;^9_hS(P&*w zFJNKw)v+<6X7yU2eXoGw?xB?gO8zn3#cV%je`H@e^|Hsc{0E0TUTqJ!X03Sz29T-?D(bD%6OMLKkSY}r0S?mH>iW$Y zUW^`a2#AlmCe=w6H(-xM<9J#pC)sK~qjb}~O7bf4C3Qc*dPJ=0(iD~IoW&sV9uiME z+mnOzGEB#FExA6J6>@++6~D_xY>Lz&g>L(dEvo9$e=_V_xBpRJI795G^17IY3tmz!qL(jb;zaLl+AJ^^>nrPzz4vY;Bs_CL5*$ha3D0-C<1Q@B@+x$Y@6$%f5Ne$CmTI-t8hM;y|x zW34I7Q!s}>y8KT>N=l4l^PeX>cfB@pAo%M-x{XSdxNL#r*au(z$MbsCq~Lz{;@6i3 zglj*P=>u-%tX7xaAEK68Yo*$F*1?i(Q;pgtf5UmGJzTZ*)6z~w=j=`uxWN`qDKR`x zChmL$3?B4Ph#S{BqN%k3-pbEkLw!QXe}7oI7Y)}%Tbk%m8)@o5hyB31md2AgABM9! z)KE8MnK!DKkHK@@nP}fsjGg~aIpw-&vZL?rBdbopq?y|7WG-D zf7Mg(uEKF^l)lP7{Lk7DsCbZn0alrI=>3*7lN`>+0NTF>k1IcJ7545HtHrjoIXZmE0WZl zi};&Y1W#(SKgMzK)VuL8mCEw+V%s7Zv4K<;k)qvERUM| z5uy7e?Lba-bS4j@4<;hswhkgEf0%f+ss|HH={|+O@%rUZ(Pq#+C7Gu+jaz16W%OMp z0elM4`v5n3uVUEjD&tp;)GZ?9pBA>GbJCs700|PE5cz+IGizu|ns0*I)9jAi;OSaA zbE`X;%<^={@q>rvKG-aVmj-`+O`~TKv{zGl2rPKuh1b=3Brf%9RhhLOe`?;YO(=C@ zr{pe9Bn}OB@qctXmaHu0&i`pW7WG^shNSAcxNtHnS3qHTzo*fpZ94HghSYT^i8`2RJTFLPP3dE|FfB7H3ReFkoH_u}H{2)`F(&4QjI_^_>uu|i-yR*6^I~2El zrmmDYEgUX=PKjyMlW^;CeU~=kim>0S0XAuY#dq2AS8}s#X&(K1pqAlwXrKVpGTREdQwLp)V7v_RRRe-m`iGO^S6-(?HS zbQCld{c$;St9J%n{sjlmN5IRu;3*tFj%ld#k8wQnZqpema}ZJ(b2=*Mn%w&&KSE(t zf3necJRmo9IzA>E0^lVI8#1gg###~Ge!opiw?=jY1ss5L-KA5Emv4LL3B(Rn@*de` z3wW%GV>K$e5j6qzf5eEGe`KOvOFtQYfLXw3il+Otj;XCxEzIwRW}UTUIBA$A)UQzq zm*BS&y_#67#C_Idt{w$+Oy+gjHwMUNi9;`}KIgIhu@B>&5Qy;s=lT~C`u)G}l9w*6 zx>b(OXM1t`%5xmBQu!3D`AohxQYj1HoJe6x4t#z)o5DFjf7HkE)zXk0Di@Cn!`?KJ zo5S9--xYyNHYeA&D{nmS=MgsczF|O1(_%zrQ1#RrWW6hIr0m~lvyuVuVHE1+wY&Dz zr$y*aLzu(Z=nuJY=I!z~o?FAbTaN^o*W1>e4)>MeLsjMLroK(IV9KR^ty zOE{Uzh5CI*LLnM=7Jop4T(^)D< zhnsL7Z(wj?&})=YPfuWvQ?TvyB)87u_z1}GOZ&b1=Hj*m=T~**UP1uE6|<+M>4iw%^4r(Dfetai(_>MO{pnk9_V+DvVX*WI{G#vZ`jpo{ zUVq67e_M)30HGxno!s>+>z7ZKKQ@!WTlmf#aWUr{ckH(Bq$jKhpLMkyKIxOdB__Jb zao+ODInpY9C^FqVP-{Uq!`6Rjs(_AP-?M#HT)qH2@L94IVA?W!f+U$}jNdK>gaOGQ zcD5P zk8aKwA1HW&{}v|kyC5io__Mii=-F4&;kQVIwm1B%n5jfBrAY zH}=sRE5cl7Nqe?lZn!xlTmxwx{|3=3IKd(@!}>&zM@%BfMN$9v`ytA&>WWWZ3&1!^ zG~Y8SKe(*_-8JtcQ>>nXh^x=3QLzu6!aBu0-hNQ$y z#E(<_N2!J{&Xh*Fs3>hM2=-Pc=rq4yJJBNBn&KT$iIkcp3sy6lobl_OvJ=3(B{gcqHpg6&uF1c!Q{T z9QQ&_n;^DjVfU71Q@Dg;f7{ONM}^eZJ~915D4%ZwL#McuO9$#WZmlbe`SX)7oLGit zT{yxAuQB~g>vb%QL!A8QJ3$Q%_(_T&lnBzH4>O3p6F^p1smOjDI96U>prJeZ)$d!~ z%l+JTxOGGMq<9?0q*yD1Yb7Vt`lxTLOE)41ivtt6WH!tM?e}dae@^)Oy(CjIehP*c zw(U*;1_F)-7&#}Fq#)jnWbS3-(2!+h)bd}S3(H(k;0k%s$%l@1^_RHaoAgR#%KRJT z_BgQ~Ov0o+=er~r+0{>NBVf8{nL+>2wuxgb?gGWP|Y_eZtD0e{VC6(uby-JKPkBGv;#>Fq%O zyoxZ$Ih?+ZNsV9f4zo1#Y~2j_x}_?#%x%;$&r$oS;u5a`q*`T*s9uk)4Mtbf&n_?j zItgj!uy`_!&`0U@wP5x(Od6#FkIdsgH9TsXX&9_L!E%3Je|ZS1$cnsE`HMf#?kapZ z-|~a?pRPCSE8$aB)b_@!?70rK$$_8Ux)8U!uFA*;aw&za${6A?8Heq+d=SvnZH?TEFBjdcQU@N z*dDBc|1__)nGY^HHc{_#A?teD>Q2C}Tl%3Tts!SfD8fj}q$h80pk~vm8THWU4Cq=O zB!~Cr7K5(j5@0<3GTfI~j#jVBKcU`e_5%%7TA<0qpkVH1?r^T_0lAm z57B09ym9G>BP?6=v} z}4*S9@M#NB+gG zU-;VKjXP;iQ%ILQzf_T{E;`4RXP++DL7>^xx$Mp+66E%IHHvvEdSfSjxP_b6E>W8TIS0}}e|8jpf?x>5?trP+7#1s^9FmOK_H5mX z*LJaXWBnqWZC?O@TlFoK>#sy74eoGW7Kv*O=ZkUJ6{14Uxp$~*ITrWV#Yih{d4Qh_ z(2MOv=DR{bxSF|;fP*|N%Ic=;d7RpfW>Xe&Q^50{vd0O1saBq<7#?Acx8-1ju( zVm8*njcasr#qc0-j0MUe8qXCr)^og;`plBhE&j|Ahf9(O-N^U(MqSPsVeGZ1AA(eH zK{9%{W}d%B~5{a9n>{9$k6BicLkg0C%9~q6-TV?7#U`RVa1 zYiPe#2>TKyZS+}`w34G#G|4aFDy#)$kOv~=5RtU)aaZ6*FU=kk_E-qC%=+y`MqKjcXse43 z+Gm!?&<+j+$5PPRSLa0Ji^v(@LEH)EeQC<1BgzuEb6PpFm5Qzw&dhYu1+xJfaOkxK zMrp-LjrTgDK43DYpq`4!e|h^kIhz`#2`o_N+OojCNR@y1dLK8aH5qL_RZg`%!UhKZ zs7B}29#1KIIzTgBJj5hjX9dO(%qKlY8@+EX|C6UjjD%3W9 z03{bn6MCPito*9K(AsU*-^swCNF)`q$$x3F&%j3Ph$b zB`RWwlPFSU;ia02O_i|<0z7PEtE8~DE^H$|J0CvE+h6N}p<0pbLEMUbq-VLyBY zqnqhN?qwhgwX1|1zkd1!w!4|seoA;`1ryaVL2Y`?&@YIne`RTHW$WTxi`T-uH zbz(hKN&5^TuC#|+<}^Kc7aw9rgswQCurmL7GXlF>z-X1poFb`?4jLzb9(tF?R#!uMh)i0FkpFEs5l<%!`!%rOnI>C(3?&_J z+6x>3=8|Nff5CLMq-cwcFheETUWC9;I<8A$OQ}swfHDuqgsVdd|LY27QY+Z`jQd-E*WNN@?mv?Fl1z&qtKvj!2|?a)x6p(vy!ikTNLgAiZai z;@TTURwz%rLbiT&x2W4=(~>pWuS}kEnDX@DV!>?5e@VFubRFaB7rP&D+ixi~6lciB zKj4&Y@s}{E^kc(hIS+dk93 zV#H(C`Kv~%t|=fgqMRz*s%%NM}VKNUPK6>XN~}+T@t_{ zVhLghf6yh80NN*z6F^?F2i+t5QAncnZK0+Dpbi`ji!f1VTBXgfF7eIr1Eh`{WJiEN zBq^0o*VnO+UAV%_qU}XRAh4YJp+aZj`PT^Ic#cSS^r9`TXN-xqC6||TurKqs^qYFC zkWTRO4xoR+(F%-)f3H^sJjF&ek8x;OPY^J-e_JZMn|{uWh$x>CxhGA%{EK8vaEIJ; zkys6hB6H!GSgDzxdE$GWN-m!*u&h1_6;XlNNc3xI1+B zYeC^*MkfQQm>QU9^|;P`%2$cu^8q~te|`&Uq=_M_$0eEAQb>iU+RpPZkjNBrjTNOw z>kk-)h|E_v$w+Y173jPv?G5^#B46VkXkGSS6(j}&R9w@m-vc?{CnUSL(Q4KisZWDaLn9BPE8ZWFki5~ZFqrU4!;{GOY41l)A6=q@Owc1-NCbu0r_1I#*85C3Q(Un zr=}=@&>7Mm0?z|;U_pKJbDXj3LK{7MI+G6Ie<^%o8ALFQLxlj_^HbdVlln{1p=oGz z3QUT1(3K5xorj9mJrx->m!Dplf75p1$>rsz{IIHkrey8bMBur3f7hSrn&(n=?K-44 zfAD=GIw;*Ev#O^LN>5k|6&_bq9AiM=`Rf=k=#G7U^mpd&gI~IVz^`_(aYcu%M zqfoJ;Mx{fUsnAgdd_2VC4P^TqXv!=n=76WTv-Gf;JdTcDa0%{8KLY2;zoHxd=$83} zSy%(bpFcIE3Pe0!*@N0efBM`BHNum^c;l$&>Va~oo{$tu^Kq=tkeHneh_jNuSYG?w33TnF143B%&CT#j#p|F{_u&O`_MVcUO9XGXxg8qT z!RXY>Mn?&&BV1@^f3~wdf?3~=i0g!Q zc@HgCn;=&W%pDOa?v&kdnJ%Lg1MXA;!zumK^}O{FOAKmAEYs?NVip1k(lF&0=pYkJ zS!A($oLEqGhCi%=l}iC`yFzzURWL?q(f#wJf4+UVtTefYazk)pyabJ+qJv&lXhOSS z0q3T1T*a91y0|6>fB!KLj`?jV+(8gyOt*+r^zjA?BF*P2YvSrBK%yOLp@2zz2hHq+ z{|@97X)@3Vm%mT=+cRE>?X1Q>9LI-z<7w>0a(=)ydu=~G)`r{0*So`We8_98#$J5R z1$^UmdROIakl#4HzdgeX*xqXVi{pI2H=d5YTDA{!jo16bf8*GYZG4Tpc+LmB#_HJ1 zXS&edc)fR9h7I_=)A)<$ynt=2j(<7E2lj2JwWsC#0oQwtzj&Mt*v8j!muGyy>$JLh z`79Ukz1MpO%h<5rI30g^#tYbv)%DND^8w#`8hf#vA8?J=@t4QgfbIA?cl6vI@OrDa z2cL0a-*_GGf3l1X_#LP7hiCSJwY6IR?ie5Tji+NT%lHA;@j8Eaj16hKUgu7p@nNsG z8usvc7w{dg^A5|{5WnlR{pJ}jY#XcNUykts-|=+zu#6w#IS$HR!ia-yfd$0=8py{^1xO;yX`ce-F#)hg#>g{NeF7Y#YAD-JbCw zUS~D-@HsBjw_VRWEN=sT$Lajx885_kR^uOz;~Az<^&q-|$PNHuY}y#0!sJ+OFBl1- zL)M01xWhgO#rcy@3G{)SgzwFuMIX*kWnV!ZTM>iXv`(i-?nPJn85Mw(Vmb1Wcs@QT zFQpE_f1818-*TkIULL9pf4g>v*190Y-;oz5Ti7j(L?t|u(jqiK z*w6&tcs)BSC%*Qt-PdYg_~PpZ{0jV4cdfxf9QwtBM>(uSVJ&^jo8`flccx94HM)+C zSbFiRhh>d}^~iR?!G@a)-oVvZFEr<1H0C4!^YNPb`2T!NR}(rHz~c%0=XTxr=nfF= ze<1|>)*8Wn%9Y~)wn5Jr2PL39*G6y)#&LD@lp2&< zIK-*L6TABx1l7)5T;2;$PCrn_nd9L$n5ZoQh@G6f1H2%8O(emk(nwFu=D;bia$-$D z(`?#5W7xBDJ`ChzaX-A8O6GJ^TrS} z%(Kamy?W?Y%~fI3hICtP!;@~Q!P#6DyhJGqOlodm`l9A*1f|$g*#7urz=)>gxcDc0 zd?X%(Ee4Nz9(9(tjYQZ!_9X9{VA0tm70t3!bdEIz;xMKT0`Ss?$AxA=hwg$7v3LVm zMg569n*Qp|wP>TDsSv0{jwEt4f5n`Ly@S45DR%)IA0w`ARgXzUQk9U%*6|?Y+tW&s zQ?f)5+|uLIp`S4k_1s3ZF{dHViLQz>;_WTo4H;{z+5 z1F$WNw)g<-*u&T@V;Ch;uAC_0Avt4r3O0>_1LLMh?lO**Avtvmd`0D(NM2O>BT7Nz zHLFTAyPs_kFfDLWBjv9}f800*d@@MhYHJ~q++vlz&C&@|SIlUdF0ada9P~dpO|~|i z`cwm!S=A@^_QT^ouq$80?ZH(z(08*OOiMNlGYafxYls1?mFPc%!TK~feYC`U{nrrN zu9|R-2X&TJ$S2_9z3!uzi(S;k;9-=y1iJT24;YtB_bY6;XI4eT{0ZbfjNID3;G6R!r|*7$O8$~pf?mx3O?FsH{5SK! zi0Q&~u{l!Nbk);twow^{FQ4{wR@2mVszUdz2WOxdl$7grQ;PBSdgi2Dfq) z6Ak-hWNuj|qo`IgGAM$V`Nb~N$B)co_`@IE&#i*|{KxpvZE?*i6R)_2QE_f)^O-ri z84fNo?mwR^e_nNb0vj#H+~~+b%wqyW%x&a{$5M>sN5(wlVniDQA`;?o{hYWfQY?mG z1|5RE2G_18tiUc3C@7JX;Y_pqaYGQW)j|c2kr3X@V45ljcg7D0^|ltHTTGis#XcIY?^Vq|okno@$LDtV1T zix5{NBy`y6%Lo_Gkcfe4AE)dP-KMZ;9cBH3!s)oB0CV&>C@UgvMKD%!W~hQXUNc>+ zwB6^#Iwhxv`PNa*qaEaK4uK*n2FpDa!)f`x5&x*7nQ9b45Gv?KlvLrjcgJoWk75$>kMEaGRwMVh^Hv6SOVtOfH9TLB=4r zqQDg-ZWQEJb$Y}@qItHEi2}!jA>!3Bhm;*Re?)AFkmVMzusq6)#d=t7v}G*8%8yr0 zyE2*gN~RDZQ+jm6UT}j|4CoShp+^Si4r=H|^PFn7Y!fzy+4riNj1Zo|6=uIYqz4`o z)rew9Q#9#xxrzqSWW>{Q0y7d-G7%>+67x@#3A#MMD55EfDI$9m&dDuKXOoK?gohDy ze{CVKV8O)HfL4J~A~*}pijhUUfdgu)g+Q-en?=+)nwWtthhx+39+DU-Mka-U8770V zbtH5{o=}V^NokT>P$jnuNS+%N40}urM}mZ*4J}f{7H()w?WYNNvB)e`JR>PeBl!g* zwK*~(IC7~G5*g`_)WpaI5sb7Ji|m$}fA5Vv=#JHbo0ym`oof_m;RZ#O+N6N3l{!;) zB?E@J?E4*qy5n^Xq=clAfH>RL^@sM@qS72X-)X}A&mHiZwaDuUmRG!ulM!x%O;&X{tL{;uIRf3LMj z1u5BR5G+cRmaLSn-Gt$V--Zp_r_wSQ7dS_h_H`mzJ6Slg7&1ffOc6C4-%M54 z-5xQF@vcyeTNEw^+s7E71z7kvWWKoB+)y#h6-Os$g2P(=3rZLQrLqQ$8>`X{g4=rv z2jP-)i{J0+Tn;#cpgwz%V88%}CRj>RR?@A~G8=>F#SN3*Rj<$sz6>{p*#Uq-|-j@MOBEMA&C+5WKqvlMB#)IVa&{Ib%kI8#Ya|y zfGd$J)w)ndF4yG5du;LQqym2dWyfCB+xS24^4}f(x^6 zDgFexg?)VD)=!Gepl6g7Q>veV2MJ?wg3!{as$27xAki6J6JZ07jjcxMl38y+fibw; z4G7HFbEg$VQ`im?6RF0T|@Jl)&%_vvK6!T;!;kr>hiLze^V2AQX*rMpqfz0 z1}zF>oPu3946R^~PugLU>ZFGR;k*kw z(^TD*s^Jxajv0CAe?CjqG#bLqY8!;HtT5;DTG@tnWW-W)2CIwUrgjIpM>iE`twn}F zkTnQ=QH>_8-PmEOBN~K5v5g7#$UPW(C>BIn-Jio%SXG4&v_~KvUbR%+GOBJ8P@U>l z0R>at>{z$$5(>kd&56Jyr8*VuArb{sf0(+-Wi|>iu~2ZYe>Nl(P2G;0oFR!FaY`-q z#tux*fTgf%6_4Sr_?X3cfE#k9D0RCvF2B@PIaThBc3`j8i(+NrPq9y zzVo_Ws34g_Oj<#u8+Sy?gP`=L+aY4F(!*Toc1pKVqGmXv%pfu`5-UlB zWEu(S0qI&3got!Q2@9*63PvCXdh*fji$`%sH>w*Pe`M05+Z^3A1R-oBrkX}K60%Fs zhbAKMjBb|>4IXNCB#UlYbR|+$bPEieU!+d;GmNDWLp#&a`c4AZYP ztn8}%Q~s299b;!N8?6&KD0CDxjYc+zfdYBwe>~_qbh{S)p-iVM>VjQi>(k#bs?uzdhK$Xq8d6{3P-2K0J0&3pv3-*q%+cJoCTSwCAEcpxtWU;fB#p|DQZe&&h?zkl ze?b^25{G?fsr!O?5R!&zlxV@2mG8)JP{#1B$1=%^*Q6{PjO+J6VLu;7qi{G@e zP63g8e%wG*FC;-8EZZ{H(Z?c@VQ5!w)VNc&a+ChvS8g*@WLTvt6ceO;6PGfjV^MAU zUfJ1n+Gi+9u}72_225+Eeb85lcs8vne=kM387>N8F$;0(b%Q>Yte$F+f$nAy{9wzs z6qMbnT+u;_VqxM;4klV)SD)N4LsD)iC4>Sz;kuyagjsP*MeVgUfs7%4D!3aGKNE&B zH_|#xb%0@M1y@aOCN(x8t0klvu<|`<2sR$xLMY4}0wrGl`p-ho>JV%27ZrLof21S> zUvjn(8P&Jd6{e<+D1{G_B#tXI*cBKiDpHr_Qhi-PJrD&8sp}asa^u?2%ut+ic%k`q zAsex^A_9??gbOaWC@?1bEQLEY$;@EPTnNucJ&Q(ea?2Hw%k5;B5VQ7&2WYw%6}gcJ zeZ)lZ!lRNisyzV)E1`!Df^$d*f59G{S?ExKELcWu=m-fYsCNeLiW> zBKTZL{~QDnjx0rXjERScHmxcT1T#z4`Zhn!R0w8>xbL`nEkWH~(D#L{5D7}e;R$g2 z46m^G@96CO4cKH|C=zk5LGYyf21wdF}Qu8 zR@!gRU~Z5^RdRz4w6BWv`CC=RHSf0||E*_ZU=(L>{$_$jCltQ?Er@P7msH75UK1sQ zyv41D5b(DPP#S|q8N(xnaZusEE>zvI*4luq!@ScLLIu&08)f?Y1W%sS7}9-G7U0tu zBRueddxsqIWx1$%e^psre^DtCmfR!AF#L@(ApC8!)9$ynJL#2NW!k9{6Q!h%QW2&z z`CQ70i(Y&)Il{ap)+_Uk@uo-N?7DT>{DrnsXW^#BDOg1t?p;R{d zxZ$+uB6weDqRuj(@GqAZ@Qp-xsdj$($#rFn7{Krv4H`k?7#OP( z7k87p>I*!L+nHWK8l>@&xF+E-$hc(WWUOS|Bn8PLqc9nRFf7Jx5#u&DL_CJz#gT&x z;&6JmOAg#oWqXY3tyDC*-dV;B#~2u5!cT+6i6DZ7jSa>!)!fGxZ$!izZj4GBB(o1{ zX|TeYD=XfE{u3WY;8cHfsK1nQv!8XEkzhf z;-D{bh9+ZJ;xv(mcy}fxanek;OE6LE)$;GGJ4!mwVFdYqaeKTYZlpFL9uE{wA81{} zzAL>_NvCF%((R3baq=-=pdkB{ZW1hARYaGn8f&Us5LK6WP*s_BD#80L zrle&iCgs#^(-NPUlc$W#+bJTLBD*lgAG8XpfA58^*T4viJAa; zPvrX;QI(PvVZ3x(dN181>N8kKBK6|B73QUGS--U7@#|KlBST|Rn8%rHylQ;Cq^SNN ze^_VfD8n#;C`V=8Notl0o5ljM&K2sGq$m^hi8^G8z6=Z%-PBE%0bbd?2+ziK%b-n< zaKWaV21ZPi#3LwVf~g73GSIE513Fz0&;)V;6>mToB^559n`NMDP`ZT;*$5ucO@ku` z9Z;YjF-)N73bm$Tv`a`vpo$E#>6o&He_Mr!U#7sKDDl|IbepJ)x`2{+3H)Fd(`}?K zWf&V0xm06>GdR)Rhly*E-SU)Q*AbEGc2z+JJ+=^8yLMNxD_%jBSfy1iTZK;g1{M%i zl8@URx2m7Y&@!+prOT3TU2(;dbx60qNc{vBBR!D>t%|$Ff(k)LDN+Tu85bxxe>hBp z!)7>)IIM`nERDl1?Hq=Q$zhtxVVm~lFh=IEko>@5kpY<(RnZEGgtz!e0k>|0c}BPH z*k7n%>`0*~NUbr+BBj~t%G?)qY27Yq)O9k3i+zH-Y#rvWmZE1|SokX>h)N=8R~xzo zi`ElYqMIc?PU7qpBoY2$!88U6e`6;;p6U{g5pku{WBUo&njrN;P|c$;n4F0qhg-TN zGs4^ujS;^sd$jo=m|F$W0b=yrxPu9}br0q#QWeY-LzvqHL)jD%z6Yh!@^ZlkNXYZ= zZD{vlT_y7gnYk3$C!_+j{l!PvbNSugcQH;VU2x1@vFbE;U5CRNB21&#eP67a%UJUu6 zVj9a36h5Q=vxD{_`MD3}Zy%OlO7{|kbE)rCSGF;3$%7O&#oz@{DEf&;sSylag_KP; z$#04-O3@z?0@ zySZy7Jy?FvLj9uJ2|Enxk6p^e0OH5oCVt<1m~J$w2|pP+sAsUtL)!~QlMG(Gv>c1qeWg*G;&t9f zexO!ur31gj#`?Apv3X4W3w*1abTkiTf7ukCx5Zx;En;!u^V}VBLm3vb4D!=8*leyb z<`!eQNe!C=f72+T%9sk7$A+0k3TT#2;#I9l4D&(lKSm!=OP+h}y%my3XTPJ`)QGs0 z1|qkp(sN8>G}zkc8BTNea@tMjs~ThWgRgwf3Ir%ElS@cT4d)x zC|p1fJxCK_7fX$y{jT+$|5;<0t+w%9S>HFd7dwNJxyYxYGpBV;V$<=WG^HghdT>r4 zK0kckfB#q!c65m8^R2|g4TOfF2bxk$UV^vExxsl0ZpjWG+DJYz?MP+P&eA3fpfo#_ zo>LXl_=edIe8&qq31;g-^~x=zCgyu+friK7s+Z6iaSd_Cp)%j#uhSq9H+t@abK)84 zYH^TGqHibqa?r8nG_&_>^=!vNMv~-ANu_uGe?J7kJ#Cia z>f9CS->oucf5%&vbUnp5C=Kahmx}FxLrFyBzb$WgK8Eln9S%;0jNx*x6@UO8%pP6F zApi09(ZFSHM-gE&yone;jA~RZKGIR|p)-RL&{^!ehYnh{5$+p@0pe#g_E6QXgxRBJ zf4lvBJ)0m!yX?|9bc<{snvG~*!e9<)c|LST-fk);WGlvt%diibA5kFQOBzUi#76O6 z(!k|s93y&7(54^KdnF`@os5~rrsVbo1j4W{2(%{pOTrF6TL-PjZQC5C*Sf2Z1-{*J6u$8$MCXb%m2j%qWP8x5wKYX6vB zzYiVP{=69eSSlB2zf+#?pcr_=5V5+qp8YQd_~n^`k}oZ~!SQ?cSGtO6ugktFog4jH zjwg9}9go30aca<=tVnXk?G)ZTyG1~u{;QGHPg`~FR070}V;ej0VM;}Sb$lZae@CnS zhIeQJoVRo?)ZG~U!-6T^qr-kE?MZg*;K7zeVx?d-{#LM%Rd7gvi&s(^wcKzUF2tW^t~$r1my3z*YxX2O!b-SC)*-1)u1Aa9iv)G@7r}wnGzqyu zk>2~^pEqe!&p#CGpSSq($z!!jf61U+MJNGFrbs(3!ND~$otgWE7FiJYE4gUOK(=0B z=T!{g9Z=>(aq7A~@D&(*JngRg)>&Fy){Pc1^tr)9tg+Gd`&N?!KhoTQOQl^O{K*{K zEyZ6^ShGXM2MyCm(SyvXNOp(@wyd^4m`fMX=|3DI_64(Cxw1(T^LPBze*{u{ppz>X zB`U-%RhkMhsySBP*a+P5bTj5sGNj=5dRgOmstcnLBFlxJ)`iXWT*sr|!?8826gb1XW-%*rPF#K!k5d!!u4_sNuXEcAxt@!k7r?tJOvw=;RSKiN* zYq|TOWx!tk=(;6ka4#9pJej)DVWP0Dt@{b+krkj*;YH0qxFQU-lv_*lg}uIj9* zgJk{j92)3axUrKwD>TGEC2(I|Pd z-9s`H4L%p~_ch-6+w+SZ3hLg>a$RsIXZ@pR5!aT}W~}&HkRl!nD*K%9ji1?pd;=j^ zYDIpuwE8#v=B=f-R%w_va`L=!H%47${c6(t zne~{UF~s@rI9h}BeRfPmvOsn+;V$w->@5ZMxRivj6q!GuY}8T+-gg{VBy=b?eNLR` zpYPxWLz~vF-+6I<8_MPPJJ9X*hDD0+eOZWJu>o51Tsg+D&ACIbkxv31n|^Y^pChC! zk+qycbN!r?f2EGgirKvx!)M+Bd9sUV9?;u<9nj9>1Z0Z1#GS3qBx%3S4rbMV`+MeuvIP*GlMUF z)rGi9Yd2T;H9Tx^aOMZ&fh&{FlS>5ecCYDKm&7}!f8iW<5!`TqST?zigv=6 zS!+&oo?F~8ouQv1GQHN4ZNQ&zU(s_?ar#JG|1rV`sKZrC((>s)-#7~lMTQpXEBcABF4#V?Kbja&%(3Dfp^{^l9E;R> zf@qZOIHn;0ACVOH9g%=8$NPho&dNZpXRRbXe>19l#K~*)ND-^v;OF@6@E7KFKt?YF zt#%y|RvOslNkaCi;Z?bL(qVBAK!38D|E1B z;wT!+Uc{V2J%-!db3Q<>lqh#-J-J-XNENzV{eOt6BLx6+VrBLLV?$r9IRK8MK;vdZ ze?J`cL&-xdRwy_lY!e?N>y5hFvVK~C3$J8H~=y2pYKR~+V(lvg5L z=W&pne1t*{J$8H26Za6Tm|Ss8Jpr~8t&n}_kgrs^T_nA44d5P|<7H-XJ+}w*_ZIT@ z{lr9{Avnq{aqSsV7E@)_EQ&M#I!^b!K z%px*WWorT(#zzXg=HOH=WoYm3fA{Mgqfsv>MikJ>0-u}y$|htG5ygXw5zbV%-f^|? zyR#{~y!AWk!f&f)HSJjZiP~TGcB7ElPkAsGUo0vxeHXVl3FtuSH72T|{Vk%Iqz}DQ z%m9Fp;rU_UzP~h@gd*KsUtk7-z-_OI0ZY&R8dSjgs>cmKe%SafHd~{geu zfafHy|3FxwGzc_t@-#+*afoQjFN=yQEc)+^X*l#Peb>b6iM+rU!kx5D#LSFYV`dLh zKy!`Qx?MFp-FGvQbYD54e~~1caW9}=L3?&{J{n`@>Eg z6TF2D5kfn2+3RJ``nKdcD?-iMVN_*9vCqcrIInGW=aN(|?4^CZ1#Dc+wyoP{W@ct) zW~LZpW@d&M+sxc%W@bBP#u#H}cFdGGW_6b}w^yE$^_-)N^o_+-$NGkvLsReg&(-lW8jBAIP%F}B8)5*GQgjag~l!6=gc z5^{O|WguU*z_&v3=K7v#CLt{McNENn8i?|TqKVF?v-9B&Or2TKGx2LVW8ySVt#+&d z*F6)?j1O9L)!ImWKEwUc({Zy=K43>Ihx$UgIMcqMlBx&gsu*Yk0opr8M8=Ex%ZCM@C)}+_G&EiMfO#AezAaEuv zS1Ovmv6k?QH(nw?w355p<`_v+ESWeX2Gwfs$jgCMoHXjoo(1~W2MYO30Saz}DJ5Dm zrcVtS!abmi4kmg@@aem7ps&S)#Hze#?FGNx9^cFz0hzRpa|HJ{k3^|u)SOXWjwVP` zHwW1-rUp2el4McNfa`AIe(RYQa^SV)(gMDLG{ZQO2P9p(Row5x!MlEa7btw^wyR#Y zSA{|*?kL3&9Rz;kmpM({`RSgN)Z<*CQauC9wdq-p$ks-Iw8h2-X~b@#w4D_D zBs-4`#UX}|ZmTnArsJf{G)?so|FJPgXF|;u6$@-Q&W$6askEZPPh&ZXNU?KOopx<=`UGo0)e&Aogez@RYq(a-#x|4 zit^oJ4f-6|Dm9eHr+4#SE%LnBpuQV>q&=ls2ELa226mEIcciN4%=PmxU6iQXjMzU$ zBE1CO$SC~b`F1y}NZ32ukp(=dmXKCfX{uRQ)Jp%dqdv44<)De%Qn52vr@;xe9Zqh) zIpQKSh83-7T@Z%WPl}C_CbR3gTJ{%qgbWskET>AsbS3p#G4{>mH^6=b+HpEW>4P3a zfIyHD#-tw8ZzN7`ML@JJ)2*jbp|Y@^W?!B9F$G`aM!o1l@~=*lok#>{*y>G?65?-$oJzp(C);gx2$HbXmng0&G_Q;BP5muBOIpai8>PGNa#lq8yYjgOi7AY{3`(n6b|~9N^Nn#Zqz6P zk+u$He9!2=4GGkwelmjR*yuL`i9LCb6BCLqIAB#Z$i!V4O*+-LqtD3wJ_N?k>M`C1 z%QsVLBwL4VC42!WL+5YwizdO(x_x_MY*~Ry>232q>I&xvXP+**_Ek7}nX2ao`Su78 z%*t^iKBpd0X22|j>GjF~B1*wQW~%*~8(FqR^`IjQBM#(!dOE=R7DlzrB%G%I`j9=f zqI4>hb*OHAS#oMQ(7prkuSP_zunSwu%gYuS*A z1FC3Md)jq|8jgaJ6C{zO1Ou$^gX;Lu+`4@h7zj?;-aN1#_8&)k4Z@Aa<1w>4rYB|* z_2po{ddWeCWu4iVjx>r?YTE(^7OIo1vNZoCg5lOGJ+ zBgNnu9WUNv?Gz>7&EGL982gHAh1>OT!Wo<5ZhaEbrmbUv$m1+!DAtoyUZJJN&;+Xj zu%C>z8U1vHAE*AJWSB1rq-Y~g`n z=fmZi>%{a#IrsfXydbj*x>YVsw2u9TBhMvP^q>-~FG-WPZKE0L?2JQw-77&?iLqlztU z!Q?ac<=Fi3Vr7|lX=Ch$`C4X@g5P!%VRFFNkM@^K$Hfy|0E*sMR{J7H zQ?JN-leDW*;?TVtS@`yJS#)RuQX)vtBC#v3AX&#});;b3mN}L=O;3j|H`MV(bXwT2 zva*{nh}%EQTc<2tUquZ8ZlY*UsfR0j*S8wa!iLnuCf)6m^}Oon@~SY2X);$kGG(R| zD0g>aqz^VbLa!@M*)0`oRgassOAbIE7i@%Dq-2yq!5qY1!pQrX0HWXm&Tr0e@Yyg` zDP9|L!2bZgYrr4(NNCGd! zATib8<0K6;T-eR(e5Pymso=-CFr9$W`IMfgn_FG4S<~)UKk0{llh4d;w739^JuKOT z@fKF{BwFbPkt$EchA1XsJ9{c~^PJ~=hL$k?fKEpuJ1hyyHEeS`=^W&31414M!D?F7 zx*oq3q)N=hc0;$mP4ASvBWMbHR=?oex>n5O!G&sCL3ta#^{A0A7a10uqR!_>sru)tkZcS~!Tx6r5e`sWVZ0Uq> z>Z1e&d|jMpm~C*XMYcstjCP47w=LvPDrH(kS7#SZOQj;=%&tDbZbII;b8Zu!c4I;# zR`80sPM}P{kbSxKU&bug{|&XX3ei;{cjM6*Ld4~PIlmKQhNTslSB=>(VlRBoqtQ@jiw8ysVpvB7Vtyy$U5ltMPq5&4Xydz|`HQRb?& z(Mcq-GE?9^s999n(su#|p>=Icf5DXBqWPFM4>7B@x>2xq)c*YVSQKC5$Y5iDKLE{) z@0NReUT-nT(ED(7!6Odno?zRXN-uUCkzZaE;|o`fau!1qS+vKlZ&x-@qdcOZAz_N= z7*MFqaP#KGbXx(Js}~{WWXbtEPfzt%LU-eu;TgiRtz+i2ROISR=07zTsiU`BQ1ZnSzIp@YW{V98{#jN)1(i~ufhn75#92YFK@!E z(xkZq3=M=OlJg&dd&TzLy{Y44Al=n4}ltBJatz~ zmXv?%#xWPZYz3P_Ac@cZ>vU)v}UK^GG~jh??mn%`K6uC z6Yw>a=|Q%Qx3)+y)OKH?5Y9KAOSQ~H-RoxSU^%MP1MHuzG0f zyDjI(KREcO0{O#n0fx>D!Nbp8I=S)g+mRILyCcww9VU#8TK=xg%#a35Xxa9Hxtt%9UeIU7X;5HU(Rn7)4pHK#)!|X27Vu_oG z1A6eeboAcHgi(^2E|eIztFH#SYi>^Xwl(ijqr|V$4Fimn-uzS5ijH}&aWIydkLZsY z-C^%RFl~o*Prw#Zfl!P9t@|5Wx(60rJ24wAg*H2eb!>Z5vTEF6#5*p)oTw?I4XrP=xR&>;jkt!r9x=#3{GNWOwFwKG zbH5Y%8WvEvh45LpP20E@acUyNPtdP6d#5Y{#%5`T6gKg))IoO8;3)IbLUeMf2WS@m z=l2<5fDpH@7IpR?c1&r-avkon(n9!RyY_GuT0rFu3;Jp{JnEogAy*Wnp>R|pnnF%y zh*pLhVP%)z#(dWA6kAS^p!|;#rbfzX6-@jX4P=^t?cc=P7^ck2vH~wb+xn}-`j+3q zekheuY3GLH;+*%1l14bA;3{2CVgOGd(h)Fzt@d|WvU!A}esQwWh&lMi9msK>mMs~# zMZ5=)@TcdML+mdcUd$5SC0tOxi}7h)etcsnFcj`29@3sVqp5-*B~5Wk!=|i6;5NDR{I74aJv1@yjw{ zZJ5L|tTWK=iyGpcgwyHMgkKo2Ag&QHagj`&EYM8D9pY&s6|hS0w)$mdqK7iV6n#$g zB}JWuEa>mAE%z5^F%6FO9jd^o=xMbJ>75&6@DwrvP`VTD2g%%+{*S}u!Vo$NId!n# zm7wIgGJ;4J^0NZ5@3#))?AH1oWFYI5(MYMLT;d*M)P%UF6r~;MJuhqPW`a83Z*388272NSzQ9w-(Aa zOe*2)J)hMopVNCsQ$lshhO`WqYXT2JiFx+~rv82ZKhp(knmiIfXLA6HZqG}z8B|&q zwpmhfYn*?37k;ZVRl8~*!2iSC+By1n!Y_nG+Dok# z&KGXc2!)>cKB+H}B4}1!VoqJCX!>#J2BNkr-u$g7FN5Qr#*$W>*CyL*(f$2ZnfG94 zDi3IBWg=2>C3sgrg)=4c;L$%amL+aGj`{cO&O{1du)U=dOv?APb73~9_R*+gjK5=> z#T9Q1&=V!9AoOyNH4#m>p)#M!#0m0Hgyw&|r{IWJirxUMD=e%dnoe zL`4{jW|1Bxjfv4Ct09*8oBxL-s5wA(GWKNK$JrFwN;Lk8lXFcN7?yxkc$%~IHkZPl zA>H8Tgx?y)#(eF@S{52SrDL-F4V=?{TGH?55N9M0wI96KWM>@xepEo6AeJdEu$3-E ztS{k|lMw+KNCVn^A%+h~(5Dz)0b|%plp-u}LqN#(Nk@B8ZI7CIsI1e#IqtVoakTwX z9emTYeR|8*ld*sLfe9U?%5IRAod1Sm6!T;$oX&~uu5WhT@1hBr*5oGm;QF&RD2%sE z^Yjv+Af7_SJ-gg2~H%lr1~3 zkBl&Lnx8G;D>=lmMKkh1?{}X|5%BcD0@#Koh6|`vhMiy=Ckn5 zYVBJUkWF7TbW1&G1Eo4U@fO4H%Uj9O33(itNTg#enlt^lnk(J`;|}X@pWfG zS6AWJ442j@cIjU>(41g0C7>LUYzKkJh z&+s`QhnFhUO^?EtLx<_QM9w6?ra2G0X}kW|w#WN2-&9)=9~`Jp1-^GUq7q(W%B!I~ zk)_2>B$?gTJ$4e$VJCh07P{xAbE(!~(9Py>Zi(6q+-YMS!*BH%j}12G2^W@iXbWED z2Np!5`|}4~seFIu&a?lTo^dy8qby(F(fm@r98=M7!-_EUyf1%8p&T&2HJReUvZVs& z$HEzGc^{qQT9WST2W+6`LB2O;GVt)n-^iMbFHhc(pCrTBOp$FCz{k>tc#mxxtf{-) zi_t|Dw#r=NZnv+RNTk0zo#`Al3dbd_2OjQ*WO9gYm8X}0W_lz!^Jb(`x^U`HEywo# zS+0(aYdAxeyD-HU&oS-9w`G4aG1;(7k!_r3(7 znCFxL_|QakAes2!17i|2&%X>SatnbCV92gDoR)R>ABHbIt@f9@u6`R_wx-w90J12b zCY6;DPZ_C?Po6p6<}$mn*)y};FoYB(5p`hxDlJg^QH+g4U#9KV0DrEB4~xh@eA!mQ zu>od3ElUhWY&7pmkCN;H`x3DtT|E42AeX=3zQ0GDP&D9k@ku{_C1N=CiIDq)&-M7FD(I;VNF7GNL^3(-iaV z7d7F<5&5R2MJd+C>h*O=h=4d-aXOOwvD56~iZMGZj6HG!XF>tl;X%IHKMVA*u-x0^ zcU28nLrO14wztA~_i_g%v`^nGYrA;g+8cvfu*sV6^uM>Nuitc&MI+|@lTwD91~lLPtlJGKBDJ?Pkdmk;cv>(9B=-Ba$S zewn_1WL9AR@@u77h>G7X6ax#PLyo68czUJ~yxOATG@*PzSloVEY^r>Ogxf?8cD3v& z70;wXS^X1hL@g6|n5iVA{gOV9%_V5m$0vX@7OG!(`}-Z?PPa1jX_<6ShsOz^p@8K(*xda{6P0)PF~X$ucpX1 zp)-)d6Wc3qYl-nQm z7!dgO3|C$l2!;Bg4)(6cF6tVzHJo)3_HVZ(K&f`F@|Cdqi{HbpyCK;o$4Pv zX@1F4H5BbxYG-FLBxZZua+}-nWMSH&PVgT)y&|tOQl>_kK?5Oz`ZGCEWV?4&xfwzr zoWm-zr-dJ%7~|-?QMFS*%UF?HG9tr-t0EZ@l_%DePh$?;He-u*ZJy2W){Lb*{<}i8 zY_{thGs47Y#HKs82YQ!hWxss9PWl}lrn}phS0AuNesIZ$=1+VeIy8GLci%X;>B_B; zwz56feEbsVItPLdJf1)8PeFkRZmRYs-{c*TkI&(g6-VBn?ApAF?{eVAWyuI`40}yDE4S0MK7Ko-yS%9LP`^dXL!t@&6?KayorrS&A zVlEYj%;wk}r3UT^YCHRzD6~eZ-SE%wm5UYO#+zOx<>R-*IzGzHdcN7qla09uf=79- zcTA^UJ^?4V(Dsf5t%k6lb?75XvW2&XmybBlTISB>HV2P!pq=>ENSU{ux6JSmCDhf% zN5|0eRpeYd7QsULERwvTzrS1+i$#|Zb*Ehsqg_+^E1R z>Dyy(Dsp((rq6f}a(5pSO&daUmq>c6rAd0+;{(T*ax_JLH2W1Kt}i6Wg}5WGFMZm1 zsSe&miP={Q-M{VcUl8eJ8WCghIu(cKp}PXPPXF4|A&R*LE^?aijR&IL4ILp@ls(P6X2`6W z6CabC#vf%DEoUX#UKbU2mq)znkvP;R|6%t5{k^*bVSev@1y%g1=~h%H7^| zq6?(Z>d1aV2*af+&DINEtu@v>!80c?Gy_u4!s2P*^MN9)y*H3|VsCpFP=-RJ!Zoq& z?z|3e=r01}=Ty!u=E!Z$3lFn|$@fo`6eE7tRNc2-XcX{2+^p3s#&;6o_GuqS3_(YR zDXc&8%EBJi8|>4BG=Kw6qy`(!=x^x!w%Saj>x$K!>s}CNy=Z){tqb|utF}WCgn{dcIXwK5pTliTJF55eEitxc_$&?gWA1&=x!mMmcw|Ud%`DRul04%EBMCLi3_M>Tb zR8JiBA9JkHMk#8?w$V!wL(>{Q=apvBoRku{2<-R^N^*haiXDCD%}2UCM`0Vcs=vRD zu>X3q<>D8U{UY+wlu43FdP}*U4+ExgHoiz7IxYVm5s1*rGUwP>2|>!N{}32>mOe*L zl!_25*xhHVy7@xYZM&it-;s7Pe#w^~WO)nUuGz6f8JZ%#0Yz-kGo?q0ijs90NqV&h zEspRqMpaf0??VvHm^5*DP{)`v^Kw_4+wryB1u1*uFe7_FC)o*A?XK3Kx(jHQRPu!a z`?(gzk+Y#RD?_z!#x=*8M`E=C70hhr$peXv)b}sz+sD&%A3bMs2xgRA&)U{_BH<&LyEnxsSU~+iteBLpn4VfxN`4jFgk@N zH8Yhx2NdiONCGf$Vf(t1e)-S(-(76;JCV!J*x>zsgenECaz3SaR=iQqQoYZ{pw}78#A(-)7k8i}3haVJY6N1_ zSSo>YdBl?CK*+Wu>>woigz<$I0Jh$E6n#MEq)VsT$Iy|=UYaPu!nu(0Do`dDT|!#8b(>oh+u zlwl0~3m_>S%>5K~ls|Y<%Mv4%(~^ON>3b~lLkpl8&xS=}wPe~e3K@qbrNe+LimX7P z>{_Hr@JWKcnWoZC_`%9^oq33xuW$-Xme;rcqpPhYb%O_pg*aR@J)*D&TK6^tKX2Kv z2eNFXg%m6eZ2a1~LZz)mdSc9I@dk1`O7{qaDqLBoe~J4u@L-o+B1NwH3zFkaJgaz2 zCa5oo%-G&dz`ilBhAVAgFunKF4{qX3MTr9V5GJMUm3#P5*2syWztHaiP6 zEq1?YF6#p{i)%#Aygr4oVHc7wwDDQ~YME{vqF*3>t{+J&55$7J7{B`I62B6Uvx~A! z27POYrc^gGO_2qeG4U|e>PaAt!g^K5Z1^VGb1<>OQ{l-$x;&3->*Awl(nbhNW{!6s zf60}OCV0PY*QnMys3|={-&LSjwRgT%TYtALsf`c(a4P0B zg_@oHl{uXuLMI;qnK{C65Y4*a$33(7JL2Y*2~1jasOmkMf_JK${$bD8Qb!`16p$92 zT-?b8tG#H^P>o>bTc$tGyR#2r3nrwOQh5{)_`#OcplHwI$ZqPZTFsUy>M2R zhnWX7s3H*6&vh>w^^im@aljTWS8)`d}>{AKA+7ec>$%F z_2gU(F8sN(thURe&qim{WGwZ26IbvfF$tts1$XQfMr8tosli6~;*}Z*Y}mr?P_F^k z(DF9te)beL_pgZN2wzMQS8NFgOWA6?8=-P94a8T5{ z$Wb5qti3>U;V?gFf$Q~RC2r1c>;yfb6BCJ{W1cDnzg`Y*>r#ohW#q7L?!^#LOViuk zW_tASv_GtD`tpoBFzs_bKBx`<^U<1Lr9LK6D|eifT{IdNViKuW+8fVB7}_~N6xtkU z+#SaYBXIhb`GVUwSYI=c5`H5|NZZPgp#wpb0aGsaN()E z#2e5FV_iU*{6YiXRV5M`{e!!i_U5MrZjsds$|O59->N2B=(J|@xuAvN!W4;X)YB=k zk>KX0DsrElDLTTuqErQ!J@~d!JHay0L8HJ-W#Wv&^C*3JIN9F7)Nd3~CF%ErgM>_W z3C^%sOPBt!{8C1^*=q73_a1S=rAuy-n&W0S8>NCR8p7UuZ|crSkgehrNcbr-MpC9D z)$Q(TeJ4YSL0%B$6vY($`a4PwoN8n+RN(JNI=Ta%&?G4_9q5qmD;?Rb7WY1&6GFgy zLYXAxF&$N!+O3asn;SW5*`wlU!L8Addw=H)OEMDLP=nAPsvg|PL6b!eDkeSlCasf2 zA}2AE+Sp-Dd>yw^swQ4pPPLU3id#ax(V}+^>mGTRi<-m-%pEX>id$WX^=Y zKbo@mayl!%l4*Rr)k1}cViyhp%^d7U#IXj(P8;DH4rAq*Hg-$(21Kq9m+RntI@J45 zxX}37+On8zUJ^KEl6@GN=%DpVrQ~<8l$J+u7k3<&5|CN4GDNLsT^=bN^_xy0%5fs_ zlpihT<3K|gLqCc(Gi(o*vYN-86frsKrd*67kpVv^w}nSn<^JTQ>Bq|gzqdBOK3xj) z$50)agiN$9Art$_VGL9&)*lqkB@;1X^cM)LmRO&yG7YG*Q`V@xMB3&orcjPZeUr3$O;$XEC(%$~ZZJ(c1yfwZm@6Uta)bl8P(b!Dg+o}14QU%xQ$U1cT=QNao$}YN8dvFqJ zu0r89_BN>D5FcNfBSUQTB_x;v+>N-+zxR0!ue$LC+K`sm?DzwGlW&XG%dq|5oF!Lt zMES2G>T1b2XDaCGfWtB=Y-LBmDpxx5Jg*&zoAwcH#nzpHXNVBO*<1>50{4Z9eJeuF ziG?|%9kqU)wJPRDXa1erckk~h+Za^_<=XGe2eh=m@DpjwDLMjfZC>ej({}4^nNXl- zwng3BA`(a9QCD8vGb*WUV9()Dhy~U6EqDei@MDfU6}l0lfED3|EvD{|zxl)0W->rZ zgM!S-L6a)s^oM%-a~%FkA;@H(1%^uSzjZm9VW6jW+ER{q7ay|D>RZ61t%4s98 zm}BF`W)NSC0ty9aL`r!YIgIwqitRusj8)suxjSbrN&oQpW^~DS2UcGgi_#^Dj%L}$ zU4`X?==lN@Wnz!`C4!jE^0kErKJBPr`ukR6+7kQ1rK^tWE1|F(FW%=d$-p@20Tr^$ z^!`V1=PaGJwlN+ zSZBiOtl?dveQ(TqWE@5WdWCB>WUPyt^b;#Zoi&Ev*MUp$zb8EE4${rUrnfW5sOzm| zjpc&qkKgU>33jhM!Awe7J^+s)FPzESl>7IyMy^3i7+Ai7s?m!GvTU~AlJ+{wqSR(9 zle5-%Uw{jSYCV;px9I9GZUG;(VBk0+3@=3cQH03ZkRrc_(=7u*eAf#M1`3 z4(Ao^vl8*lI9E_g<(vaQw@ar7$rCh?ZS{C>6SAZg(?c1Jw%RCU^ZO&^#~ktRL4 zd|lX>=&3JNLh{em4gLCybF|TN1~b^(M=EkqtsAI4+;oJGFd9Lf{zEW=>foGQq&JJ1 znQN$x(bDQVjzsLnCW78M1&72?`qDChuwlf1fRhM8A57$>SAhfviW#X8T<#}UJghof z5_PNxKbzgzY1~s-gCXGc*toNioJq3#PO&e!9sbBa9zP;m^ULa-5=|$_q_`8f9|7I5IvXD|-bc7^q(S9oKCI##OS*&qXCqC_ZyBej3;PJug zO(N<&%8fw*@nvWDFb^B-rU8;zqYB$t5N`Liaq(lWC@pa~f{Bz)i_2kbaNEe{!rnZ_ zSIZyC(evn@C3%KGWao^13Nd^==HWD-=l#!9VcGhC5XsSpu;_QOo$M1_PZjT2X-}G6 zJsy3bEWVyi>JObk4{ge9*nnRP8w}6yP4DC)hM;$N=C< zu0$MWt9Q}KrX?MVB@2toxfSosoew3PcgeSCMmdtOQW%@s>bSJSK3;>jqI>K_juSL@ z?`&)N^MFC7Fr#9skTA6fRDiNpp$)2SvU2*YKj*Gp^4vU__WOEYzrKhxCRw8EmEE09 zIR1hc7Ag&dB{YvO)>A&&-FSk8lmrmDwSro27qjhdJ3t>%^l;~J@iGFT!V6aGJ+5hx@PLUAhYmq?uv56-p0cA+q_PtAv*mgSNh41XwO zd)+i`==|#;*>}H1r(XE9T+YpIw=T1_@b$F~$86hwS}yO9Xu1b+{1tmKT?q&&YS+3~ zxFS!mtY|RIp`7^#7yN=wfLOZc(QaR2iu7a!d4kBtho}u4lU;T?{si`gqvH_fH&VFK zf+6Vg7ahyk9LCl9xk(K)Q#r;kgYb;CZ0+k&nfzVF$?WB?TPjk{z&qPFoMoRW&t+^Au6>l5!ucse# zHeR_hD~lTp`%b2zS~!}X4U@nD$QW5Cf@IVIeF8WK@5-m#l&@v447I@d5V8DOo98-u zuIu$dS(yqx(3B;;PY{1b%!1Q7!OD=%_`=*mIpO%@~U=3!Sh4Wo4v=o#q8AVvg9);~e?4 zKn&7b4}CEv^c)lCKy08@PC9GE91_RQV!wr0VHw_NGQb5`y-wlAOI@d*fq8T>PK`@| zdT+l<0So7t>p1m&x$-?0`e)!aCL71+X(iU03iYzeH5Q8X4mu}`KD$Qeho5cL2q%ew zQ}XMuiB9e>M_Yw=#Mbjw;uipfNOucJc>-sqBgvhd+S^;S=W?I{PN2ASn2xfQ#$el` zcKUJjw}Dd9H0;*-E{M*#DSbJdFxFg&@Ye6(9k^bZv_csxUvs3^z!JAOXfoVT=h$nz z?MzVuUuX-tY64ls>UM;lPj@G-?(#dk{U3uof^I6by-Bj70#lS9#eF@g$O6&zMR1dRZ3*JSr%O2syp z1O^Uh5HL0`)cFZn=I7LjX$!PV0c~aQ%JjaKWU2NVPRgag=ip>suKxbBG1=^+v3b?1 zf_0S%(jHaRH-s!r1+4g+w}wiGONa@Um!i8GbYIKj=m6kZ?U!$Teit65O#Zki`YyT3 zQ~C0yKFE-#&yf0~Eh}5#NM;k*KRuZx0h29JoJtzm;gFpwXyZy2?bG%xEv^u2rxjDn2~9bl*7t7waI}$Y`NQ_T=sYg=g`QiHYf7Z4J9) zGN;0VEzDg?cb@Ok%(3i_&L`5I`C&#!h=Wz7-dj|Kjna6!!vk<#N8=Tq`Yu~1g=%hi z>jtvj)(N~|bTYrb=v6v=n=rlui2PNl>iw9ox|#)&?Y(+lKOf%pH+71NeuaVf%8av$ zMcE-*+%}*#nBP=7f>jW6EYP(tVE_G`4KXq4P|X7Th#S1ht26fF!6wGS&ezeKfFX=2 zlrOZ&?aSX}R9G9QgDb!r=|X!)ODD~Vs7`|S(wcz4E=b_^}K zlCHNvx_ggV2tZ)Txg@^g!1@(bC~`)Syy|0>+I}CQujlodQWN9(s}xJEx#t0>7dK*Z zdEr|QVv+CQ&Icw9NZ@7P;Agg?5ygTHv&_mId8FdAt?grIrU(162R%RC6w>^TfkY1C zrur;Mfr@ll%^();MA8ZqGE?XETzWM6JDM?&eD`CO4iD=~82B%tn~&@vBG?pDZL_4y zd`bsCAzc|!xHqr4GqXo>3U&$2Vq2?|X6@4RBejA_Oz{5I{QGv7^5As`YTpmG&9Sj3 zS<3i@)9Ey#9s_gty@fn4%L?A7>sUk0J)1>LF`i8shwo-SuRpYbs-Xy9M~R$DoI{#E z{STf<&|ig+3e@Q!S9wisnU)ItV+B9|@a2MdEs(7UVCuWoJ5}M)()UWje4R%*DDG1q z;JlE3!VV|x+uIB6VS5sKohy8!2#LO-^pxCmjcYsqC?57>{G(>_(xW+L>*>gw+qh=$@eXS~G8YPX+KAu<*5YDU5mMS*Gf?#5!}n;8>eG$jf)_AKAs)O- zDbfmKVIlyEf1rJ%rXnX#SCIYjQEy9)UK-ZFjiNJ`pxyo1biYeDk75s`Npa$!O8gXp#mMQ9_!@V@YK z=epVdVrZrV@pVxm zb7-cbNRoX-yuok_wyz~{5{4fJi{*z-Rq zsw6(GC_NVvK&(Gn+>np}NpMTHe;O!vlo;}pml%ZFM)ZkFgFYgBt4;DbXF|&n#OD$T z_wV-2f(?WaOtGK%5SHw${}CJe|0a+}GaXnANTiMMpO8@gJs|dLGnfVh;E@Z2Iz-(3 z*1Q1@H4F(TfdFl_QW9{m{ZIOg3~>8@k>L=34z>8Xw*FZ^Mk68w|3cyW@2;T2QGBR> zaPC29pWpq-gY*CAdA0v11|E=D14Z*|+b#Hiz&Kg|BR0}i;B`ByD7nINohtJ0KKjiO<9KKj|Ni|LXafaWXV40zycD1~~hFKl~XvAtWk*qzc5^O!=7@a;%5{EX{w1 T-lG1mynLpGxS4Lu{p0@w6ts!! delta 45357 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

9kU5modW@Pj@-hW_nHMh} z`i6i#N`rM}{D!3tqGgOi51$Wn^}*#O@?37F8W3&<3D3rPKFU<`1cq1kIkj6gD^I2W zOF2l5j?%4kX?P&vrT_(+y7L){cetpsGnpWMB%vAciB=Q8M)j_`Vh5736nIO%WZzT9K@N0mXrwL$Ow6HpaR%ZP}~f==#FqA5h}t zn*}SeuW~w?s6QK+g(|_N!{S(g(>}$3M<7wLe{zMktS#%0TJf*TK)VReMQaXPC_&El z!{{>4&H@D-!i>I$_;bj?O0<#{n3Mnse9k~J>p`bIa!h4jbEQQ&~!i1feBqHD9^DO+Ukn_yOo$VT%!#V7WrlGBz>*9-Ey$$af59y1&XR?22>EgL z+33LJ;D%3hJMjLto*i$=3)n9EBUCOQSIuQErVXCKUDrN2b2-BLHIcBm0Bwg0u=>q( ziv|;TmtstkRpc7|oshA{1w%9`i3Sit3H+nngbYSE_0WF_sV@blxu5+RYgzHvS~&FI z&_Sou0=G&VAN01dG$%vI77|5nRyPqMa_*R!!^Z7kRmsA+zY*3{1jV>S)2dbMXhfr6TUj(?BU9rxM=Cqpo&_vrJpm??jZN**&*k6m_5uob8}H|H|3W~S+YJ^ z$8h!)p3a~dj5^=*uXsp0Rc1gjNPxYWNwfHcug~;+vH>_U8;l=jzLKwXkVDt_S%j~a|#c^t^)`qTT@B|8+tTh zFhPBU{+nYs&MD5ZUK!16Smrwynect;YvH_Zb*-|{LzSF0ea15S;htfRtr(c**%mZ@ z@J&V!<*Kk(I*y3aI>l7ZXE35Gv!hoF~n8{aO^Da1s#>i7lk{8;@|K zZqbx!VqQOiy#Ta0m2F@-HVi7iJTmxaA7A8G6hRba*e*O#2456(xDY}_aA6lu^h#T@Y>t_bM7I5<2AmZ;%U&=4YmGLF5Eq$JTLk`K#?c-sDv zUkAz~A16YQA{?II6n|WBr}BqP^Y#@$wc|UOokMU)E(jrP`s_EldkGN79eKb?|HApq zf`lfRrqXfFra4l{$tgV3`5r$A>Ajo%@I zy3d6qU+*tyOuj+4V(wUAvB3*Nf*#p9boKNN42_r>nV4C=v$FlqH_N{R5mD7a2R_}c zA>s2%V8)r>sUhLv+uK>stp(ve;e8I$+nLbBeRiVfp*5jfJyP3g&-;D0@yp~0Z!AtN zBKcteHu`L*pfK?KVFWz<2Lpy@B~bf5yTkKPt+|67G7l2O63bXaEXBh5oe(4m%{vb> zsqYEm`FQN`dS{$Ld=%mbUO*q%S&5#Yw~vG+UT^Jtu-gWtEaQ*#C78*L|H{aJPIMoe zT0;q63JD?uH)3UFWn*QJ1x9cILXheIE%*O#Scbn@?*E?*8${|T|=y;#sh@?fB#<5=BB&RFlpPcU3X$`GJO=Gcx_Qm~R(#!1fDs0J9= zjdPfPl9^&@+el+Q>#9G+L4sz%IgE^qV)^T_V&&>lV=d}oV>|nCAV+v_onwEs6Or~1 zoPpii&5DKX68|EA@m>AN3R5#tICvf7J8|w&MZm z-!)zJ6Vy00Jb~lCLV#GnjhGnz9U1?xAvUe~YbyB%~WMM(dd9wGmR z;`m?I_y5BhqT>LmhOh$<;y+|r82%mq3;3VcSd;GmC5!d{NY;zBW}^H3KOEWq;kem{ z6?@SKi^A|Ot^{@6a{6iA*pRSF~ZsLc!5CSSb*L)nu{trV&R)&Aa|3dv2 zLzdyMu?!6;|A3?ZGm?aErT^2JP&BMHsl)H!Q2*+{*o-kUr2i`Y52x6_wV1H~S$Y?- xTVr&n3jbb!LI4I0>%Zn6(+4jC80LQ_9pV2@x(^ZEf5s^Gs-LiCZQTCv{{dRMZzcc$ diff --git a/src/Nethermind/Chains/unichain-sepolia.json.zst b/src/Nethermind/Chains/unichain-sepolia.json.zst index af355eb5bbac9c2d27146b4bad42e0fef556f028..7ff9d5fd16e38d1a801fdc82853ba7a348b72783 100644 GIT binary patch delta 29514 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 B5X}Gp diff --git a/src/Nethermind/Chains/worldchain-mainnet.json.zst b/src/Nethermind/Chains/worldchain-mainnet.json.zst index 2c243b391bbe9fbc6a347069c6e71d8c1c98cfa6..5f0e5ba592c96df2116610aa9a4a3a98079f8a1d 100644 GIT binary patch delta 12742 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 E2P)(DjQ{`u delta 11558 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 a{C|1u|2yHwn|SLs2M-K1cPlmoIRAfxMZ0?d diff --git a/src/Nethermind/Chains/worldchain-sepolia.json.zst b/src/Nethermind/Chains/worldchain-sepolia.json.zst index 830f302c5d8828376eb952a2c4899a2e9d5d8b57..317fe21409075385c7ee07da31cbf48c7624f6a2 100644 GIT binary patch delta 12718 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%0DZt%Bp4 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 delta 11583 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 B;uHV? diff --git a/src/Nethermind/Chains/zora-mainnet.json.zst b/src/Nethermind/Chains/zora-mainnet.json.zst index 1d93b533b0b7f9e587dc72a235ffa7aadd87e43b..b71d1ecb1f9a3a5abccd1b84d404513652d7f69a 100644 GIT binary patch delta 13925 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 delta 12935 zcmb_i1z1#Tx1OPUXrxBEJBDyT8tE=+hLjovfgv19N_qh46c7wTQb8%DQ%M0qS`j3q z(L3Nd_doYPf1Urbc%HTAdG?<7Tl-tolbz8iC9#u zLOKR(U!PM?0`SwYMv zEYLlk=-Z0JV&kI`+6tjeYzn?%oOp;lthXYhqDg=V3{n+4u>t@V$z*g$XiaM*$r=s$ zU~kJ=iXa=;8O^g&5fqgLGtF%#27#rIBrj|op5-7Th2A}(V(*&}J+|`lXzq!TO(?8v zSpGUtT4pb9lHD;#5FDIe1&gTdC}Mt0h;MC?Qer9v6AkQos2X57aYxf{D_z3Pyw>EY ziau^3wfb|6Fke!$iZmV0h z+hP{sW5Y6uHFzJv*C(Li^qx&#cInxbS#0PJwYjejAWl^vw!B>OSp zgBRpZZK)F7NQ8sYvD zsa7;#3o5;tv|VL098#q#<_zab$d*=eNsE1Z%akNmq=F>&c8Fp)kc1dpQjBzdSl94# zfq%xBrsm9;w=Sh((Hk*bW%;>cMManhu5vX&X;`Qn&lLtrLL36pDw{&4Smp4xg4UnI zRZRCn3xS2H*eYzD!U6)%*q?TmPiMWDMLs9)QA$d98}ICPKL+1@z&ArQg_e#@#AsDI zI>^vk>%^4SQ-7I!sKPYSr8MB%;m=naO+DI^J> z=i^A+LOAkdw>T7Lw)&WO4T_=N$U7nd^hXbfE$gMZG{H*lb=^Plt$CXs9MHdAj?5z2 zIX+8>7s$V7gZx-)1mORV zNJFJwB(97rxNUFxG;Whu#P1R%7xixil8a_k1 zo>$Z$de-rU!7=3Q*sPb=wyf41+pNCu0y75|eo7Xv!XuPwe_3_k06b+W5ck1HQM-?g zR9BgQ>MW-vHBu)ZZooBH`v-eWHMl)TS#k_N)}h~ zI0bykiGOs|FJtA>l-h6L+#6T{Xnt;&YgBeP1he57@kT1Dz3?!oFbwFGo-|)NMf`W&{sw z{V_YZn|+b}j~+?>oXAgEfQ4BgXV|kcy}!<-2E|t(02m>1R?P2YJ!2LYRFyvX91A73 zgf}-$9uzS$+>_#X&5Vl^_RWRD{KlY-oahtPZG!dAleQLZ8TIbBSPUEMn+ zQcnZ9=P0BBPLyjd<(qsfk5q^yi|H1e=u#Q?z7@%^`uLfzs30vNrKPs@{3y9t@ot)x zFHO2G<+{F7$IWEW5Qivr5@%(T({rJKVUa3&Jqgii#?&Eo+*R^%-Sx?ne6|AUW50rU z3Av1b(!+x~3Tr%ZN(r5Lf9Td*kZqCk*f<>dChL%6E4PqB&Su4d>ibwsld_z>KtcCp zAdT{ASz0K)@K#NbJK5uouG{iII-C%n=)g6$nGokGq zlq)ymHi6B_EZ`1*L7#WCd630qApUTvv6!to*6iL-{6=SgE9a+7hX~XB!(P5`1G{h# zaybp}Sc%8ln+)gB^Wu8Bnf1EA$(qh#F=!~ASZLjf>iT`7rIKA>p!_)eM78?|vlrfz+pn6Gp#wnFM%maDvpbY*)_h-=A5G zez)n@iGKj^M?bcbdcxrS`y=qgMeUbeWSotEgZk|eulBQqYYSji$^V0NzM5C3%fYoz z5rL9_Ktldnt+C6iY<4l4;y0`Qu$tt;-L71CBJ1Cb`eiuxf#j#!6GpFgnZFtJo86o< zi;eHltKp;H?+-}mS5wV_zh2aC>>~T$EkOSMOdx~T^jU)B0ysg6N9Ehm%da*Qv%Zb2 z=!emkZ8a*I;X#=Pl>{4&4#`If1`oxw@cSN*jj3Yll`dYr?sfl}>d4#S?M{*Rl|3H? z+@dQLjUkL*gD8$po?ESh4K_I}P7~V;Rl5WNs^@9w$40*(fF>S&(Mp0y_AnXUPkDGT z^Q?aKeWA{`4v&p;73wuJF7a!b@Lq2I#xb?>69@#esUcw-M7MN{g(rQ`JfNZcgrOCbUxT8blI{h?M z8efaJEjS>u*52LlF%MNK-7E0Zb`^JBkP(=cqJF1298m;VC%!jNJBA#C)aN3)!^03E zp%YHQ!Efic_v7@jXK+ljKBW6JQ}>Z((#JM3&v{raP<3A4r(UXu!JBz`jr~gr84S~q z8+|7q(|p>+dQLK_6(+xR4BomvS^P>j_h^3;o<-T3rbyG2v$GwNgaX>eHZ%1JP%0oSZGTKoPW5&rW*mo<< zAw_10?Ny7Z-G;-4<|6@vLz^#0gZukh3M#4wjG;vj+~@Q%c3w>(!H0F*^+T`Ra?6N4 zhpr+U?m5K!y_4A5Ecb0d*fRnKuFE-m$b!$AYt zmfWOuBP}AyjY1rX%1i|i=V*3Rz(B_U!g&jk5%{P9+AITVSq+`rg~Pym8BqcK9r1|a zi;oSf@>>YxFsEY)_2C=;K*{{j5tyL~YxMtKI3n+fpkKv)`OGDU_YQCei9Ia(`|Be`m^LqgyY8FvY91xS*-`vbE z)YhjDBaq9CB`N6+>rmzn2YsP)hq8as^&_(PFhDO-qZyy1bs6ZEf;3O3R2v20;ywUm z03HCGT)f+_s)gH0Tcxj?(k|0tlM)cL3bUl#h|S1*vvNamb$6*}AX+Zb)INxuL;z9< zf(*9?=g0&(RK9I}O(Xo6u9VQ0siE9gOMQ8843|bsybopMtX}FISJ)x@ERE^uox<+~ zGJIG_5|w!s$dk?ZrOaCi1AHS4x(V?ZsT5-NJy4mEJY3xRELoZ7LI$(1 z#N*w_aPx7QRZ>5g z==r-KdT}#mbBRq^zo9}7M?6sC{fyG+$l>pdk#J>_-0+ib0`^4(F1!i;97ar5Nv7!fYB8B>u;33R z?ko)@(YcmYO||(@oi8g(w;<$wFHCFs-DljL73gAcn$qw%P-b zg80*P&C+0;ho4>e54j8I>{pR^tdx>iPFESG{ql*;DfV$+QWyZ`UQYCTJjYBu`dZqn zNOAIr6DjZ{B+Ze?s#j?GEj4tAeDX+1#nYNtY|vk$+b!!$z2LHI@8jCr})yxx~Cd%Mi7WcN1&xS%cEh~cXvBUZtx za>&@M$`ZB6z*Qf@JPLEo%CHnB(Lv`Ny^s11u*u#Q4HMbg=V(So2 zfG2nba*+B7wO*y)u5a$g;E?Kqeem9ot=aoIm`5&W(lb~E1Zuy*AI26n# z?Zd!}&A>MLghizz(dE8T)kYU@Z;agbj^Ta0Fe{Ok=%QxLrOvUj>4_RoiAw6@-R1hQ zC3jIO<6HXt4vTXoB7t5V>9*L1Ihscg_R>0i8dk1Y3WnQPg&?DzHwkw3?tlto;V8h5 zr%0}~Tc67?O~2;;G*eo7Qm#oh*r1?dzUx(NJ$5TI)we%pJa%KqKmIW}P`rjNN*wcU z>N`o@6hh##Lb;K8eyHE!UB!+8M-l0umg|jDpR;f{j)KYo5S--5>uDSg&09YV_W5&4 zA5slBtln@7i=RgB69(fVTPa{C_h><>H{M6`dj7n@cw?$=%&a$U+WE^~s`dj2km)66 z`r(?jSUFz5b^0?7lWw74MPpeS@yx}U$kjH=EOo~9YwrttoyL9LMrAvu!K7Jz9px`3 ztMqW>t)u?L|Hma5d|RTfDZiwdTyVWB2e*g zjjf}Jk?M&)q&ggI$w0Dx*jV|~h^Ox18y8f*fs$T>k#D%pl2u;p@hF7ebnKz#n#LDT z;=->*kw3WWS5FUHB$6j;1Yawf_Om1HIHz>YvN3iI^basLN2>E~D==Dkj9UtdxjZ+p z{49Alb#?1XLxOLTJU3R5%1f*JVafAVSG|XZ;zOSBd`8X}t90dXNu}&`8ecO^ZfWDA zL-!Tfk>5U*8@^!T&)$FZ$yPl2rOww^RTOgzM%D$w+G{0AWyhfeuEEDk$vn@K-z1U9 z`7$~tQ#7-Mun3${7K&h1W|cu;O}Ltje4ZS<;7!ZOaa}#4s_p|eg`f+s zPCd;^G(zFJ1Npn))-gYup>-*|Eor>FS2zXhuE|DlgufzvKVtlWl>_{J{CMMzySXrYet!rsmZdHJIj_++D*;1 zG5KwGoZU55_Kj5QWKvonk4xF-b&W6djDB$cR3vVEO_Z=V_KlSmx2GkoYG$;Pnxn+s zN+lSDc(=h^vkgiF%eedVpLKU!!4@T~y@I`yF~K)e<;rh-gk$#lE!m`)rD$rsK24P@ zOx)ziH0Ow}cr>?13pm>;0Xv(~ex>LBHkE3{2f&|-X{+03e}%l^&=9#JaHn9R*k$%* z%?dJt?5B6`k1(DaYBfKaB(|o`L?-2;TK5MmidZIQ7V;Bl*@?KVC_8fc&1!pt2&5eN zd&}{h-$c=lc6PhrOeH_d4$^N>889uKf(Zv>(zS;-&q?|3Xy@FSajdqDNp9Vwdehd# z$IM}WPwrn_vO@zgn3SrD5e(>cww>$BD zdzc#c%Za^8ft)uzkO3`$?~zAK8Hnsb#pHP3T0XPKTg82`MVuS>M&Fj5S-J^E6r^9= zrex&A7R#yQmDFZB27=`EBMUZ-w~3RmujBODpOG@D@qv=`@eNZrT%Syma4=7&Dh(MT z??mm#Rg+uGl$gpCDYK~tYkniH2-YRhr5hlE&3D#p=N(b17SA`aY+vt{-mQ^Ihu9aBZBZ2gvpmZO zN^sDK*mIGm2}M%0g4aICNl}E6GmlKNBE5)+JiJrmHknS%)7XSl_R7o4##7dxjOc~m zB;Q*u>yl1+sH*ZhOL8P$?h|Vr69;YMp|5?H1bs+P^o;0ef|4;k0B9L;xA+@Nj7ve+ ziA{2h5(l$|Y5D`Uq7h67(;i|52}{w3cB_d?giPag-~GlJu}gDAg+3QwOIEd8(npFX z+#_0tSm>vJ9K&Qfc_!3EupP}y;kl~-xJQ{+xLmDrqrtUt)Li+IEdPGz>H1YGymY!c z{VTrAojnI?>PdJhiHC0h3o83%|+cFxw4jcqMt&{}u z4-o-edZ8h7EBL8Gcs4W&&zZ-Zh;*r7gx#GQSx#wRub@_nn7%IViNvu!tPm1alzNFV zmD<(K%3p_LvB$;q0A828rJ9kF7BX+5pi#itGq;-`PcFt%Zx>CSD2|e%Q4)UVD z=iz$f;|2_11`LFI4s^7v0Ff;#q2{~j38%_x_gj2Y$<=rK&Da1e3_CILzY_=qA_Wl> zhe$|D+1kMbH@0!euai$>%8h%1JqE(_b_))Ejar3syQDMZi;4|3fR=sFy(z=j?+|_rGsxww`!+qRsW}S4>2p z`_P%ZsJUI;EkvUXD6l^qybJGuZis`>^PTzbuq*hpIH+Nza02??73}dTer}qd0Sj$9 z#NNSC;_pOKQd0DH+nG1Ezx>2;atEU~A%A4=%nA^#*9I^f2QblkI2#w9jf>U= z5}iJY5SQz-M$-9_^}K zG;Rx`aX`@%ytlFd0kt58v!gyh_g4PXo5`kUzz7r>3+p*(*hv0g`tmY>i(*kvu zg~Oc>?Ht-TEP}4X5uBajj-7(2<%P(dVc2c~=PvvuVyzl6j6yWdLiZ-2Ydz5B-#^TF zfCJpXd-h-t36Kl~6d(d>$c_SMiwI(;_N71K8fzxqi z_&^eTAhZrJ1$&r+(R!~6I#UHjYcxco?H_>dTMff7b1>1-=_n#1g3=RZFkwP(U)||0 zJUeO*x;D^(4#!=zryCG!1L1SfnUz8bP{3cg8H+p*0*DyuD=(woRWx?T@a6cs(EHrq zA#4}^9(i{;plu50pkIH1ejL4=AnId1#tcH;WM|buzy8;;%K;^1Zs|mIvGbi{{uO`x zD^Hrt{|G|Ka9r3)ez5xAu<%br8khI1MOAP}+A;u`Myvl6fWHyx-$pVQNCE8vkyv^QwY6^z{ojSe&gYPPQS{Yj;B3}l|A8B zs3>z(loblMg#*;U0Yd8lEwG0cI6&NKodzQa)x*hZgx+)L`^)hoJ+vk0oFL-js96aH z({qNXPh1Z4HNut)YKuT15-3ec2IF&T+g=*A8&WT*Erxwd_I zwTGJHx$eRMD1)OAYqL;vxoH$ftcfFrU3cKFJ7`@nv9vJ0R5-CD|L-GN3%r+w?j@jT zjfVIQ=tIRO4aP;h2whYHM*Z_}d9{BzX<$WOwFJHW>%12}?H{?EbWH|3_o*@81?cqf z<oKE^n&0Ydy2xpja1azFz~TXImj{Oo#YM-g8duO;%45hxOY zi#?^kBt|peJp(qQ0tKYd37Y%ee_saB4Gq^n?W5w0OYcJ5P^5x_=v5>}|En^f)Bo?f zDO<0-=fEITmLRJkdfSyRshr>Ey(j}B=T(5wc}X1q;&IOL*TNS;o_a|f z=OsSIgrfLFc+lZjH2!~z0lL50Dd<{R;Mg60iT*d=?%%R_4|LB1j2;?h?7;!(*4hp1 zAp&Ya&j%2YL}t|7KsY!%D!X5CYehbBphIh=@Swh^3kM4uRV^ZG&j65W_`@x~)&Jit z@b|5leN8@b5K2q*B4u@5Qak7-=NW-|A$qazW0y4fh3cPI0Zy?C(8Wud{4^BL6LzZD z#oKRRQnS}5)DnaOieF%&e_V7aBWmBzA2}!w@rxoreQCS~S<^?Jn;`=etF^TN+IWNfgk#ZAiL-z zBy@dftVW=ZkkDQJKMe3c55bi;aem(&VgV`@o7jX>GY&oM*(>M7_yrupvh<2m%vj=ao8XkWb~qBo<7j(YYB SlmC(k4zxT2KBtp~JN*x5p)Jz@ diff --git a/src/Nethermind/Chains/zora-sepolia.json.zst b/src/Nethermind/Chains/zora-sepolia.json.zst index 763641aa82e85fa24a249bfe9205b65f519e757d..06ec4c0b54493c07a12b36a06c10f4587b95f15d 100644 GIT binary patch delta 10204 zcmb_g2UJsAo27RM9VrP!=_0)wPy{Ig66q+?K}A3iP!g4hL4tyS+|WDHq$naHC{cPB z0wP6<^h8h*h*+@9O?+?Wf3s%hf3sc|Yn^q;z4yE4+uz>%JLjwxF*Q{&VH6*!{a|*5 z5c&AbhggZ#s2V`r-moA}{mWABHfHS&B zcS(-hq=kFz-A$@}@=n}%Kt;{Qsjr8yEqz{PWm@2*2+B!rcE;(;^Cs7RB^%Aem2Zt! zE}AfQGWF4d{VS%8N5@uxi(&bcNl5In-I^2 ztFw%jIe&OVOiEDbm)Eb4m~zEQWOA35{H|r_mt@5o+THDviCjrt2foL~Ik@Tzf6iHr z(^3sZBE5^4u3NDBz8CtOVA^IfpG(FLhu-9LY`PoQX3~1sC-B*Plzv~hxF5!n^3C7l zbQD}fMG+|_yux{%-Ly?(UP;+eU0EF_#I3zcNSP^=3&)noj%obbg}JjJ9XiLu+1+6g zIwzdOsT{S~%EpK;%Nf6PMkuj-hOQk! z7D^lnP#Nf7d&1e}$%c5P}j z-+pjgLk#9XVAVGv^+ttiGzLrnS}VN*9l~t1xFAv zuBTg$Kf`${beh*jmZ_(I`@2MrQqdl{7hYzI{72 zE=2K(dVKh-_c77(ow1@hz41d2PtRjb4O4vhwu+KIl$xA2m&J${)m%Wnzr(TFV6-Y2 zmUz%nGf0E1DKo~xZmAx^$>E-PnuxQwdOC_#NM@peg@cbtlvQQA6&~t!jfEsEJ8}Jv zk0Q5F)FC5@@`KERqbwq-QYo#bEMoFfEDB7VyZA&T4oY2Z>G1g&f7Rj;3yJG-Z;C&U z3s=b^<8DpfF70NF2A{XY&-qq34E9~xW^s?URH8)~I};*Gimj8mGo$N7?c5%}IsU;I z28a|)RLV1E<7$VV#8K~CoK z!0i~UHc#k><@Qq%m#?!uoF@E;NNra+(Eywju@&^qt$3k{$=+MM0h8_OsO+n*BvBgi z{#)Ie0yH%XO!ICpHmO39Tpm1U(7E#$M=r?HRKb~ru* zEzdT*OYqG_TjR9;SN2j>AJgAg-T8d~lo}9tdGEm4zSmzPlw;4^rMm|^>=|F?FEE`B zxRtI`Z2-GaA3m%4 zf~U1RLSFW;=UCpi$(g1DTTU~h#*ZW&9O5oF=8ZN_JHj=-mo*Os6z4`g3AP6B^NpHc^ zSh_dOMh!aRhB<=IAAH)G;(B-4-mbMS)$%Rc%87CI#oCqhejRH`k5udLzg|?{j`}Qd zeo$faUV4DU_)&-B9sTdzCh?|ERPWxn5p~Mh$&u8o)RFUe7ses5Ch@FLFVf-68SCI1 zm30T~@{Ak0T<;c@D!5qQHCljI;wJeuJJ95Ksf(gMhukGG1&I#_$HvFCU)>yb5ud~h zJHS%kS2y6Kl%@l?g(M9f*6hBJdn@2(iH3RAJ{je?Pmcul%kDdlOSqeR&3%+ZQ!;RO{BXaCw??ft%0Q}#u=LV>4U-AA{x zt7j`!be0y(O)+Ms9Ssfj=H|ubPr8a#ZC{o8_4>p*D`yDzi|@9A_1X@f=eU&9`Z9~9 zH&-Y9{NUXKC)|Q_-EQHm9zS!ejKW2Z@0>duSfgD7^$9XRn^So@t~2u_RHrPZ^3J{L z>c0J>_fq}i{Q3NyUOmuoh`(7^pCT@G;j>IN;HN3rIJ;hkQD=1J?ROL~_GsV#5EnQYccv?1WM7g{VS{9=nY44?&l4>#F?udI+M-retO=vUG+|KR9)x5RwJqvLdq<}3PN-_sd@FyXyjzVCI~pKU(3bh(m7O9wh z)DCHWnMkNfTTm$7_<6W0=iB|yj~RX~yw|v>LH4?JGpk+*b$Bw`)I0cFpnX<)zDr>F z@TuJWksG+-=} z*aQ1e!h(@{Ha&PXWvra;C)sHMGQKTvuFLi*%k^t{ZC{y`K4%-tMJEc1gp#n9@^>5M zl_Gu~`ss^l`r$fzX%ri63f(gQta2Z%(^rsjh^$&BS%a++YpJcr_wR#VvU2mPZMvEA znLJu~rRVww*GU)85W~1VPmZaT9lT?*S5*YH#Jdxh`YxAw<-%oI6d%genCsiZb7kXa z8ZY`LJ2S?5@)ec*?25-i;-q~W%NX;7Lo6$6iafBQm@YEch3&MK&Yp|dJ9mOSO%;B? zrb_(%)Rz6*8M6!q%6vQ5*h3uWP~4XzR}K2qu>G$K;Cxf5oNw}yY@D4ae4(+il8Ja{ zgMxj>s}<8uB=r>!Wt-LhY%969G~pz4g!M+d{qx{kg^1c`=O3V>hY@|DhkSXpU5DSC zh?B3t2VoY{7Oul&U7}CtnP-hy#|B2W1wXZ&Y02j9EPzfnLzk2`;$2LVu9vweyE-|ac4 zyi0BmPVBO_nn&W3#vDE6%FlAEE4b^n7p{5ErL;fOpKd$-UC@fB-Swd$kzqaVMvIsd z8%|p7@MEvs%O}4;zfL%DjdWbfGDrT14moa}_#-@A#X7WS{)f2BEk5Q;rz!-Bs?`jH zC_yWjcuUX8wOHj}mGPOznRtAa@QIKvVdu@gN4_Q@Awk<-6c*p;dpgn#;eFpp7EGmI zTBng{a#8O#aA{BDr*}Iam?V3?r?$#|J104K3s;6DPyK&}JEd=sdP>RVK(;YqG%U|zD`0L0;=wzxljlOptGUWM=D5vp&XE(_z#uxXhu2hV z=u|j>vAp2+^%h1XllABXa_8C*EU{hhMc@tn2Y{ulISGo}IQg`ACM|#Y`s;aeP4D;O6Z9)ebm$uc@-(8-&Rdf7( zZq5DRSIVI)r`m%XEcs>T#A0f;6<)SI5wagM<@WOsl)`zOXJGjbeVA0a)|~0Z15hd zzp167nc{-Kr#}4we&gQJtC)CE(HI4*8|+(Ku9zpqNe5X2RBf8;ZpWq{6#Qy#&?RE2 z=J^w%=TUzzX+{CljDK{oG3A}`^Ig@azuM1+y(ae<9y>j08txlR5zt>lQg)4{HoUb$ zr${rgT0D83Q7}-zD+_#g^Lr#f+7mV<=RV6_`783Y&kRrUtI52l`3}vkrUzmUOH_T= zx|RW86!oUcRH2zd_(&JKoJ^}aqDp({cdLakE?&kKa#%Rf#vFlH&WkTm zqHFzS6qfSR-MK|89myQ7#mLxgmAM%oH}@l%XqcuDbEsDugNv12lc+M(d#C(4D`muU zW^%n$D)((8$CUcu;6A=|)vN0kTkxK*e%ASr&!}CVc~MA74NJ0Bv<` z4Q)+rEo~iLJ@7ZcmPgq2&F?voiZYgpK5r8DZW5~y6hjE%1$ACIDDo8;Itw?uQZ$Yt zN5arKIFFU0&nrb@v?td#i8-`~X-WK^?V#y>^xa=kvcO{=O(${|f_xZ8IS!+wuoG5H zl2ODgKpog%tTZNwyL(w?9_oX<`mnXLc$Ub8von-qZH z)o>y|LXhPk=s_^$LK5Q7>S zGI%*i`xW^X&Vc})^QyWNpCTx_RH*N75!e0;3h>eghR(yyf=KC8W{*9y%0*DjVQ6a@ z8o+sw{u3YYJ_r?9AVbKd0xDV=DDpCjoVN6%H$@w;;L~vZBO_lJC6nD~g_>MAjFQbx z7^2PzOduRZOhAxp5M(%Yp0j`E2O#i4kn}rA;?1rskqs6oW~N#E^_c6AW!m{yQyQp0 zQD}bvB?Ly9K#+S8kADU%*l# z81M=m1NKw3yDp;WLWqE&NktdwGPo4~pq0tn#|Q9||6Qhp&!m4YVwx4$@qtPP#zhs5 z(oep1tB^$Ch?w5VLfovmm7F?BV7m@ba_1pItWJb zgZ&N0yITDc(FUf)_WAsGG5kU87ubBTS-!x+a@dWs*a_hHH^1?RZ1!j8^V1wiQxou1 zlyapyQ2RK&9LNX?@`Razg~=b7dKiDb+5unfD<^$TjwYb93X}YQTCRNSP zz>sg~7Lplsc|b!#2}v(w{|f3E^fPUsMi6?!k6JUqF?!XE@QKV?O|6-pzds{As%Q&o{l8`WAWIehw&nl!b+xlMqv0yRYiSj5 zWA@io&_6YPg%w(-`evETiwC@96ddTXjH4|EeLg^F&JhL7^Fxp8((Hb5n_dkZ?BZoy z)QScGNO7p6KDDG9bX&!#u~B{h37l>^A^fDSzW42>TR@{PR3#VCF<=-~$pU+noam|K zyT0rtEDRJTO(12D6i~Q_1*qO5=r!Ggpv)pDb>NWzicCO}{kMpFw}_X(6P}eK7uwlM z2DlFgyG$O=Lj^)`g`ppSClBC>!X`0cxk#+vR1Ms}|Ias;2+_KDKuVUL9ovPf?_^o} zb?l;Hn#KDA{323rdNhv@d_^yhCs={Z0EM9rL_?u&22^20(NEP}>%Ud44s1YSPCnG- zKr%f=eQyE}xXUXXrJt15sZt)2r&gOS z3VZ2q_u%fCWCA%`+|5=G99N|8X^SHDw1o%;rPCfwjp>im7T}n;l$$=y;=j>t5qCf4 z_aaUMcn*g;dQ)Y>F+w*DegYK^C@Ry>GQxk0S{t}8DCz9yPg}ys(ofcP>ktIMvsdNh Xzd3dJ3s3)l8_iNl-G{(l7ythN!2(As delta 8944 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 Context.Resolve(); - public IJsonSerializer EthereumJsonSerializer => _dependencies.JsonSerializer; + public EthereumJsonSerializer EthereumJsonSerializer => _dependencies.JsonSerializer; public IKeyStore? KeyStore { get; set; } public ILogManager LogManager => _dependencies.LogManager; public IMessageSerializationService MessageSerializationService => Context.Resolve(); diff --git a/src/Nethermind/Nethermind.Benchmark/Core/FastHashBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Core/FastHashBenchmarks.cs new file mode 100644 index 000000000000..a6481332d087 --- /dev/null +++ b/src/Nethermind/Nethermind.Benchmark/Core/FastHashBenchmarks.cs @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Runtime.InteropServices; +using BenchmarkDotNet.Attributes; +using Nethermind.Core.Extensions; + +namespace Nethermind.Benchmarks.Core; + +[ShortRunJob] +[DisassemblyDiagnoser] +[MemoryDiagnoser] +public class FastHashBenchmarks +{ + private byte[] _data = null!; + + [Params(16, 20, 32, 64, 128, 256, 512, 1024)] + public int Size; + + [GlobalSetup] + public void Setup() + { + _data = new byte[Size]; + Random.Shared.NextBytes(_data); + } + + [Benchmark(Baseline = true)] + public int FastHash() + { + return ((ReadOnlySpan)_data).FastHash(); + } + + [Benchmark] + public int FastHashAes() + { + ref byte start = ref MemoryMarshal.GetReference(_data); + return SpanExtensions.FastHashAesX64(ref start, _data.Length, SpanExtensions.ComputeSeed(_data.Length)); + } + + [Benchmark] + public int FastHashCrc() + { + ref byte start = ref MemoryMarshal.GetReference(_data); + return SpanExtensions.FastHashCrc(ref start, _data.Length, SpanExtensions.ComputeSeed(_data.Length)); + } +} diff --git a/src/Nethermind/Nethermind.Benchmark/Core/SeqlockCacheBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Core/SeqlockCacheBenchmarks.cs new file mode 100644 index 000000000000..9ba71e47e39e --- /dev/null +++ b/src/Nethermind/Nethermind.Benchmark/Core/SeqlockCacheBenchmarks.cs @@ -0,0 +1,345 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +#nullable enable + +using System; +using System.Collections.Concurrent; +using BenchmarkDotNet.Attributes; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Int256; + +namespace Nethermind.Benchmarks.Core; + +[MemoryDiagnoser] +[DisassemblyDiagnoser(maxDepth: 3)] +public class SeqlockCacheBenchmarks +{ + private SeqlockCache _seqlockCache = null!; + private ConcurrentDictionary _concurrentDict = null!; + + private StorageCell[] _keys = null!; + private byte[][] _values = null!; + private StorageCell _missKey; + + [Params(1000)] + public int KeyCount { get; set; } + + [GlobalSetup] + public void Setup() + { + _seqlockCache = new SeqlockCache(); + _concurrentDict = new ConcurrentDictionary(); + + _keys = new StorageCell[KeyCount]; + _values = new byte[KeyCount][]; + + var random = new Random(42); + for (int i = 0; i < KeyCount; i++) + { + var addressBytes = new byte[20]; + random.NextBytes(addressBytes); + var address = new Address(addressBytes); + var index = new UInt256((ulong)i); + + _keys[i] = new StorageCell(address, index); + _values[i] = new byte[32]; + random.NextBytes(_values[i]); + + // Pre-populate both caches + _seqlockCache.Set(in _keys[i], _values[i]); + _concurrentDict[_keys[i]] = _values[i]; + } + + // Create a key that won't be in the cache + var missAddressBytes = new byte[20]; + random.NextBytes(missAddressBytes); + _missKey = new StorageCell(new Address(missAddressBytes), UInt256.MaxValue); + } + + // ==================== TryGetValue (Hit) ==================== + + [Benchmark(Baseline = true)] + public bool SeqlockCache_TryGetValue_Hit() + { + return _seqlockCache.TryGetValue(in _keys[500], out _); + } + + [Benchmark] + public bool ConcurrentDict_TryGetValue_Hit() + { + return _concurrentDict.TryGetValue(_keys[500], out _); + } + + // ==================== TryGetValue (Miss) ==================== + + [Benchmark] + public bool SeqlockCache_TryGetValue_Miss() + { + return _seqlockCache.TryGetValue(in _missKey, out _); + } + + [Benchmark] + public bool ConcurrentDict_TryGetValue_Miss() + { + return _concurrentDict.TryGetValue(_missKey, out _); + } + + // ==================== Set (Existing Key) ==================== + + [Benchmark] + public void SeqlockCache_Set_Existing() + { + _seqlockCache.Set(in _keys[500], _values[500]); + } + + [Benchmark] + public void ConcurrentDict_Set_Existing() + { + _concurrentDict[_keys[500]] = _values[500]; + } + + // ==================== GetOrAdd (Hit) ==================== + + [Benchmark] + public byte[]? SeqlockCache_GetOrAdd_Hit() + { + return _seqlockCache.GetOrAdd(in _keys[500], static (in StorageCell _) => new byte[32]); + } + + [Benchmark] + public byte[] ConcurrentDict_GetOrAdd_Hit() + { + return _concurrentDict.GetOrAdd(_keys[500], static _ => new byte[32]); + } + + // ==================== GetOrAdd (Miss - measures factory overhead) ==================== + + private int _missCounter; + + [Benchmark] + public byte[]? SeqlockCache_GetOrAdd_Miss() + { + // Use incrementing key to always miss + var key = new StorageCell(_keys[0].Address, new UInt256((ulong)(KeyCount + _missCounter++))); + return _seqlockCache.GetOrAdd(in key, static (in StorageCell _) => new byte[32]); + } + + [Benchmark] + public byte[] ConcurrentDict_GetOrAdd_Miss() + { + var key = new StorageCell(_keys[0].Address, new UInt256((ulong)(KeyCount + _missCounter++))); + return _concurrentDict.GetOrAdd(key, static _ => new byte[32]); + } +} + +///

+/// Benchmark comparing read-heavy workloads (90% reads, 10% writes) +/// +[MemoryDiagnoser] +public class SeqlockCacheMixedWorkloadBenchmarks +{ + private SeqlockCache _seqlockCache = null!; + private ConcurrentDictionary _concurrentDict = null!; + + private StorageCell[] _keys = null!; + private byte[][] _values = null!; + + private const int KeyCount = 10000; + private const int OperationsPerInvoke = 1000; + + [GlobalSetup] + public void Setup() + { + _seqlockCache = new SeqlockCache(); + _concurrentDict = new ConcurrentDictionary(); + + _keys = new StorageCell[KeyCount]; + _values = new byte[KeyCount][]; + + var random = new Random(42); + for (int i = 0; i < KeyCount; i++) + { + var addressBytes = new byte[20]; + random.NextBytes(addressBytes); + var address = new Address(addressBytes); + var index = new UInt256((ulong)i); + + _keys[i] = new StorageCell(address, index); + _values[i] = new byte[32]; + random.NextBytes(_values[i]); + + // Pre-populate both caches + _seqlockCache.Set(in _keys[i], _values[i]); + _concurrentDict[_keys[i]] = _values[i]; + } + } + + [Benchmark(Baseline = true, OperationsPerInvoke = OperationsPerInvoke)] + public int SeqlockCache_MixedWorkload_90Read_10Write() + { + int hits = 0; + for (int i = 0; i < OperationsPerInvoke; i++) + { + int keyIndex = i % KeyCount; + if (i % 10 == 0) + { + // 10% writes + _seqlockCache.Set(in _keys[keyIndex], _values[keyIndex]); + } + else + { + // 90% reads + if (_seqlockCache.TryGetValue(in _keys[keyIndex], out _)) + hits++; + } + } + return hits; + } + + [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] + public int ConcurrentDict_MixedWorkload_90Read_10Write() + { + int hits = 0; + for (int i = 0; i < OperationsPerInvoke; i++) + { + int keyIndex = i % KeyCount; + if (i % 10 == 0) + { + // 10% writes + _concurrentDict[_keys[keyIndex]] = _values[keyIndex]; + } + else + { + // 90% reads + if (_concurrentDict.TryGetValue(_keys[keyIndex], out _)) + hits++; + } + } + return hits; + } + + [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] + public int SeqlockCache_ReadOnly() + { + int hits = 0; + for (int i = 0; i < OperationsPerInvoke; i++) + { + int keyIndex = i % KeyCount; + if (_seqlockCache.TryGetValue(in _keys[keyIndex], out _)) + hits++; + } + return hits; + } + + [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] + public int ConcurrentDict_ReadOnly() + { + int hits = 0; + for (int i = 0; i < OperationsPerInvoke; i++) + { + int keyIndex = i % KeyCount; + if (_concurrentDict.TryGetValue(_keys[keyIndex], out _)) + hits++; + } + return hits; + } +} + +/// +/// Benchmark measuring effective hit rate after populating with N keys. +/// This directly measures the impact of collision rate. +/// +public class SeqlockCacheHitRateBenchmarks +{ + private SeqlockCache _seqlockCache = null!; + private StorageCell[] _keys = null!; + private byte[][] _values = null!; + + [Params(1000, 5000, 10000, 20000)] + public int KeyCount { get; set; } + + [GlobalSetup] + public void Setup() + { + _seqlockCache = new SeqlockCache(); + _keys = new StorageCell[KeyCount]; + _values = new byte[KeyCount][]; + + var random = new Random(42); + for (int i = 0; i < KeyCount; i++) + { + var addressBytes = new byte[20]; + random.NextBytes(addressBytes); + _keys[i] = new StorageCell(new Address(addressBytes), new UInt256((ulong)i)); + _values[i] = new byte[32]; + random.NextBytes(_values[i]); + _seqlockCache.Set(in _keys[i], _values[i]); + } + } + + [Benchmark] + public double MeasureHitRate() + { + int hits = 0; + for (int i = 0; i < KeyCount; i++) + { + if (_seqlockCache.TryGetValue(in _keys[i], out byte[]? val) && ReferenceEquals(val, _values[i])) + hits++; + } + return (double)hits / KeyCount * 100; + } +} + +[MemoryDiagnoser] +public class SeqlockCacheCallSiteBenchmarks +{ + private SeqlockCache _cache = null!; + private SeqlockCache.ValueFactory _cachedFactory = null!; + private StorageCell _key; + private byte[] _value = null!; + + [GlobalSetup] + public void Setup() + { + _cache = new SeqlockCache(); + + byte[] addressBytes = new byte[20]; + new Random(123).NextBytes(addressBytes); + _key = new StorageCell(new Address(addressBytes), UInt256.One); + _value = new byte[32]; + + _cache.Set(in _key, _value); + _cachedFactory = LoadFromBackingStore; + } + + [Benchmark(Baseline = true)] + public byte[]? GetOrAdd_Hit_PerCallMethodGroup() + { + return _cache.GetOrAdd(in _key, LoadFromBackingStore); + } + + [Benchmark] + public byte[]? GetOrAdd_Hit_CachedDelegate() + { + return _cache.GetOrAdd(in _key, _cachedFactory); + } + + [Benchmark] + public bool TryGetValue_WithIn() + { + return _cache.TryGetValue(in _key, out _); + } + + [Benchmark] + public bool TryGetValue_WithoutIn() + { + return _cache.TryGetValue(_key, out _); + } + + private byte[] LoadFromBackingStore(in StorageCell _) + { + return _value; + } +} diff --git a/src/Nethermind/Nethermind.Benchmark/Scheduler/BackgroundTaskSchedulerBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Scheduler/BackgroundTaskSchedulerBenchmarks.cs new file mode 100644 index 000000000000..5ab4d42ecf03 --- /dev/null +++ b/src/Nethermind/Nethermind.Benchmark/Scheduler/BackgroundTaskSchedulerBenchmarks.cs @@ -0,0 +1,181 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Scheduler; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.TxPool; + +namespace Nethermind.Benchmarks.Scheduler; + +/// +/// Benchmarks the throughput of the BackgroundTaskScheduler under concurrent task +/// scheduling with periodic block-processing pauses — the scenario that caused +/// the "Background task queue is full" issue on synced nodes. +/// +[MemoryDiagnoser] +[SimpleJob(warmupCount: 2, iterationCount: 5)] +public class BackgroundTaskSchedulerBenchmarks +{ + private StubBranchProcessor _branchProcessor = null!; + private StubChainHeadInfoProvider _chainHeadInfo = null!; + + [Params(1024, 2048)] + public int Capacity { get; set; } + + [Params(2)] + public int Concurrency { get; set; } + + [Params(50)] + public int BlockProcessingDurationMs { get; set; } + + [Params(5)] + public int BlockProcessingCycles { get; set; } + + [GlobalSetup] + public void Setup() + { + _branchProcessor = new StubBranchProcessor(); + _chainHeadInfo = new StubChainHeadInfoProvider(); + } + + /// + /// Simulates the real-world scenario: a background producer keeps scheduling tasks + /// while block-processing cycles pause and resume execution. Measures total wall-clock + /// time for scheduling + draining all tasks across several block-processing windows. + /// + [Benchmark] + public async Task ScheduleAndDrainDuringBlockProcessing() + { + await using BackgroundTaskScheduler scheduler = new( + _branchProcessor, _chainHeadInfo, Concurrency, Capacity, LimboLogs.Instance); + + int totalScheduled = 0; + int totalExecuted = 0; + int totalDropped = 0; + + for (int cycle = 0; cycle < BlockProcessingCycles; cycle++) + { + // Simulate block arriving — cancels current tasks, pauses non-expired ones + _branchProcessor.RaiseBlocksProcessing(); + + // Schedule a burst of tasks while block is being processed + int batchSize = Capacity / 2; + for (int i = 0; i < batchSize; i++) + { + bool accepted = scheduler.TryScheduleTask(i, (_, token) => + { + Interlocked.Increment(ref totalExecuted); + return Task.CompletedTask; + }, TimeSpan.FromMilliseconds(BlockProcessingDurationMs + 100)); + + if (accepted) + Interlocked.Increment(ref totalScheduled); + else + Interlocked.Increment(ref totalDropped); + } + + // Simulate block processing time + await Task.Delay(BlockProcessingDurationMs); + + // Block done — resume normal task execution + _branchProcessor.RaiseBlockProcessed(); + + // Wait for all scheduled tasks to drain before next cycle + SpinWait spin = default; + while (Volatile.Read(ref totalExecuted) < Volatile.Read(ref totalScheduled)) + { + spin.SpinOnce(); + if (spin.Count % 100 == 0) + await Task.Yield(); + } + } + } + + /// + /// Measures pure scheduling throughput without block-processing interruptions. + /// Useful as a baseline to compare against . + /// + [Benchmark(Baseline = true)] + public async Task ScheduleAndDrainWithoutBlockProcessing() + { + await using BackgroundTaskScheduler scheduler = new( + _branchProcessor, _chainHeadInfo, Concurrency, Capacity, LimboLogs.Instance); + + int totalScheduled = 0; + int totalExecuted = 0; + + int totalTasks = (Capacity / 2) * BlockProcessingCycles; + for (int i = 0; i < totalTasks; i++) + { + bool accepted = scheduler.TryScheduleTask(i, (_, _) => + { + Interlocked.Increment(ref totalExecuted); + return Task.CompletedTask; + }); + if (accepted) + Interlocked.Increment(ref totalScheduled); + } + + SpinWait spin = default; + while (Volatile.Read(ref totalExecuted) < Volatile.Read(ref totalScheduled)) + { + spin.SpinOnce(); + if (spin.Count % 100 == 0) + await Task.Yield(); + } + } + + /// + /// Minimal stub for to expose events without any real block processing. + /// + private sealed class StubBranchProcessor : IBranchProcessor + { + public event EventHandler? BlockProcessed; + public event EventHandler? BlocksProcessing; +#pragma warning disable CS0067 // Event is never used + public event EventHandler? BlockProcessing; +#pragma warning restore CS0067 + + public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlocks, + ProcessingOptions processingOptions, IBlockTracer blockTracer, CancellationToken token = default) + => []; + + public void RaiseBlocksProcessing() => + BlocksProcessing?.Invoke(this, new BlocksProcessingEventArgs([])); + + public void RaiseBlockProcessed() => + BlockProcessed?.Invoke(this, new BlockProcessedEventArgs(null!, null!)); + } + + /// + /// Minimal stub for — reports node as not syncing. + /// + private sealed class StubChainHeadInfoProvider : IChainHeadInfoProvider + { + public IChainHeadSpecProvider SpecProvider => null!; + public IReadOnlyStateProvider ReadOnlyStateProvider => null!; + public long HeadNumber => 0; + public long? BlockGasLimit => null; + public UInt256 CurrentBaseFee => UInt256.Zero; + public UInt256 CurrentFeePerBlobGas => UInt256.Zero; + public ProofVersion CurrentProofVersion => ProofVersion.V0; + public bool IsSyncing => false; + public bool IsProcessingBlock => false; +#pragma warning disable CS0067 // Event is never used + public event EventHandler? HeadChanged; +#pragma warning restore CS0067 + } +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs index 7288c0d85ac1..abbd0cb530af 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs @@ -17,6 +17,8 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class BeaconBlockRootHandlerTests { private BeaconBlockRootHandler _beaconBlockRootHandler; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockFinderExtensionsTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockFinderExtensionsTests.cs index 7665e94c284e..1ad1c053cbd4 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockFinderExtensionsTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockFinderExtensionsTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class BlockFinderExtensionsTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs index 0471540f3e5c..4e180459cf7d 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs @@ -36,6 +36,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class BlockProcessorTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeSuggestPacerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeSuggestPacerTests.cs index 34392783f02f..46a3ea978935 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeSuggestPacerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeSuggestPacerTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class BlockTreeSuggestPacerTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs index 3d327872c84f..d2f2a332753e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs @@ -32,6 +32,8 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class BlockTreeTests { private TestMemDb _blocksInfosDb = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs index 3f9fed9e8859..b9152d322bff 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs @@ -28,7 +28,8 @@ namespace Nethermind.Blockchain.Test; -[Parallelizable(ParallelScope.Self)] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class BlockchainProcessorTests { private class ProcessingTestContext @@ -195,7 +196,7 @@ public ProcessingTestContext(bool startProcessor) .TestObject; _branchProcessor = new BranchProcessorMock(_logManager, _stateReader); _recoveryStep = new RecoveryStepMock(_logManager); - _processor = new BlockchainProcessor(_blockTree, _branchProcessor, _recoveryStep, _stateReader, LimboLogs.Instance, BlockchainProcessor.Options.Default); + _processor = new BlockchainProcessor(_blockTree, _branchProcessor, _recoveryStep, _stateReader, LimboLogs.Instance, BlockchainProcessor.Options.Default, Substitute.For()); _resetEvent = new AutoResetEvent(false); _queueEmptyResetEvent = new AutoResetEvent(false); @@ -283,11 +284,30 @@ public AfterBlock ProcessedFail(Block block) public ProcessingTestContext Suggested(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) { - AddBlockResult result = _blockTree.SuggestBlock(block, options); - if (result != AddBlockResult.Added) + if ((options & BlockTreeSuggestOptions.ShouldProcess) != 0) { - _logger.Info($"Finished waiting for {block.ToString(Block.Format.Short)} as block was ignored"); - _resetEvent.Set(); + // Use Task.Run to avoid blocking when AllowSynchronousContinuations + // causes inline processing on the calling thread + Task.Run(() => + { + AddBlockResult result = _blockTree.SuggestBlock(block, options); + if (result != AddBlockResult.Added) + { + _logger.Info($"Finished waiting for {block.ToString(Block.Format.Short)} as block was ignored"); + _resetEvent.Set(); + } + }); + // Wait for block to be in the tree before returning + SpinWait.SpinUntil(() => _blockTree.IsKnownBlock(block.Number, block.Hash!), ProcessingWait); + } + else + { + AddBlockResult result = _blockTree.SuggestBlock(block, options); + if (result != AddBlockResult.Added) + { + _logger.Info($"Finished waiting for {block.ToString(Block.Format.Short)} as block was ignored"); + _resetEvent.Set(); + } } return this; @@ -328,8 +348,7 @@ public ProcessingTestContext Recovered(Block block) public ProcessingTestContext CountIs(int expectedCount) { - var count = ((IBlockProcessingQueue)_processor).Count; - Assert.That(expectedCount, Is.EqualTo(count)); + Assert.That(() => ((IBlockProcessingQueue)_processor).Count, Is.EqualTo(expectedCount).After(ProcessingWait, 10)); return this; } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs index 1f218bdddc2a..ec9997f9df50 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs @@ -438,6 +438,7 @@ public void BlockhashStore_uses_custom_ring_buffer_size() } [Test, MaxTime(Timeout.MaxTestTime)] + [NonParallelizable] public async Task Prefetches_come_in_wrong_order() { const int chainLength = 261; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BadBlockStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BadBlockStoreTests.cs index dd149c475ccc..f8fefc3679d0 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BadBlockStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BadBlockStoreTests.cs @@ -12,6 +12,7 @@ namespace Nethermind.Blockchain.Test.Blocks; +[Parallelizable(ParallelScope.All)] public class BadBlockStoreTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs index 46c3d9da71cf..861264efda96 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs @@ -15,6 +15,7 @@ namespace Nethermind.Blockchain.Test.Blocks; +[Parallelizable(ParallelScope.All)] public class BlockStoreTests { private readonly Func, EquivalencyAssertionOptions> _ignoreEncodedSize = options => options.Excluding(b => b.EncodedSize); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/HeaderStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/HeaderStoreTests.cs index bc1d967eb9ec..130bda2d4829 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/HeaderStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/HeaderStoreTests.cs @@ -11,9 +11,9 @@ namespace Nethermind.Blockchain.Test.Blocks; +[Parallelizable(ParallelScope.All)] public class HeaderStoreTests { - [Test] public void TestCanStoreAndGetHeader() { diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs b/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs index 86c7706fe3f5..4dbb1cc3e31a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs @@ -11,14 +11,15 @@ namespace Nethermind.Blockchain.Test.Builders { public class FilterBuilder { - private static int _id; + private int _id; private BlockParameter _fromBlock = new(BlockParameterType.Latest); private BlockParameter _toBlock = new(BlockParameterType.Latest); private AddressFilter _address = AddressFilter.AnyAddress; private SequenceTopicsFilter _topicsFilter = new(); - private FilterBuilder() + private FilterBuilder(int id) { + _id = id; } public static FilterBuilder New() @@ -29,9 +30,9 @@ public static FilterBuilder New() public static FilterBuilder New(ref int currentFilterIndex) { - _id = currentFilterIndex; + int id = currentFilterIndex; currentFilterIndex++; - return new FilterBuilder(); + return new FilterBuilder(id); } public FilterBuilder WithId(int id) diff --git a/src/Nethermind/Nethermind.Blockchain.Test/CachedCodeInfoRepositoryTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/CachedCodeInfoRepositoryTests.cs index 10280c27796c..b933980d7e6b 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/CachedCodeInfoRepositoryTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/CachedCodeInfoRepositoryTests.cs @@ -19,6 +19,7 @@ namespace Nethermind.Blockchain.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] public class CachedCodeInfoRepositoryTests { private static IReleaseSpec CreateSpecWithPrecompile(Address precompileAddress) @@ -35,7 +36,7 @@ public void Precompile_WithCachingEnabled_IsWrappedInCachedPrecompile() TestPrecompile cachingPrecompile = new(supportsCaching: true); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(cachingPrecompile) }.ToFrozenDictionary(); @@ -50,7 +51,7 @@ public void Precompile_WithCachingEnabled_IsWrappedInCachedPrecompile() // Act CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); // Assert codeInfo.Should().NotBeNull(); @@ -65,7 +66,7 @@ public void Precompile_WithCachingDisabled_IsNotWrapped() TestPrecompile nonCachingPrecompile = new(supportsCaching: false); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(nonCachingPrecompile) }.ToFrozenDictionary(); @@ -80,7 +81,7 @@ public void Precompile_WithCachingDisabled_IsNotWrapped() // Act CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); // Assert codeInfo.Should().NotBeNull(); @@ -91,7 +92,7 @@ public void Precompile_WithCachingDisabled_IsNotWrapped() public void IdentityPrecompile_IsNotWrapped_WhenCacheEnabled() { // Arrange - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [IdentityPrecompile.Address] = new(IdentityPrecompile.Instance) }.ToFrozenDictionary(); @@ -106,7 +107,7 @@ public void IdentityPrecompile_IsNotWrapped_WhenCacheEnabled() // Act CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); // Assert codeInfo.Should().NotBeNull(); @@ -121,7 +122,7 @@ public void CachedPrecompile_CachesResults_ForCachingEnabledPrecompile() TestPrecompile cachingPrecompile = new(supportsCaching: true, onRun: () => runCount++); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(cachingPrecompile) }.ToFrozenDictionary(); @@ -135,7 +136,7 @@ public void CachedPrecompile_CachesResults_ForCachingEnabledPrecompile() IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); byte[] input = [1, 2, 3]; @@ -156,7 +157,7 @@ public void NonCachingPrecompile_DoesNotCacheResults() TestPrecompile nonCachingPrecompile = new(supportsCaching: false, onRun: () => runCount++); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(nonCachingPrecompile) }.ToFrozenDictionary(); @@ -170,7 +171,7 @@ public void NonCachingPrecompile_DoesNotCacheResults() IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); byte[] input = [1, 2, 3]; @@ -190,7 +191,7 @@ public void NullCache_DoesNotWrapAnyPrecompiles() TestPrecompile cachingPrecompile = new(supportsCaching: true); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(cachingPrecompile) }.ToFrozenDictionary(); @@ -204,7 +205,7 @@ public void NullCache_DoesNotWrapAnyPrecompiles() // Act - pass null cache CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, null); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); // Assert - precompile should not be wrapped codeInfo.Should().NotBeNull(); @@ -215,7 +216,7 @@ public void NullCache_DoesNotWrapAnyPrecompiles() public void Sha256Precompile_IsWrapped_WhenCacheEnabled() { // Arrange - Sha256Precompile has SupportsCaching = true (default) - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [Sha256Precompile.Address] = new(Sha256Precompile.Instance) }.ToFrozenDictionary(); @@ -230,7 +231,7 @@ public void Sha256Precompile_IsWrapped_WhenCacheEnabled() // Act CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); // Assert - Sha256Precompile should be wrapped (unlike IdentityPrecompile) codeInfo.Should().NotBeNull(); @@ -242,7 +243,7 @@ public void Sha256Precompile_IsWrapped_WhenCacheEnabled() public void MixedPrecompiles_OnlyCachingEnabledAreWrapped() { // Arrange - mix of caching and non-caching precompiles - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [Sha256Precompile.Address] = new(Sha256Precompile.Instance), // SupportsCaching = true [IdentityPrecompile.Address] = new(IdentityPrecompile.Instance) // SupportsCaching = false @@ -263,8 +264,8 @@ public void MixedPrecompiles_OnlyCachingEnabledAreWrapped() // Act CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo sha256CodeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); - ICodeInfo identityCodeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); + CodeInfo sha256CodeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); + CodeInfo identityCodeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); // Assert - Sha256 wrapped, Identity not wrapped sha256CodeInfo.Precompile.Should().NotBeSameAs(Sha256Precompile.Instance); @@ -281,7 +282,7 @@ public void CachedPrecompile_DifferentInputs_CreateSeparateCacheEntries() TestPrecompile cachingPrecompile = new(supportsCaching: true, onRun: () => runCount++); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(cachingPrecompile) }.ToFrozenDictionary(); @@ -295,7 +296,7 @@ public void CachedPrecompile_DifferentInputs_CreateSeparateCacheEntries() IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); byte[] input1 = [1, 2, 3]; byte[] input2 = [4, 5, 6]; @@ -320,7 +321,7 @@ public void CachedPrecompile_ReturnsCachedResult_OnCacheHit() TestPrecompile cachingPrecompile = new(supportsCaching: true, onRun: () => runCount++, fixedOutput: expectedOutput); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(cachingPrecompile) }.ToFrozenDictionary(); @@ -334,7 +335,7 @@ public void CachedPrecompile_ReturnsCachedResult_OnCacheHit() IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); byte[] input = [1, 2, 3]; @@ -354,7 +355,7 @@ public void CachedPrecompile_ReturnsCachedResult_OnCacheHit() public void Sha256Precompile_CachesResults_WithRealComputation() { // Arrange - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [Sha256Precompile.Address] = new(Sha256Precompile.Instance) }.ToFrozenDictionary(); @@ -368,7 +369,7 @@ public void Sha256Precompile_CachesResults_WithRealComputation() IReleaseSpec spec = CreateSpecWithPrecompile(Sha256Precompile.Address); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); byte[] input = [1, 2, 3, 4, 5]; @@ -387,7 +388,7 @@ public void Sha256Precompile_CachesResults_WithRealComputation() public void IdentityPrecompile_DoesNotCache_WithRealComputation() { // Arrange - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [IdentityPrecompile.Address] = new(IdentityPrecompile.Instance) }.ToFrozenDictionary(); @@ -401,7 +402,7 @@ public void IdentityPrecompile_DoesNotCache_WithRealComputation() IReleaseSpec spec = CreateSpecWithPrecompile(IdentityPrecompile.Address); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); byte[] input = [1, 2, 3, 4, 5]; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ChainHeadReadOnlyStateProviderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ChainHeadReadOnlyStateProviderTests.cs index 6711e1283518..092d3b8b14cb 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ChainHeadReadOnlyStateProviderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ChainHeadReadOnlyStateProviderTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class ChainHeadReadOnlyStateProviderTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/ClefSignerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/ClefSignerTests.cs index 54d83e4a3617..4ecb01eee74f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/ClefSignerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/ClefSignerTests.cs @@ -15,6 +15,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class ClefSignerTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/CompositeTxSourceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/CompositeTxSourceTests.cs index cc61b6a7fc4a..6a9b7270cfc5 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/CompositeTxSourceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/CompositeTxSourceTests.cs @@ -14,6 +14,7 @@ namespace Nethermind.Blockchain.Test.Consensus; +[Parallelizable(ParallelScope.All)] public class CompositeTxSourceTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NethDevSealEngineTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NethDevSealEngineTests.cs index d9f0704c5167..e06a1eeaf3d6 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NethDevSealEngineTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NethDevSealEngineTests.cs @@ -12,6 +12,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class NethDevSealEngineTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSealEngineTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSealEngineTests.cs index dcf4bf713101..df8bed08b4f1 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSealEngineTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSealEngineTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class NullSealEngineTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs index 17af0b2ad38a..1de624592784 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs @@ -12,6 +12,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class NullSignerTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/OneByOneTxSourceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/OneByOneTxSourceTests.cs index 354848108367..0c26037ba28e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/OneByOneTxSourceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/OneByOneTxSourceTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class OneByOneTxSourceTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SealEngineExceptionTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SealEngineExceptionTests.cs index 5d5fcb9602c4..c6bef99ca9bc 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SealEngineExceptionTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SealEngineExceptionTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class SealEngineExceptionTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SignerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SignerTests.cs index edd88c1a7224..fe897e8ff3e9 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SignerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SignerTests.cs @@ -16,6 +16,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class SignerTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SinglePendingTxSelectorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SinglePendingTxSelectorTests.cs index feb4d47964c0..ad529fda85ba 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SinglePendingTxSelectorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SinglePendingTxSelectorTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Consensus { + [Parallelizable(ParallelScope.All)] public class SinglePendingTxSelectorTests { private readonly BlockHeader _anyParent = Build.A.BlockHeader.TestObject; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/DaoDataTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/DaoDataTests.cs index b7483603e603..f2dbf4d95c20 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/DaoDataTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/DaoDataTests.cs @@ -7,6 +7,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class DaoDataTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Data/FileLocalDataSourceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Data/FileLocalDataSourceTests.cs index 03e9008d489f..d13afa2d1143 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Data/FileLocalDataSourceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Data/FileLocalDataSourceTests.cs @@ -16,6 +16,7 @@ namespace Nethermind.Blockchain.Test.Data { + [Parallelizable(ParallelScope.All)] public class FileLocalDataSourceTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ExitOnBlocknumberHandlerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ExitOnBlocknumberHandlerTests.cs index 72da5efe337e..34d04d45bf72 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ExitOnBlocknumberHandlerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ExitOnBlocknumberHandlerTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class ExitOnBlocknumberHandlerTests { [TestCase(10, false)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs index 4fe896388c71..4dde1eadbff2 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Filters; [TestFixture] +[Parallelizable(ParallelScope.All)] public class AddressFilterTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterManagerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterManagerTests.cs index 0b863d6112bc..a8dd1a2a6c9c 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterManagerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterManagerTests.cs @@ -21,11 +21,11 @@ namespace Nethermind.Blockchain.Test.Filters; +[Parallelizable(ParallelScope.None)] public class FilterManagerTests { private FilterStore _filterStore = null!; - private IBranchProcessor _branchProcessor = null!; - private IMainProcessingContext _mainProcessingContext = null!; + private TestMainProcessingContext _mainProcessingContext = null!; private ITxPool _txPool = null!; private ILogManager _logManager = null!; private FilterManager _filterManager = null!; @@ -36,10 +36,8 @@ public class FilterManagerTests public void Setup() { _currentFilterId = 0; - _filterStore = new FilterStore(new TimerFactory(), 20, 10); - _branchProcessor = Substitute.For(); - _mainProcessingContext = Substitute.For(); - _mainProcessingContext.BranchProcessor.Returns(_branchProcessor); + _filterStore = new FilterStore(new TimerFactory(), 400, 100); + _mainProcessingContext = new TestMainProcessingContext(); _txPool = Substitute.For(); _logManager = LimboLogs.Instance; } @@ -55,7 +53,7 @@ public async Task removing_filter_removes_data() { LogsShouldNotBeEmpty(static _ => { }, static _ => { }); _filterManager.GetLogs(0).Should().NotBeEmpty(); - await Task.Delay(60); + await Task.Delay(600); _filterManager.GetLogs(0).Should().BeEmpty(); } @@ -257,6 +255,7 @@ public void logs_should_be_empty_for_existing_block_and_addresses_and_non_existi [Test, MaxTime(Timeout.MaxTestTime)] [TestCase(1, 1)] [TestCase(5, 3)] + [NonParallelizable] public void logs_should_have_correct_log_indexes(int filtersCount, int logsPerTx) { const int txCount = 10; @@ -330,12 +329,12 @@ private void Assert(IEnumerable> filterBuilders, _filterStore.SaveFilters(filters.OfType()); _filterManager = new FilterManager(_filterStore, _mainProcessingContext, _txPool, _logManager); - _branchProcessor.BlockProcessed += Raise.EventWith(_branchProcessor, new BlockProcessedEventArgs(block, [])); + _mainProcessingContext.TestBranchProcessor.RaiseBlockProcessed(new BlockProcessedEventArgs(block, [])); int index = 1; foreach (TxReceipt receipt in receipts) { - _mainProcessingContext.TransactionProcessed += Raise.EventWith(_branchProcessor, + _mainProcessingContext.RaiseTransactionProcessed( new TxProcessedEventArgs(index, Build.A.Transaction.TestObject, block.Header, receipt)); index++; } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs index 80fabb6c8e13..dfec4eac051e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs @@ -18,6 +18,8 @@ namespace Nethermind.Blockchain.Test.Filters; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class FilterStoreTests { [Test, MaxTime(Timeout.MaxTestTime)] @@ -152,6 +154,7 @@ public void Correctly_creates_topics_filter(Hash256[]?[]? topics, TopicsFilter e } [Test, MaxTime(Timeout.MaxTestTime)] + [Parallelizable(ParallelScope.None)] public async Task CleanUps_filters() { List removedFilterIds = new(); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs index 9d497daa7dd0..df6bde0db6b9 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs @@ -11,6 +11,8 @@ namespace Nethermind.Blockchain.Test.Filters; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class LogFilterTests { private int _filterCounter; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogIndexFilterVisitorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogIndexFilterVisitorTests.cs new file mode 100644 index 000000000000..3a901bfaafdf --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogIndexFilterVisitorTests.cs @@ -0,0 +1,413 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Nethermind.Blockchain.Filters; +using Nethermind.Blockchain.Test.Builders; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Db.LogIndex; +using Nethermind.Facade.Filters; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Blockchain.Test.Filters; + +[Parallelizable(ParallelScope.All)] +public class LogIndexFilterVisitorTests +{ + private record Ranges(Dictionary> Address, Dictionary>[] Topic) + { + public List this[Address address] => Address[address]; + public List this[int topicIndex, Hash256 hash] => Topic[topicIndex][hash]; + } + + public class EnumeratorWrapper(int[] array) : IEnumerator + { + private readonly IEnumerator _enumerator = array.Cast().GetEnumerator(); + public bool MoveNext() => _enumerator.MoveNext(); + public void Reset() => _enumerator.Reset(); + public int Current => _enumerator.Current; + object IEnumerator.Current => Current; + public virtual void Dispose() => _enumerator.Dispose(); + } + + [TestCase( + new[] { 1, 3, 5, 7, 9, }, + new[] { 0, 2, 4, 6, 8 }, + TestName = "Non-intersecting, but similar ranges" + )] + [TestCase( + new[] { 1, 2, 3, 4, 5, }, + new[] { 5, 6, 7, 8, 9 }, + TestName = "Intersects on first/last" + )] + [TestCase( + new[] { 1, 2, 3, 4, 5, }, + new[] { 6, 7, 8, 9, 10 }, + TestName = "Non-intersecting ranges" + )] + public void IntersectEnumerator(int[] s1, int[] s2) + { + var expected = s1.Intersect(s2).Order().ToArray(); + + VerifyEnumerator(s1, s2, expected); + VerifyEnumerator(s2, s1, expected); + } + + [TestCase(1, 1)] + [TestCase(20, 20)] + [TestCase(20, 100)] + [TestCase(100, 100)] + [TestCase(1000, 1000)] + public void IntersectEnumerator_Random(int len1, int len2) + { + var random = new Random(42); + var s1 = RandomAscending(random, len1, Math.Max(1, len1 / 10)); + var s2 = RandomAscending(random, len2, Math.Max(1, len2 / 10)); + + var expected = s1.Intersect(s2).Order().ToArray(); + Assert.That(expected, Is.Not.Empty, "Unreliable test: Needs non-empty sequence to verify against."); + + VerifyEnumerator(s1, s2, expected); + VerifyEnumerator(s2, s1, expected); + } + + [TestCase(0, 0)] + [TestCase(0, 1)] + [TestCase(0, 10)] + public void IntersectEnumerator_SomeEmpty(int len1, int len2) + { + var s1 = Enumerable.Range(0, len1).ToArray(); + var s2 = Enumerable.Range(0, len2).ToArray(); + + VerifyEnumerator(s1, s2, []); + VerifyEnumerator(s2, s1, []); + } + + [TestCase( + new[] { 1, 2, 3, 4, 5, }, + new[] { 2, 3, 4 }, + TestName = "Contained" + )] + [TestCase( + new[] { 1, 2, 3, 4, 5, }, + new[] { 1, 2, 3, 4, 5, }, + TestName = "Identical" + )] + [TestCase( + new[] { 1, 3, 5, 7, 9, }, + new[] { 2, 4, 6, 8, 10 }, + TestName = "Complementary" + )] + public void UnionEnumerator(int[] s1, int[] s2) + { + var expected = s1.Union(s2).Distinct().Order().ToArray(); + + VerifyEnumerator(s1, s2, expected); + VerifyEnumerator(s2, s1, expected); + } + + [TestCase(1, 1)] + [TestCase(20, 20)] + [TestCase(20, 100)] + [TestCase(100, 100)] + [TestCase(1000, 1000)] + public void UnionEnumerator_Random(int len1, int len2) + { + var random = new Random(42); + var s1 = RandomAscending(random, len1, Math.Max(1, len1 / 10)); + var s2 = RandomAscending(random, len2, Math.Max(1, len2 / 10)); + + var expected = s1.Union(s2).Distinct().Order().ToArray(); + + VerifyEnumerator(s1, s2, expected); + VerifyEnumerator(s2, s1, expected); + } + + [TestCase(0, 0)] + [TestCase(0, 1)] + [TestCase(0, 10)] + public void UnionEnumerator_SomeEmpty(int len1, int len2) + { + var s1 = Enumerable.Range(0, len1).ToArray(); + var s2 = Enumerable.Range(0, len2).ToArray(); + + var expected = s1.Union(s2).Distinct().Order().ToArray(); + + VerifyEnumerator(s1, s2, expected); + VerifyEnumerator(s2, s1, expected); + } + + [TestCaseSource(nameof(FilterTestData))] + public void FilterEnumerator(string name, LogFilter filter, List expected) + { + Assert.That(expected, + Has.Count.InRange(from: 1, to: ToBlock - FromBlock - 1), + "Unreliable test: none or all blocks are selected." + ); + ILogIndexStorage storage = Substitute.For(); + + foreach ((Address address, List range) in LogIndexRanges.Address) + { + storage + .GetEnumerator(address, Arg.Any(), Arg.Any()) + .Returns(info => range.SkipWhile(x => x < info.ArgAt(1)).TakeWhile(x => x <= info.ArgAt(2)).GetEnumerator()); + } + + for (var i = 0; i < LogIndexRanges.Topic.Length; i++) + { + foreach ((Hash256 topic, List range) in LogIndexRanges.Topic[i]) + { + storage + .GetEnumerator(Arg.Is(i), topic, Arg.Any(), Arg.Any()) + .Returns(info => range.SkipWhile(x => x < info.ArgAt(2)).TakeWhile(x => x <= info.ArgAt(3)).GetEnumerator()); + } + } + + Assert.That(storage.EnumerateBlockNumbersFor(filter, FromBlock, ToBlock), Is.EquivalentTo(expected)); + } + + [TestCaseSource(nameof(FilterTestData))] + public void FilterEnumerator_Dispose(string name, LogFilter filter, List _) + { + int[] blockNumbers = [1, 2, 3, 4, 5]; + List> enumerators = []; + + ILogIndexStorage storage = Substitute.For(); + storage.GetEnumerator(Arg.Any
(), Arg.Any(), Arg.Any()).Returns(_ => MockEnumerator()); + storage.GetEnumerator(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(_ => MockEnumerator()); + + storage.EnumerateBlockNumbersFor(filter, FromBlock, ToBlock).ForEach(_ => { }); + + enumerators.ForEach(enumerator => enumerator.Received().Dispose()); + + IEnumerator MockEnumerator() + { + IEnumerator? enumerator = Substitute.ForPartsOf(blockNumbers); + enumerators.Add(enumerator); + return enumerator; + } + } + + public static IEnumerable FilterTestData + { + get + { + yield return new TestCaseData( + "AddressA", // name + + BuildFilter() // filter + .WithAddress(TestItem.AddressA) + .Build(), + + LogIndexRanges[TestItem.AddressA] // expected range + ); + + yield return new TestCaseData( + "AddressA or AddressA", + + BuildFilter() + .WithAddresses(TestItem.AddressA, TestItem.AddressA) + .Build(), + + LogIndexRanges[TestItem.AddressA] + ); + + yield return new TestCaseData( + "AddressA or AddressB", + + BuildFilter() + .WithAddresses(TestItem.AddressA, TestItem.AddressB) + .Build(), + + LogIndexRanges[TestItem.AddressA].Union(LogIndexRanges[TestItem.AddressB]) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "TopicA", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Specific(TestItem.KeccakA) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA] + ); + + yield return new TestCaseData( + "TopicA or TopicA", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Or(TestItem.KeccakA, TestItem.KeccakA) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA] + ); + + yield return new TestCaseData( + "TopicA or TopicB", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Or(TestItem.KeccakA, TestItem.KeccakB) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA].Union(LogIndexRanges[0, TestItem.KeccakB]) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "TopicA, TopicB", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Specific(TestItem.KeccakA), + TestTopicExpressions.Specific(TestItem.KeccakB) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA] + .Intersect(LogIndexRanges[1, TestItem.KeccakB]) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "TopicA, -, TopicA", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Specific(TestItem.KeccakA), + TestTopicExpressions.Any, + TestTopicExpressions.Specific(TestItem.KeccakA) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA] + .Intersect(LogIndexRanges[2, TestItem.KeccakA]) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "TopicA, -, TopicB or TopicC", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Specific(TestItem.KeccakA), + TestTopicExpressions.Any, + TestTopicExpressions.Or(TestItem.KeccakB, TestItem.KeccakC) + ).Build(), + LogIndexRanges[0, TestItem.KeccakA] + .Intersect(LogIndexRanges[2, TestItem.KeccakB].Union(LogIndexRanges[2, TestItem.KeccakC])) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "AddressA | TopicA or TopicB, TopicC", + + BuildFilter() + .WithAddress(TestItem.AddressA) + .WithTopicExpressions( + TestTopicExpressions.Or(TestItem.KeccakA, TestItem.KeccakB), + TestTopicExpressions.Specific(TestItem.KeccakC) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA].Union(LogIndexRanges[0, TestItem.KeccakB]) + .Intersect(LogIndexRanges[1, TestItem.KeccakC]) + .Intersect(LogIndexRanges[TestItem.AddressA]) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "AddressA or AddressB | TopicA or TopicB, -, TopicC, TopicD or TopicE", + + BuildFilter() + .WithAddresses(TestItem.AddressA, TestItem.AddressB) + .WithTopicExpressions( + TestTopicExpressions.Or(TestItem.KeccakA, TestItem.KeccakB), + TestTopicExpressions.Any, + TestTopicExpressions.Specific(TestItem.KeccakC), + TestTopicExpressions.Or(TestItem.KeccakD, TestItem.KeccakE) + ).Build(), + + LogIndexRanges[TestItem.AddressA].Union(LogIndexRanges[TestItem.AddressB]) + .Intersect(LogIndexRanges[0, TestItem.KeccakA].Union(LogIndexRanges[0, TestItem.KeccakB])) + .Intersect(LogIndexRanges[2, TestItem.KeccakC]) + .Intersect(LogIndexRanges[3, TestItem.KeccakD].Union(LogIndexRanges[3, TestItem.KeccakE])) + .Distinct().Order().ToList() + ); + } + } + + private static int[] RandomAscending(Random random, int count, int maxDelta) + { + var result = new int[count]; + + for (var i = 0; i < result.Length; i++) + { + var min = i > 0 ? result[i - 1] : -1; + result[i] = random.Next(min + 1, min + 1 + maxDelta); + } + + return result; + } + + private static void VerifyEnumerator(int[] s1, int[] s2, int[] ex) + where T : IEnumerator + { + using var enumerator = (T)Activator.CreateInstance( + typeof(T), + s1.Cast().GetEnumerator(), + s2.Cast().GetEnumerator() + )!; + + Assert.That(EnumerateOnce(enumerator), Is.EqualTo(ex)); + } + + private static IEnumerable EnumerateOnce(IEnumerator enumerator) + { + while (enumerator.MoveNext()) + yield return enumerator.Current; + } + + private const long FromBlock = 0; + private const long ToBlock = 99; + + private static readonly Ranges LogIndexRanges = GenerateLogIndexRanges(); + + private static Ranges GenerateLogIndexRanges() + { + var random = new Random(42); + + var addressRanges = new Dictionary>(); + foreach (Address address in new[] { TestItem.AddressA, TestItem.AddressB, TestItem.AddressC, TestItem.AddressD, TestItem.AddressE }) + { + var range = Enumerable.Range((int)FromBlock, (int)(ToBlock + 1)).Where(_ => random.NextDouble() < 0.3).ToList(); + addressRanges.Add(address, range); + } + + Dictionary>[] topicRanges = Enumerable + .Range(0, LogIndexStorage.MaxTopics) + .Select(_ => new Dictionary>()).ToArray(); + + foreach (Dictionary> ranges in topicRanges) + { + foreach (Hash256 topic in new[] { TestItem.KeccakA, TestItem.KeccakB, TestItem.KeccakC, TestItem.KeccakD, TestItem.KeccakE }) + { + var range = Enumerable.Range((int)FromBlock, (int)(ToBlock + 1)).Where(_ => random.NextDouble() < 0.2).ToList(); + ranges.Add(topic, range); + } + } + + return new(addressRanges, topicRanges); + } + + private static FilterBuilder BuildFilter() => FilterBuilder.New() + .FromBlock(FromBlock) + .ToBlock(ToBlock); +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/TestProcessingContext.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/TestProcessingContext.cs new file mode 100644 index 000000000000..5b089c3f11c3 --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/TestProcessingContext.cs @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; + +namespace Nethermind.Blockchain.Test.Filters; + +/// +/// Test implementation of IBranchProcessor that allows manual event raising. +/// +internal class TestBranchProcessor : IBranchProcessor +{ + public event EventHandler? BlockProcessed; + public event EventHandler? BlocksProcessing { add { } remove { } } + public event EventHandler? BlockProcessing { add { } remove { } } + + public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlocks, + ProcessingOptions processingOptions, IBlockTracer blockTracer, CancellationToken token = default) + => []; + + public void RaiseBlockProcessed(BlockProcessedEventArgs args) + => BlockProcessed?.Invoke(this, args); +} + +/// +/// Test implementation of IMainProcessingContext that allows manual event raising. +/// +internal class TestMainProcessingContext : IMainProcessingContext +{ + private readonly TestBranchProcessor _branchProcessor = new(); + + public ITransactionProcessor TransactionProcessor => null!; + public IBranchProcessor BranchProcessor => _branchProcessor; + public IBlockProcessor BlockProcessor => null!; + public IBlockchainProcessor BlockchainProcessor => null!; + public IBlockProcessingQueue BlockProcessingQueue => null!; + public IWorldState WorldState => null!; + public IGenesisLoader GenesisLoader => null!; + + public event EventHandler? TransactionProcessed; + + public TestBranchProcessor TestBranchProcessor => _branchProcessor; + + public void RaiseTransactionProcessed(TxProcessedEventArgs args) + => TransactionProcessed?.Invoke(this, args); +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs index e8ae192041b0..ae11d8419b65 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs @@ -19,13 +19,17 @@ using Nethermind.Db; using Nethermind.Logging; using Nethermind.Db.Blooms; +using Nethermind.Db.LogIndex; using Nethermind.Facade.Filters; using Nethermind.Facade.Find; using NSubstitute; using NUnit.Framework; +using NUnit.Framework.Internal; namespace Nethermind.Blockchain.Test.Find; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class LogFinderTests { private IBlockTree _blockTree = null!; @@ -45,19 +49,19 @@ public void SetUp() [TearDown] public void TearDown() => _bloomStorage?.Dispose(); - private void SetUp(bool allowReceiptIterator) + private void SetUp(bool allowReceiptIterator, int chainLength = 5) { var specProvider = Substitute.For(); specProvider.GetSpec(Arg.Any()).IsEip155Enabled.Returns(true); _receiptStorage = new InMemoryReceiptStorage(allowReceiptIterator); _rawBlockTree = Build.A.BlockTree() .WithTransactions(_receiptStorage, LogsForBlockBuilder) - .OfChainLength(out _headTestBlock, 5) + .OfChainLength(out _headTestBlock, chainLength) .TestObject; _blockTree = _rawBlockTree; _bloomStorage = new BloomStorage(new BloomConfig(), new MemDb(), new InMemoryDictionaryFileStoreFactory()); _receiptsRecovery = Substitute.For(); - _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = CreateLogFinder(); } private void SetupHeadWithNoTransaction() @@ -129,15 +133,8 @@ public void filter_all_logs_iteratively([ValueSource(nameof(WithBloomValues))] b SetUp(allowReceiptIterator); LogFilter logFilter = AllBlockFilter().Build(); FilterLog[] logs = _logFinder.FindLogs(logFilter).ToArray(); - logs.Length.Should().Be(5); var indexes = logs.Select(static l => (int)l.LogIndex).ToArray(); - // indexes[0].Should().Be(0); - // indexes[1].Should().Be(1); - // indexes[2].Should().Be(0); - // indexes[3].Should().Be(1); - // indexes[4].Should().Be(2); - // BeEquivalentTo does not check the ordering!!! :O - indexes.Should().BeEquivalentTo(new[] { 0, 1, 0, 1, 2 }); + Assert.That(indexes, Is.EqualTo([0, 1, 0, 1, 2])); } [Test, MaxTime(Timeout.MaxTestTime)] @@ -145,7 +142,7 @@ public void throw_exception_when_receipts_are_missing([ValueSource(nameof(WithBl { StoreTreeBlooms(withBloomDb); _receiptStorage = NullReceiptStorage.Instance; - _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = CreateLogFinder(); var logFilter = AllBlockFilter().Build(); @@ -158,7 +155,7 @@ public void throw_exception_when_receipts_are_missing([ValueSource(nameof(WithBl public void when_receipts_are_missing_and_header_has_no_receipt_root_do_not_throw_exception_() { _receiptStorage = NullReceiptStorage.Instance; - _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = CreateLogFinder(); SetupHeadWithNoTransaction(); @@ -174,7 +171,7 @@ public void filter_all_logs_should_throw_when_to_block_is_not_found([ValueSource { StoreTreeBlooms(withBloomDb); var blockFinder = Substitute.For(); - _logFinder = new LogFinder(blockFinder, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = CreateLogFinder(blockFinder); var logFilter = AllBlockFilter().Build(); var action = new Func>(() => _logFinder.FindLogs(logFilter)); action.Should().Throw(); @@ -277,14 +274,13 @@ public void filter_by_blocks(LogFilter filter, int expectedCount, bool withBloom public void filter_by_blocks_with_limit([ValueSource(nameof(WithBloomValues))] bool withBloomDb) { StoreTreeBlooms(withBloomDb); - _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery, 2); + _logFinder = CreateLogFinder(); var filter = FilterBuilder.New().FromLatestBlock().ToLatestBlock().Build(); var logs = _logFinder.FindLogs(filter).ToArray(); logs.Length.Should().Be(3); } - public static IEnumerable ComplexFilterTestsData { get @@ -316,6 +312,7 @@ public void complex_filter(LogFilter filter, int expectedCount, bool withBloomDb } [Test, MaxTime(Timeout.MaxTestTime)] + [NonParallelizable] public async Task Throw_log_finder_operation_canceled_after_given_timeout([Values(2, 0.01)] double waitTime) { var timeout = TimeSpan.FromMilliseconds(Timeout.MaxWaitTime); @@ -323,24 +320,122 @@ public async Task Throw_log_finder_operation_canceled_after_given_timeout([Value CancellationToken cancellationToken = cancellationTokenSource.Token; StoreTreeBlooms(true); - _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = CreateLogFinder(); var logFilter = AllBlockFilter().Build(); var logs = _logFinder.FindLogs(logFilter, cancellationToken); await Task.Delay(timeout * waitTime); - Func action = () => logs.ToArray(); + TestDelegate action = () => _ = logs.ToArray(); if (waitTime > 1) { - action.Should().Throw().WithInnerException(); + Assert.That(action, Throws + .Exception.InstanceOf() + .Or.InnerException.InstanceOf() // PLINQ can wrap into AggregateException + ); } else { - action.Should().NotThrow(); + Assert.DoesNotThrow(action); } } + [TestCase("Empty index", + 1, 2, + null, null, + null, null + )] + [TestCase("No intersection, left", + 1, 2, + 4, 6, + null, null + )] + [TestCase("No intersection, adjacent left", + 1, 3, + 4, 6, + null, null + )] + [TestCase("1 block intersection, left", + 1, 4, + 4, 6, + 4, 4 + )] + [TestCase("Partial intersection, left", + 1, 5, + 4, 6, + 4, 5 + )] + [TestCase("Full containment, border right", + 1, 6, + 4, 6, + 4, 6 + )] + [TestCase("Full containment", + 1, 9, + 4, 6, + 4, 6 + )] + [TestCase("Full containment, border left", + 4, 9, + 4, 6, + 4, 6 + )] + [TestCase("Partial intersection, right", + 5, 9, + 4, 6, + 5, 6 + )] + [TestCase("1 block intersection, right", + 6, 9, + 4, 6, + 6, 6 + )] + [TestCase("No intersection, adjacent right", + 7, 9, + 4, 6, + null, null + )] + [TestCase("No intersection, right", + 8, 9, + 4, 6, + null, null + )] + public void query_intersected_range_from_log_index(string name, + int from, int to, + int? indexFrom, int? indexTo, + int? exFrom, int? exTo + ) + { + SetUp(true, chainLength: 10); + + var logIndexStorage = Substitute.For(); + logIndexStorage.Enabled.Returns(true); + logIndexStorage.MinBlockNumber.Returns(indexFrom); + logIndexStorage.MaxBlockNumber.Returns(indexTo); + logIndexStorage.GetEnumerator(Arg.Any
(), Arg.Any(), Arg.Any()) + .Returns(_ => Array.Empty().Cast().GetEnumerator()); + + Address address = TestItem.AddressA; + BlockHeader fromHeader = Build.A.BlockHeader.WithNumber(from).TestObject; + BlockHeader toHeader = Build.A.BlockHeader.WithNumber(to).TestObject; + LogFilter filter = FilterBuilder.New() + .FromBlock(from).ToBlock(to) + .WithAddress(address) + .Build(); + + var logFinder = new IndexedLogFinder( + _blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery, + logIndexStorage, minBlocksToUseIndex: 1 + ); + _ = logFinder.FindLogs(filter, fromHeader, toHeader).ToArray(); + + if (exTo is not null && exFrom is not null) + logIndexStorage.Received(1).GetEnumerator(address, exFrom.Value, exTo.Value); + else + logIndexStorage.DidNotReceiveWithAnyArgs().GetEnumerator(Arg.Any
(), Arg.Any(), Arg.Any()); + } + private static FilterBuilder AllBlockFilter() => FilterBuilder.New().FromEarliestBlock().ToPendingBlock(); private void StoreTreeBlooms(bool withBloomDb) @@ -354,4 +449,6 @@ private void StoreTreeBlooms(bool withBloomDb) } } + private LogFinder CreateLogFinder(IBlockFinder? blockFinder = null) => + new(blockFinder ?? _blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs index 7c5aae0c8b2b..406ae0c89e36 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs @@ -21,18 +21,11 @@ namespace Nethermind.Blockchain.Test.FullPruning; -[Parallelizable(ParallelScope.Self)] +[Parallelizable(ParallelScope.All)] [TestFixture(INodeStorage.KeyScheme.HalfPath)] [TestFixture(INodeStorage.KeyScheme.Hash)] -public class CopyTreeVisitorTests +public class CopyTreeVisitorTests(INodeStorage.KeyScheme scheme) { - private readonly INodeStorage.KeyScheme _keyScheme; - - public CopyTreeVisitorTests(INodeStorage.KeyScheme scheme) - { - _keyScheme = scheme; - } - [TestCase(0, 1)] [TestCase(0, 8)] [TestCase(1, 1)] @@ -83,7 +76,7 @@ public void cancel_coping_state_between_dbs() private IPruningContext CopyDb(IPruningContext pruningContext, CancellationToken cancellationToken, MemDb trieDb, VisitingOptions? visitingOptions = null, WriteFlags writeFlags = WriteFlags.None) { LimboLogs logManager = LimboLogs.Instance; - PatriciaTree trie = Build.A.Trie(new NodeStorage(trieDb, _keyScheme)).WithAccountsByIndex(0, 100).TestObject; + PatriciaTree trie = Build.A.Trie(new NodeStorage(trieDb, scheme)).WithAccountsByIndex(0, 100).TestObject; // Create a custom DbProvider that uses the trieDb from the test IDbProvider dbProvider = Substitute.For(); @@ -94,16 +87,16 @@ private IPruningContext CopyDb(IPruningContext pruningContext, CancellationToken (IWorldState worldState, IStateReader stateReader) = TestWorldStateFactory.CreateForTestWithStateReader(dbProvider, logManager); BlockHeader? baseBlock = Build.A.BlockHeader.WithStateRoot(trie.RootHash).TestObject; - if (_keyScheme == INodeStorage.KeyScheme.Hash) + if (scheme == INodeStorage.KeyScheme.Hash) { - NodeStorage nodeStorage = new NodeStorage(pruningContext, _keyScheme); + NodeStorage nodeStorage = new NodeStorage(pruningContext, scheme); using CopyTreeVisitor copyTreeVisitor = new(nodeStorage, writeFlags, logManager, cancellationToken); stateReader.RunTreeVisitor(copyTreeVisitor, baseBlock, visitingOptions); copyTreeVisitor.Finish(); } else { - NodeStorage nodeStorage = new NodeStorage(pruningContext, _keyScheme); + NodeStorage nodeStorage = new NodeStorage(pruningContext, scheme); using CopyTreeVisitor copyTreeVisitor = new(nodeStorage, writeFlags, logManager, cancellationToken); stateReader.RunTreeVisitor(copyTreeVisitor, baseBlock, visitingOptions); copyTreeVisitor.Finish(); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPrunerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPrunerTests.cs index c13e1e2482cc..d9b07e032cb3 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPrunerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPrunerTests.cs @@ -30,18 +30,10 @@ namespace Nethermind.Blockchain.Test.FullPruning; [TestFixture(0, 4)] [TestFixture(1, 1)] [TestFixture(1, 4)] -[Parallelizable(ParallelScope.Children)] -public class FullPrunerTests +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class FullPrunerTests(int fullPrunerMemoryBudgetMb, int degreeOfParallelism) { - private readonly int _fullPrunerMemoryBudgetMb; - private readonly int _degreeOfParallelism; - - public FullPrunerTests(int fullPrunerMemoryBudgetMb, int degreeOfParallelism) - { - _fullPrunerMemoryBudgetMb = fullPrunerMemoryBudgetMb; - _degreeOfParallelism = degreeOfParallelism; - } - [Test, MaxTime(Timeout.MaxTestTime)] public async Task can_prune() { @@ -61,8 +53,8 @@ public async Task can_prune_and_switch_key_scheme(INodeStorage.KeyScheme current true, false, FullPruningCompletionBehavior.None, - _fullPrunerMemoryBudgetMb, - _degreeOfParallelism, + fullPrunerMemoryBudgetMb, + degreeOfParallelism, currentKeyScheme: currentKeyScheme, preferredKeyScheme: newKeyScheme); @@ -192,8 +184,8 @@ private TestContext CreateTest( successfulPruning, clearPrunedDb, completionBehavior, - _fullPrunerMemoryBudgetMb, - _degreeOfParallelism); + fullPrunerMemoryBudgetMb, + degreeOfParallelism); private class TestContext { diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs index d28cf1ab7be1..98483a553cce 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs @@ -30,6 +30,7 @@ namespace Nethermind.Blockchain.Test.FullPruning; +[Parallelizable(ParallelScope.All)] public class FullPruningDiskTest { public class PruningTestBlockchain : TestBlockchain @@ -109,27 +110,24 @@ public static async Task Create(IPruningConfig? pruningCo return chain; } - public class FullTestPruner : FullPruner + public class FullTestPruner( + IFullPruningDb pruningDb, + INodeStorageFactory nodeStorageFactory, + INodeStorage mainNodeStorage, + IPruningTrigger pruningTrigger, + IPruningConfig pruningConfig, + IBlockTree blockTree, + IStateReader stateReader, + IProcessExitSource processExitSource, + IDriveInfo driveInfo, + IPruningTrieStore trieStore, + IChainEstimations chainEstimations, + ILogManager logManager) + : FullPruner(pruningDb, nodeStorageFactory, mainNodeStorage, pruningTrigger, pruningConfig, blockTree, + stateReader, processExitSource, chainEstimations, driveInfo, trieStore, logManager) { public EventWaitHandle WaitHandle { get; } = new ManualResetEvent(false); - public FullTestPruner( - IFullPruningDb pruningDb, - INodeStorageFactory nodeStorageFactory, - INodeStorage mainNodeStorage, - IPruningTrigger pruningTrigger, - IPruningConfig pruningConfig, - IBlockTree blockTree, - IStateReader stateReader, - IProcessExitSource processExitSource, - IDriveInfo driveInfo, - IPruningTrieStore trieStore, - IChainEstimations chainEstimations, - ILogManager logManager) - : base(pruningDb, nodeStorageFactory, mainNodeStorage, pruningTrigger, pruningConfig, blockTree, stateReader, processExitSource, chainEstimations, driveInfo, trieStore, logManager) - { - } - protected override async Task RunFullPruning(CancellationToken cancellationToken) { await base.RunFullPruning(cancellationToken); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/PruningTriggerPruningStrategyTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/PruningTriggerPruningStrategyTests.cs index 505f6227fb29..fc43e2dce333 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/PruningTriggerPruningStrategyTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/PruningTriggerPruningStrategyTests.cs @@ -12,7 +12,8 @@ namespace Nethermind.Blockchain.Test.FullPruning { [TestFixture] - [Parallelizable(ParallelScope.Self)] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class PruningTriggerPruningStrategyTests { private IFullPruningDb _fullPruningDb; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockOnEachPendingTxTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockOnEachPendingTxTests.cs index fb7dcde6abf1..f1d61f1a256e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockOnEachPendingTxTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockOnEachPendingTxTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class BuildBlockOnEachPendingTxTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockRegularlyTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockRegularlyTests.cs index 4aa9dac4cbba..c1279a679d17 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockRegularlyTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockRegularlyTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class BuildBlockRegularlyTests { [Test, MaxTime(Timeout.MaxTestTime), Retry(3)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksWhenRequestedTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksWhenRequestedTests.cs index a244c84aacb9..d3c709b6ee11 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksWhenRequestedTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksWhenRequestedTests.cs @@ -7,6 +7,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class BuildBlocksWhenRequestedTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/CompositeBlockProductionTriggerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/CompositeBlockProductionTriggerTests.cs index 478c37bbebc4..d493795f1b3e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/CompositeBlockProductionTriggerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/CompositeBlockProductionTriggerTests.cs @@ -7,6 +7,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class CompositeBlockProductionTriggerTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs index 733b73a3c804..b8bc669d2a8c 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs @@ -17,6 +17,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class DevBlockProducerTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/IfPoolIsNotEmptyTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/IfPoolIsNotEmptyTests.cs index 3f2d97ac18be..14549530b899 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/IfPoolIsNotEmptyTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/IfPoolIsNotEmptyTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class IfPoolIsNotEmptyTests { [MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/ReceiptTrieTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/ReceiptTrieTests.cs index 0533fde1a350..82c883a83576 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/ReceiptTrieTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/ReceiptTrieTests.cs @@ -17,6 +17,7 @@ namespace Nethermind.Blockchain.Test.Proofs; +[Parallelizable(ParallelScope.All)] public class ReceiptTrieTests { private static readonly IRlpStreamDecoder _decoder = Rlp.GetStreamDecoder()!; @@ -57,6 +58,28 @@ public void Can_collect_proof_with_branch() VerifyProof(proof, trie.RootHash); } + [Test, MaxTime(Timeout.MaxTestTime)] + public void Parallel_and_non_parallel_root_hashing_produce_same_root() + { + const int receiptCount = 100; + IReleaseSpec spec = MainnetSpecProvider.Instance.GetSpec((MainnetSpecProvider.MuirGlacierBlockNumber, null)); + TxReceipt[] receipts = new TxReceipt[receiptCount]; + for (int i = 0; i < receiptCount; i++) + { + receipts[i] = Build.A.Receipt.WithAllFieldsFilled.WithGasUsedTotal(1000 + i).TestObject; + } + + using TrackingCappedArrayPool parallelPool = new(receiptCount * 4, canBeParallel: true); + ReceiptTrie parallelTrie = new(spec, receipts, _decoder, parallelPool, canBeParallel: true); + Hash256 parallelRoot = parallelTrie.RootHash; + + using TrackingCappedArrayPool sequentialPool = new(receiptCount * 4, canBeParallel: false); + ReceiptTrie sequentialTrie = new(spec, receipts, _decoder, sequentialPool, canBeParallel: false); + Hash256 sequentialRoot = sequentialTrie.RootHash; + + Assert.That(sequentialRoot, Is.EqualTo(parallelRoot)); + } + private void VerifyProof(byte[][] proof, Hash256 receiptRoot) { TrieNode node = new(NodeType.Unknown, proof.Last()); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/TxTrieTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/TxTrieTests.cs index 4f8e912e1887..02b86b29a6a3 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/TxTrieTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/TxTrieTests.cs @@ -7,6 +7,7 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; +using Nethermind.Int256; using Nethermind.Serialization.Rlp; using Nethermind.Specs.Forks; using Nethermind.State.Proofs; @@ -17,14 +18,11 @@ namespace Nethermind.Blockchain.Test.Proofs; [TestFixture(true)] [TestFixture(false)] -public class TxTrieTests +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class TxTrieTests(bool useEip2718) { - private readonly IReleaseSpec _releaseSpec; - - public TxTrieTests(bool useEip2718) - { - _releaseSpec = useEip2718 ? Berlin.Instance : MuirGlacier.Instance; - } + private readonly IReleaseSpec _releaseSpec = useEip2718 ? Berlin.Instance : MuirGlacier.Instance; [Test, MaxTime(Timeout.MaxTestTime)] public void Can_calculate_root() @@ -82,6 +80,46 @@ public void Can_collect_proof_with_trie_case_3_modified() } } + [Test, MaxTime(Timeout.MaxTestTime)] + public void Encoded_and_decoded_transaction_paths_have_same_root() + { + Transaction[] transactions = + [ + Build.A.Transaction.WithNonce(1).WithType(TxType.Legacy).Signed().TestObject, + Build.A.Transaction.WithNonce(2).WithType(useEip2718 ? TxType.EIP1559 : TxType.Legacy).Signed().TestObject, + Build.A.Transaction.WithNonce(3).WithType(useEip2718 ? TxType.AccessList : TxType.Legacy).Signed().TestObject, + ]; + + byte[][] encodedTransactions = transactions + .Select(static tx => Rlp.Encode(tx, RlpBehaviors.SkipTypedWrapping).Bytes) + .ToArray(); + + Hash256 decodedRoot = TxTrie.CalculateRoot(transactions); + Hash256 encodedRoot = TxTrie.CalculateRoot(encodedTransactions); + + Assert.That(encodedRoot, Is.EqualTo(decodedRoot)); + } + + [Test, MaxTime(Timeout.MaxTestTime)] + public void Parallel_and_non_parallel_root_hashing_produce_same_root() + { + const int txCount = 100; + Transaction[] transactions = new Transaction[txCount]; + for (int i = 0; i < txCount; i++) + { + transactions[i] = Build.A.Transaction.WithNonce((UInt256)(i + 1)).Signed().TestObject; + } + + using TrackingCappedArrayPool pool = new(); + TxTrie txTrie = new(transactions, canBuildProof: false, pool); + Hash256 parallelRoot = txTrie.RootHash; + + txTrie.UpdateRootHash(canBeParallel: false); + Hash256 nonParallelRoot = txTrie.RootHash; + + Assert.That(nonParallelRoot, Is.EqualTo(parallelRoot)); + } + private static void VerifyProof(byte[][] proof, Hash256 txRoot) { for (int i = proof.Length; i > 0; i--) diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs index 4dada95cceb2..02c002758efd 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Proofs; +[Parallelizable(ParallelScope.All)] public class WithdrawalTrieTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReadOnlyBlockTreeTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReadOnlyBlockTreeTests.cs index 2a85fb94a0e2..baa7e9d4af2e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ReadOnlyBlockTreeTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReadOnlyBlockTreeTests.cs @@ -9,6 +9,8 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class ReadOnlyBlockTreeTests { private IBlockTree _innerBlockTree = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/KeccaksIteratorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/KeccaksIteratorTests.cs index 96be4973fcae..2c66c04dfaa1 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/KeccaksIteratorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/KeccaksIteratorTests.cs @@ -13,6 +13,7 @@ namespace Nethermind.Blockchain.Test.Receipts; +[Parallelizable(ParallelScope.All)] public class KeccaksIteratorTests { [TestCaseSource(nameof(TestKeccaks))] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs index 4f7aa07eac27..caa0794e7ac3 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs @@ -26,23 +26,19 @@ namespace Nethermind.Blockchain.Test.Receipts; [TestFixture(true)] [TestFixture(false)] -public class PersistentReceiptStorageTests +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class PersistentReceiptStorageTests(bool useCompactReceipts) { - private readonly TestSpecProvider _specProvider = new TestSpecProvider(Byzantium.Instance); + private readonly TestSpecProvider _specProvider = new(Byzantium.Instance); private TestMemColumnsDb _receiptsDb = null!; private ReceiptsRecovery _receiptsRecovery = null!; private IBlockTree _blockTree = null!; private IBlockStore _blockStore = null!; - private readonly bool _useCompactReceipts; private ReceiptConfig _receiptConfig = null!; private PersistentReceiptStorage _storage = null!; private ReceiptArrayStorageDecoder _decoder = null!; - public PersistentReceiptStorageTests(bool useCompactReceipts) - { - _useCompactReceipts = useCompactReceipts; - } - [SetUp] public void SetUp() { @@ -64,7 +60,7 @@ public void TearDown() private void CreateStorage() { - _decoder = new ReceiptArrayStorageDecoder(_useCompactReceipts); + _decoder = new ReceiptArrayStorageDecoder(useCompactReceipts); _storage = new PersistentReceiptStorage( _receiptsDb, _specProvider, @@ -387,7 +383,7 @@ public void When_NewHeadBlock_DoNotRemove_TxIndex_WhenTxIsInOtherBlockNumber() [Test] public async Task When_NewHeadBlock_Remove_TxIndex_OfRemovedBlock_Unless_ItsAlsoInNewBlock() { - _receiptConfig.CompactTxIndex = _useCompactReceipts; + _receiptConfig.CompactTxIndex = useCompactReceipts; CreateStorage(); (Block block, _) = InsertBlock(); Block block2 = Build.A.Block diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs index ad7d30e52564..14f6d357d52a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs @@ -14,9 +14,10 @@ namespace Nethermind.Blockchain.Test.Receipts; +[Parallelizable(ParallelScope.All)] public class ReceiptsIteratorTests { - readonly ReceiptArrayStorageDecoder _decoder = ReceiptArrayStorageDecoder.Instance; + private readonly ReceiptArrayStorageDecoder _decoder = ReceiptArrayStorageDecoder.Instance; [Test] public void SmokeTestWithRecovery() diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs index 7cd1b0ec1292..fd10cec441d6 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs @@ -11,6 +11,8 @@ namespace Nethermind.Blockchain.Test.Receipts; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class ReceiptsRecoveryTests { private IReceiptsRecovery _receiptsRecovery = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRootTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRootTests.cs index 1c3db195441b..e7b8009e2e92 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRootTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRootTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Receipts { + [Parallelizable(ParallelScope.All)] public class ReceiptsRootTests { public static IEnumerable ReceiptsRootTestCases diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReorgDepthFinalizedStateProviderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReorgDepthFinalizedStateProviderTests.cs index 7e2e9b7f7e97..b8d4fd4f6c0b 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ReorgDepthFinalizedStateProviderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReorgDepthFinalizedStateProviderTests.cs @@ -11,7 +11,8 @@ namespace Nethermind.Blockchain.Test; [TestFixture] -[Parallelizable(ParallelScope.Self)] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class ReorgDepthFinalizedStateProviderTests { private IBlockTree _blockTree = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs index b8acad1530f1..8adf5fad4594 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs @@ -29,6 +29,7 @@ using Nethermind.Evm.State; using Nethermind.State; using Nethermind.TxPool; +using NSubstitute; using NUnit.Framework; namespace Nethermind.Blockchain.Test; @@ -131,7 +132,9 @@ public void Setup() specProvider, LimboLogs.Instance), stateReader, - LimboLogs.Instance, BlockchainProcessor.Options.Default); + LimboLogs.Instance, + BlockchainProcessor.Options.Default, + Substitute.For()); } [OneTimeTearDown] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Rewards/NoBlockRewardsTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Rewards/NoBlockRewardsTests.cs index 1dc264011284..212a6448829f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Rewards/NoBlockRewardsTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Rewards/NoBlockRewardsTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Blockchain.Test.Rewards; +[Parallelizable(ParallelScope.All)] public class NoBlockRewardsTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Rewards/RewardCalculatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Rewards/RewardCalculatorTests.cs index d4225963be51..3553483a1f69 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Rewards/RewardCalculatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Rewards/RewardCalculatorTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Blockchain.Test.Rewards; +[Parallelizable(ParallelScope.All)] public class RewardCalculatorTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Services/HealthHintServiceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Services/HealthHintServiceTests.cs index aee9e3a4df56..fc89b9798e8f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Services/HealthHintServiceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Services/HealthHintServiceTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Blockchain.Test.Services; +[Parallelizable(ParallelScope.All)] public class HealthHintServiceTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionGasPriceComparisonTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionGasPriceComparisonTests.cs index f247ffb1e153..089c1010237f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionGasPriceComparisonTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionGasPriceComparisonTests.cs @@ -14,6 +14,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class TransactionComparisonTests { [MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs index 2659aa7fc129..73267a3f94a2 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs @@ -25,6 +25,8 @@ namespace Nethermind.Evm.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] internal class TransactionProcessorEip7702Tests { private ISpecProvider _specProvider; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs index fe71d8075570..bb8c1fe456df 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs @@ -32,7 +32,8 @@ namespace Nethermind.Evm.Test; [TestFixture(true)] [TestFixture(false)] [Todo(Improve.Refactor, "Check why fixture test cases did not work")] -[Parallelizable(ParallelScope.Self)] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class TransactionProcessorTests { private readonly bool _isEip155Enabled; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs index 2866322ce40b..c31d811b18f6 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs @@ -28,6 +28,7 @@ namespace Nethermind.Blockchain.Test { + [Parallelizable(ParallelScope.All)] public class TransactionSelectorTests { public static IEnumerable ProperTransactionsSelectedTestCases diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs index ab506b1fbab2..1282d9038c21 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs @@ -31,6 +31,7 @@ namespace Nethermind.Blockchain.Test { + [Parallelizable(ParallelScope.All)] public class TransactionsExecutorTests { public static IEnumerable ProperTransactionsSelectedTestCases diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Utils/LastNStateRootTrackerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Utils/LastNStateRootTrackerTests.cs index 699ef5593bfa..07c79a8a1ec5 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Utils/LastNStateRootTrackerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Utils/LastNStateRootTrackerTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Utils; +[Parallelizable(ParallelScope.All)] public class LastNStateRootTrackerTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/BlockValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/BlockValidatorTests.cs index dff67022674e..78a14474285d 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/BlockValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/BlockValidatorTests.cs @@ -18,6 +18,8 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class BlockValidatorTests { private static BlockValidator _blockValidator = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/HeaderValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/HeaderValidatorTests.cs index 445672cdf87a..fb3a63499864 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/HeaderValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/HeaderValidatorTests.cs @@ -23,6 +23,8 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class HeaderValidatorTests { private IHeaderValidator _validator = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs index 0d386ee72497..28e7fe635cbe 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs @@ -15,6 +15,7 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] public class ShardBlobBlockValidatorTests { [TestCaseSource(nameof(BlobGasFieldsPerForkTestCases))] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs index 8dcb6a44720f..ac04461b93fc 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs @@ -26,12 +26,9 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] public class TxValidatorTests { - [SetUp] - public void Setup() - { - } [Test, MaxTime(Timeout.MaxTestTime)] public void Curve_is_correct() diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs index 069960655340..0ecbe42eb588 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs @@ -12,6 +12,8 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class UnclesValidatorTests { private Block _greatGrandparent; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs index 7cc4bacbc4e1..604c64e2d497 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs @@ -14,6 +14,7 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] public class WithdrawalValidatorTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Visitors/DbBlocksLoaderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Visitors/DbBlocksLoaderTests.cs index b468c23eed9c..420a86305e72 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Visitors/DbBlocksLoaderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Visitors/DbBlocksLoaderTests.cs @@ -17,6 +17,7 @@ namespace Nethermind.Blockchain.Test.Visitors; +[Parallelizable(ParallelScope.All)] public class DbBlocksLoaderTests { private readonly int _dbLoadTimeout = 5000; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Visitors/StartupTreeFixerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Visitors/StartupTreeFixerTests.cs index 8e787f1a5067..1191fe481b7a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Visitors/StartupTreeFixerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Visitors/StartupTreeFixerTests.cs @@ -17,6 +17,7 @@ namespace Nethermind.Blockchain.Test.Visitors; +[Parallelizable(ParallelScope.All)] public class StartupTreeFixerTests { [Test, MaxTime(Timeout.MaxTestTime), Ignore("Not implemented")] diff --git a/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs b/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs index ea638750cea7..200341f05cdb 100644 --- a/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs @@ -25,6 +25,7 @@ public class BlockhashProvider( private readonly IBlockhashStore _blockhashStore = new BlockhashStore(worldState); private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); private Hash256[]? _hashes; + private long _prefetchVersion; public Hash256? GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec spec) { @@ -39,7 +40,7 @@ public class BlockhashProvider( } long depth = currentBlock.Number - number; - Hash256[]? hashes = _hashes; + Hash256[]? hashes = Volatile.Read(ref _hashes); return depth switch { @@ -60,7 +61,8 @@ public class BlockhashProvider( public async Task Prefetch(BlockHeader currentBlock, CancellationToken token) { - _hashes = null; + long prefetchVersion = Interlocked.Increment(ref _prefetchVersion); + Volatile.Write(ref _hashes, null); Hash256[]? hashes = await blockhashCache.Prefetch(currentBlock, token); // This leverages that branch processing is single threaded @@ -69,9 +71,9 @@ public async Task Prefetch(BlockHeader currentBlock, CancellationToken token) // This allows us to avoid await on Prefetch in BranchProcessor lock (_blockhashStore) { - if (!token.IsCancellationRequested) + if (!token.IsCancellationRequested && prefetchVersion == Interlocked.Read(ref _prefetchVersion)) { - _hashes = hashes; + Volatile.Write(ref _hashes, hashes); } } } diff --git a/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs b/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs index c8ca251cb403..cdbd1857d4fb 100644 --- a/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs @@ -21,11 +21,11 @@ public class CachedCodeInfoRepository( ICodeInfoRepository baseCodeInfoRepository, ConcurrentDictionary>? precompileCache) : ICodeInfoRepository { - private readonly FrozenDictionary _cachedPrecompile = precompileCache is null + private readonly FrozenDictionary _cachedPrecompile = precompileCache is null ? precompileProvider.GetPrecompiles() : precompileProvider.GetPrecompiles().ToFrozenDictionary(kvp => kvp.Key, kvp => CreateCachedPrecompile(kvp, precompileCache)); - public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, + public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) { if (vmSpec.IsPrecompile(codeSource) && _cachedPrecompile.TryGetValue(codeSource, out var cachedCodeInfo)) @@ -57,15 +57,15 @@ public bool TryGetDelegation(Address address, IReleaseSpec spec, return baseCodeInfoRepository.TryGetDelegation(address, spec, out delegatedAddress); } - private static PrecompileInfo CreateCachedPrecompile( - in KeyValuePair originalPrecompile, + private static CodeInfo CreateCachedPrecompile( + in KeyValuePair originalPrecompile, ConcurrentDictionary> cache) { IPrecompile precompile = originalPrecompile.Value.Precompile!; return !precompile.SupportsCaching ? originalPrecompile.Value - : new PrecompileInfo(new CachedPrecompile(originalPrecompile.Key.Value, precompile, cache)); + : new CodeInfo(new CachedPrecompile(originalPrecompile.Key.Value, precompile, cache)); } private class CachedPrecompile( diff --git a/src/Nethermind/Nethermind.Blockchain/EthereumPrecompileProvider.cs b/src/Nethermind/Nethermind.Blockchain/EthereumPrecompileProvider.cs index d793c9a65ba3..03fa5cff876d 100644 --- a/src/Nethermind/Nethermind.Blockchain/EthereumPrecompileProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/EthereumPrecompileProvider.cs @@ -13,9 +13,9 @@ namespace Nethermind.Blockchain; public class EthereumPrecompileProvider() : IPrecompileProvider { - private static FrozenDictionary Precompiles + private static FrozenDictionary Precompiles { - get => new Dictionary + get => new Dictionary { [EcRecoverPrecompile.Address] = new(EcRecoverPrecompile.Instance), [Sha256Precompile.Address] = new(Sha256Precompile.Instance), @@ -45,7 +45,7 @@ private static FrozenDictionary Precompiles }.ToFrozenDictionary(); } - public FrozenDictionary GetPrecompiles() + public FrozenDictionary GetPrecompiles() { return Precompiles; } diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs index 296e39acbb4f..2460334a9b60 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs @@ -11,6 +11,7 @@ using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; +using Nethermind.Evm.CodeAnalysis; namespace Nethermind.Blockchain.Tracing.GethStyle.Custom.JavaScript; @@ -108,7 +109,7 @@ public override void ReportAction(long gas, UInt256 value, Address from, Address public override void StartOperation(int pc, Instruction opcode, long gas, in ExecutionEnvironment env, int codeSection = 0, int functionDepth = 0) { - _log.pc = pc + env.CodeInfo.PcOffset(); + _log.pc = pc + (env.CodeInfo is EofCodeInfo eof ? eof.PcOffset() : 0); _log.op = new Log.Opcode(opcode); _log.gas = gas; _log.depth = env.GetGethTraceDepth(); diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs index 565e15a80a36..4c69cdc72712 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs @@ -57,16 +57,20 @@ public NativeCallTracer( public override GethLikeTxTrace BuildResult() { GethLikeTxTrace result = base.BuildResult(); - NativeCallTracerCallFrame firstCallFrame = _callStack[0]; Debug.Assert(_callStack.Count == 1, $"Unexpected frames on call stack, expected only master frame, found {_callStack.Count} frames."); - _callStack.RemoveAt(0); - _disposables.Add(firstCallFrame); + if (_callStack.Count is not 0) + { + NativeCallTracerCallFrame firstCallFrame = _callStack[0]; + _callStack.RemoveAt(0); + _disposables.Add(firstCallFrame); - result.TxHash = _txHash; - result.CustomTracerResult = new GethLikeCustomTrace { Value = firstCallFrame }; + result.TxHash = _txHash; + result.CustomTracerResult = new GethLikeCustomTrace { Value = firstCallFrame }; + } + result.TxHash = _txHash; _resultBuilt = true; return result; diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethTraceOptions.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethTraceOptions.cs index 6af4bd4fea7e..66605682db0b 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethTraceOptions.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethTraceOptions.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Text.Json; -using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Evm; @@ -13,33 +12,26 @@ namespace Nethermind.Blockchain.Tracing.GethStyle; public record GethTraceOptions { - [JsonPropertyName("disableMemory")] [Obsolete("Use EnableMemory instead.")] public bool DisableMemory { get => !EnableMemory; init => EnableMemory = !value; } - [JsonPropertyName("disableStorage")] public bool DisableStorage { get; init; } - [JsonPropertyName("enableMemory")] public bool EnableMemory { get; init; } - [JsonPropertyName("disableStack")] public bool DisableStack { get; init; } - [JsonPropertyName("timeout")] public string Timeout { get; init; } - [JsonPropertyName("tracer")] public string Tracer { get; init; } - [JsonPropertyName("txHash")] public Hash256? TxHash { get; init; } - [JsonPropertyName("tracerConfig")] public JsonElement? TracerConfig { get; init; } - [JsonPropertyName("stateOverrides")] public Dictionary? StateOverrides { get; init; } + public BlockOverride? BlockOverrides { get; set; } + public static GethTraceOptions Default { get; } = new(); } diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/ParityStyle/ParityLikeTxTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/ParityStyle/ParityLikeTxTracer.cs index 43d4ec399cfe..cc1b104b0a85 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/ParityStyle/ParityLikeTxTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/ParityStyle/ParityLikeTxTracer.cs @@ -9,6 +9,7 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Evm; +using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.Int256; @@ -236,7 +237,7 @@ public override void StartOperation(int pc, Instruction opcode, long gas, in Exe { ParityVmOperationTrace operationTrace = new(); _gasAlreadySetForCurrentOp = false; - operationTrace.Pc = pc + env.CodeInfo.PcOffset(); + operationTrace.Pc = pc + (env.CodeInfo is EofCodeInfo eof ? eof.PcOffset() : 0); operationTrace.Cost = gas; // skip codeSection // skip functionDepth diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockFinalizationManager.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockFinalizationManager.cs index 87d5e8a15959..76a37745f107 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockFinalizationManager.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockFinalizationManager.cs @@ -345,6 +345,7 @@ private set public void Dispose() { _branchProcessor.BlockProcessed -= OnBlockProcessed; + _branchProcessor.BlocksProcessing -= OnBlocksProcessing; } [DebuggerDisplay("Count = {Count}")] diff --git a/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs b/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs index 6c1bf551641a..9ad3590d1392 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using Nethermind.Blockchain; using Nethermind.Core; namespace Nethermind.Consensus.Clique @@ -13,23 +11,6 @@ public static bool IsInTurn(this BlockHeader header) { return header.Difficulty == Clique.DifficultyInTurn; } - - internal static Address[] ExtractSigners(BlockHeader blockHeader) - { - if (blockHeader.ExtraData is null) - { - throw new BlockchainException("Block header ExtraData cannot be null when extracting signers"); - } - - Span signersData = blockHeader.ExtraData.AsSpan(Clique.ExtraVanityLength, (blockHeader.ExtraData.Length - Clique.ExtraSealLength)); - Address[] signers = new Address[signersData.Length / Address.Size]; - for (int i = 0; i < signers.Length; i++) - { - signers[i] = new Address(signersData.Slice(i * 20, 20).ToArray()); - } - - return signers; - } } internal static class BlockExtensions diff --git a/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs b/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs index a838bce57d6c..f93dcb2af89a 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs @@ -142,4 +142,179 @@ public async Task Test_task_that_is_scheduled_during_block_processing_but_deadli wasCancelled.Should().BeTrue(); } + + [Test] + public async Task Test_expired_tasks_are_drained_during_block_processing() + { + int capacity = 16; + await using BackgroundTaskScheduler scheduler = new(_branchProcessor, _chainHeadInfo, 1, capacity, LimboLogs.Instance); + + // Start block processing — signal is reset, token cancelled + _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); + + int cancelledCount = 0; + for (int i = 0; i < capacity; i++) + { + scheduler.TryScheduleTask(1, (_, token) => + { + if (token.IsCancellationRequested) + { + Interlocked.Increment(ref cancelledCount); + } + return Task.CompletedTask; + }, TimeSpan.FromMilliseconds(1)); + } + + // Expired tasks should be drained even while block processing is in progress + Assert.That(() => cancelledCount, Is.EqualTo(capacity).After(2000, 10)); + + _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); + } + + [Test] + public async Task Test_queue_accepts_new_tasks_after_expired_tasks_drain_during_block_processing() + { + int capacity = 16; + await using BackgroundTaskScheduler scheduler = new(_branchProcessor, _chainHeadInfo, 1, capacity, LimboLogs.Instance); + + // Start block processing — signal is reset, token cancelled + _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); + + // Fill the queue with short-lived tasks + for (int i = 0; i < capacity; i++) + { + scheduler.TryScheduleTask(1, (_, _) => Task.CompletedTask, TimeSpan.FromMilliseconds(1)).Should().BeTrue(); + } + + // Wait for deadlines to pass and expired tasks to be drained + await Task.Delay(200); + + // New tasks should be accepted because expired tasks freed up queue space + for (int i = 0; i < capacity; i++) + { + bool accepted = scheduler.TryScheduleTask(1, (_, _) => Task.CompletedTask, TimeSpan.FromMilliseconds(1)); + accepted.Should().BeTrue($"Task {i} should be accepted after expired tasks were drained"); + } + + _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); + } + + [Test] + public async Task Test_high_capacity_queue_survives_repeated_block_processing_cycles() + { + int capacity = 1024; + int concurrency = 2; + await using BackgroundTaskScheduler scheduler = new(_branchProcessor, _chainHeadInfo, concurrency, capacity, LimboLogs.Instance); + + int executedCount = 0; + int cancelledCount = 0; + + // --- Phase 1: Fill the queue to capacity during block processing --- + _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); + + for (int i = 0; i < capacity; i++) + { + bool accepted = scheduler.TryScheduleTask(1, (_, token) => + { + if (token.IsCancellationRequested) + Interlocked.Increment(ref cancelledCount); + else + Interlocked.Increment(ref executedCount); + return Task.CompletedTask; + }, TimeSpan.FromMilliseconds(10)); + accepted.Should().BeTrue($"Phase 1: task {i} should be accepted up to capacity"); + } + + // Wait for deadlines to expire and tasks to drain + Assert.That( + () => Volatile.Read(ref cancelledCount), + Is.EqualTo(capacity).After(5000, 10), + "all tasks should be drained with cancelled tokens during block processing"); + + // --- Phase 2: End block processing, verify queue accepts tasks and runs them normally --- + _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); + + Interlocked.Exchange(ref executedCount, 0); + + int phase2Count = capacity / 2; + for (int i = 0; i < phase2Count; i++) + { + bool accepted = scheduler.TryScheduleTask(1, (_, _) => + { + Interlocked.Increment(ref executedCount); + return Task.CompletedTask; + }); + accepted.Should().BeTrue($"Phase 2: task {i} should be accepted after queue drained"); + } + + Assert.That( + () => Volatile.Read(ref executedCount), + Is.EqualTo(phase2Count).After(5000, 10), + "all phase 2 tasks should execute normally after block processing ends"); + + // --- Phase 3: Another block processing cycle with mixed short and long timeouts --- + _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); + + int phase3CancelledCount = 0; + int phase3ExecutedCount = 0; + + // Short-lived tasks (will expire during block processing) + int shortLivedCount = capacity / 2; + for (int i = 0; i < shortLivedCount; i++) + { + scheduler.TryScheduleTask(1, (_, token) => + { + if (token.IsCancellationRequested) + Interlocked.Increment(ref phase3CancelledCount); + return Task.CompletedTask; + }, TimeSpan.FromMilliseconds(5)).Should().BeTrue($"Phase 3: short-lived task {i} should be accepted"); + } + + // Long-lived tasks (will survive until block processing ends) + int longLivedCount = capacity / 4; + for (int i = 0; i < longLivedCount; i++) + { + scheduler.TryScheduleTask(1, (_, token) => + { + if (!token.IsCancellationRequested) + Interlocked.Increment(ref phase3ExecutedCount); + return Task.CompletedTask; + }, TimeSpan.FromSeconds(30)).Should().BeTrue($"Phase 3: long-lived task {i} should be accepted"); + } + + // Wait for short-lived tasks to expire and drain + Assert.That( + () => Volatile.Read(ref phase3CancelledCount), + Is.EqualTo(shortLivedCount).After(5000, 10), + "short-lived tasks should drain with cancelled tokens during block processing"); + + // Long-lived tasks should not have executed yet (still waiting for block processing to end) + Volatile.Read(ref phase3ExecutedCount).Should().Be(0, + "long-lived tasks should wait during block processing"); + + // End block processing — long-lived tasks should now execute + _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); + + Assert.That( + () => Volatile.Read(ref phase3ExecutedCount), + Is.EqualTo(longLivedCount).After(5000, 10), + "long-lived tasks should execute after block processing ends"); + + // --- Phase 4: Verify queue is fully operational with one more fill-and-drain --- + Interlocked.Exchange(ref executedCount, 0); + + for (int i = 0; i < capacity; i++) + { + scheduler.TryScheduleTask(1, (_, _) => + { + Interlocked.Increment(ref executedCount); + return Task.CompletedTask; + }).Should().BeTrue($"Phase 4: task {i} should be accepted in fully recovered queue"); + } + + Assert.That( + () => Volatile.Read(ref executedCount), + Is.EqualTo(capacity).After(5000, 10), + "all tasks in the final phase should execute successfully"); + } } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs index 80340ae16a8b..fb7ab548d42f 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs @@ -57,7 +57,7 @@ public Task PreWarmCaches(Block suggestedBlock, BlockHeader? parent, IReleaseSpe if (preBlockCaches is not null) { CacheType result = preBlockCaches.ClearCaches(); - result |= nodeStorageCache.ClearCaches() ? CacheType.Rlp : CacheType.None; + nodeStorageCache.ClearCaches(); nodeStorageCache.Enabled = true; if (result != default) { diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs index 816517ff416b..77ae74dddf2a 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs @@ -57,14 +57,16 @@ public sealed class BlockchainProcessor : IBlockchainProcessor, IBlockProcessing new BoundedChannelOptions(MaxProcessingQueueSize) { // Optimize for single reader concurrency - SingleReader = true + SingleReader = true, + // If queues are empty we want the block processing to continue on NewPayload thread and inherit its priority + AllowSynchronousContinuations = true, }); private bool _recoveryComplete = false; private int _queueCount; private bool _disposed; - private readonly ProcessingStats _stats; + private readonly IProcessingStats _stats; private CancellationTokenSource? _loopCancellationSource; private Task? _recoveryTask; @@ -89,13 +91,15 @@ public sealed class BlockchainProcessor : IBlockchainProcessor, IBlockProcessing /// /// /// + /// public BlockchainProcessor( IBlockTree blockTree, IBranchProcessor branchProcessor, IBlockPreprocessorStep recoveryStep, IStateReader stateReader, ILogManager logManager, - Options options) + Options options, + IProcessingStats processingStats) { _logger = logManager.GetClassLogger(); _blockTree = blockTree; @@ -104,7 +108,7 @@ public BlockchainProcessor( _stateReader = stateReader; _options = options; - _stats = new ProcessingStats(stateReader, logManager.GetClassLogger()); + _stats = processingStats; _loopCancellationSource = new CancellationTokenSource(); _stats.NewProcessingStatistics += OnNewProcessingStatistics; } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessingScope.cs b/src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessingScope.cs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessorSource.cs b/src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessorSource.cs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IProcessingStats.cs b/src/Nethermind/Nethermind.Consensus/Processing/IProcessingStats.cs new file mode 100644 index 000000000000..7d7b65723e69 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Processing/IProcessingStats.cs @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; + +namespace Nethermind.Consensus.Processing; + +/// +/// Interface for processing statistics tracking during block processing. +/// +public interface IProcessingStats +{ + /// + /// Event fired when new processing statistics are available. + /// + event EventHandler? NewProcessingStatistics; + + /// + /// Start the statistics timer. + /// + void Start(); + + /// + /// Capture the starting values of metrics before processing begins. + /// + void CaptureStartStats(); + + /// + /// Update statistics after a block has been processed. + /// + /// The processed block. + /// The parent block header. + /// Processing time in microseconds. + void UpdateStats(Block? block, BlockHeader? baseBlock, long blockProcessingTimeInMicros); +} diff --git a/src/Nethermind/Nethermind.Consensus/Processing/OverridableTxProcessingScope.cs b/src/Nethermind/Nethermind.Consensus/Processing/OverridableTxProcessingScope.cs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs index f2246ed3e733..0cd19058da01 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs @@ -19,27 +19,27 @@ namespace Nethermind.Consensus.Processing { public class BlockStatistics { - public long BlockCount { get; internal set; } - public long BlockFrom { get; internal set; } - public long BlockTo { get; internal set; } - public double ProcessingMs { get; internal set; } - public double SlotMs { get; internal set; } + public long BlockCount { get; set; } + public long BlockFrom { get; set; } + public long BlockTo { get; set; } + public double ProcessingMs { get; set; } + public double SlotMs { get; set; } [JsonPropertyName("mgasPerSecond")] - public double MGasPerSecond { get; internal set; } - public float MinGas { get; internal set; } - public float MedianGas { get; internal set; } - public float AveGas { get; internal set; } - public float MaxGas { get; internal set; } - public long GasLimit { get; internal set; } + public double MGasPerSecond { get; set; } + public float MinGas { get; set; } + public float MedianGas { get; set; } + public float AveGas { get; set; } + public float MaxGas { get; set; } + public long GasLimit { get; set; } } //TODO Consult on disabling of such metrics from configuration - internal class ProcessingStats + public class ProcessingStats : IProcessingStats { private static readonly DefaultObjectPool _dataPool = new(new BlockDataPolicy(), 16); private readonly Action _executeFromThreadPool; public event EventHandler? NewProcessingStatistics; - private readonly IStateReader _stateReader; - private readonly ILogger _logger; + protected readonly IStateReader _stateReader; + protected readonly ILogger _logger; private readonly Stopwatch _runStopwatch = new(); private bool _showBlobs; @@ -69,12 +69,12 @@ internal class ProcessingStats private long _contractsAnalyzed; private long _cachedContractsUsed; - public ProcessingStats(IStateReader stateReader, ILogger logger) + public ProcessingStats(IStateReader stateReader, ILogManager logManager) { _executeFromThreadPool = ExecuteFromThreadPool; _stateReader = stateReader; - _logger = logger; + _logger = logManager.GetClassLogger(); // the line below just to avoid compilation errors if (_logger.IsTrace) _logger.Trace($"Processing Stats in debug mode?: {_logger.IsDebug}"); @@ -151,7 +151,7 @@ void ExecuteFromThreadPool(BlockData data) } } - private void GenerateReport(BlockData data) + protected virtual void GenerateReport(BlockData data) { const long weiToEth = 1_000_000_000_000_000_000; const string resetColor = "\u001b[37m"; @@ -443,7 +443,7 @@ public bool Return(BlockData data) } } - private class BlockData + protected class BlockData { public Block Block; public BlockHeader? BaseBlock; diff --git a/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs b/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs index c138491985f4..27ed452d518a 100644 --- a/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs +++ b/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs @@ -34,7 +34,6 @@ public class BackgroundTaskScheduler : IBackgroundTaskScheduler, IAsyncDisposabl private readonly CancellationTokenSource _mainCancellationTokenSource; private readonly Channel _taskQueue; private readonly BelowNormalPriorityTaskScheduler _scheduler; - private readonly ManualResetEventSlim _restartQueueSignal; private readonly Task[] _tasksExecutors; private readonly ILogger _logger; private readonly IBranchProcessor _branchProcessor; @@ -43,6 +42,7 @@ public class BackgroundTaskScheduler : IBackgroundTaskScheduler, IAsyncDisposabl private long _queueCount; private CancellationTokenSource _blockProcessorCancellationTokenSource; + private volatile TaskCompletionSource? _blockProcessingDoneSignal; private bool _disposed = false; public BackgroundTaskScheduler(IBranchProcessor branchProcessor, IChainHeadInfoProvider headInfo, int concurrency, int capacity, ILogManager logManager) @@ -65,7 +65,6 @@ public BackgroundTaskScheduler(IBranchProcessor branchProcessor, IChainHeadInfoP _logger = logManager.GetClassLogger(); _branchProcessor = branchProcessor; _headInfo = headInfo; - _restartQueueSignal = new ManualResetEventSlim(initialState: true); _capacity = capacity; _branchProcessor.BlocksProcessing += BranchProcessorOnBranchesProcessing; @@ -74,7 +73,6 @@ public BackgroundTaskScheduler(IBranchProcessor branchProcessor, IChainHeadInfoP // TaskScheduler to run tasks at BelowNormal priority _scheduler = new BelowNormalPriorityTaskScheduler( concurrency, - _restartQueueSignal, logManager, _mainCancellationTokenSource.Token); @@ -88,8 +86,9 @@ private void BranchProcessorOnBranchesProcessing(object? sender, BlocksProcessin // as there are potentially no gaps between blocks if (!_headInfo.IsSyncing) { - // Reset the background queue processing signal, causing it to wait - _restartQueueSignal.Reset(); + // Signal that block processing is in progress so the Throttle path in StartChannel + // can async-wait instead of busy-polling + _blockProcessingDoneSignal = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); // On block processing, we cancel the block process cts, causing the current task to get canceled. _blockProcessorCancellationTokenSource.Cancel(); } @@ -102,8 +101,8 @@ private void BranchProcessorOnBranchProcessed(object? sender, BlockProcessedEven ref _blockProcessorCancellationTokenSource, new CancellationTokenSource()); - // We also set a queue signal causing it to continue processing the task. - _restartQueueSignal.Set(); + // Signal that block processing is done so the Throttle path can resume + Interlocked.Exchange(ref _blockProcessingDoneSignal, null)?.TrySetResult(); } private async Task StartChannel() @@ -157,7 +156,16 @@ private async Task StartChannel() continue; Throttle: - await Task.Delay(millisecondsDelay: 1); + // Wait for block processing to complete, with periodic wake-ups to drain newly expired tasks + TaskCompletionSource? signal = _blockProcessingDoneSignal; + if (signal is not null) + { + await Task.WhenAny(signal.Task, Task.Delay(millisecondsDelay: 1)); + } + else + { + await Task.Delay(millisecondsDelay: 1); + } } } @@ -250,17 +258,15 @@ private sealed class BelowNormalPriorityTaskScheduler : TaskScheduler, IDisposab { private readonly BlockingCollection _tasks = []; private readonly Thread[] workerThreads; - private readonly ManualResetEventSlim _restartQueueSignal; private readonly int _maxDegreeOfParallelism; private readonly ILogger _logger; private readonly CancellationToken _cancellationToken; - public BelowNormalPriorityTaskScheduler(int maxDegreeOfParallelism, ManualResetEventSlim restartQueueSignal, ILogManager logManager, CancellationToken cancellationToken) + public BelowNormalPriorityTaskScheduler(int maxDegreeOfParallelism, ILogManager logManager, CancellationToken cancellationToken) { ArgumentOutOfRangeException.ThrowIfLessThan(maxDegreeOfParallelism, 1); _logger = logManager.GetClassLogger(); - _restartQueueSignal = restartQueueSignal; _maxDegreeOfParallelism = maxDegreeOfParallelism; _cancellationToken = cancellationToken; workerThreads = [.. Enumerable.Range(0, maxDegreeOfParallelism) @@ -283,8 +289,6 @@ private void ProcessBackgroundTasks(object _) { foreach (Task task in _tasks.GetConsumingEnumerable(_cancellationToken)) { - // Wait if processing blocks - _restartQueueSignal.Wait(_cancellationToken); try { TryExecuteTask(task); diff --git a/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs b/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs index 83833ef5631e..b2bce702e101 100644 --- a/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs +++ b/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs @@ -179,12 +179,14 @@ public IEnumerable TraceBadBlockToFile(Hash256 blockHash, GethTraceOptio // // Wild stuff! BlockHeader baseBlockHeader = block.Header; + if ((processingOptions & ProcessingOptions.ForceSameBlock) == 0) { baseBlockHeader = FindParent(block); } - using var scope = blockProcessingEnv.BuildAndOverride(baseBlockHeader, options.StateOverrides); + options.BlockOverrides?.ApplyOverrides(block.Header); + using Scope scope = blockProcessingEnv.BuildAndOverride(baseBlockHeader, options.StateOverrides); IBlockTracer tracer = CreateOptionsTracer(block.Header, options with { TxHash = txHash }, scope.Component.WorldState, specProvider); try diff --git a/src/Nethermind/Nethermind.Core.Test/BytesTests.cs b/src/Nethermind/Nethermind.Core.Test/BytesTests.cs index f3371d834c1c..ed7ccc0c2ac4 100644 --- a/src/Nethermind/Nethermind.Core.Test/BytesTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/BytesTests.cs @@ -19,11 +19,11 @@ public class BytesTests { [TestCase("0x", "0x", 0)] [TestCase(null, null, 0)] - [TestCase(null, "0x", 1)] - [TestCase("0x", null, -1)] + [TestCase(null, "0x", -1)] + [TestCase("0x", null, 1)] [TestCase("0x01", "0x01", 0)] - [TestCase("0x01", "0x0102", 1)] - [TestCase("0x0102", "0x01", -1)] + [TestCase("0x01", "0x0102", -1)] + [TestCase("0x0102", "0x01", 1)] public void Compares_bytes_properly(string? hexString1, string? hexString2, int expectedResult) { IComparer comparer = Bytes.Comparer; @@ -467,5 +467,139 @@ public void NullableComparison() { Bytes.NullableEqualityComparer.Equals(null, null).Should().BeTrue(); } + + [Test] + public void FastHash_EmptyInput_ReturnsZero() + { + ReadOnlySpan empty = ReadOnlySpan.Empty; + empty.FastHash().Should().Be(0); + } + + [Test] + public void FastHash_SameInput_ReturnsSameHash() + { + byte[] input = new byte[100]; + TestContext.CurrentContext.Random.NextBytes(input); + + int hash1 = ((ReadOnlySpan)input).FastHash(); + int hash2 = ((ReadOnlySpan)input).FastHash(); + + hash1.Should().Be(hash2); + } + + [Test] + public void FastHash_DifferentInput_ReturnsDifferentHash() + { + byte[] input1 = new byte[100]; + byte[] input2 = new byte[100]; + TestContext.CurrentContext.Random.NextBytes(input1); + Array.Copy(input1, input2, input1.Length); + input2[50] ^= 0xFF; // Flip bits at position 50 + + int hash1 = ((ReadOnlySpan)input1).FastHash(); + int hash2 = ((ReadOnlySpan)input2).FastHash(); + + hash1.Should().NotBe(hash2); + } + + // Test cases for the fold-back bug fix: remaining in [49-63] after 64-byte initial load + // For len=113 to 127, remaining = len-64 = 49 to 63, which requires the last64 fold-back + [TestCase(113)] // remaining=49, boundary case for last64 + [TestCase(120)] // remaining=56, middle of the gap range + [TestCase(127)] // remaining=63, upper boundary + [TestCase(65)] // remaining=1, lower boundary for >64 path + [TestCase(80)] // remaining=16 + [TestCase(96)] // remaining=32 + [TestCase(112)] // remaining=48, boundary where last64 is NOT needed + public void FastHash_AllBytesAreHashed_FoldBackCoverage(int length) + { + byte[] input = new byte[length]; + TestContext.CurrentContext.Random.NextBytes(input); + + int originalHash = ((ReadOnlySpan)input).FastHash(); + + // Verify that changing any byte changes the hash + // This catches the gap bug where bytes[64-71] weren't being hashed + for (int i = 0; i < length; i++) + { + byte[] modified = (byte[])input.Clone(); + modified[i] ^= 0xFF; + + int modifiedHash = ((ReadOnlySpan)modified).FastHash(); + modifiedHash.Should().NotBe(originalHash, $"Changing byte at index {i} should change the hash for length {length}"); + } + } + + // Specifically test the gap range that was buggy: bytes[64-71] for len=120 + [Test] + public void FastHash_GapBytesAreHashed_Len120() + { + byte[] input = new byte[120]; + TestContext.CurrentContext.Random.NextBytes(input); + + int originalHash = ((ReadOnlySpan)input).FastHash(); + + // The bug was that bytes[64-71] weren't hashed for len=120 + // Test each byte in the gap + for (int i = 64; i < 72; i++) + { + byte[] modified = (byte[])input.Clone(); + modified[i] ^= 0xFF; + + int modifiedHash = ((ReadOnlySpan)modified).FastHash(); + modifiedHash.Should().NotBe(originalHash, $"Changing byte at index {i} (in gap range) should change the hash"); + } + } + + // Test medium-large case (33-64 bytes) with overlap to verify it works + [TestCase(50)] // Tests overlap in medium-large path + public void FastHash_MediumLarge_AllBytesContribute(int length) + { + byte[] input = new byte[length]; + TestContext.CurrentContext.Random.NextBytes(input); + + int originalHash = ((ReadOnlySpan)input).FastHash(); + + // Test ALL bytes to verify overlap handling works + for (int i = 0; i < length; i++) + { + byte[] modified = (byte[])input.Clone(); + modified[i] ^= 0xFF; + + int modifiedHash = ((ReadOnlySpan)modified).FastHash(); + modifiedHash.Should().NotBe(originalHash, $"Changing byte at index {i} should change the hash for length {length}"); + } + } + + [TestCase(1)] + [TestCase(7)] + [TestCase(8)] + [TestCase(15)] + [TestCase(16)] + [TestCase(31)] + [TestCase(32)] + [TestCase(33)] + [TestCase(64)] + [TestCase(128)] + [TestCase(256)] + [TestCase(500)] + public void FastHash_VariousLengths_AllBytesContribute(int length) + { + byte[] input = new byte[length]; + TestContext.CurrentContext.Random.NextBytes(input); + + int originalHash = ((ReadOnlySpan)input).FastHash(); + + // Test first, middle, and last bytes to ensure all contribute + int[] indicesToTest = [0, length / 2, length - 1]; + foreach (int i in indicesToTest) + { + byte[] modified = (byte[])input.Clone(); + modified[i] ^= 0xFF; + + int modifiedHash = ((ReadOnlySpan)modified).FastHash(); + modifiedHash.Should().NotBe(originalHash, $"Changing byte at index {i} should change the hash for length {length}"); + } + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Collections/SeqlockCacheTests.cs b/src/Nethermind/Nethermind.Core.Test/Collections/SeqlockCacheTests.cs new file mode 100644 index 000000000000..ee076970d44b --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Collections/SeqlockCacheTests.cs @@ -0,0 +1,428 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Nethermind.Core.Collections; +using Nethermind.Int256; +using NUnit.Framework; + +namespace Nethermind.Core.Test.Collections; + +public class SeqlockCacheTests +{ + private static StorageCell CreateKey(int seed) + { + byte[] addressBytes = new byte[20]; + new Random(seed).NextBytes(addressBytes); + return new StorageCell(new Address(addressBytes), new UInt256((ulong)seed)); + } + + private static byte[] CreateValue(int seed) + { + byte[] value = new byte[32]; + new Random(seed).NextBytes(value); + return value; + } + + [Test] + public void New_cache_returns_miss() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + + bool found = cache.TryGetValue(in key, out byte[]? value); + + found.Should().BeFalse(); + value.Should().BeNull(); + } + + [Test] + public void Set_then_get_returns_value() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] expected = CreateValue(1); + + cache.Set(in key, expected); + bool found = cache.TryGetValue(in key, out byte[]? value); + + found.Should().BeTrue(); + value.Should().BeSameAs(expected); + } + + [Test] + public void Set_overwrites_existing_value() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] first = CreateValue(1); + byte[] second = CreateValue(2); + + cache.Set(in key, first); + cache.Set(in key, second); + bool found = cache.TryGetValue(in key, out byte[]? value); + + found.Should().BeTrue(); + value.Should().BeSameAs(second); + } + + [Test] + public void Set_with_same_value_is_noop() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] expected = CreateValue(1); + + cache.Set(in key, expected); + cache.Set(in key, expected); // Same reference - should be fast-path no-op + bool found = cache.TryGetValue(in key, out byte[]? value); + + found.Should().BeTrue(); + value.Should().BeSameAs(expected); + } + + [Test] + public void Null_value_can_be_stored_and_retrieved() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + + cache.Set(in key, null); + bool found = cache.TryGetValue(in key, out byte[]? value); + + found.Should().BeTrue(); + value.Should().BeNull(); + } + + [Test] + public void GetOrAdd_returns_existing_value() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] expected = CreateValue(1); + + cache.Set(in key, expected); + byte[]? result = cache.GetOrAdd(in key, static (in StorageCell _) => new byte[32]); + + result.Should().BeSameAs(expected); + } + + [Test] + public void GetOrAdd_calls_factory_on_miss() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] factoryResult = CreateValue(1); + + byte[]? result = cache.GetOrAdd(in key, (in StorageCell _) => factoryResult); + + result.Should().BeSameAs(factoryResult); + + // Value should now be cached + bool found = cache.TryGetValue(in key, out byte[]? cached); + found.Should().BeTrue(); + cached.Should().BeSameAs(factoryResult); + } + + [Test] + public void GetOrAdd_with_func_returns_existing_value() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] expected = CreateValue(1); + + cache.Set(in key, expected); + byte[]? result = cache.GetOrAdd(in key, static (in _) => new byte[32]); + + result.Should().BeSameAs(expected); + } + + [Test] + public void GetOrAdd_with_func_calls_factory_on_miss() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] factoryResult = CreateValue(1); + + byte[]? result = cache.GetOrAdd(in key, (in _) => factoryResult); + + result.Should().BeSameAs(factoryResult); + } + + [Test] + public void Clear_invalidates_all_entries() + { + SeqlockCache cache = new(); + StorageCell key1 = CreateKey(1); + StorageCell key2 = CreateKey(2); + + cache.Set(in key1, CreateValue(1)); + cache.Set(in key2, CreateValue(2)); + + cache.Clear(); + + cache.TryGetValue(in key1, out _).Should().BeFalse(); + cache.TryGetValue(in key2, out _).Should().BeFalse(); + } + + [Test] + public void Clear_allows_new_entries() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] beforeClear = CreateValue(1); + byte[] afterClear = CreateValue(2); + + cache.Set(in key, beforeClear); + cache.Clear(); + cache.Set(in key, afterClear); + + bool found = cache.TryGetValue(in key, out byte[]? value); + found.Should().BeTrue(); + value.Should().BeSameAs(afterClear); + } + + [Test] + public void Multiple_clears_work() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + + for (int i = 0; i < 100; i++) + { + byte[] value = CreateValue(i); + cache.Set(in key, value); + cache.TryGetValue(in key, out byte[]? retrieved).Should().BeTrue(); + retrieved.Should().BeSameAs(value); + cache.Clear(); + cache.TryGetValue(in key, out _).Should().BeFalse(); + } + } + + [Test] + public void Different_keys_can_be_stored() + { + SeqlockCache cache = new(); + const int count = 100; + + StorageCell[] keys = new StorageCell[count]; + byte[][] values = new byte[count][]; + + for (int i = 0; i < count; i++) + { + keys[i] = CreateKey(i); + values[i] = CreateValue(i); + cache.Set(in keys[i], values[i]); + } + + // Note: This is a direct-mapped cache, so some entries may be evicted + // due to hash collisions. We just verify that at least some survive. + int hits = 0; + for (int i = 0; i < count; i++) + { + if (cache.TryGetValue(in keys[i], out byte[]? value) && ReferenceEquals(value, values[i])) + { + hits++; + } + } + + hits.Should().BeGreaterThan(0, "at least some entries should survive"); + } + + [Test] + public void Concurrent_reads_are_safe() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] expected = CreateValue(1); + cache.Set(in key, expected); + + const int threadCount = 8; + const int iterations = 10000; + int successCount = 0; + + Parallel.For(0, threadCount, _ => + { + for (int i = 0; i < iterations; i++) + { + if (cache.TryGetValue(in key, out byte[]? value) && ReferenceEquals(value, expected)) + { + Interlocked.Increment(ref successCount); + } + } + }); + + successCount.Should().Be(threadCount * iterations); + } + + [Test] + public void Concurrent_writes_do_not_corrupt() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + + const int threadCount = 8; + const int iterations = 1000; + byte[][] values = new byte[threadCount][]; + for (int i = 0; i < threadCount; i++) + { + values[i] = CreateValue(i); + } + + Parallel.For(0, threadCount, t => + { + for (int i = 0; i < iterations; i++) + { + cache.Set(in key, values[t]); + } + }); + + // After concurrent writes, the cache should contain one of the values + bool found = cache.TryGetValue(in key, out byte[]? result); + if (found) + { + // Value should be one of the values we wrote + bool isValid = false; + for (int i = 0; i < threadCount; i++) + { + if (ReferenceEquals(result, values[i])) + { + isValid = true; + break; + } + } + isValid.Should().BeTrue("cached value should be one of the written values"); + } + } + + [Test] + public void Concurrent_read_write_is_safe() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] value1 = CreateValue(1); + byte[] value2 = CreateValue(2); + + const int iterations = 10000; + bool stop = false; + + // Writer thread + Task writer = Task.Run(() => + { + for (int i = 0; i < iterations && !stop; i++) + { + cache.Set(in key, i % 2 == 0 ? value1 : value2); + } + }); + + // Reader thread + int validReads = 0; + int misses = 0; + Task reader = Task.Run(() => + { + for (int i = 0; i < iterations; i++) + { + if (cache.TryGetValue(in key, out byte[]? value)) + { + // Value should be either value1 or value2 + if (ReferenceEquals(value, value1) || ReferenceEquals(value, value2)) + { + Interlocked.Increment(ref validReads); + } + } + else + { + Interlocked.Increment(ref misses); + } + } + }); + + Task.WaitAll(writer, reader); + stop = true; + + // All reads should have returned valid values (or miss due to concurrent write) + (validReads + misses).Should().Be(iterations); + } + + [Test] + public void AddressAsKey_works_with_cache() + { + SeqlockCache cache = new(); + Address address = new Address("0x1234567890123456789012345678901234567890"); + AddressAsKey key = address; + Account account = new Account(100, 1); + + cache.Set(in key, account); + bool found = cache.TryGetValue(in key, out Account? result); + + found.Should().BeTrue(); + result.Should().BeSameAs(account); + } + + [Test] + public void Concurrent_set_same_value_fast_path_is_safe() + { + // Tests the fast-path optimization where Set skips write if value matches. + // This exercises the seqlock protocol in the fast-path to avoid torn reads. + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] value = CreateValue(1); + + cache.Set(in key, value); + + const int threadCount = 8; + const int iterations = 10000; + + // Multiple threads all trying to set the same key to the same value + // This hammers the fast-path check + Parallel.For(0, threadCount, _ => + { + for (int i = 0; i < iterations; i++) + { + cache.Set(in key, value); + } + }); + + // Value should still be retrievable and correct + bool found = cache.TryGetValue(in key, out byte[]? result); + found.Should().BeTrue(); + result.Should().BeSameAs(value); + } + + [Test] + public void Concurrent_set_alternating_values_is_safe() + { + // Tests concurrent writes with different values interleaved with same-value writes. + // Exercises both the fast-path (same value) and slow-path (different value). + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[][] values = new byte[4][]; + for (int i = 0; i < values.Length; i++) + { + values[i] = CreateValue(i); + } + + const int threadCount = 8; + const int iterations = 5000; + + Parallel.For(0, threadCount, t => + { + for (int i = 0; i < iterations; i++) + { + // Each thread cycles through values, creating both fast-path and slow-path scenarios + cache.Set(in key, values[(t + i) % values.Length]); + } + }); + + // After all writes, cache should contain one of the valid values + bool found = cache.TryGetValue(in key, out byte[]? result); + if (found) + { + bool isValid = Array.Exists(values, v => ReferenceEquals(v, result)); + isValid.Should().BeTrue("cached value should be one of the written values"); + } + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs index bd8b19db914f..2f4575e7c705 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; @@ -16,9 +18,9 @@ namespace Nethermind.Core.Test.Encoding; public class BlockDecoderTests { - private readonly Block[] _scenarios; + private static readonly Block[] _scenarios = BuildScenarios(); - public BlockDecoderTests() + private static Block[] BuildScenarios() { var transactions = new Transaction[100]; for (int i = 0; i < transactions.Length; i++) @@ -40,8 +42,8 @@ public BlockDecoderTests() .TestObject; } - _scenarios = new[] - { + return + [ Build.A.Block.WithNumber(1).TestObject, Build.A.Block .WithNumber(1) @@ -95,9 +97,14 @@ public BlockDecoderTests() .WithExcessBlobGas(ulong.MaxValue) .WithMixHash(Keccak.EmptyTreeHash) .TestObject - }; + ]; } + private static IEnumerable BlockScenarios() => _scenarios; + + private static IEnumerable BlockScenariosWithTxs() => + _scenarios.Where(static b => b.Transactions.Length > 0); + [Test] public void Can_do_roundtrip_null([Values(true, false)] bool valueDecoder) { @@ -122,17 +129,16 @@ public void Can_do_roundtrip_regression([Values(true, false)] bool valueDecoder) } [Test] - public void Can_do_roundtrip_scenarios([Values(true, false)] bool valueDecoder) + public void Can_do_roundtrip_scenarios( + [ValueSource(nameof(BlockScenarios))] Block block, + [Values(true, false)] bool valueDecoder) { BlockDecoder decoder = new(); - foreach (Block block in _scenarios) - { - Rlp encoded = decoder.Encode(block); - Rlp.ValueDecoderContext valueDecoderContext = new(encoded.Bytes); - Block? decoded = valueDecoder ? decoder.Decode(ref valueDecoderContext) : decoder.Decode(new RlpStream(encoded.Bytes)); - Rlp encoded2 = decoder.Encode(decoded); - Assert.That(encoded2.Bytes.ToHexString(), Is.EqualTo(encoded.Bytes.ToHexString())); - } + Rlp encoded = decoder.Encode(block); + Rlp.ValueDecoderContext valueDecoderContext = new(encoded.Bytes); + Block? decoded = valueDecoder ? decoder.Decode(ref valueDecoderContext) : decoder.Decode(new RlpStream(encoded.Bytes)); + Rlp encoded2 = decoder.Encode(decoded); + Assert.That(encoded2.Bytes.ToHexString(), Is.EqualTo(encoded.Bytes.ToHexString())); } [TestCase("0xf902cef9025ba055870e2f3ef77a9e6163ee5c005dc51d648a2eead382b9044b1a5ad2ee69b0c6a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0b77e3b74c6c8af85408677375183385a2e55446bd071bf193a4958f7417dc8fba056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800188016345785d8a0000800c80a0000000000000000000000000000000000000000000000000000000000000000088000000000000000007a0cc3b10b54dc4e97c01f1df20e8b95874cd5fe83bf6eae64935a16cb08db85fa98080a00000000000000000000000000000000000000000000000000000000000000000a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855c0c0f86ce08080946389e7f33ce3b1e94e4325ef02829cd12297ef7188ffffffffffffffffd80180948a0a19589531694250d570040a0c4b74576919b801d8028094000000000000000000000000000000000000100080d8038094a94f5374fce5edbc8e2a8697c15331677e6ebf0b80")] @@ -143,6 +149,60 @@ public void Write_rlp_of_blocks_to_file(string rlp) File.WriteAllBytes("chains\\block1.rlp".GetApplicationResourcePath(), Bytes.FromHexString(rlp)); } + [Test] + public void Encode_with_pre_encoded_transactions_produces_same_rlp( + [ValueSource(nameof(BlockScenariosWithTxs))] Block block) + { + byte[][] encodedTxs = new byte[block.Transactions.Length][]; + for (int i = 0; i < block.Transactions.Length; i++) + { + encodedTxs[i] = Rlp.Encode(block.Transactions[i], RlpBehaviors.SkipTypedWrapping).Bytes; + } + + BlockDecoder decoder = new(); + Rlp standard = decoder.Encode(block); + + Block blockWithEncoded = new(block.Header, block.Body) { EncodedTransactions = encodedTxs }; + Rlp fast = decoder.Encode(blockWithEncoded); + + Assert.That(fast.Bytes.ToHexString(), Is.EqualTo(standard.Bytes.ToHexString())); + } + + [Test] + public void Encode_with_pre_encoded_typed_transactions_produces_same_rlp() + { + Transaction[] transactions = + [ + Build.A.Transaction.WithNonce(1).WithType(TxType.Legacy).Signed().TestObject, + Build.A.Transaction.WithNonce(2).WithType(TxType.AccessList).Signed().TestObject, + Build.A.Transaction.WithNonce(3).WithType(TxType.EIP1559).Signed().TestObject, + ]; + + Block block = Build.A.Block + .WithNumber(1) + .WithBaseFeePerGas(1) + .WithTransactions(transactions) + .WithWithdrawals(2) + .WithBlobGasUsed(0) + .WithExcessBlobGas(0) + .WithMixHash(Keccak.EmptyTreeHash) + .TestObject; + + byte[][] encodedTxs = new byte[transactions.Length][]; + for (int i = 0; i < transactions.Length; i++) + { + encodedTxs[i] = Rlp.Encode(transactions[i], RlpBehaviors.SkipTypedWrapping).Bytes; + } + + BlockDecoder decoder = new(); + Rlp standard = decoder.Encode(block); + + Block blockWithEncoded = new(block.Header, block.Body) { EncodedTransactions = encodedTxs }; + Rlp fast = decoder.Encode(blockWithEncoded); + + Assert.That(fast.Bytes.ToHexString(), Is.EqualTo(standard.Bytes.ToHexString())); + } + [Test] public void Get_length_null() { diff --git a/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs index 5888c30c65e1..757d490234c2 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs @@ -296,6 +296,71 @@ public void Fuzz_RandomHex_SegmentationInvariant() } } + [TestCase(new byte[] { 0xab, 0xcd }, true, true, "\"0xabcd\"")] + [TestCase(new byte[] { 0xab, 0xcd }, false, true, "\"0xabcd\"")] + [TestCase(new byte[] { 0x00, 0xab }, true, true, "\"0xab\"")] + [TestCase(new byte[] { 0x00, 0xab }, false, true, "\"0x00ab\"")] + [TestCase(new byte[] { 0x00, 0x00 }, true, true, "\"0x0\"")] + [TestCase(new byte[] { 0x00, 0x00 }, false, true, "\"0x0000\"")] + [TestCase(new byte[] { 0xab }, true, false, "\"ab\"")] + [TestCase(new byte[] { 0xab }, false, false, "\"ab\"")] + [TestCase(new byte[] { 0x0a }, true, true, "\"0xa\"")] + [TestCase(new byte[] { 0x0a }, false, true, "\"0x0a\"")] + public void Write_OutputFormat(byte[] input, bool skipLeadingZeros, bool addHexPrefix, string expected) + { + using System.IO.MemoryStream ms = new(); + using Utf8JsonWriter writer = new(ms); + ByteArrayConverter.Convert(writer, input, skipLeadingZeros, addHexPrefix); + writer.Flush(); + Encoding.UTF8.GetString(ms.ToArray()).Should().Be(expected); + } + + [Test] + public void Write_LargeOutput_UsesArrayPool() + { + // 200 bytes = 400 hex chars + "0x" prefix + quotes > 256 byte InlineArray threshold + byte[] input = new byte[200]; + for (int i = 0; i < input.Length; i++) input[i] = (byte)(i & 0xFF); + + using System.IO.MemoryStream ms = new(); + using Utf8JsonWriter writer = new(ms); + ByteArrayConverter.Convert(writer, input, skipLeadingZeros: false); + writer.Flush(); + string output = Encoding.UTF8.GetString(ms.ToArray()); + output.Should().StartWith("\"0x"); + output.Should().EndWith("\""); + output.Length.Should().Be(404); // 400 hex + 2 prefix + 2 quotes + } + + [Test] + public void WriteAsPropertyName_Format() + { + ByteArrayConverter converter = new(); + using System.IO.MemoryStream ms = new(); + using Utf8JsonWriter writer = new(ms); + writer.WriteStartObject(); + converter.WriteAsPropertyName(writer, new byte[] { 0xab, 0xcd }, JsonSerializerOptions.Default); + writer.WriteNumberValue(1); + writer.WriteEndObject(); + writer.Flush(); + Encoding.UTF8.GetString(ms.ToArray()).Should().Be("{\"0xabcd\":1}"); + } + + [Test] + public void WriteAsPropertyName_AllZeros() + { + ByteArrayConverter converter = new(); + using System.IO.MemoryStream ms = new(); + using Utf8JsonWriter writer = new(ms); + writer.WriteStartObject(); + converter.WriteAsPropertyName(writer, new byte[] { 0x00, 0x00 }, JsonSerializerOptions.Default); + writer.WriteNumberValue(1); + writer.WriteEndObject(); + writer.Flush(); + // skipLeadingZeros: false preserves all zeros + Encoding.UTF8.GetString(ms.ToArray()).Should().Be("{\"0x0000\":1}"); + } + [Test] public void Test_DictionaryKey() { diff --git a/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs index 1e6afeffa5a5..cd6fe2466ebf 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Text.Json; using Nethermind.Core.Crypto; @@ -22,5 +23,67 @@ public void Can_read_null() Hash256? result = JsonSerializer.Deserialize("null", options); Assert.That(result, Is.EqualTo(null)); } + + [Test] + public void Writes_zero_hash() + { + Hash256 hash = new(new byte[32]); + string result = JsonSerializer.Serialize(hash, options); + Assert.That(result, Is.EqualTo("\"0x0000000000000000000000000000000000000000000000000000000000000000\"")); + } + + [Test] + public void Writes_all_ones_hash() + { + byte[] bytes = new byte[32]; + Array.Fill(bytes, (byte)0xFF); + Hash256 hash = new(bytes); + string result = JsonSerializer.Serialize(hash, options); + Assert.That(result, Is.EqualTo("\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"")); + } + + [Test] + public void Writes_known_hash() + { + // Keccak256 of empty string + Hash256 hash = Keccak.OfAnEmptyString; + string result = JsonSerializer.Serialize(hash, options); + Assert.That(result, Is.EqualTo($"\"0x{hash.ToString(false)}\"")); + } + + [Test] + public void Writes_sequential_bytes() + { + byte[] bytes = new byte[32]; + for (int i = 0; i < 32; i++) bytes[i] = (byte)i; + Hash256 hash = new(bytes); + string result = JsonSerializer.Serialize(hash, options); + Assert.That(result, Is.EqualTo("\"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\"")); + } + + [Test] + public void Writes_roundtrip() + { + Hash256 hash = Keccak.Compute("test data"u8); + string json = JsonSerializer.Serialize(hash, options); + Hash256? deserialized = JsonSerializer.Deserialize(json, options); + Assert.That(deserialized, Is.EqualTo(hash)); + } + + [Test] + public void Writes_each_nibble_value() + { + // Ensure all hex chars 0-f appear correctly + byte[] bytes = new byte[32]; + for (int i = 0; i < 16; i++) + { + bytes[i * 2] = (byte)((i << 4) | i); // 0x00, 0x11, 0x22, ..., 0xff + bytes[i * 2 + 1] = (byte)((i << 4) | (15 - i)); // 0x0f, 0x1e, 0x2d, ... + } + Hash256 hash = new(bytes); + string result = JsonSerializer.Serialize(hash, options); + Hash256? roundtrip = JsonSerializer.Deserialize(result, options); + Assert.That(roundtrip, Is.EqualTo(hash)); + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs index f4e8088d4da1..413734f8a535 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs @@ -15,6 +15,7 @@ public class LongConverterTests : ConverterTestBase static readonly LongConverter converter = new(); static readonly JsonSerializerOptions options = new JsonSerializerOptions { Converters = { converter } }; + [Test] public void Test_roundtrip() { TestConverter(int.MaxValue, static (a, b) => a.Equals(b), converter); @@ -63,5 +64,46 @@ public void Throws_on_null() Assert.Throws( static () => JsonSerializer.Deserialize("null", options)); } + + [TestCase(0L, "\"0x0\"")] + [TestCase(1L, "\"0x1\"")] + [TestCase(15L, "\"0xf\"")] + [TestCase(16L, "\"0x10\"")] + [TestCase(255L, "\"0xff\"")] + [TestCase(256L, "\"0x100\"")] + [TestCase(0xabcdefL, "\"0xabcdef\"")] + [TestCase(0x1L, "\"0x1\"")] + [TestCase(0x10L, "\"0x10\"")] + [TestCase(0x100L, "\"0x100\"")] + [TestCase(0x1000L, "\"0x1000\"")] + [TestCase(0x10000L, "\"0x10000\"")] + [TestCase(0x100000L, "\"0x100000\"")] + [TestCase(0x1000000L, "\"0x1000000\"")] + [TestCase(0x10000000L, "\"0x10000000\"")] + [TestCase(int.MaxValue, "\"0x7fffffff\"")] + [TestCase(long.MaxValue, "\"0x7fffffffffffffff\"")] + [TestCase(-1L, "\"0xffffffffffffffff\"")] + [TestCase(-9223372036854775808L, "\"0x8000000000000000\"")] // long.MinValue + public void Writes_correct_hex(long value, string expected) + { + string result = JsonSerializer.Serialize(value, options); + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void Writes_hex_roundtrip_all_nibble_counts() + { + // Test every nibble count from 1 to 16 + for (int nibbles = 1; nibbles <= 16; nibbles++) + { + long value = nibbles <= 15 + ? 1L << ((nibbles - 1) * 4) + : unchecked((long)0x8000000000000000UL); + + string json = JsonSerializer.Serialize(value, options); + long deserialized = JsonSerializer.Deserialize(json, options); + Assert.That(deserialized, Is.EqualTo(value), $"Roundtrip failed for nibbles={nibbles}, value=0x{(ulong)value:x}"); + } + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs index 1d1cd86a8334..fdcd1e24d186 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs @@ -72,5 +72,32 @@ public void Throws_on_negative_numbers() Assert.Throws( static () => JsonSerializer.Deserialize("-1", options)); } + + [TestCase(0UL, "\"0x0\"")] + [TestCase(1UL, "\"0x1\"")] + [TestCase(15UL, "\"0xf\"")] + [TestCase(16UL, "\"0x10\"")] + [TestCase(255UL, "\"0xff\"")] + [TestCase(0xabcdefUL, "\"0xabcdef\"")] + [TestCase(0xffffffffUL, "\"0xffffffff\"")] + [TestCase(0x100000000UL, "\"0x100000000\"")] + [TestCase(ulong.MaxValue, "\"0xffffffffffffffff\"")] + public void Writes_correct_hex(ulong value, string expected) + { + string result = JsonSerializer.Serialize((ulong?)value, options); + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void Writes_hex_roundtrip_all_nibble_counts() + { + for (int nibbles = 1; nibbles <= 16; nibbles++) + { + ulong value = 1UL << ((nibbles - 1) * 4); + string json = JsonSerializer.Serialize((ulong?)value, options); + ulong? deserialized = JsonSerializer.Deserialize(json, options); + Assert.That(deserialized, Is.EqualTo(value), $"Roundtrip failed for nibbles={nibbles}, value=0x{value:x}"); + } + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs index 9bbe9f7e0e0b..25f4e9737c29 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs @@ -152,5 +152,140 @@ public void Throws_on_null() Assert.Throws( static () => JsonSerializer.Deserialize("null", options)); } + + [TestCase(0ul, 0ul, 0ul, 0ul, "\"0x0\"")] + [TestCase(1ul, 0ul, 0ul, 0ul, "\"0x1\"")] + [TestCase(0xful, 0ul, 0ul, 0ul, "\"0xf\"")] + [TestCase(0xfful, 0ul, 0ul, 0ul, "\"0xff\"")] + [TestCase(0xabcdeful, 0ul, 0ul, 0ul, "\"0xabcdef\"")] + [TestCase(ulong.MaxValue, 0ul, 0ul, 0ul, "\"0xffffffffffffffff\"")] + [TestCase(ulong.MaxValue, 1ul, 0ul, 0ul, "\"0x1ffffffffffffffff\"")] + [TestCase(0ul, 0ul, 1ul, 0ul, "\"0x100000000000000000000000000000000\"")] + [TestCase(0ul, 0ul, 0ul, 1ul, "\"0x1000000000000000000000000000000000000000000000000\"")] + [TestCase(ulong.MaxValue, ulong.MaxValue, ulong.MaxValue, ulong.MaxValue, "\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"")] + public void Writes_hex(ulong u0, ulong u1, ulong u2, ulong u3, string expected) + { + UInt256 value = new(u0, u1, u2, u3); + string result = JsonSerializer.Serialize(value, options); + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void Writes_hex_roundtrip_all_limb_boundaries() + { + // Test values at each limb boundary + UInt256[] values = + [ + UInt256.One, + new UInt256(ulong.MaxValue), + new UInt256(0, 1), + new UInt256(ulong.MaxValue, ulong.MaxValue), + new UInt256(0, 0, 1, 0), + new UInt256(ulong.MaxValue, ulong.MaxValue, ulong.MaxValue, 0), + new UInt256(0, 0, 0, 1), + UInt256.MaxValue, + ]; + + foreach (UInt256 value in values) + { + string json = JsonSerializer.Serialize(value, options); + UInt256 deserialized = JsonSerializer.Deserialize(json, options); + Assert.That(deserialized, Is.EqualTo(value), $"Roundtrip failed for {value}"); + } + } + + [Test] + public void Writes_zero_padded_hex() + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.ZeroPaddedHex; + try + { + string result = JsonSerializer.Serialize(UInt256.One, options); + Assert.That(result, Is.EqualTo("\"0x0000000000000000000000000000000000000000000000000000000000000001\"")); + + result = JsonSerializer.Serialize(UInt256.MaxValue, options); + Assert.That(result, Is.EqualTo("\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"")); + + result = JsonSerializer.Serialize(UInt256.Zero, options); + Assert.That(result, Is.EqualTo("\"0x0000000000000000000000000000000000000000000000000000000000000000\"")); + } + finally + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.Hex; + } + } + + [Test] + public void Writes_zero_padded_hex_roundtrip() + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.ZeroPaddedHex; + try + { + UInt256 value = new(0xdeadbeef, 0xcafebabe, 0x12345678, 0x9abcdef0); + string json = JsonSerializer.Serialize(value, options); + UInt256 deserialized = JsonSerializer.Deserialize(json, options); + Assert.That(deserialized, Is.EqualTo(value)); + } + finally + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.Hex; + } + } + + [Test] + public void Writes_property_name_hex() + { + using var stream = new System.IO.MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + + writer.WriteStartObject(); + converter.WriteAsPropertyName(writer, (UInt256)0xabcdef, options); + writer.WriteNumberValue(1); + writer.WriteEndObject(); + writer.Flush(); + + string result = System.Text.Encoding.UTF8.GetString(stream.ToArray()); + Assert.That(result, Is.EqualTo("{\"0xabcdef\":1}")); + } + + [Test] + public void Writes_property_name_zero() + { + using var stream = new System.IO.MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + + writer.WriteStartObject(); + converter.WriteAsPropertyName(writer, UInt256.Zero, options); + writer.WriteNumberValue(1); + writer.WriteEndObject(); + writer.Flush(); + + string result = System.Text.Encoding.UTF8.GetString(stream.ToArray()); + Assert.That(result, Is.EqualTo("{\"0x0\":1}")); + } + + [Test] + public void Writes_property_name_zero_padded_hex() + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.ZeroPaddedHex; + try + { + using var stream = new System.IO.MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + + writer.WriteStartObject(); + converter.WriteAsPropertyName(writer, UInt256.One, options); + writer.WriteNumberValue(1); + writer.WriteEndObject(); + writer.Flush(); + + string result = System.Text.Encoding.UTF8.GetString(stream.ToArray()); + Assert.That(result, Is.EqualTo("{\"0x0000000000000000000000000000000000000000000000000000000000000001\":1}")); + } + finally + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.Hex; + } + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/MCSLockTests.cs b/src/Nethermind/Nethermind.Core.Test/MCSLockTests.cs index 0dbf3e1ea56e..16d4fac388eb 100644 --- a/src/Nethermind/Nethermind.Core.Test/MCSLockTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/MCSLockTests.cs @@ -59,6 +59,7 @@ public void MultipleThreads() } [Test] + [Retry(3)] public void LockFairnessTest() { int numberOfThreads = 10; diff --git a/src/Nethermind/Nethermind.Core.Test/RlpTests.cs b/src/Nethermind/Nethermind.Core.Test/RlpTests.cs index 49e9a776687e..01320fdf3d38 100644 --- a/src/Nethermind/Nethermind.Core.Test/RlpTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/RlpTests.cs @@ -246,6 +246,16 @@ public void Long_and_big_integer_encoded_the_same(long value) Assert.That(rlpBigInt.Bytes, Is.EqualTo(rlpLong.Bytes)); } + [Test] + public void Encode_generic_with_Rlp_input_preserves_original_bytes() + { + Rlp original = Rlp.Encode(255L); + Rlp reEncoded = Rlp.Encode(original); + + Assert.That(reEncoded.Bytes, Is.EqualTo(original.Bytes)); + Assert.That(reEncoded, Is.SameAs(original)); + } + [TestCase(true)] [TestCase(false)] public void RlpContextWithSliceMemory_shouldNotCopyUnderlyingData(bool sliceValue) diff --git a/src/Nethermind/Nethermind.Core.Test/TestMemDb.cs b/src/Nethermind/Nethermind.Core.Test/TestMemDb.cs index 169a166c58c7..b174709b9662 100644 --- a/src/Nethermind/Nethermind.Core.Test/TestMemDb.cs +++ b/src/Nethermind/Nethermind.Core.Test/TestMemDb.cs @@ -23,18 +23,15 @@ public class TestMemDb : MemDb, ITunableDb public Func? ReadFunc { get; set; } public Func? WriteFunc { get; set; } - public Action? RemoveFunc { get; set; } public bool WasFlushed => FlushCount > 0; - public int FlushCount { get; set; } = 0; + public int FlushCount { get; private set; } [MethodImpl(MethodImplOptions.Synchronized)] public override byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) { _readKeys.Add((key.ToArray(), flags)); - - if (ReadFunc is not null) return ReadFunc(key.ToArray()); - return base.Get(key, flags); + return ReadFunc is not null ? ReadFunc(key.ToArray()) : base.Get(key, flags); } [MethodImpl(MethodImplOptions.Synchronized)] @@ -46,71 +43,32 @@ public override void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags base.Set(key, value, flags); } - public override Span GetSpan(ReadOnlySpan key) - { - return Get(key); - } - [MethodImpl(MethodImplOptions.Synchronized)] public override void Remove(ReadOnlySpan key) { _removedKeys.Add(key.ToArray()); - - if (RemoveFunc is not null) - { - RemoveFunc.Invoke(key.ToArray()); - return; - } base.Remove(key); } - public void Tune(ITunableDb.TuneType type) - { - _tuneTypes.Add(type); - } - - public bool WasTunedWith(ITunableDb.TuneType type) - { - return _tuneTypes.Contains(type); - } + public void Tune(ITunableDb.TuneType type) => _tuneTypes.Add(type); + public bool WasTunedWith(ITunableDb.TuneType type) => _tuneTypes.Contains(type); - public void KeyWasRead(byte[] key, int times = 1) - { + public void KeyWasRead(byte[] key, int times = 1) => _readKeys.Count(it => Bytes.AreEqual(it.Item1, key)).Should().Be(times); - } - public void KeyWasReadWithFlags(byte[] key, ReadFlags flags, int times = 1) - { + public void KeyWasReadWithFlags(byte[] key, ReadFlags flags, int times = 1) => _readKeys.Count(it => Bytes.AreEqual(it.Item1, key) && it.Item2 == flags).Should().Be(times); - } - public void KeyWasWritten(byte[] key, int times = 1) - { + public void KeyWasWritten(byte[] key, int times = 1) => _writes.Count(it => Bytes.AreEqual(it.Item1.Item1, key)).Should().Be(times); - } - public void KeyWasWritten(Func<(byte[], byte[]?), bool> cond, int times = 1) - { + public void KeyWasWritten(Func<(byte[], byte[]?), bool> cond, int times = 1) => _writes.Count(it => cond.Invoke(it.Item1)).Should().Be(times); - } - public void KeyWasWrittenWithFlags(byte[] key, WriteFlags flags, int times = 1) - { + public void KeyWasWrittenWithFlags(byte[] key, WriteFlags flags, int times = 1) => _writes.Count(it => Bytes.AreEqual(it.Item1.Item1, key) && it.Item2 == flags).Should().Be(times); - } - - public void KeyWasRemoved(Func cond, int times = 1) - { - _removedKeys.Count(cond).Should().Be(times); - } - public override IWriteBatch StartWriteBatch() - { - return new InMemoryWriteBatch(this); - } - - public override void Flush(bool onlyWal) - { - FlushCount++; - } + public void KeyWasRemoved(Func cond, int times = 1) => _removedKeys.Count(cond).Should().Be(times); + public override IWriteBatch StartWriteBatch() => new InMemoryWriteBatch(this); + public override void Flush(bool onlyWal) => FlushCount++; } diff --git a/src/Nethermind/Nethermind.Core/Address.cs b/src/Nethermind/Nethermind.Core/Address.cs index 0bad6dd83609..80516f59c95c 100644 --- a/src/Nethermind/Nethermind.Core/Address.cs +++ b/src/Nethermind/Nethermind.Core/Address.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Text.Json.Serialization; - +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -20,7 +20,7 @@ namespace Nethermind.Core [JsonConverter(typeof(AddressConverter))] [TypeConverter(typeof(AddressTypeConverter))] [DebuggerDisplay("{ToString()}")] - public class Address : IEquatable
, IComparable
+ public sealed class Address : IEquatable
, IComparable
{ public const int Size = 20; private const int HexCharsCount = 2 * Size; // 5a4eab120fb44eb6684e5e32785702ff45ea344d @@ -273,9 +273,11 @@ public ValueHash256 ToHash() return result; } + + internal long GetHashCode64() => SpanExtensions.FastHash64For20Bytes(ref MemoryMarshal.GetArrayDataReference(Bytes)); } - public readonly struct AddressAsKey(Address key) : IEquatable + public readonly struct AddressAsKey(Address key) : IEquatable, IHash64bit { private readonly Address _key = key; public Address Value => _key; @@ -289,6 +291,10 @@ public override string ToString() { return _key?.ToString() ?? ""; } + + public long GetHashCode64() => _key is not null ? _key.GetHashCode64() : 0; + + public bool Equals(in AddressAsKey other) => _key == other._key; } public ref struct AddressStructRef diff --git a/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs b/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs index c6a24a3e10cb..b93ec4916d3e 100644 --- a/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs +++ b/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs @@ -2,9 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using System.Buffers.Text; using System.IO; using System.Runtime.CompilerServices; using System.Security.Cryptography; +using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -21,27 +24,53 @@ public sealed partial class JwtAuthentication : IRpcAuthentication private const int JwtTokenTtl = 60; private const int JwtSecretLength = 64; + // Manual HS256 validation limits + private const int SHA256HashBytes = 32; + private const int HS256SignatureSegmentLength = 44; // 43 Base64Url chars + dot separator + private const int StackBufferSize = 256; + private const int MaxManualJwtLength = StackBufferSize + HS256SignatureSegmentLength; // 300 + private static readonly Task True = Task.FromResult(true); private static readonly Task False = Task.FromResult(false); + // Known HS256 JWT header Base64Url encodings used by consensus clients + // {"alg":"HS256","typ":"JWT"} + private const string HeaderAlgTyp = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; + // {"typ":"JWT","alg":"HS256"} + private const string HeaderTypAlg = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"; + // {"alg":"HS256"} (some CLs omit typ) + private const string HeaderAlgOnly = "eyJhbGciOiJIUzI1NiJ9"; + private readonly JsonWebTokenHandler _handler = new(); - private readonly SecurityKey _securityKey; + private readonly byte[] _secretBytes; + private readonly TokenValidationParameters _tokenValidationParameters; private readonly ILogger _logger; private readonly ITimestamper _timestamper; - private readonly LifetimeValidator _lifetimeValidator; - // Single entry cache: last successfully validated token - private TokenCacheEntry? _lastToken; + // Single entry cache: last successfully validated token (allocation-free) + // Write order: iat first, then token with Volatile.Write (release fence) + // Read order: token with Volatile.Read (acquire fence), then iat + private string? _cachedToken; + private long _cachedTokenIat; private JwtAuthentication(byte[] secret, ITimestamper timestamper, ILogger logger) { ArgumentNullException.ThrowIfNull(secret); ArgumentNullException.ThrowIfNull(timestamper); - _securityKey = new SymmetricSecurityKey(secret); + _secretBytes = secret; + SecurityKey securityKey = new SymmetricSecurityKey(secret); _logger = logger; _timestamper = timestamper; - _lifetimeValidator = LifetimeValidator; + _tokenValidationParameters = new TokenValidationParameters + { + IssuerSigningKey = securityKey, + RequireExpirationTime = false, + ValidateLifetime = true, + ValidateAudience = false, + ValidateIssuer = false, + LifetimeValidator = LifetimeValidator + }; } public static JwtAuthentication FromSecret(string secret, ITimestamper timestamper, ILogger logger) @@ -136,7 +165,14 @@ public Task Authenticate(string? token) return True; } - return AuthenticateCore(token); + // fast manual HS256 validation: avoids Microsoft.IdentityModel overhead + if (TryValidateManual(token, nowUnixSeconds, out bool accepted)) + { + return accepted ? True : False; + } + + // fallback to full library validation for unrecognized header formats + return AuthenticateCore(token, nowUnixSeconds); [MethodImpl(MethodImplOptions.NoInlining)] void WarnTokenNotFound() => _logger.Warn("Message authentication error: The token cannot be found."); @@ -145,81 +181,267 @@ public Task Authenticate(string? token) void TokenMalformed() => _logger.Warn($"Message authentication error: The token must start with '{JwtMessagePrefix}'."); } - private async Task AuthenticateCore(string token) + /// + /// Manual HS256 JWT validation for known header formats. + /// Returns true if handled (result in ), false to fall through to library. + /// + [SkipLocalsInit] + private bool TryValidateManual(string token, long nowUnixSeconds, out bool accepted) { - try + accepted = false; + // Extract raw JWT (after "Bearer ") + ReadOnlySpan jwt = token.AsSpan(JwtMessagePrefix.Length); + + // Bail early: signed part must fit in StackBufferSize, plus HS256SignatureSegmentLength for the signature. + if (jwt.Length > MaxManualJwtLength) + return false; + + // Known HS256 header lengths: 36 (AlgTyp, TypAlg) or 20 (AlgOnly). + // Check dot at known position and verify header in one shot — avoids IndexOf scan. + int firstDot; + if (jwt.Length > 36 && jwt[36] == '.' && + (jwt[..36].SequenceEqual(HeaderAlgTyp) || jwt[..36].SequenceEqual(HeaderTypAlg))) { - TokenValidationParameters tokenValidationParameters = new() - { - IssuerSigningKey = _securityKey, - RequireExpirationTime = false, - ValidateLifetime = true, - ValidateAudience = false, - ValidateIssuer = false, - LifetimeValidator = _lifetimeValidator - }; + firstDot = 36; + } + else if (jwt.Length > 20 && jwt[20] == '.' && jwt[..20].SequenceEqual(HeaderAlgOnly)) + { + firstDot = 20; + } + else + { + return false; + } - ReadOnlyMemory tokenSlice = token.AsMemory(JwtMessagePrefix.Length); - JsonWebToken jwtToken = _handler.ReadJsonWebToken(tokenSlice); - TokenValidationResult result = await _handler.ValidateTokenAsync(jwtToken, tokenValidationParameters); + // HS256 sig = 43 Base64Url chars, so second dot is at jwt.Length - HS256SignatureSegmentLength. + // Computed directly — eliminates IndexOf scan over payload+signature. + int secondDot = jwt.Length - HS256SignatureSegmentLength; + if (secondDot <= firstDot || jwt[secondDot] != '.') + return false; + + ReadOnlySpan payload = jwt[(firstDot + 1)..secondDot]; + ReadOnlySpan signature = jwt[(secondDot + 1)..]; + + // Compute HMAC-SHA256 over "header.payload" (ASCII bytes). + // secondDot == char count == byte count (JWT is pure ASCII). + // Early length check guarantees secondDot <= 256. + ReadOnlySpan signedPart = jwt[..secondDot]; + Span signedBytes = stackalloc byte[StackBufferSize]; + signedBytes = signedBytes[..secondDot]; + + if (Ascii.FromUtf16(signedPart, signedBytes, out _) != OperationStatus.Done) + return false; + + Span computedHash = stackalloc byte[SHA256HashBytes]; + HMACSHA256.HashData(_secretBytes, signedBytes, computedHash); + + Span sigBytes = stackalloc byte[SHA256HashBytes]; + if (Base64Url.DecodeFromChars(signature, sigBytes, out _, out int sigBytesWritten) != OperationStatus.Done + || sigBytesWritten != SHA256HashBytes) + { + return true; + } + + if (!CryptographicOperations.FixedTimeEquals(computedHash, sigBytes)) + { + if (_logger.IsWarn) WarnInvalidSig(); + return true; + } + + if (!TryExtractClaims(payload, out long iat, out long exp)) + return false; // sig valid but can't parse claims — let library handle it + + if (exp > 0 && nowUnixSeconds >= exp) + { + if (_logger.IsWarn) WarnTokenExpiredExp(exp, nowUnixSeconds); + return true; + } + + // Overflow-safe absolute-difference check: casting to ulong maps negative values to + // large positives, so (ulong)(a - b + c) > (ulong)(2*c) is equivalent to |a - b| > c + // without needing Math.Abs (which can overflow on long.MinValue). + if ((ulong)(iat - nowUnixSeconds + JwtTokenTtl) > (ulong)(JwtTokenTtl * 2)) + { + if (_logger.IsWarn) WarnTokenExpiredIat(iat, nowUnixSeconds); + return true; + } + + CacheLastToken(token, iat); + accepted = true; + return true; + + + [MethodImpl(MethodImplOptions.NoInlining)] + void WarnTokenExpiredExp(long e, long now) => _logger.Warn($"Token expired. exp: {e}, now: {now}"); + + [MethodImpl(MethodImplOptions.NoInlining)] + void WarnTokenExpiredIat(long i, long now) => _logger.Warn($"Token expired. iat: {i}, now: {now}"); + + [MethodImpl(MethodImplOptions.NoInlining)] + void WarnInvalidSig() => _logger.Warn("Message authentication error: Invalid token signature."); + } + + /// + /// Extract "iat" (required) and "exp" (optional) integer claims from a JWT payload. + /// Uses a lightweight byte scanner instead of Utf8JsonReader to avoid: + /// - 552-byte stack frame (Utf8JsonReader struct ~200 bytes + locals) + /// - 432-byte prolog zeroing loop + /// - ArrayPool rent/return + try/finally + /// - 90 inlined reader methods bloating to 2979 bytes of native code + /// + [SkipLocalsInit] + private static bool TryExtractClaims(ReadOnlySpan payloadBase64Url, out long iat, out long exp) + { + iat = 0; + exp = 0; + + // Decode payload from Base64Url into a fixed stack buffer. + // Engine API payloads are tiny (~30 bytes). If decoded output exceeds + // StackBufferSize bytes, DecodeFromChars returns DestinationTooSmall → we reject. + Span decoded = stackalloc byte[StackBufferSize]; + if (Base64Url.DecodeFromChars(payloadBase64Url, decoded, out _, out int bytesWritten) != OperationStatus.Done) + return false; + + // Scan decoded UTF-8 bytes for "iat" and "exp" keys with integer values. + // JWT payloads are compact JSON objects: {"iat":NNNNN,"exp":NNNNN} + // The scanner finds quoted 3-letter keys and parses the integer after the colon. + ReadOnlySpan json = decoded[..bytesWritten]; + bool foundIat = false; - if (!result.IsValid) + for (int i = 0; i < json.Length - 4; i++) + { + if (json[i] != '"') continue; + + byte k1 = json[i + 1], k2 = json[i + 2], k3 = json[i + 3]; + if (json[i + 4] != '"') continue; + + if (k1 == 'i' && k2 == 'a' && k3 == 't') { - if (_logger.IsWarn) WarnInvalidResult(result.Exception); - return false; + foundIat = TryParseClaimValue(json, i + 5, out iat); } - - DateTime now = _timestamper.UtcNow; - long issuedAtUnix = jwtToken.IssuedAt.ToUnixTimeSeconds(); - if (Math.Abs(issuedAtUnix - now.ToUnixTimeSeconds()) <= JwtTokenTtl) + else if (k1 == 'e' && k2 == 'x' && k3 == 'p') { - // full validation succeeded and TTL check passed - cache as last valid token - CacheLastToken(token, issuedAtUnix); - - if (_logger.IsTrace) Trace(jwtToken, now, tokenSlice); - return true; + TryParseClaimValue(json, i + 5, out exp); } + } - if (_logger.IsWarn) WarnTokenExpired(jwtToken, now); - return false; + return foundIat; + } + + /// + /// Parse an integer claim value starting at (expects ":digits"). + /// The (uint)pos < (uint)json.Length pattern collapses a two-condition bounds check + /// (pos >= 0 && pos < Length) into a single unsigned comparison, allowing the + /// JIT to eliminate the redundant range check on the subsequent indexer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryParseClaimValue(ReadOnlySpan json, int pos, out long value) + { + value = 0; + // Skip optional whitespace before colon + while ((uint)pos < (uint)json.Length && json[pos] == ' ') pos++; + if ((uint)pos >= (uint)json.Length || json[pos] != ':') return false; + pos++; + // Skip optional whitespace after colon + while ((uint)pos < (uint)json.Length && json[pos] == ' ') pos++; + // Parse unsigned integer digits + bool hasDigit = false; + while ((uint)pos < (uint)json.Length) + { + uint digit = (uint)(json[pos] - '0'); + if (digit > 9) break; + value = value * 10 + digit; + hasDigit = true; + pos++; + } + return hasDigit; + } + + /// + /// Library-based JWT validation for unrecognized header formats. + /// Checks for synchronous Task completion to avoid async state machine on the hot path. + /// + private Task AuthenticateCore(string token, long nowUnixSeconds) + { + try + { + ReadOnlyMemory tokenSlice = token.AsMemory(JwtMessagePrefix.Length); + JsonWebToken jwtToken = _handler.ReadJsonWebToken(tokenSlice); + Task task = _handler.ValidateTokenAsync(jwtToken, _tokenValidationParameters); + + // HS256 validation is CPU-bound → task is almost always already completed. + // Avoid async state machine overhead for the common synchronous path. + return task.IsCompletedSuccessfully + ? ValidateLibraryResult(task.GetAwaiter().GetResult(), token, jwtToken, nowUnixSeconds) ? True : False + : AwaitValidation(task, token, jwtToken, nowUnixSeconds); } catch (Exception ex) { - if (_logger.IsWarn) WarnAuthenticationError(ex); + if (_logger.IsWarn) WarnAuthError(ex); + return False; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void WarnAuthError(Exception? ex) => _logger.Warn($"Message authentication error: {ex?.Message}"); + } + + private bool ValidateLibraryResult(TokenValidationResult result, string token, JsonWebToken jwtToken, long nowUnixSeconds) + { + if (!result.IsValid) + { + if (_logger.IsWarn) WarnInvalidResult(result.Exception); + return false; + } + + long issuedAtUnix = jwtToken.IssuedAt.ToUnixTimeSeconds(); + + // Unsigned range check: |iat - now| <= TTL without Math.Abs overflow guard + if ((ulong)(issuedAtUnix - nowUnixSeconds + JwtTokenTtl) > (ulong)(JwtTokenTtl * 2)) + { + if (_logger.IsWarn) WarnTokenExpired(issuedAtUnix, nowUnixSeconds); return false; } + CacheLastToken(token, issuedAtUnix); + if (_logger.IsTrace) TraceAuth(jwtToken, nowUnixSeconds, token); + return true; + [MethodImpl(MethodImplOptions.NoInlining)] void WarnInvalidResult(Exception? ex) { - if (ex is SecurityTokenDecryptionFailedException) - { - _logger.Warn("Message authentication error: The token cannot be decrypted."); - } - else if (ex is SecurityTokenReplayDetectedException) - { - _logger.Warn("Message authentication error: The token has been used multiple times."); - } - else if (ex is SecurityTokenInvalidSignatureException) + _logger.Warn(ex switch { - _logger.Warn("Message authentication error: Invalid token signature."); - } - else - { - WarnAuthenticationError(ex); - } + SecurityTokenDecryptionFailedException => "Message authentication error: The token cannot be decrypted.", + SecurityTokenReplayDetectedException => "Message authentication error: The token has been used multiple times.", + SecurityTokenInvalidSignatureException => "Message authentication error: Invalid token signature.", + _ => $"Message authentication error: {ex?.Message}" + }); } [MethodImpl(MethodImplOptions.NoInlining)] - void WarnAuthenticationError(Exception? ex) => _logger.Warn($"Message authentication error: {ex?.Message}"); + void WarnTokenExpired(long iat, long now) + => _logger.Warn($"Token expired. iat: {iat}, now: {now}"); [MethodImpl(MethodImplOptions.NoInlining)] - void WarnTokenExpired(JsonWebToken jwtToken, DateTime now) - => _logger.Warn($"Token expired. Now is {now}, token issued at {jwtToken.IssuedAt}"); + void TraceAuth(JsonWebToken jwt, long now, string tok) + => _logger.Trace($"Message authenticated. Token: {tok.AsMemory(JwtMessagePrefix.Length)}, iat: {jwt.IssuedAt}, time: {now}"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private async Task AwaitValidation(Task task, string token, JsonWebToken jwtToken, long nowUnixSeconds) + { + try + { + return ValidateLibraryResult(await task, token, jwtToken, nowUnixSeconds); + } + catch (Exception ex) + { + if (_logger.IsWarn) WarnAuthError(ex); + return false; + } [MethodImpl(MethodImplOptions.NoInlining)] - void Trace(JsonWebToken jwtToken, DateTime now, ReadOnlyMemory token) - => _logger.Trace($"Message authenticated. Token: {token}, iat: {jwtToken.IssuedAt}, time: {now}"); + void WarnAuthError(Exception? ex) => _logger.Warn($"Message authentication error: {ex?.Message}"); } private bool LifetimeValidator( @@ -232,44 +454,37 @@ private bool LifetimeValidator( return _timestamper.UnixTime.SecondsLong < expires.Value.ToUnixTimeSeconds(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void CacheLastToken(string token, long issuedAtUnixSeconds) { - TokenCacheEntry entry = new(token, issuedAtUnixSeconds); - // last writer wins, atomic swap - Interlocked.Exchange(ref _lastToken, entry); + // Write iat first (plain store), then token with release fence. + // Reader uses acquire fence on token, then reads iat — guarantees + // the iat visible is at least as fresh as the token that was read. + _cachedTokenIat = issuedAtUnixSeconds; + Volatile.Write(ref _cachedToken, token); } private bool TryLastValidationFromCache(string token, long nowUnixSeconds) { - // Read the last validated token entry atomically - // this is a single entry cache because tokens tend to be reused - // for a handful of sequential requests before a fresh token is issued - TokenCacheEntry? entry = Volatile.Read(ref _lastToken); - if (entry is null) + // Acquire fence on token read; guarantees _cachedTokenIat is at least as fresh + string? cached = Volatile.Read(ref _cachedToken); + if (cached is null) return false; - // Only allow cache hit if the exact same token string is being reused - // different tokens bypass the cache and undergo full validation - if (!string.Equals(entry.Token, token, StringComparison.Ordinal)) + if (!string.Equals(cached, token, StringComparison.Ordinal)) return false; - // Token reuse is only allowed within the original JWT lifetime - // We never extend token validity beyond what the issuer intended - // - IssuedAtUnixSeconds ensures we don't accept a token older than TTL - if (Math.Abs(entry.IssuedAtUnixSeconds - nowUnixSeconds) > JwtTokenTtl) + // Unsigned range check: |iat - now| <= TTL + if ((ulong)(_cachedTokenIat - nowUnixSeconds + JwtTokenTtl) > (ulong)(JwtTokenTtl * 2)) { - // Token lifetime exceeded - drop the cached entry and force a fresh validation - Interlocked.CompareExchange(ref _lastToken, null, entry); + Volatile.Write(ref _cachedToken, null); return false; } - // Same token, within TTL, recently validated: - // Accept as valid without rerunning JWT parsing and crypto checks return true; } [GeneratedRegex("^(0x)?[0-9a-fA-F]{64}$")] private static partial Regex SecretRegex(); - private record TokenCacheEntry(string Token, long IssuedAtUnixSeconds); } diff --git a/src/Nethermind/Nethermind.Core/Block.cs b/src/Nethermind/Nethermind.Core/Block.cs index 8d53097d5198..5fd94383cdb6 100644 --- a/src/Nethermind/Nethermind.Core/Block.cs +++ b/src/Nethermind/Nethermind.Core/Block.cs @@ -126,6 +126,13 @@ public Transaction[] Transactions [JsonIgnore] public int? EncodedSize { get; set; } + /// + /// Pre-encoded transaction bytes in SkipTypedWrapping format (as received from CL). + /// Used to avoid re-encoding transactions when storing blocks. + /// + [JsonIgnore] + public byte[][]? EncodedTransactions { get; set; } + public override string ToString() => ToString(Format.Short); public string ToString(Format format) => format switch diff --git a/src/Nethermind/Nethermind.Core/Bloom.cs b/src/Nethermind/Nethermind.Core/Bloom.cs index baeb3e75e168..cc5d3acce000 100644 --- a/src/Nethermind/Nethermind.Core/Bloom.cs +++ b/src/Nethermind/Nethermind.Core/Bloom.cs @@ -53,7 +53,9 @@ public Bloom(ReadOnlySpan bytes) bytes.CopyTo(Bytes); } + [JsonIgnore] public Span Bytes => _bloomData.AsSpan(); + [JsonIgnore] public ReadOnlySpan ReadOnlyBytes => _bloomData.AsReadOnlySpan(); private Span ULongs => _bloomData.AsULongs(); diff --git a/src/Nethermind/Nethermind.Core/Buffers/ArrayMemoryManager.cs b/src/Nethermind/Nethermind.Core/Buffers/ArrayMemoryManager.cs new file mode 100644 index 000000000000..52c1bf4dd496 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Buffers/ArrayMemoryManager.cs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; + +namespace Nethermind.Core.Buffers; + +/// +/// Simple MemoryManager that wraps a byte array without any pinning. +/// Used for in-memory stores where the array is managed and doesn't require special release handling. +/// +public sealed class ArrayMemoryManager(byte[] array) : MemoryManager +{ + protected override void Dispose(bool disposing) { } + + public override Span GetSpan() => array; + + public override MemoryHandle Pin(int elementIndex = 0) => default; + + public override void Unpin() { } +} diff --git a/src/Nethermind/Nethermind.Core/DbSpanMemoryManager.cs b/src/Nethermind/Nethermind.Core/Buffers/DbSpanMemoryManager.cs similarity index 58% rename from src/Nethermind/Nethermind.Core/DbSpanMemoryManager.cs rename to src/Nethermind/Nethermind.Core/Buffers/DbSpanMemoryManager.cs index 48d881ae00c0..18a32b032a88 100644 --- a/src/Nethermind/Nethermind.Core/DbSpanMemoryManager.cs +++ b/src/Nethermind/Nethermind.Core/Buffers/DbSpanMemoryManager.cs @@ -10,24 +10,16 @@ namespace Nethermind.Core.Buffers; -public unsafe sealed class DbSpanMemoryManager : MemoryManager +public sealed unsafe class DbSpanMemoryManager(IReadOnlyKeyValueStore db, Span unmanagedSpan) : MemoryManager { - private readonly IReadOnlyKeyValueStore _db; - private void* _ptr; - private readonly int _length; - - public DbSpanMemoryManager(IReadOnlyKeyValueStore db, Span unmanagedSpan) - { - _db = db; - _ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(unmanagedSpan)); - _length = unmanagedSpan.Length; - } + private void* _ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(unmanagedSpan)); + private readonly int _length = unmanagedSpan.Length; protected override void Dispose(bool disposing) { if (_ptr is not null) { - _db.DangerousReleaseMemory(GetSpan()); + db.DangerousReleaseMemory(GetSpan()); } _ptr = null; @@ -53,13 +45,8 @@ public override MemoryHandle Pin(int elementIndex = 0) return new MemoryHandle(_ptr); } - public override void Unpin() - { - } + public override void Unpin() { } [DoesNotReturn, StackTraceHidden] - private static void ThrowDisposed() - { - throw new ObjectDisposedException(nameof(DbSpanMemoryManager)); - } + private static void ThrowDisposed() => throw new ObjectDisposedException(nameof(DbSpanMemoryManager)); } diff --git a/src/Nethermind/Nethermind.Core/Caching/ISpanCache.cs b/src/Nethermind/Nethermind.Core/Caching/ISpanCache.cs deleted file mode 100644 index c7b56ebd2a8f..000000000000 --- a/src/Nethermind/Nethermind.Core/Caching/ISpanCache.cs +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; - -namespace Nethermind.Core.Caching -{ - /// - /// Its like `ICache` but you can index the key by span - /// - /// - /// - public interface ISpanCache - { - void Clear(); - TValue? Get(ReadOnlySpan key); - bool TryGet(ReadOnlySpan key, out TValue? value); - - /// - /// Sets value in the cache. - /// - /// - /// - /// True if key didn't exist in the cache, otherwise false. - bool Set(ReadOnlySpan key, TValue val); - - /// - /// Delete key from cache. - /// - /// - /// True if key existed in the cache, otherwise false. - bool Delete(ReadOnlySpan key); - bool Contains(ReadOnlySpan key); - int Count { get; } - } -} diff --git a/src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs b/src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs deleted file mode 100644 index 1f499890f03c..000000000000 --- a/src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Buffers; - -namespace Nethermind.Core.Buffers; - -public class CappedArrayMemoryManager(CappedArray? data) : MemoryManager -{ - private readonly CappedArray _data = data ?? throw new ArgumentNullException(nameof(data)); - private bool _isDisposed; - - public override Span GetSpan() - { - ObjectDisposedException.ThrowIf(_isDisposed, this); - return _data.AsSpan(); - } - - public override MemoryHandle Pin(int elementIndex = 0) - { - ObjectDisposedException.ThrowIf(_isDisposed, this); - ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)elementIndex, (uint)_data.Length); - // Pinning is a no-op in this managed implementation - return default; - } - - public override void Unpin() - { - ObjectDisposedException.ThrowIf(_isDisposed, this); - // Unpinning is a no-op in this managed implementation - } - - protected override void Dispose(bool disposing) - { - _isDisposed = true; - } - - protected override bool TryGetArray(out ArraySegment segment) - { - ObjectDisposedException.ThrowIf(_isDisposed, this); - segment = _data.AsArraySegment(); - return true; - } -} diff --git a/src/Nethermind/Nethermind.Core/Collections/ArrayListCore.cs b/src/Nethermind/Nethermind.Core/Collections/ArrayListCore.cs index 00b95e683953..518fb7512842 100644 --- a/src/Nethermind/Nethermind.Core/Collections/ArrayListCore.cs +++ b/src/Nethermind/Nethermind.Core/Collections/ArrayListCore.cs @@ -51,6 +51,15 @@ public static void ClearToCount(T[] array, int count) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ClearTail(T[] array, int newCount, int oldCount) + { + if (RuntimeHelpers.IsReferenceOrContainsReferences() && newCount < oldCount) + { + Array.Clear(array, newCount, oldCount - newCount); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Add( ArrayPool pool, @@ -108,9 +117,9 @@ public static void ReduceCount( ClearToCount(oldArray, oldCount); pool.Return(oldArray); } - else if (RuntimeHelpers.IsReferenceOrContainsReferences()) + else { - Array.Clear(array, newCount, oldCount - newCount); + ClearTail(array, newCount, oldCount); } [DoesNotReturn] @@ -228,6 +237,7 @@ public static void Insert( public static void Truncate(int newLength, T[] array, ref int count) { GuardIndex(newLength, count, shouldThrow: true, allowEqualToCount: true); + ClearTail(array, newLength, count); count = newLength; } diff --git a/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs b/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs index 491e4e3528ca..9fdf91723f45 100644 --- a/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +using Nethermind.Core.Resettables; namespace Nethermind.Core.Collections; @@ -15,21 +17,48 @@ public static void Increment(this Dictionary dictionary, TKey k res++; } - public static ref TValue GetOrAdd(this Dictionary dictionary, - TKey key, Func factory, - out bool exists) - where TKey : notnull + extension(Dictionary dictionary) where TKey : notnull { - ref TValue? existing = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out exists); + public ref TValue GetOrAdd(TKey key, Func factory, out bool exists) + { + ref TValue? existing = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out exists); - if (!exists) - existing = factory(key); + if (!exists) + existing = factory(key); + + return ref existing!; + } + + public ref TValue GetOrAdd(TKey key, Func factory) => ref dictionary.GetOrAdd(key, factory, out _); - return ref existing!; } - public static ref TValue GetOrAdd(this Dictionary dictionary, - TKey key, Func factory) - where TKey : notnull => - ref GetOrAdd(dictionary, key, factory, out _); + /// The dictionary whose values will be returned and cleared. + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary, which must implement . + extension(IDictionary dictionary) where TValue : class, IReturnable + { + /// + /// Returns all values in the dictionary to their pool by calling on each value, + /// then clears the dictionary. + /// + /// + /// Use this method when you need to both return pooled objects and clear the dictionary in one operation. + /// + public void ResetAndClear() + { + foreach (TValue value in dictionary.Values) + { + value.Return(); + } + dictionary.Clear(); + } + } + + extension(Dictionary.AlternateLookup dictionary) + where TKey : notnull where TAlternateKey : notnull, allows ref struct + { + public bool TryRemove(TAlternateKey key, [MaybeNullWhen(false)] out TValue value) => + dictionary.Remove(key, out _, out value); + } } diff --git a/src/Nethermind/Nethermind.Core/Collections/HashHelpers.cs b/src/Nethermind/Nethermind.Core/Collections/HashHelpers.cs deleted file mode 100644 index e998398d5e13..000000000000 --- a/src/Nethermind/Nethermind.Core/Collections/HashHelpers.cs +++ /dev/null @@ -1,132 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.Serialization; -using System.Threading; - -namespace Nethermind.Core.Collections -{ - /// - /// Adapted from .net source code. - /// - internal static class HashHelpers - { - public const uint HashCollisionThreshold = 100; - - // This is the maximum prime smaller than Array.MaxLength. - public const int MaxPrimeArrayLength = 0x7FFFFFC3; - - public const int HashPrime = 101; - - // Table of prime numbers to use as hash table sizes. - // A typical resize algorithm would pick the smallest prime number in this array - // that is larger than twice the previous capacity. - // Suppose our Hashtable currently has capacity x and enough elements are added - // such that a resize needs to occur. Resizing first computes 2x then finds the - // first prime in the table greater than 2x, i.e. if primes are ordered - // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. - // Doubling is important for preserving the asymptotic complexity of the - // hashtable operations such as add. Having a prime guarantees that double - // hashing does not lead to infinite loops. IE, your hash function will be - // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. - // We prefer the low computation costs of higher prime numbers over the increased - // memory allocation of a fixed prime number i.e. when right sizing a HashSet. - private static readonly int[] s_primes = - { - 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, - 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, - 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, - 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, - 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 - }; - - public static bool IsPrime(int candidate) - { - if ((candidate & 1) != 0) - { - int limit = (int)Math.Sqrt(candidate); - for (int divisor = 3; divisor <= limit; divisor += 2) - { - if ((candidate % divisor) == 0) - return false; - } - return true; - } - return candidate == 2; - } - - public static int GetPrime(int min) - { - if (min < 0) - throw new ArgumentException("Hashtable's capacity overflowed and went negative. Check load factor, capacity and the current size of the table."); - - foreach (int prime in s_primes) - { - if (prime >= min) - return prime; - } - - // Outside of our predefined table. Compute the hard way. - for (int i = (min | 1); i < int.MaxValue; i += 2) - { - if (IsPrime(i) && ((i - 1) % HashPrime != 0)) - return i; - } - return min; - } - - // Returns size of hashtable to grow to. - public static int ExpandPrime(int oldSize) - { - int newSize = 2 * oldSize; - - // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow. - // Note that this check works even when _items.Length overflowed thanks to the (uint) cast - if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) - { - Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); - return MaxPrimeArrayLength; - } - - return GetPrime(newSize); - } - - /// Returns approximate reciprocal of the divisor: ceil(2**64 / divisor). - /// This should only be used on 64-bit. - public static ulong GetFastModMultiplier(uint divisor) => - ulong.MaxValue / divisor + 1; - - /// Performs a mod operation using the multiplier pre-computed with . - /// This should only be used on 64-bit. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint FastMod(uint value, uint divisor, ulong multiplier) - { - // We use modified Daniel Lemire's fastmod algorithm (https://github.com/dotnet/runtime/pull/406), - // which allows to avoid the long multiplication if the divisor is less than 2**31. - Debug.Assert(divisor <= int.MaxValue); - - // This is equivalent of (uint)Math.BigMul(multiplier * value, divisor, out _). This version - // is faster than BigMul currently because we only need the high bits. - uint highbits = (uint)(((((multiplier * value) >> 32) + 1) * divisor) >> 32); - - Debug.Assert(highbits == value % divisor); - return highbits; - } - - private static ConditionalWeakTable? s_serializationInfoTable; - - public static ConditionalWeakTable SerializationInfoTable - { - get - { - if (s_serializationInfoTable is null) - Interlocked.CompareExchange(ref s_serializationInfoTable, new ConditionalWeakTable(), null); - - return s_serializationInfoTable; - } - } - } -} diff --git a/src/Nethermind/Nethermind.Core/Collections/IHash64bit.cs b/src/Nethermind/Nethermind.Core/Collections/IHash64bit.cs new file mode 100644 index 000000000000..746146576cf4 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Collections/IHash64bit.cs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Core.Collections; + +/// +/// Provides a 64-bit hash code for high-performance caching with reduced collision probability. +/// +/// +/// Types implementing this interface can be used with caches that require extended hash bits +/// for collision resistance (for example, Seqlock-based caches that use additional bits beyond +/// the standard hash code for bucket indexing and collision detection). +/// The 64-bit hash should have good distribution across all bits. +/// +public interface IHash64bit +{ + /// + /// Returns a 64-bit hash code for the current instance. + /// + /// A 64-bit hash code with good distribution across all bits. + long GetHashCode64(); + bool Equals(in TKey other); +} diff --git a/src/Nethermind/Nethermind.Core/Collections/InsertionBehavior.cs b/src/Nethermind/Nethermind.Core/Collections/InsertionBehavior.cs deleted file mode 100644 index 90f4431ec102..000000000000 --- a/src/Nethermind/Nethermind.Core/Collections/InsertionBehavior.cs +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -internal enum InsertionBehavior : byte -{ - None, - OverwriteExisting, - ThrowOnExisting, -} diff --git a/src/Nethermind/Nethermind.Core/Collections/SeqlockCache.cs b/src/Nethermind/Nethermind.Core/Collections/SeqlockCache.cs new file mode 100644 index 000000000000..6ce764fa46b6 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Collections/SeqlockCache.cs @@ -0,0 +1,400 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.X86; +using System.Threading; + +namespace Nethermind.Core.Collections; + +/// +/// A high-performance 2-way skew-associative cache using a seqlock-style header per entry. +/// +/// Design goals: +/// - Lock-free reads (seqlock pattern) - readers never take locks. +/// - Best-effort writes - writers skip on contention. +/// - O(1) logical Clear() via a global epoch (no per-entry zeroing). +/// - 2-way skew-associative: each way uses independent hash bits for set indexing, +/// breaking correlation between ways ("power of two choices"). Keys that collide +/// in way 0 scatter to different sets in way 1, virtually eliminating conflict misses. +/// +/// Hash bit partitioning (64-bit hash): +/// Bits 0-13: way 0 set index (14 bits) +/// Bits 14-41: hash signature stored in header (28 bits) +/// Bits 42-55: way 1 set index (14 bits, independent from way 0) +/// +/// Header layout (64-bit): +/// [Lock:1][Epoch:26][Hash:28][Seq:8][Occ:1] +/// - Lock (bit 63): set during writes - readers retry/miss +/// - Epoch (bits 37-62): global epoch tag - changes on Clear() +/// - Hash (bits 9-36): per-bucket hash signature (28 bits) +/// - Seq (bits 1- 8): per-entry sequence counter (8 bits) - increments on every successful write +/// - Occ (bit 0): occupied flag - set when slot contains valid data (value may still be null) +/// +/// Array layout: [way0_set0..way0_set16383, way1_set0..way1_set16383] (split, not interleaved). +/// +/// The key type (struct implementing IHash64bit) +/// The value type (reference type, nullable allowed) +public sealed class SeqlockCache + where TKey : struct, IHash64bit + where TValue : class? +{ + /// + /// Number of sets. Must be a power of 2 for mask operations. + /// 16384 sets × 2 ways = 32768 total entries. + /// + private const int Sets = 1 << 14; // 16384 + private const int SetMask = Sets - 1; + + // Header bit layout: + // [Lock:1][Epoch:26][Hash:28][Seq:8][Occ:1] + + private const long LockMarker = unchecked((long)0x8000_0000_0000_0000); // bit 63 + + private const int EpochShift = 37; + private const long EpochMask = 0x7FFF_FFE0_0000_0000; // bits 37-62 (26 bits) + + private const long HashMask = 0x0000_0001_FFFF_FE00; // bits 9-36 (28 bits) + + private const long SeqMask = 0x0000_0000_0000_01FE; // bits 1-8 (8 bits) + private const long SeqInc = 0x0000_0000_0000_0002; // +1 in seq field + + private const long OccupiedBit = 1L; // bit 0 + + // Mask of all "identity" bits for an entry, excluding Lock and Seq. + private const long TagMask = EpochMask | HashMask | OccupiedBit; + + // Mask for checking if an entry is live in the current epoch. + private const long EpochOccMask = EpochMask | OccupiedBit; + + // With 14-bit set index (bits 0-13) for way 0, hash signature needs bits 14+. + // HashShift=5 maps header bits 9-36 to original bits 14-41, avoiding overlap with both ways. + private const int HashShift = 5; + + // Way 1 uses bits 42-55 of the original hash (completely independent from way 0's bits 0-13). + private const int Way1Shift = 42; + + /// + /// Array of entries: [way0_set0..way0_setN, way1_set0..way1_setN]. + /// Split layout ensures each way is a contiguous block for better prefetch behavior. + /// + private readonly Entry[] _entries; + + /// + /// Current epoch counter (unshifted, informational / debugging). + /// + private long _epoch; + + /// + /// Pre-shifted epoch tag: (_epoch << EpochShift) & EpochMask. + /// Readers use this directly to avoid shift/mask in the hot path. + /// + private long _shiftedEpoch; + + public SeqlockCache() + { + _entries = new Entry[Sets << 1]; // Sets * 2 + _epoch = 0; + _shiftedEpoch = 0; + } + + /// + /// Tries to get a value from the cache using a seqlock pattern (lock-free reads). + /// Checks both ways of the target set for the key. + /// + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe bool TryGetValue(in TKey key, out TValue? value) + { + long hashCode = key.GetHashCode64(); + int idx0 = (int)hashCode & SetMask; + int idx1 = Sets + ((int)(hashCode >> Way1Shift) & SetMask); + + long epochTag = Volatile.Read(ref _shiftedEpoch); + long hashPart = (hashCode >> HashShift) & HashMask; + long expectedTag = epochTag | hashPart | OccupiedBit; + + ref Entry entries = ref MemoryMarshal.GetArrayDataReference(_entries); + + // Prefetch way 1 while we check way 0 — hides L2/L3 latency for skew layout. + if (Sse.IsSupported) + { + Sse.PrefetchNonTemporal(Unsafe.AsPointer(ref Unsafe.Add(ref entries, idx1))); + } + + // === Way 0 === + ref Entry e0 = ref Unsafe.Add(ref entries, idx0); + long h1 = Volatile.Read(ref e0.HashEpochSeqLock); + + if ((h1 & (TagMask | LockMarker)) == expectedTag) + { + ref readonly TKey storedKey = ref e0.Key; + TValue? storedValue = e0.Value; + + long h2 = Volatile.Read(ref e0.HashEpochSeqLock); + if (h1 == h2 && storedKey.Equals(in key)) + { + value = storedValue; + return true; + } + } + + // === Way 1 === + ref Entry e1 = ref Unsafe.Add(ref entries, idx1); + long w1 = Volatile.Read(ref e1.HashEpochSeqLock); + + if ((w1 & (TagMask | LockMarker)) == expectedTag) + { + ref readonly TKey storedKey = ref e1.Key; + TValue? storedValue = e1.Value; + + long w2 = Volatile.Read(ref e1.HashEpochSeqLock); + if (w1 == w2 && storedKey.Equals(in key)) + { + value = storedValue; + return true; + } + } + + value = default; + return false; + } + + /// + /// Delegate-based factory that avoids copying large keys (passes by in). + /// Prefer this over Func<TKey, TValue?> when TKey is big (eg 48 bytes). + /// + public delegate TValue? ValueFactory(in TKey key); + + /// + /// Gets a value from the cache, or adds it using the factory if not present. + /// + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TValue? GetOrAdd(in TKey key, ValueFactory valueFactory) + { + long hashCode = key.GetHashCode64(); + int idx0 = (int)hashCode & SetMask; + int idx1 = Sets + ((int)(hashCode >> Way1Shift) & SetMask); + long hashPart = (hashCode >> HashShift) & HashMask; + + if (TryGetValueCore(in key, idx0, idx1, hashPart, out TValue? value)) + { + return value; + } + + return GetOrAddMiss(in key, valueFactory, idx0, idx1, hashPart); + } + + /// + /// Cold path for GetOrAdd: invokes factory and stores the result. + /// Kept out-of-line so the hot path (cache hit) compiles to a lean method body + /// with minimal register saves and stack frame. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private TValue? GetOrAddMiss(in TKey key, ValueFactory valueFactory, int idx0, int idx1, long hashPart) + { + TValue? value = valueFactory(in key); + SetCore(in key, value, idx0, idx1, hashPart); + return value; + } + + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe bool TryGetValueCore(in TKey key, int idx0, int idx1, long hashPart, out TValue? value) + { + long epochTag = Volatile.Read(ref _shiftedEpoch); + long expectedTag = epochTag | hashPart | OccupiedBit; + + ref Entry entries = ref MemoryMarshal.GetArrayDataReference(_entries); + + if (Sse.IsSupported) + { + Sse.PrefetchNonTemporal(Unsafe.AsPointer(ref Unsafe.Add(ref entries, idx1))); + } + + // Way 0 + ref Entry e0 = ref Unsafe.Add(ref entries, idx0); + long h1 = Volatile.Read(ref e0.HashEpochSeqLock); + + if ((h1 & (TagMask | LockMarker)) == expectedTag) + { + ref readonly TKey storedKey = ref e0.Key; + TValue? storedValue = e0.Value; + + long h2 = Volatile.Read(ref e0.HashEpochSeqLock); + if (h1 == h2 && storedKey.Equals(in key)) + { + value = storedValue; + return true; + } + } + + // Way 1 + ref Entry e1 = ref Unsafe.Add(ref entries, idx1); + long w1 = Volatile.Read(ref e1.HashEpochSeqLock); + + if ((w1 & (TagMask | LockMarker)) == expectedTag) + { + ref readonly TKey storedKey = ref e1.Key; + TValue? storedValue = e1.Value; + + long w2 = Volatile.Read(ref e1.HashEpochSeqLock); + if (w1 == w2 && storedKey.Equals(in key)) + { + value = storedValue; + return true; + } + } + + value = default; + return false; + } + + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetCore(in TKey key, TValue? value, int idx0, int idx1, long hashPart) + { + long epochTag = Volatile.Read(ref _shiftedEpoch); + long tagToStore = epochTag | hashPart | OccupiedBit; + long epochOccTag = epochTag | OccupiedBit; + + ref Entry entries = ref MemoryMarshal.GetArrayDataReference(_entries); + ref Entry e0 = ref Unsafe.Add(ref entries, idx0); + + long h0 = Volatile.Read(ref e0.HashEpochSeqLock); + + // === Way 0: check for matching key === + if (h0 >= 0 && (h0 & TagMask) == tagToStore) + { + ref readonly TKey k0 = ref e0.Key; + TValue? v0 = e0.Value; + + long h0_2 = Volatile.Read(ref e0.HashEpochSeqLock); + if (h0 == h0_2 && k0.Equals(in key)) + { + if (ReferenceEquals(v0, value)) return; // fast-path: same key+value, no-op + WriteEntry(ref e0, h0_2, in key, value, tagToStore); + return; + } + h0 = h0_2; + } + + // === Way 1: check for matching key === + ref Entry e1 = ref Unsafe.Add(ref entries, idx1); + long h1 = Volatile.Read(ref e1.HashEpochSeqLock); + + if (h1 >= 0 && (h1 & TagMask) == tagToStore) + { + ref readonly TKey k1 = ref e1.Key; + TValue? v1 = e1.Value; + + long h1_2 = Volatile.Read(ref e1.HashEpochSeqLock); + if (h1 == h1_2 && k1.Equals(in key)) + { + if (ReferenceEquals(v1, value)) return; // fast-path: same key+value, no-op + WriteEntry(ref e1, h1_2, in key, value, tagToStore); + return; + } + h1 = h1_2; + } + + // === Key not in either way. Evict into an available slot. === + // Priority: stale/empty unlocked > live (alternating by hash bit) > any unlocked > skip. + // The decision tree selects which way to evict into, then issues a single WriteEntry call. + bool h0Live = h0 >= 0 && (h0 & EpochOccMask) == epochOccTag; + bool h1Live = h1 >= 0 && (h1 & EpochOccMask) == epochOccTag; + + bool pick0; + if (!h0Live && h0 >= 0) pick0 = true; + else if (!h1Live && h1 >= 0) pick0 = false; + else if (h0Live && h1Live) pick0 = (hashPart & (1L << 9)) != 0; + else if (h0 >= 0) pick0 = true; + else if (h1 >= 0) pick0 = false; + else return; // both locked, skip + + WriteEntry( + ref pick0 ? ref e0 : ref e1, + pick0 ? h0 : h1, + in key, value, tagToStore); + } + + /// + /// Sets a key-value pair in the cache. + /// Checks both ways of the target set for an existing key match before evicting. + /// + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Set(in TKey key, TValue? value) + { + long hashCode = key.GetHashCode64(); + int idx0 = (int)hashCode & SetMask; + int idx1 = Sets + ((int)(hashCode >> Way1Shift) & SetMask); + long hashPart = (hashCode >> HashShift) & HashMask; + + SetCore(in key, value, idx0, idx1, hashPart); + } + + /// + /// Attempts a CAS-guarded write to a single entry. + /// Kept out-of-line: the CAS atomic dominates latency, so call overhead is invisible, + /// while de-duplication reclaims ~350 bytes of inlined copies across SetCore call sites. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private static void WriteEntry(ref Entry entry, long existing, in TKey key, TValue? value, long tagToStore) + { + if (existing < 0) return; // locked + + long newSeq = ((existing & SeqMask) + SeqInc) & SeqMask; + long lockedHeader = tagToStore | newSeq | LockMarker; + + if (Interlocked.CompareExchange(ref entry.HashEpochSeqLock, lockedHeader, existing) != existing) + { + return; + } + + entry.Key = key; + entry.Value = value; + + Volatile.Write(ref entry.HashEpochSeqLock, tagToStore | newSeq); + } + + /// + /// Clears all cached entries by incrementing the global epoch tag (O(1)). + /// Entries with stale epochs are treated as empty on subsequent lookups. + /// + public void Clear() + { + long oldShifted = Volatile.Read(ref _shiftedEpoch); + + while (true) + { + long oldEpoch = (oldShifted & EpochMask) >> EpochShift; + long newEpoch = oldEpoch + 1; + long newShifted = (newEpoch << EpochShift) & EpochMask; + + long prev = Interlocked.CompareExchange(ref _shiftedEpoch, newShifted, oldShifted); + if (prev == oldShifted) + { + Volatile.Write(ref _epoch, newEpoch); + return; + } + + oldShifted = prev; + } + } + + /// + /// Cache entry struct. + /// Header is a single 64-bit field to keep the seqlock control word in one atomic unit. + /// + [StructLayout(LayoutKind.Sequential)] + private struct Entry + { + public long HashEpochSeqLock; // [Lock|Epoch|Hash|Seq|Occ] + public TKey Key; + public TValue? Value; + } +} diff --git a/src/Nethermind/Nethermind.Core/Collections/SortedRealList.cs b/src/Nethermind/Nethermind.Core/Collections/SortedRealList.cs deleted file mode 100644 index 02d66af6e651..000000000000 --- a/src/Nethermind/Nethermind.Core/Collections/SortedRealList.cs +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; - -namespace Nethermind.Core.Collections -{ - public class SortedRealList : SortedList, IList> where TKey : notnull - { - // Constructs a new sorted list. The sorted list is initially empty and has - // a capacity of zero. Upon adding the first element to the sorted list the - // capacity is increased to DefaultCapacity, and then increased in multiples of two as - // required. The elements of the sorted list are ordered according to the - // IComparable interface, which must be implemented by the keys of - // all entries added to the sorted list. - public SortedRealList() { } - - // Constructs a new sorted list. The sorted list is initially empty and has - // a capacity of zero. Upon adding the first element to the sorted list the - // capacity is increased to 16, and then increased in multiples of two as - // required. The elements of the sorted list are ordered according to the - // IComparable interface, which must be implemented by the keys of - // all entries added to the sorted list. - // - public SortedRealList(int capacity) : base(capacity) { } - - // Constructs a new sorted list with a given IComparer - // implementation. The sorted list is initially empty and has a capacity of - // zero. Upon adding the first element to the sorted list the capacity is - // increased to 16, and then increased in multiples of two as required. The - // elements of the sorted list are ordered according to the given - // IComparer implementation. If comparer is null, the - // elements are compared to each other using the IComparable - // interface, which in that case must be implemented by the keys of all - // entries added to the sorted list. - // - public SortedRealList(IComparer? comparer) : base(comparer) - { - } - - // Constructs a new sorted dictionary with a given IComparer - // implementation and a given initial capacity. The sorted list is - // initially empty, but will have room for the given number of elements - // before any reallocations are required. The elements of the sorted list - // are ordered according to the given IComparer implementation. If - // comparer is null, the elements are compared to each other using - // the IComparable interface, which in that case must be implemented - // by the keys of all entries added to the sorted list. - // - public SortedRealList(int capacity, IComparer? comparer) : base(capacity, comparer) { } - - // Constructs a new sorted list containing a copy of the entries in the - // given dictionary. The elements of the sorted list are ordered according - // to the IComparable interface, which must be implemented by the - // keys of all entries in the given dictionary as well as keys - // subsequently added to the sorted list. - // - public SortedRealList(IDictionary dictionary) : base(dictionary) { } - - // Constructs a new sorted list containing a copy of the entries in the - // given dictionary. The elements of the sorted list are ordered according - // to the given IComparer implementation. If comparer is - // null, the elements are compared to each other using the - // IComparable interface, which in that case must be implemented - // by the keys of all entries in the given dictionary as well as keys - // subsequently added to the sorted list. - // - public SortedRealList(IDictionary dictionary, IComparer? comparer) : base(dictionary, comparer) { } - - public int IndexOf(KeyValuePair item) => IndexOfKey(item.Key); - - public void Insert(int index, KeyValuePair item) => this.TryAdd(item.Key, item.Value); - - public KeyValuePair this[int index] - { - get => new(Keys[index], Values[index]); - set => this.TryAdd(value.Key, value.Value); - } - } -} diff --git a/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs b/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs index 0ee2476c0762..4b8566b3e4ba 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using System.Text.Json.Serialization; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -25,7 +26,9 @@ namespace Nethermind.Core.Crypto public const int MemorySize = 32; public static int Length => MemorySize; + [JsonIgnore] public Span BytesAsSpan => MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in _bytes), 1)); + [JsonIgnore] public ReadOnlySpan Bytes => MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in _bytes), 1)); public static implicit operator ValueHash256?(Hash256? keccak) => keccak?.ValueHash256; @@ -129,6 +132,7 @@ public sealed class Hash256 : IEquatable, IComparable public ref readonly ValueHash256 ValueHash256 => ref _hash256; + [JsonIgnore] public Span Bytes => MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in _hash256), 1)); public Hash256(string hexString) diff --git a/src/Nethermind/Nethermind.Core/Crypto/Signature.cs b/src/Nethermind/Nethermind.Core/Crypto/Signature.cs index 35834c23904b..61f152101c09 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/Signature.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/Signature.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using System.Text.Json.Serialization; using Nethermind.Core.Attributes; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -58,6 +59,7 @@ public Signature(string hexString) : this(Core.Extensions.Bytes.FromHexString(hexString)) { } + [JsonIgnore] public Span Bytes => MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref _signature, 1)); public override Memory Memory => CreateMemory(64); @@ -70,8 +72,10 @@ public Signature(string hexString) public static byte GetRecoveryId(ulong v) => v <= VOffset + 1 ? (byte)(v - VOffset) : (byte)(1 - v % 2); public Memory R => Memory.Slice(0, 32); + [JsonIgnore] public ReadOnlySpan RAsSpan => Bytes.Slice(0, 32); public Memory S => Memory.Slice(32, 32); + [JsonIgnore] public ReadOnlySpan SAsSpan => Bytes.Slice(32, 32); [Todo("Change signature to store 65 bytes and just slice it for normal Bytes.")] diff --git a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs index 8e395ecb7208..4fe4d044ee55 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs @@ -73,64 +73,17 @@ public override int Compare(byte[]? x, byte[]? y) if (x is null) { - return y is null ? 0 : 1; + return y is null ? 0 : -1; } - if (y is null) - { - return -1; - } - - if (x.Length == 0) - { - return y.Length == 0 ? 0 : 1; - } - - for (int i = 0; i < x.Length; i++) - { - if (y.Length <= i) - { - return -1; - } + if (y is null) return 1; - int result = x[i].CompareTo(y[i]); - if (result != 0) - { - return result; - } - } - - return y.Length > x.Length ? 1 : 0; + return x.SequenceCompareTo(y); } public static int Compare(ReadOnlySpan x, ReadOnlySpan y) { - if (Unsafe.AreSame(ref MemoryMarshal.GetReference(x), ref MemoryMarshal.GetReference(y)) && - x.Length == y.Length) - { - return 0; - } - - if (x.Length == 0) - { - return y.Length == 0 ? 0 : 1; - } - - for (int i = 0; i < x.Length; i++) - { - if (y.Length <= i) - { - return -1; - } - - int result = x[i].CompareTo(y[i]); - if (result != 0) - { - return result; - } - } - - return y.Length > x.Length ? 1 : 0; + return x.SequenceCompareTo(y); } } diff --git a/src/Nethermind/Nethermind.Core/Extensions/DictionaryExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/DictionaryExtensions.cs deleted file mode 100644 index 6168e96121f4..000000000000 --- a/src/Nethermind/Nethermind.Core/Extensions/DictionaryExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Nethermind.Core.Resettables; - -namespace Nethermind.Core.Extensions; - -public static class DictionaryExtensions -{ - /// - /// Returns all values in the dictionary to their pool by calling on each value, - /// then clears the dictionary. - /// - /// The type of the keys in the dictionary. - /// The type of the values in the dictionary, which must implement . - /// The dictionary whose values will be returned and cleared. - /// - /// Use this method when you need to both return pooled objects and clear the dictionary in one operation. - /// - public static void ResetAndClear(this IDictionary dictionary) - where TValue : class, IReturnable - { - foreach (TValue value in dictionary.Values) - { - value.Return(); - } - dictionary.Clear(); - } -} diff --git a/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs index 1ecd2adc0d84..3f4a9e0c2919 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs @@ -29,13 +29,6 @@ public static UInt256 GWei(this int @this) return (uint)@this * Unit.GWei; } - public static byte[] ToByteArray(this int value) - { - byte[] bytes = new byte[sizeof(int)]; - BinaryPrimitives.WriteInt32BigEndian(bytes, value); - return bytes; - } - public static byte[] ToBigEndianByteArray(this uint value) { byte[] bytes = BitConverter.GetBytes(value); @@ -49,4 +42,14 @@ public static byte[] ToBigEndianByteArray(this uint value) public static byte[] ToBigEndianByteArray(this int value) => ToBigEndianByteArray((uint)value); + + public static byte[] ToLittleEndianByteArray(this uint value) + { + byte[] bytes = new byte[sizeof(uint)]; + BinaryPrimitives.WriteUInt32LittleEndian(bytes, value); + return bytes; + } + + public static byte[] ToLittleEndianByteArray(this int value) + => ToLittleEndianByteArray((uint)value); } diff --git a/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs index 5118ea293bb2..7ae71df3f508 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs @@ -7,6 +7,9 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using Arm = System.Runtime.Intrinsics.Arm; +using x64 = System.Runtime.Intrinsics.X86; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; @@ -19,6 +22,8 @@ public static class SpanExtensions // the performance of the network as a whole. private static readonly uint s_instanceRandom = (uint)System.Security.Cryptography.RandomNumberGenerator.GetInt32(int.MinValue, int.MaxValue); + internal static uint ComputeSeed(int len) => s_instanceRandom + (uint)len; + public static string ToHexString(this in Memory memory, bool withZeroX = false) { return ToHexString(memory.Span, withZeroX, false, false); @@ -224,149 +229,286 @@ public static ArrayPoolListRef ToPooledListRef(this in ReadOnlySpan spa [SkipLocalsInit] public static int FastHash(this ReadOnlySpan input) { - // Fast hardware-accelerated, non-cryptographic hash. - // Core idea: CRC32C is extremely cheap on CPUs with SSE4.2/ARM CRC, - // and gives good diffusion for hashing. We then optionally add extra - // mixing to reduce "CRC linearity" artifacts. - int len = input.Length; - - // Contract choice: empty input hashes to 0. - // (Also avoids doing any ref work on an empty span.) if (len == 0) return 0; - // Using ref + Unsafe.ReadUnaligned lets the JIT hoist bounds checks - // and keep the hot loop tight. - ref byte start = ref MemoryMarshal.GetReference(input); - // Seed with an instance-random value so attackers cannot trivially - // engineer lots of same-bucket keys. Mixing in length makes "same prefix, - // different length" less correlated (CRC alone can be length-sensitive). + ref byte start = ref MemoryMarshal.GetReference(input); uint seed = s_instanceRandom + (uint)len; - // Small: 1-7 bytes. - // Using the tail routine here avoids building a synthetic - // 64-bit value with shifts/byte-permute. - if (len < 8) + if (len >= 16) { - uint small = CrcTailOrdered(seed, ref start, len); - // FinalMix breaks some remaining linearity and improves avalanche for tiny inputs. - return (int)FinalMix(small); + if (x64.Aes.IsSupported) return FastHashAesX64(ref start, len, seed); + if (Arm.Aes.IsSupported) return FastHashAesArm(ref start, len, seed); } - // Medium: 8-31 bytes. - // A single CRC lane is usually fine here - overhead dominates, - // and latency hiding is less important. - if (len < 32) + return FastHashCrc(ref start, len, seed); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [SkipLocalsInit] + internal static int FastHashAesX64(ref byte start, int len, uint seed) + { + Vector128 seedVec = Vector128.CreateScalar(seed).AsByte(); + Vector128 acc0 = Unsafe.As>(ref start) ^ seedVec; + + if (len > 64) { - uint h = seed; - ref byte p = ref start; + Vector128 acc1 = Unsafe.As>(ref Unsafe.Add(ref start, 16)) ^ seedVec; + Vector128 acc2 = Unsafe.As>(ref Unsafe.Add(ref start, 32)) ^ seedVec; + Vector128 acc3 = Unsafe.As>(ref Unsafe.Add(ref start, 48)) ^ seedVec; - // Process as many full 64-bit words as possible. - // "& ~7" is a cheap round-down-to-multiple-of-8 (no division/mod). - int full = len & ~7; - int tail = len - full; + ref byte p = ref Unsafe.Add(ref start, 64); + int remaining = len - 64; - // Streaming CRC over 8-byte chunks. - // ReadUnaligned keeps us safe for arbitrary input alignment. - for (int i = 0; i < full; i += 8) + while (remaining >= 64) { - h = BitOperations.Crc32C(h, Unsafe.ReadUnaligned(ref p)); - p = ref Unsafe.Add(ref p, 8); + acc0 = x64.Aes.Encrypt(Unsafe.As>(ref p), acc0); + acc1 = x64.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 16)), acc1); + acc2 = x64.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 32)), acc2); + acc3 = x64.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 48)), acc3); + + p = ref Unsafe.Add(ref p, 64); + remaining -= 64; } - // Hash remaining 1-7 bytes in strict order (no over-read). - if (tail != 0) - h = CrcTailOrdered(h, ref p, tail); + // Fold 4 lanes: 3 XOR + 1 AES (minimal serial latency) + acc0 ^= acc1; + acc2 ^= acc3; + acc0 ^= acc2; + acc0 = x64.Aes.Encrypt(seedVec, acc0); - // Final mixing for better bit diffusion than raw CRC, - // especially for shorter payloads. - return (int)FinalMix(h); + // Drain remaining 0-63 bytes + while (remaining >= 16) + { + acc0 = x64.Aes.Encrypt(Unsafe.As>(ref p), acc0); + p = ref Unsafe.Add(ref p, 16); + remaining -= 16; + } + + // Remaining 1-15 bytes: use CRC to avoid overlap with drain blocks + if (remaining > 0) + { + uint crc = seed; + if (remaining >= 8) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 8); + remaining -= 8; + } + if ((remaining & 4) != 0) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 4); + } + if ((remaining & 2) != 0) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 2); + } + if ((remaining & 1) != 0) + { + crc = BitOperations.Crc32C(crc, p); + } + acc0 = x64.Aes.Encrypt(Vector128.CreateScalar(crc).AsByte(), acc0); + } } + else if (len > 32) + { + ref byte p = ref Unsafe.Add(ref start, 16); + int remaining = len - 16; - // Large: 32+ bytes. - // Use multiple independent CRC accumulators ("lanes") to hide crc32 - // latency and increase ILP. CRC32C instructions have decent throughput - // but non-trivial latency; 4 lanes keeps the CPU busy. - uint h0 = seed; - uint h1 = seed ^ 0x9E3779B9u; // golden-ratio-ish constants to separate lanes - uint h2 = seed ^ 0x85EBCA6Bu; // constants borrowed from common finalizers (good bit dispersion) - uint h3 = seed ^ 0xC2B2AE35u; - - ref byte q = ref start; - - // Consume all full 64-bit words first. Tail (1-7 bytes) is handled later. - int aligned = len & ~7; - int remaining = aligned; - - // 64-byte unroll: - // - amortizes loop branch/compare overhead - // - feeds enough independent work to keep OoO cores busy - // - maps nicely onto cache line sized chunks - while (remaining >= 64) + while (remaining > 16) + { + acc0 = x64.Aes.Encrypt(Unsafe.As>(ref p), acc0); + p = ref Unsafe.Add(ref p, 16); + remaining -= 16; + } + + Vector128 last = Unsafe.As>(ref Unsafe.Add(ref start, len - 16)); + acc0 = x64.Aes.Encrypt(last, acc0); + } + else { - h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); - h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); - h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); - h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 24))); - - h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 32))); - h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 40))); - h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 48))); - h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 56))); - - q = ref Unsafe.Add(ref q, 64); - remaining -= 64; + Vector128 data = Unsafe.As>(ref Unsafe.Add(ref start, len - 16)); + acc0 = x64.Aes.Encrypt(data, acc0); } - // One more half-unroll for 32 bytes if present. - // Keeps the "drain" path short and avoids a smaller loop with more branches. - if (remaining >= 32) + ulong compressed = acc0.AsUInt64().GetElement(0) ^ acc0.AsUInt64().GetElement(1); + return (int)(uint)(compressed ^ (compressed >> 32)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [SkipLocalsInit] + internal static int FastHashAesArm(ref byte start, int len, uint seed) + { + Vector128 seedVec = Vector128.CreateScalar(seed).AsByte(); + Vector128 acc0 = Unsafe.As>(ref start) ^ seedVec; + + if (len > 64) { - h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); - h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); - h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); - h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 24))); + Vector128 acc1 = Unsafe.As>(ref Unsafe.Add(ref start, 16)) ^ seedVec; + Vector128 acc2 = Unsafe.As>(ref Unsafe.Add(ref start, 32)) ^ seedVec; + Vector128 acc3 = Unsafe.As>(ref Unsafe.Add(ref start, 48)) ^ seedVec; + + ref byte p = ref Unsafe.Add(ref start, 64); + int remaining = len - 64; + + while (remaining >= 64) + { + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref p), acc0)); + acc1 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 16)), acc1)); + acc2 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 32)), acc2)); + acc3 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 48)), acc3)); + + p = ref Unsafe.Add(ref p, 64); + remaining -= 64; + } + + acc0 ^= acc1; + acc2 ^= acc3; + acc0 ^= acc2; + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(seedVec, acc0)); + + while (remaining >= 16) + { + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref p), acc0)); + p = ref Unsafe.Add(ref p, 16); + remaining -= 16; + } - q = ref Unsafe.Add(ref q, 32); - remaining -= 32; + if (remaining > 0) + { + uint crc = seed; + if (remaining >= 8) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 8); + remaining -= 8; + } + if ((remaining & 4) != 0) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 4); + } + if ((remaining & 2) != 0) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 2); + } + if ((remaining & 1) != 0) + { + crc = BitOperations.Crc32C(crc, p); + } + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Vector128.CreateScalar(crc).AsByte(), acc0)); + } } + else if (len > 32) + { + ref byte p = ref Unsafe.Add(ref start, 16); + int remaining = len - 16; - // Drain any remaining full 64-bit words (0, 8, 16, or 24 bytes). - // This is branchy but only runs once, so it is cheaper than another loop. - if (remaining != 0) + while (remaining > 16) + { + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref p), acc0)); + p = ref Unsafe.Add(ref p, 16); + remaining -= 16; + } + + Vector128 last = Unsafe.As>(ref Unsafe.Add(ref start, len - 16)); + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(last, acc0)); + } + else { - // remaining is a multiple of 8 here. - if (remaining >= 8) h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); - if (remaining >= 16) h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); - if (remaining == 24) h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); + Vector128 data = Unsafe.As>(ref Unsafe.Add(ref start, len - 16)); + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(data, acc0)); } - // Fold lanes down to one 32-bit value. - // Rotates permute bit positions so each lane contributes differently. - // Adds (rather than XOR) deliberately introduce carries - // - CRC is linear over GF(2), and carry breaks that, making simple algebraic - // structure harder to exploit for collision clustering in hash tables. - h2 = BitOperations.RotateLeft(h2, 17) + BitOperations.RotateLeft(h3, 23); - h0 += BitOperations.RotateLeft(h1, 11); - uint hash = h2 + h0; - - // Handle tail bytes (1-7 bytes) that were not part of the 64-bit-aligned stream. - // This is exact, in-order processing - no overlap and no over-read. - int tailBytes = len - aligned; - if (tailBytes != 0) + ulong compressed = acc0.AsUInt64().GetElement(0) ^ acc0.AsUInt64().GetElement(1); + return (int)(uint)(compressed ^ (compressed >> 32)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [SkipLocalsInit] + internal static int FastHashCrc(ref byte start, int len, uint seed) + { + uint hash; + if (len < 16) { - ref byte tailRef = ref Unsafe.Add(ref start, aligned); - hash = CrcTailOrdered(hash, ref tailRef, tailBytes); + if (len >= 8) + { + ulong lo = Unsafe.ReadUnaligned(ref start); + ulong hi = Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, len - 8)); + uint h0 = BitOperations.Crc32C(seed, lo); + uint h1 = BitOperations.Crc32C(seed ^ 0x9E3779B9u, hi); + hash = h0 + BitOperations.RotateLeft(h1, 11); + } + else + { + hash = CrcTailOrdered(seed, ref start, len); + } + } + else + { + uint h0 = seed; + uint h1 = seed ^ 0x9E3779B9u; + uint h2 = seed ^ 0x85EBCA6Bu; + uint h3 = seed ^ 0xC2B2AE35u; + + ref byte q = ref start; + int aligned = len & ~7; + int remaining = aligned; + + while (remaining >= 64) + { + h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); + h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); + h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); + h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 24))); + + h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 32))); + h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 40))); + h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 48))); + h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 56))); + + q = ref Unsafe.Add(ref q, 64); + remaining -= 64; + } + + if (remaining >= 32) + { + h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); + h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); + h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); + h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 24))); + + q = ref Unsafe.Add(ref q, 32); + remaining -= 32; + } + + if (remaining >= 8) h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); + if (remaining >= 16) h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); + if (remaining >= 24) h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); + + h2 = BitOperations.RotateLeft(h2, 17) + BitOperations.RotateLeft(h3, 23); + h0 += BitOperations.RotateLeft(h1, 11); + hash = h2 + h0; + + int tailBytes = len - aligned; + if (tailBytes != 0) + { + ref byte tailRef = ref Unsafe.Add(ref start, aligned); + hash = CrcTailOrdered(hash, ref tailRef, tailBytes); + } } - // FinalMix breaks some remaining linearity and improves avalanche - return (int)FinalMix(hash); + hash ^= hash >> 16; + hash *= 0x9E3779B1u; + hash ^= hash >> 16; + return (int)hash; [MethodImpl(MethodImplOptions.AggressiveInlining)] static uint CrcTailOrdered(uint hash, ref byte p, int length) { - // length is 1..7 - // Process 4-2-1 bytes in natural order if ((length & 4) != 0) { hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned(ref p)); @@ -383,19 +525,70 @@ static uint CrcTailOrdered(uint hash, ref byte p, int length) } return hash; } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static uint FinalMix(uint x) + /// + /// Computes a very fast, non-cryptographic 64-bit hash of exactly 32 bytes. + /// + /// Reference to the first byte of the 32-byte input. + /// A 64-bit hash value with good distribution across all bits. + /// + /// Uses AES hardware acceleration when available, falls back to CRC32C otherwise. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long FastHash64For32Bytes(ref byte start) + { + uint seed = s_instanceRandom + 32; + + if (x64.Aes.IsSupported || Arm.Aes.IsSupported) + { + Vector128 key = Unsafe.As>(ref start); + Vector128 data = Unsafe.As>(ref Unsafe.Add(ref start, 16)); + key ^= Vector128.CreateScalar(seed).AsByte(); + Vector128 mixed = x64.Aes.IsSupported + ? x64.Aes.Encrypt(data, key) + : Arm.Aes.MixColumns(Arm.Aes.Encrypt(data, key)); + return (long)(mixed.AsUInt64().GetElement(0) ^ mixed.AsUInt64().GetElement(1)); + } + + // Fallback: CRC32C-based 64-bit hash + ulong h0 = BitOperations.Crc32C(seed, Unsafe.ReadUnaligned(ref start)); + ulong h1 = BitOperations.Crc32C(seed ^ 0x9E3779B9u, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 8))); + ulong h2 = BitOperations.Crc32C(seed ^ 0x85EBCA6Bu, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 16))); + ulong h3 = BitOperations.Crc32C(seed ^ 0xC2B2AE35u, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 24))); + return (long)((h0 | (h1 << 32)) ^ (h2 | (h3 << 32))); + } + + /// + /// Computes a very fast, non-cryptographic 64-bit hash of exactly 20 bytes (Address size). + /// + /// Reference to the first byte of the 20-byte input. + /// A 64-bit hash value with good distribution across all bits. + /// + /// Uses AES hardware acceleration when available, falls back to CRC32C otherwise. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long FastHash64For20Bytes(ref byte start) + { + uint seed = s_instanceRandom + 20; + + if (x64.Aes.IsSupported || Arm.Aes.IsSupported) { - // A tiny finalizer to improve avalanche: - // - xor-fold high bits down - // - multiply by an odd constant to spread changes across bits - // - xor-fold again to propagate the multiply result - x ^= x >> 16; - x *= 0x9E3779B1u; - x ^= x >> 16; - return x; + Vector128 key = Unsafe.As>(ref start); + uint last4 = Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 16)); + Vector128 data = Vector128.CreateScalar(last4).AsByte(); + key ^= Vector128.CreateScalar(seed).AsByte(); + Vector128 mixed = x64.Aes.IsSupported + ? x64.Aes.Encrypt(data, key) + : Arm.Aes.MixColumns(Arm.Aes.Encrypt(data, key)); + return (long)(mixed.AsUInt64().GetElement(0) ^ mixed.AsUInt64().GetElement(1)); } + + // Fallback: CRC32C-based 64-bit hash + ulong h0 = BitOperations.Crc32C(seed, Unsafe.ReadUnaligned(ref start)); + ulong h1 = BitOperations.Crc32C(seed ^ 0x9E3779B9u, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 8))); + uint h2 = BitOperations.Crc32C(seed ^ 0x85EBCA6Bu, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 16))); + return (long)((h0 | (h1 << 32)) ^ ((ulong)h2 * 0x9E3779B97F4A7C15)); } } } diff --git a/src/Nethermind/Nethermind.Core/IKeyValueStore.cs b/src/Nethermind/Nethermind.Core/IKeyValueStore.cs index d80cfc3d40ae..d30dbcfe87df 100644 --- a/src/Nethermind/Nethermind.Core/IKeyValueStore.cs +++ b/src/Nethermind/Nethermind.Core/IKeyValueStore.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using Nethermind.Core.Buffers; using Nethermind.Core.Extensions; namespace Nethermind.Core @@ -22,10 +24,12 @@ public interface IReadOnlyKeyValueStore byte[]? Get(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None); /// - /// Return span. Must call `DangerousReleaseMemory` or there can be some leak. + /// Return span. Must call after use to avoid memory leaks. + /// Prefer using which handles release automatically via disposal. /// - /// - /// Can return null or empty Span on missing key + /// Key whose associated value should be read. + /// Read behavior flags that control how the value is retrieved. + /// Can return null or empty Span on a missing key Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => Get(key, flags); /// @@ -63,6 +67,19 @@ bool KeyExists(ReadOnlySpan key) } void DangerousReleaseMemory(in ReadOnlySpan span) { } + + /// + /// Returns a MemoryManager wrapping the value for the given key. + /// The MemoryManager must be disposed of when done to release any underlying resources. + /// + /// Key whose associated value should be read. + /// Read behavior flags that control how the value is retrieved. + /// A MemoryManager wrapping the value or null if the key doesn't exist. + MemoryManager? GetOwnedMemory(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + { + byte[]? data = Get(key, flags); + return data is null or { Length: 0 } ? null : new ArrayMemoryManager(data); + } } public interface IReadOnlyNativeKeyValueStore diff --git a/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs b/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs index 4edda62a99ba..1d1c0aa88199 100644 --- a/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs +++ b/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs @@ -185,16 +185,69 @@ public override void Write( Convert(writer, bytes, skipLeadingZeros: false); } + /// + /// Writes bytes as a hex string value (e.g. "0xabcd") using WriteRawValue. + /// [SkipLocalsInit] public static void Convert(Utf8JsonWriter writer, ReadOnlySpan bytes, bool skipLeadingZeros = true, bool addHexPrefix = true) { - Convert(writer, - bytes, - static (w, h) => w.WriteRawValue(h, skipInputValidation: true), skipLeadingZeros, addHexPrefix: addHexPrefix); + int leadingNibbleZeros = skipLeadingZeros ? bytes.CountLeadingNibbleZeros() : 0; + int nibblesCount = bytes.Length * 2; + + if (skipLeadingZeros && nibblesCount is not 0 && leadingNibbleZeros == nibblesCount) + { + WriteZeroValue(writer); + return; + } + + int prefixLength = addHexPrefix ? 2 : 0; + // +2 for surrounding quotes: "0xABCD..." + int rawLength = nibblesCount - leadingNibbleZeros + prefixLength + 2; + + byte[]? array = null; + Unsafe.SkipInit(out HexBuffer256 buffer); + Span hex = rawLength <= 256 + ? MemoryMarshal.CreateSpan(ref Unsafe.As(ref buffer), 256) + : (array = ArrayPool.Shared.Rent(rawLength)); + hex = hex[..rawLength]; + + // Build the JSON string value directly: "0x" + ref byte hexRef = ref MemoryMarshal.GetReference(hex); + hexRef = (byte)'"'; + int start = 1; + if (addHexPrefix) + { + Unsafe.As(ref Unsafe.Add(ref hexRef, 1)) = HexPrefix; + start = 3; + } + Unsafe.Add(ref hexRef, rawLength - 1) = (byte)'"'; + + int offset = leadingNibbleZeros >>> 1; + MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref MemoryMarshal.GetReference(bytes), offset), bytes.Length - offset) + .OutputBytesToByteHex( + MemoryMarshal.CreateSpan(ref Unsafe.Add(ref hexRef, start), rawLength - 1 - start), + extraNibble: (leadingNibbleZeros & 1) != 0); + // Hex chars (0-9, a-f) never need JSON escaping — bypass encoder entirely + writer.WriteRawValue(hex, skipInputValidation: true); + + if (array is not null) + ArrayPool.Shared.Return(array); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void WriteZeroValue(Utf8JsonWriter writer) => writer.WriteStringValue("0x0"u8); + + [InlineArray(256)] + private struct HexBuffer256 + { + private byte _element0; } public delegate void WriteHex(Utf8JsonWriter writer, ReadOnlySpan hex); + /// + /// Writes bytes as hex using a custom write action (e.g. for property names). + /// [SkipLocalsInit] public static void Convert( Utf8JsonWriter writer, @@ -204,51 +257,57 @@ public static void Convert( bool addQuotations = true, bool addHexPrefix = true) { - const int maxStackLength = 128; - const int stackLength = 256; - - var leadingNibbleZeros = skipLeadingZeros ? bytes.CountLeadingNibbleZeros() : 0; - var nibblesCount = bytes.Length * 2; + int leadingNibbleZeros = skipLeadingZeros ? bytes.CountLeadingNibbleZeros() : 0; + int nibblesCount = bytes.Length * 2; if (skipLeadingZeros && nibblesCount is not 0 && leadingNibbleZeros == nibblesCount) { - writer.WriteStringValue(Bytes.ZeroHexValue); + WriteZeroValue(writer, writeAction, addQuotations); return; } - var prefixLength = addHexPrefix ? 2 : 0; - var length = nibblesCount - leadingNibbleZeros + prefixLength + (addQuotations ? 2 : 0); + int prefixLength = addHexPrefix ? 2 : 0; + int quotesLength = addQuotations ? 2 : 0; + int length = nibblesCount - leadingNibbleZeros + prefixLength + quotesLength; byte[]? array = null; - if (length > maxStackLength) - array = ArrayPool.Shared.Rent(length); + Unsafe.SkipInit(out HexBuffer256 buffer); + Span hex = length <= 256 + ? MemoryMarshal.CreateSpan(ref Unsafe.As(ref buffer), 256) + : (array = ArrayPool.Shared.Rent(length)); + hex = hex[..length]; - Span hex = (array ?? stackalloc byte[stackLength])[..length]; - var start = 0; - Index end = ^0; + ref byte hexRef = ref MemoryMarshal.GetReference(hex); + int start = 0; + int endPad = 0; if (addQuotations) { - end = ^1; - hex[^1] = (byte)'"'; - hex[start++] = (byte)'"'; + hexRef = (byte)'"'; + Unsafe.Add(ref hexRef, length - 1) = (byte)'"'; + start = 1; + endPad = 1; } if (addHexPrefix) { - hex[start++] = (byte)'0'; - hex[start++] = (byte)'x'; + Unsafe.As(ref Unsafe.Add(ref hexRef, start)) = HexPrefix; + start += 2; } - Span output = hex[start..end]; - - ReadOnlySpan input = bytes[(leadingNibbleZeros / 2)..]; - input.OutputBytesToByteHex(output, extraNibble: (leadingNibbleZeros & 1) != 0); + ReadOnlySpan input = bytes[(leadingNibbleZeros >>> 1)..]; + input.OutputBytesToByteHex( + MemoryMarshal.CreateSpan(ref Unsafe.Add(ref hexRef, start), length - start - endPad), + extraNibble: (leadingNibbleZeros & 1) != 0); writeAction(writer, hex); if (array is not null) ArrayPool.Shared.Return(array); } + [MethodImpl(MethodImplOptions.NoInlining)] + private static void WriteZeroValue(Utf8JsonWriter writer, WriteHex writeAction, bool addQuotations) + => writeAction(writer, addQuotations ? "\"0x0\""u8 : "0x0"u8); + public override byte[] ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { byte[]? result = Convert(ref reader); diff --git a/src/Nethermind/Nethermind.Core/KeyValueStoreExtensions.cs b/src/Nethermind/Nethermind.Core/KeyValueStoreExtensions.cs index 906b39acf45d..0220b81ae103 100644 --- a/src/Nethermind/Nethermind.Core/KeyValueStoreExtensions.cs +++ b/src/Nethermind/Nethermind.Core/KeyValueStoreExtensions.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using System.Runtime.CompilerServices; using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; @@ -12,114 +11,108 @@ namespace Nethermind.Core { public static class KeyValueStoreExtensions { - public static IWriteBatch LikeABatch(this IWriteOnlyKeyValueStore keyValueStore) - { - return LikeABatch(keyValueStore, null); - } - - public static IWriteBatch LikeABatch(this IWriteOnlyKeyValueStore keyValueStore, Action? onDispose) - { - return new FakeWriteBatch(keyValueStore, onDispose); - } - - #region Getters - - public static byte[]? Get(this IReadOnlyKeyValueStore db, Hash256 key) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void GuardKey(Hash256 key) { #if DEBUG - if (key == Keccak.OfAnEmptyString) - { - throw new InvalidOperationException(); - } + if (key == Keccak.OfAnEmptyString) throw new InvalidOperationException(); #endif - - return db[key.Bytes]; } - /// - /// - /// /// - /// - /// Can return null or empty Span on missing key - /// - public static Span GetSpan(this IReadOnlyKeyValueStore db, Hash256 key) + extension(IReadOnlyKeyValueStore db) { -#if DEBUG - if (key == Keccak.OfAnEmptyString) + public byte[]? Get(Hash256 key) { - throw new InvalidOperationException(); + GuardKey(key); + return db[key.Bytes]; } -#endif - return db.GetSpan(key.Bytes); - } + /// + /// + /// + /// + /// Can return null or empty Span on missing key + /// + public Span GetSpan(Hash256 key) + { + GuardKey(key); + return db.GetSpan(key.Bytes); + } - public static bool KeyExists(this IReadOnlyKeyValueStore db, Hash256 key) - { -#if DEBUG - if (key == Keccak.OfAnEmptyString) + public bool KeyExists(Hash256 key) { - throw new InvalidOperationException(); + GuardKey(key); + return db.KeyExists(key.Bytes); } -#endif - return db.KeyExists(key.Bytes); - } + public bool KeyExists(long key) => db.KeyExists(key.ToBigEndianSpanWithoutLeadingZeros(out _)); - public static bool KeyExists(this IReadOnlyKeyValueStore db, long key) - { - return db.KeyExists(key.ToBigEndianSpanWithoutLeadingZeros(out _)); + public byte[]? Get(long key) => db[key.ToBigEndianSpanWithoutLeadingZeros(out _)]; } - public static byte[]? Get(this IReadOnlyKeyValueStore db, long key) => db[key.ToBigEndianSpanWithoutLeadingZeros(out _)]; - - /// - /// - /// - /// - /// - /// Can return null or empty Span on missing key - public static Span GetSpan(this IReadOnlyKeyValueStore db, long key) => db.GetSpan(key.ToBigEndianSpanWithoutLeadingZeros(out _)); - - public static MemoryManager? GetOwnedMemory(this IReadOnlyKeyValueStore db, ReadOnlySpan key) + extension(IWriteOnlyKeyValueStore db) { - Span span = db.GetSpan(key); - return span.IsNullOrEmpty() ? null : new DbSpanMemoryManager(db, span); - } + public IWriteBatch LikeABatch(Action? onDispose = null) => new FakeWriteBatch(db, onDispose); + public void Set(Hash256 key, byte[] value, WriteFlags writeFlags = WriteFlags.None) + { + if (db.PreferWriteByArray) + { + db.Set(key.Bytes, value, writeFlags); + } + else + { + db.PutSpan(key.Bytes, value, writeFlags); + } + } - #endregion + public void Set(Hash256 key, in CappedArray value, WriteFlags writeFlags = WriteFlags.None) + { + if (db.PreferWriteByArray && value.IsUncapped) + { + db.Set(key.Bytes, value.UnderlyingArray, writeFlags); + } + else + { + db.PutSpan(key.Bytes, value.AsSpan(), writeFlags); + } + } + public void Set(long blockNumber, Hash256 key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) + { + Span blockNumberPrefixedKey = stackalloc byte[40]; + GetBlockNumPrefixedKey(blockNumber, key, blockNumberPrefixedKey); + db.PutSpan(blockNumberPrefixedKey, value, writeFlags); + } - #region Setters + public void Set(in ValueHash256 key, Span value) + { + db.PutSpan(key.Bytes, value); + } - public static void Set(this IWriteOnlyKeyValueStore db, Hash256 key, byte[] value, WriteFlags writeFlags = WriteFlags.None) - { - if (db.PreferWriteByArray) + public void Delete(Hash256 key) { - db.Set(key.Bytes, value, writeFlags); - return; + db.Remove(key.Bytes); } - db.PutSpan(key.Bytes, value, writeFlags); - } - public static void Set(this IWriteOnlyKeyValueStore db, Hash256 key, in CappedArray value, WriteFlags writeFlags = WriteFlags.None) - { - if (value.IsUncapped && db.PreferWriteByArray) + public void Delete(long key) { - db.PutSpan(key.Bytes, value.AsSpan(), writeFlags); - return; + db.Remove(key.ToBigEndianSpanWithoutLeadingZeros(out _)); } - db.PutSpan(key.Bytes, value.AsSpan(), writeFlags); - } + [SkipLocalsInit] + public void Delete(long blockNumber, Hash256 hash) + { + Span key = stackalloc byte[40]; + GetBlockNumPrefixedKey(blockNumber, hash, key); + db.Remove(key); + } - public static void Set(this IWriteOnlyKeyValueStore db, long blockNumber, Hash256 key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) - { - Span blockNumberPrefixedKey = stackalloc byte[40]; - GetBlockNumPrefixedKey(blockNumber, key, blockNumberPrefixedKey); - db.PutSpan(blockNumberPrefixedKey, value, writeFlags); + public void Set(long key, byte[] value) + { + db[key.ToBigEndianSpanWithoutLeadingZeros(out _)] = value; + } } public static void GetBlockNumPrefixedKey(long blockNumber, ValueHash256 blockHash, Span output) @@ -127,35 +120,5 @@ public static void GetBlockNumPrefixedKey(long blockNumber, ValueHash256 blockHa blockNumber.WriteBigEndian(output); blockHash!.Bytes.CopyTo(output[8..]); } - - public static void Set(this IWriteOnlyKeyValueStore db, in ValueHash256 key, Span value) - { - db.PutSpan(key.Bytes, value); - } - - public static void Delete(this IWriteOnlyKeyValueStore db, Hash256 key) - { - db.Remove(key.Bytes); - } - - public static void Delete(this IWriteOnlyKeyValueStore db, long key) - { - db.Remove(key.ToBigEndianSpanWithoutLeadingZeros(out _)); - } - - [SkipLocalsInit] - public static void Delete(this IWriteOnlyKeyValueStore db, long blockNumber, Hash256 hash) - { - Span key = stackalloc byte[40]; - GetBlockNumPrefixedKey(blockNumber, hash, key); - db.Remove(key); - } - - public static void Set(this IWriteOnlyKeyValueStore db, long key, byte[] value) - { - db[key.ToBigEndianSpanWithoutLeadingZeros(out _)] = value; - } - - #endregion } } diff --git a/src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs b/src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs deleted file mode 100644 index a59bf7aadbe2..000000000000 --- a/src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading.Tasks; -using Nethermind.Core.Collections; - -namespace Nethermind.Core.PubSub -{ - public class CompositePublisher(params IPublisher[] publishers) : IPublisher - { - public async Task PublishAsync(T data) where T : class - { - using ArrayPoolList tasks = new(publishers.Length); - for (int i = 0; i < publishers.Length; i++) - { - tasks.Add(publishers[i].PublishAsync(data)); - } - - await Task.WhenAll(tasks.AsSpan()); - } - - public void Dispose() - { - foreach (IPublisher publisher in publishers) - { - publisher.Dispose(); - } - } - } -} diff --git a/src/Nethermind/Nethermind.Core/StorageCell.cs b/src/Nethermind/Nethermind.Core/StorageCell.cs index a35bc2eda97c..d28c5648f364 100644 --- a/src/Nethermind/Nethermind.Core/StorageCell.cs +++ b/src/Nethermind/Nethermind.Core/StorageCell.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -13,12 +14,13 @@ namespace Nethermind.Core { [DebuggerDisplay("{Address}->{Index}")] - public readonly struct StorageCell : IEquatable + public readonly struct StorageCell : IEquatable, IHash64bit { + private readonly AddressAsKey _address; private readonly UInt256 _index; private readonly bool _isHash; - public Address Address { get; } + public Address Address => _address.Value; public bool IsHash => _isHash; public UInt256 Index => _index; @@ -33,21 +35,45 @@ private ValueHash256 GetHash() public StorageCell(Address address, in UInt256 index) { - Address = address; + _address = address; _index = index; } public StorageCell(Address address, ValueHash256 hash) { - Address = address; + _address = address; _index = Unsafe.As(ref hash); _isHash = true; } - public bool Equals(StorageCell other) => - _isHash == other._isHash && - Unsafe.As>(ref Unsafe.AsRef(in _index)) == Unsafe.As>(ref Unsafe.AsRef(in other._index)) && - Address.Equals(other.Address); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(in StorageCell other) + { + if (_isHash != other._isHash) + return false; + + if (Unsafe.As>(ref Unsafe.AsRef(in _index)) != + Unsafe.As>(ref Unsafe.AsRef(in other._index))) + return false; + + // Inline 20-byte Address comparison: avoids the Address.Equals call + // that the JIT refuses to inline when called from deep inline chains + // (e.g. SeqlockCache.TryGetValue). Address.Bytes is always exactly 20 bytes. + Address a = _address.Value; + Address b = other._address.Value; + if (ReferenceEquals(a, b)) + return true; + + ref byte ab = ref MemoryMarshal.GetArrayDataReference(a.Bytes); + ref byte bb = ref MemoryMarshal.GetArrayDataReference(b.Bytes); + return Unsafe.As>(ref ab) == Unsafe.As>(ref bb) + && Unsafe.As(ref Unsafe.Add(ref ab, 16)) == Unsafe.As(ref Unsafe.Add(ref bb, 16)); + } + + public bool Equals(StorageCell other) => Equals(in other); + + public long GetHashCode64() + => SpanExtensions.FastHash64For32Bytes(ref Unsafe.As(ref Unsafe.AsRef(in _index))) ^ _address.Value.GetHashCode64(); public override bool Equals(object? obj) { @@ -62,12 +88,12 @@ public override bool Equals(object? obj) public override int GetHashCode() { int hash = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in _index), 1)).FastHash(); - return hash ^ Address.GetHashCode(); + return hash ^ _address.Value.GetHashCode(); } public override string ToString() { - return $"{Address}.{Index}"; + return $"{_address.Value}.{Index}"; } } } diff --git a/src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs b/src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs deleted file mode 100644 index a6c80ed33fc7..000000000000 --- a/src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Concurrent; -using System.Threading; - -namespace Nethermind.Core.Utils; - -/// -/// Batches writes into a set of concurrent batches. For cases where throughput matter, but not atomicity. -/// -public class ConcurrentWriteBatcher : IWriteBatch -{ - private const long PersistEveryNWrite = 10000; - - private long _counter = 0; - private readonly ConcurrentQueue _batches = new(); - private readonly IKeyValueStoreWithBatching _underlyingDb; - private bool _disposing = false; - - public ConcurrentWriteBatcher(IKeyValueStoreWithBatching underlyingDb) - { - _underlyingDb = underlyingDb; - } - - public void Dispose() - { - _disposing = true; - foreach (IWriteBatch batch in _batches) - { - batch.Dispose(); - } - } - - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - IWriteBatch currentBatch = RentWriteBatch(); - currentBatch.PutSpan(key, value, flags); - ReturnWriteBatch(currentBatch); - } - - public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - IWriteBatch currentBatch = RentWriteBatch(); - currentBatch.Merge(key, value, flags); - ReturnWriteBatch(currentBatch); - } - - public void Clear() - { - throw new NotSupportedException($"{nameof(ConcurrentWriteBatcher)} can not be cancelled."); - } - - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - { - IWriteBatch currentBatch = RentWriteBatch(); - currentBatch.Set(key, value, flags); - ReturnWriteBatch(currentBatch); - } - - private void ReturnWriteBatch(IWriteBatch currentBatch) - { - long val = Interlocked.Increment(ref _counter); - if (val % PersistEveryNWrite == 0) - { - currentBatch.Dispose(); - } - else - { - _batches.Enqueue(currentBatch); - } - } - - private IWriteBatch RentWriteBatch() - { - if (_disposing) throw new InvalidOperationException("Trying to set while disposing"); - if (!_batches.TryDequeue(out IWriteBatch? currentBatch)) - { - currentBatch = _underlyingDb.StartWriteBatch(); - } - - return currentBatch; - } -} diff --git a/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs b/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs index af75f303e417..94633dfd94f6 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs @@ -2,9 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using Nethermind.Core; +using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using RocksDbSharp; using IWriteBatch = Nethermind.Core.IWriteBatch; @@ -31,52 +34,34 @@ public ColumnDb(RocksDb rocksDb, DbOnTheRocks mainDb, string name) _reader = new RocksDbReader(mainDb, mainDb.CreateReadOptions, _iteratorManager, _columnFamily); } - public void Dispose() - { - _iteratorManager.Dispose(); - } - + public void Dispose() => _iteratorManager.Dispose(); public string Name { get; } - byte[]? IReadOnlyKeyValueStore.Get(ReadOnlySpan key, ReadFlags flags) - { - return _reader.Get(key, flags); - } + byte[]? IReadOnlyKeyValueStore.Get(ReadOnlySpan key, ReadFlags flags) => _reader.Get(key, flags); - Span IReadOnlyKeyValueStore.GetSpan(scoped ReadOnlySpan key, ReadFlags flags) - { - return _reader.GetSpan(key, flags); - } + Span IReadOnlyKeyValueStore.GetSpan(scoped ReadOnlySpan key, ReadFlags flags) => _reader.GetSpan(key, flags); - int IReadOnlyKeyValueStore.Get(scoped ReadOnlySpan key, Span output, ReadFlags flags) + MemoryManager? IReadOnlyKeyValueStore.GetOwnedMemory(ReadOnlySpan key, ReadFlags flags) { - return _reader.Get(key, output, flags); + Span span = ((IReadOnlyKeyValueStore)this).GetSpan(key, flags); + return span.IsNullOrEmpty() ? null : new DbSpanMemoryManager(this, span); } - bool IReadOnlyKeyValueStore.KeyExists(ReadOnlySpan key) - { - return _reader.KeyExists(key); - } - void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan key) - { - _reader.DangerousReleaseMemory(key); - } + int IReadOnlyKeyValueStore.Get(scoped ReadOnlySpan key, Span output, ReadFlags flags) => _reader.Get(key, output, flags); - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - { + bool IReadOnlyKeyValueStore.KeyExists(ReadOnlySpan key) => _reader.KeyExists(key); + + void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan key) => _reader.DangerousReleaseMemory(key); + + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) => _mainDb.SetWithColumnFamily(key, _columnFamily, value, flags); - } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) - { + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) => _mainDb.SetWithColumnFamily(key, _columnFamily, value, writeFlags); - } - public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) - { + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) => _mainDb.MergeWithColumnFamily(key, _columnFamily, value, writeFlags); - } public KeyValuePair[] this[byte[][] keys] { @@ -106,77 +91,48 @@ public IEnumerable GetAllValues(bool ordered = false) return _mainDb.GetAllValuesCore(iterator); } - public IWriteBatch StartWriteBatch() - { - return new ColumnsDbWriteBatch(this, (DbOnTheRocks.RocksDbWriteBatch)_mainDb.StartWriteBatch()); - } + public IWriteBatch StartWriteBatch() => new ColumnsDbWriteBatch(this, (DbOnTheRocks.RocksDbWriteBatch)_mainDb.StartWriteBatch()); - private class ColumnsDbWriteBatch : IWriteBatch + private class ColumnsDbWriteBatch(ColumnDb columnDb, DbOnTheRocks.RocksDbWriteBatch underlyingWriteBatch) + : IWriteBatch { - private readonly ColumnDb _columnDb; - private readonly DbOnTheRocks.RocksDbWriteBatch _underlyingWriteBatch; + public void Dispose() => underlyingWriteBatch.Dispose(); - public ColumnsDbWriteBatch(ColumnDb columnDb, DbOnTheRocks.RocksDbWriteBatch underlyingWriteBatch) - { - _columnDb = columnDb; - _underlyingWriteBatch = underlyingWriteBatch; - } - - public void Dispose() - { - _underlyingWriteBatch.Dispose(); - } - - public void Clear() - { - _underlyingWriteBatch.Clear(); - } + public void Clear() => underlyingWriteBatch.Clear(); public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { if (value is null) { - _underlyingWriteBatch.Delete(key, _columnDb._columnFamily); + underlyingWriteBatch.Delete(key, columnDb._columnFamily); } else { - _underlyingWriteBatch.Set(key, value, _columnDb._columnFamily, flags); + underlyingWriteBatch.Set(key, value, columnDb._columnFamily, flags); } } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - _underlyingWriteBatch.Set(key, value, _columnDb._columnFamily, flags); - } + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => + underlyingWriteBatch.Set(key, value, columnDb._columnFamily, flags); - public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - _underlyingWriteBatch.Merge(key, value, _columnDb._columnFamily, flags); - } + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => + underlyingWriteBatch.Merge(key, value, columnDb._columnFamily, flags); } - public void Remove(ReadOnlySpan key) - { - Set(key, null); - } + public void Remove(ReadOnlySpan key) => Set(key, null); - public void Flush(bool onlyWal) - { - _mainDb.FlushWithColumnFamily(_columnFamily); - } + public void Flush(bool onlyWal) => _mainDb.FlushWithColumnFamily(_columnFamily); - public void Compact() - { + public void Compact() => _rocksDb.CompactRange(Keccak.Zero.BytesToArray(), Keccak.MaxValue.BytesToArray(), _columnFamily); - } /// /// Not sure how to handle delete of the columns DB /// /// - public void Clear() { throw new NotSupportedException(); } + public void Clear() => throw new NotSupportedException(); - // Maybe it should be column specific metric? + // Maybe it should be column-specific metric? public IDbMeta.DbMetric GatherMetric() => _mainDb.GatherMetric(); public byte[]? FirstKey @@ -199,10 +155,8 @@ public byte[]? LastKey } } - public ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey) - { - return _mainDb.GetViewBetween(firstKey, lastKey, _columnFamily); - } + public ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey) => + _mainDb.GetViewBetween(firstKey, lastKey, _columnFamily); public IKeyValueStoreSnapshot CreateSnapshot() { diff --git a/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs b/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs index b631d969a28b..5b22cc4f0580 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs @@ -105,24 +105,19 @@ protected override void ApplyOptions(IDictionary options) private class RocksColumnsWriteBatch : IColumnsWriteBatch { - internal RocksDbWriteBatch _writeBatch; + internal readonly RocksDbWriteBatch WriteBatch; private readonly ColumnsDb _columnsDb; public RocksColumnsWriteBatch(ColumnsDb columnsDb) { - _writeBatch = new RocksDbWriteBatch(columnsDb); + WriteBatch = new RocksDbWriteBatch(columnsDb); _columnsDb = columnsDb; } - public IWriteBatch GetColumnBatch(T key) - { - return new RocksColumnWriteBatch(_columnsDb._columnDbs[key], this); - } + public IWriteBatch GetColumnBatch(T key) => new RocksColumnWriteBatch(_columnsDb._columnDbs[key], this); - public void Dispose() - { - _writeBatch.Dispose(); - } + public void Clear() => WriteBatch.Clear(); + public void Dispose() => WriteBatch.Dispose(); } private class RocksColumnWriteBatch : IWriteBatch @@ -143,17 +138,17 @@ public void Dispose() public void Clear() { - _writeBatch._writeBatch.Clear(); + _writeBatch.WriteBatch.Clear(); } public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { - _writeBatch._writeBatch.Set(key, value, _column._columnFamily, flags); + _writeBatch.WriteBatch.Set(key, value, _column._columnFamily, flags); } public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) { - _writeBatch._writeBatch.Merge(key, value, _column._columnFamily, flags); + _writeBatch.WriteBatch.Merge(key, value, _column._columnFamily, flags); } } diff --git a/src/Nethermind/Nethermind.Db.Rocks/Config/DbConfig.cs b/src/Nethermind/Nethermind.Db.Rocks/Config/DbConfig.cs index c6004bcfc716..f97bccfd637b 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/Config/DbConfig.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/Config/DbConfig.cs @@ -271,4 +271,19 @@ public class DbConfig : IDbConfig public string L1OriginDbRocksDbOptions { get; set; } = ""; public string? L1OriginDbAdditionalRocksDbOptions { get; set; } + + public string LogIndexStorageDbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageDbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageMetaDbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageMetaDbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageAddressesDbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageAddressesDbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics0DbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics0DbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics1DbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics1DbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics2DbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics2DbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics3DbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics3DbAdditionalRocksDbOptions { get; set; } = ""; } diff --git a/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs b/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs index fa7c8e2023b6..5c8b1211a410 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs @@ -102,4 +102,19 @@ public interface IDbConfig : IConfig string L1OriginDbRocksDbOptions { get; set; } string? L1OriginDbAdditionalRocksDbOptions { get; set; } + + string LogIndexStorageDbRocksDbOptions { get; set; } + string LogIndexStorageDbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageMetaDbRocksDbOptions { get; set; } + string LogIndexStorageMetaDbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageAddressesDbRocksDbOptions { get; set; } + string LogIndexStorageAddressesDbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageTopics0DbRocksDbOptions { get; set; } + string LogIndexStorageTopics0DbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageTopics1DbRocksDbOptions { get; set; } + string LogIndexStorageTopics1DbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageTopics2DbRocksDbOptions { get; set; } + string LogIndexStorageTopics2DbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageTopics3DbRocksDbOptions { get; set; } + string LogIndexStorageTopics3DbAdditionalRocksDbOptions { get; set; } } diff --git a/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs b/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs index b5488c733b3a..cca010c66f8c 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Buffers.Binary; using System.Collections.Concurrent; using System.Collections.Generic; @@ -18,6 +19,7 @@ using ConcurrentCollections; using Nethermind.Config; using Nethermind.Core; +using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; using Nethermind.Core.Exceptions; using Nethermind.Core.Extensions; @@ -103,7 +105,7 @@ public DbOnTheRocks( IRocksDbConfigFactory rocksDbConfigFactory, ILogManager logManager, IList? columnFamilies = null, - RocksDbSharp.Native? rocksDbNative = null, + Native? rocksDbNative = null, IFileSystem? fileSystem = null, IntPtr? sharedCache = null) { @@ -111,7 +113,7 @@ public DbOnTheRocks( _settings = dbSettings; Name = _settings.DbName; _fileSystem = fileSystem ?? new FileSystem(); - _rocksDbNative = rocksDbNative ?? RocksDbSharp.Native.Instance; + _rocksDbNative = rocksDbNative ?? Native.Instance; _rocksDbConfigFactory = rocksDbConfigFactory; _perTableDbConfig = rocksDbConfigFactory.GetForDatabase(Name, null); _db = Init(basePath, dbSettings.DbPath, dbConfig, logManager, columnFamilies, dbSettings.DeleteOnStart, sharedCache); @@ -129,7 +131,6 @@ protected virtual RocksDb DoOpen(string path, (DbOptions Options, ColumnFamilies private RocksDb Open(string path, (DbOptions Options, ColumnFamilies? Families) db) { RepairIfCorrupted(db.Options); - return DoOpen(path, db); } @@ -179,7 +180,7 @@ private RocksDb Init(string basePath, string dbPath, IDbConfig dbConfig, ILogMan if (dbConfig.EnableMetricsUpdater) { - DbMetricsUpdater metricUpdater = new DbMetricsUpdater(Name, DbOptions, db, null, dbConfig, _logger); + DbMetricsUpdater metricUpdater = new(Name, DbOptions, db, null, dbConfig, _logger); metricUpdater.StartUpdating(); _metricsUpdaters.Add(metricUpdater); @@ -190,7 +191,7 @@ private RocksDb Init(string basePath, string dbPath, IDbConfig dbConfig, ILogMan if (columnFamily.Name == "default") continue; if (db.TryGetColumnFamily(columnFamily.Name, out ColumnFamilyHandle handle)) { - DbMetricsUpdater columnMetricUpdater = new DbMetricsUpdater( + DbMetricsUpdater columnMetricUpdater = new( Name + "_" + columnFamily.Name, columnFamily.Options, db, handle, dbConfig, _logger); columnMetricUpdater.StartUpdating(); _metricsUpdaters.Add(columnMetricUpdater); @@ -264,8 +265,8 @@ private void WarmupFile(string basePath, RocksDb db) } return take; }) - // We reverse them again so that lower level goes last so that it is the freshest. - // Not all of the available memory is actually available so we are probably over reading things. + // We reverse them again so that the lower level goes last so that it is the freshest. + // Not all the available memory is actually available, so we are probably over reading things. .Reverse() .ToList(); @@ -340,15 +341,11 @@ protected internal void UpdateWriteMetrics() Interlocked.Increment(ref _totalWrites); } - protected virtual long FetchTotalPropertyValue(string propertyName) - { - long value = long.TryParse(_db.GetProperty(propertyName), out long parsedValue) + protected virtual long FetchTotalPropertyValue(string propertyName) => + long.TryParse(_db.GetProperty(propertyName), out long parsedValue) ? parsedValue : 0; - return value; - } - public IDbMeta.DbMetric GatherMetric() { if (_isDisposed) @@ -491,8 +488,8 @@ public static string NormalizeRocksDbOptions(string dbOptions) protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options options, IntPtr? sharedCache, IMergeOperator? mergeOperator) where T : Options { - // This section is about the table factory.. and block cache apparently. - // This effect the format of the SST files and usually require resync to take effect. + // This section is about the table factory and block cache, apparently. + // This affects the format of the SST files and usually requires resyncing to take effect. // Note: Keep in mind, the term 'index' here usually means mapping to a block, not to a value. #region TableFactory sections @@ -559,21 +556,18 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio int writeBufferNumber = _maxWriteBufferNumber; _maxThisDbSize += (long)writeBufferSize * writeBufferNumber; Interlocked.Add(ref _maxRocksSize, _maxThisDbSize); - if (_logger.IsDebug) - _logger.Debug( - $"Expected max memory footprint of {Name} DB is {_maxThisDbSize / 1000 / 1000} MB ({writeBufferNumber} * {writeBufferSize / 1000 / 1000} MB + {blockCacheSize / 1000 / 1000} MB)"); + if (_logger.IsDebug) _logger.Debug($"Expected max memory footprint of {Name} DB is {_maxThisDbSize / 1000 / 1000} MB ({writeBufferNumber} * {writeBufferSize / 1000 / 1000} MB + {blockCacheSize / 1000 / 1000} MB)"); if (_logger.IsDebug) _logger.Debug($"Total max DB footprint so far is {_maxRocksSize / 1000 / 1000} MB"); } #endregion - // This section affect compactions, flushes and the LSM shape. + // This section affects compactions, flushes and the LSM shape. #region Compaction /* - * Multi-Threaded Compactions - * Compactions are needed to remove multiple copies of the same key that may occur if an application overwrites an existing key. Compactions also process deletions of keys. Compactions may occur in multiple threads if configured appropriately. + * Multi-Threaded Compactions are needed to remove multiple copies of the same key that may occur if an application overwrites an existing key. Compactions also process deletions of keys. Compactions may occur in multiple threads if configured appropriately. * The entire database is stored in a set of sstfiles. When a memtable is full, its content is written out to a file in Level-0 (L0). RocksDB removes duplicate and overwritten keys in the memtable when it is flushed to a file in L0. Some files are periodically read in and merged to form larger files - this is called compaction. - * The overall write throughput of an LSM database directly depends on the speed at which compactions can occur, especially when the data is stored in fast storage like SSD or RAM. RocksDB may be configured to issue concurrent compaction requests from multiple threads. It is observed that sustained write rates may increase by as much as a factor of 10 with multi-threaded compaction when the database is on SSDs, as compared to single-threaded compactions. + * The overall writing throughput of an LSM database directly depends on the speed at which compactions can occur, especially when the data is stored in fast storage like SSD or RAM. RocksDB may be configured to issue concurrent compaction requests from multiple threads. It is observed that sustained write rates may increase by as much as a factor of 10 with multi-threaded compaction when the database is on SSDs, as compared to single-threaded compactions. * TKS: Observed 500MB/s compared to ~100MB/s between multithreaded and single thread compactions on my machine (processor count is returning 12 for 6 cores with hyperthreading) * TKS: CPU goes to insane 30% usage on idle - compacting only app */ @@ -592,11 +586,11 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio if (dbConfig.RowCacheSize > 0) { - // Row cache is basically a per-key cache. Nothing special to it. This is different from block cache - // which cache the whole block at once, so read still need to traverse the block index, so this could be + // Row cache is basically a per-key cache. Nothing special about it. This is different from a block cache + // that caches the whole block at once, so read still needs to traverse the block index, so this could be // more CPU efficient. - // Note: Memtable also act like a per-key cache, that does not get updated on read. So in some case - // maybe it make more sense to put more memory to memtable. + // Note: Memtable also acts like a per-key cache that does not get updated on read. So in some case + // maybe it makes more sense to put more memory to memtable. _rowCache = _rocksDbNative.rocksdb_cache_create_lru(new UIntPtr(dbConfig.RowCacheSize.Value)); _rocksDbNative.rocksdb_options_set_row_cache(options.Handle, _rowCache.Value); } @@ -655,7 +649,7 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio _hintCacheMissOptions = CreateReadOptions(); _hintCacheMissOptions.SetFillCache(false); - // When readahead flag is on, the next keys are expected to be after the current key. Increasing this value, + // When a readahead flag is on, the next keys are expected to be after the current key. Increasing this value // will increase the chances that the next keys will be in the cache, which reduces iops and latency. This // increases throughput, however, if a lot of the keys are not close to the current key, it will increase read // bandwidth requirement, since each read must be at least this size. This value is tuned for a batched trie @@ -672,7 +666,7 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio private static WriteOptions CreateWriteOptions(IRocksDbConfig dbConfig) { WriteOptions options = new(); - // potential fix for corruption on hard process termination, may cause performance degradation + // a potential fix for corruption on hard process termination may cause performance degradation options.SetSync(dbConfig.WriteAheadLogSync); return options; } @@ -684,32 +678,23 @@ internal ReadOptions CreateReadOptions() return readOptions; } - byte[]? IReadOnlyKeyValueStore.Get(ReadOnlySpan key, ReadFlags flags) - { - return _reader.Get(key, flags); - } + byte[]? IReadOnlyKeyValueStore.Get(ReadOnlySpan key, ReadFlags flags) => _reader.Get(key, flags); - Span IReadOnlyKeyValueStore.GetSpan(scoped ReadOnlySpan key, ReadFlags flags) - { - return _reader.GetSpan(key, flags); - } + Span IReadOnlyKeyValueStore.GetSpan(scoped ReadOnlySpan key, ReadFlags flags) => _reader.GetSpan(key, flags); - int IReadOnlyKeyValueStore.Get(scoped ReadOnlySpan key, Span output, ReadFlags flags) + MemoryManager? IReadOnlyKeyValueStore.GetOwnedMemory(ReadOnlySpan key, ReadFlags flags) { - return _reader.Get(key, output, flags); + Span span = ((IReadOnlyKeyValueStore)this).GetSpan(key, flags); + return span.IsNullOrEmpty() ? null : new DbSpanMemoryManager(this, span); } - bool IReadOnlyKeyValueStore.KeyExists(ReadOnlySpan key) - { - return _reader.KeyExists(key); - } + int IReadOnlyKeyValueStore.Get(scoped ReadOnlySpan key, Span output, ReadFlags flags) => _reader.Get(key, output, flags); - void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan span) - { - _reader.DangerousReleaseMemory(span); - } + bool IReadOnlyKeyValueStore.KeyExists(ReadOnlySpan key) => _reader.KeyExists(key); + + void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan span) => _reader.DangerousReleaseMemory(span); - internal unsafe byte[]? GetWithIterator(ReadOnlySpan key, ColumnFamilyHandle? cf, IteratorManager iteratorManager, ReadFlags flags, out bool success) + internal byte[]? GetWithIterator(ReadOnlySpan key, ColumnFamilyHandle? cf, IteratorManager iteratorManager, ReadFlags flags, out bool success) { success = true; @@ -770,10 +755,7 @@ void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan span) } [DoesNotReturn, StackTraceHidden] - static unsafe void ThrowRocksDbException(nint errPtr) - { - throw new RocksDbException(errPtr); - } + static void ThrowRocksDbException(nint errPtr) => throw new RocksDbException(errPtr); } /// @@ -783,6 +765,7 @@ static unsafe void ThrowRocksDbException(nint errPtr) /// /// /// + /// /// private bool TryCloseReadAhead(Iterator iterator, ReadOnlySpan key, out byte[]? result) { @@ -842,10 +825,8 @@ private bool TryCloseReadAhead(Iterator iterator, ReadOnlySpan key, out by return false; } - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - { + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) => SetWithColumnFamily(key, null, value, flags); - } internal void SetWithColumnFamily(ReadOnlySpan key, ColumnFamilyHandle? cf, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) { @@ -871,25 +852,13 @@ internal void SetWithColumnFamily(ReadOnlySpan key, ColumnFamilyHandle? cf } } - public WriteOptions? WriteFlagsToWriteOptions(WriteFlags flags) + public WriteOptions? WriteFlagsToWriteOptions(WriteFlags flags) => flags switch { - if ((flags & WriteFlags.LowPriorityAndNoWAL) == WriteFlags.LowPriorityAndNoWAL) - { - return _lowPriorityAndNoWalWrite; - } - - if ((flags & WriteFlags.DisableWAL) == WriteFlags.DisableWAL) - { - return _noWalWrite; - } - - if ((flags & WriteFlags.LowPriority) == WriteFlags.LowPriority) - { - return _lowPriorityWriteOptions; - } - - return WriteOptions; - } + _ when (flags & WriteFlags.LowPriorityAndNoWAL) == WriteFlags.LowPriorityAndNoWAL => _lowPriorityAndNoWalWrite, + _ when (flags & WriteFlags.DisableWAL) == WriteFlags.DisableWAL => _noWalWrite, + _ when (flags & WriteFlags.LowPriority) == WriteFlags.LowPriority => _lowPriorityWriteOptions, + _ => WriteOptions + }; public KeyValuePair[] this[byte[][] keys] @@ -939,15 +908,15 @@ internal unsafe int GetCStyleWithColumnFamily(scoped ReadOnlySpan key, Spa UpdateReadMetrics(); nint db = _db.Handle; - nint read_options = readOptions.Handle; + nint readOptionsHandle = readOptions.Handle; UIntPtr skLength = (UIntPtr)key.Length; IntPtr errPtr; IntPtr slice; fixed (byte* ptr = &MemoryMarshal.GetReference(key)) { slice = cf is null - ? Native.Instance.rocksdb_get_pinned(db, read_options, ptr, skLength, out errPtr) - : Native.Instance.rocksdb_get_pinned_cf(db, read_options, cf.Handle, ptr, skLength, out errPtr); + ? Native.Instance.rocksdb_get_pinned(db, readOptionsHandle, ptr, skLength, out errPtr) + : Native.Instance.rocksdb_get_pinned_cf(db, readOptionsHandle, cf.Handle, ptr, skLength, out errPtr); } if (errPtr != IntPtr.Zero) ThrowRocksDbException(errPtr); @@ -972,22 +941,15 @@ internal unsafe int GetCStyleWithColumnFamily(scoped ReadOnlySpan key, Spa return length; [DoesNotReturn, StackTraceHidden] - static unsafe void ThrowRocksDbException(nint errPtr) - { - throw new RocksDbException(errPtr); - } + static void ThrowRocksDbException(nint errPtr) => throw new RocksDbException(errPtr); [DoesNotReturn, StackTraceHidden] - static unsafe void ThrowNotEnoughMemory(int length, int bufferLength) - { + static void ThrowNotEnoughMemory(int length, int bufferLength) => throw new ArgumentException($"Output buffer not large enough. Output size: {length}, Buffer size: {bufferLength}"); - } } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags) - { + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags) => SetWithColumnFamily(key, null, value, writeFlags); - } public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) { @@ -1277,8 +1239,8 @@ internal class RocksDbWriteBatch : IWriteBatch /// /// Because of how rocksdb parallelize writes, a large write batch can stall other new concurrent writes, so - /// we writes the batch in smaller batches. This removes atomicity so its only turned on when NoWAL flag is on. - /// It does not work as well as just turning on unordered_write, but Snapshot and Iterator can still works. + /// we write the batch in smaller batches. This removes atomicity so it's only turned on when the NoWAL flag is on. + /// It does not work as well as just turning on unordered_write, but Snapshot and Iterator can still work. /// private const int MaxWritesOnNoWal = 256; private int _writeCount; @@ -1316,7 +1278,6 @@ private static void ReturnWriteBatch(WriteBatch batch) public void Clear() { ObjectDisposedException.ThrowIf(_dbOnTheRocks._isDisposed, _dbOnTheRocks); - _rocksBatch.Clear(); } @@ -1368,20 +1329,14 @@ public void Set(ReadOnlySpan key, ReadOnlySpan value, ColumnFamilyHa if ((flags & WriteFlags.DisableWAL) != 0) FlushOnTooManyWrites(); } - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - { + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) => Set(key, value, null, flags); - } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => Set(key, value, null, flags); - } - public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => Merge(key, value, null, flags); - } public void Merge(ReadOnlySpan key, ReadOnlySpan value, ColumnFamilyHandle? cf = null, WriteFlags flags = WriteFlags.None) { @@ -1497,18 +1452,13 @@ private class FlushOptions { internal static FlushOptions DefaultFlushOptions { get; } = new(); - public FlushOptions() - { - Handle = RocksDbSharp.Native.Instance.rocksdb_flushoptions_create(); - } - - public IntPtr Handle { get; private set; } + public IntPtr Handle { get; private set; } = Native.Instance.rocksdb_flushoptions_create(); ~FlushOptions() { if (Handle != IntPtr.Zero) { - RocksDbSharp.Native.Instance.rocksdb_flushoptions_destroy(Handle); + Native.Instance.rocksdb_flushoptions_destroy(Handle); Handle = IntPtr.Zero; } } @@ -1573,15 +1523,15 @@ public virtual void Tune(ITunableDb.TuneType type) // See https://github.com/EighteenZi/rocksdb_wiki/blob/master/RocksDB-Tuning-Guide.md switch (type) { - // Depending on tune type, allow num of L0 files to grow causing compaction to occur in larger size. This + // Depending on tune type, allow num of L0 files to grow causing compaction to occur in a larger size. This // reduces write amplification at the expense of read response time and amplification while the tune is // active. Additionally, the larger compaction causes larger spikes of IO, larger memory usage, and may temporarily - // use up large amount of disk space. User may not want to enable this if they plan to run a validator node - // while the node is still syncing, or run another node on the same machine. Specifying a rate limit + // use up a large amount of disk space. User may not want to enable this if they plan to run a validator node + // while the node is still syncing or run another node on the same machine. Specifying a rate limit // smoothens this spike somewhat by not blocking writes while allowing compaction to happen in background // at 1/10th the specified speed (if rate limited). // - // Total writes written on different tune during mainnet sync in TB. + // Total writes written on different tunes during mainnet sync in TB. // +-----------------------+-------+-------+-------+-------+-------+---------+ // | L0FileNumTarget | Total | State | Code | Header| Blocks| Receipts | // +-----------------------+-------+-------+-------+-------+-------+---------+ @@ -1592,45 +1542,45 @@ public virtual void Tune(ITunableDb.TuneType type) // | DisableCompaction | 2.215 | 0.36 | 0.031 | 0.137 | 1.14 | 0.547 | // +-----------------------+-------+-------+-------+-------+-------+---------+ // Note, in practice on my machine, the reads does not reach the SSD. Read measured from SSD is much lower - // than read measured from process. It is likely that most files are cached as I have 128GB of RAM. + // than read measured from a process. It is likely that most files are cached as I have 128GB of RAM. // Also notice that the heavier the tune, the higher the reads. case ITunableDb.TuneType.WriteBias: - // Keep the same l1 size but apply other adjustment which should increase buffer number and make - // l0 the same size as l1, but keep the LSM the same. This improve flush parallelization, and + // Keep the same l1 size but apply other adjustment which should increase the buffer number and make + // l0 the same size as l1 but keep the LSM the same. This improves flush parallelization and // write amplification due to mismatch of l0 and l1 size, but does not reduce compaction from other // levels. ApplyOptions(GetHeavyWriteOptions(_maxBytesForLevelBase)); break; case ITunableDb.TuneType.HeavyWrite: - // Compaction spikes are clear at this point. Will definitely affect attestation performance. - // Its unclear if it improve or slow down sync time. Seems to be the sweet spot. + // Compaction spikes are clear at this point. Will definitely affect attestations performance. + // It's unclear if it improves or slows down sync time. Seems to be the sweet spot. ApplyOptions(GetHeavyWriteOptions((ulong)2.GiB())); break; case ITunableDb.TuneType.AggressiveHeavyWrite: - // For when, you are desperate, but don't wanna disable compaction completely, because you don't want + // For when you are desperate, but don't wanna disable compaction completely, because you don't want // peers to drop. Tend to be faster than disabling compaction completely, except if your ratelimit // is a bit low and your compaction is lagging behind, which will trigger slowdown, so sync will hang // intermittently, but at least peer count is stable. ApplyOptions(GetHeavyWriteOptions((ulong)16.GiB())); break; case ITunableDb.TuneType.DisableCompaction: - // Completely disable compaction. On mainnet, max num of l0 files for state seems to be about 10800. - // Blocksdb are way more at 53000. Final compaction for state db need 30 minute, while blocks db need - // 13 hour. Receipts db don't show up in metrics likely because its a column db. + // Completely disable compaction. On mainnet, the max num of l0 files for state seems to be about 10800. + // Blocksdb are way more at 53000. Final compaction for state db needs 30 minutes, while blocks db need + // 13 hours. Receipts db don't show up in metrics likely because it's a column db. // Ram usage at that time was 86 GB. The default buffer size for blocks on mainnet is too low // to make this work reasonably well. - // L0 to L1 compaction is known to be slower than other level so its - // Snap sync performance suffer as it does have some read during stitching. - // If you don't specify a lower open files limit, it has a tendency to crash, like.. the whole system - // crash. I don't have any open file limit at OS level. - // Also, if a peer send a packet that causes a query to the state db during snap sync like GetNodeData - // or some of the tx filter querying state, It'll cause the network stack to hang and triggers a + // L0 to L1 compaction is known to be slower than other levels, so its + // Snap sync performance suffers as it does have some read during stitching. + // If you don't specify a lower open files limit, it tends to crash, like... the whole system + // crashes. I don't have any open file limit at OS level. + // Also, if a peer sends a packet that causes a query to the state db during snap sync like GetNodeData + // or some of the tx filter querying state, It'll cause the network stack to hang and triggers // large peer drops. Also happens on lesser tune, but weaker. - // State sync essentially hang until that completes because its read heavy, and the uncompacted db is + // State sync essentially hangs until that completes because its read heavy, and the uncompacted db is // slow to a halt. // Additionally, the number of open files handles measured from collectd jumped massively higher. Some // user config may not be able to handle this. - // With all those cons, this result in the minimum write amplification possible via tweaking compaction + // With all those cons, this results in the minimum writes amplification possible via tweaking compaction // without changing memory budget. Not recommended for mainnet, unless you are very desperate. ApplyOptions(GetDisableCompactionOptions()); break; @@ -1649,15 +1599,11 @@ public virtual void Tune(ITunableDb.TuneType type) _currentTune = type; } - protected virtual void ApplyOptions(IDictionary options) - { - _db.SetOptions(options); - } + protected virtual void ApplyOptions(IDictionary options) => _db.SetOptions(options); - private IDictionary GetStandardOptions() - { + private IDictionary GetStandardOptions() => // Defaults are from rocksdb source code - return new Dictionary() + new Dictionary() { { "write_buffer_size", _writeBufferSize.ToString() }, { "max_write_buffer_number", _maxWriteBufferNumber.ToString() }, @@ -1666,7 +1612,7 @@ private IDictionary GetStandardOptions() { "level0_slowdown_writes_trigger", 20.ToString() }, // Very high, so that after moving from HeavyWrite, we don't immediately hang. - // This does means that under very rare case, the l0 file can accumulate, which slow down the db + // This does mean that under a very rare case, the l0 file can accumulate, which slows down the db // until they get compacted. { "level0_stop_writes_trigger", 1024.ToString() }, @@ -1679,13 +1625,11 @@ private IDictionary GetStandardOptions() { "soft_pending_compaction_bytes_limit", 64.GiB().ToString() }, { "hard_pending_compaction_bytes_limit", 256.GiB().ToString() }, }; - } - private IDictionary GetHashDbOptions() - { - return new Dictionary() + private IDictionary GetHashDbOptions() => + new Dictionary() { - // Some database config is slightly faster on hash db database. These are applied when hash db is detected + // Some database config is slightly faster on a hash db database. These are applied when hash db is detected // to prevent unexpected regression. { "table_factory.block_size", "4096" }, { "table_factory.block_restart_interval", "16" }, @@ -1693,35 +1637,34 @@ private IDictionary GetHashDbOptions() { "max_bytes_for_level_multiplier", "10" }, { "max_bytes_for_level_base", "256000000" }, }; - } /// - /// Allow num of l0 file to grow very large. This dramatically increase read response time by about - /// (l0FileNumTarget / (default num (4) + max level usually (4)). but it saves write bandwidth as l0->l1 happens - /// in larger size. In addition to that, the large base l1 size means the number of level is a bit lower. - /// Note: Regardless of max_open_files config, the number of files handle jumped by this number when compacting. It - /// could be that l0->l1 compaction does not (or cant?) follow the max_open_files limit. + /// Allow number of l0 files to grow very large. This dramatically increases read response time by about + /// (l0FileNumTarget / (default num (4) + max level usually (4)), but it saves write bandwidth as l0->l1 happens + /// in larger size. In addition to that, the large base l1 size means the number of levels is a bit lower. + /// Note: Regardless of max_open_files config, the number of files handles jumped by this number when compacting. It + /// could be that l0->l1 compaction does not (or can't?) follow the max_open_files limit. /// - /// + /// /// This caps the maximum allowed number of l0 files, which is also the read response time amplification. /// /// private IDictionary GetHeavyWriteOptions(ulong l0SizeTarget) { // Make buffer (probably) smaller so that it does not take too much memory to have many of them. - // More buffer means more parallel flush, but each read have to go through all buffer one by one much like l0 + // More buffer means more parallel flush, but each read has to go through all buffers one by one, much like l0 // but no io, only cpu. - // bufferSize*maxBufferNumber = 16MB*Core count, which is the max memory used, which tend to be the case as its now - // stalled by compaction instead of flush. - // The buffer is not compressed unlike l0File, so to account for it, its size need to be slightly larger. + // bufferSize*maxBufferNumber = 16MB*Core count, which is the max memory used, which tends to be the case as it's now + // stalled by compaction instead of a flush. + // The buffer is not compressed unlike l0File, so to account for it, its size needs to be slightly larger. ulong targetFileSize = (ulong)16.MiB(); ulong bufferSize = (ulong)(targetFileSize / _perTableDbConfig.CompressibilityHint); ulong l0FileSize = targetFileSize * (ulong)_minWriteBufferToMerge; ulong maxBufferNumber = (ulong)Environment.ProcessorCount; - // Guide recommend to have l0 and l1 to be the same size. They have to be compacted together so if l1 is larger, + // Guide recommends having l0 and l1 to be the same size. They have to be compacted together, so if l1 is larger, // the extra size in l1 is basically extra rewrites. If l0 is larger... then I don't know why not. Even so, it seems to - // always get triggered when l0 size exceed max_bytes_for_level_base even if file num is less than l0FileNumTarget. + // always get triggered when l0 size exceeds max_bytes_for_level_base even if the file number is less than l0FileNumTarget. ulong l0FileNumTarget = l0SizeTarget / l0FileSize; ulong l1SizeTarget = l0SizeTarget; @@ -1749,10 +1692,10 @@ private IDictionary GetDisableCompactionOptions() IDictionary heavyWriteOption = GetHeavyWriteOptions((ulong)32.GiB()); heavyWriteOption["disable_auto_compactions"] = "true"; - // Increase the size of the write buffer, which reduces the number of l0 file by 4x. This does slows down + // Increase the size of the write buffer, which reduces the number of l0 files by 4x. This does slow down // the memtable a little bit. So if you are not write limited, you'll get memtable limited instead. // This does increase the total memory buffer size, but counterintuitively, this reduces overall memory usage - // as it ran out of bloom filter cache so it need to do actual IO. + // as it ran out of bloom filter cache, so it needs to do actual IO. heavyWriteOption["write_buffer_size"] = 64.MiB().ToString(); return heavyWriteOption; @@ -1762,17 +1705,17 @@ private IDictionary GetDisableCompactionOptions() private static IDictionary GetBlobFilesOptions() { // Enable blob files, see: https://rocksdb.org/blog/2021/05/26/integrated-blob-db.html - // This is very useful for blocks, as it almost eliminate 95% of the compaction as the main db no longer + // This is very useful for blocks, as it almost eliminates 95% of the compaction as the main db no longer // store the actual data, but only points to blob files. This config reduces total blocks db writes from about - // 4.6 TB to 0.76 TB, where even the the WAL took 0.45 TB (wal is not compressed), with peak writes of about 300MBps, + // 4.6 TB to 0.76 TB, where even the WAL took 0.45 TB (wal is not compressed), with peak writes of about 300MBps, // it may not even saturate a SATA SSD on a 1GBps internet. - // You don't want to turn this on on other DB as it does add an indirection which take up an additional iop. + // You don't want to turn this on other DB as it does add an indirection which take up an additional iop. // But for large values like blocks (3MB decompressed to 8MB), the response time increase is negligible. - // However without a large buffer size, it will create tens of thousands of small files. There are - // various workaround it, but it all increase total writes, which defeats the purpose. - // Additionally, as the `max_bytes_for_level_base` is set to very low, existing user will suddenly - // get a lot of compaction. So cant turn this on all the time. Turning this back off, will just put back + // However, without a large buffer size, it will create tens of thousands of small files. There are + // various workaround it, but it all increases total writes, which defeats the purpose. + // Additionally, as the `max_bytes_for_level_base` is set to very low, existing users will suddenly + // get a lot of compaction. So can't turn this on all the time. Turning this back off will just put back // new data to SST files. return new Dictionary() @@ -1780,9 +1723,9 @@ private static IDictionary GetBlobFilesOptions() { "enable_blob_files", "true" }, { "blob_compression_type", "kSnappyCompression" }, - // Make file size big, so we have less of them. + // Make the file size big, so we have less of them. { "write_buffer_size", 256.MiB().ToString() }, - // Current memtable + 2 concurrent writes. Can't have too many of these as it take up RAM. + // Current memtable + 2 concurrent writes. Can't have too many of these as it takes up RAM. { "max_write_buffer_number", 3.ToString() }, // These two are SST files instead of the blobs, which are now much smaller. @@ -1795,7 +1738,7 @@ private static IDictionary GetBlobFilesOptions() /// Iterators should not be kept for long as it will pin some memory block and sst file. This would show up as /// temporary higher disk usage or memory usage. /// - /// This class handles a periodic timer which periodically dispose all iterator. + /// This class handles a periodic timer that periodically disposes all iterators. /// public class IteratorManager : IDisposable { @@ -1840,35 +1783,23 @@ public void Dispose() public RentWrapper Rent(ReadFlags flags) { - - ManagedIterators iterators = _readaheadIterators; - if ((flags & ReadFlags.HintReadAhead2) != 0) - { - iterators = _readaheadIterators2; - } - else if ((flags & ReadFlags.HintReadAhead3) != 0) - { - iterators = _readaheadIterators3; - } - + ManagedIterators iterators = GetIterators(flags); IteratorHolder holder = iterators.Value!; // If null, we create a new one. Iterator? iterator = Interlocked.Exchange(ref holder.Iterator, null); return new RentWrapper(iterator ?? _rocksDb.NewIterator(_cf, _readOptions), flags, this); } - private void Return(Iterator iterator, ReadFlags flags) + private ManagedIterators GetIterators(ReadFlags flags) => flags switch { - ManagedIterators iterators = _readaheadIterators; - if ((flags & ReadFlags.HintReadAhead2) != 0) - { - iterators = _readaheadIterators2; - } - else if ((flags & ReadFlags.HintReadAhead3) != 0) - { - iterators = _readaheadIterators3; - } + _ when (flags & ReadFlags.HintReadAhead2) != 0 => _readaheadIterators2, + _ when (flags & ReadFlags.HintReadAhead3) != 0 => _readaheadIterators3, + _ => _readaheadIterators + }; + private void Return(Iterator iterator, ReadFlags flags) + { + ManagedIterators iterators = GetIterators(flags); IteratorHolder holder = iterators.Value!; // We don't keep using the same iterator for too long. @@ -1882,7 +1813,7 @@ private void Return(Iterator iterator, ReadFlags flags) holder.Usage++; Iterator? oldIterator = Interlocked.Exchange(ref holder.Iterator, iterator); - // Well... this is weird. I'll just dispose it. + // Well... this is weird. I'll just dispose of it. oldIterator?.Dispose(); } @@ -1890,21 +1821,14 @@ public readonly struct RentWrapper(Iterator iterator, ReadFlags flags, IteratorM { public Iterator Iterator => iterator; - public void Dispose() - { - manager.Return(iterator, flags); - } + public void Dispose() => manager.Return(iterator, flags); } // Note: use of threadlocal is very important as the seek forward is fast, but the seek backward is not fast. - private sealed class ManagedIterators : ThreadLocal + private sealed class ManagedIterators() : ThreadLocal(static () => new IteratorHolder(), trackAllValues: true) { private bool _disposed = false; - public ManagedIterators() : base(static () => new IteratorHolder(), trackAllValues: true) - { - } - public void ClearIterators() { if (_disposed) return; @@ -1923,7 +1847,7 @@ public void DisposeAll() protected override void Dispose(bool disposing) { - // Note: This is called from finalizer thread, so we can't use foreach to dispose all values + // Note: This is called from finalizer thread, so we can't use foreach to dispose of all values Value?.Dispose(); Value = null!; _disposed = true; @@ -1963,17 +1887,14 @@ public byte[]? LastKey } } - public ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey) - { - return GetViewBetween(firstKey, lastKey, null); - } + public ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey) => GetViewBetween(firstKey, lastKey, null); internal ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey, ColumnFamilyHandle? cf) { ReadOptions readOptions = CreateReadOptions(); - IntPtr iterateLowerBound = IntPtr.Zero; - IntPtr iterateUpperBound = IntPtr.Zero; + IntPtr iterateLowerBound; + IntPtr iterateUpperBound; unsafe { @@ -2008,9 +1929,6 @@ public class RocksDbSnapshot( Snapshot snapshot ) : RocksDbReader(mainDb, readOptionsFactory, null, columnFamily), IKeyValueStoreSnapshot { - public void Dispose() - { - snapshot.Dispose(); - } + public void Dispose() => snapshot.Dispose(); } } diff --git a/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs b/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs index 14bf9efc98f8..548d3d9556a8 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs @@ -15,13 +15,13 @@ internal class MergeOperatorAdapter(IMergeOperator inner) : MergeOperator public string Name => inner.Name; // TODO: fix and return array ptr instead of copying to unmanaged memory? - private static unsafe IntPtr GetResult(ArrayPoolList? data, out IntPtr resultLength, out IntPtr success) + private static unsafe nint GetResult(ArrayPoolList? data, out nint resultLength, out byte success) { if (data is null) { - success = Convert.ToInt32(false); - resultLength = IntPtr.Zero; - return IntPtr.Zero; + success = 0; + resultLength = nint.Zero; + return nint.Zero; } using (data) @@ -34,47 +34,31 @@ private static unsafe IntPtr GetResult(ArrayPoolList? data, out IntPtr res // Fixing RocksDbSharp invalid callback signature, TODO: submit an issue/PR Unsafe.SkipInit(out success); - Unsafe.As(ref success) = 1; + Unsafe.As(ref success) = 1; - return (IntPtr)resultPtr; + return (nint)resultPtr; } } - unsafe IntPtr MergeOperator.PartialMerge( - IntPtr keyPtr, - UIntPtr keyLength, - IntPtr operandsList, - IntPtr operandsListLength, - int numOperands, - out IntPtr successPtr, - out IntPtr resultLength) + public unsafe nint PartialMerge(nint key, nuint keyLength, nint operandsList, nint operandsListLength, int numOperands, out byte success, out nint newValueLength) { - var key = new Span((void*)keyPtr, (int)keyLength); + var keyBytes = new Span((void*)key, (int)keyLength); var enumerator = new RocksDbMergeEnumerator(new((void*)operandsList, numOperands), new((void*)operandsListLength, numOperands)); - ArrayPoolList? result = inner.PartialMerge(key, enumerator); - return GetResult(result, out resultLength, out successPtr); + ArrayPoolList? result = inner.PartialMerge(keyBytes, enumerator); + return GetResult(result, out newValueLength, out success); } - unsafe IntPtr MergeOperator.FullMerge( - IntPtr keyPtr, - UIntPtr keyLength, - IntPtr existingValuePtr, - UIntPtr existingValueLength, - IntPtr operandsList, - IntPtr operandsListLength, - int numOperands, - out IntPtr successPtr, - out IntPtr resultLength) + public unsafe nint FullMerge(nint key, nuint keyLength, nint existingValue, nuint existingValueLength, nint operandsList, nint operandsListLength, int numOperands, out byte success, out nint newValueLength) { - var key = new ReadOnlySpan((void*)keyPtr, (int)keyLength); - bool hasExistingValue = existingValuePtr != IntPtr.Zero; - Span existingValue = hasExistingValue ? new((void*)existingValuePtr, (int)existingValueLength) : Span.Empty; - var enumerator = new RocksDbMergeEnumerator(existingValue, hasExistingValue, new((void*)operandsList, numOperands), new((void*)operandsListLength, numOperands)); + var keyBytes = new ReadOnlySpan((void*)key, (int)keyLength); + bool hasExistingValue = existingValue != nint.Zero; + Span existingValueBytes = hasExistingValue ? new((void*)existingValue, (int)existingValueLength) : []; + var enumerator = new RocksDbMergeEnumerator(existingValueBytes, hasExistingValue, new((void*)operandsList, numOperands), new((void*)operandsListLength, numOperands)); - ArrayPoolList? result = inner.FullMerge(key, enumerator); - return GetResult(result, out resultLength, out successPtr); + ArrayPoolList? result = inner.FullMerge(keyBytes, enumerator); + return GetResult(result, out newValueLength, out success); } - unsafe void MergeOperator.DeleteValue(IntPtr value, UIntPtr valueLength) => NativeMemory.Free((void*)value); + unsafe void MergeOperator.DeleteValue(nint value, nuint valueLength) => NativeMemory.Free((void*)value); } diff --git a/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs b/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs index 61bb02e57e13..a94366c63761 100644 --- a/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs +++ b/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using Nethermind.Core; +using Nethermind.Core.Buffers; using Nethermind.Core.Extensions; using Nethermind.JsonRpc; using Nethermind.JsonRpc.Client; @@ -13,35 +15,25 @@ namespace Nethermind.Db.Rpc { - public class RpcDb : IDb + public class RpcDb( + string dbName, + IJsonSerializer jsonSerializer, + IJsonRpcClient rpcClient, + ILogManager logManager, + IDb recordDb) + : IDb { - private readonly string _dbName; - private readonly IJsonSerializer _jsonSerializer; - private readonly ILogger _logger; - private readonly IJsonRpcClient _rpcClient; - private readonly IDb _recordDb; - - public RpcDb(string dbName, IJsonSerializer jsonSerializer, IJsonRpcClient rpcClient, ILogManager logManager, IDb recordDb) - { - _dbName = dbName; - _rpcClient = rpcClient ?? throw new ArgumentNullException(nameof(rpcClient)); - _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - _recordDb = recordDb; - } + private readonly IJsonSerializer _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); + private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + private readonly IJsonRpcClient _rpcClient = rpcClient ?? throw new ArgumentNullException(nameof(rpcClient)); public void Dispose() { _logger.Info($"Disposing RPC DB {Name}"); - _recordDb.Dispose(); + recordDb.Dispose(); } - public long GetSize() => 0; - public long GetCacheSize() => 0; - public long GetIndexSize() => 0; - public long GetMemtableSize() => 0; - - public string Name { get; } = "RpcDb"; + public string Name => "RpcDb"; public byte[] this[ReadOnlySpan key] { @@ -49,40 +41,16 @@ public byte[] this[ReadOnlySpan key] set => Set(key, value); } - public void Set(ReadOnlySpan key, byte[] value, WriteFlags flags = WriteFlags.None) - { - ThrowWritesNotSupported(); - } - - public byte[] Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - { - return GetThroughRpc(key); - } - + public void Set(ReadOnlySpan key, byte[] value, WriteFlags flags = WriteFlags.None) => ThrowWritesNotSupported(); + public byte[] Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => GetThroughRpc(key); public KeyValuePair[] this[byte[][] keys] => keys.Select(k => new KeyValuePair(k, GetThroughRpc(k))).ToArray(); - - public void Remove(ReadOnlySpan key) - { - ThrowWritesNotSupported(); - } - - public bool KeyExists(ReadOnlySpan key) - { - return GetThroughRpc(key) is not null; - } - - public IDb Innermost => this; // record db is just a helper DB here - public void Flush() { } + public void Remove(ReadOnlySpan key) => ThrowWritesNotSupported(); + public bool KeyExists(ReadOnlySpan key) => GetThroughRpc(key) is not null; public void Flush(bool onlyWal = false) { } - public void Clear() { } - - public IEnumerable> GetAll(bool ordered = false) => _recordDb.GetAll(); - - public IEnumerable GetAllKeys(bool ordered = false) => _recordDb.GetAllKeys(); - - public IEnumerable GetAllValues(bool ordered = false) => _recordDb.GetAllValues(); - + public IEnumerable> GetAll(bool ordered = false) => recordDb.GetAll(); + public IEnumerable GetAllKeys(bool ordered = false) => recordDb.GetAllKeys(); + public IEnumerable GetAllValues(bool ordered = false) => recordDb.GetAllValues(); public IWriteBatch StartWriteBatch() { ThrowWritesNotSupported(); @@ -92,36 +60,24 @@ public IWriteBatch StartWriteBatch() private byte[] GetThroughRpc(ReadOnlySpan key) { - string responseJson = _rpcClient.Post("debug_getFromDb", _dbName, key.ToHexString()).Result; + string responseJson = _rpcClient.Post("debug_getFromDb", dbName, key.ToHexString()).Result; JsonRpcSuccessResponse response = _jsonSerializer.Deserialize(responseJson); byte[] value = null; if (response.Result is not null) { value = Bytes.FromHexString((string)response.Result); - if (_recordDb is not null) + if (recordDb is not null) { - _recordDb[key] = value; + recordDb[key] = value; } } return value; } - public Span GetSpan(ReadOnlySpan key) - { - return Get(key); - } - - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags) - { - ThrowWritesNotSupported(); - } - + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags) => ThrowWritesNotSupported(); private static void ThrowWritesNotSupported() => throw new InvalidOperationException("RPC DB does not support writes"); - - public void DangerousReleaseMemory(in ReadOnlySpan span) - { - } + public void DangerousReleaseMemory(in ReadOnlySpan span) { } } } diff --git a/src/Nethermind/Nethermind.Db.Test/CompressingStoreTests.cs b/src/Nethermind/Nethermind.Db.Test/CompressingStoreTests.cs index 117a4b6d52b8..cb7d63a2d4ba 100644 --- a/src/Nethermind/Nethermind.Db.Test/CompressingStoreTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/CompressingStoreTests.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Test; @@ -71,7 +72,15 @@ public void EOAWithSPan() ctx.Compressed.PutSpan(Key, encoded.Bytes); Assert.That(encoded.Bytes, Is.EqualTo(ctx.Compressed[Key]).AsCollection); - Assert.That(encoded.Bytes, Is.EqualTo(ctx.Compressed.GetSpan(Key).ToArray()).AsCollection); + Span span = ctx.Compressed.GetSpan(Key); + try + { + Assert.That(encoded.Bytes, Is.EqualTo(span.ToArray()).AsCollection); + } + finally + { + ctx.Compressed.DangerousReleaseMemory(span); + } ctx.Wrapped[Key]!.Length.Should().Be(5); } diff --git a/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageCompactorTests.cs b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageCompactorTests.cs new file mode 100644 index 000000000000..e23c4d8a1cba --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageCompactorTests.cs @@ -0,0 +1,184 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Db.LogIndex; +using Nethermind.Logging; +using NSubstitute; +using NUnit.Framework; +using static Nethermind.Db.LogIndex.LogIndexStorage; + +namespace Nethermind.Db.Test.LogIndex; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class LogIndexStorageCompactorTests +{ + private const int RaceConditionTestRepeat = 3; + + private static ILogIndexStorage MockStorage(int? min = null, int? max = null) + { + ILogIndexStorage storage = Substitute.For(); + storage.MinBlockNumber.Returns(min); + storage.MaxBlockNumber.Returns(max); + return storage; + } + + private static Compactor CreateCompactor(ILogIndexStorage storage, IDbMeta? db = null, int compactionDistance = 100) => + new(storage, db ?? new FakeDb(), LimboLogs.Instance.GetClassLogger(), compactionDistance); + + private static Compactor CreateCompactor(ILogIndexStorage storage, int compactionDistance = 100) => + CreateCompactor(storage, db: null, compactionDistance: compactionDistance); + + [TestCase(0, 50, 0, 50, 100, ExpectedResult = false, Description = "No change from baseline")] + [TestCase(0, 0, 0, 99, 100, ExpectedResult = false, Description = "99 blocks forward, threshold 100")] + [TestCase(0, 0, 0, 100, 100, ExpectedResult = true, Description = "Exactly at threshold")] + [TestCase(100, 200, 50, 250, 100, ExpectedResult = true, Description = "Both directions sum to threshold")] + public async Task TryEnqueue_Respects_CompactionDistance( + int initMin, int initMax, int newMin, int newMax, int compactionDistance + ) + { + ILogIndexStorage storage = MockStorage(min: initMin, max: initMax); + using Compactor compactor = CreateCompactor(storage, compactionDistance); + + storage.MinBlockNumber.Returns(newMin); + storage.MaxBlockNumber.Returns(newMax); + + bool result = compactor.TryEnqueue(); + + await compactor.StopAsync(); + return result; + } + + [Test] + [Repeat(RaceConditionTestRepeat)] + public async Task TryEnqueue_During_Compact_Does_Not_Run_Compact_Concurrently() + { + const int compactionDistance = 10; + var compactionDelay = TimeSpan.FromMilliseconds(200); + + ILogIndexStorage storage = MockStorage(min: 0, max: 0); + FakeDb db = new(compactionDelay); + using Compactor compactor = CreateCompactor(storage, db, compactionDistance); + + // Trigger first compaction + storage.MaxBlockNumber.Returns(compactionDistance); + Assert.That(compactor.TryEnqueue(), Is.True); + + await Task.Delay(compactionDelay / 4); + + // Try to cause a second compaction + storage.MaxBlockNumber.Returns(storage.MaxBlockNumber + compactionDistance); + compactor.TryEnqueue(); + + await compactor.ForceAsync(); + await compactor.StopAsync(); + } + + [TestCase(false)] + [TestCase(true)] + [Repeat(RaceConditionTestRepeat)] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] + public async Task ForceAsync_Does_Not_Run_Compact_Concurrently(bool duringCompact) + { + const int compactionDistance = 10; + var compactionDelay = TimeSpan.FromMilliseconds(200); + + ILogIndexStorage storage = MockStorage(min: 0, max: 0); + FakeDb db = new(compactionDelay); + using Compactor compactor = CreateCompactor(storage, db, compactionDistance); + + if (duringCompact) + { + storage.MaxBlockNumber.Returns(compactionDistance); + compactor.TryEnqueue(); + + await Task.Delay(compactionDelay / 4); + } + + const int concurrentCalls = 5; + await Task.WhenAll(Enumerable.Range(0, concurrentCalls).Select(_ => Task.Run(compactor.ForceAsync)).ToArray()); + + await compactor.StopAsync(); + } + + [Test] + public async Task TryEnqueue_Resets_Baseline_After_Enqueue() + { + const int compactionDistance = 10; + + ILogIndexStorage storage = MockStorage(min: 0, max: 0); + using Compactor compactor = CreateCompactor(storage, compactionDistance); + + storage.MaxBlockNumber.Returns(compactionDistance); + Assert.That(compactor.TryEnqueue(), Is.True); + + await Task.Delay(100); + + storage.MaxBlockNumber.Returns(storage.MaxBlockNumber + compactionDistance / 2); + Assert.That(compactor.TryEnqueue(), Is.False); + + await Task.Delay(100); + + storage.MaxBlockNumber.Returns(storage.MaxBlockNumber + compactionDistance / 2); + Assert.That(compactor.TryEnqueue(), Is.True); + + await compactor.StopAsync(); + } + + [Test] + public async Task TryEnqueue_Returns_False_After_Stop() + { + const int compactionDistance = 10; + + ILogIndexStorage storage = MockStorage(min: 0, max: 0); + using Compactor compactor = CreateCompactor(storage, new NonCompactableDb(), compactionDistance); + + await compactor.StopAsync(); + + storage.MaxBlockNumber.Returns(compactionDistance); + Assert.That(compactor.TryEnqueue(), Is.False); + } + + // Fails on compaction attempt + private class NonCompactableDb : IDbMeta + { + private class CompactionException : Exception; + + public void Compact() => throw new CompactionException(); + public void Flush(bool onlyWal = false) { } + } + + // Simulates compaction with Thread.Sleep and fail on concurrent calls + private class FakeDb(TimeSpan? compactDelay = null) : IDbMeta + { + private class ConcurrentCompactionException : Exception; + + private readonly TimeSpan _compactDelay = compactDelay ?? TimeSpan.Zero; + + private int _compacting; + + public void Compact() + { + if (Interlocked.CompareExchange(ref _compacting, 1, 0) != 0) + throw new ConcurrentCompactionException(); + + try + { + if (_compactDelay > TimeSpan.Zero) + Thread.Sleep(_compactDelay); + } + finally + { + Interlocked.Exchange(ref _compacting, 0); + } + } + + public void Flush(bool onlyWal = false) { } + } +} diff --git a/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageIntegrationTests.cs b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageIntegrationTests.cs new file mode 100644 index 000000000000..86e75574ea49 --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageIntegrationTests.cs @@ -0,0 +1,1110 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Runtime.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; +using MathNet.Numerics.Random; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test; +using Nethermind.Core.Test.Builders; +using Nethermind.Db.LogIndex; +using Nethermind.Db.Rocks; +using Nethermind.Db.Rocks.Config; +using Nethermind.Logging; +using Nethermind.TurboPForBindings; +using NUnit.Framework; +using NUnit.Framework.Interfaces; +using static Nethermind.Db.LogIndex.LogIndexStorage; + +namespace Nethermind.Db.Test.LogIndex +{ + [TestFixtureSource(nameof(TestCases))] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] + public class LogIndexStorageIntegrationTests(LogIndexStorageIntegrationTests.TestData testData) + { + private const int RaceConditionTestRepeat = 3; + +#if DEBUG + private static bool LogStatistics => true; +#else + private static bool LogStatistics => false; +#endif + + public static readonly TestFixtureData[] TestCases = + [ + new(new TestData(10, 100) { Compression = CompressionAlgorithm.Best.Key }), + new(new TestData(5, 200) { Compression = nameof(TurboPFor.p4nd1enc128v32) }), + new(new TestData(10, 100) { Compression = CompressionAlgorithm.Best.Key, ExtendedGetRanges = true }) { RunState = RunState.Explicit }, + new(new TestData(100, 100) { Compression = nameof(TurboPFor.p4nd1enc128v32) }) { RunState = RunState.Explicit }, + new(new TestData(100, 200) { Compression = CompressionAlgorithm.Best.Key }) { RunState = RunState.Explicit } + ]; + + private string _dbPath = null!; + private IDbFactory _dbFactory = null!; + private readonly List _createdStorages = []; + + private ILogIndexStorage CreateLogIndexStorage( + int compactionDistance = 262_144, int compressionParallelism = 16, int maxReorgDepth = 64, IDbFactory? dbFactory = null, + string? compressionAlgo = null, int? failOnBlock = null, int? failOnCallN = null, bool failOnMerge = false + ) + { + LogIndexConfig config = new() + { + Enabled = true, + CompactionDistance = compactionDistance, + MaxCompressionParallelism = compressionParallelism, + MaxReorgDepth = maxReorgDepth, + CompressionAlgorithm = compressionAlgo ?? testData.Compression + }; + + ILogIndexStorage storage = failOnBlock is null && failOnCallN is null + ? new LogIndexStorage(dbFactory ?? _dbFactory, LimboLogs.Instance, config) + : new SaveFailingLogIndexStorage(dbFactory ?? _dbFactory, LimboLogs.Instance, config) + { + FailOnBlock = failOnBlock ?? 0, + FailOnCallN = failOnCallN ?? 0, + FailOnMerge = failOnMerge + }; + + return storage.AddTo(_createdStorages); + } + + [SetUp] + public void Setup() + { + _dbPath = $"{nameof(LogIndexStorageIntegrationTests)}/{Guid.NewGuid():N}"; + + if (Directory.Exists(_dbPath)) + Directory.Delete(_dbPath, true); + + Directory.CreateDirectory(_dbPath); + + var config = new DbConfig(); + var configFactory = new RocksDbConfigFactory(config, new PruningConfig(), new TestHardwareInfo(0), LimboLogs.Instance); + _dbFactory = new RocksDbFactory(configFactory, config, new HyperClockCacheWrapper(), new TestLogManager(), _dbPath); + } + + [TearDown] + public async Task TearDown() + { + foreach (ILogIndexStorage storage in _createdStorages) + { + await using (storage) + await storage.StopAsync(); + } + + if (!Directory.Exists(_dbPath)) + return; + + try + { + Directory.Delete(_dbPath, true); + } + catch + { + // ignore + } + } + + [OneTimeSetUp] + //[OneTimeTearDown] // Causes dispose issues, seems to be executed out-of-order + public static void RemoveRootFolder() + { + if (!Directory.Exists(nameof(LogIndexStorageIntegrationTests))) + return; + + try + { + Directory.Delete(nameof(LogIndexStorageIntegrationTests), true); + } + catch + { + // ignore + } + } + + [Combinatorial] + public async Task Set_Get_Test( + [Values(100, 200, int.MaxValue)] int compactionDistance, + [Values(1, 8, 16)] byte ioParallelism, + [Values] bool isBackwardsSync, + [Values] bool compact + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance, ioParallelism); + + BlockReceipts[][] batches = isBackwardsSync ? Reverse(testData.Batches) : testData.Batches; + await AddReceiptsAsync(logIndexStorage, batches, isBackwardsSync); + + if (compact) + await CompactAsync(logIndexStorage); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task SetIntersecting_Get_Test( + [Values(100, 200, int.MaxValue)] int compactionDistance, + [Values(1, 8, 16)] byte ioParallelism, + [Values] bool isBackwardsSync, + [Values] bool compact + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance, ioParallelism); + + BlockReceipts[][] batches = isBackwardsSync ? Reverse(testData.Batches) : testData.Batches; + batches = Intersect(batches); + await AddReceiptsAsync(logIndexStorage, batches, isBackwardsSync); + + if (compact) + await CompactAsync(logIndexStorage); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task BackwardsSet_Set_Get_Test( + [Values(100, 200, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + BlockReceipts[][] batches = testData.Batches; + var half = batches.Length / 2; + + for (var i = 0; i < half + 1; i++) + { + if (half + i < batches.Length) + await AddReceiptsAsync(logIndexStorage, [batches[half + i]], isBackwardsSync: false); + if (i != 0 && half - i >= 0) + await AddReceiptsAsync(logIndexStorage, Reverse([batches[half - i]]), isBackwardsSync: true); + } + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + [Repeat(RaceConditionTestRepeat)] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] + public async Task Concurrent_BackwardsSet_Set_Get_Test( + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using (ILogIndexStorage setStorage = CreateLogIndexStorage(compactionDistance)) + { + int half = testData.Batches.Length / 2; + BlockReceipts[][] batches = testData.Batches + .Select((b, i) => i >= half ? b : b.Reverse().ToArray()) + .ToArray(); + + var forwardTask = Task.Run(async () => + { + for (int i = half; i < batches.Length; i++) + { + BlockReceipts[] batch = batches[i]; + await AddReceiptsAsync(setStorage, [batch], isBackwardsSync: false); + + Assert.That(setStorage.MinBlockNumber, Is.LessThanOrEqualTo(batch[0].BlockNumber)); + Assert.That(setStorage.MaxBlockNumber, Is.EqualTo(batch[^1].BlockNumber)); + } + }); + + var backwardTask = Task.Run(async () => + { + for (int i = half - 1; i >= 0; i--) + { + BlockReceipts[] batch = batches[i]; + await AddReceiptsAsync(setStorage, [batch], isBackwardsSync: true); + + Assert.That(setStorage.MinBlockNumber, Is.EqualTo(batch[^1].BlockNumber)); + Assert.That(setStorage.MaxBlockNumber, Is.GreaterThanOrEqualTo(batch[0].BlockNumber)); + } + }); + + await Task.WhenAll(forwardTask, backwardTask); + } + + // Create new storage to force-load everything from DB + await using (ILogIndexStorage testStorage = CreateLogIndexStorage(compactionDistance)) + VerifyReceipts(testStorage, testData); + } + + [Combinatorial] + [Repeat(RaceConditionTestRepeat)] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] + public async Task Concurrent_BackwardSet_Reorg_Get_Test( + [Values(1, 5, 10)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + int half = testData.Batches.Length / 2; + BlockReceipts[][] forwardBatches = testData.Batches.Skip(half).ToArray(); + BlockReceipts[][] backwardBatches = testData.Batches.Take(half).ToArray(); + + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + // Add forward blocks first to establish the head of the chain + await AddReceiptsAsync(logIndexStorage, forwardBatches, isBackwardsSync: false); + + BlockReceipts[] reorgBlocks = forwardBatches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + + var reorgTask = Task.Run(async () => + { + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + }); + + var backwardTask = Task.Run(async () => + { + foreach (BlockReceipts[] batch in Reverse(backwardBatches)) + await AddReceiptsAsync(logIndexStorage, [batch], isBackwardsSync: true); + }); + + await Task.WhenAll(reorgTask, backwardTask); + + VerifyReceipts(logIndexStorage, testData, excludedBlocks: reorgBlocks, maxBlock: reorgBlocks[0].BlockNumber - 1); + } + + [Combinatorial] + public async Task Set_ReorgLast_Get_Test( + [Values(1, 5, 20)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + BlockReceipts[] reorgBlocks = testData.Batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + + VerifyReceipts(logIndexStorage, testData, excludedBlocks: reorgBlocks, maxBlock: reorgBlocks[0].BlockNumber - 1); + } + + [Ignore("Out-of-order reorgs are not supported ATM: only the first (by write order) Reorg operand per key is applied by MergeOperator.")] + [Combinatorial] + public async Task Set_ReorgOutOfOrder_Get_Test( + [Values(2, 5, 10)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + BlockReceipts[] reverseReorg = testData.Batches.SelectMany(b => b).Reverse().Take(reorgDepth).ToArray(); + + foreach (BlockReceipts block in reverseReorg) + await logIndexStorage.RemoveReorgedAsync(block); + + // Full data verification: would fail because MergeOperator only applies the first Reorg operand + // (the highest block in descending order), leaving intermediate blocks as stale data. + VerifyReceipts(logIndexStorage, testData, excludedBlocks: reverseReorg, maxBlock: reverseReorg[0].BlockNumber - 1); + } + + [Combinatorial] + public async Task Set_ReorgAndSetLast_Get_Test( + [Values(1, 5, 20)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + BlockReceipts[] reorgBlocks = testData.Batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + foreach (BlockReceipts block in reorgBlocks) + { + await logIndexStorage.RemoveReorgedAsync(block); + await logIndexStorage.AddReceiptsAsync([block], false); + } + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task Set_ReorgLast_SetLast_Get_Test( + [Values(1, 5, 20)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + BlockReceipts[] reorgBlocks = testData.Batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + + await logIndexStorage.AddReceiptsAsync(reorgBlocks, false); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task Set_ReorgNonexistent_Get_Test( + [Values(1, 5)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + var lastBlock = testData.Batches[^1][^1].BlockNumber; + BlockReceipts[] reorgBlocks = GenerateBlocks(new Random(4242), lastBlock - reorgDepth + 1, reorgDepth); + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + + // Need custom check because Reorg updates the last block even if it's "nonexistent" + Assert.That(logIndexStorage.MaxBlockNumber, Is.EqualTo(lastBlock - reorgDepth)); + + VerifyReceipts(logIndexStorage, testData, excludedBlocks: reorgBlocks, validateMinMax: false); + } + + [TestCase(1, 1)] + [TestCase(32, 64)] + [TestCase(64, 64)] + [TestCase(65, 64, Explicit = true)] + public async Task Set_Compact_ReorgLast_Get_Test(int reorgDepth, int maxReorgDepth) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(maxReorgDepth: maxReorgDepth); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + await CompactAsync(logIndexStorage); + + BlockReceipts[] reorgBlocks = testData.Batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + + var lastBlock = testData.Batches[^1][^1].BlockNumber; + VerifyReceipts(logIndexStorage, testData, maxBlock: lastBlock - reorgDepth); + } + + [Combinatorial] + public async Task Set_PeriodicReorg_Get_Test( + [Values(10, 70)] int reorgFrequency, + [Values(1, 5)] int maxReorgDepth, + [Values] bool compactAfter + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(); + + var random = new Random(42); + var allReorgBlocks = new List(); + var allAddedBlocks = new List(); + + foreach (BlockReceipts[][] batches in testData.Batches.GroupBy(b => b[0].BlockNumber / reorgFrequency).Select(g => g.ToArray())) + { + await AddReceiptsAsync(logIndexStorage, batches); + + var reorgDepth = random.Next(1, maxReorgDepth); + BlockReceipts[] reorgBlocks = batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + BlockReceipts[] addedBlocks = GenerateBlocks(random, reorgBlocks.First().BlockNumber, reorgBlocks.Length); + + allReorgBlocks.AddRange(reorgBlocks); + allAddedBlocks.AddRange(addedBlocks); + + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + + if (compactAfter) + await CompactAsync(logIndexStorage); + + await logIndexStorage.AddReceiptsAsync(addedBlocks, false); + } + + VerifyReceipts(logIndexStorage, testData, excludedBlocks: allReorgBlocks, addedBlocks: allAddedBlocks); + } + + [Ignore("Not supported, but is probably not needed.")] + [Combinatorial] + public async Task Set_ConsecutiveReorgsLast_Get_Test( + [Values(new[] { 2, 1 }, new[] { 1, 2 })] int[] reorgDepths, + [Values] bool compactBetween + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + BlockReceipts[] testBlocks = testData.Batches.SelectMany(b => b).ToArray(); + + foreach (var reorgDepth in reorgDepths) + { + foreach (BlockReceipts block in testBlocks.TakeLast(reorgDepth).ToArray()) + await logIndexStorage.RemoveReorgedAsync(block); + + if (compactBetween) + await CompactAsync(logIndexStorage); + } + + VerifyReceipts(logIndexStorage, testData, maxBlock: testBlocks[^1].BlockNumber - reorgDepths.Max()); + } + + [Combinatorial] + public async Task SetMultiInstance_Get_Test( + [Values(100, int.MaxValue)] int compactionDistance, + [Values] bool isBackwardsSync + ) + { + var half = testData.Batches.Length / 2; + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + await AddReceiptsAsync(logIndexStorage, testData.Batches.Take(half)); + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + await AddReceiptsAsync(logIndexStorage, testData.Batches.Skip(half)); + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task RepeatedSet_Get_Test( + [Values(100, int.MaxValue)] int compactionDistance, + [Values] bool isBackwardsSync + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task RepeatedSetMultiInstance_Get_Test( + [Values(100, int.MaxValue)] int compactionDistance, + [Values] bool isBackwardsSync + ) + { + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task Set_NewInstance_Get_Test( + [Values(1, 8)] int ioParallelism, + [Values(100, int.MaxValue)] int compactionDistance, + [Values] bool isBackwardsSync + ) + { + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + [Repeat(RaceConditionTestRepeat)] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] + public async Task Set_ConcurrentGet_Test( + [Values(1, 200, int.MaxValue)] int compactionDistance, + [Values] bool isBackwardsSync + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + using var getCancellation = new CancellationTokenSource(); + CancellationToken token = getCancellation.Token; + + ConcurrentBag exceptions = []; + Thread[] getThreads = + [ + new(() => VerifyReceiptsPartialLoop(new Random(42), logIndexStorage, testData, exceptions, token)), + new(() => VerifyReceiptsPartialLoop(new Random(4242), logIndexStorage, testData, exceptions, token)), + new(() => VerifyReceiptsPartialLoop(new Random(424242), logIndexStorage, testData, exceptions, token)) + ]; + getThreads.ForEach(t => t.Start()); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + await getCancellation.CancelAsync(); + getThreads.ForEach(t => t.Join()); + + if (exceptions.FirstOrDefault() is { } exception) + ExceptionDispatchInfo.Capture(exception).Throw(); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task SetFailure_Get_Test( + [Values(1, 20, 51, 100)] int failOnCallN, + [Values] bool isBackwardsSync + ) + { + BlockReceipts[][] batches = isBackwardsSync ? Reverse(testData.Batches) : testData.Batches; + var midBlock = testData.Batches[^1][^1].BlockNumber / 2; + + await using ILogIndexStorage failLogIndexStorage = CreateLogIndexStorage(failOnBlock: midBlock, failOnCallN: failOnCallN); + + Exception exception = Assert.ThrowsAsync(() => AddReceiptsAsync(failLogIndexStorage, batches, isBackwardsSync)); + Assert.That(exception, Has.Message.EqualTo(SaveFailingLogIndexStorage.FailMessage)); + + VerifyReceipts( + failLogIndexStorage, testData, + minBlock: failLogIndexStorage.MinBlockNumber ?? 0, maxBlock: failLogIndexStorage.MaxBlockNumber ?? 0 + ); + } + + [Combinatorial] + public async Task SetFailure_Set_Get_Test( + [Values(1, 20, 51, 100)] int failOnCallN, + [Values] bool isBackwardsSync + ) + { + BlockReceipts[][] batches = isBackwardsSync ? Reverse(testData.Batches) : testData.Batches; + var midBlock = testData.Batches[^1][^1].BlockNumber / 2; + + await using (ILogIndexStorage failLogIndexStorage = CreateLogIndexStorage(failOnBlock: midBlock, failOnCallN: failOnCallN)) + { + Exception exception = Assert.ThrowsAsync(() => AddReceiptsAsync(failLogIndexStorage, batches, isBackwardsSync)); + Assert.That(exception, Has.Message.EqualTo(SaveFailingLogIndexStorage.FailMessage)); + } + + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(); + await AddReceiptsAsync(logIndexStorage, batches, isBackwardsSync); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] + public async Task SetMergeFailure_AnyWrite_Test( + [Values(1, 20, 51, 100)] int failOnCallN + ) + { + var midBlock = testData.Batches[^1][^1].BlockNumber / 2; + await using var storage = (LogIndexStorage)CreateLogIndexStorage(failOnBlock: midBlock, failOnCallN: failOnCallN, failOnMerge: true); + + try + { + await AddReceiptsAsync(storage, testData.Batches); + + // force compaction if the error hasn't propagated already + await storage.CompactAsync(); + } + catch (LogIndexStateException) + { + // Expected + } + + Assert.That(storage.HasBackgroundError, Is.True); + + IEnumerable sinceLastBatch = testData.Batches.SelectMany(b => b) + .SkipWhile(b => b.BlockNumber < storage.MaxBlockNumber); + + await Assert.ThatAsync( + () => storage.AddReceiptsAsync(sinceLastBatch.Skip(1).Take(10).ToArray(), false), + Throws.InstanceOf().And.Message.Contain("merge") + ); + + await Assert.ThatAsync( + () => storage.RemoveReorgedAsync(sinceLastBatch.First()), + Throws.InstanceOf().And.Message.Contain("merge") + ); + + await Assert.ThatAsync( + () => storage.CompactAsync(), + Throws.InstanceOf().And.Message.Contain("merge") + ); + + Assert.DoesNotThrowAsync(() => storage.StopAsync()); + Assert.DoesNotThrowAsync(() => storage.StopAsync()); + } + + [Combinatorial] + public async Task Set_AlgoChange_Test() + { + if (CompressionAlgorithm.Supported.Count < 2) + Assert.Ignore("Less than 2 supported compression algorithms"); + + await using (var logIndexStorage = CreateLogIndexStorage()) + await AddReceiptsAsync(logIndexStorage, [testData.Batches[0]]); + + var oldAlgo = testData.Compression ?? CompressionAlgorithm.Best.Key; + var newAlgo = CompressionAlgorithm.Supported.First(c => c.Key != oldAlgo).Key; + + NotSupportedException exception = Assert.Throws(() => CreateLogIndexStorage(compressionAlgo: newAlgo)); + Assert.That(exception, Has.Message.Contain(oldAlgo).And.Message.Contain(newAlgo)); + } + + private static BlockReceipts[] GenerateBlocks(Random random, int from, int count) => + new TestData(random, 1, count, startNum: from).Batches[0]; + + private static async Task AddReceiptsAsync(ILogIndexStorage logIndexStorage, IEnumerable batches, bool isBackwardsSync = false) + { + long timestamp = Stopwatch.GetTimestamp(); + + LogIndexUpdateStats totalStats = new(logIndexStorage); + (int count, int length) = (0, 0); + foreach (BlockReceipts[] batch in batches) + { + count++; + length = batch.Length; + await logIndexStorage.AddReceiptsAsync(batch, isBackwardsSync, totalStats); + } + + if (LogStatistics) + { + await TestContext.Out.WriteLineAsync( + $""" + x{count} {nameof(LogIndexStorage.AddReceiptsAsync)}([{length}], {isBackwardsSync}) in {Stopwatch.GetElapsedTime(timestamp)}: + {totalStats:d} + {'\t'}DB size: {logIndexStorage.GetDbSize()} + + """ + ); + } + } + + private static void VerifyReceipts(ILogIndexStorage logIndexStorage, TestData testData, + Dictionary>? excludedAddresses = null, + Dictionary>>? excludedTopics = null, + Dictionary>? addedAddresses = null, + Dictionary>>? addedTopics = null, + int? minBlock = null, int? maxBlock = null, + bool validateMinMax = true + ) + { + minBlock ??= testData.Batches[0][0].BlockNumber; + maxBlock ??= testData.Batches[^1][^1].BlockNumber; + + if (validateMinMax) + { + using (Assert.EnterMultipleScope()) + { + Assert.That(logIndexStorage.MinBlockNumber, Is.EqualTo(minBlock)); + Assert.That(logIndexStorage.MaxBlockNumber, Is.EqualTo(maxBlock)); + } + } + + foreach ((Address address, HashSet blocks) in testData.AddressMap) + { + IEnumerable expectedBlocks = blocks; + + if (excludedAddresses != null && excludedAddresses.TryGetValue(address, out HashSet addressExcludedBlocks)) + expectedBlocks = expectedBlocks.Except(addressExcludedBlocks); + + if (addedAddresses != null && addedAddresses.TryGetValue(address, out HashSet addressAddedBlocks)) + expectedBlocks = expectedBlocks.Concat(addressAddedBlocks); + + expectedBlocks = expectedBlocks.Order(); + + if (minBlock > testData.Batches[0][0].BlockNumber) + expectedBlocks = expectedBlocks.SkipWhile(b => b < minBlock); + + if (maxBlock < testData.Batches[^1][^1].BlockNumber) + expectedBlocks = expectedBlocks.TakeWhile(b => b <= maxBlock); + + expectedBlocks = expectedBlocks.ToArray(); + + foreach (var (from, to) in testData.Ranges) + { + Assert.That( + logIndexStorage.GetBlockNumbersFor(address, from, to), + Is.EqualTo(expectedBlocks.SkipWhile(i => i < from).TakeWhile(i => i <= to)), + $"Address: {address}, from {from} to {to}" + ); + } + } + + foreach ((int idx, Dictionary> byTopic) in testData.TopicMap) + { + foreach ((Hash256 topic, HashSet blocks) in byTopic) + { + IEnumerable expectedBlocks = blocks; + + if (excludedTopics != null && excludedTopics[idx].TryGetValue(topic, out HashSet topicExcludedBlocks)) + expectedBlocks = expectedBlocks.Except(topicExcludedBlocks); + + if (addedTopics != null && addedTopics[idx].TryGetValue(topic, out HashSet topicAddedBlocks)) + expectedBlocks = expectedBlocks.Concat(topicAddedBlocks); + + expectedBlocks = expectedBlocks.Order(); + + if (minBlock > testData.Batches[0][0].BlockNumber) + expectedBlocks = expectedBlocks.SkipWhile(b => b < minBlock); + + if (maxBlock < testData.Batches[^1][^1].BlockNumber) + expectedBlocks = expectedBlocks.TakeWhile(b => b <= maxBlock); + + expectedBlocks = expectedBlocks.ToArray(); + + foreach (var (from, to) in testData.Ranges) + { + Assert.That( + logIndexStorage.GetBlockNumbersFor(idx, topic, from, to), + Is.EqualTo(expectedBlocks.SkipWhile(i => i < from).TakeWhile(i => i <= to)), + $"Topic: [{idx}] {topic}, {from} - {to}" + ); + } + } + } + } + + private static void VerifyReceipts(ILogIndexStorage logIndexStorage, TestData testData, + IEnumerable? excludedBlocks, IEnumerable? addedBlocks = null, + int? minBlock = null, int? maxBlock = null, + bool validateMinMax = true) + { + var excludeMaps = excludedBlocks == null ? default : TestData.GenerateMaps(excludedBlocks); + var addMaps = addedBlocks == null ? default : TestData.GenerateMaps(addedBlocks); + + VerifyReceipts( + logIndexStorage, testData, + excludedAddresses: excludeMaps.address, excludedTopics: excludeMaps.topic, + addedAddresses: addMaps.address, addedTopics: addMaps.topic, + minBlock: minBlock, maxBlock: maxBlock, + validateMinMax: validateMinMax + ); + } + + private static void VerifyReceiptsPartialLoop(Random random, ILogIndexStorage logIndexStorage, TestData testData, + ConcurrentBag exceptions, CancellationToken cancellationToken) + { + try + { + (List
addresses, List<(int, Hash256)> topics) = (testData.Addresses, testData.Topics); + + while (!cancellationToken.IsCancellationRequested) + { + if (addresses.Count != 0) + { + Address address = random.NextFrom(addresses); + HashSet expectedBlocks = testData.AddressMap[address]; + + if (logIndexStorage.MinBlockNumber is not { } min || logIndexStorage.MaxBlockNumber is not { } max) + continue; + + Assert.That( + logIndexStorage.GetBlockNumbersFor(address, min, max), + Is.EqualTo(expectedBlocks.SkipWhile(i => i < min).TakeWhile(i => i <= max)), + $"Address: {address}, available: {min} - {max}" + ); + } + + if (topics.Count != 0) + { + (int idx, Hash256 topic) = random.NextFrom(topics); + HashSet expectedBlocks = testData.TopicMap[idx][topic]; + + if (logIndexStorage.MinBlockNumber is not { } min || logIndexStorage.MaxBlockNumber is not { } max) + continue; + + Assert.That( + logIndexStorage.GetBlockNumbersFor(idx, topic, min, max), + Is.EqualTo(expectedBlocks.SkipWhile(i => i < min).TakeWhile(i => i <= max)), + $"Topic: [{idx}] {topic}, available: {min} - {max}" + ); + } + } + } + catch (Exception ex) + { + exceptions.Add(ex); + } + } + + private static BlockReceipts[][] Reverse(BlockReceipts[][] batches) + { + int length = batches.Length; + BlockReceipts[][] result = new BlockReceipts[length][]; + + int index = 0; + foreach (BlockReceipts[] batch in batches.Reverse()) + result[index++] = batch.Reverse().ToArray(); + + return result; + } + + private static BlockReceipts[][] Intersect(BlockReceipts[][] batches) + { + BlockReceipts[][] result = new BlockReceipts[batches.Length + 1][]; + + for (int i = 0; i < result.Length; i++) + { + if (i == 0) + result[i] = batches[i]; + else if (i == batches.Length) + result[i] = batches[^1].Skip(batches[^2].Length / 2).ToArray(); + else + result[i] = batches[i - 1].Skip(batches[i - 1].Length / 2).Concat(batches[i].Take(batches[i].Length / 2)).ToArray(); + } + + return result; + } + + private static async Task CompactAsync(ILogIndexStorage logIndexStorage) + { + long timestamp = Stopwatch.GetTimestamp(); + await ((LogIndexStorage)logIndexStorage).CompactAsync(); + + if (LogStatistics) + { + await TestContext.Out.WriteLineAsync( + $""" + {nameof(LogIndexStorage.CompactAsync)}() in {Stopwatch.GetElapsedTime(timestamp)}: + {'\t'}DB size: {logIndexStorage.GetDbSize()} + + """ + ); + } + } + + public class TestData + { + private readonly int _batchCount; + private readonly int _blocksPerBatch; + private readonly int _startNum; + + // Lazy avoids generating all the data just to display test cases in the runner + private readonly Lazy _batches; + public BlockReceipts[][] Batches => _batches.Value; + + private List
? _addresses; + private List<(int, Hash256)>? _topics; + + private readonly Lazy> _ranges; + public IEnumerable<(int from, int to)> Ranges => _ranges.Value; + + public Dictionary> AddressMap { get; private set; } = new(); + public Dictionary>> TopicMap { get; private set; } = new(); + + public List
Addresses + { + get + { + _ = Batches; + return _addresses!; + } + } + + public List<(int, Hash256)> Topics + { + get + { + _ = Batches; + return _topics!; + } + } + + public bool ExtendedGetRanges { get; init; } + public string? Compression { get; init; } + + public TestData(Random random, int batchCount, int blocksPerBatch, int startNum = 0) + { + _batchCount = batchCount; + _blocksPerBatch = blocksPerBatch; + _startNum = startNum; + + _batches = new(() => GenerateBatches(random, batchCount, blocksPerBatch, startNum)); + _ranges = new(() => ExtendedGetRanges ? GenerateExtendedRanges() : GenerateSimpleRanges()); + } + + public TestData(int batchCount, int blocksPerBatch, int startNum = 0) : this(new(42), batchCount, blocksPerBatch, startNum) { } + + private BlockReceipts[][] GenerateBatches(Random random, int batchCount, int blocksPerBatch, int startNum = 0) + { + var batches = new BlockReceipts[batchCount][]; + var blocksCount = batchCount * blocksPerBatch; + + Address[] customAddresses = + [ + Address.Zero, Address.MaxValue, + new(new byte[] { 1 }.PadLeft(Address.Size)), new(new byte[] { 1, 1 }.PadLeft(Address.Size)), + new(new byte[] { 1 }.PadRight(Address.Size)), new(new byte[] { 1, 1 }.PadRight(Address.Size)), + new(new byte[] { 0 }.PadLeft(Address.Size, 0xFF)), new(new byte[] { 0 }.PadRight(Address.Size, 0xFF)), + ]; + + Hash256[] customTopics = + [ + Hash256.Zero, new(Array.Empty().PadRight(Hash256.Size, 0xFF)), + new(new byte[] { 0 }.PadLeft(Hash256.Size)), new(new byte[] { 1 }.PadLeft(Hash256.Size)), + new(new byte[] { 0 }.PadRight(Hash256.Size)), new(new byte[] { 1 }.PadRight(Hash256.Size)), + new(new byte[] { 0 }.PadLeft(Hash256.Size, 0xFF)), new(new byte[] { 0 }.PadRight(Hash256.Size, 0xFF)), + ]; + + Address[] addresses = Enumerable.Repeat(0, Math.Max(10, blocksCount / 5) - customAddresses.Length) + //var addresses = Enumerable.Repeat(0, 0) + .Select(_ => new Address(random.NextBytes(Address.Size))) + .Concat(customAddresses) + .ToArray(); + Hash256[] topics = Enumerable.Repeat(0, addresses.Length * 7 - customTopics.Length) + //var topics = Enumerable.Repeat(0, 0) + .Select(_ => new Hash256(random.NextBytes(Hash256.Size))) + .Concat(customTopics) + .ToArray(); + + // Generate batches + int blockNum = startNum; + for (int i = 0; i < batches.Length; i++) + { + BlockReceipts[] batch = batches[i] = new BlockReceipts[blocksPerBatch]; + + for (int j = 0; j < batch.Length; j++) + batch[j] = new(blockNum++, GenerateReceipts(random, addresses, topics)); + } + + var maps = GenerateMaps(batches.SelectMany(b => b)); + + (AddressMap, TopicMap) = (maps.address, maps.topic); + + _addresses = maps.address.Keys.ToList(); + _topics = maps.topic.SelectMany(byIdx => byIdx.Value.Select(byTpc => (byIdx.Key, byTpc.Key))).ToList(); + + return batches; + } + + public static (Dictionary> address, Dictionary>> topic) GenerateMaps( + IEnumerable blocks) + { + Dictionary> address = new(); + Dictionary>> topic = new(); + + foreach (BlockReceipts block in blocks) + { + foreach (TxReceipt txReceipt in block.Receipts) + { + foreach (LogEntry log in txReceipt.Logs!) + { + HashSet addressMap = address.GetOrAdd(log.Address, static _ => []); + addressMap.Add(block.BlockNumber); + + for (int i = 0; i < log.Topics.Length; i++) + { + Dictionary> topicI = topic.GetOrAdd(i, static _ => []); + HashSet topicMap = topicI.GetOrAdd(log.Topics[i], static _ => []); + topicMap.Add(block.BlockNumber); + } + } + } + } + + return (address, topic); + } + + private static TxReceipt[] GenerateReceipts(Random random, Address[] addresses, Hash256[] topics) + { + (int min, int max) logsPerBlock = (0, 200); + (int min, int max) logsPerTx = (0, 10); + + LogEntry[] logs = Enumerable + .Repeat(0, random.Next(logsPerBlock.min, logsPerBlock.max + 1)) + .Select(_ => Build.A.LogEntry + .WithAddress(random.NextFrom(addresses)) + .WithTopics(topics.Length == 0 + ? [] + : Enumerable.Repeat(0, random.Next(4)).Select(_ => random.NextFrom(topics)).ToArray() + ).TestObject + ).ToArray(); + + List receipts = new(); + for (var i = 0; i < logs.Length;) + { + int count = random.Next(logsPerTx.min, Math.Min(logsPerTx.max, logs.Length - i) + 1); + Range range = i..(i + count); + + receipts.Add(new() { Logs = logs[range] }); + i = range.End.Value; + } + + return receipts.ToArray(); + } + + private static HashSet<(int from, int to)> GenerateSimpleRanges(int min, int max) + { + int quarter = (max - min) / 4; + return [(0, int.MaxValue), (min, max), (min + quarter, max - quarter)]; + } + + private static HashSet<(int from, int to)> GenerateExtendedRanges(int min, int max) + { + HashSet<(int, int)> ranges = new(); + + int[] edges = [min - 1, min, min + 1, max - 1, max + 1]; + ranges.AddRange(edges.SelectMany(_ => edges, static (x, y) => (x, y))); + + const int step = 100; + for (var i = min; i <= max; i += step) + { + var middles = new[] { i - step, i - 1, i, i + 1, i + step }; + ranges.AddRange(middles.SelectMany(_ => middles, static (x, y) => (x, y))); + } + + return ranges; + } + + private HashSet<(int from, int to)> GenerateSimpleRanges() => GenerateSimpleRanges( + _startNum, _startNum + _batchCount * _blocksPerBatch - 1 + ); + + private HashSet<(int from, int to)> GenerateExtendedRanges() => GenerateExtendedRanges( + _startNum, _startNum + _batchCount * _blocksPerBatch - 1 + ); + + public override string ToString() => + $"{_batchCount} * {_blocksPerBatch} blocks (ex-ranges: {ExtendedGetRanges}, compression: {Compression})"; + } + + private class SaveFailingLogIndexStorage(IDbFactory dbFactory, ILogManager logManager, ILogIndexConfig config) + : LogIndexStorage(dbFactory, logManager, config) + { + public const string FailMessage = "Test exception."; + + public int FailOnBlock { get; init; } + public int FailOnCallN { get; init; } + public bool FailOnMerge { get; init; } + + private int _count; + private bool _corrupted; + + protected override void MergeBlockNumbers(IWriteBatch dbBatch, ReadOnlySpan key, List numbers, bool isBackwardSync, LogIndexUpdateStats? stats) + { + var isFailBlock = + FailOnBlock >= Math.Min(numbers[0], numbers[^1]) && + FailOnBlock <= Math.Max(numbers[0], numbers[^1]); + + if (isFailBlock && Interlocked.Increment(ref _count) >= FailOnCallN && !Interlocked.Exchange(ref _corrupted, true)) + { + if (FailOnMerge) + { + // Force "invalid order" in MergeOperator + int invalidBlockNum = isBackwardSync ? int.MaxValue : 0; + base.MergeBlockNumbers(dbBatch, key, [invalidBlockNum], isBackwardSync, stats); + } + else + { + throw new(FailMessage); + } + } + + base.MergeBlockNumbers(dbBatch, key, numbers, isBackwardSync, stats); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageTestExtensions.cs b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageTestExtensions.cs new file mode 100644 index 000000000000..dccf8578a996 --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageTestExtensions.cs @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Db.LogIndex; + +namespace Nethermind.Db.Test.LogIndex; + +public static class LogIndexStorageTestExtensions +{ + extension(ILogIndexStorage storage) + { + public List GetBlockNumbersFor(Address address, int from, int to) + { + var result = new List(); + using IEnumerator enumerator = storage.GetEnumerator(address, from, to); + + while (enumerator.MoveNext()) + result.Add(enumerator.Current); + + return result; + } + + public List GetBlockNumbersFor(int index, Hash256 topic, int from, int to) + { + var result = new List(); + using IEnumerator enumerator = storage.GetEnumerator(index, topic, from, to); + + while (enumerator.MoveNext()) + result.Add(enumerator.Current); + + return result; + } + + public Task AddReceiptsAsync(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats = null) + { + LogIndexAggregate aggregate = storage.Aggregate(batch, isBackwardSync, stats); + return storage.AddReceiptsAsync(aggregate, stats); + } + } +} diff --git a/src/Nethermind/Nethermind.Db.Test/LogIndex/MergeOperatorTests.cs b/src/Nethermind/Nethermind.Db.Test/LogIndex/MergeOperatorTests.cs new file mode 100644 index 000000000000..a7d9386face6 --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Test/LogIndex/MergeOperatorTests.cs @@ -0,0 +1,247 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Linq; +using System.Runtime.InteropServices; +using ConcurrentCollections; +using MathNet.Numerics.Random; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Extensions; +using Nethermind.Db.LogIndex; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Db.Test.LogIndex; + +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class MergeOperatorTests +{ + [TestCase( + null, + new[] { "1, 2", "3", "4" }, + "1, 2, 3, 4" + )] + [TestCase( + "1", + new[] { "2, 3", "4" }, + "1, 2, 3, 4" + )] + [TestCase( + "1, 2", + new[] { "3, 4", "Reorg:3", "5, 6" }, + "1, 2, 5, 6" + )] + [TestCase( + "1, 2", + new[] { "3, 4", "Reorg:2", "5, 6" }, + "1, 5, 6" + )] + [TestCase( + "1, 2", + new[] { "3, 4", "Reorg:3", "Reorg:4", "5, 6" }, + "1, 2, 5, 6" + )] + [TestCase( + "1, 2", + new[] { "3, 4", "Reorg:4", "Reorg:3", "5, 6" }, + "1, 2, 5, 6", + Ignore = "Subsequent reverse reorgs are not supported." + )] + [TestCase( + "1, 2", + new[] { "3, 4", "Reorg:3", "5, 6", "Reorg:5", "6, 7" }, + "1, 2, 6, 7" + )] + [TestCase( + "1, 2, 3", + new[] { "4, 5", "Truncate:4", "6, 7" }, + "5, 6, 7" + )] + [TestCase( + "1, 2, 3", + new[] { "4, 5", "Truncate:3", "6, 7" }, + "4, 5, 6, 7" + )] + [TestCase( + "1, 2, 3", + new[] { "4, 5", "Truncate:3", "Truncate:4", "6, 7" }, + "5, 6, 7" + )] + [TestCase( + "1, 2, 3", + new[] { "4, 5", "Truncate:4", "Truncate:3", "6, 7" }, + "5, 6, 7" + )] + [TestCase( + "1, 2, 3", + new[] { "4, 5", "Truncate:3", "6, 7", "Truncate:6" }, + "7" + )] + public void FullMergeForward(string? existing, string[] operands, string expected) + { + LogIndexStorage.MergeOperator op = CreateOperator(); + CreateEnumerator(Serialize(existing), operands.Select(Serialize).ToArray(), out RocksDbMergeEnumerator enumerator); + + byte[] key = GenerateKey(Address.Size, isBackward: false); + using ArrayPoolList merged = op.FullMerge(key, enumerator); + Assert.That( + Deserialize(merged?.ToArray()), + Is.EqualTo(expected.Split(',').Select(int.Parse).ToArray()) + ); + } + + [TestCase( + null, + new[] { "4", "3", "2, 1" }, + "4, 3, 2, 1" + )] + [TestCase( + "4", + new[] { "3, 2", "1" }, + "4, 3, 2, 1" + )] + [TestCase( + "7, 6, 5", + new[] { "4, 3", "Truncate:4", "2, 1" }, + "3, 2, 1" + )] + [TestCase( + "7, 6, 5", + new[] { "4, 3", "Truncate:5", "2, 1" }, + "4, 3, 2, 1" + )] + [TestCase( + "7, 6, 5", + new[] { "4, 3", "Truncate:5", "Truncate:4", "2, 1" }, + "3, 2, 1" + )] + [TestCase( + "7, 6, 5", + new[] { "4, 3", "Truncate:4", "Truncate:5", "2, 1" }, + "3, 2, 1" + )] + [TestCase( + "7, 6, 5", + new[] { "4, 3", "Truncate:5", "2, 1", "Truncate:2" }, + "1" + )] + public void FullMergeBackward(string? existing, string[] operands, string expected) + { + LogIndexStorage.MergeOperator op = CreateOperator(); + CreateEnumerator(Serialize(existing), operands.Select(Serialize).ToArray(), out RocksDbMergeEnumerator enumerator); + + byte[] key = GenerateKey(Address.Size, isBackward: true); + ArrayPoolList merged = op.FullMerge(key, enumerator); + Assert.That( + Deserialize(merged?.ToArray()), + Is.EqualTo(expected.Split(',').Select(int.Parse).ToArray()) + ); + } + + [TestCase( + new[] { "1", "2", "3", "4" }, + "1, 2, 3, 4" + )] + [TestCase( + new[] { "1", "2, 3", "4" }, + "1, 2, 3, 4" + )] + public void PartialMergeForward(string[] operands, string expected) + { + LogIndexStorage.MergeOperator op = CreateOperator(); + CreateEnumerator(null, operands.Select(Serialize).ToArray(), out RocksDbMergeEnumerator enumerator); + + byte[] key = GenerateKey(Address.Size, isBackward: false); + using ArrayPoolList merged = op.PartialMerge(key, enumerator); + Assert.That( + Deserialize(merged?.ToArray()), + Is.EqualTo(expected.Split(',').Select(int.Parse).ToArray()) + ); + } + + [TestCase( + new[] { "4", "3", "2", "1" }, + "4, 3, 2, 1" + )] + [TestCase( + new[] { "4", "3, 2", "1" }, + "4, 3, 2, 1" + )] + public void PartialMergeBackward(string[] operands, string expected) + { + LogIndexStorage.MergeOperator op = CreateOperator(); + CreateEnumerator(null, operands.Select(Serialize).ToArray(), out RocksDbMergeEnumerator enumerator); + + byte[] key = GenerateKey(Address.Size, isBackward: true); + using ArrayPoolList merged = op.PartialMerge(key, enumerator); + Assert.That( + Deserialize(merged?.ToArray()), + Is.EqualTo(expected.Split(',').Select(int.Parse).ToArray()) + ); + } + + [OneTimeTearDown] + public static void OneTimeTearDown() => Handles.ForEach(h => h.Free()); + + private static readonly LogIndexStorage.ICompressor Compressor = new LogIndexStorage.NoOpCompressor(); + + private static readonly ConcurrentHashSet Handles = []; + + private static LogIndexStorage.MergeOperator CreateOperator() + { + ILogIndexStorage storage = Substitute.For(); + return new(storage, Compressor, 0); + } + + private static void CreateEnumerator(byte[]? existingValue, byte[][] operands, out RocksDbMergeEnumerator enumerator) + { + var operandsPtrs = new IntPtr[operands.Length]; + var operandsLengths = operands.Select(x => (long)x.Length).ToArray(); + + for (int i = 0; i < operands.Length; i++) + { + var handle = GCHandle.Alloc(operands[i], GCHandleType.Pinned); + Handles.Add(handle); + + operandsPtrs[i] = handle.AddrOfPinnedObject(); + operandsLengths[i] = operands[i].Length; + } + + enumerator = existingValue is null + ? new(Span.Empty, false, operandsPtrs, operandsLengths) + : new(existingValue, true, operandsPtrs, operandsLengths); + } + + private static byte[] GenerateKey(int prefixSize, bool isBackward) => Random.Shared + .NextBytes(prefixSize + LogIndexStorage.BlockNumberSize) + .Concat(isBackward ? LogIndexStorage.Postfix.BackwardMerge : LogIndexStorage.Postfix.ForwardMerge) + .ToArray(); + + private static byte[]? Serialize(string? input) => + input is null ? null : Bytes.Concat(input.Split(',').Select(s => s.Trim()).Select(s => s switch + { + _ when int.TryParse(s, out int blockNum) => blockNum.ToLittleEndianByteArray(), + _ when TryParseMergeOp(s, out Span op) => op.ToArray(), + _ => throw new FormatException($"Invalid operand: \"{input}\".") + }).ToArray()); + + private static bool TryParseMergeOp(string input, out Span bytes) + { + bytes = default; + + var parts = input.Split(":"); + if (parts.Length != 2) return false; + + if (!Enum.TryParse(parts[0], out LogIndexStorage.MergeOp op)) return false; + if (!int.TryParse(parts[1], out int blockNum)) return false; + + var buffer = new byte[LogIndexStorage.MergeOps.Size]; + bytes = LogIndexStorage.MergeOps.Create(op, blockNum, buffer); + return true; + } + + private static int[]? Deserialize(byte[]? input) => input is null ? null : MemoryMarshal.Cast(input).ToArray(); +} diff --git a/src/Nethermind/Nethermind.Db/CompressingDb.cs b/src/Nethermind/Nethermind.Db/CompressingDb.cs index 881b839f25bb..301da2f7b1d4 100644 --- a/src/Nethermind/Nethermind.Db/CompressingDb.cs +++ b/src/Nethermind/Nethermind.Db/CompressingDb.cs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using Nethermind.Core; +using Nethermind.Core.Buffers; using Nethermind.Core.Extensions; namespace Nethermind.Db @@ -18,75 +20,55 @@ public static class KeyValueStoreCompressingExtensions /// A wrapped db. public static IDb WithEOACompressed(this IDb @this) => new EOACompressingDb(@this); - private class EOACompressingDb : IDb, ITunableDb + // TODO: consider wrapping IDbWithSpan to make the read with a span, with no alloc for reading? + private class EOACompressingDb(IDb wrapped) : IDb, ITunableDb { - private readonly IDb _wrapped; - - public EOACompressingDb(IDb wrapped) - { - // TODO: consider wrapping IDbWithSpan to make the read with a span, with no alloc for reading? - _wrapped = wrapped; - } - public byte[]? this[ReadOnlySpan key] { - get => Decompress(_wrapped[key]); - set => _wrapped[key] = Compress(value); + get => Decompress(wrapped[key]); + set => wrapped[key] = Compress(value); } - public IWriteBatch StartWriteBatch() => new WriteBatch(_wrapped.StartWriteBatch()); + public IWriteBatch StartWriteBatch() => new WriteBatch(wrapped.StartWriteBatch()); - private class WriteBatch : IWriteBatch + private class WriteBatch(IWriteBatch wrapped) : IWriteBatch { - private readonly IWriteBatch _wrapped; - - public WriteBatch(IWriteBatch wrapped) => _wrapped = wrapped; + public void Dispose() => wrapped.Dispose(); - public void Dispose() => _wrapped.Dispose(); - - public void Clear() => _wrapped.Clear(); + public void Clear() => wrapped.Clear(); public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - => _wrapped.Set(key, Compress(value), flags); + => wrapped.Set(key, Compress(value), flags); public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - _wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags); - } + => wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags); public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - throw new InvalidOperationException("EOA compressing DB does not support merging"); - } + => throw new InvalidOperationException("EOA compressing DB does not support merging"); - public bool PreferWriteByArray => _wrapped.PreferWriteByArray; + public bool PreferWriteByArray => wrapped.PreferWriteByArray; public byte[]? this[ReadOnlySpan key] { - set => _wrapped[key] = Compress(value); + set => wrapped[key] = Compress(value); } } - /// /// The end of rlp of an EOA account, an empty and an empty . /// - private static ReadOnlySpan EmptyCodeHashStorageRoot => new byte[] - { + private static ReadOnlySpan EmptyCodeHashStorageRoot => + [ 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112 - }; + ]; private const byte PreambleLength = 1; private const byte PreambleIndex = 0; private const byte PreambleValue = 0; - private static byte[]? Compress(byte[]? bytes) - { - if (bytes is null) return null; - return Compress(bytes, stackalloc byte[bytes.Length]).ToArray(); - } + private static byte[]? Compress(byte[]? bytes) => bytes is null ? null : Compress(bytes, stackalloc byte[bytes.Length]).ToArray(); private static ReadOnlySpan Compress(ReadOnlySpan bytes, Span compressed) { @@ -125,55 +107,56 @@ private static ReadOnlySpan Compress(ReadOnlySpan bytes, Span return decompressed; } - public void Dispose() => _wrapped.Dispose(); + public void Dispose() => wrapped.Dispose(); - public string Name => _wrapped.Name; + public string Name => wrapped.Name; public KeyValuePair[] this[byte[][] keys] => throw new NotImplementedException(); - public IEnumerable> GetAll(bool ordered = false) => _wrapped.GetAll(ordered) + public IEnumerable> GetAll(bool ordered = false) => wrapped.GetAll(ordered) .Select(static kvp => new KeyValuePair(kvp.Key, Decompress(kvp.Value))); public IEnumerable GetAllKeys(bool ordered = false) => - _wrapped.GetAllKeys(ordered); + wrapped.GetAllKeys(ordered); public IEnumerable GetAllValues(bool ordered = false) => - _wrapped.GetAllValues(ordered).Select(Decompress); + wrapped.GetAllValues(ordered).Select(Decompress); - public void Remove(ReadOnlySpan key) => _wrapped.Remove(key); + public void Remove(ReadOnlySpan key) => wrapped.Remove(key); - public bool KeyExists(ReadOnlySpan key) => _wrapped.KeyExists(key); + public bool KeyExists(ReadOnlySpan key) => wrapped.KeyExists(key); - public void Flush(bool onlyWal) => _wrapped.Flush(onlyWal); + public void Flush(bool onlyWal) => wrapped.Flush(onlyWal); - public void Clear() => _wrapped.Clear(); + public void Clear() => wrapped.Clear(); - public IDbMeta.DbMetric GatherMetric() => _wrapped.GatherMetric(); + public IDbMeta.DbMetric GatherMetric() => wrapped.GatherMetric(); public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - => _wrapped.Set(key, Compress(value), flags); + => wrapped.Set(key, Compress(value), flags); public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - => Decompress(_wrapped.Get(key, flags)); - + => Decompress(wrapped.Get(key, flags)); - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - _wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags); - } + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => + wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags); - public Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - { + public Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => // Can't properly implement span for reading. As the decompressed span is different from the span // from DB, it would crash on DangerouslyReleaseMemory. - return Decompress(Get(key, flags)); + Decompress(Get(key, flags)); + + public MemoryManager? GetOwnedMemory(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + { + byte[]? data = Decompress(Get(key, flags)); + return data is null or { Length: 0 } ? null : new ArrayMemoryManager(data); } - public bool PreferWriteByArray => _wrapped.PreferWriteByArray; + public bool PreferWriteByArray => wrapped.PreferWriteByArray; public void Tune(ITunableDb.TuneType type) { - if (_wrapped is ITunableDb tunable) + if (wrapped is ITunableDb tunable) tunable.Tune(type); } } diff --git a/src/Nethermind/Nethermind.Db/DbNames.cs b/src/Nethermind/Nethermind.Db/DbNames.cs index 2030be8e2bdb..9dd16b3ddc01 100644 --- a/src/Nethermind/Nethermind.Db/DbNames.cs +++ b/src/Nethermind/Nethermind.Db/DbNames.cs @@ -20,5 +20,6 @@ public static class DbNames public const string DiscoveryNodes = "discoveryNodes"; public const string DiscoveryV5Nodes = "discoveryV5Nodes"; public const string PeersDb = "peers"; + public const string LogIndex = "logIndex"; } } diff --git a/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs b/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs index 32979c963832..cac0113d670a 100755 --- a/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs +++ b/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.Threading; using Nethermind.Core; @@ -71,6 +72,17 @@ public Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadF return value; } + public MemoryManager? GetOwnedMemory(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + { + MemoryManager? memoryManager = _currentDb.GetOwnedMemory(key, flags); + if (memoryManager is not null && _pruningContext?.DuplicateReads == true && (flags & ReadFlags.SkipDuplicateRead) == 0) + { + Duplicate(_pruningContext.CloningDb, key, memoryManager.GetSpan(), WriteFlags.None); + } + + return memoryManager; + } + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { _currentDb.Set(key, value, flags); // we are writing to the main DB @@ -143,6 +155,8 @@ public void Remove(ReadOnlySpan key) public bool KeyExists(ReadOnlySpan key) => _currentDb.KeyExists(key); + public void DangerousReleaseMemory(in ReadOnlySpan span) => _currentDb.DangerousReleaseMemory(span); + // inner DB's can be deleted in the future and // we cannot expose a DB that will potentially be later deleted public IDb Innermost => this; diff --git a/src/Nethermind/Nethermind.Db/IColumnsDb.cs b/src/Nethermind/Nethermind.Db/IColumnsDb.cs index 5ffddced6ab9..e7ec61e8fe27 100644 --- a/src/Nethermind/Nethermind.Db/IColumnsDb.cs +++ b/src/Nethermind/Nethermind.Db/IColumnsDb.cs @@ -19,6 +19,7 @@ public interface IColumnsDb : IDbMeta, IDisposable public interface IColumnsWriteBatch : IDisposable { IWriteBatch GetColumnBatch(TKey key); + void Clear(); } diff --git a/src/Nethermind/Nethermind.Db/IMergeOperator.cs b/src/Nethermind/Nethermind.Db/IMergeOperator.cs index 7df2946bd71f..3a2367636fd9 100644 --- a/src/Nethermind/Nethermind.Db/IMergeOperator.cs +++ b/src/Nethermind/Nethermind.Db/IMergeOperator.cs @@ -3,12 +3,16 @@ using System; using Nethermind.Core.Collections; +using Nethermind.Db.LogIndex; namespace Nethermind.Db; public interface IMergeOperator { string Name { get; } + LogIndexUpdateStats Stats { get; } + LogIndexUpdateStats GetAndResetStats(); + ArrayPoolList? FullMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator); ArrayPoolList? PartialMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator); } diff --git a/src/Nethermind/Nethermind.Db/InMemoryColumnBatch.cs b/src/Nethermind/Nethermind.Db/InMemoryColumnBatch.cs index 9cff6e2fa957..45575158a8d0 100644 --- a/src/Nethermind/Nethermind.Db/InMemoryColumnBatch.cs +++ b/src/Nethermind/Nethermind.Db/InMemoryColumnBatch.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Nethermind.Core; +using Nethermind.Core.Collections; namespace Nethermind.Db { @@ -23,6 +24,14 @@ public IWriteBatch GetColumnBatch(TKey key) return writeBatch; } + public void Clear() + { + foreach (IWriteBatch batch in _underlyingBatch) + { + batch.Clear(); + } + } + public void Dispose() { foreach (IWriteBatch batch in _underlyingBatch) diff --git a/src/Nethermind/Nethermind.Db/LogIndex/AverageStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/AverageStats.cs new file mode 100644 index 000000000000..ba679987db6e --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/AverageStats.cs @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading; + +namespace Nethermind.Db.LogIndex; + +/// +/// Aggregates average of multiple incrementally-added values. +/// +public class AverageStats +{ + private long _total; + private int _count; + + public void Include(long value) + { + Interlocked.Add(ref _total, value); + Interlocked.Increment(ref _count); + } + + public double Average => _count == 0 ? 0 : (double)_total / _count; + + public override string ToString() => $"{Average:F2} ({_count:N0})"; + + public void Combine(AverageStats stats) + { + _total += stats._total; + _count += stats._count; + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/BlockReceipts.cs b/src/Nethermind/Nethermind.Db/LogIndex/BlockReceipts.cs new file mode 100644 index 000000000000..332c17e88ea2 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/BlockReceipts.cs @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Db.LogIndex; + +public readonly record struct BlockReceipts(int BlockNumber, TxReceipt[] Receipts); diff --git a/src/Nethermind/Nethermind.Db/LogIndex/CompactingStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/CompactingStats.cs new file mode 100644 index 000000000000..2a2b73718dcc --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/CompactingStats.cs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Db.LogIndex; + +public class CompactingStats +{ + public ExecTimeStats Total { get; set; } = new(); + + public void Combine(CompactingStats other) + { + Total.Combine(other.Total); + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/Compactor.cs b/src/Nethermind/Nethermind.Db/LogIndex/Compactor.cs new file mode 100644 index 000000000000..03561369a2dc --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/Compactor.cs @@ -0,0 +1,203 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using Nethermind.Logging; + +[assembly: InternalsVisibleTo("Nethermind.Db.Test")] +namespace Nethermind.Db.LogIndex; + +partial class LogIndexStorage +{ + /// + /// Periodically forces background log index compaction for every N added blocks. + /// + internal class Compactor : ICompactor + { + private int? _lastAtMin; + private int? _lastAtMax; + + private CompactingStats _stats = new(); + private readonly ILogIndexStorage _storage; + private readonly IDbMeta _rootDb; + private readonly ILogger _logger; + private readonly int _compactionDistance; + + private readonly CancellationTokenSource _cts = new(); + + /// + /// Bounded(1) compaction work queue consumed by .
+ /// null — fire-and-forget compaction enqueued by ;
+ /// not null — caller-awaitable compaction enqueued by . + ///
+ private readonly Channel _channel = Channel.CreateBounded(1); + + private volatile TaskCompletionSource? _pendingForcedCompaction; + private readonly Task _compactionTask; + + public Compactor(ILogIndexStorage storage, IDbMeta rootDb, ILogger logger, int compactionDistance) + { + _storage = storage; + _rootDb = rootDb; + _logger = logger; + + if (compactionDistance < 1) throw new ArgumentException("Compaction distance must be a positive value.", nameof(compactionDistance)); + _compactionDistance = compactionDistance; + + _lastAtMin = storage.MinBlockNumber; + _lastAtMax = storage.MaxBlockNumber; + + _compactionTask = DoCompactAsync(); + } + + public CompactingStats GetAndResetStats() => Interlocked.Exchange(ref _stats, new()); + + // Not thread-safe + public bool TryEnqueue() + { + if (_cts.IsCancellationRequested) + return false; + + _lastAtMin ??= _storage.MinBlockNumber; + _lastAtMax ??= _storage.MaxBlockNumber; + + var uncompacted = 0; + if (_storage.MinBlockNumber is { } storageMin && storageMin < _lastAtMin) + uncompacted += _lastAtMin.Value - storageMin; + if (_storage.MaxBlockNumber is { } storageMax && storageMax > _lastAtMax) + uncompacted += storageMax - _lastAtMax.Value; + + if (uncompacted < _compactionDistance) + return false; + + if (!_channel.Writer.TryWrite(null)) + return false; + + _lastAtMin = _storage.MinBlockNumber; + _lastAtMax = _storage.MaxBlockNumber; + return true; + } + + public async Task StopAsync() + { + await _cts.CancelAsync(); + _channel.Writer.TryComplete(); + await _compactionTask; + } + + public async Task ForceAsync() + { + // Coalesce concurrent calls — all callers share a single compaction + TaskCompletionSource tcs = new(TaskCreationOptions.RunContinuationsAsynchronously); + TaskCompletionSource? existing = Interlocked.CompareExchange(ref _pendingForcedCompaction, tcs, null); + + if (existing is not null) + { + await existing.Task; + return _stats; + } + + try + { + await _channel.Writer.WriteAsync(tcs, _cts.Token); + } + catch (Exception ex) + { + Interlocked.CompareExchange(ref _pendingForcedCompaction, null, tcs); + if (ex is OperationCanceledException) + tcs.TrySetCanceled(); + else + tcs.TrySetException(ex); + + throw; + } + + await tcs.Task; + return _stats; + } + + private async Task DoCompactAsync() + { + CancellationToken cancellation = _cts.Token; + try + { + await foreach (TaskCompletionSource? tcs in _channel.Reader.ReadAllAsync(cancellation)) + { + try + { + if (_logger.IsInfo) _logger.Info($"Log index: compaction started, DB size: {_storage.GetDbSize()}"); + + var timestamp = Stopwatch.GetTimestamp(); + _rootDb.Compact(); + + TimeSpan elapsed = Stopwatch.GetElapsedTime(timestamp); + _stats.Total.Include(elapsed); + + if (_logger.IsInfo) _logger.Info($"Log index: compaction ended in {elapsed}, DB size: {_storage.GetDbSize()}"); + + tcs?.TrySetResult(); + } + catch (OperationCanceledException) + { + tcs?.TrySetCanceled(); + } + catch (Exception ex) + { + tcs?.TrySetException(ex); + (_storage as LogIndexStorage)?.OnBackgroundError(ex); + + await _cts.CancelAsync(); + _channel.Writer.TryComplete(); + + break; + } + finally + { + if (tcs is not null) + Interlocked.CompareExchange(ref _pendingForcedCompaction, null, tcs); + } + } + } + catch (OperationCanceledException) + { + if (_logger.IsDebug) _logger.Debug("Log index: compaction loop canceled"); + } + finally + { + while (_channel.Reader.TryRead(out TaskCompletionSource? remaining) && remaining is not null) + { + remaining.TrySetCanceled(); + Interlocked.CompareExchange(ref _pendingForcedCompaction, null, remaining); + } + } + } + + public void Dispose() + { + _cts.Dispose(); + _channel.Writer.TryComplete(); + } + } + + private class NoOpCompactor : ICompactor + { + public CompactingStats GetAndResetStats() => new(); + public bool TryEnqueue() => false; + public Task StopAsync() => Task.CompletedTask; + public Task ForceAsync() => Task.FromResult(new CompactingStats()); + public void Dispose() { } + } + + internal interface ICompactor : IDisposable + { + CompactingStats GetAndResetStats(); + bool TryEnqueue(); + Task StopAsync(); + Task ForceAsync(); + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/CompressionAlgorithm.cs b/src/Nethermind/Nethermind.Db/LogIndex/CompressionAlgorithm.cs new file mode 100644 index 000000000000..3c0a3b83280b --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/CompressionAlgorithm.cs @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using static Nethermind.TurboPForBindings.TurboPFor; + +namespace Nethermind.Db.LogIndex; + +partial class LogIndexStorage +{ + /// + /// Represents compression algorithm to be used by log index. + /// + public class CompressionAlgorithm( + string name, + CompressionAlgorithm.CompressFunc compressionFunc, + CompressionAlgorithm.DecompressFunc decompressionFunc + ) + { + public delegate nuint CompressFunc(ReadOnlySpan @in, nuint n, Span @out); + public delegate nuint DecompressFunc(ReadOnlySpan @in, nuint n, Span @out); + + private static readonly Dictionary SupportedMap = new(); + + public static IReadOnlyDictionary Supported => SupportedMap; + + public static KeyValuePair Best => + SupportedMap.TryGetValue(nameof(p4nd1enc256v32), out CompressionAlgorithm p256) + ? KeyValuePair.Create(nameof(p4nd1enc256v32), p256) + : KeyValuePair.Create(nameof(p4nd1enc128v32), SupportedMap[nameof(p4nd1enc128v32)]); + + static CompressionAlgorithm() + { + SupportedMap.Add( + nameof(p4nd1enc128v32), + new(nameof(p4nd1enc128v32), p4nd1enc128v32, p4nd1dec128v32) + ); + + if (Supports256Blocks) + { + SupportedMap.Add( + nameof(p4nd1enc256v32), + new(nameof(p4nd1enc256v32), p4nd1enc256v32, p4nd1dec256v32) + ); + } + } + + public string Name => name; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public nuint Compress(ReadOnlySpan @in, nuint n, Span @out) => compressionFunc(@in, n, @out); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public nuint Decompress(ReadOnlySpan @in, nuint n, Span @out) => decompressionFunc(@in, n, @out); + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/Compressor.cs b/src/Nethermind/Nethermind.Db/LogIndex/Compressor.cs new file mode 100644 index 000000000000..3d6bf0ae947c --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/Compressor.cs @@ -0,0 +1,208 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; +using Nethermind.Core; +using Nethermind.Core.Extensions; + +namespace Nethermind.Db.LogIndex; + +partial class LogIndexStorage +{ + /// + /// Does background compression for keys with the number of blocks above the threshold. + /// + /// + /// Consumes "transient" keys with value being too big (see ) + /// from and performs compression in the background.
+ /// Can utilize multiple threads, as per . + /// + /// + /// For each "transient" key in the queue performs the following: + /// + /// reads the latest (uncompressed) value from the database; + /// truncates potentially reorgable blocks from the sequence (as compressed values become immutable); + /// reverts sequence if needed - so the new value is always in ascending order; + /// compresses the sequence using specified TurboPFor ; + /// stores the compressed value at a new pair using {address-or-topic} || ({first-block-number-in-the-sequence} + 1) as a key; + /// queues truncation for the "transient" key via - to remove finalized blocks from the old sequence. + /// + /// + /// Last 2 operations are done via a single to maintain data consistency. + ///
+ private class Compressor : ICompressor + { + private readonly int _minLengthToCompress; + + // Used instead of a channel to prevent duplicates + private readonly ConcurrentDictionary _compressQueue = new(Bytes.EqualityComparer); + private readonly ConcurrentDictionary.AlternateLookup> _compressQueueLookup; + private readonly LogIndexStorage _storage; + private readonly ActionBlock<(int?, byte[])> _processing; + private readonly ManualResetEventSlim _startEvent = new(false); + private readonly ManualResetEventSlim _queueEmptyEvent = new(true); + + private int _processingCount; + private PostMergeProcessingStats _stats = new(); + + public PostMergeProcessingStats GetAndResetStats() + { + _stats.QueueLength = _processing.InputCount; + return Interlocked.Exchange(ref _stats, new()); + } + + public Compressor(LogIndexStorage storage, int compressionDistance, int parallelism) + { + _compressQueueLookup = _compressQueue.GetAlternateLookup>(); + _storage = storage; + + _minLengthToCompress = compressionDistance * BlockNumberSize; + + if (parallelism < 1) throw new ArgumentException("Compression parallelism degree must be a positive value.", nameof(parallelism)); + _processing = new(x => CompressValue(x.Item1, x.Item2), new() { MaxDegreeOfParallelism = parallelism, BoundedCapacity = 10_000 }); + } + + public bool TryEnqueue(int? topicIndex, ReadOnlySpan dbKey, ReadOnlySpan dbValue) + { + if (dbValue.Length < _minLengthToCompress) + return false; + + if (_compressQueueLookup.TryGetValue(dbKey, out _)) + return false; + + byte[] dbKeyArr = dbKey.ToArray(); + if (!_compressQueue.TryAdd(dbKeyArr, true)) + return false; + + if (_processing.Post((topicIndex, dbKeyArr))) + return true; + + _compressQueue.TryRemove(dbKeyArr, out _); + return false; + } + + public async Task EnqueueAsync(int? topicIndex, byte[] dbKey) + { + await _processing.SendAsync((topicIndex, dbKey)); + _queueEmptyEvent.Reset(); + } + + public Task WaitUntilEmptyAsync(TimeSpan waitTime, CancellationToken cancellationToken) => + _queueEmptyEvent.WaitHandle.WaitOneAsync(waitTime, cancellationToken); + + private void CompressValue(int? topicIndex, byte[] dbKey) + { + if (_storage.HasBackgroundError) + return; + + Interlocked.Increment(ref _processingCount); + + try + { + _startEvent.Wait(); + + if (_storage.HasBackgroundError) + return; + + long execTimestamp = Stopwatch.GetTimestamp(); + IDb db = _storage.GetDb(topicIndex); + + long timestamp = Stopwatch.GetTimestamp(); + Span dbValue = db.Get(dbKey); + _stats.DBReading.Include(Stopwatch.GetElapsedTime(timestamp)); + + // Do not compress blocks that can be reorged, as compressed data is immutable + if (!UseBackwardSyncFor(dbKey)) + dbValue = _storage.RemoveReorgableBlocks(dbValue); + + if (dbValue.Length < _minLengthToCompress) + return; + + int truncateBlock = ReadLastBlockNumber(dbValue); + + ReverseBlocksIfNeeded(dbValue); + + int postfixBlock = ReadBlockNumber(dbValue); + + ReadOnlySpan key = ExtractKey(dbKey); + Span dbKeyComp = stackalloc byte[key.Length + BlockNumberSize]; + key.CopyTo(dbKeyComp); + WriteKeyBlockNumber(dbKeyComp[key.Length..], postfixBlock); + + timestamp = Stopwatch.GetTimestamp(); + dbValue = _storage.CompressDbValue(dbKey, dbValue); + _stats.CompressingValue.Include(Stopwatch.GetElapsedTime(timestamp)); + + // Put compressed value at a new key and clear the uncompressed one + timestamp = Stopwatch.GetTimestamp(); + using (IWriteBatch batch = db.StartWriteBatch()) + { + Span truncateOp = MergeOps.Create(MergeOp.Truncate, truncateBlock, stackalloc byte[MergeOps.Size]); + batch.PutSpan(dbKeyComp, dbValue); + batch.Merge(dbKey, truncateOp); + } + + _stats.DBSaving.Include(Stopwatch.GetElapsedTime(timestamp)); + + Interlocked.Increment(ref topicIndex is null ? ref _stats.CompressedAddressKeys : ref _stats.CompressedTopicKeys); + _stats.Total.Include(Stopwatch.GetElapsedTime(execTimestamp)); + } + catch (Exception ex) + { + _storage.OnBackgroundError(ex); + } + finally + { + _compressQueue.TryRemove(dbKey, out _); + + int processingCount = Interlocked.Decrement(ref _processingCount); + + if (_processing.InputCount == 0 && processingCount == 0) + _queueEmptyEvent.Set(); + } + } + + public void Start() => _startEvent.Set(); + + public Task StopAsync() + { + _processing.Complete(); + return _processing.Completion; // Wait for the compression queue to finish + } + + public void Dispose() + { + _startEvent.Dispose(); + _queueEmptyEvent.Dispose(); + } + } + + public sealed class NoOpCompressor : ICompressor + { + private PostMergeProcessingStats Stats { get; } = new(); + public PostMergeProcessingStats GetAndResetStats() => Stats; + public bool TryEnqueue(int? topicIndex, ReadOnlySpan dbKey, ReadOnlySpan dbValue) => false; + public Task EnqueueAsync(int? topicIndex, byte[] dbKey) => Task.CompletedTask; + public Task WaitUntilEmptyAsync(TimeSpan waitTime, CancellationToken cancellationToken) => Task.CompletedTask; + public void Start() { } + public Task StopAsync() => Task.CompletedTask; + public void Dispose() { } + } + + public interface ICompressor : IDisposable + { + PostMergeProcessingStats GetAndResetStats(); + + bool TryEnqueue(int? topicIndex, ReadOnlySpan dbKey, ReadOnlySpan dbValue); + Task EnqueueAsync(int? topicIndex, byte[] dbKey); + Task WaitUntilEmptyAsync(TimeSpan waitTime = default, CancellationToken cancellationToken = default); + + void Start(); + Task StopAsync(); + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/DisabledLogIndexStorage.cs b/src/Nethermind/Nethermind.Db/LogIndex/DisabledLogIndexStorage.cs new file mode 100644 index 000000000000..9c483a550533 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/DisabledLogIndexStorage.cs @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Db.LogIndex; + +public sealed class DisabledLogIndexStorage : ILogIndexStorage +{ + public bool Enabled => false; + + public string GetDbSize() => "0 B"; + + public int? MaxBlockNumber => null; + public int? MinBlockNumber => null; + + public IEnumerator GetEnumerator(Address address, int from, int to) => + throw new NotSupportedException(); + + public IEnumerator GetEnumerator(int topicIndex, Hash256 topic, int from, int to) => + throw new NotSupportedException(); + + public LogIndexAggregate Aggregate(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats = null) => + throw new NotSupportedException(); + + public Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null) => + throw new NotSupportedException(); + + public Task RemoveReorgedAsync(BlockReceipts block) => + throw new NotSupportedException(); + + public ValueTask DisposeAsync() => ValueTask.CompletedTask; + public Task StopAsync() => Task.CompletedTask; +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/ExecTimeStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/ExecTimeStats.cs new file mode 100644 index 000000000000..19c00334ee3e --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/ExecTimeStats.cs @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading; + +namespace Nethermind.Db.LogIndex; + +/// +/// Aggregates average and total execution time of multiple executions of the same operation. +/// +public class ExecTimeStats +{ + private long _totalTicks; + private int _count; + + public void Include(TimeSpan elapsed) + { + Interlocked.Add(ref _totalTicks, elapsed.Ticks); + Interlocked.Increment(ref _count); + } + + public TimeSpan Total => TimeSpan.FromTicks(_totalTicks); + public TimeSpan Average => _count == 0 ? TimeSpan.Zero : TimeSpan.FromTicks((long)((double)_totalTicks / _count)); + + private string Format(TimeSpan value) => value switch + { + { TotalDays: >= 1 } x => $"{x.TotalDays:F2}d", + { TotalHours: >= 1 } x => $"{x.TotalHours:F2}h", + { TotalMinutes: >= 1 } x => $"{x.TotalMinutes:F2}m", + { TotalSeconds: >= 1 } x => $"{x.TotalSeconds:F2}s", + { TotalMilliseconds: >= 1 } x => $"{x.TotalMilliseconds:F1}ms", + { TotalMicroseconds: >= 1 } x => $"{x.TotalMicroseconds:F1}μs", + var x => $"{x.TotalNanoseconds:F1}ns" + }; + + public override string ToString() => $"{Format(Average)} ({_count:N0}) [{Format(Total)}]"; + + public void Combine(ExecTimeStats stats) + { + _totalTicks += stats._totalTicks; + _count += stats._count; + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexConfig.cs b/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexConfig.cs new file mode 100644 index 000000000000..53cb3b45148c --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexConfig.cs @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Config; +using Nethermind.TurboPForBindings; + +namespace Nethermind.Db.LogIndex; + +public interface ILogIndexConfig : IConfig +{ + [ConfigItem( + Description = "Whether log index should be enabled.", + DefaultValue = "false" + )] + public bool Enabled { get; set; } + + [ConfigItem( + Description = "Log index is reset on startup if enabled.", + DefaultValue = "false" + )] + public bool Reset { get; set; } + + [ConfigItem( + Description = "Max allowed reorg depth for the index.", + DefaultValue = "64", + HiddenFromDocs = true + )] + public int? MaxReorgDepth { get; set; } + + [ConfigItem( + Description = "Maximum number of blocks with receipts to add to index per iteration.", + DefaultValue = "256", + HiddenFromDocs = true + )] + public int MaxBatchSize { get; set; } + + [ConfigItem( + Description = "Maximum number of batches to queue for aggregation.", + DefaultValue = "16", + HiddenFromDocs = true + )] + public int MaxAggregationQueueSize { get; set; } + + [ConfigItem( + Description = "Maximum number of aggregated batches to queue for inclusion to the index.", + DefaultValue = "16", + HiddenFromDocs = true + )] + public int MaxSavingQueueSize { get; set; } + + [ConfigItem( + Description = "Maximum degree of parallelism for fetching receipts.", + DefaultValue = "Max(ProcessorCount / 2, 1)", + HiddenFromDocs = true + )] + public int MaxReceiptsParallelism { get; set; } + + [ConfigItem( + Description = "Maximum degree of parallelism for aggregating batches.", + DefaultValue = "Max(ProcessorCount / 2, 1)", + HiddenFromDocs = true + )] + public int MaxAggregationParallelism { get; set; } + + [ConfigItem( + Description = "Maximum degree of parallelism for compressing overgrown key values.", + DefaultValue = "Max(ProcessorCount / 2, 1)", + HiddenFromDocs = true + )] + public int MaxCompressionParallelism { get; set; } + + [ConfigItem( + Description = "Minimum number of blocks under a single key to compress.", + DefaultValue = "128", + HiddenFromDocs = true + )] + public int CompressionDistance { get; set; } + + [ConfigItem( + Description = "Number of newly added blocks after which to run DB compaction.", + DefaultValue = "262,144", + HiddenFromDocs = true + )] + public int CompactionDistance { get; set; } + + [ConfigItem( + Description = "Compression algorithm to use for block numbers.", + DefaultValue = nameof(TurboPFor.p4nd1enc256v32) + " if supported, otherwise " + nameof(TurboPFor.p4nd1enc128v32), + HiddenFromDocs = true + )] + string? CompressionAlgorithm { get; set; } + + [ConfigItem( + Description = "Whether to show detailed stats in progress logs.", + DefaultValue = "false", + HiddenFromDocs = true + )] + bool DetailedLogs { get; set; } + + [ConfigItem( + Description = "Whether to verify that eth_getLogs response generated using index matches one generated without.", + DefaultValue = "false", + HiddenFromDocs = true + )] + bool VerifyRpcResponse { get; set; } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexStorage.cs b/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexStorage.cs new file mode 100644 index 000000000000..af084f85c310 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexStorage.cs @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.ServiceStopper; + +namespace Nethermind.Db.LogIndex; + +public interface ILogIndexStorage : IAsyncDisposable, IStoppableService +{ + bool Enabled { get; } + + /// + /// Max block number added to the index. + /// + int? MaxBlockNumber { get; } + + /// + /// Min block number added to the index. + /// + int? MinBlockNumber { get; } + + /// + /// Gets enumerator of block numbers between and + /// where given has occurred. + /// + IEnumerator GetEnumerator(Address address, int from, int to); + + /// + /// Gets enumerator of block numbers between and + /// where given has occurred at the given . + /// + IEnumerator GetEnumerator(int topicIndex, Hash256 topic, int from, int to); + + /// + /// Aggregates receipts from the into in-memory . + /// + LogIndexAggregate Aggregate(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats = null); + + /// + /// Adds receipts from the to the index. + /// + Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null); + + /// + /// Removes reorged from the index. + /// This must be called for each reorged block in a sequential ascending order. + /// + Task RemoveReorgedAsync(BlockReceipts block); + + string GetDbSize(); +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexAggregate.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexAggregate.cs new file mode 100644 index 000000000000..bd0f40280484 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexAggregate.cs @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Linq; +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Db.LogIndex; + +/// +/// Set of in-memory dictionaries mapping each address/topic to a sequence of block numbers +/// from the - range. +/// +public struct LogIndexAggregate(int firstBlockNum, int lastBlockNum) +{ + private Dictionary>? _address; + private Dictionary>[]? _topic; + + public int FirstBlockNum { get; } = firstBlockNum; + public int LastBlockNum { get; } = lastBlockNum; + + public Dictionary> Address => _address ??= new(); + + public Dictionary>[] Topic => _topic ??= Enumerable.Range(0, LogIndexStorage.MaxTopics) + .Select(static _ => new Dictionary>()) + .ToArray(); + + public bool IsEmpty => (_address is null || _address.Count == 0) && (_topic is null || _topic[0].Count == 0); + public int TopicCount => _topic is { Length: > 0 } ? _topic.Sum(static t => t.Count) : 0; + + public LogIndexAggregate(IReadOnlyList batch) : this(batch[0].BlockNumber, batch[^1].BlockNumber) { } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexColumns.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexColumns.cs new file mode 100644 index 000000000000..7492a1aca278 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexColumns.cs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Db.LogIndex; + +public enum LogIndexColumns +{ + Meta, + Addresses, + Topics0, + Topics1, + Topics2, + Topics3 +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexConfig.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexConfig.cs new file mode 100644 index 000000000000..5208f5ba2003 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexConfig.cs @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Config; + +namespace Nethermind.Db.LogIndex; + +[ConfigCategory(Description = "Configuration of the log index behaviour.")] +public class LogIndexConfig : ILogIndexConfig +{ + public bool Enabled { get; set; } = false; + public bool Reset { get; set; } = false; + + // set from PruningConfig via decorator + public int? MaxReorgDepth { get; set; } + + public int MaxBatchSize { get; set; } = 256; + public int MaxAggregationQueueSize { get; set; } = 16; + public int MaxSavingQueueSize { get; set; } = 16; + + public int MaxReceiptsParallelism { get; set; } = Math.Max(Environment.ProcessorCount / 2, 1); + public int MaxAggregationParallelism { get; set; } = Math.Max(Environment.ProcessorCount / 2, 1); + public int MaxCompressionParallelism { get; set; } = Math.Max(Environment.ProcessorCount / 2, 1); + + public int CompressionDistance { get; set; } = 128; + public int CompactionDistance { get; set; } = 262_144; + + public string? CompressionAlgorithm { get; set; } = LogIndexStorage.CompressionAlgorithm.Best.Key; + + public bool DetailedLogs { get; set; } = false; + public bool VerifyRpcResponse { get; set; } = false; +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexEnumerator.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexEnumerator.cs new file mode 100644 index 000000000000..1eb40242a6ca --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexEnumerator.cs @@ -0,0 +1,153 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections; +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.Core.Collections; + +namespace Nethermind.Db.LogIndex; + +public partial class LogIndexStorage +{ + // TODO: pre-fetch next value? + /// + /// Enumerates block numbers from for the given key, + /// within the specified from/to range. + /// + public sealed class LogIndexEnumerator : IEnumerator + { + private const int CompletedIndex = int.MinValue; + + private readonly CompressionAlgorithm _compressionAlgorithm; + private readonly byte[] _key; + private readonly int _from; + private readonly int _to; + private readonly ISortedView _view; + + private ArrayPoolList? _value; + private int _index; + + public LogIndexEnumerator(ISortedKeyValueStore db, CompressionAlgorithm compressionAlgorithm, byte[] key, int from, int to) + { + if (from < 0) from = 0; + if (to < from) throw new ArgumentException("To must be greater or equal to from.", nameof(to)); + + _key = key; + (_from, _to) = (from, to); + _compressionAlgorithm = compressionAlgorithm; + + ReadOnlySpan fromKey = CreateDbKey(_key, Postfix.BackwardMerge, stackalloc byte[MaxDbKeyLength]); + ReadOnlySpan toKey = CreateDbKey(_key, Postfix.UpperBound, stackalloc byte[MaxDbKeyLength]); + _view = db.GetViewBetween(fromKey, toKey); + } + + private bool IsWithinRange() + { + int current = Current; + return current >= _from && current <= _to; + } + + public bool MoveNext() => _index != CompletedIndex && (_value is null ? TryStart() : TryMove()); + + private bool TryStart() + { + if (TryStartView()) + { + SetValue(); + _index = FindFromIndex(); + } + else // End immediately + { + _index = CompletedIndex; + return false; + } + + // Shift the view until we can start at `from` + while (Current < _from && _view.MoveNext()) + { + SetValue(); + _index = FindFromIndex(); + } + + // Check if the end of the range is reached + if (!IsWithinRange()) + { + _index = CompletedIndex; + return false; + } + + return true; + } + + private bool TryMove() + { + _index++; + + // Shift the view until we can continue + while (_index >= _value!.Count && _view.MoveNext()) + { + SetValue(); + _index = 0; + } + + // Check if the end of the range is reached + if (!IsWithinRange()) + { + _index = CompletedIndex; + return false; + } + + return true; + } + + private bool TryStartView() + { + ReadOnlySpan startKey = CreateDbKey(_key, _from, stackalloc byte[MaxDbKeyLength]); + + // need to start either just before the startKey + // or at the beginning of a view otherwise + return _view.StartBefore(startKey) || _view.MoveNext(); + } + + private void SetValue() + { + _value?.Dispose(); + + ReadOnlySpan viewValue = _view.CurrentValue; + + if (IsCompressed(viewValue, out var length)) + { + // +1 fixes TurboPFor reading outside of array bounds + _value = new(capacity: length + 1, count: length); + DecompressDbValue(_compressionAlgorithm, viewValue, _value.AsSpan()); + } + else + { + length = viewValue.Length / BlockNumberSize; + _value = new(capacity: length, count: length); + ReadBlockNumbers(viewValue, _value.AsSpan()); + } + + ReverseBlocksIfNeeded(_value.AsSpan()); + } + + private int FindFromIndex() + { + int index = BinarySearch(_value!.AsSpan(), _from); + return index >= 0 ? index : ~index; + } + + public void Reset() => throw new NotSupportedException($"{nameof(LogIndexEnumerator)} can not be reset."); + + public int Current => _value is not null && _index >= 0 && _index < _value.Count ? _value[_index] : -1; + object? IEnumerator.Current => Current; + + public void Dispose() + { + _view.Dispose(); + _value?.Dispose(); + } + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStateException.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStateException.cs new file mode 100644 index 000000000000..113ebfb045b7 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStateException.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Db.LogIndex; + +public class LogIndexStateException(string message, ReadOnlySpan key = default) : Exception(message) +{ + public byte[]? Key { get; } = key.Length == 0 ? null : key.ToArray(); + + public override string Message => Key is null + ? base.Message + : $"{base.Message} (Key: {Convert.ToHexString(Key)})"; +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStorage.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStorage.cs new file mode 100644 index 000000000000..fc8e6a8f73ff --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStorage.cs @@ -0,0 +1,1006 @@ +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Logging; + +namespace Nethermind.Db.LogIndex +{ + // TODO: use uint instead of int for block number? + /// + /// Database for log index, mapping addresses/topics to a set of blocks they occur in. + /// + /// + /// + /// + /// Uses 6 column families (see ): + /// + /// 1 for metadata (for now stores only the earliest and the latest added block numbers); + /// 1 for address mappings; + /// 4 for topic mappings (separate 1 for each topic position). + /// + /// + /// + /// + /// Each unique filter (topic/address) has a set of DB mappings filter -> block numbers: + /// + /// + /// 1 for newly coming numbers from backward sync;
+ /// key here is formed as filter || ;
+ /// value is a sequence of concatenated block numbers in strictly descending order using little-endian encoding; + ///
+ /// + /// 1 for newly coming numbers from forward sync;
+ /// key is filter || ;
+ /// value is a sequence of concatenated block numbers in strictly ascending order using little-endian encoding; + ///
+ /// + /// any number of "finalized" mappings, storing block numbers in strictly ascending order, compressed via TurboPFor;
+ /// key is filter || ({first-block-number-in-the-sequence} + 1) with number being encoded in big-endian (for correct RocksDB sorting);
+ /// value is a sequence of block numbers compressed via TurboPFor (see ). + ///
+ ///
+ /// Keys (1) and (2) are called "transient", as their content can change frequently, + /// while keys from (3) - "finalized", as their data is immutable.
+ /// Block sequences are not-intersecting and following a strict order, such that:
+ /// + /// max({backward-sync-numbers}) < any({finalized-numbers}) < min({forward-sync-numbers}) + /// + ///
+ /// + /// + /// New blocks are added in batches in a strictly ascending or descending (backward sync) order without gaps.
+ /// Process is separated into 2 steps to improve parallelization: + /// + /// in-memory dictionary is aggregated, mapping filter to a sequence of block numbers (see ); + /// each dictionary pair is saved to DB via call (see ). + /// + /// Whole block batch is added in a single cross-family .
+ /// Merging always happens to a "transient" (backward- or forward-sync) key, depending on the direction (passed as a parameter during aggregation). + /// After that, is responsible for concatenating sequences into a single uncompressed DB value, + /// and is forming "finalized" compressed values when "transient" sequences grow too big. + ///
+ /// + /// + /// Fetching block numbers for the given filters (see ) + /// is done by iterating keys via (with the provided filter as a prefix), + /// decomposing DB values back into block number sequences, and returning obtained numbers in ascending order. + /// Check for details. + /// + ///
+ public partial class LogIndexStorage : ILogIndexStorage + { + private static class SpecialKey + { + public static readonly byte[] Version = "ver"u8.ToArray(); + public static readonly byte[] MinBlockNum = "min"u8.ToArray(); + public static readonly byte[] MaxBlockNum = "max"u8.ToArray(); + public static readonly byte[] CompressionAlgo = "alg"u8.ToArray(); + } + + public static class Postfix + { + // Any ordered prefix seeking will start on it + public static readonly byte[] BackwardMerge = Enumerable.Repeat((byte)0, BlockNumberSize).ToArray(); + + // Any ordered prefix seeking will end on it + public static readonly byte[] ForwardMerge = Enumerable.Repeat(byte.MaxValue, BlockNumberSize).ToArray(); + + // Exclusive upper bound for iterator seek, so that ForwardMerge will be the last key + public static readonly byte[] UpperBound = Enumerable.Repeat(byte.MaxValue, BlockNumberSize).Concat([byte.MinValue]).ToArray(); + } + + [InlineArray(MaxTopics)] + private struct TopicBatches + { + private IWriteBatch _element; + } + + [InlineArray(MaxTopics + 1)] + private struct AllMergeOperators + { + private IMergeOperator _element; + } + + private ref struct DbBatches : IDisposable + { + private bool _completed; + + private readonly IColumnsWriteBatch _batch; + public IWriteBatch Meta { get; } + public IWriteBatch Address { get; } + public readonly TopicBatches Topics; + + public DbBatches(IColumnsDb rootDb) + { + _batch = rootDb.StartWriteBatch(); + + Meta = _batch.GetColumnBatch(LogIndexColumns.Meta); + Address = _batch.GetColumnBatch(LogIndexColumns.Addresses); + for (var topicIndex = 0; topicIndex < MaxTopics; topicIndex++) + Topics[topicIndex] = _batch.GetColumnBatch(GetColumn(topicIndex)); + } + + // Require explicit Commit call instead of committing on Dispose + public void Commit() + { + if (_completed) return; + _completed = true; + + _batch.Dispose(); + } + + public void Dispose() + { + if (_completed) return; + _completed = true; + + _batch.Clear(); + _batch.Dispose(); + } + } + + private static readonly byte[] VersionBytes = [1]; + + public const int MaxTopics = 4; + + public bool Enabled { get; } + + public const int BlockNumberSize = sizeof(int); + private const int MaxKeyLength = Hash256.Size + 1; // Math.Max(Address.Size, Hash256.Size) + private const int MaxDbKeyLength = MaxKeyLength + BlockNumberSize; + + private static readonly ArrayPool Pool = ArrayPool.Shared; + + private readonly IColumnsDb _rootDb; + private readonly IDb _metaDb; + private readonly IDb _addressDb; + private readonly IDb[] _topicDbs; + + private IEnumerable DBColumns + { + get + { + yield return _metaDb; + yield return _addressDb; + + foreach (IDb topicDb in _topicDbs) + yield return topicDb; + } + } + + private readonly ILogger _logger; + + private readonly int _maxReorgDepth; + + private readonly AllMergeOperators _mergeOperators; + private readonly ICompressor _compressor; + private readonly ICompactor _compactor; + private readonly CompressionAlgorithm _compressionAlgorithm; + + private readonly Lock _rangeInitLock = new(); + + private int? _maxBlock; + private int? _minBlock; + + public int? MaxBlockNumber => _maxBlock; + public int? MinBlockNumber => _minBlock; + + private Exception? _lastBackgroundError; + public bool HasBackgroundError => _lastBackgroundError is not null; + + /// + /// Whether a first batch was already added. + /// + private bool FirstBlockAdded => _minBlock is not null || _maxBlock is not null; + + /// + /// Guarantees / initialization won't be run concurrently. + /// + private readonly SemaphoreSlim _initSemaphore = new(1, 1); + + /// + /// Used for blocking concurrent executions and + /// ensuring the current iteration is completed before stopping/disposing. + /// + private readonly SemaphoreSlim _forwardWriteSemaphore = new(1, 1); + private readonly SemaphoreSlim _backwardWriteSemaphore = new(1, 1); + + private bool _stopped; + private bool _disposed; + + public LogIndexStorage(IDbFactory dbFactory, ILogManager logManager, ILogIndexConfig config) + { + try + { + Enabled = config.Enabled; + + _maxReorgDepth = config.MaxReorgDepth!.Value; + + _logger = logManager.GetClassLogger(); + + _compressor = config.CompressionDistance > 0 + ? new Compressor(this, config.CompressionDistance, config.MaxCompressionParallelism) + : new NoOpCompressor(); + + for (int i = -1; i < MaxTopics; i++) + _mergeOperators[i + 1] = new MergeOperator(this, _compressor, topicIndex: i < 0 ? null : i); + + _rootDb = CreateRootDb(dbFactory, config.Reset); + _metaDb = GetMetaDb(_rootDb); + _addressDb = _rootDb.GetColumnDb(LogIndexColumns.Addresses); + _topicDbs = Enumerable.Range(0, MaxTopics).Select(topicIndex => _rootDb.GetColumnDb(GetColumn(topicIndex))).ToArray(); + + _compactor = config.CompactionDistance > 0 + ? new Compactor(this, _rootDb, _logger, config.CompactionDistance) + : new NoOpCompactor(); + + _compressionAlgorithm = SelectCompressionAlgorithm(config.CompressionAlgorithm); + + (_minBlock, _maxBlock) = (LoadRangeBound(SpecialKey.MinBlockNum), LoadRangeBound(SpecialKey.MaxBlockNum)); + + if (Enabled) + _compressor.Start(); + } + catch // TODO: do not throw errors from constructor? + { + DisposeCore(); + throw; + } + } + + private IColumnsDb CreateRootDb(IDbFactory dbFactory, bool reset) + { + (IColumnsDb root, IDb meta) = CreateDb(); + + if (reset) + return ResetAndCreateNew(root, "Log index: resetting data per configuration..."); + + Span versionBytes = meta.GetSpan(SpecialKey.Version); + try + { + if (versionBytes.IsEmpty) // DB is empty + { + meta.Set(SpecialKey.Version, VersionBytes); + return root; + } + + return versionBytes.SequenceEqual(VersionBytes) + ? root + : ResetAndCreateNew(root, $"Log index: version is incorrect: {versionBytes[0]} <> {VersionBytes[0]}, resetting data..."); + } + finally + { + meta.DangerousReleaseMemory(versionBytes); + } + + IColumnsDb ResetAndCreateNew(IColumnsDb db, string message) + { + if (_logger.IsWarn) + _logger.Warn(message); + + db.Clear(); + + // `Clear` removes the DB folder, need to create a new instance + db.Dispose(); + (db, meta) = CreateDb(); + + meta.Set(SpecialKey.Version, VersionBytes); + return db; + } + + (IColumnsDb root, IDb meta) CreateDb() + { + IColumnsDb db = dbFactory.CreateColumnsDb(new("logIndexStorage", DbNames.LogIndex) + { + ColumnsMergeOperators = Enumerable.Range(-1, MaxTopics + 1).ToDictionary( + topicIndex => $"{GetColumn(topicIndex < 0 ? null : topicIndex)}", + topicIndex => _mergeOperators[topicIndex + 1] + ) + }); + + return (db, GetMetaDb(db)); + } + } + + private CompressionAlgorithm SelectCompressionAlgorithm(string? configAlgoName) + { + CompressionAlgorithm? configAlgo = null; + if (configAlgoName is not null && !CompressionAlgorithm.Supported.TryGetValue(configAlgoName, out configAlgo)) + { + throw new NotSupportedException( + $"Configured compression algorithm ({configAlgoName}) is not supported on this platform." + ); + } + + Span algoBytes = _metaDb.GetSpan(SpecialKey.CompressionAlgo); + string usedAlgoName; + try + { + if (algoBytes.IsEmpty) // DB is empty + { + KeyValuePair selected = configAlgo is not null + ? KeyValuePair.Create(configAlgoName, configAlgo) + : CompressionAlgorithm.Best; + + _metaDb.Set(SpecialKey.CompressionAlgo, Encoding.ASCII.GetBytes(selected.Key)); + return selected.Value; + } + + usedAlgoName = Encoding.ASCII.GetString(algoBytes); + } + finally + { + _metaDb.DangerousReleaseMemory(algoBytes); + } + + if (!CompressionAlgorithm.Supported.TryGetValue(usedAlgoName, out CompressionAlgorithm usedAlgo)) + { + throw new NotSupportedException( + $"Used compression algorithm ({usedAlgoName}) is not supported on this platform. " + + "Log index must be reset to use a different compression algorithm." + ); + } + + configAlgoName ??= usedAlgoName; + if (usedAlgoName != configAlgoName) + { + throw new NotSupportedException( + $"Used compression algorithm ({usedAlgoName}) is different from the one configured ({configAlgoName}). " + + "Log index must be reset to use a different compression algorithm." + ); + } + + return usedAlgo; + } + + private static void ForceMerge(IDb db) + { + // Fetching RocksDB key values forces it to merge corresponding parts + db.GetAllValues().ForEach(static _ => { }); + } + + public Task StopAsync() => StopAsync(acquireLock: true); + + private async Task StopAsync(bool acquireLock) + { + if (Interlocked.Exchange(ref _stopped, true)) + { + return; + } + + if (acquireLock) + { + await _forwardWriteSemaphore.WaitAsync(); + await _backwardWriteSemaphore.WaitAsync(); + } + + try + { + // Disposing RocksDB during any write operation will cause 0xC0000005, so stop them all + await Task.WhenAll( + _compactor.StopAsync(), + _compressor.StopAsync() + ); + + if (_logger.IsInfo) _logger.Info("Log index storage stopped"); + } + finally + { + if (acquireLock) + { + _forwardWriteSemaphore.Release(); + _backwardWriteSemaphore.Release(); + } + } + } + + private void ThrowIfStopped() + { + if (_stopped) + throw new InvalidOperationException("Log index storage is stopped."); + } + + private void OnBackgroundError(Exception error) + { + _lastBackgroundError = error; + + if (_logger.IsError) + _logger.Error($"Error in {typeof(TCaller).Name}", error); + } + + private void ThrowIfHasError() + { + if (_lastBackgroundError is { } error) + ExceptionDispatchInfo.Throw(error); + } + + async ValueTask IAsyncDisposable.DisposeAsync() + { + if (Interlocked.Exchange(ref _disposed, true)) + { + return; + } + + await _forwardWriteSemaphore.WaitAsync(); + await _backwardWriteSemaphore.WaitAsync(); + + await StopAsync(acquireLock: false); + + // No need to free semaphores now + DisposeCore(); + } + + private void DisposeCore() + { + _forwardWriteSemaphore.Dispose(); + _backwardWriteSemaphore.Dispose(); + _compressor?.Dispose(); + _compactor?.Dispose(); + DBColumns?.DisposeItems(); + _rootDb?.Dispose(); + } + + private int? LoadRangeBound(ReadOnlySpan key) + { + Span value = _metaDb.GetSpan(key); + try + { + return !value.IsEmpty ? ReadBlockNumber(value) : null; + } + finally + { + _metaDb.DangerousReleaseMemory(value); + } + } + + private void UpdateRange(int minBlock, int maxBlock, bool isBackwardSync) + { + if (!FirstBlockAdded) + { + using Lock.Scope _ = _rangeInitLock.EnterScope(); // May not be needed, but added for safety + (_minBlock, _maxBlock) = (minBlock, maxBlock); + return; + } + + // Update fields separately for each direction + // so that concurrent different direction sync won't overwrite each other + if (isBackwardSync) _minBlock = minBlock; + else _maxBlock = maxBlock; + } + + private static int SaveRangeBound(IWriteOnlyKeyValueStore dbBatch, byte[] key, int value) + { + Span buffer = stackalloc byte[BlockNumberSize]; + WriteBlockNumber(buffer, value); + dbBatch.PutSpan(key, buffer); + return value; + } + + private (int min, int max) SaveRange(DbBatches batches, int firstBlock, int lastBlock, bool isBackwardSync, bool isReorg = false) + { + int batchMin = Math.Min(firstBlock, lastBlock); + int batchMax = Math.Max(firstBlock, lastBlock); + + int min = _minBlock ?? SaveRangeBound(batches.Meta, SpecialKey.MinBlockNum, batchMin); + int max = _maxBlock ?? SaveRangeBound(batches.Meta, SpecialKey.MaxBlockNum, batchMax); + + if (isBackwardSync) + { + if (isReorg) + throw new ArgumentException("Backwards sync does not support reorgs."); + if (batchMin < _minBlock) + min = SaveRangeBound(batches.Meta, SpecialKey.MinBlockNum, batchMin); + } + else + { + if ((isReorg && batchMax < _maxBlock) || (!isReorg && batchMax > _maxBlock)) + max = SaveRangeBound(batches.Meta, SpecialKey.MaxBlockNum, batchMax); + } + + return (min, max); + } + + private int? GetLastReorgableBlockNumber() => _maxBlock - _maxReorgDepth; + + private static bool IsBlockNewer(int next, int? lastMin, int? lastMax, bool isBackwardSync) => isBackwardSync + ? lastMin is null || next < lastMin + : lastMax is null || next > lastMax; + + private bool IsBlockNewer(int next, bool isBackwardSync) => + IsBlockNewer(next, _minBlock, _maxBlock, isBackwardSync); + + public string GetDbSize() => _rootDb.GatherMetric().Size.SizeToString(useSi: true, addSpace: true); + + public IEnumerator GetEnumerator(Address address, int from, int to) => + GetEnumerator(null, address.Bytes, from, to); + + public IEnumerator GetEnumerator(int topicIndex, Hash256 topic, int from, int to) => + GetEnumerator(topicIndex, topic.BytesToArray(), from, to); + + public IEnumerator GetEnumerator(int? topicIndex, byte[] key, int from, int to) + { + IDb db = GetDb(topicIndex); + ISortedKeyValueStore? sortedDb = db as ISortedKeyValueStore + ?? throw new NotSupportedException($"{db.GetType().Name} DB does not support sorted lookups."); + + return new LogIndexEnumerator(sortedDb, _compressionAlgorithm, key, from, to); + } + + // TODO: discuss potential optimizations + public LogIndexAggregate Aggregate(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats) + { + ThrowIfStopped(); + ThrowIfHasError(); + + if ((!isBackwardSync && !IsSeqAsc(batch)) || (isBackwardSync && !IsSeqDesc(batch))) + throw new ArgumentException($"Unexpected blocks batch order: ({batch[0]} to {batch[^1]})."); + + if (!IsBlockNewer(batch[^1].BlockNumber, isBackwardSync)) + return new(batch); + + long timestamp = Stopwatch.GetTimestamp(); + + LogIndexAggregate aggregate = new(batch); + foreach ((int blockNumber, TxReceipt[] receipts) in batch) + { + if (!IsBlockNewer(blockNumber, isBackwardSync)) + continue; + + stats?.IncrementBlocks(); + stats?.IncrementTx(receipts.Length); + + foreach (TxReceipt receipt in receipts) + { + if (receipt.Logs == null) + continue; + + foreach (LogEntry log in receipt.Logs) + { + stats?.IncrementLogs(); + + List addressBlocks = aggregate.Address.GetOrAdd(log.Address, static _ => new(1)); + + if (addressBlocks.Count == 0 || addressBlocks[^1] != blockNumber) + addressBlocks.Add(blockNumber); + + int topicsLength = Math.Min(log.Topics.Length, MaxTopics); + for (byte topicIndex = 0; topicIndex < topicsLength; topicIndex++) + { + stats?.IncrementTopics(); + + List topicBlocks = aggregate.Topic[topicIndex].GetOrAdd(log.Topics[topicIndex], static _ => new(1)); + + if (topicBlocks.Count == 0 || topicBlocks[^1] != blockNumber) + topicBlocks.Add(blockNumber); + } + } + } + } + + stats?.KeysCount.Include(aggregate.Address.Count + aggregate.TopicCount); + stats?.Aggregating.Include(Stopwatch.GetElapsedTime(timestamp)); + + return aggregate; + } + + private async ValueTask LockRunAsync(SemaphoreSlim semaphore) + { + if (!await semaphore.WaitAsync(TimeSpan.Zero, CancellationToken.None)) + { + ThrowIfStopped(); + throw new InvalidOperationException($"{nameof(LogIndexStorage)} does not support concurrent invocations in the same direction."); + } + } + + public async Task RemoveReorgedAsync(BlockReceipts block) + { + ThrowIfStopped(); + ThrowIfHasError(); + + if (!FirstBlockAdded) + return; + + await LockRunAsync(_forwardWriteSemaphore); + + try + { + RemoveReorgedCore(block); + } + finally + { + _forwardWriteSemaphore.Release(); + } + } + + private void RemoveReorgedCore(BlockReceipts block) + { + const bool isBackwardSync = false; + + using DbBatches batches = new(_rootDb); + + Span keyBuffer = stackalloc byte[MaxDbKeyLength]; + Span dbValue = MergeOps.Create(MergeOp.Reorg, block.BlockNumber, stackalloc byte[MergeOps.Size]); + + foreach (TxReceipt receipt in block.Receipts) + { + foreach (LogEntry log in receipt.Logs ?? []) + { + ReadOnlySpan addressKey = CreateMergeDbKey(log.Address.Bytes, keyBuffer, isBackwardSync: false); + batches.Address.Merge(addressKey, dbValue); + + var topicsLength = Math.Min(log.Topics.Length, MaxTopics); + for (var topicIndex = 0; topicIndex < topicsLength; topicIndex++) + { + Hash256 topic = log.Topics[topicIndex]; + ReadOnlySpan topicKey = CreateMergeDbKey(topic.Bytes, keyBuffer, isBackwardSync: false); + batches.Topics[topicIndex].Merge(topicKey, dbValue); + } + } + } + + // Need to update the last block number so that new-receipts comparison won't fail when rewriting it + int blockNum = block.BlockNumber - 1; + + (int minBlock, int maxBlock) = SaveRange(batches, blockNum, blockNum, isBackwardSync, isReorg: true); + + batches.Commit(); + + // Postpone in-memory values update until batch is committed + UpdateRange(minBlock, maxBlock, isBackwardSync); + } + + // TODO: refactor compaction to explicitly compress full range for each involved key + public async Task CompactAsync(bool flush = false, int mergeIterations = 0, LogIndexUpdateStats? stats = null) + { + ThrowIfStopped(); + ThrowIfHasError(); + + if (_logger.IsInfo) + _logger.Info($"Log index forced compaction started, DB size: {GetDbSize()}"); + + var timestamp = Stopwatch.GetTimestamp(); + + if (flush) + DBColumns.ForEach(static db => db.Flush()); + + for (var i = 0; i < mergeIterations; i++) + { + Task[] tasks = DBColumns + .Select(static db => Task.Run(() => ForceMerge(db))) + .ToArray(); + + await Task.WhenAll(tasks); + await _compressor.WaitUntilEmptyAsync(TimeSpan.FromSeconds(30)); + } + + CompactingStats compactStats = await _compactor.ForceAsync(); + stats?.Compacting.Combine(compactStats); + + foreach (IMergeOperator mergeOperator in _mergeOperators) + stats?.Combine(mergeOperator.Stats); + + if (_logger.IsInfo) + _logger.Info($"Log index forced compaction finished in {Stopwatch.GetElapsedTime(timestamp)}, DB size: {GetDbSize()} {stats:d}"); + } + + public async Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null) + { + ThrowIfStopped(); + ThrowIfHasError(); + + long totalTimestamp = Stopwatch.GetTimestamp(); + + bool isBackwardSync = aggregate.LastBlockNum < aggregate.FirstBlockNum; + SemaphoreSlim semaphore = isBackwardSync ? _backwardWriteSemaphore : _forwardWriteSemaphore; + await LockRunAsync(semaphore); + + bool wasInitialized = FirstBlockAdded; + if (!wasInitialized) + await _initSemaphore.WaitAsync(); + + try + { + using DbBatches batches = new(_rootDb); + + // Add values to batches + long timestamp; + if (!aggregate.IsEmpty) + { + timestamp = Stopwatch.GetTimestamp(); + + // Add addresses + foreach ((Address address, List blocks) in aggregate.Address) + { + MergeBlockNumbers(batches.Address, address.Bytes, blocks, isBackwardSync, stats); + } + + // Add topics + for (var topicIndex = 0; topicIndex < aggregate.Topic.Length; topicIndex++) + { + Dictionary> topics = aggregate.Topic[topicIndex]; + + foreach ((Hash256 topic, List blocks) in topics) + { + MergeBlockNumbers(batches.Topics[topicIndex], topic.Bytes, blocks, isBackwardSync, stats); + } + } + + stats?.Merging.Include(Stopwatch.GetElapsedTime(timestamp)); + } + + timestamp = Stopwatch.GetTimestamp(); + (int addressRange, int topicRanges) = SaveRange(batches, aggregate.FirstBlockNum, aggregate.LastBlockNum, isBackwardSync); + stats?.UpdatingMeta.Include(Stopwatch.GetElapsedTime(timestamp)); + + // Submit batches + timestamp = Stopwatch.GetTimestamp(); + batches.Commit(); + stats?.CommittingBatch.Include(Stopwatch.GetElapsedTime(timestamp)); + + UpdateRange(addressRange, topicRanges, isBackwardSync); + + // Enqueue compaction if needed + _compactor.TryEnqueue(); + } + finally + { + if (!wasInitialized) + _initSemaphore.Release(); + + semaphore.Release(); + } + + foreach (IMergeOperator mergeOperator in _mergeOperators) + stats?.Combine(mergeOperator.GetAndResetStats()); + stats?.Compressing.Combine(_compressor.GetAndResetStats()); + stats?.Compacting.Combine(_compactor.GetAndResetStats()); + stats?.Adding.Include(Stopwatch.GetElapsedTime(totalTimestamp)); + } + + protected virtual void MergeBlockNumbers( + IWriteBatch dbBatch, ReadOnlySpan key, List numbers, + bool isBackwardSync, LogIndexUpdateStats? stats + ) + { + Span dbKeyBuffer = stackalloc byte[MaxDbKeyLength]; + ReadOnlySpan dbKey = CreateMergeDbKey(key, dbKeyBuffer, isBackwardSync); + + byte[] newValue = CreateDbValue(numbers); + + long timestamp = Stopwatch.GetTimestamp(); + + if (newValue is null or []) + throw new LogIndexStateException("No block numbers to save.", key); + + // TODO: consider disabling WAL, but check: + // - FlushOnTooManyWrites + // - atomic flushing + dbBatch.Merge(dbKey, newValue); + stats?.DBMerging.Include(Stopwatch.GetElapsedTime(timestamp)); + } + + private static ReadOnlySpan WriteKey(ReadOnlySpan key, Span buffer) + { + key.CopyTo(buffer); + return buffer[..key.Length]; + } + + private static ReadOnlySpan ExtractKey(ReadOnlySpan dbKey) => dbKey[..^BlockNumberSize]; + + /// + /// Generates a key consisting of the key || block-number byte array. + /// / + private static ReadOnlySpan CreateDbKey(ReadOnlySpan key, int blockNumber, Span buffer) + { + key = WriteKey(key, buffer); + WriteKeyBlockNumber(buffer[key.Length..], blockNumber); + + int length = key.Length + BlockNumberSize; + return buffer[..length]; + } + + /// + /// Generates a key consisting of the key || block-number byte array. + /// / + private static ReadOnlySpan CreateDbKey(ReadOnlySpan key, ReadOnlySpan blockNumber, Span buffer) + { + key = WriteKey(key, buffer); + blockNumber.CopyTo(buffer[key.Length..]); + + int length = key.Length + blockNumber.Length; + return buffer[..length]; + } + + private static ReadOnlySpan CreateMergeDbKey(ReadOnlySpan key, Span buffer, bool isBackwardSync) => + CreateDbKey(key, isBackwardSync ? Postfix.BackwardMerge : Postfix.ForwardMerge, buffer); + + // RocksDB uses big-endian (lexicographic) ordering + // +1 is needed as 0 is used for the backward-merge key + private static void WriteKeyBlockNumber(Span dbKeyEnd, int number) => BinaryPrimitives.WriteInt32BigEndian(dbKeyEnd, number + 1); + + private static bool UseBackwardSyncFor(ReadOnlySpan dbKey) => dbKey.EndsWith(Postfix.BackwardMerge); + + private static int BinarySearch(ReadOnlySpan blocks, int from) + { + int index = blocks.BinarySearch(from); + return index < 0 ? ~index : index; + } + + private ReadOnlySpan Compress(Span data, Span buffer) + { + ReadOnlySpan blockNumbers = MemoryMarshal.Cast(data); + int length = (int)_compressionAlgorithm.Compress(blockNumbers, (nuint)blockNumbers.Length, buffer); + return buffer[..length]; + } + + private static int ReadCompressionMarker(ReadOnlySpan source) => -BinaryPrimitives.ReadInt32LittleEndian(source); + private static void WriteCompressionMarker(Span source, int len) => BinaryPrimitives.WriteInt32LittleEndian(source, -len); + + private static bool IsCompressed(ReadOnlySpan source, out int len) + { + if (source.Length == 0) + { + len = 0; + return false; + } + + len = ReadCompressionMarker(source); + return len > 0; + } + + private static void WriteBlockNumber(Span destination, int number) => BinaryPrimitives.WriteInt32LittleEndian(destination, number); + private static int ReadBlockNumber(ReadOnlySpan source) => BinaryPrimitives.ReadInt32LittleEndian(source); + private static int ReadLastBlockNumber(ReadOnlySpan source) => ReadBlockNumber(source[^BlockNumberSize..]); + + private static void ReadBlockNumbers(ReadOnlySpan source, Span buffer) + { + if (source.Length % BlockNumberSize != 0) + throw new LogIndexStateException("Invalid length for array of block numbers."); + + if (buffer.Length < source.Length / BlockNumberSize) + throw new ArgumentException($"Buffer is too small to hold {source.Length / BlockNumberSize} block numbers.", nameof(buffer)); + + if (BitConverter.IsLittleEndian) + { + ReadOnlySpan sourceInt = MemoryMarshal.Cast(source); + sourceInt.CopyTo(buffer); + } + else + { + for (var i = 0; i < source.Length; i += BlockNumberSize) + buffer[i / BlockNumberSize] = ReadBlockNumber(source[i..]); + } + } + + private static byte[] CreateDbValue(List numbers) + { + byte[] value = new byte[numbers.Count * BlockNumberSize]; + numbers.CopyTo(MemoryMarshal.Cast(value.AsSpan())); + return value; + } + + private static LogIndexColumns GetColumn(int? topicIndex) => topicIndex.HasValue + ? (LogIndexColumns)(topicIndex + LogIndexColumns.Topics0) + : LogIndexColumns.Addresses; + + private IDb GetDb(int? topicIndex) => topicIndex.HasValue ? _topicDbs[topicIndex.Value] : _addressDb; + + private static IDb GetMetaDb(IColumnsDb rootDb) => rootDb.GetColumnDb(LogIndexColumns.Meta); + + private byte[] CompressDbValue(ReadOnlySpan key, Span data) + { + if (IsCompressed(data, out _)) + throw new LogIndexStateException("Attempt to compress already compressed data.", key); + if (data.Length % BlockNumberSize != 0) + throw new LogIndexStateException($"Invalid length of data to compress: {data.Length}.", key); + + byte[] buffer = Pool.Rent(data.Length + BlockNumberSize); + + try + { + WriteCompressionMarker(buffer, data.Length / BlockNumberSize); + int compressedLen = Compress(data, buffer.AsSpan(BlockNumberSize..)).Length; + return buffer[..(BlockNumberSize + compressedLen)]; + } + finally + { + Pool.Return(buffer); + } + } + + private static void DecompressDbValue(CompressionAlgorithm algorithm, ReadOnlySpan data, Span buffer) + { + if (!IsCompressed(data, out int len)) + throw new ValidationException("Data is not compressed"); + + if (buffer.Length < len) + throw new ArgumentException($"Buffer is too small to decompress {len} block numbers.", nameof(buffer)); + + _ = algorithm.Decompress(data[BlockNumberSize..], (nuint)len, buffer); + } + + private void DecompressDbValue(ReadOnlySpan data, Span buffer) => DecompressDbValue(_compressionAlgorithm, data, buffer); + + private Span RemoveReorgableBlocks(Span data) + { + if (GetLastReorgableBlockNumber() is not { } lastCompressBlock) + return Span.Empty; + + int lastCompressIndex = LastBlockSearch(data, lastCompressBlock, false); + + if (lastCompressIndex < 0) lastCompressIndex = 0; + if (lastCompressIndex > data.Length) lastCompressIndex = data.Length; + + return data[..lastCompressIndex]; + } + + private static void ReverseBlocksIfNeeded(Span data) + { + if (data.Length != 0 && ReadBlockNumber(data) > ReadLastBlockNumber(data)) + MemoryMarshal.Cast(data).Reverse(); + } + + private static void ReverseBlocksIfNeeded(Span blocks) + { + if (blocks.Length != 0 && blocks[0] > blocks[^1]) + blocks.Reverse(); + } + + private static int LastBlockSearch(ReadOnlySpan operand, int block, bool isBackward) + { + if (operand.IsEmpty) + return 0; + + int i = operand.Length - BlockNumberSize; + for (; i >= 0; i -= BlockNumberSize) + { + int currentBlock = ReadBlockNumber(operand[i..]); + if (currentBlock == block) + return i; + + if (isBackward) + { + if (currentBlock > block) + return i + BlockNumberSize; + } + else + { + if (currentBlock < block) + return i + BlockNumberSize; + } + } + + return i; + } + + private static bool IsSeqAsc(IReadOnlyList blocks) + { + int j = blocks.Count - 1; + int i = 1, d = blocks[0].BlockNumber; + while (i <= j && blocks[i].BlockNumber - i == d) i++; + return i > j; + } + + private static bool IsSeqDesc(IReadOnlyList blocks) + { + int j = blocks.Count - 1; + int i = 1, d = blocks[0].BlockNumber; + while (i <= j && blocks[i].BlockNumber + i == d) i++; + return i > j; + } + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexUpdateStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexUpdateStats.cs new file mode 100644 index 000000000000..22651564ed8b --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexUpdateStats.cs @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading; + +namespace Nethermind.Db.LogIndex; + +/// +/// Log index building statistics across some time range. +/// +public class LogIndexUpdateStats(ILogIndexStorage storage) : IFormattable +{ + private long _blocksAdded; + private long _txAdded; + private long _logsAdded; + private long _topicsAdded; + + public long BlocksAdded => _blocksAdded; + public long TxAdded => _txAdded; + public long LogsAdded => _logsAdded; + public long TopicsAdded => _topicsAdded; + + public long? MaxBlockNumber => storage.MaxBlockNumber; + public long? MinBlockNumber => storage.MinBlockNumber; + + public ExecTimeStats Adding { get; } = new(); + public ExecTimeStats Aggregating { get; } = new(); + public ExecTimeStats Merging { get; } = new(); + + public ExecTimeStats DBMerging { get; } = new(); + public ExecTimeStats UpdatingMeta { get; } = new(); + public ExecTimeStats CommittingBatch { get; } = new(); + public ExecTimeStats BackgroundMerging { get; } = new(); + + public AverageStats KeysCount { get; } = new(); + + public ExecTimeStats QueueingAddressCompression { get; } = new(); + public ExecTimeStats QueueingTopicCompression { get; } = new(); + + public PostMergeProcessingStats Compressing { get; } = new(); + public CompactingStats Compacting { get; } = new(); + + public ExecTimeStats LoadingReceipts { get; } = new(); + + public void Combine(LogIndexUpdateStats other) + { + _blocksAdded += other._blocksAdded; + _txAdded += other._txAdded; + _logsAdded += other._logsAdded; + _topicsAdded += other._topicsAdded; + + Adding.Combine(other.Adding); + Aggregating.Combine(other.Aggregating); + Merging.Combine(other.Merging); + UpdatingMeta.Combine(other.UpdatingMeta); + DBMerging.Combine(other.DBMerging); + CommittingBatch.Combine(other.CommittingBatch); + BackgroundMerging.Combine(other.BackgroundMerging); + KeysCount.Combine(other.KeysCount); + + QueueingAddressCompression.Combine(other.QueueingAddressCompression); + QueueingTopicCompression.Combine(other.QueueingTopicCompression); + + Compressing.Combine(other.Compressing); + Compacting.Combine(other.Compacting); + + LoadingReceipts.Combine(other.LoadingReceipts); + } + + public void IncrementBlocks() => Interlocked.Increment(ref _blocksAdded); + public void IncrementTx(int count = 1) => Interlocked.Add(ref _txAdded, count); + public void IncrementLogs() => Interlocked.Increment(ref _logsAdded); + public void IncrementTopics() => Interlocked.Increment(ref _topicsAdded); + + public string ToString(string? format, IFormatProvider? formatProvider) + { + const string tab = "\t"; + + return !string.Equals(format, "D", StringComparison.OrdinalIgnoreCase) + ? $"{MinBlockNumber:N0} - {MaxBlockNumber:N0} (blocks: +{BlocksAdded:N0} | txs: +{TxAdded:N0} | logs: +{LogsAdded:N0} | topics: +{TopicsAdded:N0})" + : $""" + + {tab}Blocks: {MinBlockNumber:N0} - {MaxBlockNumber:N0} (+{_blocksAdded:N0}) + + {tab}Txs: +{TxAdded:N0} + {tab}Logs: +{LogsAdded:N0} + {tab}Topics: +{TopicsAdded:N0} + + {tab}Keys per batch: {KeysCount:N0} + + {tab}Loading receipts: {LoadingReceipts} + {tab}Aggregating: {Aggregating} + + {tab}Adding receipts: {Adding} + {tab}{tab}Merging: {Merging} (DB: {DBMerging}) + {tab}{tab}Updating metadata: {UpdatingMeta} + {tab}{tab}Committing batch: {CommittingBatch} + + {tab}Background merging: {BackgroundMerging} + + {tab}Post-merge compression: {Compressing.Total} + {tab}{tab}DB reading: {Compressing.DBReading} + {tab}{tab}Compressing: {Compressing.CompressingValue} + {tab}{tab}DB saving: {Compressing.DBSaving} + {tab}{tab}Keys compressed: {Compressing.CompressedAddressKeys:N0} address, {Compressing.CompressedTopicKeys:N0} topic + {tab}{tab}Keys in queue: {Compressing.QueueLength:N0} + + {tab}Compacting: {Compacting.Total} + """; + } + + public override string ToString() => ToString(null, null); +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/MergeOperator.cs b/src/Nethermind/Nethermind.Db/LogIndex/MergeOperator.cs new file mode 100644 index 000000000000..a9f30b6fe4e7 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/MergeOperator.cs @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Diagnostics; +using System.Threading; +using Nethermind.Core; +using Nethermind.Core.Collections; + +namespace Nethermind.Db.LogIndex; + +partial class LogIndexStorage +{ + // TODO: check if success=false + paranoid_checks=true is better than throwing an exception + /// + /// Merges log index incoming block number sequences into a single DB value. + /// + /// + /// Handles calls from . + /// Operator works only with uncompressed data under "transient" keys.
+ /// Each log index column family has its own merge operator instance, + /// parameter is used to find corresponding DB.
+ /// + /// + /// Supports 2 different use cases depending on the incoming operand: + /// + /// + /// In case if operand is a sequence of block numbers - + /// directly concatenates with the existing DB value, validating order remains correct (see ).
+ /// If value size grows to or over block numbers - + /// queues it for compression (via ). + ///
+ /// + /// If operand from - performs specified operation on the previously obtained sequence. + /// + ///
+ /// Check MergeOperator tests for the expected behavior. + ///
+ ///
+ public class MergeOperator(ILogIndexStorage storage, ICompressor compressor, int? topicIndex) : IMergeOperator + { + private LogIndexUpdateStats _stats = new(storage); + public LogIndexUpdateStats Stats => _stats; + public LogIndexUpdateStats GetAndResetStats() => Interlocked.Exchange(ref _stats, new(storage)); + + public string Name => $"{nameof(LogIndexStorage)}.{nameof(MergeOperator)}"; + + public ArrayPoolList? FullMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator) => + Merge(key, enumerator, isPartial: false); + + public ArrayPoolList? PartialMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator) => + Merge(key, enumerator, isPartial: true); + + private static bool IsBlockNewer(int next, int? last, bool isBackwardSync) => + LogIndexStorage.IsBlockNewer(next, last, last, isBackwardSync); + + // Validate we are merging non-intersecting segments - to prevent data corruption + private static void AddEnsureSorted(ReadOnlySpan key, ArrayPoolList result, ReadOnlySpan value, bool isBackward) + { + if (value.Length == 0) + return; + + int nextBlock = ReadBlockNumber(value); + int? lastBlock = result.Count > 0 ? ReadLastBlockNumber(result.AsSpan()) : (int?)null; + + if (!IsBlockNewer(next: nextBlock, last: lastBlock, isBackward)) + throw new LogIndexStateException($"Invalid order during merge: {lastBlock} -> {nextBlock} (backward: {isBackward}).", key); + + result.AddRange(value); + } + + // TODO: avoid array copying in case of a single value? + private ArrayPoolList? Merge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator, bool isPartial) + { + var success = false; + ArrayPoolList? result = null; + var timestamp = Stopwatch.GetTimestamp(); + + try + { + // Fast return in case of a single operand + if (!enumerator.HasExistingValue && enumerator.OperandsCount == 1 && !MergeOps.IsAny(enumerator.GetOperand(0))) + return new(enumerator.GetOperand(0)); + + bool isBackwards = UseBackwardSyncFor(key); + + // Calculate total length + var resultLength = enumerator.GetExistingValue().Length; + for (var i = 0; i < enumerator.OperandsCount; i++) + { + ReadOnlySpan operand = enumerator.GetOperand(i); + + if (MergeOps.IsAny(operand)) + { + if (isPartial) + return null; // Notify RocksDB that we can't partially merge custom ops + + continue; + } + + resultLength += operand.Length; + } + + result = new(resultLength); + + // For truncate - just use max/min for all operands + int? truncateAggregate = Aggregate(MergeOp.Truncate, enumerator, isBackwards); + + int iReorg = 0; + for (int i = 0; i < enumerator.TotalCount; i++) + { + Span operand = enumerator.Get(i); + + if (MergeOps.IsAny(operand)) + continue; + + // For reorg - order matters, so we need to always traverse from the current position + iReorg = Math.Max(iReorg, i + 1); + if (FindNext(MergeOp.Reorg, enumerator, ref iReorg) is { } reorgBlock) + operand = MergeOps.ApplyTo(operand, MergeOp.Reorg, reorgBlock, isBackwards); + + if (truncateAggregate is { } truncateBlock) + operand = MergeOps.ApplyTo(operand, MergeOp.Truncate, truncateBlock, isBackwards); + + AddEnsureSorted(key, result, operand, isBackwards); + } + + if (result.Count % BlockNumberSize != 0) + throw new LogIndexStateException($"Invalid data length post-merge: {result.Count}.", key); + + compressor.TryEnqueue(topicIndex, key, result.AsSpan()); + + success = true; + return result; + } + catch (Exception exception) + { + (storage as LogIndexStorage)?.OnBackgroundError(exception); + return null; + } + finally + { + if (!success) result?.Dispose(); + + _stats.BackgroundMerging.Include(Stopwatch.GetElapsedTime(timestamp)); + } + } + + private static int? FindNext(MergeOp op, RocksDbMergeEnumerator enumerator, ref int i) + { + while (i < enumerator.TotalCount && !MergeOps.Is(op, enumerator.Get(i))) + i++; + + return i < enumerator.TotalCount && MergeOps.Is(op, enumerator.Get(i), out int block) + ? block + : null; + } + + private static int? Aggregate(MergeOp op, RocksDbMergeEnumerator enumerator, bool isBackwardSync) + { + int? result = null; + for (var i = 0; i < enumerator.OperandsCount; i++) + { + if (!MergeOps.Is(op, enumerator.GetOperand(i), out var next)) + continue; + + if (result is null || (isBackwardSync && next < result) || (!isBackwardSync && next > result)) + result = next; + } + + return result; + } + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/MergeOps.cs b/src/Nethermind/Nethermind.Db/LogIndex/MergeOps.cs new file mode 100644 index 000000000000..74d494cd5d0f --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/MergeOps.cs @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Db.LogIndex; + +partial class LogIndexStorage +{ + /// + /// Custom operations for . + /// + public enum MergeOp : byte + { + /// + /// Reorgs from the provided block number, + /// removing any numbers starting from it. + /// + /// + /// Added to support "fast" reorgs - without explicitly fetching value from the database. + /// + Reorg = 1, + + /// + /// Truncates data up to the provided block number, + /// removing it and anything coming before. + /// + /// + /// Added to remove numbers already saved in "finalized" keys (via ) + /// from "transient" keys, without the need for a lock (in case of concurrent merges). + /// + Truncate = 2 + } + + /// + /// Helper class to create and parse operations. + /// + public static class MergeOps + { + public const int Size = BlockNumberSize + 1; + + public static bool Is(MergeOp op, ReadOnlySpan operand) => + operand.Length == Size && operand[0] == (byte)op; + + public static bool Is(MergeOp op, ReadOnlySpan operand, out int fromBlock) + { + if (operand.Length == Size && operand[0] == (byte)op) + { + fromBlock = ReadLastBlockNumber(operand); + return true; + } + + fromBlock = 0; + return false; + } + + public static bool IsAny(ReadOnlySpan operand) => + Is(MergeOp.Reorg, operand, out _) || + Is(MergeOp.Truncate, operand, out _); + + public static Span Create(MergeOp op, int fromBlock, Span buffer) + { + Span dbValue = buffer[..Size]; + dbValue[0] = (byte)op; + WriteBlockNumber(dbValue[1..], fromBlock); + return dbValue; + } + + public static Span ApplyTo(Span operand, MergeOp op, int block, bool isBackward) + { + // In most cases the searched block will be near or at the end of the operand, if present there + int i = LastBlockSearch(operand, block, isBackward); + + return op switch + { + MergeOp.Reorg => i switch + { + < 0 => Span.Empty, + _ when i >= operand.Length => operand, + _ => operand[..i] + }, + + MergeOp.Truncate => i switch + { + < 0 => operand, + _ when i >= operand.Length => Span.Empty, + _ => operand[(i + BlockNumberSize)..] + }, + + _ => throw new ArgumentOutOfRangeException(nameof(op)) + }; + } + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/PostMergeProcessingStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/PostMergeProcessingStats.cs new file mode 100644 index 000000000000..312056e88054 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/PostMergeProcessingStats.cs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Db.LogIndex; + +public class PostMergeProcessingStats +{ + public ExecTimeStats DBReading { get; set; } = new(); + public ExecTimeStats CompressingValue { get; set; } = new(); + public ExecTimeStats DBSaving { get; set; } = new(); + public int QueueLength { get; set; } + + public long CompressedAddressKeys; + public long CompressedTopicKeys; + + public ExecTimeStats Total { get; } = new(); + + public void Combine(PostMergeProcessingStats other) + { + DBReading.Combine(other.DBReading); + CompressingValue.Combine(other.CompressingValue); + DBSaving.Combine(other.DBSaving); + + CompressedAddressKeys += other.CompressedAddressKeys; + CompressedTopicKeys += other.CompressedTopicKeys; + + Total.Combine(other.Total); + } +} diff --git a/src/Nethermind/Nethermind.Db/MemDb.cs b/src/Nethermind/Nethermind.Db/MemDb.cs index 39490693200f..e68055560f3c 100644 --- a/src/Nethermind/Nethermind.Db/MemDb.cs +++ b/src/Nethermind/Nethermind.Db/MemDb.cs @@ -5,6 +5,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using Nethermind.Core; using Nethermind.Core.Extensions; @@ -19,14 +21,13 @@ public class MemDb : IFullDb public long WritesCount { get; private set; } #if ZK - private readonly Dictionary _db; + private readonly Dictionary _db = new(Bytes.EqualityComparer); private readonly Dictionary.AlternateLookup> _spanDb; #else - private readonly ConcurrentDictionary _db; + private readonly ConcurrentDictionary _db = new(Bytes.EqualityComparer); private readonly ConcurrentDictionary.AlternateLookup> _spanDb; #endif - public MemDb(string name) : this(0, 0) { @@ -35,7 +36,7 @@ public MemDb(string name) public static MemDb CopyFrom(IDb anotherDb) { - MemDb newDb = new MemDb(); + MemDb newDb = new(); foreach (KeyValuePair kv in anotherDb.GetAll()) { newDb[kv.Key] = kv.Value; @@ -52,11 +53,6 @@ public MemDb(int writeDelay, int readDelay) { _writeDelay = writeDelay; _readDelay = readDelay; -#if ZK - _db = new Dictionary(Bytes.EqualityComparer); -#else - _db = new ConcurrentDictionary(Bytes.EqualityComparer); -#endif _spanDb = _db.GetAlternateLookup>(); } @@ -64,14 +60,8 @@ public MemDb(int writeDelay, int readDelay) public virtual byte[]? this[ReadOnlySpan key] { - get - { - return Get(key); - } - set - { - Set(key, value); - } + get => Get(key); + set => Set(key, value); } public KeyValuePair[] this[byte[][] keys] @@ -84,18 +74,11 @@ public virtual byte[]? this[ReadOnlySpan key] } ReadsCount += keys.Length; - return keys.Select(k => new KeyValuePair(k, _db.TryGetValue(k, out var value) ? value : null)).ToArray(); + return keys.Select(k => new KeyValuePair(k, _db.GetValueOrDefault(k))).ToArray(); } } - public virtual void Remove(ReadOnlySpan key) - { -#if ZK - _spanDb.Remove(key); -#else - _spanDb.TryRemove(key, out _); -#endif - } + public virtual void Remove(ReadOnlySpan key) => _spanDb.TryRemove(key, out _); public bool KeyExists(ReadOnlySpan key) => _spanDb.ContainsKey(key); @@ -120,9 +103,7 @@ public void Dispose() { } public bool PreferWriteByArray => true; - public virtual Span GetSpan(ReadOnlySpan key) => Get(key).AsSpan(); - - public void DangerousReleaseMemory(in ReadOnlySpan span) { } + public unsafe void DangerousReleaseMemory(in ReadOnlySpan span) { } public virtual byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) { @@ -135,6 +116,9 @@ public void DangerousReleaseMemory(in ReadOnlySpan span) { } return _spanDb.TryGetValue(key, out byte[] value) ? value : null; } + public unsafe Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + => Get(key).AsSpan(); + public virtual void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { if (_writeDelay > 0) diff --git a/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj b/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj index 04ad5b8b916b..f8a963c6bf31 100644 --- a/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj +++ b/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs b/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs index bc734119beb3..98a7d045220f 100644 --- a/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs +++ b/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using Nethermind.Core; +using Nethermind.Core.Buffers; namespace Nethermind.Db { @@ -12,17 +14,12 @@ public class ReadOnlyDb(IDb wrappedDb, bool createInMemWriteStore) : IReadOnlyDb { private readonly MemDb _memDb = new(); - public void Dispose() - { - _memDb.Dispose(); - } + public void Dispose() => _memDb.Dispose(); public string Name { get => wrappedDb.Name; } - public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - { - return _memDb.Get(key, flags) ?? wrappedDb.Get(key, flags); - } + public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => + _memDb.Get(key, flags) ?? wrappedDb.Get(key, flags); public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { @@ -38,11 +35,11 @@ public KeyValuePair[] this[byte[][] keys] { get { - var result = wrappedDb[keys]; - var memResult = _memDb[keys]; + KeyValuePair[]? result = wrappedDb[keys]; + KeyValuePair[]? memResult = _memDb[keys]; for (int i = 0; i < memResult.Length; i++) { - var memValue = memResult[i]; + KeyValuePair memValue = memResult[i]; if (memValue.Value is not null) { result[i] = memValue; @@ -73,7 +70,6 @@ public void Flush(bool onlyWal) { } public virtual void ClearTempChanges() => _memDb.Clear(); - public Span GetSpan(ReadOnlySpan key) => Get(key).AsSpan(); public void PutSpan(ReadOnlySpan keyBytes, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) { if (!createInMemWriteStore) diff --git a/src/Nethermind/Nethermind.Db/RocksDbSettings.cs b/src/Nethermind/Nethermind.Db/RocksDbSettings.cs index b22c1962b5be..2c7feceed718 100644 --- a/src/Nethermind/Nethermind.Db/RocksDbSettings.cs +++ b/src/Nethermind/Nethermind.Db/RocksDbSettings.cs @@ -5,16 +5,10 @@ namespace Nethermind.Db { - public class DbSettings + public class DbSettings(string name, string path) { - public DbSettings(string name, string path) - { - DbName = name; - DbPath = path; - } - - public string DbName { get; private set; } - public string DbPath { get; private set; } + public string DbName { get; private set; } = name; + public string DbPath { get; private set; } = path; public bool DeleteOnStart { get; set; } public bool CanDeleteFolder { get; set; } = true; diff --git a/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs b/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs index d69a532c8735..7f49f1a1367a 100644 --- a/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs +++ b/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -23,12 +24,11 @@ public class SimpleFilePublicKeyDb : IFullDb private readonly ILogger _logger; private bool _hasPendingChanges; - private ConcurrentDictionary _cache; - private ConcurrentDictionary.AlternateLookup> _cacheSpan; + private readonly ConcurrentDictionary _cache = new(Bytes.EqualityComparer); + private readonly ConcurrentDictionary.AlternateLookup> _cacheSpan; - public string DbPath { get; } + private string DbPath { get; } public string Name { get; } - public string Description { get; } public ICollection Keys => _cache.Keys.ToArray(); public ICollection Values => _cache.Values; @@ -40,26 +40,27 @@ public SimpleFilePublicKeyDb(string name, string dbDirectoryPath, ILogManager lo ArgumentNullException.ThrowIfNull(dbDirectoryPath); Name = name ?? throw new ArgumentNullException(nameof(name)); DbPath = Path.Combine(dbDirectoryPath, DbFileName); - Description = $"{Name}|{DbPath}"; if (!Directory.Exists(dbDirectoryPath)) { Directory.CreateDirectory(dbDirectoryPath); } - LoadData(); + _cacheSpan = _cache.GetAlternateLookup>(); + + if (File.Exists(DbPath)) + { + LoadData(); + } } public byte[]? this[ReadOnlySpan key] { - get => Get(key, ReadFlags.None); - set => Set(key, value, WriteFlags.None); + get => Get(key); + set => Set(key, value); } - public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - { - return _cacheSpan[key]; - } + public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => _cacheSpan[key]; public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { @@ -98,10 +99,7 @@ public void Remove(ReadOnlySpan key) } } - public bool KeyExists(ReadOnlySpan key) - { - return _cacheSpan.ContainsKey(key); - } + public bool KeyExists(ReadOnlySpan key) => _cacheSpan.ContainsKey(key); public void Flush(bool onlyWal = false) { } @@ -117,10 +115,7 @@ public void Clear() public IEnumerable GetAllValues(bool ordered = false) => _cache.Values; - public IWriteBatch StartWriteBatch() - { - return this.LikeABatch(CommitBatch); - } + public IWriteBatch StartWriteBatch() => this.LikeABatch(CommitBatch); private void CommitBatch() { @@ -219,14 +214,6 @@ private void LoadData() { const int maxLineLength = 2048; - _cache = new ConcurrentDictionary(Bytes.EqualityComparer); - _cacheSpan = _cache.GetAlternateLookup>(); - - if (!File.Exists(DbPath)) - { - return; - } - using SafeFileHandle fileHandle = File.OpenHandle(DbPath, FileMode.OpenOrCreate); using var handle = ArrayPoolDisposableReturn.Rent(maxLineLength, out byte[] rentedBuffer); @@ -306,24 +293,6 @@ void RecordError(Span data) } } - private byte[] Update(byte[] oldValue, byte[] newValue) - { - if (!Bytes.AreEqual(oldValue, newValue)) - { - _hasPendingChanges = true; - } - - return newValue; - } - - private byte[] Add(byte[] value) - { - _hasPendingChanges = true; - return value; - } - - public void Dispose() - { - } + public void Dispose() { } } } diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/BlockBenchmarkHelper.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/BlockBenchmarkHelper.cs new file mode 100644 index 000000000000..2ec48b5437c7 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/BlockBenchmarkHelper.cs @@ -0,0 +1,232 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Autofac; +using Nethermind.Blockchain; +using Nethermind.Blockchain.BeaconBlockRoot; +using Nethermind.Blockchain.Blocks; +using Nethermind.Blockchain.Receipts; +using Nethermind.Blockchain.Visitors; +using Nethermind.Config; +using Nethermind.Consensus.ExecutionRequests; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Rewards; +using Nethermind.Consensus.Validators; +using Nethermind.Consensus.Withdrawals; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Logging; +using Nethermind.State; +using Nethermind.Trie; +using Nethermind.Int256; + +namespace Nethermind.Evm.Benchmark.GasBenchmarks; + +/// +/// Shared setup helpers for block-level gas benchmarks (BlockOne, Block, NewPayload modes). +/// +internal static class BlockBenchmarkHelper +{ + private sealed class WorldStateReaderAdapter(IWorldState worldState) : IStateReader + { + public bool TryGetAccount(BlockHeader baseBlock, Address address, out AccountStruct account) + { + account = default; + return false; + } + + public ReadOnlySpan GetStorage(BlockHeader baseBlock, Address address, in UInt256 index) => []; + + public byte[] GetCode(Hash256 codeHash) => null; + + public byte[] GetCode(in ValueHash256 codeHash) => null; + + public void RunTreeVisitor(ITreeVisitor treeVisitor, BlockHeader baseBlock, VisitingOptions visitingOptions = null) + where TCtx : struct, INodeContext + { + } + + public bool HasStateForBlock(BlockHeader baseBlock) => worldState.HasStateForBlock(baseBlock); + } + + public readonly struct BranchProcessingContext( + IWorldState state, + IBlockCachePreWarmer preWarmer, + IDisposable preWarmerLifetime, + PreBlockCaches preBlockCaches, + bool cachePrecompiles) + { + public IWorldState State { get; } = state; + public IBlockCachePreWarmer PreWarmer { get; } = preWarmer; + public IDisposable PreWarmerLifetime { get; } = preWarmerLifetime; + public PreBlockCaches PreBlockCaches { get; } = preBlockCaches; + public bool CachePrecompiles { get; } = cachePrecompiles; + } + + public static BranchProcessingContext CreateBranchProcessingContext( + ISpecProvider specProvider, + IBlockhashProvider blockhashProvider) + { + IBlocksConfig blocksConfig = new BlocksConfig(); + if (!blocksConfig.PreWarmStateOnBlockProcessing) + { + return new BranchProcessingContext( + PayloadLoader.CreateWorldState(), + preWarmer: null, + preWarmerLifetime: null, + preBlockCaches: null, + cachePrecompiles: false); + } + + NodeStorageCache nodeStorageCache = new(); + PreBlockCaches preBlockCaches = new(); + IWorldState state = PayloadLoader.CreateWorldState( + nodeStorageCache, + preBlockCaches, + populatePreBlockCache: false); + IWorldStateManager worldStateManager = PayloadLoader.CreateWorldStateManager(nodeStorageCache); + + ContainerBuilder containerBuilder = new(); + containerBuilder.RegisterInstance(specProvider).As().SingleInstance(); + containerBuilder.RegisterInstance(blockhashProvider).As().SingleInstance(); + containerBuilder.RegisterInstance(LimboLogs.Instance).As().SingleInstance(); + containerBuilder + .RegisterInstance(BlobBaseFeeCalculator.Instance) + .As() + .SingleInstance(); + containerBuilder.RegisterType().As().InstancePerLifetimeScope(); + containerBuilder.RegisterType().As().InstancePerLifetimeScope(); + containerBuilder.RegisterType().As().InstancePerLifetimeScope(); + containerBuilder.RegisterType().As().SingleInstance(); + containerBuilder.RegisterDecorator((context, _, baseCodeInfoRepository) => + new CachedCodeInfoRepository( + context.Resolve(), + baseCodeInfoRepository, + blocksConfig.CachePrecompilesOnBlockProcessing ? preBlockCaches.PrecompileCache : null)); + containerBuilder.RegisterType().As().InstancePerLifetimeScope(); + + IContainer preWarmerContainer = containerBuilder.Build(); + PrewarmerEnvFactory prewarmerEnvFactory = new(worldStateManager, preWarmerContainer); + IBlockCachePreWarmer preWarmer = new BlockCachePreWarmer( + prewarmerEnvFactory, + blocksConfig, + nodeStorageCache, + preBlockCaches, + LimboLogs.Instance); + + return new BranchProcessingContext( + state, + preWarmer, + preWarmerContainer, + preBlockCaches, + blocksConfig.CachePrecompilesOnBlockProcessing); + } + + public static BlockHeader CreateGenesisHeader() => + new(Keccak.Zero, Keccak.OfAnEmptySequenceRlp, Address.Zero, 0, 0, 0, 0, Array.Empty()) + { + StateRoot = PayloadLoader.GenesisStateRoot + }; + + public static IStateReader CreateStateReader(IWorldState worldState) => new WorldStateReaderAdapter(worldState); + + public static IReceiptStorage CreateReceiptStorage(IReceiptConfig receiptConfig) => + receiptConfig.StoreReceipts ? new InMemoryReceiptStorage() : NullReceiptStorage.Instance; + + public static ProcessingOptions GetImportProcessingOptions(IReceiptConfig receiptConfig) => + receiptConfig.StoreReceipts ? ProcessingOptions.StoreReceipts : ProcessingOptions.None; + + public static ProcessingOptions GetNewPayloadProcessingOptions(IReceiptConfig receiptConfig) => + receiptConfig.StoreReceipts + ? ProcessingOptions.EthereumMerge | ProcessingOptions.StoreReceipts + : ProcessingOptions.EthereumMerge; + + public static ProcessingOptions GetBlockBuildingProcessingOptions(IBlocksConfig blocksConfig) => + blocksConfig.BuildBlocksOnMainState + ? ProcessingOptions.NoValidation | ProcessingOptions.StoreReceipts | ProcessingOptions.DoNotUpdateHead + : ProcessingOptions.ProducingBlock; + + public static BlockchainProcessor.Options GetBlockBuildingBlockchainProcessorOptions(IBlocksConfig blocksConfig) => + blocksConfig.BuildBlocksOnMainState ? BlockchainProcessor.Options.Default : BlockchainProcessor.Options.NoReceipts; + + public static ITransactionProcessor CreateTransactionProcessor( + IWorldState state, + IBlockhashProvider blockhashProvider, + ISpecProvider specProvider, + PreBlockCaches preBlockCaches = null, + bool cachePrecompiles = false) + { + ICodeInfoRepository codeInfoRepo = new EthereumCodeInfoRepository(state); + if (cachePrecompiles && preBlockCaches is not null) + { + codeInfoRepo = new CachedCodeInfoRepository( + new EthereumPrecompileProvider(), + codeInfoRepo, + preBlockCaches.PrecompileCache); + } + + EthereumVirtualMachine vm = new(blockhashProvider, specProvider, LimboLogs.Instance); + + return new EthereumTransactionProcessor( + BlobBaseFeeCalculator.Instance, specProvider, state, vm, codeInfoRepo, LimboLogs.Instance); + } + + public static BlockProcessor CreateBlockProcessor( + ISpecProvider specProvider, ITransactionProcessor txProcessor, IWorldState state, IReceiptStorage receiptStorage = null) => + new(specProvider, + Always.Valid, + NoBlockRewards.Instance, + new BlockProcessor.BlockValidationTransactionsExecutor( + new ExecuteTransactionProcessorAdapter(txProcessor), state), + state, + receiptStorage ?? NullReceiptStorage.Instance, + new BeaconBlockRootHandler(txProcessor, state), + new BlockhashStore(state), + LimboLogs.Instance, + new WithdrawalProcessor(state, LimboLogs.Instance), + new ExecutionRequestsProcessor(txProcessor)); + + public static BlockProcessor CreateBlockBuildingProcessor( + ISpecProvider specProvider, ITransactionProcessor txProcessor, IWorldState state, IReceiptStorage receiptStorage = null) => + new(specProvider, + Always.Valid, + NoBlockRewards.Instance, + new BlockProcessor.BlockProductionTransactionsExecutor( + new BuildUpTransactionProcessorAdapter(txProcessor), + state, + new BlockProcessor.BlockProductionTransactionPicker(specProvider), + LimboLogs.Instance), + state, + receiptStorage ?? NullReceiptStorage.Instance, + new BeaconBlockRootHandler(txProcessor, state), + new BlockhashStore(state), + LimboLogs.Instance, + new WithdrawalProcessor(state, LimboLogs.Instance), + new ExecutionRequestsProcessor(txProcessor)); + + public static void ExecuteSetupPayload( + IWorldState state, ITransactionProcessor txProcessor, + BlockHeader preBlockHeader, GasPayloadBenchmarks.TestCase scenario, + IReleaseSpec spec) + { + string setupFile = GasPayloadBenchmarks.FindSetupFile(scenario.FileName); + if (setupFile is null) return; + + using IDisposable setupScope = state.BeginScope(preBlockHeader); + (BlockHeader setupHeader, Transaction[] setupTxs) = PayloadLoader.LoadPayload(setupFile); + txProcessor.SetBlockExecutionContext(setupHeader); + for (int i = 0; i < setupTxs.Length; i++) + { + txProcessor.Execute(setupTxs[i], NullTxTracer.Instance); + } + state.Commit(spec); + state.CommitTree(preBlockHeader.Number); + preBlockHeader.StateRoot = state.StateRoot; + } +} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBenchmarkColumnProvider.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBenchmarkColumnProvider.cs new file mode 100644 index 000000000000..665d29aa8902 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBenchmarkColumnProvider.cs @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Linq; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Mathematics; +using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Running; +using Perfolizer.Mathematics.Common; + +namespace Nethermind.Evm.Benchmark.GasBenchmarks; + +public class GasBenchmarkColumnProvider : IColumnProvider +{ + private static readonly IColumn[] Columns = + [ + new MGasThroughputColumn(), + new MGasConfidenceIntervalColumn(true), + new MGasConfidenceIntervalColumn(false) + ]; + + public IEnumerable GetColumns(Summary summary) => Columns; + + private static double CalculateMGasThroughput(double nanoseconds) + { + // All gas-benchmark payloads use 100M gas + const long gas = 100_000_000L; + double opThroughput = 1_000_000_000.0 / nanoseconds; + return gas * opThroughput / 1_000_000.0; + } + + private class MGasThroughputColumn : IColumn + { + public string Id => "MGas/s"; + public string ColumnName => "MGas/s"; + public string Legend => "Throughput in millions of gas per second"; + public bool AlwaysShow => true; + public ColumnCategory Category => ColumnCategory.Custom; + public int PriorityInCategory => 0; + public bool IsNumeric => true; + public UnitType UnitType => UnitType.Size; + + public string GetValue(Summary summary, BenchmarkCase benchmarkCase) + { + Statistics stats = summary.Reports.FirstOrDefault(r => r.BenchmarkCase == benchmarkCase)?.ResultStatistics; + if (stats is null) + return "N/A"; + + return CalculateMGasThroughput(stats.Mean).ToString("F2"); + } + + public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) + => GetValue(summary, benchmarkCase); + + public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) => false; + public bool IsAvailable(Summary summary) => true; + } + + private class MGasConfidenceIntervalColumn(bool isLower) : IColumn + { + public string Id => isLower ? "CI-Lower" : "CI-Upper"; + public string ColumnName => isLower ? "CI-Lower" : "CI-Upper"; + public string Legend => $"{(isLower ? "Lower" : "Upper")} bound of MGas/s 99% confidence interval"; + public bool AlwaysShow => true; + public ColumnCategory Category => ColumnCategory.Custom; + public int PriorityInCategory => 0; + public bool IsNumeric => true; + public UnitType UnitType => UnitType.Size; + + public string GetValue(Summary summary, BenchmarkCase benchmarkCase) + { + Statistics stats = summary.Reports.FirstOrDefault(r => r.BenchmarkCase == benchmarkCase)?.ResultStatistics; + if (stats is null) + return "N/A"; + + ConfidenceInterval ci = stats.GetConfidenceInterval(ConfidenceLevel.L99); + double bound = isLower ? ci.Lower : ci.Upper; + return CalculateMGasThroughput(bound).ToString("F2"); + } + + public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) + => GetValue(summary, benchmarkCase); + + public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) => false; + public bool IsAvailable(Summary summary) => true; + } +} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBenchmarkConfig.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBenchmarkConfig.cs new file mode 100644 index 000000000000..cef280207e1d --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBenchmarkConfig.cs @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Exporters.Json; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Toolchains.InProcess.Emit; + +namespace Nethermind.Evm.Benchmark.GasBenchmarks; + +public class GasBenchmarkConfig : ManualConfig +{ + internal static bool InProcess { get; set; } + + /// 1-based chunk index (e.g. 1 means first chunk). 0 = no chunking. + internal static int ChunkIndex { get; set; } + + /// Total number of chunks to split scenarios into. + internal static int ChunkTotal { get; set; } + + public GasBenchmarkConfig() + { + Job job = Job.MediumRun.WithLaunchCount(1).WithIterationCount(10); + + if (InProcess) + { + job = job.WithToolchain(InProcessEmitToolchain.Instance); + } + + AddJob(job); + AddDiagnoser(MemoryDiagnoser.Default); + AddColumnProvider(new GasBenchmarkColumnProvider()); + AddExporter(JsonExporter.Full); + } +} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockBenchmarks.cs new file mode 100644 index 000000000000..66a80a7e6bef --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockBenchmarks.cs @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using BenchmarkDotNet.Attributes; +using Nethermind.Blockchain; +using Nethermind.Blockchain.BeaconBlockRoot; +using Nethermind.Blockchain.Receipts; +using Nethermind.Blockchain.Tracing; +using Nethermind.Config; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Logging; +using Nethermind.Specs; +using Nethermind.Specs.Forks; + +namespace Nethermind.Evm.Benchmark.GasBenchmarks; + +/// +/// Benchmarks that replay gas-benchmark payload files via BranchProcessor.Process, +/// matching the real Nethermind block processing pipeline. Includes state scope management, +/// CommitTree, pre-warming coordination, beacon root, blockhash store, transaction execution, +/// bloom filters, receipts root, withdrawals, execution requests, and state root recalculation. +/// +[Config(typeof(GasBenchmarkConfig))] +public class GasBlockBenchmarks +{ + private IWorldState _state; + private BranchProcessor _branchProcessor; + private Block[] _blocksToProcess; + private BlockHeader _preBlockHeader; + private IBlockCachePreWarmer _preWarmer; + private IDisposable _preWarmerLifetime; + private ProcessingOptions _processingOptions; + + [ParamsSource(nameof(GetTestCases))] + public GasPayloadBenchmarks.TestCase Scenario { get; set; } + + public static IEnumerable GetTestCases() => GasPayloadBenchmarks.GetTestCases(); + + [GlobalSetup] + public void GlobalSetup() + { + IReleaseSpec pragueSpec = Prague.Instance; + ISpecProvider specProvider = new SingleReleaseSpecProvider(pragueSpec, 1, 1); + + PayloadLoader.EnsureGenesisInitialized(GasPayloadBenchmarks.s_genesisPath, pragueSpec); + + TestBlockhashProvider blockhashProvider = new(); + BlockBenchmarkHelper.BranchProcessingContext branchProcessingContext = BlockBenchmarkHelper.CreateBranchProcessingContext(specProvider, blockhashProvider); + _state = branchProcessingContext.State; + _preWarmer = branchProcessingContext.PreWarmer; + _preWarmerLifetime = branchProcessingContext.PreWarmerLifetime; + _preBlockHeader = BlockBenchmarkHelper.CreateGenesisHeader(); + ITransactionProcessor txProcessor = BlockBenchmarkHelper.CreateTransactionProcessor( + _state, + blockhashProvider, + specProvider, + branchProcessingContext.PreBlockCaches, + branchProcessingContext.CachePrecompiles); + + BlockBenchmarkHelper.ExecuteSetupPayload(_state, txProcessor, _preBlockHeader, Scenario, pragueSpec); + + ReceiptConfig receiptConfig = new(); + IReceiptStorage receiptStorage = BlockBenchmarkHelper.CreateReceiptStorage(receiptConfig); + _processingOptions = BlockBenchmarkHelper.GetImportProcessingOptions(receiptConfig); + + BlockProcessor blockProcessor = BlockBenchmarkHelper.CreateBlockProcessor( + specProvider, txProcessor, _state, receiptStorage); + + _branchProcessor = new BranchProcessor( + blockProcessor, specProvider, _state, + new BeaconBlockRootHandler(txProcessor, _state), + blockhashProvider, LimboLogs.Instance, _preWarmer); + + _blocksToProcess = [PayloadLoader.LoadBlock(Scenario.FilePath)]; + + // Warm up and verify correctness + Block[] result = _branchProcessor.Process( + _preBlockHeader, _blocksToProcess, + _processingOptions, + NullBlockTracer.Instance); + PayloadLoader.VerifyProcessedBlock(result[0], Scenario.ToString(), Scenario.FilePath); + } + + [Benchmark] + public void ProcessBlock() + { + _branchProcessor.Process( + _preBlockHeader, _blocksToProcess, + _processingOptions, + NullBlockTracer.Instance); + } + + [GlobalCleanup] + public void GlobalCleanup() + { + _preWarmer?.ClearCaches(); + _preWarmerLifetime?.Dispose(); + _state = null; + _branchProcessor = null; + _blocksToProcess = null; + _preWarmer = null; + _preWarmerLifetime = null; + _processingOptions = ProcessingOptions.None; + } +} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockBuildingBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockBuildingBenchmarks.cs new file mode 100644 index 000000000000..c2b4b71fe631 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockBuildingBenchmarks.cs @@ -0,0 +1,321 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Nethermind.Blockchain; +using Nethermind.Blockchain.BeaconBlockRoot; +using Nethermind.Blockchain.Find; +using Nethermind.Blockchain.Tracing; +using Nethermind.Blockchain.Visitors; +using Nethermind.Config; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Producers; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Crypto; +using Nethermind.Int256; +using Nethermind.Evm.State; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Logging; +using Nethermind.Specs; +using Nethermind.Specs.Forks; + +namespace Nethermind.Evm.Benchmark.GasBenchmarks; + +/// +/// Benchmarks block-building flow using BlockToProduce + BlockProductionTransactionsExecutor +/// with ProcessingOptions.ProducingBlock, matching Nethermind's producer-side transaction path. +/// +[Config(typeof(GasBenchmarkConfig))] +public class GasBlockBuildingBenchmarks +{ + internal const string BuildBlocksOnMainStateEnvVar = "NETHERMIND_BLOCKBUILDING_MAIN_STATE"; + private static readonly EthereumEcdsa s_ecdsa = new(1); + + private IWorldState _state; + private BranchProcessor _branchProcessor; + private IBlockchainProcessor _chainProcessor; + private BlockHeader _chainParentHeader; + private BlockHeader _preBlockHeader; + private BlockHeader _testHeaderTemplate; + private Transaction[] _testTransactions; + private BlockHeader[] _testUncles; + private Withdrawal[] _testWithdrawals; + private ProcessingOptions _processingOptions; + private IBlockCachePreWarmer _preWarmer; + private IDisposable _preWarmerLifetime; + + [ParamsSource(nameof(GetTestCases))] + public GasPayloadBenchmarks.TestCase Scenario { get; set; } + + public static IEnumerable GetTestCases() => GasPayloadBenchmarks.GetTestCases(); + + [GlobalSetup] + public void GlobalSetup() + { + IReleaseSpec pragueSpec = Prague.Instance; + ISpecProvider specProvider = new SingleReleaseSpecProvider(pragueSpec, 1, 1); + + PayloadLoader.EnsureGenesisInitialized(GasPayloadBenchmarks.s_genesisPath, pragueSpec); + + TestBlockhashProvider blockhashProvider = new(); + BlockBenchmarkHelper.BranchProcessingContext branchProcessingContext = BlockBenchmarkHelper.CreateBranchProcessingContext(specProvider, blockhashProvider); + _state = branchProcessingContext.State; + _preWarmer = branchProcessingContext.PreWarmer; + _preWarmerLifetime = branchProcessingContext.PreWarmerLifetime; + _preBlockHeader = BlockBenchmarkHelper.CreateGenesisHeader(); + + IBlocksConfig blocksConfig = new BlocksConfig(); + string buildOnMainStateValue = Environment.GetEnvironmentVariable(BuildBlocksOnMainStateEnvVar); + if (!string.IsNullOrWhiteSpace(buildOnMainStateValue) && bool.TryParse(buildOnMainStateValue, out bool buildOnMainState)) + { + blocksConfig.BuildBlocksOnMainState = buildOnMainState; + } + + _processingOptions = BlockBenchmarkHelper.GetBlockBuildingProcessingOptions(blocksConfig); + + ITransactionProcessor txProcessor = BlockBenchmarkHelper.CreateTransactionProcessor( + _state, + blockhashProvider, + specProvider, + branchProcessingContext.PreBlockCaches, + branchProcessingContext.CachePrecompiles); + + Block testBlock = PayloadLoader.LoadBlock(Scenario.FilePath); + _testHeaderTemplate = testBlock.Header; + _testTransactions = testBlock.Transactions; + _testUncles = testBlock.Uncles; + _testWithdrawals = testBlock.Withdrawals; + + _chainParentHeader = _preBlockHeader.Clone(); + _chainParentHeader.Hash = _testHeaderTemplate.ParentHash; + _chainParentHeader.Number = _testHeaderTemplate.Number > 0 ? _testHeaderTemplate.Number - 1 : 0; + _chainParentHeader.TotalDifficulty = _testHeaderTemplate.TotalDifficulty is null + ? UInt256.Zero + : _testHeaderTemplate.TotalDifficulty - _testHeaderTemplate.Difficulty; + + BlockBenchmarkHelper.ExecuteSetupPayload(_state, txProcessor, _preBlockHeader, Scenario, pragueSpec); + _chainParentHeader.StateRoot = _preBlockHeader.StateRoot; + + BlockProcessor blockProcessor = BlockBenchmarkHelper.CreateBlockBuildingProcessor(specProvider, txProcessor, _state); + _branchProcessor = new BranchProcessor( + blockProcessor, + specProvider, + _state, + new BeaconBlockRootHandler(txProcessor, _state), + blockhashProvider, + LimboLogs.Instance, + _preWarmer); + + BlockchainProcessor blockchainProcessor = new( + new BenchmarkBlockProducerBlockTree(_chainParentHeader), + _branchProcessor, + new RecoverSignatures(s_ecdsa, specProvider, LimboLogs.Instance), + BlockBenchmarkHelper.CreateStateReader(_state), + LimboLogs.Instance, + BlockBenchmarkHelper.GetBlockBuildingBlockchainProcessorOptions(blocksConfig), + new NoopProcessingStats()); + _chainProcessor = new OneTimeChainProcessor(_state, blockchainProcessor); + + // Warm up and verify correctness. + BlockToProduce warmupBlock = CreateBlockToProduce(); + Block warmupResult = _chainProcessor.Process(warmupBlock, _processingOptions, NullBlockTracer.Instance); + if (warmupResult is null) + { + throw new InvalidOperationException("Block building warmup did not produce a processed block."); + } + + PayloadLoader.VerifyProcessedBlock(warmupResult, Scenario.ToString(), Scenario.FilePath); + } + + [Benchmark] + public void ProcessBlock() + { + BlockToProduce blockToProduce = CreateBlockToProduce(); + _chainProcessor.Process(blockToProduce, _processingOptions, NullBlockTracer.Instance); + } + + private BlockToProduce CreateBlockToProduce() + { + BlockHeader header = _testHeaderTemplate.Clone(); + if (header.TotalDifficulty is null) + { + UInt256 parentTotalDifficulty = _chainParentHeader.TotalDifficulty ?? UInt256.Zero; + header.TotalDifficulty = parentTotalDifficulty + header.Difficulty; + } + + return new BlockToProduce(header, _testTransactions, _testUncles, _testWithdrawals); + } + + [GlobalCleanup] + public void GlobalCleanup() + { + _chainProcessor?.DisposeAsync().AsTask().GetAwaiter().GetResult(); + _preWarmer?.ClearCaches(); + _preWarmerLifetime?.Dispose(); + _state = null; + _branchProcessor = null; + _chainProcessor = null; + _chainParentHeader = null; + _preWarmer = null; + _preWarmerLifetime = null; + _testHeaderTemplate = null; + _testTransactions = null; + _testUncles = null; + _testWithdrawals = null; + } + + private sealed class NoopProcessingStats : IProcessingStats + { + event EventHandler IProcessingStats.NewProcessingStatistics + { + add { } + remove { } + } + + public void Start() { } + + public void CaptureStartStats() { } + + public void UpdateStats(Block block, BlockHeader baseBlock, long blockProcessingTimeInMicros) { } + } + + private sealed class BenchmarkBlockProducerBlockTree : IBlockTree + { + private readonly BlockHeader _parentHeader; + private readonly Block _head; + + public BenchmarkBlockProducerBlockTree(BlockHeader parentHeader) + { + _parentHeader = parentHeader; + _head = new Block(parentHeader, new BlockBody(Array.Empty(), Array.Empty(), null)); + SyncPivot = (0, Keccak.Zero); + BestSuggestedHeader = parentHeader; + } + + public Hash256 HeadHash => _head.Hash; + public Hash256 GenesisHash => _parentHeader.Hash; + public Hash256 PendingHash => null; + public Hash256 FinalizedHash => null; + public Hash256 SafeHash => null; + public Block Head => _head; + public ulong NetworkId => 1; + public ulong ChainId => 1; + public BlockHeader Genesis => _parentHeader; + public BlockHeader BestSuggestedHeader { get; set; } + public Block BestSuggestedBody => _head; + public BlockHeader BestSuggestedBeaconHeader => _parentHeader; + public BlockHeader LowestInsertedHeader { get; set; } + public BlockHeader LowestInsertedBeaconHeader { get; set; } + public long BestKnownNumber => _parentHeader.Number; + public long BestKnownBeaconNumber => _parentHeader.Number; + public bool CanAcceptNewBlocks => true; + public (long BlockNumber, Hash256 BlockHash) SyncPivot { get; set; } + public bool IsProcessingBlock { get; set; } + public long? BestPersistedState { get; set; } + + public event EventHandler NewBestSuggestedBlock { add { } remove { } } + public event EventHandler NewSuggestedBlock { add { } remove { } } + public event EventHandler BlockAddedToMain { add { } remove { } } + public event EventHandler NewHeadBlock { add { } remove { } } + public event EventHandler OnUpdateMainChain { add { } remove { } } + public event EventHandler OnForkChoiceUpdated { add { } remove { } } + + public Block FindBlock(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) => + blockHash == _parentHeader.Hash ? _head : null; + + public Block FindBlock(long blockNumber, BlockTreeLookupOptions options) => + blockNumber == _parentHeader.Number ? _head : null; + + public bool HasBlock(long blockNumber, Hash256 blockHash) => + blockNumber == _parentHeader.Number && blockHash == _parentHeader.Hash; + + public BlockHeader FindHeader(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) => + blockHash == _parentHeader.Hash ? _parentHeader : null; + + public BlockHeader FindHeader(long blockNumber, BlockTreeLookupOptions options) => + blockNumber == _parentHeader.Number ? _parentHeader : null; + + public Hash256 FindBlockHash(long blockNumber) => + blockNumber == _parentHeader.Number ? _parentHeader.Hash : null; + + public bool IsMainChain(BlockHeader blockHeader) => blockHeader?.Hash == _parentHeader.Hash; + + public bool IsMainChain(Hash256 blockHash, bool throwOnMissingHash = true) => blockHash == _parentHeader.Hash; + + public BlockHeader FindBestSuggestedHeader() => BestSuggestedHeader; + + public long GetLowestBlock() => _parentHeader.Number; + + public AddBlockResult Insert(BlockHeader header, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) => + AddBlockResult.Added; + + public void BulkInsertHeader(IReadOnlyList headers, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) { } + + public AddBlockResult Insert( + Block block, + BlockTreeInsertBlockOptions insertBlockOptions = BlockTreeInsertBlockOptions.None, + BlockTreeInsertHeaderOptions insertHeaderOptions = BlockTreeInsertHeaderOptions.None, + WriteFlags bodiesWriteFlags = WriteFlags.None) => + AddBlockResult.Added; + + public void UpdateHeadBlock(Hash256 blockHash) { } + + public void NewOldestBlock(long oldestBlock) { } + + public AddBlockResult SuggestBlock(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) => + AddBlockResult.Added; + + public ValueTask SuggestBlockAsync(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) => + ValueTask.FromResult(AddBlockResult.Added); + + public AddBlockResult SuggestHeader(BlockHeader header) => AddBlockResult.Added; + + public bool IsKnownBlock(long number, Hash256 blockHash) => + number == _parentHeader.Number && blockHash == _parentHeader.Hash; + + public bool IsKnownBeaconBlock(long number, Hash256 blockHash) => + number == _parentHeader.Number && blockHash == _parentHeader.Hash; + + public bool WasProcessed(long number, Hash256 blockHash) => + number == _parentHeader.Number && blockHash == _parentHeader.Hash; + + public void UpdateMainChain(IReadOnlyList blocks, bool wereProcessed, bool forceHeadBlock = false) { } + + public void MarkChainAsProcessed(IReadOnlyList blocks) { } + + public Task Accept(IBlockTreeVisitor blockTreeVisitor, CancellationToken cancellationToken) => Task.CompletedTask; + + public (BlockInfo Info, ChainLevelInfo Level) GetInfo(long number, Hash256 blockHash) => (null, null); + + public ChainLevelInfo FindLevel(long number) => null; + + public BlockInfo FindCanonicalBlockInfo(long blockNumber) => null; + + public Hash256 FindHash(long blockNumber) => blockNumber == _parentHeader.Number ? _parentHeader.Hash : null; + + public IOwnedReadOnlyList FindHeaders(Hash256 hash, int numberOfBlocks, int skip, bool reverse) => + new ArrayPoolList(0); + + public void DeleteInvalidBlock(Block invalidBlock) { } + + public void DeleteOldBlock(long blockNumber, Hash256 blockHash) { } + + public void ForkChoiceUpdated(Hash256 finalizedBlockHash, Hash256 safeBlockBlockHash) { } + + public int DeleteChainSlice(in long startNumber, long? endNumber = null, bool force = false) => 0; + + public bool IsBetterThanHead(BlockHeader header) => true; + + public void UpdateBeaconMainChain(BlockInfo[] blockInfos, long clearBeaconMainChainStartPoint) { } + + public void RecalculateTreeLevels() { } + } + +} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockOneBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockOneBenchmarks.cs new file mode 100644 index 000000000000..575a44ea8892 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockOneBenchmarks.cs @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using BenchmarkDotNet.Attributes; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Receipts; +using Nethermind.Blockchain.Tracing; +using Nethermind.Config; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm.State; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Specs; +using Nethermind.Specs.Forks; + +namespace Nethermind.Evm.Benchmark.GasBenchmarks; + +/// +/// Benchmarks that replay gas-benchmark payload files via BlockProcessor.ProcessOne. +/// This measures block processing without the BranchProcessor overhead (state scope management, +/// pre-warming coordination, cache clearing, TxHashCalculator). +/// +[Config(typeof(GasBenchmarkConfig))] +public class GasBlockOneBenchmarks +{ + private IWorldState _state; + private BlockProcessor _blockProcessor; + private Block _testBlock; + private BlockHeader _preBlockHeader; + private IReleaseSpec _spec; + private ProcessingOptions _processingOptions; + + [ParamsSource(nameof(GetTestCases))] + public GasPayloadBenchmarks.TestCase Scenario { get; set; } + + public static IEnumerable GetTestCases() => GasPayloadBenchmarks.GetTestCases(); + + [GlobalSetup] + public void GlobalSetup() + { + IReleaseSpec pragueSpec = Prague.Instance; + ISpecProvider specProvider = new SingleReleaseSpecProvider(pragueSpec, 1, 1); + _spec = pragueSpec; + + PayloadLoader.EnsureGenesisInitialized(GasPayloadBenchmarks.s_genesisPath, pragueSpec); + + _state = PayloadLoader.CreateWorldState(); + _preBlockHeader = BlockBenchmarkHelper.CreateGenesisHeader(); + + TestBlockhashProvider blockhashProvider = new(); + ITransactionProcessor txProcessor = BlockBenchmarkHelper.CreateTransactionProcessor( + _state, blockhashProvider, specProvider); + + BlockBenchmarkHelper.ExecuteSetupPayload(_state, txProcessor, _preBlockHeader, Scenario, pragueSpec); + + ReceiptConfig receiptConfig = new(); + IReceiptStorage receiptStorage = BlockBenchmarkHelper.CreateReceiptStorage(receiptConfig); + _processingOptions = BlockBenchmarkHelper.GetImportProcessingOptions(receiptConfig); + + _blockProcessor = BlockBenchmarkHelper.CreateBlockProcessor(specProvider, txProcessor, _state, receiptStorage); + _testBlock = PayloadLoader.LoadBlock(Scenario.FilePath); + + // Warm up and verify correctness + using (IDisposable scope = _state.BeginScope(_preBlockHeader)) + { + (Block processedBlock, _) = _blockProcessor.ProcessOne( + _testBlock, + _processingOptions, + NullBlockTracer.Instance, _spec, default); + _state.CommitTree(_preBlockHeader.Number + 1); + PayloadLoader.VerifyProcessedBlock(processedBlock, Scenario.ToString(), Scenario.FilePath); + } + } + + [Benchmark] + public void ProcessBlock() + { + using IDisposable scope = _state.BeginScope(_preBlockHeader); + _blockProcessor.ProcessOne( + _testBlock, + _processingOptions, + NullBlockTracer.Instance, _spec, default); + _state.CommitTree(_preBlockHeader.Number + 1); + } + + [GlobalCleanup] + public void GlobalCleanup() + { + _state = null; + _blockProcessor = null; + _testBlock = null; + _processingOptions = ProcessingOptions.None; + } +} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasNewPayloadBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasNewPayloadBenchmarks.cs new file mode 100644 index 000000000000..397c8ee91d44 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasNewPayloadBenchmarks.cs @@ -0,0 +1,1187 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Nethermind.Blockchain; +using Nethermind.Blockchain.BeaconBlockRoot; +using Nethermind.Blockchain.Receipts; +using Nethermind.Blockchain.Tracing; +using Nethermind.Blockchain.Visitors; +using Nethermind.Consensus; +using Nethermind.Consensus.Producers; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Validators; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Crypto; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Int256; +using Nethermind.JsonRpc; +using Nethermind.Logging; +using Nethermind.Merge.Plugin; +using Nethermind.Merge.Plugin.BlockProduction; +using Nethermind.Merge.Plugin.Data; +using Nethermind.Merge.Plugin.Handlers; +using Nethermind.Merge.Plugin.InvalidChainTracker; +using Nethermind.Merge.Plugin.Synchronization; +using Nethermind.Serialization.Json; +using Nethermind.Specs; +using Nethermind.Specs.Forks; +using Nethermind.Synchronization; + +namespace Nethermind.Evm.Benchmark.GasBenchmarks; + +/// +/// Benchmarks that replay payload files through NewPayloadHandler flow to mirror +/// production newPayload processing (request decode + payload validation + handler + queue processing). +/// +[Config(typeof(GasBenchmarkConfig))] +public class GasNewPayloadBenchmarks +{ + internal const string TimingFileEnvVar = "NETHERMIND_NEWPAYLOAD_REAL_TIMING_FILE"; + internal const string TimingReportFileEnvVar = "NETHERMIND_NEWPAYLOAD_REAL_TIMING_REPORT_FILE"; + private const int MaxConsoleScenarioCount = 10; + private static readonly JsonSerializerOptions s_jsonOptions = EthereumJsonSerializer.JsonOptions; + private static readonly EthereumEcdsa s_ecdsa = new(1); + private static readonly object s_timingFileLock = new(); + + private IReleaseSpec _releaseSpec; + private ISpecProvider _specProvider; + private IWorldState _state; + private BranchProcessor _branchProcessor; + private BlockHeader _preBlockHeader; + private byte[] _rawJsonBytes; + private BenchmarkNewPayloadBlockTree _blockTree; + private BenchmarkProcessingQueue _processingQueue; + private NewPayloadHandler _newPayloadHandler; + private TimedTransactionProcessor _timedTransactionProcessor; + private IBlockCachePreWarmer _preWarmer; + private IDisposable _preWarmerLifetime; + + private long _jsonParseTicks; + private long _payloadDeserializeTicks; + private long _optionalParamsTicks; + private long _validateForkTicks; + private long _validateParamsTicks; + private long _handlerTicks; + private long _queueTotalTicks; + private long _senderRecoveryTicks; + private long _blockProcessTicks; + private long _txExecutionTicks; + private int _iterationCount; + private int _txExecutionCount; + private readonly long[] _senderRecoveryByTypeTicks = new long[256]; + private readonly int[] _senderRecoveryByTypeCount = new int[256]; + private readonly long[] _txExecutionByTypeTicks = new long[256]; + private readonly int[] _txExecutionByTypeCount = new int[256]; + + [ParamsSource(nameof(GetTestCases))] + public GasPayloadBenchmarks.TestCase Scenario { get; set; } + + public static IEnumerable GetTestCases() => GasPayloadBenchmarks.GetTestCases(); + + [GlobalSetup] + public void GlobalSetup() + { + _releaseSpec = Prague.Instance; + _specProvider = new SingleReleaseSpecProvider(_releaseSpec, 1, 1); + + PayloadLoader.EnsureGenesisInitialized(GasPayloadBenchmarks.s_genesisPath, _releaseSpec); + + TestBlockhashProvider blockhashProvider = new(); + BlockBenchmarkHelper.BranchProcessingContext branchProcessingContext = BlockBenchmarkHelper.CreateBranchProcessingContext(_specProvider, blockhashProvider); + _state = branchProcessingContext.State; + _preWarmer = branchProcessingContext.PreWarmer; + _preWarmerLifetime = branchProcessingContext.PreWarmerLifetime; + _preBlockHeader = BlockBenchmarkHelper.CreateGenesisHeader(); + _preBlockHeader.TotalDifficulty = UInt256.Zero; + + ITransactionProcessor txProcessor = BlockBenchmarkHelper.CreateTransactionProcessor( + _state, + blockhashProvider, + _specProvider, + branchProcessingContext.PreBlockCaches, + branchProcessingContext.CachePrecompiles); + _timedTransactionProcessor = new TimedTransactionProcessor(txProcessor, this); + + BlockBenchmarkHelper.ExecuteSetupPayload(_state, _timedTransactionProcessor, _preBlockHeader, Scenario, _releaseSpec); + + ReceiptConfig receiptConfig = new(); + IReceiptStorage receiptStorage = BlockBenchmarkHelper.CreateReceiptStorage(receiptConfig); + BlockProcessor blockProcessor = BlockBenchmarkHelper.CreateBlockProcessor( + _specProvider, + _timedTransactionProcessor, + _state, + receiptStorage); + + _branchProcessor = new BranchProcessor( + blockProcessor, + _specProvider, + _state, + new BeaconBlockRootHandler(_timedTransactionProcessor, _state), + blockhashProvider, + LimboLogs.Instance, + _preWarmer); + + string rawJson = PayloadLoader.ReadRawJson(Scenario.FilePath); + _rawJsonBytes = Encoding.UTF8.GetBytes(rawJson); + ExecutionPayloadParams initialRequest = DeserializeRequest(collectTiming: false); + + _preBlockHeader.Hash = initialRequest.ExecutionPayload.ParentHash; + long parentNumber = (long)initialRequest.ExecutionPayload.BlockNumber - 1; + _preBlockHeader.Number = parentNumber >= 0 ? parentNumber : 0; + + _blockTree = new BenchmarkNewPayloadBlockTree(_preBlockHeader); + _processingQueue = new BenchmarkProcessingQueue( + _branchProcessor, + _preBlockHeader, + _specProvider, + this); + + MergeConfig mergeConfig = new() + { + // Benchmark iterations replay the same block hash, so cache must be disabled. + NewPayloadCacheSize = 0, + SimulateBlockProduction = false, + }; + + _newPayloadHandler = new NewPayloadHandler( + new NoopPayloadPreparationService(), + Always.Valid, + _blockTree, + AlwaysPoS.Instance, + Nethermind.Synchronization.No.BeaconSync, + new StaticBeaconPivot(_preBlockHeader), + new BlockCacheService(), + _processingQueue, + new NoopInvalidChainTracker(), + new NoopMergeSyncController(), + mergeConfig, + receiptConfig, + BlockBenchmarkHelper.CreateStateReader(_state), + LimboLogs.Instance); + + ExecuteNewPayload(initialRequest, collectTiming: false); + + Block processedBlock = _processingQueue.LastProcessedBlock; + if (processedBlock is null) + { + throw new InvalidOperationException("Warmup did not produce a processed block."); + } + + PayloadLoader.VerifyProcessedBlock(processedBlock, Scenario.ToString(), Scenario.FilePath); + ResetTimingAccumulators(); + } + + [Benchmark] + public void ProcessBlock() + { + ExecutionPayloadParams request = DeserializeRequest(collectTiming: true); + ExecuteNewPayload(request, collectTiming: true); + _iterationCount++; + } + + private void ExecuteNewPayload(ExecutionPayloadParams request, bool collectTiming) + { + ExecutionPayload executionPayload = request.ExecutionPayload; + long start = Stopwatch.GetTimestamp(); + if (!executionPayload.ValidateFork(_specProvider)) + { + throw new InvalidOperationException("Payload fork validation failed."); + } + if (collectTiming) + { + _validateForkTicks += Stopwatch.GetTimestamp() - start; + } + + start = Stopwatch.GetTimestamp(); + IReleaseSpec releaseSpec = _specProvider.GetSpec(executionPayload.BlockNumber, executionPayload.Timestamp); + Nethermind.Merge.Plugin.Data.ValidationResult validationResult = request.ValidateParams(releaseSpec, EngineApiVersions.Prague, out string validationError); + if (validationResult != Nethermind.Merge.Plugin.Data.ValidationResult.Success) + { + throw new InvalidOperationException($"Payload parameter validation failed: {validationError}"); + } + if (collectTiming) + { + _validateParamsTicks += Stopwatch.GetTimestamp() - start; + } + + _processingQueue.CollectTiming = collectTiming; + start = Stopwatch.GetTimestamp(); + ResultWrapper result = _newPayloadHandler.HandleAsync(executionPayload).GetAwaiter().GetResult(); + if (collectTiming) + { + _handlerTicks += Stopwatch.GetTimestamp() - start; + } + if (result.ErrorCode != ErrorCodes.None) + { + throw new InvalidOperationException($"NewPayload handler returned error code {result.ErrorCode} ({result.Result.Error})"); + } + + if (result.Data is null || result.Data.Status != PayloadStatus.Valid) + { + string status = result.Data is null ? "" : result.Data.Status; + string error = result.Data is null ? "" : result.Data.ValidationError; + throw new InvalidOperationException($"NewPayload handler returned status {status}. {error}"); + } + } + + private ExecutionPayloadParams DeserializeRequest(bool collectTiming) + { + long start = Stopwatch.GetTimestamp(); + using JsonDocument doc = JsonDocument.Parse(_rawJsonBytes); + JsonElement paramsArray = doc.RootElement.GetProperty("params"); + if (collectTiming) + { + _jsonParseTicks += Stopwatch.GetTimestamp() - start; + } + + start = Stopwatch.GetTimestamp(); + ExecutionPayloadV3 payload = JsonSerializer.Deserialize( + paramsArray[0].GetRawText(), + s_jsonOptions); + if (collectTiming) + { + _payloadDeserializeTicks += Stopwatch.GetTimestamp() - start; + } + + start = Stopwatch.GetTimestamp(); + byte[][] blobVersionedHashes = Array.Empty(); + if (paramsArray.GetArrayLength() > 1 && paramsArray[1].ValueKind == JsonValueKind.Array) + { + blobVersionedHashes = JsonSerializer.Deserialize( + paramsArray[1].GetRawText(), + s_jsonOptions); + if (blobVersionedHashes is null) + { + blobVersionedHashes = Array.Empty(); + } + } + + Hash256 parentBeaconBlockRoot = null; + if (paramsArray.GetArrayLength() > 2 && paramsArray[2].ValueKind == JsonValueKind.String) + { + parentBeaconBlockRoot = JsonSerializer.Deserialize( + paramsArray[2].GetRawText(), + s_jsonOptions); + payload.ParentBeaconBlockRoot = parentBeaconBlockRoot; + } + + byte[][] executionRequests = null; + if (paramsArray.GetArrayLength() > 3 && paramsArray[3].ValueKind == JsonValueKind.Array) + { + executionRequests = JsonSerializer.Deserialize( + paramsArray[3].GetRawText(), + s_jsonOptions); + payload.ExecutionRequests = executionRequests; + } + if (collectTiming) + { + _optionalParamsTicks += Stopwatch.GetTimestamp() - start; + } + + return new ExecutionPayloadParams( + payload, + blobVersionedHashes, + parentBeaconBlockRoot, + executionRequests); + } + + private void AddQueueTiming(Block block, long senderRecoveryTicks, long blockProcessTicks, long queueTotalTicks) + { + _senderRecoveryTicks += senderRecoveryTicks; + _blockProcessTicks += blockProcessTicks; + _queueTotalTicks += queueTotalTicks; + AddSenderRecoveryTypeBreakdown(block, senderRecoveryTicks); + } + + private void AddSenderRecoveryTypeBreakdown(Block block, long elapsedTicks) + { + int txCount = block.Transactions.Length; + if (txCount == 0) + { + return; + } + + int[] countsPerType = new int[_senderRecoveryByTypeCount.Length]; + int firstTypeIndex = -1; + for (int i = 0; i < txCount; i++) + { + int txTypeIndex = (int)block.Transactions[i].Type; + if (firstTypeIndex == -1) + { + firstTypeIndex = txTypeIndex; + } + + countsPerType[txTypeIndex]++; + _senderRecoveryByTypeCount[txTypeIndex]++; + } + + long allocatedTicks = 0; + for (int txTypeIndex = 0; txTypeIndex < countsPerType.Length; txTypeIndex++) + { + int count = countsPerType[txTypeIndex]; + if (count == 0) + { + continue; + } + + long typeTicks = elapsedTicks * count / txCount; + _senderRecoveryByTypeTicks[txTypeIndex] += typeTicks; + allocatedTicks += typeTicks; + } + + if (firstTypeIndex >= 0 && allocatedTicks != elapsedTicks) + { + _senderRecoveryByTypeTicks[firstTypeIndex] += elapsedTicks - allocatedTicks; + } + } + + private void AddTxExecutionTiming(TxType txType, long elapsedTicks) + { + _txExecutionTicks += elapsedTicks; + _txExecutionCount++; + int txTypeIndex = (int)txType; + _txExecutionByTypeTicks[txTypeIndex] += elapsedTicks; + _txExecutionByTypeCount[txTypeIndex]++; + } + + private void ResetTimingAccumulators() + { + _jsonParseTicks = 0; + _payloadDeserializeTicks = 0; + _optionalParamsTicks = 0; + _validateForkTicks = 0; + _validateParamsTicks = 0; + _handlerTicks = 0; + _queueTotalTicks = 0; + _senderRecoveryTicks = 0; + _blockProcessTicks = 0; + _txExecutionTicks = 0; + _iterationCount = 0; + _txExecutionCount = 0; + Array.Clear(_senderRecoveryByTypeTicks, 0, _senderRecoveryByTypeTicks.Length); + Array.Clear(_senderRecoveryByTypeCount, 0, _senderRecoveryByTypeCount.Length); + Array.Clear(_txExecutionByTypeTicks, 0, _txExecutionByTypeTicks.Length); + Array.Clear(_txExecutionByTypeCount, 0, _txExecutionByTypeCount.Length); + } + + [GlobalCleanup] + public void GlobalCleanup() + { + if (_iterationCount > 0) + { + TimingBreakdownSummary summary = new( + Scenario?.ToString() ?? "", + _iterationCount, + _jsonParseTicks, + _payloadDeserializeTicks, + _optionalParamsTicks, + _validateForkTicks, + _validateParamsTicks, + _handlerTicks, + _queueTotalTicks, + _senderRecoveryTicks, + _blockProcessTicks, + _txExecutionTicks, + _txExecutionCount, + (long[])_senderRecoveryByTypeTicks.Clone(), + (int[])_senderRecoveryByTypeCount.Clone(), + (long[])_txExecutionByTypeTicks.Clone(), + (int[])_txExecutionByTypeCount.Clone()); + AppendSummaryToFile(summary); + } + + _newPayloadHandler?.Dispose(); + _preWarmer?.ClearCaches(); + _preWarmerLifetime?.Dispose(); + _newPayloadHandler = null; + _processingQueue = null; + _blockTree = null; + _rawJsonBytes = null; + _branchProcessor = null; + _state = null; + _timedTransactionProcessor = null; + _preWarmer = null; + _preWarmerLifetime = null; + } + + public static void PrintFinalTimingBreakdown() + { + string timingFilePath = Environment.GetEnvironmentVariable(TimingFileEnvVar); + if (string.IsNullOrWhiteSpace(timingFilePath) || !File.Exists(timingFilePath)) + { + return; + } + + string[] lines = File.ReadAllLines(timingFilePath); + List summaries = new(lines.Length); + for (int i = 0; i < lines.Length; i++) + { + string line = lines[i]; + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + TimingBreakdownSummary summary = JsonSerializer.Deserialize(line, s_jsonOptions); + if (summary is not null) + { + summaries.Add(summary); + } + } + + if (summaries.Count == 0) + { + return; + } + + File.Delete(timingFilePath); + + string reportFilePath = ResolveTimingReportFilePath(); + string reportDirectory = Path.GetDirectoryName(reportFilePath); + if (!string.IsNullOrWhiteSpace(reportDirectory)) + { + Directory.CreateDirectory(reportDirectory); + } + + using (StreamWriter reportWriter = new(reportFilePath, false)) + { + WriteReport(reportWriter, summaries); + } + + if (summaries.Count <= MaxConsoleScenarioCount) + { + Console.WriteLine(); + WriteReport(Console.Out, summaries); + Console.WriteLine($"NewPayload timing breakdown saved to: {reportFilePath}"); + } + else + { + Console.WriteLine($"NewPayload timing breakdown saved to: {reportFilePath} (scenario count: {summaries.Count}, console output suppressed for runs with more than {MaxConsoleScenarioCount} scenarios)"); + } + } + + private static void WriteReport(TextWriter writer, List summaries) + { + writer.WriteLine("=== NewPayload timing breakdown (final) ==="); + long totalJsonParseTicks = 0; + long totalPayloadDeserializeTicks = 0; + long totalOptionalParamsTicks = 0; + long totalValidateForkTicks = 0; + long totalValidateParamsTicks = 0; + long totalHandlerTicks = 0; + long totalQueueTotalTicks = 0; + long totalSenderRecoveryTicks = 0; + long totalBlockProcessTicks = 0; + long totalTxExecutionTicks = 0; + int totalIterations = 0; + int totalTxExecutionCount = 0; + long[] totalSenderRecoveryByTypeTicks = new long[256]; + int[] totalSenderRecoveryByTypeCount = new int[256]; + long[] totalTxExecutionByTypeTicks = new long[256]; + int[] totalTxExecutionByTypeCount = new int[256]; + + for (int i = 0; i < summaries.Count; i++) + { + TimingBreakdownSummary summary = summaries[i]; + PrintSummary( + writer, + summary.Name, + summary.Iterations, + summary.JsonParseTicks, + summary.PayloadDeserializeTicks, + summary.OptionalParamsTicks, + summary.ValidateForkTicks, + summary.ValidateParamsTicks, + summary.HandlerTicks, + summary.QueueTotalTicks, + summary.SenderRecoveryTicks, + summary.BlockProcessTicks, + summary.TxExecutionTicks, + summary.TxExecutionCount, + summary.SenderRecoveryByTypeTicks, + summary.SenderRecoveryByTypeCount, + summary.TxExecutionByTypeTicks, + summary.TxExecutionByTypeCount); + + totalJsonParseTicks += summary.JsonParseTicks; + totalPayloadDeserializeTicks += summary.PayloadDeserializeTicks; + totalOptionalParamsTicks += summary.OptionalParamsTicks; + totalValidateForkTicks += summary.ValidateForkTicks; + totalValidateParamsTicks += summary.ValidateParamsTicks; + totalHandlerTicks += summary.HandlerTicks; + totalQueueTotalTicks += summary.QueueTotalTicks; + totalSenderRecoveryTicks += summary.SenderRecoveryTicks; + totalBlockProcessTicks += summary.BlockProcessTicks; + totalTxExecutionTicks += summary.TxExecutionTicks; + totalIterations += summary.Iterations; + totalTxExecutionCount += summary.TxExecutionCount; + AddTypeBreakdown(totalSenderRecoveryByTypeTicks, summary.SenderRecoveryByTypeTicks); + AddTypeBreakdown(totalSenderRecoveryByTypeCount, summary.SenderRecoveryByTypeCount); + AddTypeBreakdown(totalTxExecutionByTypeTicks, summary.TxExecutionByTypeTicks); + AddTypeBreakdown(totalTxExecutionByTypeCount, summary.TxExecutionByTypeCount); + } + + if (summaries.Count > 1 && totalIterations > 0) + { + PrintSummary( + writer, + "ALL SCENARIOS", + totalIterations, + totalJsonParseTicks, + totalPayloadDeserializeTicks, + totalOptionalParamsTicks, + totalValidateForkTicks, + totalValidateParamsTicks, + totalHandlerTicks, + totalQueueTotalTicks, + totalSenderRecoveryTicks, + totalBlockProcessTicks, + totalTxExecutionTicks, + totalTxExecutionCount, + totalSenderRecoveryByTypeTicks, + totalSenderRecoveryByTypeCount, + totalTxExecutionByTypeTicks, + totalTxExecutionByTypeCount); + } + } + + private static string ResolveTimingReportFilePath() + { + string configuredPath = Environment.GetEnvironmentVariable(TimingReportFileEnvVar); + if (!string.IsNullOrWhiteSpace(configuredPath)) + { + return configuredPath; + } + + return Path.Combine( + Path.GetTempPath(), + $"nethermind-newpayload-real-timing-breakdown-{DateTime.UtcNow:yyyyMMdd-HHmmss}-{Guid.NewGuid():N}.txt"); + } + + private static void AppendSummaryToFile(TimingBreakdownSummary summary) + { + string timingFilePath = Environment.GetEnvironmentVariable(TimingFileEnvVar); + if (string.IsNullOrWhiteSpace(timingFilePath)) + { + return; + } + + string line = JsonSerializer.Serialize(summary, s_jsonOptions); + lock (s_timingFileLock) + { + File.AppendAllText(timingFilePath, line + Environment.NewLine); + } + } + + private static void PrintSummary( + TextWriter writer, + string name, + int iterations, + long jsonParseTicks, + long payloadDeserializeTicks, + long optionalParamsTicks, + long validateForkTicks, + long validateParamsTicks, + long handlerTicks, + long queueTotalTicks, + long senderRecoveryTicks, + long blockProcessTicks, + long txExecutionTicks, + int txExecutionCount, + long[] senderRecoveryByTypeTicks, + int[] senderRecoveryByTypeCount, + long[] txExecutionByTypeTicks, + int[] txExecutionByTypeCount) + { + double jsonParseMs = TicksToMs(jsonParseTicks); + double payloadDeserializeMs = TicksToMs(payloadDeserializeTicks); + double optionalParamsMs = TicksToMs(optionalParamsTicks); + double validateForkMs = TicksToMs(validateForkTicks); + double validateParamsMs = TicksToMs(validateParamsTicks); + double handlerMs = TicksToMs(handlerTicks); + double queueTotalMs = TicksToMs(queueTotalTicks); + double senderRecoveryMs = TicksToMs(senderRecoveryTicks); + double blockProcessMs = TicksToMs(blockProcessTicks); + double txExecutionMs = TicksToMs(txExecutionTicks); + double handlerNonQueueMs = handlerMs - queueTotalMs; + if (handlerNonQueueMs < 0) + { + handlerNonQueueMs = 0; + } + + double queueNonStagedMs = queueTotalMs - senderRecoveryMs - blockProcessMs; + if (queueNonStagedMs < 0) + { + queueNonStagedMs = 0; + } + + double nonTxBlockMs = blockProcessMs - txExecutionMs; + if (nonTxBlockMs < 0) + { + nonTxBlockMs = 0; + } + + int senderRecoveryCount = GetTotalCount(senderRecoveryByTypeCount); + double totalMs = jsonParseMs + payloadDeserializeMs + optionalParamsMs + validateForkMs + validateParamsMs + handlerMs; + + writer.WriteLine($"--- {name} ({iterations} iterations) ---"); + PrintLine(writer, "JSON parse", jsonParseMs, totalMs, iterations); + PrintLine(writer, "Payload deserialize", payloadDeserializeMs, totalMs, iterations); + PrintLine(writer, "Optional params", optionalParamsMs, totalMs, iterations); + PrintLine(writer, "Validate fork", validateForkMs, totalMs, iterations); + PrintLine(writer, "Validate params", validateParamsMs, totalMs, iterations); + PrintLine(writer, "Handler total", handlerMs, totalMs, iterations); + writer.WriteLine($" {"Total",-20} {totalMs,9:F3} ms avg {totalMs / iterations:F3} ms/iter"); + writer.WriteLine(" Handler detail:"); + writer.WriteLine($" {"Queue total",-18} {queueTotalMs,9:F3} ms ({GetShare(queueTotalMs, handlerMs),5:F1}% of handler)"); + writer.WriteLine($" {"Handler non-queue",-18} {handlerNonQueueMs,9:F3} ms ({GetShare(handlerNonQueueMs, handlerMs),5:F1}% of handler)"); + writer.WriteLine(" Queue detail:"); + writer.WriteLine($" {"Sender recovery",-18} {senderRecoveryMs,9:F3} ms ({GetShare(senderRecoveryMs, queueTotalMs),5:F1}% of queue) avg {GetAverage(senderRecoveryMs, senderRecoveryCount):F3} ms/tx"); + writer.WriteLine($" {"Block processing",-18} {blockProcessMs,9:F3} ms ({GetShare(blockProcessMs, queueTotalMs),5:F1}% of queue)"); + writer.WriteLine($" {"Queue non-staged",-18} {queueNonStagedMs,9:F3} ms ({GetShare(queueNonStagedMs, queueTotalMs),5:F1}% of queue)"); + writer.WriteLine(" Block processing detail:"); + writer.WriteLine($" {"Tx execution",-18} {txExecutionMs,9:F3} ms ({GetShare(txExecutionMs, blockProcessMs),5:F1}% of block) avg {GetAverage(txExecutionMs, txExecutionCount):F3} ms/tx"); + writer.WriteLine($" {"Non-tx overhead",-18} {nonTxBlockMs,9:F3} ms ({GetShare(nonTxBlockMs, blockProcessMs),5:F1}% of block)"); + PrintTxTypeBreakdown(writer, "Tx execution by type", txExecutionByTypeTicks, txExecutionByTypeCount, txExecutionMs); + writer.WriteLine(" Sender recovery detail:"); + writer.WriteLine($" {"Recovered txs",-18} {senderRecoveryCount,9} tx"); + writer.WriteLine($" {"Avg per tx",-18} {GetAverage(senderRecoveryMs, senderRecoveryCount),9:F3} ms/tx"); + PrintTxTypeBreakdown(writer, "Sender recovery by type", senderRecoveryByTypeTicks, senderRecoveryByTypeCount, senderRecoveryMs); + } + + private static void PrintLine(TextWriter writer, string label, double stageMs, double totalMs, int iterations) + { + double share = totalMs > 0 ? (100.0 * stageMs / totalMs) : 0.0; + double avg = iterations > 0 ? (stageMs / iterations) : 0.0; + writer.WriteLine($" {label,-20} {stageMs,9:F3} ms ({share,5:F1}%) avg {avg:F3} ms/iter"); + } + + private static void PrintTxTypeBreakdown(TextWriter writer, string title, long[] ticksByType, int[] countByType, double stageTotalMs) + { + writer.WriteLine($" {title}:"); + for (int txTypeIndex = 0; txTypeIndex < countByType.Length; txTypeIndex++) + { + int count = countByType[txTypeIndex]; + if (count == 0) + { + continue; + } + + double ms = TicksToMs(ticksByType[txTypeIndex]); + double share = GetShare(ms, stageTotalMs); + double avg = GetAverage(ms, count); + writer.WriteLine($" {FormatTxType((TxType)txTypeIndex),-16} {ms,9:F3} ms ({share,5:F1}%) avg {avg:F3} ms/tx count {count}"); + } + } + + private static string FormatTxType(TxType txType) + { + return txType switch + { + TxType.Legacy => "Legacy", + TxType.AccessList => "AccessList", + TxType.EIP1559 => "EIP1559", + TxType.Blob => "Blob", + TxType.SetCode => "SetCode", + TxType.DepositTx => "DepositTx", + _ => $"Type(0x{(byte)txType:X2})", + }; + } + + private static void AddTypeBreakdown(long[] destination, long[] source) + { + int length = destination.Length < source.Length ? destination.Length : source.Length; + for (int i = 0; i < length; i++) + { + destination[i] += source[i]; + } + } + + private static void AddTypeBreakdown(int[] destination, int[] source) + { + int length = destination.Length < source.Length ? destination.Length : source.Length; + for (int i = 0; i < length; i++) + { + destination[i] += source[i]; + } + } + + private static int GetTotalCount(int[] countByType) + { + int total = 0; + for (int i = 0; i < countByType.Length; i++) + { + total += countByType[i]; + } + + return total; + } + + private static double GetShare(double part, double whole) + { + return whole > 0 ? (100.0 * part / whole) : 0.0; + } + + private static double GetAverage(double totalMs, int count) + { + return count > 0 ? (totalMs / count) : 0.0; + } + + private static double TicksToMs(long ticks) => ticks * 1000.0 / Stopwatch.Frequency; + + private sealed class BenchmarkProcessingQueue : IBlockProcessingQueue + { + private readonly BranchProcessor _branchProcessor; + private readonly BlockHeader _parentHeader; + private readonly RecoverSignatures _recoverSignatures; + private readonly GasNewPayloadBenchmarks _owner; + private int _count; + + public BenchmarkProcessingQueue( + BranchProcessor branchProcessor, + BlockHeader parentHeader, + ISpecProvider specProvider, + GasNewPayloadBenchmarks owner) + { + _branchProcessor = branchProcessor; + _parentHeader = parentHeader; + _recoverSignatures = new RecoverSignatures(s_ecdsa, specProvider, LimboLogs.Instance); + _owner = owner; + } + + public bool CollectTiming { get; set; } + + public Block LastProcessedBlock { get; private set; } + + public event EventHandler ProcessingQueueEmpty; + public event EventHandler BlockAdded; + public event EventHandler BlockRemoved; + + public int Count => _count; + + public ValueTask Enqueue(Block block, ProcessingOptions processingOptions) + { + Interlocked.Increment(ref _count); + long queueStart = 0; + long senderRecoveryTicks = 0; + long blockProcessTicks = 0; + try + { + BlockAdded?.Invoke(this, new BlockEventArgs(block)); + if (CollectTiming) + { + queueStart = Stopwatch.GetTimestamp(); + long senderStart = Stopwatch.GetTimestamp(); + _recoverSignatures.RecoverData(block); + senderRecoveryTicks = Stopwatch.GetTimestamp() - senderStart; + + long processStart = Stopwatch.GetTimestamp(); + Block[] processedWithTiming = _branchProcessor.Process( + _parentHeader, + [block], + processingOptions, + NullBlockTracer.Instance); + blockProcessTicks = Stopwatch.GetTimestamp() - processStart; + LastProcessedBlock = processedWithTiming[0]; + } + else + { + _recoverSignatures.RecoverData(block); + Block[] processed = _branchProcessor.Process( + _parentHeader, + [block], + processingOptions, + NullBlockTracer.Instance); + LastProcessedBlock = processed[0]; + } + + Hash256 blockHash = block.GetOrCalculateHash(); + BlockRemoved?.Invoke(this, new BlockRemovedEventArgs(blockHash, ProcessingResult.Success)); + } + catch (Exception ex) + { + Hash256 blockHash = block.GetOrCalculateHash(); + BlockRemoved?.Invoke(this, new BlockRemovedEventArgs(blockHash, ProcessingResult.Exception, ex)); + throw; + } + finally + { + if (CollectTiming && queueStart != 0) + { + long queueTotalTicks = Stopwatch.GetTimestamp() - queueStart; + _owner.AddQueueTiming(block, senderRecoveryTicks, blockProcessTicks, queueTotalTicks); + } + + if (Interlocked.Decrement(ref _count) == 0) + { + ProcessingQueueEmpty?.Invoke(this, EventArgs.Empty); + } + } + + return ValueTask.CompletedTask; + } + } + + private sealed class TimedTransactionProcessor(ITransactionProcessor inner, GasNewPayloadBenchmarks owner) : ITransactionProcessor + { + public TransactionResult Execute(Transaction transaction, ITxTracer txTracer) + { + long start = Stopwatch.GetTimestamp(); + TransactionResult result = inner.Execute(transaction, txTracer); + long elapsedTicks = Stopwatch.GetTimestamp() - start; + owner.AddTxExecutionTiming(transaction.Type, elapsedTicks); + return result; + } + + public TransactionResult CallAndRestore(Transaction transaction, ITxTracer txTracer) => + inner.CallAndRestore(transaction, txTracer); + + public TransactionResult BuildUp(Transaction transaction, ITxTracer txTracer) => + inner.BuildUp(transaction, txTracer); + + public TransactionResult Trace(Transaction transaction, ITxTracer txTracer) => + inner.Trace(transaction, txTracer); + + public TransactionResult Warmup(Transaction transaction, ITxTracer txTracer) => + inner.Warmup(transaction, txTracer); + + public void SetBlockExecutionContext(BlockHeader blockHeader) => + inner.SetBlockExecutionContext(blockHeader); + + public void SetBlockExecutionContext(in BlockExecutionContext blockExecutionContext) => + inner.SetBlockExecutionContext(in blockExecutionContext); + } + + private sealed record TimingBreakdownSummary + { + public TimingBreakdownSummary( + string name, + int iterations, + long jsonParseTicks, + long payloadDeserializeTicks, + long optionalParamsTicks, + long validateForkTicks, + long validateParamsTicks, + long handlerTicks, + long queueTotalTicks, + long senderRecoveryTicks, + long blockProcessTicks, + long txExecutionTicks, + int txExecutionCount, + long[] senderRecoveryByTypeTicks, + int[] senderRecoveryByTypeCount, + long[] txExecutionByTypeTicks, + int[] txExecutionByTypeCount) + { + Name = name; + Iterations = iterations; + JsonParseTicks = jsonParseTicks; + PayloadDeserializeTicks = payloadDeserializeTicks; + OptionalParamsTicks = optionalParamsTicks; + ValidateForkTicks = validateForkTicks; + ValidateParamsTicks = validateParamsTicks; + HandlerTicks = handlerTicks; + QueueTotalTicks = queueTotalTicks; + SenderRecoveryTicks = senderRecoveryTicks; + BlockProcessTicks = blockProcessTicks; + TxExecutionTicks = txExecutionTicks; + TxExecutionCount = txExecutionCount; + SenderRecoveryByTypeTicks = senderRecoveryByTypeTicks; + SenderRecoveryByTypeCount = senderRecoveryByTypeCount; + TxExecutionByTypeTicks = txExecutionByTypeTicks; + TxExecutionByTypeCount = txExecutionByTypeCount; + } + + public string Name { get; init; } + public int Iterations { get; init; } + public long JsonParseTicks { get; init; } + public long PayloadDeserializeTicks { get; init; } + public long OptionalParamsTicks { get; init; } + public long ValidateForkTicks { get; init; } + public long ValidateParamsTicks { get; init; } + public long HandlerTicks { get; init; } + public long QueueTotalTicks { get; init; } + public long SenderRecoveryTicks { get; init; } + public long BlockProcessTicks { get; init; } + public long TxExecutionTicks { get; init; } + public int TxExecutionCount { get; init; } + public long[] SenderRecoveryByTypeTicks { get; init; } + public int[] SenderRecoveryByTypeCount { get; init; } + public long[] TxExecutionByTypeTicks { get; init; } + public int[] TxExecutionByTypeCount { get; init; } + } + + private sealed class BenchmarkNewPayloadBlockTree : IBlockTree + { + private readonly BlockHeader _parentHeader; + private readonly Block _head; + private readonly BlockInfo _parentInfo; + + public BenchmarkNewPayloadBlockTree(BlockHeader parentHeader) + { + _parentHeader = parentHeader; + _head = new Block(parentHeader, new BlockBody(Array.Empty(), Array.Empty(), null)); + _parentInfo = new BlockInfo(parentHeader.Hash, parentHeader.TotalDifficulty ?? UInt256.Zero) + { + WasProcessed = true, + BlockNumber = parentHeader.Number, + }; + BestSuggestedHeader = parentHeader; + SyncPivot = (0, Keccak.Zero); + } + + public Block Head => _head; + public BlockHeader BestSuggestedHeader { get; set; } + + public event EventHandler BlockAddedToMain { add { } remove { } } + public event EventHandler NewBestSuggestedBlock { add { } remove { } } + public event EventHandler NewSuggestedBlock { add { } remove { } } + public event EventHandler NewHeadBlock { add { } remove { } } + public event EventHandler OnUpdateMainChain { add { } remove { } } + public event EventHandler OnForkChoiceUpdated { add { } remove { } } + + public BlockHeader FindBestSuggestedHeader() => BestSuggestedHeader; + + public Hash256 HeadHash => _head.Hash; + public Hash256 GenesisHash => Keccak.Zero; + public Hash256 PendingHash => null; + public Hash256 FinalizedHash => null; + public Hash256 SafeHash => null; + public long? BestPersistedState { get; set; } + + public Block FindBlock(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) + { + if (blockHash == _parentHeader.Hash) + { + return _head; + } + + return null; + } + + public Block FindBlock(long blockNumber, BlockTreeLookupOptions options) + { + if (blockNumber == _parentHeader.Number) + { + return _head; + } + + return null; + } + + public bool HasBlock(long blockNumber, Hash256 blockHash) => + blockNumber == _parentHeader.Number && blockHash == _parentHeader.Hash; + + public BlockHeader FindHeader(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) + { + if (blockHash == _parentHeader.Hash) + { + return _parentHeader; + } + + return null; + } + + public BlockHeader FindHeader(long blockNumber, BlockTreeLookupOptions options) + { + if (blockNumber == _parentHeader.Number) + { + return _parentHeader; + } + + return null; + } + + public Hash256 FindBlockHash(long blockNumber) + { + if (blockNumber == _parentHeader.Number) + { + return _parentHeader.Hash; + } + + return null; + } + + public bool IsMainChain(BlockHeader blockHeader) => blockHeader?.Hash == _parentHeader.Hash; + + public bool IsMainChain(Hash256 blockHash, bool throwOnMissingHash = true) => blockHash == _parentHeader.Hash; + + public long GetLowestBlock() => _parentHeader.Number; + + public ulong NetworkId => 1; + public ulong ChainId => 1; + public BlockHeader Genesis => _parentHeader; + public Block BestSuggestedBody => _head; + public BlockHeader BestSuggestedBeaconHeader => _parentHeader; + public BlockHeader LowestInsertedHeader { get; set; } + public BlockHeader LowestInsertedBeaconHeader { get; set; } + public long BestKnownNumber => _parentHeader.Number; + public long BestKnownBeaconNumber => _parentHeader.Number; + public bool CanAcceptNewBlocks => true; + public (long BlockNumber, Hash256 BlockHash) SyncPivot { get; set; } + public bool IsProcessingBlock { get; set; } + + public AddBlockResult Insert(BlockHeader header, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) + => AddBlockResult.Added; + + public void BulkInsertHeader(IReadOnlyList headers, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) { } + + public AddBlockResult Insert( + Block block, + BlockTreeInsertBlockOptions insertBlockOptions = BlockTreeInsertBlockOptions.None, + BlockTreeInsertHeaderOptions insertHeaderOptions = BlockTreeInsertHeaderOptions.None, + WriteFlags bodiesWriteFlags = WriteFlags.None) + => AddBlockResult.Added; + + public void UpdateHeadBlock(Hash256 blockHash) { } + + public void NewOldestBlock(long oldestBlock) { } + + public AddBlockResult SuggestBlock(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) + => AddBlockResult.Added; + + public ValueTask SuggestBlockAsync(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) + => ValueTask.FromResult(AddBlockResult.Added); + + public AddBlockResult SuggestHeader(BlockHeader header) => AddBlockResult.Added; + + public bool IsKnownBlock(long number, Hash256 blockHash) => + number == _parentHeader.Number && blockHash == _parentHeader.Hash; + + public bool IsKnownBeaconBlock(long number, Hash256 blockHash) => + number == _parentHeader.Number && blockHash == _parentHeader.Hash; + + public bool WasProcessed(long number, Hash256 blockHash) => + number == _parentHeader.Number && blockHash == _parentHeader.Hash; + + public void UpdateMainChain(IReadOnlyList blocks, bool wereProcessed, bool forceHeadBlock = false) { } + + public void MarkChainAsProcessed(IReadOnlyList blocks) { } + + public Task Accept(IBlockTreeVisitor blockTreeVisitor, CancellationToken cancellationToken) => Task.CompletedTask; + + public (BlockInfo Info, ChainLevelInfo Level) GetInfo(long number, Hash256 blockHash) + { + if (number == _parentHeader.Number && blockHash == _parentHeader.Hash) + { + return (_parentInfo, null); + } + + return (null, null); + } + + public ChainLevelInfo FindLevel(long number) => null; + + public BlockInfo FindCanonicalBlockInfo(long blockNumber) => _parentInfo; + + public Hash256 FindHash(long blockNumber) + { + if (blockNumber == _parentHeader.Number) + { + return _parentHeader.Hash; + } + + return null; + } + + public IOwnedReadOnlyList FindHeaders(Hash256 hash, int numberOfBlocks, int skip, bool reverse) + => new ArrayPoolList(0); + + public void DeleteInvalidBlock(Block invalidBlock) { } + + public void DeleteOldBlock(long blockNumber, Hash256 blockHash) { } + + public void ForkChoiceUpdated(Hash256 finalizedBlockHash, Hash256 safeBlockBlockHash) { } + + public int DeleteChainSlice(in long startNumber, long? endNumber = null, bool force = false) => 0; + + public bool IsBetterThanHead(BlockHeader header) => false; + + public void UpdateBeaconMainChain(BlockInfo[] blockInfos, long clearBeaconMainChainStartPoint) { } + + public void RecalculateTreeLevels() { } + } + +#nullable enable + private sealed class NoopPayloadPreparationService : IPayloadPreparationService + { + public string? StartPreparingPayload(BlockHeader parentHeader, PayloadAttributes payloadAttributes) => null; + + public ValueTask GetPayload(string payloadId, bool skipCancel = false) + => ValueTask.FromResult(null); + + public void CancelBlockProduction(string payloadId) { } + } +#nullable disable + + private sealed class NoopMergeSyncController : IMergeSyncController + { + public void StopSyncing() { } + + public bool TryInitBeaconHeaderSync(BlockHeader blockHeader) => false; + + public void StopBeaconModeControl() { } + } + + private sealed class NoopInvalidChainTracker : IInvalidChainTracker + { + public void SetChildParent(Hash256 child, Hash256 parent) { } + + public void OnInvalidBlock(Hash256 failedBlock, Hash256 parent) { } + + public bool IsOnKnownInvalidChain(Hash256 blockHash, out Hash256 lastValidHash) + { + lastValidHash = null; + return false; + } + + public void Dispose() { } + } + + private sealed class StaticBeaconPivot : IBeaconPivot + { + private readonly BlockHeader _pivot; + + public StaticBeaconPivot(BlockHeader pivot) + { + _pivot = pivot; + ProcessDestination = pivot; + } + + public long PivotNumber => _pivot.Number; + public Hash256 PivotHash => _pivot.Hash; + public Hash256 PivotParentHash => _pivot.ParentHash; + public long PivotDestinationNumber => _pivot.Number; + + public BlockHeader ProcessDestination { get; set; } + + public bool ShouldForceStartNewSync { get; set; } + + public void EnsurePivot(BlockHeader blockHeader, bool updateOnlyIfNull = false) + { + if (!updateOnlyIfNull || ProcessDestination is null) + { + ProcessDestination = blockHeader; + } + } + + public void RemoveBeaconPivot() { } + + public bool BeaconPivotExists() => true; + } + +} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasNewPayloadMeasuredBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasNewPayloadMeasuredBenchmarks.cs new file mode 100644 index 000000000000..e27643077b6c --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasNewPayloadMeasuredBenchmarks.cs @@ -0,0 +1,679 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text.Json; +using BenchmarkDotNet.Attributes; +using Nethermind.Blockchain; +using Nethermind.Blockchain.BeaconBlockRoot; +using Nethermind.Blockchain.Receipts; +using Nethermind.Blockchain.Tracing; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Validators; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Crypto; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Merge.Plugin.Data; +using Nethermind.Serialization.Json; +using Nethermind.Specs; +using Nethermind.Specs.Forks; + +namespace Nethermind.Evm.Benchmark.GasBenchmarks; + +/// +/// Benchmarks that replay gas-benchmark payload files through the full newPayload path: +/// JSON deserialization → ExecutionPayloadV3 → TryGetBlock → BranchProcessor.Process. +/// Each iteration re-deserializes JSON from memory, measuring the overhead of payload +/// decoding, RLP transaction decoding, and block construction on top of block processing. +/// +[Config(typeof(GasBenchmarkConfig))] +public class GasNewPayloadMeasuredBenchmarks +{ + internal const string TimingFileEnvVar = "NETHERMIND_NEWPAYLOAD_TIMING_FILE"; + internal const string TimingReportFileEnvVar = "NETHERMIND_NEWPAYLOAD_TIMING_REPORT_FILE"; + private const int MaxConsoleScenarioCount = 10; + + private IWorldState _state; + private BranchProcessor _branchProcessor; + private BlockHeader _preBlockHeader; + private byte[] _rawJsonBytes; + private TimedTransactionProcessor _timedTransactionProcessor; + private IBlockCachePreWarmer _preWarmer; + private IDisposable _preWarmerLifetime; + private RecoverSignatures _recoverSignatures; + private ProcessingOptions _newPayloadProcessingOptions; + private static readonly JsonSerializerOptions s_jsonOptions = EthereumJsonSerializer.JsonOptions; + private static readonly EthereumEcdsa s_ecdsa = new(1); + private static readonly object s_timingFileLock = new(); + + // Timing breakdown accumulators (captured in ticks for sub-millisecond precision). + private long _jsonParseTicks; + private long _payloadDeserializeTicks; + private long _optionalParamsTicks; + private long _tryGetBlockTicks; + private long _headerValidationTicks; + private long _senderRecoveryTicks; + private long _blockProcessTicks; + private long _txExecutionTicks; + private int _iterationCount; + private int _txExecutionCount; + + private readonly long[] _senderRecoveryByTypeTicks = new long[256]; + private readonly int[] _senderRecoveryByTypeCount = new int[256]; + private readonly long[] _txExecutionByTypeTicks = new long[256]; + private readonly int[] _txExecutionByTypeCount = new int[256]; + + [ParamsSource(nameof(GetTestCases))] + public GasPayloadBenchmarks.TestCase Scenario { get; set; } + + public static IEnumerable GetTestCases() => GasPayloadBenchmarks.GetTestCases(); + + [GlobalSetup] + public void GlobalSetup() + { + IReleaseSpec pragueSpec = Prague.Instance; + ISpecProvider specProvider = new SingleReleaseSpecProvider(pragueSpec, 1, 1); + + PayloadLoader.EnsureGenesisInitialized(GasPayloadBenchmarks.s_genesisPath, pragueSpec); + + TestBlockhashProvider blockhashProvider = new(); + BlockBenchmarkHelper.BranchProcessingContext branchProcessingContext = BlockBenchmarkHelper.CreateBranchProcessingContext(specProvider, blockhashProvider); + _state = branchProcessingContext.State; + _preWarmer = branchProcessingContext.PreWarmer; + _preWarmerLifetime = branchProcessingContext.PreWarmerLifetime; + _preBlockHeader = BlockBenchmarkHelper.CreateGenesisHeader(); + _preBlockHeader.TotalDifficulty = UInt256.Zero; + ITransactionProcessor txProcessor = BlockBenchmarkHelper.CreateTransactionProcessor( + _state, + blockhashProvider, + specProvider, + branchProcessingContext.PreBlockCaches, + branchProcessingContext.CachePrecompiles); + _timedTransactionProcessor = new TimedTransactionProcessor(txProcessor, this); + _recoverSignatures = new RecoverSignatures(s_ecdsa, specProvider, LimboLogs.Instance); + + BlockBenchmarkHelper.ExecuteSetupPayload(_state, _timedTransactionProcessor, _preBlockHeader, Scenario, pragueSpec); + + ReceiptConfig receiptConfig = new(); + IReceiptStorage receiptStorage = BlockBenchmarkHelper.CreateReceiptStorage(receiptConfig); + _newPayloadProcessingOptions = BlockBenchmarkHelper.GetNewPayloadProcessingOptions(receiptConfig); + + BlockProcessor blockProcessor = BlockBenchmarkHelper.CreateBlockProcessor( + specProvider, _timedTransactionProcessor, _state, receiptStorage); + + _branchProcessor = new BranchProcessor( + blockProcessor, specProvider, _state, + new BeaconBlockRootHandler(_timedTransactionProcessor, _state), + blockhashProvider, LimboLogs.Instance, _preWarmer); + + // Store raw JSON bytes for deserialization in each iteration + string rawJson = PayloadLoader.ReadRawJson(Scenario.FilePath); + _rawJsonBytes = System.Text.Encoding.UTF8.GetBytes(rawJson); + + // Warm up: deserialize + process once, then verify correctness + Block block = DeserializeAndBuildBlock(); + Block[] result = _branchProcessor.Process( + _preBlockHeader, [block], + _newPayloadProcessingOptions, + NullBlockTracer.Instance); + PayloadLoader.VerifyProcessedBlock(result[0], Scenario.ToString(), Scenario.FilePath); + ResetTimingAccumulators(); + } + + [Benchmark] + public void ProcessBlock() + { + Block block = DeserializeAndBuildBlock(collectTiming: true); + + long processStart = Stopwatch.GetTimestamp(); + _branchProcessor.Process( + _preBlockHeader, [block], + _newPayloadProcessingOptions, + NullBlockTracer.Instance); + _blockProcessTicks += Stopwatch.GetTimestamp() - processStart; + _iterationCount++; + } + + private void AddTxExecutionTiming(TxType txType, long elapsedTicks) + { + _txExecutionTicks += elapsedTicks; + _txExecutionCount++; + int txTypeIndex = (int)txType; + _txExecutionByTypeTicks[txTypeIndex] += elapsedTicks; + _txExecutionByTypeCount[txTypeIndex]++; + } + + private void ResetTimingAccumulators() + { + _jsonParseTicks = 0; + _payloadDeserializeTicks = 0; + _optionalParamsTicks = 0; + _tryGetBlockTicks = 0; + _headerValidationTicks = 0; + _senderRecoveryTicks = 0; + _blockProcessTicks = 0; + _txExecutionTicks = 0; + _iterationCount = 0; + _txExecutionCount = 0; + Array.Clear(_senderRecoveryByTypeTicks, 0, _senderRecoveryByTypeTicks.Length); + Array.Clear(_senderRecoveryByTypeCount, 0, _senderRecoveryByTypeCount.Length); + Array.Clear(_txExecutionByTypeTicks, 0, _txExecutionByTypeTicks.Length); + Array.Clear(_txExecutionByTypeCount, 0, _txExecutionByTypeCount.Length); + } + + /// + /// Deserializes the raw JSON-RPC payload into an ExecutionPayloadV3, extracts additional + /// parameters (parentBeaconBlockRoot, executionRequests), and converts to a Block via TryGetBlock. + /// + private Block DeserializeAndBuildBlock(bool collectTiming = false) + { + long start = Stopwatch.GetTimestamp(); + using JsonDocument doc = JsonDocument.Parse(_rawJsonBytes); + JsonElement paramsArray = doc.RootElement.GetProperty("params"); + if (collectTiming) + { + _jsonParseTicks += Stopwatch.GetTimestamp() - start; + } + + // Deserialize ExecutionPayloadV3 from the first parameter + start = Stopwatch.GetTimestamp(); + ExecutionPayloadV3 payload = JsonSerializer.Deserialize( + paramsArray[0].GetRawText(), s_jsonOptions); + if (collectTiming) + { + _payloadDeserializeTicks += Stopwatch.GetTimestamp() - start; + } + + start = Stopwatch.GetTimestamp(); + // Extract parentBeaconBlockRoot (params[2]) — separate JSON-RPC parameter + if (paramsArray.GetArrayLength() > 2 && paramsArray[2].ValueKind == JsonValueKind.String) + { + payload.ParentBeaconBlockRoot = JsonSerializer.Deserialize( + paramsArray[2].GetRawText(), s_jsonOptions); + } + + // Extract executionRequests (params[3]) — Prague V4 parameter + if (paramsArray.GetArrayLength() > 3 && paramsArray[3].ValueKind == JsonValueKind.Array) + { + payload.ExecutionRequests = JsonSerializer.Deserialize( + paramsArray[3].GetRawText(), s_jsonOptions); + } + if (collectTiming) + { + _optionalParamsTicks += Stopwatch.GetTimestamp() - start; + } + + start = Stopwatch.GetTimestamp(); + BlockDecodingResult decodingResult = payload.TryGetBlock(); + if (decodingResult.Block is null) + { + throw new InvalidOperationException( + $"Failed to decode block from payload: {decodingResult.Error}"); + } + if (collectTiming) + { + _tryGetBlockTicks += Stopwatch.GetTimestamp() - start; + } + + // Recover sender addresses — in production this happens during block validation + Block block = decodingResult.Block; + start = Stopwatch.GetTimestamp(); + block.Header.IsPostMerge = true; + block.Header.TotalDifficulty = (_preBlockHeader.TotalDifficulty ?? UInt256.Zero) + block.Difficulty; + if (!HeaderValidator.ValidateHash(block.Header, out Hash256 actualHash)) + { + throw new InvalidOperationException( + $"Payload block hash mismatch. Declared: {block.Header.Hash}, Calculated: {actualHash}"); + } + if (collectTiming) + { + _headerValidationTicks += Stopwatch.GetTimestamp() - start; + } + + if (collectTiming) + { + start = Stopwatch.GetTimestamp(); + _recoverSignatures.RecoverData(block); + long elapsedTicks = Stopwatch.GetTimestamp() - start; + _senderRecoveryTicks += elapsedTicks; + AddSenderRecoveryTypeBreakdown(block, elapsedTicks); + } + else + { + _recoverSignatures.RecoverData(block); + } + + return block; + } + + private void AddSenderRecoveryTypeBreakdown(Block block, long elapsedTicks) + { + int txCount = block.Transactions.Length; + if (txCount == 0) + { + return; + } + + int[] countsPerType = new int[_senderRecoveryByTypeCount.Length]; + int firstTypeIndex = -1; + for (int i = 0; i < txCount; i++) + { + int txTypeIndex = (int)block.Transactions[i].Type; + if (firstTypeIndex == -1) + { + firstTypeIndex = txTypeIndex; + } + + countsPerType[txTypeIndex]++; + _senderRecoveryByTypeCount[txTypeIndex]++; + } + + long allocatedTicks = 0; + for (int txTypeIndex = 0; txTypeIndex < countsPerType.Length; txTypeIndex++) + { + int count = countsPerType[txTypeIndex]; + if (count == 0) + { + continue; + } + + long typeTicks = elapsedTicks * count / txCount; + _senderRecoveryByTypeTicks[txTypeIndex] += typeTicks; + allocatedTicks += typeTicks; + } + + if (firstTypeIndex >= 0 && allocatedTicks != elapsedTicks) + { + _senderRecoveryByTypeTicks[firstTypeIndex] += elapsedTicks - allocatedTicks; + } + } + + [GlobalCleanup] + public void GlobalCleanup() + { + if (_iterationCount > 0) + { + TimingBreakdownSummary summary = new( + Scenario?.ToString() ?? "", + _iterationCount, + _jsonParseTicks, + _payloadDeserializeTicks, + _optionalParamsTicks, + _tryGetBlockTicks, + _headerValidationTicks, + _senderRecoveryTicks, + _blockProcessTicks, + _txExecutionTicks, + _txExecutionCount, + (long[])_senderRecoveryByTypeTicks.Clone(), + (int[])_senderRecoveryByTypeCount.Clone(), + (long[])_txExecutionByTypeTicks.Clone(), + (int[])_txExecutionByTypeCount.Clone()); + AppendSummaryToFile(summary); + } + + _preWarmer?.ClearCaches(); + _preWarmerLifetime?.Dispose(); + _state = null; + _branchProcessor = null; + _rawJsonBytes = null; + _timedTransactionProcessor = null; + _preWarmer = null; + _preWarmerLifetime = null; + } + + public static void PrintFinalTimingBreakdown() + { + string timingFilePath = Environment.GetEnvironmentVariable(TimingFileEnvVar); + if (string.IsNullOrWhiteSpace(timingFilePath) || !File.Exists(timingFilePath)) + { + return; + } + + string[] lines = File.ReadAllLines(timingFilePath); + List summaries = new(lines.Length); + for (int i = 0; i < lines.Length; i++) + { + string line = lines[i]; + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + TimingBreakdownSummary summary = JsonSerializer.Deserialize(line, s_jsonOptions); + if (summary is not null) + { + summaries.Add(summary); + } + } + + if (summaries.Count == 0) + { + return; + } + + File.Delete(timingFilePath); + + string reportFilePath = ResolveTimingReportFilePath(); + string reportDirectory = Path.GetDirectoryName(reportFilePath); + if (!string.IsNullOrWhiteSpace(reportDirectory)) + { + Directory.CreateDirectory(reportDirectory); + } + + using (StreamWriter reportWriter = new(reportFilePath, false)) + { + WriteReport(reportWriter, summaries); + } + + if (summaries.Count <= MaxConsoleScenarioCount) + { + Console.WriteLine(); + WriteReport(Console.Out, summaries); + Console.WriteLine($"NewPayloadMeasured timing breakdown saved to: {reportFilePath}"); + } + else + { + Console.WriteLine($"NewPayloadMeasured timing breakdown saved to: {reportFilePath} (scenario count: {summaries.Count}, console output suppressed for runs with more than {MaxConsoleScenarioCount} scenarios)"); + } + } + + private static void WriteReport(TextWriter writer, List summaries) + { + writer.WriteLine("=== NewPayloadMeasured timing breakdown (final) ==="); + long totalJsonParseTicks = 0; + long totalPayloadDeserializeTicks = 0; + long totalOptionalParamsTicks = 0; + long totalTryGetBlockTicks = 0; + long totalHeaderValidationTicks = 0; + long totalSenderRecoveryTicks = 0; + long totalBlockProcessTicks = 0; + long totalTxExecutionTicks = 0; + int totalIterations = 0; + int totalTxExecutionCount = 0; + long[] totalSenderRecoveryByTypeTicks = new long[256]; + int[] totalSenderRecoveryByTypeCount = new int[256]; + long[] totalTxExecutionByTypeTicks = new long[256]; + int[] totalTxExecutionByTypeCount = new int[256]; + + for (int i = 0; i < summaries.Count; i++) + { + TimingBreakdownSummary summary = summaries[i]; + PrintSummary(writer, summary.Name, summary.Iterations, summary.JsonParseTicks, summary.PayloadDeserializeTicks, summary.OptionalParamsTicks, summary.TryGetBlockTicks, summary.HeaderValidationTicks, summary.SenderRecoveryTicks, summary.BlockProcessTicks, summary.TxExecutionTicks, summary.TxExecutionCount, summary.SenderRecoveryByTypeTicks, summary.SenderRecoveryByTypeCount, summary.TxExecutionByTypeTicks, summary.TxExecutionByTypeCount); + + totalJsonParseTicks += summary.JsonParseTicks; + totalPayloadDeserializeTicks += summary.PayloadDeserializeTicks; + totalOptionalParamsTicks += summary.OptionalParamsTicks; + totalTryGetBlockTicks += summary.TryGetBlockTicks; + totalHeaderValidationTicks += summary.HeaderValidationTicks; + totalSenderRecoveryTicks += summary.SenderRecoveryTicks; + totalBlockProcessTicks += summary.BlockProcessTicks; + totalTxExecutionTicks += summary.TxExecutionTicks; + totalIterations += summary.Iterations; + totalTxExecutionCount += summary.TxExecutionCount; + AddTypeBreakdown(totalSenderRecoveryByTypeTicks, summary.SenderRecoveryByTypeTicks); + AddTypeBreakdown(totalSenderRecoveryByTypeCount, summary.SenderRecoveryByTypeCount); + AddTypeBreakdown(totalTxExecutionByTypeTicks, summary.TxExecutionByTypeTicks); + AddTypeBreakdown(totalTxExecutionByTypeCount, summary.TxExecutionByTypeCount); + } + + if (summaries.Count > 1 && totalIterations > 0) + { + PrintSummary(writer, "ALL SCENARIOS", totalIterations, totalJsonParseTicks, totalPayloadDeserializeTicks, totalOptionalParamsTicks, totalTryGetBlockTicks, totalHeaderValidationTicks, totalSenderRecoveryTicks, totalBlockProcessTicks, totalTxExecutionTicks, totalTxExecutionCount, totalSenderRecoveryByTypeTicks, totalSenderRecoveryByTypeCount, totalTxExecutionByTypeTicks, totalTxExecutionByTypeCount); + } + } + + private static string ResolveTimingReportFilePath() + { + string configuredPath = Environment.GetEnvironmentVariable(TimingReportFileEnvVar); + if (!string.IsNullOrWhiteSpace(configuredPath)) + { + return configuredPath; + } + + return Path.Combine( + Path.GetTempPath(), + $"nethermind-newpayload-timing-breakdown-{DateTime.UtcNow:yyyyMMdd-HHmmss}-{Guid.NewGuid():N}.txt"); + } + + private static void AppendSummaryToFile(TimingBreakdownSummary summary) + { + string timingFilePath = Environment.GetEnvironmentVariable(TimingFileEnvVar); + if (string.IsNullOrWhiteSpace(timingFilePath)) + { + return; + } + + string line = JsonSerializer.Serialize(summary, s_jsonOptions); + lock (s_timingFileLock) + { + File.AppendAllText(timingFilePath, line + Environment.NewLine); + } + } + + private static void PrintSummary( + TextWriter writer, + string name, + int iterations, + long jsonParseTicks, + long payloadDeserializeTicks, + long optionalParamsTicks, + long tryGetBlockTicks, + long headerValidationTicks, + long senderRecoveryTicks, + long blockProcessTicks, + long txExecutionTicks, + int txExecutionCount, + long[] senderRecoveryByTypeTicks, + int[] senderRecoveryByTypeCount, + long[] txExecutionByTypeTicks, + int[] txExecutionByTypeCount) + { + double jsonParseMs = TicksToMs(jsonParseTicks); + double payloadDeserializeMs = TicksToMs(payloadDeserializeTicks); + double optionalParamsMs = TicksToMs(optionalParamsTicks); + double tryGetBlockMs = TicksToMs(tryGetBlockTicks); + double headerValidationMs = TicksToMs(headerValidationTicks); + double senderRecoveryMs = TicksToMs(senderRecoveryTicks); + double blockProcessMs = TicksToMs(blockProcessTicks); + double txExecutionMs = TicksToMs(txExecutionTicks); + double nonTxBlockMs = blockProcessMs - txExecutionMs; + if (nonTxBlockMs < 0) + { + nonTxBlockMs = 0; + } + + int senderRecoveryCount = GetTotalCount(senderRecoveryByTypeCount); + double totalMs = jsonParseMs + payloadDeserializeMs + optionalParamsMs + tryGetBlockMs + headerValidationMs + senderRecoveryMs + blockProcessMs; + + writer.WriteLine($"--- {name} ({iterations} iterations) ---"); + PrintLine(writer, "JSON parse", jsonParseMs, totalMs, iterations); + PrintLine(writer, "Payload deserialize", payloadDeserializeMs, totalMs, iterations); + PrintLine(writer, "Optional params", optionalParamsMs, totalMs, iterations); + PrintLine(writer, "TryGetBlock", tryGetBlockMs, totalMs, iterations); + PrintLine(writer, "Header validate", headerValidationMs, totalMs, iterations); + PrintLine(writer, "Sender recovery", senderRecoveryMs, totalMs, iterations); + PrintLine(writer, "Block processing", blockProcessMs, totalMs, iterations); + writer.WriteLine($" {"Total",-20} {totalMs,9:F3} ms avg {totalMs / iterations:F3} ms/iter"); + writer.WriteLine(" Block processing detail:"); + writer.WriteLine($" {"Tx execution",-18} {txExecutionMs,9:F3} ms ({GetShare(txExecutionMs, blockProcessMs),5:F1}% of block) avg {GetAverage(txExecutionMs, txExecutionCount):F3} ms/tx"); + writer.WriteLine($" {"Non-tx overhead",-18} {nonTxBlockMs,9:F3} ms ({GetShare(nonTxBlockMs, blockProcessMs),5:F1}% of block)"); + PrintTxTypeBreakdown(writer, "Tx execution by type", txExecutionByTypeTicks, txExecutionByTypeCount, txExecutionMs); + writer.WriteLine(" Sender recovery detail:"); + writer.WriteLine($" {"Recovered txs",-18} {senderRecoveryCount,9} tx"); + writer.WriteLine($" {"Avg per tx",-18} {GetAverage(senderRecoveryMs, senderRecoveryCount),9:F3} ms/tx"); + PrintTxTypeBreakdown(writer, "Sender recovery by type", senderRecoveryByTypeTicks, senderRecoveryByTypeCount, senderRecoveryMs); + } + + private static void PrintLine(TextWriter writer, string label, double stageMs, double totalMs, int iterations) + { + double share = totalMs > 0 ? (100.0 * stageMs / totalMs) : 0.0; + double avg = iterations > 0 ? (stageMs / iterations) : 0.0; + writer.WriteLine($" {label,-20} {stageMs,9:F3} ms ({share,5:F1}%) avg {avg:F3} ms/iter"); + } + + private static void PrintTxTypeBreakdown(TextWriter writer, string title, long[] ticksByType, int[] countByType, double stageTotalMs) + { + writer.WriteLine($" {title}:"); + for (int txTypeIndex = 0; txTypeIndex < countByType.Length; txTypeIndex++) + { + int count = countByType[txTypeIndex]; + if (count == 0) + { + continue; + } + + double ms = TicksToMs(ticksByType[txTypeIndex]); + double share = GetShare(ms, stageTotalMs); + double avg = GetAverage(ms, count); + writer.WriteLine($" {FormatTxType((TxType)txTypeIndex),-16} {ms,9:F3} ms ({share,5:F1}%) avg {avg:F3} ms/tx count {count}"); + } + } + + private static string FormatTxType(TxType txType) + { + return txType switch + { + TxType.Legacy => "Legacy", + TxType.AccessList => "AccessList", + TxType.EIP1559 => "EIP1559", + TxType.Blob => "Blob", + TxType.SetCode => "SetCode", + TxType.DepositTx => "DepositTx", + _ => $"Type(0x{(byte)txType:X2})" + }; + } + + private static void AddTypeBreakdown(long[] destination, long[] source) + { + int length = destination.Length < source.Length ? destination.Length : source.Length; + for (int i = 0; i < length; i++) + { + destination[i] += source[i]; + } + } + + private static void AddTypeBreakdown(int[] destination, int[] source) + { + int length = destination.Length < source.Length ? destination.Length : source.Length; + for (int i = 0; i < length; i++) + { + destination[i] += source[i]; + } + } + + private static int GetTotalCount(int[] countByType) + { + int total = 0; + for (int i = 0; i < countByType.Length; i++) + { + total += countByType[i]; + } + + return total; + } + + private static double GetShare(double part, double whole) + { + return whole > 0 ? (100.0 * part / whole) : 0.0; + } + + private static double GetAverage(double totalMs, int count) + { + return count > 0 ? (totalMs / count) : 0.0; + } + + private static double TicksToMs(long ticks) => ticks * 1000.0 / Stopwatch.Frequency; + + private sealed class TimedTransactionProcessor(ITransactionProcessor inner, GasNewPayloadMeasuredBenchmarks owner) : ITransactionProcessor + { + public TransactionResult Execute(Transaction transaction, ITxTracer txTracer) + { + long start = Stopwatch.GetTimestamp(); + TransactionResult result = inner.Execute(transaction, txTracer); + long elapsedTicks = Stopwatch.GetTimestamp() - start; + owner.AddTxExecutionTiming(transaction.Type, elapsedTicks); + return result; + } + + public TransactionResult CallAndRestore(Transaction transaction, ITxTracer txTracer) => + inner.CallAndRestore(transaction, txTracer); + + public TransactionResult BuildUp(Transaction transaction, ITxTracer txTracer) => + inner.BuildUp(transaction, txTracer); + + public TransactionResult Trace(Transaction transaction, ITxTracer txTracer) => + inner.Trace(transaction, txTracer); + + public TransactionResult Warmup(Transaction transaction, ITxTracer txTracer) => + inner.Warmup(transaction, txTracer); + + public void SetBlockExecutionContext(BlockHeader blockHeader) => + inner.SetBlockExecutionContext(blockHeader); + + public void SetBlockExecutionContext(in BlockExecutionContext blockExecutionContext) => + inner.SetBlockExecutionContext(in blockExecutionContext); + } + + private sealed record TimingBreakdownSummary + { + public TimingBreakdownSummary( + string name, + int iterations, + long jsonParseTicks, + long payloadDeserializeTicks, + long optionalParamsTicks, + long tryGetBlockTicks, + long headerValidationTicks, + long senderRecoveryTicks, + long blockProcessTicks, + long txExecutionTicks, + int txExecutionCount, + long[] senderRecoveryByTypeTicks, + int[] senderRecoveryByTypeCount, + long[] txExecutionByTypeTicks, + int[] txExecutionByTypeCount) + { + Name = name; + Iterations = iterations; + JsonParseTicks = jsonParseTicks; + PayloadDeserializeTicks = payloadDeserializeTicks; + OptionalParamsTicks = optionalParamsTicks; + TryGetBlockTicks = tryGetBlockTicks; + HeaderValidationTicks = headerValidationTicks; + SenderRecoveryTicks = senderRecoveryTicks; + BlockProcessTicks = blockProcessTicks; + TxExecutionTicks = txExecutionTicks; + TxExecutionCount = txExecutionCount; + SenderRecoveryByTypeTicks = senderRecoveryByTypeTicks; + SenderRecoveryByTypeCount = senderRecoveryByTypeCount; + TxExecutionByTypeTicks = txExecutionByTypeTicks; + TxExecutionByTypeCount = txExecutionByTypeCount; + } + + public string Name { get; init; } + public int Iterations { get; init; } + public long JsonParseTicks { get; init; } + public long PayloadDeserializeTicks { get; init; } + public long OptionalParamsTicks { get; init; } + public long TryGetBlockTicks { get; init; } + public long HeaderValidationTicks { get; init; } + public long SenderRecoveryTicks { get; init; } + public long BlockProcessTicks { get; init; } + public long TxExecutionTicks { get; init; } + public int TxExecutionCount { get; init; } + public long[] SenderRecoveryByTypeTicks { get; init; } + public int[] SenderRecoveryByTypeCount { get; init; } + public long[] TxExecutionByTypeTicks { get; init; } + public int[] TxExecutionByTypeCount { get; init; } + } +} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasPayloadBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasPayloadBenchmarks.cs new file mode 100644 index 000000000000..deaa5deff01f --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasPayloadBenchmarks.cs @@ -0,0 +1,258 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using BenchmarkDotNet.Attributes; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Blockchain; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Logging; +using Nethermind.Specs; +using Nethermind.Specs.Forks; + +namespace Nethermind.Evm.Benchmark.GasBenchmarks; + +/// +/// Benchmarks that replay gas-benchmark payload files via TransactionProcessor.BuildUp. +/// This models block-building style execution (uncommitted per-transaction flow). +/// Test cases are auto-discovered from the gas-benchmarks submodule. +/// +[Config(typeof(GasBenchmarkConfig))] +public class GasPayloadBenchmarks +{ + private static readonly string s_repoRoot = FindRepoRoot(); + private static readonly string s_gasBenchmarksRoot = Path.Combine(s_repoRoot, "tools", "gas-benchmarks"); + private static readonly string s_testingDir = Path.Combine(s_gasBenchmarksRoot, "eest_tests", "testing"); + private static readonly string s_setupDir = Path.Combine(s_gasBenchmarksRoot, "eest_tests", "setup"); + internal static readonly string s_genesisPath = Path.Combine(s_gasBenchmarksRoot, "scripts", "genesisfiles", "nethermind", "zkevmgenesis.json"); + private static bool s_missingSubmoduleWarned; + + private IWorldState _state; + private IDisposable _stateScope; + private ITransactionProcessor _txProcessor; + private Transaction[] _testTransactions; + private BlockHeader _testHeader; + + [ParamsSource(nameof(GetTestCases))] + public TestCase Scenario { get; set; } + + [GlobalSetup] + public void GlobalSetup() + { + IReleaseSpec pragueSpec = Prague.Instance; + ISpecProvider specProvider = new SingleReleaseSpecProvider(pragueSpec, 1, 1); + + // Load genesis state once (shared across all test cases) + PayloadLoader.EnsureGenesisInitialized(s_genesisPath, pragueSpec); + + // Create a fresh WorldState and open scope at genesis + _state = PayloadLoader.CreateWorldState(); + BlockHeader genesisBlock = new(Keccak.Zero, Keccak.OfAnEmptySequenceRlp, Address.Zero, 0, 0, 0, 0, Array.Empty()) + { + StateRoot = PayloadLoader.GenesisStateRoot + }; + _stateScope = _state.BeginScope(genesisBlock); + + // Set up EVM infrastructure + TestBlockhashProvider blockhashProvider = new(); + EthereumCodeInfoRepository codeInfoRepo = new(_state); + EthereumVirtualMachine vm = new(blockhashProvider, specProvider, LimboLogs.Instance); + + _txProcessor = new EthereumTransactionProcessor( + BlobBaseFeeCalculator.Instance, + specProvider, + _state, + vm, + codeInfoRepo, + LimboLogs.Instance); + + // Execute setup payload if one exists for this scenario + string setupFile = FindSetupFile(Scenario.FileName); + if (setupFile is not null) + { + (BlockHeader setupHeader, Transaction[] setupTxs) = PayloadLoader.LoadPayload(setupFile); + _txProcessor.SetBlockExecutionContext(setupHeader); + for (int i = 0; i < setupTxs.Length; i++) + { + _txProcessor.Execute(setupTxs[i], NullTxTracer.Instance); + } + _state.Commit(pragueSpec); + } + + // Parse the test payload + (BlockHeader header, Transaction[] txs) = PayloadLoader.LoadPayload(Scenario.FilePath); + _testHeader = header; + _testTransactions = txs; + _txProcessor.SetBlockExecutionContext(_testHeader); + + // Warm up: execute once via CallAndRestore to prime code caches + for (int i = 0; i < _testTransactions.Length; i++) + { + _txProcessor.CallAndRestore(_testTransactions[i], NullTxTracer.Instance); + } + } + + [Benchmark] + public void ExecutePayload() + { + for (int i = 0; i < _testTransactions.Length; i++) + { + _txProcessor.BuildUp(_testTransactions[i], NullTxTracer.Instance); + } + + _state.Reset(); + } + + [GlobalCleanup] + public void GlobalCleanup() + { + _stateScope?.Dispose(); + _stateScope = null; + _state = null; + _txProcessor = null; + _testTransactions = null; + } + + /// + /// Auto-discovers test cases from the gas-benchmarks testing directory. + /// + public static IEnumerable GetTestCases() + { + if (!Directory.Exists(s_testingDir)) + { + if (!s_missingSubmoduleWarned) + { + s_missingSubmoduleWarned = true; + string hint = "\u001b[33m[GasPayloadBenchmarks] No test cases found.\u001b[0m Initialize the gas-benchmarks submodule:\n" + + " \u001b[36mgit lfs install && git submodule update --init tools/gas-benchmarks\u001b[0m"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + hint += "\n On Windows, you may also need: \u001b[36mgit config --global core.longpaths true\u001b[0m"; + Console.Error.WriteLine(hint); + } + + yield break; + } + + string[] dirs = Directory.GetDirectories(s_testingDir); + Array.Sort(dirs); + + int globalIndex = 0; + int chunkIndex = GasBenchmarkConfig.ChunkIndex; + int chunkTotal = GasBenchmarkConfig.ChunkTotal; + + for (int d = 0; d < dirs.Length; d++) + { + string[] files = Directory.GetFiles(dirs[d], "*.txt"); + Array.Sort(files); + for (int f = 0; f < files.Length; f++) + { + if (chunkTotal > 0 && (globalIndex % chunkTotal) != (chunkIndex - 1)) + { + globalIndex++; + continue; + } + globalIndex++; + yield return new TestCase(files[f]); + } + } + } + + /// + /// Finds the setup payload file matching a given test filename, if any. + /// Setup files share the same filename as test files but live under setup/ directories. + /// + internal static string FindSetupFile(string testFileName) + { + if (!Directory.Exists(s_setupDir)) + return null; + + string[] setupDirs = Directory.GetDirectories(s_setupDir); + for (int i = 0; i < setupDirs.Length; i++) + { + string candidate = Path.Combine(setupDirs[i], testFileName); + if (File.Exists(candidate)) + return candidate; + } + + return null; + } + + private static string FindRepoRoot() + { + string dir = AppDomain.CurrentDomain.BaseDirectory; + while (dir is not null) + { + if (Directory.Exists(Path.Combine(dir, ".git"))) + return dir; + dir = Directory.GetParent(dir)?.FullName; + } + + throw new DirectoryNotFoundException("Could not find repository root (.git directory)."); + } + + /// + /// Represents a single gas-benchmark test case with a short display name. + /// + public sealed class TestCase + { + public string FilePath { get; } + public string FileName { get; } + public string DisplayName { get; } + + public TestCase(string filePath) + { + FilePath = filePath; + FileName = Path.GetFileName(filePath); + DisplayName = ExtractShortName(FileName); + } + + public override string ToString() => DisplayName; + + /// + /// Extracts a short benchmark name from the long filename. + /// Input: tests_benchmark_compute_instruction_test_foo.py__test_bar[fork_Prague-benchmark-blockchain_test_engine_x-param1-param2]-gas-value_100M.txt + /// Output: bar[param1-param2] + /// + private static string ExtractShortName(string fileName) + { + // Find test method name after "__test_" + int testMethodStart = fileName.IndexOf("__test_", StringComparison.Ordinal); + if (testMethodStart < 0) + return fileName; + + string afterTestPrefix = fileName.Substring(testMethodStart + 7); + + // Remove the "-gas-value_*" suffix + int gasValueIdx = afterTestPrefix.IndexOf("-gas-value_", StringComparison.Ordinal); + if (gasValueIdx >= 0) + afterTestPrefix = afterTestPrefix.Substring(0, gasValueIdx); + + // Remove the "[fork_Prague-benchmark-blockchain_test_engine_x-" prefix from params + const string forkPrefix = "[fork_Prague-benchmark-blockchain_test_engine_x-"; + int forkIdx = afterTestPrefix.IndexOf(forkPrefix, StringComparison.Ordinal); + if (forkIdx >= 0) + { + string methodName = afterTestPrefix.Substring(0, forkIdx); + string paramsStr = afterTestPrefix.Substring(forkIdx + forkPrefix.Length); + + // Remove trailing ']' + if (paramsStr.EndsWith("]")) + paramsStr = paramsStr.Substring(0, paramsStr.Length - 1); + + return string.IsNullOrEmpty(paramsStr) + ? methodName + : methodName + "[" + paramsStr + "]"; + } + + return afterTestPrefix; + } + } +} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasPayloadExecuteBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasPayloadExecuteBenchmarks.cs new file mode 100644 index 000000000000..864a0cd72358 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasPayloadExecuteBenchmarks.cs @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using BenchmarkDotNet.Attributes; +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Logging; +using Nethermind.Specs; +using Nethermind.Specs.Forks; + +namespace Nethermind.Evm.Benchmark.GasBenchmarks; + +/// +/// Benchmarks that replay gas-benchmark payload files via TransactionProcessor.Execute. +/// This follows the validation/import execution path (as used for normal block processing), +/// while keeping the harness focused on transaction execution itself. +/// +[Config(typeof(GasBenchmarkConfig))] +public class GasPayloadExecuteBenchmarks +{ + private IWorldState _state; + private IDisposable _stateScope; + private ITransactionProcessor _txProcessor; + private Transaction[] _testTransactions; + private BlockHeader _testHeaderTemplate; + + [ParamsSource(nameof(GetTestCases))] + public GasPayloadBenchmarks.TestCase Scenario { get; set; } + + public static IEnumerable GetTestCases() => GasPayloadBenchmarks.GetTestCases(); + + [GlobalSetup] + public void GlobalSetup() + { + IReleaseSpec pragueSpec = Prague.Instance; + ISpecProvider specProvider = new SingleReleaseSpecProvider(pragueSpec, 1, 1); + + // Load genesis state once (shared across all test cases) + PayloadLoader.EnsureGenesisInitialized(GasPayloadBenchmarks.s_genesisPath, pragueSpec); + + // Create a fresh WorldState and open scope at genesis + _state = PayloadLoader.CreateWorldState(); + BlockHeader genesisBlock = new(Keccak.Zero, Keccak.OfAnEmptySequenceRlp, Address.Zero, 0, 0, 0, 0, Array.Empty()) + { + StateRoot = PayloadLoader.GenesisStateRoot + }; + _stateScope = _state.BeginScope(genesisBlock); + + // Set up EVM infrastructure + TestBlockhashProvider blockhashProvider = new(); + EthereumCodeInfoRepository codeInfoRepo = new(_state); + EthereumVirtualMachine vm = new(blockhashProvider, specProvider, LimboLogs.Instance); + + _txProcessor = new EthereumTransactionProcessor( + BlobBaseFeeCalculator.Instance, + specProvider, + _state, + vm, + codeInfoRepo, + LimboLogs.Instance); + + // Execute setup payload if one exists for this scenario + string setupFile = GasPayloadBenchmarks.FindSetupFile(Scenario.FileName); + if (setupFile is not null) + { + (BlockHeader setupHeader, Transaction[] setupTxs) = PayloadLoader.LoadPayload(setupFile); + _txProcessor.SetBlockExecutionContext(setupHeader); + for (int i = 0; i < setupTxs.Length; i++) + { + _txProcessor.Execute(setupTxs[i], NullTxTracer.Instance); + } + _state.Commit(pragueSpec); + } + + // Parse the test payload + (BlockHeader header, Transaction[] txs) = PayloadLoader.LoadPayload(Scenario.FilePath); + _testHeaderTemplate = header; + _testTransactions = txs; + + // Warm up once on Execute path, then restore state. + ExecutePayloadCore(); + _state.Reset(); + } + + [Benchmark] + public void ExecutePayload() + { + ExecutePayloadCore(); + _state.Reset(); + } + + private void ExecutePayloadCore() + { + BlockHeader executionHeader = _testHeaderTemplate.Clone(); + executionHeader.GasUsed = 0; + _txProcessor.SetBlockExecutionContext(executionHeader); + for (int i = 0; i < _testTransactions.Length; i++) + { + _txProcessor.Execute(_testTransactions[i], NullTxTracer.Instance); + } + } + + [GlobalCleanup] + public void GlobalCleanup() + { + _stateScope?.Dispose(); + _stateScope = null; + _state = null; + _txProcessor = null; + _testTransactions = null; + _testHeaderTemplate = null; + } +} + diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/PayloadLoader.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/PayloadLoader.cs new file mode 100644 index 000000000000..7929ee09213c --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/PayloadLoader.cs @@ -0,0 +1,457 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text.Json; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Core.Test; +using Nethermind.Core.Test.Db; +using Nethermind.Crypto; +using Nethermind.Db; +using Nethermind.Evm.State; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; +using Nethermind.State; +using Nethermind.State.Proofs; +using Nethermind.Trie; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Evm.Benchmark.GasBenchmarks; + +/// +/// Loads engine_newPayloadV4 payload files and genesis state for gas benchmarks. +/// +public static class PayloadLoader +{ + private static readonly object s_genesisLock = new(); + private static IDbProvider s_dbProvider; + private static TrieStore s_trieStore; + private static IDb s_codeDb; + private static Hash256 s_genesisStateRoot; + private static bool s_genesisInitialized; + + /// + /// Parses an engine_newPayloadV4 payload file and returns a BlockHeader + decoded transactions. + /// + public static (BlockHeader Header, Transaction[] Transactions) LoadPayload(string filePath) + { + string firstLine; + using (StreamReader reader = new(filePath)) + { + firstLine = reader.ReadLine(); + } + + using JsonDocument doc = JsonDocument.Parse(firstLine); + JsonElement paramsArray = doc.RootElement.GetProperty("params"); + JsonElement payload = paramsArray[0]; + + long blockNumber = ParseHexLong(payload, "blockNumber"); + long gasLimit = ParseHexLong(payload, "gasLimit"); + long gasUsed = ParseHexLong(payload, "gasUsed"); + ulong timestamp = ParseHexULong(payload, "timestamp"); + UInt256 baseFeePerGas = ParseHexUInt256(payload, "baseFeePerGas"); + Hash256 parentHash = new(Bytes.FromHexString(payload.GetProperty("parentHash").GetString())); + Hash256 prevRandao = new(Bytes.FromHexString(payload.GetProperty("prevRandao").GetString())); + Address beneficiary = new(payload.GetProperty("feeRecipient").GetString()); + + BlockHeader header = new( + parentHash, + Keccak.OfAnEmptySequenceRlp, + beneficiary, + UInt256.Zero, + blockNumber, + gasLimit, + timestamp, + Array.Empty()) + { + GasUsed = gasUsed, + BaseFeePerGas = baseFeePerGas, + MixHash = prevRandao, + IsPostMerge = true, + Hash = new Hash256(Bytes.FromHexString(payload.GetProperty("blockHash").GetString())) + }; + + JsonElement txsArray = payload.GetProperty("transactions"); + int txCount = txsArray.GetArrayLength(); + Transaction[] transactions = new Transaction[txCount]; + + EthereumEcdsa ecdsa = new(1); + for (int i = 0; i < txCount; i++) + { + byte[] rlpBytes = Bytes.FromHexString(txsArray[i].GetString()); + transactions[i] = TxDecoder.Instance.Decode(rlpBytes); + transactions[i].SenderAddress = ecdsa.RecoverAddress(transactions[i]); + } + + return (header, transactions); + } + + /// + /// Parses an engine_newPayloadV4 payload file and returns a full Block suitable for BlockProcessor. + /// Includes withdrawals, TxRoot, WithdrawalsRoot, and other block-level fields. + /// + public static Block LoadBlock(string filePath) + { + string firstLine; + using (StreamReader reader = new(filePath)) + { + firstLine = reader.ReadLine(); + } + + using JsonDocument doc = JsonDocument.Parse(firstLine); + JsonElement paramsArray = doc.RootElement.GetProperty("params"); + JsonElement payload = paramsArray[0]; + + long blockNumber = ParseHexLong(payload, "blockNumber"); + long gasLimit = ParseHexLong(payload, "gasLimit"); + long gasUsed = ParseHexLong(payload, "gasUsed"); + ulong timestamp = ParseHexULong(payload, "timestamp"); + UInt256 baseFeePerGas = ParseHexUInt256(payload, "baseFeePerGas"); + Hash256 parentHash = new(Bytes.FromHexString(payload.GetProperty("parentHash").GetString())); + Hash256 prevRandao = new(Bytes.FromHexString(payload.GetProperty("prevRandao").GetString())); + Address beneficiary = new(payload.GetProperty("feeRecipient").GetString()); + + // Parse blob gas fields (EIP-4844) + ulong? blobGasUsed = payload.TryGetProperty("blobGasUsed", out JsonElement blobGasEl) ? ParseHexULong(blobGasEl.GetString()) : null; + ulong? excessBlobGas = payload.TryGetProperty("excessBlobGas", out JsonElement excessEl) ? ParseHexULong(excessEl.GetString()) : null; + + // Parse parent beacon block root (EIP-4788) — separate JSON-RPC param, not inside payload object + Hash256 parentBeaconBlockRoot = null; + if (paramsArray.GetArrayLength() > 2 && paramsArray[2].ValueKind == JsonValueKind.String) + { + string beaconRootHex = paramsArray[2].GetString(); + if (beaconRootHex is not null && beaconRootHex.Length > 2) + parentBeaconBlockRoot = new Hash256(Bytes.FromHexString(beaconRootHex)); + } + + // Decode transactions + JsonElement txsArray = payload.GetProperty("transactions"); + int txCount = txsArray.GetArrayLength(); + Transaction[] transactions = new Transaction[txCount]; + + EthereumEcdsa ecdsa = new(1); + for (int i = 0; i < txCount; i++) + { + byte[] rlpBytes = Bytes.FromHexString(txsArray[i].GetString()); + transactions[i] = TxDecoder.Instance.Decode(rlpBytes); + transactions[i].SenderAddress = ecdsa.RecoverAddress(transactions[i]); + } + + // Parse withdrawals (EIP-4895) + Withdrawal[] withdrawals = null; + if (payload.TryGetProperty("withdrawals", out JsonElement withdrawalsEl) && withdrawalsEl.ValueKind == JsonValueKind.Array) + { + int wCount = withdrawalsEl.GetArrayLength(); + withdrawals = new Withdrawal[wCount]; + for (int i = 0; i < wCount; i++) + { + JsonElement w = withdrawalsEl[i]; + withdrawals[i] = new Withdrawal + { + Index = ParseHexULong(w, "index"), + ValidatorIndex = ParseHexULong(w, "validatorIndex"), + Address = new Address(w.GetProperty("address").GetString()), + AmountInGwei = ParseHexULong(w, "amount"), + }; + } + } + + // Build header with full block-level fields + BlockHeader header = new( + parentHash, + Keccak.OfAnEmptySequenceRlp, + beneficiary, + UInt256.Zero, + blockNumber, + gasLimit, + timestamp, + Array.Empty(), + blobGasUsed, + excessBlobGas) + { + GasUsed = gasUsed, + BaseFeePerGas = baseFeePerGas, + MixHash = prevRandao, + IsPostMerge = true, + Hash = new Hash256(Bytes.FromHexString(payload.GetProperty("blockHash").GetString())), + ParentBeaconBlockRoot = parentBeaconBlockRoot, + TxRoot = TxTrie.CalculateRoot(transactions), + WithdrawalsRoot = withdrawals is not null ? WithdrawalTrie.CalculateRoot(withdrawals) : null, + }; + + return new Block(header, new BlockBody(transactions, Array.Empty(), withdrawals)); + } + + /// + /// Ensures the genesis state is loaded from the chainspec file into a shared TrieStore. + /// Subsequent calls are no-ops. Call CreateWorldState() to get a WorldState rooted at genesis. + /// + public static void EnsureGenesisInitialized(string genesisPath, IReleaseSpec spec) + { + if (s_genesisInitialized) return; + + lock (s_genesisLock) + { + if (s_genesisInitialized) return; + + s_dbProvider = TestMemDbProvider.Init(); + PruningConfig pruningConfig = new(); + TestFinalizedStateProvider finalizedStateProvider = new(pruningConfig.PruningBoundary); + + s_trieStore = new TrieStore( + new NodeStorage(s_dbProvider.StateDb), + No.Pruning, + Persist.EveryBlock, + finalizedStateProvider, + pruningConfig, + LimboLogs.Instance); + + finalizedStateProvider.TrieStore = s_trieStore; + s_codeDb = s_dbProvider.CodeDb; + + WorldState state = new( + new TrieStoreScopeProvider(s_trieStore, s_codeDb, LimboLogs.Instance), + LimboLogs.Instance); + + using (state.BeginScope(IWorldState.PreGenesis)) + { + LoadGenesisAccounts(state, genesisPath, spec); + state.Commit(spec); + state.CommitTree(0); + s_genesisStateRoot = state.StateRoot; + } + + s_genesisInitialized = true; + } + } + + public static Hash256 GenesisStateRoot + { + get + { + if (!s_genesisInitialized) + throw new InvalidOperationException("Genesis not initialized."); + return s_genesisStateRoot; + } + } + + /// + /// Creates a new WorldState backed by the shared TrieStore (which contains the genesis trie). + /// Caller must call BeginScope with a BlockHeader whose StateRoot is GenesisStateRoot. + /// + public static IWorldState CreateWorldState( + NodeStorageCache nodeStorageCache = null, + PreBlockCaches preBlockCaches = null, + bool populatePreBlockCache = true) + { + if (!s_genesisInitialized) + throw new InvalidOperationException("Genesis not initialized. Call EnsureGenesisInitialized first."); + + ITrieStore trieStore = s_trieStore; + if (nodeStorageCache is not null) + { + trieStore = new PreCachedTrieStore(trieStore, nodeStorageCache); + } + + IWorldStateScopeProvider scopeProvider = new TrieStoreScopeProvider(trieStore, s_codeDb, LimboLogs.Instance); + if (preBlockCaches is not null) + { + scopeProvider = new PrewarmerScopeProvider(scopeProvider, preBlockCaches, populatePreBlockCache); + } + + return new WorldState(scopeProvider, LimboLogs.Instance); + } + + public static IWorldStateManager CreateWorldStateManager(NodeStorageCache nodeStorageCache = null) + { + if (!s_genesisInitialized) + throw new InvalidOperationException("Genesis not initialized. Call EnsureGenesisInitialized first."); + + ITrieStore trieStore = s_trieStore; + if (nodeStorageCache is not null) + { + trieStore = new PreCachedTrieStore(trieStore, nodeStorageCache); + } + + IWorldStateScopeProvider scopeProvider = new TrieStoreScopeProvider(trieStore, s_codeDb, LimboLogs.Instance); + return new WorldStateManager(scopeProvider, s_trieStore, s_dbProvider, LimboLogs.Instance); + } + + private static void LoadGenesisAccounts(IWorldState state, string genesisPath, IReleaseSpec spec) + { + if (!File.Exists(genesisPath)) + { + string message = $"Genesis file not found: {genesisPath}\n" + + "Make sure the gas-benchmarks submodule is initialized:\n" + + " git lfs install && git submodule update --init tools/gas-benchmarks"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + message += "\n On Windows, you may also need: git config --global core.longpaths true"; + throw new FileNotFoundException(message); + } + + using FileStream fs = File.OpenRead(genesisPath); + + // Detect Git LFS pointer (starts with "version https://git-lfs") + byte[] header = new byte[8]; + int read = fs.Read(header, 0, header.Length); + fs.Position = 0; + + if (read >= 7 && header[0] == (byte)'v' && header[1] == (byte)'e' && header[2] == (byte)'r') + { + throw new InvalidOperationException( + $"Genesis file appears to be a Git LFS pointer: {genesisPath}\n" + + "Git LFS was not installed when the submodule was cloned. Fix with:\n" + + " git lfs install && cd tools/gas-benchmarks && git lfs pull"); + } + + using JsonDocument doc = JsonDocument.Parse(fs); + JsonElement accounts = doc.RootElement.GetProperty("accounts"); + + foreach (JsonProperty entry in accounts.EnumerateObject()) + { + // Skip builtin precompile definitions + if (entry.Value.TryGetProperty("builtin", out _)) + continue; + + Address address = new(entry.Name); + + UInt256 balance = UInt256.Zero; + if (entry.Value.TryGetProperty("balance", out JsonElement balanceEl)) + UInt256.TryParse(balanceEl.GetString(), out balance); + + state.CreateAccount(address, balance); + + if (entry.Value.TryGetProperty("nonce", out JsonElement nonceEl)) + { + UInt256 nonce = ParseHexUInt256(nonceEl.GetString()); + if (nonce > UInt256.Zero) + state.IncrementNonce(address, nonce); + } + + if (entry.Value.TryGetProperty("code", out JsonElement codeEl)) + { + string codeHex = codeEl.GetString(); + if (codeHex is not null && codeHex.Length > 2) + { + byte[] code = Bytes.FromHexString(codeHex); + ValueHash256 codeHash = ValueKeccak.Compute(code); + state.InsertCode(address, in codeHash, code, spec, isGenesis: true); + } + } + + if (entry.Value.TryGetProperty("storage", out JsonElement storageEl)) + { + foreach (JsonProperty storageEntry in storageEl.EnumerateObject()) + { + UInt256 slot = ParseHexUInt256(storageEntry.Name); + byte[] value = Bytes.FromHexString(storageEntry.Value.GetString()); + state.Set(new StorageCell(address, slot), value); + } + } + } + } + + /// + /// Reads the raw JSON-RPC line from a payload file. Used by NewPayload mode to measure deserialization. + /// + public static string ReadRawJson(string filePath) + { + using StreamReader reader = new(filePath); + return reader.ReadLine(); + } + + /// + /// Parses expected stateRoot and blockHash from a payload file for verification. + /// + public static (Hash256 StateRoot, Hash256 BlockHash) ParseExpectedHashes(string filePath) + { + string firstLine = ReadRawJson(filePath); + + using JsonDocument doc = JsonDocument.Parse(firstLine); + JsonElement payload = doc.RootElement.GetProperty("params")[0]; + + Hash256 stateRoot = null; + if (payload.TryGetProperty("stateRoot", out JsonElement stateRootEl)) + { + string hex = stateRootEl.GetString(); + if (hex is not null && hex.Length > 2) + stateRoot = new Hash256(Bytes.FromHexString(hex)); + } + + Hash256 blockHash = null; + if (payload.TryGetProperty("blockHash", out JsonElement blockHashEl)) + { + string hex = blockHashEl.GetString(); + if (hex is not null && hex.Length > 2) + blockHash = new Hash256(Bytes.FromHexString(hex)); + } + + return (stateRoot, blockHash); + } + + /// + /// Verifies a processed block's state root and block hash against expected values from the payload. + /// + public static void VerifyProcessedBlock(Block processedBlock, string scenarioName, string filePath) + { + (Hash256 expectedStateRoot, Hash256 expectedBlockHash) = ParseExpectedHashes(filePath); + + if (expectedStateRoot is not null && processedBlock.Header.StateRoot != expectedStateRoot) + { + throw new InvalidOperationException( + $"State root mismatch for {scenarioName}!\n" + + $" Expected: {expectedStateRoot}\n" + + $" Computed: {processedBlock.Header.StateRoot}\n" + + "Block processing produced incorrect results."); + } + + if (expectedBlockHash is not null && processedBlock.Header.Hash != expectedBlockHash) + { + throw new InvalidOperationException( + $"Block hash mismatch for {scenarioName}!\n" + + $" Expected: {expectedBlockHash}\n" + + $" Computed: {processedBlock.Header.Hash}\n" + + $" StateRoot match: {processedBlock.Header.StateRoot == expectedStateRoot}\n" + + "Block processing produced a different block hash — some header field differs."); + } + } + + private static long ParseHexLong(JsonElement parent, string propertyName) + { + string hex = parent.GetProperty(propertyName).GetString(); + return Convert.ToInt64(hex, 16); + } + + private static ulong ParseHexULong(JsonElement parent, string propertyName) + { + string hex = parent.GetProperty(propertyName).GetString(); + return ParseHexULong(hex); + } + + private static ulong ParseHexULong(string hex) + { + return Convert.ToUInt64(hex, 16); + } + + private static UInt256 ParseHexUInt256(JsonElement parent, string propertyName) + { + string hex = parent.GetProperty(propertyName).GetString(); + return ParseHexUInt256(hex); + } + + private static UInt256 ParseHexUInt256(string hex) + { + if (hex is null || hex == "0x" || hex == "0x0" || hex == "0x00") + return UInt256.Zero; + + ReadOnlySpan hexSpan = hex.AsSpan(2); + UInt256.TryParse(hexSpan, System.Globalization.NumberStyles.HexNumber, null, out UInt256 result); + return result; + } +} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/Nethermind.Evm.Benchmark.csproj b/src/Nethermind/Nethermind.Evm.Benchmark/Nethermind.Evm.Benchmark.csproj index 3dad15dc2826..fc3ed5d184b2 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/Nethermind.Evm.Benchmark.csproj +++ b/src/Nethermind/Nethermind.Evm.Benchmark/Nethermind.Evm.Benchmark.csproj @@ -1,12 +1,20 @@ + + Exe + false + + + + + diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/Program.cs b/src/Nethermind/Nethermind.Evm.Benchmark/Program.cs new file mode 100644 index 000000000000..37708d2ce2d8 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Benchmark/Program.cs @@ -0,0 +1,347 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Diagnostics; +using System.IO; +using BenchmarkDotNet.Running; +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.Evm.Benchmark; +using Nethermind.Evm.Benchmark.GasBenchmarks; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Logging; +using Nethermind.Specs; +using Nethermind.Specs.Forks; + +if (args.Length > 0 && args[0] == "--diag") +{ + string pattern = args.Length > 1 ? args[1] : "*"; + RunDiagnostic(pattern); + return; +} + +int inprocessIndex = Array.IndexOf(args, "--inprocess"); +if (inprocessIndex >= 0) +{ + GasBenchmarkConfig.InProcess = true; + args = RemoveArguments(args, inprocessIndex, 1); +} + +args = ApplyModeFilter(args); +args = ApplyChunkFilter(args); + +ConfigureTimingFilePath(); +BenchmarkSwitcher.FromAssembly(typeof(EvmBenchmarks).Assembly).Run(args); +GasNewPayloadMeasuredBenchmarks.PrintFinalTimingBreakdown(); +GasNewPayloadBenchmarks.PrintFinalTimingBreakdown(); + +static void ConfigureTimingFilePath() +{ + string measuredTimingFilePath = Path.Combine( + Path.GetTempPath(), + $"nethermind-newpayload-timing-{Guid.NewGuid():N}.jsonl"); + string measuredTimingReportFilePath = Path.Combine( + Directory.GetCurrentDirectory(), + "BenchmarkDotNet.Artifacts", + "results", + $"newpayload-measured-timing-breakdown-{DateTime.UtcNow:yyyyMMdd-HHmmss}-{Guid.NewGuid():N}.txt"); + string realTimingFilePath = Path.Combine( + Path.GetTempPath(), + $"nethermind-newpayload-real-timing-{Guid.NewGuid():N}.jsonl"); + string realTimingReportFilePath = Path.Combine( + Directory.GetCurrentDirectory(), + "BenchmarkDotNet.Artifacts", + "results", + $"newpayload-timing-breakdown-{DateTime.UtcNow:yyyyMMdd-HHmmss}-{Guid.NewGuid():N}.txt"); + + Environment.SetEnvironmentVariable(GasNewPayloadMeasuredBenchmarks.TimingFileEnvVar, measuredTimingFilePath); + Environment.SetEnvironmentVariable(GasNewPayloadMeasuredBenchmarks.TimingReportFileEnvVar, measuredTimingReportFilePath); + Environment.SetEnvironmentVariable(GasNewPayloadBenchmarks.TimingFileEnvVar, realTimingFilePath); + Environment.SetEnvironmentVariable(GasNewPayloadBenchmarks.TimingReportFileEnvVar, realTimingReportFilePath); +} + +static string[] RemoveArguments(string[] args, int index, int removeCount) +{ + string[] remaining = new string[args.Length - removeCount]; + Array.Copy(args, 0, remaining, 0, index); + Array.Copy(args, index + removeCount, remaining, index, args.Length - index - removeCount); + return remaining; +} + +static string[] MergeWithClassFilter(string[] args, string classFilter) +{ + int filterIndex = Array.IndexOf(args, "--filter"); + if (filterIndex >= 0 && filterIndex + 1 < args.Length) + { + string existingFilter = args[filterIndex + 1].Trim('"'); + args[filterIndex + 1] = classFilter.TrimEnd('*') + "*" + existingFilter.TrimStart('*'); + return args; + } + + string[] withFilter = new string[args.Length + 2]; + Array.Copy(args, withFilter, args.Length); + withFilter[args.Length] = "--filter"; + withFilter[args.Length + 1] = classFilter; + return withFilter; +} + +static (string Value, int RemoveCount) GetOptionValue(string[] args, int optionIndex, string optionName) +{ + string token = args[optionIndex]; + int separatorIndex = token.IndexOfAny(new[] { '=', ':' }); + if (separatorIndex >= 0) + { + return (token[(separatorIndex + 1)..], 1); + } + + if (optionIndex + 1 < args.Length) + { + return (args[optionIndex + 1], 2); + } + + throw new ArgumentException($"{optionName} requires a value."); +} + +static (string ClassFilter, bool? BuildBlocksOnMainState) ResolveModeDefinition(string modeValue) +{ + switch (modeValue.ToUpperInvariant()) + { + case "EVMEXECUTE": + return ("*GasPayloadExecuteBenchmarks*", null); + case "EVMBUILDUP": + return ("*GasPayloadBenchmarks*", null); + case "BLOCKBUILDING": + return ("*GasBlockBuildingBenchmarks*", false); + case "BLOCKBUILDINGMAINSTATE": + return ("*GasBlockBuildingBenchmarks*", true); + case "BLOCKONE": + return ("*GasBlockOne*", null); + case "BLOCK": + return ("*GasBlockBenchmarks*", null); + case "NEWPAYLOAD": + return ("*GasNewPayloadBenchmarks*", null); + case "NEWPAYLOADMEASURED": + return ("*GasNewPayloadMeasuredBenchmarks*", null); + default: + throw new ArgumentException($"Unknown --mode value: '{modeValue}'. Expected 'EVMExecute', 'EVMBuildUp', 'BlockBuilding', 'BlockBuildingMainState', 'BlockOne', 'Block', 'NewPayload', or 'NewPayloadMeasured'."); + } +} + +static string[] ApplyModeFilter(string[] args) +{ + int modeIndex = -1; + for (int i = 0; i < args.Length; i++) + { + if (args[i].StartsWith("--mode=", StringComparison.OrdinalIgnoreCase) + || args[i].StartsWith("--mode:", StringComparison.OrdinalIgnoreCase) + || string.Equals(args[i], "--mode", StringComparison.OrdinalIgnoreCase)) + { + modeIndex = i; + break; + } + } + + if (modeIndex < 0) + { + return args; + } + + (string modeValue, int removeCount) = GetOptionValue(args, modeIndex, "--mode"); + (string classFilter, bool? buildBlocksOnMainStateValue) = ResolveModeDefinition(modeValue); + if (buildBlocksOnMainStateValue is bool buildBlocksOnMainState) + { + Environment.SetEnvironmentVariable( + GasBlockBuildingBenchmarks.BuildBlocksOnMainStateEnvVar, + buildBlocksOnMainState ? bool.TrueString : bool.FalseString); + } + + string[] remaining = RemoveArguments(args, modeIndex, removeCount); + return MergeWithClassFilter(remaining, classFilter); +} + +static string[] ApplyChunkFilter(string[] args) +{ + int chunkIndex = -1; + for (int i = 0; i < args.Length; i++) + { + if (args[i].StartsWith("--chunk", StringComparison.OrdinalIgnoreCase) + || string.Equals(args[i], "--chunk", StringComparison.OrdinalIgnoreCase)) + { + chunkIndex = i; + break; + } + } + + if (chunkIndex < 0) + { + return args; + } + + (string chunkValue, int removeCount) = GetOptionValue(args, chunkIndex, "--chunk"); + string[] parts = chunkValue.Split('/'); + if (parts.Length != 2 || !int.TryParse(parts[0], out int n) || !int.TryParse(parts[1], out int m) || n < 1 || n > m) + { + throw new ArgumentException($"Invalid --chunk value: '{chunkValue}'. Expected format N/M where 1 <= N <= M (e.g. 2/5)"); + } + + GasBenchmarkConfig.ChunkIndex = n; + GasBenchmarkConfig.ChunkTotal = m; + return RemoveArguments(args, chunkIndex, removeCount); +} + +static void RunDiagnostic(string pattern) +{ + string repoRoot = FindRepoRoot(); + string gasBenchmarksRoot = Path.Combine(repoRoot, "tools", "gas-benchmarks"); + string genesisPath = Path.Combine(gasBenchmarksRoot, "scripts", "genesisfiles", "nethermind", "zkevmgenesis.json"); + string testingDir = Path.Combine(gasBenchmarksRoot, "eest_tests", "testing"); + + string matchedFile = FindFirstMatchingTestFile(testingDir, pattern); + if (matchedFile is null) + { + Console.WriteLine($"ERROR: No test file matching '{pattern}' found"); + return; + } + + Console.WriteLine($"Test file: {Path.GetFileName(matchedFile)}"); + + IReleaseSpec pragueSpec = Prague.Instance; + ISpecProvider specProvider = new SingleReleaseSpecProvider(pragueSpec, 1, 1); + + Console.WriteLine("Loading genesis..."); + Stopwatch sw = Stopwatch.StartNew(); + PayloadLoader.EnsureGenesisInitialized(genesisPath, pragueSpec); + Console.WriteLine($"Genesis loaded in {sw.ElapsedMilliseconds}ms, StateRoot={PayloadLoader.GenesisStateRoot}"); + + IWorldState state = PayloadLoader.CreateWorldState(); + BlockHeader genesisBlock = new(Keccak.Zero, Keccak.OfAnEmptySequenceRlp, Address.Zero, 0, 0, 0, 0, Array.Empty()) + { + StateRoot = PayloadLoader.GenesisStateRoot + }; + IDisposable scope = state.BeginScope(genesisBlock); + + (BlockHeader header, Transaction[] txs) = PayloadLoader.LoadPayload(matchedFile); + Console.WriteLine($"Block: number={header.Number}, gasLimit={header.GasLimit}, gasUsed={header.GasUsed}"); + Console.WriteLine($"Transactions: {txs.Length}"); + + for (int i = 0; i < txs.Length; i++) + { + Transaction tx = txs[i]; + Console.WriteLine($"\nTx[{i}]:"); + Console.WriteLine($" SenderAddress: {tx.SenderAddress}"); + Console.WriteLine($" To: {tx.To}"); + Console.WriteLine($" Nonce: {tx.Nonce}"); + Console.WriteLine($" GasLimit: {tx.GasLimit}"); + Console.WriteLine($" GasPrice: {tx.GasPrice}"); + Console.WriteLine($" Value: {tx.Value}"); + Console.WriteLine($" Data length: {tx.Data.Length}"); + + if (tx.SenderAddress is not null) + { + bool senderExists = state.AccountExists(tx.SenderAddress); + Console.WriteLine($" Sender exists: {senderExists}"); + if (senderExists) + { + Console.WriteLine($" Sender balance: {state.GetBalance(tx.SenderAddress)}"); + Console.WriteLine($" Sender nonce: {state.GetNonce(tx.SenderAddress)}"); + } + } + + if (tx.To is not null) + { + bool toExists = state.AccountExists(tx.To); + Console.WriteLine($" To exists: {toExists}"); + if (toExists) + { + Console.WriteLine($" To code size: {state.GetCodeHash(tx.To)}"); + Console.WriteLine($" To has code: {state.GetCodeHash(tx.To) != Keccak.OfAnEmptyString}"); + } + } + } + + TestBlockhashProvider blockhashProvider = new(); + EthereumCodeInfoRepository codeInfoRepo = new(state); + EthereumVirtualMachine vm = new(blockhashProvider, specProvider, LimboLogs.Instance); + + ITransactionProcessor txProcessor = new EthereumTransactionProcessor( + BlobBaseFeeCalculator.Instance, + specProvider, + state, + vm, + codeInfoRepo, + LimboLogs.Instance); + + string setupFile = GasPayloadBenchmarks.FindSetupFile(Path.GetFileName(matchedFile)); + if (setupFile is not null) + { + Console.WriteLine($"\nSetup file: {Path.GetFileName(setupFile)}"); + (BlockHeader setupHeader, Transaction[] setupTxs) = PayloadLoader.LoadPayload(setupFile); + txProcessor.SetBlockExecutionContext(setupHeader); + for (int i = 0; i < setupTxs.Length; i++) + { + txProcessor.Execute(setupTxs[i], NullTxTracer.Instance); + } + state.Commit(pragueSpec); + Console.WriteLine($"Setup complete: {setupTxs.Length} transactions executed"); + } + + txProcessor.SetBlockExecutionContext(header); + + Console.WriteLine("\n--- Executing transactions ---"); + for (int i = 0; i < txs.Length; i++) + { + sw.Restart(); + TransactionResult result = txProcessor.BuildUp(txs[i], NullTxTracer.Instance); + sw.Stop(); + Console.WriteLine($"Tx[{i}] result: {result}, elapsed: {sw.ElapsedMilliseconds}ms"); + } + + state.Reset(); + + Console.WriteLine("\n--- CallAndRestore ---"); + for (int i = 0; i < txs.Length; i++) + { + sw.Restart(); + txProcessor.CallAndRestore(txs[i], NullTxTracer.Instance); + sw.Stop(); + Console.WriteLine($"Tx[{i}] CallAndRestore elapsed: {sw.ElapsedMilliseconds}ms"); + } + + scope.Dispose(); + Console.WriteLine("\nDiagnostic complete."); +} + +static string FindFirstMatchingTestFile(string testingDir, string pattern) +{ + foreach (string dir in Directory.GetDirectories(testingDir)) + { + foreach (string file in Directory.GetFiles(dir, $"*{pattern}*")) + { + return file; + } + } + + return null; +} + +static string FindRepoRoot() +{ + string dir = AppDomain.CurrentDomain.BaseDirectory; + while (dir is not null) + { + if (Directory.Exists(Path.Combine(dir, ".git"))) + { + return dir; + } + + dir = Directory.GetParent(dir)?.FullName; + } + + throw new DirectoryNotFoundException("Could not find repository root."); +} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/Properties/launchSettings.json b/src/Nethermind/Nethermind.Evm.Benchmark/Properties/launchSettings.json new file mode 100644 index 000000000000..9a268f547cbe --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Benchmark/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Nethermind.Evm.Benchmark": { + "commandName": "Project", + "commandLineArgs": "--diag MULMOD" + } + } +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs b/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs index 9865e3441676..b8da26cbc008 100644 --- a/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs @@ -144,7 +144,7 @@ public void GetCachedCodeInfo_CodeTryGetDelegation_ReturnsCodeOfDelegation(byte[ stateProvider.InsertCode(delegationAddress, delegationCode, _releaseSpec); EthereumCodeInfoRepository sut = new(stateProvider); - ICodeInfo result = sut.GetCachedCodeInfo(TestItem.AddressA, _releaseSpec); + CodeInfo result = sut.GetCachedCodeInfo(TestItem.AddressA, _releaseSpec); result.CodeSpan.ToArray().Should().BeEquivalentTo(delegationCode); } diff --git a/src/Nethermind/Nethermind.Evm/BadInstructionException.cs b/src/Nethermind/Nethermind.Evm/BadInstructionException.cs deleted file mode 100644 index e8caed45f5cd..000000000000 --- a/src/Nethermind/Nethermind.Evm/BadInstructionException.cs +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Evm; - -public class BadInstructionException : EvmException -{ - public override EvmExceptionType ExceptionType => EvmExceptionType.BadInstruction; -} diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockOverride.cs b/src/Nethermind/Nethermind.Evm/BlockOverride.cs similarity index 96% rename from src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockOverride.cs rename to src/Nethermind/Nethermind.Evm/BlockOverride.cs index 4bb929aef316..fb0f7b6e52c2 100644 --- a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockOverride.cs +++ b/src/Nethermind/Nethermind.Evm/BlockOverride.cs @@ -6,7 +6,7 @@ using Nethermind.Core.Crypto; using Nethermind.Int256; -namespace Nethermind.Facade.Proxy.Models.Simulate; +namespace Nethermind.Evm; public class BlockOverride { diff --git a/src/Nethermind/Nethermind.Evm/CallResult.cs b/src/Nethermind/Nethermind.Evm/CallResult.cs index 08d88379284f..d23ac7ab54e3 100644 --- a/src/Nethermind/Nethermind.Evm/CallResult.cs +++ b/src/Nethermind/Nethermind.Evm/CallResult.cs @@ -44,7 +44,7 @@ public CallResult(ReadOnlyMemory output, bool? precompileSuccess, int from FromVersion = fromVersion; } - public CallResult(ICodeInfo? container, ReadOnlyMemory output, bool? precompileSuccess, int fromVersion, bool shouldRevert = false, EvmExceptionType exceptionType = EvmExceptionType.None) + public CallResult(CodeInfo? container, ReadOnlyMemory output, bool? precompileSuccess, int fromVersion, bool shouldRevert = false, EvmExceptionType exceptionType = EvmExceptionType.None) { StateToExecute = null; Output = (container, output); @@ -64,7 +64,7 @@ private CallResult(EvmExceptionType exceptionType) } public VmState? StateToExecute { get; } - public (ICodeInfo Container, ReadOnlyMemory Bytes) Output { get; } + public (CodeInfo Container, ReadOnlyMemory Bytes) Output { get; } public EvmExceptionType ExceptionType { get; } public bool ShouldRevert { get; } public bool? PrecompileSuccess { get; } diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs index 4e2320bd3ff6..75a16c118201 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs @@ -3,35 +3,101 @@ using System; using System.Threading; +using Nethermind.Core.Extensions; +using Nethermind.Evm.Precompiles; namespace Nethermind.Evm.CodeAnalysis; -public sealed class CodeInfo(ReadOnlyMemory code) : ICodeInfo, IThreadPoolWorkItem +public class CodeInfo : IThreadPoolWorkItem, IEquatable { - private static readonly JumpDestinationAnalyzer _emptyAnalyzer = new(Array.Empty()); - public static CodeInfo Empty { get; } = new(ReadOnlyMemory.Empty); - public ReadOnlyMemory Code { get; } = code; - ReadOnlySpan ICodeInfo.CodeSpan => Code.Span; + public static CodeInfo Empty { get; } = new(); + // Empty code sentinel + private static readonly JumpDestinationAnalyzer? _emptyAnalyzer = new(Empty, skipAnalysis: true); - private readonly JumpDestinationAnalyzer _analyzer = code.Length == 0 ? _emptyAnalyzer : new JumpDestinationAnalyzer(code); + // Empty + private CodeInfo() + { + _analyzer = null; + } - public bool IsEmpty => ReferenceEquals(_analyzer, _emptyAnalyzer); + protected CodeInfo(IPrecompile precompile, int version, ReadOnlyMemory code) + { + Precompile = precompile; + Version = version; + Code = code; + _analyzer = null; + } - public bool ValidateJump(int destination) + // Eof + protected CodeInfo(int version, ReadOnlyMemory code) { - return _analyzer.ValidateJump(destination); + Version = version; + Code = code; + _analyzer = null; } - void IThreadPoolWorkItem.Execute() + // Regular contract + public CodeInfo(ReadOnlyMemory code) { - _analyzer.Execute(); + Code = code; + _analyzer = code.Length == 0 ? _emptyAnalyzer : new JumpDestinationAnalyzer(this); } + // Precompile + public CodeInfo(IPrecompile? precompile) + { + Precompile = precompile; + _analyzer = null; + } + + public ReadOnlyMemory Code { get; } + public ReadOnlySpan CodeSpan => Code.Span; + + public IPrecompile? Precompile { get; } + + private readonly JumpDestinationAnalyzer _analyzer; + + public bool IsEmpty => ReferenceEquals(_analyzer, _emptyAnalyzer); + public bool IsPrecompile => Precompile is not null; + + public bool ValidateJump(int destination) + => _analyzer?.ValidateJump(destination) ?? false; + + /// + /// Gets the version of the code format. + /// The default implementation returns 0, representing a legacy code format or non-EOF code. + /// + public int Version { get; } = 0; + + void IThreadPoolWorkItem.Execute() + => _analyzer?.Execute(); + public void AnalyzeInBackgroundIfRequired() { - if (!ReferenceEquals(_analyzer, _emptyAnalyzer) && _analyzer.RequiresAnalysis) + if (!ReferenceEquals(_analyzer, _emptyAnalyzer) && (_analyzer?.RequiresAnalysis ?? false)) { ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false); } } + + public override bool Equals(object? obj) + => Equals(obj as CodeInfo); + + public override int GetHashCode() + { + if (IsPrecompile) + return Precompile?.GetType().GetHashCode() ?? 0; + return CodeSpan.FastHash(); + } + + public bool Equals(CodeInfo? other) + { + if (other is null) + return false; + if (ReferenceEquals(this, other)) + return true; + if (IsPrecompile || other.IsPrecompile) + return Precompile?.GetType() == other.Precompile?.GetType(); + return CodeSpan.SequenceEqual(other.CodeSpan); + } } diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs index 2ceca978a1ba..7923f62aeafc 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs @@ -16,7 +16,7 @@ namespace Nethermind.Evm.CodeAnalysis; -public sealed class JumpDestinationAnalyzer(ReadOnlyMemory code) +public sealed class JumpDestinationAnalyzer(CodeInfo codeInfo, bool skipAnalysis = false) { private const int PUSH1 = (int)Instruction.PUSH1; private const int PUSHx = PUSH1 - 1; @@ -24,10 +24,10 @@ public sealed class JumpDestinationAnalyzer(ReadOnlyMemory code) private const int BitShiftPerInt64 = 6; private static readonly long[]? _emptyJumpDestinationBitmap = new long[1]; - private long[]? _jumpDestinationBitmap = code.Length == 0 ? _emptyJumpDestinationBitmap : null; + private long[]? _jumpDestinationBitmap = (codeInfo.Code.Length == 0 || skipAnalysis) ? _emptyJumpDestinationBitmap : null; private object? _analysisComplete; - private ReadOnlyMemory MachineCode { get; } = code; + public ReadOnlyMemory MachineCode => codeInfo.Code; public bool ValidateJump(int destination) { diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs b/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs index 43fd8a86b8b2..91ff0753e0e1 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs @@ -10,7 +10,7 @@ namespace Nethermind.Evm.CodeAnalysis; public static class CodeInfoFactory { - public static ICodeInfo CreateCodeInfo(ReadOnlyMemory code, IReleaseSpec spec, ValidationStrategy validationRules = ValidationStrategy.ExtractHeader) + public static CodeInfo CreateCodeInfo(ReadOnlyMemory code, IReleaseSpec spec, ValidationStrategy validationRules = ValidationStrategy.ExtractHeader) { if (spec.IsEofEnabled && code.Span.StartsWith(EofValidator.MAGIC) @@ -23,7 +23,7 @@ public static ICodeInfo CreateCodeInfo(ReadOnlyMemory code, IReleaseSpec s return codeInfo; } - public static bool CreateInitCodeInfo(ReadOnlyMemory data, IReleaseSpec spec, [NotNullWhen(true)] out ICodeInfo? codeInfo, out ReadOnlyMemory extraCallData) + public static bool CreateInitCodeInfo(ReadOnlyMemory data, IReleaseSpec spec, [NotNullWhen(true)] out CodeInfo? codeInfo, out ReadOnlyMemory extraCallData) { extraCallData = default; if (spec.IsEofEnabled && data.Span.StartsWith(EofValidator.MAGIC)) diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs index 959ed46ff4c3..1fb55fdaf67f 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs @@ -19,7 +19,7 @@ namespace Nethermind.Evm; public class CodeInfoRepository : ICodeInfoRepository { private static readonly CodeLruCache _codeCache = new(); - private readonly FrozenDictionary _localPrecompiles; + private readonly FrozenDictionary _localPrecompiles; private readonly IWorldState _worldState; public CodeInfoRepository(IWorldState worldState, IPrecompileProvider precompileProvider) @@ -28,7 +28,7 @@ public CodeInfoRepository(IWorldState worldState, IPrecompileProvider precompile _worldState = worldState; } - public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) + public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) { delegationAddress = null; if (vmSpec.IsPrecompile(codeSource)) // _localPrecompiles have to have all precompiles @@ -36,7 +36,7 @@ public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IR return _localPrecompiles[codeSource]; } - ICodeInfo cachedCodeInfo = InternalGetCachedCode(_worldState, codeSource, vmSpec); + CodeInfo cachedCodeInfo = InternalGetCachedCode(_worldState, codeSource, vmSpec); if (!cachedCodeInfo.IsEmpty && ICodeInfoRepository.TryGetDelegatedAddress(cachedCodeInfo.CodeSpan, out delegationAddress)) { @@ -47,21 +47,21 @@ public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IR return cachedCodeInfo; } - private ICodeInfo InternalGetCachedCode(Address codeSource, IReleaseSpec vmSpec) + private CodeInfo InternalGetCachedCode(Address codeSource, IReleaseSpec vmSpec) { ref readonly ValueHash256 codeHash = ref _worldState.GetCodeHash(codeSource); return InternalGetCachedCode(_worldState, in codeHash, vmSpec); } - private static ICodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, Address codeSource, IReleaseSpec vmSpec) + private static CodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, Address codeSource, IReleaseSpec vmSpec) { ValueHash256 codeHash = worldState.GetCodeHash(codeSource); return InternalGetCachedCode(worldState, in codeHash, vmSpec); } - private static ICodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, in ValueHash256 codeHash, IReleaseSpec vmSpec) + private static CodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, in ValueHash256 codeHash, IReleaseSpec vmSpec) { - ICodeInfo? cachedCodeInfo = null; + CodeInfo? cachedCodeInfo = null; if (codeHash == Keccak.OfAnEmptyString.ValueHash256) { cachedCodeInfo = CodeInfo.Empty; @@ -101,7 +101,7 @@ public void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpe if (_worldState.InsertCode(codeOwner, in codeHash, code, spec) && _codeCache.Get(in codeHash) is null) { - ICodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(code, spec, ValidationStrategy.ExtractHeader); + CodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(code, spec, ValidationStrategy.ExtractHeader); _codeCache.Set(in codeHash, codeInfo); } } @@ -138,7 +138,7 @@ public ValueHash256 GetExecutableCodeHash(Address address, IReleaseSpec spec) return Keccak.OfAnEmptyString.ValueHash256; } - ICodeInfo codeInfo = InternalGetCachedCode(_worldState, address, spec); + CodeInfo codeInfo = InternalGetCachedCode(_worldState, address, spec); return codeInfo.IsEmpty ? Keccak.OfAnEmptyString.ValueHash256 : codeHash; @@ -154,33 +154,33 @@ private sealed class CodeLruCache { private const int CacheCount = 16; private const int CacheMax = CacheCount - 1; - private readonly ClockCache[] _caches; + private readonly ClockCache[] _caches; public CodeLruCache() { - _caches = new ClockCache[CacheCount]; + _caches = new ClockCache[CacheCount]; for (int i = 0; i < _caches.Length; i++) { // Cache per nibble to reduce contention as TxPool is very parallel - _caches[i] = new ClockCache(MemoryAllowance.CodeCacheSize / CacheCount); + _caches[i] = new ClockCache(MemoryAllowance.CodeCacheSize / CacheCount); } } - public ICodeInfo? Get(in ValueHash256 codeHash) + public CodeInfo? Get(in ValueHash256 codeHash) { - ClockCache cache = _caches[GetCacheIndex(codeHash)]; + ClockCache cache = _caches[GetCacheIndex(codeHash)]; return cache.Get(codeHash); } - public bool Set(in ValueHash256 codeHash, ICodeInfo codeInfo) + public bool Set(in ValueHash256 codeHash, CodeInfo codeInfo) { - ClockCache cache = _caches[GetCacheIndex(codeHash)]; + ClockCache cache = _caches[GetCacheIndex(codeHash)]; return cache.Set(codeHash, codeInfo); } private static int GetCacheIndex(in ValueHash256 codeHash) => codeHash.Bytes[^1] & CacheMax; - public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out ICodeInfo? codeInfo) + public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out CodeInfo? codeInfo) { codeInfo = Get(in codeHash); return codeInfo is not null; diff --git a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs index 140a3034b36d..82e5a8ee5b95 100644 --- a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs +++ b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs @@ -6,17 +6,18 @@ namespace Nethermind.Evm.CodeAnalysis; -public sealed class EofCodeInfo(in EofContainer container) : ICodeInfo +public sealed class EofCodeInfo : CodeInfo { - public EofContainer EofContainer { get; private set; } = container; - public ReadOnlyMemory Code => EofContainer.Container; - public int Version => EofContainer.Header.Version; - public bool IsEmpty => EofContainer.IsEmpty; + public EofCodeInfo(in EofContainer container) : base(container.Header.Version, container.Container) + { + EofContainer = container; + } + + public EofContainer EofContainer { get; private set; } public ReadOnlyMemory TypeSection => EofContainer.TypeSection; public ReadOnlyMemory CodeSection => EofContainer.CodeSection; public ReadOnlyMemory DataSection => EofContainer.DataSection; public ReadOnlyMemory ContainerSection => EofContainer.ContainerSection; - ReadOnlySpan ICodeInfo.CodeSpan => CodeSection.Span; public SectionHeader CodeSectionOffset(int sectionId) => EofContainer.Header.CodeSections[sectionId]; public SectionHeader? ContainerSectionOffset(int sectionId) => EofContainer.Header.ContainerSections.Value[sectionId]; diff --git a/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs b/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs index 0f396d82bd47..ff60de7b2723 100644 --- a/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs +++ b/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs @@ -22,7 +22,7 @@ public sealed class ExecutionEnvironment : IDisposable /// /// Parsed bytecode for the current call. /// - public ICodeInfo CodeInfo { get; private set; } = null!; + public CodeInfo CodeInfo { get; private set; } = null!; /// /// Currently executing account (in DELEGATECALL this will be equal to caller). @@ -65,7 +65,7 @@ private ExecutionEnvironment() { } /// Rents an ExecutionEnvironment from the pool and initializes it with the provided values. /// public static ExecutionEnvironment Rent( - ICodeInfo codeInfo, + CodeInfo codeInfo, Address executingAccount, Address caller, Address? codeSource, diff --git a/src/Nethermind/Nethermind.Evm/ICodeInfo.cs b/src/Nethermind/Nethermind.Evm/ICodeInfo.cs deleted file mode 100644 index 07156260530e..000000000000 --- a/src/Nethermind/Nethermind.Evm/ICodeInfo.cs +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Evm.Precompiles; - -namespace Nethermind.Evm.CodeAnalysis; - -/// -/// Represents common code information for EVM execution. -/// Implementations include , (EVM Object Format), -/// and for precompiled contracts. -/// -public interface ICodeInfo -{ - /// - /// Gets the version of the code format. - /// The default implementation returns 0, representing a legacy code format or non-EOF code. - /// - int Version => 0; - - /// - /// Indicates whether the code is empty or not. - /// - bool IsEmpty { get; } - - /// - /// Gets the raw machine code as a segment. - /// This is the primary code section from which the EVM executes instructions. - /// - ReadOnlyMemory Code { get; } - - /// - /// Indicates whether this code represents a precompiled contract. - /// By default, this returns false. - /// - bool IsPrecompile => false; - IPrecompile? Precompile => null; - - /// - /// Gets the code section. - /// By default, this returns the same contents as . - /// - ReadOnlyMemory CodeSection => Code; - ReadOnlySpan CodeSpan { get; } - - /// - /// Gets the data section, which is reserved for additional data segments in EOF. - /// By default, this returns an empty memory segment. - /// - ReadOnlyMemory DataSection => Memory.Empty; - - /// - /// Computes the offset to be added to the program counter when executing instructions. - /// By default, this returns 0, meaning no offset is applied. - /// - /// The program counter offset for this code format. - int PcOffset() => 0; - - /// - /// Validates whether a jump destination is permissible according to this code format. - /// By default, this returns false. - /// - /// The instruction index to validate. - /// true if the jump is valid; otherwise, false. - bool ValidateJump(int destination) => false; -} diff --git a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs index 17e568d007e0..20cedaa8233d 100644 --- a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs @@ -11,7 +11,7 @@ namespace Nethermind.Evm; public interface ICodeInfoRepository { - ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress); + CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress); ValueHash256 GetExecutableCodeHash(Address address, IReleaseSpec spec); void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec); void SetDelegation(Address codeSource, Address authority, IReleaseSpec spec); @@ -37,8 +37,8 @@ static bool TryGetDelegatedAddress(ReadOnlySpan code, [NotNullWhen(true)] public static class CodeInfoRepositoryExtensions { - public static ICodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, Address codeSource, IReleaseSpec vmSpec) + public static CodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, Address codeSource, IReleaseSpec vmSpec) => codeInfoRepository.GetCachedCodeInfo(codeSource, vmSpec, out _); - public static ICodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress) + public static CodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress) => codeInfoRepository.GetCachedCodeInfo(codeSource, true, vmSpec, out delegationAddress); } diff --git a/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs index 6cee9b50c462..5b9dd5dd3697 100644 --- a/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs @@ -9,7 +9,7 @@ namespace Nethermind.Evm; public interface IOverridableCodeInfoRepository : ICodeInfoRepository { - void SetCodeOverride(IReleaseSpec vmSpec, Address key, ICodeInfo value); + void SetCodeOverride(IReleaseSpec vmSpec, Address key, CodeInfo value); void MovePrecompile(IReleaseSpec vmSpec, Address precompileAddr, Address targetAddr); void ResetOverrides(); void ResetPrecompileOverrides(); diff --git a/src/Nethermind/Nethermind.Evm/IPrecompileProvider.cs b/src/Nethermind/Nethermind.Evm/IPrecompileProvider.cs index d6654879331d..6e17d2e8c3d3 100644 --- a/src/Nethermind/Nethermind.Evm/IPrecompileProvider.cs +++ b/src/Nethermind/Nethermind.Evm/IPrecompileProvider.cs @@ -9,5 +9,5 @@ namespace Nethermind.Evm; public interface IPrecompileProvider { - public FrozenDictionary GetPrecompiles(); + public FrozenDictionary GetPrecompiles(); } diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs index e802d92416fe..2728f87e21b2 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs @@ -177,7 +177,7 @@ public static EvmExceptionType InstructionCall( if (!TGasPolicy.UpdateMemoryCost(ref gas, in a, result, vm.VmState)) goto OutOfGas; - ICodeInfo codeInfo = vm.GetExtCodeInfoCached(address, spec); + CodeInfo codeInfo = vm.GetExtCodeInfoCached(address, spec); // Get the external code from the repository. ReadOnlySpan externalCode = codeInfo.CodeSpan; diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs index 66f79084b2cb..d433f76793e7 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs @@ -200,7 +200,7 @@ public static EvmExceptionType InstructionCreate(Vir where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; // Ensure the instruction is only valid for non-legacy (EOF) code. - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; // Deduct gas required for data loading. @@ -193,8 +192,7 @@ public static EvmExceptionType InstructionDataLoadN(Vi where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.DataLoadN)) @@ -225,8 +223,7 @@ public static EvmExceptionType InstructionDataSize(Vir where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.DataSize)) @@ -251,8 +248,7 @@ public static EvmExceptionType InstructionDataCopy(Vir where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; // Pop destination memory offset, data section offset, and size. @@ -303,8 +299,7 @@ public static EvmExceptionType InstructionDataCopy(Vir public static EvmExceptionType InstructionRelativeJump(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.RJump)) @@ -330,8 +325,7 @@ public static EvmExceptionType InstructionRelativeJump(VirtualMachin public static EvmExceptionType InstructionRelativeJumpIf(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.RJumpi)) @@ -365,8 +359,7 @@ public static EvmExceptionType InstructionRelativeJumpIf(VirtualMach public static EvmExceptionType InstructionJumpTable(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.RJumpv)) @@ -406,7 +399,7 @@ public static EvmExceptionType InstructionJumpTable(VirtualMachine(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - ICodeInfo iCodeInfo = vm.VmState.Env.CodeInfo; + CodeInfo iCodeInfo = vm.VmState.Env.CodeInfo; if (iCodeInfo.Version == 0) goto BadInstruction; @@ -462,7 +455,7 @@ public static EvmExceptionType InstructionCallFunction(VirtualMachin public static EvmExceptionType InstructionReturnFunction(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; + CodeInfo codeInfo = vm.VmState.Env.CodeInfo; if (codeInfo.Version == 0) goto BadInstruction; @@ -490,7 +483,7 @@ public static EvmExceptionType InstructionReturnFunction(VirtualMach public static EvmExceptionType InstructionJumpFunction(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - ICodeInfo iCodeInfo = vm.VmState.Env.CodeInfo; + CodeInfo iCodeInfo = vm.VmState.Env.CodeInfo; if (iCodeInfo.Version == 0) goto BadInstruction; @@ -530,8 +523,7 @@ public static EvmExceptionType InstructionDupN(Virtual where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Dupn)) @@ -561,8 +553,7 @@ public static EvmExceptionType InstructionSwapN(Virtua where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Swapn)) @@ -591,8 +582,7 @@ public static EvmExceptionType InstructionExchange(Vir where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Swapn)) @@ -742,7 +732,7 @@ public static EvmExceptionType InstructionEofCreate(Vi state.SubtractFromBalance(env.ExecutingAccount, value, spec); // Create new code info for the init code. - ICodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(initContainer, spec, ValidationStrategy.ExtractHeader); + CodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(initContainer, spec, ValidationStrategy.ExtractHeader); // 8. Prepare the callData from the caller’s memory slice. if (!vm.VmState.Memory.TryLoad(dataOffset, dataSize, out ReadOnlyMemory callData)) @@ -843,7 +833,7 @@ public static EvmExceptionType InstructionReturnDataLoad EvmExceptionType.InvalidCode; - } -} diff --git a/src/Nethermind/Nethermind.Evm/Metrics.cs b/src/Nethermind/Nethermind.Evm/Metrics.cs index b4f5502b4161..0f94291471b6 100644 --- a/src/Nethermind/Nethermind.Evm/Metrics.cs +++ b/src/Nethermind/Nethermind.Evm/Metrics.cs @@ -19,6 +19,11 @@ public class Metrics private static readonly ZeroContentionCounter _codeDbCache = new(); [Description("Number of Code DB cache reads on thread.")] internal static long ThreadLocalCodeDbCache => _codeDbCache.ThreadLocalValue; + + /// + /// Gets thread-local code DB cache count. Use this for external access. + /// + public static long GetThreadLocalCodeDbCache() => _codeDbCache.ThreadLocalValue; internal static void IncrementCodeDbCache() => _codeDbCache.Increment(); [CounterMetric] [Description("Number of EVM exceptions thrown by contracts.")] @@ -168,6 +173,18 @@ internal static float BlockEstMedianGasPrice } } + /// + /// Gets block gas price data for external access. Returns (min, estMedian, ave, max). + /// Returns null if no gas data available (min is float.MaxValue). + /// + public static (float Min, float EstMedian, float Ave, float Max)? GetBlockGasPrices() + { + if (_blockMinGasPrice == float.MaxValue) + return null; + + return (_blockMinGasPrice, _blockEstMedianGasPrice, _blockAveGasPrice, _blockMaxGasPrice); + } + [GaugeMetric] [Description("Minimum tx gas price in block")] public static float GasPriceMin { get; private set; } diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/PrecompileInfo.cs b/src/Nethermind/Nethermind.Evm/Precompiles/PrecompileInfo.cs deleted file mode 100644 index 66c6da879ea4..000000000000 --- a/src/Nethermind/Nethermind.Evm/Precompiles/PrecompileInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Evm.Precompiles; - -namespace Nethermind.Evm.CodeAnalysis; - -public sealed class PrecompileInfo(IPrecompile precompile) : ICodeInfo -{ - public ReadOnlyMemory Code => Array.Empty(); - ReadOnlySpan ICodeInfo.CodeSpan => Code.Span; - public IPrecompile? Precompile { get; } = precompile; - - public bool IsPrecompile => true; - public bool IsEmpty => false; -} diff --git a/src/Nethermind/Nethermind.Evm/TransactionCollisionException.cs b/src/Nethermind/Nethermind.Evm/TransactionCollisionException.cs deleted file mode 100644 index 0c577ef83c04..000000000000 --- a/src/Nethermind/Nethermind.Evm/TransactionCollisionException.cs +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Evm -{ - public class TransactionCollisionException : EvmException - { - public override EvmExceptionType ExceptionType => EvmExceptionType.TransactionCollision; - } -} diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs index e927b73bf0ec..2ae5c1892b50 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs @@ -614,7 +614,7 @@ private TransactionResult BuildExecutionEnvironment( { Address recipient = tx.GetRecipient(tx.IsContractCreation ? WorldState.GetNonce(tx.SenderAddress!) : 0); if (recipient is null) ThrowInvalidDataException("Recipient has not been resolved properly before tx execution"); - ICodeInfo? codeInfo; + CodeInfo? codeInfo; ReadOnlyMemory inputData = tx.IsMessageCall ? tx.Data : default; if (tx.IsContractCreation) { diff --git a/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs b/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs index f335e854c52e..624ac196f00a 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs @@ -52,7 +52,7 @@ public readonly ref struct TransactionSubstate public string? Error { get; } public string? SubstateError { get; } public EvmExceptionType EvmExceptionType { get; } - public (ICodeInfo DeployCode, ReadOnlyMemory Bytes) Output { get; } + public (CodeInfo DeployCode, ReadOnlyMemory Bytes) Output { get; } public bool ShouldRevert { get; } public long Refund { get; } public IToArrayCollection Logs => _logs ?? _emptyLogs; @@ -80,7 +80,7 @@ private TransactionSubstate(string errorCode) ShouldRevert = true; } - public TransactionSubstate((ICodeInfo eofDeployCode, ReadOnlyMemory bytes) output, + public TransactionSubstate((CodeInfo eofDeployCode, ReadOnlyMemory bytes) output, long refund, IHashSetEnumerableCollection
destroyList, IToArrayCollection logs, diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs index 60eff6b6993e..0d785c9b28f8 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs @@ -41,7 +41,7 @@ internal uint GetExtCodeSizeCached(Address address, IReleaseSpec spec) { if (_maxExtCodeCacheEntries == 0) { - ICodeInfo uncachedCodeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + CodeInfo uncachedCodeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); return uncachedCodeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)uncachedCodeInfo.CodeSpan.Length; } @@ -54,7 +54,7 @@ internal uint GetExtCodeSizeCached(Address address, IReleaseSpec spec) return entry.CodeSize; } - ICodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + CodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); uint codeSize = codeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)codeInfo.CodeSpan.Length; StoreCacheEntry(key, codeHash, codeSize, codeInfo); @@ -80,11 +80,11 @@ private void ResetExtCodeCacheForBlock() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ICodeInfo GetExtCodeInfoCached(Address address, IReleaseSpec spec) + internal CodeInfo GetExtCodeInfoCached(Address address, IReleaseSpec spec) { if (_maxExtCodeCacheEntries == 0) { - ICodeInfo uncachedCodeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + CodeInfo uncachedCodeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); return uncachedCodeInfo; } @@ -99,14 +99,14 @@ internal ICodeInfo GetExtCodeInfoCached(Address address, IReleaseSpec spec) return entry.CodeInfo; } - ICodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + CodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); uint codeSize = codeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)codeInfo.CodeSpan.Length; StoreCacheEntry(key, codeHash, codeSize, codeInfo); return codeInfo; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void StoreCacheEntry(AddressAsKey key, in ValueHash256 codeHash, uint codeSize, ICodeInfo? codeInfo) + private void StoreCacheEntry(AddressAsKey key, in ValueHash256 codeHash, uint codeSize, CodeInfo? codeInfo) { if (_maxExtCodeCacheEntries == 0) { @@ -120,5 +120,5 @@ private void StoreCacheEntry(AddressAsKey key, in ValueHash256 codeHash, uint co _extCodeCache[key] = new ExtCodeCacheEntry(codeHash, codeSize, codeInfo); } - private readonly record struct ExtCodeCacheEntry(ValueHash256 CodeHash, uint CodeSize, ICodeInfo? CodeInfo); + private readonly record struct ExtCodeCacheEntry(ValueHash256 CodeHash, uint CodeSize, CodeInfo? CodeInfo); } diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index e26a5ea777f0..cae5cc2df888 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -1216,7 +1216,7 @@ protected virtual unsafe CallResult RunByteCode( SectionIndex = VmState.FunctionIndex; // Retrieve the code information and create a read-only span of instructions. - ICodeInfo codeInfo = VmState.Env.CodeInfo; + CodeInfo codeInfo = VmState.Env.CodeInfo; ReadOnlySpan codeSection = GetInstructions(codeInfo); // Initialize the exception type to "None". @@ -1331,16 +1331,16 @@ protected virtual unsafe CallResult RunByteCode( debugger?.TryWait(ref _currentState, ref programCounter, ref gas, ref stack.Head); #endif // Process the return data based on its runtime type. - if (ReturnData is VmState state) + if (ReturnData is byte[] data) { - return new CallResult(state); + // Fall back to returning a CallResult with a byte array as the return data. + return new CallResult(null, data, null, codeInfo.Version); } - else if (ReturnData is EofCodeInfo eofCodeInfo) + else if (ReturnData is VmState state) { - return new CallResult(eofCodeInfo, ReturnDataBuffer, null, codeInfo.Version); + return new CallResult(state); } - // Fall back to returning a CallResult with a byte array as the return data. - return new CallResult(null, (byte[])ReturnData, null, codeInfo.Version); + return ReturnEof(codeInfo); Revert: // Return a CallResult indicating a revert. @@ -1356,7 +1356,7 @@ protected virtual unsafe CallResult RunByteCode( // Converts the code section bytes into a read-only span of instructions. // Lightest weight conversion as mostly just helpful when debugging to see what the opcodes are. - static ReadOnlySpan GetInstructions(ICodeInfo codeInfo) + static ReadOnlySpan GetInstructions(CodeInfo codeInfo) { ReadOnlySpan codeBytes = codeInfo.CodeSpan; return MemoryMarshal.CreateReadOnlySpan( @@ -1364,6 +1364,10 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(codeBytes)), codeBytes.Length); } + [MethodImpl(MethodImplOptions.NoInlining)] + CallResult ReturnEof(CodeInfo codeInfo) + => new(ReturnData as EofCodeInfo, ReturnDataBuffer, null, codeInfo.Version); + [DoesNotReturn] static void ThrowOperationCanceledException() => throw new OperationCanceledException("Cancellation Requested"); } diff --git a/src/Nethermind/Nethermind.Facade.Test/LogIndexBuilderTests.cs b/src/Nethermind/Nethermind.Facade.Test/LogIndexBuilderTests.cs new file mode 100644 index 000000000000..8f5977660136 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade.Test/LogIndexBuilderTests.cs @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Receipts; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Events; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test; +using Nethermind.Core.Test.Builders; +using Nethermind.Db.LogIndex; +using Nethermind.Facade.Find; +using Nethermind.Logging; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Facade.Test; + +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class LogIndexBuilderTests +{ + private class TestLogIndexStorage : ILogIndexStorage + { + private int? _minBlockNumber; + private int? _maxBlockNumber; + + public bool Enabled => true; + + public event EventHandler? NewMaxBlockNumber; + public event EventHandler? NewMinBlockNumber; + + public int? MinBlockNumber + { + get => _minBlockNumber; + init => _minBlockNumber = value; + } + + public int? MaxBlockNumber + { + get => _maxBlockNumber; + init => _maxBlockNumber = value; + } + + public IEnumerator GetEnumerator(Address address, int from, int to) => + throw new NotImplementedException(); + + public IEnumerator GetEnumerator(int topicIndex, Hash256 topic, int from, int to) => + throw new NotImplementedException(); + + public string GetDbSize() => 0L.SizeToString(); + + public LogIndexAggregate Aggregate(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats = null) => + new(batch); + + public virtual Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null) + { + var min = Math.Min(aggregate.FirstBlockNum, aggregate.LastBlockNum); + var max = Math.Max(aggregate.FirstBlockNum, aggregate.LastBlockNum); + + if (_minBlockNumber is null || min < _minBlockNumber) + { + if (_minBlockNumber is not null && max != _minBlockNumber - 1) + throw new InvalidOperationException("Invalid receipts order."); + + _minBlockNumber = min; + NewMinBlockNumber?.Invoke(this, min); + } + + if (_maxBlockNumber is null || max > _maxBlockNumber) + { + if (_maxBlockNumber is not null && min != _maxBlockNumber + 1) + throw new InvalidOperationException("Invalid receipts order."); + + _maxBlockNumber = max; + NewMaxBlockNumber?.Invoke(this, max); + } + + return Task.CompletedTask; + } + + public Task RemoveReorgedAsync(BlockReceipts block) => Task.CompletedTask; + + public Task StopAsync() => Task.CompletedTask; + + public ValueTask DisposeAsync() => ValueTask.CompletedTask; + } + + private class FailingLogIndexStorage(int failAfter, Exception exception) : TestLogIndexStorage + { + private int _callCount; + + public override Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null) + { + return Interlocked.Increment(ref _callCount) <= failAfter + ? base.AddReceiptsAsync(aggregate, stats) + : throw exception; + } + } + + private const int MaxReorgDepth = 8; + private const int MaxBlock = 100; + private const int MaxSyncBlock = MaxBlock - MaxReorgDepth; + private const int BatchSize = 10; + + private ILogIndexConfig _config = null!; + private IBlockTree _blockTree = null!; + private ISyncConfig _syncConfig = null!; + private IReceiptStorage _receiptStorage = null!; + private ILogManager _logManager = null!; + private List _testDisposables = null!; + + [SetUp] + public void SetUp() + { + _config = new LogIndexConfig { Enabled = true, MaxReorgDepth = MaxReorgDepth, MaxBatchSize = BatchSize }; + _blockTree = Build.A.BlockTree().OfChainLength(MaxBlock + 1).BlockTree; + _syncConfig = new SyncConfig { FastSync = true, SnapSync = true }; + _receiptStorage = Substitute.For(); + _logManager = new TestLogManager(); + _testDisposables = []; + + Block head = _blockTree.Head!; + _blockTree.SyncPivot = (head.Number, head.Hash); + _syncConfig.PivotNumber = _blockTree.SyncPivot.BlockNumber; + + _receiptStorage + .Get(Arg.Any()) + .Returns(c => []); + } + + [TearDown] + public async Task TearDownAsync() + { + foreach (var disposable in _testDisposables) + { + if (disposable is IAsyncDisposable asyncDisposable) + await asyncDisposable.DisposeAsync(); + else if (disposable is IDisposable disposable1) + disposable1.Dispose(); + } + } + + private LogIndexBuilder GetService(ILogIndexStorage logIndexStorage) + { + return new LogIndexBuilder( + logIndexStorage, _config, _blockTree, _syncConfig, _receiptStorage, _logManager + ).AddTo(_testDisposables); + } + + [Test] + [CancelAfter(60_000)] + public async Task Should_SyncToBarrier( + [Values(1, 10)] int minBarrier, + [Values(1, 16, MaxBlock)] int batchSize, + [Values( + new[] { -1, -1 }, // -1 is treated as null + new[] { 0, MaxSyncBlock / 2 }, + new[] { MaxSyncBlock / 2, MaxSyncBlock / 2 }, + new[] { MaxSyncBlock / 2, MaxSyncBlock }, + new[] { 5, MaxSyncBlock - 5 } + )] + int[] synced, + CancellationToken cancellation + ) + { + _config.MaxBatchSize = batchSize; + _syncConfig.AncientReceiptsBarrier = minBarrier; + Assert.That(_syncConfig.AncientReceiptsBarrierCalc, Is.EqualTo(minBarrier)); + + var expectedMin = minBarrier <= 1 ? 0 : synced[0] < 0 ? minBarrier : Math.Min(synced[0], minBarrier); + var storage = new TestLogIndexStorage + { + MinBlockNumber = synced[0] < 0 ? null : synced[0], + MaxBlockNumber = synced[1] < 0 ? null : synced[1] + }; + + LogIndexBuilder builder = GetService(storage); + + Task completion = WaitBlocksAsync(storage, expectedMin, MaxSyncBlock, cancellation); + await builder.StartAsync(); + await completion; + + using (Assert.EnterMultipleScope()) + { + Assert.That(builder.LastError, Is.Null); + + Assert.That(storage.MinBlockNumber, Is.EqualTo(expectedMin)); + Assert.That(storage.MaxBlockNumber, Is.EqualTo(MaxSyncBlock)); + } + } + + [Test] + public async Task Should_ForwardError( + [Values(0, 1, 4)] int failAfter + ) + { + var exception = new Exception(nameof(Should_ForwardError)); + LogIndexBuilder builder = GetService(new FailingLogIndexStorage(failAfter, exception)); + + await builder.StartAsync(); + + using (Assert.EnterMultipleScope()) + { + Exception thrown = Assert.ThrowsAsync(() => builder.BackwardSyncCompletion.WaitAsync(TimeSpan.FromSeconds(999))); + Assert.That(thrown, Is.EqualTo(exception)); + Assert.That(builder.LastError, Is.EqualTo(exception)); + } + } + + [Test] + [Sequential] + public async Task Should_CompleteImmediately_IfAlreadySynced( + [Values(1, 10, 10, 10)] int minBarrier, + [Values(0, 00, 05, 10)] int minBlock + ) + { + Assert.That(minBlock, Is.LessThanOrEqualTo(minBarrier)); + + _syncConfig.AncientReceiptsBarrier = minBarrier; + LogIndexBuilder builder = GetService(new FailingLogIndexStorage(0, new("Should not set new receipts.")) + { + MinBlockNumber = minBlock, + MaxBlockNumber = MaxSyncBlock + }); + + await builder.StartAsync(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(builder.BackwardSyncCompletion.IsCompleted); + Assert.That(builder.LastError, Is.Null); + Assert.That(builder.LastUpdate, Is.Null); + } + } + + private static Task WaitMaxBlockAsync(TestLogIndexStorage storage, int blockNumber, CancellationToken cancellation) + { + if (storage.MaxBlockNumber >= blockNumber) + return Task.CompletedTask; + + return Wait.ForEventCondition( + cancellation, + e => storage.NewMaxBlockNumber += e, + e => storage.NewMaxBlockNumber -= e, + e => e >= blockNumber + ); + } + + private static Task WaitMinBlockAsync(TestLogIndexStorage storage, int blockNumber, CancellationToken cancellation) + { + if (storage.MinBlockNumber <= blockNumber) + return Task.CompletedTask; + + return Wait.ForEventCondition( + cancellation, + e => storage.NewMinBlockNumber += e, + e => storage.NewMinBlockNumber -= e, + e => e <= blockNumber + ); + } + + private static Task WaitBlocksAsync(TestLogIndexStorage storage, int minBlock, int maxBlock, CancellationToken cancellation) => Task.WhenAll( + WaitMinBlockAsync(storage, minBlock, cancellation), + WaitMaxBlockAsync(storage, maxBlock, cancellation) + ); +} diff --git a/src/Nethermind/Nethermind.Facade/Eth/FacadeJsonContext.cs b/src/Nethermind/Nethermind.Facade/Eth/FacadeJsonContext.cs new file mode 100644 index 000000000000..4204be813233 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/FacadeJsonContext.cs @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json.Serialization; +using Nethermind.Facade.Eth.RpcTransaction; +using Nethermind.Facade.Filters; + +namespace Nethermind.Facade.Eth; + +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Metadata, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(BlockForRpc))] +[JsonSerializable(typeof(FilterLog))] +[JsonSerializable(typeof(TransactionForRpc))] +[JsonSerializable(typeof(SyncingResult))] +public partial class FacadeJsonContext : JsonSerializerContext; diff --git a/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs index fc17885d64b6..e15f2cc5aaea 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Blockchain.Filters.Topics; @@ -19,6 +19,11 @@ public class LogFilter( public TopicsFilter TopicsFilter { get; } = topicsFilter; public BlockParameter FromBlock { get; } = fromBlock; public BlockParameter ToBlock { get; } = toBlock; + public bool UseIndex { get; set; } = true; + + public bool AcceptsAnyBlock => + AddressFilter.Addresses.Count == 0 && + TopicsFilter.AcceptsAnyBlock; public bool Accepts(LogEntry logEntry) => AddressFilter.Accepts(logEntry.Address) && TopicsFilter.Accepts(logEntry); diff --git a/src/Nethermind/Nethermind.Facade/Filters/LogIndexFilterVisitor.cs b/src/Nethermind/Nethermind.Facade/Filters/LogIndexFilterVisitor.cs new file mode 100644 index 000000000000..824c21538065 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Filters/LogIndexFilterVisitor.cs @@ -0,0 +1,187 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Nethermind.Blockchain.Filters; +using Nethermind.Blockchain.Filters.Topics; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Db.LogIndex; + +[assembly: InternalsVisibleTo("Nethermind.Blockchain.Test")] + +namespace Nethermind.Facade.Filters; + +/// +/// Converts tree and block range into an enumerator of block numbers from , +/// by building corresponding "tree of enumerators". +/// +public class LogIndexFilterVisitor(ILogIndexStorage storage, LogFilter filter, int fromBlock, int toBlock) : IEnumerable +{ + internal sealed class IntersectEnumerator(IEnumerator e1, IEnumerator e2) : IEnumerator + { + public bool MoveNext() + { + bool has1 = e1.MoveNext(); + bool has2 = e2.MoveNext(); + + while (has1 && has2) + { + int c1 = e1.Current; + int c2 = e2.Current; + if (c1 == c2) + { + Current = c1; + return true; + } + + if (c1 < c2) has1 = e1.MoveNext(); + else has2 = e2.MoveNext(); + } + + return false; + } + + public void Reset() + { + e1.Reset(); + e2.Reset(); + } + + public int Current { get; private set; } + + object? IEnumerator.Current => Current; + + public void Dispose() + { + e1.Dispose(); + e2.Dispose(); + } + } + + internal sealed class UnionEnumerator(IEnumerator e1, IEnumerator e2) : IEnumerator + { + private bool _has1 = e1.MoveNext(); + private bool _has2 = e2.MoveNext(); + + public bool MoveNext() => + (_has1, _has2) switch + { + (true, true) => e1.Current.CompareTo(e2.Current) switch + { + 0 => MoveNext(e1, out _has1) && MoveNext(e2, out _has2), + < 0 => MoveNext(e1, out _has1), + > 0 => MoveNext(e2, out _has2) + }, + (true, false) => MoveNext(e1, out _has1), + (false, true) => MoveNext(e2, out _has2), + (false, false) => false + }; + + private bool MoveNext(IEnumerator enumerator, out bool has) + { + Current = enumerator.Current; + has = enumerator.MoveNext(); + return true; + } + + public void Reset() + { + e1.Reset(); + e2.Reset(); + _has1 = e1.MoveNext(); + _has2 = e2.MoveNext(); + } + + public int Current { get; private set; } + + object? IEnumerator.Current => Current; + + public void Dispose() + { + e1.Dispose(); + e2.Dispose(); + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IEnumerator GetEnumerator() + { + IEnumerator? addressEnumerator = Visit(filter.AddressFilter); + IEnumerator? topicEnumerator = Visit(filter.TopicsFilter); + + if (addressEnumerator is not null && topicEnumerator is not null) + return new IntersectEnumerator(addressEnumerator, topicEnumerator); + + return addressEnumerator ?? topicEnumerator ?? throw new InvalidOperationException("Provided filter covers whole block range."); + } + + private IEnumerator? Visit(AddressFilter addressFilter) + { + IEnumerator? result = null; + + foreach (AddressAsKey address in addressFilter.Addresses) + { + IEnumerator next = Visit(address); + result = result is null ? next : new UnionEnumerator(result, next); + } + + return result; + } + + private IEnumerator? Visit(TopicsFilter topicsFilter) + { + IEnumerator result = null; + + var topicIndex = 0; + foreach (TopicExpression expression in topicsFilter.Expressions) + { + if (Visit(topicIndex++, expression) is not { } next) + continue; + + result = result is null ? next : new IntersectEnumerator(result, next); + } + + return result; + } + + private IEnumerator? Visit(int topicIndex, TopicExpression expression) => expression switch + { + AnyTopic => null, + OrExpression orExpression => Visit(topicIndex, orExpression), + SpecificTopic specificTopic => Visit(topicIndex, specificTopic.Topic), + _ => throw new ArgumentOutOfRangeException($"Unknown topic expression type: {expression.GetType().Name}.") + }; + + private IEnumerator? Visit(int topicIndex, OrExpression orExpression) + { + IEnumerator? result = null; + + foreach (TopicExpression expression in orExpression.SubExpressions) + { + if (Visit(topicIndex, expression) is not { } next) + continue; + + result = result is null ? next : new UnionEnumerator(result, next); + } + + return result; + } + + private IEnumerator Visit(Address address) => + storage.GetEnumerator(address, fromBlock, toBlock); + + private IEnumerator Visit(int topicIndex, Hash256 topic) => + storage.GetEnumerator(topicIndex, topic, fromBlock, toBlock); +} + +public static class LogIndexFilterVisitorExtensions +{ + public static IEnumerable EnumerateBlockNumbersFor(this ILogIndexStorage storage, LogFilter filter, long fromBlock, long toBlock) => + new LogIndexFilterVisitor(storage, filter, (int)fromBlock, (int)toBlock).Select(static i => (long)i); +} diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopic.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopic.cs index f8278d7ff632..bce87e8886f3 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopic.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopic.cs @@ -10,6 +10,8 @@ public class AnyTopic : TopicExpression { public static readonly AnyTopic Instance = new(); + public override bool AcceptsAnyBlock => true; + private AnyTopic() { } public override bool Accepts(Hash256 topic) => true; diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs index 7bf063bfb60c..a1b8c1bed972 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; +using System.Linq; using Nethermind.Blockchain.Receipts; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -10,6 +12,9 @@ namespace Nethermind.Blockchain.Filters.Topics { public class AnyTopicsFilter(params TopicExpression[] expressions) : TopicsFilter { + public override IEnumerable Expressions => expressions; + public override bool AcceptsAnyBlock => expressions.Length == 0 || expressions.Any(static e => e.AcceptsAnyBlock); + public override bool Accepts(LogEntry entry) => Accepts(entry.Topics); private bool Accepts(Hash256[] topics) diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/OrExpression.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/OrExpression.cs index d1d7801b43d1..406e4ee48671 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/OrExpression.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/OrExpression.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; +using System.Linq; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -11,6 +13,9 @@ public class OrExpression : TopicExpression, IEquatable { private readonly TopicExpression[] _subexpressions; + public IEnumerable SubExpressions => _subexpressions; + public override bool AcceptsAnyBlock => _subexpressions.Any(static e => e.AcceptsAnyBlock); + public OrExpression(params TopicExpression[] subexpressions) { _subexpressions = subexpressions; diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs index 69807f781ab0..1e490caa472a 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; +using System.Linq; using Nethermind.Blockchain.Receipts; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -15,6 +17,9 @@ public class SequenceTopicsFilter(params TopicExpression[] expressions) private readonly TopicExpression[] _expressions = expressions; + public override IEnumerable Expressions => _expressions; + public override bool AcceptsAnyBlock => _expressions.Length == 0 || _expressions.All(static e => e.AcceptsAnyBlock); + public override bool Accepts(LogEntry entry) => Accepts(entry.Topics); private bool Accepts(Hash256[] topics) diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/SpecificTopic.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/SpecificTopic.cs index a9dee55bc8c1..91aa003889c4 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/SpecificTopic.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/SpecificTopic.cs @@ -11,6 +11,9 @@ public class SpecificTopic : TopicExpression private readonly Hash256 _topic; private Bloom.BloomExtract _bloomExtract; + public Hash256 Topic => _topic; + public override bool AcceptsAnyBlock => false; + public SpecificTopic(Hash256 topic) { _topic = topic; diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicExpression.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicExpression.cs index 5193e0bfba8b..f344435bc680 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicExpression.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicExpression.cs @@ -8,6 +8,8 @@ namespace Nethermind.Blockchain.Filters.Topics { public abstract class TopicExpression { + public abstract bool AcceptsAnyBlock { get; } + public abstract bool Accepts(Hash256 topic); public abstract bool Accepts(ref Hash256StructRef topic); diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicsFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicsFilter.cs index 0cdc7ece380f..17b15c591c53 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicsFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicsFilter.cs @@ -1,12 +1,16 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Collections.Generic; using Nethermind.Core; namespace Nethermind.Blockchain.Filters.Topics { public abstract class TopicsFilter { + public abstract IEnumerable Expressions { get; } + public abstract bool AcceptsAnyBlock { get; } + public abstract bool Accepts(LogEntry entry); public abstract bool Accepts(ref LogEntryStructRef entry); diff --git a/src/Nethermind/Nethermind.Facade/Find/ILogIndexBuilder.cs b/src/Nethermind/Nethermind.Facade/Find/ILogIndexBuilder.cs new file mode 100644 index 000000000000..3f898f360640 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Find/ILogIndexBuilder.cs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading.Tasks; +using Nethermind.Core.ServiceStopper; + +namespace Nethermind.Facade.Find +{ + public interface ILogIndexBuilder : IAsyncDisposable, IStoppableService + { + Task StartAsync(); + bool IsRunning { get; } + + int MaxTargetBlockNumber { get; } + int MinTargetBlockNumber { get; } + + DateTimeOffset? LastUpdate { get; } + Exception? LastError { get; } + } +} diff --git a/src/Nethermind/Nethermind.Facade/Find/IndexedLogFinder.cs b/src/Nethermind/Nethermind.Facade/Find/IndexedLogFinder.cs new file mode 100644 index 000000000000..2c9827d6c607 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Find/IndexedLogFinder.cs @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading; +using Nethermind.Blockchain.Filters; +using Nethermind.Blockchain.Find; +using Nethermind.Blockchain.Receipts; +using Nethermind.Core; +using Nethermind.Db.Blooms; +using Nethermind.Db.LogIndex; +using Nethermind.Facade.Filters; +using Nethermind.Logging; + +namespace Nethermind.Facade.Find; + +/// +/// Extended that adds log index support for faster eth_getLogs queries. +/// When the log index is available and applicable, it uses the index to identify relevant blocks +/// before fetching logs from those specific blocks. +/// +public class IndexedLogFinder( + IBlockFinder blockFinder, + IReceiptFinder receiptFinder, + IReceiptStorage receiptStorage, + IBloomStorage bloomStorage, + ILogManager logManager, + IReceiptsRecovery receiptsRecovery, + ILogIndexStorage logIndexStorage, + int maxBlockDepth = 1000, + int minBlocksToUseIndex = 32) + : LogFinder(blockFinder, receiptFinder, receiptStorage, bloomStorage, logManager, receiptsRecovery, maxBlockDepth) +{ + private readonly ILogIndexStorage _logIndexStorage = logIndexStorage ?? throw new ArgumentNullException(nameof(logIndexStorage)); + + public override IEnumerable FindLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken = default) => + GetLogIndexRange(filter, fromBlock, toBlock) is not { } indexRange + ? base.FindLogs(filter, fromBlock, toBlock, cancellationToken) + : FindIndexedLogs(filter, fromBlock, toBlock, indexRange, cancellationToken); + + private IEnumerable FindIndexedLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, (int from, int to) indexRange, CancellationToken cancellationToken) + { + if (indexRange.from > fromBlock.Number && FindHeaderOrLogError(indexRange.from - 1, cancellationToken) is { } beforeIndex) + { + foreach (FilterLog log in base.FindLogs(filter, fromBlock, beforeIndex, cancellationToken)) + yield return log; + } + + cancellationToken.ThrowIfCancellationRequested(); + + IEnumerable indexNumbers = _logIndexStorage.EnumerateBlockNumbersFor(filter, indexRange.from, indexRange.to); + foreach (FilterLog log in FilterLogsInBlocksParallel(filter, indexNumbers, cancellationToken)) + { + yield return log; + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (indexRange.to < toBlock.Number && FindHeaderOrLogError(indexRange.to + 1, cancellationToken) is { } afterIndex) + { + foreach (FilterLog log in base.FindLogs(filter, afterIndex, toBlock, cancellationToken)) + yield return log; + } + } + + private (int from, int to)? GetLogIndexRange(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock) + { + bool tryUseIndex = filter.UseIndex; + filter.UseIndex = false; + + if (!tryUseIndex || !_logIndexStorage.Enabled || filter.AcceptsAnyBlock) + return null; + + if (_logIndexStorage.MinBlockNumber is not { } indexFrom || _logIndexStorage.MaxBlockNumber is not { } indexTo) + return null; + + (int from, int to) range = ( + Math.Max((int)fromBlock.Number, indexFrom), + Math.Min((int)toBlock.Number, indexTo) + ); + + if (range.from > range.to) + return null; + + if (range.to - range.from + 1 < minBlocksToUseIndex) + return null; + + filter.UseIndex = true; + return range; + } +} diff --git a/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs b/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs index b1eddee6df12..d1b4d5636152 100644 --- a/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs +++ b/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs @@ -18,37 +18,26 @@ namespace Nethermind.Facade.Find { - public class LogFinder : ILogFinder + public class LogFinder( + IBlockFinder? blockFinder, + IReceiptFinder? receiptFinder, + IReceiptStorage? receiptStorage, + IBloomStorage? bloomStorage, + ILogManager? logManager, + IReceiptsRecovery? receiptsRecovery, + int maxBlockDepth = 1000) + : ILogFinder { private static int ParallelExecutions = 0; private static int ParallelLock = 0; - private readonly IReceiptFinder _receiptFinder; - private readonly IReceiptStorage _receiptStorage; - private readonly IBloomStorage _bloomStorage; - private readonly IReceiptsRecovery _receiptsRecovery; - private readonly int _maxBlockDepth; - private readonly int _rpcConfigGetLogsThreads; - private readonly IBlockFinder _blockFinder; - private readonly ILogger _logger; - - public LogFinder(IBlockFinder? blockFinder, - IReceiptFinder? receiptFinder, - IReceiptStorage? receiptStorage, - IBloomStorage? bloomStorage, - ILogManager? logManager, - IReceiptsRecovery? receiptsRecovery, - int maxBlockDepth = 1000) - { - _blockFinder = blockFinder ?? throw new ArgumentNullException(nameof(blockFinder)); - _receiptFinder = receiptFinder ?? throw new ArgumentNullException(nameof(receiptFinder)); - _receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage)); ; - _bloomStorage = bloomStorage ?? throw new ArgumentNullException(nameof(bloomStorage)); - _receiptsRecovery = receiptsRecovery ?? throw new ArgumentNullException(nameof(receiptsRecovery)); - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - _maxBlockDepth = maxBlockDepth; - _rpcConfigGetLogsThreads = Math.Max(1, Environment.ProcessorCount / 4); - } + private readonly IReceiptFinder _receiptFinder = receiptFinder ?? throw new ArgumentNullException(nameof(receiptFinder)); + private readonly IReceiptStorage _receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage)); + private readonly IBloomStorage _bloomStorage = bloomStorage ?? throw new ArgumentNullException(nameof(bloomStorage)); + private readonly IReceiptsRecovery _receiptsRecovery = receiptsRecovery ?? throw new ArgumentNullException(nameof(receiptsRecovery)); + private readonly int _rpcConfigGetLogsThreads = Math.Max(1, Environment.ProcessorCount / 4); + private readonly IBlockFinder _blockFinder = blockFinder ?? throw new ArgumentNullException(nameof(blockFinder)); + private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); public IEnumerable FindLogs(LogFilter filter, CancellationToken cancellationToken = default) { @@ -65,7 +54,7 @@ BlockHeader FindHeader(BlockParameter blockParameter, string name, bool headLimi return FindLogs(filter, fromBlock, toBlock, cancellationToken); } - public IEnumerable FindLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken = default) + public virtual IEnumerable FindLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -97,36 +86,37 @@ public IEnumerable FindLogs(LogFilter filter, BlockHeader fromBlock, private static bool ShouldUseBloomDatabase(BlockHeader fromBlock, BlockHeader toBlock) { - var blocksToSearch = toBlock.Number - fromBlock.Number + 1; + long blocksToSearch = toBlock.Number - fromBlock.Number + 1; return blocksToSearch > 1; // if we are searching only in 1 block skip bloom index altogether, this can be tweaked } private IEnumerable FilterLogsWithBloomsIndex(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken) { - BlockHeader FindBlockHeader(long blockNumber, CancellationToken token) + IEnumerable EnumerateBlockNumbers(LogFilter f, long from, long to) { - token.ThrowIfCancellationRequested(); - var block = _blockFinder.FindHeader(blockNumber); - if (block is null) + IBloomEnumeration enumeration = _bloomStorage.GetBlooms(from, to); + foreach (Bloom bloom in enumeration) { - if (_logger.IsError) _logger.Error($"Could not find block {blockNumber} in database. eth_getLogs will return incomplete results."); + if (f.Matches(bloom) && enumeration.TryGetBlockNumber(out var blockNumber)) + { + yield return blockNumber; + } } - - return block; } - IEnumerable FilterBlocks(LogFilter f, long @from, long to, bool runParallel, CancellationToken token) + return FilterLogsInBlocksParallel(filter, EnumerateBlockNumbers(filter, fromBlock.Number, toBlock.Number), cancellationToken); + } + + protected IEnumerable FilterLogsInBlocksParallel(LogFilter filter, IEnumerable blockNumbers, CancellationToken cancellationToken) + { + static IEnumerable ParallelizeWithLock(IEnumerable blocks, bool runParallel, CancellationToken ct) { try { - var enumeration = _bloomStorage.GetBlooms(from, to); - foreach (var bloom in enumeration) + foreach (long blockNumber in blocks) { - token.ThrowIfCancellationRequested(); - if (f.Matches(bloom) && enumeration.TryGetBlockNumber(out var blockNumber)) - { - yield return blockNumber; - } + yield return blockNumber; + ct.ThrowIfCancellationRequested(); } } finally @@ -145,7 +135,7 @@ IEnumerable FilterBlocks(LogFilter f, long @from, long to, bool runParalle int parallelExecutions = Interlocked.Increment(ref ParallelExecutions) - 1; bool canRunParallel = parallelLock == 0; - IEnumerable filterBlocks = FilterBlocks(filter, fromBlock.Number, toBlock.Number, canRunParallel, cancellationToken); + IEnumerable filterBlocks = ParallelizeWithLock(blockNumbers, canRunParallel, cancellationToken); if (canRunParallel) { @@ -160,7 +150,7 @@ IEnumerable FilterBlocks(LogFilter f, long @from, long to, bool runParalle } return filterBlocks - .SelectMany(blockNumber => FindLogsInBlock(filter, FindBlockHeader(blockNumber, cancellationToken), cancellationToken)); + .SelectMany(blockNumber => FindLogsInBlock(filter, FindHeaderOrLogError(blockNumber, cancellationToken), cancellationToken)); } private bool CanUseBloomDatabase(BlockHeader toBlock, BlockHeader fromBlock) @@ -191,7 +181,7 @@ private bool CanUseBloomDatabase(BlockHeader toBlock, BlockHeader fromBlock) private IEnumerable FilterLogsIteratively(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken) { int count = 0; - while (count < _maxBlockDepth && fromBlock.Number <= (toBlock?.Number ?? fromBlock.Number)) + while (count < maxBlockDepth && fromBlock.Number <= (toBlock?.Number ?? fromBlock.Number)) { foreach (var filterLog in FindLogsInBlock(filter, fromBlock, cancellationToken)) { @@ -206,11 +196,11 @@ private IEnumerable FilterLogsIteratively(LogFilter filter, BlockHead } private IEnumerable FindLogsInBlock(LogFilter filter, BlockHeader block, CancellationToken cancellationToken) => - filter.Matches(block.Bloom) + filter.Matches(block.Bloom!) ? FindLogsInBlock(filter, block.Hash, block.Number, block.Timestamp, cancellationToken) : []; - private IEnumerable FindLogsInBlock(LogFilter filter, Hash256 blockHash, long blockNumber, ulong blockTimestamp, CancellationToken cancellationToken) + private IEnumerable FindLogsInBlock(LogFilter filter, Hash256? blockHash, long blockNumber, ulong blockTimestamp, CancellationToken cancellationToken) { if (blockHash is not null) { @@ -348,5 +338,18 @@ void RecoverReceiptsData(Hash256 hash, TxReceipt[] receipts) } } } + + protected BlockHeader? FindHeaderOrLogError(long blockNumber, CancellationToken token) + { + token.ThrowIfCancellationRequested(); + + BlockHeader? block = _blockFinder.FindHeader(blockNumber); + if (block is null && _logger.IsError) + { + _logger.Error($"Could not find block {blockNumber} in database. eth_getLogs will return incomplete results."); + } + + return block; + } } } diff --git a/src/Nethermind/Nethermind.Facade/Find/LogIndexBuilder.cs b/src/Nethermind/Nethermind.Facade/Find/LogIndexBuilder.cs new file mode 100644 index 000000000000..3112b36545f6 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Find/LogIndexBuilder.cs @@ -0,0 +1,495 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Receipts; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Core; +using Nethermind.Db.LogIndex; +using Nethermind.Logging; +using Timer = System.Timers.Timer; +using static System.Threading.Tasks.TaskCreationOptions; + +namespace Nethermind.Facade.Find; + +// TODO: reduce periodic logging +public sealed class LogIndexBuilder : ILogIndexBuilder +{ + private sealed class ProcessingQueue( + TransformBlock, LogIndexAggregate> aggregateBlock, + ActionBlock addReceiptsBlock) + { + public int QueueCount => aggregateBlock.InputCount + addReceiptsBlock.InputCount; + public Task WriteAsync(IReadOnlyList batch, CancellationToken cancellation) => aggregateBlock.SendAsync(batch, cancellation); + public Task Completion => Task.WhenAll(aggregateBlock.Completion, addReceiptsBlock.Completion); + } + + private struct DirectionState() + { + public ProcessingQueue? Queue; + public ProgressLogger? Progress; + public readonly TaskCompletionSource Completion = new(RunContinuationsAsynchronously); + } + + [InlineArray(2)] + private struct DirectionStates + { + private DirectionState _element; + } + + private readonly IBlockTree _blockTree; + private readonly ISyncConfig _syncConfig; + private readonly ILogger _logger; + + private readonly CancellationTokenSource _cancellationSource = new(); + private CancellationToken CancellationToken => _cancellationSource.Token; + + private int MaxReorgDepth => _config.MaxReorgDepth!.Value; + private static readonly TimeSpan NewBlockWaitTimeout = TimeSpan.FromSeconds(5); + + private readonly ILogIndexStorage _logIndexStorage; + private readonly ILogIndexConfig _config; + private readonly IReceiptStorage _receiptStorage; + private readonly ILogManager _logManager; + private Timer? _progressLoggerTimer; + + private readonly TaskCompletionSource _pivotSource = new(RunContinuationsAsynchronously); + private readonly Task _pivotTask; + + private readonly List _tasks = new(); + + private DirectionStates _directions; + + private ref DirectionState Direction(bool isForward) => ref _directions[isForward ? 1 : 0]; + + private LogIndexUpdateStats _stats; + + public string Description => "log index builder"; + + public Task BackwardSyncCompletion => Direction(isForward: false).Completion.Task; + + public LogIndexBuilder(ILogIndexStorage logIndexStorage, ILogIndexConfig config, + IBlockTree blockTree, ISyncConfig syncConfig, IReceiptStorage receiptStorage, + ILogManager logManager) + { + ArgumentNullException.ThrowIfNull(logIndexStorage); + ArgumentNullException.ThrowIfNull(blockTree); + ArgumentNullException.ThrowIfNull(receiptStorage); + ArgumentNullException.ThrowIfNull(logManager); + ArgumentNullException.ThrowIfNull(syncConfig); + + _config = config; + _logIndexStorage = logIndexStorage; + _blockTree = blockTree; + _syncConfig = syncConfig; + _receiptStorage = receiptStorage; + _logManager = logManager; + _logger = logManager.GetClassLogger(); + _pivotTask = _pivotSource.Task; + _stats = new(_logIndexStorage); + + Direction(isForward: false) = new(); + Direction(isForward: true) = new(); + } + + private void StartProcessing(bool isForward) + { + // Do not start backward sync if the target is already reached + if (!isForward && _logIndexStorage.MinBlockNumber <= MinTargetBlockNumber) + { + MarkCompleted(false); + return; + } + + ref DirectionState dir = ref Direction(isForward); + dir.Queue = BuildQueue(isForward); + dir.Progress = new(GetLogPrefix(isForward), _logManager); + + _tasks.AddRange( + Task.Run(() => DoQueueBlocks(isForward), CancellationToken), + dir.Queue.Completion + ); + } + + public async Task StartAsync() + { + try + { + if (!_config.Enabled) + return; + + _receiptStorage.ReceiptsInserted += OnReceiptsInserted; + + TrySetPivot(_logIndexStorage.MaxBlockNumber); + TrySetPivot((int)_blockTree.SyncPivot.BlockNumber); + + if (!_pivotTask.IsCompleted && _logger.IsInfo) + _logger.Info($"{GetLogPrefix()}: waiting for the first block..."); + + await _pivotTask; + + StartProcessing(isForward: true); + StartProcessing(isForward: false); + + UpdateProgress(); + LogProgress(); + + _progressLoggerTimer = new(TimeSpan.FromSeconds(30)); + _progressLoggerTimer.AutoReset = true; + _progressLoggerTimer.Elapsed += (_, _) => LogProgress(); + _progressLoggerTimer.Start(); + + IsRunning = true; + } + catch (Exception ex) + { + await HandleExceptionAsync(ex); + } + } + + public async Task StopAsync() + { + if (!_config.Enabled) + return; + + await _cancellationSource.CancelAsync(); + + _pivotSource.TrySetCanceled(CancellationToken); + _progressLoggerTimer?.Stop(); + + foreach (Task task in _tasks) + { + try + { + await task; + } + catch (Exception ex) + { + await HandleExceptionAsync(ex, isStopping: true); + } + } + + await _logIndexStorage.StopAsync(); + + IsRunning = false; + } + + public async ValueTask DisposeAsync() + { + await _logIndexStorage.DisposeAsync(); + _progressLoggerTimer?.Dispose(); + _cancellationSource.Dispose(); + } + + private void LogStats() + { + LogIndexUpdateStats stats = _stats; + + if (stats is not { BlocksAdded: > 0 }) + return; + + _stats = new(_logIndexStorage); + + if (_logger.IsInfo) + { + _logger.Info(_config.DetailedLogs + ? $"{GetLogPrefix()}: {stats:d}" + : $"{GetLogPrefix()}: {stats}" + ); + } + } + + private void LogProgress() + { + LogStats(); + Direction(isForward: false).Progress?.LogProgress(); + Direction(isForward: true).Progress?.LogProgress(); + } + + private bool TrySetPivot(int? blockNumber) + { + if (blockNumber is not { } number || number is 0) + return false; + + if (_pivotSource.Task.IsCompleted) + return false; + + number = Math.Max(MinTargetBlockNumber, number); + number = Math.Min(MaxTargetBlockNumber, number); + + if (number is 0) + return false; + + if (!TryGetBlockReceipts(number, out _)) + return false; + + if (!_pivotSource.TrySetResult(number)) + return false; + + _logger.Info($"{GetLogPrefix()}: using block {number} as pivot."); + return true; + } + + private void OnReceiptsInserted(object? sender, ReceiptsEventArgs args) + { + int next = (int)args.BlockHeader.Number; + if (TrySetPivot(next)) + _receiptStorage.ReceiptsInserted -= OnReceiptsInserted; + } + + public int MaxTargetBlockNumber => (int)Math.Max(_blockTree.BestKnownNumber - MaxReorgDepth, 0); + + // Block 0 should always be present + public int MinTargetBlockNumber => (int)(_syncConfig.AncientReceiptsBarrierCalc <= 1 ? 0 : _syncConfig.AncientReceiptsBarrierCalc); + + public bool IsRunning { get; private set; } + public DateTimeOffset? LastUpdate { get; private set; } + public Exception? LastError { get; private set; } + + private ProcessingQueue BuildQueue(bool isForward) + { + var aggregateBlock = new TransformBlock, LogIndexAggregate>( + batch => Aggregate(batch, isForward), + new() + { + BoundedCapacity = _config.MaxAggregationQueueSize, + MaxDegreeOfParallelism = _config.MaxAggregationParallelism, + CancellationToken = CancellationToken, + SingleProducerConstrained = true + } + ); + + var addReceiptsBlock = new ActionBlock( + aggr => AddReceiptsAsync(aggr, isForward), + new() + { + BoundedCapacity = _config.MaxSavingQueueSize, + MaxDegreeOfParallelism = 1, + CancellationToken = CancellationToken, + SingleProducerConstrained = true + } + ); + + aggregateBlock.Completion.ContinueWith(t => HandleExceptionAsync(t.Exception), TaskContinuationOptions.OnlyOnFaulted); + addReceiptsBlock.Completion.ContinueWith(t => HandleExceptionAsync(t.Exception), TaskContinuationOptions.OnlyOnFaulted); + + aggregateBlock.LinkTo(addReceiptsBlock, new() { PropagateCompletion = true }); + return new(aggregateBlock, addReceiptsBlock); + } + + private async Task HandleExceptionAsync(Exception? exception, bool isStopping = false) + { + if (exception is null) + return; + + if (exception is AggregateException a) + exception = a.InnerException; + + if (exception is OperationCanceledException oc && oc.CancellationToken == CancellationToken) + return; // Cancelled + + if (_logger.IsError) + _logger.Error($"{GetLogPrefix()} failed. Please restart the client.", exception); + + LastError = exception; + + Direction(isForward: false).Completion.TrySetException(exception!); + Direction(isForward: true).Completion.TrySetException(exception!); + + if (!isStopping) + await StopAsync(); + } + + private LogIndexAggregate Aggregate(IReadOnlyList batch, bool isForward) => _logIndexStorage.Aggregate(batch, !isForward, _stats); + + private async Task AddReceiptsAsync(LogIndexAggregate aggregate, bool isForward) + { + if (GetNextBlockNumber(_logIndexStorage, isForward) is { } next && next != aggregate.FirstBlockNum) + throw new($"{GetLogPrefix(isForward)}: non sequential batches: ({aggregate.FirstBlockNum} instead of {next})."); + + await _logIndexStorage.AddReceiptsAsync(aggregate, _stats); + LastUpdate = DateTimeOffset.Now; + + UpdateProgress(); + + if (_logIndexStorage.MinBlockNumber <= MinTargetBlockNumber) + MarkCompleted(false); + } + + private async Task DoQueueBlocks(bool isForward) + { + try + { + int pivotNumber = await _pivotTask; + + ProcessingQueue queue = Direction(isForward).Queue!; + + int? next = GetNextBlockNumber(isForward); + if (next is not { } start) + { + if (isForward) + { + start = pivotNumber; + } + else + { + start = pivotNumber - 1; + } + } + + BlockReceipts[] buffer = new BlockReceipts[_config.MaxBatchSize]; + while (!CancellationToken.IsCancellationRequested) + { + if (!isForward && start < MinTargetBlockNumber) + { + if (_logger.IsTrace) + _logger.Trace($"{GetLogPrefix(isForward)}: queued last block"); + + return; + } + + int batchSize = _config.MaxBatchSize; + int end = isForward ? start + batchSize - 1 : start - batchSize + 1; + end = Math.Max(end, MinTargetBlockNumber); + end = Math.Min(end, MaxTargetBlockNumber); + + // from - inclusive, to - exclusive + var (from, to) = isForward + ? (start, end + 1) + : (end, start + 1); + + var timestamp = Stopwatch.GetTimestamp(); + Array.Clear(buffer); + ReadOnlySpan batch = GetNextBatch(from, to, buffer, isForward, CancellationToken); + + if (batch.Length == 0) + { + // TODO: stop waiting immediately when receipts become available + await Task.Delay(NewBlockWaitTimeout, CancellationToken); + continue; + } + + _stats.LoadingReceipts.Include(Stopwatch.GetElapsedTime(timestamp)); + + start = GetNextBlockNumber(batch[^1].BlockNumber, isForward); + await queue.WriteAsync(batch.ToArray(), CancellationToken); + } + } + catch (Exception ex) + { + await HandleExceptionAsync(ex); + } + + if (_logger.IsTrace) + _logger.Trace($"{GetLogPrefix(isForward)}: queueing completed."); + } + + private void UpdateProgress() + { + if (!_pivotTask.IsCompletedSuccessfully) return; + var pivotNumber = _pivotTask.Result; + + DirectionState forward = Direction(isForward: true); + if (forward.Progress is { HasEnded: false } forwardProgress) + { + forwardProgress.TargetValue = Math.Max(0, _blockTree.BestKnownNumber - MaxReorgDepth - pivotNumber + 1); + forwardProgress.Update(_logIndexStorage.MaxBlockNumber is { } max ? max - pivotNumber + 1 : 0); + forwardProgress.CurrentQueued = forward.Queue!.QueueCount; + } + + DirectionState backward = Direction(isForward: false); + if (backward.Progress is { HasEnded: false } backwardProgress) + { + backwardProgress.TargetValue = pivotNumber - MinTargetBlockNumber; + backwardProgress.Update(_logIndexStorage.MinBlockNumber is { } min ? pivotNumber - min : 0); + backwardProgress.CurrentQueued = backward.Queue!.QueueCount; + } + } + + private void MarkCompleted(bool isForward) + { + DirectionState dir = Direction(isForward); + if (!dir.Completion.TrySetResult()) + return; + + dir.Progress?.MarkEnd(); + + if (_logger.IsInfo) + _logger.Info($"{GetLogPrefix(isForward)}: completed."); + } + + private static int? GetNextBlockNumber(ILogIndexStorage storage, bool isForward) => isForward ? storage.MaxBlockNumber + 1 : storage.MinBlockNumber - 1; + + private int? GetNextBlockNumber(bool isForward) => GetNextBlockNumber(_logIndexStorage, isForward); + + private static int GetNextBlockNumber(int last, bool isForward) => isForward ? last + 1 : last - 1; + + private ReadOnlySpan GetNextBatch(int from, int to, BlockReceipts[] buffer, bool isForward, CancellationToken token) + { + if (to <= from) + return ReadOnlySpan.Empty; + + if (to - from > buffer.Length) + throw new InvalidOperationException($"{GetLogPrefix()}: buffer size is too small: {buffer.Length} / {to - from}"); + + // Check the immediate next block first + int nextIndex = isForward ? from : to - 1; + if (!TryGetBlockReceipts(nextIndex, out buffer[0])) + return ReadOnlySpan.Empty; + + Parallel.For(from, to, new() + { + CancellationToken = token, + MaxDegreeOfParallelism = _config.MaxReceiptsParallelism + }, i => + { + int bufferIndex = isForward ? i - from : to - 1 - i; + if (buffer[bufferIndex] == default) + TryGetBlockReceipts(i, out buffer[bufferIndex]); + }); + + int endIndex = Array.IndexOf(buffer, default); + return endIndex < 0 ? buffer : buffer.AsSpan(..endIndex); + } + + // TODO: move to IReceiptStorage? + private bool TryGetBlockReceipts(int i, out BlockReceipts blockReceipts) + { + blockReceipts = default; + + if (_blockTree.FindBlock(i, BlockTreeLookupOptions.ExcludeTxHashes) is not { Hash: not null } block) + { + return false; + } + + if (!block.Header.HasTransactions) + { + blockReceipts = new(i, []); + return true; + } + + TxReceipt[] receipts = _receiptStorage.Get(block) ?? []; + + if (receipts.Length == 0) + { + return false; // block should have transactions but nothing in storage + } + + blockReceipts = new(i, receipts); + return true; + } + + private static string GetLogPrefix(bool? isForward = null) => isForward switch + { + true => "Log index sync (Forward)", + false => "Log index sync (Backward)", + _ => "Log index sync" + }; +} diff --git a/src/Nethermind/Nethermind.Init/Modules/BlockProcessingModule.cs b/src/Nethermind/Nethermind.Init/Modules/BlockProcessingModule.cs index 4165e4fba354..21765cfc0635 100644 --- a/src/Nethermind/Nethermind.Init/Modules/BlockProcessingModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/BlockProcessingModule.cs @@ -63,6 +63,7 @@ protected override void Load(ContainerBuilder builder) .AddScoped() .AddScoped() .AddScoped() + .AddScoped() .AddScoped() .AddScoped((rewardSource, txP) => rewardSource.Get(txP)) .AddScoped((specProvider, blocksConfig) => diff --git a/src/Nethermind/Nethermind.Init/Modules/BlockTreeModule.cs b/src/Nethermind/Nethermind.Init/Modules/BlockTreeModule.cs index e769c59eb754..139dabadad2b 100644 --- a/src/Nethermind/Nethermind.Init/Modules/BlockTreeModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/BlockTreeModule.cs @@ -15,14 +15,16 @@ using Nethermind.Crypto; using Nethermind.Db; using Nethermind.Db.Blooms; +using Nethermind.Db.LogIndex; using Nethermind.Facade.Find; +using Nethermind.Logging; using Nethermind.History; using Nethermind.State.Repositories; using Nethermind.TxPool; namespace Nethermind.Init.Modules; -public class BlockTreeModule(IReceiptConfig receiptConfig) : Autofac.Module +public class BlockTreeModule(IReceiptConfig receiptConfig, ILogIndexConfig logIndexConfig) : Autofac.Module { protected override void Load(ContainerBuilder builder) { @@ -37,16 +39,34 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddSingleton() .AddSingleton((ecdsa, specProvider, receiptConfig) => - new ReceiptsRecovery(ecdsa, specProvider, !receiptConfig.CompactReceiptStore)) + new ReceiptsRecovery(ecdsa, specProvider, !receiptConfig.CompactReceiptStore) + ) .AddSingleton() .AddSingleton() - .AddSingleton() .Bind() - .AddSingleton() - .AddSingleton((bt) => bt.AsReadOnly()) + .AddSingleton((bt) => bt.AsReadOnly()); + + builder.AddSingleton() + .AddDecorator((ctx, config) => + { + IPruningConfig pruningConfig = ctx.Resolve(); + config.MaxReorgDepth ??= pruningConfig.PruningBoundary; + return config; + }); - ; + if (logIndexConfig.Enabled) + { + builder + .AddSingleton() + .AddSingleton(); + } + else + { + builder + .AddSingleton() + .AddSingleton(); + } if (!receiptConfig.StoreReceipts) { diff --git a/src/Nethermind/Nethermind.Init/Modules/BuiltInStepsModule.cs b/src/Nethermind/Nethermind.Init/Modules/BuiltInStepsModule.cs index a20ca48e5d90..0b27e6863e92 100644 --- a/src/Nethermind/Nethermind.Init/Modules/BuiltInStepsModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/BuiltInStepsModule.cs @@ -33,6 +33,7 @@ public class BuiltInStepsModule : Module typeof(StartBlockProcessor), typeof(StartBlockProducer), typeof(StartMonitoring), + typeof(StartLogIndex) ]; protected override void Load(ContainerBuilder builder) diff --git a/src/Nethermind/Nethermind.Init/Modules/DbModule.cs b/src/Nethermind/Nethermind.Init/Modules/DbModule.cs index 1a3400c7e1f0..0caa67fe530b 100644 --- a/src/Nethermind/Nethermind.Init/Modules/DbModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/DbModule.cs @@ -66,7 +66,6 @@ protected override void Load(ContainerBuilder builder) .AddDatabase(DbNames.Headers) .AddDatabase(DbNames.BlockInfos) .AddDatabase(DbNames.Bloom) - .AddDatabase(DbNames.BlobTransactions) .AddColumnDatabase(DbNames.Receipts) .AddColumnDatabase(DbNames.BlobTransactions) diff --git a/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs b/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs index 07414446bbf9..9133320d1788 100644 --- a/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs +++ b/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs @@ -46,20 +46,22 @@ public MainProcessingContext( .AddSingleton(this) .AddModule(mainProcessingModules) - .AddScoped((branchProcessor) => new BlockchainProcessor( - blockTree, - branchProcessor, - compositeBlockPreprocessorStep, - worldStateManager.GlobalStateReader, - logManager, - new BlockchainProcessor.Options + .AddScoped((branchProcessor, processingStats) => + new BlockchainProcessor( + blockTree, + branchProcessor, + compositeBlockPreprocessorStep, + worldStateManager.GlobalStateReader, + logManager, + new BlockchainProcessor.Options + { + StoreReceiptsByDefault = receiptConfig.StoreReceipts, + DumpOptions = initConfig.AutoDump + }, + processingStats) { - StoreReceiptsByDefault = receiptConfig.StoreReceipts, - DumpOptions = initConfig.AutoDump + IsMainProcessor = true // Manual construction because of this flag }) - { - IsMainProcessor = true // Manual construction because of this flag - }) .AddScoped(ctx => ctx.Resolve()) .AddScoped(ctx => ctx.Resolve()) // And finally, to wrap things up. diff --git a/src/Nethermind/Nethermind.Init/Modules/NethermindModule.cs b/src/Nethermind/Nethermind.Init/Modules/NethermindModule.cs index 41df70db2387..cd17a2f96dc6 100644 --- a/src/Nethermind/Nethermind.Init/Modules/NethermindModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/NethermindModule.cs @@ -15,6 +15,7 @@ using Nethermind.Core.Specs; using Nethermind.Core.Timers; using Nethermind.Crypto; +using Nethermind.Db.LogIndex; using Nethermind.Era1; using Nethermind.JsonRpc; using Nethermind.Logging; @@ -54,7 +55,7 @@ protected override void Load(ContainerBuilder builder) .AddModule(new EraModule()) .AddSource(new ConfigRegistrationSource()) .AddModule(new BlockProcessingModule(configProvider.GetConfig(), configProvider.GetConfig())) - .AddModule(new BlockTreeModule(configProvider.GetConfig())) + .AddModule(new BlockTreeModule(configProvider.GetConfig(), configProvider.GetConfig())) .AddModule(new MonitoringModule(configProvider.GetConfig())) .AddSingleton() diff --git a/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs b/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs index c9ff690ad826..3cf04ff24cc0 100644 --- a/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs +++ b/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs @@ -13,6 +13,7 @@ using Nethermind.Core.Timers; using Nethermind.Facade; using Nethermind.Facade.Eth; +using Nethermind.Facade.Find; using Nethermind.Facade.Simulate; using Nethermind.Init.Steps.Migrations; using Nethermind.JsonRpc; @@ -21,6 +22,7 @@ using Nethermind.JsonRpc.Modules.DebugModule; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.JsonRpc.Modules.Eth.FeeHistory; +using Nethermind.JsonRpc.Modules.LogIndex; using Nethermind.JsonRpc.Modules.Net; using Nethermind.JsonRpc.Modules.Parity; using Nethermind.JsonRpc.Modules.Personal; @@ -58,6 +60,7 @@ protected override void Load(ContainerBuilder builder) .RegisterSingletonJsonRpcModule() .RegisterSingletonJsonRpcModule() .RegisterSingletonJsonRpcModule() + .RegisterSingletonJsonRpcModule() // Txpool rpc .RegisterSingletonJsonRpcModule() diff --git a/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs b/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs index 9b8e0542617e..5cfab78ac27b 100644 --- a/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs +++ b/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs @@ -17,6 +17,7 @@ using Nethermind.Core.Timers; using Nethermind.Db; using Nethermind.Db.FullPruning; +using Nethermind.Db.LogIndex; using Nethermind.Db.Rocks.Config; using Nethermind.Evm.State; using Nethermind.JsonRpc.Modules.Admin; @@ -177,6 +178,7 @@ public MainPruningTrieStoreFactory( IFinalizedStateProvider finalizedStateProvider, IBlockTree blockTree, IDbConfig dbConfig, + ILogIndexConfig logIndexConfig, IHardwareInfo hardwareInfo, ILogManager logManager ) @@ -187,6 +189,9 @@ ILogManager logManager if (syncConfig.SnapServingEnabled == true && pruningConfig.PruningBoundary < syncConfig.SnapServingMaxDepth) { + // use PruningBoundary for log-index MaxReorgDepth before it's overwritten + logIndexConfig.MaxReorgDepth ??= pruningConfig.PruningBoundary; + if (_logger.IsInfo) _logger.Info($"Snap serving enabled, but {nameof(pruningConfig.PruningBoundary)} is less than {syncConfig.SnapServingMaxDepth}. Setting to {syncConfig.SnapServingMaxDepth}."); pruningConfig.PruningBoundary = syncConfig.SnapServingMaxDepth; } diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs index aa64b11639b8..8e866c3ac38a 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs @@ -32,7 +32,6 @@ namespace Nethermind.Init.Steps public class InitializeBlockchain(INethermindApi api, IChainHeadInfoProvider chainHeadInfoProvider) : IStep { private readonly INethermindApi _api = api; - private ILogManager _logManager = api.LogManager; public async Task Execute(CancellationToken _) { diff --git a/src/Nethermind/Nethermind.Init/Steps/StartLogIndex.cs b/src/Nethermind/Nethermind.Init/Steps/StartLogIndex.cs new file mode 100644 index 000000000000..ef329611cf29 --- /dev/null +++ b/src/Nethermind/Nethermind.Init/Steps/StartLogIndex.cs @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Api; +using Nethermind.Api.Steps; +using Nethermind.Core.ServiceStopper; +using Nethermind.Db; +using Nethermind.Db.LogIndex; +using Nethermind.Facade.Find; + +namespace Nethermind.Init.Steps +{ + [RunnerStepDependencies(typeof(InitDatabase), typeof(StartBlockProcessor))] + public class StartLogIndex(IBasicApi api, ILogIndexBuilder logIndexBuilder) : IStep + { + public Task Execute(CancellationToken cancellationToken) + { + if (api.Config().Enabled) + _ = logIndexBuilder.StartAsync(); + + return Task.CompletedTask; + } + + public bool MustInitialize => false; + } +} diff --git a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs index fb5104b65651..7544c8a272bf 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs @@ -25,6 +25,7 @@ using Nethermind.Wallet; using Nethermind.Config; using Nethermind.Core.Test.Modules; +using Nethermind.Db.LogIndex; using Nethermind.Network; namespace Nethermind.JsonRpc.Benchmark @@ -80,6 +81,7 @@ public void GlobalSetup() feeHistoryOracle, _container.Resolve(), _container.Resolve(), + new LogIndexConfig(), new BlocksConfig().SecondsPerSlot); } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs index 99d9b33ba9c8..3ecf250af76c 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using FluentAssertions; using Nethermind.Core.Extensions; +using Nethermind.Config; using Nethermind.Logging; using Nethermind.JsonRpc.Modules; using NSubstitute; @@ -401,6 +402,65 @@ public async Task Can_handle_null_request() result.DisposeItems(); } + [Test] + public async Task Should_stop_processing_when_shutdown_requested() + { + IJsonRpcService service = Substitute.For(); + service.GetErrorResponse(Arg.Any(), Arg.Any()) + .Returns(new JsonRpcErrorResponse { Error = new Error { Code = ErrorCodes.ResourceUnavailable, Message = "Shutting down" } }); + + IProcessExitSource processExitSource = Substitute.For(); + processExitSource.Token.Returns(new CancellationToken(canceled: true)); + + JsonRpcProcessor processor = new( + service, + new JsonRpcConfig(), + Substitute.For(), + LimboLogs.Instance, + processExitSource); + + string request = "{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"; + List results = await processor.ProcessAsync(request, new JsonRpcContext(RpcEndpoint.Http)).ToListAsync(); + + results.Should().HaveCount(1); + results[0].Response.Should().BeOfType(); + ((JsonRpcErrorResponse)results[0].Response!).Error!.Code.Should().Be(ErrorCodes.ResourceUnavailable); + await service.DidNotReceive().SendRequestAsync(Arg.Any(), Arg.Any()); + results.DisposeItems(); + } + + [Test] + public async Task Should_complete_pipe_reader_when_shutdown_requested() + { + IJsonRpcService service = Substitute.For(); + service.GetErrorResponse(Arg.Any(), Arg.Any()) + .Returns(new JsonRpcErrorResponse { Error = new Error { Code = ErrorCodes.ResourceUnavailable, Message = "Shutting down" } }); + + IProcessExitSource processExitSource = Substitute.For(); + processExitSource.Token.Returns(new CancellationToken(canceled: true)); + + JsonRpcProcessor processor = new( + service, + new JsonRpcConfig(), + Substitute.For(), + LimboLogs.Instance, + processExitSource); + + Pipe pipe = new(); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\",\"params\":[]}")); + + List results = await processor.ProcessAsync(pipe.Reader, new JsonRpcContext(RpcEndpoint.Http)).ToListAsync(); + + results.Should().HaveCount(1); + results[0].Response.Should().BeOfType(); + + // Verify PipeReader was completed by the processor (reading again should throw) + await FluentActions.Invoking(async () => await pipe.Reader.ReadAsync()) + .Should().ThrowAsync(); + + results.DisposeItems(); + } + [Test] public void Cannot_accept_null_file_system() { diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs index 11e1e43e21e4..82e655f5f597 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs @@ -6,6 +6,7 @@ using Nethermind.Config; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; +using Nethermind.Db.LogIndex; using Nethermind.JsonRpc.Modules; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Logging; @@ -56,7 +57,8 @@ public Task Initialize() Substitute.For(), Substitute.For(), new BlocksConfig(), - Substitute.For()), + Substitute.For(), + Substitute.For()), 1, 1000); return Task.CompletedTask; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs index 20cd1112eba5..a7647dc34534 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs @@ -1173,7 +1173,7 @@ public async Task Send_raw_transaction_will_send_transaction(string rawTransacti string serialized = await ctx.Test.TestEthRpc("eth_sendRawTransaction", rawTransaction); Transaction tx = Rlp.Decode(Bytes.FromHexString(rawTransaction)); await txSender.Received().SendTransaction(tx, TxHandlingOptions.PersistentBroadcast); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32010,\"message\":\"Invalid, InvalidTxSignature: Signature is invalid.\"},\"id\":67}")); + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"Invalid, InvalidTxSignature: Signature is invalid.\"},\"id\":67}")); } [Test] @@ -1224,7 +1224,7 @@ public async Task Send_transaction_should_return_ErrorCode_if_tx_not_added() string serialized = await ctx.Test.TestEthRpc("eth_sendTransaction", txForRpc); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32010,\"message\":\"InsufficientFunds, Balance is zero, cannot pay gas\"},\"id\":67}")); + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"InsufficientFunds, Balance is zero, cannot pay gas\"},\"id\":67}")); } public enum AccessListProvided diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs index cde82c8033a5..1033def4ff7e 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs @@ -19,6 +19,7 @@ using Nethermind.JsonRpc.Modules.Eth.FeeHistory; using Nethermind.JsonRpc.Modules.Eth.GasPrice; using Nethermind.Config; +using Nethermind.Db.LogIndex; using Nethermind.Network; using Nethermind.State; @@ -55,7 +56,8 @@ public Task Initialize() Substitute.For(), Substitute.For(), new BlocksConfig(), - Substitute.For()); + Substitute.For(), + Substitute.For()); return Task.CompletedTask; } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs index a00b1905ef40..77ba56292c40 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs @@ -34,6 +34,7 @@ using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Test.Container; +using Nethermind.Db.LogIndex; using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Modules; using Nethermind.JsonRpc.Modules.Trace; @@ -58,6 +59,7 @@ public class TestRpcBlockchain : TestBlockchain public IReceiptFinder ReceiptFinder => Container.Resolve(); public IGasPriceOracle GasPriceOracle { get; private set; } = null!; public IProtocolsManager ProtocolsManager { get; private set; } = null!; + public ILogIndexConfig LogIndexConfig { get; } = new LogIndexConfig(); public IKeyStore KeyStore { get; } = new MemKeyStore(TestItem.PrivateKeys, Path.Combine("testKeyStoreDir", Path.GetRandomFileName())); public IWallet TestWallet { get; } = @@ -193,6 +195,7 @@ public async Task Build(Action configurer) new FeeHistoryOracle(@this.BlockTree, @this.ReceiptStorage, @this.SpecProvider), @this.ProtocolsManager, @this.ForkInfo, + @this.LogIndexConfig, @this.BlocksConfig.SecondsPerSlot); protected override async Task Build(Action? configurer = null) @@ -255,7 +258,7 @@ public async Task RestartBlockchainProcessor() // simulating restarts - we stopped the old blockchain processor and create the new one _currentBlockchainProcessor = new BlockchainProcessor(BlockTree, BranchProcessor, - BlockPreprocessorStep, StateReader, LimboLogs.Instance, Nethermind.Consensus.Processing.BlockchainProcessor.Options.Default); + BlockPreprocessorStep, StateReader, LimboLogs.Instance, Nethermind.Consensus.Processing.BlockchainProcessor.Options.Default, Substitute.For()); _currentBlockchainProcessor.Start(); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/EthRpcJsonContext.cs b/src/Nethermind/Nethermind.JsonRpc/Data/EthRpcJsonContext.cs new file mode 100644 index 000000000000..a7537c67d09c --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/Data/EthRpcJsonContext.cs @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json.Serialization; +using Nethermind.Blockchain.Tracing.GethStyle; +using Nethermind.Blockchain.Tracing.ParityStyle; +using Nethermind.Evm; +using Nethermind.JsonRpc.Modules.DebugModule; +using Nethermind.JsonRpc.Modules.Eth; +using Nethermind.JsonRpc.Modules.Trace; +using Nethermind.State.Proofs; + +namespace Nethermind.JsonRpc.Data; + +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Metadata, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +// eth_ types +[JsonSerializable(typeof(ReceiptForRpc))] +[JsonSerializable(typeof(LogEntryForRpc))] +[JsonSerializable(typeof(FeeHistoryResults))] +[JsonSerializable(typeof(AccessListResultForRpc))] +[JsonSerializable(typeof(AccountInfoForRpc))] +[JsonSerializable(typeof(AccountOverride))] +[JsonSerializable(typeof(AccountProof))] +[JsonSerializable(typeof(BadBlock))] +// debug_ types +[JsonSerializable(typeof(GethLikeTxTrace))] +[JsonSerializable(typeof(GethTraceOptions))] +[JsonSerializable(typeof(ChainLevelForRpc))] +// trace_ types +[JsonSerializable(typeof(ParityTxTraceFromReplay))] +[JsonSerializable(typeof(ParityTxTraceFromStore))] +[JsonSerializable(typeof(ParityLikeTxTrace))] +[JsonSerializable(typeof(TraceFilterForRpc))] +public partial class EthRpcJsonContext : JsonSerializerContext; diff --git a/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs b/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs index cea92a933c03..47bc3b4dc769 100644 --- a/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs +++ b/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs @@ -48,35 +48,25 @@ public static class ErrorCodes public const int ResourceNotFound = -32000; /// - /// Requested resource not available + /// Transaction creation failed /// - public const int ResourceUnavailable = -32002; + public const int TransactionRejected = -32000; /// - /// Transaction creation failed + /// Requested resource not available /// - public const int TransactionRejected = -32010; + public const int ResourceUnavailable = -32002; /// /// Account locked /// public const int AccountLocked = -32020; - /// - /// Method is not implemented - /// - public const int MethodNotSupported = -32004; - /// /// Request exceeds defined limit /// public const int LimitExceeded = -32005; - /// - /// - /// - public const int ExecutionError = -32015; - /// /// Request exceeds defined timeout limit /// @@ -107,16 +97,6 @@ public static class ErrorCodes /// public const int InvalidInputTooManyBlocks = -38026; - /// - /// Invalid RPC simulate call Not enough gas provided to pay for intrinsic gas for a transaction - /// - public const int InsufficientIntrinsicGas = -38013; - - /// - /// Invalid RPC simulate call transaction - /// - public const int InvalidTransaction = -38014; - /// /// Too many blocks for simulation /// @@ -132,16 +112,6 @@ public static class ErrorCodes /// public const int Default = -32000; - /// - /// Transaction.Nonce is bigger than expected nonce - /// - public const int NonceTooHigh = -38011; - - /// - /// Transaction.Nonce is smaller than expected nonce - /// - public const int NonceTooLow = -38010; - /// /// Invalid intrinsic gas. Miner premium is negative /// @@ -157,21 +127,6 @@ public static class ErrorCodes /// public const int BlockGasLimitReached = -38015; - /// - /// Invalid block number - /// - public const int BlockNumberInvalid = -38020; - - /// - /// Invalid block timestamp - /// - public const int BlockTimestampInvalid = -38021; - - /// - /// Transaction.Sender is not an EOA - /// - public const int SenderIsNotEOA = -38024; - /// /// EIP-3860. Code size is to big /// @@ -186,7 +141,5 @@ public static class ErrorCodes /// Error during EVM execution /// public const int VMError = -32015; - public const int TxSyncTimeout = 4; - public const int ClientLimitExceeded = -38026; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcResult.cs b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcResult.cs deleted file mode 100644 index 845540f0d11e..000000000000 --- a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.JsonRpc -{ - public interface IJsonRpcResult - { - object ToJson(); - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/IStreamableResult.cs b/src/Nethermind/Nethermind.JsonRpc/IStreamableResult.cs new file mode 100644 index 000000000000..6e3c1cf02c03 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/IStreamableResult.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; + +namespace Nethermind.JsonRpc; + +/// +/// Implemented by result objects that can write themselves directly to a , +/// bypassing to avoid extra buffer copies. +/// +public interface IStreamableResult +{ + ValueTask WriteToAsync(PipeWriter writer, CancellationToken cancellationToken); +} diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfigExtension.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfigExtension.cs index ce2181efb4f5..acb929e83633 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfigExtension.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfigExtension.cs @@ -1,15 +1,22 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; namespace Nethermind.JsonRpc { public static class JsonRpcConfigExtension { + private static readonly ConcurrentQueue _ctsPool = new(); + private const int MaxPoolSize = 64; + private static int _ctsPoolSize; + public static void EnableModules(this IJsonRpcConfig config, params string[] modules) { HashSet enabledModules = config.EnabledModules.ToHashSet(); @@ -21,12 +28,45 @@ public static void EnableModules(this IJsonRpcConfig config, params string[] mod } /// - /// Constructs a that timeouts after - /// if is false. + /// Rents a that timeouts after . + /// When debugger is attached, no timeout is applied. + /// Call to return to pool. /// public static CancellationTokenSource BuildTimeoutCancellationToken(this IJsonRpcConfig config) { - return Debugger.IsAttached ? new CancellationTokenSource() : new CancellationTokenSource(config.Timeout); + if (Debugger.IsAttached) + { + return new CancellationTokenSource(); + } + + if (_ctsPool.TryDequeue(out CancellationTokenSource? cts)) + { + Interlocked.Decrement(ref _ctsPoolSize); + cts.CancelAfter(config.Timeout); + return cts; + } + + return new CancellationTokenSource(config.Timeout); + } + + /// + /// Returns a CTS to the pool if it can be reset, otherwise disposes it. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ReturnTimeoutCancellationToken(CancellationTokenSource cts) + { + if (cts.TryReset()) + { + if (Interlocked.Increment(ref _ctsPoolSize) <= MaxPoolSize) + { + _ctsPool.Enqueue(cts); + return; + } + + Interlocked.Decrement(ref _ctsPoolSize); + } + + cts.Dispose(); } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcContext.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcContext.cs index c145607924d5..b8e3f2f803f7 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcContext.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcContext.cs @@ -2,14 +2,16 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Threading; using Nethermind.JsonRpc.Modules; namespace Nethermind.JsonRpc { public class JsonRpcContext : IDisposable { - public static AsyncLocal Current { get; private set; } = new(); + [ThreadStatic] + private static JsonRpcContext? _current; + + public static ThreadStaticAccessor Current { get; } = new(); public static JsonRpcContext Http(JsonRpcUrl url) => new(RpcEndpoint.Http, url: url); public static JsonRpcContext WebSocket(JsonRpcUrl url) => new(RpcEndpoint.Ws, url: url); @@ -20,7 +22,7 @@ public JsonRpcContext(RpcEndpoint rpcEndpoint, IJsonRpcDuplexClient? duplexClien DuplexClient = duplexClient; Url = url; IsAuthenticated = Url?.IsAuthenticated == true || RpcEndpoint == RpcEndpoint.IPC; - Current.Value = this; + _current = this; } public RpcEndpoint RpcEndpoint { get; } @@ -29,9 +31,22 @@ public JsonRpcContext(RpcEndpoint rpcEndpoint, IJsonRpcDuplexClient? duplexClien public bool IsAuthenticated { get; } public void Dispose() { - if (Current.Value == this) + if (_current == this) + { + _current = null; + } + } + + /// + /// Provides .Value accessor compatible with AsyncLocal API shape so callers + /// like JsonRpcContext.Current.Value?.IsAuthenticated continue to compile. + /// + public sealed class ThreadStaticAccessor + { + public JsonRpcContext? Value { - Current.Value = null; + get => _current; + set => _current = value; } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs index bd042072fa1c..8093b2c40c97 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs @@ -9,7 +9,6 @@ using System.IO; using System.IO.Abstractions; using System.IO.Pipelines; -using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; @@ -27,7 +26,7 @@ namespace Nethermind.JsonRpc; -public class JsonRpcProcessor : IJsonRpcProcessor +public sealed class JsonRpcProcessor : IJsonRpcProcessor { private readonly IJsonRpcConfig _jsonRpcConfig; private readonly ILogger _logger; @@ -90,7 +89,7 @@ private JsonRpcRequest DeserializeObject(JsonElement element) { id = idNumber; } - else if (decimal.TryParse(idElement.GetRawText(), out var value)) + else if (idElement.TryGetDecimal(out var value)) { id = value; } @@ -104,7 +103,7 @@ private JsonRpcRequest DeserializeObject(JsonElement element) string? method = null; if (element.TryGetProperty("method"u8, out JsonElement methodElement)) { - method = methodElement.GetString(); + method = InternMethodName(methodElement); } if (!element.TryGetProperty("params"u8, out JsonElement paramsElement)) @@ -121,30 +120,61 @@ private JsonRpcRequest DeserializeObject(JsonElement element) }; } - private ArrayPoolList DeserializeArray(JsonElement element) => - new(element.GetArrayLength(), element.EnumerateArray().Select(DeserializeObject)); + private ArrayPoolList DeserializeArray(JsonElement element) + { + ArrayPoolList list = new(element.GetArrayLength()); + foreach (JsonElement item in element.EnumerateArray()) + { + list.Add(DeserializeObject(item)); + } + return list; + } + + /// + /// Returns a cached string constant for known engine method names to avoid allocation. + /// Falls back to for unknown methods. + /// + private static string? InternMethodName(JsonElement methodElement) + { + if (methodElement.ValueEquals("engine_newPayloadV4"u8)) return "engine_newPayloadV4"; + if (methodElement.ValueEquals("engine_forkchoiceUpdatedV3"u8)) return "engine_forkchoiceUpdatedV3"; + if (methodElement.ValueEquals("engine_newPayloadV3"u8)) return "engine_newPayloadV3"; + if (methodElement.ValueEquals("engine_forkchoiceUpdatedV2"u8)) return "engine_forkchoiceUpdatedV2"; + if (methodElement.ValueEquals("engine_getPayloadV4"u8)) return "engine_getPayloadV4"; + if (methodElement.ValueEquals("engine_getPayloadV3"u8)) return "engine_getPayloadV3"; + if (methodElement.ValueEquals("engine_newPayloadV2"u8)) return "engine_newPayloadV2"; + if (methodElement.ValueEquals("engine_newPayloadV1"u8)) return "engine_newPayloadV1"; + if (methodElement.ValueEquals("engine_exchangeCapabilities"u8)) return "engine_exchangeCapabilities"; + return methodElement.GetString(); + } private static readonly JsonReaderOptions _socketJsonReaderOptions = new() { AllowMultipleValues = true }; public async IAsyncEnumerable ProcessAsync(PipeReader reader, JsonRpcContext context) { - if (ProcessExit.IsCancellationRequested) + // Engine API (authenticated) requests skip the timeout CTS entirely -- they are from + // trusted consensus clients, must complete for consensus, and connection drops are + // handled by the PipeReader. Non-engine paths still use the pooled CTS. + CancellationTokenSource? timeoutSource = context.IsAuthenticated ? null : _jsonRpcConfig.BuildTimeoutCancellationToken(); + CancellationToken timeoutToken = timeoutSource?.Token ?? CancellationToken.None; + try { - JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ResourceUnavailable, "Shutting down"); - yield return JsonRpcResult.Single(RecordResponse(response, new RpcReport("Shutdown", 0, false))); - } + if (ProcessExit.IsCancellationRequested) + { + JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ResourceUnavailable, "Shutting down"); + yield return JsonRpcResult.Single(RecordResponse(response, new RpcReport("Shutdown", 0, false))); + yield break; + } - if (IsRecordingRequest) - { - reader = await RecordRequest(reader); - } + if (IsRecordingRequest) + { + reader = await RecordRequest(reader); + } + + JsonReaderState readerState = CreateJsonReaderState(context); + bool freshState = true; + bool shouldExit = false; - using CancellationTokenSource timeoutSource = _jsonRpcConfig.BuildTimeoutCancellationToken(); - JsonReaderState readerState = CreateJsonReaderState(context); - bool freshState = true; - bool shouldExit = false; - try - { while (!shouldExit) { long startTime = Stopwatch.GetTimestamp(); @@ -152,7 +182,7 @@ public async IAsyncEnumerable ProcessAsync(PipeReader reader, Jso ReadResult readResult; try { - readResult = await reader.ReadAsync(timeoutSource.Token); + readResult = await reader.ReadAsync(timeoutToken); } catch (BadHttpRequestException e) { @@ -231,6 +261,8 @@ public async IAsyncEnumerable ProcessAsync(PipeReader reader, Jso finally { await reader.CompleteAsync(); + if (timeoutSource is not null) + JsonRpcConfigExtension.ReturnTimeoutCancellationToken(timeoutSource); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcResponseJsonContext.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcResponseJsonContext.cs new file mode 100644 index 000000000000..322a04158fa3 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcResponseJsonContext.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json.Serialization; + +namespace Nethermind.JsonRpc; + +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Metadata, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(JsonRpcSuccessResponse))] +[JsonSerializable(typeof(JsonRpcErrorResponse))] +[JsonSerializable(typeof(JsonRpcResponse))] +[JsonSerializable(typeof(Error))] +public partial class JsonRpcResponseJsonContext : JsonSerializerContext; diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs index 1246c6b49cf8..781e7da43de2 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs @@ -9,6 +9,7 @@ using System.Text; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; using Nethermind.Blockchain; @@ -24,7 +25,7 @@ namespace Nethermind.JsonRpc; -public class JsonRpcService : IJsonRpcService +public sealed class JsonRpcService : IJsonRpcService { private readonly static Lock _reparseLock = new(); private static Dictionary _reparseReflectionCache = new(); @@ -344,7 +345,7 @@ private void LogRequest(string methodName, JsonElement providedParameters, Expec { foreach (JsonElement param in providedParameters.EnumerateArray()) { - string? parameter = expectedParameters.ElementAtOrDefault(paramsCount).Info?.Name == "passphrase" + string? parameter = (uint)paramsCount < (uint)expectedParameters.Length && expectedParameters[paramsCount].Info?.Name == "passphrase" ? "{passphrase}" : param.GetRawText(); @@ -403,6 +404,7 @@ private void LogRequest(string methodName, JsonElement providedParameters, Expec } else { + EthereumJsonSerializer.JsonOptions.TryGetTypeInfo(paramType, out JsonTypeInfo? typeInfo); if (providedParameter.ValueKind == JsonValueKind.String) { if (!_reparseReflectionCache.TryGetValue(paramType, out bool reparseString)) @@ -412,11 +414,15 @@ private void LogRequest(string methodName, JsonElement providedParameters, Expec executionParam = reparseString ? JsonSerializer.Deserialize(providedParameter.GetString(), paramType, EthereumJsonSerializer.JsonOptions) - : providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); + : typeInfo is not null + ? providedParameter.Deserialize(typeInfo) + : providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); } else { - executionParam = providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); + executionParam = typeInfo is not null + ? providedParameter.Deserialize(typeInfo) + : providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/TransactionBundle.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/TransactionBundle.cs index 3d1119669d45..4e11f17c79cb 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/TransactionBundle.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/TransactionBundle.cs @@ -5,7 +5,6 @@ using Nethermind.Core; using Nethermind.Evm; using Nethermind.Facade.Eth.RpcTransaction; -using Nethermind.Facade.Proxy.Models.Simulate; namespace Nethermind.JsonRpc.Modules.DebugModule; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs index d5b6001e4aa7..7ead40a36d49 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs @@ -5,6 +5,7 @@ using Nethermind.Blockchain.Receipts; using Nethermind.Config; using Nethermind.Core.Specs; +using Nethermind.Db.LogIndex; using Nethermind.Facade; using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Modules.Eth.GasPrice; @@ -33,7 +34,8 @@ public class EthModuleFactory( IFeeHistoryOracle feeHistoryOracle, IProtocolsManager protocolsManager, IBlocksConfig blocksConfig, - IForkInfo forkInfo) + IForkInfo forkInfo, + ILogIndexConfig logIndexConfig) : ModuleFactoryBase { private readonly ulong _secondsPerSlot = blocksConfig.SecondsPerSlot; @@ -57,6 +59,7 @@ public override IEthRpcModule Create() feeHistoryOracle, protocolsManager, forkInfo, + logIndexConfig, _secondsPerSlot); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index 713bccba0101..bce797084c92 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -9,6 +9,7 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; +using Nethermind.Db.LogIndex; using Nethermind.Evm; using Nethermind.Evm.Precompiles; using Nethermind.Facade; @@ -65,6 +66,7 @@ public partial class EthRpcModule( IFeeHistoryOracle feeHistoryOracle, IProtocolsManager protocolsManager, IForkInfo forkInfo, + ILogIndexConfig? logIndexConfig, ulong? secondsPerSlot) : IEthRpcModule { public const int GetProofStorageKeyLimit = 1000; @@ -645,6 +647,10 @@ public ResultWrapper> eth_getLogs(Filter filter) { LogFilter logFilter = _blockchainBridge.GetFilter(fromBlock, toBlock, filter.Address, filter.Topics); + // ReSharper disable once ConditionIsAlwaysTrueOrFalse - can be null in tests + if (logFilter is not null && filter is not null) + logFilter.UseIndex = filter.UseIndex; + IEnumerable filterLogs = _blockchainBridge.GetLogs(logFilter, fromBlockHeader, toBlockHeader, cancellationToken); ArrayPoolList logs = new(_rpcConfig.MaxLogsPerResponse); @@ -661,6 +667,9 @@ public ResultWrapper> eth_getLogs(Filter filter) } } + if (logIndexConfig?.VerifyRpcResponse is true && logFilter.UseIndex) + VerifyLogsResponse(logs, logFilter, fromBlockHeader, toBlockHeader, cancellationToken); + return ResultWrapper>.Success(logs); } catch (ResourceNotFoundException exception) @@ -839,4 +848,28 @@ public ResultWrapper eth_config() private CancellationTokenSource BuildTimeoutCancellationTokenSource() => _rpcConfig.BuildTimeoutCancellationToken(); + + private void VerifyLogsResponse(IList response, LogFilter filter, BlockHeader from, BlockHeader to, CancellationToken cancellation) + { + filter.UseIndex = false; + IEnumerable? expectedResponse = _blockchainBridge.GetLogs(filter, from, to, cancellation); + + using IEnumerator expectedEnum = expectedResponse.GetEnumerator(); + + var i = -1; + while (++i < response.Count | expectedEnum.MoveNext()) + { + FilterLog? actual = i < response.Count ? response[i] : null; + FilterLog? expected = expectedEnum.Current; + + if ((actual?.BlockNumber, actual?.LogIndex) != (expected?.BlockNumber, expected?.LogIndex)) + { + throw new LogIndexStateException( + $"Incorrect result from log index at position #{i}. " + + $"Expected: block {expected?.BlockNumber}, log #{expected?.LogIndex}. " + + $"Actual: block {actual?.BlockNumber}, log #{actual?.LogIndex}." + ); + } + } + } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs index 57f26cee883a..3f79cbd33cc3 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs @@ -15,12 +15,14 @@ public class Filter : IJsonRpcParam { public HashSet? Address { get; set; } - public BlockParameter FromBlock { get; set; } + public BlockParameter FromBlock { get; set; } = BlockParameter.Latest; - public BlockParameter ToBlock { get; set; } + public BlockParameter ToBlock { get; set; } = BlockParameter.Latest; public IEnumerable? Topics { get; set; } + public bool UseIndex { get; set; } = true; + public void ReadJson(JsonElement filter, JsonSerializerOptions options) { JsonDocument doc = null; @@ -65,6 +67,16 @@ public void ReadJson(JsonElement filter, JsonSerializerOptions options) { Topics = GetTopics(topicsElement, options); } + + if (filter.TryGetProperty("useIndex"u8, out JsonElement useIndex)) + { + UseIndex = useIndex.ValueKind switch + { + JsonValueKind.False => false, + JsonValueKind.True => true, + _ => UseIndex + }; + } } finally { diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/ILogIndexRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/ILogIndexRpcModule.cs new file mode 100644 index 000000000000..9a62dd485a05 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/ILogIndexRpcModule.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.JsonRpc.Modules.Eth; + +namespace Nethermind.JsonRpc.Modules.LogIndex; + +[RpcModule(ModuleType.LogIndex)] +public interface ILogIndexRpcModule : IRpcModule +{ + [JsonRpcMethod(Description = "Retrieves log index block number for the given filter.", IsImplemented = true, IsSharable = true)] + ResultWrapper> logIndex_blockNumbers([JsonRpcParameter] Filter filter); + + [JsonRpcMethod(Description = "Retrieves log index status.", IsImplemented = true, IsSharable = true)] + ResultWrapper logIndex_status(); +} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexRpcModule.cs new file mode 100644 index 000000000000..94c4a48272ba --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexRpcModule.cs @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Blockchain.Filters; +using Nethermind.Blockchain.Find; +using Nethermind.Db.LogIndex; +using Nethermind.Facade; +using Nethermind.Facade.Filters; +using Nethermind.Facade.Find; +using Nethermind.JsonRpc.Modules.Eth; + +namespace Nethermind.JsonRpc.Modules.LogIndex; + +public class LogIndexRpcModule(ILogIndexStorage storage, ILogIndexBuilder builder, IBlockFinder blockFinder, IBlockchainBridge blockchainBridge) + : ILogIndexRpcModule +{ + public ResultWrapper> logIndex_blockNumbers(Filter filter) + { + LogFilter logFilter = blockchainBridge.GetFilter(filter.FromBlock, filter.ToBlock, filter.Address, filter.Topics); + + if (GetBlockNumber(logFilter.FromBlock) is not { } from) + return ResultWrapper>.Fail($"Block {logFilter.FromBlock} is not found.", ErrorCodes.UnknownBlockError); + + if (GetBlockNumber(logFilter.ToBlock) is not { } to) + return ResultWrapper>.Fail($"Block {logFilter.ToBlock} is not found.", ErrorCodes.UnknownBlockError); + + return ResultWrapper>.Success(storage.EnumerateBlockNumbersFor(logFilter, from, to)); + } + + public ResultWrapper logIndex_status() + { + return ResultWrapper.Success(new() + { + Current = new() + { + FromBlock = storage.MinBlockNumber, + ToBlock = storage.MaxBlockNumber + }, + Target = new() + { + FromBlock = builder.MinTargetBlockNumber, + ToBlock = builder.MaxTargetBlockNumber + }, + IsRunning = builder.IsRunning, + LastUpdate = builder.LastUpdate, + LastError = builder.LastError?.ToString(), + DbSize = storage.GetDbSize() + }); + } + + private long? GetBlockNumber(BlockParameter parameter) => + parameter.BlockNumber ?? blockFinder.FindBlock(parameter)?.Number; +} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexStatus.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexStatus.cs new file mode 100644 index 000000000000..47a7d1b10180 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexStatus.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.JsonRpc.Modules.LogIndex; + +public class LogIndexStatus +{ + public readonly record struct Range(int? FromBlock, int? ToBlock); + + public required Range Current { get; init; } + public required Range Target { get; init; } + public bool IsRunning { get; init; } + public DateTimeOffset? LastUpdate { get; init; } + public string? LastError { get; init; } + public required string DbSize { get; init; } +} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs index a93b8fdc203f..3dc4ec0e2707 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs @@ -14,6 +14,7 @@ public static class ModuleType public const string Debug = nameof(Debug); public const string Erc20 = nameof(Erc20); public const string Eth = nameof(Eth); + public const string LogIndex = nameof(LogIndex); public const string Evm = nameof(Evm); public const string Flashbots = nameof(Flashbots); public const string Net = nameof(Net); @@ -25,7 +26,6 @@ public static class ModuleType public const string Trace = nameof(Trace); public const string TxPool = nameof(TxPool); public const string Web3 = nameof(Web3); - public const string Vault = nameof(Vault); public const string Deposit = nameof(Deposit); public const string Health = nameof(Health); public const string Rpc = nameof(Rpc); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs index 2c00637b395e..1b2580b8591f 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs @@ -1054,8 +1054,7 @@ private MultiSyncModeSelector CreateMultiSyncModeSelector(MergeTestBlockchain ch Substitute.For>(), Substitute.For>(), Substitute.For>(), - Substitute.For>(), - LimboLogs.Instance); + Substitute.For>()); MultiSyncModeSelector multiSyncModeSelector = new(syncProgressResolver, syncPeerPool, new SyncConfig(), No.BeaconSync, diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index d8f6335d262b..d0ba23fd16c3 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -13,6 +13,7 @@ using FluentAssertions; using Nethermind.Blockchain; using Nethermind.Blockchain.Find; +using Nethermind.Consensus.Processing; using Nethermind.Consensus.Producers; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -668,7 +669,19 @@ public async Task forkChoiceUpdatedV1_block_still_processing() Block blockTreeHead = chain.BlockTree.Head!; Block block = Build.A.Block.WithNumber(blockTreeHead.Number + 1).WithParent(blockTreeHead).WithNonce(0).WithDifficulty(0).TestObject; - chain.ThrottleBlockProcessor(200); + chain.ThrottleBlockProcessor(1000); + ManualResetEventSlim processingStarted = new(false); + ((TestBranchProcessorInterceptor)chain.BranchProcessor).ProcessingStarted = processingStarted; + + // Directly enqueue a block to occupy the processor (bypasses the RPC semaphore), + // ensuring subsequent blocks route through the recovery queue (slow path) + Block occupyBlock = Build.A.Block.WithNumber(blockTreeHead.Number + 1).WithParent(blockTreeHead) + .WithNonce(0).WithDifficulty(0).WithStateRoot(blockTreeHead.StateRoot!).TestObject; + occupyBlock.Header.TotalDifficulty = blockTreeHead.TotalDifficulty; + _ = Task.Run(async () => await chain.BlockProcessingQueue.Enqueue( + occupyBlock, ProcessingOptions.ForceProcessing | ProcessingOptions.DoNotUpdateHead)); + processingStarted.Wait(TimeSpan.FromSeconds(5)); + ResultWrapper newPayloadV1 = await rpc.engine_newPayloadV1(ExecutionPayload.Create(block)); newPayloadV1.Data.Status.Should().Be("SYNCING"); @@ -749,6 +762,18 @@ public async Task Invalid_block_on_processing_wont_be_accepted_if_sent_twice_in_ .TestObject; chain.ThrottleBlockProcessor(1000); // throttle the block processor enough so that the block processing queue is never empty + ManualResetEventSlim processingStarted = new(false); + ((TestBranchProcessorInterceptor)chain.BranchProcessor).ProcessingStarted = processingStarted; + + // Directly enqueue a block to occupy the processor (bypasses the RPC semaphore), + // ensuring subsequent blocks route through the recovery queue (slow path) + Block occupyBlock = Build.A.Block.WithNumber(head.Number + 1).WithParent(head) + .WithNonce(0).WithDifficulty(0).WithStateRoot(head.StateRoot!).TestObject; + occupyBlock.Header.TotalDifficulty = head.TotalDifficulty; + _ = Task.Run(async () => await chain.BlockProcessingQueue.Enqueue( + occupyBlock, ProcessingOptions.ForceProcessing | ProcessingOptions.DoNotUpdateHead)); + processingStarted.Wait(TimeSpan.FromSeconds(5)); + (await rpc.engine_newPayloadV1(ExecutionPayload.Create(block))).Data.Status.Should().Be(PayloadStatus.Syncing); (await rpc.engine_newPayloadV1(ExecutionPayload.Create(block))).Data.Status.Should().BeOneOf(PayloadStatus.Syncing); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs index 7940bc6c34b7..e34ec509534a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs @@ -4,7 +4,10 @@ using System; using System.Collections.Generic; using System.IO.Abstractions; +using System.IO.Pipelines; using System.Linq; +using System.Text; +using System.Text.Json; using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; @@ -14,6 +17,7 @@ using Nethermind.Blockchain.Synchronization; using Nethermind.Consensus.Producers; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; @@ -683,6 +687,33 @@ public async Task GetBlobsV1_should_return_mix_of_blobs_and_nulls([Values(1, 2, } } + [Test] + public async Task BlobsV1DirectResponse_WriteToAsync_produces_valid_json() + { + byte[] blob = new byte[16]; + Random.Shared.NextBytes(blob); + byte[] proof = new byte[48]; + Random.Shared.NextBytes(proof); + + ArrayPoolList items = new(2); + items.Add(new BlobAndProofV1(blob, proof)); + items.Add(null); + + using BlobsV1DirectResponse response = new(items); + + Pipe pipe = new(); + await response.WriteToAsync(pipe.Writer, CancellationToken.None); + await pipe.Writer.CompleteAsync(); + + ReadResult readResult = await pipe.Reader.ReadAsync(); + string streamedJson = Encoding.UTF8.GetString(readResult.Buffer); + pipe.Reader.AdvanceTo(readResult.Buffer.End); + + string stjJson = JsonSerializer.Serialize(response, EthereumJsonSerializer.JsonOptions); + + streamedJson.Should().Be(stjJson); + } + [Test] public async Task Sync_proper_chain_when_header_fork_came_from_fcu_and_beacon_sync() { diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs index 825da6cc4e1a..218014837ee0 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs @@ -3,7 +3,11 @@ using System; using System.Collections.Generic; +using System.IO.Pipelines; using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using CkzgLib; using FluentAssertions; @@ -14,6 +18,7 @@ using Nethermind.Evm; using Nethermind.JsonRpc; using Nethermind.Merge.Plugin.Data; +using Nethermind.Serialization.Json; using Nethermind.Specs.Forks; using Nethermind.TxPool; using NUnit.Framework; @@ -246,4 +251,67 @@ public async Task GetBlobsV1_should_return_invalid_fork_post_osaka() result.Result.Should().BeEquivalentTo(Result.Fail(MergeErrorMessages.UnsupportedFork)); result.ErrorCode.Should().Be(MergeErrorCodes.UnsupportedFork); } + + [Test] + public async Task BlobsV2DirectResponse_WriteToAsync_produces_valid_json() + { + // Build a small list with one real entry and one null + byte[] blob = new byte[16]; + Random.Shared.NextBytes(blob); + byte[] proof1 = new byte[48]; + Random.Shared.NextBytes(proof1); + byte[] proof2 = new byte[48]; + Random.Shared.NextBytes(proof2); + + byte[]?[] blobs = [blob, null]; + ReadOnlyMemory[] proofs = [new ReadOnlyMemory([proof1, proof2]), default]; + + BlobsV2DirectResponse response = new(blobs, proofs, 2); + + // Write via streaming path + Pipe pipe = new(); + await response.WriteToAsync(pipe.Writer, CancellationToken.None); + await pipe.Writer.CompleteAsync(); + + ReadResult readResult = await pipe.Reader.ReadAsync(); + string streamedJson = Encoding.UTF8.GetString(readResult.Buffer); + pipe.Reader.AdvanceTo(readResult.Buffer.End); + + // Write via STJ for comparison + string stjJson = JsonSerializer.Serialize(response, EthereumJsonSerializer.JsonOptions); + + streamedJson.Should().Be(stjJson); + } + + [Test] + public async Task BlobsV2DirectResponse_WriteToAsync_empty_list() + { + BlobsV2DirectResponse response = new([], [], 0); + + Pipe pipe = new(); + await response.WriteToAsync(pipe.Writer, CancellationToken.None); + await pipe.Writer.CompleteAsync(); + + ReadResult readResult = await pipe.Reader.ReadAsync(); + string json = Encoding.UTF8.GetString(readResult.Buffer); + pipe.Reader.AdvanceTo(readResult.Buffer.End); + + json.Should().Be("[]"); + } + + [Test] + public void BlobsV2DirectResponse_WriteToAsync_throws_on_cancelled_token() + { + byte[] blob = new byte[131072]; // 128KB + byte[]?[] blobs = [blob]; + ReadOnlyMemory[] proofs = [new ReadOnlyMemory([new byte[48]])]; + BlobsV2DirectResponse response = new(blobs, proofs, 1); + + using CancellationTokenSource cts = new(); + cts.Cancel(); + + Pipe pipe = new(); + Func act = async () => await response.WriteToAsync(pipe.Writer, cts.Token); + act.Should().ThrowAsync(); + } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs index f111b2197bcc..3a49eb9e9358 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs @@ -3,9 +3,12 @@ /* cSpell:disable */ using System; +using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; using Nethermind.Core; using Nethermind.Core.Authentication; +using Nethermind.Core.Extensions; using Nethermind.Logging; using NUnit.Framework; @@ -13,6 +16,9 @@ namespace Nethermind.Merge.Plugin.Test; public class JwtTest { + private const string HexSecret = "5166546A576E5A7234753778214125442A472D4A614E645267556B5870327335"; + private const long TestIat = 1644994971; + [Test] [TestCase("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDQ5OTQ5NzF9.RmIbZajyYGF9fhAq7A9YrTetdf15ebHIJiSdAhX7PME", "true")] [TestCase("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDQ5OTQ5NzV9.HfWy49SIyB12PBB_xEpy6IAiIan5mIqD6Jzeh_J1QNw", "true")] @@ -30,12 +36,135 @@ public class JwtTest [TestCase("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDQ5OTQ5NjF9.huhtaE1cUU2JuhqKmeTrHC3wgl2Tp_1pVh7DuYkKrQo", "true")] public async Task long_key_tests(string token, bool expected) { - ManualTimestamper manualTimestamper = new() { UtcNow = DateTimeOffset.FromUnixTimeSeconds(1644994971).UtcDateTime }; - IRpcAuthentication authentication = JwtAuthentication.FromSecret("5166546A576E5A7234753778214125442A472D4A614E645267556B5870327335", manualTimestamper, LimboTraceLogger.Instance); - IRpcAuthentication authenticationWithPrefix = JwtAuthentication.FromSecret("0x5166546A576E5A7234753778214125442A472D4A614E645267556B5870327335", manualTimestamper, LimboTraceLogger.Instance); + ManualTimestamper manualTimestamper = new() { UtcNow = DateTimeOffset.FromUnixTimeSeconds(TestIat).UtcDateTime }; + IRpcAuthentication authentication = JwtAuthentication.FromSecret(HexSecret, manualTimestamper, LimboTraceLogger.Instance); + IRpcAuthentication authenticationWithPrefix = JwtAuthentication.FromSecret("0x" + HexSecret, manualTimestamper, LimboTraceLogger.Instance); bool actual = await authentication.Authenticate(token); Assert.That(actual, Is.EqualTo(expected)); actual = await authenticationWithPrefix.Authenticate(token); Assert.That(expected, Is.EqualTo(actual)); } + + // --- Guard clause tests (Authenticate entry) --- + + [Test] + public async Task Null_token_returns_false() + { + Assert.That(await CreateAuth().Authenticate(null!), Is.False); + } + + [Test] + public async Task Empty_token_returns_false() + { + Assert.That(await CreateAuth().Authenticate(""), Is.False); + } + + [Test] + public async Task Missing_bearer_prefix_returns_false() + { + // Valid JWT but without "Bearer " prefix + string jwt = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat}}}"); + Assert.That(await CreateAuth().Authenticate(jwt["Bearer ".Length..]), Is.False); + } + + // --- Alternate header format tests (TryValidateManual branches) --- + + [Test] + public async Task HeaderTypAlg_valid_token_returns_true() + { + // {"typ":"JWT","alg":"HS256"} — reversed field order, 36-char Base64Url header + string token = CreateJwt("{\"typ\":\"JWT\",\"alg\":\"HS256\"}", $"{{\"iat\":{TestIat}}}"); + Assert.That(await CreateAuth().Authenticate(token), Is.True); + } + + [Test] + public async Task HeaderAlgOnly_valid_token_returns_true() + { + // {"alg":"HS256"} — no typ field, 20-char Base64Url header + string token = CreateJwt("{\"alg\":\"HS256\"}", $"{{\"iat\":{TestIat}}}"); + Assert.That(await CreateAuth().Authenticate(token), Is.True); + } + + [Test] + public async Task HeaderAlgOnly_expired_iat_returns_false() + { + string token = CreateJwt("{\"alg\":\"HS256\"}", $"{{\"iat\":{TestIat - 200}}}"); + Assert.That(await CreateAuth().Authenticate(token), Is.False); + } + + // --- AuthenticateCore fallback (unrecognized header → library path) --- + + [Test] + public async Task Unrecognized_header_valid_token_falls_to_library() + { + // Extra "kid" field makes the Base64Url header unrecognized → AuthenticateCore path + string token = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\",\"kid\":\"1\"}", $"{{\"iat\":{TestIat}}}"); + Assert.That(await CreateAuth().Authenticate(token), Is.True); + } + + [Test] + public async Task Unrecognized_header_expired_iat_returns_false() + { + string token = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\",\"kid\":\"1\"}", $"{{\"iat\":{TestIat - 200}}}"); + Assert.That(await CreateAuth().Authenticate(token), Is.False); + } + + // --- Cache path tests (TryLastValidationFromCache) --- + + [Test] + public async Task Cache_hit_returns_true_on_repeated_call() + { + IRpcAuthentication auth = CreateAuth(); + string token = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat}}}"); + + Assert.That(await auth.Authenticate(token), Is.True); + // Second call with same token should hit cache and still return true + Assert.That(await auth.Authenticate(token), Is.True); + } + + [Test] + public async Task Cache_eviction_when_iat_expires() + { + ManualTimestamper ts = new() { UtcNow = DateTimeOffset.FromUnixTimeSeconds(TestIat).UtcDateTime }; + IRpcAuthentication auth = JwtAuthentication.FromSecret(HexSecret, ts, LimboTraceLogger.Instance); + string token = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat}}}"); + + Assert.That(await auth.Authenticate(token), Is.True); + + // Advance time beyond TTL — cache should evict, iat check should fail + ts.UtcNow = DateTimeOffset.FromUnixTimeSeconds(TestIat + 61).UtcDateTime; + Assert.That(await auth.Authenticate(token), Is.False); + } + + [Test] + public async Task Cache_miss_different_token_revalidates() + { + IRpcAuthentication auth = CreateAuth(); + string token1 = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat}}}"); + string token2 = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat + 1}}}"); + + Assert.That(await auth.Authenticate(token1), Is.True); + // Different token — cache miss, must revalidate + Assert.That(await auth.Authenticate(token2), Is.True); + } + + // --- Helpers --- + + private static IRpcAuthentication CreateAuth(long nowUnixSeconds = TestIat) + { + ManualTimestamper ts = new() { UtcNow = DateTimeOffset.FromUnixTimeSeconds(nowUnixSeconds).UtcDateTime }; + return JwtAuthentication.FromSecret(HexSecret, ts, LimboTraceLogger.Instance); + } + + private static string CreateJwt(string headerJson, string payloadJson) + { + byte[] secret = Bytes.FromHexString(HexSecret); + string header = Base64UrlEncode(Encoding.UTF8.GetBytes(headerJson)); + string payload = Base64UrlEncode(Encoding.UTF8.GetBytes(payloadJson)); + byte[] sig = HMACSHA256.HashData(secret, Encoding.ASCII.GetBytes($"{header}.{payload}")); + return $"Bearer {header}.{payload}.{Base64UrlEncode(sig)}"; + } + + private static string Base64UrlEncode(byte[] data) + => Convert.ToBase64String(data).TrimEnd('=').Replace('+', '-').Replace('/', '_'); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs index c6b82b964fdc..3739ab6d7c85 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; using System.Threading.Tasks; using Autofac; using FluentAssertions; @@ -29,6 +31,17 @@ namespace Nethermind.Merge.Plugin.Test; public class MergePluginTests { + private sealed class SourceGenProbe + { + public int Value { get; set; } + } + + private sealed class ThrowingProbeResolver : IJsonTypeInfoResolver + { + public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options) => + type == typeof(SourceGenProbe) ? throw new InvalidOperationException("probe resolver was used") : null; + } + private ChainSpec _chainSpec = null!; private MergeConfig _mergeConfig = null!; private IJsonRpcConfig _jsonRpcConfig = null!; @@ -102,6 +115,15 @@ public void Init_merge_plugin_does_not_throw_exception(bool enabled) Assert.DoesNotThrow(() => _plugin.InitBlockProducer(_consensusPlugin!)); } + [Test] + public void AddTypeInfoResolver_updates_existing_serializer_instances() + { + EthereumJsonSerializer serializer = new(); + EthereumJsonSerializer.AddTypeInfoResolver(new ThrowingProbeResolver()); + + Assert.Throws(() => serializer.Serialize(new SourceGenProbe { Value = 1 })); + } + [Test] public async Task Initializes_correctly() { diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/TestBlockProcessorInterceptor.Setup.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/TestBlockProcessorInterceptor.Setup.cs index 3e10767608f2..5f6716e42aef 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/TestBlockProcessorInterceptor.Setup.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/TestBlockProcessorInterceptor.Setup.cs @@ -14,9 +14,12 @@ public class TestBranchProcessorInterceptor(IBranchProcessor baseBlockProcessor, { public int DelayMs { get; set; } = delayMs; public Exception? ExceptionToThrow { get; set; } + public ManualResetEventSlim? ProcessingStarted { get; set; } public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlocks, ProcessingOptions processingOptions, IBlockTracer blockTracer, CancellationToken token) { + ProcessingStarted?.Set(); + if (DelayMs > 0) { Thread.Sleep(DelayMs); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV1DirectResponse.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV1DirectResponse.cs new file mode 100644 index 000000000000..5135babb86eb --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV1DirectResponse.cs @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Core.Collections; +using Nethermind.JsonRpc; +using Nethermind.Serialization.Json; + +namespace Nethermind.Merge.Plugin.Data; + +/// +/// Wraps an of and writes JSON +/// directly into a , bypassing +/// to avoid extra buffer copies for large blob payloads. +/// +public sealed class BlobsV1DirectResponse : IStreamableResult, IEnumerable, IDisposable +{ + private readonly ArrayPoolList _items; + + public BlobsV1DirectResponse(ArrayPoolList items) + { + _items = items; + } + + public async ValueTask WriteToAsync(PipeWriter writer, CancellationToken cancellationToken) + { + writer.Write("["u8); + + int count = _items.Count; + for (int i = 0; i < count; i++) + { + if (i > 0) writer.Write(","u8); + + BlobAndProofV1? item = _items[i]; + if (item is null) + { + writer.Write("null"u8); + } + else + { + writer.Write("{\"blob\":\"0x"u8); + HexWriter.WriteHexChunked(writer, item.Blob); + writer.Write("\",\"proof\":\"0x"u8); + HexWriter.WriteHexSmall(writer, item.Proof); + writer.Write("\"}"u8); + } + + // Flush after each entry for backpressure + FlushResult flushResult = await writer.FlushAsync(cancellationToken); + if (flushResult.IsCompleted || flushResult.IsCanceled) + return; + } + + writer.Write("]"u8); + } + + public IEnumerator GetEnumerator() => _items.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public void Dispose() => _items.Dispose(); +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV2DirectResponse.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV2DirectResponse.cs new file mode 100644 index 000000000000..ad4048f31294 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV2DirectResponse.cs @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.JsonRpc; +using Nethermind.Serialization.Json; + +namespace Nethermind.Merge.Plugin.Data; + +/// +/// Wraps parallel arrays of blobs and proofs and writes JSON directly into a +/// , bypassing +/// to avoid extra buffer copies for large blob payloads. +/// +public sealed class BlobsV2DirectResponse : IStreamableResult, IEnumerable +{ + private readonly byte[]?[] _blobs; + private readonly ReadOnlyMemory[] _proofs; + private readonly int _count; + + public BlobsV2DirectResponse(byte[]?[] blobs, ReadOnlyMemory[] proofs, int count) + { + Debug.Assert(count <= blobs.Length && count <= proofs.Length, + "count must not exceed array lengths"); + _blobs = blobs; + _proofs = proofs; + _count = count; + } + + public async ValueTask WriteToAsync(PipeWriter writer, CancellationToken cancellationToken) + { + writer.Write("["u8); + + for (int i = 0; i < _count; i++) + { + if (i > 0) writer.Write(","u8); + + byte[]? blob = _blobs[i]; + if (blob is null) + { + writer.Write("null"u8); + } + else + { + writer.Write("{\"blob\":\"0x"u8); + HexWriter.WriteHexChunked(writer, blob); + writer.Write("\",\"proofs\":["u8); + + ReadOnlySpan proofs = _proofs[i].Span; + for (int p = 0; p < proofs.Length; p++) + { + if (p > 0) writer.Write(","u8); + writer.Write("\"0x"u8); + HexWriter.WriteHexSmall(writer, proofs[p]); + writer.Write("\""u8); + } + + writer.Write("]}"u8); + } + + // Flush after each entry for backpressure + FlushResult flushResult = await writer.FlushAsync(cancellationToken); + if (flushResult.IsCompleted || flushResult.IsCanceled) + return; + } + + writer.Write("]"u8); + } + + // Explicit interface implementation: only used by tests via IEnumerable cast. + // Production serialization goes through IStreamableResult.WriteToAsync. + IEnumerator IEnumerable.GetEnumerator() + { + for (int i = 0; i < _count; i++) + { + byte[]? blob = _blobs[i]; + yield return blob is null ? null : new BlobAndProofV2(blob, _proofs[i].ToArray()); + } + } + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/EngineApiJsonContext.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/EngineApiJsonContext.cs new file mode 100644 index 000000000000..912c2421b1fd --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/EngineApiJsonContext.cs @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json.Serialization; +using Nethermind.Consensus.Producers; +using Nethermind.Merge.Plugin.Handlers; + +namespace Nethermind.Merge.Plugin.Data; + +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Metadata, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + IncludeFields = true)] +[JsonSerializable(typeof(ExecutionPayload))] +[JsonSerializable(typeof(ExecutionPayloadV3))] +[JsonSerializable(typeof(PayloadStatusV1))] +[JsonSerializable(typeof(byte[][]))] +[JsonSerializable(typeof(ForkchoiceStateV1))] +[JsonSerializable(typeof(ForkchoiceUpdatedV1Result))] +[JsonSerializable(typeof(PayloadAttributes))] +[JsonSerializable(typeof(BlobAndProofV1))] +[JsonSerializable(typeof(BlobAndProofV2))] +[JsonSerializable(typeof(BlobsBundleV1))] +[JsonSerializable(typeof(BlobsBundleV2))] +[JsonSerializable(typeof(GetPayloadV2Result))] +[JsonSerializable(typeof(GetPayloadV3Result))] +[JsonSerializable(typeof(GetPayloadV4Result))] +[JsonSerializable(typeof(GetPayloadV5Result))] +[JsonSerializable(typeof(GetBlobsHandlerV2Request))] +[JsonSerializable(typeof(ExecutionPayloadBodyV1Result))] +[JsonSerializable(typeof(TransitionConfigurationV1))] +[JsonSerializable(typeof(ClientVersionV1))] +internal partial class EngineApiJsonContext : JsonSerializerContext; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs index d0eae608228a..93ba25cde5b7 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs @@ -137,6 +137,7 @@ public byte[][] Transactions /// true if block created successfully; otherwise, false. public virtual BlockDecodingResult TryGetBlock(UInt256? totalDifficulty = null) { + byte[][] encodedTransactions = Transactions; TransactionDecodingResult transactions = TryGetTransactions(); if (transactions.Error is not null) { @@ -164,11 +165,15 @@ public virtual BlockDecodingResult TryGetBlock(UInt256? totalDifficulty = null) Author = FeeRecipient, IsPostMerge = true, TotalDifficulty = totalDifficulty, - TxRoot = TxTrie.CalculateRoot(transactions.Transactions), + TxRoot = TxTrie.CalculateRoot(encodedTransactions), WithdrawalsRoot = BuildWithdrawalsRoot(), }; - return new BlockDecodingResult(new Block(header, transactions.Transactions, Array.Empty(), Withdrawals)); + Block block = new(header, transactions.Transactions, Array.Empty(), Withdrawals) + { + EncodedTransactions = encodedTransactions + }; + return new BlockDecodingResult(block); } protected virtual Hash256? BuildWithdrawalsRoot() diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandler.cs index 4a0dcf8b7e87..05cc9936d00a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandler.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Nethermind.Core.Collections; using Nethermind.Core.Specs; using Nethermind.JsonRpc; using Nethermind.Merge.Plugin.Data; @@ -27,35 +28,41 @@ public class GetBlobsHandler(ITxPool txPool, IChainHeadSpecProvider chainHeadSpe return ResultWrapper>.Fail(error, MergeErrorCodes.TooLargeRequest); } - return ResultWrapper>.Success(GetBlobsAndProofs(request)); - } - - private IEnumerable GetBlobsAndProofs(byte[][] request) - { bool allBlobsAvailable = true; Metrics.NumberOfRequestedBlobs += request.Length; - foreach (byte[] requestedBlobVersionedHash in request) + ArrayPoolList response = new(request.Length); + try { - if (txPool.TryGetBlobAndProofV0(requestedBlobVersionedHash, out byte[]? blob, out byte[]? proof)) + foreach (byte[] requestedBlobVersionedHash in request) + { + if (txPool.TryGetBlobAndProofV0(requestedBlobVersionedHash, out byte[]? blob, out byte[]? proof)) + { + Metrics.NumberOfSentBlobs++; + response.Add(new BlobAndProofV1(blob, proof)); + } + else + { + allBlobsAvailable = false; + response.Add(null); + } + } + + if (allBlobsAvailable) { - Metrics.NumberOfSentBlobs++; - yield return new BlobAndProofV1(blob, proof); + Metrics.GetBlobsRequestsSuccessTotal++; } else { - allBlobsAvailable = false; - yield return null; + Metrics.GetBlobsRequestsFailureTotal++; } - } - if (allBlobsAvailable) - { - Metrics.GetBlobsRequestsSuccessTotal++; + return ResultWrapper>.Success(new BlobsV1DirectResponse(response)); } - else + catch { - Metrics.GetBlobsRequestsFailureTotal++; + response.Dispose(); + throw; } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs index 47d223833282..341fb547a5f6 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; using System.Threading.Tasks; -using Nethermind.Core.Collections; using Nethermind.JsonRpc; using Nethermind.Merge.Plugin.Data; using Nethermind.TxPool; @@ -20,51 +20,27 @@ public class GetBlobsHandlerV2(ITxPool txPool) : IAsyncHandler MaxRequest) { - var error = $"The number of requested blobs must not exceed {MaxRequest}"; + string error = $"The number of requested blobs must not exceed {MaxRequest}"; return ResultWrapper?>.Fail(error, MergeErrorCodes.TooLargeRequest); } Metrics.GetBlobsRequestsTotal += request.BlobVersionedHashes.Length; - int count = txPool.GetBlobCounts(request.BlobVersionedHashes); + int n = request.BlobVersionedHashes.Length; + byte[]?[] blobs = new byte[n][]; + ReadOnlyMemory[] proofs = new ReadOnlyMemory[n]; + int count = txPool.TryGetBlobsAndProofsV1(request.BlobVersionedHashes, blobs, proofs); + Metrics.GetBlobsRequestsInBlobpoolTotal += count; // quick fail if we don't have some blob (unless partial return is allowed) - if (!request.AllowPartialReturn && count != request.BlobVersionedHashes.Length) + if (!request.AllowPartialReturn && count != n) { return ReturnEmptyArray(); } - ArrayPoolList response = new(request.BlobVersionedHashes.Length); - - try - { - foreach (byte[] requestedBlobVersionedHash in request.BlobVersionedHashes) - { - if (txPool.TryGetBlobAndProofV1(requestedBlobVersionedHash, out byte[]? blob, out byte[][]? cellProofs)) - { - response.Add(new BlobAndProofV2(blob, cellProofs)); - } - else if (request.AllowPartialReturn) - { - response.Add(null); - } - else - { - // fail if we were not able to collect full blob data - response.Dispose(); - return ReturnEmptyArray(); - } - } - - Metrics.GetBlobsRequestsSuccessTotal++; - return ResultWrapper?>.Success(response); - } - catch - { - response.Dispose(); - throw; - } + Metrics.GetBlobsRequestsSuccessTotal++; + return ResultWrapper?>.Success(new BlobsV2DirectResponse(blobs, proofs, n)); } private Task?>> ReturnEmptyArray() diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index 9e98a951e330..756e9d571add 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -35,6 +35,7 @@ using Nethermind.Merge.Plugin.InvalidChainTracker; using Nethermind.Merge.Plugin.Synchronization; using Nethermind.Network.Contract.P2P; +using Nethermind.Serialization.Json; using Nethermind.Specs.ChainSpecStyle; using Nethermind.State; using Nethermind.Synchronization; @@ -68,6 +69,7 @@ public partial class MergePlugin(ChainSpec chainSpec, IMergeConfig mergeConfig) public virtual Task Init(INethermindApi nethermindApi) { _api = nethermindApi; + EthereumJsonSerializer.AddTypeInfoResolver(EngineApiJsonContext.Default); _syncConfig = nethermindApi.Config(); _blocksConfig = nethermindApi.Config(); _txPoolConfig = nethermindApi.Config(); diff --git a/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs b/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs index bb35e69108b5..e21d3b810dfa 100644 --- a/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs +++ b/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs @@ -8,6 +8,8 @@ using System.Linq; using System.Reflection; using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Attributes; @@ -176,6 +178,45 @@ public void Register_and_update_metrics_should_not_throw_exception() }); } + [Test] + public void UpdateAllMetrics_does_not_throw_when_registration_is_concurrent() + { + MetricsConfig metricsConfig = new() { Enabled = true }; + MetricsController metricsController = new(metricsConfig); + + using CancellationTokenSource cts = new(TimeSpan.FromSeconds(2)); + CancellationToken ct = cts.Token; + + // Continuously call UpdateAllMetrics on one thread while registering metrics on another + Task updater = Task.Run(() => + { + while (!ct.IsCancellationRequested) + { + metricsController.UpdateAllMetrics(); + } + }); + + Task registrar = Task.Run(() => + { + Type[] types = + [ + typeof(TestMetrics), + typeof(Blockchain.Metrics), + typeof(Evm.Metrics), + typeof(Network.Metrics), + typeof(Db.Metrics), + ]; + + for (int i = 0; !ct.IsCancellationRequested; i++) + { + metricsController.RegisterMetrics(types[i % types.Length]); + metricsController.AddMetricsUpdateAction(() => { }); + } + }); + + Assert.DoesNotThrowAsync(() => Task.WhenAll(updater, registrar)); + } + [Test] public void All_config_items_have_descriptions() { diff --git a/src/Nethermind/Nethermind.Monitoring/Metrics/MetricsController.cs b/src/Nethermind/Nethermind.Monitoring/Metrics/MetricsController.cs index 6eefbce28fc1..1f8661324615 100644 --- a/src/Nethermind/Nethermind.Monitoring/Metrics/MetricsController.cs +++ b/src/Nethermind/Nethermind.Monitoring/Metrics/MetricsController.cs @@ -33,6 +33,7 @@ public partial class MetricsController : IMetricsController private static bool _staticLabelsInitialized; private readonly Dictionary _metricUpdaters = new(); + private volatile IMetricUpdater[][] _updaterValues = []; // Largely for testing reason internal readonly Dictionary _individualUpdater = new(); @@ -40,7 +41,7 @@ public partial class MetricsController : IMetricsController private readonly bool _useCounters; private readonly bool _enableDetailedMetric; - private readonly List _callbacks = new(); + private volatile Action[] _callbacks = []; public interface IMetricUpdater { @@ -235,6 +236,7 @@ public void RegisterMetrics(Type type) } } _metricUpdaters[type] = metricUpdaters.ToArray(); + _updaterValues = [.. _metricUpdaters.Values]; } } @@ -354,7 +356,7 @@ public void UpdateAllMetrics() callback(); } - foreach (IMetricUpdater[] updaters in _metricUpdaters.Values) + foreach (IMetricUpdater[] updaters in _updaterValues) { foreach (IMetricUpdater metricUpdater in updaters) { @@ -363,7 +365,7 @@ public void UpdateAllMetrics() } } - public void AddMetricsUpdateAction(Action callback) => _callbacks.Add(callback); + public void AddMetricsUpdateAction(Action callback) => _callbacks = [.. _callbacks, callback]; private static string GetGaugeNameKey(params string[] par) => string.Join('.', par); diff --git a/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs b/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs index d379f1855bd4..63f19c0cd136 100644 --- a/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs +++ b/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs @@ -34,10 +34,6 @@ public static class Protocol /// public const string Par = "par"; /// - /// Nethermind Data Marketplace - /// - public const string Ndm = "ndm"; - /// /// Account Abstraction /// public const string AA = "aa"; diff --git a/src/Nethermind/Nethermind.Network.Contract/P2P/ProtocolParser.cs b/src/Nethermind/Nethermind.Network.Contract/P2P/ProtocolParser.cs index a0f185b8082d..5f2e4e274bbd 100644 --- a/src/Nethermind/Nethermind.Network.Contract/P2P/ProtocolParser.cs +++ b/src/Nethermind/Nethermind.Network.Contract/P2P/ProtocolParser.cs @@ -17,7 +17,6 @@ public static class ProtocolParser private const uint Shh = 0x686873u; // "shh" private const uint Bzz = 0x7A7A62u; // "bzz" private const uint Par = 0x726170u; // "par" - private const uint Ndm = 0x6D646Eu; // "ndm" private const uint Snap = 0x70616E73u; // "snap" private const ulong Nodedata = 0x6174616465646F6Eul; // "nodedata" @@ -48,8 +47,6 @@ public static bool TryGetProtocolCode(ReadOnlySpan protocolSpan, [NotNullW protocol = Protocol.Bzz; return true; case Par: protocol = Protocol.Par; return true; - case Ndm: - protocol = Protocol.Ndm; return true; } break; diff --git a/src/Nethermind/Nethermind.Network.Discovery/Serializers/IDiscoveryMsgSerializersProvider.cs b/src/Nethermind/Nethermind.Network.Discovery/Serializers/IDiscoveryMsgSerializersProvider.cs deleted file mode 100644 index ea094cec0d7e..000000000000 --- a/src/Nethermind/Nethermind.Network.Discovery/Serializers/IDiscoveryMsgSerializersProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Network.Discovery.Serializers; - -public interface IDiscoveryMsgSerializersProvider -{ - void RegisterDiscoverySerializers(); -} diff --git a/src/Nethermind/Nethermind.Network.Test/MessageQueueTests.cs b/src/Nethermind/Nethermind.Network.Test/MessageQueueTests.cs new file mode 100644 index 000000000000..fe653da76271 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/MessageQueueTests.cs @@ -0,0 +1,168 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Network.P2P; +using Nethermind.Network.P2P.Subprotocols; +using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Network.Test; + +public class MessageQueueTests +{ + private readonly List _recordedSends = new(); + private MessageQueue> _queue; + + [SetUp] + public void Setup() + { + _recordedSends.Clear(); + _queue = new((message) => _recordedSends.Add(message)); + } + + [Test] + public void Send_first_request_is_sent_immediately() + { + Request> request = CreateRequest(); + + _queue.Send(request); + + _recordedSends.Count.Should().Be(1); + request.CompletionSource.Task.IsCompleted.Should().BeFalse(); + } + + [Test] + public void Send_second_request_is_queued() + { + Request> request1 = CreateRequest(); + Request> request2 = CreateRequest(); + + _queue.Send(request1); + _queue.Send(request2); + + _recordedSends.Count.Should().Be(1); + request2.CompletionSource.Task.IsCompleted.Should().BeFalse(); + } + + [Test] + public void Handle_completes_current_request_and_sends_next() + { + Request> request1 = CreateRequest(); + Request> request2 = CreateRequest(); + + IOwnedReadOnlyList response = new[] { Build.A.BlockHeader.TestObject }.ToPooledList(); + + _queue.Send(request1); + _queue.Send(request2); + + _queue.Handle(response, 100); + + request1.CompletionSource.Task.IsCompleted.Should().BeTrue(); + request1.CompletionSource.Task.Result.Should().BeSameAs(response); + _recordedSends.Count.Should().Be(2); + } + + [Test] + public void Handle_throws_when_no_current_request() + { + using IOwnedReadOnlyList response = new[] { Build.A.BlockHeader.TestObject }.ToPooledList(); + + _queue.Invoking(q => q.Handle(response, 100)) + .Should() + .Throw(); + } + + [Test] + public void Handle_disposes_data_when_no_current_request() + { + IOwnedReadOnlyList response = Substitute.For>(); + + _queue.Invoking(q => q.Handle(response, 100)) + .Should() + .Throw(); + + response.Received().Dispose(); + } + + [Test] + public void Handle_does_not_throw_when_completion_source_already_cancelled() + { + Request> request = CreateRequest(); + + _queue.Send(request); + + // Simulate timeout cancelling the CompletionSource + request.CompletionSource.TrySetCanceled(); + + using IOwnedReadOnlyList response = new[] { Build.A.BlockHeader.TestObject }.ToPooledList(); + + // Should not throw — this is the core regression test + _queue.Invoking(q => q.Handle(response, 100)) + .Should() + .NotThrow(); + } + + [Test] + public void Handle_disposes_data_when_completion_source_already_cancelled() + { + Request> request = CreateRequest(); + + _queue.Send(request); + + // Simulate timeout cancelling the CompletionSource + request.CompletionSource.TrySetCanceled(); + + IOwnedReadOnlyList response = Substitute.For>(); + + _queue.Handle(response, 100); + + response.Received().Dispose(); + } + + [Test] + public void Handle_dequeues_next_request_even_when_current_was_cancelled() + { + Request> request1 = CreateRequest(); + Request> request2 = CreateRequest(); + + _queue.Send(request1); + _queue.Send(request2); + + // Simulate timeout cancelling the first request + request1.CompletionSource.TrySetCanceled(); + + IOwnedReadOnlyList response = Substitute.For>(); + + _queue.Handle(response, 100); + + // The second request should have been dequeued and sent + _recordedSends.Count.Should().Be(2); + request2.CompletionSource.Task.IsCompleted.Should().BeFalse(); + } + + [Test] + public void Send_does_not_send_when_closed() + { + _queue.CompleteAdding(); + + GetBlockHeadersMessage msg = new(); + Request> request = new(msg); + + _queue.Send(request); + + _recordedSends.Count.Should().Be(0); + } + + private static Request> CreateRequest() + { + return new(new GetBlockHeadersMessage()); + } +} diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/ProtocolParserTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/ProtocolParserTests.cs index 3baecbc75c80..43860b4f0dc7 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/ProtocolParserTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/ProtocolParserTests.cs @@ -61,16 +61,6 @@ public void TryGetProtocolCode_Par_ReturnsTrue() Assert.That(protocol, Is.EqualTo(Protocol.Par)); } - [Test] - public void TryGetProtocolCode_Ndm_ReturnsTrue() - { - byte[] protocolBytes = Encoding.UTF8.GetBytes("ndm"); - bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); - - Assert.That(result, Is.True); - Assert.That(protocol, Is.EqualTo(Protocol.Ndm)); - } - [Test] public void TryGetProtocolCode_Snap_ReturnsTrue() { @@ -206,15 +196,6 @@ public void TryGetProtocolCode_ValidatesHexConstants_Par() Assert.That(parValue, Is.EqualTo(0x726170u), "Hex constant for 'par' should match"); } - [Test] - public void TryGetProtocolCode_ValidatesHexConstants_Ndm() - { - byte[] ndmBytes = Encoding.UTF8.GetBytes("ndm"); - uint ndmValue = CalculateThreeByteKey(ndmBytes); - - Assert.That(ndmValue, Is.EqualTo(0x6D646Eu), "Hex constant for 'ndm' should match"); - } - [Test] public void TryGetProtocolCode_ValidatesHexConstants_Snap() { diff --git a/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs b/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs index 3121523f1e07..36ab108cbd36 100644 --- a/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs @@ -78,7 +78,7 @@ public async Task Will_connect_to_a_candidate_node() ctx.SetupPersistedPeers(1); ctx.PeerPool.Start(); ctx.PeerManager.Start(); - Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.EqualTo(1).After(_travisDelay, 10)); + Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.EqualTo(1).After(_delay, 10)); } [Test, Retry(3)] @@ -88,14 +88,14 @@ public async Task Will_only_connect_up_to_max_peers() ctx.SetupPersistedPeers(50); ctx.PeerPool.Start(); ctx.PeerManager.Start(); - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); int expectedConnectCount = 25; Assert.That( () => ctx.RlpxPeer.ConnectAsyncCallsCount, Is .InRange(expectedConnectCount, expectedConnectCount + 1) - .After(_travisDelay * 10, 10)); + .After(_delay * 10, 10)); } [Test] @@ -184,6 +184,7 @@ public async Task Will_accept_static_connection() [TestCase(false, ConnectionDirection.In)] // [TestCase(true, ConnectionDirection.Out)] // cannot create an active peer waiting for the test [TestCase(false, ConnectionDirection.Out)] + [NonParallelizable] public async Task Will_agree_on_which_session_to_disconnect_when_connecting_at_once(bool shouldLose, ConnectionDirection firstDirection) { @@ -207,9 +208,12 @@ void EnsureSession(ISession? session) { if (session is null) return; if (session.State < SessionState.HandshakeComplete) session.Handshake(session.Node.Id); - session.Init(5, context, packetSender); + if (session.State < SessionState.Initialized) session.Init(5, context, packetSender); } + bool expectedOutSessionClosing = firstDirection == ConnectionDirection.In ? shouldLose : !shouldLose; + bool expectedInSessionClosing = !expectedOutSessionClosing; + if (firstDirection == ConnectionDirection.In) { ctx.RlpxPeer.CreateIncoming(session1); @@ -217,12 +221,6 @@ void EnsureSession(ISession? session) { throw new NetworkingException($"Failed to connect to {session1.Node:s}", NetworkExceptionType.TargetUnreachable); } - - EnsureSession(ctx.PeerManager.ActivePeers.First().OutSession); - EnsureSession(ctx.PeerManager.ActivePeers.First().InSession); - - (ctx.PeerManager.ActivePeers.First().OutSession?.IsClosing ?? true).Should().Be(shouldLose); - (ctx.PeerManager.ActivePeers.First().InSession?.IsClosing ?? true).Should().Be(!shouldLose); } else { @@ -233,15 +231,23 @@ void EnsureSession(ISession? session) } ctx.RlpxPeer.SessionCreated -= HandshakeOnCreate; ctx.RlpxPeer.CreateIncoming(session1); + } - EnsureSession(ctx.PeerManager.ActivePeers.First().OutSession); - EnsureSession(ctx.PeerManager.ActivePeers.First().InSession); + Assert.That(() => + { + Peer? activePeer = ctx.PeerManager.ActivePeers.SingleOrDefault(); + if (activePeer is null) return false; - (ctx.PeerManager.ActivePeers.First().OutSession?.IsClosing ?? true).Should().Be(!shouldLose); - (ctx.PeerManager.ActivePeers.First().InSession?.IsClosing ?? true).Should().Be(shouldLose); - } + EnsureSession(activePeer.OutSession); + EnsureSession(activePeer.InSession); - ctx.PeerManager.ActivePeers.Count.Should().Be(1); + return activePeer.OutSession is not null + && activePeer.InSession is not null + && activePeer.OutSession.IsClosing == expectedOutSessionClosing + && activePeer.InSession.IsClosing == expectedInSessionClosing; + }, Is.True.After(_delayLonger, 20)); + + Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(1).After(_delay, 10)); } private void HandshakeOnCreate(object sender, SessionEventArgs e) @@ -256,11 +262,11 @@ public async Task Will_fill_up_on_disconnects() ctx.SetupPersistedPeers(50); ctx.PeerPool.Start(); ctx.PeerManager.Start(); - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); Assert.That(ctx.RlpxPeer.ConnectAsyncCallsCount, Is.AtLeast(25)); ctx.DisconnectAllSessions(); - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); Assert.That(ctx.RlpxPeer.ConnectAsyncCallsCount, Is.AtLeast(50)); } @@ -273,7 +279,7 @@ public async Task Ok_if_fails_to_connect() ctx.PeerPool.Start(); ctx.PeerManager.Start(); - Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(0).After(_travisDelay, 10)); + Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(0).After(_delay, 10)); } [Test, Retry(3)] @@ -296,7 +302,7 @@ public async Task Will_fill_up_over_and_over_again_on_disconnects() { Assert.That( () => ctx.PeerPool.ActivePeers.Count, - Is.AtLeast(25).After(_travisDelayLonger * 2, 10)); + Is.AtLeast(25).After(_delayLonger * 2, 10)); ctx.DisconnectAllSessions(); } } @@ -318,7 +324,7 @@ public async Task Will_fill_up_over_and_over_again_on_newly_discovered() for (int i = 0; i < 10; i++) { ctx.DiscoverNew(25); - Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(25).After(_travisDelay, 10)); + Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(25).After(_delay, 10)); } } @@ -334,8 +340,8 @@ public async Task Will_not_stop_trying_on_rlpx_connection_failure() for (int i = 0; i < 10; i++) { ctx.DiscoverNew(25); - await Task.Delay(_travisDelay); - Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.EqualTo(25 * (i + 1)).After(1000, 10)); + await Task.Delay(_delay); + Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.EqualTo(25 * (i + 1)).After(_delayLonger, 10)); } } @@ -377,13 +383,13 @@ public async Task IfPeerAdded_with_invalid_chain_then_do_not_connect() ctx.PeerPool.GetOrAdd(networkNode); - Assert.That(() => ctx.PeerPool.ActivePeers.Count, Is.EqualTo(0).After(_travisDelay, 10)); + Assert.That(() => ctx.PeerPool.ActivePeers.Count, Is.EqualTo(0).After(_delay, 10)); } - private readonly int _travisDelay = 500; + private readonly int _delay = 500; - private readonly int _travisDelayLong = 1000; - private readonly int _travisDelayLonger = 3000; + private readonly int _delayLong = 1000; + private readonly int _delayLonger = 3000; [Test] [Ignore("Behaviour changed that allows peers to go over max if awaiting response")] @@ -397,7 +403,7 @@ public async Task Will_fill_up_with_incoming_over_and_over_again_on_disconnects( for (int i = 0; i < 10; i++) { ctx.CreateNewIncomingSessions(25); - Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(25).After(_travisDelay, 10)); + Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(25).After(_delay, 10)); } } @@ -416,10 +422,10 @@ public async Task Will_fill_up_over_and_over_again_on_disconnects_and_when_ids_k { currentCount += 25; maxCount += 50; - Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.InRange(currentCount, maxCount).After(_travisDelayLonger * 2, 10)); + Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.InRange(currentCount, maxCount).After(_delayLonger * 2, 10)); ctx.RlpxPeer.ConnectAsyncCallsCount.Should().BeInRange(currentCount, maxCount); ctx.HandshakeAllSessions(); - await Task.Delay(_travisDelay); + await Task.Delay(_delay); ctx.DisconnectAllSessions(); } @@ -448,10 +454,10 @@ public async Task Will_fill_up_over_and_over_again_on_disconnects_and_when_ids_k for (int i = 0; i < 10; i++) { currentCount += count; - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); ctx.RlpxPeer.ConnectAsyncCallsCount.Should().BeInRange(currentCount, currentCount + count); ctx.HandshakeAllSessions(); - await Task.Delay(_travisDelay); + await Task.Delay(_delay); ctx.DisconnectAllSessions(); } } @@ -473,12 +479,12 @@ public async Task Will_fill_up_over_and_over_again_on_disconnects_and_when_ids_k for (int i = 0; i < 10; i++) { currentCount += count; - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); ctx.RlpxPeer.ConnectAsyncCallsCount.Should().BeInRange(currentCount, currentCount + count); ctx.HandshakeAllSessions(); - await Task.Delay(_travisDelay); + await Task.Delay(_delay); ctx.CreateIncomingSessions(); - await Task.Delay(_travisDelay); + await Task.Delay(_delay); ctx.DisconnectAllSessions(); } } @@ -514,7 +520,7 @@ public async Task Will_load_static_nodes_and_connect_to_them() ctx.TestNodeSource.AddNode(new Node(TestItem.PublicKeyA, node.Host, node.Port)); } - Assert.That(() => ctx.PeerManager.ActivePeers.Count(static p => p.Node.IsStatic), Is.EqualTo(nodesCount).After(_travisDelay, 10)); + Assert.That(() => ctx.PeerManager.ActivePeers.Count(static p => p.Node.IsStatic), Is.EqualTo(nodesCount).After(_delay, 10)); } [Test, Retry(5)] @@ -527,7 +533,7 @@ public async Task Will_disconnect_on_remove_static_node() ctx.StaticNodesManager.DiscoverNodes(Arg.Any()).Returns(staticNodes.Select(n => new Node(n, true)).ToAsyncEnumerable()); ctx.PeerPool.Start(); ctx.PeerManager.Start(); - await Task.Delay(_travisDelay); + await Task.Delay(_delay); void DisconnectHandler(object o, DisconnectEventArgs e) => disconnections++; ctx.Sessions.ForEach(s => s.Disconnected += DisconnectHandler); @@ -548,7 +554,7 @@ public async Task Will_connect_and_disconnect_on_peer_management() ctx.PeerManager.Start(); var node = new NetworkNode(ctx.GenerateEnode()); ctx.PeerPool.GetOrAdd(node); - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); void DisconnectHandler(object o, DisconnectEventArgs e) => disconnections++; ctx.PeerManager.ActivePeers.Select(p => p.Node.Id).Should().BeEquivalentTo(new[] { node.NodeId }); @@ -570,7 +576,7 @@ public async Task Will_only_add_same_peer_once() ctx.PeerPool.GetOrAdd(node); ctx.PeerPool.GetOrAdd(node); ctx.PeerPool.GetOrAdd(node); - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); ctx.PeerManager.ActivePeers.Should().HaveCount(1); } @@ -581,7 +587,7 @@ public async Task RemovePeer_should_fail_if_peer_not_added() ctx.PeerPool.Start(); ctx.PeerManager.Start(); var node = new NetworkNode(ctx.GenerateEnode()); - Assert.That(() => ctx.PeerPool.TryRemove(node.NodeId, out _), Is.False.After(_travisDelay, 10)); + Assert.That(() => ctx.PeerPool.TryRemove(node.NodeId, out _), Is.False.After(_delay, 10)); } private class Context : IAsyncDisposable diff --git a/src/Nethermind/Nethermind.Network/P2P/MessageQueue.cs b/src/Nethermind/Nethermind.Network/P2P/MessageQueue.cs index e8c6ef82f4b1..21f8bcd26995 100644 --- a/src/Nethermind/Nethermind.Network/P2P/MessageQueue.cs +++ b/src/Nethermind/Nethermind.Network/P2P/MessageQueue.cs @@ -47,16 +47,15 @@ public void Handle(TData data, long size) { if (_currentRequest is null) { - if (data is IDisposable d) - { - d.Dispose(); - } - + data.TryDispose(); throw new SubprotocolException($"Received a response to {nameof(TMsg)} that has not been requested"); } _currentRequest.ResponseSize = size; - _currentRequest.CompletionSource.SetResult(data); + if (!_currentRequest.CompletionSource.TrySetResult(data)) + { + data.TryDispose(); + } if (_requestQueue.TryDequeue(out _currentRequest)) { _currentRequest!.StartMeasuringTime(); diff --git a/src/Nethermind/Nethermind.Network/P2P/Messages/AddCapabilityMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Messages/AddCapabilityMessageSerializer.cs index 1ab325c5940a..8c9e24f43d51 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Messages/AddCapabilityMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Messages/AddCapabilityMessageSerializer.cs @@ -9,7 +9,7 @@ namespace Nethermind.Network.P2P.Messages { /// - /// This is probably used in NDM + /// Serializes P2P capability negotiation messages. /// public class AddCapabilityMessageSerializer : IZeroMessageSerializer { diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs index 598cf8a0dc53..a9f15b75d8dc 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs @@ -20,7 +20,6 @@ using Nethermind.Network.P2P.Subprotocols.Eth.V62; using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; using Nethermind.Network.P2P.Subprotocols.Eth.V63.Messages; -using Nethermind.Serialization.Rlp; using Nethermind.Stats; using Nethermind.Stats.Model; using Nethermind.Synchronization; @@ -50,7 +49,6 @@ public abstract class SyncPeerProtocolHandlerBase : ZeroProtocolHandlerBase, ISy protected Hash256 _remoteHeadBlockHash; protected readonly ITimestamper _timestamper; - protected readonly TxDecoder _txDecoder; protected readonly MessageQueue> _headersRequests; protected readonly MessageQueue _bodiesRequests; @@ -67,7 +65,6 @@ protected SyncPeerProtocolHandlerBase(ISession session, { SyncServer = syncServer ?? throw new ArgumentNullException(nameof(syncServer)); _timestamper = Timestamper.Default; - _txDecoder = TxDecoder.Instance; _headersRequests = new MessageQueue>(Send); _bodiesRequests = new MessageQueue(Send); } diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs index 6427402d7b68..59fcac9a4f6f 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs @@ -86,15 +86,21 @@ CancellationToken token } else { - _ = task.ContinueWith(static t => + // TrySetCanceled first: if it succeeds we own the TCS and need to + // dispose any late-arriving response. If it fails, the response was + // already set by Handle() and the caller owns the data — registering + // a disposal continuation would dispose data the caller still holds. + if (request.CompletionSource.TrySetCanceled(cancellationToken)) { - if (t.IsCompletedSuccessfully) + _ = task.ContinueWith(static t => { - t.Result.TryDispose(); - } - }); + if (t.IsCompletedSuccessfully) + { + t.Result.TryDispose(); + } + }); + } - request.CompletionSource.TrySetCanceled(cancellationToken); StatsManager.ReportTransferSpeedEvent(Session.Node, speedType, 0L); if (Logger.IsDebug) Logger.Debug($"{Session} Request timeout in {describeRequestFunc(request.Message)}"); diff --git a/src/Nethermind/Nethermind.Network/PeerEqualityComparer.cs b/src/Nethermind/Nethermind.Network/PeerEqualityComparer.cs deleted file mode 100644 index 68a0ad6649d3..000000000000 --- a/src/Nethermind/Nethermind.Network/PeerEqualityComparer.cs +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; - -namespace Nethermind.Network -{ - internal class PeerEqualityComparer : IEqualityComparer - { - public bool Equals(Peer x, Peer y) - { - if (x is null || y is null) - { - return false; - } - - return x.Node.Id.Equals(y.Node.Id); - } - - public int GetHashCode(Peer obj) => obj?.Node is null ? 0 : obj.Node.GetHashCode(); - } -} diff --git a/src/Nethermind/Nethermind.Network/Timeouts.cs b/src/Nethermind/Nethermind.Network/Timeouts.cs index 9ed3d1973cf1..2a2051d8ea81 100644 --- a/src/Nethermind/Nethermind.Network/Timeouts.cs +++ b/src/Nethermind/Nethermind.Network/Timeouts.cs @@ -14,11 +14,6 @@ public static class Timeouts public static readonly TimeSpan P2PHello = TimeSpan.FromSeconds(3); public static readonly TimeSpan Eth62Status = TimeSpan.FromSeconds(3); public static readonly TimeSpan Les3Status = TimeSpan.FromSeconds(3); - public static readonly TimeSpan NdmHi = TimeSpan.FromSeconds(3); - public static readonly TimeSpan NdmDeliveryReceipt = TimeSpan.FromSeconds(3); - public static readonly TimeSpan NdmDepositApproval = TimeSpan.FromSeconds(3); - public static readonly TimeSpan NdmEthRequest = TimeSpan.FromSeconds(3); - public static readonly TimeSpan NdmDataRequestResult = TimeSpan.FromSeconds(3); public static readonly TimeSpan Handshake = TimeSpan.FromSeconds(3); public static readonly TimeSpan Disconnection = TimeSpan.FromSeconds(1); } diff --git a/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs b/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs index 07002bf116b6..1dcfe7c78d52 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs @@ -12,6 +12,7 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; +using Nethermind.Db.LogIndex; using Nethermind.Evm; using Nethermind.Facade; using Nethermind.Facade.Eth; @@ -593,8 +594,7 @@ public static TestRpcBlockchain.Builder WithOptimismEthRpcMod blockchain.ProtocolsManager, blockchain.ForkInfo, new BlocksConfig().SecondsPerSlot, - - sequencerRpcClient, ecdsa, sealer, opSpecHelper + sequencerRpcClient, ecdsa, sealer, new LogIndexConfig(), opSpecHelper )); } } diff --git a/src/Nethermind/Nethermind.Optimism/CL/L1Bridge/EthereumL1Bridge.cs b/src/Nethermind/Nethermind.Optimism/CL/L1Bridge/EthereumL1Bridge.cs index 879b6044d189..7ca731bd5a3d 100644 --- a/src/Nethermind/Nethermind.Optimism/CL/L1Bridge/EthereumL1Bridge.cs +++ b/src/Nethermind/Nethermind.Optimism/CL/L1Bridge/EthereumL1Bridge.cs @@ -17,9 +17,6 @@ namespace Nethermind.Optimism.CL.L1Bridge; public class EthereumL1Bridge : IL1Bridge { - private const int L1EpochSlotSize = 32; - private const int L1SlotTimeMilliseconds = 12000; - private const int L1EpochTimeMilliseconds = L1EpochSlotSize * L1SlotTimeMilliseconds; private readonly IEthApi _ethL1Api; private readonly IBeaconApi _beaconApi; private readonly ILogger _logger; @@ -224,11 +221,6 @@ private DaDataSource ProcessCalldataBatcherTransaction(L1Transaction transaction return _unfinalizedL1BlocksQueue.Count == 0 ? null : _unfinalizedL1BlocksQueue.Dequeue(); } - private void LogReorg() - { - if (_logger.IsInfo) _logger.Info("L1 reorg detected. Resetting pipeline"); - } - public async Task GetBlock(ulong blockNumber, CancellationToken token) => await RetryGetBlock(async () => await _ethL1Api.GetBlockByNumber(blockNumber, true), token); diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs index aa6db5df906f..ad5e85acdcdc 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs @@ -17,6 +17,7 @@ using Nethermind.Config; using Nethermind.Core; using Nethermind.Crypto; +using Nethermind.Db.LogIndex; using Nethermind.JsonRpc.Client; using Nethermind.Network; using Nethermind.Serialization.Json; @@ -44,6 +45,7 @@ public class OptimismEthModuleFactory : ModuleFactoryBase private readonly IOptimismSpecHelper _opSpecHelper; private readonly IProtocolsManager _protocolsManager; private readonly IForkInfo _forkInfo; + private readonly ILogIndexConfig _logIndexConfig; private readonly ulong? _secondsPerSlot; private readonly IJsonRpcClient? _sequencerRpcClient; @@ -67,7 +69,8 @@ public OptimismEthModuleFactory(IJsonRpcConfig rpcConfig, IOptimismSpecHelper opSpecHelper, IOptimismConfig config, IJsonSerializer jsonSerializer, - ITimestamper timestamper + ITimestamper timestamper, + ILogIndexConfig logIndexConfig ) { _secondsPerSlot = blocksConfig.SecondsPerSlot; @@ -88,6 +91,7 @@ ITimestamper timestamper _opSpecHelper = opSpecHelper; _protocolsManager = protocolsManager; _forkInfo = forkInfo; + _logIndexConfig = logIndexConfig; ILogger logger = logManager.GetClassLogger(); if (config.SequencerUrl is null && logger.IsWarn) { @@ -123,10 +127,10 @@ public override IOptimismEthRpcModule Create() _protocolsManager, _forkInfo, _secondsPerSlot, - _sequencerRpcClient, _ecdsa, _sealer, + _logIndexConfig, _opSpecHelper ); } diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs index b1bc00f3f1fa..e8befe91bc7a 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs @@ -10,6 +10,7 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Crypto; +using Nethermind.Db.LogIndex; using Nethermind.Evm; using Nethermind.Facade; using Nethermind.Facade.Eth; @@ -52,6 +53,7 @@ public class OptimismEthRpcModule( IJsonRpcClient? sequencerRpcClient, IEthereumEcdsa ecdsa, ITxSealer sealer, + ILogIndexConfig? logIndexConfig, IOptimismSpecHelper opSpecHelper) : EthRpcModule(rpcConfig, blockchainBridge, @@ -68,6 +70,7 @@ public class OptimismEthRpcModule( feeHistoryOracle, protocolsManager, forkInfo, + logIndexConfig, secondsPerSlot), IOptimismEthRpcModule { public override ResultWrapper eth_getBlockReceipts(BlockParameter blockParameter) diff --git a/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs b/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs index 9b3ca7354aff..174526aaf142 100644 --- a/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs +++ b/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs @@ -35,7 +35,7 @@ public static NethermindApi ContextWithMocks() { NethermindApi.Dependencies apiDependencies = new NethermindApi.Dependencies( Substitute.For(), - Substitute.For(), + new EthereumJsonSerializer(), LimboLogs.Instance, new ChainSpec { Parameters = new ChainParameters(), }, Substitute.For(), diff --git a/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/EthereumStepsManagerTests.cs b/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/EthereumStepsManagerTests.cs index 88222c13a190..761107c92260 100644 --- a/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/EthereumStepsManagerTests.cs +++ b/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/EthereumStepsManagerTests.cs @@ -176,7 +176,8 @@ private static ContainerBuilder CreateCommonBuilder(params IEnumerable .AddSingleton() .AddSingleton() .AddSingleton(new ConfigProvider()) - .AddSingleton(new EthereumJsonSerializer()) + .AddSingleton(new EthereumJsonSerializer()) + .Bind() .AddSingleton(LimboLogs.Instance) .AddSingleton(new ChainSpec()) .AddSingleton(Substitute.For()) diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs b/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs index 35cdf5e4840c..2bf6eefbe92e 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs @@ -20,7 +20,7 @@ namespace Nethermind.Runner.Ethereum.Api; public class ApiBuilder { private readonly IConfigProvider _configProvider; - private readonly IJsonSerializer _jsonSerializer; + private readonly EthereumJsonSerializer _jsonSerializer; private readonly ILogManager _logManager; private readonly ILogger _logger; private readonly IInitConfig _initConfig; @@ -64,7 +64,7 @@ public EthereumRunner CreateEthereumRunner(IEnumerable plugin return container.Resolve(); } - private ChainSpec LoadChainSpec(IJsonSerializer ethereumJsonSerializer) + private ChainSpec LoadChainSpec(EthereumJsonSerializer ethereumJsonSerializer) { if (_logger.IsDebug) _logger.Debug($"Loading chain spec from {_initConfig.ChainSpecPath}"); diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/EthereumRunner.cs b/src/Nethermind/Nethermind.Runner/Ethereum/EthereumRunner.cs index 9cb196730d2c..75ec67da4646 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/EthereumRunner.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/EthereumRunner.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Threading; using System.Threading.Tasks; using Autofac; @@ -41,18 +40,4 @@ public async Task StopAsync() _logger.Info("Ethereum runner stopped"); } } - - private Task Stop(Func stopAction, string description) - { - try - { - if (_logger.IsInfo) _logger.Info(description); - return stopAction() ?? Task.CompletedTask; - } - catch (Exception e) - { - if (_logger.IsError) _logger.Error($"{description} shutdown error.", e); - return Task.CompletedTask; - } - } } diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs b/src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs index a01cf8002ee1..4f5062940051 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs @@ -37,7 +37,7 @@ public class JsonRpcRunner private readonly IConfigProvider _configurationProvider; private readonly IRpcAuthentication _rpcAuthentication; private readonly ILogManager _logManager; - private readonly IJsonRpcProcessor _jsonRpcProcessor; + private readonly JsonRpcProcessor _jsonRpcProcessor; private readonly IJsonRpcUrlCollection _jsonRpcUrlCollection; private readonly IWebSocketsManager _webSocketsManager; private WebHost? _webApp; @@ -50,7 +50,7 @@ public class JsonRpcRunner private readonly IMainProcessingContext _mainProcessingContext; public JsonRpcRunner( - IJsonRpcProcessor jsonRpcProcessor, + JsonRpcProcessor jsonRpcProcessor, IJsonRpcUrlCollection jsonRpcUrlCollection, IWebSocketsManager webSocketsManager, IConfigProvider configurationProvider, diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs b/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs index 8378db80d5d9..fbdae0a5805d 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs @@ -31,7 +31,7 @@ namespace Nethermind.Runner.Ethereum.Modules; /// /// public class NethermindRunnerModule( - IJsonSerializer jsonSerializer, + EthereumJsonSerializer jsonSerializer, ChainSpec chainSpec, IConfigProvider configProvider, IProcessExitSource processExitSource, @@ -75,6 +75,7 @@ protected override void Load(ContainerBuilder builder) .AddSingleton((api) => api.BlockPreprocessor) .AddSingleton(jsonSerializer) + .AddSingleton(jsonSerializer) .AddSingleton(consensusPlugin) ; diff --git a/src/Nethermind/Nethermind.Runner/JsonRpc/Bootstrap.cs b/src/Nethermind/Nethermind.Runner/JsonRpc/Bootstrap.cs index 7471c2700762..3e8d96b58138 100644 --- a/src/Nethermind/Nethermind.Runner/JsonRpc/Bootstrap.cs +++ b/src/Nethermind/Nethermind.Runner/JsonRpc/Bootstrap.cs @@ -17,9 +17,9 @@ private Bootstrap() { } public static Bootstrap Instance => _instance ??= new Bootstrap(); - public IJsonRpcService? JsonRpcService { private get; set; } + public JsonRpcService? JsonRpcService { private get; set; } public ILogManager? LogManager { private get; set; } - public IJsonSerializer? JsonSerializer { private get; set; } + public EthereumJsonSerializer? JsonSerializer { private get; set; } public IJsonRpcLocalStats? JsonRpcLocalStats { private get; set; } public IRpcAuthentication? JsonRpcAuthentication { private get; set; } diff --git a/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs b/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs index 1da1f1ac071c..73c04f118138 100644 --- a/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs +++ b/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs @@ -7,7 +7,10 @@ using System.IO; using System.IO.Pipelines; using System.Net; +using System.Runtime.CompilerServices; using System.Security.Authentication; +using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using HealthChecks.UI.Client; @@ -26,8 +29,10 @@ using Nethermind.Config; using Nethermind.Core.Authentication; using Nethermind.Core.Resettables; +using Nethermind.Facade.Eth; using Nethermind.HealthChecks; using Nethermind.JsonRpc; +using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules; using Nethermind.Logging; using Nethermind.Serialization.Json; @@ -37,6 +42,14 @@ namespace Nethermind.Runner.JsonRpc; public class Startup : IStartup { + private JsonRpcProcessor _jsonRpcProcessor = null!; + private JsonRpcService _jsonRpcService = null!; + private IJsonRpcLocalStats _jsonRpcLocalStats = null!; + private EthereumJsonSerializer _jsonSerializer = null!; + private IJsonRpcConfig _jsonRpcConfig = null!; + private IRpcAuthentication? _rpcAuthentication; + private ILogger _logger = default; + private static ReadOnlySpan _jsonOpeningBracket => [(byte)'[']; private static ReadOnlySpan _jsonComma => [(byte)',']; private static ReadOnlySpan _jsonClosingBracket => [(byte)']']; @@ -84,23 +97,30 @@ public void Configure(IApplicationBuilder app) Configure( app, services.GetRequiredService(), - services.GetRequiredService(), - services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService(), services.GetRequiredService(), - services.GetRequiredService(), + services.GetRequiredService(), services.GetRequiredService()); } - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IJsonRpcProcessor jsonRpcProcessor, IJsonRpcService jsonRpcService, IJsonRpcLocalStats jsonRpcLocalStats, IJsonSerializer jsonSerializer, ApplicationLifetime lifetime) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, JsonRpcProcessor jsonRpcProcessor, JsonRpcService jsonRpcService, IJsonRpcLocalStats jsonRpcLocalStats, EthereumJsonSerializer jsonSerializer, ApplicationLifetime lifetime) { + // Register source-generated type info resolvers before warmup + EthereumJsonSerializer.AddTypeInfoResolver(JsonRpcResponseJsonContext.Default); + EthereumJsonSerializer.AddTypeInfoResolver(FacadeJsonContext.Default); + EthereumJsonSerializer.AddTypeInfoResolver(EthRpcJsonContext.Default); + + // Warm up System.Text.Json metadata for hot response types + EthereumJsonSerializer.WarmupSerializer( + new JsonRpcSuccessResponse { Id = 0 }, + new JsonRpcErrorResponse { Id = 0, Error = new Error { Code = 0, Message = string.Empty } }); + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } - app.UseRouting(); - app.UseCors(); - IConfigProvider? configProvider = app.ApplicationServices.GetService(); IRpcAuthentication? rpcAuthentication = app.ApplicationServices.GetService(); @@ -116,10 +136,39 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IJsonRpc IJsonRpcUrlCollection jsonRpcUrlCollection = app.ApplicationServices.GetRequiredService(); IHealthChecksConfig healthChecksConfig = configProvider.GetConfig(); - // If request is local, don't use response compression, - // as it allocates a lot, but doesn't improve much for loopback + _jsonRpcProcessor = jsonRpcProcessor; + _jsonRpcService = jsonRpcService; + _jsonRpcLocalStats = jsonRpcLocalStats; + _jsonSerializer = jsonSerializer; + _jsonRpcConfig = jsonRpcConfig; + _rpcAuthentication = rpcAuthentication; + _logger = logger; + + // Engine API fast lane: authenticated engine port POST requests bypass + // routing, CORS, compression, and WebSocket middleware + app.Use(async (ctx, next) => + { + if (ctx.Request.Method != "POST" || + !(ctx.Request.ContentType?.Contains("application/json") ?? false) || + !jsonRpcUrlCollection.TryGetValue(ctx.Connection.LocalPort, out JsonRpcUrl jsonRpcUrl) || + !jsonRpcUrl.IsAuthenticated || + !jsonRpcUrl.RpcEndpoint.HasFlag(RpcEndpoint.Http)) + { + await next(); + return; + } + + await ProcessJsonRpcRequestCoreAsync(ctx, jsonRpcUrl); + }); + + app.UseRouting(); + app.UseCors(); + + // Skip response compression for localhost (low benefit, high allocation cost) + // and for Engine API requests (latency-sensitive consensus path) app.UseWhen(ctx => - !IsLocalhost(ctx.Connection.RemoteIpAddress!), + !IsLocalhost(ctx.Connection.RemoteIpAddress!) && + !(jsonRpcUrlCollection.TryGetValue(ctx.Connection.LocalPort, out JsonRpcUrl url) && url.IsAuthenticated), builder => builder.UseResponseCompression()); if (initConfig.WebSocketsEnabled) @@ -158,37 +207,22 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IJsonRpc }); app.MapWhen( - (ctx) => ctx.Request.ContentType?.Contains("application/json") ?? false, + ctx => ctx.Request.ContentType?.Contains("application/json") ?? false, builder => builder.Run(async ctx => { - var method = ctx.Request.Method; + string method = ctx.Request.Method; if (method is not "POST" and not "GET") { - ctx.Response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; - return; - } - - if (jsonRpcProcessor.ProcessExit.IsCancellationRequested) - { - ctx.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable; + ctx.Response.StatusCode = StatusCodes.Status405MethodNotAllowed; return; } if (!jsonRpcUrlCollection.TryGetValue(ctx.Connection.LocalPort, out JsonRpcUrl jsonRpcUrl) || !jsonRpcUrl.RpcEndpoint.HasFlag(RpcEndpoint.Http)) { - ctx.Response.StatusCode = (int)HttpStatusCode.NotFound; + ctx.Response.StatusCode = StatusCodes.Status404NotFound; return; } - if (jsonRpcUrl.IsAuthenticated) - { - if (!await rpcAuthentication!.Authenticate(ctx.Request.Headers.Authorization)) - { - await PushErrorResponse(StatusCodes.Status401Unauthorized, ErrorCodes.InvalidRequest, "Authentication error"); - return; - } - } - if (method == "GET" && ctx.Request.Headers.Accept.Count > 0 && !ctx.Request.Headers.Accept[0]!.Contains("text/html", StringComparison.Ordinal)) { @@ -196,128 +230,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IJsonRpc } else if (ctx.Request.ContentType?.Contains("application/json") == false) { - await PushErrorResponse(StatusCodes.Status415UnsupportedMediaType, ErrorCodes.InvalidRequest, "Missing 'application/json' Content-Type header"); + await PushErrorResponseAsync(ctx, StatusCodes.Status415UnsupportedMediaType, ErrorCodes.InvalidRequest, "Missing 'application/json' Content-Type header"); } else { - if (jsonRpcUrl.MaxRequestBodySize is not null) - ctx.Features.Get().MaxRequestBodySize = jsonRpcUrl.MaxRequestBodySize; - - long startTime = Stopwatch.GetTimestamp(); - CountingPipeReader request = new(ctx.Request.BodyReader); - try - { - using JsonRpcContext jsonRpcContext = JsonRpcContext.Http(jsonRpcUrl); - await foreach (JsonRpcResult result in jsonRpcProcessor.ProcessAsync(request, jsonRpcContext)) - { - using (result) - { - await using Stream stream = jsonRpcConfig.BufferResponses ? RecyclableStream.GetStream("http") : null; - CountingWriter resultWriter = stream is not null ? new CountingStreamPipeWriter(stream) : new CountingPipeWriter(ctx.Response.BodyWriter); - try - { - ctx.Response.ContentType = "application/json"; - ctx.Response.StatusCode = GetStatusCode(result); - - if (result.IsCollection) - { - resultWriter.Write(_jsonOpeningBracket); - bool first = true; - JsonRpcBatchResultAsyncEnumerator enumerator = result.BatchedResponses.GetAsyncEnumerator(CancellationToken.None); - try - { - while (await enumerator.MoveNextAsync()) - { - JsonRpcResult.Entry entry = enumerator.Current; - using (entry) - { - if (!first) - { - resultWriter.Write(_jsonComma); - } - - first = false; - await jsonSerializer.SerializeAsync(resultWriter, entry.Response); - _ = jsonRpcLocalStats.ReportCall(entry.Report); - - // We reached the limit and don't want to respond to more request in the batch - if (!jsonRpcContext.IsAuthenticated && resultWriter.WrittenCount > jsonRpcConfig.MaxBatchResponseBodySize) - { - if (logger.IsWarn) logger.Warn($"The max batch response body size exceeded. The current response size {resultWriter.WrittenCount}, and the config setting is JsonRpc.{nameof(jsonRpcConfig.MaxBatchResponseBodySize)} = {jsonRpcConfig.MaxBatchResponseBodySize}"); - enumerator.IsStopped = true; - } - } - } - } - finally - { - await enumerator.DisposeAsync(); - } - - resultWriter.Write(_jsonClosingBracket); - } - else - { - await jsonSerializer.SerializeAsync(resultWriter, result.Response); - } - await resultWriter.CompleteAsync(); - if (stream is not null) - { - ctx.Response.ContentLength = resultWriter.WrittenCount; - stream.Seek(0, SeekOrigin.Begin); - await stream.CopyToAsync(ctx.Response.Body); - } - } - catch (Exception e) when (e.InnerException is OperationCanceledException) - { - await SerializeTimeoutException(resultWriter); - } - catch (OperationCanceledException) - { - await SerializeTimeoutException(resultWriter); - } - finally - { - await ctx.Response.CompleteAsync(); - } - - long handlingTimeMicroseconds = (long)Stopwatch.GetElapsedTime(startTime).TotalMicroseconds; - _ = jsonRpcLocalStats.ReportCall(result.IsCollection - ? new RpcReport("# collection serialization #", handlingTimeMicroseconds, true) - : result.Report.Value, handlingTimeMicroseconds, resultWriter.WrittenCount); - - Interlocked.Add(ref Metrics.JsonRpcBytesSentHttp, resultWriter.WrittenCount); - - // There should be only one response because we don't expect multiple JSON tokens in the request - break; - } - } - } - catch (Microsoft.AspNetCore.Http.BadHttpRequestException e) - { - if (logger.IsDebug) logger.Debug($"Couldn't read request.{Environment.NewLine}{e}"); - await PushErrorResponse(e.StatusCode, e.StatusCode == StatusCodes.Status413PayloadTooLarge - ? ErrorCodes.LimitExceeded - : ErrorCodes.InvalidRequest, - e.Message); - } - finally - { - Interlocked.Add(ref Metrics.JsonRpcBytesReceivedHttp, ctx.Request.ContentLength ?? request.Length); - } - } - Task SerializeTimeoutException(CountingWriter resultStream) - { - JsonRpcErrorResponse? error = jsonRpcService.GetErrorResponse(ErrorCodes.Timeout, "Request was canceled due to enabled timeout."); - return jsonSerializer.SerializeAsync(resultStream, error); - } - async Task PushErrorResponse(int statusCode, int errorCode, string message) - { - JsonRpcErrorResponse? response = jsonRpcService.GetErrorResponse(errorCode, message); - ctx.Response.ContentType = "application/json"; - ctx.Response.StatusCode = statusCode; - await jsonSerializer.SerializeAsync(ctx.Response.BodyWriter, response); - await ctx.Response.CompleteAsync(); + await ProcessJsonRpcRequestCoreAsync(ctx, jsonRpcUrl); } })); @@ -357,6 +274,275 @@ private static bool IsResourceUnavailableError(JsonRpcResponse? response) or JsonRpcErrorResponse { Error.Code: ErrorCodes.LimitExceeded }; } + private async Task PushErrorResponseAsync(HttpContext ctx, int statusCode, int errorCode, string message) + { + ctx.Response.ContentType = "application/json"; + ctx.Response.StatusCode = statusCode; + JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(errorCode, message); + await _jsonSerializer.SerializeAsync(ctx.Response.BodyWriter, response); + await ctx.Response.CompleteAsync(); + } + + private async Task ProcessJsonRpcRequestCoreAsync(HttpContext ctx, JsonRpcUrl jsonRpcUrl) + { + if (_jsonRpcProcessor.ProcessExit.IsCancellationRequested) + { + ctx.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; + return; + } + + if (jsonRpcUrl.IsAuthenticated && !await _rpcAuthentication!.Authenticate(ctx.Request.Headers.Authorization)) + { + await PushErrorResponseAsync(ctx, StatusCodes.Status401Unauthorized, ErrorCodes.InvalidRequest, "Authentication error"); + return; + } + + if (jsonRpcUrl.MaxRequestBodySize is not null) + ctx.Features.Get()!.MaxRequestBodySize = jsonRpcUrl.MaxRequestBodySize; + + long startTime = Stopwatch.GetTimestamp(); + // Skip CountingPipeReader when Content-Length is known + long? knownContentLength = ctx.Request.ContentLength; + CountingPipeReader? countingReader = knownContentLength > 0 ? null : new(ctx.Request.BodyReader); + PipeReader request = countingReader ?? ctx.Request.BodyReader; + try + { + using JsonRpcContext jsonRpcContext = JsonRpcContext.Http(jsonRpcUrl); + await foreach (JsonRpcResult result in _jsonRpcProcessor.ProcessAsync(request, jsonRpcContext)) + { + using (result) + { + // Authenticated single responses bypass buffering to avoid double-copy + bool bufferResponse = _jsonRpcConfig.BufferResponses && !(jsonRpcUrl.IsAuthenticated && !result.IsCollection); + await using Stream stream = bufferResponse ? RecyclableStream.GetStream("http") : null; + CountingWriter resultWriter = stream is not null ? new CountingStreamPipeWriter(stream) : new CountingPipeWriter(ctx.Response.BodyWriter); + try + { + ctx.Response.ContentType = "application/json"; + ctx.Response.StatusCode = GetStatusCode(result); + + // Flush headers before body for unbuffered responses + if (stream is null) + { + await ctx.Response.StartAsync(); + } + + if (result.IsCollection) + { + resultWriter.Write(_jsonOpeningBracket); + bool first = true; + JsonRpcBatchResultAsyncEnumerator enumerator = result.BatchedResponses.GetAsyncEnumerator(CancellationToken.None); + try + { + while (await enumerator.MoveNextAsync()) + { + JsonRpcResult.Entry entry = enumerator.Current; + using (entry) + { + if (!first) resultWriter.Write(_jsonComma); + first = false; + await _jsonSerializer.SerializeAsync(resultWriter, entry.Response); + _ = _jsonRpcLocalStats.ReportCall(entry.Report); + + // Stop batch if non-authenticated response exceeds configured size limit + if (!jsonRpcContext.IsAuthenticated && resultWriter.WrittenCount > _jsonRpcConfig.MaxBatchResponseBodySize) + { + if (_logger.IsWarn) _logger.Warn($"The max batch response body size exceeded. The current response size {resultWriter.WrittenCount}, and the config setting is JsonRpc.{nameof(_jsonRpcConfig.MaxBatchResponseBodySize)} = {_jsonRpcConfig.MaxBatchResponseBodySize}"); + enumerator.IsStopped = true; + } + } + } + } + finally + { + await enumerator.DisposeAsync(); + } + resultWriter.Write(_jsonClosingBracket); + } + else if (result.Response is JsonRpcSuccessResponse { Result: IStreamableResult streamable }) + { + await WriteStreamableResponseAsync(resultWriter, result.Response, streamable, ctx.RequestAborted); + } + else + { + WriteJsonRpcResponse(resultWriter, result.Response); + } + await resultWriter.CompleteAsync(); + if (stream is not null) + { + ctx.Response.ContentLength = resultWriter.WrittenCount; + stream.Seek(0, SeekOrigin.Begin); + await stream.CopyToAsync(ctx.Response.Body); + } + } + catch (Exception e) when (e is OperationCanceledException || e.InnerException is OperationCanceledException) + { + JsonRpcErrorResponse error = _jsonRpcService.GetErrorResponse(ErrorCodes.Timeout, "Request was canceled due to enabled timeout."); + await _jsonSerializer.SerializeAsync(resultWriter, error); + } + finally + { + await ctx.Response.CompleteAsync(); + } + + long handlingTimeMicroseconds = (long)Stopwatch.GetElapsedTime(startTime).TotalMicroseconds; + _ = _jsonRpcLocalStats.ReportCall(result.IsCollection + ? new RpcReport("# collection serialization #", handlingTimeMicroseconds, true) + : result.Report.Value, handlingTimeMicroseconds, resultWriter.WrittenCount); + Interlocked.Add(ref Metrics.JsonRpcBytesSentHttp, resultWriter.WrittenCount); + break; + } + } + } + catch (Microsoft.AspNetCore.Http.BadHttpRequestException e) + { + if (_logger.IsDebug) LogBadRequest(_logger, e); + ctx.Response.ContentType = "application/json"; + ctx.Response.StatusCode = e.StatusCode; + JsonRpcErrorResponse errResp = _jsonRpcService.GetErrorResponse( + e.StatusCode == StatusCodes.Status413PayloadTooLarge ? ErrorCodes.LimitExceeded : ErrorCodes.InvalidRequest, + e.Message); + await _jsonSerializer.SerializeAsync(ctx.Response.BodyWriter, errResp); + await ctx.Response.CompleteAsync(); + } + finally + { + Interlocked.Add(ref Metrics.JsonRpcBytesReceivedHttp, knownContentLength ?? countingReader?.Length ?? 0); + } + } + + /// + /// Writes a JSON-RPC response with typed serialization for the result/error payload, + /// avoiding polymorphic dispatch through the JsonRpcResponse base class hierarchy. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteJsonRpcResponse(IBufferWriter writer, JsonRpcResponse response) + { + using var jsonWriter = new Utf8JsonWriter(writer, new JsonWriterOptions { SkipValidation = true }); + + jsonWriter.WriteStartObject(); + jsonWriter.WriteString("jsonrpc"u8, "2.0"u8); + + if (response is JsonRpcSuccessResponse successResponse) + { + jsonWriter.WritePropertyName("result"u8); + object? result = successResponse.Result; + if (result is not null) + { + JsonSerializer.Serialize(jsonWriter, result, result.GetType(), EthereumJsonSerializer.JsonOptions); + } + else + { + jsonWriter.WriteNullValue(); + } + } + else if (response is JsonRpcErrorResponse errorResponse) + { + jsonWriter.WritePropertyName("error"u8); + if (errorResponse.Error is not null) + { + JsonSerializer.Serialize(jsonWriter, errorResponse.Error, EthereumJsonSerializer.JsonOptions); + } + else + { + jsonWriter.WriteNullValue(); + } + } + + jsonWriter.WritePropertyName("id"u8); + WriteId(jsonWriter, response.Id); + + jsonWriter.WriteEndObject(); + } + + private static void WriteId(Utf8JsonWriter writer, object? id) + { + switch (id) + { + case int intId: + writer.WriteNumberValue(intId); + break; + case long longId: + writer.WriteNumberValue(longId); + break; + case string strId: + writer.WriteStringValue(strId); + break; + case null: + writer.WriteNullValue(); + break; + default: + WriteOther(writer, id); + break; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void WriteOther(Utf8JsonWriter writer, object? id) + { + JsonSerializer.Serialize(writer, id, id.GetType(), EthereumJsonSerializer.JsonOptions); + } + } + + private static async ValueTask WriteStreamableResponseAsync( + CountingWriter writer, JsonRpcResponse response, + IStreamableResult streamable, CancellationToken ct) + { + writer.Write("{\"jsonrpc\":\"2.0\",\"result\":"u8); + await streamable.WriteToAsync(writer, ct); + writer.Write(",\"id\":"u8); + WriteIdRaw(writer, response.Id); + writer.Write("}"u8); + } + + private static void WriteIdRaw(PipeWriter writer, object? id) + { + switch (id) + { + case int intId: + { + Span buf = writer.GetSpan(11); + intId.TryFormat(buf, out int written); + writer.Advance(written); + break; + } + case long longId: + { + Span buf = writer.GetSpan(20); + longId.TryFormat(buf, out int written); + writer.Advance(written); + break; + } + default: + WriteOther(writer, id); + break; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void WriteOther(PipeWriter writer, object? id) + { + switch (id) + { + case string strId: + { + // JSON-RPC IDs are simple values (typically numeric); no escaping needed + Span buf = writer.GetSpan(strId.Length * 3 + 2); + buf[0] = (byte)'"'; + int len = Encoding.UTF8.GetBytes(strId, buf[1..]); + buf[len + 1] = (byte)'"'; + writer.Advance(len + 2); + break; + } + default: + writer.Write("null"u8); + break; + } + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void LogBadRequest(ILogger logger, Exception e) => + logger.Debug($"Couldn't read request.{Environment.NewLine}{e}"); + private sealed class CountingPipeReader(PipeReader stream) : PipeReader { private ReadOnlySequence _currentSequence; diff --git a/src/Nethermind/Nethermind.Runner/Program.cs b/src/Nethermind/Nethermind.Runner/Program.cs index 13512037d052..2440b46e7819 100644 --- a/src/Nethermind/Nethermind.Runner/Program.cs +++ b/src/Nethermind/Nethermind.Runner/Program.cs @@ -52,6 +52,13 @@ ILogger logger = new(SimpleConsoleLogger.Instance); ProcessExitSource? processExitSource = default; var unhandledError = "A critical error has occurred"; +Option[] deprecatedOptions = +[ + BasicOptions.ConfigurationDirectory, + BasicOptions.DatabasePath, + BasicOptions.LoggerConfigurationSource, + BasicOptions.PluginsDirectory +]; AppDomain.CurrentDomain.UnhandledException += (sender, e) => { @@ -230,9 +237,6 @@ static Option CreateOption(Type configType, string name, string? alias) foreach (Type configType in configTypes.Where(ct => !ct.IsAssignableTo(typeof(INoCategoryConfig))).OrderBy(c => c.Name)) { - if (configType is null) - continue; - ConfigCategoryAttribute? typeLevel = configType.GetCustomAttribute(); if (typeLevel is not null && typeLevel.DisabledForCli) @@ -277,14 +281,6 @@ static Option CreateOption(Type configType, string name, string? alias) void CheckForDeprecatedOptions(ParseResult parseResult) { - Option[] deprecatedOptions = - [ - BasicOptions.ConfigurationDirectory, - BasicOptions.DatabasePath, - BasicOptions.LoggerConfigurationSource, - BasicOptions.PluginsDirectory - ]; - foreach (Token token in parseResult.Tokens) { foreach (Option option in deprecatedOptions) diff --git a/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json index a2020a9c637c..932eb67f0559 100644 --- a/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 41550000, - "PivotHash": "0xf7d2cc48b2f2f3e785df037395a729aa6ae08640ee024826aa0d7938f56944e8" + "PivotNumber": 42150000, + "PivotHash": "0x54691ca9f732f6b3a2fd2b85ecf7359744c34bbc9ca08c619fdebe857196fb4f" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/configs/base-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/base-sepolia.json index bfec9bf16048..571e90d9457b 100644 --- a/src/Nethermind/Nethermind.Runner/configs/base-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/base-sepolia.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 37060000, - "PivotHash": "0x3f02d8a236bc757b2ae605078a8881dd5b208794fc1ad2e31501498ea54a062b" + "PivotNumber": 37660000, + "PivotHash": "0x1c23e29f8f4e5e3cc356bfeb79f01ceb5d9cf0026d39667f66bb1032c655ca9d" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/configs/chiado.json b/src/Nethermind/Nethermind.Runner/configs/chiado.json index 2453ce6ca660..7ef8547654c1 100644 --- a/src/Nethermind/Nethermind.Runner/configs/chiado.json +++ b/src/Nethermind/Nethermind.Runner/configs/chiado.json @@ -18,8 +18,8 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 19870000, - "PivotHash": "0x21d17776c1d770325eefc8df0753a91236886fef0774be51fccc4c1ed85d8a83", + "PivotNumber": 19780000, + "PivotHash": "0xac62a1145915936b74ef989b8ef0110be4542283e904fef2826726866fc6725e", "PivotTotalDifficulty": "231708131825107706987652208063906496124457284", "FastSyncCatchUpHeightDelta": 10000000000, "UseGethLimitsInFastBlocks": false diff --git a/src/Nethermind/Nethermind.Runner/configs/gnosis.json b/src/Nethermind/Nethermind.Runner/configs/gnosis.json index 93071d8dcba5..10ddaf8c213a 100644 --- a/src/Nethermind/Nethermind.Runner/configs/gnosis.json +++ b/src/Nethermind/Nethermind.Runner/configs/gnosis.json @@ -14,8 +14,8 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 44430000, - "PivotHash": "0x4975eece3dea5d0f47b308c557d0a5473328f55276e893ece40d7956d5b7bc58", + "PivotNumber": 44670000, + "PivotHash": "0x229a26df1c0b804793d81de764b4bcd9de5e810174f4e21ddc787d650235cc16", "PivotTotalDifficulty": "8626000110427538733349499292577475819600160930", "UseGethLimitsInFastBlocks": false, "FastSyncCatchUpHeightDelta": 10000000000, diff --git a/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json index 0fed00a3dfc4..bbfcd15344a9 100644 --- a/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json @@ -12,9 +12,9 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 22050000, - "PivotHash": "0x14d4cb874b1cb80011554f6942f68927ec2f5927b773d0f4edda8baca912ac9e", - "PivotTotalDifficulty": "39571169" + "PivotNumber": 22290000, + "PivotHash": "0x4bba26f5d256f972384413be51b4bfb5fd6c21d192d5fb55880d7baadc682631", + "PivotTotalDifficulty": "39936580" }, "Metrics": { "NodeName": "JOC-Mainnet" diff --git a/src/Nethermind/Nethermind.Runner/configs/joc-testnet.json b/src/Nethermind/Nethermind.Runner/configs/joc-testnet.json index 2bbf52bb35cf..b638cd8b5ca9 100644 --- a/src/Nethermind/Nethermind.Runner/configs/joc-testnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/joc-testnet.json @@ -12,9 +12,9 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 15650000, - "PivotHash": "0x482f1c47d98099e75cbf9ed03411f9ba1dd935962477cfc8276e40f5a03ec9b9", - "PivotTotalDifficulty": "26000126" + "PivotNumber": 15900000, + "PivotHash": "0xc545fbce23bbd9e6d756210003ef4b3c5021f84763b82cb33ba3f739cbe796fc", + "PivotTotalDifficulty": "26391193" }, "Metrics": { "NodeName": "JOC-Testnet" diff --git a/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json index 0c2559954cb9..f461ff99a533 100644 --- a/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json @@ -17,9 +17,9 @@ }, "Sync": { "SnapSync": true, - "PivotNumber": 28500000, - "PivotHash": "0xc59f58328c02099e9114d69187be7cbe373ea0f6c489c16e8064c448e013aed3", - "PivotTotalDifficulty": "0", + "PivotNumber": 28870000, + "PivotHash": "0x9d00c48eaf9e84f48936baeea8aba0866422524641546af30c9bb4afa256c367", + "PivotTotalDifficulty": "49575263", "HeaderStateDistance": 6 }, "JsonRpc": { diff --git a/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json index a5b145e4f66d..eb127802e2d4 100644 --- a/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json @@ -17,8 +17,8 @@ }, "Sync": { "SnapSync": true, - "PivotNumber": 24080000, - "PivotHash": "0xf11dd5fdf30d5f9d2e3a2ffae4da3b3353aa3576619d44a8457f667d1c72256d", + "PivotNumber": 24850000, + "PivotHash": "0xd5816d5b0b0024581511a44f029b9df64940b3d20ebff9cd8b2790fab67e4950", "PivotTotalDifficulty": "37331807", "HeaderStateDistance": 6 }, diff --git a/src/Nethermind/Nethermind.Runner/configs/mainnet.json b/src/Nethermind/Nethermind.Runner/configs/mainnet.json index 06c341f13328..9063a4642f8f 100644 --- a/src/Nethermind/Nethermind.Runner/configs/mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/mainnet.json @@ -10,8 +10,8 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 24357000, - "PivotHash": "0x46ee912a15df3d0642723f8f608386c560a6312ebea4a372f2f66e71fd8761bc", + "PivotNumber": 24457000, + "PivotHash": "0x1b315b4d85f8c804569022f1e0a021def8c2029ac4b55edeea03e2cf7c1f936e", "PivotTotalDifficulty": "58750003716598352816469", "FastSyncCatchUpHeightDelta": 10000000000, "AncientReceiptsBarrier": 15537394, diff --git a/src/Nethermind/Nethermind.Runner/configs/op-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/op-mainnet.json index 179ce25f29e7..8e20ed622711 100644 --- a/src/Nethermind/Nethermind.Runner/configs/op-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/op-mainnet.json @@ -15,8 +15,8 @@ "FastSyncCatchUpHeightDelta": "10000000000", "AncientBodiesBarrier": 105235063, "AncientReceiptsBarrier": 105235063, - "PivotNumber": 147140000, - "PivotHash": "0x41178d97cb45fa28393d94861e9ce65622bb2289a0e0d0e82d866fc5cda68f5e" + "PivotNumber": 147750000, + "PivotHash": "0xc5ca711298ccd110764eff891270897df703ba081239562286b263fbc046755e" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/configs/op-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/op-sepolia.json index e014551b66f5..6d79b23d20ef 100644 --- a/src/Nethermind/Nethermind.Runner/configs/op-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/op-sepolia.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 39040000, - "PivotHash": "0x638722bcdbef6103c08709eae7828dada842a5f25620ccc668aec1aef1e55406" + "PivotNumber": 39640000, + "PivotHash": "0x60392267e63a958936e102312fb81a90f023c23b372279e1b25112bdf92ea42c" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/configs/sepolia.json b/src/Nethermind/Nethermind.Runner/configs/sepolia.json index 06111c6d309a..00765e81652d 100644 --- a/src/Nethermind/Nethermind.Runner/configs/sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/sepolia.json @@ -17,8 +17,8 @@ "FastSync": true, "SnapSync": true, "UseGethLimitsInFastBlocks": true, - "PivotNumber": 10164000, - "PivotHash": "0x85a3f029e6d9d13080ef0aec663c5a457121ecc574cc5d322ab4aed2bf7c6a13", + "PivotNumber": 10261000, + "PivotHash": "0x4afc23c05b485ec4a9cdf17eb9d53e847d24d4ad580287a3db80ac5b06d5f241", "PivotTotalDifficulty": "17000018015853232", "FastSyncCatchUpHeightDelta": 10000000000, "AncientReceiptsBarrier": 1450409, diff --git a/src/Nethermind/Nethermind.Runner/configs/spaceneth.json b/src/Nethermind/Nethermind.Runner/configs/spaceneth.json index 1fa31a55f406..a9d0deccc1fe 100644 --- a/src/Nethermind/Nethermind.Runner/configs/spaceneth.json +++ b/src/Nethermind/Nethermind.Runner/configs/spaceneth.json @@ -46,7 +46,6 @@ "Subscribe", "Trace", "TxPool", - "Vault", "Web3" ] }, diff --git a/src/Nethermind/Nethermind.Runner/configs/spaceneth_persistent.json b/src/Nethermind/Nethermind.Runner/configs/spaceneth_persistent.json index 688f9c6f9567..efbc825140ea 100644 --- a/src/Nethermind/Nethermind.Runner/configs/spaceneth_persistent.json +++ b/src/Nethermind/Nethermind.Runner/configs/spaceneth_persistent.json @@ -41,7 +41,6 @@ "Subscribe", "Trace", "TxPool", - "Vault", "Web3" ] }, diff --git a/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json index 415192600e02..ac4884475eee 100644 --- a/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 25270000, - "PivotHash": "0x78183e57fb644b981fa8495ce86f7683bf4fbc3d3ff935d022795d809ec0e229" + "PivotNumber": 25880000, + "PivotHash": "0x8fa42f29309ccc1455c2d5af57f22a9941dcb13d7db23cc49508977bfb922bb6" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/configs/worldchain-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/worldchain-sepolia.json index eaa2d08f3f64..e2efd7ba62d3 100644 --- a/src/Nethermind/Nethermind.Runner/configs/worldchain-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/worldchain-sepolia.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 24670000, - "PivotHash": "0xec997d3cd44843014f21b520ed11bea72ca1d0a405a41da81b645f98a4c23c61" + "PivotNumber": 25270000, + "PivotHash": "0x4e5f1ecdcbc3931cc6dc40d3c5db56c00d08dac306caf26ccee31d7b6d0dadaf" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/packages.lock.json b/src/Nethermind/Nethermind.Runner/packages.lock.json index 858ddab75fd0..4c14de3e87d7 100644 --- a/src/Nethermind/Nethermind.Runner/packages.lock.json +++ b/src/Nethermind/Nethermind.Runner/packages.lock.json @@ -13,21 +13,21 @@ }, "Microsoft.Build.Tasks.Git": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + "requested": "[10.0.103, )", + "resolved": "10.0.103", + "contentHash": "QoiCMcPuxC6eqRQmrmF9zBY96ejIznXtve/lJJbonGD9I5Aygf2AUCOWslGiCEtBbfWRSuUnepBjuuVOdAl5ag==" }, "Microsoft.Extensions.FileProviders.Embedded": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "PkrDqw6uwm4Y7IucI3PjqwhCeCoHno3hzOeEt0hUrFI+ccYBC0X3NfQbhkFG46TgclnCyUDStmgJzpie9T9ZlQ==" + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "kw/xPl7m4Gv6bqx2ojihTtWiN2K2AklyMIrvncuSi2MOdwu0oMKoyh0G3p2Brt7m43Q9ER0IaA2G4EGjfgDh/w==" }, "Microsoft.VisualStudio.Azure.Containers.Tools.Targets": { "type": "Direct", - "requested": "[1.22.1, )", - "resolved": "1.22.1", - "contentHash": "EfYANhAWqmWKoLwN6bxoiPZSOfJSO9lzX+UrU6GVhLhPub1Hd+5f0zL0/tggIA6mRz6Ebw2xCNcIsM4k+7NPng==" + "requested": "[1.23.0, )", + "resolved": "1.23.0", + "contentHash": "2wDnb4umupJZ/1ikgWozFVpggH1mlHQFc0odXVv2ZagL3RYwXgW9zmC15fiqIBzmaC0vLZUnLGwDY+p8ZR7Syw==" }, "NLog.Targets.Seq": { "type": "Direct", @@ -40,15 +40,15 @@ }, "Pyroscope": { "type": "Direct", - "requested": "[0.13.0, )", - "resolved": "0.13.0", - "contentHash": "rdthieTs1xwkAl3z9eePA3kpQM+xRCqhiqupyXt15emyU5wVp+X5ur29W//fDmaJx4Rm2OH9xcLgJqacdtOMKg==" + "requested": "[0.14.1, )", + "resolved": "0.14.1", + "contentHash": "i8BoY82ZBrBOmgal7Zbf69CzQ2tRJkV0v7se1yB54rsOuuxFIzcUMplyD0VIg6Gl+EkVORNpm5OUL5tJGhR+lg==" }, "System.CommandLine": { "type": "Direct", - "requested": "[2.0.1, )", - "resolved": "2.0.1", - "contentHash": "GLc43eDFq8KbpxIb7UhTwV0vC5CzB0NspJvfFbfhoW4O057xCJXuO18KLpVn9x3JykQn2mRske6+I6JXHwqmDg==" + "requested": "[2.0.3, )", + "resolved": "2.0.3", + "contentHash": "5nY9hlrGGFEmyecNUux58sohD2Q16U6jlFBYwH57b2IVUs+u7LfMFaHwOtw2tuIi8CUl6jKXw5s4FuIt9aLtug==" }, "AspNetCore.HealthChecks.UI.Core": { "type": "Transitive", @@ -663,8 +663,7 @@ "dependencies": { "Nethermind.Core": "[1.37.0-unstable, )", "NonBlocking": "[2.1.2, )", - "PierTwo.Lantern.Discv5.WireProtocol": "[1.0.0-preview.7, )", - "System.Configuration.ConfigurationManager": "[10.0.1, )" + "System.Configuration.ConfigurationManager": "[10.0.3, )" } }, "nethermind.consensus": { @@ -732,11 +731,11 @@ "type": "Project", "dependencies": { "BouncyCastle.Cryptography": "[2.6.2, )", - "Ckzg.Bindings": "[2.1.5.1544, )", + "Ckzg.Bindings": "[2.1.5.1551, )", "Nethermind.Core": "[1.37.0-unstable, )", "Nethermind.Crypto.Bls": "[1.0.5, )", "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", - "System.Security.Cryptography.ProtectedData": "[10.0.1, )" + "System.Security.Cryptography.ProtectedData": "[10.0.3, )" } }, "nethermind.db": { @@ -744,6 +743,7 @@ "dependencies": { "Nethermind.Config": "[1.37.0-unstable, )", "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", + "Nethermind.TurboPForBindings": "[1.0.0, )", "NonBlocking": "[2.1.2, )" } }, @@ -754,7 +754,7 @@ "Nethermind.Api": "[1.37.0-unstable, )", "Nethermind.Db": "[1.37.0-unstable, )", "NonBlocking": "[2.1.2, )", - "RocksDB": "[10.4.2.62659, 10.4.2.62659]" + "RocksDB": "[10.4.2.63147, 10.4.2.63147]" } }, "nethermind.db.rpc": { @@ -779,7 +779,7 @@ "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", "Nethermind.Serialization.Ssz": "[1.37.0-unstable, )", "Nethermind.State": "[1.37.0-unstable, )", - "Snappier": "[1.2.0, )" + "Snappier": "[1.3.0, )" } }, "nethermind.ethstats": { @@ -813,7 +813,7 @@ "Nethermind.Crypto.SecP256r1": "[1.0.0-preview.6, )", "Nethermind.Evm": "[1.37.0-unstable, )", "Nethermind.GmpBindings": "[1.0.3, )", - "Nethermind.MclBindings": "[1.0.4, )", + "Nethermind.MclBindings": "[1.0.5, )", "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", "Nethermind.Specs": "[1.37.0-unstable, )" } @@ -844,8 +844,8 @@ "nethermind.grpc": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.33.2, )", - "Google.Protobuf.Tools": "[3.33.2, )", + "Google.Protobuf": "[3.33.5, )", + "Google.Protobuf.Tools": "[3.33.5, )", "Grpc": "[2.46.6, )", "Nethermind.Config": "[1.37.0-unstable, )", "Nethermind.Core": "[1.37.0-unstable, )", @@ -986,7 +986,7 @@ "Nethermind.Network.Contract": "[1.37.0-unstable, )", "Nethermind.Network.Stats": "[1.37.0-unstable, )", "Nethermind.Synchronization": "[1.37.0-unstable, )", - "Snappier": "[1.2.0, )" + "Snappier": "[1.3.0, )" } }, "nethermind.network.contract": { @@ -1033,7 +1033,7 @@ "nethermind.optimism": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.33.2, )", + "Google.Protobuf": "[3.33.5, )", "Nethermind.Api": "[1.37.0-unstable, )", "Nethermind.Blockchain": "[1.37.0-unstable, )", "Nethermind.Consensus": "[1.37.0-unstable, )", @@ -1043,7 +1043,7 @@ "Nethermind.Libp2p": "[1.0.0-preview.45, )", "Nethermind.Libp2p.Protocols.PubsubPeerDiscovery": "[1.0.0-preview.45, )", "Nethermind.Merge.Plugin": "[1.37.0-unstable, )", - "Snappier": "[1.2.0, )" + "Snappier": "[1.3.0, )" } }, "nethermind.seq": { @@ -1075,7 +1075,7 @@ "nethermind.shutter": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.33.2, )", + "Google.Protobuf": "[3.33.5, )", "Nethermind.Blockchain": "[1.37.0-unstable, )", "Nethermind.Consensus": "[1.37.0-unstable, )", "Nethermind.Core": "[1.37.0-unstable, )", @@ -1104,7 +1104,7 @@ "Nethermind.Config": "[1.37.0-unstable, )", "Nethermind.Core": "[1.37.0-unstable, )", "Nethermind.Serialization.Json": "[1.37.0-unstable, )", - "ZstdSharp.Port": "[0.8.6, )" + "ZstdSharp.Port": "[0.8.7, )" } }, "nethermind.state": { @@ -1237,9 +1237,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1544, )", - "resolved": "2.1.5.1544", - "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" }, "CommunityToolkit.HighPerformance": { "type": "CentralTransitive", @@ -1280,15 +1280,15 @@ }, "Google.Protobuf": { "type": "CentralTransitive", - "requested": "[3.33.2, )", - "resolved": "3.33.2", - "contentHash": "vZXVbrZgBqUkP5iWQi0CS6pucIS2MQdEYPS1duWCo8fGrrt4th6HTiHfLFX2RmAWAQl1oUnzGgyDBsfq7fHQJA==" + "requested": "[3.33.5, )", + "resolved": "3.33.5", + "contentHash": "XEzLpCTosZb5I6eGSPn7rAES0VfkJkn3Cqydh0W39POdZwkdhPhOmAROTFJF9g0ardst4ulNXRm/q/iXwNu+Qw==" }, "Google.Protobuf.Tools": { "type": "CentralTransitive", - "requested": "[3.33.2, )", - "resolved": "3.33.2", - "contentHash": "3YFiSs39mhBiAfeQ9u27JniqVNunVrYomNnSb8Rx6D3dJqC9Uwdpm5Xu2f2ZOGvUzkB114NAvU44KySOplgCzw==" + "requested": "[3.33.5, )", + "resolved": "3.33.5", + "contentHash": "A1UnkTCZvOsIW1+S8papxEx0CxrcIZlxEC+audWbvovU7cZAaF6Rfb2yJgPsTkzqU+dpyBpHE5v1tLPQ+NF4rQ==" }, "Grpc": { "type": "CentralTransitive", @@ -1478,9 +1478,9 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.4, )", - "resolved": "1.0.4", - "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" }, "Nethermind.Numerics.Int256": { "type": "CentralTransitive", @@ -1488,6 +1488,12 @@ "resolved": "1.4.0", "contentHash": "w8HRMsdpX9fG9kcELJeJPEKIZgOUTCe47ebtejCvfBYQVlabA9blqba6QWIt5oG8cRSgnVlQ24DsdGsLzqFM+Q==" }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" + }, "Nito.Collections.Deque": { "type": "CentralTransitive", "requested": "[1.2.1, )", @@ -1544,9 +1550,9 @@ }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.62659, 10.4.2.62659]", - "resolved": "10.4.2.62659", - "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" }, "SCrypt": { "type": "CentralTransitive", @@ -1556,24 +1562,24 @@ }, "Snappier": { "type": "CentralTransitive", - "requested": "[1.2.0, )", - "resolved": "1.2.0", - "contentHash": "Lv83i7hQZbl+r0qkO6VrBZ0OHL/R/onAVcCcxgYpT8inhqJ2/f1qkIWT3gWwdcCz4cPHOQrS0uX40cFUQyOS5Q==" + "requested": "[1.3.0, )", + "resolved": "1.3.0", + "contentHash": "yYANMXm5MUiF9jzsOI7WH5Cj1HYzcWEIEVI2Ljq8N7hS/zwLCBx+GoGeyc7aJFAvpRcbSIbdNaelVomHqd6UDQ==" }, "System.Configuration.ConfigurationManager": { "type": "CentralTransitive", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "HfOAIlSA8OuaxBZD6xjsUWhtB0KdKSWEfRId8gSGveLUjuP6G8IxfiFgJNxaiRIEC1kx4pSvz3Em5xW/J6LLxA==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "69ZT/MYxQSxwdiiHRtI08noXiG5drj/bXDDZISmeWkNUtbIfYgmTiof16tCVOLTdmSQY7W7gwxkMliKdreWHGQ==", "dependencies": { - "System.Security.Cryptography.ProtectedData": "10.0.1" + "System.Security.Cryptography.ProtectedData": "10.0.3" } }, "System.Security.Cryptography.ProtectedData": { "type": "CentralTransitive", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "9SqHNq+lAjZeyPcm69FTQEjr+wsRYvkS3aW8yxoEndVYwDRkCrsP/44QPqpWHwzevoX26rkOoQ6kr7GZWngw2A==" + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "JCKbH/CN5l0CSoJBILEvJmNQVp5vV+FY3q2ue4K9p4eDT4mFEv0bjTQCV+MD6Qk1b/qk9fWmZZKhG1TklbXw1Q==" }, "TestableIO.System.IO.Abstractions.Wrappers": { "type": "CentralTransitive", @@ -1596,9 +1602,9 @@ }, "ZstdSharp.Port": { "type": "CentralTransitive", - "requested": "[0.8.6, )", - "resolved": "0.8.6", - "contentHash": "iP4jVLQoQmUjMU88g1WObiNr6YKZGvh4aOXn3yOJsHqZsflwRsxZPcIBvNXgjXO3vQKSLctXGLTpcBPLnWPS8A==" + "requested": "[0.8.7, )", + "resolved": "0.8.7", + "contentHash": "+4VpxvzEKaHpfTjsLRMhQHx6brqGBkIA+fjUM3wUW8ZoWpPFAeKrO2Nf4uZDeBjjzNDNxSRvLQH4b3S9Ku4JJQ==" } }, "net10.0/linux-arm64": { @@ -1622,9 +1628,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1544, )", - "resolved": "2.1.5.1544", - "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -1682,15 +1688,21 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.4, )", - "resolved": "1.0.4", - "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.62659, 10.4.2.62659]", - "resolved": "10.4.2.62659", - "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" } }, "net10.0/linux-x64": { @@ -1714,9 +1726,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1544, )", - "resolved": "2.1.5.1544", - "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -1774,15 +1786,21 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.4, )", - "resolved": "1.0.4", - "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.62659, 10.4.2.62659]", - "resolved": "10.4.2.62659", - "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" } }, "net10.0/osx-arm64": { @@ -1806,9 +1824,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1544, )", - "resolved": "2.1.5.1544", - "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -1866,15 +1884,21 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.4, )", - "resolved": "1.0.4", - "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.62659, 10.4.2.62659]", - "resolved": "10.4.2.62659", - "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" } }, "net10.0/osx-x64": { @@ -1898,9 +1922,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1544, )", - "resolved": "2.1.5.1544", - "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -1958,15 +1982,21 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.4, )", - "resolved": "1.0.4", - "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.62659, 10.4.2.62659]", - "resolved": "10.4.2.62659", - "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" } }, "net10.0/win-x64": { @@ -1990,9 +2020,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1544, )", - "resolved": "2.1.5.1544", - "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -2050,15 +2080,21 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.4, )", - "resolved": "1.0.4", - "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.62659, 10.4.2.62659]", - "resolved": "10.4.2.62659", - "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/CountingTextReader.cs b/src/Nethermind/Nethermind.Serialization.Json/CountingTextReader.cs deleted file mode 100644 index fa57f6b5a795..000000000000 --- a/src/Nethermind/Nethermind.Serialization.Json/CountingTextReader.cs +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Nethermind.Serialization.Json -{ - public class CountingTextReader : TextReader - { - private readonly TextReader _innerReader; - public int Length { get; private set; } - - public CountingTextReader(TextReader innerReader) - { - _innerReader = innerReader; - } - - public override void Close() - { - base.Close(); - _innerReader.Close(); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposing) - { - _innerReader.Dispose(); - } - } - - public override int Peek() => _innerReader.Peek(); - - public override int Read() - { - Length++; - return _innerReader.Read(); - } - - public override int Read(char[] buffer, int index, int count) => IncrementLength(_innerReader.Read(buffer, index, count)); - - public override int Read(Span buffer) => IncrementLength(_innerReader.Read(buffer)); - - public override async Task ReadAsync(char[] buffer, int index, int count) => IncrementLength(await _innerReader.ReadAsync(buffer, index, count)); - - public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => IncrementLength(await _innerReader.ReadAsync(buffer, cancellationToken)); - - public override int ReadBlock(char[] buffer, int index, int count) => IncrementLength(_innerReader.ReadBlock(buffer, index, count)); - - public override int ReadBlock(Span buffer) => IncrementLength(_innerReader.ReadBlock(buffer)); - - public override string ReadLine() => IncrementLength(_innerReader.ReadLine()); - - public override async ValueTask ReadBlockAsync(Memory buffer, CancellationToken cancellationToken = default) => IncrementLength(await _innerReader.ReadBlockAsync(buffer, cancellationToken)); - - public override async Task ReadBlockAsync(char[] buffer, int index, int count) => IncrementLength(await _innerReader.ReadBlockAsync(buffer, index, count)); - - public override async Task ReadLineAsync() => IncrementLength(await _innerReader.ReadLineAsync()); - - public override string ReadToEnd() => IncrementLength(_innerReader.ReadToEnd()); - - public override async Task ReadToEndAsync() => IncrementLength(await _innerReader.ReadToEndAsync()); - - private string IncrementLength(in string read) - { - if (!string.IsNullOrEmpty(read)) - { - Length += read.Length; - } - return read; - } - - private int IncrementLength(in int read) - { - Length += read; - return read; - } - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Json/CountingTextWriter.cs b/src/Nethermind/Nethermind.Serialization.Json/CountingTextWriter.cs deleted file mode 100644 index 16beb11721d9..000000000000 --- a/src/Nethermind/Nethermind.Serialization.Json/CountingTextWriter.cs +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.IO; -using System.Text; - -namespace Nethermind.Serialization.Json -{ - public class CountingTextWriter : TextWriter - { - private readonly TextWriter _textWriter; - - public long Size { get; private set; } - - public CountingTextWriter(TextWriter textWriter) - { - _textWriter = textWriter ?? throw new ArgumentNullException(nameof(textWriter)); - } - - public override Encoding Encoding => _textWriter.Encoding; - - public override void Write(char value) - { - _textWriter.Write(value); - Size++; - } - - public override void Flush() - { - _textWriter.Flush(); - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - _textWriter.Dispose(); - } - } - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs b/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs index 173e83017d5d..7ce9dc3bb296 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs @@ -9,57 +9,74 @@ using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; using Nethermind.Core.Collections; namespace Nethermind.Serialization.Json { - public class EthereumJsonSerializer : IJsonSerializer + public sealed class EthereumJsonSerializer : IJsonSerializer { public const int DefaultMaxDepth = 128; + private static readonly object _globalOptionsLock = new(); + + private static readonly List _additionalConverters = new(); + private static readonly List _additionalResolvers = new(); + private static bool _strictHexFormat; + private static int _optionsVersion; + private readonly int? _maxDepth; - private readonly JsonSerializerOptions _jsonOptions; + private readonly JsonConverter[] _instanceConverters; + private readonly object _instanceOptionsLock = new(); + + private JsonSerializerOptions _jsonOptions = null!; + private JsonSerializerOptions _jsonOptionsIndented = null!; + private int _instanceOptionsVersion; public EthereumJsonSerializer(IEnumerable converters, int maxDepth = DefaultMaxDepth) { _maxDepth = maxDepth; - _jsonOptions = CreateOptions(indented: false, maxDepth: maxDepth, converters: converters); + _instanceConverters = CopyConverters(converters); + RefreshInstanceOptions(); } public EthereumJsonSerializer(int maxDepth = DefaultMaxDepth) { _maxDepth = maxDepth; - _jsonOptions = maxDepth != DefaultMaxDepth ? CreateOptions(indented: false, maxDepth: maxDepth) : JsonOptions; + _instanceConverters = []; + RefreshInstanceOptions(); } public object Deserialize(string json, Type type) { - return JsonSerializer.Deserialize(json, type, _jsonOptions); + return JsonSerializer.Deserialize(json, type, GetSerializerOptions(indented: false)); } public T Deserialize(Stream stream) { - return JsonSerializer.Deserialize(stream, _jsonOptions); + return JsonSerializer.Deserialize(stream, GetSerializerOptions(indented: false)); } public T Deserialize(string json) { - return JsonSerializer.Deserialize(json, _jsonOptions); + return JsonSerializer.Deserialize(json, GetSerializerOptions(indented: false)); } public T Deserialize(ref Utf8JsonReader json) { - return JsonSerializer.Deserialize(ref json, _jsonOptions); + return JsonSerializer.Deserialize(ref json, GetSerializerOptions(indented: false)); } public string Serialize(T value, bool indented = false) { - return JsonSerializer.Serialize(value, indented ? JsonOptionsIndented : _jsonOptions); + return JsonSerializer.Serialize(value, GetSerializerOptions(indented)); } - private static JsonSerializerOptions CreateOptions(bool indented, IEnumerable converters = null, int maxDepth = DefaultMaxDepth) + private static JsonSerializerOptions CreateOptions(bool indented, IEnumerable instanceConverters = null, int maxDepth = DefaultMaxDepth) { + SnapshotGlobalOptions(out bool strictHexFormat, out JsonConverter[] additionalConverters, out IJsonTypeInfoResolver[] additionalResolvers); + var result = new JsonSerializerOptions { WriteIndented = indented, @@ -71,6 +88,7 @@ private static JsonSerializerOptions CreateOptions(bool indented, IEnumerable()); + result.Converters.AddRange(additionalConverters); + result.Converters.AddRange(instanceConverters ?? Array.Empty()); return result; } - private static readonly List _additionalConverters = new(); public static void AddConverter(JsonConverter converter) { - _additionalConverters.Add(converter); + ArgumentNullException.ThrowIfNull(converter); + lock (_globalOptionsLock) + { + _additionalConverters.Add(converter); + RefreshGlobalOptionsNoLock(); + } + } - JsonOptions = CreateOptions(indented: false); - JsonOptionsIndented = CreateOptions(indented: true); + public static void AddTypeInfoResolver(IJsonTypeInfoResolver resolver) + { + ArgumentNullException.ThrowIfNull(resolver); + lock (_globalOptionsLock) + { + for (int i = 0; i < _additionalResolvers.Count; i++) + { + if (ReferenceEquals(_additionalResolvers[i], resolver)) + { + return; + } + } + + _additionalResolvers.Add(resolver); + RefreshGlobalOptionsNoLock(); + } } - private static bool _strictHexFormat; public static bool StrictHexFormat { get => _strictHexFormat; set { - if (_strictHexFormat == value) - return; - _strictHexFormat = value; - JsonOptions = CreateOptions(indented: false); - JsonOptionsIndented = CreateOptions(indented: true); + lock (_globalOptionsLock) + { + if (_strictHexFormat == value) + return; + + _strictHexFormat = value; + RefreshGlobalOptionsNoLock(); + } } } @@ -132,8 +171,8 @@ public static bool StrictHexFormat public static JsonSerializerOptions JsonOptionsIndented { get; private set; } = CreateOptions(indented: true); - private static readonly StreamPipeWriterOptions optionsLeaveOpen = new(pool: MemoryPool.Shared, minimumBufferSize: 4096, leaveOpen: true); - private static readonly StreamPipeWriterOptions options = new(pool: MemoryPool.Shared, minimumBufferSize: 4096, leaveOpen: false); + private static readonly StreamPipeWriterOptions optionsLeaveOpen = new(pool: MemoryPool.Shared, minimumBufferSize: 16384, leaveOpen: true); + private static readonly StreamPipeWriterOptions options = new(pool: MemoryPool.Shared, minimumBufferSize: 16384, leaveOpen: false); private static CountingStreamPipeWriter GetPipeWriter(Stream stream, bool leaveOpen) { @@ -144,7 +183,7 @@ public long Serialize(Stream stream, T value, bool indented = false, bool lea { var countingWriter = GetPipeWriter(stream, leaveOpen); using var writer = new Utf8JsonWriter(countingWriter, CreateWriterOptions(indented)); - JsonSerializer.Serialize(writer, value, indented ? JsonOptionsIndented : _jsonOptions); + JsonSerializer.Serialize(writer, value, GetSerializerOptions(indented)); countingWriter.Complete(); long outputCount = countingWriter.WrittenCount; @@ -161,7 +200,7 @@ private JsonWriterOptions CreateWriterOptions(bool indented) public async ValueTask SerializeAsync(Stream stream, T value, CancellationToken cancellationToken, bool indented = false, bool leaveOpen = true) { var writer = GetPipeWriter(stream, leaveOpen); - await JsonSerializer.SerializeAsync(writer, value, indented ? JsonOptionsIndented : _jsonOptions, cancellationToken); + await JsonSerializer.SerializeAsync(writer, value, GetSerializerOptions(indented), cancellationToken); await writer.CompleteAsync(); long outputCount = writer.WrittenCount; @@ -169,12 +208,121 @@ public async ValueTask SerializeAsync(Stream stream, T value, Cancellat } public Task SerializeAsync(PipeWriter writer, T value, bool indented = false) - => JsonSerializer.SerializeAsync(writer, value, indented ? JsonOptionsIndented : _jsonOptions); + { + using var jsonWriter = new Utf8JsonWriter((IBufferWriter)writer, CreateWriterOptions(indented)); + JsonSerializer.Serialize(jsonWriter, value, GetSerializerOptions(indented)); + return Task.CompletedTask; + } + + /// + /// Pre-serializes instances to warm System.Text.Json metadata caches at startup. + /// + public static void WarmupSerializer(params object[] instances) + { + foreach (object instance in instances) + { + _ = JsonSerializer.SerializeToUtf8Bytes(instance, instance.GetType(), JsonOptions); + } + } public static void SerializeToStream(Stream stream, T value, bool indented = false) { JsonSerializer.Serialize(stream, value, indented ? JsonOptionsIndented : JsonOptions); } + + private JsonSerializerOptions GetSerializerOptions(bool indented) + { + EnsureInstanceOptionsCurrent(); + return indented ? _jsonOptionsIndented : _jsonOptions; + } + + private void EnsureInstanceOptionsCurrent() + { + int currentVersion = Volatile.Read(ref _optionsVersion); + if (_instanceOptionsVersion == currentVersion) + { + return; + } + + lock (_instanceOptionsLock) + { + if (_instanceOptionsVersion != currentVersion) + { + RefreshInstanceOptions(); + } + } + } + + private void RefreshInstanceOptions() + { + _jsonOptions = CreateOptions(indented: false, instanceConverters: _instanceConverters, maxDepth: _maxDepth ?? DefaultMaxDepth); + _jsonOptionsIndented = CreateOptions(indented: true, instanceConverters: _instanceConverters, maxDepth: _maxDepth ?? DefaultMaxDepth); + _instanceOptionsVersion = Volatile.Read(ref _optionsVersion); + } + + private static void RefreshGlobalOptionsNoLock() + { + JsonOptions = CreateOptions(indented: false); + JsonOptionsIndented = CreateOptions(indented: true); + Interlocked.Increment(ref _optionsVersion); + } + + private static void SnapshotGlobalOptions(out bool strictHexFormat, out JsonConverter[] additionalConverters, out IJsonTypeInfoResolver[] additionalResolvers) + { + lock (_globalOptionsLock) + { + strictHexFormat = _strictHexFormat; + additionalConverters = new JsonConverter[_additionalConverters.Count]; + for (int i = 0; i < _additionalConverters.Count; i++) + { + additionalConverters[i] = _additionalConverters[i]; + } + + additionalResolvers = new IJsonTypeInfoResolver[_additionalResolvers.Count]; + for (int i = 0; i < _additionalResolvers.Count; i++) + { + additionalResolvers[i] = _additionalResolvers[i]; + } + } + } + + private static IJsonTypeInfoResolver BuildTypeInfoResolver(IReadOnlyList additionalResolvers) + { + int additionalResolversCount = additionalResolvers.Count; + if (additionalResolversCount == 0) + { + return new DefaultJsonTypeInfoResolver(); + } + + IJsonTypeInfoResolver[] resolverChain = new IJsonTypeInfoResolver[additionalResolversCount + 1]; + for (int i = 0; i < additionalResolversCount; i++) + { + resolverChain[i] = additionalResolvers[i]; + } + + resolverChain[additionalResolversCount] = new DefaultJsonTypeInfoResolver(); + return JsonTypeInfoResolver.Combine(resolverChain); + } + + private static JsonConverter[] CopyConverters(IEnumerable converters) + { + ArgumentNullException.ThrowIfNull(converters); + + if (converters is JsonConverter[] convertersArray) + { + JsonConverter[] clone = new JsonConverter[convertersArray.Length]; + Array.Copy(convertersArray, clone, convertersArray.Length); + return clone; + } + + List list = new(); + foreach (JsonConverter converter in converters) + { + list.Add(converter); + } + + return [.. list]; + } } public static class JsonElementExtensions diff --git a/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs b/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs index 27d95de085e4..b6c0046f1b18 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs @@ -1,13 +1,37 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Runtime.CompilerServices; using System.Threading; namespace Nethermind.Serialization.Json; public static class ForcedNumberConversion { - public static readonly AsyncLocal ForcedConversion = new(); + public static readonly ThreadAwareAsyncLocal ForcedConversion = new(); - public static NumberConversion GetFinalConversion() => ForcedConversion.Value ?? NumberConversion.Hex; + [ThreadStatic] + private static NumberConversion? _threadCache; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NumberConversion GetFinalConversion() => _threadCache ?? NumberConversion.Hex; + + /// + /// Wrapper around AsyncLocal that also updates a ThreadStatic cache for fast reads. + /// + public sealed class ThreadAwareAsyncLocal + { + private readonly AsyncLocal _asyncLocal = new(); + + public NumberConversion? Value + { + get => _asyncLocal.Value; + set + { + _asyncLocal.Value = value; + _threadCache = value; + } + } + } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/Hash256Converter.cs b/src/Nethermind/Nethermind.Serialization.Json/Hash256Converter.cs index 2f74049a80ff..6ff8b3c1eb3d 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/Hash256Converter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/Hash256Converter.cs @@ -3,8 +3,10 @@ #nullable enable using System; -using System.Text.Json.Serialization; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text.Json; +using System.Text.Json.Serialization; using Nethermind.Core.Crypto; namespace Nethermind.Serialization.Json; @@ -28,12 +30,36 @@ public Hash256Converter(bool strictHexFormat = false) return bytes is null ? null : new Hash256(bytes); } + [SkipLocalsInit] public override void Write( Utf8JsonWriter writer, Hash256 keccak, JsonSerializerOptions options) { - ByteArrayConverter.Convert(writer, keccak.Bytes, skipLeadingZeros: false); + WriteHashHex(writer, in keccak.ValueHash256); + } + + /// + /// SIMD-accelerated hex encoding for 32-byte hashes. + /// Writes raw JSON (including quotes) via WriteRawValue to bypass the encoder entirely. + /// + [SkipLocalsInit] + internal static void WriteHashHex(Utf8JsonWriter writer, in ValueHash256 hash) + { + // Raw JSON: '"' + "0x" + 64 hex chars + '"' = 68 bytes + Unsafe.SkipInit(out HexWriter.HexBuffer72 rawBuf); + ref byte b = ref Unsafe.As(ref rawBuf); + + Unsafe.Add(ref b, 0) = (byte)'"'; + Unsafe.WriteUnaligned(ref Unsafe.Add(ref b, 1), (ushort)0x7830); // "0x" LE + + HexWriter.Encode32Bytes(ref Unsafe.Add(ref b, 3), hash.Bytes); + + Unsafe.Add(ref b, 67) = (byte)'"'; + + writer.WriteRawValue( + MemoryMarshal.CreateReadOnlySpan(ref b, 68), + skipInputValidation: true); } // Methods needed to ser/de dictionary keys diff --git a/src/Nethermind/Nethermind.Serialization.Json/HexWriter.cs b/src/Nethermind/Nethermind.Serialization.Json/HexWriter.cs new file mode 100644 index 000000000000..ef0fbd2106fc --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/HexWriter.cs @@ -0,0 +1,428 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers.Binary; +using System.IO.Pipelines; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using System.Text.Json; +using Nethermind.Int256; + +namespace Nethermind.Serialization.Json; + +/// +/// Shared low-level hex encoding primitives used by JSON converters. +/// +public static class HexWriter +{ + /// + /// Encode the low 8 bytes of a Vector128 to 16 hex chars using SSSE3 PSHUFB. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Ssse3Encode8Bytes(ref byte dest, Vector128 input) + { + Vector128 hexLookup = Vector128.Create( + (byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', + (byte)'c', (byte)'d', (byte)'e', (byte)'f'); + Vector128 mask = Vector128.Create((byte)0x0F); + + Vector128 hi = Sse2.ShiftRightLogical(input.AsUInt16(), 4).AsByte() & mask; + Vector128 lo = input & mask; + Ssse3.Shuffle(hexLookup, Sse2.UnpackLow(hi, lo)).StoreUnsafe(ref dest); + } + + /// + /// Encode 16 bytes of a Vector128 to 32 hex chars using SSSE3 PSHUFB. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Ssse3Encode16Bytes(ref byte dest, Vector128 input) + { + Vector128 hexLookup = Vector128.Create( + (byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', + (byte)'c', (byte)'d', (byte)'e', (byte)'f'); + Vector128 mask = Vector128.Create((byte)0x0F); + + Vector128 hi = Sse2.ShiftRightLogical(input.AsUInt16(), 4).AsByte() & mask; + Vector128 lo = input & mask; + Ssse3.Shuffle(hexLookup, Sse2.UnpackLow(hi, lo)).StoreUnsafe(ref dest); + Ssse3.Shuffle(hexLookup, Sse2.UnpackHigh(hi, lo)).StoreUnsafe(ref Unsafe.Add(ref dest, 16)); + } + + /// + /// Encode 32 bytes to 64 hex chars using AVX-512 VBMI cross-lane byte permutation. + /// vpermi2b does arbitrary byte interleave across the full 256-bit register in a single + /// instruction, eliminating the UnpackLow/UnpackHigh + lane-crossing overhead of SSSE3/AVX2. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Avx512VbmiEncode32Bytes(ref byte dest, Vector256 input) + { + Vector256 mask = Vector256.Create((byte)0x0F); + Vector256 hi = Avx2.ShiftRightLogical(input.AsUInt16(), 4).AsByte() & mask; + Vector256 lo = input & mask; + + // vpermi2b: pick hi[i], lo[i] pairs across full 256-bit width + // indices 0-31 select from hi, 32-63 select from lo + Vector256 interleaved0 = Avx512Vbmi.VL.PermuteVar32x8x2(hi, + Vector256.Create( + (byte)0, 32, 1, 33, 2, 34, 3, 35, 4, 36, 5, 37, 6, 38, 7, 39, + 8, 40, 9, 41, 10, 42, 11, 43, 12, 44, 13, 45, 14, 46, 15, 47), lo); + + Vector256 interleaved1 = Avx512Vbmi.VL.PermuteVar32x8x2(hi, + Vector256.Create( + (byte)16, 48, 17, 49, 18, 50, 19, 51, 20, 52, 21, 53, 22, 54, 23, 55, + 24, 56, 25, 57, 26, 58, 27, 59, 28, 60, 29, 61, 30, 62, 31, 63), lo); + + // vpshufb: nibble-to-hex lookup (works within 128-bit lanes, lookup replicated in both) + Vector256 hexLookup = Vector256.Create( + (byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', + (byte)'c', (byte)'d', (byte)'e', (byte)'f', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', + (byte)'c', (byte)'d', (byte)'e', (byte)'f'); + + Avx2.Shuffle(hexLookup, interleaved0).StoreUnsafe(ref dest); + Avx2.Shuffle(hexLookup, interleaved1).StoreUnsafe(ref Unsafe.Add(ref dest, 32)); + } + + /// + /// 512-byte lookup table: for byte value i, HexByteLookup[i*2] and [i*2+1] are the + /// two lowercase hex ASCII chars. Single indexed load + 16-bit store per byte, + /// replacing ~10 ALU ops of a branchless arithmetic approach. + /// + private static ReadOnlySpan HexByteLookup => + "000102030405060708090a0b0c0d0e0f"u8 + + "101112131415161718191a1b1c1d1e1f"u8 + + "202122232425262728292a2b2c2d2e2f"u8 + + "303132333435363738393a3b3c3d3e3f"u8 + + "404142434445464748494a4b4c4d4e4f"u8 + + "505152535455565758595a5b5c5d5e5f"u8 + + "606162636465666768696a6b6c6d6e6f"u8 + + "707172737475767778797a7b7c7d7e7f"u8 + + "808182838485868788898a8b8c8d8e8f"u8 + + "909192939495969798999a9b9c9d9e9f"u8 + + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"u8 + + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"u8 + + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"u8 + + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"u8 + + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"u8 + + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"u8; + + /// + /// Scalar: encode one byte to 2 hex chars via lookup table. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void EncodeByte(ref byte dest, int byteVal) + { + Unsafe.WriteUnaligned(ref dest, + Unsafe.ReadUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(HexByteLookup), byteVal * 2))); + } + + /// + /// Scalar: encode a ulong (big-endian byte order) to 16 hex chars. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void EncodeUlongScalar(ref byte dest, ulong value) + { + ref byte lookup = ref MemoryMarshal.GetReference(HexByteLookup); + for (int i = 0; i < 8; i++) + { + int byteVal = (int)(value >> ((7 - i) << 3)) & 0xFF; + Unsafe.WriteUnaligned(ref Unsafe.Add(ref dest, i * 2), + Unsafe.ReadUnaligned(ref Unsafe.Add(ref lookup, byteVal * 2))); + } + } + + /// + /// Scalar: encode a byte span to hex chars. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void EncodeBytesScalar(ref byte dest, ReadOnlySpan src) + { + ref byte lookup = ref MemoryMarshal.GetReference(HexByteLookup); + for (int i = 0; i < src.Length; i++) + { + Unsafe.WriteUnaligned(ref Unsafe.Add(ref dest, i * 2), + Unsafe.ReadUnaligned(ref Unsafe.Add(ref lookup, src[i] * 2))); + } + } + + /// + /// Encode a ulong to 16 hex chars, dispatching to SSSE3 or scalar. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void EncodeUlong(ref byte dest, ulong value) + { + if (Ssse3.IsSupported) + { + ulong be = BinaryPrimitives.ReverseEndianness(value); + Ssse3Encode8Bytes(ref dest, Vector128.CreateScalarUnsafe(be).AsByte()); + } + else + { + EncodeUlongScalar(ref dest, value); + } + } + + /// + /// Encode 32 bytes to 64 hex chars, dispatching to AVX-512 VBMI, SSSE3, or scalar. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Encode32Bytes(ref byte dest, ReadOnlySpan src) + { + if (Avx512Vbmi.VL.IsSupported) + { + Avx512VbmiEncode32Bytes(ref dest, Vector256.LoadUnsafe(ref MemoryMarshal.GetReference(src))); + } + else if (Ssse3.IsSupported) + { + ref byte srcRef = ref MemoryMarshal.GetReference(src); + Ssse3Encode16Bytes(ref dest, Vector128.LoadUnsafe(ref srcRef)); + Ssse3Encode16Bytes(ref Unsafe.Add(ref dest, 32), Vector128.LoadUnsafe(ref srcRef, 16)); + } + else + { + EncodeBytesScalar(ref dest, src); + } + } + + /// + /// Write a non-zero ulong as a hex JSON string value ("0x...") using WriteRawValue. + /// Used by LongConverter and ULongConverter. + /// + [SkipLocalsInit] + internal static void WriteUlongHexRawValue(Utf8JsonWriter writer, ulong value) + { + // Use InlineArray to avoid GS cookie overhead from stackalloc + Unsafe.SkipInit(out HexBuffer24 rawBuf); + ref byte b = ref Unsafe.As(ref rawBuf); + + EncodeUlong(ref Unsafe.Add(ref b, 3), value); + + // nibbleCount: ceil(significantBits / 4), guaranteed >= 1 since value != 0 + // nint keeps Unsafe.Add in 64-bit register arithmetic, avoiding movsxd + nint nibbleCount = (nint)((67 - (uint)BitOperations.LeadingZeroCount(value)) >> 2); + nint spanStart = 16 - nibbleCount; + + ref byte spanRef = ref Unsafe.Add(ref b, spanStart); + spanRef = (byte)'"'; + Unsafe.WriteUnaligned(ref Unsafe.Add(ref spanRef, 1), (ushort)0x7830); // "0x" LE + Unsafe.Add(ref b, 19) = (byte)'"'; + + writer.WriteRawValue( + MemoryMarshal.CreateReadOnlySpan(ref spanRef, (int)nibbleCount + 4), + skipInputValidation: true); + } + + /// + /// Write a UInt256 as a hex JSON string value ("0x...") using WriteRawValue. + /// + [SkipLocalsInit] + internal static void WriteUInt256HexRawValue(Utf8JsonWriter writer, UInt256 value, bool zeroPadded = false) + { + Unsafe.SkipInit(out HexBuffer72 rawBuf); + ref byte buffer = ref Unsafe.As(ref rawBuf); + + BuildUInt256Hex(ref buffer, value, includeQuotes: true, zeroPadded, out nint spanStart, out int spanLength); + + writer.WriteRawValue( + MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref buffer, spanStart), spanLength), + skipInputValidation: true); + } + + /// + /// Write a UInt256 as a hex property name ("0x..."). + /// + [SkipLocalsInit] + internal static void WriteUInt256HexPropertyName(Utf8JsonWriter writer, UInt256 value, bool zeroPadded = false) + { + Unsafe.SkipInit(out HexBuffer72 rawBuf); + ref byte buffer = ref Unsafe.As(ref rawBuf); + + BuildUInt256Hex(ref buffer, value, includeQuotes: false, zeroPadded, out nint spanStart, out int spanLength); + + writer.WritePropertyName( + MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref buffer, spanStart), spanLength)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void BuildUInt256Hex(ref byte buffer, UInt256 value, bool includeQuotes, bool zeroPadded, out nint spanStart, out int spanLength) + { + nint hexOffset = includeQuotes ? 3 : 2; + EncodeUInt256Hex(ref Unsafe.Add(ref buffer, hexOffset), value); + + int nibbleCount = zeroPadded ? 64 : GetSignificantNibbleCount(value); + spanStart = zeroPadded ? 0 : 64 - nibbleCount; + ref byte spanRef = ref Unsafe.Add(ref buffer, spanStart); + + if (includeQuotes) + { + spanRef = (byte)'"'; + Unsafe.WriteUnaligned(ref Unsafe.Add(ref spanRef, 1), (ushort)0x7830); // "0x" LE + Unsafe.Add(ref spanRef, nibbleCount + 3) = (byte)'"'; + spanLength = nibbleCount + 4; + } + else + { + Unsafe.WriteUnaligned(ref spanRef, (ushort)0x7830); // "0x" LE + spanLength = nibbleCount + 2; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetSignificantNibbleCount(UInt256 value) + { + int leadingZeroBits; + if (value.u3 != 0) + { + leadingZeroBits = BitOperations.LeadingZeroCount(value.u3); + } + else if (value.u2 != 0) + { + leadingZeroBits = 64 + BitOperations.LeadingZeroCount(value.u2); + } + else if (value.u1 != 0) + { + leadingZeroBits = 128 + BitOperations.LeadingZeroCount(value.u1); + } + else + { + leadingZeroBits = 192 + BitOperations.LeadingZeroCount(value.u0); + } + + int nibbleCount = (259 - leadingZeroBits) >> 2; + return nibbleCount == 0 ? 1 : nibbleCount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EncodeUInt256Hex(ref byte dest, UInt256 value) + { + if (Avx512Vbmi.VL.IsSupported) + { + Vector256 reversed = Avx512Vbmi.VL.PermuteVar32x8( + Vector256.LoadUnsafe(ref Unsafe.As(ref value)), + Vector256.Create( + (byte)31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)); + Avx512VbmiEncode32Bytes(ref dest, reversed); + } + else if (Ssse3.IsSupported) + { + Ssse3Encode16Bytes(ref dest, + Vector128.Create( + BinaryPrimitives.ReverseEndianness(value.u3), + BinaryPrimitives.ReverseEndianness(value.u2)).AsByte()); + + Ssse3Encode16Bytes(ref Unsafe.Add(ref dest, 32), + Vector128.Create( + BinaryPrimitives.ReverseEndianness(value.u1), + BinaryPrimitives.ReverseEndianness(value.u0)).AsByte()); + } + else + { + EncodeUlongScalar(ref dest, value.u3); + EncodeUlongScalar(ref Unsafe.Add(ref dest, 16), value.u2); + EncodeUlongScalar(ref Unsafe.Add(ref dest, 32), value.u1); + EncodeUlongScalar(ref Unsafe.Add(ref dest, 48), value.u0); + } + } + + /// + /// 24-byte inline buffer for ulong hex encoding (20 bytes needed, rounded up to + /// 3 x 8-byte ulong elements for alignment). Used instead of stackalloc to avoid + /// GS cookie (stack canary) overhead. The JIT inserts a cookie write in the prologue + /// and a verify + CORINFO_HELP_FAIL_FAST call in the epilogue for every stackalloc + /// buffer, adding ~35 bytes per method. Inline array structs are treated as regular + /// locals and avoid this. + /// + [InlineArray(3)] + private struct HexBuffer24 + { + private ulong _element0; + } + + /// + /// 72-byte inline buffer for hash/UInt256 hex encoding (68 bytes needed, rounded up + /// to 9 x 8-byte ulong elements for alignment). Used instead of stackalloc to avoid + /// GS cookie (stack canary) overhead. The JIT inserts a cookie write in the prologue + /// and a verify + CORINFO_HELP_FAIL_FAST call in the epilogue for every stackalloc + /// buffer, adding ~35 bytes per method. Inline array structs are treated as regular + /// locals and avoid this. + /// + [InlineArray(9)] + internal struct HexBuffer72 + { + private ulong _element0; + } + + private const int MaxHexRequest = 4096; + + /// + /// Writes a large byte array as hex directly into a + /// in chunks, bounded by the actual span size returned by GetSpan. + /// + public static void WriteHexChunked(PipeWriter writer, byte[] data) + { + ReadOnlySpan remaining = data; + while (remaining.Length > 0) + { + Span hex = writer.GetSpan(Math.Min(remaining.Length * 2, MaxHexRequest)); + int inputLen = Math.Min(remaining.Length, hex.Length / 2); + EncodeToHex(remaining[..inputLen], ref MemoryMarshal.GetReference(hex)); + writer.Advance(inputLen * 2); + + remaining = remaining[inputLen..]; + } + } + + /// + /// Writes a small byte array as hex in a single span into a . + /// + public static void WriteHexSmall(PipeWriter writer, byte[] data) + { + int hexLen = data.Length * 2; + Span hex = writer.GetSpan(hexLen); + int inputLen = Math.Min(data.Length, hex.Length / 2); + EncodeToHex(((ReadOnlySpan)data)[..inputLen], ref MemoryMarshal.GetReference(hex)); + writer.Advance(inputLen * 2); + } + + /// + /// Encode arbitrary-length bytes to hex using SIMD (AVX-512 VBMI / SSSE3) with scalar tail. + /// + private static void EncodeToHex(ReadOnlySpan src, ref byte dest) + { + int offset = 0; + + // 32-byte blocks: AVX-512 VBMI or 2x SSSE3 or scalar + while (offset + 32 <= src.Length) + { + Encode32Bytes(ref Unsafe.Add(ref dest, offset * 2), src.Slice(offset, 32)); + offset += 32; + } + + // 16-byte block via SSSE3 + if (Ssse3.IsSupported && offset + 16 <= src.Length) + { + Ssse3Encode16Bytes(ref Unsafe.Add(ref dest, offset * 2), + Vector128.LoadUnsafe(ref Unsafe.Add(ref MemoryMarshal.GetReference(src), offset))); + offset += 16; + } + + // Scalar tail + if (offset < src.Length) + { + EncodeBytesScalar(ref Unsafe.Add(ref dest, offset * 2), src[offset..]); + } + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs index 7b46c9e2dc3d..a2ecd66fc590 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs @@ -2,142 +2,141 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using System.Buffers.Text; using System.Globalization; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Text.Json.Serialization; +using Nethermind.Core.Extensions; -namespace Nethermind.Serialization.Json +namespace Nethermind.Serialization.Json; + +public class LongConverter : JsonConverter { - using Nethermind.Core.Extensions; - using System.Buffers; - using System.Buffers.Binary; - using System.Buffers.Text; - using System.Runtime.CompilerServices; - using System.Text.Json; - using System.Text.Json.Serialization; - - public class LongConverter : JsonConverter + public static long FromString(string s) { - public static long FromString(string s) + if (s is null) { - if (s is null) - { - throw new JsonException("null cannot be assigned to long"); - } + throw new JsonException("null cannot be assigned to long"); + } - if (s == Bytes.ZeroHexValue) - { - return 0L; - } + if (s == Bytes.ZeroHexValue) + { + return 0L; + } - if (s.StartsWith("0x0")) - { - return long.Parse(s.AsSpan(2), NumberStyles.AllowHexSpecifier); - } + if (s.StartsWith("0x0")) + { + return long.Parse(s.AsSpan(2), NumberStyles.AllowHexSpecifier); + } - if (s.StartsWith("0x")) - { - Span withZero = new(new char[s.Length - 1]); - withZero[0] = '0'; - s.AsSpan(2).CopyTo(withZero[1..]); - return long.Parse(withZero, NumberStyles.AllowHexSpecifier); - } + if (s.StartsWith("0x")) + { + Span withZero = new(new char[s.Length - 1]); + withZero[0] = '0'; + s.AsSpan(2).CopyTo(withZero[1..]); + return long.Parse(withZero, NumberStyles.AllowHexSpecifier); + } - return long.Parse(s, NumberStyles.Integer); + return long.Parse(s, NumberStyles.Integer); + } + + public override long ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + ReadOnlySpan hex = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; + return FromString(hex); + } + + public static long FromString(ReadOnlySpan s) + { + if (s.Length == 0) + { + throw new JsonException("null cannot be assigned to long"); } - public override long ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + if (s.SequenceEqual("0x0"u8)) { - ReadOnlySpan hex = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; - return FromString(hex); + return 0L; } - public static long FromString(ReadOnlySpan s) + long value; + if (s.StartsWith("0x"u8)) { - if (s.Length == 0) + s = s[2..]; + if (Utf8Parser.TryParse(s, out value, out _, 'x')) { - throw new JsonException("null cannot be assigned to long"); + return value; } + } + else if (Utf8Parser.TryParse(s, out value, out _)) + { + return value; + } - if (s.SequenceEqual("0x0"u8)) - { - return 0L; - } + ThrowJsonException(); + return default; - long value; - if (s.StartsWith("0x"u8)) - { - s = s[2..]; - if (Utf8Parser.TryParse(s, out value, out _, 'x')) - { - return value; - } - } - else if (Utf8Parser.TryParse(s, out value, out _)) - { - return value; - } + [DoesNotReturn, StackTraceHidden] + static void ThrowJsonException() => throw new JsonException("hex to long"); + } - throw new JsonException("hex to long"); + internal static long ReadCore(ref Utf8JsonReader reader) + { + if (reader.TokenType == JsonTokenType.Number) + { + return reader.GetInt64(); } + else if (reader.TokenType == JsonTokenType.String) + { + return !reader.HasValueSequence + ? FromString(reader.ValueSpan) + : FromString(reader.ValueSequence.ToArray()); + } + + ThrowJsonException(); + return default; + + [DoesNotReturn, StackTraceHidden] + static void ThrowJsonException() => throw new JsonException(); + } + + public override long Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + return ReadCore(ref reader); + } - internal static long ReadCore(ref Utf8JsonReader reader) + [SkipLocalsInit] + public override void Write( + Utf8JsonWriter writer, + long value, + JsonSerializerOptions options) + { + switch (ForcedNumberConversion.GetFinalConversion()) { - if (reader.TokenType == JsonTokenType.Number) - { - return reader.GetInt64(); - } - else if (reader.TokenType == JsonTokenType.String) - { - if (!reader.HasValueSequence) + case NumberConversion.Hex: + if (value == 0) { - return FromString(reader.ValueSpan); + writer.WriteStringValue("0x0"u8); } else { - return FromString(reader.ValueSequence.ToArray()); + HexWriter.WriteUlongHexRawValue(writer, (ulong)value); } - } - - throw new JsonException(); - } - - public override long Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) - { - return ReadCore(ref reader); - } - - [SkipLocalsInit] - public override void Write( - Utf8JsonWriter writer, - long value, - JsonSerializerOptions options) - { - switch (ForcedNumberConversion.GetFinalConversion()) - { - case NumberConversion.Hex: - if (value == 0) - { - writer.WriteRawValue("\"0x0\""u8, skipInputValidation: true); - } - else - { - Span bytes = stackalloc byte[8]; - BinaryPrimitives.WriteInt64BigEndian(bytes, value); - ByteArrayConverter.Convert(writer, bytes, skipLeadingZeros: true); - } - break; - case NumberConversion.Decimal: - writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); - break; - case NumberConversion.Raw: - writer.WriteNumberValue(value); - break; - default: - throw new NotSupportedException(); - - } + break; + case NumberConversion.Decimal: + writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); + break; + case NumberConversion.Raw: + writer.WriteNumberValue(value); + break; + default: + throw new NotSupportedException(); } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs index 9d023cdcf66f..0ca57dd9f5c6 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs @@ -2,41 +2,39 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json; +using System.Text.Json.Serialization; -namespace Nethermind.Serialization.Json +namespace Nethermind.Serialization.Json; + +public class NullableULongConverter : JsonConverter { - using System.Text.Json; - using System.Text.Json.Serialization; + private readonly ULongConverter _converter = new(); - public class NullableULongConverter : JsonConverter + public override ulong? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { - private readonly ULongConverter _converter = new(); - - public override ulong? Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) + if (reader.TokenType == JsonTokenType.Null) { - if (reader.TokenType == JsonTokenType.Null) - { - return null; - } - - return _converter.Read(ref reader, typeToConvert, options); + return null; } - public override void Write( - Utf8JsonWriter writer, - ulong? value, - JsonSerializerOptions options) - { - if (!value.HasValue) - { - writer.WriteNullValue(); - return; - } + return _converter.Read(ref reader, typeToConvert, options); + } - _converter.Write(writer, value.GetValueOrDefault(), options); + public override void Write( + Utf8JsonWriter writer, + ulong? value, + JsonSerializerOptions options) + { + if (!value.HasValue) + { + writer.WriteNullValue(); + return; } + + _converter.Write(writer, value.GetValueOrDefault(), options); } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/PubSub/LogPublisher.cs b/src/Nethermind/Nethermind.Serialization.Json/PubSub/LogPublisher.cs deleted file mode 100644 index 75a0807cca7d..000000000000 --- a/src/Nethermind/Nethermind.Serialization.Json/PubSub/LogPublisher.cs +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading.Tasks; -using Nethermind.Core.PubSub; -using Nethermind.Logging; - -namespace Nethermind.Serialization.Json.PubSub -{ - public class LogPublisher : IPublisher - { - private readonly ILogger _logger; - private readonly IJsonSerializer _jsonSerializer; - - public LogPublisher(IJsonSerializer jsonSerializer, ILogManager logManager) - { - _logger = logManager.GetClassLogger(); - _jsonSerializer = jsonSerializer; - } - - public Task PublishAsync(T data) where T : class - { - if (_logger.IsInfo) _logger.Info(_jsonSerializer.Serialize(data)); - return Task.CompletedTask; - } - - public void Dispose() - { - } - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs b/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs index 336fd38d6d69..611e64a0affc 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs @@ -99,28 +99,16 @@ public static UInt256 ReadHex(ReadOnlySpan hex) return new UInt256(in readOnlyBytes, isBigEndian: true); } - [SkipLocalsInit] public override void Write( Utf8JsonWriter writer, UInt256 value, JsonSerializerOptions options) { - NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); - if (value.IsZero) - { - writer.WriteRawValue(usedConversion == NumberConversion.ZeroPaddedHex - ? "\"0x0000000000000000000000000000000000000000000000000000000000000000\""u8 - : "\"0x0\""u8); - return; - } - switch (usedConversion) + NumberConversion conversion = ForcedNumberConversion.GetFinalConversion(); + switch (conversion) { case NumberConversion.Hex: - { - Span bytes = stackalloc byte[32]; - value.ToBigEndian(bytes); - ByteArrayConverter.Convert(writer, bytes); - } + HexWriter.WriteUInt256HexRawValue(writer, value); break; case NumberConversion.Decimal: writer.WriteRawValue(value.ToString(CultureInfo.InvariantCulture)); @@ -129,35 +117,23 @@ public override void Write( writer.WriteStringValue(((BigInteger)value).ToString(CultureInfo.InvariantCulture)); break; case NumberConversion.ZeroPaddedHex: - { - Span bytes = stackalloc byte[32]; - value.ToBigEndian(bytes); - ByteArrayConverter.Convert(writer, bytes, skipLeadingZeros: false); - } + HexWriter.WriteUInt256HexRawValue(writer, value, zeroPadded: true); break; default: - throw new NotSupportedException($"{usedConversion} format is not supported for {nameof(UInt256)}"); + throw new NotSupportedException($"{conversion} format is not supported for {nameof(UInt256)}"); } } public override UInt256 ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => ReadInternal(ref reader, JsonTokenType.PropertyName); - [SkipLocalsInit] public override void WriteAsPropertyName(Utf8JsonWriter writer, UInt256 value, JsonSerializerOptions options) { - NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); - if (value.IsZero) - { - writer.WritePropertyName(usedConversion == NumberConversion.ZeroPaddedHex - ? "0x0000000000000000000000000000000000000000000000000000000000000000"u8 - : "0x0"u8); - return; - } - switch (usedConversion) + NumberConversion conversion = ForcedNumberConversion.GetFinalConversion(); + switch (conversion) { case NumberConversion.Hex: - WriteHexPropertyName(writer, value, false); + HexWriter.WriteUInt256HexPropertyName(writer, value); break; case NumberConversion.Decimal: writer.WritePropertyName(value.ToString(CultureInfo.InvariantCulture)); @@ -166,25 +142,13 @@ public override void WriteAsPropertyName(Utf8JsonWriter writer, UInt256 value, J writer.WritePropertyName(((BigInteger)value).ToString(CultureInfo.InvariantCulture)); break; case NumberConversion.ZeroPaddedHex: - WriteHexPropertyName(writer, value, true); + HexWriter.WriteUInt256HexPropertyName(writer, value, zeroPadded: true); break; default: - throw new NotSupportedException($"{usedConversion} format is not supported for {nameof(UInt256)}"); + throw new NotSupportedException($"{conversion} format is not supported for {nameof(UInt256)}"); } } - private static void WriteHexPropertyName(Utf8JsonWriter writer, UInt256 value, bool isZeroPadded) - { - Span bytes = stackalloc byte[32]; - value.ToBigEndian(bytes); - ByteArrayConverter.Convert( - writer, - bytes, - static (w, h) => w.WritePropertyName(h), - skipLeadingZeros: !isZeroPadded, - addQuotations: false); - } - [DoesNotReturn, StackTraceHidden] private static void ThrowJsonException() { diff --git a/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs index e0fb0988f5e8..d9f672dedf9e 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs @@ -2,109 +2,108 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Text.Json.Serialization; -namespace Nethermind.Serialization.Json -{ - using System.Buffers; - using System.Buffers.Binary; - using System.Buffers.Text; - using System.Globalization; - using System.Runtime.CompilerServices; - using System.Text.Json; - using System.Text.Json.Serialization; +namespace Nethermind.Serialization.Json; - public class ULongConverter : JsonConverter +public class ULongConverter : JsonConverter +{ + public static ulong FromString(ReadOnlySpan s) { - public static ulong FromString(ReadOnlySpan s) + if (s.Length == 0) { - if (s.Length == 0) - { - throw new JsonException("null cannot be assigned to ulong"); - } + throw new JsonException("null cannot be assigned to ulong"); + } - if (s.SequenceEqual("0x0"u8)) - { - return 0uL; - } + if (s.SequenceEqual("0x0"u8)) + { + return 0uL; + } - ulong value; - if (s.StartsWith("0x"u8)) - { - s = s[2..]; - if (Utf8Parser.TryParse(s, out value, out _, 'x')) - { - return value; - } - } - else if (Utf8Parser.TryParse(s, out value, out _)) + ulong value; + if (s.StartsWith("0x"u8)) + { + s = s[2..]; + if (Utf8Parser.TryParse(s, out value, out _, 'x')) { return value; } - - throw new JsonException("hex to long"); } - - [SkipLocalsInit] - public override void Write( - Utf8JsonWriter writer, - ulong value, - JsonSerializerOptions options) + else if (Utf8Parser.TryParse(s, out value, out _)) { - NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); - switch (usedConversion) - { - case NumberConversion.Hex: - { - if (value == 0) - { - writer.WriteRawValue("\"0x0\""u8, skipInputValidation: true); - } - else - { - Span bytes = stackalloc byte[8]; - BinaryPrimitives.WriteUInt64BigEndian(bytes, value); - ByteArrayConverter.Convert(writer, bytes, skipLeadingZeros: true); - } - break; - } - case NumberConversion.Decimal: - writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); - break; - case NumberConversion.Raw: - writer.WriteNumberValue(value); - break; - default: - throw new NotSupportedException(); - } + return value; } - internal static ulong ReadCore(ref Utf8JsonReader reader) + + ThrowJsonException(); + return default; + + [DoesNotReturn, StackTraceHidden] + static void ThrowJsonException() => throw new JsonException("hex to long"); + } + + [SkipLocalsInit] + public override void Write( + Utf8JsonWriter writer, + ulong value, + JsonSerializerOptions options) + { + NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); + switch (usedConversion) { - if (reader.TokenType == JsonTokenType.Number) - { - return reader.GetUInt64(); - } - if (reader.TokenType == JsonTokenType.String) - { - if (!reader.HasValueSequence) + case NumberConversion.Hex: + if (value == 0) { - return FromString(reader.ValueSpan); + writer.WriteStringValue("0x0"u8); } else { - return FromString(reader.ValueSequence.ToArray()); + HexWriter.WriteUlongHexRawValue(writer, value); } - } - - throw new JsonException(); + break; + case NumberConversion.Decimal: + writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); + break; + case NumberConversion.Raw: + writer.WriteNumberValue(value); + break; + default: + throw new NotSupportedException(); } + } - public override ulong Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) + internal static ulong ReadCore(ref Utf8JsonReader reader) + { + if (reader.TokenType == JsonTokenType.Number) { - return ReadCore(ref reader); + return reader.GetUInt64(); } + if (reader.TokenType == JsonTokenType.String) + { + return !reader.HasValueSequence + ? FromString(reader.ValueSpan) + : FromString(reader.ValueSequence.ToArray()); + } + + ThrowJsonException(); + return default; + + [DoesNotReturn, StackTraceHidden] + static void ThrowJsonException() => throw new JsonException(); + } + + public override ulong Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + return ReadCore(ref reader); } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/ValueHash256Converter.cs b/src/Nethermind/Nethermind.Serialization.Json/ValueHash256Converter.cs index 18282391b58d..c38cd342cba6 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ValueHash256Converter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ValueHash256Converter.cs @@ -3,6 +3,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Serialization; using Nethermind.Core.Crypto; @@ -27,11 +28,12 @@ public override ValueHash256 Read( return bytes is null ? null : new ValueHash256(bytes); } + [SkipLocalsInit] public override void Write( Utf8JsonWriter writer, ValueHash256 keccak, JsonSerializerOptions options) { - ByteArrayConverter.Convert(writer, keccak.Bytes, skipLeadingZeros: false); + Hash256Converter.WriteHashHex(writer, in keccak); } } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs index 136035521590..90c559c1ab62 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs @@ -40,6 +40,12 @@ public BlockDecoder() : this(new HeaderDecoder()) { } (int txs, int uncles, int? withdrawals) = _blockBodyDecoder.GetBodyComponentLength(item.Body); + byte[][]? encodedTxs = item.EncodedTransactions; + if (encodedTxs is not null) + { + txs = GetPreEncodedTxLength(item.Transactions, encodedTxs); + } + int contentLength = headerLength + Rlp.LengthOfSequence(txs) + @@ -48,6 +54,16 @@ public BlockDecoder() : this(new HeaderDecoder()) { } return (contentLength, txs, uncles, withdrawals); } + private static int GetPreEncodedTxLength(Transaction[] txs, byte[][] encodedTxs) + { + int sum = 0; + for (int i = 0; i < encodedTxs.Length; i++) + { + sum += TxDecoder.GetWrappedTxLength(txs[i].Type, encodedTxs[i].Length); + } + return sum; + } + public override int GetLength(Block? item, RlpBehaviors rlpBehaviors) { if (item is null) @@ -104,9 +120,21 @@ public override void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehav stream.StartSequence(contentLength); _headerDecoder.Encode(stream, item.Header); stream.StartSequence(txsLength); - for (int i = 0; i < item.Transactions.Length; i++) + + byte[][]? encodedTxs = item.EncodedTransactions; + if (encodedTxs is not null) { - stream.Encode(item.Transactions[i]); + for (int i = 0; i < encodedTxs.Length; i++) + { + TxDecoder.WriteWrappedFormat(stream, item.Transactions[i].Type, encodedTxs[i]); + } + } + else + { + for (int i = 0; i < item.Transactions.Length; i++) + { + stream.Encode(item.Transactions[i]); + } } stream.StartSequence(unclesLength); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs index b16af62649d5..c523cc2555e3 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs @@ -328,8 +328,7 @@ public static Rlp Encode(T item, RlpBehaviors behaviors = RlpBehaviors.None) { if (item is Rlp rlp) { - RlpStream stream = new(LengthOfSequence(rlp.Length)); - return new(stream.Data.ToArray()); + return rlp; } IRlpStreamDecoder? rlpStreamDecoder = GetStreamDecoder(); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs index bf3174dd9a74..22d063b13661 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs @@ -24,6 +24,25 @@ static TxDecoder() Instance = new TxDecoder(static () => TxObjectPool.Get()); Rlp.RegisterDecoder(typeof(Transaction), Instance); } + + /// + /// Gets the block-format length of a pre-encoded CL-format transaction. + /// Legacy txs use the same format; typed txs are wrapped in an RLP byte string. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetWrappedTxLength(TxType type, int clEncodedLength) + => type == TxType.Legacy ? clEncodedLength : Rlp.LengthOfSequence(clEncodedLength); + + /// + /// Writes a pre-encoded CL-format transaction in block format. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteWrappedFormat(RlpStream stream, TxType type, byte[] clEncoded) + { + if (type != TxType.Legacy) + stream.StartByteArray(clEncoded.Length, false); + stream.Write(clEncoded); + } } public sealed class SystemTxDecoder : TxDecoder; diff --git a/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.BasicTypes.cs b/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.BasicTypes.cs index f95471e7864e..41bd474e5c5a 100644 --- a/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.BasicTypes.cs +++ b/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.BasicTypes.cs @@ -15,6 +15,8 @@ namespace Nethermind.Serialization.Ssz; /// public static partial class Ssz { + private const int VarOffsetSize = sizeof(uint); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(Span span, byte[] value, ref int offset) { diff --git a/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.Containers.cs b/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.Containers.cs deleted file mode 100644 index 3055b6038a62..000000000000 --- a/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.Containers.cs +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; - -namespace Nethermind.Serialization.Ssz; - -public partial class Ssz -{ - private const int VarOffsetSize = sizeof(uint); - - private static void DecodeDynamicOffset(ReadOnlySpan span, ref int offset, out int dynamicOffset) - { - dynamicOffset = (int)DecodeUInt(span.Slice(offset, VarOffsetSize)); - offset += sizeof(uint); - } - -} diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs index 5bd3e4fc9a97..c50e687cbce7 100644 --- a/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs @@ -115,7 +115,9 @@ public async Task Can_increment_metric_on_missed_keys() time += (long)ShutterTestsCommon.SlotLength.TotalSeconds; } - Assert.That(Metrics.ShutterKeysMissed, Is.EqualTo(5)); + // ImproveBlock tasks run in the background and may not have completed yet + // when GetPayload returns (it only waits 50ms), so poll until all increments land. + Assert.That(() => Metrics.ShutterKeysMissed, Is.EqualTo((ulong)5).After(5000, 50)); } } diff --git a/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs b/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs index d80eebe4be39..3ec0ba3c2cce 100644 --- a/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs @@ -433,7 +433,7 @@ public void Selfdestruct_clears_cache() WorldState provider = BuildStorageProvider(ctx); StorageCell accessedStorageCell = new StorageCell(TestItem.AddressA, 1); StorageCell nonAccessedStorageCell = new StorageCell(TestItem.AddressA, 2); - preBlockCaches.StorageCache[accessedStorageCell] = [1, 2, 3]; + preBlockCaches.StorageCache.Set(accessedStorageCell, [1, 2, 3]); provider.Get(accessedStorageCell); provider.Commit(Paris.Instance); provider.ClearStorage(TestItem.AddressA); @@ -602,7 +602,7 @@ public void Selfdestruct_persist_between_commit() PreBlockCaches preBlockCaches = new PreBlockCaches(); Context ctx = new(preBlockCaches); StorageCell accessedStorageCell = new StorageCell(TestItem.AddressA, 1); - preBlockCaches.StorageCache[accessedStorageCell] = [1, 2, 3]; + preBlockCaches.StorageCache.Set(accessedStorageCell, [1, 2, 3]); WorldState provider = BuildStorageProvider(ctx); provider.Get(accessedStorageCell).ToArray().Should().BeEquivalentTo([1, 2, 3]); @@ -611,6 +611,25 @@ public void Selfdestruct_persist_between_commit() provider.Get(accessedStorageCell).ToArray().Should().BeEquivalentTo(StorageTree.ZeroBytes); } + [Test] + public void Eip161_empty_account_with_storage_does_not_throw_on_commit() + { + IWorldState worldState = new WorldState( + new TrieStoreScopeProvider(TestTrieStoreFactory.Build(new MemDb(), LimboLogs.Instance), new MemDb(), LimboLogs.Instance), LogManager); + + using var disposable = worldState.BeginScope(IWorldState.PreGenesis); + + // Create an empty account (balance=0, nonce=0, no code) and set storage on it. + // EIP-161 (via SpuriousDragon+) deletes empty accounts during commit, but the + // storage flush has already produced a non-empty storage root. The commit must + // handle this gracefully by skipping the storage root update for deleted accounts. + worldState.CreateAccount(TestItem.AddressA, 0); + worldState.Set(new StorageCell(TestItem.AddressA, 1), [1, 2, 3]); + worldState.Commit(SpuriousDragon.Instance); + + worldState.AccountExists(TestItem.AddressA).Should().BeFalse(); + } + [TestCase(2)] [TestCase(1000)] public void Set_empty_value_for_storage_cell_without_read_clears_data(int numItems) diff --git a/src/Nethermind/Nethermind.State.Test/TrieStoreScopeProviderTests.cs b/src/Nethermind/Nethermind.State.Test/TrieStoreScopeProviderTests.cs index d3742eec32b1..6338d4340452 100644 --- a/src/Nethermind/Nethermind.State.Test/TrieStoreScopeProviderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/TrieStoreScopeProviderTests.cs @@ -96,4 +96,26 @@ public void Test_CanSaveToCode() codeKv.WritesCount.Should().Be(1); } + + [Test] + public void Test_NullAccountWithNonEmptyStorageDoesNotThrow() + { + TestMemDb kv = new TestMemDb(); + IWorldStateScopeProvider scopeProvider = new TrieStoreScopeProvider(new TestRawTrieStore(kv), new MemDb(), LimboLogs.Instance); + + using var scope = scopeProvider.BeginScope(null); + + // Simulates the EIP-161 scenario: storage is flushed for an account that was + // then deleted (set to null) during state commit. The write batch Dispose should + // skip the storage root update for the deleted account instead of throwing. + using (var writeBatch = scope.StartWriteBatch(1)) + { + using (var storageSet = writeBatch.CreateStorageWriteBatch(TestItem.AddressA, 1)) + { + storageSet.Set(1, [1, 2, 3]); + } + + writeBatch.Set(TestItem.AddressA, null); + } + } } diff --git a/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs b/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs index d8ac3c455f1d..7b1a1da5dd5a 100644 --- a/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs @@ -15,10 +15,10 @@ namespace Nethermind.State.OverridableEnv; public class OverridableCodeInfoRepository(ICodeInfoRepository codeInfoRepository, IWorldState worldState) : IOverridableCodeInfoRepository { - private readonly Dictionary _codeOverrides = new(); - private readonly Dictionary _precompileOverrides = new(); + private readonly Dictionary _codeOverrides = new(); + private readonly Dictionary _precompileOverrides = new(); - public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) + public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) { delegationAddress = null; if (_precompileOverrides.TryGetValue(codeSource, out var precompile)) return precompile.codeInfo; @@ -41,9 +41,9 @@ public void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpe public void SetCodeOverride( IReleaseSpec vmSpec, Address key, - ICodeInfo value) + CodeInfo value) { - _codeOverrides[key] = (value, ValueKeccak.Compute(value.CodeSpan)); + _codeOverrides[key] = (value, ValueKeccak.Compute(value.Code.Span)); } public void MovePrecompile(IReleaseSpec vmSpec, Address precompileAddr, Address targetAddr) diff --git a/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs b/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs index f74d9a3f5d46..85bf38a4e6ca 100644 --- a/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs +++ b/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs @@ -452,7 +452,6 @@ public void UnmarkClear() private sealed class PerContractState : IReturnable { - private static readonly Func _loadFromTreeStorageFunc = LoadFromTreeStorage; private IWorldStateScopeProvider.IStorageTree? _backend; private readonly DefaultableDictionary BlockChange = new(); diff --git a/src/Nethermind/Nethermind.State/PreBlockCaches.cs b/src/Nethermind/Nethermind.State/PreBlockCaches.cs index 3fd8cf5e8cd7..e7f8bf290735 100644 --- a/src/Nethermind/Nethermind.State/PreBlockCaches.cs +++ b/src/Nethermind/Nethermind.State/PreBlockCaches.cs @@ -19,24 +19,24 @@ public class PreBlockCaches private readonly Func[] _clearCaches; - private readonly ConcurrentDictionary _storageCache = new(LockPartitions, InitialCapacity); - private readonly ConcurrentDictionary _stateCache = new(LockPartitions, InitialCapacity); - private readonly ConcurrentDictionary _rlpCache = new(LockPartitions, InitialCapacity); + private readonly SeqlockCache _storageCache = new(); + private readonly SeqlockCache _stateCache = new(); + private readonly SeqlockCache _rlpCache = new(); private readonly ConcurrentDictionary> _precompileCache = new(LockPartitions, InitialCapacity); public PreBlockCaches() { _clearCaches = [ - () => _storageCache.NoResizeClear() ? CacheType.Storage : CacheType.None, - () => _stateCache.NoResizeClear() ? CacheType.State : CacheType.None, - () => _precompileCache.NoResizeClear() ? CacheType.Precompile : CacheType.None + () => { _storageCache.Clear(); return CacheType.None; }, + () => { _stateCache.Clear(); return CacheType.None; }, + () => { _precompileCache.NoResizeClear(); return CacheType.None; } ]; } - public ConcurrentDictionary StorageCache => _storageCache; - public ConcurrentDictionary StateCache => _stateCache; - public ConcurrentDictionary RlpCache => _rlpCache; + public SeqlockCache StorageCache => _storageCache; + public SeqlockCache StateCache => _stateCache; + public SeqlockCache RlpCache => _rlpCache; public ConcurrentDictionary> PrecompileCache => _precompileCache; public CacheType ClearCaches() diff --git a/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs b/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs index d4260a7f5278..af258806395f 100644 --- a/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs +++ b/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Concurrent; using System.Diagnostics; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Metric; using Nethermind.Db; @@ -40,16 +40,26 @@ public class PrewarmerScopeProvider( public PreBlockCaches? Caches => preBlockCaches; public bool IsWarmWorldState => !populatePreBlockCache; - private sealed class ScopeWrapper( - IWorldStateScopeProvider.IScope baseScope, - PreBlockCaches preBlockCaches, - bool populatePreBlockCache) - : IWorldStateScopeProvider.IScope + private sealed class ScopeWrapper : IWorldStateScopeProvider.IScope { - ConcurrentDictionary preBlockCache = preBlockCaches.StateCache; + private readonly IWorldStateScopeProvider.IScope baseScope; + private readonly SeqlockCache preBlockCache; + private readonly SeqlockCache storageCache; + private readonly bool populatePreBlockCache; + private readonly SeqlockCache.ValueFactory _getFromBaseTree; private readonly IMetricObserver _metricObserver = Metrics.PrewarmerGetTime; private readonly bool _measureMetric = Metrics.DetailedMetricsEnabled; - private readonly PrewarmerGetTimeLabels _labels = populatePreBlockCache ? PrewarmerGetTimeLabels.Prewarmer : PrewarmerGetTimeLabels.NonPrewarmer; + private readonly PrewarmerGetTimeLabels _labels; + + public ScopeWrapper(IWorldStateScopeProvider.IScope baseScope, PreBlockCaches preBlockCaches, bool populatePreBlockCache) + { + this.baseScope = baseScope; + preBlockCache = preBlockCaches.StateCache; + storageCache = preBlockCaches.StorageCache; + this.populatePreBlockCache = populatePreBlockCache; + _labels = populatePreBlockCache ? PrewarmerGetTimeLabels.Prewarmer : PrewarmerGetTimeLabels.NonPrewarmer; + _getFromBaseTree = GetFromBaseTree; + } public void Dispose() => baseScope.Dispose(); @@ -59,7 +69,7 @@ public IWorldStateScopeProvider.IStorageTree CreateStorageTree(Address address) { return new StorageTreeWrapper( baseScope.CreateStorageTree(address), - preBlockCaches.StorageCache, + storageCache, address, populatePreBlockCache); } @@ -114,7 +124,7 @@ public void UpdateRootHash() if (populatePreBlockCache) { long priorReads = Metrics.ThreadLocalStateTreeReads; - Account? account = preBlockCache.GetOrAdd(address, GetFromBaseTree); + Account? account = preBlockCache.GetOrAdd(in addressAsKey, _getFromBaseTree); if (Metrics.ThreadLocalStateTreeReads == priorReads) { @@ -129,7 +139,7 @@ public void UpdateRootHash() } else { - if (preBlockCache?.TryGetValue(addressAsKey, out Account? account) ?? false) + if (preBlockCache.TryGetValue(in addressAsKey, out Account? account)) { if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.AddressHit); baseScope.HintGet(address, account); @@ -137,7 +147,7 @@ public void UpdateRootHash() } else { - account = GetFromBaseTree(addressAsKey); + account = GetFromBaseTree(in addressAsKey); if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.AddressMiss); } return account; @@ -146,22 +156,36 @@ public void UpdateRootHash() public void HintGet(Address address, Account? account) => baseScope.HintGet(address, account); - private Account? GetFromBaseTree(AddressAsKey address) + private Account? GetFromBaseTree(in AddressAsKey address) { return baseScope.Get(address); } } - private sealed class StorageTreeWrapper( - IWorldStateScopeProvider.IStorageTree baseStorageTree, - ConcurrentDictionary preBlockCache, - Address address, - bool populatePreBlockCache - ) : IWorldStateScopeProvider.IStorageTree + private sealed class StorageTreeWrapper : IWorldStateScopeProvider.IStorageTree { + private readonly IWorldStateScopeProvider.IStorageTree baseStorageTree; + private readonly SeqlockCache preBlockCache; + private readonly Address address; + private readonly bool populatePreBlockCache; + private readonly SeqlockCache.ValueFactory _loadFromTreeStorage; private readonly IMetricObserver _metricObserver = Db.Metrics.PrewarmerGetTime; private readonly bool _measureMetric = Db.Metrics.DetailedMetricsEnabled; - private readonly PrewarmerGetTimeLabels _labels = populatePreBlockCache ? PrewarmerGetTimeLabels.Prewarmer : PrewarmerGetTimeLabels.NonPrewarmer; + private readonly PrewarmerGetTimeLabels _labels; + + public StorageTreeWrapper( + IWorldStateScopeProvider.IStorageTree baseStorageTree, + SeqlockCache preBlockCache, + Address address, + bool populatePreBlockCache) + { + this.baseStorageTree = baseStorageTree; + this.preBlockCache = preBlockCache; + this.address = address; + this.populatePreBlockCache = populatePreBlockCache; + _labels = populatePreBlockCache ? PrewarmerGetTimeLabels.Prewarmer : PrewarmerGetTimeLabels.NonPrewarmer; + _loadFromTreeStorage = LoadFromTreeStorage; + } public Hash256 RootHash => baseStorageTree.RootHash; @@ -173,7 +197,7 @@ public byte[] Get(in UInt256 index) { long priorReads = Db.Metrics.ThreadLocalStorageTreeReads; - byte[] value = preBlockCache.GetOrAdd(storageCell, LoadFromTreeStorage); + byte[] value = preBlockCache.GetOrAdd(in storageCell, _loadFromTreeStorage); if (Db.Metrics.ThreadLocalStorageTreeReads == priorReads) { @@ -189,15 +213,15 @@ public byte[] Get(in UInt256 index) } else { - if (preBlockCache?.TryGetValue(storageCell, out byte[] value) ?? false) + if (preBlockCache.TryGetValue(in storageCell, out byte[] value)) { if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.SlotGetHit); - baseStorageTree.HintGet(index, value); + baseStorageTree.HintGet(in index, value); Db.Metrics.IncrementStorageTreeCache(); } else { - value = LoadFromTreeStorage(storageCell); + value = LoadFromTreeStorage(in storageCell); if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.SlotGetMiss); } return value; @@ -206,7 +230,7 @@ public byte[] Get(in UInt256 index) public void HintGet(in UInt256 index, byte[]? value) => baseStorageTree.HintGet(in index, value); - private byte[] LoadFromTreeStorage(StorageCell storageCell) + private byte[] LoadFromTreeStorage(in StorageCell storageCell) { Db.Metrics.IncrementStorageTreeReads(); diff --git a/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs b/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs index e54f7fb46bbb..ce45389b1fc3 100644 --- a/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs +++ b/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs @@ -17,12 +17,13 @@ namespace Nethermind.State.Trie; /// The type of the elements in the collection used to build the trie. public abstract class PatriciaTrie : PatriciaTree { + protected const int MinItemsForParallelRootHash = 64; /// The collection to build the trie of. /// /// true to maintain an in-memory database for proof computation; /// otherwise, false. /// - protected PatriciaTrie(ReadOnlySpan list, bool canBuildProof, ICappedArrayPool? bufferPool = null) + protected PatriciaTrie(ReadOnlySpan list, bool canBuildProof, ICappedArrayPool? bufferPool = null, bool canBeParallel = true) : base(canBuildProof ? new MemDb() : NullDb.Instance, EmptyTreeHash, false, NullLogManager.Instance, bufferPool: bufferPool) { CanBuildProof = canBuildProof; @@ -31,7 +32,8 @@ protected PatriciaTrie(ReadOnlySpan list, bool canBuildProof, ICappedArrayPoo { // ReSharper disable once VirtualMemberCallInConstructor Initialize(list); - UpdateRootHash(); + // Parallel root hashing adds scheduling overhead for small tries. + UpdateRootHash(canBeParallel); } } diff --git a/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs b/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs index a84177868b06..f3391d31649b 100644 --- a/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs @@ -20,8 +20,8 @@ public sealed class ReceiptTrie : PatriciaTrie private readonly IRlpStreamDecoder _decoder; /// /// The transaction receipts to build the trie of. - public ReceiptTrie(IReceiptSpec spec, ReadOnlySpan receipts, IRlpStreamDecoder trieDecoder, ICappedArrayPool bufferPool, bool canBuildProof = false) - : base(null, canBuildProof, bufferPool: bufferPool) + public ReceiptTrie(IReceiptSpec spec, ReadOnlySpan receipts, IRlpStreamDecoder trieDecoder, ICappedArrayPool bufferPool, bool canBuildProof = false, bool canBeParallel = true) + : base(null, canBuildProof, bufferPool: bufferPool, canBeParallel) { ArgumentNullException.ThrowIfNull(spec); ArgumentNullException.ThrowIfNull(trieDecoder); @@ -30,7 +30,7 @@ public ReceiptTrie(IReceiptSpec spec, ReadOnlySpan receipts, IRlpStre if (receipts.Length > 0) { Initialize(receipts, spec); - UpdateRootHash(); + UpdateRootHash(canBeParallel); } } @@ -53,14 +53,16 @@ private void Initialize(ReadOnlySpan receipts, IReceiptSpec spec) public static byte[][] CalculateReceiptProofs(IReleaseSpec spec, ReadOnlySpan receipts, int index, IRlpStreamDecoder decoder) { - using TrackingCappedArrayPool cappedArrayPool = new(receipts.Length * 4); - return new ReceiptTrie(spec, receipts, decoder, cappedArrayPool, canBuildProof: true).BuildProof(index); + bool canBeParallel = receipts.Length > MinItemsForParallelRootHash; + using TrackingCappedArrayPool cappedArrayPool = new(receipts.Length * 4, canBeParallel: canBeParallel); + return new ReceiptTrie(spec, receipts, decoder, cappedArrayPool, canBuildProof: true, canBeParallel: canBeParallel).BuildProof(index); } public static Hash256 CalculateRoot(IReceiptSpec receiptSpec, ReadOnlySpan txReceipts, IRlpStreamDecoder decoder) { - using TrackingCappedArrayPool cappedArrayPool = new(txReceipts.Length * 4); - Hash256 receiptsRoot = new ReceiptTrie(receiptSpec, txReceipts, decoder, bufferPool: cappedArrayPool).RootHash; + bool canBeParallel = txReceipts.Length > MinItemsForParallelRootHash; + using TrackingCappedArrayPool cappedArrayPool = new(txReceipts.Length * 4, canBeParallel: canBeParallel); + Hash256 receiptsRoot = new ReceiptTrie(receiptSpec, txReceipts, decoder, bufferPool: cappedArrayPool, canBeParallel: canBeParallel).RootHash; return receiptsRoot; } } diff --git a/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs b/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs index 7e5c6b5415a7..49aeaac04930 100644 --- a/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs @@ -22,8 +22,8 @@ public sealed class TxTrie : PatriciaTrie /// /// The transactions to build the trie of. - public TxTrie(ReadOnlySpan transactions, bool canBuildProof = false, ICappedArrayPool? bufferPool = null) - : base(transactions, canBuildProof, bufferPool: bufferPool) { } + public TxTrie(ReadOnlySpan transactions, bool canBuildProof = false, ICappedArrayPool? bufferPool = null, bool canBeParallel = true) + : base(transactions, canBuildProof, bufferPool: bufferPool, canBeParallel: canBeParallel) { } protected override void Initialize(ReadOnlySpan list) { @@ -57,20 +57,41 @@ static SpanSource CopyExistingRlp(ReadOnlySpan rlp, ICappedArrayPool? buff } } + private void InitializeFromEncodedTransactions(ReadOnlySpan list) + { + for (int key = 0; key < list.Length; key++) + { + SpanSource keyBuffer = key.EncodeToSpanSource(_bufferPool); + Set(keyBuffer.Span, list[key]); + } + } + [DoesNotReturn, StackTraceHidden] private static void ThrowSpanSourceNotCappedArray() => throw new InvalidOperationException("Encode to SpanSource failed to get a CappedArray."); public static byte[][] CalculateProof(ReadOnlySpan transactions, int index) { - using TrackingCappedArrayPool cappedArray = new(transactions.Length * 4); - byte[][] rootHash = new TxTrie(transactions, canBuildProof: true, bufferPool: cappedArray).BuildProof(index); + bool canBeParallel = transactions.Length > MinItemsForParallelRootHash; + using TrackingCappedArrayPool cappedArray = new(transactions.Length * 4, canBeParallel: canBeParallel); + byte[][] rootHash = new TxTrie(transactions, canBuildProof: true, bufferPool: cappedArray, canBeParallel: canBeParallel).BuildProof(index); return rootHash; } public static Hash256 CalculateRoot(ReadOnlySpan transactions) { - using TrackingCappedArrayPool cappedArray = new(transactions.Length * 4); - Hash256 rootHash = new TxTrie(transactions, canBuildProof: false, bufferPool: cappedArray).RootHash; + bool canBeParallel = transactions.Length > MinItemsForParallelRootHash; + using TrackingCappedArrayPool cappedArray = new(transactions.Length * 4, canBeParallel: canBeParallel); + Hash256 rootHash = new TxTrie(transactions, canBuildProof: false, bufferPool: cappedArray, canBeParallel: canBeParallel).RootHash; return rootHash; } + + public static Hash256 CalculateRoot(ReadOnlySpan encodedTransactions) + { + bool canBeParallel = encodedTransactions.Length > MinItemsForParallelRootHash; + using TrackingCappedArrayPool cappedArray = new(encodedTransactions.Length * 4, canBeParallel: canBeParallel); + TxTrie txTrie = new(ReadOnlySpan.Empty, canBuildProof: false, bufferPool: cappedArray, canBeParallel: canBeParallel); + txTrie.InitializeFromEncodedTransactions(encodedTransactions); + txTrie.UpdateRootHash(canBeParallel); + return txTrie.RootHash; + } } diff --git a/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs b/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs index 10efa64e86a4..d63dc61471b0 100644 --- a/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs @@ -19,7 +19,7 @@ public sealed class WithdrawalTrie : PatriciaTrie /// /// The withdrawals to build the trie of. public WithdrawalTrie(ReadOnlySpan withdrawals, bool canBuildProof = false) - : base(withdrawals, canBuildProof) { } + : base(withdrawals, canBuildProof, canBeParallel: false) { } public static Hash256? CalculateRoot(ReadOnlySpan withdrawals) => new WithdrawalTrie(withdrawals).RootHash; diff --git a/src/Nethermind/Nethermind.State/TrieStoreScopeProvider.cs b/src/Nethermind/Nethermind.State/TrieStoreScopeProvider.cs index e4f01d4008ed..88026fd8d282 100644 --- a/src/Nethermind/Nethermind.State/TrieStoreScopeProvider.cs +++ b/src/Nethermind/Nethermind.State/TrieStoreScopeProvider.cs @@ -5,7 +5,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -194,10 +193,14 @@ public void Dispose() while (_dirtyStorageTree.TryDequeue(out (AddressAsKey, Hash256) entry)) { (AddressAsKey key, Hash256 storageRoot) = entry; - if (!_dirtyAccounts.TryGetValue(key, out var account)) account = scope.Get(key); - if (account == null && storageRoot == Keccak.EmptyTreeHash) continue; - account ??= ThrowNullAccount(key); - account = account!.WithChangedStorageRoot(storageRoot); + if (!_dirtyAccounts.TryGetValue(key, out var account)) + account = scope.Get(key); + + // Account may be null when EIP-161 deletes an empty account that had storage + // changes in the same block. Skip the storage root update since the account + // will not exist in the state trie. + if (account is null) continue; + account = account.WithChangedStorageRoot(storageRoot); _dirtyAccounts[key] = account; OnAccountUpdated?.Invoke(key, new IWorldStateScopeProvider.AccountUpdated(key, account)); if (logger.IsTrace) Trace(key, storageRoot, account); @@ -217,10 +220,6 @@ public void Dispose() [MethodImpl(MethodImplOptions.NoInlining)] void Trace(Address address, Hash256 storageRoot, Account? account) => logger.Trace($"Update {address} S {account?.StorageRoot} -> {storageRoot}"); - - [DoesNotReturn, StackTraceHidden] - static Account ThrowNullAccount(Address address) - => throw new InvalidOperationException($"Account {address} is null when updating storage hash"); } } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs index d6d7f65685d3..92ffa310957e 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs @@ -19,6 +19,7 @@ namespace Nethermind.Synchronization.Test.FastSync; [Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class StateSyncFeedHealingTests : StateSyncFeedTestsBase { [Test] diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs index af08c2f41a61..ffc7eb77719a 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs @@ -6,7 +6,6 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; -using Nethermind.Logging; using Nethermind.State; using Nethermind.Synchronization.FastBlocks; using Nethermind.Synchronization.ParallelSync; @@ -31,7 +30,7 @@ public void Header_block_is_0_when_no_header_was_suggested() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); blockTree.BestSuggestedHeader.ReturnsNull(); Assert.That(syncProgressResolver.FindBestHeader(), Is.EqualTo(0)); } @@ -46,7 +45,7 @@ public void Best_block_is_0_when_no_block_was_suggested() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); blockTree.BestSuggestedBody.ReturnsNull(); Assert.That(syncProgressResolver.FindBestFullBlock(), Is.EqualTo(0)); } @@ -61,7 +60,7 @@ public void Best_state_is_head_when_there_are_no_suggested_blocks() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); Block head = Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(5).WithStateRoot(TestItem.KeccakA).TestObject).TestObject; blockTree.Head.Returns(head); blockTree.BestSuggestedHeader.Returns(head.Header); @@ -79,7 +78,7 @@ public void Best_state_is_suggested_if_there_is_suggested_block_with_state() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); Block head = Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(5).WithStateRoot(TestItem.KeccakA).TestObject).TestObject; BlockHeader suggested = Build.A.BlockHeader.WithNumber(6).WithStateRoot(TestItem.KeccakB).TestObject; blockTree.Head.Returns(head); @@ -101,7 +100,7 @@ public void Best_state_is_head_if_there_is_suggested_block_without_state() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); Block head = Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(5).WithStateRoot(TestItem.KeccakA).TestObject).TestObject; BlockHeader suggested = Build.A.BlockHeader.WithNumber(6).WithStateRoot(TestItem.KeccakB).TestObject; blockTree.Head.Returns(head); @@ -123,7 +122,7 @@ public void Is_fast_block_finished_returns_true_when_no_fast_sync_is_used() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); Assert.That(syncProgressResolver.IsFastBlocksHeadersFinished(), Is.True); Assert.That(syncProgressResolver.IsFastBlocksBodiesFinished(), Is.True); Assert.That(syncProgressResolver.IsFastBlocksReceiptsFinished(), Is.True); @@ -145,7 +144,7 @@ public void Is_fast_block_bodies_finished_returns_false_when_blocks_not_download blockTree.LowestInsertedHeader.Returns(Build.A.BlockHeader.WithNumber(1).WithStateRoot(TestItem.KeccakA).TestObject); - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); Assert.That(syncProgressResolver.IsFastBlocksBodiesFinished(), Is.False); } @@ -164,12 +163,12 @@ public void Is_fast_block_receipts_finished_returns_true_when_receipts_not_downl blockTree.LowestInsertedHeader.Returns(Build.A.BlockHeader.WithNumber(1).WithStateRoot(TestItem.KeccakA).TestObject); - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, true, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, true, syncConfig); Assert.That(syncProgressResolver.IsFastBlocksReceiptsFinished(), Is.True); } - private SyncProgressResolver CreateProgressResolver(IBlockTree blockTree, IStateReader stateReader, bool isReceiptFinished, SyncConfig syncConfig, LimboLogs limboLogs) + private SyncProgressResolver CreateProgressResolver(IBlockTree blockTree, IStateReader stateReader, bool isReceiptFinished, SyncConfig syncConfig) { ISyncFeed receiptFeed = Substitute.For>(); receiptFeed.IsFinished.Returns(isReceiptFinished); @@ -181,8 +180,7 @@ private SyncProgressResolver CreateProgressResolver(IBlockTree blockTree, IState Substitute.For>(), Substitute.For>(), receiptFeed, - Substitute.For>(), - limboLogs + Substitute.For>() ); } } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs index 4b2d33f62f8a..e65fa535eff9 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs @@ -666,7 +666,7 @@ public async Task Broadcast_NewBlock_on_arrival_to_sqrt_of_peers([Values(1, 2, 3 int count = 0; remoteServer .When(r => r.AddNewBlock(Arg.Is(b => b.Hash == remoteBlockTree.Head!.Hash), Arg.Any())) - .Do(_ => count++); + .Do(_ => Interlocked.Increment(ref count)); PeerInfo[] peers = Enumerable.Range(0, peerCount).Take(peerCount) .Select(_ => new PeerInfo(new SyncPeerMock(remoteBlockTree, remoteSyncServer: remoteServer))) .ToArray(); diff --git a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs index 0f2c9cd0b1a2..1eaf7d0d9cd1 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs @@ -8,7 +8,6 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Int256; -using Nethermind.Logging; using Nethermind.Synchronization.FastBlocks; using Nethermind.Synchronization.SnapSync; @@ -21,9 +20,6 @@ public class SyncProgressResolver : ISyncProgressResolver private readonly ISyncConfig _syncConfig; private readonly IFullStateFinder _fullStateFinder; - // ReSharper disable once NotAccessedField.Local - private readonly ILogger _logger; - private readonly ISyncFeed _headersSyncFeed; private readonly ISyncFeed _bodiesSyncFeed; private readonly ISyncFeed _receiptsSyncFeed; @@ -36,10 +32,8 @@ public SyncProgressResolver( [KeyFilter(nameof(HeadersSyncFeed))] ISyncFeed headersSyncFeed, ISyncFeed bodiesSyncFeed, ISyncFeed receiptsSyncFeed, - ISyncFeed snapSyncFeed, - ILogManager logManager) + ISyncFeed snapSyncFeed) { - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); _fullStateFinder = fullStateFinder ?? throw new ArgumentNullException(nameof(fullStateFinder)); _syncConfig = syncConfig ?? throw new ArgumentNullException(nameof(syncConfig)); diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/ClientTypeStrategy.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/ClientTypeStrategy.cs deleted file mode 100644 index 142c2ff819c8..000000000000 --- a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/ClientTypeStrategy.cs +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using System.Linq; -using Nethermind.Blockchain; -using Nethermind.Stats; -using Nethermind.Stats.Model; - -namespace Nethermind.Synchronization.Peers.AllocationStrategies; - -public class ClientTypeStrategy : IPeerAllocationStrategy -{ - private readonly IPeerAllocationStrategy _strategy; - private readonly bool _allowOtherIfNone; - private readonly HashSet _supportedClientTypes; - - public ClientTypeStrategy(IPeerAllocationStrategy strategy, bool allowOtherIfNone, params NodeClientType[] supportedClientTypes) - : this(strategy, allowOtherIfNone, (IEnumerable)supportedClientTypes) - { - } - - public ClientTypeStrategy(IPeerAllocationStrategy strategy, bool allowOtherIfNone, IEnumerable supportedClientTypes) - { - _strategy = strategy; - _allowOtherIfNone = allowOtherIfNone; - _supportedClientTypes = new HashSet(supportedClientTypes); - } - - public PeerInfo? Allocate(PeerInfo? currentPeer, IEnumerable peers, INodeStatsManager nodeStatsManager, IBlockTree blockTree) - { - IEnumerable originalPeers = peers; - peers = peers.Where(p => _supportedClientTypes.Contains(p.PeerClientType)); - - if (_allowOtherIfNone) - { - if (!peers.Any()) - { - peers = originalPeers; - } - } - return _strategy.Allocate(currentPeer, peers, nodeStatsManager, blockTree); - } -} diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/NullStrategy.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/NullStrategy.cs deleted file mode 100644 index fcf28b1a4a45..000000000000 --- a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/NullStrategy.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Nethermind.Blockchain; -using Nethermind.Stats; - -namespace Nethermind.Synchronization.Peers.AllocationStrategies -{ - /// - /// used only for failed allocations - /// - public class NullStrategy : IPeerAllocationStrategy - { - private NullStrategy() - { - } - - public static IPeerAllocationStrategy Instance { get; } = new NullStrategy(); - - public PeerInfo? Allocate(PeerInfo? currentPeer, IEnumerable peers, INodeStatsManager nodeStatsManager, IBlockTree blockTree) - { - return null; - } - } -} diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StaticStrategy.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StaticStrategy.cs deleted file mode 100644 index 7d9a5b4de624..000000000000 --- a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StaticStrategy.cs +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Nethermind.Blockchain; -using Nethermind.Stats; - -namespace Nethermind.Synchronization.Peers.AllocationStrategies -{ - public class StaticStrategy : IPeerAllocationStrategy - { - private readonly PeerInfo _peerInfo; - - public StaticStrategy(PeerInfo peerInfo) - { - _peerInfo = peerInfo; - } - - public PeerInfo Allocate(PeerInfo? currentPeer, IEnumerable peers, INodeStatsManager nodeStatsManager, IBlockTree blockTree) - { - return _peerInfo; - } - } -} diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StrategySelectionType.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StrategySelectionType.cs deleted file mode 100644 index c20046181c27..000000000000 --- a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StrategySelectionType.cs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Synchronization.Peers.AllocationStrategies; - -public enum StrategySelectionType -{ - Better = 1, - AtLeastTheSame = 0, - CanBeSlightlyWorse = -1 -} diff --git a/src/Nethermind/Nethermind.Synchronization/SyncPeerEventArgs.cs b/src/Nethermind/Nethermind.Synchronization/SyncPeerEventArgs.cs deleted file mode 100644 index 3d5fbf168612..000000000000 --- a/src/Nethermind/Nethermind.Synchronization/SyncPeerEventArgs.cs +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Synchronization.Peers; - -namespace Nethermind.Synchronization -{ - public class AllocationChangeEventArgs - { - public AllocationChangeEventArgs(PeerInfo? previous, PeerInfo? current) - { - Previous = previous; - Current = current; - } - - public PeerInfo? Previous { get; } - - public PeerInfo? Current { get; } - } -} diff --git a/src/Nethermind/Nethermind.Taiko/Tdx/TdxsClient.cs b/src/Nethermind/Nethermind.Taiko/Tdx/TdxsClient.cs index 081e33262199..999fabaa0f41 100644 --- a/src/Nethermind/Nethermind.Taiko/Tdx/TdxsClient.cs +++ b/src/Nethermind/Nethermind.Taiko/Tdx/TdxsClient.cs @@ -6,7 +6,6 @@ using System.Net.Sockets; using System.Text; using System.Text.Json; -using Nethermind.Logging; namespace Nethermind.Taiko.Tdx; @@ -14,9 +13,8 @@ namespace Nethermind.Taiko.Tdx; /// Client for communicating with the tdxs daemon via Unix socket. /// Protocol: JSON request/response over Unix socket. /// -public class TdxsClient(ISurgeTdxConfig config, ILogManager logManager) : ITdxsClient +public class TdxsClient(ISurgeTdxConfig config) : ITdxsClient { - private readonly ILogger _logger = logManager.GetClassLogger(); public byte[] Issue(byte[] userData, byte[] nonce) { diff --git a/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs b/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs index f332f113489c..bc89ff38ecff 100644 --- a/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs +++ b/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs @@ -10,6 +10,7 @@ using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; +using Nethermind.Evm.CodeAnalysis; namespace Nethermind.Test.Runner; @@ -54,7 +55,7 @@ public void StartOperation(int pc, Instruction opcode, long gas, in ExecutionEnv { _gasAlreadySetForCurrentOp = false; _traceEntry = new StateTestTxTraceEntry(); - _traceEntry.Pc = pc + env.CodeInfo.PcOffset(); + _traceEntry.Pc = pc + (env.CodeInfo is EofCodeInfo eofCodeInfo ? eofCodeInfo.PcOffset() : 0); _traceEntry.Section = codeSection; _traceEntry.Operation = (byte)opcode; _traceEntry.OperationName = opcode.GetName(); diff --git a/src/Nethermind/Nethermind.Trie.Test/CacheTests.cs b/src/Nethermind/Nethermind.Trie.Test/CacheTests.cs index 8891482ff7f4..02f361be5b42 100644 --- a/src/Nethermind/Nethermind.Trie.Test/CacheTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/CacheTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Trie.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] public class CacheTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs b/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs index bf35cbb1232d..c8a8bc507a07 100644 --- a/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs @@ -7,6 +7,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class HexPrefixTests { [TestCase(false, (byte)3, (byte)19)] diff --git a/src/Nethermind/Nethermind.Trie.Test/NibbleTests.cs b/src/Nethermind/Nethermind.Trie.Test/NibbleTests.cs index 1338eafa0183..a575cb2ddbbd 100644 --- a/src/Nethermind/Nethermind.Trie.Test/NibbleTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/NibbleTests.cs @@ -6,6 +6,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class NibbleTests { private readonly byte[][] _hexEncoding = diff --git a/src/Nethermind/Nethermind.Trie.Test/NodeStorageFactoryTests.cs b/src/Nethermind/Nethermind.Trie.Test/NodeStorageFactoryTests.cs index 5e0308ea2a5c..740048565d78 100644 --- a/src/Nethermind/Nethermind.Trie.Test/NodeStorageFactoryTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/NodeStorageFactoryTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class NodeStorageFactoryTests { [TestCase(INodeStorage.KeyScheme.Hash)] diff --git a/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs b/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs index 51702876eec0..c212d15a8e8e 100644 --- a/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs @@ -13,31 +13,25 @@ namespace Nethermind.Trie.Test; [TestFixture(INodeStorage.KeyScheme.Hash)] [TestFixture(INodeStorage.KeyScheme.HalfPath)] -public class NodeStorageTests +[Parallelizable(ParallelScope.All)] +public class NodeStorageTests(INodeStorage.KeyScheme currentKeyScheme) { - private readonly INodeStorage.KeyScheme _currentKeyScheme; - - public NodeStorageTests(INodeStorage.KeyScheme currentKeyScheme) - { - _currentKeyScheme = currentKeyScheme; - } - [Test] public void Should_StoreAndRead() { TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); nodeStorage.KeyExists(null, TreePath.Empty, TestItem.KeccakA).Should().BeFalse(); nodeStorage.Set(null, TreePath.Empty, TestItem.KeccakA, TestItem.KeccakA.BytesToArray()); nodeStorage.Get(null, TreePath.Empty, TestItem.KeccakA).Should().BeEquivalentTo(TestItem.KeccakA.BytesToArray()); nodeStorage.KeyExists(null, TreePath.Empty, TestItem.KeccakA).Should().BeTrue(); - if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) + if (currentKeyScheme == INodeStorage.KeyScheme.Hash) { testDb[TestItem.KeccakA.Bytes].Should().NotBeNull(); } - else if (_currentKeyScheme == INodeStorage.KeyScheme.HalfPath) + else if (currentKeyScheme == INodeStorage.KeyScheme.HalfPath) { testDb[NodeStorage.GetHalfPathNodeStoragePath(null, TreePath.Empty, TestItem.KeccakA)].Should().NotBeNull(); } @@ -47,18 +41,18 @@ public void Should_StoreAndRead() public void Should_StoreAndRead_WithStorage() { TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); nodeStorage.KeyExists(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA).Should().BeFalse(); nodeStorage.Set(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA, TestItem.KeccakA.BytesToArray()); nodeStorage.Get(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA).Should().BeEquivalentTo(TestItem.KeccakA.BytesToArray()); nodeStorage.KeyExists(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA).Should().BeTrue(); - if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) + if (currentKeyScheme == INodeStorage.KeyScheme.Hash) { testDb[TestItem.KeccakA.Bytes].Should().NotBeNull(); } - else if (_currentKeyScheme == INodeStorage.KeyScheme.HalfPath) + else if (currentKeyScheme == INodeStorage.KeyScheme.HalfPath) { testDb[NodeStorage.GetHalfPathNodeStoragePath(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA)].Should().NotBeNull(); } @@ -68,7 +62,7 @@ public void Should_StoreAndRead_WithStorage() public void When_KeyNotExist_Should_TryBothKeyType() { TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); nodeStorage.Get(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA).Should().BeNull(); @@ -80,9 +74,9 @@ public void When_KeyNotExist_Should_TryBothKeyType() public void When_EntryOfDifferentScheme_Should_StillBeAbleToRead() { TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); - if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) + if (currentKeyScheme == INodeStorage.KeyScheme.Hash) { testDb[NodeStorage.GetHalfPathNodeStoragePath(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA)] = TestItem.KeccakA.BytesToArray(); @@ -109,7 +103,7 @@ public void When_EntryOfDifferentScheme_Should_StillBeAbleToRead() [TestCase(true, 32, "0211111111111111111111111111111111111111111111111111111111111111112222222222222222203333333333333333333333333333333333333333333333333333333333333333")] public void Test_HalfPathEncoding(bool hasAddress, int pathLength, string expectedKey) { - if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) return; + if (currentKeyScheme == INodeStorage.KeyScheme.Hash) return; Hash256? address = null; if (hasAddress) @@ -130,10 +124,10 @@ public void Test_HalfPathEncoding(bool hasAddress, int pathLength, string expect [TestCase(true, 3, ReadFlags.HintReadAhead | ReadFlags.HintReadAhead3)] public void Test_WhenReadaheadUseDifferentReadaheadOnDifferentSection(bool hasAddress, int pathLength, ReadFlags expectedReadFlags) { - if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) return; + if (currentKeyScheme == INodeStorage.KeyScheme.Hash) return; TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); Hash256? address = null; if (hasAddress) diff --git a/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs index ed8c23ee2640..2c70a4944949 100644 --- a/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs @@ -15,6 +15,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class OverlayTrieStoreTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/Pruning/MaxBlockInCachePruneStrategyTests.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/MaxBlockInCachePruneStrategyTests.cs index f288a30f6e0e..93c61e44ed48 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/MaxBlockInCachePruneStrategyTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/MaxBlockInCachePruneStrategyTests.cs @@ -8,7 +8,8 @@ namespace Nethermind.Trie.Test.Pruning { [TestFixture] - [Parallelizable(ParallelScope.Self)] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class MaxBlockInCachePruneStrategyTests { private IPruningStrategy _baseStrategy; diff --git a/src/Nethermind/Nethermind.Trie.Test/Pruning/MinBlockInCachePruneStrategyTests.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/MinBlockInCachePruneStrategyTests.cs index 27814dd58fce..63930144e889 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/MinBlockInCachePruneStrategyTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/MinBlockInCachePruneStrategyTests.cs @@ -9,7 +9,8 @@ namespace Nethermind.Trie.Test.Pruning { [TestFixture] - [Parallelizable(ParallelScope.Self)] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class MinBlockInCachePruneStrategyTests { private IPruningStrategy _baseStrategy; diff --git a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs index 2a98ac0b6aa9..b75849af8999 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs @@ -28,18 +28,13 @@ namespace Nethermind.Trie.Test.Pruning { [TestFixture(INodeStorage.KeyScheme.HalfPath)] [TestFixture(INodeStorage.KeyScheme.Hash)] - public class TreeStoreTests + [Parallelizable(ParallelScope.All)] + public class TreeStoreTests(INodeStorage.KeyScheme scheme) { private readonly ILogManager _logManager = LimboLogs.Instance; // new OneLoggerLogManager(new NUnitLogger(LogLevel.Trace)); private readonly AccountDecoder _accountDecoder = new(); - private readonly INodeStorage.KeyScheme _scheme; - - public TreeStoreTests(INodeStorage.KeyScheme scheme) - { - _scheme = scheme; - } private TrieStore CreateTrieStore( IPruningStrategy? pruningStrategy = null, @@ -59,7 +54,7 @@ private TrieStore CreateTrieStore( finalizedStateProvider ??= new TestFinalizedStateProvider(pruningConfig.PruningBoundary); TrieStore trieStore = new( - new NodeStorage(kvStore, _scheme, requirePath: _scheme == INodeStorage.KeyScheme.HalfPath), + new NodeStorage(kvStore, scheme, requirePath: scheme == INodeStorage.KeyScheme.HalfPath), pruningStrategy, persistenceStrategy, finalizedStateProvider, @@ -148,7 +143,7 @@ public void Pruning_off_cache_should_change_commit_node() committer.CommitNode(ref emptyPath, trieNode3); } fullTrieStore.WaitForPruning(); - fullTrieStore.MemoryUsedByDirtyCache.Should().Be(_scheme == INodeStorage.KeyScheme.HalfPath ? 832 : 676); + fullTrieStore.MemoryUsedByDirtyCache.Should().Be(scheme == INodeStorage.KeyScheme.HalfPath ? 832 : 676); } [Test] @@ -177,7 +172,7 @@ public void Pruning_off_cache_should_find_cached_or_unknown() Assert.That(returnedNode2.NodeType, Is.EqualTo(NodeType.Unknown)); Assert.That(returnedNode3.NodeType, Is.EqualTo(NodeType.Unknown)); trieStore.WaitForPruning(); - trieStore.MemoryUsedByDirtyCache.Should().Be(_scheme == INodeStorage.KeyScheme.HalfPath ? 552 : 396); + trieStore.MemoryUsedByDirtyCache.Should().Be(scheme == INodeStorage.KeyScheme.HalfPath ? 552 : 396); } [Test] @@ -242,7 +237,7 @@ public void Memory_with_concurrent_commits_is_correct() tree.Commit(); } - fullTrieStore.MemoryUsedByDirtyCache.Should().Be(_scheme == INodeStorage.KeyScheme.Hash ? 545956 : 616104L); + fullTrieStore.MemoryUsedByDirtyCache.Should().Be(scheme == INodeStorage.KeyScheme.Hash ? 545956 : 616104L); fullTrieStore.CommittedNodesCount.Should().Be(1349); } @@ -871,7 +866,7 @@ public void ReadOnly_store_returns_copies(bool pruning) readOnlyNode.Key?.ToString().Should().Be(originalNode.Key?.ToString()); } - private long ExpectedPerNodeKeyMemorySize => (_scheme == INodeStorage.KeyScheme.Hash ? 0 : TrieStoreDirtyNodesCache.Key.MemoryUsage) + MemorySizes.ObjectHeaderMethodTable + MemorySizes.RefSize + 4 + MemorySizes.RefSize; + private long ExpectedPerNodeKeyMemorySize => (scheme == INodeStorage.KeyScheme.Hash ? 0 : TrieStoreDirtyNodesCache.Key.MemoryUsage) + MemorySizes.ObjectHeaderMethodTable + MemorySizes.RefSize + 4 + MemorySizes.RefSize; [Test] public void After_commit_should_have_has_root() @@ -902,6 +897,7 @@ public void After_commit_should_have_has_root() [Test] [Retry(3)] + [NonParallelizable] public async Task Will_RemovePastKeys_OnSnapshot() { MemDb memDb = new(); @@ -930,7 +926,7 @@ public async Task Will_RemovePastKeys_OnSnapshot() await Task.Delay(TimeSpan.FromMilliseconds(10)); } - if (_scheme == INodeStorage.KeyScheme.Hash) + if (scheme == INodeStorage.KeyScheme.Hash) { memDb.Count.Should().NotBe(1); } @@ -941,6 +937,8 @@ public async Task Will_RemovePastKeys_OnSnapshot() } [Test] + [Retry(3)] + [NonParallelizable] public async Task Will_Trigger_ReorgBoundaryEvent_On_Prune() { // TODO: Check why slow @@ -1021,7 +1019,7 @@ public void When_SomeKindOfNonResolvedNotInMainWorldState_OnPrune_DoNotDeleteNod Address address = TestItem.AddressA; UInt256 slot = 1; - INodeStorage nodeStorage = new NodeStorage(memDbProvider.StateDb, _scheme); + INodeStorage nodeStorage = new NodeStorage(memDbProvider.StateDb, scheme); (Hash256 stateRoot, ValueHash256 storageRoot) = SetupStartingState(); nodeStorage.Get(address.ToAccountPath.ToCommitment(), TreePath.Empty, storageRoot).Should().NotBeNull(); @@ -1104,7 +1102,7 @@ public Task When_Prune_ClearRecommittedPersistedNode() } memDb.Count.Should().Be(1); - fullTrieStore.MemoryUsedByDirtyCache.Should().Be(_scheme == INodeStorage.KeyScheme.Hash ? 12032 : 15360); + fullTrieStore.MemoryUsedByDirtyCache.Should().Be(scheme == INodeStorage.KeyScheme.Hash ? 12032 : 15360); fullTrieStore.PersistCache(default); memDb.Count.Should().Be(64); @@ -1145,7 +1143,7 @@ public void OnDispose_PersistAtLeastOneCommitSet() [Test] public void Will_NotPruneTopLevelNode() { - if (_scheme == INodeStorage.KeyScheme.Hash) Assert.Ignore("Not applicable for hash"); + if (scheme == INodeStorage.KeyScheme.Hash) Assert.Ignore("Not applicable for hash"); MemDb memDb = new(); TestPruningStrategy testPruningStrategy = new TestPruningStrategy( @@ -1224,7 +1222,7 @@ void WriteRandomData(int seed) [Test] public void Can_Prune_StorageTreeRoot() { - if (_scheme == INodeStorage.KeyScheme.Hash) Assert.Ignore("Not applicable for hash"); + if (scheme == INodeStorage.KeyScheme.Hash) Assert.Ignore("Not applicable for hash"); MemDb memDb = new(); TestPruningStrategy testPruningStrategy = new TestPruningStrategy( @@ -1593,7 +1591,7 @@ void VerifyAllTrieExceptGenesis() long cachedPersistedNode = fullTrieStore.CachedNodesCount - fullTrieStore.DirtyCachedNodesCount; long perStatePersistedNode = 20; - if (_scheme == INodeStorage.KeyScheme.Hash) + if (scheme == INodeStorage.KeyScheme.Hash) { cachedPersistedNode.Should().Be(perStatePersistedNode + 3); } diff --git a/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs b/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs index 223282c66048..cd0cd0d3ee19 100644 --- a/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs @@ -27,6 +27,7 @@ namespace Nethermind.Trie.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] public class PruningScenariosTests { private ILogger _logger; @@ -1222,7 +1223,7 @@ public void Retain_Some_PersistedNodes() ctx .AssertThatDirtyNodeCountIs(9) - .AssertThatCachedNodeCountMoreThan(280); + .AssertThatCachedNodeCountMoreThan(275); } [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/RawTrieStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/RawTrieStoreTests.cs index 7663cff436f4..2535489626fb 100644 --- a/src/Nethermind/Nethermind.Trie.Test/RawTrieStoreTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/RawTrieStoreTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class RawTrieStoreTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TinyTreePathTests.cs b/src/Nethermind/Nethermind.Trie.Test/TinyTreePathTests.cs index a2d51d1a5a18..d6c93f2b3f96 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TinyTreePathTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TinyTreePathTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class TinyTreePathTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TrackingCappedArrayPoolTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrackingCappedArrayPoolTests.cs index 322804621d13..bb30481d6f14 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrackingCappedArrayPoolTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrackingCappedArrayPoolTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class TrackingCappedArrayPoolTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TreePathTests.cs b/src/Nethermind/Nethermind.Trie.Test/TreePathTests.cs index b398b4b3b65e..da83b8a3da8f 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TreePathTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TreePathTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class TreePathTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs index df8fcf6dac17..6d28229901d0 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class TrieNodeResolverWithReadFlagsTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs index cc8bbf893719..39acb58ec63e 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs @@ -27,6 +27,8 @@ namespace Nethermind.Trie.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class TrieTests { private ILogger _logger; @@ -1081,6 +1083,8 @@ public void Fuzz_accounts_with_reorganizations( } [TestCaseSource(nameof(FuzzAccountsWithStorageScenarios))] + [Retry(3)] + [NonParallelizable] public void Fuzz_accounts_with_storage( (TrieStoreConfigurations trieStoreConfigurations, int accountsCount, diff --git a/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs b/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs index 2fd6f53b2b60..7221240b00a8 100644 --- a/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs @@ -20,6 +20,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class VisitingTests { [TestCaseSource(nameof(GetOptions))] diff --git a/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs b/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs index 3b3e4b1b2a46..f22b68a1123b 100644 --- a/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class VisitorProgressTrackerTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs b/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs index 75236286b82d..28146edf7611 100644 --- a/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs +++ b/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs @@ -1,15 +1,13 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Concurrent; using Nethermind.Core.Collections; namespace Nethermind.Trie; public sealed class NodeStorageCache { - private ConcurrentDictionary _cache = new(); + private readonly SeqlockCache _cache = new(); private volatile bool _enabled = false; @@ -19,17 +17,18 @@ public bool Enabled set => _enabled = value; } - public byte[]? GetOrAdd(NodeKey nodeKey, Func tryLoadRlp) + public byte[]? GetOrAdd(in NodeKey nodeKey, SeqlockCache.ValueFactory tryLoadRlp) { if (!_enabled) { - return tryLoadRlp(nodeKey); + return tryLoadRlp(in nodeKey); } - return _cache.GetOrAdd(nodeKey, tryLoadRlp); + return _cache.GetOrAdd(in nodeKey, tryLoadRlp); } public bool ClearCaches() { - return _cache.NoResizeClear(); + _cache.Clear(); + return true; } } diff --git a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs index 08b96d207fc8..d42c85a23f6e 100644 --- a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs +++ b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -25,7 +24,6 @@ namespace Nethermind.Trie public partial class PatriciaTree { private const int MaxKeyStackAlloc = 64; - private readonly static byte[][] _singleByteKeys = [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15]]; private readonly ILogger _logger; @@ -1003,25 +1001,5 @@ bool TryGetRootRef(out TrieNode? rootRef) [DoesNotReturn, StackTraceHidden] static void ThrowReadOnlyTrieException() => throw new TrieException("Commits are not allowed on this trie."); - - [DoesNotReturn, StackTraceHidden] - private static void ThrowInvalidDataException(TrieNode originalNode) - { - throw new InvalidDataException( - $"Extension {originalNode.Keccak} has no child."); - } - - [DoesNotReturn, StackTraceHidden] - private static void ThrowMissingChildException(TrieNode node) - { - throw new TrieException( - $"Found an {nameof(NodeType.Extension)} {node.Keccak} that is missing a child."); - } - - [DoesNotReturn, StackTraceHidden] - private static void ThrowMissingPrefixException() - { - throw new InvalidDataException("An attempt to visit a node without a prefix path."); - } } } diff --git a/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs b/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs index dc6d9b6d1fa0..3a0d52617152 100644 --- a/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs @@ -3,8 +3,11 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using Nethermind.Trie.Pruning; namespace Nethermind.Trie; @@ -13,8 +16,8 @@ public sealed class PreCachedTrieStore : ITrieStore { private readonly ITrieStore _inner; private readonly NodeStorageCache _preBlockCache; - private readonly Func _loadRlp; - private readonly Func _tryLoadRlp; + private readonly SeqlockCache.ValueFactory _loadRlp; + private readonly SeqlockCache.ValueFactory _tryLoadRlp; public PreCachedTrieStore(ITrieStore inner, NodeStorageCache cache) { @@ -22,8 +25,8 @@ public PreCachedTrieStore(ITrieStore inner, NodeStorageCache cache) _preBlockCache = cache; // Capture the delegate once for default path to avoid the allocation of the lambda per call - _loadRlp = (NodeKey key) => _inner.LoadRlp(key.Address, in key.Path, key.Hash, flags: ReadFlags.None); - _tryLoadRlp = (NodeKey key) => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash, flags: ReadFlags.None); + _loadRlp = (in NodeKey key) => _inner.LoadRlp(key.Address, in key.Path, key.Hash, flags: ReadFlags.None); + _tryLoadRlp = (in NodeKey key) => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash, flags: ReadFlags.None); } public void Dispose() @@ -43,8 +46,7 @@ public IBlockCommitter BeginBlockCommit(long blockNumber) public bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 keccak) { - byte[]? rlp = _preBlockCache.GetOrAdd(new(address, in path, in keccak), - key => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash)); + byte[]? rlp = _preBlockCache.GetOrAdd(new(address, in path, in keccak), _tryLoadRlp); return rlp is not null; } @@ -60,17 +62,17 @@ public bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 kecc public byte[]? LoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => _preBlockCache.GetOrAdd(new(address, in path, hash), flags == ReadFlags.None ? _loadRlp : - key => _inner.LoadRlp(key.Address, in key.Path, key.Hash, flags)); + (in NodeKey key) => _inner.LoadRlp(key.Address, in key.Path, key.Hash, flags)); public byte[]? TryLoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => _preBlockCache.GetOrAdd(new(address, in path, hash), flags == ReadFlags.None ? _tryLoadRlp : - key => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash, flags)); + (in NodeKey key) => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash, flags)); public INodeStorage.KeyScheme Scheme => _inner.Scheme; } -public readonly struct NodeKey : IEquatable +public readonly struct NodeKey : IEquatable, IHash64bit { public readonly Hash256? Address; public readonly TreePath Path; @@ -90,15 +92,28 @@ public NodeKey(Hash256? address, in TreePath path, Hash256 hash) Hash = hash; } - public bool Equals(NodeKey other) => - Address == other.Address && Path.Equals(in other.Path) && Hash.Equals(other.Hash); + public bool Equals(NodeKey other) => Equals(in other); public override bool Equals(object? obj) => obj is NodeKey key && Equals(key); + public bool Equals(in NodeKey other) => + Address == other.Address && Path.Equals(in other.Path) && Hash.Equals(other.Hash); + public override int GetHashCode() { uint hashCode0 = (uint)Hash.GetHashCode(); ulong hashCode1 = ((ulong)(uint)Path.GetHashCode() << 32) | (uint)(Address?.GetHashCode() ?? 1); return (int)BitOperations.Crc32C(hashCode0, hashCode1); } + + public long GetHashCode64() + { + long hashCode0 = Address is null ? 1L : SpanExtensions.FastHash64For32Bytes(ref Unsafe.As(ref Unsafe.AsRef(in Address.ValueHash256))); + long hashCode1 = SpanExtensions.FastHash64For32Bytes(ref Unsafe.As(ref Unsafe.AsRef(in Hash.ValueHash256))); + long hashCode2 = SpanExtensions.FastHash64For32Bytes(ref Unsafe.As(ref Unsafe.AsRef(in Path.Path))); + + // Rotations spaced by 64/3 ensure way 0 (bits 0-13) and way 1 (bits 42-55) + // sample non-overlapping 14-bit windows from each input + return hashCode1 + (long)BitOperations.RotateLeft((ulong)hashCode0, 21) + (long)BitOperations.RotateLeft((ulong)hashCode2, 42); + } } diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs index a53f33528f54..abd7da715b18 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs @@ -1228,11 +1228,6 @@ public bool IsNoLongerNeeded(long lastCommit) && lastCommit < LatestCommittedBlockNumber - _maxDepth; } - private bool IsStillNeeded(long lastCommit) - { - return !IsNoLongerNeeded(lastCommit); - } - private void AnnounceReorgBoundaries() { if (LatestCommittedBlockNumber < 1) diff --git a/src/Nethermind/Nethermind.Trie/TrackingCappedArrayPool.cs b/src/Nethermind/Nethermind.Trie/TrackingCappedArrayPool.cs index 82c39b94b134..c0b679074f99 100644 --- a/src/Nethermind/Nethermind.Trie/TrackingCappedArrayPool.cs +++ b/src/Nethermind/Nethermind.Trie/TrackingCappedArrayPool.cs @@ -3,7 +3,9 @@ using System; using System.Buffers; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Nethermind.Core.Buffers; @@ -13,21 +15,16 @@ namespace Nethermind.Trie; /// /// Track every rented CappedArray and return them all at once /// -public sealed class TrackingCappedArrayPool : ICappedArrayPool, IDisposable +public sealed class TrackingCappedArrayPool(int initialCapacity, ArrayPool arrayPool = null, bool canBeParallel = true) : ICappedArrayPool, IDisposable { - private readonly List _rentedBuffers; - private readonly ArrayPool? _arrayPool; + private readonly ConcurrentQueue? _rentedQueue = canBeParallel ? new() : null; + private readonly List? _rentedList = canBeParallel ? null : new(initialCapacity); + private readonly ArrayPool? _arrayPool = arrayPool; public TrackingCappedArrayPool() : this(0) { } - public TrackingCappedArrayPool(int initialCapacity, ArrayPool arrayPool = null) - { - _rentedBuffers = new List(initialCapacity); - _arrayPool = arrayPool; - } - public CappedArray Rent(int size) { if (size == 0) @@ -37,9 +34,16 @@ public CappedArray Rent(int size) // Devirtualize shared array pool by referring directly to it byte[] array = _arrayPool?.Rent(size) ?? ArrayPool.Shared.Rent(size); - CappedArray rented = new CappedArray(array, size); + CappedArray rented = new(array, size); array.AsSpan().Clear(); - lock (_rentedBuffers) _rentedBuffers.Add(array); + if (_rentedQueue is not null) + { + _rentedQueue.Enqueue(array); + } + else + { + _rentedList.Add(array); + } return rented; } @@ -49,9 +53,16 @@ public void Return(in CappedArray buffer) public void Dispose() { - if (_arrayPool is null) + if (_arrayPool is not null) + { + DisposeCustomArrayPool(); + return; + } + + ConcurrentQueue? rentedQueue = _rentedQueue; + if (rentedQueue is not null) { - foreach (byte[] rentedBuffer in CollectionsMarshal.AsSpan(_rentedBuffers)) + while (rentedQueue.TryDequeue(out byte[]? rentedBuffer)) { // Devirtualize shared array pool by referring directly to it ArrayPool.Shared.Return(rentedBuffer); @@ -59,9 +70,32 @@ public void Dispose() } else { - foreach (byte[] rentedBuffer in CollectionsMarshal.AsSpan(_rentedBuffers)) + Span items = CollectionsMarshal.AsSpan(_rentedList); + foreach (byte[] rentedBuffer in items) + { + // Devirtualize shared array pool by referring directly to it + ArrayPool.Shared.Return(rentedBuffer); + } + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void DisposeCustomArrayPool() + { + ArrayPool arrayPool = _arrayPool; + if (_rentedQueue is not null) + { + while (_rentedQueue.TryDequeue(out byte[]? rentedBuffer)) + { + arrayPool.Return(rentedBuffer); + } + } + else + { + Span items = CollectionsMarshal.AsSpan(_rentedList); + foreach (byte[] rentedBuffer in items) { - _arrayPool.Return(rentedBuffer); + arrayPool.Return(rentedBuffer); } } } diff --git a/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs b/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs index 4b711e210f17..ef8b70015635 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs @@ -137,7 +137,7 @@ public static SpanSource RlpEncodeBranch(TrieNode item, ITrieNodeResolver tree, Metrics.TreeNodeRlpEncodings++; const int valueRlpLength = 1; - int contentLength = valueRlpLength + (UseParallel(canBeParallel) ? GetChildrenRlpLengthForBranchParallel(tree, ref path, item, pool) : GetChildrenRlpLengthForBranch(tree, ref path, item, pool)); + int contentLength = valueRlpLength + (UseParallel(canBeParallel, item) ? GetChildrenRlpLengthForBranchParallel(tree, ref path, item, pool) : GetChildrenRlpLengthForBranch(tree, ref path, item, pool)); int sequenceLength = Rlp.LengthOfSequence(contentLength); SpanSource result = pool.SafeRentBuffer(sequenceLength); Span resultSpan = result.Span; @@ -148,7 +148,26 @@ public static SpanSource RlpEncodeBranch(TrieNode item, ITrieNodeResolver tree, return result; - static bool UseParallel(bool canBeParallel) => Environment.ProcessorCount > 1 && canBeParallel; + static bool UseParallel(bool canBeParallel, TrieNode item) + { + if (Environment.ProcessorCount <= 1 || !canBeParallel) + { + return false; + } + + const int MinChildrenForParallel = 4; + int nonNullChildren = 0; + for (int i = 0; i < BranchesCount; i++) + { + object? data = item._nodeData[i]; + if (data is not null && !ReferenceEquals(data, _nullNode) && ++nonNullChildren >= MinChildrenForParallel) + { + return true; + } + } + + return false; + } } private static int GetChildrenRlpLengthForBranch(ITrieNodeResolver tree, ref TreePath path, TrieNode item, ICappedArrayPool? bufferPool) diff --git a/src/Nethermind/Nethermind.Trie/TrieNode.cs b/src/Nethermind/Nethermind.Trie/TrieNode.cs index 52dc5e0b1c9b..e660f86c5d87 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNode.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNode.cs @@ -32,7 +32,6 @@ public sealed partial class TrieNode #endif private static readonly object _nullNode = new(); - private static readonly TrieNodeDecoder _nodeDecoder = new(); private static readonly AccountDecoder _accountDecoder = new(); private const byte _dirtyMask = 0b001; diff --git a/src/Nethermind/Nethermind.Trie/VisitContext.cs b/src/Nethermind/Nethermind.Trie/VisitContext.cs index 5d8f3f0c0c7e..e636efd9557d 100644 --- a/src/Nethermind/Nethermind.Trie/VisitContext.cs +++ b/src/Nethermind/Nethermind.Trie/VisitContext.cs @@ -58,7 +58,6 @@ public SmallTrieVisitContext(TrieVisitContext trieVisitContext) } public byte Level { get; internal set; } - private byte _branchChildIndex = 255; private byte _flags = 0; private const byte StorageFlag = 1; diff --git a/src/Nethermind/Nethermind.TxPool.Test/BlobTxStorageTests.cs b/src/Nethermind/Nethermind.TxPool.Test/BlobTxStorageTests.cs index b8333b8af243..802844f03cd1 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/BlobTxStorageTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/BlobTxStorageTests.cs @@ -4,12 +4,16 @@ using System; using FluentAssertions; using Nethermind.Core; +using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Int256; using NUnit.Framework; namespace Nethermind.TxPool.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] public class BlobTxStorageTests { [Test] @@ -32,4 +36,100 @@ public void should_throw_when_trying_to_add_tx_with_null_hash() Action act = () => blobTxStorage.Add(tx); act.Should().Throw(); } + + [Test] + public void TryGetMany_should_return_zero_for_empty_batch() + { + BlobTxStorage blobTxStorage = new(); + Transaction[] results = new Transaction[0]; + + int found = blobTxStorage.TryGetMany([], 0, results); + found.Should().Be(0); + } + + [Test] + public void TryGetMany_should_batch_retrieve_stored_transactions() + { + BlobTxStorage blobTxStorage = new(); + EthereumEcdsa ecdsa = new(BlockchainIds.Mainnet); + + Transaction[] txs = new Transaction[3]; + TxLookupKey[] keys = new TxLookupKey[3]; + + for (int i = 0; i < 3; i++) + { + txs[i] = Build.A.Transaction + .WithShardBlobTxTypeAndFields() + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce((UInt256)i) + .SignedAndResolved(ecdsa, TestItem.PrivateKeys[i]).TestObject; + + blobTxStorage.Add(txs[i]); + keys[i] = new TxLookupKey(txs[i].Hash, txs[i].SenderAddress!, txs[i].Timestamp); + } + + Transaction[] results = new Transaction[3]; + int found = blobTxStorage.TryGetMany(keys, 3, results); + + found.Should().Be(3); + for (int i = 0; i < 3; i++) + { + results[i].Should().BeEquivalentTo(txs[i], static options => options + .Excluding(static t => t.GasBottleneck) + .Excluding(static t => t.PoolIndex)); + } + } + + [Test] + public void TryGetMany_should_handle_mix_of_existing_and_missing_keys() + { + BlobTxStorage blobTxStorage = new(); + EthereumEcdsa ecdsa = new(BlockchainIds.Mainnet); + + Transaction[] txs = new Transaction[2]; + for (int i = 0; i < 2; i++) + { + txs[i] = Build.A.Transaction + .WithShardBlobTxTypeAndFields() + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce((UInt256)i) + .SignedAndResolved(ecdsa, TestItem.PrivateKeys[i]).TestObject; + + blobTxStorage.Add(txs[i]); + } + + TxLookupKey[] keys = new TxLookupKey[3]; + keys[0] = new TxLookupKey(txs[0].Hash, txs[0].SenderAddress!, txs[0].Timestamp); + keys[1] = new TxLookupKey(txs[1].Hash, txs[1].SenderAddress!, txs[1].Timestamp); + keys[2] = new TxLookupKey(TestItem.KeccakA, TestItem.AddressC, UInt256.One); + + Transaction[] results = new Transaction[3]; + int found = blobTxStorage.TryGetMany(keys, 3, results); + + found.Should().Be(2); + results[0].Should().NotBeNull(); + results[1].Should().NotBeNull(); + results[2].Should().BeNull(); + } + + [Test] + public void TryGetMany_should_handle_all_missing_keys() + { + BlobTxStorage blobTxStorage = new(); + + TxLookupKey[] keys = + [ + new TxLookupKey(TestItem.KeccakA, TestItem.AddressA, UInt256.One), + new TxLookupKey(TestItem.KeccakB, TestItem.AddressB, UInt256.One), + ]; + + Transaction[] results = new Transaction[2]; + int found = blobTxStorage.TryGetMany(keys, 2, results); + + found.Should().Be(0); + results[0].Should().BeNull(); + results[1].Should().BeNull(); + } } diff --git a/src/Nethermind/Nethermind.TxPool.Test/Collections/SortedPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/Collections/SortedPoolTests.cs index 758a9c9ff3da..b2820b770334 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/Collections/SortedPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/Collections/SortedPoolTests.cs @@ -20,6 +20,8 @@ namespace Nethermind.TxPool.Test.Collections { [TestFixture] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class SortedPoolTests { private const int Capacity = 16; diff --git a/src/Nethermind/Nethermind.TxPool.Test/CompetingTransactionEqualityComparerTests.cs b/src/Nethermind/Nethermind.TxPool.Test/CompetingTransactionEqualityComparerTests.cs index 9d316746a3f5..c5fab4a47687 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/CompetingTransactionEqualityComparerTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/CompetingTransactionEqualityComparerTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.TxPool.Test { + [Parallelizable(ParallelScope.All)] public class CompetingTransactionEqualityComparerTests { public static IEnumerable TestCases diff --git a/src/Nethermind/Nethermind.TxPool.Test/DelegatedAccountFilterTest.cs b/src/Nethermind/Nethermind.TxPool.Test/DelegatedAccountFilterTest.cs index c7d004f471f2..b7d7e3590d5c 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/DelegatedAccountFilterTest.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/DelegatedAccountFilterTest.cs @@ -19,6 +19,7 @@ namespace Nethermind.TxPool.Test; +[Parallelizable(ParallelScope.All)] internal class DelegatedAccountFilterTest { [Test] @@ -26,7 +27,7 @@ public void Accept_SenderIsNotDelegated_ReturnsAccepted() { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For(), new DelegationCache()); Transaction transaction = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; @@ -46,7 +47,7 @@ public void Accept_SenderIsDelegatedWithNoTransactionsInPool_ReturnsAccepted() stateProvider.InsertCode(code, TestItem.AddressA); IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, stateProvider, new DelegationCache()); Transaction transaction = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; @@ -62,7 +63,7 @@ public void Accept_SenderIsDelegatedWithOneTransactionInPoolWithSameNonce_Return { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); Transaction inPool = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; standardPool.TryInsert(inPool.Hash, inPool); @@ -84,7 +85,7 @@ public void Accept_SenderIsDelegatedWithOneTransactionInPoolWithDifferentNonce_R { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); Transaction inPool = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; standardPool.TryInsert(inPool.Hash, inPool); @@ -111,7 +112,7 @@ public void Accept_Eip7702IsNotActivated_ReturnsExpected(bool isActive, AcceptTx { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(isActive ? Prague.Instance : Cancun.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); Transaction inPool = Build.A.Transaction.WithNonce(0).SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; standardPool.TryInsert(inPool.Hash, inPool); @@ -140,7 +141,7 @@ public void Accept_SenderHasPendingDelegation_OnlyAcceptsIfNonceIsExactMatch(int { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegationCache pendingDelegations = new(); pendingDelegations.IncrementDelegationCount(TestItem.AddressA); @@ -161,7 +162,7 @@ public void Accept_AuthorityHasPendingTransaction_ReturnsDelegatorHasPendingTx(b { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For(), new()); Transaction transaction; @@ -200,7 +201,7 @@ public void Accept_SetCodeTxHasAuthorityWithPendingTx_ReturnsDelegatorHasPending { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegationCache pendingDelegations = new(); pendingDelegations.IncrementDelegationCount(TestItem.AddressA); diff --git a/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs b/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs index dce8e3bde40c..f5d801d747a8 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs @@ -19,6 +19,8 @@ namespace Nethermind.TxPool.Test; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class NonceManagerTests { private ISpecProvider _specProvider; diff --git a/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs b/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs index cf9b832a7cb3..4bebaa002d24 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs @@ -19,6 +19,8 @@ namespace Nethermind.TxPool.Test { [TestFixture(true)] [TestFixture(false)] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class ReceiptStorageTests { private readonly bool _useEip2718; diff --git a/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs b/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs index 9b09028fedaa..1ee4370de27c 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs @@ -3,16 +3,16 @@ using Nethermind.Core; using Nethermind.Logging; -using NSubstitute; using NUnit.Framework; using System; using System.Threading; using System.Threading.Tasks; -using Nethermind.Core.Test; namespace Nethermind.TxPool.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class RetryCacheTests { public readonly struct ResourceRequestMessage : INew @@ -23,10 +23,27 @@ public class RetryCacheTests public interface ITestHandler : IMessageHandler; + /// + /// A test handler that tracks HandleMessage calls without using NSubstitute. + /// + private class TestHandler : ITestHandler + { + private int _handleMessageCallCount; + public int HandleMessageCallCount => _handleMessageCallCount; + public bool WasCalled => _handleMessageCallCount > 0; + public Action OnHandleMessage { get; set; } + + public void HandleMessage(ResourceRequestMessage message) + { + Interlocked.Increment(ref _handleMessageCallCount); + OnHandleMessage?.Invoke(message); + } + } + private CancellationTokenSource _cancellationTokenSource; private RetryCache _cache; - private readonly int Timeout = 5000; + private readonly int Timeout = 10000; [SetUp] public void Setup() @@ -45,8 +62,8 @@ public void TearDown() [Test] public void Announced_SameResourceDifferentNode_ReturnsEnqueued() { - AnnounceResult result1 = _cache.Announced(1, Substitute.For()); - AnnounceResult result2 = _cache.Announced(1, Substitute.For()); + AnnounceResult result1 = _cache.Announced(1, new TestHandler()); + AnnounceResult result2 = _cache.Announced(1, new TestHandler()); Assert.That(result1, Is.EqualTo(AnnounceResult.RequestRequired)); Assert.That(result2, Is.EqualTo(AnnounceResult.Delayed)); @@ -55,58 +72,57 @@ public void Announced_SameResourceDifferentNode_ReturnsEnqueued() [Test] public void Announced_AfterTimeout_ExecutesRetryRequests() { - ITestHandler request1 = Substitute.For(); - ITestHandler request2 = Substitute.For(); + TestHandler request1 = new(); + TestHandler request2 = new(); _cache.Announced(1, request1); _cache.Announced(1, request2); - Assert.That(() => request2.ReceivedCallsMatching(r => r.HandleMessage(Arg.Any())), Is.True.After(Timeout, 100)); - request1.DidNotReceive().HandleMessage(Arg.Any()); + Assert.That(() => request2.WasCalled, Is.True.After(Timeout, 100)); + Assert.That(request1.WasCalled, Is.False); } [Test] public void Announced_MultipleResources_ExecutesAllRetryRequestsExceptInitialOne() { - ITestHandler request1 = Substitute.For(); - ITestHandler request2 = Substitute.For(); - ITestHandler request3 = Substitute.For(); - ITestHandler request4 = Substitute.For(); + TestHandler request1 = new(); + TestHandler request2 = new(); + TestHandler request3 = new(); + TestHandler request4 = new(); _cache.Announced(1, request1); _cache.Announced(1, request2); _cache.Announced(2, request3); _cache.Announced(2, request4); - Assert.That(() => request2.ReceivedCallsMatching(r => r.HandleMessage(Arg.Any())), Is.True.After(Timeout, 100)); - Assert.That(() => request4.ReceivedCallsMatching(r => r.HandleMessage(Arg.Any())), Is.True.After(Timeout, 100)); - request1.DidNotReceive().HandleMessage(Arg.Any()); - request3.DidNotReceive().HandleMessage(Arg.Any()); + Assert.That(() => request2.WasCalled, Is.True.After(Timeout, 100)); + Assert.That(() => request4.WasCalled, Is.True.After(Timeout, 100)); + Assert.That(request1.WasCalled, Is.False); + Assert.That(request3.WasCalled, Is.False); } [Test] public void Received_RemovesResourceFromRetryQueue() { - _cache.Announced(1, Substitute.For()); + _cache.Announced(1, new TestHandler()); _cache.Received(1); - AnnounceResult result = _cache.Announced(1, Substitute.For()); + AnnounceResult result = _cache.Announced(1, new TestHandler()); Assert.That(result, Is.EqualTo(AnnounceResult.RequestRequired)); } [Test] public async Task Received_BeforeTimeout_PreventsRetryExecution() { - ITestHandler request = Substitute.For(); + TestHandler request = new(); _cache.Announced(1, request); _cache.Announced(1, request); _cache.Received(1); - await Task.Delay(Timeout, _cancellationTokenSource.Token); - request.DidNotReceive().HandleMessage(ResourceRequestMessage.New(1)); + Assert.That(request.WasCalled, Is.False); } [Test] @@ -116,9 +132,7 @@ public async Task Clear_cache_after_timeout() { Parallel.For(0, 100, (j) => { - ITestHandler request = Substitute.For(); - - _cache.Announced(i, request); + _cache.Announced(i, new TestHandler()); }); }); @@ -128,40 +142,39 @@ public async Task Clear_cache_after_timeout() } [Test] + [Retry(3)] public void RetryExecution_HandlesExceptions() { - ITestHandler faultyRequest = Substitute.For(); - ITestHandler normalRequest = Substitute.For(); - - faultyRequest.When(x => x.HandleMessage(Arg.Any())).Do(x => throw new InvalidOperationException("Test exception")); + TestHandler faultyRequest = new() { OnHandleMessage = _ => throw new InvalidOperationException("Test exception") }; + TestHandler normalRequest = new(); - _cache.Announced(1, Substitute.For()); + _cache.Announced(1, new TestHandler()); _cache.Announced(1, faultyRequest); _cache.Announced(1, normalRequest); - Assert.That(() => normalRequest.ReceivedCallsMatching(r => r.HandleMessage(ResourceRequestMessage.New(1))), Is.True.After(Timeout, 100)); + Assert.That(() => normalRequest.WasCalled, Is.True.After(Timeout, 100)); } [Test] public async Task CancellationToken_StopsProcessing() { - ITestHandler request = Substitute.For(); + TestHandler request = new(); _cache.Announced(1, request); await _cancellationTokenSource.CancelAsync(); await Task.Delay(Timeout); - request.DidNotReceive().HandleMessage(Arg.Any()); + Assert.That(request.WasCalled, Is.False); } [Test] public async Task Announced_AfterRetryInProgress_ReturnsNew() { - _cache.Announced(1, Substitute.For()); + _cache.Announced(1, new TestHandler()); await Task.Delay(Timeout, _cancellationTokenSource.Token); - Assert.That(() => _cache.Announced(1, Substitute.For()), Is.EqualTo(AnnounceResult.RequestRequired).After(Timeout, 100)); + Assert.That(() => _cache.Announced(1, new TestHandler()), Is.EqualTo(AnnounceResult.RequestRequired).After(Timeout, 100)); } [Test] diff --git a/src/Nethermind/Nethermind.TxPool.Test/TestBlockTree.cs b/src/Nethermind/Nethermind.TxPool.Test/TestBlockTree.cs new file mode 100644 index 000000000000..a8473dc82663 --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool.Test/TestBlockTree.cs @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Find; +using Nethermind.Blockchain.Visitors; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; + +namespace Nethermind.TxPool.Test; + +/// +/// A minimal IBlockTree implementation for testing that avoids NSubstitute's +/// static state issues when running tests in parallel. +/// +internal class TestBlockTree : IBlockTree +{ + public Block? Head { get; set; } + public BlockHeader? BestSuggestedHeader { get; set; } + + public event EventHandler? BlockAddedToMain; + public event EventHandler? NewBestSuggestedBlock { add { } remove { } } + public event EventHandler? NewSuggestedBlock { add { } remove { } } + public event EventHandler? NewHeadBlock { add { } remove { } } + public event EventHandler? OnUpdateMainChain { add { } remove { } } + public event EventHandler? OnForkChoiceUpdated { add { } remove { } } + + public void RaiseBlockAddedToMain(BlockReplacementEventArgs args) + { + BlockAddedToMain?.Invoke(this, args); + } + + public BlockHeader FindBestSuggestedHeader() => BestSuggestedHeader!; + + // IBlockFinder implementation + public Hash256 HeadHash => Head?.Hash ?? Keccak.Zero; + public Hash256 GenesisHash => Keccak.Zero; + public Hash256? PendingHash => null; + public Hash256? FinalizedHash => null; + public Hash256? SafeHash => null; + public long? BestPersistedState { get; set; } + + public Block? FindBlock(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) => null; + public Block? FindBlock(long blockNumber, BlockTreeLookupOptions options) => null; + public bool HasBlock(long blockNumber, Hash256 blockHash) => false; + public BlockHeader? FindHeader(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) => null; + public BlockHeader? FindHeader(long blockNumber, BlockTreeLookupOptions options) => null; + public Hash256? FindBlockHash(long blockNumber) => null; + public bool IsMainChain(BlockHeader blockHeader) => false; + public bool IsMainChain(Hash256 blockHash, bool throwOnMissingHash = true) => false; + public long GetLowestBlock() => 0; + + // IBlockTree implementation + public ulong NetworkId => 1; + public ulong ChainId => 1; + public BlockHeader? Genesis => null; + public Block? BestSuggestedBody => null; + public BlockHeader? BestSuggestedBeaconHeader => null; + public BlockHeader? LowestInsertedHeader { get; set; } + public BlockHeader? LowestInsertedBeaconHeader { get; set; } + public long BestKnownNumber => Head?.Number ?? 0; + public long BestKnownBeaconNumber => 0; + public bool CanAcceptNewBlocks => true; + public (long BlockNumber, Hash256 BlockHash) SyncPivot { get; set; } + public bool IsProcessingBlock { get; set; } + + public AddBlockResult Insert(BlockHeader header, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) + => AddBlockResult.Added; + + public void BulkInsertHeader(IReadOnlyList headers, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) { } + + public AddBlockResult Insert(Block block, BlockTreeInsertBlockOptions insertBlockOptions = BlockTreeInsertBlockOptions.None, + BlockTreeInsertHeaderOptions insertHeaderOptions = BlockTreeInsertHeaderOptions.None, WriteFlags bodiesWriteFlags = WriteFlags.None) + => AddBlockResult.Added; + + public void UpdateHeadBlock(Hash256 blockHash) { } + public void NewOldestBlock(long oldestBlock) { } + public AddBlockResult SuggestBlock(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) => AddBlockResult.Added; + public ValueTask SuggestBlockAsync(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) + => ValueTask.FromResult(AddBlockResult.Added); + public AddBlockResult SuggestHeader(BlockHeader header) => AddBlockResult.Added; + public bool IsKnownBlock(long number, Hash256 blockHash) => false; + public bool IsKnownBeaconBlock(long number, Hash256 blockHash) => false; + public bool WasProcessed(long number, Hash256 blockHash) => false; + public void UpdateMainChain(IReadOnlyList blocks, bool wereProcessed, bool forceHeadBlock = false) { } + public void MarkChainAsProcessed(IReadOnlyList blocks) { } + public Task Accept(IBlockTreeVisitor blockTreeVisitor, CancellationToken cancellationToken) => Task.CompletedTask; + public (BlockInfo? Info, ChainLevelInfo? Level) GetInfo(long number, Hash256 blockHash) => (null, null); + public ChainLevelInfo? FindLevel(long number) => null; + public BlockInfo FindCanonicalBlockInfo(long blockNumber) => null!; + public Hash256? FindHash(long blockNumber) => null; + public IOwnedReadOnlyList FindHeaders(Hash256 hash, int numberOfBlocks, int skip, bool reverse) + => new ArrayPoolList(0); + public void DeleteInvalidBlock(Block invalidBlock) { } + public void DeleteOldBlock(long blockNumber, Hash256 blockHash) { } + public void ForkChoiceUpdated(Hash256? finalizedBlockHash, Hash256? safeBlockBlockHash) { } + public int DeleteChainSlice(in long startNumber, long? endNumber = null, bool force = false) => 0; + public bool IsBetterThanHead(BlockHeader? header) => false; + public void UpdateBeaconMainChain(BlockInfo[]? blockInfos, long clearBeaconMainChainStartPoint) { } + public void RecalculateTreeLevels() { } +} diff --git a/src/Nethermind/Nethermind.TxPool.Test/TestChainHeadInfoProvider.cs b/src/Nethermind/Nethermind.TxPool.Test/TestChainHeadInfoProvider.cs new file mode 100644 index 000000000000..9cb26ba9b4ea --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool.Test/TestChainHeadInfoProvider.cs @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +#nullable enable + +using System; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm.State; +using Nethermind.Int256; + +namespace Nethermind.TxPool.Test; + +/// +/// A minimal IChainHeadInfoProvider implementation for testing that avoids NSubstitute's +/// static state issues when running tests in parallel. +/// +internal class TestChainHeadInfoProvider : IChainHeadInfoProvider +{ + public IChainHeadSpecProvider SpecProvider { get; set; } = null!; + public IReadOnlyStateProvider ReadOnlyStateProvider { get; set; } = null!; + public long HeadNumber { get; set; } + public long? BlockGasLimit { get; set; } = 30_000_000; + public UInt256 CurrentBaseFee { get; set; } + public UInt256 CurrentFeePerBlobGas { get; set; } + public ProofVersion CurrentProofVersion { get; set; } + public bool IsSyncing { get; set; } + public bool IsProcessingBlock { get; set; } + public event EventHandler? HeadChanged; + + public void RaiseHeadChanged(BlockReplacementEventArgs args) + { + HeadChanged?.Invoke(this, args); + } +} diff --git a/src/Nethermind/Nethermind.TxPool.Test/TransactionExtensionsTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TransactionExtensionsTests.cs index f8baecc5b94f..1028eddc5577 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TransactionExtensionsTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TransactionExtensionsTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.TxPool.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] public class TransactionExtensionsTests { [Test] diff --git a/src/Nethermind/Nethermind.TxPool.Test/TransactionPoolInfoProviderTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TransactionPoolInfoProviderTests.cs index f9486f29e11c..722f7ba43e68 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TransactionPoolInfoProviderTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TransactionPoolInfoProviderTests.cs @@ -13,6 +13,8 @@ namespace Nethermind.TxPool.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class TxPoolInfoProviderTests { private Address _address; diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs index 696f7f1385ee..17daab08c3ac 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs @@ -39,6 +39,8 @@ namespace Nethermind.TxPool.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class TxBroadcasterTests { private ILogManager _logManager; @@ -48,7 +50,7 @@ public class TxBroadcasterTests private TxBroadcaster _broadcaster; private EthereumEcdsa _ethereumEcdsa; private TxPoolConfig _txPoolConfig; - private IChainHeadInfoProvider _headInfo; + private TestChainHeadInfoProvider _headInfo; [SetUp] public void Setup() @@ -59,18 +61,19 @@ public void Setup() _blockTree = Substitute.For(); _comparer = new TransactionComparerProvider(_specProvider, _blockTree).GetDefaultComparer(); _txPoolConfig = new TxPoolConfig(); - _headInfo = Substitute.For(); + _headInfo = new TestChainHeadInfoProvider(); } [TearDown] public void TearDown() => _broadcaster?.Dispose(); [Test] - public async Task should_not_broadcast_persisted_tx_to_peer_too_quickly() + public void should_not_broadcast_persisted_tx_to_peer_too_quickly() { + ManualTimestamper timestamper = new(DateTime.UtcNow); _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = 100 }; - _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager, timestamper: timestamper); + _headInfo.CurrentBaseFee = 0.GWei(); int addedTxsCount = TestItem.PrivateKeys.Length; Transaction[] transactions = new Transaction[addedTxsCount]; @@ -102,7 +105,8 @@ public async Task should_not_broadcast_persisted_tx_to_peer_too_quickly() peer.Received(1).SendNewTransactions(Arg.Any>(), true); - await Task.Delay(TimeSpan.FromMilliseconds(1001)); + // Advance time by 2 seconds (throttle is 1 second) + timestamper.Add(TimeSpan.FromSeconds(2)); peer.Received(1).SendNewTransactions(Arg.Any>(), true); @@ -120,7 +124,7 @@ public void should_pick_best_persistent_txs_to_broadcast([Values(1, 2, 99, 100, { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); int addedTxsCount = TestItem.PrivateKeys.Length; Transaction[] transactions = new Transaction[addedTxsCount]; @@ -203,7 +207,7 @@ public void should_skip_large_txs_when_picking_best_persistent_txs_to_broadcast( { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); // add 256 transactions, 10% of them is large int addedTxsCount = TestItem.PrivateKeys.Length; @@ -249,7 +253,7 @@ public void should_skip_blob_txs_when_picking_best_persistent_txs_to_broadcast([ { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); // add 256 transactions, 10% of them is blob type int addedTxsCount = TestItem.PrivateKeys.Length; @@ -297,7 +301,7 @@ public void should_not_pick_txs_with_GasPrice_lower_than_CurrentBaseFee([Values( _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); const int currentBaseFeeInGwei = 250; - _headInfo.CurrentBaseFee.Returns(currentBaseFeeInGwei.GWei()); + _headInfo.CurrentBaseFee = currentBaseFeeInGwei.GWei(); Block headBlock = Build.A.Block .WithNumber(MainnetSpecProvider.LondonBlockNumber) .WithBaseFeePerGas(currentBaseFeeInGwei.GWei()) @@ -341,7 +345,7 @@ public void should_not_pick_txs_with_GasPrice_lower_than_CurrentBaseFee([Values( [TestCase(150, true)] public void should_not_broadcast_tx_with_MaxFeePerGas_lower_than_70_percent_of_CurrentBaseFee(int maxFeePerGas, bool shouldBroadcast) { - _headInfo.CurrentBaseFee.Returns((UInt256)100); + _headInfo.CurrentBaseFee = (UInt256)100; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); @@ -371,7 +375,7 @@ public void should_not_pick_1559_txs_with_MaxFeePerGas_lower_than_CurrentBaseFee _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); const int currentBaseFeeInGwei = 250; - _headInfo.CurrentBaseFee.Returns(currentBaseFeeInGwei.GWei()); + _headInfo.CurrentBaseFee = currentBaseFeeInGwei.GWei(); Block headBlock = Build.A.Block .WithNumber(MainnetSpecProvider.LondonBlockNumber) .WithBaseFeePerGas(currentBaseFeeInGwei.GWei()) @@ -416,7 +420,7 @@ public void should_not_pick_blob_txs_with_MaxFeePerBlobGas_lower_than_CurrentFee _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); const int currentFeePerBlobGas = 250; - _headInfo.CurrentFeePerBlobGas.Returns(currentFeePerBlobGas.GWei()); + _headInfo.CurrentFeePerBlobGas = currentFeePerBlobGas.GWei(); // add 256 transactions with MaxFeePerBlobGas 0-255 int addedTxsCount = TestItem.PrivateKeys.Length; @@ -463,7 +467,7 @@ public void should_pick_tx_with_lowest_nonce_from_bucket() { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = 5 }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); const int addedTxsCount = 5; Transaction[] transactions = new Transaction[addedTxsCount]; @@ -508,7 +512,7 @@ public void should_broadcast_local_tx_immediately_after_receiving_it() public void should_broadcast_full_local_tx_immediately_after_receiving_it() { _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); ISession session = Substitute.For(); session.Node.Returns(new Node(TestItem.PublicKeyA, TestItem.IPEndPointA)); @@ -646,7 +650,7 @@ public void should_check_tx_policy_for_broadcast(bool canGossipTransactions, boo { ITxGossipPolicy txGossipPolicy = Substitute.For(); _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager, txGossipPolicy); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); ISession session = Substitute.For(); session.Node.Returns(new Node(TestItem.PublicKeyA, TestItem.IPEndPointA)); @@ -708,7 +712,7 @@ public void should_rebroadcast_all_persistent_transactions_if_PeerNotificationTh [TestCase(10000, 7000)] public void should_calculate_baseFeeThreshold_correctly(int baseFee, int expectedThreshold) { - _headInfo.CurrentBaseFee.Returns((UInt256)baseFee); + _headInfo.CurrentBaseFee = (UInt256)baseFee; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); _broadcaster.CalculateBaseFeeThreshold().Should().Be((UInt256)expectedThreshold); } @@ -717,7 +721,7 @@ public void should_calculate_baseFeeThreshold_correctly(int baseFee, int expecte public void calculation_of_baseFeeThreshold_should_handle_overflow_correctly([Values(0, 70, 100, 101, 500)] int threshold, [Values(2, 3, 4, 5, 6, 7, 8, 9, 10, 11)] int divisor) { UInt256.Divide(UInt256.MaxValue, (UInt256)divisor, out UInt256 baseFee); - _headInfo.CurrentBaseFee.Returns(baseFee); + _headInfo.CurrentBaseFee = baseFee; _txPoolConfig = new TxPoolConfig() { MinBaseFeeThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs index 17764368551b..ce0d4cf783f7 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using CkzgLib; using FluentAssertions; using Nethermind.Blockchain; @@ -607,7 +608,8 @@ Transaction GetTx(bool isBlob, UInt256 nonce) _txPool.GetPendingTransactionsCount().Should().Be(firstIsBlob ? 0 : 1); _txPool.GetPendingBlobTransactionsCount().Should().Be(firstIsBlob ? 1 : 0); _stateProvider.IncrementNonce(TestItem.AddressA); - await RaiseBlockAddedToMainAndWaitForTransactions(1); + Block block = Build.A.Block.WithNumber(1).TestObject; + await RaiseBlockAddedToMainAndWaitForNewHead(block); _txPool.GetPendingTransactionsCount().Should().Be(0); _txPool.GetPendingBlobTransactionsCount().Should().Be(0); @@ -951,7 +953,7 @@ public void should_convert_blob_proofs_to_cell_proofs_if_enabled([Values(true, f EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); // update head and set correct current proof version - _blockTree.BlockAddedToMain += Raise.EventWith(new BlockReplacementEventArgs(Build.A.Block.TestObject)); + _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.TestObject)); Transaction blobTxAdded = Build.A.Transaction .WithShardBlobTxTypeAndFields() @@ -985,7 +987,7 @@ public void should_convert_blob_proofs_to_cell_proofs_if_enabled([Values(true, f public async Task should_evict_based_on_proof_version_and_fork(BlobsSupportMode poolMode, TestAction[] testActions) { Block head = _blockTree.Head; - _blockTree.FindBestSuggestedHeader().Returns(head.Header); + _blockTree.BestSuggestedHeader = head.Header; (ChainSpecBasedSpecProvider provider, _) = TestSpecHelper.LoadChainSpec(new ChainSpecJson { @@ -1053,12 +1055,12 @@ TestCaseData MakeTestCase(string testName, int finalCount, BlobsSupportMode mode } } - private Task AddEmptyBlock() + private async Task AddEmptyBlock() { BlockHeader bh = new(_blockTree.Head.Hash, Keccak.EmptyTreeHash, TestItem.AddressA, 0, _blockTree.Head.Number + 1, _blockTree.Head.GasLimit, _blockTree.Head.Timestamp + 1, []); - _blockTree.FindBestSuggestedHeader().Returns(bh); - _blockTree.BlockAddedToMain += Raise.EventWith(new BlockReplacementEventArgs(new Block(bh, new BlockBody([], [])), _blockTree.Head)); - return Task.Delay(300); + _blockTree.BestSuggestedHeader = bh; + Block block = new Block(bh, new BlockBody([], [])); + await RaiseBlockAddedToMainAndWaitForNewHead(block, _blockTree.Head); } private Transaction CreateBlobTx(PrivateKey sender, UInt256 nonce = default, int blobCount = 1, IReleaseSpec releaseSpec = default) @@ -1092,7 +1094,7 @@ public async Task should_evict_txs_with_too_many_blobs_per_tx_after_fork() }; Block head = _blockTree.Head; - _blockTree.FindBestSuggestedHeader().Returns(head.Header); + _blockTree.BestSuggestedHeader = head.Header; _txPool = CreatePool(specProvider: provider); EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); @@ -1126,7 +1128,7 @@ public async Task should_evict_txs_with_too_many_blobs_per_block_after_fork() }; Block head = _blockTree.Head; - _blockTree.FindBestSuggestedHeader().Returns(head.Header); + _blockTree.BestSuggestedHeader = head.Header; _txPool = CreatePool(specProvider: provider); EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); @@ -1155,5 +1157,139 @@ public void max_blobs_per_tx_should_not_exceed_max_blobs_per_block() Assert.That(maxBlobsPerTx, Is.EqualTo(regularMaxBlobCount)); } + + [Test] + public void should_batch_return_blobs_and_proofs_v1_from_persistent_storage() + { + // BlobCacheSize = 1 forces cache eviction after the first insert, + // so the second tx must be fetched via TryGetMany (Phase 2 DB path). + TxPoolConfig txPoolConfig = new() + { + BlobsSupport = BlobsSupportMode.Storage, + BlobCacheSize = 1, + Size = 10 + }; + BlobTxStorage blobTxStorage = new(); + _txPool = CreatePool(txPoolConfig, GetOsakaSpecProvider(), txStorage: blobTxStorage); + EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); + EnsureSenderBalance(TestItem.AddressB, UInt256.MaxValue); + + Transaction tx1 = Build.A.Transaction + .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce(UInt256.Zero) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; + + Transaction tx2 = Build.A.Transaction + .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce(UInt256.Zero) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyB).TestObject; + + _txPool.SubmitTx(tx1, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + _txPool.SubmitTx(tx2, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + + // tx1 was evicted from cache (size=1) when tx2 was inserted, + // so at least one must come from DB via TryGetMany + byte[][] requestedHashes = [tx1.BlobVersionedHashes![0]!, tx2.BlobVersionedHashes![0]!]; + byte[][] blobs = new byte[2][]; + ReadOnlyMemory[] proofs = new ReadOnlyMemory[2]; + + int found = _txPool.TryGetBlobsAndProofsV1(requestedHashes, blobs, proofs); + + found.Should().Be(2); + blobs[0].Should().NotBeNull(); + blobs[1].Should().NotBeNull(); + proofs[0].Length.Should().Be(Ckzg.CellsPerExtBlob); + proofs[1].Length.Should().Be(Ckzg.CellsPerExtBlob); + } + + [Test] + public void should_batch_return_partial_blobs_when_some_missing() + { + TxPoolConfig txPoolConfig = new() + { + BlobsSupport = BlobsSupportMode.Storage, + BlobCacheSize = 1, + Size = 10 + }; + BlobTxStorage blobTxStorage = new(); + _txPool = CreatePool(txPoolConfig, GetOsakaSpecProvider(), txStorage: blobTxStorage); + EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); + + Transaction tx1 = Build.A.Transaction + .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce(UInt256.Zero) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; + + _txPool.SubmitTx(tx1, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + + byte[] fakeBlobHash = new byte[32]; + fakeBlobHash[0] = 0x01; // versioned hash prefix + byte[][] requestedHashes = [tx1.BlobVersionedHashes![0]!, fakeBlobHash]; + byte[][] blobs = new byte[2][]; + ReadOnlyMemory[] proofs = new ReadOnlyMemory[2]; + + int found = _txPool.TryGetBlobsAndProofsV1(requestedHashes, blobs, proofs); + + found.Should().Be(1); + blobs[0].Should().NotBeNull(); + blobs[1].Should().BeNull(); + } + + [Test] + public void should_batch_return_blobs_from_cache_and_db() + { + // BlobCacheSize = 1: after inserting tx1 and tx2, only tx2 remains in cache. + // Accessing tx1 via single lookup re-populates it, evicting tx2. + // Batch lookup then exercises: tx1 from cache, tx2 from DB (TryGetMany). + TxPoolConfig txPoolConfig = new() + { + BlobsSupport = BlobsSupportMode.Storage, + BlobCacheSize = 1, + Size = 10 + }; + BlobTxStorage blobTxStorage = new(); + _txPool = CreatePool(txPoolConfig, GetOsakaSpecProvider(), txStorage: blobTxStorage); + EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); + EnsureSenderBalance(TestItem.AddressB, UInt256.MaxValue); + + Transaction tx1 = Build.A.Transaction + .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce(UInt256.Zero) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; + + Transaction tx2 = Build.A.Transaction + .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce(UInt256.Zero) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyB).TestObject; + + _txPool.SubmitTx(tx1, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + _txPool.SubmitTx(tx2, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + + // Access tx1 via single lookup — this re-populates tx1 in cache, evicting tx2 + _txPool.TryGetBlobAndProofV1(tx1.BlobVersionedHashes![0]!, out byte[] _, out byte[][] _).Should().BeTrue(); + + // Now batch lookup: tx1 from cache (just accessed), tx2 from DB + byte[][] requestedHashes = [tx1.BlobVersionedHashes![0]!, tx2.BlobVersionedHashes![0]!]; + byte[][] blobs = new byte[2][]; + ReadOnlyMemory[] proofs = new ReadOnlyMemory[2]; + + int found = _txPool.TryGetBlobsAndProofsV1(requestedHashes, blobs, proofs); + + found.Should().Be(2); + blobs[0].Should().NotBeNull(); + blobs[1].Should().NotBeNull(); + proofs[0].Length.Should().Be(Ckzg.CellsPerExtBlob); + proofs[1].Length.Should().Be(Ckzg.CellsPerExtBlob); + } } } diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs index 6f431b074d40..537752efcb27 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs @@ -36,16 +36,25 @@ namespace Nethermind.TxPool.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public partial class TxPoolTests { + private const int Timeout = 10000; private ILogManager _logManager; private IEthereumEcdsa _ethereumEcdsa; private ISpecProvider _specProvider; private TxPool _txPool; private TestReadOnlyStateProvider _stateProvider; - private IBlockTree _blockTree; + private TestBlockTree _blockTree; - private readonly int _txGasLimit = 1_000_000; + private const int TxGasLimit = 1_000_000; + + [OneTimeSetUp] + public static void OneTimeSetup() + { + KzgPolynomialCommitments.InitializeAsync().Wait(); + } [SetUp] public void Setup() @@ -54,19 +63,17 @@ public void Setup() _specProvider = MainnetSpecProvider.Instance; _ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId); _stateProvider = new TestReadOnlyStateProvider(); - _blockTree = Substitute.For(); - Block block = Build.A.Block.WithNumber(10000000 - 1).TestObject; - _blockTree.Head.Returns(block); - _blockTree.FindBestSuggestedHeader().Returns(Build.A.BlockHeader.WithNumber(10000000).TestObject); - - KzgPolynomialCommitments.InitializeAsync().Wait(); + _blockTree = new TestBlockTree(); + Block block = Build.A.Block.WithNumber(10000000 - 1).WithBaseFeePerGas(0).TestObject; + _blockTree.Head = block; + _blockTree.BestSuggestedHeader = Build.A.BlockHeader.WithNumber(10000000).WithBaseFee(0).TestObject; } [Test] public void should_add_peers() { _txPool = CreatePool(); - var peers = GetPeers(); + IDictionary peers = GetPeers(); foreach ((ITxPoolPeer peer, _) in peers) { @@ -78,7 +85,7 @@ public void should_add_peers() public void should_delete_peers() { _txPool = CreatePool(); - var peers = GetPeers(); + IDictionary peers = GetPeers(); foreach ((ITxPoolPeer peer, _) in peers) { @@ -152,7 +159,7 @@ public void should_add_valid_transactions_recovering_its_address() { _txPool = CreatePool(); Transaction tx = Build.A.Transaction - .WithGasLimit(_txGasLimit) + .WithGasLimit(TxGasLimit) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); tx.SenderAddress = null; @@ -166,7 +173,7 @@ public void should_reject_transactions_from_contract_address() { _txPool = CreatePool(); Transaction tx = Build.A.Transaction - .WithGasLimit(_txGasLimit) + .WithGasLimit(TxGasLimit) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); _stateProvider.InsertCode(TestItem.AddressA, "A"u8.ToArray(), _specProvider.GetSpec((ForkActivation)1)); @@ -192,7 +199,7 @@ public void should_accept_1559_transactions_only_when_eip1559_enabled([Values(fa .WithMaxPriorityFeePerGas(5.GWei()) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); - _blockTree.BlockAddedToMain += Raise.EventWith(_blockTree, new BlockReplacementEventArgs(Build.A.Block.WithGasLimit(10000000).TestObject)); + _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.WithGasLimit(10000000).TestObject)); AcceptTxResult result = txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); txPool.GetPendingTransactionsCount().Should().Be(eip1559Enabled ? 1 : 0); result.Should().Be(eip1559Enabled ? AcceptTxResult.Accepted : AcceptTxResult.Invalid); @@ -215,7 +222,7 @@ public void should_not_ignore_insufficient_funds_for_eip1559_transactions() var headProcessed = new ManualResetEventSlim(false); txPool.TxPoolHeadChanged += (s, a) => headProcessed.Set(); - _blockTree.BlockAddedToMain += Raise.EventWith(_blockTree, new BlockReplacementEventArgs(Build.A.Block.WithGasLimit(10000000).TestObject)); + _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.WithGasLimit(10000000).TestObject)); headProcessed.Wait(); result = txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); @@ -332,7 +339,7 @@ public void should_ignore_overflow_transactions() public void should_ignore_overflow_transactions_gas_premium_and_fee_cap() { ISpecProvider specProvider = GetLondonSpecProvider(); - var txPool = CreatePool(null, specProvider); + TxPool txPool = CreatePool(null, specProvider); Transaction tx = Build.A.Transaction.WithGasPrice(UInt256.MaxValue / Transaction.BaseTxGasCost) .WithGasLimit(Transaction.BaseTxGasCost) .WithValue(Transaction.BaseTxGasCost) @@ -366,7 +373,7 @@ public void should_reject_tx_if_max_size_is_exceeded([Values(true, false)] bool Transaction tx = Build.A.Transaction.SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); - var txPoolConfig = new TxPoolConfig() { MaxTxSize = tx.GetLength() - (sizeExceeded ? 1 : 0) }; + TxPoolConfig txPoolConfig = new() { MaxTxSize = tx.GetLength() - (sizeExceeded ? 1 : 0) }; _txPool = CreatePool(txPoolConfig); AcceptTxResult result = _txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); @@ -399,7 +406,7 @@ public void should_ignore_tx_gas_limit_exceeded() { _txPool = CreatePool(); Transaction tx = Build.A.Transaction - .WithGasLimit(_txGasLimit + 1) + .WithGasLimit(TxGasLimit + 1) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); AcceptTxResult result = _txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); @@ -558,7 +565,7 @@ public void should_not_add_tx_if_already_pending_lower_nonces_are_exhausting_bal { const int gasPrice = 10; const int value = 1; - int oneTxPrice = _txGasLimit * gasPrice + value; + int oneTxPrice = TxGasLimit * gasPrice + value; _txPool = CreatePool(); Transaction[] transactions = new Transaction[10]; @@ -570,7 +577,7 @@ public void should_not_add_tx_if_already_pending_lower_nonces_are_exhausting_bal .WithSenderAddress(TestItem.AddressA) .WithNonce((UInt256)i) .WithGasPrice((UInt256)gasPrice) - .WithGasLimit(_txGasLimit) + .WithGasLimit(TxGasLimit) .WithValue(value) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; }); @@ -591,7 +598,7 @@ public void should_not_count_txs_with_stale_nonces_when_calculating_cumulative_c { const int gasPrice = 10; const int value = 1; - int oneTxPrice = _txGasLimit * gasPrice + value; + int oneTxPrice = TxGasLimit * gasPrice + value; _txPool = CreatePool(); EnsureSenderBalance(TestItem.AddressA, (UInt256)(oneTxPrice * numberOfTxsPossibleToExecuteBeforeGasExhaustion)); @@ -604,7 +611,7 @@ public void should_not_count_txs_with_stale_nonces_when_calculating_cumulative_c .WithSenderAddress(TestItem.AddressA) .WithNonce((UInt256)i) .WithGasPrice((UInt256)gasPrice) - .WithGasLimit(_txGasLimit) + .WithGasLimit(TxGasLimit) .WithValue(value) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; }); @@ -631,7 +638,7 @@ public void should_add_tx_if_cost_of_executing_all_txs_in_bucket_exceeds_balance const int count = 10; const int gasPrice = 10; const int value = 1; - int oneTxPrice = _txGasLimit * gasPrice + value; + int oneTxPrice = TxGasLimit * gasPrice + value; _txPool = CreatePool(); Transaction[] transactions = new Transaction[count]; @@ -643,7 +650,7 @@ public void should_add_tx_if_cost_of_executing_all_txs_in_bucket_exceeds_balance .WithSenderAddress(TestItem.AddressA) .WithNonce((UInt256)i) .WithGasPrice((UInt256)gasPrice) - .WithGasLimit(_txGasLimit) + .WithGasLimit(TxGasLimit) .WithValue(value) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; }); @@ -777,7 +784,7 @@ public void should_remove_txHash_from_hashCache_when_tx_removed_because_of_txPoo var headProcessed = new ManualResetEventSlim(false); _txPool.TxPoolHeadChanged += (s, a) => headProcessed.Set(); - _blockTree.BlockAddedToMain += Raise.EventWith(new BlockReplacementEventArgs(Build.A.Block.TestObject)); + _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.TestObject)); headProcessed.Wait(); _txPool.IsKnown(higherPriorityTx.Hash).Should().BeTrue(); @@ -888,10 +895,11 @@ public void should_remove_stale_txs_from_persistent_transactions(int numberOfTxs BlockReplacementEventArgs blockReplacementEventArgs = new(block, null); ManualResetEvent manualResetEvent = new(false); - _txPool.RemoveTransaction(Arg.Do(t => manualResetEvent.Set())); - _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); - manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); + _txPool.TxPoolHeadChanged += (o, e) => manualResetEvent.Set(); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); + bool signaled = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(Timeout)); + signaled.Should().BeTrue("TxPoolHeadChanged event should have been raised"); // transactions[nonceIncludedInBlock] was included in the block and should be removed, as well as all lower nonces. _txPool.GetOwnPendingTransactions().Length.Should().Be(numberOfTxs - nonceIncludedInBlock - 1); } @@ -926,10 +934,11 @@ public void broadcaster_should_work_well_when_there_are_no_txs_in_persistent_txs BlockReplacementEventArgs blockReplacementEventArgs = new(block, null); ManualResetEvent manualResetEvent = new(false); - _txPool.RemoveTransaction(Arg.Do(t => manualResetEvent.Set())); - _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); - manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); + _txPool.TxPoolHeadChanged += (o, e) => manualResetEvent.Set(); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); + bool signaled = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(Timeout)); + signaled.Should().BeTrue("TxPoolHeadChanged event should have been raised"); _txPool.GetPendingTransactionsCount().Should().Be(1); _txPool.GetOwnPendingTransactions().Length.Should().Be(1); } @@ -960,7 +969,7 @@ public async Task should_remove_transactions_concurrently() public void should_add_transactions_concurrently() { int size = 3; - TxPoolConfig config = new() { GasLimit = _txGasLimit, Size = size }; + TxPoolConfig config = new() { GasLimit = TxGasLimit, Size = size }; _txPool = CreatePool(config); foreach (PrivateKey privateKey in TestItem.PrivateKeys) @@ -1007,10 +1016,11 @@ public void should_remove_tx_from_txPool_when_included_in_block(bool sameTransac BlockReplacementEventArgs blockReplacementEventArgs = new(block, null); ManualResetEvent manualResetEvent = new(false); - _txPool.RemoveTransaction(Arg.Do(t => manualResetEvent.Set())); - _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); - manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); + _txPool.TxPoolHeadChanged += (o, e) => manualResetEvent.Set(); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); + bool signaled = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(Timeout)); + signaled.Should().BeTrue("TxPoolHeadChanged event should have been raised"); _txPool.GetPendingTransactionsCount().Should().Be(0); } @@ -1030,10 +1040,11 @@ public void should_not_remove_txHash_from_hashCache_when_tx_removed_because_of_i BlockReplacementEventArgs blockReplacementEventArgs = new(block, null); ManualResetEvent manualResetEvent = new(false); - _txPool.RemoveTransaction(Arg.Do(t => manualResetEvent.Set())); - _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); - manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); + _txPool.TxPoolHeadChanged += (o, e) => manualResetEvent.Set(); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); + bool signaled = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(Timeout)); + signaled.Should().BeTrue("TxPoolHeadChanged event should have been raised"); foreach (Transaction transaction in transactions) { _txPool.IsKnown(transaction.Hash).Should().BeTrue(); @@ -1168,9 +1179,9 @@ public void should_accept_access_list_transactions_only_when_eip2930_enabled([Va { if (!eip2930Enabled) { - _blockTree.FindBestSuggestedHeader().Returns(Build.A.BlockHeader.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 1).TestObject); + _blockTree.BestSuggestedHeader = Build.A.BlockHeader.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 1).TestObject; Block block = Build.A.Block.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 2).TestObject; - _blockTree.Head.Returns(block); + _blockTree.Head = block; } _txPool = CreatePool(null, new TestSpecProvider(eip2930Enabled ? Berlin.Instance : Istanbul.Instance)); @@ -1189,9 +1200,9 @@ public void should_accept_only_when_synced([Values(false, true)] bool isSynced, { if (!isSynced) { - _blockTree.FindBestSuggestedHeader().Returns(Build.A.BlockHeader.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 1).TestObject); + _blockTree.BestSuggestedHeader = Build.A.BlockHeader.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 1).TestObject; Block block = Build.A.Block.WithNumber(1).TestObject; - _blockTree.Head.Returns(block); + _blockTree.Head = block; } _txPool = CreatePool(null, new TestSpecProvider(Berlin.Instance)); @@ -1488,6 +1499,8 @@ public void should_increase_nonce_when_transaction_not_included_in_txPool_but_br } [Test] + [Retry(3)] + [NonParallelizable] public void should_include_transaction_after_removal() { ISpecProvider specProvider = GetLondonSpecProvider(); @@ -1523,12 +1536,10 @@ public void should_include_transaction_after_removal() _txPool.SubmitTx(expensiveTx2, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); // Rise new block event to cleanup cash and remove one expensive tx - _blockTree.BlockAddedToMain += - Raise.Event>(this, - new BlockReplacementEventArgs(Build.A.Block.WithTransactions(expensiveTx1).TestObject)); + _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.WithTransactions(expensiveTx1).TestObject)); // Wait for event processing and send txA again => should be Accepted - Assert.That(() => _txPool.SubmitTx(txA, TxHandlingOptions.None), Is.EqualTo(AcceptTxResult.Accepted).After(100, 10)); + Assert.That(() => _txPool.SubmitTx(txA, TxHandlingOptions.None), Is.EqualTo(AcceptTxResult.Accepted).After(Timeout, 10)); } [TestCase(true, 1, 1, true)] @@ -2245,7 +2256,7 @@ private TxPool CreatePool( _ethereumEcdsa, txStorage, _headInfo, - config ?? new TxPoolConfig() { GasLimit = _txGasLimit }, + config ?? new TxPoolConfig() { GasLimit = TxGasLimit }, new TxValidator(_specProvider.ChainId), _logManager, transactionComparerProvider.GetDefaultComparer(), @@ -2263,33 +2274,13 @@ private ITxPoolPeer GetPeer(PublicKey publicKey) return peer; } - private static ISpecProvider GetLondonSpecProvider() - { - var specProvider = Substitute.For(); - specProvider.GetSpec(Arg.Any()).Returns(London.Instance); - return specProvider; - } + private static ISpecProvider GetLondonSpecProvider() => new TestSpecProvider(London.Instance); - private static ISpecProvider GetCancunSpecProvider() - { - var specProvider = Substitute.For(); - specProvider.GetSpec(Arg.Any()).Returns(Cancun.Instance); - return specProvider; - } + private static ISpecProvider GetCancunSpecProvider() => new TestSpecProvider(Cancun.Instance); - private static ISpecProvider GetPragueSpecProvider() - { - var specProvider = Substitute.For(); - specProvider.GetSpec(Arg.Any()).Returns(Prague.Instance); - return specProvider; - } + private static ISpecProvider GetPragueSpecProvider() => new TestSpecProvider(Prague.Instance); - private static ISpecProvider GetOsakaSpecProvider() - { - var specProvider = Substitute.For(); - specProvider.GetSpec(Arg.Any()).Returns(Osaka.Instance); - return specProvider; - } + private static ISpecProvider GetOsakaSpecProvider() => new TestSpecProvider(Osaka.Instance); private Transaction[] AddTransactionsToPool(bool sameTransactionSenderPerPeer = true, bool sameNoncePerPeer = false, int transactionsPerPeer = 10) { @@ -2397,10 +2388,10 @@ private async Task RaiseBlockAddedToMainAndWaitForTransactions(int txCount, Bloc SemaphoreSlim semaphoreSlim = new(0, txCount); _txPool.NewPending += (o, e) => semaphoreSlim.Release(); - _blockTree.BlockAddedToMain += Raise.EventWith(blockReplacementEventArgs); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); for (int i = 0; i < txCount; i++) { - await semaphoreSlim.WaitAsync(10); + await semaphoreSlim.WaitAsync(1000); } } @@ -2417,7 +2408,7 @@ private async Task RaiseBlockAddedToMainAndWaitForNewHead(Block block, Block pre e => e.Number == block.Number ); - _blockTree.BlockAddedToMain += Raise.EventWith(blockReplacementEventArgs); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); await waitTask; } diff --git a/src/Nethermind/Nethermind.TxPool/BlobTxStorage.cs b/src/Nethermind/Nethermind.TxPool/BlobTxStorage.cs index cc76e38f619e..d76d0f620a0e 100644 --- a/src/Nethermind/Nethermind.TxPool/BlobTxStorage.cs +++ b/src/Nethermind/Nethermind.TxPool/BlobTxStorage.cs @@ -3,6 +3,7 @@ using System; using System.Buffers.Binary; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Nethermind.Core; @@ -17,7 +18,9 @@ namespace Nethermind.TxPool; public class BlobTxStorage : IBlobTxStorage { + private const int MaxPooledKeys = 128; private static readonly TxDecoder _txDecoder = TxDecoder.Instance; + private readonly ConcurrentQueue _keyPool = new(); private readonly IDb _fullBlobTxsDb; private readonly IDb _lightBlobTxsDb; private readonly IDb _processedBlobTxsDb; @@ -45,6 +48,35 @@ public bool TryGet(in ValueHash256 hash, Address sender, in UInt256 timestamp, [ return TryDecodeFullTx(txBytes, sender, out transaction); } + public int TryGetMany(TxLookupKey[] keys, int count, Transaction?[] results) + { + if (count == 0) return 0; + + // Outer array must be exact-size for the IDb indexer (uses keys.Length). + // Inner byte[64] keys are pooled via ConcurrentQueue to avoid per-call allocations. + byte[][] dbKeys = new byte[count][]; + for (int i = 0; i < dbKeys.Length; i++) + { + byte[] key = RentKey(); + GetHashPrefixedByTimestamp(keys[i].Timestamp, keys[i].Hash, key); + dbKeys[i] = key; + } + + KeyValuePair[] dbResults = _fullBlobTxsDb[dbKeys]; + + int found = 0; + for (int i = 0; i < count; i++) + { + if (TryDecodeFullTx(dbResults[i].Value, keys[i].Sender, out results[i])) + found++; + } + + for (int i = 0; i < count; i++) + ReturnKey(dbKeys[i]); + + return found; + } + public IEnumerable GetAll() { foreach (byte[] txBytes in _lightBlobTxsDb.GetAllValues()) @@ -133,6 +165,14 @@ private static bool TryDecodeLightTx(byte[]? txBytes, out LightTransaction? ligh return false; } + private byte[] RentKey() => _keyPool.TryDequeue(out byte[]? key) ? key : new byte[64]; + + private void ReturnKey(byte[] key) + { + if (_keyPool.Count < MaxPooledKeys) + _keyPool.Enqueue(key); + } + private static void GetHashPrefixedByTimestamp(UInt256 timestamp, ValueHash256 hash, Span txHashPrefixed) { timestamp.WriteBigEndian(txHashPrefixed); diff --git a/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs b/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs index cd552b82e484..c7b49199a4aa 100644 --- a/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs +++ b/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs @@ -75,20 +75,46 @@ private bool TryGetBlobAndProof( return false; } - public int GetBlobCounts(byte[][] requestedBlobVersionedHashes) + public virtual int TryGetBlobsAndProofsV1( + byte[][] requestedBlobVersionedHashes, + byte[]?[] blobs, + ReadOnlyMemory[] proofs) { - using var lockRelease = Lock.Acquire(); - int count = 0; + using McsLock.Disposable lockRelease = Lock.Acquire(); + int found = 0; - foreach (byte[] requestedBlobVersionedHash in requestedBlobVersionedHashes) + for (int i = 0; i < requestedBlobVersionedHashes.Length; i++) { - if (BlobIndex.ContainsKey(requestedBlobVersionedHash)) + byte[] requestedBlobVersionedHash = requestedBlobVersionedHashes[i]; + if (!BlobIndex.TryGetValue(requestedBlobVersionedHash, out List? txHashes)) + continue; + + foreach (Hash256 hash in CollectionsMarshal.AsSpan(txHashes)) { - count += 1; + if (!TryGetValueNonLocked(hash, out Transaction? blobTx) + || blobTx.BlobVersionedHashes is not { Length: > 0 }) + continue; + + bool matched = false; + for (int indexOfBlob = 0; indexOfBlob < blobTx.BlobVersionedHashes.Length; indexOfBlob++) + { + if (Bytes.AreEqual(blobTx.BlobVersionedHashes[indexOfBlob], requestedBlobVersionedHash) + && blobTx.NetworkWrapper is ShardBlobNetworkWrapper { Version: ProofVersion.V1 } wrapper) + { + blobs[i] = wrapper.Blobs[indexOfBlob]; + proofs[i] = new ReadOnlyMemory( + wrapper.Proofs, + Ckzg.CellsPerExtBlob * indexOfBlob, + Ckzg.CellsPerExtBlob); + found++; + matched = true; + break; + } + } + if (matched) break; } } - - return count; + return found; } protected override bool InsertCore(ValueHash256 key, Transaction value, AddressAsKey groupKey) diff --git a/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs b/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs index 7b4e1e4b1235..18647a7f5b7c 100644 --- a/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs +++ b/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs @@ -2,12 +2,17 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using CkzgLib; using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Threading; using Nethermind.Logging; namespace Nethermind.TxPool.Collections; @@ -89,6 +94,127 @@ protected override bool TryGetValueNonLocked(ValueHash256 hash, [NotNullWhen(tru return false; } + public override int TryGetBlobsAndProofsV1( + byte[][] requestedBlobVersionedHashes, + byte[]?[] blobs, + ReadOnlyMemory[] proofs) + { + int found = 0; + int missCount = 0; + + // Rent arrays for Phase 2 (DB lookup of cache misses). + // Sized to 4x request length to accommodate multiple candidate tx hashes per blob + // (e.g. when the same blob versioned hash appears in multiple transactions). + int maxMisses = requestedBlobVersionedHashes.Length * 4; + TxLookupKey[] dbKeys = ArrayPool.Shared.Rent(maxMisses); + int[] missOutputIndex = ArrayPool.Shared.Rent(maxMisses); + int[] missBlobIndex = ArrayPool.Shared.Rent(maxMisses); + try + { + // Phase 1: Under lock — in-memory lookups only + using (McsLock.Disposable lockRelease = Lock.Acquire()) + { + for (int i = 0; i < requestedBlobVersionedHashes.Length; i++) + { + byte[] requestedBlobVersionedHash = requestedBlobVersionedHashes[i]; + if (!BlobIndex.TryGetValue(requestedBlobVersionedHash, out List? txHashes)) + continue; + + foreach (Hash256 hash in CollectionsMarshal.AsSpan(txHashes)) + { + if (!base.TryGetValueNonLocked(hash, out Transaction? lightTx) + || lightTx is not LightTransaction lt + || lt.ProofVersion != ProofVersion.V1 + || lightTx.BlobVersionedHashes is not { Length: > 0 }) + continue; + + int indexOfBlob = -1; + for (int b = 0; b < lightTx.BlobVersionedHashes.Length; b++) + { + if (Bytes.AreEqual(lightTx.BlobVersionedHashes[b], requestedBlobVersionedHash)) + { + indexOfBlob = b; + break; + } + } + if (indexOfBlob < 0) + continue; + + // Try cache first — on hit, populate and stop searching for this blob hash + if (_blobTxCache.TryGet(hash, out Transaction? cachedTx) + && cachedTx.NetworkWrapper is ShardBlobNetworkWrapper { Version: ProofVersion.V1 } cachedWrapper) + { + blobs[i] = cachedWrapper.Blobs[indexOfBlob]; + proofs[i] = new ReadOnlyMemory( + cachedWrapper.Proofs, + Ckzg.CellsPerExtBlob * indexOfBlob, + Ckzg.CellsPerExtBlob); + found++; + break; + } + + // Cache miss — record for Phase 2 DB lookup and continue to try + // remaining tx hashes so that if this candidate is missing from DB, + // later candidates can still satisfy the request. + if (missCount < maxMisses) + { + dbKeys[missCount] = new TxLookupKey(hash, lightTx.SenderAddress!, lightTx.Timestamp); + missOutputIndex[missCount] = i; + missBlobIndex[missCount] = indexOfBlob; + missCount++; + } + } + } + } + + // Phase 2: Outside lock — single RocksDB MultiGet for all misses + if (missCount > 0) + { + Transaction?[] dbResults = ArrayPool.Shared.Rent(missCount); + try + { + Array.Clear(dbResults, 0, missCount); + _blobTxStorage.TryGetMany(dbKeys, missCount, dbResults); + + for (int m = 0; m < missCount; m++) + { + int outIdx = missOutputIndex[m]; + + // Skip if this output slot was already filled by a cache hit or earlier candidate + if (blobs[outIdx] is not null) + continue; + + Transaction? fullTx = dbResults[m]; + if (fullTx?.NetworkWrapper is ShardBlobNetworkWrapper { Version: ProofVersion.V1 } wrapper) + { + int blobIdx = missBlobIndex[m]; + blobs[outIdx] = wrapper.Blobs[blobIdx]; + proofs[outIdx] = new ReadOnlyMemory( + wrapper.Proofs, + Ckzg.CellsPerExtBlob * blobIdx, + Ckzg.CellsPerExtBlob); + found++; + + _blobTxCache.Set(dbKeys[m].Hash, fullTx); + } + } + } + finally + { + ArrayPool.Shared.Return(dbResults, clearArray: true); + } + } + + return found; + } + finally + { + ArrayPool.Shared.Return(dbKeys, clearArray: true); + ArrayPool.Shared.Return(missOutputIndex); + ArrayPool.Shared.Return(missBlobIndex); + } + } + protected override bool Remove(ValueHash256 hash, out Transaction? tx) { if (base.Remove(hash, out tx)) diff --git a/src/Nethermind/Nethermind.TxPool/ITxPool.cs b/src/Nethermind/Nethermind.TxPool/ITxPool.cs index 4e6b9a0d7f95..9759c6bd9f8f 100644 --- a/src/Nethermind/Nethermind.TxPool/ITxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/ITxPool.cs @@ -53,7 +53,8 @@ bool TryGetBlobAndProofV0(byte[] blobVersionedHash, bool TryGetBlobAndProofV1(byte[] blobVersionedHash, [NotNullWhen(true)] out byte[]? blob, [NotNullWhen(true)] out byte[][]? cellProofs); - int GetBlobCounts(byte[][] blobVersionedHashes); + int TryGetBlobsAndProofsV1(byte[][] requestedBlobVersionedHashes, + byte[]?[] blobs, ReadOnlyMemory[] proofs); UInt256 GetLatestPendingNonce(Address address); event EventHandler NewDiscovered; event EventHandler NewPending; diff --git a/src/Nethermind/Nethermind.TxPool/ITxStorage.cs b/src/Nethermind/Nethermind.TxPool/ITxStorage.cs index 251d723b9e05..beb3268f0bd8 100644 --- a/src/Nethermind/Nethermind.TxPool/ITxStorage.cs +++ b/src/Nethermind/Nethermind.TxPool/ITxStorage.cs @@ -9,9 +9,12 @@ namespace Nethermind.TxPool; +public readonly record struct TxLookupKey(ValueHash256 Hash, Address Sender, UInt256 Timestamp); + public interface ITxStorage { bool TryGet(in ValueHash256 hash, Address sender, in UInt256 timestamp, [NotNullWhen(true)] out Transaction? transaction); + int TryGetMany(TxLookupKey[] keys, int count, Transaction?[] results); IEnumerable GetAll(); void Add(Transaction transaction); void Delete(in ValueHash256 hash, in UInt256 timestamp); diff --git a/src/Nethermind/Nethermind.TxPool/NullBlobTxStorage.cs b/src/Nethermind/Nethermind.TxPool/NullBlobTxStorage.cs index a3ed71f0067a..e7bf5bd0f406 100644 --- a/src/Nethermind/Nethermind.TxPool/NullBlobTxStorage.cs +++ b/src/Nethermind/Nethermind.TxPool/NullBlobTxStorage.cs @@ -21,6 +21,8 @@ public bool TryGet(in ValueHash256 hash, Address sender, in UInt256 timestamp, [ return false; } + public int TryGetMany(TxLookupKey[] keys, int count, Transaction?[] results) => 0; + public IEnumerable GetAll() => Array.Empty(); public void Add(Transaction transaction) { } diff --git a/src/Nethermind/Nethermind.TxPool/NullTxPool.cs b/src/Nethermind/Nethermind.TxPool/NullTxPool.cs index 9ab7f22ab6b8..40c78fd606c4 100644 --- a/src/Nethermind/Nethermind.TxPool/NullTxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/NullTxPool.cs @@ -83,7 +83,8 @@ public bool TryGetBlobAndProofV1(byte[] blobVersionedHash, return false; } - public int GetBlobCounts(byte[][] blobVersionedHashes) => 0; + public int TryGetBlobsAndProofsV1(byte[][] requestedBlobVersionedHashes, + byte[]?[] blobs, ReadOnlyMemory[] proofs) => 0; public UInt256 GetLatestPendingNonce(Address address) => 0; diff --git a/src/Nethermind/Nethermind.TxPool/RetryCache.cs b/src/Nethermind/Nethermind.TxPool/RetryCache.cs index 4bd9b40626c9..9a797f733b86 100644 --- a/src/Nethermind/Nethermind.TxPool/RetryCache.cs +++ b/src/Nethermind/Nethermind.TxPool/RetryCache.cs @@ -122,11 +122,7 @@ public AnnounceResult Announced(in TResourceId resourceId, IMessageHandler - { - return AnnounceAdd(resourceId, retryHandler, out result); - }, _announceUpdate, handler); - + _retryRequests.AddOrUpdate(resourceId, (resourceId, retryHandler) => AnnounceAdd(resourceId, retryHandler, out result), _announceUpdate, handler); return result; } diff --git a/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs b/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs index 2c3ec6bbca67..9b481c5b10fd 100644 --- a/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs +++ b/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs @@ -68,21 +68,24 @@ internal class TxBroadcaster : IDisposable private readonly TimeSpan _minTimeBetweenPersistedTxBroadcast = TimeSpan.FromSeconds(1); private readonly ILogger _logger; + private readonly ITimestamper _timestamper; public TxBroadcaster(IComparer comparer, ITimerFactory timerFactory, ITxPoolConfig txPoolConfig, IChainHeadInfoProvider chainHeadInfoProvider, ILogManager? logManager, - ITxGossipPolicy? transactionsGossipPolicy = null) + ITxGossipPolicy? transactionsGossipPolicy = null, + ITimestamper? timestamper = null) { _txPoolConfig = txPoolConfig; _headInfo = chainHeadInfoProvider; + _timestamper = timestamper ?? Timestamper.Default; _txGossipPolicy = transactionsGossipPolicy ?? ShouldGossip.Instance; // Allocate closure once _gossipFilter = _txGossipPolicy.ShouldGossipTransaction; _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - _persistentTxs = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, comparer, logManager); + _persistentTxs = new TxDistinctSortedPool(txPoolConfig.Size, comparer, logManager); _accumulatedTemporaryTxs = new ResettableList(512, 4); _txsToSend = new ResettableList(512, 4); @@ -172,7 +175,7 @@ internal void BroadcastPersistentTxs() return; } - DateTimeOffset now = DateTimeOffset.UtcNow; + DateTimeOffset now = _timestamper.UtcNowOffset; if (_lastPersistedTxBroadcast + _minTimeBetweenPersistedTxBroadcast > now) { if (_logger.IsTrace) _logger.Trace($"Minimum time between persistent tx broadcast not reached."); diff --git a/src/Nethermind/Nethermind.TxPool/TxNonceTxPoolReserveSealer.cs b/src/Nethermind/Nethermind.TxPool/TxNonceTxPoolReserveSealer.cs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.NonceInfo.cs b/src/Nethermind/Nethermind.TxPool/TxPool.NonceInfo.cs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 20aaa898432b..4846479dd29f 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -135,7 +135,7 @@ public TxPool(IEthereumEcdsa ecdsa, _broadcaster = new TxBroadcaster(comparer, TimerFactory.Default, txPoolConfig, chainHeadInfoProvider, logManager, transactionsGossipPolicy); TxPoolHeadChanged += _broadcaster.OnNewHead; - _transactions = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, comparer, logManager); + _transactions = new TxDistinctSortedPool(txPoolConfig.Size, comparer, logManager); _transactions.Removed += OnRemovedTx; _blobTransactions = txPoolConfig.BlobsSupport.IsPersistentStorage() @@ -225,8 +225,9 @@ public bool TryGetBlobAndProofV1(byte[] blobVersionedHash, [NotNullWhen(true)] out byte[][]? cellProofs) => _blobTransactions.TryGetBlobAndProofV1(blobVersionedHash, out blob, out cellProofs); - public int GetBlobCounts(byte[][] blobVersionedHashes) - => _blobTransactions.GetBlobCounts(blobVersionedHashes); + public int TryGetBlobsAndProofsV1(byte[][] requestedBlobVersionedHashes, + byte[]?[] blobs, ReadOnlyMemory[] proofs) + => _blobTransactions.TryGetBlobsAndProofsV1(requestedBlobVersionedHashes, blobs, proofs); private void OnRemovedTx(object? sender, SortedPool.SortedPoolRemovedEventArgs args) { diff --git a/src/Nethermind/Nethermind.Xdc.Test/BlockInfoTests.cs b/src/Nethermind/Nethermind.Xdc.Test/BlockInfoTests.cs index aba2ee0d65c8..ced27d8d1c34 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/BlockInfoTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/BlockInfoTests.cs @@ -8,7 +8,6 @@ namespace Nethermind.Xdc.Test; -[Parallelizable(ParallelScope.All)] internal class BlockInfoTests { [Test] diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs index f41e2e596293..18c46daf5ecb 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs @@ -494,8 +494,8 @@ public async Task SimulateVoting() VoteDecoder voteDecoder = new VoteDecoder(); - var newHeadWaitHandle = new TaskCompletionSource(); - var newRoundWaitHandle = new TaskCompletionSource(); + var newHeadWaitHandle = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var newRoundWaitHandle = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); XdcContext.NewRoundSetEvent += OnNewRound; try { @@ -545,7 +545,7 @@ public async Task TriggerBlockProposal() var head = (XdcBlockHeader)BlockTree.Head!.Header; var spec = SpecProvider.GetXdcSpec(head, XdcContext.CurrentRound); - var newHeadWaitHandle = new TaskCompletionSource(); + var newHeadWaitHandle = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); BlockTree.NewHeadBlock += OnNewHead; try diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs index fe51092ef36d..277fc1c5d9b3 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs @@ -13,7 +13,6 @@ namespace Nethermind.Xdc.Test; -[Parallelizable(ParallelScope.All)] internal class ProposedBlockTests { [Test] diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/RewardTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/RewardTests.cs index 757734e69090..becec46623a9 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/RewardTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/RewardTests.cs @@ -117,12 +117,24 @@ await chain.AddBlock(BuildSigningTx( long current = chain.BlockTree.Head!.Number; await chain.AddBlocks((int)(targetIncludingBlockForSecondSign - current - 1)); // move so AddBlockMayHaveExtraTx produces the target + // For 4E reward calculation, the masternodes come from the second epoch switch found + // when walking backwards from 4E. The signed header (3E - mergeSignRange) is in the + // range [2E+1, 3E), so its epoch switch info provides the relevant masternodes. + // Use a masternode from that epoch to ensure the signature is counted. + EpochSwitchInfo? epochSwitchInfoFor2E = chain.EpochSwitchManager.GetEpochSwitchInfo(signedHeader3EMinusMerge); + Assert.That(epochSwitchInfoFor2E, Is.Not.Null); + PrivateKey signerForPart2 = chain.MasterNodeCandidates.First(k => k.Address == epochSwitchInfoFor2E!.Masternodes[0]); + + // Set the chain's signer to our chosen masternode - required because + // SignTransactionFilter rejects signing txs from non-current-signers + chain.Signer.SetSigner(signerForPart2); + await chain.AddBlock(BuildSigningTx( spec, signedHeader3EMinusMerge.Number, signedHeader3EMinusMerge.Hash ?? signedHeader3EMinusMerge.CalculateHash().ToHash256(), - chain.Signer.Key!, - (long)chain.ReadOnlyState.GetNonce(chain.Signer.Address))); + signerForPart2, + (long)chain.ReadOnlyState.GetNonce(signerForPart2.Address))); // --- Evaluate rewards at checkpoint (4E) --- long checkpoint4E = 4 * epochLength; diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/SyncInfoDecoderTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/SyncInfoDecoderTests.cs new file mode 100644 index 000000000000..db199e8b09e4 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/SyncInfoDecoderTests.cs @@ -0,0 +1,175 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.RLP; +using Nethermind.Xdc.Types; +using NUnit.Framework; +using System.Collections; + +namespace Nethermind.Xdc.Test; + +[TestFixture, Parallelizable(ParallelScope.All)] +public class SyncInfoDecoderTests +{ + public static IEnumerable SyncInfoCases + { + get + { + yield return new TestCaseData( + new SyncInfo( + new QuorumCertificate( + new BlockRoundInfo(Hash256.Zero, 1, 1), + [new Signature(new byte[64], 0), new Signature(new byte[64], 0)], + 0 + ), + new TimeoutCertificate( + 1, + [new Signature(new byte[64], 0), new Signature(new byte[64], 0)], + 0 + ) + ), + true + ); + + yield return new TestCaseData( + new SyncInfo( + new QuorumCertificate( + new BlockRoundInfo(Hash256.Zero, 1, 1), + [], + 0 + ), + new TimeoutCertificate(1, [], 0) + ), + false + ); + + yield return new TestCaseData( + new SyncInfo( + new QuorumCertificate( + new BlockRoundInfo(Hash256.Zero, ulong.MaxValue, long.MaxValue), + [], + ulong.MaxValue + ), + new TimeoutCertificate(ulong.MaxValue, [], ulong.MaxValue) + ), + true + ); + } + } + + [TestCaseSource(nameof(SyncInfoCases))] + public void EncodeDecode_RoundTrip_Matches_AllFields(SyncInfo syncInfo, bool useRlpStream) + { + var decoder = new SyncInfoDecoder(); + + Rlp encoded = decoder.Encode(syncInfo); + var stream = new RlpStream(encoded.Bytes); + SyncInfo decoded; + + if (useRlpStream) + { + decoded = decoder.Decode(stream); + } + else + { + Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(stream.Data.AsSpan()); + decoded = decoder.Decode(ref decoderContext); + } + + decoded.Should().BeEquivalentTo(syncInfo); + } + + [Test] + public void Encode_UseBothRlpStreamAndValueDecoderContext_IsEquivalentAfterReencoding() + { + SyncInfo syncInfo = new( + new QuorumCertificate( + new BlockRoundInfo(Hash256.Zero, 1, 1), + [new Signature(new byte[64], 0), new Signature(new byte[64], 0), new Signature(new byte[64], 0)], + 0 + ), + new TimeoutCertificate( + 1, + [new Signature(new byte[64], 0), new Signature(new byte[64], 0)], + 0 + ) + ); + + SyncInfoDecoder decoder = new(); + RlpStream stream = new RlpStream(decoder.GetLength(syncInfo)); + decoder.Encode(stream, syncInfo); + stream.Position = 0; + + // Decode with RlpStream + SyncInfo decodedStream = decoder.Decode(stream); + stream.Position = 0; + + // Decode with ValueDecoderContext + Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(stream.Data.AsSpan()); + SyncInfo decodedContext = decoder.Decode(ref decoderContext); + + // Both should be equivalent to original + decodedStream.Should().BeEquivalentTo(syncInfo); + decodedContext.Should().BeEquivalentTo(syncInfo); + decodedStream.Should().BeEquivalentTo(decodedContext); + } + + [Test] + public void TotalLength_Equals_GetLength() + { + SyncInfo syncInfo = new( + new QuorumCertificate( + new BlockRoundInfo(Hash256.Zero, 42, 42), + [new Signature(new byte[64], 0)], + 10 + ), + new TimeoutCertificate( + 41, + [new Signature(new byte[64], 1)], + 10 + ) + ); + + var decoder = new SyncInfoDecoder(); + Rlp encoded = decoder.Encode(syncInfo); + + int expectedTotal = decoder.GetLength(syncInfo, RlpBehaviors.None); + Assert.That(encoded.Bytes.Length, Is.EqualTo(expectedTotal), + "Encoded total length should match GetLength()."); + } + + [Test] + public void Encode_Null_ReturnsEmptySequence() + { + var decoder = new SyncInfoDecoder(); + + Rlp encoded = decoder.Encode(null!); + + Assert.That(encoded, Is.EqualTo(Rlp.OfEmptySequence)); + } + + [Test] + public void Decode_Null_ReturnsNull() + { + var decoder = new SyncInfoDecoder(); + var stream = new RlpStream(Rlp.OfEmptySequence.Bytes); + + SyncInfo decoded = decoder.Decode(stream); + + Assert.That(decoded, Is.Null); + } + + [Test] + public void Decode_EmptyByteArray_ValueDecoderContext_ReturnsNull() + { + var decoder = new SyncInfoDecoder(); + Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(Rlp.OfEmptySequence.Bytes); + + SyncInfo decoded = decoder.Decode(ref decoderContext); + + Assert.That(decoded, Is.Null); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs index 02a8317d50ff..4b545ba8cd02 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs @@ -8,7 +8,6 @@ namespace Nethermind.Xdc.Test; -[Parallelizable(ParallelScope.All)] internal class XdcReorgModuleTests { [Test] diff --git a/src/Nethermind/Nethermind.Xdc/Errors/QuorumCertificateException.cs b/src/Nethermind/Nethermind.Xdc/Errors/QuorumCertificateException.cs deleted file mode 100644 index 09cf944eeb3f..000000000000 --- a/src/Nethermind/Nethermind.Xdc/Errors/QuorumCertificateException.cs +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Blockchain; -using Nethermind.Xdc.Types; - -namespace Nethermind.Xdc.Errors; - -internal class QuorumCertificateException(QuorumCertificate certificate, string message) : BlockchainException(message) -{ - public QuorumCertificate Certificate { get; } = certificate; -} diff --git a/src/Nethermind/Nethermind.Xdc/IXdcSealer.cs b/src/Nethermind/Nethermind.Xdc/IXdcSealer.cs deleted file mode 100644 index cab6b4eb0b52..000000000000 --- a/src/Nethermind/Nethermind.Xdc/IXdcSealer.cs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Consensus; - -namespace Nethermind.Xdc; - -internal interface IXdcSealer : ISealer -{ - bool CanSeal(ulong round, XdcBlockHeader parentHash); -} diff --git a/src/Nethermind/Nethermind.Xdc/RLP/SyncInfoDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/SyncInfoDecoder.cs new file mode 100644 index 000000000000..0ebaf9d39ad8 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/RLP/SyncInfoDecoder.cs @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.RLP; +using Nethermind.Xdc.Types; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Nethermind.Xdc; + +internal class SyncInfoDecoder : RlpValueDecoder +{ + private readonly QuorumCertificateDecoder _quorumCertificateDecoder = new(); + private readonly TimeoutCertificateDecoder _timeoutCertificateDecoder = new(); + + protected override SyncInfo DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (decoderContext.IsNextItemNull()) + return null; + + int sequenceLength = decoderContext.ReadSequenceLength(); + int endPosition = decoderContext.Position + sequenceLength; + + QuorumCertificate highestQuorumCert = _quorumCertificateDecoder.Decode(ref decoderContext, rlpBehaviors); + TimeoutCertificate highestTimeoutCert = _timeoutCertificateDecoder.Decode(ref decoderContext, rlpBehaviors); + + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + decoderContext.Check(endPosition); + } + + return new SyncInfo(highestQuorumCert, highestTimeoutCert); + } + + protected override SyncInfo DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (rlpStream.IsNextItemNull()) + return null; + + int sequenceLength = rlpStream.ReadSequenceLength(); + int endPosition = rlpStream.Position + sequenceLength; + + QuorumCertificate highestQuorumCert = _quorumCertificateDecoder.Decode(rlpStream, rlpBehaviors); + TimeoutCertificate highestTimeoutCert = _timeoutCertificateDecoder.Decode(rlpStream, rlpBehaviors); + + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + rlpStream.Check(endPosition); + } + + return new SyncInfo(highestQuorumCert, highestTimeoutCert); + } + + public Rlp Encode(SyncInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + return Rlp.OfEmptySequence; + + RlpStream rlpStream = new(GetLength(item, rlpBehaviors)); + Encode(rlpStream, item, rlpBehaviors); + + return new Rlp(rlpStream.Data.ToArray()); + } + + public override void Encode(RlpStream stream, SyncInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + { + stream.EncodeNullObject(); + return; + } + + stream.StartSequence(GetContentLength(item, rlpBehaviors)); + _quorumCertificateDecoder.Encode(stream, item.HighestQuorumCert, rlpBehaviors); + _timeoutCertificateDecoder.Encode(stream, item.HighestTimeoutCert, rlpBehaviors); + } + + public override int GetLength(SyncInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); + } + + public int GetContentLength(SyncInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + return 0; + + return _quorumCertificateDecoder.GetLength(item.HighestQuorumCert, rlpBehaviors) + + _timeoutCertificateDecoder.GetLength(item.HighestTimeoutCert, rlpBehaviors); + } +} diff --git a/src/Nethermind/Nethermind.Xdc/RLP/VoteDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/VoteDecoder.cs index 9116c18635a0..ff3b6504f5af 100644 --- a/src/Nethermind/Nethermind.Xdc/RLP/VoteDecoder.cs +++ b/src/Nethermind/Nethermind.Xdc/RLP/VoteDecoder.cs @@ -93,7 +93,7 @@ public override int GetLength(Vote item, RlpBehaviors rlpBehaviors) private int GetContentLength(Vote item, RlpBehaviors rlpBehaviors) { return - (rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing ? Rlp.LengthOfSequence(Signature.Size) : 0 + ((rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing ? Rlp.LengthOfSequence(Signature.Size) : 0) + Rlp.LengthOf(item.GapNumber) + _xdcBlockInfoDecoder.GetLength(item.ProposedBlockInfo, rlpBehaviors); } diff --git a/src/Nethermind/Nethermind.Xdc/XdcDbNames.cs b/src/Nethermind/Nethermind.Xdc/XdcDbNames.cs deleted file mode 100644 index 720beedcc121..000000000000 --- a/src/Nethermind/Nethermind.Xdc/XdcDbNames.cs +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Xdc; - -internal class XdcDbNames -{ - public static long HighestQcKey = 2000; - public static long HighestTimeoutCertKey = 2001; - public static long LockQcKey = 2002; - public static long HighestVotedRoundKey = 2004; -} diff --git a/src/Nethermind/Nethermind.Xdc/XdcModule.cs b/src/Nethermind/Nethermind.Xdc/XdcModule.cs index ae9d992edb66..10286cd98407 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcModule.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcModule.cs @@ -25,6 +25,8 @@ using Nethermind.Xdc.TxPool; using Nethermind.Api.Steps; using Nethermind.Synchronization; +using Nethermind.Synchronization.FastSync; +using Nethermind.Synchronization.ParallelSync; namespace Nethermind.Xdc; @@ -88,8 +90,9 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddSingleton() - // beacon sync strategy + // sync .AddSingleton() + .AddSingleton, XdcStateSyncAllocationStrategyFactory>() .AddSingleton() diff --git a/src/Nethermind/Nethermind.Xdc/XdcStateSyncAllocationStrategyFactory.cs b/src/Nethermind/Nethermind.Xdc/XdcStateSyncAllocationStrategyFactory.cs new file mode 100644 index 000000000000..74277a46fbbf --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcStateSyncAllocationStrategyFactory.cs @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Stats; +using Nethermind.Synchronization.FastSync; +using Nethermind.Synchronization.ParallelSync; +using Nethermind.Synchronization.Peers; +using Nethermind.Synchronization.Peers.AllocationStrategies; +using Nethermind.Synchronization.StateSync; + +namespace Nethermind.Xdc; + +public class XdcStateSyncAllocationStrategyFactory : StaticPeerAllocationStrategyFactory +{ + private static readonly IPeerAllocationStrategy DefaultStrategy = + new AllocationStrategy(new BySpeedStrategy(TransferSpeedType.NodeData, true)); + + public XdcStateSyncAllocationStrategyFactory() : base(DefaultStrategy) + { + } + + internal class AllocationStrategy : FilterPeerAllocationStrategy + { + public AllocationStrategy(IPeerAllocationStrategy strategy) : base(strategy) + { + } + + protected override bool Filter(PeerInfo peerInfo) + { + return peerInfo.CanGetSnapData() || peerInfo.SyncPeer.ProtocolVersion == 100; + } + } +} + diff --git a/src/Nethermind/Nethermind.slnx b/src/Nethermind/Nethermind.slnx index c64f1a920b75..6298a9f62d2f 100644 --- a/src/Nethermind/Nethermind.slnx +++ b/src/Nethermind/Nethermind.slnx @@ -31,6 +31,7 @@ + diff --git a/tools/JitAsm/DisassemblyParser.cs b/tools/JitAsm/DisassemblyParser.cs new file mode 100644 index 000000000000..dcbb9687af8f --- /dev/null +++ b/tools/JitAsm/DisassemblyParser.cs @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text; +using System.Text.RegularExpressions; + +namespace JitAsm; + +internal static partial class DisassemblyParser +{ + // Pattern to detect the start of a method's disassembly + // Example: ; Assembly listing for method Namespace.Type:Method(args) + [GeneratedRegex(@"^; Assembly listing for method (?.+)$", RegexOptions.Compiled | RegexOptions.Multiline)] + private static partial Regex MethodHeaderPattern(); + + // Pattern to detect end of method disassembly (next method or end) + [GeneratedRegex(@"^; Total bytes of code", RegexOptions.Compiled | RegexOptions.Multiline)] + private static partial Regex MethodEndPattern(); + + public static string Parse(string jitOutput, bool lastOnly = false) + { + if (string.IsNullOrWhiteSpace(jitOutput)) + { + return string.Empty; + } + + var result = new StringBuilder(); + var matches = MethodHeaderPattern().Matches(jitOutput); + + if (matches.Count == 0) + { + // No method headers found, return raw output if it looks like assembly + if (jitOutput.Contains("mov") || jitOutput.Contains("call") || jitOutput.Contains("ret")) + { + return jitOutput.Trim(); + } + return string.Empty; + } + + // In tier1 mode, JitDisasm captures both Tier-0 and Tier-1 compilations. + // We want the LAST compilation (Tier-1 with full optimizations). + int startIdx = lastOnly ? matches.Count - 1 : 0; + + for (int i = startIdx; i < matches.Count; i++) + { + var match = matches[i]; + var startIndex = match.Index; + + // Find the end of this method's disassembly + int endIndex = (i + 1 < matches.Count) ? matches[i + 1].Index : jitOutput.Length; + + // Extract this method's disassembly + var methodAsm = jitOutput[startIndex..endIndex].TrimEnd(); + + // Find "Total bytes of code" line and include it + var totalBytesMatch = MethodEndPattern().Match(methodAsm); + if (totalBytesMatch.Success) + { + // Find end of line after "Total bytes of code" + var lineEnd = methodAsm.IndexOf('\n', totalBytesMatch.Index); + if (lineEnd > 0) + { + methodAsm = methodAsm[..(lineEnd + 1)].TrimEnd(); + } + } + + if (result.Length > 0) + { + result.AppendLine(); + result.AppendLine(new string('-', 80)); + result.AppendLine(); + } + + result.AppendLine(methodAsm); + } + + return result.ToString().Trim(); + } + + public static IEnumerable ParseMethods(string jitOutput) + { + if (string.IsNullOrWhiteSpace(jitOutput)) + { + yield break; + } + + var matches = MethodHeaderPattern().Matches(jitOutput); + + for (int i = 0; i < matches.Count; i++) + { + var match = matches[i]; + var methodName = match.Groups["method"].Value; + var startIndex = match.Index; + + int endIndex = (i + 1 < matches.Count) ? matches[i + 1].Index : jitOutput.Length; + + var methodAsm = jitOutput[startIndex..endIndex].TrimEnd(); + + yield return new MethodDisassembly + { + MethodName = methodName, + Assembly = methodAsm + }; + } + } +} + +internal sealed class MethodDisassembly +{ + public required string MethodName { get; init; } + public required string Assembly { get; init; } +} diff --git a/tools/JitAsm/InstructionAnnotator.cs b/tools/JitAsm/InstructionAnnotator.cs new file mode 100644 index 000000000000..068d5ab34853 --- /dev/null +++ b/tools/JitAsm/InstructionAnnotator.cs @@ -0,0 +1,441 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text; +using System.Text.RegularExpressions; + +namespace JitAsm; + +internal static partial class InstructionAnnotator +{ + // Matches JIT assembly instruction lines: + // " add rax, rcx" + // " mov dword ptr [rbp+0x10], eax" + [GeneratedRegex(@"^\s+(?[a-z]\w*)\s+(?.+)$", RegexOptions.IgnoreCase)] + private static partial Regex InstructionLineRegex(); + + // Matches zero-operand instructions: " ret" or " nop" + [GeneratedRegex(@"^\s+(?[a-z]\w*)\s*$", RegexOptions.IgnoreCase)] + private static partial Regex ZeroOperandRegex(); + + // 64-bit registers + private static readonly HashSet Regs64 = new(StringComparer.OrdinalIgnoreCase) + { + "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rsp", "rbp", + "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15" + }; + + // 32-bit registers + private static readonly HashSet Regs32 = new(StringComparer.OrdinalIgnoreCase) + { + "eax", "ebx", "ecx", "edx", "esi", "edi", "esp", "ebp", + "r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d" + }; + + // 16-bit registers + private static readonly HashSet Regs16 = new(StringComparer.OrdinalIgnoreCase) + { + "ax", "bx", "cx", "dx", "si", "di", "sp", "bp", + "r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w" + }; + + // 8-bit registers + private static readonly HashSet Regs8 = new(StringComparer.OrdinalIgnoreCase) + { + "al", "bl", "cl", "dl", "sil", "dil", "spl", "bpl", "ah", "bh", "ch", "dh", + "r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b" + }; + + // JIT mnemonic → uops.info mnemonic mapping for conditional jumps + // JIT uses Intel-style aliases (je, jne, ja, etc.) but uops.info uses the + // canonical forms (jz, jnz, jnbe, etc.) + private static readonly Dictionary MnemonicAliases = new(StringComparer.OrdinalIgnoreCase) + { + ["je"] = "jz", + ["jne"] = "jnz", + ["ja"] = "jnbe", + ["jae"] = "jnb", + ["jb"] = "jb", // canonical + ["jbe"] = "jna", + ["jg"] = "jnle", + ["jge"] = "jnl", + ["jl"] = "jnge", + ["jle"] = "jng", + ["jc"] = "jb", + ["jnc"] = "jnb", + ["jp"] = "jp", // canonical + ["jnp"] = "jnp", // canonical + ["js"] = "js", // canonical + ["jns"] = "jns", // canonical + ["jo"] = "jo", // canonical + ["jno"] = "jno", // canonical + ["cmove"] = "cmovz", + ["cmovne"] = "cmovnz", + ["cmova"] = "cmovnbe", + ["cmovae"] = "cmovnb", + ["cmovb"] = "cmovb", // canonical + ["cmovbe"] = "cmovna", + ["cmovg"] = "cmovnle", + ["cmovge"] = "cmovnl", + ["cmovl"] = "cmovnge", + ["cmovle"] = "cmovng", + ["sete"] = "setz", + ["setne"] = "setnz", + ["seta"] = "setnbe", + ["setae"] = "setnb", + ["setb"] = "setb", // canonical + ["setbe"] = "setna", + ["setg"] = "setnle", + ["setge"] = "setnl", + ["setl"] = "setnge", + ["setle"] = "setng", + }; + + public static string Annotate(string disassembly, InstructionDb db) + { + var sb = new StringBuilder(); + var lines = JoinContinuationLines(disassembly.Split('\n')); + + foreach (string rawLine in lines) + { + string line = rawLine.TrimEnd('\r'); + + // Skip comment lines, labels, directives + if (IsNonInstructionLine(line)) + { + sb.AppendLine(line); + continue; + } + + var match = InstructionLineRegex().Match(line); + if (match.Success) + { + string mnemonic = match.Groups["mnemonic"].Value.ToLowerInvariant(); + string operandsRaw = match.Groups["operands"].Value.Trim(); + + // Skip annotations for calls and jumps to labels + if (ShouldSkipAnnotation(mnemonic, operandsRaw)) + { + sb.AppendLine(line); + continue; + } + + string pattern = ClassifyOperands(operandsRaw, mnemonic); + + // Try the original mnemonic first, then any alias + string lookupMnemonic = MnemonicAliases.TryGetValue(mnemonic, out var alias) + ? alias : mnemonic; + var info = db.Lookup(mnemonic, pattern) + ?? (lookupMnemonic != mnemonic ? db.Lookup(lookupMnemonic, pattern) : null); + + if (info is not null) + { + string annotation = FormatAnnotation(info); + // Pad the line to align annotations + int padTo = Math.Max(line.Length + 1, 55); + sb.Append(line.PadRight(padTo)); + sb.AppendLine(annotation); + } + else + { + sb.AppendLine(line); + } + continue; + } + + // Try zero-operand match + var zeroMatch = ZeroOperandRegex().Match(line); + if (zeroMatch.Success) + { + string mnemonic = zeroMatch.Groups["mnemonic"].Value.ToLowerInvariant(); + if (!ShouldSkipAnnotation(mnemonic, "")) + { + string zeroLookup = MnemonicAliases.TryGetValue(mnemonic, out var zAlias) + ? zAlias : mnemonic; + var info = db.Lookup(mnemonic, "") + ?? (zeroLookup != mnemonic ? db.Lookup(zeroLookup, "") : null); + if (info is not null) + { + string annotation = FormatAnnotation(info); + int padTo = Math.Max(line.Length + 1, 55); + sb.Append(line.PadRight(padTo)); + sb.AppendLine(annotation); + continue; + } + } + } + + sb.AppendLine(line); + } + + return sb.ToString().TrimEnd(); + } + + /// + /// Joins JIT output continuation lines. The JIT wraps long lines at ~80 chars: + /// " call \n[System.Threading.ThreadLocal`1[...]:get_Value():...]\n" + /// "; Assembly listing for method \nNamespace.Type:Method(...)\n" + /// This joins them so each logical line is a single string. + /// + private static List JoinContinuationLines(string[] rawLines) + { + var result = new List(rawLines.Length); + for (int i = 0; i < rawLines.Length; i++) + { + string line = rawLines[i].TrimEnd('\r'); + + // Keep joining while the next line looks like a continuation + while (i + 1 < rawLines.Length) + { + string next = rawLines[i + 1].TrimEnd('\r'); + if (IsContinuationLine(next)) + { + // Preserve a single space between joined parts so "call \n[Type:Method]" + // becomes "call [Type:Method]" rather than "call[Type:Method]" + line = line.TrimEnd() + " " + next.TrimStart(); + i++; + } + else + { + break; + } + } + + result.Add(line); + } + return result; + } + + /// + /// A line is a continuation if it doesn't match any known "primary" line type: + /// blank, comment (;), label (G_M...:), instruction (leading whitespace), data (RWD), or alignment. + /// + private static bool IsContinuationLine(string line) + { + if (line.Length == 0) return false; + + ReadOnlySpan trimmed = line.AsSpan().TrimEnd('\r'); + if (trimmed.Length == 0) return false; + + // Instructions start with whitespace + if (char.IsWhiteSpace(trimmed[0])) return false; + + // Comments start with ';' + if (trimmed[0] == ';') return false; + + // Labels: "G_M000_IG01:" or similar identifiers ending with ':' + // Check if line contains ':' and starts with a label-like pattern + if (trimmed.StartsWith("G_M", StringComparison.Ordinal) && trimmed.Contains(":", StringComparison.Ordinal)) + return false; + + // Read-only data table entries: "RWD00 dd ..." + if (trimmed.StartsWith("RWD", StringComparison.Ordinal)) return false; + + // Alignment directives: "align [N bytes for IG...]" + if (trimmed.StartsWith("align", StringComparison.OrdinalIgnoreCase)) return false; + + // Everything else is a continuation of the previous line + return true; + } + + private static bool IsNonInstructionLine(string line) + { + if (line.Length == 0) return true; + + // Instructions always start with whitespace (indented). + // Labels, data tables, directives, and other non-instruction lines start at column 0. + if (line[0] == ';') return true; // Comment at column 0 + if (!char.IsWhiteSpace(line[0])) return true; // Labels (G_M000_IG01:), data (RWD00), directives, etc. + + // Indented comments: " ; comment" + ReadOnlySpan trimmed = line.AsSpan().TrimStart(); + if (trimmed.Length > 0 && trimmed[0] == ';') return true; + + return false; + } + + private static bool ShouldSkipAnnotation(string mnemonic, string operands) + { + // Skip calls (to runtime helpers, methods, etc.) + if (mnemonic == "call") return true; + + // Skip ret - uops.info TP_unrolled is a microbenchmark artifact (return stack buffer + // mispredictions make the measurement meaningless for real code) + if (mnemonic == "ret") return true; + + // Skip int3/nop - not meaningful for performance analysis + if (mnemonic is "int3" or "nop" or "int") return true; + + return false; + } + + internal static string ClassifyOperands(string operandsRaw, string? mnemonic = null) + { + // Handle trailing comments after operands: "rax, rcx ; some comment" + int commentIdx = operandsRaw.IndexOf(';'); + if (commentIdx >= 0) + operandsRaw = operandsRaw[..commentIdx].TrimEnd(); + + if (string.IsNullOrWhiteSpace(operandsRaw)) + return ""; + + // Split operands by comma, but respect brackets for memory operands + var operands = SplitOperands(operandsRaw); + var parts = new List(); + + for (int i = 0; i < operands.Count; i++) + { + string op = operands[i].Trim(); + + // LEA's second operand is an address expression, not a memory load + // uops.info classifies it as "agen" (address generation) + if (mnemonic is "lea" && i == 1 && op.Contains('[')) + { + parts.Add("agen"); + continue; + } + + string classified = ClassifySingleOperand(op); + if (classified.Length > 0) + parts.Add(classified); + } + + return string.Join(",", parts); + } + + private static List SplitOperands(string operands) + { + var result = new List(); + int depth = 0; + int start = 0; + + for (int i = 0; i < operands.Length; i++) + { + char c = operands[i]; + if (c == '[') depth++; + else if (c == ']') depth--; + else if (c == ',' && depth == 0) + { + result.Add(operands[start..i]); + start = i + 1; + } + } + + result.Add(operands[start..]); + return result; + } + + private static string ClassifySingleOperand(string op) + { + // Memory operand: "dword ptr [rbp+10h]", "qword ptr [rsp+20h]", "[rax]" + if (op.Contains('[')) + { + if (op.Contains("zmmword ptr", StringComparison.OrdinalIgnoreCase)) return "m512"; + if (op.Contains("ymmword ptr", StringComparison.OrdinalIgnoreCase)) return "m256"; + if (op.Contains("xmmword ptr", StringComparison.OrdinalIgnoreCase)) return "m128"; + if (op.Contains("qword ptr", StringComparison.OrdinalIgnoreCase)) return "m64"; + // gword ptr = GC-tracked pointer-width memory (.NET JIT specific, equivalent to qword on x64) + if (op.Contains("gword ptr", StringComparison.OrdinalIgnoreCase)) return "m64"; + // bword ptr = pointer-width memory without GC tracking (.NET JIT specific, equivalent to qword on x64) + if (op.Contains("bword ptr", StringComparison.OrdinalIgnoreCase)) return "m64"; + if (op.Contains("dword ptr", StringComparison.OrdinalIgnoreCase)) return "m32"; + if (op.Contains("word ptr", StringComparison.OrdinalIgnoreCase) && + !op.Contains("dword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("qword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("gword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("bword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("xmmword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("ymmword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("zmmword", StringComparison.OrdinalIgnoreCase)) + return "m16"; + if (op.Contains("byte ptr", StringComparison.OrdinalIgnoreCase)) return "m8"; + return "m"; + } + + // Register operands + string regName = op.Trim(); + + // ZMM registers + if (regName.StartsWith("zmm", StringComparison.OrdinalIgnoreCase)) return "zmm"; + // YMM registers + if (regName.StartsWith("ymm", StringComparison.OrdinalIgnoreCase)) return "ymm"; + // XMM registers + if (regName.StartsWith("xmm", StringComparison.OrdinalIgnoreCase)) return "xmm"; + // K mask registers + if (regName.StartsWith("k", StringComparison.OrdinalIgnoreCase) && regName.Length <= 2 && + regName.Length > 1 && char.IsDigit(regName[1])) return "k"; + + if (Regs64.Contains(regName)) return "r64"; + if (Regs32.Contains(regName)) return "r32"; + if (Regs16.Contains(regName)) return "r16"; + if (Regs8.Contains(regName)) return "r8"; + + // Immediate: hex (0x1A, 1Ah), decimal, or negative + if (IsImmediate(regName)) + { + // Try to determine imm8 vs imm32 from value range + if (TryParseImmediate(regName, out long value)) + { + return value is >= -128 and <= 255 ? "imm8" : "imm32"; + } + return "imm"; + } + + // Label reference (for jumps) - strip SHORT/NEAR prefix added by JIT + if (regName.StartsWith("SHORT ", StringComparison.OrdinalIgnoreCase)) + regName = regName[6..].TrimStart(); + if (regName.StartsWith("NEAR ", StringComparison.OrdinalIgnoreCase)) + regName = regName[5..].TrimStart(); + + if (regName.StartsWith("G_M", StringComparison.OrdinalIgnoreCase)) + return "rel"; + + // Unknown + return ""; + } + + private static bool IsImmediate(string op) + { + if (op.Length == 0) return false; + + // Hex: 0x prefix or trailing h + if (op.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) return true; + if (op.EndsWith('h') || op.EndsWith('H')) + { + return op[..^1].All(c => char.IsAsciiHexDigit(c) || c == '-'); + } + + // Decimal (possibly negative) + return op.All(c => char.IsDigit(c) || c == '-'); + } + + private static bool TryParseImmediate(string op, out long value) + { + value = 0; + + if (op.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + return long.TryParse(op.AsSpan(2), System.Globalization.NumberStyles.HexNumber, + System.Globalization.CultureInfo.InvariantCulture, out value); + + if (op.EndsWith('h') || op.EndsWith('H')) + return long.TryParse(op.AsSpan(0, op.Length - 1), System.Globalization.NumberStyles.HexNumber, + System.Globalization.CultureInfo.InvariantCulture, out value); + + return long.TryParse(op, out value); + } + + private static string FormatAnnotation(InstructionInfo info) + { + var sb = new StringBuilder(); + sb.Append("; ["); + sb.Append($"TP:{info.Throughput:F2}"); + sb.Append($" | Lat:{info.Latency,2}"); + sb.Append($" | Uops:{info.Uops}"); + if (info.Ports is not null) + { + sb.Append($" | {info.Ports}"); + } + sb.Append(']'); + return sb.ToString(); + } +} diff --git a/tools/JitAsm/InstructionDb.cs b/tools/JitAsm/InstructionDb.cs new file mode 100644 index 000000000000..df39ca110944 --- /dev/null +++ b/tools/JitAsm/InstructionDb.cs @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace JitAsm; + +internal sealed class InstructionInfo +{ + public required string Mnemonic { get; init; } + public required string OperandPattern { get; init; } + public float Throughput { get; init; } + public int Latency { get; init; } + public int Uops { get; init; } + public string? Ports { get; init; } +} + +internal sealed class InstructionDb +{ + private const string Magic = "UOPS"; + private const ushort Version = 2; + + private readonly Dictionary> _instructions = new(StringComparer.OrdinalIgnoreCase); + + public string ArchName { get; } + + public InstructionDb(string archName) + { + ArchName = archName; + } + + public void Add(InstructionInfo info) + { + string key = info.Mnemonic.ToLowerInvariant(); + if (!_instructions.TryGetValue(key, out var list)) + { + list = []; + _instructions[key] = list; + } + list.Add(info); + } + + public int Count => _instructions.Sum(kv => kv.Value.Count); + + public InstructionInfo? Lookup(string mnemonic, string operandPattern) + { + if (!_instructions.TryGetValue(mnemonic, out var forms)) + return null; + + // Exact match + foreach (var form in forms) + { + if (string.Equals(form.OperandPattern, operandPattern, StringComparison.OrdinalIgnoreCase)) + return form; + } + + // Relaxed match: ignore register width differences (r32 ≈ r64 for same instruction class) + string relaxed = RelaxPattern(operandPattern); + foreach (var form in forms) + { + if (string.Equals(RelaxPattern(form.OperandPattern), relaxed, StringComparison.OrdinalIgnoreCase)) + return form; + } + + // Mnemonic-only match for zero-operand instructions (ret, nop, etc.) + if (operandPattern.Length == 0) + { + foreach (var form in forms) + { + if (form.OperandPattern.Length == 0) + return form; + } + } + + return null; + } + + private static string RelaxPattern(string pattern) + { + // Normalize register widths: r8/r16/r32/r64 → r, m8/m16/m32/m64/m128/m256/m512 → m, imm8/imm32 → imm + return pattern + .Replace("r64", "r").Replace("r32", "r").Replace("r16", "r").Replace("r8", "r") + .Replace("m512", "m").Replace("m256", "m").Replace("m128", "m") + .Replace("m64", "m").Replace("m32", "m").Replace("m16", "m").Replace("m8", "m") + .Replace("imm32", "imm").Replace("imm8", "imm"); + } + + public void Save(string path) + { + using var stream = File.Create(path); + using var writer = new BinaryWriter(stream); + + // Header + writer.Write(Magic.ToCharArray()); + writer.Write(Version); + writer.Write(ArchName); + + // Count all entries + int entryCount = Count; + writer.Write(entryCount); + + // Entries + foreach (var (_, forms) in _instructions) + { + foreach (var info in forms) + { + writer.Write(info.Mnemonic); + writer.Write(info.OperandPattern); + writer.Write(info.Throughput); + writer.Write((short)info.Latency); + writer.Write((short)info.Uops); + writer.Write(info.Ports ?? string.Empty); + } + } + } + + public static InstructionDb Load(string path) + { + using var stream = File.OpenRead(path); + using var reader = new BinaryReader(stream); + + // Header + char[] magic = reader.ReadChars(4); + if (new string(magic) != Magic) + throw new InvalidDataException($"Invalid instruction database file: bad magic"); + + ushort version = reader.ReadUInt16(); + if (version != Version) + throw new InvalidDataException($"Unsupported instruction database version: {version}"); + + string archName = reader.ReadString(); + int entryCount = reader.ReadInt32(); + + var db = new InstructionDb(archName); + + for (int i = 0; i < entryCount; i++) + { + string mnemonic = reader.ReadString(); + string operandPattern = reader.ReadString(); + float throughput = reader.ReadSingle(); + short latency = reader.ReadInt16(); + short uops = reader.ReadInt16(); + string ports = reader.ReadString(); + + db.Add(new InstructionInfo + { + Mnemonic = mnemonic, + OperandPattern = operandPattern, + Throughput = throughput, + Latency = latency, + Uops = uops, + Ports = ports.Length > 0 ? ports : null + }); + } + + return db; + } +} diff --git a/tools/JitAsm/InstructionDbBuilder.cs b/tools/JitAsm/InstructionDbBuilder.cs new file mode 100644 index 000000000000..7daf9cfa517b --- /dev/null +++ b/tools/JitAsm/InstructionDbBuilder.cs @@ -0,0 +1,265 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Globalization; +using System.Xml; + +namespace JitAsm; + +internal static class InstructionDbBuilder +{ + // Map from CLI flag value to uops.info architecture name + private static readonly Dictionary ArchMap = new(StringComparer.OrdinalIgnoreCase) + { + ["alder-lake"] = "ADL-P", + ["rocket-lake"] = "RKL", + ["ice-lake"] = "ICL", + ["tiger-lake"] = "TGL", + ["skylake"] = "SKL", + ["zen4"] = "ZEN4", + ["zen3"] = "ZEN3", + ["zen2"] = "ZEN2", + }; + + // Fallback chain: if the target arch has no data, try these in order + private static readonly Dictionary FallbackChain = new(StringComparer.OrdinalIgnoreCase) + { + ["ADL-P"] = ["RKL", "TGL", "ICL", "SKL"], + ["RKL"] = ["TGL", "ICL", "SKL"], + ["TGL"] = ["ICL", "SKL"], + ["ICL"] = ["SKL", "SKX", "HSW"], + ["SKL"] = ["SKX", "HSW"], + ["ZEN4"] = ["ZEN3", "ZEN2", "ZEN+"], + ["ZEN3"] = ["ZEN2", "ZEN+"], + ["ZEN2"] = ["ZEN+"], + }; + + public static string ResolveArchName(string cliValue) + { + return ArchMap.TryGetValue(cliValue, out var name) ? name : cliValue.ToUpperInvariant(); + } + + public static IReadOnlyCollection SupportedArchitectures => ArchMap.Keys; + + public static InstructionDb Build(string xmlPath, string archCliValue) + { + string targetArch = ResolveArchName(archCliValue); + string[] fallbacks = FallbackChain.TryGetValue(targetArch, out var fb) ? fb : []; + + var db = new InstructionDb(targetArch); + var seen = new HashSet(StringComparer.OrdinalIgnoreCase); + + using var stream = File.OpenRead(xmlPath); + using var reader = XmlReader.Create(stream, new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }); + + while (reader.Read()) + { + if (reader.NodeType != XmlNodeType.Element || reader.Name != "instruction") + continue; + + string? asm = reader.GetAttribute("asm"); + if (asm is null) + continue; + + // Clean the asm mnemonic: remove prefixes like "{load} " or "{store} " + string mnemonic = CleanMnemonic(asm); + if (mnemonic.Length == 0) + continue; + + // Read the instruction subtree + string instructionXml = reader.ReadOuterXml(); + var instrDoc = new XmlDocument(); + instrDoc.LoadXml(instructionXml); + var instrNode = instrDoc.DocumentElement!; + + // Parse operands + string operandPattern = BuildOperandPattern(instrNode); + + // Dedup key + string key = $"{mnemonic.ToLowerInvariant()}|{operandPattern}"; + if (seen.Contains(key)) + continue; + + // Find measurement for target architecture (with fallback) + var measurement = FindMeasurement(instrNode, targetArch, fallbacks); + if (measurement is null) + continue; + + float throughput = ParseFloat(measurement.GetAttribute("TP_unrolled")) + ?? ParseFloat(measurement.GetAttribute("TP_loop")) + ?? 0; + + int uops = ParseInt(measurement.GetAttribute("uops")) ?? 0; + string? ports = measurement.GetAttribute("ports"); + if (string.IsNullOrEmpty(ports)) ports = null; + + // Get max latency from child elements + int latency = 0; + foreach (XmlNode child in measurement.ChildNodes) + { + if (child is XmlElement latencyEl && latencyEl.Name == "latency") + { + int? cycles = ParseInt(latencyEl.GetAttribute("cycles")) + ?? ParseInt(latencyEl.GetAttribute("cycles_mem")) + ?? ParseInt(latencyEl.GetAttribute("cycles_addr")); + if (cycles.HasValue && cycles.Value > latency) + latency = cycles.Value; + } + } + + seen.Add(key); + db.Add(new InstructionInfo + { + Mnemonic = mnemonic.ToLowerInvariant(), + OperandPattern = operandPattern, + Throughput = throughput, + Latency = latency, + Uops = uops, + Ports = ports + }); + } + + return db; + } + + private static string CleanMnemonic(string asm) + { + // Remove assembler hints like "{load} ", "{store} ", "{vex} ", "{evex} " + ReadOnlySpan span = asm.AsSpan().Trim(); + while (span.Length > 0 && span[0] == '{') + { + int end = span.IndexOf('}'); + if (end < 0) break; + span = span[(end + 1)..].TrimStart(); + } + + // Take only the first word (mnemonic), skip any operand hints + int space = span.IndexOf(' '); + if (space > 0) + span = span[..space]; + + return span.ToString(); + } + + private static string BuildOperandPattern(XmlElement instrNode) + { + var parts = new List(); + foreach (XmlNode child in instrNode.ChildNodes) + { + if (child is not XmlElement operandEl || operandEl.Name != "operand") + continue; + + // Skip suppressed operands (flags, implicit registers) + if (operandEl.GetAttribute("suppressed") == "1") + continue; + + string? type = operandEl.GetAttribute("type"); + string? width = operandEl.GetAttribute("width"); + + string part = type switch + { + "reg" => ClassifyReg(width, operandEl.InnerText), + "mem" => ClassifyMem(width), + "agen" => "agen", + "imm" => ClassifyImm(width), + "relbr" => "rel", + _ => "" + }; + + if (part.Length > 0) + parts.Add(part); + } + + return string.Join(",", parts); + } + + private static string ClassifyReg(string? width, string? regNames) + { + // Check register names for xmm/ymm/zmm/mm/k + if (regNames is not null) + { + string firstReg = regNames.Split(',')[0].Trim().ToUpperInvariant(); + if (firstReg.StartsWith("ZMM")) return "zmm"; + if (firstReg.StartsWith("YMM")) return "ymm"; + if (firstReg.StartsWith("XMM")) return "xmm"; + if (firstReg.StartsWith("MM")) return "mm"; + if (firstReg.StartsWith("K")) return "k"; + } + + return width switch + { + "8" => "r8", + "16" => "r16", + "32" => "r32", + "64" => "r64", + "128" => "xmm", + "256" => "ymm", + "512" => "zmm", + _ => "r" + }; + } + + private static string ClassifyMem(string? width) + { + return width switch + { + "8" => "m8", + "16" => "m16", + "32" => "m32", + "64" => "m64", + "128" => "m128", + "256" => "m256", + "512" => "m512", + _ => "m" + }; + } + + private static string ClassifyImm(string? width) + { + return width switch + { + "8" => "imm8", + "16" => "imm16", + "32" => "imm32", + _ => "imm" + }; + } + + private static XmlElement? FindMeasurement(XmlElement instrNode, string targetArch, string[] fallbacks) + { + // Try target arch first, then fallbacks + var archsToTry = new List { targetArch }; + archsToTry.AddRange(fallbacks); + + foreach (string archName in archsToTry) + { + foreach (XmlNode child in instrNode.ChildNodes) + { + if (child is not XmlElement archEl || archEl.Name != "architecture") + continue; + if (archEl.GetAttribute("name") != archName) + continue; + + foreach (XmlNode archChild in archEl.ChildNodes) + { + if (archChild is XmlElement measureEl && measureEl.Name == "measurement") + return measureEl; + } + } + } + + return null; + } + + private static float? ParseFloat(string? value) + { + if (string.IsNullOrEmpty(value)) return null; + return float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out float result) ? result : null; + } + + private static int? ParseInt(string? value) + { + if (string.IsNullOrEmpty(value)) return null; + return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int result) ? result : null; + } +} diff --git a/tools/JitAsm/JitAsm.csproj b/tools/JitAsm/JitAsm.csproj new file mode 100644 index 000000000000..a505e193dda2 --- /dev/null +++ b/tools/JitAsm/JitAsm.csproj @@ -0,0 +1,12 @@ + + + Exe + + + + + + + + + diff --git a/tools/JitAsm/JitAsm.slnx b/tools/JitAsm/JitAsm.slnx new file mode 100644 index 000000000000..b30012a5e817 --- /dev/null +++ b/tools/JitAsm/JitAsm.slnx @@ -0,0 +1,3 @@ + + + diff --git a/tools/JitAsm/JitRunner.cs b/tools/JitAsm/JitRunner.cs new file mode 100644 index 000000000000..b348446161f9 --- /dev/null +++ b/tools/JitAsm/JitRunner.cs @@ -0,0 +1,269 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Diagnostics; +using System.Text; + +namespace JitAsm; + +internal sealed class JitRunner(string assemblyPath, string? typeName, string methodName, string? typeParams, string? classTypeParams, bool verbose, bool tier1 = false) +{ + public async Task RunSinglePassAsync(IReadOnlyList? cctorsToInit = null) + { + var result = await RunJitProcessAsync(cctorsToInit); + return result; + } + + public async Task RunTwoPassAsync() + { + // Pass 1: Run without cctor initialization to detect static constructor calls + var pass1Result = await RunJitProcessAsync(null); + + if (!pass1Result.Success) + { + return pass1Result; + } + + // Detect static constructors in the output + var detectedCctors = StaticCtorDetector.DetectStaticCtors(pass1Result.Output ?? string.Empty); + + if (detectedCctors.Count == 0) + { + // No cctors detected, return pass 1 result + return pass1Result; + } + + // Pass 2: Run with cctor initialization + var pass2Result = await RunJitProcessAsync(detectedCctors); + + return new JitResult + { + Success = pass2Result.Success, + Output = pass2Result.Output, + Error = pass2Result.Error, + Pass1Output = verbose ? pass1Result.Output : null, + DetectedCctors = detectedCctors + }; + } + + private async Task RunJitProcessAsync(IReadOnlyList? cctorsToInit) + { + // Get the path to the JitAsm executable + var (executablePath, argumentPrefix) = GetExecutablePath(); + + // Build the method pattern for JitDisasm + var methodPattern = BuildMethodPattern(); + + var startInfo = new ProcessStartInfo + { + FileName = executablePath, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + + // Set JIT environment variables - these must be set before the process starts + if (tier1) + { + // Tier-1 simulation: enable tiered compilation so the method compiles at Tier-0 first, + // then gets recompiled at Tier-1 with full optimizations after the cctors have run. + startInfo.EnvironmentVariables["DOTNET_TieredCompilation"] = "1"; + startInfo.EnvironmentVariables["DOTNET_TieredPGO"] = "1"; + startInfo.EnvironmentVariables["DOTNET_TC_CallCountThreshold"] = "1"; + startInfo.EnvironmentVariables["DOTNET_TC_CallCountingDelayMs"] = "0"; + } + else + { + startInfo.EnvironmentVariables["DOTNET_TieredCompilation"] = "0"; + startInfo.EnvironmentVariables["DOTNET_TC_QuickJit"] = "0"; + } + startInfo.EnvironmentVariables["DOTNET_JitDisasm"] = methodPattern; + startInfo.EnvironmentVariables["DOTNET_JitDiffableDasm"] = "1"; + if (verbose) + startInfo.EnvironmentVariables["JITASM_VERBOSE"] = "1"; + + // Build arguments for internal runner + var args = new StringBuilder(); + if (argumentPrefix is not null) + { + args.Append(EscapeArg(argumentPrefix)); + args.Append(' '); + } + args.Append("--internal-runner "); + args.Append(EscapeArg(assemblyPath)); + args.Append(' '); + args.Append(EscapeArg(methodName)); + + if (typeName is not null) + { + args.Append(" --type "); + args.Append(EscapeArg(typeName)); + } + + if (typeParams is not null) + { + args.Append(" --type-params "); + args.Append(EscapeArg(typeParams)); + } + + if (classTypeParams is not null) + { + args.Append(" --class-type-params "); + args.Append(EscapeArg(classTypeParams)); + } + + if (cctorsToInit is not null && cctorsToInit.Count > 0) + { + args.Append(" --init-cctors "); + args.Append(EscapeArg(string.Join(";", cctorsToInit))); + } + + if (tier1) + { + args.Append(" --tier1"); + } + + startInfo.Arguments = args.ToString(); + + if (verbose) + { + Spectre.Console.AnsiConsole.MarkupLine($"[grey]Running: {Spectre.Console.Markup.Escape(executablePath)} {Spectre.Console.Markup.Escape(startInfo.Arguments)}[/]"); + Spectre.Console.AnsiConsole.MarkupLine($"[grey]DOTNET_JitDisasm={Spectre.Console.Markup.Escape(methodPattern)}[/]"); + Spectre.Console.AnsiConsole.WriteLine(); + } + + try + { + using var process = Process.Start(startInfo); + if (process is null) + { + return new JitResult { Success = false, Error = "Failed to start process" }; + } + + var stdoutTask = process.StandardOutput.ReadToEndAsync(); + var stderrTask = process.StandardError.ReadToEndAsync(); + + await process.WaitForExitAsync(); + + var stdout = await stdoutTask; + var stderr = await stderrTask; + + // Parse the JIT output from stdout (JIT diagnostics can go to either stream) + // In tier1 mode, multiple compilations are captured; take only the last (Tier-1) + var disassembly = DisassemblyParser.Parse(stdout, lastOnly: tier1); + + if (string.IsNullOrWhiteSpace(disassembly)) + { + // Try stderr + disassembly = DisassemblyParser.Parse(stderr, lastOnly: tier1); + } + + if (string.IsNullOrWhiteSpace(disassembly)) + { + // Check if there's an error + if (!string.IsNullOrWhiteSpace(stderr) && stderr.Contains("Error")) + { + return new JitResult { Success = false, Error = stderr.Trim() }; + } + + // No disassembly found + return new JitResult + { + Success = false, + Error = "No disassembly output found. Method may not exist or JIT output was not captured.", + Output = $"stdout:\n{stdout}\n\nstderr:\n{stderr}" + }; + } + + return new JitResult + { + Success = true, + Output = disassembly + }; + } + catch (Exception ex) + { + return new JitResult { Success = false, Error = ex.Message }; + } + } + + private string BuildMethodPattern() + { + // Build pattern for DOTNET_JitDisasm + // Just use the method name - the JIT will match any method with this name + // Type filtering is done post-hoc by parsing the output + return methodName; + } + + /// + /// Returns the executable path and any prefix arguments needed (e.g., DLL path for dotnet host). + /// + private static (string FileName, string? ArgumentPrefix) GetExecutablePath() + { + // Get the path to the current executable + var currentExe = Environment.ProcessPath; + if (currentExe is not null && File.Exists(currentExe)) + { + // When running via "dotnet run", ProcessPath is the dotnet host, not our tool. + // Detect this by checking if it ends with "dotnet" (or "dotnet.exe"). + var exeName = Path.GetFileNameWithoutExtension(currentExe); + if (exeName.Equals("dotnet", StringComparison.OrdinalIgnoreCase)) + { + var assemblyLocation = typeof(JitRunner).Assembly.Location; + if (!string.IsNullOrEmpty(assemblyLocation)) + { + return ("dotnet", assemblyLocation); + } + } + + return (currentExe, null); + } + + // Fallback to assembly location + var location = typeof(JitRunner).Assembly.Location; + if (!string.IsNullOrEmpty(location)) + { + // For .dll, try to find the corresponding .exe + var directory = Path.GetDirectoryName(location)!; + var baseName = Path.GetFileNameWithoutExtension(location); + + // Try .exe first (Windows) + var exePath = Path.Combine(directory, baseName + ".exe"); + if (File.Exists(exePath)) + { + return (exePath, null); + } + + // Try without extension (Linux/macOS) + exePath = Path.Combine(directory, baseName); + if (File.Exists(exePath)) + { + return (exePath, null); + } + + // Use dotnet to run the dll + return ("dotnet", location); + } + + throw new InvalidOperationException("Could not determine executable path"); + } + + private static string EscapeArg(string arg) + { + if (arg.Contains(' ') || arg.Contains('"')) + { + return $"\"{arg.Replace("\"", "\\\"")}\""; + } + return arg; + } +} + +internal sealed class JitResult +{ + public bool Success { get; init; } + public string? Output { get; init; } + public string? Error { get; init; } + public string? Pass1Output { get; init; } + public IReadOnlyList DetectedCctors { get; init; } = []; +} diff --git a/tools/JitAsm/MethodResolver.cs b/tools/JitAsm/MethodResolver.cs new file mode 100644 index 000000000000..334d7fb14b5e --- /dev/null +++ b/tools/JitAsm/MethodResolver.cs @@ -0,0 +1,393 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Reflection; + +namespace JitAsm; + +internal sealed class MethodResolver(Assembly assembly) +{ + private static readonly Dictionary TypeAliases = new(StringComparer.OrdinalIgnoreCase) + { + ["bool"] = typeof(bool), + ["byte"] = typeof(byte), + ["sbyte"] = typeof(sbyte), + ["char"] = typeof(char), + ["short"] = typeof(short), + ["ushort"] = typeof(ushort), + ["int"] = typeof(int), + ["uint"] = typeof(uint), + ["long"] = typeof(long), + ["ulong"] = typeof(ulong), + ["float"] = typeof(float), + ["double"] = typeof(double), + ["decimal"] = typeof(decimal), + ["string"] = typeof(string), + ["object"] = typeof(object), + ["void"] = typeof(void), + ["nint"] = typeof(nint), + ["nuint"] = typeof(nuint), + }; + + public MethodInfo? ResolveMethod(string? typeName, string methodName, string? typeParams, string? classTypeParams = null) + { + var candidates = new List<(Type Type, MethodInfo Method)>(); + + if (typeName is not null) + { + // Search specific type + var type = ResolveType(typeName); + if (type is null) + { + return null; + } + + // If the type is a generic type definition and we have class type params, construct the concrete type + if (type.IsGenericTypeDefinition && classTypeParams is not null) + { + type = MakeGenericType(type, classTypeParams); + if (type is null) + { + return null; + } + } + + var methods = FindMethods(type, methodName); + candidates.AddRange(methods.Select(m => (type, m))); + + // Also search base types if no methods found (for inherited methods) + if (candidates.Count == 0) + { + var baseType = type.BaseType; + while (baseType is not null && candidates.Count == 0) + { + methods = FindMethods(baseType, methodName, includeInherited: true); + candidates.AddRange(methods.Select(m => (baseType, m))); + baseType = baseType.BaseType; + } + } + } + else + { + // Search all types + foreach (var type in assembly.GetTypes()) + { + var searchType = type; + + // If the type is a generic type definition and we have class type params, try to construct it + if (type.IsGenericTypeDefinition && classTypeParams is not null) + { + var typeParamCount = classTypeParams.Split(',').Length; + if (type.GetGenericArguments().Length == typeParamCount) + { + var constructed = MakeGenericType(type, classTypeParams); + if (constructed is not null) + { + searchType = constructed; + } + } + } + + var methods = FindMethods(searchType, methodName); + candidates.AddRange(methods.Select(m => (searchType, m))); + } + } + + if (candidates.Count == 0) + { + return null; + } + + bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; + if (verbose) + { + Console.Error.WriteLine($"[DEBUG] Candidates before filtering: {candidates.Count}"); + foreach (var c in candidates) + { + Console.Error.WriteLine($"[DEBUG] Method: {c.Method.Name}, IsGenericMethodDefinition: {c.Method.IsGenericMethodDefinition}, GenericArgCount: {c.Method.GetGenericArguments().Length}"); + } + } + + // If type params are specified, filter to only methods with matching generic param count + if (typeParams is not null) + { + var genericParamCount = typeParams.Split(',').Length; + if (verbose) + Console.Error.WriteLine($"[DEBUG] Looking for generic methods with {genericParamCount} type params"); + + var genericMatches = candidates.Where(c => c.Method.IsGenericMethodDefinition && + c.Method.GetGenericArguments().Length == genericParamCount).ToList(); + + if (verbose) + Console.Error.WriteLine($"[DEBUG] Generic matches: {genericMatches.Count}"); + + if (genericMatches.Count > 0) + { + candidates = genericMatches; + } + } + else + { + // Prefer non-generic methods if no type params specified + var nonGeneric = candidates.Where(c => !c.Method.IsGenericMethodDefinition).ToList(); + if (nonGeneric.Count > 0) + { + candidates = nonGeneric; + } + } + + if (candidates.Count == 0) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] No candidates after filtering"); + return null; + } + + if (verbose) + Console.Error.WriteLine($"[DEBUG] Final candidates: {candidates.Count}, calling MakeGenericIfNeeded"); + + // If there's only one candidate, use it + if (candidates.Count == 1) + { + return MakeGenericIfNeeded(candidates[0].Method, typeParams); + } + + // Return first match + return MakeGenericIfNeeded(candidates[0].Method, typeParams); + } + + private Type? MakeGenericType(Type genericTypeDefinition, string classTypeParams) + { + var typeNames = classTypeParams.Split(',', StringSplitOptions.TrimEntries); + var types = new Type[typeNames.Length]; + + bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; + + for (int i = 0; i < typeNames.Length; i++) + { + var resolved = ResolveTypeParam(typeNames[i]); + if (resolved is null) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] Failed to resolve type param: {typeNames[i]}"); + return null; + } + types[i] = resolved; + if (verbose) + Console.Error.WriteLine($"[DEBUG] Resolved type param {typeNames[i]} to {resolved.FullName}"); + } + + try + { + var result = genericTypeDefinition.MakeGenericType(types); + if (verbose) + Console.Error.WriteLine($"[DEBUG] Constructed generic type: {result.FullName}"); + return result; + } + catch (Exception ex) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericType failed: {ex.Message}"); + return null; + } + } + + private Type? ResolveType(string typeName) + { + // Try direct lookup + var type = assembly.GetType(typeName); + if (type is not null) return type; + + // Try with assembly name prefix removed if present + var types = assembly.GetTypes(); + + // Try exact match on FullName + type = types.FirstOrDefault(t => t.FullName == typeName); + if (type is not null) return type; + + // Try match on Name only + type = types.FirstOrDefault(t => t.Name == typeName); + if (type is not null) return type; + + // Try matching generic types by base name (without the `N suffix) + // e.g., "TransactionProcessorBase" should match "TransactionProcessorBase`1" + type = types.FirstOrDefault(t => t.IsGenericTypeDefinition && + (t.FullName?.StartsWith(typeName + "`") == true || + t.Name.StartsWith(typeName + "`"))); + if (type is not null) return type; + + // Also try matching with the ` syntax - the type might be specified as TypeName`1 + if (typeName.Contains('`')) + { + type = types.FirstOrDefault(t => t.FullName == typeName || t.Name == typeName); + if (type is not null) return type; + } + + // Try nested types + foreach (var t in types) + { + if (t.FullName is not null && typeName.StartsWith(t.FullName + "+")) + { + var nestedName = typeName[(t.FullName.Length + 1)..]; + var nested = t.GetNestedType(nestedName, BindingFlags.Public | BindingFlags.NonPublic); + if (nested is not null) return nested; + } + } + + // Search referenced assemblies for cross-assembly types + foreach (var refName in assembly.GetReferencedAssemblies()) + { + try + { + var refAssembly = Assembly.Load(refName); + type = refAssembly.GetType(typeName); + if (type is not null) return type; + + // Try matching generic types by base name (e.g., "ClockCache" matches "ClockCache`2") + Type[] refTypes; + try { refTypes = refAssembly.GetTypes(); } + catch (ReflectionTypeLoadException ex) { refTypes = ex.Types.Where(t => t is not null).ToArray()!; } + + type = refTypes.FirstOrDefault(t => + t.FullName == typeName || t.Name == typeName || + (t.IsGenericTypeDefinition && + (t.FullName?.StartsWith(typeName + "`") == true || + t.Name.StartsWith(typeName + "`")))); + if (type is not null) return type; + } + catch + { + // Skip assemblies that can't be loaded + } + } + + return null; + } + + private static IEnumerable FindMethods(Type type, string methodName, bool includeInherited = false) + { + var flags = BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.Static; + + if (!includeInherited) + { + flags |= BindingFlags.DeclaredOnly; + } + + bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; + var methods = type.GetMethods(flags).Where(m => m.Name == methodName).ToList(); + if (verbose) + { + Console.Error.WriteLine($"[DEBUG] FindMethods on {type.FullName} for '{methodName}': found {methods.Count} methods"); + if (methods.Count == 0) + { + // List some methods to help debug + var allMethods = type.GetMethods(flags).Where(m => m.Name.Contains("Evm") || m.Name.Contains("Execute")).Take(10); + Console.Error.WriteLine($"[DEBUG] Sample methods containing 'Evm' or 'Execute': {string.Join(", ", allMethods.Select(m => m.Name))}"); + } + } + + return methods; + } + + private MethodInfo? MakeGenericIfNeeded(MethodInfo method, string? typeParams) + { + bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; + + if (typeParams is null || !method.IsGenericMethodDefinition) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: returning method as-is (typeParams={typeParams}, IsGenericMethodDefinition={method.IsGenericMethodDefinition})"); + return method; + } + + var typeNames = typeParams.Split(',', StringSplitOptions.TrimEntries); + var types = new Type[typeNames.Length]; + + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: resolving {typeNames.Length} type params: {string.Join(", ", typeNames)}"); + + for (int i = 0; i < typeNames.Length; i++) + { + var resolved = ResolveTypeParam(typeNames[i]); + if (resolved is null) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: failed to resolve type param '{typeNames[i]}'"); + return null; + } + types[i] = resolved; + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: resolved '{typeNames[i]}' to {resolved.FullName}"); + } + + try + { + var result = method.MakeGenericMethod(types); + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: success, created {result}"); + return result; + } + catch (Exception ex) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: MakeGenericMethod failed: {ex.Message}"); + return null; + } + } + + private Type? ResolveTypeParam(string typeName) + { + // Check aliases first + if (TypeAliases.TryGetValue(typeName, out var aliasType)) + { + return aliasType; + } + + // Try the target assembly + var type = ResolveType(typeName); + if (type is not null) return type; + + // Try referenced assemblies + foreach (var refName in assembly.GetReferencedAssemblies()) + { + try + { + var refAssembly = Assembly.Load(refName); + type = refAssembly.GetType(typeName); + if (type is not null) return type; + + // Try by short name + type = refAssembly.GetTypes().FirstOrDefault(t => t.Name == typeName || t.FullName == typeName); + if (type is not null) return type; + } + catch + { + // Skip assemblies that can't be loaded + } + } + + // Try Type.GetType as last resort + return Type.GetType(typeName); + } + + public IEnumerable FindAllMethods(string? typeName, string methodName) + { + if (typeName is not null) + { + var type = ResolveType(typeName); + if (type is not null) + { + return FindMethods(type, methodName); + } + return []; + } + + var results = new List(); + foreach (var type in assembly.GetTypes()) + { + results.AddRange(FindMethods(type, methodName)); + } + return results; + } +} diff --git a/tools/JitAsm/Program.cs b/tools/JitAsm/Program.cs new file mode 100644 index 000000000000..deb928163f70 --- /dev/null +++ b/tools/JitAsm/Program.cs @@ -0,0 +1,581 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.CommandLine; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Loader; +using System.Text.Json; +using Spectre.Console; + +namespace JitAsm; + +internal static class Program +{ + private static readonly Option AssemblyOption = new("-a", "--assembly") + { + Description = "Path to the assembly containing the method", + Required = true + }; + + private static readonly Option TypeOption = new("-t", "--type") + { + Description = "Fully qualified type name (optional, will search all types if not specified)" + }; + + private static readonly Option MethodOption = new("-m", "--method") + { + Description = "Method name to disassemble", + Required = true + }; + + private static readonly Option TypeParamsOption = new("--type-params") + { + Description = "Method generic type parameters (comma-separated type names)" + }; + + private static readonly Option ClassTypeParamsOption = new("--class-type-params") + { + Description = "Class generic type parameters (comma-separated type names, e.g., for TransactionProcessorBase`1)" + }; + + private static readonly Option SkipCctorOption = new("--skip-cctor-detection") + { + Description = "Skip automatic static constructor detection (single pass only)" + }; + + private static readonly Option FullOptsOption = new("--fullopts") + { + Description = "Use single-pass FullOpts compilation (DOTNET_TieredCompilation=0) instead of the default Tier-1 + PGO simulation" + }; + + private static readonly Option NoAnnotateOption = new("--no-annotate") + { + Description = "Disable per-instruction annotations (throughput, latency, uops, ports from uops.info)" + }; + + private static readonly Option ArchOption = new("--arch") + { + Description = "Target microarchitecture for annotations (default: zen4). Options: zen4, zen3, zen2, alder-lake, rocket-lake, ice-lake, tiger-lake, skylake", + DefaultValueFactory = _ => "zen4" + }; + + private static readonly Option VerboseOption = new("-v", "--verbose") + { + Description = "Show resolution details and both passes" + }; + + private static int Main(string[] args) + { + // Check if running in internal runner mode + if (args.Length > 0 && args[0] == "--internal-runner") + { + return RunInternalRunner(args.Skip(1).ToArray()); + } + + return RunCli(args); + } + + private static int RunCli(string[] args) + { + var rootCommand = new RootCommand("JIT Assembly Disassembler - Generate JIT assembly output for .NET methods") + { + AssemblyOption, + TypeOption, + MethodOption, + TypeParamsOption, + ClassTypeParamsOption, + SkipCctorOption, + FullOptsOption, + NoAnnotateOption, + ArchOption, + VerboseOption + }; + + int exitCode = 0; + rootCommand.SetAction(parseResult => + { + var assembly = parseResult.GetValue(AssemblyOption)!; + var typeName = parseResult.GetValue(TypeOption); + var methodName = parseResult.GetValue(MethodOption)!; + var typeParams = parseResult.GetValue(TypeParamsOption); + var classTypeParams = parseResult.GetValue(ClassTypeParamsOption); + var skipCctor = parseResult.GetValue(SkipCctorOption); + var fullOpts = parseResult.GetValue(FullOptsOption); + var annotate = !parseResult.GetValue(NoAnnotateOption); + var arch = parseResult.GetValue(ArchOption)!; + var verbose = parseResult.GetValue(VerboseOption); + + exitCode = Execute(assembly, typeName, methodName, typeParams, classTypeParams, skipCctor, fullOpts, annotate, arch, verbose); + }); + + int parseExitCode = rootCommand.Parse(args).Invoke(); + return parseExitCode != 0 ? parseExitCode : exitCode; + } + + private static int Execute(FileInfo assembly, string? typeName, string methodName, string? typeParams, string? classTypeParams, bool skipCctor, bool fullOpts, bool annotate, string arch, bool verbose) + { + if (!assembly.Exists) + { + AnsiConsole.MarkupLine($"[red]Error:[/] Assembly not found: {assembly.FullName}"); + return 1; + } + + bool tier1 = !fullOpts; + + if (verbose) + { + AnsiConsole.MarkupLine($"[blue]Assembly:[/] {assembly.FullName}"); + AnsiConsole.MarkupLine($"[blue]Type:[/] {typeName ?? "(search all)"}"); + AnsiConsole.MarkupLine($"[blue]Method:[/] {methodName}"); + if (classTypeParams is not null) + { + AnsiConsole.MarkupLine($"[blue]Class Type Parameters:[/] {classTypeParams}"); + } + if (typeParams is not null) + { + AnsiConsole.MarkupLine($"[blue]Method Type Parameters:[/] {typeParams}"); + } + AnsiConsole.MarkupLine(tier1 + ? "[blue]Mode:[/] Tier-1 + Dynamic PGO (default)" + : "[blue]Mode:[/] FullOpts (TieredCompilation=0)"); + AnsiConsole.WriteLine(); + } + + var runner = new JitRunner(assembly.FullName, typeName, methodName, typeParams, classTypeParams, verbose, tier1); + + InstructionDb? instructionDb = annotate ? LoadOrBuildInstructionDb(arch, verbose) : null; + + JitResult result = (skipCctor ? + runner.RunSinglePassAsync() : + runner.RunTwoPassAsync()) + .GetAwaiter().GetResult(); + + return OutputResult(result, verbose, instructionDb); + } + + private static int OutputResult(JitResult result, bool verbose, InstructionDb? instructionDb = null) + { + if (!result.Success) + { + AnsiConsole.MarkupLine($"[red]Error:[/] {Markup.Escape(result.Error ?? "Unknown error")}"); + if (!string.IsNullOrEmpty(result.Output)) + { + AnsiConsole.WriteLine(); + AnsiConsole.MarkupLine("[yellow]Output:[/]"); + AnsiConsole.WriteLine(result.Output); + } + return 1; + } + + if (verbose && result.DetectedCctors.Count > 0) + { + AnsiConsole.MarkupLine("[blue]Detected static constructors:[/]"); + foreach (var cctor in result.DetectedCctors) + { + AnsiConsole.MarkupLine($" [grey]- {Markup.Escape(cctor)}[/]"); + } + AnsiConsole.WriteLine(); + } + + if (verbose && result.Pass1Output is not null && result.DetectedCctors.Count > 0) + { + AnsiConsole.MarkupLine("[blue]Pass 1 Output (before cctor initialization):[/]"); + AnsiConsole.WriteLine(result.Pass1Output); + AnsiConsole.WriteLine(); + AnsiConsole.MarkupLine("[blue]Pass 2 Output (after cctor initialization):[/]"); + } + + string output = result.Output ?? string.Empty; + if (instructionDb is not null) + { + output = InstructionAnnotator.Annotate(output, instructionDb); + } + + Console.WriteLine(output); + return 0; + } + + private static InstructionDb? LoadOrBuildInstructionDb(string arch, bool verbose) + { + string toolDir = AppContext.BaseDirectory; + // Look for files relative to the project source directory first, then the binary directory + string projectDir = Path.GetFullPath(Path.Combine(toolDir, "..", "..", "..", "..")); + if (!File.Exists(Path.Combine(projectDir, "JitAsm.csproj"))) + { + // Fallback: try to find the project directory from the current working directory + string cwd = Directory.GetCurrentDirectory(); + if (File.Exists(Path.Combine(cwd, "tools", "JitAsm", "JitAsm.csproj"))) + projectDir = Path.Combine(cwd, "tools", "JitAsm"); + else if (File.Exists(Path.Combine(cwd, "JitAsm.csproj"))) + projectDir = cwd; + else + projectDir = toolDir; + } + + string dbPath = Path.Combine(projectDir, "instructions.db"); + string xmlPath = Path.Combine(projectDir, "instructions.xml"); + + // Check if we have a cached .db for this architecture + if (File.Exists(dbPath)) + { + try + { + var db = InstructionDb.Load(dbPath); + string targetArch = InstructionDbBuilder.ResolveArchName(arch); + if (db.ArchName == targetArch) + { + if (verbose) + AnsiConsole.MarkupLine($"[blue]Loaded instruction database:[/] {dbPath} ({db.Count} entries for {db.ArchName})"); + return db; + } + + if (verbose) + AnsiConsole.MarkupLine($"[yellow]Cached DB is for {db.ArchName}, need {targetArch}. Rebuilding...[/]"); + } + catch (Exception ex) + { + if (verbose) + AnsiConsole.MarkupLine($"[yellow]Failed to load cached DB: {Markup.Escape(ex.Message)}. Rebuilding...[/]"); + } + } + + // Build from XML + if (!File.Exists(xmlPath)) + { + AnsiConsole.MarkupLine("[yellow]Instruction database not found. Annotations disabled.[/]"); + AnsiConsole.MarkupLine("[yellow]Download it with:[/]"); + AnsiConsole.MarkupLine($" curl -o {Markup.Escape(xmlPath)} https://uops.info/instructions.xml"); + AnsiConsole.MarkupLine("[yellow]Or use --no-annotate to suppress this warning.[/]"); + return null; + } + + AnsiConsole.MarkupLine($"[blue]Building instruction database for {arch}...[/]"); + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + var builtDb = InstructionDbBuilder.Build(xmlPath, arch); + stopwatch.Stop(); + + AnsiConsole.MarkupLine($"[blue]Built {builtDb.Count} instruction entries in {stopwatch.Elapsed.TotalSeconds:F1}s[/]"); + + try + { + builtDb.Save(dbPath); + if (verbose) + AnsiConsole.MarkupLine($"[blue]Saved to:[/] {dbPath}"); + } + catch (Exception ex) + { + if (verbose) + AnsiConsole.MarkupLine($"[yellow]Warning: Could not save DB cache: {Markup.Escape(ex.Message)}[/]"); + } + + return builtDb; + } + + private static int RunInternalRunner(string[] args) + { + // Parse internal runner arguments + // Format: [--type ] [--type-params ] [--class-type-params ] [--init-cctors ] + if (args.Length < 2) + { + Console.Error.WriteLine("Internal runner requires assembly and method arguments"); + return 1; + } + + var assemblyPath = args[0]; + var methodName = args[1]; + string? typeName = null; + string? typeParams = null; + string? classTypeParams = null; + List cctorsToInit = []; + bool tier1 = false; + + for (int i = 2; i < args.Length; i++) + { + switch (args[i]) + { + case "--type" when i + 1 < args.Length: + typeName = args[++i]; + break; + case "--type-params" when i + 1 < args.Length: + typeParams = args[++i]; + break; + case "--class-type-params" when i + 1 < args.Length: + classTypeParams = args[++i]; + break; + case "--init-cctors" when i + 1 < args.Length: + cctorsToInit = args[++i].Split(';', StringSplitOptions.RemoveEmptyEntries).ToList(); + break; + case "--tier1": + tier1 = true; + break; + } + } + + // Set up dependency resolution for the target assembly. + // Without this, RuntimeHelpers.PrepareMethod silently fails when the + // JIT can't resolve types from transitive NuGet packages (e.g. Nethermind.Int256). + bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; + + // Build assembly name → file path mapping from the deps.json file. + // This resolves both project references (in the same directory) and + // NuGet packages (from the global packages cache). + var assemblyDir = Path.GetDirectoryName(Path.GetFullPath(assemblyPath))!; + var assemblyMap = BuildAssemblyMap(assemblyPath, assemblyDir, verbose); + + AssemblyLoadContext.Default.Resolving += (context, name) => + { + // First check the target assembly's directory + var localPath = Path.Combine(assemblyDir, name.Name + ".dll"); + if (File.Exists(localPath)) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] AssemblyResolve: {name.Name} → {localPath} (local)"); + return context.LoadFromAssemblyPath(localPath); + } + + // Then check deps.json mapped paths + if (assemblyMap.TryGetValue(name.Name!, out var mappedPath) && File.Exists(mappedPath)) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] AssemblyResolve: {name.Name} → {mappedPath} (deps.json)"); + return context.LoadFromAssemblyPath(mappedPath); + } + + if (verbose) + Console.Error.WriteLine($"[DEBUG] AssemblyResolve: {name.Name} → NOT FOUND"); + return null; + }; + + try + { + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(assemblyPath)); + + if (verbose) + { + Console.Error.WriteLine($"[DEBUG] Loaded assembly: {assembly.FullName}"); + Console.Error.WriteLine($"[DEBUG] Type name: {typeName}"); + Console.Error.WriteLine($"[DEBUG] Method name: {methodName}"); + Console.Error.WriteLine($"[DEBUG] Type params: {typeParams}"); + Console.Error.WriteLine($"[DEBUG] Class type params: {classTypeParams}"); + } + + // Initialize static constructors if requested + foreach (var cctorTypeName in cctorsToInit) + { + var cctorType = ResolveType(assembly, cctorTypeName, verbose); + if (cctorType is not null) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] Running cctor for: {cctorType.FullName}"); + RuntimeHelpers.RunClassConstructor(cctorType.TypeHandle); + } + else if (verbose) + { + Console.Error.WriteLine($"[DEBUG] WARNING: Could not resolve cctor type: {cctorTypeName}"); + } + } + + // Resolve the method + var resolver = new MethodResolver(assembly); + var method = resolver.ResolveMethod(typeName, methodName, typeParams, classTypeParams); + + if (method is null) + { + if (verbose) + { + var types = assembly.GetTypes().Where(t => t.Name.Contains(typeName ?? "")).Take(5); + Console.Error.WriteLine($"[DEBUG] Possible types: {string.Join(", ", types.Select(t => t.FullName))}"); + } + Console.Error.WriteLine($"Could not resolve method: {methodName}"); + return 1; + } + + if (tier1) + { + var parameters = method.GetParameters(); + var invokeArgs = new object?[parameters.Length]; + object? target = method.IsStatic ? null : TryCreateInstance(method.DeclaringType!); + + void InvokeN(int count) + { + for (int i = 0; i < count; i++) + { + try { method.Invoke(target, invokeArgs); } + catch { /* Expected - args are null/default */ } + } + } + + if (verbose) + Console.Error.WriteLine("[DEBUG] Phase 1: Invoking to trigger Tier-0 → Instrumented Tier-0..."); + InvokeN(50); + Thread.Sleep(1000); + + if (verbose) + Console.Error.WriteLine("[DEBUG] Phase 2: Invoking to trigger Instrumented Tier-0 → Tier-1..."); + InvokeN(50); + Thread.Sleep(2000); + + if (verbose) + Console.Error.WriteLine("[DEBUG] Phase 3: Final invocations to ensure Tier-1 is installed..."); + InvokeN(50); + Thread.Sleep(1000); + } + else + { + RuntimeHelpers.PrepareMethod(method.MethodHandle); + if (verbose) + Console.Error.WriteLine($"[DEBUG] PrepareMethod completed for {method.DeclaringType?.FullName}:{method.Name}"); + + // Also invoke the method to ensure all code paths are JIT-compiled. + // PrepareMethod alone may not trigger DOTNET_JitDisasm output for + // methods from dynamically loaded assemblies. + var parameters = method.GetParameters(); + var invokeArgs = new object?[parameters.Length]; + object? target = method.IsStatic ? null : TryCreateInstance(method.DeclaringType!); + try { method.Invoke(target, invokeArgs); } + catch { /* Expected - args are null/default */ } + if (verbose) + Console.Error.WriteLine("[DEBUG] Method invocation completed"); + } + + return 0; + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error: {ex.Message}"); + return 1; + } + } + + private static object? TryCreateInstance(Type type) + { + try + { + return RuntimeHelpers.GetUninitializedObject(type); + } + catch + { + return null; + } + } + + private static Type? ResolveType(Assembly assembly, string typeName, bool verbose = false) + { + // Try direct lookup in target assembly first + var type = assembly.GetType(typeName); + if (type is not null) return type; + + // Try searching all types in target assembly + foreach (var t in assembly.GetTypes()) + { + if (t.FullName == typeName || t.Name == typeName) + { + return t; + } + } + + // Search referenced assemblies (types often come from other assemblies, + // e.g., Nethermind.Core types referenced from Nethermind.Evm) + foreach (var refName in assembly.GetReferencedAssemblies()) + { + try + { + var refAssembly = Assembly.Load(refName); + type = refAssembly.GetType(typeName); + if (type is not null) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] Resolved cctor type '{typeName}' from referenced assembly {refName.Name}"); + return type; + } + + // Try by short name + foreach (var t in refAssembly.GetTypes()) + { + if (t.FullName == typeName || t.Name == typeName) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] Resolved cctor type '{typeName}' from referenced assembly {refName.Name} (by scan)"); + return t; + } + } + } + catch + { + // Skip assemblies that can't be loaded + } + } + + // Try Type.GetType as last resort (requires assembly-qualified names for cross-assembly) + return Type.GetType(typeName); + } + + /// + /// Build a mapping from assembly name to file path using the deps.json file. + /// Resolves NuGet package references via the global packages cache. + /// + private static Dictionary BuildAssemblyMap(string assemblyPath, string assemblyDir, bool verbose) + { + var map = new Dictionary(StringComparer.OrdinalIgnoreCase); + string depsPath = Path.ChangeExtension(assemblyPath, ".deps.json"); + if (!File.Exists(depsPath)) + return map; + + string nugetPackages = Environment.GetEnvironmentVariable("NUGET_PACKAGES") + ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); + + try + { + using var stream = File.OpenRead(depsPath); + using var doc = JsonDocument.Parse(stream); + + var root = doc.RootElement; + if (!root.TryGetProperty("targets", out var targets)) + return map; + + // Get the first (and usually only) target + foreach (var target in targets.EnumerateObject()) + { + foreach (var package in target.Value.EnumerateObject()) + { + if (!package.Value.TryGetProperty("runtime", out var runtime)) + continue; + + foreach (var dll in runtime.EnumerateObject()) + { + string dllRelativePath = dll.Name; // e.g. "lib/net10.0/Nethermind.Int256.dll" + string asmName = Path.GetFileNameWithoutExtension(dllRelativePath); + + // Skip if already in the local directory + if (File.Exists(Path.Combine(assemblyDir, asmName + ".dll"))) + continue; + + // Resolve from NuGet cache: packages/{id}/{version}/{path} + string packageId = package.Name; // e.g. "Nethermind.Numerics.Int256/1.4.0" + string[] parts = packageId.Split('/'); + if (parts.Length == 2) + { + string fullPath = Path.Combine(nugetPackages, parts[0].ToLowerInvariant(), parts[1], dllRelativePath); + if (File.Exists(fullPath)) + { + map[asmName] = fullPath; + if (verbose) + Console.Error.WriteLine($"[DEBUG] Deps map: {asmName} → {fullPath}"); + } + } + } + } + break; // Only process the first target + } + } + catch (Exception ex) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] Failed to parse deps.json: {ex.Message}"); + } + + return map; + } +} + diff --git a/tools/JitAsm/README.md b/tools/JitAsm/README.md new file mode 100644 index 000000000000..705821f64be3 --- /dev/null +++ b/tools/JitAsm/README.md @@ -0,0 +1,494 @@ +# JitAsm + +A tool for viewing JIT-compiled assembly output for .NET methods. Useful for analyzing code generation, verifying optimizations, and comparing different implementations. + +## How It Works + +JitAsm spawns a child process with JIT diagnostic environment variables (`DOTNET_JitDisasm`) to capture disassembly output. + +By default, the tool simulates **Tier-1 recompilation with Dynamic PGO** — the same compilation tier that runs in production after warm-up. This produces the most representative assembly: smaller code, eliminated static base helpers, and PGO-guided branch layout. + +### Compilation Tiers + +The .NET runtime compiles methods through multiple stages: + +| Stage | Description | Typical Size | +|-------|-------------|-------------| +| **Tier-0** | Quick JIT, minimal optimization (`minopt`) | Largest | +| **Instrumented Tier-0** | Tier-0 + PGO probes for profiling | Larger still | +| **Tier-1 + PGO** (default) | Full optimization with profile data | **Smallest** | +| **FullOpts** (`--fullopts`) | Full optimization, no PGO, no tiering | Middle | + +Key difference between Tier-1 and FullOpts: +- **FullOpts** (`DOTNET_TieredCompilation=0`) compiles in a single pass. Cross-module static base helpers (`CORINFO_HELP_GET_NONGCSTATIC_BASE`) persist because the JIT hasn't resolved the static base address yet. +- **Tier-1 + PGO** compiles after Tier-0 has executed. By then, static bases are resolved, and the JIT can embed addresses directly — eliminating the helper entirely. PGO data also improves branch layout and inlining decisions. + +### Two-Pass Static Constructor Handling + +When a method references static fields, the JIT may include static constructor initialization checks (`CORINFO_HELP_GET_NONGCSTATIC_BASE`, `CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE`). JitAsm automatically detects these and runs a second pass with static constructors pre-initialized, showing the steady-state optimized code path. + +In Tier-1 mode (default), many of these helpers are eliminated naturally since the static bases are already resolved by Tier-0 execution. + +### Tier-1 Simulation + +The tool drives through all PGO compilation stages: + +1. **Invoke** method via reflection (triggers Tier-0 JIT + installs call counting stubs) +2. **Wait** for Instrumented Tier-0 recompilation (PGO profiling version) +3. **Invoke** again (triggers call counter through instrumented code) +4. **Wait** for Tier-1 recompilation (fully optimized with PGO data) +5. **Capture** the final Tier-1 assembly output + +Critical env var: `DOTNET_TC_CallCountingDelayMs=0` — without this, counting stubs aren't installed in time and Tier-1 never triggers. + +## Usage + +```bash +dotnet run --project tools/JitAsm -c Release -- [options] +``` + +### Options + +| Option | Description | +|--------|-------------| +| `-a, --assembly ` | Path to the assembly containing the method (required) | +| `-t, --type ` | Fully qualified type name (optional, searches all types if not specified) | +| `-m, --method ` | Method name to disassemble (required) | +| `--type-params ` | Method generic type parameters (comma-separated) | +| `--class-type-params ` | Class generic type parameters (comma-separated, for generic containing types) | +| `--fullopts` | Use single-pass FullOpts compilation (`TieredCompilation=0`) instead of Tier-1 + PGO | +| `--no-annotate` | Disable per-instruction annotations (throughput, latency, uops, ports). Annotations are **on by default** | +| `--arch ` | Target microarchitecture for annotations (default: `zen4`). See [Supported Architectures](#supported-architectures) | +| `--skip-cctor-detection` | Skip automatic static constructor detection (single pass only) | +| `-v, --verbose` | Show resolution details and both passes | + +### Default Mode: Tier-1 + PGO + +By default, the tool simulates Tier-1 recompilation with Dynamic PGO. This is the compilation tier code runs at in production and produces the most optimized output. + +```bash +# Default: Tier-1 + PGO (production-representative) +dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName +``` + +### FullOpts Mode + +Use `--fullopts` when you need the older single-pass compilation. This is faster (no invocation/sleep cycle) but may show static base helpers and lack PGO-guided optimizations. + +```bash +# FullOpts: single compilation, no PGO +dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName --fullopts +``` + +## Prerequisites + +Build the target project in Release configuration before disassembling: + +```bash +dotnet build src/Nethermind/Nethermind.Evm -c Release +``` + +Assemblies are output to `src/Nethermind/artifacts/bin/{ProjectName}/release/{ProjectName}.dll`. For example: +- `src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll` +- `src/Nethermind/artifacts/bin/Nethermind.Core/release/Nethermind.Core.dll` + +## Platform Notes (Windows) + +On Windows, follow these rules to avoid argument parsing errors: + +- **Avoid quoted strings** around paths and type parameters — they can cause `'' was not matched` errors +- **Use forward slashes** in paths (works on both Windows and Linux) +- **Put the entire command on a single line** — avoid line continuations (`\`) +- Use absolute paths when possible for clarity + +```bash +# Good (Windows) +dotnet run --project tools/JitAsm -c Release -- -a D:/GitHub/nethermind/src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmStack -m PushBytesRef + +# Bad (may fail on Windows) +dotnet run --project tools/JitAsm -c Release -- \ + -a "src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll" \ + -t "Nethermind.Evm.EvmStack" \ + -m PushBytesRef +``` + +On Linux/macOS, both styles work fine. + +## Examples + +### Basic Usage + +```bash +# Disassemble a simple method (searches all types, default Tier-1 + PGO) +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m PushBytesRef +``` + +### With Type Specification + +```bash +# Specify the containing type to narrow the search +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmStack -m PushBytesRef +``` + +### Generic Methods (Method-Level Type Parameters) + +Use `--type-params` for type parameters on the method itself: + +```bash +# PushBytes(...) where TTracingInst = OffFlag +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmStack -m PushBytes --type-params Nethermind.Core.OffFlag +``` + +### Generic Classes (Class-Level Type Parameters) + +Use `--class-type-params` when the containing class is generic. This is separate from `--type-params` which is for method-level generics. Generic type names can omit the backtick arity suffix (e.g., `TransactionProcessorBase` matches `TransactionProcessorBase`1`). + +```bash +# TransactionProcessorBase.Execute(...) +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.TransactionProcessing.TransactionProcessorBase -m Execute --class-type-params Nethermind.Evm.GasPolicy.EthereumGasPolicy --type-params Nethermind.Core.OffFlag +``` + +### Multiple Generic Parameters + +Use comma-separated type names (no spaces after commas): + +```bash +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m SomeGenericMethod --type-params System.Int32,System.String +``` + +### EVM Instruction Example + +Disassemble the MUL opcode implementation with specific gas policy and tracing flags: + +```bash +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmInstructions -m InstructionMath2Param --type-params Nethermind.Evm.GasPolicy.EthereumGasPolicy,Nethermind.Evm.EvmInstructions+OpMul,Nethermind.Core.OffFlag +``` + +This shows the JIT output for `InstructionMath2Param`: +- `EthereumGasPolicy` - Standard Ethereum gas accounting +- `OpMul` - The multiplication operation (nested type, use `+` syntax) +- `OffFlag` - Tracing disabled (allows dead code elimination of tracing paths) + +Other math operations can be viewed by replacing `OpMul` with: `OpAdd`, `OpSub`, `OpDiv`, `OpMod`, `OpSDiv`, `OpSMod`, `OpLt`, `OpGt`, `OpSLt`, `OpSGt`, `OpEq`, `OpAnd`, `OpOr`, `OpXor`. + +### Type Aliases + +Common C# type aliases are supported: + +```bash +# These are equivalent +--type-params int +--type-params System.Int32 +``` + +Supported aliases: `bool`, `byte`, `sbyte`, `char`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double`, `decimal`, `string`, `object`, `nint`, `nuint` + +### Verbose Mode + +```bash +# Show detailed output including cctor detection and compilation tier info +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m PushBytesRef -v +``` + +### Skip Static Constructor Detection + +```bash +# Single pass only (faster, but may show cctor overhead) +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m SomeMethod --skip-cctor-detection +``` + +### FullOpts vs Tier-1 Comparison + +Compare the same method under different compilation modes to see what Tier-1 + PGO eliminates: + +```bash +# Tier-1 + PGO (default) — production-representative +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.BlockExecutionContext -m GetBlobBaseFee > tier1.asm 2>&1 + +# FullOpts — single-pass, no PGO +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.BlockExecutionContext -m GetBlobBaseFee --fullopts > fullopts.asm 2>&1 + +# Compare +diff tier1.asm fullopts.asm +``` + +Example result for `GetBlobBaseFee`: +- **FullOpts**: 356 bytes, has `CORINFO_HELP_GET_NONGCSTATIC_BASE` +- **Tier-1 + PGO**: 236 bytes (34% smaller), helper eliminated entirely + +## Example Output + +Default Tier-1 + PGO output: + +``` +; Assembly listing for method Namespace.Type:Method() (Tier1) +; Emitting BLENDED_CODE for generic X64 + VEX + EVEX on Windows +; Tier1 code ← Tier-1 recompilation (production tier) +; optimized code +; optimized using Dynamic PGO +; rsp based frame +; partially interruptible +; with Dynamic PGO: fgCalledCount is 50 + +G_M000_IG01: ;; offset=0x0000 + sub rsp, 40 + ... + +; Total bytes of code 236 +``` + +FullOpts output (with `--fullopts`): + +``` +; Assembly listing for method Namespace.Type:Method() (FullOpts) +; FullOpts code ← Single-pass FullOpts +; optimized code +; No PGO data ← No profile data available + ... + +; Total bytes of code 356 +``` + +## Instruction Annotations + +Per-instruction performance data from [uops.info](https://uops.info) is included **by default**, showing throughput, latency, micro-op count, and execution port usage inline with the assembly output. Use `--no-annotate` to disable. + +### Setup + +Download the uops.info instruction database (110MB, one-time): + +```bash +curl -o tools/JitAsm/instructions.xml https://uops.info/instructions.xml +``` + +On first use, the XML is preprocessed into a compact binary cache (`instructions.db`, ~1-2MB). Subsequent runs load from the cache. + +### Usage + +```bash +# Annotations are on by default (zen4) +dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName + +# Use a different architecture +dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName --arch alder-lake + +# Disable annotations +dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName --no-annotate +``` + +### Annotation Format + +Each instruction line gets an end-of-line annotation: + +``` + add rax, rcx ; [TP:0.25 | Lat: 1 | Uops:1] + mov qword ptr [rbp+10h], rax ; [TP:0.50 | Lat:11 | Uops:1] + vmovdqu ymm0, ymmword ptr [rsi] ; [TP:0.50 | Lat: 8 | Uops:1 | 1*FP_LD] +``` + +| Field | Meaning | +|-------|---------| +| `TP` | Reciprocal throughput (cycles per instruction). Lower = faster. | +| `Lat` | Latency (cycles from input ready to output ready). Matters for dependency chains. | +| `Uops` | Micro-op count. Fewer = less pressure on the execution engine. | +| Ports | Execution port usage (e.g., `1*p0156`). Shows which functional units are used. | + +### Supported Architectures + +| `--arch` value | CPU | Description | +|----------------|-----|-------------| +| `zen4` (default) | AMD Zen 4 | Ryzen 7000 / EPYC 9004 | +| `zen3` | AMD Zen 3 | Ryzen 5000 / EPYC 7003 | +| `zen2` | AMD Zen 2 | Ryzen 3000 / EPYC 7002 | +| `alder-lake` | Intel ADL-P | 12th Gen Core (P-cores) | +| `rocket-lake` | Intel RKL | 11th Gen Core | +| `ice-lake` | Intel ICL | 10th Gen Core | +| `tiger-lake` | Intel TGL | 11th Gen Mobile | +| `skylake` | Intel SKL | 6th Gen Core | + +When data for the selected architecture is unavailable for a specific instruction, the tool falls back to a nearby architecture in the same family. + +### Annotation Details + +**Mnemonic mapping:** The .NET JIT uses Intel-style mnemonic aliases (e.g., `je`, `jne`, `ja`) while uops.info uses canonical forms (`jz`, `jnz`, `jnbe`). The annotator automatically maps between them for conditional jumps, conditional moves (`cmove`→`cmovz`), and set-byte instructions (`sete`→`setz`). + +**LEA handling:** The `lea` instruction computes an address but doesn't access memory. Its second operand is classified as `agen` (address generation) rather than a memory load, matching the uops.info encoding. + +**.NET JIT-specific prefixes:** +- `gword ptr` — GC-tracked pointer-width memory reference. Treated as `m64` on x64. +- `bword ptr` — Pointer-width memory reference without GC tracking. Treated as `m64` on x64. +- `SHORT` / `NEAR` — Jump distance hints from the JIT. Stripped before operand classification. + +**Skipped instructions:** `call`, `ret`, `int3`, `nop`, and `int` are not annotated. `ret` is skipped because uops.info's `TP_unrolled` for `ret` reflects return stack buffer mispredictions in microbenchmarks, not real-world performance. + +## Iterative Workflow + +JitAsm is designed for rapid iteration when optimizing code: + +1. **Modify source code** +2. **Build the target assembly:** + ```bash + dotnet build src/Nethermind/Nethermind.Evm -c Release + ``` +3. **View the generated assembly:** + ```bash + dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m YourMethod + ``` +4. **Compare output between iterations** + +For fast iteration during optimization, `--fullopts` skips the invocation/sleep cycle (~4s faster) at the cost of less representative output. Use default Tier-1 for final verification. + +## Environment Variables + +The tool sets these JIT diagnostic variables for the child process: + +### Default Mode (Tier-1 + PGO) + +| Variable | Value | Purpose | +|----------|-------|---------| +| `DOTNET_TieredCompilation` | `1` | Enable tiered compilation | +| `DOTNET_TieredPGO` | `1` | Enable Dynamic PGO | +| `DOTNET_TC_CallCountThreshold` | `1` | Minimal call count before recompilation | +| `DOTNET_TC_CallCountingDelayMs` | `0` | Install counting stubs immediately (critical) | +| `DOTNET_JitDisasm` | `` | Output disassembly for matching methods | +| `DOTNET_JitDiffableDasm` | `1` | Consistent, diffable output format | + +### FullOpts Mode (`--fullopts`) + +| Variable | Value | Purpose | +|----------|-------|---------| +| `DOTNET_TieredCompilation` | `0` | Disable tiered compilation | +| `DOTNET_TC_QuickJit` | `0` | Disable quick JIT | +| `DOTNET_JitDisasm` | `` | Output disassembly for matching methods | +| `DOTNET_JitDiffableDasm` | `1` | Consistent, diffable output format | + +## Comparing Generic Specializations (OffFlag vs OnFlag) + +A common workflow is comparing two generic instantiations to verify dead code elimination. Nethermind uses the `IFlag` pattern (`OnFlag`/`OffFlag`) for compile-time branch elimination. + +```bash +# Generate ASM for tracing disabled (common case) +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmInstructions -m InstructionMath2Param --type-params Nethermind.Evm.GasPolicy.EthereumGasPolicy,Nethermind.Evm.EvmInstructions+OpAdd,Nethermind.Core.OffFlag > off.asm 2>&1 + +# Generate ASM for tracing enabled +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmInstructions -m InstructionMath2Param --type-params Nethermind.Evm.GasPolicy.EthereumGasPolicy,Nethermind.Evm.EvmInstructions+OpAdd,Nethermind.Core.OnFlag > on.asm 2>&1 + +# Compare: the OnFlag version should have tracing code that is absent from OffFlag +diff off.asm on.asm +``` + +**Red flag:** If both versions have identical code size, the JIT may not be eliminating dead code as expected. + +## Extracting Metrics from ASM Output + +Use these patterns to extract key metrics from the disassembly output for tracking optimization progress: + +```bash +# Code size (from the last line, e.g., "; Total bytes of code 40") +tail -1 output.asm + +# Basic block count (fewer = simpler control flow = better branch prediction) +grep -c "G_M000_IG[0-9]*:" output.asm + +# Branch count (conditional jumps) +grep -cE "\bj(e|ne|g|l|ge|le|a|b|ae|be|nz|z)\b" output.asm + +# Call count (should be minimal in hot paths) +grep -c "call" output.asm + +# Register saves in prologue (>4 push = register pressure issue) +grep -c "push" output.asm + +# Stack frame size (from prologue, e.g., "sub rsp, 40") +grep "sub.*rsp" output.asm +``` + +## Reading Assembly Output + +### Output Structure + +The disassembly header contains useful metadata: + +``` +; Assembly listing for method Namespace.Type:Method() (Tier1) +; Emitting BLENDED_CODE for generic X64 + VEX + EVEX on Windows +; Tier1 code ← Tier-1 optimized (production tier) +; optimized code +; optimized using Dynamic PGO ← PGO-guided optimizations applied +; rsp based frame +; partially interruptible +; with Dynamic PGO: fgCalledCount is 50 ← How many times method was called during profiling +``` + +The code is organized into **basic blocks** labeled `G_M000_IG01`, `G_M000_IG02`, etc. Each block is a straight-line sequence of instructions ending with a branch or fall-through. + +### Common Red Flags + +| Pattern in ASM | Meaning | Severity | +|----------------|---------|----------| +| `call CORINFO_HELP_BOX` | Boxing allocation in hot path | High | +| `call CORINFO_HELP_VIRTUAL_FUNC_PTR` | Virtual dispatch not devirtualized (~25-50 cycles) | High | +| `call CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE` | Static constructor check | High | +| `call CORINFO_HELP_GET_NONGCSTATIC_BASE` | Cross-module static base resolution (eliminated in Tier-1) | Medium* | +| `call CORINFO_HELP_ASSIGN_REF` | GC write barrier (bad in loops) | High | +| `callvirt [Interface:Method()]` | Interface dispatch not devirtualized | High | +| `call [SmallType:SmallMethod()]` | Failed inlining of small method | Medium | +| `cmp ...; jae THROW_LABEL` | Bounds check (may be eliminable) | Medium | +| `idiv` | Division by non-constant (~20-80 cycles) | Medium | +| `>4 push` instructions in prologue | Register pressure | Medium | +| `vmovdqu32 zmmword ptr` in prologue | Large stack zeroing (cold locals bloating hot path) | Low | + +*`CORINFO_HELP_GET_NONGCSTATIC_BASE` appears in FullOpts mode for cross-module static field access but is naturally eliminated in Tier-1 + PGO. If you see it in default (Tier-1) output, it indicates the static base couldn't be resolved at runtime — a real issue. + +### What Good ASM Looks Like + +- Few basic blocks (simple control flow) +- No `call` instructions in the hot path (everything inlined) +- Compact code size (better I-cache utilization) +- No redundant loads of the same memory location +- SIMD instructions (`vpaddd`, `vpxor`, etc.) for bulk data operations +- `cmov*` (conditional moves) instead of branches where appropriate +- Fall-through on the hot path, forward jumps to cold/error paths +- `; optimized using Dynamic PGO` in the header (confirms PGO applied) + +## Troubleshooting + +### No disassembly output + +- Verify the assembly path is correct and the DLL exists +- Check if the method name is spelled correctly (case-sensitive) +- Try without the `-t` option to search all types +- Use `-v` for verbose output to see what's happening +- Ensure you built with `-c Release` (Debug builds produce different code) + +### Tier-1 produces no output / shows Tier-0 instead + +- The Tier-1 simulation invokes the method via reflection with default arguments. Methods taking **ref struct** parameters (`Span`, `ReadOnlySpan`, `Memory`) cannot be invoked this way — the simulation silently fails and produces no output. +- **Workaround:** Use `--fullopts` for methods with ref struct parameters. It compiles in a single pass without invocation. +- For other methods: this means the Tier-1 recompilation didn't fire. The method may not be invocable with null/default arguments. +- Check `-v` output for error messages during invocation phases. + +### Method not found + +- Ensure the assembly is built (Release configuration recommended) +- For generic methods, provide all required type parameters via `--type-params` +- For methods in generic classes, provide class type parameters via `--class-type-params` +- Use fully qualified type names for type parameters from other assemblies +- Generic type names can omit the backtick arity (e.g., `TransactionProcessorBase` matches `TransactionProcessorBase`1`) + +### Multiple methods with same name + +- Specify the type with `-t` to narrow the search +- The tool will use the first matching overload if multiple exist + +### On Windows: `'' was not matched` error + +- Remove all quotes from arguments — pass paths and type names unquoted +- Replace backslashes with forward slashes in paths +- Put the entire command on a single line + +## Building + +```bash +dotnet build tools/JitAsm -c Release +``` diff --git a/tools/JitAsm/StaticCtorDetector.cs b/tools/JitAsm/StaticCtorDetector.cs new file mode 100644 index 000000000000..1b512dceb957 --- /dev/null +++ b/tools/JitAsm/StaticCtorDetector.cs @@ -0,0 +1,175 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.RegularExpressions; + +namespace JitAsm; + +internal static partial class StaticCtorDetector +{ + // Patterns for detecting static constructor calls in JIT disassembly + // Example: call Namespace.Type:.cctor() + [GeneratedRegex(@"call\s+(?[\w.+`\[\],]+):\.cctor\(\)", RegexOptions.Compiled)] + private static partial Regex CctorCallPattern(); + + // JIT helpers for static field initialization + // Matches both shared and non-shared variants, with optional brackets: + // call CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE + // call [CORINFO_HELP_GET_NONGCSTATIC_BASE] + // call CORINFO_HELP_CLASSINIT_SHARED_DYNAMICCLASS + [GeneratedRegex(@"call\s+\[?CORINFO_HELP_(?:(?:GETSHARED_|GET_)(?:NON)?GCSTATIC_BASE|CLASSINIT_SHARED_DYNAMICCLASS)\]?", RegexOptions.Compiled)] + private static partial Regex StaticHelperPattern(); + + // Pattern for type references in static helper context + // The JIT output often shows the type being initialized nearby + // Example: ; Namespace.Type + [GeneratedRegex(@";\s*(?[\w.+`\[\],]+)\s*$", RegexOptions.Compiled | RegexOptions.Multiline)] + private static partial Regex TypeCommentPattern(); + + // Pattern for detecting lazy initialization checks + // Example: cmp dword ptr [Namespace.Type:initialized], 0 + [GeneratedRegex(@"\[(?[\w.+`\[\],]+):", RegexOptions.Compiled)] + private static partial Regex StaticFieldAccessPattern(); + + // Pattern for extracting types from call targets in JIT output. + // In diffable mode, long call targets wrap to the next line, e.g.: + // call + // [Nethermind.Core.Eip4844Constants:get_MinBlobGasPrice():...] + // In non-diffable mode, they appear on one line: + // call [Nethermind.Core.Eip4844Constants:get_MinBlobGasPrice():...] + // This pattern matches the "[Type:Method" portion (possibly with [r11] prefix for callvirt). + [GeneratedRegex(@"(?:\[(?:r\d+\])?)?\[?(?[\w.+`\[\],]+):(?!:)", RegexOptions.Compiled)] + private static partial Regex CallTargetTypePattern(); + + public static IReadOnlyList DetectStaticCtors(string disassemblyOutput) + { + var detectedTypes = new HashSet(StringComparer.Ordinal); + + // Detect direct .cctor calls + foreach (Match match in CctorCallPattern().Matches(disassemblyOutput)) + { + var typeName = NormalizeTypeName(match.Groups["type"].Value); + if (!string.IsNullOrEmpty(typeName)) + { + detectedTypes.Add(typeName); + } + } + + // Check for static helper calls and try to find associated types + if (StaticHelperPattern().IsMatch(disassemblyOutput)) + { + int typesBeforeHelperScan = detectedTypes.Count; + + // Look for type references near the helper calls (±3 lines) + var lines = disassemblyOutput.Split('\n'); + for (int i = 0; i < lines.Length; i++) + { + if (StaticHelperPattern().IsMatch(lines[i])) + { + // Check surrounding lines for type context + for (int j = Math.Max(0, i - 3); j <= Math.Min(lines.Length - 1, i + 3); j++) + { + var typeMatch = TypeCommentPattern().Match(lines[j]); + if (typeMatch.Success) + { + var typeName = NormalizeTypeName(typeMatch.Groups["type"].Value); + if (!string.IsNullOrEmpty(typeName) && IsValidTypeName(typeName)) + { + detectedTypes.Add(typeName); + } + } + + var fieldMatch = StaticFieldAccessPattern().Match(lines[j]); + if (fieldMatch.Success) + { + var typeName = NormalizeTypeName(fieldMatch.Groups["type"].Value); + if (!string.IsNullOrEmpty(typeName) && IsValidTypeName(typeName)) + { + detectedTypes.Add(typeName); + } + } + } + } + } + + // Fallback: if helpers were found but no types extracted from nearby context + // (common when JitDiffableDasm strips annotations or helper has no nearby type comment), + // scan the entire method for types referenced in call instructions. + // In diffable mode, long call targets wrap to the next line: + // call + // [Nethermind.Core.Eip4844Constants:get_MinBlobGasPrice():...] + // Pre-running extra cctors is cheap; missing the right one means stale output. + if (detectedTypes.Count == typesBeforeHelperScan) + { + for (int i = 0; i < lines.Length; i++) + { + var line = lines[i]; + // Check current line and continuation lines after bare "call" instructions + if (line.TrimStart().StartsWith("call") || (i > 0 && lines[i - 1].TrimEnd().EndsWith("call"))) + { + var callMatch = CallTargetTypePattern().Match(line); + if (callMatch.Success) + { + var typeName = NormalizeTypeName(callMatch.Groups["type"].Value); + if (!string.IsNullOrEmpty(typeName) && IsValidTypeName(typeName)) + { + detectedTypes.Add(typeName); + } + } + } + } + } + } + + var result = new List(detectedTypes); + result.Sort(StringComparer.Ordinal); + return result; + } + + private static string NormalizeTypeName(string typeName) + { + // Remove generic arity suffix if present (e.g., `1, `2) + var tickIndex = typeName.IndexOf('`'); + if (tickIndex > 0) + { + // Keep up to and including the backtick and number for proper type resolution + var endIndex = tickIndex + 1; + while (endIndex < typeName.Length && char.IsDigit(typeName[endIndex])) + { + endIndex++; + } + + // If there's more after the generic arity, check for nested types + if (endIndex < typeName.Length && typeName[endIndex] == '+') + { + // Keep nested type info + return typeName; + } + + return typeName[..endIndex]; + } + + return typeName.Trim(); + } + + private static bool IsValidTypeName(string typeName) + { + // Filter out obvious non-type names + if (string.IsNullOrWhiteSpace(typeName)) + return false; + + // Must contain at least one letter + if (!typeName.Any(char.IsLetter)) + return false; + + // Should not be just a keyword or register name + var lowered = typeName.ToLowerInvariant(); + string[] invalidNames = ["rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rsp", "rbp", + "eax", "ebx", "ecx", "edx", "esi", "edi", "esp", "ebp", + "ptr", "dword", "qword", "byte", "word"]; + if (invalidNames.Contains(lowered)) + return false; + + return true; + } +} diff --git a/tools/Kute/Nethermind.Tools.Kute/Program.cs b/tools/Kute/Nethermind.Tools.Kute/Program.cs index 6c640ab0f808..e4bd6d9bcfa1 100644 --- a/tools/Kute/Nethermind.Tools.Kute/Program.cs +++ b/tools/Kute/Nethermind.Tools.Kute/Program.cs @@ -14,6 +14,7 @@ using Nethermind.Tools.Kute.SecretProvider; using Nethermind.Tools.Kute.SystemClock; using System.CommandLine; +using System.Net; namespace Nethermind.Tools.Kute; @@ -56,7 +57,18 @@ private static IServiceProvider BuildServiceProvider(ParseResult parseResult) collection.AddSingleton(); collection.AddSingleton(); - collection.AddSingleton(); + collection.AddSingleton(_ => + { + // Disable proxy auto-detection to avoid timeout (~2s) on Windows. + // Each Kute invocation is a separate process, so the proxy lookup + // would otherwise be paid on every single measured request. + var handler = new SocketsHttpHandler + { + UseProxy = false, + AutomaticDecompression = DecompressionMethods.None, + }; + return new HttpClient(handler); + }); collection.AddSingleton(new FileSecretProvider(parseResult.GetValue(Config.JwtSecretFilePath)!)); collection.AddSingleton(provider => new TtlAuth( diff --git a/tools/gas-benchmarks b/tools/gas-benchmarks new file mode 160000 index 000000000000..56714853a2f1 --- /dev/null +++ b/tools/gas-benchmarks @@ -0,0 +1 @@ +Subproject commit 56714853a2f17d7a556cae075212c363afbc4678 From 44c4e4832719875827c6a0389466e58d14a73303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Tue, 17 Feb 2026 22:53:56 +0100 Subject: [PATCH 06/13] Revert "Merge branch 'kch/gas-benchmarks-bdn' into perf/extcodesize-cache-bdn" This reverts commit bd8098c67fe9c97d8e1014f436db774cea72ba94. --- .github/workflows/build-tools.yml | 1 - .github/workflows/ci-surge.yml | 167 --- .github/workflows/ci-taiko.yml | 62 +- .github/workflows/gas-benchmarks-bdn.yml | 161 --- .github/workflows/pr-labeler.yml | 201 --- .gitignore | 4 - .gitmodules | 4 - AGENTS.md | 108 +- Directory.Packages.props | 49 +- Dockerfile | 4 +- Dockerfile.chiseled | 4 +- README.md | 2 +- cspell.json | 113 -- scripts/benchmarks/compare_bdn_results.py | 156 --- scripts/benchmarks/merge_bdn_results.py | 53 - scripts/build/Dockerfile | 2 +- .../Chains/arena-z-mainnet.json.zst | Bin 87959 -> 86216 bytes .../Chains/arena-z-sepolia.json.zst | Bin 84295 -> 82744 bytes .../Chains/automata-mainnet.json.zst | Bin 79847 -> 78345 bytes src/Nethermind/Chains/base-mainnet.json.zst | Bin 38338 -> 37232 bytes src/Nethermind/Chains/base-sepolia.json.zst | Bin 46230 -> 45456 bytes src/Nethermind/Chains/bob-mainnet.json.zst | Bin 70308 -> 69168 bytes src/Nethermind/Chains/boba-mainnet.json.zst | Bin 2564 -> 1502 bytes src/Nethermind/Chains/boba-sepolia.json.zst | Bin 2538 -> 1500 bytes src/Nethermind/Chains/camp-sepolia.json.zst | Bin 75484 -> 73911 bytes .../Chains/celo-sep-sepolia.json.zst | Bin 87789 -> 86242 bytes src/Nethermind/Chains/cyber-mainnet.json.zst | Bin 73511 -> 72230 bytes src/Nethermind/Chains/cyber-sepolia.json.zst | Bin 73487 -> 72241 bytes src/Nethermind/Chains/dictionary | Bin 65536 -> 65536 bytes .../Chains/ethernity-mainnet.json.zst | Bin 72175 -> 70753 bytes .../Chains/ethernity-sepolia.json.zst | Bin 75578 -> 74028 bytes .../Chains/fraxtal-mainnet.json.zst | Bin 144497 -> 143082 bytes src/Nethermind/Chains/funki-mainnet.json.zst | Bin 79866 -> 78337 bytes src/Nethermind/Chains/funki-sepolia.json.zst | Bin 73514 -> 72257 bytes .../Chains/hashkeychain-mainnet.json.zst | Bin 83435 -> 81902 bytes src/Nethermind/Chains/ink-mainnet.json.zst | Bin 87925 -> 86144 bytes src/Nethermind/Chains/ink-sepolia.json.zst | Bin 87884 -> 86245 bytes src/Nethermind/Chains/lisk-mainnet.json.zst | Bin 70230 -> 69115 bytes src/Nethermind/Chains/lisk-sepolia.json.zst | Bin 71731 -> 70141 bytes src/Nethermind/Chains/lyra-mainnet.json.zst | Bin 37934 -> 36671 bytes src/Nethermind/Chains/metal-mainnet.json.zst | Bin 70318 -> 69180 bytes src/Nethermind/Chains/metal-sepolia.json.zst | Bin 38283 -> 36972 bytes src/Nethermind/Chains/mint-mainnet.json.zst | Bin 70307 -> 69143 bytes src/Nethermind/Chains/mode-mainnet.json.zst | Bin 38311 -> 37229 bytes src/Nethermind/Chains/mode-sepolia.json.zst | Bin 42379 -> 41330 bytes src/Nethermind/Chains/op-mainnet.json.zst | Bin 2676 -> 1587 bytes src/Nethermind/Chains/op-sepolia.json.zst | Bin 49842 -> 48781 bytes .../Chains/orderly-mainnet.json.zst | Bin 38301 -> 37213 bytes src/Nethermind/Chains/ozean-sepolia.json.zst | Bin 72077 -> 70705 bytes .../Chains/pivotal-sepolia.json.zst | Bin 75415 -> 73882 bytes .../Chains/polynomial-mainnet.json.zst | Bin 70295 -> 69011 bytes src/Nethermind/Chains/race-mainnet.json.zst | Bin 72014 -> 70604 bytes src/Nethermind/Chains/race-sepolia.json.zst | Bin 72083 -> 70667 bytes .../Chains/redstone-mainnet.json.zst | Bin 70143 -> 69018 bytes .../Chains/settlus-mainnet-mainnet.json.zst | Bin 84357 -> 82794 bytes .../Chains/settlus-sepolia-sepolia.json.zst | Bin 87753 -> 86117 bytes src/Nethermind/Chains/shape-mainnet.json.zst | Bin 70130 -> 69036 bytes src/Nethermind/Chains/shape-sepolia.json.zst | Bin 73439 -> 72315 bytes .../Chains/soneium-mainnet.json.zst | Bin 87970 -> 86239 bytes .../Chains/soneium-minato-sepolia.json.zst | Bin 80019 -> 78497 bytes src/Nethermind/Chains/sseed-mainnet.json.zst | Bin 72166 -> 70707 bytes src/Nethermind/Chains/swan-mainnet.json.zst | Bin 69279 -> 67884 bytes src/Nethermind/Chains/swell-mainnet.json.zst | Bin 84459 -> 82890 bytes src/Nethermind/Chains/tbn-mainnet.json.zst | Bin 83494 -> 81789 bytes src/Nethermind/Chains/tbn-sepolia.json.zst | Bin 75515 -> 73978 bytes .../Chains/unichain-mainnet.json.zst | Bin 118352 -> 116381 bytes .../Chains/unichain-sepolia.json.zst | Bin 103133 -> 101586 bytes .../Chains/worldchain-mainnet.json.zst | Bin 73618 -> 72303 bytes .../Chains/worldchain-sepolia.json.zst | Bin 73622 -> 72376 bytes .../Chains/xterio-eth-mainnet.json.zst | Bin 73537 -> 72282 bytes src/Nethermind/Chains/zora-mainnet.json.zst | Bin 42378 -> 41317 bytes src/Nethermind/Chains/zora-sepolia.json.zst | Bin 38329 -> 37098 bytes src/Nethermind/Nethermind.Api/IBasicApi.cs | 2 +- src/Nethermind/Nethermind.Api/IInitConfig.cs | 2 +- src/Nethermind/Nethermind.Api/InitConfig.cs | 2 +- .../Nethermind.Api/NethermindApi.cs | 4 +- .../Core/FastHashBenchmarks.cs | 47 - .../Core/SeqlockCacheBenchmarks.cs | 345 ----- .../BackgroundTaskSchedulerBenchmarks.cs | 181 --- .../BeaconBlockRootHandlerTests.cs | 2 - .../BlockFinderExtensionsTests.cs | 1 - .../BlockProcessorTests.cs | 1 - .../BlockTreeSuggestPacerTests.cs | 1 - .../BlockTreeTests.cs | 2 - .../BlockchainProcessorTests.cs | 35 +- .../BlockhashProviderTests.cs | 1 - .../Blocks/BadBlockStoreTests.cs | 1 - .../Blocks/BlockStoreTests.cs | 1 - .../Blocks/HeaderStoreTests.cs | 2 +- .../Builders/FilterBuilder.cs | 9 +- .../CachedCodeInfoRepositoryTests.cs | 51 +- .../ChainHeadReadOnlyStateProviderTests.cs | 1 - .../Consensus/ClefSignerTests.cs | 1 - .../Consensus/CompositeTxSourceTests.cs | 1 - .../Consensus/NethDevSealEngineTests.cs | 1 - .../Consensus/NullSealEngineTests.cs | 1 - .../Consensus/NullSignerTests.cs | 1 - .../Consensus/OneByOneTxSourceTests.cs | 1 - .../Consensus/SealEngineExceptionTests.cs | 1 - .../Consensus/SignerTests.cs | 1 - .../Consensus/SinglePendingTxSelectorTests.cs | 1 - .../DaoDataTests.cs | 1 - .../Data/FileLocalDataSourceTests.cs | 1 - .../ExitOnBlocknumberHandlerTests.cs | 1 - .../Filters/AddressFilterTests.cs | 1 - .../Filters/FilterManagerTests.cs | 17 +- .../Filters/FilterStoreTests.cs | 3 - .../Filters/LogFilterTests.cs | 2 - .../Filters/LogIndexFilterVisitorTests.cs | 413 ------ .../Filters/TestProcessingContext.cs | 55 - .../Find/LogFinderTests.cs | 137 +- .../FullPruning/CopyTreeVisitorTests.cs | 19 +- .../FullPruning/FullPrunerTests.cs | 22 +- .../FullPruning/FullPruningDiskTest.cs | 34 +- .../PruningTriggerPruningStrategyTests.cs | 3 +- .../BuildBlockOnEachPendingTxTests.cs | 1 - .../Producers/BuildBlockRegularlyTests.cs | 1 - .../BuildBlocksWhenRequestedTests.cs | 1 - .../CompositeBlockProductionTriggerTests.cs | 1 - .../Producers/DevBlockproducerTests.cs | 1 - .../Producers/IfPoolIsNotEmptyTests.cs | 1 - .../Proofs/ReceiptTrieTests.cs | 23 - .../Proofs/TxTrieTests.cs | 52 +- .../Proofs/WithdrawalTrieTests.cs | 1 - .../ReadOnlyBlockTreeTests.cs | 2 - .../Receipts/KeccaksIteratorTests.cs | 1 - .../Receipts/PersistentReceiptStorageTests.cs | 16 +- .../Receipts/ReceiptsIteratorTests.cs | 3 +- .../Receipts/ReceiptsRecoveryTests.cs | 2 - .../Receipts/ReceiptsRootTests.cs | 1 - .../ReorgDepthFinalizedStateProviderTests.cs | 3 +- .../Nethermind.Blockchain.Test/ReorgTests.cs | 5 +- .../Rewards/NoBlockRewardsTests.cs | 1 - .../Rewards/RewardCalculatorTests.cs | 1 - .../Services/HealthHintServiceTests.cs | 1 - .../TransactionGasPriceComparisonTests.cs | 1 - .../TransactionProcessorEip7702Tests.cs | 2 - .../TransactionProcessorTests.cs | 3 +- .../TransactionSelectorTests.cs | 1 - .../TransactionsExecutorTests.cs | 1 - .../Utils/LastNStateRootTrackerTests.cs | 1 - .../Validators/BlockValidatorTests.cs | 2 - .../Validators/HeaderValidatorTests.cs | 2 - .../ShardBlobBlockValidatorTests.cs | 1 - .../Validators/TxValidatorTests.cs | 5 +- .../Validators/UnclesValidatorTests.cs | 2 - .../Validators/WithdrawalValidatorTests.cs | 1 - .../Visitors/DbBlocksLoaderTests.cs | 1 - .../Visitors/StartupTreeFixerTests.cs | 1 - .../BlockhashProvider.cs | 10 +- .../CachedCodeInfoRepository.cs | 10 +- .../EthereumPrecompileProvider.cs | 6 +- .../JavaScript/GethLikeJavaScriptTxTracer.cs | 3 +- .../Custom/Native/Call/NativeCallTracer.cs | 14 +- .../Tracing/GethStyle/GethTraceOptions.cs | 12 +- .../Tracing/ParityStyle/ParityLikeTxTracer.cs | 3 +- .../AuRaBlockFinalizationManager.cs | 1 - .../BlockHeaderExtensions.cs | 19 + .../Scheduler/BackgroundTaskSchedulerTests.cs | 175 --- .../Processing/BlockCachePreWarmer.cs | 2 +- .../Processing/BlockchainProcessor.cs | 12 +- .../IOverridableTxProcessingScope.cs | 0 .../IOverridableTxProcessorSource.cs | 0 .../Processing/IProcessingStats.cs | 36 - .../OverridableTxProcessingScope.cs | 0 .../Processing/ProcessingStats.cs | 36 +- .../Scheduler/BackgroundTaskScheduler.cs | 30 +- .../Tracing/GethStyleTracer.cs | 4 +- .../Nethermind.Core.Test/BytesTests.cs | 142 +- .../Collections/SeqlockCacheTests.cs | 428 ------ .../Encoding/BlockDecoderTests.cs | 88 +- .../Json/ByteArrayConverterTests.cs | 65 - .../Json/Hash256ConverterTests.cs | 63 - .../Json/LongConverterTests.cs | 42 - .../Json/NullableUlongConverterTests.cs | 27 - .../Json/UInt256ConverterTests.cs | 135 -- .../Nethermind.Core.Test/MCSLockTests.cs | 1 - .../Nethermind.Core.Test/RlpTests.cs | 10 - .../Nethermind.Core.Test/TestMemDb.cs | 66 +- src/Nethermind/Nethermind.Core/Address.cs | 12 +- .../Authentication/JwtAuthentication.cs | 367 ++--- src/Nethermind/Nethermind.Core/Block.cs | 7 - src/Nethermind/Nethermind.Core/Bloom.cs | 2 - .../Buffers/ArrayMemoryManager.cs | 22 - .../Nethermind.Core/Caching/ISpanCache.cs | 36 + .../CappedArrayMemoryManager.cs | 45 + .../Collections/ArrayListCore.cs | 14 +- .../Collections/DictionaryExtensions.cs | 53 +- .../Collections/HashHelpers.cs | 132 ++ .../Nethermind.Core/Collections/IHash64bit.cs | 23 - .../Collections/InsertionBehavior.cs | 9 + .../Collections/SeqlockCache.cs | 400 ------ .../Collections/SortedRealList.cs | 80 ++ .../Nethermind.Core/Crypto/Hash256.cs | 4 - .../Nethermind.Core/Crypto/Signature.cs | 4 - .../{Buffers => }/DbSpanMemoryManager.cs | 25 +- .../Nethermind.Core/Extensions/Bytes.cs | 55 +- .../Extensions/DictionaryExtensions.cs | 30 + .../Extensions/IntExtensions.cs | 17 +- .../Extensions/SpanExtensions.cs | 431 ++---- .../Nethermind.Core/IKeyValueStore.cs | 23 +- .../JsonConverters/ByteArrayConverter.cs | 109 +- .../KeyValueStoreExtensions.cs | 181 ++- .../PubSub/CompositePublisher.cs | 30 + src/Nethermind/Nethermind.Core/StorageCell.cs | 46 +- .../Utils/ConcurrentWriteBatcher.cs | 85 ++ .../Nethermind.Db.Rocks/ColumnDb.cs | 116 +- .../Nethermind.Db.Rocks/ColumnsDb.cs | 21 +- .../Nethermind.Db.Rocks/Config/DbConfig.cs | 15 - .../Nethermind.Db.Rocks/Config/IDbConfig.cs | 15 - .../Nethermind.Db.Rocks/DbOnTheRocks.cs | 328 +++-- .../MergeOperatorAdapter.cs | 52 +- src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs | 96 +- .../CompressingStoreTests.cs | 11 +- .../LogIndex/LogIndexStorageCompactorTests.cs | 184 --- .../LogIndexStorageIntegrationTests.cs | 1110 --------------- .../LogIndex/LogIndexStorageTestExtensions.cs | 44 - .../LogIndex/MergeOperatorTests.cs | 247 ---- src/Nethermind/Nethermind.Db/CompressingDb.cs | 101 +- src/Nethermind/Nethermind.Db/DbNames.cs | 1 - .../FullPruning/FullPruningDb.cs | 14 - src/Nethermind/Nethermind.Db/IColumnsDb.cs | 1 - .../Nethermind.Db/IMergeOperator.cs | 4 - .../Nethermind.Db/InMemoryColumnBatch.cs | 9 - .../Nethermind.Db/LogIndex/AverageStats.cs | 31 - .../Nethermind.Db/LogIndex/BlockReceipts.cs | 8 - .../Nethermind.Db/LogIndex/CompactingStats.cs | 14 - .../Nethermind.Db/LogIndex/Compactor.cs | 203 --- .../LogIndex/CompressionAlgorithm.cs | 58 - .../Nethermind.Db/LogIndex/Compressor.cs | 208 --- .../LogIndex/DisabledLogIndexStorage.cs | 38 - .../Nethermind.Db/LogIndex/ExecTimeStats.cs | 44 - .../Nethermind.Db/LogIndex/ILogIndexConfig.cs | 106 -- .../LogIndex/ILogIndexStorage.cs | 56 - .../LogIndex/LogIndexAggregate.cs | 33 - .../Nethermind.Db/LogIndex/LogIndexColumns.cs | 14 - .../Nethermind.Db/LogIndex/LogIndexConfig.cs | 33 - .../LogIndex/LogIndexEnumerator.cs | 153 --- .../LogIndex/LogIndexStateException.cs | 15 - .../Nethermind.Db/LogIndex/LogIndexStorage.cs | 1006 -------------- .../LogIndex/LogIndexUpdateStats.cs | 114 -- .../Nethermind.Db/LogIndex/MergeOperator.cs | 174 --- .../Nethermind.Db/LogIndex/MergeOps.cs | 94 -- .../LogIndex/PostMergeProcessingStats.cs | 29 - src/Nethermind/Nethermind.Db/MemDb.cs | 42 +- .../Nethermind.Db/Nethermind.Db.csproj | 1 - src/Nethermind/Nethermind.Db/ReadOnlyDb.cs | 20 +- .../Nethermind.Db/RocksDbSettings.cs | 12 +- .../Nethermind.Db/SimpleFilePublicKeyDb.cs | 63 +- .../GasBenchmarks/BlockBenchmarkHelper.cs | 232 ---- .../GasBenchmarkColumnProvider.cs | 88 -- .../GasBenchmarks/GasBenchmarkConfig.cs | 36 - .../GasBenchmarks/GasBlockBenchmarks.cs | 112 -- .../GasBlockBuildingBenchmarks.cs | 321 ----- .../GasBenchmarks/GasBlockOneBenchmarks.cs | 97 -- .../GasBenchmarks/GasNewPayloadBenchmarks.cs | 1187 ----------------- .../GasNewPayloadMeasuredBenchmarks.cs | 679 ---------- .../GasBenchmarks/GasPayloadBenchmarks.cs | 258 ---- .../GasPayloadExecuteBenchmarks.cs | 121 -- .../GasBenchmarks/PayloadLoader.cs | 457 ------- .../Nethermind.Evm.Benchmark.csproj | 8 - .../Nethermind.Evm.Benchmark/Program.cs | 347 ----- .../Properties/launchSettings.json | 8 - .../CodeInfoRepositoryTests.cs | 2 +- .../Nethermind.Evm/BadInstructionException.cs | 9 + src/Nethermind/Nethermind.Evm/CallResult.cs | 4 +- .../Nethermind.Evm/CodeAnalysis/CodeInfo.cs | 90 +- .../CodeAnalysis/JumpDestinationAnalyzer.cs | 6 +- .../Nethermind.Evm/CodeInfoFactory.cs | 4 +- .../Nethermind.Evm/CodeInfoRepository.cs | 34 +- .../EvmObjectFormat/EofCodeInfo.cs | 13 +- .../Nethermind.Evm/ExecutionEnvironment.cs | 4 +- src/Nethermind/Nethermind.Evm/ICodeInfo.cs | 67 + .../Nethermind.Evm/ICodeInfoRepository.cs | 6 +- .../IOverridableCodeInfoRepository.cs | 2 +- .../Nethermind.Evm/IPrecompileProvider.cs | 2 +- .../Instructions/EvmInstructions.Call.cs | 2 +- .../Instructions/EvmInstructions.CodeCopy.cs | 2 +- .../Instructions/EvmInstructions.Create.cs | 2 +- .../EvmInstructions.Environment.cs | 2 +- .../Instructions/EvmInstructions.Eof.cs | 42 +- .../Nethermind.Evm/InvalidCodeException.cs | 10 + src/Nethermind/Nethermind.Evm/Metrics.cs | 17 - .../Precompiles/PrecompileInfo.cs | 17 + .../TransactionCollisionException.cs | 10 + .../TransactionProcessor.cs | 2 +- .../Nethermind.Evm/TransactionSubstate.cs | 4 +- .../VirtualMachine.ExtCodeCache.cs | 14 +- .../Nethermind.Evm/VirtualMachine.cs | 20 +- .../LogIndexBuilderTests.cs | 273 ---- .../Eth/FacadeJsonContext.cs | 19 - .../Nethermind.Facade/Filters/LogFilter.cs | 7 +- .../Filters/LogIndexFilterVisitor.cs | 187 --- .../Filters/Topics/AnyTopic.cs | 2 - .../Filters/Topics/AnyTopicsFilter.cs | 5 - .../Filters/Topics/OrExpression.cs | 5 - .../Filters/Topics/SequenceTopicsFilter.cs | 5 - .../Filters/Topics/SpecificTopic.cs | 3 - .../Filters/Topics/TopicExpression.cs | 2 - .../Filters/Topics/TopicsFilter.cs | 4 - .../Find/ILogIndexBuilder.cs | 21 - .../Find/IndexedLogFinder.cs | 92 -- .../Nethermind.Facade/Find/LogFinder.cs | 101 +- .../Nethermind.Facade/Find/LogIndexBuilder.cs | 495 ------- .../Proxy/Models/Simulate}/BlockOverride.cs | 2 +- .../Modules/BlockProcessingModule.cs | 1 - .../Modules/BlockTreeModule.cs | 32 +- .../Modules/BuiltInStepsModule.cs | 1 - .../Nethermind.Init/Modules/DbModule.cs | 1 + .../Modules/MainProcessingContext.cs | 26 +- .../Modules/NethermindModule.cs | 3 +- .../Nethermind.Init/Modules/RpcModules.cs | 3 - .../PruningTrieStateFactory.cs | 5 - .../Steps/InitializeBlockchain.cs | 1 + .../Nethermind.Init/Steps/StartLogIndex.cs | 28 - .../EthModuleBenchmarks.cs | 2 - .../JsonRpcProcessorTests.cs | 60 - .../Modules/BoundedModulePoolTests.cs | 4 +- .../Modules/Eth/EthRpcModuleTests.cs | 4 +- .../Modules/SingletonModulePoolTests.cs | 4 +- .../Modules/TestRpcBlockchain.cs | 5 +- .../Data/EthRpcJsonContext.cs | 38 - .../Nethermind.JsonRpc/ErrorCodes.cs | 55 +- .../Nethermind.JsonRpc/IJsonRpcResult.cs | 10 + .../Nethermind.JsonRpc/IStreamableResult.cs | 17 - .../JsonRpcConfigExtension.cs | 46 +- .../Nethermind.JsonRpc/JsonRpcContext.cs | 25 +- .../Nethermind.JsonRpc/JsonRpcProcessor.cs | 74 +- .../JsonRpcResponseJsonContext.cs | 15 - .../Nethermind.JsonRpc/JsonRpcService.cs | 14 +- .../Modules/DebugModule/TransactionBundle.cs | 1 + .../Modules/Eth/EthModuleFactory.cs | 5 +- .../Modules/Eth/EthRpcModule.cs | 33 - .../Nethermind.JsonRpc/Modules/Eth/Filter.cs | 16 +- .../Modules/LogIndex/ILogIndexRpcModule.cs | 17 - .../Modules/LogIndex/LogIndexRpcModule.cs | 54 - .../Modules/LogIndex/LogIndexStatus.cs | 18 - .../Nethermind.JsonRpc/Modules/ModuleType.cs | 2 +- .../EngineModuleTests.Synchronization.cs | 3 +- .../EngineModuleTests.V1.cs | 27 +- .../EngineModuleTests.V3.cs | 31 - .../EngineModuleTests.V5.cs | 68 - .../Nethermind.Merge.Plugin.Test/JwtTest.cs | 135 +- .../MergePluginTests.cs | 22 - .../TestBlockProcessorInterceptor.Setup.cs | 3 - .../Data/BlobsV1DirectResponse.cs | 68 - .../Data/BlobsV2DirectResponse.cs | 89 -- .../Data/EngineApiJsonContext.cs | 35 - .../Data/ExecutionPayload.cs | 9 +- .../Handlers/GetBlobsHandler.cs | 41 +- .../Handlers/GetBlobsHandlerV2.cs | 44 +- .../Nethermind.Merge.Plugin/MergePlugin.cs | 2 - .../MetricsTests.cs | 41 - .../Metrics/MetricsController.cs | 8 +- .../P2P/Protocol.cs | 4 + .../P2P/ProtocolParser.cs | 3 + .../IDiscoveryMsgSerializersProvider.cs | 9 + .../MessageQueueTests.cs | 168 --- .../P2P/ProtocolParserTests.cs | 19 + .../PeerManagerTests.cs | 86 +- .../Nethermind.Network/P2P/MessageQueue.cs | 11 +- .../AddCapabilityMessageSerializer.cs | 2 +- .../SyncPeerProtocolHandlerBase.cs | 3 + .../ZeroProtocolHandlerBase.cs | 18 +- .../PeerEqualityComparer.cs | 22 + src/Nethermind/Nethermind.Network/Timeouts.cs | 5 + .../Rpc/OptimismEthRpcModuleTest.cs | 4 +- .../CL/L1Bridge/EthereumL1Bridge.cs | 8 + .../Rpc/OptimismEthModuleFactory.cs | 8 +- .../Rpc/OptimismEthRpcModule.cs | 3 - .../Ethereum/ContextWithMocks.cs | 2 +- .../Steps/EthereumStepsManagerTests.cs | 3 +- .../Ethereum/Api/ApiBuilder.cs | 4 +- .../Ethereum/EthereumRunner.cs | 15 + .../Ethereum/JsonRpcRunner.cs | 4 +- .../Modules/NethermindRunnerModule.cs | 3 +- .../Nethermind.Runner/JsonRpc/Bootstrap.cs | 4 +- .../Nethermind.Runner/JsonRpc/Startup.cs | 482 ++----- src/Nethermind/Nethermind.Runner/Program.cs | 18 +- .../configs/base-mainnet.json | 4 +- .../configs/base-sepolia.json | 4 +- .../Nethermind.Runner/configs/chiado.json | 4 +- .../Nethermind.Runner/configs/gnosis.json | 4 +- .../configs/joc-mainnet.json | 6 +- .../configs/joc-testnet.json | 6 +- .../configs/linea-mainnet.json | 6 +- .../configs/linea-sepolia.json | 4 +- .../Nethermind.Runner/configs/mainnet.json | 4 +- .../Nethermind.Runner/configs/op-mainnet.json | 4 +- .../Nethermind.Runner/configs/op-sepolia.json | 4 +- .../Nethermind.Runner/configs/sepolia.json | 4 +- .../Nethermind.Runner/configs/spaceneth.json | 1 + .../configs/spaceneth_persistent.json | 1 + .../configs/worldchain-mainnet.json | 4 +- .../configs/worldchain-sepolia.json | 4 +- .../Nethermind.Runner/packages.lock.json | 240 ++-- .../CountingTextReader.cs | 83 ++ .../CountingTextWriter.cs | 42 + .../EthereumJsonSerializer.cs | 206 +-- .../ForcedNumberConversion.cs | 28 +- .../Hash256Converter.cs | 30 +- .../HexWriter.cs | 428 ------ .../LongConverter.cs | 215 +-- .../NullableULongConverter.cs | 52 +- .../PubSub/LogPublisher.cs | 31 + .../UInt256Converter.cs | 56 +- .../ULongConverter.cs | 161 +-- .../ValueHash256Converter.cs | 4 +- .../BlockDecoder.cs | 32 +- .../Nethermind.Serialization.Rlp/Rlp.cs | 3 +- .../Nethermind.Serialization.Rlp/TxDecoder.cs | 19 - .../Ssz.BasicTypes.cs | 2 - .../Ssz.Containers.cs | 18 + .../ShutterIntegrationTests.cs | 4 +- .../StorageProviderTests.cs | 23 +- .../TrieStoreScopeProviderTests.cs | 22 - .../OverridableCodeInfoRepository.cs | 10 +- .../PersistentStorageProvider.cs | 1 + .../Nethermind.State/PreBlockCaches.cs | 18 +- .../PrewarmerScopeProvider.cs | 74 +- .../Nethermind.State/Proofs/PatriciaTrieT.cs | 6 +- .../Nethermind.State/Proofs/ReceiptTrie.cs | 16 +- .../Nethermind.State/Proofs/TxTrie.cs | 33 +- .../Nethermind.State/Proofs/WithdrawalTrie.cs | 2 +- .../TrieStoreScopeProvider.cs | 17 +- .../FastSync/StateSyncFeedHealingTests.cs | 1 - .../SyncProgressResolverTests.cs | 22 +- .../SyncServerTests.cs | 2 +- .../ParallelSync/SyncProgressResolver.cs | 8 +- .../ClientTypeStrategy.cs | 44 + .../AllocationStrategies/NullStrategy.cs | 26 + .../AllocationStrategies/StaticStrategy.cs | 24 + .../StrategySelectionType.cs | 11 + .../SyncPeerEventArgs.cs | 20 + .../Nethermind.Taiko/Tdx/TdxsClient.cs | 4 +- .../StateTestTxTracer.cs | 3 +- .../Nethermind.Trie.Test/CacheTests.cs | 1 - .../Nethermind.Trie.Test/HexPrefixTests.cs | 1 - .../Nethermind.Trie.Test/NibbleTests.cs | 1 - .../NodeStorageFactoryTests.cs | 1 - .../Nethermind.Trie.Test/NodeStorageTests.cs | 34 +- .../OverlayTrieStoreTests.cs | 1 - .../MaxBlockInCachePruneStrategyTests.cs | 3 +- .../MinBlockInCachePruneStrategyTests.cs | 3 +- .../Pruning/TreeStoreTests.cs | 34 +- .../PruningScenariosTests.cs | 3 +- .../Nethermind.Trie.Test/RawTrieStoreTests.cs | 1 - .../Nethermind.Trie.Test/TinyTreePathTests.cs | 1 - .../TrackingCappedArrayPoolTests.cs | 1 - .../Nethermind.Trie.Test/TreePathTests.cs | 1 - .../TrieNodeResolverWithReadFlagsTests.cs | 1 - .../Nethermind.Trie.Test/TrieTests.cs | 4 - .../Nethermind.Trie.Test/VisitingTests.cs | 1 - .../VisitorProgressTrackerTests.cs | 1 - .../Nethermind.Trie/NodeStorageCache.cs | 13 +- .../Nethermind.Trie/PatriciaTree.cs | 22 + .../Nethermind.Trie/PreCachedTrieStore.cs | 37 +- .../Nethermind.Trie/Pruning/TrieStore.cs | 5 + .../TrackingCappedArrayPool.cs | 64 +- .../Nethermind.Trie/TrieNode.Decoder.cs | 23 +- src/Nethermind/Nethermind.Trie/TrieNode.cs | 1 + .../Nethermind.Trie/VisitContext.cs | 1 + .../BlobTxStorageTests.cs | 100 -- .../Collections/SortedPoolTests.cs | 2 - ...mpetingTransactionEqualityComparerTests.cs | 1 - .../DelegatedAccountFilterTest.cs | 17 +- .../NonceManagerTests.cs | 2 - .../ReceiptStorageTests.cs | 2 - .../Nethermind.TxPool.Test/RetryCacheTests.cs | 83 +- .../Nethermind.TxPool.Test/TestBlockTree.cs | 108 -- .../TestChainHeadInfoProvider.cs | 35 - .../TransactionExtensionsTests.cs | 1 - .../TransactionPoolInfoProviderTests.cs | 2 - .../TxBroadcasterTests.cs | 40 +- .../TxPoolTests.Blobs.cs | 154 +-- .../Nethermind.TxPool.Test/TxPoolTests.cs | 137 +- .../Nethermind.TxPool/BlobTxStorage.cs | 40 - .../Collections/BlobTxDistinctSortedPool.cs | 42 +- .../PersistentBlobTxDistinctSortedPool.cs | 126 -- src/Nethermind/Nethermind.TxPool/ITxPool.cs | 3 +- .../Nethermind.TxPool/ITxStorage.cs | 3 - .../Nethermind.TxPool/NullBlobTxStorage.cs | 2 - .../Nethermind.TxPool/NullTxPool.cs | 3 +- .../Nethermind.TxPool/RetryCache.cs | 6 +- .../Nethermind.TxPool/TxBroadcaster.cs | 9 +- .../TxNonceTxPoolReserveSealer.cs | 0 .../Nethermind.TxPool/TxPool.NonceInfo.cs | 0 src/Nethermind/Nethermind.TxPool/TxPool.cs | 7 +- .../Nethermind.Xdc.Test/BlockInfoTests.cs | 1 + .../Helpers/XdcTestBlockchain.cs | 6 +- .../ModuleTests/ProposedBlockTests.cs | 1 + .../ModuleTests/RewardTests.cs | 16 +- .../ModuleTests/SyncInfoDecoderTests.cs | 175 --- .../ModuleTests/XdcReorgModuleTests.cs | 1 + .../Errors/QuorumCertificateException.cs | 12 + src/Nethermind/Nethermind.Xdc/IXdcSealer.cs | 11 + .../Nethermind.Xdc/RLP/SyncInfoDecoder.cs | 93 -- .../Nethermind.Xdc/RLP/VoteDecoder.cs | 2 +- src/Nethermind/Nethermind.Xdc/XdcDbNames.cs | 12 + src/Nethermind/Nethermind.Xdc/XdcModule.cs | 5 +- .../XdcStateSyncAllocationStrategyFactory.cs | 34 - src/Nethermind/Nethermind.slnx | 1 - tools/JitAsm/DisassemblyParser.cs | 112 -- tools/JitAsm/InstructionAnnotator.cs | 441 ------ tools/JitAsm/InstructionDb.cs | 156 --- tools/JitAsm/InstructionDbBuilder.cs | 265 ---- tools/JitAsm/JitAsm.csproj | 12 - tools/JitAsm/JitAsm.slnx | 3 - tools/JitAsm/JitRunner.cs | 269 ---- tools/JitAsm/MethodResolver.cs | 393 ------ tools/JitAsm/Program.cs | 581 -------- tools/JitAsm/README.md | 494 ------- tools/JitAsm/StaticCtorDetector.cs | 175 --- tools/Kute/Nethermind.Tools.Kute/Program.cs | 14 +- tools/gas-benchmarks | 1 - 515 files changed, 3713 insertions(+), 21839 deletions(-) delete mode 100644 .github/workflows/ci-surge.yml delete mode 100644 .github/workflows/gas-benchmarks-bdn.yml delete mode 100644 .github/workflows/pr-labeler.yml delete mode 100644 scripts/benchmarks/compare_bdn_results.py delete mode 100644 scripts/benchmarks/merge_bdn_results.py delete mode 100644 src/Nethermind/Nethermind.Benchmark/Core/FastHashBenchmarks.cs delete mode 100644 src/Nethermind/Nethermind.Benchmark/Core/SeqlockCacheBenchmarks.cs delete mode 100644 src/Nethermind/Nethermind.Benchmark/Scheduler/BackgroundTaskSchedulerBenchmarks.cs delete mode 100644 src/Nethermind/Nethermind.Blockchain.Test/Filters/LogIndexFilterVisitorTests.cs delete mode 100644 src/Nethermind/Nethermind.Blockchain.Test/Filters/TestProcessingContext.cs create mode 100644 src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessingScope.cs create mode 100644 src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessorSource.cs delete mode 100644 src/Nethermind/Nethermind.Consensus/Processing/IProcessingStats.cs create mode 100644 src/Nethermind/Nethermind.Consensus/Processing/OverridableTxProcessingScope.cs delete mode 100644 src/Nethermind/Nethermind.Core.Test/Collections/SeqlockCacheTests.cs delete mode 100644 src/Nethermind/Nethermind.Core/Buffers/ArrayMemoryManager.cs create mode 100644 src/Nethermind/Nethermind.Core/Caching/ISpanCache.cs create mode 100644 src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs create mode 100644 src/Nethermind/Nethermind.Core/Collections/HashHelpers.cs delete mode 100644 src/Nethermind/Nethermind.Core/Collections/IHash64bit.cs create mode 100644 src/Nethermind/Nethermind.Core/Collections/InsertionBehavior.cs delete mode 100644 src/Nethermind/Nethermind.Core/Collections/SeqlockCache.cs create mode 100644 src/Nethermind/Nethermind.Core/Collections/SortedRealList.cs rename src/Nethermind/Nethermind.Core/{Buffers => }/DbSpanMemoryManager.cs (58%) create mode 100644 src/Nethermind/Nethermind.Core/Extensions/DictionaryExtensions.cs create mode 100644 src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs create mode 100644 src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs delete mode 100644 src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageCompactorTests.cs delete mode 100644 src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageIntegrationTests.cs delete mode 100644 src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageTestExtensions.cs delete mode 100644 src/Nethermind/Nethermind.Db.Test/LogIndex/MergeOperatorTests.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/AverageStats.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/BlockReceipts.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/CompactingStats.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/Compactor.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/CompressionAlgorithm.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/Compressor.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/DisabledLogIndexStorage.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/ExecTimeStats.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/ILogIndexConfig.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/ILogIndexStorage.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexAggregate.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexColumns.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexConfig.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexEnumerator.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexStateException.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexStorage.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexUpdateStats.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/MergeOperator.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/MergeOps.cs delete mode 100644 src/Nethermind/Nethermind.Db/LogIndex/PostMergeProcessingStats.cs delete mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/BlockBenchmarkHelper.cs delete mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBenchmarkColumnProvider.cs delete mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBenchmarkConfig.cs delete mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockBenchmarks.cs delete mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockBuildingBenchmarks.cs delete mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockOneBenchmarks.cs delete mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasNewPayloadBenchmarks.cs delete mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasNewPayloadMeasuredBenchmarks.cs delete mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasPayloadBenchmarks.cs delete mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasPayloadExecuteBenchmarks.cs delete mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/PayloadLoader.cs delete mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/Program.cs delete mode 100644 src/Nethermind/Nethermind.Evm.Benchmark/Properties/launchSettings.json create mode 100644 src/Nethermind/Nethermind.Evm/BadInstructionException.cs create mode 100644 src/Nethermind/Nethermind.Evm/ICodeInfo.cs create mode 100644 src/Nethermind/Nethermind.Evm/InvalidCodeException.cs create mode 100644 src/Nethermind/Nethermind.Evm/Precompiles/PrecompileInfo.cs create mode 100644 src/Nethermind/Nethermind.Evm/TransactionCollisionException.cs delete mode 100644 src/Nethermind/Nethermind.Facade.Test/LogIndexBuilderTests.cs delete mode 100644 src/Nethermind/Nethermind.Facade/Eth/FacadeJsonContext.cs delete mode 100644 src/Nethermind/Nethermind.Facade/Filters/LogIndexFilterVisitor.cs delete mode 100644 src/Nethermind/Nethermind.Facade/Find/ILogIndexBuilder.cs delete mode 100644 src/Nethermind/Nethermind.Facade/Find/IndexedLogFinder.cs delete mode 100644 src/Nethermind/Nethermind.Facade/Find/LogIndexBuilder.cs rename src/Nethermind/{Nethermind.Evm => Nethermind.Facade/Proxy/Models/Simulate}/BlockOverride.cs (96%) delete mode 100644 src/Nethermind/Nethermind.Init/Steps/StartLogIndex.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Data/EthRpcJsonContext.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc/IJsonRpcResult.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/IStreamableResult.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/JsonRpcResponseJsonContext.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/ILogIndexRpcModule.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexRpcModule.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexStatus.cs delete mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV1DirectResponse.cs delete mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV2DirectResponse.cs delete mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/EngineApiJsonContext.cs create mode 100644 src/Nethermind/Nethermind.Network.Discovery/Serializers/IDiscoveryMsgSerializersProvider.cs delete mode 100644 src/Nethermind/Nethermind.Network.Test/MessageQueueTests.cs create mode 100644 src/Nethermind/Nethermind.Network/PeerEqualityComparer.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Json/CountingTextReader.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Json/CountingTextWriter.cs delete mode 100644 src/Nethermind/Nethermind.Serialization.Json/HexWriter.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Json/PubSub/LogPublisher.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Ssz/Ssz.Containers.cs create mode 100644 src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/ClientTypeStrategy.cs create mode 100644 src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/NullStrategy.cs create mode 100644 src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StaticStrategy.cs create mode 100644 src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StrategySelectionType.cs create mode 100644 src/Nethermind/Nethermind.Synchronization/SyncPeerEventArgs.cs delete mode 100644 src/Nethermind/Nethermind.TxPool.Test/TestBlockTree.cs delete mode 100644 src/Nethermind/Nethermind.TxPool.Test/TestChainHeadInfoProvider.cs create mode 100644 src/Nethermind/Nethermind.TxPool/TxNonceTxPoolReserveSealer.cs create mode 100644 src/Nethermind/Nethermind.TxPool/TxPool.NonceInfo.cs delete mode 100644 src/Nethermind/Nethermind.Xdc.Test/ModuleTests/SyncInfoDecoderTests.cs create mode 100644 src/Nethermind/Nethermind.Xdc/Errors/QuorumCertificateException.cs create mode 100644 src/Nethermind/Nethermind.Xdc/IXdcSealer.cs delete mode 100644 src/Nethermind/Nethermind.Xdc/RLP/SyncInfoDecoder.cs create mode 100644 src/Nethermind/Nethermind.Xdc/XdcDbNames.cs delete mode 100644 src/Nethermind/Nethermind.Xdc/XdcStateSyncAllocationStrategyFactory.cs delete mode 100644 tools/JitAsm/DisassemblyParser.cs delete mode 100644 tools/JitAsm/InstructionAnnotator.cs delete mode 100644 tools/JitAsm/InstructionDb.cs delete mode 100644 tools/JitAsm/InstructionDbBuilder.cs delete mode 100644 tools/JitAsm/JitAsm.csproj delete mode 100644 tools/JitAsm/JitAsm.slnx delete mode 100644 tools/JitAsm/JitRunner.cs delete mode 100644 tools/JitAsm/MethodResolver.cs delete mode 100644 tools/JitAsm/Program.cs delete mode 100644 tools/JitAsm/README.md delete mode 100644 tools/JitAsm/StaticCtorDetector.cs delete mode 160000 tools/gas-benchmarks diff --git a/.github/workflows/build-tools.yml b/.github/workflows/build-tools.yml index 4369e74a07b4..8038f514a80f 100644 --- a/.github/workflows/build-tools.yml +++ b/.github/workflows/build-tools.yml @@ -21,7 +21,6 @@ jobs: - Evm/Evm.slnx - HiveCompare/HiveCompare.slnx - HiveConsensusWorkflowGenerator/HiveConsensusWorkflowGenerator.slnx - - JitAsm/JitAsm.slnx - Kute/Kute.slnx # - SchemaGenerator/SchemaGenerator.slnx - SendBlobs/SendBlobs.slnx diff --git a/.github/workflows/ci-surge.yml b/.github/workflows/ci-surge.yml deleted file mode 100644 index 87ff8b8c4688..000000000000 --- a/.github/workflows/ci-surge.yml +++ /dev/null @@ -1,167 +0,0 @@ -name: "Surge Integration Tests" - -on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - paths: - - "src/Nethermind/Nethermind.Taiko/**" - - ".github/workflows/ci-surge.yml" - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - integration_tests: - if: >- - ${{ github.event.pull_request.draft == false - && !startsWith(github.head_ref, 'release-please') }} - name: Integration tests - runs-on: [ubuntu-latest] - timeout-minutes: 45 - env: - SURGE_TAIKO_MONO_DIR: surge-taiko-mono - PACAYA_FORK_TAIKO_MONO_DIR: pacaya-fork-taiko-mono - SHASTA_FORK_TAIKO_MONO_DIR: shasta-fork-taiko-mono - - steps: - - name: Checkout Nethermind - uses: actions/checkout@v6 - - - name: Checkout surge-taiko-mono - uses: actions/checkout@v6 - with: - repository: NethermindEth/surge-taiko-mono - path: ${{ env.SURGE_TAIKO_MONO_DIR }} - ref: surge-shasta - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version-file: ${{ env.SURGE_TAIKO_MONO_DIR }}/go.mod - cache: true - - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9 - run_install: false - - - name: Install Node.js - uses: actions/setup-node@v5 - with: - node-version: 20 - cache: pnpm - cache-dependency-path: ${{ env.SURGE_TAIKO_MONO_DIR }}/pnpm-lock.yaml - - - name: Install dependencies - working-directory: ${{ env.SURGE_TAIKO_MONO_DIR }} - shell: bash - run: pnpm install - - - name: Checkout Pacaya fork - uses: actions/checkout@v6 - with: - repository: NethermindEth/surge-taiko-mono - path: >- - ${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, - env.PACAYA_FORK_TAIKO_MONO_DIR) }} - ref: jmadibekov/pacaya-dummy-verifiers-fix - - - name: Checkout Shasta fork - uses: actions/checkout@v6 - with: - repository: NethermindEth/surge-taiko-mono - path: >- - ${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, - env.SHASTA_FORK_TAIKO_MONO_DIR) }} - ref: surge-alethia-protocol-v3.0.0 - - - name: Install pnpm dependencies for pacaya fork taiko-mono - working-directory: >- - ${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, - env.PACAYA_FORK_TAIKO_MONO_DIR) }} - run: cd ./packages/protocol && pnpm install - - - name: Install pnpm dependencies for shasta fork taiko-mono - working-directory: >- - ${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, - env.SHASTA_FORK_TAIKO_MONO_DIR) }} - run: cd ./packages/protocol && pnpm install - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Docker Build Nethermind Client - run: | - image_name="nethermindeth/nethermind" - image_tag="${GITHUB_SHA:0:8}" - full_image="${image_name}:${image_tag}" - - echo "Building Docker image: ${full_image}" - - docker buildx build . \ - --platform linux/amd64 \ - -f Dockerfile \ - -t "${full_image}" \ - --load \ - --build-arg BUILD_CONFIG=release \ - --build-arg CI=true \ - --build-arg COMMIT_HASH=${{ github.sha }} \ - --build-arg SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) - - echo "IMAGE_TAG=${full_image}" >> $GITHUB_ENV - - echo "Verifying image exists locally:" - docker images | grep "${image_name}" | grep "${image_tag}" || (echo "Error: Image not found locally" && exit 1) - - - name: Install yq - run: | - sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 - sudo chmod +x /usr/local/bin/yq - yq --version - - - name: Update taiko-client docker-compose.yml with new image - working-directory: >- - ${{ env.SURGE_TAIKO_MONO_DIR }}/packages/taiko-client/internal/docker/nodes - run: | - docker_compose_file="docker-compose.yml" - if [ -f "$docker_compose_file" ]; then - echo "Current image in docker-compose.yml:" - yq eval '.services.l2_nmc.image' "$docker_compose_file" - - echo "Updating docker-compose.yml with image: ${IMAGE_TAG}" - yq eval '.services.l2_nmc.image = "'"${IMAGE_TAG}"'"' -i "$docker_compose_file" - - yq eval '.services.l2_nmc.pull_policy = "never"' -i "$docker_compose_file" - - echo "Updated image in docker-compose.yml:" - yq eval '.services.l2_nmc.image' "$docker_compose_file" - - echo "Pull policy set to:" - yq eval '.services.l2_nmc.pull_policy' "$docker_compose_file" - else - echo "Warning: docker-compose.yml not found at expected path" - exit 1 - fi - - - name: Run integration tests - working-directory: >- - ${{ env.SURGE_TAIKO_MONO_DIR }}/packages/taiko-client - env: - L2_NODE: l2_nmc - run: >- - SHASTA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} - PACAYA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} - make test - - - name: Codecov.io - uses: codecov/codecov-action@v5 - with: - files: ${{ env.SURGE_TAIKO_MONO_DIR }}/packages/taiko-client/coverage.out - flags: taiko-client - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/ci-taiko.yml b/.github/workflows/ci-taiko.yml index 2d92a38090aa..d01e20692415 100644 --- a/.github/workflows/ci-taiko.yml +++ b/.github/workflows/ci-taiko.yml @@ -1,4 +1,4 @@ -name: "Taiko Integration Tests" +name: "Nethermind/Ethereum Taiko Client CI Tests" on: pull_request: @@ -18,22 +18,25 @@ jobs: && !startsWith(github.head_ref, 'release-please') }} name: Integration tests runs-on: [ubuntu-latest] - timeout-minutes: 45 + timeout-minutes: 30 env: - TAIKO_MONO_DIR: taiko-mono + OLD_FORK_TAIKO_MONO_DIR: old-fork-taiko-mono + TAIKO_MONO_MAIN_DIR: taiko-mono-main PACAYA_FORK_TAIKO_MONO_DIR: pacaya-fork-taiko-mono SHASTA_FORK_TAIKO_MONO_DIR: shasta-fork-taiko-mono + strategy: + matrix: + execution_node: [l2_nmc] + steps: - - name: Checkout Nethermind - uses: actions/checkout@v6 + - uses: actions/checkout@v6 - - name: Checkout taiko-mono - uses: actions/checkout@v6 + - uses: actions/checkout@v6 with: - repository: taikoxyz/taiko-mono - path: ${{ env.TAIKO_MONO_DIR }} - ref: main + repository: NethermindEth/surge-taiko-mono + path: ${{ env.TAIKO_MONO_MAIN_DIR }} + ref: surge-shasta - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 @@ -41,9 +44,14 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version-file: ${{ env.TAIKO_MONO_DIR }}/go.mod + go-version-file: ${{ env.TAIKO_MONO_MAIN_DIR }}/go.mod cache: true + - name: Set up Git to use HTTPS + shell: bash + run: | + git config --global url."https://github.com/".insteadOf "git@github.com:" + - name: Install pnpm uses: pnpm/action-setup@v4 with: @@ -55,40 +63,38 @@ jobs: with: node-version: 20 cache: pnpm - cache-dependency-path: ${{ env.TAIKO_MONO_DIR }}/pnpm-lock.yaml + cache-dependency-path: ${{ env.TAIKO_MONO_MAIN_DIR }}/pnpm-lock.yaml - name: Install dependencies - working-directory: ${{ env.TAIKO_MONO_DIR }} + working-directory: ${{ env.TAIKO_MONO_MAIN_DIR }} shell: bash run: pnpm install - - name: Checkout Pacaya fork - uses: actions/checkout@v6 + - uses: actions/checkout@v6 with: repository: taikoxyz/taiko-mono path: >- - ${{ format('{0}/{1}', env.TAIKO_MONO_DIR, + ${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} ref: taiko-alethia-protocol-v2.3.0-devnet-shasta-test - - name: Checkout Shasta fork - uses: actions/checkout@v6 + - uses: actions/checkout@v6 with: repository: taikoxyz/taiko-mono path: >- - ${{ format('{0}/{1}', env.TAIKO_MONO_DIR, + ${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} ref: taiko-alethia-protocol-v3.0.0 - name: Install pnpm dependencies for pacaya fork taiko-mono working-directory: >- - ${{ format('{0}/{1}', env.TAIKO_MONO_DIR, + ${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} run: cd ./packages/protocol && pnpm install - name: Install pnpm dependencies for shasta fork taiko-mono working-directory: >- - ${{ format('{0}/{1}', env.TAIKO_MONO_DIR, + ${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} run: cd ./packages/protocol && pnpm install @@ -126,7 +132,7 @@ jobs: - name: Update taiko-client docker-compose.yml with new image working-directory: >- - ${{ env.TAIKO_MONO_DIR }}/packages/taiko-client/internal/docker/nodes + ${{ env.TAIKO_MONO_MAIN_DIR }}/packages/taiko-client/internal/docker/nodes run: | docker_compose_file="docker-compose.yml" if [ -f "$docker_compose_file" ]; then @@ -148,20 +154,20 @@ jobs: exit 1 fi - - name: Run integration tests + - name: Run Tests on ${{ matrix.execution_node }} execution engine working-directory: >- - ${{ env.TAIKO_MONO_DIR }}/packages/taiko-client + ${{ env.TAIKO_MONO_MAIN_DIR }}/packages/taiko-client env: - L2_NODE: l2_nmc + L2_NODE: ${{ matrix.execution_node }} run: >- - SHASTA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.TAIKO_MONO_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} - PACAYA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.TAIKO_MONO_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} + SHASTA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} + PACAYA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} make test - name: Codecov.io uses: codecov/codecov-action@v5 with: - files: ${{ env.TAIKO_MONO_DIR }}/packages/taiko-client/coverage.out + files: ${{ env.TAIKO_MONO_MAIN_DIR }}/packages/taiko-client/coverage.out flags: taiko-client env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/gas-benchmarks-bdn.yml b/.github/workflows/gas-benchmarks-bdn.yml deleted file mode 100644 index 86c2aacabbba..000000000000 --- a/.github/workflows/gas-benchmarks-bdn.yml +++ /dev/null @@ -1,161 +0,0 @@ -name: Gas Benchmarks (BDN) - -on: - push: - branches: [master] - paths: ['src/Nethermind/**'] - pull_request: - branches: [master] - types: [labeled] - workflow_dispatch: - inputs: - mode: - description: 'Benchmark mode (Block or EVM)' - required: false - default: 'Block' - type: choice - options: - - Block - - EVM - filter: - description: 'BDN filter pattern (optional, e.g. *MULMOD*)' - required: false - default: '' - -env: - DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: "1" - TERM: xterm - -jobs: - resolve: - if: >- - github.event_name == 'push' || - github.event_name == 'workflow_dispatch' || - (github.event_name == 'pull_request' && github.event.label.name == 'performance is good') - runs-on: ubuntu-slim - outputs: - mode: ${{ steps.check.outputs.mode }} - filter: ${{ steps.check.outputs.filter }} - steps: - - name: Resolve inputs - id: check - shell: bash - run: | - set -euo pipefail - - mode="Block" - filter="" - - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then - mode="${{ inputs.mode }}" - filter="${{ inputs.filter }}" - fi - - { - echo "mode=${mode}" - echo "filter=${filter}" - } >> "$GITHUB_OUTPUT" - - benchmark: - needs: resolve - if: always() && !cancelled() && needs.resolve.result == 'success' - runs-on: [self-hosted, Linux, X64, benchmark] - strategy: - fail-fast: false - matrix: - chunk: [1, 2, 3, 4, 5] - timeout-minutes: 240 - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - lfs: true - submodules: true - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v4 - with: - global-json-file: global.json - - - name: Build benchmarks - run: dotnet build src/Nethermind/Nethermind.Evm.Benchmark -c Release - - - name: Run benchmarks (chunk ${{ matrix.chunk }}/5) - shell: bash - run: | - MODE="${{ needs.resolve.outputs.mode }}" - FILTER="${{ needs.resolve.outputs.filter }}" - - ARGS="--inprocess --mode=$MODE --chunk ${{ matrix.chunk }}/5" - if [[ -n "$FILTER" ]]; then - ARGS="$ARGS --filter $FILTER" - fi - - dotnet run --project src/Nethermind/Nethermind.Evm.Benchmark -c Release --no-build -- $ARGS - - - name: Upload results - if: always() - uses: actions/upload-artifact@v4 - with: - name: bdn-results-${{ matrix.chunk }} - path: BenchmarkDotNet.Artifacts/results/*-full.json - retention-days: 90 - - collect: - needs: [resolve, benchmark] - if: always() && !cancelled() && needs.resolve.result == 'success' && needs.benchmark.result == 'success' - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - - name: Download all results - uses: actions/download-artifact@v4 - with: - pattern: bdn-results-* - path: results/ - merge-multiple: true - - - name: Merge chunk results - run: python3 scripts/benchmarks/merge_bdn_results.py results/ pr-results.json - - - name: Upload merged results - uses: actions/upload-artifact@v4 - with: - name: bdn-results-merged - path: pr-results.json - retention-days: 90 - - # On master push: cache results as baseline for future PR comparisons - - name: Cache master baseline - if: github.ref == 'refs/heads/master' - run: cp pr-results.json baseline.json - - - name: Save master cache - if: github.ref == 'refs/heads/master' - uses: actions/cache/save@v4 - with: - path: baseline.json - key: gas-benchmark-bdn-${{ github.sha }} - - # On PR: restore master baseline and compare - - name: Restore master baseline - if: github.event_name == 'pull_request' - id: cache-restore - uses: actions/cache/restore@v4 - with: - path: baseline.json - key: gas-benchmark-bdn- - restore-keys: gas-benchmark-bdn- - - - name: Compare against master - if: github.event_name == 'pull_request' && steps.cache-restore.outputs.cache-hit == 'true' - run: python3 scripts/benchmarks/compare_bdn_results.py baseline.json pr-results.json > comparison.md - - - name: Post comparison to PR - if: github.event_name == 'pull_request' && steps.cache-restore.outputs.cache-hit == 'true' - uses: marocchino/sticky-pull-request-comment@v2 - with: - header: gas-benchmark-bdn - path: comparison.md - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml deleted file mode 100644 index 9ded94bebe46..000000000000 --- a/.github/workflows/pr-labeler.yml +++ /dev/null @@ -1,201 +0,0 @@ -name: PR labeler - -on: - pull_request_target: - types: [opened, edited, ready_for_review, synchronize] - -jobs: - label: - name: Label PR by type - runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - - name: Apply labels from PR template checkboxes - uses: actions/github-script@v7 - with: - script: | - // --- Checkbox rules --- - const checkboxToLabel = { - 'Bugfix (a non-breaking change that fixes an issue)': 'bug fix + reliability', - 'New feature (a non-breaking change that adds functionality)': 'new feature', - 'Breaking change (a change that causes existing functionality not to work as expected)': 'BREAKING', - 'Optimization': 'performance is good', - 'Refactoring': 'refactoring', - 'Documentation update': 'docs', - 'Build-related changes': 'build changes', - }; - - // --- Title prefix rules (conventional commits) --- - const prefixToLabel = { - 'fix': 'bug fix + reliability', - 'feat': 'new feature', - 'perf': 'performance is good', - 'refactor': 'refactoring', - 'chore': 'minor', - 'docs': 'docs', - 'test': 'test', - 'ci': 'build changes', - 'build': 'build changes', - }; - - // --- Path rules --- - const pathToLabel = [ - { pattern: 'src/Nethermind/Nethermind.Optimism', label: 'optimism' }, - { pattern: 'src/Nethermind/Nethermind.Taiko', label: 'taiko' }, - { pattern: 'src/Nethermind/Nethermind.Xdc', label: 'XDC' }, - { pattern: 'src/Nethermind/Nethermind.Network', label: 'network' }, - { pattern: 'src/Nethermind/Nethermind.Evm', label: 'evm' }, - { pattern: 'src/Nethermind/Nethermind.Trie', label: 'trie' }, - { pattern: 'src/Nethermind/Nethermind.State', label: 'state+storage' }, - { pattern: 'src/Nethermind/Nethermind.Synchronization', label: 'sync' }, - { pattern: 'src/Nethermind/Nethermind.JsonRpc', label: 'rpc' }, - { pattern: 'src/Nethermind/Nethermind.Db.Rocks', label: 'rocksdb' }, - { pattern: 'src/Nethermind/Nethermind.Db', label: 'database' }, - { pattern: 'src/Nethermind/Nethermind.Init/Modules/DbModule.cs', label: 'database' }, - { pattern: 'src/Nethermind/Nethermind.Runner/configs', label: 'configuration' }, - { pattern: 'src/Nethermind/Nethermind.Config', label: 'configuration' }, - { pattern: 'README.md', label: 'docs' }, - { pattern: 'AGENTS.md', label: 'agentic 🤖' }, - { pattern: '.github/', label: 'devops' }, - { pattern: 'Directory.Packages.props', label: 'dependencies' }, - { pattern: 'tools/', label: 'tools' }, - ]; - - // test label: applied when ALL changed files are in a .Test project - const testOnlyLabel = 'test'; - - const checkboxLabels = new Set(Object.values(checkboxToLabel)); - const prefixLabels = new Set(Object.values(prefixToLabel)); - const pathLabels = new Set(pathToLabel.map(r => r.label)); - const managedLabels = new Set([...checkboxLabels, ...prefixLabels, ...pathLabels, testOnlyLabel, 'cleanup', 'snap sync']); - - const body = context.payload.pull_request.body || ''; - const title = context.payload.pull_request.title || ''; - - const desiredLabels = new Set(); - - // Evaluate checkboxes - for (const [text, label] of Object.entries(checkboxToLabel)) { - const escaped = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - if (new RegExp(`-\\s*\\[\\s*[xX]\\s*\\]\\s*${escaped}`).test(body)) { - desiredLabels.add(label); - } - } - - // Evaluate "Other" checkbox with keyword matching - const otherMatch = body.match(/-\s*\[\s*[xX]\s*\]\s*Other:\s*(.+)/); - if (otherMatch) { - const otherText = otherMatch[1].toLowerCase(); - if (/\btest/.test(otherText)) desiredLabels.add('test'); - if (/\btool/.test(otherText)) desiredLabels.add('tools'); - if (/\bagent/.test(otherText)) desiredLabels.add('agentic 🤖'); - if (/\bdoc/.test(otherText)) desiredLabels.add('docs'); - } - - // Evaluate title prefix: supports "fix:", "fix(scope):", "(fix)", "[fix]" - const prefixMatch = title.match(/^[(\[]?(\w+)[)\]]?[\s(:/]/) - if (prefixMatch) { - const prefix = prefixMatch[1].toLowerCase(); - if (prefixToLabel[prefix]) { - desiredLabels.add(prefixToLabel[prefix]); - } - } - - // Evaluate title for EIP mentions (eip-1234, EIP 1234, eip1234, etc.) - if (/eip[-\s]?\d+/i.test(title)) { - desiredLabels.add('eip'); - } - - // Evaluate title for optimization keyword - if (/\boptimiz/i.test(title)) { - desiredLabels.add('performance is good'); - } - - // Evaluate changed file paths - const files = await github.paginate(github.rest.pulls.listFiles, { - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - per_page: 100, - }); - - for (const { pattern, label } of pathToLabel) { - if (files.some(f => f.filename.startsWith(pattern))) { - desiredLabels.add(label); - } - } - - // SnapSync in path - if (files.some(f => /SnapSync/i.test(f.filename))) { - desiredLabels.add('snap sync'); - } - - // Dockerfile changes - if (files.some(f => /(?:^|\/)[Dd]ockerfile/.test(f.filename))) { - desiredLabels.add('devops'); - } - - // Chain config files with integration keywords - const chainKeywordToLabel = { - 'taiko': 'taiko', - 'optimism': 'optimism', - 'op-': 'optimism', - 'xdc': 'XDC', - }; - for (const f of files) { - if (/^src\/Nethermind\/Chains\//.test(f.filename)) { - const name = f.filename.toLowerCase(); - for (const [keyword, label] of Object.entries(chainKeywordToLabel)) { - if (name.includes(keyword)) { - desiredLabels.add(label); - } - } - } - } - - // Apply test label when all changed files are in Test projects - if (files.length > 0 && files.every(f => /\.Test[s]?\//.test(f.filename))) { - desiredLabels.add(testOnlyLabel); - } - - // Apply cleanup label when PR only removes code - if (files.length > 0 && files.every(f => f.additions === 0 && f.deletions > 0)) { - desiredLabels.add('cleanup'); - } - - // Diff against current labels - const currentLabels = new Set( - context.payload.pull_request.labels.map(l => l.name) - ); - - const toAdd = [...desiredLabels].filter(l => !currentLabels.has(l)); - const toRemove = [...currentLabels].filter(l => managedLabels.has(l) && !desiredLabels.has(l)); - - if (toAdd.length > 0) { - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - labels: toAdd, - }); - core.info(`Added labels: ${toAdd.join(', ')}`); - } - - for (const label of toRemove) { - try { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - name: label, - }); - core.info(`Removed label: ${label}`); - } catch (e) { - if (e.status !== 404) throw e; - } - } - - if (toAdd.length === 0 && toRemove.length === 0) { - core.info('No label changes needed'); - } diff --git a/.gitignore b/.gitignore index 5f3e510becd3..6f14b7b2d897 100644 --- a/.gitignore +++ b/.gitignore @@ -358,10 +358,6 @@ paket-files/ #tools/** #!tools/packages.config -# JitAsm uops.info database (110MB, download separately) -tools/JitAsm/instructions.xml -tools/JitAsm/instructions.db - # Tabs Studio *.tss diff --git a/.gitmodules b/.gitmodules index c674b7d49e06..f5c94ba7c690 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,7 +4,3 @@ [submodule "src/bench_precompiles"] path = src/bench_precompiles url = https://github.com/shamatar/bench_precompiles.git -[submodule "tools/gas-benchmarks"] - path = tools/gas-benchmarks - url = https://github.com/NethermindEth/gas-benchmarks - branch = main diff --git a/AGENTS.md b/AGENTS.md index 62ce5f7ef4fc..350f984894d6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,18 +23,11 @@ This guide helps to get started with the Nethermind Ethereum execution client re - Use the `ArgumentNullException.ThrowIfNull` method for null checks and other similar methods - Use the `ObjectDisposedException.ThrowIf` method for disposal checks - Use documentation comments for all public APIs with proper structure -- Avoid `var` when declaring variables, the only acceptable exceptions are very long type names e.g. nested generic types - Consider performance implications in high-throughput paths - Trust null annotations, do not add redundant null checks -- When fixing a bug, always add a regression test that fails without the fix and passes with it - Add tests to existing test files rather than creating new ones -- When adding multiple, similar tests write one test with test cases -- When adding a test, check if previous tests can be reused with new test case - Code comments must explain _why_, not _what_ -- **NEVER suggest using LINQ (`.Select()`, `.Where()`, `.Any()`, etc.) when a simple `foreach` or `for` loop would work.** LINQ has overhead and is less readable for simple iterations. Use LINQ only for complex queries where the declarative syntax significantly improves clarity. -- Keep changes minimal and focused: do not rename variables, reformat surrounding code, or refactor unrelated logic as part of a fix. Touch only what is necessary to solve the problem. -- Follow DRY: after making changes, review the result for duplicated logic. Extract repeated blocks (roughly 5+ lines) into shared methods, but do not over-extract trivial one-liners into their own methods. -- In generic types, move methods that do not depend on the type parameter to a non-generic base class or static helper to avoid redundant JIT instantiations per closed type. +- Do not suggest using LINQ when a simple loop would suffice - Do not use the `#region` and `#endregion` pragmas - Do not alter anything in the [src/bench_precompiles](./src/bench_precompiles/) and [src/tests](./src/tests/) directories @@ -113,105 +106,8 @@ Before creating a pull request: ```bash dotnet format whitespace src/Nethermind/ --folder ``` -- Follow the [pull_request_template.md](.github/pull_request_template.md) format: fill in the changes section, tick the appropriate type-of-change checkboxes, and complete the testing/documentation sections. The checkboxes drive automatic PR labeling. +- Use [pull_request_template.md](.github/pull_request_template.md) ## Prerequisites See [global.json](./global.json) for the required .NET SDK version. - -## EVM Gas Benchmarks - -The [Nethermind.Evm.Benchmark](./src/Nethermind/Nethermind.Evm.Benchmark/) project includes BenchmarkDotNet benchmarks that replay real `engine_newPayloadV4` payload files from the [gas-benchmarks](https://github.com/NethermindEth/gas-benchmarks) submodule. It is the primary tool for validating performance impact of EVM, block processing, block building, and newPayload path changes. - -**When to use:** After any change to the following projects, run the relevant gas benchmarks to verify there is no throughput regression: -- [Nethermind.Evm](./src/Nethermind/Nethermind.Evm/) - opcode implementations, `VirtualMachine`, instruction handling -- [Nethermind.Evm.Precompiles](./src/Nethermind/Nethermind.Evm.Precompiles/) - precompiled contracts -- [Nethermind.State](./src/Nethermind/Nethermind.State/) - world state, storage access, `WorldState` -- [Nethermind.Trie](./src/Nethermind/Nethermind.Trie/) - Merkle Patricia trie, trie store -- [Nethermind.Blockchain](./src/Nethermind/Nethermind.Blockchain/) - block processing and transaction processing paths -- [Nethermind.Merge.Plugin](./src/Nethermind/Nethermind.Merge.Plugin/) - newPayload handler flow -- [Nethermind.Specs](./src/Nethermind/Nethermind.Specs/) - gas cost changes, hard fork rules - -### Setup (one-time) - -```bash -git lfs install && git submodule update --init tools/gas-benchmarks -``` - -On Windows, if you get "Filename too long" errors, enable long paths first: - -```bash -git config --global core.longpaths true -``` - -If the submodule was already cloned without LFS installed (genesis file shows as ~130 bytes instead of ~53MB): - -```bash -git lfs install && cd tools/gas-benchmarks && git lfs pull -``` - -### Benchmark modes (current) - -Use `--mode=` to select one path. Do not use legacy `--mode=EVM`. - -| Mode | Benchmark class | What it measures | Typical use | -|---|---|---|---| -| `EVMExecute` | `GasPayloadExecuteBenchmarks` | Transaction execution via `TransactionProcessor.Execute` | Node-like import tx execution cost | -| `EVMBuildUp` | `GasPayloadBenchmarks` | Transaction execution via `TransactionProcessor.BuildUp` | Block-building tx execution cost | -| `BlockOne` | `GasBlockOneBenchmarks` | `BlockProcessor.ProcessOne` | Block-level processing without branch-level overhead | -| `Block` | `GasBlockBenchmarks` | `BranchProcessor.Process` | Full import branch processing overhead | -| `BlockBuilding` | `GasBlockBuildingBenchmarks` | Producer path with `ProcessingOptions.ProducingBlock` | Default block production behavior | -| `BlockBuildingMainState` | `GasBlockBuildingBenchmarks` | Producer path with `BuildBlocksOnMainState=true` | Main-state production behavior | -| `NewPayload` | `GasNewPayloadBenchmarks` | `NewPayloadHandler` path | Real handler-side newPayload flow | -| `NewPayloadMeasured` | `GasNewPayloadMeasuredBenchmarks` | Instrumented near-handler path | Detailed stage timing and breakdown | - -### Running benchmarks - -```bash -# List all benchmark scenarios -dotnet run --project src/Nethermind/Nethermind.Evm.Benchmark -c Release -- --list flat --filter "*Gas*" - -# Run one mode + one scenario pattern -dotnet run --project src/Nethermind/Nethermind.Evm.Benchmark -c Release -- --inprocess --mode=EVMExecute --filter "*MULMOD*" -dotnet run --project src/Nethermind/Nethermind.Evm.Benchmark -c Release -- --inprocess --mode=Block --filter "*a_to_a*" -dotnet run --project src/Nethermind/Nethermind.Evm.Benchmark -c Release -- --inprocess --mode=NewPayloadMeasured --filter "*a_to_a*" - -# Run from pre-built executable (no build at all) -dotnet build src/Nethermind/Nethermind.Evm.Benchmark -c Release -./src/Nethermind/artifacts/bin/Nethermind.Evm.Benchmark/release/Nethermind.Evm.Benchmark --inprocess --mode=EVMExecute --filter "*MULMOD*" - -# Run all 8 modes for the same scenario (PowerShell) -$modes = @('EVMExecute','EVMBuildUp','BlockOne','Block','BlockBuilding','BlockBuildingMainState','NewPayload','NewPayloadMeasured') -foreach ($mode in $modes) { - dotnet run --project src/Nethermind/Nethermind.Evm.Benchmark -c Release -- --inprocess --mode=$mode --filter "*a_to_a*" -} - -# Quick diagnostic mode (single payload, no BDN harness, debuggable) -dotnet run --project src/Nethermind/Nethermind.Evm.Benchmark -c Release -- --diag "opcode_MULMOD-mod_bits_63" -``` - -### Reading results - -The output includes custom columns: -- **MGas/s**: `100M gas / mean_seconds / 1M` - higher is better -- **CI-Lower / CI-Upper**: 99% confidence interval bounds for MGas/s - -A regression is a drop in MGas/s outside the confidence interval. If CI intervals of before/after overlap, the difference is not statistically significant. - -For `NewPayload` and `NewPayloadMeasured`, timing breakdown reports are emitted at the end of the run and persisted to files under `BenchmarkDotNet.Artifacts/results/`. - -### Workflow for performance changes - -1. Pick modes based on the code path you changed: -`EVMExecute` + `EVMBuildUp` for tx execution changes, `BlockOne` + `Block` for import flow changes, `BlockBuilding*` for producer flow changes, `NewPayload*` for engine API flow changes. -2. Run baseline using fixed BDN settings for comparability: -`--inprocess --warmupCount 10 --iterationCount 10 --launchCount 1`. -3. Apply your change. -4. Rerun the same command(s) with the same `--mode` and `--filter`. -5. Compare mean time, MGas/s, and allocations. -6. Add before/after numbers to your PR description. -7. Investigate any statistically meaningful drop before merge. - -To run only scenarios related to your change, use `--filter` with a pattern matching opcode or scenario name. Use `--list flat --filter "*Gas*"` to discover exact names. - -Test scenarios are auto-discovered from `tools/gas-benchmarks/eest_tests/testing/`. New tests added to the gas-benchmarks submodule appear automatically after `git submodule update --remote tools/gas-benchmarks`. diff --git a/Directory.Packages.props b/Directory.Packages.props index 0c968919dcc0..2112ae3eadde 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,7 +11,7 @@ - + @@ -19,15 +19,15 @@ - - + + - + - - - + + + @@ -36,18 +36,18 @@ - - + + - - - - + + + + - - + + @@ -57,9 +57,8 @@ - + - @@ -68,25 +67,25 @@ - + - + - + - + - - - + + + - + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index a2eef01c8319..8ecddbd54135 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.103-noble@sha256:0a506ab0c8aa077361af42f82569d364ab1b8741e967955d883e3f23683d473a AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.102-noble@sha256:25d14b400b75fa4e89d5bd4487a92a604a4e409ab65becb91821e7dc4ac7f81f AS build ARG BUILD_CONFIG=release ARG CI=true @@ -25,7 +25,7 @@ RUN arch=$([ "$TARGETARCH" = "amd64" ] && echo "x64" || echo "$TARGETARCH") && \ # A temporary symlink to support the old executable name RUN ln -sr /publish/nethermind /publish/Nethermind.Runner -FROM mcr.microsoft.com/dotnet/aspnet:10.0.3-noble@sha256:52dcfb4225fda614c38ba5997a4ec72cbd5260a624125174416e547ff9eb9b8c +FROM mcr.microsoft.com/dotnet/aspnet:10.0.2-noble@sha256:1aacc8154bc3071349907dae26849df301188be1a2e1f4560b903fb6275e481a WORKDIR /nethermind diff --git a/Dockerfile.chiseled b/Dockerfile.chiseled index 60b5b36ff752..bf96919ae532 100644 --- a/Dockerfile.chiseled +++ b/Dockerfile.chiseled @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.103-noble@sha256:0a506ab0c8aa077361af42f82569d364ab1b8741e967955d883e3f23683d473a AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.102-noble@sha256:25d14b400b75fa4e89d5bd4487a92a604a4e409ab65becb91821e7dc4ac7f81f AS build ARG BUILD_CONFIG=release ARG CI=true @@ -28,7 +28,7 @@ RUN cd /publish && \ mkdir logs && \ mkdir nethermind_db -FROM mcr.microsoft.com/dotnet/aspnet:10.0.3-noble-chiseled@sha256:3b0bd0fa83c55a73d85007ac6896b9e5ac61255d651be135b7d70622af56af78 +FROM mcr.microsoft.com/dotnet/aspnet:10.0.2-noble-chiseled@sha256:cc6a8adc9402e9c2c84423ee1a4c58a3098511ed5399804df0659eeafb0ae0cb WORKDIR /nethermind diff --git a/README.md b/README.md index 7888790426b4..e7443d757229 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [![GitHub Discussions](https://img.shields.io/github/discussions/nethermindeth/nethermind?style=social)](https://github.com/nethermindeth/nethermind/discussions) [![GitPOAPs](https://public-api.gitpoap.io/v1/repo/NethermindEth/nethermind/badge)](https://www.gitpoap.io/gh/NethermindEth/nethermind) -Nethermind is an Ethereum execution client built on .NET. Nethermind is the fastest client in terms of throughput, block processing, transactions per second (TPS), and syncing new nodes. With its modular design and plugin system, it offers extensibility and features for new chains. As one of the most adopted execution clients on Ethereum, Nethermind plays a crucial role in enhancing the diversity and resilience of the Ethereum ecosystem. +The Nethermind Ethereum execution client, built on .NET, delivers industry-leading performance in syncing and tip-of-chain processing. With its modular design and plugin system, it offers extensibility and features for new chains. As one of the most adopted execution clients on Ethereum, Nethermind plays a crucial role in enhancing the diversity and resilience of the Ethereum ecosystem. ## Documentation diff --git a/cspell.json b/cspell.json index c65fe84f9159..313bcb6496c2 100644 --- a/cspell.json +++ b/cspell.json @@ -35,13 +35,11 @@ "adab", "addmod", "affinitize", - "agen", "akhunov", "alethia", "alexey", "analysed", "apikey", - "archs", "argjson", "asmv", "aspnet", @@ -50,7 +48,6 @@ "autofac", "autogen", "auxdata", - "backpressure", "badreq", "barebone", "baseblock", @@ -88,9 +85,6 @@ "blocknr", "blocksconfig", "blocksdb", - "blockbuilding", - "blockbuildingmainstate", - "blockone", "blocktest", "blocktree", "blom", @@ -111,7 +105,6 @@ "buildtransitive", "bulkset", "bursty", - "bword", "buterin", "bylica", "bytecodes", @@ -122,41 +115,17 @@ "callf", "callme", "callvalue", - "callvirt", "cand", "canonicality", "castagnoli", - "cctor", - "cctors", "chainid", "chainspec", "chiado", "cipherparams", "ciphertext", "ckzg", - "classinit", "cloneable", "cmix", - "cmov", - "cmova", - "cmovae", - "cmovb", - "cmovbe", - "cmove", - "cmovg", - "cmovge", - "cmovl", - "cmovle", - "cmovna", - "cmovnb", - "cmovnbe", - "cmovne", - "cmovng", - "cmovnge", - "cmovnl", - "cmovnle", - "cmovnz", - "cmovz", "codecopy", "codehash", "codesection", @@ -164,9 +133,7 @@ "coef", "collectd", "colour", - "CORINFO", "commitset", - "compactable", "comparand", "concurrenc", "configurer", @@ -175,7 +142,6 @@ "containersection", "contentfiles", "corechart", - "corinfo", "cpufrequency", "crummey", "cryptosuite", @@ -185,7 +151,6 @@ "dataloadn", "datasection", "datasize", - "dasm", "dbdir", "dbsize", "deadlined", @@ -193,7 +158,6 @@ "debhelper", "decommit", "decompiled", - "dedup", "decompiler", "deconfigure", "deconfigured's", @@ -205,17 +169,13 @@ "deque", "deserialised", "dests", - "devirtualization", "devirtualize", - "devirtualized", "devnet", "devnets", "devp2p", "diagnoser", "diagnosers", - "diffable", "disappearer's", - "disasm", "discontiguous", "discport", "discv", @@ -230,14 +190,11 @@ "dpapi", "dpkg", "dupn", - "dynamicclass", "ecies", "ecrec", "edgecase", "efbbbf", - "eest", "eips", - "eliminable", "emojize", "emptish", "emptystep", @@ -269,11 +226,8 @@ "ethrex", "ethstats", "ethxx", - "evmbuildup", "evmdis", - "evmexecute", "ewasm", - "evex", "extcall", "extcode", "extcodecopy", @@ -300,22 +254,17 @@ "forkhash", "forkid", "fusaka", - "fullopts", "gasrefund", "gbps", - "gcstatic", "gcdump", "geoff", - "genesisfiles", "getblobs", "getnull", "getpayloadv", "getrlimit", - "getshared", "gettrie", "gopherium", "gwat", - "gword", "halfpath", "hardfork", "hardforks", @@ -338,13 +287,11 @@ "highbits", "hiveon", "hmac", - "hmacsha", "holesky", "hoodi", "hostnames", "hotstuff", "hyperthreading", - "idiv", "idxs", "iface", "ikvp", @@ -354,14 +301,12 @@ "initcodes", "inited", "initialized", - "inprocess", "insens", "insize", "installdeb", "instart", "internaltype", "interp", - "interruptible", "invalidblockhash", "isnull", "iszero", @@ -369,13 +314,9 @@ "ivle", "jemalloc", "jimbojones", - "jitasm", "jitted", "jitting", "jmps", - "jnbe", - "jnge", - "jnle", "jsonrpcconfig", "jumpdest", "jumpdestpush", @@ -415,10 +356,8 @@ "liskov", "logicalcpu", "longdate", - "longpaths", "lookback", "lukasz", - "lzcnt", "machdep", "machinename", "madv", @@ -427,12 +366,10 @@ "mallopt", "mapg", "marshallers", - "marocchino", "maskz", "masternode", "masternodes", "masterodes", - "materialisation", "maxcandidatepeercount", "maxcandidatepercount", "maxfee", @@ -458,20 +395,14 @@ "merkleization", "merkleize", "merkleizer", - "mgit", "mgas", - "microarchitecture", - "microbenchmark", - "microbenchmarks", "microsecs", "midnib", "millis", "mingas", "minlevel", - "minopt", "mintable", "misbehaviour", - "mispredictions", "mklink", "mload", "mmap", @@ -479,7 +410,6 @@ "modexpprecompile", "morden", "movbe", - "movsxd", "movzx", "mres", "mscorlib", @@ -519,10 +449,7 @@ "networkconfig", "networkconfigmaxcandidatepeercount", "networkid", - "newpayload", - "newpayloadmeasured", "newtonsoft", - "nint", "nito", "nlog", "nodedata", @@ -531,7 +458,6 @@ "nodetype", "nofile", "nonposdao", - "nongcstatic", "nonstring", "nops", "nostack", @@ -540,7 +466,6 @@ "npushes", "nsubstitute", "nugettest", - "nuint", "numfiles", "oand", "offchain", @@ -568,12 +493,10 @@ "permissioned", "pgrep", "physicalcpu", - "pipefail", "piechart", "pinnable", "pinnableslice", "pkcs", - "plinq", "pmsg", "poacore", "poaps", @@ -600,7 +523,6 @@ "prioritise", "protoc", "prysm", - "pshufb", "ptree", "pushgateway", "pwas", @@ -629,12 +551,9 @@ "redownloading", "reencoding", "refint", - "regs", - "relbr", "refstruct", "regenesis", "reitwiessner", - "reorgable", "reorganisation", "reorganisations", "reorganised", @@ -667,7 +586,6 @@ "samplenewpayload", "sankey", "sbrk", - "sbyte", "scopable", "sdiv", "secp", @@ -678,23 +596,7 @@ "seqlock", "serialised", "setcode", - "setb", - "setbe", "sete", - "setg", - "setge", - "setl", - "setle", - "setna", - "setnb", - "setnbe", - "setne", - "setnge", - "setnl", - "setnle", - "setng", - "setnz", - "setz", "shamir", "shlibs", "shouldly", @@ -705,7 +607,6 @@ "signextend", "sizeinbase", "skiplastn", - "skylake", "slnx", "sload", "smod", @@ -715,7 +616,6 @@ "sparkline", "spinlocks", "squarify", - "srcset", "ssse", "sstfiles", "sstore", @@ -731,7 +631,6 @@ "stfld", "stoppables", "storagefuzz", - "streamable", "stree", "strs", "stylesheet", @@ -816,32 +715,24 @@ "unreferred", "unrequested", "unresolve", - "unshifted", "unsub", "unsubscription", "unsynchronized", "unvote", - "uops", "upnp", "upto", "upvoting", "vbmi", "vitalik", - "vmovdqu", "vmovups", "vmtrace", "vote₁", "vote₂", "vote₃", "voteₙ", - "vpaddd", "vpcbr", - "vpermb", - "vpermi", "vpor", - "vpshufb", "vptest", - "vpxor", "vzeroupper", "wamp", "warmcoinbase", @@ -860,18 +751,14 @@ "wycheproof", "xdai", "xdcx", - "xmmword", "xmlstarlet", "xnpool", - "xvcj", "yellowpaper", "ymmword", "yparity", "zcompressor", "zdecompressor", "zhizhu", - "zmmword", - "zkevmgenesis", "zstandard", "zstd", "zwcm" diff --git a/scripts/benchmarks/compare_bdn_results.py b/scripts/benchmarks/compare_bdn_results.py deleted file mode 100644 index 8b5c1d81b87f..000000000000 --- a/scripts/benchmarks/compare_bdn_results.py +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/bin/env python3 -"""Compare BDN gas benchmark results between master (baseline) and PR branch. - -Reads two merged BDN JSON files, computes MGas/s from mean execution time, -and outputs a Markdown comparison table suitable for GitHub PR comments. -""" -import json -import sys - -GAS_PER_BENCHMARK = 100_000_000 # 100M gas per scenario -SIGNIFICANT_CHANGE_THRESHOLD = 3.0 # % change to flag as regression/improvement - - -def extract_scenario(benchmark): - """Extract a short scenario key from the benchmark parameters.""" - params = benchmark.get("Parameters", "") - # Format: "Scenario=name[params]" - if params.startswith("Scenario="): - return params[9:] - return params or benchmark.get("Method", "unknown") - - -def mgas_per_second(mean_ns): - """Convert mean nanoseconds to MGas/s.""" - if mean_ns <= 0: - return 0.0 - return GAS_PER_BENCHMARK / (mean_ns * 1e-9) / 1e6 - - -def load_results(filepath): - """Load BDN JSON and return {scenario: {mgas, mean_ns, ...}}.""" - with open(filepath) as f: - report = json.load(f) - - results = {} - for b in report.get("Benchmarks", []): - scenario = extract_scenario(b) - stats = b.get("Statistics", {}) - mean_ns = stats.get("Mean", 0) - results[scenario] = { - "mean_ns": mean_ns, - "mgas": mgas_per_second(mean_ns), - "stddev_ns": stats.get("StandardDeviation", 0), - } - return results - - -def main(): - if len(sys.argv) != 3: - print("Usage: compare_bdn_results.py ", file=sys.stderr) - sys.exit(1) - - baseline = load_results(sys.argv[1]) - pr = load_results(sys.argv[2]) - - all_scenarios = sorted(set(baseline.keys()) | set(pr.keys())) - - if not all_scenarios: - print("No benchmarks to compare.", file=sys.stderr) - sys.exit(1) - - # Determine regressions and improvements - regressions = [] - improvements = [] - neutral = [] - - for scenario in all_scenarios: - b = baseline.get(scenario) - p = pr.get(scenario) - - if b is None or p is None: - neutral.append((scenario, b, p)) - continue - - if b["mgas"] > 0: - change_pct = ((p["mgas"] - b["mgas"]) / b["mgas"]) * 100 - else: - change_pct = 0.0 - - entry = (scenario, b, p, change_pct) - if change_pct < -SIGNIFICANT_CHANGE_THRESHOLD: - regressions.append(entry) - elif change_pct > SIGNIFICANT_CHANGE_THRESHOLD: - improvements.append(entry) - else: - neutral.append(entry) - - # Output Markdown - lines = [] - lines.append("## Gas Benchmark Results (BDN)") - lines.append("") - - has_regressions = len(regressions) > 0 - has_improvements = len(improvements) > 0 - - if has_regressions: - lines.append("### :warning: Regressions (>{:.0f}% slower)".format(SIGNIFICANT_CHANGE_THRESHOLD)) - lines.append("") - lines.append("| Scenario | Master (MGas/s) | PR (MGas/s) | Change |") - lines.append("|----------|----------------:|------------:|-------:|") - for scenario, b, p, change_pct in sorted(regressions, key=lambda x: x[3]): - lines.append("| {} | {:.1f} | {:.1f} | **{:+.1f}%** |".format( - scenario, b["mgas"], p["mgas"], change_pct)) - lines.append("") - - if has_improvements: - lines.append("### :rocket: Improvements (>{:.0f}% faster)".format(SIGNIFICANT_CHANGE_THRESHOLD)) - lines.append("") - lines.append("| Scenario | Master (MGas/s) | PR (MGas/s) | Change |") - lines.append("|----------|----------------:|------------:|-------:|") - for scenario, b, p, change_pct in sorted(improvements, key=lambda x: -x[3]): - lines.append("| {} | {:.1f} | {:.1f} | **{:+.1f}%** |".format( - scenario, b["mgas"], p["mgas"], change_pct)) - lines.append("") - - if not has_regressions and not has_improvements: - lines.append(":white_check_mark: No significant changes detected (all within +/-{:.0f}%).".format(SIGNIFICANT_CHANGE_THRESHOLD)) - lines.append("") - - # Summary table (collapsed for large sets) - if len(all_scenarios) > 20: - lines.append("
") - lines.append("Full results ({} scenarios)".format(len(all_scenarios))) - lines.append("") - - lines.append("| Scenario | Master (MGas/s) | PR (MGas/s) | Change |") - lines.append("|----------|----------------:|------------:|-------:|") - - for scenario in all_scenarios: - b = baseline.get(scenario) - p = pr.get(scenario) - - master_str = "{:.1f}".format(b["mgas"]) if b else "N/A" - pr_str = "{:.1f}".format(p["mgas"]) if p else "N/A" - - if b and p and b["mgas"] > 0: - change_pct = ((p["mgas"] - b["mgas"]) / b["mgas"]) * 100 - change_str = "{:+.1f}%".format(change_pct) - elif b is None: - change_str = "NEW" - elif p is None: - change_str = "REMOVED" - else: - change_str = "N/A" - - lines.append("| {} | {} | {} | {} |".format(scenario, master_str, pr_str, change_str)) - - if len(all_scenarios) > 20: - lines.append("") - lines.append("
") - - print("\n".join(lines)) - - -if __name__ == "__main__": - main() diff --git a/scripts/benchmarks/merge_bdn_results.py b/scripts/benchmarks/merge_bdn_results.py deleted file mode 100644 index c3e417e5e77b..000000000000 --- a/scripts/benchmarks/merge_bdn_results.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python3 -"""Merge BenchmarkDotNet JSON results from multiple chunks into a single file.""" -import json -import os -import sys - - -def main(): - if len(sys.argv) != 3: - print("Usage: merge_bdn_results.py ", file=sys.stderr) - sys.exit(1) - - results_dir = sys.argv[1] - output_file = sys.argv[2] - - all_benchmarks = [] - base_report = None - - for filename in sorted(os.listdir(results_dir)): - if not filename.endswith("-full.json"): - continue - - filepath = os.path.join(results_dir, filename) - with open(filepath) as f: - report = json.load(f) - - if base_report is None: - base_report = report - - benchmarks = report.get("Benchmarks", []) - all_benchmarks.extend(benchmarks) - - if base_report is None: - print("No BDN result files found in {}".format(results_dir), file=sys.stderr) - sys.exit(1) - - # Deduplicate by full benchmark name (in case of overlapping chunks) - seen = {} - for b in all_benchmarks: - key = b.get("FullName", b.get("DisplayInfo", "")) - seen[key] = b - - base_report["Benchmarks"] = list(seen.values()) - base_report["Benchmarks"].sort(key=lambda b: b.get("FullName", "")) - - with open(output_file, "w") as f: - json.dump(base_report, f, indent=2) - - print("Merged {} benchmarks into {}".format(len(base_report["Benchmarks"]), output_file)) - - -if __name__ == "__main__": - main() diff --git a/scripts/build/Dockerfile b/scripts/build/Dockerfile index 57fad31cfd91..bb2f165f6116 100644 --- a/scripts/build/Dockerfile +++ b/scripts/build/Dockerfile @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM mcr.microsoft.com/dotnet/sdk:10.0.103-noble@sha256:0a506ab0c8aa077361af42f82569d364ab1b8741e967955d883e3f23683d473a +FROM mcr.microsoft.com/dotnet/sdk:10.0.102-noble@sha256:25d14b400b75fa4e89d5bd4487a92a604a4e409ab65becb91821e7dc4ac7f81f ARG COMMIT_HASH ARG SOURCE_DATE_EPOCH diff --git a/src/Nethermind/Chains/arena-z-mainnet.json.zst b/src/Nethermind/Chains/arena-z-mainnet.json.zst index 10503dbf9dbf43f30b371e38139fa85e5d97b8c3..fef9920bc9192b35e375e370fb7cac36df95bb39 100644 GIT binary patch delta 18203 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 delta 19943 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 diff --git a/src/Nethermind/Chains/arena-z-sepolia.json.zst b/src/Nethermind/Chains/arena-z-sepolia.json.zst index 0051877e65c12f082beaacee49bdbec72aeaf2b9..3a5412fe44e0bc72478dfb8981f5386a5f49841d 100644 GIT binary patch delta 14670 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 KSANh@sQ(ArF(c&w diff --git a/src/Nethermind/Chains/automata-mainnet.json.zst b/src/Nethermind/Chains/automata-mainnet.json.zst index b2fba4976c6de4ba6cf1cb1eb0c4b88152b2a789..4c2ad397e9624fa4a90581f6a9ba83cec5a8e6d9 100644 GIT binary patch delta 14629 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 delta 16157 zcmYkjQc7mdDl3OB;nmse8QQJ0k259*C3LZa6c-LR%E)Eo11a7P zbQ?ljbZwE21ngVrFG)z!%WPs;X3y7c1zb~r2SJ@3m!uLi2@bBGN)`5TcMv$MG8yA< z;`8aQ%suTIVmQ)y7m+1Bc`c_Irm8X!T7w~Dx*#~kh}!5O#CyA1mO5&S4vhG*A!5fY zJ8>{(Thy>}7gQ;6S$Em+;CrbiII6Q zThL)|0~i7rJJ6Fsv9ix*R5FmQ`4P1MJCffZ)f>=FHp+y?mc~l;@<@=-nT|*@*g;&R z)Ueh0Z@Z8jP=RFIYuXkjiC zpp65NBw7e;a|nTugiyx;pomOH906JSQj!mrzetivli!L`SRswY8-yWJSSiv0y8Kt? z5AjQa4Zs^n#sETCwlN^ko?SR0d5onR2;YiD%3inzrg0!CHur&w#r_&LP`LAw6_9E4 zEbfsY(}X|>3?m3}9x6G>Sctufx1c<3FOKbM2BO~(m_|zN#&n>#!1;mLcd;!-aE>6L zuxzU(<3z#T{Xz2unx%_Gp(95i%vh|vJ)8W3ObaC9|(P?(DT z5)Gq&-3kDI8Bh=kMrZ@YvFtCEL_$DuL;ChSM?WePlpkuw4;f`(`W`q32d z_JhJij5Qd+!Ft9l8)@}pn>FB=B@8%26ocjoK@!;B2Cnh}K_LYodIuw!A%S4v-$DuX zqcRO}1ON#I0O{QhK=BzN+6n<9bOa-~gJOXJ0f8W4(*;5n2g5S}f#eAR0TBgYL;OdE z>URJ^6oG^W3IGCO0_6G9P1_E-icW(MoZIKkb+)8!Me;D1xE7Tp(ksR<%q2`Q7bIr! zbvg(IBJ%APfFcgvgZ~0*1{T>JfW!s)?gu3*um~3dVS@q!0!xrKM9`2E8o7rM=_m6H z@Q+#8Wx(1A^bQ2aP(qLu2fdX{-ll<=+XdMOcr{`~8h{|#1t7cv!Sx4lEM?B&;Wo86 zj)2?k>xro9gD*(YG}lXYzJl$m+OMG$M~a;y`g$0vKapgz=Tp9$vot|i-8ArHj|C4q z#oC)<6Qfr(tLT9#7HmYMk@Y(4rYa+8X_=0V#K`R-bz0|b$-#;^2bsm_`DF*qW#J`u z>NPnwvz=&Z0i3(!z;lzR{W4Riu$32PqL`C$xFD7ZDH1M*~ z_{=n=#6cN%c<&?h;zAhnyFMW_T?scP!}RpYAWVS}ssh0k8-YMUwHyQc`&mGtACYdM z`F3|fa|Ui9Cy9|P29AOeJcAK!iL^kbAh;ko2z(px0k*rqh&;P+TA=us5KJ9LA^o5T zY##q|O8~Wv$hGSfgrEV27}F0t7>opmfctNHA@I!sh4+J!toDQA`rZaYx;6wt@E9S& z8F7vo39b&{yEY7P00Dgrc=rb*N*Dz~wgm%~5NTzN)3C0W6z_?S7Bal~n45NN)|1Fs z__IX-tW-+S?KsW0xHeRmAg^wd6*~_Pmyh%-WEw9|Bw1J=VAZIt+3Kr4^J1#i225vz zHDTJp_K>tm(cljda;Jz(U?pSVf``hKzi}ifWsfStSnjBhvfzhHH_hN>N&U+A=^CEg zh-w#!>}xu>yGL%+S?JcJmn(j{k1Ky@_Xd6dnMR%W(*`Pg9x}2zMHj!}tV-N*d8uN4 zB#s(-T(HvhaevGOBx5V@c4FaZNYgd*oCs1qIiyu_QivfVV<0eoJiG=KHr4bdpzo4# zQsv}he>*+meG6%t`mh)xz{}HwA5nXNl%j~%8xW(GS`({uy3)J1v4)z!y_xD>%KWj1jJJ9&q*Q@W*I4oi;3?d~jUjW0IL(Cot5 z(~99NIt#5vL1xBHdjCrE1jf;}s)Ysm$K*OJJ30@XS$`d9DPqNg?R0Az>je z99agJ!Y(DlXsvaql2*r5*5SA{Syj|+g^J?zw-+KRY34-+Vk&u6TF2y8mHOlHb$e79 zC6%0$#v}J7CBoeUgx^1lhhr*$r3HytZzZgJVx0PR`9@`8oP597PW}| zVQB0tl=HY#WzpfeC7Qx5yiB(S&hSb%&Q4O+GC2Y%r;VV9v?4XrW2gzBlQIe>nCE$C zUOHwvSblN#=%)ziL`*F-9WtIsnBQF1Z8uyG7ImUy z;6_(*qpR&SN@!6@VNaB@`F71Cz=a!Ifr3O@P-I_FWgPzxUvCM(s`S(KcFaM&^_Qx> zeOeJoa8*(aKPTh*PwvFjxn0x$e4PD%JzD7htRD7%&01%~{|ueT1ClZOU5zCOt}|eP zYrdnF%y4ms39!|R5H{1q$Mos`z|=Z0_0*+uWlw$6&~cZ zG2>>YkcjG+M26$Sf6U2pp->x0$mR*NkGT7{g_GCYS+^?#_V1z`8gvz887Zo$P17<} zf`5(xmXdG3N;RJScpP}Rd6JVcdw#y*pLdi#>Te+cMfHxSpZT?hqPRCmMJ`V@k{K|` z;=JC8O=3jIvjsRFg^(NDHDOS-n1r#KMa;^@Mn&gVJk%Q8UbQLj^<=W(UoLNYQ;MPA z2XEC3^ofrEtM)=Rp7*iOoAEx&v?G@_OxriVF@WEZ%H%BCa#9jxb?0Leb=Q`rvMlNp z$4!lS=O>uDdSqsg8ZNeW zd+az|%Ijam1#&vmjBAf5E|Lb3mXb&dGA4aIxq;=@Fr$^cPJuq5xlL7Ve;XVPz{5r^ z+t8>1xgw|^V!FjP$dMA<`+HyZVm55`@9G0GR?<<@VZX|H5^D0&;uy{=sm=XgMb4=v zvQQ~I-pB^tq^b^;RiackdnWLjO9V$VBvdBf**nSLn(^DbcW^C^T;>l;*|^+;!$JiG z`1nOKC)jFmmr!$v-^U{V!uWAUL@pQDz*pV_P_?}zW63Z{CvD`Xq|tGFrWI|LPHns& zaIuqIMxo-R4hM=$SA_ZkUvhT#c0W02DW~11BpgA%EX+guADF_A|7s}faNRnZQ4+K= z`LEK==87BJ#h}Y>h5cn$A?>rVrYv()tdB%kH0i8iB1?-b(@0UuSm6Iiu#oklu&6-- z;E0z^$G51w%UOWs8ipNIzBr$&ai{o1{fbSCwY`{fICCzPG);8kj@kb}L=fF=YA8ka z+Su!02b^|VJZvShaR84)JoPV^KBz^Cga4sW`HL&W3zP7RJY4z}=@4ss_eFXGOk zn~lWzs9eQl*CF1E`T7z_T8;bV;A$c;inIk zk2@j;xy8L&O)W18NzLDNK&oYSItp(eYt`$%w`rVklToKpuPZ^3IN~g(v?faf;D}8? zQTM@Q-lrIr=`p=fC!;9k(cNN2$`~Q)eHKO$>r7n^s@4Rb9mBg95UD^$rQnGibl@e0 zm~TR%jMTR1!)ZrLpuNmYvB}IM&r{2YX;TU|8&7OGrJ08Ol^=2=@m)YjwIm888IF=; z`aJ_?i|m%fTtWK=S{|oQH~;|yK(O^iplG_IX-_F!fBsH&98qp)pjO?%==2#{EC7`lOa_rn| z=+}m`4XvS%ROb8rZ`~GrPe7b(gt+!xju*H_FkNh^`>7uTWeNK33%ixOM3R*WXrAK&rmziLqn6tcY&~&9ADD5dElT`7^m51Q?P}3k4(D6}Xuij0#U)xuy*E;P zxhDIYP~5k)%r&qx)6!d6ooV9FF{nE9Qm}a`VR+9L3!L;&rkUA5q<$BK%8?g}5=SZXm#NgOe8%+kVma~bVcwKFzym2-K{$Ar}9&(o&ne9@$Qxa9}a zEIhKb*l?DjvhpIn%7tL8%%qd0hmGHOPJfvGYRwiV1xr@}gf+vl9%z%dWc6Vmx|LkBOi~J z8)oDyMbW>1eH@s3oc+4|42tzMu(y`FX0aDDA?tjQoj6)8HLt9T1UE1(!$anAZk%9O zIT5)`JNuLYh=o!l&D=-@pNB2HO`m<{t?*r(_4XF)ke6{#J=F-|VOSJlZkp36RPJ?- zoUDzQ)i4xh1^8(49=Ejoa6i1+AD6%N&5uw3Bg%s^os8J{j zq5(npIWvR^F(rhP7&GC)6=Q`35efaj|HcE3O7zGN0ip-#el zO-KP5!=*8F6Te&cheTm$=yW{c{|wzOR|UC3d|c7KpRQ?D>|}@c!>qe+@_yqk0+cG& z#)jr}?eCt9_6f6vPM|yxH+K$jHadWi3YM?Od5qYr6a&MgXKCeYrsos1OTzYd1xOLZ z_0Nd@R6taS9qx4z9YBdGbq=1-EB@<%GESAYCC$qt+PgEp51;ce@mNy8>*;#c2&JA&>6z{xHsbL z&r}@q-LUA@SW4u}ZH~X!)Zx+CSx8ri9HJlwG8>aQej)t)6?+G5Oq(SV=6fnPq!H74}l+)|Pvtl3dykti0S zdJ4R1bt2T(Tm3V=p6qso2!RBYyfur(?oAQ`$SbZU@Ns|-3pq&vY$LUNaRh(~t+B_k z>#9*=A6-AK2baPQ(q8oLbB2jCS)mxa;kS^SxGT7e%4H3VSn_8FqaGbblTDO_xLKw` zYfvNfsn^0E{GPTT4d}p)T{Q{mav-*Ij^ePR8|p_hpJ(cmjl2!)Ww*gyN#SY%;W-|U zfeYXnDho5nbC!3Nqaq2fNmRgvPhoPE?xVgWYyz3cxQvG~Z}kHP&+SLh??-h}Mv z9_8k!HtP1}Nu-Yb5QCR*b(jqi!|WaQ00b1q*LV`#Zxvafmwy#db|+!XZSc9T%NbJm zlP51tq54`X>CgxT#Uhu_T92@Ig5W7OY)g!!O6lTqjb6X#Cei1qEGPiyQnfneN{NMR z5Xl9oB$ftc213HJhPueXtc}{FzQ13nJqx;0aeVYNas^C5cCxs}uaF~WM}lC+P{W7k zKOGFq!*k~OZz)FWS#3O5tHS7l{dM15Ib?J)o0>7X!SbmF&OZ4(!{V9;8b&kg-4Pb; zv3IqZ6hjaC9p4K13=V+i^=S@7#B)@2oa6B7p$&gW^7-{ZzVv?A+a~xFjmVH{aM`Yp zjLe8)9_W&9riJta@vV}UUy4Jh*4N-Cw0?*~v8-KHX_5ii*k_$3B|S@hQBD#bWfKM6 ztD+QA+ngK(BR}KjqqHbX$a4whe-P5V66iQ*4kG|lxszjopdSG*ksyr* zmO?L)vHRZTft##x#zXrF1_2Du+pL+76dS8(AiV)Xo;3kbhJX&T!9u%Ho!=)mqez08 z@Oh#kn3&7qe>=UDBQ*>OIe^xie83g%yh<v4*~w=lJ!C;)?zgqm86z z*8Da(f1FBqPsn_A*U4C3moRE7GR>KyvGB-3N}nNqqVWo>D*#4NYk8hfGx{^IS?o<1 zu-e8P!Mb3-kGC6}OyzlBt+6&49NMvWWMypXwLkAplez)7rdrLY@;76DQE^uBnlcwo zJ5Jyxe@DDg%#~Fh5ned{mb_}>it`YS@K?1522*o(v3fuU1rO$q4CMh0(rX;AyuC%$ z!5%?+bsFZ~RSpK7B~}p$_(dsqfcd1hh0^z&bo(d9)2#Et`-u$2sPOaeaDabQ(EXn2 zyTrgs+S4AFq&kNqO_Q}stA0kioOD~ZR7X+#0gE(bHrj_fd^K>oB25r@a$ z{_~kMSd`XwSq$fyV|C=uV$RzraOKnjZU9YoIR9;$iO0X;`Pd;t8*t-O(e>)MjmZ4K)$R z{{0icw1U(2_9n&Uhavv7wu8FA2pGMBo5v4-woVS!Da=s5p#QlY-~Jh3xZ)O@`OQIy zm`lw{BzKo{V_8J2&a%+64iFYn~aeipYJc(V)wF2 z51kbxt}2wFjpOR-_PBr(LaHHJhcrfk-wQnU5YwAmDNwdOz-$gxDLTP}U2Hc!WhcO& z4QH?kN;do>WB9O|Wb`e!%zh?6u4)HJ{D>ya^q&ldg`Ud?Ganu2B|$Kpld}o&i7g6`xvI;cvFKPyy- zno$&`Vnpm3s%f&<)jHjY@=*akz6rLx9Kr zGsO#i6{66ANO&$e*pA8$iMD<3$7N0MA$n+7(8YK_^c(876P>aED&pV#&;I^NJE=Vw z^dbMe*c+|td7P@QX-M)!9Y8M+rY=L@EDpK`l}NFMX#Q10>2i(5`P6|@3hE{) z?3*4s5Ce8R5c1Cr#Fta|l1Se2E6cISs`WhlK}Gnuk`4^RJhaheC2-4paxsTtZE?4~ zpP9Qg)T+;l4(pFWqrnIG*5_@X%{Y3MoNXeJOlAYQL8cgf<;^-0ln2QRNGkgR_psYn zPqy%C<8#A27qgIqTa!l#dslwysgG{gzK&$UBYxwl;--Gq3QnXBwD0id!PXH%EvOTG z3DPqIPdmU{Wp%a{mL6xL$U8`G;V~ELS<1K1G2k?VjF=8O2wejr1y<0O8@W;rQ7 zA`PHhw$P_KKX|1y!O zzLHcA2K-*=#a{)`{uZll^f?Q=p{Z1eF+4Ybbyib8Lc&O$5h0_7XqM$_FH(!mX zw0ADer8NUH5*Us1P>pI?R$dYz!0oJBoZNNTnvn?D>?}{($DTZ}%a32t*vY0>hgg}# zgN@%a;VyTjUjND;!QVpg#ggB%mtS)(AxR(zDl~9yK~DyJHDuErhKf6wTaTU|8Dy2u zb}aMkR5)ISve){aiC8pSejIMJz5+oi@ogIQ?&Vqd%&`|vh)(7-ZOvv5gLKrBDf~)u zZFgLqj#At{d%_HYWevwD7+_w8uPD$aQGd6TZ9-axe7`bX}i%W*v+2W(XX z{NY_N!Y7)`3g5ksq1{&D01w8(uGn?bcKj;E@(TbpcDU@W3mhy(J#VbR8zm8~iniKY z?fZp?>l{nkv{=w@!P00qV`4b24KqZtlNKx8!dnkQBUuyuS5bZ;68-sK=FNHcoiH=C zZ*S_&!GxG!Lc@Pz!L@eRiSPQBzIhG_mzLr03vMwATWDaIF%#y(nZ>1k8#`CbK_R_b zL+bzpF1rlx_!h64gQ(aKv+u|^F(Sv{y>kfr3N!I86#4ka+F33d71QAiiw;HZ?YFgF z14;^i`+ehO+7>liWamH;{MM!k9a8kowZ7R};%-v^^Msd@E1eEOke*!wM)!&J`;3c8 zJ0be8R)y#mAT(GpVEri?Mqq)O95C;gA!7mvC`=t66C&iWnk24S=_d$r7(+v` zuvA1J)=VdIDWi@`e_gu_>p`y8(D_`=OLVc$Tz>Hi)O@!}F3-h|W#W*sZja{Ft$7Hz z+Gf!zHU^yE-3dMQy%@fBym(I+x@bk+gOqG+A=zAA1vZPn`H|{+D6KcsalRC1yhy)+ zc5ctR@x;V1&F@owtrSL3$hI8~nOPeQf6)EN3#b&AOe>i}m%#WijX3=V((hKY9MI|; zj1u&=G2+@*ud2qmb2!om=#oG`D)%=q*tS#(On z#!K?9qy3n%m$Ii!X4o=+NTN@R)gEa@5IeztZ1&hK`Kx3re+mQI2*B*qi@Cn;%H_}~ zl@MYrqx-CQ-D~MaW&u|G6;^m=LS)S{ zdsM><>=^XJSad;VNU|=%VHC-&?F3?Per1#WH3vn;se&YI)l}|9=tN#EzW^+a2{kL2 z?d{X$?zxG$oo;}?76#z3-dPC^?XPy;O#HHLO57Vp(Lo*bC<%L=A!qGPqv-rb^zL}UZsaevcKt&?fsp~$!@NNq^jX_2d- z!RZz`9yzuj(oi-ML zaK*mf#wylBt%eAgd&Ml7#figv^s=nphaGH?f*)_@k(J!A7^TjXCZ^4@@ol`UJo$R4 zr2Pj^uWmeK=MKqN&zl1TOK!cG-Lb-A&s6J-bdj>3k*`!bX+%IV2t&P`O+5{L!~|Xv z$mmEb4h*kDddKM*`f>d8XKBZF+ci*mK0-alHKF}E?hkkq%wFR+f2V)It8N`K!DT?e zW#?0Yhp6Q?cH~WPpgyJ5ko!s|X{p=%(vvrn(<7r9vLo=b*C#v16sh7Py>Duuci5)@ zq59}8g_7Hmcmm)Th4(LOf=aRsWl*IDvsa=*3MP@D6ZIF-3E3DzGqmI&pu}zcADL3b zNU$X&HmLG4@`matMh(!5j)4*wu_f4;Weu8qE4Q-ig*z{p+?+2RcZDpdG5XnDX>R6dx+*Q!Tsow{su!7BKK zUQTIzvr?nmO)pz-pINKf>Poea={$N1cA4=IF#`0ry-^ger7tvN*R38P|6}xsF}$5d z{J2OvHJW<955;AH<8G9Y>-D(9*Z(v$(rvWuE%%Y5V-rq4c8l@}xOr$u{fJ93YaCnJ z2D?y8<^ix<08>wf-ivHW+(rBg$q+HVaS=+P7lnc*0jH$=l_OC*}G zNu~L-`GyCic27AEPHg(tg-_ZU^?PuTSSqX~QmbWNGoYuoU1yJjcQVR4*6Na0#;nC- zxH6g_E_?dz{w`b>)>OFwSPVxCH?0&Vx}tF3jsO?L`7! zCRkD5cT%5x6iz04jW=aa0j-?p?YrdcLYu*fzCOF~Xg1SJ%Pi||sHh$3kxSJ~-g2Ei zD10?~Z3Lg!P96_JDRYKG-eLKLT=4Yt3x5+(6mhP;k}|TRzh=Q&c+tBn3hrywmm4`F zng`&tzqW^EEJ~HIK^1TJH3bQ#g2{e0?%)m3AO|Nsj(?4+JOh-jdOV z6}h~Sx?qWpj;9MNy)9G5KYdb{+YhL)lm9{(3zd@Z?zg6(EYI^e1kaCLZ|Blvquayc znw5AX0S33UR7Ec>aS1NGg}wncVN?D_gf^kFcxd=;~lxHB7NPyP65zHyK ztur*Lo(2Ol=+hSiIAcz1@GCLlCwJ9I0-oT9{pAjvsa+z&u1u^sW%E7p0IqnMM0K?d z8(7klKmz|ti@EVI`89sl@8adE&4J71RD`mlcU&}c{$hhqx>)vqB`t7>g}DPff5_f> z__JW-5#BHa^Xi%-YTF`;VF4@QYn?Y>8HDm0iiD6sso@5OYVmO)=j&!%8>tA3va9z} zwh+aE(Ht#0X;8feqc-HRwd(^#%&=+k39R6=J*f1EGJPw1DWAOH@&P!FsEL6wG4GK{ zi?o%}H(?N~I?3V{VrG`N@vjyPZ_KxI?qirQBnDrl7`F=!&Xe?hFMuyU-B87-$dqOT zu{oVDuTL~8+o@A61qD}Ax#K@^xQiDQJtcBN@58{utu0Y)O>uq`lvU&L+B;VmFAZFy&)e#s(>^#Vwjp=h6P)r9AD7v=21*tJXj_Jl9F6 zcAV?>VehW2TyC--ivSiNQ&1Dh*r}+Go4aD2(rV=^ohPtC!Bj?_nPnZXjbUTavHt3K z=N+4i1gi=T9u7ROicK}qB`F1y;)wUO`dPsMZ_`@S6tdj-K($hZh?QY7gTqpK4c);i zod&<9D!+7)tdsr`9nAdEMnCBr?<3xa0o5;pY!m@%C?xG@TL5CNhNVM_sIVmi_$9(9 z0BIokoWsW}Su?8-tUO`duF#CE*}SNRX<@7q?@;1Z06mGP3_J=aRu8Sh~{J-*KK zjnEirf4b|K)d4T`zdJ$+bv|(j9D*GrO1SukpCu@=&$H+414PeF9@t2Dl?u`sl1-)8 zy(Z^S9gg%&^-a``%VO=K4)-lFFIl zHnGH6=*(?MA#a|wpkL&T#Inis#~i2=BQj26GmuRk7y)Q{Iw1(-&9<~brYrgaXC+Xr z7z}k+yV5$yf+qc^%w7Y_nNTS-N|ssRRyFLQoQ_oRP^U^BtO&K|%dK*Lv=~l5gm|F?p>^ck+rprpK)( zKF|+(;eb6nmpt%c-@^nlofFrSG+eGtTst(@3klHfFmiej1K$LKv7x`<#6E+L2gXDc zemm1F@FF*=&Tv$@Ro<;4C0e zDZtaDw$=}Mj`H{N3TKH9I&Pr!41VSZeu(CZ76CL}bSA8}%K6YvoF`>@oWEU>c%pwR zeAu^|(LdBX&r1?)IiV)%)IQtRKhHJ3oMR|v$-0!PyviZ`W!X3aANnj*fG_2m^j%$`hNMlgjczY)wXyVC^;G1FFKr!>iGVN3!xd5B1xg z&jDmayjIvHqz2iC2YjyR5+$NlY5^+r+==sexkOe4 z6*CNa(VY|{1EbTSW7D9Dprhi5Z`6UIagmbA0)$cMyLI_BeU9{|x=7ngqZPKPKTSQD zzzJ1?WDAGibN-rer@*3KLBKm{(MN(Az60zX(u3ju6ah8q;g@v6hV5uu-G4oNMx7$l z$HH|2zdR{g9yEibv=EQTE;6cIsC)(|J%{Y6-Gp9uB(+TtvXGVC3U!d$HM9*m`kre+ zn3ucwL;0k0ci+o=Z?v;A8A`G8ffGq4S@3@Kaz|P3s~fP2UJ?I*%=o>Do8wxk$^&>= zlXV|c^X@6-%{>T{!@6|Hewd=qRERmpgsU0A9nFiFV};_T-QAc?#e03+?xSQUg-%R} zJ==;}^Xsz9$B?-GJ(IGk;90J-HKX-QJO(|(jYhdGEX&aQlhF^!tW=;f`WA4C)vch> z@x8j87d|#RtozQj&B)P7HlaQ@Ee`-4YW+L$U`f(GEl`yzlx@-11o3AXCb=`Wc2s_1 zGh!L=6rcqvPAhz|W;WQPq-{=^vNa|d>LSo2@*^8*_-E#LR8VWg#X7}?y&QA&5Zr9) zxFaNc?qRfl0NCjVOHEf(Q!0MjF?r!$i%I8Lyxl{kW0oqsh~Wfd3DxA)T?x=c2-|3F zcsG9jNYup=e3H#cY^0qaAk@~ZFUpnd53RjrX3<*WkppDlbn1a5v8H1viXdN8pYNl3 znOE+pFjj`e3xt%TQ%J|OR>=>?h`kH3XCd+HYM{04L|?Lg43&XT4J;r|-8>Nw>M^3gUObbZ0rQ0+wA|rz6ksI@|=%xpO8*k>@bqvJbT%Q5Hw4u-HSr!7u5n zS;1Eq;T_2I1^JkSJGvLX`N_@a%QS%?6kIJ|`N8-6On61c$=1jOMCL#0F-gvee0C1e6$@eT4aA&LpPhyQdW zSY=8rV+xsLup~!6Zn};U_57c9IKB-@FSp-l+GIk9udSr<+r;2wd-d#T9B>x>v0q8c zhMoktX<$L~ntR||LZSf1CX^+BEV4N6rZ*)&2N}jdk(fyHOH@G=S)8#XLRbnKNJ`oTm0ZkO!@OCPu3+ zZ91IL*(mW+4syDJZ)f!IWSQ*Gsh&4t7*>Cb#P=+r?2-`)C9?bqwxo{r? zCVIQ-RC3D*J*|ZHXCf!(1U?QD=W(BM)PQl$GP=4i$A4<;Ta`?B`v zGur;!9t!p_o+%D1aBU;N7raMJo{xBN>dB2Cw4ocxXhIJpQ9u4 z)He4q#{hsd3*fDK{2lq0=@gg}@|KkCEyb(H{FIiV28M(M^P{e?gh9x;stuBPJYZFU zW*<6YhM7?Pl76raw=fn7?F7%BlYYu(p%Vc4?1#HKhz&(~v^C)usH^0ipL~b=%PYIw8kPvXy8V~APvZ|Wb3c(gsCPuWa z;_K6S{!40f@3Ek`(~+Mi%8P`%vAyAOMF}de%Gpg#{i|5s5~z@oj^%Wq`N7k}YNx%` z5@f={i}#{+!+nhfTO9Lz&HgWHI)w1a7)!mY=ujH8v_7Z9k%Ku=^WdH_P~w(G-y3|3 z?Hr(?oQNq_ofB1-kVZx3YL;EfL^|P+)Qf#W)(;|)JNP`vg|Xb}0o!NFKh@WHrpmYn zci$^FJG1k=vIa34`Sb^6ahH_liLEj4Q0APdHTHN<)V`A;>bp3qlu_!-^zmYysOepK zi$+-L2)viMJ$pl5*-5gPXt6^`dHM0_MF_C}4n2PPE{?7nsD^FZ<&HwpJ+9O>kXKMh!xqc&ah0$76USJUSFcSmo#mk?ix ziFzCL_h42Or`P1h?`%dE6uQ=gZ#bw0e79#W+P-)N5E9E}H)si6wz?ZgWg-~cJ1>CH z3r*RVErem$^{>Jl3njMz&a^?t6eayqyOLWQ49UGwg8O=O+M*mca$1ySJa>*=kKZD! zFAJ;5XSNIV;@q0#5Df7~AuO4OoI%Vz;d7-r)jwNVYh*6xr|%oyXbqcdZ#x|wmsgV4 zJBa8esi8#|dkDrFbeE+kp3jVnu}}b)oU`=@rvq#4EMs51u@mbzFjwe2N=gr|T|z(D z#>Q}2{#tMV&hG~|UGPN37%s0r$=Hon!Zm^f zon7{tZh8oR-*b4wBJabi)Iy9W5v}6#hvyElOxeVeQcdL)Nm>6AnNoZw7_XZ zH7&?W=7}yTOI&by3eyPFLSw4&#yjwB;CZRv=$zh?rrx(&SS zpZotKy zrFRUrXsXO3;4#({Bd!BQ9I1^AW@@TT+y5tYB+Sd1`!eXEsj~kCk2N_T>1vplH^#$c zilUC429L6)8R;naN7!O=bXCUe1xawWGIt5h%iHN;vQ+`7V{ZOWVHlz+W75E+imHsc z0g+%0WoB5aC}SH5#a&(+IS>(NPi5BY|0isN<8G;qbOg+2Ev)J_P?d3vgklY&jdU3m zXH0NmvUsavPlBUwEsb;n6=$t`Vlv^VVjToY*uflh^%rF<{3EPYac@Kl#>`;icFs5piRf|G(g7T%0|~rPpv;X7n#}TPZOv!o`^j zSpP{2PBtvsvf6(VmpQS@VC$-i{RceOzNBo95N=XdxLj3`5D!gZa44h((=9kXIh;_! ziViXa?1xU4&d-tnfIT~?p*-|Qr13F*Fimo$foo4-;c&Eiw|;>* zkkvOS5`X!XELcJd?H3o7!z(R3Ak40U@jaa#PL#clr zrj&zM$9;+DN^(qL8-BmvAIIY~;78rFTngrG7?C!fp)*X=x;3@hcU3rV2H}I@>pBi$ z1t1`?R}jhrFONYZtInQUtshM^i8(|Mci$EGV_J-43i=~YrQI;3%`j}oe0jjhg(t*> zQiCaCo`zZp5We;iD1i8DoVzCJNUT{<_u=VYg$Tjq;>Gr};IT0|zapFyRCFI!5?s7M zTr$b<_%06V{d{?wF3J0BLqG|1LlD=<28?)#4PS2`^$w&-B*;hY2bnnrpcxJ>83G6?ZXq-)`V z_QPW@Z+3__I&d(s2$yt?pdks+y>OsSp2@#U`y>3(7q!>saL-Cw|7qE|-|EA!M8Z4t zy7mbQXai~Gy5e_OD647fv~0Gklp=4c5{a~ajcEAz4P-3a{5CEsJI8cwSc_Ozc1Ntx zLd;wSq}oXE**xb4DuL7x?wM4%#7JQ#l&ouX=5x?@HcQ;^^NwNhv16_&BsW)d!1a13 zHQh^LAQdX(s8_q=Yo|pC6-P8uA$OxR`y;{uz_=C?=f@WCgCN92CUez|MJOjmUSmMt zZ!zUgh`&=9?TRL5mP>&v zKr-TYcUR_Nwru&_a-w|V$`dj%FBuHupsCDz`@@l;!;TxKFz>R zvq7T}lR(NO$(lVfEmw>jGtbw?3mLPCoo zewBp60*N9Lg+fabfkFX^BT9z?#1eiBM*{l1f~QZWVx uUpxOs_LZ$#?R*LBPO(UX9uQ7wrFw<~c5CP!K2e<_%tk=l=)wOs+5h diff --git a/src/Nethermind/Chains/base-mainnet.json.zst b/src/Nethermind/Chains/base-mainnet.json.zst index 450c06d9d0d3c46a0c5f1a42bfdf04dc243974a7..aaaea720746b40fc02e899e2fb65d8354d88fb23 100644 GIT binary patch delta 9369 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 diff --git a/src/Nethermind/Chains/base-sepolia.json.zst b/src/Nethermind/Chains/base-sepolia.json.zst index c887ddeaf8db13cf083c565f469fc38509d5f95f..9e2091dc43a2abe91e9025463674f4168a861f2b 100644 GIT binary patch delta 9583 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 diff --git a/src/Nethermind/Chains/bob-mainnet.json.zst b/src/Nethermind/Chains/bob-mainnet.json.zst index 9b2e1d89607d011c8375e38c6ccfcbf20739f64a..6a90d30d85edf0e81d5b1465bef3ba36e5e0b27b 100644 GIT binary patch delta 7954 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 Dwc$OI delta 9106 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 Gy!d}={}L|% diff --git a/src/Nethermind/Chains/boba-mainnet.json.zst b/src/Nethermind/Chains/boba-mainnet.json.zst index b9ada0eee6339f51e5dc150d23940e44f1bf2ba6..5921651083973e0880f7846a40def096e8dded8e 100644 GIT binary patch literal 1502 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 aIb<*D-BVlTa<}EX*HHr6Ll%`W35Ww}W9YH~ diff --git a/src/Nethermind/Chains/boba-sepolia.json.zst b/src/Nethermind/Chains/boba-sepolia.json.zst index 4191037f691897fb1c3766ae71b6b92cd26319a1..ab72b76af1ac5dec523626667186ba38b4e4844a 100644 GIT binary patch literal 1500 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 A^8f$< diff --git a/src/Nethermind/Chains/camp-sepolia.json.zst b/src/Nethermind/Chains/camp-sepolia.json.zst index 1faf3709e11ffe69e6a7a03f522862c044ccff53..af0607192f97ae8d4ec81e487b21a374f0bc2cac 100644 GIT binary patch delta 10812 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 Z!`#13#uB8de`4~5gM+uM_huEl`G2hr8TJ4G diff --git a/src/Nethermind/Chains/celo-sep-sepolia.json.zst b/src/Nethermind/Chains/celo-sep-sepolia.json.zst index 222d78216eeed46eea3d664d2efd1c619dbf7d85..79009164efc63f1dd77b6888cb7244712df82891 100644 GIT binary patch delta 15538 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 E2a6-`YybcN delta 17097 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 diff --git a/src/Nethermind/Chains/cyber-mainnet.json.zst b/src/Nethermind/Chains/cyber-mainnet.json.zst index 46cb6963559929dd5c15f375a909e4aefeccb430..ca178a08100ec2071325b6f64cce0862369c2a01 100644 GIT binary patch delta 11488 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 diff --git a/src/Nethermind/Chains/cyber-sepolia.json.zst b/src/Nethermind/Chains/cyber-sepolia.json.zst index 244f260d8941b576f31c58c7d1106ec430caa64f..8cd41d3686636f2a06d7b94de3980de541730e72 100644 GIT binary patch delta 11442 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 afB!!xD8B+V{;7aBWq_BsMUrjm>VE;_nUoy> delta 12566 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 diff --git a/src/Nethermind/Chains/dictionary b/src/Nethermind/Chains/dictionary index 9617a6705bd58bb65bb20cb4e6875390026735c5..3bb35bbf9d5552c79f1b3906976fef2c89f3f123 100644 GIT binary patch literal 65536 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 literal 65536 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 RlnFnTc3``|eo=iD{|`)ZI^qBT diff --git a/src/Nethermind/Chains/ethernity-mainnet.json.zst b/src/Nethermind/Chains/ethernity-mainnet.json.zst index 3cbad235b874c0b690b22370fa464f0e6bb8977b..278fc5701e55d43799a05b75429d7f9351c7d4b4 100644 GIT binary patch delta 7347 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 KHFhuxT>TH|3;JpR delta 8780 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 YQz+-Ym&lC@Z22%SHg1i7*yV5kA60&BNB{r; delta 12482 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 diff --git a/src/Nethermind/Chains/fraxtal-mainnet.json.zst b/src/Nethermind/Chains/fraxtal-mainnet.json.zst index debfe6488fec103a535938b3b47d162d54ef2f67..869705de89cb1ea0f3970b6e2034d4a21f60f24a 100644 GIT binary patch delta 6980 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 BMoRzy delta 8405 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 diff --git a/src/Nethermind/Chains/funki-mainnet.json.zst b/src/Nethermind/Chains/funki-mainnet.json.zst index 9c0e92a9b5b5f5c33675498c64680bf3e59c2dd8..3f45d457365b68268ac7bdc86425df8a3a0ff9ed 100644 GIT binary patch delta 14685 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 diff --git a/src/Nethermind/Chains/funki-sepolia.json.zst b/src/Nethermind/Chains/funki-sepolia.json.zst index fe59a54d0e37ae7a2d34577ba6e1b246a488d167..eb82ebcedc17e21aea50d997e201f89c24e3738d 100644 GIT binary patch delta 11447 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 delta 12569 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 delta 19948 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 diff --git a/src/Nethermind/Chains/ink-sepolia.json.zst b/src/Nethermind/Chains/ink-sepolia.json.zst index 100be96e49e14e1484ee4498e46fe7878fd15be9..7e317838c1ece1d6105f79698e8982f48f17992c 100644 GIT binary patch delta 18257 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 KPq%ze0ni6hc=hW5 delta 19892 zcmXtfQ*fYNux+e~HL;V4ZQB#uw(T#rC$??d*2K1LYwmw;om2hL{jgW{uHG-J)?R&- z4>?r>2~ajOe1U5q%_1Uho`PDj4#-mP#+Q*HegFK8ykmp7s_~~Pok=I z67>tMmAg1XiiQlyPbc{vyvqYJ_E$7N^D`QaXqj)Xh)<$}-O)K~2r5Z&1C(Nv;8)E@ zQ3KN09+FtDNkkfvi8LT14c#&M$bHRN!KlY!jx;!(z7DS$wcc4(Ov0OPt8?)u+OO-- zC14Y0ye?Hw4MY6`BTy$sm@^aw2bRDdV<;LYi_JXo0^2phxX#xiry(Jx^^d*s@JdFg zRbqn5yGLl+q|*UvYP{$bWnpnNy;LCSxJeXx_aPSzOiDpP(mc0xIIj&!;PH0eHG<1d zpB+f%uqYz2wEhpJqH)*9M=2!<(cotM3n1PtMyP9Wx1TjRwxa=1FAPaqVO@9NKHI!)?h$v44&MjBLvC{vrt%Eth0()b8I{C z61LI~)1CHD+oE!c<)tGi%dcAuV7yt4U2j|CL*&y>JtDPV1K-YQ|DF6r0 zmyVA04uXycFW8^|UxE?r50H}z$RDsEA;_`9fefwrc&ejhe5_u-I=9kC$Le5a#NLID zHw1$W4THtxb?65lH1H_%|0TMr)W2u=7z~Su+(CZ1Rhpuf8^$3bEmF}s?SW3dDoW{R7?q$6Wkazegi&Koxs?Y#KuF0j*8-kaF4)5C*k&kBSQEqfWUnE6Yqk+k}2>CLAVG_<8Fe%ssswKCV}A)1<3mc{y>Zm z2!YWd5)#T26$FDvqMj8D1}E|tHrfRRqd^hulNLH+Yf}#D0GrtZWf8$c-p_;GgBcQH zpmhiifxL#CpG9WnWaNbh+yp`33E)IRLg4st*1>+TLi{(}2~bc*k6VKP5#_#Hf7q?r zJ~Y8Te{f5KaDPY~FepS2e^jEq0Blbb<=FaI=#$+@453{gCfH|RhE-W8JkcP~Asv>8 zKNor=F54o#1qJKBQ^k3MXJVSD=lGm4#bi%alQu5O_WyPMLpG}eBKJU%5Yce(pk9K0 zh5^F`I_`h8(G}Xui%zfCW zed2b=3~c-w~nkIu>ZC@k|o@8;pKMX>{A*5(fmNnPh32unb1AbH++xyRUV z7-$rL1P>tS2nD_!5tr`@2HXotcJ%M{8#d*n-46!;uIc=N;{gLltI+3ACa5KQ+sD{1 z6j&oD1PPDM$g7|s2p$B1peqDIJ=@n;*M~F(kA_Fd3W2DS*AIg303L(@QN&{UkQRcI z_@W!O_rNIcl^vH@kiS0~ix~5Cd!`{G>Vj^{?G1==u<#uqvu@1=?-Y5`u8EUlTZw8Y z{`y0%%3Pu`Ped(=uuQ|3QbgX2Pg|(;n^w_Ng1tSdS>0g{eF)#9pwV6fj%A|ml|7VXhI(H4AmPHhvE@{?56$*KX-VMk6s9tG&pSL<4vv9V`z%boq7 zA>yMNhwNs`!bCY(jhaHgmsVs)3^lzYG3n~iNwg!`x1HQ?&yUYgin8uHV=+hB?tA$~ zI?*+0y=$NmlrYOZWIQa!*EX<~ABl|J4FR6#7Rn3u%9?P6d|@`RmheTPSMp#OIuG&( zOvsGssm;0AeLp(^O1>m3^&k`SaB*8fzAz^y}g%>BF4T z^Xt#M_S+cW^~Y+icb%2xJNQeA>lk62*5-rDb{0qB7=)SPKWYDFfC1h#M}rk_d( zcRRk+-c3N?!qni+!F#i7c1a;x^{Af`NMcs?) zj+b&vmPbF0ZHY1klUT}`OLJj!RO5C~Pc&QbL{M;j&6FpWXBX3xj_xL;R2V60ifj!y zva7j{5Y5FEI+7e0Lb!$@tUpNaT96fonMP>K(H`wu$fj)kG1b0jl8Oo%NP5m` zc{&}0^{@L6Yf*#`SsG>Nfd2TzWYTEmyWTYF7hIE4foyUF&h;}pCO7dBk7?7J1K}86j*9aX>%I= zh;*O7$y1R%G+aEX9Ac#-k1j1qNr7O%rHqe9 z-EEVJv*&0&WztUuw{Qz9D#c~pE#myrQ7u!NI*Qt&B#Os43@*`8j(M{tWa?L=n4vq7 z`=AlCAqgi<3UXUmT*u_P`GWZyopfqc`L_fGso@JViz`d3PYs?4m-05kZno@$#1gLi zYfK~jqMfr#h5Al5z(sxeZwjGfSon*CRt9{3WrVp{uG0S%^#9B2kf{ET;8Ff>5%+%u zQtJOwK69D>rF@0|NBPG8Kjnj84`~)}FLz`3TUTW=FgzlYf_tu%&E&!xiY0)F0AOe> z7@rdspy(J7 z!EFj})9~QbjPM}$c9=yd5P5W_5K}_=SXT8?zb;Z(CRdS!YDX5SXnNCV1(Il{e`%4L zQUzImw687Y^Qht%ekuY}gY){oSDJRqPwk~FBa$AC-HV3^0n8WDwU0Ses<^^Da?N*n zn}3l`(?e-d}7JJx6%2QR&3xXq>C z+3-K+#BC+M9=UvH9ZUUefcI$y`Nn6j-g%m;h1q|?@7gFYSqhWCCh_Et|7|B#PTjG; zG&NCGH(g1%rpUa_@|l=-$>+2%orqX zEG;B@5K`JPW$sl<;H^7U9GB$%l_EUG87hh2$N*W-B$~Mf9WyWS3FLGsZ!jPX6kz;v zUSv9!gdD|-2!+Odx#u-~9C0n8s!dhVx3Zahmc=8)j6zkP=Q(AMb%Fn&tdGukO;1O0 z=38@ZQI1Yiv~HA{(48NV350+qkPpY3XW_QmDJWr{vsYz8F`ii7oO6gcoRp6Eg}pC0 z7c#o{e4WB}Zuu394v;q|EnsUmboHlu4WYs{!6o#O{#Pbe|UaAAhXJ%!lm?yhFGQHdGULx1?4by{BHj( z)ynIrtKC*{p7^NvsGiBO-f2vQ0fFl0uZRk{pvDnpb4+J#XFwco5^jqNkxi^BkuA4U znYOaLh)2x5@Brs(=?(KtQpJJ>Eu$oVt;o{0w;-16PFg}%n!6z*ahqvtjx35B6)7&k z{v5M-G6laSg^Vcz^LG_mwsWrPTWSi%RPh8hn2* zl2PUMQk7G4Qxz00%T7;5=5XkUT7}n#la};2?@K-;MYY}Ton$--4L4D(XVS*slH00! zhgrzbRdZsr)Ws3lYbp8M?qHW1=OE10OS(dKWf0 zXIbDlE$P7(v!{RD6v5Qk3*)}T&jYQ74nCY1*vm`v4C04=ncu+kT{-=9p~%^A`6Oa%D>5gPs;IO@((7}l6kJmuiK=i{h;hsi z*d(GOVaV7~)_fbsQ_WOSMXRxCMK!5b#Lq}|rvCc}lRQEWMYmv1QkJH!~b>h$( z#hzj+in!N(CUq^9(WyxYd}>t0P+~Ztm}$~J*3;bY3Bo-YY?P;Ur1Q!HQ`=0Z2yUH# zg4mt>EV52jqE?c($H;G<@qLeg7Cy;AsM zwbE5*nR{kSK}UOXvca@s@v*6`EmlpGpyImr_IHAxn*|iH*k`xIw@W0a1%Gx^<;{># zzKf2A1bKiM`-R1bsmxz8QJKg|U{I=|cfsI&DnWg_0z*$p&Ql4$`LfDxn7*dOSJG0~ zjU=*Hpd*`rOqeZ5I_VuHJ7p4{Len^IELggoxkDu`%G+e-h>)sWIr)WF$@FQhfIItq zRTEzn6#Cgj1l7W_Wlg}nH=%bTJi5%VF~&&RzIp!>GkAG%Hri34LxQU{HsuG z*Eb0KJ3eXU(T5|H?D=b<(eCU*=H{->jxMpyxiKOpB^SJwWWKNgkNh8 zw`rB;T)q8j^MonSvmNc;>t*^5NX&_W&p|rDDdiIPGe!e&4moy0ApT&cE9X%N356w7 zA-^^PPZjHUD0}wQtPDG@no`jSxedX!26VQZF4 zKg-re!9BL>Z$g*L8IhM-d(G*JR8vZS$>MY`YKPU77RkiEz7sOCn`brBnHI?@xz6^` zvN`VOEq?$jLAK*VXVb^Zv8;q5N`Z-6aS)-rt>dQX5;oHso<#?! zrZ2`WM#HSMKi<(h^s6&qfs1H+Lc>V1T z&&oIE(GS!PhnqF@CeBs6$Y>q}<3n&GZ@qm38!liifL9n87g%lGuT%0pp{=*X$Vy=U ztkg}Nfa>GM*$Lcc&qElp)tf((R8ReG{rfVOb*tc3$8KBlwl)pRQoS#;j0=%b>@1Zs z$c7o*K>?x)d8?>`>wX>(1)8S26a)@Z0Bx&0an(YdpWs}UpZOF50TdR zp2Xhu9o1*fEn*KE6Wj^u86yV~JXcted%N+_W`e0bu5YV|x&E|677DmtKmUm6Arqc1 ze}apqot3$SXvSO7HP}SWBNSjFb_X|TnF3z_4kP^WOQ1S8uCmE9&K7WvRt7nfT~$zg z>r^IXq542KL&IcmtpHzOblNND-(xv^DUFB7prmL4^|Yv22_vQ=m>H%gIrx4uIBi1Q z-NuXfsI~XziGnrQdi)5qeV^g=EUx*})uSS2A31$xD%llSqPx`#W8MxzeF_5O69^0; zOcx@7fJ>fJ`weUPaBRXi!L9mV6l%w@(*A^nKGn-aHA}u*n?SmAeB8!A^@eY`6w7<3 zDy0~v*K)+wn8#ceu`wd|)>q3`+04y{jf6AAt5?r{EetE$G}7gjU|Z(yEOiiFU*569 zx8{lr!C@Bt7d5@fi;nN8*cXEI8E+9AO*pYA- zB`R5Ekyh@m3J%64+8{mYF&^_wamlJtL9;7!8W$ zd7>h@diCR4do-kEZcAG?dAVbUjT5HQy@WHK_2}OuUDevTtD(-oq27NpnUr4n=bk2Aelim2P1ASg)wzL7wN@3M+;_ z_W<5ZcA|F0=JxJ1ee`y4B`~tgz32vZdI9e;T}i3!1ZJ{l8ZMvswK(u7g-K)Hbc1ms`+h`szbn4CA$-?Pqp#A=(j<8pFEID){9l zvLkrO^BtvCaZxYU4R@M_HT8ljf)pE5&rF70&RU0T_75sz2iPFb$!-iNh3Px}QlAR3 zO^)9ZNLEyqi56OWr66Xt(*6DK>xCS45W==nN1^2J)tL=?w(8FJWbRUk&(L=hCdElM z_z54*iTD$>QM__k+N*51HQ&Oep#H4~qebZ(V~S9^t!8@|g-q}U@z2D+ZpR{+a?XXy ztL*VCf!##n+rU3|uH%>Fl=|92{y+wvu$(6dSx&XOXtr3}v9txIzu?u&3>%z-w>U#Y ziyq_SRA$>!1Y24iTUgs$)--sPjGuN(#?w&sDATpM*j^&OZ`nTAG{biGQMkJmY+eDr zUGgiAH51TrEW3H2kk3Uo4$Yr0db$ZY@*vr9VL6?WLV(uAzqo}0$Y1N4Xy-BWRSf}$ z4$Nnl|Ip_s7xeun*p^>3m^jCkRQ6%SzqqB>B5p$hL~!U?n6>c1a+7z`lK|FG8L0X0dVYaK z%dg3X2SHJ}9wy9EHG}uY%C5Th09+4s-1W(Zc(|X>+I5X!&9NbWBS__m`98U_en$U} zOBbZQ?rooo`gEr>T%<};?W>n<8zMwdTGyoq-vUhT-rV&NoO||L3Oc=8dBcv;;HiD% zkLh_3El4DWUge@MmtV1s@>wZyMhEDLrqN)6pWi5ZDf~XIO|Gm>B2+f@nH{D9DlTQZ z`BF}^fA&(E!opjB9M0MvV=WMgwft5PPy&>=XAjIdPQ=UC_=w@MdlaQ(HOx={4n{4X zV*{eXHZdFu_%DSQU``gICKs4oMEZk;C>_^2Z&py23DBP^kCwk(tJrdC>!~|bM*g8P zZ6m^fj@9&#X_4}h9z2oM`McH{Pku=llg)P%uRZV%mp>Xjyj@(#n!*buh?&crRBk}; zSn&E0fzrkHljqspbnF$3_iC6i-@wTy9|A)77SsQNRv2b)jXLod2H0P6o{4fz$$cJC z!B^jFA82dEDN5>N`I3^2Rn*pv>^m`wIhzH zh8NsqTJY~Sv}pLZ5wBf&uw-HT|5HyLP4{bIQ21lhAiZ+>@uw%F7hjQTy(Csw98H|&j~)<2eLX|i1RD5o=M zNGOB&?U$}^K~K$5*>b@pgpKYqtTT=tv1Q=LWsvhOVT!r_|p zVy4mOogbWp&wTo0^ylZ4pLR^HHSBzkm<+t^`t&#g61bw(4;Byv0LphxZM9?unq z4;x}>@L0?co}`?Rd5s@_J<6?4Lqh_+7Yl?{&}9X zymfUmvR~qqRjB-No~N0cOd}Gpy^{80op7wYBJR6eb3K4)=bdi5u`~ZBZqp!hn?I&K zB^WFJiAeYZ-Bb}b&S_`2t2C;zatU(rpa-1eSf;6lz~T~v)AoggJfIAK_h1rC;?nmu zl1~6XEm1eX!qgvg7XwS*)&P30SV)EGeR5kVWQ+_W9!~ zzXc`7-&WFL9x(xaX#}sCA6JULIiC5*+;;Gm?h|JeG9^lwuXY@eO#-A}Y-)#(VpgBS zGw)X8_BgU$B|VhSkKA)UYX+>Vkh!Ol2Yocfip5QP%%7Ea81n4D7H;Gt78-`sSjw}OsqzKPRqD2ZjU09(LBM%xDKgMQr7F(v}wT^Q}mRPY2MZ1U+}ff263PzNABZU@6Y|wN*oFj zoaK}qM20Is`{$SQWA00PU%L@54&=}NqM3xf%Knq@>zC#o4UO1H?@asU)b_J(=y+1+AP%4E ztldzVkjskkD=fxPclrRfngD8rXVIO-=NB4rI`PA2piEP@Xl%=?JoZM%ssJKm+?vP& z7dCS_)t4_~ohfko`%^(aO%mrzvvx&!zZPs~S<){#6EuF9HE+K^7jhMHo%lsR2f#-~ zI>&|PEs>2<2&d*|{&@*In1(9f6Mv}Cg5oe@#h~58cJ$Xdr~IZ-nzDH|Q-sWi9<`U7 z8>2(NfS(XYo|pee0kcni+|W-fU8lQ;yDr5G&tt3uz+R+D%2wvihYT-2kl@E=$K>mSn}UR= zlss3q;Zs{N5O0HvdHyUlhI9if>lZP+5sbSZ`?5`3dc03#9;dg2{}@cJRBwIp6kQdG z=hfZgxUK1)0~!Tyeg6!3Cq6cscwbAjK95(*Q?3AW?Of4_j0p<&Pv9Z})G{VVL+40_ zqAy(EG1b3EgV_hG8j8q<*~&Tx8{9e{e}T!DEji4mvNbMN}FJB01X$&{iF4QMOkw6wZ8GBd_8;UcJe~eY9!vS3Vzpk19*p zLxKR(9HhI=Ua>VFOsce$nu>N@y`B2Z?`~Y>z{myiu+T{G*;afJHO!^Ige^HYsJfxj zMv+ckX@p5ZJ%M4K@&_WV-kCdhRN6wy_hBo;B`P*(xqO)maThVad4^FSj^YYZ3i*)| zhK=$Qy`LAeN>8X9$Kg39vd)5SiLCkit}p=oih$(P?bRj?3#ri?x$!ZzRROF@81!L` zZ`$$)@b`h=5@8XiX`Po$2{^4PIb~tTv#VFb^s)O@zHOr3=r?US=(VF^HBBh$!5?}E z7PeS;8PofogVWK)>9W@-VWCV1d^%Rf*mfZfo!K#BOSd0v(okK@hhY68B1SEcMP@*J zayX<-1?o#usA$EZMShP+@{J!tW89)+7!KkRCCqCgXydC|Vmk3pjSt?FjkVobWd}_o z9IE3ng3+!*x6pUI)v)Q$FBn@9zdx^W{@<_0w-AIgamrUCqipzK3ZkGep%~_7*a25B zC6;#uV`h16OtN)R?r`(>zBitM;s5DB$|#1bVA^U-CK8I9c7MX2vFe{CmKU2Z|2%t5 zQO1F(Bt ztq`jBHysxv6fv;<>XD!6p^FObt9n=hrB~ftNqm{wDE2DG*BEBvNfZ3?W1nb^3sR9JdFy398$R%xXdXKK!B z>)aK$er&w?E)JFD45Y-O!V|!fJRj`$Xuo!Vn8yU@C05Cnqp}EAGjj1a5)`~=1L{VM zdF6aLJiMCFlmD2 zx|zYW?jE?Zz7R|~Tcuq~(u0OWe27#k*w9d?8s;zy*>PTIUNDd9T_RA^!=QoCG>T9} zy`5Y$juEMR6tOcA{u&r8SrzgPgN77-;=}z_P{2YT(T~|+lPOyE2>-#kwkc=sBqQo3 zXLH-y_iRR22_LooE(LQXFe^p5S$nsCegMr*IeKk>BCh*xqZ5O%zAe8%n(A+Dt#9px z&%04(NSBblY=?Z9st!QCE!~Fg7U=Ii?Y0Bj?@EYm+Xos203>h!oci{Cl>uP09aLKM zS9{L*k@;F9XGaQ&gs5k3N%)P-TO%p0stK=@T24-612n8)by}o?u57~p=rug~?5@(e z<>qNHWiCwMoqU)v;Zj~9nsxLbb=crTGDmvlq{|~JnG;>?2w*Tvv@KpUdA13)KA^JW z6|hQ8god@sJpEu_rrc`mS%&j&V5#}KcsIT+gs2A8U3H_4>(=Dsv4E!LTTIA?q{Fa2 zW37tY%?zC|^Yy@c^Y{nk;~;OFh6cM*Y`o^C07ZP`X_u1@42mrxt_ttxO!TwZcq8*$S4CG=5IauImhNy;6qsa z+t#mqEE7~AL|q$y2*QHEDRqOQYRSiO(MGg}`Hy*OM869GW`&r@zQj~EIOvSbs=qW)TsPsRY`)CD$ zzV4ner$QB~k}5}|WYOo|^r2beE`SdzJGFa<(V$g-UwXy_tS2 zuEX}XQ&cre^9ekNgDNg_W}i4CqR)TU4zm;>e>DR0-8BR;^e3M_j?{{-H71+-Qu@G) zp_v^IsL^He4h}RZyy70CV0*o3>#wFi zBQEYR-?OwCyZl?un;K_fLL=HiN*ph)G?7kw9H-gYbNh<)iv0Qq2RO=}^<8uZSz#4|+&HADBF-bDQS!V_!Dhh({@oyI)@sUbow}d-MnJHGPAeM#?fP zAFGpsFXRCu7!avV_@%WweT&|dhxS_R8NHNuga^~gvW(8JY3kj_fCguS2d$qJdG!d1 zkMCdE?Z0Jx^GYD{(c{+X>YD~r)ciT5>xgC<#5O`%;Lu^r**>)`gK9F)wjVBvv-66t zavhDo@NUQ9xQwZ}Mp=PQ_}5w^76hpDZNt2@Hv?ORW7|4kV{_>*SR`g_hQF8dmKgRf z@~QMi+n&&`puOK3<&JvRbqnKZi}eCIKVW+2G+1?ePnSh?CqwjC#w$KdY+l~NChJ3o z3<3jxD`gQVcw1-vMw|-QUt;QM!Ap$|(;zTTI{eP$J!IaDtHuMWOX*KCmMLc03e!WU z7N65=$&qY&R7MC-Mo!6#rEOj082{8v!xVR8-U)-gv!cc6BFThrmK;O_dv)L+R`chq zK@fd>QjjwbXGC0ut?Q7fbODS<1$5ER&wIr44@1k^eUU-Bw@lhw{2i#&=iPownw!Kb z~Ai&+8@XBV7tTB&w|$|1~pJ+PNwMn%NM`4WT$|z*LKUN;nwyTXWHKz61wui z;!{00vV6L8E$@dK%ENmqmtIx2Q$(k0g}>Xnr~0CjJ5}_06RjOQQ0Q2l8t~v4<6beO z2go{T7x%O?oNQi;xYqA?c~eL4HpgQ(!G2Ud@!1D5dDuiNEInECUV~*&&RWHqfkPH> zZ&dR8D!~Jp7uptB=Os{$Pi>cjmN!bMF>saGNRw3EAV`m| zIcpA`_I4Np6cyMSLwsy|r4L_|zFWCC5sE_z&;#7UWN2=YpuruNC`8wzIM{Ui;u%JN z**Qw;CBi^Rg*9fHOJWm**LHopjv;dVISob*jmyB3`3Uo((a4cNsBeyIEk;f4+95)$ zBu7ceyV*{3t$H%*v#IP1WHsu?m;t)Gpqq?wOXdP4TsYnJ0f!*o({AbOg4{mV3c5@ZCY)@s~En6w+^zF)h{vo>ERV6epWw zyvJn4yY08h084c$DJ=xkwti5rHKDJjp8!}S+4|tZn`a~ljgIe@XAICa@+@9;mTAjh zPhzG|Gh~B1V}~X3NyJ(~%ae^{hcDsUpzvgFEydr*U(2Xc`sAl3Dkbx6(PmzK{{*P18c~QYG4Jf7dwt>}K$! z2&=Dal1<;hE$i}1A=Om(h2mgE5#jN7!u#>Jxptq7l`!FAL*x$$t-<5QTVjngBT1bVY6L2zsirAuE!LTwp2sE|^GQN2I1fzjD99 zYg`!aVR*D3DD#QU-=IEAtg4U)t2gBa1=KN9u^xd6X6NK>WeBI`!b9(V{=&iY`WEh% zLG=H4lCw80echLoXc*#O{=RQvuFF?>2>4#O1xvhzHJU0Lzc@CqO`8&OPqXAfRlRS{-Hj?|m$UmS##>L+&~=#@g;&X4u>pG5ryrkJ4h9 zjCY+rA<#TDhZjcz8OT%Ah~IN*8v@lhSi8aC^i{l2?pVr z7!Xv8q-i8YLeZL)xecQvhsS__sT57|PRG)#C#OYD!`gy?ZsMI80aPlp+y3!xn zd#!ReSFeZq56zOV;&{fbHFfnyw9X^$NpzK(%x`RNZ1IF>bm*@hK?IzV!X;s6I$nnl#2&gG{7)^l4(v)=^CZ%MZA^TgR)QU4){1BX+K4$iQMcZeqOrc%3Dc6H zcar4tuv2oRh;_8q&GQ8$SNO}4zU?8r^S$vVxtVCm3t};n0GajaUL~2C4TZad!r-|? z<6d+N`j|?A5<-!U=iGQ^ad16)HZ=+Kl!f1snOXfv68&=pHlyK>R|Ir_eqs2N9kHgb z$M=SwF2eBdk4m7K0Q+hq9B6lW20*fBn;A%#$A#-@i&g8bgmRH8^6dj7Oo z(wO)?`CA?iT+#Q&d18ct6Ab-a8SDT%66)k>Z`n{psSAgnY zxP{r{TuFX1a7Pw;iVRwe`bmrc#=w3o+5GaW+HR3n05%(rXfWVWCaESer1L*7uM!mf3iZ$cpb;|ExMucUdx^Y@?)q@3o=As|r0fXe4|dv$&4O;gl+wTY0W&J)I-`nS#I<^6r5_Y@7;Cz&xorC zBX1+yLVU2)-9JTg54Tqdi57M$|)2DD#n)amA(9^Sm zi~{HUj9*)dG&dM%XFY&Q%7tCWXEUU`cH@Ew*gB}bRLhIdP#2Z0R~?p#hcQP7=Vq`p zi~S{KU{ZcWR3z7U)IU`mI<18G@9XPT+V)ub8xlifs@7<@6|7O)axyexZ;|!#CLs6; zEir?pKbc*XdT`SuR&~-~oj5K-TFP{*Fo6Z<{reZ?;m2C?GNYfbi_=$632r&3=+uoA z@CJ7<@TDU>htk5s%jY!m(HLsK4vNCK5yRAPYiA*TjMogsOuCjH) z^vxd`Av#O%Q+A80a%H|NmYnqZ{9y1$dkpk+A+0_3k91=B$j@Ll8JoVgs0Sy=>VHh|BA>UmAVh2ybuH%XR`$_|1PAsn9 z;$OJ65!pKOqbt8aACV*HbRWgDCUa14oD>olE6RO3^z4jF18?ZEF}Dew*G^GTaw+&~ zsV&YZ(xFDiefZDfLsFry?POoUnubk?@xi0eh9ke_6@>%~#BOAq&|yDJ9YlLX0l)L5 zeNEbr%=KrR9ZMMBAEpx_#a->D6_Cgze+)-VG`9!po+{;u^3l_NASoxq>--vt$Bt5} z73B?{x9LEa%^J&f2%V^7gRVf;46)1+L zb80QxjttGIea>ForR)EpOpK^S3;4HjVBVoDt%0l`)$LLhj-wH**Nb@%;gxb0*Y`t>JeyTeS9;L^&j=U~BZj*?1!VX>=u(l1w7bi1+lPQqYq|2Nr;Qt|^YN zBCuK|NOi!hA(M%BP|@ttU3N8a^ppjy^li{0)Z)yA(}I_3={&B{T46MNat{>wdNB=j zH4x+gnxI@DBx6o0QZ`hnAuED5K}rOq6U@KdfW~xh&;(KGpB7$As#3;MEx{ZGGrv8` zXq&mJF&vp8ilCRaVVXxb5a4u@b{;A5GrsN=RLBhXC(>si1ZwxsS}}^CZ-SC7|D-?i zbwqKZTyQ+wxT|6#WJB>xA4ovjH!_atotA<~}16>X%J%3Ko>y^A`+ zlseZ@DU4%nq;3RbJau$F^owGkER==l%Ao@x>u5nUyNX^mLu z_)`SS+^EI?fH9svvYIx7F?I_g!Cim7wpD4winNwBAQN$p;fl6m*X`g$V+oYOvZgO| z1}_$47I7Rc1^p#5jYKVnsS$o4PczyR4-6Cjbb0+|DeKnmY5GXl#ixSjP0?7j_{= zS+F|&3eha7m6^@?F^(dn*!gaTJ_G}UJT={U+p1)dXGcr{5Q$>Jq^2)9vwMVXaCvcr z#eQ==n95y#82*&!NwKduP~M*ia!d*b*4Voob$rW=5ti6)x zg~25nE7r$fu=#=7hc)0QS_e0*BD00zA!7A4g}@G5Gj=ZYjS3IZk#6oo;Io%7u@AZP z+2b8aq$#_*}aOjsJAu@CtuVm^vH2C)xWv8(X738BAmEJUgZ;b{AT0?7I( zgdrxncKAx>1OVg9W)F3vla$_9)GkBub3qS_+cEECN#RnsfTmFI512pa+qbeiHUZ5n1 z<`oPHI9Oo~X$V*@2WT`i4!I01Fhdvy7sGubs`5lN{!ckxrUe!C1im?9sZ5aYFTVx* z9U(H47a05kPsuzJ56mLEmc%8l5Yk`i-*j<_>MFiWi-~p5vz{jT3m@~NAucU%W+)b) zY%|PvFmJ*KrTq~-n5--0ogRCaZGXI&7<1J}IL^dwm`-@J8BXcHN2LXD0iSmomy^n) zlv5Yp9mK;sTiR)ru1ZB}V$KjWaoW`gr$H~M7zgU`KHhzW3Rnf^9#kUGFDb*Y6Hsi8 zt%i$qYWT(+vMAi>=|C&fRKNM5kKZ2T$T#DSe(SaN7 zp?@j#Lo&$%L_B8De2t?JfJhG0$|7&BVgsUZ+>I(~XcQUZJCTsXA+?DMGb)pR!f7eA z!GSIIfbDcKZ-`>83R)+0q;PwCkFLpmkEqf7) zDZPBZZ&HJ_JD$ydptX{2Q3E8y z3+?0e#mf9|VFOz|(3;7x9_K&`5EJE;5m2-2B0DW3EgXokI6{PksHmvOf4enPofYZf?kOVm?Vp_=IVEgH z3DItC>7wC(W6>I@xqbOF;Dv+7(o_H2%ZX?+DVsZM{%39oMV7?L(~gfSs6Yf0whM&& zlawKYg%rV{17NXvst^!wphqG@AdWb@H{f9`Y>XE5p=~%GZF#I!ocT~nTK^6EwN-kR(NGJbLf&<n807QTN*#_bpRdo!p+P2ZpbDXMX5-^f3GDGK`o@16k`RBuQwiAp zLT#8sybyy&xLpQeRNjORJN?qY;VizjXi$MFmx*V&CTV>0cY|uu0eM$N|AA0<1lsgy z$oYFA1(@1(ss^e0FQ|fs@GTq^ZW{upuGdjTVE+rpSVy#3@&f^}&hn_qyz?a9D17S~#j{YVvWo8ZnN2pu z$F0GC4UsO&pvAf2J>KqZIP7UdrTon z7p%2%_vV5D$TgaIiR_f{j6tyf*5`53EfIgP)`Q`2Y0fXIf_r>tXQGxh85bx}MJyNu zzz3d#HwtK*oMnaNsD=;+280Nid6N_2x%wVP-N?62EkFQCt59Ei*jK8-J; zK!|QFs_%5vc=lkn?K6xlR)sF|gLB-Nn$b~pW|2S5PPg$=-V_7KYT(fF`&A)O#8WAJ z|6c$^4ZHF(SJt0Rl{EIa>l7_F{r> z?3G#_mC->E<<>!OLpqj9H8dygG8l~v8O#9&NG}7@hKymeAcb9_J;T|YpPfDYWwu`0 zj(^-EfPHZ|omEsi6skZb+SLdOwiY4yA^3eKb0^sk(|bBi5e-$a+uQBQc)u40FH9sx zu;Bo>fzS|eG^3W}4&hOxS95$Yk609K)8FLuaOjQ|1PCp-!^J`juse(tNDVX`vOq;k z2&+JrL!Uijc=UQb{osGx{P6T;SOc3&lYgz73t2mjC7!HLZYUAfsmTwis6FAqPZJhG zgA#UoKVOjnYr_*5Zq+aOvNwNRv>m(y{CIB?;qZuaB&oy3|?6>yVu`o&BnOQw)HYP6>Hw`ERpNB%)nac2gjp zZ_HMcIIN3dFMa$IMOd8G6zuVSmlbE0I=4@pq7Ksr^V13{w$F*cHSeVY1|EI$MKaKu zII%eWYu=;b@{?aP-*mY3n+xbt)_4^;H3_SzlSF{&GGsuD9L7eD>SSpKk0?JJeXp-~U6gNn*CaJQ3`el$Jq?_p?__IY%4j^=7wb7zB$Np|(0?b@%WE@3 zj?Km}oLhOtv0p{m21Obm9FH|0#9?aQ!=6Rm-Cfahtk#|3XDzbAVB=m;St4@OusIm) zdC;qK|DD<$8Mu&e#^Py5lU_r>7CD7nI9&TqQXke8PUU&oLGhZilh}60xUDks56NxJ zpO?7vUz~{d;K}Rd;Zf9;pnuJl8nj)*#y+S7`VL+T55=zfCezhG6?w=-;#V|2U&DT8 zH3ZO=0a*)=9;%q{hStg?*Wz|oegAZ{z7}12(!H55jrC#aqLxlP4n3mbjrAssLQnt# z1cY!FV3oACmdYP%^KCx-p=he^AciBd30=@PbK%0YP)ZlJy$ljzjDK6^=Pf99jhZ~v zND8t?R~D+_(-Uf3P#2v4ZP2zFsBbM2zGrDF_Mn7`yXb((^Kjp9Mxr2xc?5%!&{8@X<>_d6JZErcU8dUgfry;WtSF!)Uv$Z*u;L8l*^W5W zroP}$>PJUwX#d2Y?e!nAEShIJD);a48hBECT zwLAWOPZIUqh?zdpTgKB+hFL~-T_||@OdDxWom(W zWXB1GqkqU4G2=gO$t9CJQ9#As=*?o6W<|8_C^r8VE(Tub^oKz*H@TO=tif$i#w074 zg9ZvOT-Z$oPIgcZf7g1@vRvPWP*b3)8M;oEAzUp=LOzHEQ=(gPppb)dLQ};!Vm`H> z{MlTsD+Mwq3H|dgG?Awpj+^%Pl(vXKj7=BLT zJ|nTjA)tPTRaO}^)MHX4L!1MSkCBssT3&5av+S`LbS2RI@hI(njjBM9lIK}<$ys4Ag( ze1oPa_eKyDL{4o&rC2m`|AJb4r<{8Vm4AXuXJc{__u{!|FwJFah{`Zdd)?Ta=T-v= zy5f=mLrzfDQ08$ks5)}s1|A2(O+g5404QPm(~K6wGk*t(mB(bS4Hsa9au#?O1XbhH zF~)9qg68uh^@#4L#!qpuKiaBxUoc1N{K-|Yb9tEZYvMN_WkA>GK=#iQlqDiCG=C1B zRVJ6NSU2i(RA@jKiy5lnMx>g07pnpfr|p9JAt&3Oprjz5oDNMjVPxlj6%sgvBKIvp z1aR{_bpn5=gU?VCQ}Fci#Y~4p6$Mt(3(JU3EEe?PaFn>VKBLpC18~DS%L%3Sf=*qCn;>D%38Ygiv|@TSnk*i6_;G?MzcauNgmT{L22a~djYISm*jIEar$@Pz_4^e!IINIyPWHrw;)Hqxq9T-iIg z96=TT-%j!KQ-`j?xCTMceUdCk(9_znF}KKfe!k>k-;;$PNS8b2s!G((qvbW$HKq&0*0b*ncN9MUkP-xo6vQwLwNI;D?~6ZMRgBudbX(e8x0gF??Y$ zkd4rf5m*qoU;@a=F5fW})OT{w!JPZUI4~l!n3&rF<>F)?OxpV}|3FH8ps^1&?86%S zQ1d>}xDRgZ0}cCdV;^eXhc@m5jeW3TAJ*81T8OZn8hA-@>#)941Ahw9JugrP755kv?P z5CK7gA|L`{2oWHHOo;>%h`KXpzi~GAFjO*^A8IX zUXvgpr0&xL=<`wa2cJ;);kggWeGvBnxevpAVD1BVAGZ6T?tcSxAD;W5?gMlmuDK6A zQ499r6wB1-Eo-w}Qgs%NcXMZ}M*liH6h-1sHOn$g23Cm(0YzWAD^wt_PBkPG)4r#G zZaXM^KkoY`YJ~53iR$wVFAi0BrB50%q0$S(?fyeyLN6s+DSp`tDHE**an(%4fQI;S zu~E@_mX(6sQ-2`&$pd@n7fI`eZ3$a}jBpt=A`KSJ z%}qlvL|6%ox${9Xb#VJ=%5EcPDosyrH<;cGWFjZ*CjuA3#@tYKCatTE90@txCn>8m zqBa98u2_)aBL0D{Kq5#!+Nr)rnk5cp)^C?9vJ0oxJ%8f_I?Y4nr>b_`BR4Vyoj|j8 zEL{7H;Z+ov_>3HNF{}BfBtAh#b2AeG2rb zM}Gogb$;U{%$$B8gS@}O?&eS{Q8=21+v;BjtJe?*=4_lr^s6sRy4XY@D_Nsa7fghP zN?5_EEq{6W03Cbucd{j>biap-P*7#;M;R0ET4Mj-_sPyqn)li^uF a1@A*Yq6)L|Mj`?Nk(+}RNw*bA0ni6(ECvDq diff --git a/src/Nethermind/Chains/lisk-mainnet.json.zst b/src/Nethermind/Chains/lisk-mainnet.json.zst index 6c902a374cad8da5e88f7668c8ffe7eb5f63347b..598598dfa3be25257d7e54795fd950f1a5c9a438 100644 GIT binary patch delta 7892 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 k9B59&J(umC2hM?ne4NavbAr*h$R@&Et; delta 9016 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 diff --git a/src/Nethermind/Chains/lisk-sepolia.json.zst b/src/Nethermind/Chains/lisk-sepolia.json.zst index 2d100695c7669bf34681e021a45a5e64e49aaecc..88a36f768fde1fcb8d452e85e63a42a190b83339 100644 GIT binary patch delta 10587 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 delta 12122 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 delta 10041 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 b{J&WS#7`?JDN`#V6HJ(61}ixvWS##7jl%G- diff --git a/src/Nethermind/Chains/metal-mainnet.json.zst b/src/Nethermind/Chains/metal-mainnet.json.zst index f422775e3f91beee582a1980673426c679cf9476..c0d9dc63466d8d721e5042a7db1db42a9080988c 100644 GIT binary patch delta 8126 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 F{{TjSakKyc delta 9205 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 diff --git a/src/Nethermind/Chains/mint-mainnet.json.zst b/src/Nethermind/Chains/mint-mainnet.json.zst index 2ca717cfc1fed7f01dadb7cce94f58b7322083fa..827516e59bd92cb38361a35d7ec6a0a04ec3e136 100644 GIT binary patch delta 7904 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 delta 9077 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 diff --git a/src/Nethermind/Chains/mode-sepolia.json.zst b/src/Nethermind/Chains/mode-sepolia.json.zst index ed6795c8ca250f9b375dcb49594a360fc21d7cf0..2b237e4d741e1fd31a00845c0e309cb2f085ddb9 100644 GIT binary patch delta 12816 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 JUxX8E{|_DWw@d&4 diff --git a/src/Nethermind/Chains/op-mainnet.json.zst b/src/Nethermind/Chains/op-mainnet.json.zst index 470120f31209865ae7501d5748b6d2e1d1e2f3fd..6664eeb91e507bdd202c628aae7f2db9b2cdf8f8 100644 GIT binary patch literal 1587 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 diff --git a/src/Nethermind/Chains/orderly-mainnet.json.zst b/src/Nethermind/Chains/orderly-mainnet.json.zst index 833bbf0818ac9f63317b5b17133dfe634d9961be..88a56c6cc9ab0ef9cc5553b2d9cb8ed4e41a8b0d 100644 GIT binary patch delta 9370 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 diff --git a/src/Nethermind/Chains/pivotal-sepolia.json.zst b/src/Nethermind/Chains/pivotal-sepolia.json.zst index 45fd5a197893ba6215d3b8eacdc3cf641fc48df4..7b0a4d2ee1f8400643e4d8ac2717d500e1bbf5af 100644 GIT binary patch delta 10775 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 diff --git a/src/Nethermind/Chains/polynomial-mainnet.json.zst b/src/Nethermind/Chains/polynomial-mainnet.json.zst index a5e9f89e056d67d092da5704417e2aae92d9d1ca..c6777472a00c6e2efdbf035cf51a5fdfd05ed86b 100644 GIT binary patch delta 7795 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 delta 9106 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 CCM1>s diff --git a/src/Nethermind/Chains/race-mainnet.json.zst b/src/Nethermind/Chains/race-mainnet.json.zst index c4259e7c81976d440d71530c1827e414a0986947..c9d5738c277eee9adbfad8b6b44ee7113ec130cc 100644 GIT binary patch delta 7218 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 M&P2l%v%%&60YMkqqW}N^ delta 8654 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 WZ)Kp12=`!{+QdoMQcT5q`~LtBzm8A< diff --git a/src/Nethermind/Chains/race-sepolia.json.zst b/src/Nethermind/Chains/race-sepolia.json.zst index f2bef6a125e741ab5e3e7925d1d7b3398c633dec..be45c6ea1ddd541e7f0efd523a7be2fd12afb561 100644 GIT binary patch delta 7303 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 Ge*G6w$ws#T diff --git a/src/Nethermind/Chains/redstone-mainnet.json.zst b/src/Nethermind/Chains/redstone-mainnet.json.zst index a5d41913ef9f753995e13fe7dc1009d24bdeed56..21bc7200f92c22121f884c53927e7aa97463c8b8 100644 GIT binary patch delta 7828 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 delta 8978 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%0D?GPBoCAdyjgAl1TCQs0=lC>6x`n!76mT@44te% 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

d56L;__B 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 not)CtltGcMF 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

MT$BV}uh5}%_bWYXD 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 IGnn4~5Boy15C8xG delta 8732 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 X{e7EtD{A{LBOf~IQ9BW9#`%8$VxwAa delta 8420 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 Z36{(qH5dsMQr? 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 diff --git a/src/Nethermind/Chains/swell-mainnet.json.zst b/src/Nethermind/Chains/swell-mainnet.json.zst index d928fac7e89699d57d9da3f918547cd95a2927c1..b0e270c4f8aaf6993756f4bd341b90d974bddffd 100644 GIT binary patch delta 14775 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 X++<`ihNf3`Ul^DHlO}Jj;QRjrr#d?@ delta 12349 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

9kU5modW@Pj@-hW_nHMh} z`i6i#N`rM}{D!3tqGgOi51$Wn^}*#O@?37F8W3&<3D3rPKFU<`1cq1kIkj6gD^I2W zOF2l5j?%4kX?P&vrT_(+y7L){cetpsGnpWMB%vAciB=Q8M)j_`Vh5736nIO%WZzT9K@N0mXrwL$Ow6HpaR%ZP}~f==#FqA5h}t zn*}SeuW~w?s6QK+g(|_N!{S(g(>}$3M<7wLe{zMktS#%0TJf*TK)VReMQaXPC_&El z!{{>4&H@D-!i>I$_;bj?O0<#{n3Mnse9k~J>p`bIa!h4jbEQQ&~!i1feBqHD9^DO+Ukn_yOo$VT%!#V7WrlGBz>*9-Ey$$af59y1&XR?22>EgL z+33LJ;D%3hJMjLto*i$=3)n9EBUCOQSIuQErVXCKUDrN2b2-BLHIcBm0Bwg0u=>q( ziv|;TmtstkRpc7|oshA{1w%9`i3Sit3H+nngbYSE_0WF_sV@blxu5+RYgzHvS~&FI z&_Sou0=G&VAN01dG$%vI77|5nRyPqMa_*R!!^Z7kRmsA+zY*3{1jV>S)2dbMXhfr6TUj(?BU9rxM=Cqpo&_vrJpm??jZN**&*k6m_5uob8}H|H|3W~S+YJ^ z$8h!)p3a~dj5^=*uXsp0Rc1gjNPxYWNwfHcug~;+vH>_U8;l=jzLKwXkVDt_S%j~a|#c^t^)`qTT@B|8+tTh zFhPBU{+nYs&MD5ZUK!16Smrwynect;YvH_Zb*-|{LzSF0ea15S;htfRtr(c**%mZ@ z@J&V!<*Kk(I*y3aI>l7ZXE35Gv!hoF~n8{aO^Da1s#>i7lk{8;@|K zZqbx!VqQOiy#Ta0m2F@-HVi7iJTmxaA7A8G6hRba*e*O#2456(xDY}_aA6lu^h#T@Y>t_bM7I5<2AmZ;%U&=4YmGLF5Eq$JTLk`K#?c-sDv zUkAz~A16YQA{?II6n|WBr}BqP^Y#@$wc|UOokMU)E(jrP`s_EldkGN79eKb?|HApq zf`lfRrqXfFra4l{$tgV3`5r$A>Ajo%@I zy3d6qU+*tyOuj+4V(wUAvB3*Nf*#p9boKNN42_r>nV4C=v$FlqH_N{R5mD7a2R_}c zA>s2%V8)r>sUhLv+uK>stp(ve;e8I$+nLbBeRiVfp*5jfJyP3g&-;D0@yp~0Z!AtN zBKcteHu`L*pfK?KVFWz<2Lpy@B~bf5yTkKPt+|67G7l2O63bXaEXBh5oe(4m%{vb> zsqYEm`FQN`dS{$Ld=%mbUO*q%S&5#Yw~vG+UT^Jtu-gWtEaQ*#C78*L|H{aJPIMoe zT0;q63JD?uH)3UFWn*QJ1x9cILXheIE%*O#Scbn@?*E?*8${|T|=y;#sh@?fB#<5=BB&RFlpPcU3X$`GJO=Gcx_Qm~R(#!1fDs0J9= zjdPfPl9^&@+el+Q>#9G+L4sz%IgE^qV)^T_V&&>lV=d}oV>|nCAV+v_onwEs6Or~1 zoPpii&5DKX68|EA@m>AN3R5#tICvf7J8|w&MZm z-!)zJ6Vy00Jb~lCLV#GnjhGnz9U1?xAvUe~YbyB%~WMM(dd9wGmR z;`m?I_y5BhqT>LmhOh$<;y+|r82%mq3;3VcSd;GmC5!d{NY;zBW}^H3KOEWq;kem{ z6?@SKi^A|Ot^{@6a{6iA*pRSF~ZsLc!5CSSb*L)nu{trV&R)&Aa|3dv2 zLzdyMu?!6;|A3?ZGm?aErT^2JP&BMHsl)H!Q2*+{*o-kUr2i`Y52x6_wV1H~S$Y?- xTVr&n3jbb!LI4I0>%Zn6(+4jC80LQ_9pV2@x(^ZEf5s^Gs-LiCZQTCv{{dRMZzcc$ delta 47216 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 T-lG1mynLpGxS4Lu{p0@w6ts!! diff --git a/src/Nethermind/Chains/unichain-sepolia.json.zst b/src/Nethermind/Chains/unichain-sepolia.json.zst index 7ff9d5fd16e38d1a801fdc82853ba7a348b72783..af355eb5bbac9c2d27146b4bad42e0fef556f028 100644 GIT binary patch delta 27974 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 B5X}Gp delta 29514 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 a{C|1u|2yHwn|SLs2M-K1cPlmoIRAfxMZ0?d delta 12742 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 E2P)(DjQ{`u diff --git a/src/Nethermind/Chains/worldchain-sepolia.json.zst b/src/Nethermind/Chains/worldchain-sepolia.json.zst index 317fe21409075385c7ee07da31cbf48c7624f6a2..830f302c5d8828376eb952a2c4899a2e9d5d8b57 100644 GIT binary patch delta 11583 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%0DZt%Bp4 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 diff --git a/src/Nethermind/Chains/xterio-eth-mainnet.json.zst b/src/Nethermind/Chains/xterio-eth-mainnet.json.zst index cf5e06d4a5f7e0b7c87f8376dd2b05bd220f2891..6e8bb10444c038e2acd5a2316be3b6780210eed8 100644 GIT binary patch delta 11474 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 B;uHV? delta 12571 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 SlmC(k4zxT2KBtp~JN*x5p)Jz@ delta 13925 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 diff --git a/src/Nethermind/Chains/zora-sepolia.json.zst b/src/Nethermind/Chains/zora-sepolia.json.zst index 06ec4c0b54493c07a12b36a06c10f4587b95f15d..763641aa82e85fa24a249bfe9205b65f519e757d 100644 GIT binary patch delta 8944 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 Xzd3dJ3s3)l8_iNl-G{(l7ythN!2(As diff --git a/src/Nethermind/Nethermind.Api/IBasicApi.cs b/src/Nethermind/Nethermind.Api/IBasicApi.cs index 29ce719bcb52..577294912bc5 100644 --- a/src/Nethermind/Nethermind.Api/IBasicApi.cs +++ b/src/Nethermind/Nethermind.Api/IBasicApi.cs @@ -32,7 +32,7 @@ public interface IBasicApi IDbProvider DbProvider { get; } IEthereumEcdsa EthereumEcdsa { get; } [SkipServiceCollection] - EthereumJsonSerializer EthereumJsonSerializer { get; } + IJsonSerializer EthereumJsonSerializer { get; } IFileSystem FileSystem { get; } IKeyStore? KeyStore { get; set; } [SkipServiceCollection] diff --git a/src/Nethermind/Nethermind.Api/IInitConfig.cs b/src/Nethermind/Nethermind.Api/IInitConfig.cs index 525c066d1886..63ad69d84108 100644 --- a/src/Nethermind/Nethermind.Api/IInitConfig.cs +++ b/src/Nethermind/Nethermind.Api/IInitConfig.cs @@ -93,7 +93,7 @@ public interface IInitConfig : IConfig [ConfigItem(Description = "[TECHNICAL] Specify concurrency limit for background task.", DefaultValue = "2", HiddenFromDocs = true)] int BackgroundTaskConcurrency { get; set; } - [ConfigItem(Description = "[TECHNICAL] Specify max number of background task.", DefaultValue = "2048", HiddenFromDocs = true)] + [ConfigItem(Description = "[TECHNICAL] Specify max number of background task.", DefaultValue = "1024", HiddenFromDocs = true)] int BackgroundTaskMaxNumber { get; set; } [ConfigItem(Description = "[TECHNICAL] True when in runner test. Disable some wait.", DefaultValue = "false", HiddenFromDocs = true)] diff --git a/src/Nethermind/Nethermind.Api/InitConfig.cs b/src/Nethermind/Nethermind.Api/InitConfig.cs index 914849cc7be8..4c7e4dc0d5e4 100644 --- a/src/Nethermind/Nethermind.Api/InitConfig.cs +++ b/src/Nethermind/Nethermind.Api/InitConfig.cs @@ -38,7 +38,7 @@ public class InitConfig : IInitConfig public long? ExitOnBlockNumber { get; set; } = null; public bool ExitOnInvalidBlock { get; set; } = false; public int BackgroundTaskConcurrency { get; set; } = 2; - public int BackgroundTaskMaxNumber { get; set; } = 2048; + public int BackgroundTaskMaxNumber { get; set; } = 1024; public bool InRunnerTest { get; set; } = false; public string? DataDir { get; set; } diff --git a/src/Nethermind/Nethermind.Api/NethermindApi.cs b/src/Nethermind/Nethermind.Api/NethermindApi.cs index 7dbae388e628..9fa69a8e2cdb 100644 --- a/src/Nethermind/Nethermind.Api/NethermindApi.cs +++ b/src/Nethermind/Nethermind.Api/NethermindApi.cs @@ -45,7 +45,7 @@ public class NethermindApi(NethermindApi.Dependencies dependencies) : INethermin // A simple class to prevent having to modify subclass of NethermindApi many times public record Dependencies( IConfigProvider ConfigProvider, - EthereumJsonSerializer JsonSerializer, + IJsonSerializer JsonSerializer, ILogManager LogManager, ChainSpec ChainSpec, ISpecProvider SpecProvider, @@ -78,7 +78,7 @@ ILifetimeScope Context new BuildBlocksWhenRequested(); public IIPResolver IpResolver => Context.Resolve(); - public EthereumJsonSerializer EthereumJsonSerializer => _dependencies.JsonSerializer; + public IJsonSerializer EthereumJsonSerializer => _dependencies.JsonSerializer; public IKeyStore? KeyStore { get; set; } public ILogManager LogManager => _dependencies.LogManager; public IMessageSerializationService MessageSerializationService => Context.Resolve(); diff --git a/src/Nethermind/Nethermind.Benchmark/Core/FastHashBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Core/FastHashBenchmarks.cs deleted file mode 100644 index a6481332d087..000000000000 --- a/src/Nethermind/Nethermind.Benchmark/Core/FastHashBenchmarks.cs +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Runtime.InteropServices; -using BenchmarkDotNet.Attributes; -using Nethermind.Core.Extensions; - -namespace Nethermind.Benchmarks.Core; - -[ShortRunJob] -[DisassemblyDiagnoser] -[MemoryDiagnoser] -public class FastHashBenchmarks -{ - private byte[] _data = null!; - - [Params(16, 20, 32, 64, 128, 256, 512, 1024)] - public int Size; - - [GlobalSetup] - public void Setup() - { - _data = new byte[Size]; - Random.Shared.NextBytes(_data); - } - - [Benchmark(Baseline = true)] - public int FastHash() - { - return ((ReadOnlySpan)_data).FastHash(); - } - - [Benchmark] - public int FastHashAes() - { - ref byte start = ref MemoryMarshal.GetReference(_data); - return SpanExtensions.FastHashAesX64(ref start, _data.Length, SpanExtensions.ComputeSeed(_data.Length)); - } - - [Benchmark] - public int FastHashCrc() - { - ref byte start = ref MemoryMarshal.GetReference(_data); - return SpanExtensions.FastHashCrc(ref start, _data.Length, SpanExtensions.ComputeSeed(_data.Length)); - } -} diff --git a/src/Nethermind/Nethermind.Benchmark/Core/SeqlockCacheBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Core/SeqlockCacheBenchmarks.cs deleted file mode 100644 index 9ba71e47e39e..000000000000 --- a/src/Nethermind/Nethermind.Benchmark/Core/SeqlockCacheBenchmarks.cs +++ /dev/null @@ -1,345 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -#nullable enable - -using System; -using System.Collections.Concurrent; -using BenchmarkDotNet.Attributes; -using Nethermind.Core; -using Nethermind.Core.Collections; -using Nethermind.Int256; - -namespace Nethermind.Benchmarks.Core; - -[MemoryDiagnoser] -[DisassemblyDiagnoser(maxDepth: 3)] -public class SeqlockCacheBenchmarks -{ - private SeqlockCache _seqlockCache = null!; - private ConcurrentDictionary _concurrentDict = null!; - - private StorageCell[] _keys = null!; - private byte[][] _values = null!; - private StorageCell _missKey; - - [Params(1000)] - public int KeyCount { get; set; } - - [GlobalSetup] - public void Setup() - { - _seqlockCache = new SeqlockCache(); - _concurrentDict = new ConcurrentDictionary(); - - _keys = new StorageCell[KeyCount]; - _values = new byte[KeyCount][]; - - var random = new Random(42); - for (int i = 0; i < KeyCount; i++) - { - var addressBytes = new byte[20]; - random.NextBytes(addressBytes); - var address = new Address(addressBytes); - var index = new UInt256((ulong)i); - - _keys[i] = new StorageCell(address, index); - _values[i] = new byte[32]; - random.NextBytes(_values[i]); - - // Pre-populate both caches - _seqlockCache.Set(in _keys[i], _values[i]); - _concurrentDict[_keys[i]] = _values[i]; - } - - // Create a key that won't be in the cache - var missAddressBytes = new byte[20]; - random.NextBytes(missAddressBytes); - _missKey = new StorageCell(new Address(missAddressBytes), UInt256.MaxValue); - } - - // ==================== TryGetValue (Hit) ==================== - - [Benchmark(Baseline = true)] - public bool SeqlockCache_TryGetValue_Hit() - { - return _seqlockCache.TryGetValue(in _keys[500], out _); - } - - [Benchmark] - public bool ConcurrentDict_TryGetValue_Hit() - { - return _concurrentDict.TryGetValue(_keys[500], out _); - } - - // ==================== TryGetValue (Miss) ==================== - - [Benchmark] - public bool SeqlockCache_TryGetValue_Miss() - { - return _seqlockCache.TryGetValue(in _missKey, out _); - } - - [Benchmark] - public bool ConcurrentDict_TryGetValue_Miss() - { - return _concurrentDict.TryGetValue(_missKey, out _); - } - - // ==================== Set (Existing Key) ==================== - - [Benchmark] - public void SeqlockCache_Set_Existing() - { - _seqlockCache.Set(in _keys[500], _values[500]); - } - - [Benchmark] - public void ConcurrentDict_Set_Existing() - { - _concurrentDict[_keys[500]] = _values[500]; - } - - // ==================== GetOrAdd (Hit) ==================== - - [Benchmark] - public byte[]? SeqlockCache_GetOrAdd_Hit() - { - return _seqlockCache.GetOrAdd(in _keys[500], static (in StorageCell _) => new byte[32]); - } - - [Benchmark] - public byte[] ConcurrentDict_GetOrAdd_Hit() - { - return _concurrentDict.GetOrAdd(_keys[500], static _ => new byte[32]); - } - - // ==================== GetOrAdd (Miss - measures factory overhead) ==================== - - private int _missCounter; - - [Benchmark] - public byte[]? SeqlockCache_GetOrAdd_Miss() - { - // Use incrementing key to always miss - var key = new StorageCell(_keys[0].Address, new UInt256((ulong)(KeyCount + _missCounter++))); - return _seqlockCache.GetOrAdd(in key, static (in StorageCell _) => new byte[32]); - } - - [Benchmark] - public byte[] ConcurrentDict_GetOrAdd_Miss() - { - var key = new StorageCell(_keys[0].Address, new UInt256((ulong)(KeyCount + _missCounter++))); - return _concurrentDict.GetOrAdd(key, static _ => new byte[32]); - } -} - -///

-/// Benchmark comparing read-heavy workloads (90% reads, 10% writes) -/// -[MemoryDiagnoser] -public class SeqlockCacheMixedWorkloadBenchmarks -{ - private SeqlockCache _seqlockCache = null!; - private ConcurrentDictionary _concurrentDict = null!; - - private StorageCell[] _keys = null!; - private byte[][] _values = null!; - - private const int KeyCount = 10000; - private const int OperationsPerInvoke = 1000; - - [GlobalSetup] - public void Setup() - { - _seqlockCache = new SeqlockCache(); - _concurrentDict = new ConcurrentDictionary(); - - _keys = new StorageCell[KeyCount]; - _values = new byte[KeyCount][]; - - var random = new Random(42); - for (int i = 0; i < KeyCount; i++) - { - var addressBytes = new byte[20]; - random.NextBytes(addressBytes); - var address = new Address(addressBytes); - var index = new UInt256((ulong)i); - - _keys[i] = new StorageCell(address, index); - _values[i] = new byte[32]; - random.NextBytes(_values[i]); - - // Pre-populate both caches - _seqlockCache.Set(in _keys[i], _values[i]); - _concurrentDict[_keys[i]] = _values[i]; - } - } - - [Benchmark(Baseline = true, OperationsPerInvoke = OperationsPerInvoke)] - public int SeqlockCache_MixedWorkload_90Read_10Write() - { - int hits = 0; - for (int i = 0; i < OperationsPerInvoke; i++) - { - int keyIndex = i % KeyCount; - if (i % 10 == 0) - { - // 10% writes - _seqlockCache.Set(in _keys[keyIndex], _values[keyIndex]); - } - else - { - // 90% reads - if (_seqlockCache.TryGetValue(in _keys[keyIndex], out _)) - hits++; - } - } - return hits; - } - - [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] - public int ConcurrentDict_MixedWorkload_90Read_10Write() - { - int hits = 0; - for (int i = 0; i < OperationsPerInvoke; i++) - { - int keyIndex = i % KeyCount; - if (i % 10 == 0) - { - // 10% writes - _concurrentDict[_keys[keyIndex]] = _values[keyIndex]; - } - else - { - // 90% reads - if (_concurrentDict.TryGetValue(_keys[keyIndex], out _)) - hits++; - } - } - return hits; - } - - [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] - public int SeqlockCache_ReadOnly() - { - int hits = 0; - for (int i = 0; i < OperationsPerInvoke; i++) - { - int keyIndex = i % KeyCount; - if (_seqlockCache.TryGetValue(in _keys[keyIndex], out _)) - hits++; - } - return hits; - } - - [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] - public int ConcurrentDict_ReadOnly() - { - int hits = 0; - for (int i = 0; i < OperationsPerInvoke; i++) - { - int keyIndex = i % KeyCount; - if (_concurrentDict.TryGetValue(_keys[keyIndex], out _)) - hits++; - } - return hits; - } -} - -/// -/// Benchmark measuring effective hit rate after populating with N keys. -/// This directly measures the impact of collision rate. -/// -public class SeqlockCacheHitRateBenchmarks -{ - private SeqlockCache _seqlockCache = null!; - private StorageCell[] _keys = null!; - private byte[][] _values = null!; - - [Params(1000, 5000, 10000, 20000)] - public int KeyCount { get; set; } - - [GlobalSetup] - public void Setup() - { - _seqlockCache = new SeqlockCache(); - _keys = new StorageCell[KeyCount]; - _values = new byte[KeyCount][]; - - var random = new Random(42); - for (int i = 0; i < KeyCount; i++) - { - var addressBytes = new byte[20]; - random.NextBytes(addressBytes); - _keys[i] = new StorageCell(new Address(addressBytes), new UInt256((ulong)i)); - _values[i] = new byte[32]; - random.NextBytes(_values[i]); - _seqlockCache.Set(in _keys[i], _values[i]); - } - } - - [Benchmark] - public double MeasureHitRate() - { - int hits = 0; - for (int i = 0; i < KeyCount; i++) - { - if (_seqlockCache.TryGetValue(in _keys[i], out byte[]? val) && ReferenceEquals(val, _values[i])) - hits++; - } - return (double)hits / KeyCount * 100; - } -} - -[MemoryDiagnoser] -public class SeqlockCacheCallSiteBenchmarks -{ - private SeqlockCache _cache = null!; - private SeqlockCache.ValueFactory _cachedFactory = null!; - private StorageCell _key; - private byte[] _value = null!; - - [GlobalSetup] - public void Setup() - { - _cache = new SeqlockCache(); - - byte[] addressBytes = new byte[20]; - new Random(123).NextBytes(addressBytes); - _key = new StorageCell(new Address(addressBytes), UInt256.One); - _value = new byte[32]; - - _cache.Set(in _key, _value); - _cachedFactory = LoadFromBackingStore; - } - - [Benchmark(Baseline = true)] - public byte[]? GetOrAdd_Hit_PerCallMethodGroup() - { - return _cache.GetOrAdd(in _key, LoadFromBackingStore); - } - - [Benchmark] - public byte[]? GetOrAdd_Hit_CachedDelegate() - { - return _cache.GetOrAdd(in _key, _cachedFactory); - } - - [Benchmark] - public bool TryGetValue_WithIn() - { - return _cache.TryGetValue(in _key, out _); - } - - [Benchmark] - public bool TryGetValue_WithoutIn() - { - return _cache.TryGetValue(_key, out _); - } - - private byte[] LoadFromBackingStore(in StorageCell _) - { - return _value; - } -} diff --git a/src/Nethermind/Nethermind.Benchmark/Scheduler/BackgroundTaskSchedulerBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Scheduler/BackgroundTaskSchedulerBenchmarks.cs deleted file mode 100644 index 5ab4d42ecf03..000000000000 --- a/src/Nethermind/Nethermind.Benchmark/Scheduler/BackgroundTaskSchedulerBenchmarks.cs +++ /dev/null @@ -1,181 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -#nullable enable - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using BenchmarkDotNet.Attributes; -using Nethermind.Consensus.Processing; -using Nethermind.Consensus.Scheduler; -using Nethermind.Core; -using Nethermind.Core.Specs; -using Nethermind.Evm.State; -using Nethermind.Evm.Tracing; -using Nethermind.Int256; -using Nethermind.Logging; -using Nethermind.TxPool; - -namespace Nethermind.Benchmarks.Scheduler; - -/// -/// Benchmarks the throughput of the BackgroundTaskScheduler under concurrent task -/// scheduling with periodic block-processing pauses — the scenario that caused -/// the "Background task queue is full" issue on synced nodes. -/// -[MemoryDiagnoser] -[SimpleJob(warmupCount: 2, iterationCount: 5)] -public class BackgroundTaskSchedulerBenchmarks -{ - private StubBranchProcessor _branchProcessor = null!; - private StubChainHeadInfoProvider _chainHeadInfo = null!; - - [Params(1024, 2048)] - public int Capacity { get; set; } - - [Params(2)] - public int Concurrency { get; set; } - - [Params(50)] - public int BlockProcessingDurationMs { get; set; } - - [Params(5)] - public int BlockProcessingCycles { get; set; } - - [GlobalSetup] - public void Setup() - { - _branchProcessor = new StubBranchProcessor(); - _chainHeadInfo = new StubChainHeadInfoProvider(); - } - - /// - /// Simulates the real-world scenario: a background producer keeps scheduling tasks - /// while block-processing cycles pause and resume execution. Measures total wall-clock - /// time for scheduling + draining all tasks across several block-processing windows. - /// - [Benchmark] - public async Task ScheduleAndDrainDuringBlockProcessing() - { - await using BackgroundTaskScheduler scheduler = new( - _branchProcessor, _chainHeadInfo, Concurrency, Capacity, LimboLogs.Instance); - - int totalScheduled = 0; - int totalExecuted = 0; - int totalDropped = 0; - - for (int cycle = 0; cycle < BlockProcessingCycles; cycle++) - { - // Simulate block arriving — cancels current tasks, pauses non-expired ones - _branchProcessor.RaiseBlocksProcessing(); - - // Schedule a burst of tasks while block is being processed - int batchSize = Capacity / 2; - for (int i = 0; i < batchSize; i++) - { - bool accepted = scheduler.TryScheduleTask(i, (_, token) => - { - Interlocked.Increment(ref totalExecuted); - return Task.CompletedTask; - }, TimeSpan.FromMilliseconds(BlockProcessingDurationMs + 100)); - - if (accepted) - Interlocked.Increment(ref totalScheduled); - else - Interlocked.Increment(ref totalDropped); - } - - // Simulate block processing time - await Task.Delay(BlockProcessingDurationMs); - - // Block done — resume normal task execution - _branchProcessor.RaiseBlockProcessed(); - - // Wait for all scheduled tasks to drain before next cycle - SpinWait spin = default; - while (Volatile.Read(ref totalExecuted) < Volatile.Read(ref totalScheduled)) - { - spin.SpinOnce(); - if (spin.Count % 100 == 0) - await Task.Yield(); - } - } - } - - /// - /// Measures pure scheduling throughput without block-processing interruptions. - /// Useful as a baseline to compare against . - /// - [Benchmark(Baseline = true)] - public async Task ScheduleAndDrainWithoutBlockProcessing() - { - await using BackgroundTaskScheduler scheduler = new( - _branchProcessor, _chainHeadInfo, Concurrency, Capacity, LimboLogs.Instance); - - int totalScheduled = 0; - int totalExecuted = 0; - - int totalTasks = (Capacity / 2) * BlockProcessingCycles; - for (int i = 0; i < totalTasks; i++) - { - bool accepted = scheduler.TryScheduleTask(i, (_, _) => - { - Interlocked.Increment(ref totalExecuted); - return Task.CompletedTask; - }); - if (accepted) - Interlocked.Increment(ref totalScheduled); - } - - SpinWait spin = default; - while (Volatile.Read(ref totalExecuted) < Volatile.Read(ref totalScheduled)) - { - spin.SpinOnce(); - if (spin.Count % 100 == 0) - await Task.Yield(); - } - } - - /// - /// Minimal stub for to expose events without any real block processing. - /// - private sealed class StubBranchProcessor : IBranchProcessor - { - public event EventHandler? BlockProcessed; - public event EventHandler? BlocksProcessing; -#pragma warning disable CS0067 // Event is never used - public event EventHandler? BlockProcessing; -#pragma warning restore CS0067 - - public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlocks, - ProcessingOptions processingOptions, IBlockTracer blockTracer, CancellationToken token = default) - => []; - - public void RaiseBlocksProcessing() => - BlocksProcessing?.Invoke(this, new BlocksProcessingEventArgs([])); - - public void RaiseBlockProcessed() => - BlockProcessed?.Invoke(this, new BlockProcessedEventArgs(null!, null!)); - } - - /// - /// Minimal stub for — reports node as not syncing. - /// - private sealed class StubChainHeadInfoProvider : IChainHeadInfoProvider - { - public IChainHeadSpecProvider SpecProvider => null!; - public IReadOnlyStateProvider ReadOnlyStateProvider => null!; - public long HeadNumber => 0; - public long? BlockGasLimit => null; - public UInt256 CurrentBaseFee => UInt256.Zero; - public UInt256 CurrentFeePerBlobGas => UInt256.Zero; - public ProofVersion CurrentProofVersion => ProofVersion.V0; - public bool IsSyncing => false; - public bool IsProcessingBlock => false; -#pragma warning disable CS0067 // Event is never used - public event EventHandler? HeadChanged; -#pragma warning restore CS0067 - } -} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs index abbd0cb530af..7288c0d85ac1 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs @@ -17,8 +17,6 @@ namespace Nethermind.Blockchain.Test; -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class BeaconBlockRootHandlerTests { private BeaconBlockRootHandler _beaconBlockRootHandler; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockFinderExtensionsTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockFinderExtensionsTests.cs index 1ad1c053cbd4..7665e94c284e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockFinderExtensionsTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockFinderExtensionsTests.cs @@ -11,7 +11,6 @@ namespace Nethermind.Blockchain.Test; -[Parallelizable(ParallelScope.All)] public class BlockFinderExtensionsTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs index 4e180459cf7d..0471540f3e5c 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs @@ -36,7 +36,6 @@ namespace Nethermind.Blockchain.Test; -[Parallelizable(ParallelScope.All)] public class BlockProcessorTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeSuggestPacerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeSuggestPacerTests.cs index 46a3ea978935..34392783f02f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeSuggestPacerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeSuggestPacerTests.cs @@ -10,7 +10,6 @@ namespace Nethermind.Blockchain.Test; -[Parallelizable(ParallelScope.All)] public class BlockTreeSuggestPacerTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs index d2f2a332753e..3d327872c84f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs @@ -32,8 +32,6 @@ namespace Nethermind.Blockchain.Test; -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class BlockTreeTests { private TestMemDb _blocksInfosDb = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs index b9152d322bff..3f9fed9e8859 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs @@ -28,8 +28,7 @@ namespace Nethermind.Blockchain.Test; -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +[Parallelizable(ParallelScope.Self)] public class BlockchainProcessorTests { private class ProcessingTestContext @@ -196,7 +195,7 @@ public ProcessingTestContext(bool startProcessor) .TestObject; _branchProcessor = new BranchProcessorMock(_logManager, _stateReader); _recoveryStep = new RecoveryStepMock(_logManager); - _processor = new BlockchainProcessor(_blockTree, _branchProcessor, _recoveryStep, _stateReader, LimboLogs.Instance, BlockchainProcessor.Options.Default, Substitute.For()); + _processor = new BlockchainProcessor(_blockTree, _branchProcessor, _recoveryStep, _stateReader, LimboLogs.Instance, BlockchainProcessor.Options.Default); _resetEvent = new AutoResetEvent(false); _queueEmptyResetEvent = new AutoResetEvent(false); @@ -284,30 +283,11 @@ public AfterBlock ProcessedFail(Block block) public ProcessingTestContext Suggested(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) { - if ((options & BlockTreeSuggestOptions.ShouldProcess) != 0) - { - // Use Task.Run to avoid blocking when AllowSynchronousContinuations - // causes inline processing on the calling thread - Task.Run(() => - { - AddBlockResult result = _blockTree.SuggestBlock(block, options); - if (result != AddBlockResult.Added) - { - _logger.Info($"Finished waiting for {block.ToString(Block.Format.Short)} as block was ignored"); - _resetEvent.Set(); - } - }); - // Wait for block to be in the tree before returning - SpinWait.SpinUntil(() => _blockTree.IsKnownBlock(block.Number, block.Hash!), ProcessingWait); - } - else + AddBlockResult result = _blockTree.SuggestBlock(block, options); + if (result != AddBlockResult.Added) { - AddBlockResult result = _blockTree.SuggestBlock(block, options); - if (result != AddBlockResult.Added) - { - _logger.Info($"Finished waiting for {block.ToString(Block.Format.Short)} as block was ignored"); - _resetEvent.Set(); - } + _logger.Info($"Finished waiting for {block.ToString(Block.Format.Short)} as block was ignored"); + _resetEvent.Set(); } return this; @@ -348,7 +328,8 @@ public ProcessingTestContext Recovered(Block block) public ProcessingTestContext CountIs(int expectedCount) { - Assert.That(() => ((IBlockProcessingQueue)_processor).Count, Is.EqualTo(expectedCount).After(ProcessingWait, 10)); + var count = ((IBlockProcessingQueue)_processor).Count; + Assert.That(expectedCount, Is.EqualTo(count)); return this; } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs index ec9997f9df50..1f218bdddc2a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs @@ -438,7 +438,6 @@ public void BlockhashStore_uses_custom_ring_buffer_size() } [Test, MaxTime(Timeout.MaxTestTime)] - [NonParallelizable] public async Task Prefetches_come_in_wrong_order() { const int chainLength = 261; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BadBlockStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BadBlockStoreTests.cs index f8fefc3679d0..dd149c475ccc 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BadBlockStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BadBlockStoreTests.cs @@ -12,7 +12,6 @@ namespace Nethermind.Blockchain.Test.Blocks; -[Parallelizable(ParallelScope.All)] public class BadBlockStoreTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs index 861264efda96..46c3d9da71cf 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs @@ -15,7 +15,6 @@ namespace Nethermind.Blockchain.Test.Blocks; -[Parallelizable(ParallelScope.All)] public class BlockStoreTests { private readonly Func, EquivalencyAssertionOptions> _ignoreEncodedSize = options => options.Excluding(b => b.EncodedSize); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/HeaderStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/HeaderStoreTests.cs index 130bda2d4829..bc1d967eb9ec 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/HeaderStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/HeaderStoreTests.cs @@ -11,9 +11,9 @@ namespace Nethermind.Blockchain.Test.Blocks; -[Parallelizable(ParallelScope.All)] public class HeaderStoreTests { + [Test] public void TestCanStoreAndGetHeader() { diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs b/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs index 4dbb1cc3e31a..86c7706fe3f5 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs @@ -11,15 +11,14 @@ namespace Nethermind.Blockchain.Test.Builders { public class FilterBuilder { - private int _id; + private static int _id; private BlockParameter _fromBlock = new(BlockParameterType.Latest); private BlockParameter _toBlock = new(BlockParameterType.Latest); private AddressFilter _address = AddressFilter.AnyAddress; private SequenceTopicsFilter _topicsFilter = new(); - private FilterBuilder(int id) + private FilterBuilder() { - _id = id; } public static FilterBuilder New() @@ -30,9 +29,9 @@ public static FilterBuilder New() public static FilterBuilder New(ref int currentFilterIndex) { - int id = currentFilterIndex; + _id = currentFilterIndex; currentFilterIndex++; - return new FilterBuilder(id); + return new FilterBuilder(); } public FilterBuilder WithId(int id) diff --git a/src/Nethermind/Nethermind.Blockchain.Test/CachedCodeInfoRepositoryTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/CachedCodeInfoRepositoryTests.cs index b933980d7e6b..10280c27796c 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/CachedCodeInfoRepositoryTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/CachedCodeInfoRepositoryTests.cs @@ -19,7 +19,6 @@ namespace Nethermind.Blockchain.Test; [TestFixture] -[Parallelizable(ParallelScope.All)] public class CachedCodeInfoRepositoryTests { private static IReleaseSpec CreateSpecWithPrecompile(Address precompileAddress) @@ -36,7 +35,7 @@ public void Precompile_WithCachingEnabled_IsWrappedInCachedPrecompile() TestPrecompile cachingPrecompile = new(supportsCaching: true); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(cachingPrecompile) }.ToFrozenDictionary(); @@ -51,7 +50,7 @@ public void Precompile_WithCachingEnabled_IsWrappedInCachedPrecompile() // Act CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); // Assert codeInfo.Should().NotBeNull(); @@ -66,7 +65,7 @@ public void Precompile_WithCachingDisabled_IsNotWrapped() TestPrecompile nonCachingPrecompile = new(supportsCaching: false); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(nonCachingPrecompile) }.ToFrozenDictionary(); @@ -81,7 +80,7 @@ public void Precompile_WithCachingDisabled_IsNotWrapped() // Act CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); // Assert codeInfo.Should().NotBeNull(); @@ -92,7 +91,7 @@ public void Precompile_WithCachingDisabled_IsNotWrapped() public void IdentityPrecompile_IsNotWrapped_WhenCacheEnabled() { // Arrange - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [IdentityPrecompile.Address] = new(IdentityPrecompile.Instance) }.ToFrozenDictionary(); @@ -107,7 +106,7 @@ public void IdentityPrecompile_IsNotWrapped_WhenCacheEnabled() // Act CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - CodeInfo codeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); + ICodeInfo codeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); // Assert codeInfo.Should().NotBeNull(); @@ -122,7 +121,7 @@ public void CachedPrecompile_CachesResults_ForCachingEnabledPrecompile() TestPrecompile cachingPrecompile = new(supportsCaching: true, onRun: () => runCount++); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(cachingPrecompile) }.ToFrozenDictionary(); @@ -136,7 +135,7 @@ public void CachedPrecompile_CachesResults_ForCachingEnabledPrecompile() IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); byte[] input = [1, 2, 3]; @@ -157,7 +156,7 @@ public void NonCachingPrecompile_DoesNotCacheResults() TestPrecompile nonCachingPrecompile = new(supportsCaching: false, onRun: () => runCount++); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(nonCachingPrecompile) }.ToFrozenDictionary(); @@ -171,7 +170,7 @@ public void NonCachingPrecompile_DoesNotCacheResults() IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); byte[] input = [1, 2, 3]; @@ -191,7 +190,7 @@ public void NullCache_DoesNotWrapAnyPrecompiles() TestPrecompile cachingPrecompile = new(supportsCaching: true); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(cachingPrecompile) }.ToFrozenDictionary(); @@ -205,7 +204,7 @@ public void NullCache_DoesNotWrapAnyPrecompiles() // Act - pass null cache CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, null); - CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); // Assert - precompile should not be wrapped codeInfo.Should().NotBeNull(); @@ -216,7 +215,7 @@ public void NullCache_DoesNotWrapAnyPrecompiles() public void Sha256Precompile_IsWrapped_WhenCacheEnabled() { // Arrange - Sha256Precompile has SupportsCaching = true (default) - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [Sha256Precompile.Address] = new(Sha256Precompile.Instance) }.ToFrozenDictionary(); @@ -231,7 +230,7 @@ public void Sha256Precompile_IsWrapped_WhenCacheEnabled() // Act CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - CodeInfo codeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); + ICodeInfo codeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); // Assert - Sha256Precompile should be wrapped (unlike IdentityPrecompile) codeInfo.Should().NotBeNull(); @@ -243,7 +242,7 @@ public void Sha256Precompile_IsWrapped_WhenCacheEnabled() public void MixedPrecompiles_OnlyCachingEnabledAreWrapped() { // Arrange - mix of caching and non-caching precompiles - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [Sha256Precompile.Address] = new(Sha256Precompile.Instance), // SupportsCaching = true [IdentityPrecompile.Address] = new(IdentityPrecompile.Instance) // SupportsCaching = false @@ -264,8 +263,8 @@ public void MixedPrecompiles_OnlyCachingEnabledAreWrapped() // Act CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - CodeInfo sha256CodeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); - CodeInfo identityCodeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); + ICodeInfo sha256CodeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); + ICodeInfo identityCodeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); // Assert - Sha256 wrapped, Identity not wrapped sha256CodeInfo.Precompile.Should().NotBeSameAs(Sha256Precompile.Instance); @@ -282,7 +281,7 @@ public void CachedPrecompile_DifferentInputs_CreateSeparateCacheEntries() TestPrecompile cachingPrecompile = new(supportsCaching: true, onRun: () => runCount++); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(cachingPrecompile) }.ToFrozenDictionary(); @@ -296,7 +295,7 @@ public void CachedPrecompile_DifferentInputs_CreateSeparateCacheEntries() IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); byte[] input1 = [1, 2, 3]; byte[] input2 = [4, 5, 6]; @@ -321,7 +320,7 @@ public void CachedPrecompile_ReturnsCachedResult_OnCacheHit() TestPrecompile cachingPrecompile = new(supportsCaching: true, onRun: () => runCount++, fixedOutput: expectedOutput); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(cachingPrecompile) }.ToFrozenDictionary(); @@ -335,7 +334,7 @@ public void CachedPrecompile_ReturnsCachedResult_OnCacheHit() IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); byte[] input = [1, 2, 3]; @@ -355,7 +354,7 @@ public void CachedPrecompile_ReturnsCachedResult_OnCacheHit() public void Sha256Precompile_CachesResults_WithRealComputation() { // Arrange - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [Sha256Precompile.Address] = new(Sha256Precompile.Instance) }.ToFrozenDictionary(); @@ -369,7 +368,7 @@ public void Sha256Precompile_CachesResults_WithRealComputation() IReleaseSpec spec = CreateSpecWithPrecompile(Sha256Precompile.Address); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - CodeInfo codeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); + ICodeInfo codeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); byte[] input = [1, 2, 3, 4, 5]; @@ -388,7 +387,7 @@ public void Sha256Precompile_CachesResults_WithRealComputation() public void IdentityPrecompile_DoesNotCache_WithRealComputation() { // Arrange - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [IdentityPrecompile.Address] = new(IdentityPrecompile.Instance) }.ToFrozenDictionary(); @@ -402,7 +401,7 @@ public void IdentityPrecompile_DoesNotCache_WithRealComputation() IReleaseSpec spec = CreateSpecWithPrecompile(IdentityPrecompile.Address); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - CodeInfo codeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); + ICodeInfo codeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); byte[] input = [1, 2, 3, 4, 5]; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ChainHeadReadOnlyStateProviderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ChainHeadReadOnlyStateProviderTests.cs index 092d3b8b14cb..6711e1283518 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ChainHeadReadOnlyStateProviderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ChainHeadReadOnlyStateProviderTests.cs @@ -9,7 +9,6 @@ namespace Nethermind.Blockchain.Test; -[Parallelizable(ParallelScope.All)] public class ChainHeadReadOnlyStateProviderTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/ClefSignerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/ClefSignerTests.cs index 4ecb01eee74f..54d83e4a3617 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/ClefSignerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/ClefSignerTests.cs @@ -15,7 +15,6 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] - [Parallelizable(ParallelScope.All)] public class ClefSignerTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/CompositeTxSourceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/CompositeTxSourceTests.cs index 6a9b7270cfc5..cc61b6a7fc4a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/CompositeTxSourceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/CompositeTxSourceTests.cs @@ -14,7 +14,6 @@ namespace Nethermind.Blockchain.Test.Consensus; -[Parallelizable(ParallelScope.All)] public class CompositeTxSourceTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NethDevSealEngineTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NethDevSealEngineTests.cs index e06a1eeaf3d6..d9f0704c5167 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NethDevSealEngineTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NethDevSealEngineTests.cs @@ -12,7 +12,6 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] - [Parallelizable(ParallelScope.All)] public class NethDevSealEngineTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSealEngineTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSealEngineTests.cs index df8bed08b4f1..dcf4bf713101 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSealEngineTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSealEngineTests.cs @@ -11,7 +11,6 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] - [Parallelizable(ParallelScope.All)] public class NullSealEngineTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs index 1de624592784..17af0b2ad38a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs @@ -12,7 +12,6 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] - [Parallelizable(ParallelScope.All)] public class NullSignerTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/OneByOneTxSourceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/OneByOneTxSourceTests.cs index 0c26037ba28e..354848108367 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/OneByOneTxSourceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/OneByOneTxSourceTests.cs @@ -11,7 +11,6 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] - [Parallelizable(ParallelScope.All)] public class OneByOneTxSourceTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SealEngineExceptionTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SealEngineExceptionTests.cs index c6bef99ca9bc..5d5fcb9602c4 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SealEngineExceptionTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SealEngineExceptionTests.cs @@ -8,7 +8,6 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] - [Parallelizable(ParallelScope.All)] public class SealEngineExceptionTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SignerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SignerTests.cs index fe897e8ff3e9..edd88c1a7224 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SignerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SignerTests.cs @@ -16,7 +16,6 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] - [Parallelizable(ParallelScope.All)] public class SignerTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SinglePendingTxSelectorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SinglePendingTxSelectorTests.cs index ad529fda85ba..feb4d47964c0 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SinglePendingTxSelectorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SinglePendingTxSelectorTests.cs @@ -11,7 +11,6 @@ namespace Nethermind.Blockchain.Test.Consensus { - [Parallelizable(ParallelScope.All)] public class SinglePendingTxSelectorTests { private readonly BlockHeader _anyParent = Build.A.BlockHeader.TestObject; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/DaoDataTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/DaoDataTests.cs index f2dbf4d95c20..b7483603e603 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/DaoDataTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/DaoDataTests.cs @@ -7,7 +7,6 @@ namespace Nethermind.Blockchain.Test; -[Parallelizable(ParallelScope.All)] public class DaoDataTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Data/FileLocalDataSourceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Data/FileLocalDataSourceTests.cs index d13afa2d1143..03e9008d489f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Data/FileLocalDataSourceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Data/FileLocalDataSourceTests.cs @@ -16,7 +16,6 @@ namespace Nethermind.Blockchain.Test.Data { - [Parallelizable(ParallelScope.All)] public class FileLocalDataSourceTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ExitOnBlocknumberHandlerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ExitOnBlocknumberHandlerTests.cs index 34d04d45bf72..72da5efe337e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ExitOnBlocknumberHandlerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ExitOnBlocknumberHandlerTests.cs @@ -10,7 +10,6 @@ namespace Nethermind.Blockchain.Test; -[Parallelizable(ParallelScope.All)] public class ExitOnBlocknumberHandlerTests { [TestCase(10, false)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs index 4dde1eadbff2..4fe896388c71 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs @@ -11,7 +11,6 @@ namespace Nethermind.Blockchain.Test.Filters; [TestFixture] -[Parallelizable(ParallelScope.All)] public class AddressFilterTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterManagerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterManagerTests.cs index a8dd1a2a6c9c..0b863d6112bc 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterManagerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterManagerTests.cs @@ -21,11 +21,11 @@ namespace Nethermind.Blockchain.Test.Filters; -[Parallelizable(ParallelScope.None)] public class FilterManagerTests { private FilterStore _filterStore = null!; - private TestMainProcessingContext _mainProcessingContext = null!; + private IBranchProcessor _branchProcessor = null!; + private IMainProcessingContext _mainProcessingContext = null!; private ITxPool _txPool = null!; private ILogManager _logManager = null!; private FilterManager _filterManager = null!; @@ -36,8 +36,10 @@ public class FilterManagerTests public void Setup() { _currentFilterId = 0; - _filterStore = new FilterStore(new TimerFactory(), 400, 100); - _mainProcessingContext = new TestMainProcessingContext(); + _filterStore = new FilterStore(new TimerFactory(), 20, 10); + _branchProcessor = Substitute.For(); + _mainProcessingContext = Substitute.For(); + _mainProcessingContext.BranchProcessor.Returns(_branchProcessor); _txPool = Substitute.For(); _logManager = LimboLogs.Instance; } @@ -53,7 +55,7 @@ public async Task removing_filter_removes_data() { LogsShouldNotBeEmpty(static _ => { }, static _ => { }); _filterManager.GetLogs(0).Should().NotBeEmpty(); - await Task.Delay(600); + await Task.Delay(60); _filterManager.GetLogs(0).Should().BeEmpty(); } @@ -255,7 +257,6 @@ public void logs_should_be_empty_for_existing_block_and_addresses_and_non_existi [Test, MaxTime(Timeout.MaxTestTime)] [TestCase(1, 1)] [TestCase(5, 3)] - [NonParallelizable] public void logs_should_have_correct_log_indexes(int filtersCount, int logsPerTx) { const int txCount = 10; @@ -329,12 +330,12 @@ private void Assert(IEnumerable> filterBuilders, _filterStore.SaveFilters(filters.OfType()); _filterManager = new FilterManager(_filterStore, _mainProcessingContext, _txPool, _logManager); - _mainProcessingContext.TestBranchProcessor.RaiseBlockProcessed(new BlockProcessedEventArgs(block, [])); + _branchProcessor.BlockProcessed += Raise.EventWith(_branchProcessor, new BlockProcessedEventArgs(block, [])); int index = 1; foreach (TxReceipt receipt in receipts) { - _mainProcessingContext.RaiseTransactionProcessed( + _mainProcessingContext.TransactionProcessed += Raise.EventWith(_branchProcessor, new TxProcessedEventArgs(index, Build.A.Transaction.TestObject, block.Header, receipt)); index++; } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs index dfec4eac051e..80fabb6c8e13 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs @@ -18,8 +18,6 @@ namespace Nethermind.Blockchain.Test.Filters; -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class FilterStoreTests { [Test, MaxTime(Timeout.MaxTestTime)] @@ -154,7 +152,6 @@ public void Correctly_creates_topics_filter(Hash256[]?[]? topics, TopicsFilter e } [Test, MaxTime(Timeout.MaxTestTime)] - [Parallelizable(ParallelScope.None)] public async Task CleanUps_filters() { List removedFilterIds = new(); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs index df6bde0db6b9..9d497daa7dd0 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs @@ -11,8 +11,6 @@ namespace Nethermind.Blockchain.Test.Filters; -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class LogFilterTests { private int _filterCounter; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogIndexFilterVisitorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogIndexFilterVisitorTests.cs deleted file mode 100644 index 3a901bfaafdf..000000000000 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogIndexFilterVisitorTests.cs +++ /dev/null @@ -1,413 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Nethermind.Blockchain.Filters; -using Nethermind.Blockchain.Test.Builders; -using Nethermind.Core; -using Nethermind.Core.Collections; -using Nethermind.Core.Crypto; -using Nethermind.Core.Test.Builders; -using Nethermind.Db.LogIndex; -using Nethermind.Facade.Filters; -using NSubstitute; -using NUnit.Framework; - -namespace Nethermind.Blockchain.Test.Filters; - -[Parallelizable(ParallelScope.All)] -public class LogIndexFilterVisitorTests -{ - private record Ranges(Dictionary> Address, Dictionary>[] Topic) - { - public List this[Address address] => Address[address]; - public List this[int topicIndex, Hash256 hash] => Topic[topicIndex][hash]; - } - - public class EnumeratorWrapper(int[] array) : IEnumerator - { - private readonly IEnumerator _enumerator = array.Cast().GetEnumerator(); - public bool MoveNext() => _enumerator.MoveNext(); - public void Reset() => _enumerator.Reset(); - public int Current => _enumerator.Current; - object IEnumerator.Current => Current; - public virtual void Dispose() => _enumerator.Dispose(); - } - - [TestCase( - new[] { 1, 3, 5, 7, 9, }, - new[] { 0, 2, 4, 6, 8 }, - TestName = "Non-intersecting, but similar ranges" - )] - [TestCase( - new[] { 1, 2, 3, 4, 5, }, - new[] { 5, 6, 7, 8, 9 }, - TestName = "Intersects on first/last" - )] - [TestCase( - new[] { 1, 2, 3, 4, 5, }, - new[] { 6, 7, 8, 9, 10 }, - TestName = "Non-intersecting ranges" - )] - public void IntersectEnumerator(int[] s1, int[] s2) - { - var expected = s1.Intersect(s2).Order().ToArray(); - - VerifyEnumerator(s1, s2, expected); - VerifyEnumerator(s2, s1, expected); - } - - [TestCase(1, 1)] - [TestCase(20, 20)] - [TestCase(20, 100)] - [TestCase(100, 100)] - [TestCase(1000, 1000)] - public void IntersectEnumerator_Random(int len1, int len2) - { - var random = new Random(42); - var s1 = RandomAscending(random, len1, Math.Max(1, len1 / 10)); - var s2 = RandomAscending(random, len2, Math.Max(1, len2 / 10)); - - var expected = s1.Intersect(s2).Order().ToArray(); - Assert.That(expected, Is.Not.Empty, "Unreliable test: Needs non-empty sequence to verify against."); - - VerifyEnumerator(s1, s2, expected); - VerifyEnumerator(s2, s1, expected); - } - - [TestCase(0, 0)] - [TestCase(0, 1)] - [TestCase(0, 10)] - public void IntersectEnumerator_SomeEmpty(int len1, int len2) - { - var s1 = Enumerable.Range(0, len1).ToArray(); - var s2 = Enumerable.Range(0, len2).ToArray(); - - VerifyEnumerator(s1, s2, []); - VerifyEnumerator(s2, s1, []); - } - - [TestCase( - new[] { 1, 2, 3, 4, 5, }, - new[] { 2, 3, 4 }, - TestName = "Contained" - )] - [TestCase( - new[] { 1, 2, 3, 4, 5, }, - new[] { 1, 2, 3, 4, 5, }, - TestName = "Identical" - )] - [TestCase( - new[] { 1, 3, 5, 7, 9, }, - new[] { 2, 4, 6, 8, 10 }, - TestName = "Complementary" - )] - public void UnionEnumerator(int[] s1, int[] s2) - { - var expected = s1.Union(s2).Distinct().Order().ToArray(); - - VerifyEnumerator(s1, s2, expected); - VerifyEnumerator(s2, s1, expected); - } - - [TestCase(1, 1)] - [TestCase(20, 20)] - [TestCase(20, 100)] - [TestCase(100, 100)] - [TestCase(1000, 1000)] - public void UnionEnumerator_Random(int len1, int len2) - { - var random = new Random(42); - var s1 = RandomAscending(random, len1, Math.Max(1, len1 / 10)); - var s2 = RandomAscending(random, len2, Math.Max(1, len2 / 10)); - - var expected = s1.Union(s2).Distinct().Order().ToArray(); - - VerifyEnumerator(s1, s2, expected); - VerifyEnumerator(s2, s1, expected); - } - - [TestCase(0, 0)] - [TestCase(0, 1)] - [TestCase(0, 10)] - public void UnionEnumerator_SomeEmpty(int len1, int len2) - { - var s1 = Enumerable.Range(0, len1).ToArray(); - var s2 = Enumerable.Range(0, len2).ToArray(); - - var expected = s1.Union(s2).Distinct().Order().ToArray(); - - VerifyEnumerator(s1, s2, expected); - VerifyEnumerator(s2, s1, expected); - } - - [TestCaseSource(nameof(FilterTestData))] - public void FilterEnumerator(string name, LogFilter filter, List expected) - { - Assert.That(expected, - Has.Count.InRange(from: 1, to: ToBlock - FromBlock - 1), - "Unreliable test: none or all blocks are selected." - ); - ILogIndexStorage storage = Substitute.For(); - - foreach ((Address address, List range) in LogIndexRanges.Address) - { - storage - .GetEnumerator(address, Arg.Any(), Arg.Any()) - .Returns(info => range.SkipWhile(x => x < info.ArgAt(1)).TakeWhile(x => x <= info.ArgAt(2)).GetEnumerator()); - } - - for (var i = 0; i < LogIndexRanges.Topic.Length; i++) - { - foreach ((Hash256 topic, List range) in LogIndexRanges.Topic[i]) - { - storage - .GetEnumerator(Arg.Is(i), topic, Arg.Any(), Arg.Any()) - .Returns(info => range.SkipWhile(x => x < info.ArgAt(2)).TakeWhile(x => x <= info.ArgAt(3)).GetEnumerator()); - } - } - - Assert.That(storage.EnumerateBlockNumbersFor(filter, FromBlock, ToBlock), Is.EquivalentTo(expected)); - } - - [TestCaseSource(nameof(FilterTestData))] - public void FilterEnumerator_Dispose(string name, LogFilter filter, List _) - { - int[] blockNumbers = [1, 2, 3, 4, 5]; - List> enumerators = []; - - ILogIndexStorage storage = Substitute.For(); - storage.GetEnumerator(Arg.Any
(), Arg.Any(), Arg.Any()).Returns(_ => MockEnumerator()); - storage.GetEnumerator(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(_ => MockEnumerator()); - - storage.EnumerateBlockNumbersFor(filter, FromBlock, ToBlock).ForEach(_ => { }); - - enumerators.ForEach(enumerator => enumerator.Received().Dispose()); - - IEnumerator MockEnumerator() - { - IEnumerator? enumerator = Substitute.ForPartsOf(blockNumbers); - enumerators.Add(enumerator); - return enumerator; - } - } - - public static IEnumerable FilterTestData - { - get - { - yield return new TestCaseData( - "AddressA", // name - - BuildFilter() // filter - .WithAddress(TestItem.AddressA) - .Build(), - - LogIndexRanges[TestItem.AddressA] // expected range - ); - - yield return new TestCaseData( - "AddressA or AddressA", - - BuildFilter() - .WithAddresses(TestItem.AddressA, TestItem.AddressA) - .Build(), - - LogIndexRanges[TestItem.AddressA] - ); - - yield return new TestCaseData( - "AddressA or AddressB", - - BuildFilter() - .WithAddresses(TestItem.AddressA, TestItem.AddressB) - .Build(), - - LogIndexRanges[TestItem.AddressA].Union(LogIndexRanges[TestItem.AddressB]) - .Distinct().Order().ToList() - ); - - yield return new TestCaseData( - "TopicA", - - BuildFilter() - .WithTopicExpressions( - TestTopicExpressions.Specific(TestItem.KeccakA) - ).Build(), - - LogIndexRanges[0, TestItem.KeccakA] - ); - - yield return new TestCaseData( - "TopicA or TopicA", - - BuildFilter() - .WithTopicExpressions( - TestTopicExpressions.Or(TestItem.KeccakA, TestItem.KeccakA) - ).Build(), - - LogIndexRanges[0, TestItem.KeccakA] - ); - - yield return new TestCaseData( - "TopicA or TopicB", - - BuildFilter() - .WithTopicExpressions( - TestTopicExpressions.Or(TestItem.KeccakA, TestItem.KeccakB) - ).Build(), - - LogIndexRanges[0, TestItem.KeccakA].Union(LogIndexRanges[0, TestItem.KeccakB]) - .Distinct().Order().ToList() - ); - - yield return new TestCaseData( - "TopicA, TopicB", - - BuildFilter() - .WithTopicExpressions( - TestTopicExpressions.Specific(TestItem.KeccakA), - TestTopicExpressions.Specific(TestItem.KeccakB) - ).Build(), - - LogIndexRanges[0, TestItem.KeccakA] - .Intersect(LogIndexRanges[1, TestItem.KeccakB]) - .Distinct().Order().ToList() - ); - - yield return new TestCaseData( - "TopicA, -, TopicA", - - BuildFilter() - .WithTopicExpressions( - TestTopicExpressions.Specific(TestItem.KeccakA), - TestTopicExpressions.Any, - TestTopicExpressions.Specific(TestItem.KeccakA) - ).Build(), - - LogIndexRanges[0, TestItem.KeccakA] - .Intersect(LogIndexRanges[2, TestItem.KeccakA]) - .Distinct().Order().ToList() - ); - - yield return new TestCaseData( - "TopicA, -, TopicB or TopicC", - - BuildFilter() - .WithTopicExpressions( - TestTopicExpressions.Specific(TestItem.KeccakA), - TestTopicExpressions.Any, - TestTopicExpressions.Or(TestItem.KeccakB, TestItem.KeccakC) - ).Build(), - LogIndexRanges[0, TestItem.KeccakA] - .Intersect(LogIndexRanges[2, TestItem.KeccakB].Union(LogIndexRanges[2, TestItem.KeccakC])) - .Distinct().Order().ToList() - ); - - yield return new TestCaseData( - "AddressA | TopicA or TopicB, TopicC", - - BuildFilter() - .WithAddress(TestItem.AddressA) - .WithTopicExpressions( - TestTopicExpressions.Or(TestItem.KeccakA, TestItem.KeccakB), - TestTopicExpressions.Specific(TestItem.KeccakC) - ).Build(), - - LogIndexRanges[0, TestItem.KeccakA].Union(LogIndexRanges[0, TestItem.KeccakB]) - .Intersect(LogIndexRanges[1, TestItem.KeccakC]) - .Intersect(LogIndexRanges[TestItem.AddressA]) - .Distinct().Order().ToList() - ); - - yield return new TestCaseData( - "AddressA or AddressB | TopicA or TopicB, -, TopicC, TopicD or TopicE", - - BuildFilter() - .WithAddresses(TestItem.AddressA, TestItem.AddressB) - .WithTopicExpressions( - TestTopicExpressions.Or(TestItem.KeccakA, TestItem.KeccakB), - TestTopicExpressions.Any, - TestTopicExpressions.Specific(TestItem.KeccakC), - TestTopicExpressions.Or(TestItem.KeccakD, TestItem.KeccakE) - ).Build(), - - LogIndexRanges[TestItem.AddressA].Union(LogIndexRanges[TestItem.AddressB]) - .Intersect(LogIndexRanges[0, TestItem.KeccakA].Union(LogIndexRanges[0, TestItem.KeccakB])) - .Intersect(LogIndexRanges[2, TestItem.KeccakC]) - .Intersect(LogIndexRanges[3, TestItem.KeccakD].Union(LogIndexRanges[3, TestItem.KeccakE])) - .Distinct().Order().ToList() - ); - } - } - - private static int[] RandomAscending(Random random, int count, int maxDelta) - { - var result = new int[count]; - - for (var i = 0; i < result.Length; i++) - { - var min = i > 0 ? result[i - 1] : -1; - result[i] = random.Next(min + 1, min + 1 + maxDelta); - } - - return result; - } - - private static void VerifyEnumerator(int[] s1, int[] s2, int[] ex) - where T : IEnumerator - { - using var enumerator = (T)Activator.CreateInstance( - typeof(T), - s1.Cast().GetEnumerator(), - s2.Cast().GetEnumerator() - )!; - - Assert.That(EnumerateOnce(enumerator), Is.EqualTo(ex)); - } - - private static IEnumerable EnumerateOnce(IEnumerator enumerator) - { - while (enumerator.MoveNext()) - yield return enumerator.Current; - } - - private const long FromBlock = 0; - private const long ToBlock = 99; - - private static readonly Ranges LogIndexRanges = GenerateLogIndexRanges(); - - private static Ranges GenerateLogIndexRanges() - { - var random = new Random(42); - - var addressRanges = new Dictionary>(); - foreach (Address address in new[] { TestItem.AddressA, TestItem.AddressB, TestItem.AddressC, TestItem.AddressD, TestItem.AddressE }) - { - var range = Enumerable.Range((int)FromBlock, (int)(ToBlock + 1)).Where(_ => random.NextDouble() < 0.3).ToList(); - addressRanges.Add(address, range); - } - - Dictionary>[] topicRanges = Enumerable - .Range(0, LogIndexStorage.MaxTopics) - .Select(_ => new Dictionary>()).ToArray(); - - foreach (Dictionary> ranges in topicRanges) - { - foreach (Hash256 topic in new[] { TestItem.KeccakA, TestItem.KeccakB, TestItem.KeccakC, TestItem.KeccakD, TestItem.KeccakE }) - { - var range = Enumerable.Range((int)FromBlock, (int)(ToBlock + 1)).Where(_ => random.NextDouble() < 0.2).ToList(); - ranges.Add(topic, range); - } - } - - return new(addressRanges, topicRanges); - } - - private static FilterBuilder BuildFilter() => FilterBuilder.New() - .FromBlock(FromBlock) - .ToBlock(ToBlock); -} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/TestProcessingContext.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/TestProcessingContext.cs deleted file mode 100644 index 5b089c3f11c3..000000000000 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/TestProcessingContext.cs +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -#nullable enable - -using System; -using System.Collections.Generic; -using System.Threading; -using Nethermind.Consensus.Processing; -using Nethermind.Core; -using Nethermind.Evm.State; -using Nethermind.Evm.Tracing; -using Nethermind.Evm.TransactionProcessing; - -namespace Nethermind.Blockchain.Test.Filters; - -/// -/// Test implementation of IBranchProcessor that allows manual event raising. -/// -internal class TestBranchProcessor : IBranchProcessor -{ - public event EventHandler? BlockProcessed; - public event EventHandler? BlocksProcessing { add { } remove { } } - public event EventHandler? BlockProcessing { add { } remove { } } - - public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlocks, - ProcessingOptions processingOptions, IBlockTracer blockTracer, CancellationToken token = default) - => []; - - public void RaiseBlockProcessed(BlockProcessedEventArgs args) - => BlockProcessed?.Invoke(this, args); -} - -/// -/// Test implementation of IMainProcessingContext that allows manual event raising. -/// -internal class TestMainProcessingContext : IMainProcessingContext -{ - private readonly TestBranchProcessor _branchProcessor = new(); - - public ITransactionProcessor TransactionProcessor => null!; - public IBranchProcessor BranchProcessor => _branchProcessor; - public IBlockProcessor BlockProcessor => null!; - public IBlockchainProcessor BlockchainProcessor => null!; - public IBlockProcessingQueue BlockProcessingQueue => null!; - public IWorldState WorldState => null!; - public IGenesisLoader GenesisLoader => null!; - - public event EventHandler? TransactionProcessed; - - public TestBranchProcessor TestBranchProcessor => _branchProcessor; - - public void RaiseTransactionProcessed(TxProcessedEventArgs args) - => TransactionProcessed?.Invoke(this, args); -} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs index ae11d8419b65..e8ae192041b0 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs @@ -19,17 +19,13 @@ using Nethermind.Db; using Nethermind.Logging; using Nethermind.Db.Blooms; -using Nethermind.Db.LogIndex; using Nethermind.Facade.Filters; using Nethermind.Facade.Find; using NSubstitute; using NUnit.Framework; -using NUnit.Framework.Internal; namespace Nethermind.Blockchain.Test.Find; -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class LogFinderTests { private IBlockTree _blockTree = null!; @@ -49,19 +45,19 @@ public void SetUp() [TearDown] public void TearDown() => _bloomStorage?.Dispose(); - private void SetUp(bool allowReceiptIterator, int chainLength = 5) + private void SetUp(bool allowReceiptIterator) { var specProvider = Substitute.For(); specProvider.GetSpec(Arg.Any()).IsEip155Enabled.Returns(true); _receiptStorage = new InMemoryReceiptStorage(allowReceiptIterator); _rawBlockTree = Build.A.BlockTree() .WithTransactions(_receiptStorage, LogsForBlockBuilder) - .OfChainLength(out _headTestBlock, chainLength) + .OfChainLength(out _headTestBlock, 5) .TestObject; _blockTree = _rawBlockTree; _bloomStorage = new BloomStorage(new BloomConfig(), new MemDb(), new InMemoryDictionaryFileStoreFactory()); _receiptsRecovery = Substitute.For(); - _logFinder = CreateLogFinder(); + _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); } private void SetupHeadWithNoTransaction() @@ -133,8 +129,15 @@ public void filter_all_logs_iteratively([ValueSource(nameof(WithBloomValues))] b SetUp(allowReceiptIterator); LogFilter logFilter = AllBlockFilter().Build(); FilterLog[] logs = _logFinder.FindLogs(logFilter).ToArray(); + logs.Length.Should().Be(5); var indexes = logs.Select(static l => (int)l.LogIndex).ToArray(); - Assert.That(indexes, Is.EqualTo([0, 1, 0, 1, 2])); + // indexes[0].Should().Be(0); + // indexes[1].Should().Be(1); + // indexes[2].Should().Be(0); + // indexes[3].Should().Be(1); + // indexes[4].Should().Be(2); + // BeEquivalentTo does not check the ordering!!! :O + indexes.Should().BeEquivalentTo(new[] { 0, 1, 0, 1, 2 }); } [Test, MaxTime(Timeout.MaxTestTime)] @@ -142,7 +145,7 @@ public void throw_exception_when_receipts_are_missing([ValueSource(nameof(WithBl { StoreTreeBlooms(withBloomDb); _receiptStorage = NullReceiptStorage.Instance; - _logFinder = CreateLogFinder(); + _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); var logFilter = AllBlockFilter().Build(); @@ -155,7 +158,7 @@ public void throw_exception_when_receipts_are_missing([ValueSource(nameof(WithBl public void when_receipts_are_missing_and_header_has_no_receipt_root_do_not_throw_exception_() { _receiptStorage = NullReceiptStorage.Instance; - _logFinder = CreateLogFinder(); + _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); SetupHeadWithNoTransaction(); @@ -171,7 +174,7 @@ public void filter_all_logs_should_throw_when_to_block_is_not_found([ValueSource { StoreTreeBlooms(withBloomDb); var blockFinder = Substitute.For(); - _logFinder = CreateLogFinder(blockFinder); + _logFinder = new LogFinder(blockFinder, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); var logFilter = AllBlockFilter().Build(); var action = new Func>(() => _logFinder.FindLogs(logFilter)); action.Should().Throw(); @@ -274,13 +277,14 @@ public void filter_by_blocks(LogFilter filter, int expectedCount, bool withBloom public void filter_by_blocks_with_limit([ValueSource(nameof(WithBloomValues))] bool withBloomDb) { StoreTreeBlooms(withBloomDb); - _logFinder = CreateLogFinder(); + _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery, 2); var filter = FilterBuilder.New().FromLatestBlock().ToLatestBlock().Build(); var logs = _logFinder.FindLogs(filter).ToArray(); logs.Length.Should().Be(3); } + public static IEnumerable ComplexFilterTestsData { get @@ -312,7 +316,6 @@ public void complex_filter(LogFilter filter, int expectedCount, bool withBloomDb } [Test, MaxTime(Timeout.MaxTestTime)] - [NonParallelizable] public async Task Throw_log_finder_operation_canceled_after_given_timeout([Values(2, 0.01)] double waitTime) { var timeout = TimeSpan.FromMilliseconds(Timeout.MaxWaitTime); @@ -320,122 +323,24 @@ public async Task Throw_log_finder_operation_canceled_after_given_timeout([Value CancellationToken cancellationToken = cancellationTokenSource.Token; StoreTreeBlooms(true); - _logFinder = CreateLogFinder(); + _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); var logFilter = AllBlockFilter().Build(); var logs = _logFinder.FindLogs(logFilter, cancellationToken); await Task.Delay(timeout * waitTime); - TestDelegate action = () => _ = logs.ToArray(); + Func action = () => logs.ToArray(); if (waitTime > 1) { - Assert.That(action, Throws - .Exception.InstanceOf() - .Or.InnerException.InstanceOf() // PLINQ can wrap into AggregateException - ); + action.Should().Throw().WithInnerException(); } else { - Assert.DoesNotThrow(action); + action.Should().NotThrow(); } } - [TestCase("Empty index", - 1, 2, - null, null, - null, null - )] - [TestCase("No intersection, left", - 1, 2, - 4, 6, - null, null - )] - [TestCase("No intersection, adjacent left", - 1, 3, - 4, 6, - null, null - )] - [TestCase("1 block intersection, left", - 1, 4, - 4, 6, - 4, 4 - )] - [TestCase("Partial intersection, left", - 1, 5, - 4, 6, - 4, 5 - )] - [TestCase("Full containment, border right", - 1, 6, - 4, 6, - 4, 6 - )] - [TestCase("Full containment", - 1, 9, - 4, 6, - 4, 6 - )] - [TestCase("Full containment, border left", - 4, 9, - 4, 6, - 4, 6 - )] - [TestCase("Partial intersection, right", - 5, 9, - 4, 6, - 5, 6 - )] - [TestCase("1 block intersection, right", - 6, 9, - 4, 6, - 6, 6 - )] - [TestCase("No intersection, adjacent right", - 7, 9, - 4, 6, - null, null - )] - [TestCase("No intersection, right", - 8, 9, - 4, 6, - null, null - )] - public void query_intersected_range_from_log_index(string name, - int from, int to, - int? indexFrom, int? indexTo, - int? exFrom, int? exTo - ) - { - SetUp(true, chainLength: 10); - - var logIndexStorage = Substitute.For(); - logIndexStorage.Enabled.Returns(true); - logIndexStorage.MinBlockNumber.Returns(indexFrom); - logIndexStorage.MaxBlockNumber.Returns(indexTo); - logIndexStorage.GetEnumerator(Arg.Any
(), Arg.Any(), Arg.Any()) - .Returns(_ => Array.Empty().Cast().GetEnumerator()); - - Address address = TestItem.AddressA; - BlockHeader fromHeader = Build.A.BlockHeader.WithNumber(from).TestObject; - BlockHeader toHeader = Build.A.BlockHeader.WithNumber(to).TestObject; - LogFilter filter = FilterBuilder.New() - .FromBlock(from).ToBlock(to) - .WithAddress(address) - .Build(); - - var logFinder = new IndexedLogFinder( - _blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery, - logIndexStorage, minBlocksToUseIndex: 1 - ); - _ = logFinder.FindLogs(filter, fromHeader, toHeader).ToArray(); - - if (exTo is not null && exFrom is not null) - logIndexStorage.Received(1).GetEnumerator(address, exFrom.Value, exTo.Value); - else - logIndexStorage.DidNotReceiveWithAnyArgs().GetEnumerator(Arg.Any
(), Arg.Any(), Arg.Any()); - } - private static FilterBuilder AllBlockFilter() => FilterBuilder.New().FromEarliestBlock().ToPendingBlock(); private void StoreTreeBlooms(bool withBloomDb) @@ -449,6 +354,4 @@ private void StoreTreeBlooms(bool withBloomDb) } } - private LogFinder CreateLogFinder(IBlockFinder? blockFinder = null) => - new(blockFinder ?? _blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs index 406ae0c89e36..7c5aae0c8b2b 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs @@ -21,11 +21,18 @@ namespace Nethermind.Blockchain.Test.FullPruning; -[Parallelizable(ParallelScope.All)] +[Parallelizable(ParallelScope.Self)] [TestFixture(INodeStorage.KeyScheme.HalfPath)] [TestFixture(INodeStorage.KeyScheme.Hash)] -public class CopyTreeVisitorTests(INodeStorage.KeyScheme scheme) +public class CopyTreeVisitorTests { + private readonly INodeStorage.KeyScheme _keyScheme; + + public CopyTreeVisitorTests(INodeStorage.KeyScheme scheme) + { + _keyScheme = scheme; + } + [TestCase(0, 1)] [TestCase(0, 8)] [TestCase(1, 1)] @@ -76,7 +83,7 @@ public void cancel_coping_state_between_dbs() private IPruningContext CopyDb(IPruningContext pruningContext, CancellationToken cancellationToken, MemDb trieDb, VisitingOptions? visitingOptions = null, WriteFlags writeFlags = WriteFlags.None) { LimboLogs logManager = LimboLogs.Instance; - PatriciaTree trie = Build.A.Trie(new NodeStorage(trieDb, scheme)).WithAccountsByIndex(0, 100).TestObject; + PatriciaTree trie = Build.A.Trie(new NodeStorage(trieDb, _keyScheme)).WithAccountsByIndex(0, 100).TestObject; // Create a custom DbProvider that uses the trieDb from the test IDbProvider dbProvider = Substitute.For(); @@ -87,16 +94,16 @@ private IPruningContext CopyDb(IPruningContext pruningContext, CancellationToken (IWorldState worldState, IStateReader stateReader) = TestWorldStateFactory.CreateForTestWithStateReader(dbProvider, logManager); BlockHeader? baseBlock = Build.A.BlockHeader.WithStateRoot(trie.RootHash).TestObject; - if (scheme == INodeStorage.KeyScheme.Hash) + if (_keyScheme == INodeStorage.KeyScheme.Hash) { - NodeStorage nodeStorage = new NodeStorage(pruningContext, scheme); + NodeStorage nodeStorage = new NodeStorage(pruningContext, _keyScheme); using CopyTreeVisitor copyTreeVisitor = new(nodeStorage, writeFlags, logManager, cancellationToken); stateReader.RunTreeVisitor(copyTreeVisitor, baseBlock, visitingOptions); copyTreeVisitor.Finish(); } else { - NodeStorage nodeStorage = new NodeStorage(pruningContext, scheme); + NodeStorage nodeStorage = new NodeStorage(pruningContext, _keyScheme); using CopyTreeVisitor copyTreeVisitor = new(nodeStorage, writeFlags, logManager, cancellationToken); stateReader.RunTreeVisitor(copyTreeVisitor, baseBlock, visitingOptions); copyTreeVisitor.Finish(); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPrunerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPrunerTests.cs index d9b07e032cb3..c13e1e2482cc 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPrunerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPrunerTests.cs @@ -30,10 +30,18 @@ namespace Nethermind.Blockchain.Test.FullPruning; [TestFixture(0, 4)] [TestFixture(1, 1)] [TestFixture(1, 4)] -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] -public class FullPrunerTests(int fullPrunerMemoryBudgetMb, int degreeOfParallelism) +[Parallelizable(ParallelScope.Children)] +public class FullPrunerTests { + private readonly int _fullPrunerMemoryBudgetMb; + private readonly int _degreeOfParallelism; + + public FullPrunerTests(int fullPrunerMemoryBudgetMb, int degreeOfParallelism) + { + _fullPrunerMemoryBudgetMb = fullPrunerMemoryBudgetMb; + _degreeOfParallelism = degreeOfParallelism; + } + [Test, MaxTime(Timeout.MaxTestTime)] public async Task can_prune() { @@ -53,8 +61,8 @@ public async Task can_prune_and_switch_key_scheme(INodeStorage.KeyScheme current true, false, FullPruningCompletionBehavior.None, - fullPrunerMemoryBudgetMb, - degreeOfParallelism, + _fullPrunerMemoryBudgetMb, + _degreeOfParallelism, currentKeyScheme: currentKeyScheme, preferredKeyScheme: newKeyScheme); @@ -184,8 +192,8 @@ private TestContext CreateTest( successfulPruning, clearPrunedDb, completionBehavior, - fullPrunerMemoryBudgetMb, - degreeOfParallelism); + _fullPrunerMemoryBudgetMb, + _degreeOfParallelism); private class TestContext { diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs index 98483a553cce..d28cf1ab7be1 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs @@ -30,7 +30,6 @@ namespace Nethermind.Blockchain.Test.FullPruning; -[Parallelizable(ParallelScope.All)] public class FullPruningDiskTest { public class PruningTestBlockchain : TestBlockchain @@ -110,24 +109,27 @@ public static async Task Create(IPruningConfig? pruningCo return chain; } - public class FullTestPruner( - IFullPruningDb pruningDb, - INodeStorageFactory nodeStorageFactory, - INodeStorage mainNodeStorage, - IPruningTrigger pruningTrigger, - IPruningConfig pruningConfig, - IBlockTree blockTree, - IStateReader stateReader, - IProcessExitSource processExitSource, - IDriveInfo driveInfo, - IPruningTrieStore trieStore, - IChainEstimations chainEstimations, - ILogManager logManager) - : FullPruner(pruningDb, nodeStorageFactory, mainNodeStorage, pruningTrigger, pruningConfig, blockTree, - stateReader, processExitSource, chainEstimations, driveInfo, trieStore, logManager) + public class FullTestPruner : FullPruner { public EventWaitHandle WaitHandle { get; } = new ManualResetEvent(false); + public FullTestPruner( + IFullPruningDb pruningDb, + INodeStorageFactory nodeStorageFactory, + INodeStorage mainNodeStorage, + IPruningTrigger pruningTrigger, + IPruningConfig pruningConfig, + IBlockTree blockTree, + IStateReader stateReader, + IProcessExitSource processExitSource, + IDriveInfo driveInfo, + IPruningTrieStore trieStore, + IChainEstimations chainEstimations, + ILogManager logManager) + : base(pruningDb, nodeStorageFactory, mainNodeStorage, pruningTrigger, pruningConfig, blockTree, stateReader, processExitSource, chainEstimations, driveInfo, trieStore, logManager) + { + } + protected override async Task RunFullPruning(CancellationToken cancellationToken) { await base.RunFullPruning(cancellationToken); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/PruningTriggerPruningStrategyTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/PruningTriggerPruningStrategyTests.cs index fc43e2dce333..505f6227fb29 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/PruningTriggerPruningStrategyTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/PruningTriggerPruningStrategyTests.cs @@ -12,8 +12,7 @@ namespace Nethermind.Blockchain.Test.FullPruning { [TestFixture] - [Parallelizable(ParallelScope.All)] - [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] + [Parallelizable(ParallelScope.Self)] public class PruningTriggerPruningStrategyTests { private IFullPruningDb _fullPruningDb; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockOnEachPendingTxTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockOnEachPendingTxTests.cs index f1d61f1a256e..fb7dcde6abf1 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockOnEachPendingTxTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockOnEachPendingTxTests.cs @@ -10,7 +10,6 @@ namespace Nethermind.Blockchain.Test.Producers; -[Parallelizable(ParallelScope.All)] public class BuildBlockOnEachPendingTxTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockRegularlyTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockRegularlyTests.cs index c1279a679d17..4aa9dac4cbba 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockRegularlyTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockRegularlyTests.cs @@ -9,7 +9,6 @@ namespace Nethermind.Blockchain.Test.Producers; -[Parallelizable(ParallelScope.All)] public class BuildBlockRegularlyTests { [Test, MaxTime(Timeout.MaxTestTime), Retry(3)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksWhenRequestedTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksWhenRequestedTests.cs index d3c709b6ee11..a244c84aacb9 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksWhenRequestedTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksWhenRequestedTests.cs @@ -7,7 +7,6 @@ namespace Nethermind.Blockchain.Test.Producers; -[Parallelizable(ParallelScope.All)] public class BuildBlocksWhenRequestedTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/CompositeBlockProductionTriggerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/CompositeBlockProductionTriggerTests.cs index d493795f1b3e..478c37bbebc4 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/CompositeBlockProductionTriggerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/CompositeBlockProductionTriggerTests.cs @@ -7,7 +7,6 @@ namespace Nethermind.Blockchain.Test.Producers; -[Parallelizable(ParallelScope.All)] public class CompositeBlockProductionTriggerTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs index b8bc669d2a8c..733b73a3c804 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs @@ -17,7 +17,6 @@ namespace Nethermind.Blockchain.Test.Producers; -[Parallelizable(ParallelScope.All)] public class DevBlockProducerTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/IfPoolIsNotEmptyTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/IfPoolIsNotEmptyTests.cs index 14549530b899..3f2d97ac18be 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/IfPoolIsNotEmptyTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/IfPoolIsNotEmptyTests.cs @@ -9,7 +9,6 @@ namespace Nethermind.Blockchain.Test.Producers; -[Parallelizable(ParallelScope.All)] public class IfPoolIsNotEmptyTests { [MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/ReceiptTrieTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/ReceiptTrieTests.cs index 82c883a83576..0533fde1a350 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/ReceiptTrieTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/ReceiptTrieTests.cs @@ -17,7 +17,6 @@ namespace Nethermind.Blockchain.Test.Proofs; -[Parallelizable(ParallelScope.All)] public class ReceiptTrieTests { private static readonly IRlpStreamDecoder _decoder = Rlp.GetStreamDecoder()!; @@ -58,28 +57,6 @@ public void Can_collect_proof_with_branch() VerifyProof(proof, trie.RootHash); } - [Test, MaxTime(Timeout.MaxTestTime)] - public void Parallel_and_non_parallel_root_hashing_produce_same_root() - { - const int receiptCount = 100; - IReleaseSpec spec = MainnetSpecProvider.Instance.GetSpec((MainnetSpecProvider.MuirGlacierBlockNumber, null)); - TxReceipt[] receipts = new TxReceipt[receiptCount]; - for (int i = 0; i < receiptCount; i++) - { - receipts[i] = Build.A.Receipt.WithAllFieldsFilled.WithGasUsedTotal(1000 + i).TestObject; - } - - using TrackingCappedArrayPool parallelPool = new(receiptCount * 4, canBeParallel: true); - ReceiptTrie parallelTrie = new(spec, receipts, _decoder, parallelPool, canBeParallel: true); - Hash256 parallelRoot = parallelTrie.RootHash; - - using TrackingCappedArrayPool sequentialPool = new(receiptCount * 4, canBeParallel: false); - ReceiptTrie sequentialTrie = new(spec, receipts, _decoder, sequentialPool, canBeParallel: false); - Hash256 sequentialRoot = sequentialTrie.RootHash; - - Assert.That(sequentialRoot, Is.EqualTo(parallelRoot)); - } - private void VerifyProof(byte[][] proof, Hash256 receiptRoot) { TrieNode node = new(NodeType.Unknown, proof.Last()); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/TxTrieTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/TxTrieTests.cs index 02b86b29a6a3..4f8e912e1887 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/TxTrieTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/TxTrieTests.cs @@ -7,7 +7,6 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; -using Nethermind.Int256; using Nethermind.Serialization.Rlp; using Nethermind.Specs.Forks; using Nethermind.State.Proofs; @@ -18,11 +17,14 @@ namespace Nethermind.Blockchain.Test.Proofs; [TestFixture(true)] [TestFixture(false)] -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] -public class TxTrieTests(bool useEip2718) +public class TxTrieTests { - private readonly IReleaseSpec _releaseSpec = useEip2718 ? Berlin.Instance : MuirGlacier.Instance; + private readonly IReleaseSpec _releaseSpec; + + public TxTrieTests(bool useEip2718) + { + _releaseSpec = useEip2718 ? Berlin.Instance : MuirGlacier.Instance; + } [Test, MaxTime(Timeout.MaxTestTime)] public void Can_calculate_root() @@ -80,46 +82,6 @@ public void Can_collect_proof_with_trie_case_3_modified() } } - [Test, MaxTime(Timeout.MaxTestTime)] - public void Encoded_and_decoded_transaction_paths_have_same_root() - { - Transaction[] transactions = - [ - Build.A.Transaction.WithNonce(1).WithType(TxType.Legacy).Signed().TestObject, - Build.A.Transaction.WithNonce(2).WithType(useEip2718 ? TxType.EIP1559 : TxType.Legacy).Signed().TestObject, - Build.A.Transaction.WithNonce(3).WithType(useEip2718 ? TxType.AccessList : TxType.Legacy).Signed().TestObject, - ]; - - byte[][] encodedTransactions = transactions - .Select(static tx => Rlp.Encode(tx, RlpBehaviors.SkipTypedWrapping).Bytes) - .ToArray(); - - Hash256 decodedRoot = TxTrie.CalculateRoot(transactions); - Hash256 encodedRoot = TxTrie.CalculateRoot(encodedTransactions); - - Assert.That(encodedRoot, Is.EqualTo(decodedRoot)); - } - - [Test, MaxTime(Timeout.MaxTestTime)] - public void Parallel_and_non_parallel_root_hashing_produce_same_root() - { - const int txCount = 100; - Transaction[] transactions = new Transaction[txCount]; - for (int i = 0; i < txCount; i++) - { - transactions[i] = Build.A.Transaction.WithNonce((UInt256)(i + 1)).Signed().TestObject; - } - - using TrackingCappedArrayPool pool = new(); - TxTrie txTrie = new(transactions, canBuildProof: false, pool); - Hash256 parallelRoot = txTrie.RootHash; - - txTrie.UpdateRootHash(canBeParallel: false); - Hash256 nonParallelRoot = txTrie.RootHash; - - Assert.That(nonParallelRoot, Is.EqualTo(parallelRoot)); - } - private static void VerifyProof(byte[][] proof, Hash256 txRoot) { for (int i = proof.Length; i > 0; i--) diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs index 02c002758efd..4dada95cceb2 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs @@ -11,7 +11,6 @@ namespace Nethermind.Blockchain.Test.Proofs; -[Parallelizable(ParallelScope.All)] public class WithdrawalTrieTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReadOnlyBlockTreeTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReadOnlyBlockTreeTests.cs index baa7e9d4af2e..2a85fb94a0e2 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ReadOnlyBlockTreeTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReadOnlyBlockTreeTests.cs @@ -9,8 +9,6 @@ namespace Nethermind.Blockchain.Test; -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class ReadOnlyBlockTreeTests { private IBlockTree _innerBlockTree = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/KeccaksIteratorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/KeccaksIteratorTests.cs index 2c66c04dfaa1..96be4973fcae 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/KeccaksIteratorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/KeccaksIteratorTests.cs @@ -13,7 +13,6 @@ namespace Nethermind.Blockchain.Test.Receipts; -[Parallelizable(ParallelScope.All)] public class KeccaksIteratorTests { [TestCaseSource(nameof(TestKeccaks))] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs index caa0794e7ac3..4f7aa07eac27 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs @@ -26,19 +26,23 @@ namespace Nethermind.Blockchain.Test.Receipts; [TestFixture(true)] [TestFixture(false)] -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] -public class PersistentReceiptStorageTests(bool useCompactReceipts) +public class PersistentReceiptStorageTests { - private readonly TestSpecProvider _specProvider = new(Byzantium.Instance); + private readonly TestSpecProvider _specProvider = new TestSpecProvider(Byzantium.Instance); private TestMemColumnsDb _receiptsDb = null!; private ReceiptsRecovery _receiptsRecovery = null!; private IBlockTree _blockTree = null!; private IBlockStore _blockStore = null!; + private readonly bool _useCompactReceipts; private ReceiptConfig _receiptConfig = null!; private PersistentReceiptStorage _storage = null!; private ReceiptArrayStorageDecoder _decoder = null!; + public PersistentReceiptStorageTests(bool useCompactReceipts) + { + _useCompactReceipts = useCompactReceipts; + } + [SetUp] public void SetUp() { @@ -60,7 +64,7 @@ public void TearDown() private void CreateStorage() { - _decoder = new ReceiptArrayStorageDecoder(useCompactReceipts); + _decoder = new ReceiptArrayStorageDecoder(_useCompactReceipts); _storage = new PersistentReceiptStorage( _receiptsDb, _specProvider, @@ -383,7 +387,7 @@ public void When_NewHeadBlock_DoNotRemove_TxIndex_WhenTxIsInOtherBlockNumber() [Test] public async Task When_NewHeadBlock_Remove_TxIndex_OfRemovedBlock_Unless_ItsAlsoInNewBlock() { - _receiptConfig.CompactTxIndex = useCompactReceipts; + _receiptConfig.CompactTxIndex = _useCompactReceipts; CreateStorage(); (Block block, _) = InsertBlock(); Block block2 = Build.A.Block diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs index 14f6d357d52a..ad7d30e52564 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs @@ -14,10 +14,9 @@ namespace Nethermind.Blockchain.Test.Receipts; -[Parallelizable(ParallelScope.All)] public class ReceiptsIteratorTests { - private readonly ReceiptArrayStorageDecoder _decoder = ReceiptArrayStorageDecoder.Instance; + readonly ReceiptArrayStorageDecoder _decoder = ReceiptArrayStorageDecoder.Instance; [Test] public void SmokeTestWithRecovery() diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs index fd10cec441d6..7cd1b0ec1292 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs @@ -11,8 +11,6 @@ namespace Nethermind.Blockchain.Test.Receipts; -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class ReceiptsRecoveryTests { private IReceiptsRecovery _receiptsRecovery = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRootTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRootTests.cs index e7b8009e2e92..1c3db195441b 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRootTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRootTests.cs @@ -11,7 +11,6 @@ namespace Nethermind.Blockchain.Test.Receipts { - [Parallelizable(ParallelScope.All)] public class ReceiptsRootTests { public static IEnumerable ReceiptsRootTestCases diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReorgDepthFinalizedStateProviderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReorgDepthFinalizedStateProviderTests.cs index b8d4fd4f6c0b..7e2e9b7f7e97 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ReorgDepthFinalizedStateProviderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReorgDepthFinalizedStateProviderTests.cs @@ -11,8 +11,7 @@ namespace Nethermind.Blockchain.Test; [TestFixture] -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +[Parallelizable(ParallelScope.Self)] public class ReorgDepthFinalizedStateProviderTests { private IBlockTree _blockTree = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs index 8adf5fad4594..b8acad1530f1 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs @@ -29,7 +29,6 @@ using Nethermind.Evm.State; using Nethermind.State; using Nethermind.TxPool; -using NSubstitute; using NUnit.Framework; namespace Nethermind.Blockchain.Test; @@ -132,9 +131,7 @@ public void Setup() specProvider, LimboLogs.Instance), stateReader, - LimboLogs.Instance, - BlockchainProcessor.Options.Default, - Substitute.For()); + LimboLogs.Instance, BlockchainProcessor.Options.Default); } [OneTimeTearDown] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Rewards/NoBlockRewardsTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Rewards/NoBlockRewardsTests.cs index 212a6448829f..1dc264011284 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Rewards/NoBlockRewardsTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Rewards/NoBlockRewardsTests.cs @@ -8,7 +8,6 @@ namespace Nethermind.Blockchain.Test.Rewards; -[Parallelizable(ParallelScope.All)] public class NoBlockRewardsTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Rewards/RewardCalculatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Rewards/RewardCalculatorTests.cs index 3553483a1f69..d4225963be51 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Rewards/RewardCalculatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Rewards/RewardCalculatorTests.cs @@ -9,7 +9,6 @@ namespace Nethermind.Blockchain.Test.Rewards; -[Parallelizable(ParallelScope.All)] public class RewardCalculatorTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Services/HealthHintServiceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Services/HealthHintServiceTests.cs index fc89b9798e8f..aee9e3a4df56 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Services/HealthHintServiceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Services/HealthHintServiceTests.cs @@ -9,7 +9,6 @@ namespace Nethermind.Blockchain.Test.Services; -[Parallelizable(ParallelScope.All)] public class HealthHintServiceTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionGasPriceComparisonTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionGasPriceComparisonTests.cs index 089c1010237f..f247ffb1e153 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionGasPriceComparisonTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionGasPriceComparisonTests.cs @@ -14,7 +14,6 @@ namespace Nethermind.Blockchain.Test; -[Parallelizable(ParallelScope.All)] public class TransactionComparisonTests { [MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs index 73267a3f94a2..2659aa7fc129 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs @@ -25,8 +25,6 @@ namespace Nethermind.Evm.Test; [TestFixture] -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] internal class TransactionProcessorEip7702Tests { private ISpecProvider _specProvider; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs index bb8c1fe456df..fe71d8075570 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs @@ -32,8 +32,7 @@ namespace Nethermind.Evm.Test; [TestFixture(true)] [TestFixture(false)] [Todo(Improve.Refactor, "Check why fixture test cases did not work")] -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +[Parallelizable(ParallelScope.Self)] public class TransactionProcessorTests { private readonly bool _isEip155Enabled; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs index c31d811b18f6..2866322ce40b 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs @@ -28,7 +28,6 @@ namespace Nethermind.Blockchain.Test { - [Parallelizable(ParallelScope.All)] public class TransactionSelectorTests { public static IEnumerable ProperTransactionsSelectedTestCases diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs index 1282d9038c21..ab506b1fbab2 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs @@ -31,7 +31,6 @@ namespace Nethermind.Blockchain.Test { - [Parallelizable(ParallelScope.All)] public class TransactionsExecutorTests { public static IEnumerable ProperTransactionsSelectedTestCases diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Utils/LastNStateRootTrackerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Utils/LastNStateRootTrackerTests.cs index 07c79a8a1ec5..699ef5593bfa 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Utils/LastNStateRootTrackerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Utils/LastNStateRootTrackerTests.cs @@ -11,7 +11,6 @@ namespace Nethermind.Blockchain.Test.Utils; -[Parallelizable(ParallelScope.All)] public class LastNStateRootTrackerTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/BlockValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/BlockValidatorTests.cs index 78a14474285d..dff67022674e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/BlockValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/BlockValidatorTests.cs @@ -18,8 +18,6 @@ namespace Nethermind.Blockchain.Test.Validators; -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class BlockValidatorTests { private static BlockValidator _blockValidator = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/HeaderValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/HeaderValidatorTests.cs index fb3a63499864..445672cdf87a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/HeaderValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/HeaderValidatorTests.cs @@ -23,8 +23,6 @@ namespace Nethermind.Blockchain.Test.Validators; -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class HeaderValidatorTests { private IHeaderValidator _validator = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs index 28e7fe635cbe..0d386ee72497 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs @@ -15,7 +15,6 @@ namespace Nethermind.Blockchain.Test.Validators; -[Parallelizable(ParallelScope.All)] public class ShardBlobBlockValidatorTests { [TestCaseSource(nameof(BlobGasFieldsPerForkTestCases))] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs index ac04461b93fc..8dcb6a44720f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs @@ -26,9 +26,12 @@ namespace Nethermind.Blockchain.Test.Validators; -[Parallelizable(ParallelScope.All)] public class TxValidatorTests { + [SetUp] + public void Setup() + { + } [Test, MaxTime(Timeout.MaxTestTime)] public void Curve_is_correct() diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs index 0ecbe42eb588..069960655340 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs @@ -12,8 +12,6 @@ namespace Nethermind.Blockchain.Test.Validators; -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class UnclesValidatorTests { private Block _greatGrandparent; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs index 604c64e2d497..7cc4bacbc4e1 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs @@ -14,7 +14,6 @@ namespace Nethermind.Blockchain.Test.Validators; -[Parallelizable(ParallelScope.All)] public class WithdrawalValidatorTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Visitors/DbBlocksLoaderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Visitors/DbBlocksLoaderTests.cs index 420a86305e72..b468c23eed9c 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Visitors/DbBlocksLoaderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Visitors/DbBlocksLoaderTests.cs @@ -17,7 +17,6 @@ namespace Nethermind.Blockchain.Test.Visitors; -[Parallelizable(ParallelScope.All)] public class DbBlocksLoaderTests { private readonly int _dbLoadTimeout = 5000; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Visitors/StartupTreeFixerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Visitors/StartupTreeFixerTests.cs index 1191fe481b7a..8e787f1a5067 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Visitors/StartupTreeFixerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Visitors/StartupTreeFixerTests.cs @@ -17,7 +17,6 @@ namespace Nethermind.Blockchain.Test.Visitors; -[Parallelizable(ParallelScope.All)] public class StartupTreeFixerTests { [Test, MaxTime(Timeout.MaxTestTime), Ignore("Not implemented")] diff --git a/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs b/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs index 200341f05cdb..ea638750cea7 100644 --- a/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs @@ -25,7 +25,6 @@ public class BlockhashProvider( private readonly IBlockhashStore _blockhashStore = new BlockhashStore(worldState); private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); private Hash256[]? _hashes; - private long _prefetchVersion; public Hash256? GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec spec) { @@ -40,7 +39,7 @@ public class BlockhashProvider( } long depth = currentBlock.Number - number; - Hash256[]? hashes = Volatile.Read(ref _hashes); + Hash256[]? hashes = _hashes; return depth switch { @@ -61,8 +60,7 @@ public class BlockhashProvider( public async Task Prefetch(BlockHeader currentBlock, CancellationToken token) { - long prefetchVersion = Interlocked.Increment(ref _prefetchVersion); - Volatile.Write(ref _hashes, null); + _hashes = null; Hash256[]? hashes = await blockhashCache.Prefetch(currentBlock, token); // This leverages that branch processing is single threaded @@ -71,9 +69,9 @@ public async Task Prefetch(BlockHeader currentBlock, CancellationToken token) // This allows us to avoid await on Prefetch in BranchProcessor lock (_blockhashStore) { - if (!token.IsCancellationRequested && prefetchVersion == Interlocked.Read(ref _prefetchVersion)) + if (!token.IsCancellationRequested) { - Volatile.Write(ref _hashes, hashes); + _hashes = hashes; } } } diff --git a/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs b/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs index cdbd1857d4fb..c8ca251cb403 100644 --- a/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs @@ -21,11 +21,11 @@ public class CachedCodeInfoRepository( ICodeInfoRepository baseCodeInfoRepository, ConcurrentDictionary>? precompileCache) : ICodeInfoRepository { - private readonly FrozenDictionary _cachedPrecompile = precompileCache is null + private readonly FrozenDictionary _cachedPrecompile = precompileCache is null ? precompileProvider.GetPrecompiles() : precompileProvider.GetPrecompiles().ToFrozenDictionary(kvp => kvp.Key, kvp => CreateCachedPrecompile(kvp, precompileCache)); - public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, + public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) { if (vmSpec.IsPrecompile(codeSource) && _cachedPrecompile.TryGetValue(codeSource, out var cachedCodeInfo)) @@ -57,15 +57,15 @@ public bool TryGetDelegation(Address address, IReleaseSpec spec, return baseCodeInfoRepository.TryGetDelegation(address, spec, out delegatedAddress); } - private static CodeInfo CreateCachedPrecompile( - in KeyValuePair originalPrecompile, + private static PrecompileInfo CreateCachedPrecompile( + in KeyValuePair originalPrecompile, ConcurrentDictionary> cache) { IPrecompile precompile = originalPrecompile.Value.Precompile!; return !precompile.SupportsCaching ? originalPrecompile.Value - : new CodeInfo(new CachedPrecompile(originalPrecompile.Key.Value, precompile, cache)); + : new PrecompileInfo(new CachedPrecompile(originalPrecompile.Key.Value, precompile, cache)); } private class CachedPrecompile( diff --git a/src/Nethermind/Nethermind.Blockchain/EthereumPrecompileProvider.cs b/src/Nethermind/Nethermind.Blockchain/EthereumPrecompileProvider.cs index 03fa5cff876d..d793c9a65ba3 100644 --- a/src/Nethermind/Nethermind.Blockchain/EthereumPrecompileProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/EthereumPrecompileProvider.cs @@ -13,9 +13,9 @@ namespace Nethermind.Blockchain; public class EthereumPrecompileProvider() : IPrecompileProvider { - private static FrozenDictionary Precompiles + private static FrozenDictionary Precompiles { - get => new Dictionary + get => new Dictionary { [EcRecoverPrecompile.Address] = new(EcRecoverPrecompile.Instance), [Sha256Precompile.Address] = new(Sha256Precompile.Instance), @@ -45,7 +45,7 @@ private static FrozenDictionary Precompiles }.ToFrozenDictionary(); } - public FrozenDictionary GetPrecompiles() + public FrozenDictionary GetPrecompiles() { return Precompiles; } diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs index 2460334a9b60..296e39acbb4f 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs @@ -11,7 +11,6 @@ using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; -using Nethermind.Evm.CodeAnalysis; namespace Nethermind.Blockchain.Tracing.GethStyle.Custom.JavaScript; @@ -109,7 +108,7 @@ public override void ReportAction(long gas, UInt256 value, Address from, Address public override void StartOperation(int pc, Instruction opcode, long gas, in ExecutionEnvironment env, int codeSection = 0, int functionDepth = 0) { - _log.pc = pc + (env.CodeInfo is EofCodeInfo eof ? eof.PcOffset() : 0); + _log.pc = pc + env.CodeInfo.PcOffset(); _log.op = new Log.Opcode(opcode); _log.gas = gas; _log.depth = env.GetGethTraceDepth(); diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs index 4c69cdc72712..565e15a80a36 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs @@ -57,20 +57,16 @@ public NativeCallTracer( public override GethLikeTxTrace BuildResult() { GethLikeTxTrace result = base.BuildResult(); + NativeCallTracerCallFrame firstCallFrame = _callStack[0]; Debug.Assert(_callStack.Count == 1, $"Unexpected frames on call stack, expected only master frame, found {_callStack.Count} frames."); - if (_callStack.Count is not 0) - { - NativeCallTracerCallFrame firstCallFrame = _callStack[0]; - _callStack.RemoveAt(0); - _disposables.Add(firstCallFrame); - - result.TxHash = _txHash; - result.CustomTracerResult = new GethLikeCustomTrace { Value = firstCallFrame }; - } + _callStack.RemoveAt(0); + _disposables.Add(firstCallFrame); result.TxHash = _txHash; + result.CustomTracerResult = new GethLikeCustomTrace { Value = firstCallFrame }; + _resultBuilt = true; return result; diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethTraceOptions.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethTraceOptions.cs index 66605682db0b..6af4bd4fea7e 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethTraceOptions.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethTraceOptions.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Text.Json; +using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Evm; @@ -12,26 +13,33 @@ namespace Nethermind.Blockchain.Tracing.GethStyle; public record GethTraceOptions { + [JsonPropertyName("disableMemory")] [Obsolete("Use EnableMemory instead.")] public bool DisableMemory { get => !EnableMemory; init => EnableMemory = !value; } + [JsonPropertyName("disableStorage")] public bool DisableStorage { get; init; } + [JsonPropertyName("enableMemory")] public bool EnableMemory { get; init; } + [JsonPropertyName("disableStack")] public bool DisableStack { get; init; } + [JsonPropertyName("timeout")] public string Timeout { get; init; } + [JsonPropertyName("tracer")] public string Tracer { get; init; } + [JsonPropertyName("txHash")] public Hash256? TxHash { get; init; } + [JsonPropertyName("tracerConfig")] public JsonElement? TracerConfig { get; init; } + [JsonPropertyName("stateOverrides")] public Dictionary? StateOverrides { get; init; } - public BlockOverride? BlockOverrides { get; set; } - public static GethTraceOptions Default { get; } = new(); } diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/ParityStyle/ParityLikeTxTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/ParityStyle/ParityLikeTxTracer.cs index cc1b104b0a85..43d4ec399cfe 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/ParityStyle/ParityLikeTxTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/ParityStyle/ParityLikeTxTracer.cs @@ -9,7 +9,6 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Evm; -using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.Int256; @@ -237,7 +236,7 @@ public override void StartOperation(int pc, Instruction opcode, long gas, in Exe { ParityVmOperationTrace operationTrace = new(); _gasAlreadySetForCurrentOp = false; - operationTrace.Pc = pc + (env.CodeInfo is EofCodeInfo eof ? eof.PcOffset() : 0); + operationTrace.Pc = pc + env.CodeInfo.PcOffset(); operationTrace.Cost = gas; // skip codeSection // skip functionDepth diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockFinalizationManager.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockFinalizationManager.cs index 76a37745f107..87d5e8a15959 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockFinalizationManager.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockFinalizationManager.cs @@ -345,7 +345,6 @@ private set public void Dispose() { _branchProcessor.BlockProcessed -= OnBlockProcessed; - _branchProcessor.BlocksProcessing -= OnBlocksProcessing; } [DebuggerDisplay("Count = {Count}")] diff --git a/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs b/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs index 9ad3590d1392..6c1bf551641a 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using Nethermind.Blockchain; using Nethermind.Core; namespace Nethermind.Consensus.Clique @@ -11,6 +13,23 @@ public static bool IsInTurn(this BlockHeader header) { return header.Difficulty == Clique.DifficultyInTurn; } + + internal static Address[] ExtractSigners(BlockHeader blockHeader) + { + if (blockHeader.ExtraData is null) + { + throw new BlockchainException("Block header ExtraData cannot be null when extracting signers"); + } + + Span signersData = blockHeader.ExtraData.AsSpan(Clique.ExtraVanityLength, (blockHeader.ExtraData.Length - Clique.ExtraSealLength)); + Address[] signers = new Address[signersData.Length / Address.Size]; + for (int i = 0; i < signers.Length; i++) + { + signers[i] = new Address(signersData.Slice(i * 20, 20).ToArray()); + } + + return signers; + } } internal static class BlockExtensions diff --git a/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs b/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs index f93dcb2af89a..a838bce57d6c 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs @@ -142,179 +142,4 @@ public async Task Test_task_that_is_scheduled_during_block_processing_but_deadli wasCancelled.Should().BeTrue(); } - - [Test] - public async Task Test_expired_tasks_are_drained_during_block_processing() - { - int capacity = 16; - await using BackgroundTaskScheduler scheduler = new(_branchProcessor, _chainHeadInfo, 1, capacity, LimboLogs.Instance); - - // Start block processing — signal is reset, token cancelled - _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); - - int cancelledCount = 0; - for (int i = 0; i < capacity; i++) - { - scheduler.TryScheduleTask(1, (_, token) => - { - if (token.IsCancellationRequested) - { - Interlocked.Increment(ref cancelledCount); - } - return Task.CompletedTask; - }, TimeSpan.FromMilliseconds(1)); - } - - // Expired tasks should be drained even while block processing is in progress - Assert.That(() => cancelledCount, Is.EqualTo(capacity).After(2000, 10)); - - _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); - } - - [Test] - public async Task Test_queue_accepts_new_tasks_after_expired_tasks_drain_during_block_processing() - { - int capacity = 16; - await using BackgroundTaskScheduler scheduler = new(_branchProcessor, _chainHeadInfo, 1, capacity, LimboLogs.Instance); - - // Start block processing — signal is reset, token cancelled - _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); - - // Fill the queue with short-lived tasks - for (int i = 0; i < capacity; i++) - { - scheduler.TryScheduleTask(1, (_, _) => Task.CompletedTask, TimeSpan.FromMilliseconds(1)).Should().BeTrue(); - } - - // Wait for deadlines to pass and expired tasks to be drained - await Task.Delay(200); - - // New tasks should be accepted because expired tasks freed up queue space - for (int i = 0; i < capacity; i++) - { - bool accepted = scheduler.TryScheduleTask(1, (_, _) => Task.CompletedTask, TimeSpan.FromMilliseconds(1)); - accepted.Should().BeTrue($"Task {i} should be accepted after expired tasks were drained"); - } - - _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); - } - - [Test] - public async Task Test_high_capacity_queue_survives_repeated_block_processing_cycles() - { - int capacity = 1024; - int concurrency = 2; - await using BackgroundTaskScheduler scheduler = new(_branchProcessor, _chainHeadInfo, concurrency, capacity, LimboLogs.Instance); - - int executedCount = 0; - int cancelledCount = 0; - - // --- Phase 1: Fill the queue to capacity during block processing --- - _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); - - for (int i = 0; i < capacity; i++) - { - bool accepted = scheduler.TryScheduleTask(1, (_, token) => - { - if (token.IsCancellationRequested) - Interlocked.Increment(ref cancelledCount); - else - Interlocked.Increment(ref executedCount); - return Task.CompletedTask; - }, TimeSpan.FromMilliseconds(10)); - accepted.Should().BeTrue($"Phase 1: task {i} should be accepted up to capacity"); - } - - // Wait for deadlines to expire and tasks to drain - Assert.That( - () => Volatile.Read(ref cancelledCount), - Is.EqualTo(capacity).After(5000, 10), - "all tasks should be drained with cancelled tokens during block processing"); - - // --- Phase 2: End block processing, verify queue accepts tasks and runs them normally --- - _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); - - Interlocked.Exchange(ref executedCount, 0); - - int phase2Count = capacity / 2; - for (int i = 0; i < phase2Count; i++) - { - bool accepted = scheduler.TryScheduleTask(1, (_, _) => - { - Interlocked.Increment(ref executedCount); - return Task.CompletedTask; - }); - accepted.Should().BeTrue($"Phase 2: task {i} should be accepted after queue drained"); - } - - Assert.That( - () => Volatile.Read(ref executedCount), - Is.EqualTo(phase2Count).After(5000, 10), - "all phase 2 tasks should execute normally after block processing ends"); - - // --- Phase 3: Another block processing cycle with mixed short and long timeouts --- - _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); - - int phase3CancelledCount = 0; - int phase3ExecutedCount = 0; - - // Short-lived tasks (will expire during block processing) - int shortLivedCount = capacity / 2; - for (int i = 0; i < shortLivedCount; i++) - { - scheduler.TryScheduleTask(1, (_, token) => - { - if (token.IsCancellationRequested) - Interlocked.Increment(ref phase3CancelledCount); - return Task.CompletedTask; - }, TimeSpan.FromMilliseconds(5)).Should().BeTrue($"Phase 3: short-lived task {i} should be accepted"); - } - - // Long-lived tasks (will survive until block processing ends) - int longLivedCount = capacity / 4; - for (int i = 0; i < longLivedCount; i++) - { - scheduler.TryScheduleTask(1, (_, token) => - { - if (!token.IsCancellationRequested) - Interlocked.Increment(ref phase3ExecutedCount); - return Task.CompletedTask; - }, TimeSpan.FromSeconds(30)).Should().BeTrue($"Phase 3: long-lived task {i} should be accepted"); - } - - // Wait for short-lived tasks to expire and drain - Assert.That( - () => Volatile.Read(ref phase3CancelledCount), - Is.EqualTo(shortLivedCount).After(5000, 10), - "short-lived tasks should drain with cancelled tokens during block processing"); - - // Long-lived tasks should not have executed yet (still waiting for block processing to end) - Volatile.Read(ref phase3ExecutedCount).Should().Be(0, - "long-lived tasks should wait during block processing"); - - // End block processing — long-lived tasks should now execute - _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); - - Assert.That( - () => Volatile.Read(ref phase3ExecutedCount), - Is.EqualTo(longLivedCount).After(5000, 10), - "long-lived tasks should execute after block processing ends"); - - // --- Phase 4: Verify queue is fully operational with one more fill-and-drain --- - Interlocked.Exchange(ref executedCount, 0); - - for (int i = 0; i < capacity; i++) - { - scheduler.TryScheduleTask(1, (_, _) => - { - Interlocked.Increment(ref executedCount); - return Task.CompletedTask; - }).Should().BeTrue($"Phase 4: task {i} should be accepted in fully recovered queue"); - } - - Assert.That( - () => Volatile.Read(ref executedCount), - Is.EqualTo(capacity).After(5000, 10), - "all tasks in the final phase should execute successfully"); - } } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs index fb7ab548d42f..80340ae16a8b 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs @@ -57,7 +57,7 @@ public Task PreWarmCaches(Block suggestedBlock, BlockHeader? parent, IReleaseSpe if (preBlockCaches is not null) { CacheType result = preBlockCaches.ClearCaches(); - nodeStorageCache.ClearCaches(); + result |= nodeStorageCache.ClearCaches() ? CacheType.Rlp : CacheType.None; nodeStorageCache.Enabled = true; if (result != default) { diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs index 77ae74dddf2a..816517ff416b 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs @@ -57,16 +57,14 @@ public sealed class BlockchainProcessor : IBlockchainProcessor, IBlockProcessing new BoundedChannelOptions(MaxProcessingQueueSize) { // Optimize for single reader concurrency - SingleReader = true, - // If queues are empty we want the block processing to continue on NewPayload thread and inherit its priority - AllowSynchronousContinuations = true, + SingleReader = true }); private bool _recoveryComplete = false; private int _queueCount; private bool _disposed; - private readonly IProcessingStats _stats; + private readonly ProcessingStats _stats; private CancellationTokenSource? _loopCancellationSource; private Task? _recoveryTask; @@ -91,15 +89,13 @@ public sealed class BlockchainProcessor : IBlockchainProcessor, IBlockProcessing /// /// /// - /// public BlockchainProcessor( IBlockTree blockTree, IBranchProcessor branchProcessor, IBlockPreprocessorStep recoveryStep, IStateReader stateReader, ILogManager logManager, - Options options, - IProcessingStats processingStats) + Options options) { _logger = logManager.GetClassLogger(); _blockTree = blockTree; @@ -108,7 +104,7 @@ public BlockchainProcessor( _stateReader = stateReader; _options = options; - _stats = processingStats; + _stats = new ProcessingStats(stateReader, logManager.GetClassLogger()); _loopCancellationSource = new CancellationTokenSource(); _stats.NewProcessingStatistics += OnNewProcessingStatistics; } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessingScope.cs b/src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessingScope.cs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessorSource.cs b/src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessorSource.cs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IProcessingStats.cs b/src/Nethermind/Nethermind.Consensus/Processing/IProcessingStats.cs deleted file mode 100644 index 7d7b65723e69..000000000000 --- a/src/Nethermind/Nethermind.Consensus/Processing/IProcessingStats.cs +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Core; - -namespace Nethermind.Consensus.Processing; - -/// -/// Interface for processing statistics tracking during block processing. -/// -public interface IProcessingStats -{ - /// - /// Event fired when new processing statistics are available. - /// - event EventHandler? NewProcessingStatistics; - - /// - /// Start the statistics timer. - /// - void Start(); - - /// - /// Capture the starting values of metrics before processing begins. - /// - void CaptureStartStats(); - - /// - /// Update statistics after a block has been processed. - /// - /// The processed block. - /// The parent block header. - /// Processing time in microseconds. - void UpdateStats(Block? block, BlockHeader? baseBlock, long blockProcessingTimeInMicros); -} diff --git a/src/Nethermind/Nethermind.Consensus/Processing/OverridableTxProcessingScope.cs b/src/Nethermind/Nethermind.Consensus/Processing/OverridableTxProcessingScope.cs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs index 0cd19058da01..f2246ed3e733 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs @@ -19,27 +19,27 @@ namespace Nethermind.Consensus.Processing { public class BlockStatistics { - public long BlockCount { get; set; } - public long BlockFrom { get; set; } - public long BlockTo { get; set; } - public double ProcessingMs { get; set; } - public double SlotMs { get; set; } + public long BlockCount { get; internal set; } + public long BlockFrom { get; internal set; } + public long BlockTo { get; internal set; } + public double ProcessingMs { get; internal set; } + public double SlotMs { get; internal set; } [JsonPropertyName("mgasPerSecond")] - public double MGasPerSecond { get; set; } - public float MinGas { get; set; } - public float MedianGas { get; set; } - public float AveGas { get; set; } - public float MaxGas { get; set; } - public long GasLimit { get; set; } + public double MGasPerSecond { get; internal set; } + public float MinGas { get; internal set; } + public float MedianGas { get; internal set; } + public float AveGas { get; internal set; } + public float MaxGas { get; internal set; } + public long GasLimit { get; internal set; } } //TODO Consult on disabling of such metrics from configuration - public class ProcessingStats : IProcessingStats + internal class ProcessingStats { private static readonly DefaultObjectPool _dataPool = new(new BlockDataPolicy(), 16); private readonly Action _executeFromThreadPool; public event EventHandler? NewProcessingStatistics; - protected readonly IStateReader _stateReader; - protected readonly ILogger _logger; + private readonly IStateReader _stateReader; + private readonly ILogger _logger; private readonly Stopwatch _runStopwatch = new(); private bool _showBlobs; @@ -69,12 +69,12 @@ public class ProcessingStats : IProcessingStats private long _contractsAnalyzed; private long _cachedContractsUsed; - public ProcessingStats(IStateReader stateReader, ILogManager logManager) + public ProcessingStats(IStateReader stateReader, ILogger logger) { _executeFromThreadPool = ExecuteFromThreadPool; _stateReader = stateReader; - _logger = logManager.GetClassLogger(); + _logger = logger; // the line below just to avoid compilation errors if (_logger.IsTrace) _logger.Trace($"Processing Stats in debug mode?: {_logger.IsDebug}"); @@ -151,7 +151,7 @@ void ExecuteFromThreadPool(BlockData data) } } - protected virtual void GenerateReport(BlockData data) + private void GenerateReport(BlockData data) { const long weiToEth = 1_000_000_000_000_000_000; const string resetColor = "\u001b[37m"; @@ -443,7 +443,7 @@ public bool Return(BlockData data) } } - protected class BlockData + private class BlockData { public Block Block; public BlockHeader? BaseBlock; diff --git a/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs b/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs index 27ed452d518a..c138491985f4 100644 --- a/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs +++ b/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs @@ -34,6 +34,7 @@ public class BackgroundTaskScheduler : IBackgroundTaskScheduler, IAsyncDisposabl private readonly CancellationTokenSource _mainCancellationTokenSource; private readonly Channel _taskQueue; private readonly BelowNormalPriorityTaskScheduler _scheduler; + private readonly ManualResetEventSlim _restartQueueSignal; private readonly Task[] _tasksExecutors; private readonly ILogger _logger; private readonly IBranchProcessor _branchProcessor; @@ -42,7 +43,6 @@ public class BackgroundTaskScheduler : IBackgroundTaskScheduler, IAsyncDisposabl private long _queueCount; private CancellationTokenSource _blockProcessorCancellationTokenSource; - private volatile TaskCompletionSource? _blockProcessingDoneSignal; private bool _disposed = false; public BackgroundTaskScheduler(IBranchProcessor branchProcessor, IChainHeadInfoProvider headInfo, int concurrency, int capacity, ILogManager logManager) @@ -65,6 +65,7 @@ public BackgroundTaskScheduler(IBranchProcessor branchProcessor, IChainHeadInfoP _logger = logManager.GetClassLogger(); _branchProcessor = branchProcessor; _headInfo = headInfo; + _restartQueueSignal = new ManualResetEventSlim(initialState: true); _capacity = capacity; _branchProcessor.BlocksProcessing += BranchProcessorOnBranchesProcessing; @@ -73,6 +74,7 @@ public BackgroundTaskScheduler(IBranchProcessor branchProcessor, IChainHeadInfoP // TaskScheduler to run tasks at BelowNormal priority _scheduler = new BelowNormalPriorityTaskScheduler( concurrency, + _restartQueueSignal, logManager, _mainCancellationTokenSource.Token); @@ -86,9 +88,8 @@ private void BranchProcessorOnBranchesProcessing(object? sender, BlocksProcessin // as there are potentially no gaps between blocks if (!_headInfo.IsSyncing) { - // Signal that block processing is in progress so the Throttle path in StartChannel - // can async-wait instead of busy-polling - _blockProcessingDoneSignal = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + // Reset the background queue processing signal, causing it to wait + _restartQueueSignal.Reset(); // On block processing, we cancel the block process cts, causing the current task to get canceled. _blockProcessorCancellationTokenSource.Cancel(); } @@ -101,8 +102,8 @@ private void BranchProcessorOnBranchProcessed(object? sender, BlockProcessedEven ref _blockProcessorCancellationTokenSource, new CancellationTokenSource()); - // Signal that block processing is done so the Throttle path can resume - Interlocked.Exchange(ref _blockProcessingDoneSignal, null)?.TrySetResult(); + // We also set a queue signal causing it to continue processing the task. + _restartQueueSignal.Set(); } private async Task StartChannel() @@ -156,16 +157,7 @@ private async Task StartChannel() continue; Throttle: - // Wait for block processing to complete, with periodic wake-ups to drain newly expired tasks - TaskCompletionSource? signal = _blockProcessingDoneSignal; - if (signal is not null) - { - await Task.WhenAny(signal.Task, Task.Delay(millisecondsDelay: 1)); - } - else - { - await Task.Delay(millisecondsDelay: 1); - } + await Task.Delay(millisecondsDelay: 1); } } @@ -258,15 +250,17 @@ private sealed class BelowNormalPriorityTaskScheduler : TaskScheduler, IDisposab { private readonly BlockingCollection _tasks = []; private readonly Thread[] workerThreads; + private readonly ManualResetEventSlim _restartQueueSignal; private readonly int _maxDegreeOfParallelism; private readonly ILogger _logger; private readonly CancellationToken _cancellationToken; - public BelowNormalPriorityTaskScheduler(int maxDegreeOfParallelism, ILogManager logManager, CancellationToken cancellationToken) + public BelowNormalPriorityTaskScheduler(int maxDegreeOfParallelism, ManualResetEventSlim restartQueueSignal, ILogManager logManager, CancellationToken cancellationToken) { ArgumentOutOfRangeException.ThrowIfLessThan(maxDegreeOfParallelism, 1); _logger = logManager.GetClassLogger(); + _restartQueueSignal = restartQueueSignal; _maxDegreeOfParallelism = maxDegreeOfParallelism; _cancellationToken = cancellationToken; workerThreads = [.. Enumerable.Range(0, maxDegreeOfParallelism) @@ -289,6 +283,8 @@ private void ProcessBackgroundTasks(object _) { foreach (Task task in _tasks.GetConsumingEnumerable(_cancellationToken)) { + // Wait if processing blocks + _restartQueueSignal.Wait(_cancellationToken); try { TryExecuteTask(task); diff --git a/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs b/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs index b2bce702e101..83833ef5631e 100644 --- a/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs +++ b/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs @@ -179,14 +179,12 @@ public IEnumerable TraceBadBlockToFile(Hash256 blockHash, GethTraceOptio // // Wild stuff! BlockHeader baseBlockHeader = block.Header; - if ((processingOptions & ProcessingOptions.ForceSameBlock) == 0) { baseBlockHeader = FindParent(block); } - options.BlockOverrides?.ApplyOverrides(block.Header); - using Scope scope = blockProcessingEnv.BuildAndOverride(baseBlockHeader, options.StateOverrides); + using var scope = blockProcessingEnv.BuildAndOverride(baseBlockHeader, options.StateOverrides); IBlockTracer tracer = CreateOptionsTracer(block.Header, options with { TxHash = txHash }, scope.Component.WorldState, specProvider); try diff --git a/src/Nethermind/Nethermind.Core.Test/BytesTests.cs b/src/Nethermind/Nethermind.Core.Test/BytesTests.cs index ed7ccc0c2ac4..f3371d834c1c 100644 --- a/src/Nethermind/Nethermind.Core.Test/BytesTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/BytesTests.cs @@ -19,11 +19,11 @@ public class BytesTests { [TestCase("0x", "0x", 0)] [TestCase(null, null, 0)] - [TestCase(null, "0x", -1)] - [TestCase("0x", null, 1)] + [TestCase(null, "0x", 1)] + [TestCase("0x", null, -1)] [TestCase("0x01", "0x01", 0)] - [TestCase("0x01", "0x0102", -1)] - [TestCase("0x0102", "0x01", 1)] + [TestCase("0x01", "0x0102", 1)] + [TestCase("0x0102", "0x01", -1)] public void Compares_bytes_properly(string? hexString1, string? hexString2, int expectedResult) { IComparer comparer = Bytes.Comparer; @@ -467,139 +467,5 @@ public void NullableComparison() { Bytes.NullableEqualityComparer.Equals(null, null).Should().BeTrue(); } - - [Test] - public void FastHash_EmptyInput_ReturnsZero() - { - ReadOnlySpan empty = ReadOnlySpan.Empty; - empty.FastHash().Should().Be(0); - } - - [Test] - public void FastHash_SameInput_ReturnsSameHash() - { - byte[] input = new byte[100]; - TestContext.CurrentContext.Random.NextBytes(input); - - int hash1 = ((ReadOnlySpan)input).FastHash(); - int hash2 = ((ReadOnlySpan)input).FastHash(); - - hash1.Should().Be(hash2); - } - - [Test] - public void FastHash_DifferentInput_ReturnsDifferentHash() - { - byte[] input1 = new byte[100]; - byte[] input2 = new byte[100]; - TestContext.CurrentContext.Random.NextBytes(input1); - Array.Copy(input1, input2, input1.Length); - input2[50] ^= 0xFF; // Flip bits at position 50 - - int hash1 = ((ReadOnlySpan)input1).FastHash(); - int hash2 = ((ReadOnlySpan)input2).FastHash(); - - hash1.Should().NotBe(hash2); - } - - // Test cases for the fold-back bug fix: remaining in [49-63] after 64-byte initial load - // For len=113 to 127, remaining = len-64 = 49 to 63, which requires the last64 fold-back - [TestCase(113)] // remaining=49, boundary case for last64 - [TestCase(120)] // remaining=56, middle of the gap range - [TestCase(127)] // remaining=63, upper boundary - [TestCase(65)] // remaining=1, lower boundary for >64 path - [TestCase(80)] // remaining=16 - [TestCase(96)] // remaining=32 - [TestCase(112)] // remaining=48, boundary where last64 is NOT needed - public void FastHash_AllBytesAreHashed_FoldBackCoverage(int length) - { - byte[] input = new byte[length]; - TestContext.CurrentContext.Random.NextBytes(input); - - int originalHash = ((ReadOnlySpan)input).FastHash(); - - // Verify that changing any byte changes the hash - // This catches the gap bug where bytes[64-71] weren't being hashed - for (int i = 0; i < length; i++) - { - byte[] modified = (byte[])input.Clone(); - modified[i] ^= 0xFF; - - int modifiedHash = ((ReadOnlySpan)modified).FastHash(); - modifiedHash.Should().NotBe(originalHash, $"Changing byte at index {i} should change the hash for length {length}"); - } - } - - // Specifically test the gap range that was buggy: bytes[64-71] for len=120 - [Test] - public void FastHash_GapBytesAreHashed_Len120() - { - byte[] input = new byte[120]; - TestContext.CurrentContext.Random.NextBytes(input); - - int originalHash = ((ReadOnlySpan)input).FastHash(); - - // The bug was that bytes[64-71] weren't hashed for len=120 - // Test each byte in the gap - for (int i = 64; i < 72; i++) - { - byte[] modified = (byte[])input.Clone(); - modified[i] ^= 0xFF; - - int modifiedHash = ((ReadOnlySpan)modified).FastHash(); - modifiedHash.Should().NotBe(originalHash, $"Changing byte at index {i} (in gap range) should change the hash"); - } - } - - // Test medium-large case (33-64 bytes) with overlap to verify it works - [TestCase(50)] // Tests overlap in medium-large path - public void FastHash_MediumLarge_AllBytesContribute(int length) - { - byte[] input = new byte[length]; - TestContext.CurrentContext.Random.NextBytes(input); - - int originalHash = ((ReadOnlySpan)input).FastHash(); - - // Test ALL bytes to verify overlap handling works - for (int i = 0; i < length; i++) - { - byte[] modified = (byte[])input.Clone(); - modified[i] ^= 0xFF; - - int modifiedHash = ((ReadOnlySpan)modified).FastHash(); - modifiedHash.Should().NotBe(originalHash, $"Changing byte at index {i} should change the hash for length {length}"); - } - } - - [TestCase(1)] - [TestCase(7)] - [TestCase(8)] - [TestCase(15)] - [TestCase(16)] - [TestCase(31)] - [TestCase(32)] - [TestCase(33)] - [TestCase(64)] - [TestCase(128)] - [TestCase(256)] - [TestCase(500)] - public void FastHash_VariousLengths_AllBytesContribute(int length) - { - byte[] input = new byte[length]; - TestContext.CurrentContext.Random.NextBytes(input); - - int originalHash = ((ReadOnlySpan)input).FastHash(); - - // Test first, middle, and last bytes to ensure all contribute - int[] indicesToTest = [0, length / 2, length - 1]; - foreach (int i in indicesToTest) - { - byte[] modified = (byte[])input.Clone(); - modified[i] ^= 0xFF; - - int modifiedHash = ((ReadOnlySpan)modified).FastHash(); - modifiedHash.Should().NotBe(originalHash, $"Changing byte at index {i} should change the hash for length {length}"); - } - } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Collections/SeqlockCacheTests.cs b/src/Nethermind/Nethermind.Core.Test/Collections/SeqlockCacheTests.cs deleted file mode 100644 index ee076970d44b..000000000000 --- a/src/Nethermind/Nethermind.Core.Test/Collections/SeqlockCacheTests.cs +++ /dev/null @@ -1,428 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Nethermind.Core.Collections; -using Nethermind.Int256; -using NUnit.Framework; - -namespace Nethermind.Core.Test.Collections; - -public class SeqlockCacheTests -{ - private static StorageCell CreateKey(int seed) - { - byte[] addressBytes = new byte[20]; - new Random(seed).NextBytes(addressBytes); - return new StorageCell(new Address(addressBytes), new UInt256((ulong)seed)); - } - - private static byte[] CreateValue(int seed) - { - byte[] value = new byte[32]; - new Random(seed).NextBytes(value); - return value; - } - - [Test] - public void New_cache_returns_miss() - { - SeqlockCache cache = new(); - StorageCell key = CreateKey(1); - - bool found = cache.TryGetValue(in key, out byte[]? value); - - found.Should().BeFalse(); - value.Should().BeNull(); - } - - [Test] - public void Set_then_get_returns_value() - { - SeqlockCache cache = new(); - StorageCell key = CreateKey(1); - byte[] expected = CreateValue(1); - - cache.Set(in key, expected); - bool found = cache.TryGetValue(in key, out byte[]? value); - - found.Should().BeTrue(); - value.Should().BeSameAs(expected); - } - - [Test] - public void Set_overwrites_existing_value() - { - SeqlockCache cache = new(); - StorageCell key = CreateKey(1); - byte[] first = CreateValue(1); - byte[] second = CreateValue(2); - - cache.Set(in key, first); - cache.Set(in key, second); - bool found = cache.TryGetValue(in key, out byte[]? value); - - found.Should().BeTrue(); - value.Should().BeSameAs(second); - } - - [Test] - public void Set_with_same_value_is_noop() - { - SeqlockCache cache = new(); - StorageCell key = CreateKey(1); - byte[] expected = CreateValue(1); - - cache.Set(in key, expected); - cache.Set(in key, expected); // Same reference - should be fast-path no-op - bool found = cache.TryGetValue(in key, out byte[]? value); - - found.Should().BeTrue(); - value.Should().BeSameAs(expected); - } - - [Test] - public void Null_value_can_be_stored_and_retrieved() - { - SeqlockCache cache = new(); - StorageCell key = CreateKey(1); - - cache.Set(in key, null); - bool found = cache.TryGetValue(in key, out byte[]? value); - - found.Should().BeTrue(); - value.Should().BeNull(); - } - - [Test] - public void GetOrAdd_returns_existing_value() - { - SeqlockCache cache = new(); - StorageCell key = CreateKey(1); - byte[] expected = CreateValue(1); - - cache.Set(in key, expected); - byte[]? result = cache.GetOrAdd(in key, static (in StorageCell _) => new byte[32]); - - result.Should().BeSameAs(expected); - } - - [Test] - public void GetOrAdd_calls_factory_on_miss() - { - SeqlockCache cache = new(); - StorageCell key = CreateKey(1); - byte[] factoryResult = CreateValue(1); - - byte[]? result = cache.GetOrAdd(in key, (in StorageCell _) => factoryResult); - - result.Should().BeSameAs(factoryResult); - - // Value should now be cached - bool found = cache.TryGetValue(in key, out byte[]? cached); - found.Should().BeTrue(); - cached.Should().BeSameAs(factoryResult); - } - - [Test] - public void GetOrAdd_with_func_returns_existing_value() - { - SeqlockCache cache = new(); - StorageCell key = CreateKey(1); - byte[] expected = CreateValue(1); - - cache.Set(in key, expected); - byte[]? result = cache.GetOrAdd(in key, static (in _) => new byte[32]); - - result.Should().BeSameAs(expected); - } - - [Test] - public void GetOrAdd_with_func_calls_factory_on_miss() - { - SeqlockCache cache = new(); - StorageCell key = CreateKey(1); - byte[] factoryResult = CreateValue(1); - - byte[]? result = cache.GetOrAdd(in key, (in _) => factoryResult); - - result.Should().BeSameAs(factoryResult); - } - - [Test] - public void Clear_invalidates_all_entries() - { - SeqlockCache cache = new(); - StorageCell key1 = CreateKey(1); - StorageCell key2 = CreateKey(2); - - cache.Set(in key1, CreateValue(1)); - cache.Set(in key2, CreateValue(2)); - - cache.Clear(); - - cache.TryGetValue(in key1, out _).Should().BeFalse(); - cache.TryGetValue(in key2, out _).Should().BeFalse(); - } - - [Test] - public void Clear_allows_new_entries() - { - SeqlockCache cache = new(); - StorageCell key = CreateKey(1); - byte[] beforeClear = CreateValue(1); - byte[] afterClear = CreateValue(2); - - cache.Set(in key, beforeClear); - cache.Clear(); - cache.Set(in key, afterClear); - - bool found = cache.TryGetValue(in key, out byte[]? value); - found.Should().BeTrue(); - value.Should().BeSameAs(afterClear); - } - - [Test] - public void Multiple_clears_work() - { - SeqlockCache cache = new(); - StorageCell key = CreateKey(1); - - for (int i = 0; i < 100; i++) - { - byte[] value = CreateValue(i); - cache.Set(in key, value); - cache.TryGetValue(in key, out byte[]? retrieved).Should().BeTrue(); - retrieved.Should().BeSameAs(value); - cache.Clear(); - cache.TryGetValue(in key, out _).Should().BeFalse(); - } - } - - [Test] - public void Different_keys_can_be_stored() - { - SeqlockCache cache = new(); - const int count = 100; - - StorageCell[] keys = new StorageCell[count]; - byte[][] values = new byte[count][]; - - for (int i = 0; i < count; i++) - { - keys[i] = CreateKey(i); - values[i] = CreateValue(i); - cache.Set(in keys[i], values[i]); - } - - // Note: This is a direct-mapped cache, so some entries may be evicted - // due to hash collisions. We just verify that at least some survive. - int hits = 0; - for (int i = 0; i < count; i++) - { - if (cache.TryGetValue(in keys[i], out byte[]? value) && ReferenceEquals(value, values[i])) - { - hits++; - } - } - - hits.Should().BeGreaterThan(0, "at least some entries should survive"); - } - - [Test] - public void Concurrent_reads_are_safe() - { - SeqlockCache cache = new(); - StorageCell key = CreateKey(1); - byte[] expected = CreateValue(1); - cache.Set(in key, expected); - - const int threadCount = 8; - const int iterations = 10000; - int successCount = 0; - - Parallel.For(0, threadCount, _ => - { - for (int i = 0; i < iterations; i++) - { - if (cache.TryGetValue(in key, out byte[]? value) && ReferenceEquals(value, expected)) - { - Interlocked.Increment(ref successCount); - } - } - }); - - successCount.Should().Be(threadCount * iterations); - } - - [Test] - public void Concurrent_writes_do_not_corrupt() - { - SeqlockCache cache = new(); - StorageCell key = CreateKey(1); - - const int threadCount = 8; - const int iterations = 1000; - byte[][] values = new byte[threadCount][]; - for (int i = 0; i < threadCount; i++) - { - values[i] = CreateValue(i); - } - - Parallel.For(0, threadCount, t => - { - for (int i = 0; i < iterations; i++) - { - cache.Set(in key, values[t]); - } - }); - - // After concurrent writes, the cache should contain one of the values - bool found = cache.TryGetValue(in key, out byte[]? result); - if (found) - { - // Value should be one of the values we wrote - bool isValid = false; - for (int i = 0; i < threadCount; i++) - { - if (ReferenceEquals(result, values[i])) - { - isValid = true; - break; - } - } - isValid.Should().BeTrue("cached value should be one of the written values"); - } - } - - [Test] - public void Concurrent_read_write_is_safe() - { - SeqlockCache cache = new(); - StorageCell key = CreateKey(1); - byte[] value1 = CreateValue(1); - byte[] value2 = CreateValue(2); - - const int iterations = 10000; - bool stop = false; - - // Writer thread - Task writer = Task.Run(() => - { - for (int i = 0; i < iterations && !stop; i++) - { - cache.Set(in key, i % 2 == 0 ? value1 : value2); - } - }); - - // Reader thread - int validReads = 0; - int misses = 0; - Task reader = Task.Run(() => - { - for (int i = 0; i < iterations; i++) - { - if (cache.TryGetValue(in key, out byte[]? value)) - { - // Value should be either value1 or value2 - if (ReferenceEquals(value, value1) || ReferenceEquals(value, value2)) - { - Interlocked.Increment(ref validReads); - } - } - else - { - Interlocked.Increment(ref misses); - } - } - }); - - Task.WaitAll(writer, reader); - stop = true; - - // All reads should have returned valid values (or miss due to concurrent write) - (validReads + misses).Should().Be(iterations); - } - - [Test] - public void AddressAsKey_works_with_cache() - { - SeqlockCache cache = new(); - Address address = new Address("0x1234567890123456789012345678901234567890"); - AddressAsKey key = address; - Account account = new Account(100, 1); - - cache.Set(in key, account); - bool found = cache.TryGetValue(in key, out Account? result); - - found.Should().BeTrue(); - result.Should().BeSameAs(account); - } - - [Test] - public void Concurrent_set_same_value_fast_path_is_safe() - { - // Tests the fast-path optimization where Set skips write if value matches. - // This exercises the seqlock protocol in the fast-path to avoid torn reads. - SeqlockCache cache = new(); - StorageCell key = CreateKey(1); - byte[] value = CreateValue(1); - - cache.Set(in key, value); - - const int threadCount = 8; - const int iterations = 10000; - - // Multiple threads all trying to set the same key to the same value - // This hammers the fast-path check - Parallel.For(0, threadCount, _ => - { - for (int i = 0; i < iterations; i++) - { - cache.Set(in key, value); - } - }); - - // Value should still be retrievable and correct - bool found = cache.TryGetValue(in key, out byte[]? result); - found.Should().BeTrue(); - result.Should().BeSameAs(value); - } - - [Test] - public void Concurrent_set_alternating_values_is_safe() - { - // Tests concurrent writes with different values interleaved with same-value writes. - // Exercises both the fast-path (same value) and slow-path (different value). - SeqlockCache cache = new(); - StorageCell key = CreateKey(1); - byte[][] values = new byte[4][]; - for (int i = 0; i < values.Length; i++) - { - values[i] = CreateValue(i); - } - - const int threadCount = 8; - const int iterations = 5000; - - Parallel.For(0, threadCount, t => - { - for (int i = 0; i < iterations; i++) - { - // Each thread cycles through values, creating both fast-path and slow-path scenarios - cache.Set(in key, values[(t + i) % values.Length]); - } - }); - - // After all writes, cache should contain one of the valid values - bool found = cache.TryGetValue(in key, out byte[]? result); - if (found) - { - bool isValid = Array.Exists(values, v => ReferenceEquals(v, result)); - isValid.Should().BeTrue("cached value should be one of the written values"); - } - } -} diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs index 2f4575e7c705..bd8b19db914f 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs @@ -2,9 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; @@ -18,9 +16,9 @@ namespace Nethermind.Core.Test.Encoding; public class BlockDecoderTests { - private static readonly Block[] _scenarios = BuildScenarios(); + private readonly Block[] _scenarios; - private static Block[] BuildScenarios() + public BlockDecoderTests() { var transactions = new Transaction[100]; for (int i = 0; i < transactions.Length; i++) @@ -42,8 +40,8 @@ private static Block[] BuildScenarios() .TestObject; } - return - [ + _scenarios = new[] + { Build.A.Block.WithNumber(1).TestObject, Build.A.Block .WithNumber(1) @@ -97,14 +95,9 @@ private static Block[] BuildScenarios() .WithExcessBlobGas(ulong.MaxValue) .WithMixHash(Keccak.EmptyTreeHash) .TestObject - ]; + }; } - private static IEnumerable BlockScenarios() => _scenarios; - - private static IEnumerable BlockScenariosWithTxs() => - _scenarios.Where(static b => b.Transactions.Length > 0); - [Test] public void Can_do_roundtrip_null([Values(true, false)] bool valueDecoder) { @@ -129,16 +122,17 @@ public void Can_do_roundtrip_regression([Values(true, false)] bool valueDecoder) } [Test] - public void Can_do_roundtrip_scenarios( - [ValueSource(nameof(BlockScenarios))] Block block, - [Values(true, false)] bool valueDecoder) + public void Can_do_roundtrip_scenarios([Values(true, false)] bool valueDecoder) { BlockDecoder decoder = new(); - Rlp encoded = decoder.Encode(block); - Rlp.ValueDecoderContext valueDecoderContext = new(encoded.Bytes); - Block? decoded = valueDecoder ? decoder.Decode(ref valueDecoderContext) : decoder.Decode(new RlpStream(encoded.Bytes)); - Rlp encoded2 = decoder.Encode(decoded); - Assert.That(encoded2.Bytes.ToHexString(), Is.EqualTo(encoded.Bytes.ToHexString())); + foreach (Block block in _scenarios) + { + Rlp encoded = decoder.Encode(block); + Rlp.ValueDecoderContext valueDecoderContext = new(encoded.Bytes); + Block? decoded = valueDecoder ? decoder.Decode(ref valueDecoderContext) : decoder.Decode(new RlpStream(encoded.Bytes)); + Rlp encoded2 = decoder.Encode(decoded); + Assert.That(encoded2.Bytes.ToHexString(), Is.EqualTo(encoded.Bytes.ToHexString())); + } } [TestCase("0xf902cef9025ba055870e2f3ef77a9e6163ee5c005dc51d648a2eead382b9044b1a5ad2ee69b0c6a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0b77e3b74c6c8af85408677375183385a2e55446bd071bf193a4958f7417dc8fba056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800188016345785d8a0000800c80a0000000000000000000000000000000000000000000000000000000000000000088000000000000000007a0cc3b10b54dc4e97c01f1df20e8b95874cd5fe83bf6eae64935a16cb08db85fa98080a00000000000000000000000000000000000000000000000000000000000000000a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855c0c0f86ce08080946389e7f33ce3b1e94e4325ef02829cd12297ef7188ffffffffffffffffd80180948a0a19589531694250d570040a0c4b74576919b801d8028094000000000000000000000000000000000000100080d8038094a94f5374fce5edbc8e2a8697c15331677e6ebf0b80")] @@ -149,60 +143,6 @@ public void Write_rlp_of_blocks_to_file(string rlp) File.WriteAllBytes("chains\\block1.rlp".GetApplicationResourcePath(), Bytes.FromHexString(rlp)); } - [Test] - public void Encode_with_pre_encoded_transactions_produces_same_rlp( - [ValueSource(nameof(BlockScenariosWithTxs))] Block block) - { - byte[][] encodedTxs = new byte[block.Transactions.Length][]; - for (int i = 0; i < block.Transactions.Length; i++) - { - encodedTxs[i] = Rlp.Encode(block.Transactions[i], RlpBehaviors.SkipTypedWrapping).Bytes; - } - - BlockDecoder decoder = new(); - Rlp standard = decoder.Encode(block); - - Block blockWithEncoded = new(block.Header, block.Body) { EncodedTransactions = encodedTxs }; - Rlp fast = decoder.Encode(blockWithEncoded); - - Assert.That(fast.Bytes.ToHexString(), Is.EqualTo(standard.Bytes.ToHexString())); - } - - [Test] - public void Encode_with_pre_encoded_typed_transactions_produces_same_rlp() - { - Transaction[] transactions = - [ - Build.A.Transaction.WithNonce(1).WithType(TxType.Legacy).Signed().TestObject, - Build.A.Transaction.WithNonce(2).WithType(TxType.AccessList).Signed().TestObject, - Build.A.Transaction.WithNonce(3).WithType(TxType.EIP1559).Signed().TestObject, - ]; - - Block block = Build.A.Block - .WithNumber(1) - .WithBaseFeePerGas(1) - .WithTransactions(transactions) - .WithWithdrawals(2) - .WithBlobGasUsed(0) - .WithExcessBlobGas(0) - .WithMixHash(Keccak.EmptyTreeHash) - .TestObject; - - byte[][] encodedTxs = new byte[transactions.Length][]; - for (int i = 0; i < transactions.Length; i++) - { - encodedTxs[i] = Rlp.Encode(transactions[i], RlpBehaviors.SkipTypedWrapping).Bytes; - } - - BlockDecoder decoder = new(); - Rlp standard = decoder.Encode(block); - - Block blockWithEncoded = new(block.Header, block.Body) { EncodedTransactions = encodedTxs }; - Rlp fast = decoder.Encode(blockWithEncoded); - - Assert.That(fast.Bytes.ToHexString(), Is.EqualTo(standard.Bytes.ToHexString())); - } - [Test] public void Get_length_null() { diff --git a/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs index 757d490234c2..5888c30c65e1 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs @@ -296,71 +296,6 @@ public void Fuzz_RandomHex_SegmentationInvariant() } } - [TestCase(new byte[] { 0xab, 0xcd }, true, true, "\"0xabcd\"")] - [TestCase(new byte[] { 0xab, 0xcd }, false, true, "\"0xabcd\"")] - [TestCase(new byte[] { 0x00, 0xab }, true, true, "\"0xab\"")] - [TestCase(new byte[] { 0x00, 0xab }, false, true, "\"0x00ab\"")] - [TestCase(new byte[] { 0x00, 0x00 }, true, true, "\"0x0\"")] - [TestCase(new byte[] { 0x00, 0x00 }, false, true, "\"0x0000\"")] - [TestCase(new byte[] { 0xab }, true, false, "\"ab\"")] - [TestCase(new byte[] { 0xab }, false, false, "\"ab\"")] - [TestCase(new byte[] { 0x0a }, true, true, "\"0xa\"")] - [TestCase(new byte[] { 0x0a }, false, true, "\"0x0a\"")] - public void Write_OutputFormat(byte[] input, bool skipLeadingZeros, bool addHexPrefix, string expected) - { - using System.IO.MemoryStream ms = new(); - using Utf8JsonWriter writer = new(ms); - ByteArrayConverter.Convert(writer, input, skipLeadingZeros, addHexPrefix); - writer.Flush(); - Encoding.UTF8.GetString(ms.ToArray()).Should().Be(expected); - } - - [Test] - public void Write_LargeOutput_UsesArrayPool() - { - // 200 bytes = 400 hex chars + "0x" prefix + quotes > 256 byte InlineArray threshold - byte[] input = new byte[200]; - for (int i = 0; i < input.Length; i++) input[i] = (byte)(i & 0xFF); - - using System.IO.MemoryStream ms = new(); - using Utf8JsonWriter writer = new(ms); - ByteArrayConverter.Convert(writer, input, skipLeadingZeros: false); - writer.Flush(); - string output = Encoding.UTF8.GetString(ms.ToArray()); - output.Should().StartWith("\"0x"); - output.Should().EndWith("\""); - output.Length.Should().Be(404); // 400 hex + 2 prefix + 2 quotes - } - - [Test] - public void WriteAsPropertyName_Format() - { - ByteArrayConverter converter = new(); - using System.IO.MemoryStream ms = new(); - using Utf8JsonWriter writer = new(ms); - writer.WriteStartObject(); - converter.WriteAsPropertyName(writer, new byte[] { 0xab, 0xcd }, JsonSerializerOptions.Default); - writer.WriteNumberValue(1); - writer.WriteEndObject(); - writer.Flush(); - Encoding.UTF8.GetString(ms.ToArray()).Should().Be("{\"0xabcd\":1}"); - } - - [Test] - public void WriteAsPropertyName_AllZeros() - { - ByteArrayConverter converter = new(); - using System.IO.MemoryStream ms = new(); - using Utf8JsonWriter writer = new(ms); - writer.WriteStartObject(); - converter.WriteAsPropertyName(writer, new byte[] { 0x00, 0x00 }, JsonSerializerOptions.Default); - writer.WriteNumberValue(1); - writer.WriteEndObject(); - writer.Flush(); - // skipLeadingZeros: false preserves all zeros - Encoding.UTF8.GetString(ms.ToArray()).Should().Be("{\"0x0000\":1}"); - } - [Test] public void Test_DictionaryKey() { diff --git a/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs index cd6fe2466ebf..1e6afeffa5a5 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Text.Json; using Nethermind.Core.Crypto; @@ -23,67 +22,5 @@ public void Can_read_null() Hash256? result = JsonSerializer.Deserialize("null", options); Assert.That(result, Is.EqualTo(null)); } - - [Test] - public void Writes_zero_hash() - { - Hash256 hash = new(new byte[32]); - string result = JsonSerializer.Serialize(hash, options); - Assert.That(result, Is.EqualTo("\"0x0000000000000000000000000000000000000000000000000000000000000000\"")); - } - - [Test] - public void Writes_all_ones_hash() - { - byte[] bytes = new byte[32]; - Array.Fill(bytes, (byte)0xFF); - Hash256 hash = new(bytes); - string result = JsonSerializer.Serialize(hash, options); - Assert.That(result, Is.EqualTo("\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"")); - } - - [Test] - public void Writes_known_hash() - { - // Keccak256 of empty string - Hash256 hash = Keccak.OfAnEmptyString; - string result = JsonSerializer.Serialize(hash, options); - Assert.That(result, Is.EqualTo($"\"0x{hash.ToString(false)}\"")); - } - - [Test] - public void Writes_sequential_bytes() - { - byte[] bytes = new byte[32]; - for (int i = 0; i < 32; i++) bytes[i] = (byte)i; - Hash256 hash = new(bytes); - string result = JsonSerializer.Serialize(hash, options); - Assert.That(result, Is.EqualTo("\"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\"")); - } - - [Test] - public void Writes_roundtrip() - { - Hash256 hash = Keccak.Compute("test data"u8); - string json = JsonSerializer.Serialize(hash, options); - Hash256? deserialized = JsonSerializer.Deserialize(json, options); - Assert.That(deserialized, Is.EqualTo(hash)); - } - - [Test] - public void Writes_each_nibble_value() - { - // Ensure all hex chars 0-f appear correctly - byte[] bytes = new byte[32]; - for (int i = 0; i < 16; i++) - { - bytes[i * 2] = (byte)((i << 4) | i); // 0x00, 0x11, 0x22, ..., 0xff - bytes[i * 2 + 1] = (byte)((i << 4) | (15 - i)); // 0x0f, 0x1e, 0x2d, ... - } - Hash256 hash = new(bytes); - string result = JsonSerializer.Serialize(hash, options); - Hash256? roundtrip = JsonSerializer.Deserialize(result, options); - Assert.That(roundtrip, Is.EqualTo(hash)); - } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs index 413734f8a535..f4e8088d4da1 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs @@ -15,7 +15,6 @@ public class LongConverterTests : ConverterTestBase static readonly LongConverter converter = new(); static readonly JsonSerializerOptions options = new JsonSerializerOptions { Converters = { converter } }; - [Test] public void Test_roundtrip() { TestConverter(int.MaxValue, static (a, b) => a.Equals(b), converter); @@ -64,46 +63,5 @@ public void Throws_on_null() Assert.Throws( static () => JsonSerializer.Deserialize("null", options)); } - - [TestCase(0L, "\"0x0\"")] - [TestCase(1L, "\"0x1\"")] - [TestCase(15L, "\"0xf\"")] - [TestCase(16L, "\"0x10\"")] - [TestCase(255L, "\"0xff\"")] - [TestCase(256L, "\"0x100\"")] - [TestCase(0xabcdefL, "\"0xabcdef\"")] - [TestCase(0x1L, "\"0x1\"")] - [TestCase(0x10L, "\"0x10\"")] - [TestCase(0x100L, "\"0x100\"")] - [TestCase(0x1000L, "\"0x1000\"")] - [TestCase(0x10000L, "\"0x10000\"")] - [TestCase(0x100000L, "\"0x100000\"")] - [TestCase(0x1000000L, "\"0x1000000\"")] - [TestCase(0x10000000L, "\"0x10000000\"")] - [TestCase(int.MaxValue, "\"0x7fffffff\"")] - [TestCase(long.MaxValue, "\"0x7fffffffffffffff\"")] - [TestCase(-1L, "\"0xffffffffffffffff\"")] - [TestCase(-9223372036854775808L, "\"0x8000000000000000\"")] // long.MinValue - public void Writes_correct_hex(long value, string expected) - { - string result = JsonSerializer.Serialize(value, options); - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void Writes_hex_roundtrip_all_nibble_counts() - { - // Test every nibble count from 1 to 16 - for (int nibbles = 1; nibbles <= 16; nibbles++) - { - long value = nibbles <= 15 - ? 1L << ((nibbles - 1) * 4) - : unchecked((long)0x8000000000000000UL); - - string json = JsonSerializer.Serialize(value, options); - long deserialized = JsonSerializer.Deserialize(json, options); - Assert.That(deserialized, Is.EqualTo(value), $"Roundtrip failed for nibbles={nibbles}, value=0x{(ulong)value:x}"); - } - } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs index fdcd1e24d186..1d1cd86a8334 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs @@ -72,32 +72,5 @@ public void Throws_on_negative_numbers() Assert.Throws( static () => JsonSerializer.Deserialize("-1", options)); } - - [TestCase(0UL, "\"0x0\"")] - [TestCase(1UL, "\"0x1\"")] - [TestCase(15UL, "\"0xf\"")] - [TestCase(16UL, "\"0x10\"")] - [TestCase(255UL, "\"0xff\"")] - [TestCase(0xabcdefUL, "\"0xabcdef\"")] - [TestCase(0xffffffffUL, "\"0xffffffff\"")] - [TestCase(0x100000000UL, "\"0x100000000\"")] - [TestCase(ulong.MaxValue, "\"0xffffffffffffffff\"")] - public void Writes_correct_hex(ulong value, string expected) - { - string result = JsonSerializer.Serialize((ulong?)value, options); - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void Writes_hex_roundtrip_all_nibble_counts() - { - for (int nibbles = 1; nibbles <= 16; nibbles++) - { - ulong value = 1UL << ((nibbles - 1) * 4); - string json = JsonSerializer.Serialize((ulong?)value, options); - ulong? deserialized = JsonSerializer.Deserialize(json, options); - Assert.That(deserialized, Is.EqualTo(value), $"Roundtrip failed for nibbles={nibbles}, value=0x{value:x}"); - } - } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs index 25f4e9737c29..9bbe9f7e0e0b 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs @@ -152,140 +152,5 @@ public void Throws_on_null() Assert.Throws( static () => JsonSerializer.Deserialize("null", options)); } - - [TestCase(0ul, 0ul, 0ul, 0ul, "\"0x0\"")] - [TestCase(1ul, 0ul, 0ul, 0ul, "\"0x1\"")] - [TestCase(0xful, 0ul, 0ul, 0ul, "\"0xf\"")] - [TestCase(0xfful, 0ul, 0ul, 0ul, "\"0xff\"")] - [TestCase(0xabcdeful, 0ul, 0ul, 0ul, "\"0xabcdef\"")] - [TestCase(ulong.MaxValue, 0ul, 0ul, 0ul, "\"0xffffffffffffffff\"")] - [TestCase(ulong.MaxValue, 1ul, 0ul, 0ul, "\"0x1ffffffffffffffff\"")] - [TestCase(0ul, 0ul, 1ul, 0ul, "\"0x100000000000000000000000000000000\"")] - [TestCase(0ul, 0ul, 0ul, 1ul, "\"0x1000000000000000000000000000000000000000000000000\"")] - [TestCase(ulong.MaxValue, ulong.MaxValue, ulong.MaxValue, ulong.MaxValue, "\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"")] - public void Writes_hex(ulong u0, ulong u1, ulong u2, ulong u3, string expected) - { - UInt256 value = new(u0, u1, u2, u3); - string result = JsonSerializer.Serialize(value, options); - Assert.That(result, Is.EqualTo(expected)); - } - - [Test] - public void Writes_hex_roundtrip_all_limb_boundaries() - { - // Test values at each limb boundary - UInt256[] values = - [ - UInt256.One, - new UInt256(ulong.MaxValue), - new UInt256(0, 1), - new UInt256(ulong.MaxValue, ulong.MaxValue), - new UInt256(0, 0, 1, 0), - new UInt256(ulong.MaxValue, ulong.MaxValue, ulong.MaxValue, 0), - new UInt256(0, 0, 0, 1), - UInt256.MaxValue, - ]; - - foreach (UInt256 value in values) - { - string json = JsonSerializer.Serialize(value, options); - UInt256 deserialized = JsonSerializer.Deserialize(json, options); - Assert.That(deserialized, Is.EqualTo(value), $"Roundtrip failed for {value}"); - } - } - - [Test] - public void Writes_zero_padded_hex() - { - ForcedNumberConversion.ForcedConversion.Value = NumberConversion.ZeroPaddedHex; - try - { - string result = JsonSerializer.Serialize(UInt256.One, options); - Assert.That(result, Is.EqualTo("\"0x0000000000000000000000000000000000000000000000000000000000000001\"")); - - result = JsonSerializer.Serialize(UInt256.MaxValue, options); - Assert.That(result, Is.EqualTo("\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"")); - - result = JsonSerializer.Serialize(UInt256.Zero, options); - Assert.That(result, Is.EqualTo("\"0x0000000000000000000000000000000000000000000000000000000000000000\"")); - } - finally - { - ForcedNumberConversion.ForcedConversion.Value = NumberConversion.Hex; - } - } - - [Test] - public void Writes_zero_padded_hex_roundtrip() - { - ForcedNumberConversion.ForcedConversion.Value = NumberConversion.ZeroPaddedHex; - try - { - UInt256 value = new(0xdeadbeef, 0xcafebabe, 0x12345678, 0x9abcdef0); - string json = JsonSerializer.Serialize(value, options); - UInt256 deserialized = JsonSerializer.Deserialize(json, options); - Assert.That(deserialized, Is.EqualTo(value)); - } - finally - { - ForcedNumberConversion.ForcedConversion.Value = NumberConversion.Hex; - } - } - - [Test] - public void Writes_property_name_hex() - { - using var stream = new System.IO.MemoryStream(); - using var writer = new Utf8JsonWriter(stream); - - writer.WriteStartObject(); - converter.WriteAsPropertyName(writer, (UInt256)0xabcdef, options); - writer.WriteNumberValue(1); - writer.WriteEndObject(); - writer.Flush(); - - string result = System.Text.Encoding.UTF8.GetString(stream.ToArray()); - Assert.That(result, Is.EqualTo("{\"0xabcdef\":1}")); - } - - [Test] - public void Writes_property_name_zero() - { - using var stream = new System.IO.MemoryStream(); - using var writer = new Utf8JsonWriter(stream); - - writer.WriteStartObject(); - converter.WriteAsPropertyName(writer, UInt256.Zero, options); - writer.WriteNumberValue(1); - writer.WriteEndObject(); - writer.Flush(); - - string result = System.Text.Encoding.UTF8.GetString(stream.ToArray()); - Assert.That(result, Is.EqualTo("{\"0x0\":1}")); - } - - [Test] - public void Writes_property_name_zero_padded_hex() - { - ForcedNumberConversion.ForcedConversion.Value = NumberConversion.ZeroPaddedHex; - try - { - using var stream = new System.IO.MemoryStream(); - using var writer = new Utf8JsonWriter(stream); - - writer.WriteStartObject(); - converter.WriteAsPropertyName(writer, UInt256.One, options); - writer.WriteNumberValue(1); - writer.WriteEndObject(); - writer.Flush(); - - string result = System.Text.Encoding.UTF8.GetString(stream.ToArray()); - Assert.That(result, Is.EqualTo("{\"0x0000000000000000000000000000000000000000000000000000000000000001\":1}")); - } - finally - { - ForcedNumberConversion.ForcedConversion.Value = NumberConversion.Hex; - } - } } } diff --git a/src/Nethermind/Nethermind.Core.Test/MCSLockTests.cs b/src/Nethermind/Nethermind.Core.Test/MCSLockTests.cs index 16d4fac388eb..0dbf3e1ea56e 100644 --- a/src/Nethermind/Nethermind.Core.Test/MCSLockTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/MCSLockTests.cs @@ -59,7 +59,6 @@ public void MultipleThreads() } [Test] - [Retry(3)] public void LockFairnessTest() { int numberOfThreads = 10; diff --git a/src/Nethermind/Nethermind.Core.Test/RlpTests.cs b/src/Nethermind/Nethermind.Core.Test/RlpTests.cs index 01320fdf3d38..49e9a776687e 100644 --- a/src/Nethermind/Nethermind.Core.Test/RlpTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/RlpTests.cs @@ -246,16 +246,6 @@ public void Long_and_big_integer_encoded_the_same(long value) Assert.That(rlpBigInt.Bytes, Is.EqualTo(rlpLong.Bytes)); } - [Test] - public void Encode_generic_with_Rlp_input_preserves_original_bytes() - { - Rlp original = Rlp.Encode(255L); - Rlp reEncoded = Rlp.Encode(original); - - Assert.That(reEncoded.Bytes, Is.EqualTo(original.Bytes)); - Assert.That(reEncoded, Is.SameAs(original)); - } - [TestCase(true)] [TestCase(false)] public void RlpContextWithSliceMemory_shouldNotCopyUnderlyingData(bool sliceValue) diff --git a/src/Nethermind/Nethermind.Core.Test/TestMemDb.cs b/src/Nethermind/Nethermind.Core.Test/TestMemDb.cs index b174709b9662..169a166c58c7 100644 --- a/src/Nethermind/Nethermind.Core.Test/TestMemDb.cs +++ b/src/Nethermind/Nethermind.Core.Test/TestMemDb.cs @@ -23,15 +23,18 @@ public class TestMemDb : MemDb, ITunableDb public Func? ReadFunc { get; set; } public Func? WriteFunc { get; set; } + public Action? RemoveFunc { get; set; } public bool WasFlushed => FlushCount > 0; - public int FlushCount { get; private set; } + public int FlushCount { get; set; } = 0; [MethodImpl(MethodImplOptions.Synchronized)] public override byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) { _readKeys.Add((key.ToArray(), flags)); - return ReadFunc is not null ? ReadFunc(key.ToArray()) : base.Get(key, flags); + + if (ReadFunc is not null) return ReadFunc(key.ToArray()); + return base.Get(key, flags); } [MethodImpl(MethodImplOptions.Synchronized)] @@ -43,32 +46,71 @@ public override void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags base.Set(key, value, flags); } + public override Span GetSpan(ReadOnlySpan key) + { + return Get(key); + } + [MethodImpl(MethodImplOptions.Synchronized)] public override void Remove(ReadOnlySpan key) { _removedKeys.Add(key.ToArray()); + + if (RemoveFunc is not null) + { + RemoveFunc.Invoke(key.ToArray()); + return; + } base.Remove(key); } - public void Tune(ITunableDb.TuneType type) => _tuneTypes.Add(type); - public bool WasTunedWith(ITunableDb.TuneType type) => _tuneTypes.Contains(type); + public void Tune(ITunableDb.TuneType type) + { + _tuneTypes.Add(type); + } + + public bool WasTunedWith(ITunableDb.TuneType type) + { + return _tuneTypes.Contains(type); + } - public void KeyWasRead(byte[] key, int times = 1) => + public void KeyWasRead(byte[] key, int times = 1) + { _readKeys.Count(it => Bytes.AreEqual(it.Item1, key)).Should().Be(times); + } - public void KeyWasReadWithFlags(byte[] key, ReadFlags flags, int times = 1) => + public void KeyWasReadWithFlags(byte[] key, ReadFlags flags, int times = 1) + { _readKeys.Count(it => Bytes.AreEqual(it.Item1, key) && it.Item2 == flags).Should().Be(times); + } - public void KeyWasWritten(byte[] key, int times = 1) => + public void KeyWasWritten(byte[] key, int times = 1) + { _writes.Count(it => Bytes.AreEqual(it.Item1.Item1, key)).Should().Be(times); + } - public void KeyWasWritten(Func<(byte[], byte[]?), bool> cond, int times = 1) => + public void KeyWasWritten(Func<(byte[], byte[]?), bool> cond, int times = 1) + { _writes.Count(it => cond.Invoke(it.Item1)).Should().Be(times); + } - public void KeyWasWrittenWithFlags(byte[] key, WriteFlags flags, int times = 1) => + public void KeyWasWrittenWithFlags(byte[] key, WriteFlags flags, int times = 1) + { _writes.Count(it => Bytes.AreEqual(it.Item1.Item1, key) && it.Item2 == flags).Should().Be(times); + } + + public void KeyWasRemoved(Func cond, int times = 1) + { + _removedKeys.Count(cond).Should().Be(times); + } - public void KeyWasRemoved(Func cond, int times = 1) => _removedKeys.Count(cond).Should().Be(times); - public override IWriteBatch StartWriteBatch() => new InMemoryWriteBatch(this); - public override void Flush(bool onlyWal) => FlushCount++; + public override IWriteBatch StartWriteBatch() + { + return new InMemoryWriteBatch(this); + } + + public override void Flush(bool onlyWal) + { + FlushCount++; + } } diff --git a/src/Nethermind/Nethermind.Core/Address.cs b/src/Nethermind/Nethermind.Core/Address.cs index 80516f59c95c..0bad6dd83609 100644 --- a/src/Nethermind/Nethermind.Core/Address.cs +++ b/src/Nethermind/Nethermind.Core/Address.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Text.Json.Serialization; -using Nethermind.Core.Collections; + using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -20,7 +20,7 @@ namespace Nethermind.Core [JsonConverter(typeof(AddressConverter))] [TypeConverter(typeof(AddressTypeConverter))] [DebuggerDisplay("{ToString()}")] - public sealed class Address : IEquatable
, IComparable
+ public class Address : IEquatable
, IComparable
{ public const int Size = 20; private const int HexCharsCount = 2 * Size; // 5a4eab120fb44eb6684e5e32785702ff45ea344d @@ -273,11 +273,9 @@ public ValueHash256 ToHash() return result; } - - internal long GetHashCode64() => SpanExtensions.FastHash64For20Bytes(ref MemoryMarshal.GetArrayDataReference(Bytes)); } - public readonly struct AddressAsKey(Address key) : IEquatable, IHash64bit + public readonly struct AddressAsKey(Address key) : IEquatable { private readonly Address _key = key; public Address Value => _key; @@ -291,10 +289,6 @@ public override string ToString() { return _key?.ToString() ?? ""; } - - public long GetHashCode64() => _key is not null ? _key.GetHashCode64() : 0; - - public bool Equals(in AddressAsKey other) => _key == other._key; } public ref struct AddressStructRef diff --git a/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs b/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs index b93ec4916d3e..c6a24a3e10cb 100644 --- a/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs +++ b/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs @@ -2,12 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; -using System.Buffers.Text; using System.IO; using System.Runtime.CompilerServices; using System.Security.Cryptography; -using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -24,53 +21,27 @@ public sealed partial class JwtAuthentication : IRpcAuthentication private const int JwtTokenTtl = 60; private const int JwtSecretLength = 64; - // Manual HS256 validation limits - private const int SHA256HashBytes = 32; - private const int HS256SignatureSegmentLength = 44; // 43 Base64Url chars + dot separator - private const int StackBufferSize = 256; - private const int MaxManualJwtLength = StackBufferSize + HS256SignatureSegmentLength; // 300 - private static readonly Task True = Task.FromResult(true); private static readonly Task False = Task.FromResult(false); - // Known HS256 JWT header Base64Url encodings used by consensus clients - // {"alg":"HS256","typ":"JWT"} - private const string HeaderAlgTyp = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; - // {"typ":"JWT","alg":"HS256"} - private const string HeaderTypAlg = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"; - // {"alg":"HS256"} (some CLs omit typ) - private const string HeaderAlgOnly = "eyJhbGciOiJIUzI1NiJ9"; - private readonly JsonWebTokenHandler _handler = new(); - private readonly byte[] _secretBytes; - private readonly TokenValidationParameters _tokenValidationParameters; + private readonly SecurityKey _securityKey; private readonly ILogger _logger; private readonly ITimestamper _timestamper; + private readonly LifetimeValidator _lifetimeValidator; - // Single entry cache: last successfully validated token (allocation-free) - // Write order: iat first, then token with Volatile.Write (release fence) - // Read order: token with Volatile.Read (acquire fence), then iat - private string? _cachedToken; - private long _cachedTokenIat; + // Single entry cache: last successfully validated token + private TokenCacheEntry? _lastToken; private JwtAuthentication(byte[] secret, ITimestamper timestamper, ILogger logger) { ArgumentNullException.ThrowIfNull(secret); ArgumentNullException.ThrowIfNull(timestamper); - _secretBytes = secret; - SecurityKey securityKey = new SymmetricSecurityKey(secret); + _securityKey = new SymmetricSecurityKey(secret); _logger = logger; _timestamper = timestamper; - _tokenValidationParameters = new TokenValidationParameters - { - IssuerSigningKey = securityKey, - RequireExpirationTime = false, - ValidateLifetime = true, - ValidateAudience = false, - ValidateIssuer = false, - LifetimeValidator = LifetimeValidator - }; + _lifetimeValidator = LifetimeValidator; } public static JwtAuthentication FromSecret(string secret, ITimestamper timestamper, ILogger logger) @@ -165,14 +136,7 @@ public Task Authenticate(string? token) return True; } - // fast manual HS256 validation: avoids Microsoft.IdentityModel overhead - if (TryValidateManual(token, nowUnixSeconds, out bool accepted)) - { - return accepted ? True : False; - } - - // fallback to full library validation for unrecognized header formats - return AuthenticateCore(token, nowUnixSeconds); + return AuthenticateCore(token); [MethodImpl(MethodImplOptions.NoInlining)] void WarnTokenNotFound() => _logger.Warn("Message authentication error: The token cannot be found."); @@ -181,267 +145,81 @@ public Task Authenticate(string? token) void TokenMalformed() => _logger.Warn($"Message authentication error: The token must start with '{JwtMessagePrefix}'."); } - /// - /// Manual HS256 JWT validation for known header formats. - /// Returns true if handled (result in ), false to fall through to library. - /// - [SkipLocalsInit] - private bool TryValidateManual(string token, long nowUnixSeconds, out bool accepted) + private async Task AuthenticateCore(string token) { - accepted = false; - // Extract raw JWT (after "Bearer ") - ReadOnlySpan jwt = token.AsSpan(JwtMessagePrefix.Length); - - // Bail early: signed part must fit in StackBufferSize, plus HS256SignatureSegmentLength for the signature. - if (jwt.Length > MaxManualJwtLength) - return false; - - // Known HS256 header lengths: 36 (AlgTyp, TypAlg) or 20 (AlgOnly). - // Check dot at known position and verify header in one shot — avoids IndexOf scan. - int firstDot; - if (jwt.Length > 36 && jwt[36] == '.' && - (jwt[..36].SequenceEqual(HeaderAlgTyp) || jwt[..36].SequenceEqual(HeaderTypAlg))) - { - firstDot = 36; - } - else if (jwt.Length > 20 && jwt[20] == '.' && jwt[..20].SequenceEqual(HeaderAlgOnly)) - { - firstDot = 20; - } - else - { - return false; - } - - // HS256 sig = 43 Base64Url chars, so second dot is at jwt.Length - HS256SignatureSegmentLength. - // Computed directly — eliminates IndexOf scan over payload+signature. - int secondDot = jwt.Length - HS256SignatureSegmentLength; - if (secondDot <= firstDot || jwt[secondDot] != '.') - return false; - - ReadOnlySpan payload = jwt[(firstDot + 1)..secondDot]; - ReadOnlySpan signature = jwt[(secondDot + 1)..]; - - // Compute HMAC-SHA256 over "header.payload" (ASCII bytes). - // secondDot == char count == byte count (JWT is pure ASCII). - // Early length check guarantees secondDot <= 256. - ReadOnlySpan signedPart = jwt[..secondDot]; - Span signedBytes = stackalloc byte[StackBufferSize]; - signedBytes = signedBytes[..secondDot]; - - if (Ascii.FromUtf16(signedPart, signedBytes, out _) != OperationStatus.Done) - return false; - - Span computedHash = stackalloc byte[SHA256HashBytes]; - HMACSHA256.HashData(_secretBytes, signedBytes, computedHash); - - Span sigBytes = stackalloc byte[SHA256HashBytes]; - if (Base64Url.DecodeFromChars(signature, sigBytes, out _, out int sigBytesWritten) != OperationStatus.Done - || sigBytesWritten != SHA256HashBytes) - { - return true; - } - - if (!CryptographicOperations.FixedTimeEquals(computedHash, sigBytes)) - { - if (_logger.IsWarn) WarnInvalidSig(); - return true; - } - - if (!TryExtractClaims(payload, out long iat, out long exp)) - return false; // sig valid but can't parse claims — let library handle it - - if (exp > 0 && nowUnixSeconds >= exp) - { - if (_logger.IsWarn) WarnTokenExpiredExp(exp, nowUnixSeconds); - return true; - } - - // Overflow-safe absolute-difference check: casting to ulong maps negative values to - // large positives, so (ulong)(a - b + c) > (ulong)(2*c) is equivalent to |a - b| > c - // without needing Math.Abs (which can overflow on long.MinValue). - if ((ulong)(iat - nowUnixSeconds + JwtTokenTtl) > (ulong)(JwtTokenTtl * 2)) - { - if (_logger.IsWarn) WarnTokenExpiredIat(iat, nowUnixSeconds); - return true; - } - - CacheLastToken(token, iat); - accepted = true; - return true; - - - [MethodImpl(MethodImplOptions.NoInlining)] - void WarnTokenExpiredExp(long e, long now) => _logger.Warn($"Token expired. exp: {e}, now: {now}"); - - [MethodImpl(MethodImplOptions.NoInlining)] - void WarnTokenExpiredIat(long i, long now) => _logger.Warn($"Token expired. iat: {i}, now: {now}"); - - [MethodImpl(MethodImplOptions.NoInlining)] - void WarnInvalidSig() => _logger.Warn("Message authentication error: Invalid token signature."); - } - - /// - /// Extract "iat" (required) and "exp" (optional) integer claims from a JWT payload. - /// Uses a lightweight byte scanner instead of Utf8JsonReader to avoid: - /// - 552-byte stack frame (Utf8JsonReader struct ~200 bytes + locals) - /// - 432-byte prolog zeroing loop - /// - ArrayPool rent/return + try/finally - /// - 90 inlined reader methods bloating to 2979 bytes of native code - /// - [SkipLocalsInit] - private static bool TryExtractClaims(ReadOnlySpan payloadBase64Url, out long iat, out long exp) - { - iat = 0; - exp = 0; - - // Decode payload from Base64Url into a fixed stack buffer. - // Engine API payloads are tiny (~30 bytes). If decoded output exceeds - // StackBufferSize bytes, DecodeFromChars returns DestinationTooSmall → we reject. - Span decoded = stackalloc byte[StackBufferSize]; - if (Base64Url.DecodeFromChars(payloadBase64Url, decoded, out _, out int bytesWritten) != OperationStatus.Done) - return false; - - // Scan decoded UTF-8 bytes for "iat" and "exp" keys with integer values. - // JWT payloads are compact JSON objects: {"iat":NNNNN,"exp":NNNNN} - // The scanner finds quoted 3-letter keys and parses the integer after the colon. - ReadOnlySpan json = decoded[..bytesWritten]; - bool foundIat = false; - - for (int i = 0; i < json.Length - 4; i++) + try { - if (json[i] != '"') continue; - - byte k1 = json[i + 1], k2 = json[i + 2], k3 = json[i + 3]; - if (json[i + 4] != '"') continue; - - if (k1 == 'i' && k2 == 'a' && k3 == 't') - { - foundIat = TryParseClaimValue(json, i + 5, out iat); - } - else if (k1 == 'e' && k2 == 'x' && k3 == 'p') + TokenValidationParameters tokenValidationParameters = new() { - TryParseClaimValue(json, i + 5, out exp); - } - } - - return foundIat; - } - - /// - /// Parse an integer claim value starting at (expects ":digits"). - /// The (uint)pos < (uint)json.Length pattern collapses a two-condition bounds check - /// (pos >= 0 && pos < Length) into a single unsigned comparison, allowing the - /// JIT to eliminate the redundant range check on the subsequent indexer. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryParseClaimValue(ReadOnlySpan json, int pos, out long value) - { - value = 0; - // Skip optional whitespace before colon - while ((uint)pos < (uint)json.Length && json[pos] == ' ') pos++; - if ((uint)pos >= (uint)json.Length || json[pos] != ':') return false; - pos++; - // Skip optional whitespace after colon - while ((uint)pos < (uint)json.Length && json[pos] == ' ') pos++; - // Parse unsigned integer digits - bool hasDigit = false; - while ((uint)pos < (uint)json.Length) - { - uint digit = (uint)(json[pos] - '0'); - if (digit > 9) break; - value = value * 10 + digit; - hasDigit = true; - pos++; - } - return hasDigit; - } + IssuerSigningKey = _securityKey, + RequireExpirationTime = false, + ValidateLifetime = true, + ValidateAudience = false, + ValidateIssuer = false, + LifetimeValidator = _lifetimeValidator + }; - /// - /// Library-based JWT validation for unrecognized header formats. - /// Checks for synchronous Task completion to avoid async state machine on the hot path. - /// - private Task AuthenticateCore(string token, long nowUnixSeconds) - { - try - { ReadOnlyMemory tokenSlice = token.AsMemory(JwtMessagePrefix.Length); JsonWebToken jwtToken = _handler.ReadJsonWebToken(tokenSlice); - Task task = _handler.ValidateTokenAsync(jwtToken, _tokenValidationParameters); + TokenValidationResult result = await _handler.ValidateTokenAsync(jwtToken, tokenValidationParameters); - // HS256 validation is CPU-bound → task is almost always already completed. - // Avoid async state machine overhead for the common synchronous path. - return task.IsCompletedSuccessfully - ? ValidateLibraryResult(task.GetAwaiter().GetResult(), token, jwtToken, nowUnixSeconds) ? True : False - : AwaitValidation(task, token, jwtToken, nowUnixSeconds); - } - catch (Exception ex) - { - if (_logger.IsWarn) WarnAuthError(ex); - return False; - } + if (!result.IsValid) + { + if (_logger.IsWarn) WarnInvalidResult(result.Exception); + return false; + } - [MethodImpl(MethodImplOptions.NoInlining)] - void WarnAuthError(Exception? ex) => _logger.Warn($"Message authentication error: {ex?.Message}"); - } + DateTime now = _timestamper.UtcNow; + long issuedAtUnix = jwtToken.IssuedAt.ToUnixTimeSeconds(); + if (Math.Abs(issuedAtUnix - now.ToUnixTimeSeconds()) <= JwtTokenTtl) + { + // full validation succeeded and TTL check passed - cache as last valid token + CacheLastToken(token, issuedAtUnix); - private bool ValidateLibraryResult(TokenValidationResult result, string token, JsonWebToken jwtToken, long nowUnixSeconds) - { - if (!result.IsValid) - { - if (_logger.IsWarn) WarnInvalidResult(result.Exception); + if (_logger.IsTrace) Trace(jwtToken, now, tokenSlice); + return true; + } + + if (_logger.IsWarn) WarnTokenExpired(jwtToken, now); return false; } - - long issuedAtUnix = jwtToken.IssuedAt.ToUnixTimeSeconds(); - - // Unsigned range check: |iat - now| <= TTL without Math.Abs overflow guard - if ((ulong)(issuedAtUnix - nowUnixSeconds + JwtTokenTtl) > (ulong)(JwtTokenTtl * 2)) + catch (Exception ex) { - if (_logger.IsWarn) WarnTokenExpired(issuedAtUnix, nowUnixSeconds); + if (_logger.IsWarn) WarnAuthenticationError(ex); return false; } - CacheLastToken(token, issuedAtUnix); - if (_logger.IsTrace) TraceAuth(jwtToken, nowUnixSeconds, token); - return true; - [MethodImpl(MethodImplOptions.NoInlining)] void WarnInvalidResult(Exception? ex) { - _logger.Warn(ex switch + if (ex is SecurityTokenDecryptionFailedException) + { + _logger.Warn("Message authentication error: The token cannot be decrypted."); + } + else if (ex is SecurityTokenReplayDetectedException) + { + _logger.Warn("Message authentication error: The token has been used multiple times."); + } + else if (ex is SecurityTokenInvalidSignatureException) { - SecurityTokenDecryptionFailedException => "Message authentication error: The token cannot be decrypted.", - SecurityTokenReplayDetectedException => "Message authentication error: The token has been used multiple times.", - SecurityTokenInvalidSignatureException => "Message authentication error: Invalid token signature.", - _ => $"Message authentication error: {ex?.Message}" - }); + _logger.Warn("Message authentication error: Invalid token signature."); + } + else + { + WarnAuthenticationError(ex); + } } [MethodImpl(MethodImplOptions.NoInlining)] - void WarnTokenExpired(long iat, long now) - => _logger.Warn($"Token expired. iat: {iat}, now: {now}"); + void WarnAuthenticationError(Exception? ex) => _logger.Warn($"Message authentication error: {ex?.Message}"); [MethodImpl(MethodImplOptions.NoInlining)] - void TraceAuth(JsonWebToken jwt, long now, string tok) - => _logger.Trace($"Message authenticated. Token: {tok.AsMemory(JwtMessagePrefix.Length)}, iat: {jwt.IssuedAt}, time: {now}"); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private async Task AwaitValidation(Task task, string token, JsonWebToken jwtToken, long nowUnixSeconds) - { - try - { - return ValidateLibraryResult(await task, token, jwtToken, nowUnixSeconds); - } - catch (Exception ex) - { - if (_logger.IsWarn) WarnAuthError(ex); - return false; - } + void WarnTokenExpired(JsonWebToken jwtToken, DateTime now) + => _logger.Warn($"Token expired. Now is {now}, token issued at {jwtToken.IssuedAt}"); [MethodImpl(MethodImplOptions.NoInlining)] - void WarnAuthError(Exception? ex) => _logger.Warn($"Message authentication error: {ex?.Message}"); + void Trace(JsonWebToken jwtToken, DateTime now, ReadOnlyMemory token) + => _logger.Trace($"Message authenticated. Token: {token}, iat: {jwtToken.IssuedAt}, time: {now}"); } private bool LifetimeValidator( @@ -454,37 +232,44 @@ private bool LifetimeValidator( return _timestamper.UnixTime.SecondsLong < expires.Value.ToUnixTimeSeconds(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private void CacheLastToken(string token, long issuedAtUnixSeconds) { - // Write iat first (plain store), then token with release fence. - // Reader uses acquire fence on token, then reads iat — guarantees - // the iat visible is at least as fresh as the token that was read. - _cachedTokenIat = issuedAtUnixSeconds; - Volatile.Write(ref _cachedToken, token); + TokenCacheEntry entry = new(token, issuedAtUnixSeconds); + // last writer wins, atomic swap + Interlocked.Exchange(ref _lastToken, entry); } private bool TryLastValidationFromCache(string token, long nowUnixSeconds) { - // Acquire fence on token read; guarantees _cachedTokenIat is at least as fresh - string? cached = Volatile.Read(ref _cachedToken); - if (cached is null) + // Read the last validated token entry atomically + // this is a single entry cache because tokens tend to be reused + // for a handful of sequential requests before a fresh token is issued + TokenCacheEntry? entry = Volatile.Read(ref _lastToken); + if (entry is null) return false; - if (!string.Equals(cached, token, StringComparison.Ordinal)) + // Only allow cache hit if the exact same token string is being reused + // different tokens bypass the cache and undergo full validation + if (!string.Equals(entry.Token, token, StringComparison.Ordinal)) return false; - // Unsigned range check: |iat - now| <= TTL - if ((ulong)(_cachedTokenIat - nowUnixSeconds + JwtTokenTtl) > (ulong)(JwtTokenTtl * 2)) + // Token reuse is only allowed within the original JWT lifetime + // We never extend token validity beyond what the issuer intended + // - IssuedAtUnixSeconds ensures we don't accept a token older than TTL + if (Math.Abs(entry.IssuedAtUnixSeconds - nowUnixSeconds) > JwtTokenTtl) { - Volatile.Write(ref _cachedToken, null); + // Token lifetime exceeded - drop the cached entry and force a fresh validation + Interlocked.CompareExchange(ref _lastToken, null, entry); return false; } + // Same token, within TTL, recently validated: + // Accept as valid without rerunning JWT parsing and crypto checks return true; } [GeneratedRegex("^(0x)?[0-9a-fA-F]{64}$")] private static partial Regex SecretRegex(); + private record TokenCacheEntry(string Token, long IssuedAtUnixSeconds); } diff --git a/src/Nethermind/Nethermind.Core/Block.cs b/src/Nethermind/Nethermind.Core/Block.cs index 5fd94383cdb6..8d53097d5198 100644 --- a/src/Nethermind/Nethermind.Core/Block.cs +++ b/src/Nethermind/Nethermind.Core/Block.cs @@ -126,13 +126,6 @@ public Transaction[] Transactions [JsonIgnore] public int? EncodedSize { get; set; } - /// - /// Pre-encoded transaction bytes in SkipTypedWrapping format (as received from CL). - /// Used to avoid re-encoding transactions when storing blocks. - /// - [JsonIgnore] - public byte[][]? EncodedTransactions { get; set; } - public override string ToString() => ToString(Format.Short); public string ToString(Format format) => format switch diff --git a/src/Nethermind/Nethermind.Core/Bloom.cs b/src/Nethermind/Nethermind.Core/Bloom.cs index cc5d3acce000..baeb3e75e168 100644 --- a/src/Nethermind/Nethermind.Core/Bloom.cs +++ b/src/Nethermind/Nethermind.Core/Bloom.cs @@ -53,9 +53,7 @@ public Bloom(ReadOnlySpan bytes) bytes.CopyTo(Bytes); } - [JsonIgnore] public Span Bytes => _bloomData.AsSpan(); - [JsonIgnore] public ReadOnlySpan ReadOnlyBytes => _bloomData.AsReadOnlySpan(); private Span ULongs => _bloomData.AsULongs(); diff --git a/src/Nethermind/Nethermind.Core/Buffers/ArrayMemoryManager.cs b/src/Nethermind/Nethermind.Core/Buffers/ArrayMemoryManager.cs deleted file mode 100644 index 52c1bf4dd496..000000000000 --- a/src/Nethermind/Nethermind.Core/Buffers/ArrayMemoryManager.cs +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Buffers; - -namespace Nethermind.Core.Buffers; - -/// -/// Simple MemoryManager that wraps a byte array without any pinning. -/// Used for in-memory stores where the array is managed and doesn't require special release handling. -/// -public sealed class ArrayMemoryManager(byte[] array) : MemoryManager -{ - protected override void Dispose(bool disposing) { } - - public override Span GetSpan() => array; - - public override MemoryHandle Pin(int elementIndex = 0) => default; - - public override void Unpin() { } -} diff --git a/src/Nethermind/Nethermind.Core/Caching/ISpanCache.cs b/src/Nethermind/Nethermind.Core/Caching/ISpanCache.cs new file mode 100644 index 000000000000..c7b56ebd2a8f --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Caching/ISpanCache.cs @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Core.Caching +{ + /// + /// Its like `ICache` but you can index the key by span + /// + /// + /// + public interface ISpanCache + { + void Clear(); + TValue? Get(ReadOnlySpan key); + bool TryGet(ReadOnlySpan key, out TValue? value); + + /// + /// Sets value in the cache. + /// + /// + /// + /// True if key didn't exist in the cache, otherwise false. + bool Set(ReadOnlySpan key, TValue val); + + /// + /// Delete key from cache. + /// + /// + /// True if key existed in the cache, otherwise false. + bool Delete(ReadOnlySpan key); + bool Contains(ReadOnlySpan key); + int Count { get; } + } +} diff --git a/src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs b/src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs new file mode 100644 index 000000000000..1f499890f03c --- /dev/null +++ b/src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; + +namespace Nethermind.Core.Buffers; + +public class CappedArrayMemoryManager(CappedArray? data) : MemoryManager +{ + private readonly CappedArray _data = data ?? throw new ArgumentNullException(nameof(data)); + private bool _isDisposed; + + public override Span GetSpan() + { + ObjectDisposedException.ThrowIf(_isDisposed, this); + return _data.AsSpan(); + } + + public override MemoryHandle Pin(int elementIndex = 0) + { + ObjectDisposedException.ThrowIf(_isDisposed, this); + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)elementIndex, (uint)_data.Length); + // Pinning is a no-op in this managed implementation + return default; + } + + public override void Unpin() + { + ObjectDisposedException.ThrowIf(_isDisposed, this); + // Unpinning is a no-op in this managed implementation + } + + protected override void Dispose(bool disposing) + { + _isDisposed = true; + } + + protected override bool TryGetArray(out ArraySegment segment) + { + ObjectDisposedException.ThrowIf(_isDisposed, this); + segment = _data.AsArraySegment(); + return true; + } +} diff --git a/src/Nethermind/Nethermind.Core/Collections/ArrayListCore.cs b/src/Nethermind/Nethermind.Core/Collections/ArrayListCore.cs index 518fb7512842..00b95e683953 100644 --- a/src/Nethermind/Nethermind.Core/Collections/ArrayListCore.cs +++ b/src/Nethermind/Nethermind.Core/Collections/ArrayListCore.cs @@ -51,15 +51,6 @@ public static void ClearToCount(T[] array, int count) } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ClearTail(T[] array, int newCount, int oldCount) - { - if (RuntimeHelpers.IsReferenceOrContainsReferences() && newCount < oldCount) - { - Array.Clear(array, newCount, oldCount - newCount); - } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Add( ArrayPool pool, @@ -117,9 +108,9 @@ public static void ReduceCount( ClearToCount(oldArray, oldCount); pool.Return(oldArray); } - else + else if (RuntimeHelpers.IsReferenceOrContainsReferences()) { - ClearTail(array, newCount, oldCount); + Array.Clear(array, newCount, oldCount - newCount); } [DoesNotReturn] @@ -237,7 +228,6 @@ public static void Insert( public static void Truncate(int newLength, T[] array, ref int count) { GuardIndex(newLength, count, shouldThrow: true, allowEqualToCount: true); - ClearTail(array, newLength, count); count = newLength; } diff --git a/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs b/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs index 9fdf91723f45..491e4e3528ca 100644 --- a/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -using Nethermind.Core.Resettables; namespace Nethermind.Core.Collections; @@ -17,48 +15,21 @@ public static void Increment(this Dictionary dictionary, TKey k res++; } - extension(Dictionary dictionary) where TKey : notnull + public static ref TValue GetOrAdd(this Dictionary dictionary, + TKey key, Func factory, + out bool exists) + where TKey : notnull { - public ref TValue GetOrAdd(TKey key, Func factory, out bool exists) - { - ref TValue? existing = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out exists); + ref TValue? existing = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out exists); - if (!exists) - existing = factory(key); - - return ref existing!; - } - - public ref TValue GetOrAdd(TKey key, Func factory) => ref dictionary.GetOrAdd(key, factory, out _); + if (!exists) + existing = factory(key); + return ref existing!; } - /// The dictionary whose values will be returned and cleared. - /// The type of the keys in the dictionary. - /// The type of the values in the dictionary, which must implement . - extension(IDictionary dictionary) where TValue : class, IReturnable - { - /// - /// Returns all values in the dictionary to their pool by calling on each value, - /// then clears the dictionary. - /// - /// - /// Use this method when you need to both return pooled objects and clear the dictionary in one operation. - /// - public void ResetAndClear() - { - foreach (TValue value in dictionary.Values) - { - value.Return(); - } - dictionary.Clear(); - } - } - - extension(Dictionary.AlternateLookup dictionary) - where TKey : notnull where TAlternateKey : notnull, allows ref struct - { - public bool TryRemove(TAlternateKey key, [MaybeNullWhen(false)] out TValue value) => - dictionary.Remove(key, out _, out value); - } + public static ref TValue GetOrAdd(this Dictionary dictionary, + TKey key, Func factory) + where TKey : notnull => + ref GetOrAdd(dictionary, key, factory, out _); } diff --git a/src/Nethermind/Nethermind.Core/Collections/HashHelpers.cs b/src/Nethermind/Nethermind.Core/Collections/HashHelpers.cs new file mode 100644 index 000000000000..e998398d5e13 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Collections/HashHelpers.cs @@ -0,0 +1,132 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; +using System.Threading; + +namespace Nethermind.Core.Collections +{ + /// + /// Adapted from .net source code. + /// + internal static class HashHelpers + { + public const uint HashCollisionThreshold = 100; + + // This is the maximum prime smaller than Array.MaxLength. + public const int MaxPrimeArrayLength = 0x7FFFFFC3; + + public const int HashPrime = 101; + + // Table of prime numbers to use as hash table sizes. + // A typical resize algorithm would pick the smallest prime number in this array + // that is larger than twice the previous capacity. + // Suppose our Hashtable currently has capacity x and enough elements are added + // such that a resize needs to occur. Resizing first computes 2x then finds the + // first prime in the table greater than 2x, i.e. if primes are ordered + // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. + // Doubling is important for preserving the asymptotic complexity of the + // hashtable operations such as add. Having a prime guarantees that double + // hashing does not lead to infinite loops. IE, your hash function will be + // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. + // We prefer the low computation costs of higher prime numbers over the increased + // memory allocation of a fixed prime number i.e. when right sizing a HashSet. + private static readonly int[] s_primes = + { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, + 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, + 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, + 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, + 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 + }; + + public static bool IsPrime(int candidate) + { + if ((candidate & 1) != 0) + { + int limit = (int)Math.Sqrt(candidate); + for (int divisor = 3; divisor <= limit; divisor += 2) + { + if ((candidate % divisor) == 0) + return false; + } + return true; + } + return candidate == 2; + } + + public static int GetPrime(int min) + { + if (min < 0) + throw new ArgumentException("Hashtable's capacity overflowed and went negative. Check load factor, capacity and the current size of the table."); + + foreach (int prime in s_primes) + { + if (prime >= min) + return prime; + } + + // Outside of our predefined table. Compute the hard way. + for (int i = (min | 1); i < int.MaxValue; i += 2) + { + if (IsPrime(i) && ((i - 1) % HashPrime != 0)) + return i; + } + return min; + } + + // Returns size of hashtable to grow to. + public static int ExpandPrime(int oldSize) + { + int newSize = 2 * oldSize; + + // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) + { + Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); + return MaxPrimeArrayLength; + } + + return GetPrime(newSize); + } + + /// Returns approximate reciprocal of the divisor: ceil(2**64 / divisor). + /// This should only be used on 64-bit. + public static ulong GetFastModMultiplier(uint divisor) => + ulong.MaxValue / divisor + 1; + + /// Performs a mod operation using the multiplier pre-computed with . + /// This should only be used on 64-bit. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint FastMod(uint value, uint divisor, ulong multiplier) + { + // We use modified Daniel Lemire's fastmod algorithm (https://github.com/dotnet/runtime/pull/406), + // which allows to avoid the long multiplication if the divisor is less than 2**31. + Debug.Assert(divisor <= int.MaxValue); + + // This is equivalent of (uint)Math.BigMul(multiplier * value, divisor, out _). This version + // is faster than BigMul currently because we only need the high bits. + uint highbits = (uint)(((((multiplier * value) >> 32) + 1) * divisor) >> 32); + + Debug.Assert(highbits == value % divisor); + return highbits; + } + + private static ConditionalWeakTable? s_serializationInfoTable; + + public static ConditionalWeakTable SerializationInfoTable + { + get + { + if (s_serializationInfoTable is null) + Interlocked.CompareExchange(ref s_serializationInfoTable, new ConditionalWeakTable(), null); + + return s_serializationInfoTable; + } + } + } +} diff --git a/src/Nethermind/Nethermind.Core/Collections/IHash64bit.cs b/src/Nethermind/Nethermind.Core/Collections/IHash64bit.cs deleted file mode 100644 index 746146576cf4..000000000000 --- a/src/Nethermind/Nethermind.Core/Collections/IHash64bit.cs +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Core.Collections; - -/// -/// Provides a 64-bit hash code for high-performance caching with reduced collision probability. -/// -/// -/// Types implementing this interface can be used with caches that require extended hash bits -/// for collision resistance (for example, Seqlock-based caches that use additional bits beyond -/// the standard hash code for bucket indexing and collision detection). -/// The 64-bit hash should have good distribution across all bits. -/// -public interface IHash64bit -{ - /// - /// Returns a 64-bit hash code for the current instance. - /// - /// A 64-bit hash code with good distribution across all bits. - long GetHashCode64(); - bool Equals(in TKey other); -} diff --git a/src/Nethermind/Nethermind.Core/Collections/InsertionBehavior.cs b/src/Nethermind/Nethermind.Core/Collections/InsertionBehavior.cs new file mode 100644 index 000000000000..90f4431ec102 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Collections/InsertionBehavior.cs @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +internal enum InsertionBehavior : byte +{ + None, + OverwriteExisting, + ThrowOnExisting, +} diff --git a/src/Nethermind/Nethermind.Core/Collections/SeqlockCache.cs b/src/Nethermind/Nethermind.Core/Collections/SeqlockCache.cs deleted file mode 100644 index 6ce764fa46b6..000000000000 --- a/src/Nethermind/Nethermind.Core/Collections/SeqlockCache.cs +++ /dev/null @@ -1,400 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics.X86; -using System.Threading; - -namespace Nethermind.Core.Collections; - -/// -/// A high-performance 2-way skew-associative cache using a seqlock-style header per entry. -/// -/// Design goals: -/// - Lock-free reads (seqlock pattern) - readers never take locks. -/// - Best-effort writes - writers skip on contention. -/// - O(1) logical Clear() via a global epoch (no per-entry zeroing). -/// - 2-way skew-associative: each way uses independent hash bits for set indexing, -/// breaking correlation between ways ("power of two choices"). Keys that collide -/// in way 0 scatter to different sets in way 1, virtually eliminating conflict misses. -/// -/// Hash bit partitioning (64-bit hash): -/// Bits 0-13: way 0 set index (14 bits) -/// Bits 14-41: hash signature stored in header (28 bits) -/// Bits 42-55: way 1 set index (14 bits, independent from way 0) -/// -/// Header layout (64-bit): -/// [Lock:1][Epoch:26][Hash:28][Seq:8][Occ:1] -/// - Lock (bit 63): set during writes - readers retry/miss -/// - Epoch (bits 37-62): global epoch tag - changes on Clear() -/// - Hash (bits 9-36): per-bucket hash signature (28 bits) -/// - Seq (bits 1- 8): per-entry sequence counter (8 bits) - increments on every successful write -/// - Occ (bit 0): occupied flag - set when slot contains valid data (value may still be null) -/// -/// Array layout: [way0_set0..way0_set16383, way1_set0..way1_set16383] (split, not interleaved). -/// -/// The key type (struct implementing IHash64bit) -/// The value type (reference type, nullable allowed) -public sealed class SeqlockCache - where TKey : struct, IHash64bit - where TValue : class? -{ - /// - /// Number of sets. Must be a power of 2 for mask operations. - /// 16384 sets × 2 ways = 32768 total entries. - /// - private const int Sets = 1 << 14; // 16384 - private const int SetMask = Sets - 1; - - // Header bit layout: - // [Lock:1][Epoch:26][Hash:28][Seq:8][Occ:1] - - private const long LockMarker = unchecked((long)0x8000_0000_0000_0000); // bit 63 - - private const int EpochShift = 37; - private const long EpochMask = 0x7FFF_FFE0_0000_0000; // bits 37-62 (26 bits) - - private const long HashMask = 0x0000_0001_FFFF_FE00; // bits 9-36 (28 bits) - - private const long SeqMask = 0x0000_0000_0000_01FE; // bits 1-8 (8 bits) - private const long SeqInc = 0x0000_0000_0000_0002; // +1 in seq field - - private const long OccupiedBit = 1L; // bit 0 - - // Mask of all "identity" bits for an entry, excluding Lock and Seq. - private const long TagMask = EpochMask | HashMask | OccupiedBit; - - // Mask for checking if an entry is live in the current epoch. - private const long EpochOccMask = EpochMask | OccupiedBit; - - // With 14-bit set index (bits 0-13) for way 0, hash signature needs bits 14+. - // HashShift=5 maps header bits 9-36 to original bits 14-41, avoiding overlap with both ways. - private const int HashShift = 5; - - // Way 1 uses bits 42-55 of the original hash (completely independent from way 0's bits 0-13). - private const int Way1Shift = 42; - - /// - /// Array of entries: [way0_set0..way0_setN, way1_set0..way1_setN]. - /// Split layout ensures each way is a contiguous block for better prefetch behavior. - /// - private readonly Entry[] _entries; - - /// - /// Current epoch counter (unshifted, informational / debugging). - /// - private long _epoch; - - /// - /// Pre-shifted epoch tag: (_epoch << EpochShift) & EpochMask. - /// Readers use this directly to avoid shift/mask in the hot path. - /// - private long _shiftedEpoch; - - public SeqlockCache() - { - _entries = new Entry[Sets << 1]; // Sets * 2 - _epoch = 0; - _shiftedEpoch = 0; - } - - /// - /// Tries to get a value from the cache using a seqlock pattern (lock-free reads). - /// Checks both ways of the target set for the key. - /// - [SkipLocalsInit] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe bool TryGetValue(in TKey key, out TValue? value) - { - long hashCode = key.GetHashCode64(); - int idx0 = (int)hashCode & SetMask; - int idx1 = Sets + ((int)(hashCode >> Way1Shift) & SetMask); - - long epochTag = Volatile.Read(ref _shiftedEpoch); - long hashPart = (hashCode >> HashShift) & HashMask; - long expectedTag = epochTag | hashPart | OccupiedBit; - - ref Entry entries = ref MemoryMarshal.GetArrayDataReference(_entries); - - // Prefetch way 1 while we check way 0 — hides L2/L3 latency for skew layout. - if (Sse.IsSupported) - { - Sse.PrefetchNonTemporal(Unsafe.AsPointer(ref Unsafe.Add(ref entries, idx1))); - } - - // === Way 0 === - ref Entry e0 = ref Unsafe.Add(ref entries, idx0); - long h1 = Volatile.Read(ref e0.HashEpochSeqLock); - - if ((h1 & (TagMask | LockMarker)) == expectedTag) - { - ref readonly TKey storedKey = ref e0.Key; - TValue? storedValue = e0.Value; - - long h2 = Volatile.Read(ref e0.HashEpochSeqLock); - if (h1 == h2 && storedKey.Equals(in key)) - { - value = storedValue; - return true; - } - } - - // === Way 1 === - ref Entry e1 = ref Unsafe.Add(ref entries, idx1); - long w1 = Volatile.Read(ref e1.HashEpochSeqLock); - - if ((w1 & (TagMask | LockMarker)) == expectedTag) - { - ref readonly TKey storedKey = ref e1.Key; - TValue? storedValue = e1.Value; - - long w2 = Volatile.Read(ref e1.HashEpochSeqLock); - if (w1 == w2 && storedKey.Equals(in key)) - { - value = storedValue; - return true; - } - } - - value = default; - return false; - } - - /// - /// Delegate-based factory that avoids copying large keys (passes by in). - /// Prefer this over Func<TKey, TValue?> when TKey is big (eg 48 bytes). - /// - public delegate TValue? ValueFactory(in TKey key); - - /// - /// Gets a value from the cache, or adds it using the factory if not present. - /// - [SkipLocalsInit] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public TValue? GetOrAdd(in TKey key, ValueFactory valueFactory) - { - long hashCode = key.GetHashCode64(); - int idx0 = (int)hashCode & SetMask; - int idx1 = Sets + ((int)(hashCode >> Way1Shift) & SetMask); - long hashPart = (hashCode >> HashShift) & HashMask; - - if (TryGetValueCore(in key, idx0, idx1, hashPart, out TValue? value)) - { - return value; - } - - return GetOrAddMiss(in key, valueFactory, idx0, idx1, hashPart); - } - - /// - /// Cold path for GetOrAdd: invokes factory and stores the result. - /// Kept out-of-line so the hot path (cache hit) compiles to a lean method body - /// with minimal register saves and stack frame. - /// - [MethodImpl(MethodImplOptions.NoInlining)] - private TValue? GetOrAddMiss(in TKey key, ValueFactory valueFactory, int idx0, int idx1, long hashPart) - { - TValue? value = valueFactory(in key); - SetCore(in key, value, idx0, idx1, hashPart); - return value; - } - - [SkipLocalsInit] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe bool TryGetValueCore(in TKey key, int idx0, int idx1, long hashPart, out TValue? value) - { - long epochTag = Volatile.Read(ref _shiftedEpoch); - long expectedTag = epochTag | hashPart | OccupiedBit; - - ref Entry entries = ref MemoryMarshal.GetArrayDataReference(_entries); - - if (Sse.IsSupported) - { - Sse.PrefetchNonTemporal(Unsafe.AsPointer(ref Unsafe.Add(ref entries, idx1))); - } - - // Way 0 - ref Entry e0 = ref Unsafe.Add(ref entries, idx0); - long h1 = Volatile.Read(ref e0.HashEpochSeqLock); - - if ((h1 & (TagMask | LockMarker)) == expectedTag) - { - ref readonly TKey storedKey = ref e0.Key; - TValue? storedValue = e0.Value; - - long h2 = Volatile.Read(ref e0.HashEpochSeqLock); - if (h1 == h2 && storedKey.Equals(in key)) - { - value = storedValue; - return true; - } - } - - // Way 1 - ref Entry e1 = ref Unsafe.Add(ref entries, idx1); - long w1 = Volatile.Read(ref e1.HashEpochSeqLock); - - if ((w1 & (TagMask | LockMarker)) == expectedTag) - { - ref readonly TKey storedKey = ref e1.Key; - TValue? storedValue = e1.Value; - - long w2 = Volatile.Read(ref e1.HashEpochSeqLock); - if (w1 == w2 && storedKey.Equals(in key)) - { - value = storedValue; - return true; - } - } - - value = default; - return false; - } - - [SkipLocalsInit] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void SetCore(in TKey key, TValue? value, int idx0, int idx1, long hashPart) - { - long epochTag = Volatile.Read(ref _shiftedEpoch); - long tagToStore = epochTag | hashPart | OccupiedBit; - long epochOccTag = epochTag | OccupiedBit; - - ref Entry entries = ref MemoryMarshal.GetArrayDataReference(_entries); - ref Entry e0 = ref Unsafe.Add(ref entries, idx0); - - long h0 = Volatile.Read(ref e0.HashEpochSeqLock); - - // === Way 0: check for matching key === - if (h0 >= 0 && (h0 & TagMask) == tagToStore) - { - ref readonly TKey k0 = ref e0.Key; - TValue? v0 = e0.Value; - - long h0_2 = Volatile.Read(ref e0.HashEpochSeqLock); - if (h0 == h0_2 && k0.Equals(in key)) - { - if (ReferenceEquals(v0, value)) return; // fast-path: same key+value, no-op - WriteEntry(ref e0, h0_2, in key, value, tagToStore); - return; - } - h0 = h0_2; - } - - // === Way 1: check for matching key === - ref Entry e1 = ref Unsafe.Add(ref entries, idx1); - long h1 = Volatile.Read(ref e1.HashEpochSeqLock); - - if (h1 >= 0 && (h1 & TagMask) == tagToStore) - { - ref readonly TKey k1 = ref e1.Key; - TValue? v1 = e1.Value; - - long h1_2 = Volatile.Read(ref e1.HashEpochSeqLock); - if (h1 == h1_2 && k1.Equals(in key)) - { - if (ReferenceEquals(v1, value)) return; // fast-path: same key+value, no-op - WriteEntry(ref e1, h1_2, in key, value, tagToStore); - return; - } - h1 = h1_2; - } - - // === Key not in either way. Evict into an available slot. === - // Priority: stale/empty unlocked > live (alternating by hash bit) > any unlocked > skip. - // The decision tree selects which way to evict into, then issues a single WriteEntry call. - bool h0Live = h0 >= 0 && (h0 & EpochOccMask) == epochOccTag; - bool h1Live = h1 >= 0 && (h1 & EpochOccMask) == epochOccTag; - - bool pick0; - if (!h0Live && h0 >= 0) pick0 = true; - else if (!h1Live && h1 >= 0) pick0 = false; - else if (h0Live && h1Live) pick0 = (hashPart & (1L << 9)) != 0; - else if (h0 >= 0) pick0 = true; - else if (h1 >= 0) pick0 = false; - else return; // both locked, skip - - WriteEntry( - ref pick0 ? ref e0 : ref e1, - pick0 ? h0 : h1, - in key, value, tagToStore); - } - - /// - /// Sets a key-value pair in the cache. - /// Checks both ways of the target set for an existing key match before evicting. - /// - [SkipLocalsInit] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Set(in TKey key, TValue? value) - { - long hashCode = key.GetHashCode64(); - int idx0 = (int)hashCode & SetMask; - int idx1 = Sets + ((int)(hashCode >> Way1Shift) & SetMask); - long hashPart = (hashCode >> HashShift) & HashMask; - - SetCore(in key, value, idx0, idx1, hashPart); - } - - /// - /// Attempts a CAS-guarded write to a single entry. - /// Kept out-of-line: the CAS atomic dominates latency, so call overhead is invisible, - /// while de-duplication reclaims ~350 bytes of inlined copies across SetCore call sites. - /// - [MethodImpl(MethodImplOptions.NoInlining)] - private static void WriteEntry(ref Entry entry, long existing, in TKey key, TValue? value, long tagToStore) - { - if (existing < 0) return; // locked - - long newSeq = ((existing & SeqMask) + SeqInc) & SeqMask; - long lockedHeader = tagToStore | newSeq | LockMarker; - - if (Interlocked.CompareExchange(ref entry.HashEpochSeqLock, lockedHeader, existing) != existing) - { - return; - } - - entry.Key = key; - entry.Value = value; - - Volatile.Write(ref entry.HashEpochSeqLock, tagToStore | newSeq); - } - - /// - /// Clears all cached entries by incrementing the global epoch tag (O(1)). - /// Entries with stale epochs are treated as empty on subsequent lookups. - /// - public void Clear() - { - long oldShifted = Volatile.Read(ref _shiftedEpoch); - - while (true) - { - long oldEpoch = (oldShifted & EpochMask) >> EpochShift; - long newEpoch = oldEpoch + 1; - long newShifted = (newEpoch << EpochShift) & EpochMask; - - long prev = Interlocked.CompareExchange(ref _shiftedEpoch, newShifted, oldShifted); - if (prev == oldShifted) - { - Volatile.Write(ref _epoch, newEpoch); - return; - } - - oldShifted = prev; - } - } - - /// - /// Cache entry struct. - /// Header is a single 64-bit field to keep the seqlock control word in one atomic unit. - /// - [StructLayout(LayoutKind.Sequential)] - private struct Entry - { - public long HashEpochSeqLock; // [Lock|Epoch|Hash|Seq|Occ] - public TKey Key; - public TValue? Value; - } -} diff --git a/src/Nethermind/Nethermind.Core/Collections/SortedRealList.cs b/src/Nethermind/Nethermind.Core/Collections/SortedRealList.cs new file mode 100644 index 000000000000..02d66af6e651 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Collections/SortedRealList.cs @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; + +namespace Nethermind.Core.Collections +{ + public class SortedRealList : SortedList, IList> where TKey : notnull + { + // Constructs a new sorted list. The sorted list is initially empty and has + // a capacity of zero. Upon adding the first element to the sorted list the + // capacity is increased to DefaultCapacity, and then increased in multiples of two as + // required. The elements of the sorted list are ordered according to the + // IComparable interface, which must be implemented by the keys of + // all entries added to the sorted list. + public SortedRealList() { } + + // Constructs a new sorted list. The sorted list is initially empty and has + // a capacity of zero. Upon adding the first element to the sorted list the + // capacity is increased to 16, and then increased in multiples of two as + // required. The elements of the sorted list are ordered according to the + // IComparable interface, which must be implemented by the keys of + // all entries added to the sorted list. + // + public SortedRealList(int capacity) : base(capacity) { } + + // Constructs a new sorted list with a given IComparer + // implementation. The sorted list is initially empty and has a capacity of + // zero. Upon adding the first element to the sorted list the capacity is + // increased to 16, and then increased in multiples of two as required. The + // elements of the sorted list are ordered according to the given + // IComparer implementation. If comparer is null, the + // elements are compared to each other using the IComparable + // interface, which in that case must be implemented by the keys of all + // entries added to the sorted list. + // + public SortedRealList(IComparer? comparer) : base(comparer) + { + } + + // Constructs a new sorted dictionary with a given IComparer + // implementation and a given initial capacity. The sorted list is + // initially empty, but will have room for the given number of elements + // before any reallocations are required. The elements of the sorted list + // are ordered according to the given IComparer implementation. If + // comparer is null, the elements are compared to each other using + // the IComparable interface, which in that case must be implemented + // by the keys of all entries added to the sorted list. + // + public SortedRealList(int capacity, IComparer? comparer) : base(capacity, comparer) { } + + // Constructs a new sorted list containing a copy of the entries in the + // given dictionary. The elements of the sorted list are ordered according + // to the IComparable interface, which must be implemented by the + // keys of all entries in the given dictionary as well as keys + // subsequently added to the sorted list. + // + public SortedRealList(IDictionary dictionary) : base(dictionary) { } + + // Constructs a new sorted list containing a copy of the entries in the + // given dictionary. The elements of the sorted list are ordered according + // to the given IComparer implementation. If comparer is + // null, the elements are compared to each other using the + // IComparable interface, which in that case must be implemented + // by the keys of all entries in the given dictionary as well as keys + // subsequently added to the sorted list. + // + public SortedRealList(IDictionary dictionary, IComparer? comparer) : base(dictionary, comparer) { } + + public int IndexOf(KeyValuePair item) => IndexOfKey(item.Key); + + public void Insert(int index, KeyValuePair item) => this.TryAdd(item.Key, item.Value); + + public KeyValuePair this[int index] + { + get => new(Keys[index], Values[index]); + set => this.TryAdd(value.Key, value.Value); + } + } +} diff --git a/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs b/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs index 4b8566b3e4ba..0ee2476c0762 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs @@ -6,7 +6,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using System.Text.Json.Serialization; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -26,9 +25,7 @@ namespace Nethermind.Core.Crypto public const int MemorySize = 32; public static int Length => MemorySize; - [JsonIgnore] public Span BytesAsSpan => MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in _bytes), 1)); - [JsonIgnore] public ReadOnlySpan Bytes => MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in _bytes), 1)); public static implicit operator ValueHash256?(Hash256? keccak) => keccak?.ValueHash256; @@ -132,7 +129,6 @@ public sealed class Hash256 : IEquatable, IComparable public ref readonly ValueHash256 ValueHash256 => ref _hash256; - [JsonIgnore] public Span Bytes => MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in _hash256), 1)); public Hash256(string hexString) diff --git a/src/Nethermind/Nethermind.Core/Crypto/Signature.cs b/src/Nethermind/Nethermind.Core/Crypto/Signature.cs index 61f152101c09..35834c23904b 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/Signature.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/Signature.cs @@ -5,7 +5,6 @@ using System.Buffers; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using System.Text.Json.Serialization; using Nethermind.Core.Attributes; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -59,7 +58,6 @@ public Signature(string hexString) : this(Core.Extensions.Bytes.FromHexString(hexString)) { } - [JsonIgnore] public Span Bytes => MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref _signature, 1)); public override Memory Memory => CreateMemory(64); @@ -72,10 +70,8 @@ public Signature(string hexString) public static byte GetRecoveryId(ulong v) => v <= VOffset + 1 ? (byte)(v - VOffset) : (byte)(1 - v % 2); public Memory R => Memory.Slice(0, 32); - [JsonIgnore] public ReadOnlySpan RAsSpan => Bytes.Slice(0, 32); public Memory S => Memory.Slice(32, 32); - [JsonIgnore] public ReadOnlySpan SAsSpan => Bytes.Slice(32, 32); [Todo("Change signature to store 65 bytes and just slice it for normal Bytes.")] diff --git a/src/Nethermind/Nethermind.Core/Buffers/DbSpanMemoryManager.cs b/src/Nethermind/Nethermind.Core/DbSpanMemoryManager.cs similarity index 58% rename from src/Nethermind/Nethermind.Core/Buffers/DbSpanMemoryManager.cs rename to src/Nethermind/Nethermind.Core/DbSpanMemoryManager.cs index 18a32b032a88..48d881ae00c0 100644 --- a/src/Nethermind/Nethermind.Core/Buffers/DbSpanMemoryManager.cs +++ b/src/Nethermind/Nethermind.Core/DbSpanMemoryManager.cs @@ -10,16 +10,24 @@ namespace Nethermind.Core.Buffers; -public sealed unsafe class DbSpanMemoryManager(IReadOnlyKeyValueStore db, Span unmanagedSpan) : MemoryManager +public unsafe sealed class DbSpanMemoryManager : MemoryManager { - private void* _ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(unmanagedSpan)); - private readonly int _length = unmanagedSpan.Length; + private readonly IReadOnlyKeyValueStore _db; + private void* _ptr; + private readonly int _length; + + public DbSpanMemoryManager(IReadOnlyKeyValueStore db, Span unmanagedSpan) + { + _db = db; + _ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(unmanagedSpan)); + _length = unmanagedSpan.Length; + } protected override void Dispose(bool disposing) { if (_ptr is not null) { - db.DangerousReleaseMemory(GetSpan()); + _db.DangerousReleaseMemory(GetSpan()); } _ptr = null; @@ -45,8 +53,13 @@ public override MemoryHandle Pin(int elementIndex = 0) return new MemoryHandle(_ptr); } - public override void Unpin() { } + public override void Unpin() + { + } [DoesNotReturn, StackTraceHidden] - private static void ThrowDisposed() => throw new ObjectDisposedException(nameof(DbSpanMemoryManager)); + private static void ThrowDisposed() + { + throw new ObjectDisposedException(nameof(DbSpanMemoryManager)); + } } diff --git a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs index 4fe4d044ee55..8e395ecb7208 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs @@ -73,17 +73,64 @@ public override int Compare(byte[]? x, byte[]? y) if (x is null) { - return y is null ? 0 : -1; + return y is null ? 0 : 1; } - if (y is null) return 1; + if (y is null) + { + return -1; + } + + if (x.Length == 0) + { + return y.Length == 0 ? 0 : 1; + } + + for (int i = 0; i < x.Length; i++) + { + if (y.Length <= i) + { + return -1; + } - return x.SequenceCompareTo(y); + int result = x[i].CompareTo(y[i]); + if (result != 0) + { + return result; + } + } + + return y.Length > x.Length ? 1 : 0; } public static int Compare(ReadOnlySpan x, ReadOnlySpan y) { - return x.SequenceCompareTo(y); + if (Unsafe.AreSame(ref MemoryMarshal.GetReference(x), ref MemoryMarshal.GetReference(y)) && + x.Length == y.Length) + { + return 0; + } + + if (x.Length == 0) + { + return y.Length == 0 ? 0 : 1; + } + + for (int i = 0; i < x.Length; i++) + { + if (y.Length <= i) + { + return -1; + } + + int result = x[i].CompareTo(y[i]); + if (result != 0) + { + return result; + } + } + + return y.Length > x.Length ? 1 : 0; } } diff --git a/src/Nethermind/Nethermind.Core/Extensions/DictionaryExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/DictionaryExtensions.cs new file mode 100644 index 000000000000..6168e96121f4 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Extensions/DictionaryExtensions.cs @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Core.Resettables; + +namespace Nethermind.Core.Extensions; + +public static class DictionaryExtensions +{ + /// + /// Returns all values in the dictionary to their pool by calling on each value, + /// then clears the dictionary. + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary, which must implement . + /// The dictionary whose values will be returned and cleared. + /// + /// Use this method when you need to both return pooled objects and clear the dictionary in one operation. + /// + public static void ResetAndClear(this IDictionary dictionary) + where TValue : class, IReturnable + { + foreach (TValue value in dictionary.Values) + { + value.Return(); + } + dictionary.Clear(); + } +} diff --git a/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs index 3f4a9e0c2919..1ecd2adc0d84 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs @@ -29,6 +29,13 @@ public static UInt256 GWei(this int @this) return (uint)@this * Unit.GWei; } + public static byte[] ToByteArray(this int value) + { + byte[] bytes = new byte[sizeof(int)]; + BinaryPrimitives.WriteInt32BigEndian(bytes, value); + return bytes; + } + public static byte[] ToBigEndianByteArray(this uint value) { byte[] bytes = BitConverter.GetBytes(value); @@ -42,14 +49,4 @@ public static byte[] ToBigEndianByteArray(this uint value) public static byte[] ToBigEndianByteArray(this int value) => ToBigEndianByteArray((uint)value); - - public static byte[] ToLittleEndianByteArray(this uint value) - { - byte[] bytes = new byte[sizeof(uint)]; - BinaryPrimitives.WriteUInt32LittleEndian(bytes, value); - return bytes; - } - - public static byte[] ToLittleEndianByteArray(this int value) - => ToLittleEndianByteArray((uint)value); } diff --git a/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs index 7ae71df3f508..5118ea293bb2 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs @@ -7,9 +7,6 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using Arm = System.Runtime.Intrinsics.Arm; -using x64 = System.Runtime.Intrinsics.X86; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; @@ -22,8 +19,6 @@ public static class SpanExtensions // the performance of the network as a whole. private static readonly uint s_instanceRandom = (uint)System.Security.Cryptography.RandomNumberGenerator.GetInt32(int.MinValue, int.MaxValue); - internal static uint ComputeSeed(int len) => s_instanceRandom + (uint)len; - public static string ToHexString(this in Memory memory, bool withZeroX = false) { return ToHexString(memory.Span, withZeroX, false, false); @@ -229,286 +224,149 @@ public static ArrayPoolListRef ToPooledListRef(this in ReadOnlySpan spa [SkipLocalsInit] public static int FastHash(this ReadOnlySpan input) { + // Fast hardware-accelerated, non-cryptographic hash. + // Core idea: CRC32C is extremely cheap on CPUs with SSE4.2/ARM CRC, + // and gives good diffusion for hashing. We then optionally add extra + // mixing to reduce "CRC linearity" artifacts. + int len = input.Length; - if (len == 0) return 0; + // Contract choice: empty input hashes to 0. + // (Also avoids doing any ref work on an empty span.) + if (len == 0) return 0; + // Using ref + Unsafe.ReadUnaligned lets the JIT hoist bounds checks + // and keep the hot loop tight. ref byte start = ref MemoryMarshal.GetReference(input); + + // Seed with an instance-random value so attackers cannot trivially + // engineer lots of same-bucket keys. Mixing in length makes "same prefix, + // different length" less correlated (CRC alone can be length-sensitive). uint seed = s_instanceRandom + (uint)len; - if (len >= 16) + // Small: 1-7 bytes. + // Using the tail routine here avoids building a synthetic + // 64-bit value with shifts/byte-permute. + if (len < 8) { - if (x64.Aes.IsSupported) return FastHashAesX64(ref start, len, seed); - if (Arm.Aes.IsSupported) return FastHashAesArm(ref start, len, seed); + uint small = CrcTailOrdered(seed, ref start, len); + // FinalMix breaks some remaining linearity and improves avalanche for tiny inputs. + return (int)FinalMix(small); } - return FastHashCrc(ref start, len, seed); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - [SkipLocalsInit] - internal static int FastHashAesX64(ref byte start, int len, uint seed) - { - Vector128 seedVec = Vector128.CreateScalar(seed).AsByte(); - Vector128 acc0 = Unsafe.As>(ref start) ^ seedVec; - - if (len > 64) + // Medium: 8-31 bytes. + // A single CRC lane is usually fine here - overhead dominates, + // and latency hiding is less important. + if (len < 32) { - Vector128 acc1 = Unsafe.As>(ref Unsafe.Add(ref start, 16)) ^ seedVec; - Vector128 acc2 = Unsafe.As>(ref Unsafe.Add(ref start, 32)) ^ seedVec; - Vector128 acc3 = Unsafe.As>(ref Unsafe.Add(ref start, 48)) ^ seedVec; + uint h = seed; + ref byte p = ref start; - ref byte p = ref Unsafe.Add(ref start, 64); - int remaining = len - 64; + // Process as many full 64-bit words as possible. + // "& ~7" is a cheap round-down-to-multiple-of-8 (no division/mod). + int full = len & ~7; + int tail = len - full; - while (remaining >= 64) + // Streaming CRC over 8-byte chunks. + // ReadUnaligned keeps us safe for arbitrary input alignment. + for (int i = 0; i < full; i += 8) { - acc0 = x64.Aes.Encrypt(Unsafe.As>(ref p), acc0); - acc1 = x64.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 16)), acc1); - acc2 = x64.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 32)), acc2); - acc3 = x64.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 48)), acc3); - - p = ref Unsafe.Add(ref p, 64); - remaining -= 64; + h = BitOperations.Crc32C(h, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 8); } - // Fold 4 lanes: 3 XOR + 1 AES (minimal serial latency) - acc0 ^= acc1; - acc2 ^= acc3; - acc0 ^= acc2; - acc0 = x64.Aes.Encrypt(seedVec, acc0); + // Hash remaining 1-7 bytes in strict order (no over-read). + if (tail != 0) + h = CrcTailOrdered(h, ref p, tail); - // Drain remaining 0-63 bytes - while (remaining >= 16) - { - acc0 = x64.Aes.Encrypt(Unsafe.As>(ref p), acc0); - p = ref Unsafe.Add(ref p, 16); - remaining -= 16; - } - - // Remaining 1-15 bytes: use CRC to avoid overlap with drain blocks - if (remaining > 0) - { - uint crc = seed; - if (remaining >= 8) - { - crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); - p = ref Unsafe.Add(ref p, 8); - remaining -= 8; - } - if ((remaining & 4) != 0) - { - crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); - p = ref Unsafe.Add(ref p, 4); - } - if ((remaining & 2) != 0) - { - crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); - p = ref Unsafe.Add(ref p, 2); - } - if ((remaining & 1) != 0) - { - crc = BitOperations.Crc32C(crc, p); - } - acc0 = x64.Aes.Encrypt(Vector128.CreateScalar(crc).AsByte(), acc0); - } + // Final mixing for better bit diffusion than raw CRC, + // especially for shorter payloads. + return (int)FinalMix(h); } - else if (len > 32) - { - ref byte p = ref Unsafe.Add(ref start, 16); - int remaining = len - 16; - while (remaining > 16) - { - acc0 = x64.Aes.Encrypt(Unsafe.As>(ref p), acc0); - p = ref Unsafe.Add(ref p, 16); - remaining -= 16; - } - - Vector128 last = Unsafe.As>(ref Unsafe.Add(ref start, len - 16)); - acc0 = x64.Aes.Encrypt(last, acc0); - } - else + // Large: 32+ bytes. + // Use multiple independent CRC accumulators ("lanes") to hide crc32 + // latency and increase ILP. CRC32C instructions have decent throughput + // but non-trivial latency; 4 lanes keeps the CPU busy. + uint h0 = seed; + uint h1 = seed ^ 0x9E3779B9u; // golden-ratio-ish constants to separate lanes + uint h2 = seed ^ 0x85EBCA6Bu; // constants borrowed from common finalizers (good bit dispersion) + uint h3 = seed ^ 0xC2B2AE35u; + + ref byte q = ref start; + + // Consume all full 64-bit words first. Tail (1-7 bytes) is handled later. + int aligned = len & ~7; + int remaining = aligned; + + // 64-byte unroll: + // - amortizes loop branch/compare overhead + // - feeds enough independent work to keep OoO cores busy + // - maps nicely onto cache line sized chunks + while (remaining >= 64) { - Vector128 data = Unsafe.As>(ref Unsafe.Add(ref start, len - 16)); - acc0 = x64.Aes.Encrypt(data, acc0); + h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); + h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); + h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); + h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 24))); + + h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 32))); + h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 40))); + h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 48))); + h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 56))); + + q = ref Unsafe.Add(ref q, 64); + remaining -= 64; } - ulong compressed = acc0.AsUInt64().GetElement(0) ^ acc0.AsUInt64().GetElement(1); - return (int)(uint)(compressed ^ (compressed >> 32)); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - [SkipLocalsInit] - internal static int FastHashAesArm(ref byte start, int len, uint seed) - { - Vector128 seedVec = Vector128.CreateScalar(seed).AsByte(); - Vector128 acc0 = Unsafe.As>(ref start) ^ seedVec; - - if (len > 64) + // One more half-unroll for 32 bytes if present. + // Keeps the "drain" path short and avoids a smaller loop with more branches. + if (remaining >= 32) { - Vector128 acc1 = Unsafe.As>(ref Unsafe.Add(ref start, 16)) ^ seedVec; - Vector128 acc2 = Unsafe.As>(ref Unsafe.Add(ref start, 32)) ^ seedVec; - Vector128 acc3 = Unsafe.As>(ref Unsafe.Add(ref start, 48)) ^ seedVec; - - ref byte p = ref Unsafe.Add(ref start, 64); - int remaining = len - 64; - - while (remaining >= 64) - { - acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref p), acc0)); - acc1 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 16)), acc1)); - acc2 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 32)), acc2)); - acc3 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 48)), acc3)); - - p = ref Unsafe.Add(ref p, 64); - remaining -= 64; - } - - acc0 ^= acc1; - acc2 ^= acc3; - acc0 ^= acc2; - acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(seedVec, acc0)); - - while (remaining >= 16) - { - acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref p), acc0)); - p = ref Unsafe.Add(ref p, 16); - remaining -= 16; - } + h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); + h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); + h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); + h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 24))); - if (remaining > 0) - { - uint crc = seed; - if (remaining >= 8) - { - crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); - p = ref Unsafe.Add(ref p, 8); - remaining -= 8; - } - if ((remaining & 4) != 0) - { - crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); - p = ref Unsafe.Add(ref p, 4); - } - if ((remaining & 2) != 0) - { - crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); - p = ref Unsafe.Add(ref p, 2); - } - if ((remaining & 1) != 0) - { - crc = BitOperations.Crc32C(crc, p); - } - acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Vector128.CreateScalar(crc).AsByte(), acc0)); - } + q = ref Unsafe.Add(ref q, 32); + remaining -= 32; } - else if (len > 32) - { - ref byte p = ref Unsafe.Add(ref start, 16); - int remaining = len - 16; - while (remaining > 16) - { - acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref p), acc0)); - p = ref Unsafe.Add(ref p, 16); - remaining -= 16; - } - - Vector128 last = Unsafe.As>(ref Unsafe.Add(ref start, len - 16)); - acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(last, acc0)); - } - else + // Drain any remaining full 64-bit words (0, 8, 16, or 24 bytes). + // This is branchy but only runs once, so it is cheaper than another loop. + if (remaining != 0) { - Vector128 data = Unsafe.As>(ref Unsafe.Add(ref start, len - 16)); - acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(data, acc0)); - } - - ulong compressed = acc0.AsUInt64().GetElement(0) ^ acc0.AsUInt64().GetElement(1); - return (int)(uint)(compressed ^ (compressed >> 32)); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - [SkipLocalsInit] - internal static int FastHashCrc(ref byte start, int len, uint seed) - { - uint hash; - if (len < 16) - { - if (len >= 8) - { - ulong lo = Unsafe.ReadUnaligned(ref start); - ulong hi = Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, len - 8)); - uint h0 = BitOperations.Crc32C(seed, lo); - uint h1 = BitOperations.Crc32C(seed ^ 0x9E3779B9u, hi); - hash = h0 + BitOperations.RotateLeft(h1, 11); - } - else - { - hash = CrcTailOrdered(seed, ref start, len); - } - } - else - { - uint h0 = seed; - uint h1 = seed ^ 0x9E3779B9u; - uint h2 = seed ^ 0x85EBCA6Bu; - uint h3 = seed ^ 0xC2B2AE35u; - - ref byte q = ref start; - int aligned = len & ~7; - int remaining = aligned; - - while (remaining >= 64) - { - h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); - h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); - h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); - h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 24))); - - h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 32))); - h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 40))); - h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 48))); - h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 56))); - - q = ref Unsafe.Add(ref q, 64); - remaining -= 64; - } - - if (remaining >= 32) - { - h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); - h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); - h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); - h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 24))); - - q = ref Unsafe.Add(ref q, 32); - remaining -= 32; - } - + // remaining is a multiple of 8 here. if (remaining >= 8) h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); if (remaining >= 16) h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); - if (remaining >= 24) h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); - - h2 = BitOperations.RotateLeft(h2, 17) + BitOperations.RotateLeft(h3, 23); - h0 += BitOperations.RotateLeft(h1, 11); - hash = h2 + h0; + if (remaining == 24) h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); + } - int tailBytes = len - aligned; - if (tailBytes != 0) - { - ref byte tailRef = ref Unsafe.Add(ref start, aligned); - hash = CrcTailOrdered(hash, ref tailRef, tailBytes); - } + // Fold lanes down to one 32-bit value. + // Rotates permute bit positions so each lane contributes differently. + // Adds (rather than XOR) deliberately introduce carries + // - CRC is linear over GF(2), and carry breaks that, making simple algebraic + // structure harder to exploit for collision clustering in hash tables. + h2 = BitOperations.RotateLeft(h2, 17) + BitOperations.RotateLeft(h3, 23); + h0 += BitOperations.RotateLeft(h1, 11); + uint hash = h2 + h0; + + // Handle tail bytes (1-7 bytes) that were not part of the 64-bit-aligned stream. + // This is exact, in-order processing - no overlap and no over-read. + int tailBytes = len - aligned; + if (tailBytes != 0) + { + ref byte tailRef = ref Unsafe.Add(ref start, aligned); + hash = CrcTailOrdered(hash, ref tailRef, tailBytes); } - hash ^= hash >> 16; - hash *= 0x9E3779B1u; - hash ^= hash >> 16; - return (int)hash; + // FinalMix breaks some remaining linearity and improves avalanche + return (int)FinalMix(hash); [MethodImpl(MethodImplOptions.AggressiveInlining)] static uint CrcTailOrdered(uint hash, ref byte p, int length) { + // length is 1..7 + // Process 4-2-1 bytes in natural order if ((length & 4) != 0) { hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned(ref p)); @@ -525,70 +383,19 @@ static uint CrcTailOrdered(uint hash, ref byte p, int length) } return hash; } - } - - /// - /// Computes a very fast, non-cryptographic 64-bit hash of exactly 32 bytes. - /// - /// Reference to the first byte of the 32-byte input. - /// A 64-bit hash value with good distribution across all bits. - /// - /// Uses AES hardware acceleration when available, falls back to CRC32C otherwise. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long FastHash64For32Bytes(ref byte start) - { - uint seed = s_instanceRandom + 32; - - if (x64.Aes.IsSupported || Arm.Aes.IsSupported) - { - Vector128 key = Unsafe.As>(ref start); - Vector128 data = Unsafe.As>(ref Unsafe.Add(ref start, 16)); - key ^= Vector128.CreateScalar(seed).AsByte(); - Vector128 mixed = x64.Aes.IsSupported - ? x64.Aes.Encrypt(data, key) - : Arm.Aes.MixColumns(Arm.Aes.Encrypt(data, key)); - return (long)(mixed.AsUInt64().GetElement(0) ^ mixed.AsUInt64().GetElement(1)); - } - - // Fallback: CRC32C-based 64-bit hash - ulong h0 = BitOperations.Crc32C(seed, Unsafe.ReadUnaligned(ref start)); - ulong h1 = BitOperations.Crc32C(seed ^ 0x9E3779B9u, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 8))); - ulong h2 = BitOperations.Crc32C(seed ^ 0x85EBCA6Bu, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 16))); - ulong h3 = BitOperations.Crc32C(seed ^ 0xC2B2AE35u, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 24))); - return (long)((h0 | (h1 << 32)) ^ (h2 | (h3 << 32))); - } - /// - /// Computes a very fast, non-cryptographic 64-bit hash of exactly 20 bytes (Address size). - /// - /// Reference to the first byte of the 20-byte input. - /// A 64-bit hash value with good distribution across all bits. - /// - /// Uses AES hardware acceleration when available, falls back to CRC32C otherwise. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long FastHash64For20Bytes(ref byte start) - { - uint seed = s_instanceRandom + 20; - - if (x64.Aes.IsSupported || Arm.Aes.IsSupported) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static uint FinalMix(uint x) { - Vector128 key = Unsafe.As>(ref start); - uint last4 = Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 16)); - Vector128 data = Vector128.CreateScalar(last4).AsByte(); - key ^= Vector128.CreateScalar(seed).AsByte(); - Vector128 mixed = x64.Aes.IsSupported - ? x64.Aes.Encrypt(data, key) - : Arm.Aes.MixColumns(Arm.Aes.Encrypt(data, key)); - return (long)(mixed.AsUInt64().GetElement(0) ^ mixed.AsUInt64().GetElement(1)); + // A tiny finalizer to improve avalanche: + // - xor-fold high bits down + // - multiply by an odd constant to spread changes across bits + // - xor-fold again to propagate the multiply result + x ^= x >> 16; + x *= 0x9E3779B1u; + x ^= x >> 16; + return x; } - - // Fallback: CRC32C-based 64-bit hash - ulong h0 = BitOperations.Crc32C(seed, Unsafe.ReadUnaligned(ref start)); - ulong h1 = BitOperations.Crc32C(seed ^ 0x9E3779B9u, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 8))); - uint h2 = BitOperations.Crc32C(seed ^ 0x85EBCA6Bu, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 16))); - return (long)((h0 | (h1 << 32)) ^ ((ulong)h2 * 0x9E3779B97F4A7C15)); } } } diff --git a/src/Nethermind/Nethermind.Core/IKeyValueStore.cs b/src/Nethermind/Nethermind.Core/IKeyValueStore.cs index d30dbcfe87df..d80cfc3d40ae 100644 --- a/src/Nethermind/Nethermind.Core/IKeyValueStore.cs +++ b/src/Nethermind/Nethermind.Core/IKeyValueStore.cs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; -using Nethermind.Core.Buffers; using Nethermind.Core.Extensions; namespace Nethermind.Core @@ -24,12 +22,10 @@ public interface IReadOnlyKeyValueStore byte[]? Get(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None); /// - /// Return span. Must call after use to avoid memory leaks. - /// Prefer using which handles release automatically via disposal. + /// Return span. Must call `DangerousReleaseMemory` or there can be some leak. /// - /// Key whose associated value should be read. - /// Read behavior flags that control how the value is retrieved. - /// Can return null or empty Span on a missing key + /// + /// Can return null or empty Span on missing key Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => Get(key, flags); /// @@ -67,19 +63,6 @@ bool KeyExists(ReadOnlySpan key) } void DangerousReleaseMemory(in ReadOnlySpan span) { } - - /// - /// Returns a MemoryManager wrapping the value for the given key. - /// The MemoryManager must be disposed of when done to release any underlying resources. - /// - /// Key whose associated value should be read. - /// Read behavior flags that control how the value is retrieved. - /// A MemoryManager wrapping the value or null if the key doesn't exist. - MemoryManager? GetOwnedMemory(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - { - byte[]? data = Get(key, flags); - return data is null or { Length: 0 } ? null : new ArrayMemoryManager(data); - } } public interface IReadOnlyNativeKeyValueStore diff --git a/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs b/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs index 1d1c0aa88199..4edda62a99ba 100644 --- a/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs +++ b/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs @@ -185,69 +185,16 @@ public override void Write( Convert(writer, bytes, skipLeadingZeros: false); } - /// - /// Writes bytes as a hex string value (e.g. "0xabcd") using WriteRawValue. - /// [SkipLocalsInit] public static void Convert(Utf8JsonWriter writer, ReadOnlySpan bytes, bool skipLeadingZeros = true, bool addHexPrefix = true) { - int leadingNibbleZeros = skipLeadingZeros ? bytes.CountLeadingNibbleZeros() : 0; - int nibblesCount = bytes.Length * 2; - - if (skipLeadingZeros && nibblesCount is not 0 && leadingNibbleZeros == nibblesCount) - { - WriteZeroValue(writer); - return; - } - - int prefixLength = addHexPrefix ? 2 : 0; - // +2 for surrounding quotes: "0xABCD..." - int rawLength = nibblesCount - leadingNibbleZeros + prefixLength + 2; - - byte[]? array = null; - Unsafe.SkipInit(out HexBuffer256 buffer); - Span hex = rawLength <= 256 - ? MemoryMarshal.CreateSpan(ref Unsafe.As(ref buffer), 256) - : (array = ArrayPool.Shared.Rent(rawLength)); - hex = hex[..rawLength]; - - // Build the JSON string value directly: "0x" - ref byte hexRef = ref MemoryMarshal.GetReference(hex); - hexRef = (byte)'"'; - int start = 1; - if (addHexPrefix) - { - Unsafe.As(ref Unsafe.Add(ref hexRef, 1)) = HexPrefix; - start = 3; - } - Unsafe.Add(ref hexRef, rawLength - 1) = (byte)'"'; - - int offset = leadingNibbleZeros >>> 1; - MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref MemoryMarshal.GetReference(bytes), offset), bytes.Length - offset) - .OutputBytesToByteHex( - MemoryMarshal.CreateSpan(ref Unsafe.Add(ref hexRef, start), rawLength - 1 - start), - extraNibble: (leadingNibbleZeros & 1) != 0); - // Hex chars (0-9, a-f) never need JSON escaping — bypass encoder entirely - writer.WriteRawValue(hex, skipInputValidation: true); - - if (array is not null) - ArrayPool.Shared.Return(array); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private static void WriteZeroValue(Utf8JsonWriter writer) => writer.WriteStringValue("0x0"u8); - - [InlineArray(256)] - private struct HexBuffer256 - { - private byte _element0; + Convert(writer, + bytes, + static (w, h) => w.WriteRawValue(h, skipInputValidation: true), skipLeadingZeros, addHexPrefix: addHexPrefix); } public delegate void WriteHex(Utf8JsonWriter writer, ReadOnlySpan hex); - /// - /// Writes bytes as hex using a custom write action (e.g. for property names). - /// [SkipLocalsInit] public static void Convert( Utf8JsonWriter writer, @@ -257,57 +204,51 @@ public static void Convert( bool addQuotations = true, bool addHexPrefix = true) { - int leadingNibbleZeros = skipLeadingZeros ? bytes.CountLeadingNibbleZeros() : 0; - int nibblesCount = bytes.Length * 2; + const int maxStackLength = 128; + const int stackLength = 256; + + var leadingNibbleZeros = skipLeadingZeros ? bytes.CountLeadingNibbleZeros() : 0; + var nibblesCount = bytes.Length * 2; if (skipLeadingZeros && nibblesCount is not 0 && leadingNibbleZeros == nibblesCount) { - WriteZeroValue(writer, writeAction, addQuotations); + writer.WriteStringValue(Bytes.ZeroHexValue); return; } - int prefixLength = addHexPrefix ? 2 : 0; - int quotesLength = addQuotations ? 2 : 0; - int length = nibblesCount - leadingNibbleZeros + prefixLength + quotesLength; + var prefixLength = addHexPrefix ? 2 : 0; + var length = nibblesCount - leadingNibbleZeros + prefixLength + (addQuotations ? 2 : 0); byte[]? array = null; - Unsafe.SkipInit(out HexBuffer256 buffer); - Span hex = length <= 256 - ? MemoryMarshal.CreateSpan(ref Unsafe.As(ref buffer), 256) - : (array = ArrayPool.Shared.Rent(length)); - hex = hex[..length]; + if (length > maxStackLength) + array = ArrayPool.Shared.Rent(length); - ref byte hexRef = ref MemoryMarshal.GetReference(hex); - int start = 0; - int endPad = 0; + Span hex = (array ?? stackalloc byte[stackLength])[..length]; + var start = 0; + Index end = ^0; if (addQuotations) { - hexRef = (byte)'"'; - Unsafe.Add(ref hexRef, length - 1) = (byte)'"'; - start = 1; - endPad = 1; + end = ^1; + hex[^1] = (byte)'"'; + hex[start++] = (byte)'"'; } if (addHexPrefix) { - Unsafe.As(ref Unsafe.Add(ref hexRef, start)) = HexPrefix; - start += 2; + hex[start++] = (byte)'0'; + hex[start++] = (byte)'x'; } - ReadOnlySpan input = bytes[(leadingNibbleZeros >>> 1)..]; - input.OutputBytesToByteHex( - MemoryMarshal.CreateSpan(ref Unsafe.Add(ref hexRef, start), length - start - endPad), - extraNibble: (leadingNibbleZeros & 1) != 0); + Span output = hex[start..end]; + + ReadOnlySpan input = bytes[(leadingNibbleZeros / 2)..]; + input.OutputBytesToByteHex(output, extraNibble: (leadingNibbleZeros & 1) != 0); writeAction(writer, hex); if (array is not null) ArrayPool.Shared.Return(array); } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void WriteZeroValue(Utf8JsonWriter writer, WriteHex writeAction, bool addQuotations) - => writeAction(writer, addQuotations ? "\"0x0\""u8 : "0x0"u8); - public override byte[] ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { byte[]? result = Convert(ref reader); diff --git a/src/Nethermind/Nethermind.Core/KeyValueStoreExtensions.cs b/src/Nethermind/Nethermind.Core/KeyValueStoreExtensions.cs index 0220b81ae103..906b39acf45d 100644 --- a/src/Nethermind/Nethermind.Core/KeyValueStoreExtensions.cs +++ b/src/Nethermind/Nethermind.Core/KeyValueStoreExtensions.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Runtime.CompilerServices; using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; @@ -11,108 +12,114 @@ namespace Nethermind.Core { public static class KeyValueStoreExtensions { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void GuardKey(Hash256 key) + public static IWriteBatch LikeABatch(this IWriteOnlyKeyValueStore keyValueStore) + { + return LikeABatch(keyValueStore, null); + } + + public static IWriteBatch LikeABatch(this IWriteOnlyKeyValueStore keyValueStore, Action? onDispose) + { + return new FakeWriteBatch(keyValueStore, onDispose); + } + + #region Getters + + public static byte[]? Get(this IReadOnlyKeyValueStore db, Hash256 key) { #if DEBUG - if (key == Keccak.OfAnEmptyString) throw new InvalidOperationException(); + if (key == Keccak.OfAnEmptyString) + { + throw new InvalidOperationException(); + } #endif + + return db[key.Bytes]; } + /// + /// + /// /// - extension(IReadOnlyKeyValueStore db) + /// + /// Can return null or empty Span on missing key + /// + public static Span GetSpan(this IReadOnlyKeyValueStore db, Hash256 key) { - public byte[]? Get(Hash256 key) +#if DEBUG + if (key == Keccak.OfAnEmptyString) { - GuardKey(key); - return db[key.Bytes]; + throw new InvalidOperationException(); } +#endif - /// - /// - /// - /// - /// Can return null or empty Span on missing key - /// - public Span GetSpan(Hash256 key) - { - GuardKey(key); - return db.GetSpan(key.Bytes); - } + return db.GetSpan(key.Bytes); + } - public bool KeyExists(Hash256 key) + public static bool KeyExists(this IReadOnlyKeyValueStore db, Hash256 key) + { +#if DEBUG + if (key == Keccak.OfAnEmptyString) { - GuardKey(key); - return db.KeyExists(key.Bytes); + throw new InvalidOperationException(); } +#endif - public bool KeyExists(long key) => db.KeyExists(key.ToBigEndianSpanWithoutLeadingZeros(out _)); + return db.KeyExists(key.Bytes); + } - public byte[]? Get(long key) => db[key.ToBigEndianSpanWithoutLeadingZeros(out _)]; + public static bool KeyExists(this IReadOnlyKeyValueStore db, long key) + { + return db.KeyExists(key.ToBigEndianSpanWithoutLeadingZeros(out _)); } - extension(IWriteOnlyKeyValueStore db) + public static byte[]? Get(this IReadOnlyKeyValueStore db, long key) => db[key.ToBigEndianSpanWithoutLeadingZeros(out _)]; + + /// + /// + /// + /// + /// + /// Can return null or empty Span on missing key + public static Span GetSpan(this IReadOnlyKeyValueStore db, long key) => db.GetSpan(key.ToBigEndianSpanWithoutLeadingZeros(out _)); + + public static MemoryManager? GetOwnedMemory(this IReadOnlyKeyValueStore db, ReadOnlySpan key) { - public IWriteBatch LikeABatch(Action? onDispose = null) => new FakeWriteBatch(db, onDispose); + Span span = db.GetSpan(key); + return span.IsNullOrEmpty() ? null : new DbSpanMemoryManager(db, span); + } - public void Set(Hash256 key, byte[] value, WriteFlags writeFlags = WriteFlags.None) - { - if (db.PreferWriteByArray) - { - db.Set(key.Bytes, value, writeFlags); - } - else - { - db.PutSpan(key.Bytes, value, writeFlags); - } - } - public void Set(Hash256 key, in CappedArray value, WriteFlags writeFlags = WriteFlags.None) - { - if (db.PreferWriteByArray && value.IsUncapped) - { - db.Set(key.Bytes, value.UnderlyingArray, writeFlags); - } - else - { - db.PutSpan(key.Bytes, value.AsSpan(), writeFlags); - } - } + #endregion - public void Set(long blockNumber, Hash256 key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) - { - Span blockNumberPrefixedKey = stackalloc byte[40]; - GetBlockNumPrefixedKey(blockNumber, key, blockNumberPrefixedKey); - db.PutSpan(blockNumberPrefixedKey, value, writeFlags); - } - public void Set(in ValueHash256 key, Span value) - { - db.PutSpan(key.Bytes, value); - } + #region Setters - public void Delete(Hash256 key) + public static void Set(this IWriteOnlyKeyValueStore db, Hash256 key, byte[] value, WriteFlags writeFlags = WriteFlags.None) + { + if (db.PreferWriteByArray) { - db.Remove(key.Bytes); + db.Set(key.Bytes, value, writeFlags); + return; } + db.PutSpan(key.Bytes, value, writeFlags); + } - public void Delete(long key) + public static void Set(this IWriteOnlyKeyValueStore db, Hash256 key, in CappedArray value, WriteFlags writeFlags = WriteFlags.None) + { + if (value.IsUncapped && db.PreferWriteByArray) { - db.Remove(key.ToBigEndianSpanWithoutLeadingZeros(out _)); + db.PutSpan(key.Bytes, value.AsSpan(), writeFlags); + return; } - [SkipLocalsInit] - public void Delete(long blockNumber, Hash256 hash) - { - Span key = stackalloc byte[40]; - GetBlockNumPrefixedKey(blockNumber, hash, key); - db.Remove(key); - } + db.PutSpan(key.Bytes, value.AsSpan(), writeFlags); + } - public void Set(long key, byte[] value) - { - db[key.ToBigEndianSpanWithoutLeadingZeros(out _)] = value; - } + public static void Set(this IWriteOnlyKeyValueStore db, long blockNumber, Hash256 key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) + { + Span blockNumberPrefixedKey = stackalloc byte[40]; + GetBlockNumPrefixedKey(blockNumber, key, blockNumberPrefixedKey); + db.PutSpan(blockNumberPrefixedKey, value, writeFlags); } public static void GetBlockNumPrefixedKey(long blockNumber, ValueHash256 blockHash, Span output) @@ -120,5 +127,35 @@ public static void GetBlockNumPrefixedKey(long blockNumber, ValueHash256 blockHa blockNumber.WriteBigEndian(output); blockHash!.Bytes.CopyTo(output[8..]); } + + public static void Set(this IWriteOnlyKeyValueStore db, in ValueHash256 key, Span value) + { + db.PutSpan(key.Bytes, value); + } + + public static void Delete(this IWriteOnlyKeyValueStore db, Hash256 key) + { + db.Remove(key.Bytes); + } + + public static void Delete(this IWriteOnlyKeyValueStore db, long key) + { + db.Remove(key.ToBigEndianSpanWithoutLeadingZeros(out _)); + } + + [SkipLocalsInit] + public static void Delete(this IWriteOnlyKeyValueStore db, long blockNumber, Hash256 hash) + { + Span key = stackalloc byte[40]; + GetBlockNumPrefixedKey(blockNumber, hash, key); + db.Remove(key); + } + + public static void Set(this IWriteOnlyKeyValueStore db, long key, byte[] value) + { + db[key.ToBigEndianSpanWithoutLeadingZeros(out _)] = value; + } + + #endregion } } diff --git a/src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs b/src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs new file mode 100644 index 000000000000..a59bf7aadbe2 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading.Tasks; +using Nethermind.Core.Collections; + +namespace Nethermind.Core.PubSub +{ + public class CompositePublisher(params IPublisher[] publishers) : IPublisher + { + public async Task PublishAsync(T data) where T : class + { + using ArrayPoolList tasks = new(publishers.Length); + for (int i = 0; i < publishers.Length; i++) + { + tasks.Add(publishers[i].PublishAsync(data)); + } + + await Task.WhenAll(tasks.AsSpan()); + } + + public void Dispose() + { + foreach (IPublisher publisher in publishers) + { + publisher.Dispose(); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Core/StorageCell.cs b/src/Nethermind/Nethermind.Core/StorageCell.cs index d28c5648f364..a35bc2eda97c 100644 --- a/src/Nethermind/Nethermind.Core/StorageCell.cs +++ b/src/Nethermind/Nethermind.Core/StorageCell.cs @@ -6,7 +6,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; -using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -14,13 +13,12 @@ namespace Nethermind.Core { [DebuggerDisplay("{Address}->{Index}")] - public readonly struct StorageCell : IEquatable, IHash64bit + public readonly struct StorageCell : IEquatable { - private readonly AddressAsKey _address; private readonly UInt256 _index; private readonly bool _isHash; - public Address Address => _address.Value; + public Address Address { get; } public bool IsHash => _isHash; public UInt256 Index => _index; @@ -35,45 +33,21 @@ private ValueHash256 GetHash() public StorageCell(Address address, in UInt256 index) { - _address = address; + Address = address; _index = index; } public StorageCell(Address address, ValueHash256 hash) { - _address = address; + Address = address; _index = Unsafe.As(ref hash); _isHash = true; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(in StorageCell other) - { - if (_isHash != other._isHash) - return false; - - if (Unsafe.As>(ref Unsafe.AsRef(in _index)) != - Unsafe.As>(ref Unsafe.AsRef(in other._index))) - return false; - - // Inline 20-byte Address comparison: avoids the Address.Equals call - // that the JIT refuses to inline when called from deep inline chains - // (e.g. SeqlockCache.TryGetValue). Address.Bytes is always exactly 20 bytes. - Address a = _address.Value; - Address b = other._address.Value; - if (ReferenceEquals(a, b)) - return true; - - ref byte ab = ref MemoryMarshal.GetArrayDataReference(a.Bytes); - ref byte bb = ref MemoryMarshal.GetArrayDataReference(b.Bytes); - return Unsafe.As>(ref ab) == Unsafe.As>(ref bb) - && Unsafe.As(ref Unsafe.Add(ref ab, 16)) == Unsafe.As(ref Unsafe.Add(ref bb, 16)); - } - - public bool Equals(StorageCell other) => Equals(in other); - - public long GetHashCode64() - => SpanExtensions.FastHash64For32Bytes(ref Unsafe.As(ref Unsafe.AsRef(in _index))) ^ _address.Value.GetHashCode64(); + public bool Equals(StorageCell other) => + _isHash == other._isHash && + Unsafe.As>(ref Unsafe.AsRef(in _index)) == Unsafe.As>(ref Unsafe.AsRef(in other._index)) && + Address.Equals(other.Address); public override bool Equals(object? obj) { @@ -88,12 +62,12 @@ public override bool Equals(object? obj) public override int GetHashCode() { int hash = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in _index), 1)).FastHash(); - return hash ^ _address.Value.GetHashCode(); + return hash ^ Address.GetHashCode(); } public override string ToString() { - return $"{_address.Value}.{Index}"; + return $"{Address}.{Index}"; } } } diff --git a/src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs b/src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs new file mode 100644 index 000000000000..a6c80ed33fc7 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace Nethermind.Core.Utils; + +/// +/// Batches writes into a set of concurrent batches. For cases where throughput matter, but not atomicity. +/// +public class ConcurrentWriteBatcher : IWriteBatch +{ + private const long PersistEveryNWrite = 10000; + + private long _counter = 0; + private readonly ConcurrentQueue _batches = new(); + private readonly IKeyValueStoreWithBatching _underlyingDb; + private bool _disposing = false; + + public ConcurrentWriteBatcher(IKeyValueStoreWithBatching underlyingDb) + { + _underlyingDb = underlyingDb; + } + + public void Dispose() + { + _disposing = true; + foreach (IWriteBatch batch in _batches) + { + batch.Dispose(); + } + } + + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) + { + IWriteBatch currentBatch = RentWriteBatch(); + currentBatch.PutSpan(key, value, flags); + ReturnWriteBatch(currentBatch); + } + + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) + { + IWriteBatch currentBatch = RentWriteBatch(); + currentBatch.Merge(key, value, flags); + ReturnWriteBatch(currentBatch); + } + + public void Clear() + { + throw new NotSupportedException($"{nameof(ConcurrentWriteBatcher)} can not be cancelled."); + } + + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) + { + IWriteBatch currentBatch = RentWriteBatch(); + currentBatch.Set(key, value, flags); + ReturnWriteBatch(currentBatch); + } + + private void ReturnWriteBatch(IWriteBatch currentBatch) + { + long val = Interlocked.Increment(ref _counter); + if (val % PersistEveryNWrite == 0) + { + currentBatch.Dispose(); + } + else + { + _batches.Enqueue(currentBatch); + } + } + + private IWriteBatch RentWriteBatch() + { + if (_disposing) throw new InvalidOperationException("Trying to set while disposing"); + if (!_batches.TryDequeue(out IWriteBatch? currentBatch)) + { + currentBatch = _underlyingDb.StartWriteBatch(); + } + + return currentBatch; + } +} diff --git a/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs b/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs index 94633dfd94f6..af75f303e417 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs @@ -2,12 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using System.Collections.Generic; using Nethermind.Core; -using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; using RocksDbSharp; using IWriteBatch = Nethermind.Core.IWriteBatch; @@ -34,34 +31,52 @@ public ColumnDb(RocksDb rocksDb, DbOnTheRocks mainDb, string name) _reader = new RocksDbReader(mainDb, mainDb.CreateReadOptions, _iteratorManager, _columnFamily); } - public void Dispose() => _iteratorManager.Dispose(); - public string Name { get; } - - byte[]? IReadOnlyKeyValueStore.Get(ReadOnlySpan key, ReadFlags flags) => _reader.Get(key, flags); + public void Dispose() + { + _iteratorManager.Dispose(); + } - Span IReadOnlyKeyValueStore.GetSpan(scoped ReadOnlySpan key, ReadFlags flags) => _reader.GetSpan(key, flags); + public string Name { get; } - MemoryManager? IReadOnlyKeyValueStore.GetOwnedMemory(ReadOnlySpan key, ReadFlags flags) + byte[]? IReadOnlyKeyValueStore.Get(ReadOnlySpan key, ReadFlags flags) { - Span span = ((IReadOnlyKeyValueStore)this).GetSpan(key, flags); - return span.IsNullOrEmpty() ? null : new DbSpanMemoryManager(this, span); + return _reader.Get(key, flags); } + Span IReadOnlyKeyValueStore.GetSpan(scoped ReadOnlySpan key, ReadFlags flags) + { + return _reader.GetSpan(key, flags); + } - int IReadOnlyKeyValueStore.Get(scoped ReadOnlySpan key, Span output, ReadFlags flags) => _reader.Get(key, output, flags); + int IReadOnlyKeyValueStore.Get(scoped ReadOnlySpan key, Span output, ReadFlags flags) + { + return _reader.Get(key, output, flags); + } - bool IReadOnlyKeyValueStore.KeyExists(ReadOnlySpan key) => _reader.KeyExists(key); + bool IReadOnlyKeyValueStore.KeyExists(ReadOnlySpan key) + { + return _reader.KeyExists(key); + } - void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan key) => _reader.DangerousReleaseMemory(key); + void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan key) + { + _reader.DangerousReleaseMemory(key); + } - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) => + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) + { _mainDb.SetWithColumnFamily(key, _columnFamily, value, flags); + } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) => + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) + { _mainDb.SetWithColumnFamily(key, _columnFamily, value, writeFlags); + } - public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) => + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) + { _mainDb.MergeWithColumnFamily(key, _columnFamily, value, writeFlags); + } public KeyValuePair[] this[byte[][] keys] { @@ -91,48 +106,77 @@ public IEnumerable GetAllValues(bool ordered = false) return _mainDb.GetAllValuesCore(iterator); } - public IWriteBatch StartWriteBatch() => new ColumnsDbWriteBatch(this, (DbOnTheRocks.RocksDbWriteBatch)_mainDb.StartWriteBatch()); + public IWriteBatch StartWriteBatch() + { + return new ColumnsDbWriteBatch(this, (DbOnTheRocks.RocksDbWriteBatch)_mainDb.StartWriteBatch()); + } - private class ColumnsDbWriteBatch(ColumnDb columnDb, DbOnTheRocks.RocksDbWriteBatch underlyingWriteBatch) - : IWriteBatch + private class ColumnsDbWriteBatch : IWriteBatch { - public void Dispose() => underlyingWriteBatch.Dispose(); + private readonly ColumnDb _columnDb; + private readonly DbOnTheRocks.RocksDbWriteBatch _underlyingWriteBatch; - public void Clear() => underlyingWriteBatch.Clear(); + public ColumnsDbWriteBatch(ColumnDb columnDb, DbOnTheRocks.RocksDbWriteBatch underlyingWriteBatch) + { + _columnDb = columnDb; + _underlyingWriteBatch = underlyingWriteBatch; + } + + public void Dispose() + { + _underlyingWriteBatch.Dispose(); + } + + public void Clear() + { + _underlyingWriteBatch.Clear(); + } public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { if (value is null) { - underlyingWriteBatch.Delete(key, columnDb._columnFamily); + _underlyingWriteBatch.Delete(key, _columnDb._columnFamily); } else { - underlyingWriteBatch.Set(key, value, columnDb._columnFamily, flags); + _underlyingWriteBatch.Set(key, value, _columnDb._columnFamily, flags); } } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => - underlyingWriteBatch.Set(key, value, columnDb._columnFamily, flags); + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) + { + _underlyingWriteBatch.Set(key, value, _columnDb._columnFamily, flags); + } - public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => - underlyingWriteBatch.Merge(key, value, columnDb._columnFamily, flags); + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) + { + _underlyingWriteBatch.Merge(key, value, _columnDb._columnFamily, flags); + } } - public void Remove(ReadOnlySpan key) => Set(key, null); + public void Remove(ReadOnlySpan key) + { + Set(key, null); + } - public void Flush(bool onlyWal) => _mainDb.FlushWithColumnFamily(_columnFamily); + public void Flush(bool onlyWal) + { + _mainDb.FlushWithColumnFamily(_columnFamily); + } - public void Compact() => + public void Compact() + { _rocksDb.CompactRange(Keccak.Zero.BytesToArray(), Keccak.MaxValue.BytesToArray(), _columnFamily); + } /// /// Not sure how to handle delete of the columns DB /// /// - public void Clear() => throw new NotSupportedException(); + public void Clear() { throw new NotSupportedException(); } - // Maybe it should be column-specific metric? + // Maybe it should be column specific metric? public IDbMeta.DbMetric GatherMetric() => _mainDb.GatherMetric(); public byte[]? FirstKey @@ -155,8 +199,10 @@ public byte[]? LastKey } } - public ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey) => - _mainDb.GetViewBetween(firstKey, lastKey, _columnFamily); + public ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey) + { + return _mainDb.GetViewBetween(firstKey, lastKey, _columnFamily); + } public IKeyValueStoreSnapshot CreateSnapshot() { diff --git a/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs b/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs index 5b22cc4f0580..b631d969a28b 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs @@ -105,19 +105,24 @@ protected override void ApplyOptions(IDictionary options) private class RocksColumnsWriteBatch : IColumnsWriteBatch { - internal readonly RocksDbWriteBatch WriteBatch; + internal RocksDbWriteBatch _writeBatch; private readonly ColumnsDb _columnsDb; public RocksColumnsWriteBatch(ColumnsDb columnsDb) { - WriteBatch = new RocksDbWriteBatch(columnsDb); + _writeBatch = new RocksDbWriteBatch(columnsDb); _columnsDb = columnsDb; } - public IWriteBatch GetColumnBatch(T key) => new RocksColumnWriteBatch(_columnsDb._columnDbs[key], this); + public IWriteBatch GetColumnBatch(T key) + { + return new RocksColumnWriteBatch(_columnsDb._columnDbs[key], this); + } - public void Clear() => WriteBatch.Clear(); - public void Dispose() => WriteBatch.Dispose(); + public void Dispose() + { + _writeBatch.Dispose(); + } } private class RocksColumnWriteBatch : IWriteBatch @@ -138,17 +143,17 @@ public void Dispose() public void Clear() { - _writeBatch.WriteBatch.Clear(); + _writeBatch._writeBatch.Clear(); } public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { - _writeBatch.WriteBatch.Set(key, value, _column._columnFamily, flags); + _writeBatch._writeBatch.Set(key, value, _column._columnFamily, flags); } public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) { - _writeBatch.WriteBatch.Merge(key, value, _column._columnFamily, flags); + _writeBatch._writeBatch.Merge(key, value, _column._columnFamily, flags); } } diff --git a/src/Nethermind/Nethermind.Db.Rocks/Config/DbConfig.cs b/src/Nethermind/Nethermind.Db.Rocks/Config/DbConfig.cs index f97bccfd637b..c6004bcfc716 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/Config/DbConfig.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/Config/DbConfig.cs @@ -271,19 +271,4 @@ public class DbConfig : IDbConfig public string L1OriginDbRocksDbOptions { get; set; } = ""; public string? L1OriginDbAdditionalRocksDbOptions { get; set; } - - public string LogIndexStorageDbRocksDbOptions { get; set; } = ""; - public string LogIndexStorageDbAdditionalRocksDbOptions { get; set; } = ""; - public string LogIndexStorageMetaDbRocksDbOptions { get; set; } = ""; - public string LogIndexStorageMetaDbAdditionalRocksDbOptions { get; set; } = ""; - public string LogIndexStorageAddressesDbRocksDbOptions { get; set; } = ""; - public string LogIndexStorageAddressesDbAdditionalRocksDbOptions { get; set; } = ""; - public string LogIndexStorageTopics0DbRocksDbOptions { get; set; } = ""; - public string LogIndexStorageTopics0DbAdditionalRocksDbOptions { get; set; } = ""; - public string LogIndexStorageTopics1DbRocksDbOptions { get; set; } = ""; - public string LogIndexStorageTopics1DbAdditionalRocksDbOptions { get; set; } = ""; - public string LogIndexStorageTopics2DbRocksDbOptions { get; set; } = ""; - public string LogIndexStorageTopics2DbAdditionalRocksDbOptions { get; set; } = ""; - public string LogIndexStorageTopics3DbRocksDbOptions { get; set; } = ""; - public string LogIndexStorageTopics3DbAdditionalRocksDbOptions { get; set; } = ""; } diff --git a/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs b/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs index 5c8b1211a410..fa7c8e2023b6 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs @@ -102,19 +102,4 @@ public interface IDbConfig : IConfig string L1OriginDbRocksDbOptions { get; set; } string? L1OriginDbAdditionalRocksDbOptions { get; set; } - - string LogIndexStorageDbRocksDbOptions { get; set; } - string LogIndexStorageDbAdditionalRocksDbOptions { get; set; } - string LogIndexStorageMetaDbRocksDbOptions { get; set; } - string LogIndexStorageMetaDbAdditionalRocksDbOptions { get; set; } - string LogIndexStorageAddressesDbRocksDbOptions { get; set; } - string LogIndexStorageAddressesDbAdditionalRocksDbOptions { get; set; } - string LogIndexStorageTopics0DbRocksDbOptions { get; set; } - string LogIndexStorageTopics0DbAdditionalRocksDbOptions { get; set; } - string LogIndexStorageTopics1DbRocksDbOptions { get; set; } - string LogIndexStorageTopics1DbAdditionalRocksDbOptions { get; set; } - string LogIndexStorageTopics2DbRocksDbOptions { get; set; } - string LogIndexStorageTopics2DbAdditionalRocksDbOptions { get; set; } - string LogIndexStorageTopics3DbRocksDbOptions { get; set; } - string LogIndexStorageTopics3DbAdditionalRocksDbOptions { get; set; } } diff --git a/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs b/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs index cca010c66f8c..b5488c733b3a 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using System.Buffers.Binary; using System.Collections.Concurrent; using System.Collections.Generic; @@ -19,7 +18,6 @@ using ConcurrentCollections; using Nethermind.Config; using Nethermind.Core; -using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; using Nethermind.Core.Exceptions; using Nethermind.Core.Extensions; @@ -105,7 +103,7 @@ public DbOnTheRocks( IRocksDbConfigFactory rocksDbConfigFactory, ILogManager logManager, IList? columnFamilies = null, - Native? rocksDbNative = null, + RocksDbSharp.Native? rocksDbNative = null, IFileSystem? fileSystem = null, IntPtr? sharedCache = null) { @@ -113,7 +111,7 @@ public DbOnTheRocks( _settings = dbSettings; Name = _settings.DbName; _fileSystem = fileSystem ?? new FileSystem(); - _rocksDbNative = rocksDbNative ?? Native.Instance; + _rocksDbNative = rocksDbNative ?? RocksDbSharp.Native.Instance; _rocksDbConfigFactory = rocksDbConfigFactory; _perTableDbConfig = rocksDbConfigFactory.GetForDatabase(Name, null); _db = Init(basePath, dbSettings.DbPath, dbConfig, logManager, columnFamilies, dbSettings.DeleteOnStart, sharedCache); @@ -131,6 +129,7 @@ protected virtual RocksDb DoOpen(string path, (DbOptions Options, ColumnFamilies private RocksDb Open(string path, (DbOptions Options, ColumnFamilies? Families) db) { RepairIfCorrupted(db.Options); + return DoOpen(path, db); } @@ -180,7 +179,7 @@ private RocksDb Init(string basePath, string dbPath, IDbConfig dbConfig, ILogMan if (dbConfig.EnableMetricsUpdater) { - DbMetricsUpdater metricUpdater = new(Name, DbOptions, db, null, dbConfig, _logger); + DbMetricsUpdater metricUpdater = new DbMetricsUpdater(Name, DbOptions, db, null, dbConfig, _logger); metricUpdater.StartUpdating(); _metricsUpdaters.Add(metricUpdater); @@ -191,7 +190,7 @@ private RocksDb Init(string basePath, string dbPath, IDbConfig dbConfig, ILogMan if (columnFamily.Name == "default") continue; if (db.TryGetColumnFamily(columnFamily.Name, out ColumnFamilyHandle handle)) { - DbMetricsUpdater columnMetricUpdater = new( + DbMetricsUpdater columnMetricUpdater = new DbMetricsUpdater( Name + "_" + columnFamily.Name, columnFamily.Options, db, handle, dbConfig, _logger); columnMetricUpdater.StartUpdating(); _metricsUpdaters.Add(columnMetricUpdater); @@ -265,8 +264,8 @@ private void WarmupFile(string basePath, RocksDb db) } return take; }) - // We reverse them again so that the lower level goes last so that it is the freshest. - // Not all the available memory is actually available, so we are probably over reading things. + // We reverse them again so that lower level goes last so that it is the freshest. + // Not all of the available memory is actually available so we are probably over reading things. .Reverse() .ToList(); @@ -341,11 +340,15 @@ protected internal void UpdateWriteMetrics() Interlocked.Increment(ref _totalWrites); } - protected virtual long FetchTotalPropertyValue(string propertyName) => - long.TryParse(_db.GetProperty(propertyName), out long parsedValue) + protected virtual long FetchTotalPropertyValue(string propertyName) + { + long value = long.TryParse(_db.GetProperty(propertyName), out long parsedValue) ? parsedValue : 0; + return value; + } + public IDbMeta.DbMetric GatherMetric() { if (_isDisposed) @@ -488,8 +491,8 @@ public static string NormalizeRocksDbOptions(string dbOptions) protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options options, IntPtr? sharedCache, IMergeOperator? mergeOperator) where T : Options { - // This section is about the table factory and block cache, apparently. - // This affects the format of the SST files and usually requires resyncing to take effect. + // This section is about the table factory.. and block cache apparently. + // This effect the format of the SST files and usually require resync to take effect. // Note: Keep in mind, the term 'index' here usually means mapping to a block, not to a value. #region TableFactory sections @@ -556,18 +559,21 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio int writeBufferNumber = _maxWriteBufferNumber; _maxThisDbSize += (long)writeBufferSize * writeBufferNumber; Interlocked.Add(ref _maxRocksSize, _maxThisDbSize); - if (_logger.IsDebug) _logger.Debug($"Expected max memory footprint of {Name} DB is {_maxThisDbSize / 1000 / 1000} MB ({writeBufferNumber} * {writeBufferSize / 1000 / 1000} MB + {blockCacheSize / 1000 / 1000} MB)"); + if (_logger.IsDebug) + _logger.Debug( + $"Expected max memory footprint of {Name} DB is {_maxThisDbSize / 1000 / 1000} MB ({writeBufferNumber} * {writeBufferSize / 1000 / 1000} MB + {blockCacheSize / 1000 / 1000} MB)"); if (_logger.IsDebug) _logger.Debug($"Total max DB footprint so far is {_maxRocksSize / 1000 / 1000} MB"); } #endregion - // This section affects compactions, flushes and the LSM shape. + // This section affect compactions, flushes and the LSM shape. #region Compaction /* - * Multi-Threaded Compactions are needed to remove multiple copies of the same key that may occur if an application overwrites an existing key. Compactions also process deletions of keys. Compactions may occur in multiple threads if configured appropriately. + * Multi-Threaded Compactions + * Compactions are needed to remove multiple copies of the same key that may occur if an application overwrites an existing key. Compactions also process deletions of keys. Compactions may occur in multiple threads if configured appropriately. * The entire database is stored in a set of sstfiles. When a memtable is full, its content is written out to a file in Level-0 (L0). RocksDB removes duplicate and overwritten keys in the memtable when it is flushed to a file in L0. Some files are periodically read in and merged to form larger files - this is called compaction. - * The overall writing throughput of an LSM database directly depends on the speed at which compactions can occur, especially when the data is stored in fast storage like SSD or RAM. RocksDB may be configured to issue concurrent compaction requests from multiple threads. It is observed that sustained write rates may increase by as much as a factor of 10 with multi-threaded compaction when the database is on SSDs, as compared to single-threaded compactions. + * The overall write throughput of an LSM database directly depends on the speed at which compactions can occur, especially when the data is stored in fast storage like SSD or RAM. RocksDB may be configured to issue concurrent compaction requests from multiple threads. It is observed that sustained write rates may increase by as much as a factor of 10 with multi-threaded compaction when the database is on SSDs, as compared to single-threaded compactions. * TKS: Observed 500MB/s compared to ~100MB/s between multithreaded and single thread compactions on my machine (processor count is returning 12 for 6 cores with hyperthreading) * TKS: CPU goes to insane 30% usage on idle - compacting only app */ @@ -586,11 +592,11 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio if (dbConfig.RowCacheSize > 0) { - // Row cache is basically a per-key cache. Nothing special about it. This is different from a block cache - // that caches the whole block at once, so read still needs to traverse the block index, so this could be + // Row cache is basically a per-key cache. Nothing special to it. This is different from block cache + // which cache the whole block at once, so read still need to traverse the block index, so this could be // more CPU efficient. - // Note: Memtable also acts like a per-key cache that does not get updated on read. So in some case - // maybe it makes more sense to put more memory to memtable. + // Note: Memtable also act like a per-key cache, that does not get updated on read. So in some case + // maybe it make more sense to put more memory to memtable. _rowCache = _rocksDbNative.rocksdb_cache_create_lru(new UIntPtr(dbConfig.RowCacheSize.Value)); _rocksDbNative.rocksdb_options_set_row_cache(options.Handle, _rowCache.Value); } @@ -649,7 +655,7 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio _hintCacheMissOptions = CreateReadOptions(); _hintCacheMissOptions.SetFillCache(false); - // When a readahead flag is on, the next keys are expected to be after the current key. Increasing this value + // When readahead flag is on, the next keys are expected to be after the current key. Increasing this value, // will increase the chances that the next keys will be in the cache, which reduces iops and latency. This // increases throughput, however, if a lot of the keys are not close to the current key, it will increase read // bandwidth requirement, since each read must be at least this size. This value is tuned for a batched trie @@ -666,7 +672,7 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio private static WriteOptions CreateWriteOptions(IRocksDbConfig dbConfig) { WriteOptions options = new(); - // a potential fix for corruption on hard process termination may cause performance degradation + // potential fix for corruption on hard process termination, may cause performance degradation options.SetSync(dbConfig.WriteAheadLogSync); return options; } @@ -678,23 +684,32 @@ internal ReadOptions CreateReadOptions() return readOptions; } - byte[]? IReadOnlyKeyValueStore.Get(ReadOnlySpan key, ReadFlags flags) => _reader.Get(key, flags); - - Span IReadOnlyKeyValueStore.GetSpan(scoped ReadOnlySpan key, ReadFlags flags) => _reader.GetSpan(key, flags); + byte[]? IReadOnlyKeyValueStore.Get(ReadOnlySpan key, ReadFlags flags) + { + return _reader.Get(key, flags); + } - MemoryManager? IReadOnlyKeyValueStore.GetOwnedMemory(ReadOnlySpan key, ReadFlags flags) + Span IReadOnlyKeyValueStore.GetSpan(scoped ReadOnlySpan key, ReadFlags flags) { - Span span = ((IReadOnlyKeyValueStore)this).GetSpan(key, flags); - return span.IsNullOrEmpty() ? null : new DbSpanMemoryManager(this, span); + return _reader.GetSpan(key, flags); } - int IReadOnlyKeyValueStore.Get(scoped ReadOnlySpan key, Span output, ReadFlags flags) => _reader.Get(key, output, flags); + int IReadOnlyKeyValueStore.Get(scoped ReadOnlySpan key, Span output, ReadFlags flags) + { + return _reader.Get(key, output, flags); + } - bool IReadOnlyKeyValueStore.KeyExists(ReadOnlySpan key) => _reader.KeyExists(key); + bool IReadOnlyKeyValueStore.KeyExists(ReadOnlySpan key) + { + return _reader.KeyExists(key); + } - void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan span) => _reader.DangerousReleaseMemory(span); + void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan span) + { + _reader.DangerousReleaseMemory(span); + } - internal byte[]? GetWithIterator(ReadOnlySpan key, ColumnFamilyHandle? cf, IteratorManager iteratorManager, ReadFlags flags, out bool success) + internal unsafe byte[]? GetWithIterator(ReadOnlySpan key, ColumnFamilyHandle? cf, IteratorManager iteratorManager, ReadFlags flags, out bool success) { success = true; @@ -755,7 +770,10 @@ internal ReadOptions CreateReadOptions() } [DoesNotReturn, StackTraceHidden] - static void ThrowRocksDbException(nint errPtr) => throw new RocksDbException(errPtr); + static unsafe void ThrowRocksDbException(nint errPtr) + { + throw new RocksDbException(errPtr); + } } /// @@ -765,7 +783,6 @@ internal ReadOptions CreateReadOptions() /// /// /// - /// /// private bool TryCloseReadAhead(Iterator iterator, ReadOnlySpan key, out byte[]? result) { @@ -825,8 +842,10 @@ private bool TryCloseReadAhead(Iterator iterator, ReadOnlySpan key, out by return false; } - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) => + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) + { SetWithColumnFamily(key, null, value, flags); + } internal void SetWithColumnFamily(ReadOnlySpan key, ColumnFamilyHandle? cf, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) { @@ -852,13 +871,25 @@ internal void SetWithColumnFamily(ReadOnlySpan key, ColumnFamilyHandle? cf } } - public WriteOptions? WriteFlagsToWriteOptions(WriteFlags flags) => flags switch + public WriteOptions? WriteFlagsToWriteOptions(WriteFlags flags) { - _ when (flags & WriteFlags.LowPriorityAndNoWAL) == WriteFlags.LowPriorityAndNoWAL => _lowPriorityAndNoWalWrite, - _ when (flags & WriteFlags.DisableWAL) == WriteFlags.DisableWAL => _noWalWrite, - _ when (flags & WriteFlags.LowPriority) == WriteFlags.LowPriority => _lowPriorityWriteOptions, - _ => WriteOptions - }; + if ((flags & WriteFlags.LowPriorityAndNoWAL) == WriteFlags.LowPriorityAndNoWAL) + { + return _lowPriorityAndNoWalWrite; + } + + if ((flags & WriteFlags.DisableWAL) == WriteFlags.DisableWAL) + { + return _noWalWrite; + } + + if ((flags & WriteFlags.LowPriority) == WriteFlags.LowPriority) + { + return _lowPriorityWriteOptions; + } + + return WriteOptions; + } public KeyValuePair[] this[byte[][] keys] @@ -908,15 +939,15 @@ internal unsafe int GetCStyleWithColumnFamily(scoped ReadOnlySpan key, Spa UpdateReadMetrics(); nint db = _db.Handle; - nint readOptionsHandle = readOptions.Handle; + nint read_options = readOptions.Handle; UIntPtr skLength = (UIntPtr)key.Length; IntPtr errPtr; IntPtr slice; fixed (byte* ptr = &MemoryMarshal.GetReference(key)) { slice = cf is null - ? Native.Instance.rocksdb_get_pinned(db, readOptionsHandle, ptr, skLength, out errPtr) - : Native.Instance.rocksdb_get_pinned_cf(db, readOptionsHandle, cf.Handle, ptr, skLength, out errPtr); + ? Native.Instance.rocksdb_get_pinned(db, read_options, ptr, skLength, out errPtr) + : Native.Instance.rocksdb_get_pinned_cf(db, read_options, cf.Handle, ptr, skLength, out errPtr); } if (errPtr != IntPtr.Zero) ThrowRocksDbException(errPtr); @@ -941,15 +972,22 @@ internal unsafe int GetCStyleWithColumnFamily(scoped ReadOnlySpan key, Spa return length; [DoesNotReturn, StackTraceHidden] - static void ThrowRocksDbException(nint errPtr) => throw new RocksDbException(errPtr); + static unsafe void ThrowRocksDbException(nint errPtr) + { + throw new RocksDbException(errPtr); + } [DoesNotReturn, StackTraceHidden] - static void ThrowNotEnoughMemory(int length, int bufferLength) => + static unsafe void ThrowNotEnoughMemory(int length, int bufferLength) + { throw new ArgumentException($"Output buffer not large enough. Output size: {length}, Buffer size: {bufferLength}"); + } } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags) => + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags) + { SetWithColumnFamily(key, null, value, writeFlags); + } public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) { @@ -1239,8 +1277,8 @@ internal class RocksDbWriteBatch : IWriteBatch /// /// Because of how rocksdb parallelize writes, a large write batch can stall other new concurrent writes, so - /// we write the batch in smaller batches. This removes atomicity so it's only turned on when the NoWAL flag is on. - /// It does not work as well as just turning on unordered_write, but Snapshot and Iterator can still work. + /// we writes the batch in smaller batches. This removes atomicity so its only turned on when NoWAL flag is on. + /// It does not work as well as just turning on unordered_write, but Snapshot and Iterator can still works. /// private const int MaxWritesOnNoWal = 256; private int _writeCount; @@ -1278,6 +1316,7 @@ private static void ReturnWriteBatch(WriteBatch batch) public void Clear() { ObjectDisposedException.ThrowIf(_dbOnTheRocks._isDisposed, _dbOnTheRocks); + _rocksBatch.Clear(); } @@ -1329,14 +1368,20 @@ public void Set(ReadOnlySpan key, ReadOnlySpan value, ColumnFamilyHa if ((flags & WriteFlags.DisableWAL) != 0) FlushOnTooManyWrites(); } - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) => + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) + { Set(key, value, null, flags); + } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) + { Set(key, value, null, flags); + } - public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) + { Merge(key, value, null, flags); + } public void Merge(ReadOnlySpan key, ReadOnlySpan value, ColumnFamilyHandle? cf = null, WriteFlags flags = WriteFlags.None) { @@ -1452,13 +1497,18 @@ private class FlushOptions { internal static FlushOptions DefaultFlushOptions { get; } = new(); - public IntPtr Handle { get; private set; } = Native.Instance.rocksdb_flushoptions_create(); + public FlushOptions() + { + Handle = RocksDbSharp.Native.Instance.rocksdb_flushoptions_create(); + } + + public IntPtr Handle { get; private set; } ~FlushOptions() { if (Handle != IntPtr.Zero) { - Native.Instance.rocksdb_flushoptions_destroy(Handle); + RocksDbSharp.Native.Instance.rocksdb_flushoptions_destroy(Handle); Handle = IntPtr.Zero; } } @@ -1523,15 +1573,15 @@ public virtual void Tune(ITunableDb.TuneType type) // See https://github.com/EighteenZi/rocksdb_wiki/blob/master/RocksDB-Tuning-Guide.md switch (type) { - // Depending on tune type, allow num of L0 files to grow causing compaction to occur in a larger size. This + // Depending on tune type, allow num of L0 files to grow causing compaction to occur in larger size. This // reduces write amplification at the expense of read response time and amplification while the tune is // active. Additionally, the larger compaction causes larger spikes of IO, larger memory usage, and may temporarily - // use up a large amount of disk space. User may not want to enable this if they plan to run a validator node - // while the node is still syncing or run another node on the same machine. Specifying a rate limit + // use up large amount of disk space. User may not want to enable this if they plan to run a validator node + // while the node is still syncing, or run another node on the same machine. Specifying a rate limit // smoothens this spike somewhat by not blocking writes while allowing compaction to happen in background // at 1/10th the specified speed (if rate limited). // - // Total writes written on different tunes during mainnet sync in TB. + // Total writes written on different tune during mainnet sync in TB. // +-----------------------+-------+-------+-------+-------+-------+---------+ // | L0FileNumTarget | Total | State | Code | Header| Blocks| Receipts | // +-----------------------+-------+-------+-------+-------+-------+---------+ @@ -1542,45 +1592,45 @@ public virtual void Tune(ITunableDb.TuneType type) // | DisableCompaction | 2.215 | 0.36 | 0.031 | 0.137 | 1.14 | 0.547 | // +-----------------------+-------+-------+-------+-------+-------+---------+ // Note, in practice on my machine, the reads does not reach the SSD. Read measured from SSD is much lower - // than read measured from a process. It is likely that most files are cached as I have 128GB of RAM. + // than read measured from process. It is likely that most files are cached as I have 128GB of RAM. // Also notice that the heavier the tune, the higher the reads. case ITunableDb.TuneType.WriteBias: - // Keep the same l1 size but apply other adjustment which should increase the buffer number and make - // l0 the same size as l1 but keep the LSM the same. This improves flush parallelization and + // Keep the same l1 size but apply other adjustment which should increase buffer number and make + // l0 the same size as l1, but keep the LSM the same. This improve flush parallelization, and // write amplification due to mismatch of l0 and l1 size, but does not reduce compaction from other // levels. ApplyOptions(GetHeavyWriteOptions(_maxBytesForLevelBase)); break; case ITunableDb.TuneType.HeavyWrite: - // Compaction spikes are clear at this point. Will definitely affect attestations performance. - // It's unclear if it improves or slows down sync time. Seems to be the sweet spot. + // Compaction spikes are clear at this point. Will definitely affect attestation performance. + // Its unclear if it improve or slow down sync time. Seems to be the sweet spot. ApplyOptions(GetHeavyWriteOptions((ulong)2.GiB())); break; case ITunableDb.TuneType.AggressiveHeavyWrite: - // For when you are desperate, but don't wanna disable compaction completely, because you don't want + // For when, you are desperate, but don't wanna disable compaction completely, because you don't want // peers to drop. Tend to be faster than disabling compaction completely, except if your ratelimit // is a bit low and your compaction is lagging behind, which will trigger slowdown, so sync will hang // intermittently, but at least peer count is stable. ApplyOptions(GetHeavyWriteOptions((ulong)16.GiB())); break; case ITunableDb.TuneType.DisableCompaction: - // Completely disable compaction. On mainnet, the max num of l0 files for state seems to be about 10800. - // Blocksdb are way more at 53000. Final compaction for state db needs 30 minutes, while blocks db need - // 13 hours. Receipts db don't show up in metrics likely because it's a column db. + // Completely disable compaction. On mainnet, max num of l0 files for state seems to be about 10800. + // Blocksdb are way more at 53000. Final compaction for state db need 30 minute, while blocks db need + // 13 hour. Receipts db don't show up in metrics likely because its a column db. // Ram usage at that time was 86 GB. The default buffer size for blocks on mainnet is too low // to make this work reasonably well. - // L0 to L1 compaction is known to be slower than other levels, so its - // Snap sync performance suffers as it does have some read during stitching. - // If you don't specify a lower open files limit, it tends to crash, like... the whole system - // crashes. I don't have any open file limit at OS level. - // Also, if a peer sends a packet that causes a query to the state db during snap sync like GetNodeData - // or some of the tx filter querying state, It'll cause the network stack to hang and triggers + // L0 to L1 compaction is known to be slower than other level so its + // Snap sync performance suffer as it does have some read during stitching. + // If you don't specify a lower open files limit, it has a tendency to crash, like.. the whole system + // crash. I don't have any open file limit at OS level. + // Also, if a peer send a packet that causes a query to the state db during snap sync like GetNodeData + // or some of the tx filter querying state, It'll cause the network stack to hang and triggers a // large peer drops. Also happens on lesser tune, but weaker. - // State sync essentially hangs until that completes because its read heavy, and the uncompacted db is + // State sync essentially hang until that completes because its read heavy, and the uncompacted db is // slow to a halt. // Additionally, the number of open files handles measured from collectd jumped massively higher. Some // user config may not be able to handle this. - // With all those cons, this results in the minimum writes amplification possible via tweaking compaction + // With all those cons, this result in the minimum write amplification possible via tweaking compaction // without changing memory budget. Not recommended for mainnet, unless you are very desperate. ApplyOptions(GetDisableCompactionOptions()); break; @@ -1599,11 +1649,15 @@ public virtual void Tune(ITunableDb.TuneType type) _currentTune = type; } - protected virtual void ApplyOptions(IDictionary options) => _db.SetOptions(options); + protected virtual void ApplyOptions(IDictionary options) + { + _db.SetOptions(options); + } - private IDictionary GetStandardOptions() => + private IDictionary GetStandardOptions() + { // Defaults are from rocksdb source code - new Dictionary() + return new Dictionary() { { "write_buffer_size", _writeBufferSize.ToString() }, { "max_write_buffer_number", _maxWriteBufferNumber.ToString() }, @@ -1612,7 +1666,7 @@ private IDictionary GetStandardOptions() => { "level0_slowdown_writes_trigger", 20.ToString() }, // Very high, so that after moving from HeavyWrite, we don't immediately hang. - // This does mean that under a very rare case, the l0 file can accumulate, which slows down the db + // This does means that under very rare case, the l0 file can accumulate, which slow down the db // until they get compacted. { "level0_stop_writes_trigger", 1024.ToString() }, @@ -1625,11 +1679,13 @@ private IDictionary GetStandardOptions() => { "soft_pending_compaction_bytes_limit", 64.GiB().ToString() }, { "hard_pending_compaction_bytes_limit", 256.GiB().ToString() }, }; + } - private IDictionary GetHashDbOptions() => - new Dictionary() + private IDictionary GetHashDbOptions() + { + return new Dictionary() { - // Some database config is slightly faster on a hash db database. These are applied when hash db is detected + // Some database config is slightly faster on hash db database. These are applied when hash db is detected // to prevent unexpected regression. { "table_factory.block_size", "4096" }, { "table_factory.block_restart_interval", "16" }, @@ -1637,34 +1693,35 @@ private IDictionary GetHashDbOptions() => { "max_bytes_for_level_multiplier", "10" }, { "max_bytes_for_level_base", "256000000" }, }; + } /// - /// Allow number of l0 files to grow very large. This dramatically increases read response time by about - /// (l0FileNumTarget / (default num (4) + max level usually (4)), but it saves write bandwidth as l0->l1 happens - /// in larger size. In addition to that, the large base l1 size means the number of levels is a bit lower. - /// Note: Regardless of max_open_files config, the number of files handles jumped by this number when compacting. It - /// could be that l0->l1 compaction does not (or can't?) follow the max_open_files limit. + /// Allow num of l0 file to grow very large. This dramatically increase read response time by about + /// (l0FileNumTarget / (default num (4) + max level usually (4)). but it saves write bandwidth as l0->l1 happens + /// in larger size. In addition to that, the large base l1 size means the number of level is a bit lower. + /// Note: Regardless of max_open_files config, the number of files handle jumped by this number when compacting. It + /// could be that l0->l1 compaction does not (or cant?) follow the max_open_files limit. /// - /// + /// /// This caps the maximum allowed number of l0 files, which is also the read response time amplification. /// /// private IDictionary GetHeavyWriteOptions(ulong l0SizeTarget) { // Make buffer (probably) smaller so that it does not take too much memory to have many of them. - // More buffer means more parallel flush, but each read has to go through all buffers one by one, much like l0 + // More buffer means more parallel flush, but each read have to go through all buffer one by one much like l0 // but no io, only cpu. - // bufferSize*maxBufferNumber = 16MB*Core count, which is the max memory used, which tends to be the case as it's now - // stalled by compaction instead of a flush. - // The buffer is not compressed unlike l0File, so to account for it, its size needs to be slightly larger. + // bufferSize*maxBufferNumber = 16MB*Core count, which is the max memory used, which tend to be the case as its now + // stalled by compaction instead of flush. + // The buffer is not compressed unlike l0File, so to account for it, its size need to be slightly larger. ulong targetFileSize = (ulong)16.MiB(); ulong bufferSize = (ulong)(targetFileSize / _perTableDbConfig.CompressibilityHint); ulong l0FileSize = targetFileSize * (ulong)_minWriteBufferToMerge; ulong maxBufferNumber = (ulong)Environment.ProcessorCount; - // Guide recommends having l0 and l1 to be the same size. They have to be compacted together, so if l1 is larger, + // Guide recommend to have l0 and l1 to be the same size. They have to be compacted together so if l1 is larger, // the extra size in l1 is basically extra rewrites. If l0 is larger... then I don't know why not. Even so, it seems to - // always get triggered when l0 size exceeds max_bytes_for_level_base even if the file number is less than l0FileNumTarget. + // always get triggered when l0 size exceed max_bytes_for_level_base even if file num is less than l0FileNumTarget. ulong l0FileNumTarget = l0SizeTarget / l0FileSize; ulong l1SizeTarget = l0SizeTarget; @@ -1692,10 +1749,10 @@ private IDictionary GetDisableCompactionOptions() IDictionary heavyWriteOption = GetHeavyWriteOptions((ulong)32.GiB()); heavyWriteOption["disable_auto_compactions"] = "true"; - // Increase the size of the write buffer, which reduces the number of l0 files by 4x. This does slow down + // Increase the size of the write buffer, which reduces the number of l0 file by 4x. This does slows down // the memtable a little bit. So if you are not write limited, you'll get memtable limited instead. // This does increase the total memory buffer size, but counterintuitively, this reduces overall memory usage - // as it ran out of bloom filter cache, so it needs to do actual IO. + // as it ran out of bloom filter cache so it need to do actual IO. heavyWriteOption["write_buffer_size"] = 64.MiB().ToString(); return heavyWriteOption; @@ -1705,17 +1762,17 @@ private IDictionary GetDisableCompactionOptions() private static IDictionary GetBlobFilesOptions() { // Enable blob files, see: https://rocksdb.org/blog/2021/05/26/integrated-blob-db.html - // This is very useful for blocks, as it almost eliminates 95% of the compaction as the main db no longer + // This is very useful for blocks, as it almost eliminate 95% of the compaction as the main db no longer // store the actual data, but only points to blob files. This config reduces total blocks db writes from about - // 4.6 TB to 0.76 TB, where even the WAL took 0.45 TB (wal is not compressed), with peak writes of about 300MBps, + // 4.6 TB to 0.76 TB, where even the the WAL took 0.45 TB (wal is not compressed), with peak writes of about 300MBps, // it may not even saturate a SATA SSD on a 1GBps internet. - // You don't want to turn this on other DB as it does add an indirection which take up an additional iop. + // You don't want to turn this on on other DB as it does add an indirection which take up an additional iop. // But for large values like blocks (3MB decompressed to 8MB), the response time increase is negligible. - // However, without a large buffer size, it will create tens of thousands of small files. There are - // various workaround it, but it all increases total writes, which defeats the purpose. - // Additionally, as the `max_bytes_for_level_base` is set to very low, existing users will suddenly - // get a lot of compaction. So can't turn this on all the time. Turning this back off will just put back + // However without a large buffer size, it will create tens of thousands of small files. There are + // various workaround it, but it all increase total writes, which defeats the purpose. + // Additionally, as the `max_bytes_for_level_base` is set to very low, existing user will suddenly + // get a lot of compaction. So cant turn this on all the time. Turning this back off, will just put back // new data to SST files. return new Dictionary() @@ -1723,9 +1780,9 @@ private static IDictionary GetBlobFilesOptions() { "enable_blob_files", "true" }, { "blob_compression_type", "kSnappyCompression" }, - // Make the file size big, so we have less of them. + // Make file size big, so we have less of them. { "write_buffer_size", 256.MiB().ToString() }, - // Current memtable + 2 concurrent writes. Can't have too many of these as it takes up RAM. + // Current memtable + 2 concurrent writes. Can't have too many of these as it take up RAM. { "max_write_buffer_number", 3.ToString() }, // These two are SST files instead of the blobs, which are now much smaller. @@ -1738,7 +1795,7 @@ private static IDictionary GetBlobFilesOptions() /// Iterators should not be kept for long as it will pin some memory block and sst file. This would show up as /// temporary higher disk usage or memory usage. /// - /// This class handles a periodic timer that periodically disposes all iterators. + /// This class handles a periodic timer which periodically dispose all iterator. /// public class IteratorManager : IDisposable { @@ -1783,23 +1840,35 @@ public void Dispose() public RentWrapper Rent(ReadFlags flags) { - ManagedIterators iterators = GetIterators(flags); + + ManagedIterators iterators = _readaheadIterators; + if ((flags & ReadFlags.HintReadAhead2) != 0) + { + iterators = _readaheadIterators2; + } + else if ((flags & ReadFlags.HintReadAhead3) != 0) + { + iterators = _readaheadIterators3; + } + IteratorHolder holder = iterators.Value!; // If null, we create a new one. Iterator? iterator = Interlocked.Exchange(ref holder.Iterator, null); return new RentWrapper(iterator ?? _rocksDb.NewIterator(_cf, _readOptions), flags, this); } - private ManagedIterators GetIterators(ReadFlags flags) => flags switch - { - _ when (flags & ReadFlags.HintReadAhead2) != 0 => _readaheadIterators2, - _ when (flags & ReadFlags.HintReadAhead3) != 0 => _readaheadIterators3, - _ => _readaheadIterators - }; - private void Return(Iterator iterator, ReadFlags flags) { - ManagedIterators iterators = GetIterators(flags); + ManagedIterators iterators = _readaheadIterators; + if ((flags & ReadFlags.HintReadAhead2) != 0) + { + iterators = _readaheadIterators2; + } + else if ((flags & ReadFlags.HintReadAhead3) != 0) + { + iterators = _readaheadIterators3; + } + IteratorHolder holder = iterators.Value!; // We don't keep using the same iterator for too long. @@ -1813,7 +1882,7 @@ private void Return(Iterator iterator, ReadFlags flags) holder.Usage++; Iterator? oldIterator = Interlocked.Exchange(ref holder.Iterator, iterator); - // Well... this is weird. I'll just dispose of it. + // Well... this is weird. I'll just dispose it. oldIterator?.Dispose(); } @@ -1821,14 +1890,21 @@ public readonly struct RentWrapper(Iterator iterator, ReadFlags flags, IteratorM { public Iterator Iterator => iterator; - public void Dispose() => manager.Return(iterator, flags); + public void Dispose() + { + manager.Return(iterator, flags); + } } // Note: use of threadlocal is very important as the seek forward is fast, but the seek backward is not fast. - private sealed class ManagedIterators() : ThreadLocal(static () => new IteratorHolder(), trackAllValues: true) + private sealed class ManagedIterators : ThreadLocal { private bool _disposed = false; + public ManagedIterators() : base(static () => new IteratorHolder(), trackAllValues: true) + { + } + public void ClearIterators() { if (_disposed) return; @@ -1847,7 +1923,7 @@ public void DisposeAll() protected override void Dispose(bool disposing) { - // Note: This is called from finalizer thread, so we can't use foreach to dispose of all values + // Note: This is called from finalizer thread, so we can't use foreach to dispose all values Value?.Dispose(); Value = null!; _disposed = true; @@ -1887,14 +1963,17 @@ public byte[]? LastKey } } - public ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey) => GetViewBetween(firstKey, lastKey, null); + public ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey) + { + return GetViewBetween(firstKey, lastKey, null); + } internal ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey, ColumnFamilyHandle? cf) { ReadOptions readOptions = CreateReadOptions(); - IntPtr iterateLowerBound; - IntPtr iterateUpperBound; + IntPtr iterateLowerBound = IntPtr.Zero; + IntPtr iterateUpperBound = IntPtr.Zero; unsafe { @@ -1929,6 +2008,9 @@ public class RocksDbSnapshot( Snapshot snapshot ) : RocksDbReader(mainDb, readOptionsFactory, null, columnFamily), IKeyValueStoreSnapshot { - public void Dispose() => snapshot.Dispose(); + public void Dispose() + { + snapshot.Dispose(); + } } } diff --git a/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs b/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs index 548d3d9556a8..14bf9efc98f8 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs @@ -15,13 +15,13 @@ internal class MergeOperatorAdapter(IMergeOperator inner) : MergeOperator public string Name => inner.Name; // TODO: fix and return array ptr instead of copying to unmanaged memory? - private static unsafe nint GetResult(ArrayPoolList? data, out nint resultLength, out byte success) + private static unsafe IntPtr GetResult(ArrayPoolList? data, out IntPtr resultLength, out IntPtr success) { if (data is null) { - success = 0; - resultLength = nint.Zero; - return nint.Zero; + success = Convert.ToInt32(false); + resultLength = IntPtr.Zero; + return IntPtr.Zero; } using (data) @@ -34,31 +34,47 @@ private static unsafe nint GetResult(ArrayPoolList? data, out nint resultL // Fixing RocksDbSharp invalid callback signature, TODO: submit an issue/PR Unsafe.SkipInit(out success); - Unsafe.As(ref success) = 1; + Unsafe.As(ref success) = 1; - return (nint)resultPtr; + return (IntPtr)resultPtr; } } - public unsafe nint PartialMerge(nint key, nuint keyLength, nint operandsList, nint operandsListLength, int numOperands, out byte success, out nint newValueLength) + unsafe IntPtr MergeOperator.PartialMerge( + IntPtr keyPtr, + UIntPtr keyLength, + IntPtr operandsList, + IntPtr operandsListLength, + int numOperands, + out IntPtr successPtr, + out IntPtr resultLength) { - var keyBytes = new Span((void*)key, (int)keyLength); + var key = new Span((void*)keyPtr, (int)keyLength); var enumerator = new RocksDbMergeEnumerator(new((void*)operandsList, numOperands), new((void*)operandsListLength, numOperands)); - ArrayPoolList? result = inner.PartialMerge(keyBytes, enumerator); - return GetResult(result, out newValueLength, out success); + ArrayPoolList? result = inner.PartialMerge(key, enumerator); + return GetResult(result, out resultLength, out successPtr); } - public unsafe nint FullMerge(nint key, nuint keyLength, nint existingValue, nuint existingValueLength, nint operandsList, nint operandsListLength, int numOperands, out byte success, out nint newValueLength) + unsafe IntPtr MergeOperator.FullMerge( + IntPtr keyPtr, + UIntPtr keyLength, + IntPtr existingValuePtr, + UIntPtr existingValueLength, + IntPtr operandsList, + IntPtr operandsListLength, + int numOperands, + out IntPtr successPtr, + out IntPtr resultLength) { - var keyBytes = new ReadOnlySpan((void*)key, (int)keyLength); - bool hasExistingValue = existingValue != nint.Zero; - Span existingValueBytes = hasExistingValue ? new((void*)existingValue, (int)existingValueLength) : []; - var enumerator = new RocksDbMergeEnumerator(existingValueBytes, hasExistingValue, new((void*)operandsList, numOperands), new((void*)operandsListLength, numOperands)); + var key = new ReadOnlySpan((void*)keyPtr, (int)keyLength); + bool hasExistingValue = existingValuePtr != IntPtr.Zero; + Span existingValue = hasExistingValue ? new((void*)existingValuePtr, (int)existingValueLength) : Span.Empty; + var enumerator = new RocksDbMergeEnumerator(existingValue, hasExistingValue, new((void*)operandsList, numOperands), new((void*)operandsListLength, numOperands)); - ArrayPoolList? result = inner.FullMerge(keyBytes, enumerator); - return GetResult(result, out newValueLength, out success); + ArrayPoolList? result = inner.FullMerge(key, enumerator); + return GetResult(result, out resultLength, out successPtr); } - unsafe void MergeOperator.DeleteValue(nint value, nuint valueLength) => NativeMemory.Free((void*)value); + unsafe void MergeOperator.DeleteValue(IntPtr value, UIntPtr valueLength) => NativeMemory.Free((void*)value); } diff --git a/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs b/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs index a94366c63761..61bb02e57e13 100644 --- a/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs +++ b/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs @@ -2,11 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using System.Collections.Generic; using System.Linq; using Nethermind.Core; -using Nethermind.Core.Buffers; using Nethermind.Core.Extensions; using Nethermind.JsonRpc; using Nethermind.JsonRpc.Client; @@ -15,25 +13,35 @@ namespace Nethermind.Db.Rpc { - public class RpcDb( - string dbName, - IJsonSerializer jsonSerializer, - IJsonRpcClient rpcClient, - ILogManager logManager, - IDb recordDb) - : IDb + public class RpcDb : IDb { - private readonly IJsonSerializer _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); - private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - private readonly IJsonRpcClient _rpcClient = rpcClient ?? throw new ArgumentNullException(nameof(rpcClient)); + private readonly string _dbName; + private readonly IJsonSerializer _jsonSerializer; + private readonly ILogger _logger; + private readonly IJsonRpcClient _rpcClient; + private readonly IDb _recordDb; + + public RpcDb(string dbName, IJsonSerializer jsonSerializer, IJsonRpcClient rpcClient, ILogManager logManager, IDb recordDb) + { + _dbName = dbName; + _rpcClient = rpcClient ?? throw new ArgumentNullException(nameof(rpcClient)); + _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); + _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + _recordDb = recordDb; + } public void Dispose() { _logger.Info($"Disposing RPC DB {Name}"); - recordDb.Dispose(); + _recordDb.Dispose(); } - public string Name => "RpcDb"; + public long GetSize() => 0; + public long GetCacheSize() => 0; + public long GetIndexSize() => 0; + public long GetMemtableSize() => 0; + + public string Name { get; } = "RpcDb"; public byte[] this[ReadOnlySpan key] { @@ -41,16 +49,40 @@ public byte[] this[ReadOnlySpan key] set => Set(key, value); } - public void Set(ReadOnlySpan key, byte[] value, WriteFlags flags = WriteFlags.None) => ThrowWritesNotSupported(); - public byte[] Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => GetThroughRpc(key); + public void Set(ReadOnlySpan key, byte[] value, WriteFlags flags = WriteFlags.None) + { + ThrowWritesNotSupported(); + } + + public byte[] Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + { + return GetThroughRpc(key); + } + public KeyValuePair[] this[byte[][] keys] => keys.Select(k => new KeyValuePair(k, GetThroughRpc(k))).ToArray(); - public void Remove(ReadOnlySpan key) => ThrowWritesNotSupported(); - public bool KeyExists(ReadOnlySpan key) => GetThroughRpc(key) is not null; + + public void Remove(ReadOnlySpan key) + { + ThrowWritesNotSupported(); + } + + public bool KeyExists(ReadOnlySpan key) + { + return GetThroughRpc(key) is not null; + } + + public IDb Innermost => this; // record db is just a helper DB here + public void Flush() { } public void Flush(bool onlyWal = false) { } + public void Clear() { } - public IEnumerable> GetAll(bool ordered = false) => recordDb.GetAll(); - public IEnumerable GetAllKeys(bool ordered = false) => recordDb.GetAllKeys(); - public IEnumerable GetAllValues(bool ordered = false) => recordDb.GetAllValues(); + + public IEnumerable> GetAll(bool ordered = false) => _recordDb.GetAll(); + + public IEnumerable GetAllKeys(bool ordered = false) => _recordDb.GetAllKeys(); + + public IEnumerable GetAllValues(bool ordered = false) => _recordDb.GetAllValues(); + public IWriteBatch StartWriteBatch() { ThrowWritesNotSupported(); @@ -60,24 +92,36 @@ public IWriteBatch StartWriteBatch() private byte[] GetThroughRpc(ReadOnlySpan key) { - string responseJson = _rpcClient.Post("debug_getFromDb", dbName, key.ToHexString()).Result; + string responseJson = _rpcClient.Post("debug_getFromDb", _dbName, key.ToHexString()).Result; JsonRpcSuccessResponse response = _jsonSerializer.Deserialize(responseJson); byte[] value = null; if (response.Result is not null) { value = Bytes.FromHexString((string)response.Result); - if (recordDb is not null) + if (_recordDb is not null) { - recordDb[key] = value; + _recordDb[key] = value; } } return value; } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags) => ThrowWritesNotSupported(); + public Span GetSpan(ReadOnlySpan key) + { + return Get(key); + } + + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags) + { + ThrowWritesNotSupported(); + } + private static void ThrowWritesNotSupported() => throw new InvalidOperationException("RPC DB does not support writes"); - public void DangerousReleaseMemory(in ReadOnlySpan span) { } + + public void DangerousReleaseMemory(in ReadOnlySpan span) + { + } } } diff --git a/src/Nethermind/Nethermind.Db.Test/CompressingStoreTests.cs b/src/Nethermind/Nethermind.Db.Test/CompressingStoreTests.cs index cb7d63a2d4ba..117a4b6d52b8 100644 --- a/src/Nethermind/Nethermind.Db.Test/CompressingStoreTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/CompressingStoreTests.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Test; @@ -72,15 +71,7 @@ public void EOAWithSPan() ctx.Compressed.PutSpan(Key, encoded.Bytes); Assert.That(encoded.Bytes, Is.EqualTo(ctx.Compressed[Key]).AsCollection); - Span span = ctx.Compressed.GetSpan(Key); - try - { - Assert.That(encoded.Bytes, Is.EqualTo(span.ToArray()).AsCollection); - } - finally - { - ctx.Compressed.DangerousReleaseMemory(span); - } + Assert.That(encoded.Bytes, Is.EqualTo(ctx.Compressed.GetSpan(Key).ToArray()).AsCollection); ctx.Wrapped[Key]!.Length.Should().Be(5); } diff --git a/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageCompactorTests.cs b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageCompactorTests.cs deleted file mode 100644 index e23c4d8a1cba..000000000000 --- a/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageCompactorTests.cs +++ /dev/null @@ -1,184 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Nethermind.Db.LogIndex; -using Nethermind.Logging; -using NSubstitute; -using NUnit.Framework; -using static Nethermind.Db.LogIndex.LogIndexStorage; - -namespace Nethermind.Db.Test.LogIndex; - -[TestFixture] -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] -public class LogIndexStorageCompactorTests -{ - private const int RaceConditionTestRepeat = 3; - - private static ILogIndexStorage MockStorage(int? min = null, int? max = null) - { - ILogIndexStorage storage = Substitute.For(); - storage.MinBlockNumber.Returns(min); - storage.MaxBlockNumber.Returns(max); - return storage; - } - - private static Compactor CreateCompactor(ILogIndexStorage storage, IDbMeta? db = null, int compactionDistance = 100) => - new(storage, db ?? new FakeDb(), LimboLogs.Instance.GetClassLogger(), compactionDistance); - - private static Compactor CreateCompactor(ILogIndexStorage storage, int compactionDistance = 100) => - CreateCompactor(storage, db: null, compactionDistance: compactionDistance); - - [TestCase(0, 50, 0, 50, 100, ExpectedResult = false, Description = "No change from baseline")] - [TestCase(0, 0, 0, 99, 100, ExpectedResult = false, Description = "99 blocks forward, threshold 100")] - [TestCase(0, 0, 0, 100, 100, ExpectedResult = true, Description = "Exactly at threshold")] - [TestCase(100, 200, 50, 250, 100, ExpectedResult = true, Description = "Both directions sum to threshold")] - public async Task TryEnqueue_Respects_CompactionDistance( - int initMin, int initMax, int newMin, int newMax, int compactionDistance - ) - { - ILogIndexStorage storage = MockStorage(min: initMin, max: initMax); - using Compactor compactor = CreateCompactor(storage, compactionDistance); - - storage.MinBlockNumber.Returns(newMin); - storage.MaxBlockNumber.Returns(newMax); - - bool result = compactor.TryEnqueue(); - - await compactor.StopAsync(); - return result; - } - - [Test] - [Repeat(RaceConditionTestRepeat)] - public async Task TryEnqueue_During_Compact_Does_Not_Run_Compact_Concurrently() - { - const int compactionDistance = 10; - var compactionDelay = TimeSpan.FromMilliseconds(200); - - ILogIndexStorage storage = MockStorage(min: 0, max: 0); - FakeDb db = new(compactionDelay); - using Compactor compactor = CreateCompactor(storage, db, compactionDistance); - - // Trigger first compaction - storage.MaxBlockNumber.Returns(compactionDistance); - Assert.That(compactor.TryEnqueue(), Is.True); - - await Task.Delay(compactionDelay / 4); - - // Try to cause a second compaction - storage.MaxBlockNumber.Returns(storage.MaxBlockNumber + compactionDistance); - compactor.TryEnqueue(); - - await compactor.ForceAsync(); - await compactor.StopAsync(); - } - - [TestCase(false)] - [TestCase(true)] - [Repeat(RaceConditionTestRepeat)] - [SuppressMessage("ReSharper", "AccessToDisposedClosure")] - public async Task ForceAsync_Does_Not_Run_Compact_Concurrently(bool duringCompact) - { - const int compactionDistance = 10; - var compactionDelay = TimeSpan.FromMilliseconds(200); - - ILogIndexStorage storage = MockStorage(min: 0, max: 0); - FakeDb db = new(compactionDelay); - using Compactor compactor = CreateCompactor(storage, db, compactionDistance); - - if (duringCompact) - { - storage.MaxBlockNumber.Returns(compactionDistance); - compactor.TryEnqueue(); - - await Task.Delay(compactionDelay / 4); - } - - const int concurrentCalls = 5; - await Task.WhenAll(Enumerable.Range(0, concurrentCalls).Select(_ => Task.Run(compactor.ForceAsync)).ToArray()); - - await compactor.StopAsync(); - } - - [Test] - public async Task TryEnqueue_Resets_Baseline_After_Enqueue() - { - const int compactionDistance = 10; - - ILogIndexStorage storage = MockStorage(min: 0, max: 0); - using Compactor compactor = CreateCompactor(storage, compactionDistance); - - storage.MaxBlockNumber.Returns(compactionDistance); - Assert.That(compactor.TryEnqueue(), Is.True); - - await Task.Delay(100); - - storage.MaxBlockNumber.Returns(storage.MaxBlockNumber + compactionDistance / 2); - Assert.That(compactor.TryEnqueue(), Is.False); - - await Task.Delay(100); - - storage.MaxBlockNumber.Returns(storage.MaxBlockNumber + compactionDistance / 2); - Assert.That(compactor.TryEnqueue(), Is.True); - - await compactor.StopAsync(); - } - - [Test] - public async Task TryEnqueue_Returns_False_After_Stop() - { - const int compactionDistance = 10; - - ILogIndexStorage storage = MockStorage(min: 0, max: 0); - using Compactor compactor = CreateCompactor(storage, new NonCompactableDb(), compactionDistance); - - await compactor.StopAsync(); - - storage.MaxBlockNumber.Returns(compactionDistance); - Assert.That(compactor.TryEnqueue(), Is.False); - } - - // Fails on compaction attempt - private class NonCompactableDb : IDbMeta - { - private class CompactionException : Exception; - - public void Compact() => throw new CompactionException(); - public void Flush(bool onlyWal = false) { } - } - - // Simulates compaction with Thread.Sleep and fail on concurrent calls - private class FakeDb(TimeSpan? compactDelay = null) : IDbMeta - { - private class ConcurrentCompactionException : Exception; - - private readonly TimeSpan _compactDelay = compactDelay ?? TimeSpan.Zero; - - private int _compacting; - - public void Compact() - { - if (Interlocked.CompareExchange(ref _compacting, 1, 0) != 0) - throw new ConcurrentCompactionException(); - - try - { - if (_compactDelay > TimeSpan.Zero) - Thread.Sleep(_compactDelay); - } - finally - { - Interlocked.Exchange(ref _compacting, 0); - } - } - - public void Flush(bool onlyWal = false) { } - } -} diff --git a/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageIntegrationTests.cs b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageIntegrationTests.cs deleted file mode 100644 index 86e75574ea49..000000000000 --- a/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageIntegrationTests.cs +++ /dev/null @@ -1,1110 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Runtime.ExceptionServices; -using System.Threading; -using System.Threading.Tasks; -using MathNet.Numerics.Random; -using Nethermind.Core; -using Nethermind.Core.Collections; -using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; -using Nethermind.Core.Test; -using Nethermind.Core.Test.Builders; -using Nethermind.Db.LogIndex; -using Nethermind.Db.Rocks; -using Nethermind.Db.Rocks.Config; -using Nethermind.Logging; -using Nethermind.TurboPForBindings; -using NUnit.Framework; -using NUnit.Framework.Interfaces; -using static Nethermind.Db.LogIndex.LogIndexStorage; - -namespace Nethermind.Db.Test.LogIndex -{ - [TestFixtureSource(nameof(TestCases))] - [Parallelizable(ParallelScope.All)] - [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] - public class LogIndexStorageIntegrationTests(LogIndexStorageIntegrationTests.TestData testData) - { - private const int RaceConditionTestRepeat = 3; - -#if DEBUG - private static bool LogStatistics => true; -#else - private static bool LogStatistics => false; -#endif - - public static readonly TestFixtureData[] TestCases = - [ - new(new TestData(10, 100) { Compression = CompressionAlgorithm.Best.Key }), - new(new TestData(5, 200) { Compression = nameof(TurboPFor.p4nd1enc128v32) }), - new(new TestData(10, 100) { Compression = CompressionAlgorithm.Best.Key, ExtendedGetRanges = true }) { RunState = RunState.Explicit }, - new(new TestData(100, 100) { Compression = nameof(TurboPFor.p4nd1enc128v32) }) { RunState = RunState.Explicit }, - new(new TestData(100, 200) { Compression = CompressionAlgorithm.Best.Key }) { RunState = RunState.Explicit } - ]; - - private string _dbPath = null!; - private IDbFactory _dbFactory = null!; - private readonly List _createdStorages = []; - - private ILogIndexStorage CreateLogIndexStorage( - int compactionDistance = 262_144, int compressionParallelism = 16, int maxReorgDepth = 64, IDbFactory? dbFactory = null, - string? compressionAlgo = null, int? failOnBlock = null, int? failOnCallN = null, bool failOnMerge = false - ) - { - LogIndexConfig config = new() - { - Enabled = true, - CompactionDistance = compactionDistance, - MaxCompressionParallelism = compressionParallelism, - MaxReorgDepth = maxReorgDepth, - CompressionAlgorithm = compressionAlgo ?? testData.Compression - }; - - ILogIndexStorage storage = failOnBlock is null && failOnCallN is null - ? new LogIndexStorage(dbFactory ?? _dbFactory, LimboLogs.Instance, config) - : new SaveFailingLogIndexStorage(dbFactory ?? _dbFactory, LimboLogs.Instance, config) - { - FailOnBlock = failOnBlock ?? 0, - FailOnCallN = failOnCallN ?? 0, - FailOnMerge = failOnMerge - }; - - return storage.AddTo(_createdStorages); - } - - [SetUp] - public void Setup() - { - _dbPath = $"{nameof(LogIndexStorageIntegrationTests)}/{Guid.NewGuid():N}"; - - if (Directory.Exists(_dbPath)) - Directory.Delete(_dbPath, true); - - Directory.CreateDirectory(_dbPath); - - var config = new DbConfig(); - var configFactory = new RocksDbConfigFactory(config, new PruningConfig(), new TestHardwareInfo(0), LimboLogs.Instance); - _dbFactory = new RocksDbFactory(configFactory, config, new HyperClockCacheWrapper(), new TestLogManager(), _dbPath); - } - - [TearDown] - public async Task TearDown() - { - foreach (ILogIndexStorage storage in _createdStorages) - { - await using (storage) - await storage.StopAsync(); - } - - if (!Directory.Exists(_dbPath)) - return; - - try - { - Directory.Delete(_dbPath, true); - } - catch - { - // ignore - } - } - - [OneTimeSetUp] - //[OneTimeTearDown] // Causes dispose issues, seems to be executed out-of-order - public static void RemoveRootFolder() - { - if (!Directory.Exists(nameof(LogIndexStorageIntegrationTests))) - return; - - try - { - Directory.Delete(nameof(LogIndexStorageIntegrationTests), true); - } - catch - { - // ignore - } - } - - [Combinatorial] - public async Task Set_Get_Test( - [Values(100, 200, int.MaxValue)] int compactionDistance, - [Values(1, 8, 16)] byte ioParallelism, - [Values] bool isBackwardsSync, - [Values] bool compact - ) - { - await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance, ioParallelism); - - BlockReceipts[][] batches = isBackwardsSync ? Reverse(testData.Batches) : testData.Batches; - await AddReceiptsAsync(logIndexStorage, batches, isBackwardsSync); - - if (compact) - await CompactAsync(logIndexStorage); - - VerifyReceipts(logIndexStorage, testData); - } - - [Combinatorial] - public async Task SetIntersecting_Get_Test( - [Values(100, 200, int.MaxValue)] int compactionDistance, - [Values(1, 8, 16)] byte ioParallelism, - [Values] bool isBackwardsSync, - [Values] bool compact - ) - { - await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance, ioParallelism); - - BlockReceipts[][] batches = isBackwardsSync ? Reverse(testData.Batches) : testData.Batches; - batches = Intersect(batches); - await AddReceiptsAsync(logIndexStorage, batches, isBackwardsSync); - - if (compact) - await CompactAsync(logIndexStorage); - - VerifyReceipts(logIndexStorage, testData); - } - - [Combinatorial] - public async Task BackwardsSet_Set_Get_Test( - [Values(100, 200, int.MaxValue)] int compactionDistance - ) - { - await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); - - BlockReceipts[][] batches = testData.Batches; - var half = batches.Length / 2; - - for (var i = 0; i < half + 1; i++) - { - if (half + i < batches.Length) - await AddReceiptsAsync(logIndexStorage, [batches[half + i]], isBackwardsSync: false); - if (i != 0 && half - i >= 0) - await AddReceiptsAsync(logIndexStorage, Reverse([batches[half - i]]), isBackwardsSync: true); - } - - VerifyReceipts(logIndexStorage, testData); - } - - [Combinatorial] - [Repeat(RaceConditionTestRepeat)] - [SuppressMessage("ReSharper", "AccessToDisposedClosure")] - public async Task Concurrent_BackwardsSet_Set_Get_Test( - [Values(100, int.MaxValue)] int compactionDistance - ) - { - await using (ILogIndexStorage setStorage = CreateLogIndexStorage(compactionDistance)) - { - int half = testData.Batches.Length / 2; - BlockReceipts[][] batches = testData.Batches - .Select((b, i) => i >= half ? b : b.Reverse().ToArray()) - .ToArray(); - - var forwardTask = Task.Run(async () => - { - for (int i = half; i < batches.Length; i++) - { - BlockReceipts[] batch = batches[i]; - await AddReceiptsAsync(setStorage, [batch], isBackwardsSync: false); - - Assert.That(setStorage.MinBlockNumber, Is.LessThanOrEqualTo(batch[0].BlockNumber)); - Assert.That(setStorage.MaxBlockNumber, Is.EqualTo(batch[^1].BlockNumber)); - } - }); - - var backwardTask = Task.Run(async () => - { - for (int i = half - 1; i >= 0; i--) - { - BlockReceipts[] batch = batches[i]; - await AddReceiptsAsync(setStorage, [batch], isBackwardsSync: true); - - Assert.That(setStorage.MinBlockNumber, Is.EqualTo(batch[^1].BlockNumber)); - Assert.That(setStorage.MaxBlockNumber, Is.GreaterThanOrEqualTo(batch[0].BlockNumber)); - } - }); - - await Task.WhenAll(forwardTask, backwardTask); - } - - // Create new storage to force-load everything from DB - await using (ILogIndexStorage testStorage = CreateLogIndexStorage(compactionDistance)) - VerifyReceipts(testStorage, testData); - } - - [Combinatorial] - [Repeat(RaceConditionTestRepeat)] - [SuppressMessage("ReSharper", "AccessToDisposedClosure")] - public async Task Concurrent_BackwardSet_Reorg_Get_Test( - [Values(1, 5, 10)] int reorgDepth, - [Values(100, int.MaxValue)] int compactionDistance - ) - { - int half = testData.Batches.Length / 2; - BlockReceipts[][] forwardBatches = testData.Batches.Skip(half).ToArray(); - BlockReceipts[][] backwardBatches = testData.Batches.Take(half).ToArray(); - - await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); - - // Add forward blocks first to establish the head of the chain - await AddReceiptsAsync(logIndexStorage, forwardBatches, isBackwardsSync: false); - - BlockReceipts[] reorgBlocks = forwardBatches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); - - var reorgTask = Task.Run(async () => - { - foreach (BlockReceipts block in reorgBlocks) - await logIndexStorage.RemoveReorgedAsync(block); - }); - - var backwardTask = Task.Run(async () => - { - foreach (BlockReceipts[] batch in Reverse(backwardBatches)) - await AddReceiptsAsync(logIndexStorage, [batch], isBackwardsSync: true); - }); - - await Task.WhenAll(reorgTask, backwardTask); - - VerifyReceipts(logIndexStorage, testData, excludedBlocks: reorgBlocks, maxBlock: reorgBlocks[0].BlockNumber - 1); - } - - [Combinatorial] - public async Task Set_ReorgLast_Get_Test( - [Values(1, 5, 20)] int reorgDepth, - [Values(100, int.MaxValue)] int compactionDistance - ) - { - await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); - - await AddReceiptsAsync(logIndexStorage, testData.Batches); - - BlockReceipts[] reorgBlocks = testData.Batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); - foreach (BlockReceipts block in reorgBlocks) - await logIndexStorage.RemoveReorgedAsync(block); - - VerifyReceipts(logIndexStorage, testData, excludedBlocks: reorgBlocks, maxBlock: reorgBlocks[0].BlockNumber - 1); - } - - [Ignore("Out-of-order reorgs are not supported ATM: only the first (by write order) Reorg operand per key is applied by MergeOperator.")] - [Combinatorial] - public async Task Set_ReorgOutOfOrder_Get_Test( - [Values(2, 5, 10)] int reorgDepth, - [Values(100, int.MaxValue)] int compactionDistance - ) - { - await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); - - await AddReceiptsAsync(logIndexStorage, testData.Batches); - - BlockReceipts[] reverseReorg = testData.Batches.SelectMany(b => b).Reverse().Take(reorgDepth).ToArray(); - - foreach (BlockReceipts block in reverseReorg) - await logIndexStorage.RemoveReorgedAsync(block); - - // Full data verification: would fail because MergeOperator only applies the first Reorg operand - // (the highest block in descending order), leaving intermediate blocks as stale data. - VerifyReceipts(logIndexStorage, testData, excludedBlocks: reverseReorg, maxBlock: reverseReorg[0].BlockNumber - 1); - } - - [Combinatorial] - public async Task Set_ReorgAndSetLast_Get_Test( - [Values(1, 5, 20)] int reorgDepth, - [Values(100, int.MaxValue)] int compactionDistance - ) - { - await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); - - await AddReceiptsAsync(logIndexStorage, testData.Batches); - - BlockReceipts[] reorgBlocks = testData.Batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); - foreach (BlockReceipts block in reorgBlocks) - { - await logIndexStorage.RemoveReorgedAsync(block); - await logIndexStorage.AddReceiptsAsync([block], false); - } - - VerifyReceipts(logIndexStorage, testData); - } - - [Combinatorial] - public async Task Set_ReorgLast_SetLast_Get_Test( - [Values(1, 5, 20)] int reorgDepth, - [Values(100, int.MaxValue)] int compactionDistance - ) - { - await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); - - await AddReceiptsAsync(logIndexStorage, testData.Batches); - - BlockReceipts[] reorgBlocks = testData.Batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); - - foreach (BlockReceipts block in reorgBlocks) - await logIndexStorage.RemoveReorgedAsync(block); - - await logIndexStorage.AddReceiptsAsync(reorgBlocks, false); - - VerifyReceipts(logIndexStorage, testData); - } - - [Combinatorial] - public async Task Set_ReorgNonexistent_Get_Test( - [Values(1, 5)] int reorgDepth, - [Values(100, int.MaxValue)] int compactionDistance - ) - { - await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); - - await AddReceiptsAsync(logIndexStorage, testData.Batches); - - var lastBlock = testData.Batches[^1][^1].BlockNumber; - BlockReceipts[] reorgBlocks = GenerateBlocks(new Random(4242), lastBlock - reorgDepth + 1, reorgDepth); - foreach (BlockReceipts block in reorgBlocks) - await logIndexStorage.RemoveReorgedAsync(block); - - // Need custom check because Reorg updates the last block even if it's "nonexistent" - Assert.That(logIndexStorage.MaxBlockNumber, Is.EqualTo(lastBlock - reorgDepth)); - - VerifyReceipts(logIndexStorage, testData, excludedBlocks: reorgBlocks, validateMinMax: false); - } - - [TestCase(1, 1)] - [TestCase(32, 64)] - [TestCase(64, 64)] - [TestCase(65, 64, Explicit = true)] - public async Task Set_Compact_ReorgLast_Get_Test(int reorgDepth, int maxReorgDepth) - { - await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(maxReorgDepth: maxReorgDepth); - - await AddReceiptsAsync(logIndexStorage, testData.Batches); - await CompactAsync(logIndexStorage); - - BlockReceipts[] reorgBlocks = testData.Batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); - foreach (BlockReceipts block in reorgBlocks) - await logIndexStorage.RemoveReorgedAsync(block); - - var lastBlock = testData.Batches[^1][^1].BlockNumber; - VerifyReceipts(logIndexStorage, testData, maxBlock: lastBlock - reorgDepth); - } - - [Combinatorial] - public async Task Set_PeriodicReorg_Get_Test( - [Values(10, 70)] int reorgFrequency, - [Values(1, 5)] int maxReorgDepth, - [Values] bool compactAfter - ) - { - await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(); - - var random = new Random(42); - var allReorgBlocks = new List(); - var allAddedBlocks = new List(); - - foreach (BlockReceipts[][] batches in testData.Batches.GroupBy(b => b[0].BlockNumber / reorgFrequency).Select(g => g.ToArray())) - { - await AddReceiptsAsync(logIndexStorage, batches); - - var reorgDepth = random.Next(1, maxReorgDepth); - BlockReceipts[] reorgBlocks = batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); - BlockReceipts[] addedBlocks = GenerateBlocks(random, reorgBlocks.First().BlockNumber, reorgBlocks.Length); - - allReorgBlocks.AddRange(reorgBlocks); - allAddedBlocks.AddRange(addedBlocks); - - foreach (BlockReceipts block in reorgBlocks) - await logIndexStorage.RemoveReorgedAsync(block); - - if (compactAfter) - await CompactAsync(logIndexStorage); - - await logIndexStorage.AddReceiptsAsync(addedBlocks, false); - } - - VerifyReceipts(logIndexStorage, testData, excludedBlocks: allReorgBlocks, addedBlocks: allAddedBlocks); - } - - [Ignore("Not supported, but is probably not needed.")] - [Combinatorial] - public async Task Set_ConsecutiveReorgsLast_Get_Test( - [Values(new[] { 2, 1 }, new[] { 1, 2 })] int[] reorgDepths, - [Values] bool compactBetween - ) - { - await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(); - - await AddReceiptsAsync(logIndexStorage, testData.Batches); - - BlockReceipts[] testBlocks = testData.Batches.SelectMany(b => b).ToArray(); - - foreach (var reorgDepth in reorgDepths) - { - foreach (BlockReceipts block in testBlocks.TakeLast(reorgDepth).ToArray()) - await logIndexStorage.RemoveReorgedAsync(block); - - if (compactBetween) - await CompactAsync(logIndexStorage); - } - - VerifyReceipts(logIndexStorage, testData, maxBlock: testBlocks[^1].BlockNumber - reorgDepths.Max()); - } - - [Combinatorial] - public async Task SetMultiInstance_Get_Test( - [Values(100, int.MaxValue)] int compactionDistance, - [Values] bool isBackwardsSync - ) - { - var half = testData.Batches.Length / 2; - - await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) - await AddReceiptsAsync(logIndexStorage, testData.Batches.Take(half)); - - await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) - await AddReceiptsAsync(logIndexStorage, testData.Batches.Skip(half)); - - await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) - VerifyReceipts(logIndexStorage, testData); - } - - [Combinatorial] - public async Task RepeatedSet_Get_Test( - [Values(100, int.MaxValue)] int compactionDistance, - [Values] bool isBackwardsSync - ) - { - await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); - - await AddReceiptsAsync(logIndexStorage, testData.Batches); - await AddReceiptsAsync(logIndexStorage, testData.Batches); - - VerifyReceipts(logIndexStorage, testData); - } - - [Combinatorial] - public async Task RepeatedSetMultiInstance_Get_Test( - [Values(100, int.MaxValue)] int compactionDistance, - [Values] bool isBackwardsSync - ) - { - await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) - await AddReceiptsAsync(logIndexStorage, testData.Batches); - - await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) - await AddReceiptsAsync(logIndexStorage, testData.Batches); - - await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) - VerifyReceipts(logIndexStorage, testData); - } - - [Combinatorial] - public async Task Set_NewInstance_Get_Test( - [Values(1, 8)] int ioParallelism, - [Values(100, int.MaxValue)] int compactionDistance, - [Values] bool isBackwardsSync - ) - { - await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) - await AddReceiptsAsync(logIndexStorage, testData.Batches); - - await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) - VerifyReceipts(logIndexStorage, testData); - } - - [Combinatorial] - [Repeat(RaceConditionTestRepeat)] - [SuppressMessage("ReSharper", "AccessToDisposedClosure")] - public async Task Set_ConcurrentGet_Test( - [Values(1, 200, int.MaxValue)] int compactionDistance, - [Values] bool isBackwardsSync - ) - { - await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); - - using var getCancellation = new CancellationTokenSource(); - CancellationToken token = getCancellation.Token; - - ConcurrentBag exceptions = []; - Thread[] getThreads = - [ - new(() => VerifyReceiptsPartialLoop(new Random(42), logIndexStorage, testData, exceptions, token)), - new(() => VerifyReceiptsPartialLoop(new Random(4242), logIndexStorage, testData, exceptions, token)), - new(() => VerifyReceiptsPartialLoop(new Random(424242), logIndexStorage, testData, exceptions, token)) - ]; - getThreads.ForEach(t => t.Start()); - - await AddReceiptsAsync(logIndexStorage, testData.Batches); - - await getCancellation.CancelAsync(); - getThreads.ForEach(t => t.Join()); - - if (exceptions.FirstOrDefault() is { } exception) - ExceptionDispatchInfo.Capture(exception).Throw(); - - VerifyReceipts(logIndexStorage, testData); - } - - [Combinatorial] - public async Task SetFailure_Get_Test( - [Values(1, 20, 51, 100)] int failOnCallN, - [Values] bool isBackwardsSync - ) - { - BlockReceipts[][] batches = isBackwardsSync ? Reverse(testData.Batches) : testData.Batches; - var midBlock = testData.Batches[^1][^1].BlockNumber / 2; - - await using ILogIndexStorage failLogIndexStorage = CreateLogIndexStorage(failOnBlock: midBlock, failOnCallN: failOnCallN); - - Exception exception = Assert.ThrowsAsync(() => AddReceiptsAsync(failLogIndexStorage, batches, isBackwardsSync)); - Assert.That(exception, Has.Message.EqualTo(SaveFailingLogIndexStorage.FailMessage)); - - VerifyReceipts( - failLogIndexStorage, testData, - minBlock: failLogIndexStorage.MinBlockNumber ?? 0, maxBlock: failLogIndexStorage.MaxBlockNumber ?? 0 - ); - } - - [Combinatorial] - public async Task SetFailure_Set_Get_Test( - [Values(1, 20, 51, 100)] int failOnCallN, - [Values] bool isBackwardsSync - ) - { - BlockReceipts[][] batches = isBackwardsSync ? Reverse(testData.Batches) : testData.Batches; - var midBlock = testData.Batches[^1][^1].BlockNumber / 2; - - await using (ILogIndexStorage failLogIndexStorage = CreateLogIndexStorage(failOnBlock: midBlock, failOnCallN: failOnCallN)) - { - Exception exception = Assert.ThrowsAsync(() => AddReceiptsAsync(failLogIndexStorage, batches, isBackwardsSync)); - Assert.That(exception, Has.Message.EqualTo(SaveFailingLogIndexStorage.FailMessage)); - } - - await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(); - await AddReceiptsAsync(logIndexStorage, batches, isBackwardsSync); - - VerifyReceipts(logIndexStorage, testData); - } - - [Combinatorial] - [SuppressMessage("ReSharper", "AccessToDisposedClosure")] - public async Task SetMergeFailure_AnyWrite_Test( - [Values(1, 20, 51, 100)] int failOnCallN - ) - { - var midBlock = testData.Batches[^1][^1].BlockNumber / 2; - await using var storage = (LogIndexStorage)CreateLogIndexStorage(failOnBlock: midBlock, failOnCallN: failOnCallN, failOnMerge: true); - - try - { - await AddReceiptsAsync(storage, testData.Batches); - - // force compaction if the error hasn't propagated already - await storage.CompactAsync(); - } - catch (LogIndexStateException) - { - // Expected - } - - Assert.That(storage.HasBackgroundError, Is.True); - - IEnumerable sinceLastBatch = testData.Batches.SelectMany(b => b) - .SkipWhile(b => b.BlockNumber < storage.MaxBlockNumber); - - await Assert.ThatAsync( - () => storage.AddReceiptsAsync(sinceLastBatch.Skip(1).Take(10).ToArray(), false), - Throws.InstanceOf().And.Message.Contain("merge") - ); - - await Assert.ThatAsync( - () => storage.RemoveReorgedAsync(sinceLastBatch.First()), - Throws.InstanceOf().And.Message.Contain("merge") - ); - - await Assert.ThatAsync( - () => storage.CompactAsync(), - Throws.InstanceOf().And.Message.Contain("merge") - ); - - Assert.DoesNotThrowAsync(() => storage.StopAsync()); - Assert.DoesNotThrowAsync(() => storage.StopAsync()); - } - - [Combinatorial] - public async Task Set_AlgoChange_Test() - { - if (CompressionAlgorithm.Supported.Count < 2) - Assert.Ignore("Less than 2 supported compression algorithms"); - - await using (var logIndexStorage = CreateLogIndexStorage()) - await AddReceiptsAsync(logIndexStorage, [testData.Batches[0]]); - - var oldAlgo = testData.Compression ?? CompressionAlgorithm.Best.Key; - var newAlgo = CompressionAlgorithm.Supported.First(c => c.Key != oldAlgo).Key; - - NotSupportedException exception = Assert.Throws(() => CreateLogIndexStorage(compressionAlgo: newAlgo)); - Assert.That(exception, Has.Message.Contain(oldAlgo).And.Message.Contain(newAlgo)); - } - - private static BlockReceipts[] GenerateBlocks(Random random, int from, int count) => - new TestData(random, 1, count, startNum: from).Batches[0]; - - private static async Task AddReceiptsAsync(ILogIndexStorage logIndexStorage, IEnumerable batches, bool isBackwardsSync = false) - { - long timestamp = Stopwatch.GetTimestamp(); - - LogIndexUpdateStats totalStats = new(logIndexStorage); - (int count, int length) = (0, 0); - foreach (BlockReceipts[] batch in batches) - { - count++; - length = batch.Length; - await logIndexStorage.AddReceiptsAsync(batch, isBackwardsSync, totalStats); - } - - if (LogStatistics) - { - await TestContext.Out.WriteLineAsync( - $""" - x{count} {nameof(LogIndexStorage.AddReceiptsAsync)}([{length}], {isBackwardsSync}) in {Stopwatch.GetElapsedTime(timestamp)}: - {totalStats:d} - {'\t'}DB size: {logIndexStorage.GetDbSize()} - - """ - ); - } - } - - private static void VerifyReceipts(ILogIndexStorage logIndexStorage, TestData testData, - Dictionary>? excludedAddresses = null, - Dictionary>>? excludedTopics = null, - Dictionary>? addedAddresses = null, - Dictionary>>? addedTopics = null, - int? minBlock = null, int? maxBlock = null, - bool validateMinMax = true - ) - { - minBlock ??= testData.Batches[0][0].BlockNumber; - maxBlock ??= testData.Batches[^1][^1].BlockNumber; - - if (validateMinMax) - { - using (Assert.EnterMultipleScope()) - { - Assert.That(logIndexStorage.MinBlockNumber, Is.EqualTo(minBlock)); - Assert.That(logIndexStorage.MaxBlockNumber, Is.EqualTo(maxBlock)); - } - } - - foreach ((Address address, HashSet blocks) in testData.AddressMap) - { - IEnumerable expectedBlocks = blocks; - - if (excludedAddresses != null && excludedAddresses.TryGetValue(address, out HashSet addressExcludedBlocks)) - expectedBlocks = expectedBlocks.Except(addressExcludedBlocks); - - if (addedAddresses != null && addedAddresses.TryGetValue(address, out HashSet addressAddedBlocks)) - expectedBlocks = expectedBlocks.Concat(addressAddedBlocks); - - expectedBlocks = expectedBlocks.Order(); - - if (minBlock > testData.Batches[0][0].BlockNumber) - expectedBlocks = expectedBlocks.SkipWhile(b => b < minBlock); - - if (maxBlock < testData.Batches[^1][^1].BlockNumber) - expectedBlocks = expectedBlocks.TakeWhile(b => b <= maxBlock); - - expectedBlocks = expectedBlocks.ToArray(); - - foreach (var (from, to) in testData.Ranges) - { - Assert.That( - logIndexStorage.GetBlockNumbersFor(address, from, to), - Is.EqualTo(expectedBlocks.SkipWhile(i => i < from).TakeWhile(i => i <= to)), - $"Address: {address}, from {from} to {to}" - ); - } - } - - foreach ((int idx, Dictionary> byTopic) in testData.TopicMap) - { - foreach ((Hash256 topic, HashSet blocks) in byTopic) - { - IEnumerable expectedBlocks = blocks; - - if (excludedTopics != null && excludedTopics[idx].TryGetValue(topic, out HashSet topicExcludedBlocks)) - expectedBlocks = expectedBlocks.Except(topicExcludedBlocks); - - if (addedTopics != null && addedTopics[idx].TryGetValue(topic, out HashSet topicAddedBlocks)) - expectedBlocks = expectedBlocks.Concat(topicAddedBlocks); - - expectedBlocks = expectedBlocks.Order(); - - if (minBlock > testData.Batches[0][0].BlockNumber) - expectedBlocks = expectedBlocks.SkipWhile(b => b < minBlock); - - if (maxBlock < testData.Batches[^1][^1].BlockNumber) - expectedBlocks = expectedBlocks.TakeWhile(b => b <= maxBlock); - - expectedBlocks = expectedBlocks.ToArray(); - - foreach (var (from, to) in testData.Ranges) - { - Assert.That( - logIndexStorage.GetBlockNumbersFor(idx, topic, from, to), - Is.EqualTo(expectedBlocks.SkipWhile(i => i < from).TakeWhile(i => i <= to)), - $"Topic: [{idx}] {topic}, {from} - {to}" - ); - } - } - } - } - - private static void VerifyReceipts(ILogIndexStorage logIndexStorage, TestData testData, - IEnumerable? excludedBlocks, IEnumerable? addedBlocks = null, - int? minBlock = null, int? maxBlock = null, - bool validateMinMax = true) - { - var excludeMaps = excludedBlocks == null ? default : TestData.GenerateMaps(excludedBlocks); - var addMaps = addedBlocks == null ? default : TestData.GenerateMaps(addedBlocks); - - VerifyReceipts( - logIndexStorage, testData, - excludedAddresses: excludeMaps.address, excludedTopics: excludeMaps.topic, - addedAddresses: addMaps.address, addedTopics: addMaps.topic, - minBlock: minBlock, maxBlock: maxBlock, - validateMinMax: validateMinMax - ); - } - - private static void VerifyReceiptsPartialLoop(Random random, ILogIndexStorage logIndexStorage, TestData testData, - ConcurrentBag exceptions, CancellationToken cancellationToken) - { - try - { - (List
addresses, List<(int, Hash256)> topics) = (testData.Addresses, testData.Topics); - - while (!cancellationToken.IsCancellationRequested) - { - if (addresses.Count != 0) - { - Address address = random.NextFrom(addresses); - HashSet expectedBlocks = testData.AddressMap[address]; - - if (logIndexStorage.MinBlockNumber is not { } min || logIndexStorage.MaxBlockNumber is not { } max) - continue; - - Assert.That( - logIndexStorage.GetBlockNumbersFor(address, min, max), - Is.EqualTo(expectedBlocks.SkipWhile(i => i < min).TakeWhile(i => i <= max)), - $"Address: {address}, available: {min} - {max}" - ); - } - - if (topics.Count != 0) - { - (int idx, Hash256 topic) = random.NextFrom(topics); - HashSet expectedBlocks = testData.TopicMap[idx][topic]; - - if (logIndexStorage.MinBlockNumber is not { } min || logIndexStorage.MaxBlockNumber is not { } max) - continue; - - Assert.That( - logIndexStorage.GetBlockNumbersFor(idx, topic, min, max), - Is.EqualTo(expectedBlocks.SkipWhile(i => i < min).TakeWhile(i => i <= max)), - $"Topic: [{idx}] {topic}, available: {min} - {max}" - ); - } - } - } - catch (Exception ex) - { - exceptions.Add(ex); - } - } - - private static BlockReceipts[][] Reverse(BlockReceipts[][] batches) - { - int length = batches.Length; - BlockReceipts[][] result = new BlockReceipts[length][]; - - int index = 0; - foreach (BlockReceipts[] batch in batches.Reverse()) - result[index++] = batch.Reverse().ToArray(); - - return result; - } - - private static BlockReceipts[][] Intersect(BlockReceipts[][] batches) - { - BlockReceipts[][] result = new BlockReceipts[batches.Length + 1][]; - - for (int i = 0; i < result.Length; i++) - { - if (i == 0) - result[i] = batches[i]; - else if (i == batches.Length) - result[i] = batches[^1].Skip(batches[^2].Length / 2).ToArray(); - else - result[i] = batches[i - 1].Skip(batches[i - 1].Length / 2).Concat(batches[i].Take(batches[i].Length / 2)).ToArray(); - } - - return result; - } - - private static async Task CompactAsync(ILogIndexStorage logIndexStorage) - { - long timestamp = Stopwatch.GetTimestamp(); - await ((LogIndexStorage)logIndexStorage).CompactAsync(); - - if (LogStatistics) - { - await TestContext.Out.WriteLineAsync( - $""" - {nameof(LogIndexStorage.CompactAsync)}() in {Stopwatch.GetElapsedTime(timestamp)}: - {'\t'}DB size: {logIndexStorage.GetDbSize()} - - """ - ); - } - } - - public class TestData - { - private readonly int _batchCount; - private readonly int _blocksPerBatch; - private readonly int _startNum; - - // Lazy avoids generating all the data just to display test cases in the runner - private readonly Lazy _batches; - public BlockReceipts[][] Batches => _batches.Value; - - private List
? _addresses; - private List<(int, Hash256)>? _topics; - - private readonly Lazy> _ranges; - public IEnumerable<(int from, int to)> Ranges => _ranges.Value; - - public Dictionary> AddressMap { get; private set; } = new(); - public Dictionary>> TopicMap { get; private set; } = new(); - - public List
Addresses - { - get - { - _ = Batches; - return _addresses!; - } - } - - public List<(int, Hash256)> Topics - { - get - { - _ = Batches; - return _topics!; - } - } - - public bool ExtendedGetRanges { get; init; } - public string? Compression { get; init; } - - public TestData(Random random, int batchCount, int blocksPerBatch, int startNum = 0) - { - _batchCount = batchCount; - _blocksPerBatch = blocksPerBatch; - _startNum = startNum; - - _batches = new(() => GenerateBatches(random, batchCount, blocksPerBatch, startNum)); - _ranges = new(() => ExtendedGetRanges ? GenerateExtendedRanges() : GenerateSimpleRanges()); - } - - public TestData(int batchCount, int blocksPerBatch, int startNum = 0) : this(new(42), batchCount, blocksPerBatch, startNum) { } - - private BlockReceipts[][] GenerateBatches(Random random, int batchCount, int blocksPerBatch, int startNum = 0) - { - var batches = new BlockReceipts[batchCount][]; - var blocksCount = batchCount * blocksPerBatch; - - Address[] customAddresses = - [ - Address.Zero, Address.MaxValue, - new(new byte[] { 1 }.PadLeft(Address.Size)), new(new byte[] { 1, 1 }.PadLeft(Address.Size)), - new(new byte[] { 1 }.PadRight(Address.Size)), new(new byte[] { 1, 1 }.PadRight(Address.Size)), - new(new byte[] { 0 }.PadLeft(Address.Size, 0xFF)), new(new byte[] { 0 }.PadRight(Address.Size, 0xFF)), - ]; - - Hash256[] customTopics = - [ - Hash256.Zero, new(Array.Empty().PadRight(Hash256.Size, 0xFF)), - new(new byte[] { 0 }.PadLeft(Hash256.Size)), new(new byte[] { 1 }.PadLeft(Hash256.Size)), - new(new byte[] { 0 }.PadRight(Hash256.Size)), new(new byte[] { 1 }.PadRight(Hash256.Size)), - new(new byte[] { 0 }.PadLeft(Hash256.Size, 0xFF)), new(new byte[] { 0 }.PadRight(Hash256.Size, 0xFF)), - ]; - - Address[] addresses = Enumerable.Repeat(0, Math.Max(10, blocksCount / 5) - customAddresses.Length) - //var addresses = Enumerable.Repeat(0, 0) - .Select(_ => new Address(random.NextBytes(Address.Size))) - .Concat(customAddresses) - .ToArray(); - Hash256[] topics = Enumerable.Repeat(0, addresses.Length * 7 - customTopics.Length) - //var topics = Enumerable.Repeat(0, 0) - .Select(_ => new Hash256(random.NextBytes(Hash256.Size))) - .Concat(customTopics) - .ToArray(); - - // Generate batches - int blockNum = startNum; - for (int i = 0; i < batches.Length; i++) - { - BlockReceipts[] batch = batches[i] = new BlockReceipts[blocksPerBatch]; - - for (int j = 0; j < batch.Length; j++) - batch[j] = new(blockNum++, GenerateReceipts(random, addresses, topics)); - } - - var maps = GenerateMaps(batches.SelectMany(b => b)); - - (AddressMap, TopicMap) = (maps.address, maps.topic); - - _addresses = maps.address.Keys.ToList(); - _topics = maps.topic.SelectMany(byIdx => byIdx.Value.Select(byTpc => (byIdx.Key, byTpc.Key))).ToList(); - - return batches; - } - - public static (Dictionary> address, Dictionary>> topic) GenerateMaps( - IEnumerable blocks) - { - Dictionary> address = new(); - Dictionary>> topic = new(); - - foreach (BlockReceipts block in blocks) - { - foreach (TxReceipt txReceipt in block.Receipts) - { - foreach (LogEntry log in txReceipt.Logs!) - { - HashSet addressMap = address.GetOrAdd(log.Address, static _ => []); - addressMap.Add(block.BlockNumber); - - for (int i = 0; i < log.Topics.Length; i++) - { - Dictionary> topicI = topic.GetOrAdd(i, static _ => []); - HashSet topicMap = topicI.GetOrAdd(log.Topics[i], static _ => []); - topicMap.Add(block.BlockNumber); - } - } - } - } - - return (address, topic); - } - - private static TxReceipt[] GenerateReceipts(Random random, Address[] addresses, Hash256[] topics) - { - (int min, int max) logsPerBlock = (0, 200); - (int min, int max) logsPerTx = (0, 10); - - LogEntry[] logs = Enumerable - .Repeat(0, random.Next(logsPerBlock.min, logsPerBlock.max + 1)) - .Select(_ => Build.A.LogEntry - .WithAddress(random.NextFrom(addresses)) - .WithTopics(topics.Length == 0 - ? [] - : Enumerable.Repeat(0, random.Next(4)).Select(_ => random.NextFrom(topics)).ToArray() - ).TestObject - ).ToArray(); - - List receipts = new(); - for (var i = 0; i < logs.Length;) - { - int count = random.Next(logsPerTx.min, Math.Min(logsPerTx.max, logs.Length - i) + 1); - Range range = i..(i + count); - - receipts.Add(new() { Logs = logs[range] }); - i = range.End.Value; - } - - return receipts.ToArray(); - } - - private static HashSet<(int from, int to)> GenerateSimpleRanges(int min, int max) - { - int quarter = (max - min) / 4; - return [(0, int.MaxValue), (min, max), (min + quarter, max - quarter)]; - } - - private static HashSet<(int from, int to)> GenerateExtendedRanges(int min, int max) - { - HashSet<(int, int)> ranges = new(); - - int[] edges = [min - 1, min, min + 1, max - 1, max + 1]; - ranges.AddRange(edges.SelectMany(_ => edges, static (x, y) => (x, y))); - - const int step = 100; - for (var i = min; i <= max; i += step) - { - var middles = new[] { i - step, i - 1, i, i + 1, i + step }; - ranges.AddRange(middles.SelectMany(_ => middles, static (x, y) => (x, y))); - } - - return ranges; - } - - private HashSet<(int from, int to)> GenerateSimpleRanges() => GenerateSimpleRanges( - _startNum, _startNum + _batchCount * _blocksPerBatch - 1 - ); - - private HashSet<(int from, int to)> GenerateExtendedRanges() => GenerateExtendedRanges( - _startNum, _startNum + _batchCount * _blocksPerBatch - 1 - ); - - public override string ToString() => - $"{_batchCount} * {_blocksPerBatch} blocks (ex-ranges: {ExtendedGetRanges}, compression: {Compression})"; - } - - private class SaveFailingLogIndexStorage(IDbFactory dbFactory, ILogManager logManager, ILogIndexConfig config) - : LogIndexStorage(dbFactory, logManager, config) - { - public const string FailMessage = "Test exception."; - - public int FailOnBlock { get; init; } - public int FailOnCallN { get; init; } - public bool FailOnMerge { get; init; } - - private int _count; - private bool _corrupted; - - protected override void MergeBlockNumbers(IWriteBatch dbBatch, ReadOnlySpan key, List numbers, bool isBackwardSync, LogIndexUpdateStats? stats) - { - var isFailBlock = - FailOnBlock >= Math.Min(numbers[0], numbers[^1]) && - FailOnBlock <= Math.Max(numbers[0], numbers[^1]); - - if (isFailBlock && Interlocked.Increment(ref _count) >= FailOnCallN && !Interlocked.Exchange(ref _corrupted, true)) - { - if (FailOnMerge) - { - // Force "invalid order" in MergeOperator - int invalidBlockNum = isBackwardSync ? int.MaxValue : 0; - base.MergeBlockNumbers(dbBatch, key, [invalidBlockNum], isBackwardSync, stats); - } - else - { - throw new(FailMessage); - } - } - - base.MergeBlockNumbers(dbBatch, key, numbers, isBackwardSync, stats); - } - } - } -} diff --git a/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageTestExtensions.cs b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageTestExtensions.cs deleted file mode 100644 index dccf8578a996..000000000000 --- a/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageTestExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using System.Threading.Tasks; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Db.LogIndex; - -namespace Nethermind.Db.Test.LogIndex; - -public static class LogIndexStorageTestExtensions -{ - extension(ILogIndexStorage storage) - { - public List GetBlockNumbersFor(Address address, int from, int to) - { - var result = new List(); - using IEnumerator enumerator = storage.GetEnumerator(address, from, to); - - while (enumerator.MoveNext()) - result.Add(enumerator.Current); - - return result; - } - - public List GetBlockNumbersFor(int index, Hash256 topic, int from, int to) - { - var result = new List(); - using IEnumerator enumerator = storage.GetEnumerator(index, topic, from, to); - - while (enumerator.MoveNext()) - result.Add(enumerator.Current); - - return result; - } - - public Task AddReceiptsAsync(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats = null) - { - LogIndexAggregate aggregate = storage.Aggregate(batch, isBackwardSync, stats); - return storage.AddReceiptsAsync(aggregate, stats); - } - } -} diff --git a/src/Nethermind/Nethermind.Db.Test/LogIndex/MergeOperatorTests.cs b/src/Nethermind/Nethermind.Db.Test/LogIndex/MergeOperatorTests.cs deleted file mode 100644 index a7d9386face6..000000000000 --- a/src/Nethermind/Nethermind.Db.Test/LogIndex/MergeOperatorTests.cs +++ /dev/null @@ -1,247 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Linq; -using System.Runtime.InteropServices; -using ConcurrentCollections; -using MathNet.Numerics.Random; -using Nethermind.Core; -using Nethermind.Core.Collections; -using Nethermind.Core.Extensions; -using Nethermind.Db.LogIndex; -using NSubstitute; -using NUnit.Framework; - -namespace Nethermind.Db.Test.LogIndex; - -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] -public class MergeOperatorTests -{ - [TestCase( - null, - new[] { "1, 2", "3", "4" }, - "1, 2, 3, 4" - )] - [TestCase( - "1", - new[] { "2, 3", "4" }, - "1, 2, 3, 4" - )] - [TestCase( - "1, 2", - new[] { "3, 4", "Reorg:3", "5, 6" }, - "1, 2, 5, 6" - )] - [TestCase( - "1, 2", - new[] { "3, 4", "Reorg:2", "5, 6" }, - "1, 5, 6" - )] - [TestCase( - "1, 2", - new[] { "3, 4", "Reorg:3", "Reorg:4", "5, 6" }, - "1, 2, 5, 6" - )] - [TestCase( - "1, 2", - new[] { "3, 4", "Reorg:4", "Reorg:3", "5, 6" }, - "1, 2, 5, 6", - Ignore = "Subsequent reverse reorgs are not supported." - )] - [TestCase( - "1, 2", - new[] { "3, 4", "Reorg:3", "5, 6", "Reorg:5", "6, 7" }, - "1, 2, 6, 7" - )] - [TestCase( - "1, 2, 3", - new[] { "4, 5", "Truncate:4", "6, 7" }, - "5, 6, 7" - )] - [TestCase( - "1, 2, 3", - new[] { "4, 5", "Truncate:3", "6, 7" }, - "4, 5, 6, 7" - )] - [TestCase( - "1, 2, 3", - new[] { "4, 5", "Truncate:3", "Truncate:4", "6, 7" }, - "5, 6, 7" - )] - [TestCase( - "1, 2, 3", - new[] { "4, 5", "Truncate:4", "Truncate:3", "6, 7" }, - "5, 6, 7" - )] - [TestCase( - "1, 2, 3", - new[] { "4, 5", "Truncate:3", "6, 7", "Truncate:6" }, - "7" - )] - public void FullMergeForward(string? existing, string[] operands, string expected) - { - LogIndexStorage.MergeOperator op = CreateOperator(); - CreateEnumerator(Serialize(existing), operands.Select(Serialize).ToArray(), out RocksDbMergeEnumerator enumerator); - - byte[] key = GenerateKey(Address.Size, isBackward: false); - using ArrayPoolList merged = op.FullMerge(key, enumerator); - Assert.That( - Deserialize(merged?.ToArray()), - Is.EqualTo(expected.Split(',').Select(int.Parse).ToArray()) - ); - } - - [TestCase( - null, - new[] { "4", "3", "2, 1" }, - "4, 3, 2, 1" - )] - [TestCase( - "4", - new[] { "3, 2", "1" }, - "4, 3, 2, 1" - )] - [TestCase( - "7, 6, 5", - new[] { "4, 3", "Truncate:4", "2, 1" }, - "3, 2, 1" - )] - [TestCase( - "7, 6, 5", - new[] { "4, 3", "Truncate:5", "2, 1" }, - "4, 3, 2, 1" - )] - [TestCase( - "7, 6, 5", - new[] { "4, 3", "Truncate:5", "Truncate:4", "2, 1" }, - "3, 2, 1" - )] - [TestCase( - "7, 6, 5", - new[] { "4, 3", "Truncate:4", "Truncate:5", "2, 1" }, - "3, 2, 1" - )] - [TestCase( - "7, 6, 5", - new[] { "4, 3", "Truncate:5", "2, 1", "Truncate:2" }, - "1" - )] - public void FullMergeBackward(string? existing, string[] operands, string expected) - { - LogIndexStorage.MergeOperator op = CreateOperator(); - CreateEnumerator(Serialize(existing), operands.Select(Serialize).ToArray(), out RocksDbMergeEnumerator enumerator); - - byte[] key = GenerateKey(Address.Size, isBackward: true); - ArrayPoolList merged = op.FullMerge(key, enumerator); - Assert.That( - Deserialize(merged?.ToArray()), - Is.EqualTo(expected.Split(',').Select(int.Parse).ToArray()) - ); - } - - [TestCase( - new[] { "1", "2", "3", "4" }, - "1, 2, 3, 4" - )] - [TestCase( - new[] { "1", "2, 3", "4" }, - "1, 2, 3, 4" - )] - public void PartialMergeForward(string[] operands, string expected) - { - LogIndexStorage.MergeOperator op = CreateOperator(); - CreateEnumerator(null, operands.Select(Serialize).ToArray(), out RocksDbMergeEnumerator enumerator); - - byte[] key = GenerateKey(Address.Size, isBackward: false); - using ArrayPoolList merged = op.PartialMerge(key, enumerator); - Assert.That( - Deserialize(merged?.ToArray()), - Is.EqualTo(expected.Split(',').Select(int.Parse).ToArray()) - ); - } - - [TestCase( - new[] { "4", "3", "2", "1" }, - "4, 3, 2, 1" - )] - [TestCase( - new[] { "4", "3, 2", "1" }, - "4, 3, 2, 1" - )] - public void PartialMergeBackward(string[] operands, string expected) - { - LogIndexStorage.MergeOperator op = CreateOperator(); - CreateEnumerator(null, operands.Select(Serialize).ToArray(), out RocksDbMergeEnumerator enumerator); - - byte[] key = GenerateKey(Address.Size, isBackward: true); - using ArrayPoolList merged = op.PartialMerge(key, enumerator); - Assert.That( - Deserialize(merged?.ToArray()), - Is.EqualTo(expected.Split(',').Select(int.Parse).ToArray()) - ); - } - - [OneTimeTearDown] - public static void OneTimeTearDown() => Handles.ForEach(h => h.Free()); - - private static readonly LogIndexStorage.ICompressor Compressor = new LogIndexStorage.NoOpCompressor(); - - private static readonly ConcurrentHashSet Handles = []; - - private static LogIndexStorage.MergeOperator CreateOperator() - { - ILogIndexStorage storage = Substitute.For(); - return new(storage, Compressor, 0); - } - - private static void CreateEnumerator(byte[]? existingValue, byte[][] operands, out RocksDbMergeEnumerator enumerator) - { - var operandsPtrs = new IntPtr[operands.Length]; - var operandsLengths = operands.Select(x => (long)x.Length).ToArray(); - - for (int i = 0; i < operands.Length; i++) - { - var handle = GCHandle.Alloc(operands[i], GCHandleType.Pinned); - Handles.Add(handle); - - operandsPtrs[i] = handle.AddrOfPinnedObject(); - operandsLengths[i] = operands[i].Length; - } - - enumerator = existingValue is null - ? new(Span.Empty, false, operandsPtrs, operandsLengths) - : new(existingValue, true, operandsPtrs, operandsLengths); - } - - private static byte[] GenerateKey(int prefixSize, bool isBackward) => Random.Shared - .NextBytes(prefixSize + LogIndexStorage.BlockNumberSize) - .Concat(isBackward ? LogIndexStorage.Postfix.BackwardMerge : LogIndexStorage.Postfix.ForwardMerge) - .ToArray(); - - private static byte[]? Serialize(string? input) => - input is null ? null : Bytes.Concat(input.Split(',').Select(s => s.Trim()).Select(s => s switch - { - _ when int.TryParse(s, out int blockNum) => blockNum.ToLittleEndianByteArray(), - _ when TryParseMergeOp(s, out Span op) => op.ToArray(), - _ => throw new FormatException($"Invalid operand: \"{input}\".") - }).ToArray()); - - private static bool TryParseMergeOp(string input, out Span bytes) - { - bytes = default; - - var parts = input.Split(":"); - if (parts.Length != 2) return false; - - if (!Enum.TryParse(parts[0], out LogIndexStorage.MergeOp op)) return false; - if (!int.TryParse(parts[1], out int blockNum)) return false; - - var buffer = new byte[LogIndexStorage.MergeOps.Size]; - bytes = LogIndexStorage.MergeOps.Create(op, blockNum, buffer); - return true; - } - - private static int[]? Deserialize(byte[]? input) => input is null ? null : MemoryMarshal.Cast(input).ToArray(); -} diff --git a/src/Nethermind/Nethermind.Db/CompressingDb.cs b/src/Nethermind/Nethermind.Db/CompressingDb.cs index 301da2f7b1d4..881b839f25bb 100644 --- a/src/Nethermind/Nethermind.Db/CompressingDb.cs +++ b/src/Nethermind/Nethermind.Db/CompressingDb.cs @@ -2,11 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using System.Collections.Generic; using System.Linq; using Nethermind.Core; -using Nethermind.Core.Buffers; using Nethermind.Core.Extensions; namespace Nethermind.Db @@ -20,55 +18,75 @@ public static class KeyValueStoreCompressingExtensions /// A wrapped db. public static IDb WithEOACompressed(this IDb @this) => new EOACompressingDb(@this); - // TODO: consider wrapping IDbWithSpan to make the read with a span, with no alloc for reading? - private class EOACompressingDb(IDb wrapped) : IDb, ITunableDb + private class EOACompressingDb : IDb, ITunableDb { + private readonly IDb _wrapped; + + public EOACompressingDb(IDb wrapped) + { + // TODO: consider wrapping IDbWithSpan to make the read with a span, with no alloc for reading? + _wrapped = wrapped; + } + public byte[]? this[ReadOnlySpan key] { - get => Decompress(wrapped[key]); - set => wrapped[key] = Compress(value); + get => Decompress(_wrapped[key]); + set => _wrapped[key] = Compress(value); } - public IWriteBatch StartWriteBatch() => new WriteBatch(wrapped.StartWriteBatch()); + public IWriteBatch StartWriteBatch() => new WriteBatch(_wrapped.StartWriteBatch()); - private class WriteBatch(IWriteBatch wrapped) : IWriteBatch + private class WriteBatch : IWriteBatch { - public void Dispose() => wrapped.Dispose(); + private readonly IWriteBatch _wrapped; + + public WriteBatch(IWriteBatch wrapped) => _wrapped = wrapped; - public void Clear() => wrapped.Clear(); + public void Dispose() => _wrapped.Dispose(); + + public void Clear() => _wrapped.Clear(); public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - => wrapped.Set(key, Compress(value), flags); + => _wrapped.Set(key, Compress(value), flags); public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - => wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags); + { + _wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags); + } public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - => throw new InvalidOperationException("EOA compressing DB does not support merging"); + { + throw new InvalidOperationException("EOA compressing DB does not support merging"); + } - public bool PreferWriteByArray => wrapped.PreferWriteByArray; + public bool PreferWriteByArray => _wrapped.PreferWriteByArray; public byte[]? this[ReadOnlySpan key] { - set => wrapped[key] = Compress(value); + set => _wrapped[key] = Compress(value); } } + /// /// The end of rlp of an EOA account, an empty and an empty . /// - private static ReadOnlySpan EmptyCodeHashStorageRoot => - [ + private static ReadOnlySpan EmptyCodeHashStorageRoot => new byte[] + { 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112 - ]; + }; private const byte PreambleLength = 1; private const byte PreambleIndex = 0; private const byte PreambleValue = 0; - private static byte[]? Compress(byte[]? bytes) => bytes is null ? null : Compress(bytes, stackalloc byte[bytes.Length]).ToArray(); + private static byte[]? Compress(byte[]? bytes) + { + if (bytes is null) return null; + return Compress(bytes, stackalloc byte[bytes.Length]).ToArray(); + } private static ReadOnlySpan Compress(ReadOnlySpan bytes, Span compressed) { @@ -107,56 +125,55 @@ private static ReadOnlySpan Compress(ReadOnlySpan bytes, Span return decompressed; } - public void Dispose() => wrapped.Dispose(); + public void Dispose() => _wrapped.Dispose(); - public string Name => wrapped.Name; + public string Name => _wrapped.Name; public KeyValuePair[] this[byte[][] keys] => throw new NotImplementedException(); - public IEnumerable> GetAll(bool ordered = false) => wrapped.GetAll(ordered) + public IEnumerable> GetAll(bool ordered = false) => _wrapped.GetAll(ordered) .Select(static kvp => new KeyValuePair(kvp.Key, Decompress(kvp.Value))); public IEnumerable GetAllKeys(bool ordered = false) => - wrapped.GetAllKeys(ordered); + _wrapped.GetAllKeys(ordered); public IEnumerable GetAllValues(bool ordered = false) => - wrapped.GetAllValues(ordered).Select(Decompress); + _wrapped.GetAllValues(ordered).Select(Decompress); - public void Remove(ReadOnlySpan key) => wrapped.Remove(key); + public void Remove(ReadOnlySpan key) => _wrapped.Remove(key); - public bool KeyExists(ReadOnlySpan key) => wrapped.KeyExists(key); + public bool KeyExists(ReadOnlySpan key) => _wrapped.KeyExists(key); - public void Flush(bool onlyWal) => wrapped.Flush(onlyWal); + public void Flush(bool onlyWal) => _wrapped.Flush(onlyWal); - public void Clear() => wrapped.Clear(); + public void Clear() => _wrapped.Clear(); - public IDbMeta.DbMetric GatherMetric() => wrapped.GatherMetric(); + public IDbMeta.DbMetric GatherMetric() => _wrapped.GatherMetric(); public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - => wrapped.Set(key, Compress(value), flags); + => _wrapped.Set(key, Compress(value), flags); public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - => Decompress(wrapped.Get(key, flags)); + => Decompress(_wrapped.Get(key, flags)); - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => - wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags); - public Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => - // Can't properly implement span for reading. As the decompressed span is different from the span - // from DB, it would crash on DangerouslyReleaseMemory. - Decompress(Get(key, flags)); + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) + { + _wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags); + } - public MemoryManager? GetOwnedMemory(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + public Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) { - byte[]? data = Decompress(Get(key, flags)); - return data is null or { Length: 0 } ? null : new ArrayMemoryManager(data); + // Can't properly implement span for reading. As the decompressed span is different from the span + // from DB, it would crash on DangerouslyReleaseMemory. + return Decompress(Get(key, flags)); } - public bool PreferWriteByArray => wrapped.PreferWriteByArray; + public bool PreferWriteByArray => _wrapped.PreferWriteByArray; public void Tune(ITunableDb.TuneType type) { - if (wrapped is ITunableDb tunable) + if (_wrapped is ITunableDb tunable) tunable.Tune(type); } } diff --git a/src/Nethermind/Nethermind.Db/DbNames.cs b/src/Nethermind/Nethermind.Db/DbNames.cs index 9dd16b3ddc01..2030be8e2bdb 100644 --- a/src/Nethermind/Nethermind.Db/DbNames.cs +++ b/src/Nethermind/Nethermind.Db/DbNames.cs @@ -20,6 +20,5 @@ public static class DbNames public const string DiscoveryNodes = "discoveryNodes"; public const string DiscoveryV5Nodes = "discoveryV5Nodes"; public const string PeersDb = "peers"; - public const string LogIndex = "logIndex"; } } diff --git a/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs b/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs index cac0113d670a..32979c963832 100755 --- a/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs +++ b/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using System.Collections.Generic; using System.Threading; using Nethermind.Core; @@ -72,17 +71,6 @@ public Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadF return value; } - public MemoryManager? GetOwnedMemory(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - { - MemoryManager? memoryManager = _currentDb.GetOwnedMemory(key, flags); - if (memoryManager is not null && _pruningContext?.DuplicateReads == true && (flags & ReadFlags.SkipDuplicateRead) == 0) - { - Duplicate(_pruningContext.CloningDb, key, memoryManager.GetSpan(), WriteFlags.None); - } - - return memoryManager; - } - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { _currentDb.Set(key, value, flags); // we are writing to the main DB @@ -155,8 +143,6 @@ public void Remove(ReadOnlySpan key) public bool KeyExists(ReadOnlySpan key) => _currentDb.KeyExists(key); - public void DangerousReleaseMemory(in ReadOnlySpan span) => _currentDb.DangerousReleaseMemory(span); - // inner DB's can be deleted in the future and // we cannot expose a DB that will potentially be later deleted public IDb Innermost => this; diff --git a/src/Nethermind/Nethermind.Db/IColumnsDb.cs b/src/Nethermind/Nethermind.Db/IColumnsDb.cs index e7ec61e8fe27..5ffddced6ab9 100644 --- a/src/Nethermind/Nethermind.Db/IColumnsDb.cs +++ b/src/Nethermind/Nethermind.Db/IColumnsDb.cs @@ -19,7 +19,6 @@ public interface IColumnsDb : IDbMeta, IDisposable public interface IColumnsWriteBatch : IDisposable { IWriteBatch GetColumnBatch(TKey key); - void Clear(); } diff --git a/src/Nethermind/Nethermind.Db/IMergeOperator.cs b/src/Nethermind/Nethermind.Db/IMergeOperator.cs index 3a2367636fd9..7df2946bd71f 100644 --- a/src/Nethermind/Nethermind.Db/IMergeOperator.cs +++ b/src/Nethermind/Nethermind.Db/IMergeOperator.cs @@ -3,16 +3,12 @@ using System; using Nethermind.Core.Collections; -using Nethermind.Db.LogIndex; namespace Nethermind.Db; public interface IMergeOperator { string Name { get; } - LogIndexUpdateStats Stats { get; } - LogIndexUpdateStats GetAndResetStats(); - ArrayPoolList? FullMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator); ArrayPoolList? PartialMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator); } diff --git a/src/Nethermind/Nethermind.Db/InMemoryColumnBatch.cs b/src/Nethermind/Nethermind.Db/InMemoryColumnBatch.cs index 45575158a8d0..9cff6e2fa957 100644 --- a/src/Nethermind/Nethermind.Db/InMemoryColumnBatch.cs +++ b/src/Nethermind/Nethermind.Db/InMemoryColumnBatch.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using Nethermind.Core; -using Nethermind.Core.Collections; namespace Nethermind.Db { @@ -24,14 +23,6 @@ public IWriteBatch GetColumnBatch(TKey key) return writeBatch; } - public void Clear() - { - foreach (IWriteBatch batch in _underlyingBatch) - { - batch.Clear(); - } - } - public void Dispose() { foreach (IWriteBatch batch in _underlyingBatch) diff --git a/src/Nethermind/Nethermind.Db/LogIndex/AverageStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/AverageStats.cs deleted file mode 100644 index ba679987db6e..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/AverageStats.cs +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading; - -namespace Nethermind.Db.LogIndex; - -/// -/// Aggregates average of multiple incrementally-added values. -/// -public class AverageStats -{ - private long _total; - private int _count; - - public void Include(long value) - { - Interlocked.Add(ref _total, value); - Interlocked.Increment(ref _count); - } - - public double Average => _count == 0 ? 0 : (double)_total / _count; - - public override string ToString() => $"{Average:F2} ({_count:N0})"; - - public void Combine(AverageStats stats) - { - _total += stats._total; - _count += stats._count; - } -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/BlockReceipts.cs b/src/Nethermind/Nethermind.Db/LogIndex/BlockReceipts.cs deleted file mode 100644 index 332c17e88ea2..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/BlockReceipts.cs +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Core; - -namespace Nethermind.Db.LogIndex; - -public readonly record struct BlockReceipts(int BlockNumber, TxReceipt[] Receipts); diff --git a/src/Nethermind/Nethermind.Db/LogIndex/CompactingStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/CompactingStats.cs deleted file mode 100644 index 2a2b73718dcc..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/CompactingStats.cs +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Db.LogIndex; - -public class CompactingStats -{ - public ExecTimeStats Total { get; set; } = new(); - - public void Combine(CompactingStats other) - { - Total.Combine(other.Total); - } -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/Compactor.cs b/src/Nethermind/Nethermind.Db/LogIndex/Compactor.cs deleted file mode 100644 index 03561369a2dc..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/Compactor.cs +++ /dev/null @@ -1,203 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; -using Nethermind.Logging; - -[assembly: InternalsVisibleTo("Nethermind.Db.Test")] -namespace Nethermind.Db.LogIndex; - -partial class LogIndexStorage -{ - /// - /// Periodically forces background log index compaction for every N added blocks. - /// - internal class Compactor : ICompactor - { - private int? _lastAtMin; - private int? _lastAtMax; - - private CompactingStats _stats = new(); - private readonly ILogIndexStorage _storage; - private readonly IDbMeta _rootDb; - private readonly ILogger _logger; - private readonly int _compactionDistance; - - private readonly CancellationTokenSource _cts = new(); - - /// - /// Bounded(1) compaction work queue consumed by .
- /// null — fire-and-forget compaction enqueued by ;
- /// not null — caller-awaitable compaction enqueued by . - ///
- private readonly Channel _channel = Channel.CreateBounded(1); - - private volatile TaskCompletionSource? _pendingForcedCompaction; - private readonly Task _compactionTask; - - public Compactor(ILogIndexStorage storage, IDbMeta rootDb, ILogger logger, int compactionDistance) - { - _storage = storage; - _rootDb = rootDb; - _logger = logger; - - if (compactionDistance < 1) throw new ArgumentException("Compaction distance must be a positive value.", nameof(compactionDistance)); - _compactionDistance = compactionDistance; - - _lastAtMin = storage.MinBlockNumber; - _lastAtMax = storage.MaxBlockNumber; - - _compactionTask = DoCompactAsync(); - } - - public CompactingStats GetAndResetStats() => Interlocked.Exchange(ref _stats, new()); - - // Not thread-safe - public bool TryEnqueue() - { - if (_cts.IsCancellationRequested) - return false; - - _lastAtMin ??= _storage.MinBlockNumber; - _lastAtMax ??= _storage.MaxBlockNumber; - - var uncompacted = 0; - if (_storage.MinBlockNumber is { } storageMin && storageMin < _lastAtMin) - uncompacted += _lastAtMin.Value - storageMin; - if (_storage.MaxBlockNumber is { } storageMax && storageMax > _lastAtMax) - uncompacted += storageMax - _lastAtMax.Value; - - if (uncompacted < _compactionDistance) - return false; - - if (!_channel.Writer.TryWrite(null)) - return false; - - _lastAtMin = _storage.MinBlockNumber; - _lastAtMax = _storage.MaxBlockNumber; - return true; - } - - public async Task StopAsync() - { - await _cts.CancelAsync(); - _channel.Writer.TryComplete(); - await _compactionTask; - } - - public async Task ForceAsync() - { - // Coalesce concurrent calls — all callers share a single compaction - TaskCompletionSource tcs = new(TaskCreationOptions.RunContinuationsAsynchronously); - TaskCompletionSource? existing = Interlocked.CompareExchange(ref _pendingForcedCompaction, tcs, null); - - if (existing is not null) - { - await existing.Task; - return _stats; - } - - try - { - await _channel.Writer.WriteAsync(tcs, _cts.Token); - } - catch (Exception ex) - { - Interlocked.CompareExchange(ref _pendingForcedCompaction, null, tcs); - if (ex is OperationCanceledException) - tcs.TrySetCanceled(); - else - tcs.TrySetException(ex); - - throw; - } - - await tcs.Task; - return _stats; - } - - private async Task DoCompactAsync() - { - CancellationToken cancellation = _cts.Token; - try - { - await foreach (TaskCompletionSource? tcs in _channel.Reader.ReadAllAsync(cancellation)) - { - try - { - if (_logger.IsInfo) _logger.Info($"Log index: compaction started, DB size: {_storage.GetDbSize()}"); - - var timestamp = Stopwatch.GetTimestamp(); - _rootDb.Compact(); - - TimeSpan elapsed = Stopwatch.GetElapsedTime(timestamp); - _stats.Total.Include(elapsed); - - if (_logger.IsInfo) _logger.Info($"Log index: compaction ended in {elapsed}, DB size: {_storage.GetDbSize()}"); - - tcs?.TrySetResult(); - } - catch (OperationCanceledException) - { - tcs?.TrySetCanceled(); - } - catch (Exception ex) - { - tcs?.TrySetException(ex); - (_storage as LogIndexStorage)?.OnBackgroundError(ex); - - await _cts.CancelAsync(); - _channel.Writer.TryComplete(); - - break; - } - finally - { - if (tcs is not null) - Interlocked.CompareExchange(ref _pendingForcedCompaction, null, tcs); - } - } - } - catch (OperationCanceledException) - { - if (_logger.IsDebug) _logger.Debug("Log index: compaction loop canceled"); - } - finally - { - while (_channel.Reader.TryRead(out TaskCompletionSource? remaining) && remaining is not null) - { - remaining.TrySetCanceled(); - Interlocked.CompareExchange(ref _pendingForcedCompaction, null, remaining); - } - } - } - - public void Dispose() - { - _cts.Dispose(); - _channel.Writer.TryComplete(); - } - } - - private class NoOpCompactor : ICompactor - { - public CompactingStats GetAndResetStats() => new(); - public bool TryEnqueue() => false; - public Task StopAsync() => Task.CompletedTask; - public Task ForceAsync() => Task.FromResult(new CompactingStats()); - public void Dispose() { } - } - - internal interface ICompactor : IDisposable - { - CompactingStats GetAndResetStats(); - bool TryEnqueue(); - Task StopAsync(); - Task ForceAsync(); - } -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/CompressionAlgorithm.cs b/src/Nethermind/Nethermind.Db/LogIndex/CompressionAlgorithm.cs deleted file mode 100644 index 3c0a3b83280b..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/CompressionAlgorithm.cs +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using static Nethermind.TurboPForBindings.TurboPFor; - -namespace Nethermind.Db.LogIndex; - -partial class LogIndexStorage -{ - /// - /// Represents compression algorithm to be used by log index. - /// - public class CompressionAlgorithm( - string name, - CompressionAlgorithm.CompressFunc compressionFunc, - CompressionAlgorithm.DecompressFunc decompressionFunc - ) - { - public delegate nuint CompressFunc(ReadOnlySpan @in, nuint n, Span @out); - public delegate nuint DecompressFunc(ReadOnlySpan @in, nuint n, Span @out); - - private static readonly Dictionary SupportedMap = new(); - - public static IReadOnlyDictionary Supported => SupportedMap; - - public static KeyValuePair Best => - SupportedMap.TryGetValue(nameof(p4nd1enc256v32), out CompressionAlgorithm p256) - ? KeyValuePair.Create(nameof(p4nd1enc256v32), p256) - : KeyValuePair.Create(nameof(p4nd1enc128v32), SupportedMap[nameof(p4nd1enc128v32)]); - - static CompressionAlgorithm() - { - SupportedMap.Add( - nameof(p4nd1enc128v32), - new(nameof(p4nd1enc128v32), p4nd1enc128v32, p4nd1dec128v32) - ); - - if (Supports256Blocks) - { - SupportedMap.Add( - nameof(p4nd1enc256v32), - new(nameof(p4nd1enc256v32), p4nd1enc256v32, p4nd1dec256v32) - ); - } - } - - public string Name => name; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public nuint Compress(ReadOnlySpan @in, nuint n, Span @out) => compressionFunc(@in, n, @out); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public nuint Decompress(ReadOnlySpan @in, nuint n, Span @out) => decompressionFunc(@in, n, @out); - } -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/Compressor.cs b/src/Nethermind/Nethermind.Db/LogIndex/Compressor.cs deleted file mode 100644 index 3d6bf0ae947c..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/Compressor.cs +++ /dev/null @@ -1,208 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Concurrent; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using System.Threading.Tasks.Dataflow; -using Nethermind.Core; -using Nethermind.Core.Extensions; - -namespace Nethermind.Db.LogIndex; - -partial class LogIndexStorage -{ - /// - /// Does background compression for keys with the number of blocks above the threshold. - /// - /// - /// Consumes "transient" keys with value being too big (see ) - /// from and performs compression in the background.
- /// Can utilize multiple threads, as per . - /// - /// - /// For each "transient" key in the queue performs the following: - /// - /// reads the latest (uncompressed) value from the database; - /// truncates potentially reorgable blocks from the sequence (as compressed values become immutable); - /// reverts sequence if needed - so the new value is always in ascending order; - /// compresses the sequence using specified TurboPFor ; - /// stores the compressed value at a new pair using {address-or-topic} || ({first-block-number-in-the-sequence} + 1) as a key; - /// queues truncation for the "transient" key via - to remove finalized blocks from the old sequence. - /// - /// - /// Last 2 operations are done via a single to maintain data consistency. - ///
- private class Compressor : ICompressor - { - private readonly int _minLengthToCompress; - - // Used instead of a channel to prevent duplicates - private readonly ConcurrentDictionary _compressQueue = new(Bytes.EqualityComparer); - private readonly ConcurrentDictionary.AlternateLookup> _compressQueueLookup; - private readonly LogIndexStorage _storage; - private readonly ActionBlock<(int?, byte[])> _processing; - private readonly ManualResetEventSlim _startEvent = new(false); - private readonly ManualResetEventSlim _queueEmptyEvent = new(true); - - private int _processingCount; - private PostMergeProcessingStats _stats = new(); - - public PostMergeProcessingStats GetAndResetStats() - { - _stats.QueueLength = _processing.InputCount; - return Interlocked.Exchange(ref _stats, new()); - } - - public Compressor(LogIndexStorage storage, int compressionDistance, int parallelism) - { - _compressQueueLookup = _compressQueue.GetAlternateLookup>(); - _storage = storage; - - _minLengthToCompress = compressionDistance * BlockNumberSize; - - if (parallelism < 1) throw new ArgumentException("Compression parallelism degree must be a positive value.", nameof(parallelism)); - _processing = new(x => CompressValue(x.Item1, x.Item2), new() { MaxDegreeOfParallelism = parallelism, BoundedCapacity = 10_000 }); - } - - public bool TryEnqueue(int? topicIndex, ReadOnlySpan dbKey, ReadOnlySpan dbValue) - { - if (dbValue.Length < _minLengthToCompress) - return false; - - if (_compressQueueLookup.TryGetValue(dbKey, out _)) - return false; - - byte[] dbKeyArr = dbKey.ToArray(); - if (!_compressQueue.TryAdd(dbKeyArr, true)) - return false; - - if (_processing.Post((topicIndex, dbKeyArr))) - return true; - - _compressQueue.TryRemove(dbKeyArr, out _); - return false; - } - - public async Task EnqueueAsync(int? topicIndex, byte[] dbKey) - { - await _processing.SendAsync((topicIndex, dbKey)); - _queueEmptyEvent.Reset(); - } - - public Task WaitUntilEmptyAsync(TimeSpan waitTime, CancellationToken cancellationToken) => - _queueEmptyEvent.WaitHandle.WaitOneAsync(waitTime, cancellationToken); - - private void CompressValue(int? topicIndex, byte[] dbKey) - { - if (_storage.HasBackgroundError) - return; - - Interlocked.Increment(ref _processingCount); - - try - { - _startEvent.Wait(); - - if (_storage.HasBackgroundError) - return; - - long execTimestamp = Stopwatch.GetTimestamp(); - IDb db = _storage.GetDb(topicIndex); - - long timestamp = Stopwatch.GetTimestamp(); - Span dbValue = db.Get(dbKey); - _stats.DBReading.Include(Stopwatch.GetElapsedTime(timestamp)); - - // Do not compress blocks that can be reorged, as compressed data is immutable - if (!UseBackwardSyncFor(dbKey)) - dbValue = _storage.RemoveReorgableBlocks(dbValue); - - if (dbValue.Length < _minLengthToCompress) - return; - - int truncateBlock = ReadLastBlockNumber(dbValue); - - ReverseBlocksIfNeeded(dbValue); - - int postfixBlock = ReadBlockNumber(dbValue); - - ReadOnlySpan key = ExtractKey(dbKey); - Span dbKeyComp = stackalloc byte[key.Length + BlockNumberSize]; - key.CopyTo(dbKeyComp); - WriteKeyBlockNumber(dbKeyComp[key.Length..], postfixBlock); - - timestamp = Stopwatch.GetTimestamp(); - dbValue = _storage.CompressDbValue(dbKey, dbValue); - _stats.CompressingValue.Include(Stopwatch.GetElapsedTime(timestamp)); - - // Put compressed value at a new key and clear the uncompressed one - timestamp = Stopwatch.GetTimestamp(); - using (IWriteBatch batch = db.StartWriteBatch()) - { - Span truncateOp = MergeOps.Create(MergeOp.Truncate, truncateBlock, stackalloc byte[MergeOps.Size]); - batch.PutSpan(dbKeyComp, dbValue); - batch.Merge(dbKey, truncateOp); - } - - _stats.DBSaving.Include(Stopwatch.GetElapsedTime(timestamp)); - - Interlocked.Increment(ref topicIndex is null ? ref _stats.CompressedAddressKeys : ref _stats.CompressedTopicKeys); - _stats.Total.Include(Stopwatch.GetElapsedTime(execTimestamp)); - } - catch (Exception ex) - { - _storage.OnBackgroundError(ex); - } - finally - { - _compressQueue.TryRemove(dbKey, out _); - - int processingCount = Interlocked.Decrement(ref _processingCount); - - if (_processing.InputCount == 0 && processingCount == 0) - _queueEmptyEvent.Set(); - } - } - - public void Start() => _startEvent.Set(); - - public Task StopAsync() - { - _processing.Complete(); - return _processing.Completion; // Wait for the compression queue to finish - } - - public void Dispose() - { - _startEvent.Dispose(); - _queueEmptyEvent.Dispose(); - } - } - - public sealed class NoOpCompressor : ICompressor - { - private PostMergeProcessingStats Stats { get; } = new(); - public PostMergeProcessingStats GetAndResetStats() => Stats; - public bool TryEnqueue(int? topicIndex, ReadOnlySpan dbKey, ReadOnlySpan dbValue) => false; - public Task EnqueueAsync(int? topicIndex, byte[] dbKey) => Task.CompletedTask; - public Task WaitUntilEmptyAsync(TimeSpan waitTime, CancellationToken cancellationToken) => Task.CompletedTask; - public void Start() { } - public Task StopAsync() => Task.CompletedTask; - public void Dispose() { } - } - - public interface ICompressor : IDisposable - { - PostMergeProcessingStats GetAndResetStats(); - - bool TryEnqueue(int? topicIndex, ReadOnlySpan dbKey, ReadOnlySpan dbValue); - Task EnqueueAsync(int? topicIndex, byte[] dbKey); - Task WaitUntilEmptyAsync(TimeSpan waitTime = default, CancellationToken cancellationToken = default); - - void Start(); - Task StopAsync(); - } -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/DisabledLogIndexStorage.cs b/src/Nethermind/Nethermind.Db/LogIndex/DisabledLogIndexStorage.cs deleted file mode 100644 index 9c483a550533..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/DisabledLogIndexStorage.cs +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Nethermind.Core; -using Nethermind.Core.Crypto; - -namespace Nethermind.Db.LogIndex; - -public sealed class DisabledLogIndexStorage : ILogIndexStorage -{ - public bool Enabled => false; - - public string GetDbSize() => "0 B"; - - public int? MaxBlockNumber => null; - public int? MinBlockNumber => null; - - public IEnumerator GetEnumerator(Address address, int from, int to) => - throw new NotSupportedException(); - - public IEnumerator GetEnumerator(int topicIndex, Hash256 topic, int from, int to) => - throw new NotSupportedException(); - - public LogIndexAggregate Aggregate(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats = null) => - throw new NotSupportedException(); - - public Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null) => - throw new NotSupportedException(); - - public Task RemoveReorgedAsync(BlockReceipts block) => - throw new NotSupportedException(); - - public ValueTask DisposeAsync() => ValueTask.CompletedTask; - public Task StopAsync() => Task.CompletedTask; -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/ExecTimeStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/ExecTimeStats.cs deleted file mode 100644 index 19c00334ee3e..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/ExecTimeStats.cs +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Threading; - -namespace Nethermind.Db.LogIndex; - -/// -/// Aggregates average and total execution time of multiple executions of the same operation. -/// -public class ExecTimeStats -{ - private long _totalTicks; - private int _count; - - public void Include(TimeSpan elapsed) - { - Interlocked.Add(ref _totalTicks, elapsed.Ticks); - Interlocked.Increment(ref _count); - } - - public TimeSpan Total => TimeSpan.FromTicks(_totalTicks); - public TimeSpan Average => _count == 0 ? TimeSpan.Zero : TimeSpan.FromTicks((long)((double)_totalTicks / _count)); - - private string Format(TimeSpan value) => value switch - { - { TotalDays: >= 1 } x => $"{x.TotalDays:F2}d", - { TotalHours: >= 1 } x => $"{x.TotalHours:F2}h", - { TotalMinutes: >= 1 } x => $"{x.TotalMinutes:F2}m", - { TotalSeconds: >= 1 } x => $"{x.TotalSeconds:F2}s", - { TotalMilliseconds: >= 1 } x => $"{x.TotalMilliseconds:F1}ms", - { TotalMicroseconds: >= 1 } x => $"{x.TotalMicroseconds:F1}μs", - var x => $"{x.TotalNanoseconds:F1}ns" - }; - - public override string ToString() => $"{Format(Average)} ({_count:N0}) [{Format(Total)}]"; - - public void Combine(ExecTimeStats stats) - { - _totalTicks += stats._totalTicks; - _count += stats._count; - } -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexConfig.cs b/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexConfig.cs deleted file mode 100644 index 53cb3b45148c..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexConfig.cs +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Config; -using Nethermind.TurboPForBindings; - -namespace Nethermind.Db.LogIndex; - -public interface ILogIndexConfig : IConfig -{ - [ConfigItem( - Description = "Whether log index should be enabled.", - DefaultValue = "false" - )] - public bool Enabled { get; set; } - - [ConfigItem( - Description = "Log index is reset on startup if enabled.", - DefaultValue = "false" - )] - public bool Reset { get; set; } - - [ConfigItem( - Description = "Max allowed reorg depth for the index.", - DefaultValue = "64", - HiddenFromDocs = true - )] - public int? MaxReorgDepth { get; set; } - - [ConfigItem( - Description = "Maximum number of blocks with receipts to add to index per iteration.", - DefaultValue = "256", - HiddenFromDocs = true - )] - public int MaxBatchSize { get; set; } - - [ConfigItem( - Description = "Maximum number of batches to queue for aggregation.", - DefaultValue = "16", - HiddenFromDocs = true - )] - public int MaxAggregationQueueSize { get; set; } - - [ConfigItem( - Description = "Maximum number of aggregated batches to queue for inclusion to the index.", - DefaultValue = "16", - HiddenFromDocs = true - )] - public int MaxSavingQueueSize { get; set; } - - [ConfigItem( - Description = "Maximum degree of parallelism for fetching receipts.", - DefaultValue = "Max(ProcessorCount / 2, 1)", - HiddenFromDocs = true - )] - public int MaxReceiptsParallelism { get; set; } - - [ConfigItem( - Description = "Maximum degree of parallelism for aggregating batches.", - DefaultValue = "Max(ProcessorCount / 2, 1)", - HiddenFromDocs = true - )] - public int MaxAggregationParallelism { get; set; } - - [ConfigItem( - Description = "Maximum degree of parallelism for compressing overgrown key values.", - DefaultValue = "Max(ProcessorCount / 2, 1)", - HiddenFromDocs = true - )] - public int MaxCompressionParallelism { get; set; } - - [ConfigItem( - Description = "Minimum number of blocks under a single key to compress.", - DefaultValue = "128", - HiddenFromDocs = true - )] - public int CompressionDistance { get; set; } - - [ConfigItem( - Description = "Number of newly added blocks after which to run DB compaction.", - DefaultValue = "262,144", - HiddenFromDocs = true - )] - public int CompactionDistance { get; set; } - - [ConfigItem( - Description = "Compression algorithm to use for block numbers.", - DefaultValue = nameof(TurboPFor.p4nd1enc256v32) + " if supported, otherwise " + nameof(TurboPFor.p4nd1enc128v32), - HiddenFromDocs = true - )] - string? CompressionAlgorithm { get; set; } - - [ConfigItem( - Description = "Whether to show detailed stats in progress logs.", - DefaultValue = "false", - HiddenFromDocs = true - )] - bool DetailedLogs { get; set; } - - [ConfigItem( - Description = "Whether to verify that eth_getLogs response generated using index matches one generated without.", - DefaultValue = "false", - HiddenFromDocs = true - )] - bool VerifyRpcResponse { get; set; } -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexStorage.cs b/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexStorage.cs deleted file mode 100644 index af084f85c310..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexStorage.cs +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Core.ServiceStopper; - -namespace Nethermind.Db.LogIndex; - -public interface ILogIndexStorage : IAsyncDisposable, IStoppableService -{ - bool Enabled { get; } - - /// - /// Max block number added to the index. - /// - int? MaxBlockNumber { get; } - - /// - /// Min block number added to the index. - /// - int? MinBlockNumber { get; } - - /// - /// Gets enumerator of block numbers between and - /// where given has occurred. - /// - IEnumerator GetEnumerator(Address address, int from, int to); - - /// - /// Gets enumerator of block numbers between and - /// where given has occurred at the given . - /// - IEnumerator GetEnumerator(int topicIndex, Hash256 topic, int from, int to); - - /// - /// Aggregates receipts from the into in-memory . - /// - LogIndexAggregate Aggregate(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats = null); - - /// - /// Adds receipts from the to the index. - /// - Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null); - - /// - /// Removes reorged from the index. - /// This must be called for each reorged block in a sequential ascending order. - /// - Task RemoveReorgedAsync(BlockReceipts block); - - string GetDbSize(); -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexAggregate.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexAggregate.cs deleted file mode 100644 index bd0f40280484..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexAggregate.cs +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using System.Linq; -using Nethermind.Core; -using Nethermind.Core.Crypto; - -namespace Nethermind.Db.LogIndex; - -/// -/// Set of in-memory dictionaries mapping each address/topic to a sequence of block numbers -/// from the - range. -/// -public struct LogIndexAggregate(int firstBlockNum, int lastBlockNum) -{ - private Dictionary>? _address; - private Dictionary>[]? _topic; - - public int FirstBlockNum { get; } = firstBlockNum; - public int LastBlockNum { get; } = lastBlockNum; - - public Dictionary> Address => _address ??= new(); - - public Dictionary>[] Topic => _topic ??= Enumerable.Range(0, LogIndexStorage.MaxTopics) - .Select(static _ => new Dictionary>()) - .ToArray(); - - public bool IsEmpty => (_address is null || _address.Count == 0) && (_topic is null || _topic[0].Count == 0); - public int TopicCount => _topic is { Length: > 0 } ? _topic.Sum(static t => t.Count) : 0; - - public LogIndexAggregate(IReadOnlyList batch) : this(batch[0].BlockNumber, batch[^1].BlockNumber) { } -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexColumns.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexColumns.cs deleted file mode 100644 index 7492a1aca278..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexColumns.cs +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Db.LogIndex; - -public enum LogIndexColumns -{ - Meta, - Addresses, - Topics0, - Topics1, - Topics2, - Topics3 -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexConfig.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexConfig.cs deleted file mode 100644 index 5208f5ba2003..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexConfig.cs +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Config; - -namespace Nethermind.Db.LogIndex; - -[ConfigCategory(Description = "Configuration of the log index behaviour.")] -public class LogIndexConfig : ILogIndexConfig -{ - public bool Enabled { get; set; } = false; - public bool Reset { get; set; } = false; - - // set from PruningConfig via decorator - public int? MaxReorgDepth { get; set; } - - public int MaxBatchSize { get; set; } = 256; - public int MaxAggregationQueueSize { get; set; } = 16; - public int MaxSavingQueueSize { get; set; } = 16; - - public int MaxReceiptsParallelism { get; set; } = Math.Max(Environment.ProcessorCount / 2, 1); - public int MaxAggregationParallelism { get; set; } = Math.Max(Environment.ProcessorCount / 2, 1); - public int MaxCompressionParallelism { get; set; } = Math.Max(Environment.ProcessorCount / 2, 1); - - public int CompressionDistance { get; set; } = 128; - public int CompactionDistance { get; set; } = 262_144; - - public string? CompressionAlgorithm { get; set; } = LogIndexStorage.CompressionAlgorithm.Best.Key; - - public bool DetailedLogs { get; set; } = false; - public bool VerifyRpcResponse { get; set; } = false; -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexEnumerator.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexEnumerator.cs deleted file mode 100644 index 1eb40242a6ca..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexEnumerator.cs +++ /dev/null @@ -1,153 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections; -using System.Collections.Generic; -using Nethermind.Core; -using Nethermind.Core.Collections; - -namespace Nethermind.Db.LogIndex; - -public partial class LogIndexStorage -{ - // TODO: pre-fetch next value? - /// - /// Enumerates block numbers from for the given key, - /// within the specified from/to range. - /// - public sealed class LogIndexEnumerator : IEnumerator - { - private const int CompletedIndex = int.MinValue; - - private readonly CompressionAlgorithm _compressionAlgorithm; - private readonly byte[] _key; - private readonly int _from; - private readonly int _to; - private readonly ISortedView _view; - - private ArrayPoolList? _value; - private int _index; - - public LogIndexEnumerator(ISortedKeyValueStore db, CompressionAlgorithm compressionAlgorithm, byte[] key, int from, int to) - { - if (from < 0) from = 0; - if (to < from) throw new ArgumentException("To must be greater or equal to from.", nameof(to)); - - _key = key; - (_from, _to) = (from, to); - _compressionAlgorithm = compressionAlgorithm; - - ReadOnlySpan fromKey = CreateDbKey(_key, Postfix.BackwardMerge, stackalloc byte[MaxDbKeyLength]); - ReadOnlySpan toKey = CreateDbKey(_key, Postfix.UpperBound, stackalloc byte[MaxDbKeyLength]); - _view = db.GetViewBetween(fromKey, toKey); - } - - private bool IsWithinRange() - { - int current = Current; - return current >= _from && current <= _to; - } - - public bool MoveNext() => _index != CompletedIndex && (_value is null ? TryStart() : TryMove()); - - private bool TryStart() - { - if (TryStartView()) - { - SetValue(); - _index = FindFromIndex(); - } - else // End immediately - { - _index = CompletedIndex; - return false; - } - - // Shift the view until we can start at `from` - while (Current < _from && _view.MoveNext()) - { - SetValue(); - _index = FindFromIndex(); - } - - // Check if the end of the range is reached - if (!IsWithinRange()) - { - _index = CompletedIndex; - return false; - } - - return true; - } - - private bool TryMove() - { - _index++; - - // Shift the view until we can continue - while (_index >= _value!.Count && _view.MoveNext()) - { - SetValue(); - _index = 0; - } - - // Check if the end of the range is reached - if (!IsWithinRange()) - { - _index = CompletedIndex; - return false; - } - - return true; - } - - private bool TryStartView() - { - ReadOnlySpan startKey = CreateDbKey(_key, _from, stackalloc byte[MaxDbKeyLength]); - - // need to start either just before the startKey - // or at the beginning of a view otherwise - return _view.StartBefore(startKey) || _view.MoveNext(); - } - - private void SetValue() - { - _value?.Dispose(); - - ReadOnlySpan viewValue = _view.CurrentValue; - - if (IsCompressed(viewValue, out var length)) - { - // +1 fixes TurboPFor reading outside of array bounds - _value = new(capacity: length + 1, count: length); - DecompressDbValue(_compressionAlgorithm, viewValue, _value.AsSpan()); - } - else - { - length = viewValue.Length / BlockNumberSize; - _value = new(capacity: length, count: length); - ReadBlockNumbers(viewValue, _value.AsSpan()); - } - - ReverseBlocksIfNeeded(_value.AsSpan()); - } - - private int FindFromIndex() - { - int index = BinarySearch(_value!.AsSpan(), _from); - return index >= 0 ? index : ~index; - } - - public void Reset() => throw new NotSupportedException($"{nameof(LogIndexEnumerator)} can not be reset."); - - public int Current => _value is not null && _index >= 0 && _index < _value.Count ? _value[_index] : -1; - object? IEnumerator.Current => Current; - - public void Dispose() - { - _view.Dispose(); - _value?.Dispose(); - } - } -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStateException.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStateException.cs deleted file mode 100644 index 113ebfb045b7..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStateException.cs +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; - -namespace Nethermind.Db.LogIndex; - -public class LogIndexStateException(string message, ReadOnlySpan key = default) : Exception(message) -{ - public byte[]? Key { get; } = key.Length == 0 ? null : key.ToArray(); - - public override string Message => Key is null - ? base.Message - : $"{base.Message} (Key: {Convert.ToHexString(Key)})"; -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStorage.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStorage.cs deleted file mode 100644 index fc8e6a8f73ff..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStorage.cs +++ /dev/null @@ -1,1006 +0,0 @@ -using System; -using System.Buffers; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Diagnostics; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Runtime.ExceptionServices; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Nethermind.Core; -using Nethermind.Core.Collections; -using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; -using Nethermind.Logging; - -namespace Nethermind.Db.LogIndex -{ - // TODO: use uint instead of int for block number? - /// - /// Database for log index, mapping addresses/topics to a set of blocks they occur in. - /// - /// - /// - /// - /// Uses 6 column families (see ): - /// - /// 1 for metadata (for now stores only the earliest and the latest added block numbers); - /// 1 for address mappings; - /// 4 for topic mappings (separate 1 for each topic position). - /// - /// - /// - /// - /// Each unique filter (topic/address) has a set of DB mappings filter -> block numbers: - /// - /// - /// 1 for newly coming numbers from backward sync;
- /// key here is formed as filter || ;
- /// value is a sequence of concatenated block numbers in strictly descending order using little-endian encoding; - ///
- /// - /// 1 for newly coming numbers from forward sync;
- /// key is filter || ;
- /// value is a sequence of concatenated block numbers in strictly ascending order using little-endian encoding; - ///
- /// - /// any number of "finalized" mappings, storing block numbers in strictly ascending order, compressed via TurboPFor;
- /// key is filter || ({first-block-number-in-the-sequence} + 1) with number being encoded in big-endian (for correct RocksDB sorting);
- /// value is a sequence of block numbers compressed via TurboPFor (see ). - ///
- ///
- /// Keys (1) and (2) are called "transient", as their content can change frequently, - /// while keys from (3) - "finalized", as their data is immutable.
- /// Block sequences are not-intersecting and following a strict order, such that:
- /// - /// max({backward-sync-numbers}) < any({finalized-numbers}) < min({forward-sync-numbers}) - /// - ///
- /// - /// - /// New blocks are added in batches in a strictly ascending or descending (backward sync) order without gaps.
- /// Process is separated into 2 steps to improve parallelization: - /// - /// in-memory dictionary is aggregated, mapping filter to a sequence of block numbers (see ); - /// each dictionary pair is saved to DB via call (see ). - /// - /// Whole block batch is added in a single cross-family .
- /// Merging always happens to a "transient" (backward- or forward-sync) key, depending on the direction (passed as a parameter during aggregation). - /// After that, is responsible for concatenating sequences into a single uncompressed DB value, - /// and is forming "finalized" compressed values when "transient" sequences grow too big. - ///
- /// - /// - /// Fetching block numbers for the given filters (see ) - /// is done by iterating keys via (with the provided filter as a prefix), - /// decomposing DB values back into block number sequences, and returning obtained numbers in ascending order. - /// Check for details. - /// - ///
- public partial class LogIndexStorage : ILogIndexStorage - { - private static class SpecialKey - { - public static readonly byte[] Version = "ver"u8.ToArray(); - public static readonly byte[] MinBlockNum = "min"u8.ToArray(); - public static readonly byte[] MaxBlockNum = "max"u8.ToArray(); - public static readonly byte[] CompressionAlgo = "alg"u8.ToArray(); - } - - public static class Postfix - { - // Any ordered prefix seeking will start on it - public static readonly byte[] BackwardMerge = Enumerable.Repeat((byte)0, BlockNumberSize).ToArray(); - - // Any ordered prefix seeking will end on it - public static readonly byte[] ForwardMerge = Enumerable.Repeat(byte.MaxValue, BlockNumberSize).ToArray(); - - // Exclusive upper bound for iterator seek, so that ForwardMerge will be the last key - public static readonly byte[] UpperBound = Enumerable.Repeat(byte.MaxValue, BlockNumberSize).Concat([byte.MinValue]).ToArray(); - } - - [InlineArray(MaxTopics)] - private struct TopicBatches - { - private IWriteBatch _element; - } - - [InlineArray(MaxTopics + 1)] - private struct AllMergeOperators - { - private IMergeOperator _element; - } - - private ref struct DbBatches : IDisposable - { - private bool _completed; - - private readonly IColumnsWriteBatch _batch; - public IWriteBatch Meta { get; } - public IWriteBatch Address { get; } - public readonly TopicBatches Topics; - - public DbBatches(IColumnsDb rootDb) - { - _batch = rootDb.StartWriteBatch(); - - Meta = _batch.GetColumnBatch(LogIndexColumns.Meta); - Address = _batch.GetColumnBatch(LogIndexColumns.Addresses); - for (var topicIndex = 0; topicIndex < MaxTopics; topicIndex++) - Topics[topicIndex] = _batch.GetColumnBatch(GetColumn(topicIndex)); - } - - // Require explicit Commit call instead of committing on Dispose - public void Commit() - { - if (_completed) return; - _completed = true; - - _batch.Dispose(); - } - - public void Dispose() - { - if (_completed) return; - _completed = true; - - _batch.Clear(); - _batch.Dispose(); - } - } - - private static readonly byte[] VersionBytes = [1]; - - public const int MaxTopics = 4; - - public bool Enabled { get; } - - public const int BlockNumberSize = sizeof(int); - private const int MaxKeyLength = Hash256.Size + 1; // Math.Max(Address.Size, Hash256.Size) - private const int MaxDbKeyLength = MaxKeyLength + BlockNumberSize; - - private static readonly ArrayPool Pool = ArrayPool.Shared; - - private readonly IColumnsDb _rootDb; - private readonly IDb _metaDb; - private readonly IDb _addressDb; - private readonly IDb[] _topicDbs; - - private IEnumerable DBColumns - { - get - { - yield return _metaDb; - yield return _addressDb; - - foreach (IDb topicDb in _topicDbs) - yield return topicDb; - } - } - - private readonly ILogger _logger; - - private readonly int _maxReorgDepth; - - private readonly AllMergeOperators _mergeOperators; - private readonly ICompressor _compressor; - private readonly ICompactor _compactor; - private readonly CompressionAlgorithm _compressionAlgorithm; - - private readonly Lock _rangeInitLock = new(); - - private int? _maxBlock; - private int? _minBlock; - - public int? MaxBlockNumber => _maxBlock; - public int? MinBlockNumber => _minBlock; - - private Exception? _lastBackgroundError; - public bool HasBackgroundError => _lastBackgroundError is not null; - - /// - /// Whether a first batch was already added. - /// - private bool FirstBlockAdded => _minBlock is not null || _maxBlock is not null; - - /// - /// Guarantees / initialization won't be run concurrently. - /// - private readonly SemaphoreSlim _initSemaphore = new(1, 1); - - /// - /// Used for blocking concurrent executions and - /// ensuring the current iteration is completed before stopping/disposing. - /// - private readonly SemaphoreSlim _forwardWriteSemaphore = new(1, 1); - private readonly SemaphoreSlim _backwardWriteSemaphore = new(1, 1); - - private bool _stopped; - private bool _disposed; - - public LogIndexStorage(IDbFactory dbFactory, ILogManager logManager, ILogIndexConfig config) - { - try - { - Enabled = config.Enabled; - - _maxReorgDepth = config.MaxReorgDepth!.Value; - - _logger = logManager.GetClassLogger(); - - _compressor = config.CompressionDistance > 0 - ? new Compressor(this, config.CompressionDistance, config.MaxCompressionParallelism) - : new NoOpCompressor(); - - for (int i = -1; i < MaxTopics; i++) - _mergeOperators[i + 1] = new MergeOperator(this, _compressor, topicIndex: i < 0 ? null : i); - - _rootDb = CreateRootDb(dbFactory, config.Reset); - _metaDb = GetMetaDb(_rootDb); - _addressDb = _rootDb.GetColumnDb(LogIndexColumns.Addresses); - _topicDbs = Enumerable.Range(0, MaxTopics).Select(topicIndex => _rootDb.GetColumnDb(GetColumn(topicIndex))).ToArray(); - - _compactor = config.CompactionDistance > 0 - ? new Compactor(this, _rootDb, _logger, config.CompactionDistance) - : new NoOpCompactor(); - - _compressionAlgorithm = SelectCompressionAlgorithm(config.CompressionAlgorithm); - - (_minBlock, _maxBlock) = (LoadRangeBound(SpecialKey.MinBlockNum), LoadRangeBound(SpecialKey.MaxBlockNum)); - - if (Enabled) - _compressor.Start(); - } - catch // TODO: do not throw errors from constructor? - { - DisposeCore(); - throw; - } - } - - private IColumnsDb CreateRootDb(IDbFactory dbFactory, bool reset) - { - (IColumnsDb root, IDb meta) = CreateDb(); - - if (reset) - return ResetAndCreateNew(root, "Log index: resetting data per configuration..."); - - Span versionBytes = meta.GetSpan(SpecialKey.Version); - try - { - if (versionBytes.IsEmpty) // DB is empty - { - meta.Set(SpecialKey.Version, VersionBytes); - return root; - } - - return versionBytes.SequenceEqual(VersionBytes) - ? root - : ResetAndCreateNew(root, $"Log index: version is incorrect: {versionBytes[0]} <> {VersionBytes[0]}, resetting data..."); - } - finally - { - meta.DangerousReleaseMemory(versionBytes); - } - - IColumnsDb ResetAndCreateNew(IColumnsDb db, string message) - { - if (_logger.IsWarn) - _logger.Warn(message); - - db.Clear(); - - // `Clear` removes the DB folder, need to create a new instance - db.Dispose(); - (db, meta) = CreateDb(); - - meta.Set(SpecialKey.Version, VersionBytes); - return db; - } - - (IColumnsDb root, IDb meta) CreateDb() - { - IColumnsDb db = dbFactory.CreateColumnsDb(new("logIndexStorage", DbNames.LogIndex) - { - ColumnsMergeOperators = Enumerable.Range(-1, MaxTopics + 1).ToDictionary( - topicIndex => $"{GetColumn(topicIndex < 0 ? null : topicIndex)}", - topicIndex => _mergeOperators[topicIndex + 1] - ) - }); - - return (db, GetMetaDb(db)); - } - } - - private CompressionAlgorithm SelectCompressionAlgorithm(string? configAlgoName) - { - CompressionAlgorithm? configAlgo = null; - if (configAlgoName is not null && !CompressionAlgorithm.Supported.TryGetValue(configAlgoName, out configAlgo)) - { - throw new NotSupportedException( - $"Configured compression algorithm ({configAlgoName}) is not supported on this platform." - ); - } - - Span algoBytes = _metaDb.GetSpan(SpecialKey.CompressionAlgo); - string usedAlgoName; - try - { - if (algoBytes.IsEmpty) // DB is empty - { - KeyValuePair selected = configAlgo is not null - ? KeyValuePair.Create(configAlgoName, configAlgo) - : CompressionAlgorithm.Best; - - _metaDb.Set(SpecialKey.CompressionAlgo, Encoding.ASCII.GetBytes(selected.Key)); - return selected.Value; - } - - usedAlgoName = Encoding.ASCII.GetString(algoBytes); - } - finally - { - _metaDb.DangerousReleaseMemory(algoBytes); - } - - if (!CompressionAlgorithm.Supported.TryGetValue(usedAlgoName, out CompressionAlgorithm usedAlgo)) - { - throw new NotSupportedException( - $"Used compression algorithm ({usedAlgoName}) is not supported on this platform. " + - "Log index must be reset to use a different compression algorithm." - ); - } - - configAlgoName ??= usedAlgoName; - if (usedAlgoName != configAlgoName) - { - throw new NotSupportedException( - $"Used compression algorithm ({usedAlgoName}) is different from the one configured ({configAlgoName}). " + - "Log index must be reset to use a different compression algorithm." - ); - } - - return usedAlgo; - } - - private static void ForceMerge(IDb db) - { - // Fetching RocksDB key values forces it to merge corresponding parts - db.GetAllValues().ForEach(static _ => { }); - } - - public Task StopAsync() => StopAsync(acquireLock: true); - - private async Task StopAsync(bool acquireLock) - { - if (Interlocked.Exchange(ref _stopped, true)) - { - return; - } - - if (acquireLock) - { - await _forwardWriteSemaphore.WaitAsync(); - await _backwardWriteSemaphore.WaitAsync(); - } - - try - { - // Disposing RocksDB during any write operation will cause 0xC0000005, so stop them all - await Task.WhenAll( - _compactor.StopAsync(), - _compressor.StopAsync() - ); - - if (_logger.IsInfo) _logger.Info("Log index storage stopped"); - } - finally - { - if (acquireLock) - { - _forwardWriteSemaphore.Release(); - _backwardWriteSemaphore.Release(); - } - } - } - - private void ThrowIfStopped() - { - if (_stopped) - throw new InvalidOperationException("Log index storage is stopped."); - } - - private void OnBackgroundError(Exception error) - { - _lastBackgroundError = error; - - if (_logger.IsError) - _logger.Error($"Error in {typeof(TCaller).Name}", error); - } - - private void ThrowIfHasError() - { - if (_lastBackgroundError is { } error) - ExceptionDispatchInfo.Throw(error); - } - - async ValueTask IAsyncDisposable.DisposeAsync() - { - if (Interlocked.Exchange(ref _disposed, true)) - { - return; - } - - await _forwardWriteSemaphore.WaitAsync(); - await _backwardWriteSemaphore.WaitAsync(); - - await StopAsync(acquireLock: false); - - // No need to free semaphores now - DisposeCore(); - } - - private void DisposeCore() - { - _forwardWriteSemaphore.Dispose(); - _backwardWriteSemaphore.Dispose(); - _compressor?.Dispose(); - _compactor?.Dispose(); - DBColumns?.DisposeItems(); - _rootDb?.Dispose(); - } - - private int? LoadRangeBound(ReadOnlySpan key) - { - Span value = _metaDb.GetSpan(key); - try - { - return !value.IsEmpty ? ReadBlockNumber(value) : null; - } - finally - { - _metaDb.DangerousReleaseMemory(value); - } - } - - private void UpdateRange(int minBlock, int maxBlock, bool isBackwardSync) - { - if (!FirstBlockAdded) - { - using Lock.Scope _ = _rangeInitLock.EnterScope(); // May not be needed, but added for safety - (_minBlock, _maxBlock) = (minBlock, maxBlock); - return; - } - - // Update fields separately for each direction - // so that concurrent different direction sync won't overwrite each other - if (isBackwardSync) _minBlock = minBlock; - else _maxBlock = maxBlock; - } - - private static int SaveRangeBound(IWriteOnlyKeyValueStore dbBatch, byte[] key, int value) - { - Span buffer = stackalloc byte[BlockNumberSize]; - WriteBlockNumber(buffer, value); - dbBatch.PutSpan(key, buffer); - return value; - } - - private (int min, int max) SaveRange(DbBatches batches, int firstBlock, int lastBlock, bool isBackwardSync, bool isReorg = false) - { - int batchMin = Math.Min(firstBlock, lastBlock); - int batchMax = Math.Max(firstBlock, lastBlock); - - int min = _minBlock ?? SaveRangeBound(batches.Meta, SpecialKey.MinBlockNum, batchMin); - int max = _maxBlock ?? SaveRangeBound(batches.Meta, SpecialKey.MaxBlockNum, batchMax); - - if (isBackwardSync) - { - if (isReorg) - throw new ArgumentException("Backwards sync does not support reorgs."); - if (batchMin < _minBlock) - min = SaveRangeBound(batches.Meta, SpecialKey.MinBlockNum, batchMin); - } - else - { - if ((isReorg && batchMax < _maxBlock) || (!isReorg && batchMax > _maxBlock)) - max = SaveRangeBound(batches.Meta, SpecialKey.MaxBlockNum, batchMax); - } - - return (min, max); - } - - private int? GetLastReorgableBlockNumber() => _maxBlock - _maxReorgDepth; - - private static bool IsBlockNewer(int next, int? lastMin, int? lastMax, bool isBackwardSync) => isBackwardSync - ? lastMin is null || next < lastMin - : lastMax is null || next > lastMax; - - private bool IsBlockNewer(int next, bool isBackwardSync) => - IsBlockNewer(next, _minBlock, _maxBlock, isBackwardSync); - - public string GetDbSize() => _rootDb.GatherMetric().Size.SizeToString(useSi: true, addSpace: true); - - public IEnumerator GetEnumerator(Address address, int from, int to) => - GetEnumerator(null, address.Bytes, from, to); - - public IEnumerator GetEnumerator(int topicIndex, Hash256 topic, int from, int to) => - GetEnumerator(topicIndex, topic.BytesToArray(), from, to); - - public IEnumerator GetEnumerator(int? topicIndex, byte[] key, int from, int to) - { - IDb db = GetDb(topicIndex); - ISortedKeyValueStore? sortedDb = db as ISortedKeyValueStore - ?? throw new NotSupportedException($"{db.GetType().Name} DB does not support sorted lookups."); - - return new LogIndexEnumerator(sortedDb, _compressionAlgorithm, key, from, to); - } - - // TODO: discuss potential optimizations - public LogIndexAggregate Aggregate(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats) - { - ThrowIfStopped(); - ThrowIfHasError(); - - if ((!isBackwardSync && !IsSeqAsc(batch)) || (isBackwardSync && !IsSeqDesc(batch))) - throw new ArgumentException($"Unexpected blocks batch order: ({batch[0]} to {batch[^1]})."); - - if (!IsBlockNewer(batch[^1].BlockNumber, isBackwardSync)) - return new(batch); - - long timestamp = Stopwatch.GetTimestamp(); - - LogIndexAggregate aggregate = new(batch); - foreach ((int blockNumber, TxReceipt[] receipts) in batch) - { - if (!IsBlockNewer(blockNumber, isBackwardSync)) - continue; - - stats?.IncrementBlocks(); - stats?.IncrementTx(receipts.Length); - - foreach (TxReceipt receipt in receipts) - { - if (receipt.Logs == null) - continue; - - foreach (LogEntry log in receipt.Logs) - { - stats?.IncrementLogs(); - - List addressBlocks = aggregate.Address.GetOrAdd(log.Address, static _ => new(1)); - - if (addressBlocks.Count == 0 || addressBlocks[^1] != blockNumber) - addressBlocks.Add(blockNumber); - - int topicsLength = Math.Min(log.Topics.Length, MaxTopics); - for (byte topicIndex = 0; topicIndex < topicsLength; topicIndex++) - { - stats?.IncrementTopics(); - - List topicBlocks = aggregate.Topic[topicIndex].GetOrAdd(log.Topics[topicIndex], static _ => new(1)); - - if (topicBlocks.Count == 0 || topicBlocks[^1] != blockNumber) - topicBlocks.Add(blockNumber); - } - } - } - } - - stats?.KeysCount.Include(aggregate.Address.Count + aggregate.TopicCount); - stats?.Aggregating.Include(Stopwatch.GetElapsedTime(timestamp)); - - return aggregate; - } - - private async ValueTask LockRunAsync(SemaphoreSlim semaphore) - { - if (!await semaphore.WaitAsync(TimeSpan.Zero, CancellationToken.None)) - { - ThrowIfStopped(); - throw new InvalidOperationException($"{nameof(LogIndexStorage)} does not support concurrent invocations in the same direction."); - } - } - - public async Task RemoveReorgedAsync(BlockReceipts block) - { - ThrowIfStopped(); - ThrowIfHasError(); - - if (!FirstBlockAdded) - return; - - await LockRunAsync(_forwardWriteSemaphore); - - try - { - RemoveReorgedCore(block); - } - finally - { - _forwardWriteSemaphore.Release(); - } - } - - private void RemoveReorgedCore(BlockReceipts block) - { - const bool isBackwardSync = false; - - using DbBatches batches = new(_rootDb); - - Span keyBuffer = stackalloc byte[MaxDbKeyLength]; - Span dbValue = MergeOps.Create(MergeOp.Reorg, block.BlockNumber, stackalloc byte[MergeOps.Size]); - - foreach (TxReceipt receipt in block.Receipts) - { - foreach (LogEntry log in receipt.Logs ?? []) - { - ReadOnlySpan addressKey = CreateMergeDbKey(log.Address.Bytes, keyBuffer, isBackwardSync: false); - batches.Address.Merge(addressKey, dbValue); - - var topicsLength = Math.Min(log.Topics.Length, MaxTopics); - for (var topicIndex = 0; topicIndex < topicsLength; topicIndex++) - { - Hash256 topic = log.Topics[topicIndex]; - ReadOnlySpan topicKey = CreateMergeDbKey(topic.Bytes, keyBuffer, isBackwardSync: false); - batches.Topics[topicIndex].Merge(topicKey, dbValue); - } - } - } - - // Need to update the last block number so that new-receipts comparison won't fail when rewriting it - int blockNum = block.BlockNumber - 1; - - (int minBlock, int maxBlock) = SaveRange(batches, blockNum, blockNum, isBackwardSync, isReorg: true); - - batches.Commit(); - - // Postpone in-memory values update until batch is committed - UpdateRange(minBlock, maxBlock, isBackwardSync); - } - - // TODO: refactor compaction to explicitly compress full range for each involved key - public async Task CompactAsync(bool flush = false, int mergeIterations = 0, LogIndexUpdateStats? stats = null) - { - ThrowIfStopped(); - ThrowIfHasError(); - - if (_logger.IsInfo) - _logger.Info($"Log index forced compaction started, DB size: {GetDbSize()}"); - - var timestamp = Stopwatch.GetTimestamp(); - - if (flush) - DBColumns.ForEach(static db => db.Flush()); - - for (var i = 0; i < mergeIterations; i++) - { - Task[] tasks = DBColumns - .Select(static db => Task.Run(() => ForceMerge(db))) - .ToArray(); - - await Task.WhenAll(tasks); - await _compressor.WaitUntilEmptyAsync(TimeSpan.FromSeconds(30)); - } - - CompactingStats compactStats = await _compactor.ForceAsync(); - stats?.Compacting.Combine(compactStats); - - foreach (IMergeOperator mergeOperator in _mergeOperators) - stats?.Combine(mergeOperator.Stats); - - if (_logger.IsInfo) - _logger.Info($"Log index forced compaction finished in {Stopwatch.GetElapsedTime(timestamp)}, DB size: {GetDbSize()} {stats:d}"); - } - - public async Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null) - { - ThrowIfStopped(); - ThrowIfHasError(); - - long totalTimestamp = Stopwatch.GetTimestamp(); - - bool isBackwardSync = aggregate.LastBlockNum < aggregate.FirstBlockNum; - SemaphoreSlim semaphore = isBackwardSync ? _backwardWriteSemaphore : _forwardWriteSemaphore; - await LockRunAsync(semaphore); - - bool wasInitialized = FirstBlockAdded; - if (!wasInitialized) - await _initSemaphore.WaitAsync(); - - try - { - using DbBatches batches = new(_rootDb); - - // Add values to batches - long timestamp; - if (!aggregate.IsEmpty) - { - timestamp = Stopwatch.GetTimestamp(); - - // Add addresses - foreach ((Address address, List blocks) in aggregate.Address) - { - MergeBlockNumbers(batches.Address, address.Bytes, blocks, isBackwardSync, stats); - } - - // Add topics - for (var topicIndex = 0; topicIndex < aggregate.Topic.Length; topicIndex++) - { - Dictionary> topics = aggregate.Topic[topicIndex]; - - foreach ((Hash256 topic, List blocks) in topics) - { - MergeBlockNumbers(batches.Topics[topicIndex], topic.Bytes, blocks, isBackwardSync, stats); - } - } - - stats?.Merging.Include(Stopwatch.GetElapsedTime(timestamp)); - } - - timestamp = Stopwatch.GetTimestamp(); - (int addressRange, int topicRanges) = SaveRange(batches, aggregate.FirstBlockNum, aggregate.LastBlockNum, isBackwardSync); - stats?.UpdatingMeta.Include(Stopwatch.GetElapsedTime(timestamp)); - - // Submit batches - timestamp = Stopwatch.GetTimestamp(); - batches.Commit(); - stats?.CommittingBatch.Include(Stopwatch.GetElapsedTime(timestamp)); - - UpdateRange(addressRange, topicRanges, isBackwardSync); - - // Enqueue compaction if needed - _compactor.TryEnqueue(); - } - finally - { - if (!wasInitialized) - _initSemaphore.Release(); - - semaphore.Release(); - } - - foreach (IMergeOperator mergeOperator in _mergeOperators) - stats?.Combine(mergeOperator.GetAndResetStats()); - stats?.Compressing.Combine(_compressor.GetAndResetStats()); - stats?.Compacting.Combine(_compactor.GetAndResetStats()); - stats?.Adding.Include(Stopwatch.GetElapsedTime(totalTimestamp)); - } - - protected virtual void MergeBlockNumbers( - IWriteBatch dbBatch, ReadOnlySpan key, List numbers, - bool isBackwardSync, LogIndexUpdateStats? stats - ) - { - Span dbKeyBuffer = stackalloc byte[MaxDbKeyLength]; - ReadOnlySpan dbKey = CreateMergeDbKey(key, dbKeyBuffer, isBackwardSync); - - byte[] newValue = CreateDbValue(numbers); - - long timestamp = Stopwatch.GetTimestamp(); - - if (newValue is null or []) - throw new LogIndexStateException("No block numbers to save.", key); - - // TODO: consider disabling WAL, but check: - // - FlushOnTooManyWrites - // - atomic flushing - dbBatch.Merge(dbKey, newValue); - stats?.DBMerging.Include(Stopwatch.GetElapsedTime(timestamp)); - } - - private static ReadOnlySpan WriteKey(ReadOnlySpan key, Span buffer) - { - key.CopyTo(buffer); - return buffer[..key.Length]; - } - - private static ReadOnlySpan ExtractKey(ReadOnlySpan dbKey) => dbKey[..^BlockNumberSize]; - - /// - /// Generates a key consisting of the key || block-number byte array. - /// / - private static ReadOnlySpan CreateDbKey(ReadOnlySpan key, int blockNumber, Span buffer) - { - key = WriteKey(key, buffer); - WriteKeyBlockNumber(buffer[key.Length..], blockNumber); - - int length = key.Length + BlockNumberSize; - return buffer[..length]; - } - - /// - /// Generates a key consisting of the key || block-number byte array. - /// / - private static ReadOnlySpan CreateDbKey(ReadOnlySpan key, ReadOnlySpan blockNumber, Span buffer) - { - key = WriteKey(key, buffer); - blockNumber.CopyTo(buffer[key.Length..]); - - int length = key.Length + blockNumber.Length; - return buffer[..length]; - } - - private static ReadOnlySpan CreateMergeDbKey(ReadOnlySpan key, Span buffer, bool isBackwardSync) => - CreateDbKey(key, isBackwardSync ? Postfix.BackwardMerge : Postfix.ForwardMerge, buffer); - - // RocksDB uses big-endian (lexicographic) ordering - // +1 is needed as 0 is used for the backward-merge key - private static void WriteKeyBlockNumber(Span dbKeyEnd, int number) => BinaryPrimitives.WriteInt32BigEndian(dbKeyEnd, number + 1); - - private static bool UseBackwardSyncFor(ReadOnlySpan dbKey) => dbKey.EndsWith(Postfix.BackwardMerge); - - private static int BinarySearch(ReadOnlySpan blocks, int from) - { - int index = blocks.BinarySearch(from); - return index < 0 ? ~index : index; - } - - private ReadOnlySpan Compress(Span data, Span buffer) - { - ReadOnlySpan blockNumbers = MemoryMarshal.Cast(data); - int length = (int)_compressionAlgorithm.Compress(blockNumbers, (nuint)blockNumbers.Length, buffer); - return buffer[..length]; - } - - private static int ReadCompressionMarker(ReadOnlySpan source) => -BinaryPrimitives.ReadInt32LittleEndian(source); - private static void WriteCompressionMarker(Span source, int len) => BinaryPrimitives.WriteInt32LittleEndian(source, -len); - - private static bool IsCompressed(ReadOnlySpan source, out int len) - { - if (source.Length == 0) - { - len = 0; - return false; - } - - len = ReadCompressionMarker(source); - return len > 0; - } - - private static void WriteBlockNumber(Span destination, int number) => BinaryPrimitives.WriteInt32LittleEndian(destination, number); - private static int ReadBlockNumber(ReadOnlySpan source) => BinaryPrimitives.ReadInt32LittleEndian(source); - private static int ReadLastBlockNumber(ReadOnlySpan source) => ReadBlockNumber(source[^BlockNumberSize..]); - - private static void ReadBlockNumbers(ReadOnlySpan source, Span buffer) - { - if (source.Length % BlockNumberSize != 0) - throw new LogIndexStateException("Invalid length for array of block numbers."); - - if (buffer.Length < source.Length / BlockNumberSize) - throw new ArgumentException($"Buffer is too small to hold {source.Length / BlockNumberSize} block numbers.", nameof(buffer)); - - if (BitConverter.IsLittleEndian) - { - ReadOnlySpan sourceInt = MemoryMarshal.Cast(source); - sourceInt.CopyTo(buffer); - } - else - { - for (var i = 0; i < source.Length; i += BlockNumberSize) - buffer[i / BlockNumberSize] = ReadBlockNumber(source[i..]); - } - } - - private static byte[] CreateDbValue(List numbers) - { - byte[] value = new byte[numbers.Count * BlockNumberSize]; - numbers.CopyTo(MemoryMarshal.Cast(value.AsSpan())); - return value; - } - - private static LogIndexColumns GetColumn(int? topicIndex) => topicIndex.HasValue - ? (LogIndexColumns)(topicIndex + LogIndexColumns.Topics0) - : LogIndexColumns.Addresses; - - private IDb GetDb(int? topicIndex) => topicIndex.HasValue ? _topicDbs[topicIndex.Value] : _addressDb; - - private static IDb GetMetaDb(IColumnsDb rootDb) => rootDb.GetColumnDb(LogIndexColumns.Meta); - - private byte[] CompressDbValue(ReadOnlySpan key, Span data) - { - if (IsCompressed(data, out _)) - throw new LogIndexStateException("Attempt to compress already compressed data.", key); - if (data.Length % BlockNumberSize != 0) - throw new LogIndexStateException($"Invalid length of data to compress: {data.Length}.", key); - - byte[] buffer = Pool.Rent(data.Length + BlockNumberSize); - - try - { - WriteCompressionMarker(buffer, data.Length / BlockNumberSize); - int compressedLen = Compress(data, buffer.AsSpan(BlockNumberSize..)).Length; - return buffer[..(BlockNumberSize + compressedLen)]; - } - finally - { - Pool.Return(buffer); - } - } - - private static void DecompressDbValue(CompressionAlgorithm algorithm, ReadOnlySpan data, Span buffer) - { - if (!IsCompressed(data, out int len)) - throw new ValidationException("Data is not compressed"); - - if (buffer.Length < len) - throw new ArgumentException($"Buffer is too small to decompress {len} block numbers.", nameof(buffer)); - - _ = algorithm.Decompress(data[BlockNumberSize..], (nuint)len, buffer); - } - - private void DecompressDbValue(ReadOnlySpan data, Span buffer) => DecompressDbValue(_compressionAlgorithm, data, buffer); - - private Span RemoveReorgableBlocks(Span data) - { - if (GetLastReorgableBlockNumber() is not { } lastCompressBlock) - return Span.Empty; - - int lastCompressIndex = LastBlockSearch(data, lastCompressBlock, false); - - if (lastCompressIndex < 0) lastCompressIndex = 0; - if (lastCompressIndex > data.Length) lastCompressIndex = data.Length; - - return data[..lastCompressIndex]; - } - - private static void ReverseBlocksIfNeeded(Span data) - { - if (data.Length != 0 && ReadBlockNumber(data) > ReadLastBlockNumber(data)) - MemoryMarshal.Cast(data).Reverse(); - } - - private static void ReverseBlocksIfNeeded(Span blocks) - { - if (blocks.Length != 0 && blocks[0] > blocks[^1]) - blocks.Reverse(); - } - - private static int LastBlockSearch(ReadOnlySpan operand, int block, bool isBackward) - { - if (operand.IsEmpty) - return 0; - - int i = operand.Length - BlockNumberSize; - for (; i >= 0; i -= BlockNumberSize) - { - int currentBlock = ReadBlockNumber(operand[i..]); - if (currentBlock == block) - return i; - - if (isBackward) - { - if (currentBlock > block) - return i + BlockNumberSize; - } - else - { - if (currentBlock < block) - return i + BlockNumberSize; - } - } - - return i; - } - - private static bool IsSeqAsc(IReadOnlyList blocks) - { - int j = blocks.Count - 1; - int i = 1, d = blocks[0].BlockNumber; - while (i <= j && blocks[i].BlockNumber - i == d) i++; - return i > j; - } - - private static bool IsSeqDesc(IReadOnlyList blocks) - { - int j = blocks.Count - 1; - int i = 1, d = blocks[0].BlockNumber; - while (i <= j && blocks[i].BlockNumber + i == d) i++; - return i > j; - } - } -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexUpdateStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexUpdateStats.cs deleted file mode 100644 index 22651564ed8b..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexUpdateStats.cs +++ /dev/null @@ -1,114 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Threading; - -namespace Nethermind.Db.LogIndex; - -/// -/// Log index building statistics across some time range. -/// -public class LogIndexUpdateStats(ILogIndexStorage storage) : IFormattable -{ - private long _blocksAdded; - private long _txAdded; - private long _logsAdded; - private long _topicsAdded; - - public long BlocksAdded => _blocksAdded; - public long TxAdded => _txAdded; - public long LogsAdded => _logsAdded; - public long TopicsAdded => _topicsAdded; - - public long? MaxBlockNumber => storage.MaxBlockNumber; - public long? MinBlockNumber => storage.MinBlockNumber; - - public ExecTimeStats Adding { get; } = new(); - public ExecTimeStats Aggregating { get; } = new(); - public ExecTimeStats Merging { get; } = new(); - - public ExecTimeStats DBMerging { get; } = new(); - public ExecTimeStats UpdatingMeta { get; } = new(); - public ExecTimeStats CommittingBatch { get; } = new(); - public ExecTimeStats BackgroundMerging { get; } = new(); - - public AverageStats KeysCount { get; } = new(); - - public ExecTimeStats QueueingAddressCompression { get; } = new(); - public ExecTimeStats QueueingTopicCompression { get; } = new(); - - public PostMergeProcessingStats Compressing { get; } = new(); - public CompactingStats Compacting { get; } = new(); - - public ExecTimeStats LoadingReceipts { get; } = new(); - - public void Combine(LogIndexUpdateStats other) - { - _blocksAdded += other._blocksAdded; - _txAdded += other._txAdded; - _logsAdded += other._logsAdded; - _topicsAdded += other._topicsAdded; - - Adding.Combine(other.Adding); - Aggregating.Combine(other.Aggregating); - Merging.Combine(other.Merging); - UpdatingMeta.Combine(other.UpdatingMeta); - DBMerging.Combine(other.DBMerging); - CommittingBatch.Combine(other.CommittingBatch); - BackgroundMerging.Combine(other.BackgroundMerging); - KeysCount.Combine(other.KeysCount); - - QueueingAddressCompression.Combine(other.QueueingAddressCompression); - QueueingTopicCompression.Combine(other.QueueingTopicCompression); - - Compressing.Combine(other.Compressing); - Compacting.Combine(other.Compacting); - - LoadingReceipts.Combine(other.LoadingReceipts); - } - - public void IncrementBlocks() => Interlocked.Increment(ref _blocksAdded); - public void IncrementTx(int count = 1) => Interlocked.Add(ref _txAdded, count); - public void IncrementLogs() => Interlocked.Increment(ref _logsAdded); - public void IncrementTopics() => Interlocked.Increment(ref _topicsAdded); - - public string ToString(string? format, IFormatProvider? formatProvider) - { - const string tab = "\t"; - - return !string.Equals(format, "D", StringComparison.OrdinalIgnoreCase) - ? $"{MinBlockNumber:N0} - {MaxBlockNumber:N0} (blocks: +{BlocksAdded:N0} | txs: +{TxAdded:N0} | logs: +{LogsAdded:N0} | topics: +{TopicsAdded:N0})" - : $""" - - {tab}Blocks: {MinBlockNumber:N0} - {MaxBlockNumber:N0} (+{_blocksAdded:N0}) - - {tab}Txs: +{TxAdded:N0} - {tab}Logs: +{LogsAdded:N0} - {tab}Topics: +{TopicsAdded:N0} - - {tab}Keys per batch: {KeysCount:N0} - - {tab}Loading receipts: {LoadingReceipts} - {tab}Aggregating: {Aggregating} - - {tab}Adding receipts: {Adding} - {tab}{tab}Merging: {Merging} (DB: {DBMerging}) - {tab}{tab}Updating metadata: {UpdatingMeta} - {tab}{tab}Committing batch: {CommittingBatch} - - {tab}Background merging: {BackgroundMerging} - - {tab}Post-merge compression: {Compressing.Total} - {tab}{tab}DB reading: {Compressing.DBReading} - {tab}{tab}Compressing: {Compressing.CompressingValue} - {tab}{tab}DB saving: {Compressing.DBSaving} - {tab}{tab}Keys compressed: {Compressing.CompressedAddressKeys:N0} address, {Compressing.CompressedTopicKeys:N0} topic - {tab}{tab}Keys in queue: {Compressing.QueueLength:N0} - - {tab}Compacting: {Compacting.Total} - """; - } - - public override string ToString() => ToString(null, null); -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/MergeOperator.cs b/src/Nethermind/Nethermind.Db/LogIndex/MergeOperator.cs deleted file mode 100644 index a9f30b6fe4e7..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/MergeOperator.cs +++ /dev/null @@ -1,174 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Diagnostics; -using System.Threading; -using Nethermind.Core; -using Nethermind.Core.Collections; - -namespace Nethermind.Db.LogIndex; - -partial class LogIndexStorage -{ - // TODO: check if success=false + paranoid_checks=true is better than throwing an exception - /// - /// Merges log index incoming block number sequences into a single DB value. - /// - /// - /// Handles calls from . - /// Operator works only with uncompressed data under "transient" keys.
- /// Each log index column family has its own merge operator instance, - /// parameter is used to find corresponding DB.
- /// - /// - /// Supports 2 different use cases depending on the incoming operand: - /// - /// - /// In case if operand is a sequence of block numbers - - /// directly concatenates with the existing DB value, validating order remains correct (see ).
- /// If value size grows to or over block numbers - - /// queues it for compression (via ). - ///
- /// - /// If operand from - performs specified operation on the previously obtained sequence. - /// - ///
- /// Check MergeOperator tests for the expected behavior. - ///
- ///
- public class MergeOperator(ILogIndexStorage storage, ICompressor compressor, int? topicIndex) : IMergeOperator - { - private LogIndexUpdateStats _stats = new(storage); - public LogIndexUpdateStats Stats => _stats; - public LogIndexUpdateStats GetAndResetStats() => Interlocked.Exchange(ref _stats, new(storage)); - - public string Name => $"{nameof(LogIndexStorage)}.{nameof(MergeOperator)}"; - - public ArrayPoolList? FullMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator) => - Merge(key, enumerator, isPartial: false); - - public ArrayPoolList? PartialMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator) => - Merge(key, enumerator, isPartial: true); - - private static bool IsBlockNewer(int next, int? last, bool isBackwardSync) => - LogIndexStorage.IsBlockNewer(next, last, last, isBackwardSync); - - // Validate we are merging non-intersecting segments - to prevent data corruption - private static void AddEnsureSorted(ReadOnlySpan key, ArrayPoolList result, ReadOnlySpan value, bool isBackward) - { - if (value.Length == 0) - return; - - int nextBlock = ReadBlockNumber(value); - int? lastBlock = result.Count > 0 ? ReadLastBlockNumber(result.AsSpan()) : (int?)null; - - if (!IsBlockNewer(next: nextBlock, last: lastBlock, isBackward)) - throw new LogIndexStateException($"Invalid order during merge: {lastBlock} -> {nextBlock} (backward: {isBackward}).", key); - - result.AddRange(value); - } - - // TODO: avoid array copying in case of a single value? - private ArrayPoolList? Merge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator, bool isPartial) - { - var success = false; - ArrayPoolList? result = null; - var timestamp = Stopwatch.GetTimestamp(); - - try - { - // Fast return in case of a single operand - if (!enumerator.HasExistingValue && enumerator.OperandsCount == 1 && !MergeOps.IsAny(enumerator.GetOperand(0))) - return new(enumerator.GetOperand(0)); - - bool isBackwards = UseBackwardSyncFor(key); - - // Calculate total length - var resultLength = enumerator.GetExistingValue().Length; - for (var i = 0; i < enumerator.OperandsCount; i++) - { - ReadOnlySpan operand = enumerator.GetOperand(i); - - if (MergeOps.IsAny(operand)) - { - if (isPartial) - return null; // Notify RocksDB that we can't partially merge custom ops - - continue; - } - - resultLength += operand.Length; - } - - result = new(resultLength); - - // For truncate - just use max/min for all operands - int? truncateAggregate = Aggregate(MergeOp.Truncate, enumerator, isBackwards); - - int iReorg = 0; - for (int i = 0; i < enumerator.TotalCount; i++) - { - Span operand = enumerator.Get(i); - - if (MergeOps.IsAny(operand)) - continue; - - // For reorg - order matters, so we need to always traverse from the current position - iReorg = Math.Max(iReorg, i + 1); - if (FindNext(MergeOp.Reorg, enumerator, ref iReorg) is { } reorgBlock) - operand = MergeOps.ApplyTo(operand, MergeOp.Reorg, reorgBlock, isBackwards); - - if (truncateAggregate is { } truncateBlock) - operand = MergeOps.ApplyTo(operand, MergeOp.Truncate, truncateBlock, isBackwards); - - AddEnsureSorted(key, result, operand, isBackwards); - } - - if (result.Count % BlockNumberSize != 0) - throw new LogIndexStateException($"Invalid data length post-merge: {result.Count}.", key); - - compressor.TryEnqueue(topicIndex, key, result.AsSpan()); - - success = true; - return result; - } - catch (Exception exception) - { - (storage as LogIndexStorage)?.OnBackgroundError(exception); - return null; - } - finally - { - if (!success) result?.Dispose(); - - _stats.BackgroundMerging.Include(Stopwatch.GetElapsedTime(timestamp)); - } - } - - private static int? FindNext(MergeOp op, RocksDbMergeEnumerator enumerator, ref int i) - { - while (i < enumerator.TotalCount && !MergeOps.Is(op, enumerator.Get(i))) - i++; - - return i < enumerator.TotalCount && MergeOps.Is(op, enumerator.Get(i), out int block) - ? block - : null; - } - - private static int? Aggregate(MergeOp op, RocksDbMergeEnumerator enumerator, bool isBackwardSync) - { - int? result = null; - for (var i = 0; i < enumerator.OperandsCount; i++) - { - if (!MergeOps.Is(op, enumerator.GetOperand(i), out var next)) - continue; - - if (result is null || (isBackwardSync && next < result) || (!isBackwardSync && next > result)) - result = next; - } - - return result; - } - } -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/MergeOps.cs b/src/Nethermind/Nethermind.Db/LogIndex/MergeOps.cs deleted file mode 100644 index 74d494cd5d0f..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/MergeOps.cs +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; - -namespace Nethermind.Db.LogIndex; - -partial class LogIndexStorage -{ - /// - /// Custom operations for . - /// - public enum MergeOp : byte - { - /// - /// Reorgs from the provided block number, - /// removing any numbers starting from it. - /// - /// - /// Added to support "fast" reorgs - without explicitly fetching value from the database. - /// - Reorg = 1, - - /// - /// Truncates data up to the provided block number, - /// removing it and anything coming before. - /// - /// - /// Added to remove numbers already saved in "finalized" keys (via ) - /// from "transient" keys, without the need for a lock (in case of concurrent merges). - /// - Truncate = 2 - } - - /// - /// Helper class to create and parse operations. - /// - public static class MergeOps - { - public const int Size = BlockNumberSize + 1; - - public static bool Is(MergeOp op, ReadOnlySpan operand) => - operand.Length == Size && operand[0] == (byte)op; - - public static bool Is(MergeOp op, ReadOnlySpan operand, out int fromBlock) - { - if (operand.Length == Size && operand[0] == (byte)op) - { - fromBlock = ReadLastBlockNumber(operand); - return true; - } - - fromBlock = 0; - return false; - } - - public static bool IsAny(ReadOnlySpan operand) => - Is(MergeOp.Reorg, operand, out _) || - Is(MergeOp.Truncate, operand, out _); - - public static Span Create(MergeOp op, int fromBlock, Span buffer) - { - Span dbValue = buffer[..Size]; - dbValue[0] = (byte)op; - WriteBlockNumber(dbValue[1..], fromBlock); - return dbValue; - } - - public static Span ApplyTo(Span operand, MergeOp op, int block, bool isBackward) - { - // In most cases the searched block will be near or at the end of the operand, if present there - int i = LastBlockSearch(operand, block, isBackward); - - return op switch - { - MergeOp.Reorg => i switch - { - < 0 => Span.Empty, - _ when i >= operand.Length => operand, - _ => operand[..i] - }, - - MergeOp.Truncate => i switch - { - < 0 => operand, - _ when i >= operand.Length => Span.Empty, - _ => operand[(i + BlockNumberSize)..] - }, - - _ => throw new ArgumentOutOfRangeException(nameof(op)) - }; - } - } -} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/PostMergeProcessingStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/PostMergeProcessingStats.cs deleted file mode 100644 index 312056e88054..000000000000 --- a/src/Nethermind/Nethermind.Db/LogIndex/PostMergeProcessingStats.cs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Db.LogIndex; - -public class PostMergeProcessingStats -{ - public ExecTimeStats DBReading { get; set; } = new(); - public ExecTimeStats CompressingValue { get; set; } = new(); - public ExecTimeStats DBSaving { get; set; } = new(); - public int QueueLength { get; set; } - - public long CompressedAddressKeys; - public long CompressedTopicKeys; - - public ExecTimeStats Total { get; } = new(); - - public void Combine(PostMergeProcessingStats other) - { - DBReading.Combine(other.DBReading); - CompressingValue.Combine(other.CompressingValue); - DBSaving.Combine(other.DBSaving); - - CompressedAddressKeys += other.CompressedAddressKeys; - CompressedTopicKeys += other.CompressedTopicKeys; - - Total.Combine(other.Total); - } -} diff --git a/src/Nethermind/Nethermind.Db/MemDb.cs b/src/Nethermind/Nethermind.Db/MemDb.cs index e68055560f3c..39490693200f 100644 --- a/src/Nethermind/Nethermind.Db/MemDb.cs +++ b/src/Nethermind/Nethermind.Db/MemDb.cs @@ -5,8 +5,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Threading; using Nethermind.Core; using Nethermind.Core.Extensions; @@ -21,13 +19,14 @@ public class MemDb : IFullDb public long WritesCount { get; private set; } #if ZK - private readonly Dictionary _db = new(Bytes.EqualityComparer); + private readonly Dictionary _db; private readonly Dictionary.AlternateLookup> _spanDb; #else - private readonly ConcurrentDictionary _db = new(Bytes.EqualityComparer); + private readonly ConcurrentDictionary _db; private readonly ConcurrentDictionary.AlternateLookup> _spanDb; #endif + public MemDb(string name) : this(0, 0) { @@ -36,7 +35,7 @@ public MemDb(string name) public static MemDb CopyFrom(IDb anotherDb) { - MemDb newDb = new(); + MemDb newDb = new MemDb(); foreach (KeyValuePair kv in anotherDb.GetAll()) { newDb[kv.Key] = kv.Value; @@ -53,6 +52,11 @@ public MemDb(int writeDelay, int readDelay) { _writeDelay = writeDelay; _readDelay = readDelay; +#if ZK + _db = new Dictionary(Bytes.EqualityComparer); +#else + _db = new ConcurrentDictionary(Bytes.EqualityComparer); +#endif _spanDb = _db.GetAlternateLookup>(); } @@ -60,8 +64,14 @@ public MemDb(int writeDelay, int readDelay) public virtual byte[]? this[ReadOnlySpan key] { - get => Get(key); - set => Set(key, value); + get + { + return Get(key); + } + set + { + Set(key, value); + } } public KeyValuePair[] this[byte[][] keys] @@ -74,11 +84,18 @@ public virtual byte[]? this[ReadOnlySpan key] } ReadsCount += keys.Length; - return keys.Select(k => new KeyValuePair(k, _db.GetValueOrDefault(k))).ToArray(); + return keys.Select(k => new KeyValuePair(k, _db.TryGetValue(k, out var value) ? value : null)).ToArray(); } } - public virtual void Remove(ReadOnlySpan key) => _spanDb.TryRemove(key, out _); + public virtual void Remove(ReadOnlySpan key) + { +#if ZK + _spanDb.Remove(key); +#else + _spanDb.TryRemove(key, out _); +#endif + } public bool KeyExists(ReadOnlySpan key) => _spanDb.ContainsKey(key); @@ -103,7 +120,9 @@ public void Dispose() { } public bool PreferWriteByArray => true; - public unsafe void DangerousReleaseMemory(in ReadOnlySpan span) { } + public virtual Span GetSpan(ReadOnlySpan key) => Get(key).AsSpan(); + + public void DangerousReleaseMemory(in ReadOnlySpan span) { } public virtual byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) { @@ -116,9 +135,6 @@ public unsafe void DangerousReleaseMemory(in ReadOnlySpan span) { } return _spanDb.TryGetValue(key, out byte[] value) ? value : null; } - public unsafe Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - => Get(key).AsSpan(); - public virtual void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { if (_writeDelay > 0) diff --git a/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj b/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj index f8a963c6bf31..04ad5b8b916b 100644 --- a/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj +++ b/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj @@ -10,7 +10,6 @@ - diff --git a/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs b/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs index 98a7d045220f..bc734119beb3 100644 --- a/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs +++ b/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs @@ -2,11 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using System.Collections.Generic; using System.Linq; using Nethermind.Core; -using Nethermind.Core.Buffers; namespace Nethermind.Db { @@ -14,12 +12,17 @@ public class ReadOnlyDb(IDb wrappedDb, bool createInMemWriteStore) : IReadOnlyDb { private readonly MemDb _memDb = new(); - public void Dispose() => _memDb.Dispose(); + public void Dispose() + { + _memDb.Dispose(); + } public string Name { get => wrappedDb.Name; } - public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => - _memDb.Get(key, flags) ?? wrappedDb.Get(key, flags); + public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + { + return _memDb.Get(key, flags) ?? wrappedDb.Get(key, flags); + } public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { @@ -35,11 +38,11 @@ public KeyValuePair[] this[byte[][] keys] { get { - KeyValuePair[]? result = wrappedDb[keys]; - KeyValuePair[]? memResult = _memDb[keys]; + var result = wrappedDb[keys]; + var memResult = _memDb[keys]; for (int i = 0; i < memResult.Length; i++) { - KeyValuePair memValue = memResult[i]; + var memValue = memResult[i]; if (memValue.Value is not null) { result[i] = memValue; @@ -70,6 +73,7 @@ public void Flush(bool onlyWal) { } public virtual void ClearTempChanges() => _memDb.Clear(); + public Span GetSpan(ReadOnlySpan key) => Get(key).AsSpan(); public void PutSpan(ReadOnlySpan keyBytes, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) { if (!createInMemWriteStore) diff --git a/src/Nethermind/Nethermind.Db/RocksDbSettings.cs b/src/Nethermind/Nethermind.Db/RocksDbSettings.cs index 2c7feceed718..b22c1962b5be 100644 --- a/src/Nethermind/Nethermind.Db/RocksDbSettings.cs +++ b/src/Nethermind/Nethermind.Db/RocksDbSettings.cs @@ -5,10 +5,16 @@ namespace Nethermind.Db { - public class DbSettings(string name, string path) + public class DbSettings { - public string DbName { get; private set; } = name; - public string DbPath { get; private set; } = path; + public DbSettings(string name, string path) + { + DbName = name; + DbPath = path; + } + + public string DbName { get; private set; } + public string DbPath { get; private set; } public bool DeleteOnStart { get; set; } public bool CanDeleteFolder { get; set; } = true; diff --git a/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs b/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs index 7f49f1a1367a..d69a532c8735 100644 --- a/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs +++ b/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -24,11 +23,12 @@ public class SimpleFilePublicKeyDb : IFullDb private readonly ILogger _logger; private bool _hasPendingChanges; - private readonly ConcurrentDictionary _cache = new(Bytes.EqualityComparer); - private readonly ConcurrentDictionary.AlternateLookup> _cacheSpan; + private ConcurrentDictionary _cache; + private ConcurrentDictionary.AlternateLookup> _cacheSpan; - private string DbPath { get; } + public string DbPath { get; } public string Name { get; } + public string Description { get; } public ICollection Keys => _cache.Keys.ToArray(); public ICollection Values => _cache.Values; @@ -40,27 +40,26 @@ public SimpleFilePublicKeyDb(string name, string dbDirectoryPath, ILogManager lo ArgumentNullException.ThrowIfNull(dbDirectoryPath); Name = name ?? throw new ArgumentNullException(nameof(name)); DbPath = Path.Combine(dbDirectoryPath, DbFileName); + Description = $"{Name}|{DbPath}"; if (!Directory.Exists(dbDirectoryPath)) { Directory.CreateDirectory(dbDirectoryPath); } - _cacheSpan = _cache.GetAlternateLookup>(); - - if (File.Exists(DbPath)) - { - LoadData(); - } + LoadData(); } public byte[]? this[ReadOnlySpan key] { - get => Get(key); - set => Set(key, value); + get => Get(key, ReadFlags.None); + set => Set(key, value, WriteFlags.None); } - public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => _cacheSpan[key]; + public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + { + return _cacheSpan[key]; + } public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { @@ -99,7 +98,10 @@ public void Remove(ReadOnlySpan key) } } - public bool KeyExists(ReadOnlySpan key) => _cacheSpan.ContainsKey(key); + public bool KeyExists(ReadOnlySpan key) + { + return _cacheSpan.ContainsKey(key); + } public void Flush(bool onlyWal = false) { } @@ -115,7 +117,10 @@ public void Clear() public IEnumerable GetAllValues(bool ordered = false) => _cache.Values; - public IWriteBatch StartWriteBatch() => this.LikeABatch(CommitBatch); + public IWriteBatch StartWriteBatch() + { + return this.LikeABatch(CommitBatch); + } private void CommitBatch() { @@ -214,6 +219,14 @@ private void LoadData() { const int maxLineLength = 2048; + _cache = new ConcurrentDictionary(Bytes.EqualityComparer); + _cacheSpan = _cache.GetAlternateLookup>(); + + if (!File.Exists(DbPath)) + { + return; + } + using SafeFileHandle fileHandle = File.OpenHandle(DbPath, FileMode.OpenOrCreate); using var handle = ArrayPoolDisposableReturn.Rent(maxLineLength, out byte[] rentedBuffer); @@ -293,6 +306,24 @@ void RecordError(Span data) } } - public void Dispose() { } + private byte[] Update(byte[] oldValue, byte[] newValue) + { + if (!Bytes.AreEqual(oldValue, newValue)) + { + _hasPendingChanges = true; + } + + return newValue; + } + + private byte[] Add(byte[] value) + { + _hasPendingChanges = true; + return value; + } + + public void Dispose() + { + } } } diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/BlockBenchmarkHelper.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/BlockBenchmarkHelper.cs deleted file mode 100644 index 2ec48b5437c7..000000000000 --- a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/BlockBenchmarkHelper.cs +++ /dev/null @@ -1,232 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Autofac; -using Nethermind.Blockchain; -using Nethermind.Blockchain.BeaconBlockRoot; -using Nethermind.Blockchain.Blocks; -using Nethermind.Blockchain.Receipts; -using Nethermind.Blockchain.Visitors; -using Nethermind.Config; -using Nethermind.Consensus.ExecutionRequests; -using Nethermind.Consensus.Processing; -using Nethermind.Consensus.Rewards; -using Nethermind.Consensus.Validators; -using Nethermind.Consensus.Withdrawals; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Core.Specs; -using Nethermind.Evm; -using Nethermind.Evm.State; -using Nethermind.Evm.Tracing; -using Nethermind.Evm.TransactionProcessing; -using Nethermind.Logging; -using Nethermind.State; -using Nethermind.Trie; -using Nethermind.Int256; - -namespace Nethermind.Evm.Benchmark.GasBenchmarks; - -/// -/// Shared setup helpers for block-level gas benchmarks (BlockOne, Block, NewPayload modes). -/// -internal static class BlockBenchmarkHelper -{ - private sealed class WorldStateReaderAdapter(IWorldState worldState) : IStateReader - { - public bool TryGetAccount(BlockHeader baseBlock, Address address, out AccountStruct account) - { - account = default; - return false; - } - - public ReadOnlySpan GetStorage(BlockHeader baseBlock, Address address, in UInt256 index) => []; - - public byte[] GetCode(Hash256 codeHash) => null; - - public byte[] GetCode(in ValueHash256 codeHash) => null; - - public void RunTreeVisitor(ITreeVisitor treeVisitor, BlockHeader baseBlock, VisitingOptions visitingOptions = null) - where TCtx : struct, INodeContext - { - } - - public bool HasStateForBlock(BlockHeader baseBlock) => worldState.HasStateForBlock(baseBlock); - } - - public readonly struct BranchProcessingContext( - IWorldState state, - IBlockCachePreWarmer preWarmer, - IDisposable preWarmerLifetime, - PreBlockCaches preBlockCaches, - bool cachePrecompiles) - { - public IWorldState State { get; } = state; - public IBlockCachePreWarmer PreWarmer { get; } = preWarmer; - public IDisposable PreWarmerLifetime { get; } = preWarmerLifetime; - public PreBlockCaches PreBlockCaches { get; } = preBlockCaches; - public bool CachePrecompiles { get; } = cachePrecompiles; - } - - public static BranchProcessingContext CreateBranchProcessingContext( - ISpecProvider specProvider, - IBlockhashProvider blockhashProvider) - { - IBlocksConfig blocksConfig = new BlocksConfig(); - if (!blocksConfig.PreWarmStateOnBlockProcessing) - { - return new BranchProcessingContext( - PayloadLoader.CreateWorldState(), - preWarmer: null, - preWarmerLifetime: null, - preBlockCaches: null, - cachePrecompiles: false); - } - - NodeStorageCache nodeStorageCache = new(); - PreBlockCaches preBlockCaches = new(); - IWorldState state = PayloadLoader.CreateWorldState( - nodeStorageCache, - preBlockCaches, - populatePreBlockCache: false); - IWorldStateManager worldStateManager = PayloadLoader.CreateWorldStateManager(nodeStorageCache); - - ContainerBuilder containerBuilder = new(); - containerBuilder.RegisterInstance(specProvider).As().SingleInstance(); - containerBuilder.RegisterInstance(blockhashProvider).As().SingleInstance(); - containerBuilder.RegisterInstance(LimboLogs.Instance).As().SingleInstance(); - containerBuilder - .RegisterInstance(BlobBaseFeeCalculator.Instance) - .As() - .SingleInstance(); - containerBuilder.RegisterType().As().InstancePerLifetimeScope(); - containerBuilder.RegisterType().As().InstancePerLifetimeScope(); - containerBuilder.RegisterType().As().InstancePerLifetimeScope(); - containerBuilder.RegisterType().As().SingleInstance(); - containerBuilder.RegisterDecorator((context, _, baseCodeInfoRepository) => - new CachedCodeInfoRepository( - context.Resolve(), - baseCodeInfoRepository, - blocksConfig.CachePrecompilesOnBlockProcessing ? preBlockCaches.PrecompileCache : null)); - containerBuilder.RegisterType().As().InstancePerLifetimeScope(); - - IContainer preWarmerContainer = containerBuilder.Build(); - PrewarmerEnvFactory prewarmerEnvFactory = new(worldStateManager, preWarmerContainer); - IBlockCachePreWarmer preWarmer = new BlockCachePreWarmer( - prewarmerEnvFactory, - blocksConfig, - nodeStorageCache, - preBlockCaches, - LimboLogs.Instance); - - return new BranchProcessingContext( - state, - preWarmer, - preWarmerContainer, - preBlockCaches, - blocksConfig.CachePrecompilesOnBlockProcessing); - } - - public static BlockHeader CreateGenesisHeader() => - new(Keccak.Zero, Keccak.OfAnEmptySequenceRlp, Address.Zero, 0, 0, 0, 0, Array.Empty()) - { - StateRoot = PayloadLoader.GenesisStateRoot - }; - - public static IStateReader CreateStateReader(IWorldState worldState) => new WorldStateReaderAdapter(worldState); - - public static IReceiptStorage CreateReceiptStorage(IReceiptConfig receiptConfig) => - receiptConfig.StoreReceipts ? new InMemoryReceiptStorage() : NullReceiptStorage.Instance; - - public static ProcessingOptions GetImportProcessingOptions(IReceiptConfig receiptConfig) => - receiptConfig.StoreReceipts ? ProcessingOptions.StoreReceipts : ProcessingOptions.None; - - public static ProcessingOptions GetNewPayloadProcessingOptions(IReceiptConfig receiptConfig) => - receiptConfig.StoreReceipts - ? ProcessingOptions.EthereumMerge | ProcessingOptions.StoreReceipts - : ProcessingOptions.EthereumMerge; - - public static ProcessingOptions GetBlockBuildingProcessingOptions(IBlocksConfig blocksConfig) => - blocksConfig.BuildBlocksOnMainState - ? ProcessingOptions.NoValidation | ProcessingOptions.StoreReceipts | ProcessingOptions.DoNotUpdateHead - : ProcessingOptions.ProducingBlock; - - public static BlockchainProcessor.Options GetBlockBuildingBlockchainProcessorOptions(IBlocksConfig blocksConfig) => - blocksConfig.BuildBlocksOnMainState ? BlockchainProcessor.Options.Default : BlockchainProcessor.Options.NoReceipts; - - public static ITransactionProcessor CreateTransactionProcessor( - IWorldState state, - IBlockhashProvider blockhashProvider, - ISpecProvider specProvider, - PreBlockCaches preBlockCaches = null, - bool cachePrecompiles = false) - { - ICodeInfoRepository codeInfoRepo = new EthereumCodeInfoRepository(state); - if (cachePrecompiles && preBlockCaches is not null) - { - codeInfoRepo = new CachedCodeInfoRepository( - new EthereumPrecompileProvider(), - codeInfoRepo, - preBlockCaches.PrecompileCache); - } - - EthereumVirtualMachine vm = new(blockhashProvider, specProvider, LimboLogs.Instance); - - return new EthereumTransactionProcessor( - BlobBaseFeeCalculator.Instance, specProvider, state, vm, codeInfoRepo, LimboLogs.Instance); - } - - public static BlockProcessor CreateBlockProcessor( - ISpecProvider specProvider, ITransactionProcessor txProcessor, IWorldState state, IReceiptStorage receiptStorage = null) => - new(specProvider, - Always.Valid, - NoBlockRewards.Instance, - new BlockProcessor.BlockValidationTransactionsExecutor( - new ExecuteTransactionProcessorAdapter(txProcessor), state), - state, - receiptStorage ?? NullReceiptStorage.Instance, - new BeaconBlockRootHandler(txProcessor, state), - new BlockhashStore(state), - LimboLogs.Instance, - new WithdrawalProcessor(state, LimboLogs.Instance), - new ExecutionRequestsProcessor(txProcessor)); - - public static BlockProcessor CreateBlockBuildingProcessor( - ISpecProvider specProvider, ITransactionProcessor txProcessor, IWorldState state, IReceiptStorage receiptStorage = null) => - new(specProvider, - Always.Valid, - NoBlockRewards.Instance, - new BlockProcessor.BlockProductionTransactionsExecutor( - new BuildUpTransactionProcessorAdapter(txProcessor), - state, - new BlockProcessor.BlockProductionTransactionPicker(specProvider), - LimboLogs.Instance), - state, - receiptStorage ?? NullReceiptStorage.Instance, - new BeaconBlockRootHandler(txProcessor, state), - new BlockhashStore(state), - LimboLogs.Instance, - new WithdrawalProcessor(state, LimboLogs.Instance), - new ExecutionRequestsProcessor(txProcessor)); - - public static void ExecuteSetupPayload( - IWorldState state, ITransactionProcessor txProcessor, - BlockHeader preBlockHeader, GasPayloadBenchmarks.TestCase scenario, - IReleaseSpec spec) - { - string setupFile = GasPayloadBenchmarks.FindSetupFile(scenario.FileName); - if (setupFile is null) return; - - using IDisposable setupScope = state.BeginScope(preBlockHeader); - (BlockHeader setupHeader, Transaction[] setupTxs) = PayloadLoader.LoadPayload(setupFile); - txProcessor.SetBlockExecutionContext(setupHeader); - for (int i = 0; i < setupTxs.Length; i++) - { - txProcessor.Execute(setupTxs[i], NullTxTracer.Instance); - } - state.Commit(spec); - state.CommitTree(preBlockHeader.Number); - preBlockHeader.StateRoot = state.StateRoot; - } -} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBenchmarkColumnProvider.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBenchmarkColumnProvider.cs deleted file mode 100644 index 665d29aa8902..000000000000 --- a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBenchmarkColumnProvider.cs +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using System.Linq; -using BenchmarkDotNet.Columns; -using BenchmarkDotNet.Mathematics; -using BenchmarkDotNet.Reports; -using BenchmarkDotNet.Running; -using Perfolizer.Mathematics.Common; - -namespace Nethermind.Evm.Benchmark.GasBenchmarks; - -public class GasBenchmarkColumnProvider : IColumnProvider -{ - private static readonly IColumn[] Columns = - [ - new MGasThroughputColumn(), - new MGasConfidenceIntervalColumn(true), - new MGasConfidenceIntervalColumn(false) - ]; - - public IEnumerable GetColumns(Summary summary) => Columns; - - private static double CalculateMGasThroughput(double nanoseconds) - { - // All gas-benchmark payloads use 100M gas - const long gas = 100_000_000L; - double opThroughput = 1_000_000_000.0 / nanoseconds; - return gas * opThroughput / 1_000_000.0; - } - - private class MGasThroughputColumn : IColumn - { - public string Id => "MGas/s"; - public string ColumnName => "MGas/s"; - public string Legend => "Throughput in millions of gas per second"; - public bool AlwaysShow => true; - public ColumnCategory Category => ColumnCategory.Custom; - public int PriorityInCategory => 0; - public bool IsNumeric => true; - public UnitType UnitType => UnitType.Size; - - public string GetValue(Summary summary, BenchmarkCase benchmarkCase) - { - Statistics stats = summary.Reports.FirstOrDefault(r => r.BenchmarkCase == benchmarkCase)?.ResultStatistics; - if (stats is null) - return "N/A"; - - return CalculateMGasThroughput(stats.Mean).ToString("F2"); - } - - public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) - => GetValue(summary, benchmarkCase); - - public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) => false; - public bool IsAvailable(Summary summary) => true; - } - - private class MGasConfidenceIntervalColumn(bool isLower) : IColumn - { - public string Id => isLower ? "CI-Lower" : "CI-Upper"; - public string ColumnName => isLower ? "CI-Lower" : "CI-Upper"; - public string Legend => $"{(isLower ? "Lower" : "Upper")} bound of MGas/s 99% confidence interval"; - public bool AlwaysShow => true; - public ColumnCategory Category => ColumnCategory.Custom; - public int PriorityInCategory => 0; - public bool IsNumeric => true; - public UnitType UnitType => UnitType.Size; - - public string GetValue(Summary summary, BenchmarkCase benchmarkCase) - { - Statistics stats = summary.Reports.FirstOrDefault(r => r.BenchmarkCase == benchmarkCase)?.ResultStatistics; - if (stats is null) - return "N/A"; - - ConfidenceInterval ci = stats.GetConfidenceInterval(ConfidenceLevel.L99); - double bound = isLower ? ci.Lower : ci.Upper; - return CalculateMGasThroughput(bound).ToString("F2"); - } - - public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) - => GetValue(summary, benchmarkCase); - - public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) => false; - public bool IsAvailable(Summary summary) => true; - } -} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBenchmarkConfig.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBenchmarkConfig.cs deleted file mode 100644 index cef280207e1d..000000000000 --- a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBenchmarkConfig.cs +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Exporters.Json; -using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Toolchains.InProcess.Emit; - -namespace Nethermind.Evm.Benchmark.GasBenchmarks; - -public class GasBenchmarkConfig : ManualConfig -{ - internal static bool InProcess { get; set; } - - /// 1-based chunk index (e.g. 1 means first chunk). 0 = no chunking. - internal static int ChunkIndex { get; set; } - - /// Total number of chunks to split scenarios into. - internal static int ChunkTotal { get; set; } - - public GasBenchmarkConfig() - { - Job job = Job.MediumRun.WithLaunchCount(1).WithIterationCount(10); - - if (InProcess) - { - job = job.WithToolchain(InProcessEmitToolchain.Instance); - } - - AddJob(job); - AddDiagnoser(MemoryDiagnoser.Default); - AddColumnProvider(new GasBenchmarkColumnProvider()); - AddExporter(JsonExporter.Full); - } -} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockBenchmarks.cs deleted file mode 100644 index 66a80a7e6bef..000000000000 --- a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockBenchmarks.cs +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using BenchmarkDotNet.Attributes; -using Nethermind.Blockchain; -using Nethermind.Blockchain.BeaconBlockRoot; -using Nethermind.Blockchain.Receipts; -using Nethermind.Blockchain.Tracing; -using Nethermind.Config; -using Nethermind.Consensus.Processing; -using Nethermind.Core; -using Nethermind.Core.Specs; -using Nethermind.Evm.State; -using Nethermind.Evm.Tracing; -using Nethermind.Evm.TransactionProcessing; -using Nethermind.Logging; -using Nethermind.Specs; -using Nethermind.Specs.Forks; - -namespace Nethermind.Evm.Benchmark.GasBenchmarks; - -/// -/// Benchmarks that replay gas-benchmark payload files via BranchProcessor.Process, -/// matching the real Nethermind block processing pipeline. Includes state scope management, -/// CommitTree, pre-warming coordination, beacon root, blockhash store, transaction execution, -/// bloom filters, receipts root, withdrawals, execution requests, and state root recalculation. -/// -[Config(typeof(GasBenchmarkConfig))] -public class GasBlockBenchmarks -{ - private IWorldState _state; - private BranchProcessor _branchProcessor; - private Block[] _blocksToProcess; - private BlockHeader _preBlockHeader; - private IBlockCachePreWarmer _preWarmer; - private IDisposable _preWarmerLifetime; - private ProcessingOptions _processingOptions; - - [ParamsSource(nameof(GetTestCases))] - public GasPayloadBenchmarks.TestCase Scenario { get; set; } - - public static IEnumerable GetTestCases() => GasPayloadBenchmarks.GetTestCases(); - - [GlobalSetup] - public void GlobalSetup() - { - IReleaseSpec pragueSpec = Prague.Instance; - ISpecProvider specProvider = new SingleReleaseSpecProvider(pragueSpec, 1, 1); - - PayloadLoader.EnsureGenesisInitialized(GasPayloadBenchmarks.s_genesisPath, pragueSpec); - - TestBlockhashProvider blockhashProvider = new(); - BlockBenchmarkHelper.BranchProcessingContext branchProcessingContext = BlockBenchmarkHelper.CreateBranchProcessingContext(specProvider, blockhashProvider); - _state = branchProcessingContext.State; - _preWarmer = branchProcessingContext.PreWarmer; - _preWarmerLifetime = branchProcessingContext.PreWarmerLifetime; - _preBlockHeader = BlockBenchmarkHelper.CreateGenesisHeader(); - ITransactionProcessor txProcessor = BlockBenchmarkHelper.CreateTransactionProcessor( - _state, - blockhashProvider, - specProvider, - branchProcessingContext.PreBlockCaches, - branchProcessingContext.CachePrecompiles); - - BlockBenchmarkHelper.ExecuteSetupPayload(_state, txProcessor, _preBlockHeader, Scenario, pragueSpec); - - ReceiptConfig receiptConfig = new(); - IReceiptStorage receiptStorage = BlockBenchmarkHelper.CreateReceiptStorage(receiptConfig); - _processingOptions = BlockBenchmarkHelper.GetImportProcessingOptions(receiptConfig); - - BlockProcessor blockProcessor = BlockBenchmarkHelper.CreateBlockProcessor( - specProvider, txProcessor, _state, receiptStorage); - - _branchProcessor = new BranchProcessor( - blockProcessor, specProvider, _state, - new BeaconBlockRootHandler(txProcessor, _state), - blockhashProvider, LimboLogs.Instance, _preWarmer); - - _blocksToProcess = [PayloadLoader.LoadBlock(Scenario.FilePath)]; - - // Warm up and verify correctness - Block[] result = _branchProcessor.Process( - _preBlockHeader, _blocksToProcess, - _processingOptions, - NullBlockTracer.Instance); - PayloadLoader.VerifyProcessedBlock(result[0], Scenario.ToString(), Scenario.FilePath); - } - - [Benchmark] - public void ProcessBlock() - { - _branchProcessor.Process( - _preBlockHeader, _blocksToProcess, - _processingOptions, - NullBlockTracer.Instance); - } - - [GlobalCleanup] - public void GlobalCleanup() - { - _preWarmer?.ClearCaches(); - _preWarmerLifetime?.Dispose(); - _state = null; - _branchProcessor = null; - _blocksToProcess = null; - _preWarmer = null; - _preWarmerLifetime = null; - _processingOptions = ProcessingOptions.None; - } -} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockBuildingBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockBuildingBenchmarks.cs deleted file mode 100644 index c2b4b71fe631..000000000000 --- a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockBuildingBenchmarks.cs +++ /dev/null @@ -1,321 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using BenchmarkDotNet.Attributes; -using Nethermind.Blockchain; -using Nethermind.Blockchain.BeaconBlockRoot; -using Nethermind.Blockchain.Find; -using Nethermind.Blockchain.Tracing; -using Nethermind.Blockchain.Visitors; -using Nethermind.Config; -using Nethermind.Consensus.Processing; -using Nethermind.Consensus.Producers; -using Nethermind.Core; -using Nethermind.Core.Collections; -using Nethermind.Core.Crypto; -using Nethermind.Core.Specs; -using Nethermind.Crypto; -using Nethermind.Int256; -using Nethermind.Evm.State; -using Nethermind.Evm.TransactionProcessing; -using Nethermind.Logging; -using Nethermind.Specs; -using Nethermind.Specs.Forks; - -namespace Nethermind.Evm.Benchmark.GasBenchmarks; - -/// -/// Benchmarks block-building flow using BlockToProduce + BlockProductionTransactionsExecutor -/// with ProcessingOptions.ProducingBlock, matching Nethermind's producer-side transaction path. -/// -[Config(typeof(GasBenchmarkConfig))] -public class GasBlockBuildingBenchmarks -{ - internal const string BuildBlocksOnMainStateEnvVar = "NETHERMIND_BLOCKBUILDING_MAIN_STATE"; - private static readonly EthereumEcdsa s_ecdsa = new(1); - - private IWorldState _state; - private BranchProcessor _branchProcessor; - private IBlockchainProcessor _chainProcessor; - private BlockHeader _chainParentHeader; - private BlockHeader _preBlockHeader; - private BlockHeader _testHeaderTemplate; - private Transaction[] _testTransactions; - private BlockHeader[] _testUncles; - private Withdrawal[] _testWithdrawals; - private ProcessingOptions _processingOptions; - private IBlockCachePreWarmer _preWarmer; - private IDisposable _preWarmerLifetime; - - [ParamsSource(nameof(GetTestCases))] - public GasPayloadBenchmarks.TestCase Scenario { get; set; } - - public static IEnumerable GetTestCases() => GasPayloadBenchmarks.GetTestCases(); - - [GlobalSetup] - public void GlobalSetup() - { - IReleaseSpec pragueSpec = Prague.Instance; - ISpecProvider specProvider = new SingleReleaseSpecProvider(pragueSpec, 1, 1); - - PayloadLoader.EnsureGenesisInitialized(GasPayloadBenchmarks.s_genesisPath, pragueSpec); - - TestBlockhashProvider blockhashProvider = new(); - BlockBenchmarkHelper.BranchProcessingContext branchProcessingContext = BlockBenchmarkHelper.CreateBranchProcessingContext(specProvider, blockhashProvider); - _state = branchProcessingContext.State; - _preWarmer = branchProcessingContext.PreWarmer; - _preWarmerLifetime = branchProcessingContext.PreWarmerLifetime; - _preBlockHeader = BlockBenchmarkHelper.CreateGenesisHeader(); - - IBlocksConfig blocksConfig = new BlocksConfig(); - string buildOnMainStateValue = Environment.GetEnvironmentVariable(BuildBlocksOnMainStateEnvVar); - if (!string.IsNullOrWhiteSpace(buildOnMainStateValue) && bool.TryParse(buildOnMainStateValue, out bool buildOnMainState)) - { - blocksConfig.BuildBlocksOnMainState = buildOnMainState; - } - - _processingOptions = BlockBenchmarkHelper.GetBlockBuildingProcessingOptions(blocksConfig); - - ITransactionProcessor txProcessor = BlockBenchmarkHelper.CreateTransactionProcessor( - _state, - blockhashProvider, - specProvider, - branchProcessingContext.PreBlockCaches, - branchProcessingContext.CachePrecompiles); - - Block testBlock = PayloadLoader.LoadBlock(Scenario.FilePath); - _testHeaderTemplate = testBlock.Header; - _testTransactions = testBlock.Transactions; - _testUncles = testBlock.Uncles; - _testWithdrawals = testBlock.Withdrawals; - - _chainParentHeader = _preBlockHeader.Clone(); - _chainParentHeader.Hash = _testHeaderTemplate.ParentHash; - _chainParentHeader.Number = _testHeaderTemplate.Number > 0 ? _testHeaderTemplate.Number - 1 : 0; - _chainParentHeader.TotalDifficulty = _testHeaderTemplate.TotalDifficulty is null - ? UInt256.Zero - : _testHeaderTemplate.TotalDifficulty - _testHeaderTemplate.Difficulty; - - BlockBenchmarkHelper.ExecuteSetupPayload(_state, txProcessor, _preBlockHeader, Scenario, pragueSpec); - _chainParentHeader.StateRoot = _preBlockHeader.StateRoot; - - BlockProcessor blockProcessor = BlockBenchmarkHelper.CreateBlockBuildingProcessor(specProvider, txProcessor, _state); - _branchProcessor = new BranchProcessor( - blockProcessor, - specProvider, - _state, - new BeaconBlockRootHandler(txProcessor, _state), - blockhashProvider, - LimboLogs.Instance, - _preWarmer); - - BlockchainProcessor blockchainProcessor = new( - new BenchmarkBlockProducerBlockTree(_chainParentHeader), - _branchProcessor, - new RecoverSignatures(s_ecdsa, specProvider, LimboLogs.Instance), - BlockBenchmarkHelper.CreateStateReader(_state), - LimboLogs.Instance, - BlockBenchmarkHelper.GetBlockBuildingBlockchainProcessorOptions(blocksConfig), - new NoopProcessingStats()); - _chainProcessor = new OneTimeChainProcessor(_state, blockchainProcessor); - - // Warm up and verify correctness. - BlockToProduce warmupBlock = CreateBlockToProduce(); - Block warmupResult = _chainProcessor.Process(warmupBlock, _processingOptions, NullBlockTracer.Instance); - if (warmupResult is null) - { - throw new InvalidOperationException("Block building warmup did not produce a processed block."); - } - - PayloadLoader.VerifyProcessedBlock(warmupResult, Scenario.ToString(), Scenario.FilePath); - } - - [Benchmark] - public void ProcessBlock() - { - BlockToProduce blockToProduce = CreateBlockToProduce(); - _chainProcessor.Process(blockToProduce, _processingOptions, NullBlockTracer.Instance); - } - - private BlockToProduce CreateBlockToProduce() - { - BlockHeader header = _testHeaderTemplate.Clone(); - if (header.TotalDifficulty is null) - { - UInt256 parentTotalDifficulty = _chainParentHeader.TotalDifficulty ?? UInt256.Zero; - header.TotalDifficulty = parentTotalDifficulty + header.Difficulty; - } - - return new BlockToProduce(header, _testTransactions, _testUncles, _testWithdrawals); - } - - [GlobalCleanup] - public void GlobalCleanup() - { - _chainProcessor?.DisposeAsync().AsTask().GetAwaiter().GetResult(); - _preWarmer?.ClearCaches(); - _preWarmerLifetime?.Dispose(); - _state = null; - _branchProcessor = null; - _chainProcessor = null; - _chainParentHeader = null; - _preWarmer = null; - _preWarmerLifetime = null; - _testHeaderTemplate = null; - _testTransactions = null; - _testUncles = null; - _testWithdrawals = null; - } - - private sealed class NoopProcessingStats : IProcessingStats - { - event EventHandler IProcessingStats.NewProcessingStatistics - { - add { } - remove { } - } - - public void Start() { } - - public void CaptureStartStats() { } - - public void UpdateStats(Block block, BlockHeader baseBlock, long blockProcessingTimeInMicros) { } - } - - private sealed class BenchmarkBlockProducerBlockTree : IBlockTree - { - private readonly BlockHeader _parentHeader; - private readonly Block _head; - - public BenchmarkBlockProducerBlockTree(BlockHeader parentHeader) - { - _parentHeader = parentHeader; - _head = new Block(parentHeader, new BlockBody(Array.Empty(), Array.Empty(), null)); - SyncPivot = (0, Keccak.Zero); - BestSuggestedHeader = parentHeader; - } - - public Hash256 HeadHash => _head.Hash; - public Hash256 GenesisHash => _parentHeader.Hash; - public Hash256 PendingHash => null; - public Hash256 FinalizedHash => null; - public Hash256 SafeHash => null; - public Block Head => _head; - public ulong NetworkId => 1; - public ulong ChainId => 1; - public BlockHeader Genesis => _parentHeader; - public BlockHeader BestSuggestedHeader { get; set; } - public Block BestSuggestedBody => _head; - public BlockHeader BestSuggestedBeaconHeader => _parentHeader; - public BlockHeader LowestInsertedHeader { get; set; } - public BlockHeader LowestInsertedBeaconHeader { get; set; } - public long BestKnownNumber => _parentHeader.Number; - public long BestKnownBeaconNumber => _parentHeader.Number; - public bool CanAcceptNewBlocks => true; - public (long BlockNumber, Hash256 BlockHash) SyncPivot { get; set; } - public bool IsProcessingBlock { get; set; } - public long? BestPersistedState { get; set; } - - public event EventHandler NewBestSuggestedBlock { add { } remove { } } - public event EventHandler NewSuggestedBlock { add { } remove { } } - public event EventHandler BlockAddedToMain { add { } remove { } } - public event EventHandler NewHeadBlock { add { } remove { } } - public event EventHandler OnUpdateMainChain { add { } remove { } } - public event EventHandler OnForkChoiceUpdated { add { } remove { } } - - public Block FindBlock(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) => - blockHash == _parentHeader.Hash ? _head : null; - - public Block FindBlock(long blockNumber, BlockTreeLookupOptions options) => - blockNumber == _parentHeader.Number ? _head : null; - - public bool HasBlock(long blockNumber, Hash256 blockHash) => - blockNumber == _parentHeader.Number && blockHash == _parentHeader.Hash; - - public BlockHeader FindHeader(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) => - blockHash == _parentHeader.Hash ? _parentHeader : null; - - public BlockHeader FindHeader(long blockNumber, BlockTreeLookupOptions options) => - blockNumber == _parentHeader.Number ? _parentHeader : null; - - public Hash256 FindBlockHash(long blockNumber) => - blockNumber == _parentHeader.Number ? _parentHeader.Hash : null; - - public bool IsMainChain(BlockHeader blockHeader) => blockHeader?.Hash == _parentHeader.Hash; - - public bool IsMainChain(Hash256 blockHash, bool throwOnMissingHash = true) => blockHash == _parentHeader.Hash; - - public BlockHeader FindBestSuggestedHeader() => BestSuggestedHeader; - - public long GetLowestBlock() => _parentHeader.Number; - - public AddBlockResult Insert(BlockHeader header, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) => - AddBlockResult.Added; - - public void BulkInsertHeader(IReadOnlyList headers, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) { } - - public AddBlockResult Insert( - Block block, - BlockTreeInsertBlockOptions insertBlockOptions = BlockTreeInsertBlockOptions.None, - BlockTreeInsertHeaderOptions insertHeaderOptions = BlockTreeInsertHeaderOptions.None, - WriteFlags bodiesWriteFlags = WriteFlags.None) => - AddBlockResult.Added; - - public void UpdateHeadBlock(Hash256 blockHash) { } - - public void NewOldestBlock(long oldestBlock) { } - - public AddBlockResult SuggestBlock(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) => - AddBlockResult.Added; - - public ValueTask SuggestBlockAsync(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) => - ValueTask.FromResult(AddBlockResult.Added); - - public AddBlockResult SuggestHeader(BlockHeader header) => AddBlockResult.Added; - - public bool IsKnownBlock(long number, Hash256 blockHash) => - number == _parentHeader.Number && blockHash == _parentHeader.Hash; - - public bool IsKnownBeaconBlock(long number, Hash256 blockHash) => - number == _parentHeader.Number && blockHash == _parentHeader.Hash; - - public bool WasProcessed(long number, Hash256 blockHash) => - number == _parentHeader.Number && blockHash == _parentHeader.Hash; - - public void UpdateMainChain(IReadOnlyList blocks, bool wereProcessed, bool forceHeadBlock = false) { } - - public void MarkChainAsProcessed(IReadOnlyList blocks) { } - - public Task Accept(IBlockTreeVisitor blockTreeVisitor, CancellationToken cancellationToken) => Task.CompletedTask; - - public (BlockInfo Info, ChainLevelInfo Level) GetInfo(long number, Hash256 blockHash) => (null, null); - - public ChainLevelInfo FindLevel(long number) => null; - - public BlockInfo FindCanonicalBlockInfo(long blockNumber) => null; - - public Hash256 FindHash(long blockNumber) => blockNumber == _parentHeader.Number ? _parentHeader.Hash : null; - - public IOwnedReadOnlyList FindHeaders(Hash256 hash, int numberOfBlocks, int skip, bool reverse) => - new ArrayPoolList(0); - - public void DeleteInvalidBlock(Block invalidBlock) { } - - public void DeleteOldBlock(long blockNumber, Hash256 blockHash) { } - - public void ForkChoiceUpdated(Hash256 finalizedBlockHash, Hash256 safeBlockBlockHash) { } - - public int DeleteChainSlice(in long startNumber, long? endNumber = null, bool force = false) => 0; - - public bool IsBetterThanHead(BlockHeader header) => true; - - public void UpdateBeaconMainChain(BlockInfo[] blockInfos, long clearBeaconMainChainStartPoint) { } - - public void RecalculateTreeLevels() { } - } - -} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockOneBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockOneBenchmarks.cs deleted file mode 100644 index 575a44ea8892..000000000000 --- a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasBlockOneBenchmarks.cs +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using BenchmarkDotNet.Attributes; -using Nethermind.Blockchain; -using Nethermind.Blockchain.Receipts; -using Nethermind.Blockchain.Tracing; -using Nethermind.Config; -using Nethermind.Consensus.Processing; -using Nethermind.Core; -using Nethermind.Core.Specs; -using Nethermind.Evm.State; -using Nethermind.Evm.TransactionProcessing; -using Nethermind.Specs; -using Nethermind.Specs.Forks; - -namespace Nethermind.Evm.Benchmark.GasBenchmarks; - -/// -/// Benchmarks that replay gas-benchmark payload files via BlockProcessor.ProcessOne. -/// This measures block processing without the BranchProcessor overhead (state scope management, -/// pre-warming coordination, cache clearing, TxHashCalculator). -/// -[Config(typeof(GasBenchmarkConfig))] -public class GasBlockOneBenchmarks -{ - private IWorldState _state; - private BlockProcessor _blockProcessor; - private Block _testBlock; - private BlockHeader _preBlockHeader; - private IReleaseSpec _spec; - private ProcessingOptions _processingOptions; - - [ParamsSource(nameof(GetTestCases))] - public GasPayloadBenchmarks.TestCase Scenario { get; set; } - - public static IEnumerable GetTestCases() => GasPayloadBenchmarks.GetTestCases(); - - [GlobalSetup] - public void GlobalSetup() - { - IReleaseSpec pragueSpec = Prague.Instance; - ISpecProvider specProvider = new SingleReleaseSpecProvider(pragueSpec, 1, 1); - _spec = pragueSpec; - - PayloadLoader.EnsureGenesisInitialized(GasPayloadBenchmarks.s_genesisPath, pragueSpec); - - _state = PayloadLoader.CreateWorldState(); - _preBlockHeader = BlockBenchmarkHelper.CreateGenesisHeader(); - - TestBlockhashProvider blockhashProvider = new(); - ITransactionProcessor txProcessor = BlockBenchmarkHelper.CreateTransactionProcessor( - _state, blockhashProvider, specProvider); - - BlockBenchmarkHelper.ExecuteSetupPayload(_state, txProcessor, _preBlockHeader, Scenario, pragueSpec); - - ReceiptConfig receiptConfig = new(); - IReceiptStorage receiptStorage = BlockBenchmarkHelper.CreateReceiptStorage(receiptConfig); - _processingOptions = BlockBenchmarkHelper.GetImportProcessingOptions(receiptConfig); - - _blockProcessor = BlockBenchmarkHelper.CreateBlockProcessor(specProvider, txProcessor, _state, receiptStorage); - _testBlock = PayloadLoader.LoadBlock(Scenario.FilePath); - - // Warm up and verify correctness - using (IDisposable scope = _state.BeginScope(_preBlockHeader)) - { - (Block processedBlock, _) = _blockProcessor.ProcessOne( - _testBlock, - _processingOptions, - NullBlockTracer.Instance, _spec, default); - _state.CommitTree(_preBlockHeader.Number + 1); - PayloadLoader.VerifyProcessedBlock(processedBlock, Scenario.ToString(), Scenario.FilePath); - } - } - - [Benchmark] - public void ProcessBlock() - { - using IDisposable scope = _state.BeginScope(_preBlockHeader); - _blockProcessor.ProcessOne( - _testBlock, - _processingOptions, - NullBlockTracer.Instance, _spec, default); - _state.CommitTree(_preBlockHeader.Number + 1); - } - - [GlobalCleanup] - public void GlobalCleanup() - { - _state = null; - _blockProcessor = null; - _testBlock = null; - _processingOptions = ProcessingOptions.None; - } -} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasNewPayloadBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasNewPayloadBenchmarks.cs deleted file mode 100644 index 397c8ee91d44..000000000000 --- a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasNewPayloadBenchmarks.cs +++ /dev/null @@ -1,1187 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using BenchmarkDotNet.Attributes; -using Nethermind.Blockchain; -using Nethermind.Blockchain.BeaconBlockRoot; -using Nethermind.Blockchain.Receipts; -using Nethermind.Blockchain.Tracing; -using Nethermind.Blockchain.Visitors; -using Nethermind.Consensus; -using Nethermind.Consensus.Producers; -using Nethermind.Consensus.Processing; -using Nethermind.Consensus.Validators; -using Nethermind.Core; -using Nethermind.Core.Collections; -using Nethermind.Core.Crypto; -using Nethermind.Core.Specs; -using Nethermind.Crypto; -using Nethermind.Evm.State; -using Nethermind.Evm.Tracing; -using Nethermind.Evm.TransactionProcessing; -using Nethermind.Int256; -using Nethermind.JsonRpc; -using Nethermind.Logging; -using Nethermind.Merge.Plugin; -using Nethermind.Merge.Plugin.BlockProduction; -using Nethermind.Merge.Plugin.Data; -using Nethermind.Merge.Plugin.Handlers; -using Nethermind.Merge.Plugin.InvalidChainTracker; -using Nethermind.Merge.Plugin.Synchronization; -using Nethermind.Serialization.Json; -using Nethermind.Specs; -using Nethermind.Specs.Forks; -using Nethermind.Synchronization; - -namespace Nethermind.Evm.Benchmark.GasBenchmarks; - -/// -/// Benchmarks that replay payload files through NewPayloadHandler flow to mirror -/// production newPayload processing (request decode + payload validation + handler + queue processing). -/// -[Config(typeof(GasBenchmarkConfig))] -public class GasNewPayloadBenchmarks -{ - internal const string TimingFileEnvVar = "NETHERMIND_NEWPAYLOAD_REAL_TIMING_FILE"; - internal const string TimingReportFileEnvVar = "NETHERMIND_NEWPAYLOAD_REAL_TIMING_REPORT_FILE"; - private const int MaxConsoleScenarioCount = 10; - private static readonly JsonSerializerOptions s_jsonOptions = EthereumJsonSerializer.JsonOptions; - private static readonly EthereumEcdsa s_ecdsa = new(1); - private static readonly object s_timingFileLock = new(); - - private IReleaseSpec _releaseSpec; - private ISpecProvider _specProvider; - private IWorldState _state; - private BranchProcessor _branchProcessor; - private BlockHeader _preBlockHeader; - private byte[] _rawJsonBytes; - private BenchmarkNewPayloadBlockTree _blockTree; - private BenchmarkProcessingQueue _processingQueue; - private NewPayloadHandler _newPayloadHandler; - private TimedTransactionProcessor _timedTransactionProcessor; - private IBlockCachePreWarmer _preWarmer; - private IDisposable _preWarmerLifetime; - - private long _jsonParseTicks; - private long _payloadDeserializeTicks; - private long _optionalParamsTicks; - private long _validateForkTicks; - private long _validateParamsTicks; - private long _handlerTicks; - private long _queueTotalTicks; - private long _senderRecoveryTicks; - private long _blockProcessTicks; - private long _txExecutionTicks; - private int _iterationCount; - private int _txExecutionCount; - private readonly long[] _senderRecoveryByTypeTicks = new long[256]; - private readonly int[] _senderRecoveryByTypeCount = new int[256]; - private readonly long[] _txExecutionByTypeTicks = new long[256]; - private readonly int[] _txExecutionByTypeCount = new int[256]; - - [ParamsSource(nameof(GetTestCases))] - public GasPayloadBenchmarks.TestCase Scenario { get; set; } - - public static IEnumerable GetTestCases() => GasPayloadBenchmarks.GetTestCases(); - - [GlobalSetup] - public void GlobalSetup() - { - _releaseSpec = Prague.Instance; - _specProvider = new SingleReleaseSpecProvider(_releaseSpec, 1, 1); - - PayloadLoader.EnsureGenesisInitialized(GasPayloadBenchmarks.s_genesisPath, _releaseSpec); - - TestBlockhashProvider blockhashProvider = new(); - BlockBenchmarkHelper.BranchProcessingContext branchProcessingContext = BlockBenchmarkHelper.CreateBranchProcessingContext(_specProvider, blockhashProvider); - _state = branchProcessingContext.State; - _preWarmer = branchProcessingContext.PreWarmer; - _preWarmerLifetime = branchProcessingContext.PreWarmerLifetime; - _preBlockHeader = BlockBenchmarkHelper.CreateGenesisHeader(); - _preBlockHeader.TotalDifficulty = UInt256.Zero; - - ITransactionProcessor txProcessor = BlockBenchmarkHelper.CreateTransactionProcessor( - _state, - blockhashProvider, - _specProvider, - branchProcessingContext.PreBlockCaches, - branchProcessingContext.CachePrecompiles); - _timedTransactionProcessor = new TimedTransactionProcessor(txProcessor, this); - - BlockBenchmarkHelper.ExecuteSetupPayload(_state, _timedTransactionProcessor, _preBlockHeader, Scenario, _releaseSpec); - - ReceiptConfig receiptConfig = new(); - IReceiptStorage receiptStorage = BlockBenchmarkHelper.CreateReceiptStorage(receiptConfig); - BlockProcessor blockProcessor = BlockBenchmarkHelper.CreateBlockProcessor( - _specProvider, - _timedTransactionProcessor, - _state, - receiptStorage); - - _branchProcessor = new BranchProcessor( - blockProcessor, - _specProvider, - _state, - new BeaconBlockRootHandler(_timedTransactionProcessor, _state), - blockhashProvider, - LimboLogs.Instance, - _preWarmer); - - string rawJson = PayloadLoader.ReadRawJson(Scenario.FilePath); - _rawJsonBytes = Encoding.UTF8.GetBytes(rawJson); - ExecutionPayloadParams initialRequest = DeserializeRequest(collectTiming: false); - - _preBlockHeader.Hash = initialRequest.ExecutionPayload.ParentHash; - long parentNumber = (long)initialRequest.ExecutionPayload.BlockNumber - 1; - _preBlockHeader.Number = parentNumber >= 0 ? parentNumber : 0; - - _blockTree = new BenchmarkNewPayloadBlockTree(_preBlockHeader); - _processingQueue = new BenchmarkProcessingQueue( - _branchProcessor, - _preBlockHeader, - _specProvider, - this); - - MergeConfig mergeConfig = new() - { - // Benchmark iterations replay the same block hash, so cache must be disabled. - NewPayloadCacheSize = 0, - SimulateBlockProduction = false, - }; - - _newPayloadHandler = new NewPayloadHandler( - new NoopPayloadPreparationService(), - Always.Valid, - _blockTree, - AlwaysPoS.Instance, - Nethermind.Synchronization.No.BeaconSync, - new StaticBeaconPivot(_preBlockHeader), - new BlockCacheService(), - _processingQueue, - new NoopInvalidChainTracker(), - new NoopMergeSyncController(), - mergeConfig, - receiptConfig, - BlockBenchmarkHelper.CreateStateReader(_state), - LimboLogs.Instance); - - ExecuteNewPayload(initialRequest, collectTiming: false); - - Block processedBlock = _processingQueue.LastProcessedBlock; - if (processedBlock is null) - { - throw new InvalidOperationException("Warmup did not produce a processed block."); - } - - PayloadLoader.VerifyProcessedBlock(processedBlock, Scenario.ToString(), Scenario.FilePath); - ResetTimingAccumulators(); - } - - [Benchmark] - public void ProcessBlock() - { - ExecutionPayloadParams request = DeserializeRequest(collectTiming: true); - ExecuteNewPayload(request, collectTiming: true); - _iterationCount++; - } - - private void ExecuteNewPayload(ExecutionPayloadParams request, bool collectTiming) - { - ExecutionPayload executionPayload = request.ExecutionPayload; - long start = Stopwatch.GetTimestamp(); - if (!executionPayload.ValidateFork(_specProvider)) - { - throw new InvalidOperationException("Payload fork validation failed."); - } - if (collectTiming) - { - _validateForkTicks += Stopwatch.GetTimestamp() - start; - } - - start = Stopwatch.GetTimestamp(); - IReleaseSpec releaseSpec = _specProvider.GetSpec(executionPayload.BlockNumber, executionPayload.Timestamp); - Nethermind.Merge.Plugin.Data.ValidationResult validationResult = request.ValidateParams(releaseSpec, EngineApiVersions.Prague, out string validationError); - if (validationResult != Nethermind.Merge.Plugin.Data.ValidationResult.Success) - { - throw new InvalidOperationException($"Payload parameter validation failed: {validationError}"); - } - if (collectTiming) - { - _validateParamsTicks += Stopwatch.GetTimestamp() - start; - } - - _processingQueue.CollectTiming = collectTiming; - start = Stopwatch.GetTimestamp(); - ResultWrapper result = _newPayloadHandler.HandleAsync(executionPayload).GetAwaiter().GetResult(); - if (collectTiming) - { - _handlerTicks += Stopwatch.GetTimestamp() - start; - } - if (result.ErrorCode != ErrorCodes.None) - { - throw new InvalidOperationException($"NewPayload handler returned error code {result.ErrorCode} ({result.Result.Error})"); - } - - if (result.Data is null || result.Data.Status != PayloadStatus.Valid) - { - string status = result.Data is null ? "" : result.Data.Status; - string error = result.Data is null ? "" : result.Data.ValidationError; - throw new InvalidOperationException($"NewPayload handler returned status {status}. {error}"); - } - } - - private ExecutionPayloadParams DeserializeRequest(bool collectTiming) - { - long start = Stopwatch.GetTimestamp(); - using JsonDocument doc = JsonDocument.Parse(_rawJsonBytes); - JsonElement paramsArray = doc.RootElement.GetProperty("params"); - if (collectTiming) - { - _jsonParseTicks += Stopwatch.GetTimestamp() - start; - } - - start = Stopwatch.GetTimestamp(); - ExecutionPayloadV3 payload = JsonSerializer.Deserialize( - paramsArray[0].GetRawText(), - s_jsonOptions); - if (collectTiming) - { - _payloadDeserializeTicks += Stopwatch.GetTimestamp() - start; - } - - start = Stopwatch.GetTimestamp(); - byte[][] blobVersionedHashes = Array.Empty(); - if (paramsArray.GetArrayLength() > 1 && paramsArray[1].ValueKind == JsonValueKind.Array) - { - blobVersionedHashes = JsonSerializer.Deserialize( - paramsArray[1].GetRawText(), - s_jsonOptions); - if (blobVersionedHashes is null) - { - blobVersionedHashes = Array.Empty(); - } - } - - Hash256 parentBeaconBlockRoot = null; - if (paramsArray.GetArrayLength() > 2 && paramsArray[2].ValueKind == JsonValueKind.String) - { - parentBeaconBlockRoot = JsonSerializer.Deserialize( - paramsArray[2].GetRawText(), - s_jsonOptions); - payload.ParentBeaconBlockRoot = parentBeaconBlockRoot; - } - - byte[][] executionRequests = null; - if (paramsArray.GetArrayLength() > 3 && paramsArray[3].ValueKind == JsonValueKind.Array) - { - executionRequests = JsonSerializer.Deserialize( - paramsArray[3].GetRawText(), - s_jsonOptions); - payload.ExecutionRequests = executionRequests; - } - if (collectTiming) - { - _optionalParamsTicks += Stopwatch.GetTimestamp() - start; - } - - return new ExecutionPayloadParams( - payload, - blobVersionedHashes, - parentBeaconBlockRoot, - executionRequests); - } - - private void AddQueueTiming(Block block, long senderRecoveryTicks, long blockProcessTicks, long queueTotalTicks) - { - _senderRecoveryTicks += senderRecoveryTicks; - _blockProcessTicks += blockProcessTicks; - _queueTotalTicks += queueTotalTicks; - AddSenderRecoveryTypeBreakdown(block, senderRecoveryTicks); - } - - private void AddSenderRecoveryTypeBreakdown(Block block, long elapsedTicks) - { - int txCount = block.Transactions.Length; - if (txCount == 0) - { - return; - } - - int[] countsPerType = new int[_senderRecoveryByTypeCount.Length]; - int firstTypeIndex = -1; - for (int i = 0; i < txCount; i++) - { - int txTypeIndex = (int)block.Transactions[i].Type; - if (firstTypeIndex == -1) - { - firstTypeIndex = txTypeIndex; - } - - countsPerType[txTypeIndex]++; - _senderRecoveryByTypeCount[txTypeIndex]++; - } - - long allocatedTicks = 0; - for (int txTypeIndex = 0; txTypeIndex < countsPerType.Length; txTypeIndex++) - { - int count = countsPerType[txTypeIndex]; - if (count == 0) - { - continue; - } - - long typeTicks = elapsedTicks * count / txCount; - _senderRecoveryByTypeTicks[txTypeIndex] += typeTicks; - allocatedTicks += typeTicks; - } - - if (firstTypeIndex >= 0 && allocatedTicks != elapsedTicks) - { - _senderRecoveryByTypeTicks[firstTypeIndex] += elapsedTicks - allocatedTicks; - } - } - - private void AddTxExecutionTiming(TxType txType, long elapsedTicks) - { - _txExecutionTicks += elapsedTicks; - _txExecutionCount++; - int txTypeIndex = (int)txType; - _txExecutionByTypeTicks[txTypeIndex] += elapsedTicks; - _txExecutionByTypeCount[txTypeIndex]++; - } - - private void ResetTimingAccumulators() - { - _jsonParseTicks = 0; - _payloadDeserializeTicks = 0; - _optionalParamsTicks = 0; - _validateForkTicks = 0; - _validateParamsTicks = 0; - _handlerTicks = 0; - _queueTotalTicks = 0; - _senderRecoveryTicks = 0; - _blockProcessTicks = 0; - _txExecutionTicks = 0; - _iterationCount = 0; - _txExecutionCount = 0; - Array.Clear(_senderRecoveryByTypeTicks, 0, _senderRecoveryByTypeTicks.Length); - Array.Clear(_senderRecoveryByTypeCount, 0, _senderRecoveryByTypeCount.Length); - Array.Clear(_txExecutionByTypeTicks, 0, _txExecutionByTypeTicks.Length); - Array.Clear(_txExecutionByTypeCount, 0, _txExecutionByTypeCount.Length); - } - - [GlobalCleanup] - public void GlobalCleanup() - { - if (_iterationCount > 0) - { - TimingBreakdownSummary summary = new( - Scenario?.ToString() ?? "", - _iterationCount, - _jsonParseTicks, - _payloadDeserializeTicks, - _optionalParamsTicks, - _validateForkTicks, - _validateParamsTicks, - _handlerTicks, - _queueTotalTicks, - _senderRecoveryTicks, - _blockProcessTicks, - _txExecutionTicks, - _txExecutionCount, - (long[])_senderRecoveryByTypeTicks.Clone(), - (int[])_senderRecoveryByTypeCount.Clone(), - (long[])_txExecutionByTypeTicks.Clone(), - (int[])_txExecutionByTypeCount.Clone()); - AppendSummaryToFile(summary); - } - - _newPayloadHandler?.Dispose(); - _preWarmer?.ClearCaches(); - _preWarmerLifetime?.Dispose(); - _newPayloadHandler = null; - _processingQueue = null; - _blockTree = null; - _rawJsonBytes = null; - _branchProcessor = null; - _state = null; - _timedTransactionProcessor = null; - _preWarmer = null; - _preWarmerLifetime = null; - } - - public static void PrintFinalTimingBreakdown() - { - string timingFilePath = Environment.GetEnvironmentVariable(TimingFileEnvVar); - if (string.IsNullOrWhiteSpace(timingFilePath) || !File.Exists(timingFilePath)) - { - return; - } - - string[] lines = File.ReadAllLines(timingFilePath); - List summaries = new(lines.Length); - for (int i = 0; i < lines.Length; i++) - { - string line = lines[i]; - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - - TimingBreakdownSummary summary = JsonSerializer.Deserialize(line, s_jsonOptions); - if (summary is not null) - { - summaries.Add(summary); - } - } - - if (summaries.Count == 0) - { - return; - } - - File.Delete(timingFilePath); - - string reportFilePath = ResolveTimingReportFilePath(); - string reportDirectory = Path.GetDirectoryName(reportFilePath); - if (!string.IsNullOrWhiteSpace(reportDirectory)) - { - Directory.CreateDirectory(reportDirectory); - } - - using (StreamWriter reportWriter = new(reportFilePath, false)) - { - WriteReport(reportWriter, summaries); - } - - if (summaries.Count <= MaxConsoleScenarioCount) - { - Console.WriteLine(); - WriteReport(Console.Out, summaries); - Console.WriteLine($"NewPayload timing breakdown saved to: {reportFilePath}"); - } - else - { - Console.WriteLine($"NewPayload timing breakdown saved to: {reportFilePath} (scenario count: {summaries.Count}, console output suppressed for runs with more than {MaxConsoleScenarioCount} scenarios)"); - } - } - - private static void WriteReport(TextWriter writer, List summaries) - { - writer.WriteLine("=== NewPayload timing breakdown (final) ==="); - long totalJsonParseTicks = 0; - long totalPayloadDeserializeTicks = 0; - long totalOptionalParamsTicks = 0; - long totalValidateForkTicks = 0; - long totalValidateParamsTicks = 0; - long totalHandlerTicks = 0; - long totalQueueTotalTicks = 0; - long totalSenderRecoveryTicks = 0; - long totalBlockProcessTicks = 0; - long totalTxExecutionTicks = 0; - int totalIterations = 0; - int totalTxExecutionCount = 0; - long[] totalSenderRecoveryByTypeTicks = new long[256]; - int[] totalSenderRecoveryByTypeCount = new int[256]; - long[] totalTxExecutionByTypeTicks = new long[256]; - int[] totalTxExecutionByTypeCount = new int[256]; - - for (int i = 0; i < summaries.Count; i++) - { - TimingBreakdownSummary summary = summaries[i]; - PrintSummary( - writer, - summary.Name, - summary.Iterations, - summary.JsonParseTicks, - summary.PayloadDeserializeTicks, - summary.OptionalParamsTicks, - summary.ValidateForkTicks, - summary.ValidateParamsTicks, - summary.HandlerTicks, - summary.QueueTotalTicks, - summary.SenderRecoveryTicks, - summary.BlockProcessTicks, - summary.TxExecutionTicks, - summary.TxExecutionCount, - summary.SenderRecoveryByTypeTicks, - summary.SenderRecoveryByTypeCount, - summary.TxExecutionByTypeTicks, - summary.TxExecutionByTypeCount); - - totalJsonParseTicks += summary.JsonParseTicks; - totalPayloadDeserializeTicks += summary.PayloadDeserializeTicks; - totalOptionalParamsTicks += summary.OptionalParamsTicks; - totalValidateForkTicks += summary.ValidateForkTicks; - totalValidateParamsTicks += summary.ValidateParamsTicks; - totalHandlerTicks += summary.HandlerTicks; - totalQueueTotalTicks += summary.QueueTotalTicks; - totalSenderRecoveryTicks += summary.SenderRecoveryTicks; - totalBlockProcessTicks += summary.BlockProcessTicks; - totalTxExecutionTicks += summary.TxExecutionTicks; - totalIterations += summary.Iterations; - totalTxExecutionCount += summary.TxExecutionCount; - AddTypeBreakdown(totalSenderRecoveryByTypeTicks, summary.SenderRecoveryByTypeTicks); - AddTypeBreakdown(totalSenderRecoveryByTypeCount, summary.SenderRecoveryByTypeCount); - AddTypeBreakdown(totalTxExecutionByTypeTicks, summary.TxExecutionByTypeTicks); - AddTypeBreakdown(totalTxExecutionByTypeCount, summary.TxExecutionByTypeCount); - } - - if (summaries.Count > 1 && totalIterations > 0) - { - PrintSummary( - writer, - "ALL SCENARIOS", - totalIterations, - totalJsonParseTicks, - totalPayloadDeserializeTicks, - totalOptionalParamsTicks, - totalValidateForkTicks, - totalValidateParamsTicks, - totalHandlerTicks, - totalQueueTotalTicks, - totalSenderRecoveryTicks, - totalBlockProcessTicks, - totalTxExecutionTicks, - totalTxExecutionCount, - totalSenderRecoveryByTypeTicks, - totalSenderRecoveryByTypeCount, - totalTxExecutionByTypeTicks, - totalTxExecutionByTypeCount); - } - } - - private static string ResolveTimingReportFilePath() - { - string configuredPath = Environment.GetEnvironmentVariable(TimingReportFileEnvVar); - if (!string.IsNullOrWhiteSpace(configuredPath)) - { - return configuredPath; - } - - return Path.Combine( - Path.GetTempPath(), - $"nethermind-newpayload-real-timing-breakdown-{DateTime.UtcNow:yyyyMMdd-HHmmss}-{Guid.NewGuid():N}.txt"); - } - - private static void AppendSummaryToFile(TimingBreakdownSummary summary) - { - string timingFilePath = Environment.GetEnvironmentVariable(TimingFileEnvVar); - if (string.IsNullOrWhiteSpace(timingFilePath)) - { - return; - } - - string line = JsonSerializer.Serialize(summary, s_jsonOptions); - lock (s_timingFileLock) - { - File.AppendAllText(timingFilePath, line + Environment.NewLine); - } - } - - private static void PrintSummary( - TextWriter writer, - string name, - int iterations, - long jsonParseTicks, - long payloadDeserializeTicks, - long optionalParamsTicks, - long validateForkTicks, - long validateParamsTicks, - long handlerTicks, - long queueTotalTicks, - long senderRecoveryTicks, - long blockProcessTicks, - long txExecutionTicks, - int txExecutionCount, - long[] senderRecoveryByTypeTicks, - int[] senderRecoveryByTypeCount, - long[] txExecutionByTypeTicks, - int[] txExecutionByTypeCount) - { - double jsonParseMs = TicksToMs(jsonParseTicks); - double payloadDeserializeMs = TicksToMs(payloadDeserializeTicks); - double optionalParamsMs = TicksToMs(optionalParamsTicks); - double validateForkMs = TicksToMs(validateForkTicks); - double validateParamsMs = TicksToMs(validateParamsTicks); - double handlerMs = TicksToMs(handlerTicks); - double queueTotalMs = TicksToMs(queueTotalTicks); - double senderRecoveryMs = TicksToMs(senderRecoveryTicks); - double blockProcessMs = TicksToMs(blockProcessTicks); - double txExecutionMs = TicksToMs(txExecutionTicks); - double handlerNonQueueMs = handlerMs - queueTotalMs; - if (handlerNonQueueMs < 0) - { - handlerNonQueueMs = 0; - } - - double queueNonStagedMs = queueTotalMs - senderRecoveryMs - blockProcessMs; - if (queueNonStagedMs < 0) - { - queueNonStagedMs = 0; - } - - double nonTxBlockMs = blockProcessMs - txExecutionMs; - if (nonTxBlockMs < 0) - { - nonTxBlockMs = 0; - } - - int senderRecoveryCount = GetTotalCount(senderRecoveryByTypeCount); - double totalMs = jsonParseMs + payloadDeserializeMs + optionalParamsMs + validateForkMs + validateParamsMs + handlerMs; - - writer.WriteLine($"--- {name} ({iterations} iterations) ---"); - PrintLine(writer, "JSON parse", jsonParseMs, totalMs, iterations); - PrintLine(writer, "Payload deserialize", payloadDeserializeMs, totalMs, iterations); - PrintLine(writer, "Optional params", optionalParamsMs, totalMs, iterations); - PrintLine(writer, "Validate fork", validateForkMs, totalMs, iterations); - PrintLine(writer, "Validate params", validateParamsMs, totalMs, iterations); - PrintLine(writer, "Handler total", handlerMs, totalMs, iterations); - writer.WriteLine($" {"Total",-20} {totalMs,9:F3} ms avg {totalMs / iterations:F3} ms/iter"); - writer.WriteLine(" Handler detail:"); - writer.WriteLine($" {"Queue total",-18} {queueTotalMs,9:F3} ms ({GetShare(queueTotalMs, handlerMs),5:F1}% of handler)"); - writer.WriteLine($" {"Handler non-queue",-18} {handlerNonQueueMs,9:F3} ms ({GetShare(handlerNonQueueMs, handlerMs),5:F1}% of handler)"); - writer.WriteLine(" Queue detail:"); - writer.WriteLine($" {"Sender recovery",-18} {senderRecoveryMs,9:F3} ms ({GetShare(senderRecoveryMs, queueTotalMs),5:F1}% of queue) avg {GetAverage(senderRecoveryMs, senderRecoveryCount):F3} ms/tx"); - writer.WriteLine($" {"Block processing",-18} {blockProcessMs,9:F3} ms ({GetShare(blockProcessMs, queueTotalMs),5:F1}% of queue)"); - writer.WriteLine($" {"Queue non-staged",-18} {queueNonStagedMs,9:F3} ms ({GetShare(queueNonStagedMs, queueTotalMs),5:F1}% of queue)"); - writer.WriteLine(" Block processing detail:"); - writer.WriteLine($" {"Tx execution",-18} {txExecutionMs,9:F3} ms ({GetShare(txExecutionMs, blockProcessMs),5:F1}% of block) avg {GetAverage(txExecutionMs, txExecutionCount):F3} ms/tx"); - writer.WriteLine($" {"Non-tx overhead",-18} {nonTxBlockMs,9:F3} ms ({GetShare(nonTxBlockMs, blockProcessMs),5:F1}% of block)"); - PrintTxTypeBreakdown(writer, "Tx execution by type", txExecutionByTypeTicks, txExecutionByTypeCount, txExecutionMs); - writer.WriteLine(" Sender recovery detail:"); - writer.WriteLine($" {"Recovered txs",-18} {senderRecoveryCount,9} tx"); - writer.WriteLine($" {"Avg per tx",-18} {GetAverage(senderRecoveryMs, senderRecoveryCount),9:F3} ms/tx"); - PrintTxTypeBreakdown(writer, "Sender recovery by type", senderRecoveryByTypeTicks, senderRecoveryByTypeCount, senderRecoveryMs); - } - - private static void PrintLine(TextWriter writer, string label, double stageMs, double totalMs, int iterations) - { - double share = totalMs > 0 ? (100.0 * stageMs / totalMs) : 0.0; - double avg = iterations > 0 ? (stageMs / iterations) : 0.0; - writer.WriteLine($" {label,-20} {stageMs,9:F3} ms ({share,5:F1}%) avg {avg:F3} ms/iter"); - } - - private static void PrintTxTypeBreakdown(TextWriter writer, string title, long[] ticksByType, int[] countByType, double stageTotalMs) - { - writer.WriteLine($" {title}:"); - for (int txTypeIndex = 0; txTypeIndex < countByType.Length; txTypeIndex++) - { - int count = countByType[txTypeIndex]; - if (count == 0) - { - continue; - } - - double ms = TicksToMs(ticksByType[txTypeIndex]); - double share = GetShare(ms, stageTotalMs); - double avg = GetAverage(ms, count); - writer.WriteLine($" {FormatTxType((TxType)txTypeIndex),-16} {ms,9:F3} ms ({share,5:F1}%) avg {avg:F3} ms/tx count {count}"); - } - } - - private static string FormatTxType(TxType txType) - { - return txType switch - { - TxType.Legacy => "Legacy", - TxType.AccessList => "AccessList", - TxType.EIP1559 => "EIP1559", - TxType.Blob => "Blob", - TxType.SetCode => "SetCode", - TxType.DepositTx => "DepositTx", - _ => $"Type(0x{(byte)txType:X2})", - }; - } - - private static void AddTypeBreakdown(long[] destination, long[] source) - { - int length = destination.Length < source.Length ? destination.Length : source.Length; - for (int i = 0; i < length; i++) - { - destination[i] += source[i]; - } - } - - private static void AddTypeBreakdown(int[] destination, int[] source) - { - int length = destination.Length < source.Length ? destination.Length : source.Length; - for (int i = 0; i < length; i++) - { - destination[i] += source[i]; - } - } - - private static int GetTotalCount(int[] countByType) - { - int total = 0; - for (int i = 0; i < countByType.Length; i++) - { - total += countByType[i]; - } - - return total; - } - - private static double GetShare(double part, double whole) - { - return whole > 0 ? (100.0 * part / whole) : 0.0; - } - - private static double GetAverage(double totalMs, int count) - { - return count > 0 ? (totalMs / count) : 0.0; - } - - private static double TicksToMs(long ticks) => ticks * 1000.0 / Stopwatch.Frequency; - - private sealed class BenchmarkProcessingQueue : IBlockProcessingQueue - { - private readonly BranchProcessor _branchProcessor; - private readonly BlockHeader _parentHeader; - private readonly RecoverSignatures _recoverSignatures; - private readonly GasNewPayloadBenchmarks _owner; - private int _count; - - public BenchmarkProcessingQueue( - BranchProcessor branchProcessor, - BlockHeader parentHeader, - ISpecProvider specProvider, - GasNewPayloadBenchmarks owner) - { - _branchProcessor = branchProcessor; - _parentHeader = parentHeader; - _recoverSignatures = new RecoverSignatures(s_ecdsa, specProvider, LimboLogs.Instance); - _owner = owner; - } - - public bool CollectTiming { get; set; } - - public Block LastProcessedBlock { get; private set; } - - public event EventHandler ProcessingQueueEmpty; - public event EventHandler BlockAdded; - public event EventHandler BlockRemoved; - - public int Count => _count; - - public ValueTask Enqueue(Block block, ProcessingOptions processingOptions) - { - Interlocked.Increment(ref _count); - long queueStart = 0; - long senderRecoveryTicks = 0; - long blockProcessTicks = 0; - try - { - BlockAdded?.Invoke(this, new BlockEventArgs(block)); - if (CollectTiming) - { - queueStart = Stopwatch.GetTimestamp(); - long senderStart = Stopwatch.GetTimestamp(); - _recoverSignatures.RecoverData(block); - senderRecoveryTicks = Stopwatch.GetTimestamp() - senderStart; - - long processStart = Stopwatch.GetTimestamp(); - Block[] processedWithTiming = _branchProcessor.Process( - _parentHeader, - [block], - processingOptions, - NullBlockTracer.Instance); - blockProcessTicks = Stopwatch.GetTimestamp() - processStart; - LastProcessedBlock = processedWithTiming[0]; - } - else - { - _recoverSignatures.RecoverData(block); - Block[] processed = _branchProcessor.Process( - _parentHeader, - [block], - processingOptions, - NullBlockTracer.Instance); - LastProcessedBlock = processed[0]; - } - - Hash256 blockHash = block.GetOrCalculateHash(); - BlockRemoved?.Invoke(this, new BlockRemovedEventArgs(blockHash, ProcessingResult.Success)); - } - catch (Exception ex) - { - Hash256 blockHash = block.GetOrCalculateHash(); - BlockRemoved?.Invoke(this, new BlockRemovedEventArgs(blockHash, ProcessingResult.Exception, ex)); - throw; - } - finally - { - if (CollectTiming && queueStart != 0) - { - long queueTotalTicks = Stopwatch.GetTimestamp() - queueStart; - _owner.AddQueueTiming(block, senderRecoveryTicks, blockProcessTicks, queueTotalTicks); - } - - if (Interlocked.Decrement(ref _count) == 0) - { - ProcessingQueueEmpty?.Invoke(this, EventArgs.Empty); - } - } - - return ValueTask.CompletedTask; - } - } - - private sealed class TimedTransactionProcessor(ITransactionProcessor inner, GasNewPayloadBenchmarks owner) : ITransactionProcessor - { - public TransactionResult Execute(Transaction transaction, ITxTracer txTracer) - { - long start = Stopwatch.GetTimestamp(); - TransactionResult result = inner.Execute(transaction, txTracer); - long elapsedTicks = Stopwatch.GetTimestamp() - start; - owner.AddTxExecutionTiming(transaction.Type, elapsedTicks); - return result; - } - - public TransactionResult CallAndRestore(Transaction transaction, ITxTracer txTracer) => - inner.CallAndRestore(transaction, txTracer); - - public TransactionResult BuildUp(Transaction transaction, ITxTracer txTracer) => - inner.BuildUp(transaction, txTracer); - - public TransactionResult Trace(Transaction transaction, ITxTracer txTracer) => - inner.Trace(transaction, txTracer); - - public TransactionResult Warmup(Transaction transaction, ITxTracer txTracer) => - inner.Warmup(transaction, txTracer); - - public void SetBlockExecutionContext(BlockHeader blockHeader) => - inner.SetBlockExecutionContext(blockHeader); - - public void SetBlockExecutionContext(in BlockExecutionContext blockExecutionContext) => - inner.SetBlockExecutionContext(in blockExecutionContext); - } - - private sealed record TimingBreakdownSummary - { - public TimingBreakdownSummary( - string name, - int iterations, - long jsonParseTicks, - long payloadDeserializeTicks, - long optionalParamsTicks, - long validateForkTicks, - long validateParamsTicks, - long handlerTicks, - long queueTotalTicks, - long senderRecoveryTicks, - long blockProcessTicks, - long txExecutionTicks, - int txExecutionCount, - long[] senderRecoveryByTypeTicks, - int[] senderRecoveryByTypeCount, - long[] txExecutionByTypeTicks, - int[] txExecutionByTypeCount) - { - Name = name; - Iterations = iterations; - JsonParseTicks = jsonParseTicks; - PayloadDeserializeTicks = payloadDeserializeTicks; - OptionalParamsTicks = optionalParamsTicks; - ValidateForkTicks = validateForkTicks; - ValidateParamsTicks = validateParamsTicks; - HandlerTicks = handlerTicks; - QueueTotalTicks = queueTotalTicks; - SenderRecoveryTicks = senderRecoveryTicks; - BlockProcessTicks = blockProcessTicks; - TxExecutionTicks = txExecutionTicks; - TxExecutionCount = txExecutionCount; - SenderRecoveryByTypeTicks = senderRecoveryByTypeTicks; - SenderRecoveryByTypeCount = senderRecoveryByTypeCount; - TxExecutionByTypeTicks = txExecutionByTypeTicks; - TxExecutionByTypeCount = txExecutionByTypeCount; - } - - public string Name { get; init; } - public int Iterations { get; init; } - public long JsonParseTicks { get; init; } - public long PayloadDeserializeTicks { get; init; } - public long OptionalParamsTicks { get; init; } - public long ValidateForkTicks { get; init; } - public long ValidateParamsTicks { get; init; } - public long HandlerTicks { get; init; } - public long QueueTotalTicks { get; init; } - public long SenderRecoveryTicks { get; init; } - public long BlockProcessTicks { get; init; } - public long TxExecutionTicks { get; init; } - public int TxExecutionCount { get; init; } - public long[] SenderRecoveryByTypeTicks { get; init; } - public int[] SenderRecoveryByTypeCount { get; init; } - public long[] TxExecutionByTypeTicks { get; init; } - public int[] TxExecutionByTypeCount { get; init; } - } - - private sealed class BenchmarkNewPayloadBlockTree : IBlockTree - { - private readonly BlockHeader _parentHeader; - private readonly Block _head; - private readonly BlockInfo _parentInfo; - - public BenchmarkNewPayloadBlockTree(BlockHeader parentHeader) - { - _parentHeader = parentHeader; - _head = new Block(parentHeader, new BlockBody(Array.Empty(), Array.Empty(), null)); - _parentInfo = new BlockInfo(parentHeader.Hash, parentHeader.TotalDifficulty ?? UInt256.Zero) - { - WasProcessed = true, - BlockNumber = parentHeader.Number, - }; - BestSuggestedHeader = parentHeader; - SyncPivot = (0, Keccak.Zero); - } - - public Block Head => _head; - public BlockHeader BestSuggestedHeader { get; set; } - - public event EventHandler BlockAddedToMain { add { } remove { } } - public event EventHandler NewBestSuggestedBlock { add { } remove { } } - public event EventHandler NewSuggestedBlock { add { } remove { } } - public event EventHandler NewHeadBlock { add { } remove { } } - public event EventHandler OnUpdateMainChain { add { } remove { } } - public event EventHandler OnForkChoiceUpdated { add { } remove { } } - - public BlockHeader FindBestSuggestedHeader() => BestSuggestedHeader; - - public Hash256 HeadHash => _head.Hash; - public Hash256 GenesisHash => Keccak.Zero; - public Hash256 PendingHash => null; - public Hash256 FinalizedHash => null; - public Hash256 SafeHash => null; - public long? BestPersistedState { get; set; } - - public Block FindBlock(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) - { - if (blockHash == _parentHeader.Hash) - { - return _head; - } - - return null; - } - - public Block FindBlock(long blockNumber, BlockTreeLookupOptions options) - { - if (blockNumber == _parentHeader.Number) - { - return _head; - } - - return null; - } - - public bool HasBlock(long blockNumber, Hash256 blockHash) => - blockNumber == _parentHeader.Number && blockHash == _parentHeader.Hash; - - public BlockHeader FindHeader(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) - { - if (blockHash == _parentHeader.Hash) - { - return _parentHeader; - } - - return null; - } - - public BlockHeader FindHeader(long blockNumber, BlockTreeLookupOptions options) - { - if (blockNumber == _parentHeader.Number) - { - return _parentHeader; - } - - return null; - } - - public Hash256 FindBlockHash(long blockNumber) - { - if (blockNumber == _parentHeader.Number) - { - return _parentHeader.Hash; - } - - return null; - } - - public bool IsMainChain(BlockHeader blockHeader) => blockHeader?.Hash == _parentHeader.Hash; - - public bool IsMainChain(Hash256 blockHash, bool throwOnMissingHash = true) => blockHash == _parentHeader.Hash; - - public long GetLowestBlock() => _parentHeader.Number; - - public ulong NetworkId => 1; - public ulong ChainId => 1; - public BlockHeader Genesis => _parentHeader; - public Block BestSuggestedBody => _head; - public BlockHeader BestSuggestedBeaconHeader => _parentHeader; - public BlockHeader LowestInsertedHeader { get; set; } - public BlockHeader LowestInsertedBeaconHeader { get; set; } - public long BestKnownNumber => _parentHeader.Number; - public long BestKnownBeaconNumber => _parentHeader.Number; - public bool CanAcceptNewBlocks => true; - public (long BlockNumber, Hash256 BlockHash) SyncPivot { get; set; } - public bool IsProcessingBlock { get; set; } - - public AddBlockResult Insert(BlockHeader header, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) - => AddBlockResult.Added; - - public void BulkInsertHeader(IReadOnlyList headers, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) { } - - public AddBlockResult Insert( - Block block, - BlockTreeInsertBlockOptions insertBlockOptions = BlockTreeInsertBlockOptions.None, - BlockTreeInsertHeaderOptions insertHeaderOptions = BlockTreeInsertHeaderOptions.None, - WriteFlags bodiesWriteFlags = WriteFlags.None) - => AddBlockResult.Added; - - public void UpdateHeadBlock(Hash256 blockHash) { } - - public void NewOldestBlock(long oldestBlock) { } - - public AddBlockResult SuggestBlock(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) - => AddBlockResult.Added; - - public ValueTask SuggestBlockAsync(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) - => ValueTask.FromResult(AddBlockResult.Added); - - public AddBlockResult SuggestHeader(BlockHeader header) => AddBlockResult.Added; - - public bool IsKnownBlock(long number, Hash256 blockHash) => - number == _parentHeader.Number && blockHash == _parentHeader.Hash; - - public bool IsKnownBeaconBlock(long number, Hash256 blockHash) => - number == _parentHeader.Number && blockHash == _parentHeader.Hash; - - public bool WasProcessed(long number, Hash256 blockHash) => - number == _parentHeader.Number && blockHash == _parentHeader.Hash; - - public void UpdateMainChain(IReadOnlyList blocks, bool wereProcessed, bool forceHeadBlock = false) { } - - public void MarkChainAsProcessed(IReadOnlyList blocks) { } - - public Task Accept(IBlockTreeVisitor blockTreeVisitor, CancellationToken cancellationToken) => Task.CompletedTask; - - public (BlockInfo Info, ChainLevelInfo Level) GetInfo(long number, Hash256 blockHash) - { - if (number == _parentHeader.Number && blockHash == _parentHeader.Hash) - { - return (_parentInfo, null); - } - - return (null, null); - } - - public ChainLevelInfo FindLevel(long number) => null; - - public BlockInfo FindCanonicalBlockInfo(long blockNumber) => _parentInfo; - - public Hash256 FindHash(long blockNumber) - { - if (blockNumber == _parentHeader.Number) - { - return _parentHeader.Hash; - } - - return null; - } - - public IOwnedReadOnlyList FindHeaders(Hash256 hash, int numberOfBlocks, int skip, bool reverse) - => new ArrayPoolList(0); - - public void DeleteInvalidBlock(Block invalidBlock) { } - - public void DeleteOldBlock(long blockNumber, Hash256 blockHash) { } - - public void ForkChoiceUpdated(Hash256 finalizedBlockHash, Hash256 safeBlockBlockHash) { } - - public int DeleteChainSlice(in long startNumber, long? endNumber = null, bool force = false) => 0; - - public bool IsBetterThanHead(BlockHeader header) => false; - - public void UpdateBeaconMainChain(BlockInfo[] blockInfos, long clearBeaconMainChainStartPoint) { } - - public void RecalculateTreeLevels() { } - } - -#nullable enable - private sealed class NoopPayloadPreparationService : IPayloadPreparationService - { - public string? StartPreparingPayload(BlockHeader parentHeader, PayloadAttributes payloadAttributes) => null; - - public ValueTask GetPayload(string payloadId, bool skipCancel = false) - => ValueTask.FromResult(null); - - public void CancelBlockProduction(string payloadId) { } - } -#nullable disable - - private sealed class NoopMergeSyncController : IMergeSyncController - { - public void StopSyncing() { } - - public bool TryInitBeaconHeaderSync(BlockHeader blockHeader) => false; - - public void StopBeaconModeControl() { } - } - - private sealed class NoopInvalidChainTracker : IInvalidChainTracker - { - public void SetChildParent(Hash256 child, Hash256 parent) { } - - public void OnInvalidBlock(Hash256 failedBlock, Hash256 parent) { } - - public bool IsOnKnownInvalidChain(Hash256 blockHash, out Hash256 lastValidHash) - { - lastValidHash = null; - return false; - } - - public void Dispose() { } - } - - private sealed class StaticBeaconPivot : IBeaconPivot - { - private readonly BlockHeader _pivot; - - public StaticBeaconPivot(BlockHeader pivot) - { - _pivot = pivot; - ProcessDestination = pivot; - } - - public long PivotNumber => _pivot.Number; - public Hash256 PivotHash => _pivot.Hash; - public Hash256 PivotParentHash => _pivot.ParentHash; - public long PivotDestinationNumber => _pivot.Number; - - public BlockHeader ProcessDestination { get; set; } - - public bool ShouldForceStartNewSync { get; set; } - - public void EnsurePivot(BlockHeader blockHeader, bool updateOnlyIfNull = false) - { - if (!updateOnlyIfNull || ProcessDestination is null) - { - ProcessDestination = blockHeader; - } - } - - public void RemoveBeaconPivot() { } - - public bool BeaconPivotExists() => true; - } - -} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasNewPayloadMeasuredBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasNewPayloadMeasuredBenchmarks.cs deleted file mode 100644 index e27643077b6c..000000000000 --- a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasNewPayloadMeasuredBenchmarks.cs +++ /dev/null @@ -1,679 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text.Json; -using BenchmarkDotNet.Attributes; -using Nethermind.Blockchain; -using Nethermind.Blockchain.BeaconBlockRoot; -using Nethermind.Blockchain.Receipts; -using Nethermind.Blockchain.Tracing; -using Nethermind.Consensus.Processing; -using Nethermind.Consensus.Validators; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Core.Specs; -using Nethermind.Crypto; -using Nethermind.Evm.State; -using Nethermind.Evm.Tracing; -using Nethermind.Evm.TransactionProcessing; -using Nethermind.Int256; -using Nethermind.Logging; -using Nethermind.Merge.Plugin.Data; -using Nethermind.Serialization.Json; -using Nethermind.Specs; -using Nethermind.Specs.Forks; - -namespace Nethermind.Evm.Benchmark.GasBenchmarks; - -/// -/// Benchmarks that replay gas-benchmark payload files through the full newPayload path: -/// JSON deserialization → ExecutionPayloadV3 → TryGetBlock → BranchProcessor.Process. -/// Each iteration re-deserializes JSON from memory, measuring the overhead of payload -/// decoding, RLP transaction decoding, and block construction on top of block processing. -/// -[Config(typeof(GasBenchmarkConfig))] -public class GasNewPayloadMeasuredBenchmarks -{ - internal const string TimingFileEnvVar = "NETHERMIND_NEWPAYLOAD_TIMING_FILE"; - internal const string TimingReportFileEnvVar = "NETHERMIND_NEWPAYLOAD_TIMING_REPORT_FILE"; - private const int MaxConsoleScenarioCount = 10; - - private IWorldState _state; - private BranchProcessor _branchProcessor; - private BlockHeader _preBlockHeader; - private byte[] _rawJsonBytes; - private TimedTransactionProcessor _timedTransactionProcessor; - private IBlockCachePreWarmer _preWarmer; - private IDisposable _preWarmerLifetime; - private RecoverSignatures _recoverSignatures; - private ProcessingOptions _newPayloadProcessingOptions; - private static readonly JsonSerializerOptions s_jsonOptions = EthereumJsonSerializer.JsonOptions; - private static readonly EthereumEcdsa s_ecdsa = new(1); - private static readonly object s_timingFileLock = new(); - - // Timing breakdown accumulators (captured in ticks for sub-millisecond precision). - private long _jsonParseTicks; - private long _payloadDeserializeTicks; - private long _optionalParamsTicks; - private long _tryGetBlockTicks; - private long _headerValidationTicks; - private long _senderRecoveryTicks; - private long _blockProcessTicks; - private long _txExecutionTicks; - private int _iterationCount; - private int _txExecutionCount; - - private readonly long[] _senderRecoveryByTypeTicks = new long[256]; - private readonly int[] _senderRecoveryByTypeCount = new int[256]; - private readonly long[] _txExecutionByTypeTicks = new long[256]; - private readonly int[] _txExecutionByTypeCount = new int[256]; - - [ParamsSource(nameof(GetTestCases))] - public GasPayloadBenchmarks.TestCase Scenario { get; set; } - - public static IEnumerable GetTestCases() => GasPayloadBenchmarks.GetTestCases(); - - [GlobalSetup] - public void GlobalSetup() - { - IReleaseSpec pragueSpec = Prague.Instance; - ISpecProvider specProvider = new SingleReleaseSpecProvider(pragueSpec, 1, 1); - - PayloadLoader.EnsureGenesisInitialized(GasPayloadBenchmarks.s_genesisPath, pragueSpec); - - TestBlockhashProvider blockhashProvider = new(); - BlockBenchmarkHelper.BranchProcessingContext branchProcessingContext = BlockBenchmarkHelper.CreateBranchProcessingContext(specProvider, blockhashProvider); - _state = branchProcessingContext.State; - _preWarmer = branchProcessingContext.PreWarmer; - _preWarmerLifetime = branchProcessingContext.PreWarmerLifetime; - _preBlockHeader = BlockBenchmarkHelper.CreateGenesisHeader(); - _preBlockHeader.TotalDifficulty = UInt256.Zero; - ITransactionProcessor txProcessor = BlockBenchmarkHelper.CreateTransactionProcessor( - _state, - blockhashProvider, - specProvider, - branchProcessingContext.PreBlockCaches, - branchProcessingContext.CachePrecompiles); - _timedTransactionProcessor = new TimedTransactionProcessor(txProcessor, this); - _recoverSignatures = new RecoverSignatures(s_ecdsa, specProvider, LimboLogs.Instance); - - BlockBenchmarkHelper.ExecuteSetupPayload(_state, _timedTransactionProcessor, _preBlockHeader, Scenario, pragueSpec); - - ReceiptConfig receiptConfig = new(); - IReceiptStorage receiptStorage = BlockBenchmarkHelper.CreateReceiptStorage(receiptConfig); - _newPayloadProcessingOptions = BlockBenchmarkHelper.GetNewPayloadProcessingOptions(receiptConfig); - - BlockProcessor blockProcessor = BlockBenchmarkHelper.CreateBlockProcessor( - specProvider, _timedTransactionProcessor, _state, receiptStorage); - - _branchProcessor = new BranchProcessor( - blockProcessor, specProvider, _state, - new BeaconBlockRootHandler(_timedTransactionProcessor, _state), - blockhashProvider, LimboLogs.Instance, _preWarmer); - - // Store raw JSON bytes for deserialization in each iteration - string rawJson = PayloadLoader.ReadRawJson(Scenario.FilePath); - _rawJsonBytes = System.Text.Encoding.UTF8.GetBytes(rawJson); - - // Warm up: deserialize + process once, then verify correctness - Block block = DeserializeAndBuildBlock(); - Block[] result = _branchProcessor.Process( - _preBlockHeader, [block], - _newPayloadProcessingOptions, - NullBlockTracer.Instance); - PayloadLoader.VerifyProcessedBlock(result[0], Scenario.ToString(), Scenario.FilePath); - ResetTimingAccumulators(); - } - - [Benchmark] - public void ProcessBlock() - { - Block block = DeserializeAndBuildBlock(collectTiming: true); - - long processStart = Stopwatch.GetTimestamp(); - _branchProcessor.Process( - _preBlockHeader, [block], - _newPayloadProcessingOptions, - NullBlockTracer.Instance); - _blockProcessTicks += Stopwatch.GetTimestamp() - processStart; - _iterationCount++; - } - - private void AddTxExecutionTiming(TxType txType, long elapsedTicks) - { - _txExecutionTicks += elapsedTicks; - _txExecutionCount++; - int txTypeIndex = (int)txType; - _txExecutionByTypeTicks[txTypeIndex] += elapsedTicks; - _txExecutionByTypeCount[txTypeIndex]++; - } - - private void ResetTimingAccumulators() - { - _jsonParseTicks = 0; - _payloadDeserializeTicks = 0; - _optionalParamsTicks = 0; - _tryGetBlockTicks = 0; - _headerValidationTicks = 0; - _senderRecoveryTicks = 0; - _blockProcessTicks = 0; - _txExecutionTicks = 0; - _iterationCount = 0; - _txExecutionCount = 0; - Array.Clear(_senderRecoveryByTypeTicks, 0, _senderRecoveryByTypeTicks.Length); - Array.Clear(_senderRecoveryByTypeCount, 0, _senderRecoveryByTypeCount.Length); - Array.Clear(_txExecutionByTypeTicks, 0, _txExecutionByTypeTicks.Length); - Array.Clear(_txExecutionByTypeCount, 0, _txExecutionByTypeCount.Length); - } - - /// - /// Deserializes the raw JSON-RPC payload into an ExecutionPayloadV3, extracts additional - /// parameters (parentBeaconBlockRoot, executionRequests), and converts to a Block via TryGetBlock. - /// - private Block DeserializeAndBuildBlock(bool collectTiming = false) - { - long start = Stopwatch.GetTimestamp(); - using JsonDocument doc = JsonDocument.Parse(_rawJsonBytes); - JsonElement paramsArray = doc.RootElement.GetProperty("params"); - if (collectTiming) - { - _jsonParseTicks += Stopwatch.GetTimestamp() - start; - } - - // Deserialize ExecutionPayloadV3 from the first parameter - start = Stopwatch.GetTimestamp(); - ExecutionPayloadV3 payload = JsonSerializer.Deserialize( - paramsArray[0].GetRawText(), s_jsonOptions); - if (collectTiming) - { - _payloadDeserializeTicks += Stopwatch.GetTimestamp() - start; - } - - start = Stopwatch.GetTimestamp(); - // Extract parentBeaconBlockRoot (params[2]) — separate JSON-RPC parameter - if (paramsArray.GetArrayLength() > 2 && paramsArray[2].ValueKind == JsonValueKind.String) - { - payload.ParentBeaconBlockRoot = JsonSerializer.Deserialize( - paramsArray[2].GetRawText(), s_jsonOptions); - } - - // Extract executionRequests (params[3]) — Prague V4 parameter - if (paramsArray.GetArrayLength() > 3 && paramsArray[3].ValueKind == JsonValueKind.Array) - { - payload.ExecutionRequests = JsonSerializer.Deserialize( - paramsArray[3].GetRawText(), s_jsonOptions); - } - if (collectTiming) - { - _optionalParamsTicks += Stopwatch.GetTimestamp() - start; - } - - start = Stopwatch.GetTimestamp(); - BlockDecodingResult decodingResult = payload.TryGetBlock(); - if (decodingResult.Block is null) - { - throw new InvalidOperationException( - $"Failed to decode block from payload: {decodingResult.Error}"); - } - if (collectTiming) - { - _tryGetBlockTicks += Stopwatch.GetTimestamp() - start; - } - - // Recover sender addresses — in production this happens during block validation - Block block = decodingResult.Block; - start = Stopwatch.GetTimestamp(); - block.Header.IsPostMerge = true; - block.Header.TotalDifficulty = (_preBlockHeader.TotalDifficulty ?? UInt256.Zero) + block.Difficulty; - if (!HeaderValidator.ValidateHash(block.Header, out Hash256 actualHash)) - { - throw new InvalidOperationException( - $"Payload block hash mismatch. Declared: {block.Header.Hash}, Calculated: {actualHash}"); - } - if (collectTiming) - { - _headerValidationTicks += Stopwatch.GetTimestamp() - start; - } - - if (collectTiming) - { - start = Stopwatch.GetTimestamp(); - _recoverSignatures.RecoverData(block); - long elapsedTicks = Stopwatch.GetTimestamp() - start; - _senderRecoveryTicks += elapsedTicks; - AddSenderRecoveryTypeBreakdown(block, elapsedTicks); - } - else - { - _recoverSignatures.RecoverData(block); - } - - return block; - } - - private void AddSenderRecoveryTypeBreakdown(Block block, long elapsedTicks) - { - int txCount = block.Transactions.Length; - if (txCount == 0) - { - return; - } - - int[] countsPerType = new int[_senderRecoveryByTypeCount.Length]; - int firstTypeIndex = -1; - for (int i = 0; i < txCount; i++) - { - int txTypeIndex = (int)block.Transactions[i].Type; - if (firstTypeIndex == -1) - { - firstTypeIndex = txTypeIndex; - } - - countsPerType[txTypeIndex]++; - _senderRecoveryByTypeCount[txTypeIndex]++; - } - - long allocatedTicks = 0; - for (int txTypeIndex = 0; txTypeIndex < countsPerType.Length; txTypeIndex++) - { - int count = countsPerType[txTypeIndex]; - if (count == 0) - { - continue; - } - - long typeTicks = elapsedTicks * count / txCount; - _senderRecoveryByTypeTicks[txTypeIndex] += typeTicks; - allocatedTicks += typeTicks; - } - - if (firstTypeIndex >= 0 && allocatedTicks != elapsedTicks) - { - _senderRecoveryByTypeTicks[firstTypeIndex] += elapsedTicks - allocatedTicks; - } - } - - [GlobalCleanup] - public void GlobalCleanup() - { - if (_iterationCount > 0) - { - TimingBreakdownSummary summary = new( - Scenario?.ToString() ?? "", - _iterationCount, - _jsonParseTicks, - _payloadDeserializeTicks, - _optionalParamsTicks, - _tryGetBlockTicks, - _headerValidationTicks, - _senderRecoveryTicks, - _blockProcessTicks, - _txExecutionTicks, - _txExecutionCount, - (long[])_senderRecoveryByTypeTicks.Clone(), - (int[])_senderRecoveryByTypeCount.Clone(), - (long[])_txExecutionByTypeTicks.Clone(), - (int[])_txExecutionByTypeCount.Clone()); - AppendSummaryToFile(summary); - } - - _preWarmer?.ClearCaches(); - _preWarmerLifetime?.Dispose(); - _state = null; - _branchProcessor = null; - _rawJsonBytes = null; - _timedTransactionProcessor = null; - _preWarmer = null; - _preWarmerLifetime = null; - } - - public static void PrintFinalTimingBreakdown() - { - string timingFilePath = Environment.GetEnvironmentVariable(TimingFileEnvVar); - if (string.IsNullOrWhiteSpace(timingFilePath) || !File.Exists(timingFilePath)) - { - return; - } - - string[] lines = File.ReadAllLines(timingFilePath); - List summaries = new(lines.Length); - for (int i = 0; i < lines.Length; i++) - { - string line = lines[i]; - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - - TimingBreakdownSummary summary = JsonSerializer.Deserialize(line, s_jsonOptions); - if (summary is not null) - { - summaries.Add(summary); - } - } - - if (summaries.Count == 0) - { - return; - } - - File.Delete(timingFilePath); - - string reportFilePath = ResolveTimingReportFilePath(); - string reportDirectory = Path.GetDirectoryName(reportFilePath); - if (!string.IsNullOrWhiteSpace(reportDirectory)) - { - Directory.CreateDirectory(reportDirectory); - } - - using (StreamWriter reportWriter = new(reportFilePath, false)) - { - WriteReport(reportWriter, summaries); - } - - if (summaries.Count <= MaxConsoleScenarioCount) - { - Console.WriteLine(); - WriteReport(Console.Out, summaries); - Console.WriteLine($"NewPayloadMeasured timing breakdown saved to: {reportFilePath}"); - } - else - { - Console.WriteLine($"NewPayloadMeasured timing breakdown saved to: {reportFilePath} (scenario count: {summaries.Count}, console output suppressed for runs with more than {MaxConsoleScenarioCount} scenarios)"); - } - } - - private static void WriteReport(TextWriter writer, List summaries) - { - writer.WriteLine("=== NewPayloadMeasured timing breakdown (final) ==="); - long totalJsonParseTicks = 0; - long totalPayloadDeserializeTicks = 0; - long totalOptionalParamsTicks = 0; - long totalTryGetBlockTicks = 0; - long totalHeaderValidationTicks = 0; - long totalSenderRecoveryTicks = 0; - long totalBlockProcessTicks = 0; - long totalTxExecutionTicks = 0; - int totalIterations = 0; - int totalTxExecutionCount = 0; - long[] totalSenderRecoveryByTypeTicks = new long[256]; - int[] totalSenderRecoveryByTypeCount = new int[256]; - long[] totalTxExecutionByTypeTicks = new long[256]; - int[] totalTxExecutionByTypeCount = new int[256]; - - for (int i = 0; i < summaries.Count; i++) - { - TimingBreakdownSummary summary = summaries[i]; - PrintSummary(writer, summary.Name, summary.Iterations, summary.JsonParseTicks, summary.PayloadDeserializeTicks, summary.OptionalParamsTicks, summary.TryGetBlockTicks, summary.HeaderValidationTicks, summary.SenderRecoveryTicks, summary.BlockProcessTicks, summary.TxExecutionTicks, summary.TxExecutionCount, summary.SenderRecoveryByTypeTicks, summary.SenderRecoveryByTypeCount, summary.TxExecutionByTypeTicks, summary.TxExecutionByTypeCount); - - totalJsonParseTicks += summary.JsonParseTicks; - totalPayloadDeserializeTicks += summary.PayloadDeserializeTicks; - totalOptionalParamsTicks += summary.OptionalParamsTicks; - totalTryGetBlockTicks += summary.TryGetBlockTicks; - totalHeaderValidationTicks += summary.HeaderValidationTicks; - totalSenderRecoveryTicks += summary.SenderRecoveryTicks; - totalBlockProcessTicks += summary.BlockProcessTicks; - totalTxExecutionTicks += summary.TxExecutionTicks; - totalIterations += summary.Iterations; - totalTxExecutionCount += summary.TxExecutionCount; - AddTypeBreakdown(totalSenderRecoveryByTypeTicks, summary.SenderRecoveryByTypeTicks); - AddTypeBreakdown(totalSenderRecoveryByTypeCount, summary.SenderRecoveryByTypeCount); - AddTypeBreakdown(totalTxExecutionByTypeTicks, summary.TxExecutionByTypeTicks); - AddTypeBreakdown(totalTxExecutionByTypeCount, summary.TxExecutionByTypeCount); - } - - if (summaries.Count > 1 && totalIterations > 0) - { - PrintSummary(writer, "ALL SCENARIOS", totalIterations, totalJsonParseTicks, totalPayloadDeserializeTicks, totalOptionalParamsTicks, totalTryGetBlockTicks, totalHeaderValidationTicks, totalSenderRecoveryTicks, totalBlockProcessTicks, totalTxExecutionTicks, totalTxExecutionCount, totalSenderRecoveryByTypeTicks, totalSenderRecoveryByTypeCount, totalTxExecutionByTypeTicks, totalTxExecutionByTypeCount); - } - } - - private static string ResolveTimingReportFilePath() - { - string configuredPath = Environment.GetEnvironmentVariable(TimingReportFileEnvVar); - if (!string.IsNullOrWhiteSpace(configuredPath)) - { - return configuredPath; - } - - return Path.Combine( - Path.GetTempPath(), - $"nethermind-newpayload-timing-breakdown-{DateTime.UtcNow:yyyyMMdd-HHmmss}-{Guid.NewGuid():N}.txt"); - } - - private static void AppendSummaryToFile(TimingBreakdownSummary summary) - { - string timingFilePath = Environment.GetEnvironmentVariable(TimingFileEnvVar); - if (string.IsNullOrWhiteSpace(timingFilePath)) - { - return; - } - - string line = JsonSerializer.Serialize(summary, s_jsonOptions); - lock (s_timingFileLock) - { - File.AppendAllText(timingFilePath, line + Environment.NewLine); - } - } - - private static void PrintSummary( - TextWriter writer, - string name, - int iterations, - long jsonParseTicks, - long payloadDeserializeTicks, - long optionalParamsTicks, - long tryGetBlockTicks, - long headerValidationTicks, - long senderRecoveryTicks, - long blockProcessTicks, - long txExecutionTicks, - int txExecutionCount, - long[] senderRecoveryByTypeTicks, - int[] senderRecoveryByTypeCount, - long[] txExecutionByTypeTicks, - int[] txExecutionByTypeCount) - { - double jsonParseMs = TicksToMs(jsonParseTicks); - double payloadDeserializeMs = TicksToMs(payloadDeserializeTicks); - double optionalParamsMs = TicksToMs(optionalParamsTicks); - double tryGetBlockMs = TicksToMs(tryGetBlockTicks); - double headerValidationMs = TicksToMs(headerValidationTicks); - double senderRecoveryMs = TicksToMs(senderRecoveryTicks); - double blockProcessMs = TicksToMs(blockProcessTicks); - double txExecutionMs = TicksToMs(txExecutionTicks); - double nonTxBlockMs = blockProcessMs - txExecutionMs; - if (nonTxBlockMs < 0) - { - nonTxBlockMs = 0; - } - - int senderRecoveryCount = GetTotalCount(senderRecoveryByTypeCount); - double totalMs = jsonParseMs + payloadDeserializeMs + optionalParamsMs + tryGetBlockMs + headerValidationMs + senderRecoveryMs + blockProcessMs; - - writer.WriteLine($"--- {name} ({iterations} iterations) ---"); - PrintLine(writer, "JSON parse", jsonParseMs, totalMs, iterations); - PrintLine(writer, "Payload deserialize", payloadDeserializeMs, totalMs, iterations); - PrintLine(writer, "Optional params", optionalParamsMs, totalMs, iterations); - PrintLine(writer, "TryGetBlock", tryGetBlockMs, totalMs, iterations); - PrintLine(writer, "Header validate", headerValidationMs, totalMs, iterations); - PrintLine(writer, "Sender recovery", senderRecoveryMs, totalMs, iterations); - PrintLine(writer, "Block processing", blockProcessMs, totalMs, iterations); - writer.WriteLine($" {"Total",-20} {totalMs,9:F3} ms avg {totalMs / iterations:F3} ms/iter"); - writer.WriteLine(" Block processing detail:"); - writer.WriteLine($" {"Tx execution",-18} {txExecutionMs,9:F3} ms ({GetShare(txExecutionMs, blockProcessMs),5:F1}% of block) avg {GetAverage(txExecutionMs, txExecutionCount):F3} ms/tx"); - writer.WriteLine($" {"Non-tx overhead",-18} {nonTxBlockMs,9:F3} ms ({GetShare(nonTxBlockMs, blockProcessMs),5:F1}% of block)"); - PrintTxTypeBreakdown(writer, "Tx execution by type", txExecutionByTypeTicks, txExecutionByTypeCount, txExecutionMs); - writer.WriteLine(" Sender recovery detail:"); - writer.WriteLine($" {"Recovered txs",-18} {senderRecoveryCount,9} tx"); - writer.WriteLine($" {"Avg per tx",-18} {GetAverage(senderRecoveryMs, senderRecoveryCount),9:F3} ms/tx"); - PrintTxTypeBreakdown(writer, "Sender recovery by type", senderRecoveryByTypeTicks, senderRecoveryByTypeCount, senderRecoveryMs); - } - - private static void PrintLine(TextWriter writer, string label, double stageMs, double totalMs, int iterations) - { - double share = totalMs > 0 ? (100.0 * stageMs / totalMs) : 0.0; - double avg = iterations > 0 ? (stageMs / iterations) : 0.0; - writer.WriteLine($" {label,-20} {stageMs,9:F3} ms ({share,5:F1}%) avg {avg:F3} ms/iter"); - } - - private static void PrintTxTypeBreakdown(TextWriter writer, string title, long[] ticksByType, int[] countByType, double stageTotalMs) - { - writer.WriteLine($" {title}:"); - for (int txTypeIndex = 0; txTypeIndex < countByType.Length; txTypeIndex++) - { - int count = countByType[txTypeIndex]; - if (count == 0) - { - continue; - } - - double ms = TicksToMs(ticksByType[txTypeIndex]); - double share = GetShare(ms, stageTotalMs); - double avg = GetAverage(ms, count); - writer.WriteLine($" {FormatTxType((TxType)txTypeIndex),-16} {ms,9:F3} ms ({share,5:F1}%) avg {avg:F3} ms/tx count {count}"); - } - } - - private static string FormatTxType(TxType txType) - { - return txType switch - { - TxType.Legacy => "Legacy", - TxType.AccessList => "AccessList", - TxType.EIP1559 => "EIP1559", - TxType.Blob => "Blob", - TxType.SetCode => "SetCode", - TxType.DepositTx => "DepositTx", - _ => $"Type(0x{(byte)txType:X2})" - }; - } - - private static void AddTypeBreakdown(long[] destination, long[] source) - { - int length = destination.Length < source.Length ? destination.Length : source.Length; - for (int i = 0; i < length; i++) - { - destination[i] += source[i]; - } - } - - private static void AddTypeBreakdown(int[] destination, int[] source) - { - int length = destination.Length < source.Length ? destination.Length : source.Length; - for (int i = 0; i < length; i++) - { - destination[i] += source[i]; - } - } - - private static int GetTotalCount(int[] countByType) - { - int total = 0; - for (int i = 0; i < countByType.Length; i++) - { - total += countByType[i]; - } - - return total; - } - - private static double GetShare(double part, double whole) - { - return whole > 0 ? (100.0 * part / whole) : 0.0; - } - - private static double GetAverage(double totalMs, int count) - { - return count > 0 ? (totalMs / count) : 0.0; - } - - private static double TicksToMs(long ticks) => ticks * 1000.0 / Stopwatch.Frequency; - - private sealed class TimedTransactionProcessor(ITransactionProcessor inner, GasNewPayloadMeasuredBenchmarks owner) : ITransactionProcessor - { - public TransactionResult Execute(Transaction transaction, ITxTracer txTracer) - { - long start = Stopwatch.GetTimestamp(); - TransactionResult result = inner.Execute(transaction, txTracer); - long elapsedTicks = Stopwatch.GetTimestamp() - start; - owner.AddTxExecutionTiming(transaction.Type, elapsedTicks); - return result; - } - - public TransactionResult CallAndRestore(Transaction transaction, ITxTracer txTracer) => - inner.CallAndRestore(transaction, txTracer); - - public TransactionResult BuildUp(Transaction transaction, ITxTracer txTracer) => - inner.BuildUp(transaction, txTracer); - - public TransactionResult Trace(Transaction transaction, ITxTracer txTracer) => - inner.Trace(transaction, txTracer); - - public TransactionResult Warmup(Transaction transaction, ITxTracer txTracer) => - inner.Warmup(transaction, txTracer); - - public void SetBlockExecutionContext(BlockHeader blockHeader) => - inner.SetBlockExecutionContext(blockHeader); - - public void SetBlockExecutionContext(in BlockExecutionContext blockExecutionContext) => - inner.SetBlockExecutionContext(in blockExecutionContext); - } - - private sealed record TimingBreakdownSummary - { - public TimingBreakdownSummary( - string name, - int iterations, - long jsonParseTicks, - long payloadDeserializeTicks, - long optionalParamsTicks, - long tryGetBlockTicks, - long headerValidationTicks, - long senderRecoveryTicks, - long blockProcessTicks, - long txExecutionTicks, - int txExecutionCount, - long[] senderRecoveryByTypeTicks, - int[] senderRecoveryByTypeCount, - long[] txExecutionByTypeTicks, - int[] txExecutionByTypeCount) - { - Name = name; - Iterations = iterations; - JsonParseTicks = jsonParseTicks; - PayloadDeserializeTicks = payloadDeserializeTicks; - OptionalParamsTicks = optionalParamsTicks; - TryGetBlockTicks = tryGetBlockTicks; - HeaderValidationTicks = headerValidationTicks; - SenderRecoveryTicks = senderRecoveryTicks; - BlockProcessTicks = blockProcessTicks; - TxExecutionTicks = txExecutionTicks; - TxExecutionCount = txExecutionCount; - SenderRecoveryByTypeTicks = senderRecoveryByTypeTicks; - SenderRecoveryByTypeCount = senderRecoveryByTypeCount; - TxExecutionByTypeTicks = txExecutionByTypeTicks; - TxExecutionByTypeCount = txExecutionByTypeCount; - } - - public string Name { get; init; } - public int Iterations { get; init; } - public long JsonParseTicks { get; init; } - public long PayloadDeserializeTicks { get; init; } - public long OptionalParamsTicks { get; init; } - public long TryGetBlockTicks { get; init; } - public long HeaderValidationTicks { get; init; } - public long SenderRecoveryTicks { get; init; } - public long BlockProcessTicks { get; init; } - public long TxExecutionTicks { get; init; } - public int TxExecutionCount { get; init; } - public long[] SenderRecoveryByTypeTicks { get; init; } - public int[] SenderRecoveryByTypeCount { get; init; } - public long[] TxExecutionByTypeTicks { get; init; } - public int[] TxExecutionByTypeCount { get; init; } - } -} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasPayloadBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasPayloadBenchmarks.cs deleted file mode 100644 index deaa5deff01f..000000000000 --- a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasPayloadBenchmarks.cs +++ /dev/null @@ -1,258 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using BenchmarkDotNet.Attributes; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Core.Specs; -using Nethermind.Blockchain; -using Nethermind.Evm.State; -using Nethermind.Evm.Tracing; -using Nethermind.Evm.TransactionProcessing; -using Nethermind.Logging; -using Nethermind.Specs; -using Nethermind.Specs.Forks; - -namespace Nethermind.Evm.Benchmark.GasBenchmarks; - -/// -/// Benchmarks that replay gas-benchmark payload files via TransactionProcessor.BuildUp. -/// This models block-building style execution (uncommitted per-transaction flow). -/// Test cases are auto-discovered from the gas-benchmarks submodule. -/// -[Config(typeof(GasBenchmarkConfig))] -public class GasPayloadBenchmarks -{ - private static readonly string s_repoRoot = FindRepoRoot(); - private static readonly string s_gasBenchmarksRoot = Path.Combine(s_repoRoot, "tools", "gas-benchmarks"); - private static readonly string s_testingDir = Path.Combine(s_gasBenchmarksRoot, "eest_tests", "testing"); - private static readonly string s_setupDir = Path.Combine(s_gasBenchmarksRoot, "eest_tests", "setup"); - internal static readonly string s_genesisPath = Path.Combine(s_gasBenchmarksRoot, "scripts", "genesisfiles", "nethermind", "zkevmgenesis.json"); - private static bool s_missingSubmoduleWarned; - - private IWorldState _state; - private IDisposable _stateScope; - private ITransactionProcessor _txProcessor; - private Transaction[] _testTransactions; - private BlockHeader _testHeader; - - [ParamsSource(nameof(GetTestCases))] - public TestCase Scenario { get; set; } - - [GlobalSetup] - public void GlobalSetup() - { - IReleaseSpec pragueSpec = Prague.Instance; - ISpecProvider specProvider = new SingleReleaseSpecProvider(pragueSpec, 1, 1); - - // Load genesis state once (shared across all test cases) - PayloadLoader.EnsureGenesisInitialized(s_genesisPath, pragueSpec); - - // Create a fresh WorldState and open scope at genesis - _state = PayloadLoader.CreateWorldState(); - BlockHeader genesisBlock = new(Keccak.Zero, Keccak.OfAnEmptySequenceRlp, Address.Zero, 0, 0, 0, 0, Array.Empty()) - { - StateRoot = PayloadLoader.GenesisStateRoot - }; - _stateScope = _state.BeginScope(genesisBlock); - - // Set up EVM infrastructure - TestBlockhashProvider blockhashProvider = new(); - EthereumCodeInfoRepository codeInfoRepo = new(_state); - EthereumVirtualMachine vm = new(blockhashProvider, specProvider, LimboLogs.Instance); - - _txProcessor = new EthereumTransactionProcessor( - BlobBaseFeeCalculator.Instance, - specProvider, - _state, - vm, - codeInfoRepo, - LimboLogs.Instance); - - // Execute setup payload if one exists for this scenario - string setupFile = FindSetupFile(Scenario.FileName); - if (setupFile is not null) - { - (BlockHeader setupHeader, Transaction[] setupTxs) = PayloadLoader.LoadPayload(setupFile); - _txProcessor.SetBlockExecutionContext(setupHeader); - for (int i = 0; i < setupTxs.Length; i++) - { - _txProcessor.Execute(setupTxs[i], NullTxTracer.Instance); - } - _state.Commit(pragueSpec); - } - - // Parse the test payload - (BlockHeader header, Transaction[] txs) = PayloadLoader.LoadPayload(Scenario.FilePath); - _testHeader = header; - _testTransactions = txs; - _txProcessor.SetBlockExecutionContext(_testHeader); - - // Warm up: execute once via CallAndRestore to prime code caches - for (int i = 0; i < _testTransactions.Length; i++) - { - _txProcessor.CallAndRestore(_testTransactions[i], NullTxTracer.Instance); - } - } - - [Benchmark] - public void ExecutePayload() - { - for (int i = 0; i < _testTransactions.Length; i++) - { - _txProcessor.BuildUp(_testTransactions[i], NullTxTracer.Instance); - } - - _state.Reset(); - } - - [GlobalCleanup] - public void GlobalCleanup() - { - _stateScope?.Dispose(); - _stateScope = null; - _state = null; - _txProcessor = null; - _testTransactions = null; - } - - /// - /// Auto-discovers test cases from the gas-benchmarks testing directory. - /// - public static IEnumerable GetTestCases() - { - if (!Directory.Exists(s_testingDir)) - { - if (!s_missingSubmoduleWarned) - { - s_missingSubmoduleWarned = true; - string hint = "\u001b[33m[GasPayloadBenchmarks] No test cases found.\u001b[0m Initialize the gas-benchmarks submodule:\n" + - " \u001b[36mgit lfs install && git submodule update --init tools/gas-benchmarks\u001b[0m"; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - hint += "\n On Windows, you may also need: \u001b[36mgit config --global core.longpaths true\u001b[0m"; - Console.Error.WriteLine(hint); - } - - yield break; - } - - string[] dirs = Directory.GetDirectories(s_testingDir); - Array.Sort(dirs); - - int globalIndex = 0; - int chunkIndex = GasBenchmarkConfig.ChunkIndex; - int chunkTotal = GasBenchmarkConfig.ChunkTotal; - - for (int d = 0; d < dirs.Length; d++) - { - string[] files = Directory.GetFiles(dirs[d], "*.txt"); - Array.Sort(files); - for (int f = 0; f < files.Length; f++) - { - if (chunkTotal > 0 && (globalIndex % chunkTotal) != (chunkIndex - 1)) - { - globalIndex++; - continue; - } - globalIndex++; - yield return new TestCase(files[f]); - } - } - } - - /// - /// Finds the setup payload file matching a given test filename, if any. - /// Setup files share the same filename as test files but live under setup/ directories. - /// - internal static string FindSetupFile(string testFileName) - { - if (!Directory.Exists(s_setupDir)) - return null; - - string[] setupDirs = Directory.GetDirectories(s_setupDir); - for (int i = 0; i < setupDirs.Length; i++) - { - string candidate = Path.Combine(setupDirs[i], testFileName); - if (File.Exists(candidate)) - return candidate; - } - - return null; - } - - private static string FindRepoRoot() - { - string dir = AppDomain.CurrentDomain.BaseDirectory; - while (dir is not null) - { - if (Directory.Exists(Path.Combine(dir, ".git"))) - return dir; - dir = Directory.GetParent(dir)?.FullName; - } - - throw new DirectoryNotFoundException("Could not find repository root (.git directory)."); - } - - /// - /// Represents a single gas-benchmark test case with a short display name. - /// - public sealed class TestCase - { - public string FilePath { get; } - public string FileName { get; } - public string DisplayName { get; } - - public TestCase(string filePath) - { - FilePath = filePath; - FileName = Path.GetFileName(filePath); - DisplayName = ExtractShortName(FileName); - } - - public override string ToString() => DisplayName; - - /// - /// Extracts a short benchmark name from the long filename. - /// Input: tests_benchmark_compute_instruction_test_foo.py__test_bar[fork_Prague-benchmark-blockchain_test_engine_x-param1-param2]-gas-value_100M.txt - /// Output: bar[param1-param2] - /// - private static string ExtractShortName(string fileName) - { - // Find test method name after "__test_" - int testMethodStart = fileName.IndexOf("__test_", StringComparison.Ordinal); - if (testMethodStart < 0) - return fileName; - - string afterTestPrefix = fileName.Substring(testMethodStart + 7); - - // Remove the "-gas-value_*" suffix - int gasValueIdx = afterTestPrefix.IndexOf("-gas-value_", StringComparison.Ordinal); - if (gasValueIdx >= 0) - afterTestPrefix = afterTestPrefix.Substring(0, gasValueIdx); - - // Remove the "[fork_Prague-benchmark-blockchain_test_engine_x-" prefix from params - const string forkPrefix = "[fork_Prague-benchmark-blockchain_test_engine_x-"; - int forkIdx = afterTestPrefix.IndexOf(forkPrefix, StringComparison.Ordinal); - if (forkIdx >= 0) - { - string methodName = afterTestPrefix.Substring(0, forkIdx); - string paramsStr = afterTestPrefix.Substring(forkIdx + forkPrefix.Length); - - // Remove trailing ']' - if (paramsStr.EndsWith("]")) - paramsStr = paramsStr.Substring(0, paramsStr.Length - 1); - - return string.IsNullOrEmpty(paramsStr) - ? methodName - : methodName + "[" + paramsStr + "]"; - } - - return afterTestPrefix; - } - } -} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasPayloadExecuteBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasPayloadExecuteBenchmarks.cs deleted file mode 100644 index 864a0cd72358..000000000000 --- a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/GasPayloadExecuteBenchmarks.cs +++ /dev/null @@ -1,121 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using BenchmarkDotNet.Attributes; -using Nethermind.Blockchain; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Core.Specs; -using Nethermind.Evm.State; -using Nethermind.Evm.Tracing; -using Nethermind.Evm.TransactionProcessing; -using Nethermind.Logging; -using Nethermind.Specs; -using Nethermind.Specs.Forks; - -namespace Nethermind.Evm.Benchmark.GasBenchmarks; - -/// -/// Benchmarks that replay gas-benchmark payload files via TransactionProcessor.Execute. -/// This follows the validation/import execution path (as used for normal block processing), -/// while keeping the harness focused on transaction execution itself. -/// -[Config(typeof(GasBenchmarkConfig))] -public class GasPayloadExecuteBenchmarks -{ - private IWorldState _state; - private IDisposable _stateScope; - private ITransactionProcessor _txProcessor; - private Transaction[] _testTransactions; - private BlockHeader _testHeaderTemplate; - - [ParamsSource(nameof(GetTestCases))] - public GasPayloadBenchmarks.TestCase Scenario { get; set; } - - public static IEnumerable GetTestCases() => GasPayloadBenchmarks.GetTestCases(); - - [GlobalSetup] - public void GlobalSetup() - { - IReleaseSpec pragueSpec = Prague.Instance; - ISpecProvider specProvider = new SingleReleaseSpecProvider(pragueSpec, 1, 1); - - // Load genesis state once (shared across all test cases) - PayloadLoader.EnsureGenesisInitialized(GasPayloadBenchmarks.s_genesisPath, pragueSpec); - - // Create a fresh WorldState and open scope at genesis - _state = PayloadLoader.CreateWorldState(); - BlockHeader genesisBlock = new(Keccak.Zero, Keccak.OfAnEmptySequenceRlp, Address.Zero, 0, 0, 0, 0, Array.Empty()) - { - StateRoot = PayloadLoader.GenesisStateRoot - }; - _stateScope = _state.BeginScope(genesisBlock); - - // Set up EVM infrastructure - TestBlockhashProvider blockhashProvider = new(); - EthereumCodeInfoRepository codeInfoRepo = new(_state); - EthereumVirtualMachine vm = new(blockhashProvider, specProvider, LimboLogs.Instance); - - _txProcessor = new EthereumTransactionProcessor( - BlobBaseFeeCalculator.Instance, - specProvider, - _state, - vm, - codeInfoRepo, - LimboLogs.Instance); - - // Execute setup payload if one exists for this scenario - string setupFile = GasPayloadBenchmarks.FindSetupFile(Scenario.FileName); - if (setupFile is not null) - { - (BlockHeader setupHeader, Transaction[] setupTxs) = PayloadLoader.LoadPayload(setupFile); - _txProcessor.SetBlockExecutionContext(setupHeader); - for (int i = 0; i < setupTxs.Length; i++) - { - _txProcessor.Execute(setupTxs[i], NullTxTracer.Instance); - } - _state.Commit(pragueSpec); - } - - // Parse the test payload - (BlockHeader header, Transaction[] txs) = PayloadLoader.LoadPayload(Scenario.FilePath); - _testHeaderTemplate = header; - _testTransactions = txs; - - // Warm up once on Execute path, then restore state. - ExecutePayloadCore(); - _state.Reset(); - } - - [Benchmark] - public void ExecutePayload() - { - ExecutePayloadCore(); - _state.Reset(); - } - - private void ExecutePayloadCore() - { - BlockHeader executionHeader = _testHeaderTemplate.Clone(); - executionHeader.GasUsed = 0; - _txProcessor.SetBlockExecutionContext(executionHeader); - for (int i = 0; i < _testTransactions.Length; i++) - { - _txProcessor.Execute(_testTransactions[i], NullTxTracer.Instance); - } - } - - [GlobalCleanup] - public void GlobalCleanup() - { - _stateScope?.Dispose(); - _stateScope = null; - _state = null; - _txProcessor = null; - _testTransactions = null; - _testHeaderTemplate = null; - } -} - diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/PayloadLoader.cs b/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/PayloadLoader.cs deleted file mode 100644 index 7929ee09213c..000000000000 --- a/src/Nethermind/Nethermind.Evm.Benchmark/GasBenchmarks/PayloadLoader.cs +++ /dev/null @@ -1,457 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.IO; -using System.Runtime.InteropServices; -using System.Text.Json; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; -using Nethermind.Core.Specs; -using Nethermind.Core.Test; -using Nethermind.Core.Test.Db; -using Nethermind.Crypto; -using Nethermind.Db; -using Nethermind.Evm.State; -using Nethermind.Int256; -using Nethermind.Logging; -using Nethermind.Serialization.Rlp; -using Nethermind.State; -using Nethermind.State.Proofs; -using Nethermind.Trie; -using Nethermind.Trie.Pruning; - -namespace Nethermind.Evm.Benchmark.GasBenchmarks; - -/// -/// Loads engine_newPayloadV4 payload files and genesis state for gas benchmarks. -/// -public static class PayloadLoader -{ - private static readonly object s_genesisLock = new(); - private static IDbProvider s_dbProvider; - private static TrieStore s_trieStore; - private static IDb s_codeDb; - private static Hash256 s_genesisStateRoot; - private static bool s_genesisInitialized; - - /// - /// Parses an engine_newPayloadV4 payload file and returns a BlockHeader + decoded transactions. - /// - public static (BlockHeader Header, Transaction[] Transactions) LoadPayload(string filePath) - { - string firstLine; - using (StreamReader reader = new(filePath)) - { - firstLine = reader.ReadLine(); - } - - using JsonDocument doc = JsonDocument.Parse(firstLine); - JsonElement paramsArray = doc.RootElement.GetProperty("params"); - JsonElement payload = paramsArray[0]; - - long blockNumber = ParseHexLong(payload, "blockNumber"); - long gasLimit = ParseHexLong(payload, "gasLimit"); - long gasUsed = ParseHexLong(payload, "gasUsed"); - ulong timestamp = ParseHexULong(payload, "timestamp"); - UInt256 baseFeePerGas = ParseHexUInt256(payload, "baseFeePerGas"); - Hash256 parentHash = new(Bytes.FromHexString(payload.GetProperty("parentHash").GetString())); - Hash256 prevRandao = new(Bytes.FromHexString(payload.GetProperty("prevRandao").GetString())); - Address beneficiary = new(payload.GetProperty("feeRecipient").GetString()); - - BlockHeader header = new( - parentHash, - Keccak.OfAnEmptySequenceRlp, - beneficiary, - UInt256.Zero, - blockNumber, - gasLimit, - timestamp, - Array.Empty()) - { - GasUsed = gasUsed, - BaseFeePerGas = baseFeePerGas, - MixHash = prevRandao, - IsPostMerge = true, - Hash = new Hash256(Bytes.FromHexString(payload.GetProperty("blockHash").GetString())) - }; - - JsonElement txsArray = payload.GetProperty("transactions"); - int txCount = txsArray.GetArrayLength(); - Transaction[] transactions = new Transaction[txCount]; - - EthereumEcdsa ecdsa = new(1); - for (int i = 0; i < txCount; i++) - { - byte[] rlpBytes = Bytes.FromHexString(txsArray[i].GetString()); - transactions[i] = TxDecoder.Instance.Decode(rlpBytes); - transactions[i].SenderAddress = ecdsa.RecoverAddress(transactions[i]); - } - - return (header, transactions); - } - - /// - /// Parses an engine_newPayloadV4 payload file and returns a full Block suitable for BlockProcessor. - /// Includes withdrawals, TxRoot, WithdrawalsRoot, and other block-level fields. - /// - public static Block LoadBlock(string filePath) - { - string firstLine; - using (StreamReader reader = new(filePath)) - { - firstLine = reader.ReadLine(); - } - - using JsonDocument doc = JsonDocument.Parse(firstLine); - JsonElement paramsArray = doc.RootElement.GetProperty("params"); - JsonElement payload = paramsArray[0]; - - long blockNumber = ParseHexLong(payload, "blockNumber"); - long gasLimit = ParseHexLong(payload, "gasLimit"); - long gasUsed = ParseHexLong(payload, "gasUsed"); - ulong timestamp = ParseHexULong(payload, "timestamp"); - UInt256 baseFeePerGas = ParseHexUInt256(payload, "baseFeePerGas"); - Hash256 parentHash = new(Bytes.FromHexString(payload.GetProperty("parentHash").GetString())); - Hash256 prevRandao = new(Bytes.FromHexString(payload.GetProperty("prevRandao").GetString())); - Address beneficiary = new(payload.GetProperty("feeRecipient").GetString()); - - // Parse blob gas fields (EIP-4844) - ulong? blobGasUsed = payload.TryGetProperty("blobGasUsed", out JsonElement blobGasEl) ? ParseHexULong(blobGasEl.GetString()) : null; - ulong? excessBlobGas = payload.TryGetProperty("excessBlobGas", out JsonElement excessEl) ? ParseHexULong(excessEl.GetString()) : null; - - // Parse parent beacon block root (EIP-4788) — separate JSON-RPC param, not inside payload object - Hash256 parentBeaconBlockRoot = null; - if (paramsArray.GetArrayLength() > 2 && paramsArray[2].ValueKind == JsonValueKind.String) - { - string beaconRootHex = paramsArray[2].GetString(); - if (beaconRootHex is not null && beaconRootHex.Length > 2) - parentBeaconBlockRoot = new Hash256(Bytes.FromHexString(beaconRootHex)); - } - - // Decode transactions - JsonElement txsArray = payload.GetProperty("transactions"); - int txCount = txsArray.GetArrayLength(); - Transaction[] transactions = new Transaction[txCount]; - - EthereumEcdsa ecdsa = new(1); - for (int i = 0; i < txCount; i++) - { - byte[] rlpBytes = Bytes.FromHexString(txsArray[i].GetString()); - transactions[i] = TxDecoder.Instance.Decode(rlpBytes); - transactions[i].SenderAddress = ecdsa.RecoverAddress(transactions[i]); - } - - // Parse withdrawals (EIP-4895) - Withdrawal[] withdrawals = null; - if (payload.TryGetProperty("withdrawals", out JsonElement withdrawalsEl) && withdrawalsEl.ValueKind == JsonValueKind.Array) - { - int wCount = withdrawalsEl.GetArrayLength(); - withdrawals = new Withdrawal[wCount]; - for (int i = 0; i < wCount; i++) - { - JsonElement w = withdrawalsEl[i]; - withdrawals[i] = new Withdrawal - { - Index = ParseHexULong(w, "index"), - ValidatorIndex = ParseHexULong(w, "validatorIndex"), - Address = new Address(w.GetProperty("address").GetString()), - AmountInGwei = ParseHexULong(w, "amount"), - }; - } - } - - // Build header with full block-level fields - BlockHeader header = new( - parentHash, - Keccak.OfAnEmptySequenceRlp, - beneficiary, - UInt256.Zero, - blockNumber, - gasLimit, - timestamp, - Array.Empty(), - blobGasUsed, - excessBlobGas) - { - GasUsed = gasUsed, - BaseFeePerGas = baseFeePerGas, - MixHash = prevRandao, - IsPostMerge = true, - Hash = new Hash256(Bytes.FromHexString(payload.GetProperty("blockHash").GetString())), - ParentBeaconBlockRoot = parentBeaconBlockRoot, - TxRoot = TxTrie.CalculateRoot(transactions), - WithdrawalsRoot = withdrawals is not null ? WithdrawalTrie.CalculateRoot(withdrawals) : null, - }; - - return new Block(header, new BlockBody(transactions, Array.Empty(), withdrawals)); - } - - /// - /// Ensures the genesis state is loaded from the chainspec file into a shared TrieStore. - /// Subsequent calls are no-ops. Call CreateWorldState() to get a WorldState rooted at genesis. - /// - public static void EnsureGenesisInitialized(string genesisPath, IReleaseSpec spec) - { - if (s_genesisInitialized) return; - - lock (s_genesisLock) - { - if (s_genesisInitialized) return; - - s_dbProvider = TestMemDbProvider.Init(); - PruningConfig pruningConfig = new(); - TestFinalizedStateProvider finalizedStateProvider = new(pruningConfig.PruningBoundary); - - s_trieStore = new TrieStore( - new NodeStorage(s_dbProvider.StateDb), - No.Pruning, - Persist.EveryBlock, - finalizedStateProvider, - pruningConfig, - LimboLogs.Instance); - - finalizedStateProvider.TrieStore = s_trieStore; - s_codeDb = s_dbProvider.CodeDb; - - WorldState state = new( - new TrieStoreScopeProvider(s_trieStore, s_codeDb, LimboLogs.Instance), - LimboLogs.Instance); - - using (state.BeginScope(IWorldState.PreGenesis)) - { - LoadGenesisAccounts(state, genesisPath, spec); - state.Commit(spec); - state.CommitTree(0); - s_genesisStateRoot = state.StateRoot; - } - - s_genesisInitialized = true; - } - } - - public static Hash256 GenesisStateRoot - { - get - { - if (!s_genesisInitialized) - throw new InvalidOperationException("Genesis not initialized."); - return s_genesisStateRoot; - } - } - - /// - /// Creates a new WorldState backed by the shared TrieStore (which contains the genesis trie). - /// Caller must call BeginScope with a BlockHeader whose StateRoot is GenesisStateRoot. - /// - public static IWorldState CreateWorldState( - NodeStorageCache nodeStorageCache = null, - PreBlockCaches preBlockCaches = null, - bool populatePreBlockCache = true) - { - if (!s_genesisInitialized) - throw new InvalidOperationException("Genesis not initialized. Call EnsureGenesisInitialized first."); - - ITrieStore trieStore = s_trieStore; - if (nodeStorageCache is not null) - { - trieStore = new PreCachedTrieStore(trieStore, nodeStorageCache); - } - - IWorldStateScopeProvider scopeProvider = new TrieStoreScopeProvider(trieStore, s_codeDb, LimboLogs.Instance); - if (preBlockCaches is not null) - { - scopeProvider = new PrewarmerScopeProvider(scopeProvider, preBlockCaches, populatePreBlockCache); - } - - return new WorldState(scopeProvider, LimboLogs.Instance); - } - - public static IWorldStateManager CreateWorldStateManager(NodeStorageCache nodeStorageCache = null) - { - if (!s_genesisInitialized) - throw new InvalidOperationException("Genesis not initialized. Call EnsureGenesisInitialized first."); - - ITrieStore trieStore = s_trieStore; - if (nodeStorageCache is not null) - { - trieStore = new PreCachedTrieStore(trieStore, nodeStorageCache); - } - - IWorldStateScopeProvider scopeProvider = new TrieStoreScopeProvider(trieStore, s_codeDb, LimboLogs.Instance); - return new WorldStateManager(scopeProvider, s_trieStore, s_dbProvider, LimboLogs.Instance); - } - - private static void LoadGenesisAccounts(IWorldState state, string genesisPath, IReleaseSpec spec) - { - if (!File.Exists(genesisPath)) - { - string message = $"Genesis file not found: {genesisPath}\n" + - "Make sure the gas-benchmarks submodule is initialized:\n" + - " git lfs install && git submodule update --init tools/gas-benchmarks"; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - message += "\n On Windows, you may also need: git config --global core.longpaths true"; - throw new FileNotFoundException(message); - } - - using FileStream fs = File.OpenRead(genesisPath); - - // Detect Git LFS pointer (starts with "version https://git-lfs") - byte[] header = new byte[8]; - int read = fs.Read(header, 0, header.Length); - fs.Position = 0; - - if (read >= 7 && header[0] == (byte)'v' && header[1] == (byte)'e' && header[2] == (byte)'r') - { - throw new InvalidOperationException( - $"Genesis file appears to be a Git LFS pointer: {genesisPath}\n" + - "Git LFS was not installed when the submodule was cloned. Fix with:\n" + - " git lfs install && cd tools/gas-benchmarks && git lfs pull"); - } - - using JsonDocument doc = JsonDocument.Parse(fs); - JsonElement accounts = doc.RootElement.GetProperty("accounts"); - - foreach (JsonProperty entry in accounts.EnumerateObject()) - { - // Skip builtin precompile definitions - if (entry.Value.TryGetProperty("builtin", out _)) - continue; - - Address address = new(entry.Name); - - UInt256 balance = UInt256.Zero; - if (entry.Value.TryGetProperty("balance", out JsonElement balanceEl)) - UInt256.TryParse(balanceEl.GetString(), out balance); - - state.CreateAccount(address, balance); - - if (entry.Value.TryGetProperty("nonce", out JsonElement nonceEl)) - { - UInt256 nonce = ParseHexUInt256(nonceEl.GetString()); - if (nonce > UInt256.Zero) - state.IncrementNonce(address, nonce); - } - - if (entry.Value.TryGetProperty("code", out JsonElement codeEl)) - { - string codeHex = codeEl.GetString(); - if (codeHex is not null && codeHex.Length > 2) - { - byte[] code = Bytes.FromHexString(codeHex); - ValueHash256 codeHash = ValueKeccak.Compute(code); - state.InsertCode(address, in codeHash, code, spec, isGenesis: true); - } - } - - if (entry.Value.TryGetProperty("storage", out JsonElement storageEl)) - { - foreach (JsonProperty storageEntry in storageEl.EnumerateObject()) - { - UInt256 slot = ParseHexUInt256(storageEntry.Name); - byte[] value = Bytes.FromHexString(storageEntry.Value.GetString()); - state.Set(new StorageCell(address, slot), value); - } - } - } - } - - /// - /// Reads the raw JSON-RPC line from a payload file. Used by NewPayload mode to measure deserialization. - /// - public static string ReadRawJson(string filePath) - { - using StreamReader reader = new(filePath); - return reader.ReadLine(); - } - - /// - /// Parses expected stateRoot and blockHash from a payload file for verification. - /// - public static (Hash256 StateRoot, Hash256 BlockHash) ParseExpectedHashes(string filePath) - { - string firstLine = ReadRawJson(filePath); - - using JsonDocument doc = JsonDocument.Parse(firstLine); - JsonElement payload = doc.RootElement.GetProperty("params")[0]; - - Hash256 stateRoot = null; - if (payload.TryGetProperty("stateRoot", out JsonElement stateRootEl)) - { - string hex = stateRootEl.GetString(); - if (hex is not null && hex.Length > 2) - stateRoot = new Hash256(Bytes.FromHexString(hex)); - } - - Hash256 blockHash = null; - if (payload.TryGetProperty("blockHash", out JsonElement blockHashEl)) - { - string hex = blockHashEl.GetString(); - if (hex is not null && hex.Length > 2) - blockHash = new Hash256(Bytes.FromHexString(hex)); - } - - return (stateRoot, blockHash); - } - - /// - /// Verifies a processed block's state root and block hash against expected values from the payload. - /// - public static void VerifyProcessedBlock(Block processedBlock, string scenarioName, string filePath) - { - (Hash256 expectedStateRoot, Hash256 expectedBlockHash) = ParseExpectedHashes(filePath); - - if (expectedStateRoot is not null && processedBlock.Header.StateRoot != expectedStateRoot) - { - throw new InvalidOperationException( - $"State root mismatch for {scenarioName}!\n" + - $" Expected: {expectedStateRoot}\n" + - $" Computed: {processedBlock.Header.StateRoot}\n" + - "Block processing produced incorrect results."); - } - - if (expectedBlockHash is not null && processedBlock.Header.Hash != expectedBlockHash) - { - throw new InvalidOperationException( - $"Block hash mismatch for {scenarioName}!\n" + - $" Expected: {expectedBlockHash}\n" + - $" Computed: {processedBlock.Header.Hash}\n" + - $" StateRoot match: {processedBlock.Header.StateRoot == expectedStateRoot}\n" + - "Block processing produced a different block hash — some header field differs."); - } - } - - private static long ParseHexLong(JsonElement parent, string propertyName) - { - string hex = parent.GetProperty(propertyName).GetString(); - return Convert.ToInt64(hex, 16); - } - - private static ulong ParseHexULong(JsonElement parent, string propertyName) - { - string hex = parent.GetProperty(propertyName).GetString(); - return ParseHexULong(hex); - } - - private static ulong ParseHexULong(string hex) - { - return Convert.ToUInt64(hex, 16); - } - - private static UInt256 ParseHexUInt256(JsonElement parent, string propertyName) - { - string hex = parent.GetProperty(propertyName).GetString(); - return ParseHexUInt256(hex); - } - - private static UInt256 ParseHexUInt256(string hex) - { - if (hex is null || hex == "0x" || hex == "0x0" || hex == "0x00") - return UInt256.Zero; - - ReadOnlySpan hexSpan = hex.AsSpan(2); - UInt256.TryParse(hexSpan, System.Globalization.NumberStyles.HexNumber, null, out UInt256 result); - return result; - } -} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/Nethermind.Evm.Benchmark.csproj b/src/Nethermind/Nethermind.Evm.Benchmark/Nethermind.Evm.Benchmark.csproj index fc3ed5d184b2..3dad15dc2826 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/Nethermind.Evm.Benchmark.csproj +++ b/src/Nethermind/Nethermind.Evm.Benchmark/Nethermind.Evm.Benchmark.csproj @@ -1,20 +1,12 @@ - - Exe - false - - - - - diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/Program.cs b/src/Nethermind/Nethermind.Evm.Benchmark/Program.cs deleted file mode 100644 index 37708d2ce2d8..000000000000 --- a/src/Nethermind/Nethermind.Evm.Benchmark/Program.cs +++ /dev/null @@ -1,347 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Diagnostics; -using System.IO; -using BenchmarkDotNet.Running; -using Nethermind.Blockchain; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Core.Specs; -using Nethermind.Evm; -using Nethermind.Evm.Benchmark; -using Nethermind.Evm.Benchmark.GasBenchmarks; -using Nethermind.Evm.State; -using Nethermind.Evm.Tracing; -using Nethermind.Evm.TransactionProcessing; -using Nethermind.Logging; -using Nethermind.Specs; -using Nethermind.Specs.Forks; - -if (args.Length > 0 && args[0] == "--diag") -{ - string pattern = args.Length > 1 ? args[1] : "*"; - RunDiagnostic(pattern); - return; -} - -int inprocessIndex = Array.IndexOf(args, "--inprocess"); -if (inprocessIndex >= 0) -{ - GasBenchmarkConfig.InProcess = true; - args = RemoveArguments(args, inprocessIndex, 1); -} - -args = ApplyModeFilter(args); -args = ApplyChunkFilter(args); - -ConfigureTimingFilePath(); -BenchmarkSwitcher.FromAssembly(typeof(EvmBenchmarks).Assembly).Run(args); -GasNewPayloadMeasuredBenchmarks.PrintFinalTimingBreakdown(); -GasNewPayloadBenchmarks.PrintFinalTimingBreakdown(); - -static void ConfigureTimingFilePath() -{ - string measuredTimingFilePath = Path.Combine( - Path.GetTempPath(), - $"nethermind-newpayload-timing-{Guid.NewGuid():N}.jsonl"); - string measuredTimingReportFilePath = Path.Combine( - Directory.GetCurrentDirectory(), - "BenchmarkDotNet.Artifacts", - "results", - $"newpayload-measured-timing-breakdown-{DateTime.UtcNow:yyyyMMdd-HHmmss}-{Guid.NewGuid():N}.txt"); - string realTimingFilePath = Path.Combine( - Path.GetTempPath(), - $"nethermind-newpayload-real-timing-{Guid.NewGuid():N}.jsonl"); - string realTimingReportFilePath = Path.Combine( - Directory.GetCurrentDirectory(), - "BenchmarkDotNet.Artifacts", - "results", - $"newpayload-timing-breakdown-{DateTime.UtcNow:yyyyMMdd-HHmmss}-{Guid.NewGuid():N}.txt"); - - Environment.SetEnvironmentVariable(GasNewPayloadMeasuredBenchmarks.TimingFileEnvVar, measuredTimingFilePath); - Environment.SetEnvironmentVariable(GasNewPayloadMeasuredBenchmarks.TimingReportFileEnvVar, measuredTimingReportFilePath); - Environment.SetEnvironmentVariable(GasNewPayloadBenchmarks.TimingFileEnvVar, realTimingFilePath); - Environment.SetEnvironmentVariable(GasNewPayloadBenchmarks.TimingReportFileEnvVar, realTimingReportFilePath); -} - -static string[] RemoveArguments(string[] args, int index, int removeCount) -{ - string[] remaining = new string[args.Length - removeCount]; - Array.Copy(args, 0, remaining, 0, index); - Array.Copy(args, index + removeCount, remaining, index, args.Length - index - removeCount); - return remaining; -} - -static string[] MergeWithClassFilter(string[] args, string classFilter) -{ - int filterIndex = Array.IndexOf(args, "--filter"); - if (filterIndex >= 0 && filterIndex + 1 < args.Length) - { - string existingFilter = args[filterIndex + 1].Trim('"'); - args[filterIndex + 1] = classFilter.TrimEnd('*') + "*" + existingFilter.TrimStart('*'); - return args; - } - - string[] withFilter = new string[args.Length + 2]; - Array.Copy(args, withFilter, args.Length); - withFilter[args.Length] = "--filter"; - withFilter[args.Length + 1] = classFilter; - return withFilter; -} - -static (string Value, int RemoveCount) GetOptionValue(string[] args, int optionIndex, string optionName) -{ - string token = args[optionIndex]; - int separatorIndex = token.IndexOfAny(new[] { '=', ':' }); - if (separatorIndex >= 0) - { - return (token[(separatorIndex + 1)..], 1); - } - - if (optionIndex + 1 < args.Length) - { - return (args[optionIndex + 1], 2); - } - - throw new ArgumentException($"{optionName} requires a value."); -} - -static (string ClassFilter, bool? BuildBlocksOnMainState) ResolveModeDefinition(string modeValue) -{ - switch (modeValue.ToUpperInvariant()) - { - case "EVMEXECUTE": - return ("*GasPayloadExecuteBenchmarks*", null); - case "EVMBUILDUP": - return ("*GasPayloadBenchmarks*", null); - case "BLOCKBUILDING": - return ("*GasBlockBuildingBenchmarks*", false); - case "BLOCKBUILDINGMAINSTATE": - return ("*GasBlockBuildingBenchmarks*", true); - case "BLOCKONE": - return ("*GasBlockOne*", null); - case "BLOCK": - return ("*GasBlockBenchmarks*", null); - case "NEWPAYLOAD": - return ("*GasNewPayloadBenchmarks*", null); - case "NEWPAYLOADMEASURED": - return ("*GasNewPayloadMeasuredBenchmarks*", null); - default: - throw new ArgumentException($"Unknown --mode value: '{modeValue}'. Expected 'EVMExecute', 'EVMBuildUp', 'BlockBuilding', 'BlockBuildingMainState', 'BlockOne', 'Block', 'NewPayload', or 'NewPayloadMeasured'."); - } -} - -static string[] ApplyModeFilter(string[] args) -{ - int modeIndex = -1; - for (int i = 0; i < args.Length; i++) - { - if (args[i].StartsWith("--mode=", StringComparison.OrdinalIgnoreCase) - || args[i].StartsWith("--mode:", StringComparison.OrdinalIgnoreCase) - || string.Equals(args[i], "--mode", StringComparison.OrdinalIgnoreCase)) - { - modeIndex = i; - break; - } - } - - if (modeIndex < 0) - { - return args; - } - - (string modeValue, int removeCount) = GetOptionValue(args, modeIndex, "--mode"); - (string classFilter, bool? buildBlocksOnMainStateValue) = ResolveModeDefinition(modeValue); - if (buildBlocksOnMainStateValue is bool buildBlocksOnMainState) - { - Environment.SetEnvironmentVariable( - GasBlockBuildingBenchmarks.BuildBlocksOnMainStateEnvVar, - buildBlocksOnMainState ? bool.TrueString : bool.FalseString); - } - - string[] remaining = RemoveArguments(args, modeIndex, removeCount); - return MergeWithClassFilter(remaining, classFilter); -} - -static string[] ApplyChunkFilter(string[] args) -{ - int chunkIndex = -1; - for (int i = 0; i < args.Length; i++) - { - if (args[i].StartsWith("--chunk", StringComparison.OrdinalIgnoreCase) - || string.Equals(args[i], "--chunk", StringComparison.OrdinalIgnoreCase)) - { - chunkIndex = i; - break; - } - } - - if (chunkIndex < 0) - { - return args; - } - - (string chunkValue, int removeCount) = GetOptionValue(args, chunkIndex, "--chunk"); - string[] parts = chunkValue.Split('/'); - if (parts.Length != 2 || !int.TryParse(parts[0], out int n) || !int.TryParse(parts[1], out int m) || n < 1 || n > m) - { - throw new ArgumentException($"Invalid --chunk value: '{chunkValue}'. Expected format N/M where 1 <= N <= M (e.g. 2/5)"); - } - - GasBenchmarkConfig.ChunkIndex = n; - GasBenchmarkConfig.ChunkTotal = m; - return RemoveArguments(args, chunkIndex, removeCount); -} - -static void RunDiagnostic(string pattern) -{ - string repoRoot = FindRepoRoot(); - string gasBenchmarksRoot = Path.Combine(repoRoot, "tools", "gas-benchmarks"); - string genesisPath = Path.Combine(gasBenchmarksRoot, "scripts", "genesisfiles", "nethermind", "zkevmgenesis.json"); - string testingDir = Path.Combine(gasBenchmarksRoot, "eest_tests", "testing"); - - string matchedFile = FindFirstMatchingTestFile(testingDir, pattern); - if (matchedFile is null) - { - Console.WriteLine($"ERROR: No test file matching '{pattern}' found"); - return; - } - - Console.WriteLine($"Test file: {Path.GetFileName(matchedFile)}"); - - IReleaseSpec pragueSpec = Prague.Instance; - ISpecProvider specProvider = new SingleReleaseSpecProvider(pragueSpec, 1, 1); - - Console.WriteLine("Loading genesis..."); - Stopwatch sw = Stopwatch.StartNew(); - PayloadLoader.EnsureGenesisInitialized(genesisPath, pragueSpec); - Console.WriteLine($"Genesis loaded in {sw.ElapsedMilliseconds}ms, StateRoot={PayloadLoader.GenesisStateRoot}"); - - IWorldState state = PayloadLoader.CreateWorldState(); - BlockHeader genesisBlock = new(Keccak.Zero, Keccak.OfAnEmptySequenceRlp, Address.Zero, 0, 0, 0, 0, Array.Empty()) - { - StateRoot = PayloadLoader.GenesisStateRoot - }; - IDisposable scope = state.BeginScope(genesisBlock); - - (BlockHeader header, Transaction[] txs) = PayloadLoader.LoadPayload(matchedFile); - Console.WriteLine($"Block: number={header.Number}, gasLimit={header.GasLimit}, gasUsed={header.GasUsed}"); - Console.WriteLine($"Transactions: {txs.Length}"); - - for (int i = 0; i < txs.Length; i++) - { - Transaction tx = txs[i]; - Console.WriteLine($"\nTx[{i}]:"); - Console.WriteLine($" SenderAddress: {tx.SenderAddress}"); - Console.WriteLine($" To: {tx.To}"); - Console.WriteLine($" Nonce: {tx.Nonce}"); - Console.WriteLine($" GasLimit: {tx.GasLimit}"); - Console.WriteLine($" GasPrice: {tx.GasPrice}"); - Console.WriteLine($" Value: {tx.Value}"); - Console.WriteLine($" Data length: {tx.Data.Length}"); - - if (tx.SenderAddress is not null) - { - bool senderExists = state.AccountExists(tx.SenderAddress); - Console.WriteLine($" Sender exists: {senderExists}"); - if (senderExists) - { - Console.WriteLine($" Sender balance: {state.GetBalance(tx.SenderAddress)}"); - Console.WriteLine($" Sender nonce: {state.GetNonce(tx.SenderAddress)}"); - } - } - - if (tx.To is not null) - { - bool toExists = state.AccountExists(tx.To); - Console.WriteLine($" To exists: {toExists}"); - if (toExists) - { - Console.WriteLine($" To code size: {state.GetCodeHash(tx.To)}"); - Console.WriteLine($" To has code: {state.GetCodeHash(tx.To) != Keccak.OfAnEmptyString}"); - } - } - } - - TestBlockhashProvider blockhashProvider = new(); - EthereumCodeInfoRepository codeInfoRepo = new(state); - EthereumVirtualMachine vm = new(blockhashProvider, specProvider, LimboLogs.Instance); - - ITransactionProcessor txProcessor = new EthereumTransactionProcessor( - BlobBaseFeeCalculator.Instance, - specProvider, - state, - vm, - codeInfoRepo, - LimboLogs.Instance); - - string setupFile = GasPayloadBenchmarks.FindSetupFile(Path.GetFileName(matchedFile)); - if (setupFile is not null) - { - Console.WriteLine($"\nSetup file: {Path.GetFileName(setupFile)}"); - (BlockHeader setupHeader, Transaction[] setupTxs) = PayloadLoader.LoadPayload(setupFile); - txProcessor.SetBlockExecutionContext(setupHeader); - for (int i = 0; i < setupTxs.Length; i++) - { - txProcessor.Execute(setupTxs[i], NullTxTracer.Instance); - } - state.Commit(pragueSpec); - Console.WriteLine($"Setup complete: {setupTxs.Length} transactions executed"); - } - - txProcessor.SetBlockExecutionContext(header); - - Console.WriteLine("\n--- Executing transactions ---"); - for (int i = 0; i < txs.Length; i++) - { - sw.Restart(); - TransactionResult result = txProcessor.BuildUp(txs[i], NullTxTracer.Instance); - sw.Stop(); - Console.WriteLine($"Tx[{i}] result: {result}, elapsed: {sw.ElapsedMilliseconds}ms"); - } - - state.Reset(); - - Console.WriteLine("\n--- CallAndRestore ---"); - for (int i = 0; i < txs.Length; i++) - { - sw.Restart(); - txProcessor.CallAndRestore(txs[i], NullTxTracer.Instance); - sw.Stop(); - Console.WriteLine($"Tx[{i}] CallAndRestore elapsed: {sw.ElapsedMilliseconds}ms"); - } - - scope.Dispose(); - Console.WriteLine("\nDiagnostic complete."); -} - -static string FindFirstMatchingTestFile(string testingDir, string pattern) -{ - foreach (string dir in Directory.GetDirectories(testingDir)) - { - foreach (string file in Directory.GetFiles(dir, $"*{pattern}*")) - { - return file; - } - } - - return null; -} - -static string FindRepoRoot() -{ - string dir = AppDomain.CurrentDomain.BaseDirectory; - while (dir is not null) - { - if (Directory.Exists(Path.Combine(dir, ".git"))) - { - return dir; - } - - dir = Directory.GetParent(dir)?.FullName; - } - - throw new DirectoryNotFoundException("Could not find repository root."); -} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/Properties/launchSettings.json b/src/Nethermind/Nethermind.Evm.Benchmark/Properties/launchSettings.json deleted file mode 100644 index 9a268f547cbe..000000000000 --- a/src/Nethermind/Nethermind.Evm.Benchmark/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "Nethermind.Evm.Benchmark": { - "commandName": "Project", - "commandLineArgs": "--diag MULMOD" - } - } -} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs b/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs index b8da26cbc008..9865e3441676 100644 --- a/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs @@ -144,7 +144,7 @@ public void GetCachedCodeInfo_CodeTryGetDelegation_ReturnsCodeOfDelegation(byte[ stateProvider.InsertCode(delegationAddress, delegationCode, _releaseSpec); EthereumCodeInfoRepository sut = new(stateProvider); - CodeInfo result = sut.GetCachedCodeInfo(TestItem.AddressA, _releaseSpec); + ICodeInfo result = sut.GetCachedCodeInfo(TestItem.AddressA, _releaseSpec); result.CodeSpan.ToArray().Should().BeEquivalentTo(delegationCode); } diff --git a/src/Nethermind/Nethermind.Evm/BadInstructionException.cs b/src/Nethermind/Nethermind.Evm/BadInstructionException.cs new file mode 100644 index 000000000000..e8caed45f5cd --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/BadInstructionException.cs @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Evm; + +public class BadInstructionException : EvmException +{ + public override EvmExceptionType ExceptionType => EvmExceptionType.BadInstruction; +} diff --git a/src/Nethermind/Nethermind.Evm/CallResult.cs b/src/Nethermind/Nethermind.Evm/CallResult.cs index d23ac7ab54e3..08d88379284f 100644 --- a/src/Nethermind/Nethermind.Evm/CallResult.cs +++ b/src/Nethermind/Nethermind.Evm/CallResult.cs @@ -44,7 +44,7 @@ public CallResult(ReadOnlyMemory output, bool? precompileSuccess, int from FromVersion = fromVersion; } - public CallResult(CodeInfo? container, ReadOnlyMemory output, bool? precompileSuccess, int fromVersion, bool shouldRevert = false, EvmExceptionType exceptionType = EvmExceptionType.None) + public CallResult(ICodeInfo? container, ReadOnlyMemory output, bool? precompileSuccess, int fromVersion, bool shouldRevert = false, EvmExceptionType exceptionType = EvmExceptionType.None) { StateToExecute = null; Output = (container, output); @@ -64,7 +64,7 @@ private CallResult(EvmExceptionType exceptionType) } public VmState? StateToExecute { get; } - public (CodeInfo Container, ReadOnlyMemory Bytes) Output { get; } + public (ICodeInfo Container, ReadOnlyMemory Bytes) Output { get; } public EvmExceptionType ExceptionType { get; } public bool ShouldRevert { get; } public bool? PrecompileSuccess { get; } diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs index 75a16c118201..4e2320bd3ff6 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs @@ -3,101 +3,35 @@ using System; using System.Threading; -using Nethermind.Core.Extensions; -using Nethermind.Evm.Precompiles; namespace Nethermind.Evm.CodeAnalysis; -public class CodeInfo : IThreadPoolWorkItem, IEquatable +public sealed class CodeInfo(ReadOnlyMemory code) : ICodeInfo, IThreadPoolWorkItem { - public static CodeInfo Empty { get; } = new(); - // Empty code sentinel - private static readonly JumpDestinationAnalyzer? _emptyAnalyzer = new(Empty, skipAnalysis: true); + private static readonly JumpDestinationAnalyzer _emptyAnalyzer = new(Array.Empty()); + public static CodeInfo Empty { get; } = new(ReadOnlyMemory.Empty); + public ReadOnlyMemory Code { get; } = code; + ReadOnlySpan ICodeInfo.CodeSpan => Code.Span; - // Empty - private CodeInfo() - { - _analyzer = null; - } + private readonly JumpDestinationAnalyzer _analyzer = code.Length == 0 ? _emptyAnalyzer : new JumpDestinationAnalyzer(code); - protected CodeInfo(IPrecompile precompile, int version, ReadOnlyMemory code) - { - Precompile = precompile; - Version = version; - Code = code; - _analyzer = null; - } - - // Eof - protected CodeInfo(int version, ReadOnlyMemory code) - { - Version = version; - Code = code; - _analyzer = null; - } + public bool IsEmpty => ReferenceEquals(_analyzer, _emptyAnalyzer); - // Regular contract - public CodeInfo(ReadOnlyMemory code) + public bool ValidateJump(int destination) { - Code = code; - _analyzer = code.Length == 0 ? _emptyAnalyzer : new JumpDestinationAnalyzer(this); + return _analyzer.ValidateJump(destination); } - // Precompile - public CodeInfo(IPrecompile? precompile) + void IThreadPoolWorkItem.Execute() { - Precompile = precompile; - _analyzer = null; + _analyzer.Execute(); } - public ReadOnlyMemory Code { get; } - public ReadOnlySpan CodeSpan => Code.Span; - - public IPrecompile? Precompile { get; } - - private readonly JumpDestinationAnalyzer _analyzer; - - public bool IsEmpty => ReferenceEquals(_analyzer, _emptyAnalyzer); - public bool IsPrecompile => Precompile is not null; - - public bool ValidateJump(int destination) - => _analyzer?.ValidateJump(destination) ?? false; - - /// - /// Gets the version of the code format. - /// The default implementation returns 0, representing a legacy code format or non-EOF code. - /// - public int Version { get; } = 0; - - void IThreadPoolWorkItem.Execute() - => _analyzer?.Execute(); - public void AnalyzeInBackgroundIfRequired() { - if (!ReferenceEquals(_analyzer, _emptyAnalyzer) && (_analyzer?.RequiresAnalysis ?? false)) + if (!ReferenceEquals(_analyzer, _emptyAnalyzer) && _analyzer.RequiresAnalysis) { ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false); } } - - public override bool Equals(object? obj) - => Equals(obj as CodeInfo); - - public override int GetHashCode() - { - if (IsPrecompile) - return Precompile?.GetType().GetHashCode() ?? 0; - return CodeSpan.FastHash(); - } - - public bool Equals(CodeInfo? other) - { - if (other is null) - return false; - if (ReferenceEquals(this, other)) - return true; - if (IsPrecompile || other.IsPrecompile) - return Precompile?.GetType() == other.Precompile?.GetType(); - return CodeSpan.SequenceEqual(other.CodeSpan); - } } diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs index 7923f62aeafc..2ceca978a1ba 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs @@ -16,7 +16,7 @@ namespace Nethermind.Evm.CodeAnalysis; -public sealed class JumpDestinationAnalyzer(CodeInfo codeInfo, bool skipAnalysis = false) +public sealed class JumpDestinationAnalyzer(ReadOnlyMemory code) { private const int PUSH1 = (int)Instruction.PUSH1; private const int PUSHx = PUSH1 - 1; @@ -24,10 +24,10 @@ public sealed class JumpDestinationAnalyzer(CodeInfo codeInfo, bool skipAnalysis private const int BitShiftPerInt64 = 6; private static readonly long[]? _emptyJumpDestinationBitmap = new long[1]; - private long[]? _jumpDestinationBitmap = (codeInfo.Code.Length == 0 || skipAnalysis) ? _emptyJumpDestinationBitmap : null; + private long[]? _jumpDestinationBitmap = code.Length == 0 ? _emptyJumpDestinationBitmap : null; private object? _analysisComplete; - public ReadOnlyMemory MachineCode => codeInfo.Code; + private ReadOnlyMemory MachineCode { get; } = code; public bool ValidateJump(int destination) { diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs b/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs index 91ff0753e0e1..43fd8a86b8b2 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs @@ -10,7 +10,7 @@ namespace Nethermind.Evm.CodeAnalysis; public static class CodeInfoFactory { - public static CodeInfo CreateCodeInfo(ReadOnlyMemory code, IReleaseSpec spec, ValidationStrategy validationRules = ValidationStrategy.ExtractHeader) + public static ICodeInfo CreateCodeInfo(ReadOnlyMemory code, IReleaseSpec spec, ValidationStrategy validationRules = ValidationStrategy.ExtractHeader) { if (spec.IsEofEnabled && code.Span.StartsWith(EofValidator.MAGIC) @@ -23,7 +23,7 @@ public static CodeInfo CreateCodeInfo(ReadOnlyMemory code, IReleaseSpec sp return codeInfo; } - public static bool CreateInitCodeInfo(ReadOnlyMemory data, IReleaseSpec spec, [NotNullWhen(true)] out CodeInfo? codeInfo, out ReadOnlyMemory extraCallData) + public static bool CreateInitCodeInfo(ReadOnlyMemory data, IReleaseSpec spec, [NotNullWhen(true)] out ICodeInfo? codeInfo, out ReadOnlyMemory extraCallData) { extraCallData = default; if (spec.IsEofEnabled && data.Span.StartsWith(EofValidator.MAGIC)) diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs index 1fb55fdaf67f..959ed46ff4c3 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs @@ -19,7 +19,7 @@ namespace Nethermind.Evm; public class CodeInfoRepository : ICodeInfoRepository { private static readonly CodeLruCache _codeCache = new(); - private readonly FrozenDictionary _localPrecompiles; + private readonly FrozenDictionary _localPrecompiles; private readonly IWorldState _worldState; public CodeInfoRepository(IWorldState worldState, IPrecompileProvider precompileProvider) @@ -28,7 +28,7 @@ public CodeInfoRepository(IWorldState worldState, IPrecompileProvider precompile _worldState = worldState; } - public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) + public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) { delegationAddress = null; if (vmSpec.IsPrecompile(codeSource)) // _localPrecompiles have to have all precompiles @@ -36,7 +36,7 @@ public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IRe return _localPrecompiles[codeSource]; } - CodeInfo cachedCodeInfo = InternalGetCachedCode(_worldState, codeSource, vmSpec); + ICodeInfo cachedCodeInfo = InternalGetCachedCode(_worldState, codeSource, vmSpec); if (!cachedCodeInfo.IsEmpty && ICodeInfoRepository.TryGetDelegatedAddress(cachedCodeInfo.CodeSpan, out delegationAddress)) { @@ -47,21 +47,21 @@ public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IRe return cachedCodeInfo; } - private CodeInfo InternalGetCachedCode(Address codeSource, IReleaseSpec vmSpec) + private ICodeInfo InternalGetCachedCode(Address codeSource, IReleaseSpec vmSpec) { ref readonly ValueHash256 codeHash = ref _worldState.GetCodeHash(codeSource); return InternalGetCachedCode(_worldState, in codeHash, vmSpec); } - private static CodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, Address codeSource, IReleaseSpec vmSpec) + private static ICodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, Address codeSource, IReleaseSpec vmSpec) { ValueHash256 codeHash = worldState.GetCodeHash(codeSource); return InternalGetCachedCode(worldState, in codeHash, vmSpec); } - private static CodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, in ValueHash256 codeHash, IReleaseSpec vmSpec) + private static ICodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, in ValueHash256 codeHash, IReleaseSpec vmSpec) { - CodeInfo? cachedCodeInfo = null; + ICodeInfo? cachedCodeInfo = null; if (codeHash == Keccak.OfAnEmptyString.ValueHash256) { cachedCodeInfo = CodeInfo.Empty; @@ -101,7 +101,7 @@ public void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpe if (_worldState.InsertCode(codeOwner, in codeHash, code, spec) && _codeCache.Get(in codeHash) is null) { - CodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(code, spec, ValidationStrategy.ExtractHeader); + ICodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(code, spec, ValidationStrategy.ExtractHeader); _codeCache.Set(in codeHash, codeInfo); } } @@ -138,7 +138,7 @@ public ValueHash256 GetExecutableCodeHash(Address address, IReleaseSpec spec) return Keccak.OfAnEmptyString.ValueHash256; } - CodeInfo codeInfo = InternalGetCachedCode(_worldState, address, spec); + ICodeInfo codeInfo = InternalGetCachedCode(_worldState, address, spec); return codeInfo.IsEmpty ? Keccak.OfAnEmptyString.ValueHash256 : codeHash; @@ -154,33 +154,33 @@ private sealed class CodeLruCache { private const int CacheCount = 16; private const int CacheMax = CacheCount - 1; - private readonly ClockCache[] _caches; + private readonly ClockCache[] _caches; public CodeLruCache() { - _caches = new ClockCache[CacheCount]; + _caches = new ClockCache[CacheCount]; for (int i = 0; i < _caches.Length; i++) { // Cache per nibble to reduce contention as TxPool is very parallel - _caches[i] = new ClockCache(MemoryAllowance.CodeCacheSize / CacheCount); + _caches[i] = new ClockCache(MemoryAllowance.CodeCacheSize / CacheCount); } } - public CodeInfo? Get(in ValueHash256 codeHash) + public ICodeInfo? Get(in ValueHash256 codeHash) { - ClockCache cache = _caches[GetCacheIndex(codeHash)]; + ClockCache cache = _caches[GetCacheIndex(codeHash)]; return cache.Get(codeHash); } - public bool Set(in ValueHash256 codeHash, CodeInfo codeInfo) + public bool Set(in ValueHash256 codeHash, ICodeInfo codeInfo) { - ClockCache cache = _caches[GetCacheIndex(codeHash)]; + ClockCache cache = _caches[GetCacheIndex(codeHash)]; return cache.Set(codeHash, codeInfo); } private static int GetCacheIndex(in ValueHash256 codeHash) => codeHash.Bytes[^1] & CacheMax; - public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out CodeInfo? codeInfo) + public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out ICodeInfo? codeInfo) { codeInfo = Get(in codeHash); return codeInfo is not null; diff --git a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs index 82e5a8ee5b95..140a3034b36d 100644 --- a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs +++ b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs @@ -6,18 +6,17 @@ namespace Nethermind.Evm.CodeAnalysis; -public sealed class EofCodeInfo : CodeInfo +public sealed class EofCodeInfo(in EofContainer container) : ICodeInfo { - public EofCodeInfo(in EofContainer container) : base(container.Header.Version, container.Container) - { - EofContainer = container; - } - - public EofContainer EofContainer { get; private set; } + public EofContainer EofContainer { get; private set; } = container; + public ReadOnlyMemory Code => EofContainer.Container; + public int Version => EofContainer.Header.Version; + public bool IsEmpty => EofContainer.IsEmpty; public ReadOnlyMemory TypeSection => EofContainer.TypeSection; public ReadOnlyMemory CodeSection => EofContainer.CodeSection; public ReadOnlyMemory DataSection => EofContainer.DataSection; public ReadOnlyMemory ContainerSection => EofContainer.ContainerSection; + ReadOnlySpan ICodeInfo.CodeSpan => CodeSection.Span; public SectionHeader CodeSectionOffset(int sectionId) => EofContainer.Header.CodeSections[sectionId]; public SectionHeader? ContainerSectionOffset(int sectionId) => EofContainer.Header.ContainerSections.Value[sectionId]; diff --git a/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs b/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs index ff60de7b2723..0f396d82bd47 100644 --- a/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs +++ b/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs @@ -22,7 +22,7 @@ public sealed class ExecutionEnvironment : IDisposable /// /// Parsed bytecode for the current call. /// - public CodeInfo CodeInfo { get; private set; } = null!; + public ICodeInfo CodeInfo { get; private set; } = null!; /// /// Currently executing account (in DELEGATECALL this will be equal to caller). @@ -65,7 +65,7 @@ private ExecutionEnvironment() { } /// Rents an ExecutionEnvironment from the pool and initializes it with the provided values. /// public static ExecutionEnvironment Rent( - CodeInfo codeInfo, + ICodeInfo codeInfo, Address executingAccount, Address caller, Address? codeSource, diff --git a/src/Nethermind/Nethermind.Evm/ICodeInfo.cs b/src/Nethermind/Nethermind.Evm/ICodeInfo.cs new file mode 100644 index 000000000000..07156260530e --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/ICodeInfo.cs @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Evm.Precompiles; + +namespace Nethermind.Evm.CodeAnalysis; + +/// +/// Represents common code information for EVM execution. +/// Implementations include , (EVM Object Format), +/// and for precompiled contracts. +/// +public interface ICodeInfo +{ + /// + /// Gets the version of the code format. + /// The default implementation returns 0, representing a legacy code format or non-EOF code. + /// + int Version => 0; + + /// + /// Indicates whether the code is empty or not. + /// + bool IsEmpty { get; } + + /// + /// Gets the raw machine code as a segment. + /// This is the primary code section from which the EVM executes instructions. + /// + ReadOnlyMemory Code { get; } + + /// + /// Indicates whether this code represents a precompiled contract. + /// By default, this returns false. + /// + bool IsPrecompile => false; + IPrecompile? Precompile => null; + + /// + /// Gets the code section. + /// By default, this returns the same contents as . + /// + ReadOnlyMemory CodeSection => Code; + ReadOnlySpan CodeSpan { get; } + + /// + /// Gets the data section, which is reserved for additional data segments in EOF. + /// By default, this returns an empty memory segment. + /// + ReadOnlyMemory DataSection => Memory.Empty; + + /// + /// Computes the offset to be added to the program counter when executing instructions. + /// By default, this returns 0, meaning no offset is applied. + /// + /// The program counter offset for this code format. + int PcOffset() => 0; + + /// + /// Validates whether a jump destination is permissible according to this code format. + /// By default, this returns false. + /// + /// The instruction index to validate. + /// true if the jump is valid; otherwise, false. + bool ValidateJump(int destination) => false; +} diff --git a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs index 20cedaa8233d..17e568d007e0 100644 --- a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs @@ -11,7 +11,7 @@ namespace Nethermind.Evm; public interface ICodeInfoRepository { - CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress); + ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress); ValueHash256 GetExecutableCodeHash(Address address, IReleaseSpec spec); void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec); void SetDelegation(Address codeSource, Address authority, IReleaseSpec spec); @@ -37,8 +37,8 @@ static bool TryGetDelegatedAddress(ReadOnlySpan code, [NotNullWhen(true)] public static class CodeInfoRepositoryExtensions { - public static CodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, Address codeSource, IReleaseSpec vmSpec) + public static ICodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, Address codeSource, IReleaseSpec vmSpec) => codeInfoRepository.GetCachedCodeInfo(codeSource, vmSpec, out _); - public static CodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress) + public static ICodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress) => codeInfoRepository.GetCachedCodeInfo(codeSource, true, vmSpec, out delegationAddress); } diff --git a/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs index 5b9dd5dd3697..6cee9b50c462 100644 --- a/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs @@ -9,7 +9,7 @@ namespace Nethermind.Evm; public interface IOverridableCodeInfoRepository : ICodeInfoRepository { - void SetCodeOverride(IReleaseSpec vmSpec, Address key, CodeInfo value); + void SetCodeOverride(IReleaseSpec vmSpec, Address key, ICodeInfo value); void MovePrecompile(IReleaseSpec vmSpec, Address precompileAddr, Address targetAddr); void ResetOverrides(); void ResetPrecompileOverrides(); diff --git a/src/Nethermind/Nethermind.Evm/IPrecompileProvider.cs b/src/Nethermind/Nethermind.Evm/IPrecompileProvider.cs index 6e17d2e8c3d3..d6654879331d 100644 --- a/src/Nethermind/Nethermind.Evm/IPrecompileProvider.cs +++ b/src/Nethermind/Nethermind.Evm/IPrecompileProvider.cs @@ -9,5 +9,5 @@ namespace Nethermind.Evm; public interface IPrecompileProvider { - public FrozenDictionary GetPrecompiles(); + public FrozenDictionary GetPrecompiles(); } diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs index 2728f87e21b2..e802d92416fe 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs @@ -177,7 +177,7 @@ public static EvmExceptionType InstructionCall( if (!TGasPolicy.UpdateMemoryCost(ref gas, in a, result, vm.VmState)) goto OutOfGas; - CodeInfo codeInfo = vm.GetExtCodeInfoCached(address, spec); + ICodeInfo codeInfo = vm.GetExtCodeInfoCached(address, spec); // Get the external code from the repository. ReadOnlySpan externalCode = codeInfo.CodeSpan; diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs index d433f76793e7..66f79084b2cb 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs @@ -200,7 +200,7 @@ public static EvmExceptionType InstructionCreate(Vir where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; // Ensure the instruction is only valid for non-legacy (EOF) code. - if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) + if (codeInfo.Version == 0) goto BadInstruction; // Deduct gas required for data loading. @@ -192,7 +193,8 @@ public static EvmExceptionType InstructionDataLoadN(Vi where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; + if (codeInfo.Version == 0) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.DataLoadN)) @@ -223,7 +225,8 @@ public static EvmExceptionType InstructionDataSize(Vir where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; + if (codeInfo.Version == 0) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.DataSize)) @@ -248,7 +251,8 @@ public static EvmExceptionType InstructionDataCopy(Vir where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; + if (codeInfo.Version == 0) goto BadInstruction; // Pop destination memory offset, data section offset, and size. @@ -299,7 +303,8 @@ public static EvmExceptionType InstructionDataCopy(Vir public static EvmExceptionType InstructionRelativeJump(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; + if (codeInfo.Version == 0) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.RJump)) @@ -325,7 +330,8 @@ public static EvmExceptionType InstructionRelativeJump(VirtualMachin public static EvmExceptionType InstructionRelativeJumpIf(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; + if (codeInfo.Version == 0) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.RJumpi)) @@ -359,7 +365,8 @@ public static EvmExceptionType InstructionRelativeJumpIf(VirtualMach public static EvmExceptionType InstructionJumpTable(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; + if (codeInfo.Version == 0) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.RJumpv)) @@ -399,7 +406,7 @@ public static EvmExceptionType InstructionJumpTable(VirtualMachine(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - CodeInfo iCodeInfo = vm.VmState.Env.CodeInfo; + ICodeInfo iCodeInfo = vm.VmState.Env.CodeInfo; if (iCodeInfo.Version == 0) goto BadInstruction; @@ -455,7 +462,7 @@ public static EvmExceptionType InstructionCallFunction(VirtualMachin public static EvmExceptionType InstructionReturnFunction(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - CodeInfo codeInfo = vm.VmState.Env.CodeInfo; + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; if (codeInfo.Version == 0) goto BadInstruction; @@ -483,7 +490,7 @@ public static EvmExceptionType InstructionReturnFunction(VirtualMach public static EvmExceptionType InstructionJumpFunction(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - CodeInfo iCodeInfo = vm.VmState.Env.CodeInfo; + ICodeInfo iCodeInfo = vm.VmState.Env.CodeInfo; if (iCodeInfo.Version == 0) goto BadInstruction; @@ -523,7 +530,8 @@ public static EvmExceptionType InstructionDupN(Virtual where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; + if (codeInfo.Version == 0) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Dupn)) @@ -553,7 +561,8 @@ public static EvmExceptionType InstructionSwapN(Virtua where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; + if (codeInfo.Version == 0) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Swapn)) @@ -582,7 +591,8 @@ public static EvmExceptionType InstructionExchange(Vir where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; + if (codeInfo.Version == 0) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Swapn)) @@ -732,7 +742,7 @@ public static EvmExceptionType InstructionEofCreate(Vi state.SubtractFromBalance(env.ExecutingAccount, value, spec); // Create new code info for the init code. - CodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(initContainer, spec, ValidationStrategy.ExtractHeader); + ICodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(initContainer, spec, ValidationStrategy.ExtractHeader); // 8. Prepare the callData from the caller’s memory slice. if (!vm.VmState.Memory.TryLoad(dataOffset, dataSize, out ReadOnlyMemory callData)) @@ -833,7 +843,7 @@ public static EvmExceptionType InstructionReturnDataLoad EvmExceptionType.InvalidCode; + } +} diff --git a/src/Nethermind/Nethermind.Evm/Metrics.cs b/src/Nethermind/Nethermind.Evm/Metrics.cs index 0f94291471b6..b4f5502b4161 100644 --- a/src/Nethermind/Nethermind.Evm/Metrics.cs +++ b/src/Nethermind/Nethermind.Evm/Metrics.cs @@ -19,11 +19,6 @@ public class Metrics private static readonly ZeroContentionCounter _codeDbCache = new(); [Description("Number of Code DB cache reads on thread.")] internal static long ThreadLocalCodeDbCache => _codeDbCache.ThreadLocalValue; - - /// - /// Gets thread-local code DB cache count. Use this for external access. - /// - public static long GetThreadLocalCodeDbCache() => _codeDbCache.ThreadLocalValue; internal static void IncrementCodeDbCache() => _codeDbCache.Increment(); [CounterMetric] [Description("Number of EVM exceptions thrown by contracts.")] @@ -173,18 +168,6 @@ internal static float BlockEstMedianGasPrice } } - /// - /// Gets block gas price data for external access. Returns (min, estMedian, ave, max). - /// Returns null if no gas data available (min is float.MaxValue). - /// - public static (float Min, float EstMedian, float Ave, float Max)? GetBlockGasPrices() - { - if (_blockMinGasPrice == float.MaxValue) - return null; - - return (_blockMinGasPrice, _blockEstMedianGasPrice, _blockAveGasPrice, _blockMaxGasPrice); - } - [GaugeMetric] [Description("Minimum tx gas price in block")] public static float GasPriceMin { get; private set; } diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/PrecompileInfo.cs b/src/Nethermind/Nethermind.Evm/Precompiles/PrecompileInfo.cs new file mode 100644 index 000000000000..66c6da879ea4 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/Precompiles/PrecompileInfo.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Evm.Precompiles; + +namespace Nethermind.Evm.CodeAnalysis; + +public sealed class PrecompileInfo(IPrecompile precompile) : ICodeInfo +{ + public ReadOnlyMemory Code => Array.Empty(); + ReadOnlySpan ICodeInfo.CodeSpan => Code.Span; + public IPrecompile? Precompile { get; } = precompile; + + public bool IsPrecompile => true; + public bool IsEmpty => false; +} diff --git a/src/Nethermind/Nethermind.Evm/TransactionCollisionException.cs b/src/Nethermind/Nethermind.Evm/TransactionCollisionException.cs new file mode 100644 index 000000000000..0c577ef83c04 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/TransactionCollisionException.cs @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Evm +{ + public class TransactionCollisionException : EvmException + { + public override EvmExceptionType ExceptionType => EvmExceptionType.TransactionCollision; + } +} diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs index 2ae5c1892b50..e927b73bf0ec 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs @@ -614,7 +614,7 @@ private TransactionResult BuildExecutionEnvironment( { Address recipient = tx.GetRecipient(tx.IsContractCreation ? WorldState.GetNonce(tx.SenderAddress!) : 0); if (recipient is null) ThrowInvalidDataException("Recipient has not been resolved properly before tx execution"); - CodeInfo? codeInfo; + ICodeInfo? codeInfo; ReadOnlyMemory inputData = tx.IsMessageCall ? tx.Data : default; if (tx.IsContractCreation) { diff --git a/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs b/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs index 624ac196f00a..f335e854c52e 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs @@ -52,7 +52,7 @@ public readonly ref struct TransactionSubstate public string? Error { get; } public string? SubstateError { get; } public EvmExceptionType EvmExceptionType { get; } - public (CodeInfo DeployCode, ReadOnlyMemory Bytes) Output { get; } + public (ICodeInfo DeployCode, ReadOnlyMemory Bytes) Output { get; } public bool ShouldRevert { get; } public long Refund { get; } public IToArrayCollection Logs => _logs ?? _emptyLogs; @@ -80,7 +80,7 @@ private TransactionSubstate(string errorCode) ShouldRevert = true; } - public TransactionSubstate((CodeInfo eofDeployCode, ReadOnlyMemory bytes) output, + public TransactionSubstate((ICodeInfo eofDeployCode, ReadOnlyMemory bytes) output, long refund, IHashSetEnumerableCollection
destroyList, IToArrayCollection logs, diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs index 0d785c9b28f8..60eff6b6993e 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs @@ -41,7 +41,7 @@ internal uint GetExtCodeSizeCached(Address address, IReleaseSpec spec) { if (_maxExtCodeCacheEntries == 0) { - CodeInfo uncachedCodeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + ICodeInfo uncachedCodeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); return uncachedCodeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)uncachedCodeInfo.CodeSpan.Length; } @@ -54,7 +54,7 @@ internal uint GetExtCodeSizeCached(Address address, IReleaseSpec spec) return entry.CodeSize; } - CodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + ICodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); uint codeSize = codeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)codeInfo.CodeSpan.Length; StoreCacheEntry(key, codeHash, codeSize, codeInfo); @@ -80,11 +80,11 @@ private void ResetExtCodeCacheForBlock() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal CodeInfo GetExtCodeInfoCached(Address address, IReleaseSpec spec) + internal ICodeInfo GetExtCodeInfoCached(Address address, IReleaseSpec spec) { if (_maxExtCodeCacheEntries == 0) { - CodeInfo uncachedCodeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + ICodeInfo uncachedCodeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); return uncachedCodeInfo; } @@ -99,14 +99,14 @@ internal CodeInfo GetExtCodeInfoCached(Address address, IReleaseSpec spec) return entry.CodeInfo; } - CodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + ICodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); uint codeSize = codeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)codeInfo.CodeSpan.Length; StoreCacheEntry(key, codeHash, codeSize, codeInfo); return codeInfo; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void StoreCacheEntry(AddressAsKey key, in ValueHash256 codeHash, uint codeSize, CodeInfo? codeInfo) + private void StoreCacheEntry(AddressAsKey key, in ValueHash256 codeHash, uint codeSize, ICodeInfo? codeInfo) { if (_maxExtCodeCacheEntries == 0) { @@ -120,5 +120,5 @@ private void StoreCacheEntry(AddressAsKey key, in ValueHash256 codeHash, uint co _extCodeCache[key] = new ExtCodeCacheEntry(codeHash, codeSize, codeInfo); } - private readonly record struct ExtCodeCacheEntry(ValueHash256 CodeHash, uint CodeSize, CodeInfo? CodeInfo); + private readonly record struct ExtCodeCacheEntry(ValueHash256 CodeHash, uint CodeSize, ICodeInfo? CodeInfo); } diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index cae5cc2df888..e26a5ea777f0 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -1216,7 +1216,7 @@ protected virtual unsafe CallResult RunByteCode( SectionIndex = VmState.FunctionIndex; // Retrieve the code information and create a read-only span of instructions. - CodeInfo codeInfo = VmState.Env.CodeInfo; + ICodeInfo codeInfo = VmState.Env.CodeInfo; ReadOnlySpan codeSection = GetInstructions(codeInfo); // Initialize the exception type to "None". @@ -1331,16 +1331,16 @@ protected virtual unsafe CallResult RunByteCode( debugger?.TryWait(ref _currentState, ref programCounter, ref gas, ref stack.Head); #endif // Process the return data based on its runtime type. - if (ReturnData is byte[] data) + if (ReturnData is VmState state) { - // Fall back to returning a CallResult with a byte array as the return data. - return new CallResult(null, data, null, codeInfo.Version); + return new CallResult(state); } - else if (ReturnData is VmState state) + else if (ReturnData is EofCodeInfo eofCodeInfo) { - return new CallResult(state); + return new CallResult(eofCodeInfo, ReturnDataBuffer, null, codeInfo.Version); } - return ReturnEof(codeInfo); + // Fall back to returning a CallResult with a byte array as the return data. + return new CallResult(null, (byte[])ReturnData, null, codeInfo.Version); Revert: // Return a CallResult indicating a revert. @@ -1356,7 +1356,7 @@ protected virtual unsafe CallResult RunByteCode( // Converts the code section bytes into a read-only span of instructions. // Lightest weight conversion as mostly just helpful when debugging to see what the opcodes are. - static ReadOnlySpan GetInstructions(CodeInfo codeInfo) + static ReadOnlySpan GetInstructions(ICodeInfo codeInfo) { ReadOnlySpan codeBytes = codeInfo.CodeSpan; return MemoryMarshal.CreateReadOnlySpan( @@ -1364,10 +1364,6 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(codeBytes)), codeBytes.Length); } - [MethodImpl(MethodImplOptions.NoInlining)] - CallResult ReturnEof(CodeInfo codeInfo) - => new(ReturnData as EofCodeInfo, ReturnDataBuffer, null, codeInfo.Version); - [DoesNotReturn] static void ThrowOperationCanceledException() => throw new OperationCanceledException("Cancellation Requested"); } diff --git a/src/Nethermind/Nethermind.Facade.Test/LogIndexBuilderTests.cs b/src/Nethermind/Nethermind.Facade.Test/LogIndexBuilderTests.cs deleted file mode 100644 index 8f5977660136..000000000000 --- a/src/Nethermind/Nethermind.Facade.Test/LogIndexBuilderTests.cs +++ /dev/null @@ -1,273 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Nethermind.Blockchain; -using Nethermind.Blockchain.Receipts; -using Nethermind.Blockchain.Synchronization; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Core.Events; -using Nethermind.Core.Extensions; -using Nethermind.Core.Test; -using Nethermind.Core.Test.Builders; -using Nethermind.Db.LogIndex; -using Nethermind.Facade.Find; -using Nethermind.Logging; -using NSubstitute; -using NUnit.Framework; - -namespace Nethermind.Facade.Test; - -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] -public class LogIndexBuilderTests -{ - private class TestLogIndexStorage : ILogIndexStorage - { - private int? _minBlockNumber; - private int? _maxBlockNumber; - - public bool Enabled => true; - - public event EventHandler? NewMaxBlockNumber; - public event EventHandler? NewMinBlockNumber; - - public int? MinBlockNumber - { - get => _minBlockNumber; - init => _minBlockNumber = value; - } - - public int? MaxBlockNumber - { - get => _maxBlockNumber; - init => _maxBlockNumber = value; - } - - public IEnumerator GetEnumerator(Address address, int from, int to) => - throw new NotImplementedException(); - - public IEnumerator GetEnumerator(int topicIndex, Hash256 topic, int from, int to) => - throw new NotImplementedException(); - - public string GetDbSize() => 0L.SizeToString(); - - public LogIndexAggregate Aggregate(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats = null) => - new(batch); - - public virtual Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null) - { - var min = Math.Min(aggregate.FirstBlockNum, aggregate.LastBlockNum); - var max = Math.Max(aggregate.FirstBlockNum, aggregate.LastBlockNum); - - if (_minBlockNumber is null || min < _minBlockNumber) - { - if (_minBlockNumber is not null && max != _minBlockNumber - 1) - throw new InvalidOperationException("Invalid receipts order."); - - _minBlockNumber = min; - NewMinBlockNumber?.Invoke(this, min); - } - - if (_maxBlockNumber is null || max > _maxBlockNumber) - { - if (_maxBlockNumber is not null && min != _maxBlockNumber + 1) - throw new InvalidOperationException("Invalid receipts order."); - - _maxBlockNumber = max; - NewMaxBlockNumber?.Invoke(this, max); - } - - return Task.CompletedTask; - } - - public Task RemoveReorgedAsync(BlockReceipts block) => Task.CompletedTask; - - public Task StopAsync() => Task.CompletedTask; - - public ValueTask DisposeAsync() => ValueTask.CompletedTask; - } - - private class FailingLogIndexStorage(int failAfter, Exception exception) : TestLogIndexStorage - { - private int _callCount; - - public override Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null) - { - return Interlocked.Increment(ref _callCount) <= failAfter - ? base.AddReceiptsAsync(aggregate, stats) - : throw exception; - } - } - - private const int MaxReorgDepth = 8; - private const int MaxBlock = 100; - private const int MaxSyncBlock = MaxBlock - MaxReorgDepth; - private const int BatchSize = 10; - - private ILogIndexConfig _config = null!; - private IBlockTree _blockTree = null!; - private ISyncConfig _syncConfig = null!; - private IReceiptStorage _receiptStorage = null!; - private ILogManager _logManager = null!; - private List _testDisposables = null!; - - [SetUp] - public void SetUp() - { - _config = new LogIndexConfig { Enabled = true, MaxReorgDepth = MaxReorgDepth, MaxBatchSize = BatchSize }; - _blockTree = Build.A.BlockTree().OfChainLength(MaxBlock + 1).BlockTree; - _syncConfig = new SyncConfig { FastSync = true, SnapSync = true }; - _receiptStorage = Substitute.For(); - _logManager = new TestLogManager(); - _testDisposables = []; - - Block head = _blockTree.Head!; - _blockTree.SyncPivot = (head.Number, head.Hash); - _syncConfig.PivotNumber = _blockTree.SyncPivot.BlockNumber; - - _receiptStorage - .Get(Arg.Any()) - .Returns(c => []); - } - - [TearDown] - public async Task TearDownAsync() - { - foreach (var disposable in _testDisposables) - { - if (disposable is IAsyncDisposable asyncDisposable) - await asyncDisposable.DisposeAsync(); - else if (disposable is IDisposable disposable1) - disposable1.Dispose(); - } - } - - private LogIndexBuilder GetService(ILogIndexStorage logIndexStorage) - { - return new LogIndexBuilder( - logIndexStorage, _config, _blockTree, _syncConfig, _receiptStorage, _logManager - ).AddTo(_testDisposables); - } - - [Test] - [CancelAfter(60_000)] - public async Task Should_SyncToBarrier( - [Values(1, 10)] int minBarrier, - [Values(1, 16, MaxBlock)] int batchSize, - [Values( - new[] { -1, -1 }, // -1 is treated as null - new[] { 0, MaxSyncBlock / 2 }, - new[] { MaxSyncBlock / 2, MaxSyncBlock / 2 }, - new[] { MaxSyncBlock / 2, MaxSyncBlock }, - new[] { 5, MaxSyncBlock - 5 } - )] - int[] synced, - CancellationToken cancellation - ) - { - _config.MaxBatchSize = batchSize; - _syncConfig.AncientReceiptsBarrier = minBarrier; - Assert.That(_syncConfig.AncientReceiptsBarrierCalc, Is.EqualTo(minBarrier)); - - var expectedMin = minBarrier <= 1 ? 0 : synced[0] < 0 ? minBarrier : Math.Min(synced[0], minBarrier); - var storage = new TestLogIndexStorage - { - MinBlockNumber = synced[0] < 0 ? null : synced[0], - MaxBlockNumber = synced[1] < 0 ? null : synced[1] - }; - - LogIndexBuilder builder = GetService(storage); - - Task completion = WaitBlocksAsync(storage, expectedMin, MaxSyncBlock, cancellation); - await builder.StartAsync(); - await completion; - - using (Assert.EnterMultipleScope()) - { - Assert.That(builder.LastError, Is.Null); - - Assert.That(storage.MinBlockNumber, Is.EqualTo(expectedMin)); - Assert.That(storage.MaxBlockNumber, Is.EqualTo(MaxSyncBlock)); - } - } - - [Test] - public async Task Should_ForwardError( - [Values(0, 1, 4)] int failAfter - ) - { - var exception = new Exception(nameof(Should_ForwardError)); - LogIndexBuilder builder = GetService(new FailingLogIndexStorage(failAfter, exception)); - - await builder.StartAsync(); - - using (Assert.EnterMultipleScope()) - { - Exception thrown = Assert.ThrowsAsync(() => builder.BackwardSyncCompletion.WaitAsync(TimeSpan.FromSeconds(999))); - Assert.That(thrown, Is.EqualTo(exception)); - Assert.That(builder.LastError, Is.EqualTo(exception)); - } - } - - [Test] - [Sequential] - public async Task Should_CompleteImmediately_IfAlreadySynced( - [Values(1, 10, 10, 10)] int minBarrier, - [Values(0, 00, 05, 10)] int minBlock - ) - { - Assert.That(minBlock, Is.LessThanOrEqualTo(minBarrier)); - - _syncConfig.AncientReceiptsBarrier = minBarrier; - LogIndexBuilder builder = GetService(new FailingLogIndexStorage(0, new("Should not set new receipts.")) - { - MinBlockNumber = minBlock, - MaxBlockNumber = MaxSyncBlock - }); - - await builder.StartAsync(); - - using (Assert.EnterMultipleScope()) - { - Assert.That(builder.BackwardSyncCompletion.IsCompleted); - Assert.That(builder.LastError, Is.Null); - Assert.That(builder.LastUpdate, Is.Null); - } - } - - private static Task WaitMaxBlockAsync(TestLogIndexStorage storage, int blockNumber, CancellationToken cancellation) - { - if (storage.MaxBlockNumber >= blockNumber) - return Task.CompletedTask; - - return Wait.ForEventCondition( - cancellation, - e => storage.NewMaxBlockNumber += e, - e => storage.NewMaxBlockNumber -= e, - e => e >= blockNumber - ); - } - - private static Task WaitMinBlockAsync(TestLogIndexStorage storage, int blockNumber, CancellationToken cancellation) - { - if (storage.MinBlockNumber <= blockNumber) - return Task.CompletedTask; - - return Wait.ForEventCondition( - cancellation, - e => storage.NewMinBlockNumber += e, - e => storage.NewMinBlockNumber -= e, - e => e <= blockNumber - ); - } - - private static Task WaitBlocksAsync(TestLogIndexStorage storage, int minBlock, int maxBlock, CancellationToken cancellation) => Task.WhenAll( - WaitMinBlockAsync(storage, minBlock, cancellation), - WaitMaxBlockAsync(storage, maxBlock, cancellation) - ); -} diff --git a/src/Nethermind/Nethermind.Facade/Eth/FacadeJsonContext.cs b/src/Nethermind/Nethermind.Facade/Eth/FacadeJsonContext.cs deleted file mode 100644 index 4204be813233..000000000000 --- a/src/Nethermind/Nethermind.Facade/Eth/FacadeJsonContext.cs +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Text.Json.Serialization; -using Nethermind.Facade.Eth.RpcTransaction; -using Nethermind.Facade.Filters; - -namespace Nethermind.Facade.Eth; - -[JsonSourceGenerationOptions( - GenerationMode = JsonSourceGenerationMode.Metadata, - PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] -[JsonSerializable(typeof(BlockForRpc))] -[JsonSerializable(typeof(FilterLog))] -[JsonSerializable(typeof(TransactionForRpc))] -[JsonSerializable(typeof(SyncingResult))] -public partial class FacadeJsonContext : JsonSerializerContext; diff --git a/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs index e15f2cc5aaea..fc17885d64b6 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Blockchain.Filters.Topics; @@ -19,11 +19,6 @@ public class LogFilter( public TopicsFilter TopicsFilter { get; } = topicsFilter; public BlockParameter FromBlock { get; } = fromBlock; public BlockParameter ToBlock { get; } = toBlock; - public bool UseIndex { get; set; } = true; - - public bool AcceptsAnyBlock => - AddressFilter.Addresses.Count == 0 && - TopicsFilter.AcceptsAnyBlock; public bool Accepts(LogEntry logEntry) => AddressFilter.Accepts(logEntry.Address) && TopicsFilter.Accepts(logEntry); diff --git a/src/Nethermind/Nethermind.Facade/Filters/LogIndexFilterVisitor.cs b/src/Nethermind/Nethermind.Facade/Filters/LogIndexFilterVisitor.cs deleted file mode 100644 index 824c21538065..000000000000 --- a/src/Nethermind/Nethermind.Facade/Filters/LogIndexFilterVisitor.cs +++ /dev/null @@ -1,187 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using Nethermind.Blockchain.Filters; -using Nethermind.Blockchain.Filters.Topics; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Db.LogIndex; - -[assembly: InternalsVisibleTo("Nethermind.Blockchain.Test")] - -namespace Nethermind.Facade.Filters; - -/// -/// Converts tree and block range into an enumerator of block numbers from , -/// by building corresponding "tree of enumerators". -/// -public class LogIndexFilterVisitor(ILogIndexStorage storage, LogFilter filter, int fromBlock, int toBlock) : IEnumerable -{ - internal sealed class IntersectEnumerator(IEnumerator e1, IEnumerator e2) : IEnumerator - { - public bool MoveNext() - { - bool has1 = e1.MoveNext(); - bool has2 = e2.MoveNext(); - - while (has1 && has2) - { - int c1 = e1.Current; - int c2 = e2.Current; - if (c1 == c2) - { - Current = c1; - return true; - } - - if (c1 < c2) has1 = e1.MoveNext(); - else has2 = e2.MoveNext(); - } - - return false; - } - - public void Reset() - { - e1.Reset(); - e2.Reset(); - } - - public int Current { get; private set; } - - object? IEnumerator.Current => Current; - - public void Dispose() - { - e1.Dispose(); - e2.Dispose(); - } - } - - internal sealed class UnionEnumerator(IEnumerator e1, IEnumerator e2) : IEnumerator - { - private bool _has1 = e1.MoveNext(); - private bool _has2 = e2.MoveNext(); - - public bool MoveNext() => - (_has1, _has2) switch - { - (true, true) => e1.Current.CompareTo(e2.Current) switch - { - 0 => MoveNext(e1, out _has1) && MoveNext(e2, out _has2), - < 0 => MoveNext(e1, out _has1), - > 0 => MoveNext(e2, out _has2) - }, - (true, false) => MoveNext(e1, out _has1), - (false, true) => MoveNext(e2, out _has2), - (false, false) => false - }; - - private bool MoveNext(IEnumerator enumerator, out bool has) - { - Current = enumerator.Current; - has = enumerator.MoveNext(); - return true; - } - - public void Reset() - { - e1.Reset(); - e2.Reset(); - _has1 = e1.MoveNext(); - _has2 = e2.MoveNext(); - } - - public int Current { get; private set; } - - object? IEnumerator.Current => Current; - - public void Dispose() - { - e1.Dispose(); - e2.Dispose(); - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public IEnumerator GetEnumerator() - { - IEnumerator? addressEnumerator = Visit(filter.AddressFilter); - IEnumerator? topicEnumerator = Visit(filter.TopicsFilter); - - if (addressEnumerator is not null && topicEnumerator is not null) - return new IntersectEnumerator(addressEnumerator, topicEnumerator); - - return addressEnumerator ?? topicEnumerator ?? throw new InvalidOperationException("Provided filter covers whole block range."); - } - - private IEnumerator? Visit(AddressFilter addressFilter) - { - IEnumerator? result = null; - - foreach (AddressAsKey address in addressFilter.Addresses) - { - IEnumerator next = Visit(address); - result = result is null ? next : new UnionEnumerator(result, next); - } - - return result; - } - - private IEnumerator? Visit(TopicsFilter topicsFilter) - { - IEnumerator result = null; - - var topicIndex = 0; - foreach (TopicExpression expression in topicsFilter.Expressions) - { - if (Visit(topicIndex++, expression) is not { } next) - continue; - - result = result is null ? next : new IntersectEnumerator(result, next); - } - - return result; - } - - private IEnumerator? Visit(int topicIndex, TopicExpression expression) => expression switch - { - AnyTopic => null, - OrExpression orExpression => Visit(topicIndex, orExpression), - SpecificTopic specificTopic => Visit(topicIndex, specificTopic.Topic), - _ => throw new ArgumentOutOfRangeException($"Unknown topic expression type: {expression.GetType().Name}.") - }; - - private IEnumerator? Visit(int topicIndex, OrExpression orExpression) - { - IEnumerator? result = null; - - foreach (TopicExpression expression in orExpression.SubExpressions) - { - if (Visit(topicIndex, expression) is not { } next) - continue; - - result = result is null ? next : new UnionEnumerator(result, next); - } - - return result; - } - - private IEnumerator Visit(Address address) => - storage.GetEnumerator(address, fromBlock, toBlock); - - private IEnumerator Visit(int topicIndex, Hash256 topic) => - storage.GetEnumerator(topicIndex, topic, fromBlock, toBlock); -} - -public static class LogIndexFilterVisitorExtensions -{ - public static IEnumerable EnumerateBlockNumbersFor(this ILogIndexStorage storage, LogFilter filter, long fromBlock, long toBlock) => - new LogIndexFilterVisitor(storage, filter, (int)fromBlock, (int)toBlock).Select(static i => (long)i); -} diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopic.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopic.cs index bce87e8886f3..f8278d7ff632 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopic.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopic.cs @@ -10,8 +10,6 @@ public class AnyTopic : TopicExpression { public static readonly AnyTopic Instance = new(); - public override bool AcceptsAnyBlock => true; - private AnyTopic() { } public override bool Accepts(Hash256 topic) => true; diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs index a1b8c1bed972..7bf063bfb60c 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; -using System.Linq; using Nethermind.Blockchain.Receipts; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -12,9 +10,6 @@ namespace Nethermind.Blockchain.Filters.Topics { public class AnyTopicsFilter(params TopicExpression[] expressions) : TopicsFilter { - public override IEnumerable Expressions => expressions; - public override bool AcceptsAnyBlock => expressions.Length == 0 || expressions.Any(static e => e.AcceptsAnyBlock); - public override bool Accepts(LogEntry entry) => Accepts(entry.Topics); private bool Accepts(Hash256[] topics) diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/OrExpression.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/OrExpression.cs index 406e4ee48671..d1d7801b43d1 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/OrExpression.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/OrExpression.cs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; -using System.Linq; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -13,9 +11,6 @@ public class OrExpression : TopicExpression, IEquatable { private readonly TopicExpression[] _subexpressions; - public IEnumerable SubExpressions => _subexpressions; - public override bool AcceptsAnyBlock => _subexpressions.Any(static e => e.AcceptsAnyBlock); - public OrExpression(params TopicExpression[] subexpressions) { _subexpressions = subexpressions; diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs index 1e490caa472a..69807f781ab0 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; -using System.Linq; using Nethermind.Blockchain.Receipts; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -17,9 +15,6 @@ public class SequenceTopicsFilter(params TopicExpression[] expressions) private readonly TopicExpression[] _expressions = expressions; - public override IEnumerable Expressions => _expressions; - public override bool AcceptsAnyBlock => _expressions.Length == 0 || _expressions.All(static e => e.AcceptsAnyBlock); - public override bool Accepts(LogEntry entry) => Accepts(entry.Topics); private bool Accepts(Hash256[] topics) diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/SpecificTopic.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/SpecificTopic.cs index 91aa003889c4..a9dee55bc8c1 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/SpecificTopic.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/SpecificTopic.cs @@ -11,9 +11,6 @@ public class SpecificTopic : TopicExpression private readonly Hash256 _topic; private Bloom.BloomExtract _bloomExtract; - public Hash256 Topic => _topic; - public override bool AcceptsAnyBlock => false; - public SpecificTopic(Hash256 topic) { _topic = topic; diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicExpression.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicExpression.cs index f344435bc680..5193e0bfba8b 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicExpression.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicExpression.cs @@ -8,8 +8,6 @@ namespace Nethermind.Blockchain.Filters.Topics { public abstract class TopicExpression { - public abstract bool AcceptsAnyBlock { get; } - public abstract bool Accepts(Hash256 topic); public abstract bool Accepts(ref Hash256StructRef topic); diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicsFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicsFilter.cs index 17b15c591c53..0cdc7ece380f 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicsFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicsFilter.cs @@ -1,16 +1,12 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Collections.Generic; using Nethermind.Core; namespace Nethermind.Blockchain.Filters.Topics { public abstract class TopicsFilter { - public abstract IEnumerable Expressions { get; } - public abstract bool AcceptsAnyBlock { get; } - public abstract bool Accepts(LogEntry entry); public abstract bool Accepts(ref LogEntryStructRef entry); diff --git a/src/Nethermind/Nethermind.Facade/Find/ILogIndexBuilder.cs b/src/Nethermind/Nethermind.Facade/Find/ILogIndexBuilder.cs deleted file mode 100644 index 3f898f360640..000000000000 --- a/src/Nethermind/Nethermind.Facade/Find/ILogIndexBuilder.cs +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Threading.Tasks; -using Nethermind.Core.ServiceStopper; - -namespace Nethermind.Facade.Find -{ - public interface ILogIndexBuilder : IAsyncDisposable, IStoppableService - { - Task StartAsync(); - bool IsRunning { get; } - - int MaxTargetBlockNumber { get; } - int MinTargetBlockNumber { get; } - - DateTimeOffset? LastUpdate { get; } - Exception? LastError { get; } - } -} diff --git a/src/Nethermind/Nethermind.Facade/Find/IndexedLogFinder.cs b/src/Nethermind/Nethermind.Facade/Find/IndexedLogFinder.cs deleted file mode 100644 index 2c9827d6c607..000000000000 --- a/src/Nethermind/Nethermind.Facade/Find/IndexedLogFinder.cs +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.Threading; -using Nethermind.Blockchain.Filters; -using Nethermind.Blockchain.Find; -using Nethermind.Blockchain.Receipts; -using Nethermind.Core; -using Nethermind.Db.Blooms; -using Nethermind.Db.LogIndex; -using Nethermind.Facade.Filters; -using Nethermind.Logging; - -namespace Nethermind.Facade.Find; - -/// -/// Extended that adds log index support for faster eth_getLogs queries. -/// When the log index is available and applicable, it uses the index to identify relevant blocks -/// before fetching logs from those specific blocks. -/// -public class IndexedLogFinder( - IBlockFinder blockFinder, - IReceiptFinder receiptFinder, - IReceiptStorage receiptStorage, - IBloomStorage bloomStorage, - ILogManager logManager, - IReceiptsRecovery receiptsRecovery, - ILogIndexStorage logIndexStorage, - int maxBlockDepth = 1000, - int minBlocksToUseIndex = 32) - : LogFinder(blockFinder, receiptFinder, receiptStorage, bloomStorage, logManager, receiptsRecovery, maxBlockDepth) -{ - private readonly ILogIndexStorage _logIndexStorage = logIndexStorage ?? throw new ArgumentNullException(nameof(logIndexStorage)); - - public override IEnumerable FindLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken = default) => - GetLogIndexRange(filter, fromBlock, toBlock) is not { } indexRange - ? base.FindLogs(filter, fromBlock, toBlock, cancellationToken) - : FindIndexedLogs(filter, fromBlock, toBlock, indexRange, cancellationToken); - - private IEnumerable FindIndexedLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, (int from, int to) indexRange, CancellationToken cancellationToken) - { - if (indexRange.from > fromBlock.Number && FindHeaderOrLogError(indexRange.from - 1, cancellationToken) is { } beforeIndex) - { - foreach (FilterLog log in base.FindLogs(filter, fromBlock, beforeIndex, cancellationToken)) - yield return log; - } - - cancellationToken.ThrowIfCancellationRequested(); - - IEnumerable indexNumbers = _logIndexStorage.EnumerateBlockNumbersFor(filter, indexRange.from, indexRange.to); - foreach (FilterLog log in FilterLogsInBlocksParallel(filter, indexNumbers, cancellationToken)) - { - yield return log; - } - - cancellationToken.ThrowIfCancellationRequested(); - - if (indexRange.to < toBlock.Number && FindHeaderOrLogError(indexRange.to + 1, cancellationToken) is { } afterIndex) - { - foreach (FilterLog log in base.FindLogs(filter, afterIndex, toBlock, cancellationToken)) - yield return log; - } - } - - private (int from, int to)? GetLogIndexRange(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock) - { - bool tryUseIndex = filter.UseIndex; - filter.UseIndex = false; - - if (!tryUseIndex || !_logIndexStorage.Enabled || filter.AcceptsAnyBlock) - return null; - - if (_logIndexStorage.MinBlockNumber is not { } indexFrom || _logIndexStorage.MaxBlockNumber is not { } indexTo) - return null; - - (int from, int to) range = ( - Math.Max((int)fromBlock.Number, indexFrom), - Math.Min((int)toBlock.Number, indexTo) - ); - - if (range.from > range.to) - return null; - - if (range.to - range.from + 1 < minBlocksToUseIndex) - return null; - - filter.UseIndex = true; - return range; - } -} diff --git a/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs b/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs index d1b4d5636152..b1eddee6df12 100644 --- a/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs +++ b/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs @@ -18,26 +18,37 @@ namespace Nethermind.Facade.Find { - public class LogFinder( - IBlockFinder? blockFinder, - IReceiptFinder? receiptFinder, - IReceiptStorage? receiptStorage, - IBloomStorage? bloomStorage, - ILogManager? logManager, - IReceiptsRecovery? receiptsRecovery, - int maxBlockDepth = 1000) - : ILogFinder + public class LogFinder : ILogFinder { private static int ParallelExecutions = 0; private static int ParallelLock = 0; - private readonly IReceiptFinder _receiptFinder = receiptFinder ?? throw new ArgumentNullException(nameof(receiptFinder)); - private readonly IReceiptStorage _receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage)); - private readonly IBloomStorage _bloomStorage = bloomStorage ?? throw new ArgumentNullException(nameof(bloomStorage)); - private readonly IReceiptsRecovery _receiptsRecovery = receiptsRecovery ?? throw new ArgumentNullException(nameof(receiptsRecovery)); - private readonly int _rpcConfigGetLogsThreads = Math.Max(1, Environment.ProcessorCount / 4); - private readonly IBlockFinder _blockFinder = blockFinder ?? throw new ArgumentNullException(nameof(blockFinder)); - private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + private readonly IReceiptFinder _receiptFinder; + private readonly IReceiptStorage _receiptStorage; + private readonly IBloomStorage _bloomStorage; + private readonly IReceiptsRecovery _receiptsRecovery; + private readonly int _maxBlockDepth; + private readonly int _rpcConfigGetLogsThreads; + private readonly IBlockFinder _blockFinder; + private readonly ILogger _logger; + + public LogFinder(IBlockFinder? blockFinder, + IReceiptFinder? receiptFinder, + IReceiptStorage? receiptStorage, + IBloomStorage? bloomStorage, + ILogManager? logManager, + IReceiptsRecovery? receiptsRecovery, + int maxBlockDepth = 1000) + { + _blockFinder = blockFinder ?? throw new ArgumentNullException(nameof(blockFinder)); + _receiptFinder = receiptFinder ?? throw new ArgumentNullException(nameof(receiptFinder)); + _receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage)); ; + _bloomStorage = bloomStorage ?? throw new ArgumentNullException(nameof(bloomStorage)); + _receiptsRecovery = receiptsRecovery ?? throw new ArgumentNullException(nameof(receiptsRecovery)); + _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + _maxBlockDepth = maxBlockDepth; + _rpcConfigGetLogsThreads = Math.Max(1, Environment.ProcessorCount / 4); + } public IEnumerable FindLogs(LogFilter filter, CancellationToken cancellationToken = default) { @@ -54,7 +65,7 @@ BlockHeader FindHeader(BlockParameter blockParameter, string name, bool headLimi return FindLogs(filter, fromBlock, toBlock, cancellationToken); } - public virtual IEnumerable FindLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken = default) + public IEnumerable FindLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -86,37 +97,36 @@ public virtual IEnumerable FindLogs(LogFilter filter, BlockHeader fro private static bool ShouldUseBloomDatabase(BlockHeader fromBlock, BlockHeader toBlock) { - long blocksToSearch = toBlock.Number - fromBlock.Number + 1; + var blocksToSearch = toBlock.Number - fromBlock.Number + 1; return blocksToSearch > 1; // if we are searching only in 1 block skip bloom index altogether, this can be tweaked } private IEnumerable FilterLogsWithBloomsIndex(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken) { - IEnumerable EnumerateBlockNumbers(LogFilter f, long from, long to) + BlockHeader FindBlockHeader(long blockNumber, CancellationToken token) { - IBloomEnumeration enumeration = _bloomStorage.GetBlooms(from, to); - foreach (Bloom bloom in enumeration) + token.ThrowIfCancellationRequested(); + var block = _blockFinder.FindHeader(blockNumber); + if (block is null) { - if (f.Matches(bloom) && enumeration.TryGetBlockNumber(out var blockNumber)) - { - yield return blockNumber; - } + if (_logger.IsError) _logger.Error($"Could not find block {blockNumber} in database. eth_getLogs will return incomplete results."); } - } - return FilterLogsInBlocksParallel(filter, EnumerateBlockNumbers(filter, fromBlock.Number, toBlock.Number), cancellationToken); - } + return block; + } - protected IEnumerable FilterLogsInBlocksParallel(LogFilter filter, IEnumerable blockNumbers, CancellationToken cancellationToken) - { - static IEnumerable ParallelizeWithLock(IEnumerable blocks, bool runParallel, CancellationToken ct) + IEnumerable FilterBlocks(LogFilter f, long @from, long to, bool runParallel, CancellationToken token) { try { - foreach (long blockNumber in blocks) + var enumeration = _bloomStorage.GetBlooms(from, to); + foreach (var bloom in enumeration) { - yield return blockNumber; - ct.ThrowIfCancellationRequested(); + token.ThrowIfCancellationRequested(); + if (f.Matches(bloom) && enumeration.TryGetBlockNumber(out var blockNumber)) + { + yield return blockNumber; + } } } finally @@ -135,7 +145,7 @@ static IEnumerable ParallelizeWithLock(IEnumerable blocks, bool runP int parallelExecutions = Interlocked.Increment(ref ParallelExecutions) - 1; bool canRunParallel = parallelLock == 0; - IEnumerable filterBlocks = ParallelizeWithLock(blockNumbers, canRunParallel, cancellationToken); + IEnumerable filterBlocks = FilterBlocks(filter, fromBlock.Number, toBlock.Number, canRunParallel, cancellationToken); if (canRunParallel) { @@ -150,7 +160,7 @@ static IEnumerable ParallelizeWithLock(IEnumerable blocks, bool runP } return filterBlocks - .SelectMany(blockNumber => FindLogsInBlock(filter, FindHeaderOrLogError(blockNumber, cancellationToken), cancellationToken)); + .SelectMany(blockNumber => FindLogsInBlock(filter, FindBlockHeader(blockNumber, cancellationToken), cancellationToken)); } private bool CanUseBloomDatabase(BlockHeader toBlock, BlockHeader fromBlock) @@ -181,7 +191,7 @@ private bool CanUseBloomDatabase(BlockHeader toBlock, BlockHeader fromBlock) private IEnumerable FilterLogsIteratively(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken) { int count = 0; - while (count < maxBlockDepth && fromBlock.Number <= (toBlock?.Number ?? fromBlock.Number)) + while (count < _maxBlockDepth && fromBlock.Number <= (toBlock?.Number ?? fromBlock.Number)) { foreach (var filterLog in FindLogsInBlock(filter, fromBlock, cancellationToken)) { @@ -196,11 +206,11 @@ private IEnumerable FilterLogsIteratively(LogFilter filter, BlockHead } private IEnumerable FindLogsInBlock(LogFilter filter, BlockHeader block, CancellationToken cancellationToken) => - filter.Matches(block.Bloom!) + filter.Matches(block.Bloom) ? FindLogsInBlock(filter, block.Hash, block.Number, block.Timestamp, cancellationToken) : []; - private IEnumerable FindLogsInBlock(LogFilter filter, Hash256? blockHash, long blockNumber, ulong blockTimestamp, CancellationToken cancellationToken) + private IEnumerable FindLogsInBlock(LogFilter filter, Hash256 blockHash, long blockNumber, ulong blockTimestamp, CancellationToken cancellationToken) { if (blockHash is not null) { @@ -338,18 +348,5 @@ void RecoverReceiptsData(Hash256 hash, TxReceipt[] receipts) } } } - - protected BlockHeader? FindHeaderOrLogError(long blockNumber, CancellationToken token) - { - token.ThrowIfCancellationRequested(); - - BlockHeader? block = _blockFinder.FindHeader(blockNumber); - if (block is null && _logger.IsError) - { - _logger.Error($"Could not find block {blockNumber} in database. eth_getLogs will return incomplete results."); - } - - return block; - } } } diff --git a/src/Nethermind/Nethermind.Facade/Find/LogIndexBuilder.cs b/src/Nethermind/Nethermind.Facade/Find/LogIndexBuilder.cs deleted file mode 100644 index 3112b36545f6..000000000000 --- a/src/Nethermind/Nethermind.Facade/Find/LogIndexBuilder.cs +++ /dev/null @@ -1,495 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using System.Threading.Tasks.Dataflow; -using Nethermind.Blockchain; -using Nethermind.Blockchain.Receipts; -using Nethermind.Blockchain.Synchronization; -using Nethermind.Core; -using Nethermind.Db.LogIndex; -using Nethermind.Logging; -using Timer = System.Timers.Timer; -using static System.Threading.Tasks.TaskCreationOptions; - -namespace Nethermind.Facade.Find; - -// TODO: reduce periodic logging -public sealed class LogIndexBuilder : ILogIndexBuilder -{ - private sealed class ProcessingQueue( - TransformBlock, LogIndexAggregate> aggregateBlock, - ActionBlock addReceiptsBlock) - { - public int QueueCount => aggregateBlock.InputCount + addReceiptsBlock.InputCount; - public Task WriteAsync(IReadOnlyList batch, CancellationToken cancellation) => aggregateBlock.SendAsync(batch, cancellation); - public Task Completion => Task.WhenAll(aggregateBlock.Completion, addReceiptsBlock.Completion); - } - - private struct DirectionState() - { - public ProcessingQueue? Queue; - public ProgressLogger? Progress; - public readonly TaskCompletionSource Completion = new(RunContinuationsAsynchronously); - } - - [InlineArray(2)] - private struct DirectionStates - { - private DirectionState _element; - } - - private readonly IBlockTree _blockTree; - private readonly ISyncConfig _syncConfig; - private readonly ILogger _logger; - - private readonly CancellationTokenSource _cancellationSource = new(); - private CancellationToken CancellationToken => _cancellationSource.Token; - - private int MaxReorgDepth => _config.MaxReorgDepth!.Value; - private static readonly TimeSpan NewBlockWaitTimeout = TimeSpan.FromSeconds(5); - - private readonly ILogIndexStorage _logIndexStorage; - private readonly ILogIndexConfig _config; - private readonly IReceiptStorage _receiptStorage; - private readonly ILogManager _logManager; - private Timer? _progressLoggerTimer; - - private readonly TaskCompletionSource _pivotSource = new(RunContinuationsAsynchronously); - private readonly Task _pivotTask; - - private readonly List _tasks = new(); - - private DirectionStates _directions; - - private ref DirectionState Direction(bool isForward) => ref _directions[isForward ? 1 : 0]; - - private LogIndexUpdateStats _stats; - - public string Description => "log index builder"; - - public Task BackwardSyncCompletion => Direction(isForward: false).Completion.Task; - - public LogIndexBuilder(ILogIndexStorage logIndexStorage, ILogIndexConfig config, - IBlockTree blockTree, ISyncConfig syncConfig, IReceiptStorage receiptStorage, - ILogManager logManager) - { - ArgumentNullException.ThrowIfNull(logIndexStorage); - ArgumentNullException.ThrowIfNull(blockTree); - ArgumentNullException.ThrowIfNull(receiptStorage); - ArgumentNullException.ThrowIfNull(logManager); - ArgumentNullException.ThrowIfNull(syncConfig); - - _config = config; - _logIndexStorage = logIndexStorage; - _blockTree = blockTree; - _syncConfig = syncConfig; - _receiptStorage = receiptStorage; - _logManager = logManager; - _logger = logManager.GetClassLogger(); - _pivotTask = _pivotSource.Task; - _stats = new(_logIndexStorage); - - Direction(isForward: false) = new(); - Direction(isForward: true) = new(); - } - - private void StartProcessing(bool isForward) - { - // Do not start backward sync if the target is already reached - if (!isForward && _logIndexStorage.MinBlockNumber <= MinTargetBlockNumber) - { - MarkCompleted(false); - return; - } - - ref DirectionState dir = ref Direction(isForward); - dir.Queue = BuildQueue(isForward); - dir.Progress = new(GetLogPrefix(isForward), _logManager); - - _tasks.AddRange( - Task.Run(() => DoQueueBlocks(isForward), CancellationToken), - dir.Queue.Completion - ); - } - - public async Task StartAsync() - { - try - { - if (!_config.Enabled) - return; - - _receiptStorage.ReceiptsInserted += OnReceiptsInserted; - - TrySetPivot(_logIndexStorage.MaxBlockNumber); - TrySetPivot((int)_blockTree.SyncPivot.BlockNumber); - - if (!_pivotTask.IsCompleted && _logger.IsInfo) - _logger.Info($"{GetLogPrefix()}: waiting for the first block..."); - - await _pivotTask; - - StartProcessing(isForward: true); - StartProcessing(isForward: false); - - UpdateProgress(); - LogProgress(); - - _progressLoggerTimer = new(TimeSpan.FromSeconds(30)); - _progressLoggerTimer.AutoReset = true; - _progressLoggerTimer.Elapsed += (_, _) => LogProgress(); - _progressLoggerTimer.Start(); - - IsRunning = true; - } - catch (Exception ex) - { - await HandleExceptionAsync(ex); - } - } - - public async Task StopAsync() - { - if (!_config.Enabled) - return; - - await _cancellationSource.CancelAsync(); - - _pivotSource.TrySetCanceled(CancellationToken); - _progressLoggerTimer?.Stop(); - - foreach (Task task in _tasks) - { - try - { - await task; - } - catch (Exception ex) - { - await HandleExceptionAsync(ex, isStopping: true); - } - } - - await _logIndexStorage.StopAsync(); - - IsRunning = false; - } - - public async ValueTask DisposeAsync() - { - await _logIndexStorage.DisposeAsync(); - _progressLoggerTimer?.Dispose(); - _cancellationSource.Dispose(); - } - - private void LogStats() - { - LogIndexUpdateStats stats = _stats; - - if (stats is not { BlocksAdded: > 0 }) - return; - - _stats = new(_logIndexStorage); - - if (_logger.IsInfo) - { - _logger.Info(_config.DetailedLogs - ? $"{GetLogPrefix()}: {stats:d}" - : $"{GetLogPrefix()}: {stats}" - ); - } - } - - private void LogProgress() - { - LogStats(); - Direction(isForward: false).Progress?.LogProgress(); - Direction(isForward: true).Progress?.LogProgress(); - } - - private bool TrySetPivot(int? blockNumber) - { - if (blockNumber is not { } number || number is 0) - return false; - - if (_pivotSource.Task.IsCompleted) - return false; - - number = Math.Max(MinTargetBlockNumber, number); - number = Math.Min(MaxTargetBlockNumber, number); - - if (number is 0) - return false; - - if (!TryGetBlockReceipts(number, out _)) - return false; - - if (!_pivotSource.TrySetResult(number)) - return false; - - _logger.Info($"{GetLogPrefix()}: using block {number} as pivot."); - return true; - } - - private void OnReceiptsInserted(object? sender, ReceiptsEventArgs args) - { - int next = (int)args.BlockHeader.Number; - if (TrySetPivot(next)) - _receiptStorage.ReceiptsInserted -= OnReceiptsInserted; - } - - public int MaxTargetBlockNumber => (int)Math.Max(_blockTree.BestKnownNumber - MaxReorgDepth, 0); - - // Block 0 should always be present - public int MinTargetBlockNumber => (int)(_syncConfig.AncientReceiptsBarrierCalc <= 1 ? 0 : _syncConfig.AncientReceiptsBarrierCalc); - - public bool IsRunning { get; private set; } - public DateTimeOffset? LastUpdate { get; private set; } - public Exception? LastError { get; private set; } - - private ProcessingQueue BuildQueue(bool isForward) - { - var aggregateBlock = new TransformBlock, LogIndexAggregate>( - batch => Aggregate(batch, isForward), - new() - { - BoundedCapacity = _config.MaxAggregationQueueSize, - MaxDegreeOfParallelism = _config.MaxAggregationParallelism, - CancellationToken = CancellationToken, - SingleProducerConstrained = true - } - ); - - var addReceiptsBlock = new ActionBlock( - aggr => AddReceiptsAsync(aggr, isForward), - new() - { - BoundedCapacity = _config.MaxSavingQueueSize, - MaxDegreeOfParallelism = 1, - CancellationToken = CancellationToken, - SingleProducerConstrained = true - } - ); - - aggregateBlock.Completion.ContinueWith(t => HandleExceptionAsync(t.Exception), TaskContinuationOptions.OnlyOnFaulted); - addReceiptsBlock.Completion.ContinueWith(t => HandleExceptionAsync(t.Exception), TaskContinuationOptions.OnlyOnFaulted); - - aggregateBlock.LinkTo(addReceiptsBlock, new() { PropagateCompletion = true }); - return new(aggregateBlock, addReceiptsBlock); - } - - private async Task HandleExceptionAsync(Exception? exception, bool isStopping = false) - { - if (exception is null) - return; - - if (exception is AggregateException a) - exception = a.InnerException; - - if (exception is OperationCanceledException oc && oc.CancellationToken == CancellationToken) - return; // Cancelled - - if (_logger.IsError) - _logger.Error($"{GetLogPrefix()} failed. Please restart the client.", exception); - - LastError = exception; - - Direction(isForward: false).Completion.TrySetException(exception!); - Direction(isForward: true).Completion.TrySetException(exception!); - - if (!isStopping) - await StopAsync(); - } - - private LogIndexAggregate Aggregate(IReadOnlyList batch, bool isForward) => _logIndexStorage.Aggregate(batch, !isForward, _stats); - - private async Task AddReceiptsAsync(LogIndexAggregate aggregate, bool isForward) - { - if (GetNextBlockNumber(_logIndexStorage, isForward) is { } next && next != aggregate.FirstBlockNum) - throw new($"{GetLogPrefix(isForward)}: non sequential batches: ({aggregate.FirstBlockNum} instead of {next})."); - - await _logIndexStorage.AddReceiptsAsync(aggregate, _stats); - LastUpdate = DateTimeOffset.Now; - - UpdateProgress(); - - if (_logIndexStorage.MinBlockNumber <= MinTargetBlockNumber) - MarkCompleted(false); - } - - private async Task DoQueueBlocks(bool isForward) - { - try - { - int pivotNumber = await _pivotTask; - - ProcessingQueue queue = Direction(isForward).Queue!; - - int? next = GetNextBlockNumber(isForward); - if (next is not { } start) - { - if (isForward) - { - start = pivotNumber; - } - else - { - start = pivotNumber - 1; - } - } - - BlockReceipts[] buffer = new BlockReceipts[_config.MaxBatchSize]; - while (!CancellationToken.IsCancellationRequested) - { - if (!isForward && start < MinTargetBlockNumber) - { - if (_logger.IsTrace) - _logger.Trace($"{GetLogPrefix(isForward)}: queued last block"); - - return; - } - - int batchSize = _config.MaxBatchSize; - int end = isForward ? start + batchSize - 1 : start - batchSize + 1; - end = Math.Max(end, MinTargetBlockNumber); - end = Math.Min(end, MaxTargetBlockNumber); - - // from - inclusive, to - exclusive - var (from, to) = isForward - ? (start, end + 1) - : (end, start + 1); - - var timestamp = Stopwatch.GetTimestamp(); - Array.Clear(buffer); - ReadOnlySpan batch = GetNextBatch(from, to, buffer, isForward, CancellationToken); - - if (batch.Length == 0) - { - // TODO: stop waiting immediately when receipts become available - await Task.Delay(NewBlockWaitTimeout, CancellationToken); - continue; - } - - _stats.LoadingReceipts.Include(Stopwatch.GetElapsedTime(timestamp)); - - start = GetNextBlockNumber(batch[^1].BlockNumber, isForward); - await queue.WriteAsync(batch.ToArray(), CancellationToken); - } - } - catch (Exception ex) - { - await HandleExceptionAsync(ex); - } - - if (_logger.IsTrace) - _logger.Trace($"{GetLogPrefix(isForward)}: queueing completed."); - } - - private void UpdateProgress() - { - if (!_pivotTask.IsCompletedSuccessfully) return; - var pivotNumber = _pivotTask.Result; - - DirectionState forward = Direction(isForward: true); - if (forward.Progress is { HasEnded: false } forwardProgress) - { - forwardProgress.TargetValue = Math.Max(0, _blockTree.BestKnownNumber - MaxReorgDepth - pivotNumber + 1); - forwardProgress.Update(_logIndexStorage.MaxBlockNumber is { } max ? max - pivotNumber + 1 : 0); - forwardProgress.CurrentQueued = forward.Queue!.QueueCount; - } - - DirectionState backward = Direction(isForward: false); - if (backward.Progress is { HasEnded: false } backwardProgress) - { - backwardProgress.TargetValue = pivotNumber - MinTargetBlockNumber; - backwardProgress.Update(_logIndexStorage.MinBlockNumber is { } min ? pivotNumber - min : 0); - backwardProgress.CurrentQueued = backward.Queue!.QueueCount; - } - } - - private void MarkCompleted(bool isForward) - { - DirectionState dir = Direction(isForward); - if (!dir.Completion.TrySetResult()) - return; - - dir.Progress?.MarkEnd(); - - if (_logger.IsInfo) - _logger.Info($"{GetLogPrefix(isForward)}: completed."); - } - - private static int? GetNextBlockNumber(ILogIndexStorage storage, bool isForward) => isForward ? storage.MaxBlockNumber + 1 : storage.MinBlockNumber - 1; - - private int? GetNextBlockNumber(bool isForward) => GetNextBlockNumber(_logIndexStorage, isForward); - - private static int GetNextBlockNumber(int last, bool isForward) => isForward ? last + 1 : last - 1; - - private ReadOnlySpan GetNextBatch(int from, int to, BlockReceipts[] buffer, bool isForward, CancellationToken token) - { - if (to <= from) - return ReadOnlySpan.Empty; - - if (to - from > buffer.Length) - throw new InvalidOperationException($"{GetLogPrefix()}: buffer size is too small: {buffer.Length} / {to - from}"); - - // Check the immediate next block first - int nextIndex = isForward ? from : to - 1; - if (!TryGetBlockReceipts(nextIndex, out buffer[0])) - return ReadOnlySpan.Empty; - - Parallel.For(from, to, new() - { - CancellationToken = token, - MaxDegreeOfParallelism = _config.MaxReceiptsParallelism - }, i => - { - int bufferIndex = isForward ? i - from : to - 1 - i; - if (buffer[bufferIndex] == default) - TryGetBlockReceipts(i, out buffer[bufferIndex]); - }); - - int endIndex = Array.IndexOf(buffer, default); - return endIndex < 0 ? buffer : buffer.AsSpan(..endIndex); - } - - // TODO: move to IReceiptStorage? - private bool TryGetBlockReceipts(int i, out BlockReceipts blockReceipts) - { - blockReceipts = default; - - if (_blockTree.FindBlock(i, BlockTreeLookupOptions.ExcludeTxHashes) is not { Hash: not null } block) - { - return false; - } - - if (!block.Header.HasTransactions) - { - blockReceipts = new(i, []); - return true; - } - - TxReceipt[] receipts = _receiptStorage.Get(block) ?? []; - - if (receipts.Length == 0) - { - return false; // block should have transactions but nothing in storage - } - - blockReceipts = new(i, receipts); - return true; - } - - private static string GetLogPrefix(bool? isForward = null) => isForward switch - { - true => "Log index sync (Forward)", - false => "Log index sync (Backward)", - _ => "Log index sync" - }; -} diff --git a/src/Nethermind/Nethermind.Evm/BlockOverride.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockOverride.cs similarity index 96% rename from src/Nethermind/Nethermind.Evm/BlockOverride.cs rename to src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockOverride.cs index fb0f7b6e52c2..4bb929aef316 100644 --- a/src/Nethermind/Nethermind.Evm/BlockOverride.cs +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockOverride.cs @@ -6,7 +6,7 @@ using Nethermind.Core.Crypto; using Nethermind.Int256; -namespace Nethermind.Evm; +namespace Nethermind.Facade.Proxy.Models.Simulate; public class BlockOverride { diff --git a/src/Nethermind/Nethermind.Init/Modules/BlockProcessingModule.cs b/src/Nethermind/Nethermind.Init/Modules/BlockProcessingModule.cs index 21765cfc0635..4165e4fba354 100644 --- a/src/Nethermind/Nethermind.Init/Modules/BlockProcessingModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/BlockProcessingModule.cs @@ -63,7 +63,6 @@ protected override void Load(ContainerBuilder builder) .AddScoped() .AddScoped() .AddScoped() - .AddScoped() .AddScoped() .AddScoped((rewardSource, txP) => rewardSource.Get(txP)) .AddScoped((specProvider, blocksConfig) => diff --git a/src/Nethermind/Nethermind.Init/Modules/BlockTreeModule.cs b/src/Nethermind/Nethermind.Init/Modules/BlockTreeModule.cs index 139dabadad2b..e769c59eb754 100644 --- a/src/Nethermind/Nethermind.Init/Modules/BlockTreeModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/BlockTreeModule.cs @@ -15,16 +15,14 @@ using Nethermind.Crypto; using Nethermind.Db; using Nethermind.Db.Blooms; -using Nethermind.Db.LogIndex; using Nethermind.Facade.Find; -using Nethermind.Logging; using Nethermind.History; using Nethermind.State.Repositories; using Nethermind.TxPool; namespace Nethermind.Init.Modules; -public class BlockTreeModule(IReceiptConfig receiptConfig, ILogIndexConfig logIndexConfig) : Autofac.Module +public class BlockTreeModule(IReceiptConfig receiptConfig) : Autofac.Module { protected override void Load(ContainerBuilder builder) { @@ -39,34 +37,16 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddSingleton() .AddSingleton((ecdsa, specProvider, receiptConfig) => - new ReceiptsRecovery(ecdsa, specProvider, !receiptConfig.CompactReceiptStore) - ) + new ReceiptsRecovery(ecdsa, specProvider, !receiptConfig.CompactReceiptStore)) .AddSingleton() .AddSingleton() + .AddSingleton() .Bind() - .AddSingleton((bt) => bt.AsReadOnly()); - - builder.AddSingleton() - .AddDecorator((ctx, config) => - { - IPruningConfig pruningConfig = ctx.Resolve(); - config.MaxReorgDepth ??= pruningConfig.PruningBoundary; - return config; - }); + .AddSingleton() + .AddSingleton((bt) => bt.AsReadOnly()) - if (logIndexConfig.Enabled) - { - builder - .AddSingleton() - .AddSingleton(); - } - else - { - builder - .AddSingleton() - .AddSingleton(); - } + ; if (!receiptConfig.StoreReceipts) { diff --git a/src/Nethermind/Nethermind.Init/Modules/BuiltInStepsModule.cs b/src/Nethermind/Nethermind.Init/Modules/BuiltInStepsModule.cs index 0b27e6863e92..a20ca48e5d90 100644 --- a/src/Nethermind/Nethermind.Init/Modules/BuiltInStepsModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/BuiltInStepsModule.cs @@ -33,7 +33,6 @@ public class BuiltInStepsModule : Module typeof(StartBlockProcessor), typeof(StartBlockProducer), typeof(StartMonitoring), - typeof(StartLogIndex) ]; protected override void Load(ContainerBuilder builder) diff --git a/src/Nethermind/Nethermind.Init/Modules/DbModule.cs b/src/Nethermind/Nethermind.Init/Modules/DbModule.cs index 0caa67fe530b..1a3400c7e1f0 100644 --- a/src/Nethermind/Nethermind.Init/Modules/DbModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/DbModule.cs @@ -66,6 +66,7 @@ protected override void Load(ContainerBuilder builder) .AddDatabase(DbNames.Headers) .AddDatabase(DbNames.BlockInfos) .AddDatabase(DbNames.Bloom) + .AddDatabase(DbNames.BlobTransactions) .AddColumnDatabase(DbNames.Receipts) .AddColumnDatabase(DbNames.BlobTransactions) diff --git a/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs b/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs index 9133320d1788..07414446bbf9 100644 --- a/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs +++ b/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs @@ -46,22 +46,20 @@ public MainProcessingContext( .AddSingleton(this) .AddModule(mainProcessingModules) - .AddScoped((branchProcessor, processingStats) => - new BlockchainProcessor( - blockTree, - branchProcessor, - compositeBlockPreprocessorStep, - worldStateManager.GlobalStateReader, - logManager, - new BlockchainProcessor.Options - { - StoreReceiptsByDefault = receiptConfig.StoreReceipts, - DumpOptions = initConfig.AutoDump - }, - processingStats) + .AddScoped((branchProcessor) => new BlockchainProcessor( + blockTree, + branchProcessor, + compositeBlockPreprocessorStep, + worldStateManager.GlobalStateReader, + logManager, + new BlockchainProcessor.Options { - IsMainProcessor = true // Manual construction because of this flag + StoreReceiptsByDefault = receiptConfig.StoreReceipts, + DumpOptions = initConfig.AutoDump }) + { + IsMainProcessor = true // Manual construction because of this flag + }) .AddScoped(ctx => ctx.Resolve()) .AddScoped(ctx => ctx.Resolve()) // And finally, to wrap things up. diff --git a/src/Nethermind/Nethermind.Init/Modules/NethermindModule.cs b/src/Nethermind/Nethermind.Init/Modules/NethermindModule.cs index cd17a2f96dc6..41df70db2387 100644 --- a/src/Nethermind/Nethermind.Init/Modules/NethermindModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/NethermindModule.cs @@ -15,7 +15,6 @@ using Nethermind.Core.Specs; using Nethermind.Core.Timers; using Nethermind.Crypto; -using Nethermind.Db.LogIndex; using Nethermind.Era1; using Nethermind.JsonRpc; using Nethermind.Logging; @@ -55,7 +54,7 @@ protected override void Load(ContainerBuilder builder) .AddModule(new EraModule()) .AddSource(new ConfigRegistrationSource()) .AddModule(new BlockProcessingModule(configProvider.GetConfig(), configProvider.GetConfig())) - .AddModule(new BlockTreeModule(configProvider.GetConfig(), configProvider.GetConfig())) + .AddModule(new BlockTreeModule(configProvider.GetConfig())) .AddModule(new MonitoringModule(configProvider.GetConfig())) .AddSingleton() diff --git a/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs b/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs index 3cf04ff24cc0..c9ff690ad826 100644 --- a/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs +++ b/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs @@ -13,7 +13,6 @@ using Nethermind.Core.Timers; using Nethermind.Facade; using Nethermind.Facade.Eth; -using Nethermind.Facade.Find; using Nethermind.Facade.Simulate; using Nethermind.Init.Steps.Migrations; using Nethermind.JsonRpc; @@ -22,7 +21,6 @@ using Nethermind.JsonRpc.Modules.DebugModule; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.JsonRpc.Modules.Eth.FeeHistory; -using Nethermind.JsonRpc.Modules.LogIndex; using Nethermind.JsonRpc.Modules.Net; using Nethermind.JsonRpc.Modules.Parity; using Nethermind.JsonRpc.Modules.Personal; @@ -60,7 +58,6 @@ protected override void Load(ContainerBuilder builder) .RegisterSingletonJsonRpcModule() .RegisterSingletonJsonRpcModule() .RegisterSingletonJsonRpcModule() - .RegisterSingletonJsonRpcModule() // Txpool rpc .RegisterSingletonJsonRpcModule() diff --git a/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs b/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs index 5cfab78ac27b..9b8e0542617e 100644 --- a/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs +++ b/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs @@ -17,7 +17,6 @@ using Nethermind.Core.Timers; using Nethermind.Db; using Nethermind.Db.FullPruning; -using Nethermind.Db.LogIndex; using Nethermind.Db.Rocks.Config; using Nethermind.Evm.State; using Nethermind.JsonRpc.Modules.Admin; @@ -178,7 +177,6 @@ public MainPruningTrieStoreFactory( IFinalizedStateProvider finalizedStateProvider, IBlockTree blockTree, IDbConfig dbConfig, - ILogIndexConfig logIndexConfig, IHardwareInfo hardwareInfo, ILogManager logManager ) @@ -189,9 +187,6 @@ ILogManager logManager if (syncConfig.SnapServingEnabled == true && pruningConfig.PruningBoundary < syncConfig.SnapServingMaxDepth) { - // use PruningBoundary for log-index MaxReorgDepth before it's overwritten - logIndexConfig.MaxReorgDepth ??= pruningConfig.PruningBoundary; - if (_logger.IsInfo) _logger.Info($"Snap serving enabled, but {nameof(pruningConfig.PruningBoundary)} is less than {syncConfig.SnapServingMaxDepth}. Setting to {syncConfig.SnapServingMaxDepth}."); pruningConfig.PruningBoundary = syncConfig.SnapServingMaxDepth; } diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs index 8e866c3ac38a..aa64b11639b8 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs @@ -32,6 +32,7 @@ namespace Nethermind.Init.Steps public class InitializeBlockchain(INethermindApi api, IChainHeadInfoProvider chainHeadInfoProvider) : IStep { private readonly INethermindApi _api = api; + private ILogManager _logManager = api.LogManager; public async Task Execute(CancellationToken _) { diff --git a/src/Nethermind/Nethermind.Init/Steps/StartLogIndex.cs b/src/Nethermind/Nethermind.Init/Steps/StartLogIndex.cs deleted file mode 100644 index ef329611cf29..000000000000 --- a/src/Nethermind/Nethermind.Init/Steps/StartLogIndex.cs +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading; -using System.Threading.Tasks; -using Nethermind.Api; -using Nethermind.Api.Steps; -using Nethermind.Core.ServiceStopper; -using Nethermind.Db; -using Nethermind.Db.LogIndex; -using Nethermind.Facade.Find; - -namespace Nethermind.Init.Steps -{ - [RunnerStepDependencies(typeof(InitDatabase), typeof(StartBlockProcessor))] - public class StartLogIndex(IBasicApi api, ILogIndexBuilder logIndexBuilder) : IStep - { - public Task Execute(CancellationToken cancellationToken) - { - if (api.Config().Enabled) - _ = logIndexBuilder.StartAsync(); - - return Task.CompletedTask; - } - - public bool MustInitialize => false; - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs index 7544c8a272bf..fb5104b65651 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs @@ -25,7 +25,6 @@ using Nethermind.Wallet; using Nethermind.Config; using Nethermind.Core.Test.Modules; -using Nethermind.Db.LogIndex; using Nethermind.Network; namespace Nethermind.JsonRpc.Benchmark @@ -81,7 +80,6 @@ public void GlobalSetup() feeHistoryOracle, _container.Resolve(), _container.Resolve(), - new LogIndexConfig(), new BlocksConfig().SecondsPerSlot); } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs index 3ecf250af76c..99d9b33ba9c8 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using FluentAssertions; using Nethermind.Core.Extensions; -using Nethermind.Config; using Nethermind.Logging; using Nethermind.JsonRpc.Modules; using NSubstitute; @@ -402,65 +401,6 @@ public async Task Can_handle_null_request() result.DisposeItems(); } - [Test] - public async Task Should_stop_processing_when_shutdown_requested() - { - IJsonRpcService service = Substitute.For(); - service.GetErrorResponse(Arg.Any(), Arg.Any()) - .Returns(new JsonRpcErrorResponse { Error = new Error { Code = ErrorCodes.ResourceUnavailable, Message = "Shutting down" } }); - - IProcessExitSource processExitSource = Substitute.For(); - processExitSource.Token.Returns(new CancellationToken(canceled: true)); - - JsonRpcProcessor processor = new( - service, - new JsonRpcConfig(), - Substitute.For(), - LimboLogs.Instance, - processExitSource); - - string request = "{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"; - List results = await processor.ProcessAsync(request, new JsonRpcContext(RpcEndpoint.Http)).ToListAsync(); - - results.Should().HaveCount(1); - results[0].Response.Should().BeOfType(); - ((JsonRpcErrorResponse)results[0].Response!).Error!.Code.Should().Be(ErrorCodes.ResourceUnavailable); - await service.DidNotReceive().SendRequestAsync(Arg.Any(), Arg.Any()); - results.DisposeItems(); - } - - [Test] - public async Task Should_complete_pipe_reader_when_shutdown_requested() - { - IJsonRpcService service = Substitute.For(); - service.GetErrorResponse(Arg.Any(), Arg.Any()) - .Returns(new JsonRpcErrorResponse { Error = new Error { Code = ErrorCodes.ResourceUnavailable, Message = "Shutting down" } }); - - IProcessExitSource processExitSource = Substitute.For(); - processExitSource.Token.Returns(new CancellationToken(canceled: true)); - - JsonRpcProcessor processor = new( - service, - new JsonRpcConfig(), - Substitute.For(), - LimboLogs.Instance, - processExitSource); - - Pipe pipe = new(); - await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\",\"params\":[]}")); - - List results = await processor.ProcessAsync(pipe.Reader, new JsonRpcContext(RpcEndpoint.Http)).ToListAsync(); - - results.Should().HaveCount(1); - results[0].Response.Should().BeOfType(); - - // Verify PipeReader was completed by the processor (reading again should throw) - await FluentActions.Invoking(async () => await pipe.Reader.ReadAsync()) - .Should().ThrowAsync(); - - results.DisposeItems(); - } - [Test] public void Cannot_accept_null_file_system() { diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs index 82e655f5f597..11e1e43e21e4 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs @@ -6,7 +6,6 @@ using Nethermind.Config; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; -using Nethermind.Db.LogIndex; using Nethermind.JsonRpc.Modules; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Logging; @@ -57,8 +56,7 @@ public Task Initialize() Substitute.For(), Substitute.For(), new BlocksConfig(), - Substitute.For(), - Substitute.For()), + Substitute.For()), 1, 1000); return Task.CompletedTask; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs index a7647dc34534..20cd1112eba5 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs @@ -1173,7 +1173,7 @@ public async Task Send_raw_transaction_will_send_transaction(string rawTransacti string serialized = await ctx.Test.TestEthRpc("eth_sendRawTransaction", rawTransaction); Transaction tx = Rlp.Decode(Bytes.FromHexString(rawTransaction)); await txSender.Received().SendTransaction(tx, TxHandlingOptions.PersistentBroadcast); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"Invalid, InvalidTxSignature: Signature is invalid.\"},\"id\":67}")); + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32010,\"message\":\"Invalid, InvalidTxSignature: Signature is invalid.\"},\"id\":67}")); } [Test] @@ -1224,7 +1224,7 @@ public async Task Send_transaction_should_return_ErrorCode_if_tx_not_added() string serialized = await ctx.Test.TestEthRpc("eth_sendTransaction", txForRpc); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"InsufficientFunds, Balance is zero, cannot pay gas\"},\"id\":67}")); + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32010,\"message\":\"InsufficientFunds, Balance is zero, cannot pay gas\"},\"id\":67}")); } public enum AccessListProvided diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs index 1033def4ff7e..cde82c8033a5 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs @@ -19,7 +19,6 @@ using Nethermind.JsonRpc.Modules.Eth.FeeHistory; using Nethermind.JsonRpc.Modules.Eth.GasPrice; using Nethermind.Config; -using Nethermind.Db.LogIndex; using Nethermind.Network; using Nethermind.State; @@ -56,8 +55,7 @@ public Task Initialize() Substitute.For(), Substitute.For(), new BlocksConfig(), - Substitute.For(), - Substitute.For()); + Substitute.For()); return Task.CompletedTask; } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs index 77ba56292c40..a00b1905ef40 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs @@ -34,7 +34,6 @@ using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Test.Container; -using Nethermind.Db.LogIndex; using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Modules; using Nethermind.JsonRpc.Modules.Trace; @@ -59,7 +58,6 @@ public class TestRpcBlockchain : TestBlockchain public IReceiptFinder ReceiptFinder => Container.Resolve(); public IGasPriceOracle GasPriceOracle { get; private set; } = null!; public IProtocolsManager ProtocolsManager { get; private set; } = null!; - public ILogIndexConfig LogIndexConfig { get; } = new LogIndexConfig(); public IKeyStore KeyStore { get; } = new MemKeyStore(TestItem.PrivateKeys, Path.Combine("testKeyStoreDir", Path.GetRandomFileName())); public IWallet TestWallet { get; } = @@ -195,7 +193,6 @@ public async Task Build(Action configurer) new FeeHistoryOracle(@this.BlockTree, @this.ReceiptStorage, @this.SpecProvider), @this.ProtocolsManager, @this.ForkInfo, - @this.LogIndexConfig, @this.BlocksConfig.SecondsPerSlot); protected override async Task Build(Action? configurer = null) @@ -258,7 +255,7 @@ public async Task RestartBlockchainProcessor() // simulating restarts - we stopped the old blockchain processor and create the new one _currentBlockchainProcessor = new BlockchainProcessor(BlockTree, BranchProcessor, - BlockPreprocessorStep, StateReader, LimboLogs.Instance, Nethermind.Consensus.Processing.BlockchainProcessor.Options.Default, Substitute.For()); + BlockPreprocessorStep, StateReader, LimboLogs.Instance, Nethermind.Consensus.Processing.BlockchainProcessor.Options.Default); _currentBlockchainProcessor.Start(); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/EthRpcJsonContext.cs b/src/Nethermind/Nethermind.JsonRpc/Data/EthRpcJsonContext.cs deleted file mode 100644 index a7537c67d09c..000000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Data/EthRpcJsonContext.cs +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Text.Json.Serialization; -using Nethermind.Blockchain.Tracing.GethStyle; -using Nethermind.Blockchain.Tracing.ParityStyle; -using Nethermind.Evm; -using Nethermind.JsonRpc.Modules.DebugModule; -using Nethermind.JsonRpc.Modules.Eth; -using Nethermind.JsonRpc.Modules.Trace; -using Nethermind.State.Proofs; - -namespace Nethermind.JsonRpc.Data; - -[JsonSourceGenerationOptions( - GenerationMode = JsonSourceGenerationMode.Metadata, - PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] -// eth_ types -[JsonSerializable(typeof(ReceiptForRpc))] -[JsonSerializable(typeof(LogEntryForRpc))] -[JsonSerializable(typeof(FeeHistoryResults))] -[JsonSerializable(typeof(AccessListResultForRpc))] -[JsonSerializable(typeof(AccountInfoForRpc))] -[JsonSerializable(typeof(AccountOverride))] -[JsonSerializable(typeof(AccountProof))] -[JsonSerializable(typeof(BadBlock))] -// debug_ types -[JsonSerializable(typeof(GethLikeTxTrace))] -[JsonSerializable(typeof(GethTraceOptions))] -[JsonSerializable(typeof(ChainLevelForRpc))] -// trace_ types -[JsonSerializable(typeof(ParityTxTraceFromReplay))] -[JsonSerializable(typeof(ParityTxTraceFromStore))] -[JsonSerializable(typeof(ParityLikeTxTrace))] -[JsonSerializable(typeof(TraceFilterForRpc))] -public partial class EthRpcJsonContext : JsonSerializerContext; diff --git a/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs b/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs index 47bc3b4dc769..cea92a933c03 100644 --- a/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs +++ b/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs @@ -48,25 +48,35 @@ public static class ErrorCodes public const int ResourceNotFound = -32000; /// - /// Transaction creation failed + /// Requested resource not available /// - public const int TransactionRejected = -32000; + public const int ResourceUnavailable = -32002; /// - /// Requested resource not available + /// Transaction creation failed /// - public const int ResourceUnavailable = -32002; + public const int TransactionRejected = -32010; /// /// Account locked /// public const int AccountLocked = -32020; + /// + /// Method is not implemented + /// + public const int MethodNotSupported = -32004; + /// /// Request exceeds defined limit /// public const int LimitExceeded = -32005; + /// + /// + /// + public const int ExecutionError = -32015; + /// /// Request exceeds defined timeout limit /// @@ -97,6 +107,16 @@ public static class ErrorCodes /// public const int InvalidInputTooManyBlocks = -38026; + /// + /// Invalid RPC simulate call Not enough gas provided to pay for intrinsic gas for a transaction + /// + public const int InsufficientIntrinsicGas = -38013; + + /// + /// Invalid RPC simulate call transaction + /// + public const int InvalidTransaction = -38014; + /// /// Too many blocks for simulation /// @@ -112,6 +132,16 @@ public static class ErrorCodes /// public const int Default = -32000; + /// + /// Transaction.Nonce is bigger than expected nonce + /// + public const int NonceTooHigh = -38011; + + /// + /// Transaction.Nonce is smaller than expected nonce + /// + public const int NonceTooLow = -38010; + /// /// Invalid intrinsic gas. Miner premium is negative /// @@ -127,6 +157,21 @@ public static class ErrorCodes /// public const int BlockGasLimitReached = -38015; + /// + /// Invalid block number + /// + public const int BlockNumberInvalid = -38020; + + /// + /// Invalid block timestamp + /// + public const int BlockTimestampInvalid = -38021; + + /// + /// Transaction.Sender is not an EOA + /// + public const int SenderIsNotEOA = -38024; + /// /// EIP-3860. Code size is to big /// @@ -141,5 +186,7 @@ public static class ErrorCodes /// Error during EVM execution /// public const int VMError = -32015; + public const int TxSyncTimeout = 4; + public const int ClientLimitExceeded = -38026; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcResult.cs b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcResult.cs new file mode 100644 index 000000000000..845540f0d11e --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcResult.cs @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.JsonRpc +{ + public interface IJsonRpcResult + { + object ToJson(); + } +} diff --git a/src/Nethermind/Nethermind.JsonRpc/IStreamableResult.cs b/src/Nethermind/Nethermind.JsonRpc/IStreamableResult.cs deleted file mode 100644 index 6e3c1cf02c03..000000000000 --- a/src/Nethermind/Nethermind.JsonRpc/IStreamableResult.cs +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.IO.Pipelines; -using System.Threading; -using System.Threading.Tasks; - -namespace Nethermind.JsonRpc; - -/// -/// Implemented by result objects that can write themselves directly to a , -/// bypassing to avoid extra buffer copies. -/// -public interface IStreamableResult -{ - ValueTask WriteToAsync(PipeWriter writer, CancellationToken cancellationToken); -} diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfigExtension.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfigExtension.cs index acb929e83633..ce2181efb4f5 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfigExtension.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfigExtension.cs @@ -1,22 +1,15 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading; namespace Nethermind.JsonRpc { public static class JsonRpcConfigExtension { - private static readonly ConcurrentQueue _ctsPool = new(); - private const int MaxPoolSize = 64; - private static int _ctsPoolSize; - public static void EnableModules(this IJsonRpcConfig config, params string[] modules) { HashSet enabledModules = config.EnabledModules.ToHashSet(); @@ -28,45 +21,12 @@ public static void EnableModules(this IJsonRpcConfig config, params string[] mod } /// - /// Rents a that timeouts after . - /// When debugger is attached, no timeout is applied. - /// Call to return to pool. + /// Constructs a that timeouts after + /// if is false. /// public static CancellationTokenSource BuildTimeoutCancellationToken(this IJsonRpcConfig config) { - if (Debugger.IsAttached) - { - return new CancellationTokenSource(); - } - - if (_ctsPool.TryDequeue(out CancellationTokenSource? cts)) - { - Interlocked.Decrement(ref _ctsPoolSize); - cts.CancelAfter(config.Timeout); - return cts; - } - - return new CancellationTokenSource(config.Timeout); - } - - /// - /// Returns a CTS to the pool if it can be reset, otherwise disposes it. - /// - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ReturnTimeoutCancellationToken(CancellationTokenSource cts) - { - if (cts.TryReset()) - { - if (Interlocked.Increment(ref _ctsPoolSize) <= MaxPoolSize) - { - _ctsPool.Enqueue(cts); - return; - } - - Interlocked.Decrement(ref _ctsPoolSize); - } - - cts.Dispose(); + return Debugger.IsAttached ? new CancellationTokenSource() : new CancellationTokenSource(config.Timeout); } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcContext.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcContext.cs index b8e3f2f803f7..c145607924d5 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcContext.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcContext.cs @@ -2,16 +2,14 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Threading; using Nethermind.JsonRpc.Modules; namespace Nethermind.JsonRpc { public class JsonRpcContext : IDisposable { - [ThreadStatic] - private static JsonRpcContext? _current; - - public static ThreadStaticAccessor Current { get; } = new(); + public static AsyncLocal Current { get; private set; } = new(); public static JsonRpcContext Http(JsonRpcUrl url) => new(RpcEndpoint.Http, url: url); public static JsonRpcContext WebSocket(JsonRpcUrl url) => new(RpcEndpoint.Ws, url: url); @@ -22,7 +20,7 @@ public JsonRpcContext(RpcEndpoint rpcEndpoint, IJsonRpcDuplexClient? duplexClien DuplexClient = duplexClient; Url = url; IsAuthenticated = Url?.IsAuthenticated == true || RpcEndpoint == RpcEndpoint.IPC; - _current = this; + Current.Value = this; } public RpcEndpoint RpcEndpoint { get; } @@ -31,22 +29,9 @@ public JsonRpcContext(RpcEndpoint rpcEndpoint, IJsonRpcDuplexClient? duplexClien public bool IsAuthenticated { get; } public void Dispose() { - if (_current == this) - { - _current = null; - } - } - - /// - /// Provides .Value accessor compatible with AsyncLocal API shape so callers - /// like JsonRpcContext.Current.Value?.IsAuthenticated continue to compile. - /// - public sealed class ThreadStaticAccessor - { - public JsonRpcContext? Value + if (Current.Value == this) { - get => _current; - set => _current = value; + Current.Value = null; } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs index 8093b2c40c97..bd042072fa1c 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs @@ -9,6 +9,7 @@ using System.IO; using System.IO.Abstractions; using System.IO.Pipelines; +using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; @@ -26,7 +27,7 @@ namespace Nethermind.JsonRpc; -public sealed class JsonRpcProcessor : IJsonRpcProcessor +public class JsonRpcProcessor : IJsonRpcProcessor { private readonly IJsonRpcConfig _jsonRpcConfig; private readonly ILogger _logger; @@ -89,7 +90,7 @@ private JsonRpcRequest DeserializeObject(JsonElement element) { id = idNumber; } - else if (idElement.TryGetDecimal(out var value)) + else if (decimal.TryParse(idElement.GetRawText(), out var value)) { id = value; } @@ -103,7 +104,7 @@ private JsonRpcRequest DeserializeObject(JsonElement element) string? method = null; if (element.TryGetProperty("method"u8, out JsonElement methodElement)) { - method = InternMethodName(methodElement); + method = methodElement.GetString(); } if (!element.TryGetProperty("params"u8, out JsonElement paramsElement)) @@ -120,61 +121,30 @@ private JsonRpcRequest DeserializeObject(JsonElement element) }; } - private ArrayPoolList DeserializeArray(JsonElement element) - { - ArrayPoolList list = new(element.GetArrayLength()); - foreach (JsonElement item in element.EnumerateArray()) - { - list.Add(DeserializeObject(item)); - } - return list; - } - - /// - /// Returns a cached string constant for known engine method names to avoid allocation. - /// Falls back to for unknown methods. - /// - private static string? InternMethodName(JsonElement methodElement) - { - if (methodElement.ValueEquals("engine_newPayloadV4"u8)) return "engine_newPayloadV4"; - if (methodElement.ValueEquals("engine_forkchoiceUpdatedV3"u8)) return "engine_forkchoiceUpdatedV3"; - if (methodElement.ValueEquals("engine_newPayloadV3"u8)) return "engine_newPayloadV3"; - if (methodElement.ValueEquals("engine_forkchoiceUpdatedV2"u8)) return "engine_forkchoiceUpdatedV2"; - if (methodElement.ValueEquals("engine_getPayloadV4"u8)) return "engine_getPayloadV4"; - if (methodElement.ValueEquals("engine_getPayloadV3"u8)) return "engine_getPayloadV3"; - if (methodElement.ValueEquals("engine_newPayloadV2"u8)) return "engine_newPayloadV2"; - if (methodElement.ValueEquals("engine_newPayloadV1"u8)) return "engine_newPayloadV1"; - if (methodElement.ValueEquals("engine_exchangeCapabilities"u8)) return "engine_exchangeCapabilities"; - return methodElement.GetString(); - } + private ArrayPoolList DeserializeArray(JsonElement element) => + new(element.GetArrayLength(), element.EnumerateArray().Select(DeserializeObject)); private static readonly JsonReaderOptions _socketJsonReaderOptions = new() { AllowMultipleValues = true }; public async IAsyncEnumerable ProcessAsync(PipeReader reader, JsonRpcContext context) { - // Engine API (authenticated) requests skip the timeout CTS entirely -- they are from - // trusted consensus clients, must complete for consensus, and connection drops are - // handled by the PipeReader. Non-engine paths still use the pooled CTS. - CancellationTokenSource? timeoutSource = context.IsAuthenticated ? null : _jsonRpcConfig.BuildTimeoutCancellationToken(); - CancellationToken timeoutToken = timeoutSource?.Token ?? CancellationToken.None; - try + if (ProcessExit.IsCancellationRequested) { - if (ProcessExit.IsCancellationRequested) - { - JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ResourceUnavailable, "Shutting down"); - yield return JsonRpcResult.Single(RecordResponse(response, new RpcReport("Shutdown", 0, false))); - yield break; - } - - if (IsRecordingRequest) - { - reader = await RecordRequest(reader); - } + JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ResourceUnavailable, "Shutting down"); + yield return JsonRpcResult.Single(RecordResponse(response, new RpcReport("Shutdown", 0, false))); + } - JsonReaderState readerState = CreateJsonReaderState(context); - bool freshState = true; - bool shouldExit = false; + if (IsRecordingRequest) + { + reader = await RecordRequest(reader); + } + using CancellationTokenSource timeoutSource = _jsonRpcConfig.BuildTimeoutCancellationToken(); + JsonReaderState readerState = CreateJsonReaderState(context); + bool freshState = true; + bool shouldExit = false; + try + { while (!shouldExit) { long startTime = Stopwatch.GetTimestamp(); @@ -182,7 +152,7 @@ public async IAsyncEnumerable ProcessAsync(PipeReader reader, Jso ReadResult readResult; try { - readResult = await reader.ReadAsync(timeoutToken); + readResult = await reader.ReadAsync(timeoutSource.Token); } catch (BadHttpRequestException e) { @@ -261,8 +231,6 @@ public async IAsyncEnumerable ProcessAsync(PipeReader reader, Jso finally { await reader.CompleteAsync(); - if (timeoutSource is not null) - JsonRpcConfigExtension.ReturnTimeoutCancellationToken(timeoutSource); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcResponseJsonContext.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcResponseJsonContext.cs deleted file mode 100644 index 322a04158fa3..000000000000 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcResponseJsonContext.cs +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Text.Json.Serialization; - -namespace Nethermind.JsonRpc; - -[JsonSourceGenerationOptions( - GenerationMode = JsonSourceGenerationMode.Metadata, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] -[JsonSerializable(typeof(JsonRpcSuccessResponse))] -[JsonSerializable(typeof(JsonRpcErrorResponse))] -[JsonSerializable(typeof(JsonRpcResponse))] -[JsonSerializable(typeof(Error))] -public partial class JsonRpcResponseJsonContext : JsonSerializerContext; diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs index 781e7da43de2..1246c6b49cf8 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs @@ -9,7 +9,6 @@ using System.Text; using System.Text.Json; using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; using Nethermind.Blockchain; @@ -25,7 +24,7 @@ namespace Nethermind.JsonRpc; -public sealed class JsonRpcService : IJsonRpcService +public class JsonRpcService : IJsonRpcService { private readonly static Lock _reparseLock = new(); private static Dictionary _reparseReflectionCache = new(); @@ -345,7 +344,7 @@ private void LogRequest(string methodName, JsonElement providedParameters, Expec { foreach (JsonElement param in providedParameters.EnumerateArray()) { - string? parameter = (uint)paramsCount < (uint)expectedParameters.Length && expectedParameters[paramsCount].Info?.Name == "passphrase" + string? parameter = expectedParameters.ElementAtOrDefault(paramsCount).Info?.Name == "passphrase" ? "{passphrase}" : param.GetRawText(); @@ -404,7 +403,6 @@ private void LogRequest(string methodName, JsonElement providedParameters, Expec } else { - EthereumJsonSerializer.JsonOptions.TryGetTypeInfo(paramType, out JsonTypeInfo? typeInfo); if (providedParameter.ValueKind == JsonValueKind.String) { if (!_reparseReflectionCache.TryGetValue(paramType, out bool reparseString)) @@ -414,15 +412,11 @@ private void LogRequest(string methodName, JsonElement providedParameters, Expec executionParam = reparseString ? JsonSerializer.Deserialize(providedParameter.GetString(), paramType, EthereumJsonSerializer.JsonOptions) - : typeInfo is not null - ? providedParameter.Deserialize(typeInfo) - : providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); + : providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); } else { - executionParam = typeInfo is not null - ? providedParameter.Deserialize(typeInfo) - : providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); + executionParam = providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/TransactionBundle.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/TransactionBundle.cs index 4e11f17c79cb..3d1119669d45 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/TransactionBundle.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/TransactionBundle.cs @@ -5,6 +5,7 @@ using Nethermind.Core; using Nethermind.Evm; using Nethermind.Facade.Eth.RpcTransaction; +using Nethermind.Facade.Proxy.Models.Simulate; namespace Nethermind.JsonRpc.Modules.DebugModule; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs index 7ead40a36d49..d5b6001e4aa7 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs @@ -5,7 +5,6 @@ using Nethermind.Blockchain.Receipts; using Nethermind.Config; using Nethermind.Core.Specs; -using Nethermind.Db.LogIndex; using Nethermind.Facade; using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Modules.Eth.GasPrice; @@ -34,8 +33,7 @@ public class EthModuleFactory( IFeeHistoryOracle feeHistoryOracle, IProtocolsManager protocolsManager, IBlocksConfig blocksConfig, - IForkInfo forkInfo, - ILogIndexConfig logIndexConfig) + IForkInfo forkInfo) : ModuleFactoryBase { private readonly ulong _secondsPerSlot = blocksConfig.SecondsPerSlot; @@ -59,7 +57,6 @@ public override IEthRpcModule Create() feeHistoryOracle, protocolsManager, forkInfo, - logIndexConfig, _secondsPerSlot); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index bce797084c92..713bccba0101 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -9,7 +9,6 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; -using Nethermind.Db.LogIndex; using Nethermind.Evm; using Nethermind.Evm.Precompiles; using Nethermind.Facade; @@ -66,7 +65,6 @@ public partial class EthRpcModule( IFeeHistoryOracle feeHistoryOracle, IProtocolsManager protocolsManager, IForkInfo forkInfo, - ILogIndexConfig? logIndexConfig, ulong? secondsPerSlot) : IEthRpcModule { public const int GetProofStorageKeyLimit = 1000; @@ -647,10 +645,6 @@ public ResultWrapper> eth_getLogs(Filter filter) { LogFilter logFilter = _blockchainBridge.GetFilter(fromBlock, toBlock, filter.Address, filter.Topics); - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - can be null in tests - if (logFilter is not null && filter is not null) - logFilter.UseIndex = filter.UseIndex; - IEnumerable filterLogs = _blockchainBridge.GetLogs(logFilter, fromBlockHeader, toBlockHeader, cancellationToken); ArrayPoolList logs = new(_rpcConfig.MaxLogsPerResponse); @@ -667,9 +661,6 @@ public ResultWrapper> eth_getLogs(Filter filter) } } - if (logIndexConfig?.VerifyRpcResponse is true && logFilter.UseIndex) - VerifyLogsResponse(logs, logFilter, fromBlockHeader, toBlockHeader, cancellationToken); - return ResultWrapper>.Success(logs); } catch (ResourceNotFoundException exception) @@ -848,28 +839,4 @@ public ResultWrapper eth_config() private CancellationTokenSource BuildTimeoutCancellationTokenSource() => _rpcConfig.BuildTimeoutCancellationToken(); - - private void VerifyLogsResponse(IList response, LogFilter filter, BlockHeader from, BlockHeader to, CancellationToken cancellation) - { - filter.UseIndex = false; - IEnumerable? expectedResponse = _blockchainBridge.GetLogs(filter, from, to, cancellation); - - using IEnumerator expectedEnum = expectedResponse.GetEnumerator(); - - var i = -1; - while (++i < response.Count | expectedEnum.MoveNext()) - { - FilterLog? actual = i < response.Count ? response[i] : null; - FilterLog? expected = expectedEnum.Current; - - if ((actual?.BlockNumber, actual?.LogIndex) != (expected?.BlockNumber, expected?.LogIndex)) - { - throw new LogIndexStateException( - $"Incorrect result from log index at position #{i}. " + - $"Expected: block {expected?.BlockNumber}, log #{expected?.LogIndex}. " + - $"Actual: block {actual?.BlockNumber}, log #{actual?.LogIndex}." - ); - } - } - } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs index 3f79cbd33cc3..57f26cee883a 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs @@ -15,14 +15,12 @@ public class Filter : IJsonRpcParam { public HashSet? Address { get; set; } - public BlockParameter FromBlock { get; set; } = BlockParameter.Latest; + public BlockParameter FromBlock { get; set; } - public BlockParameter ToBlock { get; set; } = BlockParameter.Latest; + public BlockParameter ToBlock { get; set; } public IEnumerable? Topics { get; set; } - public bool UseIndex { get; set; } = true; - public void ReadJson(JsonElement filter, JsonSerializerOptions options) { JsonDocument doc = null; @@ -67,16 +65,6 @@ public void ReadJson(JsonElement filter, JsonSerializerOptions options) { Topics = GetTopics(topicsElement, options); } - - if (filter.TryGetProperty("useIndex"u8, out JsonElement useIndex)) - { - UseIndex = useIndex.ValueKind switch - { - JsonValueKind.False => false, - JsonValueKind.True => true, - _ => UseIndex - }; - } } finally { diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/ILogIndexRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/ILogIndexRpcModule.cs deleted file mode 100644 index 9a62dd485a05..000000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/ILogIndexRpcModule.cs +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Nethermind.JsonRpc.Modules.Eth; - -namespace Nethermind.JsonRpc.Modules.LogIndex; - -[RpcModule(ModuleType.LogIndex)] -public interface ILogIndexRpcModule : IRpcModule -{ - [JsonRpcMethod(Description = "Retrieves log index block number for the given filter.", IsImplemented = true, IsSharable = true)] - ResultWrapper> logIndex_blockNumbers([JsonRpcParameter] Filter filter); - - [JsonRpcMethod(Description = "Retrieves log index status.", IsImplemented = true, IsSharable = true)] - ResultWrapper logIndex_status(); -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexRpcModule.cs deleted file mode 100644 index 94c4a48272ba..000000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexRpcModule.cs +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Nethermind.Blockchain.Filters; -using Nethermind.Blockchain.Find; -using Nethermind.Db.LogIndex; -using Nethermind.Facade; -using Nethermind.Facade.Filters; -using Nethermind.Facade.Find; -using Nethermind.JsonRpc.Modules.Eth; - -namespace Nethermind.JsonRpc.Modules.LogIndex; - -public class LogIndexRpcModule(ILogIndexStorage storage, ILogIndexBuilder builder, IBlockFinder blockFinder, IBlockchainBridge blockchainBridge) - : ILogIndexRpcModule -{ - public ResultWrapper> logIndex_blockNumbers(Filter filter) - { - LogFilter logFilter = blockchainBridge.GetFilter(filter.FromBlock, filter.ToBlock, filter.Address, filter.Topics); - - if (GetBlockNumber(logFilter.FromBlock) is not { } from) - return ResultWrapper>.Fail($"Block {logFilter.FromBlock} is not found.", ErrorCodes.UnknownBlockError); - - if (GetBlockNumber(logFilter.ToBlock) is not { } to) - return ResultWrapper>.Fail($"Block {logFilter.ToBlock} is not found.", ErrorCodes.UnknownBlockError); - - return ResultWrapper>.Success(storage.EnumerateBlockNumbersFor(logFilter, from, to)); - } - - public ResultWrapper logIndex_status() - { - return ResultWrapper.Success(new() - { - Current = new() - { - FromBlock = storage.MinBlockNumber, - ToBlock = storage.MaxBlockNumber - }, - Target = new() - { - FromBlock = builder.MinTargetBlockNumber, - ToBlock = builder.MaxTargetBlockNumber - }, - IsRunning = builder.IsRunning, - LastUpdate = builder.LastUpdate, - LastError = builder.LastError?.ToString(), - DbSize = storage.GetDbSize() - }); - } - - private long? GetBlockNumber(BlockParameter parameter) => - parameter.BlockNumber ?? blockFinder.FindBlock(parameter)?.Number; -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexStatus.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexStatus.cs deleted file mode 100644 index 47a7d1b10180..000000000000 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexStatus.cs +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; - -namespace Nethermind.JsonRpc.Modules.LogIndex; - -public class LogIndexStatus -{ - public readonly record struct Range(int? FromBlock, int? ToBlock); - - public required Range Current { get; init; } - public required Range Target { get; init; } - public bool IsRunning { get; init; } - public DateTimeOffset? LastUpdate { get; init; } - public string? LastError { get; init; } - public required string DbSize { get; init; } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs index 3dc4ec0e2707..a93b8fdc203f 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs @@ -14,7 +14,6 @@ public static class ModuleType public const string Debug = nameof(Debug); public const string Erc20 = nameof(Erc20); public const string Eth = nameof(Eth); - public const string LogIndex = nameof(LogIndex); public const string Evm = nameof(Evm); public const string Flashbots = nameof(Flashbots); public const string Net = nameof(Net); @@ -26,6 +25,7 @@ public static class ModuleType public const string Trace = nameof(Trace); public const string TxPool = nameof(TxPool); public const string Web3 = nameof(Web3); + public const string Vault = nameof(Vault); public const string Deposit = nameof(Deposit); public const string Health = nameof(Health); public const string Rpc = nameof(Rpc); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs index 1b2580b8591f..2c00637b395e 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs @@ -1054,7 +1054,8 @@ private MultiSyncModeSelector CreateMultiSyncModeSelector(MergeTestBlockchain ch Substitute.For>(), Substitute.For>(), Substitute.For>(), - Substitute.For>()); + Substitute.For>(), + LimboLogs.Instance); MultiSyncModeSelector multiSyncModeSelector = new(syncProgressResolver, syncPeerPool, new SyncConfig(), No.BeaconSync, diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index d0ba23fd16c3..d8f6335d262b 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -13,7 +13,6 @@ using FluentAssertions; using Nethermind.Blockchain; using Nethermind.Blockchain.Find; -using Nethermind.Consensus.Processing; using Nethermind.Consensus.Producers; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -669,19 +668,7 @@ public async Task forkChoiceUpdatedV1_block_still_processing() Block blockTreeHead = chain.BlockTree.Head!; Block block = Build.A.Block.WithNumber(blockTreeHead.Number + 1).WithParent(blockTreeHead).WithNonce(0).WithDifficulty(0).TestObject; - chain.ThrottleBlockProcessor(1000); - ManualResetEventSlim processingStarted = new(false); - ((TestBranchProcessorInterceptor)chain.BranchProcessor).ProcessingStarted = processingStarted; - - // Directly enqueue a block to occupy the processor (bypasses the RPC semaphore), - // ensuring subsequent blocks route through the recovery queue (slow path) - Block occupyBlock = Build.A.Block.WithNumber(blockTreeHead.Number + 1).WithParent(blockTreeHead) - .WithNonce(0).WithDifficulty(0).WithStateRoot(blockTreeHead.StateRoot!).TestObject; - occupyBlock.Header.TotalDifficulty = blockTreeHead.TotalDifficulty; - _ = Task.Run(async () => await chain.BlockProcessingQueue.Enqueue( - occupyBlock, ProcessingOptions.ForceProcessing | ProcessingOptions.DoNotUpdateHead)); - processingStarted.Wait(TimeSpan.FromSeconds(5)); - + chain.ThrottleBlockProcessor(200); ResultWrapper newPayloadV1 = await rpc.engine_newPayloadV1(ExecutionPayload.Create(block)); newPayloadV1.Data.Status.Should().Be("SYNCING"); @@ -762,18 +749,6 @@ public async Task Invalid_block_on_processing_wont_be_accepted_if_sent_twice_in_ .TestObject; chain.ThrottleBlockProcessor(1000); // throttle the block processor enough so that the block processing queue is never empty - ManualResetEventSlim processingStarted = new(false); - ((TestBranchProcessorInterceptor)chain.BranchProcessor).ProcessingStarted = processingStarted; - - // Directly enqueue a block to occupy the processor (bypasses the RPC semaphore), - // ensuring subsequent blocks route through the recovery queue (slow path) - Block occupyBlock = Build.A.Block.WithNumber(head.Number + 1).WithParent(head) - .WithNonce(0).WithDifficulty(0).WithStateRoot(head.StateRoot!).TestObject; - occupyBlock.Header.TotalDifficulty = head.TotalDifficulty; - _ = Task.Run(async () => await chain.BlockProcessingQueue.Enqueue( - occupyBlock, ProcessingOptions.ForceProcessing | ProcessingOptions.DoNotUpdateHead)); - processingStarted.Wait(TimeSpan.FromSeconds(5)); - (await rpc.engine_newPayloadV1(ExecutionPayload.Create(block))).Data.Status.Should().Be(PayloadStatus.Syncing); (await rpc.engine_newPayloadV1(ExecutionPayload.Create(block))).Data.Status.Should().BeOneOf(PayloadStatus.Syncing); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs index e34ec509534a..7940bc6c34b7 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs @@ -4,10 +4,7 @@ using System; using System.Collections.Generic; using System.IO.Abstractions; -using System.IO.Pipelines; using System.Linq; -using System.Text; -using System.Text.Json; using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; @@ -17,7 +14,6 @@ using Nethermind.Blockchain.Synchronization; using Nethermind.Consensus.Producers; using Nethermind.Core; -using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; @@ -687,33 +683,6 @@ public async Task GetBlobsV1_should_return_mix_of_blobs_and_nulls([Values(1, 2, } } - [Test] - public async Task BlobsV1DirectResponse_WriteToAsync_produces_valid_json() - { - byte[] blob = new byte[16]; - Random.Shared.NextBytes(blob); - byte[] proof = new byte[48]; - Random.Shared.NextBytes(proof); - - ArrayPoolList items = new(2); - items.Add(new BlobAndProofV1(blob, proof)); - items.Add(null); - - using BlobsV1DirectResponse response = new(items); - - Pipe pipe = new(); - await response.WriteToAsync(pipe.Writer, CancellationToken.None); - await pipe.Writer.CompleteAsync(); - - ReadResult readResult = await pipe.Reader.ReadAsync(); - string streamedJson = Encoding.UTF8.GetString(readResult.Buffer); - pipe.Reader.AdvanceTo(readResult.Buffer.End); - - string stjJson = JsonSerializer.Serialize(response, EthereumJsonSerializer.JsonOptions); - - streamedJson.Should().Be(stjJson); - } - [Test] public async Task Sync_proper_chain_when_header_fork_came_from_fcu_and_beacon_sync() { diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs index 218014837ee0..825da6cc4e1a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs @@ -3,11 +3,7 @@ using System; using System.Collections.Generic; -using System.IO.Pipelines; using System.Linq; -using System.Text; -using System.Text.Json; -using System.Threading; using System.Threading.Tasks; using CkzgLib; using FluentAssertions; @@ -18,7 +14,6 @@ using Nethermind.Evm; using Nethermind.JsonRpc; using Nethermind.Merge.Plugin.Data; -using Nethermind.Serialization.Json; using Nethermind.Specs.Forks; using Nethermind.TxPool; using NUnit.Framework; @@ -251,67 +246,4 @@ public async Task GetBlobsV1_should_return_invalid_fork_post_osaka() result.Result.Should().BeEquivalentTo(Result.Fail(MergeErrorMessages.UnsupportedFork)); result.ErrorCode.Should().Be(MergeErrorCodes.UnsupportedFork); } - - [Test] - public async Task BlobsV2DirectResponse_WriteToAsync_produces_valid_json() - { - // Build a small list with one real entry and one null - byte[] blob = new byte[16]; - Random.Shared.NextBytes(blob); - byte[] proof1 = new byte[48]; - Random.Shared.NextBytes(proof1); - byte[] proof2 = new byte[48]; - Random.Shared.NextBytes(proof2); - - byte[]?[] blobs = [blob, null]; - ReadOnlyMemory[] proofs = [new ReadOnlyMemory([proof1, proof2]), default]; - - BlobsV2DirectResponse response = new(blobs, proofs, 2); - - // Write via streaming path - Pipe pipe = new(); - await response.WriteToAsync(pipe.Writer, CancellationToken.None); - await pipe.Writer.CompleteAsync(); - - ReadResult readResult = await pipe.Reader.ReadAsync(); - string streamedJson = Encoding.UTF8.GetString(readResult.Buffer); - pipe.Reader.AdvanceTo(readResult.Buffer.End); - - // Write via STJ for comparison - string stjJson = JsonSerializer.Serialize(response, EthereumJsonSerializer.JsonOptions); - - streamedJson.Should().Be(stjJson); - } - - [Test] - public async Task BlobsV2DirectResponse_WriteToAsync_empty_list() - { - BlobsV2DirectResponse response = new([], [], 0); - - Pipe pipe = new(); - await response.WriteToAsync(pipe.Writer, CancellationToken.None); - await pipe.Writer.CompleteAsync(); - - ReadResult readResult = await pipe.Reader.ReadAsync(); - string json = Encoding.UTF8.GetString(readResult.Buffer); - pipe.Reader.AdvanceTo(readResult.Buffer.End); - - json.Should().Be("[]"); - } - - [Test] - public void BlobsV2DirectResponse_WriteToAsync_throws_on_cancelled_token() - { - byte[] blob = new byte[131072]; // 128KB - byte[]?[] blobs = [blob]; - ReadOnlyMemory[] proofs = [new ReadOnlyMemory([new byte[48]])]; - BlobsV2DirectResponse response = new(blobs, proofs, 1); - - using CancellationTokenSource cts = new(); - cts.Cancel(); - - Pipe pipe = new(); - Func act = async () => await response.WriteToAsync(pipe.Writer, cts.Token); - act.Should().ThrowAsync(); - } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs index 3a49eb9e9358..f111b2197bcc 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs @@ -3,12 +3,9 @@ /* cSpell:disable */ using System; -using System.Security.Cryptography; -using System.Text; using System.Threading.Tasks; using Nethermind.Core; using Nethermind.Core.Authentication; -using Nethermind.Core.Extensions; using Nethermind.Logging; using NUnit.Framework; @@ -16,9 +13,6 @@ namespace Nethermind.Merge.Plugin.Test; public class JwtTest { - private const string HexSecret = "5166546A576E5A7234753778214125442A472D4A614E645267556B5870327335"; - private const long TestIat = 1644994971; - [Test] [TestCase("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDQ5OTQ5NzF9.RmIbZajyYGF9fhAq7A9YrTetdf15ebHIJiSdAhX7PME", "true")] [TestCase("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDQ5OTQ5NzV9.HfWy49SIyB12PBB_xEpy6IAiIan5mIqD6Jzeh_J1QNw", "true")] @@ -36,135 +30,12 @@ public class JwtTest [TestCase("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDQ5OTQ5NjF9.huhtaE1cUU2JuhqKmeTrHC3wgl2Tp_1pVh7DuYkKrQo", "true")] public async Task long_key_tests(string token, bool expected) { - ManualTimestamper manualTimestamper = new() { UtcNow = DateTimeOffset.FromUnixTimeSeconds(TestIat).UtcDateTime }; - IRpcAuthentication authentication = JwtAuthentication.FromSecret(HexSecret, manualTimestamper, LimboTraceLogger.Instance); - IRpcAuthentication authenticationWithPrefix = JwtAuthentication.FromSecret("0x" + HexSecret, manualTimestamper, LimboTraceLogger.Instance); + ManualTimestamper manualTimestamper = new() { UtcNow = DateTimeOffset.FromUnixTimeSeconds(1644994971).UtcDateTime }; + IRpcAuthentication authentication = JwtAuthentication.FromSecret("5166546A576E5A7234753778214125442A472D4A614E645267556B5870327335", manualTimestamper, LimboTraceLogger.Instance); + IRpcAuthentication authenticationWithPrefix = JwtAuthentication.FromSecret("0x5166546A576E5A7234753778214125442A472D4A614E645267556B5870327335", manualTimestamper, LimboTraceLogger.Instance); bool actual = await authentication.Authenticate(token); Assert.That(actual, Is.EqualTo(expected)); actual = await authenticationWithPrefix.Authenticate(token); Assert.That(expected, Is.EqualTo(actual)); } - - // --- Guard clause tests (Authenticate entry) --- - - [Test] - public async Task Null_token_returns_false() - { - Assert.That(await CreateAuth().Authenticate(null!), Is.False); - } - - [Test] - public async Task Empty_token_returns_false() - { - Assert.That(await CreateAuth().Authenticate(""), Is.False); - } - - [Test] - public async Task Missing_bearer_prefix_returns_false() - { - // Valid JWT but without "Bearer " prefix - string jwt = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat}}}"); - Assert.That(await CreateAuth().Authenticate(jwt["Bearer ".Length..]), Is.False); - } - - // --- Alternate header format tests (TryValidateManual branches) --- - - [Test] - public async Task HeaderTypAlg_valid_token_returns_true() - { - // {"typ":"JWT","alg":"HS256"} — reversed field order, 36-char Base64Url header - string token = CreateJwt("{\"typ\":\"JWT\",\"alg\":\"HS256\"}", $"{{\"iat\":{TestIat}}}"); - Assert.That(await CreateAuth().Authenticate(token), Is.True); - } - - [Test] - public async Task HeaderAlgOnly_valid_token_returns_true() - { - // {"alg":"HS256"} — no typ field, 20-char Base64Url header - string token = CreateJwt("{\"alg\":\"HS256\"}", $"{{\"iat\":{TestIat}}}"); - Assert.That(await CreateAuth().Authenticate(token), Is.True); - } - - [Test] - public async Task HeaderAlgOnly_expired_iat_returns_false() - { - string token = CreateJwt("{\"alg\":\"HS256\"}", $"{{\"iat\":{TestIat - 200}}}"); - Assert.That(await CreateAuth().Authenticate(token), Is.False); - } - - // --- AuthenticateCore fallback (unrecognized header → library path) --- - - [Test] - public async Task Unrecognized_header_valid_token_falls_to_library() - { - // Extra "kid" field makes the Base64Url header unrecognized → AuthenticateCore path - string token = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\",\"kid\":\"1\"}", $"{{\"iat\":{TestIat}}}"); - Assert.That(await CreateAuth().Authenticate(token), Is.True); - } - - [Test] - public async Task Unrecognized_header_expired_iat_returns_false() - { - string token = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\",\"kid\":\"1\"}", $"{{\"iat\":{TestIat - 200}}}"); - Assert.That(await CreateAuth().Authenticate(token), Is.False); - } - - // --- Cache path tests (TryLastValidationFromCache) --- - - [Test] - public async Task Cache_hit_returns_true_on_repeated_call() - { - IRpcAuthentication auth = CreateAuth(); - string token = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat}}}"); - - Assert.That(await auth.Authenticate(token), Is.True); - // Second call with same token should hit cache and still return true - Assert.That(await auth.Authenticate(token), Is.True); - } - - [Test] - public async Task Cache_eviction_when_iat_expires() - { - ManualTimestamper ts = new() { UtcNow = DateTimeOffset.FromUnixTimeSeconds(TestIat).UtcDateTime }; - IRpcAuthentication auth = JwtAuthentication.FromSecret(HexSecret, ts, LimboTraceLogger.Instance); - string token = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat}}}"); - - Assert.That(await auth.Authenticate(token), Is.True); - - // Advance time beyond TTL — cache should evict, iat check should fail - ts.UtcNow = DateTimeOffset.FromUnixTimeSeconds(TestIat + 61).UtcDateTime; - Assert.That(await auth.Authenticate(token), Is.False); - } - - [Test] - public async Task Cache_miss_different_token_revalidates() - { - IRpcAuthentication auth = CreateAuth(); - string token1 = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat}}}"); - string token2 = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat + 1}}}"); - - Assert.That(await auth.Authenticate(token1), Is.True); - // Different token — cache miss, must revalidate - Assert.That(await auth.Authenticate(token2), Is.True); - } - - // --- Helpers --- - - private static IRpcAuthentication CreateAuth(long nowUnixSeconds = TestIat) - { - ManualTimestamper ts = new() { UtcNow = DateTimeOffset.FromUnixTimeSeconds(nowUnixSeconds).UtcDateTime }; - return JwtAuthentication.FromSecret(HexSecret, ts, LimboTraceLogger.Instance); - } - - private static string CreateJwt(string headerJson, string payloadJson) - { - byte[] secret = Bytes.FromHexString(HexSecret); - string header = Base64UrlEncode(Encoding.UTF8.GetBytes(headerJson)); - string payload = Base64UrlEncode(Encoding.UTF8.GetBytes(payloadJson)); - byte[] sig = HMACSHA256.HashData(secret, Encoding.ASCII.GetBytes($"{header}.{payload}")); - return $"Bearer {header}.{payload}.{Base64UrlEncode(sig)}"; - } - - private static string Base64UrlEncode(byte[] data) - => Convert.ToBase64String(data).TrimEnd('=').Replace('+', '-').Replace('/', '_'); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs index 3739ab6d7c85..c6b82b964fdc 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs @@ -2,8 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Text.Json; -using System.Text.Json.Serialization.Metadata; using System.Threading.Tasks; using Autofac; using FluentAssertions; @@ -31,17 +29,6 @@ namespace Nethermind.Merge.Plugin.Test; public class MergePluginTests { - private sealed class SourceGenProbe - { - public int Value { get; set; } - } - - private sealed class ThrowingProbeResolver : IJsonTypeInfoResolver - { - public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options) => - type == typeof(SourceGenProbe) ? throw new InvalidOperationException("probe resolver was used") : null; - } - private ChainSpec _chainSpec = null!; private MergeConfig _mergeConfig = null!; private IJsonRpcConfig _jsonRpcConfig = null!; @@ -115,15 +102,6 @@ public void Init_merge_plugin_does_not_throw_exception(bool enabled) Assert.DoesNotThrow(() => _plugin.InitBlockProducer(_consensusPlugin!)); } - [Test] - public void AddTypeInfoResolver_updates_existing_serializer_instances() - { - EthereumJsonSerializer serializer = new(); - EthereumJsonSerializer.AddTypeInfoResolver(new ThrowingProbeResolver()); - - Assert.Throws(() => serializer.Serialize(new SourceGenProbe { Value = 1 })); - } - [Test] public async Task Initializes_correctly() { diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/TestBlockProcessorInterceptor.Setup.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/TestBlockProcessorInterceptor.Setup.cs index 5f6716e42aef..3e10767608f2 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/TestBlockProcessorInterceptor.Setup.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/TestBlockProcessorInterceptor.Setup.cs @@ -14,12 +14,9 @@ public class TestBranchProcessorInterceptor(IBranchProcessor baseBlockProcessor, { public int DelayMs { get; set; } = delayMs; public Exception? ExceptionToThrow { get; set; } - public ManualResetEventSlim? ProcessingStarted { get; set; } public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlocks, ProcessingOptions processingOptions, IBlockTracer blockTracer, CancellationToken token) { - ProcessingStarted?.Set(); - if (DelayMs > 0) { Thread.Sleep(DelayMs); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV1DirectResponse.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV1DirectResponse.cs deleted file mode 100644 index 5135babb86eb..000000000000 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV1DirectResponse.cs +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Buffers; -using System.Collections; -using System.Collections.Generic; -using System.IO.Pipelines; -using System.Threading; -using System.Threading.Tasks; -using Nethermind.Core.Collections; -using Nethermind.JsonRpc; -using Nethermind.Serialization.Json; - -namespace Nethermind.Merge.Plugin.Data; - -/// -/// Wraps an of and writes JSON -/// directly into a , bypassing -/// to avoid extra buffer copies for large blob payloads. -/// -public sealed class BlobsV1DirectResponse : IStreamableResult, IEnumerable, IDisposable -{ - private readonly ArrayPoolList _items; - - public BlobsV1DirectResponse(ArrayPoolList items) - { - _items = items; - } - - public async ValueTask WriteToAsync(PipeWriter writer, CancellationToken cancellationToken) - { - writer.Write("["u8); - - int count = _items.Count; - for (int i = 0; i < count; i++) - { - if (i > 0) writer.Write(","u8); - - BlobAndProofV1? item = _items[i]; - if (item is null) - { - writer.Write("null"u8); - } - else - { - writer.Write("{\"blob\":\"0x"u8); - HexWriter.WriteHexChunked(writer, item.Blob); - writer.Write("\",\"proof\":\"0x"u8); - HexWriter.WriteHexSmall(writer, item.Proof); - writer.Write("\"}"u8); - } - - // Flush after each entry for backpressure - FlushResult flushResult = await writer.FlushAsync(cancellationToken); - if (flushResult.IsCompleted || flushResult.IsCanceled) - return; - } - - writer.Write("]"u8); - } - - public IEnumerator GetEnumerator() => _items.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public void Dispose() => _items.Dispose(); -} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV2DirectResponse.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV2DirectResponse.cs deleted file mode 100644 index ad4048f31294..000000000000 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV2DirectResponse.cs +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Buffers; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO.Pipelines; -using System.Threading; -using System.Threading.Tasks; -using Nethermind.JsonRpc; -using Nethermind.Serialization.Json; - -namespace Nethermind.Merge.Plugin.Data; - -/// -/// Wraps parallel arrays of blobs and proofs and writes JSON directly into a -/// , bypassing -/// to avoid extra buffer copies for large blob payloads. -/// -public sealed class BlobsV2DirectResponse : IStreamableResult, IEnumerable -{ - private readonly byte[]?[] _blobs; - private readonly ReadOnlyMemory[] _proofs; - private readonly int _count; - - public BlobsV2DirectResponse(byte[]?[] blobs, ReadOnlyMemory[] proofs, int count) - { - Debug.Assert(count <= blobs.Length && count <= proofs.Length, - "count must not exceed array lengths"); - _blobs = blobs; - _proofs = proofs; - _count = count; - } - - public async ValueTask WriteToAsync(PipeWriter writer, CancellationToken cancellationToken) - { - writer.Write("["u8); - - for (int i = 0; i < _count; i++) - { - if (i > 0) writer.Write(","u8); - - byte[]? blob = _blobs[i]; - if (blob is null) - { - writer.Write("null"u8); - } - else - { - writer.Write("{\"blob\":\"0x"u8); - HexWriter.WriteHexChunked(writer, blob); - writer.Write("\",\"proofs\":["u8); - - ReadOnlySpan proofs = _proofs[i].Span; - for (int p = 0; p < proofs.Length; p++) - { - if (p > 0) writer.Write(","u8); - writer.Write("\"0x"u8); - HexWriter.WriteHexSmall(writer, proofs[p]); - writer.Write("\""u8); - } - - writer.Write("]}"u8); - } - - // Flush after each entry for backpressure - FlushResult flushResult = await writer.FlushAsync(cancellationToken); - if (flushResult.IsCompleted || flushResult.IsCanceled) - return; - } - - writer.Write("]"u8); - } - - // Explicit interface implementation: only used by tests via IEnumerable cast. - // Production serialization goes through IStreamableResult.WriteToAsync. - IEnumerator IEnumerable.GetEnumerator() - { - for (int i = 0; i < _count; i++) - { - byte[]? blob = _blobs[i]; - yield return blob is null ? null : new BlobAndProofV2(blob, _proofs[i].ToArray()); - } - } - - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); -} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/EngineApiJsonContext.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/EngineApiJsonContext.cs deleted file mode 100644 index 912c2421b1fd..000000000000 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/EngineApiJsonContext.cs +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Text.Json.Serialization; -using Nethermind.Consensus.Producers; -using Nethermind.Merge.Plugin.Handlers; - -namespace Nethermind.Merge.Plugin.Data; - -[JsonSourceGenerationOptions( - GenerationMode = JsonSourceGenerationMode.Metadata, - PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - IncludeFields = true)] -[JsonSerializable(typeof(ExecutionPayload))] -[JsonSerializable(typeof(ExecutionPayloadV3))] -[JsonSerializable(typeof(PayloadStatusV1))] -[JsonSerializable(typeof(byte[][]))] -[JsonSerializable(typeof(ForkchoiceStateV1))] -[JsonSerializable(typeof(ForkchoiceUpdatedV1Result))] -[JsonSerializable(typeof(PayloadAttributes))] -[JsonSerializable(typeof(BlobAndProofV1))] -[JsonSerializable(typeof(BlobAndProofV2))] -[JsonSerializable(typeof(BlobsBundleV1))] -[JsonSerializable(typeof(BlobsBundleV2))] -[JsonSerializable(typeof(GetPayloadV2Result))] -[JsonSerializable(typeof(GetPayloadV3Result))] -[JsonSerializable(typeof(GetPayloadV4Result))] -[JsonSerializable(typeof(GetPayloadV5Result))] -[JsonSerializable(typeof(GetBlobsHandlerV2Request))] -[JsonSerializable(typeof(ExecutionPayloadBodyV1Result))] -[JsonSerializable(typeof(TransitionConfigurationV1))] -[JsonSerializable(typeof(ClientVersionV1))] -internal partial class EngineApiJsonContext : JsonSerializerContext; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs index 93ba25cde5b7..d0eae608228a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs @@ -137,7 +137,6 @@ public byte[][] Transactions /// true if block created successfully; otherwise, false. public virtual BlockDecodingResult TryGetBlock(UInt256? totalDifficulty = null) { - byte[][] encodedTransactions = Transactions; TransactionDecodingResult transactions = TryGetTransactions(); if (transactions.Error is not null) { @@ -165,15 +164,11 @@ public virtual BlockDecodingResult TryGetBlock(UInt256? totalDifficulty = null) Author = FeeRecipient, IsPostMerge = true, TotalDifficulty = totalDifficulty, - TxRoot = TxTrie.CalculateRoot(encodedTransactions), + TxRoot = TxTrie.CalculateRoot(transactions.Transactions), WithdrawalsRoot = BuildWithdrawalsRoot(), }; - Block block = new(header, transactions.Transactions, Array.Empty(), Withdrawals) - { - EncodedTransactions = encodedTransactions - }; - return new BlockDecodingResult(block); + return new BlockDecodingResult(new Block(header, transactions.Transactions, Array.Empty(), Withdrawals)); } protected virtual Hash256? BuildWithdrawalsRoot() diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandler.cs index 05cc9936d00a..4a0dcf8b7e87 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandler.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Nethermind.Core.Collections; using Nethermind.Core.Specs; using Nethermind.JsonRpc; using Nethermind.Merge.Plugin.Data; @@ -28,41 +27,35 @@ public class GetBlobsHandler(ITxPool txPool, IChainHeadSpecProvider chainHeadSpe return ResultWrapper>.Fail(error, MergeErrorCodes.TooLargeRequest); } + return ResultWrapper>.Success(GetBlobsAndProofs(request)); + } + + private IEnumerable GetBlobsAndProofs(byte[][] request) + { bool allBlobsAvailable = true; Metrics.NumberOfRequestedBlobs += request.Length; - ArrayPoolList response = new(request.Length); - try + foreach (byte[] requestedBlobVersionedHash in request) { - foreach (byte[] requestedBlobVersionedHash in request) - { - if (txPool.TryGetBlobAndProofV0(requestedBlobVersionedHash, out byte[]? blob, out byte[]? proof)) - { - Metrics.NumberOfSentBlobs++; - response.Add(new BlobAndProofV1(blob, proof)); - } - else - { - allBlobsAvailable = false; - response.Add(null); - } - } - - if (allBlobsAvailable) + if (txPool.TryGetBlobAndProofV0(requestedBlobVersionedHash, out byte[]? blob, out byte[]? proof)) { - Metrics.GetBlobsRequestsSuccessTotal++; + Metrics.NumberOfSentBlobs++; + yield return new BlobAndProofV1(blob, proof); } else { - Metrics.GetBlobsRequestsFailureTotal++; + allBlobsAvailable = false; + yield return null; } + } - return ResultWrapper>.Success(new BlobsV1DirectResponse(response)); + if (allBlobsAvailable) + { + Metrics.GetBlobsRequestsSuccessTotal++; } - catch + else { - response.Dispose(); - throw; + Metrics.GetBlobsRequestsFailureTotal++; } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs index 341fb547a5f6..47d223833282 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using System.Threading.Tasks; +using Nethermind.Core.Collections; using Nethermind.JsonRpc; using Nethermind.Merge.Plugin.Data; using Nethermind.TxPool; @@ -20,27 +20,51 @@ public class GetBlobsHandlerV2(ITxPool txPool) : IAsyncHandler MaxRequest) { - string error = $"The number of requested blobs must not exceed {MaxRequest}"; + var error = $"The number of requested blobs must not exceed {MaxRequest}"; return ResultWrapper?>.Fail(error, MergeErrorCodes.TooLargeRequest); } Metrics.GetBlobsRequestsTotal += request.BlobVersionedHashes.Length; - int n = request.BlobVersionedHashes.Length; - byte[]?[] blobs = new byte[n][]; - ReadOnlyMemory[] proofs = new ReadOnlyMemory[n]; - int count = txPool.TryGetBlobsAndProofsV1(request.BlobVersionedHashes, blobs, proofs); - + int count = txPool.GetBlobCounts(request.BlobVersionedHashes); Metrics.GetBlobsRequestsInBlobpoolTotal += count; // quick fail if we don't have some blob (unless partial return is allowed) - if (!request.AllowPartialReturn && count != n) + if (!request.AllowPartialReturn && count != request.BlobVersionedHashes.Length) { return ReturnEmptyArray(); } - Metrics.GetBlobsRequestsSuccessTotal++; - return ResultWrapper?>.Success(new BlobsV2DirectResponse(blobs, proofs, n)); + ArrayPoolList response = new(request.BlobVersionedHashes.Length); + + try + { + foreach (byte[] requestedBlobVersionedHash in request.BlobVersionedHashes) + { + if (txPool.TryGetBlobAndProofV1(requestedBlobVersionedHash, out byte[]? blob, out byte[][]? cellProofs)) + { + response.Add(new BlobAndProofV2(blob, cellProofs)); + } + else if (request.AllowPartialReturn) + { + response.Add(null); + } + else + { + // fail if we were not able to collect full blob data + response.Dispose(); + return ReturnEmptyArray(); + } + } + + Metrics.GetBlobsRequestsSuccessTotal++; + return ResultWrapper?>.Success(response); + } + catch + { + response.Dispose(); + throw; + } } private Task?>> ReturnEmptyArray() diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index 756e9d571add..9e98a951e330 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -35,7 +35,6 @@ using Nethermind.Merge.Plugin.InvalidChainTracker; using Nethermind.Merge.Plugin.Synchronization; using Nethermind.Network.Contract.P2P; -using Nethermind.Serialization.Json; using Nethermind.Specs.ChainSpecStyle; using Nethermind.State; using Nethermind.Synchronization; @@ -69,7 +68,6 @@ public partial class MergePlugin(ChainSpec chainSpec, IMergeConfig mergeConfig) public virtual Task Init(INethermindApi nethermindApi) { _api = nethermindApi; - EthereumJsonSerializer.AddTypeInfoResolver(EngineApiJsonContext.Default); _syncConfig = nethermindApi.Config(); _blocksConfig = nethermindApi.Config(); _txPoolConfig = nethermindApi.Config(); diff --git a/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs b/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs index e21d3b810dfa..bb35e69108b5 100644 --- a/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs +++ b/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs @@ -8,8 +8,6 @@ using System.Linq; using System.Reflection; using System.Runtime.Serialization; -using System.Threading; -using System.Threading.Tasks; using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Attributes; @@ -178,45 +176,6 @@ public void Register_and_update_metrics_should_not_throw_exception() }); } - [Test] - public void UpdateAllMetrics_does_not_throw_when_registration_is_concurrent() - { - MetricsConfig metricsConfig = new() { Enabled = true }; - MetricsController metricsController = new(metricsConfig); - - using CancellationTokenSource cts = new(TimeSpan.FromSeconds(2)); - CancellationToken ct = cts.Token; - - // Continuously call UpdateAllMetrics on one thread while registering metrics on another - Task updater = Task.Run(() => - { - while (!ct.IsCancellationRequested) - { - metricsController.UpdateAllMetrics(); - } - }); - - Task registrar = Task.Run(() => - { - Type[] types = - [ - typeof(TestMetrics), - typeof(Blockchain.Metrics), - typeof(Evm.Metrics), - typeof(Network.Metrics), - typeof(Db.Metrics), - ]; - - for (int i = 0; !ct.IsCancellationRequested; i++) - { - metricsController.RegisterMetrics(types[i % types.Length]); - metricsController.AddMetricsUpdateAction(() => { }); - } - }); - - Assert.DoesNotThrowAsync(() => Task.WhenAll(updater, registrar)); - } - [Test] public void All_config_items_have_descriptions() { diff --git a/src/Nethermind/Nethermind.Monitoring/Metrics/MetricsController.cs b/src/Nethermind/Nethermind.Monitoring/Metrics/MetricsController.cs index 1f8661324615..6eefbce28fc1 100644 --- a/src/Nethermind/Nethermind.Monitoring/Metrics/MetricsController.cs +++ b/src/Nethermind/Nethermind.Monitoring/Metrics/MetricsController.cs @@ -33,7 +33,6 @@ public partial class MetricsController : IMetricsController private static bool _staticLabelsInitialized; private readonly Dictionary _metricUpdaters = new(); - private volatile IMetricUpdater[][] _updaterValues = []; // Largely for testing reason internal readonly Dictionary _individualUpdater = new(); @@ -41,7 +40,7 @@ public partial class MetricsController : IMetricsController private readonly bool _useCounters; private readonly bool _enableDetailedMetric; - private volatile Action[] _callbacks = []; + private readonly List _callbacks = new(); public interface IMetricUpdater { @@ -236,7 +235,6 @@ public void RegisterMetrics(Type type) } } _metricUpdaters[type] = metricUpdaters.ToArray(); - _updaterValues = [.. _metricUpdaters.Values]; } } @@ -356,7 +354,7 @@ public void UpdateAllMetrics() callback(); } - foreach (IMetricUpdater[] updaters in _updaterValues) + foreach (IMetricUpdater[] updaters in _metricUpdaters.Values) { foreach (IMetricUpdater metricUpdater in updaters) { @@ -365,7 +363,7 @@ public void UpdateAllMetrics() } } - public void AddMetricsUpdateAction(Action callback) => _callbacks = [.. _callbacks, callback]; + public void AddMetricsUpdateAction(Action callback) => _callbacks.Add(callback); private static string GetGaugeNameKey(params string[] par) => string.Join('.', par); diff --git a/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs b/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs index 63f19c0cd136..d379f1855bd4 100644 --- a/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs +++ b/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs @@ -34,6 +34,10 @@ public static class Protocol /// public const string Par = "par"; /// + /// Nethermind Data Marketplace + /// + public const string Ndm = "ndm"; + /// /// Account Abstraction /// public const string AA = "aa"; diff --git a/src/Nethermind/Nethermind.Network.Contract/P2P/ProtocolParser.cs b/src/Nethermind/Nethermind.Network.Contract/P2P/ProtocolParser.cs index 5f2e4e274bbd..a0f185b8082d 100644 --- a/src/Nethermind/Nethermind.Network.Contract/P2P/ProtocolParser.cs +++ b/src/Nethermind/Nethermind.Network.Contract/P2P/ProtocolParser.cs @@ -17,6 +17,7 @@ public static class ProtocolParser private const uint Shh = 0x686873u; // "shh" private const uint Bzz = 0x7A7A62u; // "bzz" private const uint Par = 0x726170u; // "par" + private const uint Ndm = 0x6D646Eu; // "ndm" private const uint Snap = 0x70616E73u; // "snap" private const ulong Nodedata = 0x6174616465646F6Eul; // "nodedata" @@ -47,6 +48,8 @@ public static bool TryGetProtocolCode(ReadOnlySpan protocolSpan, [NotNullW protocol = Protocol.Bzz; return true; case Par: protocol = Protocol.Par; return true; + case Ndm: + protocol = Protocol.Ndm; return true; } break; diff --git a/src/Nethermind/Nethermind.Network.Discovery/Serializers/IDiscoveryMsgSerializersProvider.cs b/src/Nethermind/Nethermind.Network.Discovery/Serializers/IDiscoveryMsgSerializersProvider.cs new file mode 100644 index 000000000000..ea094cec0d7e --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Discovery/Serializers/IDiscoveryMsgSerializersProvider.cs @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Network.Discovery.Serializers; + +public interface IDiscoveryMsgSerializersProvider +{ + void RegisterDiscoverySerializers(); +} diff --git a/src/Nethermind/Nethermind.Network.Test/MessageQueueTests.cs b/src/Nethermind/Nethermind.Network.Test/MessageQueueTests.cs deleted file mode 100644 index fe653da76271..000000000000 --- a/src/Nethermind/Nethermind.Network.Test/MessageQueueTests.cs +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using FluentAssertions; -using Nethermind.Core; -using Nethermind.Core.Collections; -using Nethermind.Core.Extensions; -using Nethermind.Core.Test.Builders; -using Nethermind.Network.P2P; -using Nethermind.Network.P2P.Subprotocols; -using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; -using NSubstitute; -using NUnit.Framework; - -namespace Nethermind.Network.Test; - -public class MessageQueueTests -{ - private readonly List _recordedSends = new(); - private MessageQueue> _queue; - - [SetUp] - public void Setup() - { - _recordedSends.Clear(); - _queue = new((message) => _recordedSends.Add(message)); - } - - [Test] - public void Send_first_request_is_sent_immediately() - { - Request> request = CreateRequest(); - - _queue.Send(request); - - _recordedSends.Count.Should().Be(1); - request.CompletionSource.Task.IsCompleted.Should().BeFalse(); - } - - [Test] - public void Send_second_request_is_queued() - { - Request> request1 = CreateRequest(); - Request> request2 = CreateRequest(); - - _queue.Send(request1); - _queue.Send(request2); - - _recordedSends.Count.Should().Be(1); - request2.CompletionSource.Task.IsCompleted.Should().BeFalse(); - } - - [Test] - public void Handle_completes_current_request_and_sends_next() - { - Request> request1 = CreateRequest(); - Request> request2 = CreateRequest(); - - IOwnedReadOnlyList response = new[] { Build.A.BlockHeader.TestObject }.ToPooledList(); - - _queue.Send(request1); - _queue.Send(request2); - - _queue.Handle(response, 100); - - request1.CompletionSource.Task.IsCompleted.Should().BeTrue(); - request1.CompletionSource.Task.Result.Should().BeSameAs(response); - _recordedSends.Count.Should().Be(2); - } - - [Test] - public void Handle_throws_when_no_current_request() - { - using IOwnedReadOnlyList response = new[] { Build.A.BlockHeader.TestObject }.ToPooledList(); - - _queue.Invoking(q => q.Handle(response, 100)) - .Should() - .Throw(); - } - - [Test] - public void Handle_disposes_data_when_no_current_request() - { - IOwnedReadOnlyList response = Substitute.For>(); - - _queue.Invoking(q => q.Handle(response, 100)) - .Should() - .Throw(); - - response.Received().Dispose(); - } - - [Test] - public void Handle_does_not_throw_when_completion_source_already_cancelled() - { - Request> request = CreateRequest(); - - _queue.Send(request); - - // Simulate timeout cancelling the CompletionSource - request.CompletionSource.TrySetCanceled(); - - using IOwnedReadOnlyList response = new[] { Build.A.BlockHeader.TestObject }.ToPooledList(); - - // Should not throw — this is the core regression test - _queue.Invoking(q => q.Handle(response, 100)) - .Should() - .NotThrow(); - } - - [Test] - public void Handle_disposes_data_when_completion_source_already_cancelled() - { - Request> request = CreateRequest(); - - _queue.Send(request); - - // Simulate timeout cancelling the CompletionSource - request.CompletionSource.TrySetCanceled(); - - IOwnedReadOnlyList response = Substitute.For>(); - - _queue.Handle(response, 100); - - response.Received().Dispose(); - } - - [Test] - public void Handle_dequeues_next_request_even_when_current_was_cancelled() - { - Request> request1 = CreateRequest(); - Request> request2 = CreateRequest(); - - _queue.Send(request1); - _queue.Send(request2); - - // Simulate timeout cancelling the first request - request1.CompletionSource.TrySetCanceled(); - - IOwnedReadOnlyList response = Substitute.For>(); - - _queue.Handle(response, 100); - - // The second request should have been dequeued and sent - _recordedSends.Count.Should().Be(2); - request2.CompletionSource.Task.IsCompleted.Should().BeFalse(); - } - - [Test] - public void Send_does_not_send_when_closed() - { - _queue.CompleteAdding(); - - GetBlockHeadersMessage msg = new(); - Request> request = new(msg); - - _queue.Send(request); - - _recordedSends.Count.Should().Be(0); - } - - private static Request> CreateRequest() - { - return new(new GetBlockHeadersMessage()); - } -} diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/ProtocolParserTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/ProtocolParserTests.cs index 43860b4f0dc7..3baecbc75c80 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/ProtocolParserTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/ProtocolParserTests.cs @@ -61,6 +61,16 @@ public void TryGetProtocolCode_Par_ReturnsTrue() Assert.That(protocol, Is.EqualTo(Protocol.Par)); } + [Test] + public void TryGetProtocolCode_Ndm_ReturnsTrue() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("ndm"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.True); + Assert.That(protocol, Is.EqualTo(Protocol.Ndm)); + } + [Test] public void TryGetProtocolCode_Snap_ReturnsTrue() { @@ -196,6 +206,15 @@ public void TryGetProtocolCode_ValidatesHexConstants_Par() Assert.That(parValue, Is.EqualTo(0x726170u), "Hex constant for 'par' should match"); } + [Test] + public void TryGetProtocolCode_ValidatesHexConstants_Ndm() + { + byte[] ndmBytes = Encoding.UTF8.GetBytes("ndm"); + uint ndmValue = CalculateThreeByteKey(ndmBytes); + + Assert.That(ndmValue, Is.EqualTo(0x6D646Eu), "Hex constant for 'ndm' should match"); + } + [Test] public void TryGetProtocolCode_ValidatesHexConstants_Snap() { diff --git a/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs b/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs index 36ab108cbd36..3121523f1e07 100644 --- a/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs @@ -78,7 +78,7 @@ public async Task Will_connect_to_a_candidate_node() ctx.SetupPersistedPeers(1); ctx.PeerPool.Start(); ctx.PeerManager.Start(); - Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.EqualTo(1).After(_delay, 10)); + Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.EqualTo(1).After(_travisDelay, 10)); } [Test, Retry(3)] @@ -88,14 +88,14 @@ public async Task Will_only_connect_up_to_max_peers() ctx.SetupPersistedPeers(50); ctx.PeerPool.Start(); ctx.PeerManager.Start(); - await Task.Delay(_delayLong); + await Task.Delay(_travisDelayLong); int expectedConnectCount = 25; Assert.That( () => ctx.RlpxPeer.ConnectAsyncCallsCount, Is .InRange(expectedConnectCount, expectedConnectCount + 1) - .After(_delay * 10, 10)); + .After(_travisDelay * 10, 10)); } [Test] @@ -184,7 +184,6 @@ public async Task Will_accept_static_connection() [TestCase(false, ConnectionDirection.In)] // [TestCase(true, ConnectionDirection.Out)] // cannot create an active peer waiting for the test [TestCase(false, ConnectionDirection.Out)] - [NonParallelizable] public async Task Will_agree_on_which_session_to_disconnect_when_connecting_at_once(bool shouldLose, ConnectionDirection firstDirection) { @@ -208,12 +207,9 @@ void EnsureSession(ISession? session) { if (session is null) return; if (session.State < SessionState.HandshakeComplete) session.Handshake(session.Node.Id); - if (session.State < SessionState.Initialized) session.Init(5, context, packetSender); + session.Init(5, context, packetSender); } - bool expectedOutSessionClosing = firstDirection == ConnectionDirection.In ? shouldLose : !shouldLose; - bool expectedInSessionClosing = !expectedOutSessionClosing; - if (firstDirection == ConnectionDirection.In) { ctx.RlpxPeer.CreateIncoming(session1); @@ -221,6 +217,12 @@ void EnsureSession(ISession? session) { throw new NetworkingException($"Failed to connect to {session1.Node:s}", NetworkExceptionType.TargetUnreachable); } + + EnsureSession(ctx.PeerManager.ActivePeers.First().OutSession); + EnsureSession(ctx.PeerManager.ActivePeers.First().InSession); + + (ctx.PeerManager.ActivePeers.First().OutSession?.IsClosing ?? true).Should().Be(shouldLose); + (ctx.PeerManager.ActivePeers.First().InSession?.IsClosing ?? true).Should().Be(!shouldLose); } else { @@ -231,23 +233,15 @@ void EnsureSession(ISession? session) } ctx.RlpxPeer.SessionCreated -= HandshakeOnCreate; ctx.RlpxPeer.CreateIncoming(session1); - } - Assert.That(() => - { - Peer? activePeer = ctx.PeerManager.ActivePeers.SingleOrDefault(); - if (activePeer is null) return false; - - EnsureSession(activePeer.OutSession); - EnsureSession(activePeer.InSession); + EnsureSession(ctx.PeerManager.ActivePeers.First().OutSession); + EnsureSession(ctx.PeerManager.ActivePeers.First().InSession); - return activePeer.OutSession is not null - && activePeer.InSession is not null - && activePeer.OutSession.IsClosing == expectedOutSessionClosing - && activePeer.InSession.IsClosing == expectedInSessionClosing; - }, Is.True.After(_delayLonger, 20)); + (ctx.PeerManager.ActivePeers.First().OutSession?.IsClosing ?? true).Should().Be(!shouldLose); + (ctx.PeerManager.ActivePeers.First().InSession?.IsClosing ?? true).Should().Be(shouldLose); + } - Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(1).After(_delay, 10)); + ctx.PeerManager.ActivePeers.Count.Should().Be(1); } private void HandshakeOnCreate(object sender, SessionEventArgs e) @@ -262,11 +256,11 @@ public async Task Will_fill_up_on_disconnects() ctx.SetupPersistedPeers(50); ctx.PeerPool.Start(); ctx.PeerManager.Start(); - await Task.Delay(_delayLong); + await Task.Delay(_travisDelayLong); Assert.That(ctx.RlpxPeer.ConnectAsyncCallsCount, Is.AtLeast(25)); ctx.DisconnectAllSessions(); - await Task.Delay(_delayLong); + await Task.Delay(_travisDelayLong); Assert.That(ctx.RlpxPeer.ConnectAsyncCallsCount, Is.AtLeast(50)); } @@ -279,7 +273,7 @@ public async Task Ok_if_fails_to_connect() ctx.PeerPool.Start(); ctx.PeerManager.Start(); - Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(0).After(_delay, 10)); + Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(0).After(_travisDelay, 10)); } [Test, Retry(3)] @@ -302,7 +296,7 @@ public async Task Will_fill_up_over_and_over_again_on_disconnects() { Assert.That( () => ctx.PeerPool.ActivePeers.Count, - Is.AtLeast(25).After(_delayLonger * 2, 10)); + Is.AtLeast(25).After(_travisDelayLonger * 2, 10)); ctx.DisconnectAllSessions(); } } @@ -324,7 +318,7 @@ public async Task Will_fill_up_over_and_over_again_on_newly_discovered() for (int i = 0; i < 10; i++) { ctx.DiscoverNew(25); - Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(25).After(_delay, 10)); + Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(25).After(_travisDelay, 10)); } } @@ -340,8 +334,8 @@ public async Task Will_not_stop_trying_on_rlpx_connection_failure() for (int i = 0; i < 10; i++) { ctx.DiscoverNew(25); - await Task.Delay(_delay); - Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.EqualTo(25 * (i + 1)).After(_delayLonger, 10)); + await Task.Delay(_travisDelay); + Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.EqualTo(25 * (i + 1)).After(1000, 10)); } } @@ -383,13 +377,13 @@ public async Task IfPeerAdded_with_invalid_chain_then_do_not_connect() ctx.PeerPool.GetOrAdd(networkNode); - Assert.That(() => ctx.PeerPool.ActivePeers.Count, Is.EqualTo(0).After(_delay, 10)); + Assert.That(() => ctx.PeerPool.ActivePeers.Count, Is.EqualTo(0).After(_travisDelay, 10)); } - private readonly int _delay = 500; + private readonly int _travisDelay = 500; - private readonly int _delayLong = 1000; - private readonly int _delayLonger = 3000; + private readonly int _travisDelayLong = 1000; + private readonly int _travisDelayLonger = 3000; [Test] [Ignore("Behaviour changed that allows peers to go over max if awaiting response")] @@ -403,7 +397,7 @@ public async Task Will_fill_up_with_incoming_over_and_over_again_on_disconnects( for (int i = 0; i < 10; i++) { ctx.CreateNewIncomingSessions(25); - Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(25).After(_delay, 10)); + Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(25).After(_travisDelay, 10)); } } @@ -422,10 +416,10 @@ public async Task Will_fill_up_over_and_over_again_on_disconnects_and_when_ids_k { currentCount += 25; maxCount += 50; - Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.InRange(currentCount, maxCount).After(_delayLonger * 2, 10)); + Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.InRange(currentCount, maxCount).After(_travisDelayLonger * 2, 10)); ctx.RlpxPeer.ConnectAsyncCallsCount.Should().BeInRange(currentCount, maxCount); ctx.HandshakeAllSessions(); - await Task.Delay(_delay); + await Task.Delay(_travisDelay); ctx.DisconnectAllSessions(); } @@ -454,10 +448,10 @@ public async Task Will_fill_up_over_and_over_again_on_disconnects_and_when_ids_k for (int i = 0; i < 10; i++) { currentCount += count; - await Task.Delay(_delayLong); + await Task.Delay(_travisDelayLong); ctx.RlpxPeer.ConnectAsyncCallsCount.Should().BeInRange(currentCount, currentCount + count); ctx.HandshakeAllSessions(); - await Task.Delay(_delay); + await Task.Delay(_travisDelay); ctx.DisconnectAllSessions(); } } @@ -479,12 +473,12 @@ public async Task Will_fill_up_over_and_over_again_on_disconnects_and_when_ids_k for (int i = 0; i < 10; i++) { currentCount += count; - await Task.Delay(_delayLong); + await Task.Delay(_travisDelayLong); ctx.RlpxPeer.ConnectAsyncCallsCount.Should().BeInRange(currentCount, currentCount + count); ctx.HandshakeAllSessions(); - await Task.Delay(_delay); + await Task.Delay(_travisDelay); ctx.CreateIncomingSessions(); - await Task.Delay(_delay); + await Task.Delay(_travisDelay); ctx.DisconnectAllSessions(); } } @@ -520,7 +514,7 @@ public async Task Will_load_static_nodes_and_connect_to_them() ctx.TestNodeSource.AddNode(new Node(TestItem.PublicKeyA, node.Host, node.Port)); } - Assert.That(() => ctx.PeerManager.ActivePeers.Count(static p => p.Node.IsStatic), Is.EqualTo(nodesCount).After(_delay, 10)); + Assert.That(() => ctx.PeerManager.ActivePeers.Count(static p => p.Node.IsStatic), Is.EqualTo(nodesCount).After(_travisDelay, 10)); } [Test, Retry(5)] @@ -533,7 +527,7 @@ public async Task Will_disconnect_on_remove_static_node() ctx.StaticNodesManager.DiscoverNodes(Arg.Any()).Returns(staticNodes.Select(n => new Node(n, true)).ToAsyncEnumerable()); ctx.PeerPool.Start(); ctx.PeerManager.Start(); - await Task.Delay(_delay); + await Task.Delay(_travisDelay); void DisconnectHandler(object o, DisconnectEventArgs e) => disconnections++; ctx.Sessions.ForEach(s => s.Disconnected += DisconnectHandler); @@ -554,7 +548,7 @@ public async Task Will_connect_and_disconnect_on_peer_management() ctx.PeerManager.Start(); var node = new NetworkNode(ctx.GenerateEnode()); ctx.PeerPool.GetOrAdd(node); - await Task.Delay(_delayLong); + await Task.Delay(_travisDelayLong); void DisconnectHandler(object o, DisconnectEventArgs e) => disconnections++; ctx.PeerManager.ActivePeers.Select(p => p.Node.Id).Should().BeEquivalentTo(new[] { node.NodeId }); @@ -576,7 +570,7 @@ public async Task Will_only_add_same_peer_once() ctx.PeerPool.GetOrAdd(node); ctx.PeerPool.GetOrAdd(node); ctx.PeerPool.GetOrAdd(node); - await Task.Delay(_delayLong); + await Task.Delay(_travisDelayLong); ctx.PeerManager.ActivePeers.Should().HaveCount(1); } @@ -587,7 +581,7 @@ public async Task RemovePeer_should_fail_if_peer_not_added() ctx.PeerPool.Start(); ctx.PeerManager.Start(); var node = new NetworkNode(ctx.GenerateEnode()); - Assert.That(() => ctx.PeerPool.TryRemove(node.NodeId, out _), Is.False.After(_delay, 10)); + Assert.That(() => ctx.PeerPool.TryRemove(node.NodeId, out _), Is.False.After(_travisDelay, 10)); } private class Context : IAsyncDisposable diff --git a/src/Nethermind/Nethermind.Network/P2P/MessageQueue.cs b/src/Nethermind/Nethermind.Network/P2P/MessageQueue.cs index 21f8bcd26995..e8c6ef82f4b1 100644 --- a/src/Nethermind/Nethermind.Network/P2P/MessageQueue.cs +++ b/src/Nethermind/Nethermind.Network/P2P/MessageQueue.cs @@ -47,15 +47,16 @@ public void Handle(TData data, long size) { if (_currentRequest is null) { - data.TryDispose(); + if (data is IDisposable d) + { + d.Dispose(); + } + throw new SubprotocolException($"Received a response to {nameof(TMsg)} that has not been requested"); } _currentRequest.ResponseSize = size; - if (!_currentRequest.CompletionSource.TrySetResult(data)) - { - data.TryDispose(); - } + _currentRequest.CompletionSource.SetResult(data); if (_requestQueue.TryDequeue(out _currentRequest)) { _currentRequest!.StartMeasuringTime(); diff --git a/src/Nethermind/Nethermind.Network/P2P/Messages/AddCapabilityMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Messages/AddCapabilityMessageSerializer.cs index 8c9e24f43d51..1ab325c5940a 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Messages/AddCapabilityMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Messages/AddCapabilityMessageSerializer.cs @@ -9,7 +9,7 @@ namespace Nethermind.Network.P2P.Messages { /// - /// Serializes P2P capability negotiation messages. + /// This is probably used in NDM /// public class AddCapabilityMessageSerializer : IZeroMessageSerializer { diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs index a9f15b75d8dc..598cf8a0dc53 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs @@ -20,6 +20,7 @@ using Nethermind.Network.P2P.Subprotocols.Eth.V62; using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; using Nethermind.Network.P2P.Subprotocols.Eth.V63.Messages; +using Nethermind.Serialization.Rlp; using Nethermind.Stats; using Nethermind.Stats.Model; using Nethermind.Synchronization; @@ -49,6 +50,7 @@ public abstract class SyncPeerProtocolHandlerBase : ZeroProtocolHandlerBase, ISy protected Hash256 _remoteHeadBlockHash; protected readonly ITimestamper _timestamper; + protected readonly TxDecoder _txDecoder; protected readonly MessageQueue> _headersRequests; protected readonly MessageQueue _bodiesRequests; @@ -65,6 +67,7 @@ protected SyncPeerProtocolHandlerBase(ISession session, { SyncServer = syncServer ?? throw new ArgumentNullException(nameof(syncServer)); _timestamper = Timestamper.Default; + _txDecoder = TxDecoder.Instance; _headersRequests = new MessageQueue>(Send); _bodiesRequests = new MessageQueue(Send); } diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs index 59fcac9a4f6f..6427402d7b68 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs @@ -86,21 +86,15 @@ CancellationToken token } else { - // TrySetCanceled first: if it succeeds we own the TCS and need to - // dispose any late-arriving response. If it fails, the response was - // already set by Handle() and the caller owns the data — registering - // a disposal continuation would dispose data the caller still holds. - if (request.CompletionSource.TrySetCanceled(cancellationToken)) + _ = task.ContinueWith(static t => { - _ = task.ContinueWith(static t => + if (t.IsCompletedSuccessfully) { - if (t.IsCompletedSuccessfully) - { - t.Result.TryDispose(); - } - }); - } + t.Result.TryDispose(); + } + }); + request.CompletionSource.TrySetCanceled(cancellationToken); StatsManager.ReportTransferSpeedEvent(Session.Node, speedType, 0L); if (Logger.IsDebug) Logger.Debug($"{Session} Request timeout in {describeRequestFunc(request.Message)}"); diff --git a/src/Nethermind/Nethermind.Network/PeerEqualityComparer.cs b/src/Nethermind/Nethermind.Network/PeerEqualityComparer.cs new file mode 100644 index 000000000000..68a0ad6649d3 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/PeerEqualityComparer.cs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; + +namespace Nethermind.Network +{ + internal class PeerEqualityComparer : IEqualityComparer + { + public bool Equals(Peer x, Peer y) + { + if (x is null || y is null) + { + return false; + } + + return x.Node.Id.Equals(y.Node.Id); + } + + public int GetHashCode(Peer obj) => obj?.Node is null ? 0 : obj.Node.GetHashCode(); + } +} diff --git a/src/Nethermind/Nethermind.Network/Timeouts.cs b/src/Nethermind/Nethermind.Network/Timeouts.cs index 2a2051d8ea81..9ed3d1973cf1 100644 --- a/src/Nethermind/Nethermind.Network/Timeouts.cs +++ b/src/Nethermind/Nethermind.Network/Timeouts.cs @@ -14,6 +14,11 @@ public static class Timeouts public static readonly TimeSpan P2PHello = TimeSpan.FromSeconds(3); public static readonly TimeSpan Eth62Status = TimeSpan.FromSeconds(3); public static readonly TimeSpan Les3Status = TimeSpan.FromSeconds(3); + public static readonly TimeSpan NdmHi = TimeSpan.FromSeconds(3); + public static readonly TimeSpan NdmDeliveryReceipt = TimeSpan.FromSeconds(3); + public static readonly TimeSpan NdmDepositApproval = TimeSpan.FromSeconds(3); + public static readonly TimeSpan NdmEthRequest = TimeSpan.FromSeconds(3); + public static readonly TimeSpan NdmDataRequestResult = TimeSpan.FromSeconds(3); public static readonly TimeSpan Handshake = TimeSpan.FromSeconds(3); public static readonly TimeSpan Disconnection = TimeSpan.FromSeconds(1); } diff --git a/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs b/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs index 1dcfe7c78d52..07002bf116b6 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs @@ -12,7 +12,6 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; -using Nethermind.Db.LogIndex; using Nethermind.Evm; using Nethermind.Facade; using Nethermind.Facade.Eth; @@ -594,7 +593,8 @@ public static TestRpcBlockchain.Builder WithOptimismEthRpcMod blockchain.ProtocolsManager, blockchain.ForkInfo, new BlocksConfig().SecondsPerSlot, - sequencerRpcClient, ecdsa, sealer, new LogIndexConfig(), opSpecHelper + + sequencerRpcClient, ecdsa, sealer, opSpecHelper )); } } diff --git a/src/Nethermind/Nethermind.Optimism/CL/L1Bridge/EthereumL1Bridge.cs b/src/Nethermind/Nethermind.Optimism/CL/L1Bridge/EthereumL1Bridge.cs index 7ca731bd5a3d..879b6044d189 100644 --- a/src/Nethermind/Nethermind.Optimism/CL/L1Bridge/EthereumL1Bridge.cs +++ b/src/Nethermind/Nethermind.Optimism/CL/L1Bridge/EthereumL1Bridge.cs @@ -17,6 +17,9 @@ namespace Nethermind.Optimism.CL.L1Bridge; public class EthereumL1Bridge : IL1Bridge { + private const int L1EpochSlotSize = 32; + private const int L1SlotTimeMilliseconds = 12000; + private const int L1EpochTimeMilliseconds = L1EpochSlotSize * L1SlotTimeMilliseconds; private readonly IEthApi _ethL1Api; private readonly IBeaconApi _beaconApi; private readonly ILogger _logger; @@ -221,6 +224,11 @@ private DaDataSource ProcessCalldataBatcherTransaction(L1Transaction transaction return _unfinalizedL1BlocksQueue.Count == 0 ? null : _unfinalizedL1BlocksQueue.Dequeue(); } + private void LogReorg() + { + if (_logger.IsInfo) _logger.Info("L1 reorg detected. Resetting pipeline"); + } + public async Task GetBlock(ulong blockNumber, CancellationToken token) => await RetryGetBlock(async () => await _ethL1Api.GetBlockByNumber(blockNumber, true), token); diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs index ad5e85acdcdc..aa6db5df906f 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs @@ -17,7 +17,6 @@ using Nethermind.Config; using Nethermind.Core; using Nethermind.Crypto; -using Nethermind.Db.LogIndex; using Nethermind.JsonRpc.Client; using Nethermind.Network; using Nethermind.Serialization.Json; @@ -45,7 +44,6 @@ public class OptimismEthModuleFactory : ModuleFactoryBase private readonly IOptimismSpecHelper _opSpecHelper; private readonly IProtocolsManager _protocolsManager; private readonly IForkInfo _forkInfo; - private readonly ILogIndexConfig _logIndexConfig; private readonly ulong? _secondsPerSlot; private readonly IJsonRpcClient? _sequencerRpcClient; @@ -69,8 +67,7 @@ public OptimismEthModuleFactory(IJsonRpcConfig rpcConfig, IOptimismSpecHelper opSpecHelper, IOptimismConfig config, IJsonSerializer jsonSerializer, - ITimestamper timestamper, - ILogIndexConfig logIndexConfig + ITimestamper timestamper ) { _secondsPerSlot = blocksConfig.SecondsPerSlot; @@ -91,7 +88,6 @@ ILogIndexConfig logIndexConfig _opSpecHelper = opSpecHelper; _protocolsManager = protocolsManager; _forkInfo = forkInfo; - _logIndexConfig = logIndexConfig; ILogger logger = logManager.GetClassLogger(); if (config.SequencerUrl is null && logger.IsWarn) { @@ -127,10 +123,10 @@ public override IOptimismEthRpcModule Create() _protocolsManager, _forkInfo, _secondsPerSlot, + _sequencerRpcClient, _ecdsa, _sealer, - _logIndexConfig, _opSpecHelper ); } diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs index e8befe91bc7a..b1bc00f3f1fa 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs @@ -10,7 +10,6 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Crypto; -using Nethermind.Db.LogIndex; using Nethermind.Evm; using Nethermind.Facade; using Nethermind.Facade.Eth; @@ -53,7 +52,6 @@ public class OptimismEthRpcModule( IJsonRpcClient? sequencerRpcClient, IEthereumEcdsa ecdsa, ITxSealer sealer, - ILogIndexConfig? logIndexConfig, IOptimismSpecHelper opSpecHelper) : EthRpcModule(rpcConfig, blockchainBridge, @@ -70,7 +68,6 @@ public class OptimismEthRpcModule( feeHistoryOracle, protocolsManager, forkInfo, - logIndexConfig, secondsPerSlot), IOptimismEthRpcModule { public override ResultWrapper eth_getBlockReceipts(BlockParameter blockParameter) diff --git a/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs b/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs index 174526aaf142..9b3ca7354aff 100644 --- a/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs +++ b/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs @@ -35,7 +35,7 @@ public static NethermindApi ContextWithMocks() { NethermindApi.Dependencies apiDependencies = new NethermindApi.Dependencies( Substitute.For(), - new EthereumJsonSerializer(), + Substitute.For(), LimboLogs.Instance, new ChainSpec { Parameters = new ChainParameters(), }, Substitute.For(), diff --git a/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/EthereumStepsManagerTests.cs b/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/EthereumStepsManagerTests.cs index 761107c92260..88222c13a190 100644 --- a/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/EthereumStepsManagerTests.cs +++ b/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/EthereumStepsManagerTests.cs @@ -176,8 +176,7 @@ private static ContainerBuilder CreateCommonBuilder(params IEnumerable .AddSingleton() .AddSingleton() .AddSingleton(new ConfigProvider()) - .AddSingleton(new EthereumJsonSerializer()) - .Bind() + .AddSingleton(new EthereumJsonSerializer()) .AddSingleton(LimboLogs.Instance) .AddSingleton(new ChainSpec()) .AddSingleton(Substitute.For()) diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs b/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs index 2bf6eefbe92e..35cdf5e4840c 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs @@ -20,7 +20,7 @@ namespace Nethermind.Runner.Ethereum.Api; public class ApiBuilder { private readonly IConfigProvider _configProvider; - private readonly EthereumJsonSerializer _jsonSerializer; + private readonly IJsonSerializer _jsonSerializer; private readonly ILogManager _logManager; private readonly ILogger _logger; private readonly IInitConfig _initConfig; @@ -64,7 +64,7 @@ public EthereumRunner CreateEthereumRunner(IEnumerable plugin return container.Resolve(); } - private ChainSpec LoadChainSpec(EthereumJsonSerializer ethereumJsonSerializer) + private ChainSpec LoadChainSpec(IJsonSerializer ethereumJsonSerializer) { if (_logger.IsDebug) _logger.Debug($"Loading chain spec from {_initConfig.ChainSpecPath}"); diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/EthereumRunner.cs b/src/Nethermind/Nethermind.Runner/Ethereum/EthereumRunner.cs index 75ec67da4646..9cb196730d2c 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/EthereumRunner.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/EthereumRunner.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Threading; using System.Threading.Tasks; using Autofac; @@ -40,4 +41,18 @@ public async Task StopAsync() _logger.Info("Ethereum runner stopped"); } } + + private Task Stop(Func stopAction, string description) + { + try + { + if (_logger.IsInfo) _logger.Info(description); + return stopAction() ?? Task.CompletedTask; + } + catch (Exception e) + { + if (_logger.IsError) _logger.Error($"{description} shutdown error.", e); + return Task.CompletedTask; + } + } } diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs b/src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs index 4f5062940051..a01cf8002ee1 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs @@ -37,7 +37,7 @@ public class JsonRpcRunner private readonly IConfigProvider _configurationProvider; private readonly IRpcAuthentication _rpcAuthentication; private readonly ILogManager _logManager; - private readonly JsonRpcProcessor _jsonRpcProcessor; + private readonly IJsonRpcProcessor _jsonRpcProcessor; private readonly IJsonRpcUrlCollection _jsonRpcUrlCollection; private readonly IWebSocketsManager _webSocketsManager; private WebHost? _webApp; @@ -50,7 +50,7 @@ public class JsonRpcRunner private readonly IMainProcessingContext _mainProcessingContext; public JsonRpcRunner( - JsonRpcProcessor jsonRpcProcessor, + IJsonRpcProcessor jsonRpcProcessor, IJsonRpcUrlCollection jsonRpcUrlCollection, IWebSocketsManager webSocketsManager, IConfigProvider configurationProvider, diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs b/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs index fbdae0a5805d..8378db80d5d9 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs @@ -31,7 +31,7 @@ namespace Nethermind.Runner.Ethereum.Modules; /// /// public class NethermindRunnerModule( - EthereumJsonSerializer jsonSerializer, + IJsonSerializer jsonSerializer, ChainSpec chainSpec, IConfigProvider configProvider, IProcessExitSource processExitSource, @@ -75,7 +75,6 @@ protected override void Load(ContainerBuilder builder) .AddSingleton((api) => api.BlockPreprocessor) .AddSingleton(jsonSerializer) - .AddSingleton(jsonSerializer) .AddSingleton(consensusPlugin) ; diff --git a/src/Nethermind/Nethermind.Runner/JsonRpc/Bootstrap.cs b/src/Nethermind/Nethermind.Runner/JsonRpc/Bootstrap.cs index 3e8d96b58138..7471c2700762 100644 --- a/src/Nethermind/Nethermind.Runner/JsonRpc/Bootstrap.cs +++ b/src/Nethermind/Nethermind.Runner/JsonRpc/Bootstrap.cs @@ -17,9 +17,9 @@ private Bootstrap() { } public static Bootstrap Instance => _instance ??= new Bootstrap(); - public JsonRpcService? JsonRpcService { private get; set; } + public IJsonRpcService? JsonRpcService { private get; set; } public ILogManager? LogManager { private get; set; } - public EthereumJsonSerializer? JsonSerializer { private get; set; } + public IJsonSerializer? JsonSerializer { private get; set; } public IJsonRpcLocalStats? JsonRpcLocalStats { private get; set; } public IRpcAuthentication? JsonRpcAuthentication { private get; set; } diff --git a/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs b/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs index 73c04f118138..1da1f1ac071c 100644 --- a/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs +++ b/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs @@ -7,10 +7,7 @@ using System.IO; using System.IO.Pipelines; using System.Net; -using System.Runtime.CompilerServices; using System.Security.Authentication; -using System.Text; -using System.Text.Json; using System.Threading; using System.Threading.Tasks; using HealthChecks.UI.Client; @@ -29,10 +26,8 @@ using Nethermind.Config; using Nethermind.Core.Authentication; using Nethermind.Core.Resettables; -using Nethermind.Facade.Eth; using Nethermind.HealthChecks; using Nethermind.JsonRpc; -using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules; using Nethermind.Logging; using Nethermind.Serialization.Json; @@ -42,14 +37,6 @@ namespace Nethermind.Runner.JsonRpc; public class Startup : IStartup { - private JsonRpcProcessor _jsonRpcProcessor = null!; - private JsonRpcService _jsonRpcService = null!; - private IJsonRpcLocalStats _jsonRpcLocalStats = null!; - private EthereumJsonSerializer _jsonSerializer = null!; - private IJsonRpcConfig _jsonRpcConfig = null!; - private IRpcAuthentication? _rpcAuthentication; - private ILogger _logger = default; - private static ReadOnlySpan _jsonOpeningBracket => [(byte)'[']; private static ReadOnlySpan _jsonComma => [(byte)',']; private static ReadOnlySpan _jsonClosingBracket => [(byte)']']; @@ -97,30 +84,23 @@ public void Configure(IApplicationBuilder app) Configure( app, services.GetRequiredService(), - services.GetRequiredService(), - services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService(), services.GetRequiredService(), - services.GetRequiredService(), + services.GetRequiredService(), services.GetRequiredService()); } - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, JsonRpcProcessor jsonRpcProcessor, JsonRpcService jsonRpcService, IJsonRpcLocalStats jsonRpcLocalStats, EthereumJsonSerializer jsonSerializer, ApplicationLifetime lifetime) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IJsonRpcProcessor jsonRpcProcessor, IJsonRpcService jsonRpcService, IJsonRpcLocalStats jsonRpcLocalStats, IJsonSerializer jsonSerializer, ApplicationLifetime lifetime) { - // Register source-generated type info resolvers before warmup - EthereumJsonSerializer.AddTypeInfoResolver(JsonRpcResponseJsonContext.Default); - EthereumJsonSerializer.AddTypeInfoResolver(FacadeJsonContext.Default); - EthereumJsonSerializer.AddTypeInfoResolver(EthRpcJsonContext.Default); - - // Warm up System.Text.Json metadata for hot response types - EthereumJsonSerializer.WarmupSerializer( - new JsonRpcSuccessResponse { Id = 0 }, - new JsonRpcErrorResponse { Id = 0, Error = new Error { Code = 0, Message = string.Empty } }); - if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } + app.UseRouting(); + app.UseCors(); + IConfigProvider? configProvider = app.ApplicationServices.GetService(); IRpcAuthentication? rpcAuthentication = app.ApplicationServices.GetService(); @@ -136,39 +116,10 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, JsonRpcP IJsonRpcUrlCollection jsonRpcUrlCollection = app.ApplicationServices.GetRequiredService(); IHealthChecksConfig healthChecksConfig = configProvider.GetConfig(); - _jsonRpcProcessor = jsonRpcProcessor; - _jsonRpcService = jsonRpcService; - _jsonRpcLocalStats = jsonRpcLocalStats; - _jsonSerializer = jsonSerializer; - _jsonRpcConfig = jsonRpcConfig; - _rpcAuthentication = rpcAuthentication; - _logger = logger; - - // Engine API fast lane: authenticated engine port POST requests bypass - // routing, CORS, compression, and WebSocket middleware - app.Use(async (ctx, next) => - { - if (ctx.Request.Method != "POST" || - !(ctx.Request.ContentType?.Contains("application/json") ?? false) || - !jsonRpcUrlCollection.TryGetValue(ctx.Connection.LocalPort, out JsonRpcUrl jsonRpcUrl) || - !jsonRpcUrl.IsAuthenticated || - !jsonRpcUrl.RpcEndpoint.HasFlag(RpcEndpoint.Http)) - { - await next(); - return; - } - - await ProcessJsonRpcRequestCoreAsync(ctx, jsonRpcUrl); - }); - - app.UseRouting(); - app.UseCors(); - - // Skip response compression for localhost (low benefit, high allocation cost) - // and for Engine API requests (latency-sensitive consensus path) + // If request is local, don't use response compression, + // as it allocates a lot, but doesn't improve much for loopback app.UseWhen(ctx => - !IsLocalhost(ctx.Connection.RemoteIpAddress!) && - !(jsonRpcUrlCollection.TryGetValue(ctx.Connection.LocalPort, out JsonRpcUrl url) && url.IsAuthenticated), + !IsLocalhost(ctx.Connection.RemoteIpAddress!), builder => builder.UseResponseCompression()); if (initConfig.WebSocketsEnabled) @@ -207,22 +158,37 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, JsonRpcP }); app.MapWhen( - ctx => ctx.Request.ContentType?.Contains("application/json") ?? false, + (ctx) => ctx.Request.ContentType?.Contains("application/json") ?? false, builder => builder.Run(async ctx => { - string method = ctx.Request.Method; + var method = ctx.Request.Method; if (method is not "POST" and not "GET") { - ctx.Response.StatusCode = StatusCodes.Status405MethodNotAllowed; + ctx.Response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; + return; + } + + if (jsonRpcProcessor.ProcessExit.IsCancellationRequested) + { + ctx.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable; return; } if (!jsonRpcUrlCollection.TryGetValue(ctx.Connection.LocalPort, out JsonRpcUrl jsonRpcUrl) || !jsonRpcUrl.RpcEndpoint.HasFlag(RpcEndpoint.Http)) { - ctx.Response.StatusCode = StatusCodes.Status404NotFound; + ctx.Response.StatusCode = (int)HttpStatusCode.NotFound; return; } + if (jsonRpcUrl.IsAuthenticated) + { + if (!await rpcAuthentication!.Authenticate(ctx.Request.Headers.Authorization)) + { + await PushErrorResponse(StatusCodes.Status401Unauthorized, ErrorCodes.InvalidRequest, "Authentication error"); + return; + } + } + if (method == "GET" && ctx.Request.Headers.Accept.Count > 0 && !ctx.Request.Headers.Accept[0]!.Contains("text/html", StringComparison.Ordinal)) { @@ -230,318 +196,166 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, JsonRpcP } else if (ctx.Request.ContentType?.Contains("application/json") == false) { - await PushErrorResponseAsync(ctx, StatusCodes.Status415UnsupportedMediaType, ErrorCodes.InvalidRequest, "Missing 'application/json' Content-Type header"); + await PushErrorResponse(StatusCodes.Status415UnsupportedMediaType, ErrorCodes.InvalidRequest, "Missing 'application/json' Content-Type header"); } else { - await ProcessJsonRpcRequestCoreAsync(ctx, jsonRpcUrl); - } - })); - - if (healthChecksConfig.Enabled) - { - var fileProvider = new ManifestEmbeddedFileProvider(typeof(Startup).Assembly, "wwwroot"); - - app.UseDefaultFiles(new DefaultFilesOptions { FileProvider = fileProvider }); - app.UseStaticFiles(new StaticFileOptions { FileProvider = fileProvider }); - } - } + if (jsonRpcUrl.MaxRequestBodySize is not null) + ctx.Features.Get().MaxRequestBodySize = jsonRpcUrl.MaxRequestBodySize; - /// - /// Check for IPv4 localhost (127.0.0.1) and IPv6 localhost (::1) - /// - /// Request source - private static bool IsLocalhost(IPAddress remoteIp) - => IPAddress.IsLoopback(remoteIp) || remoteIp.Equals(IPAddress.IPv6Loopback); - - private static int GetStatusCode(in JsonRpcResult result) - { - if (result.IsCollection) - { - return StatusCodes.Status200OK; - } - else - { - return IsResourceUnavailableError(result.Response) - ? StatusCodes.Status503ServiceUnavailable - : StatusCodes.Status200OK; - } - } - - private static bool IsResourceUnavailableError(JsonRpcResponse? response) - { - return response is JsonRpcErrorResponse { Error.Code: ErrorCodes.ModuleTimeout } - or JsonRpcErrorResponse { Error.Code: ErrorCodes.LimitExceeded }; - } - - private async Task PushErrorResponseAsync(HttpContext ctx, int statusCode, int errorCode, string message) - { - ctx.Response.ContentType = "application/json"; - ctx.Response.StatusCode = statusCode; - JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(errorCode, message); - await _jsonSerializer.SerializeAsync(ctx.Response.BodyWriter, response); - await ctx.Response.CompleteAsync(); - } - - private async Task ProcessJsonRpcRequestCoreAsync(HttpContext ctx, JsonRpcUrl jsonRpcUrl) - { - if (_jsonRpcProcessor.ProcessExit.IsCancellationRequested) - { - ctx.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; - return; - } - - if (jsonRpcUrl.IsAuthenticated && !await _rpcAuthentication!.Authenticate(ctx.Request.Headers.Authorization)) - { - await PushErrorResponseAsync(ctx, StatusCodes.Status401Unauthorized, ErrorCodes.InvalidRequest, "Authentication error"); - return; - } - - if (jsonRpcUrl.MaxRequestBodySize is not null) - ctx.Features.Get()!.MaxRequestBodySize = jsonRpcUrl.MaxRequestBodySize; - - long startTime = Stopwatch.GetTimestamp(); - // Skip CountingPipeReader when Content-Length is known - long? knownContentLength = ctx.Request.ContentLength; - CountingPipeReader? countingReader = knownContentLength > 0 ? null : new(ctx.Request.BodyReader); - PipeReader request = countingReader ?? ctx.Request.BodyReader; - try - { - using JsonRpcContext jsonRpcContext = JsonRpcContext.Http(jsonRpcUrl); - await foreach (JsonRpcResult result in _jsonRpcProcessor.ProcessAsync(request, jsonRpcContext)) - { - using (result) + long startTime = Stopwatch.GetTimestamp(); + CountingPipeReader request = new(ctx.Request.BodyReader); + try { - // Authenticated single responses bypass buffering to avoid double-copy - bool bufferResponse = _jsonRpcConfig.BufferResponses && !(jsonRpcUrl.IsAuthenticated && !result.IsCollection); - await using Stream stream = bufferResponse ? RecyclableStream.GetStream("http") : null; - CountingWriter resultWriter = stream is not null ? new CountingStreamPipeWriter(stream) : new CountingPipeWriter(ctx.Response.BodyWriter); - try + using JsonRpcContext jsonRpcContext = JsonRpcContext.Http(jsonRpcUrl); + await foreach (JsonRpcResult result in jsonRpcProcessor.ProcessAsync(request, jsonRpcContext)) { - ctx.Response.ContentType = "application/json"; - ctx.Response.StatusCode = GetStatusCode(result); - - // Flush headers before body for unbuffered responses - if (stream is null) + using (result) { - await ctx.Response.StartAsync(); - } - - if (result.IsCollection) - { - resultWriter.Write(_jsonOpeningBracket); - bool first = true; - JsonRpcBatchResultAsyncEnumerator enumerator = result.BatchedResponses.GetAsyncEnumerator(CancellationToken.None); + await using Stream stream = jsonRpcConfig.BufferResponses ? RecyclableStream.GetStream("http") : null; + CountingWriter resultWriter = stream is not null ? new CountingStreamPipeWriter(stream) : new CountingPipeWriter(ctx.Response.BodyWriter); try { - while (await enumerator.MoveNextAsync()) + ctx.Response.ContentType = "application/json"; + ctx.Response.StatusCode = GetStatusCode(result); + + if (result.IsCollection) { - JsonRpcResult.Entry entry = enumerator.Current; - using (entry) + resultWriter.Write(_jsonOpeningBracket); + bool first = true; + JsonRpcBatchResultAsyncEnumerator enumerator = result.BatchedResponses.GetAsyncEnumerator(CancellationToken.None); + try { - if (!first) resultWriter.Write(_jsonComma); - first = false; - await _jsonSerializer.SerializeAsync(resultWriter, entry.Response); - _ = _jsonRpcLocalStats.ReportCall(entry.Report); - - // Stop batch if non-authenticated response exceeds configured size limit - if (!jsonRpcContext.IsAuthenticated && resultWriter.WrittenCount > _jsonRpcConfig.MaxBatchResponseBodySize) + while (await enumerator.MoveNextAsync()) { - if (_logger.IsWarn) _logger.Warn($"The max batch response body size exceeded. The current response size {resultWriter.WrittenCount}, and the config setting is JsonRpc.{nameof(_jsonRpcConfig.MaxBatchResponseBodySize)} = {_jsonRpcConfig.MaxBatchResponseBodySize}"); - enumerator.IsStopped = true; + JsonRpcResult.Entry entry = enumerator.Current; + using (entry) + { + if (!first) + { + resultWriter.Write(_jsonComma); + } + + first = false; + await jsonSerializer.SerializeAsync(resultWriter, entry.Response); + _ = jsonRpcLocalStats.ReportCall(entry.Report); + + // We reached the limit and don't want to respond to more request in the batch + if (!jsonRpcContext.IsAuthenticated && resultWriter.WrittenCount > jsonRpcConfig.MaxBatchResponseBodySize) + { + if (logger.IsWarn) logger.Warn($"The max batch response body size exceeded. The current response size {resultWriter.WrittenCount}, and the config setting is JsonRpc.{nameof(jsonRpcConfig.MaxBatchResponseBodySize)} = {jsonRpcConfig.MaxBatchResponseBodySize}"); + enumerator.IsStopped = true; + } + } } } + finally + { + await enumerator.DisposeAsync(); + } + + resultWriter.Write(_jsonClosingBracket); + } + else + { + await jsonSerializer.SerializeAsync(resultWriter, result.Response); + } + await resultWriter.CompleteAsync(); + if (stream is not null) + { + ctx.Response.ContentLength = resultWriter.WrittenCount; + stream.Seek(0, SeekOrigin.Begin); + await stream.CopyToAsync(ctx.Response.Body); } } + catch (Exception e) when (e.InnerException is OperationCanceledException) + { + await SerializeTimeoutException(resultWriter); + } + catch (OperationCanceledException) + { + await SerializeTimeoutException(resultWriter); + } finally { - await enumerator.DisposeAsync(); + await ctx.Response.CompleteAsync(); } - resultWriter.Write(_jsonClosingBracket); - } - else if (result.Response is JsonRpcSuccessResponse { Result: IStreamableResult streamable }) - { - await WriteStreamableResponseAsync(resultWriter, result.Response, streamable, ctx.RequestAborted); - } - else - { - WriteJsonRpcResponse(resultWriter, result.Response); - } - await resultWriter.CompleteAsync(); - if (stream is not null) - { - ctx.Response.ContentLength = resultWriter.WrittenCount; - stream.Seek(0, SeekOrigin.Begin); - await stream.CopyToAsync(ctx.Response.Body); - } - } - catch (Exception e) when (e is OperationCanceledException || e.InnerException is OperationCanceledException) - { - JsonRpcErrorResponse error = _jsonRpcService.GetErrorResponse(ErrorCodes.Timeout, "Request was canceled due to enabled timeout."); - await _jsonSerializer.SerializeAsync(resultWriter, error); - } - finally - { - await ctx.Response.CompleteAsync(); - } - - long handlingTimeMicroseconds = (long)Stopwatch.GetElapsedTime(startTime).TotalMicroseconds; - _ = _jsonRpcLocalStats.ReportCall(result.IsCollection - ? new RpcReport("# collection serialization #", handlingTimeMicroseconds, true) - : result.Report.Value, handlingTimeMicroseconds, resultWriter.WrittenCount); - Interlocked.Add(ref Metrics.JsonRpcBytesSentHttp, resultWriter.WrittenCount); - break; - } - } - } - catch (Microsoft.AspNetCore.Http.BadHttpRequestException e) - { - if (_logger.IsDebug) LogBadRequest(_logger, e); - ctx.Response.ContentType = "application/json"; - ctx.Response.StatusCode = e.StatusCode; - JsonRpcErrorResponse errResp = _jsonRpcService.GetErrorResponse( - e.StatusCode == StatusCodes.Status413PayloadTooLarge ? ErrorCodes.LimitExceeded : ErrorCodes.InvalidRequest, - e.Message); - await _jsonSerializer.SerializeAsync(ctx.Response.BodyWriter, errResp); - await ctx.Response.CompleteAsync(); - } - finally - { - Interlocked.Add(ref Metrics.JsonRpcBytesReceivedHttp, knownContentLength ?? countingReader?.Length ?? 0); - } - } - /// - /// Writes a JSON-RPC response with typed serialization for the result/error payload, - /// avoiding polymorphic dispatch through the JsonRpcResponse base class hierarchy. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void WriteJsonRpcResponse(IBufferWriter writer, JsonRpcResponse response) - { - using var jsonWriter = new Utf8JsonWriter(writer, new JsonWriterOptions { SkipValidation = true }); + long handlingTimeMicroseconds = (long)Stopwatch.GetElapsedTime(startTime).TotalMicroseconds; + _ = jsonRpcLocalStats.ReportCall(result.IsCollection + ? new RpcReport("# collection serialization #", handlingTimeMicroseconds, true) + : result.Report.Value, handlingTimeMicroseconds, resultWriter.WrittenCount); - jsonWriter.WriteStartObject(); - jsonWriter.WriteString("jsonrpc"u8, "2.0"u8); + Interlocked.Add(ref Metrics.JsonRpcBytesSentHttp, resultWriter.WrittenCount); - if (response is JsonRpcSuccessResponse successResponse) - { - jsonWriter.WritePropertyName("result"u8); - object? result = successResponse.Result; - if (result is not null) - { - JsonSerializer.Serialize(jsonWriter, result, result.GetType(), EthereumJsonSerializer.JsonOptions); + // There should be only one response because we don't expect multiple JSON tokens in the request + break; + } + } + } + catch (Microsoft.AspNetCore.Http.BadHttpRequestException e) + { + if (logger.IsDebug) logger.Debug($"Couldn't read request.{Environment.NewLine}{e}"); + await PushErrorResponse(e.StatusCode, e.StatusCode == StatusCodes.Status413PayloadTooLarge + ? ErrorCodes.LimitExceeded + : ErrorCodes.InvalidRequest, + e.Message); + } + finally + { + Interlocked.Add(ref Metrics.JsonRpcBytesReceivedHttp, ctx.Request.ContentLength ?? request.Length); + } } - else + Task SerializeTimeoutException(CountingWriter resultStream) { - jsonWriter.WriteNullValue(); + JsonRpcErrorResponse? error = jsonRpcService.GetErrorResponse(ErrorCodes.Timeout, "Request was canceled due to enabled timeout."); + return jsonSerializer.SerializeAsync(resultStream, error); } - } - else if (response is JsonRpcErrorResponse errorResponse) - { - jsonWriter.WritePropertyName("error"u8); - if (errorResponse.Error is not null) + async Task PushErrorResponse(int statusCode, int errorCode, string message) { - JsonSerializer.Serialize(jsonWriter, errorResponse.Error, EthereumJsonSerializer.JsonOptions); + JsonRpcErrorResponse? response = jsonRpcService.GetErrorResponse(errorCode, message); + ctx.Response.ContentType = "application/json"; + ctx.Response.StatusCode = statusCode; + await jsonSerializer.SerializeAsync(ctx.Response.BodyWriter, response); + await ctx.Response.CompleteAsync(); } - else - { - jsonWriter.WriteNullValue(); - } - } - - jsonWriter.WritePropertyName("id"u8); - WriteId(jsonWriter, response.Id); - - jsonWriter.WriteEndObject(); - } + })); - private static void WriteId(Utf8JsonWriter writer, object? id) - { - switch (id) + if (healthChecksConfig.Enabled) { - case int intId: - writer.WriteNumberValue(intId); - break; - case long longId: - writer.WriteNumberValue(longId); - break; - case string strId: - writer.WriteStringValue(strId); - break; - case null: - writer.WriteNullValue(); - break; - default: - WriteOther(writer, id); - break; - } + var fileProvider = new ManifestEmbeddedFileProvider(typeof(Startup).Assembly, "wwwroot"); - [MethodImpl(MethodImplOptions.NoInlining)] - void WriteOther(Utf8JsonWriter writer, object? id) - { - JsonSerializer.Serialize(writer, id, id.GetType(), EthereumJsonSerializer.JsonOptions); + app.UseDefaultFiles(new DefaultFilesOptions { FileProvider = fileProvider }); + app.UseStaticFiles(new StaticFileOptions { FileProvider = fileProvider }); } } - private static async ValueTask WriteStreamableResponseAsync( - CountingWriter writer, JsonRpcResponse response, - IStreamableResult streamable, CancellationToken ct) - { - writer.Write("{\"jsonrpc\":\"2.0\",\"result\":"u8); - await streamable.WriteToAsync(writer, ct); - writer.Write(",\"id\":"u8); - WriteIdRaw(writer, response.Id); - writer.Write("}"u8); - } + /// + /// Check for IPv4 localhost (127.0.0.1) and IPv6 localhost (::1) + /// + /// Request source + private static bool IsLocalhost(IPAddress remoteIp) + => IPAddress.IsLoopback(remoteIp) || remoteIp.Equals(IPAddress.IPv6Loopback); - private static void WriteIdRaw(PipeWriter writer, object? id) + private static int GetStatusCode(in JsonRpcResult result) { - switch (id) + if (result.IsCollection) { - case int intId: - { - Span buf = writer.GetSpan(11); - intId.TryFormat(buf, out int written); - writer.Advance(written); - break; - } - case long longId: - { - Span buf = writer.GetSpan(20); - longId.TryFormat(buf, out int written); - writer.Advance(written); - break; - } - default: - WriteOther(writer, id); - break; + return StatusCodes.Status200OK; } - - [MethodImpl(MethodImplOptions.NoInlining)] - void WriteOther(PipeWriter writer, object? id) + else { - switch (id) - { - case string strId: - { - // JSON-RPC IDs are simple values (typically numeric); no escaping needed - Span buf = writer.GetSpan(strId.Length * 3 + 2); - buf[0] = (byte)'"'; - int len = Encoding.UTF8.GetBytes(strId, buf[1..]); - buf[len + 1] = (byte)'"'; - writer.Advance(len + 2); - break; - } - default: - writer.Write("null"u8); - break; - } + return IsResourceUnavailableError(result.Response) + ? StatusCodes.Status503ServiceUnavailable + : StatusCodes.Status200OK; } } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void LogBadRequest(ILogger logger, Exception e) => - logger.Debug($"Couldn't read request.{Environment.NewLine}{e}"); + private static bool IsResourceUnavailableError(JsonRpcResponse? response) + { + return response is JsonRpcErrorResponse { Error.Code: ErrorCodes.ModuleTimeout } + or JsonRpcErrorResponse { Error.Code: ErrorCodes.LimitExceeded }; + } private sealed class CountingPipeReader(PipeReader stream) : PipeReader { diff --git a/src/Nethermind/Nethermind.Runner/Program.cs b/src/Nethermind/Nethermind.Runner/Program.cs index 2440b46e7819..13512037d052 100644 --- a/src/Nethermind/Nethermind.Runner/Program.cs +++ b/src/Nethermind/Nethermind.Runner/Program.cs @@ -52,13 +52,6 @@ ILogger logger = new(SimpleConsoleLogger.Instance); ProcessExitSource? processExitSource = default; var unhandledError = "A critical error has occurred"; -Option[] deprecatedOptions = -[ - BasicOptions.ConfigurationDirectory, - BasicOptions.DatabasePath, - BasicOptions.LoggerConfigurationSource, - BasicOptions.PluginsDirectory -]; AppDomain.CurrentDomain.UnhandledException += (sender, e) => { @@ -237,6 +230,9 @@ static Option CreateOption(Type configType, string name, string? alias) foreach (Type configType in configTypes.Where(ct => !ct.IsAssignableTo(typeof(INoCategoryConfig))).OrderBy(c => c.Name)) { + if (configType is null) + continue; + ConfigCategoryAttribute? typeLevel = configType.GetCustomAttribute(); if (typeLevel is not null && typeLevel.DisabledForCli) @@ -281,6 +277,14 @@ static Option CreateOption(Type configType, string name, string? alias) void CheckForDeprecatedOptions(ParseResult parseResult) { + Option[] deprecatedOptions = + [ + BasicOptions.ConfigurationDirectory, + BasicOptions.DatabasePath, + BasicOptions.LoggerConfigurationSource, + BasicOptions.PluginsDirectory + ]; + foreach (Token token in parseResult.Tokens) { foreach (Option option in deprecatedOptions) diff --git a/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json index 932eb67f0559..a2020a9c637c 100644 --- a/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 42150000, - "PivotHash": "0x54691ca9f732f6b3a2fd2b85ecf7359744c34bbc9ca08c619fdebe857196fb4f" + "PivotNumber": 41550000, + "PivotHash": "0xf7d2cc48b2f2f3e785df037395a729aa6ae08640ee024826aa0d7938f56944e8" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/configs/base-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/base-sepolia.json index 571e90d9457b..bfec9bf16048 100644 --- a/src/Nethermind/Nethermind.Runner/configs/base-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/base-sepolia.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 37660000, - "PivotHash": "0x1c23e29f8f4e5e3cc356bfeb79f01ceb5d9cf0026d39667f66bb1032c655ca9d" + "PivotNumber": 37060000, + "PivotHash": "0x3f02d8a236bc757b2ae605078a8881dd5b208794fc1ad2e31501498ea54a062b" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/configs/chiado.json b/src/Nethermind/Nethermind.Runner/configs/chiado.json index 7ef8547654c1..2453ce6ca660 100644 --- a/src/Nethermind/Nethermind.Runner/configs/chiado.json +++ b/src/Nethermind/Nethermind.Runner/configs/chiado.json @@ -18,8 +18,8 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 19780000, - "PivotHash": "0xac62a1145915936b74ef989b8ef0110be4542283e904fef2826726866fc6725e", + "PivotNumber": 19870000, + "PivotHash": "0x21d17776c1d770325eefc8df0753a91236886fef0774be51fccc4c1ed85d8a83", "PivotTotalDifficulty": "231708131825107706987652208063906496124457284", "FastSyncCatchUpHeightDelta": 10000000000, "UseGethLimitsInFastBlocks": false diff --git a/src/Nethermind/Nethermind.Runner/configs/gnosis.json b/src/Nethermind/Nethermind.Runner/configs/gnosis.json index 10ddaf8c213a..93071d8dcba5 100644 --- a/src/Nethermind/Nethermind.Runner/configs/gnosis.json +++ b/src/Nethermind/Nethermind.Runner/configs/gnosis.json @@ -14,8 +14,8 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 44670000, - "PivotHash": "0x229a26df1c0b804793d81de764b4bcd9de5e810174f4e21ddc787d650235cc16", + "PivotNumber": 44430000, + "PivotHash": "0x4975eece3dea5d0f47b308c557d0a5473328f55276e893ece40d7956d5b7bc58", "PivotTotalDifficulty": "8626000110427538733349499292577475819600160930", "UseGethLimitsInFastBlocks": false, "FastSyncCatchUpHeightDelta": 10000000000, diff --git a/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json index bbfcd15344a9..0fed00a3dfc4 100644 --- a/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json @@ -12,9 +12,9 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 22290000, - "PivotHash": "0x4bba26f5d256f972384413be51b4bfb5fd6c21d192d5fb55880d7baadc682631", - "PivotTotalDifficulty": "39936580" + "PivotNumber": 22050000, + "PivotHash": "0x14d4cb874b1cb80011554f6942f68927ec2f5927b773d0f4edda8baca912ac9e", + "PivotTotalDifficulty": "39571169" }, "Metrics": { "NodeName": "JOC-Mainnet" diff --git a/src/Nethermind/Nethermind.Runner/configs/joc-testnet.json b/src/Nethermind/Nethermind.Runner/configs/joc-testnet.json index b638cd8b5ca9..2bbf52bb35cf 100644 --- a/src/Nethermind/Nethermind.Runner/configs/joc-testnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/joc-testnet.json @@ -12,9 +12,9 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 15900000, - "PivotHash": "0xc545fbce23bbd9e6d756210003ef4b3c5021f84763b82cb33ba3f739cbe796fc", - "PivotTotalDifficulty": "26391193" + "PivotNumber": 15650000, + "PivotHash": "0x482f1c47d98099e75cbf9ed03411f9ba1dd935962477cfc8276e40f5a03ec9b9", + "PivotTotalDifficulty": "26000126" }, "Metrics": { "NodeName": "JOC-Testnet" diff --git a/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json index f461ff99a533..0c2559954cb9 100644 --- a/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json @@ -17,9 +17,9 @@ }, "Sync": { "SnapSync": true, - "PivotNumber": 28870000, - "PivotHash": "0x9d00c48eaf9e84f48936baeea8aba0866422524641546af30c9bb4afa256c367", - "PivotTotalDifficulty": "49575263", + "PivotNumber": 28500000, + "PivotHash": "0xc59f58328c02099e9114d69187be7cbe373ea0f6c489c16e8064c448e013aed3", + "PivotTotalDifficulty": "0", "HeaderStateDistance": 6 }, "JsonRpc": { diff --git a/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json index eb127802e2d4..a5b145e4f66d 100644 --- a/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json @@ -17,8 +17,8 @@ }, "Sync": { "SnapSync": true, - "PivotNumber": 24850000, - "PivotHash": "0xd5816d5b0b0024581511a44f029b9df64940b3d20ebff9cd8b2790fab67e4950", + "PivotNumber": 24080000, + "PivotHash": "0xf11dd5fdf30d5f9d2e3a2ffae4da3b3353aa3576619d44a8457f667d1c72256d", "PivotTotalDifficulty": "37331807", "HeaderStateDistance": 6 }, diff --git a/src/Nethermind/Nethermind.Runner/configs/mainnet.json b/src/Nethermind/Nethermind.Runner/configs/mainnet.json index 9063a4642f8f..06c341f13328 100644 --- a/src/Nethermind/Nethermind.Runner/configs/mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/mainnet.json @@ -10,8 +10,8 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 24457000, - "PivotHash": "0x1b315b4d85f8c804569022f1e0a021def8c2029ac4b55edeea03e2cf7c1f936e", + "PivotNumber": 24357000, + "PivotHash": "0x46ee912a15df3d0642723f8f608386c560a6312ebea4a372f2f66e71fd8761bc", "PivotTotalDifficulty": "58750003716598352816469", "FastSyncCatchUpHeightDelta": 10000000000, "AncientReceiptsBarrier": 15537394, diff --git a/src/Nethermind/Nethermind.Runner/configs/op-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/op-mainnet.json index 8e20ed622711..179ce25f29e7 100644 --- a/src/Nethermind/Nethermind.Runner/configs/op-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/op-mainnet.json @@ -15,8 +15,8 @@ "FastSyncCatchUpHeightDelta": "10000000000", "AncientBodiesBarrier": 105235063, "AncientReceiptsBarrier": 105235063, - "PivotNumber": 147750000, - "PivotHash": "0xc5ca711298ccd110764eff891270897df703ba081239562286b263fbc046755e" + "PivotNumber": 147140000, + "PivotHash": "0x41178d97cb45fa28393d94861e9ce65622bb2289a0e0d0e82d866fc5cda68f5e" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/configs/op-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/op-sepolia.json index 6d79b23d20ef..e014551b66f5 100644 --- a/src/Nethermind/Nethermind.Runner/configs/op-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/op-sepolia.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 39640000, - "PivotHash": "0x60392267e63a958936e102312fb81a90f023c23b372279e1b25112bdf92ea42c" + "PivotNumber": 39040000, + "PivotHash": "0x638722bcdbef6103c08709eae7828dada842a5f25620ccc668aec1aef1e55406" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/configs/sepolia.json b/src/Nethermind/Nethermind.Runner/configs/sepolia.json index 00765e81652d..06111c6d309a 100644 --- a/src/Nethermind/Nethermind.Runner/configs/sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/sepolia.json @@ -17,8 +17,8 @@ "FastSync": true, "SnapSync": true, "UseGethLimitsInFastBlocks": true, - "PivotNumber": 10261000, - "PivotHash": "0x4afc23c05b485ec4a9cdf17eb9d53e847d24d4ad580287a3db80ac5b06d5f241", + "PivotNumber": 10164000, + "PivotHash": "0x85a3f029e6d9d13080ef0aec663c5a457121ecc574cc5d322ab4aed2bf7c6a13", "PivotTotalDifficulty": "17000018015853232", "FastSyncCatchUpHeightDelta": 10000000000, "AncientReceiptsBarrier": 1450409, diff --git a/src/Nethermind/Nethermind.Runner/configs/spaceneth.json b/src/Nethermind/Nethermind.Runner/configs/spaceneth.json index a9d0deccc1fe..1fa31a55f406 100644 --- a/src/Nethermind/Nethermind.Runner/configs/spaceneth.json +++ b/src/Nethermind/Nethermind.Runner/configs/spaceneth.json @@ -46,6 +46,7 @@ "Subscribe", "Trace", "TxPool", + "Vault", "Web3" ] }, diff --git a/src/Nethermind/Nethermind.Runner/configs/spaceneth_persistent.json b/src/Nethermind/Nethermind.Runner/configs/spaceneth_persistent.json index efbc825140ea..688f9c6f9567 100644 --- a/src/Nethermind/Nethermind.Runner/configs/spaceneth_persistent.json +++ b/src/Nethermind/Nethermind.Runner/configs/spaceneth_persistent.json @@ -41,6 +41,7 @@ "Subscribe", "Trace", "TxPool", + "Vault", "Web3" ] }, diff --git a/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json index ac4884475eee..415192600e02 100644 --- a/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 25880000, - "PivotHash": "0x8fa42f29309ccc1455c2d5af57f22a9941dcb13d7db23cc49508977bfb922bb6" + "PivotNumber": 25270000, + "PivotHash": "0x78183e57fb644b981fa8495ce86f7683bf4fbc3d3ff935d022795d809ec0e229" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/configs/worldchain-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/worldchain-sepolia.json index e2efd7ba62d3..eaa2d08f3f64 100644 --- a/src/Nethermind/Nethermind.Runner/configs/worldchain-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/worldchain-sepolia.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 25270000, - "PivotHash": "0x4e5f1ecdcbc3931cc6dc40d3c5db56c00d08dac306caf26ccee31d7b6d0dadaf" + "PivotNumber": 24670000, + "PivotHash": "0xec997d3cd44843014f21b520ed11bea72ca1d0a405a41da81b645f98a4c23c61" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/packages.lock.json b/src/Nethermind/Nethermind.Runner/packages.lock.json index 4c14de3e87d7..858ddab75fd0 100644 --- a/src/Nethermind/Nethermind.Runner/packages.lock.json +++ b/src/Nethermind/Nethermind.Runner/packages.lock.json @@ -13,21 +13,21 @@ }, "Microsoft.Build.Tasks.Git": { "type": "Direct", - "requested": "[10.0.103, )", - "resolved": "10.0.103", - "contentHash": "QoiCMcPuxC6eqRQmrmF9zBY96ejIznXtve/lJJbonGD9I5Aygf2AUCOWslGiCEtBbfWRSuUnepBjuuVOdAl5ag==" + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" }, "Microsoft.Extensions.FileProviders.Embedded": { "type": "Direct", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "kw/xPl7m4Gv6bqx2ojihTtWiN2K2AklyMIrvncuSi2MOdwu0oMKoyh0G3p2Brt7m43Q9ER0IaA2G4EGjfgDh/w==" + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "PkrDqw6uwm4Y7IucI3PjqwhCeCoHno3hzOeEt0hUrFI+ccYBC0X3NfQbhkFG46TgclnCyUDStmgJzpie9T9ZlQ==" }, "Microsoft.VisualStudio.Azure.Containers.Tools.Targets": { "type": "Direct", - "requested": "[1.23.0, )", - "resolved": "1.23.0", - "contentHash": "2wDnb4umupJZ/1ikgWozFVpggH1mlHQFc0odXVv2ZagL3RYwXgW9zmC15fiqIBzmaC0vLZUnLGwDY+p8ZR7Syw==" + "requested": "[1.22.1, )", + "resolved": "1.22.1", + "contentHash": "EfYANhAWqmWKoLwN6bxoiPZSOfJSO9lzX+UrU6GVhLhPub1Hd+5f0zL0/tggIA6mRz6Ebw2xCNcIsM4k+7NPng==" }, "NLog.Targets.Seq": { "type": "Direct", @@ -40,15 +40,15 @@ }, "Pyroscope": { "type": "Direct", - "requested": "[0.14.1, )", - "resolved": "0.14.1", - "contentHash": "i8BoY82ZBrBOmgal7Zbf69CzQ2tRJkV0v7se1yB54rsOuuxFIzcUMplyD0VIg6Gl+EkVORNpm5OUL5tJGhR+lg==" + "requested": "[0.13.0, )", + "resolved": "0.13.0", + "contentHash": "rdthieTs1xwkAl3z9eePA3kpQM+xRCqhiqupyXt15emyU5wVp+X5ur29W//fDmaJx4Rm2OH9xcLgJqacdtOMKg==" }, "System.CommandLine": { "type": "Direct", - "requested": "[2.0.3, )", - "resolved": "2.0.3", - "contentHash": "5nY9hlrGGFEmyecNUux58sohD2Q16U6jlFBYwH57b2IVUs+u7LfMFaHwOtw2tuIi8CUl6jKXw5s4FuIt9aLtug==" + "requested": "[2.0.1, )", + "resolved": "2.0.1", + "contentHash": "GLc43eDFq8KbpxIb7UhTwV0vC5CzB0NspJvfFbfhoW4O057xCJXuO18KLpVn9x3JykQn2mRske6+I6JXHwqmDg==" }, "AspNetCore.HealthChecks.UI.Core": { "type": "Transitive", @@ -663,7 +663,8 @@ "dependencies": { "Nethermind.Core": "[1.37.0-unstable, )", "NonBlocking": "[2.1.2, )", - "System.Configuration.ConfigurationManager": "[10.0.3, )" + "PierTwo.Lantern.Discv5.WireProtocol": "[1.0.0-preview.7, )", + "System.Configuration.ConfigurationManager": "[10.0.1, )" } }, "nethermind.consensus": { @@ -731,11 +732,11 @@ "type": "Project", "dependencies": { "BouncyCastle.Cryptography": "[2.6.2, )", - "Ckzg.Bindings": "[2.1.5.1551, )", + "Ckzg.Bindings": "[2.1.5.1544, )", "Nethermind.Core": "[1.37.0-unstable, )", "Nethermind.Crypto.Bls": "[1.0.5, )", "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", - "System.Security.Cryptography.ProtectedData": "[10.0.3, )" + "System.Security.Cryptography.ProtectedData": "[10.0.1, )" } }, "nethermind.db": { @@ -743,7 +744,6 @@ "dependencies": { "Nethermind.Config": "[1.37.0-unstable, )", "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", - "Nethermind.TurboPForBindings": "[1.0.0, )", "NonBlocking": "[2.1.2, )" } }, @@ -754,7 +754,7 @@ "Nethermind.Api": "[1.37.0-unstable, )", "Nethermind.Db": "[1.37.0-unstable, )", "NonBlocking": "[2.1.2, )", - "RocksDB": "[10.4.2.63147, 10.4.2.63147]" + "RocksDB": "[10.4.2.62659, 10.4.2.62659]" } }, "nethermind.db.rpc": { @@ -779,7 +779,7 @@ "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", "Nethermind.Serialization.Ssz": "[1.37.0-unstable, )", "Nethermind.State": "[1.37.0-unstable, )", - "Snappier": "[1.3.0, )" + "Snappier": "[1.2.0, )" } }, "nethermind.ethstats": { @@ -813,7 +813,7 @@ "Nethermind.Crypto.SecP256r1": "[1.0.0-preview.6, )", "Nethermind.Evm": "[1.37.0-unstable, )", "Nethermind.GmpBindings": "[1.0.3, )", - "Nethermind.MclBindings": "[1.0.5, )", + "Nethermind.MclBindings": "[1.0.4, )", "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", "Nethermind.Specs": "[1.37.0-unstable, )" } @@ -844,8 +844,8 @@ "nethermind.grpc": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.33.5, )", - "Google.Protobuf.Tools": "[3.33.5, )", + "Google.Protobuf": "[3.33.2, )", + "Google.Protobuf.Tools": "[3.33.2, )", "Grpc": "[2.46.6, )", "Nethermind.Config": "[1.37.0-unstable, )", "Nethermind.Core": "[1.37.0-unstable, )", @@ -986,7 +986,7 @@ "Nethermind.Network.Contract": "[1.37.0-unstable, )", "Nethermind.Network.Stats": "[1.37.0-unstable, )", "Nethermind.Synchronization": "[1.37.0-unstable, )", - "Snappier": "[1.3.0, )" + "Snappier": "[1.2.0, )" } }, "nethermind.network.contract": { @@ -1033,7 +1033,7 @@ "nethermind.optimism": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.33.5, )", + "Google.Protobuf": "[3.33.2, )", "Nethermind.Api": "[1.37.0-unstable, )", "Nethermind.Blockchain": "[1.37.0-unstable, )", "Nethermind.Consensus": "[1.37.0-unstable, )", @@ -1043,7 +1043,7 @@ "Nethermind.Libp2p": "[1.0.0-preview.45, )", "Nethermind.Libp2p.Protocols.PubsubPeerDiscovery": "[1.0.0-preview.45, )", "Nethermind.Merge.Plugin": "[1.37.0-unstable, )", - "Snappier": "[1.3.0, )" + "Snappier": "[1.2.0, )" } }, "nethermind.seq": { @@ -1075,7 +1075,7 @@ "nethermind.shutter": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.33.5, )", + "Google.Protobuf": "[3.33.2, )", "Nethermind.Blockchain": "[1.37.0-unstable, )", "Nethermind.Consensus": "[1.37.0-unstable, )", "Nethermind.Core": "[1.37.0-unstable, )", @@ -1104,7 +1104,7 @@ "Nethermind.Config": "[1.37.0-unstable, )", "Nethermind.Core": "[1.37.0-unstable, )", "Nethermind.Serialization.Json": "[1.37.0-unstable, )", - "ZstdSharp.Port": "[0.8.7, )" + "ZstdSharp.Port": "[0.8.6, )" } }, "nethermind.state": { @@ -1237,9 +1237,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1551, )", - "resolved": "2.1.5.1551", - "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" + "requested": "[2.1.5.1544, )", + "resolved": "2.1.5.1544", + "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" }, "CommunityToolkit.HighPerformance": { "type": "CentralTransitive", @@ -1280,15 +1280,15 @@ }, "Google.Protobuf": { "type": "CentralTransitive", - "requested": "[3.33.5, )", - "resolved": "3.33.5", - "contentHash": "XEzLpCTosZb5I6eGSPn7rAES0VfkJkn3Cqydh0W39POdZwkdhPhOmAROTFJF9g0ardst4ulNXRm/q/iXwNu+Qw==" + "requested": "[3.33.2, )", + "resolved": "3.33.2", + "contentHash": "vZXVbrZgBqUkP5iWQi0CS6pucIS2MQdEYPS1duWCo8fGrrt4th6HTiHfLFX2RmAWAQl1oUnzGgyDBsfq7fHQJA==" }, "Google.Protobuf.Tools": { "type": "CentralTransitive", - "requested": "[3.33.5, )", - "resolved": "3.33.5", - "contentHash": "A1UnkTCZvOsIW1+S8papxEx0CxrcIZlxEC+audWbvovU7cZAaF6Rfb2yJgPsTkzqU+dpyBpHE5v1tLPQ+NF4rQ==" + "requested": "[3.33.2, )", + "resolved": "3.33.2", + "contentHash": "3YFiSs39mhBiAfeQ9u27JniqVNunVrYomNnSb8Rx6D3dJqC9Uwdpm5Xu2f2ZOGvUzkB114NAvU44KySOplgCzw==" }, "Grpc": { "type": "CentralTransitive", @@ -1478,9 +1478,9 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.5, )", - "resolved": "1.0.5", - "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + "requested": "[1.0.4, )", + "resolved": "1.0.4", + "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" }, "Nethermind.Numerics.Int256": { "type": "CentralTransitive", @@ -1488,12 +1488,6 @@ "resolved": "1.4.0", "contentHash": "w8HRMsdpX9fG9kcELJeJPEKIZgOUTCe47ebtejCvfBYQVlabA9blqba6QWIt5oG8cRSgnVlQ24DsdGsLzqFM+Q==" }, - "Nethermind.TurboPForBindings": { - "type": "CentralTransitive", - "requested": "[1.0.0, )", - "resolved": "1.0.0", - "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" - }, "Nito.Collections.Deque": { "type": "CentralTransitive", "requested": "[1.2.1, )", @@ -1550,9 +1544,9 @@ }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.63147, 10.4.2.63147]", - "resolved": "10.4.2.63147", - "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" + "requested": "[10.4.2.62659, 10.4.2.62659]", + "resolved": "10.4.2.62659", + "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" }, "SCrypt": { "type": "CentralTransitive", @@ -1562,24 +1556,24 @@ }, "Snappier": { "type": "CentralTransitive", - "requested": "[1.3.0, )", - "resolved": "1.3.0", - "contentHash": "yYANMXm5MUiF9jzsOI7WH5Cj1HYzcWEIEVI2Ljq8N7hS/zwLCBx+GoGeyc7aJFAvpRcbSIbdNaelVomHqd6UDQ==" + "requested": "[1.2.0, )", + "resolved": "1.2.0", + "contentHash": "Lv83i7hQZbl+r0qkO6VrBZ0OHL/R/onAVcCcxgYpT8inhqJ2/f1qkIWT3gWwdcCz4cPHOQrS0uX40cFUQyOS5Q==" }, "System.Configuration.ConfigurationManager": { "type": "CentralTransitive", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "69ZT/MYxQSxwdiiHRtI08noXiG5drj/bXDDZISmeWkNUtbIfYgmTiof16tCVOLTdmSQY7W7gwxkMliKdreWHGQ==", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "HfOAIlSA8OuaxBZD6xjsUWhtB0KdKSWEfRId8gSGveLUjuP6G8IxfiFgJNxaiRIEC1kx4pSvz3Em5xW/J6LLxA==", "dependencies": { - "System.Security.Cryptography.ProtectedData": "10.0.3" + "System.Security.Cryptography.ProtectedData": "10.0.1" } }, "System.Security.Cryptography.ProtectedData": { "type": "CentralTransitive", - "requested": "[10.0.3, )", - "resolved": "10.0.3", - "contentHash": "JCKbH/CN5l0CSoJBILEvJmNQVp5vV+FY3q2ue4K9p4eDT4mFEv0bjTQCV+MD6Qk1b/qk9fWmZZKhG1TklbXw1Q==" + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "9SqHNq+lAjZeyPcm69FTQEjr+wsRYvkS3aW8yxoEndVYwDRkCrsP/44QPqpWHwzevoX26rkOoQ6kr7GZWngw2A==" }, "TestableIO.System.IO.Abstractions.Wrappers": { "type": "CentralTransitive", @@ -1602,9 +1596,9 @@ }, "ZstdSharp.Port": { "type": "CentralTransitive", - "requested": "[0.8.7, )", - "resolved": "0.8.7", - "contentHash": "+4VpxvzEKaHpfTjsLRMhQHx6brqGBkIA+fjUM3wUW8ZoWpPFAeKrO2Nf4uZDeBjjzNDNxSRvLQH4b3S9Ku4JJQ==" + "requested": "[0.8.6, )", + "resolved": "0.8.6", + "contentHash": "iP4jVLQoQmUjMU88g1WObiNr6YKZGvh4aOXn3yOJsHqZsflwRsxZPcIBvNXgjXO3vQKSLctXGLTpcBPLnWPS8A==" } }, "net10.0/linux-arm64": { @@ -1628,9 +1622,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1551, )", - "resolved": "2.1.5.1551", - "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" + "requested": "[2.1.5.1544, )", + "resolved": "2.1.5.1544", + "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -1688,21 +1682,15 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.5, )", - "resolved": "1.0.5", - "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" - }, - "Nethermind.TurboPForBindings": { - "type": "CentralTransitive", - "requested": "[1.0.0, )", - "resolved": "1.0.0", - "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" + "requested": "[1.0.4, )", + "resolved": "1.0.4", + "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.63147, 10.4.2.63147]", - "resolved": "10.4.2.63147", - "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" + "requested": "[10.4.2.62659, 10.4.2.62659]", + "resolved": "10.4.2.62659", + "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" } }, "net10.0/linux-x64": { @@ -1726,9 +1714,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1551, )", - "resolved": "2.1.5.1551", - "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" + "requested": "[2.1.5.1544, )", + "resolved": "2.1.5.1544", + "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -1786,21 +1774,15 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.5, )", - "resolved": "1.0.5", - "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" - }, - "Nethermind.TurboPForBindings": { - "type": "CentralTransitive", - "requested": "[1.0.0, )", - "resolved": "1.0.0", - "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" + "requested": "[1.0.4, )", + "resolved": "1.0.4", + "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.63147, 10.4.2.63147]", - "resolved": "10.4.2.63147", - "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" + "requested": "[10.4.2.62659, 10.4.2.62659]", + "resolved": "10.4.2.62659", + "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" } }, "net10.0/osx-arm64": { @@ -1824,9 +1806,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1551, )", - "resolved": "2.1.5.1551", - "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" + "requested": "[2.1.5.1544, )", + "resolved": "2.1.5.1544", + "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -1884,21 +1866,15 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.5, )", - "resolved": "1.0.5", - "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" - }, - "Nethermind.TurboPForBindings": { - "type": "CentralTransitive", - "requested": "[1.0.0, )", - "resolved": "1.0.0", - "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" + "requested": "[1.0.4, )", + "resolved": "1.0.4", + "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.63147, 10.4.2.63147]", - "resolved": "10.4.2.63147", - "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" + "requested": "[10.4.2.62659, 10.4.2.62659]", + "resolved": "10.4.2.62659", + "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" } }, "net10.0/osx-x64": { @@ -1922,9 +1898,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1551, )", - "resolved": "2.1.5.1551", - "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" + "requested": "[2.1.5.1544, )", + "resolved": "2.1.5.1544", + "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -1982,21 +1958,15 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.5, )", - "resolved": "1.0.5", - "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" - }, - "Nethermind.TurboPForBindings": { - "type": "CentralTransitive", - "requested": "[1.0.0, )", - "resolved": "1.0.0", - "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" + "requested": "[1.0.4, )", + "resolved": "1.0.4", + "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.63147, 10.4.2.63147]", - "resolved": "10.4.2.63147", - "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" + "requested": "[10.4.2.62659, 10.4.2.62659]", + "resolved": "10.4.2.62659", + "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" } }, "net10.0/win-x64": { @@ -2020,9 +1990,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1551, )", - "resolved": "2.1.5.1551", - "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" + "requested": "[2.1.5.1544, )", + "resolved": "2.1.5.1544", + "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -2080,21 +2050,15 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.5, )", - "resolved": "1.0.5", - "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" - }, - "Nethermind.TurboPForBindings": { - "type": "CentralTransitive", - "requested": "[1.0.0, )", - "resolved": "1.0.0", - "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" + "requested": "[1.0.4, )", + "resolved": "1.0.4", + "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.63147, 10.4.2.63147]", - "resolved": "10.4.2.63147", - "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" + "requested": "[10.4.2.62659, 10.4.2.62659]", + "resolved": "10.4.2.62659", + "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/CountingTextReader.cs b/src/Nethermind/Nethermind.Serialization.Json/CountingTextReader.cs new file mode 100644 index 000000000000..fa57f6b5a795 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/CountingTextReader.cs @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Nethermind.Serialization.Json +{ + public class CountingTextReader : TextReader + { + private readonly TextReader _innerReader; + public int Length { get; private set; } + + public CountingTextReader(TextReader innerReader) + { + _innerReader = innerReader; + } + + public override void Close() + { + base.Close(); + _innerReader.Close(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) + { + _innerReader.Dispose(); + } + } + + public override int Peek() => _innerReader.Peek(); + + public override int Read() + { + Length++; + return _innerReader.Read(); + } + + public override int Read(char[] buffer, int index, int count) => IncrementLength(_innerReader.Read(buffer, index, count)); + + public override int Read(Span buffer) => IncrementLength(_innerReader.Read(buffer)); + + public override async Task ReadAsync(char[] buffer, int index, int count) => IncrementLength(await _innerReader.ReadAsync(buffer, index, count)); + + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => IncrementLength(await _innerReader.ReadAsync(buffer, cancellationToken)); + + public override int ReadBlock(char[] buffer, int index, int count) => IncrementLength(_innerReader.ReadBlock(buffer, index, count)); + + public override int ReadBlock(Span buffer) => IncrementLength(_innerReader.ReadBlock(buffer)); + + public override string ReadLine() => IncrementLength(_innerReader.ReadLine()); + + public override async ValueTask ReadBlockAsync(Memory buffer, CancellationToken cancellationToken = default) => IncrementLength(await _innerReader.ReadBlockAsync(buffer, cancellationToken)); + + public override async Task ReadBlockAsync(char[] buffer, int index, int count) => IncrementLength(await _innerReader.ReadBlockAsync(buffer, index, count)); + + public override async Task ReadLineAsync() => IncrementLength(await _innerReader.ReadLineAsync()); + + public override string ReadToEnd() => IncrementLength(_innerReader.ReadToEnd()); + + public override async Task ReadToEndAsync() => IncrementLength(await _innerReader.ReadToEndAsync()); + + private string IncrementLength(in string read) + { + if (!string.IsNullOrEmpty(read)) + { + Length += read.Length; + } + return read; + } + + private int IncrementLength(in int read) + { + Length += read; + return read; + } + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/CountingTextWriter.cs b/src/Nethermind/Nethermind.Serialization.Json/CountingTextWriter.cs new file mode 100644 index 000000000000..16beb11721d9 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/CountingTextWriter.cs @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.IO; +using System.Text; + +namespace Nethermind.Serialization.Json +{ + public class CountingTextWriter : TextWriter + { + private readonly TextWriter _textWriter; + + public long Size { get; private set; } + + public CountingTextWriter(TextWriter textWriter) + { + _textWriter = textWriter ?? throw new ArgumentNullException(nameof(textWriter)); + } + + public override Encoding Encoding => _textWriter.Encoding; + + public override void Write(char value) + { + _textWriter.Write(value); + Size++; + } + + public override void Flush() + { + _textWriter.Flush(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _textWriter.Dispose(); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs b/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs index 7ce9dc3bb296..173e83017d5d 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs @@ -9,74 +9,57 @@ using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; using Nethermind.Core.Collections; namespace Nethermind.Serialization.Json { - public sealed class EthereumJsonSerializer : IJsonSerializer + public class EthereumJsonSerializer : IJsonSerializer { public const int DefaultMaxDepth = 128; - private static readonly object _globalOptionsLock = new(); - - private static readonly List _additionalConverters = new(); - private static readonly List _additionalResolvers = new(); - private static bool _strictHexFormat; - private static int _optionsVersion; - private readonly int? _maxDepth; - private readonly JsonConverter[] _instanceConverters; - private readonly object _instanceOptionsLock = new(); - - private JsonSerializerOptions _jsonOptions = null!; - private JsonSerializerOptions _jsonOptionsIndented = null!; - private int _instanceOptionsVersion; + private readonly JsonSerializerOptions _jsonOptions; public EthereumJsonSerializer(IEnumerable converters, int maxDepth = DefaultMaxDepth) { _maxDepth = maxDepth; - _instanceConverters = CopyConverters(converters); - RefreshInstanceOptions(); + _jsonOptions = CreateOptions(indented: false, maxDepth: maxDepth, converters: converters); } public EthereumJsonSerializer(int maxDepth = DefaultMaxDepth) { _maxDepth = maxDepth; - _instanceConverters = []; - RefreshInstanceOptions(); + _jsonOptions = maxDepth != DefaultMaxDepth ? CreateOptions(indented: false, maxDepth: maxDepth) : JsonOptions; } public object Deserialize(string json, Type type) { - return JsonSerializer.Deserialize(json, type, GetSerializerOptions(indented: false)); + return JsonSerializer.Deserialize(json, type, _jsonOptions); } public T Deserialize(Stream stream) { - return JsonSerializer.Deserialize(stream, GetSerializerOptions(indented: false)); + return JsonSerializer.Deserialize(stream, _jsonOptions); } public T Deserialize(string json) { - return JsonSerializer.Deserialize(json, GetSerializerOptions(indented: false)); + return JsonSerializer.Deserialize(json, _jsonOptions); } public T Deserialize(ref Utf8JsonReader json) { - return JsonSerializer.Deserialize(ref json, GetSerializerOptions(indented: false)); + return JsonSerializer.Deserialize(ref json, _jsonOptions); } public string Serialize(T value, bool indented = false) { - return JsonSerializer.Serialize(value, GetSerializerOptions(indented)); + return JsonSerializer.Serialize(value, indented ? JsonOptionsIndented : _jsonOptions); } - private static JsonSerializerOptions CreateOptions(bool indented, IEnumerable instanceConverters = null, int maxDepth = DefaultMaxDepth) + private static JsonSerializerOptions CreateOptions(bool indented, IEnumerable converters = null, int maxDepth = DefaultMaxDepth) { - SnapshotGlobalOptions(out bool strictHexFormat, out JsonConverter[] additionalConverters, out IJsonTypeInfoResolver[] additionalResolvers); - var result = new JsonSerializerOptions { WriteIndented = indented, @@ -88,7 +71,6 @@ private static JsonSerializerOptions CreateOptions(bool indented, IEnumerable()); + result.Converters.AddRange(_additionalConverters); + result.Converters.AddRange(converters ?? Array.Empty()); return result; } + private static readonly List _additionalConverters = new(); public static void AddConverter(JsonConverter converter) { - ArgumentNullException.ThrowIfNull(converter); - lock (_globalOptionsLock) - { - _additionalConverters.Add(converter); - RefreshGlobalOptionsNoLock(); - } - } + _additionalConverters.Add(converter); - public static void AddTypeInfoResolver(IJsonTypeInfoResolver resolver) - { - ArgumentNullException.ThrowIfNull(resolver); - lock (_globalOptionsLock) - { - for (int i = 0; i < _additionalResolvers.Count; i++) - { - if (ReferenceEquals(_additionalResolvers[i], resolver)) - { - return; - } - } - - _additionalResolvers.Add(resolver); - RefreshGlobalOptionsNoLock(); - } + JsonOptions = CreateOptions(indented: false); + JsonOptionsIndented = CreateOptions(indented: true); } + private static bool _strictHexFormat; public static bool StrictHexFormat { get => _strictHexFormat; set { - lock (_globalOptionsLock) - { - if (_strictHexFormat == value) - return; - - _strictHexFormat = value; - RefreshGlobalOptionsNoLock(); - } + if (_strictHexFormat == value) + return; + _strictHexFormat = value; + JsonOptions = CreateOptions(indented: false); + JsonOptionsIndented = CreateOptions(indented: true); } } @@ -171,8 +132,8 @@ public static bool StrictHexFormat public static JsonSerializerOptions JsonOptionsIndented { get; private set; } = CreateOptions(indented: true); - private static readonly StreamPipeWriterOptions optionsLeaveOpen = new(pool: MemoryPool.Shared, minimumBufferSize: 16384, leaveOpen: true); - private static readonly StreamPipeWriterOptions options = new(pool: MemoryPool.Shared, minimumBufferSize: 16384, leaveOpen: false); + private static readonly StreamPipeWriterOptions optionsLeaveOpen = new(pool: MemoryPool.Shared, minimumBufferSize: 4096, leaveOpen: true); + private static readonly StreamPipeWriterOptions options = new(pool: MemoryPool.Shared, minimumBufferSize: 4096, leaveOpen: false); private static CountingStreamPipeWriter GetPipeWriter(Stream stream, bool leaveOpen) { @@ -183,7 +144,7 @@ public long Serialize(Stream stream, T value, bool indented = false, bool lea { var countingWriter = GetPipeWriter(stream, leaveOpen); using var writer = new Utf8JsonWriter(countingWriter, CreateWriterOptions(indented)); - JsonSerializer.Serialize(writer, value, GetSerializerOptions(indented)); + JsonSerializer.Serialize(writer, value, indented ? JsonOptionsIndented : _jsonOptions); countingWriter.Complete(); long outputCount = countingWriter.WrittenCount; @@ -200,7 +161,7 @@ private JsonWriterOptions CreateWriterOptions(bool indented) public async ValueTask SerializeAsync(Stream stream, T value, CancellationToken cancellationToken, bool indented = false, bool leaveOpen = true) { var writer = GetPipeWriter(stream, leaveOpen); - await JsonSerializer.SerializeAsync(writer, value, GetSerializerOptions(indented), cancellationToken); + await JsonSerializer.SerializeAsync(writer, value, indented ? JsonOptionsIndented : _jsonOptions, cancellationToken); await writer.CompleteAsync(); long outputCount = writer.WrittenCount; @@ -208,121 +169,12 @@ public async ValueTask SerializeAsync(Stream stream, T value, Cancellat } public Task SerializeAsync(PipeWriter writer, T value, bool indented = false) - { - using var jsonWriter = new Utf8JsonWriter((IBufferWriter)writer, CreateWriterOptions(indented)); - JsonSerializer.Serialize(jsonWriter, value, GetSerializerOptions(indented)); - return Task.CompletedTask; - } - - /// - /// Pre-serializes instances to warm System.Text.Json metadata caches at startup. - /// - public static void WarmupSerializer(params object[] instances) - { - foreach (object instance in instances) - { - _ = JsonSerializer.SerializeToUtf8Bytes(instance, instance.GetType(), JsonOptions); - } - } + => JsonSerializer.SerializeAsync(writer, value, indented ? JsonOptionsIndented : _jsonOptions); public static void SerializeToStream(Stream stream, T value, bool indented = false) { JsonSerializer.Serialize(stream, value, indented ? JsonOptionsIndented : JsonOptions); } - - private JsonSerializerOptions GetSerializerOptions(bool indented) - { - EnsureInstanceOptionsCurrent(); - return indented ? _jsonOptionsIndented : _jsonOptions; - } - - private void EnsureInstanceOptionsCurrent() - { - int currentVersion = Volatile.Read(ref _optionsVersion); - if (_instanceOptionsVersion == currentVersion) - { - return; - } - - lock (_instanceOptionsLock) - { - if (_instanceOptionsVersion != currentVersion) - { - RefreshInstanceOptions(); - } - } - } - - private void RefreshInstanceOptions() - { - _jsonOptions = CreateOptions(indented: false, instanceConverters: _instanceConverters, maxDepth: _maxDepth ?? DefaultMaxDepth); - _jsonOptionsIndented = CreateOptions(indented: true, instanceConverters: _instanceConverters, maxDepth: _maxDepth ?? DefaultMaxDepth); - _instanceOptionsVersion = Volatile.Read(ref _optionsVersion); - } - - private static void RefreshGlobalOptionsNoLock() - { - JsonOptions = CreateOptions(indented: false); - JsonOptionsIndented = CreateOptions(indented: true); - Interlocked.Increment(ref _optionsVersion); - } - - private static void SnapshotGlobalOptions(out bool strictHexFormat, out JsonConverter[] additionalConverters, out IJsonTypeInfoResolver[] additionalResolvers) - { - lock (_globalOptionsLock) - { - strictHexFormat = _strictHexFormat; - additionalConverters = new JsonConverter[_additionalConverters.Count]; - for (int i = 0; i < _additionalConverters.Count; i++) - { - additionalConverters[i] = _additionalConverters[i]; - } - - additionalResolvers = new IJsonTypeInfoResolver[_additionalResolvers.Count]; - for (int i = 0; i < _additionalResolvers.Count; i++) - { - additionalResolvers[i] = _additionalResolvers[i]; - } - } - } - - private static IJsonTypeInfoResolver BuildTypeInfoResolver(IReadOnlyList additionalResolvers) - { - int additionalResolversCount = additionalResolvers.Count; - if (additionalResolversCount == 0) - { - return new DefaultJsonTypeInfoResolver(); - } - - IJsonTypeInfoResolver[] resolverChain = new IJsonTypeInfoResolver[additionalResolversCount + 1]; - for (int i = 0; i < additionalResolversCount; i++) - { - resolverChain[i] = additionalResolvers[i]; - } - - resolverChain[additionalResolversCount] = new DefaultJsonTypeInfoResolver(); - return JsonTypeInfoResolver.Combine(resolverChain); - } - - private static JsonConverter[] CopyConverters(IEnumerable converters) - { - ArgumentNullException.ThrowIfNull(converters); - - if (converters is JsonConverter[] convertersArray) - { - JsonConverter[] clone = new JsonConverter[convertersArray.Length]; - Array.Copy(convertersArray, clone, convertersArray.Length); - return clone; - } - - List list = new(); - foreach (JsonConverter converter in converters) - { - list.Add(converter); - } - - return [.. list]; - } } public static class JsonElementExtensions diff --git a/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs b/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs index b6c0046f1b18..27d95de085e4 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs @@ -1,37 +1,13 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Runtime.CompilerServices; using System.Threading; namespace Nethermind.Serialization.Json; public static class ForcedNumberConversion { - public static readonly ThreadAwareAsyncLocal ForcedConversion = new(); + public static readonly AsyncLocal ForcedConversion = new(); - [ThreadStatic] - private static NumberConversion? _threadCache; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static NumberConversion GetFinalConversion() => _threadCache ?? NumberConversion.Hex; - - /// - /// Wrapper around AsyncLocal that also updates a ThreadStatic cache for fast reads. - /// - public sealed class ThreadAwareAsyncLocal - { - private readonly AsyncLocal _asyncLocal = new(); - - public NumberConversion? Value - { - get => _asyncLocal.Value; - set - { - _asyncLocal.Value = value; - _threadCache = value; - } - } - } + public static NumberConversion GetFinalConversion() => ForcedConversion.Value ?? NumberConversion.Hex; } diff --git a/src/Nethermind/Nethermind.Serialization.Json/Hash256Converter.cs b/src/Nethermind/Nethermind.Serialization.Json/Hash256Converter.cs index 6ff8b3c1eb3d..2f74049a80ff 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/Hash256Converter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/Hash256Converter.cs @@ -3,10 +3,8 @@ #nullable enable using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.Json; using Nethermind.Core.Crypto; namespace Nethermind.Serialization.Json; @@ -30,36 +28,12 @@ public Hash256Converter(bool strictHexFormat = false) return bytes is null ? null : new Hash256(bytes); } - [SkipLocalsInit] public override void Write( Utf8JsonWriter writer, Hash256 keccak, JsonSerializerOptions options) { - WriteHashHex(writer, in keccak.ValueHash256); - } - - /// - /// SIMD-accelerated hex encoding for 32-byte hashes. - /// Writes raw JSON (including quotes) via WriteRawValue to bypass the encoder entirely. - /// - [SkipLocalsInit] - internal static void WriteHashHex(Utf8JsonWriter writer, in ValueHash256 hash) - { - // Raw JSON: '"' + "0x" + 64 hex chars + '"' = 68 bytes - Unsafe.SkipInit(out HexWriter.HexBuffer72 rawBuf); - ref byte b = ref Unsafe.As(ref rawBuf); - - Unsafe.Add(ref b, 0) = (byte)'"'; - Unsafe.WriteUnaligned(ref Unsafe.Add(ref b, 1), (ushort)0x7830); // "0x" LE - - HexWriter.Encode32Bytes(ref Unsafe.Add(ref b, 3), hash.Bytes); - - Unsafe.Add(ref b, 67) = (byte)'"'; - - writer.WriteRawValue( - MemoryMarshal.CreateReadOnlySpan(ref b, 68), - skipInputValidation: true); + ByteArrayConverter.Convert(writer, keccak.Bytes, skipLeadingZeros: false); } // Methods needed to ser/de dictionary keys diff --git a/src/Nethermind/Nethermind.Serialization.Json/HexWriter.cs b/src/Nethermind/Nethermind.Serialization.Json/HexWriter.cs deleted file mode 100644 index ef0fbd2106fc..000000000000 --- a/src/Nethermind/Nethermind.Serialization.Json/HexWriter.cs +++ /dev/null @@ -1,428 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Buffers.Binary; -using System.IO.Pipelines; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using System.Text.Json; -using Nethermind.Int256; - -namespace Nethermind.Serialization.Json; - -/// -/// Shared low-level hex encoding primitives used by JSON converters. -/// -public static class HexWriter -{ - /// - /// Encode the low 8 bytes of a Vector128 to 16 hex chars using SSSE3 PSHUFB. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void Ssse3Encode8Bytes(ref byte dest, Vector128 input) - { - Vector128 hexLookup = Vector128.Create( - (byte)'0', (byte)'1', (byte)'2', (byte)'3', - (byte)'4', (byte)'5', (byte)'6', (byte)'7', - (byte)'8', (byte)'9', (byte)'a', (byte)'b', - (byte)'c', (byte)'d', (byte)'e', (byte)'f'); - Vector128 mask = Vector128.Create((byte)0x0F); - - Vector128 hi = Sse2.ShiftRightLogical(input.AsUInt16(), 4).AsByte() & mask; - Vector128 lo = input & mask; - Ssse3.Shuffle(hexLookup, Sse2.UnpackLow(hi, lo)).StoreUnsafe(ref dest); - } - - /// - /// Encode 16 bytes of a Vector128 to 32 hex chars using SSSE3 PSHUFB. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void Ssse3Encode16Bytes(ref byte dest, Vector128 input) - { - Vector128 hexLookup = Vector128.Create( - (byte)'0', (byte)'1', (byte)'2', (byte)'3', - (byte)'4', (byte)'5', (byte)'6', (byte)'7', - (byte)'8', (byte)'9', (byte)'a', (byte)'b', - (byte)'c', (byte)'d', (byte)'e', (byte)'f'); - Vector128 mask = Vector128.Create((byte)0x0F); - - Vector128 hi = Sse2.ShiftRightLogical(input.AsUInt16(), 4).AsByte() & mask; - Vector128 lo = input & mask; - Ssse3.Shuffle(hexLookup, Sse2.UnpackLow(hi, lo)).StoreUnsafe(ref dest); - Ssse3.Shuffle(hexLookup, Sse2.UnpackHigh(hi, lo)).StoreUnsafe(ref Unsafe.Add(ref dest, 16)); - } - - /// - /// Encode 32 bytes to 64 hex chars using AVX-512 VBMI cross-lane byte permutation. - /// vpermi2b does arbitrary byte interleave across the full 256-bit register in a single - /// instruction, eliminating the UnpackLow/UnpackHigh + lane-crossing overhead of SSSE3/AVX2. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void Avx512VbmiEncode32Bytes(ref byte dest, Vector256 input) - { - Vector256 mask = Vector256.Create((byte)0x0F); - Vector256 hi = Avx2.ShiftRightLogical(input.AsUInt16(), 4).AsByte() & mask; - Vector256 lo = input & mask; - - // vpermi2b: pick hi[i], lo[i] pairs across full 256-bit width - // indices 0-31 select from hi, 32-63 select from lo - Vector256 interleaved0 = Avx512Vbmi.VL.PermuteVar32x8x2(hi, - Vector256.Create( - (byte)0, 32, 1, 33, 2, 34, 3, 35, 4, 36, 5, 37, 6, 38, 7, 39, - 8, 40, 9, 41, 10, 42, 11, 43, 12, 44, 13, 45, 14, 46, 15, 47), lo); - - Vector256 interleaved1 = Avx512Vbmi.VL.PermuteVar32x8x2(hi, - Vector256.Create( - (byte)16, 48, 17, 49, 18, 50, 19, 51, 20, 52, 21, 53, 22, 54, 23, 55, - 24, 56, 25, 57, 26, 58, 27, 59, 28, 60, 29, 61, 30, 62, 31, 63), lo); - - // vpshufb: nibble-to-hex lookup (works within 128-bit lanes, lookup replicated in both) - Vector256 hexLookup = Vector256.Create( - (byte)'0', (byte)'1', (byte)'2', (byte)'3', - (byte)'4', (byte)'5', (byte)'6', (byte)'7', - (byte)'8', (byte)'9', (byte)'a', (byte)'b', - (byte)'c', (byte)'d', (byte)'e', (byte)'f', - (byte)'0', (byte)'1', (byte)'2', (byte)'3', - (byte)'4', (byte)'5', (byte)'6', (byte)'7', - (byte)'8', (byte)'9', (byte)'a', (byte)'b', - (byte)'c', (byte)'d', (byte)'e', (byte)'f'); - - Avx2.Shuffle(hexLookup, interleaved0).StoreUnsafe(ref dest); - Avx2.Shuffle(hexLookup, interleaved1).StoreUnsafe(ref Unsafe.Add(ref dest, 32)); - } - - /// - /// 512-byte lookup table: for byte value i, HexByteLookup[i*2] and [i*2+1] are the - /// two lowercase hex ASCII chars. Single indexed load + 16-bit store per byte, - /// replacing ~10 ALU ops of a branchless arithmetic approach. - /// - private static ReadOnlySpan HexByteLookup => - "000102030405060708090a0b0c0d0e0f"u8 + - "101112131415161718191a1b1c1d1e1f"u8 + - "202122232425262728292a2b2c2d2e2f"u8 + - "303132333435363738393a3b3c3d3e3f"u8 + - "404142434445464748494a4b4c4d4e4f"u8 + - "505152535455565758595a5b5c5d5e5f"u8 + - "606162636465666768696a6b6c6d6e6f"u8 + - "707172737475767778797a7b7c7d7e7f"u8 + - "808182838485868788898a8b8c8d8e8f"u8 + - "909192939495969798999a9b9c9d9e9f"u8 + - "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"u8 + - "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"u8 + - "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"u8 + - "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"u8 + - "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"u8 + - "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"u8; - - /// - /// Scalar: encode one byte to 2 hex chars via lookup table. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void EncodeByte(ref byte dest, int byteVal) - { - Unsafe.WriteUnaligned(ref dest, - Unsafe.ReadUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(HexByteLookup), byteVal * 2))); - } - - /// - /// Scalar: encode a ulong (big-endian byte order) to 16 hex chars. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void EncodeUlongScalar(ref byte dest, ulong value) - { - ref byte lookup = ref MemoryMarshal.GetReference(HexByteLookup); - for (int i = 0; i < 8; i++) - { - int byteVal = (int)(value >> ((7 - i) << 3)) & 0xFF; - Unsafe.WriteUnaligned(ref Unsafe.Add(ref dest, i * 2), - Unsafe.ReadUnaligned(ref Unsafe.Add(ref lookup, byteVal * 2))); - } - } - - /// - /// Scalar: encode a byte span to hex chars. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void EncodeBytesScalar(ref byte dest, ReadOnlySpan src) - { - ref byte lookup = ref MemoryMarshal.GetReference(HexByteLookup); - for (int i = 0; i < src.Length; i++) - { - Unsafe.WriteUnaligned(ref Unsafe.Add(ref dest, i * 2), - Unsafe.ReadUnaligned(ref Unsafe.Add(ref lookup, src[i] * 2))); - } - } - - /// - /// Encode a ulong to 16 hex chars, dispatching to SSSE3 or scalar. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void EncodeUlong(ref byte dest, ulong value) - { - if (Ssse3.IsSupported) - { - ulong be = BinaryPrimitives.ReverseEndianness(value); - Ssse3Encode8Bytes(ref dest, Vector128.CreateScalarUnsafe(be).AsByte()); - } - else - { - EncodeUlongScalar(ref dest, value); - } - } - - /// - /// Encode 32 bytes to 64 hex chars, dispatching to AVX-512 VBMI, SSSE3, or scalar. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void Encode32Bytes(ref byte dest, ReadOnlySpan src) - { - if (Avx512Vbmi.VL.IsSupported) - { - Avx512VbmiEncode32Bytes(ref dest, Vector256.LoadUnsafe(ref MemoryMarshal.GetReference(src))); - } - else if (Ssse3.IsSupported) - { - ref byte srcRef = ref MemoryMarshal.GetReference(src); - Ssse3Encode16Bytes(ref dest, Vector128.LoadUnsafe(ref srcRef)); - Ssse3Encode16Bytes(ref Unsafe.Add(ref dest, 32), Vector128.LoadUnsafe(ref srcRef, 16)); - } - else - { - EncodeBytesScalar(ref dest, src); - } - } - - /// - /// Write a non-zero ulong as a hex JSON string value ("0x...") using WriteRawValue. - /// Used by LongConverter and ULongConverter. - /// - [SkipLocalsInit] - internal static void WriteUlongHexRawValue(Utf8JsonWriter writer, ulong value) - { - // Use InlineArray to avoid GS cookie overhead from stackalloc - Unsafe.SkipInit(out HexBuffer24 rawBuf); - ref byte b = ref Unsafe.As(ref rawBuf); - - EncodeUlong(ref Unsafe.Add(ref b, 3), value); - - // nibbleCount: ceil(significantBits / 4), guaranteed >= 1 since value != 0 - // nint keeps Unsafe.Add in 64-bit register arithmetic, avoiding movsxd - nint nibbleCount = (nint)((67 - (uint)BitOperations.LeadingZeroCount(value)) >> 2); - nint spanStart = 16 - nibbleCount; - - ref byte spanRef = ref Unsafe.Add(ref b, spanStart); - spanRef = (byte)'"'; - Unsafe.WriteUnaligned(ref Unsafe.Add(ref spanRef, 1), (ushort)0x7830); // "0x" LE - Unsafe.Add(ref b, 19) = (byte)'"'; - - writer.WriteRawValue( - MemoryMarshal.CreateReadOnlySpan(ref spanRef, (int)nibbleCount + 4), - skipInputValidation: true); - } - - /// - /// Write a UInt256 as a hex JSON string value ("0x...") using WriteRawValue. - /// - [SkipLocalsInit] - internal static void WriteUInt256HexRawValue(Utf8JsonWriter writer, UInt256 value, bool zeroPadded = false) - { - Unsafe.SkipInit(out HexBuffer72 rawBuf); - ref byte buffer = ref Unsafe.As(ref rawBuf); - - BuildUInt256Hex(ref buffer, value, includeQuotes: true, zeroPadded, out nint spanStart, out int spanLength); - - writer.WriteRawValue( - MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref buffer, spanStart), spanLength), - skipInputValidation: true); - } - - /// - /// Write a UInt256 as a hex property name ("0x..."). - /// - [SkipLocalsInit] - internal static void WriteUInt256HexPropertyName(Utf8JsonWriter writer, UInt256 value, bool zeroPadded = false) - { - Unsafe.SkipInit(out HexBuffer72 rawBuf); - ref byte buffer = ref Unsafe.As(ref rawBuf); - - BuildUInt256Hex(ref buffer, value, includeQuotes: false, zeroPadded, out nint spanStart, out int spanLength); - - writer.WritePropertyName( - MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref buffer, spanStart), spanLength)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void BuildUInt256Hex(ref byte buffer, UInt256 value, bool includeQuotes, bool zeroPadded, out nint spanStart, out int spanLength) - { - nint hexOffset = includeQuotes ? 3 : 2; - EncodeUInt256Hex(ref Unsafe.Add(ref buffer, hexOffset), value); - - int nibbleCount = zeroPadded ? 64 : GetSignificantNibbleCount(value); - spanStart = zeroPadded ? 0 : 64 - nibbleCount; - ref byte spanRef = ref Unsafe.Add(ref buffer, spanStart); - - if (includeQuotes) - { - spanRef = (byte)'"'; - Unsafe.WriteUnaligned(ref Unsafe.Add(ref spanRef, 1), (ushort)0x7830); // "0x" LE - Unsafe.Add(ref spanRef, nibbleCount + 3) = (byte)'"'; - spanLength = nibbleCount + 4; - } - else - { - Unsafe.WriteUnaligned(ref spanRef, (ushort)0x7830); // "0x" LE - spanLength = nibbleCount + 2; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetSignificantNibbleCount(UInt256 value) - { - int leadingZeroBits; - if (value.u3 != 0) - { - leadingZeroBits = BitOperations.LeadingZeroCount(value.u3); - } - else if (value.u2 != 0) - { - leadingZeroBits = 64 + BitOperations.LeadingZeroCount(value.u2); - } - else if (value.u1 != 0) - { - leadingZeroBits = 128 + BitOperations.LeadingZeroCount(value.u1); - } - else - { - leadingZeroBits = 192 + BitOperations.LeadingZeroCount(value.u0); - } - - int nibbleCount = (259 - leadingZeroBits) >> 2; - return nibbleCount == 0 ? 1 : nibbleCount; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void EncodeUInt256Hex(ref byte dest, UInt256 value) - { - if (Avx512Vbmi.VL.IsSupported) - { - Vector256 reversed = Avx512Vbmi.VL.PermuteVar32x8( - Vector256.LoadUnsafe(ref Unsafe.As(ref value)), - Vector256.Create( - (byte)31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, - 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)); - Avx512VbmiEncode32Bytes(ref dest, reversed); - } - else if (Ssse3.IsSupported) - { - Ssse3Encode16Bytes(ref dest, - Vector128.Create( - BinaryPrimitives.ReverseEndianness(value.u3), - BinaryPrimitives.ReverseEndianness(value.u2)).AsByte()); - - Ssse3Encode16Bytes(ref Unsafe.Add(ref dest, 32), - Vector128.Create( - BinaryPrimitives.ReverseEndianness(value.u1), - BinaryPrimitives.ReverseEndianness(value.u0)).AsByte()); - } - else - { - EncodeUlongScalar(ref dest, value.u3); - EncodeUlongScalar(ref Unsafe.Add(ref dest, 16), value.u2); - EncodeUlongScalar(ref Unsafe.Add(ref dest, 32), value.u1); - EncodeUlongScalar(ref Unsafe.Add(ref dest, 48), value.u0); - } - } - - /// - /// 24-byte inline buffer for ulong hex encoding (20 bytes needed, rounded up to - /// 3 x 8-byte ulong elements for alignment). Used instead of stackalloc to avoid - /// GS cookie (stack canary) overhead. The JIT inserts a cookie write in the prologue - /// and a verify + CORINFO_HELP_FAIL_FAST call in the epilogue for every stackalloc - /// buffer, adding ~35 bytes per method. Inline array structs are treated as regular - /// locals and avoid this. - /// - [InlineArray(3)] - private struct HexBuffer24 - { - private ulong _element0; - } - - /// - /// 72-byte inline buffer for hash/UInt256 hex encoding (68 bytes needed, rounded up - /// to 9 x 8-byte ulong elements for alignment). Used instead of stackalloc to avoid - /// GS cookie (stack canary) overhead. The JIT inserts a cookie write in the prologue - /// and a verify + CORINFO_HELP_FAIL_FAST call in the epilogue for every stackalloc - /// buffer, adding ~35 bytes per method. Inline array structs are treated as regular - /// locals and avoid this. - /// - [InlineArray(9)] - internal struct HexBuffer72 - { - private ulong _element0; - } - - private const int MaxHexRequest = 4096; - - /// - /// Writes a large byte array as hex directly into a - /// in chunks, bounded by the actual span size returned by GetSpan. - /// - public static void WriteHexChunked(PipeWriter writer, byte[] data) - { - ReadOnlySpan remaining = data; - while (remaining.Length > 0) - { - Span hex = writer.GetSpan(Math.Min(remaining.Length * 2, MaxHexRequest)); - int inputLen = Math.Min(remaining.Length, hex.Length / 2); - EncodeToHex(remaining[..inputLen], ref MemoryMarshal.GetReference(hex)); - writer.Advance(inputLen * 2); - - remaining = remaining[inputLen..]; - } - } - - /// - /// Writes a small byte array as hex in a single span into a . - /// - public static void WriteHexSmall(PipeWriter writer, byte[] data) - { - int hexLen = data.Length * 2; - Span hex = writer.GetSpan(hexLen); - int inputLen = Math.Min(data.Length, hex.Length / 2); - EncodeToHex(((ReadOnlySpan)data)[..inputLen], ref MemoryMarshal.GetReference(hex)); - writer.Advance(inputLen * 2); - } - - /// - /// Encode arbitrary-length bytes to hex using SIMD (AVX-512 VBMI / SSSE3) with scalar tail. - /// - private static void EncodeToHex(ReadOnlySpan src, ref byte dest) - { - int offset = 0; - - // 32-byte blocks: AVX-512 VBMI or 2x SSSE3 or scalar - while (offset + 32 <= src.Length) - { - Encode32Bytes(ref Unsafe.Add(ref dest, offset * 2), src.Slice(offset, 32)); - offset += 32; - } - - // 16-byte block via SSSE3 - if (Ssse3.IsSupported && offset + 16 <= src.Length) - { - Ssse3Encode16Bytes(ref Unsafe.Add(ref dest, offset * 2), - Vector128.LoadUnsafe(ref Unsafe.Add(ref MemoryMarshal.GetReference(src), offset))); - offset += 16; - } - - // Scalar tail - if (offset < src.Length) - { - EncodeBytesScalar(ref Unsafe.Add(ref dest, offset * 2), src[offset..]); - } - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs index a2ecd66fc590..7b46c9e2dc3d 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs @@ -2,141 +2,142 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; -using System.Buffers.Text; using System.Globalization; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Text.Json; -using System.Text.Json.Serialization; -using Nethermind.Core.Extensions; -namespace Nethermind.Serialization.Json; - -public class LongConverter : JsonConverter +namespace Nethermind.Serialization.Json { - public static long FromString(string s) + using Nethermind.Core.Extensions; + using System.Buffers; + using System.Buffers.Binary; + using System.Buffers.Text; + using System.Runtime.CompilerServices; + using System.Text.Json; + using System.Text.Json.Serialization; + + public class LongConverter : JsonConverter { - if (s is null) - { - throw new JsonException("null cannot be assigned to long"); - } - - if (s == Bytes.ZeroHexValue) + public static long FromString(string s) { - return 0L; - } - - if (s.StartsWith("0x0")) - { - return long.Parse(s.AsSpan(2), NumberStyles.AllowHexSpecifier); - } + if (s is null) + { + throw new JsonException("null cannot be assigned to long"); + } - if (s.StartsWith("0x")) - { - Span withZero = new(new char[s.Length - 1]); - withZero[0] = '0'; - s.AsSpan(2).CopyTo(withZero[1..]); - return long.Parse(withZero, NumberStyles.AllowHexSpecifier); - } + if (s == Bytes.ZeroHexValue) + { + return 0L; + } - return long.Parse(s, NumberStyles.Integer); - } + if (s.StartsWith("0x0")) + { + return long.Parse(s.AsSpan(2), NumberStyles.AllowHexSpecifier); + } - public override long ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - ReadOnlySpan hex = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; - return FromString(hex); - } + if (s.StartsWith("0x")) + { + Span withZero = new(new char[s.Length - 1]); + withZero[0] = '0'; + s.AsSpan(2).CopyTo(withZero[1..]); + return long.Parse(withZero, NumberStyles.AllowHexSpecifier); + } - public static long FromString(ReadOnlySpan s) - { - if (s.Length == 0) - { - throw new JsonException("null cannot be assigned to long"); + return long.Parse(s, NumberStyles.Integer); } - if (s.SequenceEqual("0x0"u8)) + public override long ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return 0L; + ReadOnlySpan hex = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; + return FromString(hex); } - long value; - if (s.StartsWith("0x"u8)) + public static long FromString(ReadOnlySpan s) { - s = s[2..]; - if (Utf8Parser.TryParse(s, out value, out _, 'x')) + if (s.Length == 0) { - return value; + throw new JsonException("null cannot be assigned to long"); } - } - else if (Utf8Parser.TryParse(s, out value, out _)) - { - return value; - } - ThrowJsonException(); - return default; + if (s.SequenceEqual("0x0"u8)) + { + return 0L; + } - [DoesNotReturn, StackTraceHidden] - static void ThrowJsonException() => throw new JsonException("hex to long"); - } + long value; + if (s.StartsWith("0x"u8)) + { + s = s[2..]; + if (Utf8Parser.TryParse(s, out value, out _, 'x')) + { + return value; + } + } + else if (Utf8Parser.TryParse(s, out value, out _)) + { + return value; + } - internal static long ReadCore(ref Utf8JsonReader reader) - { - if (reader.TokenType == JsonTokenType.Number) - { - return reader.GetInt64(); + throw new JsonException("hex to long"); } - else if (reader.TokenType == JsonTokenType.String) - { - return !reader.HasValueSequence - ? FromString(reader.ValueSpan) - : FromString(reader.ValueSequence.ToArray()); - } - - ThrowJsonException(); - return default; - - [DoesNotReturn, StackTraceHidden] - static void ThrowJsonException() => throw new JsonException(); - } - - public override long Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) - { - return ReadCore(ref reader); - } - [SkipLocalsInit] - public override void Write( - Utf8JsonWriter writer, - long value, - JsonSerializerOptions options) - { - switch (ForcedNumberConversion.GetFinalConversion()) + internal static long ReadCore(ref Utf8JsonReader reader) { - case NumberConversion.Hex: - if (value == 0) + if (reader.TokenType == JsonTokenType.Number) + { + return reader.GetInt64(); + } + else if (reader.TokenType == JsonTokenType.String) + { + if (!reader.HasValueSequence) { - writer.WriteStringValue("0x0"u8); + return FromString(reader.ValueSpan); } else { - HexWriter.WriteUlongHexRawValue(writer, (ulong)value); + return FromString(reader.ValueSequence.ToArray()); } - break; - case NumberConversion.Decimal: - writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); - break; - case NumberConversion.Raw: - writer.WriteNumberValue(value); - break; - default: - throw new NotSupportedException(); + } + + throw new JsonException(); + } + + public override long Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + return ReadCore(ref reader); + } + + [SkipLocalsInit] + public override void Write( + Utf8JsonWriter writer, + long value, + JsonSerializerOptions options) + { + switch (ForcedNumberConversion.GetFinalConversion()) + { + case NumberConversion.Hex: + if (value == 0) + { + writer.WriteRawValue("\"0x0\""u8, skipInputValidation: true); + } + else + { + Span bytes = stackalloc byte[8]; + BinaryPrimitives.WriteInt64BigEndian(bytes, value); + ByteArrayConverter.Convert(writer, bytes, skipLeadingZeros: true); + } + break; + case NumberConversion.Decimal: + writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); + break; + case NumberConversion.Raw: + writer.WriteNumberValue(value); + break; + default: + throw new NotSupportedException(); + + } } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs index 0ca57dd9f5c6..9d023cdcf66f 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs @@ -2,39 +2,41 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Text.Json; -using System.Text.Json.Serialization; -namespace Nethermind.Serialization.Json; - -public class NullableULongConverter : JsonConverter +namespace Nethermind.Serialization.Json { - private readonly ULongConverter _converter = new(); + using System.Text.Json; + using System.Text.Json.Serialization; - public override ulong? Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) + public class NullableULongConverter : JsonConverter { - if (reader.TokenType == JsonTokenType.Null) + private readonly ULongConverter _converter = new(); + + public override ulong? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { - return null; - } + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } - return _converter.Read(ref reader, typeToConvert, options); - } + return _converter.Read(ref reader, typeToConvert, options); + } - public override void Write( - Utf8JsonWriter writer, - ulong? value, - JsonSerializerOptions options) - { - if (!value.HasValue) + public override void Write( + Utf8JsonWriter writer, + ulong? value, + JsonSerializerOptions options) { - writer.WriteNullValue(); - return; - } + if (!value.HasValue) + { + writer.WriteNullValue(); + return; + } - _converter.Write(writer, value.GetValueOrDefault(), options); + _converter.Write(writer, value.GetValueOrDefault(), options); + } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/PubSub/LogPublisher.cs b/src/Nethermind/Nethermind.Serialization.Json/PubSub/LogPublisher.cs new file mode 100644 index 000000000000..75a0807cca7d --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/PubSub/LogPublisher.cs @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading.Tasks; +using Nethermind.Core.PubSub; +using Nethermind.Logging; + +namespace Nethermind.Serialization.Json.PubSub +{ + public class LogPublisher : IPublisher + { + private readonly ILogger _logger; + private readonly IJsonSerializer _jsonSerializer; + + public LogPublisher(IJsonSerializer jsonSerializer, ILogManager logManager) + { + _logger = logManager.GetClassLogger(); + _jsonSerializer = jsonSerializer; + } + + public Task PublishAsync(T data) where T : class + { + if (_logger.IsInfo) _logger.Info(_jsonSerializer.Serialize(data)); + return Task.CompletedTask; + } + + public void Dispose() + { + } + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs b/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs index 611e64a0affc..336fd38d6d69 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs @@ -99,16 +99,28 @@ public static UInt256 ReadHex(ReadOnlySpan hex) return new UInt256(in readOnlyBytes, isBigEndian: true); } + [SkipLocalsInit] public override void Write( Utf8JsonWriter writer, UInt256 value, JsonSerializerOptions options) { - NumberConversion conversion = ForcedNumberConversion.GetFinalConversion(); - switch (conversion) + NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); + if (value.IsZero) + { + writer.WriteRawValue(usedConversion == NumberConversion.ZeroPaddedHex + ? "\"0x0000000000000000000000000000000000000000000000000000000000000000\""u8 + : "\"0x0\""u8); + return; + } + switch (usedConversion) { case NumberConversion.Hex: - HexWriter.WriteUInt256HexRawValue(writer, value); + { + Span bytes = stackalloc byte[32]; + value.ToBigEndian(bytes); + ByteArrayConverter.Convert(writer, bytes); + } break; case NumberConversion.Decimal: writer.WriteRawValue(value.ToString(CultureInfo.InvariantCulture)); @@ -117,23 +129,35 @@ public override void Write( writer.WriteStringValue(((BigInteger)value).ToString(CultureInfo.InvariantCulture)); break; case NumberConversion.ZeroPaddedHex: - HexWriter.WriteUInt256HexRawValue(writer, value, zeroPadded: true); + { + Span bytes = stackalloc byte[32]; + value.ToBigEndian(bytes); + ByteArrayConverter.Convert(writer, bytes, skipLeadingZeros: false); + } break; default: - throw new NotSupportedException($"{conversion} format is not supported for {nameof(UInt256)}"); + throw new NotSupportedException($"{usedConversion} format is not supported for {nameof(UInt256)}"); } } public override UInt256 ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => ReadInternal(ref reader, JsonTokenType.PropertyName); + [SkipLocalsInit] public override void WriteAsPropertyName(Utf8JsonWriter writer, UInt256 value, JsonSerializerOptions options) { - NumberConversion conversion = ForcedNumberConversion.GetFinalConversion(); - switch (conversion) + NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); + if (value.IsZero) + { + writer.WritePropertyName(usedConversion == NumberConversion.ZeroPaddedHex + ? "0x0000000000000000000000000000000000000000000000000000000000000000"u8 + : "0x0"u8); + return; + } + switch (usedConversion) { case NumberConversion.Hex: - HexWriter.WriteUInt256HexPropertyName(writer, value); + WriteHexPropertyName(writer, value, false); break; case NumberConversion.Decimal: writer.WritePropertyName(value.ToString(CultureInfo.InvariantCulture)); @@ -142,13 +166,25 @@ public override void WriteAsPropertyName(Utf8JsonWriter writer, UInt256 value, J writer.WritePropertyName(((BigInteger)value).ToString(CultureInfo.InvariantCulture)); break; case NumberConversion.ZeroPaddedHex: - HexWriter.WriteUInt256HexPropertyName(writer, value, zeroPadded: true); + WriteHexPropertyName(writer, value, true); break; default: - throw new NotSupportedException($"{conversion} format is not supported for {nameof(UInt256)}"); + throw new NotSupportedException($"{usedConversion} format is not supported for {nameof(UInt256)}"); } } + private static void WriteHexPropertyName(Utf8JsonWriter writer, UInt256 value, bool isZeroPadded) + { + Span bytes = stackalloc byte[32]; + value.ToBigEndian(bytes); + ByteArrayConverter.Convert( + writer, + bytes, + static (w, h) => w.WritePropertyName(h), + skipLeadingZeros: !isZeroPadded, + addQuotations: false); + } + [DoesNotReturn, StackTraceHidden] private static void ThrowJsonException() { diff --git a/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs index d9f672dedf9e..e0fb0988f5e8 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs @@ -2,108 +2,109 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; -using System.Buffers.Text; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Runtime.CompilerServices; -using System.Text.Json; -using System.Text.Json.Serialization; -namespace Nethermind.Serialization.Json; - -public class ULongConverter : JsonConverter +namespace Nethermind.Serialization.Json { - public static ulong FromString(ReadOnlySpan s) + using System.Buffers; + using System.Buffers.Binary; + using System.Buffers.Text; + using System.Globalization; + using System.Runtime.CompilerServices; + using System.Text.Json; + using System.Text.Json.Serialization; + + public class ULongConverter : JsonConverter { - if (s.Length == 0) + public static ulong FromString(ReadOnlySpan s) { - throw new JsonException("null cannot be assigned to ulong"); - } + if (s.Length == 0) + { + throw new JsonException("null cannot be assigned to ulong"); + } - if (s.SequenceEqual("0x0"u8)) - { - return 0uL; - } + if (s.SequenceEqual("0x0"u8)) + { + return 0uL; + } - ulong value; - if (s.StartsWith("0x"u8)) - { - s = s[2..]; - if (Utf8Parser.TryParse(s, out value, out _, 'x')) + ulong value; + if (s.StartsWith("0x"u8)) + { + s = s[2..]; + if (Utf8Parser.TryParse(s, out value, out _, 'x')) + { + return value; + } + } + else if (Utf8Parser.TryParse(s, out value, out _)) { return value; } + + throw new JsonException("hex to long"); } - else if (Utf8Parser.TryParse(s, out value, out _)) + + [SkipLocalsInit] + public override void Write( + Utf8JsonWriter writer, + ulong value, + JsonSerializerOptions options) { - return value; + NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); + switch (usedConversion) + { + case NumberConversion.Hex: + { + if (value == 0) + { + writer.WriteRawValue("\"0x0\""u8, skipInputValidation: true); + } + else + { + Span bytes = stackalloc byte[8]; + BinaryPrimitives.WriteUInt64BigEndian(bytes, value); + ByteArrayConverter.Convert(writer, bytes, skipLeadingZeros: true); + } + break; + } + case NumberConversion.Decimal: + writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); + break; + case NumberConversion.Raw: + writer.WriteNumberValue(value); + break; + default: + throw new NotSupportedException(); + } } - - ThrowJsonException(); - return default; - - [DoesNotReturn, StackTraceHidden] - static void ThrowJsonException() => throw new JsonException("hex to long"); - } - - [SkipLocalsInit] - public override void Write( - Utf8JsonWriter writer, - ulong value, - JsonSerializerOptions options) - { - NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); - switch (usedConversion) + internal static ulong ReadCore(ref Utf8JsonReader reader) { - case NumberConversion.Hex: - if (value == 0) + if (reader.TokenType == JsonTokenType.Number) + { + return reader.GetUInt64(); + } + if (reader.TokenType == JsonTokenType.String) + { + if (!reader.HasValueSequence) { - writer.WriteStringValue("0x0"u8); + return FromString(reader.ValueSpan); } else { - HexWriter.WriteUlongHexRawValue(writer, value); + return FromString(reader.ValueSequence.ToArray()); } - break; - case NumberConversion.Decimal: - writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); - break; - case NumberConversion.Raw: - writer.WriteNumberValue(value); - break; - default: - throw new NotSupportedException(); - } - } + } - internal static ulong ReadCore(ref Utf8JsonReader reader) - { - if (reader.TokenType == JsonTokenType.Number) - { - return reader.GetUInt64(); + throw new JsonException(); } - if (reader.TokenType == JsonTokenType.String) + + public override ulong Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { - return !reader.HasValueSequence - ? FromString(reader.ValueSpan) - : FromString(reader.ValueSequence.ToArray()); + return ReadCore(ref reader); } - - ThrowJsonException(); - return default; - - [DoesNotReturn, StackTraceHidden] - static void ThrowJsonException() => throw new JsonException(); - } - - public override ulong Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) - { - return ReadCore(ref reader); } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/ValueHash256Converter.cs b/src/Nethermind/Nethermind.Serialization.Json/ValueHash256Converter.cs index c38cd342cba6..18282391b58d 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ValueHash256Converter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ValueHash256Converter.cs @@ -3,7 +3,6 @@ #nullable enable using System; -using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Serialization; using Nethermind.Core.Crypto; @@ -28,12 +27,11 @@ public override ValueHash256 Read( return bytes is null ? null : new ValueHash256(bytes); } - [SkipLocalsInit] public override void Write( Utf8JsonWriter writer, ValueHash256 keccak, JsonSerializerOptions options) { - Hash256Converter.WriteHashHex(writer, in keccak); + ByteArrayConverter.Convert(writer, keccak.Bytes, skipLeadingZeros: false); } } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs index 90c559c1ab62..136035521590 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs @@ -40,12 +40,6 @@ public BlockDecoder() : this(new HeaderDecoder()) { } (int txs, int uncles, int? withdrawals) = _blockBodyDecoder.GetBodyComponentLength(item.Body); - byte[][]? encodedTxs = item.EncodedTransactions; - if (encodedTxs is not null) - { - txs = GetPreEncodedTxLength(item.Transactions, encodedTxs); - } - int contentLength = headerLength + Rlp.LengthOfSequence(txs) + @@ -54,16 +48,6 @@ public BlockDecoder() : this(new HeaderDecoder()) { } return (contentLength, txs, uncles, withdrawals); } - private static int GetPreEncodedTxLength(Transaction[] txs, byte[][] encodedTxs) - { - int sum = 0; - for (int i = 0; i < encodedTxs.Length; i++) - { - sum += TxDecoder.GetWrappedTxLength(txs[i].Type, encodedTxs[i].Length); - } - return sum; - } - public override int GetLength(Block? item, RlpBehaviors rlpBehaviors) { if (item is null) @@ -120,21 +104,9 @@ public override void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehav stream.StartSequence(contentLength); _headerDecoder.Encode(stream, item.Header); stream.StartSequence(txsLength); - - byte[][]? encodedTxs = item.EncodedTransactions; - if (encodedTxs is not null) + for (int i = 0; i < item.Transactions.Length; i++) { - for (int i = 0; i < encodedTxs.Length; i++) - { - TxDecoder.WriteWrappedFormat(stream, item.Transactions[i].Type, encodedTxs[i]); - } - } - else - { - for (int i = 0; i < item.Transactions.Length; i++) - { - stream.Encode(item.Transactions[i]); - } + stream.Encode(item.Transactions[i]); } stream.StartSequence(unclesLength); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs index c523cc2555e3..b16af62649d5 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs @@ -328,7 +328,8 @@ public static Rlp Encode(T item, RlpBehaviors behaviors = RlpBehaviors.None) { if (item is Rlp rlp) { - return rlp; + RlpStream stream = new(LengthOfSequence(rlp.Length)); + return new(stream.Data.ToArray()); } IRlpStreamDecoder? rlpStreamDecoder = GetStreamDecoder(); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs index 22d063b13661..bf3174dd9a74 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs @@ -24,25 +24,6 @@ static TxDecoder() Instance = new TxDecoder(static () => TxObjectPool.Get()); Rlp.RegisterDecoder(typeof(Transaction), Instance); } - - /// - /// Gets the block-format length of a pre-encoded CL-format transaction. - /// Legacy txs use the same format; typed txs are wrapped in an RLP byte string. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetWrappedTxLength(TxType type, int clEncodedLength) - => type == TxType.Legacy ? clEncodedLength : Rlp.LengthOfSequence(clEncodedLength); - - /// - /// Writes a pre-encoded CL-format transaction in block format. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteWrappedFormat(RlpStream stream, TxType type, byte[] clEncoded) - { - if (type != TxType.Legacy) - stream.StartByteArray(clEncoded.Length, false); - stream.Write(clEncoded); - } } public sealed class SystemTxDecoder : TxDecoder; diff --git a/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.BasicTypes.cs b/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.BasicTypes.cs index 41bd474e5c5a..f95471e7864e 100644 --- a/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.BasicTypes.cs +++ b/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.BasicTypes.cs @@ -15,8 +15,6 @@ namespace Nethermind.Serialization.Ssz; /// public static partial class Ssz { - private const int VarOffsetSize = sizeof(uint); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(Span span, byte[] value, ref int offset) { diff --git a/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.Containers.cs b/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.Containers.cs new file mode 100644 index 000000000000..3055b6038a62 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.Containers.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Serialization.Ssz; + +public partial class Ssz +{ + private const int VarOffsetSize = sizeof(uint); + + private static void DecodeDynamicOffset(ReadOnlySpan span, ref int offset, out int dynamicOffset) + { + dynamicOffset = (int)DecodeUInt(span.Slice(offset, VarOffsetSize)); + offset += sizeof(uint); + } + +} diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs index c50e687cbce7..5bd3e4fc9a97 100644 --- a/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs @@ -115,9 +115,7 @@ public async Task Can_increment_metric_on_missed_keys() time += (long)ShutterTestsCommon.SlotLength.TotalSeconds; } - // ImproveBlock tasks run in the background and may not have completed yet - // when GetPayload returns (it only waits 50ms), so poll until all increments land. - Assert.That(() => Metrics.ShutterKeysMissed, Is.EqualTo((ulong)5).After(5000, 50)); + Assert.That(Metrics.ShutterKeysMissed, Is.EqualTo(5)); } } diff --git a/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs b/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs index 3ec0ba3c2cce..d80eebe4be39 100644 --- a/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs @@ -433,7 +433,7 @@ public void Selfdestruct_clears_cache() WorldState provider = BuildStorageProvider(ctx); StorageCell accessedStorageCell = new StorageCell(TestItem.AddressA, 1); StorageCell nonAccessedStorageCell = new StorageCell(TestItem.AddressA, 2); - preBlockCaches.StorageCache.Set(accessedStorageCell, [1, 2, 3]); + preBlockCaches.StorageCache[accessedStorageCell] = [1, 2, 3]; provider.Get(accessedStorageCell); provider.Commit(Paris.Instance); provider.ClearStorage(TestItem.AddressA); @@ -602,7 +602,7 @@ public void Selfdestruct_persist_between_commit() PreBlockCaches preBlockCaches = new PreBlockCaches(); Context ctx = new(preBlockCaches); StorageCell accessedStorageCell = new StorageCell(TestItem.AddressA, 1); - preBlockCaches.StorageCache.Set(accessedStorageCell, [1, 2, 3]); + preBlockCaches.StorageCache[accessedStorageCell] = [1, 2, 3]; WorldState provider = BuildStorageProvider(ctx); provider.Get(accessedStorageCell).ToArray().Should().BeEquivalentTo([1, 2, 3]); @@ -611,25 +611,6 @@ public void Selfdestruct_persist_between_commit() provider.Get(accessedStorageCell).ToArray().Should().BeEquivalentTo(StorageTree.ZeroBytes); } - [Test] - public void Eip161_empty_account_with_storage_does_not_throw_on_commit() - { - IWorldState worldState = new WorldState( - new TrieStoreScopeProvider(TestTrieStoreFactory.Build(new MemDb(), LimboLogs.Instance), new MemDb(), LimboLogs.Instance), LogManager); - - using var disposable = worldState.BeginScope(IWorldState.PreGenesis); - - // Create an empty account (balance=0, nonce=0, no code) and set storage on it. - // EIP-161 (via SpuriousDragon+) deletes empty accounts during commit, but the - // storage flush has already produced a non-empty storage root. The commit must - // handle this gracefully by skipping the storage root update for deleted accounts. - worldState.CreateAccount(TestItem.AddressA, 0); - worldState.Set(new StorageCell(TestItem.AddressA, 1), [1, 2, 3]); - worldState.Commit(SpuriousDragon.Instance); - - worldState.AccountExists(TestItem.AddressA).Should().BeFalse(); - } - [TestCase(2)] [TestCase(1000)] public void Set_empty_value_for_storage_cell_without_read_clears_data(int numItems) diff --git a/src/Nethermind/Nethermind.State.Test/TrieStoreScopeProviderTests.cs b/src/Nethermind/Nethermind.State.Test/TrieStoreScopeProviderTests.cs index 6338d4340452..d3742eec32b1 100644 --- a/src/Nethermind/Nethermind.State.Test/TrieStoreScopeProviderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/TrieStoreScopeProviderTests.cs @@ -96,26 +96,4 @@ public void Test_CanSaveToCode() codeKv.WritesCount.Should().Be(1); } - - [Test] - public void Test_NullAccountWithNonEmptyStorageDoesNotThrow() - { - TestMemDb kv = new TestMemDb(); - IWorldStateScopeProvider scopeProvider = new TrieStoreScopeProvider(new TestRawTrieStore(kv), new MemDb(), LimboLogs.Instance); - - using var scope = scopeProvider.BeginScope(null); - - // Simulates the EIP-161 scenario: storage is flushed for an account that was - // then deleted (set to null) during state commit. The write batch Dispose should - // skip the storage root update for the deleted account instead of throwing. - using (var writeBatch = scope.StartWriteBatch(1)) - { - using (var storageSet = writeBatch.CreateStorageWriteBatch(TestItem.AddressA, 1)) - { - storageSet.Set(1, [1, 2, 3]); - } - - writeBatch.Set(TestItem.AddressA, null); - } - } } diff --git a/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs b/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs index 7b1a1da5dd5a..d8ac3c455f1d 100644 --- a/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs @@ -15,10 +15,10 @@ namespace Nethermind.State.OverridableEnv; public class OverridableCodeInfoRepository(ICodeInfoRepository codeInfoRepository, IWorldState worldState) : IOverridableCodeInfoRepository { - private readonly Dictionary _codeOverrides = new(); - private readonly Dictionary _precompileOverrides = new(); + private readonly Dictionary _codeOverrides = new(); + private readonly Dictionary _precompileOverrides = new(); - public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) + public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) { delegationAddress = null; if (_precompileOverrides.TryGetValue(codeSource, out var precompile)) return precompile.codeInfo; @@ -41,9 +41,9 @@ public void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpe public void SetCodeOverride( IReleaseSpec vmSpec, Address key, - CodeInfo value) + ICodeInfo value) { - _codeOverrides[key] = (value, ValueKeccak.Compute(value.Code.Span)); + _codeOverrides[key] = (value, ValueKeccak.Compute(value.CodeSpan)); } public void MovePrecompile(IReleaseSpec vmSpec, Address precompileAddr, Address targetAddr) diff --git a/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs b/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs index 85bf38a4e6ca..f74d9a3f5d46 100644 --- a/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs +++ b/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs @@ -452,6 +452,7 @@ public void UnmarkClear() private sealed class PerContractState : IReturnable { + private static readonly Func _loadFromTreeStorageFunc = LoadFromTreeStorage; private IWorldStateScopeProvider.IStorageTree? _backend; private readonly DefaultableDictionary BlockChange = new(); diff --git a/src/Nethermind/Nethermind.State/PreBlockCaches.cs b/src/Nethermind/Nethermind.State/PreBlockCaches.cs index e7f8bf290735..3fd8cf5e8cd7 100644 --- a/src/Nethermind/Nethermind.State/PreBlockCaches.cs +++ b/src/Nethermind/Nethermind.State/PreBlockCaches.cs @@ -19,24 +19,24 @@ public class PreBlockCaches private readonly Func[] _clearCaches; - private readonly SeqlockCache _storageCache = new(); - private readonly SeqlockCache _stateCache = new(); - private readonly SeqlockCache _rlpCache = new(); + private readonly ConcurrentDictionary _storageCache = new(LockPartitions, InitialCapacity); + private readonly ConcurrentDictionary _stateCache = new(LockPartitions, InitialCapacity); + private readonly ConcurrentDictionary _rlpCache = new(LockPartitions, InitialCapacity); private readonly ConcurrentDictionary> _precompileCache = new(LockPartitions, InitialCapacity); public PreBlockCaches() { _clearCaches = [ - () => { _storageCache.Clear(); return CacheType.None; }, - () => { _stateCache.Clear(); return CacheType.None; }, - () => { _precompileCache.NoResizeClear(); return CacheType.None; } + () => _storageCache.NoResizeClear() ? CacheType.Storage : CacheType.None, + () => _stateCache.NoResizeClear() ? CacheType.State : CacheType.None, + () => _precompileCache.NoResizeClear() ? CacheType.Precompile : CacheType.None ]; } - public SeqlockCache StorageCache => _storageCache; - public SeqlockCache StateCache => _stateCache; - public SeqlockCache RlpCache => _rlpCache; + public ConcurrentDictionary StorageCache => _storageCache; + public ConcurrentDictionary StateCache => _stateCache; + public ConcurrentDictionary RlpCache => _rlpCache; public ConcurrentDictionary> PrecompileCache => _precompileCache; public CacheType ClearCaches() diff --git a/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs b/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs index af258806395f..d4260a7f5278 100644 --- a/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs +++ b/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Concurrent; using System.Diagnostics; using Nethermind.Core; -using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Metric; using Nethermind.Db; @@ -40,26 +40,16 @@ public class PrewarmerScopeProvider( public PreBlockCaches? Caches => preBlockCaches; public bool IsWarmWorldState => !populatePreBlockCache; - private sealed class ScopeWrapper : IWorldStateScopeProvider.IScope + private sealed class ScopeWrapper( + IWorldStateScopeProvider.IScope baseScope, + PreBlockCaches preBlockCaches, + bool populatePreBlockCache) + : IWorldStateScopeProvider.IScope { - private readonly IWorldStateScopeProvider.IScope baseScope; - private readonly SeqlockCache preBlockCache; - private readonly SeqlockCache storageCache; - private readonly bool populatePreBlockCache; - private readonly SeqlockCache.ValueFactory _getFromBaseTree; + ConcurrentDictionary preBlockCache = preBlockCaches.StateCache; private readonly IMetricObserver _metricObserver = Metrics.PrewarmerGetTime; private readonly bool _measureMetric = Metrics.DetailedMetricsEnabled; - private readonly PrewarmerGetTimeLabels _labels; - - public ScopeWrapper(IWorldStateScopeProvider.IScope baseScope, PreBlockCaches preBlockCaches, bool populatePreBlockCache) - { - this.baseScope = baseScope; - preBlockCache = preBlockCaches.StateCache; - storageCache = preBlockCaches.StorageCache; - this.populatePreBlockCache = populatePreBlockCache; - _labels = populatePreBlockCache ? PrewarmerGetTimeLabels.Prewarmer : PrewarmerGetTimeLabels.NonPrewarmer; - _getFromBaseTree = GetFromBaseTree; - } + private readonly PrewarmerGetTimeLabels _labels = populatePreBlockCache ? PrewarmerGetTimeLabels.Prewarmer : PrewarmerGetTimeLabels.NonPrewarmer; public void Dispose() => baseScope.Dispose(); @@ -69,7 +59,7 @@ public IWorldStateScopeProvider.IStorageTree CreateStorageTree(Address address) { return new StorageTreeWrapper( baseScope.CreateStorageTree(address), - storageCache, + preBlockCaches.StorageCache, address, populatePreBlockCache); } @@ -124,7 +114,7 @@ public void UpdateRootHash() if (populatePreBlockCache) { long priorReads = Metrics.ThreadLocalStateTreeReads; - Account? account = preBlockCache.GetOrAdd(in addressAsKey, _getFromBaseTree); + Account? account = preBlockCache.GetOrAdd(address, GetFromBaseTree); if (Metrics.ThreadLocalStateTreeReads == priorReads) { @@ -139,7 +129,7 @@ public void UpdateRootHash() } else { - if (preBlockCache.TryGetValue(in addressAsKey, out Account? account)) + if (preBlockCache?.TryGetValue(addressAsKey, out Account? account) ?? false) { if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.AddressHit); baseScope.HintGet(address, account); @@ -147,7 +137,7 @@ public void UpdateRootHash() } else { - account = GetFromBaseTree(in addressAsKey); + account = GetFromBaseTree(addressAsKey); if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.AddressMiss); } return account; @@ -156,36 +146,22 @@ public void UpdateRootHash() public void HintGet(Address address, Account? account) => baseScope.HintGet(address, account); - private Account? GetFromBaseTree(in AddressAsKey address) + private Account? GetFromBaseTree(AddressAsKey address) { return baseScope.Get(address); } } - private sealed class StorageTreeWrapper : IWorldStateScopeProvider.IStorageTree + private sealed class StorageTreeWrapper( + IWorldStateScopeProvider.IStorageTree baseStorageTree, + ConcurrentDictionary preBlockCache, + Address address, + bool populatePreBlockCache + ) : IWorldStateScopeProvider.IStorageTree { - private readonly IWorldStateScopeProvider.IStorageTree baseStorageTree; - private readonly SeqlockCache preBlockCache; - private readonly Address address; - private readonly bool populatePreBlockCache; - private readonly SeqlockCache.ValueFactory _loadFromTreeStorage; private readonly IMetricObserver _metricObserver = Db.Metrics.PrewarmerGetTime; private readonly bool _measureMetric = Db.Metrics.DetailedMetricsEnabled; - private readonly PrewarmerGetTimeLabels _labels; - - public StorageTreeWrapper( - IWorldStateScopeProvider.IStorageTree baseStorageTree, - SeqlockCache preBlockCache, - Address address, - bool populatePreBlockCache) - { - this.baseStorageTree = baseStorageTree; - this.preBlockCache = preBlockCache; - this.address = address; - this.populatePreBlockCache = populatePreBlockCache; - _labels = populatePreBlockCache ? PrewarmerGetTimeLabels.Prewarmer : PrewarmerGetTimeLabels.NonPrewarmer; - _loadFromTreeStorage = LoadFromTreeStorage; - } + private readonly PrewarmerGetTimeLabels _labels = populatePreBlockCache ? PrewarmerGetTimeLabels.Prewarmer : PrewarmerGetTimeLabels.NonPrewarmer; public Hash256 RootHash => baseStorageTree.RootHash; @@ -197,7 +173,7 @@ public byte[] Get(in UInt256 index) { long priorReads = Db.Metrics.ThreadLocalStorageTreeReads; - byte[] value = preBlockCache.GetOrAdd(in storageCell, _loadFromTreeStorage); + byte[] value = preBlockCache.GetOrAdd(storageCell, LoadFromTreeStorage); if (Db.Metrics.ThreadLocalStorageTreeReads == priorReads) { @@ -213,15 +189,15 @@ public byte[] Get(in UInt256 index) } else { - if (preBlockCache.TryGetValue(in storageCell, out byte[] value)) + if (preBlockCache?.TryGetValue(storageCell, out byte[] value) ?? false) { if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.SlotGetHit); - baseStorageTree.HintGet(in index, value); + baseStorageTree.HintGet(index, value); Db.Metrics.IncrementStorageTreeCache(); } else { - value = LoadFromTreeStorage(in storageCell); + value = LoadFromTreeStorage(storageCell); if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.SlotGetMiss); } return value; @@ -230,7 +206,7 @@ public byte[] Get(in UInt256 index) public void HintGet(in UInt256 index, byte[]? value) => baseStorageTree.HintGet(in index, value); - private byte[] LoadFromTreeStorage(in StorageCell storageCell) + private byte[] LoadFromTreeStorage(StorageCell storageCell) { Db.Metrics.IncrementStorageTreeReads(); diff --git a/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs b/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs index ce45389b1fc3..e54f7fb46bbb 100644 --- a/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs +++ b/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs @@ -17,13 +17,12 @@ namespace Nethermind.State.Trie; /// The type of the elements in the collection used to build the trie. public abstract class PatriciaTrie : PatriciaTree { - protected const int MinItemsForParallelRootHash = 64; /// The collection to build the trie of. /// /// true to maintain an in-memory database for proof computation; /// otherwise, false. /// - protected PatriciaTrie(ReadOnlySpan list, bool canBuildProof, ICappedArrayPool? bufferPool = null, bool canBeParallel = true) + protected PatriciaTrie(ReadOnlySpan list, bool canBuildProof, ICappedArrayPool? bufferPool = null) : base(canBuildProof ? new MemDb() : NullDb.Instance, EmptyTreeHash, false, NullLogManager.Instance, bufferPool: bufferPool) { CanBuildProof = canBuildProof; @@ -32,8 +31,7 @@ protected PatriciaTrie(ReadOnlySpan list, bool canBuildProof, ICappedArrayPoo { // ReSharper disable once VirtualMemberCallInConstructor Initialize(list); - // Parallel root hashing adds scheduling overhead for small tries. - UpdateRootHash(canBeParallel); + UpdateRootHash(); } } diff --git a/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs b/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs index f3391d31649b..a84177868b06 100644 --- a/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs @@ -20,8 +20,8 @@ public sealed class ReceiptTrie : PatriciaTrie private readonly IRlpStreamDecoder _decoder; /// /// The transaction receipts to build the trie of. - public ReceiptTrie(IReceiptSpec spec, ReadOnlySpan receipts, IRlpStreamDecoder trieDecoder, ICappedArrayPool bufferPool, bool canBuildProof = false, bool canBeParallel = true) - : base(null, canBuildProof, bufferPool: bufferPool, canBeParallel) + public ReceiptTrie(IReceiptSpec spec, ReadOnlySpan receipts, IRlpStreamDecoder trieDecoder, ICappedArrayPool bufferPool, bool canBuildProof = false) + : base(null, canBuildProof, bufferPool: bufferPool) { ArgumentNullException.ThrowIfNull(spec); ArgumentNullException.ThrowIfNull(trieDecoder); @@ -30,7 +30,7 @@ public ReceiptTrie(IReceiptSpec spec, ReadOnlySpan receipts, IRlpStre if (receipts.Length > 0) { Initialize(receipts, spec); - UpdateRootHash(canBeParallel); + UpdateRootHash(); } } @@ -53,16 +53,14 @@ private void Initialize(ReadOnlySpan receipts, IReceiptSpec spec) public static byte[][] CalculateReceiptProofs(IReleaseSpec spec, ReadOnlySpan receipts, int index, IRlpStreamDecoder decoder) { - bool canBeParallel = receipts.Length > MinItemsForParallelRootHash; - using TrackingCappedArrayPool cappedArrayPool = new(receipts.Length * 4, canBeParallel: canBeParallel); - return new ReceiptTrie(spec, receipts, decoder, cappedArrayPool, canBuildProof: true, canBeParallel: canBeParallel).BuildProof(index); + using TrackingCappedArrayPool cappedArrayPool = new(receipts.Length * 4); + return new ReceiptTrie(spec, receipts, decoder, cappedArrayPool, canBuildProof: true).BuildProof(index); } public static Hash256 CalculateRoot(IReceiptSpec receiptSpec, ReadOnlySpan txReceipts, IRlpStreamDecoder decoder) { - bool canBeParallel = txReceipts.Length > MinItemsForParallelRootHash; - using TrackingCappedArrayPool cappedArrayPool = new(txReceipts.Length * 4, canBeParallel: canBeParallel); - Hash256 receiptsRoot = new ReceiptTrie(receiptSpec, txReceipts, decoder, bufferPool: cappedArrayPool, canBeParallel: canBeParallel).RootHash; + using TrackingCappedArrayPool cappedArrayPool = new(txReceipts.Length * 4); + Hash256 receiptsRoot = new ReceiptTrie(receiptSpec, txReceipts, decoder, bufferPool: cappedArrayPool).RootHash; return receiptsRoot; } } diff --git a/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs b/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs index 49aeaac04930..7e5c6b5415a7 100644 --- a/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs @@ -22,8 +22,8 @@ public sealed class TxTrie : PatriciaTrie /// /// The transactions to build the trie of. - public TxTrie(ReadOnlySpan transactions, bool canBuildProof = false, ICappedArrayPool? bufferPool = null, bool canBeParallel = true) - : base(transactions, canBuildProof, bufferPool: bufferPool, canBeParallel: canBeParallel) { } + public TxTrie(ReadOnlySpan transactions, bool canBuildProof = false, ICappedArrayPool? bufferPool = null) + : base(transactions, canBuildProof, bufferPool: bufferPool) { } protected override void Initialize(ReadOnlySpan list) { @@ -57,41 +57,20 @@ static SpanSource CopyExistingRlp(ReadOnlySpan rlp, ICappedArrayPool? buff } } - private void InitializeFromEncodedTransactions(ReadOnlySpan list) - { - for (int key = 0; key < list.Length; key++) - { - SpanSource keyBuffer = key.EncodeToSpanSource(_bufferPool); - Set(keyBuffer.Span, list[key]); - } - } - [DoesNotReturn, StackTraceHidden] private static void ThrowSpanSourceNotCappedArray() => throw new InvalidOperationException("Encode to SpanSource failed to get a CappedArray."); public static byte[][] CalculateProof(ReadOnlySpan transactions, int index) { - bool canBeParallel = transactions.Length > MinItemsForParallelRootHash; - using TrackingCappedArrayPool cappedArray = new(transactions.Length * 4, canBeParallel: canBeParallel); - byte[][] rootHash = new TxTrie(transactions, canBuildProof: true, bufferPool: cappedArray, canBeParallel: canBeParallel).BuildProof(index); + using TrackingCappedArrayPool cappedArray = new(transactions.Length * 4); + byte[][] rootHash = new TxTrie(transactions, canBuildProof: true, bufferPool: cappedArray).BuildProof(index); return rootHash; } public static Hash256 CalculateRoot(ReadOnlySpan transactions) { - bool canBeParallel = transactions.Length > MinItemsForParallelRootHash; - using TrackingCappedArrayPool cappedArray = new(transactions.Length * 4, canBeParallel: canBeParallel); - Hash256 rootHash = new TxTrie(transactions, canBuildProof: false, bufferPool: cappedArray, canBeParallel: canBeParallel).RootHash; + using TrackingCappedArrayPool cappedArray = new(transactions.Length * 4); + Hash256 rootHash = new TxTrie(transactions, canBuildProof: false, bufferPool: cappedArray).RootHash; return rootHash; } - - public static Hash256 CalculateRoot(ReadOnlySpan encodedTransactions) - { - bool canBeParallel = encodedTransactions.Length > MinItemsForParallelRootHash; - using TrackingCappedArrayPool cappedArray = new(encodedTransactions.Length * 4, canBeParallel: canBeParallel); - TxTrie txTrie = new(ReadOnlySpan.Empty, canBuildProof: false, bufferPool: cappedArray, canBeParallel: canBeParallel); - txTrie.InitializeFromEncodedTransactions(encodedTransactions); - txTrie.UpdateRootHash(canBeParallel); - return txTrie.RootHash; - } } diff --git a/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs b/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs index d63dc61471b0..10efa64e86a4 100644 --- a/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs @@ -19,7 +19,7 @@ public sealed class WithdrawalTrie : PatriciaTrie /// /// The withdrawals to build the trie of. public WithdrawalTrie(ReadOnlySpan withdrawals, bool canBuildProof = false) - : base(withdrawals, canBuildProof, canBeParallel: false) { } + : base(withdrawals, canBuildProof) { } public static Hash256? CalculateRoot(ReadOnlySpan withdrawals) => new WithdrawalTrie(withdrawals).RootHash; diff --git a/src/Nethermind/Nethermind.State/TrieStoreScopeProvider.cs b/src/Nethermind/Nethermind.State/TrieStoreScopeProvider.cs index 88026fd8d282..e4f01d4008ed 100644 --- a/src/Nethermind/Nethermind.State/TrieStoreScopeProvider.cs +++ b/src/Nethermind/Nethermind.State/TrieStoreScopeProvider.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -193,14 +194,10 @@ public void Dispose() while (_dirtyStorageTree.TryDequeue(out (AddressAsKey, Hash256) entry)) { (AddressAsKey key, Hash256 storageRoot) = entry; - if (!_dirtyAccounts.TryGetValue(key, out var account)) - account = scope.Get(key); - - // Account may be null when EIP-161 deletes an empty account that had storage - // changes in the same block. Skip the storage root update since the account - // will not exist in the state trie. - if (account is null) continue; - account = account.WithChangedStorageRoot(storageRoot); + if (!_dirtyAccounts.TryGetValue(key, out var account)) account = scope.Get(key); + if (account == null && storageRoot == Keccak.EmptyTreeHash) continue; + account ??= ThrowNullAccount(key); + account = account!.WithChangedStorageRoot(storageRoot); _dirtyAccounts[key] = account; OnAccountUpdated?.Invoke(key, new IWorldStateScopeProvider.AccountUpdated(key, account)); if (logger.IsTrace) Trace(key, storageRoot, account); @@ -220,6 +217,10 @@ public void Dispose() [MethodImpl(MethodImplOptions.NoInlining)] void Trace(Address address, Hash256 storageRoot, Account? account) => logger.Trace($"Update {address} S {account?.StorageRoot} -> {storageRoot}"); + + [DoesNotReturn, StackTraceHidden] + static Account ThrowNullAccount(Address address) + => throw new InvalidOperationException($"Account {address} is null when updating storage hash"); } } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs index 92ffa310957e..d6d7f65685d3 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs @@ -19,7 +19,6 @@ namespace Nethermind.Synchronization.Test.FastSync; [Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class StateSyncFeedHealingTests : StateSyncFeedTestsBase { [Test] diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs index ffc7eb77719a..af08c2f41a61 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs @@ -6,6 +6,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; +using Nethermind.Logging; using Nethermind.State; using Nethermind.Synchronization.FastBlocks; using Nethermind.Synchronization.ParallelSync; @@ -30,7 +31,7 @@ public void Header_block_is_0_when_no_header_was_suggested() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); blockTree.BestSuggestedHeader.ReturnsNull(); Assert.That(syncProgressResolver.FindBestHeader(), Is.EqualTo(0)); } @@ -45,7 +46,7 @@ public void Best_block_is_0_when_no_block_was_suggested() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); blockTree.BestSuggestedBody.ReturnsNull(); Assert.That(syncProgressResolver.FindBestFullBlock(), Is.EqualTo(0)); } @@ -60,7 +61,7 @@ public void Best_state_is_head_when_there_are_no_suggested_blocks() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); Block head = Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(5).WithStateRoot(TestItem.KeccakA).TestObject).TestObject; blockTree.Head.Returns(head); blockTree.BestSuggestedHeader.Returns(head.Header); @@ -78,7 +79,7 @@ public void Best_state_is_suggested_if_there_is_suggested_block_with_state() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); Block head = Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(5).WithStateRoot(TestItem.KeccakA).TestObject).TestObject; BlockHeader suggested = Build.A.BlockHeader.WithNumber(6).WithStateRoot(TestItem.KeccakB).TestObject; blockTree.Head.Returns(head); @@ -100,7 +101,7 @@ public void Best_state_is_head_if_there_is_suggested_block_without_state() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); Block head = Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(5).WithStateRoot(TestItem.KeccakA).TestObject).TestObject; BlockHeader suggested = Build.A.BlockHeader.WithNumber(6).WithStateRoot(TestItem.KeccakB).TestObject; blockTree.Head.Returns(head); @@ -122,7 +123,7 @@ public void Is_fast_block_finished_returns_true_when_no_fast_sync_is_used() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); Assert.That(syncProgressResolver.IsFastBlocksHeadersFinished(), Is.True); Assert.That(syncProgressResolver.IsFastBlocksBodiesFinished(), Is.True); Assert.That(syncProgressResolver.IsFastBlocksReceiptsFinished(), Is.True); @@ -144,7 +145,7 @@ public void Is_fast_block_bodies_finished_returns_false_when_blocks_not_download blockTree.LowestInsertedHeader.Returns(Build.A.BlockHeader.WithNumber(1).WithStateRoot(TestItem.KeccakA).TestObject); - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); Assert.That(syncProgressResolver.IsFastBlocksBodiesFinished(), Is.False); } @@ -163,12 +164,12 @@ public void Is_fast_block_receipts_finished_returns_true_when_receipts_not_downl blockTree.LowestInsertedHeader.Returns(Build.A.BlockHeader.WithNumber(1).WithStateRoot(TestItem.KeccakA).TestObject); - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, true, syncConfig); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, true, syncConfig, LimboLogs.Instance); Assert.That(syncProgressResolver.IsFastBlocksReceiptsFinished(), Is.True); } - private SyncProgressResolver CreateProgressResolver(IBlockTree blockTree, IStateReader stateReader, bool isReceiptFinished, SyncConfig syncConfig) + private SyncProgressResolver CreateProgressResolver(IBlockTree blockTree, IStateReader stateReader, bool isReceiptFinished, SyncConfig syncConfig, LimboLogs limboLogs) { ISyncFeed receiptFeed = Substitute.For>(); receiptFeed.IsFinished.Returns(isReceiptFinished); @@ -180,7 +181,8 @@ private SyncProgressResolver CreateProgressResolver(IBlockTree blockTree, IState Substitute.For>(), Substitute.For>(), receiptFeed, - Substitute.For>() + Substitute.For>(), + limboLogs ); } } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs index e65fa535eff9..4b2d33f62f8a 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs @@ -666,7 +666,7 @@ public async Task Broadcast_NewBlock_on_arrival_to_sqrt_of_peers([Values(1, 2, 3 int count = 0; remoteServer .When(r => r.AddNewBlock(Arg.Is(b => b.Hash == remoteBlockTree.Head!.Hash), Arg.Any())) - .Do(_ => Interlocked.Increment(ref count)); + .Do(_ => count++); PeerInfo[] peers = Enumerable.Range(0, peerCount).Take(peerCount) .Select(_ => new PeerInfo(new SyncPeerMock(remoteBlockTree, remoteSyncServer: remoteServer))) .ToArray(); diff --git a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs index 1eaf7d0d9cd1..0f2c9cd0b1a2 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs @@ -8,6 +8,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Int256; +using Nethermind.Logging; using Nethermind.Synchronization.FastBlocks; using Nethermind.Synchronization.SnapSync; @@ -20,6 +21,9 @@ public class SyncProgressResolver : ISyncProgressResolver private readonly ISyncConfig _syncConfig; private readonly IFullStateFinder _fullStateFinder; + // ReSharper disable once NotAccessedField.Local + private readonly ILogger _logger; + private readonly ISyncFeed _headersSyncFeed; private readonly ISyncFeed _bodiesSyncFeed; private readonly ISyncFeed _receiptsSyncFeed; @@ -32,8 +36,10 @@ public SyncProgressResolver( [KeyFilter(nameof(HeadersSyncFeed))] ISyncFeed headersSyncFeed, ISyncFeed bodiesSyncFeed, ISyncFeed receiptsSyncFeed, - ISyncFeed snapSyncFeed) + ISyncFeed snapSyncFeed, + ILogManager logManager) { + _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); _fullStateFinder = fullStateFinder ?? throw new ArgumentNullException(nameof(fullStateFinder)); _syncConfig = syncConfig ?? throw new ArgumentNullException(nameof(syncConfig)); diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/ClientTypeStrategy.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/ClientTypeStrategy.cs new file mode 100644 index 000000000000..142c2ff819c8 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/ClientTypeStrategy.cs @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Linq; +using Nethermind.Blockchain; +using Nethermind.Stats; +using Nethermind.Stats.Model; + +namespace Nethermind.Synchronization.Peers.AllocationStrategies; + +public class ClientTypeStrategy : IPeerAllocationStrategy +{ + private readonly IPeerAllocationStrategy _strategy; + private readonly bool _allowOtherIfNone; + private readonly HashSet _supportedClientTypes; + + public ClientTypeStrategy(IPeerAllocationStrategy strategy, bool allowOtherIfNone, params NodeClientType[] supportedClientTypes) + : this(strategy, allowOtherIfNone, (IEnumerable)supportedClientTypes) + { + } + + public ClientTypeStrategy(IPeerAllocationStrategy strategy, bool allowOtherIfNone, IEnumerable supportedClientTypes) + { + _strategy = strategy; + _allowOtherIfNone = allowOtherIfNone; + _supportedClientTypes = new HashSet(supportedClientTypes); + } + + public PeerInfo? Allocate(PeerInfo? currentPeer, IEnumerable peers, INodeStatsManager nodeStatsManager, IBlockTree blockTree) + { + IEnumerable originalPeers = peers; + peers = peers.Where(p => _supportedClientTypes.Contains(p.PeerClientType)); + + if (_allowOtherIfNone) + { + if (!peers.Any()) + { + peers = originalPeers; + } + } + return _strategy.Allocate(currentPeer, peers, nodeStatsManager, blockTree); + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/NullStrategy.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/NullStrategy.cs new file mode 100644 index 000000000000..fcf28b1a4a45 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/NullStrategy.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Blockchain; +using Nethermind.Stats; + +namespace Nethermind.Synchronization.Peers.AllocationStrategies +{ + /// + /// used only for failed allocations + /// + public class NullStrategy : IPeerAllocationStrategy + { + private NullStrategy() + { + } + + public static IPeerAllocationStrategy Instance { get; } = new NullStrategy(); + + public PeerInfo? Allocate(PeerInfo? currentPeer, IEnumerable peers, INodeStatsManager nodeStatsManager, IBlockTree blockTree) + { + return null; + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StaticStrategy.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StaticStrategy.cs new file mode 100644 index 000000000000..7d9a5b4de624 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StaticStrategy.cs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Blockchain; +using Nethermind.Stats; + +namespace Nethermind.Synchronization.Peers.AllocationStrategies +{ + public class StaticStrategy : IPeerAllocationStrategy + { + private readonly PeerInfo _peerInfo; + + public StaticStrategy(PeerInfo peerInfo) + { + _peerInfo = peerInfo; + } + + public PeerInfo Allocate(PeerInfo? currentPeer, IEnumerable peers, INodeStatsManager nodeStatsManager, IBlockTree blockTree) + { + return _peerInfo; + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StrategySelectionType.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StrategySelectionType.cs new file mode 100644 index 000000000000..c20046181c27 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StrategySelectionType.cs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Synchronization.Peers.AllocationStrategies; + +public enum StrategySelectionType +{ + Better = 1, + AtLeastTheSame = 0, + CanBeSlightlyWorse = -1 +} diff --git a/src/Nethermind/Nethermind.Synchronization/SyncPeerEventArgs.cs b/src/Nethermind/Nethermind.Synchronization/SyncPeerEventArgs.cs new file mode 100644 index 000000000000..3d5fbf168612 --- /dev/null +++ b/src/Nethermind/Nethermind.Synchronization/SyncPeerEventArgs.cs @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Synchronization.Peers; + +namespace Nethermind.Synchronization +{ + public class AllocationChangeEventArgs + { + public AllocationChangeEventArgs(PeerInfo? previous, PeerInfo? current) + { + Previous = previous; + Current = current; + } + + public PeerInfo? Previous { get; } + + public PeerInfo? Current { get; } + } +} diff --git a/src/Nethermind/Nethermind.Taiko/Tdx/TdxsClient.cs b/src/Nethermind/Nethermind.Taiko/Tdx/TdxsClient.cs index 999fabaa0f41..081e33262199 100644 --- a/src/Nethermind/Nethermind.Taiko/Tdx/TdxsClient.cs +++ b/src/Nethermind/Nethermind.Taiko/Tdx/TdxsClient.cs @@ -6,6 +6,7 @@ using System.Net.Sockets; using System.Text; using System.Text.Json; +using Nethermind.Logging; namespace Nethermind.Taiko.Tdx; @@ -13,8 +14,9 @@ namespace Nethermind.Taiko.Tdx; /// Client for communicating with the tdxs daemon via Unix socket. /// Protocol: JSON request/response over Unix socket. /// -public class TdxsClient(ISurgeTdxConfig config) : ITdxsClient +public class TdxsClient(ISurgeTdxConfig config, ILogManager logManager) : ITdxsClient { + private readonly ILogger _logger = logManager.GetClassLogger(); public byte[] Issue(byte[] userData, byte[] nonce) { diff --git a/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs b/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs index bc89ff38ecff..f332f113489c 100644 --- a/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs +++ b/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs @@ -10,7 +10,6 @@ using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; -using Nethermind.Evm.CodeAnalysis; namespace Nethermind.Test.Runner; @@ -55,7 +54,7 @@ public void StartOperation(int pc, Instruction opcode, long gas, in ExecutionEnv { _gasAlreadySetForCurrentOp = false; _traceEntry = new StateTestTxTraceEntry(); - _traceEntry.Pc = pc + (env.CodeInfo is EofCodeInfo eofCodeInfo ? eofCodeInfo.PcOffset() : 0); + _traceEntry.Pc = pc + env.CodeInfo.PcOffset(); _traceEntry.Section = codeSection; _traceEntry.Operation = (byte)opcode; _traceEntry.OperationName = opcode.GetName(); diff --git a/src/Nethermind/Nethermind.Trie.Test/CacheTests.cs b/src/Nethermind/Nethermind.Trie.Test/CacheTests.cs index 02f361be5b42..8891482ff7f4 100644 --- a/src/Nethermind/Nethermind.Trie.Test/CacheTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/CacheTests.cs @@ -10,7 +10,6 @@ namespace Nethermind.Trie.Test { [TestFixture] - [Parallelizable(ParallelScope.All)] public class CacheTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs b/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs index c8a8bc507a07..bf35cbb1232d 100644 --- a/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs @@ -7,7 +7,6 @@ namespace Nethermind.Trie.Test; -[Parallelizable(ParallelScope.All)] public class HexPrefixTests { [TestCase(false, (byte)3, (byte)19)] diff --git a/src/Nethermind/Nethermind.Trie.Test/NibbleTests.cs b/src/Nethermind/Nethermind.Trie.Test/NibbleTests.cs index a575cb2ddbbd..1338eafa0183 100644 --- a/src/Nethermind/Nethermind.Trie.Test/NibbleTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/NibbleTests.cs @@ -6,7 +6,6 @@ namespace Nethermind.Trie.Test; -[Parallelizable(ParallelScope.All)] public class NibbleTests { private readonly byte[][] _hexEncoding = diff --git a/src/Nethermind/Nethermind.Trie.Test/NodeStorageFactoryTests.cs b/src/Nethermind/Nethermind.Trie.Test/NodeStorageFactoryTests.cs index 740048565d78..5e0308ea2a5c 100644 --- a/src/Nethermind/Nethermind.Trie.Test/NodeStorageFactoryTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/NodeStorageFactoryTests.cs @@ -10,7 +10,6 @@ namespace Nethermind.Trie.Test; -[Parallelizable(ParallelScope.All)] public class NodeStorageFactoryTests { [TestCase(INodeStorage.KeyScheme.Hash)] diff --git a/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs b/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs index c212d15a8e8e..51702876eec0 100644 --- a/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs @@ -13,25 +13,31 @@ namespace Nethermind.Trie.Test; [TestFixture(INodeStorage.KeyScheme.Hash)] [TestFixture(INodeStorage.KeyScheme.HalfPath)] -[Parallelizable(ParallelScope.All)] -public class NodeStorageTests(INodeStorage.KeyScheme currentKeyScheme) +public class NodeStorageTests { + private readonly INodeStorage.KeyScheme _currentKeyScheme; + + public NodeStorageTests(INodeStorage.KeyScheme currentKeyScheme) + { + _currentKeyScheme = currentKeyScheme; + } + [Test] public void Should_StoreAndRead() { TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); nodeStorage.KeyExists(null, TreePath.Empty, TestItem.KeccakA).Should().BeFalse(); nodeStorage.Set(null, TreePath.Empty, TestItem.KeccakA, TestItem.KeccakA.BytesToArray()); nodeStorage.Get(null, TreePath.Empty, TestItem.KeccakA).Should().BeEquivalentTo(TestItem.KeccakA.BytesToArray()); nodeStorage.KeyExists(null, TreePath.Empty, TestItem.KeccakA).Should().BeTrue(); - if (currentKeyScheme == INodeStorage.KeyScheme.Hash) + if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) { testDb[TestItem.KeccakA.Bytes].Should().NotBeNull(); } - else if (currentKeyScheme == INodeStorage.KeyScheme.HalfPath) + else if (_currentKeyScheme == INodeStorage.KeyScheme.HalfPath) { testDb[NodeStorage.GetHalfPathNodeStoragePath(null, TreePath.Empty, TestItem.KeccakA)].Should().NotBeNull(); } @@ -41,18 +47,18 @@ public void Should_StoreAndRead() public void Should_StoreAndRead_WithStorage() { TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); nodeStorage.KeyExists(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA).Should().BeFalse(); nodeStorage.Set(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA, TestItem.KeccakA.BytesToArray()); nodeStorage.Get(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA).Should().BeEquivalentTo(TestItem.KeccakA.BytesToArray()); nodeStorage.KeyExists(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA).Should().BeTrue(); - if (currentKeyScheme == INodeStorage.KeyScheme.Hash) + if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) { testDb[TestItem.KeccakA.Bytes].Should().NotBeNull(); } - else if (currentKeyScheme == INodeStorage.KeyScheme.HalfPath) + else if (_currentKeyScheme == INodeStorage.KeyScheme.HalfPath) { testDb[NodeStorage.GetHalfPathNodeStoragePath(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA)].Should().NotBeNull(); } @@ -62,7 +68,7 @@ public void Should_StoreAndRead_WithStorage() public void When_KeyNotExist_Should_TryBothKeyType() { TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); nodeStorage.Get(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA).Should().BeNull(); @@ -74,9 +80,9 @@ public void When_KeyNotExist_Should_TryBothKeyType() public void When_EntryOfDifferentScheme_Should_StillBeAbleToRead() { TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); - if (currentKeyScheme == INodeStorage.KeyScheme.Hash) + if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) { testDb[NodeStorage.GetHalfPathNodeStoragePath(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA)] = TestItem.KeccakA.BytesToArray(); @@ -103,7 +109,7 @@ public void When_EntryOfDifferentScheme_Should_StillBeAbleToRead() [TestCase(true, 32, "0211111111111111111111111111111111111111111111111111111111111111112222222222222222203333333333333333333333333333333333333333333333333333333333333333")] public void Test_HalfPathEncoding(bool hasAddress, int pathLength, string expectedKey) { - if (currentKeyScheme == INodeStorage.KeyScheme.Hash) return; + if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) return; Hash256? address = null; if (hasAddress) @@ -124,10 +130,10 @@ public void Test_HalfPathEncoding(bool hasAddress, int pathLength, string expect [TestCase(true, 3, ReadFlags.HintReadAhead | ReadFlags.HintReadAhead3)] public void Test_WhenReadaheadUseDifferentReadaheadOnDifferentSection(bool hasAddress, int pathLength, ReadFlags expectedReadFlags) { - if (currentKeyScheme == INodeStorage.KeyScheme.Hash) return; + if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) return; TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); Hash256? address = null; if (hasAddress) diff --git a/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs index 2c70a4944949..ed8c23ee2640 100644 --- a/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs @@ -15,7 +15,6 @@ namespace Nethermind.Trie.Test; -[Parallelizable(ParallelScope.All)] public class OverlayTrieStoreTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/Pruning/MaxBlockInCachePruneStrategyTests.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/MaxBlockInCachePruneStrategyTests.cs index 93c61e44ed48..f288a30f6e0e 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/MaxBlockInCachePruneStrategyTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/MaxBlockInCachePruneStrategyTests.cs @@ -8,8 +8,7 @@ namespace Nethermind.Trie.Test.Pruning { [TestFixture] - [Parallelizable(ParallelScope.All)] - [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] + [Parallelizable(ParallelScope.Self)] public class MaxBlockInCachePruneStrategyTests { private IPruningStrategy _baseStrategy; diff --git a/src/Nethermind/Nethermind.Trie.Test/Pruning/MinBlockInCachePruneStrategyTests.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/MinBlockInCachePruneStrategyTests.cs index 63930144e889..27814dd58fce 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/MinBlockInCachePruneStrategyTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/MinBlockInCachePruneStrategyTests.cs @@ -9,8 +9,7 @@ namespace Nethermind.Trie.Test.Pruning { [TestFixture] - [Parallelizable(ParallelScope.All)] - [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] + [Parallelizable(ParallelScope.Self)] public class MinBlockInCachePruneStrategyTests { private IPruningStrategy _baseStrategy; diff --git a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs index b75849af8999..2a98ac0b6aa9 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs @@ -28,13 +28,18 @@ namespace Nethermind.Trie.Test.Pruning { [TestFixture(INodeStorage.KeyScheme.HalfPath)] [TestFixture(INodeStorage.KeyScheme.Hash)] - [Parallelizable(ParallelScope.All)] - public class TreeStoreTests(INodeStorage.KeyScheme scheme) + public class TreeStoreTests { private readonly ILogManager _logManager = LimboLogs.Instance; // new OneLoggerLogManager(new NUnitLogger(LogLevel.Trace)); private readonly AccountDecoder _accountDecoder = new(); + private readonly INodeStorage.KeyScheme _scheme; + + public TreeStoreTests(INodeStorage.KeyScheme scheme) + { + _scheme = scheme; + } private TrieStore CreateTrieStore( IPruningStrategy? pruningStrategy = null, @@ -54,7 +59,7 @@ private TrieStore CreateTrieStore( finalizedStateProvider ??= new TestFinalizedStateProvider(pruningConfig.PruningBoundary); TrieStore trieStore = new( - new NodeStorage(kvStore, scheme, requirePath: scheme == INodeStorage.KeyScheme.HalfPath), + new NodeStorage(kvStore, _scheme, requirePath: _scheme == INodeStorage.KeyScheme.HalfPath), pruningStrategy, persistenceStrategy, finalizedStateProvider, @@ -143,7 +148,7 @@ public void Pruning_off_cache_should_change_commit_node() committer.CommitNode(ref emptyPath, trieNode3); } fullTrieStore.WaitForPruning(); - fullTrieStore.MemoryUsedByDirtyCache.Should().Be(scheme == INodeStorage.KeyScheme.HalfPath ? 832 : 676); + fullTrieStore.MemoryUsedByDirtyCache.Should().Be(_scheme == INodeStorage.KeyScheme.HalfPath ? 832 : 676); } [Test] @@ -172,7 +177,7 @@ public void Pruning_off_cache_should_find_cached_or_unknown() Assert.That(returnedNode2.NodeType, Is.EqualTo(NodeType.Unknown)); Assert.That(returnedNode3.NodeType, Is.EqualTo(NodeType.Unknown)); trieStore.WaitForPruning(); - trieStore.MemoryUsedByDirtyCache.Should().Be(scheme == INodeStorage.KeyScheme.HalfPath ? 552 : 396); + trieStore.MemoryUsedByDirtyCache.Should().Be(_scheme == INodeStorage.KeyScheme.HalfPath ? 552 : 396); } [Test] @@ -237,7 +242,7 @@ public void Memory_with_concurrent_commits_is_correct() tree.Commit(); } - fullTrieStore.MemoryUsedByDirtyCache.Should().Be(scheme == INodeStorage.KeyScheme.Hash ? 545956 : 616104L); + fullTrieStore.MemoryUsedByDirtyCache.Should().Be(_scheme == INodeStorage.KeyScheme.Hash ? 545956 : 616104L); fullTrieStore.CommittedNodesCount.Should().Be(1349); } @@ -866,7 +871,7 @@ public void ReadOnly_store_returns_copies(bool pruning) readOnlyNode.Key?.ToString().Should().Be(originalNode.Key?.ToString()); } - private long ExpectedPerNodeKeyMemorySize => (scheme == INodeStorage.KeyScheme.Hash ? 0 : TrieStoreDirtyNodesCache.Key.MemoryUsage) + MemorySizes.ObjectHeaderMethodTable + MemorySizes.RefSize + 4 + MemorySizes.RefSize; + private long ExpectedPerNodeKeyMemorySize => (_scheme == INodeStorage.KeyScheme.Hash ? 0 : TrieStoreDirtyNodesCache.Key.MemoryUsage) + MemorySizes.ObjectHeaderMethodTable + MemorySizes.RefSize + 4 + MemorySizes.RefSize; [Test] public void After_commit_should_have_has_root() @@ -897,7 +902,6 @@ public void After_commit_should_have_has_root() [Test] [Retry(3)] - [NonParallelizable] public async Task Will_RemovePastKeys_OnSnapshot() { MemDb memDb = new(); @@ -926,7 +930,7 @@ public async Task Will_RemovePastKeys_OnSnapshot() await Task.Delay(TimeSpan.FromMilliseconds(10)); } - if (scheme == INodeStorage.KeyScheme.Hash) + if (_scheme == INodeStorage.KeyScheme.Hash) { memDb.Count.Should().NotBe(1); } @@ -937,8 +941,6 @@ public async Task Will_RemovePastKeys_OnSnapshot() } [Test] - [Retry(3)] - [NonParallelizable] public async Task Will_Trigger_ReorgBoundaryEvent_On_Prune() { // TODO: Check why slow @@ -1019,7 +1021,7 @@ public void When_SomeKindOfNonResolvedNotInMainWorldState_OnPrune_DoNotDeleteNod Address address = TestItem.AddressA; UInt256 slot = 1; - INodeStorage nodeStorage = new NodeStorage(memDbProvider.StateDb, scheme); + INodeStorage nodeStorage = new NodeStorage(memDbProvider.StateDb, _scheme); (Hash256 stateRoot, ValueHash256 storageRoot) = SetupStartingState(); nodeStorage.Get(address.ToAccountPath.ToCommitment(), TreePath.Empty, storageRoot).Should().NotBeNull(); @@ -1102,7 +1104,7 @@ public Task When_Prune_ClearRecommittedPersistedNode() } memDb.Count.Should().Be(1); - fullTrieStore.MemoryUsedByDirtyCache.Should().Be(scheme == INodeStorage.KeyScheme.Hash ? 12032 : 15360); + fullTrieStore.MemoryUsedByDirtyCache.Should().Be(_scheme == INodeStorage.KeyScheme.Hash ? 12032 : 15360); fullTrieStore.PersistCache(default); memDb.Count.Should().Be(64); @@ -1143,7 +1145,7 @@ public void OnDispose_PersistAtLeastOneCommitSet() [Test] public void Will_NotPruneTopLevelNode() { - if (scheme == INodeStorage.KeyScheme.Hash) Assert.Ignore("Not applicable for hash"); + if (_scheme == INodeStorage.KeyScheme.Hash) Assert.Ignore("Not applicable for hash"); MemDb memDb = new(); TestPruningStrategy testPruningStrategy = new TestPruningStrategy( @@ -1222,7 +1224,7 @@ void WriteRandomData(int seed) [Test] public void Can_Prune_StorageTreeRoot() { - if (scheme == INodeStorage.KeyScheme.Hash) Assert.Ignore("Not applicable for hash"); + if (_scheme == INodeStorage.KeyScheme.Hash) Assert.Ignore("Not applicable for hash"); MemDb memDb = new(); TestPruningStrategy testPruningStrategy = new TestPruningStrategy( @@ -1591,7 +1593,7 @@ void VerifyAllTrieExceptGenesis() long cachedPersistedNode = fullTrieStore.CachedNodesCount - fullTrieStore.DirtyCachedNodesCount; long perStatePersistedNode = 20; - if (scheme == INodeStorage.KeyScheme.Hash) + if (_scheme == INodeStorage.KeyScheme.Hash) { cachedPersistedNode.Should().Be(perStatePersistedNode + 3); } diff --git a/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs b/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs index cd0cd0d3ee19..223282c66048 100644 --- a/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs @@ -27,7 +27,6 @@ namespace Nethermind.Trie.Test { [TestFixture] - [Parallelizable(ParallelScope.All)] public class PruningScenariosTests { private ILogger _logger; @@ -1223,7 +1222,7 @@ public void Retain_Some_PersistedNodes() ctx .AssertThatDirtyNodeCountIs(9) - .AssertThatCachedNodeCountMoreThan(275); + .AssertThatCachedNodeCountMoreThan(280); } [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/RawTrieStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/RawTrieStoreTests.cs index 2535489626fb..7663cff436f4 100644 --- a/src/Nethermind/Nethermind.Trie.Test/RawTrieStoreTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/RawTrieStoreTests.cs @@ -10,7 +10,6 @@ namespace Nethermind.Trie.Test; -[Parallelizable(ParallelScope.All)] public class RawTrieStoreTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TinyTreePathTests.cs b/src/Nethermind/Nethermind.Trie.Test/TinyTreePathTests.cs index d6c93f2b3f96..a2d51d1a5a18 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TinyTreePathTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TinyTreePathTests.cs @@ -8,7 +8,6 @@ namespace Nethermind.Trie.Test; -[Parallelizable(ParallelScope.All)] public class TinyTreePathTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TrackingCappedArrayPoolTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrackingCappedArrayPoolTests.cs index bb30481d6f14..322804621d13 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrackingCappedArrayPoolTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrackingCappedArrayPoolTests.cs @@ -8,7 +8,6 @@ namespace Nethermind.Trie.Test; -[Parallelizable(ParallelScope.All)] public class TrackingCappedArrayPoolTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TreePathTests.cs b/src/Nethermind/Nethermind.Trie.Test/TreePathTests.cs index da83b8a3da8f..b398b4b3b65e 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TreePathTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TreePathTests.cs @@ -9,7 +9,6 @@ namespace Nethermind.Trie.Test; -[Parallelizable(ParallelScope.All)] public class TreePathTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs index 6d28229901d0..df8fcf6dac17 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs @@ -11,7 +11,6 @@ namespace Nethermind.Trie.Test; -[Parallelizable(ParallelScope.All)] public class TrieNodeResolverWithReadFlagsTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs index 39acb58ec63e..cc8bbf893719 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs @@ -27,8 +27,6 @@ namespace Nethermind.Trie.Test { [TestFixture] - [Parallelizable(ParallelScope.All)] - [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class TrieTests { private ILogger _logger; @@ -1083,8 +1081,6 @@ public void Fuzz_accounts_with_reorganizations( } [TestCaseSource(nameof(FuzzAccountsWithStorageScenarios))] - [Retry(3)] - [NonParallelizable] public void Fuzz_accounts_with_storage( (TrieStoreConfigurations trieStoreConfigurations, int accountsCount, diff --git a/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs b/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs index 7221240b00a8..2fd6f53b2b60 100644 --- a/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs @@ -20,7 +20,6 @@ namespace Nethermind.Trie.Test; -[Parallelizable(ParallelScope.All)] public class VisitingTests { [TestCaseSource(nameof(GetOptions))] diff --git a/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs b/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs index f22b68a1123b..3b3e4b1b2a46 100644 --- a/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs @@ -8,7 +8,6 @@ namespace Nethermind.Trie.Test; -[Parallelizable(ParallelScope.All)] public class VisitorProgressTrackerTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs b/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs index 28146edf7611..75236286b82d 100644 --- a/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs +++ b/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs @@ -1,13 +1,15 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Collections.Concurrent; using Nethermind.Core.Collections; namespace Nethermind.Trie; public sealed class NodeStorageCache { - private readonly SeqlockCache _cache = new(); + private ConcurrentDictionary _cache = new(); private volatile bool _enabled = false; @@ -17,18 +19,17 @@ public bool Enabled set => _enabled = value; } - public byte[]? GetOrAdd(in NodeKey nodeKey, SeqlockCache.ValueFactory tryLoadRlp) + public byte[]? GetOrAdd(NodeKey nodeKey, Func tryLoadRlp) { if (!_enabled) { - return tryLoadRlp(in nodeKey); + return tryLoadRlp(nodeKey); } - return _cache.GetOrAdd(in nodeKey, tryLoadRlp); + return _cache.GetOrAdd(nodeKey, tryLoadRlp); } public bool ClearCaches() { - _cache.Clear(); - return true; + return _cache.NoResizeClear(); } } diff --git a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs index d42c85a23f6e..08b96d207fc8 100644 --- a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs +++ b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -24,6 +25,7 @@ namespace Nethermind.Trie public partial class PatriciaTree { private const int MaxKeyStackAlloc = 64; + private readonly static byte[][] _singleByteKeys = [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15]]; private readonly ILogger _logger; @@ -1001,5 +1003,25 @@ bool TryGetRootRef(out TrieNode? rootRef) [DoesNotReturn, StackTraceHidden] static void ThrowReadOnlyTrieException() => throw new TrieException("Commits are not allowed on this trie."); + + [DoesNotReturn, StackTraceHidden] + private static void ThrowInvalidDataException(TrieNode originalNode) + { + throw new InvalidDataException( + $"Extension {originalNode.Keccak} has no child."); + } + + [DoesNotReturn, StackTraceHidden] + private static void ThrowMissingChildException(TrieNode node) + { + throw new TrieException( + $"Found an {nameof(NodeType.Extension)} {node.Keccak} that is missing a child."); + } + + [DoesNotReturn, StackTraceHidden] + private static void ThrowMissingPrefixException() + { + throw new InvalidDataException("An attempt to visit a node without a prefix path."); + } } } diff --git a/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs b/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs index 3a0d52617152..dc6d9b6d1fa0 100644 --- a/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs @@ -3,11 +3,8 @@ using System; using System.Numerics; -using System.Runtime.CompilerServices; using Nethermind.Core; -using Nethermind.Core.Collections; using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; using Nethermind.Trie.Pruning; namespace Nethermind.Trie; @@ -16,8 +13,8 @@ public sealed class PreCachedTrieStore : ITrieStore { private readonly ITrieStore _inner; private readonly NodeStorageCache _preBlockCache; - private readonly SeqlockCache.ValueFactory _loadRlp; - private readonly SeqlockCache.ValueFactory _tryLoadRlp; + private readonly Func _loadRlp; + private readonly Func _tryLoadRlp; public PreCachedTrieStore(ITrieStore inner, NodeStorageCache cache) { @@ -25,8 +22,8 @@ public PreCachedTrieStore(ITrieStore inner, NodeStorageCache cache) _preBlockCache = cache; // Capture the delegate once for default path to avoid the allocation of the lambda per call - _loadRlp = (in NodeKey key) => _inner.LoadRlp(key.Address, in key.Path, key.Hash, flags: ReadFlags.None); - _tryLoadRlp = (in NodeKey key) => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash, flags: ReadFlags.None); + _loadRlp = (NodeKey key) => _inner.LoadRlp(key.Address, in key.Path, key.Hash, flags: ReadFlags.None); + _tryLoadRlp = (NodeKey key) => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash, flags: ReadFlags.None); } public void Dispose() @@ -46,7 +43,8 @@ public IBlockCommitter BeginBlockCommit(long blockNumber) public bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 keccak) { - byte[]? rlp = _preBlockCache.GetOrAdd(new(address, in path, in keccak), _tryLoadRlp); + byte[]? rlp = _preBlockCache.GetOrAdd(new(address, in path, in keccak), + key => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash)); return rlp is not null; } @@ -62,17 +60,17 @@ public bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 kecc public byte[]? LoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => _preBlockCache.GetOrAdd(new(address, in path, hash), flags == ReadFlags.None ? _loadRlp : - (in NodeKey key) => _inner.LoadRlp(key.Address, in key.Path, key.Hash, flags)); + key => _inner.LoadRlp(key.Address, in key.Path, key.Hash, flags)); public byte[]? TryLoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => _preBlockCache.GetOrAdd(new(address, in path, hash), flags == ReadFlags.None ? _tryLoadRlp : - (in NodeKey key) => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash, flags)); + key => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash, flags)); public INodeStorage.KeyScheme Scheme => _inner.Scheme; } -public readonly struct NodeKey : IEquatable, IHash64bit +public readonly struct NodeKey : IEquatable { public readonly Hash256? Address; public readonly TreePath Path; @@ -92,28 +90,15 @@ public NodeKey(Hash256? address, in TreePath path, Hash256 hash) Hash = hash; } - public bool Equals(NodeKey other) => Equals(in other); + public bool Equals(NodeKey other) => + Address == other.Address && Path.Equals(in other.Path) && Hash.Equals(other.Hash); public override bool Equals(object? obj) => obj is NodeKey key && Equals(key); - public bool Equals(in NodeKey other) => - Address == other.Address && Path.Equals(in other.Path) && Hash.Equals(other.Hash); - public override int GetHashCode() { uint hashCode0 = (uint)Hash.GetHashCode(); ulong hashCode1 = ((ulong)(uint)Path.GetHashCode() << 32) | (uint)(Address?.GetHashCode() ?? 1); return (int)BitOperations.Crc32C(hashCode0, hashCode1); } - - public long GetHashCode64() - { - long hashCode0 = Address is null ? 1L : SpanExtensions.FastHash64For32Bytes(ref Unsafe.As(ref Unsafe.AsRef(in Address.ValueHash256))); - long hashCode1 = SpanExtensions.FastHash64For32Bytes(ref Unsafe.As(ref Unsafe.AsRef(in Hash.ValueHash256))); - long hashCode2 = SpanExtensions.FastHash64For32Bytes(ref Unsafe.As(ref Unsafe.AsRef(in Path.Path))); - - // Rotations spaced by 64/3 ensure way 0 (bits 0-13) and way 1 (bits 42-55) - // sample non-overlapping 14-bit windows from each input - return hashCode1 + (long)BitOperations.RotateLeft((ulong)hashCode0, 21) + (long)BitOperations.RotateLeft((ulong)hashCode2, 42); - } } diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs index abd7da715b18..a53f33528f54 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs @@ -1228,6 +1228,11 @@ public bool IsNoLongerNeeded(long lastCommit) && lastCommit < LatestCommittedBlockNumber - _maxDepth; } + private bool IsStillNeeded(long lastCommit) + { + return !IsNoLongerNeeded(lastCommit); + } + private void AnnounceReorgBoundaries() { if (LatestCommittedBlockNumber < 1) diff --git a/src/Nethermind/Nethermind.Trie/TrackingCappedArrayPool.cs b/src/Nethermind/Nethermind.Trie/TrackingCappedArrayPool.cs index c0b679074f99..82c39b94b134 100644 --- a/src/Nethermind/Nethermind.Trie/TrackingCappedArrayPool.cs +++ b/src/Nethermind/Nethermind.Trie/TrackingCappedArrayPool.cs @@ -3,9 +3,7 @@ using System; using System.Buffers; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Nethermind.Core.Buffers; @@ -15,16 +13,21 @@ namespace Nethermind.Trie; /// /// Track every rented CappedArray and return them all at once /// -public sealed class TrackingCappedArrayPool(int initialCapacity, ArrayPool arrayPool = null, bool canBeParallel = true) : ICappedArrayPool, IDisposable +public sealed class TrackingCappedArrayPool : ICappedArrayPool, IDisposable { - private readonly ConcurrentQueue? _rentedQueue = canBeParallel ? new() : null; - private readonly List? _rentedList = canBeParallel ? null : new(initialCapacity); - private readonly ArrayPool? _arrayPool = arrayPool; + private readonly List _rentedBuffers; + private readonly ArrayPool? _arrayPool; public TrackingCappedArrayPool() : this(0) { } + public TrackingCappedArrayPool(int initialCapacity, ArrayPool arrayPool = null) + { + _rentedBuffers = new List(initialCapacity); + _arrayPool = arrayPool; + } + public CappedArray Rent(int size) { if (size == 0) @@ -34,16 +37,9 @@ public CappedArray Rent(int size) // Devirtualize shared array pool by referring directly to it byte[] array = _arrayPool?.Rent(size) ?? ArrayPool.Shared.Rent(size); - CappedArray rented = new(array, size); + CappedArray rented = new CappedArray(array, size); array.AsSpan().Clear(); - if (_rentedQueue is not null) - { - _rentedQueue.Enqueue(array); - } - else - { - _rentedList.Add(array); - } + lock (_rentedBuffers) _rentedBuffers.Add(array); return rented; } @@ -53,16 +49,9 @@ public void Return(in CappedArray buffer) public void Dispose() { - if (_arrayPool is not null) - { - DisposeCustomArrayPool(); - return; - } - - ConcurrentQueue? rentedQueue = _rentedQueue; - if (rentedQueue is not null) + if (_arrayPool is null) { - while (rentedQueue.TryDequeue(out byte[]? rentedBuffer)) + foreach (byte[] rentedBuffer in CollectionsMarshal.AsSpan(_rentedBuffers)) { // Devirtualize shared array pool by referring directly to it ArrayPool.Shared.Return(rentedBuffer); @@ -70,32 +59,9 @@ public void Dispose() } else { - Span items = CollectionsMarshal.AsSpan(_rentedList); - foreach (byte[] rentedBuffer in items) - { - // Devirtualize shared array pool by referring directly to it - ArrayPool.Shared.Return(rentedBuffer); - } - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] - private void DisposeCustomArrayPool() - { - ArrayPool arrayPool = _arrayPool; - if (_rentedQueue is not null) - { - while (_rentedQueue.TryDequeue(out byte[]? rentedBuffer)) - { - arrayPool.Return(rentedBuffer); - } - } - else - { - Span items = CollectionsMarshal.AsSpan(_rentedList); - foreach (byte[] rentedBuffer in items) + foreach (byte[] rentedBuffer in CollectionsMarshal.AsSpan(_rentedBuffers)) { - arrayPool.Return(rentedBuffer); + _arrayPool.Return(rentedBuffer); } } } diff --git a/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs b/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs index ef8b70015635..4b711e210f17 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs @@ -137,7 +137,7 @@ public static SpanSource RlpEncodeBranch(TrieNode item, ITrieNodeResolver tree, Metrics.TreeNodeRlpEncodings++; const int valueRlpLength = 1; - int contentLength = valueRlpLength + (UseParallel(canBeParallel, item) ? GetChildrenRlpLengthForBranchParallel(tree, ref path, item, pool) : GetChildrenRlpLengthForBranch(tree, ref path, item, pool)); + int contentLength = valueRlpLength + (UseParallel(canBeParallel) ? GetChildrenRlpLengthForBranchParallel(tree, ref path, item, pool) : GetChildrenRlpLengthForBranch(tree, ref path, item, pool)); int sequenceLength = Rlp.LengthOfSequence(contentLength); SpanSource result = pool.SafeRentBuffer(sequenceLength); Span resultSpan = result.Span; @@ -148,26 +148,7 @@ public static SpanSource RlpEncodeBranch(TrieNode item, ITrieNodeResolver tree, return result; - static bool UseParallel(bool canBeParallel, TrieNode item) - { - if (Environment.ProcessorCount <= 1 || !canBeParallel) - { - return false; - } - - const int MinChildrenForParallel = 4; - int nonNullChildren = 0; - for (int i = 0; i < BranchesCount; i++) - { - object? data = item._nodeData[i]; - if (data is not null && !ReferenceEquals(data, _nullNode) && ++nonNullChildren >= MinChildrenForParallel) - { - return true; - } - } - - return false; - } + static bool UseParallel(bool canBeParallel) => Environment.ProcessorCount > 1 && canBeParallel; } private static int GetChildrenRlpLengthForBranch(ITrieNodeResolver tree, ref TreePath path, TrieNode item, ICappedArrayPool? bufferPool) diff --git a/src/Nethermind/Nethermind.Trie/TrieNode.cs b/src/Nethermind/Nethermind.Trie/TrieNode.cs index e660f86c5d87..52dc5e0b1c9b 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNode.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNode.cs @@ -32,6 +32,7 @@ public sealed partial class TrieNode #endif private static readonly object _nullNode = new(); + private static readonly TrieNodeDecoder _nodeDecoder = new(); private static readonly AccountDecoder _accountDecoder = new(); private const byte _dirtyMask = 0b001; diff --git a/src/Nethermind/Nethermind.Trie/VisitContext.cs b/src/Nethermind/Nethermind.Trie/VisitContext.cs index e636efd9557d..5d8f3f0c0c7e 100644 --- a/src/Nethermind/Nethermind.Trie/VisitContext.cs +++ b/src/Nethermind/Nethermind.Trie/VisitContext.cs @@ -58,6 +58,7 @@ public SmallTrieVisitContext(TrieVisitContext trieVisitContext) } public byte Level { get; internal set; } + private byte _branchChildIndex = 255; private byte _flags = 0; private const byte StorageFlag = 1; diff --git a/src/Nethermind/Nethermind.TxPool.Test/BlobTxStorageTests.cs b/src/Nethermind/Nethermind.TxPool.Test/BlobTxStorageTests.cs index 802844f03cd1..b8333b8af243 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/BlobTxStorageTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/BlobTxStorageTests.cs @@ -4,16 +4,12 @@ using System; using FluentAssertions; using Nethermind.Core; -using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; -using Nethermind.Crypto; -using Nethermind.Int256; using NUnit.Framework; namespace Nethermind.TxPool.Test; [TestFixture] -[Parallelizable(ParallelScope.All)] public class BlobTxStorageTests { [Test] @@ -36,100 +32,4 @@ public void should_throw_when_trying_to_add_tx_with_null_hash() Action act = () => blobTxStorage.Add(tx); act.Should().Throw(); } - - [Test] - public void TryGetMany_should_return_zero_for_empty_batch() - { - BlobTxStorage blobTxStorage = new(); - Transaction[] results = new Transaction[0]; - - int found = blobTxStorage.TryGetMany([], 0, results); - found.Should().Be(0); - } - - [Test] - public void TryGetMany_should_batch_retrieve_stored_transactions() - { - BlobTxStorage blobTxStorage = new(); - EthereumEcdsa ecdsa = new(BlockchainIds.Mainnet); - - Transaction[] txs = new Transaction[3]; - TxLookupKey[] keys = new TxLookupKey[3]; - - for (int i = 0; i < 3; i++) - { - txs[i] = Build.A.Transaction - .WithShardBlobTxTypeAndFields() - .WithMaxFeePerGas(1.GWei()) - .WithMaxPriorityFeePerGas(1.GWei()) - .WithNonce((UInt256)i) - .SignedAndResolved(ecdsa, TestItem.PrivateKeys[i]).TestObject; - - blobTxStorage.Add(txs[i]); - keys[i] = new TxLookupKey(txs[i].Hash, txs[i].SenderAddress!, txs[i].Timestamp); - } - - Transaction[] results = new Transaction[3]; - int found = blobTxStorage.TryGetMany(keys, 3, results); - - found.Should().Be(3); - for (int i = 0; i < 3; i++) - { - results[i].Should().BeEquivalentTo(txs[i], static options => options - .Excluding(static t => t.GasBottleneck) - .Excluding(static t => t.PoolIndex)); - } - } - - [Test] - public void TryGetMany_should_handle_mix_of_existing_and_missing_keys() - { - BlobTxStorage blobTxStorage = new(); - EthereumEcdsa ecdsa = new(BlockchainIds.Mainnet); - - Transaction[] txs = new Transaction[2]; - for (int i = 0; i < 2; i++) - { - txs[i] = Build.A.Transaction - .WithShardBlobTxTypeAndFields() - .WithMaxFeePerGas(1.GWei()) - .WithMaxPriorityFeePerGas(1.GWei()) - .WithNonce((UInt256)i) - .SignedAndResolved(ecdsa, TestItem.PrivateKeys[i]).TestObject; - - blobTxStorage.Add(txs[i]); - } - - TxLookupKey[] keys = new TxLookupKey[3]; - keys[0] = new TxLookupKey(txs[0].Hash, txs[0].SenderAddress!, txs[0].Timestamp); - keys[1] = new TxLookupKey(txs[1].Hash, txs[1].SenderAddress!, txs[1].Timestamp); - keys[2] = new TxLookupKey(TestItem.KeccakA, TestItem.AddressC, UInt256.One); - - Transaction[] results = new Transaction[3]; - int found = blobTxStorage.TryGetMany(keys, 3, results); - - found.Should().Be(2); - results[0].Should().NotBeNull(); - results[1].Should().NotBeNull(); - results[2].Should().BeNull(); - } - - [Test] - public void TryGetMany_should_handle_all_missing_keys() - { - BlobTxStorage blobTxStorage = new(); - - TxLookupKey[] keys = - [ - new TxLookupKey(TestItem.KeccakA, TestItem.AddressA, UInt256.One), - new TxLookupKey(TestItem.KeccakB, TestItem.AddressB, UInt256.One), - ]; - - Transaction[] results = new Transaction[2]; - int found = blobTxStorage.TryGetMany(keys, 2, results); - - found.Should().Be(0); - results[0].Should().BeNull(); - results[1].Should().BeNull(); - } } diff --git a/src/Nethermind/Nethermind.TxPool.Test/Collections/SortedPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/Collections/SortedPoolTests.cs index b2820b770334..758a9c9ff3da 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/Collections/SortedPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/Collections/SortedPoolTests.cs @@ -20,8 +20,6 @@ namespace Nethermind.TxPool.Test.Collections { [TestFixture] - [Parallelizable(ParallelScope.All)] - [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class SortedPoolTests { private const int Capacity = 16; diff --git a/src/Nethermind/Nethermind.TxPool.Test/CompetingTransactionEqualityComparerTests.cs b/src/Nethermind/Nethermind.TxPool.Test/CompetingTransactionEqualityComparerTests.cs index c5fab4a47687..9d316746a3f5 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/CompetingTransactionEqualityComparerTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/CompetingTransactionEqualityComparerTests.cs @@ -9,7 +9,6 @@ namespace Nethermind.TxPool.Test { - [Parallelizable(ParallelScope.All)] public class CompetingTransactionEqualityComparerTests { public static IEnumerable TestCases diff --git a/src/Nethermind/Nethermind.TxPool.Test/DelegatedAccountFilterTest.cs b/src/Nethermind/Nethermind.TxPool.Test/DelegatedAccountFilterTest.cs index b7d7e3590d5c..c7d004f471f2 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/DelegatedAccountFilterTest.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/DelegatedAccountFilterTest.cs @@ -19,7 +19,6 @@ namespace Nethermind.TxPool.Test; -[Parallelizable(ParallelScope.All)] internal class DelegatedAccountFilterTest { [Test] @@ -27,7 +26,7 @@ public void Accept_SenderIsNotDelegated_ReturnsAccepted() { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For(), new DelegationCache()); Transaction transaction = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; @@ -47,7 +46,7 @@ public void Accept_SenderIsDelegatedWithNoTransactionsInPool_ReturnsAccepted() stateProvider.InsertCode(code, TestItem.AddressA); IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, stateProvider, new DelegationCache()); Transaction transaction = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; @@ -63,7 +62,7 @@ public void Accept_SenderIsDelegatedWithOneTransactionInPoolWithSameNonce_Return { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); Transaction inPool = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; standardPool.TryInsert(inPool.Hash, inPool); @@ -85,7 +84,7 @@ public void Accept_SenderIsDelegatedWithOneTransactionInPoolWithDifferentNonce_R { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); Transaction inPool = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; standardPool.TryInsert(inPool.Hash, inPool); @@ -112,7 +111,7 @@ public void Accept_Eip7702IsNotActivated_ReturnsExpected(bool isActive, AcceptTx { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(isActive ? Prague.Instance : Cancun.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); Transaction inPool = Build.A.Transaction.WithNonce(0).SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; standardPool.TryInsert(inPool.Hash, inPool); @@ -141,7 +140,7 @@ public void Accept_SenderHasPendingDelegation_OnlyAcceptsIfNonceIsExactMatch(int { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegationCache pendingDelegations = new(); pendingDelegations.IncrementDelegationCount(TestItem.AddressA); @@ -162,7 +161,7 @@ public void Accept_AuthorityHasPendingTransaction_ReturnsDelegatorHasPendingTx(b { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For(), new()); Transaction transaction; @@ -201,7 +200,7 @@ public void Accept_SetCodeTxHasAuthorityWithPendingTx_ReturnsDelegatorHasPending { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegationCache pendingDelegations = new(); pendingDelegations.IncrementDelegationCount(TestItem.AddressA); diff --git a/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs b/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs index f5d801d747a8..dce8e3bde40c 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs @@ -19,8 +19,6 @@ namespace Nethermind.TxPool.Test; -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class NonceManagerTests { private ISpecProvider _specProvider; diff --git a/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs b/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs index 4bebaa002d24..cf9b832a7cb3 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs @@ -19,8 +19,6 @@ namespace Nethermind.TxPool.Test { [TestFixture(true)] [TestFixture(false)] - [Parallelizable(ParallelScope.All)] - [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class ReceiptStorageTests { private readonly bool _useEip2718; diff --git a/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs b/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs index 1ee4370de27c..9b09028fedaa 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs @@ -3,16 +3,16 @@ using Nethermind.Core; using Nethermind.Logging; +using NSubstitute; using NUnit.Framework; using System; using System.Threading; using System.Threading.Tasks; +using Nethermind.Core.Test; namespace Nethermind.TxPool.Test; [TestFixture] -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class RetryCacheTests { public readonly struct ResourceRequestMessage : INew @@ -23,27 +23,10 @@ public class RetryCacheTests public interface ITestHandler : IMessageHandler; - /// - /// A test handler that tracks HandleMessage calls without using NSubstitute. - /// - private class TestHandler : ITestHandler - { - private int _handleMessageCallCount; - public int HandleMessageCallCount => _handleMessageCallCount; - public bool WasCalled => _handleMessageCallCount > 0; - public Action OnHandleMessage { get; set; } - - public void HandleMessage(ResourceRequestMessage message) - { - Interlocked.Increment(ref _handleMessageCallCount); - OnHandleMessage?.Invoke(message); - } - } - private CancellationTokenSource _cancellationTokenSource; private RetryCache _cache; - private readonly int Timeout = 10000; + private readonly int Timeout = 5000; [SetUp] public void Setup() @@ -62,8 +45,8 @@ public void TearDown() [Test] public void Announced_SameResourceDifferentNode_ReturnsEnqueued() { - AnnounceResult result1 = _cache.Announced(1, new TestHandler()); - AnnounceResult result2 = _cache.Announced(1, new TestHandler()); + AnnounceResult result1 = _cache.Announced(1, Substitute.For()); + AnnounceResult result2 = _cache.Announced(1, Substitute.For()); Assert.That(result1, Is.EqualTo(AnnounceResult.RequestRequired)); Assert.That(result2, Is.EqualTo(AnnounceResult.Delayed)); @@ -72,57 +55,58 @@ public void Announced_SameResourceDifferentNode_ReturnsEnqueued() [Test] public void Announced_AfterTimeout_ExecutesRetryRequests() { - TestHandler request1 = new(); - TestHandler request2 = new(); + ITestHandler request1 = Substitute.For(); + ITestHandler request2 = Substitute.For(); _cache.Announced(1, request1); _cache.Announced(1, request2); - Assert.That(() => request2.WasCalled, Is.True.After(Timeout, 100)); - Assert.That(request1.WasCalled, Is.False); + Assert.That(() => request2.ReceivedCallsMatching(r => r.HandleMessage(Arg.Any())), Is.True.After(Timeout, 100)); + request1.DidNotReceive().HandleMessage(Arg.Any()); } [Test] public void Announced_MultipleResources_ExecutesAllRetryRequestsExceptInitialOne() { - TestHandler request1 = new(); - TestHandler request2 = new(); - TestHandler request3 = new(); - TestHandler request4 = new(); + ITestHandler request1 = Substitute.For(); + ITestHandler request2 = Substitute.For(); + ITestHandler request3 = Substitute.For(); + ITestHandler request4 = Substitute.For(); _cache.Announced(1, request1); _cache.Announced(1, request2); _cache.Announced(2, request3); _cache.Announced(2, request4); - Assert.That(() => request2.WasCalled, Is.True.After(Timeout, 100)); - Assert.That(() => request4.WasCalled, Is.True.After(Timeout, 100)); - Assert.That(request1.WasCalled, Is.False); - Assert.That(request3.WasCalled, Is.False); + Assert.That(() => request2.ReceivedCallsMatching(r => r.HandleMessage(Arg.Any())), Is.True.After(Timeout, 100)); + Assert.That(() => request4.ReceivedCallsMatching(r => r.HandleMessage(Arg.Any())), Is.True.After(Timeout, 100)); + request1.DidNotReceive().HandleMessage(Arg.Any()); + request3.DidNotReceive().HandleMessage(Arg.Any()); } [Test] public void Received_RemovesResourceFromRetryQueue() { - _cache.Announced(1, new TestHandler()); + _cache.Announced(1, Substitute.For()); _cache.Received(1); - AnnounceResult result = _cache.Announced(1, new TestHandler()); + AnnounceResult result = _cache.Announced(1, Substitute.For()); Assert.That(result, Is.EqualTo(AnnounceResult.RequestRequired)); } [Test] public async Task Received_BeforeTimeout_PreventsRetryExecution() { - TestHandler request = new(); + ITestHandler request = Substitute.For(); _cache.Announced(1, request); _cache.Announced(1, request); _cache.Received(1); + await Task.Delay(Timeout, _cancellationTokenSource.Token); - Assert.That(request.WasCalled, Is.False); + request.DidNotReceive().HandleMessage(ResourceRequestMessage.New(1)); } [Test] @@ -132,7 +116,9 @@ public async Task Clear_cache_after_timeout() { Parallel.For(0, 100, (j) => { - _cache.Announced(i, new TestHandler()); + ITestHandler request = Substitute.For(); + + _cache.Announced(i, request); }); }); @@ -142,39 +128,40 @@ public async Task Clear_cache_after_timeout() } [Test] - [Retry(3)] public void RetryExecution_HandlesExceptions() { - TestHandler faultyRequest = new() { OnHandleMessage = _ => throw new InvalidOperationException("Test exception") }; - TestHandler normalRequest = new(); + ITestHandler faultyRequest = Substitute.For(); + ITestHandler normalRequest = Substitute.For(); + + faultyRequest.When(x => x.HandleMessage(Arg.Any())).Do(x => throw new InvalidOperationException("Test exception")); - _cache.Announced(1, new TestHandler()); + _cache.Announced(1, Substitute.For()); _cache.Announced(1, faultyRequest); _cache.Announced(1, normalRequest); - Assert.That(() => normalRequest.WasCalled, Is.True.After(Timeout, 100)); + Assert.That(() => normalRequest.ReceivedCallsMatching(r => r.HandleMessage(ResourceRequestMessage.New(1))), Is.True.After(Timeout, 100)); } [Test] public async Task CancellationToken_StopsProcessing() { - TestHandler request = new(); + ITestHandler request = Substitute.For(); _cache.Announced(1, request); await _cancellationTokenSource.CancelAsync(); await Task.Delay(Timeout); - Assert.That(request.WasCalled, Is.False); + request.DidNotReceive().HandleMessage(Arg.Any()); } [Test] public async Task Announced_AfterRetryInProgress_ReturnsNew() { - _cache.Announced(1, new TestHandler()); + _cache.Announced(1, Substitute.For()); await Task.Delay(Timeout, _cancellationTokenSource.Token); - Assert.That(() => _cache.Announced(1, new TestHandler()), Is.EqualTo(AnnounceResult.RequestRequired).After(Timeout, 100)); + Assert.That(() => _cache.Announced(1, Substitute.For()), Is.EqualTo(AnnounceResult.RequestRequired).After(Timeout, 100)); } [Test] diff --git a/src/Nethermind/Nethermind.TxPool.Test/TestBlockTree.cs b/src/Nethermind/Nethermind.TxPool.Test/TestBlockTree.cs deleted file mode 100644 index a8473dc82663..000000000000 --- a/src/Nethermind/Nethermind.TxPool.Test/TestBlockTree.cs +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -#nullable enable - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Nethermind.Blockchain; -using Nethermind.Blockchain.Find; -using Nethermind.Blockchain.Visitors; -using Nethermind.Core; -using Nethermind.Core.Collections; -using Nethermind.Core.Crypto; - -namespace Nethermind.TxPool.Test; - -/// -/// A minimal IBlockTree implementation for testing that avoids NSubstitute's -/// static state issues when running tests in parallel. -/// -internal class TestBlockTree : IBlockTree -{ - public Block? Head { get; set; } - public BlockHeader? BestSuggestedHeader { get; set; } - - public event EventHandler? BlockAddedToMain; - public event EventHandler? NewBestSuggestedBlock { add { } remove { } } - public event EventHandler? NewSuggestedBlock { add { } remove { } } - public event EventHandler? NewHeadBlock { add { } remove { } } - public event EventHandler? OnUpdateMainChain { add { } remove { } } - public event EventHandler? OnForkChoiceUpdated { add { } remove { } } - - public void RaiseBlockAddedToMain(BlockReplacementEventArgs args) - { - BlockAddedToMain?.Invoke(this, args); - } - - public BlockHeader FindBestSuggestedHeader() => BestSuggestedHeader!; - - // IBlockFinder implementation - public Hash256 HeadHash => Head?.Hash ?? Keccak.Zero; - public Hash256 GenesisHash => Keccak.Zero; - public Hash256? PendingHash => null; - public Hash256? FinalizedHash => null; - public Hash256? SafeHash => null; - public long? BestPersistedState { get; set; } - - public Block? FindBlock(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) => null; - public Block? FindBlock(long blockNumber, BlockTreeLookupOptions options) => null; - public bool HasBlock(long blockNumber, Hash256 blockHash) => false; - public BlockHeader? FindHeader(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) => null; - public BlockHeader? FindHeader(long blockNumber, BlockTreeLookupOptions options) => null; - public Hash256? FindBlockHash(long blockNumber) => null; - public bool IsMainChain(BlockHeader blockHeader) => false; - public bool IsMainChain(Hash256 blockHash, bool throwOnMissingHash = true) => false; - public long GetLowestBlock() => 0; - - // IBlockTree implementation - public ulong NetworkId => 1; - public ulong ChainId => 1; - public BlockHeader? Genesis => null; - public Block? BestSuggestedBody => null; - public BlockHeader? BestSuggestedBeaconHeader => null; - public BlockHeader? LowestInsertedHeader { get; set; } - public BlockHeader? LowestInsertedBeaconHeader { get; set; } - public long BestKnownNumber => Head?.Number ?? 0; - public long BestKnownBeaconNumber => 0; - public bool CanAcceptNewBlocks => true; - public (long BlockNumber, Hash256 BlockHash) SyncPivot { get; set; } - public bool IsProcessingBlock { get; set; } - - public AddBlockResult Insert(BlockHeader header, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) - => AddBlockResult.Added; - - public void BulkInsertHeader(IReadOnlyList headers, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) { } - - public AddBlockResult Insert(Block block, BlockTreeInsertBlockOptions insertBlockOptions = BlockTreeInsertBlockOptions.None, - BlockTreeInsertHeaderOptions insertHeaderOptions = BlockTreeInsertHeaderOptions.None, WriteFlags bodiesWriteFlags = WriteFlags.None) - => AddBlockResult.Added; - - public void UpdateHeadBlock(Hash256 blockHash) { } - public void NewOldestBlock(long oldestBlock) { } - public AddBlockResult SuggestBlock(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) => AddBlockResult.Added; - public ValueTask SuggestBlockAsync(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) - => ValueTask.FromResult(AddBlockResult.Added); - public AddBlockResult SuggestHeader(BlockHeader header) => AddBlockResult.Added; - public bool IsKnownBlock(long number, Hash256 blockHash) => false; - public bool IsKnownBeaconBlock(long number, Hash256 blockHash) => false; - public bool WasProcessed(long number, Hash256 blockHash) => false; - public void UpdateMainChain(IReadOnlyList blocks, bool wereProcessed, bool forceHeadBlock = false) { } - public void MarkChainAsProcessed(IReadOnlyList blocks) { } - public Task Accept(IBlockTreeVisitor blockTreeVisitor, CancellationToken cancellationToken) => Task.CompletedTask; - public (BlockInfo? Info, ChainLevelInfo? Level) GetInfo(long number, Hash256 blockHash) => (null, null); - public ChainLevelInfo? FindLevel(long number) => null; - public BlockInfo FindCanonicalBlockInfo(long blockNumber) => null!; - public Hash256? FindHash(long blockNumber) => null; - public IOwnedReadOnlyList FindHeaders(Hash256 hash, int numberOfBlocks, int skip, bool reverse) - => new ArrayPoolList(0); - public void DeleteInvalidBlock(Block invalidBlock) { } - public void DeleteOldBlock(long blockNumber, Hash256 blockHash) { } - public void ForkChoiceUpdated(Hash256? finalizedBlockHash, Hash256? safeBlockBlockHash) { } - public int DeleteChainSlice(in long startNumber, long? endNumber = null, bool force = false) => 0; - public bool IsBetterThanHead(BlockHeader? header) => false; - public void UpdateBeaconMainChain(BlockInfo[]? blockInfos, long clearBeaconMainChainStartPoint) { } - public void RecalculateTreeLevels() { } -} diff --git a/src/Nethermind/Nethermind.TxPool.Test/TestChainHeadInfoProvider.cs b/src/Nethermind/Nethermind.TxPool.Test/TestChainHeadInfoProvider.cs deleted file mode 100644 index 9cb26ba9b4ea..000000000000 --- a/src/Nethermind/Nethermind.TxPool.Test/TestChainHeadInfoProvider.cs +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -#nullable enable - -using System; -using Nethermind.Core; -using Nethermind.Core.Specs; -using Nethermind.Evm.State; -using Nethermind.Int256; - -namespace Nethermind.TxPool.Test; - -/// -/// A minimal IChainHeadInfoProvider implementation for testing that avoids NSubstitute's -/// static state issues when running tests in parallel. -/// -internal class TestChainHeadInfoProvider : IChainHeadInfoProvider -{ - public IChainHeadSpecProvider SpecProvider { get; set; } = null!; - public IReadOnlyStateProvider ReadOnlyStateProvider { get; set; } = null!; - public long HeadNumber { get; set; } - public long? BlockGasLimit { get; set; } = 30_000_000; - public UInt256 CurrentBaseFee { get; set; } - public UInt256 CurrentFeePerBlobGas { get; set; } - public ProofVersion CurrentProofVersion { get; set; } - public bool IsSyncing { get; set; } - public bool IsProcessingBlock { get; set; } - public event EventHandler? HeadChanged; - - public void RaiseHeadChanged(BlockReplacementEventArgs args) - { - HeadChanged?.Invoke(this, args); - } -} diff --git a/src/Nethermind/Nethermind.TxPool.Test/TransactionExtensionsTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TransactionExtensionsTests.cs index 1028eddc5577..f8baecc5b94f 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TransactionExtensionsTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TransactionExtensionsTests.cs @@ -10,7 +10,6 @@ namespace Nethermind.TxPool.Test { [TestFixture] - [Parallelizable(ParallelScope.All)] public class TransactionExtensionsTests { [Test] diff --git a/src/Nethermind/Nethermind.TxPool.Test/TransactionPoolInfoProviderTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TransactionPoolInfoProviderTests.cs index 722f7ba43e68..f9486f29e11c 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TransactionPoolInfoProviderTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TransactionPoolInfoProviderTests.cs @@ -13,8 +13,6 @@ namespace Nethermind.TxPool.Test { [TestFixture] - [Parallelizable(ParallelScope.All)] - [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class TxPoolInfoProviderTests { private Address _address; diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs index 17daab08c3ac..696f7f1385ee 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs @@ -39,8 +39,6 @@ namespace Nethermind.TxPool.Test; [TestFixture] -[Parallelizable(ParallelScope.All)] -[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class TxBroadcasterTests { private ILogManager _logManager; @@ -50,7 +48,7 @@ public class TxBroadcasterTests private TxBroadcaster _broadcaster; private EthereumEcdsa _ethereumEcdsa; private TxPoolConfig _txPoolConfig; - private TestChainHeadInfoProvider _headInfo; + private IChainHeadInfoProvider _headInfo; [SetUp] public void Setup() @@ -61,19 +59,18 @@ public void Setup() _blockTree = Substitute.For(); _comparer = new TransactionComparerProvider(_specProvider, _blockTree).GetDefaultComparer(); _txPoolConfig = new TxPoolConfig(); - _headInfo = new TestChainHeadInfoProvider(); + _headInfo = Substitute.For(); } [TearDown] public void TearDown() => _broadcaster?.Dispose(); [Test] - public void should_not_broadcast_persisted_tx_to_peer_too_quickly() + public async Task should_not_broadcast_persisted_tx_to_peer_too_quickly() { - ManualTimestamper timestamper = new(DateTime.UtcNow); _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = 100 }; - _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager, timestamper: timestamper); - _headInfo.CurrentBaseFee = 0.GWei(); + _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); + _headInfo.CurrentBaseFee.Returns(0.GWei()); int addedTxsCount = TestItem.PrivateKeys.Length; Transaction[] transactions = new Transaction[addedTxsCount]; @@ -105,8 +102,7 @@ public void should_not_broadcast_persisted_tx_to_peer_too_quickly() peer.Received(1).SendNewTransactions(Arg.Any>(), true); - // Advance time by 2 seconds (throttle is 1 second) - timestamper.Add(TimeSpan.FromSeconds(2)); + await Task.Delay(TimeSpan.FromMilliseconds(1001)); peer.Received(1).SendNewTransactions(Arg.Any>(), true); @@ -124,7 +120,7 @@ public void should_pick_best_persistent_txs_to_broadcast([Values(1, 2, 99, 100, { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee = 0.GWei(); + _headInfo.CurrentBaseFee.Returns(0.GWei()); int addedTxsCount = TestItem.PrivateKeys.Length; Transaction[] transactions = new Transaction[addedTxsCount]; @@ -207,7 +203,7 @@ public void should_skip_large_txs_when_picking_best_persistent_txs_to_broadcast( { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee = 0.GWei(); + _headInfo.CurrentBaseFee.Returns(0.GWei()); // add 256 transactions, 10% of them is large int addedTxsCount = TestItem.PrivateKeys.Length; @@ -253,7 +249,7 @@ public void should_skip_blob_txs_when_picking_best_persistent_txs_to_broadcast([ { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee = 0.GWei(); + _headInfo.CurrentBaseFee.Returns(0.GWei()); // add 256 transactions, 10% of them is blob type int addedTxsCount = TestItem.PrivateKeys.Length; @@ -301,7 +297,7 @@ public void should_not_pick_txs_with_GasPrice_lower_than_CurrentBaseFee([Values( _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); const int currentBaseFeeInGwei = 250; - _headInfo.CurrentBaseFee = currentBaseFeeInGwei.GWei(); + _headInfo.CurrentBaseFee.Returns(currentBaseFeeInGwei.GWei()); Block headBlock = Build.A.Block .WithNumber(MainnetSpecProvider.LondonBlockNumber) .WithBaseFeePerGas(currentBaseFeeInGwei.GWei()) @@ -345,7 +341,7 @@ public void should_not_pick_txs_with_GasPrice_lower_than_CurrentBaseFee([Values( [TestCase(150, true)] public void should_not_broadcast_tx_with_MaxFeePerGas_lower_than_70_percent_of_CurrentBaseFee(int maxFeePerGas, bool shouldBroadcast) { - _headInfo.CurrentBaseFee = (UInt256)100; + _headInfo.CurrentBaseFee.Returns((UInt256)100); _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); @@ -375,7 +371,7 @@ public void should_not_pick_1559_txs_with_MaxFeePerGas_lower_than_CurrentBaseFee _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); const int currentBaseFeeInGwei = 250; - _headInfo.CurrentBaseFee = currentBaseFeeInGwei.GWei(); + _headInfo.CurrentBaseFee.Returns(currentBaseFeeInGwei.GWei()); Block headBlock = Build.A.Block .WithNumber(MainnetSpecProvider.LondonBlockNumber) .WithBaseFeePerGas(currentBaseFeeInGwei.GWei()) @@ -420,7 +416,7 @@ public void should_not_pick_blob_txs_with_MaxFeePerBlobGas_lower_than_CurrentFee _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); const int currentFeePerBlobGas = 250; - _headInfo.CurrentFeePerBlobGas = currentFeePerBlobGas.GWei(); + _headInfo.CurrentFeePerBlobGas.Returns(currentFeePerBlobGas.GWei()); // add 256 transactions with MaxFeePerBlobGas 0-255 int addedTxsCount = TestItem.PrivateKeys.Length; @@ -467,7 +463,7 @@ public void should_pick_tx_with_lowest_nonce_from_bucket() { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = 5 }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee = 0.GWei(); + _headInfo.CurrentBaseFee.Returns(0.GWei()); const int addedTxsCount = 5; Transaction[] transactions = new Transaction[addedTxsCount]; @@ -512,7 +508,7 @@ public void should_broadcast_local_tx_immediately_after_receiving_it() public void should_broadcast_full_local_tx_immediately_after_receiving_it() { _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee = 0.GWei(); + _headInfo.CurrentBaseFee.Returns(0.GWei()); ISession session = Substitute.For(); session.Node.Returns(new Node(TestItem.PublicKeyA, TestItem.IPEndPointA)); @@ -650,7 +646,7 @@ public void should_check_tx_policy_for_broadcast(bool canGossipTransactions, boo { ITxGossipPolicy txGossipPolicy = Substitute.For(); _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager, txGossipPolicy); - _headInfo.CurrentBaseFee = 0.GWei(); + _headInfo.CurrentBaseFee.Returns(0.GWei()); ISession session = Substitute.For(); session.Node.Returns(new Node(TestItem.PublicKeyA, TestItem.IPEndPointA)); @@ -712,7 +708,7 @@ public void should_rebroadcast_all_persistent_transactions_if_PeerNotificationTh [TestCase(10000, 7000)] public void should_calculate_baseFeeThreshold_correctly(int baseFee, int expectedThreshold) { - _headInfo.CurrentBaseFee = (UInt256)baseFee; + _headInfo.CurrentBaseFee.Returns((UInt256)baseFee); _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); _broadcaster.CalculateBaseFeeThreshold().Should().Be((UInt256)expectedThreshold); } @@ -721,7 +717,7 @@ public void should_calculate_baseFeeThreshold_correctly(int baseFee, int expecte public void calculation_of_baseFeeThreshold_should_handle_overflow_correctly([Values(0, 70, 100, 101, 500)] int threshold, [Values(2, 3, 4, 5, 6, 7, 8, 9, 10, 11)] int divisor) { UInt256.Divide(UInt256.MaxValue, (UInt256)divisor, out UInt256 baseFee); - _headInfo.CurrentBaseFee = baseFee; + _headInfo.CurrentBaseFee.Returns(baseFee); _txPoolConfig = new TxPoolConfig() { MinBaseFeeThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs index ce0d4cf783f7..17764368551b 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using CkzgLib; using FluentAssertions; using Nethermind.Blockchain; @@ -608,8 +607,7 @@ Transaction GetTx(bool isBlob, UInt256 nonce) _txPool.GetPendingTransactionsCount().Should().Be(firstIsBlob ? 0 : 1); _txPool.GetPendingBlobTransactionsCount().Should().Be(firstIsBlob ? 1 : 0); _stateProvider.IncrementNonce(TestItem.AddressA); - Block block = Build.A.Block.WithNumber(1).TestObject; - await RaiseBlockAddedToMainAndWaitForNewHead(block); + await RaiseBlockAddedToMainAndWaitForTransactions(1); _txPool.GetPendingTransactionsCount().Should().Be(0); _txPool.GetPendingBlobTransactionsCount().Should().Be(0); @@ -953,7 +951,7 @@ public void should_convert_blob_proofs_to_cell_proofs_if_enabled([Values(true, f EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); // update head and set correct current proof version - _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.TestObject)); + _blockTree.BlockAddedToMain += Raise.EventWith(new BlockReplacementEventArgs(Build.A.Block.TestObject)); Transaction blobTxAdded = Build.A.Transaction .WithShardBlobTxTypeAndFields() @@ -987,7 +985,7 @@ public void should_convert_blob_proofs_to_cell_proofs_if_enabled([Values(true, f public async Task should_evict_based_on_proof_version_and_fork(BlobsSupportMode poolMode, TestAction[] testActions) { Block head = _blockTree.Head; - _blockTree.BestSuggestedHeader = head.Header; + _blockTree.FindBestSuggestedHeader().Returns(head.Header); (ChainSpecBasedSpecProvider provider, _) = TestSpecHelper.LoadChainSpec(new ChainSpecJson { @@ -1055,12 +1053,12 @@ TestCaseData MakeTestCase(string testName, int finalCount, BlobsSupportMode mode } } - private async Task AddEmptyBlock() + private Task AddEmptyBlock() { BlockHeader bh = new(_blockTree.Head.Hash, Keccak.EmptyTreeHash, TestItem.AddressA, 0, _blockTree.Head.Number + 1, _blockTree.Head.GasLimit, _blockTree.Head.Timestamp + 1, []); - _blockTree.BestSuggestedHeader = bh; - Block block = new Block(bh, new BlockBody([], [])); - await RaiseBlockAddedToMainAndWaitForNewHead(block, _blockTree.Head); + _blockTree.FindBestSuggestedHeader().Returns(bh); + _blockTree.BlockAddedToMain += Raise.EventWith(new BlockReplacementEventArgs(new Block(bh, new BlockBody([], [])), _blockTree.Head)); + return Task.Delay(300); } private Transaction CreateBlobTx(PrivateKey sender, UInt256 nonce = default, int blobCount = 1, IReleaseSpec releaseSpec = default) @@ -1094,7 +1092,7 @@ public async Task should_evict_txs_with_too_many_blobs_per_tx_after_fork() }; Block head = _blockTree.Head; - _blockTree.BestSuggestedHeader = head.Header; + _blockTree.FindBestSuggestedHeader().Returns(head.Header); _txPool = CreatePool(specProvider: provider); EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); @@ -1128,7 +1126,7 @@ public async Task should_evict_txs_with_too_many_blobs_per_block_after_fork() }; Block head = _blockTree.Head; - _blockTree.BestSuggestedHeader = head.Header; + _blockTree.FindBestSuggestedHeader().Returns(head.Header); _txPool = CreatePool(specProvider: provider); EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); @@ -1157,139 +1155,5 @@ public void max_blobs_per_tx_should_not_exceed_max_blobs_per_block() Assert.That(maxBlobsPerTx, Is.EqualTo(regularMaxBlobCount)); } - - [Test] - public void should_batch_return_blobs_and_proofs_v1_from_persistent_storage() - { - // BlobCacheSize = 1 forces cache eviction after the first insert, - // so the second tx must be fetched via TryGetMany (Phase 2 DB path). - TxPoolConfig txPoolConfig = new() - { - BlobsSupport = BlobsSupportMode.Storage, - BlobCacheSize = 1, - Size = 10 - }; - BlobTxStorage blobTxStorage = new(); - _txPool = CreatePool(txPoolConfig, GetOsakaSpecProvider(), txStorage: blobTxStorage); - EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); - EnsureSenderBalance(TestItem.AddressB, UInt256.MaxValue); - - Transaction tx1 = Build.A.Transaction - .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) - .WithMaxFeePerGas(1.GWei()) - .WithMaxPriorityFeePerGas(1.GWei()) - .WithNonce(UInt256.Zero) - .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; - - Transaction tx2 = Build.A.Transaction - .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) - .WithMaxFeePerGas(1.GWei()) - .WithMaxPriorityFeePerGas(1.GWei()) - .WithNonce(UInt256.Zero) - .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyB).TestObject; - - _txPool.SubmitTx(tx1, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); - _txPool.SubmitTx(tx2, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); - - // tx1 was evicted from cache (size=1) when tx2 was inserted, - // so at least one must come from DB via TryGetMany - byte[][] requestedHashes = [tx1.BlobVersionedHashes![0]!, tx2.BlobVersionedHashes![0]!]; - byte[][] blobs = new byte[2][]; - ReadOnlyMemory[] proofs = new ReadOnlyMemory[2]; - - int found = _txPool.TryGetBlobsAndProofsV1(requestedHashes, blobs, proofs); - - found.Should().Be(2); - blobs[0].Should().NotBeNull(); - blobs[1].Should().NotBeNull(); - proofs[0].Length.Should().Be(Ckzg.CellsPerExtBlob); - proofs[1].Length.Should().Be(Ckzg.CellsPerExtBlob); - } - - [Test] - public void should_batch_return_partial_blobs_when_some_missing() - { - TxPoolConfig txPoolConfig = new() - { - BlobsSupport = BlobsSupportMode.Storage, - BlobCacheSize = 1, - Size = 10 - }; - BlobTxStorage blobTxStorage = new(); - _txPool = CreatePool(txPoolConfig, GetOsakaSpecProvider(), txStorage: blobTxStorage); - EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); - - Transaction tx1 = Build.A.Transaction - .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) - .WithMaxFeePerGas(1.GWei()) - .WithMaxPriorityFeePerGas(1.GWei()) - .WithNonce(UInt256.Zero) - .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; - - _txPool.SubmitTx(tx1, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); - - byte[] fakeBlobHash = new byte[32]; - fakeBlobHash[0] = 0x01; // versioned hash prefix - byte[][] requestedHashes = [tx1.BlobVersionedHashes![0]!, fakeBlobHash]; - byte[][] blobs = new byte[2][]; - ReadOnlyMemory[] proofs = new ReadOnlyMemory[2]; - - int found = _txPool.TryGetBlobsAndProofsV1(requestedHashes, blobs, proofs); - - found.Should().Be(1); - blobs[0].Should().NotBeNull(); - blobs[1].Should().BeNull(); - } - - [Test] - public void should_batch_return_blobs_from_cache_and_db() - { - // BlobCacheSize = 1: after inserting tx1 and tx2, only tx2 remains in cache. - // Accessing tx1 via single lookup re-populates it, evicting tx2. - // Batch lookup then exercises: tx1 from cache, tx2 from DB (TryGetMany). - TxPoolConfig txPoolConfig = new() - { - BlobsSupport = BlobsSupportMode.Storage, - BlobCacheSize = 1, - Size = 10 - }; - BlobTxStorage blobTxStorage = new(); - _txPool = CreatePool(txPoolConfig, GetOsakaSpecProvider(), txStorage: blobTxStorage); - EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); - EnsureSenderBalance(TestItem.AddressB, UInt256.MaxValue); - - Transaction tx1 = Build.A.Transaction - .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) - .WithMaxFeePerGas(1.GWei()) - .WithMaxPriorityFeePerGas(1.GWei()) - .WithNonce(UInt256.Zero) - .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; - - Transaction tx2 = Build.A.Transaction - .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) - .WithMaxFeePerGas(1.GWei()) - .WithMaxPriorityFeePerGas(1.GWei()) - .WithNonce(UInt256.Zero) - .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyB).TestObject; - - _txPool.SubmitTx(tx1, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); - _txPool.SubmitTx(tx2, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); - - // Access tx1 via single lookup — this re-populates tx1 in cache, evicting tx2 - _txPool.TryGetBlobAndProofV1(tx1.BlobVersionedHashes![0]!, out byte[] _, out byte[][] _).Should().BeTrue(); - - // Now batch lookup: tx1 from cache (just accessed), tx2 from DB - byte[][] requestedHashes = [tx1.BlobVersionedHashes![0]!, tx2.BlobVersionedHashes![0]!]; - byte[][] blobs = new byte[2][]; - ReadOnlyMemory[] proofs = new ReadOnlyMemory[2]; - - int found = _txPool.TryGetBlobsAndProofsV1(requestedHashes, blobs, proofs); - - found.Should().Be(2); - blobs[0].Should().NotBeNull(); - blobs[1].Should().NotBeNull(); - proofs[0].Length.Should().Be(Ckzg.CellsPerExtBlob); - proofs[1].Length.Should().Be(Ckzg.CellsPerExtBlob); - } } } diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs index 537752efcb27..6f431b074d40 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs @@ -36,25 +36,16 @@ namespace Nethermind.TxPool.Test { [TestFixture] - [Parallelizable(ParallelScope.All)] - [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public partial class TxPoolTests { - private const int Timeout = 10000; private ILogManager _logManager; private IEthereumEcdsa _ethereumEcdsa; private ISpecProvider _specProvider; private TxPool _txPool; private TestReadOnlyStateProvider _stateProvider; - private TestBlockTree _blockTree; + private IBlockTree _blockTree; - private const int TxGasLimit = 1_000_000; - - [OneTimeSetUp] - public static void OneTimeSetup() - { - KzgPolynomialCommitments.InitializeAsync().Wait(); - } + private readonly int _txGasLimit = 1_000_000; [SetUp] public void Setup() @@ -63,17 +54,19 @@ public void Setup() _specProvider = MainnetSpecProvider.Instance; _ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId); _stateProvider = new TestReadOnlyStateProvider(); - _blockTree = new TestBlockTree(); - Block block = Build.A.Block.WithNumber(10000000 - 1).WithBaseFeePerGas(0).TestObject; - _blockTree.Head = block; - _blockTree.BestSuggestedHeader = Build.A.BlockHeader.WithNumber(10000000).WithBaseFee(0).TestObject; + _blockTree = Substitute.For(); + Block block = Build.A.Block.WithNumber(10000000 - 1).TestObject; + _blockTree.Head.Returns(block); + _blockTree.FindBestSuggestedHeader().Returns(Build.A.BlockHeader.WithNumber(10000000).TestObject); + + KzgPolynomialCommitments.InitializeAsync().Wait(); } [Test] public void should_add_peers() { _txPool = CreatePool(); - IDictionary peers = GetPeers(); + var peers = GetPeers(); foreach ((ITxPoolPeer peer, _) in peers) { @@ -85,7 +78,7 @@ public void should_add_peers() public void should_delete_peers() { _txPool = CreatePool(); - IDictionary peers = GetPeers(); + var peers = GetPeers(); foreach ((ITxPoolPeer peer, _) in peers) { @@ -159,7 +152,7 @@ public void should_add_valid_transactions_recovering_its_address() { _txPool = CreatePool(); Transaction tx = Build.A.Transaction - .WithGasLimit(TxGasLimit) + .WithGasLimit(_txGasLimit) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); tx.SenderAddress = null; @@ -173,7 +166,7 @@ public void should_reject_transactions_from_contract_address() { _txPool = CreatePool(); Transaction tx = Build.A.Transaction - .WithGasLimit(TxGasLimit) + .WithGasLimit(_txGasLimit) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); _stateProvider.InsertCode(TestItem.AddressA, "A"u8.ToArray(), _specProvider.GetSpec((ForkActivation)1)); @@ -199,7 +192,7 @@ public void should_accept_1559_transactions_only_when_eip1559_enabled([Values(fa .WithMaxPriorityFeePerGas(5.GWei()) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); - _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.WithGasLimit(10000000).TestObject)); + _blockTree.BlockAddedToMain += Raise.EventWith(_blockTree, new BlockReplacementEventArgs(Build.A.Block.WithGasLimit(10000000).TestObject)); AcceptTxResult result = txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); txPool.GetPendingTransactionsCount().Should().Be(eip1559Enabled ? 1 : 0); result.Should().Be(eip1559Enabled ? AcceptTxResult.Accepted : AcceptTxResult.Invalid); @@ -222,7 +215,7 @@ public void should_not_ignore_insufficient_funds_for_eip1559_transactions() var headProcessed = new ManualResetEventSlim(false); txPool.TxPoolHeadChanged += (s, a) => headProcessed.Set(); - _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.WithGasLimit(10000000).TestObject)); + _blockTree.BlockAddedToMain += Raise.EventWith(_blockTree, new BlockReplacementEventArgs(Build.A.Block.WithGasLimit(10000000).TestObject)); headProcessed.Wait(); result = txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); @@ -339,7 +332,7 @@ public void should_ignore_overflow_transactions() public void should_ignore_overflow_transactions_gas_premium_and_fee_cap() { ISpecProvider specProvider = GetLondonSpecProvider(); - TxPool txPool = CreatePool(null, specProvider); + var txPool = CreatePool(null, specProvider); Transaction tx = Build.A.Transaction.WithGasPrice(UInt256.MaxValue / Transaction.BaseTxGasCost) .WithGasLimit(Transaction.BaseTxGasCost) .WithValue(Transaction.BaseTxGasCost) @@ -373,7 +366,7 @@ public void should_reject_tx_if_max_size_is_exceeded([Values(true, false)] bool Transaction tx = Build.A.Transaction.SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); - TxPoolConfig txPoolConfig = new() { MaxTxSize = tx.GetLength() - (sizeExceeded ? 1 : 0) }; + var txPoolConfig = new TxPoolConfig() { MaxTxSize = tx.GetLength() - (sizeExceeded ? 1 : 0) }; _txPool = CreatePool(txPoolConfig); AcceptTxResult result = _txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); @@ -406,7 +399,7 @@ public void should_ignore_tx_gas_limit_exceeded() { _txPool = CreatePool(); Transaction tx = Build.A.Transaction - .WithGasLimit(TxGasLimit + 1) + .WithGasLimit(_txGasLimit + 1) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); AcceptTxResult result = _txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); @@ -565,7 +558,7 @@ public void should_not_add_tx_if_already_pending_lower_nonces_are_exhausting_bal { const int gasPrice = 10; const int value = 1; - int oneTxPrice = TxGasLimit * gasPrice + value; + int oneTxPrice = _txGasLimit * gasPrice + value; _txPool = CreatePool(); Transaction[] transactions = new Transaction[10]; @@ -577,7 +570,7 @@ public void should_not_add_tx_if_already_pending_lower_nonces_are_exhausting_bal .WithSenderAddress(TestItem.AddressA) .WithNonce((UInt256)i) .WithGasPrice((UInt256)gasPrice) - .WithGasLimit(TxGasLimit) + .WithGasLimit(_txGasLimit) .WithValue(value) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; }); @@ -598,7 +591,7 @@ public void should_not_count_txs_with_stale_nonces_when_calculating_cumulative_c { const int gasPrice = 10; const int value = 1; - int oneTxPrice = TxGasLimit * gasPrice + value; + int oneTxPrice = _txGasLimit * gasPrice + value; _txPool = CreatePool(); EnsureSenderBalance(TestItem.AddressA, (UInt256)(oneTxPrice * numberOfTxsPossibleToExecuteBeforeGasExhaustion)); @@ -611,7 +604,7 @@ public void should_not_count_txs_with_stale_nonces_when_calculating_cumulative_c .WithSenderAddress(TestItem.AddressA) .WithNonce((UInt256)i) .WithGasPrice((UInt256)gasPrice) - .WithGasLimit(TxGasLimit) + .WithGasLimit(_txGasLimit) .WithValue(value) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; }); @@ -638,7 +631,7 @@ public void should_add_tx_if_cost_of_executing_all_txs_in_bucket_exceeds_balance const int count = 10; const int gasPrice = 10; const int value = 1; - int oneTxPrice = TxGasLimit * gasPrice + value; + int oneTxPrice = _txGasLimit * gasPrice + value; _txPool = CreatePool(); Transaction[] transactions = new Transaction[count]; @@ -650,7 +643,7 @@ public void should_add_tx_if_cost_of_executing_all_txs_in_bucket_exceeds_balance .WithSenderAddress(TestItem.AddressA) .WithNonce((UInt256)i) .WithGasPrice((UInt256)gasPrice) - .WithGasLimit(TxGasLimit) + .WithGasLimit(_txGasLimit) .WithValue(value) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; }); @@ -784,7 +777,7 @@ public void should_remove_txHash_from_hashCache_when_tx_removed_because_of_txPoo var headProcessed = new ManualResetEventSlim(false); _txPool.TxPoolHeadChanged += (s, a) => headProcessed.Set(); - _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.TestObject)); + _blockTree.BlockAddedToMain += Raise.EventWith(new BlockReplacementEventArgs(Build.A.Block.TestObject)); headProcessed.Wait(); _txPool.IsKnown(higherPriorityTx.Hash).Should().BeTrue(); @@ -895,11 +888,10 @@ public void should_remove_stale_txs_from_persistent_transactions(int numberOfTxs BlockReplacementEventArgs blockReplacementEventArgs = new(block, null); ManualResetEvent manualResetEvent = new(false); - _txPool.TxPoolHeadChanged += (o, e) => manualResetEvent.Set(); - _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); - bool signaled = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(Timeout)); + _txPool.RemoveTransaction(Arg.Do(t => manualResetEvent.Set())); + _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); + manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); - signaled.Should().BeTrue("TxPoolHeadChanged event should have been raised"); // transactions[nonceIncludedInBlock] was included in the block and should be removed, as well as all lower nonces. _txPool.GetOwnPendingTransactions().Length.Should().Be(numberOfTxs - nonceIncludedInBlock - 1); } @@ -934,11 +926,10 @@ public void broadcaster_should_work_well_when_there_are_no_txs_in_persistent_txs BlockReplacementEventArgs blockReplacementEventArgs = new(block, null); ManualResetEvent manualResetEvent = new(false); - _txPool.TxPoolHeadChanged += (o, e) => manualResetEvent.Set(); - _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); - bool signaled = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(Timeout)); + _txPool.RemoveTransaction(Arg.Do(t => manualResetEvent.Set())); + _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); + manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); - signaled.Should().BeTrue("TxPoolHeadChanged event should have been raised"); _txPool.GetPendingTransactionsCount().Should().Be(1); _txPool.GetOwnPendingTransactions().Length.Should().Be(1); } @@ -969,7 +960,7 @@ public async Task should_remove_transactions_concurrently() public void should_add_transactions_concurrently() { int size = 3; - TxPoolConfig config = new() { GasLimit = TxGasLimit, Size = size }; + TxPoolConfig config = new() { GasLimit = _txGasLimit, Size = size }; _txPool = CreatePool(config); foreach (PrivateKey privateKey in TestItem.PrivateKeys) @@ -1016,11 +1007,10 @@ public void should_remove_tx_from_txPool_when_included_in_block(bool sameTransac BlockReplacementEventArgs blockReplacementEventArgs = new(block, null); ManualResetEvent manualResetEvent = new(false); - _txPool.TxPoolHeadChanged += (o, e) => manualResetEvent.Set(); - _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); - bool signaled = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(Timeout)); + _txPool.RemoveTransaction(Arg.Do(t => manualResetEvent.Set())); + _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); + manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); - signaled.Should().BeTrue("TxPoolHeadChanged event should have been raised"); _txPool.GetPendingTransactionsCount().Should().Be(0); } @@ -1040,11 +1030,10 @@ public void should_not_remove_txHash_from_hashCache_when_tx_removed_because_of_i BlockReplacementEventArgs blockReplacementEventArgs = new(block, null); ManualResetEvent manualResetEvent = new(false); - _txPool.TxPoolHeadChanged += (o, e) => manualResetEvent.Set(); - _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); - bool signaled = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(Timeout)); + _txPool.RemoveTransaction(Arg.Do(t => manualResetEvent.Set())); + _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); + manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); - signaled.Should().BeTrue("TxPoolHeadChanged event should have been raised"); foreach (Transaction transaction in transactions) { _txPool.IsKnown(transaction.Hash).Should().BeTrue(); @@ -1179,9 +1168,9 @@ public void should_accept_access_list_transactions_only_when_eip2930_enabled([Va { if (!eip2930Enabled) { - _blockTree.BestSuggestedHeader = Build.A.BlockHeader.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 1).TestObject; + _blockTree.FindBestSuggestedHeader().Returns(Build.A.BlockHeader.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 1).TestObject); Block block = Build.A.Block.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 2).TestObject; - _blockTree.Head = block; + _blockTree.Head.Returns(block); } _txPool = CreatePool(null, new TestSpecProvider(eip2930Enabled ? Berlin.Instance : Istanbul.Instance)); @@ -1200,9 +1189,9 @@ public void should_accept_only_when_synced([Values(false, true)] bool isSynced, { if (!isSynced) { - _blockTree.BestSuggestedHeader = Build.A.BlockHeader.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 1).TestObject; + _blockTree.FindBestSuggestedHeader().Returns(Build.A.BlockHeader.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 1).TestObject); Block block = Build.A.Block.WithNumber(1).TestObject; - _blockTree.Head = block; + _blockTree.Head.Returns(block); } _txPool = CreatePool(null, new TestSpecProvider(Berlin.Instance)); @@ -1499,8 +1488,6 @@ public void should_increase_nonce_when_transaction_not_included_in_txPool_but_br } [Test] - [Retry(3)] - [NonParallelizable] public void should_include_transaction_after_removal() { ISpecProvider specProvider = GetLondonSpecProvider(); @@ -1536,10 +1523,12 @@ public void should_include_transaction_after_removal() _txPool.SubmitTx(expensiveTx2, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); // Rise new block event to cleanup cash and remove one expensive tx - _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.WithTransactions(expensiveTx1).TestObject)); + _blockTree.BlockAddedToMain += + Raise.Event>(this, + new BlockReplacementEventArgs(Build.A.Block.WithTransactions(expensiveTx1).TestObject)); // Wait for event processing and send txA again => should be Accepted - Assert.That(() => _txPool.SubmitTx(txA, TxHandlingOptions.None), Is.EqualTo(AcceptTxResult.Accepted).After(Timeout, 10)); + Assert.That(() => _txPool.SubmitTx(txA, TxHandlingOptions.None), Is.EqualTo(AcceptTxResult.Accepted).After(100, 10)); } [TestCase(true, 1, 1, true)] @@ -2256,7 +2245,7 @@ private TxPool CreatePool( _ethereumEcdsa, txStorage, _headInfo, - config ?? new TxPoolConfig() { GasLimit = TxGasLimit }, + config ?? new TxPoolConfig() { GasLimit = _txGasLimit }, new TxValidator(_specProvider.ChainId), _logManager, transactionComparerProvider.GetDefaultComparer(), @@ -2274,13 +2263,33 @@ private ITxPoolPeer GetPeer(PublicKey publicKey) return peer; } - private static ISpecProvider GetLondonSpecProvider() => new TestSpecProvider(London.Instance); + private static ISpecProvider GetLondonSpecProvider() + { + var specProvider = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(London.Instance); + return specProvider; + } - private static ISpecProvider GetCancunSpecProvider() => new TestSpecProvider(Cancun.Instance); + private static ISpecProvider GetCancunSpecProvider() + { + var specProvider = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(Cancun.Instance); + return specProvider; + } - private static ISpecProvider GetPragueSpecProvider() => new TestSpecProvider(Prague.Instance); + private static ISpecProvider GetPragueSpecProvider() + { + var specProvider = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(Prague.Instance); + return specProvider; + } - private static ISpecProvider GetOsakaSpecProvider() => new TestSpecProvider(Osaka.Instance); + private static ISpecProvider GetOsakaSpecProvider() + { + var specProvider = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(Osaka.Instance); + return specProvider; + } private Transaction[] AddTransactionsToPool(bool sameTransactionSenderPerPeer = true, bool sameNoncePerPeer = false, int transactionsPerPeer = 10) { @@ -2388,10 +2397,10 @@ private async Task RaiseBlockAddedToMainAndWaitForTransactions(int txCount, Bloc SemaphoreSlim semaphoreSlim = new(0, txCount); _txPool.NewPending += (o, e) => semaphoreSlim.Release(); - _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); + _blockTree.BlockAddedToMain += Raise.EventWith(blockReplacementEventArgs); for (int i = 0; i < txCount; i++) { - await semaphoreSlim.WaitAsync(1000); + await semaphoreSlim.WaitAsync(10); } } @@ -2408,7 +2417,7 @@ private async Task RaiseBlockAddedToMainAndWaitForNewHead(Block block, Block pre e => e.Number == block.Number ); - _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); + _blockTree.BlockAddedToMain += Raise.EventWith(blockReplacementEventArgs); await waitTask; } diff --git a/src/Nethermind/Nethermind.TxPool/BlobTxStorage.cs b/src/Nethermind/Nethermind.TxPool/BlobTxStorage.cs index d76d0f620a0e..cc76e38f619e 100644 --- a/src/Nethermind/Nethermind.TxPool/BlobTxStorage.cs +++ b/src/Nethermind/Nethermind.TxPool/BlobTxStorage.cs @@ -3,7 +3,6 @@ using System; using System.Buffers.Binary; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Nethermind.Core; @@ -18,9 +17,7 @@ namespace Nethermind.TxPool; public class BlobTxStorage : IBlobTxStorage { - private const int MaxPooledKeys = 128; private static readonly TxDecoder _txDecoder = TxDecoder.Instance; - private readonly ConcurrentQueue _keyPool = new(); private readonly IDb _fullBlobTxsDb; private readonly IDb _lightBlobTxsDb; private readonly IDb _processedBlobTxsDb; @@ -48,35 +45,6 @@ public bool TryGet(in ValueHash256 hash, Address sender, in UInt256 timestamp, [ return TryDecodeFullTx(txBytes, sender, out transaction); } - public int TryGetMany(TxLookupKey[] keys, int count, Transaction?[] results) - { - if (count == 0) return 0; - - // Outer array must be exact-size for the IDb indexer (uses keys.Length). - // Inner byte[64] keys are pooled via ConcurrentQueue to avoid per-call allocations. - byte[][] dbKeys = new byte[count][]; - for (int i = 0; i < dbKeys.Length; i++) - { - byte[] key = RentKey(); - GetHashPrefixedByTimestamp(keys[i].Timestamp, keys[i].Hash, key); - dbKeys[i] = key; - } - - KeyValuePair[] dbResults = _fullBlobTxsDb[dbKeys]; - - int found = 0; - for (int i = 0; i < count; i++) - { - if (TryDecodeFullTx(dbResults[i].Value, keys[i].Sender, out results[i])) - found++; - } - - for (int i = 0; i < count; i++) - ReturnKey(dbKeys[i]); - - return found; - } - public IEnumerable GetAll() { foreach (byte[] txBytes in _lightBlobTxsDb.GetAllValues()) @@ -165,14 +133,6 @@ private static bool TryDecodeLightTx(byte[]? txBytes, out LightTransaction? ligh return false; } - private byte[] RentKey() => _keyPool.TryDequeue(out byte[]? key) ? key : new byte[64]; - - private void ReturnKey(byte[] key) - { - if (_keyPool.Count < MaxPooledKeys) - _keyPool.Enqueue(key); - } - private static void GetHashPrefixedByTimestamp(UInt256 timestamp, ValueHash256 hash, Span txHashPrefixed) { timestamp.WriteBigEndian(txHashPrefixed); diff --git a/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs b/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs index c7b49199a4aa..cd552b82e484 100644 --- a/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs +++ b/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs @@ -75,46 +75,20 @@ private bool TryGetBlobAndProof( return false; } - public virtual int TryGetBlobsAndProofsV1( - byte[][] requestedBlobVersionedHashes, - byte[]?[] blobs, - ReadOnlyMemory[] proofs) + public int GetBlobCounts(byte[][] requestedBlobVersionedHashes) { - using McsLock.Disposable lockRelease = Lock.Acquire(); - int found = 0; + using var lockRelease = Lock.Acquire(); + int count = 0; - for (int i = 0; i < requestedBlobVersionedHashes.Length; i++) + foreach (byte[] requestedBlobVersionedHash in requestedBlobVersionedHashes) { - byte[] requestedBlobVersionedHash = requestedBlobVersionedHashes[i]; - if (!BlobIndex.TryGetValue(requestedBlobVersionedHash, out List? txHashes)) - continue; - - foreach (Hash256 hash in CollectionsMarshal.AsSpan(txHashes)) + if (BlobIndex.ContainsKey(requestedBlobVersionedHash)) { - if (!TryGetValueNonLocked(hash, out Transaction? blobTx) - || blobTx.BlobVersionedHashes is not { Length: > 0 }) - continue; - - bool matched = false; - for (int indexOfBlob = 0; indexOfBlob < blobTx.BlobVersionedHashes.Length; indexOfBlob++) - { - if (Bytes.AreEqual(blobTx.BlobVersionedHashes[indexOfBlob], requestedBlobVersionedHash) - && blobTx.NetworkWrapper is ShardBlobNetworkWrapper { Version: ProofVersion.V1 } wrapper) - { - blobs[i] = wrapper.Blobs[indexOfBlob]; - proofs[i] = new ReadOnlyMemory( - wrapper.Proofs, - Ckzg.CellsPerExtBlob * indexOfBlob, - Ckzg.CellsPerExtBlob); - found++; - matched = true; - break; - } - } - if (matched) break; + count += 1; } } - return found; + + return count; } protected override bool InsertCore(ValueHash256 key, Transaction value, AddressAsKey groupKey) diff --git a/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs b/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs index 18647a7f5b7c..7b4e1e4b1235 100644 --- a/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs +++ b/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs @@ -2,17 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using CkzgLib; using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; -using Nethermind.Core.Threading; using Nethermind.Logging; namespace Nethermind.TxPool.Collections; @@ -94,127 +89,6 @@ protected override bool TryGetValueNonLocked(ValueHash256 hash, [NotNullWhen(tru return false; } - public override int TryGetBlobsAndProofsV1( - byte[][] requestedBlobVersionedHashes, - byte[]?[] blobs, - ReadOnlyMemory[] proofs) - { - int found = 0; - int missCount = 0; - - // Rent arrays for Phase 2 (DB lookup of cache misses). - // Sized to 4x request length to accommodate multiple candidate tx hashes per blob - // (e.g. when the same blob versioned hash appears in multiple transactions). - int maxMisses = requestedBlobVersionedHashes.Length * 4; - TxLookupKey[] dbKeys = ArrayPool.Shared.Rent(maxMisses); - int[] missOutputIndex = ArrayPool.Shared.Rent(maxMisses); - int[] missBlobIndex = ArrayPool.Shared.Rent(maxMisses); - try - { - // Phase 1: Under lock — in-memory lookups only - using (McsLock.Disposable lockRelease = Lock.Acquire()) - { - for (int i = 0; i < requestedBlobVersionedHashes.Length; i++) - { - byte[] requestedBlobVersionedHash = requestedBlobVersionedHashes[i]; - if (!BlobIndex.TryGetValue(requestedBlobVersionedHash, out List? txHashes)) - continue; - - foreach (Hash256 hash in CollectionsMarshal.AsSpan(txHashes)) - { - if (!base.TryGetValueNonLocked(hash, out Transaction? lightTx) - || lightTx is not LightTransaction lt - || lt.ProofVersion != ProofVersion.V1 - || lightTx.BlobVersionedHashes is not { Length: > 0 }) - continue; - - int indexOfBlob = -1; - for (int b = 0; b < lightTx.BlobVersionedHashes.Length; b++) - { - if (Bytes.AreEqual(lightTx.BlobVersionedHashes[b], requestedBlobVersionedHash)) - { - indexOfBlob = b; - break; - } - } - if (indexOfBlob < 0) - continue; - - // Try cache first — on hit, populate and stop searching for this blob hash - if (_blobTxCache.TryGet(hash, out Transaction? cachedTx) - && cachedTx.NetworkWrapper is ShardBlobNetworkWrapper { Version: ProofVersion.V1 } cachedWrapper) - { - blobs[i] = cachedWrapper.Blobs[indexOfBlob]; - proofs[i] = new ReadOnlyMemory( - cachedWrapper.Proofs, - Ckzg.CellsPerExtBlob * indexOfBlob, - Ckzg.CellsPerExtBlob); - found++; - break; - } - - // Cache miss — record for Phase 2 DB lookup and continue to try - // remaining tx hashes so that if this candidate is missing from DB, - // later candidates can still satisfy the request. - if (missCount < maxMisses) - { - dbKeys[missCount] = new TxLookupKey(hash, lightTx.SenderAddress!, lightTx.Timestamp); - missOutputIndex[missCount] = i; - missBlobIndex[missCount] = indexOfBlob; - missCount++; - } - } - } - } - - // Phase 2: Outside lock — single RocksDB MultiGet for all misses - if (missCount > 0) - { - Transaction?[] dbResults = ArrayPool.Shared.Rent(missCount); - try - { - Array.Clear(dbResults, 0, missCount); - _blobTxStorage.TryGetMany(dbKeys, missCount, dbResults); - - for (int m = 0; m < missCount; m++) - { - int outIdx = missOutputIndex[m]; - - // Skip if this output slot was already filled by a cache hit or earlier candidate - if (blobs[outIdx] is not null) - continue; - - Transaction? fullTx = dbResults[m]; - if (fullTx?.NetworkWrapper is ShardBlobNetworkWrapper { Version: ProofVersion.V1 } wrapper) - { - int blobIdx = missBlobIndex[m]; - blobs[outIdx] = wrapper.Blobs[blobIdx]; - proofs[outIdx] = new ReadOnlyMemory( - wrapper.Proofs, - Ckzg.CellsPerExtBlob * blobIdx, - Ckzg.CellsPerExtBlob); - found++; - - _blobTxCache.Set(dbKeys[m].Hash, fullTx); - } - } - } - finally - { - ArrayPool.Shared.Return(dbResults, clearArray: true); - } - } - - return found; - } - finally - { - ArrayPool.Shared.Return(dbKeys, clearArray: true); - ArrayPool.Shared.Return(missOutputIndex); - ArrayPool.Shared.Return(missBlobIndex); - } - } - protected override bool Remove(ValueHash256 hash, out Transaction? tx) { if (base.Remove(hash, out tx)) diff --git a/src/Nethermind/Nethermind.TxPool/ITxPool.cs b/src/Nethermind/Nethermind.TxPool/ITxPool.cs index 9759c6bd9f8f..4e6b9a0d7f95 100644 --- a/src/Nethermind/Nethermind.TxPool/ITxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/ITxPool.cs @@ -53,8 +53,7 @@ bool TryGetBlobAndProofV0(byte[] blobVersionedHash, bool TryGetBlobAndProofV1(byte[] blobVersionedHash, [NotNullWhen(true)] out byte[]? blob, [NotNullWhen(true)] out byte[][]? cellProofs); - int TryGetBlobsAndProofsV1(byte[][] requestedBlobVersionedHashes, - byte[]?[] blobs, ReadOnlyMemory[] proofs); + int GetBlobCounts(byte[][] blobVersionedHashes); UInt256 GetLatestPendingNonce(Address address); event EventHandler NewDiscovered; event EventHandler NewPending; diff --git a/src/Nethermind/Nethermind.TxPool/ITxStorage.cs b/src/Nethermind/Nethermind.TxPool/ITxStorage.cs index beb3268f0bd8..251d723b9e05 100644 --- a/src/Nethermind/Nethermind.TxPool/ITxStorage.cs +++ b/src/Nethermind/Nethermind.TxPool/ITxStorage.cs @@ -9,12 +9,9 @@ namespace Nethermind.TxPool; -public readonly record struct TxLookupKey(ValueHash256 Hash, Address Sender, UInt256 Timestamp); - public interface ITxStorage { bool TryGet(in ValueHash256 hash, Address sender, in UInt256 timestamp, [NotNullWhen(true)] out Transaction? transaction); - int TryGetMany(TxLookupKey[] keys, int count, Transaction?[] results); IEnumerable GetAll(); void Add(Transaction transaction); void Delete(in ValueHash256 hash, in UInt256 timestamp); diff --git a/src/Nethermind/Nethermind.TxPool/NullBlobTxStorage.cs b/src/Nethermind/Nethermind.TxPool/NullBlobTxStorage.cs index e7bf5bd0f406..a3ed71f0067a 100644 --- a/src/Nethermind/Nethermind.TxPool/NullBlobTxStorage.cs +++ b/src/Nethermind/Nethermind.TxPool/NullBlobTxStorage.cs @@ -21,8 +21,6 @@ public bool TryGet(in ValueHash256 hash, Address sender, in UInt256 timestamp, [ return false; } - public int TryGetMany(TxLookupKey[] keys, int count, Transaction?[] results) => 0; - public IEnumerable GetAll() => Array.Empty(); public void Add(Transaction transaction) { } diff --git a/src/Nethermind/Nethermind.TxPool/NullTxPool.cs b/src/Nethermind/Nethermind.TxPool/NullTxPool.cs index 40c78fd606c4..9ab7f22ab6b8 100644 --- a/src/Nethermind/Nethermind.TxPool/NullTxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/NullTxPool.cs @@ -83,8 +83,7 @@ public bool TryGetBlobAndProofV1(byte[] blobVersionedHash, return false; } - public int TryGetBlobsAndProofsV1(byte[][] requestedBlobVersionedHashes, - byte[]?[] blobs, ReadOnlyMemory[] proofs) => 0; + public int GetBlobCounts(byte[][] blobVersionedHashes) => 0; public UInt256 GetLatestPendingNonce(Address address) => 0; diff --git a/src/Nethermind/Nethermind.TxPool/RetryCache.cs b/src/Nethermind/Nethermind.TxPool/RetryCache.cs index 9a797f733b86..4bd9b40626c9 100644 --- a/src/Nethermind/Nethermind.TxPool/RetryCache.cs +++ b/src/Nethermind/Nethermind.TxPool/RetryCache.cs @@ -122,7 +122,11 @@ public AnnounceResult Announced(in TResourceId resourceId, IMessageHandler AnnounceAdd(resourceId, retryHandler, out result), _announceUpdate, handler); + _retryRequests.AddOrUpdate(resourceId, (resourceId, retryHandler) => + { + return AnnounceAdd(resourceId, retryHandler, out result); + }, _announceUpdate, handler); + return result; } diff --git a/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs b/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs index 9b481c5b10fd..2c3ec6bbca67 100644 --- a/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs +++ b/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs @@ -68,24 +68,21 @@ internal class TxBroadcaster : IDisposable private readonly TimeSpan _minTimeBetweenPersistedTxBroadcast = TimeSpan.FromSeconds(1); private readonly ILogger _logger; - private readonly ITimestamper _timestamper; public TxBroadcaster(IComparer comparer, ITimerFactory timerFactory, ITxPoolConfig txPoolConfig, IChainHeadInfoProvider chainHeadInfoProvider, ILogManager? logManager, - ITxGossipPolicy? transactionsGossipPolicy = null, - ITimestamper? timestamper = null) + ITxGossipPolicy? transactionsGossipPolicy = null) { _txPoolConfig = txPoolConfig; _headInfo = chainHeadInfoProvider; - _timestamper = timestamper ?? Timestamper.Default; _txGossipPolicy = transactionsGossipPolicy ?? ShouldGossip.Instance; // Allocate closure once _gossipFilter = _txGossipPolicy.ShouldGossipTransaction; _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - _persistentTxs = new TxDistinctSortedPool(txPoolConfig.Size, comparer, logManager); + _persistentTxs = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, comparer, logManager); _accumulatedTemporaryTxs = new ResettableList(512, 4); _txsToSend = new ResettableList(512, 4); @@ -175,7 +172,7 @@ internal void BroadcastPersistentTxs() return; } - DateTimeOffset now = _timestamper.UtcNowOffset; + DateTimeOffset now = DateTimeOffset.UtcNow; if (_lastPersistedTxBroadcast + _minTimeBetweenPersistedTxBroadcast > now) { if (_logger.IsTrace) _logger.Trace($"Minimum time between persistent tx broadcast not reached."); diff --git a/src/Nethermind/Nethermind.TxPool/TxNonceTxPoolReserveSealer.cs b/src/Nethermind/Nethermind.TxPool/TxNonceTxPoolReserveSealer.cs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.NonceInfo.cs b/src/Nethermind/Nethermind.TxPool/TxPool.NonceInfo.cs new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 4846479dd29f..20aaa898432b 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -135,7 +135,7 @@ public TxPool(IEthereumEcdsa ecdsa, _broadcaster = new TxBroadcaster(comparer, TimerFactory.Default, txPoolConfig, chainHeadInfoProvider, logManager, transactionsGossipPolicy); TxPoolHeadChanged += _broadcaster.OnNewHead; - _transactions = new TxDistinctSortedPool(txPoolConfig.Size, comparer, logManager); + _transactions = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, comparer, logManager); _transactions.Removed += OnRemovedTx; _blobTransactions = txPoolConfig.BlobsSupport.IsPersistentStorage() @@ -225,9 +225,8 @@ public bool TryGetBlobAndProofV1(byte[] blobVersionedHash, [NotNullWhen(true)] out byte[][]? cellProofs) => _blobTransactions.TryGetBlobAndProofV1(blobVersionedHash, out blob, out cellProofs); - public int TryGetBlobsAndProofsV1(byte[][] requestedBlobVersionedHashes, - byte[]?[] blobs, ReadOnlyMemory[] proofs) - => _blobTransactions.TryGetBlobsAndProofsV1(requestedBlobVersionedHashes, blobs, proofs); + public int GetBlobCounts(byte[][] blobVersionedHashes) + => _blobTransactions.GetBlobCounts(blobVersionedHashes); private void OnRemovedTx(object? sender, SortedPool.SortedPoolRemovedEventArgs args) { diff --git a/src/Nethermind/Nethermind.Xdc.Test/BlockInfoTests.cs b/src/Nethermind/Nethermind.Xdc.Test/BlockInfoTests.cs index ced27d8d1c34..aba2ee0d65c8 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/BlockInfoTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/BlockInfoTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Xdc.Test; +[Parallelizable(ParallelScope.All)] internal class BlockInfoTests { [Test] diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs index 18c46daf5ecb..f41e2e596293 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs @@ -494,8 +494,8 @@ public async Task SimulateVoting() VoteDecoder voteDecoder = new VoteDecoder(); - var newHeadWaitHandle = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var newRoundWaitHandle = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var newHeadWaitHandle = new TaskCompletionSource(); + var newRoundWaitHandle = new TaskCompletionSource(); XdcContext.NewRoundSetEvent += OnNewRound; try { @@ -545,7 +545,7 @@ public async Task TriggerBlockProposal() var head = (XdcBlockHeader)BlockTree.Head!.Header; var spec = SpecProvider.GetXdcSpec(head, XdcContext.CurrentRound); - var newHeadWaitHandle = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var newHeadWaitHandle = new TaskCompletionSource(); BlockTree.NewHeadBlock += OnNewHead; try diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs index 277fc1c5d9b3..fe51092ef36d 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs @@ -13,6 +13,7 @@ namespace Nethermind.Xdc.Test; +[Parallelizable(ParallelScope.All)] internal class ProposedBlockTests { [Test] diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/RewardTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/RewardTests.cs index becec46623a9..757734e69090 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/RewardTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/RewardTests.cs @@ -117,24 +117,12 @@ await chain.AddBlock(BuildSigningTx( long current = chain.BlockTree.Head!.Number; await chain.AddBlocks((int)(targetIncludingBlockForSecondSign - current - 1)); // move so AddBlockMayHaveExtraTx produces the target - // For 4E reward calculation, the masternodes come from the second epoch switch found - // when walking backwards from 4E. The signed header (3E - mergeSignRange) is in the - // range [2E+1, 3E), so its epoch switch info provides the relevant masternodes. - // Use a masternode from that epoch to ensure the signature is counted. - EpochSwitchInfo? epochSwitchInfoFor2E = chain.EpochSwitchManager.GetEpochSwitchInfo(signedHeader3EMinusMerge); - Assert.That(epochSwitchInfoFor2E, Is.Not.Null); - PrivateKey signerForPart2 = chain.MasterNodeCandidates.First(k => k.Address == epochSwitchInfoFor2E!.Masternodes[0]); - - // Set the chain's signer to our chosen masternode - required because - // SignTransactionFilter rejects signing txs from non-current-signers - chain.Signer.SetSigner(signerForPart2); - await chain.AddBlock(BuildSigningTx( spec, signedHeader3EMinusMerge.Number, signedHeader3EMinusMerge.Hash ?? signedHeader3EMinusMerge.CalculateHash().ToHash256(), - signerForPart2, - (long)chain.ReadOnlyState.GetNonce(signerForPart2.Address))); + chain.Signer.Key!, + (long)chain.ReadOnlyState.GetNonce(chain.Signer.Address))); // --- Evaluate rewards at checkpoint (4E) --- long checkpoint4E = 4 * epochLength; diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/SyncInfoDecoderTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/SyncInfoDecoderTests.cs deleted file mode 100644 index db199e8b09e4..000000000000 --- a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/SyncInfoDecoderTests.cs +++ /dev/null @@ -1,175 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using FluentAssertions; -using Nethermind.Core.Crypto; -using Nethermind.Serialization.Rlp; -using Nethermind.Xdc.RLP; -using Nethermind.Xdc.Types; -using NUnit.Framework; -using System.Collections; - -namespace Nethermind.Xdc.Test; - -[TestFixture, Parallelizable(ParallelScope.All)] -public class SyncInfoDecoderTests -{ - public static IEnumerable SyncInfoCases - { - get - { - yield return new TestCaseData( - new SyncInfo( - new QuorumCertificate( - new BlockRoundInfo(Hash256.Zero, 1, 1), - [new Signature(new byte[64], 0), new Signature(new byte[64], 0)], - 0 - ), - new TimeoutCertificate( - 1, - [new Signature(new byte[64], 0), new Signature(new byte[64], 0)], - 0 - ) - ), - true - ); - - yield return new TestCaseData( - new SyncInfo( - new QuorumCertificate( - new BlockRoundInfo(Hash256.Zero, 1, 1), - [], - 0 - ), - new TimeoutCertificate(1, [], 0) - ), - false - ); - - yield return new TestCaseData( - new SyncInfo( - new QuorumCertificate( - new BlockRoundInfo(Hash256.Zero, ulong.MaxValue, long.MaxValue), - [], - ulong.MaxValue - ), - new TimeoutCertificate(ulong.MaxValue, [], ulong.MaxValue) - ), - true - ); - } - } - - [TestCaseSource(nameof(SyncInfoCases))] - public void EncodeDecode_RoundTrip_Matches_AllFields(SyncInfo syncInfo, bool useRlpStream) - { - var decoder = new SyncInfoDecoder(); - - Rlp encoded = decoder.Encode(syncInfo); - var stream = new RlpStream(encoded.Bytes); - SyncInfo decoded; - - if (useRlpStream) - { - decoded = decoder.Decode(stream); - } - else - { - Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(stream.Data.AsSpan()); - decoded = decoder.Decode(ref decoderContext); - } - - decoded.Should().BeEquivalentTo(syncInfo); - } - - [Test] - public void Encode_UseBothRlpStreamAndValueDecoderContext_IsEquivalentAfterReencoding() - { - SyncInfo syncInfo = new( - new QuorumCertificate( - new BlockRoundInfo(Hash256.Zero, 1, 1), - [new Signature(new byte[64], 0), new Signature(new byte[64], 0), new Signature(new byte[64], 0)], - 0 - ), - new TimeoutCertificate( - 1, - [new Signature(new byte[64], 0), new Signature(new byte[64], 0)], - 0 - ) - ); - - SyncInfoDecoder decoder = new(); - RlpStream stream = new RlpStream(decoder.GetLength(syncInfo)); - decoder.Encode(stream, syncInfo); - stream.Position = 0; - - // Decode with RlpStream - SyncInfo decodedStream = decoder.Decode(stream); - stream.Position = 0; - - // Decode with ValueDecoderContext - Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(stream.Data.AsSpan()); - SyncInfo decodedContext = decoder.Decode(ref decoderContext); - - // Both should be equivalent to original - decodedStream.Should().BeEquivalentTo(syncInfo); - decodedContext.Should().BeEquivalentTo(syncInfo); - decodedStream.Should().BeEquivalentTo(decodedContext); - } - - [Test] - public void TotalLength_Equals_GetLength() - { - SyncInfo syncInfo = new( - new QuorumCertificate( - new BlockRoundInfo(Hash256.Zero, 42, 42), - [new Signature(new byte[64], 0)], - 10 - ), - new TimeoutCertificate( - 41, - [new Signature(new byte[64], 1)], - 10 - ) - ); - - var decoder = new SyncInfoDecoder(); - Rlp encoded = decoder.Encode(syncInfo); - - int expectedTotal = decoder.GetLength(syncInfo, RlpBehaviors.None); - Assert.That(encoded.Bytes.Length, Is.EqualTo(expectedTotal), - "Encoded total length should match GetLength()."); - } - - [Test] - public void Encode_Null_ReturnsEmptySequence() - { - var decoder = new SyncInfoDecoder(); - - Rlp encoded = decoder.Encode(null!); - - Assert.That(encoded, Is.EqualTo(Rlp.OfEmptySequence)); - } - - [Test] - public void Decode_Null_ReturnsNull() - { - var decoder = new SyncInfoDecoder(); - var stream = new RlpStream(Rlp.OfEmptySequence.Bytes); - - SyncInfo decoded = decoder.Decode(stream); - - Assert.That(decoded, Is.Null); - } - - [Test] - public void Decode_EmptyByteArray_ValueDecoderContext_ReturnsNull() - { - var decoder = new SyncInfoDecoder(); - Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(Rlp.OfEmptySequence.Bytes); - - SyncInfo decoded = decoder.Decode(ref decoderContext); - - Assert.That(decoded, Is.Null); - } -} diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs index 4b545ba8cd02..02a8317d50ff 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Xdc.Test; +[Parallelizable(ParallelScope.All)] internal class XdcReorgModuleTests { [Test] diff --git a/src/Nethermind/Nethermind.Xdc/Errors/QuorumCertificateException.cs b/src/Nethermind/Nethermind.Xdc/Errors/QuorumCertificateException.cs new file mode 100644 index 000000000000..09cf944eeb3f --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/Errors/QuorumCertificateException.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Xdc.Types; + +namespace Nethermind.Xdc.Errors; + +internal class QuorumCertificateException(QuorumCertificate certificate, string message) : BlockchainException(message) +{ + public QuorumCertificate Certificate { get; } = certificate; +} diff --git a/src/Nethermind/Nethermind.Xdc/IXdcSealer.cs b/src/Nethermind/Nethermind.Xdc/IXdcSealer.cs new file mode 100644 index 000000000000..cab6b4eb0b52 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/IXdcSealer.cs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Consensus; + +namespace Nethermind.Xdc; + +internal interface IXdcSealer : ISealer +{ + bool CanSeal(ulong round, XdcBlockHeader parentHash); +} diff --git a/src/Nethermind/Nethermind.Xdc/RLP/SyncInfoDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/SyncInfoDecoder.cs deleted file mode 100644 index 0ebaf9d39ad8..000000000000 --- a/src/Nethermind/Nethermind.Xdc/RLP/SyncInfoDecoder.cs +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Serialization.Rlp; -using Nethermind.Xdc.RLP; -using Nethermind.Xdc.Types; -using System; -using System.Collections.Generic; -using System.Text; - -namespace Nethermind.Xdc; - -internal class SyncInfoDecoder : RlpValueDecoder -{ - private readonly QuorumCertificateDecoder _quorumCertificateDecoder = new(); - private readonly TimeoutCertificateDecoder _timeoutCertificateDecoder = new(); - - protected override SyncInfo DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) - { - if (decoderContext.IsNextItemNull()) - return null; - - int sequenceLength = decoderContext.ReadSequenceLength(); - int endPosition = decoderContext.Position + sequenceLength; - - QuorumCertificate highestQuorumCert = _quorumCertificateDecoder.Decode(ref decoderContext, rlpBehaviors); - TimeoutCertificate highestTimeoutCert = _timeoutCertificateDecoder.Decode(ref decoderContext, rlpBehaviors); - - if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) - { - decoderContext.Check(endPosition); - } - - return new SyncInfo(highestQuorumCert, highestTimeoutCert); - } - - protected override SyncInfo DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) - { - if (rlpStream.IsNextItemNull()) - return null; - - int sequenceLength = rlpStream.ReadSequenceLength(); - int endPosition = rlpStream.Position + sequenceLength; - - QuorumCertificate highestQuorumCert = _quorumCertificateDecoder.Decode(rlpStream, rlpBehaviors); - TimeoutCertificate highestTimeoutCert = _timeoutCertificateDecoder.Decode(rlpStream, rlpBehaviors); - - if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) - { - rlpStream.Check(endPosition); - } - - return new SyncInfo(highestQuorumCert, highestTimeoutCert); - } - - public Rlp Encode(SyncInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) - { - if (item is null) - return Rlp.OfEmptySequence; - - RlpStream rlpStream = new(GetLength(item, rlpBehaviors)); - Encode(rlpStream, item, rlpBehaviors); - - return new Rlp(rlpStream.Data.ToArray()); - } - - public override void Encode(RlpStream stream, SyncInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) - { - if (item is null) - { - stream.EncodeNullObject(); - return; - } - - stream.StartSequence(GetContentLength(item, rlpBehaviors)); - _quorumCertificateDecoder.Encode(stream, item.HighestQuorumCert, rlpBehaviors); - _timeoutCertificateDecoder.Encode(stream, item.HighestTimeoutCert, rlpBehaviors); - } - - public override int GetLength(SyncInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) - { - return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); - } - - public int GetContentLength(SyncInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) - { - if (item is null) - return 0; - - return _quorumCertificateDecoder.GetLength(item.HighestQuorumCert, rlpBehaviors) - + _timeoutCertificateDecoder.GetLength(item.HighestTimeoutCert, rlpBehaviors); - } -} diff --git a/src/Nethermind/Nethermind.Xdc/RLP/VoteDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/VoteDecoder.cs index ff3b6504f5af..9116c18635a0 100644 --- a/src/Nethermind/Nethermind.Xdc/RLP/VoteDecoder.cs +++ b/src/Nethermind/Nethermind.Xdc/RLP/VoteDecoder.cs @@ -93,7 +93,7 @@ public override int GetLength(Vote item, RlpBehaviors rlpBehaviors) private int GetContentLength(Vote item, RlpBehaviors rlpBehaviors) { return - ((rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing ? Rlp.LengthOfSequence(Signature.Size) : 0) + (rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing ? Rlp.LengthOfSequence(Signature.Size) : 0 + Rlp.LengthOf(item.GapNumber) + _xdcBlockInfoDecoder.GetLength(item.ProposedBlockInfo, rlpBehaviors); } diff --git a/src/Nethermind/Nethermind.Xdc/XdcDbNames.cs b/src/Nethermind/Nethermind.Xdc/XdcDbNames.cs new file mode 100644 index 000000000000..720beedcc121 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcDbNames.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Xdc; + +internal class XdcDbNames +{ + public static long HighestQcKey = 2000; + public static long HighestTimeoutCertKey = 2001; + public static long LockQcKey = 2002; + public static long HighestVotedRoundKey = 2004; +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcModule.cs b/src/Nethermind/Nethermind.Xdc/XdcModule.cs index 10286cd98407..ae9d992edb66 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcModule.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcModule.cs @@ -25,8 +25,6 @@ using Nethermind.Xdc.TxPool; using Nethermind.Api.Steps; using Nethermind.Synchronization; -using Nethermind.Synchronization.FastSync; -using Nethermind.Synchronization.ParallelSync; namespace Nethermind.Xdc; @@ -90,9 +88,8 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddSingleton() - // sync + // beacon sync strategy .AddSingleton() - .AddSingleton, XdcStateSyncAllocationStrategyFactory>() .AddSingleton() diff --git a/src/Nethermind/Nethermind.Xdc/XdcStateSyncAllocationStrategyFactory.cs b/src/Nethermind/Nethermind.Xdc/XdcStateSyncAllocationStrategyFactory.cs deleted file mode 100644 index 74277a46fbbf..000000000000 --- a/src/Nethermind/Nethermind.Xdc/XdcStateSyncAllocationStrategyFactory.cs +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Stats; -using Nethermind.Synchronization.FastSync; -using Nethermind.Synchronization.ParallelSync; -using Nethermind.Synchronization.Peers; -using Nethermind.Synchronization.Peers.AllocationStrategies; -using Nethermind.Synchronization.StateSync; - -namespace Nethermind.Xdc; - -public class XdcStateSyncAllocationStrategyFactory : StaticPeerAllocationStrategyFactory -{ - private static readonly IPeerAllocationStrategy DefaultStrategy = - new AllocationStrategy(new BySpeedStrategy(TransferSpeedType.NodeData, true)); - - public XdcStateSyncAllocationStrategyFactory() : base(DefaultStrategy) - { - } - - internal class AllocationStrategy : FilterPeerAllocationStrategy - { - public AllocationStrategy(IPeerAllocationStrategy strategy) : base(strategy) - { - } - - protected override bool Filter(PeerInfo peerInfo) - { - return peerInfo.CanGetSnapData() || peerInfo.SyncPeer.ProtocolVersion == 100; - } - } -} - diff --git a/src/Nethermind/Nethermind.slnx b/src/Nethermind/Nethermind.slnx index 6298a9f62d2f..c64f1a920b75 100644 --- a/src/Nethermind/Nethermind.slnx +++ b/src/Nethermind/Nethermind.slnx @@ -31,7 +31,6 @@ - diff --git a/tools/JitAsm/DisassemblyParser.cs b/tools/JitAsm/DisassemblyParser.cs deleted file mode 100644 index dcbb9687af8f..000000000000 --- a/tools/JitAsm/DisassemblyParser.cs +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Text; -using System.Text.RegularExpressions; - -namespace JitAsm; - -internal static partial class DisassemblyParser -{ - // Pattern to detect the start of a method's disassembly - // Example: ; Assembly listing for method Namespace.Type:Method(args) - [GeneratedRegex(@"^; Assembly listing for method (?.+)$", RegexOptions.Compiled | RegexOptions.Multiline)] - private static partial Regex MethodHeaderPattern(); - - // Pattern to detect end of method disassembly (next method or end) - [GeneratedRegex(@"^; Total bytes of code", RegexOptions.Compiled | RegexOptions.Multiline)] - private static partial Regex MethodEndPattern(); - - public static string Parse(string jitOutput, bool lastOnly = false) - { - if (string.IsNullOrWhiteSpace(jitOutput)) - { - return string.Empty; - } - - var result = new StringBuilder(); - var matches = MethodHeaderPattern().Matches(jitOutput); - - if (matches.Count == 0) - { - // No method headers found, return raw output if it looks like assembly - if (jitOutput.Contains("mov") || jitOutput.Contains("call") || jitOutput.Contains("ret")) - { - return jitOutput.Trim(); - } - return string.Empty; - } - - // In tier1 mode, JitDisasm captures both Tier-0 and Tier-1 compilations. - // We want the LAST compilation (Tier-1 with full optimizations). - int startIdx = lastOnly ? matches.Count - 1 : 0; - - for (int i = startIdx; i < matches.Count; i++) - { - var match = matches[i]; - var startIndex = match.Index; - - // Find the end of this method's disassembly - int endIndex = (i + 1 < matches.Count) ? matches[i + 1].Index : jitOutput.Length; - - // Extract this method's disassembly - var methodAsm = jitOutput[startIndex..endIndex].TrimEnd(); - - // Find "Total bytes of code" line and include it - var totalBytesMatch = MethodEndPattern().Match(methodAsm); - if (totalBytesMatch.Success) - { - // Find end of line after "Total bytes of code" - var lineEnd = methodAsm.IndexOf('\n', totalBytesMatch.Index); - if (lineEnd > 0) - { - methodAsm = methodAsm[..(lineEnd + 1)].TrimEnd(); - } - } - - if (result.Length > 0) - { - result.AppendLine(); - result.AppendLine(new string('-', 80)); - result.AppendLine(); - } - - result.AppendLine(methodAsm); - } - - return result.ToString().Trim(); - } - - public static IEnumerable ParseMethods(string jitOutput) - { - if (string.IsNullOrWhiteSpace(jitOutput)) - { - yield break; - } - - var matches = MethodHeaderPattern().Matches(jitOutput); - - for (int i = 0; i < matches.Count; i++) - { - var match = matches[i]; - var methodName = match.Groups["method"].Value; - var startIndex = match.Index; - - int endIndex = (i + 1 < matches.Count) ? matches[i + 1].Index : jitOutput.Length; - - var methodAsm = jitOutput[startIndex..endIndex].TrimEnd(); - - yield return new MethodDisassembly - { - MethodName = methodName, - Assembly = methodAsm - }; - } - } -} - -internal sealed class MethodDisassembly -{ - public required string MethodName { get; init; } - public required string Assembly { get; init; } -} diff --git a/tools/JitAsm/InstructionAnnotator.cs b/tools/JitAsm/InstructionAnnotator.cs deleted file mode 100644 index 068d5ab34853..000000000000 --- a/tools/JitAsm/InstructionAnnotator.cs +++ /dev/null @@ -1,441 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Text; -using System.Text.RegularExpressions; - -namespace JitAsm; - -internal static partial class InstructionAnnotator -{ - // Matches JIT assembly instruction lines: - // " add rax, rcx" - // " mov dword ptr [rbp+0x10], eax" - [GeneratedRegex(@"^\s+(?[a-z]\w*)\s+(?.+)$", RegexOptions.IgnoreCase)] - private static partial Regex InstructionLineRegex(); - - // Matches zero-operand instructions: " ret" or " nop" - [GeneratedRegex(@"^\s+(?[a-z]\w*)\s*$", RegexOptions.IgnoreCase)] - private static partial Regex ZeroOperandRegex(); - - // 64-bit registers - private static readonly HashSet Regs64 = new(StringComparer.OrdinalIgnoreCase) - { - "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rsp", "rbp", - "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15" - }; - - // 32-bit registers - private static readonly HashSet Regs32 = new(StringComparer.OrdinalIgnoreCase) - { - "eax", "ebx", "ecx", "edx", "esi", "edi", "esp", "ebp", - "r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d" - }; - - // 16-bit registers - private static readonly HashSet Regs16 = new(StringComparer.OrdinalIgnoreCase) - { - "ax", "bx", "cx", "dx", "si", "di", "sp", "bp", - "r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w" - }; - - // 8-bit registers - private static readonly HashSet Regs8 = new(StringComparer.OrdinalIgnoreCase) - { - "al", "bl", "cl", "dl", "sil", "dil", "spl", "bpl", "ah", "bh", "ch", "dh", - "r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b" - }; - - // JIT mnemonic → uops.info mnemonic mapping for conditional jumps - // JIT uses Intel-style aliases (je, jne, ja, etc.) but uops.info uses the - // canonical forms (jz, jnz, jnbe, etc.) - private static readonly Dictionary MnemonicAliases = new(StringComparer.OrdinalIgnoreCase) - { - ["je"] = "jz", - ["jne"] = "jnz", - ["ja"] = "jnbe", - ["jae"] = "jnb", - ["jb"] = "jb", // canonical - ["jbe"] = "jna", - ["jg"] = "jnle", - ["jge"] = "jnl", - ["jl"] = "jnge", - ["jle"] = "jng", - ["jc"] = "jb", - ["jnc"] = "jnb", - ["jp"] = "jp", // canonical - ["jnp"] = "jnp", // canonical - ["js"] = "js", // canonical - ["jns"] = "jns", // canonical - ["jo"] = "jo", // canonical - ["jno"] = "jno", // canonical - ["cmove"] = "cmovz", - ["cmovne"] = "cmovnz", - ["cmova"] = "cmovnbe", - ["cmovae"] = "cmovnb", - ["cmovb"] = "cmovb", // canonical - ["cmovbe"] = "cmovna", - ["cmovg"] = "cmovnle", - ["cmovge"] = "cmovnl", - ["cmovl"] = "cmovnge", - ["cmovle"] = "cmovng", - ["sete"] = "setz", - ["setne"] = "setnz", - ["seta"] = "setnbe", - ["setae"] = "setnb", - ["setb"] = "setb", // canonical - ["setbe"] = "setna", - ["setg"] = "setnle", - ["setge"] = "setnl", - ["setl"] = "setnge", - ["setle"] = "setng", - }; - - public static string Annotate(string disassembly, InstructionDb db) - { - var sb = new StringBuilder(); - var lines = JoinContinuationLines(disassembly.Split('\n')); - - foreach (string rawLine in lines) - { - string line = rawLine.TrimEnd('\r'); - - // Skip comment lines, labels, directives - if (IsNonInstructionLine(line)) - { - sb.AppendLine(line); - continue; - } - - var match = InstructionLineRegex().Match(line); - if (match.Success) - { - string mnemonic = match.Groups["mnemonic"].Value.ToLowerInvariant(); - string operandsRaw = match.Groups["operands"].Value.Trim(); - - // Skip annotations for calls and jumps to labels - if (ShouldSkipAnnotation(mnemonic, operandsRaw)) - { - sb.AppendLine(line); - continue; - } - - string pattern = ClassifyOperands(operandsRaw, mnemonic); - - // Try the original mnemonic first, then any alias - string lookupMnemonic = MnemonicAliases.TryGetValue(mnemonic, out var alias) - ? alias : mnemonic; - var info = db.Lookup(mnemonic, pattern) - ?? (lookupMnemonic != mnemonic ? db.Lookup(lookupMnemonic, pattern) : null); - - if (info is not null) - { - string annotation = FormatAnnotation(info); - // Pad the line to align annotations - int padTo = Math.Max(line.Length + 1, 55); - sb.Append(line.PadRight(padTo)); - sb.AppendLine(annotation); - } - else - { - sb.AppendLine(line); - } - continue; - } - - // Try zero-operand match - var zeroMatch = ZeroOperandRegex().Match(line); - if (zeroMatch.Success) - { - string mnemonic = zeroMatch.Groups["mnemonic"].Value.ToLowerInvariant(); - if (!ShouldSkipAnnotation(mnemonic, "")) - { - string zeroLookup = MnemonicAliases.TryGetValue(mnemonic, out var zAlias) - ? zAlias : mnemonic; - var info = db.Lookup(mnemonic, "") - ?? (zeroLookup != mnemonic ? db.Lookup(zeroLookup, "") : null); - if (info is not null) - { - string annotation = FormatAnnotation(info); - int padTo = Math.Max(line.Length + 1, 55); - sb.Append(line.PadRight(padTo)); - sb.AppendLine(annotation); - continue; - } - } - } - - sb.AppendLine(line); - } - - return sb.ToString().TrimEnd(); - } - - /// - /// Joins JIT output continuation lines. The JIT wraps long lines at ~80 chars: - /// " call \n[System.Threading.ThreadLocal`1[...]:get_Value():...]\n" - /// "; Assembly listing for method \nNamespace.Type:Method(...)\n" - /// This joins them so each logical line is a single string. - /// - private static List JoinContinuationLines(string[] rawLines) - { - var result = new List(rawLines.Length); - for (int i = 0; i < rawLines.Length; i++) - { - string line = rawLines[i].TrimEnd('\r'); - - // Keep joining while the next line looks like a continuation - while (i + 1 < rawLines.Length) - { - string next = rawLines[i + 1].TrimEnd('\r'); - if (IsContinuationLine(next)) - { - // Preserve a single space between joined parts so "call \n[Type:Method]" - // becomes "call [Type:Method]" rather than "call[Type:Method]" - line = line.TrimEnd() + " " + next.TrimStart(); - i++; - } - else - { - break; - } - } - - result.Add(line); - } - return result; - } - - /// - /// A line is a continuation if it doesn't match any known "primary" line type: - /// blank, comment (;), label (G_M...:), instruction (leading whitespace), data (RWD), or alignment. - /// - private static bool IsContinuationLine(string line) - { - if (line.Length == 0) return false; - - ReadOnlySpan trimmed = line.AsSpan().TrimEnd('\r'); - if (trimmed.Length == 0) return false; - - // Instructions start with whitespace - if (char.IsWhiteSpace(trimmed[0])) return false; - - // Comments start with ';' - if (trimmed[0] == ';') return false; - - // Labels: "G_M000_IG01:" or similar identifiers ending with ':' - // Check if line contains ':' and starts with a label-like pattern - if (trimmed.StartsWith("G_M", StringComparison.Ordinal) && trimmed.Contains(":", StringComparison.Ordinal)) - return false; - - // Read-only data table entries: "RWD00 dd ..." - if (trimmed.StartsWith("RWD", StringComparison.Ordinal)) return false; - - // Alignment directives: "align [N bytes for IG...]" - if (trimmed.StartsWith("align", StringComparison.OrdinalIgnoreCase)) return false; - - // Everything else is a continuation of the previous line - return true; - } - - private static bool IsNonInstructionLine(string line) - { - if (line.Length == 0) return true; - - // Instructions always start with whitespace (indented). - // Labels, data tables, directives, and other non-instruction lines start at column 0. - if (line[0] == ';') return true; // Comment at column 0 - if (!char.IsWhiteSpace(line[0])) return true; // Labels (G_M000_IG01:), data (RWD00), directives, etc. - - // Indented comments: " ; comment" - ReadOnlySpan trimmed = line.AsSpan().TrimStart(); - if (trimmed.Length > 0 && trimmed[0] == ';') return true; - - return false; - } - - private static bool ShouldSkipAnnotation(string mnemonic, string operands) - { - // Skip calls (to runtime helpers, methods, etc.) - if (mnemonic == "call") return true; - - // Skip ret - uops.info TP_unrolled is a microbenchmark artifact (return stack buffer - // mispredictions make the measurement meaningless for real code) - if (mnemonic == "ret") return true; - - // Skip int3/nop - not meaningful for performance analysis - if (mnemonic is "int3" or "nop" or "int") return true; - - return false; - } - - internal static string ClassifyOperands(string operandsRaw, string? mnemonic = null) - { - // Handle trailing comments after operands: "rax, rcx ; some comment" - int commentIdx = operandsRaw.IndexOf(';'); - if (commentIdx >= 0) - operandsRaw = operandsRaw[..commentIdx].TrimEnd(); - - if (string.IsNullOrWhiteSpace(operandsRaw)) - return ""; - - // Split operands by comma, but respect brackets for memory operands - var operands = SplitOperands(operandsRaw); - var parts = new List(); - - for (int i = 0; i < operands.Count; i++) - { - string op = operands[i].Trim(); - - // LEA's second operand is an address expression, not a memory load - // uops.info classifies it as "agen" (address generation) - if (mnemonic is "lea" && i == 1 && op.Contains('[')) - { - parts.Add("agen"); - continue; - } - - string classified = ClassifySingleOperand(op); - if (classified.Length > 0) - parts.Add(classified); - } - - return string.Join(",", parts); - } - - private static List SplitOperands(string operands) - { - var result = new List(); - int depth = 0; - int start = 0; - - for (int i = 0; i < operands.Length; i++) - { - char c = operands[i]; - if (c == '[') depth++; - else if (c == ']') depth--; - else if (c == ',' && depth == 0) - { - result.Add(operands[start..i]); - start = i + 1; - } - } - - result.Add(operands[start..]); - return result; - } - - private static string ClassifySingleOperand(string op) - { - // Memory operand: "dword ptr [rbp+10h]", "qword ptr [rsp+20h]", "[rax]" - if (op.Contains('[')) - { - if (op.Contains("zmmword ptr", StringComparison.OrdinalIgnoreCase)) return "m512"; - if (op.Contains("ymmword ptr", StringComparison.OrdinalIgnoreCase)) return "m256"; - if (op.Contains("xmmword ptr", StringComparison.OrdinalIgnoreCase)) return "m128"; - if (op.Contains("qword ptr", StringComparison.OrdinalIgnoreCase)) return "m64"; - // gword ptr = GC-tracked pointer-width memory (.NET JIT specific, equivalent to qword on x64) - if (op.Contains("gword ptr", StringComparison.OrdinalIgnoreCase)) return "m64"; - // bword ptr = pointer-width memory without GC tracking (.NET JIT specific, equivalent to qword on x64) - if (op.Contains("bword ptr", StringComparison.OrdinalIgnoreCase)) return "m64"; - if (op.Contains("dword ptr", StringComparison.OrdinalIgnoreCase)) return "m32"; - if (op.Contains("word ptr", StringComparison.OrdinalIgnoreCase) && - !op.Contains("dword", StringComparison.OrdinalIgnoreCase) && - !op.Contains("qword", StringComparison.OrdinalIgnoreCase) && - !op.Contains("gword", StringComparison.OrdinalIgnoreCase) && - !op.Contains("bword", StringComparison.OrdinalIgnoreCase) && - !op.Contains("xmmword", StringComparison.OrdinalIgnoreCase) && - !op.Contains("ymmword", StringComparison.OrdinalIgnoreCase) && - !op.Contains("zmmword", StringComparison.OrdinalIgnoreCase)) - return "m16"; - if (op.Contains("byte ptr", StringComparison.OrdinalIgnoreCase)) return "m8"; - return "m"; - } - - // Register operands - string regName = op.Trim(); - - // ZMM registers - if (regName.StartsWith("zmm", StringComparison.OrdinalIgnoreCase)) return "zmm"; - // YMM registers - if (regName.StartsWith("ymm", StringComparison.OrdinalIgnoreCase)) return "ymm"; - // XMM registers - if (regName.StartsWith("xmm", StringComparison.OrdinalIgnoreCase)) return "xmm"; - // K mask registers - if (regName.StartsWith("k", StringComparison.OrdinalIgnoreCase) && regName.Length <= 2 && - regName.Length > 1 && char.IsDigit(regName[1])) return "k"; - - if (Regs64.Contains(regName)) return "r64"; - if (Regs32.Contains(regName)) return "r32"; - if (Regs16.Contains(regName)) return "r16"; - if (Regs8.Contains(regName)) return "r8"; - - // Immediate: hex (0x1A, 1Ah), decimal, or negative - if (IsImmediate(regName)) - { - // Try to determine imm8 vs imm32 from value range - if (TryParseImmediate(regName, out long value)) - { - return value is >= -128 and <= 255 ? "imm8" : "imm32"; - } - return "imm"; - } - - // Label reference (for jumps) - strip SHORT/NEAR prefix added by JIT - if (regName.StartsWith("SHORT ", StringComparison.OrdinalIgnoreCase)) - regName = regName[6..].TrimStart(); - if (regName.StartsWith("NEAR ", StringComparison.OrdinalIgnoreCase)) - regName = regName[5..].TrimStart(); - - if (regName.StartsWith("G_M", StringComparison.OrdinalIgnoreCase)) - return "rel"; - - // Unknown - return ""; - } - - private static bool IsImmediate(string op) - { - if (op.Length == 0) return false; - - // Hex: 0x prefix or trailing h - if (op.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) return true; - if (op.EndsWith('h') || op.EndsWith('H')) - { - return op[..^1].All(c => char.IsAsciiHexDigit(c) || c == '-'); - } - - // Decimal (possibly negative) - return op.All(c => char.IsDigit(c) || c == '-'); - } - - private static bool TryParseImmediate(string op, out long value) - { - value = 0; - - if (op.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) - return long.TryParse(op.AsSpan(2), System.Globalization.NumberStyles.HexNumber, - System.Globalization.CultureInfo.InvariantCulture, out value); - - if (op.EndsWith('h') || op.EndsWith('H')) - return long.TryParse(op.AsSpan(0, op.Length - 1), System.Globalization.NumberStyles.HexNumber, - System.Globalization.CultureInfo.InvariantCulture, out value); - - return long.TryParse(op, out value); - } - - private static string FormatAnnotation(InstructionInfo info) - { - var sb = new StringBuilder(); - sb.Append("; ["); - sb.Append($"TP:{info.Throughput:F2}"); - sb.Append($" | Lat:{info.Latency,2}"); - sb.Append($" | Uops:{info.Uops}"); - if (info.Ports is not null) - { - sb.Append($" | {info.Ports}"); - } - sb.Append(']'); - return sb.ToString(); - } -} diff --git a/tools/JitAsm/InstructionDb.cs b/tools/JitAsm/InstructionDb.cs deleted file mode 100644 index df39ca110944..000000000000 --- a/tools/JitAsm/InstructionDb.cs +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace JitAsm; - -internal sealed class InstructionInfo -{ - public required string Mnemonic { get; init; } - public required string OperandPattern { get; init; } - public float Throughput { get; init; } - public int Latency { get; init; } - public int Uops { get; init; } - public string? Ports { get; init; } -} - -internal sealed class InstructionDb -{ - private const string Magic = "UOPS"; - private const ushort Version = 2; - - private readonly Dictionary> _instructions = new(StringComparer.OrdinalIgnoreCase); - - public string ArchName { get; } - - public InstructionDb(string archName) - { - ArchName = archName; - } - - public void Add(InstructionInfo info) - { - string key = info.Mnemonic.ToLowerInvariant(); - if (!_instructions.TryGetValue(key, out var list)) - { - list = []; - _instructions[key] = list; - } - list.Add(info); - } - - public int Count => _instructions.Sum(kv => kv.Value.Count); - - public InstructionInfo? Lookup(string mnemonic, string operandPattern) - { - if (!_instructions.TryGetValue(mnemonic, out var forms)) - return null; - - // Exact match - foreach (var form in forms) - { - if (string.Equals(form.OperandPattern, operandPattern, StringComparison.OrdinalIgnoreCase)) - return form; - } - - // Relaxed match: ignore register width differences (r32 ≈ r64 for same instruction class) - string relaxed = RelaxPattern(operandPattern); - foreach (var form in forms) - { - if (string.Equals(RelaxPattern(form.OperandPattern), relaxed, StringComparison.OrdinalIgnoreCase)) - return form; - } - - // Mnemonic-only match for zero-operand instructions (ret, nop, etc.) - if (operandPattern.Length == 0) - { - foreach (var form in forms) - { - if (form.OperandPattern.Length == 0) - return form; - } - } - - return null; - } - - private static string RelaxPattern(string pattern) - { - // Normalize register widths: r8/r16/r32/r64 → r, m8/m16/m32/m64/m128/m256/m512 → m, imm8/imm32 → imm - return pattern - .Replace("r64", "r").Replace("r32", "r").Replace("r16", "r").Replace("r8", "r") - .Replace("m512", "m").Replace("m256", "m").Replace("m128", "m") - .Replace("m64", "m").Replace("m32", "m").Replace("m16", "m").Replace("m8", "m") - .Replace("imm32", "imm").Replace("imm8", "imm"); - } - - public void Save(string path) - { - using var stream = File.Create(path); - using var writer = new BinaryWriter(stream); - - // Header - writer.Write(Magic.ToCharArray()); - writer.Write(Version); - writer.Write(ArchName); - - // Count all entries - int entryCount = Count; - writer.Write(entryCount); - - // Entries - foreach (var (_, forms) in _instructions) - { - foreach (var info in forms) - { - writer.Write(info.Mnemonic); - writer.Write(info.OperandPattern); - writer.Write(info.Throughput); - writer.Write((short)info.Latency); - writer.Write((short)info.Uops); - writer.Write(info.Ports ?? string.Empty); - } - } - } - - public static InstructionDb Load(string path) - { - using var stream = File.OpenRead(path); - using var reader = new BinaryReader(stream); - - // Header - char[] magic = reader.ReadChars(4); - if (new string(magic) != Magic) - throw new InvalidDataException($"Invalid instruction database file: bad magic"); - - ushort version = reader.ReadUInt16(); - if (version != Version) - throw new InvalidDataException($"Unsupported instruction database version: {version}"); - - string archName = reader.ReadString(); - int entryCount = reader.ReadInt32(); - - var db = new InstructionDb(archName); - - for (int i = 0; i < entryCount; i++) - { - string mnemonic = reader.ReadString(); - string operandPattern = reader.ReadString(); - float throughput = reader.ReadSingle(); - short latency = reader.ReadInt16(); - short uops = reader.ReadInt16(); - string ports = reader.ReadString(); - - db.Add(new InstructionInfo - { - Mnemonic = mnemonic, - OperandPattern = operandPattern, - Throughput = throughput, - Latency = latency, - Uops = uops, - Ports = ports.Length > 0 ? ports : null - }); - } - - return db; - } -} diff --git a/tools/JitAsm/InstructionDbBuilder.cs b/tools/JitAsm/InstructionDbBuilder.cs deleted file mode 100644 index 7daf9cfa517b..000000000000 --- a/tools/JitAsm/InstructionDbBuilder.cs +++ /dev/null @@ -1,265 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Globalization; -using System.Xml; - -namespace JitAsm; - -internal static class InstructionDbBuilder -{ - // Map from CLI flag value to uops.info architecture name - private static readonly Dictionary ArchMap = new(StringComparer.OrdinalIgnoreCase) - { - ["alder-lake"] = "ADL-P", - ["rocket-lake"] = "RKL", - ["ice-lake"] = "ICL", - ["tiger-lake"] = "TGL", - ["skylake"] = "SKL", - ["zen4"] = "ZEN4", - ["zen3"] = "ZEN3", - ["zen2"] = "ZEN2", - }; - - // Fallback chain: if the target arch has no data, try these in order - private static readonly Dictionary FallbackChain = new(StringComparer.OrdinalIgnoreCase) - { - ["ADL-P"] = ["RKL", "TGL", "ICL", "SKL"], - ["RKL"] = ["TGL", "ICL", "SKL"], - ["TGL"] = ["ICL", "SKL"], - ["ICL"] = ["SKL", "SKX", "HSW"], - ["SKL"] = ["SKX", "HSW"], - ["ZEN4"] = ["ZEN3", "ZEN2", "ZEN+"], - ["ZEN3"] = ["ZEN2", "ZEN+"], - ["ZEN2"] = ["ZEN+"], - }; - - public static string ResolveArchName(string cliValue) - { - return ArchMap.TryGetValue(cliValue, out var name) ? name : cliValue.ToUpperInvariant(); - } - - public static IReadOnlyCollection SupportedArchitectures => ArchMap.Keys; - - public static InstructionDb Build(string xmlPath, string archCliValue) - { - string targetArch = ResolveArchName(archCliValue); - string[] fallbacks = FallbackChain.TryGetValue(targetArch, out var fb) ? fb : []; - - var db = new InstructionDb(targetArch); - var seen = new HashSet(StringComparer.OrdinalIgnoreCase); - - using var stream = File.OpenRead(xmlPath); - using var reader = XmlReader.Create(stream, new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }); - - while (reader.Read()) - { - if (reader.NodeType != XmlNodeType.Element || reader.Name != "instruction") - continue; - - string? asm = reader.GetAttribute("asm"); - if (asm is null) - continue; - - // Clean the asm mnemonic: remove prefixes like "{load} " or "{store} " - string mnemonic = CleanMnemonic(asm); - if (mnemonic.Length == 0) - continue; - - // Read the instruction subtree - string instructionXml = reader.ReadOuterXml(); - var instrDoc = new XmlDocument(); - instrDoc.LoadXml(instructionXml); - var instrNode = instrDoc.DocumentElement!; - - // Parse operands - string operandPattern = BuildOperandPattern(instrNode); - - // Dedup key - string key = $"{mnemonic.ToLowerInvariant()}|{operandPattern}"; - if (seen.Contains(key)) - continue; - - // Find measurement for target architecture (with fallback) - var measurement = FindMeasurement(instrNode, targetArch, fallbacks); - if (measurement is null) - continue; - - float throughput = ParseFloat(measurement.GetAttribute("TP_unrolled")) - ?? ParseFloat(measurement.GetAttribute("TP_loop")) - ?? 0; - - int uops = ParseInt(measurement.GetAttribute("uops")) ?? 0; - string? ports = measurement.GetAttribute("ports"); - if (string.IsNullOrEmpty(ports)) ports = null; - - // Get max latency from child elements - int latency = 0; - foreach (XmlNode child in measurement.ChildNodes) - { - if (child is XmlElement latencyEl && latencyEl.Name == "latency") - { - int? cycles = ParseInt(latencyEl.GetAttribute("cycles")) - ?? ParseInt(latencyEl.GetAttribute("cycles_mem")) - ?? ParseInt(latencyEl.GetAttribute("cycles_addr")); - if (cycles.HasValue && cycles.Value > latency) - latency = cycles.Value; - } - } - - seen.Add(key); - db.Add(new InstructionInfo - { - Mnemonic = mnemonic.ToLowerInvariant(), - OperandPattern = operandPattern, - Throughput = throughput, - Latency = latency, - Uops = uops, - Ports = ports - }); - } - - return db; - } - - private static string CleanMnemonic(string asm) - { - // Remove assembler hints like "{load} ", "{store} ", "{vex} ", "{evex} " - ReadOnlySpan span = asm.AsSpan().Trim(); - while (span.Length > 0 && span[0] == '{') - { - int end = span.IndexOf('}'); - if (end < 0) break; - span = span[(end + 1)..].TrimStart(); - } - - // Take only the first word (mnemonic), skip any operand hints - int space = span.IndexOf(' '); - if (space > 0) - span = span[..space]; - - return span.ToString(); - } - - private static string BuildOperandPattern(XmlElement instrNode) - { - var parts = new List(); - foreach (XmlNode child in instrNode.ChildNodes) - { - if (child is not XmlElement operandEl || operandEl.Name != "operand") - continue; - - // Skip suppressed operands (flags, implicit registers) - if (operandEl.GetAttribute("suppressed") == "1") - continue; - - string? type = operandEl.GetAttribute("type"); - string? width = operandEl.GetAttribute("width"); - - string part = type switch - { - "reg" => ClassifyReg(width, operandEl.InnerText), - "mem" => ClassifyMem(width), - "agen" => "agen", - "imm" => ClassifyImm(width), - "relbr" => "rel", - _ => "" - }; - - if (part.Length > 0) - parts.Add(part); - } - - return string.Join(",", parts); - } - - private static string ClassifyReg(string? width, string? regNames) - { - // Check register names for xmm/ymm/zmm/mm/k - if (regNames is not null) - { - string firstReg = regNames.Split(',')[0].Trim().ToUpperInvariant(); - if (firstReg.StartsWith("ZMM")) return "zmm"; - if (firstReg.StartsWith("YMM")) return "ymm"; - if (firstReg.StartsWith("XMM")) return "xmm"; - if (firstReg.StartsWith("MM")) return "mm"; - if (firstReg.StartsWith("K")) return "k"; - } - - return width switch - { - "8" => "r8", - "16" => "r16", - "32" => "r32", - "64" => "r64", - "128" => "xmm", - "256" => "ymm", - "512" => "zmm", - _ => "r" - }; - } - - private static string ClassifyMem(string? width) - { - return width switch - { - "8" => "m8", - "16" => "m16", - "32" => "m32", - "64" => "m64", - "128" => "m128", - "256" => "m256", - "512" => "m512", - _ => "m" - }; - } - - private static string ClassifyImm(string? width) - { - return width switch - { - "8" => "imm8", - "16" => "imm16", - "32" => "imm32", - _ => "imm" - }; - } - - private static XmlElement? FindMeasurement(XmlElement instrNode, string targetArch, string[] fallbacks) - { - // Try target arch first, then fallbacks - var archsToTry = new List { targetArch }; - archsToTry.AddRange(fallbacks); - - foreach (string archName in archsToTry) - { - foreach (XmlNode child in instrNode.ChildNodes) - { - if (child is not XmlElement archEl || archEl.Name != "architecture") - continue; - if (archEl.GetAttribute("name") != archName) - continue; - - foreach (XmlNode archChild in archEl.ChildNodes) - { - if (archChild is XmlElement measureEl && measureEl.Name == "measurement") - return measureEl; - } - } - } - - return null; - } - - private static float? ParseFloat(string? value) - { - if (string.IsNullOrEmpty(value)) return null; - return float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out float result) ? result : null; - } - - private static int? ParseInt(string? value) - { - if (string.IsNullOrEmpty(value)) return null; - return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int result) ? result : null; - } -} diff --git a/tools/JitAsm/JitAsm.csproj b/tools/JitAsm/JitAsm.csproj deleted file mode 100644 index a505e193dda2..000000000000 --- a/tools/JitAsm/JitAsm.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - Exe - - - - - - - - - diff --git a/tools/JitAsm/JitAsm.slnx b/tools/JitAsm/JitAsm.slnx deleted file mode 100644 index b30012a5e817..000000000000 --- a/tools/JitAsm/JitAsm.slnx +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/tools/JitAsm/JitRunner.cs b/tools/JitAsm/JitRunner.cs deleted file mode 100644 index b348446161f9..000000000000 --- a/tools/JitAsm/JitRunner.cs +++ /dev/null @@ -1,269 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Diagnostics; -using System.Text; - -namespace JitAsm; - -internal sealed class JitRunner(string assemblyPath, string? typeName, string methodName, string? typeParams, string? classTypeParams, bool verbose, bool tier1 = false) -{ - public async Task RunSinglePassAsync(IReadOnlyList? cctorsToInit = null) - { - var result = await RunJitProcessAsync(cctorsToInit); - return result; - } - - public async Task RunTwoPassAsync() - { - // Pass 1: Run without cctor initialization to detect static constructor calls - var pass1Result = await RunJitProcessAsync(null); - - if (!pass1Result.Success) - { - return pass1Result; - } - - // Detect static constructors in the output - var detectedCctors = StaticCtorDetector.DetectStaticCtors(pass1Result.Output ?? string.Empty); - - if (detectedCctors.Count == 0) - { - // No cctors detected, return pass 1 result - return pass1Result; - } - - // Pass 2: Run with cctor initialization - var pass2Result = await RunJitProcessAsync(detectedCctors); - - return new JitResult - { - Success = pass2Result.Success, - Output = pass2Result.Output, - Error = pass2Result.Error, - Pass1Output = verbose ? pass1Result.Output : null, - DetectedCctors = detectedCctors - }; - } - - private async Task RunJitProcessAsync(IReadOnlyList? cctorsToInit) - { - // Get the path to the JitAsm executable - var (executablePath, argumentPrefix) = GetExecutablePath(); - - // Build the method pattern for JitDisasm - var methodPattern = BuildMethodPattern(); - - var startInfo = new ProcessStartInfo - { - FileName = executablePath, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - }; - - // Set JIT environment variables - these must be set before the process starts - if (tier1) - { - // Tier-1 simulation: enable tiered compilation so the method compiles at Tier-0 first, - // then gets recompiled at Tier-1 with full optimizations after the cctors have run. - startInfo.EnvironmentVariables["DOTNET_TieredCompilation"] = "1"; - startInfo.EnvironmentVariables["DOTNET_TieredPGO"] = "1"; - startInfo.EnvironmentVariables["DOTNET_TC_CallCountThreshold"] = "1"; - startInfo.EnvironmentVariables["DOTNET_TC_CallCountingDelayMs"] = "0"; - } - else - { - startInfo.EnvironmentVariables["DOTNET_TieredCompilation"] = "0"; - startInfo.EnvironmentVariables["DOTNET_TC_QuickJit"] = "0"; - } - startInfo.EnvironmentVariables["DOTNET_JitDisasm"] = methodPattern; - startInfo.EnvironmentVariables["DOTNET_JitDiffableDasm"] = "1"; - if (verbose) - startInfo.EnvironmentVariables["JITASM_VERBOSE"] = "1"; - - // Build arguments for internal runner - var args = new StringBuilder(); - if (argumentPrefix is not null) - { - args.Append(EscapeArg(argumentPrefix)); - args.Append(' '); - } - args.Append("--internal-runner "); - args.Append(EscapeArg(assemblyPath)); - args.Append(' '); - args.Append(EscapeArg(methodName)); - - if (typeName is not null) - { - args.Append(" --type "); - args.Append(EscapeArg(typeName)); - } - - if (typeParams is not null) - { - args.Append(" --type-params "); - args.Append(EscapeArg(typeParams)); - } - - if (classTypeParams is not null) - { - args.Append(" --class-type-params "); - args.Append(EscapeArg(classTypeParams)); - } - - if (cctorsToInit is not null && cctorsToInit.Count > 0) - { - args.Append(" --init-cctors "); - args.Append(EscapeArg(string.Join(";", cctorsToInit))); - } - - if (tier1) - { - args.Append(" --tier1"); - } - - startInfo.Arguments = args.ToString(); - - if (verbose) - { - Spectre.Console.AnsiConsole.MarkupLine($"[grey]Running: {Spectre.Console.Markup.Escape(executablePath)} {Spectre.Console.Markup.Escape(startInfo.Arguments)}[/]"); - Spectre.Console.AnsiConsole.MarkupLine($"[grey]DOTNET_JitDisasm={Spectre.Console.Markup.Escape(methodPattern)}[/]"); - Spectre.Console.AnsiConsole.WriteLine(); - } - - try - { - using var process = Process.Start(startInfo); - if (process is null) - { - return new JitResult { Success = false, Error = "Failed to start process" }; - } - - var stdoutTask = process.StandardOutput.ReadToEndAsync(); - var stderrTask = process.StandardError.ReadToEndAsync(); - - await process.WaitForExitAsync(); - - var stdout = await stdoutTask; - var stderr = await stderrTask; - - // Parse the JIT output from stdout (JIT diagnostics can go to either stream) - // In tier1 mode, multiple compilations are captured; take only the last (Tier-1) - var disassembly = DisassemblyParser.Parse(stdout, lastOnly: tier1); - - if (string.IsNullOrWhiteSpace(disassembly)) - { - // Try stderr - disassembly = DisassemblyParser.Parse(stderr, lastOnly: tier1); - } - - if (string.IsNullOrWhiteSpace(disassembly)) - { - // Check if there's an error - if (!string.IsNullOrWhiteSpace(stderr) && stderr.Contains("Error")) - { - return new JitResult { Success = false, Error = stderr.Trim() }; - } - - // No disassembly found - return new JitResult - { - Success = false, - Error = "No disassembly output found. Method may not exist or JIT output was not captured.", - Output = $"stdout:\n{stdout}\n\nstderr:\n{stderr}" - }; - } - - return new JitResult - { - Success = true, - Output = disassembly - }; - } - catch (Exception ex) - { - return new JitResult { Success = false, Error = ex.Message }; - } - } - - private string BuildMethodPattern() - { - // Build pattern for DOTNET_JitDisasm - // Just use the method name - the JIT will match any method with this name - // Type filtering is done post-hoc by parsing the output - return methodName; - } - - /// - /// Returns the executable path and any prefix arguments needed (e.g., DLL path for dotnet host). - /// - private static (string FileName, string? ArgumentPrefix) GetExecutablePath() - { - // Get the path to the current executable - var currentExe = Environment.ProcessPath; - if (currentExe is not null && File.Exists(currentExe)) - { - // When running via "dotnet run", ProcessPath is the dotnet host, not our tool. - // Detect this by checking if it ends with "dotnet" (or "dotnet.exe"). - var exeName = Path.GetFileNameWithoutExtension(currentExe); - if (exeName.Equals("dotnet", StringComparison.OrdinalIgnoreCase)) - { - var assemblyLocation = typeof(JitRunner).Assembly.Location; - if (!string.IsNullOrEmpty(assemblyLocation)) - { - return ("dotnet", assemblyLocation); - } - } - - return (currentExe, null); - } - - // Fallback to assembly location - var location = typeof(JitRunner).Assembly.Location; - if (!string.IsNullOrEmpty(location)) - { - // For .dll, try to find the corresponding .exe - var directory = Path.GetDirectoryName(location)!; - var baseName = Path.GetFileNameWithoutExtension(location); - - // Try .exe first (Windows) - var exePath = Path.Combine(directory, baseName + ".exe"); - if (File.Exists(exePath)) - { - return (exePath, null); - } - - // Try without extension (Linux/macOS) - exePath = Path.Combine(directory, baseName); - if (File.Exists(exePath)) - { - return (exePath, null); - } - - // Use dotnet to run the dll - return ("dotnet", location); - } - - throw new InvalidOperationException("Could not determine executable path"); - } - - private static string EscapeArg(string arg) - { - if (arg.Contains(' ') || arg.Contains('"')) - { - return $"\"{arg.Replace("\"", "\\\"")}\""; - } - return arg; - } -} - -internal sealed class JitResult -{ - public bool Success { get; init; } - public string? Output { get; init; } - public string? Error { get; init; } - public string? Pass1Output { get; init; } - public IReadOnlyList DetectedCctors { get; init; } = []; -} diff --git a/tools/JitAsm/MethodResolver.cs b/tools/JitAsm/MethodResolver.cs deleted file mode 100644 index 334d7fb14b5e..000000000000 --- a/tools/JitAsm/MethodResolver.cs +++ /dev/null @@ -1,393 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Reflection; - -namespace JitAsm; - -internal sealed class MethodResolver(Assembly assembly) -{ - private static readonly Dictionary TypeAliases = new(StringComparer.OrdinalIgnoreCase) - { - ["bool"] = typeof(bool), - ["byte"] = typeof(byte), - ["sbyte"] = typeof(sbyte), - ["char"] = typeof(char), - ["short"] = typeof(short), - ["ushort"] = typeof(ushort), - ["int"] = typeof(int), - ["uint"] = typeof(uint), - ["long"] = typeof(long), - ["ulong"] = typeof(ulong), - ["float"] = typeof(float), - ["double"] = typeof(double), - ["decimal"] = typeof(decimal), - ["string"] = typeof(string), - ["object"] = typeof(object), - ["void"] = typeof(void), - ["nint"] = typeof(nint), - ["nuint"] = typeof(nuint), - }; - - public MethodInfo? ResolveMethod(string? typeName, string methodName, string? typeParams, string? classTypeParams = null) - { - var candidates = new List<(Type Type, MethodInfo Method)>(); - - if (typeName is not null) - { - // Search specific type - var type = ResolveType(typeName); - if (type is null) - { - return null; - } - - // If the type is a generic type definition and we have class type params, construct the concrete type - if (type.IsGenericTypeDefinition && classTypeParams is not null) - { - type = MakeGenericType(type, classTypeParams); - if (type is null) - { - return null; - } - } - - var methods = FindMethods(type, methodName); - candidates.AddRange(methods.Select(m => (type, m))); - - // Also search base types if no methods found (for inherited methods) - if (candidates.Count == 0) - { - var baseType = type.BaseType; - while (baseType is not null && candidates.Count == 0) - { - methods = FindMethods(baseType, methodName, includeInherited: true); - candidates.AddRange(methods.Select(m => (baseType, m))); - baseType = baseType.BaseType; - } - } - } - else - { - // Search all types - foreach (var type in assembly.GetTypes()) - { - var searchType = type; - - // If the type is a generic type definition and we have class type params, try to construct it - if (type.IsGenericTypeDefinition && classTypeParams is not null) - { - var typeParamCount = classTypeParams.Split(',').Length; - if (type.GetGenericArguments().Length == typeParamCount) - { - var constructed = MakeGenericType(type, classTypeParams); - if (constructed is not null) - { - searchType = constructed; - } - } - } - - var methods = FindMethods(searchType, methodName); - candidates.AddRange(methods.Select(m => (searchType, m))); - } - } - - if (candidates.Count == 0) - { - return null; - } - - bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; - if (verbose) - { - Console.Error.WriteLine($"[DEBUG] Candidates before filtering: {candidates.Count}"); - foreach (var c in candidates) - { - Console.Error.WriteLine($"[DEBUG] Method: {c.Method.Name}, IsGenericMethodDefinition: {c.Method.IsGenericMethodDefinition}, GenericArgCount: {c.Method.GetGenericArguments().Length}"); - } - } - - // If type params are specified, filter to only methods with matching generic param count - if (typeParams is not null) - { - var genericParamCount = typeParams.Split(',').Length; - if (verbose) - Console.Error.WriteLine($"[DEBUG] Looking for generic methods with {genericParamCount} type params"); - - var genericMatches = candidates.Where(c => c.Method.IsGenericMethodDefinition && - c.Method.GetGenericArguments().Length == genericParamCount).ToList(); - - if (verbose) - Console.Error.WriteLine($"[DEBUG] Generic matches: {genericMatches.Count}"); - - if (genericMatches.Count > 0) - { - candidates = genericMatches; - } - } - else - { - // Prefer non-generic methods if no type params specified - var nonGeneric = candidates.Where(c => !c.Method.IsGenericMethodDefinition).ToList(); - if (nonGeneric.Count > 0) - { - candidates = nonGeneric; - } - } - - if (candidates.Count == 0) - { - if (verbose) - Console.Error.WriteLine($"[DEBUG] No candidates after filtering"); - return null; - } - - if (verbose) - Console.Error.WriteLine($"[DEBUG] Final candidates: {candidates.Count}, calling MakeGenericIfNeeded"); - - // If there's only one candidate, use it - if (candidates.Count == 1) - { - return MakeGenericIfNeeded(candidates[0].Method, typeParams); - } - - // Return first match - return MakeGenericIfNeeded(candidates[0].Method, typeParams); - } - - private Type? MakeGenericType(Type genericTypeDefinition, string classTypeParams) - { - var typeNames = classTypeParams.Split(',', StringSplitOptions.TrimEntries); - var types = new Type[typeNames.Length]; - - bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; - - for (int i = 0; i < typeNames.Length; i++) - { - var resolved = ResolveTypeParam(typeNames[i]); - if (resolved is null) - { - if (verbose) - Console.Error.WriteLine($"[DEBUG] Failed to resolve type param: {typeNames[i]}"); - return null; - } - types[i] = resolved; - if (verbose) - Console.Error.WriteLine($"[DEBUG] Resolved type param {typeNames[i]} to {resolved.FullName}"); - } - - try - { - var result = genericTypeDefinition.MakeGenericType(types); - if (verbose) - Console.Error.WriteLine($"[DEBUG] Constructed generic type: {result.FullName}"); - return result; - } - catch (Exception ex) - { - if (verbose) - Console.Error.WriteLine($"[DEBUG] MakeGenericType failed: {ex.Message}"); - return null; - } - } - - private Type? ResolveType(string typeName) - { - // Try direct lookup - var type = assembly.GetType(typeName); - if (type is not null) return type; - - // Try with assembly name prefix removed if present - var types = assembly.GetTypes(); - - // Try exact match on FullName - type = types.FirstOrDefault(t => t.FullName == typeName); - if (type is not null) return type; - - // Try match on Name only - type = types.FirstOrDefault(t => t.Name == typeName); - if (type is not null) return type; - - // Try matching generic types by base name (without the `N suffix) - // e.g., "TransactionProcessorBase" should match "TransactionProcessorBase`1" - type = types.FirstOrDefault(t => t.IsGenericTypeDefinition && - (t.FullName?.StartsWith(typeName + "`") == true || - t.Name.StartsWith(typeName + "`"))); - if (type is not null) return type; - - // Also try matching with the ` syntax - the type might be specified as TypeName`1 - if (typeName.Contains('`')) - { - type = types.FirstOrDefault(t => t.FullName == typeName || t.Name == typeName); - if (type is not null) return type; - } - - // Try nested types - foreach (var t in types) - { - if (t.FullName is not null && typeName.StartsWith(t.FullName + "+")) - { - var nestedName = typeName[(t.FullName.Length + 1)..]; - var nested = t.GetNestedType(nestedName, BindingFlags.Public | BindingFlags.NonPublic); - if (nested is not null) return nested; - } - } - - // Search referenced assemblies for cross-assembly types - foreach (var refName in assembly.GetReferencedAssemblies()) - { - try - { - var refAssembly = Assembly.Load(refName); - type = refAssembly.GetType(typeName); - if (type is not null) return type; - - // Try matching generic types by base name (e.g., "ClockCache" matches "ClockCache`2") - Type[] refTypes; - try { refTypes = refAssembly.GetTypes(); } - catch (ReflectionTypeLoadException ex) { refTypes = ex.Types.Where(t => t is not null).ToArray()!; } - - type = refTypes.FirstOrDefault(t => - t.FullName == typeName || t.Name == typeName || - (t.IsGenericTypeDefinition && - (t.FullName?.StartsWith(typeName + "`") == true || - t.Name.StartsWith(typeName + "`")))); - if (type is not null) return type; - } - catch - { - // Skip assemblies that can't be loaded - } - } - - return null; - } - - private static IEnumerable FindMethods(Type type, string methodName, bool includeInherited = false) - { - var flags = BindingFlags.Public | BindingFlags.NonPublic | - BindingFlags.Instance | BindingFlags.Static; - - if (!includeInherited) - { - flags |= BindingFlags.DeclaredOnly; - } - - bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; - var methods = type.GetMethods(flags).Where(m => m.Name == methodName).ToList(); - if (verbose) - { - Console.Error.WriteLine($"[DEBUG] FindMethods on {type.FullName} for '{methodName}': found {methods.Count} methods"); - if (methods.Count == 0) - { - // List some methods to help debug - var allMethods = type.GetMethods(flags).Where(m => m.Name.Contains("Evm") || m.Name.Contains("Execute")).Take(10); - Console.Error.WriteLine($"[DEBUG] Sample methods containing 'Evm' or 'Execute': {string.Join(", ", allMethods.Select(m => m.Name))}"); - } - } - - return methods; - } - - private MethodInfo? MakeGenericIfNeeded(MethodInfo method, string? typeParams) - { - bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; - - if (typeParams is null || !method.IsGenericMethodDefinition) - { - if (verbose) - Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: returning method as-is (typeParams={typeParams}, IsGenericMethodDefinition={method.IsGenericMethodDefinition})"); - return method; - } - - var typeNames = typeParams.Split(',', StringSplitOptions.TrimEntries); - var types = new Type[typeNames.Length]; - - if (verbose) - Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: resolving {typeNames.Length} type params: {string.Join(", ", typeNames)}"); - - for (int i = 0; i < typeNames.Length; i++) - { - var resolved = ResolveTypeParam(typeNames[i]); - if (resolved is null) - { - if (verbose) - Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: failed to resolve type param '{typeNames[i]}'"); - return null; - } - types[i] = resolved; - if (verbose) - Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: resolved '{typeNames[i]}' to {resolved.FullName}"); - } - - try - { - var result = method.MakeGenericMethod(types); - if (verbose) - Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: success, created {result}"); - return result; - } - catch (Exception ex) - { - if (verbose) - Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: MakeGenericMethod failed: {ex.Message}"); - return null; - } - } - - private Type? ResolveTypeParam(string typeName) - { - // Check aliases first - if (TypeAliases.TryGetValue(typeName, out var aliasType)) - { - return aliasType; - } - - // Try the target assembly - var type = ResolveType(typeName); - if (type is not null) return type; - - // Try referenced assemblies - foreach (var refName in assembly.GetReferencedAssemblies()) - { - try - { - var refAssembly = Assembly.Load(refName); - type = refAssembly.GetType(typeName); - if (type is not null) return type; - - // Try by short name - type = refAssembly.GetTypes().FirstOrDefault(t => t.Name == typeName || t.FullName == typeName); - if (type is not null) return type; - } - catch - { - // Skip assemblies that can't be loaded - } - } - - // Try Type.GetType as last resort - return Type.GetType(typeName); - } - - public IEnumerable FindAllMethods(string? typeName, string methodName) - { - if (typeName is not null) - { - var type = ResolveType(typeName); - if (type is not null) - { - return FindMethods(type, methodName); - } - return []; - } - - var results = new List(); - foreach (var type in assembly.GetTypes()) - { - results.AddRange(FindMethods(type, methodName)); - } - return results; - } -} diff --git a/tools/JitAsm/Program.cs b/tools/JitAsm/Program.cs deleted file mode 100644 index deb928163f70..000000000000 --- a/tools/JitAsm/Program.cs +++ /dev/null @@ -1,581 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.CommandLine; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.Loader; -using System.Text.Json; -using Spectre.Console; - -namespace JitAsm; - -internal static class Program -{ - private static readonly Option AssemblyOption = new("-a", "--assembly") - { - Description = "Path to the assembly containing the method", - Required = true - }; - - private static readonly Option TypeOption = new("-t", "--type") - { - Description = "Fully qualified type name (optional, will search all types if not specified)" - }; - - private static readonly Option MethodOption = new("-m", "--method") - { - Description = "Method name to disassemble", - Required = true - }; - - private static readonly Option TypeParamsOption = new("--type-params") - { - Description = "Method generic type parameters (comma-separated type names)" - }; - - private static readonly Option ClassTypeParamsOption = new("--class-type-params") - { - Description = "Class generic type parameters (comma-separated type names, e.g., for TransactionProcessorBase`1)" - }; - - private static readonly Option SkipCctorOption = new("--skip-cctor-detection") - { - Description = "Skip automatic static constructor detection (single pass only)" - }; - - private static readonly Option FullOptsOption = new("--fullopts") - { - Description = "Use single-pass FullOpts compilation (DOTNET_TieredCompilation=0) instead of the default Tier-1 + PGO simulation" - }; - - private static readonly Option NoAnnotateOption = new("--no-annotate") - { - Description = "Disable per-instruction annotations (throughput, latency, uops, ports from uops.info)" - }; - - private static readonly Option ArchOption = new("--arch") - { - Description = "Target microarchitecture for annotations (default: zen4). Options: zen4, zen3, zen2, alder-lake, rocket-lake, ice-lake, tiger-lake, skylake", - DefaultValueFactory = _ => "zen4" - }; - - private static readonly Option VerboseOption = new("-v", "--verbose") - { - Description = "Show resolution details and both passes" - }; - - private static int Main(string[] args) - { - // Check if running in internal runner mode - if (args.Length > 0 && args[0] == "--internal-runner") - { - return RunInternalRunner(args.Skip(1).ToArray()); - } - - return RunCli(args); - } - - private static int RunCli(string[] args) - { - var rootCommand = new RootCommand("JIT Assembly Disassembler - Generate JIT assembly output for .NET methods") - { - AssemblyOption, - TypeOption, - MethodOption, - TypeParamsOption, - ClassTypeParamsOption, - SkipCctorOption, - FullOptsOption, - NoAnnotateOption, - ArchOption, - VerboseOption - }; - - int exitCode = 0; - rootCommand.SetAction(parseResult => - { - var assembly = parseResult.GetValue(AssemblyOption)!; - var typeName = parseResult.GetValue(TypeOption); - var methodName = parseResult.GetValue(MethodOption)!; - var typeParams = parseResult.GetValue(TypeParamsOption); - var classTypeParams = parseResult.GetValue(ClassTypeParamsOption); - var skipCctor = parseResult.GetValue(SkipCctorOption); - var fullOpts = parseResult.GetValue(FullOptsOption); - var annotate = !parseResult.GetValue(NoAnnotateOption); - var arch = parseResult.GetValue(ArchOption)!; - var verbose = parseResult.GetValue(VerboseOption); - - exitCode = Execute(assembly, typeName, methodName, typeParams, classTypeParams, skipCctor, fullOpts, annotate, arch, verbose); - }); - - int parseExitCode = rootCommand.Parse(args).Invoke(); - return parseExitCode != 0 ? parseExitCode : exitCode; - } - - private static int Execute(FileInfo assembly, string? typeName, string methodName, string? typeParams, string? classTypeParams, bool skipCctor, bool fullOpts, bool annotate, string arch, bool verbose) - { - if (!assembly.Exists) - { - AnsiConsole.MarkupLine($"[red]Error:[/] Assembly not found: {assembly.FullName}"); - return 1; - } - - bool tier1 = !fullOpts; - - if (verbose) - { - AnsiConsole.MarkupLine($"[blue]Assembly:[/] {assembly.FullName}"); - AnsiConsole.MarkupLine($"[blue]Type:[/] {typeName ?? "(search all)"}"); - AnsiConsole.MarkupLine($"[blue]Method:[/] {methodName}"); - if (classTypeParams is not null) - { - AnsiConsole.MarkupLine($"[blue]Class Type Parameters:[/] {classTypeParams}"); - } - if (typeParams is not null) - { - AnsiConsole.MarkupLine($"[blue]Method Type Parameters:[/] {typeParams}"); - } - AnsiConsole.MarkupLine(tier1 - ? "[blue]Mode:[/] Tier-1 + Dynamic PGO (default)" - : "[blue]Mode:[/] FullOpts (TieredCompilation=0)"); - AnsiConsole.WriteLine(); - } - - var runner = new JitRunner(assembly.FullName, typeName, methodName, typeParams, classTypeParams, verbose, tier1); - - InstructionDb? instructionDb = annotate ? LoadOrBuildInstructionDb(arch, verbose) : null; - - JitResult result = (skipCctor ? - runner.RunSinglePassAsync() : - runner.RunTwoPassAsync()) - .GetAwaiter().GetResult(); - - return OutputResult(result, verbose, instructionDb); - } - - private static int OutputResult(JitResult result, bool verbose, InstructionDb? instructionDb = null) - { - if (!result.Success) - { - AnsiConsole.MarkupLine($"[red]Error:[/] {Markup.Escape(result.Error ?? "Unknown error")}"); - if (!string.IsNullOrEmpty(result.Output)) - { - AnsiConsole.WriteLine(); - AnsiConsole.MarkupLine("[yellow]Output:[/]"); - AnsiConsole.WriteLine(result.Output); - } - return 1; - } - - if (verbose && result.DetectedCctors.Count > 0) - { - AnsiConsole.MarkupLine("[blue]Detected static constructors:[/]"); - foreach (var cctor in result.DetectedCctors) - { - AnsiConsole.MarkupLine($" [grey]- {Markup.Escape(cctor)}[/]"); - } - AnsiConsole.WriteLine(); - } - - if (verbose && result.Pass1Output is not null && result.DetectedCctors.Count > 0) - { - AnsiConsole.MarkupLine("[blue]Pass 1 Output (before cctor initialization):[/]"); - AnsiConsole.WriteLine(result.Pass1Output); - AnsiConsole.WriteLine(); - AnsiConsole.MarkupLine("[blue]Pass 2 Output (after cctor initialization):[/]"); - } - - string output = result.Output ?? string.Empty; - if (instructionDb is not null) - { - output = InstructionAnnotator.Annotate(output, instructionDb); - } - - Console.WriteLine(output); - return 0; - } - - private static InstructionDb? LoadOrBuildInstructionDb(string arch, bool verbose) - { - string toolDir = AppContext.BaseDirectory; - // Look for files relative to the project source directory first, then the binary directory - string projectDir = Path.GetFullPath(Path.Combine(toolDir, "..", "..", "..", "..")); - if (!File.Exists(Path.Combine(projectDir, "JitAsm.csproj"))) - { - // Fallback: try to find the project directory from the current working directory - string cwd = Directory.GetCurrentDirectory(); - if (File.Exists(Path.Combine(cwd, "tools", "JitAsm", "JitAsm.csproj"))) - projectDir = Path.Combine(cwd, "tools", "JitAsm"); - else if (File.Exists(Path.Combine(cwd, "JitAsm.csproj"))) - projectDir = cwd; - else - projectDir = toolDir; - } - - string dbPath = Path.Combine(projectDir, "instructions.db"); - string xmlPath = Path.Combine(projectDir, "instructions.xml"); - - // Check if we have a cached .db for this architecture - if (File.Exists(dbPath)) - { - try - { - var db = InstructionDb.Load(dbPath); - string targetArch = InstructionDbBuilder.ResolveArchName(arch); - if (db.ArchName == targetArch) - { - if (verbose) - AnsiConsole.MarkupLine($"[blue]Loaded instruction database:[/] {dbPath} ({db.Count} entries for {db.ArchName})"); - return db; - } - - if (verbose) - AnsiConsole.MarkupLine($"[yellow]Cached DB is for {db.ArchName}, need {targetArch}. Rebuilding...[/]"); - } - catch (Exception ex) - { - if (verbose) - AnsiConsole.MarkupLine($"[yellow]Failed to load cached DB: {Markup.Escape(ex.Message)}. Rebuilding...[/]"); - } - } - - // Build from XML - if (!File.Exists(xmlPath)) - { - AnsiConsole.MarkupLine("[yellow]Instruction database not found. Annotations disabled.[/]"); - AnsiConsole.MarkupLine("[yellow]Download it with:[/]"); - AnsiConsole.MarkupLine($" curl -o {Markup.Escape(xmlPath)} https://uops.info/instructions.xml"); - AnsiConsole.MarkupLine("[yellow]Or use --no-annotate to suppress this warning.[/]"); - return null; - } - - AnsiConsole.MarkupLine($"[blue]Building instruction database for {arch}...[/]"); - var stopwatch = System.Diagnostics.Stopwatch.StartNew(); - var builtDb = InstructionDbBuilder.Build(xmlPath, arch); - stopwatch.Stop(); - - AnsiConsole.MarkupLine($"[blue]Built {builtDb.Count} instruction entries in {stopwatch.Elapsed.TotalSeconds:F1}s[/]"); - - try - { - builtDb.Save(dbPath); - if (verbose) - AnsiConsole.MarkupLine($"[blue]Saved to:[/] {dbPath}"); - } - catch (Exception ex) - { - if (verbose) - AnsiConsole.MarkupLine($"[yellow]Warning: Could not save DB cache: {Markup.Escape(ex.Message)}[/]"); - } - - return builtDb; - } - - private static int RunInternalRunner(string[] args) - { - // Parse internal runner arguments - // Format: [--type ] [--type-params ] [--class-type-params ] [--init-cctors ] - if (args.Length < 2) - { - Console.Error.WriteLine("Internal runner requires assembly and method arguments"); - return 1; - } - - var assemblyPath = args[0]; - var methodName = args[1]; - string? typeName = null; - string? typeParams = null; - string? classTypeParams = null; - List cctorsToInit = []; - bool tier1 = false; - - for (int i = 2; i < args.Length; i++) - { - switch (args[i]) - { - case "--type" when i + 1 < args.Length: - typeName = args[++i]; - break; - case "--type-params" when i + 1 < args.Length: - typeParams = args[++i]; - break; - case "--class-type-params" when i + 1 < args.Length: - classTypeParams = args[++i]; - break; - case "--init-cctors" when i + 1 < args.Length: - cctorsToInit = args[++i].Split(';', StringSplitOptions.RemoveEmptyEntries).ToList(); - break; - case "--tier1": - tier1 = true; - break; - } - } - - // Set up dependency resolution for the target assembly. - // Without this, RuntimeHelpers.PrepareMethod silently fails when the - // JIT can't resolve types from transitive NuGet packages (e.g. Nethermind.Int256). - bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; - - // Build assembly name → file path mapping from the deps.json file. - // This resolves both project references (in the same directory) and - // NuGet packages (from the global packages cache). - var assemblyDir = Path.GetDirectoryName(Path.GetFullPath(assemblyPath))!; - var assemblyMap = BuildAssemblyMap(assemblyPath, assemblyDir, verbose); - - AssemblyLoadContext.Default.Resolving += (context, name) => - { - // First check the target assembly's directory - var localPath = Path.Combine(assemblyDir, name.Name + ".dll"); - if (File.Exists(localPath)) - { - if (verbose) - Console.Error.WriteLine($"[DEBUG] AssemblyResolve: {name.Name} → {localPath} (local)"); - return context.LoadFromAssemblyPath(localPath); - } - - // Then check deps.json mapped paths - if (assemblyMap.TryGetValue(name.Name!, out var mappedPath) && File.Exists(mappedPath)) - { - if (verbose) - Console.Error.WriteLine($"[DEBUG] AssemblyResolve: {name.Name} → {mappedPath} (deps.json)"); - return context.LoadFromAssemblyPath(mappedPath); - } - - if (verbose) - Console.Error.WriteLine($"[DEBUG] AssemblyResolve: {name.Name} → NOT FOUND"); - return null; - }; - - try - { - var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(assemblyPath)); - - if (verbose) - { - Console.Error.WriteLine($"[DEBUG] Loaded assembly: {assembly.FullName}"); - Console.Error.WriteLine($"[DEBUG] Type name: {typeName}"); - Console.Error.WriteLine($"[DEBUG] Method name: {methodName}"); - Console.Error.WriteLine($"[DEBUG] Type params: {typeParams}"); - Console.Error.WriteLine($"[DEBUG] Class type params: {classTypeParams}"); - } - - // Initialize static constructors if requested - foreach (var cctorTypeName in cctorsToInit) - { - var cctorType = ResolveType(assembly, cctorTypeName, verbose); - if (cctorType is not null) - { - if (verbose) - Console.Error.WriteLine($"[DEBUG] Running cctor for: {cctorType.FullName}"); - RuntimeHelpers.RunClassConstructor(cctorType.TypeHandle); - } - else if (verbose) - { - Console.Error.WriteLine($"[DEBUG] WARNING: Could not resolve cctor type: {cctorTypeName}"); - } - } - - // Resolve the method - var resolver = new MethodResolver(assembly); - var method = resolver.ResolveMethod(typeName, methodName, typeParams, classTypeParams); - - if (method is null) - { - if (verbose) - { - var types = assembly.GetTypes().Where(t => t.Name.Contains(typeName ?? "")).Take(5); - Console.Error.WriteLine($"[DEBUG] Possible types: {string.Join(", ", types.Select(t => t.FullName))}"); - } - Console.Error.WriteLine($"Could not resolve method: {methodName}"); - return 1; - } - - if (tier1) - { - var parameters = method.GetParameters(); - var invokeArgs = new object?[parameters.Length]; - object? target = method.IsStatic ? null : TryCreateInstance(method.DeclaringType!); - - void InvokeN(int count) - { - for (int i = 0; i < count; i++) - { - try { method.Invoke(target, invokeArgs); } - catch { /* Expected - args are null/default */ } - } - } - - if (verbose) - Console.Error.WriteLine("[DEBUG] Phase 1: Invoking to trigger Tier-0 → Instrumented Tier-0..."); - InvokeN(50); - Thread.Sleep(1000); - - if (verbose) - Console.Error.WriteLine("[DEBUG] Phase 2: Invoking to trigger Instrumented Tier-0 → Tier-1..."); - InvokeN(50); - Thread.Sleep(2000); - - if (verbose) - Console.Error.WriteLine("[DEBUG] Phase 3: Final invocations to ensure Tier-1 is installed..."); - InvokeN(50); - Thread.Sleep(1000); - } - else - { - RuntimeHelpers.PrepareMethod(method.MethodHandle); - if (verbose) - Console.Error.WriteLine($"[DEBUG] PrepareMethod completed for {method.DeclaringType?.FullName}:{method.Name}"); - - // Also invoke the method to ensure all code paths are JIT-compiled. - // PrepareMethod alone may not trigger DOTNET_JitDisasm output for - // methods from dynamically loaded assemblies. - var parameters = method.GetParameters(); - var invokeArgs = new object?[parameters.Length]; - object? target = method.IsStatic ? null : TryCreateInstance(method.DeclaringType!); - try { method.Invoke(target, invokeArgs); } - catch { /* Expected - args are null/default */ } - if (verbose) - Console.Error.WriteLine("[DEBUG] Method invocation completed"); - } - - return 0; - } - catch (Exception ex) - { - Console.Error.WriteLine($"Error: {ex.Message}"); - return 1; - } - } - - private static object? TryCreateInstance(Type type) - { - try - { - return RuntimeHelpers.GetUninitializedObject(type); - } - catch - { - return null; - } - } - - private static Type? ResolveType(Assembly assembly, string typeName, bool verbose = false) - { - // Try direct lookup in target assembly first - var type = assembly.GetType(typeName); - if (type is not null) return type; - - // Try searching all types in target assembly - foreach (var t in assembly.GetTypes()) - { - if (t.FullName == typeName || t.Name == typeName) - { - return t; - } - } - - // Search referenced assemblies (types often come from other assemblies, - // e.g., Nethermind.Core types referenced from Nethermind.Evm) - foreach (var refName in assembly.GetReferencedAssemblies()) - { - try - { - var refAssembly = Assembly.Load(refName); - type = refAssembly.GetType(typeName); - if (type is not null) - { - if (verbose) - Console.Error.WriteLine($"[DEBUG] Resolved cctor type '{typeName}' from referenced assembly {refName.Name}"); - return type; - } - - // Try by short name - foreach (var t in refAssembly.GetTypes()) - { - if (t.FullName == typeName || t.Name == typeName) - { - if (verbose) - Console.Error.WriteLine($"[DEBUG] Resolved cctor type '{typeName}' from referenced assembly {refName.Name} (by scan)"); - return t; - } - } - } - catch - { - // Skip assemblies that can't be loaded - } - } - - // Try Type.GetType as last resort (requires assembly-qualified names for cross-assembly) - return Type.GetType(typeName); - } - - /// - /// Build a mapping from assembly name to file path using the deps.json file. - /// Resolves NuGet package references via the global packages cache. - /// - private static Dictionary BuildAssemblyMap(string assemblyPath, string assemblyDir, bool verbose) - { - var map = new Dictionary(StringComparer.OrdinalIgnoreCase); - string depsPath = Path.ChangeExtension(assemblyPath, ".deps.json"); - if (!File.Exists(depsPath)) - return map; - - string nugetPackages = Environment.GetEnvironmentVariable("NUGET_PACKAGES") - ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); - - try - { - using var stream = File.OpenRead(depsPath); - using var doc = JsonDocument.Parse(stream); - - var root = doc.RootElement; - if (!root.TryGetProperty("targets", out var targets)) - return map; - - // Get the first (and usually only) target - foreach (var target in targets.EnumerateObject()) - { - foreach (var package in target.Value.EnumerateObject()) - { - if (!package.Value.TryGetProperty("runtime", out var runtime)) - continue; - - foreach (var dll in runtime.EnumerateObject()) - { - string dllRelativePath = dll.Name; // e.g. "lib/net10.0/Nethermind.Int256.dll" - string asmName = Path.GetFileNameWithoutExtension(dllRelativePath); - - // Skip if already in the local directory - if (File.Exists(Path.Combine(assemblyDir, asmName + ".dll"))) - continue; - - // Resolve from NuGet cache: packages/{id}/{version}/{path} - string packageId = package.Name; // e.g. "Nethermind.Numerics.Int256/1.4.0" - string[] parts = packageId.Split('/'); - if (parts.Length == 2) - { - string fullPath = Path.Combine(nugetPackages, parts[0].ToLowerInvariant(), parts[1], dllRelativePath); - if (File.Exists(fullPath)) - { - map[asmName] = fullPath; - if (verbose) - Console.Error.WriteLine($"[DEBUG] Deps map: {asmName} → {fullPath}"); - } - } - } - } - break; // Only process the first target - } - } - catch (Exception ex) - { - if (verbose) - Console.Error.WriteLine($"[DEBUG] Failed to parse deps.json: {ex.Message}"); - } - - return map; - } -} - diff --git a/tools/JitAsm/README.md b/tools/JitAsm/README.md deleted file mode 100644 index 705821f64be3..000000000000 --- a/tools/JitAsm/README.md +++ /dev/null @@ -1,494 +0,0 @@ -# JitAsm - -A tool for viewing JIT-compiled assembly output for .NET methods. Useful for analyzing code generation, verifying optimizations, and comparing different implementations. - -## How It Works - -JitAsm spawns a child process with JIT diagnostic environment variables (`DOTNET_JitDisasm`) to capture disassembly output. - -By default, the tool simulates **Tier-1 recompilation with Dynamic PGO** — the same compilation tier that runs in production after warm-up. This produces the most representative assembly: smaller code, eliminated static base helpers, and PGO-guided branch layout. - -### Compilation Tiers - -The .NET runtime compiles methods through multiple stages: - -| Stage | Description | Typical Size | -|-------|-------------|-------------| -| **Tier-0** | Quick JIT, minimal optimization (`minopt`) | Largest | -| **Instrumented Tier-0** | Tier-0 + PGO probes for profiling | Larger still | -| **Tier-1 + PGO** (default) | Full optimization with profile data | **Smallest** | -| **FullOpts** (`--fullopts`) | Full optimization, no PGO, no tiering | Middle | - -Key difference between Tier-1 and FullOpts: -- **FullOpts** (`DOTNET_TieredCompilation=0`) compiles in a single pass. Cross-module static base helpers (`CORINFO_HELP_GET_NONGCSTATIC_BASE`) persist because the JIT hasn't resolved the static base address yet. -- **Tier-1 + PGO** compiles after Tier-0 has executed. By then, static bases are resolved, and the JIT can embed addresses directly — eliminating the helper entirely. PGO data also improves branch layout and inlining decisions. - -### Two-Pass Static Constructor Handling - -When a method references static fields, the JIT may include static constructor initialization checks (`CORINFO_HELP_GET_NONGCSTATIC_BASE`, `CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE`). JitAsm automatically detects these and runs a second pass with static constructors pre-initialized, showing the steady-state optimized code path. - -In Tier-1 mode (default), many of these helpers are eliminated naturally since the static bases are already resolved by Tier-0 execution. - -### Tier-1 Simulation - -The tool drives through all PGO compilation stages: - -1. **Invoke** method via reflection (triggers Tier-0 JIT + installs call counting stubs) -2. **Wait** for Instrumented Tier-0 recompilation (PGO profiling version) -3. **Invoke** again (triggers call counter through instrumented code) -4. **Wait** for Tier-1 recompilation (fully optimized with PGO data) -5. **Capture** the final Tier-1 assembly output - -Critical env var: `DOTNET_TC_CallCountingDelayMs=0` — without this, counting stubs aren't installed in time and Tier-1 never triggers. - -## Usage - -```bash -dotnet run --project tools/JitAsm -c Release -- [options] -``` - -### Options - -| Option | Description | -|--------|-------------| -| `-a, --assembly ` | Path to the assembly containing the method (required) | -| `-t, --type ` | Fully qualified type name (optional, searches all types if not specified) | -| `-m, --method ` | Method name to disassemble (required) | -| `--type-params ` | Method generic type parameters (comma-separated) | -| `--class-type-params ` | Class generic type parameters (comma-separated, for generic containing types) | -| `--fullopts` | Use single-pass FullOpts compilation (`TieredCompilation=0`) instead of Tier-1 + PGO | -| `--no-annotate` | Disable per-instruction annotations (throughput, latency, uops, ports). Annotations are **on by default** | -| `--arch ` | Target microarchitecture for annotations (default: `zen4`). See [Supported Architectures](#supported-architectures) | -| `--skip-cctor-detection` | Skip automatic static constructor detection (single pass only) | -| `-v, --verbose` | Show resolution details and both passes | - -### Default Mode: Tier-1 + PGO - -By default, the tool simulates Tier-1 recompilation with Dynamic PGO. This is the compilation tier code runs at in production and produces the most optimized output. - -```bash -# Default: Tier-1 + PGO (production-representative) -dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName -``` - -### FullOpts Mode - -Use `--fullopts` when you need the older single-pass compilation. This is faster (no invocation/sleep cycle) but may show static base helpers and lack PGO-guided optimizations. - -```bash -# FullOpts: single compilation, no PGO -dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName --fullopts -``` - -## Prerequisites - -Build the target project in Release configuration before disassembling: - -```bash -dotnet build src/Nethermind/Nethermind.Evm -c Release -``` - -Assemblies are output to `src/Nethermind/artifacts/bin/{ProjectName}/release/{ProjectName}.dll`. For example: -- `src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll` -- `src/Nethermind/artifacts/bin/Nethermind.Core/release/Nethermind.Core.dll` - -## Platform Notes (Windows) - -On Windows, follow these rules to avoid argument parsing errors: - -- **Avoid quoted strings** around paths and type parameters — they can cause `'' was not matched` errors -- **Use forward slashes** in paths (works on both Windows and Linux) -- **Put the entire command on a single line** — avoid line continuations (`\`) -- Use absolute paths when possible for clarity - -```bash -# Good (Windows) -dotnet run --project tools/JitAsm -c Release -- -a D:/GitHub/nethermind/src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmStack -m PushBytesRef - -# Bad (may fail on Windows) -dotnet run --project tools/JitAsm -c Release -- \ - -a "src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll" \ - -t "Nethermind.Evm.EvmStack" \ - -m PushBytesRef -``` - -On Linux/macOS, both styles work fine. - -## Examples - -### Basic Usage - -```bash -# Disassemble a simple method (searches all types, default Tier-1 + PGO) -dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m PushBytesRef -``` - -### With Type Specification - -```bash -# Specify the containing type to narrow the search -dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmStack -m PushBytesRef -``` - -### Generic Methods (Method-Level Type Parameters) - -Use `--type-params` for type parameters on the method itself: - -```bash -# PushBytes(...) where TTracingInst = OffFlag -dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmStack -m PushBytes --type-params Nethermind.Core.OffFlag -``` - -### Generic Classes (Class-Level Type Parameters) - -Use `--class-type-params` when the containing class is generic. This is separate from `--type-params` which is for method-level generics. Generic type names can omit the backtick arity suffix (e.g., `TransactionProcessorBase` matches `TransactionProcessorBase`1`). - -```bash -# TransactionProcessorBase.Execute(...) -dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.TransactionProcessing.TransactionProcessorBase -m Execute --class-type-params Nethermind.Evm.GasPolicy.EthereumGasPolicy --type-params Nethermind.Core.OffFlag -``` - -### Multiple Generic Parameters - -Use comma-separated type names (no spaces after commas): - -```bash -dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m SomeGenericMethod --type-params System.Int32,System.String -``` - -### EVM Instruction Example - -Disassemble the MUL opcode implementation with specific gas policy and tracing flags: - -```bash -dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmInstructions -m InstructionMath2Param --type-params Nethermind.Evm.GasPolicy.EthereumGasPolicy,Nethermind.Evm.EvmInstructions+OpMul,Nethermind.Core.OffFlag -``` - -This shows the JIT output for `InstructionMath2Param`: -- `EthereumGasPolicy` - Standard Ethereum gas accounting -- `OpMul` - The multiplication operation (nested type, use `+` syntax) -- `OffFlag` - Tracing disabled (allows dead code elimination of tracing paths) - -Other math operations can be viewed by replacing `OpMul` with: `OpAdd`, `OpSub`, `OpDiv`, `OpMod`, `OpSDiv`, `OpSMod`, `OpLt`, `OpGt`, `OpSLt`, `OpSGt`, `OpEq`, `OpAnd`, `OpOr`, `OpXor`. - -### Type Aliases - -Common C# type aliases are supported: - -```bash -# These are equivalent ---type-params int ---type-params System.Int32 -``` - -Supported aliases: `bool`, `byte`, `sbyte`, `char`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double`, `decimal`, `string`, `object`, `nint`, `nuint` - -### Verbose Mode - -```bash -# Show detailed output including cctor detection and compilation tier info -dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m PushBytesRef -v -``` - -### Skip Static Constructor Detection - -```bash -# Single pass only (faster, but may show cctor overhead) -dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m SomeMethod --skip-cctor-detection -``` - -### FullOpts vs Tier-1 Comparison - -Compare the same method under different compilation modes to see what Tier-1 + PGO eliminates: - -```bash -# Tier-1 + PGO (default) — production-representative -dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.BlockExecutionContext -m GetBlobBaseFee > tier1.asm 2>&1 - -# FullOpts — single-pass, no PGO -dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.BlockExecutionContext -m GetBlobBaseFee --fullopts > fullopts.asm 2>&1 - -# Compare -diff tier1.asm fullopts.asm -``` - -Example result for `GetBlobBaseFee`: -- **FullOpts**: 356 bytes, has `CORINFO_HELP_GET_NONGCSTATIC_BASE` -- **Tier-1 + PGO**: 236 bytes (34% smaller), helper eliminated entirely - -## Example Output - -Default Tier-1 + PGO output: - -``` -; Assembly listing for method Namespace.Type:Method() (Tier1) -; Emitting BLENDED_CODE for generic X64 + VEX + EVEX on Windows -; Tier1 code ← Tier-1 recompilation (production tier) -; optimized code -; optimized using Dynamic PGO -; rsp based frame -; partially interruptible -; with Dynamic PGO: fgCalledCount is 50 - -G_M000_IG01: ;; offset=0x0000 - sub rsp, 40 - ... - -; Total bytes of code 236 -``` - -FullOpts output (with `--fullopts`): - -``` -; Assembly listing for method Namespace.Type:Method() (FullOpts) -; FullOpts code ← Single-pass FullOpts -; optimized code -; No PGO data ← No profile data available - ... - -; Total bytes of code 356 -``` - -## Instruction Annotations - -Per-instruction performance data from [uops.info](https://uops.info) is included **by default**, showing throughput, latency, micro-op count, and execution port usage inline with the assembly output. Use `--no-annotate` to disable. - -### Setup - -Download the uops.info instruction database (110MB, one-time): - -```bash -curl -o tools/JitAsm/instructions.xml https://uops.info/instructions.xml -``` - -On first use, the XML is preprocessed into a compact binary cache (`instructions.db`, ~1-2MB). Subsequent runs load from the cache. - -### Usage - -```bash -# Annotations are on by default (zen4) -dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName - -# Use a different architecture -dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName --arch alder-lake - -# Disable annotations -dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName --no-annotate -``` - -### Annotation Format - -Each instruction line gets an end-of-line annotation: - -``` - add rax, rcx ; [TP:0.25 | Lat: 1 | Uops:1] - mov qword ptr [rbp+10h], rax ; [TP:0.50 | Lat:11 | Uops:1] - vmovdqu ymm0, ymmword ptr [rsi] ; [TP:0.50 | Lat: 8 | Uops:1 | 1*FP_LD] -``` - -| Field | Meaning | -|-------|---------| -| `TP` | Reciprocal throughput (cycles per instruction). Lower = faster. | -| `Lat` | Latency (cycles from input ready to output ready). Matters for dependency chains. | -| `Uops` | Micro-op count. Fewer = less pressure on the execution engine. | -| Ports | Execution port usage (e.g., `1*p0156`). Shows which functional units are used. | - -### Supported Architectures - -| `--arch` value | CPU | Description | -|----------------|-----|-------------| -| `zen4` (default) | AMD Zen 4 | Ryzen 7000 / EPYC 9004 | -| `zen3` | AMD Zen 3 | Ryzen 5000 / EPYC 7003 | -| `zen2` | AMD Zen 2 | Ryzen 3000 / EPYC 7002 | -| `alder-lake` | Intel ADL-P | 12th Gen Core (P-cores) | -| `rocket-lake` | Intel RKL | 11th Gen Core | -| `ice-lake` | Intel ICL | 10th Gen Core | -| `tiger-lake` | Intel TGL | 11th Gen Mobile | -| `skylake` | Intel SKL | 6th Gen Core | - -When data for the selected architecture is unavailable for a specific instruction, the tool falls back to a nearby architecture in the same family. - -### Annotation Details - -**Mnemonic mapping:** The .NET JIT uses Intel-style mnemonic aliases (e.g., `je`, `jne`, `ja`) while uops.info uses canonical forms (`jz`, `jnz`, `jnbe`). The annotator automatically maps between them for conditional jumps, conditional moves (`cmove`→`cmovz`), and set-byte instructions (`sete`→`setz`). - -**LEA handling:** The `lea` instruction computes an address but doesn't access memory. Its second operand is classified as `agen` (address generation) rather than a memory load, matching the uops.info encoding. - -**.NET JIT-specific prefixes:** -- `gword ptr` — GC-tracked pointer-width memory reference. Treated as `m64` on x64. -- `bword ptr` — Pointer-width memory reference without GC tracking. Treated as `m64` on x64. -- `SHORT` / `NEAR` — Jump distance hints from the JIT. Stripped before operand classification. - -**Skipped instructions:** `call`, `ret`, `int3`, `nop`, and `int` are not annotated. `ret` is skipped because uops.info's `TP_unrolled` for `ret` reflects return stack buffer mispredictions in microbenchmarks, not real-world performance. - -## Iterative Workflow - -JitAsm is designed for rapid iteration when optimizing code: - -1. **Modify source code** -2. **Build the target assembly:** - ```bash - dotnet build src/Nethermind/Nethermind.Evm -c Release - ``` -3. **View the generated assembly:** - ```bash - dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m YourMethod - ``` -4. **Compare output between iterations** - -For fast iteration during optimization, `--fullopts` skips the invocation/sleep cycle (~4s faster) at the cost of less representative output. Use default Tier-1 for final verification. - -## Environment Variables - -The tool sets these JIT diagnostic variables for the child process: - -### Default Mode (Tier-1 + PGO) - -| Variable | Value | Purpose | -|----------|-------|---------| -| `DOTNET_TieredCompilation` | `1` | Enable tiered compilation | -| `DOTNET_TieredPGO` | `1` | Enable Dynamic PGO | -| `DOTNET_TC_CallCountThreshold` | `1` | Minimal call count before recompilation | -| `DOTNET_TC_CallCountingDelayMs` | `0` | Install counting stubs immediately (critical) | -| `DOTNET_JitDisasm` | `` | Output disassembly for matching methods | -| `DOTNET_JitDiffableDasm` | `1` | Consistent, diffable output format | - -### FullOpts Mode (`--fullopts`) - -| Variable | Value | Purpose | -|----------|-------|---------| -| `DOTNET_TieredCompilation` | `0` | Disable tiered compilation | -| `DOTNET_TC_QuickJit` | `0` | Disable quick JIT | -| `DOTNET_JitDisasm` | `` | Output disassembly for matching methods | -| `DOTNET_JitDiffableDasm` | `1` | Consistent, diffable output format | - -## Comparing Generic Specializations (OffFlag vs OnFlag) - -A common workflow is comparing two generic instantiations to verify dead code elimination. Nethermind uses the `IFlag` pattern (`OnFlag`/`OffFlag`) for compile-time branch elimination. - -```bash -# Generate ASM for tracing disabled (common case) -dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmInstructions -m InstructionMath2Param --type-params Nethermind.Evm.GasPolicy.EthereumGasPolicy,Nethermind.Evm.EvmInstructions+OpAdd,Nethermind.Core.OffFlag > off.asm 2>&1 - -# Generate ASM for tracing enabled -dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmInstructions -m InstructionMath2Param --type-params Nethermind.Evm.GasPolicy.EthereumGasPolicy,Nethermind.Evm.EvmInstructions+OpAdd,Nethermind.Core.OnFlag > on.asm 2>&1 - -# Compare: the OnFlag version should have tracing code that is absent from OffFlag -diff off.asm on.asm -``` - -**Red flag:** If both versions have identical code size, the JIT may not be eliminating dead code as expected. - -## Extracting Metrics from ASM Output - -Use these patterns to extract key metrics from the disassembly output for tracking optimization progress: - -```bash -# Code size (from the last line, e.g., "; Total bytes of code 40") -tail -1 output.asm - -# Basic block count (fewer = simpler control flow = better branch prediction) -grep -c "G_M000_IG[0-9]*:" output.asm - -# Branch count (conditional jumps) -grep -cE "\bj(e|ne|g|l|ge|le|a|b|ae|be|nz|z)\b" output.asm - -# Call count (should be minimal in hot paths) -grep -c "call" output.asm - -# Register saves in prologue (>4 push = register pressure issue) -grep -c "push" output.asm - -# Stack frame size (from prologue, e.g., "sub rsp, 40") -grep "sub.*rsp" output.asm -``` - -## Reading Assembly Output - -### Output Structure - -The disassembly header contains useful metadata: - -``` -; Assembly listing for method Namespace.Type:Method() (Tier1) -; Emitting BLENDED_CODE for generic X64 + VEX + EVEX on Windows -; Tier1 code ← Tier-1 optimized (production tier) -; optimized code -; optimized using Dynamic PGO ← PGO-guided optimizations applied -; rsp based frame -; partially interruptible -; with Dynamic PGO: fgCalledCount is 50 ← How many times method was called during profiling -``` - -The code is organized into **basic blocks** labeled `G_M000_IG01`, `G_M000_IG02`, etc. Each block is a straight-line sequence of instructions ending with a branch or fall-through. - -### Common Red Flags - -| Pattern in ASM | Meaning | Severity | -|----------------|---------|----------| -| `call CORINFO_HELP_BOX` | Boxing allocation in hot path | High | -| `call CORINFO_HELP_VIRTUAL_FUNC_PTR` | Virtual dispatch not devirtualized (~25-50 cycles) | High | -| `call CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE` | Static constructor check | High | -| `call CORINFO_HELP_GET_NONGCSTATIC_BASE` | Cross-module static base resolution (eliminated in Tier-1) | Medium* | -| `call CORINFO_HELP_ASSIGN_REF` | GC write barrier (bad in loops) | High | -| `callvirt [Interface:Method()]` | Interface dispatch not devirtualized | High | -| `call [SmallType:SmallMethod()]` | Failed inlining of small method | Medium | -| `cmp ...; jae THROW_LABEL` | Bounds check (may be eliminable) | Medium | -| `idiv` | Division by non-constant (~20-80 cycles) | Medium | -| `>4 push` instructions in prologue | Register pressure | Medium | -| `vmovdqu32 zmmword ptr` in prologue | Large stack zeroing (cold locals bloating hot path) | Low | - -*`CORINFO_HELP_GET_NONGCSTATIC_BASE` appears in FullOpts mode for cross-module static field access but is naturally eliminated in Tier-1 + PGO. If you see it in default (Tier-1) output, it indicates the static base couldn't be resolved at runtime — a real issue. - -### What Good ASM Looks Like - -- Few basic blocks (simple control flow) -- No `call` instructions in the hot path (everything inlined) -- Compact code size (better I-cache utilization) -- No redundant loads of the same memory location -- SIMD instructions (`vpaddd`, `vpxor`, etc.) for bulk data operations -- `cmov*` (conditional moves) instead of branches where appropriate -- Fall-through on the hot path, forward jumps to cold/error paths -- `; optimized using Dynamic PGO` in the header (confirms PGO applied) - -## Troubleshooting - -### No disassembly output - -- Verify the assembly path is correct and the DLL exists -- Check if the method name is spelled correctly (case-sensitive) -- Try without the `-t` option to search all types -- Use `-v` for verbose output to see what's happening -- Ensure you built with `-c Release` (Debug builds produce different code) - -### Tier-1 produces no output / shows Tier-0 instead - -- The Tier-1 simulation invokes the method via reflection with default arguments. Methods taking **ref struct** parameters (`Span`, `ReadOnlySpan`, `Memory`) cannot be invoked this way — the simulation silently fails and produces no output. -- **Workaround:** Use `--fullopts` for methods with ref struct parameters. It compiles in a single pass without invocation. -- For other methods: this means the Tier-1 recompilation didn't fire. The method may not be invocable with null/default arguments. -- Check `-v` output for error messages during invocation phases. - -### Method not found - -- Ensure the assembly is built (Release configuration recommended) -- For generic methods, provide all required type parameters via `--type-params` -- For methods in generic classes, provide class type parameters via `--class-type-params` -- Use fully qualified type names for type parameters from other assemblies -- Generic type names can omit the backtick arity (e.g., `TransactionProcessorBase` matches `TransactionProcessorBase`1`) - -### Multiple methods with same name - -- Specify the type with `-t` to narrow the search -- The tool will use the first matching overload if multiple exist - -### On Windows: `'' was not matched` error - -- Remove all quotes from arguments — pass paths and type names unquoted -- Replace backslashes with forward slashes in paths -- Put the entire command on a single line - -## Building - -```bash -dotnet build tools/JitAsm -c Release -``` diff --git a/tools/JitAsm/StaticCtorDetector.cs b/tools/JitAsm/StaticCtorDetector.cs deleted file mode 100644 index 1b512dceb957..000000000000 --- a/tools/JitAsm/StaticCtorDetector.cs +++ /dev/null @@ -1,175 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Text.RegularExpressions; - -namespace JitAsm; - -internal static partial class StaticCtorDetector -{ - // Patterns for detecting static constructor calls in JIT disassembly - // Example: call Namespace.Type:.cctor() - [GeneratedRegex(@"call\s+(?[\w.+`\[\],]+):\.cctor\(\)", RegexOptions.Compiled)] - private static partial Regex CctorCallPattern(); - - // JIT helpers for static field initialization - // Matches both shared and non-shared variants, with optional brackets: - // call CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE - // call [CORINFO_HELP_GET_NONGCSTATIC_BASE] - // call CORINFO_HELP_CLASSINIT_SHARED_DYNAMICCLASS - [GeneratedRegex(@"call\s+\[?CORINFO_HELP_(?:(?:GETSHARED_|GET_)(?:NON)?GCSTATIC_BASE|CLASSINIT_SHARED_DYNAMICCLASS)\]?", RegexOptions.Compiled)] - private static partial Regex StaticHelperPattern(); - - // Pattern for type references in static helper context - // The JIT output often shows the type being initialized nearby - // Example: ; Namespace.Type - [GeneratedRegex(@";\s*(?[\w.+`\[\],]+)\s*$", RegexOptions.Compiled | RegexOptions.Multiline)] - private static partial Regex TypeCommentPattern(); - - // Pattern for detecting lazy initialization checks - // Example: cmp dword ptr [Namespace.Type:initialized], 0 - [GeneratedRegex(@"\[(?[\w.+`\[\],]+):", RegexOptions.Compiled)] - private static partial Regex StaticFieldAccessPattern(); - - // Pattern for extracting types from call targets in JIT output. - // In diffable mode, long call targets wrap to the next line, e.g.: - // call - // [Nethermind.Core.Eip4844Constants:get_MinBlobGasPrice():...] - // In non-diffable mode, they appear on one line: - // call [Nethermind.Core.Eip4844Constants:get_MinBlobGasPrice():...] - // This pattern matches the "[Type:Method" portion (possibly with [r11] prefix for callvirt). - [GeneratedRegex(@"(?:\[(?:r\d+\])?)?\[?(?[\w.+`\[\],]+):(?!:)", RegexOptions.Compiled)] - private static partial Regex CallTargetTypePattern(); - - public static IReadOnlyList DetectStaticCtors(string disassemblyOutput) - { - var detectedTypes = new HashSet(StringComparer.Ordinal); - - // Detect direct .cctor calls - foreach (Match match in CctorCallPattern().Matches(disassemblyOutput)) - { - var typeName = NormalizeTypeName(match.Groups["type"].Value); - if (!string.IsNullOrEmpty(typeName)) - { - detectedTypes.Add(typeName); - } - } - - // Check for static helper calls and try to find associated types - if (StaticHelperPattern().IsMatch(disassemblyOutput)) - { - int typesBeforeHelperScan = detectedTypes.Count; - - // Look for type references near the helper calls (±3 lines) - var lines = disassemblyOutput.Split('\n'); - for (int i = 0; i < lines.Length; i++) - { - if (StaticHelperPattern().IsMatch(lines[i])) - { - // Check surrounding lines for type context - for (int j = Math.Max(0, i - 3); j <= Math.Min(lines.Length - 1, i + 3); j++) - { - var typeMatch = TypeCommentPattern().Match(lines[j]); - if (typeMatch.Success) - { - var typeName = NormalizeTypeName(typeMatch.Groups["type"].Value); - if (!string.IsNullOrEmpty(typeName) && IsValidTypeName(typeName)) - { - detectedTypes.Add(typeName); - } - } - - var fieldMatch = StaticFieldAccessPattern().Match(lines[j]); - if (fieldMatch.Success) - { - var typeName = NormalizeTypeName(fieldMatch.Groups["type"].Value); - if (!string.IsNullOrEmpty(typeName) && IsValidTypeName(typeName)) - { - detectedTypes.Add(typeName); - } - } - } - } - } - - // Fallback: if helpers were found but no types extracted from nearby context - // (common when JitDiffableDasm strips annotations or helper has no nearby type comment), - // scan the entire method for types referenced in call instructions. - // In diffable mode, long call targets wrap to the next line: - // call - // [Nethermind.Core.Eip4844Constants:get_MinBlobGasPrice():...] - // Pre-running extra cctors is cheap; missing the right one means stale output. - if (detectedTypes.Count == typesBeforeHelperScan) - { - for (int i = 0; i < lines.Length; i++) - { - var line = lines[i]; - // Check current line and continuation lines after bare "call" instructions - if (line.TrimStart().StartsWith("call") || (i > 0 && lines[i - 1].TrimEnd().EndsWith("call"))) - { - var callMatch = CallTargetTypePattern().Match(line); - if (callMatch.Success) - { - var typeName = NormalizeTypeName(callMatch.Groups["type"].Value); - if (!string.IsNullOrEmpty(typeName) && IsValidTypeName(typeName)) - { - detectedTypes.Add(typeName); - } - } - } - } - } - } - - var result = new List(detectedTypes); - result.Sort(StringComparer.Ordinal); - return result; - } - - private static string NormalizeTypeName(string typeName) - { - // Remove generic arity suffix if present (e.g., `1, `2) - var tickIndex = typeName.IndexOf('`'); - if (tickIndex > 0) - { - // Keep up to and including the backtick and number for proper type resolution - var endIndex = tickIndex + 1; - while (endIndex < typeName.Length && char.IsDigit(typeName[endIndex])) - { - endIndex++; - } - - // If there's more after the generic arity, check for nested types - if (endIndex < typeName.Length && typeName[endIndex] == '+') - { - // Keep nested type info - return typeName; - } - - return typeName[..endIndex]; - } - - return typeName.Trim(); - } - - private static bool IsValidTypeName(string typeName) - { - // Filter out obvious non-type names - if (string.IsNullOrWhiteSpace(typeName)) - return false; - - // Must contain at least one letter - if (!typeName.Any(char.IsLetter)) - return false; - - // Should not be just a keyword or register name - var lowered = typeName.ToLowerInvariant(); - string[] invalidNames = ["rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rsp", "rbp", - "eax", "ebx", "ecx", "edx", "esi", "edi", "esp", "ebp", - "ptr", "dword", "qword", "byte", "word"]; - if (invalidNames.Contains(lowered)) - return false; - - return true; - } -} diff --git a/tools/Kute/Nethermind.Tools.Kute/Program.cs b/tools/Kute/Nethermind.Tools.Kute/Program.cs index e4bd6d9bcfa1..6c640ab0f808 100644 --- a/tools/Kute/Nethermind.Tools.Kute/Program.cs +++ b/tools/Kute/Nethermind.Tools.Kute/Program.cs @@ -14,7 +14,6 @@ using Nethermind.Tools.Kute.SecretProvider; using Nethermind.Tools.Kute.SystemClock; using System.CommandLine; -using System.Net; namespace Nethermind.Tools.Kute; @@ -57,18 +56,7 @@ private static IServiceProvider BuildServiceProvider(ParseResult parseResult) collection.AddSingleton(); collection.AddSingleton(); - collection.AddSingleton(_ => - { - // Disable proxy auto-detection to avoid timeout (~2s) on Windows. - // Each Kute invocation is a separate process, so the proxy lookup - // would otherwise be paid on every single measured request. - var handler = new SocketsHttpHandler - { - UseProxy = false, - AutomaticDecompression = DecompressionMethods.None, - }; - return new HttpClient(handler); - }); + collection.AddSingleton(); collection.AddSingleton(new FileSecretProvider(parseResult.GetValue(Config.JwtSecretFilePath)!)); collection.AddSingleton(provider => new TtlAuth( diff --git a/tools/gas-benchmarks b/tools/gas-benchmarks deleted file mode 160000 index 56714853a2f1..000000000000 --- a/tools/gas-benchmarks +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 56714853a2f17d7a556cae075212c363afbc4678 From a348e35fbcfe39bee0c9a007f6961d003a3e4330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Tue, 17 Feb 2026 22:54:58 +0100 Subject: [PATCH 07/13] port non-benchmark runtime changes from bdn merge --- .../Instructions/EvmInstructions.CodeCopy.cs | 2 +- .../EvmInstructions.Environment.cs | 2 +- .../VirtualMachine.ExtCodeCache.cs | 14 ++++++------- .../Nethermind.Evm/VirtualMachine.cs | 20 +++++++++++-------- .../Modules/BlockProcessingModule.cs | 1 + 5 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs index 9b2b0ee32539..186061b42922 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs @@ -166,7 +166,7 @@ public static EvmExceptionType InstructionExtCodeCopy( if (!TGasPolicy.UpdateMemoryCost(ref gas, in a, result, vm.VmState)) goto OutOfGas; - ICodeInfo codeInfo = vm.GetExtCodeInfoCached(address, spec); + CodeInfo codeInfo = vm.GetExtCodeInfoCached(address, spec); // Get the external code from the repository. ReadOnlySpan externalCode = codeInfo.CodeSpan; diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs index 9b6fe99dac53..2ee2186b5db6 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs @@ -677,7 +677,7 @@ public static EvmExceptionType InstructionExtCodeHashEof( SectionIndex = VmState.FunctionIndex; // Retrieve the code information and create a read-only span of instructions. - ICodeInfo codeInfo = VmState.Env.CodeInfo; + CodeInfo codeInfo = VmState.Env.CodeInfo; ReadOnlySpan codeSection = GetInstructions(codeInfo); // Initialize the exception type to "None". @@ -1331,16 +1331,16 @@ protected virtual unsafe CallResult RunByteCode( debugger?.TryWait(ref _currentState, ref programCounter, ref gas, ref stack.Head); #endif // Process the return data based on its runtime type. - if (ReturnData is VmState state) + if (ReturnData is byte[] data) { - return new CallResult(state); + // Fall back to returning a CallResult with a byte array as the return data. + return new CallResult(null, data, null, codeInfo.Version); } - else if (ReturnData is EofCodeInfo eofCodeInfo) + else if (ReturnData is VmState state) { - return new CallResult(eofCodeInfo, ReturnDataBuffer, null, codeInfo.Version); + return new CallResult(state); } - // Fall back to returning a CallResult with a byte array as the return data. - return new CallResult(null, (byte[])ReturnData, null, codeInfo.Version); + return ReturnEof(codeInfo); Revert: // Return a CallResult indicating a revert. @@ -1356,7 +1356,7 @@ protected virtual unsafe CallResult RunByteCode( // Converts the code section bytes into a read-only span of instructions. // Lightest weight conversion as mostly just helpful when debugging to see what the opcodes are. - static ReadOnlySpan GetInstructions(ICodeInfo codeInfo) + static ReadOnlySpan GetInstructions(CodeInfo codeInfo) { ReadOnlySpan codeBytes = codeInfo.CodeSpan; return MemoryMarshal.CreateReadOnlySpan( @@ -1364,6 +1364,10 @@ ref Unsafe.As(ref MemoryMarshal.GetReference(codeBytes)), codeBytes.Length); } + [MethodImpl(MethodImplOptions.NoInlining)] + CallResult ReturnEof(CodeInfo codeInfo) + => new(ReturnData as EofCodeInfo, ReturnDataBuffer, null, codeInfo.Version); + [DoesNotReturn] static void ThrowOperationCanceledException() => throw new OperationCanceledException("Cancellation Requested"); } diff --git a/src/Nethermind/Nethermind.Init/Modules/BlockProcessingModule.cs b/src/Nethermind/Nethermind.Init/Modules/BlockProcessingModule.cs index 4165e4fba354..21765cfc0635 100644 --- a/src/Nethermind/Nethermind.Init/Modules/BlockProcessingModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/BlockProcessingModule.cs @@ -63,6 +63,7 @@ protected override void Load(ContainerBuilder builder) .AddScoped() .AddScoped() .AddScoped() + .AddScoped() .AddScoped() .AddScoped((rewardSource, txP) => rewardSource.Get(txP)) .AddScoped((specProvider, blocksConfig) => From a0a0b19a74eca93ac56c9678ed0b4bdb9608dac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Tue, 17 Feb 2026 23:40:29 +0100 Subject: [PATCH 08/13] perf(evm): integrate hash-aware code info path for extcode cache --- .../CachedCodeInfoRepository.cs | 10 +++++++ .../Nethermind.Evm/CodeInfoRepository.cs | 11 ++++++- .../Nethermind.Evm/ICodeInfoRepository.cs | 1 + .../Instructions/EvmInstructions.CodeCopy.cs | 2 +- .../EvmInstructions.Environment.cs | 29 ++++++++++++------- .../VirtualMachine.ExtCodeCache.cs | 9 ++++-- .../OverridableCodeInfoRepository.cs | 15 ++++++++++ 7 files changed, 61 insertions(+), 16 deletions(-) diff --git a/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs b/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs index c8ca251cb403..8ad9efdc97a8 100644 --- a/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs @@ -36,6 +36,16 @@ public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IR return baseCodeInfoRepository.GetCachedCodeInfo(codeSource, followDelegation, vmSpec, out delegationAddress); } + public ICodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) + { + if (vmSpec.IsPrecompile(codeSource) && _cachedPrecompile.TryGetValue(codeSource, out PrecompileInfo cachedCodeInfo)) + { + return cachedCodeInfo; + } + + return baseCodeInfoRepository.GetCachedCodeInfo(codeSource, in codeHash, vmSpec); + } + public ValueHash256 GetExecutableCodeHash(Address address, IReleaseSpec spec) { return baseCodeInfoRepository.GetExecutableCodeHash(address, spec); diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs index 959ed46ff4c3..a272e336d35a 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs @@ -47,6 +47,16 @@ public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IR return cachedCodeInfo; } + public ICodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) + { + if (vmSpec.IsPrecompile(codeSource)) + { + return _localPrecompiles[codeSource]; + } + + return InternalGetCachedCode(_worldState, in codeHash, vmSpec); + } + private ICodeInfo InternalGetCachedCode(Address codeSource, IReleaseSpec vmSpec) { ref readonly ValueHash256 codeHash = ref _worldState.GetCodeHash(codeSource); @@ -187,4 +197,3 @@ public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out ICodeInfo? } } } - diff --git a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs index 17e568d007e0..85b359ece23c 100644 --- a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs @@ -12,6 +12,7 @@ namespace Nethermind.Evm; public interface ICodeInfoRepository { ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress); + ICodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec); ValueHash256 GetExecutableCodeHash(Address address, IReleaseSpec spec); void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec); void SetDelegation(Address codeSource, Address authority, IReleaseSpec spec); diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs index 186061b42922..340383bbe131 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs @@ -179,7 +179,7 @@ public static EvmExceptionType InstructionExtCodeCopy( } // If EOF is enabled and the code is an EOF contract, use a predefined magic value. - if (spec.IsEofEnabled && EofValidator.IsEof(externalCode, out _)) + if (spec.IsEofEnabled && codeInfo is EofCodeInfo) { externalCode = EofValidator.MAGIC; } diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs index 2ee2186b5db6..15227dcaa469 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs @@ -675,20 +675,27 @@ public static EvmExceptionType InstructionExtCodeHashEof(); } + else + { + ref readonly ValueHash256 codeHash = ref state.GetCodeHash(address); + if (codeHash == Keccak.OfAnEmptyString.ValueHash256) + { + stack.Push32Bytes(in codeHash); + return EvmExceptionType.None; + } + + CodeInfo codeInfo = vm.CodeInfoRepository.GetCachedCodeInfo(address, in codeHash, spec); + // If the code is EOF, push the EOF-specific hash. + if (codeInfo is EofCodeInfo) + { + stack.PushBytes(EofHash256); + } else { - CodeInfo codeInfo = vm.GetExtCodeInfoCached(address, spec); - // If the code is EOF, push the EOF-specific hash. - if (codeInfo is EofCodeInfo) - { - stack.PushBytes(EofHash256); - } - else - { - // Otherwise, push the standard code hash. - stack.PushBytes(state.GetCodeHash(address).Bytes); - } + // Otherwise, push the standard code hash. + stack.Push32Bytes(in codeHash); } + } return EvmExceptionType.None; // Jump forward to be unpredicted by the branch predictor. diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs index 0d785c9b28f8..63046679a3fd 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs @@ -54,7 +54,7 @@ internal uint GetExtCodeSizeCached(Address address, IReleaseSpec spec) return entry.CodeSize; } - CodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + CodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, in codeHash, spec); uint codeSize = codeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)codeInfo.CodeSpan.Length; StoreCacheEntry(key, codeHash, codeSize, codeInfo); @@ -99,7 +99,7 @@ internal CodeInfo GetExtCodeInfoCached(Address address, IReleaseSpec spec) return entry.CodeInfo; } - CodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); + CodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, in codeHash, spec); uint codeSize = codeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)codeInfo.CodeSpan.Length; StoreCacheEntry(key, codeHash, codeSize, codeInfo); return codeInfo; @@ -113,10 +113,13 @@ private void StoreCacheEntry(AddressAsKey key, in ValueHash256 codeHash, uint co return; } + // Under high-cardinality workloads, clearing the whole dictionary causes avoidable churn. + // Once capacity is reached, keep current hot set and skip admitting new entries. if (_extCodeCache!.Count >= _maxExtCodeCacheEntries) { - _extCodeCache.Clear(); + return; } + _extCodeCache[key] = new ExtCodeCacheEntry(codeHash, codeSize, codeInfo); } diff --git a/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs b/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs index d8ac3c455f1d..546613b95f95 100644 --- a/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs @@ -35,6 +35,21 @@ public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IR return codeInfoRepository.GetCachedCodeInfo(codeSource, followDelegation, vmSpec, out delegationAddress); } + public ICodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) + { + if (_precompileOverrides.TryGetValue(codeSource, out var precompile)) + { + return precompile.codeInfo; + } + + if (_codeOverrides.TryGetValue(codeSource, out var result)) + { + return result.codeInfo; + } + + return codeInfoRepository.GetCachedCodeInfo(codeSource, in codeHash, vmSpec); + } + public void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec) => codeInfoRepository.InsertCode(code, codeOwner, spec); From 70b3b0144c74f650c1482e8c380fcfe7e80097cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= <43241881+kamilchodola@users.noreply.github.com> Date: Wed, 18 Feb 2026 00:20:26 +0100 Subject: [PATCH 09/13] merge master (#10563) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixes 4 flaky test + refactors dbs (#10407) * Fix XDC flaky reward test with correct signature for transaction * Add Retry to LockFairnessTest * fix GetMemoryOwner for managed dbs + refactors * whitespace * Decrease Retain_Some_PersistedNodes threshold to resolve flakiness * fix review * tiny refactor * Parallelize Trie.Tests * Parallelize Nethermind.Blockchain.Test * Revert FilterManagerTests parall * Update Dockerfiles (#10409) Co-authored-by: rubo * parallelize txpool test + fixes for parallel blockchain tests (#10418) * Fix XDC flaky reward test with correct signature for transaction * Add Retry to LockFairnessTest * fix GetMemoryOwner for managed dbs + refactors * whitespace * Decrease Retain_Some_PersistedNodes threshold to resolve flakiness * fix review * tiny refactor * Parallelize Trie.Tests * Parallelize Nethermind.Blockchain.Test * Revert FilterManagerTests parall * parallelize some TxPool tests * Try parallelize more tests * make TxPool tests more parallelizable * revert * fix * fix issues in Nethermind.Blockchain.Test * more fixes * more fixes * FilterManagerTests nonparallelizable * fix * fix flaky test * fix shutter test * Add retry to Fuzz_accounts_with_storage * retry flaky test * add [NonParallelizable] * XDC - Add custom state sync allocation strategy (#10399) add custom state sync allocation strategy for xdc * ProcessingStats Extensibility (#10420) * processing stats extensibility * improvement * build fix * fix * fix: correct Bytes.BytesComparer length comparison ordering (#10353) * fix: correct Bytes.BytesComparer length comparison ordering The BytesComparer had inverted length comparison logic: - null was considered greater than non-null (should be less) - shorter arrays with same prefix were considered greater than longer arrays (should be less) Fixed by inverting the return values for length-related comparisons: - null < non-null - [] < [x, ...] (empty < non-empty) - [prefix] < [prefix, more] (shorter with same prefix < longer) Co-Authored-By: Claude Opus 4.5 * Switched to sequence compare to * Address comment * fix: handle null y in BytesComparer when x is non-null Null arrays implicitly convert to empty ReadOnlySpan in SequenceCompareTo, losing the null vs empty distinction. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.5 * Use CodeInfo type instead of ICodeInfo (#10423) * Use CodeInfo type instead of ICodeInfo Replace the ICodeInfo abstraction with a concrete CodeInfo type and adapt related APIs and implementations. CodeInfo was extended to carry precompile info, provide Code/CodeSpan, Version, IsPrecompile/IsEmpty semantics and background analysis. EofCodeInfo now derives from CodeInfo and provides EOF-specific data and versioning. PrecompileInfo was removed and precompiles are represented as CodeInfo (wrapping CachedPrecompile when needed). API changes: ICodeInfoRepository, IOverridableCodeInfoRepository and IPrecompileProvider signatures and caches now use CodeInfo; CodeInfoFactory and CodeLruCache updated accordingly. Call sites across the VM, instruction implementations, tracers, transaction processing, repositories and tests were updated to use CodeInfo directly and to perform EOF checks using 'is EofCodeInfo' where appropriate. JumpDestinationAnalyzer.MachineCode was made accessible for CodeInfo.Code. Overall this unifies EOF and precompile handling under a single CodeInfo model and simplifies caching and execution logic. * Feedback * Update CI workflows for Taiko/Surge integration tests (#10419) * Add CI workflow for Surge integration tests and update workflow for Taiko * Update CI workflow to reference the correct surge configuration file * Update CI workflows to increase timeout for integration tests and adjust repository references * Resolve comments * Parallelizable does not work on all XDC tests (#10431) remove broken Parallelizable Co-authored-by: Lukasz Rozmej * Add JitAsm tool to be able to analyse the Jit output (#10432) * Add JitAsm tool to be able to analyse the Jit output * Spelling * Address AGENTS.md LINQ guideline feedback on JitAsm PR (#10433) * Initial plan * Strengthen AGENTS.md LINQ guideline with explicit examples Co-authored-by: benaadams <1142958+benaadams@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: benaadams <1142958+benaadams@users.noreply.github.com> * Spell * feedback * Feedback * Improve initialization --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: benaadams <1142958+benaadams@users.noreply.github.com> * Update README with performance highlights of Nethermind (#10359) * Update README with performance highlights of Nethermind Revised description to emphasize performance metrics. * Update README.md Co-authored-by: Lukasz Rozmej * Update README.md Co-authored-by: Ruben Buniatyan * Update README with image sources and badges * Add 'srcset' to cspell.json dictionary * Remove cspell directives from README Removed cspell directives from README.md. --------- Co-authored-by: Lukasz Rozmej Co-authored-by: Ruben Buniatyan * Auto-update fast sync settings (#10449) Co-authored-by: rubo * Update OP Superchain chains (#10448) Co-authored-by: emlautarom1 * Fix flaky tests: timing and race condition (#10455) - PeerManagerTests: Increase After timeout from 1000ms to 3000ms in Will_not_stop_trying_on_rlpx_connection_failure to prevent false failures on loaded CI runners - SyncServerTests: Use Interlocked.Increment in Broadcast_NewBlock_on_arrival_to_sqrt_of_peers to fix race condition where concurrent SyncPeerMock background threads could lose count increments - Rename _travisDelay fields to _delay, _delayLong, _delayLonger Co-authored-by: Claude Opus 4.6 * Metrics and BlockStatistics extension (#10429) * Metrics and BlockStatistics extension * extensibility fixes * AGENTS shouldn't be repetitive when adding code (#10460) * more Agents changes (#10461) * more Agents changes avoid var, use testcases * Revise testing guidelines and LINQ usage recommendations Updated guidelines for adding tests and using LINQ. * CodeInfo Extension (#10467) refactoring & codeinfo changes * fix: add missing yield break after shutdown response in `JsonRpcProcessor` (#10462) * fix: add missing yield break after shutdown response * Add test * ensure pipereader is completed on shutdown early-exit * cover pipereader completion when shutdown is requested --------- Co-authored-by: Lukasz Rozmej * Fix fuzz issue (#10459) * Fix flaky network test (#10463) * Update packages (#10421) * Remove redundant allocation in Rlp.Encode when input is already Rlp (#10468) * Update Rlp.cs * Add regression test for Rlp.Encode with Rlp input --------- Co-authored-by: Ben {chmark} Adams * Add SeqlockCache (#10415) * Add SeqlockCache and use for Accounts and Values * Add tests * Spelling * By in * Feedback * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix * Feedback * Change to 2 way cache * 2-way skew-associative * Add prefetch for way 1 and hash-bit alternating eviction Prefetch way 1's cache line while checking way 0 to hide L2/L3 latency in the skew layout where ways are ~1MB apart. Use a hash bit to alternate eviction when both ways are live instead of always evicting way 0 — measured identical hit rates with zero extra memory. * Add Volatile.Read/Write for _hashes in BlockhashProvider Ensures cross-thread visibility of the prefetched hashes array on ARM's weak memory model. On x86 (TSO) these are no-ops. * Convert NodeStorageCache and PreBlockCaches.RlpCache to SeqlockCache * Inline Address comparison in StorageCell.Equals for SeqlockCache hot path Seal Address class and inline 20-byte comparison (Vector128 + uint) directly in StorageCell.Equals to eliminate Address.Equals calls that the JIT refuses to inline in deep chains. Adds ReferenceEquals fast-path. * Outline cold paths in SeqlockCache and fix IsPersisted delegate allocation Extract GetOrAddMiss as NoInlining to keep GetOrAdd hit path lean (2122 -> 748 bytes). Change WriteEntry to NoInlining and collapse eviction tree to single call site. Reuse cached _tryLoadRlp delegate in PreCachedTrieStore.IsPersisted. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add ability to override block fields in debug_ calls (#10405) * Align tx sending error code (#10464) * Optimize transaction processing via encoded-tx trie root and fast block re-encode (#10435) * Add encoded-tx root calc and trie decode perf Allow computing the transactions trie root directly from RLP-encoded transactions and update callers to use it. ExecutionPayload.TryGetBlock now passes the encoded Transactions to TxTrie.CalculateRoot. Implement TxTrie.CalculateRoot(ReadOnlySpan) and InitializeFromEncodedTransactions to populate the trie from encoded payloads and compute RootHash; use a TrackingCappedArrayPool and UpdateRootHash. Add a unit test to assert encoded and decoded transaction paths produce the same root. Also refine trie node RLP encoding parallelization: UseParallel now checks the node's non-null child count (only parallelize when >= 4 children and multiple CPU cores) to avoid parallel overhead on small branches. * Fast block re-encode * Feedback * chore: Update .NET packages (#10482) Update .NET packages * Move tx block-format wrapping logic from BlockDecoder to TxDecoder (#10476) * Move tx block-format wrapping logic from BlockDecoder to TxDecoder Extract the legacy-vs-typed tx wrapping knowledge into static helpers on TxDecoder (GetBlockFormatLength, WriteBlockFormat) so BlockDecoder no longer directly checks TxType.Legacy for encoding decisions. * Feedback * Update Dockerfiles (#10485) Co-authored-by: rubo * fix: clear reference-type elements in Truncate to prevent pool memory leak (#10472) * fix: clear reference-type elements in Truncate to prevent pool memory leak * add helper --------- Co-authored-by: Lukasz Rozmej * Remove Vault and NDM remnants (#5462) (#10478) - Remove Vault from ModuleType and spaceneth configs - Remove NDM from Protocol, ProtocolParser, and tests - Remove unused NDM timeout constants from Timeouts - Update AddCapabilityMessageSerializer comment Co-authored-by: Lukasz Rozmej * Faster FastHash (#10410) * Faster FastHash * Feedback * Split FastHash into dispatcher + dedicated AES/CRC methods Split monolithic FastHash into thin dispatcher + FastHashAesX64, FastHashAesArm, and FastHashCrc. Eliminates XMM saves from CRC path, removes ARM ternaries from x64 codegen, replaces MEMCPY tail with CRC scalar processing, and simplifies 4-lane fold to 3 XOR + 1 AES. AesX64 codegen: 294 bytes (was 564), CRC: no XMM registers at all. AES/CRC ratio: 1.75x at 256B, 2x at 1024B. * Optimize JSON-RPC request parsing and processing (#10453) * Add encoded-tx root calc and trie decode perf Allow computing the transactions trie root directly from RLP-encoded transactions and update callers to use it. ExecutionPayload.TryGetBlock now passes the encoded Transactions to TxTrie.CalculateRoot. Implement TxTrie.CalculateRoot(ReadOnlySpan) and InitializeFromEncodedTransactions to populate the trie from encoded payloads and compute RootHash; use a TrackingCappedArrayPool and UpdateRootHash. Add a unit test to assert encoded and decoded transaction paths produce the same root. Also refine trie node RLP encoding parallelization: UseParallel now checks the node's non-null child count (only parallelize when >= 4 children and multiple CPU cores) to avoid parallel overhead on small branches. * Fast block re-encode * Feedback * Cache TokenValidationParameters as readonly field Move TokenValidationParameters construction from per-request AuthenticateCore to the constructor, eliminating repeated allocation on cache-miss auth path. * Implement manual HS256 JWT fast validator with library fallback Add zero-allocation manual JWT validation for known HS256 header formats. Uses HMACSHA256.HashData (static) and CryptographicOperations.FixedTimeEquals for signature verification. Handles iat and exp claims. Falls back to Microsoft.IdentityModel for unrecognized header formats. * Refactor ByteArrayConverter write path Replace WriteRawValue with WriteStringValue(ReadOnlySpan) on the main value-write path, eliminating manual quote handling. Raise stackalloc threshold to 256 bytes. Use "0x0"u8 literal for zero-value fast path. Delegate-based overload retained for property name writes. * Add fixed-size fast path for 32-byte hash converters Hash256Converter and ValueHash256Converter now use a dedicated 66-byte stackalloc path (0x + 64 hex chars) that skips CountLeadingNibbleZeros, ArrayPool, and the generic ByteArrayConverter write path. * Direct numeric-to-hex for Long, ULong, UInt256 converters Use BitOperations.LeadingZeroCount and nibble-extraction loop to write hex directly without byte array intermediary. UInt256 uses LZCNT on limbs. All paths use WriteStringValue instead of WriteRawValue. Zero values use "0x0"u8 literal. ZeroPaddedHex uses fixed 66-byte stackalloc path. * Replace ForcedNumberConversion AsyncLocal with ThreadStatic cache GetFinalConversion() now reads from a ThreadStatic cache instead of AsyncLocal on every call. The ThreadAwareAsyncLocal wrapper updates both the AsyncLocal and ThreadStatic when Value is set, maintaining backward compatibility with tracing/debug converters. * Pool CancellationTokenSource with TryReset BuildTimeoutCancellationToken now rents from a ConcurrentBag pool. TryReset() returns CTS to pool on completion. Debugger-attached path skips pooling. Eliminates per-request CTS allocation on hot path. * Increase StreamPipeWriter minimumBufferSize from 4KB to 16KB Reduces Grow calls during serialization of typical Engine API responses (~5-20KB). Tradeoff: slightly higher per-connection memory baseline. * Bypass BufferResponses for Engine API + call StartAsync before body Authenticated single responses (Engine API) skip RecyclableStream double-copy. Call StartAsync() before serialization to flush headers early and avoid PinnedBlockMemoryPool.Rent allocations. * Replace JsonRpcContext.Current AsyncLocal with ThreadStatic Replace AsyncLocal with [ThreadStatic] field and a ThreadStaticAccessor wrapper that preserves the .Value API shape. This eliminates the ExecutionContext capture cost on every JSON-RPC request. * Use synchronous JsonSerializer.Serialize for PipeWriter path Replace JsonSerializer.SerializeAsync(PipeWriter, ...) with synchronous Utf8JsonWriter + JsonSerializer.Serialize. This eliminates the async state machine allocation since PipeWriter implements IBufferWriter and data is flushed by the caller's CompleteAsync(). * Eliminate CTS for Engine API path Skip per-request CancellationTokenSource allocation for authenticated Engine API requests. They use CancellationToken.None since they are from trusted consensus clients and must complete for consensus. Connection drops are handled by the PipeReader. Non-engine paths still use the pooled CTS from T08. * Engine API terminal middleware before routing/CORS Add an early-pipeline middleware that intercepts authenticated engine port POST requests before routing, CORS, response compression, and WebSocket middleware. Engine API requests are handled directly, writing to the response body without buffering. Non-engine requests pass through to the standard middleware pipeline. * Skip CountingPipeReader when Content-Length is known In the engine API fast lane, use ctx.Request.BodyReader directly and Content-Length for metrics, eliminating CountingPipeReader overhead. In the standard handler, skip CountingPipeReader when Content-Length is available and fall back to it only for chunked requests. * Seal hot classes and capture concrete types Seal EthereumJsonSerializer, JsonRpcProcessor, and JsonRpcService (no subclasses exist). In Startup.cs, cast DI-resolved interfaces to their concrete types and use those in middleware closures, enabling JIT devirtualization of hot-path method calls. * Micro-optimizations in request processing - Replace LINQ Select in batch deserialization with explicit loop - Use TryGetDecimal instead of GetRawText for numeric JSON-RPC id parsing - Replace ElementAtOrDefault with bounds-checked indexed access in LogRequest - Intern known engine method names via ValueEquals to avoid string allocation - Remove unused System.Linq using from JsonRpcProcessor * Startup warmup for serializer metadata Add EthereumJsonSerializer.WarmupSerializer() that pre-serializes instances to populate System.Text.Json metadata caches. Call it at startup with JsonRpcSuccessResponse and JsonRpcErrorResponse to eliminate cold-start serialization overhead on first engine request. * Move log interpolation to NoInlining local/static functions Keep string interpolation out of hot paths by moving log bodies to [MethodImpl(MethodImplOptions.NoInlining)] functions so the JIT does not inline the interpolation into callers. * STJ source generation for engine, eth, debug, and trace API types Add source-generated JsonSerializerContext instances for all major RPC type families to eliminate reflection-based metadata lookup on hot paths. - EngineApiJsonContext: 19 engine API types (payloads, forkchoice, blobs) - FacadeJsonContext: BlockForRpc, TransactionForRpc, FilterLog, SyncingResult - EthRpcJsonContext: receipts, fee history, account proofs, plus debug/trace types (GethLikeTxTrace, ParityTxTraceFromStore, ChainLevelForRpc, etc.) - JsonRpcResponseJsonContext: success/error response envelope types Infrastructure changes: - AddTypeInfoResolver() on EthereumJsonSerializer with version-tracked propagation to all existing serializer instances - Cache JsonTypeInfo on ExpectedParameter for typed deserialization - WriteJsonRpcResponse() in Startup.cs for typed response serialization - Add [JsonIgnore] to Span properties on Hash256, Bloom, Signature to prevent SYSLIB1225 source generator errors * Optimize JwtAuthentication * Optimize ByteArrayConverter * Test fix * Spelling * Optimize LongConverter * Spell * Optimize Hash256Converter * Optimize UInt256Converter * Fix tests * Spelling * Feedback * Optimize * Optimize * Spell * Also Avx512 * Formatting * Spell * Drop the weird pattern matching * Use concurrentqueue instead * Stream blobs directly Before (3 copies of hex data): 1. ByteArrayConverter → hex-encode into rented ArrayPool buffer (262KB) 2. Utf8JsonWriter.WriteRawValue → memcpy into Utf8JsonWriter's internal buffer (262KB) 3. Utf8JsonWriter flush → memcpy into PipeWriter/Kestrel send buffer (262KB) After (1 copy): 1. OutputBytesToByteHex → hex-encode directly into writer.GetSpan() (Kestrel's send buffer) So copies 2 and 3 are eliminated. Copy 1 (the binary→hex transform) remains * formatting * Spell * Tidy up * tidy up * Feeedback * Feedback * Reduce contention in trie root hashing Replace lock+List in TrackingCappedArrayPool with ConcurrentQueue for parallel paths and bare List for sequential paths. Skip parallel root hashing for small tries (<=64 items) to avoid scheduling overhead. Fix race condition where ReceiptTrie and TxTrie called UpdateRootHash without propagating canBeParallel, causing concurrent List.Add on a non-thread-safe collection. * Feedback * Run newPayload inline (#10479) * Skip response compression for Engine API requests * Optimize engine_getBlobsV2 with fused batch lookup and zero-copy proofs Replace N+1 lock acquisitions with a single fused TryGetBlobsAndProofsV1 that atomically counts and extracts blobs under one lock. Use ReadOnlyMemory to window into wrapper.Proofs arrays instead of copying via Slice+spread, eliminating ~0.4MB of proof allocations per request. Replace ArrayPoolList with parallel arrays, removing the pool rent/return overhead. * Split blob lookups into two phases to reduce lock hold time (#10173) Add ITxStorage.TryGetMany for batched RocksDB MultiGet and override TryGetBlobsAndProofsV1 in PersistentBlobTxDistinctSortedPool with a two-phase approach: fast in-memory + cache lookups under lock, then a single batched DB read outside the lock. This avoids holding the pool's McsLock during potentially slow I/O for up to 128 blobs per request. Co-Authored-By: Lukasz Rozmej * Remove dead GetBlobCounts method (#10159) No longer called after getBlobsV2 switched to batched TryGetBlobsAndProofsV1. Co-Authored-By: Marcin Sobczak <77129288+marcindsobczak@users.noreply.github.com> * Feedback * Spelling * Add some const to JwtAuthentication to understand the code better * Consolidate duplicated JSON-RPC request processing pipelines in Startup Extract shared ProcessJsonRpcRequestCoreAsync and PushErrorResponseAsync instance methods from the engine API fast lane and standard handler, eliminating ~100 lines of duplicated processing logic. Unify status code constants, auth error handling, and add streamable response support to the standard path. * Make BlobsV2DirectResponse enumerator explicit; pool byte[64] keys in BlobTxStorage - Convert GetEnumerator to explicit interface implementation since it is only used by tests via IEnumerable cast - Add ConcurrentQueue-based pool for exact-size byte[64] DB lookup keys in TryGetMany to avoid per-call allocations --------- Co-authored-by: Lukasz Rozmej Co-authored-by: Marcin Sobczak <77129288+marcindsobczak@users.noreply.github.com> Co-authored-by: lukasz.rozmej * Fix BackgroundTaskScheduler queue overflow during block processing (#10488) * Initial plan * Fix BackgroundTaskScheduler queue overflow by removing signal wait from scheduler threads During block processing, ManualResetEventSlim blocked all scheduler threads in BelowNormalPriorityTaskScheduler, preventing StartChannel from draining expired tasks. New tasks (P2P transaction messages) continued arriving, filling the queue beyond capacity (1024) and triggering task drops. Fix: Remove the ManualResetEventSlim signal entirely. The existing cancellation token mechanism in StartChannel already handles block processing correctly — expired tasks get drained with cancelled tokens (fast return), while non-expired tasks are re-queued with a 1ms throttle until their deadline passes or block processing ends. Co-authored-by: kamilchodola <43241881+kamilchodola@users.noreply.github.com> * Replace blocking ManualResetEventSlim with async TaskCompletionSource signal; add high-capacity stress test The original ManualResetEventSlim blocked scheduler threads in ProcessBackgroundTasks(), preventing StartChannel from draining expired tasks during block processing. Replace with TaskCompletionSource-based async signal awaited in StartChannel's Throttle path. Add comprehensive stress test that fills a 1024-capacity queue across multiple block processing cycles, verifying: - Tasks are dropped when queue exceeds capacity - Expired tasks drain with cancelled tokens during block processing - Queue recovers and accepts new tasks after draining - Mixed short/long-lived tasks behave correctly across cycles - Queue remains fully operational after repeated block processing Co-authored-by: kamilchodola <43241881+kamilchodola@users.noreply.github.com> * Double BackgroundTaskMaxNumber default from 1024 to 2048 Co-authored-by: benaadams <1142958+benaadams@users.noreply.github.com> * Add BenchmarkDotNet benchmark for BackgroundTaskScheduler throughput Adds BackgroundTaskSchedulerBenchmarks with two scenarios: - ScheduleAndDrainDuringBlockProcessing: simulates real-world scenario with periodic block-processing pauses (5 cycles, 50ms each) - ScheduleAndDrainWithoutBlockProcessing: baseline without interruptions Parameters: capacity (1024/2048), concurrency (2), block processing duration (50ms), cycles (5). Also fixes flaky over-capacity assertion in stress test that raced with the async task draining. Co-authored-by: kamilchodola <43241881+kamilchodola@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kamilchodola <43241881+kamilchodola@users.noreply.github.com> Co-authored-by: benaadams <1142958+benaadams@users.noreply.github.com> * Update Dockerfiles (#10493) Co-authored-by: rubo * fix: unsubscribe from BlocksProcessing in Dispose (#10499) Signed-off-by: Olexandr88 * Fix Kute Windows performance: disable proxy detection, fix file check (#10491) * Fix Kute Windows performance: disable proxy detection, fix file check - Disable WPAD proxy auto-detection in HttpClient by using SocketsHttpHandler with UseProxy=false. On Windows, the default HttpClient triggers a WPAD lookup (~2s timeout) on every fresh process, inflating measured NP times by ~2000ms. - Fix FileMessageProvider to use pathInfo.Exists instead of HasFlag(FileAttributes.Normal). On Windows, files have Archive attribute, not Normal, causing all file reads to fail. Co-Authored-By: Claude Opus 4.6 * Update comment to remove 'WPAD' reference --------- Co-authored-by: Claude Opus 4.6 * chore: Remove unused code across multiple modules (#10440) * claude experiments * reverted changes * Remove unused code * fix: restore _logger field in TdxsClient to prevent CS9113 build error The logManager parameter is DI-injected and cannot be removed, so the field must remain to consume it. Co-Authored-By: Claude Opus 4.6 * Remove unused EVM exception classes BadInstructionException, InvalidCodeException, and TransactionCollisionException are never instantiated anywhere in the codebase. The EVM uses EvmExceptionType enums and CallResult directly. Co-Authored-By: Claude Opus 4.6 * refactor: remove unused code (Phase 1) - Delete 5 empty (0-byte) files in TxPool and Consensus - Remove 7 unused private methods: - TrieStore.IsStillNeeded() - PatriciaTree.ThrowInvalidDataException/ThrowMissingChildException/ThrowMissingPrefixException - EthereumRunner.Stop() - EthereumL1Bridge.LogReorg() - Ssz.DecodeDynamicOffset() (moved VarOffsetSize to BasicTypes.cs) - Remove 7 unused private/static fields: - TrieNode._nodeDecoder - PatriciaTree._singleByteKeys - PersistentStorageProvider._loadFromTreeStorageFunc - SmallTrieVisitContext._branchChildIndex - TdxsClient._logger - InitializeBlockchain._logManager - SyncProgressResolver._logger - Remove 6 dead NDM/LES timeout constants from Timeouts.cs - Remove 11 unused/duplicate ErrorCodes constants - Remove 3 unused L1 epoch constants from EthereumL1Bridge - Clean up unused using directives (System.IO, System) * fix: remove unused logManager parameter from TdxsClient Removes unused ILogManager parameter that was causing CI build failure. The parameter was not used anywhere in the class after previous cleanup. Co-Authored-By: Claude Sonnet 4.5 * refactor: remove unused types (Phase 2) Delete 15 unreferenced type files identified through codebase-wide static analysis: Synchronization (5): - AllocationChangeEventArgs - StaticStrategy, NullStrategy, ClientTypeStrategy - StrategySelectionType enum Core (5): - CompositePublisher, SortedRealList, ConcurrentWriteBatcher - CappedArrayMemoryManager, ISpanCache Serialization/Network/RPC (5): - LogPublisher, CountingTextWriter, CountingTextReader - IDiscoveryMsgSerializersProvider, IJsonRpcResult Co-Authored-By: Claude Opus 4.6 * Remove unused logManager parameter from SyncProgressResolver constructor (#10452) * Initial plan * refactor: remove unused logManager parameter from SyncProgressResolver Co-authored-by: MarekM25 <9356351+MarekM25@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: MarekM25 <9356351+MarekM25@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.6 Co-authored-by: smartprogrammer93 Co-authored-by: smartprogrammer93 Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: MarekM25 <9356351+MarekM25@users.noreply.github.com> * fix(network): use TrySetResult in MessageQueue.Handle to prevent race condition (#10486) * fix(network): use TrySetResult in MessageQueue.Handle to prevent race condition * add tests * Fixes --------- Co-authored-by: Ben Adams * fix: fix operator precedence bug in VoteDecoder.GetContentLength (#10383) Update VoteDecoder.cs Co-authored-by: ak88 * ci: add automatic PR labeling workflow (#10512) * ci: add PR auto-labeler workflow Automatically labels PRs based on: - Template checkbox selections (bugfix, feature, breaking, etc.) - Conventional commit title prefixes (fix:, feat:, perf:, etc.) - Changed file paths (evm, network, optimism, taiko, xdc, etc.) - EIP mentions in title - Dockerfile changes, chain configs, test-only PRs, removal-only PRs * docs: require PR template usage in AGENTS.md Clarify that PRs must follow the template format and tick the type-of-change checkboxes, which drive automatic labeling. * ci: use pull_request_target for PR labeler to support fork PRs * ci: add DbModule.cs path rule for database label * ci: add trie and state+storage path rules for PR labeler * ci: match slash separator in title prefix (e.g. Perf/xdc) * ci: add sync, snap sync path rules for PR labeler * Remove redundant null check and duplicate array in Program.cs (#10506) Update Program.cs Co-authored-by: Lukasz Rozmej * Use copy-on-write snapshots in MetricsController (#10501) * fix(monitoring): use copy-on-write snapshots in MetricsController to prevent concurrent modification Replace List and Dictionary iteration with volatile array snapshots to prevent InvalidOperationException when RegisterMetrics or AddMetricsUpdateAction races with the timer-driven UpdateAllMetrics. * fix(monitoring): use copy-on-write snapshots in MetricsController to prevent concurrent modification Replace List and Dictionary iteration with volatile array snapshots to prevent InvalidOperationException when RegisterMetrics or AddMetricsUpdateAction races with the timer-driven UpdateAllMetrics. * chore: remove redundant AddDatabase for BlobTransactions (#10510) Co-authored-by: Lukasz Rozmej * perf: reduce block processing overhead via journal bypass and commit elimination - Eliminate pre-execution Commit in TransactionProcessor for non-tracing, non-restore paths - Add read-cache separation for StateProvider (bypass journal for pure reads) - Add read-cache separation for PersistentStorageProvider (bypass journal for SLOAD) - Reduce block-level commits from 4 to 3 (beacon root/blockhash accumulate with tx changes) - Preserve full tracing correctness with fallback paths for Before values and storage reads Co-Authored-By: Claude Opus 4.6 * SyncInfo decoder for XDC p2p (#10509) syncinfo decoder * Revert "Merge branch 'master' of https://github.com/NethermindEth/nethermind into perf/block-processing-pipeline-v1" This reverts commit 1d7dede877b5cbd29ff71f1e97e32c88ffac3f58, reversing changes made to 46ae4aa0f77fce1ec30d5caf2c660d2d5ce41568. * ci: add JsonRpc path pattern to PR labeler (#10526) ci: add JsonRpc path pattern to PR labeler for automatic rpc label Adds src/Nethermind/Nethermind.JsonRpc to the pathToLabel array so PRs modifying JsonRpc files get automatically labeled with 'rpc'. Co-authored-by: smartprogrammer93 * Log index (#8464) * Adjust `OnReceiptsInserted` logging * Naming * Normalize block number formatting # Conflicts: # src/Nethermind/Nethermind.Facade/Find/LogIndexService.cs * Build fix * Code cleanup * Use ancient receipts barrier for backwards sync target & include target values in RPC * Adjust logging and waiting times * Fresh start fix * Adjust min target block calculation * Fix repeated completion logging * Temporary more logging again * Try recover receipts * Optimize processing of no-tx blocks * Fir out-of-range error * Fix receipts deletion (#9231) * separate receipt deletion from receipt tx deletion * regression test * tidy test --------- Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> * `logIndex_blockNumbers` RPC * Logging tweaks * [WIP] Support for topic index * Remove key size dependencies * Try compress key in both DBs * Separate merge operator per column family * Fixes * Remove possible collisions as causing invalid merge order * More fixes * Constant db key postfix size & fixes * Screw this, use separate DB per topic index * Get rid of generic iterator interface * Fixes for seeking, but getting block numbers is now much slower * Code cleanup * Do not remove leading zeroes & revert seeking * Fix filter matching * Tests fixes * Move filter tests to separate class & code formatting * Support for concurrent forward/backward sync & tests * Fixes for concurrent sync & more aggresive test * Code cleanup * Fix log index syncing blocking other tasks * Refactor syncing for better concurrency * Increase receipts cache size to 1024 * Log receipts loading time * Adjust parallelism values * Track backward/forward syncing stats separately * Revert "Increase receipts cache size to 1024" This reverts commit 18a13d508eb98a84fae0aa2e6654815c22efbda6. * Set DB version * Option to delay compressor start * Include index version and reset data if version is invalid * Log index compaction RPC * todo * Add missing service registration * Remove "waiting for block" trace logging * Do start indexing before DB initialization * Code cleanup * Fix double DB initialization * Fix aggressive memory usage * Stop on error * todos * Formatting * Configuration for log-index & support for disabling/resetting index via config * Updates to logIndex_status, include status, last-update-data, and last-error * Replace last-update with `DateTimeOffset` * Typo fix * Syncing service small refactoring, fixes, and basic tests * Fix repeated completion logging * Adjust default `SyncFetchBatchParallelism` * TurboPFor updates to match package version * Build fix * Configurable compression algorithm, handle if unsupported * Support for `IWriteBatch` clearing * Remove `RequireCommitWriteBatch` * Throw if stopped * Simplify `GetDbSize` * Prevent DB corruption in case of mid-sync error & tests * Remove stopping on error for now * Updated compression-algo-change tests * Formatting * Small logging fix * ~Temporary log completion time * Configurable logging details * Fix for invalid ranges concatenation * Revert "~Temporary log completion time" This reverts commit f3f631f8d0e00e8c72dca348a64a64616196b0f7. * Additional check to prevent using block 0 as pivot * Flag to verify eth_getLogs responses from index * Build fix * Switch to nuget package for TurboPFor * Parallelize `LogIndexStorageFilterTests` * Formatting * Try fix `LogIndexServiceTests` * TurboPFor package update * Fix immediate backward sync completion * Tests for `LogFinder` index range calculation * Min number of blocks to use index * PR cleanup * Better handle write attempt during stopping * Temporary fix for missing Nuget dependency * PR cleanup * Revert attempt to use `OneTimeTearDown` * Optimize compressor memory usage * Fail log index on background job error * Dependency fix * Take in-progress into account when waiting for compression * Use array pool for compression * Adjust background exception handling * Adjust exception handling * Move registrations * Take sync direction from aggregation * Remove first-block-added notification * Remove testing methods * Remove unused code & PR cleanup * Formatting * PR cleanup * Namespace update * PR cleanup * Builder code cleanup * Config update * Remove RPC response verification * PR cleanup * Tests fix * Disable index by default * Tests fix * Fix compaction being disabled * Increase compaction logging level * Make compression optional * Remove completed TODO * Logging updates * Return RPC response verification flag with proper checks * Build fix * PR cleanup * Naming update to match existing signatures * Partial test run fix * Logging tweak * PR cleanup * PR cleanup * Move Merge to separate interface * PR feedback * Storage stability improvements & enable `OneTimeTearDown` in integration tests * Receipts events renaming * PR feedback * Formatting * Code cleanup * Code cleanup * Try make storage disposing thread-safe * Do not throw from merge operator * Disable `OneTimeTearDown` again * Fix missing DB config * Fix disposing in case of error in ctor * Use sorted view instead of iterator * Do not publicly expose iterator * Code cleanup * Revert changes to DB config reading * PR feedback * PR feedback * PR feedback * Build fix * Cherry-pick updates * Commit all columns via single batch, store metadata in separate table * Fix saving redundant data to range bound * DB config fix * Code formatting * Tests fix * [WIP] block number enumerator (nested) * [WIP] Fixing enumerator * [WIP] Fixing enumerator * Fixes, code cleanup & use array pool for enumerator value * Code cleanup * Visitor tests cleanup * Formatting * Simplify filter/expression updates * Code cleanup * Build fix * Tests fix * Code cleanup * Remove `AscListHelper` as not needed * Inline `UnionEnumerator`, v1 * Revert "Inline `UnionEnumerator`, v1" This reverts commit 5b725434c3e190902f555b92078d87a0e6e6f546. * More merge operator tests * Code cleanup * Update TurboPFor package * Simplify `LogFinder` * Build fix attempt * Make `LogIndexFilterVisitor` enumerators internal * Remove unused parameter * Code cleanup * Formatting * Spelling * Try make cspell happy * Try make cspell happy * Try make cspell happy * Try make cspell happy * PR fixes * Test build fix * PR fixes * Move log-index features to inherited `IndexedLogFinder` * Simplify `filter_all_logs_iteratively` test * Formatting * Simplify `StartLogIndex` step * Block numbers copying optimization (little-endian only) * Test fix * Detailed in-code docs * Doc tweaks * More doc tweaks * PR cleanup * Make `GetBlockReceipts` a bit more explicit * Build fix * Spelling * minor refactors * more minor refactors * minor refactor of Compressor - add alternative lookup to avoid allocation as long as possible in TryEnqueue * simplifications in LogIndexStorage * simplify semaphores * move to stackalloc in LogIndexStorage where possible * simplify merge operators to inline array * correctly dispose ArrayPoolList in Merge tests * more simple refactors * In LogIndexBuilder replace Dictionaries iwth Direction * Simplify UnionEnumerator * Removed linq from IndexedLogFinder * Code cleanup * Simplify DBs initialization * Code cleanup * PR feedback * Code cleanup * Remove `storage` parameter from `LogIndexEnumerator` * Add complex LogIndex integration tests (#10425) * Add complex LogIndex integration tests for reorgs, concurrency, and error propagation - Add Concurrent_ReorgAndBackwardSync_Get_Test: validates concurrent reorg and backward sync using different semaphores without deadlocks or data corruption - Add Set_ReorgOutOfOrder_Get_Test: documents that descending-order reorgs are unsupported (MergeOperator only applies first Reorg operand per key by write order) - Add Set_BackgroundJobFailure_SubsequentOps_Test: verifies MergeOperator errors propagate via OnBackgroundError and permanently fail all subsequent operations - Add BackgroundFailingLogIndexStorage test subclass that injects corrupt merge operands to trigger LogIndexStateException in MergeOperator - Remove resolved TODO comments Co-Authored-By: Claude Opus 4.6 * minor test refactors * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix * Code cleanup * More code cleanup --------- Co-authored-by: Claude Opus 4.6 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Alex Bespalov * Simplify Compactor concurrency with Channel (#10424) * Simplify Compactor concurrency with Channel Replace AutoResetEvent/ManualResetEvent synchronization with a bounded Channel, reducing complexity and removing the WaitOneAsync extension dependency. Co-Authored-By: Claude Opus 4.6 * Fix error propagation in Compactor catch block Use TrySetException(ex) for real exceptions so ForceAsync() callers observe the actual error. Keep TrySetCanceled() only for OperationCanceledException. Co-Authored-By: Claude Opus 4.6 * Address PR review comments - Coalesce concurrent ForceAsync() calls into a single compaction via a shared TaskCompletionSource, preserving the old behavior. - Use TrySetException instead of TrySetCanceled for non-cancellation errors so callers observe the actual exception. - Add debug log for compaction loop cancellation. Co-Authored-By: Claude Opus 4.6 * Make ForceAsync exclusive * whitespace * Add Compactor.Dispose * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Adjust ordering * Add comments and prepare `Compactor` for tests * Some `Compactor` tests * Spelling * Spelling dictionary update --------- Co-authored-by: Claude Opus 4.6 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Alex Bespalov * [WIP] use `PruningConfig.PruningBoundary` as default reorg depth * Use `PruningConfig.PruningBoundary` as default reorg depth * Take `PruningBoundary` before it's overwritten via `SnapServingMaxDepth` * Revert test change * Flaky test fix --------- Co-authored-by: Marc Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: lukasz.rozmej Co-authored-by: Claude Opus 4.6 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Auto-update fast sync settings (#10535) Co-authored-by: rubo * Update OP Superchain chains (#10536) Co-authored-by: emlautarom1 * fix: fix malformed RLP encoding in L1OriginDecoder (#10525) * Update L1OriginDecoder.cs * Update L1OriginStoreTests.cs * Update SurgeGasPriceOracle for Surge Shasta (#10290) * SurgeGasPriceOracle updates for Shasta * minor fixes * more minor fixes * Resolve comments --------- Co-authored-by: Nurbakyt Madibek * Estimate Gas fix (#10559) * Estimate Gas fix * format * format * test fix * Update src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs * Update src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix shared method --------- Co-authored-by: Ahmad Bitar <33181301+smartprogrammer93@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * test(evm): add extcode cache regressions for in-block code changes --------- Signed-off-by: Olexandr88 Co-authored-by: Lukasz Rozmej Co-authored-by: core-repository-dispatch-app[bot] <173070810+core-repository-dispatch-app[bot]@users.noreply.github.com> Co-authored-by: rubo Co-authored-by: Carmen Irene Cabrera Rodríguez <49727740+cicr99@users.noreply.github.com> Co-authored-by: Stavros Vlachakis <89769224+svlachakis@users.noreply.github.com> Co-authored-by: Amirul Ashraf Co-authored-by: Claude Opus 4.5 Co-authored-by: Ben {chmark} Adams Co-authored-by: Nurbakyt Madibek Co-authored-by: ak88 Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: benaadams <1142958+benaadams@users.noreply.github.com> Co-authored-by: Marek Moraczyński Co-authored-by: emlautarom1 Co-authored-by: VolodymyrBg Co-authored-by: splinter Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Alexey Osipov Co-authored-by: phrwlk Co-authored-by: Nova ✰⋆⁺ Co-authored-by: Lukasz Rozmej Co-authored-by: Marcin Sobczak <77129288+marcindsobczak@users.noreply.github.com> Co-authored-by: Olexandr88 Co-authored-by: smartprogrammer93 Co-authored-by: smartprogrammer93 Co-authored-by: MarekM25 <9356351+MarekM25@users.noreply.github.com> Co-authored-by: andrewshab <152420261+andrewshab3@users.noreply.github.com> Co-authored-by: 0xFloki Co-authored-by: Forostovec Co-authored-by: Ahmad Bitar <33181301+smartprogrammer93@users.noreply.github.com> Co-authored-by: Alex Co-authored-by: Marc Co-authored-by: Marc Harvey-Hill <10379486+Marchhill@users.noreply.github.com> Co-authored-by: bobtajson <152420524+bobtajson@users.noreply.github.com> Co-authored-by: Diptanshu Kakwani --- .github/workflows/build-tools.yml | 1 + .github/workflows/ci-surge.yml | 167 +++ .github/workflows/ci-taiko.yml | 62 +- .github/workflows/pr-labeler.yml | 201 +++ .gitignore | 4 + AGENTS.md | 11 +- Directory.Packages.props | 49 +- Dockerfile | 4 +- Dockerfile.chiseled | 4 +- README.md | 2 +- cspell.json | 99 ++ scripts/build/Dockerfile | 2 +- .../Chains/arena-z-mainnet.json.zst | Bin 86216 -> 87959 bytes .../Chains/arena-z-sepolia.json.zst | Bin 82744 -> 84295 bytes .../Chains/automata-mainnet.json.zst | Bin 78345 -> 79847 bytes src/Nethermind/Chains/base-mainnet.json.zst | Bin 37232 -> 38338 bytes src/Nethermind/Chains/base-sepolia.json.zst | Bin 45456 -> 46230 bytes src/Nethermind/Chains/bob-mainnet.json.zst | Bin 69168 -> 70308 bytes src/Nethermind/Chains/boba-mainnet.json.zst | Bin 1502 -> 2564 bytes src/Nethermind/Chains/boba-sepolia.json.zst | Bin 1500 -> 2538 bytes src/Nethermind/Chains/camp-sepolia.json.zst | Bin 73911 -> 75484 bytes .../Chains/celo-sep-sepolia.json.zst | Bin 86242 -> 87789 bytes src/Nethermind/Chains/cyber-mainnet.json.zst | Bin 72230 -> 73511 bytes src/Nethermind/Chains/cyber-sepolia.json.zst | Bin 72241 -> 73487 bytes src/Nethermind/Chains/dictionary | Bin 65536 -> 65536 bytes .../Chains/ethernity-mainnet.json.zst | Bin 70753 -> 72175 bytes .../Chains/ethernity-sepolia.json.zst | Bin 74028 -> 75578 bytes .../Chains/fraxtal-mainnet.json.zst | Bin 143082 -> 144497 bytes src/Nethermind/Chains/funki-mainnet.json.zst | Bin 78337 -> 79866 bytes src/Nethermind/Chains/funki-sepolia.json.zst | Bin 72257 -> 73514 bytes .../Chains/hashkeychain-mainnet.json.zst | Bin 81902 -> 83435 bytes src/Nethermind/Chains/ink-mainnet.json.zst | Bin 86144 -> 87925 bytes src/Nethermind/Chains/ink-sepolia.json.zst | Bin 86245 -> 87884 bytes src/Nethermind/Chains/lisk-mainnet.json.zst | Bin 69115 -> 70230 bytes src/Nethermind/Chains/lisk-sepolia.json.zst | Bin 70141 -> 71731 bytes src/Nethermind/Chains/lyra-mainnet.json.zst | Bin 36671 -> 37934 bytes src/Nethermind/Chains/metal-mainnet.json.zst | Bin 69180 -> 70318 bytes src/Nethermind/Chains/metal-sepolia.json.zst | Bin 36972 -> 38283 bytes src/Nethermind/Chains/mint-mainnet.json.zst | Bin 69143 -> 70307 bytes src/Nethermind/Chains/mode-mainnet.json.zst | Bin 37229 -> 38311 bytes src/Nethermind/Chains/mode-sepolia.json.zst | Bin 41330 -> 42379 bytes src/Nethermind/Chains/op-mainnet.json.zst | Bin 1587 -> 2676 bytes src/Nethermind/Chains/op-sepolia.json.zst | Bin 48781 -> 49842 bytes .../Chains/orderly-mainnet.json.zst | Bin 37213 -> 38301 bytes src/Nethermind/Chains/ozean-sepolia.json.zst | Bin 70705 -> 72077 bytes .../Chains/pivotal-sepolia.json.zst | Bin 73882 -> 75415 bytes .../Chains/polynomial-mainnet.json.zst | Bin 69011 -> 70295 bytes src/Nethermind/Chains/race-mainnet.json.zst | Bin 70604 -> 72014 bytes src/Nethermind/Chains/race-sepolia.json.zst | Bin 70667 -> 72083 bytes .../Chains/redstone-mainnet.json.zst | Bin 69018 -> 70143 bytes .../Chains/settlus-mainnet-mainnet.json.zst | Bin 82794 -> 84357 bytes .../Chains/settlus-sepolia-sepolia.json.zst | Bin 86117 -> 87753 bytes src/Nethermind/Chains/shape-mainnet.json.zst | Bin 69036 -> 70130 bytes src/Nethermind/Chains/shape-sepolia.json.zst | Bin 72315 -> 73439 bytes .../Chains/soneium-mainnet.json.zst | Bin 86239 -> 87970 bytes .../Chains/soneium-minato-sepolia.json.zst | Bin 78497 -> 80019 bytes src/Nethermind/Chains/sseed-mainnet.json.zst | Bin 70707 -> 72166 bytes src/Nethermind/Chains/swan-mainnet.json.zst | Bin 67884 -> 69279 bytes src/Nethermind/Chains/swell-mainnet.json.zst | Bin 82890 -> 84459 bytes src/Nethermind/Chains/tbn-mainnet.json.zst | Bin 81789 -> 83494 bytes src/Nethermind/Chains/tbn-sepolia.json.zst | Bin 73978 -> 75515 bytes .../Chains/unichain-mainnet.json.zst | Bin 116381 -> 118352 bytes .../Chains/unichain-sepolia.json.zst | Bin 101586 -> 103133 bytes .../Chains/worldchain-mainnet.json.zst | Bin 72303 -> 73618 bytes .../Chains/worldchain-sepolia.json.zst | Bin 72376 -> 73622 bytes .../Chains/xterio-eth-mainnet.json.zst | Bin 72282 -> 73537 bytes src/Nethermind/Chains/zora-mainnet.json.zst | Bin 41317 -> 42378 bytes src/Nethermind/Chains/zora-sepolia.json.zst | Bin 37098 -> 38329 bytes src/Nethermind/Nethermind.Api/IBasicApi.cs | 2 +- src/Nethermind/Nethermind.Api/IInitConfig.cs | 2 +- src/Nethermind/Nethermind.Api/InitConfig.cs | 2 +- .../Nethermind.Api/NethermindApi.cs | 4 +- .../Core/FastHashBenchmarks.cs | 47 + .../Core/SeqlockCacheBenchmarks.cs | 345 +++++ .../BackgroundTaskSchedulerBenchmarks.cs | 181 +++ .../BeaconBlockRootHandlerTests.cs | 2 + .../BlockFinderExtensionsTests.cs | 1 + .../BlockProcessorTests.cs | 1 + .../BlockTreeSuggestPacerTests.cs | 1 + .../BlockTreeTests.cs | 2 + .../BlockchainProcessorTests.cs | 35 +- .../BlockhashProviderTests.cs | 1 + .../Blocks/BadBlockStoreTests.cs | 1 + .../Blocks/BlockStoreTests.cs | 1 + .../Blocks/HeaderStoreTests.cs | 2 +- .../Builders/FilterBuilder.cs | 9 +- .../CachedCodeInfoRepositoryTests.cs | 51 +- .../ChainHeadReadOnlyStateProviderTests.cs | 1 + .../Consensus/ClefSignerTests.cs | 1 + .../Consensus/CompositeTxSourceTests.cs | 1 + .../Consensus/NethDevSealEngineTests.cs | 1 + .../Consensus/NullSealEngineTests.cs | 1 + .../Consensus/NullSignerTests.cs | 1 + .../Consensus/OneByOneTxSourceTests.cs | 1 + .../Consensus/SealEngineExceptionTests.cs | 1 + .../Consensus/SignerTests.cs | 1 + .../Consensus/SinglePendingTxSelectorTests.cs | 1 + .../DaoDataTests.cs | 1 + .../Data/FileLocalDataSourceTests.cs | 1 + .../ExitOnBlocknumberHandlerTests.cs | 1 + .../Filters/AddressFilterTests.cs | 1 + .../Filters/FilterManagerTests.cs | 17 +- .../Filters/FilterStoreTests.cs | 3 + .../Filters/LogFilterTests.cs | 2 + .../Filters/LogIndexFilterVisitorTests.cs | 413 ++++++ .../Filters/TestProcessingContext.cs | 55 + .../Find/LogFinderTests.cs | 137 +- .../FullPruning/CopyTreeVisitorTests.cs | 19 +- .../FullPruning/FullPrunerTests.cs | 22 +- .../FullPruning/FullPruningDiskTest.cs | 34 +- .../PruningTriggerPruningStrategyTests.cs | 3 +- .../BuildBlockOnEachPendingTxTests.cs | 1 + .../Producers/BuildBlockRegularlyTests.cs | 1 + .../BuildBlocksWhenRequestedTests.cs | 1 + .../CompositeBlockProductionTriggerTests.cs | 1 + .../Producers/DevBlockproducerTests.cs | 1 + .../Producers/IfPoolIsNotEmptyTests.cs | 1 + .../Proofs/ReceiptTrieTests.cs | 23 + .../Proofs/TxTrieTests.cs | 52 +- .../Proofs/WithdrawalTrieTests.cs | 1 + .../ReadOnlyBlockTreeTests.cs | 2 + .../Receipts/KeccaksIteratorTests.cs | 1 + .../Receipts/PersistentReceiptStorageTests.cs | 16 +- .../Receipts/ReceiptsIteratorTests.cs | 3 +- .../Receipts/ReceiptsRecoveryTests.cs | 2 + .../Receipts/ReceiptsRootTests.cs | 1 + .../ReorgDepthFinalizedStateProviderTests.cs | 3 +- .../Nethermind.Blockchain.Test/ReorgTests.cs | 5 +- .../Rewards/NoBlockRewardsTests.cs | 1 + .../Rewards/RewardCalculatorTests.cs | 1 + .../Services/HealthHintServiceTests.cs | 1 + .../TransactionGasPriceComparisonTests.cs | 1 + .../TransactionProcessorEip7702Tests.cs | 111 +- .../TransactionProcessorTests.cs | 30 +- .../TransactionSelectorTests.cs | 1 + .../TransactionsExecutorTests.cs | 1 + .../Utils/LastNStateRootTrackerTests.cs | 1 + .../Validators/BlockValidatorTests.cs | 2 + .../Validators/HeaderValidatorTests.cs | 2 + .../ShardBlobBlockValidatorTests.cs | 1 + .../Validators/TxValidatorTests.cs | 5 +- .../Validators/UnclesValidatorTests.cs | 2 + .../Validators/WithdrawalValidatorTests.cs | 1 + .../Visitors/DbBlocksLoaderTests.cs | 1 + .../Visitors/StartupTreeFixerTests.cs | 1 + .../BlockhashProvider.cs | 10 +- .../CachedCodeInfoRepository.cs | 14 +- .../EthereumPrecompileProvider.cs | 6 +- .../Tracing/EstimateGasTracer.cs | 11 +- .../JavaScript/GethLikeJavaScriptTxTracer.cs | 3 +- .../Custom/Native/Call/NativeCallTracer.cs | 14 +- .../Tracing/GethStyle/GethTraceOptions.cs | 12 +- .../Tracing/ParityStyle/ParityLikeTxTracer.cs | 3 +- .../AuRaBlockFinalizationManager.cs | 1 + .../BlockHeaderExtensions.cs | 19 - .../Scheduler/BackgroundTaskSchedulerTests.cs | 175 +++ .../Processing/BlockCachePreWarmer.cs | 2 +- .../Processing/BlockchainProcessor.cs | 12 +- .../IOverridableTxProcessingScope.cs | 0 .../IOverridableTxProcessorSource.cs | 0 .../Processing/IProcessingStats.cs | 36 + .../OverridableTxProcessingScope.cs | 0 .../Processing/ProcessingStats.cs | 36 +- .../Scheduler/BackgroundTaskScheduler.cs | 30 +- .../Tracing/GethStyleTracer.cs | 4 +- .../Nethermind.Core.Test/BytesTests.cs | 142 ++- .../Collections/SeqlockCacheTests.cs | 428 +++++++ .../Encoding/BlockDecoderTests.cs | 88 +- .../Json/ByteArrayConverterTests.cs | 65 + .../Json/Hash256ConverterTests.cs | 63 + .../Json/LongConverterTests.cs | 42 + .../Json/NullableUlongConverterTests.cs | 27 + .../Json/UInt256ConverterTests.cs | 135 ++ .../Nethermind.Core.Test/MCSLockTests.cs | 1 + .../Nethermind.Core.Test/RlpTests.cs | 10 + .../Nethermind.Core.Test/TestMemDb.cs | 66 +- src/Nethermind/Nethermind.Core/Address.cs | 12 +- .../Authentication/JwtAuthentication.cs | 367 ++++-- src/Nethermind/Nethermind.Core/Block.cs | 7 + src/Nethermind/Nethermind.Core/Bloom.cs | 2 + .../Buffers/ArrayMemoryManager.cs | 22 + .../{ => Buffers}/DbSpanMemoryManager.cs | 25 +- .../Nethermind.Core/Caching/ISpanCache.cs | 36 - .../CappedArrayMemoryManager.cs | 45 - .../Collections/ArrayListCore.cs | 14 +- .../Collections/DictionaryExtensions.cs | 53 +- .../Collections/HashHelpers.cs | 132 -- .../Nethermind.Core/Collections/IHash64bit.cs | 23 + .../Collections/InsertionBehavior.cs | 9 - .../Collections/SeqlockCache.cs | 400 ++++++ .../Collections/SortedRealList.cs | 80 -- .../Nethermind.Core/Crypto/Hash256.cs | 4 + .../Nethermind.Core/Crypto/Signature.cs | 4 + .../Nethermind.Core/Extensions/Bytes.cs | 55 +- .../Extensions/DictionaryExtensions.cs | 30 - .../Extensions/IntExtensions.cs | 17 +- .../Extensions/SpanExtensions.cs | 431 +++++-- .../Nethermind.Core/IKeyValueStore.cs | 23 +- .../JsonConverters/ByteArrayConverter.cs | 109 +- .../KeyValueStoreExtensions.cs | 181 ++- .../PubSub/CompositePublisher.cs | 30 - src/Nethermind/Nethermind.Core/StorageCell.cs | 46 +- .../Utils/ConcurrentWriteBatcher.cs | 85 -- .../Nethermind.Db.Rocks/ColumnDb.cs | 116 +- .../Nethermind.Db.Rocks/ColumnsDb.cs | 21 +- .../Nethermind.Db.Rocks/Config/DbConfig.cs | 15 + .../Nethermind.Db.Rocks/Config/IDbConfig.cs | 15 + .../Nethermind.Db.Rocks/DbOnTheRocks.cs | 328 ++--- .../MergeOperatorAdapter.cs | 52 +- src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs | 96 +- .../CompressingStoreTests.cs | 11 +- .../LogIndex/LogIndexStorageCompactorTests.cs | 184 +++ .../LogIndexStorageIntegrationTests.cs | 1110 +++++++++++++++++ .../LogIndex/LogIndexStorageTestExtensions.cs | 44 + .../LogIndex/MergeOperatorTests.cs | 247 ++++ src/Nethermind/Nethermind.Db/CompressingDb.cs | 101 +- src/Nethermind/Nethermind.Db/DbNames.cs | 1 + .../FullPruning/FullPruningDb.cs | 14 + src/Nethermind/Nethermind.Db/IColumnsDb.cs | 1 + .../Nethermind.Db/IMergeOperator.cs | 4 + .../Nethermind.Db/InMemoryColumnBatch.cs | 9 + .../Nethermind.Db/LogIndex/AverageStats.cs | 31 + .../Nethermind.Db/LogIndex/BlockReceipts.cs | 8 + .../Nethermind.Db/LogIndex/CompactingStats.cs | 14 + .../Nethermind.Db/LogIndex/Compactor.cs | 203 +++ .../LogIndex/CompressionAlgorithm.cs | 58 + .../Nethermind.Db/LogIndex/Compressor.cs | 208 +++ .../LogIndex/DisabledLogIndexStorage.cs | 38 + .../Nethermind.Db/LogIndex/ExecTimeStats.cs | 44 + .../Nethermind.Db/LogIndex/ILogIndexConfig.cs | 106 ++ .../LogIndex/ILogIndexStorage.cs | 56 + .../LogIndex/LogIndexAggregate.cs | 33 + .../Nethermind.Db/LogIndex/LogIndexColumns.cs | 14 + .../Nethermind.Db/LogIndex/LogIndexConfig.cs | 33 + .../LogIndex/LogIndexEnumerator.cs | 153 +++ .../LogIndex/LogIndexStateException.cs | 15 + .../Nethermind.Db/LogIndex/LogIndexStorage.cs | 1006 +++++++++++++++ .../LogIndex/LogIndexUpdateStats.cs | 114 ++ .../Nethermind.Db/LogIndex/MergeOperator.cs | 174 +++ .../Nethermind.Db/LogIndex/MergeOps.cs | 94 ++ .../LogIndex/PostMergeProcessingStats.cs | 29 + src/Nethermind/Nethermind.Db/MemDb.cs | 42 +- .../Nethermind.Db/Nethermind.Db.csproj | 1 + src/Nethermind/Nethermind.Db/ReadOnlyDb.cs | 20 +- .../Nethermind.Db/RocksDbSettings.cs | 12 +- .../Nethermind.Db/SimpleFilePublicKeyDb.cs | 63 +- .../CodeInfoRepositoryTests.cs | 2 +- .../Tracing/GasEstimationTests.cs | 592 +++++++++ .../Nethermind.Evm/BadInstructionException.cs | 9 - .../BlockOverride.cs | 2 +- src/Nethermind/Nethermind.Evm/CallResult.cs | 4 +- .../Nethermind.Evm/CodeAnalysis/CodeInfo.cs | 90 +- .../CodeAnalysis/JumpDestinationAnalyzer.cs | 6 +- .../Nethermind.Evm/CodeInfoFactory.cs | 4 +- .../Nethermind.Evm/CodeInfoRepository.cs | 38 +- .../EvmObjectFormat/EofCodeInfo.cs | 13 +- .../Nethermind.Evm/ExecutionEnvironment.cs | 4 +- src/Nethermind/Nethermind.Evm/ICodeInfo.cs | 67 - .../Nethermind.Evm/ICodeInfoRepository.cs | 10 +- .../IOverridableCodeInfoRepository.cs | 2 +- .../Nethermind.Evm/IPrecompileProvider.cs | 2 +- .../Instructions/EvmInstructions.Call.cs | 2 +- .../Instructions/EvmInstructions.CodeCopy.cs | 2 +- .../Instructions/EvmInstructions.Create.cs | 2 +- .../Instructions/EvmInstructions.Eof.cs | 42 +- .../Nethermind.Evm/InvalidCodeException.cs | 10 - src/Nethermind/Nethermind.Evm/Metrics.cs | 17 + .../Precompiles/PrecompileInfo.cs | 17 - .../TransactionCollisionException.cs | 10 - .../TransactionProcessor.cs | 2 +- .../Nethermind.Evm/TransactionSubstate.cs | 4 +- .../LogIndexBuilderTests.cs | 273 ++++ .../Eth/FacadeJsonContext.cs | 19 + .../Nethermind.Facade/Filters/LogFilter.cs | 7 +- .../Filters/LogIndexFilterVisitor.cs | 187 +++ .../Filters/Topics/AnyTopic.cs | 2 + .../Filters/Topics/AnyTopicsFilter.cs | 5 + .../Filters/Topics/OrExpression.cs | 5 + .../Filters/Topics/SequenceTopicsFilter.cs | 5 + .../Filters/Topics/SpecificTopic.cs | 3 + .../Filters/Topics/TopicExpression.cs | 2 + .../Filters/Topics/TopicsFilter.cs | 4 + .../Find/ILogIndexBuilder.cs | 21 + .../Find/IndexedLogFinder.cs | 92 ++ .../Nethermind.Facade/Find/LogFinder.cs | 101 +- .../Nethermind.Facade/Find/LogIndexBuilder.cs | 495 ++++++++ .../Modules/BlockTreeModule.cs | 32 +- .../Modules/BuiltInStepsModule.cs | 1 + .../Nethermind.Init/Modules/DbModule.cs | 1 - .../Modules/MainProcessingContext.cs | 26 +- .../Modules/NethermindModule.cs | 3 +- .../Nethermind.Init/Modules/RpcModules.cs | 3 + .../PruningTrieStateFactory.cs | 5 + .../Steps/InitializeBlockchain.cs | 1 - .../Nethermind.Init/Steps/StartLogIndex.cs | 28 + .../EthModuleBenchmarks.cs | 2 + .../JsonRpcProcessorTests.cs | 60 + .../Modules/BoundedModulePoolTests.cs | 4 +- .../Modules/Eth/EthRpcModuleTests.cs | 4 +- .../Modules/SingletonModulePoolTests.cs | 4 +- .../Modules/TestRpcBlockchain.cs | 5 +- .../Data/EthRpcJsonContext.cs | 38 + .../Nethermind.JsonRpc/ErrorCodes.cs | 55 +- .../Nethermind.JsonRpc/IJsonRpcResult.cs | 10 - .../Nethermind.JsonRpc/IStreamableResult.cs | 17 + .../JsonRpcConfigExtension.cs | 46 +- .../Nethermind.JsonRpc/JsonRpcContext.cs | 25 +- .../Nethermind.JsonRpc/JsonRpcProcessor.cs | 74 +- .../JsonRpcResponseJsonContext.cs | 15 + .../Nethermind.JsonRpc/JsonRpcService.cs | 14 +- .../Modules/DebugModule/TransactionBundle.cs | 1 - .../Modules/Eth/EthModuleFactory.cs | 5 +- .../Modules/Eth/EthRpcModule.cs | 33 + .../Nethermind.JsonRpc/Modules/Eth/Filter.cs | 16 +- .../Modules/LogIndex/ILogIndexRpcModule.cs | 17 + .../Modules/LogIndex/LogIndexRpcModule.cs | 54 + .../Modules/LogIndex/LogIndexStatus.cs | 18 + .../Nethermind.JsonRpc/Modules/ModuleType.cs | 2 +- .../EngineModuleTests.Synchronization.cs | 3 +- .../EngineModuleTests.V1.cs | 27 +- .../EngineModuleTests.V3.cs | 31 + .../EngineModuleTests.V5.cs | 68 + .../Nethermind.Merge.Plugin.Test/JwtTest.cs | 135 +- .../MergePluginTests.cs | 22 + .../TestBlockProcessorInterceptor.Setup.cs | 3 + .../Data/BlobsV1DirectResponse.cs | 68 + .../Data/BlobsV2DirectResponse.cs | 89 ++ .../Data/EngineApiJsonContext.cs | 35 + .../Data/ExecutionPayload.cs | 9 +- .../Handlers/GetBlobsHandler.cs | 41 +- .../Handlers/GetBlobsHandlerV2.cs | 44 +- .../Nethermind.Merge.Plugin/MergePlugin.cs | 2 + .../MetricsTests.cs | 41 + .../Metrics/MetricsController.cs | 8 +- .../P2P/Protocol.cs | 4 - .../P2P/ProtocolParser.cs | 3 - .../IDiscoveryMsgSerializersProvider.cs | 9 - .../MessageQueueTests.cs | 168 +++ .../P2P/ProtocolParserTests.cs | 19 - .../PeerManagerTests.cs | 86 +- .../Nethermind.Network/P2P/MessageQueue.cs | 11 +- .../AddCapabilityMessageSerializer.cs | 2 +- .../SyncPeerProtocolHandlerBase.cs | 3 - .../ZeroProtocolHandlerBase.cs | 18 +- .../PeerEqualityComparer.cs | 22 - src/Nethermind/Nethermind.Network/Timeouts.cs | 5 - .../Rpc/OptimismEthRpcModuleTest.cs | 4 +- .../CL/L1Bridge/EthereumL1Bridge.cs | 8 - .../Rpc/OptimismEthModuleFactory.cs | 8 +- .../Rpc/OptimismEthRpcModule.cs | 3 + .../Ethereum/ContextWithMocks.cs | 2 +- .../Steps/EthereumStepsManagerTests.cs | 3 +- .../Ethereum/Api/ApiBuilder.cs | 4 +- .../Ethereum/EthereumRunner.cs | 15 - .../Ethereum/JsonRpcRunner.cs | 4 +- .../Modules/NethermindRunnerModule.cs | 3 +- .../Nethermind.Runner/JsonRpc/Bootstrap.cs | 4 +- .../Nethermind.Runner/JsonRpc/Startup.cs | 482 ++++--- src/Nethermind/Nethermind.Runner/Program.cs | 18 +- .../configs/base-mainnet.json | 4 +- .../configs/base-sepolia.json | 4 +- .../Nethermind.Runner/configs/chiado.json | 4 +- .../Nethermind.Runner/configs/gnosis.json | 4 +- .../configs/joc-mainnet.json | 6 +- .../configs/joc-testnet.json | 6 +- .../configs/linea-mainnet.json | 6 +- .../configs/linea-sepolia.json | 4 +- .../Nethermind.Runner/configs/mainnet.json | 4 +- .../Nethermind.Runner/configs/op-mainnet.json | 4 +- .../Nethermind.Runner/configs/op-sepolia.json | 4 +- .../Nethermind.Runner/configs/sepolia.json | 4 +- .../Nethermind.Runner/configs/spaceneth.json | 1 - .../configs/spaceneth_persistent.json | 1 - .../configs/worldchain-mainnet.json | 4 +- .../configs/worldchain-sepolia.json | 4 +- .../Nethermind.Runner/packages.lock.json | 240 ++-- .../CountingTextReader.cs | 83 -- .../CountingTextWriter.cs | 42 - .../EthereumJsonSerializer.cs | 206 ++- .../ForcedNumberConversion.cs | 28 +- .../Hash256Converter.cs | 30 +- .../HexWriter.cs | 428 +++++++ .../LongConverter.cs | 215 ++-- .../NullableULongConverter.cs | 52 +- .../PubSub/LogPublisher.cs | 31 - .../UInt256Converter.cs | 56 +- .../ULongConverter.cs | 161 ++- .../ValueHash256Converter.cs | 4 +- .../BlockDecoder.cs | 32 +- .../Nethermind.Serialization.Rlp/Rlp.cs | 3 +- .../Nethermind.Serialization.Rlp/TxDecoder.cs | 19 + .../Ssz.BasicTypes.cs | 2 + .../Ssz.Containers.cs | 18 - .../ShutterIntegrationTests.cs | 4 +- .../StorageProviderTests.cs | 23 +- .../TrieStoreScopeProviderTests.cs | 22 + .../OverridableCodeInfoRepository.cs | 12 +- .../PersistentStorageProvider.cs | 1 - .../Nethermind.State/PreBlockCaches.cs | 18 +- .../PrewarmerScopeProvider.cs | 74 +- .../Nethermind.State/Proofs/PatriciaTrieT.cs | 6 +- .../Nethermind.State/Proofs/ReceiptTrie.cs | 16 +- .../Nethermind.State/Proofs/TxTrie.cs | 33 +- .../Nethermind.State/Proofs/WithdrawalTrie.cs | 2 +- .../TrieStoreScopeProvider.cs | 17 +- .../FastSync/StateSyncFeedHealingTests.cs | 1 + .../SyncProgressResolverTests.cs | 22 +- .../SyncServerTests.cs | 2 +- .../ParallelSync/SyncProgressResolver.cs | 8 +- .../ClientTypeStrategy.cs | 44 - .../AllocationStrategies/NullStrategy.cs | 26 - .../AllocationStrategies/StaticStrategy.cs | 24 - .../StrategySelectionType.cs | 11 - .../SyncPeerEventArgs.cs | 20 - .../L1OriginStoreTests.cs | 18 + .../SurgeGasPriceOracleTests.cs | 281 +++-- .../Nethermind.Taiko/Config/ISurgeConfig.cs | 28 +- .../Nethermind.Taiko/Config/SurgeConfig.cs | 12 +- .../Nethermind.Taiko/L1OriginDecoder.cs | 13 +- .../Rpc/SurgeGasPriceOracle.cs | 227 ++-- .../Nethermind.Taiko/Tdx/TdxsClient.cs | 4 +- .../StateTestTxTracer.cs | 3 +- .../Nethermind.Trie.Test/CacheTests.cs | 1 + .../Nethermind.Trie.Test/HexPrefixTests.cs | 1 + .../Nethermind.Trie.Test/NibbleTests.cs | 1 + .../NodeStorageFactoryTests.cs | 1 + .../Nethermind.Trie.Test/NodeStorageTests.cs | 34 +- .../OverlayTrieStoreTests.cs | 1 + .../MaxBlockInCachePruneStrategyTests.cs | 3 +- .../MinBlockInCachePruneStrategyTests.cs | 3 +- .../Pruning/TreeStoreTests.cs | 34 +- .../PruningScenariosTests.cs | 3 +- .../Nethermind.Trie.Test/RawTrieStoreTests.cs | 1 + .../Nethermind.Trie.Test/TinyTreePathTests.cs | 1 + .../TrackingCappedArrayPoolTests.cs | 1 + .../Nethermind.Trie.Test/TreePathTests.cs | 1 + .../TrieNodeResolverWithReadFlagsTests.cs | 1 + .../Nethermind.Trie.Test/TrieTests.cs | 4 + .../Nethermind.Trie.Test/VisitingTests.cs | 1 + .../VisitorProgressTrackerTests.cs | 1 + .../Nethermind.Trie/NodeStorageCache.cs | 13 +- .../Nethermind.Trie/PatriciaTree.cs | 22 - .../Nethermind.Trie/PreCachedTrieStore.cs | 37 +- .../Nethermind.Trie/Pruning/TrieStore.cs | 5 - .../TrackingCappedArrayPool.cs | 64 +- .../Nethermind.Trie/TrieNode.Decoder.cs | 23 +- src/Nethermind/Nethermind.Trie/TrieNode.cs | 1 - .../Nethermind.Trie/VisitContext.cs | 1 - .../BlobTxStorageTests.cs | 100 ++ .../Collections/SortedPoolTests.cs | 2 + ...mpetingTransactionEqualityComparerTests.cs | 1 + .../DelegatedAccountFilterTest.cs | 17 +- .../NonceManagerTests.cs | 2 + .../ReceiptStorageTests.cs | 2 + .../Nethermind.TxPool.Test/RetryCacheTests.cs | 83 +- .../Nethermind.TxPool.Test/TestBlockTree.cs | 108 ++ .../TestChainHeadInfoProvider.cs | 35 + .../TransactionExtensionsTests.cs | 1 + .../TransactionPoolInfoProviderTests.cs | 2 + .../TxBroadcasterTests.cs | 40 +- .../TxPoolTests.Blobs.cs | 154 ++- .../Nethermind.TxPool.Test/TxPoolTests.cs | 137 +- .../Nethermind.TxPool/BlobTxStorage.cs | 40 + .../Collections/BlobTxDistinctSortedPool.cs | 42 +- .../PersistentBlobTxDistinctSortedPool.cs | 126 ++ src/Nethermind/Nethermind.TxPool/ITxPool.cs | 3 +- .../Nethermind.TxPool/ITxStorage.cs | 3 + .../Nethermind.TxPool/NullBlobTxStorage.cs | 2 + .../Nethermind.TxPool/NullTxPool.cs | 3 +- .../Nethermind.TxPool/RetryCache.cs | 6 +- .../Nethermind.TxPool/TxBroadcaster.cs | 9 +- .../TxNonceTxPoolReserveSealer.cs | 0 .../Nethermind.TxPool/TxPool.NonceInfo.cs | 0 src/Nethermind/Nethermind.TxPool/TxPool.cs | 7 +- .../Nethermind.Xdc.Test/BlockInfoTests.cs | 1 - .../Helpers/XdcTestBlockchain.cs | 6 +- .../ModuleTests/ProposedBlockTests.cs | 1 - .../ModuleTests/RewardTests.cs | 16 +- .../ModuleTests/SyncInfoDecoderTests.cs | 175 +++ .../ModuleTests/XdcReorgModuleTests.cs | 1 - .../Errors/QuorumCertificateException.cs | 12 - src/Nethermind/Nethermind.Xdc/IXdcSealer.cs | 11 - .../Nethermind.Xdc/RLP/SyncInfoDecoder.cs | 93 ++ .../Nethermind.Xdc/RLP/VoteDecoder.cs | 2 +- src/Nethermind/Nethermind.Xdc/XdcDbNames.cs | 12 - src/Nethermind/Nethermind.Xdc/XdcModule.cs | 5 +- .../XdcStateSyncAllocationStrategyFactory.cs | 34 + src/Nethermind/Nethermind.slnx | 1 + tools/JitAsm/DisassemblyParser.cs | 112 ++ tools/JitAsm/InstructionAnnotator.cs | 441 +++++++ tools/JitAsm/InstructionDb.cs | 156 +++ tools/JitAsm/InstructionDbBuilder.cs | 265 ++++ tools/JitAsm/JitAsm.csproj | 12 + tools/JitAsm/JitAsm.slnx | 3 + tools/JitAsm/JitRunner.cs | 269 ++++ tools/JitAsm/MethodResolver.cs | 393 ++++++ tools/JitAsm/Program.cs | 581 +++++++++ tools/JitAsm/README.md | 494 ++++++++ tools/JitAsm/StaticCtorDetector.cs | 175 +++ tools/Kute/Nethermind.Tools.Kute/Program.cs | 14 +- 500 files changed, 18434 insertions(+), 3976 deletions(-) create mode 100644 .github/workflows/ci-surge.yml create mode 100644 .github/workflows/pr-labeler.yml create mode 100644 src/Nethermind/Nethermind.Benchmark/Core/FastHashBenchmarks.cs create mode 100644 src/Nethermind/Nethermind.Benchmark/Core/SeqlockCacheBenchmarks.cs create mode 100644 src/Nethermind/Nethermind.Benchmark/Scheduler/BackgroundTaskSchedulerBenchmarks.cs create mode 100644 src/Nethermind/Nethermind.Blockchain.Test/Filters/LogIndexFilterVisitorTests.cs create mode 100644 src/Nethermind/Nethermind.Blockchain.Test/Filters/TestProcessingContext.cs delete mode 100644 src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessingScope.cs delete mode 100644 src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessorSource.cs create mode 100644 src/Nethermind/Nethermind.Consensus/Processing/IProcessingStats.cs delete mode 100644 src/Nethermind/Nethermind.Consensus/Processing/OverridableTxProcessingScope.cs create mode 100644 src/Nethermind/Nethermind.Core.Test/Collections/SeqlockCacheTests.cs create mode 100644 src/Nethermind/Nethermind.Core/Buffers/ArrayMemoryManager.cs rename src/Nethermind/Nethermind.Core/{ => Buffers}/DbSpanMemoryManager.cs (58%) delete mode 100644 src/Nethermind/Nethermind.Core/Caching/ISpanCache.cs delete mode 100644 src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs delete mode 100644 src/Nethermind/Nethermind.Core/Collections/HashHelpers.cs create mode 100644 src/Nethermind/Nethermind.Core/Collections/IHash64bit.cs delete mode 100644 src/Nethermind/Nethermind.Core/Collections/InsertionBehavior.cs create mode 100644 src/Nethermind/Nethermind.Core/Collections/SeqlockCache.cs delete mode 100644 src/Nethermind/Nethermind.Core/Collections/SortedRealList.cs delete mode 100644 src/Nethermind/Nethermind.Core/Extensions/DictionaryExtensions.cs delete mode 100644 src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs delete mode 100644 src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs create mode 100644 src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageCompactorTests.cs create mode 100644 src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageIntegrationTests.cs create mode 100644 src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageTestExtensions.cs create mode 100644 src/Nethermind/Nethermind.Db.Test/LogIndex/MergeOperatorTests.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/AverageStats.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/BlockReceipts.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/CompactingStats.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/Compactor.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/CompressionAlgorithm.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/Compressor.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/DisabledLogIndexStorage.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/ExecTimeStats.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/ILogIndexConfig.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/ILogIndexStorage.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexAggregate.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexColumns.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexConfig.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexEnumerator.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexStateException.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexStorage.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/LogIndexUpdateStats.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/MergeOperator.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/MergeOps.cs create mode 100644 src/Nethermind/Nethermind.Db/LogIndex/PostMergeProcessingStats.cs delete mode 100644 src/Nethermind/Nethermind.Evm/BadInstructionException.cs rename src/Nethermind/{Nethermind.Facade/Proxy/Models/Simulate => Nethermind.Evm}/BlockOverride.cs (96%) delete mode 100644 src/Nethermind/Nethermind.Evm/ICodeInfo.cs delete mode 100644 src/Nethermind/Nethermind.Evm/InvalidCodeException.cs delete mode 100644 src/Nethermind/Nethermind.Evm/Precompiles/PrecompileInfo.cs delete mode 100644 src/Nethermind/Nethermind.Evm/TransactionCollisionException.cs create mode 100644 src/Nethermind/Nethermind.Facade.Test/LogIndexBuilderTests.cs create mode 100644 src/Nethermind/Nethermind.Facade/Eth/FacadeJsonContext.cs create mode 100644 src/Nethermind/Nethermind.Facade/Filters/LogIndexFilterVisitor.cs create mode 100644 src/Nethermind/Nethermind.Facade/Find/ILogIndexBuilder.cs create mode 100644 src/Nethermind/Nethermind.Facade/Find/IndexedLogFinder.cs create mode 100644 src/Nethermind/Nethermind.Facade/Find/LogIndexBuilder.cs create mode 100644 src/Nethermind/Nethermind.Init/Steps/StartLogIndex.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc/Data/EthRpcJsonContext.cs delete mode 100644 src/Nethermind/Nethermind.JsonRpc/IJsonRpcResult.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc/IStreamableResult.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc/JsonRpcResponseJsonContext.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/ILogIndexRpcModule.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexRpcModule.cs create mode 100644 src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexStatus.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV1DirectResponse.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV2DirectResponse.cs create mode 100644 src/Nethermind/Nethermind.Merge.Plugin/Data/EngineApiJsonContext.cs delete mode 100644 src/Nethermind/Nethermind.Network.Discovery/Serializers/IDiscoveryMsgSerializersProvider.cs create mode 100644 src/Nethermind/Nethermind.Network.Test/MessageQueueTests.cs delete mode 100644 src/Nethermind/Nethermind.Network/PeerEqualityComparer.cs delete mode 100644 src/Nethermind/Nethermind.Serialization.Json/CountingTextReader.cs delete mode 100644 src/Nethermind/Nethermind.Serialization.Json/CountingTextWriter.cs create mode 100644 src/Nethermind/Nethermind.Serialization.Json/HexWriter.cs delete mode 100644 src/Nethermind/Nethermind.Serialization.Json/PubSub/LogPublisher.cs delete mode 100644 src/Nethermind/Nethermind.Serialization.Ssz/Ssz.Containers.cs delete mode 100644 src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/ClientTypeStrategy.cs delete mode 100644 src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/NullStrategy.cs delete mode 100644 src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StaticStrategy.cs delete mode 100644 src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StrategySelectionType.cs delete mode 100644 src/Nethermind/Nethermind.Synchronization/SyncPeerEventArgs.cs create mode 100644 src/Nethermind/Nethermind.TxPool.Test/TestBlockTree.cs create mode 100644 src/Nethermind/Nethermind.TxPool.Test/TestChainHeadInfoProvider.cs delete mode 100644 src/Nethermind/Nethermind.TxPool/TxNonceTxPoolReserveSealer.cs delete mode 100644 src/Nethermind/Nethermind.TxPool/TxPool.NonceInfo.cs create mode 100644 src/Nethermind/Nethermind.Xdc.Test/ModuleTests/SyncInfoDecoderTests.cs delete mode 100644 src/Nethermind/Nethermind.Xdc/Errors/QuorumCertificateException.cs delete mode 100644 src/Nethermind/Nethermind.Xdc/IXdcSealer.cs create mode 100644 src/Nethermind/Nethermind.Xdc/RLP/SyncInfoDecoder.cs delete mode 100644 src/Nethermind/Nethermind.Xdc/XdcDbNames.cs create mode 100644 src/Nethermind/Nethermind.Xdc/XdcStateSyncAllocationStrategyFactory.cs create mode 100644 tools/JitAsm/DisassemblyParser.cs create mode 100644 tools/JitAsm/InstructionAnnotator.cs create mode 100644 tools/JitAsm/InstructionDb.cs create mode 100644 tools/JitAsm/InstructionDbBuilder.cs create mode 100644 tools/JitAsm/JitAsm.csproj create mode 100644 tools/JitAsm/JitAsm.slnx create mode 100644 tools/JitAsm/JitRunner.cs create mode 100644 tools/JitAsm/MethodResolver.cs create mode 100644 tools/JitAsm/Program.cs create mode 100644 tools/JitAsm/README.md create mode 100644 tools/JitAsm/StaticCtorDetector.cs diff --git a/.github/workflows/build-tools.yml b/.github/workflows/build-tools.yml index 8038f514a80f..4369e74a07b4 100644 --- a/.github/workflows/build-tools.yml +++ b/.github/workflows/build-tools.yml @@ -21,6 +21,7 @@ jobs: - Evm/Evm.slnx - HiveCompare/HiveCompare.slnx - HiveConsensusWorkflowGenerator/HiveConsensusWorkflowGenerator.slnx + - JitAsm/JitAsm.slnx - Kute/Kute.slnx # - SchemaGenerator/SchemaGenerator.slnx - SendBlobs/SendBlobs.slnx diff --git a/.github/workflows/ci-surge.yml b/.github/workflows/ci-surge.yml new file mode 100644 index 000000000000..87ff8b8c4688 --- /dev/null +++ b/.github/workflows/ci-surge.yml @@ -0,0 +1,167 @@ +name: "Surge Integration Tests" + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths: + - "src/Nethermind/Nethermind.Taiko/**" + - ".github/workflows/ci-surge.yml" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + integration_tests: + if: >- + ${{ github.event.pull_request.draft == false + && !startsWith(github.head_ref, 'release-please') }} + name: Integration tests + runs-on: [ubuntu-latest] + timeout-minutes: 45 + env: + SURGE_TAIKO_MONO_DIR: surge-taiko-mono + PACAYA_FORK_TAIKO_MONO_DIR: pacaya-fork-taiko-mono + SHASTA_FORK_TAIKO_MONO_DIR: shasta-fork-taiko-mono + + steps: + - name: Checkout Nethermind + uses: actions/checkout@v6 + + - name: Checkout surge-taiko-mono + uses: actions/checkout@v6 + with: + repository: NethermindEth/surge-taiko-mono + path: ${{ env.SURGE_TAIKO_MONO_DIR }} + ref: surge-shasta + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: ${{ env.SURGE_TAIKO_MONO_DIR }}/go.mod + cache: true + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + run_install: false + + - name: Install Node.js + uses: actions/setup-node@v5 + with: + node-version: 20 + cache: pnpm + cache-dependency-path: ${{ env.SURGE_TAIKO_MONO_DIR }}/pnpm-lock.yaml + + - name: Install dependencies + working-directory: ${{ env.SURGE_TAIKO_MONO_DIR }} + shell: bash + run: pnpm install + + - name: Checkout Pacaya fork + uses: actions/checkout@v6 + with: + repository: NethermindEth/surge-taiko-mono + path: >- + ${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, + env.PACAYA_FORK_TAIKO_MONO_DIR) }} + ref: jmadibekov/pacaya-dummy-verifiers-fix + + - name: Checkout Shasta fork + uses: actions/checkout@v6 + with: + repository: NethermindEth/surge-taiko-mono + path: >- + ${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, + env.SHASTA_FORK_TAIKO_MONO_DIR) }} + ref: surge-alethia-protocol-v3.0.0 + + - name: Install pnpm dependencies for pacaya fork taiko-mono + working-directory: >- + ${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, + env.PACAYA_FORK_TAIKO_MONO_DIR) }} + run: cd ./packages/protocol && pnpm install + + - name: Install pnpm dependencies for shasta fork taiko-mono + working-directory: >- + ${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, + env.SHASTA_FORK_TAIKO_MONO_DIR) }} + run: cd ./packages/protocol && pnpm install + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker Build Nethermind Client + run: | + image_name="nethermindeth/nethermind" + image_tag="${GITHUB_SHA:0:8}" + full_image="${image_name}:${image_tag}" + + echo "Building Docker image: ${full_image}" + + docker buildx build . \ + --platform linux/amd64 \ + -f Dockerfile \ + -t "${full_image}" \ + --load \ + --build-arg BUILD_CONFIG=release \ + --build-arg CI=true \ + --build-arg COMMIT_HASH=${{ github.sha }} \ + --build-arg SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) + + echo "IMAGE_TAG=${full_image}" >> $GITHUB_ENV + + echo "Verifying image exists locally:" + docker images | grep "${image_name}" | grep "${image_tag}" || (echo "Error: Image not found locally" && exit 1) + + - name: Install yq + run: | + sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + sudo chmod +x /usr/local/bin/yq + yq --version + + - name: Update taiko-client docker-compose.yml with new image + working-directory: >- + ${{ env.SURGE_TAIKO_MONO_DIR }}/packages/taiko-client/internal/docker/nodes + run: | + docker_compose_file="docker-compose.yml" + if [ -f "$docker_compose_file" ]; then + echo "Current image in docker-compose.yml:" + yq eval '.services.l2_nmc.image' "$docker_compose_file" + + echo "Updating docker-compose.yml with image: ${IMAGE_TAG}" + yq eval '.services.l2_nmc.image = "'"${IMAGE_TAG}"'"' -i "$docker_compose_file" + + yq eval '.services.l2_nmc.pull_policy = "never"' -i "$docker_compose_file" + + echo "Updated image in docker-compose.yml:" + yq eval '.services.l2_nmc.image' "$docker_compose_file" + + echo "Pull policy set to:" + yq eval '.services.l2_nmc.pull_policy' "$docker_compose_file" + else + echo "Warning: docker-compose.yml not found at expected path" + exit 1 + fi + + - name: Run integration tests + working-directory: >- + ${{ env.SURGE_TAIKO_MONO_DIR }}/packages/taiko-client + env: + L2_NODE: l2_nmc + run: >- + SHASTA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} + PACAYA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.SURGE_TAIKO_MONO_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} + make test + + - name: Codecov.io + uses: codecov/codecov-action@v5 + with: + files: ${{ env.SURGE_TAIKO_MONO_DIR }}/packages/taiko-client/coverage.out + flags: taiko-client + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/ci-taiko.yml b/.github/workflows/ci-taiko.yml index d01e20692415..2d92a38090aa 100644 --- a/.github/workflows/ci-taiko.yml +++ b/.github/workflows/ci-taiko.yml @@ -1,4 +1,4 @@ -name: "Nethermind/Ethereum Taiko Client CI Tests" +name: "Taiko Integration Tests" on: pull_request: @@ -18,25 +18,22 @@ jobs: && !startsWith(github.head_ref, 'release-please') }} name: Integration tests runs-on: [ubuntu-latest] - timeout-minutes: 30 + timeout-minutes: 45 env: - OLD_FORK_TAIKO_MONO_DIR: old-fork-taiko-mono - TAIKO_MONO_MAIN_DIR: taiko-mono-main + TAIKO_MONO_DIR: taiko-mono PACAYA_FORK_TAIKO_MONO_DIR: pacaya-fork-taiko-mono SHASTA_FORK_TAIKO_MONO_DIR: shasta-fork-taiko-mono - strategy: - matrix: - execution_node: [l2_nmc] - steps: - - uses: actions/checkout@v6 + - name: Checkout Nethermind + uses: actions/checkout@v6 - - uses: actions/checkout@v6 + - name: Checkout taiko-mono + uses: actions/checkout@v6 with: - repository: NethermindEth/surge-taiko-mono - path: ${{ env.TAIKO_MONO_MAIN_DIR }} - ref: surge-shasta + repository: taikoxyz/taiko-mono + path: ${{ env.TAIKO_MONO_DIR }} + ref: main - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 @@ -44,14 +41,9 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version-file: ${{ env.TAIKO_MONO_MAIN_DIR }}/go.mod + go-version-file: ${{ env.TAIKO_MONO_DIR }}/go.mod cache: true - - name: Set up Git to use HTTPS - shell: bash - run: | - git config --global url."https://github.com/".insteadOf "git@github.com:" - - name: Install pnpm uses: pnpm/action-setup@v4 with: @@ -63,38 +55,40 @@ jobs: with: node-version: 20 cache: pnpm - cache-dependency-path: ${{ env.TAIKO_MONO_MAIN_DIR }}/pnpm-lock.yaml + cache-dependency-path: ${{ env.TAIKO_MONO_DIR }}/pnpm-lock.yaml - name: Install dependencies - working-directory: ${{ env.TAIKO_MONO_MAIN_DIR }} + working-directory: ${{ env.TAIKO_MONO_DIR }} shell: bash run: pnpm install - - uses: actions/checkout@v6 + - name: Checkout Pacaya fork + uses: actions/checkout@v6 with: repository: taikoxyz/taiko-mono path: >- - ${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, + ${{ format('{0}/{1}', env.TAIKO_MONO_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} ref: taiko-alethia-protocol-v2.3.0-devnet-shasta-test - - uses: actions/checkout@v6 + - name: Checkout Shasta fork + uses: actions/checkout@v6 with: repository: taikoxyz/taiko-mono path: >- - ${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, + ${{ format('{0}/{1}', env.TAIKO_MONO_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} ref: taiko-alethia-protocol-v3.0.0 - name: Install pnpm dependencies for pacaya fork taiko-mono working-directory: >- - ${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, + ${{ format('{0}/{1}', env.TAIKO_MONO_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} run: cd ./packages/protocol && pnpm install - name: Install pnpm dependencies for shasta fork taiko-mono working-directory: >- - ${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, + ${{ format('{0}/{1}', env.TAIKO_MONO_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} run: cd ./packages/protocol && pnpm install @@ -132,7 +126,7 @@ jobs: - name: Update taiko-client docker-compose.yml with new image working-directory: >- - ${{ env.TAIKO_MONO_MAIN_DIR }}/packages/taiko-client/internal/docker/nodes + ${{ env.TAIKO_MONO_DIR }}/packages/taiko-client/internal/docker/nodes run: | docker_compose_file="docker-compose.yml" if [ -f "$docker_compose_file" ]; then @@ -154,20 +148,20 @@ jobs: exit 1 fi - - name: Run Tests on ${{ matrix.execution_node }} execution engine + - name: Run integration tests working-directory: >- - ${{ env.TAIKO_MONO_MAIN_DIR }}/packages/taiko-client + ${{ env.TAIKO_MONO_DIR }}/packages/taiko-client env: - L2_NODE: ${{ matrix.execution_node }} + L2_NODE: l2_nmc run: >- - SHASTA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} - PACAYA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} + SHASTA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.TAIKO_MONO_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} + PACAYA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.TAIKO_MONO_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} make test - name: Codecov.io uses: codecov/codecov-action@v5 with: - files: ${{ env.TAIKO_MONO_MAIN_DIR }}/packages/taiko-client/coverage.out + files: ${{ env.TAIKO_MONO_DIR }}/packages/taiko-client/coverage.out flags: taiko-client env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml new file mode 100644 index 000000000000..9ded94bebe46 --- /dev/null +++ b/.github/workflows/pr-labeler.yml @@ -0,0 +1,201 @@ +name: PR labeler + +on: + pull_request_target: + types: [opened, edited, ready_for_review, synchronize] + +jobs: + label: + name: Label PR by type + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Apply labels from PR template checkboxes + uses: actions/github-script@v7 + with: + script: | + // --- Checkbox rules --- + const checkboxToLabel = { + 'Bugfix (a non-breaking change that fixes an issue)': 'bug fix + reliability', + 'New feature (a non-breaking change that adds functionality)': 'new feature', + 'Breaking change (a change that causes existing functionality not to work as expected)': 'BREAKING', + 'Optimization': 'performance is good', + 'Refactoring': 'refactoring', + 'Documentation update': 'docs', + 'Build-related changes': 'build changes', + }; + + // --- Title prefix rules (conventional commits) --- + const prefixToLabel = { + 'fix': 'bug fix + reliability', + 'feat': 'new feature', + 'perf': 'performance is good', + 'refactor': 'refactoring', + 'chore': 'minor', + 'docs': 'docs', + 'test': 'test', + 'ci': 'build changes', + 'build': 'build changes', + }; + + // --- Path rules --- + const pathToLabel = [ + { pattern: 'src/Nethermind/Nethermind.Optimism', label: 'optimism' }, + { pattern: 'src/Nethermind/Nethermind.Taiko', label: 'taiko' }, + { pattern: 'src/Nethermind/Nethermind.Xdc', label: 'XDC' }, + { pattern: 'src/Nethermind/Nethermind.Network', label: 'network' }, + { pattern: 'src/Nethermind/Nethermind.Evm', label: 'evm' }, + { pattern: 'src/Nethermind/Nethermind.Trie', label: 'trie' }, + { pattern: 'src/Nethermind/Nethermind.State', label: 'state+storage' }, + { pattern: 'src/Nethermind/Nethermind.Synchronization', label: 'sync' }, + { pattern: 'src/Nethermind/Nethermind.JsonRpc', label: 'rpc' }, + { pattern: 'src/Nethermind/Nethermind.Db.Rocks', label: 'rocksdb' }, + { pattern: 'src/Nethermind/Nethermind.Db', label: 'database' }, + { pattern: 'src/Nethermind/Nethermind.Init/Modules/DbModule.cs', label: 'database' }, + { pattern: 'src/Nethermind/Nethermind.Runner/configs', label: 'configuration' }, + { pattern: 'src/Nethermind/Nethermind.Config', label: 'configuration' }, + { pattern: 'README.md', label: 'docs' }, + { pattern: 'AGENTS.md', label: 'agentic 🤖' }, + { pattern: '.github/', label: 'devops' }, + { pattern: 'Directory.Packages.props', label: 'dependencies' }, + { pattern: 'tools/', label: 'tools' }, + ]; + + // test label: applied when ALL changed files are in a .Test project + const testOnlyLabel = 'test'; + + const checkboxLabels = new Set(Object.values(checkboxToLabel)); + const prefixLabels = new Set(Object.values(prefixToLabel)); + const pathLabels = new Set(pathToLabel.map(r => r.label)); + const managedLabels = new Set([...checkboxLabels, ...prefixLabels, ...pathLabels, testOnlyLabel, 'cleanup', 'snap sync']); + + const body = context.payload.pull_request.body || ''; + const title = context.payload.pull_request.title || ''; + + const desiredLabels = new Set(); + + // Evaluate checkboxes + for (const [text, label] of Object.entries(checkboxToLabel)) { + const escaped = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + if (new RegExp(`-\\s*\\[\\s*[xX]\\s*\\]\\s*${escaped}`).test(body)) { + desiredLabels.add(label); + } + } + + // Evaluate "Other" checkbox with keyword matching + const otherMatch = body.match(/-\s*\[\s*[xX]\s*\]\s*Other:\s*(.+)/); + if (otherMatch) { + const otherText = otherMatch[1].toLowerCase(); + if (/\btest/.test(otherText)) desiredLabels.add('test'); + if (/\btool/.test(otherText)) desiredLabels.add('tools'); + if (/\bagent/.test(otherText)) desiredLabels.add('agentic 🤖'); + if (/\bdoc/.test(otherText)) desiredLabels.add('docs'); + } + + // Evaluate title prefix: supports "fix:", "fix(scope):", "(fix)", "[fix]" + const prefixMatch = title.match(/^[(\[]?(\w+)[)\]]?[\s(:/]/) + if (prefixMatch) { + const prefix = prefixMatch[1].toLowerCase(); + if (prefixToLabel[prefix]) { + desiredLabels.add(prefixToLabel[prefix]); + } + } + + // Evaluate title for EIP mentions (eip-1234, EIP 1234, eip1234, etc.) + if (/eip[-\s]?\d+/i.test(title)) { + desiredLabels.add('eip'); + } + + // Evaluate title for optimization keyword + if (/\boptimiz/i.test(title)) { + desiredLabels.add('performance is good'); + } + + // Evaluate changed file paths + const files = await github.paginate(github.rest.pulls.listFiles, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + per_page: 100, + }); + + for (const { pattern, label } of pathToLabel) { + if (files.some(f => f.filename.startsWith(pattern))) { + desiredLabels.add(label); + } + } + + // SnapSync in path + if (files.some(f => /SnapSync/i.test(f.filename))) { + desiredLabels.add('snap sync'); + } + + // Dockerfile changes + if (files.some(f => /(?:^|\/)[Dd]ockerfile/.test(f.filename))) { + desiredLabels.add('devops'); + } + + // Chain config files with integration keywords + const chainKeywordToLabel = { + 'taiko': 'taiko', + 'optimism': 'optimism', + 'op-': 'optimism', + 'xdc': 'XDC', + }; + for (const f of files) { + if (/^src\/Nethermind\/Chains\//.test(f.filename)) { + const name = f.filename.toLowerCase(); + for (const [keyword, label] of Object.entries(chainKeywordToLabel)) { + if (name.includes(keyword)) { + desiredLabels.add(label); + } + } + } + } + + // Apply test label when all changed files are in Test projects + if (files.length > 0 && files.every(f => /\.Test[s]?\//.test(f.filename))) { + desiredLabels.add(testOnlyLabel); + } + + // Apply cleanup label when PR only removes code + if (files.length > 0 && files.every(f => f.additions === 0 && f.deletions > 0)) { + desiredLabels.add('cleanup'); + } + + // Diff against current labels + const currentLabels = new Set( + context.payload.pull_request.labels.map(l => l.name) + ); + + const toAdd = [...desiredLabels].filter(l => !currentLabels.has(l)); + const toRemove = [...currentLabels].filter(l => managedLabels.has(l) && !desiredLabels.has(l)); + + if (toAdd.length > 0) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: toAdd, + }); + core.info(`Added labels: ${toAdd.join(', ')}`); + } + + for (const label of toRemove) { + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + name: label, + }); + core.info(`Removed label: ${label}`); + } catch (e) { + if (e.status !== 404) throw e; + } + } + + if (toAdd.length === 0 && toRemove.length === 0) { + core.info('No label changes needed'); + } diff --git a/.gitignore b/.gitignore index 6f14b7b2d897..5f3e510becd3 100644 --- a/.gitignore +++ b/.gitignore @@ -358,6 +358,10 @@ paket-files/ #tools/** #!tools/packages.config +# JitAsm uops.info database (110MB, download separately) +tools/JitAsm/instructions.xml +tools/JitAsm/instructions.db + # Tabs Studio *.tss diff --git a/AGENTS.md b/AGENTS.md index 350f984894d6..e2a0c8fc805c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,11 +23,18 @@ This guide helps to get started with the Nethermind Ethereum execution client re - Use the `ArgumentNullException.ThrowIfNull` method for null checks and other similar methods - Use the `ObjectDisposedException.ThrowIf` method for disposal checks - Use documentation comments for all public APIs with proper structure +- Avoid `var` when declaring variables, the only acceptable exceptions are very long type names e.g. nested generic types - Consider performance implications in high-throughput paths - Trust null annotations, do not add redundant null checks +- When fixing a bug, always add a regression test that fails without the fix and passes with it - Add tests to existing test files rather than creating new ones +- When adding multiple, similar tests write one test with test cases +- When adding a test, check if previous tests can be reused with new test case - Code comments must explain _why_, not _what_ -- Do not suggest using LINQ when a simple loop would suffice +- **NEVER suggest using LINQ (`.Select()`, `.Where()`, `.Any()`, etc.) when a simple `foreach` or `for` loop would work.** LINQ has overhead and is less readable for simple iterations. Use LINQ only for complex queries where the declarative syntax significantly improves clarity. +- Keep changes minimal and focused: do not rename variables, reformat surrounding code, or refactor unrelated logic as part of a fix. Touch only what is necessary to solve the problem. +- Follow DRY: after making changes, review the result for duplicated logic. Extract repeated blocks (roughly 5+ lines) into shared methods, but do not over-extract trivial one-liners into their own methods. +- In generic types, move methods that do not depend on the type parameter to a non-generic base class or static helper to avoid redundant JIT instantiations per closed type. - Do not use the `#region` and `#endregion` pragmas - Do not alter anything in the [src/bench_precompiles](./src/bench_precompiles/) and [src/tests](./src/tests/) directories @@ -106,7 +113,7 @@ Before creating a pull request: ```bash dotnet format whitespace src/Nethermind/ --folder ``` -- Use [pull_request_template.md](.github/pull_request_template.md) +- Follow the [pull_request_template.md](.github/pull_request_template.md) format: fill in the changes section, tick the appropriate type-of-change checkboxes, and complete the testing/documentation sections. The checkboxes drive automatic PR labeling. ## Prerequisites diff --git a/Directory.Packages.props b/Directory.Packages.props index 2112ae3eadde..0c968919dcc0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,7 +11,7 @@ - + @@ -19,15 +19,15 @@ - - + + - + - - - + + + @@ -36,18 +36,18 @@ - - + + - - - - + + + + - - + + @@ -57,8 +57,9 @@ - + + @@ -67,25 +68,25 @@ - + - + - + - + - - - + + + - + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 8ecddbd54135..a2eef01c8319 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.102-noble@sha256:25d14b400b75fa4e89d5bd4487a92a604a4e409ab65becb91821e7dc4ac7f81f AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.103-noble@sha256:0a506ab0c8aa077361af42f82569d364ab1b8741e967955d883e3f23683d473a AS build ARG BUILD_CONFIG=release ARG CI=true @@ -25,7 +25,7 @@ RUN arch=$([ "$TARGETARCH" = "amd64" ] && echo "x64" || echo "$TARGETARCH") && \ # A temporary symlink to support the old executable name RUN ln -sr /publish/nethermind /publish/Nethermind.Runner -FROM mcr.microsoft.com/dotnet/aspnet:10.0.2-noble@sha256:1aacc8154bc3071349907dae26849df301188be1a2e1f4560b903fb6275e481a +FROM mcr.microsoft.com/dotnet/aspnet:10.0.3-noble@sha256:52dcfb4225fda614c38ba5997a4ec72cbd5260a624125174416e547ff9eb9b8c WORKDIR /nethermind diff --git a/Dockerfile.chiseled b/Dockerfile.chiseled index bf96919ae532..60b5b36ff752 100644 --- a/Dockerfile.chiseled +++ b/Dockerfile.chiseled @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.102-noble@sha256:25d14b400b75fa4e89d5bd4487a92a604a4e409ab65becb91821e7dc4ac7f81f AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.103-noble@sha256:0a506ab0c8aa077361af42f82569d364ab1b8741e967955d883e3f23683d473a AS build ARG BUILD_CONFIG=release ARG CI=true @@ -28,7 +28,7 @@ RUN cd /publish && \ mkdir logs && \ mkdir nethermind_db -FROM mcr.microsoft.com/dotnet/aspnet:10.0.2-noble-chiseled@sha256:cc6a8adc9402e9c2c84423ee1a4c58a3098511ed5399804df0659eeafb0ae0cb +FROM mcr.microsoft.com/dotnet/aspnet:10.0.3-noble-chiseled@sha256:3b0bd0fa83c55a73d85007ac6896b9e5ac61255d651be135b7d70622af56af78 WORKDIR /nethermind diff --git a/README.md b/README.md index e7443d757229..7888790426b4 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ [![GitHub Discussions](https://img.shields.io/github/discussions/nethermindeth/nethermind?style=social)](https://github.com/nethermindeth/nethermind/discussions) [![GitPOAPs](https://public-api.gitpoap.io/v1/repo/NethermindEth/nethermind/badge)](https://www.gitpoap.io/gh/NethermindEth/nethermind) -The Nethermind Ethereum execution client, built on .NET, delivers industry-leading performance in syncing and tip-of-chain processing. With its modular design and plugin system, it offers extensibility and features for new chains. As one of the most adopted execution clients on Ethereum, Nethermind plays a crucial role in enhancing the diversity and resilience of the Ethereum ecosystem. +Nethermind is an Ethereum execution client built on .NET. Nethermind is the fastest client in terms of throughput, block processing, transactions per second (TPS), and syncing new nodes. With its modular design and plugin system, it offers extensibility and features for new chains. As one of the most adopted execution clients on Ethereum, Nethermind plays a crucial role in enhancing the diversity and resilience of the Ethereum ecosystem. ## Documentation diff --git a/cspell.json b/cspell.json index 313bcb6496c2..513f9ca1bc5a 100644 --- a/cspell.json +++ b/cspell.json @@ -35,11 +35,13 @@ "adab", "addmod", "affinitize", + "agen", "akhunov", "alethia", "alexey", "analysed", "apikey", + "archs", "argjson", "asmv", "aspnet", @@ -48,6 +50,7 @@ "autofac", "autogen", "auxdata", + "backpressure", "badreq", "barebone", "baseblock", @@ -105,6 +108,7 @@ "buildtransitive", "bulkset", "bursty", + "bword", "buterin", "bylica", "bytecodes", @@ -115,17 +119,41 @@ "callf", "callme", "callvalue", + "callvirt", "cand", "canonicality", "castagnoli", + "cctor", + "cctors", "chainid", "chainspec", "chiado", "cipherparams", "ciphertext", "ckzg", + "classinit", "cloneable", "cmix", + "cmov", + "cmova", + "cmovae", + "cmovb", + "cmovbe", + "cmove", + "cmovg", + "cmovge", + "cmovl", + "cmovle", + "cmovna", + "cmovnb", + "cmovnbe", + "cmovne", + "cmovng", + "cmovnge", + "cmovnl", + "cmovnle", + "cmovnz", + "cmovz", "codecopy", "codehash", "codesection", @@ -133,7 +161,9 @@ "coef", "collectd", "colour", + "CORINFO", "commitset", + "compactable", "comparand", "concurrenc", "configurer", @@ -142,6 +172,7 @@ "containersection", "contentfiles", "corechart", + "corinfo", "cpufrequency", "crummey", "cryptosuite", @@ -151,6 +182,7 @@ "dataloadn", "datasection", "datasize", + "dasm", "dbdir", "dbsize", "deadlined", @@ -158,6 +190,7 @@ "debhelper", "decommit", "decompiled", + "dedup", "decompiler", "deconfigure", "deconfigured's", @@ -169,13 +202,17 @@ "deque", "deserialised", "dests", + "devirtualization", "devirtualize", + "devirtualized", "devnet", "devnets", "devp2p", "diagnoser", "diagnosers", + "diffable", "disappearer's", + "disasm", "discontiguous", "discport", "discv", @@ -190,11 +227,13 @@ "dpapi", "dpkg", "dupn", + "dynamicclass", "ecies", "ecrec", "edgecase", "efbbbf", "eips", + "eliminable", "emojize", "emptish", "emptystep", @@ -228,6 +267,7 @@ "ethxx", "evmdis", "ewasm", + "evex", "extcall", "extcode", "extcodecopy", @@ -254,17 +294,21 @@ "forkhash", "forkid", "fusaka", + "fullopts", "gasrefund", "gbps", + "gcstatic", "gcdump", "geoff", "getblobs", "getnull", "getpayloadv", "getrlimit", + "getshared", "gettrie", "gopherium", "gwat", + "gword", "halfpath", "hardfork", "hardforks", @@ -287,11 +331,13 @@ "highbits", "hiveon", "hmac", + "hmacsha", "holesky", "hoodi", "hostnames", "hotstuff", "hyperthreading", + "idiv", "idxs", "iface", "ikvp", @@ -307,6 +353,7 @@ "instart", "internaltype", "interp", + "interruptible", "invalidblockhash", "isnull", "iszero", @@ -314,9 +361,13 @@ "ivle", "jemalloc", "jimbojones", + "jitasm", "jitted", "jitting", "jmps", + "jnbe", + "jnge", + "jnle", "jsonrpcconfig", "jumpdest", "jumpdestpush", @@ -358,6 +409,7 @@ "longdate", "lookback", "lukasz", + "lzcnt", "machdep", "machinename", "madv", @@ -370,6 +422,7 @@ "masternode", "masternodes", "masterodes", + "materialisation", "maxcandidatepeercount", "maxcandidatepercount", "maxfee", @@ -396,13 +449,18 @@ "merkleize", "merkleizer", "mgas", + "microarchitecture", + "microbenchmark", + "microbenchmarks", "microsecs", "midnib", "millis", "mingas", "minlevel", + "minopt", "mintable", "misbehaviour", + "mispredictions", "mklink", "mload", "mmap", @@ -410,6 +468,7 @@ "modexpprecompile", "morden", "movbe", + "movsxd", "movzx", "mres", "mscorlib", @@ -450,6 +509,7 @@ "networkconfigmaxcandidatepeercount", "networkid", "newtonsoft", + "nint", "nito", "nlog", "nodedata", @@ -458,6 +518,7 @@ "nodetype", "nofile", "nonposdao", + "nongcstatic", "nonstring", "nops", "nostack", @@ -466,6 +527,7 @@ "npushes", "nsubstitute", "nugettest", + "nuint", "numfiles", "oand", "offchain", @@ -497,6 +559,7 @@ "pinnable", "pinnableslice", "pkcs", + "plinq", "pmsg", "poacore", "poaps", @@ -523,6 +586,7 @@ "prioritise", "protoc", "prysm", + "pshufb", "ptree", "pushgateway", "pwas", @@ -551,9 +615,12 @@ "redownloading", "reencoding", "refint", + "regs", + "relbr", "refstruct", "regenesis", "reitwiessner", + "reorgable", "reorganisation", "reorganisations", "reorganised", @@ -586,6 +653,7 @@ "samplenewpayload", "sankey", "sbrk", + "sbyte", "scopable", "sdiv", "secp", @@ -596,7 +664,23 @@ "seqlock", "serialised", "setcode", + "setb", + "setbe", "sete", + "setg", + "setge", + "setl", + "setle", + "setna", + "setnb", + "setnbe", + "setne", + "setnge", + "setnl", + "setnle", + "setng", + "setnz", + "setz", "shamir", "shlibs", "shouldly", @@ -607,6 +691,7 @@ "signextend", "sizeinbase", "skiplastn", + "skylake", "slnx", "sload", "smod", @@ -616,6 +701,7 @@ "sparkline", "spinlocks", "squarify", + "srcset", "ssse", "sstfiles", "sstore", @@ -631,6 +717,7 @@ "stfld", "stoppables", "storagefuzz", + "streamable", "stree", "strs", "stylesheet", @@ -685,6 +772,7 @@ "tstore", "tukey", "tupleception", + "twap", "txcreate", "txns", "txpointer", @@ -715,24 +803,32 @@ "unreferred", "unrequested", "unresolve", + "unshifted", "unsub", "unsubscription", "unsynchronized", "unvote", + "uops", "upnp", "upto", "upvoting", "vbmi", "vitalik", + "vmovdqu", "vmovups", "vmtrace", "vote₁", "vote₂", "vote₃", "voteₙ", + "vpaddd", "vpcbr", + "vpermb", + "vpermi", "vpor", + "vpshufb", "vptest", + "vpxor", "vzeroupper", "wamp", "warmcoinbase", @@ -751,14 +847,17 @@ "wycheproof", "xdai", "xdcx", + "xmmword", "xmlstarlet", "xnpool", + "xvcj", "yellowpaper", "ymmword", "yparity", "zcompressor", "zdecompressor", "zhizhu", + "zmmword", "zstandard", "zstd", "zwcm" diff --git a/scripts/build/Dockerfile b/scripts/build/Dockerfile index bb2f165f6116..57fad31cfd91 100644 --- a/scripts/build/Dockerfile +++ b/scripts/build/Dockerfile @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM mcr.microsoft.com/dotnet/sdk:10.0.102-noble@sha256:25d14b400b75fa4e89d5bd4487a92a604a4e409ab65becb91821e7dc4ac7f81f +FROM mcr.microsoft.com/dotnet/sdk:10.0.103-noble@sha256:0a506ab0c8aa077361af42f82569d364ab1b8741e967955d883e3f23683d473a ARG COMMIT_HASH ARG SOURCE_DATE_EPOCH diff --git a/src/Nethermind/Chains/arena-z-mainnet.json.zst b/src/Nethermind/Chains/arena-z-mainnet.json.zst index fef9920bc9192b35e375e370fb7cac36df95bb39..10503dbf9dbf43f30b371e38139fa85e5d97b8c3 100644 GIT binary patch delta 19943 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 delta 18203 zcmXWiQ*fY7*D&hXwr$(CZQHi**vZ7UolG#9*tRCNCeFmp|9t!1*FpD5*E;B`uC;nq zcV;8xcrheE)5iP>t}{~I1ZmI?%6GmlLQdiwcL$y|ZZX;cWqb8~Y(M&J$`L>;q86T( zLbi<|0wXdC83C~gz6d_#`&v8i|C^?q;mk2DrMdZzV;P{`j-u|H*ia>#+Rz|Eq9h&L zNwnPfk$%)KIJ%UnN~h&11%J?TZHjF+m!tJWuo(bMIv6DyCc3I6R>W5vXW61TI%%C} zZK)`KPfZJK>c{jnXbMpOvfIR3p zxb?jzN539^kFlPRHx88tsA!M?0F!Cp10}qQ$RCd_w6oa%Wf@{g5}C1Lk%Psn6O`7K zsWS}gFan3sPF9)`E!H;n9Ao3>ff+g7bi)gnaoS&1QG@Z@6O@v~I=#!1Xe{fR7SmX( zAE?jqoYFc;P%5{Hb4>nqP^byDD&X`k@-y=YKkBM_k(9`|s+&l}e|u^W16Y}=k(6zT za2bUT3=r~VETuMU2VB6dK}*y%V#GMNi7A9>x7_Zujkv;9Cwl}s-&1c?Nao=D(d-^) z(j8RFjz|HB_o_AP=sc+sn7i}vLN&*f*X&<(Hir1DxuJj?Ezyu#R@yU z9sHT!^MzTWS5uSaMZpd_fMrcTmPu$ah!Py05Euvus6bJFmI-AMiW>~E02neu6DUY9 z$S@>`BX_LyJwPR)e|58K=I!~>?C*xu;p_klBE6ta2SRp5Cj1i z6as;&ezAEG|4VM)LWl)DpyCW8=H?)AV1n{E z17WUWhm>RwNbyC$+ES~&jLyc+rfn9j`Dr)BAS2*s!j@Ffi(_Ed@Mq#vP1NMJ--KkV zXci-$1j?t3=QoI@XjuOq4go(2R`@&}QdNQ`i9Mu=2>=4sWkT2l0wWO21%`-&kklO# zXESd?ff|-a46}+%9V<>$HP9cDwh%l3=7^dX@^alnBIsE;@sn3WPfs02c0I=%DpC-a z8lpG@!aQVX`Sz{iQ!`egk568K4hGc<%{_DiVRx3p z4PweLap+8^=aw59Jrp4BPX^ z74|vQx?(Vg4}_Bs`qZbUkg?J_shnu#KGYBT&?o4~Ggny;#5E40EgQU*^umc&b$=mY3t>C!q@=zU_Vt%h!Zw-Ncv6rCB2&;?sv3+7tTT?p+WSZv(v7 z5jCc5|J*1s85OsRAbynx_aWBOi;pr9n2{@F8H@_3|0FHM={vuoXQPB6c7(U^pfln#QNI>=8B|lQ%_T|xa}1-FMNLKbcEo)i!L*-x7dltr zM=~1MsQ?`Z`J#~x0VAPlqsH zrqq_A8skvYTiI?;5HSjP9_?P3N?sVMI z8753mMy{Hy-D_u7GmWWR$p?5C%ti1BN4p&QWWHLLJ|Ug8CgW8b^JdTy=lfdo*i+cy zIj6JhNzvwHvB~8(*211*rL*H-MuciRMppjx-y7#NL;XmqL^(D!vsv_|el6a|Y)RoT z2shBxqB62X+l~+6Qc+SL_8?}gwZQLD-jC?!uZ@>ckW0?3c~+ayzy*}16id{E&;nww zNl%a`Y1n^ZHBlg(90}SZelvj)OUZ>3yXb3kWYF0~m}ituN%555xjP0-bDi{`qt%#h zb?LDt$kLe$jsC{ZEv(qKAX2B7+>qKhDPeaQ4@aC)hHzXyN=y5uIMXpq(9~0#j5{JT z_xiz}CB;p)WZv*<>j)qw^uDE&aE#`ps#TZ@McrkcaBESN=<=1_4`uIS1Pz)r$4^ki zq`@)Vv(4C_SU&ar%zIBAU0}uTNoEv9I6L~Q2=_TjAg(P&x*vN5=bUDnic7(kjb<_I zAP&R9CWxD&k+PxDIPrP@U8SIU8UIwa^EC35n#!(zgu^-u=>Ray$S>u5)g@3EevBPz zt4|CQ^o)fZ{318xtx{e7Gtyw2YWi$uTzCl2xJt8r4}=pYO&{vAv$XN=f`p$K{(xbN442s2^4EA(KK^MeCidNe|;^QvCF>)oI32! zMkF}P)jXCdzWL>5jN_;p*PqY2&!nZP8`Yi&y1#`SzF~p3f!tZ|ilG~IS8k~-!|Q7$uyc(emP2d#};y*+PZju?}Pi?jUq-r zI6ZywrzCoorWjT4ZMjQ1_%?EXOn(=A0O}kq=1lrQ9cHB;cgDsYRT*jKHIhbt6KT^c z!x(27wN*d_tHX{i|Iv_mP)N<>^ilZ+51$Lo<3>8om^8d^by6V->D%kih9rDSr{AN- zaJUwzx4k~w?*5`u^T<)8ad08hnt}>{P0cgPS6SRSCb93PP>#3*MT6+MKP0Urwe{}W zEX)u#<|hJD)6}XlH#Zj8{;4A%-otU>iV31zpd|q1la$Q?=d^rDl1<}PY=fdb_|`P! zv%eEnsqBMz)q=QtjLaIw=~3(VNa`t`YL`%bYN&*iwj&M~@Gm{8?CfWf1{l{Jb@?K& zwC?GJOS>~BV#LgU*d&~2YH6}g+?rb?ZlBwp{jIQ35#*%o`C=138!B_P$*48WGaf2YmSpU7=U!!zQg?dj}06BG~? zD{MDaJV2m3+ELhp2WHADJX%jwPw&TyqSl;b zKMuvcUB_`DQH68}i4$HX5sS+v6duV@*%KyeL?7^!a+M9>AI zq!6fYrWHylO6pQ~w;iC4bLx2HS*2_i6(0uq7J4RSsw~oIO~#wZhPWEo;~9&U$RIqA zfA?3LuR`Ybv-qsNn82_5q~~T=l_OX-eCTLo995EOriWhRYc!DRG_k0mJJD!|gg5=8 z(M4tAI6aTH<2*zYEyWb`1~95saEgIug50!r8Y{9L1SQX^AM-c^SREY(H<=Mah&N5= zlw)ag#*5<7A2Rb10ns!e@1`wmcO-7)4B?T>ib>@Z5#hKzw7=sT8 z6cw3X#&^9tGBI~JV)o2obj>=s`3gT(+hz6FDU-(AhO0I==0|1L2FR=!vQvR%edU{w z6mgmc$hK$&9Py${uOBrIfBVIx;$g5^)u*@?<;KYDWG`ZvFbhOqX<6|fs@d=!x}fkM za6^r_>0sWn7|^}^bK{4!V%@n)Bkb=EsFta1GhQ{6^PR8{Id_gnU^sMbmO?Zv3b4Tp z)m(46oSV@Ur~|W1AJ~^CDJy`&{5#gq2GY!+Du$$>cF`1KOvAVGMQ7P#GtO#%1yxyl zgz{at#0r(pGL^+-I^E9hn4GiiJ9&euG|9@`HmsE9G!{z5pBsZ`e$m%&d7%jeLn?~+ zQ72i}Eq_3mBcO1+p?AaiB*a}Xw}W2 z#;dDbl4Q+tEmJ~A3vZJ;n3tUyCTUxIL#Ia z-~=%VQ|TCC#*#={r&ucscAt*J@$YoZ&&tl!tBzn(HFUKlQSRwex6;#dcMYSCU}BE( za%74L$kVc772(fn>>9W>wXcsPVrEUGaU;d7vJPeE=fIv6&hY_R=Btd%+|)d63<2Sk zsf48;(nL(U5AWQ}UGmbG;itz`0W#NQP7mZ9okz1bXkz7h8Rt1w|8(%f+{ETtCz3A& z>Jrha@9|4Z*zyZcK9b^PbUiziPXDV7YcMM^(*3wdW?`&KkUB=ug}>C(F*)R#Ls6kFGb+ZO5)z zjKJtnr;r+Z1grhU3Cn~_i?_=8PgpqrmWI}!T|ah})scbHuk_T7ynuII=FKX@bm7?- ziBDDcv2q^eTb;44e9$)^5?mnfn>>6(`9Ydlvf3=h?H+p|>{is4mlBeU@c#5gG=v++ zJBGjd-(6Q)k45wkZ=rG{)?UA?aVx3__EbbSJ6Eznd8j0Edbel8OL=n27On~+Q|(y`LHa($ZVidmY{5~p@!bmkEl`|egJLg zV0cba??te$X|}}L(&Iz*ge55X!U34t3UGt?W$LwmK5n8|cSoP1kZMnjp_3b&zTNm4 zull0coKmV+c?briW{@NGm-7ucv=;nU*A#2a3n_QuXXH-t-IPY&gIs@KRLRIm{Ai5EpKVrQu3VsF zss-c*Ur;HSz!@{$D_aRvdZ%5Ch+CkL5tjSKY zs}$z=-c#C&rumx_EcH-cYBBE`|46-ocLNV{ej+8Gm@3ar_y<&=JwCFDSbSvd**xJC zy~6$YE7pL5U#}U0Bzh1u{*hrp6??nq>#tg9B76R|tMpu8Zc*mHkzbC4T<@{EI|J+& z{Ejuv9^l@K7X1NwD2FUjVgr2fF}M5O=ie+>tq~C1)PRza2(SFX7iL!+bLi zPgC&a!9};b&`!?WgO1*5lO{_rR=*(tK7$ABbWA&V6E`f}Rd;}-O%QRlg+~s6Y(Gp0 zf|B_7+q6yY?GA$(7I=kvLgW{dVhiYV2ia{WiGMiB)!Le9?%jB>_3qGlw+^e##(7Al zbBOm3VI~K3$N%-pPaC%#2&FXR@pWsy2>RP04JZ7yOFyfd)Ow_9hY$`$-m_n3`3LZ>;yVdV zdjcZc9t_FKok%C(WBGhcNZN@!r!2wDArkboA3&qnSfpBpjW_-w5rWeLoZ!?I)+-cS z{>iPv(K6DAxtcg-Gi(ApBYp zd8BQHsBtPb>IhPMl#)d%{EEyH&q&0%nSjg^?2?9T!)0oL!@_t7Ca14;`MJ5+GY+Ci z!Bk>Zoh{7wg=9{!zI)n?k5(Tkg^(rh|2SWgn;IGwZjNBa6oN|agbXY?q;$eul04g` z=P$R4D-8Ku->F$d|L%IM0b;dDrV<4{f~#=RcT`~uRwdG3rFom(=Trlq((x)ASpLvb z{BGo2MnrpCLLr>XD(m(%krh}Syy7IhbA!QM1W(FYL-U%wK4koHG{7Ye;ShN}zp4`^S;{WqW69F0t8HAD0eTRUI z;|LS*s024BU(yUFrYu-=5!m>&C`!pP5KE)P(hyU0t_WB?`f1c1{cg<7ge`^*%Imeke& z1vtbtwMub(N{N8@b@UQm>YMy3pI>&mF_^1a4jg7epJ3nZw=Fs*1OGH=%YWS(S5Ofy zf^5Jax14XN{wrW;aOSJ*iNm;q=hWh6;Q4EvxS3#S%dIN2`+lDF2_XeuMi@HbZ~HIN#A?nz7>kNql~T zacY{nTG$vRUJd>jM%gxgjPJFQbczizbh%#P(dK&@3mF~%$pz$>@-~TX z&XeRzYCGK;q1)3)2)^Y=*JeDtOB8hHr)=6MGh>YP^p(evw79ZLao3-Z`!ohmed6?O zkrgVAx_1f%E`@2-)P|tTFJT4W!g2=cy{R|P9$P%PT@zNc;8r_a*x+1_YMTz# z?f+0!%p~>6CZ~T_WRv=9@t~nA_1-JP z43}nza}>hUf+7!B8yT*(|`K*K0)~0bBO&vjlrXK!%d#mhE%*t33J6 zzupK1Rd>nsEzKIwKD3w+*Ataj)PFRLu4?^ljaZ8^XJ83L#8B1+F(o9mMW|2K4ET@< z;! z@KAcZOHkp-yvbbQOmwrPd^grv`qNdyk?DNH;2E?uf}&+r#i6!&t4PG$<1Jt8SqPuLd~hsFp)8YQ z4VCc$3&>=ug!u%Az76}|bGK<6f!{S_GR4=gFS-R-LH)Ggrv&_(v#Y!Ua|k{=oKq*U_aTh&VoAFpzjvrR z!E!gldLL279atGP)&Z*=KWUPSjcazQkU8^C=E!^_dc~1~Y^UTQ;{-VI2I&n*vL*aO z>%xT{bWU~A-8_krt6$P=PryJmR$~_Ub>2rAXx}I(m9);UQ$1X!@qU8skEs)}i*-9= zhuL6{c3?Sy+z!nGGz6!1mq;n@NZ>N_T3jXDH{F3ddu=;MlAv~Mpv9$&t+hJ9M-DuX z;5qZ3tk46WvR)*n+Eo)fXS*Da2_6QtZPrZzsL=a_j=6o+Ih965HIV-4?p9M-U>jzzRc z)Q+!;@y&hyhvE1P@26Af72zUPU(|Zw^jDCsxa?R02tu#V%l@A0b$7ok>&y7>zYfJh z7+YiX1?t}JYzME#s7#Z@%92%c84*~v5-Eb#$bC9>Iu(!VaRE!!;uq!c=G{T4DuU(x z`Cjf!jQAdEY$b+avBV{-ANw*m=B8a{WK0W$w8DhhOKQ22Rj@(cSC#uh4Enlbutm6h zFw&U)5)yDk=He$jHF@YU?)`NeNnFQy3sFWCu$dJwS9?5H)3Bn5ye#Q-E5#sF|CS2g zc*JfW-)|3-DuDO++De-)xcd6!t=#0Y?;}mIk)atxtRg})q3?eeDol1i$gnV*R3Kr@ zv+~Z*)}4J?QW-@l$T-|-aKxKeOm^Bx;$gTysFg5cn&>g`K-@Pro(ic8jz(m&DTy^( z5B|wD%t5%030JD{ic)bq6`^}EjS)UZe=(Ftz8RDu5dlVFxq+KCrk?~Xvt>DfoVmI2 zgqT4uYW_!)O6=9XR_W()-_8e`VVJ+7>U1}n9MQ%Dr4k!o8QputsN~8%ahEx=@KfS6 zEpggPH!uhFvn0s`*oZ8ly|BSX?Ny75NImXpsU@J$NRj-ydmL`WL(LWOw!84&i?`PC z{zzR+4+Gh2d87ZJ(}ug_F2sITM{H<-pTjzzqgMTDppt4Mf;^{aR0@hz&z%ov2-Gi) z&h8}JC?rw|Wa4Z6Tvk2YFZzxELdcoA0Rs*C4iqdqiLTV8y^-t(E~qW;n_h6#VY=D| zJGRH@wzY=|a3b*o6*XwyKT!*i6mZv!1`QEar-0UiF6mRJ@{_YosbEatR7%tXZ+^Jl z(e!_X4R{J?uHx=j+x-FRr%0uRUTK=IW+E_0PloeMv4PMFMMcN)90YQ#KY`nOAVjIyY$F@)%4^ z0#qXNdtR7&c6~*S{1PZPbKQ8DXIbB><6(ESgcUzU#=($sVv$&pUvX&d{)W$e5KO>u z9AfD^wDRNBdhp`!tmXoq42OkzWr5tjpTs(lpooDOmQ?5aEjgh!VUwDfR=1qu)_6}w zY<*JRKgc{48r{;G&-SpBf6;I->pqY212@Uf%7>r%DZW$*^D;DK2CTCTUZ+t^K~hn7 za3$vcg6 zTy^(O7PCa^Gt|@J93HbG5QH*(k)0-Iq~yJS-{D<3plr~=GUKC_|E_jO&n`-n`3$^vmBRxu^U1ryliV#_ztjH}ARkYl%_5wx87n-JH#l zK2g>87%RP^I9CLEwRYselg3Dqd6MqlnF6uGmss^NkVO^inMFawooHeefwqm?yYm4) z;Eq#_jx!H>GeY)EwZ9S_^c~a^i*T0}5w^k&)x%WbvekOfua-qQcP z1sjM{-Q39#pt}0ezgBvftalPr;(fL|ESzJa&gSOqY(C0Tud-8?5wv z^&zWiVW3M|kxou;-dduH|%IX&uFN`-_V0ZEGzK* zOC|H!E8%J@o4m|3c{jNv`jpZbNBng1`u=!zZQX})nD?YXO=C_aps=il{1t*v@ZGS$J+o%3$>1`DPU@cTb9Ao+hsRjLT zMgNWMT-2Vdt`*E`fy*Dj7_VqW#a|{I`E|oS?&!eh1-3;ApX`nU(p03ZJnNa8&n+;K%;!@G03ioDRU=d^OLJeC!=xR(JjCF=LZvmk#Tf){bRw z%5)wEvHg6UN<<1`sYBTH3O3;P_pxu`Q4Xd_ie=1-dE7>zSwDs^Bu~G4&WhaaraH-;jvU z#=Tb^5>JMt@txEF@w@xdEN--R5a zku(}sZN@L}1+qPTy28hC#a8bmcPSggJmxo042+ck=i8rS?J3=BJnG9G{QtE$Z_^MD z52z(5fhxx*W?Mq^(D7>LG8RX$WkiD|vtroV zTcyKzVedzG|zZV6X{t% z+ILh~ozeXq(+@Jx7_KM5^XC0^WWeIRmXL0TJQznDM0;HnA<$~8im4>xH07815Mc#JvL(G9-@eLna_b*vsW_N;V26?_l!uq0T5n>LaIi9NjR7 zK2r|r3z{EEU;JoyNv>U=#}+K zQ}Zf=p4unZF=E6*9uSN`k@F*=rG(oq%V3yzK%WL&3XB8te4F%f^`4ra1pV`u)%TVL zg!QPWeE`9l&GE5)+7l(rHlc+|Np?@fb%^Pfh-(&!AL#2*Eu~@Pf1B>VJqdsDU8zo>8s{2Xg;y*BAr}d@h7wHtI&w-m{10P~bxU<$ zMITIShjF7nj1^8`I5_LcWc4#^S*;!l=L+0**B+L}ql_3g`%EpY#-QIkNLV}~l(9u^ zY;J2Wtf>0q2&yP-bnk8(zb$}~xZh(qHFyp(1Zd!QlZg&Yf1UfNt^UL>#jp+|v0;=x zO|d;7{M?)X@*^0z%6v|3{k;icr9rF7s<+f1w^wJ1a%JgU&c59dvSqYATFWMiP(TAu^Iic)>ypydd6Kdv zHDgm;XCBr1b20=Ik4^hJG6Z&LrHZzTFYckh%jeOxSMie-UP?h)iVHn2hdEyt2I$s$ zlDc{Dd%Ol|lmuDx{JE-Yafvb3lV7;yx@H-M^0UVP{otzF@!({GWq-rL9eBs`;+)hf zg!Bf~+NY`joTpy5M-d?0G9$q_*)kgIJ?wpjcxTyN1D$umZUBZAsw6-jn@IfxWJ=75~;4l3``o5O38w?iCre zgmqCn<${&$hNfs}mES#6tjLh4UuP<`+hT{5iiknALQ!*Ffdo(`m)+g6wPv7}HX{C& zdzEnAjK@?^E6Q|DfhWLT#>R)Y0$}6^Ev?!Ycih#6gJEH#7ULD&{R3ak_3Xwm;4ET0 zPr}CO?-WK6G|*5hiw5Rx+O&qDvyVKrPGP#l^HDL)D4)30PT7nF^t#ox$p;iMdIG;w z*trTOFG5!M+JL%l)i&ouNHsBT0?r+g{cdYeDK(~)i7S^wvdAv0hq}p;EzgUE2HBt( z=b!K=4VK_Fbzd+k=5AA{a`K{c$aQ5)pTb?94L%g*GpJ86dGAY7v{Ozet|^Pe^`o^V z8Yt=mg=w+KSiR_d-E!9ie;hF6X_LkVW}|UNrTy!ZiGgy5KVF_YwdKRRUdlIWda5t< zliAD*<}%6?!h)VJ=(?t%|Nbegf%ck*v=_iBwqSId=v|EH%1A;QVS~UyHWxC69dEOq zD=3p`li1vm1!Jj+tCdaqw>3}?{M=|2>FnU%aG8`UJZoqK%+zjhlL|axqgE|!6wCwnIOblv3%70 zX7P`*KNav-R@v6<(Q!G<(bL}BL!5IdE!oPiqccSZX`Wkc&Puu!zKpfAp&-rXlSfIpG1@uB*B5+`-SE zK9m#mpgjJVwl`>YmQq7!VO&;HL{xbOS~F&p_BFI~HCtpaZLfFFK0zsHy!r%MRMr0a z)MIrU>Bq2vlCWro6M{N0(F;%oC0Bu+z%nAk3Lo71om6VfS?Kpp%&8Qb%a71e_wIo>6SRrNr&SOtKEv@=Tbeok{U4nP6t<`eEk6wx zHZBkEu^*}B;1{8?V@F^Ds$I)r4J@L9846%0Y;9> z_yJ)e?y*(W1@g`|wmWu0vSIu7FU7ZM1UfyaH>ccwhJaN~ze<_FeBCC046-DVH1k1q zqp#@v*HT9t8YREfRjtBslUMQ6LJWQtk@ONEqkZgIp(FM|gFYb(SpOIiuSAX+!zISV z4UT;EFAq{ndJGxO3N6y08CQ$}aHgj#Sy(j$GO7tBl{7Ce-OQbmTD4BzjCs8LJ=Q}b zJ|O+&u;XcqqMr zY8C#J?1}0WdY%vu^kgza%LV-Gi9N7A*_<>7XEXXjGdqSs5Yi!36EZ@Aq!gH7W`V6_ zOi|MKPKVdmeM7JI7X)!Fd-Ee5CD9eu#2l4C`plA7sol{yEfljayDUGq(o%anfSRls zmS5e|%ceLhb57iLZN|v~W?VEF!=N#U(u?eDm6?{*1`)L@7>5s=JpTxuhlKxjPgg7} z)6GknC#a{(JO=j44m+$R%6;k=w8e3Aru-wD{r56Mt4K*=b`< zGSbM}ts$Z(Lt`=S!h3IY)Oe>DUeNe-b168~k*O$~T|u(x3LWfz;JkF6t16VMblx)@ zhyBU1OH?&CZzOcu39L)9MGf_oN&|^t4S8^T_*YxF>2^!5EJ%>On8Z>>!&c*+cY|%z zk4`$lA0cZ|hVhgDgqPfjoxrlfGnSBIp!Ia$+0ym%)VFYZ^NNP-L(7ziVxR;Ihegn( z_MR|njSIh{BFs~QJ}x4;oAtbZ3dX4p222;B%h6x&nH32=iJ<|0x@&c^nC0LC0oT$8 z=3VnX!X~B!o&wvH6DqrwUD_Fhm)BN(G`rf*e=%OL|I{r2*z8Z?N$8Kt&8IYkZr!Bq z5yP|trnSK;8TG@%U6o za9Qj3VF#uHR;jJN9fDrnw_j(L_xHPB6b$^-SS04_i_~8H3Z-*a_)qi~Y0_8F>^Ek0 zAMqRjp7iAggs_x@Ki1#tMhXuWL|7UTd5YhDhwp4b%627@#`FZo)w-rM2Mvv;Ft8=8LsdB3*Lr z*>Fu+>2aa|dPshC9*ZLNm3#)y-xt5Zq>`2-RN)30c%oZaxmaDyeNi$8U5s3q507Ms zoz^6Gf$colCrGXznsseuDk27slG}E?Oy8kl%CrD`l@a8OQlj_%NP(aU1;W zABaT<=)PIh0@~}}u~YENPJgQ@me=rNThi*B#60z!2{^FNOq14VYZ`kU^sVE^JHeAm z1;M0P^FEZTKO%;WVTjtA^mmR8Ii$qmUPrHJfcDYT7t~vvX4Ox5zYl7{MgnYsMqBb} z#ey+|v_{AwYJ_5laJ)^Wi}4|Egy^ouIs~nWegwq)b3-!zAQrlgJPEi-A*g~A^TjP@ zxXd@k->6-v#Jb4a=kddX5TLLLndv({h)bv78L(W&I719&&Cni&7rGuXna(+3 z0LQUTBePNjl9(LccCTWjEFYJknZFecjRfYeHeZh7(BJVSo`RRc<|<+3;$(!IItkQr z^?e|UT%WsCgFzqdw7lZvP;01=359Bt)UGw|S-0!(hoM8|bUw!tr1v*itrZPxFttL^ zxqp-g8h9*kLO5_#`D|#ud2FG_bDr0*fqcB@uOCRm;J@G3t*Dip?c_EQ)6p30Dd#-e zO|%5m*;vyQ`^I085__}_vvXQ$n6BN`HH2Yk7J=*Ter?8JBfY0iB!2R~(fU;_g< zVJ<%~gj6bG#jtKyKM=jFtBqfm@j4+_Ne9cPUv_Zynvvzdgr9OZ<=jAn?MVwUE9;D7 zHd{x<)(+PcD6p$H>0J8#djbP2GQ9V`e8Mb%MaUL&*(y4MT&GDjf+;6>_&0|WY0d#1 zJj!Csz=lP6-IpR!nwUmFR_70KK;fN=WP*=^)3oQqb~7<6Z!b-;>y}i={hkt=%(ncD+pz^(+BLTxcxvu!aTzhv zYO7V>vgPz@S>esM^44FQTG%%060F}@H)zjXxjfMDZjLo{^j4}|OJIER@MvFiVy<Z7Y*yiOKar(8fq`GB?@2|U}YuM7Rxi)il-PCF5cw?Z{p6mGJ zqS4+wQlRCnrF}U<=jGYSU+2WV$rH77X_wnW7@_abQmL6gqUrxL$1wk#sdh8MarO$M zYt!x~@x*KM(h}3!GhMLG&bzX6$nMgt)}4Rk`qz3~>F@uZ1L$t+0>90eYd09;O@>+= zzO&cZ|E%rC5RBoyug_2DMx-lWfe3ks5`?aE4$lk-juA#aD`ecMLN5Egw&V|=#1bW7 zua2EtvhJ4ll`nV7M%h6TdIqwe`-fBiiSV0gQ;>Cnfp%&MT9R_TdJEvZp*LO(1if4x=9#NDHXsJ45@fko}5QJyoK8~9ULoI(P_QR8KS zI`0^cGD97R4SA90i)w7O?3yFy1dC!JPY^qwAMvS#AQ*vAM*#c5c~)PFz!|sAGNp!n zmACM$!0s?j54yQ@r{e}b<5iJ?Ca38c^~F|tHkJE1W zGWRfQF6lH6>>^fQMTT|^#BObYt!oLZ224uj%xX+yYTkQ;6Kv_f`^O<8xqaG8DcCs} zw$~tOAfx1O=^Q?_YilK)KG2&?!_k>dzT$DhS}DZCvE9S=E^hjLI4aYYBtr539r60g z7e>3s+6&!RX@M-62w|8R;U_HEce|2j@BSH#v&WYMT<|NCr#nNu*97h-E1##~Y@E_} zMCUR``vuuzyYhnfp(6aF2||}VZU0G=i?DyS!!HZ>oI)W0ra|P78h~13?y`!)_|kB9 zV>r;5(7C=F7!Ic-9*nBsiXc|x0?{F6 z_OY2tqYbW(ff4wE;({n(ko8C!RRdb0F&hCC#?wq7sO%Zx`Md=O>by6hLyFlHi8&;b zB37Zf?bDa{v&KSIETeJ^!>a^~9T~DNMMcxdY5tG|Z`=YKBf;C7q!ekE2d}O((6OXO z5h86(beX|1OGj|>Q^C_Y#^8xu2-ZdlsZL@foSNtF84m4REFMp+<%x%i)!~OBI9vky z>oK6&zz3R+n(rezogawUl}9wz5wPUU+cdKBsxgX4eZANp7Ro}B!(q^21q%EmE#Kyb z_u#gQh8IL`#7FVK!lkZ;Sa3NUA?qhPvo(C&fGoS5?I zva)iVPc2~b*j-k*wV;;1#6^4%_B9@+!vP8U73$USnn7M!QaPx4knSRMM|;$1?$^Yz zpkC6^TM1tJ-2G28f-%n6zpf|ue#gY`2E2-74F{7gBO4KlhR6{NX)01b*-oVprpXuOjjM5xJ@f$wKc1TKGaEJc zyCZSR0iT(q8hnT12NkJr4eC4ZaGy zJ)N|abgWtfHv$ftpZz#m+b#~L>5JZK5>&|#UOh5{mXXQ89mi8$sczvhUg(Srt0%X{ zY*3K@J4qM#Qz9+oFKW#VOiyehc21oCB3(R2VF02>TE{=*$?jC8yV#SAOB* z?_6f^Pf2yoXxD*lLc` zp+qXNgt>aw=_{02Sqiz}++l>?-(7?{^?B49s_mK@2?(paJut`M{)kb}a|E=QJz`aM zMt?RH2@_N8d|kx+pY-)Mh|stgX2CX6jz zigV8s(10|yVXJ!+n681(5K|Dxoe);Ve4_|daD1(0GAYrWlp<3d>krQo3weA=!pXL< zMx>H)wSF!)SP}QMQIgTqr(C{|)6Md$)&(q1Ebk_rI^KTk8n!{KP)%vn)DN)eE3gBX z7)tPXPclezFp%?fQAb6_vL&_%(kZ4n5?+LabU>2_k5YagqkF7^(I}z)P{-)h3!1*S zaTd6@w3?6XB9`mb{YFcPj4?)4)esDafZKc}M1rK)l3H_`m3pS`u{yS!3sw^>m2pGf z3HBb#{WmG~jnvglN3K279gxo0`PZy*s`T7ksJZmdI)D+( z2}r^%!EyerxEu^NDjGmZ-EMRFoIN>gl8FQ?pQj&<^*6Ta}R3 zrHF8vS{l>pg{!+K9or>wF5MH;Aa3ER^cKfv{vRf^xwNm!PP#gOlHxF+TKDmB%8C=hS<)K`p(mGk%@6%V2b2|@xk?G_+h!9vb3+bFZY6&%rb`B4V7}*vuyxzck(Zr2TccW z(m>xRc=BqXSo8Le1FCu;?@n`6MHnX`niZzWOziOg3%UkH`CGPpVo~;mkPIQe_nis0 z{ucN;3p|LXFxHNg1SXO3um}Py7%YF(0K-@-n*$@`N2~(GY}ox;|22g!&5dyNC+hDG^hzX?F0lcJD;1cOq2M-iM{zjU=1b%kc+%G6B zFwt$4BX6FV$`G`A7B`JL4sO3~yVX?nHi&;J1{t8?iXt!Fp(cH>h(>@zoPfe_hgbq`GK@8WCluDTYN5g`wW$ ztjNJ`Cf-=5>Fq-ohvutWXj;W>#s72%>)WAZ-sP%z+AJ*6hTlazIeL%ktH|&Gg zec-i-0wKC8I5zjZAprN#BO?GJ03rZC`8Dm3`OP!PWROV~3PJ``Wn^S-(1h~RHf7m# z-*t>EQ37RCWK%P?W<$-OY$Z+6%$!u%46h^i8j;Gm&Qmdde$0P$5|54QWzzYhufK-~x8`v9>I*nL314>bG0-3LIv z56Mwh6foET=Pk(1z%$vZAxg2KU?%D<#l}&(sGCVQB`ALhTk_l&(w0fZ?z5Q_;p;@2 z-j~zRriTN9rN;pgjPS*c%G<=x6670(g^zAMGUyGjpuH6*cS zBFcrKz`i`?>HTC`M2xeBJU>0}_Vy}JC7N*j*K*I{T18Hx2^h&8Aro9R$T>B(7By0; zt-Iqy57~dq;NNjJJSMU#4&JapBI2?^)?3Fvt0#9!jU$Y+ss zn--V1Z3*8}PFWCMKz58z)x`Q3cKKANJCfGv6lTXUgAWCd)X8R`w{H*F&+Zwlr`5T1VViZ-pMgyItU=V+GF#;kr(hS}7cc-XShEkl$nYP~b zEr=R2=#=Q=*e}`9MhmD?c>kMQ9KHDrJwADb%OIshgl{;f6BoHHJqm0BP#DKR%2tHI zTRt#Wq?CAw#6+u*MLdN3rsgI(^@w;mjF)O-=6{)~ICR0ngh2Vn z{}R}tR852yjLIDyK`DaB6bLhfdS1f-J}V@|{~}$pwP4Vp3N)S4f!kKH>0@J+nh+=4 zhINjFI3h32dA6L<5c?-W@tM2|s%JjbM1_B8=g+i61&ESol|&sctD92|yEkA`P0zxo0c!q;*`*GO=LGs2g?Q%c%QlCr)bIw2~5OS(DxT!>|_+W%q~?$gl{0Do6=JQKy5&Pq)QS0n!I;h{5{+ diff --git a/src/Nethermind/Chains/arena-z-sepolia.json.zst b/src/Nethermind/Chains/arena-z-sepolia.json.zst index 3a5412fe44e0bc72478dfb8981f5386a5f49841d..0051877e65c12f082beaacee49bdbec72aeaf2b9 100644 GIT binary patch delta 16233 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 KSANh@sQ(ArF(c&w delta 14670 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 delta 14629 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 diff --git a/src/Nethermind/Chains/base-mainnet.json.zst b/src/Nethermind/Chains/base-mainnet.json.zst index aaaea720746b40fc02e899e2fb65d8354d88fb23..450c06d9d0d3c46a0c5f1a42bfdf04dc243974a7 100644 GIT binary patch delta 10460 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 delta 9369 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 delta 9583 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 Gy!d}={}L|% delta 7954 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 Dwc$OI diff --git a/src/Nethermind/Chains/boba-mainnet.json.zst b/src/Nethermind/Chains/boba-mainnet.json.zst index 5921651083973e0880f7846a40def096e8dded8e..b9ada0eee6339f51e5dc150d23940e44f1bf2ba6 100644 GIT binary patch literal 2564 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 aIb<*D-BVlTa<}EX*HHr6Ll%`W35Ww}W9YH~ literal 1502 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 A^8f$< literal 1500 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 Z!`#13#uB8de`4~5gM+uM_huEl`G2hr8TJ4G delta 10812 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 delta 15538 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 E2a6-`YybcN diff --git a/src/Nethermind/Chains/cyber-mainnet.json.zst b/src/Nethermind/Chains/cyber-mainnet.json.zst index ca178a08100ec2071325b6f64cce0862369c2a01..46cb6963559929dd5c15f375a909e4aefeccb430 100644 GIT binary patch delta 12641 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 delta 11488 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 delta 11442 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 afB!!xD8B+V{;7aBWq_BsMUrjm>VE;_nUoy> diff --git a/src/Nethermind/Chains/dictionary b/src/Nethermind/Chains/dictionary index 3bb35bbf9d5552c79f1b3906976fef2c89f3f123..9617a6705bd58bb65bb20cb4e6875390026735c5 100644 GIT binary patch literal 65536 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 RlnFnTc3``|eo=iD{|`)ZI^qBT literal 65536 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 diff --git a/src/Nethermind/Chains/ethernity-mainnet.json.zst b/src/Nethermind/Chains/ethernity-mainnet.json.zst index 278fc5701e55d43799a05b75429d7f9351c7d4b4..3cbad235b874c0b690b22370fa464f0e6bb8977b 100644 GIT binary patch delta 8780 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 KHFhuxT>TH|3;JpR diff --git a/src/Nethermind/Chains/ethernity-sepolia.json.zst b/src/Nethermind/Chains/ethernity-sepolia.json.zst index 0a365fae323be2b13e81d7ef666e6e26b59c65d2..1ce360a87a87fc35912331a547b2b7a432cd898b 100644 GIT binary patch delta 12482 zcmYMaV{j$F)-{~uoSfL0*tTs=Y}>YdVtZoSwryi#OgxiJtjUD$-22_9zSUJ-t5(%s zUA2Ggs_xab!@%x&Aeg3&`5mB*vWSd)0S)P^49!A+<0uu5oB|E<3H~5y_F$0-(M}#l zq0w%H7!(%MpK@&GMGzgq@~G*l^|-f-f3o0(fJ27b;Ugn%tXUIdDo<&| zZhx6`P!2#L0c;|H%@L9sQf|t+2GZKnWYaf)B~0F2Nx5BWrV|LEhi8yH9O2JIj$_G~ zD>+L;`dH(TPGuR#3I_`>3d1^EIg;{?c$KH#Pi*Te*~Z;V4Pk5TR&cFsv;v6hI_Y;BVbJh0gN7i{@U#Ij0HQ+W zAmiW&5F%<3R;Ks9ED+^Eh(!`4JvImn*%@SPDKG>UJlO3S!pILU!pFgR!6i5(4$`Kf zlBS0p42dpIGEVY_xS`;ruQ-FW8F$nS6 zi{utlS|r=Hj7hmYWJG%rG!Gn?6X76>Fvqdm2da6BQ0YR3Kn;t(K_E983o<3&_$CSp zF;biM>2j28pkZ(t@k`h?i-*~6P=m9Cz=vb@<5A;Qr`DEK&7ZxkGVp{6*Ih0)6Z0+s z;1sYS{0C+hzSdLvVp4-i7^i=W6s5uaB4}ylp6#K!O08gu-e|XnXPhcJe*IYG3w~Hb zyJbv-rIKG_hqRK@r1d#yU4d}rV5hNY6OT2!Y18)yF_Hojxgy?jI-6O${Y4@iSdOVF zWms2Rt{?IK`@FtQ<+lgB2r7sCh;wTixt!FLME76czM6lTK7iq-SX3Mi5f6dV8MMeW zp}0lEMnPLp7G7c&WeKJtF+?jOSqK8d_zvpIbA%lZK^@}uLrU;#D;J)4nMP?U8>2zO zq6;{)K#CSUdcT<8HaZ&%x{5(Afzgp7lvxc-(SpTFmx)bTn@9$ZI|OeBw<2&Li>JIv zAfYD3hQU0U$^r~{y`uow7?_yoD4+-po`^p^5jGkMBt(Ppujr@9%%B@onkbf{pd@b? z3?vjh(;;Z6*l;tSm_O4OF`u^{ljq$VAO$5|RhQjDGRjSY?OYD;X|44&a7TuZk1Ye! zQ#N5@tBNl3NnaE!N*V=W`=B$3TS2L_j9wV{FleYWa<3HTplzBcD*pvuRJ&+uZ)*1}kbiAen)vHU+$KEsbYR}*a|IdYO0 zce*;-QoMPmD0OdpsIz44LtWy-d0T7U-1}{*H``m2L;L@Zp#MMPwEy{)Pyf$G{Qqpg zrZ#d>j*rBDAM4Go`-Ib$Wa(DDHpQc(pX!rMp=D->4zU3Bd&Qe$$*n5X#Cq>k(YNR2#<@JvPyBX6k(J_ z$h^FFDrDa0+=V%{>T0psPF$ zJ2Y;nVH)%`bCd;vmo6mwgf)@|T&KFEjYKbYTY^{g3?jpZj?zXYF=Y*kl2ow?6EoFh zXLZqH7Q2m*h_ykB`HDPJ(tmNg4QF{kPAD8`PJ5z19k@)(Zeysa|G+>fCM_3ejIToG z-z)IF=;Q&W0JG|{C}(odJeC~$1beD^go@yYy4H(KKY7~)Y+^?`h6Q~+*k9;UH;%Tg zlI$nRbWaxU=;5Y0o_RKb`Cci%_pw)MB#P@+2c4Q~t6E7b`5BC2+9%=iwidhOQDSP5 zZC-z&Cz8Eo6z1Fnc3d%QGuS8F##4g%#2K>QairoNKHqvQG0mJq(3{?EpMG}m7mG4(FCsT z)RRq5ONvRRa$Q8%TAM7flTB}$-;otd^b`+~787K@kY=;0Tj$7ePnkdZWNHTZ`L#t% zdfLEx&7z(d<8db*;l1fUXSmaLPwI8nV%HAm@vpfaX|?uD^r_28flX&4EgIN+dF|M< zQxpFp(ivc8foyKpQbjfU8EOK1^=kE{Uf7xMk)LR$MtJg>Cbo#K=vI>26i$bB^^eSE z@ny-0N=xi|<)eN_3AeUu#^}gaNl3Kv*mRC`YR_u!M5yu7%F1u>Ys*Ijpq^A-ccziP z){d@?8>s48Tt&0lffK3-bVK>o;CYMm#G89d7IklH8u*xQ_v;gHodZZ^shZY2x$EGK znDo#EhI_=R0-}x4?pQf~cgZ{6NiRvID0Pf6*as@*`X~mtt?oRHbo|MVX=L3*C#0Ns zr>IN8FVmM!wNVI-h_FE|Q?*NPR$#kyxKfqnAeX{_q9pPe14rySz8bO#gVV{p@Fa4N zfBqs0MgQbeW9+KL%F`v%x}6%?JE=nEP?t_raj#VV&%9cd zOS?PPI=*TFQ#ah{$f#`TP0crj_5PTGB5QrRD$1M6TN01v<&)K^)^=};RBd@|`0Tcd za`d@+^x5#nM6tm7$-;!vEH30p<8*<1t$c-iX1KD3O}Is!?c^yDYe!$k=AsHh zb6Z4C58lZsv)CY+sW-O@n3Z{**n0DR$voIpOOnO~W2>smsOso$AK)tfgV|4Hp|<(PMaiV*^onZqLeK1F`Y?);{}FQYonm0@Hu>T;y%AC{{6D;7B? z?-r*D%uC-}#8z9wr{02X)mooaQkuQOR%+eM;bF#hf-N=o9baIXkgnLZOyIBbds4pL ziWc#cxdPf$_JNtOVm-)&;A%w_eZkh!mJq>IaaV-CY@ZaV3HLBZB3qIk8iCGGVGreu z_(-GE>|#+RGj95~eU>kF2OGOYhohyWqeiM7n4QbqN-}0e)6pDtK9^NWfTvN@WxHC= zY9~9^HDHZMM@BNytDqt#*5$Ua`OSqxPNqW=Mgjd6l}>gnhfdbbuO^SSc0=%K z`7AkIKgE=8bs2eX?ev5S?X;_)ytuWhx|qO|KTgx#OD^xoXXd!??A=fCHLB{HAhFdW zcx%4xxB0XU+K&S%-=*|sYnRL0QiIL(IGM;D7-Zjr$us3%+!S+?S*}nx2 zWNx?M2NU9nTn8lS*}=Qgv@XI@g|}?fYU&C9}MGTLhB3l zq#~vspMN7+#kMhvlbszLVW6QeC!pQn$@Ym}Os{rH4kTj>*n96A5J7KH`*YNEYJyw|wn%xkK-O zRkLd(xQeId)byM8PNr}oY`9bIms!u~G&ZA}{zRo$T)Q)Bu4ikr@^XHigb6he4EJVZ+pcxD9!0(lT)$rDDt zBkAbS&_xk?w7T%#@CcAsON`ncGsnllHKw%1zo%DbkoMW$T2Lt@b*`xhzU#e-oTN+o zCZKZL-gbA%5*@`_U9X?UnHvF;F&96e^b!+s`s+l9u?TbW{WGb&Wz+cXL*vui=pZ4J zml4jX{3lD1wN)eqc>DZ}y0_3QaeTbdO4PAg!ayYS;>#8}vcS<%_To<$ON3%hd+CAh zS8X}d+~I`<%!R(s5>5jZ#GHVeE57rjApHc7SPCK>%j`=_4lkiNPA49(Y7J25s#A|; z8`i&0kvgO}Zr-hOZ`e_Bn>Y$bCkKYxE{|_*oN25i7;+wiV>4eRc(Dh^bUv!XejlnF zOX@-^eO={#BxPo;K!W5BtxghP^M3pr)%*;yYCITrJ*i(|hR6JpV8OYzU4EW)tx}25 zX&YeBE8+L*r=D-5uy=%g&VkM7Hh;)k`_Njuv_XvTzZZ{5;a;qO-9 z>Q0DEgJeufG~`17D6yBlKRsE5N%hCA1~^2{(S<`qerTcVgb3&7AZPI@V=yX8y~Y>F;53xBUoFV|a449y+G}ixbs;7MOD={lpk6gf;HwYO$h^~)&Ij`I z{YLvP$fo)Axgvjn@jY+#5q4ESKQL)bqVrCrpwTE)0W#+vuMuC}O3xKPsLE|sT44yC zGeo}`5*Hc@7&ILaL{uz70zz73Y*NvW0jWBTvo?aodW&cJ`LDfe6-~@A8U}(ylzU0c zU_}SO6uI23zPvmypr#|BQ;V5o{iA_St5eB`eJeQ8}B|3fu<&$0ZT01pz>bxKI z%%^pEHI2XG7*qeeXd)Zb;J`h()H;Wxzo&5mUFfs%H#V1ID(d5{?Z)`V`1XvzE%V2+ zU!ZGZfZz5nm~xm0YjQ=Qo87cYG`WxJj@C2`Lp|v8V!z3DO`}{Kpx65ditro4=Boh< z+7o+1bRInaWDVgW^fHR?T{2cQbe|M9F~zn6F4-0AohD4PJM9z7rfiJ@sA7Z<;rGPl zzw$h9J;=y-NAJX2_Mh2~Q84F&vHX|K?}Njfe=XC-ZHY)B+1KeCf%X)JIKW4|w0T?0 z(E94<%c;p=PJ`4*K|hYzHAioaU_Z=W zmg8{M7egs}e7(c5DgOQie}hHv*MdQu3^DL^1raO@Me6mD5p(e}$8&CCY?v%N;QHxfVwv$rw@iL z81^(_!06D^g;5SfFvoHr(|+MIPtg`Q24<=)%wy|1a%0)U98M7}F3Y{64KAt91pciu-xTv~OqYbI}*R!yG>VQqux5QWG@iJe^+v!$EX1odnR z%^Zy_J1pwgyXTU{b5YvT^JL1>rV$r5DEOHB!c@c5vaVLZKcQyg9rb9e<0^1(Gl~3tw*2RXt&h0w;J6;jS(n7FX2JW zb?iX8FSPt%p!#3DeK^-0c;FkiCoIWbHG4=+xpV#u5Ta%z%f zlGp?z{=(i#Z4f@`$3E*UY@Oh`>Dt9DC1~d4k(;Utpe!3Bs6Bb|jEezq(C0%dl+co%r^dF49O^(aEH=^q7cIW!ZP%u!6q# z3QjG;%U=L=lvS(0wpu4);VmwI0M)N=?B4m=RS#u_;>!Z7k;5RyKD%jEZ;F$AxtQ}3 zl|602kg@RIcltB!RL6Gh84k3khLwK8xW9Pm6^~=ZxUQ;8x&yw5WH?1`3NF3h3Q512 z2laM&O=irCqzHBt68LXA&N5?Ou`DG`Q;uBcn@R(OZXqh*~bb>ilTJ&Uy{ntuE``Yj!WUs z^*6e0Mbt!Szbo68@5XE~X-KYi)9|apSKn36SrX}_WMyL2$fiFTv6oM_Hnf|Qf1cKu zfVGM_trOT52#A~=-a1^-BXRpzTf4PUN<#~h2(*#bcV<;TUA|S>wv|31|FoN)pzE;# zbM|`=-hPsWJrHTRX%nKD88S~elc8?Aia}m^B36BFI4k%*j zxc77hwkQK{dVN#B8t%BP_)&`Sw^vXguY?gy@v3Sw*q`&d(l~GG{YbXmXvR!F;%tBE zKevMR?LvI$J9@Z8`wDb_EU(4K{91rEC6)xQu;LJAwjysmbg1?Xi z$jA%#kkiW1%5yA+yr*C=bH}*tF7*QEPpVcr4Pw!FrJznh;{mWT4?cRK5ZI14aadK#d%gcy?`sXylv3Qs+R$UmJ-@~PWcAEZ%M zux+MOu66TuirF{8TgeuJjfAB&a~zBJkg?6`GVvg!;8?FpFz`T~o!n(Ne4sdg9wqd^ z5fS;fqw`0W%KgAS0Y|jXdE&`djAY)k3TQ&!W7C6!HI%{!zneHQoEsWUb+$#sp1WxA zl6UtV^%W!^?r|gPS+?(}0qIW@kbpRrk!aGuPL>G>fPUuEiqtHa!zAAf(CfO*B0hJ+ zwhoA7(Ty-soV`299|GZC&NwgZa9MnSIJt(IzanwZl!5^QuFLWxNPm{hg^}@$Q#Tb6 z@3+&sKsd+Chzrj85wvauxch0_5x9=&9X>?-<`0%wSECFCSNP_IOG5t=4uzFd{6!PA zKt9vVO{zECi)8ROOB~Be6t!okK@##=SJTWN^|RcQ##)wsr_YvYTocK`hCLRQ2bSJh zzl%nsa+VgUEMJ@#i~(_X4w)5>nQZG2UvVx+SjA`A4i=0PbGc@mNBnc{z>)A=!weM> zuP4j?07O@t@}CJgOXj@DNH^0C;)v|qGx{h5Y|Drv&P?Eu41N%~tmx94?!;R+V)jnvK!Tn@wOj6MRaZ#{BftWsU<5na?Wlrk^@CP)Ww|r?& z2G=k=SxDU3)J*+ieR~{Q!88BvFqcNOiCp@}5w6?rDmZsM&XzFB@fvh`GmI>0E-qdIsgNmOxGeG`Iw{C_bkZ!H?IT!H#7)} z`)F|EqVi)Tb?awVA>st_xf8)!zh%83M9_5c+NB3T5m>S48GQa`kvC)9T>o6nFugS> zRk%WT3?mE^(IKNZhvh@UuVE$P4^9-V-{LUGzn<`A_9u+<3#$2gz~=mM&K8B4xj#QC zP277CAKA2lH&@_xfIMsnJuB2>-2x^>fWiz$*w;|X@(e9S4f;3UU%u_D2bQ%N zCGONOo=Jb_mF|kUVvCKakp|Gysa481{cZjBW)e>{UnX{B(TZU zy|(e<+Zk@Ic|GjndMPi~V(X`UI(}oNWbI|xRqN-E_OGs}y{-Es4TT0|J3ERYQ^w%5 zzep@F8f6-lGp91lZRJJ+ltD?;$dK(}inQWrkIDrdUDx_4i?VHLX zq~c;YOw_(qa6hz8K||saenr2*93c%@ioVAAuo`I>G8BLFj`@y|me@dD7G768y9X;- zXsl&Eop@VMl?#g!t(e$&TF|2mtq{yGisTD=xlgcnD-1tO&+hXtQtdOZjxM(IHsRT8 zyTV=OPL;xdkv=*U{*CUod}jIedYO{vR{P?oom!_J4*W0qsdxRpyCdBbcD?9DTMfUH+rr%y!QvE;uq!lrH~#w8Q!wXv>55axB6<+Y;ep zg=-X#7hJyR$l4(Gs|Xj!Ld}OEtbe$gep%NN5_a%8#}%`mQ|G ztEkIXXqnO-R-DA{%~!5lpc zqi~R#lcnAzz?s;Z7+r-V34|HV*jyN#HV>`J6>LU=R67cV1UY;?bvEL4;;|ZP13{^= zkFz+KngcPF4wBbllK%@0>zdq}aAApf(C=g68waY+g-(FP19!S2$Ww}z0WdbpGn{pF z`H*>Eqli|5qUNFpS<`RU4kjfWAzmyQkdPWrAX^_ycobIBXZKwfAQm8IA-<@|kq$v4 z4LLv5qa_XsHbuek>Yarw^KjN%q#X*W8lA-H%JqMJ#@)zkr6ol<;TY>BzV~vUbt~m z5s0S{fYJ;|8}ac*ouuLV#ry;N`Gt9+4>rAxat>JB)FlAj2H;EO)L{Iq9S6(AG#4yn zj`mi%H^c&OzndCVnx{M_9iBsmG#aw%H*hw4r94!`>>Zt7A60e4J5@kH>2b(=e_dW5*=f zuhb7!Gj3rea(nHBJpvvnd6HT{A5Cbm;&$;ZWckj(q4)HPhzQZ&MPd2a9W$acuyQRR zt_&YN?k~-r-A$uv1v|=QwHRQ9ap7iJx;Sw94a0uL&<}<%DT<4ykA3=K3>?Q*7CP6> zj8y7Ws`y0|Ii07fxme?%fGqYBG^q?Wj1+fjdpC;!d^+|Xg*m|xi zQT3<{#7vl~w4b756|_?sL)w^Grz?L_8B+Zfg=p_0BU9B$BSPFkm?w^vh7Ien_3~iw zU=6anAHWyDXP6V%3JuFrc47}_zYz=9-=t;hp)Lg@nyLrWhjgY*ha@2(QL3(hIUr!A zI5?%Cq^u-S|8OJhliPdhV2?f|u{L@f-BlemW*KFoRUU;yP(Fc^CD6)pmNGiBfv&u) zt?EioTSbSKjgGE~Zh(&071anER|pOxXA>u9@RUiOs%&Y49YPtp=Fj4v3e4PFv5B#U z{TDXTaM$&%1u`QV;_%)|m;&*UgCbc`@t_hczo+bw3dB6vCZJfVe1G8P6(s03gN77C zU?Reej19*z_QIh}!n5Wv4~|Jk)eL5c?6(@d`Ss|__e^YeKjH=#x>PAvs|{{OLrX3# zrICR-ghK_2qQaKY#Fd~z0S{o%3q^F|sCnUwQ$@pY#9&FoP{m-;{sA40Ook049(E~? z_hh2*n)D=8X+xZ^`$Wn+UE6ly<}`*Hsc=nrYWk^i&EblTDR#!iR5%En{n7 zpEqL{?Y$}{hJ9K_(C+^Opy3eUtW66$G;hlRfa$RaBpf z!*UzvB1cHU%9kxRJ=k3qXJ>=D2dFJqPk>M|$INy3Elp?xl%{o?MXV?l)*dK} z*;-~VNyrB^K8OPYjyj}9tRf2#(esB7 zfxKs_yY4|DESKFWkC+?gz0}$FnX=YH81bb>JPYmAS=Qs{wrAs@d;%0}CS!k6ZoOGF zEzp}jpa?xk(^}5=$$@B89MW4ToP2LBIY9A&{%;Di)gxUd*iuG5HA1W)iQ{-vy}^9_ zxGowBr&u-Dfz*k>fUb!(MjHH5!@JozKzKy@rYN*cun*@}l`)s&rq2Gs{Ik%<$blJsI zutzB*T5~rfyhfb;TYe8l{fu(f#k9UQ6>TyrzK*dC%(UJ%|54id+rxKdpby zWO>jApphP);=;={ThR4sOM3bJ^n|Mhd?iiJO~MBN_mK zMeuX?#-8EMqlF9g-ammTQ-4}VcOcnc_VUx^n*Q#&7Km5-2k~b?V_D%P7B9r5kz~VI zr!%@(;J;v94xV-K7+V>gm4Vh=(ygD4 zbm>bLxJatGBRJDuP@`w19|iKH*Pus1vH4QY4EM3X-;e3QRL6^? zNa3N($EQs5YB07SDeR(tR9oaMvOe?N)jd)ble%ogx4kICVGh3Yw>meQ&HD{*;H#|3 z^7Nue%kDB4PD${-j#`t;P3OwRZnA^ywe1FNa-}C1tFM$j*;m&JN>@ zZviET59Fp#K)xsXxllLoa4nTn5*3FerfOMHpj$XP&DNJ~)!+{}@3niXOFJ(m z3nG3-Mog{fNd~baxfPPHv)C{(t*?H9CCOEBJ7k>&j7xH56b3F1gVg| zx8Lxe&LGwob?lt36L|>gU!h$55Q;F@W%Oc6{3&%#H0$tzw3kGot;W=(f4ye?2Cu_H z@WVpTdTERvfkae(NRjeJfRT*!L;{PELFO1aRPJg90nZQw(X;kEde1$j+{DUXvHXky z{v;D2Y2lP{vMlaldJZ7k zDtN$N>0f3i2r1q=zy`3Tkx%P?D@5`p^G4l;`uZz(o=W%h_Y~>eWF+83H|GAF1pczQ zlskF`KE5hEt+h?zsXj-*rpeiW>QY-&&K8i3e=)2y` zp?(*y^FiCw;2&#el)gFiTD)fsfA6z|0uns-9S*BJn(IUj&CtnGn`DTG#8P zY!Ip&*gpdQMrA?63X+LOhF~ibTcm?+x^znGTCXF_x_L*CH;=>yvfY#;*AGCKnz6!` zz>IHw){2v9X`$FU%T$$=-QmJg*cw1z`h_OSAWPbS}x?Mkx6pJ9^Fi{RFR--2SKstNCvw ztUUzsv{PLD`+p~G>5!V7Xo|#(=$y6JMnVPA2w;h5hEPcQ4I7#hB)#)S2|+z*Ed+w5 zeqhR0MH9)Xm!K2BnV0YE!HamlcMR9RWD57h22TYDq;L(w=F?R__ z;8fOQ51Q(t#GsN>%S7ZT-dU8V$!~f`t+6AJRQ_CV;$yUO&o=vKTZ(|=r$ZUh5Hk~* zww(l6uFib^31zlAa6bn^M*mmZN_RkcN~|$;iLpdwc|0@!CYIURDC`%KNQggZE3?(4 z@?r_U;PGe(-g#uO9j?>8mW>LP0o##Eu>qOj#x$YE0Al+#l-5lcy*~%ouOdAGl@7#P zzQm4gC~Z5D+fPNM33)-c2-6ipK`ptr95Bc|{6CB^Sor_| delta 10920 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 YQz+-Ym&lC@Z22%SHg1i7*yV5kA60&BNB{r; diff --git a/src/Nethermind/Chains/fraxtal-mainnet.json.zst b/src/Nethermind/Chains/fraxtal-mainnet.json.zst index 869705de89cb1ea0f3970b6e2034d4a21f60f24a..debfe6488fec103a535938b3b47d162d54ef2f67 100644 GIT binary patch delta 8405 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 delta 6980 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 BMoRzy diff --git a/src/Nethermind/Chains/funki-mainnet.json.zst b/src/Nethermind/Chains/funki-mainnet.json.zst index 3f45d457365b68268ac7bdc86425df8a3a0ff9ed..9c0e92a9b5b5f5c33675498c64680bf3e59c2dd8 100644 GIT binary patch delta 16217 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 delta 14685 zcmXxoQ*fY77bxo3p4hf++qRudFtPK-wl%SB+nm^%7!%t$-`@W|PgSq3zUZpHXslju z*ARh+5CC-x(x-&6asr%QuXI7FXU_{cb!Aji@%5 zBr7YaDz_WNGWZ{G57ZZCRnm`u&CbFGbrm}NCWMotI??OB!j#71HWk^)$;n0K?-Ey{ z+z-m1QjzpO)x5YX3=)JSI~Z7}EZxKhvmfq}5_*OTMqJ zu7LmA)fLMqwpq;o2?!)1D55a`Cq(?e9)<+M@_&xE|HnY%f0n2JM_&AYhP(cUkL`b& zD*uO;^nVI7|DW&w%ZZSl);t&(YCU_rnj`RlJTDTS$H)i0(P)Rb7eguZzOWgGIS;$q zQ~M#0CD5E9ThzaH230)Vaf+lHgJs%WPC?KjOZyjU3h>`uQA zD;^9&*jrKY)7F>7Nn7Hj)%I^}K%q|@j43pU-uxJFnIY`tLMN>Y2t^IKrS)SgLS$+A z_Xl0^bnTrG$`DR0nJ>6ZcoIXp6YLdq%OGzATP&O#cDe^us%(LNlcz{&yq?)q5c$3b zX!ugRY`l{olz7XM&8`G&yd2pNL;+K%7ACs%LeTa4N%TMp5Qqbi9CY^X^3Ku9YDo|f z;s9tw1ehmK^osK5zet`a15h9!U?^zTAfPbf%*5D8p3L|c5yIev+!4g=rw;W2!G3>* z!SLpi9h=tjI08-@H&TgP;+)>;H-b!*ByP{V0&0{h(m{fBSuJ z0-;c(_kaJkpu}9ybAJ#HHpzNqWnEYVM}(3QCa;HDgF+&-zUdc%fXe6(hAJ2chN%XJ zY9{&hR|FdgpS#`|0m{~$jf8;UH~*i^>BDh#OO?rR@EsU-?EvS|L(|9RDxi2;{O>1= zR#qVxTxf(kNtSKPG)7M9|pZQTSSLEI6;{jS=4U#w?6d~U&0L&7MTun0x)pLv`a6QrM-alDG=X%HrQwb7F7J-h{QuVza;NQOrRcDd^b2(e3tG7B<{ zcW~QqAqX;8`b%8S#rt4uRtfp#SjAAHsAv?okYL1jBrN=M39IJq397G!+)dY$_5oZ`br2nf4I2$-NA5PDe&Z8KcD znLc`qr!6b>a2-1sa-I0G$wNpbUw|Ok@tUvD&2$7nXbXdCiRKbCfw23B-5FxaAa3YP zyXS@>qGDq36%}z^i}*1VL(H2e6bX+_?C;;(x?)svOMe+sN+kwdvE&le2L${FMeS4C zWa|!gp-scCsiR^Fs)aS;{kxf-2K0M&myF36&NFJ@6l(QEiYv_bc2#wuZ z6~Z1!SMyzCTOzo;SvchJ@kzXO5Oe8l!_a+hRmV~*9KMq zqZ1Tu`4{fDJZdpKRfM^S$s#onYxdf;sZ2@8Qjn>vKRHv-&A#A)`-j;I8u{N(${IB! zD$cDOg9EL1+Ce#SUd!0eyu*t46jHO{mv9{b+nE3-mvk(>n4;5xFEMk_rr7-lUBn1L zIs%lXr4!LpyD4m`ro5C515`;Gl8kj`!{Oz_$f@iYDLKL7TB*ahZFDX0t2cSep}$m% zk}4{2a8i*4VZW9MBh%GkS$1*cQmJRLaXB?1x>k7a{tm1Ph<3Sb!D|fJ9vsQi>Sh7$ zBCu^%*miQD0G_5+Sd0Ppuw({vM~qjgsTk)9txR1_?`E~iFJv-P{GmZQS8P#yw}gS7 zLO%q)q6<=#)J!@#hmk9@XElbNMjG5mfl}?z#GuI_rrcml7;wQ?!8LR5n3Q^+6O{s- z#3#`M>oXW1i6+Cz$!yDW0}htDBvpV%$Nfu2Cv?Hvpe6Dgx62ln_`#?|pD>hndRm271&V|>GSbp4 zccQEteew)>Wa{^;BO`bCExP{(1Su!Nh>}I~^P~a`UX!jcFo1mvy51%F%C5K3%B}LZD ztf;)vl2q>0LwCzip<{qc@7(nCBPE5E__^WZ95+hEW;Q)KE}F^khr?tIzm~_1m4rDt z?cJo7f_!QR=|A=GxPKieC(r<@1O7GZx82eX$!|_ovPZfx)uSS|YXqxUafO2#XUQpQ zi63f`758o&ds~Ihmcc#BH&T5Z_w3)O+HXSnRlTtvC(!9X%hX{_v z=n7a`$UsiFRm~o>Wg&9Wlq7e_or}HiH0N>uIa;O3R+lbItPHK0;AlHuc3$bWIiVVz z#D?U?aUq-CcnIQ*5`_KIVM@w3`I)vstcI@YWb`4anR`52h9np1qFL>$jXh#)?^`M{ z`)F3OYN?4}_+7>^mnJ}7sKZ-)Ka{zP?muYU7&Ad0nF2?D&pPw-*y5?LA?H1Lbe;vP zCy_w};q35K0q%1WUrbAsWH0Ix&N0O%8Hb!T6U}_sP7H>fRRAYRJ!wO|ZsPO&yIfx7 zBIc=h=V|0AIhjrE5S!%>q#gZ?++x;Obu78T$Jl|E+Qcw^&p0q1C4XhTF{aMZ#Q5l- zo&@!FMniLcin8BVW-(*mb_q8UUrNnLN*L08N0w3=JiK6cB<%qch7trrYg&|U#07EI zOH4NQ8k#pATg@?ngWHM#TO-rY%-A=@RMx$8q-JI|?2D(HelqhBXLU#eH&PqBK~^Sq z@Wr!`8jLGpZvb3mapJf$Jw5XD&N;^>3H0T+@2C8!9o&Llm_f{B#uSGlltMMnvHX;t znGh)t%E_h;Q-pA`L>1>*X?0SKMf>K{E0K=HyGZLwGn7|Xqm__Sxa%ttr<;*w$m+J- zExRuzBKX}cyY6!H^Ru zw{p>;wmsu!Hw@Yxkkq(Z&%|Gm!9DqsN=40c9YRjwbL<(7OmW~gK1 zo{q`x`>4Km(Pu6)IAI`-e4&FV(`iWYkx&tD* zG8bB9WH;&xzB!%nV?|tRXUfYfStP-Fjhl_Ga17PQTKJ&cQLHwn~$#dvWLaC(@9_GrnoW; z{{SAL36r}nX1(Qi`PwMLrn?U7yM=*)hQVEKegV={;>SbZNm)i?Pt zH~$Ibs>oofRK4yge6G<16pAq#AL)UsC!o4%1c_wf?qJ<|#3AZtlGv8A_TvUq>atux z=hg6^vX^3oY{{gTx<8wDAs;J)StVOX^oSy-}%WvL39%F4vG181RVbL|ulxi5PEEhNNmMo{p{CT*7E}KJ96IE6_) zbT(8+s50Iqs~F?xE&bZ(Dbc(z4?z1d$!zk3>5N3&pZru*P&}povt9?s3#9`UQ&i0M zP^*YL2V+LRm|FsQr9o+ZZi+-3&cquEZBed^$;-&GSlkPtpe0=dL#KL1v2?9`v$*C0 zP5MyRbjjY{+Kl|d7?mw;dSbk$>{)n%1bsC~SDZ42MA^MdN`uqYn1oC+1c=V4u)wn^ zF+*)BeiToKo6Y}7$v}Kl$3c8E2u6HE5UIn%fbf$~gYD{37&W0A;mMvK%*cN?@dwpjLkwxEpCQQpZ3m?c>!R?vNr2|`wmeBKVSGOdj zl!VaOL@i?~u8O&?o}k+uVu#soaI=r`XG_#E)@VNN&A1g&e}*OI4pZ9lS{6^p^cBbm z8NhpyJGaTo>FT~|ux(hJ+us$kI*8K37v7>1r}uyc{=Bq0di7(^Lu0k(tVXR zJcTIh<6-VWqK>+R_VS1xBhm^O1jk2Q%u(Jj$AEH2kx_^Jn8oO_cG% z;7ua-#xzI?Q%t65(R4LMFPmxPpEnlx%@{Qh^CR{v!IY0^R+go&cqv;GS;uBJp3&#) zLP<5Z>V(T$H9@;_4d6iEt3!=GRz9sb%}}=y;SPf+Fo@%M{AgvO+DaJ1m7`L~6Qxkf zD4y)34azXeJglwW`ncjHmy)JmofC&U!Y&wAx6_r@(r{^6?{%i4l68gRn`B039l}=% z^2e;VQr(sIwrT6$$~o*V{QH`y?~voqfA?#maJSEE-GJAW3gBnr+7oleaw;M!n5XdY zPe-9$Zd6|tj~&~`hERC10Q;zG+VTp$v{KZy9@+lBF- z?P9S})fSz|l;W`^6oqJmFFcWVTTez*F|m?Sr|~PsENuN{$J#FFR^FnI)8oGPW>H;2 z-n&J{KJLqZbIN@td107KNW^;y#fj#dfPd+h+|pQs0)WhX1$>|Q&e%;V#E@~8?EB_+ z{i=#J6{6Ifs8h%Yy+;4d`}HN;B6TNL6Zv>`lQBHLZ7zc!dGI~IPkNyI38Og8lK9*z zAqDbA!cy3xgu?C-8n%ojAbOqf6h>~fdSUS?9=o2`WYR^Awg6nlUGf7E0%A0-L)QBx zjeR>;K=eo3Y6UJNbo>smj)eS0Ymvd0MAK<+zxAtJ=5Kej(9KEVS!RMm(r5QaN05W2WI}7f0CNah;bv^%B&eOBB_^}c&qp1{E@XI= zH?$2p&A(0+HDU`#n6QP=xFbi)Go+6P3OcbHH4P_@p|aSiTD|OIWrJj(b$;yBM%cRQ z0i>(07yds!$mrpVsHDUXt#nTIhRJA;AYdznM^i)n^eLIXXaIKM1MNI~o&U*t%(!Ux# z5;1;{C#V&kY)5@&zx6aDx_spf3Ov6UEe}oYzC4MX?A*N!EDfEG{0-U7*{u=`{AF3q z9z_E^E{6OD>p2BiloRLLJN-k@QSb#F8`#0b4tHhRqFai(?JatL4_XZn%JZ{UygNQU zNO)knj_7`1g)kG8Z0~GCo9j>@Sl+p$fY4O|tsr(%3x4m(p)i2Gm-5li6Q0@D++u9W z5EAV8Jm8_g{nj+-uZjI5kOeEM6U;PSq!dp=SqLwF`_FkN)!*mi_|j;xG#~#%5}v9iT@G2jG;*ftu$ux5xrOjomX9zR_gkxrZc}Gu8|nIc|bZygqB3x(QnETi zRoA=Ok??juD zD9m9*=g>s|K$6g`KzjJd!V}~I4Fne$YAx0-BTd26y4z`4fTHiF??XPI0xGd^QNG_% z1Z5YJUvy4b5LNAih&i44$^0gdiFX?`U7QT8t zlgH8^>AO7~P3-2nxw^9N=Hx^2&*++puWCDYADY40z6KgtzB|U#`u_}TyDt4J_^Aqwa)fBV*K zK2V-bsK0`lrFc8p3a97DE~Ezooa37tlNI<-I1s8Cg>=G9yEX+GfY}W=^6jHAqHIo< zZV&Ofd)qqJH|l4DD;~!bKMuhb{xM#X&bR#Vj=jjQrJ_k3TpDB?bMZsn@vc(Hr)kJs z%&GBB4Lx*pJ{MLxw=e=54mXD!E9QbnBC)4l7MIYtk^>-fV+0F9w=gJ9b>5B+!_^CkJsZo z`!Mw!nW@1eOyOS}7!jey+beeiv81O}MX<@pbO(`jh585vEPpy$xxmkDs79!@Ni8=G z+LD=$L0t56{&L80_9XF_-DrCKWsdkFl>c)Xk&xHg3gh=uzKrdZDN=ApqhvHUOj~lh z=9yjtvX@TQ!|+R5zCA>fpd_zx(0Uo!6!x}?5w>rKpbK|xUQxT~Tbjr*^}Q;?Qeu6X)9r zd3z35<~+qh^5=#3h|)fL;POVF^j9>q2~;eYxI%#iu)qFR*5f;3ZaAIz6Uyv|{quvF z^d)HUsSQD1oL`9z%Lpkdp~ixZFU>}Aey`>&&Tyk04nm%6nzI(Z`%S9i zpp)(OGKKPk;+r#CF8DJ>&>5_2Qs%?T?~ACb;0G1lUFvjW^FLoBloF*<66lL+89ADG z868eeV3Zwx#+Acv5B*@*i1HW4kEea!w-iZ7YX51v%Wyl9bGQ#)%#9{oUFps!%PHL9 zJ{C2K0|3*nCj)E@HW?Lh%f|P83_6({K zHRowPbGwm-zm7I!e$16q7`~Tg8Vp;is@OqP9rucskIm-+is$~{*?8lB~R*?ck)>G@= zLpW%2Oi?JdwIBD+oC9c0RZXmo-G&Y*FeJ;4d>vb8J$7Fx3oAik_<~K5Gr*WbAU8tq zfC}+*YLyjM#Z(=i0Lh8K3Q~WWBw2BXo-1jeUJjYGWhF(2+f9Z{BWEe@v^9eM+3+rW zarpu+Bv`br`8lB#rjg(`e*`YmiTIcB)%B!qfblc4+j@&-FG^TYd%;agj1l7-e< zjeQXFO3)YxU}3^GaVK>tOWkeD_SVqq?om0RmNquulP3p3qGW()@AAw7G*h%osd(8k zN4j&%C+xI;YncE89!b8Hd6_N%y|WWJUbuc|hu~e`(v5v(>7IXiO5wi{C+H&#K3RLX z>*z*8$yGEVWga!&Ak2|=VFSFNT+|ab*M4C6kb>>|hAPr!0!)>Y&Aw_%n{S&me8&In zE!PwiMXf>gbv}A?yb!a@fqp1Y7mCXH)69(o++S&{X?NQqSPu57=2HXs9a1S_Dx8hC zT5>jWFL(mQi%#uQ7y48|QzQm|6!!UmST2D0+=*a7%v@bzr)m&jO!^R)#8J5YlOoV_ zI71mBh#o$1IM)Y*G_NnKK1`n}5B_LTujW;dZtI7ADOv%KYSh7fy!mnP56n7qDE)GF z@^+&Q5SKdI%|-i0^#m#axmh(mcJ^q!l6xTs^Q?Bf96-z|pnW32r|~f*D0eG|$t+nG=8DW_l4PDvs8+<>+MLLJV`HpiY)cS>nD>==vx!_rFJ-=p0pm(YJO0rFaI8K z9gh5?Z##r#T049xAP`)B=Mx6C#d!W;M}z>!3mpYR%42 zMM)J&He^iNDLCftruYv0*pBDiOgXebgE~$=8~Nzcn!aASyUP>vunaRX zRM=-5gREgG{(%xO#P%cSdYtZ)`HKHTZOd~*Jr{4F@NB-$yRE^^;akI znJijwW?`wwc0Lak2hSn5k@u!0s9%#dh%Hz5{lDM!+gmBiYR&?W07i(O*?X0Kpnobd z90sx_JGVc;NAB}=y+Wd3&84Xk0u`E-5cL;dU2zuaZbsjJRm9DNG2Hegj_=m1>I-v! zxn3bfkDOAP1~rGG(a}nZ?e>M-X>yB;wmFJQ+PUC)q{=ZHbV}yFao#0+)f>}ih3s!r zinsXjfZL5u)6PS8MOcN)4NLN`kL4vtR@;RewVA&Fs9V+-hVP8V`7+>y+Y42df}_e@ zP7)&n@_-}o&RT*X*dy1gz*n*)ZiT0su_-I^3yFr-yzdtz0!mKTGtvhWd&DZ*S&#!& zd>J-GqB1h-+QE8v&pMbRtNooj*Ygm{tfnnuduHU3xEF4m5J=E%sXkT+9z&B_Y!%f- zxfDOJ*vWZcRDs8@so$xN`sWwQj|8l?71DM;i8cSux$`cO{ExC+!uZ*5005QMXBN_g zsA~-3m)W>L3Lb!GJEKW$cSQ4#mU))d!k7!2r&tNfS2FdALEr-Q;37gMLzPC)(;$~x zPe86|bg+|l%6s3vQMy@rO|v3g@Z(Kfl1>DY)7LH+y*QKOey;3tkZuJ2A*Q)MlZ1_{ zGgfe-N#Fw|o*j;{PKG41WZOy6vzzAUp?s`6rUC%kq~zhs1Bub<^r1znrRcR{As(YPS7*le?f)L&7ZMK~^9Yn63vh zV>Q$A`Gsa3n7wTWx&+HKizWk&_A+)BJA2Z12@1I)@w-v{K+%7u#v4Otk&0C~SwMk|o{bW(=gRPi zcW`9O*tN;A+)*QAncIk(w2BlGn$rQ8d3Ea};>f(;4mS40Tc+@Xc^^_Yryhh#^Prl( z8Ae^&4>iQ8GFg3HD(E`6n|2A7=U6wiHBb`64=htN@{JfcQNMz}G9Ee5ryd}h@PEf% zczPalT-l=Zj-X89qdoqvIPzZ1jeu0tr3EDi~Ul z1HX#$4PhTVAzBr4CSIK>fv>RC;`%b<9~g_oFLq#XIPiXn#7H!Z0GsFN&mg!#b|Yth zEFmr{;MtsD-gMD!TP&R&rJe#Si3qobH`}8l=jS((o>lxywL@}R(70pKlZ;I)WdR!8 z{vO9sZAt5QK$g2RUETJ)cV*P-)ue#-OHBQQg&7~Ee-fn3&? z=wzVH-- z@Z^!nm&mlOo||{ha^Jv09C@AD2;{1io_?h#?6CXlbl_TN$ho*_klGZMuBm;?vzPhm zT8O9pI1ikbjMZ$0Yu;6**(<~s{+I2@K=e+gAT9-n`%+VTUr>j*;cVAfHN~>{jRN5!(jNN@39xtT3TB{x*s} z0aoYSynn7|ZCMj*@#1dr;C%y@M$t;qg&uZ*!F9wR1m*V6Zsh`zvggB`kHV8i*Kf3# zH&sJbpZN%FpL-Aq_3*aQE?{k}f7XEXOmOSs0!?In`Td2Pu z#{ev*!4c8RKIyDhrS1^rMD7xf>Xi*qVSjgJO&1uNjpr3UNI$ zNL{k$RPY|z&DA}X0Cne>>w#gZc8h%2FpqS!x`lPypvWRjXn;23F;JLdMsMFSr=iFl z=JfsOIJ*~XOmh~>P|Wzq5UTl*KDVt2U=8?QwzjfA$kWHaf-{}a_Rw+jy6N=V1}+bp z=>?ylo&ZqsfAjg7U4w`s1P4~_(-pDwOldPSQZDxB7rW`9*)yTTmy zo&jR2MU9Gn61YhP0b02ea(j99IuD~hpH!HAPq>S)#DaOn#0E65&;6WQn?5tCeR zVj~Bl(=8hKge1*zAfS}KJRu@XEJb0P!_%w=1SQv8Uj=Hha`_sqWL+(uE${7al9H{{1upFa-~gKhT8C;{|L z(tb@}5WNn;xi5K(flcsGt{;gMN31Kd*VM@KFvA}a>QHVJzUabWpOC700GO#Ix1O-d zzo)P+xdp%7_MWK)X2&E||HCj2iKVaW|Rs)6VsrcHW-^2u| z+H@Q#o3@MDn{!f>!%Xhg;9lJF@Z&{%p4n}VuIFNxMKlFnJm?Mkb5}pweLcnTbX)jt z7FkJ%n-n!1=miS>36JaKXbN0%1iPC?ZHh38&POY^@bLfAyY7P;jFBiGI9nspmzq+c z%4(vG9)8z!W5YKw_Gb8WiyJ7CLOj&Q&o4Qwxl>a?Ogx+fdk{pzXzkz#i#;)k7o4y@ z7aJhx!?gaX>mkHIDM$^$i&J6Y434uKlDY+Rs2$F!QLCmPQR%(;=Lm50Ijj_Cb|!9Y zK>Pjkuqyo15P=S;yAUi*A(wD9kb`N_BgFRyhrk5K_t}PHe=PKxL0ma zl4+|4ts@n7Y!sl;;~cQ+((r(MQ^%W`1#!;~4PH5nw|JRyF|@NM9N@{wq@Hxg=Arw|e3G^1cbw*z#R&dyqWyfveP)_Dh$ zdSKUzg1Cl4n3Ca{Nh_HDnM`Gx%KIbWvd;K@_N4tY&{G- zTGG`@y=InlSXQ?>gA3;cQq&(Mc8J`Y1{#W4N?srBXz-{WQHJ9oxL3=@x#lN@s&LEN zO!-lNqNl@L{MPg6n7y6?g>nI=m+!R@#W(fYVo|Agn75~UG|L_MA-|qh*xC9E*kmVp zFlqSr9^m@VgxtBUIBKfR0_>j!g<+nG@q|Q0DZ3wBV=Yd<24%H*@OD`b8UHM$lAV|+ z|E}7jGi*C}?XdS)YyuZ;Hk&<)5hhM;o%$-+mo-P$rSUaA%~KMqZ=ZSyC3c-B6eGY8 zS%vKf*43I89*);(cfb!GfWj>5B`q5d!Br8*W9M z5nWYp2H-JFpX#biAEyK}iF`3;m!qM8F^Iflmd$A%$m#Wa0|W67Z%#rU{nR|D;o>?a+)tlZXJg8_*4OIpg>Ph8|Fw8UKV|Mhe&(n% zG)B9i{++4Of-VxiSCx$CrO0B=7^#f|`7Fr7Bju=tqWA&}1ra&fX-+~fHT6|bKe#A^ zm!aK3Z8sc6A0r}krL)*C$@!LKac&Vs1i+XLUXk5~5%)4zD$SiO3}7qAoO3#_LphGY z2URS2z_UG+C2WOynmxkwEf$w|dDvo^m_F7Gc_jMre-|AM|E|Gqx3ANlyEbeLB0!-_ z-1kFJ_@i65Guc%~k0?ti?lf~sI_59p+un8O%IKW%$2REv7xYqaalA&nvd@j16M%XP z73Fh+ZsJE-*!HEEI}XX90Sf2u^GZxoGnQ%VmQSM{kU!&!Tdmi4RPE<>4+_27?taw{ zT77VO>##*zFL`^^-vw8laf!DP?$*8(Yr8U?u12vs@;&-{y&M5koraqdm3-M*) zzh^#J413-cq=n)r)~<+nJoFB_CgAhvbB(Y;G*tGe&`I{cYEH0glwA|$iN($Pb%`{w zYl%dEKfg+~v~2da_k)`Lrue_bfca%c%uWv!4jsl65U_vEHojHA%%iSUg%-0A&_s)W z43p8aPg1FXjjtfo{_W)M5_gGVIXES#w;IdMOxtg8X>9h|Y1xIjhra0>Uk3QhL+$F2 zPBRM5*JeO7Q}o0Qc~+@ll7bPH5)(ye#*gS^2X#)mlC!T5^ywb`VYe&7{fS`p8HbVY zI?Lx-(}|h{dS%tvvf=7!U4?af^8ZVtD4gBiu8og;JbwHkAW8C(jf}yX{%g`y$WBSt z1xuzUJ0b{qWbRfzWwF0Wunye1(Azd)PzTpW_D8atCP=L_Eq&d&+gU3{r9!A`hRQBy zw?4_c=FoXO++3^M()K{0~|qXeF(KjWaAw%MUi5V%4!8$gOsa^u~p<5 zZVPMdI?!;NYG}8)&dA2pJ(oV!_ZPw?($6f9F`fD6yEZ*nOCvo}FF@(5XcaM%Qr(yF z%K)*B4gS9TUb@{?ASiZq0A_S~^CcYJYkm%Awk*BQG*;Mq5?!jh9*jwBD)x(r41O=a z-$ySICqMhR$~I*!D;_cpC4vn>-Z|u|akR8)DRZbm^1zd_vQ6tDn5b}Lq&^fRR33vU zT#ZQ1NJQ+PJP?-`WgwgysuWs>`@W>XRrm|*bqME{9buNWypcMK95m-y6WPYxOt5u% z6=JT#hqgSv$x!^GwOap8u4G~JztZ8a#{fF^NsmfA2?DLAv}y;#NcKu90l)3=@~A{_ zcQV)Fd8|Jfh?9y4LZ+=G*5k8cy%MkUn@n_2Qjw!1;Oy_1h=BZ3KUAMmO(Qm;W$HYg z{mj7m-3W!#LvhnFAJ->hgDJoH z^Vo2N){b1YB`>MjkNwxXCzO!q-WTf6KKWno1%|zYel;%ih(sbYfZpQ1!ye0YAffT0 z5LUnC7}LLF3E(3edfDFk{rzg(9dx$D#-$yz!ALV>JcpiIN$GlZW-}P-BOYqY!?ltU za=AkISqawtzg)2X1*g}b-WQ#Q?O=+8r(-uLc9_beN&`V6Ee8{(8b6f~aCOI%sWO;v z=ZzVUcrczD-DN$%7eJS0dd~AO(8Usg_mAy(_Ta9Mvd1>JHWe9rQhU$C_IVUjH&J>FB?AVPWz z73REDXWi<}=7lDUNVa+1)$OnDQ}rFZ2676{29Z-&iE^(QaSt706bn49uQ_x)eh0n{ zwKPMo)_ZiIy&WFG1NtCZS;{rtQh@7^OYAYX3c&MC-Ju+)wh5W#0HnLX9X!Nxf-f6( z^(o7KrmK&N#nKieVFtFwD@nc1HJwW6P&ogzGDez|az1F`?fI#V`d%;~t;N!2vf(ua zLV^gkZ{3T=_W73EvO~#W!0WG$TL>pP)}o8>`6_kmFz>s%xm1m4HKS4CxA2iA`f1*1P;G7N~yvP-2-<75O>-OEocNj8@?|0%@7_muIx? zDM^9uYM%_Z7rA`OJ_*^Pv&(LS&-mNTxF)lwFK1?BVJTCJMf^pP6He!Eh)9L2o&M_O z1vQMsjPA{iYCYan>XV#=Tygu{nRA^!s!0*`Ua!CG6AATpT;lqq24VVUAr(_%PylJD zv3+8W!hym4#$#jQ^@e(ld~ZNDPB~$`mUe&UTFKzqF^pPUsfkkKe7)=B7~;@|zMz%> z``N-S(V^aUnun$KulQIwEt2jyUNLPjW?Ge;XkS^JUfB>flv}3{wi$+i228A(#*r}h zXzNk@b8h>uOEDry#sgurnRX~FNnl_BoSE}_V3}*GUA?st01>af@YXs-bd-5!TS!94 z-0elXU=8ekL~BjOx2g2ejy14rOIMpY$Mk84+O~HoQ`sKje5&u<*5@dzT8m|PYR%G9 za6_(Yh`xMsuGHB!WzuZuvcF=l>{=WBulkr>b?l1K)iAQ7=~7j3Wy0a!0Bjp7a#ZPE zjL^Eb&HR>h!`fUMI(K(j&Z#x!YF*p3b>LVQs2*i(-JGhhbc^oM9=U4mSS-IX#q-hL zcBsu-)VLVM^wFECDeVkjUK?@u;aRSBXuzsk+vfDZT{f^YL$2DK(QxUE+Fmnttm;@+ zf3C;vT6173%UWi5GR5fH1dbJpI-~6`Ox(IUY!$T{@boV1Ioh($2{p~IHZPCVn>t4f zt4%#Nw`|ls8e#>i9k{e+&S>4uqI+tNHPyCC ztQhX39_25OW({arxdd=AYs=E3L)-^G8AP6-2<~=V%^zm|I>cm|NAK=2kTQPEv)}u8 zbI~1rXi(O0!3k17RhSfKr^@RfW`>=FD4e=LrC9~-o?7t0ceg?PIXplaGv2`445SY- zsdC|iI(}VzCLal71cDIb`0T;p)W2Ea=caM=y)@!+X-%%}$0M$%g{WrEvhBcvFbi97 z>YY~-;4}(;+tSwmDxb~;A9O^zsO$e%Znd}jtd$S5YzAw)$9FD6CK~A4NboqOp3LU} z-?kk1*Fo0bOqMaBAN=8#E74oGD$tn>f({IwkJa4B(}=Hf0$AH_w2H3@mAv`)kB%)m z_FGRe9SR=I+5AUw-(lqz=+Wj<&&A6G)8T*VaLr7?{Q(*WUJK2FKvzWo%9)9fz=K>o z`h4*B**4gG>5lk&QY2?~frgu$P@5^OvF2N3iPT~adPe1gTp=hYEON-r$ArVsq1{d+ zM&*-YMXhE*%is6?zF1YGJv4zghNxz0*IwAD4IB|4GJ~%oaVA4KCS|Z z4cOW8-j%HS5xTYJqaPi5P`mYTx;8^CM=sz&%w&FWbBeLCv9bz?|20_7x$M{VP79)4 z-6WB0OH*&z_RH2F-~X+1hTX#q=!u8GchNQFOi_gF$d*MMyLf4%(RunE%)Q?*>yci| zA)~&40XF5gXOrb>Hf$_VvebbgV^Mr)!pbONV$4A(zM}oaFk%6y@=ge%`M)4qNLc)T zMpEm5tD(dAX^YL_dbSIn4uzFN$ybnL`LiyNa|s9DTj!q!Kkq%8h@rz22l6&ZP_hTe zj)xYjgN;e!N|!Lsq457=C_{!-{aZmO175TR3B(Ps6CX(im}f)Pc6a=m#3%Ef=oRV! zTNkz=*vn=aZp|EdAjZW$V*G_r2&#z`SdpO0J6bUOfQH2=8}7hYun&R@<79!!Ejce- zv4mtkN(WM1VR$Y2$jOUuCO@LSK8+e=ilH_Bn?NL=MCJnF%uF6UIRuRoErORJ>ij-0 z641fi5s54etVgckfPgW$7=pju#Im0=MBvdy0Hr#V6U99thb2`J%XE3A&}7O1wSxe8 z;pFBqF*P%{uw-LlWn<^yL(g_{Fwu+qP}n&KcWw?(7b? zLDdXE0krKbUy!<}izp~iu;ACa6|A&}DIMjIf3qu7n*KE>W|o^6#eIlieqqm*HXEl= zP8Ey|olLThB5|aC#SikBVIE{G1%#7NOyuz)&7Z5T9)Y+i=RX4w zr7#|TX>!#&OQR}4j2_e^8X4VXKt(ko$->}508w!0xWweTV8hU;NMT1M5J^o<7Ga~| zuW)FD#GewIy{Y>8g@S@;>gei344H3mb6Bjj8R&%xvs;%X$a5*1vZk*dOM;gz#!@;J z$Zf-T5n-Z|Z=iV|IkTppPKHc};h2aSD>?|s!Db+F)yh4326VV+1P=?daal;u$TQe# z01-tp3(~-tSIn#xjBHL8IV^1F)$kQIH>4lEYz=|QPZ4a)+4D=1JlrCQb;5*KGG0aLP6cbUzn_dq$50>zo1Yx;UQ7>fgw_G z#6T!iUNEpyD8WN8Fk;umN<7y~7|7x-mnT;YC$=ojFV1m9jNaHV<8z(tCe|-|PULLA zWx1soc`tts4;~Iw$)>m?a8wjDd_t(faJeCo2zV4!!6Pch5byvVB-Wu3NE9&9U{aMs zI7V{Yi*KeQCZ^!hsMEvrjBtu@0R73d{dhP`|7$VUxYMw2G!``o^|lxy3Sr;~J|zV9 zhm)`QTKC#|TA*Yzb>cH^K9*jCmCvZS)tKtW$zKrnQJH+X^*dU#pO4JtziZk!Ht%D5 zV}Z?NHNgXu!~A477Rs^hsJu9|hgB|_7HWNtMU1-|Dg8W+LJWAl9=L5^z+AgO3mub6 zIgDGd<_A7q?BZmMe#;Cjqgm0C2#LEwLm{H0bbR6QeZyc; z28NG@#RNqpeFuv}r8G=KAtFYh{uLdDl7)psiG+qB;L@7GzEu3=sF5ThKdwukw&^7dTdOPE z&BvhcbiE>VlN-DuwG(R+Uj7MN*(ci7%IG$^+D@H5!bF-guqZdCIM`P++%z<(-dr_n zZ*gaxRwX|ubC?T60++9nc`^6v=T}_U!)Oa=l+?=2RH66>P%X;1HT9jFA z;SuuN8Z_b#(dA=E>v8Nx^gm|3{qvM+p9pl#l;kWXb=C_5YC)yvPdia`eGOg4*}y z?T>{N$^tRRs9in=AWcgl0iTC&^=ASuV{?)uad3Jd-Rr~^HSdYww-XmrwuXgO%?mho zge2a)w3krk#54J2WkEjAz6Z1)2bUJ}%_C8-Aa)9s+pyze1r|P!LU>=XER8f@#PMY;X;cbw zq~MHxOilcg?xGQ}UKC;9Ba06(mY%s6bd6D!H(*J4A}#&)#Joy7*cue3+exypO=dHe z^13)m=b1td?=dSDK)Qu#;;Yl}OdfP$0Z6cj@Z}mQ9y>ZMux?UurWik_XM2D@F}LX~ zGs>3|l@i$ibZm6c`1bE0Uzz>PR(YeV&Q?Tz7!PrbAFs9E1f|E6o87xoRV!C0=v90P z2zXd`Em5b7kLWFLFGM3X=|8Y$RA|219hphE_{>>o5YhE2%!uQf|}5H3~cIH6#4rp_|{42Ss{yWL*)j;SYw7ssN% z3@}bs82ZB`_YrkpX{C{Mc1KC9IBD5=$dvtS_*p~vXDGA(FPkG%yT%+5ntoibdreh) zM>KUf*G_txeyxDdwGT^Kg_NHxUk(RH8IA7umhUnFTz%Woh6x3Q7MW#OE&Cj_SM9X0owAZ^v#h_EO;HSWNcKPUf1gtm9oq+-;~6 z3vb>?p8J(GDE_;Qw6{Nu2d|qdoiY^ziw27E+HGdtjbmBjHM^EK*X~ykA?ME(3c&Et zg2M-OpMb)egS<>-wdu%m0>>t4gc0i=c4{X9#L?g>bYfJ+(`db6+srqhuNV9EYcKxN zWlati<>-?+Ey{EGm#VsZN}95XZFS3R^-50tx^v7?DThxy7hQq4o^w;+U99rty=#fM zzeYwuK}0mC{RmySz5Li_nR}Uw9+2wp*}nK2lm1#=c?6I3^lBx-MBB2eZE}Y~x#}?a>YrB0LWu~a*Z|}e z5|x%Ivy-5rf!;jskz=0iYK^7sJZ7!!HrRPUpMFB5wJ*=2;#cS-|odh>0wj)H5t2 zYp{A!~%v7jwQeEZEsn8w+@U`2P#f};j>#@Ye}6* zaaK|J>9Si%rPCOmQt306wyo9~lGqiwqoE2D;?3cU{ourV&)=2hrEt;$B5h@uoKUfX zcVbYIt|p0xbFs|CIdzRq%E>LBatx4z{z^w8pP8XtRVC2U;nOE=Vg($P45O2m4l`?P zG2nBom|@DSVBHJe0}JL;)bvJGeg;c4A89p+cv+N&I|@z4SBJ9t>ITE?^lM|+(*g&l z>gxyROvP7K4$3P+%p+n|&F_6Gbq=U>x0Z-jInXnPQiiOL;Hl}zbU>8(v=1sWj&@D>Y7#@{CvN)YWnp?m1kw5h5v+YP z8&on)Z5TNpn-!fkb$ODHOV9FkxSvO9{f-CvAyK&rV0xNEEuT#)^zlea(VdatOH;^F ziC28tF)pBC#7lg4AWE02S@?wBQWAmyY>Arq__Y3hfA*5}e3o8_thlPwAy6-aO^~@S za0>uAlnIkxW1{!fzT;g$jkmFJZNPAxzS)Ad(xhOvIZGs0%Ol?Ew4`9yb@mgzUtV>U zh6Vs;xd@m>is#lkKRSU3xbeE5)muU~(sM-^m)b{tSMJR+%~F%^t*oXl<_jRG4! ziGJHg`Mu7Ll;8U97JOu(FQ%y2z%lNyKC{_-TcQixd%%Qu=G;+G(z8yckF$@{bB>gE zyw7eaf1E0Zou#^KntN#;+Go^TUjUe6N1ZD`7ML~;jL00#G68(lu-j#kLNkttYmArB zT-T(nvC%x@Tkg~o_42vxd@c;A^`d_T!XQndPwmW$f33#><63L5 zZnDpFkcQ$fB$6~vZ|z<$&ikKvvG!MF#(Cn8G}8vx*}FR_J0?0$)_>XhT8j=K9kp}- z*&;~z0c}mZ5f9>t5{*bHgNInejVTrdJJD~EBy~=Be{#^Tc_#Fc{%j;F9gS{RNZI#d z#+PL$^XOG8cEHNRY2X^Ww7Xb{`rgR}DRLm+oBwP}hg^)Hv~Uf3rSZ#;-MCBJ0W#)t zqC(xxmthF+Q2~PiRPt2DpnRAKoX!FzS?blIy^1E}V;&JH&I-cuIGF@eDDa{`N?XDQ zhDyXMdd-XL6VLNeB?M#s$g>?u@HI9>_0e1BE6I?L#fQ3iP)M}uMDCge-NhaI^21&U67X9gREpdN)D{;>!MCFt3J z7?DZ6iLUXmo&1_+8-&(L)R6qsNKVBIOP=0wJ;f7|s#WOc#uyfd2B^3Zv`a&KhIwJkA)VyPh8By5m?^0%;I}r% z1O}8y(M3d?kZA+B)oc|>@_hl0UQ`pc6!^1YTM`eO3vnx;}CKvH*Yt*;ml#w0qMb z*SM=RYkgo;vPKB?`MtG>EznGuC{bIlx#C25&qCw#CvP*)rN_oFkp4CgG$~|J47CGG zwx6&X^g{E?V+hM_twb$RFllScRs^3WQ_WV5Tt#0XEd8R<7sm+o5gY_Ih%{kkv&-K z6&bSQZD~c zCER40bSW%3I-d;a{)=a>S7mx28>3tQ>}{3HDnL6RG~7|#Kg6%COZLR}Y9b$tsQk$@ z#LD)T!0#W%p0hBqyT6uU1TOn8kqpY3_;DE;TwJ=G>&i1Pym^k~(LQ9Dm=M}^WJZa> zqw99{nO&VqYHka|LT4*0z{rmFB);vx@q#h33)*gVmEjN2kq)b&dG=au6v|u|)sj8N z=T`6Q(ZdLJB-;iQIa5|n?ip!DLfLt-wUatY77A~3euHvI2q+82w>gSpqb7l(@kRJL z9qpEw{%84nl_VtfN#(0LyFz~lk(C-*ry*~cc4f6$J7f&6et~FH=C?~@_d6ChC{7y` z+1BdNS#Aw*+ix_CCwO^iTR9a}_`x}7ZNDxa|9qyybeD=67__(H>T+&y2#>|7ltx`j zNae{pmuXO0p)$l6|912Y7(=)@?!tDP;1oimC zo3+!EQJ^vC?5#NimRugsm&ygPv*qcJ>83UE$RP%>iB1=xz&)G>lMAO<_M#cuR`B=ykNyLm6;<+@@x zp376f;--G&y=N_HF1^mWX);>cy@-vl6{#0dlq0tMp(1CiT=A0%F{x9KkCxvw3SAJSnuuZ4`_8j@40z6P4reVGBDIzWbs1+qH|EHAf6j%io73EfpKT0T*$)%Lo5xT zN`h&?zr02Nk$>`n`K&Fs$1!*1x&k|Q3i}-jhwbo%s2VI;%yl-y`SKxnMk2m@>OvFkB4h%>pD1yB`C)twAf=jo3RBBBXCIIpQ6H+ z)k(SSCsW3jU}*I4(Gha2Oz=Vx=!9T;H1H(?SJ}P@-;;BB;sxQN<#La|UqnWeq{rH~ zQ$v-ocfyXu{-vhduPY;9@XevXc|(Y4RVWxrZ^=c2y$Bz3yjW8CuXi&Fm9?-3=EqK7 ziqWQ1L(M;=Jxy${V5y?@&a6w5q^_vm{xv#gJslbx>-F_1Q^FoHr;HQpe2e(X>HWh0 zCtCXuzZbGW8b$_!)3g2ct6*ry0W*peUfDW@bk{<_?_+@k5|!Ui7Y{g31eWnGngZDNb(od-(yd?LUOS*|*_}czb`M zJS77s@u!6kqvsPQnq!ne+#rH?vgsp>^vX`b9RenNR+mnMfrjY@7scPS^}Oo~z>w_V z;lhT(hQr8d!}EEIkEPeK@jh}W25v&xTifJyMzhh%G(cvwgGyl@u{;%^oU1x1a2Z&Y zem=qn_zfCJoWiMVG?2hc~ z?vcxkU?W}Y);7qy6to6@VUO2ZiMhVd8on7LOLL@!shY}(&jJAD!U0nISsl#V+@gHA z3RW+FaIS}Yp?(A#>gdHvcjO6uaP|HkIsKiw3gkl7kfgbcl=O52-O$w%#6ux*lXW-4EbEDRN{qYZ4Ys!L00jZhcy*0N^kASyfew=~H_6fDYLwAjsZ zeh&;C*F&CMKPZ80t?WdQk}OpaiTJx#FZIHsp2s|jPdOSJrFsxEkefY9T%Yvhq87HzdHjjw3MZrv0ju!y8p*Z+(UZhRuZ~8RD7W6Dp zUw-_S)?hO#e=#LAJM-kX?5oruy_BaIdvn&?4?{g5fb@BU_L}um^lL(I$7yw^xtPUjhn@ps#!{-&Z)ryW<;i-{!NQ&ZUj{m>V%?iI*?{ln44-GB#FQ9 zuG&+T^ZEdqA?#5@S$cB?&P)iPg>=q_)4Q{Ukc?`uoLI-V=hK!J1B3g|pw-3|$#>f) zr{Lg)P;d9_!W@4D3?@BIp1a+?8tpeIXXp$l!La3l{@vjHE(`6pb_cNl9xjgts-HBB zkqG^HHfV2I;+goxLrb-(@eNp?RaPoAx7MOE+Teg)sopc&{%P4LmAk^gm%$GIS5Km# zW3v-3RaEr&Dn=P7)q>H`tG#wEgTmypK(S~{+>3sF@KPF%yodo^ z;EeS}6%k%Hi=KG5LNqP25ZA9&Ia*?!sTX=sFIFH{$)1JH&=;^@Ad&SE>) zBo8oYMkR4r2c=~Mu``AhFFp2g6~Gry6if zSL(IT@j98!^Bkp4uOx6LY@JhVTCrTI_H;dQA) zw$^u+WXQ_Fbx4TqFk-c@HLD!wchKmd!nNx`qZ!wANOMXrXmc9NX~x%BLQb#Cv0jbg zSl4A;p)9NC+tzI#*JE9QW~e7%HwWtcvgew%Wa|+WkgEd%tMTUH@v3rIZaok@VJg#>JX)#EbTC1EEU3ni!f4fOMi9cX}PD9 z+QoQl)B*OeFd1gIK-m2M-a{hvl(zM_0io(V;_)H7EsO%evx@wH1qMSU?i2w2vJdF7 zhwU7|_%po$R7;-p9ywmfeJB~N>5wjucU9FgdKhqw%0njgvVB=Vu#^7@K zqRmZoSBkEXZ}g1>ny0Ay*iPbyCeu82q%J}}$uKnEl*g%unkFy#B?jgx*)Wl0hC!=3 zvU~;OKVYQc`iN{qT1p1Fv@BuHMNFz^nhm<<0ul)4ux zC=yP__(BsdNq}~VQo9IHL&Hsa00kg>c{m={W>lMOg#_BNG@O2WSzmKewJ+DHejZ17R*0`9j(P30Nk$*k0)V-h(ZP?-xJYS z3gyeIwKD<`>J_J<7c>uUf15%z^rv7B2ey=L2?Ly)K>^@6=Wplq;=QVU6KT;fDnOr?P4BO2+ zsU+SHGWGOQ70c*7@AIeFHeOfpb|B$VB7vR6VgLVqUrdaxb!D!Pm!z~?tbE3vR)=lx zqrIYo7a6c2;ikrkAtMQ+qS-h&L>LyJ)|R}}yXN|vkOEW9`MwTIrkkES-kF{p6L>wA zN+$mPX>-~mqbRaH5v?q1{0Se3IVT_=8rDQN(&by2KN^aA|izR5Cjov z28seZ5{(*0gnxj%up@CK@g-?OH7DspZ9^SE-GpsMWmO|hfYBs}nZuX0Iurl)*h)c_ zVvE4V`YgutNn#A?{vT}!+Ju+_P`insiNGEe)|MK~~NJuw$EW^%O=N zQLK3yIEmtr6`yc~7PcW7xeyv|+WeqB1#Zb$vT;^<*wv`q0}yhRy3rx-ry;X&o}@FF zzJ3ZKa?=@j+p`(3(n{9sp?f=6+kX{4mW3Uy3j)6v@2EgBvkmhH9CVPTcwhD7yW z{oo^)vW&P}8~~L<2yv6(qQ1SxMBO>I4K#3+r1@`VR?BLAix`Ep zBfKc0r1vZTQ;KfukR?vQiKVf+w?oZjQ2kaI!FSs*D5O0QF%vZ0G zumEm=@5;zp#d+(f0;d2LEuqkf1|$5oqugJMg~dE+oGN_X21u?FV%z?=i=O<>lqw>a zQHsEZ$=?kX=gy$&1>4gP-VaXHs3ZbWBs1nC9LGshXHGUdl1IV7KrmWs zdXXWPRpv78B3;79eG}z%~?QN5Mg48~VwrPRh0DIthWrL{6fN>2w(tuO z!T6s{5z>^mFMkgiMlf)l*I4P@x3V*6GN=<5Q5E!Fs^{WOLxOTS$~@^f$mD!RxOLV$ zOW0d?R(Ihe8!Q8G{U8|f=~BWj5r!X+#K|%rNi+lV(gN`kq*CpKgH)#S0@WoYCBGBM zrygu20^JfBP*4_j7wYQ#HL=GYe)9z(>G?&)MKOj~N5CVmU%MM?Dp!Dc13kVuD4#4U zm)zVnhuFfB5|v4@hw_27qZ~!PrMdhxN=IFbipLKgo*|YQKG6`$9F#PPl&IQVK54nS zRVM`(3>SPbwJZ0cpR>PW7iSAca{J;Pz@Cv0tQ$KF;=r&dQc5=+f%)P>mXICVN;6Fn zjg-kYp1+zBH`6Xvy8H8bPJo9$SE-llw&0=3sjF+s!j8a$BE)dv%4ieHKoC$zu;|6V zOcQA3;z~2vqCny?As}%$vWS?Nn3PG7oE$%6SO==ZcJ#u_@OaRn!>xpS24o&lqz4tS zPBt>vKYH7iX@t3hBvh54*mxuS@9PgoBwk+j-K9@-f1>i}3 zO_Jz5WE0)IP{*hKE?bZh=n&CQrev9=lS*;=vj0}SmRmUhk_ze87{{YkXr+smD*6eoxev@920o@fEACP`U) z+*xLM2I~@3=;=zjxhCfIS6V1zvgo`OCItW@y8FoH5hZ{Q3K>Nv149_*kkQ+ZXZIBC3dV)Xh z+e{6wtq+5k4*5kd;Qc%CP+}Fx&m8*FhRuibbw9@UkmU8!T71wLVcTnryZEijmk6zB zr~H>7Y<7q2=93mPX^%ca(kFm%qz-wX(yR(yshtPY9^9&`ePnqy^blU(h>Lmvgp0DZk`_Mk zLd$+7#aD0?cC2l9ky;jqVhPnjpKZ#a_qsw6JO5F*yvJe*)@ z!%ahpD80|K{)Mg{>>MsuugVpVrHp_-o?)BUk&KO^Szg>40-19tUXA(rFL1c#)*PWY>X->&W$y~^Oq0)%>vMp3IsWHbgW zr14+~fsN6BtCY#yAc^M1gvsl~i8IVGU^p<6=PrW3{u5arNBWL7KOF4C)7|C{MKKrI zP(%&p3n7h{8)Io6K=fUU&fHOClVEYL+$z`kBiA*PGM}h_FiyIR-t>m}dJ>enL;>jy z;fvnpK%ZpCIEo8++9hP0y(3ZB=}z0%6dQCwA2H1C4z?ld)xXxE+!@qv&XapiA*1z> zKkm;*>~#>NIuwUdUPWS4Bck>yD@z#CRx~&AWIxAf7${)F;ohPo?`Mcv z;{CiS`tKO7{UT&wPe!KFwRFP4%FUtb&dsf6V!e7@$d4M}?6eSF`_u3`{CR+%+ud_y zhh${VCkENI=U=pA&Ky16XIA!(;ttyO`RU$}!LX*IZH?TV-+A%hw^WDA-D1ME{5?6` za;}GWW)h#X=(Vr3Z$3~>opp43^kWhcp>o9=*lByM?k2#toaPRVj)t@og|Xa!y!8Kr zM%u8pK%ET;_?L!8O`ar$9ucY0-jo@aeLCe8`V-tKqduWxr2FZM_<+s#qgLLW5e6LK zsF~l36#sA%Q-4gBok22+HBwwTjpC!IWbSsQ6B>KC;;C}?Vafl0Bb3A)jJqH=INuemV8vp@F=jwyCriE zZIhcwnI6a3P)-?S#8*34n$vI9q_!KdPBnA8V4+&ye&;vFwgOhSso6&OMj3A3PZN5G zWM<_*r=#C$Q^gy^HB@5|)#Yb~UKc-JtJnohxqtEp_``H|=cm#mpXJwPgs|f2XT!l3 zxb`dJRAjeQm10&!uO(cYT#_|Fr0bo^GyXuzxLCPT|-c$^U$2EzS`O;o|lxc#(?tc}^>?8))dmUd(VyccE zEN89oiU+NkonbS7^hwk5k9;2ayB`1??_}vR6fL&Qs5j--I3(70zT?lkDSo)`C|8W9 ze~3<=tNBj+%`*+SGm6<%vqVB#KTZD*(~xV1F4IIx;5rPCGSf#79xHAci=|xa{yJ;^n~n0)i$7$*^(HptQ|(Ezib=!hz(>e5-))wAM42gGI`>ot z0}q{j?Pz8AzTTpysW)@v;}kOx)v#@cO5G2?gQdxk0+3Mm^k3idZ_UHPnF=S=ioqr6*)1x0D_VAcUYkJzErkuhNGPfU(zkPC z&QBmrXRHi&GH-MED)yHmHQ8^>YUCVOHt^ywJqp;-J{Mgt*KE+csmXJ zbkUO(y@^?^@+4|^mQVz!U!Pv5{~_Ga{rAY zw3HhHkSE7r3%{;isuDnz=|=)oG& z&bx;^wRCEP^rMzXjWjrua26;xaqbl8R3 z394u2Y62Z$2kA1zEOBQsLZzc|kin6XF8#STmw!%o6C#!j=s@kil>j~#TZ1mGNZxm% zi|)ejt^Uig1&%}`3`kz?Bo1)w^}~hyJi? z_3E`h?5^6oyP^l8zX<}MYX0#7t~p#@2Z?_Sf=e?_^_|hGr3ifXSs2V@I=!vQJq&Ft zVbkF8i|BEWGq&;hCu3-6ARN*bm<5<2Sk}VT4u$oWsQN^8OSd%aH$G?b4G+ucpwj9c zyN4ze_(%Ie?l&yb;@(?#DTD^nfGp}8&y?3t{33GFgM9vMFEuV;olybr-B?s?h`iv+ zf?A5Dx5XhG{%igkAt6xZy~)oi)2t-cK6;@_$o_~@nKzDQ>ijLT{c@SBZBK;L;wWM4 zi`i|*u?^~}qYpJLNDPriSt!{vj~QCqB@Z1$^nK6$?_iPU2_ui9ZQL1zsw zdo7*JQ4kCn=4XJS;Z;`%a$%ncct9U0tSdMo0^@Aow>B{3YvL?Wa6)|C=+G}HNa)*m z5KtsAA#iZ`_k4$PU(`597~^K+Bc5{HWr8C%Nq(YG%^QD!SZF{5JO~1=OAP(?MwPAF zUL09QC}^i5Mo;f5IS{HAst8mF#MV)9_GWk19|Y0U5*ra2A$V}$GY|rKkN_Wzyp82@ zYg3Azp8kw{4>ln&zFfOJny1ciHcwI7mD!oy+meOceI;nv@V7grUJ5DI0FDR~iW2wW zBLy|Wa{C9c1V(YFnTOw_EO-+_X{spHsisbj5C-Sc%P0`&UtLE^;MjoJW{8OeMG-V%pfyEFd}9noE4vXGL6A?ZOlsYd#BL=!V#;H_ z6GEXQT_~Q##`rw|JIe#qNEHRmPbw1BC@BH*Q42XbSOsz~19ICR0trqx;9clBkg1_h za@P?6F$x9+!Nn4s-6g{B2M3qv11;z$2>=u514V=znOn6Qah75_0#7qvdP}69rZj62!HmfLD za$H%twdx47co3Ap3iP_!gfPasw(wn!qb2~(CY~!v^%L8y3um}g3YuwCmQ?erSD09F z2X{(DjW;#fqxeWmW&i~Zr(seqhC=$^e$HNZkt)n-zZ%O+noWuzuNGC&<(lEf`DRo~ zmLuWAmZy{Bu)^q^sP=E*noCk!;I@YAF_$on`7pK3erqNce*BSRG2se19g1PanXUrp zLRZ3}CAcl((_m&KZ)sl_++FO58&PP_C!hAC6X-FOcqDYRt*s+@Y@yUXnUtWUxSR$mYTexw%XOpeCkF2{ll?k?D=>peij@H2P;-AQfxwBtd_r| z(xFq0X5Hb+{40tlsG`Etc>3`8N>T~PY{As1TFXGFpo(iwLSY>`tk71jRA%xpO6<+1 z7bcey!B50BFT_-{iti)2uJwz5^&3yHuQ;!88{bWdN$kfGFOZJ1R;!Ucve@>i)0E?- z#eMlpNT-fI6Dj`Q4IQ;K)L4+<_ov^O?Y7F&wCk^7eTJtx>%bpA%_@o<%$RWCKjf$X zA*1^K51D-DfAsqQL%IGB#pi!0MgKW6|38#tJ=5@Pv*Ky5@ZRBX0~(yu0f+>eQ$mpb z79rK?7SZ-T-&7dL+l}AR5A+-9nEoY%6hvWihNL`;q zDBJD)mVN3Lv-?U}eWGFwQ|_Lt3E;zTG=Z9Nj4tnHCLtq3)5bX|KNdpQc`-X|G7@i!*d|;_*r?Nd5u(`TPEy!|A0$vb`yci<>D9WKK_!*a1sPnzN^}0 z$K=Yf@l`SsSzcVP*i=nYIGJ%Z$D?5W(D>Tqf$E{o@onmPQpXe>!#g?tJG`zO7sHMd8 zy%~=Xg9opLJ@jeiNj&UH!Xs4ugk9XiqJfK(-Gy0CAdo}ve&BK;b=gvVQ<9Z_nm#u2 zX$fI|qAr80*^Zz%Fp>>H1yHlHsc>zgv$EdiilI@Uu1KDbb0~l-z#(766JgQRRU`APeHAXcJk`aA%b#KA(^!}0ne-aQ`uuNdsGfXeYlUiO_RHL3{y zhilS19lQC_KUmvUaw6QY#KgzZ%nGK*x_Xx74OI~f!FAf5L^}m69#L%6q+xLF{pW0y z9V9(FA|VeYRAQ0jyhTdNsMJB$(&X_HM>!55q(?5dvsd^u>&kr8S<{H zZ?|eL4fholGzZPDK$&vFxqOTIh3chfTg$U8ji;^^30!^#~1k1e*DuM4=RdILt|r;sj0D|z3T3x6PMT=_VL&;?HEl;^mN|4 zp6d0BPd=j*dQI#jUva?n)U@`RRIz5t<6?=5!n`KFH^p~UpomwTl@w3T)GTg`msix= z#l=mHE0pJ8MEuoEyhhrpG*Y$catnc^L7r(La8;`enoh2p6FolGxIX9WB@N?>bSb9*Q)`)I~i^?t4LdO>|nMj3T} zTKukFL@X*Rz-7|TRKZv~bYng#-xN;gX6EMNsO|x2q1IX&I=8mCOIpb)l86&QX+}zW zVbW^48a@(1SXy~02{A4!ZW{g`oYoB!X``>@euYPk!Jqw?lnFs%dLQ?rtpYVCy?`#T zjeU^=2zEq?d&Nyao5@LCBT`|9o8+YXf_mQGHL_p03I6sWE}>-}IU5z~`nE&J}w+@bF3MH<;54jm_k zk(z-y&x zDZZx!&_xak!Wa_=3!wxDU_%L^KnS6P3ZcL-8zxbVgMxc}TQIW>?v|Wp!Ky55j@lIe zJvs2kkAU;zzVLqJxaUkGrqUKJwTtzvZK8yeItv%=aFO%Rh)F3Z_ZTWcfiY9#cSxQP z9kGrGh!acyX<>Y#h$f-VMUuC@Vf$TBT`_yfGw6Y3i7<0GQXVg>Pxk zskXe5iZ{*Z=JfzbP9`ZO$B!?8gqfM?9#-bi)s{x!aXUti`9`I z#McO6<=>mW<`lUi#c7Ab=P@^bRd4{B@sRcRRFcI)SI#HT;Ji@%$ltSD>q14P=OStt zKLy%}K#afY_LXJ2&$XD9N`5P#R`y#xz>?Jph;Dhn>^rc=d??KoJDj$)&B&_a1eNf{ zuTwPB{8&78#%Z&{V=0G7{j@3GrcFGBsqz2KLxrSA;5~`|10MV13<}k1UVnjJNgEGb z^)9Xcl^{ojvJ`$3)WrzV^9e9EY8Vn-c-q#1JS$+IY1M8VPs%z4VVHAQ> z9qM+~!4Sqlfa@C9jp_s$G9eO=RiWoIJQ_-V#KF0$g;jz|s~ZIolq#9ZkgrVO#^!Yi z(uxgreM>N9~6E!Q(iU%G~ zgT#-{@M(2`4Zjq7*mJ=6A-*zxJYHS6vwG#5C?3V(J$Z;au;gF;$?(zXe|EIBp7%4~ z85(3`@Mk2??XKIE9^-F0fV*Mm<~5|!Wj5M`IqJddlyfzoNF6@g;yG;^>uk*8DN{s( z?`E}-LQ(HD?RN4|I5NHt64P(QIArms9P7|RQ<=NpZMk+|$!ssX9o{VdH|Ov0XF&=F zZCowLr=+ntP^Xt79m(+3c}o7`;6mS&h;g|yxx_WHeKv$_LUGAlfY~}?8zg&QQtOV&(G5e z@E{|)$hMUbQDFF+PfHw%Cv~~}27hRTW2iYkZhQSGWVn?g@12?O|4Jy>E%50engK&M z+a_3}r2jpDwu53y4M4^`89E+4J~+fT%C=Iq8(4AB!iy|)1qtN}SPZ*QmnGHhK`@Ov zxfCm({PFJR-|QpBCP8_+Wc9eS2F1t$<{q*qs%Ol=<2;zq59-KT({dudI{G;Y4%N!0HAtcCkX2h23I90DH%Md z&|V!s`$%Axls?8J@&A;k9~w?;Y*z*C7PKdi!y@&!)UN}7*v|($ZX+L)LFMiahx`$B zk7@TNaz&j&G&+P(%z9!rtqcFogGzQnJ!_+ZWJCXB zRMIz}>=qec7(foaiId#e@%}|mf(nh9Wz@;xm&BWj58YCJXovNzNYgUJZRERrm%S-3xo3wrG;51VD z$;FhOF_5_oRQ%djGG&$x{@R0NgSn!4(SspzQU~3F1JpcL+a-+F3lqsW#hq5IqcJf_ zpFw3Yp!?E0NUD3Y1>wm2c>$rQ(L3^ESV`DjI0DI!`ckgvAN2Q{2CIaSEJkh;AQQ0h zb&Dm!K)S!)6)Abdp%7Ze<#Mf5y6SS4@l3x4iBKM2s!%XzMv@WBAyqpvH4*DPRk)rH zzDnhMJ?I_Ew{nJba(H3spZi-}6=X4L@S*ALR#85Z9_k)lyDQ!q*44D-P`KN-_LdfR zgsEJgHT!;xRIEWG+EodBTya;=O@x<)8GRw*005pA!Icoirvg@YpVziWe^_%>M#kLE ztf-sRd&^>XtAI)e`^)Z2X@rCg+RJly!8k4V8~yT2IrcFF3esV`(g8W;eG8!wk{K6? zx?3g<+*Achq@nod(X_$Osqd^OH~Q(nuJsVP?rl4TT?V>g{hd4(I2a)2=7f8QWyTi} z0eizV(K%X`@1zu=W?dQ-4|q&4Qs%4h`U!|8!Y8ho1U3}YlidEG8m@n%7JlB$LlA=` z{%rIVo{f^}tSc*_+48s}KKEk`p#`hQsK$dG<)3ILTNh>0QA&a{xRpxS&|i5qtg34A zg=deAVGlTWHaJrI{$UbKW{#0=t#HhL0rdEKqcclhQ3q(PC71<{RjN)03WT>OPB_~_Iwp`JcPcVfnB+NGX1%tXAY!{Jy^|cq*YUzTvu*;L{GVdwJ}Ke5^Ncs%;;Yi zoH;1#WYXqPBXTzz{7May*Y0gBUCoz~Rh(OlZa|9-^?FkVrPcM#)|A~lS0yZ(1Y$#F zenRhvD&EeSU->VouFq`mO(O=XYnabw>sb3}OG%EbF;7t2fy~xe8<4ZuU^(K@UH+vx z7Sv>>4^@eb0B{R0mr|k4&IYmGXVhYscW7C81ufPugkeF2B*ZVY^q_@6DPR1xlgiSF zE{Fbxvo|(mrtYRRGiJ$wP>Q(W1FmpcKSL%L0w-6mJT?WZlwC$4FFdu3kL5@E{>)w3 z)zTX=T2U>mftjk>k{JGC301M=bT@ZF&{r$~d(NZWqehvLy3KSdR_FZkqgfiXixqv> z4M*On&~163yZGzH*da6AQl1wgyhe68uh=NMuoO7bs(p4?(Oq~J{*0u_2`H&Lyv{|* z@dinV0L*o~ML2|w$*BC)AZ58O=~SIx7fqnaEV)xQ-y6_Vsxr}o65e3n#3TV!7hB7n z@GpovB{r)C0`F2$Kl;W`EJDUp**{8Rh?2QOXi!JCCIx>4CB<}hV54A2=`~~`M35qm zt6K~`JKgUZrH7`u0|hO&0kS|N4l+1kLQzpca_Q1wk})aCG+?xNhH8l0ug2T8QLj@7 zj$;v%AlU)G_sQ!T+59*+A78Oz1?wpZ6fB@<7=AtR%d1$2e4gi4`rCx8TuCz>ANMu>M_Af#p z0d`_5KR+0QEaV&_?3b7QxXAiBPs*8Iash#F{`^T5$TIo8fc(tKcaV?b-}?D8sZm@( zae{C?ay?$H%G=Iz&&Uk`gW42-@8zx1MXs$P^}r0r3;`s?xYmfWgz@aa@6-wc%BZQ* zm8X@J>Y%}%)PQaGWZg`R?M&xSUbE3VQ@d!w+hU=)glj7cx+8HdlcOXxd;G;XxcfJ&#p(!HNpI|=kgz1TBk`!Mts7CgU4v7X3Kpu$+t7CZSpGSCLa1Hd z&kRy03(7UhE)T^+=E{xX32u=0{g}0@EoSyDEf?l7G|R+EXPn7VvD^9bgu#rM9YZ9a zJ<)cCVXpCjSAkouO~pmika)ES{rXY{=&)T-+<>j-YuR9BCSveR0hP}uT%@!tjs#^U zlrwK=hT9h|q-(?_8&|rPpj(7mYEf&+?P$?GS2o}5FIhh%?q|d|v|?0YtQ9@T*u^9P z@3r-;IyQL|a)CPH4mv1{Lk*e9VeMhg_%+0d1M~g|E#@EwL$h>yAU%md${H09@G(mN zm<97-yxt0!9>Yj{#sZnTPJXH)=V!vxmu>$^l&kn#KFd0{nWhEOcPy7Q;&%wgz*G=at#J8X+L4@9~2@U7F>4^7tds<5Z6oB0-S1#^N2j}5sOCf7xs zpzAlIc{^nBX>Uu9*3Jf}({e3n;G=b~Xa_2`i|zE<@0atzTv;RGy2qf*1`=<&+w6`V z$I+K;nK{T zR`RHbHk=^Jao?VCrmM~Vw^`)iy)fVGG-@IFz}QV(q7^$xaumcQqr_i~IoX%5bt4eH zKA(gFe>5>#6}mzV$tcbYF6eW~;D)A&6C}@kZ;hq#A<;mt71kD@uSUQ*;`pP%YbSWMN3UCai6f_}?O9Aa@G-8s%_hnItV?v1a1hudROKx_{ zxi+v#s@rumun^R+?Xv6{v01Ro3zX1{Xhz;-w2rfiQN%hICo*CV-Crm7iI$7@>mlOh4id2OvcRwy@IvWcE?{~D5a!+Fe z|F!@)k5rBs(;xZ>r#+K0z<~5d5S52jGsprrZcbxBT@LW;hQc0LeM*d^24iVn#J~K& z3%O{Vk8v2O*9zb?4(Ds|J(v}yrp($*m-U& zYjQ>6H(J|PmQEVP5!vc%_=(cR*;YEWajwGygpy8NW!LX(RFX#Z%Zdeq`#$fU2w?h& zSNoW%k+ZPknw9lCVPGT|)vbUEL4`{a2LuOWdID(vf>bENBoHY5xu{eS*ur@ziZG)7 z{`et%A;Hg}C%!~}MEm@nM7BiUM6N_A#{TiF{+rNv8OwbeP!-6EgOMyY#MIfk&zE(X z!H{TEbco6PH~hQ&#ORO%mc$B+-5F4t)U;#R#h~h>-#B%(xSu@U%wxlU>+sxD!iL=G z#slo3{d@%P%NTm+Qgux`cE8ykP_4rk1SuD?=oFHt@>h=3aqVG$5q{bPwqPcy9!3Sk z>mcSYPxT*qOq9A!ap%S&Mw1VBC#Tb;rQ^pYrbxwZ25rTB3z}b>2T?W#1zXErP7uT> zYSMK)LPtOrgaEscGlmf@E(R|nxZucvDg&ypI78PNJW!<;%|NuJlVOqijzH})0i9F~ zsd*4+=v0stkW>)YF1m$!X6Zr@VbEYO$LMhKMA8xstV}Y*Y=yB1N(Fmy2=5`%gCs}C zdRX5&vCb$vhl|UAb0X%Nkv<3Y<`QHh68}xfX4TmC*WJJpb1&&y&5#n)NOz5u9$-Z} zez{}z2E|(w20ywlCYsT8Q!=Ogcy%%lI-Z4XmJ9OVGgiLnf&GtkD>Wf0SY`A%qgkR#0nk+=@~~nsgABSi&Y!ZwiNqNKFz~%~;g7E%BW=rkJ(8pb?QrCKLUW0x=TY zjjMgr?>n=cE$C%lwbP1iA%$>gyi%)2Zs$ow#I3iSil~UCkp%~{V7Z?39&jF)2OiXx zXV!1@&=m9l9wy=e4gqT%7daBZVx1-4r^i$qFF1>kfGPbXOa-$-@&soQ9bhN!*cW7p zC$qpZ(i*XuO6t!3&6eSca%Q1}t34xTh}rHBJ1a>wLR5w$@9gf`8reZ)-2jZJs3^23 zVtphSXIFbvxkxfbY(bIB3-BJOO?t~|G03+Gj~^)VcHLQxXaq1)mVo)YFEFw&bxt8E z9=C2>&kpG;@M5@?!K)cxKZ+b0DzmlYJL0yMZ%i5O4V;0>_< zM$gzVgH%X`C}0qK(EsXED;in|Zh$H%oFo`U80OzX`Y%|SMamE9_AQC*TalZ$!dkAu zOCI^jCF}|<-7%aFO_1r7dLb;?(gI*$mF~G4sUS{B)K)I3U_La1iV=;gcbYLc8`w=n zC?;sRSLLcPn1H{26QVrEJ1Ii5*DdIk{@MWUek~+^n2itIYQnI?yc6=`9W=C@@M-PF zwBtgBa!TM7Q45Jree;XVbHIxUvvqZ8yn`N_;Hqrl4m6=V!lS3vv{pC$z6bBVi9EKP z)I-9fJ^x6R4u=*(qP?Hfi%m!RvJUS1)HWqQn~7!AGr)0{Whl*Ut8n%x^($lHyIC3u z-xT4F394UGP3y4FB6zWxs#5@D4dR&J20CIz=2v|-!+abLyjIwT-{z@<2WXxi?dESq4X z$@dHg2!J*5{U!XT@F+E3;bzvU(;TN{w_XN?q>8H^A9_r&^s)A@fu@Yu(iP1cJO$Ue z4zK(mM3H zE@4z;Ttr@WzfOlReY>)sT(lq7nXug%V^@mYlR&595piKs@~vs$aw8y>^#kv7sZrL& z|GbSih(5Kw73Y(3J>6v@g;mrzErC1Px0b3Fd{`%9fR{bX&X{bFi{TK_QoUVrCzM=B zWZPfd)ANbijGmPK%0w+n{@(Sn0P8nJ+?IJBWt*xYKGyI%8{FS5Ue1P{H>)2KTg!rw zvw#d+|3?0;N>jCd#?uAX;zD5fE#K%3O8+l}l&0^LC)dr3l)%e14Mz=BG?w9rbc;uVz|Dfiazved?*|0=?0jPI@2rhNOSf z`+4Zi)dOt>$Sa&6LRN{%2LP?PUydPBVgM8wj}YNvj(gH7Wniw9S93#36iBm`|EsIx z^Fnx+IL-Of^ZlBaWZqNuvBSm3f%?c048c+@Jv4??noReb*$BB}(6c^in!&VgnYR#; zvTOxJ(?YjqOBJ|3W1ZzKCBGHzG@hH8lvughf`Rd(U1T-I!ANxO9qZhkQ~`TIGu$@iTd??nxmz0tw6W#;|GKLFDI-3f$>nWZ=n6G8Wh1$k37n=n)FX zNW_9NRkDDh(1pnAPekqdEEZRg^Z=hhTpPb{KU~6p<+~}}yPvWR)vFO{wF3lH4D#1> zT$S~?OR%Ob?y@{V4BN2ZIVOA5A*Adq43;h1`dg%4lkalpj@I_ zj0CBj5h>n*rE^-!Ye27SS{rHR=p;FQI_xyxI7YeZ8gq3`=B|YA zXvN-&b--$kv_q zDJS>{e`mw%HetVu+E~`sr?VM+1?QulrZpbD%-KsrgFm_>?f0TmjHhx8WeR!rW`3Cv zC6X{*0(x!FpKDY*74e|$ohc#R-dR7JMf{*E`9LEzNy{{4W-{s@0LZ zjfKUn4^jy~L5#Jk* zCzri0lIaIqj?Ma3B>%PQj|q+vl8m~l4$)%)Ib)`KnNzVG)Xyc&Bn0DJI3{*@pK9r_ zuD9Hw;+?}{;OI>IqbIsxer`T}8rpF>{;@ilQ;Zx;1kZIJHX zqfu6RH9ei*@cM7aO-Kpd%U5r_7}-ezd+CKUT3@X@aoSSeTM<7VecIx>{l)fRQT133 z8hHXCkNpR}P5^R)ls+SkFvHD!GZAtX@Cr-?%_dsx{=kz8w9gkt1Ue6fPeJ6Jzk7;Sd^Ne0Z`AF6jcVxVx48n}8A1*jvpRR%M`2wO$J9VdK6- zrQ6Hb1mrs{mP^MGSbJ#o#VVr%tts5Ju*+G)4(^0dc4NNR>ErHLPg1;C`Ld#ey&PX9 z4b^@TbxWex@LPsGCdMmt9&^Chm%g>0EU?x6_ypqrdXY>d$kfMFD5f);0 z(z50xC#A+MgBZ~Q(;+J6`XgFY&LiV+-xHR5P*(n`;V7y0Te7RgP@C^A*m)~&zh@MW zoHyjfeXB{eaujYw3PoAe2CTjHn{iI67$EM-i^7nyn`e~}`J)MItOoZvE_2*XCV`fd zVSMA&*3uBH`sUn4s%-4;-%Qx}c1MhXoKc-I{XKeDz5ft=2_hmsC8KBVg$&E!brvZ9 zB#E%KJWQCo$37U3T!)dxg&>bfn|x?i&+Z`{x89ZuGk%63Hxh;L!SWQ2G@>Zq2e!W? zRaLHxGynY7nhMK9If-kd82en-oP9I2-U)jN(>VpNe(Jw^3V8Bu8|GiMCR(>AvfP2z zxg6BK68Ue~G0Hb@iGNd?E_AVvXh>T`^BGq&T(got57QXBQs4m+1OtqT^?wH&8=EPo zF)JH82Pc=2v5BdfIXl44&hignBu0fss|_xKmU=KFdff=)+X&Mi$`5~dv l`htA?A7S!;^spD`t&9KP4SFv^jjT-UV4#kTHY~jt{|kr#uC@RG diff --git a/src/Nethermind/Chains/hashkeychain-mainnet.json.zst b/src/Nethermind/Chains/hashkeychain-mainnet.json.zst index 28dabe61bf0513a4fd977af1557285813af1f2b2..ffdccc305982f99b8131ea689fbd79debfd07657 100644 GIT binary patch delta 16237 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 delta 18153 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 diff --git a/src/Nethermind/Chains/ink-sepolia.json.zst b/src/Nethermind/Chains/ink-sepolia.json.zst index 7e317838c1ece1d6105f79698e8982f48f17992c..100be96e49e14e1484ee4498e46fe7878fd15be9 100644 GIT binary patch delta 19892 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 a1@A*Yq6)L|Mj`?Nk(+}RNw*bA0ni6(ECvDq delta 18257 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 KPq%ze0ni6hc=hW5 diff --git a/src/Nethermind/Chains/lisk-mainnet.json.zst b/src/Nethermind/Chains/lisk-mainnet.json.zst index 598598dfa3be25257d7e54795fd950f1a5c9a438..6c902a374cad8da5e88f7668c8ffe7eb5f63347b 100644 GIT binary patch delta 9016 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 delta 7892 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 k9B59&J(umC2hM?ne4NavbAr*h$R@&Et; diff --git a/src/Nethermind/Chains/lisk-sepolia.json.zst b/src/Nethermind/Chains/lisk-sepolia.json.zst index 88a36f768fde1fcb8d452e85e63a42a190b83339..2d100695c7669bf34681e021a45a5e64e49aaecc 100644 GIT binary patch delta 12122 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 diff --git a/src/Nethermind/Chains/lyra-mainnet.json.zst b/src/Nethermind/Chains/lyra-mainnet.json.zst index c2f409b139a9d2e9cd3406c94874e65b6eff139f..7dc989ec6092f02e8d2df3e350911d6dae2a61db 100644 GIT binary patch delta 10041 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 b{J&WS#7`?JDN`#V6HJ(61}ixvWS##7jl%G- delta 8714 zcmb`Lc|6oz`^OnOG4|cqmuc){i=oC+#1x_|WzBZmriBb@ER#vfHuha8OUX`&Vw5E^ zq_U+%XeBe2iaayiy6@-p{FdkW{l0%)uQTU0*E#2UU!Ut-=UZJ4dRPF$DmAKYGB`Z0 zs9~CQqRWZa;*(SNw_6~UT;fJriCX32=wAgzemN67wD!=vE_X5v=_J6lx&PS}?|!`|~Bb@U}M-|2nfT%%};;}c&K3+J!lPq0m|_&Pj#;PPvf zq!;h|#AwYZ99?{|I=^02G*f`Gc@bok8^oDf8e_@E&XM^bhhWVgH+2h15D;8mS+N-t z#HVExdk@Ii{%0y7FnYs! zOa+~?r#F0`c&{c}BvI~Hf6DsW?l4soIONhOYLRcM9R@c6eUXzZ@V_k`-B_=<79n5` zy5K&p)G3M3JFss2-6M>#*e?p9rzA%rEoGOjC=BMU4xF#*KKVvDtLq3{{ZJPJ3-i@l zsc1J-gp`zidvEKNwR|i~aYh|3s;jqwchDz&f2Hb{d}E68XIa0}RLH@;KMtiUbaF}G z=IP|6%lXCnifQK9PldgMhu#WeP4Nzk>qn{}n|!r<=UHJxy7 zr+or9mF@eE!WEj;vyjZq+QRgvE%bpnww19>fvIT>h>=}z55fa`Ao88WyBI-b@oS8% z{An!wTzYRG$wqc`O2p~9xlO-~Qu-yp!R7faDvIbT78!#9Fr)s)MhAY^Ujp9rW~+-gX!x`4gCl(b4|8H# zT)1}+CvSEoO#ez>d3%nkr%{>3cliSQTF7&&6*t46#c2(S!b}Re8x1MIq3u|GaBg&3 z-ze!pGNow|lBbQd`(igvD0=#pEPI(`-r;5^u-G`O?A^lMO+WliP50VnTn^WA#!m?` zBX-W0Xk`B=JhV&Et2OpQ=duo!oPios8N@DYQ@=Ya={7OaPE_mCUnwV_4 zn1fwZyuze$34g>d9TxDpk}OeJ@US86hH}YFU@7$JVmln$Oi3HtsP%I`s5xPnI(SR4 z_b{I8PM7ldxQKpQgtPrvL+qO#&w9#4jsZ5*0x+%a@6#$9Ez`U!uF(~ zpsUSC%s!cKm1_ygPbD6iGGA>B8x^kQx={VK`fFlB!kXW*!iS8jY+wy(^W>3}JjVG5 z4=>S);Dgd4F_;a$(OY}YN4w*=IKrIczk=AtPU*`rVu@2A260chG$xV>vtMnd#-NetX9HJ8EvF3&Jr<%46`7xDc)!17lJBC?$!t44KK) zcuMRd?~U9lf_ZmVaa5$x)MLisbpqJ<-oyTyk-Vu1J|ByOD)7*ohHBHKA%YkOPr+NUjonbshDWYRh& z7r&B&4;~=4rB&HUMcIGG4f3b6aGv!ssxt;vMhUy}ejB$O)wAu1T4AYwKTvt3@=c=- z$IUhPLHN~Gx@XbuuM8BY$jIxcUt!;?K+t(?kL)pzE~|%`E-Rj6#FiqG7yBQ)nmOHn zL>Cp;oUI#MJFOEw`BAmb+B@>|9Np#_?@V%Y$PpD+p2R(7Z0LZ2W7h8tpN}KH4Jub2 zTOB0n5Eky$$h|)PHUMgPUfd=ij?*ahL{~4wT=N<;S*rvi`7|-oz%ntq-HiDcvJ4co zh|Ls%T-eLk3=!XW96oz(o$^6fU{oe-*zHxA-xG3(kmLo!F^&Fxg|9P5-ag=d=4DjV zmj;KP&un)ri`y#dm3c`{^vJ30=QLw4Z3tuFV`>TQFFzH!S(VW^?HQ+xO_}NLFAfd1 zQtEt(_#;wI&n$Gj%a7grQd-i7TVS%ImkTyG;i8&To_dz&ve-LraelSjmQE{qhVf^& z`?}VSoz#uUPjgmGZ4k6JHyR3j_En@R$Y8#ORn|n%J=)d&;W{HZZ}vT<{OR4kKI?{s zgxHjHpU|Wj{kPnp+Y`8@`pX5Oj4Zb+4#>G}yqgI=d1LnU+PUYhPQmw2`JH*M^{yBs zg%x8MemI1Osa&dmI&SuSd}jEjUS4&C<~h!fT)`gaxO(Oo&z7M@A zZ$9Ls-YBH|qqM-^g7?;ZVQ;6O$`_diq+gOG`LzCd!*LBAL2_ouMeHv@2Y8S~PWuPn z<$?CaBcgehZ(N05yu&syqLf#w5)xgdM=b8F^C56JcwPb8{uDVw>oX z$Y!~ge{T|C1ED$2?wJ}6ntyo3@=TtTKfP3=M3?>6H z@1}1~>a8Oqi%qBboKv0IaIHg&T{Smfi}s|DMz|+$`IuY2JlI%i>s~qiX=rc22=j{d z`xFg#*q}j{B75?bHEV&*sX)((Y&=3Ki$yr>;>%Y)dmzDsN-tUh=?qf>Gj)he*jg#x zgs|=#mut-|nnYr|i!?ot`m{bm3T(Xk#e;-pQNzrp+=XE@7O!+8ed;#uI4w2jJ|mbK7Jt%FC&`nut^a>#XBQ! z7?se8o!dvg#9@Zjdgxb4Ho5rpQuC{ch_ckvM-}3O)iQ)5L=t!^pH3N$a<$iUMiP>n zhLY*0Mt<_8J z#f_B9(l$3ehoBU&w9QYiZred3YMJB-`KSTboh$A}ENFn6=Zn{9c6Y2h=bnTiML>e! z;I>)hAf{y>xC&nmmUnpckw^Y+y5rlM!`E|O?$>6SB$#kR?VtbiBA5kfaQFc64w>W+ zq2%Cm$y8620{q0ER*XztoRWp7+7b-u7$j)WWN~}3ZR-gMDl*#?W|$-pxs`2$222Jq zqzS}iD!zbRG>@3fquESMiw4+m4w*!=ZS%-QOpD>4;~@kkmBn!Pq<`5b2ucj}hXyls z!#q^C*?5=bqF0E?EIfoRuX}`%)SEZ8NCP!WMFQPlUaifxR+x!j^Cz$f7I_!|6rqEw>sVPOJd*#&~jy+FKmL$mm@B z6k=h37zha`7wLkVF$)8+Q;5mAVqI|fe=?ba#SxGKiq@q0|L;z>&sZF>Nsprj;`Hx8 z*gOZubI0Kw&+5CLQLe+lg!n1^2SX&GNDzwpccJ``I60tgv{ca9-KSLqK&(zI{mLU; z6@nf84^!yB+ilWSP8uo?!Z%?F0$P*_RWt+)sv$2bDuTO+0;-w>45CkdKNxmAK6nm{ zM;I#&y1NMU2q4f6_;zHfLkp&zrY23L?rQ+(e;b~S2R4#$R1}H=guM8HU>bMz-`qEc zf#FVXP2;{#40;%lm|7aq5LJr-q)SWHZC#!w&_l!t$q;56MSyhq>c_9`5-mgXY!8RS zY4D9!=K+C5?Nm;R;0Qbtiq-(BlgEFjkZS%8gcCq2^*>KlDK)e7c0Hqf0a9xfFD(_& z5P;MgA-sF3{*4$XU~*0Ip~}@o5+J#D?Z|Z>p+ic>rUD?TBJFo?Q>s@~^AIpa^{F`x zQ=1e(icNrTD^`8`}g&h$Fyi#{Rmh)l^IKIr=(;6Yo~0`JDBunAwV$$ zq|;BAXtyc5EI@S{CPAZ)l?6zgtrELe=@~f*2W@Q{p)>vem)`lmXne#5R#|A>nJ0nj zoKPgoWOZl_m_i+Z48{?ro~^=P08^d~l&BfN kt*UqwAc;1A-Ffg3Y+#bMdbA8E1WcieS2*i8R5iW+0e)zmZvX%Q diff --git a/src/Nethermind/Chains/metal-mainnet.json.zst b/src/Nethermind/Chains/metal-mainnet.json.zst index c0d9dc63466d8d721e5042a7db1db42a9080988c..f422775e3f91beee582a1980673426c679cf9476 100644 GIT binary patch delta 9205 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 F{{TjSakKyc diff --git a/src/Nethermind/Chains/metal-sepolia.json.zst b/src/Nethermind/Chains/metal-sepolia.json.zst index 2806a8338b5d72e33cb5774deb02a0977952f83e..d8656b6d2ce939ef068dfacbe3fff9868337fcea 100644 GIT binary patch delta 10216 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 delta 8878 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 diff --git a/src/Nethermind/Chains/mode-mainnet.json.zst b/src/Nethermind/Chains/mode-mainnet.json.zst index fda25cd5316b51a2ca8b5f64b3c69368388b8453..19d3cdf32984a03cec6ca5dcab5c502b094afb3e 100644 GIT binary patch delta 10526 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 delta 9350 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 JUxX8E{|_DWw@d&4 delta 12816 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 delta 12241 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 delta 7319 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 delta 10775 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 CCM1>s delta 7795 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 diff --git a/src/Nethermind/Chains/race-mainnet.json.zst b/src/Nethermind/Chains/race-mainnet.json.zst index c9d5738c277eee9adbfad8b6b44ee7113ec130cc..c4259e7c81976d440d71530c1827e414a0986947 100644 GIT binary patch delta 8654 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 WZ)Kp12=`!{+QdoMQcT5q`~LtBzm8A< delta 7218 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 M&P2l%v%%&60YMkqqW}N^ diff --git a/src/Nethermind/Chains/race-sepolia.json.zst b/src/Nethermind/Chains/race-sepolia.json.zst index be45c6ea1ddd541e7f0efd523a7be2fd12afb561..f2bef6a125e741ab5e3e7925d1d7b3398c633dec 100644 GIT binary patch delta 8721 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 Ge*G6w$ws#T delta 7303 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 IJ5K?Q2aJl=IsgCw diff --git a/src/Nethermind/Chains/shape-mainnet.json.zst b/src/Nethermind/Chains/shape-mainnet.json.zst index 94111ce00e0e241cd7dfa168b22bdde5adca1699..c7b02685b219a3c73229d4c8106be4fd5e23431a 100644 GIT binary patch delta 9058 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%0D?GPBoCAdyjgAl1TCQs0=lC>6x`n!76mT@44te% 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

d56L;__B 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

MT$BV}uh5}%_bWYXD zE0KK(9)ukhJ=|47LW1M*JHSi(m%UVm_&%3G4Ye2QUnS$s5 zwh<7>uOk2O{cjMPzGu)EQ5)iC?!TnH0SkRJyx2|m7(643e`r1YLnOeD<17EYK7u;@ zu@ek}%1XfIky_|WQUXnI!=ss)3a0gsKBau5DU$-LRJwp!h$6L4r;G$f7Dec({Y@zl zqZVjcFymt@<=&si4z&KWBN4DO1lU zHK#=$6!)j%H#_MCjsQ%UD1OiJ z5;xFuB3!}JiOb^tr2arp+YHodxg&ZP1js)w9$sS;Q!{f5_Wy!|lZ*TR&K&OlNI1}POJ=WRkkud%8scqi>6Hv*jvjS*>qu;1yr(B=PJ8k%!8_~SXh_kWDXA7LPg XU<&`)CtltGcMF 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 IGnn4~5Boy15C8xG diff --git a/src/Nethermind/Chains/swan-mainnet.json.zst b/src/Nethermind/Chains/swan-mainnet.json.zst index 8c1d0552d6ad8717e91ee0a42504ccc7c36d07a4..3b5d192de95f35c2e70490c5383ef58bb6e192d3 100644 GIT binary patch delta 8420 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 Z36{(qH5dsMQr? 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 delta 7014 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 X{e7EtD{A{LBOf~IQ9BW9#`%8$VxwAa diff --git a/src/Nethermind/Chains/swell-mainnet.json.zst b/src/Nethermind/Chains/swell-mainnet.json.zst index b0e270c4f8aaf6993756f4bd341b90d974bddffd..d928fac7e89699d57d9da3f918547cd95a2927c1 100644 GIT binary patch delta 16356 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 X++<`ihNf3`Ul^DHlO}Jj;QRjrr#d?@ diff --git a/src/Nethermind/Chains/unichain-mainnet.json.zst b/src/Nethermind/Chains/unichain-mainnet.json.zst index 0c645ad0e4d6776499cc586c24f238d7dc4ff78b..4569abcb8c625652bf64b3f78fb67daf434fb9df 100644 GIT binary patch delta 47216 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 T-lG1mynLpGxS4Lu{p0@w6ts!! delta 45357 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

9kU5modW@Pj@-hW_nHMh} z`i6i#N`rM}{D!3tqGgOi51$Wn^}*#O@?37F8W3&<3D3rPKFU<`1cq1kIkj6gD^I2W zOF2l5j?%4kX?P&vrT_(+y7L){cetpsGnpWMB%vAciB=Q8M)j_`Vh5736nIO%WZzT9K@N0mXrwL$Ow6HpaR%ZP}~f==#FqA5h}t zn*}SeuW~w?s6QK+g(|_N!{S(g(>}$3M<7wLe{zMktS#%0TJf*TK)VReMQaXPC_&El z!{{>4&H@D-!i>I$_;bj?O0<#{n3Mnse9k~J>p`bIa!h4jbEQQ&~!i1feBqHD9^DO+Ukn_yOo$VT%!#V7WrlGBz>*9-Ey$$af59y1&XR?22>EgL z+33LJ;D%3hJMjLto*i$=3)n9EBUCOQSIuQErVXCKUDrN2b2-BLHIcBm0Bwg0u=>q( ziv|;TmtstkRpc7|oshA{1w%9`i3Sit3H+nngbYSE_0WF_sV@blxu5+RYgzHvS~&FI z&_Sou0=G&VAN01dG$%vI77|5nRyPqMa_*R!!^Z7kRmsA+zY*3{1jV>S)2dbMXhfr6TUj(?BU9rxM=Cqpo&_vrJpm??jZN**&*k6m_5uob8}H|H|3W~S+YJ^ z$8h!)p3a~dj5^=*uXsp0Rc1gjNPxYWNwfHcug~;+vH>_U8;l=jzLKwXkVDt_S%j~a|#c^t^)`qTT@B|8+tTh zFhPBU{+nYs&MD5ZUK!16Smrwynect;YvH_Zb*-|{LzSF0ea15S;htfRtr(c**%mZ@ z@J&V!<*Kk(I*y3aI>l7ZXE35Gv!hoF~n8{aO^Da1s#>i7lk{8;@|K zZqbx!VqQOiy#Ta0m2F@-HVi7iJTmxaA7A8G6hRba*e*O#2456(xDY}_aA6lu^h#T@Y>t_bM7I5<2AmZ;%U&=4YmGLF5Eq$JTLk`K#?c-sDv zUkAz~A16YQA{?II6n|WBr}BqP^Y#@$wc|UOokMU)E(jrP`s_EldkGN79eKb?|HApq zf`lfRrqXfFra4l{$tgV3`5r$A>Ajo%@I zy3d6qU+*tyOuj+4V(wUAvB3*Nf*#p9boKNN42_r>nV4C=v$FlqH_N{R5mD7a2R_}c zA>s2%V8)r>sUhLv+uK>stp(ve;e8I$+nLbBeRiVfp*5jfJyP3g&-;D0@yp~0Z!AtN zBKcteHu`L*pfK?KVFWz<2Lpy@B~bf5yTkKPt+|67G7l2O63bXaEXBh5oe(4m%{vb> zsqYEm`FQN`dS{$Ld=%mbUO*q%S&5#Yw~vG+UT^Jtu-gWtEaQ*#C78*L|H{aJPIMoe zT0;q63JD?uH)3UFWn*QJ1x9cILXheIE%*O#Scbn@?*E?*8${|T|=y;#sh@?fB#<5=BB&RFlpPcU3X$`GJO=Gcx_Qm~R(#!1fDs0J9= zjdPfPl9^&@+el+Q>#9G+L4sz%IgE^qV)^T_V&&>lV=d}oV>|nCAV+v_onwEs6Or~1 zoPpii&5DKX68|EA@m>AN3R5#tICvf7J8|w&MZm z-!)zJ6Vy00Jb~lCLV#GnjhGnz9U1?xAvUe~YbyB%~WMM(dd9wGmR z;`m?I_y5BhqT>LmhOh$<;y+|r82%mq3;3VcSd;GmC5!d{NY;zBW}^H3KOEWq;kem{ z6?@SKi^A|Ot^{@6a{6iA*pRSF~ZsLc!5CSSb*L)nu{trV&R)&Aa|3dv2 zLzdyMu?!6;|A3?ZGm?aErT^2JP&BMHsl)H!Q2*+{*o-kUr2i`Y52x6_wV1H~S$Y?- xTVr&n3jbb!LI4I0>%Zn6(+4jC80LQ_9pV2@x(^ZEf5s^Gs-LiCZQTCv{{dRMZzcc$ diff --git a/src/Nethermind/Chains/unichain-sepolia.json.zst b/src/Nethermind/Chains/unichain-sepolia.json.zst index af355eb5bbac9c2d27146b4bad42e0fef556f028..7ff9d5fd16e38d1a801fdc82853ba7a348b72783 100644 GIT binary patch delta 29514 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 B5X}Gp diff --git a/src/Nethermind/Chains/worldchain-mainnet.json.zst b/src/Nethermind/Chains/worldchain-mainnet.json.zst index 2c243b391bbe9fbc6a347069c6e71d8c1c98cfa6..5f0e5ba592c96df2116610aa9a4a3a98079f8a1d 100644 GIT binary patch delta 12742 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 E2P)(DjQ{`u delta 11558 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 a{C|1u|2yHwn|SLs2M-K1cPlmoIRAfxMZ0?d diff --git a/src/Nethermind/Chains/worldchain-sepolia.json.zst b/src/Nethermind/Chains/worldchain-sepolia.json.zst index 830f302c5d8828376eb952a2c4899a2e9d5d8b57..317fe21409075385c7ee07da31cbf48c7624f6a2 100644 GIT binary patch delta 12718 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%0DZt%Bp4 z#C9TEga<1K-iofE`HCJyW_g3{u!65FDuo`#2~+8QA*zQ5pRTC^#e(T|l#&I5srAC4ohE`^6KD8_f^FQv z-JUcl237hC4TwOThW#Dyfr90&kh4Y+|MHF|1!@|Y4KU1{+NsH)2u}O|GQU)qV+_IUQeD3l zqScRCcQyTBxKM_FISnV$oxv2JRKl?UiuW23(6z0oR^|M}zlI!VUL^z@WYHMP1a;6G zsx@W{-8XV05@o@(RA~8uBu~^uUQ(&%?P|k;bQSulRaeneX@!>4(4e2}iXy`%vip+@`%caQfrLdpzp_^$UbeS3 zIRV$Uh)uGQrh={kU?$_1om){)H4XuT4EGf_egG*|xG;4#x6~!wpo)k)CJejo<2e%e z3f3iEtvPq!d8r4W6LID52~7*nI+~|(%yS_l?G^T3a9=5E(1jU$h!3>}MCL1Wi4;5s zaFcO<>8os}HIMVAtIAj=3SPny$)!vsyF!n&3H-P=v-$#VM53aK2*H=NW~%#B22hcr z7Y}^i@j)aEA|`%44W^hIm@@(cmKi0JX$R}0XgW?~L6KyrV{Gk>ARvERlJ%1IAqIvPYBQE`c zHrXXpvn#>M#G31fHVJa#1WhLAH{nXTJWJ{2lpXL++67>G&24giV)dyMYn#e?yd|)>Wi^QA(XZX=gk>agH+;1}_RpoF_t6 zjNm@4l`-x~&*#+ya}d3~6Sdk+poGE;KdDSdTAYV8NsE2VjDP>TF|W*CmRmD@Qx4I& zI}mu9l`k~qYj%4%+B|^{GwJ5349!I77aNQ{u$a4g^D7cqaO~U|bl>n=Xg;Le50f-x zWtBAzA^42?s-*gbrMNP^{9}Y_=qEJC2ZvjQV;CJu2ZS3K^84zao;;S=4a+1v$WtiL zPa7*(tqL%GELg{*reWv5OX)s{*my85f3$&&DM)H;sE%L8^5h!};@`;oOp~)gdi<0r z+Tl&*NFxn1x9JG3(9A%H;6?1#8mjzKz-0Yub{0!RERNdfG70>2 ze5hDDEYEER3_6rdMyXzFMHTi#BRO-#j~wT0Nc@c^Fx)DsN9w{e4#<;F308%5Bo<(3 zEXQ^Ow)cmtZKcC5QE%tEII6#uW_e)GZ@WqUz3nZ#U~QK7j?t99&UL4H5tO%%A@=?s zn>Gu2Yd1~(6s$s9Cb6^)U0a=VA~K$O7jlm7t<1fiu)7O1%USGfa(K&!nxZ}IEsQcrseKjy~$Jt?nWzS!yV>J`VyRb^e z7WUt;%x3-m39?Is?W?o(A#M2RF#qY~c-_8khwH!YxY22F**s?i8=&V9y|?SBb3f;S zS{PnhhqFmQeroQZlKZ{^=)8MH#E_ii#bNORq@l$+e-zD@AJDB9tfG&|(+Vgb9riqn#lM`+MmoEOY zCgT#rk5+e#kR@xu*My0jA&X z5QLL&iq=AWG}=zD)HAp(+aD`@SSZp#v7&G3OH^IATS?*p}4{6mRrZX%2i-Vf9k;Opw=14z$;LK!a_?TIg5dyd|;V-U<#~kV(^yw zhYxmHxW?d3rx_k%o%|rgx0}KzK!%Dktj@B?-xt~ajw%eWd`sEQiat&iRFoTBjB&|x&dz%^SQ{cS^Re5X1ekHpC=eS0P;ZRii$>^;uQyttxwMBpXKlw`Ag_c z(mKm)u$Sq>mvFGk^WivtGy5F5?ECL>I9Wkgf~Ibs&I@-H)Yp9DHB-lNLa&=t`(X*H z3+-*~$uCCf(kH+0{Px=-?vq>8W`DF9)S{AlJ3QF}DN|ndE!msjg7%3^@{}4?z+FUP0NWW_x!5c z`&;)0(69aG)J|aDodvfT?s<81BE8-x!d~M04RMSNCNYisKMo=#1qH=_ULss`Z6Q7z zSEw}(kB-T<@32@1b5i1d40=v|Hm?lT5sWFlRrBOe{`G`3N{&)5E!J z{6lt~6zSsYnK}|dX7A%u>Bfng^Dhw&2rr-t+)=K$>B{Kqgjm82NYzjRXd2TfCu9oI{v3uf1p1()kXB?Ma=r_}~oKFo}`8B_I& zc2}}W1rt36{8^*^r(9&q5N$|DYuf|wb)P(g_TdHpN<5@WzZblzLIW5TC3Yg{maV zs%xDfz8!8A5LTH3SdMNUB68CSc-x~1uhLS+%)V;}SQ}V7m^ZbyjF*{!LN0*F$xG+0 zV_{0}8NWs+TCdMZUtaQK zGE_$zWXEVc$?*y%jk(;&8K$aEQvSqE7u9RU+AONhD!JL^w%L%Mb%MU*yD}ku8jy_y zXWcs9aNGqUrGm}cTNnxAPxx2z+oj6t*Hm=1sC;|huDDHlczL5Nexq(oEiBW(2v24DB zM#Zzjxn#7oxFfU&=jcP3WcK3r;t0$*jw3k28tclCR!pHRuQx-?0OpbMGRhH1Y;0_3 z`#fcyx{Qx(M(r)^LFw?oKGvf2%|>w8Nw4_Zie+qM4al$V9CNM=6h-o6#VzP$;3* zAdw@PR2DFX7?MurftNTulAr%PmzPlgjc@+F)pyJc%|OqvM7C#n=x}5)zjil^t`zPr zmIPJOh)~p0#bw+w?N<)&iCaq!IsQTIqYw-}&_XVy$PAG45w}@{`)o?HDBS!32eY2Z z$047|j+x?4YwuLzKW{@p@IN=g#SLLRei2X!zGY|5t^sFb7PYGza~OBPPMokHbpKSDi89?PSC zrH)V5#)n4NZD6`-E}(_3BeBh3p@$AX;!#riSeqeogi0I$(h4TEcaiJ2FFV!P+R|7K@8^O6ho{N+G5goW~Yp4YBw|@mFPrTyW_vh*F!# zV6L!N{uFSfa+r`j{ZkDK>6LcDwHHNj6CY2zK=Edfs#OU(w1@R zsGSOqJ9Oo1)T+1Lg>+E5$g2hG+&fD``F2-O!gt=oJFC!X4TeRZxDnSUq8_S4#Jp|! zT+}HF9A#m;y%Qw`nAQ(pN8aWsh0t;0$wov0RWB|y!9UYPq@mGM;5jz)+O`C8VK=;5=wrN$=uWB z7r$PbE;NhmbTyZA-4^8}cfzJfk2{x&nN|38HT^OLcSb2ZAZn(4Z0mr&5tv2M&U%f8 zyEz8UKY!ZtJlh7qRgC*UrkijASN3aC!?WFItsRjuw&~3Der3K3{FGTU%qQ8v?| zT?D(`u>JO6i>P{*wba4vXEL5lzYlfJkH+vgB)X#PV|`A;KLg*Bc$ZZ)$nk9RYblCD zesr-^yNT@uiNQcE>%LZcoCnGR#U8zePEM91(8Esz3D_Z7s?@`CliXPDbn82;3A|c4 z)tQ%%M`N=~6;~vwZ6;_sKJ1D2Uw3tz4e$izNG3@awBBPXHOTs%*o5Mj1+`q}Znv+( z`D1lEKJ3P#x* z;{D-{6P+AX0hlwN*A|6G{LU>0G5&{TRHIBvZ@HqNB|p?QdY|}FyJ{?1ZF;>2*|Kie zF>b$^QQ8+u1jZ+`2dvJPJ(5=zp^n`4n+h8Xmx1N#;o7$ny^XmsE8~bR--FMCx%Qc4zDueVnt> zB4`~e;44t-iN&tfmBQUzWVSfxwS2gLe)~}k(>49n6S{_gxk9QyQ#z=9kaUSgk(Fp7naLreG)Uv705UE)Y^t6zBSOAt{U1( z`{z=>)vHuHdQjQao@h1O0E;P6y^n}VZUmBI^uOBC1SF-%tifN`J9#O=^pZ0UBrdF$YAaV7uUo zN0a~i6(U-C9~$;V$oqo@hds{F-k~23U2Jfs*XR4u+b-A9x>(()>-2_$u?PZb8^swh zPwWj2$t6y}dQUjE>DL~cQc_n>A6f|Zj$UDw#vgW1H85K`;mq1sC;el4`CeLN>JjRG z9Lm+YVpt6hSmov0v{&1)ZCze;upe|n;RTx5ZbC=F^MI4~ue}W*ALnaSSMBK(kCf58 z--fs!!B^h()J+R8#(WpcYl4XYU)pgwiOE)KM^UF)N^hT*LLa6}v+Na=JDpBPNJ)On zDOGMQi2JNX>!^;(t2otJCg=DqQof9IPxjj}6Z7~GU}ifI>!)cHL1^zl{>eQDm96e= zRqGxcyz57Qp(?2tSRJPT`P8M(y6#t8zkv8qn~T~&TlRV9Q8yADY_U>h`EM$|Oem}J zlZcmb(jYaig)a)};xvsr7oXsdE3ti_oVBN(q867Qc2HWqMc*Qy5{$I4u~!_@LCkwf z5f=yt@EOua33guFUxxsso@4;alxC;r<=JY}|f4B3WHC!8FT(-f76O-LA5U zJf=JjFyFvSu}_yB>Dw@K*KdWL(dAuLbYnwS$caXTP_CQzhG(-22)E}8<(Hd(=d8ef z4M~J@yXm-$vSfQWn(BG}b;#B>$A%y^Iiq+F5TdB7@Z@rYwq10Au^X!6WJC;77*U5Y z-a%u!0r^bZ=~D+V@{xW|A2oyd+Vu~m4&{PsObv6{q55?RjrZ{3nR=ek4VjrJy;pRN ztGgUEP~RF6yxFoT`nbj(7i-_g$=&Z&Yni70@aDckBup;RhEp+H)GFd~(D=uyZEI2k z4xZROu}yPz3Fb^o)EM?U(thrJvO{Z9@Bx2foU;ivGxRjS%+>W*RwsHu-GryP@QEdIP`NfFpA9%*&PKP` ziOKDwBmyFNAktqfj!~)Q|HT*f>Xb zP1CtkJ_hy0bL+GynJj`7o(>N(`PTu+jdB{4XBRQ!%gD#YV71@YReKp}`em-d8Q|iW zj>djrQ`jW?W|S^wDgG;%yfQn$=(B(j2-v{~L34>`VQpz|q#M28Q{EmS&6tN#q1XH| z&3sXsXFf0Kxvzv{$eH;!s~7vD#MoNc7GkDU1`TqP$zBdT&9Q zl0v8&HO!1=-Q}%akt(WUXgb}$f(DZuUiqupQUNW3zo|6VRN6!X0?Y#t8(N>Cv4uCj zZb)Liv(z+UV-QQ|8TFEacug1GNIHH31=YDF-KyP;#xaQ`C9TPX%!;uShfqz7A-+%i z9{7_R0GVMvD_0^+ZZc{A+NTSrtoDK}GcP5Md(Z+OqFi^YSAWZ8gJ^#Q^D!W@75Nsa z_a1Z_aJwKp--T*-Edl%OW4xT;PU?gz@;M% delta 11583 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 B;uHV? diff --git a/src/Nethermind/Chains/zora-mainnet.json.zst b/src/Nethermind/Chains/zora-mainnet.json.zst index 1d93b533b0b7f9e587dc72a235ffa7aadd87e43b..b71d1ecb1f9a3a5abccd1b84d404513652d7f69a 100644 GIT binary patch delta 13925 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 delta 12935 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 SlmC(k4zxT2KBtp~JN*x5p)Jz@ diff --git a/src/Nethermind/Chains/zora-sepolia.json.zst b/src/Nethermind/Chains/zora-sepolia.json.zst index 763641aa82e85fa24a249bfe9205b65f519e757d..06ec4c0b54493c07a12b36a06c10f4587b95f15d 100644 GIT binary patch delta 10204 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 Xzd3dJ3s3)l8_iNl-G{(l7ythN!2(As delta 8944 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 Context.Resolve(); - public IJsonSerializer EthereumJsonSerializer => _dependencies.JsonSerializer; + public EthereumJsonSerializer EthereumJsonSerializer => _dependencies.JsonSerializer; public IKeyStore? KeyStore { get; set; } public ILogManager LogManager => _dependencies.LogManager; public IMessageSerializationService MessageSerializationService => Context.Resolve(); diff --git a/src/Nethermind/Nethermind.Benchmark/Core/FastHashBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Core/FastHashBenchmarks.cs new file mode 100644 index 000000000000..a6481332d087 --- /dev/null +++ b/src/Nethermind/Nethermind.Benchmark/Core/FastHashBenchmarks.cs @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Runtime.InteropServices; +using BenchmarkDotNet.Attributes; +using Nethermind.Core.Extensions; + +namespace Nethermind.Benchmarks.Core; + +[ShortRunJob] +[DisassemblyDiagnoser] +[MemoryDiagnoser] +public class FastHashBenchmarks +{ + private byte[] _data = null!; + + [Params(16, 20, 32, 64, 128, 256, 512, 1024)] + public int Size; + + [GlobalSetup] + public void Setup() + { + _data = new byte[Size]; + Random.Shared.NextBytes(_data); + } + + [Benchmark(Baseline = true)] + public int FastHash() + { + return ((ReadOnlySpan)_data).FastHash(); + } + + [Benchmark] + public int FastHashAes() + { + ref byte start = ref MemoryMarshal.GetReference(_data); + return SpanExtensions.FastHashAesX64(ref start, _data.Length, SpanExtensions.ComputeSeed(_data.Length)); + } + + [Benchmark] + public int FastHashCrc() + { + ref byte start = ref MemoryMarshal.GetReference(_data); + return SpanExtensions.FastHashCrc(ref start, _data.Length, SpanExtensions.ComputeSeed(_data.Length)); + } +} diff --git a/src/Nethermind/Nethermind.Benchmark/Core/SeqlockCacheBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Core/SeqlockCacheBenchmarks.cs new file mode 100644 index 000000000000..9ba71e47e39e --- /dev/null +++ b/src/Nethermind/Nethermind.Benchmark/Core/SeqlockCacheBenchmarks.cs @@ -0,0 +1,345 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +#nullable enable + +using System; +using System.Collections.Concurrent; +using BenchmarkDotNet.Attributes; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Int256; + +namespace Nethermind.Benchmarks.Core; + +[MemoryDiagnoser] +[DisassemblyDiagnoser(maxDepth: 3)] +public class SeqlockCacheBenchmarks +{ + private SeqlockCache _seqlockCache = null!; + private ConcurrentDictionary _concurrentDict = null!; + + private StorageCell[] _keys = null!; + private byte[][] _values = null!; + private StorageCell _missKey; + + [Params(1000)] + public int KeyCount { get; set; } + + [GlobalSetup] + public void Setup() + { + _seqlockCache = new SeqlockCache(); + _concurrentDict = new ConcurrentDictionary(); + + _keys = new StorageCell[KeyCount]; + _values = new byte[KeyCount][]; + + var random = new Random(42); + for (int i = 0; i < KeyCount; i++) + { + var addressBytes = new byte[20]; + random.NextBytes(addressBytes); + var address = new Address(addressBytes); + var index = new UInt256((ulong)i); + + _keys[i] = new StorageCell(address, index); + _values[i] = new byte[32]; + random.NextBytes(_values[i]); + + // Pre-populate both caches + _seqlockCache.Set(in _keys[i], _values[i]); + _concurrentDict[_keys[i]] = _values[i]; + } + + // Create a key that won't be in the cache + var missAddressBytes = new byte[20]; + random.NextBytes(missAddressBytes); + _missKey = new StorageCell(new Address(missAddressBytes), UInt256.MaxValue); + } + + // ==================== TryGetValue (Hit) ==================== + + [Benchmark(Baseline = true)] + public bool SeqlockCache_TryGetValue_Hit() + { + return _seqlockCache.TryGetValue(in _keys[500], out _); + } + + [Benchmark] + public bool ConcurrentDict_TryGetValue_Hit() + { + return _concurrentDict.TryGetValue(_keys[500], out _); + } + + // ==================== TryGetValue (Miss) ==================== + + [Benchmark] + public bool SeqlockCache_TryGetValue_Miss() + { + return _seqlockCache.TryGetValue(in _missKey, out _); + } + + [Benchmark] + public bool ConcurrentDict_TryGetValue_Miss() + { + return _concurrentDict.TryGetValue(_missKey, out _); + } + + // ==================== Set (Existing Key) ==================== + + [Benchmark] + public void SeqlockCache_Set_Existing() + { + _seqlockCache.Set(in _keys[500], _values[500]); + } + + [Benchmark] + public void ConcurrentDict_Set_Existing() + { + _concurrentDict[_keys[500]] = _values[500]; + } + + // ==================== GetOrAdd (Hit) ==================== + + [Benchmark] + public byte[]? SeqlockCache_GetOrAdd_Hit() + { + return _seqlockCache.GetOrAdd(in _keys[500], static (in StorageCell _) => new byte[32]); + } + + [Benchmark] + public byte[] ConcurrentDict_GetOrAdd_Hit() + { + return _concurrentDict.GetOrAdd(_keys[500], static _ => new byte[32]); + } + + // ==================== GetOrAdd (Miss - measures factory overhead) ==================== + + private int _missCounter; + + [Benchmark] + public byte[]? SeqlockCache_GetOrAdd_Miss() + { + // Use incrementing key to always miss + var key = new StorageCell(_keys[0].Address, new UInt256((ulong)(KeyCount + _missCounter++))); + return _seqlockCache.GetOrAdd(in key, static (in StorageCell _) => new byte[32]); + } + + [Benchmark] + public byte[] ConcurrentDict_GetOrAdd_Miss() + { + var key = new StorageCell(_keys[0].Address, new UInt256((ulong)(KeyCount + _missCounter++))); + return _concurrentDict.GetOrAdd(key, static _ => new byte[32]); + } +} + +///

+/// Benchmark comparing read-heavy workloads (90% reads, 10% writes) +/// +[MemoryDiagnoser] +public class SeqlockCacheMixedWorkloadBenchmarks +{ + private SeqlockCache _seqlockCache = null!; + private ConcurrentDictionary _concurrentDict = null!; + + private StorageCell[] _keys = null!; + private byte[][] _values = null!; + + private const int KeyCount = 10000; + private const int OperationsPerInvoke = 1000; + + [GlobalSetup] + public void Setup() + { + _seqlockCache = new SeqlockCache(); + _concurrentDict = new ConcurrentDictionary(); + + _keys = new StorageCell[KeyCount]; + _values = new byte[KeyCount][]; + + var random = new Random(42); + for (int i = 0; i < KeyCount; i++) + { + var addressBytes = new byte[20]; + random.NextBytes(addressBytes); + var address = new Address(addressBytes); + var index = new UInt256((ulong)i); + + _keys[i] = new StorageCell(address, index); + _values[i] = new byte[32]; + random.NextBytes(_values[i]); + + // Pre-populate both caches + _seqlockCache.Set(in _keys[i], _values[i]); + _concurrentDict[_keys[i]] = _values[i]; + } + } + + [Benchmark(Baseline = true, OperationsPerInvoke = OperationsPerInvoke)] + public int SeqlockCache_MixedWorkload_90Read_10Write() + { + int hits = 0; + for (int i = 0; i < OperationsPerInvoke; i++) + { + int keyIndex = i % KeyCount; + if (i % 10 == 0) + { + // 10% writes + _seqlockCache.Set(in _keys[keyIndex], _values[keyIndex]); + } + else + { + // 90% reads + if (_seqlockCache.TryGetValue(in _keys[keyIndex], out _)) + hits++; + } + } + return hits; + } + + [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] + public int ConcurrentDict_MixedWorkload_90Read_10Write() + { + int hits = 0; + for (int i = 0; i < OperationsPerInvoke; i++) + { + int keyIndex = i % KeyCount; + if (i % 10 == 0) + { + // 10% writes + _concurrentDict[_keys[keyIndex]] = _values[keyIndex]; + } + else + { + // 90% reads + if (_concurrentDict.TryGetValue(_keys[keyIndex], out _)) + hits++; + } + } + return hits; + } + + [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] + public int SeqlockCache_ReadOnly() + { + int hits = 0; + for (int i = 0; i < OperationsPerInvoke; i++) + { + int keyIndex = i % KeyCount; + if (_seqlockCache.TryGetValue(in _keys[keyIndex], out _)) + hits++; + } + return hits; + } + + [Benchmark(OperationsPerInvoke = OperationsPerInvoke)] + public int ConcurrentDict_ReadOnly() + { + int hits = 0; + for (int i = 0; i < OperationsPerInvoke; i++) + { + int keyIndex = i % KeyCount; + if (_concurrentDict.TryGetValue(_keys[keyIndex], out _)) + hits++; + } + return hits; + } +} + +/// +/// Benchmark measuring effective hit rate after populating with N keys. +/// This directly measures the impact of collision rate. +/// +public class SeqlockCacheHitRateBenchmarks +{ + private SeqlockCache _seqlockCache = null!; + private StorageCell[] _keys = null!; + private byte[][] _values = null!; + + [Params(1000, 5000, 10000, 20000)] + public int KeyCount { get; set; } + + [GlobalSetup] + public void Setup() + { + _seqlockCache = new SeqlockCache(); + _keys = new StorageCell[KeyCount]; + _values = new byte[KeyCount][]; + + var random = new Random(42); + for (int i = 0; i < KeyCount; i++) + { + var addressBytes = new byte[20]; + random.NextBytes(addressBytes); + _keys[i] = new StorageCell(new Address(addressBytes), new UInt256((ulong)i)); + _values[i] = new byte[32]; + random.NextBytes(_values[i]); + _seqlockCache.Set(in _keys[i], _values[i]); + } + } + + [Benchmark] + public double MeasureHitRate() + { + int hits = 0; + for (int i = 0; i < KeyCount; i++) + { + if (_seqlockCache.TryGetValue(in _keys[i], out byte[]? val) && ReferenceEquals(val, _values[i])) + hits++; + } + return (double)hits / KeyCount * 100; + } +} + +[MemoryDiagnoser] +public class SeqlockCacheCallSiteBenchmarks +{ + private SeqlockCache _cache = null!; + private SeqlockCache.ValueFactory _cachedFactory = null!; + private StorageCell _key; + private byte[] _value = null!; + + [GlobalSetup] + public void Setup() + { + _cache = new SeqlockCache(); + + byte[] addressBytes = new byte[20]; + new Random(123).NextBytes(addressBytes); + _key = new StorageCell(new Address(addressBytes), UInt256.One); + _value = new byte[32]; + + _cache.Set(in _key, _value); + _cachedFactory = LoadFromBackingStore; + } + + [Benchmark(Baseline = true)] + public byte[]? GetOrAdd_Hit_PerCallMethodGroup() + { + return _cache.GetOrAdd(in _key, LoadFromBackingStore); + } + + [Benchmark] + public byte[]? GetOrAdd_Hit_CachedDelegate() + { + return _cache.GetOrAdd(in _key, _cachedFactory); + } + + [Benchmark] + public bool TryGetValue_WithIn() + { + return _cache.TryGetValue(in _key, out _); + } + + [Benchmark] + public bool TryGetValue_WithoutIn() + { + return _cache.TryGetValue(_key, out _); + } + + private byte[] LoadFromBackingStore(in StorageCell _) + { + return _value; + } +} diff --git a/src/Nethermind/Nethermind.Benchmark/Scheduler/BackgroundTaskSchedulerBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Scheduler/BackgroundTaskSchedulerBenchmarks.cs new file mode 100644 index 000000000000..5ab4d42ecf03 --- /dev/null +++ b/src/Nethermind/Nethermind.Benchmark/Scheduler/BackgroundTaskSchedulerBenchmarks.cs @@ -0,0 +1,181 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Scheduler; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.TxPool; + +namespace Nethermind.Benchmarks.Scheduler; + +/// +/// Benchmarks the throughput of the BackgroundTaskScheduler under concurrent task +/// scheduling with periodic block-processing pauses — the scenario that caused +/// the "Background task queue is full" issue on synced nodes. +/// +[MemoryDiagnoser] +[SimpleJob(warmupCount: 2, iterationCount: 5)] +public class BackgroundTaskSchedulerBenchmarks +{ + private StubBranchProcessor _branchProcessor = null!; + private StubChainHeadInfoProvider _chainHeadInfo = null!; + + [Params(1024, 2048)] + public int Capacity { get; set; } + + [Params(2)] + public int Concurrency { get; set; } + + [Params(50)] + public int BlockProcessingDurationMs { get; set; } + + [Params(5)] + public int BlockProcessingCycles { get; set; } + + [GlobalSetup] + public void Setup() + { + _branchProcessor = new StubBranchProcessor(); + _chainHeadInfo = new StubChainHeadInfoProvider(); + } + + /// + /// Simulates the real-world scenario: a background producer keeps scheduling tasks + /// while block-processing cycles pause and resume execution. Measures total wall-clock + /// time for scheduling + draining all tasks across several block-processing windows. + /// + [Benchmark] + public async Task ScheduleAndDrainDuringBlockProcessing() + { + await using BackgroundTaskScheduler scheduler = new( + _branchProcessor, _chainHeadInfo, Concurrency, Capacity, LimboLogs.Instance); + + int totalScheduled = 0; + int totalExecuted = 0; + int totalDropped = 0; + + for (int cycle = 0; cycle < BlockProcessingCycles; cycle++) + { + // Simulate block arriving — cancels current tasks, pauses non-expired ones + _branchProcessor.RaiseBlocksProcessing(); + + // Schedule a burst of tasks while block is being processed + int batchSize = Capacity / 2; + for (int i = 0; i < batchSize; i++) + { + bool accepted = scheduler.TryScheduleTask(i, (_, token) => + { + Interlocked.Increment(ref totalExecuted); + return Task.CompletedTask; + }, TimeSpan.FromMilliseconds(BlockProcessingDurationMs + 100)); + + if (accepted) + Interlocked.Increment(ref totalScheduled); + else + Interlocked.Increment(ref totalDropped); + } + + // Simulate block processing time + await Task.Delay(BlockProcessingDurationMs); + + // Block done — resume normal task execution + _branchProcessor.RaiseBlockProcessed(); + + // Wait for all scheduled tasks to drain before next cycle + SpinWait spin = default; + while (Volatile.Read(ref totalExecuted) < Volatile.Read(ref totalScheduled)) + { + spin.SpinOnce(); + if (spin.Count % 100 == 0) + await Task.Yield(); + } + } + } + + /// + /// Measures pure scheduling throughput without block-processing interruptions. + /// Useful as a baseline to compare against . + /// + [Benchmark(Baseline = true)] + public async Task ScheduleAndDrainWithoutBlockProcessing() + { + await using BackgroundTaskScheduler scheduler = new( + _branchProcessor, _chainHeadInfo, Concurrency, Capacity, LimboLogs.Instance); + + int totalScheduled = 0; + int totalExecuted = 0; + + int totalTasks = (Capacity / 2) * BlockProcessingCycles; + for (int i = 0; i < totalTasks; i++) + { + bool accepted = scheduler.TryScheduleTask(i, (_, _) => + { + Interlocked.Increment(ref totalExecuted); + return Task.CompletedTask; + }); + if (accepted) + Interlocked.Increment(ref totalScheduled); + } + + SpinWait spin = default; + while (Volatile.Read(ref totalExecuted) < Volatile.Read(ref totalScheduled)) + { + spin.SpinOnce(); + if (spin.Count % 100 == 0) + await Task.Yield(); + } + } + + /// + /// Minimal stub for to expose events without any real block processing. + /// + private sealed class StubBranchProcessor : IBranchProcessor + { + public event EventHandler? BlockProcessed; + public event EventHandler? BlocksProcessing; +#pragma warning disable CS0067 // Event is never used + public event EventHandler? BlockProcessing; +#pragma warning restore CS0067 + + public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlocks, + ProcessingOptions processingOptions, IBlockTracer blockTracer, CancellationToken token = default) + => []; + + public void RaiseBlocksProcessing() => + BlocksProcessing?.Invoke(this, new BlocksProcessingEventArgs([])); + + public void RaiseBlockProcessed() => + BlockProcessed?.Invoke(this, new BlockProcessedEventArgs(null!, null!)); + } + + /// + /// Minimal stub for — reports node as not syncing. + /// + private sealed class StubChainHeadInfoProvider : IChainHeadInfoProvider + { + public IChainHeadSpecProvider SpecProvider => null!; + public IReadOnlyStateProvider ReadOnlyStateProvider => null!; + public long HeadNumber => 0; + public long? BlockGasLimit => null; + public UInt256 CurrentBaseFee => UInt256.Zero; + public UInt256 CurrentFeePerBlobGas => UInt256.Zero; + public ProofVersion CurrentProofVersion => ProofVersion.V0; + public bool IsSyncing => false; + public bool IsProcessingBlock => false; +#pragma warning disable CS0067 // Event is never used + public event EventHandler? HeadChanged; +#pragma warning restore CS0067 + } +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs index 7288c0d85ac1..abbd0cb530af 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BeaconBlockRootHandlerTests.cs @@ -17,6 +17,8 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class BeaconBlockRootHandlerTests { private BeaconBlockRootHandler _beaconBlockRootHandler; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockFinderExtensionsTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockFinderExtensionsTests.cs index 7665e94c284e..1ad1c053cbd4 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockFinderExtensionsTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockFinderExtensionsTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class BlockFinderExtensionsTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs index 0471540f3e5c..4e180459cf7d 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockProcessorTests.cs @@ -36,6 +36,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class BlockProcessorTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeSuggestPacerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeSuggestPacerTests.cs index 34392783f02f..46a3ea978935 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeSuggestPacerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeSuggestPacerTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class BlockTreeSuggestPacerTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs index 3d327872c84f..d2f2a332753e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs @@ -32,6 +32,8 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class BlockTreeTests { private TestMemDb _blocksInfosDb = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs index 3f9fed9e8859..b9152d322bff 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs @@ -28,7 +28,8 @@ namespace Nethermind.Blockchain.Test; -[Parallelizable(ParallelScope.Self)] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class BlockchainProcessorTests { private class ProcessingTestContext @@ -195,7 +196,7 @@ public ProcessingTestContext(bool startProcessor) .TestObject; _branchProcessor = new BranchProcessorMock(_logManager, _stateReader); _recoveryStep = new RecoveryStepMock(_logManager); - _processor = new BlockchainProcessor(_blockTree, _branchProcessor, _recoveryStep, _stateReader, LimboLogs.Instance, BlockchainProcessor.Options.Default); + _processor = new BlockchainProcessor(_blockTree, _branchProcessor, _recoveryStep, _stateReader, LimboLogs.Instance, BlockchainProcessor.Options.Default, Substitute.For()); _resetEvent = new AutoResetEvent(false); _queueEmptyResetEvent = new AutoResetEvent(false); @@ -283,11 +284,30 @@ public AfterBlock ProcessedFail(Block block) public ProcessingTestContext Suggested(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) { - AddBlockResult result = _blockTree.SuggestBlock(block, options); - if (result != AddBlockResult.Added) + if ((options & BlockTreeSuggestOptions.ShouldProcess) != 0) { - _logger.Info($"Finished waiting for {block.ToString(Block.Format.Short)} as block was ignored"); - _resetEvent.Set(); + // Use Task.Run to avoid blocking when AllowSynchronousContinuations + // causes inline processing on the calling thread + Task.Run(() => + { + AddBlockResult result = _blockTree.SuggestBlock(block, options); + if (result != AddBlockResult.Added) + { + _logger.Info($"Finished waiting for {block.ToString(Block.Format.Short)} as block was ignored"); + _resetEvent.Set(); + } + }); + // Wait for block to be in the tree before returning + SpinWait.SpinUntil(() => _blockTree.IsKnownBlock(block.Number, block.Hash!), ProcessingWait); + } + else + { + AddBlockResult result = _blockTree.SuggestBlock(block, options); + if (result != AddBlockResult.Added) + { + _logger.Info($"Finished waiting for {block.ToString(Block.Format.Short)} as block was ignored"); + _resetEvent.Set(); + } } return this; @@ -328,8 +348,7 @@ public ProcessingTestContext Recovered(Block block) public ProcessingTestContext CountIs(int expectedCount) { - var count = ((IBlockProcessingQueue)_processor).Count; - Assert.That(expectedCount, Is.EqualTo(count)); + Assert.That(() => ((IBlockProcessingQueue)_processor).Count, Is.EqualTo(expectedCount).After(ProcessingWait, 10)); return this; } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs index 1f218bdddc2a..ec9997f9df50 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs @@ -438,6 +438,7 @@ public void BlockhashStore_uses_custom_ring_buffer_size() } [Test, MaxTime(Timeout.MaxTestTime)] + [NonParallelizable] public async Task Prefetches_come_in_wrong_order() { const int chainLength = 261; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BadBlockStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BadBlockStoreTests.cs index dd149c475ccc..f8fefc3679d0 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BadBlockStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BadBlockStoreTests.cs @@ -12,6 +12,7 @@ namespace Nethermind.Blockchain.Test.Blocks; +[Parallelizable(ParallelScope.All)] public class BadBlockStoreTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs index 46c3d9da71cf..861264efda96 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs @@ -15,6 +15,7 @@ namespace Nethermind.Blockchain.Test.Blocks; +[Parallelizable(ParallelScope.All)] public class BlockStoreTests { private readonly Func, EquivalencyAssertionOptions> _ignoreEncodedSize = options => options.Excluding(b => b.EncodedSize); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/HeaderStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/HeaderStoreTests.cs index bc1d967eb9ec..130bda2d4829 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/HeaderStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/HeaderStoreTests.cs @@ -11,9 +11,9 @@ namespace Nethermind.Blockchain.Test.Blocks; +[Parallelizable(ParallelScope.All)] public class HeaderStoreTests { - [Test] public void TestCanStoreAndGetHeader() { diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs b/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs index 86c7706fe3f5..4dbb1cc3e31a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Builders/FilterBuilder.cs @@ -11,14 +11,15 @@ namespace Nethermind.Blockchain.Test.Builders { public class FilterBuilder { - private static int _id; + private int _id; private BlockParameter _fromBlock = new(BlockParameterType.Latest); private BlockParameter _toBlock = new(BlockParameterType.Latest); private AddressFilter _address = AddressFilter.AnyAddress; private SequenceTopicsFilter _topicsFilter = new(); - private FilterBuilder() + private FilterBuilder(int id) { + _id = id; } public static FilterBuilder New() @@ -29,9 +30,9 @@ public static FilterBuilder New() public static FilterBuilder New(ref int currentFilterIndex) { - _id = currentFilterIndex; + int id = currentFilterIndex; currentFilterIndex++; - return new FilterBuilder(); + return new FilterBuilder(id); } public FilterBuilder WithId(int id) diff --git a/src/Nethermind/Nethermind.Blockchain.Test/CachedCodeInfoRepositoryTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/CachedCodeInfoRepositoryTests.cs index 10280c27796c..b933980d7e6b 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/CachedCodeInfoRepositoryTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/CachedCodeInfoRepositoryTests.cs @@ -19,6 +19,7 @@ namespace Nethermind.Blockchain.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] public class CachedCodeInfoRepositoryTests { private static IReleaseSpec CreateSpecWithPrecompile(Address precompileAddress) @@ -35,7 +36,7 @@ public void Precompile_WithCachingEnabled_IsWrappedInCachedPrecompile() TestPrecompile cachingPrecompile = new(supportsCaching: true); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(cachingPrecompile) }.ToFrozenDictionary(); @@ -50,7 +51,7 @@ public void Precompile_WithCachingEnabled_IsWrappedInCachedPrecompile() // Act CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); // Assert codeInfo.Should().NotBeNull(); @@ -65,7 +66,7 @@ public void Precompile_WithCachingDisabled_IsNotWrapped() TestPrecompile nonCachingPrecompile = new(supportsCaching: false); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(nonCachingPrecompile) }.ToFrozenDictionary(); @@ -80,7 +81,7 @@ public void Precompile_WithCachingDisabled_IsNotWrapped() // Act CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); // Assert codeInfo.Should().NotBeNull(); @@ -91,7 +92,7 @@ public void Precompile_WithCachingDisabled_IsNotWrapped() public void IdentityPrecompile_IsNotWrapped_WhenCacheEnabled() { // Arrange - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [IdentityPrecompile.Address] = new(IdentityPrecompile.Instance) }.ToFrozenDictionary(); @@ -106,7 +107,7 @@ public void IdentityPrecompile_IsNotWrapped_WhenCacheEnabled() // Act CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); // Assert codeInfo.Should().NotBeNull(); @@ -121,7 +122,7 @@ public void CachedPrecompile_CachesResults_ForCachingEnabledPrecompile() TestPrecompile cachingPrecompile = new(supportsCaching: true, onRun: () => runCount++); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(cachingPrecompile) }.ToFrozenDictionary(); @@ -135,7 +136,7 @@ public void CachedPrecompile_CachesResults_ForCachingEnabledPrecompile() IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); byte[] input = [1, 2, 3]; @@ -156,7 +157,7 @@ public void NonCachingPrecompile_DoesNotCacheResults() TestPrecompile nonCachingPrecompile = new(supportsCaching: false, onRun: () => runCount++); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(nonCachingPrecompile) }.ToFrozenDictionary(); @@ -170,7 +171,7 @@ public void NonCachingPrecompile_DoesNotCacheResults() IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); byte[] input = [1, 2, 3]; @@ -190,7 +191,7 @@ public void NullCache_DoesNotWrapAnyPrecompiles() TestPrecompile cachingPrecompile = new(supportsCaching: true); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(cachingPrecompile) }.ToFrozenDictionary(); @@ -204,7 +205,7 @@ public void NullCache_DoesNotWrapAnyPrecompiles() // Act - pass null cache CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, null); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); // Assert - precompile should not be wrapped codeInfo.Should().NotBeNull(); @@ -215,7 +216,7 @@ public void NullCache_DoesNotWrapAnyPrecompiles() public void Sha256Precompile_IsWrapped_WhenCacheEnabled() { // Arrange - Sha256Precompile has SupportsCaching = true (default) - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [Sha256Precompile.Address] = new(Sha256Precompile.Instance) }.ToFrozenDictionary(); @@ -230,7 +231,7 @@ public void Sha256Precompile_IsWrapped_WhenCacheEnabled() // Act CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); // Assert - Sha256Precompile should be wrapped (unlike IdentityPrecompile) codeInfo.Should().NotBeNull(); @@ -242,7 +243,7 @@ public void Sha256Precompile_IsWrapped_WhenCacheEnabled() public void MixedPrecompiles_OnlyCachingEnabledAreWrapped() { // Arrange - mix of caching and non-caching precompiles - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [Sha256Precompile.Address] = new(Sha256Precompile.Instance), // SupportsCaching = true [IdentityPrecompile.Address] = new(IdentityPrecompile.Instance) // SupportsCaching = false @@ -263,8 +264,8 @@ public void MixedPrecompiles_OnlyCachingEnabledAreWrapped() // Act CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo sha256CodeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); - ICodeInfo identityCodeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); + CodeInfo sha256CodeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); + CodeInfo identityCodeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); // Assert - Sha256 wrapped, Identity not wrapped sha256CodeInfo.Precompile.Should().NotBeSameAs(Sha256Precompile.Instance); @@ -281,7 +282,7 @@ public void CachedPrecompile_DifferentInputs_CreateSeparateCacheEntries() TestPrecompile cachingPrecompile = new(supportsCaching: true, onRun: () => runCount++); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(cachingPrecompile) }.ToFrozenDictionary(); @@ -295,7 +296,7 @@ public void CachedPrecompile_DifferentInputs_CreateSeparateCacheEntries() IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); byte[] input1 = [1, 2, 3]; byte[] input2 = [4, 5, 6]; @@ -320,7 +321,7 @@ public void CachedPrecompile_ReturnsCachedResult_OnCacheHit() TestPrecompile cachingPrecompile = new(supportsCaching: true, onRun: () => runCount++, fixedOutput: expectedOutput); Address precompileAddress = Address.FromNumber(100); - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [precompileAddress] = new(cachingPrecompile) }.ToFrozenDictionary(); @@ -334,7 +335,7 @@ public void CachedPrecompile_ReturnsCachedResult_OnCacheHit() IReleaseSpec spec = CreateSpecWithPrecompile(precompileAddress); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(precompileAddress, false, spec, out _); byte[] input = [1, 2, 3]; @@ -354,7 +355,7 @@ public void CachedPrecompile_ReturnsCachedResult_OnCacheHit() public void Sha256Precompile_CachesResults_WithRealComputation() { // Arrange - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [Sha256Precompile.Address] = new(Sha256Precompile.Instance) }.ToFrozenDictionary(); @@ -368,7 +369,7 @@ public void Sha256Precompile_CachesResults_WithRealComputation() IReleaseSpec spec = CreateSpecWithPrecompile(Sha256Precompile.Address); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(Sha256Precompile.Address, false, spec, out _); byte[] input = [1, 2, 3, 4, 5]; @@ -387,7 +388,7 @@ public void Sha256Precompile_CachesResults_WithRealComputation() public void IdentityPrecompile_DoesNotCache_WithRealComputation() { // Arrange - FrozenDictionary precompiles = new Dictionary + FrozenDictionary precompiles = new Dictionary { [IdentityPrecompile.Address] = new(IdentityPrecompile.Instance) }.ToFrozenDictionary(); @@ -401,7 +402,7 @@ public void IdentityPrecompile_DoesNotCache_WithRealComputation() IReleaseSpec spec = CreateSpecWithPrecompile(IdentityPrecompile.Address); CachedCodeInfoRepository repository = new(precompileProvider, baseRepository, cache); - ICodeInfo codeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); + CodeInfo codeInfo = repository.GetCachedCodeInfo(IdentityPrecompile.Address, false, spec, out _); byte[] input = [1, 2, 3, 4, 5]; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ChainHeadReadOnlyStateProviderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ChainHeadReadOnlyStateProviderTests.cs index 6711e1283518..092d3b8b14cb 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ChainHeadReadOnlyStateProviderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ChainHeadReadOnlyStateProviderTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class ChainHeadReadOnlyStateProviderTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/ClefSignerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/ClefSignerTests.cs index 54d83e4a3617..4ecb01eee74f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/ClefSignerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/ClefSignerTests.cs @@ -15,6 +15,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class ClefSignerTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/CompositeTxSourceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/CompositeTxSourceTests.cs index cc61b6a7fc4a..6a9b7270cfc5 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/CompositeTxSourceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/CompositeTxSourceTests.cs @@ -14,6 +14,7 @@ namespace Nethermind.Blockchain.Test.Consensus; +[Parallelizable(ParallelScope.All)] public class CompositeTxSourceTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NethDevSealEngineTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NethDevSealEngineTests.cs index d9f0704c5167..e06a1eeaf3d6 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NethDevSealEngineTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NethDevSealEngineTests.cs @@ -12,6 +12,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class NethDevSealEngineTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSealEngineTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSealEngineTests.cs index dcf4bf713101..df8bed08b4f1 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSealEngineTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSealEngineTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class NullSealEngineTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs index 17af0b2ad38a..1de624592784 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs @@ -12,6 +12,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class NullSignerTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/OneByOneTxSourceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/OneByOneTxSourceTests.cs index 354848108367..0c26037ba28e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/OneByOneTxSourceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/OneByOneTxSourceTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class OneByOneTxSourceTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SealEngineExceptionTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SealEngineExceptionTests.cs index 5d5fcb9602c4..c6bef99ca9bc 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SealEngineExceptionTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SealEngineExceptionTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class SealEngineExceptionTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SignerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SignerTests.cs index edd88c1a7224..fe897e8ff3e9 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SignerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SignerTests.cs @@ -16,6 +16,7 @@ namespace Nethermind.Blockchain.Test.Consensus { [TestFixture] + [Parallelizable(ParallelScope.All)] public class SignerTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SinglePendingTxSelectorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SinglePendingTxSelectorTests.cs index feb4d47964c0..ad529fda85ba 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SinglePendingTxSelectorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/SinglePendingTxSelectorTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Consensus { + [Parallelizable(ParallelScope.All)] public class SinglePendingTxSelectorTests { private readonly BlockHeader _anyParent = Build.A.BlockHeader.TestObject; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/DaoDataTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/DaoDataTests.cs index b7483603e603..f2dbf4d95c20 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/DaoDataTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/DaoDataTests.cs @@ -7,6 +7,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class DaoDataTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Data/FileLocalDataSourceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Data/FileLocalDataSourceTests.cs index 03e9008d489f..d13afa2d1143 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Data/FileLocalDataSourceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Data/FileLocalDataSourceTests.cs @@ -16,6 +16,7 @@ namespace Nethermind.Blockchain.Test.Data { + [Parallelizable(ParallelScope.All)] public class FileLocalDataSourceTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ExitOnBlocknumberHandlerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ExitOnBlocknumberHandlerTests.cs index 72da5efe337e..34d04d45bf72 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ExitOnBlocknumberHandlerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ExitOnBlocknumberHandlerTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class ExitOnBlocknumberHandlerTests { [TestCase(10, false)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs index 4fe896388c71..4dde1eadbff2 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Filters; [TestFixture] +[Parallelizable(ParallelScope.All)] public class AddressFilterTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterManagerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterManagerTests.cs index 0b863d6112bc..a8dd1a2a6c9c 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterManagerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterManagerTests.cs @@ -21,11 +21,11 @@ namespace Nethermind.Blockchain.Test.Filters; +[Parallelizable(ParallelScope.None)] public class FilterManagerTests { private FilterStore _filterStore = null!; - private IBranchProcessor _branchProcessor = null!; - private IMainProcessingContext _mainProcessingContext = null!; + private TestMainProcessingContext _mainProcessingContext = null!; private ITxPool _txPool = null!; private ILogManager _logManager = null!; private FilterManager _filterManager = null!; @@ -36,10 +36,8 @@ public class FilterManagerTests public void Setup() { _currentFilterId = 0; - _filterStore = new FilterStore(new TimerFactory(), 20, 10); - _branchProcessor = Substitute.For(); - _mainProcessingContext = Substitute.For(); - _mainProcessingContext.BranchProcessor.Returns(_branchProcessor); + _filterStore = new FilterStore(new TimerFactory(), 400, 100); + _mainProcessingContext = new TestMainProcessingContext(); _txPool = Substitute.For(); _logManager = LimboLogs.Instance; } @@ -55,7 +53,7 @@ public async Task removing_filter_removes_data() { LogsShouldNotBeEmpty(static _ => { }, static _ => { }); _filterManager.GetLogs(0).Should().NotBeEmpty(); - await Task.Delay(60); + await Task.Delay(600); _filterManager.GetLogs(0).Should().BeEmpty(); } @@ -257,6 +255,7 @@ public void logs_should_be_empty_for_existing_block_and_addresses_and_non_existi [Test, MaxTime(Timeout.MaxTestTime)] [TestCase(1, 1)] [TestCase(5, 3)] + [NonParallelizable] public void logs_should_have_correct_log_indexes(int filtersCount, int logsPerTx) { const int txCount = 10; @@ -330,12 +329,12 @@ private void Assert(IEnumerable> filterBuilders, _filterStore.SaveFilters(filters.OfType()); _filterManager = new FilterManager(_filterStore, _mainProcessingContext, _txPool, _logManager); - _branchProcessor.BlockProcessed += Raise.EventWith(_branchProcessor, new BlockProcessedEventArgs(block, [])); + _mainProcessingContext.TestBranchProcessor.RaiseBlockProcessed(new BlockProcessedEventArgs(block, [])); int index = 1; foreach (TxReceipt receipt in receipts) { - _mainProcessingContext.TransactionProcessed += Raise.EventWith(_branchProcessor, + _mainProcessingContext.RaiseTransactionProcessed( new TxProcessedEventArgs(index, Build.A.Transaction.TestObject, block.Header, receipt)); index++; } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs index 80fabb6c8e13..dfec4eac051e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs @@ -18,6 +18,8 @@ namespace Nethermind.Blockchain.Test.Filters; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class FilterStoreTests { [Test, MaxTime(Timeout.MaxTestTime)] @@ -152,6 +154,7 @@ public void Correctly_creates_topics_filter(Hash256[]?[]? topics, TopicsFilter e } [Test, MaxTime(Timeout.MaxTestTime)] + [Parallelizable(ParallelScope.None)] public async Task CleanUps_filters() { List removedFilterIds = new(); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs index 9d497daa7dd0..df6bde0db6b9 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs @@ -11,6 +11,8 @@ namespace Nethermind.Blockchain.Test.Filters; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class LogFilterTests { private int _filterCounter; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogIndexFilterVisitorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogIndexFilterVisitorTests.cs new file mode 100644 index 000000000000..3a901bfaafdf --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogIndexFilterVisitorTests.cs @@ -0,0 +1,413 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Nethermind.Blockchain.Filters; +using Nethermind.Blockchain.Test.Builders; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Db.LogIndex; +using Nethermind.Facade.Filters; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Blockchain.Test.Filters; + +[Parallelizable(ParallelScope.All)] +public class LogIndexFilterVisitorTests +{ + private record Ranges(Dictionary> Address, Dictionary>[] Topic) + { + public List this[Address address] => Address[address]; + public List this[int topicIndex, Hash256 hash] => Topic[topicIndex][hash]; + } + + public class EnumeratorWrapper(int[] array) : IEnumerator + { + private readonly IEnumerator _enumerator = array.Cast().GetEnumerator(); + public bool MoveNext() => _enumerator.MoveNext(); + public void Reset() => _enumerator.Reset(); + public int Current => _enumerator.Current; + object IEnumerator.Current => Current; + public virtual void Dispose() => _enumerator.Dispose(); + } + + [TestCase( + new[] { 1, 3, 5, 7, 9, }, + new[] { 0, 2, 4, 6, 8 }, + TestName = "Non-intersecting, but similar ranges" + )] + [TestCase( + new[] { 1, 2, 3, 4, 5, }, + new[] { 5, 6, 7, 8, 9 }, + TestName = "Intersects on first/last" + )] + [TestCase( + new[] { 1, 2, 3, 4, 5, }, + new[] { 6, 7, 8, 9, 10 }, + TestName = "Non-intersecting ranges" + )] + public void IntersectEnumerator(int[] s1, int[] s2) + { + var expected = s1.Intersect(s2).Order().ToArray(); + + VerifyEnumerator(s1, s2, expected); + VerifyEnumerator(s2, s1, expected); + } + + [TestCase(1, 1)] + [TestCase(20, 20)] + [TestCase(20, 100)] + [TestCase(100, 100)] + [TestCase(1000, 1000)] + public void IntersectEnumerator_Random(int len1, int len2) + { + var random = new Random(42); + var s1 = RandomAscending(random, len1, Math.Max(1, len1 / 10)); + var s2 = RandomAscending(random, len2, Math.Max(1, len2 / 10)); + + var expected = s1.Intersect(s2).Order().ToArray(); + Assert.That(expected, Is.Not.Empty, "Unreliable test: Needs non-empty sequence to verify against."); + + VerifyEnumerator(s1, s2, expected); + VerifyEnumerator(s2, s1, expected); + } + + [TestCase(0, 0)] + [TestCase(0, 1)] + [TestCase(0, 10)] + public void IntersectEnumerator_SomeEmpty(int len1, int len2) + { + var s1 = Enumerable.Range(0, len1).ToArray(); + var s2 = Enumerable.Range(0, len2).ToArray(); + + VerifyEnumerator(s1, s2, []); + VerifyEnumerator(s2, s1, []); + } + + [TestCase( + new[] { 1, 2, 3, 4, 5, }, + new[] { 2, 3, 4 }, + TestName = "Contained" + )] + [TestCase( + new[] { 1, 2, 3, 4, 5, }, + new[] { 1, 2, 3, 4, 5, }, + TestName = "Identical" + )] + [TestCase( + new[] { 1, 3, 5, 7, 9, }, + new[] { 2, 4, 6, 8, 10 }, + TestName = "Complementary" + )] + public void UnionEnumerator(int[] s1, int[] s2) + { + var expected = s1.Union(s2).Distinct().Order().ToArray(); + + VerifyEnumerator(s1, s2, expected); + VerifyEnumerator(s2, s1, expected); + } + + [TestCase(1, 1)] + [TestCase(20, 20)] + [TestCase(20, 100)] + [TestCase(100, 100)] + [TestCase(1000, 1000)] + public void UnionEnumerator_Random(int len1, int len2) + { + var random = new Random(42); + var s1 = RandomAscending(random, len1, Math.Max(1, len1 / 10)); + var s2 = RandomAscending(random, len2, Math.Max(1, len2 / 10)); + + var expected = s1.Union(s2).Distinct().Order().ToArray(); + + VerifyEnumerator(s1, s2, expected); + VerifyEnumerator(s2, s1, expected); + } + + [TestCase(0, 0)] + [TestCase(0, 1)] + [TestCase(0, 10)] + public void UnionEnumerator_SomeEmpty(int len1, int len2) + { + var s1 = Enumerable.Range(0, len1).ToArray(); + var s2 = Enumerable.Range(0, len2).ToArray(); + + var expected = s1.Union(s2).Distinct().Order().ToArray(); + + VerifyEnumerator(s1, s2, expected); + VerifyEnumerator(s2, s1, expected); + } + + [TestCaseSource(nameof(FilterTestData))] + public void FilterEnumerator(string name, LogFilter filter, List expected) + { + Assert.That(expected, + Has.Count.InRange(from: 1, to: ToBlock - FromBlock - 1), + "Unreliable test: none or all blocks are selected." + ); + ILogIndexStorage storage = Substitute.For(); + + foreach ((Address address, List range) in LogIndexRanges.Address) + { + storage + .GetEnumerator(address, Arg.Any(), Arg.Any()) + .Returns(info => range.SkipWhile(x => x < info.ArgAt(1)).TakeWhile(x => x <= info.ArgAt(2)).GetEnumerator()); + } + + for (var i = 0; i < LogIndexRanges.Topic.Length; i++) + { + foreach ((Hash256 topic, List range) in LogIndexRanges.Topic[i]) + { + storage + .GetEnumerator(Arg.Is(i), topic, Arg.Any(), Arg.Any()) + .Returns(info => range.SkipWhile(x => x < info.ArgAt(2)).TakeWhile(x => x <= info.ArgAt(3)).GetEnumerator()); + } + } + + Assert.That(storage.EnumerateBlockNumbersFor(filter, FromBlock, ToBlock), Is.EquivalentTo(expected)); + } + + [TestCaseSource(nameof(FilterTestData))] + public void FilterEnumerator_Dispose(string name, LogFilter filter, List _) + { + int[] blockNumbers = [1, 2, 3, 4, 5]; + List> enumerators = []; + + ILogIndexStorage storage = Substitute.For(); + storage.GetEnumerator(Arg.Any
(), Arg.Any(), Arg.Any()).Returns(_ => MockEnumerator()); + storage.GetEnumerator(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()).Returns(_ => MockEnumerator()); + + storage.EnumerateBlockNumbersFor(filter, FromBlock, ToBlock).ForEach(_ => { }); + + enumerators.ForEach(enumerator => enumerator.Received().Dispose()); + + IEnumerator MockEnumerator() + { + IEnumerator? enumerator = Substitute.ForPartsOf(blockNumbers); + enumerators.Add(enumerator); + return enumerator; + } + } + + public static IEnumerable FilterTestData + { + get + { + yield return new TestCaseData( + "AddressA", // name + + BuildFilter() // filter + .WithAddress(TestItem.AddressA) + .Build(), + + LogIndexRanges[TestItem.AddressA] // expected range + ); + + yield return new TestCaseData( + "AddressA or AddressA", + + BuildFilter() + .WithAddresses(TestItem.AddressA, TestItem.AddressA) + .Build(), + + LogIndexRanges[TestItem.AddressA] + ); + + yield return new TestCaseData( + "AddressA or AddressB", + + BuildFilter() + .WithAddresses(TestItem.AddressA, TestItem.AddressB) + .Build(), + + LogIndexRanges[TestItem.AddressA].Union(LogIndexRanges[TestItem.AddressB]) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "TopicA", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Specific(TestItem.KeccakA) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA] + ); + + yield return new TestCaseData( + "TopicA or TopicA", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Or(TestItem.KeccakA, TestItem.KeccakA) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA] + ); + + yield return new TestCaseData( + "TopicA or TopicB", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Or(TestItem.KeccakA, TestItem.KeccakB) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA].Union(LogIndexRanges[0, TestItem.KeccakB]) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "TopicA, TopicB", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Specific(TestItem.KeccakA), + TestTopicExpressions.Specific(TestItem.KeccakB) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA] + .Intersect(LogIndexRanges[1, TestItem.KeccakB]) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "TopicA, -, TopicA", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Specific(TestItem.KeccakA), + TestTopicExpressions.Any, + TestTopicExpressions.Specific(TestItem.KeccakA) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA] + .Intersect(LogIndexRanges[2, TestItem.KeccakA]) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "TopicA, -, TopicB or TopicC", + + BuildFilter() + .WithTopicExpressions( + TestTopicExpressions.Specific(TestItem.KeccakA), + TestTopicExpressions.Any, + TestTopicExpressions.Or(TestItem.KeccakB, TestItem.KeccakC) + ).Build(), + LogIndexRanges[0, TestItem.KeccakA] + .Intersect(LogIndexRanges[2, TestItem.KeccakB].Union(LogIndexRanges[2, TestItem.KeccakC])) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "AddressA | TopicA or TopicB, TopicC", + + BuildFilter() + .WithAddress(TestItem.AddressA) + .WithTopicExpressions( + TestTopicExpressions.Or(TestItem.KeccakA, TestItem.KeccakB), + TestTopicExpressions.Specific(TestItem.KeccakC) + ).Build(), + + LogIndexRanges[0, TestItem.KeccakA].Union(LogIndexRanges[0, TestItem.KeccakB]) + .Intersect(LogIndexRanges[1, TestItem.KeccakC]) + .Intersect(LogIndexRanges[TestItem.AddressA]) + .Distinct().Order().ToList() + ); + + yield return new TestCaseData( + "AddressA or AddressB | TopicA or TopicB, -, TopicC, TopicD or TopicE", + + BuildFilter() + .WithAddresses(TestItem.AddressA, TestItem.AddressB) + .WithTopicExpressions( + TestTopicExpressions.Or(TestItem.KeccakA, TestItem.KeccakB), + TestTopicExpressions.Any, + TestTopicExpressions.Specific(TestItem.KeccakC), + TestTopicExpressions.Or(TestItem.KeccakD, TestItem.KeccakE) + ).Build(), + + LogIndexRanges[TestItem.AddressA].Union(LogIndexRanges[TestItem.AddressB]) + .Intersect(LogIndexRanges[0, TestItem.KeccakA].Union(LogIndexRanges[0, TestItem.KeccakB])) + .Intersect(LogIndexRanges[2, TestItem.KeccakC]) + .Intersect(LogIndexRanges[3, TestItem.KeccakD].Union(LogIndexRanges[3, TestItem.KeccakE])) + .Distinct().Order().ToList() + ); + } + } + + private static int[] RandomAscending(Random random, int count, int maxDelta) + { + var result = new int[count]; + + for (var i = 0; i < result.Length; i++) + { + var min = i > 0 ? result[i - 1] : -1; + result[i] = random.Next(min + 1, min + 1 + maxDelta); + } + + return result; + } + + private static void VerifyEnumerator(int[] s1, int[] s2, int[] ex) + where T : IEnumerator + { + using var enumerator = (T)Activator.CreateInstance( + typeof(T), + s1.Cast().GetEnumerator(), + s2.Cast().GetEnumerator() + )!; + + Assert.That(EnumerateOnce(enumerator), Is.EqualTo(ex)); + } + + private static IEnumerable EnumerateOnce(IEnumerator enumerator) + { + while (enumerator.MoveNext()) + yield return enumerator.Current; + } + + private const long FromBlock = 0; + private const long ToBlock = 99; + + private static readonly Ranges LogIndexRanges = GenerateLogIndexRanges(); + + private static Ranges GenerateLogIndexRanges() + { + var random = new Random(42); + + var addressRanges = new Dictionary>(); + foreach (Address address in new[] { TestItem.AddressA, TestItem.AddressB, TestItem.AddressC, TestItem.AddressD, TestItem.AddressE }) + { + var range = Enumerable.Range((int)FromBlock, (int)(ToBlock + 1)).Where(_ => random.NextDouble() < 0.3).ToList(); + addressRanges.Add(address, range); + } + + Dictionary>[] topicRanges = Enumerable + .Range(0, LogIndexStorage.MaxTopics) + .Select(_ => new Dictionary>()).ToArray(); + + foreach (Dictionary> ranges in topicRanges) + { + foreach (Hash256 topic in new[] { TestItem.KeccakA, TestItem.KeccakB, TestItem.KeccakC, TestItem.KeccakD, TestItem.KeccakE }) + { + var range = Enumerable.Range((int)FromBlock, (int)(ToBlock + 1)).Where(_ => random.NextDouble() < 0.2).ToList(); + ranges.Add(topic, range); + } + } + + return new(addressRanges, topicRanges); + } + + private static FilterBuilder BuildFilter() => FilterBuilder.New() + .FromBlock(FromBlock) + .ToBlock(ToBlock); +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/TestProcessingContext.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/TestProcessingContext.cs new file mode 100644 index 000000000000..5b089c3f11c3 --- /dev/null +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/TestProcessingContext.cs @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Evm.State; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; + +namespace Nethermind.Blockchain.Test.Filters; + +/// +/// Test implementation of IBranchProcessor that allows manual event raising. +/// +internal class TestBranchProcessor : IBranchProcessor +{ + public event EventHandler? BlockProcessed; + public event EventHandler? BlocksProcessing { add { } remove { } } + public event EventHandler? BlockProcessing { add { } remove { } } + + public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlocks, + ProcessingOptions processingOptions, IBlockTracer blockTracer, CancellationToken token = default) + => []; + + public void RaiseBlockProcessed(BlockProcessedEventArgs args) + => BlockProcessed?.Invoke(this, args); +} + +/// +/// Test implementation of IMainProcessingContext that allows manual event raising. +/// +internal class TestMainProcessingContext : IMainProcessingContext +{ + private readonly TestBranchProcessor _branchProcessor = new(); + + public ITransactionProcessor TransactionProcessor => null!; + public IBranchProcessor BranchProcessor => _branchProcessor; + public IBlockProcessor BlockProcessor => null!; + public IBlockchainProcessor BlockchainProcessor => null!; + public IBlockProcessingQueue BlockProcessingQueue => null!; + public IWorldState WorldState => null!; + public IGenesisLoader GenesisLoader => null!; + + public event EventHandler? TransactionProcessed; + + public TestBranchProcessor TestBranchProcessor => _branchProcessor; + + public void RaiseTransactionProcessed(TxProcessedEventArgs args) + => TransactionProcessed?.Invoke(this, args); +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs index e8ae192041b0..ae11d8419b65 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Find/LogFinderTests.cs @@ -19,13 +19,17 @@ using Nethermind.Db; using Nethermind.Logging; using Nethermind.Db.Blooms; +using Nethermind.Db.LogIndex; using Nethermind.Facade.Filters; using Nethermind.Facade.Find; using NSubstitute; using NUnit.Framework; +using NUnit.Framework.Internal; namespace Nethermind.Blockchain.Test.Find; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class LogFinderTests { private IBlockTree _blockTree = null!; @@ -45,19 +49,19 @@ public void SetUp() [TearDown] public void TearDown() => _bloomStorage?.Dispose(); - private void SetUp(bool allowReceiptIterator) + private void SetUp(bool allowReceiptIterator, int chainLength = 5) { var specProvider = Substitute.For(); specProvider.GetSpec(Arg.Any()).IsEip155Enabled.Returns(true); _receiptStorage = new InMemoryReceiptStorage(allowReceiptIterator); _rawBlockTree = Build.A.BlockTree() .WithTransactions(_receiptStorage, LogsForBlockBuilder) - .OfChainLength(out _headTestBlock, 5) + .OfChainLength(out _headTestBlock, chainLength) .TestObject; _blockTree = _rawBlockTree; _bloomStorage = new BloomStorage(new BloomConfig(), new MemDb(), new InMemoryDictionaryFileStoreFactory()); _receiptsRecovery = Substitute.For(); - _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = CreateLogFinder(); } private void SetupHeadWithNoTransaction() @@ -129,15 +133,8 @@ public void filter_all_logs_iteratively([ValueSource(nameof(WithBloomValues))] b SetUp(allowReceiptIterator); LogFilter logFilter = AllBlockFilter().Build(); FilterLog[] logs = _logFinder.FindLogs(logFilter).ToArray(); - logs.Length.Should().Be(5); var indexes = logs.Select(static l => (int)l.LogIndex).ToArray(); - // indexes[0].Should().Be(0); - // indexes[1].Should().Be(1); - // indexes[2].Should().Be(0); - // indexes[3].Should().Be(1); - // indexes[4].Should().Be(2); - // BeEquivalentTo does not check the ordering!!! :O - indexes.Should().BeEquivalentTo(new[] { 0, 1, 0, 1, 2 }); + Assert.That(indexes, Is.EqualTo([0, 1, 0, 1, 2])); } [Test, MaxTime(Timeout.MaxTestTime)] @@ -145,7 +142,7 @@ public void throw_exception_when_receipts_are_missing([ValueSource(nameof(WithBl { StoreTreeBlooms(withBloomDb); _receiptStorage = NullReceiptStorage.Instance; - _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = CreateLogFinder(); var logFilter = AllBlockFilter().Build(); @@ -158,7 +155,7 @@ public void throw_exception_when_receipts_are_missing([ValueSource(nameof(WithBl public void when_receipts_are_missing_and_header_has_no_receipt_root_do_not_throw_exception_() { _receiptStorage = NullReceiptStorage.Instance; - _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = CreateLogFinder(); SetupHeadWithNoTransaction(); @@ -174,7 +171,7 @@ public void filter_all_logs_should_throw_when_to_block_is_not_found([ValueSource { StoreTreeBlooms(withBloomDb); var blockFinder = Substitute.For(); - _logFinder = new LogFinder(blockFinder, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = CreateLogFinder(blockFinder); var logFilter = AllBlockFilter().Build(); var action = new Func>(() => _logFinder.FindLogs(logFilter)); action.Should().Throw(); @@ -277,14 +274,13 @@ public void filter_by_blocks(LogFilter filter, int expectedCount, bool withBloom public void filter_by_blocks_with_limit([ValueSource(nameof(WithBloomValues))] bool withBloomDb) { StoreTreeBlooms(withBloomDb); - _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery, 2); + _logFinder = CreateLogFinder(); var filter = FilterBuilder.New().FromLatestBlock().ToLatestBlock().Build(); var logs = _logFinder.FindLogs(filter).ToArray(); logs.Length.Should().Be(3); } - public static IEnumerable ComplexFilterTestsData { get @@ -316,6 +312,7 @@ public void complex_filter(LogFilter filter, int expectedCount, bool withBloomDb } [Test, MaxTime(Timeout.MaxTestTime)] + [NonParallelizable] public async Task Throw_log_finder_operation_canceled_after_given_timeout([Values(2, 0.01)] double waitTime) { var timeout = TimeSpan.FromMilliseconds(Timeout.MaxWaitTime); @@ -323,24 +320,122 @@ public async Task Throw_log_finder_operation_canceled_after_given_timeout([Value CancellationToken cancellationToken = cancellationTokenSource.Token; StoreTreeBlooms(true); - _logFinder = new LogFinder(_blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); + _logFinder = CreateLogFinder(); var logFilter = AllBlockFilter().Build(); var logs = _logFinder.FindLogs(logFilter, cancellationToken); await Task.Delay(timeout * waitTime); - Func action = () => logs.ToArray(); + TestDelegate action = () => _ = logs.ToArray(); if (waitTime > 1) { - action.Should().Throw().WithInnerException(); + Assert.That(action, Throws + .Exception.InstanceOf() + .Or.InnerException.InstanceOf() // PLINQ can wrap into AggregateException + ); } else { - action.Should().NotThrow(); + Assert.DoesNotThrow(action); } } + [TestCase("Empty index", + 1, 2, + null, null, + null, null + )] + [TestCase("No intersection, left", + 1, 2, + 4, 6, + null, null + )] + [TestCase("No intersection, adjacent left", + 1, 3, + 4, 6, + null, null + )] + [TestCase("1 block intersection, left", + 1, 4, + 4, 6, + 4, 4 + )] + [TestCase("Partial intersection, left", + 1, 5, + 4, 6, + 4, 5 + )] + [TestCase("Full containment, border right", + 1, 6, + 4, 6, + 4, 6 + )] + [TestCase("Full containment", + 1, 9, + 4, 6, + 4, 6 + )] + [TestCase("Full containment, border left", + 4, 9, + 4, 6, + 4, 6 + )] + [TestCase("Partial intersection, right", + 5, 9, + 4, 6, + 5, 6 + )] + [TestCase("1 block intersection, right", + 6, 9, + 4, 6, + 6, 6 + )] + [TestCase("No intersection, adjacent right", + 7, 9, + 4, 6, + null, null + )] + [TestCase("No intersection, right", + 8, 9, + 4, 6, + null, null + )] + public void query_intersected_range_from_log_index(string name, + int from, int to, + int? indexFrom, int? indexTo, + int? exFrom, int? exTo + ) + { + SetUp(true, chainLength: 10); + + var logIndexStorage = Substitute.For(); + logIndexStorage.Enabled.Returns(true); + logIndexStorage.MinBlockNumber.Returns(indexFrom); + logIndexStorage.MaxBlockNumber.Returns(indexTo); + logIndexStorage.GetEnumerator(Arg.Any
(), Arg.Any(), Arg.Any()) + .Returns(_ => Array.Empty().Cast().GetEnumerator()); + + Address address = TestItem.AddressA; + BlockHeader fromHeader = Build.A.BlockHeader.WithNumber(from).TestObject; + BlockHeader toHeader = Build.A.BlockHeader.WithNumber(to).TestObject; + LogFilter filter = FilterBuilder.New() + .FromBlock(from).ToBlock(to) + .WithAddress(address) + .Build(); + + var logFinder = new IndexedLogFinder( + _blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery, + logIndexStorage, minBlocksToUseIndex: 1 + ); + _ = logFinder.FindLogs(filter, fromHeader, toHeader).ToArray(); + + if (exTo is not null && exFrom is not null) + logIndexStorage.Received(1).GetEnumerator(address, exFrom.Value, exTo.Value); + else + logIndexStorage.DidNotReceiveWithAnyArgs().GetEnumerator(Arg.Any
(), Arg.Any(), Arg.Any()); + } + private static FilterBuilder AllBlockFilter() => FilterBuilder.New().FromEarliestBlock().ToPendingBlock(); private void StoreTreeBlooms(bool withBloomDb) @@ -354,4 +449,6 @@ private void StoreTreeBlooms(bool withBloomDb) } } + private LogFinder CreateLogFinder(IBlockFinder? blockFinder = null) => + new(blockFinder ?? _blockTree, _receiptStorage, _receiptStorage, _bloomStorage, LimboLogs.Instance, _receiptsRecovery); } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs index 7c5aae0c8b2b..406ae0c89e36 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs @@ -21,18 +21,11 @@ namespace Nethermind.Blockchain.Test.FullPruning; -[Parallelizable(ParallelScope.Self)] +[Parallelizable(ParallelScope.All)] [TestFixture(INodeStorage.KeyScheme.HalfPath)] [TestFixture(INodeStorage.KeyScheme.Hash)] -public class CopyTreeVisitorTests +public class CopyTreeVisitorTests(INodeStorage.KeyScheme scheme) { - private readonly INodeStorage.KeyScheme _keyScheme; - - public CopyTreeVisitorTests(INodeStorage.KeyScheme scheme) - { - _keyScheme = scheme; - } - [TestCase(0, 1)] [TestCase(0, 8)] [TestCase(1, 1)] @@ -83,7 +76,7 @@ public void cancel_coping_state_between_dbs() private IPruningContext CopyDb(IPruningContext pruningContext, CancellationToken cancellationToken, MemDb trieDb, VisitingOptions? visitingOptions = null, WriteFlags writeFlags = WriteFlags.None) { LimboLogs logManager = LimboLogs.Instance; - PatriciaTree trie = Build.A.Trie(new NodeStorage(trieDb, _keyScheme)).WithAccountsByIndex(0, 100).TestObject; + PatriciaTree trie = Build.A.Trie(new NodeStorage(trieDb, scheme)).WithAccountsByIndex(0, 100).TestObject; // Create a custom DbProvider that uses the trieDb from the test IDbProvider dbProvider = Substitute.For(); @@ -94,16 +87,16 @@ private IPruningContext CopyDb(IPruningContext pruningContext, CancellationToken (IWorldState worldState, IStateReader stateReader) = TestWorldStateFactory.CreateForTestWithStateReader(dbProvider, logManager); BlockHeader? baseBlock = Build.A.BlockHeader.WithStateRoot(trie.RootHash).TestObject; - if (_keyScheme == INodeStorage.KeyScheme.Hash) + if (scheme == INodeStorage.KeyScheme.Hash) { - NodeStorage nodeStorage = new NodeStorage(pruningContext, _keyScheme); + NodeStorage nodeStorage = new NodeStorage(pruningContext, scheme); using CopyTreeVisitor copyTreeVisitor = new(nodeStorage, writeFlags, logManager, cancellationToken); stateReader.RunTreeVisitor(copyTreeVisitor, baseBlock, visitingOptions); copyTreeVisitor.Finish(); } else { - NodeStorage nodeStorage = new NodeStorage(pruningContext, _keyScheme); + NodeStorage nodeStorage = new NodeStorage(pruningContext, scheme); using CopyTreeVisitor copyTreeVisitor = new(nodeStorage, writeFlags, logManager, cancellationToken); stateReader.RunTreeVisitor(copyTreeVisitor, baseBlock, visitingOptions); copyTreeVisitor.Finish(); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPrunerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPrunerTests.cs index c13e1e2482cc..d9b07e032cb3 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPrunerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPrunerTests.cs @@ -30,18 +30,10 @@ namespace Nethermind.Blockchain.Test.FullPruning; [TestFixture(0, 4)] [TestFixture(1, 1)] [TestFixture(1, 4)] -[Parallelizable(ParallelScope.Children)] -public class FullPrunerTests +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class FullPrunerTests(int fullPrunerMemoryBudgetMb, int degreeOfParallelism) { - private readonly int _fullPrunerMemoryBudgetMb; - private readonly int _degreeOfParallelism; - - public FullPrunerTests(int fullPrunerMemoryBudgetMb, int degreeOfParallelism) - { - _fullPrunerMemoryBudgetMb = fullPrunerMemoryBudgetMb; - _degreeOfParallelism = degreeOfParallelism; - } - [Test, MaxTime(Timeout.MaxTestTime)] public async Task can_prune() { @@ -61,8 +53,8 @@ public async Task can_prune_and_switch_key_scheme(INodeStorage.KeyScheme current true, false, FullPruningCompletionBehavior.None, - _fullPrunerMemoryBudgetMb, - _degreeOfParallelism, + fullPrunerMemoryBudgetMb, + degreeOfParallelism, currentKeyScheme: currentKeyScheme, preferredKeyScheme: newKeyScheme); @@ -192,8 +184,8 @@ private TestContext CreateTest( successfulPruning, clearPrunedDb, completionBehavior, - _fullPrunerMemoryBudgetMb, - _degreeOfParallelism); + fullPrunerMemoryBudgetMb, + degreeOfParallelism); private class TestContext { diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs index d28cf1ab7be1..98483a553cce 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs @@ -30,6 +30,7 @@ namespace Nethermind.Blockchain.Test.FullPruning; +[Parallelizable(ParallelScope.All)] public class FullPruningDiskTest { public class PruningTestBlockchain : TestBlockchain @@ -109,27 +110,24 @@ public static async Task Create(IPruningConfig? pruningCo return chain; } - public class FullTestPruner : FullPruner + public class FullTestPruner( + IFullPruningDb pruningDb, + INodeStorageFactory nodeStorageFactory, + INodeStorage mainNodeStorage, + IPruningTrigger pruningTrigger, + IPruningConfig pruningConfig, + IBlockTree blockTree, + IStateReader stateReader, + IProcessExitSource processExitSource, + IDriveInfo driveInfo, + IPruningTrieStore trieStore, + IChainEstimations chainEstimations, + ILogManager logManager) + : FullPruner(pruningDb, nodeStorageFactory, mainNodeStorage, pruningTrigger, pruningConfig, blockTree, + stateReader, processExitSource, chainEstimations, driveInfo, trieStore, logManager) { public EventWaitHandle WaitHandle { get; } = new ManualResetEvent(false); - public FullTestPruner( - IFullPruningDb pruningDb, - INodeStorageFactory nodeStorageFactory, - INodeStorage mainNodeStorage, - IPruningTrigger pruningTrigger, - IPruningConfig pruningConfig, - IBlockTree blockTree, - IStateReader stateReader, - IProcessExitSource processExitSource, - IDriveInfo driveInfo, - IPruningTrieStore trieStore, - IChainEstimations chainEstimations, - ILogManager logManager) - : base(pruningDb, nodeStorageFactory, mainNodeStorage, pruningTrigger, pruningConfig, blockTree, stateReader, processExitSource, chainEstimations, driveInfo, trieStore, logManager) - { - } - protected override async Task RunFullPruning(CancellationToken cancellationToken) { await base.RunFullPruning(cancellationToken); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/PruningTriggerPruningStrategyTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/PruningTriggerPruningStrategyTests.cs index 505f6227fb29..fc43e2dce333 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/PruningTriggerPruningStrategyTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/PruningTriggerPruningStrategyTests.cs @@ -12,7 +12,8 @@ namespace Nethermind.Blockchain.Test.FullPruning { [TestFixture] - [Parallelizable(ParallelScope.Self)] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class PruningTriggerPruningStrategyTests { private IFullPruningDb _fullPruningDb; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockOnEachPendingTxTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockOnEachPendingTxTests.cs index fb7dcde6abf1..f1d61f1a256e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockOnEachPendingTxTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockOnEachPendingTxTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class BuildBlockOnEachPendingTxTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockRegularlyTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockRegularlyTests.cs index 4aa9dac4cbba..c1279a679d17 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockRegularlyTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlockRegularlyTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class BuildBlockRegularlyTests { [Test, MaxTime(Timeout.MaxTestTime), Retry(3)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksWhenRequestedTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksWhenRequestedTests.cs index a244c84aacb9..d3c709b6ee11 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksWhenRequestedTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BuildBlocksWhenRequestedTests.cs @@ -7,6 +7,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class BuildBlocksWhenRequestedTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/CompositeBlockProductionTriggerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/CompositeBlockProductionTriggerTests.cs index 478c37bbebc4..d493795f1b3e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/CompositeBlockProductionTriggerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/CompositeBlockProductionTriggerTests.cs @@ -7,6 +7,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class CompositeBlockProductionTriggerTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs index 733b73a3c804..b8bc669d2a8c 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/DevBlockproducerTests.cs @@ -17,6 +17,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class DevBlockProducerTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/IfPoolIsNotEmptyTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/IfPoolIsNotEmptyTests.cs index 3f2d97ac18be..14549530b899 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/IfPoolIsNotEmptyTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/IfPoolIsNotEmptyTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Blockchain.Test.Producers; +[Parallelizable(ParallelScope.All)] public class IfPoolIsNotEmptyTests { [MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/ReceiptTrieTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/ReceiptTrieTests.cs index 0533fde1a350..82c883a83576 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/ReceiptTrieTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/ReceiptTrieTests.cs @@ -17,6 +17,7 @@ namespace Nethermind.Blockchain.Test.Proofs; +[Parallelizable(ParallelScope.All)] public class ReceiptTrieTests { private static readonly IRlpStreamDecoder _decoder = Rlp.GetStreamDecoder()!; @@ -57,6 +58,28 @@ public void Can_collect_proof_with_branch() VerifyProof(proof, trie.RootHash); } + [Test, MaxTime(Timeout.MaxTestTime)] + public void Parallel_and_non_parallel_root_hashing_produce_same_root() + { + const int receiptCount = 100; + IReleaseSpec spec = MainnetSpecProvider.Instance.GetSpec((MainnetSpecProvider.MuirGlacierBlockNumber, null)); + TxReceipt[] receipts = new TxReceipt[receiptCount]; + for (int i = 0; i < receiptCount; i++) + { + receipts[i] = Build.A.Receipt.WithAllFieldsFilled.WithGasUsedTotal(1000 + i).TestObject; + } + + using TrackingCappedArrayPool parallelPool = new(receiptCount * 4, canBeParallel: true); + ReceiptTrie parallelTrie = new(spec, receipts, _decoder, parallelPool, canBeParallel: true); + Hash256 parallelRoot = parallelTrie.RootHash; + + using TrackingCappedArrayPool sequentialPool = new(receiptCount * 4, canBeParallel: false); + ReceiptTrie sequentialTrie = new(spec, receipts, _decoder, sequentialPool, canBeParallel: false); + Hash256 sequentialRoot = sequentialTrie.RootHash; + + Assert.That(sequentialRoot, Is.EqualTo(parallelRoot)); + } + private void VerifyProof(byte[][] proof, Hash256 receiptRoot) { TrieNode node = new(NodeType.Unknown, proof.Last()); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/TxTrieTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/TxTrieTests.cs index 4f8e912e1887..02b86b29a6a3 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/TxTrieTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/TxTrieTests.cs @@ -7,6 +7,7 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; +using Nethermind.Int256; using Nethermind.Serialization.Rlp; using Nethermind.Specs.Forks; using Nethermind.State.Proofs; @@ -17,14 +18,11 @@ namespace Nethermind.Blockchain.Test.Proofs; [TestFixture(true)] [TestFixture(false)] -public class TxTrieTests +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class TxTrieTests(bool useEip2718) { - private readonly IReleaseSpec _releaseSpec; - - public TxTrieTests(bool useEip2718) - { - _releaseSpec = useEip2718 ? Berlin.Instance : MuirGlacier.Instance; - } + private readonly IReleaseSpec _releaseSpec = useEip2718 ? Berlin.Instance : MuirGlacier.Instance; [Test, MaxTime(Timeout.MaxTestTime)] public void Can_calculate_root() @@ -82,6 +80,46 @@ public void Can_collect_proof_with_trie_case_3_modified() } } + [Test, MaxTime(Timeout.MaxTestTime)] + public void Encoded_and_decoded_transaction_paths_have_same_root() + { + Transaction[] transactions = + [ + Build.A.Transaction.WithNonce(1).WithType(TxType.Legacy).Signed().TestObject, + Build.A.Transaction.WithNonce(2).WithType(useEip2718 ? TxType.EIP1559 : TxType.Legacy).Signed().TestObject, + Build.A.Transaction.WithNonce(3).WithType(useEip2718 ? TxType.AccessList : TxType.Legacy).Signed().TestObject, + ]; + + byte[][] encodedTransactions = transactions + .Select(static tx => Rlp.Encode(tx, RlpBehaviors.SkipTypedWrapping).Bytes) + .ToArray(); + + Hash256 decodedRoot = TxTrie.CalculateRoot(transactions); + Hash256 encodedRoot = TxTrie.CalculateRoot(encodedTransactions); + + Assert.That(encodedRoot, Is.EqualTo(decodedRoot)); + } + + [Test, MaxTime(Timeout.MaxTestTime)] + public void Parallel_and_non_parallel_root_hashing_produce_same_root() + { + const int txCount = 100; + Transaction[] transactions = new Transaction[txCount]; + for (int i = 0; i < txCount; i++) + { + transactions[i] = Build.A.Transaction.WithNonce((UInt256)(i + 1)).Signed().TestObject; + } + + using TrackingCappedArrayPool pool = new(); + TxTrie txTrie = new(transactions, canBuildProof: false, pool); + Hash256 parallelRoot = txTrie.RootHash; + + txTrie.UpdateRootHash(canBeParallel: false); + Hash256 nonParallelRoot = txTrie.RootHash; + + Assert.That(nonParallelRoot, Is.EqualTo(parallelRoot)); + } + private static void VerifyProof(byte[][] proof, Hash256 txRoot) { for (int i = proof.Length; i > 0; i--) diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs index 4dada95cceb2..02c002758efd 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Proofs/WithdrawalTrieTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Proofs; +[Parallelizable(ParallelScope.All)] public class WithdrawalTrieTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReadOnlyBlockTreeTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReadOnlyBlockTreeTests.cs index 2a85fb94a0e2..baa7e9d4af2e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ReadOnlyBlockTreeTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReadOnlyBlockTreeTests.cs @@ -9,6 +9,8 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class ReadOnlyBlockTreeTests { private IBlockTree _innerBlockTree = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/KeccaksIteratorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/KeccaksIteratorTests.cs index 96be4973fcae..2c66c04dfaa1 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/KeccaksIteratorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/KeccaksIteratorTests.cs @@ -13,6 +13,7 @@ namespace Nethermind.Blockchain.Test.Receipts; +[Parallelizable(ParallelScope.All)] public class KeccaksIteratorTests { [TestCaseSource(nameof(TestKeccaks))] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs index 4f7aa07eac27..caa0794e7ac3 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs @@ -26,23 +26,19 @@ namespace Nethermind.Blockchain.Test.Receipts; [TestFixture(true)] [TestFixture(false)] -public class PersistentReceiptStorageTests +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class PersistentReceiptStorageTests(bool useCompactReceipts) { - private readonly TestSpecProvider _specProvider = new TestSpecProvider(Byzantium.Instance); + private readonly TestSpecProvider _specProvider = new(Byzantium.Instance); private TestMemColumnsDb _receiptsDb = null!; private ReceiptsRecovery _receiptsRecovery = null!; private IBlockTree _blockTree = null!; private IBlockStore _blockStore = null!; - private readonly bool _useCompactReceipts; private ReceiptConfig _receiptConfig = null!; private PersistentReceiptStorage _storage = null!; private ReceiptArrayStorageDecoder _decoder = null!; - public PersistentReceiptStorageTests(bool useCompactReceipts) - { - _useCompactReceipts = useCompactReceipts; - } - [SetUp] public void SetUp() { @@ -64,7 +60,7 @@ public void TearDown() private void CreateStorage() { - _decoder = new ReceiptArrayStorageDecoder(_useCompactReceipts); + _decoder = new ReceiptArrayStorageDecoder(useCompactReceipts); _storage = new PersistentReceiptStorage( _receiptsDb, _specProvider, @@ -387,7 +383,7 @@ public void When_NewHeadBlock_DoNotRemove_TxIndex_WhenTxIsInOtherBlockNumber() [Test] public async Task When_NewHeadBlock_Remove_TxIndex_OfRemovedBlock_Unless_ItsAlsoInNewBlock() { - _receiptConfig.CompactTxIndex = _useCompactReceipts; + _receiptConfig.CompactTxIndex = useCompactReceipts; CreateStorage(); (Block block, _) = InsertBlock(); Block block2 = Build.A.Block diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs index ad7d30e52564..14f6d357d52a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs @@ -14,9 +14,10 @@ namespace Nethermind.Blockchain.Test.Receipts; +[Parallelizable(ParallelScope.All)] public class ReceiptsIteratorTests { - readonly ReceiptArrayStorageDecoder _decoder = ReceiptArrayStorageDecoder.Instance; + private readonly ReceiptArrayStorageDecoder _decoder = ReceiptArrayStorageDecoder.Instance; [Test] public void SmokeTestWithRecovery() diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs index 7cd1b0ec1292..fd10cec441d6 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRecoveryTests.cs @@ -11,6 +11,8 @@ namespace Nethermind.Blockchain.Test.Receipts; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class ReceiptsRecoveryTests { private IReceiptsRecovery _receiptsRecovery = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRootTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRootTests.cs index 1c3db195441b..e7b8009e2e92 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRootTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsRootTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Receipts { + [Parallelizable(ParallelScope.All)] public class ReceiptsRootTests { public static IEnumerable ReceiptsRootTestCases diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReorgDepthFinalizedStateProviderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReorgDepthFinalizedStateProviderTests.cs index 7e2e9b7f7e97..b8d4fd4f6c0b 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ReorgDepthFinalizedStateProviderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReorgDepthFinalizedStateProviderTests.cs @@ -11,7 +11,8 @@ namespace Nethermind.Blockchain.Test; [TestFixture] -[Parallelizable(ParallelScope.Self)] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class ReorgDepthFinalizedStateProviderTests { private IBlockTree _blockTree = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs index b8acad1530f1..8adf5fad4594 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs @@ -29,6 +29,7 @@ using Nethermind.Evm.State; using Nethermind.State; using Nethermind.TxPool; +using NSubstitute; using NUnit.Framework; namespace Nethermind.Blockchain.Test; @@ -131,7 +132,9 @@ public void Setup() specProvider, LimboLogs.Instance), stateReader, - LimboLogs.Instance, BlockchainProcessor.Options.Default); + LimboLogs.Instance, + BlockchainProcessor.Options.Default, + Substitute.For()); } [OneTimeTearDown] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Rewards/NoBlockRewardsTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Rewards/NoBlockRewardsTests.cs index 1dc264011284..212a6448829f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Rewards/NoBlockRewardsTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Rewards/NoBlockRewardsTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Blockchain.Test.Rewards; +[Parallelizable(ParallelScope.All)] public class NoBlockRewardsTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Rewards/RewardCalculatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Rewards/RewardCalculatorTests.cs index d4225963be51..3553483a1f69 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Rewards/RewardCalculatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Rewards/RewardCalculatorTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Blockchain.Test.Rewards; +[Parallelizable(ParallelScope.All)] public class RewardCalculatorTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Services/HealthHintServiceTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Services/HealthHintServiceTests.cs index aee9e3a4df56..fc89b9798e8f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Services/HealthHintServiceTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Services/HealthHintServiceTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Blockchain.Test.Services; +[Parallelizable(ParallelScope.All)] public class HealthHintServiceTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionGasPriceComparisonTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionGasPriceComparisonTests.cs index f247ffb1e153..089c1010237f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionGasPriceComparisonTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionGasPriceComparisonTests.cs @@ -14,6 +14,7 @@ namespace Nethermind.Blockchain.Test; +[Parallelizable(ParallelScope.All)] public class TransactionComparisonTests { [MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs index 2659aa7fc129..d861fc4e4d47 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs @@ -25,6 +25,8 @@ namespace Nethermind.Evm.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] internal class TransactionProcessorEip7702Tests { private ISpecProvider _specProvider; @@ -489,7 +491,7 @@ public void Execute_FirstTxHasAuthorizedCodeThatIncrementsAndSecondDoesNot_Stora .WithTransactions(tx1, tx2) .WithGasLimit(10000000).TestObject; - var blkCtx = new BlockExecutionContext(block.Header, _specProvider.GetSpec(block.Header)); + BlockExecutionContext blkCtx = new(block.Header, _specProvider.GetSpec(block.Header)); _transactionProcessor.Execute(tx1, blkCtx, NullTxTracer.Instance); _transactionProcessor.Execute(tx2, blkCtx, NullTxTracer.Instance); @@ -942,7 +944,7 @@ public void Execute_SetNormalDelegationAndThenSetDelegationWithZeroAddress_Accou .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) .WithTransactions(tx) .WithGasLimit(10000000).TestObject; - var blkCtx = new BlockExecutionContext(block.Header, _specProvider.GetSpec(block.Header)); + BlockExecutionContext blkCtx = new(block.Header, _specProvider.GetSpec(block.Header)); _transactionProcessor.Execute(tx, blkCtx, NullTxTracer.Instance); _stateProvider.CommitTree(block.Number); @@ -1016,6 +1018,111 @@ public void Execute_EXTCODESIZEOnDelegatedThatTriggersOptimization_ReturnsZeroIf Assert.That(tracer.ReturnValue, Is.EquivalentTo(new byte[] { Convert.ToByte(!isDelegated) })); } + [Test] + public void Execute_EXTCODESIZE_WhenCodeChangesWithinBlock_ReturnsUpdatedSize() + { + PrivateKey sender = TestItem.PrivateKeyA; + Address inspectedAddress = TestItem.AddressB; + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + byte[] initialInspectedCode = Prepare.EvmCode + .Op(Instruction.STOP) + .Done; + byte[] updatedInspectedCode = Prepare.EvmCode + .Op(Instruction.ADD) + .Op(Instruction.STOP) + .Done; + DeployCode(inspectedAddress, initialInspectedCode); + + byte[] extcodesizeReaderCode = Prepare.EvmCode + .PushData(inspectedAddress) + .Op(Instruction.EXTCODESIZE) + .Op(Instruction.PUSH0) + .Op(Instruction.MSTORE8) + .PushData(1) + .Op(Instruction.PUSH0) + .Op(Instruction.RETURN) + .Done; + DeployCode(codeSource, extcodesizeReaderCode); + _stateProvider.Commit(Prague.Instance, true); + + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithGasLimit(10_000_000) + .TestObject; + BlockExecutionContext blkCtx = new(block.Header, _specProvider.GetSpec(block.Header)); + + CallOutputTracer firstTracer = ExecuteCallWithOutput(sender, codeSource, blkCtx, 0); + Assert.That(firstTracer.ReturnValue?.ToArray(), Is.EquivalentTo(new byte[] { (byte)initialInspectedCode.Length })); + + DeployCode(inspectedAddress, updatedInspectedCode); + _stateProvider.Commit(Prague.Instance, true); + + CallOutputTracer secondTracer = ExecuteCallWithOutput(sender, codeSource, blkCtx, 1); + Assert.That(secondTracer.ReturnValue?.ToArray(), Is.EquivalentTo(new byte[] { (byte)updatedInspectedCode.Length })); + } + + [Test] + public void Execute_EXTCODECOPY_WhenCodeChangesWithinBlock_ReturnsUpdatedBytes() + { + PrivateKey sender = TestItem.PrivateKeyA; + Address inspectedAddress = TestItem.AddressB; + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + byte[] initialInspectedCode = Prepare.EvmCode + .Op(Instruction.ADD) + .Done; + byte[] updatedInspectedCode = Prepare.EvmCode + .Op(Instruction.MUL) + .Done; + DeployCode(inspectedAddress, initialInspectedCode); + + byte[] extcodecopyReaderCode = Prepare.EvmCode + .PushData(1) + .Op(Instruction.PUSH0) + .Op(Instruction.PUSH0) + .PushData(inspectedAddress) + .Op(Instruction.EXTCODECOPY) + .PushData(1) + .Op(Instruction.PUSH0) + .Op(Instruction.RETURN) + .Done; + DeployCode(codeSource, extcodecopyReaderCode); + _stateProvider.Commit(Prague.Instance, true); + + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithGasLimit(10_000_000) + .TestObject; + BlockExecutionContext blkCtx = new(block.Header, _specProvider.GetSpec(block.Header)); + + CallOutputTracer firstTracer = ExecuteCallWithOutput(sender, codeSource, blkCtx, 0); + Assert.That(firstTracer.ReturnValue?.ToArray(), Is.EquivalentTo(new byte[] { (byte)Instruction.ADD })); + + DeployCode(inspectedAddress, updatedInspectedCode); + _stateProvider.Commit(Prague.Instance, true); + + CallOutputTracer secondTracer = ExecuteCallWithOutput(sender, codeSource, blkCtx, 1); + Assert.That(secondTracer.ReturnValue?.ToArray(), Is.EquivalentTo(new byte[] { (byte)Instruction.MUL })); + } + + private CallOutputTracer ExecuteCallWithOutput(PrivateKey sender, Address to, BlockExecutionContext blockExecutionContext, ulong nonce) + { + Transaction tx = Build.A.Transaction + .WithType(TxType.EIP1559) + .WithNonce(nonce) + .WithTo(to) + .WithGasLimit(100_000) + .SignedAndResolved(_ethereumEcdsa, sender, true) + .TestObject; + + CallOutputTracer tracer = new(); + _ = _transactionProcessor.Execute(tx, blockExecutionContext, tracer); + return tracer; + } + private void DeployCode(Address codeSource, byte[] code) { _stateProvider.CreateAccountIfNotExists(codeSource, 0); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs index fe71d8075570..b2b89a875891 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs @@ -32,7 +32,8 @@ namespace Nethermind.Evm.Test; [TestFixture(true)] [TestFixture(false)] [Todo(Improve.Refactor, "Check why fixture test cases did not work")] -[Parallelizable(ParallelScope.Self)] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class TransactionProcessorTests { private readonly bool _isEip155Enabled; @@ -465,7 +466,7 @@ public void Can_estimate_with_destroy_refund_and_below_intrinsic_pre_berlin() tracer.CalculateAdditionalGasRequired(tx, releaseSpec).Should().Be(24080); tracer.GasSpent.Should().Be(35228L); long estimate = estimator.Estimate(tx, block.Header, tracer, out string? err, 0); - estimate.Should().Be(59307); + estimate.Should().Be(54225); Assert.That(err, Is.Null); ConfirmEnoughEstimate(tx, block, estimate); @@ -473,32 +474,19 @@ public void Can_estimate_with_destroy_refund_and_below_intrinsic_pre_berlin() private void ConfirmEnoughEstimate(Transaction tx, Block block, long estimate) { - CallOutputTracer outputTracer = new(); - tx.GasLimit = estimate; - TestContext.Out.WriteLine(tx.GasLimit); - - GethLikeTxMemoryTracer gethTracer = new(tx, GethTraceOptions.Default); var blkCtx = new BlockExecutionContext(block.Header, _specProvider.GetSpec(block.Header)); - _transactionProcessor.CallAndRestore(tx, blkCtx, gethTracer); - string traceEnoughGas = new EthereumJsonSerializer().Serialize(gethTracer.BuildResult(), true); + CallOutputTracer outputTracer = new(); + tx.GasLimit = estimate; _transactionProcessor.CallAndRestore(tx, blkCtx, outputTracer); - traceEnoughGas.Should().NotContain("OutOfGas"); + outputTracer.StatusCode.Should().Be(StatusCode.Success, + $"transaction should succeed at the estimate ({estimate})"); outputTracer = new CallOutputTracer(); tx.GasLimit = Math.Min(estimate - 1, estimate * 63 / 64); - TestContext.Out.WriteLine(tx.GasLimit); - - gethTracer = new GethLikeTxMemoryTracer(tx, GethTraceOptions.Default); - _transactionProcessor.CallAndRestore(tx, blkCtx, gethTracer); - - string traceOutOfGas = new EthereumJsonSerializer().Serialize(gethTracer.BuildResult(), true); - TestContext.Out.WriteLine(traceOutOfGas); - _transactionProcessor.CallAndRestore(tx, blkCtx, outputTracer); - - bool failed = traceEnoughGas.Contains("failed") || traceEnoughGas.Contains("OutOfGas"); - failed.Should().BeTrue(); + outputTracer.StatusCode.Should().Be(StatusCode.Failure, + $"transaction should fail below the estimate ({tx.GasLimit})"); } [TestCase] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs index 2866322ce40b..c31d811b18f6 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs @@ -28,6 +28,7 @@ namespace Nethermind.Blockchain.Test { + [Parallelizable(ParallelScope.All)] public class TransactionSelectorTests { public static IEnumerable ProperTransactionsSelectedTestCases diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs index ab506b1fbab2..1282d9038c21 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs @@ -31,6 +31,7 @@ namespace Nethermind.Blockchain.Test { + [Parallelizable(ParallelScope.All)] public class TransactionsExecutorTests { public static IEnumerable ProperTransactionsSelectedTestCases diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Utils/LastNStateRootTrackerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Utils/LastNStateRootTrackerTests.cs index 699ef5593bfa..07c79a8a1ec5 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Utils/LastNStateRootTrackerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Utils/LastNStateRootTrackerTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Blockchain.Test.Utils; +[Parallelizable(ParallelScope.All)] public class LastNStateRootTrackerTests { [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/BlockValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/BlockValidatorTests.cs index dff67022674e..78a14474285d 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/BlockValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/BlockValidatorTests.cs @@ -18,6 +18,8 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class BlockValidatorTests { private static BlockValidator _blockValidator = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/HeaderValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/HeaderValidatorTests.cs index 445672cdf87a..fb3a63499864 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/HeaderValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/HeaderValidatorTests.cs @@ -23,6 +23,8 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class HeaderValidatorTests { private IHeaderValidator _validator = null!; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs index 0d386ee72497..28e7fe635cbe 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/ShardBlobBlockValidatorTests.cs @@ -15,6 +15,7 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] public class ShardBlobBlockValidatorTests { [TestCaseSource(nameof(BlobGasFieldsPerForkTestCases))] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs index 8dcb6a44720f..ac04461b93fc 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs @@ -26,12 +26,9 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] public class TxValidatorTests { - [SetUp] - public void Setup() - { - } [Test, MaxTime(Timeout.MaxTestTime)] public void Curve_is_correct() diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs index 069960655340..0ecbe42eb588 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs @@ -12,6 +12,8 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class UnclesValidatorTests { private Block _greatGrandparent; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs index 7cc4bacbc4e1..604c64e2d497 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/WithdrawalValidatorTests.cs @@ -14,6 +14,7 @@ namespace Nethermind.Blockchain.Test.Validators; +[Parallelizable(ParallelScope.All)] public class WithdrawalValidatorTests { [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Visitors/DbBlocksLoaderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Visitors/DbBlocksLoaderTests.cs index b468c23eed9c..420a86305e72 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Visitors/DbBlocksLoaderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Visitors/DbBlocksLoaderTests.cs @@ -17,6 +17,7 @@ namespace Nethermind.Blockchain.Test.Visitors; +[Parallelizable(ParallelScope.All)] public class DbBlocksLoaderTests { private readonly int _dbLoadTimeout = 5000; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Visitors/StartupTreeFixerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Visitors/StartupTreeFixerTests.cs index 8e787f1a5067..1191fe481b7a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Visitors/StartupTreeFixerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Visitors/StartupTreeFixerTests.cs @@ -17,6 +17,7 @@ namespace Nethermind.Blockchain.Test.Visitors; +[Parallelizable(ParallelScope.All)] public class StartupTreeFixerTests { [Test, MaxTime(Timeout.MaxTestTime), Ignore("Not implemented")] diff --git a/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs b/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs index ea638750cea7..200341f05cdb 100644 --- a/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs @@ -25,6 +25,7 @@ public class BlockhashProvider( private readonly IBlockhashStore _blockhashStore = new BlockhashStore(worldState); private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); private Hash256[]? _hashes; + private long _prefetchVersion; public Hash256? GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec spec) { @@ -39,7 +40,7 @@ public class BlockhashProvider( } long depth = currentBlock.Number - number; - Hash256[]? hashes = _hashes; + Hash256[]? hashes = Volatile.Read(ref _hashes); return depth switch { @@ -60,7 +61,8 @@ public class BlockhashProvider( public async Task Prefetch(BlockHeader currentBlock, CancellationToken token) { - _hashes = null; + long prefetchVersion = Interlocked.Increment(ref _prefetchVersion); + Volatile.Write(ref _hashes, null); Hash256[]? hashes = await blockhashCache.Prefetch(currentBlock, token); // This leverages that branch processing is single threaded @@ -69,9 +71,9 @@ public async Task Prefetch(BlockHeader currentBlock, CancellationToken token) // This allows us to avoid await on Prefetch in BranchProcessor lock (_blockhashStore) { - if (!token.IsCancellationRequested) + if (!token.IsCancellationRequested && prefetchVersion == Interlocked.Read(ref _prefetchVersion)) { - _hashes = hashes; + Volatile.Write(ref _hashes, hashes); } } } diff --git a/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs b/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs index 8ad9efdc97a8..d05768a92dc4 100644 --- a/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs @@ -21,11 +21,11 @@ public class CachedCodeInfoRepository( ICodeInfoRepository baseCodeInfoRepository, ConcurrentDictionary>? precompileCache) : ICodeInfoRepository { - private readonly FrozenDictionary _cachedPrecompile = precompileCache is null + private readonly FrozenDictionary _cachedPrecompile = precompileCache is null ? precompileProvider.GetPrecompiles() : precompileProvider.GetPrecompiles().ToFrozenDictionary(kvp => kvp.Key, kvp => CreateCachedPrecompile(kvp, precompileCache)); - public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, + public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) { if (vmSpec.IsPrecompile(codeSource) && _cachedPrecompile.TryGetValue(codeSource, out var cachedCodeInfo)) @@ -36,9 +36,9 @@ public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IR return baseCodeInfoRepository.GetCachedCodeInfo(codeSource, followDelegation, vmSpec, out delegationAddress); } - public ICodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) + public CodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) { - if (vmSpec.IsPrecompile(codeSource) && _cachedPrecompile.TryGetValue(codeSource, out PrecompileInfo cachedCodeInfo)) + if (vmSpec.IsPrecompile(codeSource) && _cachedPrecompile.TryGetValue(codeSource, out CodeInfo cachedCodeInfo)) { return cachedCodeInfo; } @@ -67,15 +67,15 @@ public bool TryGetDelegation(Address address, IReleaseSpec spec, return baseCodeInfoRepository.TryGetDelegation(address, spec, out delegatedAddress); } - private static PrecompileInfo CreateCachedPrecompile( - in KeyValuePair originalPrecompile, + private static CodeInfo CreateCachedPrecompile( + in KeyValuePair originalPrecompile, ConcurrentDictionary> cache) { IPrecompile precompile = originalPrecompile.Value.Precompile!; return !precompile.SupportsCaching ? originalPrecompile.Value - : new PrecompileInfo(new CachedPrecompile(originalPrecompile.Key.Value, precompile, cache)); + : new CodeInfo(new CachedPrecompile(originalPrecompile.Key.Value, precompile, cache)); } private class CachedPrecompile( diff --git a/src/Nethermind/Nethermind.Blockchain/EthereumPrecompileProvider.cs b/src/Nethermind/Nethermind.Blockchain/EthereumPrecompileProvider.cs index d793c9a65ba3..03fa5cff876d 100644 --- a/src/Nethermind/Nethermind.Blockchain/EthereumPrecompileProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/EthereumPrecompileProvider.cs @@ -13,9 +13,9 @@ namespace Nethermind.Blockchain; public class EthereumPrecompileProvider() : IPrecompileProvider { - private static FrozenDictionary Precompiles + private static FrozenDictionary Precompiles { - get => new Dictionary + get => new Dictionary { [EcRecoverPrecompile.Address] = new(EcRecoverPrecompile.Instance), [Sha256Precompile.Address] = new(Sha256Precompile.Instance), @@ -45,7 +45,7 @@ private static FrozenDictionary Precompiles }.ToFrozenDictionary(); } - public FrozenDictionary GetPrecompiles() + public FrozenDictionary GetPrecompiles() { return Precompiles; } diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/EstimateGasTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/EstimateGasTracer.cs index 1d6e7187eaaf..1d6f49def139 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/EstimateGasTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/EstimateGasTracer.cs @@ -149,11 +149,14 @@ public void ReportActionError(EvmExceptionType exceptionType, long gasLeft) public override void ReportOperationError(EvmExceptionType error) { - OutOfGas |= error == EvmExceptionType.OutOfGas; - - if (error == EvmExceptionType.Revert && _currentNestingLevel == 0) + if (_currentNestingLevel == 0) { - TopLevelRevert = true; + OutOfGas |= error == EvmExceptionType.OutOfGas; + + if (error == EvmExceptionType.Revert) + { + TopLevelRevert = true; + } } } diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs index 296e39acbb4f..2460334a9b60 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs @@ -11,6 +11,7 @@ using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; +using Nethermind.Evm.CodeAnalysis; namespace Nethermind.Blockchain.Tracing.GethStyle.Custom.JavaScript; @@ -108,7 +109,7 @@ public override void ReportAction(long gas, UInt256 value, Address from, Address public override void StartOperation(int pc, Instruction opcode, long gas, in ExecutionEnvironment env, int codeSection = 0, int functionDepth = 0) { - _log.pc = pc + env.CodeInfo.PcOffset(); + _log.pc = pc + (env.CodeInfo is EofCodeInfo eof ? eof.PcOffset() : 0); _log.op = new Log.Opcode(opcode); _log.gas = gas; _log.depth = env.GetGethTraceDepth(); diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs index 565e15a80a36..4c69cdc72712 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/Call/NativeCallTracer.cs @@ -57,16 +57,20 @@ public NativeCallTracer( public override GethLikeTxTrace BuildResult() { GethLikeTxTrace result = base.BuildResult(); - NativeCallTracerCallFrame firstCallFrame = _callStack[0]; Debug.Assert(_callStack.Count == 1, $"Unexpected frames on call stack, expected only master frame, found {_callStack.Count} frames."); - _callStack.RemoveAt(0); - _disposables.Add(firstCallFrame); + if (_callStack.Count is not 0) + { + NativeCallTracerCallFrame firstCallFrame = _callStack[0]; + _callStack.RemoveAt(0); + _disposables.Add(firstCallFrame); - result.TxHash = _txHash; - result.CustomTracerResult = new GethLikeCustomTrace { Value = firstCallFrame }; + result.TxHash = _txHash; + result.CustomTracerResult = new GethLikeCustomTrace { Value = firstCallFrame }; + } + result.TxHash = _txHash; _resultBuilt = true; return result; diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethTraceOptions.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethTraceOptions.cs index 6af4bd4fea7e..66605682db0b 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethTraceOptions.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/GethTraceOptions.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Text.Json; -using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Evm; @@ -13,33 +12,26 @@ namespace Nethermind.Blockchain.Tracing.GethStyle; public record GethTraceOptions { - [JsonPropertyName("disableMemory")] [Obsolete("Use EnableMemory instead.")] public bool DisableMemory { get => !EnableMemory; init => EnableMemory = !value; } - [JsonPropertyName("disableStorage")] public bool DisableStorage { get; init; } - [JsonPropertyName("enableMemory")] public bool EnableMemory { get; init; } - [JsonPropertyName("disableStack")] public bool DisableStack { get; init; } - [JsonPropertyName("timeout")] public string Timeout { get; init; } - [JsonPropertyName("tracer")] public string Tracer { get; init; } - [JsonPropertyName("txHash")] public Hash256? TxHash { get; init; } - [JsonPropertyName("tracerConfig")] public JsonElement? TracerConfig { get; init; } - [JsonPropertyName("stateOverrides")] public Dictionary? StateOverrides { get; init; } + public BlockOverride? BlockOverrides { get; set; } + public static GethTraceOptions Default { get; } = new(); } diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/ParityStyle/ParityLikeTxTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/ParityStyle/ParityLikeTxTracer.cs index 43d4ec399cfe..cc1b104b0a85 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/ParityStyle/ParityLikeTxTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/ParityStyle/ParityLikeTxTracer.cs @@ -9,6 +9,7 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Evm; +using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.Int256; @@ -236,7 +237,7 @@ public override void StartOperation(int pc, Instruction opcode, long gas, in Exe { ParityVmOperationTrace operationTrace = new(); _gasAlreadySetForCurrentOp = false; - operationTrace.Pc = pc + env.CodeInfo.PcOffset(); + operationTrace.Pc = pc + (env.CodeInfo is EofCodeInfo eof ? eof.PcOffset() : 0); operationTrace.Cost = gas; // skip codeSection // skip functionDepth diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockFinalizationManager.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockFinalizationManager.cs index 87d5e8a15959..76a37745f107 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockFinalizationManager.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockFinalizationManager.cs @@ -345,6 +345,7 @@ private set public void Dispose() { _branchProcessor.BlockProcessed -= OnBlockProcessed; + _branchProcessor.BlocksProcessing -= OnBlocksProcessing; } [DebuggerDisplay("Count = {Count}")] diff --git a/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs b/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs index 6c1bf551641a..9ad3590d1392 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using Nethermind.Blockchain; using Nethermind.Core; namespace Nethermind.Consensus.Clique @@ -13,23 +11,6 @@ public static bool IsInTurn(this BlockHeader header) { return header.Difficulty == Clique.DifficultyInTurn; } - - internal static Address[] ExtractSigners(BlockHeader blockHeader) - { - if (blockHeader.ExtraData is null) - { - throw new BlockchainException("Block header ExtraData cannot be null when extracting signers"); - } - - Span signersData = blockHeader.ExtraData.AsSpan(Clique.ExtraVanityLength, (blockHeader.ExtraData.Length - Clique.ExtraSealLength)); - Address[] signers = new Address[signersData.Length / Address.Size]; - for (int i = 0; i < signers.Length; i++) - { - signers[i] = new Address(signersData.Slice(i * 20, 20).ToArray()); - } - - return signers; - } } internal static class BlockExtensions diff --git a/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs b/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs index a838bce57d6c..f93dcb2af89a 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs @@ -142,4 +142,179 @@ public async Task Test_task_that_is_scheduled_during_block_processing_but_deadli wasCancelled.Should().BeTrue(); } + + [Test] + public async Task Test_expired_tasks_are_drained_during_block_processing() + { + int capacity = 16; + await using BackgroundTaskScheduler scheduler = new(_branchProcessor, _chainHeadInfo, 1, capacity, LimboLogs.Instance); + + // Start block processing — signal is reset, token cancelled + _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); + + int cancelledCount = 0; + for (int i = 0; i < capacity; i++) + { + scheduler.TryScheduleTask(1, (_, token) => + { + if (token.IsCancellationRequested) + { + Interlocked.Increment(ref cancelledCount); + } + return Task.CompletedTask; + }, TimeSpan.FromMilliseconds(1)); + } + + // Expired tasks should be drained even while block processing is in progress + Assert.That(() => cancelledCount, Is.EqualTo(capacity).After(2000, 10)); + + _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); + } + + [Test] + public async Task Test_queue_accepts_new_tasks_after_expired_tasks_drain_during_block_processing() + { + int capacity = 16; + await using BackgroundTaskScheduler scheduler = new(_branchProcessor, _chainHeadInfo, 1, capacity, LimboLogs.Instance); + + // Start block processing — signal is reset, token cancelled + _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); + + // Fill the queue with short-lived tasks + for (int i = 0; i < capacity; i++) + { + scheduler.TryScheduleTask(1, (_, _) => Task.CompletedTask, TimeSpan.FromMilliseconds(1)).Should().BeTrue(); + } + + // Wait for deadlines to pass and expired tasks to be drained + await Task.Delay(200); + + // New tasks should be accepted because expired tasks freed up queue space + for (int i = 0; i < capacity; i++) + { + bool accepted = scheduler.TryScheduleTask(1, (_, _) => Task.CompletedTask, TimeSpan.FromMilliseconds(1)); + accepted.Should().BeTrue($"Task {i} should be accepted after expired tasks were drained"); + } + + _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); + } + + [Test] + public async Task Test_high_capacity_queue_survives_repeated_block_processing_cycles() + { + int capacity = 1024; + int concurrency = 2; + await using BackgroundTaskScheduler scheduler = new(_branchProcessor, _chainHeadInfo, concurrency, capacity, LimboLogs.Instance); + + int executedCount = 0; + int cancelledCount = 0; + + // --- Phase 1: Fill the queue to capacity during block processing --- + _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); + + for (int i = 0; i < capacity; i++) + { + bool accepted = scheduler.TryScheduleTask(1, (_, token) => + { + if (token.IsCancellationRequested) + Interlocked.Increment(ref cancelledCount); + else + Interlocked.Increment(ref executedCount); + return Task.CompletedTask; + }, TimeSpan.FromMilliseconds(10)); + accepted.Should().BeTrue($"Phase 1: task {i} should be accepted up to capacity"); + } + + // Wait for deadlines to expire and tasks to drain + Assert.That( + () => Volatile.Read(ref cancelledCount), + Is.EqualTo(capacity).After(5000, 10), + "all tasks should be drained with cancelled tokens during block processing"); + + // --- Phase 2: End block processing, verify queue accepts tasks and runs them normally --- + _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); + + Interlocked.Exchange(ref executedCount, 0); + + int phase2Count = capacity / 2; + for (int i = 0; i < phase2Count; i++) + { + bool accepted = scheduler.TryScheduleTask(1, (_, _) => + { + Interlocked.Increment(ref executedCount); + return Task.CompletedTask; + }); + accepted.Should().BeTrue($"Phase 2: task {i} should be accepted after queue drained"); + } + + Assert.That( + () => Volatile.Read(ref executedCount), + Is.EqualTo(phase2Count).After(5000, 10), + "all phase 2 tasks should execute normally after block processing ends"); + + // --- Phase 3: Another block processing cycle with mixed short and long timeouts --- + _branchProcessor.BlocksProcessing += Raise.EventWith(new BlocksProcessingEventArgs(null)); + + int phase3CancelledCount = 0; + int phase3ExecutedCount = 0; + + // Short-lived tasks (will expire during block processing) + int shortLivedCount = capacity / 2; + for (int i = 0; i < shortLivedCount; i++) + { + scheduler.TryScheduleTask(1, (_, token) => + { + if (token.IsCancellationRequested) + Interlocked.Increment(ref phase3CancelledCount); + return Task.CompletedTask; + }, TimeSpan.FromMilliseconds(5)).Should().BeTrue($"Phase 3: short-lived task {i} should be accepted"); + } + + // Long-lived tasks (will survive until block processing ends) + int longLivedCount = capacity / 4; + for (int i = 0; i < longLivedCount; i++) + { + scheduler.TryScheduleTask(1, (_, token) => + { + if (!token.IsCancellationRequested) + Interlocked.Increment(ref phase3ExecutedCount); + return Task.CompletedTask; + }, TimeSpan.FromSeconds(30)).Should().BeTrue($"Phase 3: long-lived task {i} should be accepted"); + } + + // Wait for short-lived tasks to expire and drain + Assert.That( + () => Volatile.Read(ref phase3CancelledCount), + Is.EqualTo(shortLivedCount).After(5000, 10), + "short-lived tasks should drain with cancelled tokens during block processing"); + + // Long-lived tasks should not have executed yet (still waiting for block processing to end) + Volatile.Read(ref phase3ExecutedCount).Should().Be(0, + "long-lived tasks should wait during block processing"); + + // End block processing — long-lived tasks should now execute + _branchProcessor.BlockProcessed += Raise.EventWith(new BlockProcessedEventArgs(null, null)); + + Assert.That( + () => Volatile.Read(ref phase3ExecutedCount), + Is.EqualTo(longLivedCount).After(5000, 10), + "long-lived tasks should execute after block processing ends"); + + // --- Phase 4: Verify queue is fully operational with one more fill-and-drain --- + Interlocked.Exchange(ref executedCount, 0); + + for (int i = 0; i < capacity; i++) + { + scheduler.TryScheduleTask(1, (_, _) => + { + Interlocked.Increment(ref executedCount); + return Task.CompletedTask; + }).Should().BeTrue($"Phase 4: task {i} should be accepted in fully recovered queue"); + } + + Assert.That( + () => Volatile.Read(ref executedCount), + Is.EqualTo(capacity).After(5000, 10), + "all tasks in the final phase should execute successfully"); + } } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs index 80340ae16a8b..fb7ab548d42f 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs @@ -57,7 +57,7 @@ public Task PreWarmCaches(Block suggestedBlock, BlockHeader? parent, IReleaseSpe if (preBlockCaches is not null) { CacheType result = preBlockCaches.ClearCaches(); - result |= nodeStorageCache.ClearCaches() ? CacheType.Rlp : CacheType.None; + nodeStorageCache.ClearCaches(); nodeStorageCache.Enabled = true; if (result != default) { diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs index 816517ff416b..77ae74dddf2a 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs @@ -57,14 +57,16 @@ public sealed class BlockchainProcessor : IBlockchainProcessor, IBlockProcessing new BoundedChannelOptions(MaxProcessingQueueSize) { // Optimize for single reader concurrency - SingleReader = true + SingleReader = true, + // If queues are empty we want the block processing to continue on NewPayload thread and inherit its priority + AllowSynchronousContinuations = true, }); private bool _recoveryComplete = false; private int _queueCount; private bool _disposed; - private readonly ProcessingStats _stats; + private readonly IProcessingStats _stats; private CancellationTokenSource? _loopCancellationSource; private Task? _recoveryTask; @@ -89,13 +91,15 @@ public sealed class BlockchainProcessor : IBlockchainProcessor, IBlockProcessing /// /// /// + /// public BlockchainProcessor( IBlockTree blockTree, IBranchProcessor branchProcessor, IBlockPreprocessorStep recoveryStep, IStateReader stateReader, ILogManager logManager, - Options options) + Options options, + IProcessingStats processingStats) { _logger = logManager.GetClassLogger(); _blockTree = blockTree; @@ -104,7 +108,7 @@ public BlockchainProcessor( _stateReader = stateReader; _options = options; - _stats = new ProcessingStats(stateReader, logManager.GetClassLogger()); + _stats = processingStats; _loopCancellationSource = new CancellationTokenSource(); _stats.NewProcessingStatistics += OnNewProcessingStatistics; } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessingScope.cs b/src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessingScope.cs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessorSource.cs b/src/Nethermind/Nethermind.Consensus/Processing/IOverridableTxProcessorSource.cs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IProcessingStats.cs b/src/Nethermind/Nethermind.Consensus/Processing/IProcessingStats.cs new file mode 100644 index 000000000000..7d7b65723e69 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Processing/IProcessingStats.cs @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; + +namespace Nethermind.Consensus.Processing; + +/// +/// Interface for processing statistics tracking during block processing. +/// +public interface IProcessingStats +{ + /// + /// Event fired when new processing statistics are available. + /// + event EventHandler? NewProcessingStatistics; + + /// + /// Start the statistics timer. + /// + void Start(); + + /// + /// Capture the starting values of metrics before processing begins. + /// + void CaptureStartStats(); + + /// + /// Update statistics after a block has been processed. + /// + /// The processed block. + /// The parent block header. + /// Processing time in microseconds. + void UpdateStats(Block? block, BlockHeader? baseBlock, long blockProcessingTimeInMicros); +} diff --git a/src/Nethermind/Nethermind.Consensus/Processing/OverridableTxProcessingScope.cs b/src/Nethermind/Nethermind.Consensus/Processing/OverridableTxProcessingScope.cs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs index f2246ed3e733..0cd19058da01 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs @@ -19,27 +19,27 @@ namespace Nethermind.Consensus.Processing { public class BlockStatistics { - public long BlockCount { get; internal set; } - public long BlockFrom { get; internal set; } - public long BlockTo { get; internal set; } - public double ProcessingMs { get; internal set; } - public double SlotMs { get; internal set; } + public long BlockCount { get; set; } + public long BlockFrom { get; set; } + public long BlockTo { get; set; } + public double ProcessingMs { get; set; } + public double SlotMs { get; set; } [JsonPropertyName("mgasPerSecond")] - public double MGasPerSecond { get; internal set; } - public float MinGas { get; internal set; } - public float MedianGas { get; internal set; } - public float AveGas { get; internal set; } - public float MaxGas { get; internal set; } - public long GasLimit { get; internal set; } + public double MGasPerSecond { get; set; } + public float MinGas { get; set; } + public float MedianGas { get; set; } + public float AveGas { get; set; } + public float MaxGas { get; set; } + public long GasLimit { get; set; } } //TODO Consult on disabling of such metrics from configuration - internal class ProcessingStats + public class ProcessingStats : IProcessingStats { private static readonly DefaultObjectPool _dataPool = new(new BlockDataPolicy(), 16); private readonly Action _executeFromThreadPool; public event EventHandler? NewProcessingStatistics; - private readonly IStateReader _stateReader; - private readonly ILogger _logger; + protected readonly IStateReader _stateReader; + protected readonly ILogger _logger; private readonly Stopwatch _runStopwatch = new(); private bool _showBlobs; @@ -69,12 +69,12 @@ internal class ProcessingStats private long _contractsAnalyzed; private long _cachedContractsUsed; - public ProcessingStats(IStateReader stateReader, ILogger logger) + public ProcessingStats(IStateReader stateReader, ILogManager logManager) { _executeFromThreadPool = ExecuteFromThreadPool; _stateReader = stateReader; - _logger = logger; + _logger = logManager.GetClassLogger(); // the line below just to avoid compilation errors if (_logger.IsTrace) _logger.Trace($"Processing Stats in debug mode?: {_logger.IsDebug}"); @@ -151,7 +151,7 @@ void ExecuteFromThreadPool(BlockData data) } } - private void GenerateReport(BlockData data) + protected virtual void GenerateReport(BlockData data) { const long weiToEth = 1_000_000_000_000_000_000; const string resetColor = "\u001b[37m"; @@ -443,7 +443,7 @@ public bool Return(BlockData data) } } - private class BlockData + protected class BlockData { public Block Block; public BlockHeader? BaseBlock; diff --git a/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs b/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs index c138491985f4..27ed452d518a 100644 --- a/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs +++ b/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs @@ -34,7 +34,6 @@ public class BackgroundTaskScheduler : IBackgroundTaskScheduler, IAsyncDisposabl private readonly CancellationTokenSource _mainCancellationTokenSource; private readonly Channel _taskQueue; private readonly BelowNormalPriorityTaskScheduler _scheduler; - private readonly ManualResetEventSlim _restartQueueSignal; private readonly Task[] _tasksExecutors; private readonly ILogger _logger; private readonly IBranchProcessor _branchProcessor; @@ -43,6 +42,7 @@ public class BackgroundTaskScheduler : IBackgroundTaskScheduler, IAsyncDisposabl private long _queueCount; private CancellationTokenSource _blockProcessorCancellationTokenSource; + private volatile TaskCompletionSource? _blockProcessingDoneSignal; private bool _disposed = false; public BackgroundTaskScheduler(IBranchProcessor branchProcessor, IChainHeadInfoProvider headInfo, int concurrency, int capacity, ILogManager logManager) @@ -65,7 +65,6 @@ public BackgroundTaskScheduler(IBranchProcessor branchProcessor, IChainHeadInfoP _logger = logManager.GetClassLogger(); _branchProcessor = branchProcessor; _headInfo = headInfo; - _restartQueueSignal = new ManualResetEventSlim(initialState: true); _capacity = capacity; _branchProcessor.BlocksProcessing += BranchProcessorOnBranchesProcessing; @@ -74,7 +73,6 @@ public BackgroundTaskScheduler(IBranchProcessor branchProcessor, IChainHeadInfoP // TaskScheduler to run tasks at BelowNormal priority _scheduler = new BelowNormalPriorityTaskScheduler( concurrency, - _restartQueueSignal, logManager, _mainCancellationTokenSource.Token); @@ -88,8 +86,9 @@ private void BranchProcessorOnBranchesProcessing(object? sender, BlocksProcessin // as there are potentially no gaps between blocks if (!_headInfo.IsSyncing) { - // Reset the background queue processing signal, causing it to wait - _restartQueueSignal.Reset(); + // Signal that block processing is in progress so the Throttle path in StartChannel + // can async-wait instead of busy-polling + _blockProcessingDoneSignal = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); // On block processing, we cancel the block process cts, causing the current task to get canceled. _blockProcessorCancellationTokenSource.Cancel(); } @@ -102,8 +101,8 @@ private void BranchProcessorOnBranchProcessed(object? sender, BlockProcessedEven ref _blockProcessorCancellationTokenSource, new CancellationTokenSource()); - // We also set a queue signal causing it to continue processing the task. - _restartQueueSignal.Set(); + // Signal that block processing is done so the Throttle path can resume + Interlocked.Exchange(ref _blockProcessingDoneSignal, null)?.TrySetResult(); } private async Task StartChannel() @@ -157,7 +156,16 @@ private async Task StartChannel() continue; Throttle: - await Task.Delay(millisecondsDelay: 1); + // Wait for block processing to complete, with periodic wake-ups to drain newly expired tasks + TaskCompletionSource? signal = _blockProcessingDoneSignal; + if (signal is not null) + { + await Task.WhenAny(signal.Task, Task.Delay(millisecondsDelay: 1)); + } + else + { + await Task.Delay(millisecondsDelay: 1); + } } } @@ -250,17 +258,15 @@ private sealed class BelowNormalPriorityTaskScheduler : TaskScheduler, IDisposab { private readonly BlockingCollection _tasks = []; private readonly Thread[] workerThreads; - private readonly ManualResetEventSlim _restartQueueSignal; private readonly int _maxDegreeOfParallelism; private readonly ILogger _logger; private readonly CancellationToken _cancellationToken; - public BelowNormalPriorityTaskScheduler(int maxDegreeOfParallelism, ManualResetEventSlim restartQueueSignal, ILogManager logManager, CancellationToken cancellationToken) + public BelowNormalPriorityTaskScheduler(int maxDegreeOfParallelism, ILogManager logManager, CancellationToken cancellationToken) { ArgumentOutOfRangeException.ThrowIfLessThan(maxDegreeOfParallelism, 1); _logger = logManager.GetClassLogger(); - _restartQueueSignal = restartQueueSignal; _maxDegreeOfParallelism = maxDegreeOfParallelism; _cancellationToken = cancellationToken; workerThreads = [.. Enumerable.Range(0, maxDegreeOfParallelism) @@ -283,8 +289,6 @@ private void ProcessBackgroundTasks(object _) { foreach (Task task in _tasks.GetConsumingEnumerable(_cancellationToken)) { - // Wait if processing blocks - _restartQueueSignal.Wait(_cancellationToken); try { TryExecuteTask(task); diff --git a/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs b/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs index 83833ef5631e..b2bce702e101 100644 --- a/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs +++ b/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs @@ -179,12 +179,14 @@ public IEnumerable TraceBadBlockToFile(Hash256 blockHash, GethTraceOptio // // Wild stuff! BlockHeader baseBlockHeader = block.Header; + if ((processingOptions & ProcessingOptions.ForceSameBlock) == 0) { baseBlockHeader = FindParent(block); } - using var scope = blockProcessingEnv.BuildAndOverride(baseBlockHeader, options.StateOverrides); + options.BlockOverrides?.ApplyOverrides(block.Header); + using Scope scope = blockProcessingEnv.BuildAndOverride(baseBlockHeader, options.StateOverrides); IBlockTracer tracer = CreateOptionsTracer(block.Header, options with { TxHash = txHash }, scope.Component.WorldState, specProvider); try diff --git a/src/Nethermind/Nethermind.Core.Test/BytesTests.cs b/src/Nethermind/Nethermind.Core.Test/BytesTests.cs index f3371d834c1c..ed7ccc0c2ac4 100644 --- a/src/Nethermind/Nethermind.Core.Test/BytesTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/BytesTests.cs @@ -19,11 +19,11 @@ public class BytesTests { [TestCase("0x", "0x", 0)] [TestCase(null, null, 0)] - [TestCase(null, "0x", 1)] - [TestCase("0x", null, -1)] + [TestCase(null, "0x", -1)] + [TestCase("0x", null, 1)] [TestCase("0x01", "0x01", 0)] - [TestCase("0x01", "0x0102", 1)] - [TestCase("0x0102", "0x01", -1)] + [TestCase("0x01", "0x0102", -1)] + [TestCase("0x0102", "0x01", 1)] public void Compares_bytes_properly(string? hexString1, string? hexString2, int expectedResult) { IComparer comparer = Bytes.Comparer; @@ -467,5 +467,139 @@ public void NullableComparison() { Bytes.NullableEqualityComparer.Equals(null, null).Should().BeTrue(); } + + [Test] + public void FastHash_EmptyInput_ReturnsZero() + { + ReadOnlySpan empty = ReadOnlySpan.Empty; + empty.FastHash().Should().Be(0); + } + + [Test] + public void FastHash_SameInput_ReturnsSameHash() + { + byte[] input = new byte[100]; + TestContext.CurrentContext.Random.NextBytes(input); + + int hash1 = ((ReadOnlySpan)input).FastHash(); + int hash2 = ((ReadOnlySpan)input).FastHash(); + + hash1.Should().Be(hash2); + } + + [Test] + public void FastHash_DifferentInput_ReturnsDifferentHash() + { + byte[] input1 = new byte[100]; + byte[] input2 = new byte[100]; + TestContext.CurrentContext.Random.NextBytes(input1); + Array.Copy(input1, input2, input1.Length); + input2[50] ^= 0xFF; // Flip bits at position 50 + + int hash1 = ((ReadOnlySpan)input1).FastHash(); + int hash2 = ((ReadOnlySpan)input2).FastHash(); + + hash1.Should().NotBe(hash2); + } + + // Test cases for the fold-back bug fix: remaining in [49-63] after 64-byte initial load + // For len=113 to 127, remaining = len-64 = 49 to 63, which requires the last64 fold-back + [TestCase(113)] // remaining=49, boundary case for last64 + [TestCase(120)] // remaining=56, middle of the gap range + [TestCase(127)] // remaining=63, upper boundary + [TestCase(65)] // remaining=1, lower boundary for >64 path + [TestCase(80)] // remaining=16 + [TestCase(96)] // remaining=32 + [TestCase(112)] // remaining=48, boundary where last64 is NOT needed + public void FastHash_AllBytesAreHashed_FoldBackCoverage(int length) + { + byte[] input = new byte[length]; + TestContext.CurrentContext.Random.NextBytes(input); + + int originalHash = ((ReadOnlySpan)input).FastHash(); + + // Verify that changing any byte changes the hash + // This catches the gap bug where bytes[64-71] weren't being hashed + for (int i = 0; i < length; i++) + { + byte[] modified = (byte[])input.Clone(); + modified[i] ^= 0xFF; + + int modifiedHash = ((ReadOnlySpan)modified).FastHash(); + modifiedHash.Should().NotBe(originalHash, $"Changing byte at index {i} should change the hash for length {length}"); + } + } + + // Specifically test the gap range that was buggy: bytes[64-71] for len=120 + [Test] + public void FastHash_GapBytesAreHashed_Len120() + { + byte[] input = new byte[120]; + TestContext.CurrentContext.Random.NextBytes(input); + + int originalHash = ((ReadOnlySpan)input).FastHash(); + + // The bug was that bytes[64-71] weren't hashed for len=120 + // Test each byte in the gap + for (int i = 64; i < 72; i++) + { + byte[] modified = (byte[])input.Clone(); + modified[i] ^= 0xFF; + + int modifiedHash = ((ReadOnlySpan)modified).FastHash(); + modifiedHash.Should().NotBe(originalHash, $"Changing byte at index {i} (in gap range) should change the hash"); + } + } + + // Test medium-large case (33-64 bytes) with overlap to verify it works + [TestCase(50)] // Tests overlap in medium-large path + public void FastHash_MediumLarge_AllBytesContribute(int length) + { + byte[] input = new byte[length]; + TestContext.CurrentContext.Random.NextBytes(input); + + int originalHash = ((ReadOnlySpan)input).FastHash(); + + // Test ALL bytes to verify overlap handling works + for (int i = 0; i < length; i++) + { + byte[] modified = (byte[])input.Clone(); + modified[i] ^= 0xFF; + + int modifiedHash = ((ReadOnlySpan)modified).FastHash(); + modifiedHash.Should().NotBe(originalHash, $"Changing byte at index {i} should change the hash for length {length}"); + } + } + + [TestCase(1)] + [TestCase(7)] + [TestCase(8)] + [TestCase(15)] + [TestCase(16)] + [TestCase(31)] + [TestCase(32)] + [TestCase(33)] + [TestCase(64)] + [TestCase(128)] + [TestCase(256)] + [TestCase(500)] + public void FastHash_VariousLengths_AllBytesContribute(int length) + { + byte[] input = new byte[length]; + TestContext.CurrentContext.Random.NextBytes(input); + + int originalHash = ((ReadOnlySpan)input).FastHash(); + + // Test first, middle, and last bytes to ensure all contribute + int[] indicesToTest = [0, length / 2, length - 1]; + foreach (int i in indicesToTest) + { + byte[] modified = (byte[])input.Clone(); + modified[i] ^= 0xFF; + + int modifiedHash = ((ReadOnlySpan)modified).FastHash(); + modifiedHash.Should().NotBe(originalHash, $"Changing byte at index {i} should change the hash for length {length}"); + } + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Collections/SeqlockCacheTests.cs b/src/Nethermind/Nethermind.Core.Test/Collections/SeqlockCacheTests.cs new file mode 100644 index 000000000000..ee076970d44b --- /dev/null +++ b/src/Nethermind/Nethermind.Core.Test/Collections/SeqlockCacheTests.cs @@ -0,0 +1,428 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Nethermind.Core.Collections; +using Nethermind.Int256; +using NUnit.Framework; + +namespace Nethermind.Core.Test.Collections; + +public class SeqlockCacheTests +{ + private static StorageCell CreateKey(int seed) + { + byte[] addressBytes = new byte[20]; + new Random(seed).NextBytes(addressBytes); + return new StorageCell(new Address(addressBytes), new UInt256((ulong)seed)); + } + + private static byte[] CreateValue(int seed) + { + byte[] value = new byte[32]; + new Random(seed).NextBytes(value); + return value; + } + + [Test] + public void New_cache_returns_miss() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + + bool found = cache.TryGetValue(in key, out byte[]? value); + + found.Should().BeFalse(); + value.Should().BeNull(); + } + + [Test] + public void Set_then_get_returns_value() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] expected = CreateValue(1); + + cache.Set(in key, expected); + bool found = cache.TryGetValue(in key, out byte[]? value); + + found.Should().BeTrue(); + value.Should().BeSameAs(expected); + } + + [Test] + public void Set_overwrites_existing_value() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] first = CreateValue(1); + byte[] second = CreateValue(2); + + cache.Set(in key, first); + cache.Set(in key, second); + bool found = cache.TryGetValue(in key, out byte[]? value); + + found.Should().BeTrue(); + value.Should().BeSameAs(second); + } + + [Test] + public void Set_with_same_value_is_noop() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] expected = CreateValue(1); + + cache.Set(in key, expected); + cache.Set(in key, expected); // Same reference - should be fast-path no-op + bool found = cache.TryGetValue(in key, out byte[]? value); + + found.Should().BeTrue(); + value.Should().BeSameAs(expected); + } + + [Test] + public void Null_value_can_be_stored_and_retrieved() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + + cache.Set(in key, null); + bool found = cache.TryGetValue(in key, out byte[]? value); + + found.Should().BeTrue(); + value.Should().BeNull(); + } + + [Test] + public void GetOrAdd_returns_existing_value() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] expected = CreateValue(1); + + cache.Set(in key, expected); + byte[]? result = cache.GetOrAdd(in key, static (in StorageCell _) => new byte[32]); + + result.Should().BeSameAs(expected); + } + + [Test] + public void GetOrAdd_calls_factory_on_miss() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] factoryResult = CreateValue(1); + + byte[]? result = cache.GetOrAdd(in key, (in StorageCell _) => factoryResult); + + result.Should().BeSameAs(factoryResult); + + // Value should now be cached + bool found = cache.TryGetValue(in key, out byte[]? cached); + found.Should().BeTrue(); + cached.Should().BeSameAs(factoryResult); + } + + [Test] + public void GetOrAdd_with_func_returns_existing_value() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] expected = CreateValue(1); + + cache.Set(in key, expected); + byte[]? result = cache.GetOrAdd(in key, static (in _) => new byte[32]); + + result.Should().BeSameAs(expected); + } + + [Test] + public void GetOrAdd_with_func_calls_factory_on_miss() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] factoryResult = CreateValue(1); + + byte[]? result = cache.GetOrAdd(in key, (in _) => factoryResult); + + result.Should().BeSameAs(factoryResult); + } + + [Test] + public void Clear_invalidates_all_entries() + { + SeqlockCache cache = new(); + StorageCell key1 = CreateKey(1); + StorageCell key2 = CreateKey(2); + + cache.Set(in key1, CreateValue(1)); + cache.Set(in key2, CreateValue(2)); + + cache.Clear(); + + cache.TryGetValue(in key1, out _).Should().BeFalse(); + cache.TryGetValue(in key2, out _).Should().BeFalse(); + } + + [Test] + public void Clear_allows_new_entries() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] beforeClear = CreateValue(1); + byte[] afterClear = CreateValue(2); + + cache.Set(in key, beforeClear); + cache.Clear(); + cache.Set(in key, afterClear); + + bool found = cache.TryGetValue(in key, out byte[]? value); + found.Should().BeTrue(); + value.Should().BeSameAs(afterClear); + } + + [Test] + public void Multiple_clears_work() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + + for (int i = 0; i < 100; i++) + { + byte[] value = CreateValue(i); + cache.Set(in key, value); + cache.TryGetValue(in key, out byte[]? retrieved).Should().BeTrue(); + retrieved.Should().BeSameAs(value); + cache.Clear(); + cache.TryGetValue(in key, out _).Should().BeFalse(); + } + } + + [Test] + public void Different_keys_can_be_stored() + { + SeqlockCache cache = new(); + const int count = 100; + + StorageCell[] keys = new StorageCell[count]; + byte[][] values = new byte[count][]; + + for (int i = 0; i < count; i++) + { + keys[i] = CreateKey(i); + values[i] = CreateValue(i); + cache.Set(in keys[i], values[i]); + } + + // Note: This is a direct-mapped cache, so some entries may be evicted + // due to hash collisions. We just verify that at least some survive. + int hits = 0; + for (int i = 0; i < count; i++) + { + if (cache.TryGetValue(in keys[i], out byte[]? value) && ReferenceEquals(value, values[i])) + { + hits++; + } + } + + hits.Should().BeGreaterThan(0, "at least some entries should survive"); + } + + [Test] + public void Concurrent_reads_are_safe() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] expected = CreateValue(1); + cache.Set(in key, expected); + + const int threadCount = 8; + const int iterations = 10000; + int successCount = 0; + + Parallel.For(0, threadCount, _ => + { + for (int i = 0; i < iterations; i++) + { + if (cache.TryGetValue(in key, out byte[]? value) && ReferenceEquals(value, expected)) + { + Interlocked.Increment(ref successCount); + } + } + }); + + successCount.Should().Be(threadCount * iterations); + } + + [Test] + public void Concurrent_writes_do_not_corrupt() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + + const int threadCount = 8; + const int iterations = 1000; + byte[][] values = new byte[threadCount][]; + for (int i = 0; i < threadCount; i++) + { + values[i] = CreateValue(i); + } + + Parallel.For(0, threadCount, t => + { + for (int i = 0; i < iterations; i++) + { + cache.Set(in key, values[t]); + } + }); + + // After concurrent writes, the cache should contain one of the values + bool found = cache.TryGetValue(in key, out byte[]? result); + if (found) + { + // Value should be one of the values we wrote + bool isValid = false; + for (int i = 0; i < threadCount; i++) + { + if (ReferenceEquals(result, values[i])) + { + isValid = true; + break; + } + } + isValid.Should().BeTrue("cached value should be one of the written values"); + } + } + + [Test] + public void Concurrent_read_write_is_safe() + { + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] value1 = CreateValue(1); + byte[] value2 = CreateValue(2); + + const int iterations = 10000; + bool stop = false; + + // Writer thread + Task writer = Task.Run(() => + { + for (int i = 0; i < iterations && !stop; i++) + { + cache.Set(in key, i % 2 == 0 ? value1 : value2); + } + }); + + // Reader thread + int validReads = 0; + int misses = 0; + Task reader = Task.Run(() => + { + for (int i = 0; i < iterations; i++) + { + if (cache.TryGetValue(in key, out byte[]? value)) + { + // Value should be either value1 or value2 + if (ReferenceEquals(value, value1) || ReferenceEquals(value, value2)) + { + Interlocked.Increment(ref validReads); + } + } + else + { + Interlocked.Increment(ref misses); + } + } + }); + + Task.WaitAll(writer, reader); + stop = true; + + // All reads should have returned valid values (or miss due to concurrent write) + (validReads + misses).Should().Be(iterations); + } + + [Test] + public void AddressAsKey_works_with_cache() + { + SeqlockCache cache = new(); + Address address = new Address("0x1234567890123456789012345678901234567890"); + AddressAsKey key = address; + Account account = new Account(100, 1); + + cache.Set(in key, account); + bool found = cache.TryGetValue(in key, out Account? result); + + found.Should().BeTrue(); + result.Should().BeSameAs(account); + } + + [Test] + public void Concurrent_set_same_value_fast_path_is_safe() + { + // Tests the fast-path optimization where Set skips write if value matches. + // This exercises the seqlock protocol in the fast-path to avoid torn reads. + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[] value = CreateValue(1); + + cache.Set(in key, value); + + const int threadCount = 8; + const int iterations = 10000; + + // Multiple threads all trying to set the same key to the same value + // This hammers the fast-path check + Parallel.For(0, threadCount, _ => + { + for (int i = 0; i < iterations; i++) + { + cache.Set(in key, value); + } + }); + + // Value should still be retrievable and correct + bool found = cache.TryGetValue(in key, out byte[]? result); + found.Should().BeTrue(); + result.Should().BeSameAs(value); + } + + [Test] + public void Concurrent_set_alternating_values_is_safe() + { + // Tests concurrent writes with different values interleaved with same-value writes. + // Exercises both the fast-path (same value) and slow-path (different value). + SeqlockCache cache = new(); + StorageCell key = CreateKey(1); + byte[][] values = new byte[4][]; + for (int i = 0; i < values.Length; i++) + { + values[i] = CreateValue(i); + } + + const int threadCount = 8; + const int iterations = 5000; + + Parallel.For(0, threadCount, t => + { + for (int i = 0; i < iterations; i++) + { + // Each thread cycles through values, creating both fast-path and slow-path scenarios + cache.Set(in key, values[(t + i) % values.Length]); + } + }); + + // After all writes, cache should contain one of the valid values + bool found = cache.TryGetValue(in key, out byte[]? result); + if (found) + { + bool isValid = Array.Exists(values, v => ReferenceEquals(v, result)); + isValid.Should().BeTrue("cached value should be one of the written values"); + } + } +} diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs index bd8b19db914f..2f4575e7c705 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/BlockDecoderTests.cs @@ -2,7 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; @@ -16,9 +18,9 @@ namespace Nethermind.Core.Test.Encoding; public class BlockDecoderTests { - private readonly Block[] _scenarios; + private static readonly Block[] _scenarios = BuildScenarios(); - public BlockDecoderTests() + private static Block[] BuildScenarios() { var transactions = new Transaction[100]; for (int i = 0; i < transactions.Length; i++) @@ -40,8 +42,8 @@ public BlockDecoderTests() .TestObject; } - _scenarios = new[] - { + return + [ Build.A.Block.WithNumber(1).TestObject, Build.A.Block .WithNumber(1) @@ -95,9 +97,14 @@ public BlockDecoderTests() .WithExcessBlobGas(ulong.MaxValue) .WithMixHash(Keccak.EmptyTreeHash) .TestObject - }; + ]; } + private static IEnumerable BlockScenarios() => _scenarios; + + private static IEnumerable BlockScenariosWithTxs() => + _scenarios.Where(static b => b.Transactions.Length > 0); + [Test] public void Can_do_roundtrip_null([Values(true, false)] bool valueDecoder) { @@ -122,17 +129,16 @@ public void Can_do_roundtrip_regression([Values(true, false)] bool valueDecoder) } [Test] - public void Can_do_roundtrip_scenarios([Values(true, false)] bool valueDecoder) + public void Can_do_roundtrip_scenarios( + [ValueSource(nameof(BlockScenarios))] Block block, + [Values(true, false)] bool valueDecoder) { BlockDecoder decoder = new(); - foreach (Block block in _scenarios) - { - Rlp encoded = decoder.Encode(block); - Rlp.ValueDecoderContext valueDecoderContext = new(encoded.Bytes); - Block? decoded = valueDecoder ? decoder.Decode(ref valueDecoderContext) : decoder.Decode(new RlpStream(encoded.Bytes)); - Rlp encoded2 = decoder.Encode(decoded); - Assert.That(encoded2.Bytes.ToHexString(), Is.EqualTo(encoded.Bytes.ToHexString())); - } + Rlp encoded = decoder.Encode(block); + Rlp.ValueDecoderContext valueDecoderContext = new(encoded.Bytes); + Block? decoded = valueDecoder ? decoder.Decode(ref valueDecoderContext) : decoder.Decode(new RlpStream(encoded.Bytes)); + Rlp encoded2 = decoder.Encode(decoded); + Assert.That(encoded2.Bytes.ToHexString(), Is.EqualTo(encoded.Bytes.ToHexString())); } [TestCase("0xf902cef9025ba055870e2f3ef77a9e6163ee5c005dc51d648a2eead382b9044b1a5ad2ee69b0c6a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0b77e3b74c6c8af85408677375183385a2e55446bd071bf193a4958f7417dc8fba056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800188016345785d8a0000800c80a0000000000000000000000000000000000000000000000000000000000000000088000000000000000007a0cc3b10b54dc4e97c01f1df20e8b95874cd5fe83bf6eae64935a16cb08db85fa98080a00000000000000000000000000000000000000000000000000000000000000000a0e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855c0c0f86ce08080946389e7f33ce3b1e94e4325ef02829cd12297ef7188ffffffffffffffffd80180948a0a19589531694250d570040a0c4b74576919b801d8028094000000000000000000000000000000000000100080d8038094a94f5374fce5edbc8e2a8697c15331677e6ebf0b80")] @@ -143,6 +149,60 @@ public void Write_rlp_of_blocks_to_file(string rlp) File.WriteAllBytes("chains\\block1.rlp".GetApplicationResourcePath(), Bytes.FromHexString(rlp)); } + [Test] + public void Encode_with_pre_encoded_transactions_produces_same_rlp( + [ValueSource(nameof(BlockScenariosWithTxs))] Block block) + { + byte[][] encodedTxs = new byte[block.Transactions.Length][]; + for (int i = 0; i < block.Transactions.Length; i++) + { + encodedTxs[i] = Rlp.Encode(block.Transactions[i], RlpBehaviors.SkipTypedWrapping).Bytes; + } + + BlockDecoder decoder = new(); + Rlp standard = decoder.Encode(block); + + Block blockWithEncoded = new(block.Header, block.Body) { EncodedTransactions = encodedTxs }; + Rlp fast = decoder.Encode(blockWithEncoded); + + Assert.That(fast.Bytes.ToHexString(), Is.EqualTo(standard.Bytes.ToHexString())); + } + + [Test] + public void Encode_with_pre_encoded_typed_transactions_produces_same_rlp() + { + Transaction[] transactions = + [ + Build.A.Transaction.WithNonce(1).WithType(TxType.Legacy).Signed().TestObject, + Build.A.Transaction.WithNonce(2).WithType(TxType.AccessList).Signed().TestObject, + Build.A.Transaction.WithNonce(3).WithType(TxType.EIP1559).Signed().TestObject, + ]; + + Block block = Build.A.Block + .WithNumber(1) + .WithBaseFeePerGas(1) + .WithTransactions(transactions) + .WithWithdrawals(2) + .WithBlobGasUsed(0) + .WithExcessBlobGas(0) + .WithMixHash(Keccak.EmptyTreeHash) + .TestObject; + + byte[][] encodedTxs = new byte[transactions.Length][]; + for (int i = 0; i < transactions.Length; i++) + { + encodedTxs[i] = Rlp.Encode(transactions[i], RlpBehaviors.SkipTypedWrapping).Bytes; + } + + BlockDecoder decoder = new(); + Rlp standard = decoder.Encode(block); + + Block blockWithEncoded = new(block.Header, block.Body) { EncodedTransactions = encodedTxs }; + Rlp fast = decoder.Encode(blockWithEncoded); + + Assert.That(fast.Bytes.ToHexString(), Is.EqualTo(standard.Bytes.ToHexString())); + } + [Test] public void Get_length_null() { diff --git a/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs index 5888c30c65e1..757d490234c2 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/ByteArrayConverterTests.cs @@ -296,6 +296,71 @@ public void Fuzz_RandomHex_SegmentationInvariant() } } + [TestCase(new byte[] { 0xab, 0xcd }, true, true, "\"0xabcd\"")] + [TestCase(new byte[] { 0xab, 0xcd }, false, true, "\"0xabcd\"")] + [TestCase(new byte[] { 0x00, 0xab }, true, true, "\"0xab\"")] + [TestCase(new byte[] { 0x00, 0xab }, false, true, "\"0x00ab\"")] + [TestCase(new byte[] { 0x00, 0x00 }, true, true, "\"0x0\"")] + [TestCase(new byte[] { 0x00, 0x00 }, false, true, "\"0x0000\"")] + [TestCase(new byte[] { 0xab }, true, false, "\"ab\"")] + [TestCase(new byte[] { 0xab }, false, false, "\"ab\"")] + [TestCase(new byte[] { 0x0a }, true, true, "\"0xa\"")] + [TestCase(new byte[] { 0x0a }, false, true, "\"0x0a\"")] + public void Write_OutputFormat(byte[] input, bool skipLeadingZeros, bool addHexPrefix, string expected) + { + using System.IO.MemoryStream ms = new(); + using Utf8JsonWriter writer = new(ms); + ByteArrayConverter.Convert(writer, input, skipLeadingZeros, addHexPrefix); + writer.Flush(); + Encoding.UTF8.GetString(ms.ToArray()).Should().Be(expected); + } + + [Test] + public void Write_LargeOutput_UsesArrayPool() + { + // 200 bytes = 400 hex chars + "0x" prefix + quotes > 256 byte InlineArray threshold + byte[] input = new byte[200]; + for (int i = 0; i < input.Length; i++) input[i] = (byte)(i & 0xFF); + + using System.IO.MemoryStream ms = new(); + using Utf8JsonWriter writer = new(ms); + ByteArrayConverter.Convert(writer, input, skipLeadingZeros: false); + writer.Flush(); + string output = Encoding.UTF8.GetString(ms.ToArray()); + output.Should().StartWith("\"0x"); + output.Should().EndWith("\""); + output.Length.Should().Be(404); // 400 hex + 2 prefix + 2 quotes + } + + [Test] + public void WriteAsPropertyName_Format() + { + ByteArrayConverter converter = new(); + using System.IO.MemoryStream ms = new(); + using Utf8JsonWriter writer = new(ms); + writer.WriteStartObject(); + converter.WriteAsPropertyName(writer, new byte[] { 0xab, 0xcd }, JsonSerializerOptions.Default); + writer.WriteNumberValue(1); + writer.WriteEndObject(); + writer.Flush(); + Encoding.UTF8.GetString(ms.ToArray()).Should().Be("{\"0xabcd\":1}"); + } + + [Test] + public void WriteAsPropertyName_AllZeros() + { + ByteArrayConverter converter = new(); + using System.IO.MemoryStream ms = new(); + using Utf8JsonWriter writer = new(ms); + writer.WriteStartObject(); + converter.WriteAsPropertyName(writer, new byte[] { 0x00, 0x00 }, JsonSerializerOptions.Default); + writer.WriteNumberValue(1); + writer.WriteEndObject(); + writer.Flush(); + // skipLeadingZeros: false preserves all zeros + Encoding.UTF8.GetString(ms.ToArray()).Should().Be("{\"0x0000\":1}"); + } + [Test] public void Test_DictionaryKey() { diff --git a/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs index 1e6afeffa5a5..cd6fe2466ebf 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/Hash256ConverterTests.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Text.Json; using Nethermind.Core.Crypto; @@ -22,5 +23,67 @@ public void Can_read_null() Hash256? result = JsonSerializer.Deserialize("null", options); Assert.That(result, Is.EqualTo(null)); } + + [Test] + public void Writes_zero_hash() + { + Hash256 hash = new(new byte[32]); + string result = JsonSerializer.Serialize(hash, options); + Assert.That(result, Is.EqualTo("\"0x0000000000000000000000000000000000000000000000000000000000000000\"")); + } + + [Test] + public void Writes_all_ones_hash() + { + byte[] bytes = new byte[32]; + Array.Fill(bytes, (byte)0xFF); + Hash256 hash = new(bytes); + string result = JsonSerializer.Serialize(hash, options); + Assert.That(result, Is.EqualTo("\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"")); + } + + [Test] + public void Writes_known_hash() + { + // Keccak256 of empty string + Hash256 hash = Keccak.OfAnEmptyString; + string result = JsonSerializer.Serialize(hash, options); + Assert.That(result, Is.EqualTo($"\"0x{hash.ToString(false)}\"")); + } + + [Test] + public void Writes_sequential_bytes() + { + byte[] bytes = new byte[32]; + for (int i = 0; i < 32; i++) bytes[i] = (byte)i; + Hash256 hash = new(bytes); + string result = JsonSerializer.Serialize(hash, options); + Assert.That(result, Is.EqualTo("\"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f\"")); + } + + [Test] + public void Writes_roundtrip() + { + Hash256 hash = Keccak.Compute("test data"u8); + string json = JsonSerializer.Serialize(hash, options); + Hash256? deserialized = JsonSerializer.Deserialize(json, options); + Assert.That(deserialized, Is.EqualTo(hash)); + } + + [Test] + public void Writes_each_nibble_value() + { + // Ensure all hex chars 0-f appear correctly + byte[] bytes = new byte[32]; + for (int i = 0; i < 16; i++) + { + bytes[i * 2] = (byte)((i << 4) | i); // 0x00, 0x11, 0x22, ..., 0xff + bytes[i * 2 + 1] = (byte)((i << 4) | (15 - i)); // 0x0f, 0x1e, 0x2d, ... + } + Hash256 hash = new(bytes); + string result = JsonSerializer.Serialize(hash, options); + Hash256? roundtrip = JsonSerializer.Deserialize(result, options); + Assert.That(roundtrip, Is.EqualTo(hash)); + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs index f4e8088d4da1..413734f8a535 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/LongConverterTests.cs @@ -15,6 +15,7 @@ public class LongConverterTests : ConverterTestBase static readonly LongConverter converter = new(); static readonly JsonSerializerOptions options = new JsonSerializerOptions { Converters = { converter } }; + [Test] public void Test_roundtrip() { TestConverter(int.MaxValue, static (a, b) => a.Equals(b), converter); @@ -63,5 +64,46 @@ public void Throws_on_null() Assert.Throws( static () => JsonSerializer.Deserialize("null", options)); } + + [TestCase(0L, "\"0x0\"")] + [TestCase(1L, "\"0x1\"")] + [TestCase(15L, "\"0xf\"")] + [TestCase(16L, "\"0x10\"")] + [TestCase(255L, "\"0xff\"")] + [TestCase(256L, "\"0x100\"")] + [TestCase(0xabcdefL, "\"0xabcdef\"")] + [TestCase(0x1L, "\"0x1\"")] + [TestCase(0x10L, "\"0x10\"")] + [TestCase(0x100L, "\"0x100\"")] + [TestCase(0x1000L, "\"0x1000\"")] + [TestCase(0x10000L, "\"0x10000\"")] + [TestCase(0x100000L, "\"0x100000\"")] + [TestCase(0x1000000L, "\"0x1000000\"")] + [TestCase(0x10000000L, "\"0x10000000\"")] + [TestCase(int.MaxValue, "\"0x7fffffff\"")] + [TestCase(long.MaxValue, "\"0x7fffffffffffffff\"")] + [TestCase(-1L, "\"0xffffffffffffffff\"")] + [TestCase(-9223372036854775808L, "\"0x8000000000000000\"")] // long.MinValue + public void Writes_correct_hex(long value, string expected) + { + string result = JsonSerializer.Serialize(value, options); + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void Writes_hex_roundtrip_all_nibble_counts() + { + // Test every nibble count from 1 to 16 + for (int nibbles = 1; nibbles <= 16; nibbles++) + { + long value = nibbles <= 15 + ? 1L << ((nibbles - 1) * 4) + : unchecked((long)0x8000000000000000UL); + + string json = JsonSerializer.Serialize(value, options); + long deserialized = JsonSerializer.Deserialize(json, options); + Assert.That(deserialized, Is.EqualTo(value), $"Roundtrip failed for nibbles={nibbles}, value=0x{(ulong)value:x}"); + } + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs index 1d1cd86a8334..fdcd1e24d186 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/NullableUlongConverterTests.cs @@ -72,5 +72,32 @@ public void Throws_on_negative_numbers() Assert.Throws( static () => JsonSerializer.Deserialize("-1", options)); } + + [TestCase(0UL, "\"0x0\"")] + [TestCase(1UL, "\"0x1\"")] + [TestCase(15UL, "\"0xf\"")] + [TestCase(16UL, "\"0x10\"")] + [TestCase(255UL, "\"0xff\"")] + [TestCase(0xabcdefUL, "\"0xabcdef\"")] + [TestCase(0xffffffffUL, "\"0xffffffff\"")] + [TestCase(0x100000000UL, "\"0x100000000\"")] + [TestCase(ulong.MaxValue, "\"0xffffffffffffffff\"")] + public void Writes_correct_hex(ulong value, string expected) + { + string result = JsonSerializer.Serialize((ulong?)value, options); + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void Writes_hex_roundtrip_all_nibble_counts() + { + for (int nibbles = 1; nibbles <= 16; nibbles++) + { + ulong value = 1UL << ((nibbles - 1) * 4); + string json = JsonSerializer.Serialize((ulong?)value, options); + ulong? deserialized = JsonSerializer.Deserialize(json, options); + Assert.That(deserialized, Is.EqualTo(value), $"Roundtrip failed for nibbles={nibbles}, value=0x{value:x}"); + } + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs b/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs index 9bbe9f7e0e0b..25f4e9737c29 100644 --- a/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Json/UInt256ConverterTests.cs @@ -152,5 +152,140 @@ public void Throws_on_null() Assert.Throws( static () => JsonSerializer.Deserialize("null", options)); } + + [TestCase(0ul, 0ul, 0ul, 0ul, "\"0x0\"")] + [TestCase(1ul, 0ul, 0ul, 0ul, "\"0x1\"")] + [TestCase(0xful, 0ul, 0ul, 0ul, "\"0xf\"")] + [TestCase(0xfful, 0ul, 0ul, 0ul, "\"0xff\"")] + [TestCase(0xabcdeful, 0ul, 0ul, 0ul, "\"0xabcdef\"")] + [TestCase(ulong.MaxValue, 0ul, 0ul, 0ul, "\"0xffffffffffffffff\"")] + [TestCase(ulong.MaxValue, 1ul, 0ul, 0ul, "\"0x1ffffffffffffffff\"")] + [TestCase(0ul, 0ul, 1ul, 0ul, "\"0x100000000000000000000000000000000\"")] + [TestCase(0ul, 0ul, 0ul, 1ul, "\"0x1000000000000000000000000000000000000000000000000\"")] + [TestCase(ulong.MaxValue, ulong.MaxValue, ulong.MaxValue, ulong.MaxValue, "\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"")] + public void Writes_hex(ulong u0, ulong u1, ulong u2, ulong u3, string expected) + { + UInt256 value = new(u0, u1, u2, u3); + string result = JsonSerializer.Serialize(value, options); + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void Writes_hex_roundtrip_all_limb_boundaries() + { + // Test values at each limb boundary + UInt256[] values = + [ + UInt256.One, + new UInt256(ulong.MaxValue), + new UInt256(0, 1), + new UInt256(ulong.MaxValue, ulong.MaxValue), + new UInt256(0, 0, 1, 0), + new UInt256(ulong.MaxValue, ulong.MaxValue, ulong.MaxValue, 0), + new UInt256(0, 0, 0, 1), + UInt256.MaxValue, + ]; + + foreach (UInt256 value in values) + { + string json = JsonSerializer.Serialize(value, options); + UInt256 deserialized = JsonSerializer.Deserialize(json, options); + Assert.That(deserialized, Is.EqualTo(value), $"Roundtrip failed for {value}"); + } + } + + [Test] + public void Writes_zero_padded_hex() + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.ZeroPaddedHex; + try + { + string result = JsonSerializer.Serialize(UInt256.One, options); + Assert.That(result, Is.EqualTo("\"0x0000000000000000000000000000000000000000000000000000000000000001\"")); + + result = JsonSerializer.Serialize(UInt256.MaxValue, options); + Assert.That(result, Is.EqualTo("\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"")); + + result = JsonSerializer.Serialize(UInt256.Zero, options); + Assert.That(result, Is.EqualTo("\"0x0000000000000000000000000000000000000000000000000000000000000000\"")); + } + finally + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.Hex; + } + } + + [Test] + public void Writes_zero_padded_hex_roundtrip() + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.ZeroPaddedHex; + try + { + UInt256 value = new(0xdeadbeef, 0xcafebabe, 0x12345678, 0x9abcdef0); + string json = JsonSerializer.Serialize(value, options); + UInt256 deserialized = JsonSerializer.Deserialize(json, options); + Assert.That(deserialized, Is.EqualTo(value)); + } + finally + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.Hex; + } + } + + [Test] + public void Writes_property_name_hex() + { + using var stream = new System.IO.MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + + writer.WriteStartObject(); + converter.WriteAsPropertyName(writer, (UInt256)0xabcdef, options); + writer.WriteNumberValue(1); + writer.WriteEndObject(); + writer.Flush(); + + string result = System.Text.Encoding.UTF8.GetString(stream.ToArray()); + Assert.That(result, Is.EqualTo("{\"0xabcdef\":1}")); + } + + [Test] + public void Writes_property_name_zero() + { + using var stream = new System.IO.MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + + writer.WriteStartObject(); + converter.WriteAsPropertyName(writer, UInt256.Zero, options); + writer.WriteNumberValue(1); + writer.WriteEndObject(); + writer.Flush(); + + string result = System.Text.Encoding.UTF8.GetString(stream.ToArray()); + Assert.That(result, Is.EqualTo("{\"0x0\":1}")); + } + + [Test] + public void Writes_property_name_zero_padded_hex() + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.ZeroPaddedHex; + try + { + using var stream = new System.IO.MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + + writer.WriteStartObject(); + converter.WriteAsPropertyName(writer, UInt256.One, options); + writer.WriteNumberValue(1); + writer.WriteEndObject(); + writer.Flush(); + + string result = System.Text.Encoding.UTF8.GetString(stream.ToArray()); + Assert.That(result, Is.EqualTo("{\"0x0000000000000000000000000000000000000000000000000000000000000001\":1}")); + } + finally + { + ForcedNumberConversion.ForcedConversion.Value = NumberConversion.Hex; + } + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/MCSLockTests.cs b/src/Nethermind/Nethermind.Core.Test/MCSLockTests.cs index 0dbf3e1ea56e..16d4fac388eb 100644 --- a/src/Nethermind/Nethermind.Core.Test/MCSLockTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/MCSLockTests.cs @@ -59,6 +59,7 @@ public void MultipleThreads() } [Test] + [Retry(3)] public void LockFairnessTest() { int numberOfThreads = 10; diff --git a/src/Nethermind/Nethermind.Core.Test/RlpTests.cs b/src/Nethermind/Nethermind.Core.Test/RlpTests.cs index 49e9a776687e..01320fdf3d38 100644 --- a/src/Nethermind/Nethermind.Core.Test/RlpTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/RlpTests.cs @@ -246,6 +246,16 @@ public void Long_and_big_integer_encoded_the_same(long value) Assert.That(rlpBigInt.Bytes, Is.EqualTo(rlpLong.Bytes)); } + [Test] + public void Encode_generic_with_Rlp_input_preserves_original_bytes() + { + Rlp original = Rlp.Encode(255L); + Rlp reEncoded = Rlp.Encode(original); + + Assert.That(reEncoded.Bytes, Is.EqualTo(original.Bytes)); + Assert.That(reEncoded, Is.SameAs(original)); + } + [TestCase(true)] [TestCase(false)] public void RlpContextWithSliceMemory_shouldNotCopyUnderlyingData(bool sliceValue) diff --git a/src/Nethermind/Nethermind.Core.Test/TestMemDb.cs b/src/Nethermind/Nethermind.Core.Test/TestMemDb.cs index 169a166c58c7..b174709b9662 100644 --- a/src/Nethermind/Nethermind.Core.Test/TestMemDb.cs +++ b/src/Nethermind/Nethermind.Core.Test/TestMemDb.cs @@ -23,18 +23,15 @@ public class TestMemDb : MemDb, ITunableDb public Func? ReadFunc { get; set; } public Func? WriteFunc { get; set; } - public Action? RemoveFunc { get; set; } public bool WasFlushed => FlushCount > 0; - public int FlushCount { get; set; } = 0; + public int FlushCount { get; private set; } [MethodImpl(MethodImplOptions.Synchronized)] public override byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) { _readKeys.Add((key.ToArray(), flags)); - - if (ReadFunc is not null) return ReadFunc(key.ToArray()); - return base.Get(key, flags); + return ReadFunc is not null ? ReadFunc(key.ToArray()) : base.Get(key, flags); } [MethodImpl(MethodImplOptions.Synchronized)] @@ -46,71 +43,32 @@ public override void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags base.Set(key, value, flags); } - public override Span GetSpan(ReadOnlySpan key) - { - return Get(key); - } - [MethodImpl(MethodImplOptions.Synchronized)] public override void Remove(ReadOnlySpan key) { _removedKeys.Add(key.ToArray()); - - if (RemoveFunc is not null) - { - RemoveFunc.Invoke(key.ToArray()); - return; - } base.Remove(key); } - public void Tune(ITunableDb.TuneType type) - { - _tuneTypes.Add(type); - } - - public bool WasTunedWith(ITunableDb.TuneType type) - { - return _tuneTypes.Contains(type); - } + public void Tune(ITunableDb.TuneType type) => _tuneTypes.Add(type); + public bool WasTunedWith(ITunableDb.TuneType type) => _tuneTypes.Contains(type); - public void KeyWasRead(byte[] key, int times = 1) - { + public void KeyWasRead(byte[] key, int times = 1) => _readKeys.Count(it => Bytes.AreEqual(it.Item1, key)).Should().Be(times); - } - public void KeyWasReadWithFlags(byte[] key, ReadFlags flags, int times = 1) - { + public void KeyWasReadWithFlags(byte[] key, ReadFlags flags, int times = 1) => _readKeys.Count(it => Bytes.AreEqual(it.Item1, key) && it.Item2 == flags).Should().Be(times); - } - public void KeyWasWritten(byte[] key, int times = 1) - { + public void KeyWasWritten(byte[] key, int times = 1) => _writes.Count(it => Bytes.AreEqual(it.Item1.Item1, key)).Should().Be(times); - } - public void KeyWasWritten(Func<(byte[], byte[]?), bool> cond, int times = 1) - { + public void KeyWasWritten(Func<(byte[], byte[]?), bool> cond, int times = 1) => _writes.Count(it => cond.Invoke(it.Item1)).Should().Be(times); - } - public void KeyWasWrittenWithFlags(byte[] key, WriteFlags flags, int times = 1) - { + public void KeyWasWrittenWithFlags(byte[] key, WriteFlags flags, int times = 1) => _writes.Count(it => Bytes.AreEqual(it.Item1.Item1, key) && it.Item2 == flags).Should().Be(times); - } - - public void KeyWasRemoved(Func cond, int times = 1) - { - _removedKeys.Count(cond).Should().Be(times); - } - public override IWriteBatch StartWriteBatch() - { - return new InMemoryWriteBatch(this); - } - - public override void Flush(bool onlyWal) - { - FlushCount++; - } + public void KeyWasRemoved(Func cond, int times = 1) => _removedKeys.Count(cond).Should().Be(times); + public override IWriteBatch StartWriteBatch() => new InMemoryWriteBatch(this); + public override void Flush(bool onlyWal) => FlushCount++; } diff --git a/src/Nethermind/Nethermind.Core/Address.cs b/src/Nethermind/Nethermind.Core/Address.cs index 0bad6dd83609..80516f59c95c 100644 --- a/src/Nethermind/Nethermind.Core/Address.cs +++ b/src/Nethermind/Nethermind.Core/Address.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using System.Text.Json.Serialization; - +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -20,7 +20,7 @@ namespace Nethermind.Core [JsonConverter(typeof(AddressConverter))] [TypeConverter(typeof(AddressTypeConverter))] [DebuggerDisplay("{ToString()}")] - public class Address : IEquatable
, IComparable
+ public sealed class Address : IEquatable
, IComparable
{ public const int Size = 20; private const int HexCharsCount = 2 * Size; // 5a4eab120fb44eb6684e5e32785702ff45ea344d @@ -273,9 +273,11 @@ public ValueHash256 ToHash() return result; } + + internal long GetHashCode64() => SpanExtensions.FastHash64For20Bytes(ref MemoryMarshal.GetArrayDataReference(Bytes)); } - public readonly struct AddressAsKey(Address key) : IEquatable + public readonly struct AddressAsKey(Address key) : IEquatable, IHash64bit { private readonly Address _key = key; public Address Value => _key; @@ -289,6 +291,10 @@ public override string ToString() { return _key?.ToString() ?? ""; } + + public long GetHashCode64() => _key is not null ? _key.GetHashCode64() : 0; + + public bool Equals(in AddressAsKey other) => _key == other._key; } public ref struct AddressStructRef diff --git a/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs b/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs index c6a24a3e10cb..b93ec4916d3e 100644 --- a/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs +++ b/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs @@ -2,9 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using System.Buffers.Text; using System.IO; using System.Runtime.CompilerServices; using System.Security.Cryptography; +using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -21,27 +24,53 @@ public sealed partial class JwtAuthentication : IRpcAuthentication private const int JwtTokenTtl = 60; private const int JwtSecretLength = 64; + // Manual HS256 validation limits + private const int SHA256HashBytes = 32; + private const int HS256SignatureSegmentLength = 44; // 43 Base64Url chars + dot separator + private const int StackBufferSize = 256; + private const int MaxManualJwtLength = StackBufferSize + HS256SignatureSegmentLength; // 300 + private static readonly Task True = Task.FromResult(true); private static readonly Task False = Task.FromResult(false); + // Known HS256 JWT header Base64Url encodings used by consensus clients + // {"alg":"HS256","typ":"JWT"} + private const string HeaderAlgTyp = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; + // {"typ":"JWT","alg":"HS256"} + private const string HeaderTypAlg = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"; + // {"alg":"HS256"} (some CLs omit typ) + private const string HeaderAlgOnly = "eyJhbGciOiJIUzI1NiJ9"; + private readonly JsonWebTokenHandler _handler = new(); - private readonly SecurityKey _securityKey; + private readonly byte[] _secretBytes; + private readonly TokenValidationParameters _tokenValidationParameters; private readonly ILogger _logger; private readonly ITimestamper _timestamper; - private readonly LifetimeValidator _lifetimeValidator; - // Single entry cache: last successfully validated token - private TokenCacheEntry? _lastToken; + // Single entry cache: last successfully validated token (allocation-free) + // Write order: iat first, then token with Volatile.Write (release fence) + // Read order: token with Volatile.Read (acquire fence), then iat + private string? _cachedToken; + private long _cachedTokenIat; private JwtAuthentication(byte[] secret, ITimestamper timestamper, ILogger logger) { ArgumentNullException.ThrowIfNull(secret); ArgumentNullException.ThrowIfNull(timestamper); - _securityKey = new SymmetricSecurityKey(secret); + _secretBytes = secret; + SecurityKey securityKey = new SymmetricSecurityKey(secret); _logger = logger; _timestamper = timestamper; - _lifetimeValidator = LifetimeValidator; + _tokenValidationParameters = new TokenValidationParameters + { + IssuerSigningKey = securityKey, + RequireExpirationTime = false, + ValidateLifetime = true, + ValidateAudience = false, + ValidateIssuer = false, + LifetimeValidator = LifetimeValidator + }; } public static JwtAuthentication FromSecret(string secret, ITimestamper timestamper, ILogger logger) @@ -136,7 +165,14 @@ public Task Authenticate(string? token) return True; } - return AuthenticateCore(token); + // fast manual HS256 validation: avoids Microsoft.IdentityModel overhead + if (TryValidateManual(token, nowUnixSeconds, out bool accepted)) + { + return accepted ? True : False; + } + + // fallback to full library validation for unrecognized header formats + return AuthenticateCore(token, nowUnixSeconds); [MethodImpl(MethodImplOptions.NoInlining)] void WarnTokenNotFound() => _logger.Warn("Message authentication error: The token cannot be found."); @@ -145,81 +181,267 @@ public Task Authenticate(string? token) void TokenMalformed() => _logger.Warn($"Message authentication error: The token must start with '{JwtMessagePrefix}'."); } - private async Task AuthenticateCore(string token) + /// + /// Manual HS256 JWT validation for known header formats. + /// Returns true if handled (result in ), false to fall through to library. + /// + [SkipLocalsInit] + private bool TryValidateManual(string token, long nowUnixSeconds, out bool accepted) { - try + accepted = false; + // Extract raw JWT (after "Bearer ") + ReadOnlySpan jwt = token.AsSpan(JwtMessagePrefix.Length); + + // Bail early: signed part must fit in StackBufferSize, plus HS256SignatureSegmentLength for the signature. + if (jwt.Length > MaxManualJwtLength) + return false; + + // Known HS256 header lengths: 36 (AlgTyp, TypAlg) or 20 (AlgOnly). + // Check dot at known position and verify header in one shot — avoids IndexOf scan. + int firstDot; + if (jwt.Length > 36 && jwt[36] == '.' && + (jwt[..36].SequenceEqual(HeaderAlgTyp) || jwt[..36].SequenceEqual(HeaderTypAlg))) { - TokenValidationParameters tokenValidationParameters = new() - { - IssuerSigningKey = _securityKey, - RequireExpirationTime = false, - ValidateLifetime = true, - ValidateAudience = false, - ValidateIssuer = false, - LifetimeValidator = _lifetimeValidator - }; + firstDot = 36; + } + else if (jwt.Length > 20 && jwt[20] == '.' && jwt[..20].SequenceEqual(HeaderAlgOnly)) + { + firstDot = 20; + } + else + { + return false; + } - ReadOnlyMemory tokenSlice = token.AsMemory(JwtMessagePrefix.Length); - JsonWebToken jwtToken = _handler.ReadJsonWebToken(tokenSlice); - TokenValidationResult result = await _handler.ValidateTokenAsync(jwtToken, tokenValidationParameters); + // HS256 sig = 43 Base64Url chars, so second dot is at jwt.Length - HS256SignatureSegmentLength. + // Computed directly — eliminates IndexOf scan over payload+signature. + int secondDot = jwt.Length - HS256SignatureSegmentLength; + if (secondDot <= firstDot || jwt[secondDot] != '.') + return false; + + ReadOnlySpan payload = jwt[(firstDot + 1)..secondDot]; + ReadOnlySpan signature = jwt[(secondDot + 1)..]; + + // Compute HMAC-SHA256 over "header.payload" (ASCII bytes). + // secondDot == char count == byte count (JWT is pure ASCII). + // Early length check guarantees secondDot <= 256. + ReadOnlySpan signedPart = jwt[..secondDot]; + Span signedBytes = stackalloc byte[StackBufferSize]; + signedBytes = signedBytes[..secondDot]; + + if (Ascii.FromUtf16(signedPart, signedBytes, out _) != OperationStatus.Done) + return false; + + Span computedHash = stackalloc byte[SHA256HashBytes]; + HMACSHA256.HashData(_secretBytes, signedBytes, computedHash); + + Span sigBytes = stackalloc byte[SHA256HashBytes]; + if (Base64Url.DecodeFromChars(signature, sigBytes, out _, out int sigBytesWritten) != OperationStatus.Done + || sigBytesWritten != SHA256HashBytes) + { + return true; + } + + if (!CryptographicOperations.FixedTimeEquals(computedHash, sigBytes)) + { + if (_logger.IsWarn) WarnInvalidSig(); + return true; + } + + if (!TryExtractClaims(payload, out long iat, out long exp)) + return false; // sig valid but can't parse claims — let library handle it + + if (exp > 0 && nowUnixSeconds >= exp) + { + if (_logger.IsWarn) WarnTokenExpiredExp(exp, nowUnixSeconds); + return true; + } + + // Overflow-safe absolute-difference check: casting to ulong maps negative values to + // large positives, so (ulong)(a - b + c) > (ulong)(2*c) is equivalent to |a - b| > c + // without needing Math.Abs (which can overflow on long.MinValue). + if ((ulong)(iat - nowUnixSeconds + JwtTokenTtl) > (ulong)(JwtTokenTtl * 2)) + { + if (_logger.IsWarn) WarnTokenExpiredIat(iat, nowUnixSeconds); + return true; + } + + CacheLastToken(token, iat); + accepted = true; + return true; + + + [MethodImpl(MethodImplOptions.NoInlining)] + void WarnTokenExpiredExp(long e, long now) => _logger.Warn($"Token expired. exp: {e}, now: {now}"); + + [MethodImpl(MethodImplOptions.NoInlining)] + void WarnTokenExpiredIat(long i, long now) => _logger.Warn($"Token expired. iat: {i}, now: {now}"); + + [MethodImpl(MethodImplOptions.NoInlining)] + void WarnInvalidSig() => _logger.Warn("Message authentication error: Invalid token signature."); + } + + /// + /// Extract "iat" (required) and "exp" (optional) integer claims from a JWT payload. + /// Uses a lightweight byte scanner instead of Utf8JsonReader to avoid: + /// - 552-byte stack frame (Utf8JsonReader struct ~200 bytes + locals) + /// - 432-byte prolog zeroing loop + /// - ArrayPool rent/return + try/finally + /// - 90 inlined reader methods bloating to 2979 bytes of native code + /// + [SkipLocalsInit] + private static bool TryExtractClaims(ReadOnlySpan payloadBase64Url, out long iat, out long exp) + { + iat = 0; + exp = 0; + + // Decode payload from Base64Url into a fixed stack buffer. + // Engine API payloads are tiny (~30 bytes). If decoded output exceeds + // StackBufferSize bytes, DecodeFromChars returns DestinationTooSmall → we reject. + Span decoded = stackalloc byte[StackBufferSize]; + if (Base64Url.DecodeFromChars(payloadBase64Url, decoded, out _, out int bytesWritten) != OperationStatus.Done) + return false; + + // Scan decoded UTF-8 bytes for "iat" and "exp" keys with integer values. + // JWT payloads are compact JSON objects: {"iat":NNNNN,"exp":NNNNN} + // The scanner finds quoted 3-letter keys and parses the integer after the colon. + ReadOnlySpan json = decoded[..bytesWritten]; + bool foundIat = false; - if (!result.IsValid) + for (int i = 0; i < json.Length - 4; i++) + { + if (json[i] != '"') continue; + + byte k1 = json[i + 1], k2 = json[i + 2], k3 = json[i + 3]; + if (json[i + 4] != '"') continue; + + if (k1 == 'i' && k2 == 'a' && k3 == 't') { - if (_logger.IsWarn) WarnInvalidResult(result.Exception); - return false; + foundIat = TryParseClaimValue(json, i + 5, out iat); } - - DateTime now = _timestamper.UtcNow; - long issuedAtUnix = jwtToken.IssuedAt.ToUnixTimeSeconds(); - if (Math.Abs(issuedAtUnix - now.ToUnixTimeSeconds()) <= JwtTokenTtl) + else if (k1 == 'e' && k2 == 'x' && k3 == 'p') { - // full validation succeeded and TTL check passed - cache as last valid token - CacheLastToken(token, issuedAtUnix); - - if (_logger.IsTrace) Trace(jwtToken, now, tokenSlice); - return true; + TryParseClaimValue(json, i + 5, out exp); } + } - if (_logger.IsWarn) WarnTokenExpired(jwtToken, now); - return false; + return foundIat; + } + + /// + /// Parse an integer claim value starting at (expects ":digits"). + /// The (uint)pos < (uint)json.Length pattern collapses a two-condition bounds check + /// (pos >= 0 && pos < Length) into a single unsigned comparison, allowing the + /// JIT to eliminate the redundant range check on the subsequent indexer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool TryParseClaimValue(ReadOnlySpan json, int pos, out long value) + { + value = 0; + // Skip optional whitespace before colon + while ((uint)pos < (uint)json.Length && json[pos] == ' ') pos++; + if ((uint)pos >= (uint)json.Length || json[pos] != ':') return false; + pos++; + // Skip optional whitespace after colon + while ((uint)pos < (uint)json.Length && json[pos] == ' ') pos++; + // Parse unsigned integer digits + bool hasDigit = false; + while ((uint)pos < (uint)json.Length) + { + uint digit = (uint)(json[pos] - '0'); + if (digit > 9) break; + value = value * 10 + digit; + hasDigit = true; + pos++; + } + return hasDigit; + } + + /// + /// Library-based JWT validation for unrecognized header formats. + /// Checks for synchronous Task completion to avoid async state machine on the hot path. + /// + private Task AuthenticateCore(string token, long nowUnixSeconds) + { + try + { + ReadOnlyMemory tokenSlice = token.AsMemory(JwtMessagePrefix.Length); + JsonWebToken jwtToken = _handler.ReadJsonWebToken(tokenSlice); + Task task = _handler.ValidateTokenAsync(jwtToken, _tokenValidationParameters); + + // HS256 validation is CPU-bound → task is almost always already completed. + // Avoid async state machine overhead for the common synchronous path. + return task.IsCompletedSuccessfully + ? ValidateLibraryResult(task.GetAwaiter().GetResult(), token, jwtToken, nowUnixSeconds) ? True : False + : AwaitValidation(task, token, jwtToken, nowUnixSeconds); } catch (Exception ex) { - if (_logger.IsWarn) WarnAuthenticationError(ex); + if (_logger.IsWarn) WarnAuthError(ex); + return False; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void WarnAuthError(Exception? ex) => _logger.Warn($"Message authentication error: {ex?.Message}"); + } + + private bool ValidateLibraryResult(TokenValidationResult result, string token, JsonWebToken jwtToken, long nowUnixSeconds) + { + if (!result.IsValid) + { + if (_logger.IsWarn) WarnInvalidResult(result.Exception); + return false; + } + + long issuedAtUnix = jwtToken.IssuedAt.ToUnixTimeSeconds(); + + // Unsigned range check: |iat - now| <= TTL without Math.Abs overflow guard + if ((ulong)(issuedAtUnix - nowUnixSeconds + JwtTokenTtl) > (ulong)(JwtTokenTtl * 2)) + { + if (_logger.IsWarn) WarnTokenExpired(issuedAtUnix, nowUnixSeconds); return false; } + CacheLastToken(token, issuedAtUnix); + if (_logger.IsTrace) TraceAuth(jwtToken, nowUnixSeconds, token); + return true; + [MethodImpl(MethodImplOptions.NoInlining)] void WarnInvalidResult(Exception? ex) { - if (ex is SecurityTokenDecryptionFailedException) - { - _logger.Warn("Message authentication error: The token cannot be decrypted."); - } - else if (ex is SecurityTokenReplayDetectedException) - { - _logger.Warn("Message authentication error: The token has been used multiple times."); - } - else if (ex is SecurityTokenInvalidSignatureException) + _logger.Warn(ex switch { - _logger.Warn("Message authentication error: Invalid token signature."); - } - else - { - WarnAuthenticationError(ex); - } + SecurityTokenDecryptionFailedException => "Message authentication error: The token cannot be decrypted.", + SecurityTokenReplayDetectedException => "Message authentication error: The token has been used multiple times.", + SecurityTokenInvalidSignatureException => "Message authentication error: Invalid token signature.", + _ => $"Message authentication error: {ex?.Message}" + }); } [MethodImpl(MethodImplOptions.NoInlining)] - void WarnAuthenticationError(Exception? ex) => _logger.Warn($"Message authentication error: {ex?.Message}"); + void WarnTokenExpired(long iat, long now) + => _logger.Warn($"Token expired. iat: {iat}, now: {now}"); [MethodImpl(MethodImplOptions.NoInlining)] - void WarnTokenExpired(JsonWebToken jwtToken, DateTime now) - => _logger.Warn($"Token expired. Now is {now}, token issued at {jwtToken.IssuedAt}"); + void TraceAuth(JsonWebToken jwt, long now, string tok) + => _logger.Trace($"Message authenticated. Token: {tok.AsMemory(JwtMessagePrefix.Length)}, iat: {jwt.IssuedAt}, time: {now}"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private async Task AwaitValidation(Task task, string token, JsonWebToken jwtToken, long nowUnixSeconds) + { + try + { + return ValidateLibraryResult(await task, token, jwtToken, nowUnixSeconds); + } + catch (Exception ex) + { + if (_logger.IsWarn) WarnAuthError(ex); + return false; + } [MethodImpl(MethodImplOptions.NoInlining)] - void Trace(JsonWebToken jwtToken, DateTime now, ReadOnlyMemory token) - => _logger.Trace($"Message authenticated. Token: {token}, iat: {jwtToken.IssuedAt}, time: {now}"); + void WarnAuthError(Exception? ex) => _logger.Warn($"Message authentication error: {ex?.Message}"); } private bool LifetimeValidator( @@ -232,44 +454,37 @@ private bool LifetimeValidator( return _timestamper.UnixTime.SecondsLong < expires.Value.ToUnixTimeSeconds(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void CacheLastToken(string token, long issuedAtUnixSeconds) { - TokenCacheEntry entry = new(token, issuedAtUnixSeconds); - // last writer wins, atomic swap - Interlocked.Exchange(ref _lastToken, entry); + // Write iat first (plain store), then token with release fence. + // Reader uses acquire fence on token, then reads iat — guarantees + // the iat visible is at least as fresh as the token that was read. + _cachedTokenIat = issuedAtUnixSeconds; + Volatile.Write(ref _cachedToken, token); } private bool TryLastValidationFromCache(string token, long nowUnixSeconds) { - // Read the last validated token entry atomically - // this is a single entry cache because tokens tend to be reused - // for a handful of sequential requests before a fresh token is issued - TokenCacheEntry? entry = Volatile.Read(ref _lastToken); - if (entry is null) + // Acquire fence on token read; guarantees _cachedTokenIat is at least as fresh + string? cached = Volatile.Read(ref _cachedToken); + if (cached is null) return false; - // Only allow cache hit if the exact same token string is being reused - // different tokens bypass the cache and undergo full validation - if (!string.Equals(entry.Token, token, StringComparison.Ordinal)) + if (!string.Equals(cached, token, StringComparison.Ordinal)) return false; - // Token reuse is only allowed within the original JWT lifetime - // We never extend token validity beyond what the issuer intended - // - IssuedAtUnixSeconds ensures we don't accept a token older than TTL - if (Math.Abs(entry.IssuedAtUnixSeconds - nowUnixSeconds) > JwtTokenTtl) + // Unsigned range check: |iat - now| <= TTL + if ((ulong)(_cachedTokenIat - nowUnixSeconds + JwtTokenTtl) > (ulong)(JwtTokenTtl * 2)) { - // Token lifetime exceeded - drop the cached entry and force a fresh validation - Interlocked.CompareExchange(ref _lastToken, null, entry); + Volatile.Write(ref _cachedToken, null); return false; } - // Same token, within TTL, recently validated: - // Accept as valid without rerunning JWT parsing and crypto checks return true; } [GeneratedRegex("^(0x)?[0-9a-fA-F]{64}$")] private static partial Regex SecretRegex(); - private record TokenCacheEntry(string Token, long IssuedAtUnixSeconds); } diff --git a/src/Nethermind/Nethermind.Core/Block.cs b/src/Nethermind/Nethermind.Core/Block.cs index 8d53097d5198..5fd94383cdb6 100644 --- a/src/Nethermind/Nethermind.Core/Block.cs +++ b/src/Nethermind/Nethermind.Core/Block.cs @@ -126,6 +126,13 @@ public Transaction[] Transactions [JsonIgnore] public int? EncodedSize { get; set; } + /// + /// Pre-encoded transaction bytes in SkipTypedWrapping format (as received from CL). + /// Used to avoid re-encoding transactions when storing blocks. + /// + [JsonIgnore] + public byte[][]? EncodedTransactions { get; set; } + public override string ToString() => ToString(Format.Short); public string ToString(Format format) => format switch diff --git a/src/Nethermind/Nethermind.Core/Bloom.cs b/src/Nethermind/Nethermind.Core/Bloom.cs index baeb3e75e168..cc5d3acce000 100644 --- a/src/Nethermind/Nethermind.Core/Bloom.cs +++ b/src/Nethermind/Nethermind.Core/Bloom.cs @@ -53,7 +53,9 @@ public Bloom(ReadOnlySpan bytes) bytes.CopyTo(Bytes); } + [JsonIgnore] public Span Bytes => _bloomData.AsSpan(); + [JsonIgnore] public ReadOnlySpan ReadOnlyBytes => _bloomData.AsReadOnlySpan(); private Span ULongs => _bloomData.AsULongs(); diff --git a/src/Nethermind/Nethermind.Core/Buffers/ArrayMemoryManager.cs b/src/Nethermind/Nethermind.Core/Buffers/ArrayMemoryManager.cs new file mode 100644 index 000000000000..52c1bf4dd496 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Buffers/ArrayMemoryManager.cs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; + +namespace Nethermind.Core.Buffers; + +/// +/// Simple MemoryManager that wraps a byte array without any pinning. +/// Used for in-memory stores where the array is managed and doesn't require special release handling. +/// +public sealed class ArrayMemoryManager(byte[] array) : MemoryManager +{ + protected override void Dispose(bool disposing) { } + + public override Span GetSpan() => array; + + public override MemoryHandle Pin(int elementIndex = 0) => default; + + public override void Unpin() { } +} diff --git a/src/Nethermind/Nethermind.Core/DbSpanMemoryManager.cs b/src/Nethermind/Nethermind.Core/Buffers/DbSpanMemoryManager.cs similarity index 58% rename from src/Nethermind/Nethermind.Core/DbSpanMemoryManager.cs rename to src/Nethermind/Nethermind.Core/Buffers/DbSpanMemoryManager.cs index 48d881ae00c0..18a32b032a88 100644 --- a/src/Nethermind/Nethermind.Core/DbSpanMemoryManager.cs +++ b/src/Nethermind/Nethermind.Core/Buffers/DbSpanMemoryManager.cs @@ -10,24 +10,16 @@ namespace Nethermind.Core.Buffers; -public unsafe sealed class DbSpanMemoryManager : MemoryManager +public sealed unsafe class DbSpanMemoryManager(IReadOnlyKeyValueStore db, Span unmanagedSpan) : MemoryManager { - private readonly IReadOnlyKeyValueStore _db; - private void* _ptr; - private readonly int _length; - - public DbSpanMemoryManager(IReadOnlyKeyValueStore db, Span unmanagedSpan) - { - _db = db; - _ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(unmanagedSpan)); - _length = unmanagedSpan.Length; - } + private void* _ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(unmanagedSpan)); + private readonly int _length = unmanagedSpan.Length; protected override void Dispose(bool disposing) { if (_ptr is not null) { - _db.DangerousReleaseMemory(GetSpan()); + db.DangerousReleaseMemory(GetSpan()); } _ptr = null; @@ -53,13 +45,8 @@ public override MemoryHandle Pin(int elementIndex = 0) return new MemoryHandle(_ptr); } - public override void Unpin() - { - } + public override void Unpin() { } [DoesNotReturn, StackTraceHidden] - private static void ThrowDisposed() - { - throw new ObjectDisposedException(nameof(DbSpanMemoryManager)); - } + private static void ThrowDisposed() => throw new ObjectDisposedException(nameof(DbSpanMemoryManager)); } diff --git a/src/Nethermind/Nethermind.Core/Caching/ISpanCache.cs b/src/Nethermind/Nethermind.Core/Caching/ISpanCache.cs deleted file mode 100644 index c7b56ebd2a8f..000000000000 --- a/src/Nethermind/Nethermind.Core/Caching/ISpanCache.cs +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; - -namespace Nethermind.Core.Caching -{ - /// - /// Its like `ICache` but you can index the key by span - /// - /// - /// - public interface ISpanCache - { - void Clear(); - TValue? Get(ReadOnlySpan key); - bool TryGet(ReadOnlySpan key, out TValue? value); - - /// - /// Sets value in the cache. - /// - /// - /// - /// True if key didn't exist in the cache, otherwise false. - bool Set(ReadOnlySpan key, TValue val); - - /// - /// Delete key from cache. - /// - /// - /// True if key existed in the cache, otherwise false. - bool Delete(ReadOnlySpan key); - bool Contains(ReadOnlySpan key); - int Count { get; } - } -} diff --git a/src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs b/src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs deleted file mode 100644 index 1f499890f03c..000000000000 --- a/src/Nethermind/Nethermind.Core/CappedArrayMemoryManager.cs +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Buffers; - -namespace Nethermind.Core.Buffers; - -public class CappedArrayMemoryManager(CappedArray? data) : MemoryManager -{ - private readonly CappedArray _data = data ?? throw new ArgumentNullException(nameof(data)); - private bool _isDisposed; - - public override Span GetSpan() - { - ObjectDisposedException.ThrowIf(_isDisposed, this); - return _data.AsSpan(); - } - - public override MemoryHandle Pin(int elementIndex = 0) - { - ObjectDisposedException.ThrowIf(_isDisposed, this); - ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)elementIndex, (uint)_data.Length); - // Pinning is a no-op in this managed implementation - return default; - } - - public override void Unpin() - { - ObjectDisposedException.ThrowIf(_isDisposed, this); - // Unpinning is a no-op in this managed implementation - } - - protected override void Dispose(bool disposing) - { - _isDisposed = true; - } - - protected override bool TryGetArray(out ArraySegment segment) - { - ObjectDisposedException.ThrowIf(_isDisposed, this); - segment = _data.AsArraySegment(); - return true; - } -} diff --git a/src/Nethermind/Nethermind.Core/Collections/ArrayListCore.cs b/src/Nethermind/Nethermind.Core/Collections/ArrayListCore.cs index 00b95e683953..518fb7512842 100644 --- a/src/Nethermind/Nethermind.Core/Collections/ArrayListCore.cs +++ b/src/Nethermind/Nethermind.Core/Collections/ArrayListCore.cs @@ -51,6 +51,15 @@ public static void ClearToCount(T[] array, int count) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ClearTail(T[] array, int newCount, int oldCount) + { + if (RuntimeHelpers.IsReferenceOrContainsReferences() && newCount < oldCount) + { + Array.Clear(array, newCount, oldCount - newCount); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Add( ArrayPool pool, @@ -108,9 +117,9 @@ public static void ReduceCount( ClearToCount(oldArray, oldCount); pool.Return(oldArray); } - else if (RuntimeHelpers.IsReferenceOrContainsReferences()) + else { - Array.Clear(array, newCount, oldCount - newCount); + ClearTail(array, newCount, oldCount); } [DoesNotReturn] @@ -228,6 +237,7 @@ public static void Insert( public static void Truncate(int newLength, T[] array, ref int count) { GuardIndex(newLength, count, shouldThrow: true, allowEqualToCount: true); + ClearTail(array, newLength, count); count = newLength; } diff --git a/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs b/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs index 491e4e3528ca..9fdf91723f45 100644 --- a/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Collections/DictionaryExtensions.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +using Nethermind.Core.Resettables; namespace Nethermind.Core.Collections; @@ -15,21 +17,48 @@ public static void Increment(this Dictionary dictionary, TKey k res++; } - public static ref TValue GetOrAdd(this Dictionary dictionary, - TKey key, Func factory, - out bool exists) - where TKey : notnull + extension(Dictionary dictionary) where TKey : notnull { - ref TValue? existing = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out exists); + public ref TValue GetOrAdd(TKey key, Func factory, out bool exists) + { + ref TValue? existing = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out exists); - if (!exists) - existing = factory(key); + if (!exists) + existing = factory(key); + + return ref existing!; + } + + public ref TValue GetOrAdd(TKey key, Func factory) => ref dictionary.GetOrAdd(key, factory, out _); - return ref existing!; } - public static ref TValue GetOrAdd(this Dictionary dictionary, - TKey key, Func factory) - where TKey : notnull => - ref GetOrAdd(dictionary, key, factory, out _); + /// The dictionary whose values will be returned and cleared. + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary, which must implement . + extension(IDictionary dictionary) where TValue : class, IReturnable + { + /// + /// Returns all values in the dictionary to their pool by calling on each value, + /// then clears the dictionary. + /// + /// + /// Use this method when you need to both return pooled objects and clear the dictionary in one operation. + /// + public void ResetAndClear() + { + foreach (TValue value in dictionary.Values) + { + value.Return(); + } + dictionary.Clear(); + } + } + + extension(Dictionary.AlternateLookup dictionary) + where TKey : notnull where TAlternateKey : notnull, allows ref struct + { + public bool TryRemove(TAlternateKey key, [MaybeNullWhen(false)] out TValue value) => + dictionary.Remove(key, out _, out value); + } } diff --git a/src/Nethermind/Nethermind.Core/Collections/HashHelpers.cs b/src/Nethermind/Nethermind.Core/Collections/HashHelpers.cs deleted file mode 100644 index e998398d5e13..000000000000 --- a/src/Nethermind/Nethermind.Core/Collections/HashHelpers.cs +++ /dev/null @@ -1,132 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.Serialization; -using System.Threading; - -namespace Nethermind.Core.Collections -{ - /// - /// Adapted from .net source code. - /// - internal static class HashHelpers - { - public const uint HashCollisionThreshold = 100; - - // This is the maximum prime smaller than Array.MaxLength. - public const int MaxPrimeArrayLength = 0x7FFFFFC3; - - public const int HashPrime = 101; - - // Table of prime numbers to use as hash table sizes. - // A typical resize algorithm would pick the smallest prime number in this array - // that is larger than twice the previous capacity. - // Suppose our Hashtable currently has capacity x and enough elements are added - // such that a resize needs to occur. Resizing first computes 2x then finds the - // first prime in the table greater than 2x, i.e. if primes are ordered - // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. - // Doubling is important for preserving the asymptotic complexity of the - // hashtable operations such as add. Having a prime guarantees that double - // hashing does not lead to infinite loops. IE, your hash function will be - // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. - // We prefer the low computation costs of higher prime numbers over the increased - // memory allocation of a fixed prime number i.e. when right sizing a HashSet. - private static readonly int[] s_primes = - { - 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, - 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, - 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, - 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, - 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 - }; - - public static bool IsPrime(int candidate) - { - if ((candidate & 1) != 0) - { - int limit = (int)Math.Sqrt(candidate); - for (int divisor = 3; divisor <= limit; divisor += 2) - { - if ((candidate % divisor) == 0) - return false; - } - return true; - } - return candidate == 2; - } - - public static int GetPrime(int min) - { - if (min < 0) - throw new ArgumentException("Hashtable's capacity overflowed and went negative. Check load factor, capacity and the current size of the table."); - - foreach (int prime in s_primes) - { - if (prime >= min) - return prime; - } - - // Outside of our predefined table. Compute the hard way. - for (int i = (min | 1); i < int.MaxValue; i += 2) - { - if (IsPrime(i) && ((i - 1) % HashPrime != 0)) - return i; - } - return min; - } - - // Returns size of hashtable to grow to. - public static int ExpandPrime(int oldSize) - { - int newSize = 2 * oldSize; - - // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow. - // Note that this check works even when _items.Length overflowed thanks to the (uint) cast - if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) - { - Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); - return MaxPrimeArrayLength; - } - - return GetPrime(newSize); - } - - /// Returns approximate reciprocal of the divisor: ceil(2**64 / divisor). - /// This should only be used on 64-bit. - public static ulong GetFastModMultiplier(uint divisor) => - ulong.MaxValue / divisor + 1; - - /// Performs a mod operation using the multiplier pre-computed with . - /// This should only be used on 64-bit. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint FastMod(uint value, uint divisor, ulong multiplier) - { - // We use modified Daniel Lemire's fastmod algorithm (https://github.com/dotnet/runtime/pull/406), - // which allows to avoid the long multiplication if the divisor is less than 2**31. - Debug.Assert(divisor <= int.MaxValue); - - // This is equivalent of (uint)Math.BigMul(multiplier * value, divisor, out _). This version - // is faster than BigMul currently because we only need the high bits. - uint highbits = (uint)(((((multiplier * value) >> 32) + 1) * divisor) >> 32); - - Debug.Assert(highbits == value % divisor); - return highbits; - } - - private static ConditionalWeakTable? s_serializationInfoTable; - - public static ConditionalWeakTable SerializationInfoTable - { - get - { - if (s_serializationInfoTable is null) - Interlocked.CompareExchange(ref s_serializationInfoTable, new ConditionalWeakTable(), null); - - return s_serializationInfoTable; - } - } - } -} diff --git a/src/Nethermind/Nethermind.Core/Collections/IHash64bit.cs b/src/Nethermind/Nethermind.Core/Collections/IHash64bit.cs new file mode 100644 index 000000000000..746146576cf4 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Collections/IHash64bit.cs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Core.Collections; + +/// +/// Provides a 64-bit hash code for high-performance caching with reduced collision probability. +/// +/// +/// Types implementing this interface can be used with caches that require extended hash bits +/// for collision resistance (for example, Seqlock-based caches that use additional bits beyond +/// the standard hash code for bucket indexing and collision detection). +/// The 64-bit hash should have good distribution across all bits. +/// +public interface IHash64bit +{ + /// + /// Returns a 64-bit hash code for the current instance. + /// + /// A 64-bit hash code with good distribution across all bits. + long GetHashCode64(); + bool Equals(in TKey other); +} diff --git a/src/Nethermind/Nethermind.Core/Collections/InsertionBehavior.cs b/src/Nethermind/Nethermind.Core/Collections/InsertionBehavior.cs deleted file mode 100644 index 90f4431ec102..000000000000 --- a/src/Nethermind/Nethermind.Core/Collections/InsertionBehavior.cs +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -internal enum InsertionBehavior : byte -{ - None, - OverwriteExisting, - ThrowOnExisting, -} diff --git a/src/Nethermind/Nethermind.Core/Collections/SeqlockCache.cs b/src/Nethermind/Nethermind.Core/Collections/SeqlockCache.cs new file mode 100644 index 000000000000..6ce764fa46b6 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Collections/SeqlockCache.cs @@ -0,0 +1,400 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics.X86; +using System.Threading; + +namespace Nethermind.Core.Collections; + +/// +/// A high-performance 2-way skew-associative cache using a seqlock-style header per entry. +/// +/// Design goals: +/// - Lock-free reads (seqlock pattern) - readers never take locks. +/// - Best-effort writes - writers skip on contention. +/// - O(1) logical Clear() via a global epoch (no per-entry zeroing). +/// - 2-way skew-associative: each way uses independent hash bits for set indexing, +/// breaking correlation between ways ("power of two choices"). Keys that collide +/// in way 0 scatter to different sets in way 1, virtually eliminating conflict misses. +/// +/// Hash bit partitioning (64-bit hash): +/// Bits 0-13: way 0 set index (14 bits) +/// Bits 14-41: hash signature stored in header (28 bits) +/// Bits 42-55: way 1 set index (14 bits, independent from way 0) +/// +/// Header layout (64-bit): +/// [Lock:1][Epoch:26][Hash:28][Seq:8][Occ:1] +/// - Lock (bit 63): set during writes - readers retry/miss +/// - Epoch (bits 37-62): global epoch tag - changes on Clear() +/// - Hash (bits 9-36): per-bucket hash signature (28 bits) +/// - Seq (bits 1- 8): per-entry sequence counter (8 bits) - increments on every successful write +/// - Occ (bit 0): occupied flag - set when slot contains valid data (value may still be null) +/// +/// Array layout: [way0_set0..way0_set16383, way1_set0..way1_set16383] (split, not interleaved). +/// +/// The key type (struct implementing IHash64bit) +/// The value type (reference type, nullable allowed) +public sealed class SeqlockCache + where TKey : struct, IHash64bit + where TValue : class? +{ + /// + /// Number of sets. Must be a power of 2 for mask operations. + /// 16384 sets × 2 ways = 32768 total entries. + /// + private const int Sets = 1 << 14; // 16384 + private const int SetMask = Sets - 1; + + // Header bit layout: + // [Lock:1][Epoch:26][Hash:28][Seq:8][Occ:1] + + private const long LockMarker = unchecked((long)0x8000_0000_0000_0000); // bit 63 + + private const int EpochShift = 37; + private const long EpochMask = 0x7FFF_FFE0_0000_0000; // bits 37-62 (26 bits) + + private const long HashMask = 0x0000_0001_FFFF_FE00; // bits 9-36 (28 bits) + + private const long SeqMask = 0x0000_0000_0000_01FE; // bits 1-8 (8 bits) + private const long SeqInc = 0x0000_0000_0000_0002; // +1 in seq field + + private const long OccupiedBit = 1L; // bit 0 + + // Mask of all "identity" bits for an entry, excluding Lock and Seq. + private const long TagMask = EpochMask | HashMask | OccupiedBit; + + // Mask for checking if an entry is live in the current epoch. + private const long EpochOccMask = EpochMask | OccupiedBit; + + // With 14-bit set index (bits 0-13) for way 0, hash signature needs bits 14+. + // HashShift=5 maps header bits 9-36 to original bits 14-41, avoiding overlap with both ways. + private const int HashShift = 5; + + // Way 1 uses bits 42-55 of the original hash (completely independent from way 0's bits 0-13). + private const int Way1Shift = 42; + + /// + /// Array of entries: [way0_set0..way0_setN, way1_set0..way1_setN]. + /// Split layout ensures each way is a contiguous block for better prefetch behavior. + /// + private readonly Entry[] _entries; + + /// + /// Current epoch counter (unshifted, informational / debugging). + /// + private long _epoch; + + /// + /// Pre-shifted epoch tag: (_epoch << EpochShift) & EpochMask. + /// Readers use this directly to avoid shift/mask in the hot path. + /// + private long _shiftedEpoch; + + public SeqlockCache() + { + _entries = new Entry[Sets << 1]; // Sets * 2 + _epoch = 0; + _shiftedEpoch = 0; + } + + /// + /// Tries to get a value from the cache using a seqlock pattern (lock-free reads). + /// Checks both ways of the target set for the key. + /// + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public unsafe bool TryGetValue(in TKey key, out TValue? value) + { + long hashCode = key.GetHashCode64(); + int idx0 = (int)hashCode & SetMask; + int idx1 = Sets + ((int)(hashCode >> Way1Shift) & SetMask); + + long epochTag = Volatile.Read(ref _shiftedEpoch); + long hashPart = (hashCode >> HashShift) & HashMask; + long expectedTag = epochTag | hashPart | OccupiedBit; + + ref Entry entries = ref MemoryMarshal.GetArrayDataReference(_entries); + + // Prefetch way 1 while we check way 0 — hides L2/L3 latency for skew layout. + if (Sse.IsSupported) + { + Sse.PrefetchNonTemporal(Unsafe.AsPointer(ref Unsafe.Add(ref entries, idx1))); + } + + // === Way 0 === + ref Entry e0 = ref Unsafe.Add(ref entries, idx0); + long h1 = Volatile.Read(ref e0.HashEpochSeqLock); + + if ((h1 & (TagMask | LockMarker)) == expectedTag) + { + ref readonly TKey storedKey = ref e0.Key; + TValue? storedValue = e0.Value; + + long h2 = Volatile.Read(ref e0.HashEpochSeqLock); + if (h1 == h2 && storedKey.Equals(in key)) + { + value = storedValue; + return true; + } + } + + // === Way 1 === + ref Entry e1 = ref Unsafe.Add(ref entries, idx1); + long w1 = Volatile.Read(ref e1.HashEpochSeqLock); + + if ((w1 & (TagMask | LockMarker)) == expectedTag) + { + ref readonly TKey storedKey = ref e1.Key; + TValue? storedValue = e1.Value; + + long w2 = Volatile.Read(ref e1.HashEpochSeqLock); + if (w1 == w2 && storedKey.Equals(in key)) + { + value = storedValue; + return true; + } + } + + value = default; + return false; + } + + /// + /// Delegate-based factory that avoids copying large keys (passes by in). + /// Prefer this over Func<TKey, TValue?> when TKey is big (eg 48 bytes). + /// + public delegate TValue? ValueFactory(in TKey key); + + /// + /// Gets a value from the cache, or adds it using the factory if not present. + /// + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TValue? GetOrAdd(in TKey key, ValueFactory valueFactory) + { + long hashCode = key.GetHashCode64(); + int idx0 = (int)hashCode & SetMask; + int idx1 = Sets + ((int)(hashCode >> Way1Shift) & SetMask); + long hashPart = (hashCode >> HashShift) & HashMask; + + if (TryGetValueCore(in key, idx0, idx1, hashPart, out TValue? value)) + { + return value; + } + + return GetOrAddMiss(in key, valueFactory, idx0, idx1, hashPart); + } + + /// + /// Cold path for GetOrAdd: invokes factory and stores the result. + /// Kept out-of-line so the hot path (cache hit) compiles to a lean method body + /// with minimal register saves and stack frame. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private TValue? GetOrAddMiss(in TKey key, ValueFactory valueFactory, int idx0, int idx1, long hashPart) + { + TValue? value = valueFactory(in key); + SetCore(in key, value, idx0, idx1, hashPart); + return value; + } + + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe bool TryGetValueCore(in TKey key, int idx0, int idx1, long hashPart, out TValue? value) + { + long epochTag = Volatile.Read(ref _shiftedEpoch); + long expectedTag = epochTag | hashPart | OccupiedBit; + + ref Entry entries = ref MemoryMarshal.GetArrayDataReference(_entries); + + if (Sse.IsSupported) + { + Sse.PrefetchNonTemporal(Unsafe.AsPointer(ref Unsafe.Add(ref entries, idx1))); + } + + // Way 0 + ref Entry e0 = ref Unsafe.Add(ref entries, idx0); + long h1 = Volatile.Read(ref e0.HashEpochSeqLock); + + if ((h1 & (TagMask | LockMarker)) == expectedTag) + { + ref readonly TKey storedKey = ref e0.Key; + TValue? storedValue = e0.Value; + + long h2 = Volatile.Read(ref e0.HashEpochSeqLock); + if (h1 == h2 && storedKey.Equals(in key)) + { + value = storedValue; + return true; + } + } + + // Way 1 + ref Entry e1 = ref Unsafe.Add(ref entries, idx1); + long w1 = Volatile.Read(ref e1.HashEpochSeqLock); + + if ((w1 & (TagMask | LockMarker)) == expectedTag) + { + ref readonly TKey storedKey = ref e1.Key; + TValue? storedValue = e1.Value; + + long w2 = Volatile.Read(ref e1.HashEpochSeqLock); + if (w1 == w2 && storedKey.Equals(in key)) + { + value = storedValue; + return true; + } + } + + value = default; + return false; + } + + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetCore(in TKey key, TValue? value, int idx0, int idx1, long hashPart) + { + long epochTag = Volatile.Read(ref _shiftedEpoch); + long tagToStore = epochTag | hashPart | OccupiedBit; + long epochOccTag = epochTag | OccupiedBit; + + ref Entry entries = ref MemoryMarshal.GetArrayDataReference(_entries); + ref Entry e0 = ref Unsafe.Add(ref entries, idx0); + + long h0 = Volatile.Read(ref e0.HashEpochSeqLock); + + // === Way 0: check for matching key === + if (h0 >= 0 && (h0 & TagMask) == tagToStore) + { + ref readonly TKey k0 = ref e0.Key; + TValue? v0 = e0.Value; + + long h0_2 = Volatile.Read(ref e0.HashEpochSeqLock); + if (h0 == h0_2 && k0.Equals(in key)) + { + if (ReferenceEquals(v0, value)) return; // fast-path: same key+value, no-op + WriteEntry(ref e0, h0_2, in key, value, tagToStore); + return; + } + h0 = h0_2; + } + + // === Way 1: check for matching key === + ref Entry e1 = ref Unsafe.Add(ref entries, idx1); + long h1 = Volatile.Read(ref e1.HashEpochSeqLock); + + if (h1 >= 0 && (h1 & TagMask) == tagToStore) + { + ref readonly TKey k1 = ref e1.Key; + TValue? v1 = e1.Value; + + long h1_2 = Volatile.Read(ref e1.HashEpochSeqLock); + if (h1 == h1_2 && k1.Equals(in key)) + { + if (ReferenceEquals(v1, value)) return; // fast-path: same key+value, no-op + WriteEntry(ref e1, h1_2, in key, value, tagToStore); + return; + } + h1 = h1_2; + } + + // === Key not in either way. Evict into an available slot. === + // Priority: stale/empty unlocked > live (alternating by hash bit) > any unlocked > skip. + // The decision tree selects which way to evict into, then issues a single WriteEntry call. + bool h0Live = h0 >= 0 && (h0 & EpochOccMask) == epochOccTag; + bool h1Live = h1 >= 0 && (h1 & EpochOccMask) == epochOccTag; + + bool pick0; + if (!h0Live && h0 >= 0) pick0 = true; + else if (!h1Live && h1 >= 0) pick0 = false; + else if (h0Live && h1Live) pick0 = (hashPart & (1L << 9)) != 0; + else if (h0 >= 0) pick0 = true; + else if (h1 >= 0) pick0 = false; + else return; // both locked, skip + + WriteEntry( + ref pick0 ? ref e0 : ref e1, + pick0 ? h0 : h1, + in key, value, tagToStore); + } + + /// + /// Sets a key-value pair in the cache. + /// Checks both ways of the target set for an existing key match before evicting. + /// + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Set(in TKey key, TValue? value) + { + long hashCode = key.GetHashCode64(); + int idx0 = (int)hashCode & SetMask; + int idx1 = Sets + ((int)(hashCode >> Way1Shift) & SetMask); + long hashPart = (hashCode >> HashShift) & HashMask; + + SetCore(in key, value, idx0, idx1, hashPart); + } + + /// + /// Attempts a CAS-guarded write to a single entry. + /// Kept out-of-line: the CAS atomic dominates latency, so call overhead is invisible, + /// while de-duplication reclaims ~350 bytes of inlined copies across SetCore call sites. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + private static void WriteEntry(ref Entry entry, long existing, in TKey key, TValue? value, long tagToStore) + { + if (existing < 0) return; // locked + + long newSeq = ((existing & SeqMask) + SeqInc) & SeqMask; + long lockedHeader = tagToStore | newSeq | LockMarker; + + if (Interlocked.CompareExchange(ref entry.HashEpochSeqLock, lockedHeader, existing) != existing) + { + return; + } + + entry.Key = key; + entry.Value = value; + + Volatile.Write(ref entry.HashEpochSeqLock, tagToStore | newSeq); + } + + /// + /// Clears all cached entries by incrementing the global epoch tag (O(1)). + /// Entries with stale epochs are treated as empty on subsequent lookups. + /// + public void Clear() + { + long oldShifted = Volatile.Read(ref _shiftedEpoch); + + while (true) + { + long oldEpoch = (oldShifted & EpochMask) >> EpochShift; + long newEpoch = oldEpoch + 1; + long newShifted = (newEpoch << EpochShift) & EpochMask; + + long prev = Interlocked.CompareExchange(ref _shiftedEpoch, newShifted, oldShifted); + if (prev == oldShifted) + { + Volatile.Write(ref _epoch, newEpoch); + return; + } + + oldShifted = prev; + } + } + + /// + /// Cache entry struct. + /// Header is a single 64-bit field to keep the seqlock control word in one atomic unit. + /// + [StructLayout(LayoutKind.Sequential)] + private struct Entry + { + public long HashEpochSeqLock; // [Lock|Epoch|Hash|Seq|Occ] + public TKey Key; + public TValue? Value; + } +} diff --git a/src/Nethermind/Nethermind.Core/Collections/SortedRealList.cs b/src/Nethermind/Nethermind.Core/Collections/SortedRealList.cs deleted file mode 100644 index 02d66af6e651..000000000000 --- a/src/Nethermind/Nethermind.Core/Collections/SortedRealList.cs +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; - -namespace Nethermind.Core.Collections -{ - public class SortedRealList : SortedList, IList> where TKey : notnull - { - // Constructs a new sorted list. The sorted list is initially empty and has - // a capacity of zero. Upon adding the first element to the sorted list the - // capacity is increased to DefaultCapacity, and then increased in multiples of two as - // required. The elements of the sorted list are ordered according to the - // IComparable interface, which must be implemented by the keys of - // all entries added to the sorted list. - public SortedRealList() { } - - // Constructs a new sorted list. The sorted list is initially empty and has - // a capacity of zero. Upon adding the first element to the sorted list the - // capacity is increased to 16, and then increased in multiples of two as - // required. The elements of the sorted list are ordered according to the - // IComparable interface, which must be implemented by the keys of - // all entries added to the sorted list. - // - public SortedRealList(int capacity) : base(capacity) { } - - // Constructs a new sorted list with a given IComparer - // implementation. The sorted list is initially empty and has a capacity of - // zero. Upon adding the first element to the sorted list the capacity is - // increased to 16, and then increased in multiples of two as required. The - // elements of the sorted list are ordered according to the given - // IComparer implementation. If comparer is null, the - // elements are compared to each other using the IComparable - // interface, which in that case must be implemented by the keys of all - // entries added to the sorted list. - // - public SortedRealList(IComparer? comparer) : base(comparer) - { - } - - // Constructs a new sorted dictionary with a given IComparer - // implementation and a given initial capacity. The sorted list is - // initially empty, but will have room for the given number of elements - // before any reallocations are required. The elements of the sorted list - // are ordered according to the given IComparer implementation. If - // comparer is null, the elements are compared to each other using - // the IComparable interface, which in that case must be implemented - // by the keys of all entries added to the sorted list. - // - public SortedRealList(int capacity, IComparer? comparer) : base(capacity, comparer) { } - - // Constructs a new sorted list containing a copy of the entries in the - // given dictionary. The elements of the sorted list are ordered according - // to the IComparable interface, which must be implemented by the - // keys of all entries in the given dictionary as well as keys - // subsequently added to the sorted list. - // - public SortedRealList(IDictionary dictionary) : base(dictionary) { } - - // Constructs a new sorted list containing a copy of the entries in the - // given dictionary. The elements of the sorted list are ordered according - // to the given IComparer implementation. If comparer is - // null, the elements are compared to each other using the - // IComparable interface, which in that case must be implemented - // by the keys of all entries in the given dictionary as well as keys - // subsequently added to the sorted list. - // - public SortedRealList(IDictionary dictionary, IComparer? comparer) : base(dictionary, comparer) { } - - public int IndexOf(KeyValuePair item) => IndexOfKey(item.Key); - - public void Insert(int index, KeyValuePair item) => this.TryAdd(item.Key, item.Value); - - public KeyValuePair this[int index] - { - get => new(Keys[index], Values[index]); - set => this.TryAdd(value.Key, value.Value); - } - } -} diff --git a/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs b/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs index 0ee2476c0762..4b8566b3e4ba 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/Hash256.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using System.Text.Json.Serialization; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -25,7 +26,9 @@ namespace Nethermind.Core.Crypto public const int MemorySize = 32; public static int Length => MemorySize; + [JsonIgnore] public Span BytesAsSpan => MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in _bytes), 1)); + [JsonIgnore] public ReadOnlySpan Bytes => MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in _bytes), 1)); public static implicit operator ValueHash256?(Hash256? keccak) => keccak?.ValueHash256; @@ -129,6 +132,7 @@ public sealed class Hash256 : IEquatable, IComparable public ref readonly ValueHash256 ValueHash256 => ref _hash256; + [JsonIgnore] public Span Bytes => MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in _hash256), 1)); public Hash256(string hexString) diff --git a/src/Nethermind/Nethermind.Core/Crypto/Signature.cs b/src/Nethermind/Nethermind.Core/Crypto/Signature.cs index 35834c23904b..61f152101c09 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/Signature.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/Signature.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using System.Text.Json.Serialization; using Nethermind.Core.Attributes; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -58,6 +59,7 @@ public Signature(string hexString) : this(Core.Extensions.Bytes.FromHexString(hexString)) { } + [JsonIgnore] public Span Bytes => MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref _signature, 1)); public override Memory Memory => CreateMemory(64); @@ -70,8 +72,10 @@ public Signature(string hexString) public static byte GetRecoveryId(ulong v) => v <= VOffset + 1 ? (byte)(v - VOffset) : (byte)(1 - v % 2); public Memory R => Memory.Slice(0, 32); + [JsonIgnore] public ReadOnlySpan RAsSpan => Bytes.Slice(0, 32); public Memory S => Memory.Slice(32, 32); + [JsonIgnore] public ReadOnlySpan SAsSpan => Bytes.Slice(32, 32); [Todo("Change signature to store 65 bytes and just slice it for normal Bytes.")] diff --git a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs index 8e395ecb7208..4fe4d044ee55 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs @@ -73,64 +73,17 @@ public override int Compare(byte[]? x, byte[]? y) if (x is null) { - return y is null ? 0 : 1; + return y is null ? 0 : -1; } - if (y is null) - { - return -1; - } - - if (x.Length == 0) - { - return y.Length == 0 ? 0 : 1; - } - - for (int i = 0; i < x.Length; i++) - { - if (y.Length <= i) - { - return -1; - } + if (y is null) return 1; - int result = x[i].CompareTo(y[i]); - if (result != 0) - { - return result; - } - } - - return y.Length > x.Length ? 1 : 0; + return x.SequenceCompareTo(y); } public static int Compare(ReadOnlySpan x, ReadOnlySpan y) { - if (Unsafe.AreSame(ref MemoryMarshal.GetReference(x), ref MemoryMarshal.GetReference(y)) && - x.Length == y.Length) - { - return 0; - } - - if (x.Length == 0) - { - return y.Length == 0 ? 0 : 1; - } - - for (int i = 0; i < x.Length; i++) - { - if (y.Length <= i) - { - return -1; - } - - int result = x[i].CompareTo(y[i]); - if (result != 0) - { - return result; - } - } - - return y.Length > x.Length ? 1 : 0; + return x.SequenceCompareTo(y); } } diff --git a/src/Nethermind/Nethermind.Core/Extensions/DictionaryExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/DictionaryExtensions.cs deleted file mode 100644 index 6168e96121f4..000000000000 --- a/src/Nethermind/Nethermind.Core/Extensions/DictionaryExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Nethermind.Core.Resettables; - -namespace Nethermind.Core.Extensions; - -public static class DictionaryExtensions -{ - /// - /// Returns all values in the dictionary to their pool by calling on each value, - /// then clears the dictionary. - /// - /// The type of the keys in the dictionary. - /// The type of the values in the dictionary, which must implement . - /// The dictionary whose values will be returned and cleared. - /// - /// Use this method when you need to both return pooled objects and clear the dictionary in one operation. - /// - public static void ResetAndClear(this IDictionary dictionary) - where TValue : class, IReturnable - { - foreach (TValue value in dictionary.Values) - { - value.Return(); - } - dictionary.Clear(); - } -} diff --git a/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs index 1ecd2adc0d84..3f4a9e0c2919 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs @@ -29,13 +29,6 @@ public static UInt256 GWei(this int @this) return (uint)@this * Unit.GWei; } - public static byte[] ToByteArray(this int value) - { - byte[] bytes = new byte[sizeof(int)]; - BinaryPrimitives.WriteInt32BigEndian(bytes, value); - return bytes; - } - public static byte[] ToBigEndianByteArray(this uint value) { byte[] bytes = BitConverter.GetBytes(value); @@ -49,4 +42,14 @@ public static byte[] ToBigEndianByteArray(this uint value) public static byte[] ToBigEndianByteArray(this int value) => ToBigEndianByteArray((uint)value); + + public static byte[] ToLittleEndianByteArray(this uint value) + { + byte[] bytes = new byte[sizeof(uint)]; + BinaryPrimitives.WriteUInt32LittleEndian(bytes, value); + return bytes; + } + + public static byte[] ToLittleEndianByteArray(this int value) + => ToLittleEndianByteArray((uint)value); } diff --git a/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs index 5118ea293bb2..7ae71df3f508 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs @@ -7,6 +7,9 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using Arm = System.Runtime.Intrinsics.Arm; +using x64 = System.Runtime.Intrinsics.X86; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; @@ -19,6 +22,8 @@ public static class SpanExtensions // the performance of the network as a whole. private static readonly uint s_instanceRandom = (uint)System.Security.Cryptography.RandomNumberGenerator.GetInt32(int.MinValue, int.MaxValue); + internal static uint ComputeSeed(int len) => s_instanceRandom + (uint)len; + public static string ToHexString(this in Memory memory, bool withZeroX = false) { return ToHexString(memory.Span, withZeroX, false, false); @@ -224,149 +229,286 @@ public static ArrayPoolListRef ToPooledListRef(this in ReadOnlySpan spa [SkipLocalsInit] public static int FastHash(this ReadOnlySpan input) { - // Fast hardware-accelerated, non-cryptographic hash. - // Core idea: CRC32C is extremely cheap on CPUs with SSE4.2/ARM CRC, - // and gives good diffusion for hashing. We then optionally add extra - // mixing to reduce "CRC linearity" artifacts. - int len = input.Length; - - // Contract choice: empty input hashes to 0. - // (Also avoids doing any ref work on an empty span.) if (len == 0) return 0; - // Using ref + Unsafe.ReadUnaligned lets the JIT hoist bounds checks - // and keep the hot loop tight. - ref byte start = ref MemoryMarshal.GetReference(input); - // Seed with an instance-random value so attackers cannot trivially - // engineer lots of same-bucket keys. Mixing in length makes "same prefix, - // different length" less correlated (CRC alone can be length-sensitive). + ref byte start = ref MemoryMarshal.GetReference(input); uint seed = s_instanceRandom + (uint)len; - // Small: 1-7 bytes. - // Using the tail routine here avoids building a synthetic - // 64-bit value with shifts/byte-permute. - if (len < 8) + if (len >= 16) { - uint small = CrcTailOrdered(seed, ref start, len); - // FinalMix breaks some remaining linearity and improves avalanche for tiny inputs. - return (int)FinalMix(small); + if (x64.Aes.IsSupported) return FastHashAesX64(ref start, len, seed); + if (Arm.Aes.IsSupported) return FastHashAesArm(ref start, len, seed); } - // Medium: 8-31 bytes. - // A single CRC lane is usually fine here - overhead dominates, - // and latency hiding is less important. - if (len < 32) + return FastHashCrc(ref start, len, seed); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [SkipLocalsInit] + internal static int FastHashAesX64(ref byte start, int len, uint seed) + { + Vector128 seedVec = Vector128.CreateScalar(seed).AsByte(); + Vector128 acc0 = Unsafe.As>(ref start) ^ seedVec; + + if (len > 64) { - uint h = seed; - ref byte p = ref start; + Vector128 acc1 = Unsafe.As>(ref Unsafe.Add(ref start, 16)) ^ seedVec; + Vector128 acc2 = Unsafe.As>(ref Unsafe.Add(ref start, 32)) ^ seedVec; + Vector128 acc3 = Unsafe.As>(ref Unsafe.Add(ref start, 48)) ^ seedVec; - // Process as many full 64-bit words as possible. - // "& ~7" is a cheap round-down-to-multiple-of-8 (no division/mod). - int full = len & ~7; - int tail = len - full; + ref byte p = ref Unsafe.Add(ref start, 64); + int remaining = len - 64; - // Streaming CRC over 8-byte chunks. - // ReadUnaligned keeps us safe for arbitrary input alignment. - for (int i = 0; i < full; i += 8) + while (remaining >= 64) { - h = BitOperations.Crc32C(h, Unsafe.ReadUnaligned(ref p)); - p = ref Unsafe.Add(ref p, 8); + acc0 = x64.Aes.Encrypt(Unsafe.As>(ref p), acc0); + acc1 = x64.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 16)), acc1); + acc2 = x64.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 32)), acc2); + acc3 = x64.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 48)), acc3); + + p = ref Unsafe.Add(ref p, 64); + remaining -= 64; } - // Hash remaining 1-7 bytes in strict order (no over-read). - if (tail != 0) - h = CrcTailOrdered(h, ref p, tail); + // Fold 4 lanes: 3 XOR + 1 AES (minimal serial latency) + acc0 ^= acc1; + acc2 ^= acc3; + acc0 ^= acc2; + acc0 = x64.Aes.Encrypt(seedVec, acc0); - // Final mixing for better bit diffusion than raw CRC, - // especially for shorter payloads. - return (int)FinalMix(h); + // Drain remaining 0-63 bytes + while (remaining >= 16) + { + acc0 = x64.Aes.Encrypt(Unsafe.As>(ref p), acc0); + p = ref Unsafe.Add(ref p, 16); + remaining -= 16; + } + + // Remaining 1-15 bytes: use CRC to avoid overlap with drain blocks + if (remaining > 0) + { + uint crc = seed; + if (remaining >= 8) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 8); + remaining -= 8; + } + if ((remaining & 4) != 0) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 4); + } + if ((remaining & 2) != 0) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 2); + } + if ((remaining & 1) != 0) + { + crc = BitOperations.Crc32C(crc, p); + } + acc0 = x64.Aes.Encrypt(Vector128.CreateScalar(crc).AsByte(), acc0); + } } + else if (len > 32) + { + ref byte p = ref Unsafe.Add(ref start, 16); + int remaining = len - 16; - // Large: 32+ bytes. - // Use multiple independent CRC accumulators ("lanes") to hide crc32 - // latency and increase ILP. CRC32C instructions have decent throughput - // but non-trivial latency; 4 lanes keeps the CPU busy. - uint h0 = seed; - uint h1 = seed ^ 0x9E3779B9u; // golden-ratio-ish constants to separate lanes - uint h2 = seed ^ 0x85EBCA6Bu; // constants borrowed from common finalizers (good bit dispersion) - uint h3 = seed ^ 0xC2B2AE35u; - - ref byte q = ref start; - - // Consume all full 64-bit words first. Tail (1-7 bytes) is handled later. - int aligned = len & ~7; - int remaining = aligned; - - // 64-byte unroll: - // - amortizes loop branch/compare overhead - // - feeds enough independent work to keep OoO cores busy - // - maps nicely onto cache line sized chunks - while (remaining >= 64) + while (remaining > 16) + { + acc0 = x64.Aes.Encrypt(Unsafe.As>(ref p), acc0); + p = ref Unsafe.Add(ref p, 16); + remaining -= 16; + } + + Vector128 last = Unsafe.As>(ref Unsafe.Add(ref start, len - 16)); + acc0 = x64.Aes.Encrypt(last, acc0); + } + else { - h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); - h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); - h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); - h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 24))); - - h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 32))); - h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 40))); - h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 48))); - h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 56))); - - q = ref Unsafe.Add(ref q, 64); - remaining -= 64; + Vector128 data = Unsafe.As>(ref Unsafe.Add(ref start, len - 16)); + acc0 = x64.Aes.Encrypt(data, acc0); } - // One more half-unroll for 32 bytes if present. - // Keeps the "drain" path short and avoids a smaller loop with more branches. - if (remaining >= 32) + ulong compressed = acc0.AsUInt64().GetElement(0) ^ acc0.AsUInt64().GetElement(1); + return (int)(uint)(compressed ^ (compressed >> 32)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [SkipLocalsInit] + internal static int FastHashAesArm(ref byte start, int len, uint seed) + { + Vector128 seedVec = Vector128.CreateScalar(seed).AsByte(); + Vector128 acc0 = Unsafe.As>(ref start) ^ seedVec; + + if (len > 64) { - h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); - h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); - h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); - h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 24))); + Vector128 acc1 = Unsafe.As>(ref Unsafe.Add(ref start, 16)) ^ seedVec; + Vector128 acc2 = Unsafe.As>(ref Unsafe.Add(ref start, 32)) ^ seedVec; + Vector128 acc3 = Unsafe.As>(ref Unsafe.Add(ref start, 48)) ^ seedVec; + + ref byte p = ref Unsafe.Add(ref start, 64); + int remaining = len - 64; + + while (remaining >= 64) + { + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref p), acc0)); + acc1 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 16)), acc1)); + acc2 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 32)), acc2)); + acc3 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref Unsafe.Add(ref p, 48)), acc3)); + + p = ref Unsafe.Add(ref p, 64); + remaining -= 64; + } + + acc0 ^= acc1; + acc2 ^= acc3; + acc0 ^= acc2; + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(seedVec, acc0)); + + while (remaining >= 16) + { + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref p), acc0)); + p = ref Unsafe.Add(ref p, 16); + remaining -= 16; + } - q = ref Unsafe.Add(ref q, 32); - remaining -= 32; + if (remaining > 0) + { + uint crc = seed; + if (remaining >= 8) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 8); + remaining -= 8; + } + if ((remaining & 4) != 0) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 4); + } + if ((remaining & 2) != 0) + { + crc = BitOperations.Crc32C(crc, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 2); + } + if ((remaining & 1) != 0) + { + crc = BitOperations.Crc32C(crc, p); + } + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Vector128.CreateScalar(crc).AsByte(), acc0)); + } } + else if (len > 32) + { + ref byte p = ref Unsafe.Add(ref start, 16); + int remaining = len - 16; - // Drain any remaining full 64-bit words (0, 8, 16, or 24 bytes). - // This is branchy but only runs once, so it is cheaper than another loop. - if (remaining != 0) + while (remaining > 16) + { + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(Unsafe.As>(ref p), acc0)); + p = ref Unsafe.Add(ref p, 16); + remaining -= 16; + } + + Vector128 last = Unsafe.As>(ref Unsafe.Add(ref start, len - 16)); + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(last, acc0)); + } + else { - // remaining is a multiple of 8 here. - if (remaining >= 8) h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); - if (remaining >= 16) h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); - if (remaining == 24) h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); + Vector128 data = Unsafe.As>(ref Unsafe.Add(ref start, len - 16)); + acc0 = Arm.Aes.MixColumns(Arm.Aes.Encrypt(data, acc0)); } - // Fold lanes down to one 32-bit value. - // Rotates permute bit positions so each lane contributes differently. - // Adds (rather than XOR) deliberately introduce carries - // - CRC is linear over GF(2), and carry breaks that, making simple algebraic - // structure harder to exploit for collision clustering in hash tables. - h2 = BitOperations.RotateLeft(h2, 17) + BitOperations.RotateLeft(h3, 23); - h0 += BitOperations.RotateLeft(h1, 11); - uint hash = h2 + h0; - - // Handle tail bytes (1-7 bytes) that were not part of the 64-bit-aligned stream. - // This is exact, in-order processing - no overlap and no over-read. - int tailBytes = len - aligned; - if (tailBytes != 0) + ulong compressed = acc0.AsUInt64().GetElement(0) ^ acc0.AsUInt64().GetElement(1); + return (int)(uint)(compressed ^ (compressed >> 32)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + [SkipLocalsInit] + internal static int FastHashCrc(ref byte start, int len, uint seed) + { + uint hash; + if (len < 16) { - ref byte tailRef = ref Unsafe.Add(ref start, aligned); - hash = CrcTailOrdered(hash, ref tailRef, tailBytes); + if (len >= 8) + { + ulong lo = Unsafe.ReadUnaligned(ref start); + ulong hi = Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, len - 8)); + uint h0 = BitOperations.Crc32C(seed, lo); + uint h1 = BitOperations.Crc32C(seed ^ 0x9E3779B9u, hi); + hash = h0 + BitOperations.RotateLeft(h1, 11); + } + else + { + hash = CrcTailOrdered(seed, ref start, len); + } + } + else + { + uint h0 = seed; + uint h1 = seed ^ 0x9E3779B9u; + uint h2 = seed ^ 0x85EBCA6Bu; + uint h3 = seed ^ 0xC2B2AE35u; + + ref byte q = ref start; + int aligned = len & ~7; + int remaining = aligned; + + while (remaining >= 64) + { + h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); + h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); + h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); + h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 24))); + + h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 32))); + h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 40))); + h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 48))); + h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 56))); + + q = ref Unsafe.Add(ref q, 64); + remaining -= 64; + } + + if (remaining >= 32) + { + h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); + h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); + h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); + h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 24))); + + q = ref Unsafe.Add(ref q, 32); + remaining -= 32; + } + + if (remaining >= 8) h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); + if (remaining >= 16) h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); + if (remaining >= 24) h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); + + h2 = BitOperations.RotateLeft(h2, 17) + BitOperations.RotateLeft(h3, 23); + h0 += BitOperations.RotateLeft(h1, 11); + hash = h2 + h0; + + int tailBytes = len - aligned; + if (tailBytes != 0) + { + ref byte tailRef = ref Unsafe.Add(ref start, aligned); + hash = CrcTailOrdered(hash, ref tailRef, tailBytes); + } } - // FinalMix breaks some remaining linearity and improves avalanche - return (int)FinalMix(hash); + hash ^= hash >> 16; + hash *= 0x9E3779B1u; + hash ^= hash >> 16; + return (int)hash; [MethodImpl(MethodImplOptions.AggressiveInlining)] static uint CrcTailOrdered(uint hash, ref byte p, int length) { - // length is 1..7 - // Process 4-2-1 bytes in natural order if ((length & 4) != 0) { hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned(ref p)); @@ -383,19 +525,70 @@ static uint CrcTailOrdered(uint hash, ref byte p, int length) } return hash; } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static uint FinalMix(uint x) + /// + /// Computes a very fast, non-cryptographic 64-bit hash of exactly 32 bytes. + /// + /// Reference to the first byte of the 32-byte input. + /// A 64-bit hash value with good distribution across all bits. + /// + /// Uses AES hardware acceleration when available, falls back to CRC32C otherwise. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long FastHash64For32Bytes(ref byte start) + { + uint seed = s_instanceRandom + 32; + + if (x64.Aes.IsSupported || Arm.Aes.IsSupported) + { + Vector128 key = Unsafe.As>(ref start); + Vector128 data = Unsafe.As>(ref Unsafe.Add(ref start, 16)); + key ^= Vector128.CreateScalar(seed).AsByte(); + Vector128 mixed = x64.Aes.IsSupported + ? x64.Aes.Encrypt(data, key) + : Arm.Aes.MixColumns(Arm.Aes.Encrypt(data, key)); + return (long)(mixed.AsUInt64().GetElement(0) ^ mixed.AsUInt64().GetElement(1)); + } + + // Fallback: CRC32C-based 64-bit hash + ulong h0 = BitOperations.Crc32C(seed, Unsafe.ReadUnaligned(ref start)); + ulong h1 = BitOperations.Crc32C(seed ^ 0x9E3779B9u, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 8))); + ulong h2 = BitOperations.Crc32C(seed ^ 0x85EBCA6Bu, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 16))); + ulong h3 = BitOperations.Crc32C(seed ^ 0xC2B2AE35u, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 24))); + return (long)((h0 | (h1 << 32)) ^ (h2 | (h3 << 32))); + } + + /// + /// Computes a very fast, non-cryptographic 64-bit hash of exactly 20 bytes (Address size). + /// + /// Reference to the first byte of the 20-byte input. + /// A 64-bit hash value with good distribution across all bits. + /// + /// Uses AES hardware acceleration when available, falls back to CRC32C otherwise. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long FastHash64For20Bytes(ref byte start) + { + uint seed = s_instanceRandom + 20; + + if (x64.Aes.IsSupported || Arm.Aes.IsSupported) { - // A tiny finalizer to improve avalanche: - // - xor-fold high bits down - // - multiply by an odd constant to spread changes across bits - // - xor-fold again to propagate the multiply result - x ^= x >> 16; - x *= 0x9E3779B1u; - x ^= x >> 16; - return x; + Vector128 key = Unsafe.As>(ref start); + uint last4 = Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 16)); + Vector128 data = Vector128.CreateScalar(last4).AsByte(); + key ^= Vector128.CreateScalar(seed).AsByte(); + Vector128 mixed = x64.Aes.IsSupported + ? x64.Aes.Encrypt(data, key) + : Arm.Aes.MixColumns(Arm.Aes.Encrypt(data, key)); + return (long)(mixed.AsUInt64().GetElement(0) ^ mixed.AsUInt64().GetElement(1)); } + + // Fallback: CRC32C-based 64-bit hash + ulong h0 = BitOperations.Crc32C(seed, Unsafe.ReadUnaligned(ref start)); + ulong h1 = BitOperations.Crc32C(seed ^ 0x9E3779B9u, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 8))); + uint h2 = BitOperations.Crc32C(seed ^ 0x85EBCA6Bu, Unsafe.ReadUnaligned(ref Unsafe.Add(ref start, 16))); + return (long)((h0 | (h1 << 32)) ^ ((ulong)h2 * 0x9E3779B97F4A7C15)); } } } diff --git a/src/Nethermind/Nethermind.Core/IKeyValueStore.cs b/src/Nethermind/Nethermind.Core/IKeyValueStore.cs index d80cfc3d40ae..d30dbcfe87df 100644 --- a/src/Nethermind/Nethermind.Core/IKeyValueStore.cs +++ b/src/Nethermind/Nethermind.Core/IKeyValueStore.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using Nethermind.Core.Buffers; using Nethermind.Core.Extensions; namespace Nethermind.Core @@ -22,10 +24,12 @@ public interface IReadOnlyKeyValueStore byte[]? Get(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None); /// - /// Return span. Must call `DangerousReleaseMemory` or there can be some leak. + /// Return span. Must call after use to avoid memory leaks. + /// Prefer using which handles release automatically via disposal. /// - /// - /// Can return null or empty Span on missing key + /// Key whose associated value should be read. + /// Read behavior flags that control how the value is retrieved. + /// Can return null or empty Span on a missing key Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => Get(key, flags); /// @@ -63,6 +67,19 @@ bool KeyExists(ReadOnlySpan key) } void DangerousReleaseMemory(in ReadOnlySpan span) { } + + /// + /// Returns a MemoryManager wrapping the value for the given key. + /// The MemoryManager must be disposed of when done to release any underlying resources. + /// + /// Key whose associated value should be read. + /// Read behavior flags that control how the value is retrieved. + /// A MemoryManager wrapping the value or null if the key doesn't exist. + MemoryManager? GetOwnedMemory(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + { + byte[]? data = Get(key, flags); + return data is null or { Length: 0 } ? null : new ArrayMemoryManager(data); + } } public interface IReadOnlyNativeKeyValueStore diff --git a/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs b/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs index 4edda62a99ba..1d1c0aa88199 100644 --- a/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs +++ b/src/Nethermind/Nethermind.Core/JsonConverters/ByteArrayConverter.cs @@ -185,16 +185,69 @@ public override void Write( Convert(writer, bytes, skipLeadingZeros: false); } + /// + /// Writes bytes as a hex string value (e.g. "0xabcd") using WriteRawValue. + /// [SkipLocalsInit] public static void Convert(Utf8JsonWriter writer, ReadOnlySpan bytes, bool skipLeadingZeros = true, bool addHexPrefix = true) { - Convert(writer, - bytes, - static (w, h) => w.WriteRawValue(h, skipInputValidation: true), skipLeadingZeros, addHexPrefix: addHexPrefix); + int leadingNibbleZeros = skipLeadingZeros ? bytes.CountLeadingNibbleZeros() : 0; + int nibblesCount = bytes.Length * 2; + + if (skipLeadingZeros && nibblesCount is not 0 && leadingNibbleZeros == nibblesCount) + { + WriteZeroValue(writer); + return; + } + + int prefixLength = addHexPrefix ? 2 : 0; + // +2 for surrounding quotes: "0xABCD..." + int rawLength = nibblesCount - leadingNibbleZeros + prefixLength + 2; + + byte[]? array = null; + Unsafe.SkipInit(out HexBuffer256 buffer); + Span hex = rawLength <= 256 + ? MemoryMarshal.CreateSpan(ref Unsafe.As(ref buffer), 256) + : (array = ArrayPool.Shared.Rent(rawLength)); + hex = hex[..rawLength]; + + // Build the JSON string value directly: "0x" + ref byte hexRef = ref MemoryMarshal.GetReference(hex); + hexRef = (byte)'"'; + int start = 1; + if (addHexPrefix) + { + Unsafe.As(ref Unsafe.Add(ref hexRef, 1)) = HexPrefix; + start = 3; + } + Unsafe.Add(ref hexRef, rawLength - 1) = (byte)'"'; + + int offset = leadingNibbleZeros >>> 1; + MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref MemoryMarshal.GetReference(bytes), offset), bytes.Length - offset) + .OutputBytesToByteHex( + MemoryMarshal.CreateSpan(ref Unsafe.Add(ref hexRef, start), rawLength - 1 - start), + extraNibble: (leadingNibbleZeros & 1) != 0); + // Hex chars (0-9, a-f) never need JSON escaping — bypass encoder entirely + writer.WriteRawValue(hex, skipInputValidation: true); + + if (array is not null) + ArrayPool.Shared.Return(array); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void WriteZeroValue(Utf8JsonWriter writer) => writer.WriteStringValue("0x0"u8); + + [InlineArray(256)] + private struct HexBuffer256 + { + private byte _element0; } public delegate void WriteHex(Utf8JsonWriter writer, ReadOnlySpan hex); + /// + /// Writes bytes as hex using a custom write action (e.g. for property names). + /// [SkipLocalsInit] public static void Convert( Utf8JsonWriter writer, @@ -204,51 +257,57 @@ public static void Convert( bool addQuotations = true, bool addHexPrefix = true) { - const int maxStackLength = 128; - const int stackLength = 256; - - var leadingNibbleZeros = skipLeadingZeros ? bytes.CountLeadingNibbleZeros() : 0; - var nibblesCount = bytes.Length * 2; + int leadingNibbleZeros = skipLeadingZeros ? bytes.CountLeadingNibbleZeros() : 0; + int nibblesCount = bytes.Length * 2; if (skipLeadingZeros && nibblesCount is not 0 && leadingNibbleZeros == nibblesCount) { - writer.WriteStringValue(Bytes.ZeroHexValue); + WriteZeroValue(writer, writeAction, addQuotations); return; } - var prefixLength = addHexPrefix ? 2 : 0; - var length = nibblesCount - leadingNibbleZeros + prefixLength + (addQuotations ? 2 : 0); + int prefixLength = addHexPrefix ? 2 : 0; + int quotesLength = addQuotations ? 2 : 0; + int length = nibblesCount - leadingNibbleZeros + prefixLength + quotesLength; byte[]? array = null; - if (length > maxStackLength) - array = ArrayPool.Shared.Rent(length); + Unsafe.SkipInit(out HexBuffer256 buffer); + Span hex = length <= 256 + ? MemoryMarshal.CreateSpan(ref Unsafe.As(ref buffer), 256) + : (array = ArrayPool.Shared.Rent(length)); + hex = hex[..length]; - Span hex = (array ?? stackalloc byte[stackLength])[..length]; - var start = 0; - Index end = ^0; + ref byte hexRef = ref MemoryMarshal.GetReference(hex); + int start = 0; + int endPad = 0; if (addQuotations) { - end = ^1; - hex[^1] = (byte)'"'; - hex[start++] = (byte)'"'; + hexRef = (byte)'"'; + Unsafe.Add(ref hexRef, length - 1) = (byte)'"'; + start = 1; + endPad = 1; } if (addHexPrefix) { - hex[start++] = (byte)'0'; - hex[start++] = (byte)'x'; + Unsafe.As(ref Unsafe.Add(ref hexRef, start)) = HexPrefix; + start += 2; } - Span output = hex[start..end]; - - ReadOnlySpan input = bytes[(leadingNibbleZeros / 2)..]; - input.OutputBytesToByteHex(output, extraNibble: (leadingNibbleZeros & 1) != 0); + ReadOnlySpan input = bytes[(leadingNibbleZeros >>> 1)..]; + input.OutputBytesToByteHex( + MemoryMarshal.CreateSpan(ref Unsafe.Add(ref hexRef, start), length - start - endPad), + extraNibble: (leadingNibbleZeros & 1) != 0); writeAction(writer, hex); if (array is not null) ArrayPool.Shared.Return(array); } + [MethodImpl(MethodImplOptions.NoInlining)] + private static void WriteZeroValue(Utf8JsonWriter writer, WriteHex writeAction, bool addQuotations) + => writeAction(writer, addQuotations ? "\"0x0\""u8 : "0x0"u8); + public override byte[] ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { byte[]? result = Convert(ref reader); diff --git a/src/Nethermind/Nethermind.Core/KeyValueStoreExtensions.cs b/src/Nethermind/Nethermind.Core/KeyValueStoreExtensions.cs index 906b39acf45d..0220b81ae103 100644 --- a/src/Nethermind/Nethermind.Core/KeyValueStoreExtensions.cs +++ b/src/Nethermind/Nethermind.Core/KeyValueStoreExtensions.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Buffers; using System.Runtime.CompilerServices; using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; @@ -12,114 +11,108 @@ namespace Nethermind.Core { public static class KeyValueStoreExtensions { - public static IWriteBatch LikeABatch(this IWriteOnlyKeyValueStore keyValueStore) - { - return LikeABatch(keyValueStore, null); - } - - public static IWriteBatch LikeABatch(this IWriteOnlyKeyValueStore keyValueStore, Action? onDispose) - { - return new FakeWriteBatch(keyValueStore, onDispose); - } - - #region Getters - - public static byte[]? Get(this IReadOnlyKeyValueStore db, Hash256 key) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void GuardKey(Hash256 key) { #if DEBUG - if (key == Keccak.OfAnEmptyString) - { - throw new InvalidOperationException(); - } + if (key == Keccak.OfAnEmptyString) throw new InvalidOperationException(); #endif - - return db[key.Bytes]; } - /// - /// - /// /// - /// - /// Can return null or empty Span on missing key - /// - public static Span GetSpan(this IReadOnlyKeyValueStore db, Hash256 key) + extension(IReadOnlyKeyValueStore db) { -#if DEBUG - if (key == Keccak.OfAnEmptyString) + public byte[]? Get(Hash256 key) { - throw new InvalidOperationException(); + GuardKey(key); + return db[key.Bytes]; } -#endif - return db.GetSpan(key.Bytes); - } + /// + /// + /// + /// + /// Can return null or empty Span on missing key + /// + public Span GetSpan(Hash256 key) + { + GuardKey(key); + return db.GetSpan(key.Bytes); + } - public static bool KeyExists(this IReadOnlyKeyValueStore db, Hash256 key) - { -#if DEBUG - if (key == Keccak.OfAnEmptyString) + public bool KeyExists(Hash256 key) { - throw new InvalidOperationException(); + GuardKey(key); + return db.KeyExists(key.Bytes); } -#endif - return db.KeyExists(key.Bytes); - } + public bool KeyExists(long key) => db.KeyExists(key.ToBigEndianSpanWithoutLeadingZeros(out _)); - public static bool KeyExists(this IReadOnlyKeyValueStore db, long key) - { - return db.KeyExists(key.ToBigEndianSpanWithoutLeadingZeros(out _)); + public byte[]? Get(long key) => db[key.ToBigEndianSpanWithoutLeadingZeros(out _)]; } - public static byte[]? Get(this IReadOnlyKeyValueStore db, long key) => db[key.ToBigEndianSpanWithoutLeadingZeros(out _)]; - - /// - /// - /// - /// - /// - /// Can return null or empty Span on missing key - public static Span GetSpan(this IReadOnlyKeyValueStore db, long key) => db.GetSpan(key.ToBigEndianSpanWithoutLeadingZeros(out _)); - - public static MemoryManager? GetOwnedMemory(this IReadOnlyKeyValueStore db, ReadOnlySpan key) + extension(IWriteOnlyKeyValueStore db) { - Span span = db.GetSpan(key); - return span.IsNullOrEmpty() ? null : new DbSpanMemoryManager(db, span); - } + public IWriteBatch LikeABatch(Action? onDispose = null) => new FakeWriteBatch(db, onDispose); + public void Set(Hash256 key, byte[] value, WriteFlags writeFlags = WriteFlags.None) + { + if (db.PreferWriteByArray) + { + db.Set(key.Bytes, value, writeFlags); + } + else + { + db.PutSpan(key.Bytes, value, writeFlags); + } + } - #endregion + public void Set(Hash256 key, in CappedArray value, WriteFlags writeFlags = WriteFlags.None) + { + if (db.PreferWriteByArray && value.IsUncapped) + { + db.Set(key.Bytes, value.UnderlyingArray, writeFlags); + } + else + { + db.PutSpan(key.Bytes, value.AsSpan(), writeFlags); + } + } + public void Set(long blockNumber, Hash256 key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) + { + Span blockNumberPrefixedKey = stackalloc byte[40]; + GetBlockNumPrefixedKey(blockNumber, key, blockNumberPrefixedKey); + db.PutSpan(blockNumberPrefixedKey, value, writeFlags); + } - #region Setters + public void Set(in ValueHash256 key, Span value) + { + db.PutSpan(key.Bytes, value); + } - public static void Set(this IWriteOnlyKeyValueStore db, Hash256 key, byte[] value, WriteFlags writeFlags = WriteFlags.None) - { - if (db.PreferWriteByArray) + public void Delete(Hash256 key) { - db.Set(key.Bytes, value, writeFlags); - return; + db.Remove(key.Bytes); } - db.PutSpan(key.Bytes, value, writeFlags); - } - public static void Set(this IWriteOnlyKeyValueStore db, Hash256 key, in CappedArray value, WriteFlags writeFlags = WriteFlags.None) - { - if (value.IsUncapped && db.PreferWriteByArray) + public void Delete(long key) { - db.PutSpan(key.Bytes, value.AsSpan(), writeFlags); - return; + db.Remove(key.ToBigEndianSpanWithoutLeadingZeros(out _)); } - db.PutSpan(key.Bytes, value.AsSpan(), writeFlags); - } + [SkipLocalsInit] + public void Delete(long blockNumber, Hash256 hash) + { + Span key = stackalloc byte[40]; + GetBlockNumPrefixedKey(blockNumber, hash, key); + db.Remove(key); + } - public static void Set(this IWriteOnlyKeyValueStore db, long blockNumber, Hash256 key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) - { - Span blockNumberPrefixedKey = stackalloc byte[40]; - GetBlockNumPrefixedKey(blockNumber, key, blockNumberPrefixedKey); - db.PutSpan(blockNumberPrefixedKey, value, writeFlags); + public void Set(long key, byte[] value) + { + db[key.ToBigEndianSpanWithoutLeadingZeros(out _)] = value; + } } public static void GetBlockNumPrefixedKey(long blockNumber, ValueHash256 blockHash, Span output) @@ -127,35 +120,5 @@ public static void GetBlockNumPrefixedKey(long blockNumber, ValueHash256 blockHa blockNumber.WriteBigEndian(output); blockHash!.Bytes.CopyTo(output[8..]); } - - public static void Set(this IWriteOnlyKeyValueStore db, in ValueHash256 key, Span value) - { - db.PutSpan(key.Bytes, value); - } - - public static void Delete(this IWriteOnlyKeyValueStore db, Hash256 key) - { - db.Remove(key.Bytes); - } - - public static void Delete(this IWriteOnlyKeyValueStore db, long key) - { - db.Remove(key.ToBigEndianSpanWithoutLeadingZeros(out _)); - } - - [SkipLocalsInit] - public static void Delete(this IWriteOnlyKeyValueStore db, long blockNumber, Hash256 hash) - { - Span key = stackalloc byte[40]; - GetBlockNumPrefixedKey(blockNumber, hash, key); - db.Remove(key); - } - - public static void Set(this IWriteOnlyKeyValueStore db, long key, byte[] value) - { - db[key.ToBigEndianSpanWithoutLeadingZeros(out _)] = value; - } - - #endregion } } diff --git a/src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs b/src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs deleted file mode 100644 index a59bf7aadbe2..000000000000 --- a/src/Nethermind/Nethermind.Core/PubSub/CompositePublisher.cs +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading.Tasks; -using Nethermind.Core.Collections; - -namespace Nethermind.Core.PubSub -{ - public class CompositePublisher(params IPublisher[] publishers) : IPublisher - { - public async Task PublishAsync(T data) where T : class - { - using ArrayPoolList tasks = new(publishers.Length); - for (int i = 0; i < publishers.Length; i++) - { - tasks.Add(publishers[i].PublishAsync(data)); - } - - await Task.WhenAll(tasks.AsSpan()); - } - - public void Dispose() - { - foreach (IPublisher publisher in publishers) - { - publisher.Dispose(); - } - } - } -} diff --git a/src/Nethermind/Nethermind.Core/StorageCell.cs b/src/Nethermind/Nethermind.Core/StorageCell.cs index a35bc2eda97c..d28c5648f364 100644 --- a/src/Nethermind/Nethermind.Core/StorageCell.cs +++ b/src/Nethermind/Nethermind.Core/StorageCell.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -13,12 +14,13 @@ namespace Nethermind.Core { [DebuggerDisplay("{Address}->{Index}")] - public readonly struct StorageCell : IEquatable + public readonly struct StorageCell : IEquatable, IHash64bit { + private readonly AddressAsKey _address; private readonly UInt256 _index; private readonly bool _isHash; - public Address Address { get; } + public Address Address => _address.Value; public bool IsHash => _isHash; public UInt256 Index => _index; @@ -33,21 +35,45 @@ private ValueHash256 GetHash() public StorageCell(Address address, in UInt256 index) { - Address = address; + _address = address; _index = index; } public StorageCell(Address address, ValueHash256 hash) { - Address = address; + _address = address; _index = Unsafe.As(ref hash); _isHash = true; } - public bool Equals(StorageCell other) => - _isHash == other._isHash && - Unsafe.As>(ref Unsafe.AsRef(in _index)) == Unsafe.As>(ref Unsafe.AsRef(in other._index)) && - Address.Equals(other.Address); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(in StorageCell other) + { + if (_isHash != other._isHash) + return false; + + if (Unsafe.As>(ref Unsafe.AsRef(in _index)) != + Unsafe.As>(ref Unsafe.AsRef(in other._index))) + return false; + + // Inline 20-byte Address comparison: avoids the Address.Equals call + // that the JIT refuses to inline when called from deep inline chains + // (e.g. SeqlockCache.TryGetValue). Address.Bytes is always exactly 20 bytes. + Address a = _address.Value; + Address b = other._address.Value; + if (ReferenceEquals(a, b)) + return true; + + ref byte ab = ref MemoryMarshal.GetArrayDataReference(a.Bytes); + ref byte bb = ref MemoryMarshal.GetArrayDataReference(b.Bytes); + return Unsafe.As>(ref ab) == Unsafe.As>(ref bb) + && Unsafe.As(ref Unsafe.Add(ref ab, 16)) == Unsafe.As(ref Unsafe.Add(ref bb, 16)); + } + + public bool Equals(StorageCell other) => Equals(in other); + + public long GetHashCode64() + => SpanExtensions.FastHash64For32Bytes(ref Unsafe.As(ref Unsafe.AsRef(in _index))) ^ _address.Value.GetHashCode64(); public override bool Equals(object? obj) { @@ -62,12 +88,12 @@ public override bool Equals(object? obj) public override int GetHashCode() { int hash = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in _index), 1)).FastHash(); - return hash ^ Address.GetHashCode(); + return hash ^ _address.Value.GetHashCode(); } public override string ToString() { - return $"{Address}.{Index}"; + return $"{_address.Value}.{Index}"; } } } diff --git a/src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs b/src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs deleted file mode 100644 index a6c80ed33fc7..000000000000 --- a/src/Nethermind/Nethermind.Core/Utils/ConcurrentWriteBatcher.cs +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Concurrent; -using System.Threading; - -namespace Nethermind.Core.Utils; - -/// -/// Batches writes into a set of concurrent batches. For cases where throughput matter, but not atomicity. -/// -public class ConcurrentWriteBatcher : IWriteBatch -{ - private const long PersistEveryNWrite = 10000; - - private long _counter = 0; - private readonly ConcurrentQueue _batches = new(); - private readonly IKeyValueStoreWithBatching _underlyingDb; - private bool _disposing = false; - - public ConcurrentWriteBatcher(IKeyValueStoreWithBatching underlyingDb) - { - _underlyingDb = underlyingDb; - } - - public void Dispose() - { - _disposing = true; - foreach (IWriteBatch batch in _batches) - { - batch.Dispose(); - } - } - - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - IWriteBatch currentBatch = RentWriteBatch(); - currentBatch.PutSpan(key, value, flags); - ReturnWriteBatch(currentBatch); - } - - public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - IWriteBatch currentBatch = RentWriteBatch(); - currentBatch.Merge(key, value, flags); - ReturnWriteBatch(currentBatch); - } - - public void Clear() - { - throw new NotSupportedException($"{nameof(ConcurrentWriteBatcher)} can not be cancelled."); - } - - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - { - IWriteBatch currentBatch = RentWriteBatch(); - currentBatch.Set(key, value, flags); - ReturnWriteBatch(currentBatch); - } - - private void ReturnWriteBatch(IWriteBatch currentBatch) - { - long val = Interlocked.Increment(ref _counter); - if (val % PersistEveryNWrite == 0) - { - currentBatch.Dispose(); - } - else - { - _batches.Enqueue(currentBatch); - } - } - - private IWriteBatch RentWriteBatch() - { - if (_disposing) throw new InvalidOperationException("Trying to set while disposing"); - if (!_batches.TryDequeue(out IWriteBatch? currentBatch)) - { - currentBatch = _underlyingDb.StartWriteBatch(); - } - - return currentBatch; - } -} diff --git a/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs b/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs index af75f303e417..94633dfd94f6 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs @@ -2,9 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using Nethermind.Core; +using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using RocksDbSharp; using IWriteBatch = Nethermind.Core.IWriteBatch; @@ -31,52 +34,34 @@ public ColumnDb(RocksDb rocksDb, DbOnTheRocks mainDb, string name) _reader = new RocksDbReader(mainDb, mainDb.CreateReadOptions, _iteratorManager, _columnFamily); } - public void Dispose() - { - _iteratorManager.Dispose(); - } - + public void Dispose() => _iteratorManager.Dispose(); public string Name { get; } - byte[]? IReadOnlyKeyValueStore.Get(ReadOnlySpan key, ReadFlags flags) - { - return _reader.Get(key, flags); - } + byte[]? IReadOnlyKeyValueStore.Get(ReadOnlySpan key, ReadFlags flags) => _reader.Get(key, flags); - Span IReadOnlyKeyValueStore.GetSpan(scoped ReadOnlySpan key, ReadFlags flags) - { - return _reader.GetSpan(key, flags); - } + Span IReadOnlyKeyValueStore.GetSpan(scoped ReadOnlySpan key, ReadFlags flags) => _reader.GetSpan(key, flags); - int IReadOnlyKeyValueStore.Get(scoped ReadOnlySpan key, Span output, ReadFlags flags) + MemoryManager? IReadOnlyKeyValueStore.GetOwnedMemory(ReadOnlySpan key, ReadFlags flags) { - return _reader.Get(key, output, flags); + Span span = ((IReadOnlyKeyValueStore)this).GetSpan(key, flags); + return span.IsNullOrEmpty() ? null : new DbSpanMemoryManager(this, span); } - bool IReadOnlyKeyValueStore.KeyExists(ReadOnlySpan key) - { - return _reader.KeyExists(key); - } - void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan key) - { - _reader.DangerousReleaseMemory(key); - } + int IReadOnlyKeyValueStore.Get(scoped ReadOnlySpan key, Span output, ReadFlags flags) => _reader.Get(key, output, flags); - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - { + bool IReadOnlyKeyValueStore.KeyExists(ReadOnlySpan key) => _reader.KeyExists(key); + + void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan key) => _reader.DangerousReleaseMemory(key); + + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) => _mainDb.SetWithColumnFamily(key, _columnFamily, value, flags); - } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) - { + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) => _mainDb.SetWithColumnFamily(key, _columnFamily, value, writeFlags); - } - public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) - { + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) => _mainDb.MergeWithColumnFamily(key, _columnFamily, value, writeFlags); - } public KeyValuePair[] this[byte[][] keys] { @@ -106,77 +91,48 @@ public IEnumerable GetAllValues(bool ordered = false) return _mainDb.GetAllValuesCore(iterator); } - public IWriteBatch StartWriteBatch() - { - return new ColumnsDbWriteBatch(this, (DbOnTheRocks.RocksDbWriteBatch)_mainDb.StartWriteBatch()); - } + public IWriteBatch StartWriteBatch() => new ColumnsDbWriteBatch(this, (DbOnTheRocks.RocksDbWriteBatch)_mainDb.StartWriteBatch()); - private class ColumnsDbWriteBatch : IWriteBatch + private class ColumnsDbWriteBatch(ColumnDb columnDb, DbOnTheRocks.RocksDbWriteBatch underlyingWriteBatch) + : IWriteBatch { - private readonly ColumnDb _columnDb; - private readonly DbOnTheRocks.RocksDbWriteBatch _underlyingWriteBatch; + public void Dispose() => underlyingWriteBatch.Dispose(); - public ColumnsDbWriteBatch(ColumnDb columnDb, DbOnTheRocks.RocksDbWriteBatch underlyingWriteBatch) - { - _columnDb = columnDb; - _underlyingWriteBatch = underlyingWriteBatch; - } - - public void Dispose() - { - _underlyingWriteBatch.Dispose(); - } - - public void Clear() - { - _underlyingWriteBatch.Clear(); - } + public void Clear() => underlyingWriteBatch.Clear(); public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { if (value is null) { - _underlyingWriteBatch.Delete(key, _columnDb._columnFamily); + underlyingWriteBatch.Delete(key, columnDb._columnFamily); } else { - _underlyingWriteBatch.Set(key, value, _columnDb._columnFamily, flags); + underlyingWriteBatch.Set(key, value, columnDb._columnFamily, flags); } } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - _underlyingWriteBatch.Set(key, value, _columnDb._columnFamily, flags); - } + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => + underlyingWriteBatch.Set(key, value, columnDb._columnFamily, flags); - public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - _underlyingWriteBatch.Merge(key, value, _columnDb._columnFamily, flags); - } + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => + underlyingWriteBatch.Merge(key, value, columnDb._columnFamily, flags); } - public void Remove(ReadOnlySpan key) - { - Set(key, null); - } + public void Remove(ReadOnlySpan key) => Set(key, null); - public void Flush(bool onlyWal) - { - _mainDb.FlushWithColumnFamily(_columnFamily); - } + public void Flush(bool onlyWal) => _mainDb.FlushWithColumnFamily(_columnFamily); - public void Compact() - { + public void Compact() => _rocksDb.CompactRange(Keccak.Zero.BytesToArray(), Keccak.MaxValue.BytesToArray(), _columnFamily); - } /// /// Not sure how to handle delete of the columns DB /// /// - public void Clear() { throw new NotSupportedException(); } + public void Clear() => throw new NotSupportedException(); - // Maybe it should be column specific metric? + // Maybe it should be column-specific metric? public IDbMeta.DbMetric GatherMetric() => _mainDb.GatherMetric(); public byte[]? FirstKey @@ -199,10 +155,8 @@ public byte[]? LastKey } } - public ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey) - { - return _mainDb.GetViewBetween(firstKey, lastKey, _columnFamily); - } + public ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey) => + _mainDb.GetViewBetween(firstKey, lastKey, _columnFamily); public IKeyValueStoreSnapshot CreateSnapshot() { diff --git a/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs b/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs index b631d969a28b..5b22cc4f0580 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs @@ -105,24 +105,19 @@ protected override void ApplyOptions(IDictionary options) private class RocksColumnsWriteBatch : IColumnsWriteBatch { - internal RocksDbWriteBatch _writeBatch; + internal readonly RocksDbWriteBatch WriteBatch; private readonly ColumnsDb _columnsDb; public RocksColumnsWriteBatch(ColumnsDb columnsDb) { - _writeBatch = new RocksDbWriteBatch(columnsDb); + WriteBatch = new RocksDbWriteBatch(columnsDb); _columnsDb = columnsDb; } - public IWriteBatch GetColumnBatch(T key) - { - return new RocksColumnWriteBatch(_columnsDb._columnDbs[key], this); - } + public IWriteBatch GetColumnBatch(T key) => new RocksColumnWriteBatch(_columnsDb._columnDbs[key], this); - public void Dispose() - { - _writeBatch.Dispose(); - } + public void Clear() => WriteBatch.Clear(); + public void Dispose() => WriteBatch.Dispose(); } private class RocksColumnWriteBatch : IWriteBatch @@ -143,17 +138,17 @@ public void Dispose() public void Clear() { - _writeBatch._writeBatch.Clear(); + _writeBatch.WriteBatch.Clear(); } public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { - _writeBatch._writeBatch.Set(key, value, _column._columnFamily, flags); + _writeBatch.WriteBatch.Set(key, value, _column._columnFamily, flags); } public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) { - _writeBatch._writeBatch.Merge(key, value, _column._columnFamily, flags); + _writeBatch.WriteBatch.Merge(key, value, _column._columnFamily, flags); } } diff --git a/src/Nethermind/Nethermind.Db.Rocks/Config/DbConfig.cs b/src/Nethermind/Nethermind.Db.Rocks/Config/DbConfig.cs index c6004bcfc716..f97bccfd637b 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/Config/DbConfig.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/Config/DbConfig.cs @@ -271,4 +271,19 @@ public class DbConfig : IDbConfig public string L1OriginDbRocksDbOptions { get; set; } = ""; public string? L1OriginDbAdditionalRocksDbOptions { get; set; } + + public string LogIndexStorageDbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageDbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageMetaDbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageMetaDbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageAddressesDbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageAddressesDbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics0DbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics0DbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics1DbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics1DbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics2DbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics2DbAdditionalRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics3DbRocksDbOptions { get; set; } = ""; + public string LogIndexStorageTopics3DbAdditionalRocksDbOptions { get; set; } = ""; } diff --git a/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs b/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs index fa7c8e2023b6..5c8b1211a410 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs @@ -102,4 +102,19 @@ public interface IDbConfig : IConfig string L1OriginDbRocksDbOptions { get; set; } string? L1OriginDbAdditionalRocksDbOptions { get; set; } + + string LogIndexStorageDbRocksDbOptions { get; set; } + string LogIndexStorageDbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageMetaDbRocksDbOptions { get; set; } + string LogIndexStorageMetaDbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageAddressesDbRocksDbOptions { get; set; } + string LogIndexStorageAddressesDbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageTopics0DbRocksDbOptions { get; set; } + string LogIndexStorageTopics0DbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageTopics1DbRocksDbOptions { get; set; } + string LogIndexStorageTopics1DbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageTopics2DbRocksDbOptions { get; set; } + string LogIndexStorageTopics2DbAdditionalRocksDbOptions { get; set; } + string LogIndexStorageTopics3DbRocksDbOptions { get; set; } + string LogIndexStorageTopics3DbAdditionalRocksDbOptions { get; set; } } diff --git a/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs b/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs index b5488c733b3a..cca010c66f8c 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Buffers.Binary; using System.Collections.Concurrent; using System.Collections.Generic; @@ -18,6 +19,7 @@ using ConcurrentCollections; using Nethermind.Config; using Nethermind.Core; +using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; using Nethermind.Core.Exceptions; using Nethermind.Core.Extensions; @@ -103,7 +105,7 @@ public DbOnTheRocks( IRocksDbConfigFactory rocksDbConfigFactory, ILogManager logManager, IList? columnFamilies = null, - RocksDbSharp.Native? rocksDbNative = null, + Native? rocksDbNative = null, IFileSystem? fileSystem = null, IntPtr? sharedCache = null) { @@ -111,7 +113,7 @@ public DbOnTheRocks( _settings = dbSettings; Name = _settings.DbName; _fileSystem = fileSystem ?? new FileSystem(); - _rocksDbNative = rocksDbNative ?? RocksDbSharp.Native.Instance; + _rocksDbNative = rocksDbNative ?? Native.Instance; _rocksDbConfigFactory = rocksDbConfigFactory; _perTableDbConfig = rocksDbConfigFactory.GetForDatabase(Name, null); _db = Init(basePath, dbSettings.DbPath, dbConfig, logManager, columnFamilies, dbSettings.DeleteOnStart, sharedCache); @@ -129,7 +131,6 @@ protected virtual RocksDb DoOpen(string path, (DbOptions Options, ColumnFamilies private RocksDb Open(string path, (DbOptions Options, ColumnFamilies? Families) db) { RepairIfCorrupted(db.Options); - return DoOpen(path, db); } @@ -179,7 +180,7 @@ private RocksDb Init(string basePath, string dbPath, IDbConfig dbConfig, ILogMan if (dbConfig.EnableMetricsUpdater) { - DbMetricsUpdater metricUpdater = new DbMetricsUpdater(Name, DbOptions, db, null, dbConfig, _logger); + DbMetricsUpdater metricUpdater = new(Name, DbOptions, db, null, dbConfig, _logger); metricUpdater.StartUpdating(); _metricsUpdaters.Add(metricUpdater); @@ -190,7 +191,7 @@ private RocksDb Init(string basePath, string dbPath, IDbConfig dbConfig, ILogMan if (columnFamily.Name == "default") continue; if (db.TryGetColumnFamily(columnFamily.Name, out ColumnFamilyHandle handle)) { - DbMetricsUpdater columnMetricUpdater = new DbMetricsUpdater( + DbMetricsUpdater columnMetricUpdater = new( Name + "_" + columnFamily.Name, columnFamily.Options, db, handle, dbConfig, _logger); columnMetricUpdater.StartUpdating(); _metricsUpdaters.Add(columnMetricUpdater); @@ -264,8 +265,8 @@ private void WarmupFile(string basePath, RocksDb db) } return take; }) - // We reverse them again so that lower level goes last so that it is the freshest. - // Not all of the available memory is actually available so we are probably over reading things. + // We reverse them again so that the lower level goes last so that it is the freshest. + // Not all the available memory is actually available, so we are probably over reading things. .Reverse() .ToList(); @@ -340,15 +341,11 @@ protected internal void UpdateWriteMetrics() Interlocked.Increment(ref _totalWrites); } - protected virtual long FetchTotalPropertyValue(string propertyName) - { - long value = long.TryParse(_db.GetProperty(propertyName), out long parsedValue) + protected virtual long FetchTotalPropertyValue(string propertyName) => + long.TryParse(_db.GetProperty(propertyName), out long parsedValue) ? parsedValue : 0; - return value; - } - public IDbMeta.DbMetric GatherMetric() { if (_isDisposed) @@ -491,8 +488,8 @@ public static string NormalizeRocksDbOptions(string dbOptions) protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options options, IntPtr? sharedCache, IMergeOperator? mergeOperator) where T : Options { - // This section is about the table factory.. and block cache apparently. - // This effect the format of the SST files and usually require resync to take effect. + // This section is about the table factory and block cache, apparently. + // This affects the format of the SST files and usually requires resyncing to take effect. // Note: Keep in mind, the term 'index' here usually means mapping to a block, not to a value. #region TableFactory sections @@ -559,21 +556,18 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio int writeBufferNumber = _maxWriteBufferNumber; _maxThisDbSize += (long)writeBufferSize * writeBufferNumber; Interlocked.Add(ref _maxRocksSize, _maxThisDbSize); - if (_logger.IsDebug) - _logger.Debug( - $"Expected max memory footprint of {Name} DB is {_maxThisDbSize / 1000 / 1000} MB ({writeBufferNumber} * {writeBufferSize / 1000 / 1000} MB + {blockCacheSize / 1000 / 1000} MB)"); + if (_logger.IsDebug) _logger.Debug($"Expected max memory footprint of {Name} DB is {_maxThisDbSize / 1000 / 1000} MB ({writeBufferNumber} * {writeBufferSize / 1000 / 1000} MB + {blockCacheSize / 1000 / 1000} MB)"); if (_logger.IsDebug) _logger.Debug($"Total max DB footprint so far is {_maxRocksSize / 1000 / 1000} MB"); } #endregion - // This section affect compactions, flushes and the LSM shape. + // This section affects compactions, flushes and the LSM shape. #region Compaction /* - * Multi-Threaded Compactions - * Compactions are needed to remove multiple copies of the same key that may occur if an application overwrites an existing key. Compactions also process deletions of keys. Compactions may occur in multiple threads if configured appropriately. + * Multi-Threaded Compactions are needed to remove multiple copies of the same key that may occur if an application overwrites an existing key. Compactions also process deletions of keys. Compactions may occur in multiple threads if configured appropriately. * The entire database is stored in a set of sstfiles. When a memtable is full, its content is written out to a file in Level-0 (L0). RocksDB removes duplicate and overwritten keys in the memtable when it is flushed to a file in L0. Some files are periodically read in and merged to form larger files - this is called compaction. - * The overall write throughput of an LSM database directly depends on the speed at which compactions can occur, especially when the data is stored in fast storage like SSD or RAM. RocksDB may be configured to issue concurrent compaction requests from multiple threads. It is observed that sustained write rates may increase by as much as a factor of 10 with multi-threaded compaction when the database is on SSDs, as compared to single-threaded compactions. + * The overall writing throughput of an LSM database directly depends on the speed at which compactions can occur, especially when the data is stored in fast storage like SSD or RAM. RocksDB may be configured to issue concurrent compaction requests from multiple threads. It is observed that sustained write rates may increase by as much as a factor of 10 with multi-threaded compaction when the database is on SSDs, as compared to single-threaded compactions. * TKS: Observed 500MB/s compared to ~100MB/s between multithreaded and single thread compactions on my machine (processor count is returning 12 for 6 cores with hyperthreading) * TKS: CPU goes to insane 30% usage on idle - compacting only app */ @@ -592,11 +586,11 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio if (dbConfig.RowCacheSize > 0) { - // Row cache is basically a per-key cache. Nothing special to it. This is different from block cache - // which cache the whole block at once, so read still need to traverse the block index, so this could be + // Row cache is basically a per-key cache. Nothing special about it. This is different from a block cache + // that caches the whole block at once, so read still needs to traverse the block index, so this could be // more CPU efficient. - // Note: Memtable also act like a per-key cache, that does not get updated on read. So in some case - // maybe it make more sense to put more memory to memtable. + // Note: Memtable also acts like a per-key cache that does not get updated on read. So in some case + // maybe it makes more sense to put more memory to memtable. _rowCache = _rocksDbNative.rocksdb_cache_create_lru(new UIntPtr(dbConfig.RowCacheSize.Value)); _rocksDbNative.rocksdb_options_set_row_cache(options.Handle, _rowCache.Value); } @@ -655,7 +649,7 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio _hintCacheMissOptions = CreateReadOptions(); _hintCacheMissOptions.SetFillCache(false); - // When readahead flag is on, the next keys are expected to be after the current key. Increasing this value, + // When a readahead flag is on, the next keys are expected to be after the current key. Increasing this value // will increase the chances that the next keys will be in the cache, which reduces iops and latency. This // increases throughput, however, if a lot of the keys are not close to the current key, it will increase read // bandwidth requirement, since each read must be at least this size. This value is tuned for a batched trie @@ -672,7 +666,7 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio private static WriteOptions CreateWriteOptions(IRocksDbConfig dbConfig) { WriteOptions options = new(); - // potential fix for corruption on hard process termination, may cause performance degradation + // a potential fix for corruption on hard process termination may cause performance degradation options.SetSync(dbConfig.WriteAheadLogSync); return options; } @@ -684,32 +678,23 @@ internal ReadOptions CreateReadOptions() return readOptions; } - byte[]? IReadOnlyKeyValueStore.Get(ReadOnlySpan key, ReadFlags flags) - { - return _reader.Get(key, flags); - } + byte[]? IReadOnlyKeyValueStore.Get(ReadOnlySpan key, ReadFlags flags) => _reader.Get(key, flags); - Span IReadOnlyKeyValueStore.GetSpan(scoped ReadOnlySpan key, ReadFlags flags) - { - return _reader.GetSpan(key, flags); - } + Span IReadOnlyKeyValueStore.GetSpan(scoped ReadOnlySpan key, ReadFlags flags) => _reader.GetSpan(key, flags); - int IReadOnlyKeyValueStore.Get(scoped ReadOnlySpan key, Span output, ReadFlags flags) + MemoryManager? IReadOnlyKeyValueStore.GetOwnedMemory(ReadOnlySpan key, ReadFlags flags) { - return _reader.Get(key, output, flags); + Span span = ((IReadOnlyKeyValueStore)this).GetSpan(key, flags); + return span.IsNullOrEmpty() ? null : new DbSpanMemoryManager(this, span); } - bool IReadOnlyKeyValueStore.KeyExists(ReadOnlySpan key) - { - return _reader.KeyExists(key); - } + int IReadOnlyKeyValueStore.Get(scoped ReadOnlySpan key, Span output, ReadFlags flags) => _reader.Get(key, output, flags); - void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan span) - { - _reader.DangerousReleaseMemory(span); - } + bool IReadOnlyKeyValueStore.KeyExists(ReadOnlySpan key) => _reader.KeyExists(key); + + void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan span) => _reader.DangerousReleaseMemory(span); - internal unsafe byte[]? GetWithIterator(ReadOnlySpan key, ColumnFamilyHandle? cf, IteratorManager iteratorManager, ReadFlags flags, out bool success) + internal byte[]? GetWithIterator(ReadOnlySpan key, ColumnFamilyHandle? cf, IteratorManager iteratorManager, ReadFlags flags, out bool success) { success = true; @@ -770,10 +755,7 @@ void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan span) } [DoesNotReturn, StackTraceHidden] - static unsafe void ThrowRocksDbException(nint errPtr) - { - throw new RocksDbException(errPtr); - } + static void ThrowRocksDbException(nint errPtr) => throw new RocksDbException(errPtr); } /// @@ -783,6 +765,7 @@ static unsafe void ThrowRocksDbException(nint errPtr) /// /// /// + /// /// private bool TryCloseReadAhead(Iterator iterator, ReadOnlySpan key, out byte[]? result) { @@ -842,10 +825,8 @@ private bool TryCloseReadAhead(Iterator iterator, ReadOnlySpan key, out by return false; } - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - { + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) => SetWithColumnFamily(key, null, value, flags); - } internal void SetWithColumnFamily(ReadOnlySpan key, ColumnFamilyHandle? cf, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) { @@ -871,25 +852,13 @@ internal void SetWithColumnFamily(ReadOnlySpan key, ColumnFamilyHandle? cf } } - public WriteOptions? WriteFlagsToWriteOptions(WriteFlags flags) + public WriteOptions? WriteFlagsToWriteOptions(WriteFlags flags) => flags switch { - if ((flags & WriteFlags.LowPriorityAndNoWAL) == WriteFlags.LowPriorityAndNoWAL) - { - return _lowPriorityAndNoWalWrite; - } - - if ((flags & WriteFlags.DisableWAL) == WriteFlags.DisableWAL) - { - return _noWalWrite; - } - - if ((flags & WriteFlags.LowPriority) == WriteFlags.LowPriority) - { - return _lowPriorityWriteOptions; - } - - return WriteOptions; - } + _ when (flags & WriteFlags.LowPriorityAndNoWAL) == WriteFlags.LowPriorityAndNoWAL => _lowPriorityAndNoWalWrite, + _ when (flags & WriteFlags.DisableWAL) == WriteFlags.DisableWAL => _noWalWrite, + _ when (flags & WriteFlags.LowPriority) == WriteFlags.LowPriority => _lowPriorityWriteOptions, + _ => WriteOptions + }; public KeyValuePair[] this[byte[][] keys] @@ -939,15 +908,15 @@ internal unsafe int GetCStyleWithColumnFamily(scoped ReadOnlySpan key, Spa UpdateReadMetrics(); nint db = _db.Handle; - nint read_options = readOptions.Handle; + nint readOptionsHandle = readOptions.Handle; UIntPtr skLength = (UIntPtr)key.Length; IntPtr errPtr; IntPtr slice; fixed (byte* ptr = &MemoryMarshal.GetReference(key)) { slice = cf is null - ? Native.Instance.rocksdb_get_pinned(db, read_options, ptr, skLength, out errPtr) - : Native.Instance.rocksdb_get_pinned_cf(db, read_options, cf.Handle, ptr, skLength, out errPtr); + ? Native.Instance.rocksdb_get_pinned(db, readOptionsHandle, ptr, skLength, out errPtr) + : Native.Instance.rocksdb_get_pinned_cf(db, readOptionsHandle, cf.Handle, ptr, skLength, out errPtr); } if (errPtr != IntPtr.Zero) ThrowRocksDbException(errPtr); @@ -972,22 +941,15 @@ internal unsafe int GetCStyleWithColumnFamily(scoped ReadOnlySpan key, Spa return length; [DoesNotReturn, StackTraceHidden] - static unsafe void ThrowRocksDbException(nint errPtr) - { - throw new RocksDbException(errPtr); - } + static void ThrowRocksDbException(nint errPtr) => throw new RocksDbException(errPtr); [DoesNotReturn, StackTraceHidden] - static unsafe void ThrowNotEnoughMemory(int length, int bufferLength) - { + static void ThrowNotEnoughMemory(int length, int bufferLength) => throw new ArgumentException($"Output buffer not large enough. Output size: {length}, Buffer size: {bufferLength}"); - } } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags) - { + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags) => SetWithColumnFamily(key, null, value, writeFlags); - } public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) { @@ -1277,8 +1239,8 @@ internal class RocksDbWriteBatch : IWriteBatch /// /// Because of how rocksdb parallelize writes, a large write batch can stall other new concurrent writes, so - /// we writes the batch in smaller batches. This removes atomicity so its only turned on when NoWAL flag is on. - /// It does not work as well as just turning on unordered_write, but Snapshot and Iterator can still works. + /// we write the batch in smaller batches. This removes atomicity so it's only turned on when the NoWAL flag is on. + /// It does not work as well as just turning on unordered_write, but Snapshot and Iterator can still work. /// private const int MaxWritesOnNoWal = 256; private int _writeCount; @@ -1316,7 +1278,6 @@ private static void ReturnWriteBatch(WriteBatch batch) public void Clear() { ObjectDisposedException.ThrowIf(_dbOnTheRocks._isDisposed, _dbOnTheRocks); - _rocksBatch.Clear(); } @@ -1368,20 +1329,14 @@ public void Set(ReadOnlySpan key, ReadOnlySpan value, ColumnFamilyHa if ((flags & WriteFlags.DisableWAL) != 0) FlushOnTooManyWrites(); } - public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - { + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) => Set(key, value, null, flags); - } - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => Set(key, value, null, flags); - } - public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { + public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => Merge(key, value, null, flags); - } public void Merge(ReadOnlySpan key, ReadOnlySpan value, ColumnFamilyHandle? cf = null, WriteFlags flags = WriteFlags.None) { @@ -1497,18 +1452,13 @@ private class FlushOptions { internal static FlushOptions DefaultFlushOptions { get; } = new(); - public FlushOptions() - { - Handle = RocksDbSharp.Native.Instance.rocksdb_flushoptions_create(); - } - - public IntPtr Handle { get; private set; } + public IntPtr Handle { get; private set; } = Native.Instance.rocksdb_flushoptions_create(); ~FlushOptions() { if (Handle != IntPtr.Zero) { - RocksDbSharp.Native.Instance.rocksdb_flushoptions_destroy(Handle); + Native.Instance.rocksdb_flushoptions_destroy(Handle); Handle = IntPtr.Zero; } } @@ -1573,15 +1523,15 @@ public virtual void Tune(ITunableDb.TuneType type) // See https://github.com/EighteenZi/rocksdb_wiki/blob/master/RocksDB-Tuning-Guide.md switch (type) { - // Depending on tune type, allow num of L0 files to grow causing compaction to occur in larger size. This + // Depending on tune type, allow num of L0 files to grow causing compaction to occur in a larger size. This // reduces write amplification at the expense of read response time and amplification while the tune is // active. Additionally, the larger compaction causes larger spikes of IO, larger memory usage, and may temporarily - // use up large amount of disk space. User may not want to enable this if they plan to run a validator node - // while the node is still syncing, or run another node on the same machine. Specifying a rate limit + // use up a large amount of disk space. User may not want to enable this if they plan to run a validator node + // while the node is still syncing or run another node on the same machine. Specifying a rate limit // smoothens this spike somewhat by not blocking writes while allowing compaction to happen in background // at 1/10th the specified speed (if rate limited). // - // Total writes written on different tune during mainnet sync in TB. + // Total writes written on different tunes during mainnet sync in TB. // +-----------------------+-------+-------+-------+-------+-------+---------+ // | L0FileNumTarget | Total | State | Code | Header| Blocks| Receipts | // +-----------------------+-------+-------+-------+-------+-------+---------+ @@ -1592,45 +1542,45 @@ public virtual void Tune(ITunableDb.TuneType type) // | DisableCompaction | 2.215 | 0.36 | 0.031 | 0.137 | 1.14 | 0.547 | // +-----------------------+-------+-------+-------+-------+-------+---------+ // Note, in practice on my machine, the reads does not reach the SSD. Read measured from SSD is much lower - // than read measured from process. It is likely that most files are cached as I have 128GB of RAM. + // than read measured from a process. It is likely that most files are cached as I have 128GB of RAM. // Also notice that the heavier the tune, the higher the reads. case ITunableDb.TuneType.WriteBias: - // Keep the same l1 size but apply other adjustment which should increase buffer number and make - // l0 the same size as l1, but keep the LSM the same. This improve flush parallelization, and + // Keep the same l1 size but apply other adjustment which should increase the buffer number and make + // l0 the same size as l1 but keep the LSM the same. This improves flush parallelization and // write amplification due to mismatch of l0 and l1 size, but does not reduce compaction from other // levels. ApplyOptions(GetHeavyWriteOptions(_maxBytesForLevelBase)); break; case ITunableDb.TuneType.HeavyWrite: - // Compaction spikes are clear at this point. Will definitely affect attestation performance. - // Its unclear if it improve or slow down sync time. Seems to be the sweet spot. + // Compaction spikes are clear at this point. Will definitely affect attestations performance. + // It's unclear if it improves or slows down sync time. Seems to be the sweet spot. ApplyOptions(GetHeavyWriteOptions((ulong)2.GiB())); break; case ITunableDb.TuneType.AggressiveHeavyWrite: - // For when, you are desperate, but don't wanna disable compaction completely, because you don't want + // For when you are desperate, but don't wanna disable compaction completely, because you don't want // peers to drop. Tend to be faster than disabling compaction completely, except if your ratelimit // is a bit low and your compaction is lagging behind, which will trigger slowdown, so sync will hang // intermittently, but at least peer count is stable. ApplyOptions(GetHeavyWriteOptions((ulong)16.GiB())); break; case ITunableDb.TuneType.DisableCompaction: - // Completely disable compaction. On mainnet, max num of l0 files for state seems to be about 10800. - // Blocksdb are way more at 53000. Final compaction for state db need 30 minute, while blocks db need - // 13 hour. Receipts db don't show up in metrics likely because its a column db. + // Completely disable compaction. On mainnet, the max num of l0 files for state seems to be about 10800. + // Blocksdb are way more at 53000. Final compaction for state db needs 30 minutes, while blocks db need + // 13 hours. Receipts db don't show up in metrics likely because it's a column db. // Ram usage at that time was 86 GB. The default buffer size for blocks on mainnet is too low // to make this work reasonably well. - // L0 to L1 compaction is known to be slower than other level so its - // Snap sync performance suffer as it does have some read during stitching. - // If you don't specify a lower open files limit, it has a tendency to crash, like.. the whole system - // crash. I don't have any open file limit at OS level. - // Also, if a peer send a packet that causes a query to the state db during snap sync like GetNodeData - // or some of the tx filter querying state, It'll cause the network stack to hang and triggers a + // L0 to L1 compaction is known to be slower than other levels, so its + // Snap sync performance suffers as it does have some read during stitching. + // If you don't specify a lower open files limit, it tends to crash, like... the whole system + // crashes. I don't have any open file limit at OS level. + // Also, if a peer sends a packet that causes a query to the state db during snap sync like GetNodeData + // or some of the tx filter querying state, It'll cause the network stack to hang and triggers // large peer drops. Also happens on lesser tune, but weaker. - // State sync essentially hang until that completes because its read heavy, and the uncompacted db is + // State sync essentially hangs until that completes because its read heavy, and the uncompacted db is // slow to a halt. // Additionally, the number of open files handles measured from collectd jumped massively higher. Some // user config may not be able to handle this. - // With all those cons, this result in the minimum write amplification possible via tweaking compaction + // With all those cons, this results in the minimum writes amplification possible via tweaking compaction // without changing memory budget. Not recommended for mainnet, unless you are very desperate. ApplyOptions(GetDisableCompactionOptions()); break; @@ -1649,15 +1599,11 @@ public virtual void Tune(ITunableDb.TuneType type) _currentTune = type; } - protected virtual void ApplyOptions(IDictionary options) - { - _db.SetOptions(options); - } + protected virtual void ApplyOptions(IDictionary options) => _db.SetOptions(options); - private IDictionary GetStandardOptions() - { + private IDictionary GetStandardOptions() => // Defaults are from rocksdb source code - return new Dictionary() + new Dictionary() { { "write_buffer_size", _writeBufferSize.ToString() }, { "max_write_buffer_number", _maxWriteBufferNumber.ToString() }, @@ -1666,7 +1612,7 @@ private IDictionary GetStandardOptions() { "level0_slowdown_writes_trigger", 20.ToString() }, // Very high, so that after moving from HeavyWrite, we don't immediately hang. - // This does means that under very rare case, the l0 file can accumulate, which slow down the db + // This does mean that under a very rare case, the l0 file can accumulate, which slows down the db // until they get compacted. { "level0_stop_writes_trigger", 1024.ToString() }, @@ -1679,13 +1625,11 @@ private IDictionary GetStandardOptions() { "soft_pending_compaction_bytes_limit", 64.GiB().ToString() }, { "hard_pending_compaction_bytes_limit", 256.GiB().ToString() }, }; - } - private IDictionary GetHashDbOptions() - { - return new Dictionary() + private IDictionary GetHashDbOptions() => + new Dictionary() { - // Some database config is slightly faster on hash db database. These are applied when hash db is detected + // Some database config is slightly faster on a hash db database. These are applied when hash db is detected // to prevent unexpected regression. { "table_factory.block_size", "4096" }, { "table_factory.block_restart_interval", "16" }, @@ -1693,35 +1637,34 @@ private IDictionary GetHashDbOptions() { "max_bytes_for_level_multiplier", "10" }, { "max_bytes_for_level_base", "256000000" }, }; - } /// - /// Allow num of l0 file to grow very large. This dramatically increase read response time by about - /// (l0FileNumTarget / (default num (4) + max level usually (4)). but it saves write bandwidth as l0->l1 happens - /// in larger size. In addition to that, the large base l1 size means the number of level is a bit lower. - /// Note: Regardless of max_open_files config, the number of files handle jumped by this number when compacting. It - /// could be that l0->l1 compaction does not (or cant?) follow the max_open_files limit. + /// Allow number of l0 files to grow very large. This dramatically increases read response time by about + /// (l0FileNumTarget / (default num (4) + max level usually (4)), but it saves write bandwidth as l0->l1 happens + /// in larger size. In addition to that, the large base l1 size means the number of levels is a bit lower. + /// Note: Regardless of max_open_files config, the number of files handles jumped by this number when compacting. It + /// could be that l0->l1 compaction does not (or can't?) follow the max_open_files limit. /// - /// + /// /// This caps the maximum allowed number of l0 files, which is also the read response time amplification. /// /// private IDictionary GetHeavyWriteOptions(ulong l0SizeTarget) { // Make buffer (probably) smaller so that it does not take too much memory to have many of them. - // More buffer means more parallel flush, but each read have to go through all buffer one by one much like l0 + // More buffer means more parallel flush, but each read has to go through all buffers one by one, much like l0 // but no io, only cpu. - // bufferSize*maxBufferNumber = 16MB*Core count, which is the max memory used, which tend to be the case as its now - // stalled by compaction instead of flush. - // The buffer is not compressed unlike l0File, so to account for it, its size need to be slightly larger. + // bufferSize*maxBufferNumber = 16MB*Core count, which is the max memory used, which tends to be the case as it's now + // stalled by compaction instead of a flush. + // The buffer is not compressed unlike l0File, so to account for it, its size needs to be slightly larger. ulong targetFileSize = (ulong)16.MiB(); ulong bufferSize = (ulong)(targetFileSize / _perTableDbConfig.CompressibilityHint); ulong l0FileSize = targetFileSize * (ulong)_minWriteBufferToMerge; ulong maxBufferNumber = (ulong)Environment.ProcessorCount; - // Guide recommend to have l0 and l1 to be the same size. They have to be compacted together so if l1 is larger, + // Guide recommends having l0 and l1 to be the same size. They have to be compacted together, so if l1 is larger, // the extra size in l1 is basically extra rewrites. If l0 is larger... then I don't know why not. Even so, it seems to - // always get triggered when l0 size exceed max_bytes_for_level_base even if file num is less than l0FileNumTarget. + // always get triggered when l0 size exceeds max_bytes_for_level_base even if the file number is less than l0FileNumTarget. ulong l0FileNumTarget = l0SizeTarget / l0FileSize; ulong l1SizeTarget = l0SizeTarget; @@ -1749,10 +1692,10 @@ private IDictionary GetDisableCompactionOptions() IDictionary heavyWriteOption = GetHeavyWriteOptions((ulong)32.GiB()); heavyWriteOption["disable_auto_compactions"] = "true"; - // Increase the size of the write buffer, which reduces the number of l0 file by 4x. This does slows down + // Increase the size of the write buffer, which reduces the number of l0 files by 4x. This does slow down // the memtable a little bit. So if you are not write limited, you'll get memtable limited instead. // This does increase the total memory buffer size, but counterintuitively, this reduces overall memory usage - // as it ran out of bloom filter cache so it need to do actual IO. + // as it ran out of bloom filter cache, so it needs to do actual IO. heavyWriteOption["write_buffer_size"] = 64.MiB().ToString(); return heavyWriteOption; @@ -1762,17 +1705,17 @@ private IDictionary GetDisableCompactionOptions() private static IDictionary GetBlobFilesOptions() { // Enable blob files, see: https://rocksdb.org/blog/2021/05/26/integrated-blob-db.html - // This is very useful for blocks, as it almost eliminate 95% of the compaction as the main db no longer + // This is very useful for blocks, as it almost eliminates 95% of the compaction as the main db no longer // store the actual data, but only points to blob files. This config reduces total blocks db writes from about - // 4.6 TB to 0.76 TB, where even the the WAL took 0.45 TB (wal is not compressed), with peak writes of about 300MBps, + // 4.6 TB to 0.76 TB, where even the WAL took 0.45 TB (wal is not compressed), with peak writes of about 300MBps, // it may not even saturate a SATA SSD on a 1GBps internet. - // You don't want to turn this on on other DB as it does add an indirection which take up an additional iop. + // You don't want to turn this on other DB as it does add an indirection which take up an additional iop. // But for large values like blocks (3MB decompressed to 8MB), the response time increase is negligible. - // However without a large buffer size, it will create tens of thousands of small files. There are - // various workaround it, but it all increase total writes, which defeats the purpose. - // Additionally, as the `max_bytes_for_level_base` is set to very low, existing user will suddenly - // get a lot of compaction. So cant turn this on all the time. Turning this back off, will just put back + // However, without a large buffer size, it will create tens of thousands of small files. There are + // various workaround it, but it all increases total writes, which defeats the purpose. + // Additionally, as the `max_bytes_for_level_base` is set to very low, existing users will suddenly + // get a lot of compaction. So can't turn this on all the time. Turning this back off will just put back // new data to SST files. return new Dictionary() @@ -1780,9 +1723,9 @@ private static IDictionary GetBlobFilesOptions() { "enable_blob_files", "true" }, { "blob_compression_type", "kSnappyCompression" }, - // Make file size big, so we have less of them. + // Make the file size big, so we have less of them. { "write_buffer_size", 256.MiB().ToString() }, - // Current memtable + 2 concurrent writes. Can't have too many of these as it take up RAM. + // Current memtable + 2 concurrent writes. Can't have too many of these as it takes up RAM. { "max_write_buffer_number", 3.ToString() }, // These two are SST files instead of the blobs, which are now much smaller. @@ -1795,7 +1738,7 @@ private static IDictionary GetBlobFilesOptions() /// Iterators should not be kept for long as it will pin some memory block and sst file. This would show up as /// temporary higher disk usage or memory usage. /// - /// This class handles a periodic timer which periodically dispose all iterator. + /// This class handles a periodic timer that periodically disposes all iterators. /// public class IteratorManager : IDisposable { @@ -1840,35 +1783,23 @@ public void Dispose() public RentWrapper Rent(ReadFlags flags) { - - ManagedIterators iterators = _readaheadIterators; - if ((flags & ReadFlags.HintReadAhead2) != 0) - { - iterators = _readaheadIterators2; - } - else if ((flags & ReadFlags.HintReadAhead3) != 0) - { - iterators = _readaheadIterators3; - } - + ManagedIterators iterators = GetIterators(flags); IteratorHolder holder = iterators.Value!; // If null, we create a new one. Iterator? iterator = Interlocked.Exchange(ref holder.Iterator, null); return new RentWrapper(iterator ?? _rocksDb.NewIterator(_cf, _readOptions), flags, this); } - private void Return(Iterator iterator, ReadFlags flags) + private ManagedIterators GetIterators(ReadFlags flags) => flags switch { - ManagedIterators iterators = _readaheadIterators; - if ((flags & ReadFlags.HintReadAhead2) != 0) - { - iterators = _readaheadIterators2; - } - else if ((flags & ReadFlags.HintReadAhead3) != 0) - { - iterators = _readaheadIterators3; - } + _ when (flags & ReadFlags.HintReadAhead2) != 0 => _readaheadIterators2, + _ when (flags & ReadFlags.HintReadAhead3) != 0 => _readaheadIterators3, + _ => _readaheadIterators + }; + private void Return(Iterator iterator, ReadFlags flags) + { + ManagedIterators iterators = GetIterators(flags); IteratorHolder holder = iterators.Value!; // We don't keep using the same iterator for too long. @@ -1882,7 +1813,7 @@ private void Return(Iterator iterator, ReadFlags flags) holder.Usage++; Iterator? oldIterator = Interlocked.Exchange(ref holder.Iterator, iterator); - // Well... this is weird. I'll just dispose it. + // Well... this is weird. I'll just dispose of it. oldIterator?.Dispose(); } @@ -1890,21 +1821,14 @@ public readonly struct RentWrapper(Iterator iterator, ReadFlags flags, IteratorM { public Iterator Iterator => iterator; - public void Dispose() - { - manager.Return(iterator, flags); - } + public void Dispose() => manager.Return(iterator, flags); } // Note: use of threadlocal is very important as the seek forward is fast, but the seek backward is not fast. - private sealed class ManagedIterators : ThreadLocal + private sealed class ManagedIterators() : ThreadLocal(static () => new IteratorHolder(), trackAllValues: true) { private bool _disposed = false; - public ManagedIterators() : base(static () => new IteratorHolder(), trackAllValues: true) - { - } - public void ClearIterators() { if (_disposed) return; @@ -1923,7 +1847,7 @@ public void DisposeAll() protected override void Dispose(bool disposing) { - // Note: This is called from finalizer thread, so we can't use foreach to dispose all values + // Note: This is called from finalizer thread, so we can't use foreach to dispose of all values Value?.Dispose(); Value = null!; _disposed = true; @@ -1963,17 +1887,14 @@ public byte[]? LastKey } } - public ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey) - { - return GetViewBetween(firstKey, lastKey, null); - } + public ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey) => GetViewBetween(firstKey, lastKey, null); internal ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey, ColumnFamilyHandle? cf) { ReadOptions readOptions = CreateReadOptions(); - IntPtr iterateLowerBound = IntPtr.Zero; - IntPtr iterateUpperBound = IntPtr.Zero; + IntPtr iterateLowerBound; + IntPtr iterateUpperBound; unsafe { @@ -2008,9 +1929,6 @@ public class RocksDbSnapshot( Snapshot snapshot ) : RocksDbReader(mainDb, readOptionsFactory, null, columnFamily), IKeyValueStoreSnapshot { - public void Dispose() - { - snapshot.Dispose(); - } + public void Dispose() => snapshot.Dispose(); } } diff --git a/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs b/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs index 14bf9efc98f8..548d3d9556a8 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/MergeOperatorAdapter.cs @@ -15,13 +15,13 @@ internal class MergeOperatorAdapter(IMergeOperator inner) : MergeOperator public string Name => inner.Name; // TODO: fix and return array ptr instead of copying to unmanaged memory? - private static unsafe IntPtr GetResult(ArrayPoolList? data, out IntPtr resultLength, out IntPtr success) + private static unsafe nint GetResult(ArrayPoolList? data, out nint resultLength, out byte success) { if (data is null) { - success = Convert.ToInt32(false); - resultLength = IntPtr.Zero; - return IntPtr.Zero; + success = 0; + resultLength = nint.Zero; + return nint.Zero; } using (data) @@ -34,47 +34,31 @@ private static unsafe IntPtr GetResult(ArrayPoolList? data, out IntPtr res // Fixing RocksDbSharp invalid callback signature, TODO: submit an issue/PR Unsafe.SkipInit(out success); - Unsafe.As(ref success) = 1; + Unsafe.As(ref success) = 1; - return (IntPtr)resultPtr; + return (nint)resultPtr; } } - unsafe IntPtr MergeOperator.PartialMerge( - IntPtr keyPtr, - UIntPtr keyLength, - IntPtr operandsList, - IntPtr operandsListLength, - int numOperands, - out IntPtr successPtr, - out IntPtr resultLength) + public unsafe nint PartialMerge(nint key, nuint keyLength, nint operandsList, nint operandsListLength, int numOperands, out byte success, out nint newValueLength) { - var key = new Span((void*)keyPtr, (int)keyLength); + var keyBytes = new Span((void*)key, (int)keyLength); var enumerator = new RocksDbMergeEnumerator(new((void*)operandsList, numOperands), new((void*)operandsListLength, numOperands)); - ArrayPoolList? result = inner.PartialMerge(key, enumerator); - return GetResult(result, out resultLength, out successPtr); + ArrayPoolList? result = inner.PartialMerge(keyBytes, enumerator); + return GetResult(result, out newValueLength, out success); } - unsafe IntPtr MergeOperator.FullMerge( - IntPtr keyPtr, - UIntPtr keyLength, - IntPtr existingValuePtr, - UIntPtr existingValueLength, - IntPtr operandsList, - IntPtr operandsListLength, - int numOperands, - out IntPtr successPtr, - out IntPtr resultLength) + public unsafe nint FullMerge(nint key, nuint keyLength, nint existingValue, nuint existingValueLength, nint operandsList, nint operandsListLength, int numOperands, out byte success, out nint newValueLength) { - var key = new ReadOnlySpan((void*)keyPtr, (int)keyLength); - bool hasExistingValue = existingValuePtr != IntPtr.Zero; - Span existingValue = hasExistingValue ? new((void*)existingValuePtr, (int)existingValueLength) : Span.Empty; - var enumerator = new RocksDbMergeEnumerator(existingValue, hasExistingValue, new((void*)operandsList, numOperands), new((void*)operandsListLength, numOperands)); + var keyBytes = new ReadOnlySpan((void*)key, (int)keyLength); + bool hasExistingValue = existingValue != nint.Zero; + Span existingValueBytes = hasExistingValue ? new((void*)existingValue, (int)existingValueLength) : []; + var enumerator = new RocksDbMergeEnumerator(existingValueBytes, hasExistingValue, new((void*)operandsList, numOperands), new((void*)operandsListLength, numOperands)); - ArrayPoolList? result = inner.FullMerge(key, enumerator); - return GetResult(result, out resultLength, out successPtr); + ArrayPoolList? result = inner.FullMerge(keyBytes, enumerator); + return GetResult(result, out newValueLength, out success); } - unsafe void MergeOperator.DeleteValue(IntPtr value, UIntPtr valueLength) => NativeMemory.Free((void*)value); + unsafe void MergeOperator.DeleteValue(nint value, nuint valueLength) => NativeMemory.Free((void*)value); } diff --git a/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs b/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs index 61bb02e57e13..a94366c63761 100644 --- a/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs +++ b/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using Nethermind.Core; +using Nethermind.Core.Buffers; using Nethermind.Core.Extensions; using Nethermind.JsonRpc; using Nethermind.JsonRpc.Client; @@ -13,35 +15,25 @@ namespace Nethermind.Db.Rpc { - public class RpcDb : IDb + public class RpcDb( + string dbName, + IJsonSerializer jsonSerializer, + IJsonRpcClient rpcClient, + ILogManager logManager, + IDb recordDb) + : IDb { - private readonly string _dbName; - private readonly IJsonSerializer _jsonSerializer; - private readonly ILogger _logger; - private readonly IJsonRpcClient _rpcClient; - private readonly IDb _recordDb; - - public RpcDb(string dbName, IJsonSerializer jsonSerializer, IJsonRpcClient rpcClient, ILogManager logManager, IDb recordDb) - { - _dbName = dbName; - _rpcClient = rpcClient ?? throw new ArgumentNullException(nameof(rpcClient)); - _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - _recordDb = recordDb; - } + private readonly IJsonSerializer _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); + private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); + private readonly IJsonRpcClient _rpcClient = rpcClient ?? throw new ArgumentNullException(nameof(rpcClient)); public void Dispose() { _logger.Info($"Disposing RPC DB {Name}"); - _recordDb.Dispose(); + recordDb.Dispose(); } - public long GetSize() => 0; - public long GetCacheSize() => 0; - public long GetIndexSize() => 0; - public long GetMemtableSize() => 0; - - public string Name { get; } = "RpcDb"; + public string Name => "RpcDb"; public byte[] this[ReadOnlySpan key] { @@ -49,40 +41,16 @@ public byte[] this[ReadOnlySpan key] set => Set(key, value); } - public void Set(ReadOnlySpan key, byte[] value, WriteFlags flags = WriteFlags.None) - { - ThrowWritesNotSupported(); - } - - public byte[] Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - { - return GetThroughRpc(key); - } - + public void Set(ReadOnlySpan key, byte[] value, WriteFlags flags = WriteFlags.None) => ThrowWritesNotSupported(); + public byte[] Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => GetThroughRpc(key); public KeyValuePair[] this[byte[][] keys] => keys.Select(k => new KeyValuePair(k, GetThroughRpc(k))).ToArray(); - - public void Remove(ReadOnlySpan key) - { - ThrowWritesNotSupported(); - } - - public bool KeyExists(ReadOnlySpan key) - { - return GetThroughRpc(key) is not null; - } - - public IDb Innermost => this; // record db is just a helper DB here - public void Flush() { } + public void Remove(ReadOnlySpan key) => ThrowWritesNotSupported(); + public bool KeyExists(ReadOnlySpan key) => GetThroughRpc(key) is not null; public void Flush(bool onlyWal = false) { } - public void Clear() { } - - public IEnumerable> GetAll(bool ordered = false) => _recordDb.GetAll(); - - public IEnumerable GetAllKeys(bool ordered = false) => _recordDb.GetAllKeys(); - - public IEnumerable GetAllValues(bool ordered = false) => _recordDb.GetAllValues(); - + public IEnumerable> GetAll(bool ordered = false) => recordDb.GetAll(); + public IEnumerable GetAllKeys(bool ordered = false) => recordDb.GetAllKeys(); + public IEnumerable GetAllValues(bool ordered = false) => recordDb.GetAllValues(); public IWriteBatch StartWriteBatch() { ThrowWritesNotSupported(); @@ -92,36 +60,24 @@ public IWriteBatch StartWriteBatch() private byte[] GetThroughRpc(ReadOnlySpan key) { - string responseJson = _rpcClient.Post("debug_getFromDb", _dbName, key.ToHexString()).Result; + string responseJson = _rpcClient.Post("debug_getFromDb", dbName, key.ToHexString()).Result; JsonRpcSuccessResponse response = _jsonSerializer.Deserialize(responseJson); byte[] value = null; if (response.Result is not null) { value = Bytes.FromHexString((string)response.Result); - if (_recordDb is not null) + if (recordDb is not null) { - _recordDb[key] = value; + recordDb[key] = value; } } return value; } - public Span GetSpan(ReadOnlySpan key) - { - return Get(key); - } - - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags) - { - ThrowWritesNotSupported(); - } - + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags) => ThrowWritesNotSupported(); private static void ThrowWritesNotSupported() => throw new InvalidOperationException("RPC DB does not support writes"); - - public void DangerousReleaseMemory(in ReadOnlySpan span) - { - } + public void DangerousReleaseMemory(in ReadOnlySpan span) { } } } diff --git a/src/Nethermind/Nethermind.Db.Test/CompressingStoreTests.cs b/src/Nethermind/Nethermind.Db.Test/CompressingStoreTests.cs index 117a4b6d52b8..cb7d63a2d4ba 100644 --- a/src/Nethermind/Nethermind.Db.Test/CompressingStoreTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/CompressingStoreTests.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Test; @@ -71,7 +72,15 @@ public void EOAWithSPan() ctx.Compressed.PutSpan(Key, encoded.Bytes); Assert.That(encoded.Bytes, Is.EqualTo(ctx.Compressed[Key]).AsCollection); - Assert.That(encoded.Bytes, Is.EqualTo(ctx.Compressed.GetSpan(Key).ToArray()).AsCollection); + Span span = ctx.Compressed.GetSpan(Key); + try + { + Assert.That(encoded.Bytes, Is.EqualTo(span.ToArray()).AsCollection); + } + finally + { + ctx.Compressed.DangerousReleaseMemory(span); + } ctx.Wrapped[Key]!.Length.Should().Be(5); } diff --git a/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageCompactorTests.cs b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageCompactorTests.cs new file mode 100644 index 000000000000..e23c4d8a1cba --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageCompactorTests.cs @@ -0,0 +1,184 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Db.LogIndex; +using Nethermind.Logging; +using NSubstitute; +using NUnit.Framework; +using static Nethermind.Db.LogIndex.LogIndexStorage; + +namespace Nethermind.Db.Test.LogIndex; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class LogIndexStorageCompactorTests +{ + private const int RaceConditionTestRepeat = 3; + + private static ILogIndexStorage MockStorage(int? min = null, int? max = null) + { + ILogIndexStorage storage = Substitute.For(); + storage.MinBlockNumber.Returns(min); + storage.MaxBlockNumber.Returns(max); + return storage; + } + + private static Compactor CreateCompactor(ILogIndexStorage storage, IDbMeta? db = null, int compactionDistance = 100) => + new(storage, db ?? new FakeDb(), LimboLogs.Instance.GetClassLogger(), compactionDistance); + + private static Compactor CreateCompactor(ILogIndexStorage storage, int compactionDistance = 100) => + CreateCompactor(storage, db: null, compactionDistance: compactionDistance); + + [TestCase(0, 50, 0, 50, 100, ExpectedResult = false, Description = "No change from baseline")] + [TestCase(0, 0, 0, 99, 100, ExpectedResult = false, Description = "99 blocks forward, threshold 100")] + [TestCase(0, 0, 0, 100, 100, ExpectedResult = true, Description = "Exactly at threshold")] + [TestCase(100, 200, 50, 250, 100, ExpectedResult = true, Description = "Both directions sum to threshold")] + public async Task TryEnqueue_Respects_CompactionDistance( + int initMin, int initMax, int newMin, int newMax, int compactionDistance + ) + { + ILogIndexStorage storage = MockStorage(min: initMin, max: initMax); + using Compactor compactor = CreateCompactor(storage, compactionDistance); + + storage.MinBlockNumber.Returns(newMin); + storage.MaxBlockNumber.Returns(newMax); + + bool result = compactor.TryEnqueue(); + + await compactor.StopAsync(); + return result; + } + + [Test] + [Repeat(RaceConditionTestRepeat)] + public async Task TryEnqueue_During_Compact_Does_Not_Run_Compact_Concurrently() + { + const int compactionDistance = 10; + var compactionDelay = TimeSpan.FromMilliseconds(200); + + ILogIndexStorage storage = MockStorage(min: 0, max: 0); + FakeDb db = new(compactionDelay); + using Compactor compactor = CreateCompactor(storage, db, compactionDistance); + + // Trigger first compaction + storage.MaxBlockNumber.Returns(compactionDistance); + Assert.That(compactor.TryEnqueue(), Is.True); + + await Task.Delay(compactionDelay / 4); + + // Try to cause a second compaction + storage.MaxBlockNumber.Returns(storage.MaxBlockNumber + compactionDistance); + compactor.TryEnqueue(); + + await compactor.ForceAsync(); + await compactor.StopAsync(); + } + + [TestCase(false)] + [TestCase(true)] + [Repeat(RaceConditionTestRepeat)] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] + public async Task ForceAsync_Does_Not_Run_Compact_Concurrently(bool duringCompact) + { + const int compactionDistance = 10; + var compactionDelay = TimeSpan.FromMilliseconds(200); + + ILogIndexStorage storage = MockStorage(min: 0, max: 0); + FakeDb db = new(compactionDelay); + using Compactor compactor = CreateCompactor(storage, db, compactionDistance); + + if (duringCompact) + { + storage.MaxBlockNumber.Returns(compactionDistance); + compactor.TryEnqueue(); + + await Task.Delay(compactionDelay / 4); + } + + const int concurrentCalls = 5; + await Task.WhenAll(Enumerable.Range(0, concurrentCalls).Select(_ => Task.Run(compactor.ForceAsync)).ToArray()); + + await compactor.StopAsync(); + } + + [Test] + public async Task TryEnqueue_Resets_Baseline_After_Enqueue() + { + const int compactionDistance = 10; + + ILogIndexStorage storage = MockStorage(min: 0, max: 0); + using Compactor compactor = CreateCompactor(storage, compactionDistance); + + storage.MaxBlockNumber.Returns(compactionDistance); + Assert.That(compactor.TryEnqueue(), Is.True); + + await Task.Delay(100); + + storage.MaxBlockNumber.Returns(storage.MaxBlockNumber + compactionDistance / 2); + Assert.That(compactor.TryEnqueue(), Is.False); + + await Task.Delay(100); + + storage.MaxBlockNumber.Returns(storage.MaxBlockNumber + compactionDistance / 2); + Assert.That(compactor.TryEnqueue(), Is.True); + + await compactor.StopAsync(); + } + + [Test] + public async Task TryEnqueue_Returns_False_After_Stop() + { + const int compactionDistance = 10; + + ILogIndexStorage storage = MockStorage(min: 0, max: 0); + using Compactor compactor = CreateCompactor(storage, new NonCompactableDb(), compactionDistance); + + await compactor.StopAsync(); + + storage.MaxBlockNumber.Returns(compactionDistance); + Assert.That(compactor.TryEnqueue(), Is.False); + } + + // Fails on compaction attempt + private class NonCompactableDb : IDbMeta + { + private class CompactionException : Exception; + + public void Compact() => throw new CompactionException(); + public void Flush(bool onlyWal = false) { } + } + + // Simulates compaction with Thread.Sleep and fail on concurrent calls + private class FakeDb(TimeSpan? compactDelay = null) : IDbMeta + { + private class ConcurrentCompactionException : Exception; + + private readonly TimeSpan _compactDelay = compactDelay ?? TimeSpan.Zero; + + private int _compacting; + + public void Compact() + { + if (Interlocked.CompareExchange(ref _compacting, 1, 0) != 0) + throw new ConcurrentCompactionException(); + + try + { + if (_compactDelay > TimeSpan.Zero) + Thread.Sleep(_compactDelay); + } + finally + { + Interlocked.Exchange(ref _compacting, 0); + } + } + + public void Flush(bool onlyWal = false) { } + } +} diff --git a/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageIntegrationTests.cs b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageIntegrationTests.cs new file mode 100644 index 000000000000..86e75574ea49 --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageIntegrationTests.cs @@ -0,0 +1,1110 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Runtime.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; +using MathNet.Numerics.Random; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test; +using Nethermind.Core.Test.Builders; +using Nethermind.Db.LogIndex; +using Nethermind.Db.Rocks; +using Nethermind.Db.Rocks.Config; +using Nethermind.Logging; +using Nethermind.TurboPForBindings; +using NUnit.Framework; +using NUnit.Framework.Interfaces; +using static Nethermind.Db.LogIndex.LogIndexStorage; + +namespace Nethermind.Db.Test.LogIndex +{ + [TestFixtureSource(nameof(TestCases))] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] + public class LogIndexStorageIntegrationTests(LogIndexStorageIntegrationTests.TestData testData) + { + private const int RaceConditionTestRepeat = 3; + +#if DEBUG + private static bool LogStatistics => true; +#else + private static bool LogStatistics => false; +#endif + + public static readonly TestFixtureData[] TestCases = + [ + new(new TestData(10, 100) { Compression = CompressionAlgorithm.Best.Key }), + new(new TestData(5, 200) { Compression = nameof(TurboPFor.p4nd1enc128v32) }), + new(new TestData(10, 100) { Compression = CompressionAlgorithm.Best.Key, ExtendedGetRanges = true }) { RunState = RunState.Explicit }, + new(new TestData(100, 100) { Compression = nameof(TurboPFor.p4nd1enc128v32) }) { RunState = RunState.Explicit }, + new(new TestData(100, 200) { Compression = CompressionAlgorithm.Best.Key }) { RunState = RunState.Explicit } + ]; + + private string _dbPath = null!; + private IDbFactory _dbFactory = null!; + private readonly List _createdStorages = []; + + private ILogIndexStorage CreateLogIndexStorage( + int compactionDistance = 262_144, int compressionParallelism = 16, int maxReorgDepth = 64, IDbFactory? dbFactory = null, + string? compressionAlgo = null, int? failOnBlock = null, int? failOnCallN = null, bool failOnMerge = false + ) + { + LogIndexConfig config = new() + { + Enabled = true, + CompactionDistance = compactionDistance, + MaxCompressionParallelism = compressionParallelism, + MaxReorgDepth = maxReorgDepth, + CompressionAlgorithm = compressionAlgo ?? testData.Compression + }; + + ILogIndexStorage storage = failOnBlock is null && failOnCallN is null + ? new LogIndexStorage(dbFactory ?? _dbFactory, LimboLogs.Instance, config) + : new SaveFailingLogIndexStorage(dbFactory ?? _dbFactory, LimboLogs.Instance, config) + { + FailOnBlock = failOnBlock ?? 0, + FailOnCallN = failOnCallN ?? 0, + FailOnMerge = failOnMerge + }; + + return storage.AddTo(_createdStorages); + } + + [SetUp] + public void Setup() + { + _dbPath = $"{nameof(LogIndexStorageIntegrationTests)}/{Guid.NewGuid():N}"; + + if (Directory.Exists(_dbPath)) + Directory.Delete(_dbPath, true); + + Directory.CreateDirectory(_dbPath); + + var config = new DbConfig(); + var configFactory = new RocksDbConfigFactory(config, new PruningConfig(), new TestHardwareInfo(0), LimboLogs.Instance); + _dbFactory = new RocksDbFactory(configFactory, config, new HyperClockCacheWrapper(), new TestLogManager(), _dbPath); + } + + [TearDown] + public async Task TearDown() + { + foreach (ILogIndexStorage storage in _createdStorages) + { + await using (storage) + await storage.StopAsync(); + } + + if (!Directory.Exists(_dbPath)) + return; + + try + { + Directory.Delete(_dbPath, true); + } + catch + { + // ignore + } + } + + [OneTimeSetUp] + //[OneTimeTearDown] // Causes dispose issues, seems to be executed out-of-order + public static void RemoveRootFolder() + { + if (!Directory.Exists(nameof(LogIndexStorageIntegrationTests))) + return; + + try + { + Directory.Delete(nameof(LogIndexStorageIntegrationTests), true); + } + catch + { + // ignore + } + } + + [Combinatorial] + public async Task Set_Get_Test( + [Values(100, 200, int.MaxValue)] int compactionDistance, + [Values(1, 8, 16)] byte ioParallelism, + [Values] bool isBackwardsSync, + [Values] bool compact + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance, ioParallelism); + + BlockReceipts[][] batches = isBackwardsSync ? Reverse(testData.Batches) : testData.Batches; + await AddReceiptsAsync(logIndexStorage, batches, isBackwardsSync); + + if (compact) + await CompactAsync(logIndexStorage); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task SetIntersecting_Get_Test( + [Values(100, 200, int.MaxValue)] int compactionDistance, + [Values(1, 8, 16)] byte ioParallelism, + [Values] bool isBackwardsSync, + [Values] bool compact + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance, ioParallelism); + + BlockReceipts[][] batches = isBackwardsSync ? Reverse(testData.Batches) : testData.Batches; + batches = Intersect(batches); + await AddReceiptsAsync(logIndexStorage, batches, isBackwardsSync); + + if (compact) + await CompactAsync(logIndexStorage); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task BackwardsSet_Set_Get_Test( + [Values(100, 200, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + BlockReceipts[][] batches = testData.Batches; + var half = batches.Length / 2; + + for (var i = 0; i < half + 1; i++) + { + if (half + i < batches.Length) + await AddReceiptsAsync(logIndexStorage, [batches[half + i]], isBackwardsSync: false); + if (i != 0 && half - i >= 0) + await AddReceiptsAsync(logIndexStorage, Reverse([batches[half - i]]), isBackwardsSync: true); + } + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + [Repeat(RaceConditionTestRepeat)] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] + public async Task Concurrent_BackwardsSet_Set_Get_Test( + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using (ILogIndexStorage setStorage = CreateLogIndexStorage(compactionDistance)) + { + int half = testData.Batches.Length / 2; + BlockReceipts[][] batches = testData.Batches + .Select((b, i) => i >= half ? b : b.Reverse().ToArray()) + .ToArray(); + + var forwardTask = Task.Run(async () => + { + for (int i = half; i < batches.Length; i++) + { + BlockReceipts[] batch = batches[i]; + await AddReceiptsAsync(setStorage, [batch], isBackwardsSync: false); + + Assert.That(setStorage.MinBlockNumber, Is.LessThanOrEqualTo(batch[0].BlockNumber)); + Assert.That(setStorage.MaxBlockNumber, Is.EqualTo(batch[^1].BlockNumber)); + } + }); + + var backwardTask = Task.Run(async () => + { + for (int i = half - 1; i >= 0; i--) + { + BlockReceipts[] batch = batches[i]; + await AddReceiptsAsync(setStorage, [batch], isBackwardsSync: true); + + Assert.That(setStorage.MinBlockNumber, Is.EqualTo(batch[^1].BlockNumber)); + Assert.That(setStorage.MaxBlockNumber, Is.GreaterThanOrEqualTo(batch[0].BlockNumber)); + } + }); + + await Task.WhenAll(forwardTask, backwardTask); + } + + // Create new storage to force-load everything from DB + await using (ILogIndexStorage testStorage = CreateLogIndexStorage(compactionDistance)) + VerifyReceipts(testStorage, testData); + } + + [Combinatorial] + [Repeat(RaceConditionTestRepeat)] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] + public async Task Concurrent_BackwardSet_Reorg_Get_Test( + [Values(1, 5, 10)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + int half = testData.Batches.Length / 2; + BlockReceipts[][] forwardBatches = testData.Batches.Skip(half).ToArray(); + BlockReceipts[][] backwardBatches = testData.Batches.Take(half).ToArray(); + + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + // Add forward blocks first to establish the head of the chain + await AddReceiptsAsync(logIndexStorage, forwardBatches, isBackwardsSync: false); + + BlockReceipts[] reorgBlocks = forwardBatches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + + var reorgTask = Task.Run(async () => + { + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + }); + + var backwardTask = Task.Run(async () => + { + foreach (BlockReceipts[] batch in Reverse(backwardBatches)) + await AddReceiptsAsync(logIndexStorage, [batch], isBackwardsSync: true); + }); + + await Task.WhenAll(reorgTask, backwardTask); + + VerifyReceipts(logIndexStorage, testData, excludedBlocks: reorgBlocks, maxBlock: reorgBlocks[0].BlockNumber - 1); + } + + [Combinatorial] + public async Task Set_ReorgLast_Get_Test( + [Values(1, 5, 20)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + BlockReceipts[] reorgBlocks = testData.Batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + + VerifyReceipts(logIndexStorage, testData, excludedBlocks: reorgBlocks, maxBlock: reorgBlocks[0].BlockNumber - 1); + } + + [Ignore("Out-of-order reorgs are not supported ATM: only the first (by write order) Reorg operand per key is applied by MergeOperator.")] + [Combinatorial] + public async Task Set_ReorgOutOfOrder_Get_Test( + [Values(2, 5, 10)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + BlockReceipts[] reverseReorg = testData.Batches.SelectMany(b => b).Reverse().Take(reorgDepth).ToArray(); + + foreach (BlockReceipts block in reverseReorg) + await logIndexStorage.RemoveReorgedAsync(block); + + // Full data verification: would fail because MergeOperator only applies the first Reorg operand + // (the highest block in descending order), leaving intermediate blocks as stale data. + VerifyReceipts(logIndexStorage, testData, excludedBlocks: reverseReorg, maxBlock: reverseReorg[0].BlockNumber - 1); + } + + [Combinatorial] + public async Task Set_ReorgAndSetLast_Get_Test( + [Values(1, 5, 20)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + BlockReceipts[] reorgBlocks = testData.Batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + foreach (BlockReceipts block in reorgBlocks) + { + await logIndexStorage.RemoveReorgedAsync(block); + await logIndexStorage.AddReceiptsAsync([block], false); + } + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task Set_ReorgLast_SetLast_Get_Test( + [Values(1, 5, 20)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + BlockReceipts[] reorgBlocks = testData.Batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + + await logIndexStorage.AddReceiptsAsync(reorgBlocks, false); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task Set_ReorgNonexistent_Get_Test( + [Values(1, 5)] int reorgDepth, + [Values(100, int.MaxValue)] int compactionDistance + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + var lastBlock = testData.Batches[^1][^1].BlockNumber; + BlockReceipts[] reorgBlocks = GenerateBlocks(new Random(4242), lastBlock - reorgDepth + 1, reorgDepth); + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + + // Need custom check because Reorg updates the last block even if it's "nonexistent" + Assert.That(logIndexStorage.MaxBlockNumber, Is.EqualTo(lastBlock - reorgDepth)); + + VerifyReceipts(logIndexStorage, testData, excludedBlocks: reorgBlocks, validateMinMax: false); + } + + [TestCase(1, 1)] + [TestCase(32, 64)] + [TestCase(64, 64)] + [TestCase(65, 64, Explicit = true)] + public async Task Set_Compact_ReorgLast_Get_Test(int reorgDepth, int maxReorgDepth) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(maxReorgDepth: maxReorgDepth); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + await CompactAsync(logIndexStorage); + + BlockReceipts[] reorgBlocks = testData.Batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + + var lastBlock = testData.Batches[^1][^1].BlockNumber; + VerifyReceipts(logIndexStorage, testData, maxBlock: lastBlock - reorgDepth); + } + + [Combinatorial] + public async Task Set_PeriodicReorg_Get_Test( + [Values(10, 70)] int reorgFrequency, + [Values(1, 5)] int maxReorgDepth, + [Values] bool compactAfter + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(); + + var random = new Random(42); + var allReorgBlocks = new List(); + var allAddedBlocks = new List(); + + foreach (BlockReceipts[][] batches in testData.Batches.GroupBy(b => b[0].BlockNumber / reorgFrequency).Select(g => g.ToArray())) + { + await AddReceiptsAsync(logIndexStorage, batches); + + var reorgDepth = random.Next(1, maxReorgDepth); + BlockReceipts[] reorgBlocks = batches.SelectMany(b => b).TakeLast(reorgDepth).ToArray(); + BlockReceipts[] addedBlocks = GenerateBlocks(random, reorgBlocks.First().BlockNumber, reorgBlocks.Length); + + allReorgBlocks.AddRange(reorgBlocks); + allAddedBlocks.AddRange(addedBlocks); + + foreach (BlockReceipts block in reorgBlocks) + await logIndexStorage.RemoveReorgedAsync(block); + + if (compactAfter) + await CompactAsync(logIndexStorage); + + await logIndexStorage.AddReceiptsAsync(addedBlocks, false); + } + + VerifyReceipts(logIndexStorage, testData, excludedBlocks: allReorgBlocks, addedBlocks: allAddedBlocks); + } + + [Ignore("Not supported, but is probably not needed.")] + [Combinatorial] + public async Task Set_ConsecutiveReorgsLast_Get_Test( + [Values(new[] { 2, 1 }, new[] { 1, 2 })] int[] reorgDepths, + [Values] bool compactBetween + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + BlockReceipts[] testBlocks = testData.Batches.SelectMany(b => b).ToArray(); + + foreach (var reorgDepth in reorgDepths) + { + foreach (BlockReceipts block in testBlocks.TakeLast(reorgDepth).ToArray()) + await logIndexStorage.RemoveReorgedAsync(block); + + if (compactBetween) + await CompactAsync(logIndexStorage); + } + + VerifyReceipts(logIndexStorage, testData, maxBlock: testBlocks[^1].BlockNumber - reorgDepths.Max()); + } + + [Combinatorial] + public async Task SetMultiInstance_Get_Test( + [Values(100, int.MaxValue)] int compactionDistance, + [Values] bool isBackwardsSync + ) + { + var half = testData.Batches.Length / 2; + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + await AddReceiptsAsync(logIndexStorage, testData.Batches.Take(half)); + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + await AddReceiptsAsync(logIndexStorage, testData.Batches.Skip(half)); + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task RepeatedSet_Get_Test( + [Values(100, int.MaxValue)] int compactionDistance, + [Values] bool isBackwardsSync + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task RepeatedSetMultiInstance_Get_Test( + [Values(100, int.MaxValue)] int compactionDistance, + [Values] bool isBackwardsSync + ) + { + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task Set_NewInstance_Get_Test( + [Values(1, 8)] int ioParallelism, + [Values(100, int.MaxValue)] int compactionDistance, + [Values] bool isBackwardsSync + ) + { + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + await using (ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance)) + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + [Repeat(RaceConditionTestRepeat)] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] + public async Task Set_ConcurrentGet_Test( + [Values(1, 200, int.MaxValue)] int compactionDistance, + [Values] bool isBackwardsSync + ) + { + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(compactionDistance); + + using var getCancellation = new CancellationTokenSource(); + CancellationToken token = getCancellation.Token; + + ConcurrentBag exceptions = []; + Thread[] getThreads = + [ + new(() => VerifyReceiptsPartialLoop(new Random(42), logIndexStorage, testData, exceptions, token)), + new(() => VerifyReceiptsPartialLoop(new Random(4242), logIndexStorage, testData, exceptions, token)), + new(() => VerifyReceiptsPartialLoop(new Random(424242), logIndexStorage, testData, exceptions, token)) + ]; + getThreads.ForEach(t => t.Start()); + + await AddReceiptsAsync(logIndexStorage, testData.Batches); + + await getCancellation.CancelAsync(); + getThreads.ForEach(t => t.Join()); + + if (exceptions.FirstOrDefault() is { } exception) + ExceptionDispatchInfo.Capture(exception).Throw(); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + public async Task SetFailure_Get_Test( + [Values(1, 20, 51, 100)] int failOnCallN, + [Values] bool isBackwardsSync + ) + { + BlockReceipts[][] batches = isBackwardsSync ? Reverse(testData.Batches) : testData.Batches; + var midBlock = testData.Batches[^1][^1].BlockNumber / 2; + + await using ILogIndexStorage failLogIndexStorage = CreateLogIndexStorage(failOnBlock: midBlock, failOnCallN: failOnCallN); + + Exception exception = Assert.ThrowsAsync(() => AddReceiptsAsync(failLogIndexStorage, batches, isBackwardsSync)); + Assert.That(exception, Has.Message.EqualTo(SaveFailingLogIndexStorage.FailMessage)); + + VerifyReceipts( + failLogIndexStorage, testData, + minBlock: failLogIndexStorage.MinBlockNumber ?? 0, maxBlock: failLogIndexStorage.MaxBlockNumber ?? 0 + ); + } + + [Combinatorial] + public async Task SetFailure_Set_Get_Test( + [Values(1, 20, 51, 100)] int failOnCallN, + [Values] bool isBackwardsSync + ) + { + BlockReceipts[][] batches = isBackwardsSync ? Reverse(testData.Batches) : testData.Batches; + var midBlock = testData.Batches[^1][^1].BlockNumber / 2; + + await using (ILogIndexStorage failLogIndexStorage = CreateLogIndexStorage(failOnBlock: midBlock, failOnCallN: failOnCallN)) + { + Exception exception = Assert.ThrowsAsync(() => AddReceiptsAsync(failLogIndexStorage, batches, isBackwardsSync)); + Assert.That(exception, Has.Message.EqualTo(SaveFailingLogIndexStorage.FailMessage)); + } + + await using ILogIndexStorage logIndexStorage = CreateLogIndexStorage(); + await AddReceiptsAsync(logIndexStorage, batches, isBackwardsSync); + + VerifyReceipts(logIndexStorage, testData); + } + + [Combinatorial] + [SuppressMessage("ReSharper", "AccessToDisposedClosure")] + public async Task SetMergeFailure_AnyWrite_Test( + [Values(1, 20, 51, 100)] int failOnCallN + ) + { + var midBlock = testData.Batches[^1][^1].BlockNumber / 2; + await using var storage = (LogIndexStorage)CreateLogIndexStorage(failOnBlock: midBlock, failOnCallN: failOnCallN, failOnMerge: true); + + try + { + await AddReceiptsAsync(storage, testData.Batches); + + // force compaction if the error hasn't propagated already + await storage.CompactAsync(); + } + catch (LogIndexStateException) + { + // Expected + } + + Assert.That(storage.HasBackgroundError, Is.True); + + IEnumerable sinceLastBatch = testData.Batches.SelectMany(b => b) + .SkipWhile(b => b.BlockNumber < storage.MaxBlockNumber); + + await Assert.ThatAsync( + () => storage.AddReceiptsAsync(sinceLastBatch.Skip(1).Take(10).ToArray(), false), + Throws.InstanceOf().And.Message.Contain("merge") + ); + + await Assert.ThatAsync( + () => storage.RemoveReorgedAsync(sinceLastBatch.First()), + Throws.InstanceOf().And.Message.Contain("merge") + ); + + await Assert.ThatAsync( + () => storage.CompactAsync(), + Throws.InstanceOf().And.Message.Contain("merge") + ); + + Assert.DoesNotThrowAsync(() => storage.StopAsync()); + Assert.DoesNotThrowAsync(() => storage.StopAsync()); + } + + [Combinatorial] + public async Task Set_AlgoChange_Test() + { + if (CompressionAlgorithm.Supported.Count < 2) + Assert.Ignore("Less than 2 supported compression algorithms"); + + await using (var logIndexStorage = CreateLogIndexStorage()) + await AddReceiptsAsync(logIndexStorage, [testData.Batches[0]]); + + var oldAlgo = testData.Compression ?? CompressionAlgorithm.Best.Key; + var newAlgo = CompressionAlgorithm.Supported.First(c => c.Key != oldAlgo).Key; + + NotSupportedException exception = Assert.Throws(() => CreateLogIndexStorage(compressionAlgo: newAlgo)); + Assert.That(exception, Has.Message.Contain(oldAlgo).And.Message.Contain(newAlgo)); + } + + private static BlockReceipts[] GenerateBlocks(Random random, int from, int count) => + new TestData(random, 1, count, startNum: from).Batches[0]; + + private static async Task AddReceiptsAsync(ILogIndexStorage logIndexStorage, IEnumerable batches, bool isBackwardsSync = false) + { + long timestamp = Stopwatch.GetTimestamp(); + + LogIndexUpdateStats totalStats = new(logIndexStorage); + (int count, int length) = (0, 0); + foreach (BlockReceipts[] batch in batches) + { + count++; + length = batch.Length; + await logIndexStorage.AddReceiptsAsync(batch, isBackwardsSync, totalStats); + } + + if (LogStatistics) + { + await TestContext.Out.WriteLineAsync( + $""" + x{count} {nameof(LogIndexStorage.AddReceiptsAsync)}([{length}], {isBackwardsSync}) in {Stopwatch.GetElapsedTime(timestamp)}: + {totalStats:d} + {'\t'}DB size: {logIndexStorage.GetDbSize()} + + """ + ); + } + } + + private static void VerifyReceipts(ILogIndexStorage logIndexStorage, TestData testData, + Dictionary>? excludedAddresses = null, + Dictionary>>? excludedTopics = null, + Dictionary>? addedAddresses = null, + Dictionary>>? addedTopics = null, + int? minBlock = null, int? maxBlock = null, + bool validateMinMax = true + ) + { + minBlock ??= testData.Batches[0][0].BlockNumber; + maxBlock ??= testData.Batches[^1][^1].BlockNumber; + + if (validateMinMax) + { + using (Assert.EnterMultipleScope()) + { + Assert.That(logIndexStorage.MinBlockNumber, Is.EqualTo(minBlock)); + Assert.That(logIndexStorage.MaxBlockNumber, Is.EqualTo(maxBlock)); + } + } + + foreach ((Address address, HashSet blocks) in testData.AddressMap) + { + IEnumerable expectedBlocks = blocks; + + if (excludedAddresses != null && excludedAddresses.TryGetValue(address, out HashSet addressExcludedBlocks)) + expectedBlocks = expectedBlocks.Except(addressExcludedBlocks); + + if (addedAddresses != null && addedAddresses.TryGetValue(address, out HashSet addressAddedBlocks)) + expectedBlocks = expectedBlocks.Concat(addressAddedBlocks); + + expectedBlocks = expectedBlocks.Order(); + + if (minBlock > testData.Batches[0][0].BlockNumber) + expectedBlocks = expectedBlocks.SkipWhile(b => b < minBlock); + + if (maxBlock < testData.Batches[^1][^1].BlockNumber) + expectedBlocks = expectedBlocks.TakeWhile(b => b <= maxBlock); + + expectedBlocks = expectedBlocks.ToArray(); + + foreach (var (from, to) in testData.Ranges) + { + Assert.That( + logIndexStorage.GetBlockNumbersFor(address, from, to), + Is.EqualTo(expectedBlocks.SkipWhile(i => i < from).TakeWhile(i => i <= to)), + $"Address: {address}, from {from} to {to}" + ); + } + } + + foreach ((int idx, Dictionary> byTopic) in testData.TopicMap) + { + foreach ((Hash256 topic, HashSet blocks) in byTopic) + { + IEnumerable expectedBlocks = blocks; + + if (excludedTopics != null && excludedTopics[idx].TryGetValue(topic, out HashSet topicExcludedBlocks)) + expectedBlocks = expectedBlocks.Except(topicExcludedBlocks); + + if (addedTopics != null && addedTopics[idx].TryGetValue(topic, out HashSet topicAddedBlocks)) + expectedBlocks = expectedBlocks.Concat(topicAddedBlocks); + + expectedBlocks = expectedBlocks.Order(); + + if (minBlock > testData.Batches[0][0].BlockNumber) + expectedBlocks = expectedBlocks.SkipWhile(b => b < minBlock); + + if (maxBlock < testData.Batches[^1][^1].BlockNumber) + expectedBlocks = expectedBlocks.TakeWhile(b => b <= maxBlock); + + expectedBlocks = expectedBlocks.ToArray(); + + foreach (var (from, to) in testData.Ranges) + { + Assert.That( + logIndexStorage.GetBlockNumbersFor(idx, topic, from, to), + Is.EqualTo(expectedBlocks.SkipWhile(i => i < from).TakeWhile(i => i <= to)), + $"Topic: [{idx}] {topic}, {from} - {to}" + ); + } + } + } + } + + private static void VerifyReceipts(ILogIndexStorage logIndexStorage, TestData testData, + IEnumerable? excludedBlocks, IEnumerable? addedBlocks = null, + int? minBlock = null, int? maxBlock = null, + bool validateMinMax = true) + { + var excludeMaps = excludedBlocks == null ? default : TestData.GenerateMaps(excludedBlocks); + var addMaps = addedBlocks == null ? default : TestData.GenerateMaps(addedBlocks); + + VerifyReceipts( + logIndexStorage, testData, + excludedAddresses: excludeMaps.address, excludedTopics: excludeMaps.topic, + addedAddresses: addMaps.address, addedTopics: addMaps.topic, + minBlock: minBlock, maxBlock: maxBlock, + validateMinMax: validateMinMax + ); + } + + private static void VerifyReceiptsPartialLoop(Random random, ILogIndexStorage logIndexStorage, TestData testData, + ConcurrentBag exceptions, CancellationToken cancellationToken) + { + try + { + (List
addresses, List<(int, Hash256)> topics) = (testData.Addresses, testData.Topics); + + while (!cancellationToken.IsCancellationRequested) + { + if (addresses.Count != 0) + { + Address address = random.NextFrom(addresses); + HashSet expectedBlocks = testData.AddressMap[address]; + + if (logIndexStorage.MinBlockNumber is not { } min || logIndexStorage.MaxBlockNumber is not { } max) + continue; + + Assert.That( + logIndexStorage.GetBlockNumbersFor(address, min, max), + Is.EqualTo(expectedBlocks.SkipWhile(i => i < min).TakeWhile(i => i <= max)), + $"Address: {address}, available: {min} - {max}" + ); + } + + if (topics.Count != 0) + { + (int idx, Hash256 topic) = random.NextFrom(topics); + HashSet expectedBlocks = testData.TopicMap[idx][topic]; + + if (logIndexStorage.MinBlockNumber is not { } min || logIndexStorage.MaxBlockNumber is not { } max) + continue; + + Assert.That( + logIndexStorage.GetBlockNumbersFor(idx, topic, min, max), + Is.EqualTo(expectedBlocks.SkipWhile(i => i < min).TakeWhile(i => i <= max)), + $"Topic: [{idx}] {topic}, available: {min} - {max}" + ); + } + } + } + catch (Exception ex) + { + exceptions.Add(ex); + } + } + + private static BlockReceipts[][] Reverse(BlockReceipts[][] batches) + { + int length = batches.Length; + BlockReceipts[][] result = new BlockReceipts[length][]; + + int index = 0; + foreach (BlockReceipts[] batch in batches.Reverse()) + result[index++] = batch.Reverse().ToArray(); + + return result; + } + + private static BlockReceipts[][] Intersect(BlockReceipts[][] batches) + { + BlockReceipts[][] result = new BlockReceipts[batches.Length + 1][]; + + for (int i = 0; i < result.Length; i++) + { + if (i == 0) + result[i] = batches[i]; + else if (i == batches.Length) + result[i] = batches[^1].Skip(batches[^2].Length / 2).ToArray(); + else + result[i] = batches[i - 1].Skip(batches[i - 1].Length / 2).Concat(batches[i].Take(batches[i].Length / 2)).ToArray(); + } + + return result; + } + + private static async Task CompactAsync(ILogIndexStorage logIndexStorage) + { + long timestamp = Stopwatch.GetTimestamp(); + await ((LogIndexStorage)logIndexStorage).CompactAsync(); + + if (LogStatistics) + { + await TestContext.Out.WriteLineAsync( + $""" + {nameof(LogIndexStorage.CompactAsync)}() in {Stopwatch.GetElapsedTime(timestamp)}: + {'\t'}DB size: {logIndexStorage.GetDbSize()} + + """ + ); + } + } + + public class TestData + { + private readonly int _batchCount; + private readonly int _blocksPerBatch; + private readonly int _startNum; + + // Lazy avoids generating all the data just to display test cases in the runner + private readonly Lazy _batches; + public BlockReceipts[][] Batches => _batches.Value; + + private List
? _addresses; + private List<(int, Hash256)>? _topics; + + private readonly Lazy> _ranges; + public IEnumerable<(int from, int to)> Ranges => _ranges.Value; + + public Dictionary> AddressMap { get; private set; } = new(); + public Dictionary>> TopicMap { get; private set; } = new(); + + public List
Addresses + { + get + { + _ = Batches; + return _addresses!; + } + } + + public List<(int, Hash256)> Topics + { + get + { + _ = Batches; + return _topics!; + } + } + + public bool ExtendedGetRanges { get; init; } + public string? Compression { get; init; } + + public TestData(Random random, int batchCount, int blocksPerBatch, int startNum = 0) + { + _batchCount = batchCount; + _blocksPerBatch = blocksPerBatch; + _startNum = startNum; + + _batches = new(() => GenerateBatches(random, batchCount, blocksPerBatch, startNum)); + _ranges = new(() => ExtendedGetRanges ? GenerateExtendedRanges() : GenerateSimpleRanges()); + } + + public TestData(int batchCount, int blocksPerBatch, int startNum = 0) : this(new(42), batchCount, blocksPerBatch, startNum) { } + + private BlockReceipts[][] GenerateBatches(Random random, int batchCount, int blocksPerBatch, int startNum = 0) + { + var batches = new BlockReceipts[batchCount][]; + var blocksCount = batchCount * blocksPerBatch; + + Address[] customAddresses = + [ + Address.Zero, Address.MaxValue, + new(new byte[] { 1 }.PadLeft(Address.Size)), new(new byte[] { 1, 1 }.PadLeft(Address.Size)), + new(new byte[] { 1 }.PadRight(Address.Size)), new(new byte[] { 1, 1 }.PadRight(Address.Size)), + new(new byte[] { 0 }.PadLeft(Address.Size, 0xFF)), new(new byte[] { 0 }.PadRight(Address.Size, 0xFF)), + ]; + + Hash256[] customTopics = + [ + Hash256.Zero, new(Array.Empty().PadRight(Hash256.Size, 0xFF)), + new(new byte[] { 0 }.PadLeft(Hash256.Size)), new(new byte[] { 1 }.PadLeft(Hash256.Size)), + new(new byte[] { 0 }.PadRight(Hash256.Size)), new(new byte[] { 1 }.PadRight(Hash256.Size)), + new(new byte[] { 0 }.PadLeft(Hash256.Size, 0xFF)), new(new byte[] { 0 }.PadRight(Hash256.Size, 0xFF)), + ]; + + Address[] addresses = Enumerable.Repeat(0, Math.Max(10, blocksCount / 5) - customAddresses.Length) + //var addresses = Enumerable.Repeat(0, 0) + .Select(_ => new Address(random.NextBytes(Address.Size))) + .Concat(customAddresses) + .ToArray(); + Hash256[] topics = Enumerable.Repeat(0, addresses.Length * 7 - customTopics.Length) + //var topics = Enumerable.Repeat(0, 0) + .Select(_ => new Hash256(random.NextBytes(Hash256.Size))) + .Concat(customTopics) + .ToArray(); + + // Generate batches + int blockNum = startNum; + for (int i = 0; i < batches.Length; i++) + { + BlockReceipts[] batch = batches[i] = new BlockReceipts[blocksPerBatch]; + + for (int j = 0; j < batch.Length; j++) + batch[j] = new(blockNum++, GenerateReceipts(random, addresses, topics)); + } + + var maps = GenerateMaps(batches.SelectMany(b => b)); + + (AddressMap, TopicMap) = (maps.address, maps.topic); + + _addresses = maps.address.Keys.ToList(); + _topics = maps.topic.SelectMany(byIdx => byIdx.Value.Select(byTpc => (byIdx.Key, byTpc.Key))).ToList(); + + return batches; + } + + public static (Dictionary> address, Dictionary>> topic) GenerateMaps( + IEnumerable blocks) + { + Dictionary> address = new(); + Dictionary>> topic = new(); + + foreach (BlockReceipts block in blocks) + { + foreach (TxReceipt txReceipt in block.Receipts) + { + foreach (LogEntry log in txReceipt.Logs!) + { + HashSet addressMap = address.GetOrAdd(log.Address, static _ => []); + addressMap.Add(block.BlockNumber); + + for (int i = 0; i < log.Topics.Length; i++) + { + Dictionary> topicI = topic.GetOrAdd(i, static _ => []); + HashSet topicMap = topicI.GetOrAdd(log.Topics[i], static _ => []); + topicMap.Add(block.BlockNumber); + } + } + } + } + + return (address, topic); + } + + private static TxReceipt[] GenerateReceipts(Random random, Address[] addresses, Hash256[] topics) + { + (int min, int max) logsPerBlock = (0, 200); + (int min, int max) logsPerTx = (0, 10); + + LogEntry[] logs = Enumerable + .Repeat(0, random.Next(logsPerBlock.min, logsPerBlock.max + 1)) + .Select(_ => Build.A.LogEntry + .WithAddress(random.NextFrom(addresses)) + .WithTopics(topics.Length == 0 + ? [] + : Enumerable.Repeat(0, random.Next(4)).Select(_ => random.NextFrom(topics)).ToArray() + ).TestObject + ).ToArray(); + + List receipts = new(); + for (var i = 0; i < logs.Length;) + { + int count = random.Next(logsPerTx.min, Math.Min(logsPerTx.max, logs.Length - i) + 1); + Range range = i..(i + count); + + receipts.Add(new() { Logs = logs[range] }); + i = range.End.Value; + } + + return receipts.ToArray(); + } + + private static HashSet<(int from, int to)> GenerateSimpleRanges(int min, int max) + { + int quarter = (max - min) / 4; + return [(0, int.MaxValue), (min, max), (min + quarter, max - quarter)]; + } + + private static HashSet<(int from, int to)> GenerateExtendedRanges(int min, int max) + { + HashSet<(int, int)> ranges = new(); + + int[] edges = [min - 1, min, min + 1, max - 1, max + 1]; + ranges.AddRange(edges.SelectMany(_ => edges, static (x, y) => (x, y))); + + const int step = 100; + for (var i = min; i <= max; i += step) + { + var middles = new[] { i - step, i - 1, i, i + 1, i + step }; + ranges.AddRange(middles.SelectMany(_ => middles, static (x, y) => (x, y))); + } + + return ranges; + } + + private HashSet<(int from, int to)> GenerateSimpleRanges() => GenerateSimpleRanges( + _startNum, _startNum + _batchCount * _blocksPerBatch - 1 + ); + + private HashSet<(int from, int to)> GenerateExtendedRanges() => GenerateExtendedRanges( + _startNum, _startNum + _batchCount * _blocksPerBatch - 1 + ); + + public override string ToString() => + $"{_batchCount} * {_blocksPerBatch} blocks (ex-ranges: {ExtendedGetRanges}, compression: {Compression})"; + } + + private class SaveFailingLogIndexStorage(IDbFactory dbFactory, ILogManager logManager, ILogIndexConfig config) + : LogIndexStorage(dbFactory, logManager, config) + { + public const string FailMessage = "Test exception."; + + public int FailOnBlock { get; init; } + public int FailOnCallN { get; init; } + public bool FailOnMerge { get; init; } + + private int _count; + private bool _corrupted; + + protected override void MergeBlockNumbers(IWriteBatch dbBatch, ReadOnlySpan key, List numbers, bool isBackwardSync, LogIndexUpdateStats? stats) + { + var isFailBlock = + FailOnBlock >= Math.Min(numbers[0], numbers[^1]) && + FailOnBlock <= Math.Max(numbers[0], numbers[^1]); + + if (isFailBlock && Interlocked.Increment(ref _count) >= FailOnCallN && !Interlocked.Exchange(ref _corrupted, true)) + { + if (FailOnMerge) + { + // Force "invalid order" in MergeOperator + int invalidBlockNum = isBackwardSync ? int.MaxValue : 0; + base.MergeBlockNumbers(dbBatch, key, [invalidBlockNum], isBackwardSync, stats); + } + else + { + throw new(FailMessage); + } + } + + base.MergeBlockNumbers(dbBatch, key, numbers, isBackwardSync, stats); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageTestExtensions.cs b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageTestExtensions.cs new file mode 100644 index 000000000000..dccf8578a996 --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Test/LogIndex/LogIndexStorageTestExtensions.cs @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Db.LogIndex; + +namespace Nethermind.Db.Test.LogIndex; + +public static class LogIndexStorageTestExtensions +{ + extension(ILogIndexStorage storage) + { + public List GetBlockNumbersFor(Address address, int from, int to) + { + var result = new List(); + using IEnumerator enumerator = storage.GetEnumerator(address, from, to); + + while (enumerator.MoveNext()) + result.Add(enumerator.Current); + + return result; + } + + public List GetBlockNumbersFor(int index, Hash256 topic, int from, int to) + { + var result = new List(); + using IEnumerator enumerator = storage.GetEnumerator(index, topic, from, to); + + while (enumerator.MoveNext()) + result.Add(enumerator.Current); + + return result; + } + + public Task AddReceiptsAsync(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats = null) + { + LogIndexAggregate aggregate = storage.Aggregate(batch, isBackwardSync, stats); + return storage.AddReceiptsAsync(aggregate, stats); + } + } +} diff --git a/src/Nethermind/Nethermind.Db.Test/LogIndex/MergeOperatorTests.cs b/src/Nethermind/Nethermind.Db.Test/LogIndex/MergeOperatorTests.cs new file mode 100644 index 000000000000..a7d9386face6 --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Test/LogIndex/MergeOperatorTests.cs @@ -0,0 +1,247 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Linq; +using System.Runtime.InteropServices; +using ConcurrentCollections; +using MathNet.Numerics.Random; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Extensions; +using Nethermind.Db.LogIndex; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Db.Test.LogIndex; + +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class MergeOperatorTests +{ + [TestCase( + null, + new[] { "1, 2", "3", "4" }, + "1, 2, 3, 4" + )] + [TestCase( + "1", + new[] { "2, 3", "4" }, + "1, 2, 3, 4" + )] + [TestCase( + "1, 2", + new[] { "3, 4", "Reorg:3", "5, 6" }, + "1, 2, 5, 6" + )] + [TestCase( + "1, 2", + new[] { "3, 4", "Reorg:2", "5, 6" }, + "1, 5, 6" + )] + [TestCase( + "1, 2", + new[] { "3, 4", "Reorg:3", "Reorg:4", "5, 6" }, + "1, 2, 5, 6" + )] + [TestCase( + "1, 2", + new[] { "3, 4", "Reorg:4", "Reorg:3", "5, 6" }, + "1, 2, 5, 6", + Ignore = "Subsequent reverse reorgs are not supported." + )] + [TestCase( + "1, 2", + new[] { "3, 4", "Reorg:3", "5, 6", "Reorg:5", "6, 7" }, + "1, 2, 6, 7" + )] + [TestCase( + "1, 2, 3", + new[] { "4, 5", "Truncate:4", "6, 7" }, + "5, 6, 7" + )] + [TestCase( + "1, 2, 3", + new[] { "4, 5", "Truncate:3", "6, 7" }, + "4, 5, 6, 7" + )] + [TestCase( + "1, 2, 3", + new[] { "4, 5", "Truncate:3", "Truncate:4", "6, 7" }, + "5, 6, 7" + )] + [TestCase( + "1, 2, 3", + new[] { "4, 5", "Truncate:4", "Truncate:3", "6, 7" }, + "5, 6, 7" + )] + [TestCase( + "1, 2, 3", + new[] { "4, 5", "Truncate:3", "6, 7", "Truncate:6" }, + "7" + )] + public void FullMergeForward(string? existing, string[] operands, string expected) + { + LogIndexStorage.MergeOperator op = CreateOperator(); + CreateEnumerator(Serialize(existing), operands.Select(Serialize).ToArray(), out RocksDbMergeEnumerator enumerator); + + byte[] key = GenerateKey(Address.Size, isBackward: false); + using ArrayPoolList merged = op.FullMerge(key, enumerator); + Assert.That( + Deserialize(merged?.ToArray()), + Is.EqualTo(expected.Split(',').Select(int.Parse).ToArray()) + ); + } + + [TestCase( + null, + new[] { "4", "3", "2, 1" }, + "4, 3, 2, 1" + )] + [TestCase( + "4", + new[] { "3, 2", "1" }, + "4, 3, 2, 1" + )] + [TestCase( + "7, 6, 5", + new[] { "4, 3", "Truncate:4", "2, 1" }, + "3, 2, 1" + )] + [TestCase( + "7, 6, 5", + new[] { "4, 3", "Truncate:5", "2, 1" }, + "4, 3, 2, 1" + )] + [TestCase( + "7, 6, 5", + new[] { "4, 3", "Truncate:5", "Truncate:4", "2, 1" }, + "3, 2, 1" + )] + [TestCase( + "7, 6, 5", + new[] { "4, 3", "Truncate:4", "Truncate:5", "2, 1" }, + "3, 2, 1" + )] + [TestCase( + "7, 6, 5", + new[] { "4, 3", "Truncate:5", "2, 1", "Truncate:2" }, + "1" + )] + public void FullMergeBackward(string? existing, string[] operands, string expected) + { + LogIndexStorage.MergeOperator op = CreateOperator(); + CreateEnumerator(Serialize(existing), operands.Select(Serialize).ToArray(), out RocksDbMergeEnumerator enumerator); + + byte[] key = GenerateKey(Address.Size, isBackward: true); + ArrayPoolList merged = op.FullMerge(key, enumerator); + Assert.That( + Deserialize(merged?.ToArray()), + Is.EqualTo(expected.Split(',').Select(int.Parse).ToArray()) + ); + } + + [TestCase( + new[] { "1", "2", "3", "4" }, + "1, 2, 3, 4" + )] + [TestCase( + new[] { "1", "2, 3", "4" }, + "1, 2, 3, 4" + )] + public void PartialMergeForward(string[] operands, string expected) + { + LogIndexStorage.MergeOperator op = CreateOperator(); + CreateEnumerator(null, operands.Select(Serialize).ToArray(), out RocksDbMergeEnumerator enumerator); + + byte[] key = GenerateKey(Address.Size, isBackward: false); + using ArrayPoolList merged = op.PartialMerge(key, enumerator); + Assert.That( + Deserialize(merged?.ToArray()), + Is.EqualTo(expected.Split(',').Select(int.Parse).ToArray()) + ); + } + + [TestCase( + new[] { "4", "3", "2", "1" }, + "4, 3, 2, 1" + )] + [TestCase( + new[] { "4", "3, 2", "1" }, + "4, 3, 2, 1" + )] + public void PartialMergeBackward(string[] operands, string expected) + { + LogIndexStorage.MergeOperator op = CreateOperator(); + CreateEnumerator(null, operands.Select(Serialize).ToArray(), out RocksDbMergeEnumerator enumerator); + + byte[] key = GenerateKey(Address.Size, isBackward: true); + using ArrayPoolList merged = op.PartialMerge(key, enumerator); + Assert.That( + Deserialize(merged?.ToArray()), + Is.EqualTo(expected.Split(',').Select(int.Parse).ToArray()) + ); + } + + [OneTimeTearDown] + public static void OneTimeTearDown() => Handles.ForEach(h => h.Free()); + + private static readonly LogIndexStorage.ICompressor Compressor = new LogIndexStorage.NoOpCompressor(); + + private static readonly ConcurrentHashSet Handles = []; + + private static LogIndexStorage.MergeOperator CreateOperator() + { + ILogIndexStorage storage = Substitute.For(); + return new(storage, Compressor, 0); + } + + private static void CreateEnumerator(byte[]? existingValue, byte[][] operands, out RocksDbMergeEnumerator enumerator) + { + var operandsPtrs = new IntPtr[operands.Length]; + var operandsLengths = operands.Select(x => (long)x.Length).ToArray(); + + for (int i = 0; i < operands.Length; i++) + { + var handle = GCHandle.Alloc(operands[i], GCHandleType.Pinned); + Handles.Add(handle); + + operandsPtrs[i] = handle.AddrOfPinnedObject(); + operandsLengths[i] = operands[i].Length; + } + + enumerator = existingValue is null + ? new(Span.Empty, false, operandsPtrs, operandsLengths) + : new(existingValue, true, operandsPtrs, operandsLengths); + } + + private static byte[] GenerateKey(int prefixSize, bool isBackward) => Random.Shared + .NextBytes(prefixSize + LogIndexStorage.BlockNumberSize) + .Concat(isBackward ? LogIndexStorage.Postfix.BackwardMerge : LogIndexStorage.Postfix.ForwardMerge) + .ToArray(); + + private static byte[]? Serialize(string? input) => + input is null ? null : Bytes.Concat(input.Split(',').Select(s => s.Trim()).Select(s => s switch + { + _ when int.TryParse(s, out int blockNum) => blockNum.ToLittleEndianByteArray(), + _ when TryParseMergeOp(s, out Span op) => op.ToArray(), + _ => throw new FormatException($"Invalid operand: \"{input}\".") + }).ToArray()); + + private static bool TryParseMergeOp(string input, out Span bytes) + { + bytes = default; + + var parts = input.Split(":"); + if (parts.Length != 2) return false; + + if (!Enum.TryParse(parts[0], out LogIndexStorage.MergeOp op)) return false; + if (!int.TryParse(parts[1], out int blockNum)) return false; + + var buffer = new byte[LogIndexStorage.MergeOps.Size]; + bytes = LogIndexStorage.MergeOps.Create(op, blockNum, buffer); + return true; + } + + private static int[]? Deserialize(byte[]? input) => input is null ? null : MemoryMarshal.Cast(input).ToArray(); +} diff --git a/src/Nethermind/Nethermind.Db/CompressingDb.cs b/src/Nethermind/Nethermind.Db/CompressingDb.cs index 881b839f25bb..301da2f7b1d4 100644 --- a/src/Nethermind/Nethermind.Db/CompressingDb.cs +++ b/src/Nethermind/Nethermind.Db/CompressingDb.cs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using Nethermind.Core; +using Nethermind.Core.Buffers; using Nethermind.Core.Extensions; namespace Nethermind.Db @@ -18,75 +20,55 @@ public static class KeyValueStoreCompressingExtensions /// A wrapped db. public static IDb WithEOACompressed(this IDb @this) => new EOACompressingDb(@this); - private class EOACompressingDb : IDb, ITunableDb + // TODO: consider wrapping IDbWithSpan to make the read with a span, with no alloc for reading? + private class EOACompressingDb(IDb wrapped) : IDb, ITunableDb { - private readonly IDb _wrapped; - - public EOACompressingDb(IDb wrapped) - { - // TODO: consider wrapping IDbWithSpan to make the read with a span, with no alloc for reading? - _wrapped = wrapped; - } - public byte[]? this[ReadOnlySpan key] { - get => Decompress(_wrapped[key]); - set => _wrapped[key] = Compress(value); + get => Decompress(wrapped[key]); + set => wrapped[key] = Compress(value); } - public IWriteBatch StartWriteBatch() => new WriteBatch(_wrapped.StartWriteBatch()); + public IWriteBatch StartWriteBatch() => new WriteBatch(wrapped.StartWriteBatch()); - private class WriteBatch : IWriteBatch + private class WriteBatch(IWriteBatch wrapped) : IWriteBatch { - private readonly IWriteBatch _wrapped; - - public WriteBatch(IWriteBatch wrapped) => _wrapped = wrapped; + public void Dispose() => wrapped.Dispose(); - public void Dispose() => _wrapped.Dispose(); - - public void Clear() => _wrapped.Clear(); + public void Clear() => wrapped.Clear(); public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - => _wrapped.Set(key, Compress(value), flags); + => wrapped.Set(key, Compress(value), flags); public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - _wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags); - } + => wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags); public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - throw new InvalidOperationException("EOA compressing DB does not support merging"); - } + => throw new InvalidOperationException("EOA compressing DB does not support merging"); - public bool PreferWriteByArray => _wrapped.PreferWriteByArray; + public bool PreferWriteByArray => wrapped.PreferWriteByArray; public byte[]? this[ReadOnlySpan key] { - set => _wrapped[key] = Compress(value); + set => wrapped[key] = Compress(value); } } - /// /// The end of rlp of an EOA account, an empty and an empty . /// - private static ReadOnlySpan EmptyCodeHashStorageRoot => new byte[] - { + private static ReadOnlySpan EmptyCodeHashStorageRoot => + [ 160, 86, 232, 31, 23, 27, 204, 85, 166, 255, 131, 69, 230, 146, 192, 248, 110, 91, 72, 224, 27, 153, 108, 173, 192, 1, 98, 47, 181, 227, 99, 180, 33, 160, 197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182, 83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112 - }; + ]; private const byte PreambleLength = 1; private const byte PreambleIndex = 0; private const byte PreambleValue = 0; - private static byte[]? Compress(byte[]? bytes) - { - if (bytes is null) return null; - return Compress(bytes, stackalloc byte[bytes.Length]).ToArray(); - } + private static byte[]? Compress(byte[]? bytes) => bytes is null ? null : Compress(bytes, stackalloc byte[bytes.Length]).ToArray(); private static ReadOnlySpan Compress(ReadOnlySpan bytes, Span compressed) { @@ -125,55 +107,56 @@ private static ReadOnlySpan Compress(ReadOnlySpan bytes, Span return decompressed; } - public void Dispose() => _wrapped.Dispose(); + public void Dispose() => wrapped.Dispose(); - public string Name => _wrapped.Name; + public string Name => wrapped.Name; public KeyValuePair[] this[byte[][] keys] => throw new NotImplementedException(); - public IEnumerable> GetAll(bool ordered = false) => _wrapped.GetAll(ordered) + public IEnumerable> GetAll(bool ordered = false) => wrapped.GetAll(ordered) .Select(static kvp => new KeyValuePair(kvp.Key, Decompress(kvp.Value))); public IEnumerable GetAllKeys(bool ordered = false) => - _wrapped.GetAllKeys(ordered); + wrapped.GetAllKeys(ordered); public IEnumerable GetAllValues(bool ordered = false) => - _wrapped.GetAllValues(ordered).Select(Decompress); + wrapped.GetAllValues(ordered).Select(Decompress); - public void Remove(ReadOnlySpan key) => _wrapped.Remove(key); + public void Remove(ReadOnlySpan key) => wrapped.Remove(key); - public bool KeyExists(ReadOnlySpan key) => _wrapped.KeyExists(key); + public bool KeyExists(ReadOnlySpan key) => wrapped.KeyExists(key); - public void Flush(bool onlyWal) => _wrapped.Flush(onlyWal); + public void Flush(bool onlyWal) => wrapped.Flush(onlyWal); - public void Clear() => _wrapped.Clear(); + public void Clear() => wrapped.Clear(); - public IDbMeta.DbMetric GatherMetric() => _wrapped.GatherMetric(); + public IDbMeta.DbMetric GatherMetric() => wrapped.GatherMetric(); public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) - => _wrapped.Set(key, Compress(value), flags); + => wrapped.Set(key, Compress(value), flags); public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - => Decompress(_wrapped.Get(key, flags)); - + => Decompress(wrapped.Get(key, flags)); - public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) - { - _wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags); - } + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None) => + wrapped.PutSpan(key, Compress(value, stackalloc byte[value.Length]), flags); - public Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - { + public Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => // Can't properly implement span for reading. As the decompressed span is different from the span // from DB, it would crash on DangerouslyReleaseMemory. - return Decompress(Get(key, flags)); + Decompress(Get(key, flags)); + + public MemoryManager? GetOwnedMemory(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + { + byte[]? data = Decompress(Get(key, flags)); + return data is null or { Length: 0 } ? null : new ArrayMemoryManager(data); } - public bool PreferWriteByArray => _wrapped.PreferWriteByArray; + public bool PreferWriteByArray => wrapped.PreferWriteByArray; public void Tune(ITunableDb.TuneType type) { - if (_wrapped is ITunableDb tunable) + if (wrapped is ITunableDb tunable) tunable.Tune(type); } } diff --git a/src/Nethermind/Nethermind.Db/DbNames.cs b/src/Nethermind/Nethermind.Db/DbNames.cs index 2030be8e2bdb..9dd16b3ddc01 100644 --- a/src/Nethermind/Nethermind.Db/DbNames.cs +++ b/src/Nethermind/Nethermind.Db/DbNames.cs @@ -20,5 +20,6 @@ public static class DbNames public const string DiscoveryNodes = "discoveryNodes"; public const string DiscoveryV5Nodes = "discoveryV5Nodes"; public const string PeersDb = "peers"; + public const string LogIndex = "logIndex"; } } diff --git a/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs b/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs index 32979c963832..cac0113d670a 100755 --- a/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs +++ b/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.Threading; using Nethermind.Core; @@ -71,6 +72,17 @@ public Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadF return value; } + public MemoryManager? GetOwnedMemory(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + { + MemoryManager? memoryManager = _currentDb.GetOwnedMemory(key, flags); + if (memoryManager is not null && _pruningContext?.DuplicateReads == true && (flags & ReadFlags.SkipDuplicateRead) == 0) + { + Duplicate(_pruningContext.CloningDb, key, memoryManager.GetSpan(), WriteFlags.None); + } + + return memoryManager; + } + public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { _currentDb.Set(key, value, flags); // we are writing to the main DB @@ -143,6 +155,8 @@ public void Remove(ReadOnlySpan key) public bool KeyExists(ReadOnlySpan key) => _currentDb.KeyExists(key); + public void DangerousReleaseMemory(in ReadOnlySpan span) => _currentDb.DangerousReleaseMemory(span); + // inner DB's can be deleted in the future and // we cannot expose a DB that will potentially be later deleted public IDb Innermost => this; diff --git a/src/Nethermind/Nethermind.Db/IColumnsDb.cs b/src/Nethermind/Nethermind.Db/IColumnsDb.cs index 5ffddced6ab9..e7ec61e8fe27 100644 --- a/src/Nethermind/Nethermind.Db/IColumnsDb.cs +++ b/src/Nethermind/Nethermind.Db/IColumnsDb.cs @@ -19,6 +19,7 @@ public interface IColumnsDb : IDbMeta, IDisposable public interface IColumnsWriteBatch : IDisposable { IWriteBatch GetColumnBatch(TKey key); + void Clear(); } diff --git a/src/Nethermind/Nethermind.Db/IMergeOperator.cs b/src/Nethermind/Nethermind.Db/IMergeOperator.cs index 7df2946bd71f..3a2367636fd9 100644 --- a/src/Nethermind/Nethermind.Db/IMergeOperator.cs +++ b/src/Nethermind/Nethermind.Db/IMergeOperator.cs @@ -3,12 +3,16 @@ using System; using Nethermind.Core.Collections; +using Nethermind.Db.LogIndex; namespace Nethermind.Db; public interface IMergeOperator { string Name { get; } + LogIndexUpdateStats Stats { get; } + LogIndexUpdateStats GetAndResetStats(); + ArrayPoolList? FullMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator); ArrayPoolList? PartialMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator); } diff --git a/src/Nethermind/Nethermind.Db/InMemoryColumnBatch.cs b/src/Nethermind/Nethermind.Db/InMemoryColumnBatch.cs index 9cff6e2fa957..45575158a8d0 100644 --- a/src/Nethermind/Nethermind.Db/InMemoryColumnBatch.cs +++ b/src/Nethermind/Nethermind.Db/InMemoryColumnBatch.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Nethermind.Core; +using Nethermind.Core.Collections; namespace Nethermind.Db { @@ -23,6 +24,14 @@ public IWriteBatch GetColumnBatch(TKey key) return writeBatch; } + public void Clear() + { + foreach (IWriteBatch batch in _underlyingBatch) + { + batch.Clear(); + } + } + public void Dispose() { foreach (IWriteBatch batch in _underlyingBatch) diff --git a/src/Nethermind/Nethermind.Db/LogIndex/AverageStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/AverageStats.cs new file mode 100644 index 000000000000..ba679987db6e --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/AverageStats.cs @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading; + +namespace Nethermind.Db.LogIndex; + +/// +/// Aggregates average of multiple incrementally-added values. +/// +public class AverageStats +{ + private long _total; + private int _count; + + public void Include(long value) + { + Interlocked.Add(ref _total, value); + Interlocked.Increment(ref _count); + } + + public double Average => _count == 0 ? 0 : (double)_total / _count; + + public override string ToString() => $"{Average:F2} ({_count:N0})"; + + public void Combine(AverageStats stats) + { + _total += stats._total; + _count += stats._count; + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/BlockReceipts.cs b/src/Nethermind/Nethermind.Db/LogIndex/BlockReceipts.cs new file mode 100644 index 000000000000..332c17e88ea2 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/BlockReceipts.cs @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Db.LogIndex; + +public readonly record struct BlockReceipts(int BlockNumber, TxReceipt[] Receipts); diff --git a/src/Nethermind/Nethermind.Db/LogIndex/CompactingStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/CompactingStats.cs new file mode 100644 index 000000000000..2a2b73718dcc --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/CompactingStats.cs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Db.LogIndex; + +public class CompactingStats +{ + public ExecTimeStats Total { get; set; } = new(); + + public void Combine(CompactingStats other) + { + Total.Combine(other.Total); + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/Compactor.cs b/src/Nethermind/Nethermind.Db/LogIndex/Compactor.cs new file mode 100644 index 000000000000..03561369a2dc --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/Compactor.cs @@ -0,0 +1,203 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using Nethermind.Logging; + +[assembly: InternalsVisibleTo("Nethermind.Db.Test")] +namespace Nethermind.Db.LogIndex; + +partial class LogIndexStorage +{ + /// + /// Periodically forces background log index compaction for every N added blocks. + /// + internal class Compactor : ICompactor + { + private int? _lastAtMin; + private int? _lastAtMax; + + private CompactingStats _stats = new(); + private readonly ILogIndexStorage _storage; + private readonly IDbMeta _rootDb; + private readonly ILogger _logger; + private readonly int _compactionDistance; + + private readonly CancellationTokenSource _cts = new(); + + /// + /// Bounded(1) compaction work queue consumed by .
+ /// null — fire-and-forget compaction enqueued by ;
+ /// not null — caller-awaitable compaction enqueued by . + ///
+ private readonly Channel _channel = Channel.CreateBounded(1); + + private volatile TaskCompletionSource? _pendingForcedCompaction; + private readonly Task _compactionTask; + + public Compactor(ILogIndexStorage storage, IDbMeta rootDb, ILogger logger, int compactionDistance) + { + _storage = storage; + _rootDb = rootDb; + _logger = logger; + + if (compactionDistance < 1) throw new ArgumentException("Compaction distance must be a positive value.", nameof(compactionDistance)); + _compactionDistance = compactionDistance; + + _lastAtMin = storage.MinBlockNumber; + _lastAtMax = storage.MaxBlockNumber; + + _compactionTask = DoCompactAsync(); + } + + public CompactingStats GetAndResetStats() => Interlocked.Exchange(ref _stats, new()); + + // Not thread-safe + public bool TryEnqueue() + { + if (_cts.IsCancellationRequested) + return false; + + _lastAtMin ??= _storage.MinBlockNumber; + _lastAtMax ??= _storage.MaxBlockNumber; + + var uncompacted = 0; + if (_storage.MinBlockNumber is { } storageMin && storageMin < _lastAtMin) + uncompacted += _lastAtMin.Value - storageMin; + if (_storage.MaxBlockNumber is { } storageMax && storageMax > _lastAtMax) + uncompacted += storageMax - _lastAtMax.Value; + + if (uncompacted < _compactionDistance) + return false; + + if (!_channel.Writer.TryWrite(null)) + return false; + + _lastAtMin = _storage.MinBlockNumber; + _lastAtMax = _storage.MaxBlockNumber; + return true; + } + + public async Task StopAsync() + { + await _cts.CancelAsync(); + _channel.Writer.TryComplete(); + await _compactionTask; + } + + public async Task ForceAsync() + { + // Coalesce concurrent calls — all callers share a single compaction + TaskCompletionSource tcs = new(TaskCreationOptions.RunContinuationsAsynchronously); + TaskCompletionSource? existing = Interlocked.CompareExchange(ref _pendingForcedCompaction, tcs, null); + + if (existing is not null) + { + await existing.Task; + return _stats; + } + + try + { + await _channel.Writer.WriteAsync(tcs, _cts.Token); + } + catch (Exception ex) + { + Interlocked.CompareExchange(ref _pendingForcedCompaction, null, tcs); + if (ex is OperationCanceledException) + tcs.TrySetCanceled(); + else + tcs.TrySetException(ex); + + throw; + } + + await tcs.Task; + return _stats; + } + + private async Task DoCompactAsync() + { + CancellationToken cancellation = _cts.Token; + try + { + await foreach (TaskCompletionSource? tcs in _channel.Reader.ReadAllAsync(cancellation)) + { + try + { + if (_logger.IsInfo) _logger.Info($"Log index: compaction started, DB size: {_storage.GetDbSize()}"); + + var timestamp = Stopwatch.GetTimestamp(); + _rootDb.Compact(); + + TimeSpan elapsed = Stopwatch.GetElapsedTime(timestamp); + _stats.Total.Include(elapsed); + + if (_logger.IsInfo) _logger.Info($"Log index: compaction ended in {elapsed}, DB size: {_storage.GetDbSize()}"); + + tcs?.TrySetResult(); + } + catch (OperationCanceledException) + { + tcs?.TrySetCanceled(); + } + catch (Exception ex) + { + tcs?.TrySetException(ex); + (_storage as LogIndexStorage)?.OnBackgroundError(ex); + + await _cts.CancelAsync(); + _channel.Writer.TryComplete(); + + break; + } + finally + { + if (tcs is not null) + Interlocked.CompareExchange(ref _pendingForcedCompaction, null, tcs); + } + } + } + catch (OperationCanceledException) + { + if (_logger.IsDebug) _logger.Debug("Log index: compaction loop canceled"); + } + finally + { + while (_channel.Reader.TryRead(out TaskCompletionSource? remaining) && remaining is not null) + { + remaining.TrySetCanceled(); + Interlocked.CompareExchange(ref _pendingForcedCompaction, null, remaining); + } + } + } + + public void Dispose() + { + _cts.Dispose(); + _channel.Writer.TryComplete(); + } + } + + private class NoOpCompactor : ICompactor + { + public CompactingStats GetAndResetStats() => new(); + public bool TryEnqueue() => false; + public Task StopAsync() => Task.CompletedTask; + public Task ForceAsync() => Task.FromResult(new CompactingStats()); + public void Dispose() { } + } + + internal interface ICompactor : IDisposable + { + CompactingStats GetAndResetStats(); + bool TryEnqueue(); + Task StopAsync(); + Task ForceAsync(); + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/CompressionAlgorithm.cs b/src/Nethermind/Nethermind.Db/LogIndex/CompressionAlgorithm.cs new file mode 100644 index 000000000000..3c0a3b83280b --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/CompressionAlgorithm.cs @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using static Nethermind.TurboPForBindings.TurboPFor; + +namespace Nethermind.Db.LogIndex; + +partial class LogIndexStorage +{ + /// + /// Represents compression algorithm to be used by log index. + /// + public class CompressionAlgorithm( + string name, + CompressionAlgorithm.CompressFunc compressionFunc, + CompressionAlgorithm.DecompressFunc decompressionFunc + ) + { + public delegate nuint CompressFunc(ReadOnlySpan @in, nuint n, Span @out); + public delegate nuint DecompressFunc(ReadOnlySpan @in, nuint n, Span @out); + + private static readonly Dictionary SupportedMap = new(); + + public static IReadOnlyDictionary Supported => SupportedMap; + + public static KeyValuePair Best => + SupportedMap.TryGetValue(nameof(p4nd1enc256v32), out CompressionAlgorithm p256) + ? KeyValuePair.Create(nameof(p4nd1enc256v32), p256) + : KeyValuePair.Create(nameof(p4nd1enc128v32), SupportedMap[nameof(p4nd1enc128v32)]); + + static CompressionAlgorithm() + { + SupportedMap.Add( + nameof(p4nd1enc128v32), + new(nameof(p4nd1enc128v32), p4nd1enc128v32, p4nd1dec128v32) + ); + + if (Supports256Blocks) + { + SupportedMap.Add( + nameof(p4nd1enc256v32), + new(nameof(p4nd1enc256v32), p4nd1enc256v32, p4nd1dec256v32) + ); + } + } + + public string Name => name; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public nuint Compress(ReadOnlySpan @in, nuint n, Span @out) => compressionFunc(@in, n, @out); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public nuint Decompress(ReadOnlySpan @in, nuint n, Span @out) => decompressionFunc(@in, n, @out); + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/Compressor.cs b/src/Nethermind/Nethermind.Db/LogIndex/Compressor.cs new file mode 100644 index 000000000000..3d6bf0ae947c --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/Compressor.cs @@ -0,0 +1,208 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; +using Nethermind.Core; +using Nethermind.Core.Extensions; + +namespace Nethermind.Db.LogIndex; + +partial class LogIndexStorage +{ + /// + /// Does background compression for keys with the number of blocks above the threshold. + /// + /// + /// Consumes "transient" keys with value being too big (see ) + /// from and performs compression in the background.
+ /// Can utilize multiple threads, as per . + /// + /// + /// For each "transient" key in the queue performs the following: + /// + /// reads the latest (uncompressed) value from the database; + /// truncates potentially reorgable blocks from the sequence (as compressed values become immutable); + /// reverts sequence if needed - so the new value is always in ascending order; + /// compresses the sequence using specified TurboPFor ; + /// stores the compressed value at a new pair using {address-or-topic} || ({first-block-number-in-the-sequence} + 1) as a key; + /// queues truncation for the "transient" key via - to remove finalized blocks from the old sequence. + /// + /// + /// Last 2 operations are done via a single to maintain data consistency. + ///
+ private class Compressor : ICompressor + { + private readonly int _minLengthToCompress; + + // Used instead of a channel to prevent duplicates + private readonly ConcurrentDictionary _compressQueue = new(Bytes.EqualityComparer); + private readonly ConcurrentDictionary.AlternateLookup> _compressQueueLookup; + private readonly LogIndexStorage _storage; + private readonly ActionBlock<(int?, byte[])> _processing; + private readonly ManualResetEventSlim _startEvent = new(false); + private readonly ManualResetEventSlim _queueEmptyEvent = new(true); + + private int _processingCount; + private PostMergeProcessingStats _stats = new(); + + public PostMergeProcessingStats GetAndResetStats() + { + _stats.QueueLength = _processing.InputCount; + return Interlocked.Exchange(ref _stats, new()); + } + + public Compressor(LogIndexStorage storage, int compressionDistance, int parallelism) + { + _compressQueueLookup = _compressQueue.GetAlternateLookup>(); + _storage = storage; + + _minLengthToCompress = compressionDistance * BlockNumberSize; + + if (parallelism < 1) throw new ArgumentException("Compression parallelism degree must be a positive value.", nameof(parallelism)); + _processing = new(x => CompressValue(x.Item1, x.Item2), new() { MaxDegreeOfParallelism = parallelism, BoundedCapacity = 10_000 }); + } + + public bool TryEnqueue(int? topicIndex, ReadOnlySpan dbKey, ReadOnlySpan dbValue) + { + if (dbValue.Length < _minLengthToCompress) + return false; + + if (_compressQueueLookup.TryGetValue(dbKey, out _)) + return false; + + byte[] dbKeyArr = dbKey.ToArray(); + if (!_compressQueue.TryAdd(dbKeyArr, true)) + return false; + + if (_processing.Post((topicIndex, dbKeyArr))) + return true; + + _compressQueue.TryRemove(dbKeyArr, out _); + return false; + } + + public async Task EnqueueAsync(int? topicIndex, byte[] dbKey) + { + await _processing.SendAsync((topicIndex, dbKey)); + _queueEmptyEvent.Reset(); + } + + public Task WaitUntilEmptyAsync(TimeSpan waitTime, CancellationToken cancellationToken) => + _queueEmptyEvent.WaitHandle.WaitOneAsync(waitTime, cancellationToken); + + private void CompressValue(int? topicIndex, byte[] dbKey) + { + if (_storage.HasBackgroundError) + return; + + Interlocked.Increment(ref _processingCount); + + try + { + _startEvent.Wait(); + + if (_storage.HasBackgroundError) + return; + + long execTimestamp = Stopwatch.GetTimestamp(); + IDb db = _storage.GetDb(topicIndex); + + long timestamp = Stopwatch.GetTimestamp(); + Span dbValue = db.Get(dbKey); + _stats.DBReading.Include(Stopwatch.GetElapsedTime(timestamp)); + + // Do not compress blocks that can be reorged, as compressed data is immutable + if (!UseBackwardSyncFor(dbKey)) + dbValue = _storage.RemoveReorgableBlocks(dbValue); + + if (dbValue.Length < _minLengthToCompress) + return; + + int truncateBlock = ReadLastBlockNumber(dbValue); + + ReverseBlocksIfNeeded(dbValue); + + int postfixBlock = ReadBlockNumber(dbValue); + + ReadOnlySpan key = ExtractKey(dbKey); + Span dbKeyComp = stackalloc byte[key.Length + BlockNumberSize]; + key.CopyTo(dbKeyComp); + WriteKeyBlockNumber(dbKeyComp[key.Length..], postfixBlock); + + timestamp = Stopwatch.GetTimestamp(); + dbValue = _storage.CompressDbValue(dbKey, dbValue); + _stats.CompressingValue.Include(Stopwatch.GetElapsedTime(timestamp)); + + // Put compressed value at a new key and clear the uncompressed one + timestamp = Stopwatch.GetTimestamp(); + using (IWriteBatch batch = db.StartWriteBatch()) + { + Span truncateOp = MergeOps.Create(MergeOp.Truncate, truncateBlock, stackalloc byte[MergeOps.Size]); + batch.PutSpan(dbKeyComp, dbValue); + batch.Merge(dbKey, truncateOp); + } + + _stats.DBSaving.Include(Stopwatch.GetElapsedTime(timestamp)); + + Interlocked.Increment(ref topicIndex is null ? ref _stats.CompressedAddressKeys : ref _stats.CompressedTopicKeys); + _stats.Total.Include(Stopwatch.GetElapsedTime(execTimestamp)); + } + catch (Exception ex) + { + _storage.OnBackgroundError(ex); + } + finally + { + _compressQueue.TryRemove(dbKey, out _); + + int processingCount = Interlocked.Decrement(ref _processingCount); + + if (_processing.InputCount == 0 && processingCount == 0) + _queueEmptyEvent.Set(); + } + } + + public void Start() => _startEvent.Set(); + + public Task StopAsync() + { + _processing.Complete(); + return _processing.Completion; // Wait for the compression queue to finish + } + + public void Dispose() + { + _startEvent.Dispose(); + _queueEmptyEvent.Dispose(); + } + } + + public sealed class NoOpCompressor : ICompressor + { + private PostMergeProcessingStats Stats { get; } = new(); + public PostMergeProcessingStats GetAndResetStats() => Stats; + public bool TryEnqueue(int? topicIndex, ReadOnlySpan dbKey, ReadOnlySpan dbValue) => false; + public Task EnqueueAsync(int? topicIndex, byte[] dbKey) => Task.CompletedTask; + public Task WaitUntilEmptyAsync(TimeSpan waitTime, CancellationToken cancellationToken) => Task.CompletedTask; + public void Start() { } + public Task StopAsync() => Task.CompletedTask; + public void Dispose() { } + } + + public interface ICompressor : IDisposable + { + PostMergeProcessingStats GetAndResetStats(); + + bool TryEnqueue(int? topicIndex, ReadOnlySpan dbKey, ReadOnlySpan dbValue); + Task EnqueueAsync(int? topicIndex, byte[] dbKey); + Task WaitUntilEmptyAsync(TimeSpan waitTime = default, CancellationToken cancellationToken = default); + + void Start(); + Task StopAsync(); + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/DisabledLogIndexStorage.cs b/src/Nethermind/Nethermind.Db/LogIndex/DisabledLogIndexStorage.cs new file mode 100644 index 000000000000..9c483a550533 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/DisabledLogIndexStorage.cs @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Db.LogIndex; + +public sealed class DisabledLogIndexStorage : ILogIndexStorage +{ + public bool Enabled => false; + + public string GetDbSize() => "0 B"; + + public int? MaxBlockNumber => null; + public int? MinBlockNumber => null; + + public IEnumerator GetEnumerator(Address address, int from, int to) => + throw new NotSupportedException(); + + public IEnumerator GetEnumerator(int topicIndex, Hash256 topic, int from, int to) => + throw new NotSupportedException(); + + public LogIndexAggregate Aggregate(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats = null) => + throw new NotSupportedException(); + + public Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null) => + throw new NotSupportedException(); + + public Task RemoveReorgedAsync(BlockReceipts block) => + throw new NotSupportedException(); + + public ValueTask DisposeAsync() => ValueTask.CompletedTask; + public Task StopAsync() => Task.CompletedTask; +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/ExecTimeStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/ExecTimeStats.cs new file mode 100644 index 000000000000..19c00334ee3e --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/ExecTimeStats.cs @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading; + +namespace Nethermind.Db.LogIndex; + +/// +/// Aggregates average and total execution time of multiple executions of the same operation. +/// +public class ExecTimeStats +{ + private long _totalTicks; + private int _count; + + public void Include(TimeSpan elapsed) + { + Interlocked.Add(ref _totalTicks, elapsed.Ticks); + Interlocked.Increment(ref _count); + } + + public TimeSpan Total => TimeSpan.FromTicks(_totalTicks); + public TimeSpan Average => _count == 0 ? TimeSpan.Zero : TimeSpan.FromTicks((long)((double)_totalTicks / _count)); + + private string Format(TimeSpan value) => value switch + { + { TotalDays: >= 1 } x => $"{x.TotalDays:F2}d", + { TotalHours: >= 1 } x => $"{x.TotalHours:F2}h", + { TotalMinutes: >= 1 } x => $"{x.TotalMinutes:F2}m", + { TotalSeconds: >= 1 } x => $"{x.TotalSeconds:F2}s", + { TotalMilliseconds: >= 1 } x => $"{x.TotalMilliseconds:F1}ms", + { TotalMicroseconds: >= 1 } x => $"{x.TotalMicroseconds:F1}μs", + var x => $"{x.TotalNanoseconds:F1}ns" + }; + + public override string ToString() => $"{Format(Average)} ({_count:N0}) [{Format(Total)}]"; + + public void Combine(ExecTimeStats stats) + { + _totalTicks += stats._totalTicks; + _count += stats._count; + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexConfig.cs b/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexConfig.cs new file mode 100644 index 000000000000..53cb3b45148c --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexConfig.cs @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Config; +using Nethermind.TurboPForBindings; + +namespace Nethermind.Db.LogIndex; + +public interface ILogIndexConfig : IConfig +{ + [ConfigItem( + Description = "Whether log index should be enabled.", + DefaultValue = "false" + )] + public bool Enabled { get; set; } + + [ConfigItem( + Description = "Log index is reset on startup if enabled.", + DefaultValue = "false" + )] + public bool Reset { get; set; } + + [ConfigItem( + Description = "Max allowed reorg depth for the index.", + DefaultValue = "64", + HiddenFromDocs = true + )] + public int? MaxReorgDepth { get; set; } + + [ConfigItem( + Description = "Maximum number of blocks with receipts to add to index per iteration.", + DefaultValue = "256", + HiddenFromDocs = true + )] + public int MaxBatchSize { get; set; } + + [ConfigItem( + Description = "Maximum number of batches to queue for aggregation.", + DefaultValue = "16", + HiddenFromDocs = true + )] + public int MaxAggregationQueueSize { get; set; } + + [ConfigItem( + Description = "Maximum number of aggregated batches to queue for inclusion to the index.", + DefaultValue = "16", + HiddenFromDocs = true + )] + public int MaxSavingQueueSize { get; set; } + + [ConfigItem( + Description = "Maximum degree of parallelism for fetching receipts.", + DefaultValue = "Max(ProcessorCount / 2, 1)", + HiddenFromDocs = true + )] + public int MaxReceiptsParallelism { get; set; } + + [ConfigItem( + Description = "Maximum degree of parallelism for aggregating batches.", + DefaultValue = "Max(ProcessorCount / 2, 1)", + HiddenFromDocs = true + )] + public int MaxAggregationParallelism { get; set; } + + [ConfigItem( + Description = "Maximum degree of parallelism for compressing overgrown key values.", + DefaultValue = "Max(ProcessorCount / 2, 1)", + HiddenFromDocs = true + )] + public int MaxCompressionParallelism { get; set; } + + [ConfigItem( + Description = "Minimum number of blocks under a single key to compress.", + DefaultValue = "128", + HiddenFromDocs = true + )] + public int CompressionDistance { get; set; } + + [ConfigItem( + Description = "Number of newly added blocks after which to run DB compaction.", + DefaultValue = "262,144", + HiddenFromDocs = true + )] + public int CompactionDistance { get; set; } + + [ConfigItem( + Description = "Compression algorithm to use for block numbers.", + DefaultValue = nameof(TurboPFor.p4nd1enc256v32) + " if supported, otherwise " + nameof(TurboPFor.p4nd1enc128v32), + HiddenFromDocs = true + )] + string? CompressionAlgorithm { get; set; } + + [ConfigItem( + Description = "Whether to show detailed stats in progress logs.", + DefaultValue = "false", + HiddenFromDocs = true + )] + bool DetailedLogs { get; set; } + + [ConfigItem( + Description = "Whether to verify that eth_getLogs response generated using index matches one generated without.", + DefaultValue = "false", + HiddenFromDocs = true + )] + bool VerifyRpcResponse { get; set; } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexStorage.cs b/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexStorage.cs new file mode 100644 index 000000000000..af084f85c310 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/ILogIndexStorage.cs @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.ServiceStopper; + +namespace Nethermind.Db.LogIndex; + +public interface ILogIndexStorage : IAsyncDisposable, IStoppableService +{ + bool Enabled { get; } + + /// + /// Max block number added to the index. + /// + int? MaxBlockNumber { get; } + + /// + /// Min block number added to the index. + /// + int? MinBlockNumber { get; } + + /// + /// Gets enumerator of block numbers between and + /// where given has occurred. + /// + IEnumerator GetEnumerator(Address address, int from, int to); + + /// + /// Gets enumerator of block numbers between and + /// where given has occurred at the given . + /// + IEnumerator GetEnumerator(int topicIndex, Hash256 topic, int from, int to); + + /// + /// Aggregates receipts from the into in-memory . + /// + LogIndexAggregate Aggregate(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats = null); + + /// + /// Adds receipts from the to the index. + /// + Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null); + + /// + /// Removes reorged from the index. + /// This must be called for each reorged block in a sequential ascending order. + /// + Task RemoveReorgedAsync(BlockReceipts block); + + string GetDbSize(); +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexAggregate.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexAggregate.cs new file mode 100644 index 000000000000..bd0f40280484 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexAggregate.cs @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Linq; +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Db.LogIndex; + +/// +/// Set of in-memory dictionaries mapping each address/topic to a sequence of block numbers +/// from the - range. +/// +public struct LogIndexAggregate(int firstBlockNum, int lastBlockNum) +{ + private Dictionary>? _address; + private Dictionary>[]? _topic; + + public int FirstBlockNum { get; } = firstBlockNum; + public int LastBlockNum { get; } = lastBlockNum; + + public Dictionary> Address => _address ??= new(); + + public Dictionary>[] Topic => _topic ??= Enumerable.Range(0, LogIndexStorage.MaxTopics) + .Select(static _ => new Dictionary>()) + .ToArray(); + + public bool IsEmpty => (_address is null || _address.Count == 0) && (_topic is null || _topic[0].Count == 0); + public int TopicCount => _topic is { Length: > 0 } ? _topic.Sum(static t => t.Count) : 0; + + public LogIndexAggregate(IReadOnlyList batch) : this(batch[0].BlockNumber, batch[^1].BlockNumber) { } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexColumns.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexColumns.cs new file mode 100644 index 000000000000..7492a1aca278 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexColumns.cs @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Db.LogIndex; + +public enum LogIndexColumns +{ + Meta, + Addresses, + Topics0, + Topics1, + Topics2, + Topics3 +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexConfig.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexConfig.cs new file mode 100644 index 000000000000..5208f5ba2003 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexConfig.cs @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Config; + +namespace Nethermind.Db.LogIndex; + +[ConfigCategory(Description = "Configuration of the log index behaviour.")] +public class LogIndexConfig : ILogIndexConfig +{ + public bool Enabled { get; set; } = false; + public bool Reset { get; set; } = false; + + // set from PruningConfig via decorator + public int? MaxReorgDepth { get; set; } + + public int MaxBatchSize { get; set; } = 256; + public int MaxAggregationQueueSize { get; set; } = 16; + public int MaxSavingQueueSize { get; set; } = 16; + + public int MaxReceiptsParallelism { get; set; } = Math.Max(Environment.ProcessorCount / 2, 1); + public int MaxAggregationParallelism { get; set; } = Math.Max(Environment.ProcessorCount / 2, 1); + public int MaxCompressionParallelism { get; set; } = Math.Max(Environment.ProcessorCount / 2, 1); + + public int CompressionDistance { get; set; } = 128; + public int CompactionDistance { get; set; } = 262_144; + + public string? CompressionAlgorithm { get; set; } = LogIndexStorage.CompressionAlgorithm.Best.Key; + + public bool DetailedLogs { get; set; } = false; + public bool VerifyRpcResponse { get; set; } = false; +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexEnumerator.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexEnumerator.cs new file mode 100644 index 000000000000..1eb40242a6ca --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexEnumerator.cs @@ -0,0 +1,153 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections; +using System.Collections.Generic; +using Nethermind.Core; +using Nethermind.Core.Collections; + +namespace Nethermind.Db.LogIndex; + +public partial class LogIndexStorage +{ + // TODO: pre-fetch next value? + /// + /// Enumerates block numbers from for the given key, + /// within the specified from/to range. + /// + public sealed class LogIndexEnumerator : IEnumerator + { + private const int CompletedIndex = int.MinValue; + + private readonly CompressionAlgorithm _compressionAlgorithm; + private readonly byte[] _key; + private readonly int _from; + private readonly int _to; + private readonly ISortedView _view; + + private ArrayPoolList? _value; + private int _index; + + public LogIndexEnumerator(ISortedKeyValueStore db, CompressionAlgorithm compressionAlgorithm, byte[] key, int from, int to) + { + if (from < 0) from = 0; + if (to < from) throw new ArgumentException("To must be greater or equal to from.", nameof(to)); + + _key = key; + (_from, _to) = (from, to); + _compressionAlgorithm = compressionAlgorithm; + + ReadOnlySpan fromKey = CreateDbKey(_key, Postfix.BackwardMerge, stackalloc byte[MaxDbKeyLength]); + ReadOnlySpan toKey = CreateDbKey(_key, Postfix.UpperBound, stackalloc byte[MaxDbKeyLength]); + _view = db.GetViewBetween(fromKey, toKey); + } + + private bool IsWithinRange() + { + int current = Current; + return current >= _from && current <= _to; + } + + public bool MoveNext() => _index != CompletedIndex && (_value is null ? TryStart() : TryMove()); + + private bool TryStart() + { + if (TryStartView()) + { + SetValue(); + _index = FindFromIndex(); + } + else // End immediately + { + _index = CompletedIndex; + return false; + } + + // Shift the view until we can start at `from` + while (Current < _from && _view.MoveNext()) + { + SetValue(); + _index = FindFromIndex(); + } + + // Check if the end of the range is reached + if (!IsWithinRange()) + { + _index = CompletedIndex; + return false; + } + + return true; + } + + private bool TryMove() + { + _index++; + + // Shift the view until we can continue + while (_index >= _value!.Count && _view.MoveNext()) + { + SetValue(); + _index = 0; + } + + // Check if the end of the range is reached + if (!IsWithinRange()) + { + _index = CompletedIndex; + return false; + } + + return true; + } + + private bool TryStartView() + { + ReadOnlySpan startKey = CreateDbKey(_key, _from, stackalloc byte[MaxDbKeyLength]); + + // need to start either just before the startKey + // or at the beginning of a view otherwise + return _view.StartBefore(startKey) || _view.MoveNext(); + } + + private void SetValue() + { + _value?.Dispose(); + + ReadOnlySpan viewValue = _view.CurrentValue; + + if (IsCompressed(viewValue, out var length)) + { + // +1 fixes TurboPFor reading outside of array bounds + _value = new(capacity: length + 1, count: length); + DecompressDbValue(_compressionAlgorithm, viewValue, _value.AsSpan()); + } + else + { + length = viewValue.Length / BlockNumberSize; + _value = new(capacity: length, count: length); + ReadBlockNumbers(viewValue, _value.AsSpan()); + } + + ReverseBlocksIfNeeded(_value.AsSpan()); + } + + private int FindFromIndex() + { + int index = BinarySearch(_value!.AsSpan(), _from); + return index >= 0 ? index : ~index; + } + + public void Reset() => throw new NotSupportedException($"{nameof(LogIndexEnumerator)} can not be reset."); + + public int Current => _value is not null && _index >= 0 && _index < _value.Count ? _value[_index] : -1; + object? IEnumerator.Current => Current; + + public void Dispose() + { + _view.Dispose(); + _value?.Dispose(); + } + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStateException.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStateException.cs new file mode 100644 index 000000000000..113ebfb045b7 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStateException.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Db.LogIndex; + +public class LogIndexStateException(string message, ReadOnlySpan key = default) : Exception(message) +{ + public byte[]? Key { get; } = key.Length == 0 ? null : key.ToArray(); + + public override string Message => Key is null + ? base.Message + : $"{base.Message} (Key: {Convert.ToHexString(Key)})"; +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStorage.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStorage.cs new file mode 100644 index 000000000000..fc8e6a8f73ff --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexStorage.cs @@ -0,0 +1,1006 @@ +using System; +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Logging; + +namespace Nethermind.Db.LogIndex +{ + // TODO: use uint instead of int for block number? + /// + /// Database for log index, mapping addresses/topics to a set of blocks they occur in. + /// + /// + /// + /// + /// Uses 6 column families (see ): + /// + /// 1 for metadata (for now stores only the earliest and the latest added block numbers); + /// 1 for address mappings; + /// 4 for topic mappings (separate 1 for each topic position). + /// + /// + /// + /// + /// Each unique filter (topic/address) has a set of DB mappings filter -> block numbers: + /// + /// + /// 1 for newly coming numbers from backward sync;
+ /// key here is formed as filter || ;
+ /// value is a sequence of concatenated block numbers in strictly descending order using little-endian encoding; + ///
+ /// + /// 1 for newly coming numbers from forward sync;
+ /// key is filter || ;
+ /// value is a sequence of concatenated block numbers in strictly ascending order using little-endian encoding; + ///
+ /// + /// any number of "finalized" mappings, storing block numbers in strictly ascending order, compressed via TurboPFor;
+ /// key is filter || ({first-block-number-in-the-sequence} + 1) with number being encoded in big-endian (for correct RocksDB sorting);
+ /// value is a sequence of block numbers compressed via TurboPFor (see ). + ///
+ ///
+ /// Keys (1) and (2) are called "transient", as their content can change frequently, + /// while keys from (3) - "finalized", as their data is immutable.
+ /// Block sequences are not-intersecting and following a strict order, such that:
+ /// + /// max({backward-sync-numbers}) < any({finalized-numbers}) < min({forward-sync-numbers}) + /// + ///
+ /// + /// + /// New blocks are added in batches in a strictly ascending or descending (backward sync) order without gaps.
+ /// Process is separated into 2 steps to improve parallelization: + /// + /// in-memory dictionary is aggregated, mapping filter to a sequence of block numbers (see ); + /// each dictionary pair is saved to DB via call (see ). + /// + /// Whole block batch is added in a single cross-family .
+ /// Merging always happens to a "transient" (backward- or forward-sync) key, depending on the direction (passed as a parameter during aggregation). + /// After that, is responsible for concatenating sequences into a single uncompressed DB value, + /// and is forming "finalized" compressed values when "transient" sequences grow too big. + ///
+ /// + /// + /// Fetching block numbers for the given filters (see ) + /// is done by iterating keys via (with the provided filter as a prefix), + /// decomposing DB values back into block number sequences, and returning obtained numbers in ascending order. + /// Check for details. + /// + ///
+ public partial class LogIndexStorage : ILogIndexStorage + { + private static class SpecialKey + { + public static readonly byte[] Version = "ver"u8.ToArray(); + public static readonly byte[] MinBlockNum = "min"u8.ToArray(); + public static readonly byte[] MaxBlockNum = "max"u8.ToArray(); + public static readonly byte[] CompressionAlgo = "alg"u8.ToArray(); + } + + public static class Postfix + { + // Any ordered prefix seeking will start on it + public static readonly byte[] BackwardMerge = Enumerable.Repeat((byte)0, BlockNumberSize).ToArray(); + + // Any ordered prefix seeking will end on it + public static readonly byte[] ForwardMerge = Enumerable.Repeat(byte.MaxValue, BlockNumberSize).ToArray(); + + // Exclusive upper bound for iterator seek, so that ForwardMerge will be the last key + public static readonly byte[] UpperBound = Enumerable.Repeat(byte.MaxValue, BlockNumberSize).Concat([byte.MinValue]).ToArray(); + } + + [InlineArray(MaxTopics)] + private struct TopicBatches + { + private IWriteBatch _element; + } + + [InlineArray(MaxTopics + 1)] + private struct AllMergeOperators + { + private IMergeOperator _element; + } + + private ref struct DbBatches : IDisposable + { + private bool _completed; + + private readonly IColumnsWriteBatch _batch; + public IWriteBatch Meta { get; } + public IWriteBatch Address { get; } + public readonly TopicBatches Topics; + + public DbBatches(IColumnsDb rootDb) + { + _batch = rootDb.StartWriteBatch(); + + Meta = _batch.GetColumnBatch(LogIndexColumns.Meta); + Address = _batch.GetColumnBatch(LogIndexColumns.Addresses); + for (var topicIndex = 0; topicIndex < MaxTopics; topicIndex++) + Topics[topicIndex] = _batch.GetColumnBatch(GetColumn(topicIndex)); + } + + // Require explicit Commit call instead of committing on Dispose + public void Commit() + { + if (_completed) return; + _completed = true; + + _batch.Dispose(); + } + + public void Dispose() + { + if (_completed) return; + _completed = true; + + _batch.Clear(); + _batch.Dispose(); + } + } + + private static readonly byte[] VersionBytes = [1]; + + public const int MaxTopics = 4; + + public bool Enabled { get; } + + public const int BlockNumberSize = sizeof(int); + private const int MaxKeyLength = Hash256.Size + 1; // Math.Max(Address.Size, Hash256.Size) + private const int MaxDbKeyLength = MaxKeyLength + BlockNumberSize; + + private static readonly ArrayPool Pool = ArrayPool.Shared; + + private readonly IColumnsDb _rootDb; + private readonly IDb _metaDb; + private readonly IDb _addressDb; + private readonly IDb[] _topicDbs; + + private IEnumerable DBColumns + { + get + { + yield return _metaDb; + yield return _addressDb; + + foreach (IDb topicDb in _topicDbs) + yield return topicDb; + } + } + + private readonly ILogger _logger; + + private readonly int _maxReorgDepth; + + private readonly AllMergeOperators _mergeOperators; + private readonly ICompressor _compressor; + private readonly ICompactor _compactor; + private readonly CompressionAlgorithm _compressionAlgorithm; + + private readonly Lock _rangeInitLock = new(); + + private int? _maxBlock; + private int? _minBlock; + + public int? MaxBlockNumber => _maxBlock; + public int? MinBlockNumber => _minBlock; + + private Exception? _lastBackgroundError; + public bool HasBackgroundError => _lastBackgroundError is not null; + + /// + /// Whether a first batch was already added. + /// + private bool FirstBlockAdded => _minBlock is not null || _maxBlock is not null; + + /// + /// Guarantees / initialization won't be run concurrently. + /// + private readonly SemaphoreSlim _initSemaphore = new(1, 1); + + /// + /// Used for blocking concurrent executions and + /// ensuring the current iteration is completed before stopping/disposing. + /// + private readonly SemaphoreSlim _forwardWriteSemaphore = new(1, 1); + private readonly SemaphoreSlim _backwardWriteSemaphore = new(1, 1); + + private bool _stopped; + private bool _disposed; + + public LogIndexStorage(IDbFactory dbFactory, ILogManager logManager, ILogIndexConfig config) + { + try + { + Enabled = config.Enabled; + + _maxReorgDepth = config.MaxReorgDepth!.Value; + + _logger = logManager.GetClassLogger(); + + _compressor = config.CompressionDistance > 0 + ? new Compressor(this, config.CompressionDistance, config.MaxCompressionParallelism) + : new NoOpCompressor(); + + for (int i = -1; i < MaxTopics; i++) + _mergeOperators[i + 1] = new MergeOperator(this, _compressor, topicIndex: i < 0 ? null : i); + + _rootDb = CreateRootDb(dbFactory, config.Reset); + _metaDb = GetMetaDb(_rootDb); + _addressDb = _rootDb.GetColumnDb(LogIndexColumns.Addresses); + _topicDbs = Enumerable.Range(0, MaxTopics).Select(topicIndex => _rootDb.GetColumnDb(GetColumn(topicIndex))).ToArray(); + + _compactor = config.CompactionDistance > 0 + ? new Compactor(this, _rootDb, _logger, config.CompactionDistance) + : new NoOpCompactor(); + + _compressionAlgorithm = SelectCompressionAlgorithm(config.CompressionAlgorithm); + + (_minBlock, _maxBlock) = (LoadRangeBound(SpecialKey.MinBlockNum), LoadRangeBound(SpecialKey.MaxBlockNum)); + + if (Enabled) + _compressor.Start(); + } + catch // TODO: do not throw errors from constructor? + { + DisposeCore(); + throw; + } + } + + private IColumnsDb CreateRootDb(IDbFactory dbFactory, bool reset) + { + (IColumnsDb root, IDb meta) = CreateDb(); + + if (reset) + return ResetAndCreateNew(root, "Log index: resetting data per configuration..."); + + Span versionBytes = meta.GetSpan(SpecialKey.Version); + try + { + if (versionBytes.IsEmpty) // DB is empty + { + meta.Set(SpecialKey.Version, VersionBytes); + return root; + } + + return versionBytes.SequenceEqual(VersionBytes) + ? root + : ResetAndCreateNew(root, $"Log index: version is incorrect: {versionBytes[0]} <> {VersionBytes[0]}, resetting data..."); + } + finally + { + meta.DangerousReleaseMemory(versionBytes); + } + + IColumnsDb ResetAndCreateNew(IColumnsDb db, string message) + { + if (_logger.IsWarn) + _logger.Warn(message); + + db.Clear(); + + // `Clear` removes the DB folder, need to create a new instance + db.Dispose(); + (db, meta) = CreateDb(); + + meta.Set(SpecialKey.Version, VersionBytes); + return db; + } + + (IColumnsDb root, IDb meta) CreateDb() + { + IColumnsDb db = dbFactory.CreateColumnsDb(new("logIndexStorage", DbNames.LogIndex) + { + ColumnsMergeOperators = Enumerable.Range(-1, MaxTopics + 1).ToDictionary( + topicIndex => $"{GetColumn(topicIndex < 0 ? null : topicIndex)}", + topicIndex => _mergeOperators[topicIndex + 1] + ) + }); + + return (db, GetMetaDb(db)); + } + } + + private CompressionAlgorithm SelectCompressionAlgorithm(string? configAlgoName) + { + CompressionAlgorithm? configAlgo = null; + if (configAlgoName is not null && !CompressionAlgorithm.Supported.TryGetValue(configAlgoName, out configAlgo)) + { + throw new NotSupportedException( + $"Configured compression algorithm ({configAlgoName}) is not supported on this platform." + ); + } + + Span algoBytes = _metaDb.GetSpan(SpecialKey.CompressionAlgo); + string usedAlgoName; + try + { + if (algoBytes.IsEmpty) // DB is empty + { + KeyValuePair selected = configAlgo is not null + ? KeyValuePair.Create(configAlgoName, configAlgo) + : CompressionAlgorithm.Best; + + _metaDb.Set(SpecialKey.CompressionAlgo, Encoding.ASCII.GetBytes(selected.Key)); + return selected.Value; + } + + usedAlgoName = Encoding.ASCII.GetString(algoBytes); + } + finally + { + _metaDb.DangerousReleaseMemory(algoBytes); + } + + if (!CompressionAlgorithm.Supported.TryGetValue(usedAlgoName, out CompressionAlgorithm usedAlgo)) + { + throw new NotSupportedException( + $"Used compression algorithm ({usedAlgoName}) is not supported on this platform. " + + "Log index must be reset to use a different compression algorithm." + ); + } + + configAlgoName ??= usedAlgoName; + if (usedAlgoName != configAlgoName) + { + throw new NotSupportedException( + $"Used compression algorithm ({usedAlgoName}) is different from the one configured ({configAlgoName}). " + + "Log index must be reset to use a different compression algorithm." + ); + } + + return usedAlgo; + } + + private static void ForceMerge(IDb db) + { + // Fetching RocksDB key values forces it to merge corresponding parts + db.GetAllValues().ForEach(static _ => { }); + } + + public Task StopAsync() => StopAsync(acquireLock: true); + + private async Task StopAsync(bool acquireLock) + { + if (Interlocked.Exchange(ref _stopped, true)) + { + return; + } + + if (acquireLock) + { + await _forwardWriteSemaphore.WaitAsync(); + await _backwardWriteSemaphore.WaitAsync(); + } + + try + { + // Disposing RocksDB during any write operation will cause 0xC0000005, so stop them all + await Task.WhenAll( + _compactor.StopAsync(), + _compressor.StopAsync() + ); + + if (_logger.IsInfo) _logger.Info("Log index storage stopped"); + } + finally + { + if (acquireLock) + { + _forwardWriteSemaphore.Release(); + _backwardWriteSemaphore.Release(); + } + } + } + + private void ThrowIfStopped() + { + if (_stopped) + throw new InvalidOperationException("Log index storage is stopped."); + } + + private void OnBackgroundError(Exception error) + { + _lastBackgroundError = error; + + if (_logger.IsError) + _logger.Error($"Error in {typeof(TCaller).Name}", error); + } + + private void ThrowIfHasError() + { + if (_lastBackgroundError is { } error) + ExceptionDispatchInfo.Throw(error); + } + + async ValueTask IAsyncDisposable.DisposeAsync() + { + if (Interlocked.Exchange(ref _disposed, true)) + { + return; + } + + await _forwardWriteSemaphore.WaitAsync(); + await _backwardWriteSemaphore.WaitAsync(); + + await StopAsync(acquireLock: false); + + // No need to free semaphores now + DisposeCore(); + } + + private void DisposeCore() + { + _forwardWriteSemaphore.Dispose(); + _backwardWriteSemaphore.Dispose(); + _compressor?.Dispose(); + _compactor?.Dispose(); + DBColumns?.DisposeItems(); + _rootDb?.Dispose(); + } + + private int? LoadRangeBound(ReadOnlySpan key) + { + Span value = _metaDb.GetSpan(key); + try + { + return !value.IsEmpty ? ReadBlockNumber(value) : null; + } + finally + { + _metaDb.DangerousReleaseMemory(value); + } + } + + private void UpdateRange(int minBlock, int maxBlock, bool isBackwardSync) + { + if (!FirstBlockAdded) + { + using Lock.Scope _ = _rangeInitLock.EnterScope(); // May not be needed, but added for safety + (_minBlock, _maxBlock) = (minBlock, maxBlock); + return; + } + + // Update fields separately for each direction + // so that concurrent different direction sync won't overwrite each other + if (isBackwardSync) _minBlock = minBlock; + else _maxBlock = maxBlock; + } + + private static int SaveRangeBound(IWriteOnlyKeyValueStore dbBatch, byte[] key, int value) + { + Span buffer = stackalloc byte[BlockNumberSize]; + WriteBlockNumber(buffer, value); + dbBatch.PutSpan(key, buffer); + return value; + } + + private (int min, int max) SaveRange(DbBatches batches, int firstBlock, int lastBlock, bool isBackwardSync, bool isReorg = false) + { + int batchMin = Math.Min(firstBlock, lastBlock); + int batchMax = Math.Max(firstBlock, lastBlock); + + int min = _minBlock ?? SaveRangeBound(batches.Meta, SpecialKey.MinBlockNum, batchMin); + int max = _maxBlock ?? SaveRangeBound(batches.Meta, SpecialKey.MaxBlockNum, batchMax); + + if (isBackwardSync) + { + if (isReorg) + throw new ArgumentException("Backwards sync does not support reorgs."); + if (batchMin < _minBlock) + min = SaveRangeBound(batches.Meta, SpecialKey.MinBlockNum, batchMin); + } + else + { + if ((isReorg && batchMax < _maxBlock) || (!isReorg && batchMax > _maxBlock)) + max = SaveRangeBound(batches.Meta, SpecialKey.MaxBlockNum, batchMax); + } + + return (min, max); + } + + private int? GetLastReorgableBlockNumber() => _maxBlock - _maxReorgDepth; + + private static bool IsBlockNewer(int next, int? lastMin, int? lastMax, bool isBackwardSync) => isBackwardSync + ? lastMin is null || next < lastMin + : lastMax is null || next > lastMax; + + private bool IsBlockNewer(int next, bool isBackwardSync) => + IsBlockNewer(next, _minBlock, _maxBlock, isBackwardSync); + + public string GetDbSize() => _rootDb.GatherMetric().Size.SizeToString(useSi: true, addSpace: true); + + public IEnumerator GetEnumerator(Address address, int from, int to) => + GetEnumerator(null, address.Bytes, from, to); + + public IEnumerator GetEnumerator(int topicIndex, Hash256 topic, int from, int to) => + GetEnumerator(topicIndex, topic.BytesToArray(), from, to); + + public IEnumerator GetEnumerator(int? topicIndex, byte[] key, int from, int to) + { + IDb db = GetDb(topicIndex); + ISortedKeyValueStore? sortedDb = db as ISortedKeyValueStore + ?? throw new NotSupportedException($"{db.GetType().Name} DB does not support sorted lookups."); + + return new LogIndexEnumerator(sortedDb, _compressionAlgorithm, key, from, to); + } + + // TODO: discuss potential optimizations + public LogIndexAggregate Aggregate(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats) + { + ThrowIfStopped(); + ThrowIfHasError(); + + if ((!isBackwardSync && !IsSeqAsc(batch)) || (isBackwardSync && !IsSeqDesc(batch))) + throw new ArgumentException($"Unexpected blocks batch order: ({batch[0]} to {batch[^1]})."); + + if (!IsBlockNewer(batch[^1].BlockNumber, isBackwardSync)) + return new(batch); + + long timestamp = Stopwatch.GetTimestamp(); + + LogIndexAggregate aggregate = new(batch); + foreach ((int blockNumber, TxReceipt[] receipts) in batch) + { + if (!IsBlockNewer(blockNumber, isBackwardSync)) + continue; + + stats?.IncrementBlocks(); + stats?.IncrementTx(receipts.Length); + + foreach (TxReceipt receipt in receipts) + { + if (receipt.Logs == null) + continue; + + foreach (LogEntry log in receipt.Logs) + { + stats?.IncrementLogs(); + + List addressBlocks = aggregate.Address.GetOrAdd(log.Address, static _ => new(1)); + + if (addressBlocks.Count == 0 || addressBlocks[^1] != blockNumber) + addressBlocks.Add(blockNumber); + + int topicsLength = Math.Min(log.Topics.Length, MaxTopics); + for (byte topicIndex = 0; topicIndex < topicsLength; topicIndex++) + { + stats?.IncrementTopics(); + + List topicBlocks = aggregate.Topic[topicIndex].GetOrAdd(log.Topics[topicIndex], static _ => new(1)); + + if (topicBlocks.Count == 0 || topicBlocks[^1] != blockNumber) + topicBlocks.Add(blockNumber); + } + } + } + } + + stats?.KeysCount.Include(aggregate.Address.Count + aggregate.TopicCount); + stats?.Aggregating.Include(Stopwatch.GetElapsedTime(timestamp)); + + return aggregate; + } + + private async ValueTask LockRunAsync(SemaphoreSlim semaphore) + { + if (!await semaphore.WaitAsync(TimeSpan.Zero, CancellationToken.None)) + { + ThrowIfStopped(); + throw new InvalidOperationException($"{nameof(LogIndexStorage)} does not support concurrent invocations in the same direction."); + } + } + + public async Task RemoveReorgedAsync(BlockReceipts block) + { + ThrowIfStopped(); + ThrowIfHasError(); + + if (!FirstBlockAdded) + return; + + await LockRunAsync(_forwardWriteSemaphore); + + try + { + RemoveReorgedCore(block); + } + finally + { + _forwardWriteSemaphore.Release(); + } + } + + private void RemoveReorgedCore(BlockReceipts block) + { + const bool isBackwardSync = false; + + using DbBatches batches = new(_rootDb); + + Span keyBuffer = stackalloc byte[MaxDbKeyLength]; + Span dbValue = MergeOps.Create(MergeOp.Reorg, block.BlockNumber, stackalloc byte[MergeOps.Size]); + + foreach (TxReceipt receipt in block.Receipts) + { + foreach (LogEntry log in receipt.Logs ?? []) + { + ReadOnlySpan addressKey = CreateMergeDbKey(log.Address.Bytes, keyBuffer, isBackwardSync: false); + batches.Address.Merge(addressKey, dbValue); + + var topicsLength = Math.Min(log.Topics.Length, MaxTopics); + for (var topicIndex = 0; topicIndex < topicsLength; topicIndex++) + { + Hash256 topic = log.Topics[topicIndex]; + ReadOnlySpan topicKey = CreateMergeDbKey(topic.Bytes, keyBuffer, isBackwardSync: false); + batches.Topics[topicIndex].Merge(topicKey, dbValue); + } + } + } + + // Need to update the last block number so that new-receipts comparison won't fail when rewriting it + int blockNum = block.BlockNumber - 1; + + (int minBlock, int maxBlock) = SaveRange(batches, blockNum, blockNum, isBackwardSync, isReorg: true); + + batches.Commit(); + + // Postpone in-memory values update until batch is committed + UpdateRange(minBlock, maxBlock, isBackwardSync); + } + + // TODO: refactor compaction to explicitly compress full range for each involved key + public async Task CompactAsync(bool flush = false, int mergeIterations = 0, LogIndexUpdateStats? stats = null) + { + ThrowIfStopped(); + ThrowIfHasError(); + + if (_logger.IsInfo) + _logger.Info($"Log index forced compaction started, DB size: {GetDbSize()}"); + + var timestamp = Stopwatch.GetTimestamp(); + + if (flush) + DBColumns.ForEach(static db => db.Flush()); + + for (var i = 0; i < mergeIterations; i++) + { + Task[] tasks = DBColumns + .Select(static db => Task.Run(() => ForceMerge(db))) + .ToArray(); + + await Task.WhenAll(tasks); + await _compressor.WaitUntilEmptyAsync(TimeSpan.FromSeconds(30)); + } + + CompactingStats compactStats = await _compactor.ForceAsync(); + stats?.Compacting.Combine(compactStats); + + foreach (IMergeOperator mergeOperator in _mergeOperators) + stats?.Combine(mergeOperator.Stats); + + if (_logger.IsInfo) + _logger.Info($"Log index forced compaction finished in {Stopwatch.GetElapsedTime(timestamp)}, DB size: {GetDbSize()} {stats:d}"); + } + + public async Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null) + { + ThrowIfStopped(); + ThrowIfHasError(); + + long totalTimestamp = Stopwatch.GetTimestamp(); + + bool isBackwardSync = aggregate.LastBlockNum < aggregate.FirstBlockNum; + SemaphoreSlim semaphore = isBackwardSync ? _backwardWriteSemaphore : _forwardWriteSemaphore; + await LockRunAsync(semaphore); + + bool wasInitialized = FirstBlockAdded; + if (!wasInitialized) + await _initSemaphore.WaitAsync(); + + try + { + using DbBatches batches = new(_rootDb); + + // Add values to batches + long timestamp; + if (!aggregate.IsEmpty) + { + timestamp = Stopwatch.GetTimestamp(); + + // Add addresses + foreach ((Address address, List blocks) in aggregate.Address) + { + MergeBlockNumbers(batches.Address, address.Bytes, blocks, isBackwardSync, stats); + } + + // Add topics + for (var topicIndex = 0; topicIndex < aggregate.Topic.Length; topicIndex++) + { + Dictionary> topics = aggregate.Topic[topicIndex]; + + foreach ((Hash256 topic, List blocks) in topics) + { + MergeBlockNumbers(batches.Topics[topicIndex], topic.Bytes, blocks, isBackwardSync, stats); + } + } + + stats?.Merging.Include(Stopwatch.GetElapsedTime(timestamp)); + } + + timestamp = Stopwatch.GetTimestamp(); + (int addressRange, int topicRanges) = SaveRange(batches, aggregate.FirstBlockNum, aggregate.LastBlockNum, isBackwardSync); + stats?.UpdatingMeta.Include(Stopwatch.GetElapsedTime(timestamp)); + + // Submit batches + timestamp = Stopwatch.GetTimestamp(); + batches.Commit(); + stats?.CommittingBatch.Include(Stopwatch.GetElapsedTime(timestamp)); + + UpdateRange(addressRange, topicRanges, isBackwardSync); + + // Enqueue compaction if needed + _compactor.TryEnqueue(); + } + finally + { + if (!wasInitialized) + _initSemaphore.Release(); + + semaphore.Release(); + } + + foreach (IMergeOperator mergeOperator in _mergeOperators) + stats?.Combine(mergeOperator.GetAndResetStats()); + stats?.Compressing.Combine(_compressor.GetAndResetStats()); + stats?.Compacting.Combine(_compactor.GetAndResetStats()); + stats?.Adding.Include(Stopwatch.GetElapsedTime(totalTimestamp)); + } + + protected virtual void MergeBlockNumbers( + IWriteBatch dbBatch, ReadOnlySpan key, List numbers, + bool isBackwardSync, LogIndexUpdateStats? stats + ) + { + Span dbKeyBuffer = stackalloc byte[MaxDbKeyLength]; + ReadOnlySpan dbKey = CreateMergeDbKey(key, dbKeyBuffer, isBackwardSync); + + byte[] newValue = CreateDbValue(numbers); + + long timestamp = Stopwatch.GetTimestamp(); + + if (newValue is null or []) + throw new LogIndexStateException("No block numbers to save.", key); + + // TODO: consider disabling WAL, but check: + // - FlushOnTooManyWrites + // - atomic flushing + dbBatch.Merge(dbKey, newValue); + stats?.DBMerging.Include(Stopwatch.GetElapsedTime(timestamp)); + } + + private static ReadOnlySpan WriteKey(ReadOnlySpan key, Span buffer) + { + key.CopyTo(buffer); + return buffer[..key.Length]; + } + + private static ReadOnlySpan ExtractKey(ReadOnlySpan dbKey) => dbKey[..^BlockNumberSize]; + + /// + /// Generates a key consisting of the key || block-number byte array. + /// / + private static ReadOnlySpan CreateDbKey(ReadOnlySpan key, int blockNumber, Span buffer) + { + key = WriteKey(key, buffer); + WriteKeyBlockNumber(buffer[key.Length..], blockNumber); + + int length = key.Length + BlockNumberSize; + return buffer[..length]; + } + + /// + /// Generates a key consisting of the key || block-number byte array. + /// / + private static ReadOnlySpan CreateDbKey(ReadOnlySpan key, ReadOnlySpan blockNumber, Span buffer) + { + key = WriteKey(key, buffer); + blockNumber.CopyTo(buffer[key.Length..]); + + int length = key.Length + blockNumber.Length; + return buffer[..length]; + } + + private static ReadOnlySpan CreateMergeDbKey(ReadOnlySpan key, Span buffer, bool isBackwardSync) => + CreateDbKey(key, isBackwardSync ? Postfix.BackwardMerge : Postfix.ForwardMerge, buffer); + + // RocksDB uses big-endian (lexicographic) ordering + // +1 is needed as 0 is used for the backward-merge key + private static void WriteKeyBlockNumber(Span dbKeyEnd, int number) => BinaryPrimitives.WriteInt32BigEndian(dbKeyEnd, number + 1); + + private static bool UseBackwardSyncFor(ReadOnlySpan dbKey) => dbKey.EndsWith(Postfix.BackwardMerge); + + private static int BinarySearch(ReadOnlySpan blocks, int from) + { + int index = blocks.BinarySearch(from); + return index < 0 ? ~index : index; + } + + private ReadOnlySpan Compress(Span data, Span buffer) + { + ReadOnlySpan blockNumbers = MemoryMarshal.Cast(data); + int length = (int)_compressionAlgorithm.Compress(blockNumbers, (nuint)blockNumbers.Length, buffer); + return buffer[..length]; + } + + private static int ReadCompressionMarker(ReadOnlySpan source) => -BinaryPrimitives.ReadInt32LittleEndian(source); + private static void WriteCompressionMarker(Span source, int len) => BinaryPrimitives.WriteInt32LittleEndian(source, -len); + + private static bool IsCompressed(ReadOnlySpan source, out int len) + { + if (source.Length == 0) + { + len = 0; + return false; + } + + len = ReadCompressionMarker(source); + return len > 0; + } + + private static void WriteBlockNumber(Span destination, int number) => BinaryPrimitives.WriteInt32LittleEndian(destination, number); + private static int ReadBlockNumber(ReadOnlySpan source) => BinaryPrimitives.ReadInt32LittleEndian(source); + private static int ReadLastBlockNumber(ReadOnlySpan source) => ReadBlockNumber(source[^BlockNumberSize..]); + + private static void ReadBlockNumbers(ReadOnlySpan source, Span buffer) + { + if (source.Length % BlockNumberSize != 0) + throw new LogIndexStateException("Invalid length for array of block numbers."); + + if (buffer.Length < source.Length / BlockNumberSize) + throw new ArgumentException($"Buffer is too small to hold {source.Length / BlockNumberSize} block numbers.", nameof(buffer)); + + if (BitConverter.IsLittleEndian) + { + ReadOnlySpan sourceInt = MemoryMarshal.Cast(source); + sourceInt.CopyTo(buffer); + } + else + { + for (var i = 0; i < source.Length; i += BlockNumberSize) + buffer[i / BlockNumberSize] = ReadBlockNumber(source[i..]); + } + } + + private static byte[] CreateDbValue(List numbers) + { + byte[] value = new byte[numbers.Count * BlockNumberSize]; + numbers.CopyTo(MemoryMarshal.Cast(value.AsSpan())); + return value; + } + + private static LogIndexColumns GetColumn(int? topicIndex) => topicIndex.HasValue + ? (LogIndexColumns)(topicIndex + LogIndexColumns.Topics0) + : LogIndexColumns.Addresses; + + private IDb GetDb(int? topicIndex) => topicIndex.HasValue ? _topicDbs[topicIndex.Value] : _addressDb; + + private static IDb GetMetaDb(IColumnsDb rootDb) => rootDb.GetColumnDb(LogIndexColumns.Meta); + + private byte[] CompressDbValue(ReadOnlySpan key, Span data) + { + if (IsCompressed(data, out _)) + throw new LogIndexStateException("Attempt to compress already compressed data.", key); + if (data.Length % BlockNumberSize != 0) + throw new LogIndexStateException($"Invalid length of data to compress: {data.Length}.", key); + + byte[] buffer = Pool.Rent(data.Length + BlockNumberSize); + + try + { + WriteCompressionMarker(buffer, data.Length / BlockNumberSize); + int compressedLen = Compress(data, buffer.AsSpan(BlockNumberSize..)).Length; + return buffer[..(BlockNumberSize + compressedLen)]; + } + finally + { + Pool.Return(buffer); + } + } + + private static void DecompressDbValue(CompressionAlgorithm algorithm, ReadOnlySpan data, Span buffer) + { + if (!IsCompressed(data, out int len)) + throw new ValidationException("Data is not compressed"); + + if (buffer.Length < len) + throw new ArgumentException($"Buffer is too small to decompress {len} block numbers.", nameof(buffer)); + + _ = algorithm.Decompress(data[BlockNumberSize..], (nuint)len, buffer); + } + + private void DecompressDbValue(ReadOnlySpan data, Span buffer) => DecompressDbValue(_compressionAlgorithm, data, buffer); + + private Span RemoveReorgableBlocks(Span data) + { + if (GetLastReorgableBlockNumber() is not { } lastCompressBlock) + return Span.Empty; + + int lastCompressIndex = LastBlockSearch(data, lastCompressBlock, false); + + if (lastCompressIndex < 0) lastCompressIndex = 0; + if (lastCompressIndex > data.Length) lastCompressIndex = data.Length; + + return data[..lastCompressIndex]; + } + + private static void ReverseBlocksIfNeeded(Span data) + { + if (data.Length != 0 && ReadBlockNumber(data) > ReadLastBlockNumber(data)) + MemoryMarshal.Cast(data).Reverse(); + } + + private static void ReverseBlocksIfNeeded(Span blocks) + { + if (blocks.Length != 0 && blocks[0] > blocks[^1]) + blocks.Reverse(); + } + + private static int LastBlockSearch(ReadOnlySpan operand, int block, bool isBackward) + { + if (operand.IsEmpty) + return 0; + + int i = operand.Length - BlockNumberSize; + for (; i >= 0; i -= BlockNumberSize) + { + int currentBlock = ReadBlockNumber(operand[i..]); + if (currentBlock == block) + return i; + + if (isBackward) + { + if (currentBlock > block) + return i + BlockNumberSize; + } + else + { + if (currentBlock < block) + return i + BlockNumberSize; + } + } + + return i; + } + + private static bool IsSeqAsc(IReadOnlyList blocks) + { + int j = blocks.Count - 1; + int i = 1, d = blocks[0].BlockNumber; + while (i <= j && blocks[i].BlockNumber - i == d) i++; + return i > j; + } + + private static bool IsSeqDesc(IReadOnlyList blocks) + { + int j = blocks.Count - 1; + int i = 1, d = blocks[0].BlockNumber; + while (i <= j && blocks[i].BlockNumber + i == d) i++; + return i > j; + } + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/LogIndexUpdateStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexUpdateStats.cs new file mode 100644 index 000000000000..22651564ed8b --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/LogIndexUpdateStats.cs @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading; + +namespace Nethermind.Db.LogIndex; + +/// +/// Log index building statistics across some time range. +/// +public class LogIndexUpdateStats(ILogIndexStorage storage) : IFormattable +{ + private long _blocksAdded; + private long _txAdded; + private long _logsAdded; + private long _topicsAdded; + + public long BlocksAdded => _blocksAdded; + public long TxAdded => _txAdded; + public long LogsAdded => _logsAdded; + public long TopicsAdded => _topicsAdded; + + public long? MaxBlockNumber => storage.MaxBlockNumber; + public long? MinBlockNumber => storage.MinBlockNumber; + + public ExecTimeStats Adding { get; } = new(); + public ExecTimeStats Aggregating { get; } = new(); + public ExecTimeStats Merging { get; } = new(); + + public ExecTimeStats DBMerging { get; } = new(); + public ExecTimeStats UpdatingMeta { get; } = new(); + public ExecTimeStats CommittingBatch { get; } = new(); + public ExecTimeStats BackgroundMerging { get; } = new(); + + public AverageStats KeysCount { get; } = new(); + + public ExecTimeStats QueueingAddressCompression { get; } = new(); + public ExecTimeStats QueueingTopicCompression { get; } = new(); + + public PostMergeProcessingStats Compressing { get; } = new(); + public CompactingStats Compacting { get; } = new(); + + public ExecTimeStats LoadingReceipts { get; } = new(); + + public void Combine(LogIndexUpdateStats other) + { + _blocksAdded += other._blocksAdded; + _txAdded += other._txAdded; + _logsAdded += other._logsAdded; + _topicsAdded += other._topicsAdded; + + Adding.Combine(other.Adding); + Aggregating.Combine(other.Aggregating); + Merging.Combine(other.Merging); + UpdatingMeta.Combine(other.UpdatingMeta); + DBMerging.Combine(other.DBMerging); + CommittingBatch.Combine(other.CommittingBatch); + BackgroundMerging.Combine(other.BackgroundMerging); + KeysCount.Combine(other.KeysCount); + + QueueingAddressCompression.Combine(other.QueueingAddressCompression); + QueueingTopicCompression.Combine(other.QueueingTopicCompression); + + Compressing.Combine(other.Compressing); + Compacting.Combine(other.Compacting); + + LoadingReceipts.Combine(other.LoadingReceipts); + } + + public void IncrementBlocks() => Interlocked.Increment(ref _blocksAdded); + public void IncrementTx(int count = 1) => Interlocked.Add(ref _txAdded, count); + public void IncrementLogs() => Interlocked.Increment(ref _logsAdded); + public void IncrementTopics() => Interlocked.Increment(ref _topicsAdded); + + public string ToString(string? format, IFormatProvider? formatProvider) + { + const string tab = "\t"; + + return !string.Equals(format, "D", StringComparison.OrdinalIgnoreCase) + ? $"{MinBlockNumber:N0} - {MaxBlockNumber:N0} (blocks: +{BlocksAdded:N0} | txs: +{TxAdded:N0} | logs: +{LogsAdded:N0} | topics: +{TopicsAdded:N0})" + : $""" + + {tab}Blocks: {MinBlockNumber:N0} - {MaxBlockNumber:N0} (+{_blocksAdded:N0}) + + {tab}Txs: +{TxAdded:N0} + {tab}Logs: +{LogsAdded:N0} + {tab}Topics: +{TopicsAdded:N0} + + {tab}Keys per batch: {KeysCount:N0} + + {tab}Loading receipts: {LoadingReceipts} + {tab}Aggregating: {Aggregating} + + {tab}Adding receipts: {Adding} + {tab}{tab}Merging: {Merging} (DB: {DBMerging}) + {tab}{tab}Updating metadata: {UpdatingMeta} + {tab}{tab}Committing batch: {CommittingBatch} + + {tab}Background merging: {BackgroundMerging} + + {tab}Post-merge compression: {Compressing.Total} + {tab}{tab}DB reading: {Compressing.DBReading} + {tab}{tab}Compressing: {Compressing.CompressingValue} + {tab}{tab}DB saving: {Compressing.DBSaving} + {tab}{tab}Keys compressed: {Compressing.CompressedAddressKeys:N0} address, {Compressing.CompressedTopicKeys:N0} topic + {tab}{tab}Keys in queue: {Compressing.QueueLength:N0} + + {tab}Compacting: {Compacting.Total} + """; + } + + public override string ToString() => ToString(null, null); +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/MergeOperator.cs b/src/Nethermind/Nethermind.Db/LogIndex/MergeOperator.cs new file mode 100644 index 000000000000..a9f30b6fe4e7 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/MergeOperator.cs @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Diagnostics; +using System.Threading; +using Nethermind.Core; +using Nethermind.Core.Collections; + +namespace Nethermind.Db.LogIndex; + +partial class LogIndexStorage +{ + // TODO: check if success=false + paranoid_checks=true is better than throwing an exception + /// + /// Merges log index incoming block number sequences into a single DB value. + /// + /// + /// Handles calls from . + /// Operator works only with uncompressed data under "transient" keys.
+ /// Each log index column family has its own merge operator instance, + /// parameter is used to find corresponding DB.
+ /// + /// + /// Supports 2 different use cases depending on the incoming operand: + /// + /// + /// In case if operand is a sequence of block numbers - + /// directly concatenates with the existing DB value, validating order remains correct (see ).
+ /// If value size grows to or over block numbers - + /// queues it for compression (via ). + ///
+ /// + /// If operand from - performs specified operation on the previously obtained sequence. + /// + ///
+ /// Check MergeOperator tests for the expected behavior. + ///
+ ///
+ public class MergeOperator(ILogIndexStorage storage, ICompressor compressor, int? topicIndex) : IMergeOperator + { + private LogIndexUpdateStats _stats = new(storage); + public LogIndexUpdateStats Stats => _stats; + public LogIndexUpdateStats GetAndResetStats() => Interlocked.Exchange(ref _stats, new(storage)); + + public string Name => $"{nameof(LogIndexStorage)}.{nameof(MergeOperator)}"; + + public ArrayPoolList? FullMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator) => + Merge(key, enumerator, isPartial: false); + + public ArrayPoolList? PartialMerge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator) => + Merge(key, enumerator, isPartial: true); + + private static bool IsBlockNewer(int next, int? last, bool isBackwardSync) => + LogIndexStorage.IsBlockNewer(next, last, last, isBackwardSync); + + // Validate we are merging non-intersecting segments - to prevent data corruption + private static void AddEnsureSorted(ReadOnlySpan key, ArrayPoolList result, ReadOnlySpan value, bool isBackward) + { + if (value.Length == 0) + return; + + int nextBlock = ReadBlockNumber(value); + int? lastBlock = result.Count > 0 ? ReadLastBlockNumber(result.AsSpan()) : (int?)null; + + if (!IsBlockNewer(next: nextBlock, last: lastBlock, isBackward)) + throw new LogIndexStateException($"Invalid order during merge: {lastBlock} -> {nextBlock} (backward: {isBackward}).", key); + + result.AddRange(value); + } + + // TODO: avoid array copying in case of a single value? + private ArrayPoolList? Merge(ReadOnlySpan key, RocksDbMergeEnumerator enumerator, bool isPartial) + { + var success = false; + ArrayPoolList? result = null; + var timestamp = Stopwatch.GetTimestamp(); + + try + { + // Fast return in case of a single operand + if (!enumerator.HasExistingValue && enumerator.OperandsCount == 1 && !MergeOps.IsAny(enumerator.GetOperand(0))) + return new(enumerator.GetOperand(0)); + + bool isBackwards = UseBackwardSyncFor(key); + + // Calculate total length + var resultLength = enumerator.GetExistingValue().Length; + for (var i = 0; i < enumerator.OperandsCount; i++) + { + ReadOnlySpan operand = enumerator.GetOperand(i); + + if (MergeOps.IsAny(operand)) + { + if (isPartial) + return null; // Notify RocksDB that we can't partially merge custom ops + + continue; + } + + resultLength += operand.Length; + } + + result = new(resultLength); + + // For truncate - just use max/min for all operands + int? truncateAggregate = Aggregate(MergeOp.Truncate, enumerator, isBackwards); + + int iReorg = 0; + for (int i = 0; i < enumerator.TotalCount; i++) + { + Span operand = enumerator.Get(i); + + if (MergeOps.IsAny(operand)) + continue; + + // For reorg - order matters, so we need to always traverse from the current position + iReorg = Math.Max(iReorg, i + 1); + if (FindNext(MergeOp.Reorg, enumerator, ref iReorg) is { } reorgBlock) + operand = MergeOps.ApplyTo(operand, MergeOp.Reorg, reorgBlock, isBackwards); + + if (truncateAggregate is { } truncateBlock) + operand = MergeOps.ApplyTo(operand, MergeOp.Truncate, truncateBlock, isBackwards); + + AddEnsureSorted(key, result, operand, isBackwards); + } + + if (result.Count % BlockNumberSize != 0) + throw new LogIndexStateException($"Invalid data length post-merge: {result.Count}.", key); + + compressor.TryEnqueue(topicIndex, key, result.AsSpan()); + + success = true; + return result; + } + catch (Exception exception) + { + (storage as LogIndexStorage)?.OnBackgroundError(exception); + return null; + } + finally + { + if (!success) result?.Dispose(); + + _stats.BackgroundMerging.Include(Stopwatch.GetElapsedTime(timestamp)); + } + } + + private static int? FindNext(MergeOp op, RocksDbMergeEnumerator enumerator, ref int i) + { + while (i < enumerator.TotalCount && !MergeOps.Is(op, enumerator.Get(i))) + i++; + + return i < enumerator.TotalCount && MergeOps.Is(op, enumerator.Get(i), out int block) + ? block + : null; + } + + private static int? Aggregate(MergeOp op, RocksDbMergeEnumerator enumerator, bool isBackwardSync) + { + int? result = null; + for (var i = 0; i < enumerator.OperandsCount; i++) + { + if (!MergeOps.Is(op, enumerator.GetOperand(i), out var next)) + continue; + + if (result is null || (isBackwardSync && next < result) || (!isBackwardSync && next > result)) + result = next; + } + + return result; + } + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/MergeOps.cs b/src/Nethermind/Nethermind.Db/LogIndex/MergeOps.cs new file mode 100644 index 000000000000..74d494cd5d0f --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/MergeOps.cs @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.Db.LogIndex; + +partial class LogIndexStorage +{ + /// + /// Custom operations for . + /// + public enum MergeOp : byte + { + /// + /// Reorgs from the provided block number, + /// removing any numbers starting from it. + /// + /// + /// Added to support "fast" reorgs - without explicitly fetching value from the database. + /// + Reorg = 1, + + /// + /// Truncates data up to the provided block number, + /// removing it and anything coming before. + /// + /// + /// Added to remove numbers already saved in "finalized" keys (via ) + /// from "transient" keys, without the need for a lock (in case of concurrent merges). + /// + Truncate = 2 + } + + /// + /// Helper class to create and parse operations. + /// + public static class MergeOps + { + public const int Size = BlockNumberSize + 1; + + public static bool Is(MergeOp op, ReadOnlySpan operand) => + operand.Length == Size && operand[0] == (byte)op; + + public static bool Is(MergeOp op, ReadOnlySpan operand, out int fromBlock) + { + if (operand.Length == Size && operand[0] == (byte)op) + { + fromBlock = ReadLastBlockNumber(operand); + return true; + } + + fromBlock = 0; + return false; + } + + public static bool IsAny(ReadOnlySpan operand) => + Is(MergeOp.Reorg, operand, out _) || + Is(MergeOp.Truncate, operand, out _); + + public static Span Create(MergeOp op, int fromBlock, Span buffer) + { + Span dbValue = buffer[..Size]; + dbValue[0] = (byte)op; + WriteBlockNumber(dbValue[1..], fromBlock); + return dbValue; + } + + public static Span ApplyTo(Span operand, MergeOp op, int block, bool isBackward) + { + // In most cases the searched block will be near or at the end of the operand, if present there + int i = LastBlockSearch(operand, block, isBackward); + + return op switch + { + MergeOp.Reorg => i switch + { + < 0 => Span.Empty, + _ when i >= operand.Length => operand, + _ => operand[..i] + }, + + MergeOp.Truncate => i switch + { + < 0 => operand, + _ when i >= operand.Length => Span.Empty, + _ => operand[(i + BlockNumberSize)..] + }, + + _ => throw new ArgumentOutOfRangeException(nameof(op)) + }; + } + } +} diff --git a/src/Nethermind/Nethermind.Db/LogIndex/PostMergeProcessingStats.cs b/src/Nethermind/Nethermind.Db/LogIndex/PostMergeProcessingStats.cs new file mode 100644 index 000000000000..312056e88054 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/LogIndex/PostMergeProcessingStats.cs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Db.LogIndex; + +public class PostMergeProcessingStats +{ + public ExecTimeStats DBReading { get; set; } = new(); + public ExecTimeStats CompressingValue { get; set; } = new(); + public ExecTimeStats DBSaving { get; set; } = new(); + public int QueueLength { get; set; } + + public long CompressedAddressKeys; + public long CompressedTopicKeys; + + public ExecTimeStats Total { get; } = new(); + + public void Combine(PostMergeProcessingStats other) + { + DBReading.Combine(other.DBReading); + CompressingValue.Combine(other.CompressingValue); + DBSaving.Combine(other.DBSaving); + + CompressedAddressKeys += other.CompressedAddressKeys; + CompressedTopicKeys += other.CompressedTopicKeys; + + Total.Combine(other.Total); + } +} diff --git a/src/Nethermind/Nethermind.Db/MemDb.cs b/src/Nethermind/Nethermind.Db/MemDb.cs index 39490693200f..e68055560f3c 100644 --- a/src/Nethermind/Nethermind.Db/MemDb.cs +++ b/src/Nethermind/Nethermind.Db/MemDb.cs @@ -5,6 +5,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using Nethermind.Core; using Nethermind.Core.Extensions; @@ -19,14 +21,13 @@ public class MemDb : IFullDb public long WritesCount { get; private set; } #if ZK - private readonly Dictionary _db; + private readonly Dictionary _db = new(Bytes.EqualityComparer); private readonly Dictionary.AlternateLookup> _spanDb; #else - private readonly ConcurrentDictionary _db; + private readonly ConcurrentDictionary _db = new(Bytes.EqualityComparer); private readonly ConcurrentDictionary.AlternateLookup> _spanDb; #endif - public MemDb(string name) : this(0, 0) { @@ -35,7 +36,7 @@ public MemDb(string name) public static MemDb CopyFrom(IDb anotherDb) { - MemDb newDb = new MemDb(); + MemDb newDb = new(); foreach (KeyValuePair kv in anotherDb.GetAll()) { newDb[kv.Key] = kv.Value; @@ -52,11 +53,6 @@ public MemDb(int writeDelay, int readDelay) { _writeDelay = writeDelay; _readDelay = readDelay; -#if ZK - _db = new Dictionary(Bytes.EqualityComparer); -#else - _db = new ConcurrentDictionary(Bytes.EqualityComparer); -#endif _spanDb = _db.GetAlternateLookup>(); } @@ -64,14 +60,8 @@ public MemDb(int writeDelay, int readDelay) public virtual byte[]? this[ReadOnlySpan key] { - get - { - return Get(key); - } - set - { - Set(key, value); - } + get => Get(key); + set => Set(key, value); } public KeyValuePair[] this[byte[][] keys] @@ -84,18 +74,11 @@ public virtual byte[]? this[ReadOnlySpan key] } ReadsCount += keys.Length; - return keys.Select(k => new KeyValuePair(k, _db.TryGetValue(k, out var value) ? value : null)).ToArray(); + return keys.Select(k => new KeyValuePair(k, _db.GetValueOrDefault(k))).ToArray(); } } - public virtual void Remove(ReadOnlySpan key) - { -#if ZK - _spanDb.Remove(key); -#else - _spanDb.TryRemove(key, out _); -#endif - } + public virtual void Remove(ReadOnlySpan key) => _spanDb.TryRemove(key, out _); public bool KeyExists(ReadOnlySpan key) => _spanDb.ContainsKey(key); @@ -120,9 +103,7 @@ public void Dispose() { } public bool PreferWriteByArray => true; - public virtual Span GetSpan(ReadOnlySpan key) => Get(key).AsSpan(); - - public void DangerousReleaseMemory(in ReadOnlySpan span) { } + public unsafe void DangerousReleaseMemory(in ReadOnlySpan span) { } public virtual byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) { @@ -135,6 +116,9 @@ public void DangerousReleaseMemory(in ReadOnlySpan span) { } return _spanDb.TryGetValue(key, out byte[] value) ? value : null; } + public unsafe Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + => Get(key).AsSpan(); + public virtual void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { if (_writeDelay > 0) diff --git a/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj b/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj index 04ad5b8b916b..f8a963c6bf31 100644 --- a/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj +++ b/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs b/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs index bc734119beb3..98a7d045220f 100644 --- a/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs +++ b/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using Nethermind.Core; +using Nethermind.Core.Buffers; namespace Nethermind.Db { @@ -12,17 +14,12 @@ public class ReadOnlyDb(IDb wrappedDb, bool createInMemWriteStore) : IReadOnlyDb { private readonly MemDb _memDb = new(); - public void Dispose() - { - _memDb.Dispose(); - } + public void Dispose() => _memDb.Dispose(); public string Name { get => wrappedDb.Name; } - public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - { - return _memDb.Get(key, flags) ?? wrappedDb.Get(key, flags); - } + public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => + _memDb.Get(key, flags) ?? wrappedDb.Get(key, flags); public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { @@ -38,11 +35,11 @@ public KeyValuePair[] this[byte[][] keys] { get { - var result = wrappedDb[keys]; - var memResult = _memDb[keys]; + KeyValuePair[]? result = wrappedDb[keys]; + KeyValuePair[]? memResult = _memDb[keys]; for (int i = 0; i < memResult.Length; i++) { - var memValue = memResult[i]; + KeyValuePair memValue = memResult[i]; if (memValue.Value is not null) { result[i] = memValue; @@ -73,7 +70,6 @@ public void Flush(bool onlyWal) { } public virtual void ClearTempChanges() => _memDb.Clear(); - public Span GetSpan(ReadOnlySpan key) => Get(key).AsSpan(); public void PutSpan(ReadOnlySpan keyBytes, ReadOnlySpan value, WriteFlags writeFlags = WriteFlags.None) { if (!createInMemWriteStore) diff --git a/src/Nethermind/Nethermind.Db/RocksDbSettings.cs b/src/Nethermind/Nethermind.Db/RocksDbSettings.cs index b22c1962b5be..2c7feceed718 100644 --- a/src/Nethermind/Nethermind.Db/RocksDbSettings.cs +++ b/src/Nethermind/Nethermind.Db/RocksDbSettings.cs @@ -5,16 +5,10 @@ namespace Nethermind.Db { - public class DbSettings + public class DbSettings(string name, string path) { - public DbSettings(string name, string path) - { - DbName = name; - DbPath = path; - } - - public string DbName { get; private set; } - public string DbPath { get; private set; } + public string DbName { get; private set; } = name; + public string DbPath { get; private set; } = path; public bool DeleteOnStart { get; set; } public bool CanDeleteFolder { get; set; } = true; diff --git a/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs b/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs index d69a532c8735..7f49f1a1367a 100644 --- a/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs +++ b/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -23,12 +24,11 @@ public class SimpleFilePublicKeyDb : IFullDb private readonly ILogger _logger; private bool _hasPendingChanges; - private ConcurrentDictionary _cache; - private ConcurrentDictionary.AlternateLookup> _cacheSpan; + private readonly ConcurrentDictionary _cache = new(Bytes.EqualityComparer); + private readonly ConcurrentDictionary.AlternateLookup> _cacheSpan; - public string DbPath { get; } + private string DbPath { get; } public string Name { get; } - public string Description { get; } public ICollection Keys => _cache.Keys.ToArray(); public ICollection Values => _cache.Values; @@ -40,26 +40,27 @@ public SimpleFilePublicKeyDb(string name, string dbDirectoryPath, ILogManager lo ArgumentNullException.ThrowIfNull(dbDirectoryPath); Name = name ?? throw new ArgumentNullException(nameof(name)); DbPath = Path.Combine(dbDirectoryPath, DbFileName); - Description = $"{Name}|{DbPath}"; if (!Directory.Exists(dbDirectoryPath)) { Directory.CreateDirectory(dbDirectoryPath); } - LoadData(); + _cacheSpan = _cache.GetAlternateLookup>(); + + if (File.Exists(DbPath)) + { + LoadData(); + } } public byte[]? this[ReadOnlySpan key] { - get => Get(key, ReadFlags.None); - set => Set(key, value, WriteFlags.None); + get => Get(key); + set => Set(key, value); } - public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) - { - return _cacheSpan[key]; - } + public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => _cacheSpan[key]; public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) { @@ -98,10 +99,7 @@ public void Remove(ReadOnlySpan key) } } - public bool KeyExists(ReadOnlySpan key) - { - return _cacheSpan.ContainsKey(key); - } + public bool KeyExists(ReadOnlySpan key) => _cacheSpan.ContainsKey(key); public void Flush(bool onlyWal = false) { } @@ -117,10 +115,7 @@ public void Clear() public IEnumerable GetAllValues(bool ordered = false) => _cache.Values; - public IWriteBatch StartWriteBatch() - { - return this.LikeABatch(CommitBatch); - } + public IWriteBatch StartWriteBatch() => this.LikeABatch(CommitBatch); private void CommitBatch() { @@ -219,14 +214,6 @@ private void LoadData() { const int maxLineLength = 2048; - _cache = new ConcurrentDictionary(Bytes.EqualityComparer); - _cacheSpan = _cache.GetAlternateLookup>(); - - if (!File.Exists(DbPath)) - { - return; - } - using SafeFileHandle fileHandle = File.OpenHandle(DbPath, FileMode.OpenOrCreate); using var handle = ArrayPoolDisposableReturn.Rent(maxLineLength, out byte[] rentedBuffer); @@ -306,24 +293,6 @@ void RecordError(Span data) } } - private byte[] Update(byte[] oldValue, byte[] newValue) - { - if (!Bytes.AreEqual(oldValue, newValue)) - { - _hasPendingChanges = true; - } - - return newValue; - } - - private byte[] Add(byte[] value) - { - _hasPendingChanges = true; - return value; - } - - public void Dispose() - { - } + public void Dispose() { } } } diff --git a/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs b/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs index 9865e3441676..b8da26cbc008 100644 --- a/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs @@ -144,7 +144,7 @@ public void GetCachedCodeInfo_CodeTryGetDelegation_ReturnsCodeOfDelegation(byte[ stateProvider.InsertCode(delegationAddress, delegationCode, _releaseSpec); EthereumCodeInfoRepository sut = new(stateProvider); - ICodeInfo result = sut.GetCachedCodeInfo(TestItem.AddressA, _releaseSpec); + CodeInfo result = sut.GetCachedCodeInfo(TestItem.AddressA, _releaseSpec); result.CodeSpan.ToArray().Should().BeEquivalentTo(delegationCode); } diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs index 804288dffcf9..16572c02c27b 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs @@ -669,6 +669,598 @@ public void Should_fail_with_top_level_revert() testEnvironment.tracer.TopLevelRevert.Should().BeTrue(); } + [Test] + public void Should_estimate_gas_when_inner_call_reverts_but_transaction_succeeds() + { + // Reproduces https://github.com/NethermindEth/nethermind/issues/10552 + // GnosisSafe createProxyWithNonce has inner calls that revert (try/catch pattern). + // The bug: ReportOperationError sets OutOfGas=true for ANY revert, even inner ones, + // causing the binary search in gas estimation to always think the tx failed. + using TestEnvironment testEnvironment = new(); + + Address reverterAddress = TestItem.AddressB; + Address callerAddress = TestItem.AddressC; + + // Reverter contract: always reverts with empty data + byte[] reverterCode = Prepare.EvmCode + .PushData(0x00) + .PushData(0x00) + .Op(Instruction.REVERT) + .Done; + testEnvironment.InsertContract(reverterAddress, reverterCode); + + // Caller contract: CALLs reverter (which reverts), catches the revert, then succeeds. + // This simulates GnosisSafe's try/catch pattern. + byte[] callerCode = Prepare.EvmCode + .Call(reverterAddress, 100_000) // inner call that reverts - return value 0 on stack + .Op(Instruction.POP) // discard call result + .PushData(0x01) // value = 1 + .PushData(0x00) // key = 0 + .Op(Instruction.SSTORE) // store 1 at slot 0 (proves execution continued) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(callerAddress, callerCode); + + long gasLimit = 300_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(callerAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0, "Gas estimation should succeed when inner call reverts but transaction succeeds overall"); + err.Should().BeNull("No error should occur - inner reverts should not be treated as top-level failures"); + } + + [Test] + public void Should_estimate_gas_for_create2_with_setup_call_pattern() + { + // Simulates GnosisSafe createProxyWithNonce: CREATE2 deploys a proxy, then + // the caller does a CALL to the newly deployed proxy for setup. + // The setup call may revert internally but the overall tx succeeds. + using TestEnvironment testEnvironment = new(); + + // The "proxy" runtime code: just stores a value (simulating successful setup) + byte[] proxyRuntimeCode = Prepare.EvmCode + .PushData(0x42) // value + .PushData(0x00) // key + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + + // Init code that returns the runtime code + byte[] initCode = Prepare.EvmCode + .ForInitOf(proxyRuntimeCode) + .Done; + + // Factory contract: CREATE2 the proxy, then CALL setup on it + // CREATE2(value=0, offset=0, size=initCode.length, salt=0) + // CALL(gas, addr_from_create2, value=0, ...) + byte[] factoryCode = Prepare.EvmCode + .Create2(initCode, new byte[] { 0x01 }, 0) // CREATE2 with salt=1 + .Op(Instruction.DUP1) // duplicate address for CALL + .PushData(0x00) // retSize + .PushData(0x00) // retOffset + .PushData(0x00) // argSize + .PushData(0x00) // argOffset + .PushData(0x00) // value + .Op(Instruction.SWAP5) // bring address to top (after value) + .PushData(50_000) // gas for setup call + .Op(Instruction.CALL) + .Op(Instruction.POP) // discard call result + .Op(Instruction.POP) // discard remaining address copy + .Op(Instruction.STOP) + .Done; + + Address factoryAddress = TestItem.AddressB; + testEnvironment.InsertContract(factoryAddress, factoryCode); + + long gasLimit = 500_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(factoryAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ConstantinopleFixBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0, "Gas estimation should succeed for CREATE2 + setup call pattern"); + err.Should().BeNull("No error for CREATE2 + setup call"); + } + + [Test] + public void Should_estimate_gas_with_multiple_inner_calls_mixed_reverts() + { + // Contract makes 3 inner calls: first reverts, second succeeds, third reverts. + // Transaction should still succeed and gas estimation should work. + using TestEnvironment testEnvironment = new(); + + Address reverterAddress = TestItem.AddressB; + Address succeederAddress = TestItem.AddressC; + + // Contract that always reverts + byte[] reverterCode = Prepare.EvmCode + .Revert(0, 0) + .Done; + testEnvironment.InsertContract(reverterAddress, reverterCode); + + // Contract that succeeds (stores value) + byte[] succeederCode = Prepare.EvmCode + .PushData(0x01) + .PushData(0x00) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(succeederAddress, succeederCode); + + // Caller: calls reverter, succeeder, reverter - catches all failures + Address callerAddress = TestItem.AddressD; + byte[] callerCode = Prepare.EvmCode + .Call(reverterAddress, 30_000) // call 1: reverts + .Op(Instruction.POP) + .Call(succeederAddress, 50_000) // call 2: succeeds + .Op(Instruction.POP) + .Call(reverterAddress, 30_000) // call 3: reverts + .Op(Instruction.POP) + .PushData(0xFF) + .PushData(0x01) + .Op(Instruction.SSTORE) // store to prove we got here + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(callerAddress, callerCode); + + long gasLimit = 500_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(callerAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0, "Gas estimation should succeed with mixed inner reverts"); + err.Should().BeNull("No error when inner calls revert but overall tx succeeds"); + } + + [Test] + public void Should_estimate_gas_when_inner_call_runs_out_of_gas_but_caller_handles_it() + { + // Verifies that inner OOG (caught by the caller) does not fail gas estimation. + // OutOfGas must be nesting-aware (only set at top level), matching Geth behavior. + // Geth's binary search checks result.Failed() which only reflects the top-level outcome. + // See: https://github.com/ethereum/go-ethereum/blob/master/eth/gasestimator/gasestimator.go + using TestEnvironment testEnvironment = new(); + + // Contract that consumes all gas via infinite loop (will always OOG) + Address gasGuzzlerAddress = TestItem.AddressB; + byte[] gasGuzzlerCode = Prepare.EvmCode + .Op(Instruction.JUMPDEST) // offset 0 + .PushData((byte)0x00) + .Op(Instruction.JUMP) // jump back to 0 + .Done; + testEnvironment.InsertContract(gasGuzzlerAddress, gasGuzzlerCode); + + // Middle contract: calls gas guzzler with limited gas, catches OOG + Address middleAddress = TestItem.AddressC; + byte[] middleCode = Prepare.EvmCode + .Call(gasGuzzlerAddress, 1_000) // only 1000 gas - will OOG + .Op(Instruction.POP) // discard result (0 = failure) + .PushData(0x01) + .PushData((byte)0x00) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(middleAddress, middleCode); + + // Outer caller + Address callerAddress = TestItem.AddressD; + byte[] callerCode = Prepare.EvmCode + .Call(middleAddress, 100_000) + .Op(Instruction.POP) + .PushData(0x02) + .PushData(0x01) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(callerAddress, callerCode); + + long gasLimit = 500_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(callerAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0, "Gas estimation should succeed when inner call OOGs but caller handles it"); + err.Should().BeNull("No error - inner OOG should not affect top-level estimation"); + } + + [TestCase(50_000, true)] + [TestCase(500_000, true)] + [TestCase(1_000, false)] + public void Should_estimate_gas_with_gas_sensitive_branching(long gasThreshold, bool shouldSucceed) + { + // Contract that checks gasLeft() and branches: if gasLeft >= threshold, SSTORE; else REVERT. + // Tests that the binary search correctly handles gas-dependent execution paths. + using TestEnvironment testEnvironment = new(); + + Address contractAddress = TestItem.AddressB; + + // Use the existing pattern from Should_estimate_gas_for_explicit_gas_check_and_revert + // Bytecode: PUSH3 , GAS, LT, PUSH1 , JUMPI, PUSH1 1, PUSH1 0, SSTORE, STOP, JUMPDEST, PUSH1 0, PUSH1 0, REVERT + var check = gasThreshold; + byte[] contractCode = Bytes.FromHexString($"0x62{check:x6}5a10600f576001600055005b6000806000fd"); + testEnvironment.InsertContract(contractAddress, contractCode); + + long gasLimit = 1_100_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(contractAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + if (shouldSucceed) + { + result.Should().BeGreaterThan(0, "Gas estimation should find enough gas for the success path"); + err.Should().BeNull("No error - binary search should find gas level above threshold"); + } + else + { + result.Should().BeGreaterThan(0, "Low threshold should always succeed"); + err.Should().BeNull(); + } + } + + [Test] + public void Should_estimate_gas_for_create_with_constructor_making_calls() + { + // CREATE deploys a contract whose constructor makes an external CALL. + // The constructor call might revert but CREATE still succeeds. + using TestEnvironment testEnvironment = new(); + + // External contract that reverts + Address externalAddress = TestItem.AddressB; + byte[] externalCode = Prepare.EvmCode + .Revert(0, 0) + .Done; + testEnvironment.InsertContract(externalAddress, externalCode); + + // Runtime code (deployed contract's code) + byte[] runtimeCode = Prepare.EvmCode + .PushData(0x01) + .PushData(0x00) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + + // Init code: calls external (which reverts, but init code catches it), then returns runtime code + byte[] initCode = Prepare.EvmCode + .Call(externalAddress, 10_000) + .Op(Instruction.POP) // discard call result + .ForInitOf(runtimeCode) + .Done; + + // Factory: CREATE with init code, then STOP + Address factoryAddress = TestItem.AddressC; + byte[] factoryCode = Prepare.EvmCode + .Create(initCode, 0) + .Op(Instruction.POP) // discard created address + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(factoryAddress, factoryCode); + + long gasLimit = 500_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(factoryAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0, "Gas estimation should succeed for CREATE with constructor that makes calls"); + err.Should().BeNull("No error for constructor-call pattern"); + } + + [Test] + public void Should_estimate_gas_consistently_across_repeated_calls() + { + // Tests that repeated gas estimation on the same contract yields consistent results. + // Each call creates a fresh EstimateGasTracer; this guards against non-deterministic estimation behavior across runs. + using TestEnvironment testEnvironment = new(); + + Address reverterAddress = TestItem.AddressB; + byte[] reverterCode = Prepare.EvmCode + .Revert(0, 0) + .Done; + testEnvironment.InsertContract(reverterAddress, reverterCode); + + Address callerAddress = TestItem.AddressC; + byte[] callerCode = Prepare.EvmCode + .Call(reverterAddress, 30_000) + .Op(Instruction.POP) + .PushData(0x01) + .PushData(0x00) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(callerAddress, callerCode); + + long gasLimit = 300_000; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithGasLimit(gasLimit) + .TestObject; + + long? firstResult = null; + for (int i = 0; i < 10; i++) + { + // Each estimation uses a fresh tracer (as BlockchainBridge.EstimateGas does) + TestEnvironment freshEnv = new(); + freshEnv.InsertContract(reverterAddress, reverterCode); + freshEnv.InsertContract(callerAddress, callerCode); + + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(callerAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + + long result = freshEnv.estimator.Estimate(tx, block.Header, freshEnv.tracer, out string? err); + + result.Should().BeGreaterThan(0, $"Iteration {i}: gas estimation should succeed"); + err.Should().BeNull($"Iteration {i}: no error expected"); + + firstResult ??= result; + result.Should().Be(firstResult.Value, $"Iteration {i}: result should be consistent"); + + freshEnv.Dispose(); + } + } + + [Test] + public void Should_estimate_gas_for_deeply_nested_calls() + { + // Chain of 4 nested CALLs to test nesting level tracking in EstimateGasTracer. + // A -> B -> C -> D (all succeed) + using TestEnvironment testEnvironment = new(); + + // Contract D: leaf, just stores and stops + Address addrD = TestItem.AddressD; + byte[] codeD = Prepare.EvmCode + .PushData(0x04) + .PushData(0x04) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(addrD, codeD); + + // Contract C: calls D + Address addrC = TestItem.AddressC; + byte[] codeC = Prepare.EvmCode + .Call(addrD, 50_000) + .Op(Instruction.POP) + .PushData(0x03) + .PushData(0x03) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(addrC, codeC); + + // Contract B: calls C + Address addrB = TestItem.AddressB; + byte[] codeB = Prepare.EvmCode + .Call(addrC, 100_000) + .Op(Instruction.POP) + .PushData(0x02) + .PushData(0x02) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(addrB, codeB); + + // Contract A: calls B (this is what the tx calls) + Address addrA = new("0x0000000000000000000000000000000000000042"); + byte[] codeA = Prepare.EvmCode + .Call(addrB, 200_000) + .Op(Instruction.POP) + .PushData(0x01) + .PushData(0x01) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(addrA, codeA); + + long gasLimit = 500_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(addrA) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0, "Gas estimation should succeed for deeply nested call chain"); + err.Should().BeNull("No error for deeply nested calls"); + } + + [Test] + public void Should_estimate_gas_for_nested_create2_with_inner_revert_in_constructor() + { + // CREATE2 deploys a contract whose constructor calls an external contract that reverts. + // Constructor catches the revert and continues. This is the GnosisSafe pattern: + // createProxyWithNonce -> CREATE2 -> proxy constructor -> setup() call -> possible revert + using TestEnvironment testEnvironment = new(); + + // External contract that always reverts with data + Address externalAddress = TestItem.AddressB; + byte[] externalCode = Prepare.EvmCode + .StoreDataInMemory(0, new byte[] { 0xDE, 0xAD }) + .Revert(2, 0) + .Done; + testEnvironment.InsertContract(externalAddress, externalCode); + + // Runtime code (what the proxy becomes after deployment) + byte[] runtimeCode = Prepare.EvmCode + .Op(Instruction.STOP) + .Done; + + // Init code: calls external (reverts, caught), then returns runtime code + byte[] initCode = Prepare.EvmCode + .Call(externalAddress, 20_000) // will revert, returns 0 + .Op(Instruction.POP) // discard failure result + .ForInitOf(runtimeCode) + .Done; + + // Factory: CREATE2 with salt, verify address is non-zero, STOP + Address factoryAddress = TestItem.AddressC; + byte[] factoryCode = Prepare.EvmCode + .Create2(initCode, new byte[] { 0xAB, 0xCD }, 0) // CREATE2 with salt + .Op(Instruction.POP) + .PushData(0x01) + .PushData(0x00) + .Op(Instruction.SSTORE) // record success + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(factoryAddress, factoryCode); + + long gasLimit = 500_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(factoryAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ConstantinopleFixBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0, "Gas estimation should succeed for CREATE2 with inner revert in constructor"); + err.Should().BeNull("No error for GnosisSafe-like CREATE2 pattern"); + } + + [Test] + public void Should_return_revert_error_when_top_level_call_reverts_with_data() + { + // Ensures gas estimation properly reports revert data when the top-level call reverts. + using TestEnvironment testEnvironment = new(); + + Address contractAddress = TestItem.AddressB; + // Store revert reason in memory, then REVERT with it + byte[] contractCode = Prepare.EvmCode + .StoreDataInMemory(0, new byte[] { 0x08, 0xC3, 0x79, 0xA0 }) // Error(string) selector + .Revert(4, 0) + .Done; + testEnvironment.InsertContract(contractAddress, contractCode); + + long gasLimit = 300_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(contractAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().Be(0, "Gas estimation should fail when top-level call reverts"); + err.Should().NotBeNull("Should report an error when top-level reverts"); + // The error contains the revert data (hex-encoded output from the REVERT opcode) + testEnvironment.tracer.TopLevelRevert.Should().BeTrue("TopLevelRevert should be set for top-level REVERT"); + } + + [Test] + public void Should_estimate_gas_with_delegatecall_that_reverts_internally() + { + // DELEGATECALL that reverts internally - the revert happens in the caller's context + // but at a nested level. Gas estimation should still succeed. + using TestEnvironment testEnvironment = new(); + + // Implementation that reverts + Address implAddress = TestItem.AddressB; + byte[] implCode = Prepare.EvmCode + .Revert(0, 0) + .Done; + testEnvironment.InsertContract(implAddress, implCode); + + // Proxy: DELEGATECALL to impl (reverts), catches it, then succeeds + Address proxyAddress = TestItem.AddressC; + byte[] proxyCode = Prepare.EvmCode + .DelegateCall(implAddress, 30_000) + .Op(Instruction.POP) // discard result + .PushData(0x01) + .PushData(0x00) + .Op(Instruction.SSTORE) + .Op(Instruction.STOP) + .Done; + testEnvironment.InsertContract(proxyAddress, proxyCode); + + long gasLimit = 300_000; + Transaction tx = Build.A.Transaction + .WithGasLimit(gasLimit) + .WithTo(proxyAddress) + .WithSenderAddress(TestItem.AddressA) + .TestObject; + Block block = Build.A.Block + .WithNumber(MainnetSpecProvider.ByzantiumBlockNumber + 1) + .WithTransactions(tx) + .WithGasLimit(gasLimit) + .TestObject; + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0, "Gas estimation should succeed when DELEGATECALL reverts but caller handles it"); + err.Should().BeNull("No error for caught DELEGATECALL revert"); + } + private class TestEnvironment : IDisposable { public ISpecProvider _specProvider; diff --git a/src/Nethermind/Nethermind.Evm/BadInstructionException.cs b/src/Nethermind/Nethermind.Evm/BadInstructionException.cs deleted file mode 100644 index e8caed45f5cd..000000000000 --- a/src/Nethermind/Nethermind.Evm/BadInstructionException.cs +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Evm; - -public class BadInstructionException : EvmException -{ - public override EvmExceptionType ExceptionType => EvmExceptionType.BadInstruction; -} diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockOverride.cs b/src/Nethermind/Nethermind.Evm/BlockOverride.cs similarity index 96% rename from src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockOverride.cs rename to src/Nethermind/Nethermind.Evm/BlockOverride.cs index 4bb929aef316..fb0f7b6e52c2 100644 --- a/src/Nethermind/Nethermind.Facade/Proxy/Models/Simulate/BlockOverride.cs +++ b/src/Nethermind/Nethermind.Evm/BlockOverride.cs @@ -6,7 +6,7 @@ using Nethermind.Core.Crypto; using Nethermind.Int256; -namespace Nethermind.Facade.Proxy.Models.Simulate; +namespace Nethermind.Evm; public class BlockOverride { diff --git a/src/Nethermind/Nethermind.Evm/CallResult.cs b/src/Nethermind/Nethermind.Evm/CallResult.cs index 08d88379284f..d23ac7ab54e3 100644 --- a/src/Nethermind/Nethermind.Evm/CallResult.cs +++ b/src/Nethermind/Nethermind.Evm/CallResult.cs @@ -44,7 +44,7 @@ public CallResult(ReadOnlyMemory output, bool? precompileSuccess, int from FromVersion = fromVersion; } - public CallResult(ICodeInfo? container, ReadOnlyMemory output, bool? precompileSuccess, int fromVersion, bool shouldRevert = false, EvmExceptionType exceptionType = EvmExceptionType.None) + public CallResult(CodeInfo? container, ReadOnlyMemory output, bool? precompileSuccess, int fromVersion, bool shouldRevert = false, EvmExceptionType exceptionType = EvmExceptionType.None) { StateToExecute = null; Output = (container, output); @@ -64,7 +64,7 @@ private CallResult(EvmExceptionType exceptionType) } public VmState? StateToExecute { get; } - public (ICodeInfo Container, ReadOnlyMemory Bytes) Output { get; } + public (CodeInfo Container, ReadOnlyMemory Bytes) Output { get; } public EvmExceptionType ExceptionType { get; } public bool ShouldRevert { get; } public bool? PrecompileSuccess { get; } diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs index 4e2320bd3ff6..75a16c118201 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs @@ -3,35 +3,101 @@ using System; using System.Threading; +using Nethermind.Core.Extensions; +using Nethermind.Evm.Precompiles; namespace Nethermind.Evm.CodeAnalysis; -public sealed class CodeInfo(ReadOnlyMemory code) : ICodeInfo, IThreadPoolWorkItem +public class CodeInfo : IThreadPoolWorkItem, IEquatable { - private static readonly JumpDestinationAnalyzer _emptyAnalyzer = new(Array.Empty()); - public static CodeInfo Empty { get; } = new(ReadOnlyMemory.Empty); - public ReadOnlyMemory Code { get; } = code; - ReadOnlySpan ICodeInfo.CodeSpan => Code.Span; + public static CodeInfo Empty { get; } = new(); + // Empty code sentinel + private static readonly JumpDestinationAnalyzer? _emptyAnalyzer = new(Empty, skipAnalysis: true); - private readonly JumpDestinationAnalyzer _analyzer = code.Length == 0 ? _emptyAnalyzer : new JumpDestinationAnalyzer(code); + // Empty + private CodeInfo() + { + _analyzer = null; + } - public bool IsEmpty => ReferenceEquals(_analyzer, _emptyAnalyzer); + protected CodeInfo(IPrecompile precompile, int version, ReadOnlyMemory code) + { + Precompile = precompile; + Version = version; + Code = code; + _analyzer = null; + } - public bool ValidateJump(int destination) + // Eof + protected CodeInfo(int version, ReadOnlyMemory code) { - return _analyzer.ValidateJump(destination); + Version = version; + Code = code; + _analyzer = null; } - void IThreadPoolWorkItem.Execute() + // Regular contract + public CodeInfo(ReadOnlyMemory code) { - _analyzer.Execute(); + Code = code; + _analyzer = code.Length == 0 ? _emptyAnalyzer : new JumpDestinationAnalyzer(this); } + // Precompile + public CodeInfo(IPrecompile? precompile) + { + Precompile = precompile; + _analyzer = null; + } + + public ReadOnlyMemory Code { get; } + public ReadOnlySpan CodeSpan => Code.Span; + + public IPrecompile? Precompile { get; } + + private readonly JumpDestinationAnalyzer _analyzer; + + public bool IsEmpty => ReferenceEquals(_analyzer, _emptyAnalyzer); + public bool IsPrecompile => Precompile is not null; + + public bool ValidateJump(int destination) + => _analyzer?.ValidateJump(destination) ?? false; + + /// + /// Gets the version of the code format. + /// The default implementation returns 0, representing a legacy code format or non-EOF code. + /// + public int Version { get; } = 0; + + void IThreadPoolWorkItem.Execute() + => _analyzer?.Execute(); + public void AnalyzeInBackgroundIfRequired() { - if (!ReferenceEquals(_analyzer, _emptyAnalyzer) && _analyzer.RequiresAnalysis) + if (!ReferenceEquals(_analyzer, _emptyAnalyzer) && (_analyzer?.RequiresAnalysis ?? false)) { ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false); } } + + public override bool Equals(object? obj) + => Equals(obj as CodeInfo); + + public override int GetHashCode() + { + if (IsPrecompile) + return Precompile?.GetType().GetHashCode() ?? 0; + return CodeSpan.FastHash(); + } + + public bool Equals(CodeInfo? other) + { + if (other is null) + return false; + if (ReferenceEquals(this, other)) + return true; + if (IsPrecompile || other.IsPrecompile) + return Precompile?.GetType() == other.Precompile?.GetType(); + return CodeSpan.SequenceEqual(other.CodeSpan); + } } diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs index 2ceca978a1ba..7923f62aeafc 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs @@ -16,7 +16,7 @@ namespace Nethermind.Evm.CodeAnalysis; -public sealed class JumpDestinationAnalyzer(ReadOnlyMemory code) +public sealed class JumpDestinationAnalyzer(CodeInfo codeInfo, bool skipAnalysis = false) { private const int PUSH1 = (int)Instruction.PUSH1; private const int PUSHx = PUSH1 - 1; @@ -24,10 +24,10 @@ public sealed class JumpDestinationAnalyzer(ReadOnlyMemory code) private const int BitShiftPerInt64 = 6; private static readonly long[]? _emptyJumpDestinationBitmap = new long[1]; - private long[]? _jumpDestinationBitmap = code.Length == 0 ? _emptyJumpDestinationBitmap : null; + private long[]? _jumpDestinationBitmap = (codeInfo.Code.Length == 0 || skipAnalysis) ? _emptyJumpDestinationBitmap : null; private object? _analysisComplete; - private ReadOnlyMemory MachineCode { get; } = code; + public ReadOnlyMemory MachineCode => codeInfo.Code; public bool ValidateJump(int destination) { diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs b/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs index 43fd8a86b8b2..91ff0753e0e1 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs @@ -10,7 +10,7 @@ namespace Nethermind.Evm.CodeAnalysis; public static class CodeInfoFactory { - public static ICodeInfo CreateCodeInfo(ReadOnlyMemory code, IReleaseSpec spec, ValidationStrategy validationRules = ValidationStrategy.ExtractHeader) + public static CodeInfo CreateCodeInfo(ReadOnlyMemory code, IReleaseSpec spec, ValidationStrategy validationRules = ValidationStrategy.ExtractHeader) { if (spec.IsEofEnabled && code.Span.StartsWith(EofValidator.MAGIC) @@ -23,7 +23,7 @@ public static ICodeInfo CreateCodeInfo(ReadOnlyMemory code, IReleaseSpec s return codeInfo; } - public static bool CreateInitCodeInfo(ReadOnlyMemory data, IReleaseSpec spec, [NotNullWhen(true)] out ICodeInfo? codeInfo, out ReadOnlyMemory extraCallData) + public static bool CreateInitCodeInfo(ReadOnlyMemory data, IReleaseSpec spec, [NotNullWhen(true)] out CodeInfo? codeInfo, out ReadOnlyMemory extraCallData) { extraCallData = default; if (spec.IsEofEnabled && data.Span.StartsWith(EofValidator.MAGIC)) diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs index a272e336d35a..08bb726d2c15 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs @@ -19,7 +19,7 @@ namespace Nethermind.Evm; public class CodeInfoRepository : ICodeInfoRepository { private static readonly CodeLruCache _codeCache = new(); - private readonly FrozenDictionary _localPrecompiles; + private readonly FrozenDictionary _localPrecompiles; private readonly IWorldState _worldState; public CodeInfoRepository(IWorldState worldState, IPrecompileProvider precompileProvider) @@ -28,7 +28,7 @@ public CodeInfoRepository(IWorldState worldState, IPrecompileProvider precompile _worldState = worldState; } - public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) + public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) { delegationAddress = null; if (vmSpec.IsPrecompile(codeSource)) // _localPrecompiles have to have all precompiles @@ -36,7 +36,7 @@ public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IR return _localPrecompiles[codeSource]; } - ICodeInfo cachedCodeInfo = InternalGetCachedCode(_worldState, codeSource, vmSpec); + CodeInfo cachedCodeInfo = InternalGetCachedCode(_worldState, codeSource, vmSpec); if (!cachedCodeInfo.IsEmpty && ICodeInfoRepository.TryGetDelegatedAddress(cachedCodeInfo.CodeSpan, out delegationAddress)) { @@ -47,7 +47,7 @@ public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IR return cachedCodeInfo; } - public ICodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) + public CodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) { if (vmSpec.IsPrecompile(codeSource)) { @@ -57,21 +57,21 @@ public ICodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, return InternalGetCachedCode(_worldState, in codeHash, vmSpec); } - private ICodeInfo InternalGetCachedCode(Address codeSource, IReleaseSpec vmSpec) + private CodeInfo InternalGetCachedCode(Address codeSource, IReleaseSpec vmSpec) { ref readonly ValueHash256 codeHash = ref _worldState.GetCodeHash(codeSource); return InternalGetCachedCode(_worldState, in codeHash, vmSpec); } - private static ICodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, Address codeSource, IReleaseSpec vmSpec) + private static CodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, Address codeSource, IReleaseSpec vmSpec) { ValueHash256 codeHash = worldState.GetCodeHash(codeSource); return InternalGetCachedCode(worldState, in codeHash, vmSpec); } - private static ICodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, in ValueHash256 codeHash, IReleaseSpec vmSpec) + private static CodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, in ValueHash256 codeHash, IReleaseSpec vmSpec) { - ICodeInfo? cachedCodeInfo = null; + CodeInfo? cachedCodeInfo = null; if (codeHash == Keccak.OfAnEmptyString.ValueHash256) { cachedCodeInfo = CodeInfo.Empty; @@ -111,7 +111,7 @@ public void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpe if (_worldState.InsertCode(codeOwner, in codeHash, code, spec) && _codeCache.Get(in codeHash) is null) { - ICodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(code, spec, ValidationStrategy.ExtractHeader); + CodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(code, spec, ValidationStrategy.ExtractHeader); _codeCache.Set(in codeHash, codeInfo); } } @@ -148,7 +148,7 @@ public ValueHash256 GetExecutableCodeHash(Address address, IReleaseSpec spec) return Keccak.OfAnEmptyString.ValueHash256; } - ICodeInfo codeInfo = InternalGetCachedCode(_worldState, address, spec); + CodeInfo codeInfo = InternalGetCachedCode(_worldState, address, spec); return codeInfo.IsEmpty ? Keccak.OfAnEmptyString.ValueHash256 : codeHash; @@ -164,36 +164,36 @@ private sealed class CodeLruCache { private const int CacheCount = 16; private const int CacheMax = CacheCount - 1; - private readonly ClockCache[] _caches; + private readonly ClockCache[] _caches; public CodeLruCache() { - _caches = new ClockCache[CacheCount]; + _caches = new ClockCache[CacheCount]; for (int i = 0; i < _caches.Length; i++) { // Cache per nibble to reduce contention as TxPool is very parallel - _caches[i] = new ClockCache(MemoryAllowance.CodeCacheSize / CacheCount); + _caches[i] = new ClockCache(MemoryAllowance.CodeCacheSize / CacheCount); } } - public ICodeInfo? Get(in ValueHash256 codeHash) + public CodeInfo? Get(in ValueHash256 codeHash) { - ClockCache cache = _caches[GetCacheIndex(codeHash)]; + ClockCache cache = _caches[GetCacheIndex(codeHash)]; return cache.Get(codeHash); } - public bool Set(in ValueHash256 codeHash, ICodeInfo codeInfo) + public bool Set(in ValueHash256 codeHash, CodeInfo codeInfo) { - ClockCache cache = _caches[GetCacheIndex(codeHash)]; + ClockCache cache = _caches[GetCacheIndex(codeHash)]; return cache.Set(codeHash, codeInfo); } private static int GetCacheIndex(in ValueHash256 codeHash) => codeHash.Bytes[^1] & CacheMax; - public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out ICodeInfo? codeInfo) + public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out CodeInfo? codeInfo) { codeInfo = Get(in codeHash); return codeInfo is not null; } } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs index 140a3034b36d..82e5a8ee5b95 100644 --- a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs +++ b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs @@ -6,17 +6,18 @@ namespace Nethermind.Evm.CodeAnalysis; -public sealed class EofCodeInfo(in EofContainer container) : ICodeInfo +public sealed class EofCodeInfo : CodeInfo { - public EofContainer EofContainer { get; private set; } = container; - public ReadOnlyMemory Code => EofContainer.Container; - public int Version => EofContainer.Header.Version; - public bool IsEmpty => EofContainer.IsEmpty; + public EofCodeInfo(in EofContainer container) : base(container.Header.Version, container.Container) + { + EofContainer = container; + } + + public EofContainer EofContainer { get; private set; } public ReadOnlyMemory TypeSection => EofContainer.TypeSection; public ReadOnlyMemory CodeSection => EofContainer.CodeSection; public ReadOnlyMemory DataSection => EofContainer.DataSection; public ReadOnlyMemory ContainerSection => EofContainer.ContainerSection; - ReadOnlySpan ICodeInfo.CodeSpan => CodeSection.Span; public SectionHeader CodeSectionOffset(int sectionId) => EofContainer.Header.CodeSections[sectionId]; public SectionHeader? ContainerSectionOffset(int sectionId) => EofContainer.Header.ContainerSections.Value[sectionId]; diff --git a/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs b/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs index 0f396d82bd47..ff60de7b2723 100644 --- a/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs +++ b/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs @@ -22,7 +22,7 @@ public sealed class ExecutionEnvironment : IDisposable /// /// Parsed bytecode for the current call. /// - public ICodeInfo CodeInfo { get; private set; } = null!; + public CodeInfo CodeInfo { get; private set; } = null!; /// /// Currently executing account (in DELEGATECALL this will be equal to caller). @@ -65,7 +65,7 @@ private ExecutionEnvironment() { } /// Rents an ExecutionEnvironment from the pool and initializes it with the provided values. /// public static ExecutionEnvironment Rent( - ICodeInfo codeInfo, + CodeInfo codeInfo, Address executingAccount, Address caller, Address? codeSource, diff --git a/src/Nethermind/Nethermind.Evm/ICodeInfo.cs b/src/Nethermind/Nethermind.Evm/ICodeInfo.cs deleted file mode 100644 index 07156260530e..000000000000 --- a/src/Nethermind/Nethermind.Evm/ICodeInfo.cs +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Evm.Precompiles; - -namespace Nethermind.Evm.CodeAnalysis; - -/// -/// Represents common code information for EVM execution. -/// Implementations include , (EVM Object Format), -/// and for precompiled contracts. -/// -public interface ICodeInfo -{ - /// - /// Gets the version of the code format. - /// The default implementation returns 0, representing a legacy code format or non-EOF code. - /// - int Version => 0; - - /// - /// Indicates whether the code is empty or not. - /// - bool IsEmpty { get; } - - /// - /// Gets the raw machine code as a segment. - /// This is the primary code section from which the EVM executes instructions. - /// - ReadOnlyMemory Code { get; } - - /// - /// Indicates whether this code represents a precompiled contract. - /// By default, this returns false. - /// - bool IsPrecompile => false; - IPrecompile? Precompile => null; - - /// - /// Gets the code section. - /// By default, this returns the same contents as . - /// - ReadOnlyMemory CodeSection => Code; - ReadOnlySpan CodeSpan { get; } - - /// - /// Gets the data section, which is reserved for additional data segments in EOF. - /// By default, this returns an empty memory segment. - /// - ReadOnlyMemory DataSection => Memory.Empty; - - /// - /// Computes the offset to be added to the program counter when executing instructions. - /// By default, this returns 0, meaning no offset is applied. - /// - /// The program counter offset for this code format. - int PcOffset() => 0; - - /// - /// Validates whether a jump destination is permissible according to this code format. - /// By default, this returns false. - /// - /// The instruction index to validate. - /// true if the jump is valid; otherwise, false. - bool ValidateJump(int destination) => false; -} diff --git a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs index 85b359ece23c..2b7f9ac8ba29 100644 --- a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs @@ -11,8 +11,8 @@ namespace Nethermind.Evm; public interface ICodeInfoRepository { - ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress); - ICodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec); + CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress); + CodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec); ValueHash256 GetExecutableCodeHash(Address address, IReleaseSpec spec); void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec); void SetDelegation(Address codeSource, Address authority, IReleaseSpec spec); @@ -38,8 +38,8 @@ static bool TryGetDelegatedAddress(ReadOnlySpan code, [NotNullWhen(true)] public static class CodeInfoRepositoryExtensions { - public static ICodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, Address codeSource, IReleaseSpec vmSpec) + public static CodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, Address codeSource, IReleaseSpec vmSpec) => codeInfoRepository.GetCachedCodeInfo(codeSource, vmSpec, out _); - public static ICodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress) + public static CodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress) => codeInfoRepository.GetCachedCodeInfo(codeSource, true, vmSpec, out delegationAddress); -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs index 6cee9b50c462..5b9dd5dd3697 100644 --- a/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/IOverridableCodeInfoRepository.cs @@ -9,7 +9,7 @@ namespace Nethermind.Evm; public interface IOverridableCodeInfoRepository : ICodeInfoRepository { - void SetCodeOverride(IReleaseSpec vmSpec, Address key, ICodeInfo value); + void SetCodeOverride(IReleaseSpec vmSpec, Address key, CodeInfo value); void MovePrecompile(IReleaseSpec vmSpec, Address precompileAddr, Address targetAddr); void ResetOverrides(); void ResetPrecompileOverrides(); diff --git a/src/Nethermind/Nethermind.Evm/IPrecompileProvider.cs b/src/Nethermind/Nethermind.Evm/IPrecompileProvider.cs index d6654879331d..6e17d2e8c3d3 100644 --- a/src/Nethermind/Nethermind.Evm/IPrecompileProvider.cs +++ b/src/Nethermind/Nethermind.Evm/IPrecompileProvider.cs @@ -9,5 +9,5 @@ namespace Nethermind.Evm; public interface IPrecompileProvider { - public FrozenDictionary GetPrecompiles(); + public FrozenDictionary GetPrecompiles(); } diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs index e802d92416fe..2728f87e21b2 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs @@ -177,7 +177,7 @@ public static EvmExceptionType InstructionCall( StackUnderflow: return EvmExceptionType.StackUnderflow; } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs index 66f79084b2cb..d433f76793e7 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs @@ -200,7 +200,7 @@ public static EvmExceptionType InstructionCreate(Vir where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; // Ensure the instruction is only valid for non-legacy (EOF) code. - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; // Deduct gas required for data loading. @@ -193,8 +192,7 @@ public static EvmExceptionType InstructionDataLoadN(Vi where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.DataLoadN)) @@ -225,8 +223,7 @@ public static EvmExceptionType InstructionDataSize(Vir where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.DataSize)) @@ -251,8 +248,7 @@ public static EvmExceptionType InstructionDataCopy(Vir where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; // Pop destination memory offset, data section offset, and size. @@ -303,8 +299,7 @@ public static EvmExceptionType InstructionDataCopy(Vir public static EvmExceptionType InstructionRelativeJump(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.RJump)) @@ -330,8 +325,7 @@ public static EvmExceptionType InstructionRelativeJump(VirtualMachin public static EvmExceptionType InstructionRelativeJumpIf(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.RJumpi)) @@ -365,8 +359,7 @@ public static EvmExceptionType InstructionRelativeJumpIf(VirtualMach public static EvmExceptionType InstructionJumpTable(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.RJumpv)) @@ -406,7 +399,7 @@ public static EvmExceptionType InstructionJumpTable(VirtualMachine(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - ICodeInfo iCodeInfo = vm.VmState.Env.CodeInfo; + CodeInfo iCodeInfo = vm.VmState.Env.CodeInfo; if (iCodeInfo.Version == 0) goto BadInstruction; @@ -462,7 +455,7 @@ public static EvmExceptionType InstructionCallFunction(VirtualMachin public static EvmExceptionType InstructionReturnFunction(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; + CodeInfo codeInfo = vm.VmState.Env.CodeInfo; if (codeInfo.Version == 0) goto BadInstruction; @@ -490,7 +483,7 @@ public static EvmExceptionType InstructionReturnFunction(VirtualMach public static EvmExceptionType InstructionJumpFunction(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - ICodeInfo iCodeInfo = vm.VmState.Env.CodeInfo; + CodeInfo iCodeInfo = vm.VmState.Env.CodeInfo; if (iCodeInfo.Version == 0) goto BadInstruction; @@ -530,8 +523,7 @@ public static EvmExceptionType InstructionDupN(Virtual where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Dupn)) @@ -561,8 +553,7 @@ public static EvmExceptionType InstructionSwapN(Virtua where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Swapn)) @@ -591,8 +582,7 @@ public static EvmExceptionType InstructionExchange(Vir where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; - if (codeInfo.Version == 0) + if (vm.VmState.Env.CodeInfo is not EofCodeInfo codeInfo) goto BadInstruction; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Swapn)) @@ -742,7 +732,7 @@ public static EvmExceptionType InstructionEofCreate(Vi state.SubtractFromBalance(env.ExecutingAccount, value, spec); // Create new code info for the init code. - ICodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(initContainer, spec, ValidationStrategy.ExtractHeader); + CodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(initContainer, spec, ValidationStrategy.ExtractHeader); // 8. Prepare the callData from the caller’s memory slice. if (!vm.VmState.Memory.TryLoad(dataOffset, dataSize, out ReadOnlyMemory callData)) @@ -843,7 +833,7 @@ public static EvmExceptionType InstructionReturnDataLoad EvmExceptionType.InvalidCode; - } -} diff --git a/src/Nethermind/Nethermind.Evm/Metrics.cs b/src/Nethermind/Nethermind.Evm/Metrics.cs index b4f5502b4161..0f94291471b6 100644 --- a/src/Nethermind/Nethermind.Evm/Metrics.cs +++ b/src/Nethermind/Nethermind.Evm/Metrics.cs @@ -19,6 +19,11 @@ public class Metrics private static readonly ZeroContentionCounter _codeDbCache = new(); [Description("Number of Code DB cache reads on thread.")] internal static long ThreadLocalCodeDbCache => _codeDbCache.ThreadLocalValue; + + /// + /// Gets thread-local code DB cache count. Use this for external access. + /// + public static long GetThreadLocalCodeDbCache() => _codeDbCache.ThreadLocalValue; internal static void IncrementCodeDbCache() => _codeDbCache.Increment(); [CounterMetric] [Description("Number of EVM exceptions thrown by contracts.")] @@ -168,6 +173,18 @@ internal static float BlockEstMedianGasPrice } } + /// + /// Gets block gas price data for external access. Returns (min, estMedian, ave, max). + /// Returns null if no gas data available (min is float.MaxValue). + /// + public static (float Min, float EstMedian, float Ave, float Max)? GetBlockGasPrices() + { + if (_blockMinGasPrice == float.MaxValue) + return null; + + return (_blockMinGasPrice, _blockEstMedianGasPrice, _blockAveGasPrice, _blockMaxGasPrice); + } + [GaugeMetric] [Description("Minimum tx gas price in block")] public static float GasPriceMin { get; private set; } diff --git a/src/Nethermind/Nethermind.Evm/Precompiles/PrecompileInfo.cs b/src/Nethermind/Nethermind.Evm/Precompiles/PrecompileInfo.cs deleted file mode 100644 index 66c6da879ea4..000000000000 --- a/src/Nethermind/Nethermind.Evm/Precompiles/PrecompileInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Evm.Precompiles; - -namespace Nethermind.Evm.CodeAnalysis; - -public sealed class PrecompileInfo(IPrecompile precompile) : ICodeInfo -{ - public ReadOnlyMemory Code => Array.Empty(); - ReadOnlySpan ICodeInfo.CodeSpan => Code.Span; - public IPrecompile? Precompile { get; } = precompile; - - public bool IsPrecompile => true; - public bool IsEmpty => false; -} diff --git a/src/Nethermind/Nethermind.Evm/TransactionCollisionException.cs b/src/Nethermind/Nethermind.Evm/TransactionCollisionException.cs deleted file mode 100644 index 0c577ef83c04..000000000000 --- a/src/Nethermind/Nethermind.Evm/TransactionCollisionException.cs +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Evm -{ - public class TransactionCollisionException : EvmException - { - public override EvmExceptionType ExceptionType => EvmExceptionType.TransactionCollision; - } -} diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs index e927b73bf0ec..2ae5c1892b50 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs @@ -614,7 +614,7 @@ private TransactionResult BuildExecutionEnvironment( { Address recipient = tx.GetRecipient(tx.IsContractCreation ? WorldState.GetNonce(tx.SenderAddress!) : 0); if (recipient is null) ThrowInvalidDataException("Recipient has not been resolved properly before tx execution"); - ICodeInfo? codeInfo; + CodeInfo? codeInfo; ReadOnlyMemory inputData = tx.IsMessageCall ? tx.Data : default; if (tx.IsContractCreation) { diff --git a/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs b/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs index f335e854c52e..624ac196f00a 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs @@ -52,7 +52,7 @@ public readonly ref struct TransactionSubstate public string? Error { get; } public string? SubstateError { get; } public EvmExceptionType EvmExceptionType { get; } - public (ICodeInfo DeployCode, ReadOnlyMemory Bytes) Output { get; } + public (CodeInfo DeployCode, ReadOnlyMemory Bytes) Output { get; } public bool ShouldRevert { get; } public long Refund { get; } public IToArrayCollection Logs => _logs ?? _emptyLogs; @@ -80,7 +80,7 @@ private TransactionSubstate(string errorCode) ShouldRevert = true; } - public TransactionSubstate((ICodeInfo eofDeployCode, ReadOnlyMemory bytes) output, + public TransactionSubstate((CodeInfo eofDeployCode, ReadOnlyMemory bytes) output, long refund, IHashSetEnumerableCollection
destroyList, IToArrayCollection logs, diff --git a/src/Nethermind/Nethermind.Facade.Test/LogIndexBuilderTests.cs b/src/Nethermind/Nethermind.Facade.Test/LogIndexBuilderTests.cs new file mode 100644 index 000000000000..8f5977660136 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade.Test/LogIndexBuilderTests.cs @@ -0,0 +1,273 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Receipts; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Events; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test; +using Nethermind.Core.Test.Builders; +using Nethermind.Db.LogIndex; +using Nethermind.Facade.Find; +using Nethermind.Logging; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Facade.Test; + +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] +public class LogIndexBuilderTests +{ + private class TestLogIndexStorage : ILogIndexStorage + { + private int? _minBlockNumber; + private int? _maxBlockNumber; + + public bool Enabled => true; + + public event EventHandler? NewMaxBlockNumber; + public event EventHandler? NewMinBlockNumber; + + public int? MinBlockNumber + { + get => _minBlockNumber; + init => _minBlockNumber = value; + } + + public int? MaxBlockNumber + { + get => _maxBlockNumber; + init => _maxBlockNumber = value; + } + + public IEnumerator GetEnumerator(Address address, int from, int to) => + throw new NotImplementedException(); + + public IEnumerator GetEnumerator(int topicIndex, Hash256 topic, int from, int to) => + throw new NotImplementedException(); + + public string GetDbSize() => 0L.SizeToString(); + + public LogIndexAggregate Aggregate(IReadOnlyList batch, bool isBackwardSync, LogIndexUpdateStats? stats = null) => + new(batch); + + public virtual Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null) + { + var min = Math.Min(aggregate.FirstBlockNum, aggregate.LastBlockNum); + var max = Math.Max(aggregate.FirstBlockNum, aggregate.LastBlockNum); + + if (_minBlockNumber is null || min < _minBlockNumber) + { + if (_minBlockNumber is not null && max != _minBlockNumber - 1) + throw new InvalidOperationException("Invalid receipts order."); + + _minBlockNumber = min; + NewMinBlockNumber?.Invoke(this, min); + } + + if (_maxBlockNumber is null || max > _maxBlockNumber) + { + if (_maxBlockNumber is not null && min != _maxBlockNumber + 1) + throw new InvalidOperationException("Invalid receipts order."); + + _maxBlockNumber = max; + NewMaxBlockNumber?.Invoke(this, max); + } + + return Task.CompletedTask; + } + + public Task RemoveReorgedAsync(BlockReceipts block) => Task.CompletedTask; + + public Task StopAsync() => Task.CompletedTask; + + public ValueTask DisposeAsync() => ValueTask.CompletedTask; + } + + private class FailingLogIndexStorage(int failAfter, Exception exception) : TestLogIndexStorage + { + private int _callCount; + + public override Task AddReceiptsAsync(LogIndexAggregate aggregate, LogIndexUpdateStats? stats = null) + { + return Interlocked.Increment(ref _callCount) <= failAfter + ? base.AddReceiptsAsync(aggregate, stats) + : throw exception; + } + } + + private const int MaxReorgDepth = 8; + private const int MaxBlock = 100; + private const int MaxSyncBlock = MaxBlock - MaxReorgDepth; + private const int BatchSize = 10; + + private ILogIndexConfig _config = null!; + private IBlockTree _blockTree = null!; + private ISyncConfig _syncConfig = null!; + private IReceiptStorage _receiptStorage = null!; + private ILogManager _logManager = null!; + private List _testDisposables = null!; + + [SetUp] + public void SetUp() + { + _config = new LogIndexConfig { Enabled = true, MaxReorgDepth = MaxReorgDepth, MaxBatchSize = BatchSize }; + _blockTree = Build.A.BlockTree().OfChainLength(MaxBlock + 1).BlockTree; + _syncConfig = new SyncConfig { FastSync = true, SnapSync = true }; + _receiptStorage = Substitute.For(); + _logManager = new TestLogManager(); + _testDisposables = []; + + Block head = _blockTree.Head!; + _blockTree.SyncPivot = (head.Number, head.Hash); + _syncConfig.PivotNumber = _blockTree.SyncPivot.BlockNumber; + + _receiptStorage + .Get(Arg.Any()) + .Returns(c => []); + } + + [TearDown] + public async Task TearDownAsync() + { + foreach (var disposable in _testDisposables) + { + if (disposable is IAsyncDisposable asyncDisposable) + await asyncDisposable.DisposeAsync(); + else if (disposable is IDisposable disposable1) + disposable1.Dispose(); + } + } + + private LogIndexBuilder GetService(ILogIndexStorage logIndexStorage) + { + return new LogIndexBuilder( + logIndexStorage, _config, _blockTree, _syncConfig, _receiptStorage, _logManager + ).AddTo(_testDisposables); + } + + [Test] + [CancelAfter(60_000)] + public async Task Should_SyncToBarrier( + [Values(1, 10)] int minBarrier, + [Values(1, 16, MaxBlock)] int batchSize, + [Values( + new[] { -1, -1 }, // -1 is treated as null + new[] { 0, MaxSyncBlock / 2 }, + new[] { MaxSyncBlock / 2, MaxSyncBlock / 2 }, + new[] { MaxSyncBlock / 2, MaxSyncBlock }, + new[] { 5, MaxSyncBlock - 5 } + )] + int[] synced, + CancellationToken cancellation + ) + { + _config.MaxBatchSize = batchSize; + _syncConfig.AncientReceiptsBarrier = minBarrier; + Assert.That(_syncConfig.AncientReceiptsBarrierCalc, Is.EqualTo(minBarrier)); + + var expectedMin = minBarrier <= 1 ? 0 : synced[0] < 0 ? minBarrier : Math.Min(synced[0], minBarrier); + var storage = new TestLogIndexStorage + { + MinBlockNumber = synced[0] < 0 ? null : synced[0], + MaxBlockNumber = synced[1] < 0 ? null : synced[1] + }; + + LogIndexBuilder builder = GetService(storage); + + Task completion = WaitBlocksAsync(storage, expectedMin, MaxSyncBlock, cancellation); + await builder.StartAsync(); + await completion; + + using (Assert.EnterMultipleScope()) + { + Assert.That(builder.LastError, Is.Null); + + Assert.That(storage.MinBlockNumber, Is.EqualTo(expectedMin)); + Assert.That(storage.MaxBlockNumber, Is.EqualTo(MaxSyncBlock)); + } + } + + [Test] + public async Task Should_ForwardError( + [Values(0, 1, 4)] int failAfter + ) + { + var exception = new Exception(nameof(Should_ForwardError)); + LogIndexBuilder builder = GetService(new FailingLogIndexStorage(failAfter, exception)); + + await builder.StartAsync(); + + using (Assert.EnterMultipleScope()) + { + Exception thrown = Assert.ThrowsAsync(() => builder.BackwardSyncCompletion.WaitAsync(TimeSpan.FromSeconds(999))); + Assert.That(thrown, Is.EqualTo(exception)); + Assert.That(builder.LastError, Is.EqualTo(exception)); + } + } + + [Test] + [Sequential] + public async Task Should_CompleteImmediately_IfAlreadySynced( + [Values(1, 10, 10, 10)] int minBarrier, + [Values(0, 00, 05, 10)] int minBlock + ) + { + Assert.That(minBlock, Is.LessThanOrEqualTo(minBarrier)); + + _syncConfig.AncientReceiptsBarrier = minBarrier; + LogIndexBuilder builder = GetService(new FailingLogIndexStorage(0, new("Should not set new receipts.")) + { + MinBlockNumber = minBlock, + MaxBlockNumber = MaxSyncBlock + }); + + await builder.StartAsync(); + + using (Assert.EnterMultipleScope()) + { + Assert.That(builder.BackwardSyncCompletion.IsCompleted); + Assert.That(builder.LastError, Is.Null); + Assert.That(builder.LastUpdate, Is.Null); + } + } + + private static Task WaitMaxBlockAsync(TestLogIndexStorage storage, int blockNumber, CancellationToken cancellation) + { + if (storage.MaxBlockNumber >= blockNumber) + return Task.CompletedTask; + + return Wait.ForEventCondition( + cancellation, + e => storage.NewMaxBlockNumber += e, + e => storage.NewMaxBlockNumber -= e, + e => e >= blockNumber + ); + } + + private static Task WaitMinBlockAsync(TestLogIndexStorage storage, int blockNumber, CancellationToken cancellation) + { + if (storage.MinBlockNumber <= blockNumber) + return Task.CompletedTask; + + return Wait.ForEventCondition( + cancellation, + e => storage.NewMinBlockNumber += e, + e => storage.NewMinBlockNumber -= e, + e => e <= blockNumber + ); + } + + private static Task WaitBlocksAsync(TestLogIndexStorage storage, int minBlock, int maxBlock, CancellationToken cancellation) => Task.WhenAll( + WaitMinBlockAsync(storage, minBlock, cancellation), + WaitMaxBlockAsync(storage, maxBlock, cancellation) + ); +} diff --git a/src/Nethermind/Nethermind.Facade/Eth/FacadeJsonContext.cs b/src/Nethermind/Nethermind.Facade/Eth/FacadeJsonContext.cs new file mode 100644 index 000000000000..4204be813233 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Eth/FacadeJsonContext.cs @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json.Serialization; +using Nethermind.Facade.Eth.RpcTransaction; +using Nethermind.Facade.Filters; + +namespace Nethermind.Facade.Eth; + +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Metadata, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(BlockForRpc))] +[JsonSerializable(typeof(FilterLog))] +[JsonSerializable(typeof(TransactionForRpc))] +[JsonSerializable(typeof(SyncingResult))] +public partial class FacadeJsonContext : JsonSerializerContext; diff --git a/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs index fc17885d64b6..e15f2cc5aaea 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/LogFilter.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Blockchain.Filters.Topics; @@ -19,6 +19,11 @@ public class LogFilter( public TopicsFilter TopicsFilter { get; } = topicsFilter; public BlockParameter FromBlock { get; } = fromBlock; public BlockParameter ToBlock { get; } = toBlock; + public bool UseIndex { get; set; } = true; + + public bool AcceptsAnyBlock => + AddressFilter.Addresses.Count == 0 && + TopicsFilter.AcceptsAnyBlock; public bool Accepts(LogEntry logEntry) => AddressFilter.Accepts(logEntry.Address) && TopicsFilter.Accepts(logEntry); diff --git a/src/Nethermind/Nethermind.Facade/Filters/LogIndexFilterVisitor.cs b/src/Nethermind/Nethermind.Facade/Filters/LogIndexFilterVisitor.cs new file mode 100644 index 000000000000..824c21538065 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Filters/LogIndexFilterVisitor.cs @@ -0,0 +1,187 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Nethermind.Blockchain.Filters; +using Nethermind.Blockchain.Filters.Topics; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Db.LogIndex; + +[assembly: InternalsVisibleTo("Nethermind.Blockchain.Test")] + +namespace Nethermind.Facade.Filters; + +/// +/// Converts tree and block range into an enumerator of block numbers from , +/// by building corresponding "tree of enumerators". +/// +public class LogIndexFilterVisitor(ILogIndexStorage storage, LogFilter filter, int fromBlock, int toBlock) : IEnumerable +{ + internal sealed class IntersectEnumerator(IEnumerator e1, IEnumerator e2) : IEnumerator + { + public bool MoveNext() + { + bool has1 = e1.MoveNext(); + bool has2 = e2.MoveNext(); + + while (has1 && has2) + { + int c1 = e1.Current; + int c2 = e2.Current; + if (c1 == c2) + { + Current = c1; + return true; + } + + if (c1 < c2) has1 = e1.MoveNext(); + else has2 = e2.MoveNext(); + } + + return false; + } + + public void Reset() + { + e1.Reset(); + e2.Reset(); + } + + public int Current { get; private set; } + + object? IEnumerator.Current => Current; + + public void Dispose() + { + e1.Dispose(); + e2.Dispose(); + } + } + + internal sealed class UnionEnumerator(IEnumerator e1, IEnumerator e2) : IEnumerator + { + private bool _has1 = e1.MoveNext(); + private bool _has2 = e2.MoveNext(); + + public bool MoveNext() => + (_has1, _has2) switch + { + (true, true) => e1.Current.CompareTo(e2.Current) switch + { + 0 => MoveNext(e1, out _has1) && MoveNext(e2, out _has2), + < 0 => MoveNext(e1, out _has1), + > 0 => MoveNext(e2, out _has2) + }, + (true, false) => MoveNext(e1, out _has1), + (false, true) => MoveNext(e2, out _has2), + (false, false) => false + }; + + private bool MoveNext(IEnumerator enumerator, out bool has) + { + Current = enumerator.Current; + has = enumerator.MoveNext(); + return true; + } + + public void Reset() + { + e1.Reset(); + e2.Reset(); + _has1 = e1.MoveNext(); + _has2 = e2.MoveNext(); + } + + public int Current { get; private set; } + + object? IEnumerator.Current => Current; + + public void Dispose() + { + e1.Dispose(); + e2.Dispose(); + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IEnumerator GetEnumerator() + { + IEnumerator? addressEnumerator = Visit(filter.AddressFilter); + IEnumerator? topicEnumerator = Visit(filter.TopicsFilter); + + if (addressEnumerator is not null && topicEnumerator is not null) + return new IntersectEnumerator(addressEnumerator, topicEnumerator); + + return addressEnumerator ?? topicEnumerator ?? throw new InvalidOperationException("Provided filter covers whole block range."); + } + + private IEnumerator? Visit(AddressFilter addressFilter) + { + IEnumerator? result = null; + + foreach (AddressAsKey address in addressFilter.Addresses) + { + IEnumerator next = Visit(address); + result = result is null ? next : new UnionEnumerator(result, next); + } + + return result; + } + + private IEnumerator? Visit(TopicsFilter topicsFilter) + { + IEnumerator result = null; + + var topicIndex = 0; + foreach (TopicExpression expression in topicsFilter.Expressions) + { + if (Visit(topicIndex++, expression) is not { } next) + continue; + + result = result is null ? next : new IntersectEnumerator(result, next); + } + + return result; + } + + private IEnumerator? Visit(int topicIndex, TopicExpression expression) => expression switch + { + AnyTopic => null, + OrExpression orExpression => Visit(topicIndex, orExpression), + SpecificTopic specificTopic => Visit(topicIndex, specificTopic.Topic), + _ => throw new ArgumentOutOfRangeException($"Unknown topic expression type: {expression.GetType().Name}.") + }; + + private IEnumerator? Visit(int topicIndex, OrExpression orExpression) + { + IEnumerator? result = null; + + foreach (TopicExpression expression in orExpression.SubExpressions) + { + if (Visit(topicIndex, expression) is not { } next) + continue; + + result = result is null ? next : new UnionEnumerator(result, next); + } + + return result; + } + + private IEnumerator Visit(Address address) => + storage.GetEnumerator(address, fromBlock, toBlock); + + private IEnumerator Visit(int topicIndex, Hash256 topic) => + storage.GetEnumerator(topicIndex, topic, fromBlock, toBlock); +} + +public static class LogIndexFilterVisitorExtensions +{ + public static IEnumerable EnumerateBlockNumbersFor(this ILogIndexStorage storage, LogFilter filter, long fromBlock, long toBlock) => + new LogIndexFilterVisitor(storage, filter, (int)fromBlock, (int)toBlock).Select(static i => (long)i); +} diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopic.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopic.cs index f8278d7ff632..bce87e8886f3 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopic.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopic.cs @@ -10,6 +10,8 @@ public class AnyTopic : TopicExpression { public static readonly AnyTopic Instance = new(); + public override bool AcceptsAnyBlock => true; + private AnyTopic() { } public override bool Accepts(Hash256 topic) => true; diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs index 7bf063bfb60c..a1b8c1bed972 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; +using System.Linq; using Nethermind.Blockchain.Receipts; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -10,6 +12,9 @@ namespace Nethermind.Blockchain.Filters.Topics { public class AnyTopicsFilter(params TopicExpression[] expressions) : TopicsFilter { + public override IEnumerable Expressions => expressions; + public override bool AcceptsAnyBlock => expressions.Length == 0 || expressions.Any(static e => e.AcceptsAnyBlock); + public override bool Accepts(LogEntry entry) => Accepts(entry.Topics); private bool Accepts(Hash256[] topics) diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/OrExpression.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/OrExpression.cs index d1d7801b43d1..406e4ee48671 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/OrExpression.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/OrExpression.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; +using System.Linq; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -11,6 +13,9 @@ public class OrExpression : TopicExpression, IEquatable { private readonly TopicExpression[] _subexpressions; + public IEnumerable SubExpressions => _subexpressions; + public override bool AcceptsAnyBlock => _subexpressions.Any(static e => e.AcceptsAnyBlock); + public OrExpression(params TopicExpression[] subexpressions) { _subexpressions = subexpressions; diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs index 69807f781ab0..1e490caa472a 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; +using System.Linq; using Nethermind.Blockchain.Receipts; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -15,6 +17,9 @@ public class SequenceTopicsFilter(params TopicExpression[] expressions) private readonly TopicExpression[] _expressions = expressions; + public override IEnumerable Expressions => _expressions; + public override bool AcceptsAnyBlock => _expressions.Length == 0 || _expressions.All(static e => e.AcceptsAnyBlock); + public override bool Accepts(LogEntry entry) => Accepts(entry.Topics); private bool Accepts(Hash256[] topics) diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/SpecificTopic.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/SpecificTopic.cs index a9dee55bc8c1..91aa003889c4 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/SpecificTopic.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/SpecificTopic.cs @@ -11,6 +11,9 @@ public class SpecificTopic : TopicExpression private readonly Hash256 _topic; private Bloom.BloomExtract _bloomExtract; + public Hash256 Topic => _topic; + public override bool AcceptsAnyBlock => false; + public SpecificTopic(Hash256 topic) { _topic = topic; diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicExpression.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicExpression.cs index 5193e0bfba8b..f344435bc680 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicExpression.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicExpression.cs @@ -8,6 +8,8 @@ namespace Nethermind.Blockchain.Filters.Topics { public abstract class TopicExpression { + public abstract bool AcceptsAnyBlock { get; } + public abstract bool Accepts(Hash256 topic); public abstract bool Accepts(ref Hash256StructRef topic); diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicsFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicsFilter.cs index 0cdc7ece380f..17b15c591c53 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicsFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/TopicsFilter.cs @@ -1,12 +1,16 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Collections.Generic; using Nethermind.Core; namespace Nethermind.Blockchain.Filters.Topics { public abstract class TopicsFilter { + public abstract IEnumerable Expressions { get; } + public abstract bool AcceptsAnyBlock { get; } + public abstract bool Accepts(LogEntry entry); public abstract bool Accepts(ref LogEntryStructRef entry); diff --git a/src/Nethermind/Nethermind.Facade/Find/ILogIndexBuilder.cs b/src/Nethermind/Nethermind.Facade/Find/ILogIndexBuilder.cs new file mode 100644 index 000000000000..3f898f360640 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Find/ILogIndexBuilder.cs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading.Tasks; +using Nethermind.Core.ServiceStopper; + +namespace Nethermind.Facade.Find +{ + public interface ILogIndexBuilder : IAsyncDisposable, IStoppableService + { + Task StartAsync(); + bool IsRunning { get; } + + int MaxTargetBlockNumber { get; } + int MinTargetBlockNumber { get; } + + DateTimeOffset? LastUpdate { get; } + Exception? LastError { get; } + } +} diff --git a/src/Nethermind/Nethermind.Facade/Find/IndexedLogFinder.cs b/src/Nethermind/Nethermind.Facade/Find/IndexedLogFinder.cs new file mode 100644 index 000000000000..2c9827d6c607 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Find/IndexedLogFinder.cs @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Threading; +using Nethermind.Blockchain.Filters; +using Nethermind.Blockchain.Find; +using Nethermind.Blockchain.Receipts; +using Nethermind.Core; +using Nethermind.Db.Blooms; +using Nethermind.Db.LogIndex; +using Nethermind.Facade.Filters; +using Nethermind.Logging; + +namespace Nethermind.Facade.Find; + +/// +/// Extended that adds log index support for faster eth_getLogs queries. +/// When the log index is available and applicable, it uses the index to identify relevant blocks +/// before fetching logs from those specific blocks. +/// +public class IndexedLogFinder( + IBlockFinder blockFinder, + IReceiptFinder receiptFinder, + IReceiptStorage receiptStorage, + IBloomStorage bloomStorage, + ILogManager logManager, + IReceiptsRecovery receiptsRecovery, + ILogIndexStorage logIndexStorage, + int maxBlockDepth = 1000, + int minBlocksToUseIndex = 32) + : LogFinder(blockFinder, receiptFinder, receiptStorage, bloomStorage, logManager, receiptsRecovery, maxBlockDepth) +{ + private readonly ILogIndexStorage _logIndexStorage = logIndexStorage ?? throw new ArgumentNullException(nameof(logIndexStorage)); + + public override IEnumerable FindLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken = default) => + GetLogIndexRange(filter, fromBlock, toBlock) is not { } indexRange + ? base.FindLogs(filter, fromBlock, toBlock, cancellationToken) + : FindIndexedLogs(filter, fromBlock, toBlock, indexRange, cancellationToken); + + private IEnumerable FindIndexedLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, (int from, int to) indexRange, CancellationToken cancellationToken) + { + if (indexRange.from > fromBlock.Number && FindHeaderOrLogError(indexRange.from - 1, cancellationToken) is { } beforeIndex) + { + foreach (FilterLog log in base.FindLogs(filter, fromBlock, beforeIndex, cancellationToken)) + yield return log; + } + + cancellationToken.ThrowIfCancellationRequested(); + + IEnumerable indexNumbers = _logIndexStorage.EnumerateBlockNumbersFor(filter, indexRange.from, indexRange.to); + foreach (FilterLog log in FilterLogsInBlocksParallel(filter, indexNumbers, cancellationToken)) + { + yield return log; + } + + cancellationToken.ThrowIfCancellationRequested(); + + if (indexRange.to < toBlock.Number && FindHeaderOrLogError(indexRange.to + 1, cancellationToken) is { } afterIndex) + { + foreach (FilterLog log in base.FindLogs(filter, afterIndex, toBlock, cancellationToken)) + yield return log; + } + } + + private (int from, int to)? GetLogIndexRange(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock) + { + bool tryUseIndex = filter.UseIndex; + filter.UseIndex = false; + + if (!tryUseIndex || !_logIndexStorage.Enabled || filter.AcceptsAnyBlock) + return null; + + if (_logIndexStorage.MinBlockNumber is not { } indexFrom || _logIndexStorage.MaxBlockNumber is not { } indexTo) + return null; + + (int from, int to) range = ( + Math.Max((int)fromBlock.Number, indexFrom), + Math.Min((int)toBlock.Number, indexTo) + ); + + if (range.from > range.to) + return null; + + if (range.to - range.from + 1 < minBlocksToUseIndex) + return null; + + filter.UseIndex = true; + return range; + } +} diff --git a/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs b/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs index b1eddee6df12..d1b4d5636152 100644 --- a/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs +++ b/src/Nethermind/Nethermind.Facade/Find/LogFinder.cs @@ -18,37 +18,26 @@ namespace Nethermind.Facade.Find { - public class LogFinder : ILogFinder + public class LogFinder( + IBlockFinder? blockFinder, + IReceiptFinder? receiptFinder, + IReceiptStorage? receiptStorage, + IBloomStorage? bloomStorage, + ILogManager? logManager, + IReceiptsRecovery? receiptsRecovery, + int maxBlockDepth = 1000) + : ILogFinder { private static int ParallelExecutions = 0; private static int ParallelLock = 0; - private readonly IReceiptFinder _receiptFinder; - private readonly IReceiptStorage _receiptStorage; - private readonly IBloomStorage _bloomStorage; - private readonly IReceiptsRecovery _receiptsRecovery; - private readonly int _maxBlockDepth; - private readonly int _rpcConfigGetLogsThreads; - private readonly IBlockFinder _blockFinder; - private readonly ILogger _logger; - - public LogFinder(IBlockFinder? blockFinder, - IReceiptFinder? receiptFinder, - IReceiptStorage? receiptStorage, - IBloomStorage? bloomStorage, - ILogManager? logManager, - IReceiptsRecovery? receiptsRecovery, - int maxBlockDepth = 1000) - { - _blockFinder = blockFinder ?? throw new ArgumentNullException(nameof(blockFinder)); - _receiptFinder = receiptFinder ?? throw new ArgumentNullException(nameof(receiptFinder)); - _receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage)); ; - _bloomStorage = bloomStorage ?? throw new ArgumentNullException(nameof(bloomStorage)); - _receiptsRecovery = receiptsRecovery ?? throw new ArgumentNullException(nameof(receiptsRecovery)); - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - _maxBlockDepth = maxBlockDepth; - _rpcConfigGetLogsThreads = Math.Max(1, Environment.ProcessorCount / 4); - } + private readonly IReceiptFinder _receiptFinder = receiptFinder ?? throw new ArgumentNullException(nameof(receiptFinder)); + private readonly IReceiptStorage _receiptStorage = receiptStorage ?? throw new ArgumentNullException(nameof(receiptStorage)); + private readonly IBloomStorage _bloomStorage = bloomStorage ?? throw new ArgumentNullException(nameof(bloomStorage)); + private readonly IReceiptsRecovery _receiptsRecovery = receiptsRecovery ?? throw new ArgumentNullException(nameof(receiptsRecovery)); + private readonly int _rpcConfigGetLogsThreads = Math.Max(1, Environment.ProcessorCount / 4); + private readonly IBlockFinder _blockFinder = blockFinder ?? throw new ArgumentNullException(nameof(blockFinder)); + private readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); public IEnumerable FindLogs(LogFilter filter, CancellationToken cancellationToken = default) { @@ -65,7 +54,7 @@ BlockHeader FindHeader(BlockParameter blockParameter, string name, bool headLimi return FindLogs(filter, fromBlock, toBlock, cancellationToken); } - public IEnumerable FindLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken = default) + public virtual IEnumerable FindLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -97,36 +86,37 @@ public IEnumerable FindLogs(LogFilter filter, BlockHeader fromBlock, private static bool ShouldUseBloomDatabase(BlockHeader fromBlock, BlockHeader toBlock) { - var blocksToSearch = toBlock.Number - fromBlock.Number + 1; + long blocksToSearch = toBlock.Number - fromBlock.Number + 1; return blocksToSearch > 1; // if we are searching only in 1 block skip bloom index altogether, this can be tweaked } private IEnumerable FilterLogsWithBloomsIndex(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken) { - BlockHeader FindBlockHeader(long blockNumber, CancellationToken token) + IEnumerable EnumerateBlockNumbers(LogFilter f, long from, long to) { - token.ThrowIfCancellationRequested(); - var block = _blockFinder.FindHeader(blockNumber); - if (block is null) + IBloomEnumeration enumeration = _bloomStorage.GetBlooms(from, to); + foreach (Bloom bloom in enumeration) { - if (_logger.IsError) _logger.Error($"Could not find block {blockNumber} in database. eth_getLogs will return incomplete results."); + if (f.Matches(bloom) && enumeration.TryGetBlockNumber(out var blockNumber)) + { + yield return blockNumber; + } } - - return block; } - IEnumerable FilterBlocks(LogFilter f, long @from, long to, bool runParallel, CancellationToken token) + return FilterLogsInBlocksParallel(filter, EnumerateBlockNumbers(filter, fromBlock.Number, toBlock.Number), cancellationToken); + } + + protected IEnumerable FilterLogsInBlocksParallel(LogFilter filter, IEnumerable blockNumbers, CancellationToken cancellationToken) + { + static IEnumerable ParallelizeWithLock(IEnumerable blocks, bool runParallel, CancellationToken ct) { try { - var enumeration = _bloomStorage.GetBlooms(from, to); - foreach (var bloom in enumeration) + foreach (long blockNumber in blocks) { - token.ThrowIfCancellationRequested(); - if (f.Matches(bloom) && enumeration.TryGetBlockNumber(out var blockNumber)) - { - yield return blockNumber; - } + yield return blockNumber; + ct.ThrowIfCancellationRequested(); } } finally @@ -145,7 +135,7 @@ IEnumerable FilterBlocks(LogFilter f, long @from, long to, bool runParalle int parallelExecutions = Interlocked.Increment(ref ParallelExecutions) - 1; bool canRunParallel = parallelLock == 0; - IEnumerable filterBlocks = FilterBlocks(filter, fromBlock.Number, toBlock.Number, canRunParallel, cancellationToken); + IEnumerable filterBlocks = ParallelizeWithLock(blockNumbers, canRunParallel, cancellationToken); if (canRunParallel) { @@ -160,7 +150,7 @@ IEnumerable FilterBlocks(LogFilter f, long @from, long to, bool runParalle } return filterBlocks - .SelectMany(blockNumber => FindLogsInBlock(filter, FindBlockHeader(blockNumber, cancellationToken), cancellationToken)); + .SelectMany(blockNumber => FindLogsInBlock(filter, FindHeaderOrLogError(blockNumber, cancellationToken), cancellationToken)); } private bool CanUseBloomDatabase(BlockHeader toBlock, BlockHeader fromBlock) @@ -191,7 +181,7 @@ private bool CanUseBloomDatabase(BlockHeader toBlock, BlockHeader fromBlock) private IEnumerable FilterLogsIteratively(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken) { int count = 0; - while (count < _maxBlockDepth && fromBlock.Number <= (toBlock?.Number ?? fromBlock.Number)) + while (count < maxBlockDepth && fromBlock.Number <= (toBlock?.Number ?? fromBlock.Number)) { foreach (var filterLog in FindLogsInBlock(filter, fromBlock, cancellationToken)) { @@ -206,11 +196,11 @@ private IEnumerable FilterLogsIteratively(LogFilter filter, BlockHead } private IEnumerable FindLogsInBlock(LogFilter filter, BlockHeader block, CancellationToken cancellationToken) => - filter.Matches(block.Bloom) + filter.Matches(block.Bloom!) ? FindLogsInBlock(filter, block.Hash, block.Number, block.Timestamp, cancellationToken) : []; - private IEnumerable FindLogsInBlock(LogFilter filter, Hash256 blockHash, long blockNumber, ulong blockTimestamp, CancellationToken cancellationToken) + private IEnumerable FindLogsInBlock(LogFilter filter, Hash256? blockHash, long blockNumber, ulong blockTimestamp, CancellationToken cancellationToken) { if (blockHash is not null) { @@ -348,5 +338,18 @@ void RecoverReceiptsData(Hash256 hash, TxReceipt[] receipts) } } } + + protected BlockHeader? FindHeaderOrLogError(long blockNumber, CancellationToken token) + { + token.ThrowIfCancellationRequested(); + + BlockHeader? block = _blockFinder.FindHeader(blockNumber); + if (block is null && _logger.IsError) + { + _logger.Error($"Could not find block {blockNumber} in database. eth_getLogs will return incomplete results."); + } + + return block; + } } } diff --git a/src/Nethermind/Nethermind.Facade/Find/LogIndexBuilder.cs b/src/Nethermind/Nethermind.Facade/Find/LogIndexBuilder.cs new file mode 100644 index 000000000000..3112b36545f6 --- /dev/null +++ b/src/Nethermind/Nethermind.Facade/Find/LogIndexBuilder.cs @@ -0,0 +1,495 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Receipts; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Core; +using Nethermind.Db.LogIndex; +using Nethermind.Logging; +using Timer = System.Timers.Timer; +using static System.Threading.Tasks.TaskCreationOptions; + +namespace Nethermind.Facade.Find; + +// TODO: reduce periodic logging +public sealed class LogIndexBuilder : ILogIndexBuilder +{ + private sealed class ProcessingQueue( + TransformBlock, LogIndexAggregate> aggregateBlock, + ActionBlock addReceiptsBlock) + { + public int QueueCount => aggregateBlock.InputCount + addReceiptsBlock.InputCount; + public Task WriteAsync(IReadOnlyList batch, CancellationToken cancellation) => aggregateBlock.SendAsync(batch, cancellation); + public Task Completion => Task.WhenAll(aggregateBlock.Completion, addReceiptsBlock.Completion); + } + + private struct DirectionState() + { + public ProcessingQueue? Queue; + public ProgressLogger? Progress; + public readonly TaskCompletionSource Completion = new(RunContinuationsAsynchronously); + } + + [InlineArray(2)] + private struct DirectionStates + { + private DirectionState _element; + } + + private readonly IBlockTree _blockTree; + private readonly ISyncConfig _syncConfig; + private readonly ILogger _logger; + + private readonly CancellationTokenSource _cancellationSource = new(); + private CancellationToken CancellationToken => _cancellationSource.Token; + + private int MaxReorgDepth => _config.MaxReorgDepth!.Value; + private static readonly TimeSpan NewBlockWaitTimeout = TimeSpan.FromSeconds(5); + + private readonly ILogIndexStorage _logIndexStorage; + private readonly ILogIndexConfig _config; + private readonly IReceiptStorage _receiptStorage; + private readonly ILogManager _logManager; + private Timer? _progressLoggerTimer; + + private readonly TaskCompletionSource _pivotSource = new(RunContinuationsAsynchronously); + private readonly Task _pivotTask; + + private readonly List _tasks = new(); + + private DirectionStates _directions; + + private ref DirectionState Direction(bool isForward) => ref _directions[isForward ? 1 : 0]; + + private LogIndexUpdateStats _stats; + + public string Description => "log index builder"; + + public Task BackwardSyncCompletion => Direction(isForward: false).Completion.Task; + + public LogIndexBuilder(ILogIndexStorage logIndexStorage, ILogIndexConfig config, + IBlockTree blockTree, ISyncConfig syncConfig, IReceiptStorage receiptStorage, + ILogManager logManager) + { + ArgumentNullException.ThrowIfNull(logIndexStorage); + ArgumentNullException.ThrowIfNull(blockTree); + ArgumentNullException.ThrowIfNull(receiptStorage); + ArgumentNullException.ThrowIfNull(logManager); + ArgumentNullException.ThrowIfNull(syncConfig); + + _config = config; + _logIndexStorage = logIndexStorage; + _blockTree = blockTree; + _syncConfig = syncConfig; + _receiptStorage = receiptStorage; + _logManager = logManager; + _logger = logManager.GetClassLogger(); + _pivotTask = _pivotSource.Task; + _stats = new(_logIndexStorage); + + Direction(isForward: false) = new(); + Direction(isForward: true) = new(); + } + + private void StartProcessing(bool isForward) + { + // Do not start backward sync if the target is already reached + if (!isForward && _logIndexStorage.MinBlockNumber <= MinTargetBlockNumber) + { + MarkCompleted(false); + return; + } + + ref DirectionState dir = ref Direction(isForward); + dir.Queue = BuildQueue(isForward); + dir.Progress = new(GetLogPrefix(isForward), _logManager); + + _tasks.AddRange( + Task.Run(() => DoQueueBlocks(isForward), CancellationToken), + dir.Queue.Completion + ); + } + + public async Task StartAsync() + { + try + { + if (!_config.Enabled) + return; + + _receiptStorage.ReceiptsInserted += OnReceiptsInserted; + + TrySetPivot(_logIndexStorage.MaxBlockNumber); + TrySetPivot((int)_blockTree.SyncPivot.BlockNumber); + + if (!_pivotTask.IsCompleted && _logger.IsInfo) + _logger.Info($"{GetLogPrefix()}: waiting for the first block..."); + + await _pivotTask; + + StartProcessing(isForward: true); + StartProcessing(isForward: false); + + UpdateProgress(); + LogProgress(); + + _progressLoggerTimer = new(TimeSpan.FromSeconds(30)); + _progressLoggerTimer.AutoReset = true; + _progressLoggerTimer.Elapsed += (_, _) => LogProgress(); + _progressLoggerTimer.Start(); + + IsRunning = true; + } + catch (Exception ex) + { + await HandleExceptionAsync(ex); + } + } + + public async Task StopAsync() + { + if (!_config.Enabled) + return; + + await _cancellationSource.CancelAsync(); + + _pivotSource.TrySetCanceled(CancellationToken); + _progressLoggerTimer?.Stop(); + + foreach (Task task in _tasks) + { + try + { + await task; + } + catch (Exception ex) + { + await HandleExceptionAsync(ex, isStopping: true); + } + } + + await _logIndexStorage.StopAsync(); + + IsRunning = false; + } + + public async ValueTask DisposeAsync() + { + await _logIndexStorage.DisposeAsync(); + _progressLoggerTimer?.Dispose(); + _cancellationSource.Dispose(); + } + + private void LogStats() + { + LogIndexUpdateStats stats = _stats; + + if (stats is not { BlocksAdded: > 0 }) + return; + + _stats = new(_logIndexStorage); + + if (_logger.IsInfo) + { + _logger.Info(_config.DetailedLogs + ? $"{GetLogPrefix()}: {stats:d}" + : $"{GetLogPrefix()}: {stats}" + ); + } + } + + private void LogProgress() + { + LogStats(); + Direction(isForward: false).Progress?.LogProgress(); + Direction(isForward: true).Progress?.LogProgress(); + } + + private bool TrySetPivot(int? blockNumber) + { + if (blockNumber is not { } number || number is 0) + return false; + + if (_pivotSource.Task.IsCompleted) + return false; + + number = Math.Max(MinTargetBlockNumber, number); + number = Math.Min(MaxTargetBlockNumber, number); + + if (number is 0) + return false; + + if (!TryGetBlockReceipts(number, out _)) + return false; + + if (!_pivotSource.TrySetResult(number)) + return false; + + _logger.Info($"{GetLogPrefix()}: using block {number} as pivot."); + return true; + } + + private void OnReceiptsInserted(object? sender, ReceiptsEventArgs args) + { + int next = (int)args.BlockHeader.Number; + if (TrySetPivot(next)) + _receiptStorage.ReceiptsInserted -= OnReceiptsInserted; + } + + public int MaxTargetBlockNumber => (int)Math.Max(_blockTree.BestKnownNumber - MaxReorgDepth, 0); + + // Block 0 should always be present + public int MinTargetBlockNumber => (int)(_syncConfig.AncientReceiptsBarrierCalc <= 1 ? 0 : _syncConfig.AncientReceiptsBarrierCalc); + + public bool IsRunning { get; private set; } + public DateTimeOffset? LastUpdate { get; private set; } + public Exception? LastError { get; private set; } + + private ProcessingQueue BuildQueue(bool isForward) + { + var aggregateBlock = new TransformBlock, LogIndexAggregate>( + batch => Aggregate(batch, isForward), + new() + { + BoundedCapacity = _config.MaxAggregationQueueSize, + MaxDegreeOfParallelism = _config.MaxAggregationParallelism, + CancellationToken = CancellationToken, + SingleProducerConstrained = true + } + ); + + var addReceiptsBlock = new ActionBlock( + aggr => AddReceiptsAsync(aggr, isForward), + new() + { + BoundedCapacity = _config.MaxSavingQueueSize, + MaxDegreeOfParallelism = 1, + CancellationToken = CancellationToken, + SingleProducerConstrained = true + } + ); + + aggregateBlock.Completion.ContinueWith(t => HandleExceptionAsync(t.Exception), TaskContinuationOptions.OnlyOnFaulted); + addReceiptsBlock.Completion.ContinueWith(t => HandleExceptionAsync(t.Exception), TaskContinuationOptions.OnlyOnFaulted); + + aggregateBlock.LinkTo(addReceiptsBlock, new() { PropagateCompletion = true }); + return new(aggregateBlock, addReceiptsBlock); + } + + private async Task HandleExceptionAsync(Exception? exception, bool isStopping = false) + { + if (exception is null) + return; + + if (exception is AggregateException a) + exception = a.InnerException; + + if (exception is OperationCanceledException oc && oc.CancellationToken == CancellationToken) + return; // Cancelled + + if (_logger.IsError) + _logger.Error($"{GetLogPrefix()} failed. Please restart the client.", exception); + + LastError = exception; + + Direction(isForward: false).Completion.TrySetException(exception!); + Direction(isForward: true).Completion.TrySetException(exception!); + + if (!isStopping) + await StopAsync(); + } + + private LogIndexAggregate Aggregate(IReadOnlyList batch, bool isForward) => _logIndexStorage.Aggregate(batch, !isForward, _stats); + + private async Task AddReceiptsAsync(LogIndexAggregate aggregate, bool isForward) + { + if (GetNextBlockNumber(_logIndexStorage, isForward) is { } next && next != aggregate.FirstBlockNum) + throw new($"{GetLogPrefix(isForward)}: non sequential batches: ({aggregate.FirstBlockNum} instead of {next})."); + + await _logIndexStorage.AddReceiptsAsync(aggregate, _stats); + LastUpdate = DateTimeOffset.Now; + + UpdateProgress(); + + if (_logIndexStorage.MinBlockNumber <= MinTargetBlockNumber) + MarkCompleted(false); + } + + private async Task DoQueueBlocks(bool isForward) + { + try + { + int pivotNumber = await _pivotTask; + + ProcessingQueue queue = Direction(isForward).Queue!; + + int? next = GetNextBlockNumber(isForward); + if (next is not { } start) + { + if (isForward) + { + start = pivotNumber; + } + else + { + start = pivotNumber - 1; + } + } + + BlockReceipts[] buffer = new BlockReceipts[_config.MaxBatchSize]; + while (!CancellationToken.IsCancellationRequested) + { + if (!isForward && start < MinTargetBlockNumber) + { + if (_logger.IsTrace) + _logger.Trace($"{GetLogPrefix(isForward)}: queued last block"); + + return; + } + + int batchSize = _config.MaxBatchSize; + int end = isForward ? start + batchSize - 1 : start - batchSize + 1; + end = Math.Max(end, MinTargetBlockNumber); + end = Math.Min(end, MaxTargetBlockNumber); + + // from - inclusive, to - exclusive + var (from, to) = isForward + ? (start, end + 1) + : (end, start + 1); + + var timestamp = Stopwatch.GetTimestamp(); + Array.Clear(buffer); + ReadOnlySpan batch = GetNextBatch(from, to, buffer, isForward, CancellationToken); + + if (batch.Length == 0) + { + // TODO: stop waiting immediately when receipts become available + await Task.Delay(NewBlockWaitTimeout, CancellationToken); + continue; + } + + _stats.LoadingReceipts.Include(Stopwatch.GetElapsedTime(timestamp)); + + start = GetNextBlockNumber(batch[^1].BlockNumber, isForward); + await queue.WriteAsync(batch.ToArray(), CancellationToken); + } + } + catch (Exception ex) + { + await HandleExceptionAsync(ex); + } + + if (_logger.IsTrace) + _logger.Trace($"{GetLogPrefix(isForward)}: queueing completed."); + } + + private void UpdateProgress() + { + if (!_pivotTask.IsCompletedSuccessfully) return; + var pivotNumber = _pivotTask.Result; + + DirectionState forward = Direction(isForward: true); + if (forward.Progress is { HasEnded: false } forwardProgress) + { + forwardProgress.TargetValue = Math.Max(0, _blockTree.BestKnownNumber - MaxReorgDepth - pivotNumber + 1); + forwardProgress.Update(_logIndexStorage.MaxBlockNumber is { } max ? max - pivotNumber + 1 : 0); + forwardProgress.CurrentQueued = forward.Queue!.QueueCount; + } + + DirectionState backward = Direction(isForward: false); + if (backward.Progress is { HasEnded: false } backwardProgress) + { + backwardProgress.TargetValue = pivotNumber - MinTargetBlockNumber; + backwardProgress.Update(_logIndexStorage.MinBlockNumber is { } min ? pivotNumber - min : 0); + backwardProgress.CurrentQueued = backward.Queue!.QueueCount; + } + } + + private void MarkCompleted(bool isForward) + { + DirectionState dir = Direction(isForward); + if (!dir.Completion.TrySetResult()) + return; + + dir.Progress?.MarkEnd(); + + if (_logger.IsInfo) + _logger.Info($"{GetLogPrefix(isForward)}: completed."); + } + + private static int? GetNextBlockNumber(ILogIndexStorage storage, bool isForward) => isForward ? storage.MaxBlockNumber + 1 : storage.MinBlockNumber - 1; + + private int? GetNextBlockNumber(bool isForward) => GetNextBlockNumber(_logIndexStorage, isForward); + + private static int GetNextBlockNumber(int last, bool isForward) => isForward ? last + 1 : last - 1; + + private ReadOnlySpan GetNextBatch(int from, int to, BlockReceipts[] buffer, bool isForward, CancellationToken token) + { + if (to <= from) + return ReadOnlySpan.Empty; + + if (to - from > buffer.Length) + throw new InvalidOperationException($"{GetLogPrefix()}: buffer size is too small: {buffer.Length} / {to - from}"); + + // Check the immediate next block first + int nextIndex = isForward ? from : to - 1; + if (!TryGetBlockReceipts(nextIndex, out buffer[0])) + return ReadOnlySpan.Empty; + + Parallel.For(from, to, new() + { + CancellationToken = token, + MaxDegreeOfParallelism = _config.MaxReceiptsParallelism + }, i => + { + int bufferIndex = isForward ? i - from : to - 1 - i; + if (buffer[bufferIndex] == default) + TryGetBlockReceipts(i, out buffer[bufferIndex]); + }); + + int endIndex = Array.IndexOf(buffer, default); + return endIndex < 0 ? buffer : buffer.AsSpan(..endIndex); + } + + // TODO: move to IReceiptStorage? + private bool TryGetBlockReceipts(int i, out BlockReceipts blockReceipts) + { + blockReceipts = default; + + if (_blockTree.FindBlock(i, BlockTreeLookupOptions.ExcludeTxHashes) is not { Hash: not null } block) + { + return false; + } + + if (!block.Header.HasTransactions) + { + blockReceipts = new(i, []); + return true; + } + + TxReceipt[] receipts = _receiptStorage.Get(block) ?? []; + + if (receipts.Length == 0) + { + return false; // block should have transactions but nothing in storage + } + + blockReceipts = new(i, receipts); + return true; + } + + private static string GetLogPrefix(bool? isForward = null) => isForward switch + { + true => "Log index sync (Forward)", + false => "Log index sync (Backward)", + _ => "Log index sync" + }; +} diff --git a/src/Nethermind/Nethermind.Init/Modules/BlockTreeModule.cs b/src/Nethermind/Nethermind.Init/Modules/BlockTreeModule.cs index e769c59eb754..139dabadad2b 100644 --- a/src/Nethermind/Nethermind.Init/Modules/BlockTreeModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/BlockTreeModule.cs @@ -15,14 +15,16 @@ using Nethermind.Crypto; using Nethermind.Db; using Nethermind.Db.Blooms; +using Nethermind.Db.LogIndex; using Nethermind.Facade.Find; +using Nethermind.Logging; using Nethermind.History; using Nethermind.State.Repositories; using Nethermind.TxPool; namespace Nethermind.Init.Modules; -public class BlockTreeModule(IReceiptConfig receiptConfig) : Autofac.Module +public class BlockTreeModule(IReceiptConfig receiptConfig, ILogIndexConfig logIndexConfig) : Autofac.Module { protected override void Load(ContainerBuilder builder) { @@ -37,16 +39,34 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddSingleton() .AddSingleton((ecdsa, specProvider, receiptConfig) => - new ReceiptsRecovery(ecdsa, specProvider, !receiptConfig.CompactReceiptStore)) + new ReceiptsRecovery(ecdsa, specProvider, !receiptConfig.CompactReceiptStore) + ) .AddSingleton() .AddSingleton() - .AddSingleton() .Bind() - .AddSingleton() - .AddSingleton((bt) => bt.AsReadOnly()) + .AddSingleton((bt) => bt.AsReadOnly()); + + builder.AddSingleton() + .AddDecorator((ctx, config) => + { + IPruningConfig pruningConfig = ctx.Resolve(); + config.MaxReorgDepth ??= pruningConfig.PruningBoundary; + return config; + }); - ; + if (logIndexConfig.Enabled) + { + builder + .AddSingleton() + .AddSingleton(); + } + else + { + builder + .AddSingleton() + .AddSingleton(); + } if (!receiptConfig.StoreReceipts) { diff --git a/src/Nethermind/Nethermind.Init/Modules/BuiltInStepsModule.cs b/src/Nethermind/Nethermind.Init/Modules/BuiltInStepsModule.cs index a20ca48e5d90..0b27e6863e92 100644 --- a/src/Nethermind/Nethermind.Init/Modules/BuiltInStepsModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/BuiltInStepsModule.cs @@ -33,6 +33,7 @@ public class BuiltInStepsModule : Module typeof(StartBlockProcessor), typeof(StartBlockProducer), typeof(StartMonitoring), + typeof(StartLogIndex) ]; protected override void Load(ContainerBuilder builder) diff --git a/src/Nethermind/Nethermind.Init/Modules/DbModule.cs b/src/Nethermind/Nethermind.Init/Modules/DbModule.cs index 1a3400c7e1f0..0caa67fe530b 100644 --- a/src/Nethermind/Nethermind.Init/Modules/DbModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/DbModule.cs @@ -66,7 +66,6 @@ protected override void Load(ContainerBuilder builder) .AddDatabase(DbNames.Headers) .AddDatabase(DbNames.BlockInfos) .AddDatabase(DbNames.Bloom) - .AddDatabase(DbNames.BlobTransactions) .AddColumnDatabase(DbNames.Receipts) .AddColumnDatabase(DbNames.BlobTransactions) diff --git a/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs b/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs index 07414446bbf9..9133320d1788 100644 --- a/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs +++ b/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs @@ -46,20 +46,22 @@ public MainProcessingContext( .AddSingleton(this) .AddModule(mainProcessingModules) - .AddScoped((branchProcessor) => new BlockchainProcessor( - blockTree, - branchProcessor, - compositeBlockPreprocessorStep, - worldStateManager.GlobalStateReader, - logManager, - new BlockchainProcessor.Options + .AddScoped((branchProcessor, processingStats) => + new BlockchainProcessor( + blockTree, + branchProcessor, + compositeBlockPreprocessorStep, + worldStateManager.GlobalStateReader, + logManager, + new BlockchainProcessor.Options + { + StoreReceiptsByDefault = receiptConfig.StoreReceipts, + DumpOptions = initConfig.AutoDump + }, + processingStats) { - StoreReceiptsByDefault = receiptConfig.StoreReceipts, - DumpOptions = initConfig.AutoDump + IsMainProcessor = true // Manual construction because of this flag }) - { - IsMainProcessor = true // Manual construction because of this flag - }) .AddScoped(ctx => ctx.Resolve()) .AddScoped(ctx => ctx.Resolve()) // And finally, to wrap things up. diff --git a/src/Nethermind/Nethermind.Init/Modules/NethermindModule.cs b/src/Nethermind/Nethermind.Init/Modules/NethermindModule.cs index 41df70db2387..cd17a2f96dc6 100644 --- a/src/Nethermind/Nethermind.Init/Modules/NethermindModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/NethermindModule.cs @@ -15,6 +15,7 @@ using Nethermind.Core.Specs; using Nethermind.Core.Timers; using Nethermind.Crypto; +using Nethermind.Db.LogIndex; using Nethermind.Era1; using Nethermind.JsonRpc; using Nethermind.Logging; @@ -54,7 +55,7 @@ protected override void Load(ContainerBuilder builder) .AddModule(new EraModule()) .AddSource(new ConfigRegistrationSource()) .AddModule(new BlockProcessingModule(configProvider.GetConfig(), configProvider.GetConfig())) - .AddModule(new BlockTreeModule(configProvider.GetConfig())) + .AddModule(new BlockTreeModule(configProvider.GetConfig(), configProvider.GetConfig())) .AddModule(new MonitoringModule(configProvider.GetConfig())) .AddSingleton() diff --git a/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs b/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs index c9ff690ad826..3cf04ff24cc0 100644 --- a/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs +++ b/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs @@ -13,6 +13,7 @@ using Nethermind.Core.Timers; using Nethermind.Facade; using Nethermind.Facade.Eth; +using Nethermind.Facade.Find; using Nethermind.Facade.Simulate; using Nethermind.Init.Steps.Migrations; using Nethermind.JsonRpc; @@ -21,6 +22,7 @@ using Nethermind.JsonRpc.Modules.DebugModule; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.JsonRpc.Modules.Eth.FeeHistory; +using Nethermind.JsonRpc.Modules.LogIndex; using Nethermind.JsonRpc.Modules.Net; using Nethermind.JsonRpc.Modules.Parity; using Nethermind.JsonRpc.Modules.Personal; @@ -58,6 +60,7 @@ protected override void Load(ContainerBuilder builder) .RegisterSingletonJsonRpcModule() .RegisterSingletonJsonRpcModule() .RegisterSingletonJsonRpcModule() + .RegisterSingletonJsonRpcModule() // Txpool rpc .RegisterSingletonJsonRpcModule() diff --git a/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs b/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs index 9b8e0542617e..5cfab78ac27b 100644 --- a/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs +++ b/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs @@ -17,6 +17,7 @@ using Nethermind.Core.Timers; using Nethermind.Db; using Nethermind.Db.FullPruning; +using Nethermind.Db.LogIndex; using Nethermind.Db.Rocks.Config; using Nethermind.Evm.State; using Nethermind.JsonRpc.Modules.Admin; @@ -177,6 +178,7 @@ public MainPruningTrieStoreFactory( IFinalizedStateProvider finalizedStateProvider, IBlockTree blockTree, IDbConfig dbConfig, + ILogIndexConfig logIndexConfig, IHardwareInfo hardwareInfo, ILogManager logManager ) @@ -187,6 +189,9 @@ ILogManager logManager if (syncConfig.SnapServingEnabled == true && pruningConfig.PruningBoundary < syncConfig.SnapServingMaxDepth) { + // use PruningBoundary for log-index MaxReorgDepth before it's overwritten + logIndexConfig.MaxReorgDepth ??= pruningConfig.PruningBoundary; + if (_logger.IsInfo) _logger.Info($"Snap serving enabled, but {nameof(pruningConfig.PruningBoundary)} is less than {syncConfig.SnapServingMaxDepth}. Setting to {syncConfig.SnapServingMaxDepth}."); pruningConfig.PruningBoundary = syncConfig.SnapServingMaxDepth; } diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs index aa64b11639b8..8e866c3ac38a 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs @@ -32,7 +32,6 @@ namespace Nethermind.Init.Steps public class InitializeBlockchain(INethermindApi api, IChainHeadInfoProvider chainHeadInfoProvider) : IStep { private readonly INethermindApi _api = api; - private ILogManager _logManager = api.LogManager; public async Task Execute(CancellationToken _) { diff --git a/src/Nethermind/Nethermind.Init/Steps/StartLogIndex.cs b/src/Nethermind/Nethermind.Init/Steps/StartLogIndex.cs new file mode 100644 index 000000000000..ef329611cf29 --- /dev/null +++ b/src/Nethermind/Nethermind.Init/Steps/StartLogIndex.cs @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Api; +using Nethermind.Api.Steps; +using Nethermind.Core.ServiceStopper; +using Nethermind.Db; +using Nethermind.Db.LogIndex; +using Nethermind.Facade.Find; + +namespace Nethermind.Init.Steps +{ + [RunnerStepDependencies(typeof(InitDatabase), typeof(StartBlockProcessor))] + public class StartLogIndex(IBasicApi api, ILogIndexBuilder logIndexBuilder) : IStep + { + public Task Execute(CancellationToken cancellationToken) + { + if (api.Config().Enabled) + _ = logIndexBuilder.StartAsync(); + + return Task.CompletedTask; + } + + public bool MustInitialize => false; + } +} diff --git a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs index fb5104b65651..7544c8a272bf 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs @@ -25,6 +25,7 @@ using Nethermind.Wallet; using Nethermind.Config; using Nethermind.Core.Test.Modules; +using Nethermind.Db.LogIndex; using Nethermind.Network; namespace Nethermind.JsonRpc.Benchmark @@ -80,6 +81,7 @@ public void GlobalSetup() feeHistoryOracle, _container.Resolve(), _container.Resolve(), + new LogIndexConfig(), new BlocksConfig().SecondsPerSlot); } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs index 99d9b33ba9c8..3ecf250af76c 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcProcessorTests.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using FluentAssertions; using Nethermind.Core.Extensions; +using Nethermind.Config; using Nethermind.Logging; using Nethermind.JsonRpc.Modules; using NSubstitute; @@ -401,6 +402,65 @@ public async Task Can_handle_null_request() result.DisposeItems(); } + [Test] + public async Task Should_stop_processing_when_shutdown_requested() + { + IJsonRpcService service = Substitute.For(); + service.GetErrorResponse(Arg.Any(), Arg.Any()) + .Returns(new JsonRpcErrorResponse { Error = new Error { Code = ErrorCodes.ResourceUnavailable, Message = "Shutting down" } }); + + IProcessExitSource processExitSource = Substitute.For(); + processExitSource.Token.Returns(new CancellationToken(canceled: true)); + + JsonRpcProcessor processor = new( + service, + new JsonRpcConfig(), + Substitute.For(), + LimboLogs.Instance, + processExitSource); + + string request = "{\"id\":67,\"jsonrpc\":\"2.0\",\"method\":\"eth_getTransactionCount\",\"params\":[\"0x7f01d9b227593e033bf8d6fc86e634d27aa85568\",\"0x668c24\"]}"; + List results = await processor.ProcessAsync(request, new JsonRpcContext(RpcEndpoint.Http)).ToListAsync(); + + results.Should().HaveCount(1); + results[0].Response.Should().BeOfType(); + ((JsonRpcErrorResponse)results[0].Response!).Error!.Code.Should().Be(ErrorCodes.ResourceUnavailable); + await service.DidNotReceive().SendRequestAsync(Arg.Any(), Arg.Any()); + results.DisposeItems(); + } + + [Test] + public async Task Should_complete_pipe_reader_when_shutdown_requested() + { + IJsonRpcService service = Substitute.For(); + service.GetErrorResponse(Arg.Any(), Arg.Any()) + .Returns(new JsonRpcErrorResponse { Error = new Error { Code = ErrorCodes.ResourceUnavailable, Message = "Shutting down" } }); + + IProcessExitSource processExitSource = Substitute.For(); + processExitSource.Token.Returns(new CancellationToken(canceled: true)); + + JsonRpcProcessor processor = new( + service, + new JsonRpcConfig(), + Substitute.For(), + LimboLogs.Instance, + processExitSource); + + Pipe pipe = new(); + await pipe.Writer.WriteAsync(Encoding.UTF8.GetBytes("{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\",\"params\":[]}")); + + List results = await processor.ProcessAsync(pipe.Reader, new JsonRpcContext(RpcEndpoint.Http)).ToListAsync(); + + results.Should().HaveCount(1); + results[0].Response.Should().BeOfType(); + + // Verify PipeReader was completed by the processor (reading again should throw) + await FluentActions.Invoking(async () => await pipe.Reader.ReadAsync()) + .Should().ThrowAsync(); + + results.DisposeItems(); + } + [Test] public void Cannot_accept_null_file_system() { diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs index 11e1e43e21e4..82e655f5f597 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/BoundedModulePoolTests.cs @@ -6,6 +6,7 @@ using Nethermind.Config; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; +using Nethermind.Db.LogIndex; using Nethermind.JsonRpc.Modules; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Logging; @@ -56,7 +57,8 @@ public Task Initialize() Substitute.For(), Substitute.For(), new BlocksConfig(), - Substitute.For()), + Substitute.For(), + Substitute.For()), 1, 1000); return Task.CompletedTask; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs index 20cd1112eba5..a7647dc34534 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs @@ -1173,7 +1173,7 @@ public async Task Send_raw_transaction_will_send_transaction(string rawTransacti string serialized = await ctx.Test.TestEthRpc("eth_sendRawTransaction", rawTransaction); Transaction tx = Rlp.Decode(Bytes.FromHexString(rawTransaction)); await txSender.Received().SendTransaction(tx, TxHandlingOptions.PersistentBroadcast); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32010,\"message\":\"Invalid, InvalidTxSignature: Signature is invalid.\"},\"id\":67}")); + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"Invalid, InvalidTxSignature: Signature is invalid.\"},\"id\":67}")); } [Test] @@ -1224,7 +1224,7 @@ public async Task Send_transaction_should_return_ErrorCode_if_tx_not_added() string serialized = await ctx.Test.TestEthRpc("eth_sendTransaction", txForRpc); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32010,\"message\":\"InsufficientFunds, Balance is zero, cannot pay gas\"},\"id\":67}")); + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"InsufficientFunds, Balance is zero, cannot pay gas\"},\"id\":67}")); } public enum AccessListProvided diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs index cde82c8033a5..1033def4ff7e 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SingletonModulePoolTests.cs @@ -19,6 +19,7 @@ using Nethermind.JsonRpc.Modules.Eth.FeeHistory; using Nethermind.JsonRpc.Modules.Eth.GasPrice; using Nethermind.Config; +using Nethermind.Db.LogIndex; using Nethermind.Network; using Nethermind.State; @@ -55,7 +56,8 @@ public Task Initialize() Substitute.For(), Substitute.For(), new BlocksConfig(), - Substitute.For()); + Substitute.For(), + Substitute.For()); return Task.CompletedTask; } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs index a00b1905ef40..77ba56292c40 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs @@ -34,6 +34,7 @@ using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Test.Container; +using Nethermind.Db.LogIndex; using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Modules; using Nethermind.JsonRpc.Modules.Trace; @@ -58,6 +59,7 @@ public class TestRpcBlockchain : TestBlockchain public IReceiptFinder ReceiptFinder => Container.Resolve(); public IGasPriceOracle GasPriceOracle { get; private set; } = null!; public IProtocolsManager ProtocolsManager { get; private set; } = null!; + public ILogIndexConfig LogIndexConfig { get; } = new LogIndexConfig(); public IKeyStore KeyStore { get; } = new MemKeyStore(TestItem.PrivateKeys, Path.Combine("testKeyStoreDir", Path.GetRandomFileName())); public IWallet TestWallet { get; } = @@ -193,6 +195,7 @@ public async Task Build(Action configurer) new FeeHistoryOracle(@this.BlockTree, @this.ReceiptStorage, @this.SpecProvider), @this.ProtocolsManager, @this.ForkInfo, + @this.LogIndexConfig, @this.BlocksConfig.SecondsPerSlot); protected override async Task Build(Action? configurer = null) @@ -255,7 +258,7 @@ public async Task RestartBlockchainProcessor() // simulating restarts - we stopped the old blockchain processor and create the new one _currentBlockchainProcessor = new BlockchainProcessor(BlockTree, BranchProcessor, - BlockPreprocessorStep, StateReader, LimboLogs.Instance, Nethermind.Consensus.Processing.BlockchainProcessor.Options.Default); + BlockPreprocessorStep, StateReader, LimboLogs.Instance, Nethermind.Consensus.Processing.BlockchainProcessor.Options.Default, Substitute.For()); _currentBlockchainProcessor.Start(); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/EthRpcJsonContext.cs b/src/Nethermind/Nethermind.JsonRpc/Data/EthRpcJsonContext.cs new file mode 100644 index 000000000000..a7537c67d09c --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/Data/EthRpcJsonContext.cs @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json.Serialization; +using Nethermind.Blockchain.Tracing.GethStyle; +using Nethermind.Blockchain.Tracing.ParityStyle; +using Nethermind.Evm; +using Nethermind.JsonRpc.Modules.DebugModule; +using Nethermind.JsonRpc.Modules.Eth; +using Nethermind.JsonRpc.Modules.Trace; +using Nethermind.State.Proofs; + +namespace Nethermind.JsonRpc.Data; + +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Metadata, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +// eth_ types +[JsonSerializable(typeof(ReceiptForRpc))] +[JsonSerializable(typeof(LogEntryForRpc))] +[JsonSerializable(typeof(FeeHistoryResults))] +[JsonSerializable(typeof(AccessListResultForRpc))] +[JsonSerializable(typeof(AccountInfoForRpc))] +[JsonSerializable(typeof(AccountOverride))] +[JsonSerializable(typeof(AccountProof))] +[JsonSerializable(typeof(BadBlock))] +// debug_ types +[JsonSerializable(typeof(GethLikeTxTrace))] +[JsonSerializable(typeof(GethTraceOptions))] +[JsonSerializable(typeof(ChainLevelForRpc))] +// trace_ types +[JsonSerializable(typeof(ParityTxTraceFromReplay))] +[JsonSerializable(typeof(ParityTxTraceFromStore))] +[JsonSerializable(typeof(ParityLikeTxTrace))] +[JsonSerializable(typeof(TraceFilterForRpc))] +public partial class EthRpcJsonContext : JsonSerializerContext; diff --git a/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs b/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs index cea92a933c03..47bc3b4dc769 100644 --- a/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs +++ b/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs @@ -48,35 +48,25 @@ public static class ErrorCodes public const int ResourceNotFound = -32000; /// - /// Requested resource not available + /// Transaction creation failed /// - public const int ResourceUnavailable = -32002; + public const int TransactionRejected = -32000; /// - /// Transaction creation failed + /// Requested resource not available /// - public const int TransactionRejected = -32010; + public const int ResourceUnavailable = -32002; /// /// Account locked /// public const int AccountLocked = -32020; - /// - /// Method is not implemented - /// - public const int MethodNotSupported = -32004; - /// /// Request exceeds defined limit /// public const int LimitExceeded = -32005; - /// - /// - /// - public const int ExecutionError = -32015; - /// /// Request exceeds defined timeout limit /// @@ -107,16 +97,6 @@ public static class ErrorCodes /// public const int InvalidInputTooManyBlocks = -38026; - /// - /// Invalid RPC simulate call Not enough gas provided to pay for intrinsic gas for a transaction - /// - public const int InsufficientIntrinsicGas = -38013; - - /// - /// Invalid RPC simulate call transaction - /// - public const int InvalidTransaction = -38014; - /// /// Too many blocks for simulation /// @@ -132,16 +112,6 @@ public static class ErrorCodes /// public const int Default = -32000; - /// - /// Transaction.Nonce is bigger than expected nonce - /// - public const int NonceTooHigh = -38011; - - /// - /// Transaction.Nonce is smaller than expected nonce - /// - public const int NonceTooLow = -38010; - /// /// Invalid intrinsic gas. Miner premium is negative /// @@ -157,21 +127,6 @@ public static class ErrorCodes /// public const int BlockGasLimitReached = -38015; - /// - /// Invalid block number - /// - public const int BlockNumberInvalid = -38020; - - /// - /// Invalid block timestamp - /// - public const int BlockTimestampInvalid = -38021; - - /// - /// Transaction.Sender is not an EOA - /// - public const int SenderIsNotEOA = -38024; - /// /// EIP-3860. Code size is to big /// @@ -186,7 +141,5 @@ public static class ErrorCodes /// Error during EVM execution /// public const int VMError = -32015; - public const int TxSyncTimeout = 4; - public const int ClientLimitExceeded = -38026; } } diff --git a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcResult.cs b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcResult.cs deleted file mode 100644 index 845540f0d11e..000000000000 --- a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.JsonRpc -{ - public interface IJsonRpcResult - { - object ToJson(); - } -} diff --git a/src/Nethermind/Nethermind.JsonRpc/IStreamableResult.cs b/src/Nethermind/Nethermind.JsonRpc/IStreamableResult.cs new file mode 100644 index 000000000000..6e3c1cf02c03 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/IStreamableResult.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; + +namespace Nethermind.JsonRpc; + +/// +/// Implemented by result objects that can write themselves directly to a , +/// bypassing to avoid extra buffer copies. +/// +public interface IStreamableResult +{ + ValueTask WriteToAsync(PipeWriter writer, CancellationToken cancellationToken); +} diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfigExtension.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfigExtension.cs index ce2181efb4f5..acb929e83633 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfigExtension.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfigExtension.cs @@ -1,15 +1,22 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; namespace Nethermind.JsonRpc { public static class JsonRpcConfigExtension { + private static readonly ConcurrentQueue _ctsPool = new(); + private const int MaxPoolSize = 64; + private static int _ctsPoolSize; + public static void EnableModules(this IJsonRpcConfig config, params string[] modules) { HashSet enabledModules = config.EnabledModules.ToHashSet(); @@ -21,12 +28,45 @@ public static void EnableModules(this IJsonRpcConfig config, params string[] mod } /// - /// Constructs a that timeouts after - /// if is false. + /// Rents a that timeouts after . + /// When debugger is attached, no timeout is applied. + /// Call to return to pool. /// public static CancellationTokenSource BuildTimeoutCancellationToken(this IJsonRpcConfig config) { - return Debugger.IsAttached ? new CancellationTokenSource() : new CancellationTokenSource(config.Timeout); + if (Debugger.IsAttached) + { + return new CancellationTokenSource(); + } + + if (_ctsPool.TryDequeue(out CancellationTokenSource? cts)) + { + Interlocked.Decrement(ref _ctsPoolSize); + cts.CancelAfter(config.Timeout); + return cts; + } + + return new CancellationTokenSource(config.Timeout); + } + + /// + /// Returns a CTS to the pool if it can be reset, otherwise disposes it. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ReturnTimeoutCancellationToken(CancellationTokenSource cts) + { + if (cts.TryReset()) + { + if (Interlocked.Increment(ref _ctsPoolSize) <= MaxPoolSize) + { + _ctsPool.Enqueue(cts); + return; + } + + Interlocked.Decrement(ref _ctsPoolSize); + } + + cts.Dispose(); } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcContext.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcContext.cs index c145607924d5..b8e3f2f803f7 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcContext.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcContext.cs @@ -2,14 +2,16 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Threading; using Nethermind.JsonRpc.Modules; namespace Nethermind.JsonRpc { public class JsonRpcContext : IDisposable { - public static AsyncLocal Current { get; private set; } = new(); + [ThreadStatic] + private static JsonRpcContext? _current; + + public static ThreadStaticAccessor Current { get; } = new(); public static JsonRpcContext Http(JsonRpcUrl url) => new(RpcEndpoint.Http, url: url); public static JsonRpcContext WebSocket(JsonRpcUrl url) => new(RpcEndpoint.Ws, url: url); @@ -20,7 +22,7 @@ public JsonRpcContext(RpcEndpoint rpcEndpoint, IJsonRpcDuplexClient? duplexClien DuplexClient = duplexClient; Url = url; IsAuthenticated = Url?.IsAuthenticated == true || RpcEndpoint == RpcEndpoint.IPC; - Current.Value = this; + _current = this; } public RpcEndpoint RpcEndpoint { get; } @@ -29,9 +31,22 @@ public JsonRpcContext(RpcEndpoint rpcEndpoint, IJsonRpcDuplexClient? duplexClien public bool IsAuthenticated { get; } public void Dispose() { - if (Current.Value == this) + if (_current == this) + { + _current = null; + } + } + + /// + /// Provides .Value accessor compatible with AsyncLocal API shape so callers + /// like JsonRpcContext.Current.Value?.IsAuthenticated continue to compile. + /// + public sealed class ThreadStaticAccessor + { + public JsonRpcContext? Value { - Current.Value = null; + get => _current; + set => _current = value; } } } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs index bd042072fa1c..8093b2c40c97 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcProcessor.cs @@ -9,7 +9,6 @@ using System.IO; using System.IO.Abstractions; using System.IO.Pipelines; -using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Text.Json; @@ -27,7 +26,7 @@ namespace Nethermind.JsonRpc; -public class JsonRpcProcessor : IJsonRpcProcessor +public sealed class JsonRpcProcessor : IJsonRpcProcessor { private readonly IJsonRpcConfig _jsonRpcConfig; private readonly ILogger _logger; @@ -90,7 +89,7 @@ private JsonRpcRequest DeserializeObject(JsonElement element) { id = idNumber; } - else if (decimal.TryParse(idElement.GetRawText(), out var value)) + else if (idElement.TryGetDecimal(out var value)) { id = value; } @@ -104,7 +103,7 @@ private JsonRpcRequest DeserializeObject(JsonElement element) string? method = null; if (element.TryGetProperty("method"u8, out JsonElement methodElement)) { - method = methodElement.GetString(); + method = InternMethodName(methodElement); } if (!element.TryGetProperty("params"u8, out JsonElement paramsElement)) @@ -121,30 +120,61 @@ private JsonRpcRequest DeserializeObject(JsonElement element) }; } - private ArrayPoolList DeserializeArray(JsonElement element) => - new(element.GetArrayLength(), element.EnumerateArray().Select(DeserializeObject)); + private ArrayPoolList DeserializeArray(JsonElement element) + { + ArrayPoolList list = new(element.GetArrayLength()); + foreach (JsonElement item in element.EnumerateArray()) + { + list.Add(DeserializeObject(item)); + } + return list; + } + + /// + /// Returns a cached string constant for known engine method names to avoid allocation. + /// Falls back to for unknown methods. + /// + private static string? InternMethodName(JsonElement methodElement) + { + if (methodElement.ValueEquals("engine_newPayloadV4"u8)) return "engine_newPayloadV4"; + if (methodElement.ValueEquals("engine_forkchoiceUpdatedV3"u8)) return "engine_forkchoiceUpdatedV3"; + if (methodElement.ValueEquals("engine_newPayloadV3"u8)) return "engine_newPayloadV3"; + if (methodElement.ValueEquals("engine_forkchoiceUpdatedV2"u8)) return "engine_forkchoiceUpdatedV2"; + if (methodElement.ValueEquals("engine_getPayloadV4"u8)) return "engine_getPayloadV4"; + if (methodElement.ValueEquals("engine_getPayloadV3"u8)) return "engine_getPayloadV3"; + if (methodElement.ValueEquals("engine_newPayloadV2"u8)) return "engine_newPayloadV2"; + if (methodElement.ValueEquals("engine_newPayloadV1"u8)) return "engine_newPayloadV1"; + if (methodElement.ValueEquals("engine_exchangeCapabilities"u8)) return "engine_exchangeCapabilities"; + return methodElement.GetString(); + } private static readonly JsonReaderOptions _socketJsonReaderOptions = new() { AllowMultipleValues = true }; public async IAsyncEnumerable ProcessAsync(PipeReader reader, JsonRpcContext context) { - if (ProcessExit.IsCancellationRequested) + // Engine API (authenticated) requests skip the timeout CTS entirely -- they are from + // trusted consensus clients, must complete for consensus, and connection drops are + // handled by the PipeReader. Non-engine paths still use the pooled CTS. + CancellationTokenSource? timeoutSource = context.IsAuthenticated ? null : _jsonRpcConfig.BuildTimeoutCancellationToken(); + CancellationToken timeoutToken = timeoutSource?.Token ?? CancellationToken.None; + try { - JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ResourceUnavailable, "Shutting down"); - yield return JsonRpcResult.Single(RecordResponse(response, new RpcReport("Shutdown", 0, false))); - } + if (ProcessExit.IsCancellationRequested) + { + JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(ErrorCodes.ResourceUnavailable, "Shutting down"); + yield return JsonRpcResult.Single(RecordResponse(response, new RpcReport("Shutdown", 0, false))); + yield break; + } - if (IsRecordingRequest) - { - reader = await RecordRequest(reader); - } + if (IsRecordingRequest) + { + reader = await RecordRequest(reader); + } + + JsonReaderState readerState = CreateJsonReaderState(context); + bool freshState = true; + bool shouldExit = false; - using CancellationTokenSource timeoutSource = _jsonRpcConfig.BuildTimeoutCancellationToken(); - JsonReaderState readerState = CreateJsonReaderState(context); - bool freshState = true; - bool shouldExit = false; - try - { while (!shouldExit) { long startTime = Stopwatch.GetTimestamp(); @@ -152,7 +182,7 @@ public async IAsyncEnumerable ProcessAsync(PipeReader reader, Jso ReadResult readResult; try { - readResult = await reader.ReadAsync(timeoutSource.Token); + readResult = await reader.ReadAsync(timeoutToken); } catch (BadHttpRequestException e) { @@ -231,6 +261,8 @@ public async IAsyncEnumerable ProcessAsync(PipeReader reader, Jso finally { await reader.CompleteAsync(); + if (timeoutSource is not null) + JsonRpcConfigExtension.ReturnTimeoutCancellationToken(timeoutSource); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcResponseJsonContext.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcResponseJsonContext.cs new file mode 100644 index 000000000000..322a04158fa3 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcResponseJsonContext.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json.Serialization; + +namespace Nethermind.JsonRpc; + +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Metadata, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] +[JsonSerializable(typeof(JsonRpcSuccessResponse))] +[JsonSerializable(typeof(JsonRpcErrorResponse))] +[JsonSerializable(typeof(JsonRpcResponse))] +[JsonSerializable(typeof(Error))] +public partial class JsonRpcResponseJsonContext : JsonSerializerContext; diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs index 1246c6b49cf8..781e7da43de2 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs @@ -9,6 +9,7 @@ using System.Text; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; using Nethermind.Blockchain; @@ -24,7 +25,7 @@ namespace Nethermind.JsonRpc; -public class JsonRpcService : IJsonRpcService +public sealed class JsonRpcService : IJsonRpcService { private readonly static Lock _reparseLock = new(); private static Dictionary _reparseReflectionCache = new(); @@ -344,7 +345,7 @@ private void LogRequest(string methodName, JsonElement providedParameters, Expec { foreach (JsonElement param in providedParameters.EnumerateArray()) { - string? parameter = expectedParameters.ElementAtOrDefault(paramsCount).Info?.Name == "passphrase" + string? parameter = (uint)paramsCount < (uint)expectedParameters.Length && expectedParameters[paramsCount].Info?.Name == "passphrase" ? "{passphrase}" : param.GetRawText(); @@ -403,6 +404,7 @@ private void LogRequest(string methodName, JsonElement providedParameters, Expec } else { + EthereumJsonSerializer.JsonOptions.TryGetTypeInfo(paramType, out JsonTypeInfo? typeInfo); if (providedParameter.ValueKind == JsonValueKind.String) { if (!_reparseReflectionCache.TryGetValue(paramType, out bool reparseString)) @@ -412,11 +414,15 @@ private void LogRequest(string methodName, JsonElement providedParameters, Expec executionParam = reparseString ? JsonSerializer.Deserialize(providedParameter.GetString(), paramType, EthereumJsonSerializer.JsonOptions) - : providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); + : typeInfo is not null + ? providedParameter.Deserialize(typeInfo) + : providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); } else { - executionParam = providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); + executionParam = typeInfo is not null + ? providedParameter.Deserialize(typeInfo) + : providedParameter.Deserialize(paramType, EthereumJsonSerializer.JsonOptions); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/TransactionBundle.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/TransactionBundle.cs index 3d1119669d45..4e11f17c79cb 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/TransactionBundle.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/TransactionBundle.cs @@ -5,7 +5,6 @@ using Nethermind.Core; using Nethermind.Evm; using Nethermind.Facade.Eth.RpcTransaction; -using Nethermind.Facade.Proxy.Models.Simulate; namespace Nethermind.JsonRpc.Modules.DebugModule; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs index d5b6001e4aa7..7ead40a36d49 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModuleFactory.cs @@ -5,6 +5,7 @@ using Nethermind.Blockchain.Receipts; using Nethermind.Config; using Nethermind.Core.Specs; +using Nethermind.Db.LogIndex; using Nethermind.Facade; using Nethermind.Facade.Eth; using Nethermind.JsonRpc.Modules.Eth.GasPrice; @@ -33,7 +34,8 @@ public class EthModuleFactory( IFeeHistoryOracle feeHistoryOracle, IProtocolsManager protocolsManager, IBlocksConfig blocksConfig, - IForkInfo forkInfo) + IForkInfo forkInfo, + ILogIndexConfig logIndexConfig) : ModuleFactoryBase { private readonly ulong _secondsPerSlot = blocksConfig.SecondsPerSlot; @@ -57,6 +59,7 @@ public override IEthRpcModule Create() feeHistoryOracle, protocolsManager, forkInfo, + logIndexConfig, _secondsPerSlot); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index 713bccba0101..bce797084c92 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -9,6 +9,7 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; +using Nethermind.Db.LogIndex; using Nethermind.Evm; using Nethermind.Evm.Precompiles; using Nethermind.Facade; @@ -65,6 +66,7 @@ public partial class EthRpcModule( IFeeHistoryOracle feeHistoryOracle, IProtocolsManager protocolsManager, IForkInfo forkInfo, + ILogIndexConfig? logIndexConfig, ulong? secondsPerSlot) : IEthRpcModule { public const int GetProofStorageKeyLimit = 1000; @@ -645,6 +647,10 @@ public ResultWrapper> eth_getLogs(Filter filter) { LogFilter logFilter = _blockchainBridge.GetFilter(fromBlock, toBlock, filter.Address, filter.Topics); + // ReSharper disable once ConditionIsAlwaysTrueOrFalse - can be null in tests + if (logFilter is not null && filter is not null) + logFilter.UseIndex = filter.UseIndex; + IEnumerable filterLogs = _blockchainBridge.GetLogs(logFilter, fromBlockHeader, toBlockHeader, cancellationToken); ArrayPoolList logs = new(_rpcConfig.MaxLogsPerResponse); @@ -661,6 +667,9 @@ public ResultWrapper> eth_getLogs(Filter filter) } } + if (logIndexConfig?.VerifyRpcResponse is true && logFilter.UseIndex) + VerifyLogsResponse(logs, logFilter, fromBlockHeader, toBlockHeader, cancellationToken); + return ResultWrapper>.Success(logs); } catch (ResourceNotFoundException exception) @@ -839,4 +848,28 @@ public ResultWrapper eth_config() private CancellationTokenSource BuildTimeoutCancellationTokenSource() => _rpcConfig.BuildTimeoutCancellationToken(); + + private void VerifyLogsResponse(IList response, LogFilter filter, BlockHeader from, BlockHeader to, CancellationToken cancellation) + { + filter.UseIndex = false; + IEnumerable? expectedResponse = _blockchainBridge.GetLogs(filter, from, to, cancellation); + + using IEnumerator expectedEnum = expectedResponse.GetEnumerator(); + + var i = -1; + while (++i < response.Count | expectedEnum.MoveNext()) + { + FilterLog? actual = i < response.Count ? response[i] : null; + FilterLog? expected = expectedEnum.Current; + + if ((actual?.BlockNumber, actual?.LogIndex) != (expected?.BlockNumber, expected?.LogIndex)) + { + throw new LogIndexStateException( + $"Incorrect result from log index at position #{i}. " + + $"Expected: block {expected?.BlockNumber}, log #{expected?.LogIndex}. " + + $"Actual: block {actual?.BlockNumber}, log #{actual?.LogIndex}." + ); + } + } + } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs index 57f26cee883a..3f79cbd33cc3 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs @@ -15,12 +15,14 @@ public class Filter : IJsonRpcParam { public HashSet? Address { get; set; } - public BlockParameter FromBlock { get; set; } + public BlockParameter FromBlock { get; set; } = BlockParameter.Latest; - public BlockParameter ToBlock { get; set; } + public BlockParameter ToBlock { get; set; } = BlockParameter.Latest; public IEnumerable? Topics { get; set; } + public bool UseIndex { get; set; } = true; + public void ReadJson(JsonElement filter, JsonSerializerOptions options) { JsonDocument doc = null; @@ -65,6 +67,16 @@ public void ReadJson(JsonElement filter, JsonSerializerOptions options) { Topics = GetTopics(topicsElement, options); } + + if (filter.TryGetProperty("useIndex"u8, out JsonElement useIndex)) + { + UseIndex = useIndex.ValueKind switch + { + JsonValueKind.False => false, + JsonValueKind.True => true, + _ => UseIndex + }; + } } finally { diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/ILogIndexRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/ILogIndexRpcModule.cs new file mode 100644 index 000000000000..9a62dd485a05 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/ILogIndexRpcModule.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.JsonRpc.Modules.Eth; + +namespace Nethermind.JsonRpc.Modules.LogIndex; + +[RpcModule(ModuleType.LogIndex)] +public interface ILogIndexRpcModule : IRpcModule +{ + [JsonRpcMethod(Description = "Retrieves log index block number for the given filter.", IsImplemented = true, IsSharable = true)] + ResultWrapper> logIndex_blockNumbers([JsonRpcParameter] Filter filter); + + [JsonRpcMethod(Description = "Retrieves log index status.", IsImplemented = true, IsSharable = true)] + ResultWrapper logIndex_status(); +} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexRpcModule.cs new file mode 100644 index 000000000000..94c4a48272ba --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexRpcModule.cs @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Blockchain.Filters; +using Nethermind.Blockchain.Find; +using Nethermind.Db.LogIndex; +using Nethermind.Facade; +using Nethermind.Facade.Filters; +using Nethermind.Facade.Find; +using Nethermind.JsonRpc.Modules.Eth; + +namespace Nethermind.JsonRpc.Modules.LogIndex; + +public class LogIndexRpcModule(ILogIndexStorage storage, ILogIndexBuilder builder, IBlockFinder blockFinder, IBlockchainBridge blockchainBridge) + : ILogIndexRpcModule +{ + public ResultWrapper> logIndex_blockNumbers(Filter filter) + { + LogFilter logFilter = blockchainBridge.GetFilter(filter.FromBlock, filter.ToBlock, filter.Address, filter.Topics); + + if (GetBlockNumber(logFilter.FromBlock) is not { } from) + return ResultWrapper>.Fail($"Block {logFilter.FromBlock} is not found.", ErrorCodes.UnknownBlockError); + + if (GetBlockNumber(logFilter.ToBlock) is not { } to) + return ResultWrapper>.Fail($"Block {logFilter.ToBlock} is not found.", ErrorCodes.UnknownBlockError); + + return ResultWrapper>.Success(storage.EnumerateBlockNumbersFor(logFilter, from, to)); + } + + public ResultWrapper logIndex_status() + { + return ResultWrapper.Success(new() + { + Current = new() + { + FromBlock = storage.MinBlockNumber, + ToBlock = storage.MaxBlockNumber + }, + Target = new() + { + FromBlock = builder.MinTargetBlockNumber, + ToBlock = builder.MaxTargetBlockNumber + }, + IsRunning = builder.IsRunning, + LastUpdate = builder.LastUpdate, + LastError = builder.LastError?.ToString(), + DbSize = storage.GetDbSize() + }); + } + + private long? GetBlockNumber(BlockParameter parameter) => + parameter.BlockNumber ?? blockFinder.FindBlock(parameter)?.Number; +} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexStatus.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexStatus.cs new file mode 100644 index 000000000000..47a7d1b10180 --- /dev/null +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/LogIndex/LogIndexStatus.cs @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; + +namespace Nethermind.JsonRpc.Modules.LogIndex; + +public class LogIndexStatus +{ + public readonly record struct Range(int? FromBlock, int? ToBlock); + + public required Range Current { get; init; } + public required Range Target { get; init; } + public bool IsRunning { get; init; } + public DateTimeOffset? LastUpdate { get; init; } + public string? LastError { get; init; } + public required string DbSize { get; init; } +} diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs index a93b8fdc203f..3dc4ec0e2707 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/ModuleType.cs @@ -14,6 +14,7 @@ public static class ModuleType public const string Debug = nameof(Debug); public const string Erc20 = nameof(Erc20); public const string Eth = nameof(Eth); + public const string LogIndex = nameof(LogIndex); public const string Evm = nameof(Evm); public const string Flashbots = nameof(Flashbots); public const string Net = nameof(Net); @@ -25,7 +26,6 @@ public static class ModuleType public const string Trace = nameof(Trace); public const string TxPool = nameof(TxPool); public const string Web3 = nameof(Web3); - public const string Vault = nameof(Vault); public const string Deposit = nameof(Deposit); public const string Health = nameof(Health); public const string Rpc = nameof(Rpc); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs index 2c00637b395e..1b2580b8591f 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs @@ -1054,8 +1054,7 @@ private MultiSyncModeSelector CreateMultiSyncModeSelector(MergeTestBlockchain ch Substitute.For>(), Substitute.For>(), Substitute.For>(), - Substitute.For>(), - LimboLogs.Instance); + Substitute.For>()); MultiSyncModeSelector multiSyncModeSelector = new(syncProgressResolver, syncPeerPool, new SyncConfig(), No.BeaconSync, diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index d8f6335d262b..d0ba23fd16c3 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -13,6 +13,7 @@ using FluentAssertions; using Nethermind.Blockchain; using Nethermind.Blockchain.Find; +using Nethermind.Consensus.Processing; using Nethermind.Consensus.Producers; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -668,7 +669,19 @@ public async Task forkChoiceUpdatedV1_block_still_processing() Block blockTreeHead = chain.BlockTree.Head!; Block block = Build.A.Block.WithNumber(blockTreeHead.Number + 1).WithParent(blockTreeHead).WithNonce(0).WithDifficulty(0).TestObject; - chain.ThrottleBlockProcessor(200); + chain.ThrottleBlockProcessor(1000); + ManualResetEventSlim processingStarted = new(false); + ((TestBranchProcessorInterceptor)chain.BranchProcessor).ProcessingStarted = processingStarted; + + // Directly enqueue a block to occupy the processor (bypasses the RPC semaphore), + // ensuring subsequent blocks route through the recovery queue (slow path) + Block occupyBlock = Build.A.Block.WithNumber(blockTreeHead.Number + 1).WithParent(blockTreeHead) + .WithNonce(0).WithDifficulty(0).WithStateRoot(blockTreeHead.StateRoot!).TestObject; + occupyBlock.Header.TotalDifficulty = blockTreeHead.TotalDifficulty; + _ = Task.Run(async () => await chain.BlockProcessingQueue.Enqueue( + occupyBlock, ProcessingOptions.ForceProcessing | ProcessingOptions.DoNotUpdateHead)); + processingStarted.Wait(TimeSpan.FromSeconds(5)); + ResultWrapper newPayloadV1 = await rpc.engine_newPayloadV1(ExecutionPayload.Create(block)); newPayloadV1.Data.Status.Should().Be("SYNCING"); @@ -749,6 +762,18 @@ public async Task Invalid_block_on_processing_wont_be_accepted_if_sent_twice_in_ .TestObject; chain.ThrottleBlockProcessor(1000); // throttle the block processor enough so that the block processing queue is never empty + ManualResetEventSlim processingStarted = new(false); + ((TestBranchProcessorInterceptor)chain.BranchProcessor).ProcessingStarted = processingStarted; + + // Directly enqueue a block to occupy the processor (bypasses the RPC semaphore), + // ensuring subsequent blocks route through the recovery queue (slow path) + Block occupyBlock = Build.A.Block.WithNumber(head.Number + 1).WithParent(head) + .WithNonce(0).WithDifficulty(0).WithStateRoot(head.StateRoot!).TestObject; + occupyBlock.Header.TotalDifficulty = head.TotalDifficulty; + _ = Task.Run(async () => await chain.BlockProcessingQueue.Enqueue( + occupyBlock, ProcessingOptions.ForceProcessing | ProcessingOptions.DoNotUpdateHead)); + processingStarted.Wait(TimeSpan.FromSeconds(5)); + (await rpc.engine_newPayloadV1(ExecutionPayload.Create(block))).Data.Status.Should().Be(PayloadStatus.Syncing); (await rpc.engine_newPayloadV1(ExecutionPayload.Create(block))).Data.Status.Should().BeOneOf(PayloadStatus.Syncing); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs index 7940bc6c34b7..e34ec509534a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs @@ -4,7 +4,10 @@ using System; using System.Collections.Generic; using System.IO.Abstractions; +using System.IO.Pipelines; using System.Linq; +using System.Text; +using System.Text.Json; using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; @@ -14,6 +17,7 @@ using Nethermind.Blockchain.Synchronization; using Nethermind.Consensus.Producers; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; @@ -683,6 +687,33 @@ public async Task GetBlobsV1_should_return_mix_of_blobs_and_nulls([Values(1, 2, } } + [Test] + public async Task BlobsV1DirectResponse_WriteToAsync_produces_valid_json() + { + byte[] blob = new byte[16]; + Random.Shared.NextBytes(blob); + byte[] proof = new byte[48]; + Random.Shared.NextBytes(proof); + + ArrayPoolList items = new(2); + items.Add(new BlobAndProofV1(blob, proof)); + items.Add(null); + + using BlobsV1DirectResponse response = new(items); + + Pipe pipe = new(); + await response.WriteToAsync(pipe.Writer, CancellationToken.None); + await pipe.Writer.CompleteAsync(); + + ReadResult readResult = await pipe.Reader.ReadAsync(); + string streamedJson = Encoding.UTF8.GetString(readResult.Buffer); + pipe.Reader.AdvanceTo(readResult.Buffer.End); + + string stjJson = JsonSerializer.Serialize(response, EthereumJsonSerializer.JsonOptions); + + streamedJson.Should().Be(stjJson); + } + [Test] public async Task Sync_proper_chain_when_header_fork_came_from_fcu_and_beacon_sync() { diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs index 825da6cc4e1a..218014837ee0 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs @@ -3,7 +3,11 @@ using System; using System.Collections.Generic; +using System.IO.Pipelines; using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using CkzgLib; using FluentAssertions; @@ -14,6 +18,7 @@ using Nethermind.Evm; using Nethermind.JsonRpc; using Nethermind.Merge.Plugin.Data; +using Nethermind.Serialization.Json; using Nethermind.Specs.Forks; using Nethermind.TxPool; using NUnit.Framework; @@ -246,4 +251,67 @@ public async Task GetBlobsV1_should_return_invalid_fork_post_osaka() result.Result.Should().BeEquivalentTo(Result.Fail(MergeErrorMessages.UnsupportedFork)); result.ErrorCode.Should().Be(MergeErrorCodes.UnsupportedFork); } + + [Test] + public async Task BlobsV2DirectResponse_WriteToAsync_produces_valid_json() + { + // Build a small list with one real entry and one null + byte[] blob = new byte[16]; + Random.Shared.NextBytes(blob); + byte[] proof1 = new byte[48]; + Random.Shared.NextBytes(proof1); + byte[] proof2 = new byte[48]; + Random.Shared.NextBytes(proof2); + + byte[]?[] blobs = [blob, null]; + ReadOnlyMemory[] proofs = [new ReadOnlyMemory([proof1, proof2]), default]; + + BlobsV2DirectResponse response = new(blobs, proofs, 2); + + // Write via streaming path + Pipe pipe = new(); + await response.WriteToAsync(pipe.Writer, CancellationToken.None); + await pipe.Writer.CompleteAsync(); + + ReadResult readResult = await pipe.Reader.ReadAsync(); + string streamedJson = Encoding.UTF8.GetString(readResult.Buffer); + pipe.Reader.AdvanceTo(readResult.Buffer.End); + + // Write via STJ for comparison + string stjJson = JsonSerializer.Serialize(response, EthereumJsonSerializer.JsonOptions); + + streamedJson.Should().Be(stjJson); + } + + [Test] + public async Task BlobsV2DirectResponse_WriteToAsync_empty_list() + { + BlobsV2DirectResponse response = new([], [], 0); + + Pipe pipe = new(); + await response.WriteToAsync(pipe.Writer, CancellationToken.None); + await pipe.Writer.CompleteAsync(); + + ReadResult readResult = await pipe.Reader.ReadAsync(); + string json = Encoding.UTF8.GetString(readResult.Buffer); + pipe.Reader.AdvanceTo(readResult.Buffer.End); + + json.Should().Be("[]"); + } + + [Test] + public void BlobsV2DirectResponse_WriteToAsync_throws_on_cancelled_token() + { + byte[] blob = new byte[131072]; // 128KB + byte[]?[] blobs = [blob]; + ReadOnlyMemory[] proofs = [new ReadOnlyMemory([new byte[48]])]; + BlobsV2DirectResponse response = new(blobs, proofs, 1); + + using CancellationTokenSource cts = new(); + cts.Cancel(); + + Pipe pipe = new(); + Func act = async () => await response.WriteToAsync(pipe.Writer, cts.Token); + act.Should().ThrowAsync(); + } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs index f111b2197bcc..3a49eb9e9358 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs @@ -3,9 +3,12 @@ /* cSpell:disable */ using System; +using System.Security.Cryptography; +using System.Text; using System.Threading.Tasks; using Nethermind.Core; using Nethermind.Core.Authentication; +using Nethermind.Core.Extensions; using Nethermind.Logging; using NUnit.Framework; @@ -13,6 +16,9 @@ namespace Nethermind.Merge.Plugin.Test; public class JwtTest { + private const string HexSecret = "5166546A576E5A7234753778214125442A472D4A614E645267556B5870327335"; + private const long TestIat = 1644994971; + [Test] [TestCase("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDQ5OTQ5NzF9.RmIbZajyYGF9fhAq7A9YrTetdf15ebHIJiSdAhX7PME", "true")] [TestCase("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDQ5OTQ5NzV9.HfWy49SIyB12PBB_xEpy6IAiIan5mIqD6Jzeh_J1QNw", "true")] @@ -30,12 +36,135 @@ public class JwtTest [TestCase("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDQ5OTQ5NjF9.huhtaE1cUU2JuhqKmeTrHC3wgl2Tp_1pVh7DuYkKrQo", "true")] public async Task long_key_tests(string token, bool expected) { - ManualTimestamper manualTimestamper = new() { UtcNow = DateTimeOffset.FromUnixTimeSeconds(1644994971).UtcDateTime }; - IRpcAuthentication authentication = JwtAuthentication.FromSecret("5166546A576E5A7234753778214125442A472D4A614E645267556B5870327335", manualTimestamper, LimboTraceLogger.Instance); - IRpcAuthentication authenticationWithPrefix = JwtAuthentication.FromSecret("0x5166546A576E5A7234753778214125442A472D4A614E645267556B5870327335", manualTimestamper, LimboTraceLogger.Instance); + ManualTimestamper manualTimestamper = new() { UtcNow = DateTimeOffset.FromUnixTimeSeconds(TestIat).UtcDateTime }; + IRpcAuthentication authentication = JwtAuthentication.FromSecret(HexSecret, manualTimestamper, LimboTraceLogger.Instance); + IRpcAuthentication authenticationWithPrefix = JwtAuthentication.FromSecret("0x" + HexSecret, manualTimestamper, LimboTraceLogger.Instance); bool actual = await authentication.Authenticate(token); Assert.That(actual, Is.EqualTo(expected)); actual = await authenticationWithPrefix.Authenticate(token); Assert.That(expected, Is.EqualTo(actual)); } + + // --- Guard clause tests (Authenticate entry) --- + + [Test] + public async Task Null_token_returns_false() + { + Assert.That(await CreateAuth().Authenticate(null!), Is.False); + } + + [Test] + public async Task Empty_token_returns_false() + { + Assert.That(await CreateAuth().Authenticate(""), Is.False); + } + + [Test] + public async Task Missing_bearer_prefix_returns_false() + { + // Valid JWT but without "Bearer " prefix + string jwt = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat}}}"); + Assert.That(await CreateAuth().Authenticate(jwt["Bearer ".Length..]), Is.False); + } + + // --- Alternate header format tests (TryValidateManual branches) --- + + [Test] + public async Task HeaderTypAlg_valid_token_returns_true() + { + // {"typ":"JWT","alg":"HS256"} — reversed field order, 36-char Base64Url header + string token = CreateJwt("{\"typ\":\"JWT\",\"alg\":\"HS256\"}", $"{{\"iat\":{TestIat}}}"); + Assert.That(await CreateAuth().Authenticate(token), Is.True); + } + + [Test] + public async Task HeaderAlgOnly_valid_token_returns_true() + { + // {"alg":"HS256"} — no typ field, 20-char Base64Url header + string token = CreateJwt("{\"alg\":\"HS256\"}", $"{{\"iat\":{TestIat}}}"); + Assert.That(await CreateAuth().Authenticate(token), Is.True); + } + + [Test] + public async Task HeaderAlgOnly_expired_iat_returns_false() + { + string token = CreateJwt("{\"alg\":\"HS256\"}", $"{{\"iat\":{TestIat - 200}}}"); + Assert.That(await CreateAuth().Authenticate(token), Is.False); + } + + // --- AuthenticateCore fallback (unrecognized header → library path) --- + + [Test] + public async Task Unrecognized_header_valid_token_falls_to_library() + { + // Extra "kid" field makes the Base64Url header unrecognized → AuthenticateCore path + string token = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\",\"kid\":\"1\"}", $"{{\"iat\":{TestIat}}}"); + Assert.That(await CreateAuth().Authenticate(token), Is.True); + } + + [Test] + public async Task Unrecognized_header_expired_iat_returns_false() + { + string token = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\",\"kid\":\"1\"}", $"{{\"iat\":{TestIat - 200}}}"); + Assert.That(await CreateAuth().Authenticate(token), Is.False); + } + + // --- Cache path tests (TryLastValidationFromCache) --- + + [Test] + public async Task Cache_hit_returns_true_on_repeated_call() + { + IRpcAuthentication auth = CreateAuth(); + string token = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat}}}"); + + Assert.That(await auth.Authenticate(token), Is.True); + // Second call with same token should hit cache and still return true + Assert.That(await auth.Authenticate(token), Is.True); + } + + [Test] + public async Task Cache_eviction_when_iat_expires() + { + ManualTimestamper ts = new() { UtcNow = DateTimeOffset.FromUnixTimeSeconds(TestIat).UtcDateTime }; + IRpcAuthentication auth = JwtAuthentication.FromSecret(HexSecret, ts, LimboTraceLogger.Instance); + string token = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat}}}"); + + Assert.That(await auth.Authenticate(token), Is.True); + + // Advance time beyond TTL — cache should evict, iat check should fail + ts.UtcNow = DateTimeOffset.FromUnixTimeSeconds(TestIat + 61).UtcDateTime; + Assert.That(await auth.Authenticate(token), Is.False); + } + + [Test] + public async Task Cache_miss_different_token_revalidates() + { + IRpcAuthentication auth = CreateAuth(); + string token1 = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat}}}"); + string token2 = CreateJwt("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", $"{{\"iat\":{TestIat + 1}}}"); + + Assert.That(await auth.Authenticate(token1), Is.True); + // Different token — cache miss, must revalidate + Assert.That(await auth.Authenticate(token2), Is.True); + } + + // --- Helpers --- + + private static IRpcAuthentication CreateAuth(long nowUnixSeconds = TestIat) + { + ManualTimestamper ts = new() { UtcNow = DateTimeOffset.FromUnixTimeSeconds(nowUnixSeconds).UtcDateTime }; + return JwtAuthentication.FromSecret(HexSecret, ts, LimboTraceLogger.Instance); + } + + private static string CreateJwt(string headerJson, string payloadJson) + { + byte[] secret = Bytes.FromHexString(HexSecret); + string header = Base64UrlEncode(Encoding.UTF8.GetBytes(headerJson)); + string payload = Base64UrlEncode(Encoding.UTF8.GetBytes(payloadJson)); + byte[] sig = HMACSHA256.HashData(secret, Encoding.ASCII.GetBytes($"{header}.{payload}")); + return $"Bearer {header}.{payload}.{Base64UrlEncode(sig)}"; + } + + private static string Base64UrlEncode(byte[] data) + => Convert.ToBase64String(data).TrimEnd('=').Replace('+', '-').Replace('/', '_'); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs index c6b82b964fdc..3739ab6d7c85 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/MergePluginTests.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; using System.Threading.Tasks; using Autofac; using FluentAssertions; @@ -29,6 +31,17 @@ namespace Nethermind.Merge.Plugin.Test; public class MergePluginTests { + private sealed class SourceGenProbe + { + public int Value { get; set; } + } + + private sealed class ThrowingProbeResolver : IJsonTypeInfoResolver + { + public JsonTypeInfo? GetTypeInfo(Type type, JsonSerializerOptions options) => + type == typeof(SourceGenProbe) ? throw new InvalidOperationException("probe resolver was used") : null; + } + private ChainSpec _chainSpec = null!; private MergeConfig _mergeConfig = null!; private IJsonRpcConfig _jsonRpcConfig = null!; @@ -102,6 +115,15 @@ public void Init_merge_plugin_does_not_throw_exception(bool enabled) Assert.DoesNotThrow(() => _plugin.InitBlockProducer(_consensusPlugin!)); } + [Test] + public void AddTypeInfoResolver_updates_existing_serializer_instances() + { + EthereumJsonSerializer serializer = new(); + EthereumJsonSerializer.AddTypeInfoResolver(new ThrowingProbeResolver()); + + Assert.Throws(() => serializer.Serialize(new SourceGenProbe { Value = 1 })); + } + [Test] public async Task Initializes_correctly() { diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/TestBlockProcessorInterceptor.Setup.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/TestBlockProcessorInterceptor.Setup.cs index 3e10767608f2..5f6716e42aef 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/TestBlockProcessorInterceptor.Setup.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/TestBlockProcessorInterceptor.Setup.cs @@ -14,9 +14,12 @@ public class TestBranchProcessorInterceptor(IBranchProcessor baseBlockProcessor, { public int DelayMs { get; set; } = delayMs; public Exception? ExceptionToThrow { get; set; } + public ManualResetEventSlim? ProcessingStarted { get; set; } public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlocks, ProcessingOptions processingOptions, IBlockTracer blockTracer, CancellationToken token) { + ProcessingStarted?.Set(); + if (DelayMs > 0) { Thread.Sleep(DelayMs); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV1DirectResponse.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV1DirectResponse.cs new file mode 100644 index 000000000000..5135babb86eb --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV1DirectResponse.cs @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Core.Collections; +using Nethermind.JsonRpc; +using Nethermind.Serialization.Json; + +namespace Nethermind.Merge.Plugin.Data; + +/// +/// Wraps an of and writes JSON +/// directly into a , bypassing +/// to avoid extra buffer copies for large blob payloads. +/// +public sealed class BlobsV1DirectResponse : IStreamableResult, IEnumerable, IDisposable +{ + private readonly ArrayPoolList _items; + + public BlobsV1DirectResponse(ArrayPoolList items) + { + _items = items; + } + + public async ValueTask WriteToAsync(PipeWriter writer, CancellationToken cancellationToken) + { + writer.Write("["u8); + + int count = _items.Count; + for (int i = 0; i < count; i++) + { + if (i > 0) writer.Write(","u8); + + BlobAndProofV1? item = _items[i]; + if (item is null) + { + writer.Write("null"u8); + } + else + { + writer.Write("{\"blob\":\"0x"u8); + HexWriter.WriteHexChunked(writer, item.Blob); + writer.Write("\",\"proof\":\"0x"u8); + HexWriter.WriteHexSmall(writer, item.Proof); + writer.Write("\"}"u8); + } + + // Flush after each entry for backpressure + FlushResult flushResult = await writer.FlushAsync(cancellationToken); + if (flushResult.IsCompleted || flushResult.IsCanceled) + return; + } + + writer.Write("]"u8); + } + + public IEnumerator GetEnumerator() => _items.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public void Dispose() => _items.Dispose(); +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV2DirectResponse.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV2DirectResponse.cs new file mode 100644 index 000000000000..ad4048f31294 --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/BlobsV2DirectResponse.cs @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO.Pipelines; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.JsonRpc; +using Nethermind.Serialization.Json; + +namespace Nethermind.Merge.Plugin.Data; + +/// +/// Wraps parallel arrays of blobs and proofs and writes JSON directly into a +/// , bypassing +/// to avoid extra buffer copies for large blob payloads. +/// +public sealed class BlobsV2DirectResponse : IStreamableResult, IEnumerable +{ + private readonly byte[]?[] _blobs; + private readonly ReadOnlyMemory[] _proofs; + private readonly int _count; + + public BlobsV2DirectResponse(byte[]?[] blobs, ReadOnlyMemory[] proofs, int count) + { + Debug.Assert(count <= blobs.Length && count <= proofs.Length, + "count must not exceed array lengths"); + _blobs = blobs; + _proofs = proofs; + _count = count; + } + + public async ValueTask WriteToAsync(PipeWriter writer, CancellationToken cancellationToken) + { + writer.Write("["u8); + + for (int i = 0; i < _count; i++) + { + if (i > 0) writer.Write(","u8); + + byte[]? blob = _blobs[i]; + if (blob is null) + { + writer.Write("null"u8); + } + else + { + writer.Write("{\"blob\":\"0x"u8); + HexWriter.WriteHexChunked(writer, blob); + writer.Write("\",\"proofs\":["u8); + + ReadOnlySpan proofs = _proofs[i].Span; + for (int p = 0; p < proofs.Length; p++) + { + if (p > 0) writer.Write(","u8); + writer.Write("\"0x"u8); + HexWriter.WriteHexSmall(writer, proofs[p]); + writer.Write("\""u8); + } + + writer.Write("]}"u8); + } + + // Flush after each entry for backpressure + FlushResult flushResult = await writer.FlushAsync(cancellationToken); + if (flushResult.IsCompleted || flushResult.IsCanceled) + return; + } + + writer.Write("]"u8); + } + + // Explicit interface implementation: only used by tests via IEnumerable cast. + // Production serialization goes through IStreamableResult.WriteToAsync. + IEnumerator IEnumerable.GetEnumerator() + { + for (int i = 0; i < _count; i++) + { + byte[]? blob = _blobs[i]; + yield return blob is null ? null : new BlobAndProofV2(blob, _proofs[i].ToArray()); + } + } + + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)this).GetEnumerator(); +} diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/EngineApiJsonContext.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/EngineApiJsonContext.cs new file mode 100644 index 000000000000..912c2421b1fd --- /dev/null +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/EngineApiJsonContext.cs @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json.Serialization; +using Nethermind.Consensus.Producers; +using Nethermind.Merge.Plugin.Handlers; + +namespace Nethermind.Merge.Plugin.Data; + +[JsonSourceGenerationOptions( + GenerationMode = JsonSourceGenerationMode.Metadata, + PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + IncludeFields = true)] +[JsonSerializable(typeof(ExecutionPayload))] +[JsonSerializable(typeof(ExecutionPayloadV3))] +[JsonSerializable(typeof(PayloadStatusV1))] +[JsonSerializable(typeof(byte[][]))] +[JsonSerializable(typeof(ForkchoiceStateV1))] +[JsonSerializable(typeof(ForkchoiceUpdatedV1Result))] +[JsonSerializable(typeof(PayloadAttributes))] +[JsonSerializable(typeof(BlobAndProofV1))] +[JsonSerializable(typeof(BlobAndProofV2))] +[JsonSerializable(typeof(BlobsBundleV1))] +[JsonSerializable(typeof(BlobsBundleV2))] +[JsonSerializable(typeof(GetPayloadV2Result))] +[JsonSerializable(typeof(GetPayloadV3Result))] +[JsonSerializable(typeof(GetPayloadV4Result))] +[JsonSerializable(typeof(GetPayloadV5Result))] +[JsonSerializable(typeof(GetBlobsHandlerV2Request))] +[JsonSerializable(typeof(ExecutionPayloadBodyV1Result))] +[JsonSerializable(typeof(TransitionConfigurationV1))] +[JsonSerializable(typeof(ClientVersionV1))] +internal partial class EngineApiJsonContext : JsonSerializerContext; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs index d0eae608228a..93ba25cde5b7 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ExecutionPayload.cs @@ -137,6 +137,7 @@ public byte[][] Transactions /// true if block created successfully; otherwise, false. public virtual BlockDecodingResult TryGetBlock(UInt256? totalDifficulty = null) { + byte[][] encodedTransactions = Transactions; TransactionDecodingResult transactions = TryGetTransactions(); if (transactions.Error is not null) { @@ -164,11 +165,15 @@ public virtual BlockDecodingResult TryGetBlock(UInt256? totalDifficulty = null) Author = FeeRecipient, IsPostMerge = true, TotalDifficulty = totalDifficulty, - TxRoot = TxTrie.CalculateRoot(transactions.Transactions), + TxRoot = TxTrie.CalculateRoot(encodedTransactions), WithdrawalsRoot = BuildWithdrawalsRoot(), }; - return new BlockDecodingResult(new Block(header, transactions.Transactions, Array.Empty(), Withdrawals)); + Block block = new(header, transactions.Transactions, Array.Empty(), Withdrawals) + { + EncodedTransactions = encodedTransactions + }; + return new BlockDecodingResult(block); } protected virtual Hash256? BuildWithdrawalsRoot() diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandler.cs index 4a0dcf8b7e87..05cc9936d00a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandler.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Nethermind.Core.Collections; using Nethermind.Core.Specs; using Nethermind.JsonRpc; using Nethermind.Merge.Plugin.Data; @@ -27,35 +28,41 @@ public class GetBlobsHandler(ITxPool txPool, IChainHeadSpecProvider chainHeadSpe return ResultWrapper>.Fail(error, MergeErrorCodes.TooLargeRequest); } - return ResultWrapper>.Success(GetBlobsAndProofs(request)); - } - - private IEnumerable GetBlobsAndProofs(byte[][] request) - { bool allBlobsAvailable = true; Metrics.NumberOfRequestedBlobs += request.Length; - foreach (byte[] requestedBlobVersionedHash in request) + ArrayPoolList response = new(request.Length); + try { - if (txPool.TryGetBlobAndProofV0(requestedBlobVersionedHash, out byte[]? blob, out byte[]? proof)) + foreach (byte[] requestedBlobVersionedHash in request) + { + if (txPool.TryGetBlobAndProofV0(requestedBlobVersionedHash, out byte[]? blob, out byte[]? proof)) + { + Metrics.NumberOfSentBlobs++; + response.Add(new BlobAndProofV1(blob, proof)); + } + else + { + allBlobsAvailable = false; + response.Add(null); + } + } + + if (allBlobsAvailable) { - Metrics.NumberOfSentBlobs++; - yield return new BlobAndProofV1(blob, proof); + Metrics.GetBlobsRequestsSuccessTotal++; } else { - allBlobsAvailable = false; - yield return null; + Metrics.GetBlobsRequestsFailureTotal++; } - } - if (allBlobsAvailable) - { - Metrics.GetBlobsRequestsSuccessTotal++; + return ResultWrapper>.Success(new BlobsV1DirectResponse(response)); } - else + catch { - Metrics.GetBlobsRequestsFailureTotal++; + response.Dispose(); + throw; } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs index 47d223833282..341fb547a5f6 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; using System.Threading.Tasks; -using Nethermind.Core.Collections; using Nethermind.JsonRpc; using Nethermind.Merge.Plugin.Data; using Nethermind.TxPool; @@ -20,51 +20,27 @@ public class GetBlobsHandlerV2(ITxPool txPool) : IAsyncHandler MaxRequest) { - var error = $"The number of requested blobs must not exceed {MaxRequest}"; + string error = $"The number of requested blobs must not exceed {MaxRequest}"; return ResultWrapper?>.Fail(error, MergeErrorCodes.TooLargeRequest); } Metrics.GetBlobsRequestsTotal += request.BlobVersionedHashes.Length; - int count = txPool.GetBlobCounts(request.BlobVersionedHashes); + int n = request.BlobVersionedHashes.Length; + byte[]?[] blobs = new byte[n][]; + ReadOnlyMemory[] proofs = new ReadOnlyMemory[n]; + int count = txPool.TryGetBlobsAndProofsV1(request.BlobVersionedHashes, blobs, proofs); + Metrics.GetBlobsRequestsInBlobpoolTotal += count; // quick fail if we don't have some blob (unless partial return is allowed) - if (!request.AllowPartialReturn && count != request.BlobVersionedHashes.Length) + if (!request.AllowPartialReturn && count != n) { return ReturnEmptyArray(); } - ArrayPoolList response = new(request.BlobVersionedHashes.Length); - - try - { - foreach (byte[] requestedBlobVersionedHash in request.BlobVersionedHashes) - { - if (txPool.TryGetBlobAndProofV1(requestedBlobVersionedHash, out byte[]? blob, out byte[][]? cellProofs)) - { - response.Add(new BlobAndProofV2(blob, cellProofs)); - } - else if (request.AllowPartialReturn) - { - response.Add(null); - } - else - { - // fail if we were not able to collect full blob data - response.Dispose(); - return ReturnEmptyArray(); - } - } - - Metrics.GetBlobsRequestsSuccessTotal++; - return ResultWrapper?>.Success(response); - } - catch - { - response.Dispose(); - throw; - } + Metrics.GetBlobsRequestsSuccessTotal++; + return ResultWrapper?>.Success(new BlobsV2DirectResponse(blobs, proofs, n)); } private Task?>> ReturnEmptyArray() diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index 9e98a951e330..756e9d571add 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -35,6 +35,7 @@ using Nethermind.Merge.Plugin.InvalidChainTracker; using Nethermind.Merge.Plugin.Synchronization; using Nethermind.Network.Contract.P2P; +using Nethermind.Serialization.Json; using Nethermind.Specs.ChainSpecStyle; using Nethermind.State; using Nethermind.Synchronization; @@ -68,6 +69,7 @@ public partial class MergePlugin(ChainSpec chainSpec, IMergeConfig mergeConfig) public virtual Task Init(INethermindApi nethermindApi) { _api = nethermindApi; + EthereumJsonSerializer.AddTypeInfoResolver(EngineApiJsonContext.Default); _syncConfig = nethermindApi.Config(); _blocksConfig = nethermindApi.Config(); _txPoolConfig = nethermindApi.Config(); diff --git a/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs b/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs index bb35e69108b5..e21d3b810dfa 100644 --- a/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs +++ b/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs @@ -8,6 +8,8 @@ using System.Linq; using System.Reflection; using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Attributes; @@ -176,6 +178,45 @@ public void Register_and_update_metrics_should_not_throw_exception() }); } + [Test] + public void UpdateAllMetrics_does_not_throw_when_registration_is_concurrent() + { + MetricsConfig metricsConfig = new() { Enabled = true }; + MetricsController metricsController = new(metricsConfig); + + using CancellationTokenSource cts = new(TimeSpan.FromSeconds(2)); + CancellationToken ct = cts.Token; + + // Continuously call UpdateAllMetrics on one thread while registering metrics on another + Task updater = Task.Run(() => + { + while (!ct.IsCancellationRequested) + { + metricsController.UpdateAllMetrics(); + } + }); + + Task registrar = Task.Run(() => + { + Type[] types = + [ + typeof(TestMetrics), + typeof(Blockchain.Metrics), + typeof(Evm.Metrics), + typeof(Network.Metrics), + typeof(Db.Metrics), + ]; + + for (int i = 0; !ct.IsCancellationRequested; i++) + { + metricsController.RegisterMetrics(types[i % types.Length]); + metricsController.AddMetricsUpdateAction(() => { }); + } + }); + + Assert.DoesNotThrowAsync(() => Task.WhenAll(updater, registrar)); + } + [Test] public void All_config_items_have_descriptions() { diff --git a/src/Nethermind/Nethermind.Monitoring/Metrics/MetricsController.cs b/src/Nethermind/Nethermind.Monitoring/Metrics/MetricsController.cs index 6eefbce28fc1..1f8661324615 100644 --- a/src/Nethermind/Nethermind.Monitoring/Metrics/MetricsController.cs +++ b/src/Nethermind/Nethermind.Monitoring/Metrics/MetricsController.cs @@ -33,6 +33,7 @@ public partial class MetricsController : IMetricsController private static bool _staticLabelsInitialized; private readonly Dictionary _metricUpdaters = new(); + private volatile IMetricUpdater[][] _updaterValues = []; // Largely for testing reason internal readonly Dictionary _individualUpdater = new(); @@ -40,7 +41,7 @@ public partial class MetricsController : IMetricsController private readonly bool _useCounters; private readonly bool _enableDetailedMetric; - private readonly List _callbacks = new(); + private volatile Action[] _callbacks = []; public interface IMetricUpdater { @@ -235,6 +236,7 @@ public void RegisterMetrics(Type type) } } _metricUpdaters[type] = metricUpdaters.ToArray(); + _updaterValues = [.. _metricUpdaters.Values]; } } @@ -354,7 +356,7 @@ public void UpdateAllMetrics() callback(); } - foreach (IMetricUpdater[] updaters in _metricUpdaters.Values) + foreach (IMetricUpdater[] updaters in _updaterValues) { foreach (IMetricUpdater metricUpdater in updaters) { @@ -363,7 +365,7 @@ public void UpdateAllMetrics() } } - public void AddMetricsUpdateAction(Action callback) => _callbacks.Add(callback); + public void AddMetricsUpdateAction(Action callback) => _callbacks = [.. _callbacks, callback]; private static string GetGaugeNameKey(params string[] par) => string.Join('.', par); diff --git a/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs b/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs index d379f1855bd4..63f19c0cd136 100644 --- a/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs +++ b/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs @@ -34,10 +34,6 @@ public static class Protocol /// public const string Par = "par"; /// - /// Nethermind Data Marketplace - /// - public const string Ndm = "ndm"; - /// /// Account Abstraction /// public const string AA = "aa"; diff --git a/src/Nethermind/Nethermind.Network.Contract/P2P/ProtocolParser.cs b/src/Nethermind/Nethermind.Network.Contract/P2P/ProtocolParser.cs index a0f185b8082d..5f2e4e274bbd 100644 --- a/src/Nethermind/Nethermind.Network.Contract/P2P/ProtocolParser.cs +++ b/src/Nethermind/Nethermind.Network.Contract/P2P/ProtocolParser.cs @@ -17,7 +17,6 @@ public static class ProtocolParser private const uint Shh = 0x686873u; // "shh" private const uint Bzz = 0x7A7A62u; // "bzz" private const uint Par = 0x726170u; // "par" - private const uint Ndm = 0x6D646Eu; // "ndm" private const uint Snap = 0x70616E73u; // "snap" private const ulong Nodedata = 0x6174616465646F6Eul; // "nodedata" @@ -48,8 +47,6 @@ public static bool TryGetProtocolCode(ReadOnlySpan protocolSpan, [NotNullW protocol = Protocol.Bzz; return true; case Par: protocol = Protocol.Par; return true; - case Ndm: - protocol = Protocol.Ndm; return true; } break; diff --git a/src/Nethermind/Nethermind.Network.Discovery/Serializers/IDiscoveryMsgSerializersProvider.cs b/src/Nethermind/Nethermind.Network.Discovery/Serializers/IDiscoveryMsgSerializersProvider.cs deleted file mode 100644 index ea094cec0d7e..000000000000 --- a/src/Nethermind/Nethermind.Network.Discovery/Serializers/IDiscoveryMsgSerializersProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Network.Discovery.Serializers; - -public interface IDiscoveryMsgSerializersProvider -{ - void RegisterDiscoverySerializers(); -} diff --git a/src/Nethermind/Nethermind.Network.Test/MessageQueueTests.cs b/src/Nethermind/Nethermind.Network.Test/MessageQueueTests.cs new file mode 100644 index 000000000000..fe653da76271 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/MessageQueueTests.cs @@ -0,0 +1,168 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Network.P2P; +using Nethermind.Network.P2P.Subprotocols; +using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Network.Test; + +public class MessageQueueTests +{ + private readonly List _recordedSends = new(); + private MessageQueue> _queue; + + [SetUp] + public void Setup() + { + _recordedSends.Clear(); + _queue = new((message) => _recordedSends.Add(message)); + } + + [Test] + public void Send_first_request_is_sent_immediately() + { + Request> request = CreateRequest(); + + _queue.Send(request); + + _recordedSends.Count.Should().Be(1); + request.CompletionSource.Task.IsCompleted.Should().BeFalse(); + } + + [Test] + public void Send_second_request_is_queued() + { + Request> request1 = CreateRequest(); + Request> request2 = CreateRequest(); + + _queue.Send(request1); + _queue.Send(request2); + + _recordedSends.Count.Should().Be(1); + request2.CompletionSource.Task.IsCompleted.Should().BeFalse(); + } + + [Test] + public void Handle_completes_current_request_and_sends_next() + { + Request> request1 = CreateRequest(); + Request> request2 = CreateRequest(); + + IOwnedReadOnlyList response = new[] { Build.A.BlockHeader.TestObject }.ToPooledList(); + + _queue.Send(request1); + _queue.Send(request2); + + _queue.Handle(response, 100); + + request1.CompletionSource.Task.IsCompleted.Should().BeTrue(); + request1.CompletionSource.Task.Result.Should().BeSameAs(response); + _recordedSends.Count.Should().Be(2); + } + + [Test] + public void Handle_throws_when_no_current_request() + { + using IOwnedReadOnlyList response = new[] { Build.A.BlockHeader.TestObject }.ToPooledList(); + + _queue.Invoking(q => q.Handle(response, 100)) + .Should() + .Throw(); + } + + [Test] + public void Handle_disposes_data_when_no_current_request() + { + IOwnedReadOnlyList response = Substitute.For>(); + + _queue.Invoking(q => q.Handle(response, 100)) + .Should() + .Throw(); + + response.Received().Dispose(); + } + + [Test] + public void Handle_does_not_throw_when_completion_source_already_cancelled() + { + Request> request = CreateRequest(); + + _queue.Send(request); + + // Simulate timeout cancelling the CompletionSource + request.CompletionSource.TrySetCanceled(); + + using IOwnedReadOnlyList response = new[] { Build.A.BlockHeader.TestObject }.ToPooledList(); + + // Should not throw — this is the core regression test + _queue.Invoking(q => q.Handle(response, 100)) + .Should() + .NotThrow(); + } + + [Test] + public void Handle_disposes_data_when_completion_source_already_cancelled() + { + Request> request = CreateRequest(); + + _queue.Send(request); + + // Simulate timeout cancelling the CompletionSource + request.CompletionSource.TrySetCanceled(); + + IOwnedReadOnlyList response = Substitute.For>(); + + _queue.Handle(response, 100); + + response.Received().Dispose(); + } + + [Test] + public void Handle_dequeues_next_request_even_when_current_was_cancelled() + { + Request> request1 = CreateRequest(); + Request> request2 = CreateRequest(); + + _queue.Send(request1); + _queue.Send(request2); + + // Simulate timeout cancelling the first request + request1.CompletionSource.TrySetCanceled(); + + IOwnedReadOnlyList response = Substitute.For>(); + + _queue.Handle(response, 100); + + // The second request should have been dequeued and sent + _recordedSends.Count.Should().Be(2); + request2.CompletionSource.Task.IsCompleted.Should().BeFalse(); + } + + [Test] + public void Send_does_not_send_when_closed() + { + _queue.CompleteAdding(); + + GetBlockHeadersMessage msg = new(); + Request> request = new(msg); + + _queue.Send(request); + + _recordedSends.Count.Should().Be(0); + } + + private static Request> CreateRequest() + { + return new(new GetBlockHeadersMessage()); + } +} diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/ProtocolParserTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/ProtocolParserTests.cs index 3baecbc75c80..43860b4f0dc7 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/ProtocolParserTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/ProtocolParserTests.cs @@ -61,16 +61,6 @@ public void TryGetProtocolCode_Par_ReturnsTrue() Assert.That(protocol, Is.EqualTo(Protocol.Par)); } - [Test] - public void TryGetProtocolCode_Ndm_ReturnsTrue() - { - byte[] protocolBytes = Encoding.UTF8.GetBytes("ndm"); - bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); - - Assert.That(result, Is.True); - Assert.That(protocol, Is.EqualTo(Protocol.Ndm)); - } - [Test] public void TryGetProtocolCode_Snap_ReturnsTrue() { @@ -206,15 +196,6 @@ public void TryGetProtocolCode_ValidatesHexConstants_Par() Assert.That(parValue, Is.EqualTo(0x726170u), "Hex constant for 'par' should match"); } - [Test] - public void TryGetProtocolCode_ValidatesHexConstants_Ndm() - { - byte[] ndmBytes = Encoding.UTF8.GetBytes("ndm"); - uint ndmValue = CalculateThreeByteKey(ndmBytes); - - Assert.That(ndmValue, Is.EqualTo(0x6D646Eu), "Hex constant for 'ndm' should match"); - } - [Test] public void TryGetProtocolCode_ValidatesHexConstants_Snap() { diff --git a/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs b/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs index 3121523f1e07..36ab108cbd36 100644 --- a/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs @@ -78,7 +78,7 @@ public async Task Will_connect_to_a_candidate_node() ctx.SetupPersistedPeers(1); ctx.PeerPool.Start(); ctx.PeerManager.Start(); - Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.EqualTo(1).After(_travisDelay, 10)); + Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.EqualTo(1).After(_delay, 10)); } [Test, Retry(3)] @@ -88,14 +88,14 @@ public async Task Will_only_connect_up_to_max_peers() ctx.SetupPersistedPeers(50); ctx.PeerPool.Start(); ctx.PeerManager.Start(); - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); int expectedConnectCount = 25; Assert.That( () => ctx.RlpxPeer.ConnectAsyncCallsCount, Is .InRange(expectedConnectCount, expectedConnectCount + 1) - .After(_travisDelay * 10, 10)); + .After(_delay * 10, 10)); } [Test] @@ -184,6 +184,7 @@ public async Task Will_accept_static_connection() [TestCase(false, ConnectionDirection.In)] // [TestCase(true, ConnectionDirection.Out)] // cannot create an active peer waiting for the test [TestCase(false, ConnectionDirection.Out)] + [NonParallelizable] public async Task Will_agree_on_which_session_to_disconnect_when_connecting_at_once(bool shouldLose, ConnectionDirection firstDirection) { @@ -207,9 +208,12 @@ void EnsureSession(ISession? session) { if (session is null) return; if (session.State < SessionState.HandshakeComplete) session.Handshake(session.Node.Id); - session.Init(5, context, packetSender); + if (session.State < SessionState.Initialized) session.Init(5, context, packetSender); } + bool expectedOutSessionClosing = firstDirection == ConnectionDirection.In ? shouldLose : !shouldLose; + bool expectedInSessionClosing = !expectedOutSessionClosing; + if (firstDirection == ConnectionDirection.In) { ctx.RlpxPeer.CreateIncoming(session1); @@ -217,12 +221,6 @@ void EnsureSession(ISession? session) { throw new NetworkingException($"Failed to connect to {session1.Node:s}", NetworkExceptionType.TargetUnreachable); } - - EnsureSession(ctx.PeerManager.ActivePeers.First().OutSession); - EnsureSession(ctx.PeerManager.ActivePeers.First().InSession); - - (ctx.PeerManager.ActivePeers.First().OutSession?.IsClosing ?? true).Should().Be(shouldLose); - (ctx.PeerManager.ActivePeers.First().InSession?.IsClosing ?? true).Should().Be(!shouldLose); } else { @@ -233,15 +231,23 @@ void EnsureSession(ISession? session) } ctx.RlpxPeer.SessionCreated -= HandshakeOnCreate; ctx.RlpxPeer.CreateIncoming(session1); + } - EnsureSession(ctx.PeerManager.ActivePeers.First().OutSession); - EnsureSession(ctx.PeerManager.ActivePeers.First().InSession); + Assert.That(() => + { + Peer? activePeer = ctx.PeerManager.ActivePeers.SingleOrDefault(); + if (activePeer is null) return false; - (ctx.PeerManager.ActivePeers.First().OutSession?.IsClosing ?? true).Should().Be(!shouldLose); - (ctx.PeerManager.ActivePeers.First().InSession?.IsClosing ?? true).Should().Be(shouldLose); - } + EnsureSession(activePeer.OutSession); + EnsureSession(activePeer.InSession); - ctx.PeerManager.ActivePeers.Count.Should().Be(1); + return activePeer.OutSession is not null + && activePeer.InSession is not null + && activePeer.OutSession.IsClosing == expectedOutSessionClosing + && activePeer.InSession.IsClosing == expectedInSessionClosing; + }, Is.True.After(_delayLonger, 20)); + + Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(1).After(_delay, 10)); } private void HandshakeOnCreate(object sender, SessionEventArgs e) @@ -256,11 +262,11 @@ public async Task Will_fill_up_on_disconnects() ctx.SetupPersistedPeers(50); ctx.PeerPool.Start(); ctx.PeerManager.Start(); - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); Assert.That(ctx.RlpxPeer.ConnectAsyncCallsCount, Is.AtLeast(25)); ctx.DisconnectAllSessions(); - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); Assert.That(ctx.RlpxPeer.ConnectAsyncCallsCount, Is.AtLeast(50)); } @@ -273,7 +279,7 @@ public async Task Ok_if_fails_to_connect() ctx.PeerPool.Start(); ctx.PeerManager.Start(); - Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(0).After(_travisDelay, 10)); + Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(0).After(_delay, 10)); } [Test, Retry(3)] @@ -296,7 +302,7 @@ public async Task Will_fill_up_over_and_over_again_on_disconnects() { Assert.That( () => ctx.PeerPool.ActivePeers.Count, - Is.AtLeast(25).After(_travisDelayLonger * 2, 10)); + Is.AtLeast(25).After(_delayLonger * 2, 10)); ctx.DisconnectAllSessions(); } } @@ -318,7 +324,7 @@ public async Task Will_fill_up_over_and_over_again_on_newly_discovered() for (int i = 0; i < 10; i++) { ctx.DiscoverNew(25); - Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(25).After(_travisDelay, 10)); + Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(25).After(_delay, 10)); } } @@ -334,8 +340,8 @@ public async Task Will_not_stop_trying_on_rlpx_connection_failure() for (int i = 0; i < 10; i++) { ctx.DiscoverNew(25); - await Task.Delay(_travisDelay); - Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.EqualTo(25 * (i + 1)).After(1000, 10)); + await Task.Delay(_delay); + Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.EqualTo(25 * (i + 1)).After(_delayLonger, 10)); } } @@ -377,13 +383,13 @@ public async Task IfPeerAdded_with_invalid_chain_then_do_not_connect() ctx.PeerPool.GetOrAdd(networkNode); - Assert.That(() => ctx.PeerPool.ActivePeers.Count, Is.EqualTo(0).After(_travisDelay, 10)); + Assert.That(() => ctx.PeerPool.ActivePeers.Count, Is.EqualTo(0).After(_delay, 10)); } - private readonly int _travisDelay = 500; + private readonly int _delay = 500; - private readonly int _travisDelayLong = 1000; - private readonly int _travisDelayLonger = 3000; + private readonly int _delayLong = 1000; + private readonly int _delayLonger = 3000; [Test] [Ignore("Behaviour changed that allows peers to go over max if awaiting response")] @@ -397,7 +403,7 @@ public async Task Will_fill_up_with_incoming_over_and_over_again_on_disconnects( for (int i = 0; i < 10; i++) { ctx.CreateNewIncomingSessions(25); - Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(25).After(_travisDelay, 10)); + Assert.That(() => ctx.PeerManager.ActivePeers.Count, Is.EqualTo(25).After(_delay, 10)); } } @@ -416,10 +422,10 @@ public async Task Will_fill_up_over_and_over_again_on_disconnects_and_when_ids_k { currentCount += 25; maxCount += 50; - Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.InRange(currentCount, maxCount).After(_travisDelayLonger * 2, 10)); + Assert.That(() => ctx.RlpxPeer.ConnectAsyncCallsCount, Is.InRange(currentCount, maxCount).After(_delayLonger * 2, 10)); ctx.RlpxPeer.ConnectAsyncCallsCount.Should().BeInRange(currentCount, maxCount); ctx.HandshakeAllSessions(); - await Task.Delay(_travisDelay); + await Task.Delay(_delay); ctx.DisconnectAllSessions(); } @@ -448,10 +454,10 @@ public async Task Will_fill_up_over_and_over_again_on_disconnects_and_when_ids_k for (int i = 0; i < 10; i++) { currentCount += count; - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); ctx.RlpxPeer.ConnectAsyncCallsCount.Should().BeInRange(currentCount, currentCount + count); ctx.HandshakeAllSessions(); - await Task.Delay(_travisDelay); + await Task.Delay(_delay); ctx.DisconnectAllSessions(); } } @@ -473,12 +479,12 @@ public async Task Will_fill_up_over_and_over_again_on_disconnects_and_when_ids_k for (int i = 0; i < 10; i++) { currentCount += count; - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); ctx.RlpxPeer.ConnectAsyncCallsCount.Should().BeInRange(currentCount, currentCount + count); ctx.HandshakeAllSessions(); - await Task.Delay(_travisDelay); + await Task.Delay(_delay); ctx.CreateIncomingSessions(); - await Task.Delay(_travisDelay); + await Task.Delay(_delay); ctx.DisconnectAllSessions(); } } @@ -514,7 +520,7 @@ public async Task Will_load_static_nodes_and_connect_to_them() ctx.TestNodeSource.AddNode(new Node(TestItem.PublicKeyA, node.Host, node.Port)); } - Assert.That(() => ctx.PeerManager.ActivePeers.Count(static p => p.Node.IsStatic), Is.EqualTo(nodesCount).After(_travisDelay, 10)); + Assert.That(() => ctx.PeerManager.ActivePeers.Count(static p => p.Node.IsStatic), Is.EqualTo(nodesCount).After(_delay, 10)); } [Test, Retry(5)] @@ -527,7 +533,7 @@ public async Task Will_disconnect_on_remove_static_node() ctx.StaticNodesManager.DiscoverNodes(Arg.Any()).Returns(staticNodes.Select(n => new Node(n, true)).ToAsyncEnumerable()); ctx.PeerPool.Start(); ctx.PeerManager.Start(); - await Task.Delay(_travisDelay); + await Task.Delay(_delay); void DisconnectHandler(object o, DisconnectEventArgs e) => disconnections++; ctx.Sessions.ForEach(s => s.Disconnected += DisconnectHandler); @@ -548,7 +554,7 @@ public async Task Will_connect_and_disconnect_on_peer_management() ctx.PeerManager.Start(); var node = new NetworkNode(ctx.GenerateEnode()); ctx.PeerPool.GetOrAdd(node); - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); void DisconnectHandler(object o, DisconnectEventArgs e) => disconnections++; ctx.PeerManager.ActivePeers.Select(p => p.Node.Id).Should().BeEquivalentTo(new[] { node.NodeId }); @@ -570,7 +576,7 @@ public async Task Will_only_add_same_peer_once() ctx.PeerPool.GetOrAdd(node); ctx.PeerPool.GetOrAdd(node); ctx.PeerPool.GetOrAdd(node); - await Task.Delay(_travisDelayLong); + await Task.Delay(_delayLong); ctx.PeerManager.ActivePeers.Should().HaveCount(1); } @@ -581,7 +587,7 @@ public async Task RemovePeer_should_fail_if_peer_not_added() ctx.PeerPool.Start(); ctx.PeerManager.Start(); var node = new NetworkNode(ctx.GenerateEnode()); - Assert.That(() => ctx.PeerPool.TryRemove(node.NodeId, out _), Is.False.After(_travisDelay, 10)); + Assert.That(() => ctx.PeerPool.TryRemove(node.NodeId, out _), Is.False.After(_delay, 10)); } private class Context : IAsyncDisposable diff --git a/src/Nethermind/Nethermind.Network/P2P/MessageQueue.cs b/src/Nethermind/Nethermind.Network/P2P/MessageQueue.cs index e8c6ef82f4b1..21f8bcd26995 100644 --- a/src/Nethermind/Nethermind.Network/P2P/MessageQueue.cs +++ b/src/Nethermind/Nethermind.Network/P2P/MessageQueue.cs @@ -47,16 +47,15 @@ public void Handle(TData data, long size) { if (_currentRequest is null) { - if (data is IDisposable d) - { - d.Dispose(); - } - + data.TryDispose(); throw new SubprotocolException($"Received a response to {nameof(TMsg)} that has not been requested"); } _currentRequest.ResponseSize = size; - _currentRequest.CompletionSource.SetResult(data); + if (!_currentRequest.CompletionSource.TrySetResult(data)) + { + data.TryDispose(); + } if (_requestQueue.TryDequeue(out _currentRequest)) { _currentRequest!.StartMeasuringTime(); diff --git a/src/Nethermind/Nethermind.Network/P2P/Messages/AddCapabilityMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Messages/AddCapabilityMessageSerializer.cs index 1ab325c5940a..8c9e24f43d51 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Messages/AddCapabilityMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Messages/AddCapabilityMessageSerializer.cs @@ -9,7 +9,7 @@ namespace Nethermind.Network.P2P.Messages { /// - /// This is probably used in NDM + /// Serializes P2P capability negotiation messages. /// public class AddCapabilityMessageSerializer : IZeroMessageSerializer { diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs index 598cf8a0dc53..a9f15b75d8dc 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/SyncPeerProtocolHandlerBase.cs @@ -20,7 +20,6 @@ using Nethermind.Network.P2P.Subprotocols.Eth.V62; using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; using Nethermind.Network.P2P.Subprotocols.Eth.V63.Messages; -using Nethermind.Serialization.Rlp; using Nethermind.Stats; using Nethermind.Stats.Model; using Nethermind.Synchronization; @@ -50,7 +49,6 @@ public abstract class SyncPeerProtocolHandlerBase : ZeroProtocolHandlerBase, ISy protected Hash256 _remoteHeadBlockHash; protected readonly ITimestamper _timestamper; - protected readonly TxDecoder _txDecoder; protected readonly MessageQueue> _headersRequests; protected readonly MessageQueue _bodiesRequests; @@ -67,7 +65,6 @@ protected SyncPeerProtocolHandlerBase(ISession session, { SyncServer = syncServer ?? throw new ArgumentNullException(nameof(syncServer)); _timestamper = Timestamper.Default; - _txDecoder = TxDecoder.Instance; _headersRequests = new MessageQueue>(Send); _bodiesRequests = new MessageQueue(Send); } diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs index 6427402d7b68..59fcac9a4f6f 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroProtocolHandlerBase.cs @@ -86,15 +86,21 @@ CancellationToken token } else { - _ = task.ContinueWith(static t => + // TrySetCanceled first: if it succeeds we own the TCS and need to + // dispose any late-arriving response. If it fails, the response was + // already set by Handle() and the caller owns the data — registering + // a disposal continuation would dispose data the caller still holds. + if (request.CompletionSource.TrySetCanceled(cancellationToken)) { - if (t.IsCompletedSuccessfully) + _ = task.ContinueWith(static t => { - t.Result.TryDispose(); - } - }); + if (t.IsCompletedSuccessfully) + { + t.Result.TryDispose(); + } + }); + } - request.CompletionSource.TrySetCanceled(cancellationToken); StatsManager.ReportTransferSpeedEvent(Session.Node, speedType, 0L); if (Logger.IsDebug) Logger.Debug($"{Session} Request timeout in {describeRequestFunc(request.Message)}"); diff --git a/src/Nethermind/Nethermind.Network/PeerEqualityComparer.cs b/src/Nethermind/Nethermind.Network/PeerEqualityComparer.cs deleted file mode 100644 index 68a0ad6649d3..000000000000 --- a/src/Nethermind/Nethermind.Network/PeerEqualityComparer.cs +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; - -namespace Nethermind.Network -{ - internal class PeerEqualityComparer : IEqualityComparer - { - public bool Equals(Peer x, Peer y) - { - if (x is null || y is null) - { - return false; - } - - return x.Node.Id.Equals(y.Node.Id); - } - - public int GetHashCode(Peer obj) => obj?.Node is null ? 0 : obj.Node.GetHashCode(); - } -} diff --git a/src/Nethermind/Nethermind.Network/Timeouts.cs b/src/Nethermind/Nethermind.Network/Timeouts.cs index 9ed3d1973cf1..2a2051d8ea81 100644 --- a/src/Nethermind/Nethermind.Network/Timeouts.cs +++ b/src/Nethermind/Nethermind.Network/Timeouts.cs @@ -14,11 +14,6 @@ public static class Timeouts public static readonly TimeSpan P2PHello = TimeSpan.FromSeconds(3); public static readonly TimeSpan Eth62Status = TimeSpan.FromSeconds(3); public static readonly TimeSpan Les3Status = TimeSpan.FromSeconds(3); - public static readonly TimeSpan NdmHi = TimeSpan.FromSeconds(3); - public static readonly TimeSpan NdmDeliveryReceipt = TimeSpan.FromSeconds(3); - public static readonly TimeSpan NdmDepositApproval = TimeSpan.FromSeconds(3); - public static readonly TimeSpan NdmEthRequest = TimeSpan.FromSeconds(3); - public static readonly TimeSpan NdmDataRequestResult = TimeSpan.FromSeconds(3); public static readonly TimeSpan Handshake = TimeSpan.FromSeconds(3); public static readonly TimeSpan Disconnection = TimeSpan.FromSeconds(1); } diff --git a/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs b/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs index 07002bf116b6..1dcfe7c78d52 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/Rpc/OptimismEthRpcModuleTest.cs @@ -12,6 +12,7 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; +using Nethermind.Db.LogIndex; using Nethermind.Evm; using Nethermind.Facade; using Nethermind.Facade.Eth; @@ -593,8 +594,7 @@ public static TestRpcBlockchain.Builder WithOptimismEthRpcMod blockchain.ProtocolsManager, blockchain.ForkInfo, new BlocksConfig().SecondsPerSlot, - - sequencerRpcClient, ecdsa, sealer, opSpecHelper + sequencerRpcClient, ecdsa, sealer, new LogIndexConfig(), opSpecHelper )); } } diff --git a/src/Nethermind/Nethermind.Optimism/CL/L1Bridge/EthereumL1Bridge.cs b/src/Nethermind/Nethermind.Optimism/CL/L1Bridge/EthereumL1Bridge.cs index 879b6044d189..7ca731bd5a3d 100644 --- a/src/Nethermind/Nethermind.Optimism/CL/L1Bridge/EthereumL1Bridge.cs +++ b/src/Nethermind/Nethermind.Optimism/CL/L1Bridge/EthereumL1Bridge.cs @@ -17,9 +17,6 @@ namespace Nethermind.Optimism.CL.L1Bridge; public class EthereumL1Bridge : IL1Bridge { - private const int L1EpochSlotSize = 32; - private const int L1SlotTimeMilliseconds = 12000; - private const int L1EpochTimeMilliseconds = L1EpochSlotSize * L1SlotTimeMilliseconds; private readonly IEthApi _ethL1Api; private readonly IBeaconApi _beaconApi; private readonly ILogger _logger; @@ -224,11 +221,6 @@ private DaDataSource ProcessCalldataBatcherTransaction(L1Transaction transaction return _unfinalizedL1BlocksQueue.Count == 0 ? null : _unfinalizedL1BlocksQueue.Dequeue(); } - private void LogReorg() - { - if (_logger.IsInfo) _logger.Info("L1 reorg detected. Resetting pipeline"); - } - public async Task GetBlock(ulong blockNumber, CancellationToken token) => await RetryGetBlock(async () => await _ethL1Api.GetBlockByNumber(blockNumber, true), token); diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs index aa6db5df906f..ad5e85acdcdc 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthModuleFactory.cs @@ -17,6 +17,7 @@ using Nethermind.Config; using Nethermind.Core; using Nethermind.Crypto; +using Nethermind.Db.LogIndex; using Nethermind.JsonRpc.Client; using Nethermind.Network; using Nethermind.Serialization.Json; @@ -44,6 +45,7 @@ public class OptimismEthModuleFactory : ModuleFactoryBase private readonly IOptimismSpecHelper _opSpecHelper; private readonly IProtocolsManager _protocolsManager; private readonly IForkInfo _forkInfo; + private readonly ILogIndexConfig _logIndexConfig; private readonly ulong? _secondsPerSlot; private readonly IJsonRpcClient? _sequencerRpcClient; @@ -67,7 +69,8 @@ public OptimismEthModuleFactory(IJsonRpcConfig rpcConfig, IOptimismSpecHelper opSpecHelper, IOptimismConfig config, IJsonSerializer jsonSerializer, - ITimestamper timestamper + ITimestamper timestamper, + ILogIndexConfig logIndexConfig ) { _secondsPerSlot = blocksConfig.SecondsPerSlot; @@ -88,6 +91,7 @@ ITimestamper timestamper _opSpecHelper = opSpecHelper; _protocolsManager = protocolsManager; _forkInfo = forkInfo; + _logIndexConfig = logIndexConfig; ILogger logger = logManager.GetClassLogger(); if (config.SequencerUrl is null && logger.IsWarn) { @@ -123,10 +127,10 @@ public override IOptimismEthRpcModule Create() _protocolsManager, _forkInfo, _secondsPerSlot, - _sequencerRpcClient, _ecdsa, _sealer, + _logIndexConfig, _opSpecHelper ); } diff --git a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs index b1bc00f3f1fa..e8befe91bc7a 100644 --- a/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs +++ b/src/Nethermind/Nethermind.Optimism/Rpc/OptimismEthRpcModule.cs @@ -10,6 +10,7 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Crypto; +using Nethermind.Db.LogIndex; using Nethermind.Evm; using Nethermind.Facade; using Nethermind.Facade.Eth; @@ -52,6 +53,7 @@ public class OptimismEthRpcModule( IJsonRpcClient? sequencerRpcClient, IEthereumEcdsa ecdsa, ITxSealer sealer, + ILogIndexConfig? logIndexConfig, IOptimismSpecHelper opSpecHelper) : EthRpcModule(rpcConfig, blockchainBridge, @@ -68,6 +70,7 @@ public class OptimismEthRpcModule( feeHistoryOracle, protocolsManager, forkInfo, + logIndexConfig, secondsPerSlot), IOptimismEthRpcModule { public override ResultWrapper eth_getBlockReceipts(BlockParameter blockParameter) diff --git a/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs b/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs index 9b3ca7354aff..174526aaf142 100644 --- a/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs +++ b/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs @@ -35,7 +35,7 @@ public static NethermindApi ContextWithMocks() { NethermindApi.Dependencies apiDependencies = new NethermindApi.Dependencies( Substitute.For(), - Substitute.For(), + new EthereumJsonSerializer(), LimboLogs.Instance, new ChainSpec { Parameters = new ChainParameters(), }, Substitute.For(), diff --git a/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/EthereumStepsManagerTests.cs b/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/EthereumStepsManagerTests.cs index 88222c13a190..761107c92260 100644 --- a/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/EthereumStepsManagerTests.cs +++ b/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/EthereumStepsManagerTests.cs @@ -176,7 +176,8 @@ private static ContainerBuilder CreateCommonBuilder(params IEnumerable .AddSingleton() .AddSingleton() .AddSingleton(new ConfigProvider()) - .AddSingleton(new EthereumJsonSerializer()) + .AddSingleton(new EthereumJsonSerializer()) + .Bind() .AddSingleton(LimboLogs.Instance) .AddSingleton(new ChainSpec()) .AddSingleton(Substitute.For()) diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs b/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs index 35cdf5e4840c..2bf6eefbe92e 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/Api/ApiBuilder.cs @@ -20,7 +20,7 @@ namespace Nethermind.Runner.Ethereum.Api; public class ApiBuilder { private readonly IConfigProvider _configProvider; - private readonly IJsonSerializer _jsonSerializer; + private readonly EthereumJsonSerializer _jsonSerializer; private readonly ILogManager _logManager; private readonly ILogger _logger; private readonly IInitConfig _initConfig; @@ -64,7 +64,7 @@ public EthereumRunner CreateEthereumRunner(IEnumerable plugin return container.Resolve(); } - private ChainSpec LoadChainSpec(IJsonSerializer ethereumJsonSerializer) + private ChainSpec LoadChainSpec(EthereumJsonSerializer ethereumJsonSerializer) { if (_logger.IsDebug) _logger.Debug($"Loading chain spec from {_initConfig.ChainSpecPath}"); diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/EthereumRunner.cs b/src/Nethermind/Nethermind.Runner/Ethereum/EthereumRunner.cs index 9cb196730d2c..75ec67da4646 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/EthereumRunner.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/EthereumRunner.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Threading; using System.Threading.Tasks; using Autofac; @@ -41,18 +40,4 @@ public async Task StopAsync() _logger.Info("Ethereum runner stopped"); } } - - private Task Stop(Func stopAction, string description) - { - try - { - if (_logger.IsInfo) _logger.Info(description); - return stopAction() ?? Task.CompletedTask; - } - catch (Exception e) - { - if (_logger.IsError) _logger.Error($"{description} shutdown error.", e); - return Task.CompletedTask; - } - } } diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs b/src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs index a01cf8002ee1..4f5062940051 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/JsonRpcRunner.cs @@ -37,7 +37,7 @@ public class JsonRpcRunner private readonly IConfigProvider _configurationProvider; private readonly IRpcAuthentication _rpcAuthentication; private readonly ILogManager _logManager; - private readonly IJsonRpcProcessor _jsonRpcProcessor; + private readonly JsonRpcProcessor _jsonRpcProcessor; private readonly IJsonRpcUrlCollection _jsonRpcUrlCollection; private readonly IWebSocketsManager _webSocketsManager; private WebHost? _webApp; @@ -50,7 +50,7 @@ public class JsonRpcRunner private readonly IMainProcessingContext _mainProcessingContext; public JsonRpcRunner( - IJsonRpcProcessor jsonRpcProcessor, + JsonRpcProcessor jsonRpcProcessor, IJsonRpcUrlCollection jsonRpcUrlCollection, IWebSocketsManager webSocketsManager, IConfigProvider configurationProvider, diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs b/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs index 8378db80d5d9..fbdae0a5805d 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs @@ -31,7 +31,7 @@ namespace Nethermind.Runner.Ethereum.Modules; /// /// public class NethermindRunnerModule( - IJsonSerializer jsonSerializer, + EthereumJsonSerializer jsonSerializer, ChainSpec chainSpec, IConfigProvider configProvider, IProcessExitSource processExitSource, @@ -75,6 +75,7 @@ protected override void Load(ContainerBuilder builder) .AddSingleton((api) => api.BlockPreprocessor) .AddSingleton(jsonSerializer) + .AddSingleton(jsonSerializer) .AddSingleton(consensusPlugin) ; diff --git a/src/Nethermind/Nethermind.Runner/JsonRpc/Bootstrap.cs b/src/Nethermind/Nethermind.Runner/JsonRpc/Bootstrap.cs index 7471c2700762..3e8d96b58138 100644 --- a/src/Nethermind/Nethermind.Runner/JsonRpc/Bootstrap.cs +++ b/src/Nethermind/Nethermind.Runner/JsonRpc/Bootstrap.cs @@ -17,9 +17,9 @@ private Bootstrap() { } public static Bootstrap Instance => _instance ??= new Bootstrap(); - public IJsonRpcService? JsonRpcService { private get; set; } + public JsonRpcService? JsonRpcService { private get; set; } public ILogManager? LogManager { private get; set; } - public IJsonSerializer? JsonSerializer { private get; set; } + public EthereumJsonSerializer? JsonSerializer { private get; set; } public IJsonRpcLocalStats? JsonRpcLocalStats { private get; set; } public IRpcAuthentication? JsonRpcAuthentication { private get; set; } diff --git a/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs b/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs index 1da1f1ac071c..73c04f118138 100644 --- a/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs +++ b/src/Nethermind/Nethermind.Runner/JsonRpc/Startup.cs @@ -7,7 +7,10 @@ using System.IO; using System.IO.Pipelines; using System.Net; +using System.Runtime.CompilerServices; using System.Security.Authentication; +using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using HealthChecks.UI.Client; @@ -26,8 +29,10 @@ using Nethermind.Config; using Nethermind.Core.Authentication; using Nethermind.Core.Resettables; +using Nethermind.Facade.Eth; using Nethermind.HealthChecks; using Nethermind.JsonRpc; +using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules; using Nethermind.Logging; using Nethermind.Serialization.Json; @@ -37,6 +42,14 @@ namespace Nethermind.Runner.JsonRpc; public class Startup : IStartup { + private JsonRpcProcessor _jsonRpcProcessor = null!; + private JsonRpcService _jsonRpcService = null!; + private IJsonRpcLocalStats _jsonRpcLocalStats = null!; + private EthereumJsonSerializer _jsonSerializer = null!; + private IJsonRpcConfig _jsonRpcConfig = null!; + private IRpcAuthentication? _rpcAuthentication; + private ILogger _logger = default; + private static ReadOnlySpan _jsonOpeningBracket => [(byte)'[']; private static ReadOnlySpan _jsonComma => [(byte)',']; private static ReadOnlySpan _jsonClosingBracket => [(byte)']']; @@ -84,23 +97,30 @@ public void Configure(IApplicationBuilder app) Configure( app, services.GetRequiredService(), - services.GetRequiredService(), - services.GetRequiredService(), + services.GetRequiredService(), + services.GetRequiredService(), services.GetRequiredService(), - services.GetRequiredService(), + services.GetRequiredService(), services.GetRequiredService()); } - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IJsonRpcProcessor jsonRpcProcessor, IJsonRpcService jsonRpcService, IJsonRpcLocalStats jsonRpcLocalStats, IJsonSerializer jsonSerializer, ApplicationLifetime lifetime) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, JsonRpcProcessor jsonRpcProcessor, JsonRpcService jsonRpcService, IJsonRpcLocalStats jsonRpcLocalStats, EthereumJsonSerializer jsonSerializer, ApplicationLifetime lifetime) { + // Register source-generated type info resolvers before warmup + EthereumJsonSerializer.AddTypeInfoResolver(JsonRpcResponseJsonContext.Default); + EthereumJsonSerializer.AddTypeInfoResolver(FacadeJsonContext.Default); + EthereumJsonSerializer.AddTypeInfoResolver(EthRpcJsonContext.Default); + + // Warm up System.Text.Json metadata for hot response types + EthereumJsonSerializer.WarmupSerializer( + new JsonRpcSuccessResponse { Id = 0 }, + new JsonRpcErrorResponse { Id = 0, Error = new Error { Code = 0, Message = string.Empty } }); + if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } - app.UseRouting(); - app.UseCors(); - IConfigProvider? configProvider = app.ApplicationServices.GetService(); IRpcAuthentication? rpcAuthentication = app.ApplicationServices.GetService(); @@ -116,10 +136,39 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IJsonRpc IJsonRpcUrlCollection jsonRpcUrlCollection = app.ApplicationServices.GetRequiredService(); IHealthChecksConfig healthChecksConfig = configProvider.GetConfig(); - // If request is local, don't use response compression, - // as it allocates a lot, but doesn't improve much for loopback + _jsonRpcProcessor = jsonRpcProcessor; + _jsonRpcService = jsonRpcService; + _jsonRpcLocalStats = jsonRpcLocalStats; + _jsonSerializer = jsonSerializer; + _jsonRpcConfig = jsonRpcConfig; + _rpcAuthentication = rpcAuthentication; + _logger = logger; + + // Engine API fast lane: authenticated engine port POST requests bypass + // routing, CORS, compression, and WebSocket middleware + app.Use(async (ctx, next) => + { + if (ctx.Request.Method != "POST" || + !(ctx.Request.ContentType?.Contains("application/json") ?? false) || + !jsonRpcUrlCollection.TryGetValue(ctx.Connection.LocalPort, out JsonRpcUrl jsonRpcUrl) || + !jsonRpcUrl.IsAuthenticated || + !jsonRpcUrl.RpcEndpoint.HasFlag(RpcEndpoint.Http)) + { + await next(); + return; + } + + await ProcessJsonRpcRequestCoreAsync(ctx, jsonRpcUrl); + }); + + app.UseRouting(); + app.UseCors(); + + // Skip response compression for localhost (low benefit, high allocation cost) + // and for Engine API requests (latency-sensitive consensus path) app.UseWhen(ctx => - !IsLocalhost(ctx.Connection.RemoteIpAddress!), + !IsLocalhost(ctx.Connection.RemoteIpAddress!) && + !(jsonRpcUrlCollection.TryGetValue(ctx.Connection.LocalPort, out JsonRpcUrl url) && url.IsAuthenticated), builder => builder.UseResponseCompression()); if (initConfig.WebSocketsEnabled) @@ -158,37 +207,22 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IJsonRpc }); app.MapWhen( - (ctx) => ctx.Request.ContentType?.Contains("application/json") ?? false, + ctx => ctx.Request.ContentType?.Contains("application/json") ?? false, builder => builder.Run(async ctx => { - var method = ctx.Request.Method; + string method = ctx.Request.Method; if (method is not "POST" and not "GET") { - ctx.Response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; - return; - } - - if (jsonRpcProcessor.ProcessExit.IsCancellationRequested) - { - ctx.Response.StatusCode = (int)HttpStatusCode.ServiceUnavailable; + ctx.Response.StatusCode = StatusCodes.Status405MethodNotAllowed; return; } if (!jsonRpcUrlCollection.TryGetValue(ctx.Connection.LocalPort, out JsonRpcUrl jsonRpcUrl) || !jsonRpcUrl.RpcEndpoint.HasFlag(RpcEndpoint.Http)) { - ctx.Response.StatusCode = (int)HttpStatusCode.NotFound; + ctx.Response.StatusCode = StatusCodes.Status404NotFound; return; } - if (jsonRpcUrl.IsAuthenticated) - { - if (!await rpcAuthentication!.Authenticate(ctx.Request.Headers.Authorization)) - { - await PushErrorResponse(StatusCodes.Status401Unauthorized, ErrorCodes.InvalidRequest, "Authentication error"); - return; - } - } - if (method == "GET" && ctx.Request.Headers.Accept.Count > 0 && !ctx.Request.Headers.Accept[0]!.Contains("text/html", StringComparison.Ordinal)) { @@ -196,128 +230,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IJsonRpc } else if (ctx.Request.ContentType?.Contains("application/json") == false) { - await PushErrorResponse(StatusCodes.Status415UnsupportedMediaType, ErrorCodes.InvalidRequest, "Missing 'application/json' Content-Type header"); + await PushErrorResponseAsync(ctx, StatusCodes.Status415UnsupportedMediaType, ErrorCodes.InvalidRequest, "Missing 'application/json' Content-Type header"); } else { - if (jsonRpcUrl.MaxRequestBodySize is not null) - ctx.Features.Get().MaxRequestBodySize = jsonRpcUrl.MaxRequestBodySize; - - long startTime = Stopwatch.GetTimestamp(); - CountingPipeReader request = new(ctx.Request.BodyReader); - try - { - using JsonRpcContext jsonRpcContext = JsonRpcContext.Http(jsonRpcUrl); - await foreach (JsonRpcResult result in jsonRpcProcessor.ProcessAsync(request, jsonRpcContext)) - { - using (result) - { - await using Stream stream = jsonRpcConfig.BufferResponses ? RecyclableStream.GetStream("http") : null; - CountingWriter resultWriter = stream is not null ? new CountingStreamPipeWriter(stream) : new CountingPipeWriter(ctx.Response.BodyWriter); - try - { - ctx.Response.ContentType = "application/json"; - ctx.Response.StatusCode = GetStatusCode(result); - - if (result.IsCollection) - { - resultWriter.Write(_jsonOpeningBracket); - bool first = true; - JsonRpcBatchResultAsyncEnumerator enumerator = result.BatchedResponses.GetAsyncEnumerator(CancellationToken.None); - try - { - while (await enumerator.MoveNextAsync()) - { - JsonRpcResult.Entry entry = enumerator.Current; - using (entry) - { - if (!first) - { - resultWriter.Write(_jsonComma); - } - - first = false; - await jsonSerializer.SerializeAsync(resultWriter, entry.Response); - _ = jsonRpcLocalStats.ReportCall(entry.Report); - - // We reached the limit and don't want to respond to more request in the batch - if (!jsonRpcContext.IsAuthenticated && resultWriter.WrittenCount > jsonRpcConfig.MaxBatchResponseBodySize) - { - if (logger.IsWarn) logger.Warn($"The max batch response body size exceeded. The current response size {resultWriter.WrittenCount}, and the config setting is JsonRpc.{nameof(jsonRpcConfig.MaxBatchResponseBodySize)} = {jsonRpcConfig.MaxBatchResponseBodySize}"); - enumerator.IsStopped = true; - } - } - } - } - finally - { - await enumerator.DisposeAsync(); - } - - resultWriter.Write(_jsonClosingBracket); - } - else - { - await jsonSerializer.SerializeAsync(resultWriter, result.Response); - } - await resultWriter.CompleteAsync(); - if (stream is not null) - { - ctx.Response.ContentLength = resultWriter.WrittenCount; - stream.Seek(0, SeekOrigin.Begin); - await stream.CopyToAsync(ctx.Response.Body); - } - } - catch (Exception e) when (e.InnerException is OperationCanceledException) - { - await SerializeTimeoutException(resultWriter); - } - catch (OperationCanceledException) - { - await SerializeTimeoutException(resultWriter); - } - finally - { - await ctx.Response.CompleteAsync(); - } - - long handlingTimeMicroseconds = (long)Stopwatch.GetElapsedTime(startTime).TotalMicroseconds; - _ = jsonRpcLocalStats.ReportCall(result.IsCollection - ? new RpcReport("# collection serialization #", handlingTimeMicroseconds, true) - : result.Report.Value, handlingTimeMicroseconds, resultWriter.WrittenCount); - - Interlocked.Add(ref Metrics.JsonRpcBytesSentHttp, resultWriter.WrittenCount); - - // There should be only one response because we don't expect multiple JSON tokens in the request - break; - } - } - } - catch (Microsoft.AspNetCore.Http.BadHttpRequestException e) - { - if (logger.IsDebug) logger.Debug($"Couldn't read request.{Environment.NewLine}{e}"); - await PushErrorResponse(e.StatusCode, e.StatusCode == StatusCodes.Status413PayloadTooLarge - ? ErrorCodes.LimitExceeded - : ErrorCodes.InvalidRequest, - e.Message); - } - finally - { - Interlocked.Add(ref Metrics.JsonRpcBytesReceivedHttp, ctx.Request.ContentLength ?? request.Length); - } - } - Task SerializeTimeoutException(CountingWriter resultStream) - { - JsonRpcErrorResponse? error = jsonRpcService.GetErrorResponse(ErrorCodes.Timeout, "Request was canceled due to enabled timeout."); - return jsonSerializer.SerializeAsync(resultStream, error); - } - async Task PushErrorResponse(int statusCode, int errorCode, string message) - { - JsonRpcErrorResponse? response = jsonRpcService.GetErrorResponse(errorCode, message); - ctx.Response.ContentType = "application/json"; - ctx.Response.StatusCode = statusCode; - await jsonSerializer.SerializeAsync(ctx.Response.BodyWriter, response); - await ctx.Response.CompleteAsync(); + await ProcessJsonRpcRequestCoreAsync(ctx, jsonRpcUrl); } })); @@ -357,6 +274,275 @@ private static bool IsResourceUnavailableError(JsonRpcResponse? response) or JsonRpcErrorResponse { Error.Code: ErrorCodes.LimitExceeded }; } + private async Task PushErrorResponseAsync(HttpContext ctx, int statusCode, int errorCode, string message) + { + ctx.Response.ContentType = "application/json"; + ctx.Response.StatusCode = statusCode; + JsonRpcErrorResponse response = _jsonRpcService.GetErrorResponse(errorCode, message); + await _jsonSerializer.SerializeAsync(ctx.Response.BodyWriter, response); + await ctx.Response.CompleteAsync(); + } + + private async Task ProcessJsonRpcRequestCoreAsync(HttpContext ctx, JsonRpcUrl jsonRpcUrl) + { + if (_jsonRpcProcessor.ProcessExit.IsCancellationRequested) + { + ctx.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; + return; + } + + if (jsonRpcUrl.IsAuthenticated && !await _rpcAuthentication!.Authenticate(ctx.Request.Headers.Authorization)) + { + await PushErrorResponseAsync(ctx, StatusCodes.Status401Unauthorized, ErrorCodes.InvalidRequest, "Authentication error"); + return; + } + + if (jsonRpcUrl.MaxRequestBodySize is not null) + ctx.Features.Get()!.MaxRequestBodySize = jsonRpcUrl.MaxRequestBodySize; + + long startTime = Stopwatch.GetTimestamp(); + // Skip CountingPipeReader when Content-Length is known + long? knownContentLength = ctx.Request.ContentLength; + CountingPipeReader? countingReader = knownContentLength > 0 ? null : new(ctx.Request.BodyReader); + PipeReader request = countingReader ?? ctx.Request.BodyReader; + try + { + using JsonRpcContext jsonRpcContext = JsonRpcContext.Http(jsonRpcUrl); + await foreach (JsonRpcResult result in _jsonRpcProcessor.ProcessAsync(request, jsonRpcContext)) + { + using (result) + { + // Authenticated single responses bypass buffering to avoid double-copy + bool bufferResponse = _jsonRpcConfig.BufferResponses && !(jsonRpcUrl.IsAuthenticated && !result.IsCollection); + await using Stream stream = bufferResponse ? RecyclableStream.GetStream("http") : null; + CountingWriter resultWriter = stream is not null ? new CountingStreamPipeWriter(stream) : new CountingPipeWriter(ctx.Response.BodyWriter); + try + { + ctx.Response.ContentType = "application/json"; + ctx.Response.StatusCode = GetStatusCode(result); + + // Flush headers before body for unbuffered responses + if (stream is null) + { + await ctx.Response.StartAsync(); + } + + if (result.IsCollection) + { + resultWriter.Write(_jsonOpeningBracket); + bool first = true; + JsonRpcBatchResultAsyncEnumerator enumerator = result.BatchedResponses.GetAsyncEnumerator(CancellationToken.None); + try + { + while (await enumerator.MoveNextAsync()) + { + JsonRpcResult.Entry entry = enumerator.Current; + using (entry) + { + if (!first) resultWriter.Write(_jsonComma); + first = false; + await _jsonSerializer.SerializeAsync(resultWriter, entry.Response); + _ = _jsonRpcLocalStats.ReportCall(entry.Report); + + // Stop batch if non-authenticated response exceeds configured size limit + if (!jsonRpcContext.IsAuthenticated && resultWriter.WrittenCount > _jsonRpcConfig.MaxBatchResponseBodySize) + { + if (_logger.IsWarn) _logger.Warn($"The max batch response body size exceeded. The current response size {resultWriter.WrittenCount}, and the config setting is JsonRpc.{nameof(_jsonRpcConfig.MaxBatchResponseBodySize)} = {_jsonRpcConfig.MaxBatchResponseBodySize}"); + enumerator.IsStopped = true; + } + } + } + } + finally + { + await enumerator.DisposeAsync(); + } + resultWriter.Write(_jsonClosingBracket); + } + else if (result.Response is JsonRpcSuccessResponse { Result: IStreamableResult streamable }) + { + await WriteStreamableResponseAsync(resultWriter, result.Response, streamable, ctx.RequestAborted); + } + else + { + WriteJsonRpcResponse(resultWriter, result.Response); + } + await resultWriter.CompleteAsync(); + if (stream is not null) + { + ctx.Response.ContentLength = resultWriter.WrittenCount; + stream.Seek(0, SeekOrigin.Begin); + await stream.CopyToAsync(ctx.Response.Body); + } + } + catch (Exception e) when (e is OperationCanceledException || e.InnerException is OperationCanceledException) + { + JsonRpcErrorResponse error = _jsonRpcService.GetErrorResponse(ErrorCodes.Timeout, "Request was canceled due to enabled timeout."); + await _jsonSerializer.SerializeAsync(resultWriter, error); + } + finally + { + await ctx.Response.CompleteAsync(); + } + + long handlingTimeMicroseconds = (long)Stopwatch.GetElapsedTime(startTime).TotalMicroseconds; + _ = _jsonRpcLocalStats.ReportCall(result.IsCollection + ? new RpcReport("# collection serialization #", handlingTimeMicroseconds, true) + : result.Report.Value, handlingTimeMicroseconds, resultWriter.WrittenCount); + Interlocked.Add(ref Metrics.JsonRpcBytesSentHttp, resultWriter.WrittenCount); + break; + } + } + } + catch (Microsoft.AspNetCore.Http.BadHttpRequestException e) + { + if (_logger.IsDebug) LogBadRequest(_logger, e); + ctx.Response.ContentType = "application/json"; + ctx.Response.StatusCode = e.StatusCode; + JsonRpcErrorResponse errResp = _jsonRpcService.GetErrorResponse( + e.StatusCode == StatusCodes.Status413PayloadTooLarge ? ErrorCodes.LimitExceeded : ErrorCodes.InvalidRequest, + e.Message); + await _jsonSerializer.SerializeAsync(ctx.Response.BodyWriter, errResp); + await ctx.Response.CompleteAsync(); + } + finally + { + Interlocked.Add(ref Metrics.JsonRpcBytesReceivedHttp, knownContentLength ?? countingReader?.Length ?? 0); + } + } + + /// + /// Writes a JSON-RPC response with typed serialization for the result/error payload, + /// avoiding polymorphic dispatch through the JsonRpcResponse base class hierarchy. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteJsonRpcResponse(IBufferWriter writer, JsonRpcResponse response) + { + using var jsonWriter = new Utf8JsonWriter(writer, new JsonWriterOptions { SkipValidation = true }); + + jsonWriter.WriteStartObject(); + jsonWriter.WriteString("jsonrpc"u8, "2.0"u8); + + if (response is JsonRpcSuccessResponse successResponse) + { + jsonWriter.WritePropertyName("result"u8); + object? result = successResponse.Result; + if (result is not null) + { + JsonSerializer.Serialize(jsonWriter, result, result.GetType(), EthereumJsonSerializer.JsonOptions); + } + else + { + jsonWriter.WriteNullValue(); + } + } + else if (response is JsonRpcErrorResponse errorResponse) + { + jsonWriter.WritePropertyName("error"u8); + if (errorResponse.Error is not null) + { + JsonSerializer.Serialize(jsonWriter, errorResponse.Error, EthereumJsonSerializer.JsonOptions); + } + else + { + jsonWriter.WriteNullValue(); + } + } + + jsonWriter.WritePropertyName("id"u8); + WriteId(jsonWriter, response.Id); + + jsonWriter.WriteEndObject(); + } + + private static void WriteId(Utf8JsonWriter writer, object? id) + { + switch (id) + { + case int intId: + writer.WriteNumberValue(intId); + break; + case long longId: + writer.WriteNumberValue(longId); + break; + case string strId: + writer.WriteStringValue(strId); + break; + case null: + writer.WriteNullValue(); + break; + default: + WriteOther(writer, id); + break; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void WriteOther(Utf8JsonWriter writer, object? id) + { + JsonSerializer.Serialize(writer, id, id.GetType(), EthereumJsonSerializer.JsonOptions); + } + } + + private static async ValueTask WriteStreamableResponseAsync( + CountingWriter writer, JsonRpcResponse response, + IStreamableResult streamable, CancellationToken ct) + { + writer.Write("{\"jsonrpc\":\"2.0\",\"result\":"u8); + await streamable.WriteToAsync(writer, ct); + writer.Write(",\"id\":"u8); + WriteIdRaw(writer, response.Id); + writer.Write("}"u8); + } + + private static void WriteIdRaw(PipeWriter writer, object? id) + { + switch (id) + { + case int intId: + { + Span buf = writer.GetSpan(11); + intId.TryFormat(buf, out int written); + writer.Advance(written); + break; + } + case long longId: + { + Span buf = writer.GetSpan(20); + longId.TryFormat(buf, out int written); + writer.Advance(written); + break; + } + default: + WriteOther(writer, id); + break; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void WriteOther(PipeWriter writer, object? id) + { + switch (id) + { + case string strId: + { + // JSON-RPC IDs are simple values (typically numeric); no escaping needed + Span buf = writer.GetSpan(strId.Length * 3 + 2); + buf[0] = (byte)'"'; + int len = Encoding.UTF8.GetBytes(strId, buf[1..]); + buf[len + 1] = (byte)'"'; + writer.Advance(len + 2); + break; + } + default: + writer.Write("null"u8); + break; + } + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void LogBadRequest(ILogger logger, Exception e) => + logger.Debug($"Couldn't read request.{Environment.NewLine}{e}"); + private sealed class CountingPipeReader(PipeReader stream) : PipeReader { private ReadOnlySequence _currentSequence; diff --git a/src/Nethermind/Nethermind.Runner/Program.cs b/src/Nethermind/Nethermind.Runner/Program.cs index 13512037d052..2440b46e7819 100644 --- a/src/Nethermind/Nethermind.Runner/Program.cs +++ b/src/Nethermind/Nethermind.Runner/Program.cs @@ -52,6 +52,13 @@ ILogger logger = new(SimpleConsoleLogger.Instance); ProcessExitSource? processExitSource = default; var unhandledError = "A critical error has occurred"; +Option[] deprecatedOptions = +[ + BasicOptions.ConfigurationDirectory, + BasicOptions.DatabasePath, + BasicOptions.LoggerConfigurationSource, + BasicOptions.PluginsDirectory +]; AppDomain.CurrentDomain.UnhandledException += (sender, e) => { @@ -230,9 +237,6 @@ static Option CreateOption(Type configType, string name, string? alias) foreach (Type configType in configTypes.Where(ct => !ct.IsAssignableTo(typeof(INoCategoryConfig))).OrderBy(c => c.Name)) { - if (configType is null) - continue; - ConfigCategoryAttribute? typeLevel = configType.GetCustomAttribute(); if (typeLevel is not null && typeLevel.DisabledForCli) @@ -277,14 +281,6 @@ static Option CreateOption(Type configType, string name, string? alias) void CheckForDeprecatedOptions(ParseResult parseResult) { - Option[] deprecatedOptions = - [ - BasicOptions.ConfigurationDirectory, - BasicOptions.DatabasePath, - BasicOptions.LoggerConfigurationSource, - BasicOptions.PluginsDirectory - ]; - foreach (Token token in parseResult.Tokens) { foreach (Option option in deprecatedOptions) diff --git a/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json index a2020a9c637c..932eb67f0559 100644 --- a/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 41550000, - "PivotHash": "0xf7d2cc48b2f2f3e785df037395a729aa6ae08640ee024826aa0d7938f56944e8" + "PivotNumber": 42150000, + "PivotHash": "0x54691ca9f732f6b3a2fd2b85ecf7359744c34bbc9ca08c619fdebe857196fb4f" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/configs/base-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/base-sepolia.json index bfec9bf16048..571e90d9457b 100644 --- a/src/Nethermind/Nethermind.Runner/configs/base-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/base-sepolia.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 37060000, - "PivotHash": "0x3f02d8a236bc757b2ae605078a8881dd5b208794fc1ad2e31501498ea54a062b" + "PivotNumber": 37660000, + "PivotHash": "0x1c23e29f8f4e5e3cc356bfeb79f01ceb5d9cf0026d39667f66bb1032c655ca9d" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/configs/chiado.json b/src/Nethermind/Nethermind.Runner/configs/chiado.json index 2453ce6ca660..7ef8547654c1 100644 --- a/src/Nethermind/Nethermind.Runner/configs/chiado.json +++ b/src/Nethermind/Nethermind.Runner/configs/chiado.json @@ -18,8 +18,8 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 19870000, - "PivotHash": "0x21d17776c1d770325eefc8df0753a91236886fef0774be51fccc4c1ed85d8a83", + "PivotNumber": 19780000, + "PivotHash": "0xac62a1145915936b74ef989b8ef0110be4542283e904fef2826726866fc6725e", "PivotTotalDifficulty": "231708131825107706987652208063906496124457284", "FastSyncCatchUpHeightDelta": 10000000000, "UseGethLimitsInFastBlocks": false diff --git a/src/Nethermind/Nethermind.Runner/configs/gnosis.json b/src/Nethermind/Nethermind.Runner/configs/gnosis.json index 93071d8dcba5..10ddaf8c213a 100644 --- a/src/Nethermind/Nethermind.Runner/configs/gnosis.json +++ b/src/Nethermind/Nethermind.Runner/configs/gnosis.json @@ -14,8 +14,8 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 44430000, - "PivotHash": "0x4975eece3dea5d0f47b308c557d0a5473328f55276e893ece40d7956d5b7bc58", + "PivotNumber": 44670000, + "PivotHash": "0x229a26df1c0b804793d81de764b4bcd9de5e810174f4e21ddc787d650235cc16", "PivotTotalDifficulty": "8626000110427538733349499292577475819600160930", "UseGethLimitsInFastBlocks": false, "FastSyncCatchUpHeightDelta": 10000000000, diff --git a/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json index 0fed00a3dfc4..bbfcd15344a9 100644 --- a/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json @@ -12,9 +12,9 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 22050000, - "PivotHash": "0x14d4cb874b1cb80011554f6942f68927ec2f5927b773d0f4edda8baca912ac9e", - "PivotTotalDifficulty": "39571169" + "PivotNumber": 22290000, + "PivotHash": "0x4bba26f5d256f972384413be51b4bfb5fd6c21d192d5fb55880d7baadc682631", + "PivotTotalDifficulty": "39936580" }, "Metrics": { "NodeName": "JOC-Mainnet" diff --git a/src/Nethermind/Nethermind.Runner/configs/joc-testnet.json b/src/Nethermind/Nethermind.Runner/configs/joc-testnet.json index 2bbf52bb35cf..b638cd8b5ca9 100644 --- a/src/Nethermind/Nethermind.Runner/configs/joc-testnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/joc-testnet.json @@ -12,9 +12,9 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 15650000, - "PivotHash": "0x482f1c47d98099e75cbf9ed03411f9ba1dd935962477cfc8276e40f5a03ec9b9", - "PivotTotalDifficulty": "26000126" + "PivotNumber": 15900000, + "PivotHash": "0xc545fbce23bbd9e6d756210003ef4b3c5021f84763b82cb33ba3f739cbe796fc", + "PivotTotalDifficulty": "26391193" }, "Metrics": { "NodeName": "JOC-Testnet" diff --git a/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json index 0c2559954cb9..f461ff99a533 100644 --- a/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json @@ -17,9 +17,9 @@ }, "Sync": { "SnapSync": true, - "PivotNumber": 28500000, - "PivotHash": "0xc59f58328c02099e9114d69187be7cbe373ea0f6c489c16e8064c448e013aed3", - "PivotTotalDifficulty": "0", + "PivotNumber": 28870000, + "PivotHash": "0x9d00c48eaf9e84f48936baeea8aba0866422524641546af30c9bb4afa256c367", + "PivotTotalDifficulty": "49575263", "HeaderStateDistance": 6 }, "JsonRpc": { diff --git a/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json index a5b145e4f66d..eb127802e2d4 100644 --- a/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json @@ -17,8 +17,8 @@ }, "Sync": { "SnapSync": true, - "PivotNumber": 24080000, - "PivotHash": "0xf11dd5fdf30d5f9d2e3a2ffae4da3b3353aa3576619d44a8457f667d1c72256d", + "PivotNumber": 24850000, + "PivotHash": "0xd5816d5b0b0024581511a44f029b9df64940b3d20ebff9cd8b2790fab67e4950", "PivotTotalDifficulty": "37331807", "HeaderStateDistance": 6 }, diff --git a/src/Nethermind/Nethermind.Runner/configs/mainnet.json b/src/Nethermind/Nethermind.Runner/configs/mainnet.json index 06c341f13328..9063a4642f8f 100644 --- a/src/Nethermind/Nethermind.Runner/configs/mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/mainnet.json @@ -10,8 +10,8 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 24357000, - "PivotHash": "0x46ee912a15df3d0642723f8f608386c560a6312ebea4a372f2f66e71fd8761bc", + "PivotNumber": 24457000, + "PivotHash": "0x1b315b4d85f8c804569022f1e0a021def8c2029ac4b55edeea03e2cf7c1f936e", "PivotTotalDifficulty": "58750003716598352816469", "FastSyncCatchUpHeightDelta": 10000000000, "AncientReceiptsBarrier": 15537394, diff --git a/src/Nethermind/Nethermind.Runner/configs/op-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/op-mainnet.json index 179ce25f29e7..8e20ed622711 100644 --- a/src/Nethermind/Nethermind.Runner/configs/op-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/op-mainnet.json @@ -15,8 +15,8 @@ "FastSyncCatchUpHeightDelta": "10000000000", "AncientBodiesBarrier": 105235063, "AncientReceiptsBarrier": 105235063, - "PivotNumber": 147140000, - "PivotHash": "0x41178d97cb45fa28393d94861e9ce65622bb2289a0e0d0e82d866fc5cda68f5e" + "PivotNumber": 147750000, + "PivotHash": "0xc5ca711298ccd110764eff891270897df703ba081239562286b263fbc046755e" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/configs/op-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/op-sepolia.json index e014551b66f5..6d79b23d20ef 100644 --- a/src/Nethermind/Nethermind.Runner/configs/op-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/op-sepolia.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 39040000, - "PivotHash": "0x638722bcdbef6103c08709eae7828dada842a5f25620ccc668aec1aef1e55406" + "PivotNumber": 39640000, + "PivotHash": "0x60392267e63a958936e102312fb81a90f023c23b372279e1b25112bdf92ea42c" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/configs/sepolia.json b/src/Nethermind/Nethermind.Runner/configs/sepolia.json index 06111c6d309a..00765e81652d 100644 --- a/src/Nethermind/Nethermind.Runner/configs/sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/sepolia.json @@ -17,8 +17,8 @@ "FastSync": true, "SnapSync": true, "UseGethLimitsInFastBlocks": true, - "PivotNumber": 10164000, - "PivotHash": "0x85a3f029e6d9d13080ef0aec663c5a457121ecc574cc5d322ab4aed2bf7c6a13", + "PivotNumber": 10261000, + "PivotHash": "0x4afc23c05b485ec4a9cdf17eb9d53e847d24d4ad580287a3db80ac5b06d5f241", "PivotTotalDifficulty": "17000018015853232", "FastSyncCatchUpHeightDelta": 10000000000, "AncientReceiptsBarrier": 1450409, diff --git a/src/Nethermind/Nethermind.Runner/configs/spaceneth.json b/src/Nethermind/Nethermind.Runner/configs/spaceneth.json index 1fa31a55f406..a9d0deccc1fe 100644 --- a/src/Nethermind/Nethermind.Runner/configs/spaceneth.json +++ b/src/Nethermind/Nethermind.Runner/configs/spaceneth.json @@ -46,7 +46,6 @@ "Subscribe", "Trace", "TxPool", - "Vault", "Web3" ] }, diff --git a/src/Nethermind/Nethermind.Runner/configs/spaceneth_persistent.json b/src/Nethermind/Nethermind.Runner/configs/spaceneth_persistent.json index 688f9c6f9567..efbc825140ea 100644 --- a/src/Nethermind/Nethermind.Runner/configs/spaceneth_persistent.json +++ b/src/Nethermind/Nethermind.Runner/configs/spaceneth_persistent.json @@ -41,7 +41,6 @@ "Subscribe", "Trace", "TxPool", - "Vault", "Web3" ] }, diff --git a/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json index 415192600e02..ac4884475eee 100644 --- a/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 25270000, - "PivotHash": "0x78183e57fb644b981fa8495ce86f7683bf4fbc3d3ff935d022795d809ec0e229" + "PivotNumber": 25880000, + "PivotHash": "0x8fa42f29309ccc1455c2d5af57f22a9941dcb13d7db23cc49508977bfb922bb6" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/configs/worldchain-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/worldchain-sepolia.json index eaa2d08f3f64..e2efd7ba62d3 100644 --- a/src/Nethermind/Nethermind.Runner/configs/worldchain-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/worldchain-sepolia.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 24670000, - "PivotHash": "0xec997d3cd44843014f21b520ed11bea72ca1d0a405a41da81b645f98a4c23c61" + "PivotNumber": 25270000, + "PivotHash": "0x4e5f1ecdcbc3931cc6dc40d3c5db56c00d08dac306caf26ccee31d7b6d0dadaf" }, "Discovery": { "DiscoveryVersion": "V5" diff --git a/src/Nethermind/Nethermind.Runner/packages.lock.json b/src/Nethermind/Nethermind.Runner/packages.lock.json index 858ddab75fd0..4c14de3e87d7 100644 --- a/src/Nethermind/Nethermind.Runner/packages.lock.json +++ b/src/Nethermind/Nethermind.Runner/packages.lock.json @@ -13,21 +13,21 @@ }, "Microsoft.Build.Tasks.Git": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + "requested": "[10.0.103, )", + "resolved": "10.0.103", + "contentHash": "QoiCMcPuxC6eqRQmrmF9zBY96ejIznXtve/lJJbonGD9I5Aygf2AUCOWslGiCEtBbfWRSuUnepBjuuVOdAl5ag==" }, "Microsoft.Extensions.FileProviders.Embedded": { "type": "Direct", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "PkrDqw6uwm4Y7IucI3PjqwhCeCoHno3hzOeEt0hUrFI+ccYBC0X3NfQbhkFG46TgclnCyUDStmgJzpie9T9ZlQ==" + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "kw/xPl7m4Gv6bqx2ojihTtWiN2K2AklyMIrvncuSi2MOdwu0oMKoyh0G3p2Brt7m43Q9ER0IaA2G4EGjfgDh/w==" }, "Microsoft.VisualStudio.Azure.Containers.Tools.Targets": { "type": "Direct", - "requested": "[1.22.1, )", - "resolved": "1.22.1", - "contentHash": "EfYANhAWqmWKoLwN6bxoiPZSOfJSO9lzX+UrU6GVhLhPub1Hd+5f0zL0/tggIA6mRz6Ebw2xCNcIsM4k+7NPng==" + "requested": "[1.23.0, )", + "resolved": "1.23.0", + "contentHash": "2wDnb4umupJZ/1ikgWozFVpggH1mlHQFc0odXVv2ZagL3RYwXgW9zmC15fiqIBzmaC0vLZUnLGwDY+p8ZR7Syw==" }, "NLog.Targets.Seq": { "type": "Direct", @@ -40,15 +40,15 @@ }, "Pyroscope": { "type": "Direct", - "requested": "[0.13.0, )", - "resolved": "0.13.0", - "contentHash": "rdthieTs1xwkAl3z9eePA3kpQM+xRCqhiqupyXt15emyU5wVp+X5ur29W//fDmaJx4Rm2OH9xcLgJqacdtOMKg==" + "requested": "[0.14.1, )", + "resolved": "0.14.1", + "contentHash": "i8BoY82ZBrBOmgal7Zbf69CzQ2tRJkV0v7se1yB54rsOuuxFIzcUMplyD0VIg6Gl+EkVORNpm5OUL5tJGhR+lg==" }, "System.CommandLine": { "type": "Direct", - "requested": "[2.0.1, )", - "resolved": "2.0.1", - "contentHash": "GLc43eDFq8KbpxIb7UhTwV0vC5CzB0NspJvfFbfhoW4O057xCJXuO18KLpVn9x3JykQn2mRske6+I6JXHwqmDg==" + "requested": "[2.0.3, )", + "resolved": "2.0.3", + "contentHash": "5nY9hlrGGFEmyecNUux58sohD2Q16U6jlFBYwH57b2IVUs+u7LfMFaHwOtw2tuIi8CUl6jKXw5s4FuIt9aLtug==" }, "AspNetCore.HealthChecks.UI.Core": { "type": "Transitive", @@ -663,8 +663,7 @@ "dependencies": { "Nethermind.Core": "[1.37.0-unstable, )", "NonBlocking": "[2.1.2, )", - "PierTwo.Lantern.Discv5.WireProtocol": "[1.0.0-preview.7, )", - "System.Configuration.ConfigurationManager": "[10.0.1, )" + "System.Configuration.ConfigurationManager": "[10.0.3, )" } }, "nethermind.consensus": { @@ -732,11 +731,11 @@ "type": "Project", "dependencies": { "BouncyCastle.Cryptography": "[2.6.2, )", - "Ckzg.Bindings": "[2.1.5.1544, )", + "Ckzg.Bindings": "[2.1.5.1551, )", "Nethermind.Core": "[1.37.0-unstable, )", "Nethermind.Crypto.Bls": "[1.0.5, )", "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", - "System.Security.Cryptography.ProtectedData": "[10.0.1, )" + "System.Security.Cryptography.ProtectedData": "[10.0.3, )" } }, "nethermind.db": { @@ -744,6 +743,7 @@ "dependencies": { "Nethermind.Config": "[1.37.0-unstable, )", "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", + "Nethermind.TurboPForBindings": "[1.0.0, )", "NonBlocking": "[2.1.2, )" } }, @@ -754,7 +754,7 @@ "Nethermind.Api": "[1.37.0-unstable, )", "Nethermind.Db": "[1.37.0-unstable, )", "NonBlocking": "[2.1.2, )", - "RocksDB": "[10.4.2.62659, 10.4.2.62659]" + "RocksDB": "[10.4.2.63147, 10.4.2.63147]" } }, "nethermind.db.rpc": { @@ -779,7 +779,7 @@ "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", "Nethermind.Serialization.Ssz": "[1.37.0-unstable, )", "Nethermind.State": "[1.37.0-unstable, )", - "Snappier": "[1.2.0, )" + "Snappier": "[1.3.0, )" } }, "nethermind.ethstats": { @@ -813,7 +813,7 @@ "Nethermind.Crypto.SecP256r1": "[1.0.0-preview.6, )", "Nethermind.Evm": "[1.37.0-unstable, )", "Nethermind.GmpBindings": "[1.0.3, )", - "Nethermind.MclBindings": "[1.0.4, )", + "Nethermind.MclBindings": "[1.0.5, )", "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", "Nethermind.Specs": "[1.37.0-unstable, )" } @@ -844,8 +844,8 @@ "nethermind.grpc": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.33.2, )", - "Google.Protobuf.Tools": "[3.33.2, )", + "Google.Protobuf": "[3.33.5, )", + "Google.Protobuf.Tools": "[3.33.5, )", "Grpc": "[2.46.6, )", "Nethermind.Config": "[1.37.0-unstable, )", "Nethermind.Core": "[1.37.0-unstable, )", @@ -986,7 +986,7 @@ "Nethermind.Network.Contract": "[1.37.0-unstable, )", "Nethermind.Network.Stats": "[1.37.0-unstable, )", "Nethermind.Synchronization": "[1.37.0-unstable, )", - "Snappier": "[1.2.0, )" + "Snappier": "[1.3.0, )" } }, "nethermind.network.contract": { @@ -1033,7 +1033,7 @@ "nethermind.optimism": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.33.2, )", + "Google.Protobuf": "[3.33.5, )", "Nethermind.Api": "[1.37.0-unstable, )", "Nethermind.Blockchain": "[1.37.0-unstable, )", "Nethermind.Consensus": "[1.37.0-unstable, )", @@ -1043,7 +1043,7 @@ "Nethermind.Libp2p": "[1.0.0-preview.45, )", "Nethermind.Libp2p.Protocols.PubsubPeerDiscovery": "[1.0.0-preview.45, )", "Nethermind.Merge.Plugin": "[1.37.0-unstable, )", - "Snappier": "[1.2.0, )" + "Snappier": "[1.3.0, )" } }, "nethermind.seq": { @@ -1075,7 +1075,7 @@ "nethermind.shutter": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.33.2, )", + "Google.Protobuf": "[3.33.5, )", "Nethermind.Blockchain": "[1.37.0-unstable, )", "Nethermind.Consensus": "[1.37.0-unstable, )", "Nethermind.Core": "[1.37.0-unstable, )", @@ -1104,7 +1104,7 @@ "Nethermind.Config": "[1.37.0-unstable, )", "Nethermind.Core": "[1.37.0-unstable, )", "Nethermind.Serialization.Json": "[1.37.0-unstable, )", - "ZstdSharp.Port": "[0.8.6, )" + "ZstdSharp.Port": "[0.8.7, )" } }, "nethermind.state": { @@ -1237,9 +1237,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1544, )", - "resolved": "2.1.5.1544", - "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" }, "CommunityToolkit.HighPerformance": { "type": "CentralTransitive", @@ -1280,15 +1280,15 @@ }, "Google.Protobuf": { "type": "CentralTransitive", - "requested": "[3.33.2, )", - "resolved": "3.33.2", - "contentHash": "vZXVbrZgBqUkP5iWQi0CS6pucIS2MQdEYPS1duWCo8fGrrt4th6HTiHfLFX2RmAWAQl1oUnzGgyDBsfq7fHQJA==" + "requested": "[3.33.5, )", + "resolved": "3.33.5", + "contentHash": "XEzLpCTosZb5I6eGSPn7rAES0VfkJkn3Cqydh0W39POdZwkdhPhOmAROTFJF9g0ardst4ulNXRm/q/iXwNu+Qw==" }, "Google.Protobuf.Tools": { "type": "CentralTransitive", - "requested": "[3.33.2, )", - "resolved": "3.33.2", - "contentHash": "3YFiSs39mhBiAfeQ9u27JniqVNunVrYomNnSb8Rx6D3dJqC9Uwdpm5Xu2f2ZOGvUzkB114NAvU44KySOplgCzw==" + "requested": "[3.33.5, )", + "resolved": "3.33.5", + "contentHash": "A1UnkTCZvOsIW1+S8papxEx0CxrcIZlxEC+audWbvovU7cZAaF6Rfb2yJgPsTkzqU+dpyBpHE5v1tLPQ+NF4rQ==" }, "Grpc": { "type": "CentralTransitive", @@ -1478,9 +1478,9 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.4, )", - "resolved": "1.0.4", - "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" }, "Nethermind.Numerics.Int256": { "type": "CentralTransitive", @@ -1488,6 +1488,12 @@ "resolved": "1.4.0", "contentHash": "w8HRMsdpX9fG9kcELJeJPEKIZgOUTCe47ebtejCvfBYQVlabA9blqba6QWIt5oG8cRSgnVlQ24DsdGsLzqFM+Q==" }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" + }, "Nito.Collections.Deque": { "type": "CentralTransitive", "requested": "[1.2.1, )", @@ -1544,9 +1550,9 @@ }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.62659, 10.4.2.62659]", - "resolved": "10.4.2.62659", - "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" }, "SCrypt": { "type": "CentralTransitive", @@ -1556,24 +1562,24 @@ }, "Snappier": { "type": "CentralTransitive", - "requested": "[1.2.0, )", - "resolved": "1.2.0", - "contentHash": "Lv83i7hQZbl+r0qkO6VrBZ0OHL/R/onAVcCcxgYpT8inhqJ2/f1qkIWT3gWwdcCz4cPHOQrS0uX40cFUQyOS5Q==" + "requested": "[1.3.0, )", + "resolved": "1.3.0", + "contentHash": "yYANMXm5MUiF9jzsOI7WH5Cj1HYzcWEIEVI2Ljq8N7hS/zwLCBx+GoGeyc7aJFAvpRcbSIbdNaelVomHqd6UDQ==" }, "System.Configuration.ConfigurationManager": { "type": "CentralTransitive", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "HfOAIlSA8OuaxBZD6xjsUWhtB0KdKSWEfRId8gSGveLUjuP6G8IxfiFgJNxaiRIEC1kx4pSvz3Em5xW/J6LLxA==", + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "69ZT/MYxQSxwdiiHRtI08noXiG5drj/bXDDZISmeWkNUtbIfYgmTiof16tCVOLTdmSQY7W7gwxkMliKdreWHGQ==", "dependencies": { - "System.Security.Cryptography.ProtectedData": "10.0.1" + "System.Security.Cryptography.ProtectedData": "10.0.3" } }, "System.Security.Cryptography.ProtectedData": { "type": "CentralTransitive", - "requested": "[10.0.1, )", - "resolved": "10.0.1", - "contentHash": "9SqHNq+lAjZeyPcm69FTQEjr+wsRYvkS3aW8yxoEndVYwDRkCrsP/44QPqpWHwzevoX26rkOoQ6kr7GZWngw2A==" + "requested": "[10.0.3, )", + "resolved": "10.0.3", + "contentHash": "JCKbH/CN5l0CSoJBILEvJmNQVp5vV+FY3q2ue4K9p4eDT4mFEv0bjTQCV+MD6Qk1b/qk9fWmZZKhG1TklbXw1Q==" }, "TestableIO.System.IO.Abstractions.Wrappers": { "type": "CentralTransitive", @@ -1596,9 +1602,9 @@ }, "ZstdSharp.Port": { "type": "CentralTransitive", - "requested": "[0.8.6, )", - "resolved": "0.8.6", - "contentHash": "iP4jVLQoQmUjMU88g1WObiNr6YKZGvh4aOXn3yOJsHqZsflwRsxZPcIBvNXgjXO3vQKSLctXGLTpcBPLnWPS8A==" + "requested": "[0.8.7, )", + "resolved": "0.8.7", + "contentHash": "+4VpxvzEKaHpfTjsLRMhQHx6brqGBkIA+fjUM3wUW8ZoWpPFAeKrO2Nf4uZDeBjjzNDNxSRvLQH4b3S9Ku4JJQ==" } }, "net10.0/linux-arm64": { @@ -1622,9 +1628,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1544, )", - "resolved": "2.1.5.1544", - "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -1682,15 +1688,21 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.4, )", - "resolved": "1.0.4", - "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.62659, 10.4.2.62659]", - "resolved": "10.4.2.62659", - "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" } }, "net10.0/linux-x64": { @@ -1714,9 +1726,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1544, )", - "resolved": "2.1.5.1544", - "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -1774,15 +1786,21 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.4, )", - "resolved": "1.0.4", - "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.62659, 10.4.2.62659]", - "resolved": "10.4.2.62659", - "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" } }, "net10.0/osx-arm64": { @@ -1806,9 +1824,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1544, )", - "resolved": "2.1.5.1544", - "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -1866,15 +1884,21 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.4, )", - "resolved": "1.0.4", - "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.62659, 10.4.2.62659]", - "resolved": "10.4.2.62659", - "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" } }, "net10.0/osx-x64": { @@ -1898,9 +1922,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1544, )", - "resolved": "2.1.5.1544", - "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -1958,15 +1982,21 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.4, )", - "resolved": "1.0.4", - "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.62659, 10.4.2.62659]", - "resolved": "10.4.2.62659", - "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" } }, "net10.0/win-x64": { @@ -1990,9 +2020,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1544, )", - "resolved": "2.1.5.1544", - "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" + "requested": "[2.1.5.1551, )", + "resolved": "2.1.5.1551", + "contentHash": "85AS5t3IMR/3Mco1dGSdqH+AST9STLX0TYNEeWJtmswsOD/A4hkkfuJzOxe+7/PiYdK+PNC2zK3Y8AXXAzv2bA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -2050,15 +2080,21 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.4, )", - "resolved": "1.0.4", - "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" + "requested": "[1.0.5, )", + "resolved": "1.0.5", + "contentHash": "GVzsykooi0PAydDuhdWB7ugFyHPYYkwiBFGy5sVMlKeXsqBqUdvOgqnT/29bf74b3Uhm76gfC6fiW9nEW0xA3Q==" + }, + "Nethermind.TurboPForBindings": { + "type": "CentralTransitive", + "requested": "[1.0.0, )", + "resolved": "1.0.0", + "contentHash": "G5L8iaJusi7h4zkFtiSEhA8VRX7l6SsupdKRZ7QiHIzYR/860i/HS4wZ+/Uxn2fqkU4WRBuj+aGLFyX1x0VGCw==" }, "RocksDB": { "type": "CentralTransitive", - "requested": "[10.4.2.62659, 10.4.2.62659]", - "resolved": "10.4.2.62659", - "contentHash": "+ZY7koKclaRz7+3QiCbXprWK4++Cwh0Hhqj+5Z5fcZpQvoIoo+iM9iAdCo+W5ha9XOLeI0YWbi9nZt12dNVBMg==" + "requested": "[10.4.2.63147, 10.4.2.63147]", + "resolved": "10.4.2.63147", + "contentHash": "48a93ExoQReD2VRjtfIH++2A4SeCG+Xh8Z3pzXkS0H+C4FPypwsSUX3wFF1FEGKJX+pp4Q5mnSzUZ5UumXj9PA==" } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/CountingTextReader.cs b/src/Nethermind/Nethermind.Serialization.Json/CountingTextReader.cs deleted file mode 100644 index fa57f6b5a795..000000000000 --- a/src/Nethermind/Nethermind.Serialization.Json/CountingTextReader.cs +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Nethermind.Serialization.Json -{ - public class CountingTextReader : TextReader - { - private readonly TextReader _innerReader; - public int Length { get; private set; } - - public CountingTextReader(TextReader innerReader) - { - _innerReader = innerReader; - } - - public override void Close() - { - base.Close(); - _innerReader.Close(); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (disposing) - { - _innerReader.Dispose(); - } - } - - public override int Peek() => _innerReader.Peek(); - - public override int Read() - { - Length++; - return _innerReader.Read(); - } - - public override int Read(char[] buffer, int index, int count) => IncrementLength(_innerReader.Read(buffer, index, count)); - - public override int Read(Span buffer) => IncrementLength(_innerReader.Read(buffer)); - - public override async Task ReadAsync(char[] buffer, int index, int count) => IncrementLength(await _innerReader.ReadAsync(buffer, index, count)); - - public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) => IncrementLength(await _innerReader.ReadAsync(buffer, cancellationToken)); - - public override int ReadBlock(char[] buffer, int index, int count) => IncrementLength(_innerReader.ReadBlock(buffer, index, count)); - - public override int ReadBlock(Span buffer) => IncrementLength(_innerReader.ReadBlock(buffer)); - - public override string ReadLine() => IncrementLength(_innerReader.ReadLine()); - - public override async ValueTask ReadBlockAsync(Memory buffer, CancellationToken cancellationToken = default) => IncrementLength(await _innerReader.ReadBlockAsync(buffer, cancellationToken)); - - public override async Task ReadBlockAsync(char[] buffer, int index, int count) => IncrementLength(await _innerReader.ReadBlockAsync(buffer, index, count)); - - public override async Task ReadLineAsync() => IncrementLength(await _innerReader.ReadLineAsync()); - - public override string ReadToEnd() => IncrementLength(_innerReader.ReadToEnd()); - - public override async Task ReadToEndAsync() => IncrementLength(await _innerReader.ReadToEndAsync()); - - private string IncrementLength(in string read) - { - if (!string.IsNullOrEmpty(read)) - { - Length += read.Length; - } - return read; - } - - private int IncrementLength(in int read) - { - Length += read; - return read; - } - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Json/CountingTextWriter.cs b/src/Nethermind/Nethermind.Serialization.Json/CountingTextWriter.cs deleted file mode 100644 index 16beb11721d9..000000000000 --- a/src/Nethermind/Nethermind.Serialization.Json/CountingTextWriter.cs +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.IO; -using System.Text; - -namespace Nethermind.Serialization.Json -{ - public class CountingTextWriter : TextWriter - { - private readonly TextWriter _textWriter; - - public long Size { get; private set; } - - public CountingTextWriter(TextWriter textWriter) - { - _textWriter = textWriter ?? throw new ArgumentNullException(nameof(textWriter)); - } - - public override Encoding Encoding => _textWriter.Encoding; - - public override void Write(char value) - { - _textWriter.Write(value); - Size++; - } - - public override void Flush() - { - _textWriter.Flush(); - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - _textWriter.Dispose(); - } - } - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs b/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs index 173e83017d5d..7ce9dc3bb296 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs @@ -9,57 +9,74 @@ using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using System.Threading; using System.Threading.Tasks; using Nethermind.Core.Collections; namespace Nethermind.Serialization.Json { - public class EthereumJsonSerializer : IJsonSerializer + public sealed class EthereumJsonSerializer : IJsonSerializer { public const int DefaultMaxDepth = 128; + private static readonly object _globalOptionsLock = new(); + + private static readonly List _additionalConverters = new(); + private static readonly List _additionalResolvers = new(); + private static bool _strictHexFormat; + private static int _optionsVersion; + private readonly int? _maxDepth; - private readonly JsonSerializerOptions _jsonOptions; + private readonly JsonConverter[] _instanceConverters; + private readonly object _instanceOptionsLock = new(); + + private JsonSerializerOptions _jsonOptions = null!; + private JsonSerializerOptions _jsonOptionsIndented = null!; + private int _instanceOptionsVersion; public EthereumJsonSerializer(IEnumerable converters, int maxDepth = DefaultMaxDepth) { _maxDepth = maxDepth; - _jsonOptions = CreateOptions(indented: false, maxDepth: maxDepth, converters: converters); + _instanceConverters = CopyConverters(converters); + RefreshInstanceOptions(); } public EthereumJsonSerializer(int maxDepth = DefaultMaxDepth) { _maxDepth = maxDepth; - _jsonOptions = maxDepth != DefaultMaxDepth ? CreateOptions(indented: false, maxDepth: maxDepth) : JsonOptions; + _instanceConverters = []; + RefreshInstanceOptions(); } public object Deserialize(string json, Type type) { - return JsonSerializer.Deserialize(json, type, _jsonOptions); + return JsonSerializer.Deserialize(json, type, GetSerializerOptions(indented: false)); } public T Deserialize(Stream stream) { - return JsonSerializer.Deserialize(stream, _jsonOptions); + return JsonSerializer.Deserialize(stream, GetSerializerOptions(indented: false)); } public T Deserialize(string json) { - return JsonSerializer.Deserialize(json, _jsonOptions); + return JsonSerializer.Deserialize(json, GetSerializerOptions(indented: false)); } public T Deserialize(ref Utf8JsonReader json) { - return JsonSerializer.Deserialize(ref json, _jsonOptions); + return JsonSerializer.Deserialize(ref json, GetSerializerOptions(indented: false)); } public string Serialize(T value, bool indented = false) { - return JsonSerializer.Serialize(value, indented ? JsonOptionsIndented : _jsonOptions); + return JsonSerializer.Serialize(value, GetSerializerOptions(indented)); } - private static JsonSerializerOptions CreateOptions(bool indented, IEnumerable converters = null, int maxDepth = DefaultMaxDepth) + private static JsonSerializerOptions CreateOptions(bool indented, IEnumerable instanceConverters = null, int maxDepth = DefaultMaxDepth) { + SnapshotGlobalOptions(out bool strictHexFormat, out JsonConverter[] additionalConverters, out IJsonTypeInfoResolver[] additionalResolvers); + var result = new JsonSerializerOptions { WriteIndented = indented, @@ -71,6 +88,7 @@ private static JsonSerializerOptions CreateOptions(bool indented, IEnumerable()); + result.Converters.AddRange(additionalConverters); + result.Converters.AddRange(instanceConverters ?? Array.Empty()); return result; } - private static readonly List _additionalConverters = new(); public static void AddConverter(JsonConverter converter) { - _additionalConverters.Add(converter); + ArgumentNullException.ThrowIfNull(converter); + lock (_globalOptionsLock) + { + _additionalConverters.Add(converter); + RefreshGlobalOptionsNoLock(); + } + } - JsonOptions = CreateOptions(indented: false); - JsonOptionsIndented = CreateOptions(indented: true); + public static void AddTypeInfoResolver(IJsonTypeInfoResolver resolver) + { + ArgumentNullException.ThrowIfNull(resolver); + lock (_globalOptionsLock) + { + for (int i = 0; i < _additionalResolvers.Count; i++) + { + if (ReferenceEquals(_additionalResolvers[i], resolver)) + { + return; + } + } + + _additionalResolvers.Add(resolver); + RefreshGlobalOptionsNoLock(); + } } - private static bool _strictHexFormat; public static bool StrictHexFormat { get => _strictHexFormat; set { - if (_strictHexFormat == value) - return; - _strictHexFormat = value; - JsonOptions = CreateOptions(indented: false); - JsonOptionsIndented = CreateOptions(indented: true); + lock (_globalOptionsLock) + { + if (_strictHexFormat == value) + return; + + _strictHexFormat = value; + RefreshGlobalOptionsNoLock(); + } } } @@ -132,8 +171,8 @@ public static bool StrictHexFormat public static JsonSerializerOptions JsonOptionsIndented { get; private set; } = CreateOptions(indented: true); - private static readonly StreamPipeWriterOptions optionsLeaveOpen = new(pool: MemoryPool.Shared, minimumBufferSize: 4096, leaveOpen: true); - private static readonly StreamPipeWriterOptions options = new(pool: MemoryPool.Shared, minimumBufferSize: 4096, leaveOpen: false); + private static readonly StreamPipeWriterOptions optionsLeaveOpen = new(pool: MemoryPool.Shared, minimumBufferSize: 16384, leaveOpen: true); + private static readonly StreamPipeWriterOptions options = new(pool: MemoryPool.Shared, minimumBufferSize: 16384, leaveOpen: false); private static CountingStreamPipeWriter GetPipeWriter(Stream stream, bool leaveOpen) { @@ -144,7 +183,7 @@ public long Serialize(Stream stream, T value, bool indented = false, bool lea { var countingWriter = GetPipeWriter(stream, leaveOpen); using var writer = new Utf8JsonWriter(countingWriter, CreateWriterOptions(indented)); - JsonSerializer.Serialize(writer, value, indented ? JsonOptionsIndented : _jsonOptions); + JsonSerializer.Serialize(writer, value, GetSerializerOptions(indented)); countingWriter.Complete(); long outputCount = countingWriter.WrittenCount; @@ -161,7 +200,7 @@ private JsonWriterOptions CreateWriterOptions(bool indented) public async ValueTask SerializeAsync(Stream stream, T value, CancellationToken cancellationToken, bool indented = false, bool leaveOpen = true) { var writer = GetPipeWriter(stream, leaveOpen); - await JsonSerializer.SerializeAsync(writer, value, indented ? JsonOptionsIndented : _jsonOptions, cancellationToken); + await JsonSerializer.SerializeAsync(writer, value, GetSerializerOptions(indented), cancellationToken); await writer.CompleteAsync(); long outputCount = writer.WrittenCount; @@ -169,12 +208,121 @@ public async ValueTask SerializeAsync(Stream stream, T value, Cancellat } public Task SerializeAsync(PipeWriter writer, T value, bool indented = false) - => JsonSerializer.SerializeAsync(writer, value, indented ? JsonOptionsIndented : _jsonOptions); + { + using var jsonWriter = new Utf8JsonWriter((IBufferWriter)writer, CreateWriterOptions(indented)); + JsonSerializer.Serialize(jsonWriter, value, GetSerializerOptions(indented)); + return Task.CompletedTask; + } + + /// + /// Pre-serializes instances to warm System.Text.Json metadata caches at startup. + /// + public static void WarmupSerializer(params object[] instances) + { + foreach (object instance in instances) + { + _ = JsonSerializer.SerializeToUtf8Bytes(instance, instance.GetType(), JsonOptions); + } + } public static void SerializeToStream(Stream stream, T value, bool indented = false) { JsonSerializer.Serialize(stream, value, indented ? JsonOptionsIndented : JsonOptions); } + + private JsonSerializerOptions GetSerializerOptions(bool indented) + { + EnsureInstanceOptionsCurrent(); + return indented ? _jsonOptionsIndented : _jsonOptions; + } + + private void EnsureInstanceOptionsCurrent() + { + int currentVersion = Volatile.Read(ref _optionsVersion); + if (_instanceOptionsVersion == currentVersion) + { + return; + } + + lock (_instanceOptionsLock) + { + if (_instanceOptionsVersion != currentVersion) + { + RefreshInstanceOptions(); + } + } + } + + private void RefreshInstanceOptions() + { + _jsonOptions = CreateOptions(indented: false, instanceConverters: _instanceConverters, maxDepth: _maxDepth ?? DefaultMaxDepth); + _jsonOptionsIndented = CreateOptions(indented: true, instanceConverters: _instanceConverters, maxDepth: _maxDepth ?? DefaultMaxDepth); + _instanceOptionsVersion = Volatile.Read(ref _optionsVersion); + } + + private static void RefreshGlobalOptionsNoLock() + { + JsonOptions = CreateOptions(indented: false); + JsonOptionsIndented = CreateOptions(indented: true); + Interlocked.Increment(ref _optionsVersion); + } + + private static void SnapshotGlobalOptions(out bool strictHexFormat, out JsonConverter[] additionalConverters, out IJsonTypeInfoResolver[] additionalResolvers) + { + lock (_globalOptionsLock) + { + strictHexFormat = _strictHexFormat; + additionalConverters = new JsonConverter[_additionalConverters.Count]; + for (int i = 0; i < _additionalConverters.Count; i++) + { + additionalConverters[i] = _additionalConverters[i]; + } + + additionalResolvers = new IJsonTypeInfoResolver[_additionalResolvers.Count]; + for (int i = 0; i < _additionalResolvers.Count; i++) + { + additionalResolvers[i] = _additionalResolvers[i]; + } + } + } + + private static IJsonTypeInfoResolver BuildTypeInfoResolver(IReadOnlyList additionalResolvers) + { + int additionalResolversCount = additionalResolvers.Count; + if (additionalResolversCount == 0) + { + return new DefaultJsonTypeInfoResolver(); + } + + IJsonTypeInfoResolver[] resolverChain = new IJsonTypeInfoResolver[additionalResolversCount + 1]; + for (int i = 0; i < additionalResolversCount; i++) + { + resolverChain[i] = additionalResolvers[i]; + } + + resolverChain[additionalResolversCount] = new DefaultJsonTypeInfoResolver(); + return JsonTypeInfoResolver.Combine(resolverChain); + } + + private static JsonConverter[] CopyConverters(IEnumerable converters) + { + ArgumentNullException.ThrowIfNull(converters); + + if (converters is JsonConverter[] convertersArray) + { + JsonConverter[] clone = new JsonConverter[convertersArray.Length]; + Array.Copy(convertersArray, clone, convertersArray.Length); + return clone; + } + + List list = new(); + foreach (JsonConverter converter in converters) + { + list.Add(converter); + } + + return [.. list]; + } } public static class JsonElementExtensions diff --git a/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs b/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs index 27d95de085e4..b6c0046f1b18 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ForcedNumberConversion.cs @@ -1,13 +1,37 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Runtime.CompilerServices; using System.Threading; namespace Nethermind.Serialization.Json; public static class ForcedNumberConversion { - public static readonly AsyncLocal ForcedConversion = new(); + public static readonly ThreadAwareAsyncLocal ForcedConversion = new(); - public static NumberConversion GetFinalConversion() => ForcedConversion.Value ?? NumberConversion.Hex; + [ThreadStatic] + private static NumberConversion? _threadCache; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NumberConversion GetFinalConversion() => _threadCache ?? NumberConversion.Hex; + + /// + /// Wrapper around AsyncLocal that also updates a ThreadStatic cache for fast reads. + /// + public sealed class ThreadAwareAsyncLocal + { + private readonly AsyncLocal _asyncLocal = new(); + + public NumberConversion? Value + { + get => _asyncLocal.Value; + set + { + _asyncLocal.Value = value; + _threadCache = value; + } + } + } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/Hash256Converter.cs b/src/Nethermind/Nethermind.Serialization.Json/Hash256Converter.cs index 2f74049a80ff..6ff8b3c1eb3d 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/Hash256Converter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/Hash256Converter.cs @@ -3,8 +3,10 @@ #nullable enable using System; -using System.Text.Json.Serialization; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text.Json; +using System.Text.Json.Serialization; using Nethermind.Core.Crypto; namespace Nethermind.Serialization.Json; @@ -28,12 +30,36 @@ public Hash256Converter(bool strictHexFormat = false) return bytes is null ? null : new Hash256(bytes); } + [SkipLocalsInit] public override void Write( Utf8JsonWriter writer, Hash256 keccak, JsonSerializerOptions options) { - ByteArrayConverter.Convert(writer, keccak.Bytes, skipLeadingZeros: false); + WriteHashHex(writer, in keccak.ValueHash256); + } + + /// + /// SIMD-accelerated hex encoding for 32-byte hashes. + /// Writes raw JSON (including quotes) via WriteRawValue to bypass the encoder entirely. + /// + [SkipLocalsInit] + internal static void WriteHashHex(Utf8JsonWriter writer, in ValueHash256 hash) + { + // Raw JSON: '"' + "0x" + 64 hex chars + '"' = 68 bytes + Unsafe.SkipInit(out HexWriter.HexBuffer72 rawBuf); + ref byte b = ref Unsafe.As(ref rawBuf); + + Unsafe.Add(ref b, 0) = (byte)'"'; + Unsafe.WriteUnaligned(ref Unsafe.Add(ref b, 1), (ushort)0x7830); // "0x" LE + + HexWriter.Encode32Bytes(ref Unsafe.Add(ref b, 3), hash.Bytes); + + Unsafe.Add(ref b, 67) = (byte)'"'; + + writer.WriteRawValue( + MemoryMarshal.CreateReadOnlySpan(ref b, 68), + skipInputValidation: true); } // Methods needed to ser/de dictionary keys diff --git a/src/Nethermind/Nethermind.Serialization.Json/HexWriter.cs b/src/Nethermind/Nethermind.Serialization.Json/HexWriter.cs new file mode 100644 index 000000000000..ef0fbd2106fc --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Json/HexWriter.cs @@ -0,0 +1,428 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers.Binary; +using System.IO.Pipelines; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using System.Text.Json; +using Nethermind.Int256; + +namespace Nethermind.Serialization.Json; + +/// +/// Shared low-level hex encoding primitives used by JSON converters. +/// +public static class HexWriter +{ + /// + /// Encode the low 8 bytes of a Vector128 to 16 hex chars using SSSE3 PSHUFB. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Ssse3Encode8Bytes(ref byte dest, Vector128 input) + { + Vector128 hexLookup = Vector128.Create( + (byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', + (byte)'c', (byte)'d', (byte)'e', (byte)'f'); + Vector128 mask = Vector128.Create((byte)0x0F); + + Vector128 hi = Sse2.ShiftRightLogical(input.AsUInt16(), 4).AsByte() & mask; + Vector128 lo = input & mask; + Ssse3.Shuffle(hexLookup, Sse2.UnpackLow(hi, lo)).StoreUnsafe(ref dest); + } + + /// + /// Encode 16 bytes of a Vector128 to 32 hex chars using SSSE3 PSHUFB. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Ssse3Encode16Bytes(ref byte dest, Vector128 input) + { + Vector128 hexLookup = Vector128.Create( + (byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', + (byte)'c', (byte)'d', (byte)'e', (byte)'f'); + Vector128 mask = Vector128.Create((byte)0x0F); + + Vector128 hi = Sse2.ShiftRightLogical(input.AsUInt16(), 4).AsByte() & mask; + Vector128 lo = input & mask; + Ssse3.Shuffle(hexLookup, Sse2.UnpackLow(hi, lo)).StoreUnsafe(ref dest); + Ssse3.Shuffle(hexLookup, Sse2.UnpackHigh(hi, lo)).StoreUnsafe(ref Unsafe.Add(ref dest, 16)); + } + + /// + /// Encode 32 bytes to 64 hex chars using AVX-512 VBMI cross-lane byte permutation. + /// vpermi2b does arbitrary byte interleave across the full 256-bit register in a single + /// instruction, eliminating the UnpackLow/UnpackHigh + lane-crossing overhead of SSSE3/AVX2. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Avx512VbmiEncode32Bytes(ref byte dest, Vector256 input) + { + Vector256 mask = Vector256.Create((byte)0x0F); + Vector256 hi = Avx2.ShiftRightLogical(input.AsUInt16(), 4).AsByte() & mask; + Vector256 lo = input & mask; + + // vpermi2b: pick hi[i], lo[i] pairs across full 256-bit width + // indices 0-31 select from hi, 32-63 select from lo + Vector256 interleaved0 = Avx512Vbmi.VL.PermuteVar32x8x2(hi, + Vector256.Create( + (byte)0, 32, 1, 33, 2, 34, 3, 35, 4, 36, 5, 37, 6, 38, 7, 39, + 8, 40, 9, 41, 10, 42, 11, 43, 12, 44, 13, 45, 14, 46, 15, 47), lo); + + Vector256 interleaved1 = Avx512Vbmi.VL.PermuteVar32x8x2(hi, + Vector256.Create( + (byte)16, 48, 17, 49, 18, 50, 19, 51, 20, 52, 21, 53, 22, 54, 23, 55, + 24, 56, 25, 57, 26, 58, 27, 59, 28, 60, 29, 61, 30, 62, 31, 63), lo); + + // vpshufb: nibble-to-hex lookup (works within 128-bit lanes, lookup replicated in both) + Vector256 hexLookup = Vector256.Create( + (byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', + (byte)'c', (byte)'d', (byte)'e', (byte)'f', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', + (byte)'c', (byte)'d', (byte)'e', (byte)'f'); + + Avx2.Shuffle(hexLookup, interleaved0).StoreUnsafe(ref dest); + Avx2.Shuffle(hexLookup, interleaved1).StoreUnsafe(ref Unsafe.Add(ref dest, 32)); + } + + /// + /// 512-byte lookup table: for byte value i, HexByteLookup[i*2] and [i*2+1] are the + /// two lowercase hex ASCII chars. Single indexed load + 16-bit store per byte, + /// replacing ~10 ALU ops of a branchless arithmetic approach. + /// + private static ReadOnlySpan HexByteLookup => + "000102030405060708090a0b0c0d0e0f"u8 + + "101112131415161718191a1b1c1d1e1f"u8 + + "202122232425262728292a2b2c2d2e2f"u8 + + "303132333435363738393a3b3c3d3e3f"u8 + + "404142434445464748494a4b4c4d4e4f"u8 + + "505152535455565758595a5b5c5d5e5f"u8 + + "606162636465666768696a6b6c6d6e6f"u8 + + "707172737475767778797a7b7c7d7e7f"u8 + + "808182838485868788898a8b8c8d8e8f"u8 + + "909192939495969798999a9b9c9d9e9f"u8 + + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"u8 + + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"u8 + + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"u8 + + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"u8 + + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"u8 + + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"u8; + + /// + /// Scalar: encode one byte to 2 hex chars via lookup table. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void EncodeByte(ref byte dest, int byteVal) + { + Unsafe.WriteUnaligned(ref dest, + Unsafe.ReadUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(HexByteLookup), byteVal * 2))); + } + + /// + /// Scalar: encode a ulong (big-endian byte order) to 16 hex chars. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void EncodeUlongScalar(ref byte dest, ulong value) + { + ref byte lookup = ref MemoryMarshal.GetReference(HexByteLookup); + for (int i = 0; i < 8; i++) + { + int byteVal = (int)(value >> ((7 - i) << 3)) & 0xFF; + Unsafe.WriteUnaligned(ref Unsafe.Add(ref dest, i * 2), + Unsafe.ReadUnaligned(ref Unsafe.Add(ref lookup, byteVal * 2))); + } + } + + /// + /// Scalar: encode a byte span to hex chars. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void EncodeBytesScalar(ref byte dest, ReadOnlySpan src) + { + ref byte lookup = ref MemoryMarshal.GetReference(HexByteLookup); + for (int i = 0; i < src.Length; i++) + { + Unsafe.WriteUnaligned(ref Unsafe.Add(ref dest, i * 2), + Unsafe.ReadUnaligned(ref Unsafe.Add(ref lookup, src[i] * 2))); + } + } + + /// + /// Encode a ulong to 16 hex chars, dispatching to SSSE3 or scalar. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void EncodeUlong(ref byte dest, ulong value) + { + if (Ssse3.IsSupported) + { + ulong be = BinaryPrimitives.ReverseEndianness(value); + Ssse3Encode8Bytes(ref dest, Vector128.CreateScalarUnsafe(be).AsByte()); + } + else + { + EncodeUlongScalar(ref dest, value); + } + } + + /// + /// Encode 32 bytes to 64 hex chars, dispatching to AVX-512 VBMI, SSSE3, or scalar. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Encode32Bytes(ref byte dest, ReadOnlySpan src) + { + if (Avx512Vbmi.VL.IsSupported) + { + Avx512VbmiEncode32Bytes(ref dest, Vector256.LoadUnsafe(ref MemoryMarshal.GetReference(src))); + } + else if (Ssse3.IsSupported) + { + ref byte srcRef = ref MemoryMarshal.GetReference(src); + Ssse3Encode16Bytes(ref dest, Vector128.LoadUnsafe(ref srcRef)); + Ssse3Encode16Bytes(ref Unsafe.Add(ref dest, 32), Vector128.LoadUnsafe(ref srcRef, 16)); + } + else + { + EncodeBytesScalar(ref dest, src); + } + } + + /// + /// Write a non-zero ulong as a hex JSON string value ("0x...") using WriteRawValue. + /// Used by LongConverter and ULongConverter. + /// + [SkipLocalsInit] + internal static void WriteUlongHexRawValue(Utf8JsonWriter writer, ulong value) + { + // Use InlineArray to avoid GS cookie overhead from stackalloc + Unsafe.SkipInit(out HexBuffer24 rawBuf); + ref byte b = ref Unsafe.As(ref rawBuf); + + EncodeUlong(ref Unsafe.Add(ref b, 3), value); + + // nibbleCount: ceil(significantBits / 4), guaranteed >= 1 since value != 0 + // nint keeps Unsafe.Add in 64-bit register arithmetic, avoiding movsxd + nint nibbleCount = (nint)((67 - (uint)BitOperations.LeadingZeroCount(value)) >> 2); + nint spanStart = 16 - nibbleCount; + + ref byte spanRef = ref Unsafe.Add(ref b, spanStart); + spanRef = (byte)'"'; + Unsafe.WriteUnaligned(ref Unsafe.Add(ref spanRef, 1), (ushort)0x7830); // "0x" LE + Unsafe.Add(ref b, 19) = (byte)'"'; + + writer.WriteRawValue( + MemoryMarshal.CreateReadOnlySpan(ref spanRef, (int)nibbleCount + 4), + skipInputValidation: true); + } + + /// + /// Write a UInt256 as a hex JSON string value ("0x...") using WriteRawValue. + /// + [SkipLocalsInit] + internal static void WriteUInt256HexRawValue(Utf8JsonWriter writer, UInt256 value, bool zeroPadded = false) + { + Unsafe.SkipInit(out HexBuffer72 rawBuf); + ref byte buffer = ref Unsafe.As(ref rawBuf); + + BuildUInt256Hex(ref buffer, value, includeQuotes: true, zeroPadded, out nint spanStart, out int spanLength); + + writer.WriteRawValue( + MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref buffer, spanStart), spanLength), + skipInputValidation: true); + } + + /// + /// Write a UInt256 as a hex property name ("0x..."). + /// + [SkipLocalsInit] + internal static void WriteUInt256HexPropertyName(Utf8JsonWriter writer, UInt256 value, bool zeroPadded = false) + { + Unsafe.SkipInit(out HexBuffer72 rawBuf); + ref byte buffer = ref Unsafe.As(ref rawBuf); + + BuildUInt256Hex(ref buffer, value, includeQuotes: false, zeroPadded, out nint spanStart, out int spanLength); + + writer.WritePropertyName( + MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref buffer, spanStart), spanLength)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void BuildUInt256Hex(ref byte buffer, UInt256 value, bool includeQuotes, bool zeroPadded, out nint spanStart, out int spanLength) + { + nint hexOffset = includeQuotes ? 3 : 2; + EncodeUInt256Hex(ref Unsafe.Add(ref buffer, hexOffset), value); + + int nibbleCount = zeroPadded ? 64 : GetSignificantNibbleCount(value); + spanStart = zeroPadded ? 0 : 64 - nibbleCount; + ref byte spanRef = ref Unsafe.Add(ref buffer, spanStart); + + if (includeQuotes) + { + spanRef = (byte)'"'; + Unsafe.WriteUnaligned(ref Unsafe.Add(ref spanRef, 1), (ushort)0x7830); // "0x" LE + Unsafe.Add(ref spanRef, nibbleCount + 3) = (byte)'"'; + spanLength = nibbleCount + 4; + } + else + { + Unsafe.WriteUnaligned(ref spanRef, (ushort)0x7830); // "0x" LE + spanLength = nibbleCount + 2; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetSignificantNibbleCount(UInt256 value) + { + int leadingZeroBits; + if (value.u3 != 0) + { + leadingZeroBits = BitOperations.LeadingZeroCount(value.u3); + } + else if (value.u2 != 0) + { + leadingZeroBits = 64 + BitOperations.LeadingZeroCount(value.u2); + } + else if (value.u1 != 0) + { + leadingZeroBits = 128 + BitOperations.LeadingZeroCount(value.u1); + } + else + { + leadingZeroBits = 192 + BitOperations.LeadingZeroCount(value.u0); + } + + int nibbleCount = (259 - leadingZeroBits) >> 2; + return nibbleCount == 0 ? 1 : nibbleCount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EncodeUInt256Hex(ref byte dest, UInt256 value) + { + if (Avx512Vbmi.VL.IsSupported) + { + Vector256 reversed = Avx512Vbmi.VL.PermuteVar32x8( + Vector256.LoadUnsafe(ref Unsafe.As(ref value)), + Vector256.Create( + (byte)31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)); + Avx512VbmiEncode32Bytes(ref dest, reversed); + } + else if (Ssse3.IsSupported) + { + Ssse3Encode16Bytes(ref dest, + Vector128.Create( + BinaryPrimitives.ReverseEndianness(value.u3), + BinaryPrimitives.ReverseEndianness(value.u2)).AsByte()); + + Ssse3Encode16Bytes(ref Unsafe.Add(ref dest, 32), + Vector128.Create( + BinaryPrimitives.ReverseEndianness(value.u1), + BinaryPrimitives.ReverseEndianness(value.u0)).AsByte()); + } + else + { + EncodeUlongScalar(ref dest, value.u3); + EncodeUlongScalar(ref Unsafe.Add(ref dest, 16), value.u2); + EncodeUlongScalar(ref Unsafe.Add(ref dest, 32), value.u1); + EncodeUlongScalar(ref Unsafe.Add(ref dest, 48), value.u0); + } + } + + /// + /// 24-byte inline buffer for ulong hex encoding (20 bytes needed, rounded up to + /// 3 x 8-byte ulong elements for alignment). Used instead of stackalloc to avoid + /// GS cookie (stack canary) overhead. The JIT inserts a cookie write in the prologue + /// and a verify + CORINFO_HELP_FAIL_FAST call in the epilogue for every stackalloc + /// buffer, adding ~35 bytes per method. Inline array structs are treated as regular + /// locals and avoid this. + /// + [InlineArray(3)] + private struct HexBuffer24 + { + private ulong _element0; + } + + /// + /// 72-byte inline buffer for hash/UInt256 hex encoding (68 bytes needed, rounded up + /// to 9 x 8-byte ulong elements for alignment). Used instead of stackalloc to avoid + /// GS cookie (stack canary) overhead. The JIT inserts a cookie write in the prologue + /// and a verify + CORINFO_HELP_FAIL_FAST call in the epilogue for every stackalloc + /// buffer, adding ~35 bytes per method. Inline array structs are treated as regular + /// locals and avoid this. + /// + [InlineArray(9)] + internal struct HexBuffer72 + { + private ulong _element0; + } + + private const int MaxHexRequest = 4096; + + /// + /// Writes a large byte array as hex directly into a + /// in chunks, bounded by the actual span size returned by GetSpan. + /// + public static void WriteHexChunked(PipeWriter writer, byte[] data) + { + ReadOnlySpan remaining = data; + while (remaining.Length > 0) + { + Span hex = writer.GetSpan(Math.Min(remaining.Length * 2, MaxHexRequest)); + int inputLen = Math.Min(remaining.Length, hex.Length / 2); + EncodeToHex(remaining[..inputLen], ref MemoryMarshal.GetReference(hex)); + writer.Advance(inputLen * 2); + + remaining = remaining[inputLen..]; + } + } + + /// + /// Writes a small byte array as hex in a single span into a . + /// + public static void WriteHexSmall(PipeWriter writer, byte[] data) + { + int hexLen = data.Length * 2; + Span hex = writer.GetSpan(hexLen); + int inputLen = Math.Min(data.Length, hex.Length / 2); + EncodeToHex(((ReadOnlySpan)data)[..inputLen], ref MemoryMarshal.GetReference(hex)); + writer.Advance(inputLen * 2); + } + + /// + /// Encode arbitrary-length bytes to hex using SIMD (AVX-512 VBMI / SSSE3) with scalar tail. + /// + private static void EncodeToHex(ReadOnlySpan src, ref byte dest) + { + int offset = 0; + + // 32-byte blocks: AVX-512 VBMI or 2x SSSE3 or scalar + while (offset + 32 <= src.Length) + { + Encode32Bytes(ref Unsafe.Add(ref dest, offset * 2), src.Slice(offset, 32)); + offset += 32; + } + + // 16-byte block via SSSE3 + if (Ssse3.IsSupported && offset + 16 <= src.Length) + { + Ssse3Encode16Bytes(ref Unsafe.Add(ref dest, offset * 2), + Vector128.LoadUnsafe(ref Unsafe.Add(ref MemoryMarshal.GetReference(src), offset))); + offset += 16; + } + + // Scalar tail + if (offset < src.Length) + { + EncodeBytesScalar(ref Unsafe.Add(ref dest, offset * 2), src[offset..]); + } + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs index 7b46c9e2dc3d..a2ecd66fc590 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs @@ -2,142 +2,141 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using System.Buffers.Text; using System.Globalization; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Text.Json.Serialization; +using Nethermind.Core.Extensions; -namespace Nethermind.Serialization.Json +namespace Nethermind.Serialization.Json; + +public class LongConverter : JsonConverter { - using Nethermind.Core.Extensions; - using System.Buffers; - using System.Buffers.Binary; - using System.Buffers.Text; - using System.Runtime.CompilerServices; - using System.Text.Json; - using System.Text.Json.Serialization; - - public class LongConverter : JsonConverter + public static long FromString(string s) { - public static long FromString(string s) + if (s is null) { - if (s is null) - { - throw new JsonException("null cannot be assigned to long"); - } + throw new JsonException("null cannot be assigned to long"); + } - if (s == Bytes.ZeroHexValue) - { - return 0L; - } + if (s == Bytes.ZeroHexValue) + { + return 0L; + } - if (s.StartsWith("0x0")) - { - return long.Parse(s.AsSpan(2), NumberStyles.AllowHexSpecifier); - } + if (s.StartsWith("0x0")) + { + return long.Parse(s.AsSpan(2), NumberStyles.AllowHexSpecifier); + } - if (s.StartsWith("0x")) - { - Span withZero = new(new char[s.Length - 1]); - withZero[0] = '0'; - s.AsSpan(2).CopyTo(withZero[1..]); - return long.Parse(withZero, NumberStyles.AllowHexSpecifier); - } + if (s.StartsWith("0x")) + { + Span withZero = new(new char[s.Length - 1]); + withZero[0] = '0'; + s.AsSpan(2).CopyTo(withZero[1..]); + return long.Parse(withZero, NumberStyles.AllowHexSpecifier); + } - return long.Parse(s, NumberStyles.Integer); + return long.Parse(s, NumberStyles.Integer); + } + + public override long ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + ReadOnlySpan hex = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; + return FromString(hex); + } + + public static long FromString(ReadOnlySpan s) + { + if (s.Length == 0) + { + throw new JsonException("null cannot be assigned to long"); } - public override long ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + if (s.SequenceEqual("0x0"u8)) { - ReadOnlySpan hex = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; - return FromString(hex); + return 0L; } - public static long FromString(ReadOnlySpan s) + long value; + if (s.StartsWith("0x"u8)) { - if (s.Length == 0) + s = s[2..]; + if (Utf8Parser.TryParse(s, out value, out _, 'x')) { - throw new JsonException("null cannot be assigned to long"); + return value; } + } + else if (Utf8Parser.TryParse(s, out value, out _)) + { + return value; + } - if (s.SequenceEqual("0x0"u8)) - { - return 0L; - } + ThrowJsonException(); + return default; - long value; - if (s.StartsWith("0x"u8)) - { - s = s[2..]; - if (Utf8Parser.TryParse(s, out value, out _, 'x')) - { - return value; - } - } - else if (Utf8Parser.TryParse(s, out value, out _)) - { - return value; - } + [DoesNotReturn, StackTraceHidden] + static void ThrowJsonException() => throw new JsonException("hex to long"); + } - throw new JsonException("hex to long"); + internal static long ReadCore(ref Utf8JsonReader reader) + { + if (reader.TokenType == JsonTokenType.Number) + { + return reader.GetInt64(); } + else if (reader.TokenType == JsonTokenType.String) + { + return !reader.HasValueSequence + ? FromString(reader.ValueSpan) + : FromString(reader.ValueSequence.ToArray()); + } + + ThrowJsonException(); + return default; + + [DoesNotReturn, StackTraceHidden] + static void ThrowJsonException() => throw new JsonException(); + } + + public override long Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + return ReadCore(ref reader); + } - internal static long ReadCore(ref Utf8JsonReader reader) + [SkipLocalsInit] + public override void Write( + Utf8JsonWriter writer, + long value, + JsonSerializerOptions options) + { + switch (ForcedNumberConversion.GetFinalConversion()) { - if (reader.TokenType == JsonTokenType.Number) - { - return reader.GetInt64(); - } - else if (reader.TokenType == JsonTokenType.String) - { - if (!reader.HasValueSequence) + case NumberConversion.Hex: + if (value == 0) { - return FromString(reader.ValueSpan); + writer.WriteStringValue("0x0"u8); } else { - return FromString(reader.ValueSequence.ToArray()); + HexWriter.WriteUlongHexRawValue(writer, (ulong)value); } - } - - throw new JsonException(); - } - - public override long Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) - { - return ReadCore(ref reader); - } - - [SkipLocalsInit] - public override void Write( - Utf8JsonWriter writer, - long value, - JsonSerializerOptions options) - { - switch (ForcedNumberConversion.GetFinalConversion()) - { - case NumberConversion.Hex: - if (value == 0) - { - writer.WriteRawValue("\"0x0\""u8, skipInputValidation: true); - } - else - { - Span bytes = stackalloc byte[8]; - BinaryPrimitives.WriteInt64BigEndian(bytes, value); - ByteArrayConverter.Convert(writer, bytes, skipLeadingZeros: true); - } - break; - case NumberConversion.Decimal: - writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); - break; - case NumberConversion.Raw: - writer.WriteNumberValue(value); - break; - default: - throw new NotSupportedException(); - - } + break; + case NumberConversion.Decimal: + writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); + break; + case NumberConversion.Raw: + writer.WriteNumberValue(value); + break; + default: + throw new NotSupportedException(); } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs index 9d023cdcf66f..0ca57dd9f5c6 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/NullableULongConverter.cs @@ -2,41 +2,39 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text.Json; +using System.Text.Json.Serialization; -namespace Nethermind.Serialization.Json +namespace Nethermind.Serialization.Json; + +public class NullableULongConverter : JsonConverter { - using System.Text.Json; - using System.Text.Json.Serialization; + private readonly ULongConverter _converter = new(); - public class NullableULongConverter : JsonConverter + public override ulong? Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) { - private readonly ULongConverter _converter = new(); - - public override ulong? Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) + if (reader.TokenType == JsonTokenType.Null) { - if (reader.TokenType == JsonTokenType.Null) - { - return null; - } - - return _converter.Read(ref reader, typeToConvert, options); + return null; } - public override void Write( - Utf8JsonWriter writer, - ulong? value, - JsonSerializerOptions options) - { - if (!value.HasValue) - { - writer.WriteNullValue(); - return; - } + return _converter.Read(ref reader, typeToConvert, options); + } - _converter.Write(writer, value.GetValueOrDefault(), options); + public override void Write( + Utf8JsonWriter writer, + ulong? value, + JsonSerializerOptions options) + { + if (!value.HasValue) + { + writer.WriteNullValue(); + return; } + + _converter.Write(writer, value.GetValueOrDefault(), options); } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/PubSub/LogPublisher.cs b/src/Nethermind/Nethermind.Serialization.Json/PubSub/LogPublisher.cs deleted file mode 100644 index 75a0807cca7d..000000000000 --- a/src/Nethermind/Nethermind.Serialization.Json/PubSub/LogPublisher.cs +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Threading.Tasks; -using Nethermind.Core.PubSub; -using Nethermind.Logging; - -namespace Nethermind.Serialization.Json.PubSub -{ - public class LogPublisher : IPublisher - { - private readonly ILogger _logger; - private readonly IJsonSerializer _jsonSerializer; - - public LogPublisher(IJsonSerializer jsonSerializer, ILogManager logManager) - { - _logger = logManager.GetClassLogger(); - _jsonSerializer = jsonSerializer; - } - - public Task PublishAsync(T data) where T : class - { - if (_logger.IsInfo) _logger.Info(_jsonSerializer.Serialize(data)); - return Task.CompletedTask; - } - - public void Dispose() - { - } - } -} diff --git a/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs b/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs index 336fd38d6d69..611e64a0affc 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/UInt256Converter.cs @@ -99,28 +99,16 @@ public static UInt256 ReadHex(ReadOnlySpan hex) return new UInt256(in readOnlyBytes, isBigEndian: true); } - [SkipLocalsInit] public override void Write( Utf8JsonWriter writer, UInt256 value, JsonSerializerOptions options) { - NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); - if (value.IsZero) - { - writer.WriteRawValue(usedConversion == NumberConversion.ZeroPaddedHex - ? "\"0x0000000000000000000000000000000000000000000000000000000000000000\""u8 - : "\"0x0\""u8); - return; - } - switch (usedConversion) + NumberConversion conversion = ForcedNumberConversion.GetFinalConversion(); + switch (conversion) { case NumberConversion.Hex: - { - Span bytes = stackalloc byte[32]; - value.ToBigEndian(bytes); - ByteArrayConverter.Convert(writer, bytes); - } + HexWriter.WriteUInt256HexRawValue(writer, value); break; case NumberConversion.Decimal: writer.WriteRawValue(value.ToString(CultureInfo.InvariantCulture)); @@ -129,35 +117,23 @@ public override void Write( writer.WriteStringValue(((BigInteger)value).ToString(CultureInfo.InvariantCulture)); break; case NumberConversion.ZeroPaddedHex: - { - Span bytes = stackalloc byte[32]; - value.ToBigEndian(bytes); - ByteArrayConverter.Convert(writer, bytes, skipLeadingZeros: false); - } + HexWriter.WriteUInt256HexRawValue(writer, value, zeroPadded: true); break; default: - throw new NotSupportedException($"{usedConversion} format is not supported for {nameof(UInt256)}"); + throw new NotSupportedException($"{conversion} format is not supported for {nameof(UInt256)}"); } } public override UInt256 ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => ReadInternal(ref reader, JsonTokenType.PropertyName); - [SkipLocalsInit] public override void WriteAsPropertyName(Utf8JsonWriter writer, UInt256 value, JsonSerializerOptions options) { - NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); - if (value.IsZero) - { - writer.WritePropertyName(usedConversion == NumberConversion.ZeroPaddedHex - ? "0x0000000000000000000000000000000000000000000000000000000000000000"u8 - : "0x0"u8); - return; - } - switch (usedConversion) + NumberConversion conversion = ForcedNumberConversion.GetFinalConversion(); + switch (conversion) { case NumberConversion.Hex: - WriteHexPropertyName(writer, value, false); + HexWriter.WriteUInt256HexPropertyName(writer, value); break; case NumberConversion.Decimal: writer.WritePropertyName(value.ToString(CultureInfo.InvariantCulture)); @@ -166,25 +142,13 @@ public override void WriteAsPropertyName(Utf8JsonWriter writer, UInt256 value, J writer.WritePropertyName(((BigInteger)value).ToString(CultureInfo.InvariantCulture)); break; case NumberConversion.ZeroPaddedHex: - WriteHexPropertyName(writer, value, true); + HexWriter.WriteUInt256HexPropertyName(writer, value, zeroPadded: true); break; default: - throw new NotSupportedException($"{usedConversion} format is not supported for {nameof(UInt256)}"); + throw new NotSupportedException($"{conversion} format is not supported for {nameof(UInt256)}"); } } - private static void WriteHexPropertyName(Utf8JsonWriter writer, UInt256 value, bool isZeroPadded) - { - Span bytes = stackalloc byte[32]; - value.ToBigEndian(bytes); - ByteArrayConverter.Convert( - writer, - bytes, - static (w, h) => w.WritePropertyName(h), - skipLeadingZeros: !isZeroPadded, - addQuotations: false); - } - [DoesNotReturn, StackTraceHidden] private static void ThrowJsonException() { diff --git a/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs index e0fb0988f5e8..d9f672dedf9e 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs @@ -2,109 +2,108 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Text.Json.Serialization; -namespace Nethermind.Serialization.Json -{ - using System.Buffers; - using System.Buffers.Binary; - using System.Buffers.Text; - using System.Globalization; - using System.Runtime.CompilerServices; - using System.Text.Json; - using System.Text.Json.Serialization; +namespace Nethermind.Serialization.Json; - public class ULongConverter : JsonConverter +public class ULongConverter : JsonConverter +{ + public static ulong FromString(ReadOnlySpan s) { - public static ulong FromString(ReadOnlySpan s) + if (s.Length == 0) { - if (s.Length == 0) - { - throw new JsonException("null cannot be assigned to ulong"); - } + throw new JsonException("null cannot be assigned to ulong"); + } - if (s.SequenceEqual("0x0"u8)) - { - return 0uL; - } + if (s.SequenceEqual("0x0"u8)) + { + return 0uL; + } - ulong value; - if (s.StartsWith("0x"u8)) - { - s = s[2..]; - if (Utf8Parser.TryParse(s, out value, out _, 'x')) - { - return value; - } - } - else if (Utf8Parser.TryParse(s, out value, out _)) + ulong value; + if (s.StartsWith("0x"u8)) + { + s = s[2..]; + if (Utf8Parser.TryParse(s, out value, out _, 'x')) { return value; } - - throw new JsonException("hex to long"); } - - [SkipLocalsInit] - public override void Write( - Utf8JsonWriter writer, - ulong value, - JsonSerializerOptions options) + else if (Utf8Parser.TryParse(s, out value, out _)) { - NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); - switch (usedConversion) - { - case NumberConversion.Hex: - { - if (value == 0) - { - writer.WriteRawValue("\"0x0\""u8, skipInputValidation: true); - } - else - { - Span bytes = stackalloc byte[8]; - BinaryPrimitives.WriteUInt64BigEndian(bytes, value); - ByteArrayConverter.Convert(writer, bytes, skipLeadingZeros: true); - } - break; - } - case NumberConversion.Decimal: - writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); - break; - case NumberConversion.Raw: - writer.WriteNumberValue(value); - break; - default: - throw new NotSupportedException(); - } + return value; } - internal static ulong ReadCore(ref Utf8JsonReader reader) + + ThrowJsonException(); + return default; + + [DoesNotReturn, StackTraceHidden] + static void ThrowJsonException() => throw new JsonException("hex to long"); + } + + [SkipLocalsInit] + public override void Write( + Utf8JsonWriter writer, + ulong value, + JsonSerializerOptions options) + { + NumberConversion usedConversion = ForcedNumberConversion.GetFinalConversion(); + switch (usedConversion) { - if (reader.TokenType == JsonTokenType.Number) - { - return reader.GetUInt64(); - } - if (reader.TokenType == JsonTokenType.String) - { - if (!reader.HasValueSequence) + case NumberConversion.Hex: + if (value == 0) { - return FromString(reader.ValueSpan); + writer.WriteStringValue("0x0"u8); } else { - return FromString(reader.ValueSequence.ToArray()); + HexWriter.WriteUlongHexRawValue(writer, value); } - } - - throw new JsonException(); + break; + case NumberConversion.Decimal: + writer.WriteStringValue(value == 0 ? "0" : value.ToString(CultureInfo.InvariantCulture)); + break; + case NumberConversion.Raw: + writer.WriteNumberValue(value); + break; + default: + throw new NotSupportedException(); } + } - public override ulong Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) + internal static ulong ReadCore(ref Utf8JsonReader reader) + { + if (reader.TokenType == JsonTokenType.Number) { - return ReadCore(ref reader); + return reader.GetUInt64(); } + if (reader.TokenType == JsonTokenType.String) + { + return !reader.HasValueSequence + ? FromString(reader.ValueSpan) + : FromString(reader.ValueSequence.ToArray()); + } + + ThrowJsonException(); + return default; + + [DoesNotReturn, StackTraceHidden] + static void ThrowJsonException() => throw new JsonException(); + } + + public override ulong Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + return ReadCore(ref reader); } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/ValueHash256Converter.cs b/src/Nethermind/Nethermind.Serialization.Json/ValueHash256Converter.cs index 18282391b58d..c38cd342cba6 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ValueHash256Converter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ValueHash256Converter.cs @@ -3,6 +3,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Serialization; using Nethermind.Core.Crypto; @@ -27,11 +28,12 @@ public override ValueHash256 Read( return bytes is null ? null : new ValueHash256(bytes); } + [SkipLocalsInit] public override void Write( Utf8JsonWriter writer, ValueHash256 keccak, JsonSerializerOptions options) { - ByteArrayConverter.Convert(writer, keccak.Bytes, skipLeadingZeros: false); + Hash256Converter.WriteHashHex(writer, in keccak); } } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs index 136035521590..90c559c1ab62 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs @@ -40,6 +40,12 @@ public BlockDecoder() : this(new HeaderDecoder()) { } (int txs, int uncles, int? withdrawals) = _blockBodyDecoder.GetBodyComponentLength(item.Body); + byte[][]? encodedTxs = item.EncodedTransactions; + if (encodedTxs is not null) + { + txs = GetPreEncodedTxLength(item.Transactions, encodedTxs); + } + int contentLength = headerLength + Rlp.LengthOfSequence(txs) + @@ -48,6 +54,16 @@ public BlockDecoder() : this(new HeaderDecoder()) { } return (contentLength, txs, uncles, withdrawals); } + private static int GetPreEncodedTxLength(Transaction[] txs, byte[][] encodedTxs) + { + int sum = 0; + for (int i = 0; i < encodedTxs.Length; i++) + { + sum += TxDecoder.GetWrappedTxLength(txs[i].Type, encodedTxs[i].Length); + } + return sum; + } + public override int GetLength(Block? item, RlpBehaviors rlpBehaviors) { if (item is null) @@ -104,9 +120,21 @@ public override void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehav stream.StartSequence(contentLength); _headerDecoder.Encode(stream, item.Header); stream.StartSequence(txsLength); - for (int i = 0; i < item.Transactions.Length; i++) + + byte[][]? encodedTxs = item.EncodedTransactions; + if (encodedTxs is not null) { - stream.Encode(item.Transactions[i]); + for (int i = 0; i < encodedTxs.Length; i++) + { + TxDecoder.WriteWrappedFormat(stream, item.Transactions[i].Type, encodedTxs[i]); + } + } + else + { + for (int i = 0; i < item.Transactions.Length; i++) + { + stream.Encode(item.Transactions[i]); + } } stream.StartSequence(unclesLength); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs index b16af62649d5..c523cc2555e3 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs @@ -328,8 +328,7 @@ public static Rlp Encode(T item, RlpBehaviors behaviors = RlpBehaviors.None) { if (item is Rlp rlp) { - RlpStream stream = new(LengthOfSequence(rlp.Length)); - return new(stream.Data.ToArray()); + return rlp; } IRlpStreamDecoder? rlpStreamDecoder = GetStreamDecoder(); diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs index bf3174dd9a74..22d063b13661 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs @@ -24,6 +24,25 @@ static TxDecoder() Instance = new TxDecoder(static () => TxObjectPool.Get()); Rlp.RegisterDecoder(typeof(Transaction), Instance); } + + /// + /// Gets the block-format length of a pre-encoded CL-format transaction. + /// Legacy txs use the same format; typed txs are wrapped in an RLP byte string. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetWrappedTxLength(TxType type, int clEncodedLength) + => type == TxType.Legacy ? clEncodedLength : Rlp.LengthOfSequence(clEncodedLength); + + /// + /// Writes a pre-encoded CL-format transaction in block format. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteWrappedFormat(RlpStream stream, TxType type, byte[] clEncoded) + { + if (type != TxType.Legacy) + stream.StartByteArray(clEncoded.Length, false); + stream.Write(clEncoded); + } } public sealed class SystemTxDecoder : TxDecoder; diff --git a/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.BasicTypes.cs b/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.BasicTypes.cs index f95471e7864e..41bd474e5c5a 100644 --- a/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.BasicTypes.cs +++ b/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.BasicTypes.cs @@ -15,6 +15,8 @@ namespace Nethermind.Serialization.Ssz; /// public static partial class Ssz { + private const int VarOffsetSize = sizeof(uint); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Encode(Span span, byte[] value, ref int offset) { diff --git a/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.Containers.cs b/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.Containers.cs deleted file mode 100644 index 3055b6038a62..000000000000 --- a/src/Nethermind/Nethermind.Serialization.Ssz/Ssz.Containers.cs +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; - -namespace Nethermind.Serialization.Ssz; - -public partial class Ssz -{ - private const int VarOffsetSize = sizeof(uint); - - private static void DecodeDynamicOffset(ReadOnlySpan span, ref int offset, out int dynamicOffset) - { - dynamicOffset = (int)DecodeUInt(span.Slice(offset, VarOffsetSize)); - offset += sizeof(uint); - } - -} diff --git a/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs b/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs index 5bd3e4fc9a97..c50e687cbce7 100644 --- a/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs +++ b/src/Nethermind/Nethermind.Shutter.Test/ShutterIntegrationTests.cs @@ -115,7 +115,9 @@ public async Task Can_increment_metric_on_missed_keys() time += (long)ShutterTestsCommon.SlotLength.TotalSeconds; } - Assert.That(Metrics.ShutterKeysMissed, Is.EqualTo(5)); + // ImproveBlock tasks run in the background and may not have completed yet + // when GetPayload returns (it only waits 50ms), so poll until all increments land. + Assert.That(() => Metrics.ShutterKeysMissed, Is.EqualTo((ulong)5).After(5000, 50)); } } diff --git a/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs b/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs index d80eebe4be39..3ec0ba3c2cce 100644 --- a/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs @@ -433,7 +433,7 @@ public void Selfdestruct_clears_cache() WorldState provider = BuildStorageProvider(ctx); StorageCell accessedStorageCell = new StorageCell(TestItem.AddressA, 1); StorageCell nonAccessedStorageCell = new StorageCell(TestItem.AddressA, 2); - preBlockCaches.StorageCache[accessedStorageCell] = [1, 2, 3]; + preBlockCaches.StorageCache.Set(accessedStorageCell, [1, 2, 3]); provider.Get(accessedStorageCell); provider.Commit(Paris.Instance); provider.ClearStorage(TestItem.AddressA); @@ -602,7 +602,7 @@ public void Selfdestruct_persist_between_commit() PreBlockCaches preBlockCaches = new PreBlockCaches(); Context ctx = new(preBlockCaches); StorageCell accessedStorageCell = new StorageCell(TestItem.AddressA, 1); - preBlockCaches.StorageCache[accessedStorageCell] = [1, 2, 3]; + preBlockCaches.StorageCache.Set(accessedStorageCell, [1, 2, 3]); WorldState provider = BuildStorageProvider(ctx); provider.Get(accessedStorageCell).ToArray().Should().BeEquivalentTo([1, 2, 3]); @@ -611,6 +611,25 @@ public void Selfdestruct_persist_between_commit() provider.Get(accessedStorageCell).ToArray().Should().BeEquivalentTo(StorageTree.ZeroBytes); } + [Test] + public void Eip161_empty_account_with_storage_does_not_throw_on_commit() + { + IWorldState worldState = new WorldState( + new TrieStoreScopeProvider(TestTrieStoreFactory.Build(new MemDb(), LimboLogs.Instance), new MemDb(), LimboLogs.Instance), LogManager); + + using var disposable = worldState.BeginScope(IWorldState.PreGenesis); + + // Create an empty account (balance=0, nonce=0, no code) and set storage on it. + // EIP-161 (via SpuriousDragon+) deletes empty accounts during commit, but the + // storage flush has already produced a non-empty storage root. The commit must + // handle this gracefully by skipping the storage root update for deleted accounts. + worldState.CreateAccount(TestItem.AddressA, 0); + worldState.Set(new StorageCell(TestItem.AddressA, 1), [1, 2, 3]); + worldState.Commit(SpuriousDragon.Instance); + + worldState.AccountExists(TestItem.AddressA).Should().BeFalse(); + } + [TestCase(2)] [TestCase(1000)] public void Set_empty_value_for_storage_cell_without_read_clears_data(int numItems) diff --git a/src/Nethermind/Nethermind.State.Test/TrieStoreScopeProviderTests.cs b/src/Nethermind/Nethermind.State.Test/TrieStoreScopeProviderTests.cs index d3742eec32b1..6338d4340452 100644 --- a/src/Nethermind/Nethermind.State.Test/TrieStoreScopeProviderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/TrieStoreScopeProviderTests.cs @@ -96,4 +96,26 @@ public void Test_CanSaveToCode() codeKv.WritesCount.Should().Be(1); } + + [Test] + public void Test_NullAccountWithNonEmptyStorageDoesNotThrow() + { + TestMemDb kv = new TestMemDb(); + IWorldStateScopeProvider scopeProvider = new TrieStoreScopeProvider(new TestRawTrieStore(kv), new MemDb(), LimboLogs.Instance); + + using var scope = scopeProvider.BeginScope(null); + + // Simulates the EIP-161 scenario: storage is flushed for an account that was + // then deleted (set to null) during state commit. The write batch Dispose should + // skip the storage root update for the deleted account instead of throwing. + using (var writeBatch = scope.StartWriteBatch(1)) + { + using (var storageSet = writeBatch.CreateStorageWriteBatch(TestItem.AddressA, 1)) + { + storageSet.Set(1, [1, 2, 3]); + } + + writeBatch.Set(TestItem.AddressA, null); + } + } } diff --git a/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs b/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs index 546613b95f95..874dadafcdd8 100644 --- a/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs @@ -15,10 +15,10 @@ namespace Nethermind.State.OverridableEnv; public class OverridableCodeInfoRepository(ICodeInfoRepository codeInfoRepository, IWorldState worldState) : IOverridableCodeInfoRepository { - private readonly Dictionary _codeOverrides = new(); - private readonly Dictionary _precompileOverrides = new(); + private readonly Dictionary _codeOverrides = new(); + private readonly Dictionary _precompileOverrides = new(); - public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) + public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) { delegationAddress = null; if (_precompileOverrides.TryGetValue(codeSource, out var precompile)) return precompile.codeInfo; @@ -35,7 +35,7 @@ public ICodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IR return codeInfoRepository.GetCachedCodeInfo(codeSource, followDelegation, vmSpec, out delegationAddress); } - public ICodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) + public CodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) { if (_precompileOverrides.TryGetValue(codeSource, out var precompile)) { @@ -56,9 +56,9 @@ public void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpe public void SetCodeOverride( IReleaseSpec vmSpec, Address key, - ICodeInfo value) + CodeInfo value) { - _codeOverrides[key] = (value, ValueKeccak.Compute(value.CodeSpan)); + _codeOverrides[key] = (value, ValueKeccak.Compute(value.Code.Span)); } public void MovePrecompile(IReleaseSpec vmSpec, Address precompileAddr, Address targetAddr) diff --git a/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs b/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs index f74d9a3f5d46..85bf38a4e6ca 100644 --- a/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs +++ b/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs @@ -452,7 +452,6 @@ public void UnmarkClear() private sealed class PerContractState : IReturnable { - private static readonly Func _loadFromTreeStorageFunc = LoadFromTreeStorage; private IWorldStateScopeProvider.IStorageTree? _backend; private readonly DefaultableDictionary BlockChange = new(); diff --git a/src/Nethermind/Nethermind.State/PreBlockCaches.cs b/src/Nethermind/Nethermind.State/PreBlockCaches.cs index 3fd8cf5e8cd7..e7f8bf290735 100644 --- a/src/Nethermind/Nethermind.State/PreBlockCaches.cs +++ b/src/Nethermind/Nethermind.State/PreBlockCaches.cs @@ -19,24 +19,24 @@ public class PreBlockCaches private readonly Func[] _clearCaches; - private readonly ConcurrentDictionary _storageCache = new(LockPartitions, InitialCapacity); - private readonly ConcurrentDictionary _stateCache = new(LockPartitions, InitialCapacity); - private readonly ConcurrentDictionary _rlpCache = new(LockPartitions, InitialCapacity); + private readonly SeqlockCache _storageCache = new(); + private readonly SeqlockCache _stateCache = new(); + private readonly SeqlockCache _rlpCache = new(); private readonly ConcurrentDictionary> _precompileCache = new(LockPartitions, InitialCapacity); public PreBlockCaches() { _clearCaches = [ - () => _storageCache.NoResizeClear() ? CacheType.Storage : CacheType.None, - () => _stateCache.NoResizeClear() ? CacheType.State : CacheType.None, - () => _precompileCache.NoResizeClear() ? CacheType.Precompile : CacheType.None + () => { _storageCache.Clear(); return CacheType.None; }, + () => { _stateCache.Clear(); return CacheType.None; }, + () => { _precompileCache.NoResizeClear(); return CacheType.None; } ]; } - public ConcurrentDictionary StorageCache => _storageCache; - public ConcurrentDictionary StateCache => _stateCache; - public ConcurrentDictionary RlpCache => _rlpCache; + public SeqlockCache StorageCache => _storageCache; + public SeqlockCache StateCache => _stateCache; + public SeqlockCache RlpCache => _rlpCache; public ConcurrentDictionary> PrecompileCache => _precompileCache; public CacheType ClearCaches() diff --git a/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs b/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs index d4260a7f5278..af258806395f 100644 --- a/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs +++ b/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Concurrent; using System.Diagnostics; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Metric; using Nethermind.Db; @@ -40,16 +40,26 @@ public class PrewarmerScopeProvider( public PreBlockCaches? Caches => preBlockCaches; public bool IsWarmWorldState => !populatePreBlockCache; - private sealed class ScopeWrapper( - IWorldStateScopeProvider.IScope baseScope, - PreBlockCaches preBlockCaches, - bool populatePreBlockCache) - : IWorldStateScopeProvider.IScope + private sealed class ScopeWrapper : IWorldStateScopeProvider.IScope { - ConcurrentDictionary preBlockCache = preBlockCaches.StateCache; + private readonly IWorldStateScopeProvider.IScope baseScope; + private readonly SeqlockCache preBlockCache; + private readonly SeqlockCache storageCache; + private readonly bool populatePreBlockCache; + private readonly SeqlockCache.ValueFactory _getFromBaseTree; private readonly IMetricObserver _metricObserver = Metrics.PrewarmerGetTime; private readonly bool _measureMetric = Metrics.DetailedMetricsEnabled; - private readonly PrewarmerGetTimeLabels _labels = populatePreBlockCache ? PrewarmerGetTimeLabels.Prewarmer : PrewarmerGetTimeLabels.NonPrewarmer; + private readonly PrewarmerGetTimeLabels _labels; + + public ScopeWrapper(IWorldStateScopeProvider.IScope baseScope, PreBlockCaches preBlockCaches, bool populatePreBlockCache) + { + this.baseScope = baseScope; + preBlockCache = preBlockCaches.StateCache; + storageCache = preBlockCaches.StorageCache; + this.populatePreBlockCache = populatePreBlockCache; + _labels = populatePreBlockCache ? PrewarmerGetTimeLabels.Prewarmer : PrewarmerGetTimeLabels.NonPrewarmer; + _getFromBaseTree = GetFromBaseTree; + } public void Dispose() => baseScope.Dispose(); @@ -59,7 +69,7 @@ public IWorldStateScopeProvider.IStorageTree CreateStorageTree(Address address) { return new StorageTreeWrapper( baseScope.CreateStorageTree(address), - preBlockCaches.StorageCache, + storageCache, address, populatePreBlockCache); } @@ -114,7 +124,7 @@ public void UpdateRootHash() if (populatePreBlockCache) { long priorReads = Metrics.ThreadLocalStateTreeReads; - Account? account = preBlockCache.GetOrAdd(address, GetFromBaseTree); + Account? account = preBlockCache.GetOrAdd(in addressAsKey, _getFromBaseTree); if (Metrics.ThreadLocalStateTreeReads == priorReads) { @@ -129,7 +139,7 @@ public void UpdateRootHash() } else { - if (preBlockCache?.TryGetValue(addressAsKey, out Account? account) ?? false) + if (preBlockCache.TryGetValue(in addressAsKey, out Account? account)) { if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.AddressHit); baseScope.HintGet(address, account); @@ -137,7 +147,7 @@ public void UpdateRootHash() } else { - account = GetFromBaseTree(addressAsKey); + account = GetFromBaseTree(in addressAsKey); if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.AddressMiss); } return account; @@ -146,22 +156,36 @@ public void UpdateRootHash() public void HintGet(Address address, Account? account) => baseScope.HintGet(address, account); - private Account? GetFromBaseTree(AddressAsKey address) + private Account? GetFromBaseTree(in AddressAsKey address) { return baseScope.Get(address); } } - private sealed class StorageTreeWrapper( - IWorldStateScopeProvider.IStorageTree baseStorageTree, - ConcurrentDictionary preBlockCache, - Address address, - bool populatePreBlockCache - ) : IWorldStateScopeProvider.IStorageTree + private sealed class StorageTreeWrapper : IWorldStateScopeProvider.IStorageTree { + private readonly IWorldStateScopeProvider.IStorageTree baseStorageTree; + private readonly SeqlockCache preBlockCache; + private readonly Address address; + private readonly bool populatePreBlockCache; + private readonly SeqlockCache.ValueFactory _loadFromTreeStorage; private readonly IMetricObserver _metricObserver = Db.Metrics.PrewarmerGetTime; private readonly bool _measureMetric = Db.Metrics.DetailedMetricsEnabled; - private readonly PrewarmerGetTimeLabels _labels = populatePreBlockCache ? PrewarmerGetTimeLabels.Prewarmer : PrewarmerGetTimeLabels.NonPrewarmer; + private readonly PrewarmerGetTimeLabels _labels; + + public StorageTreeWrapper( + IWorldStateScopeProvider.IStorageTree baseStorageTree, + SeqlockCache preBlockCache, + Address address, + bool populatePreBlockCache) + { + this.baseStorageTree = baseStorageTree; + this.preBlockCache = preBlockCache; + this.address = address; + this.populatePreBlockCache = populatePreBlockCache; + _labels = populatePreBlockCache ? PrewarmerGetTimeLabels.Prewarmer : PrewarmerGetTimeLabels.NonPrewarmer; + _loadFromTreeStorage = LoadFromTreeStorage; + } public Hash256 RootHash => baseStorageTree.RootHash; @@ -173,7 +197,7 @@ public byte[] Get(in UInt256 index) { long priorReads = Db.Metrics.ThreadLocalStorageTreeReads; - byte[] value = preBlockCache.GetOrAdd(storageCell, LoadFromTreeStorage); + byte[] value = preBlockCache.GetOrAdd(in storageCell, _loadFromTreeStorage); if (Db.Metrics.ThreadLocalStorageTreeReads == priorReads) { @@ -189,15 +213,15 @@ public byte[] Get(in UInt256 index) } else { - if (preBlockCache?.TryGetValue(storageCell, out byte[] value) ?? false) + if (preBlockCache.TryGetValue(in storageCell, out byte[] value)) { if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.SlotGetHit); - baseStorageTree.HintGet(index, value); + baseStorageTree.HintGet(in index, value); Db.Metrics.IncrementStorageTreeCache(); } else { - value = LoadFromTreeStorage(storageCell); + value = LoadFromTreeStorage(in storageCell); if (_measureMetric) _metricObserver.Observe(Stopwatch.GetTimestamp() - sw, _labels.SlotGetMiss); } return value; @@ -206,7 +230,7 @@ public byte[] Get(in UInt256 index) public void HintGet(in UInt256 index, byte[]? value) => baseStorageTree.HintGet(in index, value); - private byte[] LoadFromTreeStorage(StorageCell storageCell) + private byte[] LoadFromTreeStorage(in StorageCell storageCell) { Db.Metrics.IncrementStorageTreeReads(); diff --git a/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs b/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs index e54f7fb46bbb..ce45389b1fc3 100644 --- a/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs +++ b/src/Nethermind/Nethermind.State/Proofs/PatriciaTrieT.cs @@ -17,12 +17,13 @@ namespace Nethermind.State.Trie; /// The type of the elements in the collection used to build the trie. public abstract class PatriciaTrie : PatriciaTree { + protected const int MinItemsForParallelRootHash = 64; /// The collection to build the trie of. /// /// true to maintain an in-memory database for proof computation; /// otherwise, false. /// - protected PatriciaTrie(ReadOnlySpan list, bool canBuildProof, ICappedArrayPool? bufferPool = null) + protected PatriciaTrie(ReadOnlySpan list, bool canBuildProof, ICappedArrayPool? bufferPool = null, bool canBeParallel = true) : base(canBuildProof ? new MemDb() : NullDb.Instance, EmptyTreeHash, false, NullLogManager.Instance, bufferPool: bufferPool) { CanBuildProof = canBuildProof; @@ -31,7 +32,8 @@ protected PatriciaTrie(ReadOnlySpan list, bool canBuildProof, ICappedArrayPoo { // ReSharper disable once VirtualMemberCallInConstructor Initialize(list); - UpdateRootHash(); + // Parallel root hashing adds scheduling overhead for small tries. + UpdateRootHash(canBeParallel); } } diff --git a/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs b/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs index a84177868b06..f3391d31649b 100644 --- a/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/ReceiptTrie.cs @@ -20,8 +20,8 @@ public sealed class ReceiptTrie : PatriciaTrie private readonly IRlpStreamDecoder _decoder; /// /// The transaction receipts to build the trie of. - public ReceiptTrie(IReceiptSpec spec, ReadOnlySpan receipts, IRlpStreamDecoder trieDecoder, ICappedArrayPool bufferPool, bool canBuildProof = false) - : base(null, canBuildProof, bufferPool: bufferPool) + public ReceiptTrie(IReceiptSpec spec, ReadOnlySpan receipts, IRlpStreamDecoder trieDecoder, ICappedArrayPool bufferPool, bool canBuildProof = false, bool canBeParallel = true) + : base(null, canBuildProof, bufferPool: bufferPool, canBeParallel) { ArgumentNullException.ThrowIfNull(spec); ArgumentNullException.ThrowIfNull(trieDecoder); @@ -30,7 +30,7 @@ public ReceiptTrie(IReceiptSpec spec, ReadOnlySpan receipts, IRlpStre if (receipts.Length > 0) { Initialize(receipts, spec); - UpdateRootHash(); + UpdateRootHash(canBeParallel); } } @@ -53,14 +53,16 @@ private void Initialize(ReadOnlySpan receipts, IReceiptSpec spec) public static byte[][] CalculateReceiptProofs(IReleaseSpec spec, ReadOnlySpan receipts, int index, IRlpStreamDecoder decoder) { - using TrackingCappedArrayPool cappedArrayPool = new(receipts.Length * 4); - return new ReceiptTrie(spec, receipts, decoder, cappedArrayPool, canBuildProof: true).BuildProof(index); + bool canBeParallel = receipts.Length > MinItemsForParallelRootHash; + using TrackingCappedArrayPool cappedArrayPool = new(receipts.Length * 4, canBeParallel: canBeParallel); + return new ReceiptTrie(spec, receipts, decoder, cappedArrayPool, canBuildProof: true, canBeParallel: canBeParallel).BuildProof(index); } public static Hash256 CalculateRoot(IReceiptSpec receiptSpec, ReadOnlySpan txReceipts, IRlpStreamDecoder decoder) { - using TrackingCappedArrayPool cappedArrayPool = new(txReceipts.Length * 4); - Hash256 receiptsRoot = new ReceiptTrie(receiptSpec, txReceipts, decoder, bufferPool: cappedArrayPool).RootHash; + bool canBeParallel = txReceipts.Length > MinItemsForParallelRootHash; + using TrackingCappedArrayPool cappedArrayPool = new(txReceipts.Length * 4, canBeParallel: canBeParallel); + Hash256 receiptsRoot = new ReceiptTrie(receiptSpec, txReceipts, decoder, bufferPool: cappedArrayPool, canBeParallel: canBeParallel).RootHash; return receiptsRoot; } } diff --git a/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs b/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs index 7e5c6b5415a7..49aeaac04930 100644 --- a/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/TxTrie.cs @@ -22,8 +22,8 @@ public sealed class TxTrie : PatriciaTrie /// /// The transactions to build the trie of. - public TxTrie(ReadOnlySpan transactions, bool canBuildProof = false, ICappedArrayPool? bufferPool = null) - : base(transactions, canBuildProof, bufferPool: bufferPool) { } + public TxTrie(ReadOnlySpan transactions, bool canBuildProof = false, ICappedArrayPool? bufferPool = null, bool canBeParallel = true) + : base(transactions, canBuildProof, bufferPool: bufferPool, canBeParallel: canBeParallel) { } protected override void Initialize(ReadOnlySpan list) { @@ -57,20 +57,41 @@ static SpanSource CopyExistingRlp(ReadOnlySpan rlp, ICappedArrayPool? buff } } + private void InitializeFromEncodedTransactions(ReadOnlySpan list) + { + for (int key = 0; key < list.Length; key++) + { + SpanSource keyBuffer = key.EncodeToSpanSource(_bufferPool); + Set(keyBuffer.Span, list[key]); + } + } + [DoesNotReturn, StackTraceHidden] private static void ThrowSpanSourceNotCappedArray() => throw new InvalidOperationException("Encode to SpanSource failed to get a CappedArray."); public static byte[][] CalculateProof(ReadOnlySpan transactions, int index) { - using TrackingCappedArrayPool cappedArray = new(transactions.Length * 4); - byte[][] rootHash = new TxTrie(transactions, canBuildProof: true, bufferPool: cappedArray).BuildProof(index); + bool canBeParallel = transactions.Length > MinItemsForParallelRootHash; + using TrackingCappedArrayPool cappedArray = new(transactions.Length * 4, canBeParallel: canBeParallel); + byte[][] rootHash = new TxTrie(transactions, canBuildProof: true, bufferPool: cappedArray, canBeParallel: canBeParallel).BuildProof(index); return rootHash; } public static Hash256 CalculateRoot(ReadOnlySpan transactions) { - using TrackingCappedArrayPool cappedArray = new(transactions.Length * 4); - Hash256 rootHash = new TxTrie(transactions, canBuildProof: false, bufferPool: cappedArray).RootHash; + bool canBeParallel = transactions.Length > MinItemsForParallelRootHash; + using TrackingCappedArrayPool cappedArray = new(transactions.Length * 4, canBeParallel: canBeParallel); + Hash256 rootHash = new TxTrie(transactions, canBuildProof: false, bufferPool: cappedArray, canBeParallel: canBeParallel).RootHash; return rootHash; } + + public static Hash256 CalculateRoot(ReadOnlySpan encodedTransactions) + { + bool canBeParallel = encodedTransactions.Length > MinItemsForParallelRootHash; + using TrackingCappedArrayPool cappedArray = new(encodedTransactions.Length * 4, canBeParallel: canBeParallel); + TxTrie txTrie = new(ReadOnlySpan.Empty, canBuildProof: false, bufferPool: cappedArray, canBeParallel: canBeParallel); + txTrie.InitializeFromEncodedTransactions(encodedTransactions); + txTrie.UpdateRootHash(canBeParallel); + return txTrie.RootHash; + } } diff --git a/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs b/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs index 10efa64e86a4..d63dc61471b0 100644 --- a/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs +++ b/src/Nethermind/Nethermind.State/Proofs/WithdrawalTrie.cs @@ -19,7 +19,7 @@ public sealed class WithdrawalTrie : PatriciaTrie /// /// The withdrawals to build the trie of. public WithdrawalTrie(ReadOnlySpan withdrawals, bool canBuildProof = false) - : base(withdrawals, canBuildProof) { } + : base(withdrawals, canBuildProof, canBeParallel: false) { } public static Hash256? CalculateRoot(ReadOnlySpan withdrawals) => new WithdrawalTrie(withdrawals).RootHash; diff --git a/src/Nethermind/Nethermind.State/TrieStoreScopeProvider.cs b/src/Nethermind/Nethermind.State/TrieStoreScopeProvider.cs index e4f01d4008ed..88026fd8d282 100644 --- a/src/Nethermind/Nethermind.State/TrieStoreScopeProvider.cs +++ b/src/Nethermind/Nethermind.State/TrieStoreScopeProvider.cs @@ -5,7 +5,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -194,10 +193,14 @@ public void Dispose() while (_dirtyStorageTree.TryDequeue(out (AddressAsKey, Hash256) entry)) { (AddressAsKey key, Hash256 storageRoot) = entry; - if (!_dirtyAccounts.TryGetValue(key, out var account)) account = scope.Get(key); - if (account == null && storageRoot == Keccak.EmptyTreeHash) continue; - account ??= ThrowNullAccount(key); - account = account!.WithChangedStorageRoot(storageRoot); + if (!_dirtyAccounts.TryGetValue(key, out var account)) + account = scope.Get(key); + + // Account may be null when EIP-161 deletes an empty account that had storage + // changes in the same block. Skip the storage root update since the account + // will not exist in the state trie. + if (account is null) continue; + account = account.WithChangedStorageRoot(storageRoot); _dirtyAccounts[key] = account; OnAccountUpdated?.Invoke(key, new IWorldStateScopeProvider.AccountUpdated(key, account)); if (logger.IsTrace) Trace(key, storageRoot, account); @@ -217,10 +220,6 @@ public void Dispose() [MethodImpl(MethodImplOptions.NoInlining)] void Trace(Address address, Hash256 storageRoot, Account? account) => logger.Trace($"Update {address} S {account?.StorageRoot} -> {storageRoot}"); - - [DoesNotReturn, StackTraceHidden] - static Account ThrowNullAccount(Address address) - => throw new InvalidOperationException($"Account {address} is null when updating storage hash"); } } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs index d6d7f65685d3..92ffa310957e 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedHealingTests.cs @@ -19,6 +19,7 @@ namespace Nethermind.Synchronization.Test.FastSync; [Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class StateSyncFeedHealingTests : StateSyncFeedTestsBase { [Test] diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs index af08c2f41a61..ffc7eb77719a 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs @@ -6,7 +6,6 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; -using Nethermind.Logging; using Nethermind.State; using Nethermind.Synchronization.FastBlocks; using Nethermind.Synchronization.ParallelSync; @@ -31,7 +30,7 @@ public void Header_block_is_0_when_no_header_was_suggested() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); blockTree.BestSuggestedHeader.ReturnsNull(); Assert.That(syncProgressResolver.FindBestHeader(), Is.EqualTo(0)); } @@ -46,7 +45,7 @@ public void Best_block_is_0_when_no_block_was_suggested() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); blockTree.BestSuggestedBody.ReturnsNull(); Assert.That(syncProgressResolver.FindBestFullBlock(), Is.EqualTo(0)); } @@ -61,7 +60,7 @@ public void Best_state_is_head_when_there_are_no_suggested_blocks() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); Block head = Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(5).WithStateRoot(TestItem.KeccakA).TestObject).TestObject; blockTree.Head.Returns(head); blockTree.BestSuggestedHeader.Returns(head.Header); @@ -79,7 +78,7 @@ public void Best_state_is_suggested_if_there_is_suggested_block_with_state() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); Block head = Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(5).WithStateRoot(TestItem.KeccakA).TestObject).TestObject; BlockHeader suggested = Build.A.BlockHeader.WithNumber(6).WithStateRoot(TestItem.KeccakB).TestObject; blockTree.Head.Returns(head); @@ -101,7 +100,7 @@ public void Best_state_is_head_if_there_is_suggested_block_without_state() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); Block head = Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(5).WithStateRoot(TestItem.KeccakA).TestObject).TestObject; BlockHeader suggested = Build.A.BlockHeader.WithNumber(6).WithStateRoot(TestItem.KeccakB).TestObject; blockTree.Head.Returns(head); @@ -123,7 +122,7 @@ public void Is_fast_block_finished_returns_true_when_no_fast_sync_is_used() PivotNumber = 1, }; - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); Assert.That(syncProgressResolver.IsFastBlocksHeadersFinished(), Is.True); Assert.That(syncProgressResolver.IsFastBlocksBodiesFinished(), Is.True); Assert.That(syncProgressResolver.IsFastBlocksReceiptsFinished(), Is.True); @@ -145,7 +144,7 @@ public void Is_fast_block_bodies_finished_returns_false_when_blocks_not_download blockTree.LowestInsertedHeader.Returns(Build.A.BlockHeader.WithNumber(1).WithStateRoot(TestItem.KeccakA).TestObject); - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig); Assert.That(syncProgressResolver.IsFastBlocksBodiesFinished(), Is.False); } @@ -164,12 +163,12 @@ public void Is_fast_block_receipts_finished_returns_true_when_receipts_not_downl blockTree.LowestInsertedHeader.Returns(Build.A.BlockHeader.WithNumber(1).WithStateRoot(TestItem.KeccakA).TestObject); - SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, true, syncConfig, LimboLogs.Instance); + SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, true, syncConfig); Assert.That(syncProgressResolver.IsFastBlocksReceiptsFinished(), Is.True); } - private SyncProgressResolver CreateProgressResolver(IBlockTree blockTree, IStateReader stateReader, bool isReceiptFinished, SyncConfig syncConfig, LimboLogs limboLogs) + private SyncProgressResolver CreateProgressResolver(IBlockTree blockTree, IStateReader stateReader, bool isReceiptFinished, SyncConfig syncConfig) { ISyncFeed receiptFeed = Substitute.For>(); receiptFeed.IsFinished.Returns(isReceiptFinished); @@ -181,8 +180,7 @@ private SyncProgressResolver CreateProgressResolver(IBlockTree blockTree, IState Substitute.For>(), Substitute.For>(), receiptFeed, - Substitute.For>(), - limboLogs + Substitute.For>() ); } } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs index 4b2d33f62f8a..e65fa535eff9 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncServerTests.cs @@ -666,7 +666,7 @@ public async Task Broadcast_NewBlock_on_arrival_to_sqrt_of_peers([Values(1, 2, 3 int count = 0; remoteServer .When(r => r.AddNewBlock(Arg.Is(b => b.Hash == remoteBlockTree.Head!.Hash), Arg.Any())) - .Do(_ => count++); + .Do(_ => Interlocked.Increment(ref count)); PeerInfo[] peers = Enumerable.Range(0, peerCount).Take(peerCount) .Select(_ => new PeerInfo(new SyncPeerMock(remoteBlockTree, remoteSyncServer: remoteServer))) .ToArray(); diff --git a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs index 0f2c9cd0b1a2..1eaf7d0d9cd1 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncProgressResolver.cs @@ -8,7 +8,6 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Int256; -using Nethermind.Logging; using Nethermind.Synchronization.FastBlocks; using Nethermind.Synchronization.SnapSync; @@ -21,9 +20,6 @@ public class SyncProgressResolver : ISyncProgressResolver private readonly ISyncConfig _syncConfig; private readonly IFullStateFinder _fullStateFinder; - // ReSharper disable once NotAccessedField.Local - private readonly ILogger _logger; - private readonly ISyncFeed _headersSyncFeed; private readonly ISyncFeed _bodiesSyncFeed; private readonly ISyncFeed _receiptsSyncFeed; @@ -36,10 +32,8 @@ public SyncProgressResolver( [KeyFilter(nameof(HeadersSyncFeed))] ISyncFeed headersSyncFeed, ISyncFeed bodiesSyncFeed, ISyncFeed receiptsSyncFeed, - ISyncFeed snapSyncFeed, - ILogManager logManager) + ISyncFeed snapSyncFeed) { - _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); _fullStateFinder = fullStateFinder ?? throw new ArgumentNullException(nameof(fullStateFinder)); _syncConfig = syncConfig ?? throw new ArgumentNullException(nameof(syncConfig)); diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/ClientTypeStrategy.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/ClientTypeStrategy.cs deleted file mode 100644 index 142c2ff819c8..000000000000 --- a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/ClientTypeStrategy.cs +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using System.Linq; -using Nethermind.Blockchain; -using Nethermind.Stats; -using Nethermind.Stats.Model; - -namespace Nethermind.Synchronization.Peers.AllocationStrategies; - -public class ClientTypeStrategy : IPeerAllocationStrategy -{ - private readonly IPeerAllocationStrategy _strategy; - private readonly bool _allowOtherIfNone; - private readonly HashSet _supportedClientTypes; - - public ClientTypeStrategy(IPeerAllocationStrategy strategy, bool allowOtherIfNone, params NodeClientType[] supportedClientTypes) - : this(strategy, allowOtherIfNone, (IEnumerable)supportedClientTypes) - { - } - - public ClientTypeStrategy(IPeerAllocationStrategy strategy, bool allowOtherIfNone, IEnumerable supportedClientTypes) - { - _strategy = strategy; - _allowOtherIfNone = allowOtherIfNone; - _supportedClientTypes = new HashSet(supportedClientTypes); - } - - public PeerInfo? Allocate(PeerInfo? currentPeer, IEnumerable peers, INodeStatsManager nodeStatsManager, IBlockTree blockTree) - { - IEnumerable originalPeers = peers; - peers = peers.Where(p => _supportedClientTypes.Contains(p.PeerClientType)); - - if (_allowOtherIfNone) - { - if (!peers.Any()) - { - peers = originalPeers; - } - } - return _strategy.Allocate(currentPeer, peers, nodeStatsManager, blockTree); - } -} diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/NullStrategy.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/NullStrategy.cs deleted file mode 100644 index fcf28b1a4a45..000000000000 --- a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/NullStrategy.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Nethermind.Blockchain; -using Nethermind.Stats; - -namespace Nethermind.Synchronization.Peers.AllocationStrategies -{ - /// - /// used only for failed allocations - /// - public class NullStrategy : IPeerAllocationStrategy - { - private NullStrategy() - { - } - - public static IPeerAllocationStrategy Instance { get; } = new NullStrategy(); - - public PeerInfo? Allocate(PeerInfo? currentPeer, IEnumerable peers, INodeStatsManager nodeStatsManager, IBlockTree blockTree) - { - return null; - } - } -} diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StaticStrategy.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StaticStrategy.cs deleted file mode 100644 index 7d9a5b4de624..000000000000 --- a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StaticStrategy.cs +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Collections.Generic; -using Nethermind.Blockchain; -using Nethermind.Stats; - -namespace Nethermind.Synchronization.Peers.AllocationStrategies -{ - public class StaticStrategy : IPeerAllocationStrategy - { - private readonly PeerInfo _peerInfo; - - public StaticStrategy(PeerInfo peerInfo) - { - _peerInfo = peerInfo; - } - - public PeerInfo Allocate(PeerInfo? currentPeer, IEnumerable peers, INodeStatsManager nodeStatsManager, IBlockTree blockTree) - { - return _peerInfo; - } - } -} diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StrategySelectionType.cs b/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StrategySelectionType.cs deleted file mode 100644 index c20046181c27..000000000000 --- a/src/Nethermind/Nethermind.Synchronization/Peers/AllocationStrategies/StrategySelectionType.cs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Synchronization.Peers.AllocationStrategies; - -public enum StrategySelectionType -{ - Better = 1, - AtLeastTheSame = 0, - CanBeSlightlyWorse = -1 -} diff --git a/src/Nethermind/Nethermind.Synchronization/SyncPeerEventArgs.cs b/src/Nethermind/Nethermind.Synchronization/SyncPeerEventArgs.cs deleted file mode 100644 index 3d5fbf168612..000000000000 --- a/src/Nethermind/Nethermind.Synchronization/SyncPeerEventArgs.cs +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Synchronization.Peers; - -namespace Nethermind.Synchronization -{ - public class AllocationChangeEventArgs - { - public AllocationChangeEventArgs(PeerInfo? previous, PeerInfo? current) - { - Previous = previous; - Current = current; - } - - public PeerInfo? Previous { get; } - - public PeerInfo? Current { get; } - } -} diff --git a/src/Nethermind/Nethermind.Taiko.Test/L1OriginStoreTests.cs b/src/Nethermind/Nethermind.Taiko.Test/L1OriginStoreTests.cs index 954bfacd3082..4c0870ecc5e9 100644 --- a/src/Nethermind/Nethermind.Taiko.Test/L1OriginStoreTests.cs +++ b/src/Nethermind/Nethermind.Taiko.Test/L1OriginStoreTests.cs @@ -202,5 +202,23 @@ public void Fails_for_invalid_length_signature(int signatureLength) Action act = () => _decoder.Encode(origin); act.Should().Throw().WithMessage($"*Signature*{L1OriginDecoder.SignatureLength}*"); } + + [Test] + public void Encode_produces_RLP_with_correct_sequence_length( + [Values(false, true)] bool withBuildPayload, + [Values(false, true)] bool withForcedInclusion, + [Values(false, true)] bool withSignature) + { + int[]? buildPayloadArgsId = withBuildPayload ? Enumerable.Range(0, 8).ToArray() : null; + int[]? signature = withSignature ? Enumerable.Range(0, 65).ToArray() : null; + L1Origin origin = new(123, Hash256.Zero, 456, Hash256.Zero, buildPayloadArgsId, withForcedInclusion, signature); + + Rlp encoded = _decoder.Encode(origin); + RlpStream stream = new(encoded.Bytes); + (int prefixLength, int contentLength) = stream.ReadPrefixAndContentLength(); + + contentLength.Should().Be(encoded.Bytes.Length - prefixLength, + "StartSequence must receive content length, not total length"); + } } diff --git a/src/Nethermind/Nethermind.Taiko.Test/SurgeGasPriceOracleTests.cs b/src/Nethermind/Nethermind.Taiko.Test/SurgeGasPriceOracleTests.cs index 82b0daed91b3..cf4ea2c8d727 100644 --- a/src/Nethermind/Nethermind.Taiko.Test/SurgeGasPriceOracleTests.cs +++ b/src/Nethermind/Nethermind.Taiko.Test/SurgeGasPriceOracleTests.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using NSubstitute; using NUnit.Framework; +using Nethermind.Blockchain; using Nethermind.Blockchain.Find; using Nethermind.Core; using Nethermind.Core.Specs; @@ -12,7 +13,6 @@ using Nethermind.Int256; using Nethermind.JsonRpc.Client; using Nethermind.Logging; -using Nethermind.Serialization.Json; using Nethermind.Taiko.Config; using Nethermind.Taiko.Rpc; @@ -29,7 +29,36 @@ public class SurgeGasPriceOracleTests private static readonly UInt256 MinGasPrice = UInt256.Parse("1000000000"); // 1 Gwei private static string CreatePaddedHex(UInt256 value, int padding = 64) => - value.ToString("x64").PadLeft(padding, '0'); + value.ToString("x").PadLeft(padding, '0'); + + /// + /// Creates a mock CoreState response. + /// CoreState: nextProposalId (word 0), lastProposalBlockId (word 1), lastFinalizedProposalId (word 2), ... + /// + private static string CreateCoreStateResponse(ulong nextProposalId, ulong lastFinalizedProposalId) + { + return "0x" + + CreatePaddedHex(nextProposalId) + // word 0: nextProposalId + CreatePaddedHex(0) + // word 1: lastProposalBlockId + CreatePaddedHex(lastFinalizedProposalId) + // word 2: lastFinalizedProposalId + CreatePaddedHex(0) + // word 3: lastFinalizedTimestamp + CreatePaddedHex(0) + // word 4: lastCheckpointTimestamp + CreatePaddedHex(0); // word 5: lastFinalizedBlockHash + } + + /// + /// Creates a mock Config response with ringBufferSize at word 10. + /// + private static string CreateConfigResponse(ulong ringBufferSize) + { + // Config has 18 fields, ringBufferSize is at word 10 + string response = "0x"; + for (int i = 0; i < 18; i++) + { + response += i == 10 ? CreatePaddedHex(ringBufferSize) : CreatePaddedHex(0); + } + return response; + } [SetUp] public void Setup() @@ -41,7 +70,8 @@ public void Setup() _surgeConfig = new SurgeConfig { TaikoInboxAddress = "0x06a9Ab27c7e2255df1815E6CC0168d7755Feb19a", - GasPriceRefreshTimeoutSeconds = 2 + GasPriceRefreshTimeoutSeconds = 2, + L2GasUsageWindowSize = 5 }; _gasPriceOracle = new SurgeGasPriceOracle( @@ -53,6 +83,31 @@ public void Setup() _surgeConfig); } + private void SetupBlockFinderWithBlocks(long headBlockNumber, long gasUsed = 1000000) + { + Block headBlock = Build.A.Block.WithNumber(headBlockNumber).WithGasUsed(gasUsed).TestObject; + _blockFinder.Head.Returns(headBlock); + + for (long i = headBlockNumber; i >= Math.Max(0, headBlockNumber - _surgeConfig.L2GasUsageWindowSize + 1); i--) + { + _blockFinder.FindBlock(i, BlockTreeLookupOptions.RequireCanonical) + .Returns(Build.A.Block.WithNumber(i).WithGasUsed(gasUsed).TestObject); + } + } + + private void SetupInboxContractMocks(ulong ringBufferSize = 100, ulong nextProposalId = 10, ulong lastFinalizedProposalId = 5) + { + // getConfig() selector without 0x prefix + _l1RpcClient.Post("eth_call", Arg.Is(o => + o.ToString()!.ToLowerInvariant().Contains("c3f909d4")), "latest") + .Returns(CreateConfigResponse(ringBufferSize)); + + // getCoreState() selector without 0x prefix + _l1RpcClient.Post("eth_call", Arg.Is(o => + o.ToString()!.ToLowerInvariant().Contains("6aa6a01a")), "latest") + .Returns(CreateCoreStateResponse(nextProposalId, lastFinalizedProposalId)); + } + [Test] public async ValueTask GetGasPriceEstimate_WhenNoHeadBlock_ReturnsMinGasPrice() { @@ -66,8 +121,7 @@ public async ValueTask GetGasPriceEstimate_WhenNoHeadBlock_ReturnsMinGasPrice() [Test] public async ValueTask GetGasPriceEstimate_WhenL1FeeHistoryFails_ReturnsMinGasPrice() { - Block headBlock = Build.A.Block.WithNumber(1).WithGasUsed(1000000).TestObject; - _blockFinder.Head.Returns(headBlock); + SetupBlockFinderWithBlocks(10); _l1RpcClient.Post("eth_feeHistory", _surgeConfig.FeeHistoryBlockCount, BlockParameter.Latest, null) .Returns(Task.FromResult(null)); @@ -79,8 +133,7 @@ public async ValueTask GetGasPriceEstimate_WhenL1FeeHistoryFails_ReturnsMinGasPr [Test] public async ValueTask GetGasPriceEstimate_WithEmptyFeeHistory_ReturnsMinGasPrice() { - Block headBlock = Build.A.Block.WithNumber(1).WithGasUsed(1000000).TestObject; - _blockFinder.Head.Returns(headBlock); + SetupBlockFinderWithBlocks(10); var feeHistory = new L1FeeHistoryResults { @@ -99,55 +152,47 @@ public async ValueTask GetGasPriceEstimate_WithEmptyFeeHistory_ReturnsMinGasPric [Test] public async ValueTask GetGasPriceEstimate_WithValidL1FeeHistory_CalculatesCorrectGasPrice() { - Block headBlock = Build.A.Block.WithNumber(1).WithGasUsed(1000000).TestObject; - _blockFinder.Head.Returns(headBlock); + SetupInboxContractMocks(); - // Dummy Ethereum L1 fee history - var feeHistory = new L1FeeHistoryResults + // Scenario 1: Low L1 base fee (10 Gwei average) + SetupBlockFinderWithBlocks(10, 1000000); + var lowFeeHistory = new L1FeeHistoryResults { - BaseFeePerGas = - [ - UInt256.Parse("15000000000"), - UInt256.Parse("25000000000"), - UInt256.Parse("35000000000") - ], - BaseFeePerBlobGas = - [ - UInt256.Parse("1000000000"), - UInt256.Parse("1500000000") - ] + BaseFeePerGas = [UInt256.Parse("10000000000")], + BaseFeePerBlobGas = [UInt256.Parse("1000000000")] }; - // Set up the mock to match the exact parameters that will be passed _l1RpcClient.Post("eth_feeHistory", _surgeConfig.FeeHistoryBlockCount, BlockParameter.Latest, null) - .Returns(Task.FromResult(feeHistory)); + .Returns(Task.FromResult(lowFeeHistory)); - // Mock Stats2 returned by getStats2() call to have 2 batches (numBatches=2) - var stats2Response = "0x" + CreatePaddedHex(2) + CreatePaddedHex(0, 192); - _l1RpcClient.Post("eth_call", Arg.Is(o => - o.ToString()!.ToLowerInvariant().Contains("0x26baca1c")), "latest") - .Returns(stats2Response); + var oracleLowFee = new SurgeGasPriceOracle( + _blockFinder, _logManager, _specProvider, MinGasPrice, _l1RpcClient, _surgeConfig); + UInt256 gasPriceLowL1Fee = await oracleLowFee.GetGasPriceEstimate(); - // Mock Batch returned by getBatch(1) call to have lastBlockId=1 - var batchResponse = "0x" + CreatePaddedHex(0) + CreatePaddedHex(1) + CreatePaddedHex(0, 576); - _l1RpcClient.Post("eth_call", Arg.Is(o => - o.ToString()!.ToLowerInvariant().Contains("0x888775d9")), "latest") - .Returns(batchResponse); + // Scenario 2: High L1 base fee (50 Gwei average) + SetupBlockFinderWithBlocks(20, 1000000); + var highFeeHistory = new L1FeeHistoryResults + { + BaseFeePerGas = [UInt256.Parse("50000000000")], + BaseFeePerBlobGas = [UInt256.Parse("1000000000")] + }; - // Mock block finder to return block with gas usage - _blockFinder.FindBlock(1, Arg.Any()) - .Returns(Build.A.Block.WithNumber(1).WithGasUsed(1000000).TestObject); + _l1RpcClient.Post("eth_feeHistory", _surgeConfig.FeeHistoryBlockCount, BlockParameter.Latest, null) + .Returns(Task.FromResult(highFeeHistory)); - UInt256 gasPrice = await _gasPriceOracle.GetGasPriceEstimate(); + var oracleHighFee = new SurgeGasPriceOracle( + _blockFinder, _logManager, _specProvider, MinGasPrice, _l1RpcClient, _surgeConfig); + UInt256 gasPriceHighL1Fee = await oracleHighFee.GetGasPriceEstimate(); - Assert.That(gasPrice, Is.GreaterThan(MinGasPrice)); + // Higher L1 base fee should result in higher L2 gas price + Assert.That(gasPriceHighL1Fee, Is.GreaterThan(gasPriceLowL1Fee)); } [Test] - public async ValueTask GetGasPriceEstimate_WithZeroGasUsed_ReturnsAtLeastMinGasPrice() + public async ValueTask GetGasPriceEstimate_WithZeroGasUsed_UsesL2BlockGasTarget() { - Block headBlock = Build.A.Block.WithNumber(1).WithGasUsed(0).TestObject; - _blockFinder.Head.Returns(headBlock); + SetupBlockFinderWithBlocks(10, 0); + SetupInboxContractMocks(); var feeHistory = new L1FeeHistoryResults { @@ -169,25 +214,19 @@ public async ValueTask GetGasPriceEstimate_WithZeroGasUsed_ReturnsAtLeastMinGasP UInt256 gasPrice = await _gasPriceOracle.GetGasPriceEstimate(); - Assert.That(gasPrice, Is.GreaterThanOrEqualTo(MinGasPrice)); + Assert.That(gasPrice, Is.GreaterThan(UInt256.Zero)); } [Test] public async ValueTask GetGasPriceEstimate_WithCachedPrice_ReturnsCachedPrice() { - Block headBlock = Build.A.Block.WithNumber(1).WithGasUsed(1000000).TestObject; - _blockFinder.Head.Returns(headBlock); + SetupBlockFinderWithBlocks(10); + SetupInboxContractMocks(); var feeHistory = new L1FeeHistoryResults { - BaseFeePerGas = - [ - UInt256.Parse("20000000000") - ], - BaseFeePerBlobGas = - [ - UInt256.Parse("1000000000") - ] + BaseFeePerGas = [UInt256.Parse("20000000000")], + BaseFeePerBlobGas = [UInt256.Parse("1000000000")] }; _l1RpcClient.Post("eth_feeHistory", _surgeConfig.FeeHistoryBlockCount, BlockParameter.Latest, null) @@ -198,7 +237,6 @@ public async ValueTask GetGasPriceEstimate_WithCachedPrice_ReturnsCachedPrice() // Change the fee history to ensure we're using cache feeHistory.BaseFeePerGas[0] = UInt256.Parse("40000000000"); - // Second call should use cached value UInt256 secondGasPrice = await _gasPriceOracle.GetGasPriceEstimate(); Assert.That(secondGasPrice, Is.EqualTo(firstGasPrice)); @@ -207,90 +245,103 @@ public async ValueTask GetGasPriceEstimate_WithCachedPrice_ReturnsCachedPrice() [Test] public async ValueTask GetGasPriceEstimate_WithTimeout_RefreshesGasPrice() { - Block headBlock = Build.A.Block.WithNumber(1).WithGasUsed(1000000).TestObject; - _blockFinder.Head.Returns(headBlock); + SetupBlockFinderWithBlocks(10); + SetupInboxContractMocks(); var feeHistory = new L1FeeHistoryResults { - BaseFeePerGas = - [ - UInt256.Parse("20000000000") - ], - BaseFeePerBlobGas = - [ - UInt256.Parse("1000000000") - ] + BaseFeePerGas = [UInt256.Parse("20000000000")], + BaseFeePerBlobGas = [UInt256.Parse("1000000000")] }; _l1RpcClient.Post("eth_feeHistory", _surgeConfig.FeeHistoryBlockCount, BlockParameter.Latest, null) .Returns(Task.FromResult(feeHistory)); - // Mock Stats2 returned by getStats2() call to have 2 batches (numBatches=2) - var stats2Response = "0x" + CreatePaddedHex(2) + CreatePaddedHex(0, 192); - _l1RpcClient.Post("eth_call", Arg.Is(o => - o.ToString()!.ToLowerInvariant().Contains("0x26baca1c")), "latest") - .Returns(stats2Response); - - // Mock Batch returned by getBatch(1) call to have lastBlockId=1 - var batchResponse = "0x" + CreatePaddedHex(0) + CreatePaddedHex(1) + CreatePaddedHex(0, 576); - _l1RpcClient.Post("eth_call", Arg.Is(o => - o.ToString()!.ToLowerInvariant().Contains("0x888775d9")), "latest") - .Returns(batchResponse); - - // Mock block finder to return block with gas usage - _blockFinder.FindBlock(1, Arg.Any()) - .Returns(Build.A.Block.WithNumber(1).WithGasUsed(1000000).TestObject); - UInt256 firstGasPrice = await _gasPriceOracle.GetGasPriceEstimate(); - // Wait for the timeout to elapse await Task.Delay(_surgeConfig.GasPriceRefreshTimeoutSeconds * 1000 + 100); - // Change the fee history to a different value feeHistory.BaseFeePerGas[0] = UInt256.Parse("40000000000"); - // Second call should refresh due to timeout, even though head block hasn't changed UInt256 secondGasPrice = await _gasPriceOracle.GetGasPriceEstimate(); - // The gas price should be higher due to the changed L1 base fee Assert.That(secondGasPrice, Is.GreaterThan(firstGasPrice)); } [Test] - [Explicit("This test requires interacting with a live TaikoInbox contract")] - public async ValueTask GetGasPriceEstimate_WithLiveTaikoInboxContract_ReturnsValidGasPrice() + public async ValueTask GetGasPriceEstimate_WhenInboxBufferFull_UsesReducedProposalGas() { - // Create a real RPC client for L1 - var l1RpcClient = new BasicJsonRpcClient( - new Uri("https://eth.llamarpc.com"), - new EthereumJsonSerializer(), - _logManager); - - // Set up the block finder to return a valid block with gas usage for any block ID - _blockFinder.FindBlock(Arg.Any(), Arg.Any()) - .Returns(callInfo => Build.A.Block - .WithNumber(callInfo.Arg()) - .WithGasUsed(1000000) - .TestObject); - - // Create a gas price oracle with the live client - var liveGasPriceOracle = new SurgeGasPriceOracle( - _blockFinder, - _logManager, - _specProvider, - MinGasPrice, - l1RpcClient, - _surgeConfig); + // Create two separate oracles with different inbox buffer states + var feeHistory = new L1FeeHistoryResults + { + BaseFeePerGas = [UInt256.Parse("20000000000")], + BaseFeePerBlobGas = [UInt256.Parse("1000000000")] + }; - // Set up a head block with some gas used - Block headBlock = Build.A.Block.WithNumber(1).WithGasUsed(1000000).TestObject; - _blockFinder.Head.Returns(headBlock); + _l1RpcClient.Post("eth_feeHistory", _surgeConfig.FeeHistoryBlockCount, BlockParameter.Latest, null) + .Returns(Task.FromResult(feeHistory)); + + // First oracle: inbox buffer NOT full (uses FixedProposalGas = 75k) + SetupBlockFinderWithBlocks(10); + SetupInboxContractMocks(ringBufferSize: 100, nextProposalId: 50, lastFinalizedProposalId: 40); + + var oracleNotFull = new SurgeGasPriceOracle( + _blockFinder, _logManager, _specProvider, MinGasPrice, _l1RpcClient, _surgeConfig); + UInt256 gasPriceNotFull = await oracleNotFull.GetGasPriceEstimate(); + + // Second oracle: inbox buffer IS full (uses FixedProposalGasWithFullInboxBuffer = 50k) + SetupBlockFinderWithBlocks(11); + SetupInboxContractMocks(ringBufferSize: 100, nextProposalId: 150, lastFinalizedProposalId: 50); + + var oracleFull = new SurgeGasPriceOracle( + _blockFinder, _logManager, _specProvider, MinGasPrice, _l1RpcClient, _surgeConfig); + UInt256 gasPriceFull = await oracleFull.GetGasPriceEstimate(); + + // When buffer is full, lower proposal gas (50k vs 75k) should result in lower gas price + Assert.That(gasPriceFull, Is.LessThan(gasPriceNotFull)); + } + + [Test] + public async ValueTask GetGasPriceEstimate_ComputesAverageGasFromRecentBlocks() + { + var feeHistory = new L1FeeHistoryResults + { + BaseFeePerGas = [UInt256.Parse("20000000000")], + BaseFeePerBlobGas = [UInt256.Parse("1000000000")] + }; + + _l1RpcClient.Post("eth_feeHistory", _surgeConfig.FeeHistoryBlockCount, BlockParameter.Latest, null) + .Returns(Task.FromResult(feeHistory)); + + SetupInboxContractMocks(); + + // Scenario 1: Low gas usage blocks (average = 100k) + const long headBlockNumber1 = 10; + _blockFinder.Head.Returns(Build.A.Block.WithNumber(headBlockNumber1).WithGasUsed(100000).TestObject); + for (int i = 0; i < _surgeConfig.L2GasUsageWindowSize; i++) + { + _blockFinder.FindBlock(headBlockNumber1 - i, BlockTreeLookupOptions.RequireCanonical) + .Returns(Build.A.Block.WithNumber(headBlockNumber1 - i).WithGasUsed(100000).TestObject); + } + + var oracleLowGas = new SurgeGasPriceOracle( + _blockFinder, _logManager, _specProvider, MinGasPrice, _l1RpcClient, _surgeConfig); + UInt256 gasPriceLowUsage = await oracleLowGas.GetGasPriceEstimate(); + + // Scenario 2: High gas usage blocks (average = 500k) + const long headBlockNumber2 = 20; + _blockFinder.Head.Returns(Build.A.Block.WithNumber(headBlockNumber2).WithGasUsed(500000).TestObject); + for (int i = 0; i < _surgeConfig.L2GasUsageWindowSize; i++) + { + _blockFinder.FindBlock(headBlockNumber2 - i, BlockTreeLookupOptions.RequireCanonical) + .Returns(Build.A.Block.WithNumber(headBlockNumber2 - i).WithGasUsed(500000).TestObject); + } - // Get the gas price estimate - UInt256 gasPrice = await liveGasPriceOracle.GetGasPriceEstimate(); + var oracleHighGas = new SurgeGasPriceOracle( + _blockFinder, _logManager, _specProvider, MinGasPrice, _l1RpcClient, _surgeConfig); + UInt256 gasPriceHighUsage = await oracleHighGas.GetGasPriceEstimate(); - // Verify the gas price is valid - Assert.That(gasPrice, Is.GreaterThan(MinGasPrice)); - Assert.That(gasPrice, Is.LessThan(UInt256.Parse("100000000000"))); // Less than 100 Gwei + // Higher gas usage should result in lower gas price (cost spread over more gas) + Assert.That(gasPriceHighUsage, Is.LessThan(gasPriceLowUsage)); } } diff --git a/src/Nethermind/Nethermind.Taiko/Config/ISurgeConfig.cs b/src/Nethermind/Nethermind.Taiko/Config/ISurgeConfig.cs index 73ce005b672e..cf4a5ad45ed6 100644 --- a/src/Nethermind/Nethermind.Taiko/Config/ISurgeConfig.cs +++ b/src/Nethermind/Nethermind.Taiko/Config/ISurgeConfig.cs @@ -10,27 +10,33 @@ public interface ISurgeConfig : IConfig [ConfigItem(Description = "The URL of the L1 execution node JSON-RPC API.", DefaultValue = "null")] string? L1EthApiEndpoint { get; set; } - [ConfigItem(Description = "L2 gas per L2 batch for gas price calculation.", DefaultValue = "1000000")] - ulong L2GasPerL2Batch { get; set; } + [ConfigItem(Description = "Number of L2 blocks per batch.", DefaultValue = "1800")] + ulong BlocksPerBatch { get; set; } - [ConfigItem(Description = "Proving cost per L2 batch in wei.", DefaultValue = "800000000000000")] - ulong ProvingCostPerL2Batch { get; set; } + [ConfigItem(Description = "Target blob count per batch.", DefaultValue = "3")] + ulong TargetBlobCount { get; set; } - [ConfigItem(Description = "L1 gas needed for posting a batch as a blob.", DefaultValue = "180000")] - ulong BatchPostingGasWithoutCallData { get; set; } + [ConfigItem(Description = "Target gas per L2 block.", DefaultValue = "40000")] + ulong L2BlockGasTarget { get; set; } - [ConfigItem(Description = "L1 gas needed for posting a batch as calldata.", DefaultValue = "260000")] - ulong BatchPostingGasWithCallData { get; set; } + [ConfigItem(Description = "L1 gas for batch proposal.", DefaultValue = "75000")] + ulong FixedProposalGas { get; set; } - [ConfigItem(Description = "L1 gas needed to post and verify proof.", DefaultValue = "750000")] - ulong ProofPostingGas { get; set; } + [ConfigItem(Description = "L1 gas for batch proposal with full inbox buffer.", DefaultValue = "50000")] + ulong FixedProposalGasWithFullInboxBuffer { get; set; } + + [ConfigItem(Description = "L1 gas for proof verification.", DefaultValue = "30000")] + ulong FixedProvingGas { get; set; } [ConfigItem(Description = "Number of blocks to consider for computing the L1 average base fee.", DefaultValue = "200")] int FeeHistoryBlockCount { get; set; } - [ConfigItem(Description = "Number of recent L2 batches to consider for computing the moving average of gas usage.", DefaultValue = "20")] + [ConfigItem(Description = "Number of recent L2 blocks to consider for computing the moving average of gas usage.", DefaultValue = "20")] int L2GasUsageWindowSize { get; set; } + [ConfigItem(Description = "Estimated offchain proving cost per batch in wei (~$5.5 @ $3000/ETH).", DefaultValue = "1833333333333333")] + ulong EstimatedOffchainProvingCost { get; set; } + [ConfigItem(Description = "The address of the TaikoInbox contract.", DefaultValue = "null")] string? TaikoInboxAddress { get; set; } diff --git a/src/Nethermind/Nethermind.Taiko/Config/SurgeConfig.cs b/src/Nethermind/Nethermind.Taiko/Config/SurgeConfig.cs index ee48b4d0d8d1..c1ef0ebc7353 100644 --- a/src/Nethermind/Nethermind.Taiko/Config/SurgeConfig.cs +++ b/src/Nethermind/Nethermind.Taiko/Config/SurgeConfig.cs @@ -6,13 +6,15 @@ namespace Nethermind.Taiko.Config; public class SurgeConfig : ISurgeConfig { public string? L1EthApiEndpoint { get; set; } - public ulong L2GasPerL2Batch { get; set; } = 1_000_000; - public ulong ProvingCostPerL2Batch { get; set; } = 800_000_000_000_000; - public ulong BatchPostingGasWithoutCallData { get; set; } = 180_000; - public ulong BatchPostingGasWithCallData { get; set; } = 260_000; - public ulong ProofPostingGas { get; set; } = 750_000; + public ulong BlocksPerBatch { get; set; } = 1800; + public ulong TargetBlobCount { get; set; } = 3; + public ulong L2BlockGasTarget { get; set; } = 40_000; + public ulong FixedProposalGas { get; set; } = 75_000; + public ulong FixedProposalGasWithFullInboxBuffer { get; set; } = 50_000; + public ulong FixedProvingGas { get; set; } = 30_000; public int FeeHistoryBlockCount { get; set; } = 200; public int L2GasUsageWindowSize { get; set; } = 20; + public ulong EstimatedOffchainProvingCost { get; set; } = 1_833_333_333_333_333; public string? TaikoInboxAddress { get; set; } public int AverageGasUsagePercentage { get; set; } = 80; public int SharingPercentage { get; set; } = 75; diff --git a/src/Nethermind/Nethermind.Taiko/L1OriginDecoder.cs b/src/Nethermind/Nethermind.Taiko/L1OriginDecoder.cs index 9e57c56f940a..329a766b3b7a 100644 --- a/src/Nethermind/Nethermind.Taiko/L1OriginDecoder.cs +++ b/src/Nethermind/Nethermind.Taiko/L1OriginDecoder.cs @@ -49,7 +49,7 @@ public Rlp Encode(L1Origin? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) public override void Encode(RlpStream stream, L1Origin item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { - stream.StartSequence(GetLength(item, rlpBehaviors)); + stream.StartSequence(GetContentLength(item, rlpBehaviors)); stream.Encode(item.BlockId); stream.Encode(item.L2BlockHash); @@ -92,6 +92,11 @@ public override void Encode(RlpStream stream, L1Origin item, RlpBehaviors rlpBeh } public override int GetLength(L1Origin item, RlpBehaviors rlpBehaviors) + { + return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); + } + + private int GetContentLength(L1Origin item, RlpBehaviors rlpBehaviors) { int buildPayloadLength = 0; if (item.BuildPayloadArgsId is not null || item.IsForcedInclusion || item.Signature is not null) @@ -107,14 +112,12 @@ public override int GetLength(L1Origin item, RlpBehaviors rlpBehaviors) isForcedInclusionLength = Rlp.LengthOf(item.IsForcedInclusion); } - return Rlp.LengthOfSequence( - Rlp.LengthOf(item.BlockId) + return Rlp.LengthOf(item.BlockId) + Rlp.LengthOf(item.L2BlockHash) + Rlp.LengthOf(item.L1BlockHeight ?? 0) + Rlp.LengthOf(item.L1BlockHash) + buildPayloadLength + isForcedInclusionLength - + (item.Signature is null ? 0 : Rlp.LengthOfByteString(SignatureLength, 0)) - ); + + (item.Signature is null ? 0 : Rlp.LengthOfByteString(SignatureLength, 0)); } } diff --git a/src/Nethermind/Nethermind.Taiko/Rpc/SurgeGasPriceOracle.cs b/src/Nethermind/Nethermind.Taiko/Rpc/SurgeGasPriceOracle.cs index 00319c633076..faa079752d87 100644 --- a/src/Nethermind/Nethermind.Taiko/Rpc/SurgeGasPriceOracle.cs +++ b/src/Nethermind/Nethermind.Taiko/Rpc/SurgeGasPriceOracle.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; +using Nethermind.Abi; using Nethermind.Blockchain; using Nethermind.Blockchain.Find; using Nethermind.Core; @@ -15,26 +16,29 @@ using Nethermind.JsonRpc.Modules.Eth.GasPrice; using Nethermind.Logging; using Nethermind.Taiko.Config; -using Nethermind.Abi; namespace Nethermind.Taiko.Rpc; public class SurgeGasPriceOracle : GasPriceOracle { private const string ClassName = nameof(SurgeGasPriceOracle); - private const int BlobSize = (4 * 31 + 3) * 1024 - 4; + private const ulong BlobGasPerBlob = Eip4844Constants.GasPerBlob; - // ABI signatures and encoded function selectors for TaikoInbox - private static readonly AbiSignature GetStats2Signature = new("getStats2"); - private static readonly AbiSignature GetBatchSignature = new("getBatch", AbiType.UInt64); - private static readonly string GetStats2HexData = "0x" + Convert.ToHexString( - AbiEncoder.Instance.Encode(AbiEncodingStyle.IncludeSignature, GetStats2Signature)); + // ABI signatures and encoded function selectors for TaikoInbox contract. + private static readonly AbiSignature GetCoreStateSignature = new("getCoreState"); + private static readonly AbiSignature GetConfigSignature = new("getConfig"); + private static readonly string GetCoreStateHexData = "0x" + Convert.ToHexString( + AbiEncoder.Instance.Encode(AbiEncodingStyle.IncludeSignature, GetCoreStateSignature)); + private static readonly string GetConfigHexData = "0x" + Convert.ToHexString( + AbiEncoder.Instance.Encode(AbiEncodingStyle.IncludeSignature, GetConfigSignature)); private readonly IJsonRpcClient _l1RpcClient; private readonly ISurgeConfig _surgeConfig; - private readonly GasUsageRingBuffer _gasUsageBuffer; private DateTime _lastGasPriceCalculation = DateTime.MinValue; + private ulong _cachedAverageGasUsage; + private ulong _inboxRingBufferSize; + private bool _isInboxRingBufferFull; public SurgeGasPriceOracle( IBlockFinder blockFinder, @@ -46,8 +50,14 @@ public SurgeGasPriceOracle( { _l1RpcClient = l1RpcClient; _surgeConfig = surgeConfig; - _gasUsageBuffer = new GasUsageRingBuffer(_surgeConfig.L2GasUsageWindowSize); - _gasUsageBuffer.Add(_surgeConfig.L2GasPerL2Batch); + _cachedAverageGasUsage = surgeConfig.L2BlockGasTarget; + + if (_logger.IsInfo) + { + _logger.Info($"[{ClassName}] Initialized with L1 endpoint: {surgeConfig.L1EthApiEndpoint}, " + + $"TaikoInbox: {surgeConfig.TaikoInboxAddress}, L2BlockGasTarget: {surgeConfig.L2BlockGasTarget}, " + + $"MinGasPrice: {minGasPrice}"); + } } private UInt256 FallbackGasPrice() => _gasPriceEstimation.LastPrice ?? _minGasPrice; @@ -63,7 +73,7 @@ public override async ValueTask GetGasPriceEstimate() Hash256 headBlockHash = headBlock.Hash!; bool forceRefresh = ForceRefreshGasPrice(); - ulong averageGasUsage; + ulong averageGasUsage = _cachedAverageGasUsage; // Check if the cached price exists. if (_gasPriceEstimation.TryGetPrice(headBlockHash, out UInt256? price)) @@ -74,13 +84,18 @@ public override async ValueTask GetGasPriceEstimate() if (_logger.IsTrace) _logger.Trace($"[{ClassName}] Using cached gas price estimate: {price}"); return price!.Value; } - - // Since the head block has not changed, we can reuse the existing average gas usage (even with force refresh) - averageGasUsage = _gasUsageBuffer.Average; } else { - averageGasUsage = await GetAverageGasUsageAcrossBatches(); + // Since the head block has changed, we need to re-compute the average gas usage and + // update the inbox ring buffer full status + _cachedAverageGasUsage = GetAverageGasUsagePerBlock(); + averageGasUsage = _cachedAverageGasUsage; + + if (!_isInboxRingBufferFull) + { + await UpdateInboxRingBufferFullAsync(); + } } // Get the fee history from the L1 client with RPC @@ -91,23 +106,30 @@ public override async ValueTask GetGasPriceEstimate() return FallbackGasPrice(); } - // Get the latest base fee and blob base fee from the fee history - UInt256 l1BaseFee = feeHistory.BaseFeePerGas[^1]; - UInt256 l1BlobBaseFee = feeHistory.BaseFeePerBlobGas.Length > 0 ? feeHistory.BaseFeePerBlobGas[^1] : UInt256.Zero; - UInt256 l1AverageBaseFee = (UInt256)feeHistory.BaseFeePerGas.Average(fee => (decimal)fee); + // Compute TWAP for L1 base fee and blob base fee + UInt256 twapL1BaseFee = (UInt256)feeHistory.BaseFeePerGas.Average(fee => (decimal)fee); + UInt256 twapBlobBaseFee = feeHistory.BaseFeePerBlobGas.Length > 0 + ? (UInt256)feeHistory.BaseFeePerBlobGas.Average(fee => (decimal)fee) + : UInt256.Zero; - // Compute the gas cost to post a batch on L1 - UInt256 costWithCallData = _surgeConfig.BatchPostingGasWithCallData * l1BaseFee; - UInt256 costWithBlobs = _surgeConfig.BatchPostingGasWithoutCallData * l1BaseFee + BlobSize * l1BlobBaseFee; - UInt256 minProposingCost = UInt256.Min(costWithCallData, costWithBlobs); + // Based on the inbox ring buffer status, use the appropriate fixed proposal gas. + UInt256 gasRequiredForProposal = _isInboxRingBufferFull ? + _surgeConfig.FixedProposalGasWithFullInboxBuffer + : _surgeConfig.FixedProposalGas; - UInt256 proofPostingCost = _surgeConfig.ProofPostingGas * UInt256.Max(l1BaseFee, l1AverageBaseFee); + // Compute submission cost per batch + UInt256 submissionCostPerBatch = (gasRequiredForProposal + _surgeConfig.FixedProvingGas) * twapL1BaseFee + + _surgeConfig.TargetBlobCount * BlobGasPerBlob * twapBlobBaseFee + + _surgeConfig.EstimatedOffchainProvingCost; + + // Compute submission cost per block + UInt256 submissionCostPerBlock = submissionCostPerBatch / _surgeConfig.BlocksPerBatch; // Reduce the average gas usage to prevent upward trend averageGasUsage = averageGasUsage * (ulong)_surgeConfig.AverageGasUsagePercentage / 100; - UInt256 gasPriceEstimate = (minProposingCost + proofPostingCost + _surgeConfig.ProvingCostPerL2Batch) / - Math.Max(averageGasUsage, _surgeConfig.L2GasPerL2Batch); + UInt256 gasPriceEstimate = submissionCostPerBlock / + Math.Max(averageGasUsage, _surgeConfig.L2BlockGasTarget); // Adjust the gas price estimate with the config values. UInt256 adjustedGasPriceEstimate; @@ -120,6 +142,7 @@ public override async ValueTask GetGasPriceEstimate() adjustedGasPriceEstimate = gasPriceEstimate + gasPriceEstimate * (UInt256)_surgeConfig.BoostBaseFeePercentage / 100; adjustedGasPriceEstimate = adjustedGasPriceEstimate * 100 / (UInt256)_surgeConfig.SharingPercentage; } + // Update the cache and timestamp _gasPriceEstimation.Set(headBlockHash, adjustedGasPriceEstimate); _lastGasPriceCalculation = DateTime.UtcNow; @@ -127,8 +150,8 @@ public override async ValueTask GetGasPriceEstimate() if (_logger.IsDebug) { _logger.Debug($"[{ClassName}] Calculated new gas price estimate: {adjustedGasPriceEstimate}, " + - $"L1 Base Fee: {l1BaseFee}, L1 Blob Base Fee: {l1BlobBaseFee}, " + - $"L1 Average Base Fee: {l1AverageBaseFee}, Average Gas Usage: {averageGasUsage}, " + + $"TWAP L1 Base Fee: {twapL1BaseFee}, TWAP Blob Base Fee: {twapBlobBaseFee}, " + + $"Submission Cost Per Block: {submissionCostPerBlock}, Average Gas Usage: {averageGasUsage}, " + $"Adjusted with boost base fee percentage of {_surgeConfig.BoostBaseFeePercentage}% " + $"and sharing percentage of {_surgeConfig.SharingPercentage}%"); } @@ -161,83 +184,103 @@ private bool ForceRefreshGasPrice() } /// - /// Get the average gas usage across L2GasUsageWindowSize batches. - /// It uses the TaikoInbox contract to get the total number of blocks in the latest proposed batch. + /// Get the average gas usage per block across L2GasUsageWindowSize recent blocks. /// - private async ValueTask GetAverageGasUsageAcrossBatches() + private ulong GetAverageGasUsagePerBlock() { - // Get the current batch information - ulong? numBatches = await GetNumBatches(); - if (numBatches is null or < 1) - { - if (_logger.IsTrace) _logger.Trace($"[{ClassName}] Failed to get numBatches"); - return 0; - } - - // Get the latest proposed batch and the previous batch to compute the start and end block ids - ulong? currentBatchLastBlockId = numBatches > 1 ? await GetLastBlockId(numBatches.Value - 1) : 0; - ulong? previousBatchLastBlockId = numBatches > 2 ? await GetLastBlockId(numBatches.Value - 2) : 0; - - if (currentBatchLastBlockId == null || previousBatchLastBlockId == null) + Block? headBlock = _blockFinder.Head; + if (headBlock is null) { - if (_logger.IsTrace) _logger.Trace($"[{ClassName}] Failed to get batch lastBlockId"); - return 0; + if (_logger.IsTrace) _logger.Trace($"[{ClassName}] No head block available for gas usage tracking"); + return _surgeConfig.L2BlockGasTarget; } - ulong startBlockId = previousBatchLastBlockId.Value == 0 ? 0 : previousBatchLastBlockId.Value + 1; - ulong endBlockId = currentBatchLastBlockId.Value; - - // Calculate total gas used for the batch ulong totalGasUsed = 0; - for (ulong blockId = startBlockId; blockId <= endBlockId; blockId++) + int count = 0; + long currentBlockNumber = headBlock.Number; + + while (count < _surgeConfig.L2GasUsageWindowSize && currentBlockNumber >= 0) { - Block? block = _blockFinder.FindBlock((long)blockId, BlockTreeLookupOptions.RequireCanonical); + Block? block = _blockFinder.FindBlock(currentBlockNumber, BlockTreeLookupOptions.RequireCanonical); if (block != null) { totalGasUsed += (ulong)block.GasUsed; + count++; } + currentBlockNumber--; } - // Record the batch's gas usage and compute the average - _gasUsageBuffer.Add(totalGasUsed); + ulong average = totalGasUsed > 0 ? totalGasUsed / (ulong)count : _surgeConfig.L2BlockGasTarget; if (_logger.IsTrace) { - _logger.Trace($"[{ClassName}] Total gas used: {totalGasUsed}, " + - $"startBlockId: {startBlockId}, endBlockId: {endBlockId}, " + - $"Average gas usage: {_gasUsageBuffer.Average}, " + - $"L2GasUsageWindowSize: {_surgeConfig.L2GasUsageWindowSize}, " + - $"numBatches: {numBatches}"); + _logger.Trace($"[{ClassName}] Average gas usage: {average}, " + + $"Head block: {headBlock.Number}, Blocks sampled: {count}"); + } + + return average; + } + + /// + /// Update the inbox ring buffer full status. + /// Ring buffer is full when: nextProposalId - lastFinalizedProposalId >= ringBufferSize + /// + private async ValueTask UpdateInboxRingBufferFullAsync() + { + if (_inboxRingBufferSize == 0) + { + ulong? ringBufferSize = await GetInboxRingBufferSize(); + if (ringBufferSize is null) return; + _inboxRingBufferSize = ringBufferSize.Value; } - return _gasUsageBuffer.Average; + ulong? unfinalizedCount = await GetUnfinalizedProposalCount(); + if (unfinalizedCount is null) return; + + _isInboxRingBufferFull = unfinalizedCount.Value >= _inboxRingBufferSize; } /// - /// Get the number of batches from the TaikoInbox contract's getStats2() function. + /// Get ringBufferSize from the TaikoInbox contract's getConfig() function. + /// Config struct has ringBufferSize at word index 10 (0-indexed). /// - private async ValueTask GetNumBatches() + private async ValueTask GetInboxRingBufferSize() { - string? response = await CallTaikoInboxFunction(GetStats2HexData); + string? response = await CallTaikoInboxFunction(GetConfigHexData); - if (string.IsNullOrEmpty(response) || response.Length < 66) return null; + // ringBufferSize is at word 10: offset = 2 + 10*64 = 642, length = 64 + if (string.IsNullOrEmpty(response) || response.Length < 706) + { + if (_logger.IsDebug) _logger.Debug($"[{ClassName}] Failed to get config, response length: {response?.Length}"); + return null; + } - // Extract the first 32 bytes (64 hex chars) after "0x" which contains NumBatches - return ulong.Parse(response[2..66], NumberStyles.HexNumber); + return ulong.Parse(response[642..706], NumberStyles.HexNumber); } /// - /// Get the last block id from the TaikoInbox contract's getBatch(uint64) function. + /// Get the count of unfinalized proposals (nextProposalId - lastFinalizedProposalId) from getCoreState(). + /// CoreState struct layout: + /// Word 0: nextProposalId (uint48) + /// Word 2: lastFinalizedProposalId (uint48) /// - private async ValueTask GetLastBlockId(ulong batchId) + private async ValueTask GetUnfinalizedProposalCount() { - byte[] encodedData = AbiEncoder.Instance.Encode(AbiEncodingStyle.IncludeSignature, GetBatchSignature, batchId); - string? response = await CallTaikoInboxFunction("0x" + Convert.ToHexString(encodedData)); + string? response = await CallTaikoInboxFunction(GetCoreStateHexData); + + // Need at least 3 fields: 2 + 3*64 = 194 chars + if (string.IsNullOrEmpty(response) || response.Length < 194) + { + if (_logger.IsDebug) _logger.Debug($"[{ClassName}] Failed to get core state, response length: {response?.Length}"); + return null; + } - if (string.IsNullOrEmpty(response) || response.Length < 130) return null; + // nextProposalId at [2..66] + ulong nextProposalId = ulong.Parse(response[2..66], NumberStyles.HexNumber); + // lastFinalizedProposalId at [130..194] + ulong lastFinalizedProposalId = ulong.Parse(response[130..194], NumberStyles.HexNumber); - // Extract the second 32 bytes (64 hex chars) after "0x" which contains LastBlockId - return ulong.Parse(response[66..130], NumberStyles.HexNumber); + return nextProposalId - lastFinalizedProposalId; } /// @@ -255,47 +298,11 @@ private async ValueTask GetAverageGasUsageAcrossBatches() } catch (Exception ex) { - if (_logger.IsTrace) _logger.Trace($"[{ClassName}] Contract call to TaikoInbox with data: {data} failed: {ex.Message}"); + if (_logger.IsDebug) _logger.Debug($"[{ClassName}] Contract call to TaikoInbox with data: {data} failed: {ex.Message}"); return null; } } - /// - /// A fixed-size ring buffer for tracking gas usage and computing moving averages. - /// - /// +----+----+----+----+ - /// | A | B | C | D | - /// +----+----+----+----+ - /// ^ - /// insertAt = 1 (next insert location) - /// average = (A + B + C + D) / 4 - /// - private sealed class GasUsageRingBuffer(int capacity) - { - private readonly ulong[] _buffer = new ulong[capacity]; - private int _insertAt; - private int _numItems; - private ulong _sum; - - public ulong Average => _numItems == 0 ? 0 : _sum / (ulong)_numItems; - - public void Add(ulong gasUsed) - { - // If the buffer is full, overwrite the oldest value - if (_numItems == _buffer.Length) - { - _sum -= _buffer[_insertAt]; - } - else - { - _numItems++; - } - - _buffer[_insertAt] = gasUsed; - _sum += _buffer[_insertAt]; - _insertAt = (_insertAt + 1) % _buffer.Length; - } - } } /// diff --git a/src/Nethermind/Nethermind.Taiko/Tdx/TdxsClient.cs b/src/Nethermind/Nethermind.Taiko/Tdx/TdxsClient.cs index 081e33262199..999fabaa0f41 100644 --- a/src/Nethermind/Nethermind.Taiko/Tdx/TdxsClient.cs +++ b/src/Nethermind/Nethermind.Taiko/Tdx/TdxsClient.cs @@ -6,7 +6,6 @@ using System.Net.Sockets; using System.Text; using System.Text.Json; -using Nethermind.Logging; namespace Nethermind.Taiko.Tdx; @@ -14,9 +13,8 @@ namespace Nethermind.Taiko.Tdx; /// Client for communicating with the tdxs daemon via Unix socket. /// Protocol: JSON request/response over Unix socket. /// -public class TdxsClient(ISurgeTdxConfig config, ILogManager logManager) : ITdxsClient +public class TdxsClient(ISurgeTdxConfig config) : ITdxsClient { - private readonly ILogger _logger = logManager.GetClassLogger(); public byte[] Issue(byte[] userData, byte[] nonce) { diff --git a/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs b/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs index f332f113489c..bc89ff38ecff 100644 --- a/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs +++ b/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs @@ -10,6 +10,7 @@ using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; +using Nethermind.Evm.CodeAnalysis; namespace Nethermind.Test.Runner; @@ -54,7 +55,7 @@ public void StartOperation(int pc, Instruction opcode, long gas, in ExecutionEnv { _gasAlreadySetForCurrentOp = false; _traceEntry = new StateTestTxTraceEntry(); - _traceEntry.Pc = pc + env.CodeInfo.PcOffset(); + _traceEntry.Pc = pc + (env.CodeInfo is EofCodeInfo eofCodeInfo ? eofCodeInfo.PcOffset() : 0); _traceEntry.Section = codeSection; _traceEntry.Operation = (byte)opcode; _traceEntry.OperationName = opcode.GetName(); diff --git a/src/Nethermind/Nethermind.Trie.Test/CacheTests.cs b/src/Nethermind/Nethermind.Trie.Test/CacheTests.cs index 8891482ff7f4..02f361be5b42 100644 --- a/src/Nethermind/Nethermind.Trie.Test/CacheTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/CacheTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Trie.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] public class CacheTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs b/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs index bf35cbb1232d..c8a8bc507a07 100644 --- a/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs @@ -7,6 +7,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class HexPrefixTests { [TestCase(false, (byte)3, (byte)19)] diff --git a/src/Nethermind/Nethermind.Trie.Test/NibbleTests.cs b/src/Nethermind/Nethermind.Trie.Test/NibbleTests.cs index 1338eafa0183..a575cb2ddbbd 100644 --- a/src/Nethermind/Nethermind.Trie.Test/NibbleTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/NibbleTests.cs @@ -6,6 +6,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class NibbleTests { private readonly byte[][] _hexEncoding = diff --git a/src/Nethermind/Nethermind.Trie.Test/NodeStorageFactoryTests.cs b/src/Nethermind/Nethermind.Trie.Test/NodeStorageFactoryTests.cs index 5e0308ea2a5c..740048565d78 100644 --- a/src/Nethermind/Nethermind.Trie.Test/NodeStorageFactoryTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/NodeStorageFactoryTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class NodeStorageFactoryTests { [TestCase(INodeStorage.KeyScheme.Hash)] diff --git a/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs b/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs index 51702876eec0..c212d15a8e8e 100644 --- a/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs @@ -13,31 +13,25 @@ namespace Nethermind.Trie.Test; [TestFixture(INodeStorage.KeyScheme.Hash)] [TestFixture(INodeStorage.KeyScheme.HalfPath)] -public class NodeStorageTests +[Parallelizable(ParallelScope.All)] +public class NodeStorageTests(INodeStorage.KeyScheme currentKeyScheme) { - private readonly INodeStorage.KeyScheme _currentKeyScheme; - - public NodeStorageTests(INodeStorage.KeyScheme currentKeyScheme) - { - _currentKeyScheme = currentKeyScheme; - } - [Test] public void Should_StoreAndRead() { TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); nodeStorage.KeyExists(null, TreePath.Empty, TestItem.KeccakA).Should().BeFalse(); nodeStorage.Set(null, TreePath.Empty, TestItem.KeccakA, TestItem.KeccakA.BytesToArray()); nodeStorage.Get(null, TreePath.Empty, TestItem.KeccakA).Should().BeEquivalentTo(TestItem.KeccakA.BytesToArray()); nodeStorage.KeyExists(null, TreePath.Empty, TestItem.KeccakA).Should().BeTrue(); - if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) + if (currentKeyScheme == INodeStorage.KeyScheme.Hash) { testDb[TestItem.KeccakA.Bytes].Should().NotBeNull(); } - else if (_currentKeyScheme == INodeStorage.KeyScheme.HalfPath) + else if (currentKeyScheme == INodeStorage.KeyScheme.HalfPath) { testDb[NodeStorage.GetHalfPathNodeStoragePath(null, TreePath.Empty, TestItem.KeccakA)].Should().NotBeNull(); } @@ -47,18 +41,18 @@ public void Should_StoreAndRead() public void Should_StoreAndRead_WithStorage() { TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); nodeStorage.KeyExists(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA).Should().BeFalse(); nodeStorage.Set(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA, TestItem.KeccakA.BytesToArray()); nodeStorage.Get(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA).Should().BeEquivalentTo(TestItem.KeccakA.BytesToArray()); nodeStorage.KeyExists(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA).Should().BeTrue(); - if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) + if (currentKeyScheme == INodeStorage.KeyScheme.Hash) { testDb[TestItem.KeccakA.Bytes].Should().NotBeNull(); } - else if (_currentKeyScheme == INodeStorage.KeyScheme.HalfPath) + else if (currentKeyScheme == INodeStorage.KeyScheme.HalfPath) { testDb[NodeStorage.GetHalfPathNodeStoragePath(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA)].Should().NotBeNull(); } @@ -68,7 +62,7 @@ public void Should_StoreAndRead_WithStorage() public void When_KeyNotExist_Should_TryBothKeyType() { TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); nodeStorage.Get(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA).Should().BeNull(); @@ -80,9 +74,9 @@ public void When_KeyNotExist_Should_TryBothKeyType() public void When_EntryOfDifferentScheme_Should_StillBeAbleToRead() { TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); - if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) + if (currentKeyScheme == INodeStorage.KeyScheme.Hash) { testDb[NodeStorage.GetHalfPathNodeStoragePath(TestItem.KeccakB, TreePath.Empty, TestItem.KeccakA)] = TestItem.KeccakA.BytesToArray(); @@ -109,7 +103,7 @@ public void When_EntryOfDifferentScheme_Should_StillBeAbleToRead() [TestCase(true, 32, "0211111111111111111111111111111111111111111111111111111111111111112222222222222222203333333333333333333333333333333333333333333333333333333333333333")] public void Test_HalfPathEncoding(bool hasAddress, int pathLength, string expectedKey) { - if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) return; + if (currentKeyScheme == INodeStorage.KeyScheme.Hash) return; Hash256? address = null; if (hasAddress) @@ -130,10 +124,10 @@ public void Test_HalfPathEncoding(bool hasAddress, int pathLength, string expect [TestCase(true, 3, ReadFlags.HintReadAhead | ReadFlags.HintReadAhead3)] public void Test_WhenReadaheadUseDifferentReadaheadOnDifferentSection(bool hasAddress, int pathLength, ReadFlags expectedReadFlags) { - if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) return; + if (currentKeyScheme == INodeStorage.KeyScheme.Hash) return; TestMemDb testDb = new TestMemDb(); - NodeStorage nodeStorage = new NodeStorage(testDb, _currentKeyScheme); + NodeStorage nodeStorage = new NodeStorage(testDb, currentKeyScheme); Hash256? address = null; if (hasAddress) diff --git a/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs index ed8c23ee2640..2c70a4944949 100644 --- a/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs @@ -15,6 +15,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class OverlayTrieStoreTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/Pruning/MaxBlockInCachePruneStrategyTests.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/MaxBlockInCachePruneStrategyTests.cs index f288a30f6e0e..93c61e44ed48 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/MaxBlockInCachePruneStrategyTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/MaxBlockInCachePruneStrategyTests.cs @@ -8,7 +8,8 @@ namespace Nethermind.Trie.Test.Pruning { [TestFixture] - [Parallelizable(ParallelScope.Self)] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class MaxBlockInCachePruneStrategyTests { private IPruningStrategy _baseStrategy; diff --git a/src/Nethermind/Nethermind.Trie.Test/Pruning/MinBlockInCachePruneStrategyTests.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/MinBlockInCachePruneStrategyTests.cs index 27814dd58fce..63930144e889 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/MinBlockInCachePruneStrategyTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/MinBlockInCachePruneStrategyTests.cs @@ -9,7 +9,8 @@ namespace Nethermind.Trie.Test.Pruning { [TestFixture] - [Parallelizable(ParallelScope.Self)] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class MinBlockInCachePruneStrategyTests { private IPruningStrategy _baseStrategy; diff --git a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs index 2a98ac0b6aa9..b75849af8999 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs @@ -28,18 +28,13 @@ namespace Nethermind.Trie.Test.Pruning { [TestFixture(INodeStorage.KeyScheme.HalfPath)] [TestFixture(INodeStorage.KeyScheme.Hash)] - public class TreeStoreTests + [Parallelizable(ParallelScope.All)] + public class TreeStoreTests(INodeStorage.KeyScheme scheme) { private readonly ILogManager _logManager = LimboLogs.Instance; // new OneLoggerLogManager(new NUnitLogger(LogLevel.Trace)); private readonly AccountDecoder _accountDecoder = new(); - private readonly INodeStorage.KeyScheme _scheme; - - public TreeStoreTests(INodeStorage.KeyScheme scheme) - { - _scheme = scheme; - } private TrieStore CreateTrieStore( IPruningStrategy? pruningStrategy = null, @@ -59,7 +54,7 @@ private TrieStore CreateTrieStore( finalizedStateProvider ??= new TestFinalizedStateProvider(pruningConfig.PruningBoundary); TrieStore trieStore = new( - new NodeStorage(kvStore, _scheme, requirePath: _scheme == INodeStorage.KeyScheme.HalfPath), + new NodeStorage(kvStore, scheme, requirePath: scheme == INodeStorage.KeyScheme.HalfPath), pruningStrategy, persistenceStrategy, finalizedStateProvider, @@ -148,7 +143,7 @@ public void Pruning_off_cache_should_change_commit_node() committer.CommitNode(ref emptyPath, trieNode3); } fullTrieStore.WaitForPruning(); - fullTrieStore.MemoryUsedByDirtyCache.Should().Be(_scheme == INodeStorage.KeyScheme.HalfPath ? 832 : 676); + fullTrieStore.MemoryUsedByDirtyCache.Should().Be(scheme == INodeStorage.KeyScheme.HalfPath ? 832 : 676); } [Test] @@ -177,7 +172,7 @@ public void Pruning_off_cache_should_find_cached_or_unknown() Assert.That(returnedNode2.NodeType, Is.EqualTo(NodeType.Unknown)); Assert.That(returnedNode3.NodeType, Is.EqualTo(NodeType.Unknown)); trieStore.WaitForPruning(); - trieStore.MemoryUsedByDirtyCache.Should().Be(_scheme == INodeStorage.KeyScheme.HalfPath ? 552 : 396); + trieStore.MemoryUsedByDirtyCache.Should().Be(scheme == INodeStorage.KeyScheme.HalfPath ? 552 : 396); } [Test] @@ -242,7 +237,7 @@ public void Memory_with_concurrent_commits_is_correct() tree.Commit(); } - fullTrieStore.MemoryUsedByDirtyCache.Should().Be(_scheme == INodeStorage.KeyScheme.Hash ? 545956 : 616104L); + fullTrieStore.MemoryUsedByDirtyCache.Should().Be(scheme == INodeStorage.KeyScheme.Hash ? 545956 : 616104L); fullTrieStore.CommittedNodesCount.Should().Be(1349); } @@ -871,7 +866,7 @@ public void ReadOnly_store_returns_copies(bool pruning) readOnlyNode.Key?.ToString().Should().Be(originalNode.Key?.ToString()); } - private long ExpectedPerNodeKeyMemorySize => (_scheme == INodeStorage.KeyScheme.Hash ? 0 : TrieStoreDirtyNodesCache.Key.MemoryUsage) + MemorySizes.ObjectHeaderMethodTable + MemorySizes.RefSize + 4 + MemorySizes.RefSize; + private long ExpectedPerNodeKeyMemorySize => (scheme == INodeStorage.KeyScheme.Hash ? 0 : TrieStoreDirtyNodesCache.Key.MemoryUsage) + MemorySizes.ObjectHeaderMethodTable + MemorySizes.RefSize + 4 + MemorySizes.RefSize; [Test] public void After_commit_should_have_has_root() @@ -902,6 +897,7 @@ public void After_commit_should_have_has_root() [Test] [Retry(3)] + [NonParallelizable] public async Task Will_RemovePastKeys_OnSnapshot() { MemDb memDb = new(); @@ -930,7 +926,7 @@ public async Task Will_RemovePastKeys_OnSnapshot() await Task.Delay(TimeSpan.FromMilliseconds(10)); } - if (_scheme == INodeStorage.KeyScheme.Hash) + if (scheme == INodeStorage.KeyScheme.Hash) { memDb.Count.Should().NotBe(1); } @@ -941,6 +937,8 @@ public async Task Will_RemovePastKeys_OnSnapshot() } [Test] + [Retry(3)] + [NonParallelizable] public async Task Will_Trigger_ReorgBoundaryEvent_On_Prune() { // TODO: Check why slow @@ -1021,7 +1019,7 @@ public void When_SomeKindOfNonResolvedNotInMainWorldState_OnPrune_DoNotDeleteNod Address address = TestItem.AddressA; UInt256 slot = 1; - INodeStorage nodeStorage = new NodeStorage(memDbProvider.StateDb, _scheme); + INodeStorage nodeStorage = new NodeStorage(memDbProvider.StateDb, scheme); (Hash256 stateRoot, ValueHash256 storageRoot) = SetupStartingState(); nodeStorage.Get(address.ToAccountPath.ToCommitment(), TreePath.Empty, storageRoot).Should().NotBeNull(); @@ -1104,7 +1102,7 @@ public Task When_Prune_ClearRecommittedPersistedNode() } memDb.Count.Should().Be(1); - fullTrieStore.MemoryUsedByDirtyCache.Should().Be(_scheme == INodeStorage.KeyScheme.Hash ? 12032 : 15360); + fullTrieStore.MemoryUsedByDirtyCache.Should().Be(scheme == INodeStorage.KeyScheme.Hash ? 12032 : 15360); fullTrieStore.PersistCache(default); memDb.Count.Should().Be(64); @@ -1145,7 +1143,7 @@ public void OnDispose_PersistAtLeastOneCommitSet() [Test] public void Will_NotPruneTopLevelNode() { - if (_scheme == INodeStorage.KeyScheme.Hash) Assert.Ignore("Not applicable for hash"); + if (scheme == INodeStorage.KeyScheme.Hash) Assert.Ignore("Not applicable for hash"); MemDb memDb = new(); TestPruningStrategy testPruningStrategy = new TestPruningStrategy( @@ -1224,7 +1222,7 @@ void WriteRandomData(int seed) [Test] public void Can_Prune_StorageTreeRoot() { - if (_scheme == INodeStorage.KeyScheme.Hash) Assert.Ignore("Not applicable for hash"); + if (scheme == INodeStorage.KeyScheme.Hash) Assert.Ignore("Not applicable for hash"); MemDb memDb = new(); TestPruningStrategy testPruningStrategy = new TestPruningStrategy( @@ -1593,7 +1591,7 @@ void VerifyAllTrieExceptGenesis() long cachedPersistedNode = fullTrieStore.CachedNodesCount - fullTrieStore.DirtyCachedNodesCount; long perStatePersistedNode = 20; - if (_scheme == INodeStorage.KeyScheme.Hash) + if (scheme == INodeStorage.KeyScheme.Hash) { cachedPersistedNode.Should().Be(perStatePersistedNode + 3); } diff --git a/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs b/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs index 223282c66048..cd0cd0d3ee19 100644 --- a/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs @@ -27,6 +27,7 @@ namespace Nethermind.Trie.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] public class PruningScenariosTests { private ILogger _logger; @@ -1222,7 +1223,7 @@ public void Retain_Some_PersistedNodes() ctx .AssertThatDirtyNodeCountIs(9) - .AssertThatCachedNodeCountMoreThan(280); + .AssertThatCachedNodeCountMoreThan(275); } [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/RawTrieStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/RawTrieStoreTests.cs index 7663cff436f4..2535489626fb 100644 --- a/src/Nethermind/Nethermind.Trie.Test/RawTrieStoreTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/RawTrieStoreTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class RawTrieStoreTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TinyTreePathTests.cs b/src/Nethermind/Nethermind.Trie.Test/TinyTreePathTests.cs index a2d51d1a5a18..d6c93f2b3f96 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TinyTreePathTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TinyTreePathTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class TinyTreePathTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TrackingCappedArrayPoolTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrackingCappedArrayPoolTests.cs index 322804621d13..bb30481d6f14 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrackingCappedArrayPoolTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrackingCappedArrayPoolTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class TrackingCappedArrayPoolTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TreePathTests.cs b/src/Nethermind/Nethermind.Trie.Test/TreePathTests.cs index b398b4b3b65e..da83b8a3da8f 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TreePathTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TreePathTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class TreePathTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs index df8fcf6dac17..6d28229901d0 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs @@ -11,6 +11,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class TrieNodeResolverWithReadFlagsTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs index cc8bbf893719..39acb58ec63e 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs @@ -27,6 +27,8 @@ namespace Nethermind.Trie.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class TrieTests { private ILogger _logger; @@ -1081,6 +1083,8 @@ public void Fuzz_accounts_with_reorganizations( } [TestCaseSource(nameof(FuzzAccountsWithStorageScenarios))] + [Retry(3)] + [NonParallelizable] public void Fuzz_accounts_with_storage( (TrieStoreConfigurations trieStoreConfigurations, int accountsCount, diff --git a/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs b/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs index 2fd6f53b2b60..7221240b00a8 100644 --- a/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs @@ -20,6 +20,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class VisitingTests { [TestCaseSource(nameof(GetOptions))] diff --git a/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs b/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs index 3b3e4b1b2a46..f22b68a1123b 100644 --- a/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs @@ -8,6 +8,7 @@ namespace Nethermind.Trie.Test; +[Parallelizable(ParallelScope.All)] public class VisitorProgressTrackerTests { [Test] diff --git a/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs b/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs index 75236286b82d..28146edf7611 100644 --- a/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs +++ b/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs @@ -1,15 +1,13 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Concurrent; using Nethermind.Core.Collections; namespace Nethermind.Trie; public sealed class NodeStorageCache { - private ConcurrentDictionary _cache = new(); + private readonly SeqlockCache _cache = new(); private volatile bool _enabled = false; @@ -19,17 +17,18 @@ public bool Enabled set => _enabled = value; } - public byte[]? GetOrAdd(NodeKey nodeKey, Func tryLoadRlp) + public byte[]? GetOrAdd(in NodeKey nodeKey, SeqlockCache.ValueFactory tryLoadRlp) { if (!_enabled) { - return tryLoadRlp(nodeKey); + return tryLoadRlp(in nodeKey); } - return _cache.GetOrAdd(nodeKey, tryLoadRlp); + return _cache.GetOrAdd(in nodeKey, tryLoadRlp); } public bool ClearCaches() { - return _cache.NoResizeClear(); + _cache.Clear(); + return true; } } diff --git a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs index 08b96d207fc8..d42c85a23f6e 100644 --- a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs +++ b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -25,7 +24,6 @@ namespace Nethermind.Trie public partial class PatriciaTree { private const int MaxKeyStackAlloc = 64; - private readonly static byte[][] _singleByteKeys = [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15]]; private readonly ILogger _logger; @@ -1003,25 +1001,5 @@ bool TryGetRootRef(out TrieNode? rootRef) [DoesNotReturn, StackTraceHidden] static void ThrowReadOnlyTrieException() => throw new TrieException("Commits are not allowed on this trie."); - - [DoesNotReturn, StackTraceHidden] - private static void ThrowInvalidDataException(TrieNode originalNode) - { - throw new InvalidDataException( - $"Extension {originalNode.Keccak} has no child."); - } - - [DoesNotReturn, StackTraceHidden] - private static void ThrowMissingChildException(TrieNode node) - { - throw new TrieException( - $"Found an {nameof(NodeType.Extension)} {node.Keccak} that is missing a child."); - } - - [DoesNotReturn, StackTraceHidden] - private static void ThrowMissingPrefixException() - { - throw new InvalidDataException("An attempt to visit a node without a prefix path."); - } } } diff --git a/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs b/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs index dc6d9b6d1fa0..3a0d52617152 100644 --- a/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs @@ -3,8 +3,11 @@ using System; using System.Numerics; +using System.Runtime.CompilerServices; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using Nethermind.Trie.Pruning; namespace Nethermind.Trie; @@ -13,8 +16,8 @@ public sealed class PreCachedTrieStore : ITrieStore { private readonly ITrieStore _inner; private readonly NodeStorageCache _preBlockCache; - private readonly Func _loadRlp; - private readonly Func _tryLoadRlp; + private readonly SeqlockCache.ValueFactory _loadRlp; + private readonly SeqlockCache.ValueFactory _tryLoadRlp; public PreCachedTrieStore(ITrieStore inner, NodeStorageCache cache) { @@ -22,8 +25,8 @@ public PreCachedTrieStore(ITrieStore inner, NodeStorageCache cache) _preBlockCache = cache; // Capture the delegate once for default path to avoid the allocation of the lambda per call - _loadRlp = (NodeKey key) => _inner.LoadRlp(key.Address, in key.Path, key.Hash, flags: ReadFlags.None); - _tryLoadRlp = (NodeKey key) => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash, flags: ReadFlags.None); + _loadRlp = (in NodeKey key) => _inner.LoadRlp(key.Address, in key.Path, key.Hash, flags: ReadFlags.None); + _tryLoadRlp = (in NodeKey key) => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash, flags: ReadFlags.None); } public void Dispose() @@ -43,8 +46,7 @@ public IBlockCommitter BeginBlockCommit(long blockNumber) public bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 keccak) { - byte[]? rlp = _preBlockCache.GetOrAdd(new(address, in path, in keccak), - key => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash)); + byte[]? rlp = _preBlockCache.GetOrAdd(new(address, in path, in keccak), _tryLoadRlp); return rlp is not null; } @@ -60,17 +62,17 @@ public bool IsPersisted(Hash256? address, in TreePath path, in ValueHash256 kecc public byte[]? LoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => _preBlockCache.GetOrAdd(new(address, in path, hash), flags == ReadFlags.None ? _loadRlp : - key => _inner.LoadRlp(key.Address, in key.Path, key.Hash, flags)); + (in NodeKey key) => _inner.LoadRlp(key.Address, in key.Path, key.Hash, flags)); public byte[]? TryLoadRlp(Hash256? address, in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => _preBlockCache.GetOrAdd(new(address, in path, hash), flags == ReadFlags.None ? _tryLoadRlp : - key => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash, flags)); + (in NodeKey key) => _inner.TryLoadRlp(key.Address, in key.Path, key.Hash, flags)); public INodeStorage.KeyScheme Scheme => _inner.Scheme; } -public readonly struct NodeKey : IEquatable +public readonly struct NodeKey : IEquatable, IHash64bit { public readonly Hash256? Address; public readonly TreePath Path; @@ -90,15 +92,28 @@ public NodeKey(Hash256? address, in TreePath path, Hash256 hash) Hash = hash; } - public bool Equals(NodeKey other) => - Address == other.Address && Path.Equals(in other.Path) && Hash.Equals(other.Hash); + public bool Equals(NodeKey other) => Equals(in other); public override bool Equals(object? obj) => obj is NodeKey key && Equals(key); + public bool Equals(in NodeKey other) => + Address == other.Address && Path.Equals(in other.Path) && Hash.Equals(other.Hash); + public override int GetHashCode() { uint hashCode0 = (uint)Hash.GetHashCode(); ulong hashCode1 = ((ulong)(uint)Path.GetHashCode() << 32) | (uint)(Address?.GetHashCode() ?? 1); return (int)BitOperations.Crc32C(hashCode0, hashCode1); } + + public long GetHashCode64() + { + long hashCode0 = Address is null ? 1L : SpanExtensions.FastHash64For32Bytes(ref Unsafe.As(ref Unsafe.AsRef(in Address.ValueHash256))); + long hashCode1 = SpanExtensions.FastHash64For32Bytes(ref Unsafe.As(ref Unsafe.AsRef(in Hash.ValueHash256))); + long hashCode2 = SpanExtensions.FastHash64For32Bytes(ref Unsafe.As(ref Unsafe.AsRef(in Path.Path))); + + // Rotations spaced by 64/3 ensure way 0 (bits 0-13) and way 1 (bits 42-55) + // sample non-overlapping 14-bit windows from each input + return hashCode1 + (long)BitOperations.RotateLeft((ulong)hashCode0, 21) + (long)BitOperations.RotateLeft((ulong)hashCode2, 42); + } } diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs index a53f33528f54..abd7da715b18 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs @@ -1228,11 +1228,6 @@ public bool IsNoLongerNeeded(long lastCommit) && lastCommit < LatestCommittedBlockNumber - _maxDepth; } - private bool IsStillNeeded(long lastCommit) - { - return !IsNoLongerNeeded(lastCommit); - } - private void AnnounceReorgBoundaries() { if (LatestCommittedBlockNumber < 1) diff --git a/src/Nethermind/Nethermind.Trie/TrackingCappedArrayPool.cs b/src/Nethermind/Nethermind.Trie/TrackingCappedArrayPool.cs index 82c39b94b134..c0b679074f99 100644 --- a/src/Nethermind/Nethermind.Trie/TrackingCappedArrayPool.cs +++ b/src/Nethermind/Nethermind.Trie/TrackingCappedArrayPool.cs @@ -3,7 +3,9 @@ using System; using System.Buffers; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Nethermind.Core.Buffers; @@ -13,21 +15,16 @@ namespace Nethermind.Trie; /// /// Track every rented CappedArray and return them all at once /// -public sealed class TrackingCappedArrayPool : ICappedArrayPool, IDisposable +public sealed class TrackingCappedArrayPool(int initialCapacity, ArrayPool arrayPool = null, bool canBeParallel = true) : ICappedArrayPool, IDisposable { - private readonly List _rentedBuffers; - private readonly ArrayPool? _arrayPool; + private readonly ConcurrentQueue? _rentedQueue = canBeParallel ? new() : null; + private readonly List? _rentedList = canBeParallel ? null : new(initialCapacity); + private readonly ArrayPool? _arrayPool = arrayPool; public TrackingCappedArrayPool() : this(0) { } - public TrackingCappedArrayPool(int initialCapacity, ArrayPool arrayPool = null) - { - _rentedBuffers = new List(initialCapacity); - _arrayPool = arrayPool; - } - public CappedArray Rent(int size) { if (size == 0) @@ -37,9 +34,16 @@ public CappedArray Rent(int size) // Devirtualize shared array pool by referring directly to it byte[] array = _arrayPool?.Rent(size) ?? ArrayPool.Shared.Rent(size); - CappedArray rented = new CappedArray(array, size); + CappedArray rented = new(array, size); array.AsSpan().Clear(); - lock (_rentedBuffers) _rentedBuffers.Add(array); + if (_rentedQueue is not null) + { + _rentedQueue.Enqueue(array); + } + else + { + _rentedList.Add(array); + } return rented; } @@ -49,9 +53,16 @@ public void Return(in CappedArray buffer) public void Dispose() { - if (_arrayPool is null) + if (_arrayPool is not null) + { + DisposeCustomArrayPool(); + return; + } + + ConcurrentQueue? rentedQueue = _rentedQueue; + if (rentedQueue is not null) { - foreach (byte[] rentedBuffer in CollectionsMarshal.AsSpan(_rentedBuffers)) + while (rentedQueue.TryDequeue(out byte[]? rentedBuffer)) { // Devirtualize shared array pool by referring directly to it ArrayPool.Shared.Return(rentedBuffer); @@ -59,9 +70,32 @@ public void Dispose() } else { - foreach (byte[] rentedBuffer in CollectionsMarshal.AsSpan(_rentedBuffers)) + Span items = CollectionsMarshal.AsSpan(_rentedList); + foreach (byte[] rentedBuffer in items) + { + // Devirtualize shared array pool by referring directly to it + ArrayPool.Shared.Return(rentedBuffer); + } + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void DisposeCustomArrayPool() + { + ArrayPool arrayPool = _arrayPool; + if (_rentedQueue is not null) + { + while (_rentedQueue.TryDequeue(out byte[]? rentedBuffer)) + { + arrayPool.Return(rentedBuffer); + } + } + else + { + Span items = CollectionsMarshal.AsSpan(_rentedList); + foreach (byte[] rentedBuffer in items) { - _arrayPool.Return(rentedBuffer); + arrayPool.Return(rentedBuffer); } } } diff --git a/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs b/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs index 4b711e210f17..ef8b70015635 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs @@ -137,7 +137,7 @@ public static SpanSource RlpEncodeBranch(TrieNode item, ITrieNodeResolver tree, Metrics.TreeNodeRlpEncodings++; const int valueRlpLength = 1; - int contentLength = valueRlpLength + (UseParallel(canBeParallel) ? GetChildrenRlpLengthForBranchParallel(tree, ref path, item, pool) : GetChildrenRlpLengthForBranch(tree, ref path, item, pool)); + int contentLength = valueRlpLength + (UseParallel(canBeParallel, item) ? GetChildrenRlpLengthForBranchParallel(tree, ref path, item, pool) : GetChildrenRlpLengthForBranch(tree, ref path, item, pool)); int sequenceLength = Rlp.LengthOfSequence(contentLength); SpanSource result = pool.SafeRentBuffer(sequenceLength); Span resultSpan = result.Span; @@ -148,7 +148,26 @@ public static SpanSource RlpEncodeBranch(TrieNode item, ITrieNodeResolver tree, return result; - static bool UseParallel(bool canBeParallel) => Environment.ProcessorCount > 1 && canBeParallel; + static bool UseParallel(bool canBeParallel, TrieNode item) + { + if (Environment.ProcessorCount <= 1 || !canBeParallel) + { + return false; + } + + const int MinChildrenForParallel = 4; + int nonNullChildren = 0; + for (int i = 0; i < BranchesCount; i++) + { + object? data = item._nodeData[i]; + if (data is not null && !ReferenceEquals(data, _nullNode) && ++nonNullChildren >= MinChildrenForParallel) + { + return true; + } + } + + return false; + } } private static int GetChildrenRlpLengthForBranch(ITrieNodeResolver tree, ref TreePath path, TrieNode item, ICappedArrayPool? bufferPool) diff --git a/src/Nethermind/Nethermind.Trie/TrieNode.cs b/src/Nethermind/Nethermind.Trie/TrieNode.cs index 52dc5e0b1c9b..e660f86c5d87 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNode.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNode.cs @@ -32,7 +32,6 @@ public sealed partial class TrieNode #endif private static readonly object _nullNode = new(); - private static readonly TrieNodeDecoder _nodeDecoder = new(); private static readonly AccountDecoder _accountDecoder = new(); private const byte _dirtyMask = 0b001; diff --git a/src/Nethermind/Nethermind.Trie/VisitContext.cs b/src/Nethermind/Nethermind.Trie/VisitContext.cs index 5d8f3f0c0c7e..e636efd9557d 100644 --- a/src/Nethermind/Nethermind.Trie/VisitContext.cs +++ b/src/Nethermind/Nethermind.Trie/VisitContext.cs @@ -58,7 +58,6 @@ public SmallTrieVisitContext(TrieVisitContext trieVisitContext) } public byte Level { get; internal set; } - private byte _branchChildIndex = 255; private byte _flags = 0; private const byte StorageFlag = 1; diff --git a/src/Nethermind/Nethermind.TxPool.Test/BlobTxStorageTests.cs b/src/Nethermind/Nethermind.TxPool.Test/BlobTxStorageTests.cs index b8333b8af243..802844f03cd1 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/BlobTxStorageTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/BlobTxStorageTests.cs @@ -4,12 +4,16 @@ using System; using FluentAssertions; using Nethermind.Core; +using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Int256; using NUnit.Framework; namespace Nethermind.TxPool.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] public class BlobTxStorageTests { [Test] @@ -32,4 +36,100 @@ public void should_throw_when_trying_to_add_tx_with_null_hash() Action act = () => blobTxStorage.Add(tx); act.Should().Throw(); } + + [Test] + public void TryGetMany_should_return_zero_for_empty_batch() + { + BlobTxStorage blobTxStorage = new(); + Transaction[] results = new Transaction[0]; + + int found = blobTxStorage.TryGetMany([], 0, results); + found.Should().Be(0); + } + + [Test] + public void TryGetMany_should_batch_retrieve_stored_transactions() + { + BlobTxStorage blobTxStorage = new(); + EthereumEcdsa ecdsa = new(BlockchainIds.Mainnet); + + Transaction[] txs = new Transaction[3]; + TxLookupKey[] keys = new TxLookupKey[3]; + + for (int i = 0; i < 3; i++) + { + txs[i] = Build.A.Transaction + .WithShardBlobTxTypeAndFields() + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce((UInt256)i) + .SignedAndResolved(ecdsa, TestItem.PrivateKeys[i]).TestObject; + + blobTxStorage.Add(txs[i]); + keys[i] = new TxLookupKey(txs[i].Hash, txs[i].SenderAddress!, txs[i].Timestamp); + } + + Transaction[] results = new Transaction[3]; + int found = blobTxStorage.TryGetMany(keys, 3, results); + + found.Should().Be(3); + for (int i = 0; i < 3; i++) + { + results[i].Should().BeEquivalentTo(txs[i], static options => options + .Excluding(static t => t.GasBottleneck) + .Excluding(static t => t.PoolIndex)); + } + } + + [Test] + public void TryGetMany_should_handle_mix_of_existing_and_missing_keys() + { + BlobTxStorage blobTxStorage = new(); + EthereumEcdsa ecdsa = new(BlockchainIds.Mainnet); + + Transaction[] txs = new Transaction[2]; + for (int i = 0; i < 2; i++) + { + txs[i] = Build.A.Transaction + .WithShardBlobTxTypeAndFields() + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce((UInt256)i) + .SignedAndResolved(ecdsa, TestItem.PrivateKeys[i]).TestObject; + + blobTxStorage.Add(txs[i]); + } + + TxLookupKey[] keys = new TxLookupKey[3]; + keys[0] = new TxLookupKey(txs[0].Hash, txs[0].SenderAddress!, txs[0].Timestamp); + keys[1] = new TxLookupKey(txs[1].Hash, txs[1].SenderAddress!, txs[1].Timestamp); + keys[2] = new TxLookupKey(TestItem.KeccakA, TestItem.AddressC, UInt256.One); + + Transaction[] results = new Transaction[3]; + int found = blobTxStorage.TryGetMany(keys, 3, results); + + found.Should().Be(2); + results[0].Should().NotBeNull(); + results[1].Should().NotBeNull(); + results[2].Should().BeNull(); + } + + [Test] + public void TryGetMany_should_handle_all_missing_keys() + { + BlobTxStorage blobTxStorage = new(); + + TxLookupKey[] keys = + [ + new TxLookupKey(TestItem.KeccakA, TestItem.AddressA, UInt256.One), + new TxLookupKey(TestItem.KeccakB, TestItem.AddressB, UInt256.One), + ]; + + Transaction[] results = new Transaction[2]; + int found = blobTxStorage.TryGetMany(keys, 2, results); + + found.Should().Be(0); + results[0].Should().BeNull(); + results[1].Should().BeNull(); + } } diff --git a/src/Nethermind/Nethermind.TxPool.Test/Collections/SortedPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/Collections/SortedPoolTests.cs index 758a9c9ff3da..b2820b770334 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/Collections/SortedPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/Collections/SortedPoolTests.cs @@ -20,6 +20,8 @@ namespace Nethermind.TxPool.Test.Collections { [TestFixture] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class SortedPoolTests { private const int Capacity = 16; diff --git a/src/Nethermind/Nethermind.TxPool.Test/CompetingTransactionEqualityComparerTests.cs b/src/Nethermind/Nethermind.TxPool.Test/CompetingTransactionEqualityComparerTests.cs index 9d316746a3f5..c5fab4a47687 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/CompetingTransactionEqualityComparerTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/CompetingTransactionEqualityComparerTests.cs @@ -9,6 +9,7 @@ namespace Nethermind.TxPool.Test { + [Parallelizable(ParallelScope.All)] public class CompetingTransactionEqualityComparerTests { public static IEnumerable TestCases diff --git a/src/Nethermind/Nethermind.TxPool.Test/DelegatedAccountFilterTest.cs b/src/Nethermind/Nethermind.TxPool.Test/DelegatedAccountFilterTest.cs index c7d004f471f2..b7d7e3590d5c 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/DelegatedAccountFilterTest.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/DelegatedAccountFilterTest.cs @@ -19,6 +19,7 @@ namespace Nethermind.TxPool.Test; +[Parallelizable(ParallelScope.All)] internal class DelegatedAccountFilterTest { [Test] @@ -26,7 +27,7 @@ public void Accept_SenderIsNotDelegated_ReturnsAccepted() { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For(), new DelegationCache()); Transaction transaction = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; @@ -46,7 +47,7 @@ public void Accept_SenderIsDelegatedWithNoTransactionsInPool_ReturnsAccepted() stateProvider.InsertCode(code, TestItem.AddressA); IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, stateProvider, new DelegationCache()); Transaction transaction = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; @@ -62,7 +63,7 @@ public void Accept_SenderIsDelegatedWithOneTransactionInPoolWithSameNonce_Return { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); Transaction inPool = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; standardPool.TryInsert(inPool.Hash, inPool); @@ -84,7 +85,7 @@ public void Accept_SenderIsDelegatedWithOneTransactionInPoolWithDifferentNonce_R { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); Transaction inPool = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; standardPool.TryInsert(inPool.Hash, inPool); @@ -111,7 +112,7 @@ public void Accept_Eip7702IsNotActivated_ReturnsExpected(bool isActive, AcceptTx { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(isActive ? Prague.Instance : Cancun.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); Transaction inPool = Build.A.Transaction.WithNonce(0).SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; standardPool.TryInsert(inPool.Hash, inPool); @@ -140,7 +141,7 @@ public void Accept_SenderHasPendingDelegation_OnlyAcceptsIfNonceIsExactMatch(int { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegationCache pendingDelegations = new(); pendingDelegations.IncrementDelegationCount(TestItem.AddressA); @@ -161,7 +162,7 @@ public void Accept_AuthorityHasPendingTransaction_ReturnsDelegatorHasPendingTx(b { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For(), new()); Transaction transaction; @@ -200,7 +201,7 @@ public void Accept_SetCodeTxHasAuthorityWithPendingTx_ReturnsDelegatorHasPending { IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); - TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(new TxPoolConfig().Size, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegationCache pendingDelegations = new(); pendingDelegations.IncrementDelegationCount(TestItem.AddressA); diff --git a/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs b/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs index dce8e3bde40c..f5d801d747a8 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/NonceManagerTests.cs @@ -19,6 +19,8 @@ namespace Nethermind.TxPool.Test; +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class NonceManagerTests { private ISpecProvider _specProvider; diff --git a/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs b/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs index cf9b832a7cb3..4bebaa002d24 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs @@ -19,6 +19,8 @@ namespace Nethermind.TxPool.Test { [TestFixture(true)] [TestFixture(false)] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class ReceiptStorageTests { private readonly bool _useEip2718; diff --git a/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs b/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs index 9b09028fedaa..1ee4370de27c 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs @@ -3,16 +3,16 @@ using Nethermind.Core; using Nethermind.Logging; -using NSubstitute; using NUnit.Framework; using System; using System.Threading; using System.Threading.Tasks; -using Nethermind.Core.Test; namespace Nethermind.TxPool.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class RetryCacheTests { public readonly struct ResourceRequestMessage : INew @@ -23,10 +23,27 @@ public class RetryCacheTests public interface ITestHandler : IMessageHandler; + /// + /// A test handler that tracks HandleMessage calls without using NSubstitute. + /// + private class TestHandler : ITestHandler + { + private int _handleMessageCallCount; + public int HandleMessageCallCount => _handleMessageCallCount; + public bool WasCalled => _handleMessageCallCount > 0; + public Action OnHandleMessage { get; set; } + + public void HandleMessage(ResourceRequestMessage message) + { + Interlocked.Increment(ref _handleMessageCallCount); + OnHandleMessage?.Invoke(message); + } + } + private CancellationTokenSource _cancellationTokenSource; private RetryCache _cache; - private readonly int Timeout = 5000; + private readonly int Timeout = 10000; [SetUp] public void Setup() @@ -45,8 +62,8 @@ public void TearDown() [Test] public void Announced_SameResourceDifferentNode_ReturnsEnqueued() { - AnnounceResult result1 = _cache.Announced(1, Substitute.For()); - AnnounceResult result2 = _cache.Announced(1, Substitute.For()); + AnnounceResult result1 = _cache.Announced(1, new TestHandler()); + AnnounceResult result2 = _cache.Announced(1, new TestHandler()); Assert.That(result1, Is.EqualTo(AnnounceResult.RequestRequired)); Assert.That(result2, Is.EqualTo(AnnounceResult.Delayed)); @@ -55,58 +72,57 @@ public void Announced_SameResourceDifferentNode_ReturnsEnqueued() [Test] public void Announced_AfterTimeout_ExecutesRetryRequests() { - ITestHandler request1 = Substitute.For(); - ITestHandler request2 = Substitute.For(); + TestHandler request1 = new(); + TestHandler request2 = new(); _cache.Announced(1, request1); _cache.Announced(1, request2); - Assert.That(() => request2.ReceivedCallsMatching(r => r.HandleMessage(Arg.Any())), Is.True.After(Timeout, 100)); - request1.DidNotReceive().HandleMessage(Arg.Any()); + Assert.That(() => request2.WasCalled, Is.True.After(Timeout, 100)); + Assert.That(request1.WasCalled, Is.False); } [Test] public void Announced_MultipleResources_ExecutesAllRetryRequestsExceptInitialOne() { - ITestHandler request1 = Substitute.For(); - ITestHandler request2 = Substitute.For(); - ITestHandler request3 = Substitute.For(); - ITestHandler request4 = Substitute.For(); + TestHandler request1 = new(); + TestHandler request2 = new(); + TestHandler request3 = new(); + TestHandler request4 = new(); _cache.Announced(1, request1); _cache.Announced(1, request2); _cache.Announced(2, request3); _cache.Announced(2, request4); - Assert.That(() => request2.ReceivedCallsMatching(r => r.HandleMessage(Arg.Any())), Is.True.After(Timeout, 100)); - Assert.That(() => request4.ReceivedCallsMatching(r => r.HandleMessage(Arg.Any())), Is.True.After(Timeout, 100)); - request1.DidNotReceive().HandleMessage(Arg.Any()); - request3.DidNotReceive().HandleMessage(Arg.Any()); + Assert.That(() => request2.WasCalled, Is.True.After(Timeout, 100)); + Assert.That(() => request4.WasCalled, Is.True.After(Timeout, 100)); + Assert.That(request1.WasCalled, Is.False); + Assert.That(request3.WasCalled, Is.False); } [Test] public void Received_RemovesResourceFromRetryQueue() { - _cache.Announced(1, Substitute.For()); + _cache.Announced(1, new TestHandler()); _cache.Received(1); - AnnounceResult result = _cache.Announced(1, Substitute.For()); + AnnounceResult result = _cache.Announced(1, new TestHandler()); Assert.That(result, Is.EqualTo(AnnounceResult.RequestRequired)); } [Test] public async Task Received_BeforeTimeout_PreventsRetryExecution() { - ITestHandler request = Substitute.For(); + TestHandler request = new(); _cache.Announced(1, request); _cache.Announced(1, request); _cache.Received(1); - await Task.Delay(Timeout, _cancellationTokenSource.Token); - request.DidNotReceive().HandleMessage(ResourceRequestMessage.New(1)); + Assert.That(request.WasCalled, Is.False); } [Test] @@ -116,9 +132,7 @@ public async Task Clear_cache_after_timeout() { Parallel.For(0, 100, (j) => { - ITestHandler request = Substitute.For(); - - _cache.Announced(i, request); + _cache.Announced(i, new TestHandler()); }); }); @@ -128,40 +142,39 @@ public async Task Clear_cache_after_timeout() } [Test] + [Retry(3)] public void RetryExecution_HandlesExceptions() { - ITestHandler faultyRequest = Substitute.For(); - ITestHandler normalRequest = Substitute.For(); - - faultyRequest.When(x => x.HandleMessage(Arg.Any())).Do(x => throw new InvalidOperationException("Test exception")); + TestHandler faultyRequest = new() { OnHandleMessage = _ => throw new InvalidOperationException("Test exception") }; + TestHandler normalRequest = new(); - _cache.Announced(1, Substitute.For()); + _cache.Announced(1, new TestHandler()); _cache.Announced(1, faultyRequest); _cache.Announced(1, normalRequest); - Assert.That(() => normalRequest.ReceivedCallsMatching(r => r.HandleMessage(ResourceRequestMessage.New(1))), Is.True.After(Timeout, 100)); + Assert.That(() => normalRequest.WasCalled, Is.True.After(Timeout, 100)); } [Test] public async Task CancellationToken_StopsProcessing() { - ITestHandler request = Substitute.For(); + TestHandler request = new(); _cache.Announced(1, request); await _cancellationTokenSource.CancelAsync(); await Task.Delay(Timeout); - request.DidNotReceive().HandleMessage(Arg.Any()); + Assert.That(request.WasCalled, Is.False); } [Test] public async Task Announced_AfterRetryInProgress_ReturnsNew() { - _cache.Announced(1, Substitute.For()); + _cache.Announced(1, new TestHandler()); await Task.Delay(Timeout, _cancellationTokenSource.Token); - Assert.That(() => _cache.Announced(1, Substitute.For()), Is.EqualTo(AnnounceResult.RequestRequired).After(Timeout, 100)); + Assert.That(() => _cache.Announced(1, new TestHandler()), Is.EqualTo(AnnounceResult.RequestRequired).After(Timeout, 100)); } [Test] diff --git a/src/Nethermind/Nethermind.TxPool.Test/TestBlockTree.cs b/src/Nethermind/Nethermind.TxPool.Test/TestBlockTree.cs new file mode 100644 index 000000000000..a8473dc82663 --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool.Test/TestBlockTree.cs @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Find; +using Nethermind.Blockchain.Visitors; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; + +namespace Nethermind.TxPool.Test; + +/// +/// A minimal IBlockTree implementation for testing that avoids NSubstitute's +/// static state issues when running tests in parallel. +/// +internal class TestBlockTree : IBlockTree +{ + public Block? Head { get; set; } + public BlockHeader? BestSuggestedHeader { get; set; } + + public event EventHandler? BlockAddedToMain; + public event EventHandler? NewBestSuggestedBlock { add { } remove { } } + public event EventHandler? NewSuggestedBlock { add { } remove { } } + public event EventHandler? NewHeadBlock { add { } remove { } } + public event EventHandler? OnUpdateMainChain { add { } remove { } } + public event EventHandler? OnForkChoiceUpdated { add { } remove { } } + + public void RaiseBlockAddedToMain(BlockReplacementEventArgs args) + { + BlockAddedToMain?.Invoke(this, args); + } + + public BlockHeader FindBestSuggestedHeader() => BestSuggestedHeader!; + + // IBlockFinder implementation + public Hash256 HeadHash => Head?.Hash ?? Keccak.Zero; + public Hash256 GenesisHash => Keccak.Zero; + public Hash256? PendingHash => null; + public Hash256? FinalizedHash => null; + public Hash256? SafeHash => null; + public long? BestPersistedState { get; set; } + + public Block? FindBlock(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) => null; + public Block? FindBlock(long blockNumber, BlockTreeLookupOptions options) => null; + public bool HasBlock(long blockNumber, Hash256 blockHash) => false; + public BlockHeader? FindHeader(Hash256 blockHash, BlockTreeLookupOptions options, long? blockNumber = null) => null; + public BlockHeader? FindHeader(long blockNumber, BlockTreeLookupOptions options) => null; + public Hash256? FindBlockHash(long blockNumber) => null; + public bool IsMainChain(BlockHeader blockHeader) => false; + public bool IsMainChain(Hash256 blockHash, bool throwOnMissingHash = true) => false; + public long GetLowestBlock() => 0; + + // IBlockTree implementation + public ulong NetworkId => 1; + public ulong ChainId => 1; + public BlockHeader? Genesis => null; + public Block? BestSuggestedBody => null; + public BlockHeader? BestSuggestedBeaconHeader => null; + public BlockHeader? LowestInsertedHeader { get; set; } + public BlockHeader? LowestInsertedBeaconHeader { get; set; } + public long BestKnownNumber => Head?.Number ?? 0; + public long BestKnownBeaconNumber => 0; + public bool CanAcceptNewBlocks => true; + public (long BlockNumber, Hash256 BlockHash) SyncPivot { get; set; } + public bool IsProcessingBlock { get; set; } + + public AddBlockResult Insert(BlockHeader header, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) + => AddBlockResult.Added; + + public void BulkInsertHeader(IReadOnlyList headers, BlockTreeInsertHeaderOptions headerOptions = BlockTreeInsertHeaderOptions.None) { } + + public AddBlockResult Insert(Block block, BlockTreeInsertBlockOptions insertBlockOptions = BlockTreeInsertBlockOptions.None, + BlockTreeInsertHeaderOptions insertHeaderOptions = BlockTreeInsertHeaderOptions.None, WriteFlags bodiesWriteFlags = WriteFlags.None) + => AddBlockResult.Added; + + public void UpdateHeadBlock(Hash256 blockHash) { } + public void NewOldestBlock(long oldestBlock) { } + public AddBlockResult SuggestBlock(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) => AddBlockResult.Added; + public ValueTask SuggestBlockAsync(Block block, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) + => ValueTask.FromResult(AddBlockResult.Added); + public AddBlockResult SuggestHeader(BlockHeader header) => AddBlockResult.Added; + public bool IsKnownBlock(long number, Hash256 blockHash) => false; + public bool IsKnownBeaconBlock(long number, Hash256 blockHash) => false; + public bool WasProcessed(long number, Hash256 blockHash) => false; + public void UpdateMainChain(IReadOnlyList blocks, bool wereProcessed, bool forceHeadBlock = false) { } + public void MarkChainAsProcessed(IReadOnlyList blocks) { } + public Task Accept(IBlockTreeVisitor blockTreeVisitor, CancellationToken cancellationToken) => Task.CompletedTask; + public (BlockInfo? Info, ChainLevelInfo? Level) GetInfo(long number, Hash256 blockHash) => (null, null); + public ChainLevelInfo? FindLevel(long number) => null; + public BlockInfo FindCanonicalBlockInfo(long blockNumber) => null!; + public Hash256? FindHash(long blockNumber) => null; + public IOwnedReadOnlyList FindHeaders(Hash256 hash, int numberOfBlocks, int skip, bool reverse) + => new ArrayPoolList(0); + public void DeleteInvalidBlock(Block invalidBlock) { } + public void DeleteOldBlock(long blockNumber, Hash256 blockHash) { } + public void ForkChoiceUpdated(Hash256? finalizedBlockHash, Hash256? safeBlockBlockHash) { } + public int DeleteChainSlice(in long startNumber, long? endNumber = null, bool force = false) => 0; + public bool IsBetterThanHead(BlockHeader? header) => false; + public void UpdateBeaconMainChain(BlockInfo[]? blockInfos, long clearBeaconMainChainStartPoint) { } + public void RecalculateTreeLevels() { } +} diff --git a/src/Nethermind/Nethermind.TxPool.Test/TestChainHeadInfoProvider.cs b/src/Nethermind/Nethermind.TxPool.Test/TestChainHeadInfoProvider.cs new file mode 100644 index 000000000000..9cb26ba9b4ea --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool.Test/TestChainHeadInfoProvider.cs @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +#nullable enable + +using System; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm.State; +using Nethermind.Int256; + +namespace Nethermind.TxPool.Test; + +/// +/// A minimal IChainHeadInfoProvider implementation for testing that avoids NSubstitute's +/// static state issues when running tests in parallel. +/// +internal class TestChainHeadInfoProvider : IChainHeadInfoProvider +{ + public IChainHeadSpecProvider SpecProvider { get; set; } = null!; + public IReadOnlyStateProvider ReadOnlyStateProvider { get; set; } = null!; + public long HeadNumber { get; set; } + public long? BlockGasLimit { get; set; } = 30_000_000; + public UInt256 CurrentBaseFee { get; set; } + public UInt256 CurrentFeePerBlobGas { get; set; } + public ProofVersion CurrentProofVersion { get; set; } + public bool IsSyncing { get; set; } + public bool IsProcessingBlock { get; set; } + public event EventHandler? HeadChanged; + + public void RaiseHeadChanged(BlockReplacementEventArgs args) + { + HeadChanged?.Invoke(this, args); + } +} diff --git a/src/Nethermind/Nethermind.TxPool.Test/TransactionExtensionsTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TransactionExtensionsTests.cs index f8baecc5b94f..1028eddc5577 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TransactionExtensionsTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TransactionExtensionsTests.cs @@ -10,6 +10,7 @@ namespace Nethermind.TxPool.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] public class TransactionExtensionsTests { [Test] diff --git a/src/Nethermind/Nethermind.TxPool.Test/TransactionPoolInfoProviderTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TransactionPoolInfoProviderTests.cs index f9486f29e11c..722f7ba43e68 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TransactionPoolInfoProviderTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TransactionPoolInfoProviderTests.cs @@ -13,6 +13,8 @@ namespace Nethermind.TxPool.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class TxPoolInfoProviderTests { private Address _address; diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs index 696f7f1385ee..17daab08c3ac 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs @@ -39,6 +39,8 @@ namespace Nethermind.TxPool.Test; [TestFixture] +[Parallelizable(ParallelScope.All)] +[FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public class TxBroadcasterTests { private ILogManager _logManager; @@ -48,7 +50,7 @@ public class TxBroadcasterTests private TxBroadcaster _broadcaster; private EthereumEcdsa _ethereumEcdsa; private TxPoolConfig _txPoolConfig; - private IChainHeadInfoProvider _headInfo; + private TestChainHeadInfoProvider _headInfo; [SetUp] public void Setup() @@ -59,18 +61,19 @@ public void Setup() _blockTree = Substitute.For(); _comparer = new TransactionComparerProvider(_specProvider, _blockTree).GetDefaultComparer(); _txPoolConfig = new TxPoolConfig(); - _headInfo = Substitute.For(); + _headInfo = new TestChainHeadInfoProvider(); } [TearDown] public void TearDown() => _broadcaster?.Dispose(); [Test] - public async Task should_not_broadcast_persisted_tx_to_peer_too_quickly() + public void should_not_broadcast_persisted_tx_to_peer_too_quickly() { + ManualTimestamper timestamper = new(DateTime.UtcNow); _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = 100 }; - _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager, timestamper: timestamper); + _headInfo.CurrentBaseFee = 0.GWei(); int addedTxsCount = TestItem.PrivateKeys.Length; Transaction[] transactions = new Transaction[addedTxsCount]; @@ -102,7 +105,8 @@ public async Task should_not_broadcast_persisted_tx_to_peer_too_quickly() peer.Received(1).SendNewTransactions(Arg.Any>(), true); - await Task.Delay(TimeSpan.FromMilliseconds(1001)); + // Advance time by 2 seconds (throttle is 1 second) + timestamper.Add(TimeSpan.FromSeconds(2)); peer.Received(1).SendNewTransactions(Arg.Any>(), true); @@ -120,7 +124,7 @@ public void should_pick_best_persistent_txs_to_broadcast([Values(1, 2, 99, 100, { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); int addedTxsCount = TestItem.PrivateKeys.Length; Transaction[] transactions = new Transaction[addedTxsCount]; @@ -203,7 +207,7 @@ public void should_skip_large_txs_when_picking_best_persistent_txs_to_broadcast( { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); // add 256 transactions, 10% of them is large int addedTxsCount = TestItem.PrivateKeys.Length; @@ -249,7 +253,7 @@ public void should_skip_blob_txs_when_picking_best_persistent_txs_to_broadcast([ { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); // add 256 transactions, 10% of them is blob type int addedTxsCount = TestItem.PrivateKeys.Length; @@ -297,7 +301,7 @@ public void should_not_pick_txs_with_GasPrice_lower_than_CurrentBaseFee([Values( _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); const int currentBaseFeeInGwei = 250; - _headInfo.CurrentBaseFee.Returns(currentBaseFeeInGwei.GWei()); + _headInfo.CurrentBaseFee = currentBaseFeeInGwei.GWei(); Block headBlock = Build.A.Block .WithNumber(MainnetSpecProvider.LondonBlockNumber) .WithBaseFeePerGas(currentBaseFeeInGwei.GWei()) @@ -341,7 +345,7 @@ public void should_not_pick_txs_with_GasPrice_lower_than_CurrentBaseFee([Values( [TestCase(150, true)] public void should_not_broadcast_tx_with_MaxFeePerGas_lower_than_70_percent_of_CurrentBaseFee(int maxFeePerGas, bool shouldBroadcast) { - _headInfo.CurrentBaseFee.Returns((UInt256)100); + _headInfo.CurrentBaseFee = (UInt256)100; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); @@ -371,7 +375,7 @@ public void should_not_pick_1559_txs_with_MaxFeePerGas_lower_than_CurrentBaseFee _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); const int currentBaseFeeInGwei = 250; - _headInfo.CurrentBaseFee.Returns(currentBaseFeeInGwei.GWei()); + _headInfo.CurrentBaseFee = currentBaseFeeInGwei.GWei(); Block headBlock = Build.A.Block .WithNumber(MainnetSpecProvider.LondonBlockNumber) .WithBaseFeePerGas(currentBaseFeeInGwei.GWei()) @@ -416,7 +420,7 @@ public void should_not_pick_blob_txs_with_MaxFeePerBlobGas_lower_than_CurrentFee _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); const int currentFeePerBlobGas = 250; - _headInfo.CurrentFeePerBlobGas.Returns(currentFeePerBlobGas.GWei()); + _headInfo.CurrentFeePerBlobGas = currentFeePerBlobGas.GWei(); // add 256 transactions with MaxFeePerBlobGas 0-255 int addedTxsCount = TestItem.PrivateKeys.Length; @@ -463,7 +467,7 @@ public void should_pick_tx_with_lowest_nonce_from_bucket() { _txPoolConfig = new TxPoolConfig() { PeerNotificationThreshold = 5 }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); const int addedTxsCount = 5; Transaction[] transactions = new Transaction[addedTxsCount]; @@ -508,7 +512,7 @@ public void should_broadcast_local_tx_immediately_after_receiving_it() public void should_broadcast_full_local_tx_immediately_after_receiving_it() { _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); ISession session = Substitute.For(); session.Node.Returns(new Node(TestItem.PublicKeyA, TestItem.IPEndPointA)); @@ -646,7 +650,7 @@ public void should_check_tx_policy_for_broadcast(bool canGossipTransactions, boo { ITxGossipPolicy txGossipPolicy = Substitute.For(); _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager, txGossipPolicy); - _headInfo.CurrentBaseFee.Returns(0.GWei()); + _headInfo.CurrentBaseFee = 0.GWei(); ISession session = Substitute.For(); session.Node.Returns(new Node(TestItem.PublicKeyA, TestItem.IPEndPointA)); @@ -708,7 +712,7 @@ public void should_rebroadcast_all_persistent_transactions_if_PeerNotificationTh [TestCase(10000, 7000)] public void should_calculate_baseFeeThreshold_correctly(int baseFee, int expectedThreshold) { - _headInfo.CurrentBaseFee.Returns((UInt256)baseFee); + _headInfo.CurrentBaseFee = (UInt256)baseFee; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); _broadcaster.CalculateBaseFeeThreshold().Should().Be((UInt256)expectedThreshold); } @@ -717,7 +721,7 @@ public void should_calculate_baseFeeThreshold_correctly(int baseFee, int expecte public void calculation_of_baseFeeThreshold_should_handle_overflow_correctly([Values(0, 70, 100, 101, 500)] int threshold, [Values(2, 3, 4, 5, 6, 7, 8, 9, 10, 11)] int divisor) { UInt256.Divide(UInt256.MaxValue, (UInt256)divisor, out UInt256 baseFee); - _headInfo.CurrentBaseFee.Returns(baseFee); + _headInfo.CurrentBaseFee = baseFee; _txPoolConfig = new TxPoolConfig() { MinBaseFeeThreshold = threshold }; _broadcaster = new TxBroadcaster(_comparer, TimerFactory.Default, _txPoolConfig, _headInfo, _logManager); diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs index 17764368551b..ce0d4cf783f7 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.Blobs.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using CkzgLib; using FluentAssertions; using Nethermind.Blockchain; @@ -607,7 +608,8 @@ Transaction GetTx(bool isBlob, UInt256 nonce) _txPool.GetPendingTransactionsCount().Should().Be(firstIsBlob ? 0 : 1); _txPool.GetPendingBlobTransactionsCount().Should().Be(firstIsBlob ? 1 : 0); _stateProvider.IncrementNonce(TestItem.AddressA); - await RaiseBlockAddedToMainAndWaitForTransactions(1); + Block block = Build.A.Block.WithNumber(1).TestObject; + await RaiseBlockAddedToMainAndWaitForNewHead(block); _txPool.GetPendingTransactionsCount().Should().Be(0); _txPool.GetPendingBlobTransactionsCount().Should().Be(0); @@ -951,7 +953,7 @@ public void should_convert_blob_proofs_to_cell_proofs_if_enabled([Values(true, f EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); // update head and set correct current proof version - _blockTree.BlockAddedToMain += Raise.EventWith(new BlockReplacementEventArgs(Build.A.Block.TestObject)); + _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.TestObject)); Transaction blobTxAdded = Build.A.Transaction .WithShardBlobTxTypeAndFields() @@ -985,7 +987,7 @@ public void should_convert_blob_proofs_to_cell_proofs_if_enabled([Values(true, f public async Task should_evict_based_on_proof_version_and_fork(BlobsSupportMode poolMode, TestAction[] testActions) { Block head = _blockTree.Head; - _blockTree.FindBestSuggestedHeader().Returns(head.Header); + _blockTree.BestSuggestedHeader = head.Header; (ChainSpecBasedSpecProvider provider, _) = TestSpecHelper.LoadChainSpec(new ChainSpecJson { @@ -1053,12 +1055,12 @@ TestCaseData MakeTestCase(string testName, int finalCount, BlobsSupportMode mode } } - private Task AddEmptyBlock() + private async Task AddEmptyBlock() { BlockHeader bh = new(_blockTree.Head.Hash, Keccak.EmptyTreeHash, TestItem.AddressA, 0, _blockTree.Head.Number + 1, _blockTree.Head.GasLimit, _blockTree.Head.Timestamp + 1, []); - _blockTree.FindBestSuggestedHeader().Returns(bh); - _blockTree.BlockAddedToMain += Raise.EventWith(new BlockReplacementEventArgs(new Block(bh, new BlockBody([], [])), _blockTree.Head)); - return Task.Delay(300); + _blockTree.BestSuggestedHeader = bh; + Block block = new Block(bh, new BlockBody([], [])); + await RaiseBlockAddedToMainAndWaitForNewHead(block, _blockTree.Head); } private Transaction CreateBlobTx(PrivateKey sender, UInt256 nonce = default, int blobCount = 1, IReleaseSpec releaseSpec = default) @@ -1092,7 +1094,7 @@ public async Task should_evict_txs_with_too_many_blobs_per_tx_after_fork() }; Block head = _blockTree.Head; - _blockTree.FindBestSuggestedHeader().Returns(head.Header); + _blockTree.BestSuggestedHeader = head.Header; _txPool = CreatePool(specProvider: provider); EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); @@ -1126,7 +1128,7 @@ public async Task should_evict_txs_with_too_many_blobs_per_block_after_fork() }; Block head = _blockTree.Head; - _blockTree.FindBestSuggestedHeader().Returns(head.Header); + _blockTree.BestSuggestedHeader = head.Header; _txPool = CreatePool(specProvider: provider); EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); @@ -1155,5 +1157,139 @@ public void max_blobs_per_tx_should_not_exceed_max_blobs_per_block() Assert.That(maxBlobsPerTx, Is.EqualTo(regularMaxBlobCount)); } + + [Test] + public void should_batch_return_blobs_and_proofs_v1_from_persistent_storage() + { + // BlobCacheSize = 1 forces cache eviction after the first insert, + // so the second tx must be fetched via TryGetMany (Phase 2 DB path). + TxPoolConfig txPoolConfig = new() + { + BlobsSupport = BlobsSupportMode.Storage, + BlobCacheSize = 1, + Size = 10 + }; + BlobTxStorage blobTxStorage = new(); + _txPool = CreatePool(txPoolConfig, GetOsakaSpecProvider(), txStorage: blobTxStorage); + EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); + EnsureSenderBalance(TestItem.AddressB, UInt256.MaxValue); + + Transaction tx1 = Build.A.Transaction + .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce(UInt256.Zero) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; + + Transaction tx2 = Build.A.Transaction + .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce(UInt256.Zero) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyB).TestObject; + + _txPool.SubmitTx(tx1, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + _txPool.SubmitTx(tx2, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + + // tx1 was evicted from cache (size=1) when tx2 was inserted, + // so at least one must come from DB via TryGetMany + byte[][] requestedHashes = [tx1.BlobVersionedHashes![0]!, tx2.BlobVersionedHashes![0]!]; + byte[][] blobs = new byte[2][]; + ReadOnlyMemory[] proofs = new ReadOnlyMemory[2]; + + int found = _txPool.TryGetBlobsAndProofsV1(requestedHashes, blobs, proofs); + + found.Should().Be(2); + blobs[0].Should().NotBeNull(); + blobs[1].Should().NotBeNull(); + proofs[0].Length.Should().Be(Ckzg.CellsPerExtBlob); + proofs[1].Length.Should().Be(Ckzg.CellsPerExtBlob); + } + + [Test] + public void should_batch_return_partial_blobs_when_some_missing() + { + TxPoolConfig txPoolConfig = new() + { + BlobsSupport = BlobsSupportMode.Storage, + BlobCacheSize = 1, + Size = 10 + }; + BlobTxStorage blobTxStorage = new(); + _txPool = CreatePool(txPoolConfig, GetOsakaSpecProvider(), txStorage: blobTxStorage); + EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); + + Transaction tx1 = Build.A.Transaction + .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce(UInt256.Zero) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; + + _txPool.SubmitTx(tx1, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + + byte[] fakeBlobHash = new byte[32]; + fakeBlobHash[0] = 0x01; // versioned hash prefix + byte[][] requestedHashes = [tx1.BlobVersionedHashes![0]!, fakeBlobHash]; + byte[][] blobs = new byte[2][]; + ReadOnlyMemory[] proofs = new ReadOnlyMemory[2]; + + int found = _txPool.TryGetBlobsAndProofsV1(requestedHashes, blobs, proofs); + + found.Should().Be(1); + blobs[0].Should().NotBeNull(); + blobs[1].Should().BeNull(); + } + + [Test] + public void should_batch_return_blobs_from_cache_and_db() + { + // BlobCacheSize = 1: after inserting tx1 and tx2, only tx2 remains in cache. + // Accessing tx1 via single lookup re-populates it, evicting tx2. + // Batch lookup then exercises: tx1 from cache, tx2 from DB (TryGetMany). + TxPoolConfig txPoolConfig = new() + { + BlobsSupport = BlobsSupportMode.Storage, + BlobCacheSize = 1, + Size = 10 + }; + BlobTxStorage blobTxStorage = new(); + _txPool = CreatePool(txPoolConfig, GetOsakaSpecProvider(), txStorage: blobTxStorage); + EnsureSenderBalance(TestItem.AddressA, UInt256.MaxValue); + EnsureSenderBalance(TestItem.AddressB, UInt256.MaxValue); + + Transaction tx1 = Build.A.Transaction + .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce(UInt256.Zero) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; + + Transaction tx2 = Build.A.Transaction + .WithShardBlobTxTypeAndFields(spec: new ReleaseSpec() { IsEip7594Enabled = true }) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithNonce(UInt256.Zero) + .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyB).TestObject; + + _txPool.SubmitTx(tx1, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + _txPool.SubmitTx(tx2, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + + // Access tx1 via single lookup — this re-populates tx1 in cache, evicting tx2 + _txPool.TryGetBlobAndProofV1(tx1.BlobVersionedHashes![0]!, out byte[] _, out byte[][] _).Should().BeTrue(); + + // Now batch lookup: tx1 from cache (just accessed), tx2 from DB + byte[][] requestedHashes = [tx1.BlobVersionedHashes![0]!, tx2.BlobVersionedHashes![0]!]; + byte[][] blobs = new byte[2][]; + ReadOnlyMemory[] proofs = new ReadOnlyMemory[2]; + + int found = _txPool.TryGetBlobsAndProofsV1(requestedHashes, blobs, proofs); + + found.Should().Be(2); + blobs[0].Should().NotBeNull(); + blobs[1].Should().NotBeNull(); + proofs[0].Length.Should().Be(Ckzg.CellsPerExtBlob); + proofs[1].Length.Should().Be(Ckzg.CellsPerExtBlob); + } } } diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs index 6f431b074d40..537752efcb27 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs @@ -36,16 +36,25 @@ namespace Nethermind.TxPool.Test { [TestFixture] + [Parallelizable(ParallelScope.All)] + [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] public partial class TxPoolTests { + private const int Timeout = 10000; private ILogManager _logManager; private IEthereumEcdsa _ethereumEcdsa; private ISpecProvider _specProvider; private TxPool _txPool; private TestReadOnlyStateProvider _stateProvider; - private IBlockTree _blockTree; + private TestBlockTree _blockTree; - private readonly int _txGasLimit = 1_000_000; + private const int TxGasLimit = 1_000_000; + + [OneTimeSetUp] + public static void OneTimeSetup() + { + KzgPolynomialCommitments.InitializeAsync().Wait(); + } [SetUp] public void Setup() @@ -54,19 +63,17 @@ public void Setup() _specProvider = MainnetSpecProvider.Instance; _ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId); _stateProvider = new TestReadOnlyStateProvider(); - _blockTree = Substitute.For(); - Block block = Build.A.Block.WithNumber(10000000 - 1).TestObject; - _blockTree.Head.Returns(block); - _blockTree.FindBestSuggestedHeader().Returns(Build.A.BlockHeader.WithNumber(10000000).TestObject); - - KzgPolynomialCommitments.InitializeAsync().Wait(); + _blockTree = new TestBlockTree(); + Block block = Build.A.Block.WithNumber(10000000 - 1).WithBaseFeePerGas(0).TestObject; + _blockTree.Head = block; + _blockTree.BestSuggestedHeader = Build.A.BlockHeader.WithNumber(10000000).WithBaseFee(0).TestObject; } [Test] public void should_add_peers() { _txPool = CreatePool(); - var peers = GetPeers(); + IDictionary peers = GetPeers(); foreach ((ITxPoolPeer peer, _) in peers) { @@ -78,7 +85,7 @@ public void should_add_peers() public void should_delete_peers() { _txPool = CreatePool(); - var peers = GetPeers(); + IDictionary peers = GetPeers(); foreach ((ITxPoolPeer peer, _) in peers) { @@ -152,7 +159,7 @@ public void should_add_valid_transactions_recovering_its_address() { _txPool = CreatePool(); Transaction tx = Build.A.Transaction - .WithGasLimit(_txGasLimit) + .WithGasLimit(TxGasLimit) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); tx.SenderAddress = null; @@ -166,7 +173,7 @@ public void should_reject_transactions_from_contract_address() { _txPool = CreatePool(); Transaction tx = Build.A.Transaction - .WithGasLimit(_txGasLimit) + .WithGasLimit(TxGasLimit) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); _stateProvider.InsertCode(TestItem.AddressA, "A"u8.ToArray(), _specProvider.GetSpec((ForkActivation)1)); @@ -192,7 +199,7 @@ public void should_accept_1559_transactions_only_when_eip1559_enabled([Values(fa .WithMaxPriorityFeePerGas(5.GWei()) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); - _blockTree.BlockAddedToMain += Raise.EventWith(_blockTree, new BlockReplacementEventArgs(Build.A.Block.WithGasLimit(10000000).TestObject)); + _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.WithGasLimit(10000000).TestObject)); AcceptTxResult result = txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); txPool.GetPendingTransactionsCount().Should().Be(eip1559Enabled ? 1 : 0); result.Should().Be(eip1559Enabled ? AcceptTxResult.Accepted : AcceptTxResult.Invalid); @@ -215,7 +222,7 @@ public void should_not_ignore_insufficient_funds_for_eip1559_transactions() var headProcessed = new ManualResetEventSlim(false); txPool.TxPoolHeadChanged += (s, a) => headProcessed.Set(); - _blockTree.BlockAddedToMain += Raise.EventWith(_blockTree, new BlockReplacementEventArgs(Build.A.Block.WithGasLimit(10000000).TestObject)); + _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.WithGasLimit(10000000).TestObject)); headProcessed.Wait(); result = txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); @@ -332,7 +339,7 @@ public void should_ignore_overflow_transactions() public void should_ignore_overflow_transactions_gas_premium_and_fee_cap() { ISpecProvider specProvider = GetLondonSpecProvider(); - var txPool = CreatePool(null, specProvider); + TxPool txPool = CreatePool(null, specProvider); Transaction tx = Build.A.Transaction.WithGasPrice(UInt256.MaxValue / Transaction.BaseTxGasCost) .WithGasLimit(Transaction.BaseTxGasCost) .WithValue(Transaction.BaseTxGasCost) @@ -366,7 +373,7 @@ public void should_reject_tx_if_max_size_is_exceeded([Values(true, false)] bool Transaction tx = Build.A.Transaction.SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); - var txPoolConfig = new TxPoolConfig() { MaxTxSize = tx.GetLength() - (sizeExceeded ? 1 : 0) }; + TxPoolConfig txPoolConfig = new() { MaxTxSize = tx.GetLength() - (sizeExceeded ? 1 : 0) }; _txPool = CreatePool(txPoolConfig); AcceptTxResult result = _txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); @@ -399,7 +406,7 @@ public void should_ignore_tx_gas_limit_exceeded() { _txPool = CreatePool(); Transaction tx = Build.A.Transaction - .WithGasLimit(_txGasLimit + 1) + .WithGasLimit(TxGasLimit + 1) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; EnsureSenderBalance(tx); AcceptTxResult result = _txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); @@ -558,7 +565,7 @@ public void should_not_add_tx_if_already_pending_lower_nonces_are_exhausting_bal { const int gasPrice = 10; const int value = 1; - int oneTxPrice = _txGasLimit * gasPrice + value; + int oneTxPrice = TxGasLimit * gasPrice + value; _txPool = CreatePool(); Transaction[] transactions = new Transaction[10]; @@ -570,7 +577,7 @@ public void should_not_add_tx_if_already_pending_lower_nonces_are_exhausting_bal .WithSenderAddress(TestItem.AddressA) .WithNonce((UInt256)i) .WithGasPrice((UInt256)gasPrice) - .WithGasLimit(_txGasLimit) + .WithGasLimit(TxGasLimit) .WithValue(value) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; }); @@ -591,7 +598,7 @@ public void should_not_count_txs_with_stale_nonces_when_calculating_cumulative_c { const int gasPrice = 10; const int value = 1; - int oneTxPrice = _txGasLimit * gasPrice + value; + int oneTxPrice = TxGasLimit * gasPrice + value; _txPool = CreatePool(); EnsureSenderBalance(TestItem.AddressA, (UInt256)(oneTxPrice * numberOfTxsPossibleToExecuteBeforeGasExhaustion)); @@ -604,7 +611,7 @@ public void should_not_count_txs_with_stale_nonces_when_calculating_cumulative_c .WithSenderAddress(TestItem.AddressA) .WithNonce((UInt256)i) .WithGasPrice((UInt256)gasPrice) - .WithGasLimit(_txGasLimit) + .WithGasLimit(TxGasLimit) .WithValue(value) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; }); @@ -631,7 +638,7 @@ public void should_add_tx_if_cost_of_executing_all_txs_in_bucket_exceeds_balance const int count = 10; const int gasPrice = 10; const int value = 1; - int oneTxPrice = _txGasLimit * gasPrice + value; + int oneTxPrice = TxGasLimit * gasPrice + value; _txPool = CreatePool(); Transaction[] transactions = new Transaction[count]; @@ -643,7 +650,7 @@ public void should_add_tx_if_cost_of_executing_all_txs_in_bucket_exceeds_balance .WithSenderAddress(TestItem.AddressA) .WithNonce((UInt256)i) .WithGasPrice((UInt256)gasPrice) - .WithGasLimit(_txGasLimit) + .WithGasLimit(TxGasLimit) .WithValue(value) .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; }); @@ -777,7 +784,7 @@ public void should_remove_txHash_from_hashCache_when_tx_removed_because_of_txPoo var headProcessed = new ManualResetEventSlim(false); _txPool.TxPoolHeadChanged += (s, a) => headProcessed.Set(); - _blockTree.BlockAddedToMain += Raise.EventWith(new BlockReplacementEventArgs(Build.A.Block.TestObject)); + _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.TestObject)); headProcessed.Wait(); _txPool.IsKnown(higherPriorityTx.Hash).Should().BeTrue(); @@ -888,10 +895,11 @@ public void should_remove_stale_txs_from_persistent_transactions(int numberOfTxs BlockReplacementEventArgs blockReplacementEventArgs = new(block, null); ManualResetEvent manualResetEvent = new(false); - _txPool.RemoveTransaction(Arg.Do(t => manualResetEvent.Set())); - _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); - manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); + _txPool.TxPoolHeadChanged += (o, e) => manualResetEvent.Set(); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); + bool signaled = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(Timeout)); + signaled.Should().BeTrue("TxPoolHeadChanged event should have been raised"); // transactions[nonceIncludedInBlock] was included in the block and should be removed, as well as all lower nonces. _txPool.GetOwnPendingTransactions().Length.Should().Be(numberOfTxs - nonceIncludedInBlock - 1); } @@ -926,10 +934,11 @@ public void broadcaster_should_work_well_when_there_are_no_txs_in_persistent_txs BlockReplacementEventArgs blockReplacementEventArgs = new(block, null); ManualResetEvent manualResetEvent = new(false); - _txPool.RemoveTransaction(Arg.Do(t => manualResetEvent.Set())); - _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); - manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); + _txPool.TxPoolHeadChanged += (o, e) => manualResetEvent.Set(); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); + bool signaled = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(Timeout)); + signaled.Should().BeTrue("TxPoolHeadChanged event should have been raised"); _txPool.GetPendingTransactionsCount().Should().Be(1); _txPool.GetOwnPendingTransactions().Length.Should().Be(1); } @@ -960,7 +969,7 @@ public async Task should_remove_transactions_concurrently() public void should_add_transactions_concurrently() { int size = 3; - TxPoolConfig config = new() { GasLimit = _txGasLimit, Size = size }; + TxPoolConfig config = new() { GasLimit = TxGasLimit, Size = size }; _txPool = CreatePool(config); foreach (PrivateKey privateKey in TestItem.PrivateKeys) @@ -1007,10 +1016,11 @@ public void should_remove_tx_from_txPool_when_included_in_block(bool sameTransac BlockReplacementEventArgs blockReplacementEventArgs = new(block, null); ManualResetEvent manualResetEvent = new(false); - _txPool.RemoveTransaction(Arg.Do(t => manualResetEvent.Set())); - _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); - manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); + _txPool.TxPoolHeadChanged += (o, e) => manualResetEvent.Set(); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); + bool signaled = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(Timeout)); + signaled.Should().BeTrue("TxPoolHeadChanged event should have been raised"); _txPool.GetPendingTransactionsCount().Should().Be(0); } @@ -1030,10 +1040,11 @@ public void should_not_remove_txHash_from_hashCache_when_tx_removed_because_of_i BlockReplacementEventArgs blockReplacementEventArgs = new(block, null); ManualResetEvent manualResetEvent = new(false); - _txPool.RemoveTransaction(Arg.Do(t => manualResetEvent.Set())); - _blockTree.BlockAddedToMain += Raise.EventWith(new object(), blockReplacementEventArgs); - manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(200)); + _txPool.TxPoolHeadChanged += (o, e) => manualResetEvent.Set(); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); + bool signaled = manualResetEvent.WaitOne(TimeSpan.FromMilliseconds(Timeout)); + signaled.Should().BeTrue("TxPoolHeadChanged event should have been raised"); foreach (Transaction transaction in transactions) { _txPool.IsKnown(transaction.Hash).Should().BeTrue(); @@ -1168,9 +1179,9 @@ public void should_accept_access_list_transactions_only_when_eip2930_enabled([Va { if (!eip2930Enabled) { - _blockTree.FindBestSuggestedHeader().Returns(Build.A.BlockHeader.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 1).TestObject); + _blockTree.BestSuggestedHeader = Build.A.BlockHeader.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 1).TestObject; Block block = Build.A.Block.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 2).TestObject; - _blockTree.Head.Returns(block); + _blockTree.Head = block; } _txPool = CreatePool(null, new TestSpecProvider(eip2930Enabled ? Berlin.Instance : Istanbul.Instance)); @@ -1189,9 +1200,9 @@ public void should_accept_only_when_synced([Values(false, true)] bool isSynced, { if (!isSynced) { - _blockTree.FindBestSuggestedHeader().Returns(Build.A.BlockHeader.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 1).TestObject); + _blockTree.BestSuggestedHeader = Build.A.BlockHeader.WithNumber(MainnetSpecProvider.BerlinBlockNumber - 1).TestObject; Block block = Build.A.Block.WithNumber(1).TestObject; - _blockTree.Head.Returns(block); + _blockTree.Head = block; } _txPool = CreatePool(null, new TestSpecProvider(Berlin.Instance)); @@ -1488,6 +1499,8 @@ public void should_increase_nonce_when_transaction_not_included_in_txPool_but_br } [Test] + [Retry(3)] + [NonParallelizable] public void should_include_transaction_after_removal() { ISpecProvider specProvider = GetLondonSpecProvider(); @@ -1523,12 +1536,10 @@ public void should_include_transaction_after_removal() _txPool.SubmitTx(expensiveTx2, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); // Rise new block event to cleanup cash and remove one expensive tx - _blockTree.BlockAddedToMain += - Raise.Event>(this, - new BlockReplacementEventArgs(Build.A.Block.WithTransactions(expensiveTx1).TestObject)); + _blockTree.RaiseBlockAddedToMain(new BlockReplacementEventArgs(Build.A.Block.WithTransactions(expensiveTx1).TestObject)); // Wait for event processing and send txA again => should be Accepted - Assert.That(() => _txPool.SubmitTx(txA, TxHandlingOptions.None), Is.EqualTo(AcceptTxResult.Accepted).After(100, 10)); + Assert.That(() => _txPool.SubmitTx(txA, TxHandlingOptions.None), Is.EqualTo(AcceptTxResult.Accepted).After(Timeout, 10)); } [TestCase(true, 1, 1, true)] @@ -2245,7 +2256,7 @@ private TxPool CreatePool( _ethereumEcdsa, txStorage, _headInfo, - config ?? new TxPoolConfig() { GasLimit = _txGasLimit }, + config ?? new TxPoolConfig() { GasLimit = TxGasLimit }, new TxValidator(_specProvider.ChainId), _logManager, transactionComparerProvider.GetDefaultComparer(), @@ -2263,33 +2274,13 @@ private ITxPoolPeer GetPeer(PublicKey publicKey) return peer; } - private static ISpecProvider GetLondonSpecProvider() - { - var specProvider = Substitute.For(); - specProvider.GetSpec(Arg.Any()).Returns(London.Instance); - return specProvider; - } + private static ISpecProvider GetLondonSpecProvider() => new TestSpecProvider(London.Instance); - private static ISpecProvider GetCancunSpecProvider() - { - var specProvider = Substitute.For(); - specProvider.GetSpec(Arg.Any()).Returns(Cancun.Instance); - return specProvider; - } + private static ISpecProvider GetCancunSpecProvider() => new TestSpecProvider(Cancun.Instance); - private static ISpecProvider GetPragueSpecProvider() - { - var specProvider = Substitute.For(); - specProvider.GetSpec(Arg.Any()).Returns(Prague.Instance); - return specProvider; - } + private static ISpecProvider GetPragueSpecProvider() => new TestSpecProvider(Prague.Instance); - private static ISpecProvider GetOsakaSpecProvider() - { - var specProvider = Substitute.For(); - specProvider.GetSpec(Arg.Any()).Returns(Osaka.Instance); - return specProvider; - } + private static ISpecProvider GetOsakaSpecProvider() => new TestSpecProvider(Osaka.Instance); private Transaction[] AddTransactionsToPool(bool sameTransactionSenderPerPeer = true, bool sameNoncePerPeer = false, int transactionsPerPeer = 10) { @@ -2397,10 +2388,10 @@ private async Task RaiseBlockAddedToMainAndWaitForTransactions(int txCount, Bloc SemaphoreSlim semaphoreSlim = new(0, txCount); _txPool.NewPending += (o, e) => semaphoreSlim.Release(); - _blockTree.BlockAddedToMain += Raise.EventWith(blockReplacementEventArgs); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); for (int i = 0; i < txCount; i++) { - await semaphoreSlim.WaitAsync(10); + await semaphoreSlim.WaitAsync(1000); } } @@ -2417,7 +2408,7 @@ private async Task RaiseBlockAddedToMainAndWaitForNewHead(Block block, Block pre e => e.Number == block.Number ); - _blockTree.BlockAddedToMain += Raise.EventWith(blockReplacementEventArgs); + _blockTree.RaiseBlockAddedToMain(blockReplacementEventArgs); await waitTask; } diff --git a/src/Nethermind/Nethermind.TxPool/BlobTxStorage.cs b/src/Nethermind/Nethermind.TxPool/BlobTxStorage.cs index cc76e38f619e..d76d0f620a0e 100644 --- a/src/Nethermind/Nethermind.TxPool/BlobTxStorage.cs +++ b/src/Nethermind/Nethermind.TxPool/BlobTxStorage.cs @@ -3,6 +3,7 @@ using System; using System.Buffers.Binary; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Nethermind.Core; @@ -17,7 +18,9 @@ namespace Nethermind.TxPool; public class BlobTxStorage : IBlobTxStorage { + private const int MaxPooledKeys = 128; private static readonly TxDecoder _txDecoder = TxDecoder.Instance; + private readonly ConcurrentQueue _keyPool = new(); private readonly IDb _fullBlobTxsDb; private readonly IDb _lightBlobTxsDb; private readonly IDb _processedBlobTxsDb; @@ -45,6 +48,35 @@ public bool TryGet(in ValueHash256 hash, Address sender, in UInt256 timestamp, [ return TryDecodeFullTx(txBytes, sender, out transaction); } + public int TryGetMany(TxLookupKey[] keys, int count, Transaction?[] results) + { + if (count == 0) return 0; + + // Outer array must be exact-size for the IDb indexer (uses keys.Length). + // Inner byte[64] keys are pooled via ConcurrentQueue to avoid per-call allocations. + byte[][] dbKeys = new byte[count][]; + for (int i = 0; i < dbKeys.Length; i++) + { + byte[] key = RentKey(); + GetHashPrefixedByTimestamp(keys[i].Timestamp, keys[i].Hash, key); + dbKeys[i] = key; + } + + KeyValuePair[] dbResults = _fullBlobTxsDb[dbKeys]; + + int found = 0; + for (int i = 0; i < count; i++) + { + if (TryDecodeFullTx(dbResults[i].Value, keys[i].Sender, out results[i])) + found++; + } + + for (int i = 0; i < count; i++) + ReturnKey(dbKeys[i]); + + return found; + } + public IEnumerable GetAll() { foreach (byte[] txBytes in _lightBlobTxsDb.GetAllValues()) @@ -133,6 +165,14 @@ private static bool TryDecodeLightTx(byte[]? txBytes, out LightTransaction? ligh return false; } + private byte[] RentKey() => _keyPool.TryDequeue(out byte[]? key) ? key : new byte[64]; + + private void ReturnKey(byte[] key) + { + if (_keyPool.Count < MaxPooledKeys) + _keyPool.Enqueue(key); + } + private static void GetHashPrefixedByTimestamp(UInt256 timestamp, ValueHash256 hash, Span txHashPrefixed) { timestamp.WriteBigEndian(txHashPrefixed); diff --git a/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs b/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs index cd552b82e484..c7b49199a4aa 100644 --- a/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs +++ b/src/Nethermind/Nethermind.TxPool/Collections/BlobTxDistinctSortedPool.cs @@ -75,20 +75,46 @@ private bool TryGetBlobAndProof( return false; } - public int GetBlobCounts(byte[][] requestedBlobVersionedHashes) + public virtual int TryGetBlobsAndProofsV1( + byte[][] requestedBlobVersionedHashes, + byte[]?[] blobs, + ReadOnlyMemory[] proofs) { - using var lockRelease = Lock.Acquire(); - int count = 0; + using McsLock.Disposable lockRelease = Lock.Acquire(); + int found = 0; - foreach (byte[] requestedBlobVersionedHash in requestedBlobVersionedHashes) + for (int i = 0; i < requestedBlobVersionedHashes.Length; i++) { - if (BlobIndex.ContainsKey(requestedBlobVersionedHash)) + byte[] requestedBlobVersionedHash = requestedBlobVersionedHashes[i]; + if (!BlobIndex.TryGetValue(requestedBlobVersionedHash, out List? txHashes)) + continue; + + foreach (Hash256 hash in CollectionsMarshal.AsSpan(txHashes)) { - count += 1; + if (!TryGetValueNonLocked(hash, out Transaction? blobTx) + || blobTx.BlobVersionedHashes is not { Length: > 0 }) + continue; + + bool matched = false; + for (int indexOfBlob = 0; indexOfBlob < blobTx.BlobVersionedHashes.Length; indexOfBlob++) + { + if (Bytes.AreEqual(blobTx.BlobVersionedHashes[indexOfBlob], requestedBlobVersionedHash) + && blobTx.NetworkWrapper is ShardBlobNetworkWrapper { Version: ProofVersion.V1 } wrapper) + { + blobs[i] = wrapper.Blobs[indexOfBlob]; + proofs[i] = new ReadOnlyMemory( + wrapper.Proofs, + Ckzg.CellsPerExtBlob * indexOfBlob, + Ckzg.CellsPerExtBlob); + found++; + matched = true; + break; + } + } + if (matched) break; } } - - return count; + return found; } protected override bool InsertCore(ValueHash256 key, Transaction value, AddressAsKey groupKey) diff --git a/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs b/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs index 7b4e1e4b1235..18647a7f5b7c 100644 --- a/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs +++ b/src/Nethermind/Nethermind.TxPool/Collections/PersistentBlobTxDistinctSortedPool.cs @@ -2,12 +2,17 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using CkzgLib; using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Threading; using Nethermind.Logging; namespace Nethermind.TxPool.Collections; @@ -89,6 +94,127 @@ protected override bool TryGetValueNonLocked(ValueHash256 hash, [NotNullWhen(tru return false; } + public override int TryGetBlobsAndProofsV1( + byte[][] requestedBlobVersionedHashes, + byte[]?[] blobs, + ReadOnlyMemory[] proofs) + { + int found = 0; + int missCount = 0; + + // Rent arrays for Phase 2 (DB lookup of cache misses). + // Sized to 4x request length to accommodate multiple candidate tx hashes per blob + // (e.g. when the same blob versioned hash appears in multiple transactions). + int maxMisses = requestedBlobVersionedHashes.Length * 4; + TxLookupKey[] dbKeys = ArrayPool.Shared.Rent(maxMisses); + int[] missOutputIndex = ArrayPool.Shared.Rent(maxMisses); + int[] missBlobIndex = ArrayPool.Shared.Rent(maxMisses); + try + { + // Phase 1: Under lock — in-memory lookups only + using (McsLock.Disposable lockRelease = Lock.Acquire()) + { + for (int i = 0; i < requestedBlobVersionedHashes.Length; i++) + { + byte[] requestedBlobVersionedHash = requestedBlobVersionedHashes[i]; + if (!BlobIndex.TryGetValue(requestedBlobVersionedHash, out List? txHashes)) + continue; + + foreach (Hash256 hash in CollectionsMarshal.AsSpan(txHashes)) + { + if (!base.TryGetValueNonLocked(hash, out Transaction? lightTx) + || lightTx is not LightTransaction lt + || lt.ProofVersion != ProofVersion.V1 + || lightTx.BlobVersionedHashes is not { Length: > 0 }) + continue; + + int indexOfBlob = -1; + for (int b = 0; b < lightTx.BlobVersionedHashes.Length; b++) + { + if (Bytes.AreEqual(lightTx.BlobVersionedHashes[b], requestedBlobVersionedHash)) + { + indexOfBlob = b; + break; + } + } + if (indexOfBlob < 0) + continue; + + // Try cache first — on hit, populate and stop searching for this blob hash + if (_blobTxCache.TryGet(hash, out Transaction? cachedTx) + && cachedTx.NetworkWrapper is ShardBlobNetworkWrapper { Version: ProofVersion.V1 } cachedWrapper) + { + blobs[i] = cachedWrapper.Blobs[indexOfBlob]; + proofs[i] = new ReadOnlyMemory( + cachedWrapper.Proofs, + Ckzg.CellsPerExtBlob * indexOfBlob, + Ckzg.CellsPerExtBlob); + found++; + break; + } + + // Cache miss — record for Phase 2 DB lookup and continue to try + // remaining tx hashes so that if this candidate is missing from DB, + // later candidates can still satisfy the request. + if (missCount < maxMisses) + { + dbKeys[missCount] = new TxLookupKey(hash, lightTx.SenderAddress!, lightTx.Timestamp); + missOutputIndex[missCount] = i; + missBlobIndex[missCount] = indexOfBlob; + missCount++; + } + } + } + } + + // Phase 2: Outside lock — single RocksDB MultiGet for all misses + if (missCount > 0) + { + Transaction?[] dbResults = ArrayPool.Shared.Rent(missCount); + try + { + Array.Clear(dbResults, 0, missCount); + _blobTxStorage.TryGetMany(dbKeys, missCount, dbResults); + + for (int m = 0; m < missCount; m++) + { + int outIdx = missOutputIndex[m]; + + // Skip if this output slot was already filled by a cache hit or earlier candidate + if (blobs[outIdx] is not null) + continue; + + Transaction? fullTx = dbResults[m]; + if (fullTx?.NetworkWrapper is ShardBlobNetworkWrapper { Version: ProofVersion.V1 } wrapper) + { + int blobIdx = missBlobIndex[m]; + blobs[outIdx] = wrapper.Blobs[blobIdx]; + proofs[outIdx] = new ReadOnlyMemory( + wrapper.Proofs, + Ckzg.CellsPerExtBlob * blobIdx, + Ckzg.CellsPerExtBlob); + found++; + + _blobTxCache.Set(dbKeys[m].Hash, fullTx); + } + } + } + finally + { + ArrayPool.Shared.Return(dbResults, clearArray: true); + } + } + + return found; + } + finally + { + ArrayPool.Shared.Return(dbKeys, clearArray: true); + ArrayPool.Shared.Return(missOutputIndex); + ArrayPool.Shared.Return(missBlobIndex); + } + } + protected override bool Remove(ValueHash256 hash, out Transaction? tx) { if (base.Remove(hash, out tx)) diff --git a/src/Nethermind/Nethermind.TxPool/ITxPool.cs b/src/Nethermind/Nethermind.TxPool/ITxPool.cs index 4e6b9a0d7f95..9759c6bd9f8f 100644 --- a/src/Nethermind/Nethermind.TxPool/ITxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/ITxPool.cs @@ -53,7 +53,8 @@ bool TryGetBlobAndProofV0(byte[] blobVersionedHash, bool TryGetBlobAndProofV1(byte[] blobVersionedHash, [NotNullWhen(true)] out byte[]? blob, [NotNullWhen(true)] out byte[][]? cellProofs); - int GetBlobCounts(byte[][] blobVersionedHashes); + int TryGetBlobsAndProofsV1(byte[][] requestedBlobVersionedHashes, + byte[]?[] blobs, ReadOnlyMemory[] proofs); UInt256 GetLatestPendingNonce(Address address); event EventHandler NewDiscovered; event EventHandler NewPending; diff --git a/src/Nethermind/Nethermind.TxPool/ITxStorage.cs b/src/Nethermind/Nethermind.TxPool/ITxStorage.cs index 251d723b9e05..beb3268f0bd8 100644 --- a/src/Nethermind/Nethermind.TxPool/ITxStorage.cs +++ b/src/Nethermind/Nethermind.TxPool/ITxStorage.cs @@ -9,9 +9,12 @@ namespace Nethermind.TxPool; +public readonly record struct TxLookupKey(ValueHash256 Hash, Address Sender, UInt256 Timestamp); + public interface ITxStorage { bool TryGet(in ValueHash256 hash, Address sender, in UInt256 timestamp, [NotNullWhen(true)] out Transaction? transaction); + int TryGetMany(TxLookupKey[] keys, int count, Transaction?[] results); IEnumerable GetAll(); void Add(Transaction transaction); void Delete(in ValueHash256 hash, in UInt256 timestamp); diff --git a/src/Nethermind/Nethermind.TxPool/NullBlobTxStorage.cs b/src/Nethermind/Nethermind.TxPool/NullBlobTxStorage.cs index a3ed71f0067a..e7bf5bd0f406 100644 --- a/src/Nethermind/Nethermind.TxPool/NullBlobTxStorage.cs +++ b/src/Nethermind/Nethermind.TxPool/NullBlobTxStorage.cs @@ -21,6 +21,8 @@ public bool TryGet(in ValueHash256 hash, Address sender, in UInt256 timestamp, [ return false; } + public int TryGetMany(TxLookupKey[] keys, int count, Transaction?[] results) => 0; + public IEnumerable GetAll() => Array.Empty(); public void Add(Transaction transaction) { } diff --git a/src/Nethermind/Nethermind.TxPool/NullTxPool.cs b/src/Nethermind/Nethermind.TxPool/NullTxPool.cs index 9ab7f22ab6b8..40c78fd606c4 100644 --- a/src/Nethermind/Nethermind.TxPool/NullTxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/NullTxPool.cs @@ -83,7 +83,8 @@ public bool TryGetBlobAndProofV1(byte[] blobVersionedHash, return false; } - public int GetBlobCounts(byte[][] blobVersionedHashes) => 0; + public int TryGetBlobsAndProofsV1(byte[][] requestedBlobVersionedHashes, + byte[]?[] blobs, ReadOnlyMemory[] proofs) => 0; public UInt256 GetLatestPendingNonce(Address address) => 0; diff --git a/src/Nethermind/Nethermind.TxPool/RetryCache.cs b/src/Nethermind/Nethermind.TxPool/RetryCache.cs index 4bd9b40626c9..9a797f733b86 100644 --- a/src/Nethermind/Nethermind.TxPool/RetryCache.cs +++ b/src/Nethermind/Nethermind.TxPool/RetryCache.cs @@ -122,11 +122,7 @@ public AnnounceResult Announced(in TResourceId resourceId, IMessageHandler - { - return AnnounceAdd(resourceId, retryHandler, out result); - }, _announceUpdate, handler); - + _retryRequests.AddOrUpdate(resourceId, (resourceId, retryHandler) => AnnounceAdd(resourceId, retryHandler, out result), _announceUpdate, handler); return result; } diff --git a/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs b/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs index 2c3ec6bbca67..9b481c5b10fd 100644 --- a/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs +++ b/src/Nethermind/Nethermind.TxPool/TxBroadcaster.cs @@ -68,21 +68,24 @@ internal class TxBroadcaster : IDisposable private readonly TimeSpan _minTimeBetweenPersistedTxBroadcast = TimeSpan.FromSeconds(1); private readonly ILogger _logger; + private readonly ITimestamper _timestamper; public TxBroadcaster(IComparer comparer, ITimerFactory timerFactory, ITxPoolConfig txPoolConfig, IChainHeadInfoProvider chainHeadInfoProvider, ILogManager? logManager, - ITxGossipPolicy? transactionsGossipPolicy = null) + ITxGossipPolicy? transactionsGossipPolicy = null, + ITimestamper? timestamper = null) { _txPoolConfig = txPoolConfig; _headInfo = chainHeadInfoProvider; + _timestamper = timestamper ?? Timestamper.Default; _txGossipPolicy = transactionsGossipPolicy ?? ShouldGossip.Instance; // Allocate closure once _gossipFilter = _txGossipPolicy.ShouldGossipTransaction; _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - _persistentTxs = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, comparer, logManager); + _persistentTxs = new TxDistinctSortedPool(txPoolConfig.Size, comparer, logManager); _accumulatedTemporaryTxs = new ResettableList(512, 4); _txsToSend = new ResettableList(512, 4); @@ -172,7 +175,7 @@ internal void BroadcastPersistentTxs() return; } - DateTimeOffset now = DateTimeOffset.UtcNow; + DateTimeOffset now = _timestamper.UtcNowOffset; if (_lastPersistedTxBroadcast + _minTimeBetweenPersistedTxBroadcast > now) { if (_logger.IsTrace) _logger.Trace($"Minimum time between persistent tx broadcast not reached."); diff --git a/src/Nethermind/Nethermind.TxPool/TxNonceTxPoolReserveSealer.cs b/src/Nethermind/Nethermind.TxPool/TxNonceTxPoolReserveSealer.cs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.NonceInfo.cs b/src/Nethermind/Nethermind.TxPool/TxPool.NonceInfo.cs deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 20aaa898432b..4846479dd29f 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -135,7 +135,7 @@ public TxPool(IEthereumEcdsa ecdsa, _broadcaster = new TxBroadcaster(comparer, TimerFactory.Default, txPoolConfig, chainHeadInfoProvider, logManager, transactionsGossipPolicy); TxPoolHeadChanged += _broadcaster.OnNewHead; - _transactions = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, comparer, logManager); + _transactions = new TxDistinctSortedPool(txPoolConfig.Size, comparer, logManager); _transactions.Removed += OnRemovedTx; _blobTransactions = txPoolConfig.BlobsSupport.IsPersistentStorage() @@ -225,8 +225,9 @@ public bool TryGetBlobAndProofV1(byte[] blobVersionedHash, [NotNullWhen(true)] out byte[][]? cellProofs) => _blobTransactions.TryGetBlobAndProofV1(blobVersionedHash, out blob, out cellProofs); - public int GetBlobCounts(byte[][] blobVersionedHashes) - => _blobTransactions.GetBlobCounts(blobVersionedHashes); + public int TryGetBlobsAndProofsV1(byte[][] requestedBlobVersionedHashes, + byte[]?[] blobs, ReadOnlyMemory[] proofs) + => _blobTransactions.TryGetBlobsAndProofsV1(requestedBlobVersionedHashes, blobs, proofs); private void OnRemovedTx(object? sender, SortedPool.SortedPoolRemovedEventArgs args) { diff --git a/src/Nethermind/Nethermind.Xdc.Test/BlockInfoTests.cs b/src/Nethermind/Nethermind.Xdc.Test/BlockInfoTests.cs index aba2ee0d65c8..ced27d8d1c34 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/BlockInfoTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/BlockInfoTests.cs @@ -8,7 +8,6 @@ namespace Nethermind.Xdc.Test; -[Parallelizable(ParallelScope.All)] internal class BlockInfoTests { [Test] diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs index f41e2e596293..18c46daf5ecb 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs @@ -494,8 +494,8 @@ public async Task SimulateVoting() VoteDecoder voteDecoder = new VoteDecoder(); - var newHeadWaitHandle = new TaskCompletionSource(); - var newRoundWaitHandle = new TaskCompletionSource(); + var newHeadWaitHandle = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var newRoundWaitHandle = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); XdcContext.NewRoundSetEvent += OnNewRound; try { @@ -545,7 +545,7 @@ public async Task TriggerBlockProposal() var head = (XdcBlockHeader)BlockTree.Head!.Header; var spec = SpecProvider.GetXdcSpec(head, XdcContext.CurrentRound); - var newHeadWaitHandle = new TaskCompletionSource(); + var newHeadWaitHandle = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); BlockTree.NewHeadBlock += OnNewHead; try diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs index fe51092ef36d..277fc1c5d9b3 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs @@ -13,7 +13,6 @@ namespace Nethermind.Xdc.Test; -[Parallelizable(ParallelScope.All)] internal class ProposedBlockTests { [Test] diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/RewardTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/RewardTests.cs index 757734e69090..becec46623a9 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/RewardTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/RewardTests.cs @@ -117,12 +117,24 @@ await chain.AddBlock(BuildSigningTx( long current = chain.BlockTree.Head!.Number; await chain.AddBlocks((int)(targetIncludingBlockForSecondSign - current - 1)); // move so AddBlockMayHaveExtraTx produces the target + // For 4E reward calculation, the masternodes come from the second epoch switch found + // when walking backwards from 4E. The signed header (3E - mergeSignRange) is in the + // range [2E+1, 3E), so its epoch switch info provides the relevant masternodes. + // Use a masternode from that epoch to ensure the signature is counted. + EpochSwitchInfo? epochSwitchInfoFor2E = chain.EpochSwitchManager.GetEpochSwitchInfo(signedHeader3EMinusMerge); + Assert.That(epochSwitchInfoFor2E, Is.Not.Null); + PrivateKey signerForPart2 = chain.MasterNodeCandidates.First(k => k.Address == epochSwitchInfoFor2E!.Masternodes[0]); + + // Set the chain's signer to our chosen masternode - required because + // SignTransactionFilter rejects signing txs from non-current-signers + chain.Signer.SetSigner(signerForPart2); + await chain.AddBlock(BuildSigningTx( spec, signedHeader3EMinusMerge.Number, signedHeader3EMinusMerge.Hash ?? signedHeader3EMinusMerge.CalculateHash().ToHash256(), - chain.Signer.Key!, - (long)chain.ReadOnlyState.GetNonce(chain.Signer.Address))); + signerForPart2, + (long)chain.ReadOnlyState.GetNonce(signerForPart2.Address))); // --- Evaluate rewards at checkpoint (4E) --- long checkpoint4E = 4 * epochLength; diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/SyncInfoDecoderTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/SyncInfoDecoderTests.cs new file mode 100644 index 000000000000..db199e8b09e4 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/SyncInfoDecoderTests.cs @@ -0,0 +1,175 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.RLP; +using Nethermind.Xdc.Types; +using NUnit.Framework; +using System.Collections; + +namespace Nethermind.Xdc.Test; + +[TestFixture, Parallelizable(ParallelScope.All)] +public class SyncInfoDecoderTests +{ + public static IEnumerable SyncInfoCases + { + get + { + yield return new TestCaseData( + new SyncInfo( + new QuorumCertificate( + new BlockRoundInfo(Hash256.Zero, 1, 1), + [new Signature(new byte[64], 0), new Signature(new byte[64], 0)], + 0 + ), + new TimeoutCertificate( + 1, + [new Signature(new byte[64], 0), new Signature(new byte[64], 0)], + 0 + ) + ), + true + ); + + yield return new TestCaseData( + new SyncInfo( + new QuorumCertificate( + new BlockRoundInfo(Hash256.Zero, 1, 1), + [], + 0 + ), + new TimeoutCertificate(1, [], 0) + ), + false + ); + + yield return new TestCaseData( + new SyncInfo( + new QuorumCertificate( + new BlockRoundInfo(Hash256.Zero, ulong.MaxValue, long.MaxValue), + [], + ulong.MaxValue + ), + new TimeoutCertificate(ulong.MaxValue, [], ulong.MaxValue) + ), + true + ); + } + } + + [TestCaseSource(nameof(SyncInfoCases))] + public void EncodeDecode_RoundTrip_Matches_AllFields(SyncInfo syncInfo, bool useRlpStream) + { + var decoder = new SyncInfoDecoder(); + + Rlp encoded = decoder.Encode(syncInfo); + var stream = new RlpStream(encoded.Bytes); + SyncInfo decoded; + + if (useRlpStream) + { + decoded = decoder.Decode(stream); + } + else + { + Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(stream.Data.AsSpan()); + decoded = decoder.Decode(ref decoderContext); + } + + decoded.Should().BeEquivalentTo(syncInfo); + } + + [Test] + public void Encode_UseBothRlpStreamAndValueDecoderContext_IsEquivalentAfterReencoding() + { + SyncInfo syncInfo = new( + new QuorumCertificate( + new BlockRoundInfo(Hash256.Zero, 1, 1), + [new Signature(new byte[64], 0), new Signature(new byte[64], 0), new Signature(new byte[64], 0)], + 0 + ), + new TimeoutCertificate( + 1, + [new Signature(new byte[64], 0), new Signature(new byte[64], 0)], + 0 + ) + ); + + SyncInfoDecoder decoder = new(); + RlpStream stream = new RlpStream(decoder.GetLength(syncInfo)); + decoder.Encode(stream, syncInfo); + stream.Position = 0; + + // Decode with RlpStream + SyncInfo decodedStream = decoder.Decode(stream); + stream.Position = 0; + + // Decode with ValueDecoderContext + Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(stream.Data.AsSpan()); + SyncInfo decodedContext = decoder.Decode(ref decoderContext); + + // Both should be equivalent to original + decodedStream.Should().BeEquivalentTo(syncInfo); + decodedContext.Should().BeEquivalentTo(syncInfo); + decodedStream.Should().BeEquivalentTo(decodedContext); + } + + [Test] + public void TotalLength_Equals_GetLength() + { + SyncInfo syncInfo = new( + new QuorumCertificate( + new BlockRoundInfo(Hash256.Zero, 42, 42), + [new Signature(new byte[64], 0)], + 10 + ), + new TimeoutCertificate( + 41, + [new Signature(new byte[64], 1)], + 10 + ) + ); + + var decoder = new SyncInfoDecoder(); + Rlp encoded = decoder.Encode(syncInfo); + + int expectedTotal = decoder.GetLength(syncInfo, RlpBehaviors.None); + Assert.That(encoded.Bytes.Length, Is.EqualTo(expectedTotal), + "Encoded total length should match GetLength()."); + } + + [Test] + public void Encode_Null_ReturnsEmptySequence() + { + var decoder = new SyncInfoDecoder(); + + Rlp encoded = decoder.Encode(null!); + + Assert.That(encoded, Is.EqualTo(Rlp.OfEmptySequence)); + } + + [Test] + public void Decode_Null_ReturnsNull() + { + var decoder = new SyncInfoDecoder(); + var stream = new RlpStream(Rlp.OfEmptySequence.Bytes); + + SyncInfo decoded = decoder.Decode(stream); + + Assert.That(decoded, Is.Null); + } + + [Test] + public void Decode_EmptyByteArray_ValueDecoderContext_ReturnsNull() + { + var decoder = new SyncInfoDecoder(); + Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(Rlp.OfEmptySequence.Bytes); + + SyncInfo decoded = decoder.Decode(ref decoderContext); + + Assert.That(decoded, Is.Null); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs index 02a8317d50ff..4b545ba8cd02 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs @@ -8,7 +8,6 @@ namespace Nethermind.Xdc.Test; -[Parallelizable(ParallelScope.All)] internal class XdcReorgModuleTests { [Test] diff --git a/src/Nethermind/Nethermind.Xdc/Errors/QuorumCertificateException.cs b/src/Nethermind/Nethermind.Xdc/Errors/QuorumCertificateException.cs deleted file mode 100644 index 09cf944eeb3f..000000000000 --- a/src/Nethermind/Nethermind.Xdc/Errors/QuorumCertificateException.cs +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Blockchain; -using Nethermind.Xdc.Types; - -namespace Nethermind.Xdc.Errors; - -internal class QuorumCertificateException(QuorumCertificate certificate, string message) : BlockchainException(message) -{ - public QuorumCertificate Certificate { get; } = certificate; -} diff --git a/src/Nethermind/Nethermind.Xdc/IXdcSealer.cs b/src/Nethermind/Nethermind.Xdc/IXdcSealer.cs deleted file mode 100644 index cab6b4eb0b52..000000000000 --- a/src/Nethermind/Nethermind.Xdc/IXdcSealer.cs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Consensus; - -namespace Nethermind.Xdc; - -internal interface IXdcSealer : ISealer -{ - bool CanSeal(ulong round, XdcBlockHeader parentHash); -} diff --git a/src/Nethermind/Nethermind.Xdc/RLP/SyncInfoDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/SyncInfoDecoder.cs new file mode 100644 index 000000000000..0ebaf9d39ad8 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/RLP/SyncInfoDecoder.cs @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.RLP; +using Nethermind.Xdc.Types; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Nethermind.Xdc; + +internal class SyncInfoDecoder : RlpValueDecoder +{ + private readonly QuorumCertificateDecoder _quorumCertificateDecoder = new(); + private readonly TimeoutCertificateDecoder _timeoutCertificateDecoder = new(); + + protected override SyncInfo DecodeInternal(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (decoderContext.IsNextItemNull()) + return null; + + int sequenceLength = decoderContext.ReadSequenceLength(); + int endPosition = decoderContext.Position + sequenceLength; + + QuorumCertificate highestQuorumCert = _quorumCertificateDecoder.Decode(ref decoderContext, rlpBehaviors); + TimeoutCertificate highestTimeoutCert = _timeoutCertificateDecoder.Decode(ref decoderContext, rlpBehaviors); + + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + decoderContext.Check(endPosition); + } + + return new SyncInfo(highestQuorumCert, highestTimeoutCert); + } + + protected override SyncInfo DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (rlpStream.IsNextItemNull()) + return null; + + int sequenceLength = rlpStream.ReadSequenceLength(); + int endPosition = rlpStream.Position + sequenceLength; + + QuorumCertificate highestQuorumCert = _quorumCertificateDecoder.Decode(rlpStream, rlpBehaviors); + TimeoutCertificate highestTimeoutCert = _timeoutCertificateDecoder.Decode(rlpStream, rlpBehaviors); + + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + rlpStream.Check(endPosition); + } + + return new SyncInfo(highestQuorumCert, highestTimeoutCert); + } + + public Rlp Encode(SyncInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + return Rlp.OfEmptySequence; + + RlpStream rlpStream = new(GetLength(item, rlpBehaviors)); + Encode(rlpStream, item, rlpBehaviors); + + return new Rlp(rlpStream.Data.ToArray()); + } + + public override void Encode(RlpStream stream, SyncInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + { + stream.EncodeNullObject(); + return; + } + + stream.StartSequence(GetContentLength(item, rlpBehaviors)); + _quorumCertificateDecoder.Encode(stream, item.HighestQuorumCert, rlpBehaviors); + _timeoutCertificateDecoder.Encode(stream, item.HighestTimeoutCert, rlpBehaviors); + } + + public override int GetLength(SyncInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + return Rlp.LengthOfSequence(GetContentLength(item, rlpBehaviors)); + } + + public int GetContentLength(SyncInfo item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + { + if (item is null) + return 0; + + return _quorumCertificateDecoder.GetLength(item.HighestQuorumCert, rlpBehaviors) + + _timeoutCertificateDecoder.GetLength(item.HighestTimeoutCert, rlpBehaviors); + } +} diff --git a/src/Nethermind/Nethermind.Xdc/RLP/VoteDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/VoteDecoder.cs index 9116c18635a0..ff3b6504f5af 100644 --- a/src/Nethermind/Nethermind.Xdc/RLP/VoteDecoder.cs +++ b/src/Nethermind/Nethermind.Xdc/RLP/VoteDecoder.cs @@ -93,7 +93,7 @@ public override int GetLength(Vote item, RlpBehaviors rlpBehaviors) private int GetContentLength(Vote item, RlpBehaviors rlpBehaviors) { return - (rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing ? Rlp.LengthOfSequence(Signature.Size) : 0 + ((rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing ? Rlp.LengthOfSequence(Signature.Size) : 0) + Rlp.LengthOf(item.GapNumber) + _xdcBlockInfoDecoder.GetLength(item.ProposedBlockInfo, rlpBehaviors); } diff --git a/src/Nethermind/Nethermind.Xdc/XdcDbNames.cs b/src/Nethermind/Nethermind.Xdc/XdcDbNames.cs deleted file mode 100644 index 720beedcc121..000000000000 --- a/src/Nethermind/Nethermind.Xdc/XdcDbNames.cs +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -namespace Nethermind.Xdc; - -internal class XdcDbNames -{ - public static long HighestQcKey = 2000; - public static long HighestTimeoutCertKey = 2001; - public static long LockQcKey = 2002; - public static long HighestVotedRoundKey = 2004; -} diff --git a/src/Nethermind/Nethermind.Xdc/XdcModule.cs b/src/Nethermind/Nethermind.Xdc/XdcModule.cs index ae9d992edb66..10286cd98407 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcModule.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcModule.cs @@ -25,6 +25,8 @@ using Nethermind.Xdc.TxPool; using Nethermind.Api.Steps; using Nethermind.Synchronization; +using Nethermind.Synchronization.FastSync; +using Nethermind.Synchronization.ParallelSync; namespace Nethermind.Xdc; @@ -88,8 +90,9 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddSingleton() - // beacon sync strategy + // sync .AddSingleton() + .AddSingleton, XdcStateSyncAllocationStrategyFactory>() .AddSingleton() diff --git a/src/Nethermind/Nethermind.Xdc/XdcStateSyncAllocationStrategyFactory.cs b/src/Nethermind/Nethermind.Xdc/XdcStateSyncAllocationStrategyFactory.cs new file mode 100644 index 000000000000..74277a46fbbf --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcStateSyncAllocationStrategyFactory.cs @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Stats; +using Nethermind.Synchronization.FastSync; +using Nethermind.Synchronization.ParallelSync; +using Nethermind.Synchronization.Peers; +using Nethermind.Synchronization.Peers.AllocationStrategies; +using Nethermind.Synchronization.StateSync; + +namespace Nethermind.Xdc; + +public class XdcStateSyncAllocationStrategyFactory : StaticPeerAllocationStrategyFactory +{ + private static readonly IPeerAllocationStrategy DefaultStrategy = + new AllocationStrategy(new BySpeedStrategy(TransferSpeedType.NodeData, true)); + + public XdcStateSyncAllocationStrategyFactory() : base(DefaultStrategy) + { + } + + internal class AllocationStrategy : FilterPeerAllocationStrategy + { + public AllocationStrategy(IPeerAllocationStrategy strategy) : base(strategy) + { + } + + protected override bool Filter(PeerInfo peerInfo) + { + return peerInfo.CanGetSnapData() || peerInfo.SyncPeer.ProtocolVersion == 100; + } + } +} + diff --git a/src/Nethermind/Nethermind.slnx b/src/Nethermind/Nethermind.slnx index c64f1a920b75..6298a9f62d2f 100644 --- a/src/Nethermind/Nethermind.slnx +++ b/src/Nethermind/Nethermind.slnx @@ -31,6 +31,7 @@ + diff --git a/tools/JitAsm/DisassemblyParser.cs b/tools/JitAsm/DisassemblyParser.cs new file mode 100644 index 000000000000..dcbb9687af8f --- /dev/null +++ b/tools/JitAsm/DisassemblyParser.cs @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text; +using System.Text.RegularExpressions; + +namespace JitAsm; + +internal static partial class DisassemblyParser +{ + // Pattern to detect the start of a method's disassembly + // Example: ; Assembly listing for method Namespace.Type:Method(args) + [GeneratedRegex(@"^; Assembly listing for method (?.+)$", RegexOptions.Compiled | RegexOptions.Multiline)] + private static partial Regex MethodHeaderPattern(); + + // Pattern to detect end of method disassembly (next method or end) + [GeneratedRegex(@"^; Total bytes of code", RegexOptions.Compiled | RegexOptions.Multiline)] + private static partial Regex MethodEndPattern(); + + public static string Parse(string jitOutput, bool lastOnly = false) + { + if (string.IsNullOrWhiteSpace(jitOutput)) + { + return string.Empty; + } + + var result = new StringBuilder(); + var matches = MethodHeaderPattern().Matches(jitOutput); + + if (matches.Count == 0) + { + // No method headers found, return raw output if it looks like assembly + if (jitOutput.Contains("mov") || jitOutput.Contains("call") || jitOutput.Contains("ret")) + { + return jitOutput.Trim(); + } + return string.Empty; + } + + // In tier1 mode, JitDisasm captures both Tier-0 and Tier-1 compilations. + // We want the LAST compilation (Tier-1 with full optimizations). + int startIdx = lastOnly ? matches.Count - 1 : 0; + + for (int i = startIdx; i < matches.Count; i++) + { + var match = matches[i]; + var startIndex = match.Index; + + // Find the end of this method's disassembly + int endIndex = (i + 1 < matches.Count) ? matches[i + 1].Index : jitOutput.Length; + + // Extract this method's disassembly + var methodAsm = jitOutput[startIndex..endIndex].TrimEnd(); + + // Find "Total bytes of code" line and include it + var totalBytesMatch = MethodEndPattern().Match(methodAsm); + if (totalBytesMatch.Success) + { + // Find end of line after "Total bytes of code" + var lineEnd = methodAsm.IndexOf('\n', totalBytesMatch.Index); + if (lineEnd > 0) + { + methodAsm = methodAsm[..(lineEnd + 1)].TrimEnd(); + } + } + + if (result.Length > 0) + { + result.AppendLine(); + result.AppendLine(new string('-', 80)); + result.AppendLine(); + } + + result.AppendLine(methodAsm); + } + + return result.ToString().Trim(); + } + + public static IEnumerable ParseMethods(string jitOutput) + { + if (string.IsNullOrWhiteSpace(jitOutput)) + { + yield break; + } + + var matches = MethodHeaderPattern().Matches(jitOutput); + + for (int i = 0; i < matches.Count; i++) + { + var match = matches[i]; + var methodName = match.Groups["method"].Value; + var startIndex = match.Index; + + int endIndex = (i + 1 < matches.Count) ? matches[i + 1].Index : jitOutput.Length; + + var methodAsm = jitOutput[startIndex..endIndex].TrimEnd(); + + yield return new MethodDisassembly + { + MethodName = methodName, + Assembly = methodAsm + }; + } + } +} + +internal sealed class MethodDisassembly +{ + public required string MethodName { get; init; } + public required string Assembly { get; init; } +} diff --git a/tools/JitAsm/InstructionAnnotator.cs b/tools/JitAsm/InstructionAnnotator.cs new file mode 100644 index 000000000000..068d5ab34853 --- /dev/null +++ b/tools/JitAsm/InstructionAnnotator.cs @@ -0,0 +1,441 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text; +using System.Text.RegularExpressions; + +namespace JitAsm; + +internal static partial class InstructionAnnotator +{ + // Matches JIT assembly instruction lines: + // " add rax, rcx" + // " mov dword ptr [rbp+0x10], eax" + [GeneratedRegex(@"^\s+(?[a-z]\w*)\s+(?.+)$", RegexOptions.IgnoreCase)] + private static partial Regex InstructionLineRegex(); + + // Matches zero-operand instructions: " ret" or " nop" + [GeneratedRegex(@"^\s+(?[a-z]\w*)\s*$", RegexOptions.IgnoreCase)] + private static partial Regex ZeroOperandRegex(); + + // 64-bit registers + private static readonly HashSet Regs64 = new(StringComparer.OrdinalIgnoreCase) + { + "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rsp", "rbp", + "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15" + }; + + // 32-bit registers + private static readonly HashSet Regs32 = new(StringComparer.OrdinalIgnoreCase) + { + "eax", "ebx", "ecx", "edx", "esi", "edi", "esp", "ebp", + "r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d" + }; + + // 16-bit registers + private static readonly HashSet Regs16 = new(StringComparer.OrdinalIgnoreCase) + { + "ax", "bx", "cx", "dx", "si", "di", "sp", "bp", + "r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w" + }; + + // 8-bit registers + private static readonly HashSet Regs8 = new(StringComparer.OrdinalIgnoreCase) + { + "al", "bl", "cl", "dl", "sil", "dil", "spl", "bpl", "ah", "bh", "ch", "dh", + "r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b" + }; + + // JIT mnemonic → uops.info mnemonic mapping for conditional jumps + // JIT uses Intel-style aliases (je, jne, ja, etc.) but uops.info uses the + // canonical forms (jz, jnz, jnbe, etc.) + private static readonly Dictionary MnemonicAliases = new(StringComparer.OrdinalIgnoreCase) + { + ["je"] = "jz", + ["jne"] = "jnz", + ["ja"] = "jnbe", + ["jae"] = "jnb", + ["jb"] = "jb", // canonical + ["jbe"] = "jna", + ["jg"] = "jnle", + ["jge"] = "jnl", + ["jl"] = "jnge", + ["jle"] = "jng", + ["jc"] = "jb", + ["jnc"] = "jnb", + ["jp"] = "jp", // canonical + ["jnp"] = "jnp", // canonical + ["js"] = "js", // canonical + ["jns"] = "jns", // canonical + ["jo"] = "jo", // canonical + ["jno"] = "jno", // canonical + ["cmove"] = "cmovz", + ["cmovne"] = "cmovnz", + ["cmova"] = "cmovnbe", + ["cmovae"] = "cmovnb", + ["cmovb"] = "cmovb", // canonical + ["cmovbe"] = "cmovna", + ["cmovg"] = "cmovnle", + ["cmovge"] = "cmovnl", + ["cmovl"] = "cmovnge", + ["cmovle"] = "cmovng", + ["sete"] = "setz", + ["setne"] = "setnz", + ["seta"] = "setnbe", + ["setae"] = "setnb", + ["setb"] = "setb", // canonical + ["setbe"] = "setna", + ["setg"] = "setnle", + ["setge"] = "setnl", + ["setl"] = "setnge", + ["setle"] = "setng", + }; + + public static string Annotate(string disassembly, InstructionDb db) + { + var sb = new StringBuilder(); + var lines = JoinContinuationLines(disassembly.Split('\n')); + + foreach (string rawLine in lines) + { + string line = rawLine.TrimEnd('\r'); + + // Skip comment lines, labels, directives + if (IsNonInstructionLine(line)) + { + sb.AppendLine(line); + continue; + } + + var match = InstructionLineRegex().Match(line); + if (match.Success) + { + string mnemonic = match.Groups["mnemonic"].Value.ToLowerInvariant(); + string operandsRaw = match.Groups["operands"].Value.Trim(); + + // Skip annotations for calls and jumps to labels + if (ShouldSkipAnnotation(mnemonic, operandsRaw)) + { + sb.AppendLine(line); + continue; + } + + string pattern = ClassifyOperands(operandsRaw, mnemonic); + + // Try the original mnemonic first, then any alias + string lookupMnemonic = MnemonicAliases.TryGetValue(mnemonic, out var alias) + ? alias : mnemonic; + var info = db.Lookup(mnemonic, pattern) + ?? (lookupMnemonic != mnemonic ? db.Lookup(lookupMnemonic, pattern) : null); + + if (info is not null) + { + string annotation = FormatAnnotation(info); + // Pad the line to align annotations + int padTo = Math.Max(line.Length + 1, 55); + sb.Append(line.PadRight(padTo)); + sb.AppendLine(annotation); + } + else + { + sb.AppendLine(line); + } + continue; + } + + // Try zero-operand match + var zeroMatch = ZeroOperandRegex().Match(line); + if (zeroMatch.Success) + { + string mnemonic = zeroMatch.Groups["mnemonic"].Value.ToLowerInvariant(); + if (!ShouldSkipAnnotation(mnemonic, "")) + { + string zeroLookup = MnemonicAliases.TryGetValue(mnemonic, out var zAlias) + ? zAlias : mnemonic; + var info = db.Lookup(mnemonic, "") + ?? (zeroLookup != mnemonic ? db.Lookup(zeroLookup, "") : null); + if (info is not null) + { + string annotation = FormatAnnotation(info); + int padTo = Math.Max(line.Length + 1, 55); + sb.Append(line.PadRight(padTo)); + sb.AppendLine(annotation); + continue; + } + } + } + + sb.AppendLine(line); + } + + return sb.ToString().TrimEnd(); + } + + /// + /// Joins JIT output continuation lines. The JIT wraps long lines at ~80 chars: + /// " call \n[System.Threading.ThreadLocal`1[...]:get_Value():...]\n" + /// "; Assembly listing for method \nNamespace.Type:Method(...)\n" + /// This joins them so each logical line is a single string. + /// + private static List JoinContinuationLines(string[] rawLines) + { + var result = new List(rawLines.Length); + for (int i = 0; i < rawLines.Length; i++) + { + string line = rawLines[i].TrimEnd('\r'); + + // Keep joining while the next line looks like a continuation + while (i + 1 < rawLines.Length) + { + string next = rawLines[i + 1].TrimEnd('\r'); + if (IsContinuationLine(next)) + { + // Preserve a single space between joined parts so "call \n[Type:Method]" + // becomes "call [Type:Method]" rather than "call[Type:Method]" + line = line.TrimEnd() + " " + next.TrimStart(); + i++; + } + else + { + break; + } + } + + result.Add(line); + } + return result; + } + + /// + /// A line is a continuation if it doesn't match any known "primary" line type: + /// blank, comment (;), label (G_M...:), instruction (leading whitespace), data (RWD), or alignment. + /// + private static bool IsContinuationLine(string line) + { + if (line.Length == 0) return false; + + ReadOnlySpan trimmed = line.AsSpan().TrimEnd('\r'); + if (trimmed.Length == 0) return false; + + // Instructions start with whitespace + if (char.IsWhiteSpace(trimmed[0])) return false; + + // Comments start with ';' + if (trimmed[0] == ';') return false; + + // Labels: "G_M000_IG01:" or similar identifiers ending with ':' + // Check if line contains ':' and starts with a label-like pattern + if (trimmed.StartsWith("G_M", StringComparison.Ordinal) && trimmed.Contains(":", StringComparison.Ordinal)) + return false; + + // Read-only data table entries: "RWD00 dd ..." + if (trimmed.StartsWith("RWD", StringComparison.Ordinal)) return false; + + // Alignment directives: "align [N bytes for IG...]" + if (trimmed.StartsWith("align", StringComparison.OrdinalIgnoreCase)) return false; + + // Everything else is a continuation of the previous line + return true; + } + + private static bool IsNonInstructionLine(string line) + { + if (line.Length == 0) return true; + + // Instructions always start with whitespace (indented). + // Labels, data tables, directives, and other non-instruction lines start at column 0. + if (line[0] == ';') return true; // Comment at column 0 + if (!char.IsWhiteSpace(line[0])) return true; // Labels (G_M000_IG01:), data (RWD00), directives, etc. + + // Indented comments: " ; comment" + ReadOnlySpan trimmed = line.AsSpan().TrimStart(); + if (trimmed.Length > 0 && trimmed[0] == ';') return true; + + return false; + } + + private static bool ShouldSkipAnnotation(string mnemonic, string operands) + { + // Skip calls (to runtime helpers, methods, etc.) + if (mnemonic == "call") return true; + + // Skip ret - uops.info TP_unrolled is a microbenchmark artifact (return stack buffer + // mispredictions make the measurement meaningless for real code) + if (mnemonic == "ret") return true; + + // Skip int3/nop - not meaningful for performance analysis + if (mnemonic is "int3" or "nop" or "int") return true; + + return false; + } + + internal static string ClassifyOperands(string operandsRaw, string? mnemonic = null) + { + // Handle trailing comments after operands: "rax, rcx ; some comment" + int commentIdx = operandsRaw.IndexOf(';'); + if (commentIdx >= 0) + operandsRaw = operandsRaw[..commentIdx].TrimEnd(); + + if (string.IsNullOrWhiteSpace(operandsRaw)) + return ""; + + // Split operands by comma, but respect brackets for memory operands + var operands = SplitOperands(operandsRaw); + var parts = new List(); + + for (int i = 0; i < operands.Count; i++) + { + string op = operands[i].Trim(); + + // LEA's second operand is an address expression, not a memory load + // uops.info classifies it as "agen" (address generation) + if (mnemonic is "lea" && i == 1 && op.Contains('[')) + { + parts.Add("agen"); + continue; + } + + string classified = ClassifySingleOperand(op); + if (classified.Length > 0) + parts.Add(classified); + } + + return string.Join(",", parts); + } + + private static List SplitOperands(string operands) + { + var result = new List(); + int depth = 0; + int start = 0; + + for (int i = 0; i < operands.Length; i++) + { + char c = operands[i]; + if (c == '[') depth++; + else if (c == ']') depth--; + else if (c == ',' && depth == 0) + { + result.Add(operands[start..i]); + start = i + 1; + } + } + + result.Add(operands[start..]); + return result; + } + + private static string ClassifySingleOperand(string op) + { + // Memory operand: "dword ptr [rbp+10h]", "qword ptr [rsp+20h]", "[rax]" + if (op.Contains('[')) + { + if (op.Contains("zmmword ptr", StringComparison.OrdinalIgnoreCase)) return "m512"; + if (op.Contains("ymmword ptr", StringComparison.OrdinalIgnoreCase)) return "m256"; + if (op.Contains("xmmword ptr", StringComparison.OrdinalIgnoreCase)) return "m128"; + if (op.Contains("qword ptr", StringComparison.OrdinalIgnoreCase)) return "m64"; + // gword ptr = GC-tracked pointer-width memory (.NET JIT specific, equivalent to qword on x64) + if (op.Contains("gword ptr", StringComparison.OrdinalIgnoreCase)) return "m64"; + // bword ptr = pointer-width memory without GC tracking (.NET JIT specific, equivalent to qword on x64) + if (op.Contains("bword ptr", StringComparison.OrdinalIgnoreCase)) return "m64"; + if (op.Contains("dword ptr", StringComparison.OrdinalIgnoreCase)) return "m32"; + if (op.Contains("word ptr", StringComparison.OrdinalIgnoreCase) && + !op.Contains("dword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("qword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("gword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("bword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("xmmword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("ymmword", StringComparison.OrdinalIgnoreCase) && + !op.Contains("zmmword", StringComparison.OrdinalIgnoreCase)) + return "m16"; + if (op.Contains("byte ptr", StringComparison.OrdinalIgnoreCase)) return "m8"; + return "m"; + } + + // Register operands + string regName = op.Trim(); + + // ZMM registers + if (regName.StartsWith("zmm", StringComparison.OrdinalIgnoreCase)) return "zmm"; + // YMM registers + if (regName.StartsWith("ymm", StringComparison.OrdinalIgnoreCase)) return "ymm"; + // XMM registers + if (regName.StartsWith("xmm", StringComparison.OrdinalIgnoreCase)) return "xmm"; + // K mask registers + if (regName.StartsWith("k", StringComparison.OrdinalIgnoreCase) && regName.Length <= 2 && + regName.Length > 1 && char.IsDigit(regName[1])) return "k"; + + if (Regs64.Contains(regName)) return "r64"; + if (Regs32.Contains(regName)) return "r32"; + if (Regs16.Contains(regName)) return "r16"; + if (Regs8.Contains(regName)) return "r8"; + + // Immediate: hex (0x1A, 1Ah), decimal, or negative + if (IsImmediate(regName)) + { + // Try to determine imm8 vs imm32 from value range + if (TryParseImmediate(regName, out long value)) + { + return value is >= -128 and <= 255 ? "imm8" : "imm32"; + } + return "imm"; + } + + // Label reference (for jumps) - strip SHORT/NEAR prefix added by JIT + if (regName.StartsWith("SHORT ", StringComparison.OrdinalIgnoreCase)) + regName = regName[6..].TrimStart(); + if (regName.StartsWith("NEAR ", StringComparison.OrdinalIgnoreCase)) + regName = regName[5..].TrimStart(); + + if (regName.StartsWith("G_M", StringComparison.OrdinalIgnoreCase)) + return "rel"; + + // Unknown + return ""; + } + + private static bool IsImmediate(string op) + { + if (op.Length == 0) return false; + + // Hex: 0x prefix or trailing h + if (op.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) return true; + if (op.EndsWith('h') || op.EndsWith('H')) + { + return op[..^1].All(c => char.IsAsciiHexDigit(c) || c == '-'); + } + + // Decimal (possibly negative) + return op.All(c => char.IsDigit(c) || c == '-'); + } + + private static bool TryParseImmediate(string op, out long value) + { + value = 0; + + if (op.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + return long.TryParse(op.AsSpan(2), System.Globalization.NumberStyles.HexNumber, + System.Globalization.CultureInfo.InvariantCulture, out value); + + if (op.EndsWith('h') || op.EndsWith('H')) + return long.TryParse(op.AsSpan(0, op.Length - 1), System.Globalization.NumberStyles.HexNumber, + System.Globalization.CultureInfo.InvariantCulture, out value); + + return long.TryParse(op, out value); + } + + private static string FormatAnnotation(InstructionInfo info) + { + var sb = new StringBuilder(); + sb.Append("; ["); + sb.Append($"TP:{info.Throughput:F2}"); + sb.Append($" | Lat:{info.Latency,2}"); + sb.Append($" | Uops:{info.Uops}"); + if (info.Ports is not null) + { + sb.Append($" | {info.Ports}"); + } + sb.Append(']'); + return sb.ToString(); + } +} diff --git a/tools/JitAsm/InstructionDb.cs b/tools/JitAsm/InstructionDb.cs new file mode 100644 index 000000000000..df39ca110944 --- /dev/null +++ b/tools/JitAsm/InstructionDb.cs @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace JitAsm; + +internal sealed class InstructionInfo +{ + public required string Mnemonic { get; init; } + public required string OperandPattern { get; init; } + public float Throughput { get; init; } + public int Latency { get; init; } + public int Uops { get; init; } + public string? Ports { get; init; } +} + +internal sealed class InstructionDb +{ + private const string Magic = "UOPS"; + private const ushort Version = 2; + + private readonly Dictionary> _instructions = new(StringComparer.OrdinalIgnoreCase); + + public string ArchName { get; } + + public InstructionDb(string archName) + { + ArchName = archName; + } + + public void Add(InstructionInfo info) + { + string key = info.Mnemonic.ToLowerInvariant(); + if (!_instructions.TryGetValue(key, out var list)) + { + list = []; + _instructions[key] = list; + } + list.Add(info); + } + + public int Count => _instructions.Sum(kv => kv.Value.Count); + + public InstructionInfo? Lookup(string mnemonic, string operandPattern) + { + if (!_instructions.TryGetValue(mnemonic, out var forms)) + return null; + + // Exact match + foreach (var form in forms) + { + if (string.Equals(form.OperandPattern, operandPattern, StringComparison.OrdinalIgnoreCase)) + return form; + } + + // Relaxed match: ignore register width differences (r32 ≈ r64 for same instruction class) + string relaxed = RelaxPattern(operandPattern); + foreach (var form in forms) + { + if (string.Equals(RelaxPattern(form.OperandPattern), relaxed, StringComparison.OrdinalIgnoreCase)) + return form; + } + + // Mnemonic-only match for zero-operand instructions (ret, nop, etc.) + if (operandPattern.Length == 0) + { + foreach (var form in forms) + { + if (form.OperandPattern.Length == 0) + return form; + } + } + + return null; + } + + private static string RelaxPattern(string pattern) + { + // Normalize register widths: r8/r16/r32/r64 → r, m8/m16/m32/m64/m128/m256/m512 → m, imm8/imm32 → imm + return pattern + .Replace("r64", "r").Replace("r32", "r").Replace("r16", "r").Replace("r8", "r") + .Replace("m512", "m").Replace("m256", "m").Replace("m128", "m") + .Replace("m64", "m").Replace("m32", "m").Replace("m16", "m").Replace("m8", "m") + .Replace("imm32", "imm").Replace("imm8", "imm"); + } + + public void Save(string path) + { + using var stream = File.Create(path); + using var writer = new BinaryWriter(stream); + + // Header + writer.Write(Magic.ToCharArray()); + writer.Write(Version); + writer.Write(ArchName); + + // Count all entries + int entryCount = Count; + writer.Write(entryCount); + + // Entries + foreach (var (_, forms) in _instructions) + { + foreach (var info in forms) + { + writer.Write(info.Mnemonic); + writer.Write(info.OperandPattern); + writer.Write(info.Throughput); + writer.Write((short)info.Latency); + writer.Write((short)info.Uops); + writer.Write(info.Ports ?? string.Empty); + } + } + } + + public static InstructionDb Load(string path) + { + using var stream = File.OpenRead(path); + using var reader = new BinaryReader(stream); + + // Header + char[] magic = reader.ReadChars(4); + if (new string(magic) != Magic) + throw new InvalidDataException($"Invalid instruction database file: bad magic"); + + ushort version = reader.ReadUInt16(); + if (version != Version) + throw new InvalidDataException($"Unsupported instruction database version: {version}"); + + string archName = reader.ReadString(); + int entryCount = reader.ReadInt32(); + + var db = new InstructionDb(archName); + + for (int i = 0; i < entryCount; i++) + { + string mnemonic = reader.ReadString(); + string operandPattern = reader.ReadString(); + float throughput = reader.ReadSingle(); + short latency = reader.ReadInt16(); + short uops = reader.ReadInt16(); + string ports = reader.ReadString(); + + db.Add(new InstructionInfo + { + Mnemonic = mnemonic, + OperandPattern = operandPattern, + Throughput = throughput, + Latency = latency, + Uops = uops, + Ports = ports.Length > 0 ? ports : null + }); + } + + return db; + } +} diff --git a/tools/JitAsm/InstructionDbBuilder.cs b/tools/JitAsm/InstructionDbBuilder.cs new file mode 100644 index 000000000000..7daf9cfa517b --- /dev/null +++ b/tools/JitAsm/InstructionDbBuilder.cs @@ -0,0 +1,265 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Globalization; +using System.Xml; + +namespace JitAsm; + +internal static class InstructionDbBuilder +{ + // Map from CLI flag value to uops.info architecture name + private static readonly Dictionary ArchMap = new(StringComparer.OrdinalIgnoreCase) + { + ["alder-lake"] = "ADL-P", + ["rocket-lake"] = "RKL", + ["ice-lake"] = "ICL", + ["tiger-lake"] = "TGL", + ["skylake"] = "SKL", + ["zen4"] = "ZEN4", + ["zen3"] = "ZEN3", + ["zen2"] = "ZEN2", + }; + + // Fallback chain: if the target arch has no data, try these in order + private static readonly Dictionary FallbackChain = new(StringComparer.OrdinalIgnoreCase) + { + ["ADL-P"] = ["RKL", "TGL", "ICL", "SKL"], + ["RKL"] = ["TGL", "ICL", "SKL"], + ["TGL"] = ["ICL", "SKL"], + ["ICL"] = ["SKL", "SKX", "HSW"], + ["SKL"] = ["SKX", "HSW"], + ["ZEN4"] = ["ZEN3", "ZEN2", "ZEN+"], + ["ZEN3"] = ["ZEN2", "ZEN+"], + ["ZEN2"] = ["ZEN+"], + }; + + public static string ResolveArchName(string cliValue) + { + return ArchMap.TryGetValue(cliValue, out var name) ? name : cliValue.ToUpperInvariant(); + } + + public static IReadOnlyCollection SupportedArchitectures => ArchMap.Keys; + + public static InstructionDb Build(string xmlPath, string archCliValue) + { + string targetArch = ResolveArchName(archCliValue); + string[] fallbacks = FallbackChain.TryGetValue(targetArch, out var fb) ? fb : []; + + var db = new InstructionDb(targetArch); + var seen = new HashSet(StringComparer.OrdinalIgnoreCase); + + using var stream = File.OpenRead(xmlPath); + using var reader = XmlReader.Create(stream, new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }); + + while (reader.Read()) + { + if (reader.NodeType != XmlNodeType.Element || reader.Name != "instruction") + continue; + + string? asm = reader.GetAttribute("asm"); + if (asm is null) + continue; + + // Clean the asm mnemonic: remove prefixes like "{load} " or "{store} " + string mnemonic = CleanMnemonic(asm); + if (mnemonic.Length == 0) + continue; + + // Read the instruction subtree + string instructionXml = reader.ReadOuterXml(); + var instrDoc = new XmlDocument(); + instrDoc.LoadXml(instructionXml); + var instrNode = instrDoc.DocumentElement!; + + // Parse operands + string operandPattern = BuildOperandPattern(instrNode); + + // Dedup key + string key = $"{mnemonic.ToLowerInvariant()}|{operandPattern}"; + if (seen.Contains(key)) + continue; + + // Find measurement for target architecture (with fallback) + var measurement = FindMeasurement(instrNode, targetArch, fallbacks); + if (measurement is null) + continue; + + float throughput = ParseFloat(measurement.GetAttribute("TP_unrolled")) + ?? ParseFloat(measurement.GetAttribute("TP_loop")) + ?? 0; + + int uops = ParseInt(measurement.GetAttribute("uops")) ?? 0; + string? ports = measurement.GetAttribute("ports"); + if (string.IsNullOrEmpty(ports)) ports = null; + + // Get max latency from child elements + int latency = 0; + foreach (XmlNode child in measurement.ChildNodes) + { + if (child is XmlElement latencyEl && latencyEl.Name == "latency") + { + int? cycles = ParseInt(latencyEl.GetAttribute("cycles")) + ?? ParseInt(latencyEl.GetAttribute("cycles_mem")) + ?? ParseInt(latencyEl.GetAttribute("cycles_addr")); + if (cycles.HasValue && cycles.Value > latency) + latency = cycles.Value; + } + } + + seen.Add(key); + db.Add(new InstructionInfo + { + Mnemonic = mnemonic.ToLowerInvariant(), + OperandPattern = operandPattern, + Throughput = throughput, + Latency = latency, + Uops = uops, + Ports = ports + }); + } + + return db; + } + + private static string CleanMnemonic(string asm) + { + // Remove assembler hints like "{load} ", "{store} ", "{vex} ", "{evex} " + ReadOnlySpan span = asm.AsSpan().Trim(); + while (span.Length > 0 && span[0] == '{') + { + int end = span.IndexOf('}'); + if (end < 0) break; + span = span[(end + 1)..].TrimStart(); + } + + // Take only the first word (mnemonic), skip any operand hints + int space = span.IndexOf(' '); + if (space > 0) + span = span[..space]; + + return span.ToString(); + } + + private static string BuildOperandPattern(XmlElement instrNode) + { + var parts = new List(); + foreach (XmlNode child in instrNode.ChildNodes) + { + if (child is not XmlElement operandEl || operandEl.Name != "operand") + continue; + + // Skip suppressed operands (flags, implicit registers) + if (operandEl.GetAttribute("suppressed") == "1") + continue; + + string? type = operandEl.GetAttribute("type"); + string? width = operandEl.GetAttribute("width"); + + string part = type switch + { + "reg" => ClassifyReg(width, operandEl.InnerText), + "mem" => ClassifyMem(width), + "agen" => "agen", + "imm" => ClassifyImm(width), + "relbr" => "rel", + _ => "" + }; + + if (part.Length > 0) + parts.Add(part); + } + + return string.Join(",", parts); + } + + private static string ClassifyReg(string? width, string? regNames) + { + // Check register names for xmm/ymm/zmm/mm/k + if (regNames is not null) + { + string firstReg = regNames.Split(',')[0].Trim().ToUpperInvariant(); + if (firstReg.StartsWith("ZMM")) return "zmm"; + if (firstReg.StartsWith("YMM")) return "ymm"; + if (firstReg.StartsWith("XMM")) return "xmm"; + if (firstReg.StartsWith("MM")) return "mm"; + if (firstReg.StartsWith("K")) return "k"; + } + + return width switch + { + "8" => "r8", + "16" => "r16", + "32" => "r32", + "64" => "r64", + "128" => "xmm", + "256" => "ymm", + "512" => "zmm", + _ => "r" + }; + } + + private static string ClassifyMem(string? width) + { + return width switch + { + "8" => "m8", + "16" => "m16", + "32" => "m32", + "64" => "m64", + "128" => "m128", + "256" => "m256", + "512" => "m512", + _ => "m" + }; + } + + private static string ClassifyImm(string? width) + { + return width switch + { + "8" => "imm8", + "16" => "imm16", + "32" => "imm32", + _ => "imm" + }; + } + + private static XmlElement? FindMeasurement(XmlElement instrNode, string targetArch, string[] fallbacks) + { + // Try target arch first, then fallbacks + var archsToTry = new List { targetArch }; + archsToTry.AddRange(fallbacks); + + foreach (string archName in archsToTry) + { + foreach (XmlNode child in instrNode.ChildNodes) + { + if (child is not XmlElement archEl || archEl.Name != "architecture") + continue; + if (archEl.GetAttribute("name") != archName) + continue; + + foreach (XmlNode archChild in archEl.ChildNodes) + { + if (archChild is XmlElement measureEl && measureEl.Name == "measurement") + return measureEl; + } + } + } + + return null; + } + + private static float? ParseFloat(string? value) + { + if (string.IsNullOrEmpty(value)) return null; + return float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out float result) ? result : null; + } + + private static int? ParseInt(string? value) + { + if (string.IsNullOrEmpty(value)) return null; + return int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int result) ? result : null; + } +} diff --git a/tools/JitAsm/JitAsm.csproj b/tools/JitAsm/JitAsm.csproj new file mode 100644 index 000000000000..a505e193dda2 --- /dev/null +++ b/tools/JitAsm/JitAsm.csproj @@ -0,0 +1,12 @@ + + + Exe + + + + + + + + + diff --git a/tools/JitAsm/JitAsm.slnx b/tools/JitAsm/JitAsm.slnx new file mode 100644 index 000000000000..b30012a5e817 --- /dev/null +++ b/tools/JitAsm/JitAsm.slnx @@ -0,0 +1,3 @@ + + + diff --git a/tools/JitAsm/JitRunner.cs b/tools/JitAsm/JitRunner.cs new file mode 100644 index 000000000000..b348446161f9 --- /dev/null +++ b/tools/JitAsm/JitRunner.cs @@ -0,0 +1,269 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Diagnostics; +using System.Text; + +namespace JitAsm; + +internal sealed class JitRunner(string assemblyPath, string? typeName, string methodName, string? typeParams, string? classTypeParams, bool verbose, bool tier1 = false) +{ + public async Task RunSinglePassAsync(IReadOnlyList? cctorsToInit = null) + { + var result = await RunJitProcessAsync(cctorsToInit); + return result; + } + + public async Task RunTwoPassAsync() + { + // Pass 1: Run without cctor initialization to detect static constructor calls + var pass1Result = await RunJitProcessAsync(null); + + if (!pass1Result.Success) + { + return pass1Result; + } + + // Detect static constructors in the output + var detectedCctors = StaticCtorDetector.DetectStaticCtors(pass1Result.Output ?? string.Empty); + + if (detectedCctors.Count == 0) + { + // No cctors detected, return pass 1 result + return pass1Result; + } + + // Pass 2: Run with cctor initialization + var pass2Result = await RunJitProcessAsync(detectedCctors); + + return new JitResult + { + Success = pass2Result.Success, + Output = pass2Result.Output, + Error = pass2Result.Error, + Pass1Output = verbose ? pass1Result.Output : null, + DetectedCctors = detectedCctors + }; + } + + private async Task RunJitProcessAsync(IReadOnlyList? cctorsToInit) + { + // Get the path to the JitAsm executable + var (executablePath, argumentPrefix) = GetExecutablePath(); + + // Build the method pattern for JitDisasm + var methodPattern = BuildMethodPattern(); + + var startInfo = new ProcessStartInfo + { + FileName = executablePath, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + + // Set JIT environment variables - these must be set before the process starts + if (tier1) + { + // Tier-1 simulation: enable tiered compilation so the method compiles at Tier-0 first, + // then gets recompiled at Tier-1 with full optimizations after the cctors have run. + startInfo.EnvironmentVariables["DOTNET_TieredCompilation"] = "1"; + startInfo.EnvironmentVariables["DOTNET_TieredPGO"] = "1"; + startInfo.EnvironmentVariables["DOTNET_TC_CallCountThreshold"] = "1"; + startInfo.EnvironmentVariables["DOTNET_TC_CallCountingDelayMs"] = "0"; + } + else + { + startInfo.EnvironmentVariables["DOTNET_TieredCompilation"] = "0"; + startInfo.EnvironmentVariables["DOTNET_TC_QuickJit"] = "0"; + } + startInfo.EnvironmentVariables["DOTNET_JitDisasm"] = methodPattern; + startInfo.EnvironmentVariables["DOTNET_JitDiffableDasm"] = "1"; + if (verbose) + startInfo.EnvironmentVariables["JITASM_VERBOSE"] = "1"; + + // Build arguments for internal runner + var args = new StringBuilder(); + if (argumentPrefix is not null) + { + args.Append(EscapeArg(argumentPrefix)); + args.Append(' '); + } + args.Append("--internal-runner "); + args.Append(EscapeArg(assemblyPath)); + args.Append(' '); + args.Append(EscapeArg(methodName)); + + if (typeName is not null) + { + args.Append(" --type "); + args.Append(EscapeArg(typeName)); + } + + if (typeParams is not null) + { + args.Append(" --type-params "); + args.Append(EscapeArg(typeParams)); + } + + if (classTypeParams is not null) + { + args.Append(" --class-type-params "); + args.Append(EscapeArg(classTypeParams)); + } + + if (cctorsToInit is not null && cctorsToInit.Count > 0) + { + args.Append(" --init-cctors "); + args.Append(EscapeArg(string.Join(";", cctorsToInit))); + } + + if (tier1) + { + args.Append(" --tier1"); + } + + startInfo.Arguments = args.ToString(); + + if (verbose) + { + Spectre.Console.AnsiConsole.MarkupLine($"[grey]Running: {Spectre.Console.Markup.Escape(executablePath)} {Spectre.Console.Markup.Escape(startInfo.Arguments)}[/]"); + Spectre.Console.AnsiConsole.MarkupLine($"[grey]DOTNET_JitDisasm={Spectre.Console.Markup.Escape(methodPattern)}[/]"); + Spectre.Console.AnsiConsole.WriteLine(); + } + + try + { + using var process = Process.Start(startInfo); + if (process is null) + { + return new JitResult { Success = false, Error = "Failed to start process" }; + } + + var stdoutTask = process.StandardOutput.ReadToEndAsync(); + var stderrTask = process.StandardError.ReadToEndAsync(); + + await process.WaitForExitAsync(); + + var stdout = await stdoutTask; + var stderr = await stderrTask; + + // Parse the JIT output from stdout (JIT diagnostics can go to either stream) + // In tier1 mode, multiple compilations are captured; take only the last (Tier-1) + var disassembly = DisassemblyParser.Parse(stdout, lastOnly: tier1); + + if (string.IsNullOrWhiteSpace(disassembly)) + { + // Try stderr + disassembly = DisassemblyParser.Parse(stderr, lastOnly: tier1); + } + + if (string.IsNullOrWhiteSpace(disassembly)) + { + // Check if there's an error + if (!string.IsNullOrWhiteSpace(stderr) && stderr.Contains("Error")) + { + return new JitResult { Success = false, Error = stderr.Trim() }; + } + + // No disassembly found + return new JitResult + { + Success = false, + Error = "No disassembly output found. Method may not exist or JIT output was not captured.", + Output = $"stdout:\n{stdout}\n\nstderr:\n{stderr}" + }; + } + + return new JitResult + { + Success = true, + Output = disassembly + }; + } + catch (Exception ex) + { + return new JitResult { Success = false, Error = ex.Message }; + } + } + + private string BuildMethodPattern() + { + // Build pattern for DOTNET_JitDisasm + // Just use the method name - the JIT will match any method with this name + // Type filtering is done post-hoc by parsing the output + return methodName; + } + + /// + /// Returns the executable path and any prefix arguments needed (e.g., DLL path for dotnet host). + /// + private static (string FileName, string? ArgumentPrefix) GetExecutablePath() + { + // Get the path to the current executable + var currentExe = Environment.ProcessPath; + if (currentExe is not null && File.Exists(currentExe)) + { + // When running via "dotnet run", ProcessPath is the dotnet host, not our tool. + // Detect this by checking if it ends with "dotnet" (or "dotnet.exe"). + var exeName = Path.GetFileNameWithoutExtension(currentExe); + if (exeName.Equals("dotnet", StringComparison.OrdinalIgnoreCase)) + { + var assemblyLocation = typeof(JitRunner).Assembly.Location; + if (!string.IsNullOrEmpty(assemblyLocation)) + { + return ("dotnet", assemblyLocation); + } + } + + return (currentExe, null); + } + + // Fallback to assembly location + var location = typeof(JitRunner).Assembly.Location; + if (!string.IsNullOrEmpty(location)) + { + // For .dll, try to find the corresponding .exe + var directory = Path.GetDirectoryName(location)!; + var baseName = Path.GetFileNameWithoutExtension(location); + + // Try .exe first (Windows) + var exePath = Path.Combine(directory, baseName + ".exe"); + if (File.Exists(exePath)) + { + return (exePath, null); + } + + // Try without extension (Linux/macOS) + exePath = Path.Combine(directory, baseName); + if (File.Exists(exePath)) + { + return (exePath, null); + } + + // Use dotnet to run the dll + return ("dotnet", location); + } + + throw new InvalidOperationException("Could not determine executable path"); + } + + private static string EscapeArg(string arg) + { + if (arg.Contains(' ') || arg.Contains('"')) + { + return $"\"{arg.Replace("\"", "\\\"")}\""; + } + return arg; + } +} + +internal sealed class JitResult +{ + public bool Success { get; init; } + public string? Output { get; init; } + public string? Error { get; init; } + public string? Pass1Output { get; init; } + public IReadOnlyList DetectedCctors { get; init; } = []; +} diff --git a/tools/JitAsm/MethodResolver.cs b/tools/JitAsm/MethodResolver.cs new file mode 100644 index 000000000000..334d7fb14b5e --- /dev/null +++ b/tools/JitAsm/MethodResolver.cs @@ -0,0 +1,393 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Reflection; + +namespace JitAsm; + +internal sealed class MethodResolver(Assembly assembly) +{ + private static readonly Dictionary TypeAliases = new(StringComparer.OrdinalIgnoreCase) + { + ["bool"] = typeof(bool), + ["byte"] = typeof(byte), + ["sbyte"] = typeof(sbyte), + ["char"] = typeof(char), + ["short"] = typeof(short), + ["ushort"] = typeof(ushort), + ["int"] = typeof(int), + ["uint"] = typeof(uint), + ["long"] = typeof(long), + ["ulong"] = typeof(ulong), + ["float"] = typeof(float), + ["double"] = typeof(double), + ["decimal"] = typeof(decimal), + ["string"] = typeof(string), + ["object"] = typeof(object), + ["void"] = typeof(void), + ["nint"] = typeof(nint), + ["nuint"] = typeof(nuint), + }; + + public MethodInfo? ResolveMethod(string? typeName, string methodName, string? typeParams, string? classTypeParams = null) + { + var candidates = new List<(Type Type, MethodInfo Method)>(); + + if (typeName is not null) + { + // Search specific type + var type = ResolveType(typeName); + if (type is null) + { + return null; + } + + // If the type is a generic type definition and we have class type params, construct the concrete type + if (type.IsGenericTypeDefinition && classTypeParams is not null) + { + type = MakeGenericType(type, classTypeParams); + if (type is null) + { + return null; + } + } + + var methods = FindMethods(type, methodName); + candidates.AddRange(methods.Select(m => (type, m))); + + // Also search base types if no methods found (for inherited methods) + if (candidates.Count == 0) + { + var baseType = type.BaseType; + while (baseType is not null && candidates.Count == 0) + { + methods = FindMethods(baseType, methodName, includeInherited: true); + candidates.AddRange(methods.Select(m => (baseType, m))); + baseType = baseType.BaseType; + } + } + } + else + { + // Search all types + foreach (var type in assembly.GetTypes()) + { + var searchType = type; + + // If the type is a generic type definition and we have class type params, try to construct it + if (type.IsGenericTypeDefinition && classTypeParams is not null) + { + var typeParamCount = classTypeParams.Split(',').Length; + if (type.GetGenericArguments().Length == typeParamCount) + { + var constructed = MakeGenericType(type, classTypeParams); + if (constructed is not null) + { + searchType = constructed; + } + } + } + + var methods = FindMethods(searchType, methodName); + candidates.AddRange(methods.Select(m => (searchType, m))); + } + } + + if (candidates.Count == 0) + { + return null; + } + + bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; + if (verbose) + { + Console.Error.WriteLine($"[DEBUG] Candidates before filtering: {candidates.Count}"); + foreach (var c in candidates) + { + Console.Error.WriteLine($"[DEBUG] Method: {c.Method.Name}, IsGenericMethodDefinition: {c.Method.IsGenericMethodDefinition}, GenericArgCount: {c.Method.GetGenericArguments().Length}"); + } + } + + // If type params are specified, filter to only methods with matching generic param count + if (typeParams is not null) + { + var genericParamCount = typeParams.Split(',').Length; + if (verbose) + Console.Error.WriteLine($"[DEBUG] Looking for generic methods with {genericParamCount} type params"); + + var genericMatches = candidates.Where(c => c.Method.IsGenericMethodDefinition && + c.Method.GetGenericArguments().Length == genericParamCount).ToList(); + + if (verbose) + Console.Error.WriteLine($"[DEBUG] Generic matches: {genericMatches.Count}"); + + if (genericMatches.Count > 0) + { + candidates = genericMatches; + } + } + else + { + // Prefer non-generic methods if no type params specified + var nonGeneric = candidates.Where(c => !c.Method.IsGenericMethodDefinition).ToList(); + if (nonGeneric.Count > 0) + { + candidates = nonGeneric; + } + } + + if (candidates.Count == 0) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] No candidates after filtering"); + return null; + } + + if (verbose) + Console.Error.WriteLine($"[DEBUG] Final candidates: {candidates.Count}, calling MakeGenericIfNeeded"); + + // If there's only one candidate, use it + if (candidates.Count == 1) + { + return MakeGenericIfNeeded(candidates[0].Method, typeParams); + } + + // Return first match + return MakeGenericIfNeeded(candidates[0].Method, typeParams); + } + + private Type? MakeGenericType(Type genericTypeDefinition, string classTypeParams) + { + var typeNames = classTypeParams.Split(',', StringSplitOptions.TrimEntries); + var types = new Type[typeNames.Length]; + + bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; + + for (int i = 0; i < typeNames.Length; i++) + { + var resolved = ResolveTypeParam(typeNames[i]); + if (resolved is null) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] Failed to resolve type param: {typeNames[i]}"); + return null; + } + types[i] = resolved; + if (verbose) + Console.Error.WriteLine($"[DEBUG] Resolved type param {typeNames[i]} to {resolved.FullName}"); + } + + try + { + var result = genericTypeDefinition.MakeGenericType(types); + if (verbose) + Console.Error.WriteLine($"[DEBUG] Constructed generic type: {result.FullName}"); + return result; + } + catch (Exception ex) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericType failed: {ex.Message}"); + return null; + } + } + + private Type? ResolveType(string typeName) + { + // Try direct lookup + var type = assembly.GetType(typeName); + if (type is not null) return type; + + // Try with assembly name prefix removed if present + var types = assembly.GetTypes(); + + // Try exact match on FullName + type = types.FirstOrDefault(t => t.FullName == typeName); + if (type is not null) return type; + + // Try match on Name only + type = types.FirstOrDefault(t => t.Name == typeName); + if (type is not null) return type; + + // Try matching generic types by base name (without the `N suffix) + // e.g., "TransactionProcessorBase" should match "TransactionProcessorBase`1" + type = types.FirstOrDefault(t => t.IsGenericTypeDefinition && + (t.FullName?.StartsWith(typeName + "`") == true || + t.Name.StartsWith(typeName + "`"))); + if (type is not null) return type; + + // Also try matching with the ` syntax - the type might be specified as TypeName`1 + if (typeName.Contains('`')) + { + type = types.FirstOrDefault(t => t.FullName == typeName || t.Name == typeName); + if (type is not null) return type; + } + + // Try nested types + foreach (var t in types) + { + if (t.FullName is not null && typeName.StartsWith(t.FullName + "+")) + { + var nestedName = typeName[(t.FullName.Length + 1)..]; + var nested = t.GetNestedType(nestedName, BindingFlags.Public | BindingFlags.NonPublic); + if (nested is not null) return nested; + } + } + + // Search referenced assemblies for cross-assembly types + foreach (var refName in assembly.GetReferencedAssemblies()) + { + try + { + var refAssembly = Assembly.Load(refName); + type = refAssembly.GetType(typeName); + if (type is not null) return type; + + // Try matching generic types by base name (e.g., "ClockCache" matches "ClockCache`2") + Type[] refTypes; + try { refTypes = refAssembly.GetTypes(); } + catch (ReflectionTypeLoadException ex) { refTypes = ex.Types.Where(t => t is not null).ToArray()!; } + + type = refTypes.FirstOrDefault(t => + t.FullName == typeName || t.Name == typeName || + (t.IsGenericTypeDefinition && + (t.FullName?.StartsWith(typeName + "`") == true || + t.Name.StartsWith(typeName + "`")))); + if (type is not null) return type; + } + catch + { + // Skip assemblies that can't be loaded + } + } + + return null; + } + + private static IEnumerable FindMethods(Type type, string methodName, bool includeInherited = false) + { + var flags = BindingFlags.Public | BindingFlags.NonPublic | + BindingFlags.Instance | BindingFlags.Static; + + if (!includeInherited) + { + flags |= BindingFlags.DeclaredOnly; + } + + bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; + var methods = type.GetMethods(flags).Where(m => m.Name == methodName).ToList(); + if (verbose) + { + Console.Error.WriteLine($"[DEBUG] FindMethods on {type.FullName} for '{methodName}': found {methods.Count} methods"); + if (methods.Count == 0) + { + // List some methods to help debug + var allMethods = type.GetMethods(flags).Where(m => m.Name.Contains("Evm") || m.Name.Contains("Execute")).Take(10); + Console.Error.WriteLine($"[DEBUG] Sample methods containing 'Evm' or 'Execute': {string.Join(", ", allMethods.Select(m => m.Name))}"); + } + } + + return methods; + } + + private MethodInfo? MakeGenericIfNeeded(MethodInfo method, string? typeParams) + { + bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; + + if (typeParams is null || !method.IsGenericMethodDefinition) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: returning method as-is (typeParams={typeParams}, IsGenericMethodDefinition={method.IsGenericMethodDefinition})"); + return method; + } + + var typeNames = typeParams.Split(',', StringSplitOptions.TrimEntries); + var types = new Type[typeNames.Length]; + + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: resolving {typeNames.Length} type params: {string.Join(", ", typeNames)}"); + + for (int i = 0; i < typeNames.Length; i++) + { + var resolved = ResolveTypeParam(typeNames[i]); + if (resolved is null) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: failed to resolve type param '{typeNames[i]}'"); + return null; + } + types[i] = resolved; + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: resolved '{typeNames[i]}' to {resolved.FullName}"); + } + + try + { + var result = method.MakeGenericMethod(types); + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: success, created {result}"); + return result; + } + catch (Exception ex) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] MakeGenericIfNeeded: MakeGenericMethod failed: {ex.Message}"); + return null; + } + } + + private Type? ResolveTypeParam(string typeName) + { + // Check aliases first + if (TypeAliases.TryGetValue(typeName, out var aliasType)) + { + return aliasType; + } + + // Try the target assembly + var type = ResolveType(typeName); + if (type is not null) return type; + + // Try referenced assemblies + foreach (var refName in assembly.GetReferencedAssemblies()) + { + try + { + var refAssembly = Assembly.Load(refName); + type = refAssembly.GetType(typeName); + if (type is not null) return type; + + // Try by short name + type = refAssembly.GetTypes().FirstOrDefault(t => t.Name == typeName || t.FullName == typeName); + if (type is not null) return type; + } + catch + { + // Skip assemblies that can't be loaded + } + } + + // Try Type.GetType as last resort + return Type.GetType(typeName); + } + + public IEnumerable FindAllMethods(string? typeName, string methodName) + { + if (typeName is not null) + { + var type = ResolveType(typeName); + if (type is not null) + { + return FindMethods(type, methodName); + } + return []; + } + + var results = new List(); + foreach (var type in assembly.GetTypes()) + { + results.AddRange(FindMethods(type, methodName)); + } + return results; + } +} diff --git a/tools/JitAsm/Program.cs b/tools/JitAsm/Program.cs new file mode 100644 index 000000000000..deb928163f70 --- /dev/null +++ b/tools/JitAsm/Program.cs @@ -0,0 +1,581 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.CommandLine; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Loader; +using System.Text.Json; +using Spectre.Console; + +namespace JitAsm; + +internal static class Program +{ + private static readonly Option AssemblyOption = new("-a", "--assembly") + { + Description = "Path to the assembly containing the method", + Required = true + }; + + private static readonly Option TypeOption = new("-t", "--type") + { + Description = "Fully qualified type name (optional, will search all types if not specified)" + }; + + private static readonly Option MethodOption = new("-m", "--method") + { + Description = "Method name to disassemble", + Required = true + }; + + private static readonly Option TypeParamsOption = new("--type-params") + { + Description = "Method generic type parameters (comma-separated type names)" + }; + + private static readonly Option ClassTypeParamsOption = new("--class-type-params") + { + Description = "Class generic type parameters (comma-separated type names, e.g., for TransactionProcessorBase`1)" + }; + + private static readonly Option SkipCctorOption = new("--skip-cctor-detection") + { + Description = "Skip automatic static constructor detection (single pass only)" + }; + + private static readonly Option FullOptsOption = new("--fullopts") + { + Description = "Use single-pass FullOpts compilation (DOTNET_TieredCompilation=0) instead of the default Tier-1 + PGO simulation" + }; + + private static readonly Option NoAnnotateOption = new("--no-annotate") + { + Description = "Disable per-instruction annotations (throughput, latency, uops, ports from uops.info)" + }; + + private static readonly Option ArchOption = new("--arch") + { + Description = "Target microarchitecture for annotations (default: zen4). Options: zen4, zen3, zen2, alder-lake, rocket-lake, ice-lake, tiger-lake, skylake", + DefaultValueFactory = _ => "zen4" + }; + + private static readonly Option VerboseOption = new("-v", "--verbose") + { + Description = "Show resolution details and both passes" + }; + + private static int Main(string[] args) + { + // Check if running in internal runner mode + if (args.Length > 0 && args[0] == "--internal-runner") + { + return RunInternalRunner(args.Skip(1).ToArray()); + } + + return RunCli(args); + } + + private static int RunCli(string[] args) + { + var rootCommand = new RootCommand("JIT Assembly Disassembler - Generate JIT assembly output for .NET methods") + { + AssemblyOption, + TypeOption, + MethodOption, + TypeParamsOption, + ClassTypeParamsOption, + SkipCctorOption, + FullOptsOption, + NoAnnotateOption, + ArchOption, + VerboseOption + }; + + int exitCode = 0; + rootCommand.SetAction(parseResult => + { + var assembly = parseResult.GetValue(AssemblyOption)!; + var typeName = parseResult.GetValue(TypeOption); + var methodName = parseResult.GetValue(MethodOption)!; + var typeParams = parseResult.GetValue(TypeParamsOption); + var classTypeParams = parseResult.GetValue(ClassTypeParamsOption); + var skipCctor = parseResult.GetValue(SkipCctorOption); + var fullOpts = parseResult.GetValue(FullOptsOption); + var annotate = !parseResult.GetValue(NoAnnotateOption); + var arch = parseResult.GetValue(ArchOption)!; + var verbose = parseResult.GetValue(VerboseOption); + + exitCode = Execute(assembly, typeName, methodName, typeParams, classTypeParams, skipCctor, fullOpts, annotate, arch, verbose); + }); + + int parseExitCode = rootCommand.Parse(args).Invoke(); + return parseExitCode != 0 ? parseExitCode : exitCode; + } + + private static int Execute(FileInfo assembly, string? typeName, string methodName, string? typeParams, string? classTypeParams, bool skipCctor, bool fullOpts, bool annotate, string arch, bool verbose) + { + if (!assembly.Exists) + { + AnsiConsole.MarkupLine($"[red]Error:[/] Assembly not found: {assembly.FullName}"); + return 1; + } + + bool tier1 = !fullOpts; + + if (verbose) + { + AnsiConsole.MarkupLine($"[blue]Assembly:[/] {assembly.FullName}"); + AnsiConsole.MarkupLine($"[blue]Type:[/] {typeName ?? "(search all)"}"); + AnsiConsole.MarkupLine($"[blue]Method:[/] {methodName}"); + if (classTypeParams is not null) + { + AnsiConsole.MarkupLine($"[blue]Class Type Parameters:[/] {classTypeParams}"); + } + if (typeParams is not null) + { + AnsiConsole.MarkupLine($"[blue]Method Type Parameters:[/] {typeParams}"); + } + AnsiConsole.MarkupLine(tier1 + ? "[blue]Mode:[/] Tier-1 + Dynamic PGO (default)" + : "[blue]Mode:[/] FullOpts (TieredCompilation=0)"); + AnsiConsole.WriteLine(); + } + + var runner = new JitRunner(assembly.FullName, typeName, methodName, typeParams, classTypeParams, verbose, tier1); + + InstructionDb? instructionDb = annotate ? LoadOrBuildInstructionDb(arch, verbose) : null; + + JitResult result = (skipCctor ? + runner.RunSinglePassAsync() : + runner.RunTwoPassAsync()) + .GetAwaiter().GetResult(); + + return OutputResult(result, verbose, instructionDb); + } + + private static int OutputResult(JitResult result, bool verbose, InstructionDb? instructionDb = null) + { + if (!result.Success) + { + AnsiConsole.MarkupLine($"[red]Error:[/] {Markup.Escape(result.Error ?? "Unknown error")}"); + if (!string.IsNullOrEmpty(result.Output)) + { + AnsiConsole.WriteLine(); + AnsiConsole.MarkupLine("[yellow]Output:[/]"); + AnsiConsole.WriteLine(result.Output); + } + return 1; + } + + if (verbose && result.DetectedCctors.Count > 0) + { + AnsiConsole.MarkupLine("[blue]Detected static constructors:[/]"); + foreach (var cctor in result.DetectedCctors) + { + AnsiConsole.MarkupLine($" [grey]- {Markup.Escape(cctor)}[/]"); + } + AnsiConsole.WriteLine(); + } + + if (verbose && result.Pass1Output is not null && result.DetectedCctors.Count > 0) + { + AnsiConsole.MarkupLine("[blue]Pass 1 Output (before cctor initialization):[/]"); + AnsiConsole.WriteLine(result.Pass1Output); + AnsiConsole.WriteLine(); + AnsiConsole.MarkupLine("[blue]Pass 2 Output (after cctor initialization):[/]"); + } + + string output = result.Output ?? string.Empty; + if (instructionDb is not null) + { + output = InstructionAnnotator.Annotate(output, instructionDb); + } + + Console.WriteLine(output); + return 0; + } + + private static InstructionDb? LoadOrBuildInstructionDb(string arch, bool verbose) + { + string toolDir = AppContext.BaseDirectory; + // Look for files relative to the project source directory first, then the binary directory + string projectDir = Path.GetFullPath(Path.Combine(toolDir, "..", "..", "..", "..")); + if (!File.Exists(Path.Combine(projectDir, "JitAsm.csproj"))) + { + // Fallback: try to find the project directory from the current working directory + string cwd = Directory.GetCurrentDirectory(); + if (File.Exists(Path.Combine(cwd, "tools", "JitAsm", "JitAsm.csproj"))) + projectDir = Path.Combine(cwd, "tools", "JitAsm"); + else if (File.Exists(Path.Combine(cwd, "JitAsm.csproj"))) + projectDir = cwd; + else + projectDir = toolDir; + } + + string dbPath = Path.Combine(projectDir, "instructions.db"); + string xmlPath = Path.Combine(projectDir, "instructions.xml"); + + // Check if we have a cached .db for this architecture + if (File.Exists(dbPath)) + { + try + { + var db = InstructionDb.Load(dbPath); + string targetArch = InstructionDbBuilder.ResolveArchName(arch); + if (db.ArchName == targetArch) + { + if (verbose) + AnsiConsole.MarkupLine($"[blue]Loaded instruction database:[/] {dbPath} ({db.Count} entries for {db.ArchName})"); + return db; + } + + if (verbose) + AnsiConsole.MarkupLine($"[yellow]Cached DB is for {db.ArchName}, need {targetArch}. Rebuilding...[/]"); + } + catch (Exception ex) + { + if (verbose) + AnsiConsole.MarkupLine($"[yellow]Failed to load cached DB: {Markup.Escape(ex.Message)}. Rebuilding...[/]"); + } + } + + // Build from XML + if (!File.Exists(xmlPath)) + { + AnsiConsole.MarkupLine("[yellow]Instruction database not found. Annotations disabled.[/]"); + AnsiConsole.MarkupLine("[yellow]Download it with:[/]"); + AnsiConsole.MarkupLine($" curl -o {Markup.Escape(xmlPath)} https://uops.info/instructions.xml"); + AnsiConsole.MarkupLine("[yellow]Or use --no-annotate to suppress this warning.[/]"); + return null; + } + + AnsiConsole.MarkupLine($"[blue]Building instruction database for {arch}...[/]"); + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + var builtDb = InstructionDbBuilder.Build(xmlPath, arch); + stopwatch.Stop(); + + AnsiConsole.MarkupLine($"[blue]Built {builtDb.Count} instruction entries in {stopwatch.Elapsed.TotalSeconds:F1}s[/]"); + + try + { + builtDb.Save(dbPath); + if (verbose) + AnsiConsole.MarkupLine($"[blue]Saved to:[/] {dbPath}"); + } + catch (Exception ex) + { + if (verbose) + AnsiConsole.MarkupLine($"[yellow]Warning: Could not save DB cache: {Markup.Escape(ex.Message)}[/]"); + } + + return builtDb; + } + + private static int RunInternalRunner(string[] args) + { + // Parse internal runner arguments + // Format: [--type ] [--type-params ] [--class-type-params ] [--init-cctors ] + if (args.Length < 2) + { + Console.Error.WriteLine("Internal runner requires assembly and method arguments"); + return 1; + } + + var assemblyPath = args[0]; + var methodName = args[1]; + string? typeName = null; + string? typeParams = null; + string? classTypeParams = null; + List cctorsToInit = []; + bool tier1 = false; + + for (int i = 2; i < args.Length; i++) + { + switch (args[i]) + { + case "--type" when i + 1 < args.Length: + typeName = args[++i]; + break; + case "--type-params" when i + 1 < args.Length: + typeParams = args[++i]; + break; + case "--class-type-params" when i + 1 < args.Length: + classTypeParams = args[++i]; + break; + case "--init-cctors" when i + 1 < args.Length: + cctorsToInit = args[++i].Split(';', StringSplitOptions.RemoveEmptyEntries).ToList(); + break; + case "--tier1": + tier1 = true; + break; + } + } + + // Set up dependency resolution for the target assembly. + // Without this, RuntimeHelpers.PrepareMethod silently fails when the + // JIT can't resolve types from transitive NuGet packages (e.g. Nethermind.Int256). + bool verbose = Environment.GetEnvironmentVariable("JITASM_VERBOSE") == "1"; + + // Build assembly name → file path mapping from the deps.json file. + // This resolves both project references (in the same directory) and + // NuGet packages (from the global packages cache). + var assemblyDir = Path.GetDirectoryName(Path.GetFullPath(assemblyPath))!; + var assemblyMap = BuildAssemblyMap(assemblyPath, assemblyDir, verbose); + + AssemblyLoadContext.Default.Resolving += (context, name) => + { + // First check the target assembly's directory + var localPath = Path.Combine(assemblyDir, name.Name + ".dll"); + if (File.Exists(localPath)) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] AssemblyResolve: {name.Name} → {localPath} (local)"); + return context.LoadFromAssemblyPath(localPath); + } + + // Then check deps.json mapped paths + if (assemblyMap.TryGetValue(name.Name!, out var mappedPath) && File.Exists(mappedPath)) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] AssemblyResolve: {name.Name} → {mappedPath} (deps.json)"); + return context.LoadFromAssemblyPath(mappedPath); + } + + if (verbose) + Console.Error.WriteLine($"[DEBUG] AssemblyResolve: {name.Name} → NOT FOUND"); + return null; + }; + + try + { + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(assemblyPath)); + + if (verbose) + { + Console.Error.WriteLine($"[DEBUG] Loaded assembly: {assembly.FullName}"); + Console.Error.WriteLine($"[DEBUG] Type name: {typeName}"); + Console.Error.WriteLine($"[DEBUG] Method name: {methodName}"); + Console.Error.WriteLine($"[DEBUG] Type params: {typeParams}"); + Console.Error.WriteLine($"[DEBUG] Class type params: {classTypeParams}"); + } + + // Initialize static constructors if requested + foreach (var cctorTypeName in cctorsToInit) + { + var cctorType = ResolveType(assembly, cctorTypeName, verbose); + if (cctorType is not null) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] Running cctor for: {cctorType.FullName}"); + RuntimeHelpers.RunClassConstructor(cctorType.TypeHandle); + } + else if (verbose) + { + Console.Error.WriteLine($"[DEBUG] WARNING: Could not resolve cctor type: {cctorTypeName}"); + } + } + + // Resolve the method + var resolver = new MethodResolver(assembly); + var method = resolver.ResolveMethod(typeName, methodName, typeParams, classTypeParams); + + if (method is null) + { + if (verbose) + { + var types = assembly.GetTypes().Where(t => t.Name.Contains(typeName ?? "")).Take(5); + Console.Error.WriteLine($"[DEBUG] Possible types: {string.Join(", ", types.Select(t => t.FullName))}"); + } + Console.Error.WriteLine($"Could not resolve method: {methodName}"); + return 1; + } + + if (tier1) + { + var parameters = method.GetParameters(); + var invokeArgs = new object?[parameters.Length]; + object? target = method.IsStatic ? null : TryCreateInstance(method.DeclaringType!); + + void InvokeN(int count) + { + for (int i = 0; i < count; i++) + { + try { method.Invoke(target, invokeArgs); } + catch { /* Expected - args are null/default */ } + } + } + + if (verbose) + Console.Error.WriteLine("[DEBUG] Phase 1: Invoking to trigger Tier-0 → Instrumented Tier-0..."); + InvokeN(50); + Thread.Sleep(1000); + + if (verbose) + Console.Error.WriteLine("[DEBUG] Phase 2: Invoking to trigger Instrumented Tier-0 → Tier-1..."); + InvokeN(50); + Thread.Sleep(2000); + + if (verbose) + Console.Error.WriteLine("[DEBUG] Phase 3: Final invocations to ensure Tier-1 is installed..."); + InvokeN(50); + Thread.Sleep(1000); + } + else + { + RuntimeHelpers.PrepareMethod(method.MethodHandle); + if (verbose) + Console.Error.WriteLine($"[DEBUG] PrepareMethod completed for {method.DeclaringType?.FullName}:{method.Name}"); + + // Also invoke the method to ensure all code paths are JIT-compiled. + // PrepareMethod alone may not trigger DOTNET_JitDisasm output for + // methods from dynamically loaded assemblies. + var parameters = method.GetParameters(); + var invokeArgs = new object?[parameters.Length]; + object? target = method.IsStatic ? null : TryCreateInstance(method.DeclaringType!); + try { method.Invoke(target, invokeArgs); } + catch { /* Expected - args are null/default */ } + if (verbose) + Console.Error.WriteLine("[DEBUG] Method invocation completed"); + } + + return 0; + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error: {ex.Message}"); + return 1; + } + } + + private static object? TryCreateInstance(Type type) + { + try + { + return RuntimeHelpers.GetUninitializedObject(type); + } + catch + { + return null; + } + } + + private static Type? ResolveType(Assembly assembly, string typeName, bool verbose = false) + { + // Try direct lookup in target assembly first + var type = assembly.GetType(typeName); + if (type is not null) return type; + + // Try searching all types in target assembly + foreach (var t in assembly.GetTypes()) + { + if (t.FullName == typeName || t.Name == typeName) + { + return t; + } + } + + // Search referenced assemblies (types often come from other assemblies, + // e.g., Nethermind.Core types referenced from Nethermind.Evm) + foreach (var refName in assembly.GetReferencedAssemblies()) + { + try + { + var refAssembly = Assembly.Load(refName); + type = refAssembly.GetType(typeName); + if (type is not null) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] Resolved cctor type '{typeName}' from referenced assembly {refName.Name}"); + return type; + } + + // Try by short name + foreach (var t in refAssembly.GetTypes()) + { + if (t.FullName == typeName || t.Name == typeName) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] Resolved cctor type '{typeName}' from referenced assembly {refName.Name} (by scan)"); + return t; + } + } + } + catch + { + // Skip assemblies that can't be loaded + } + } + + // Try Type.GetType as last resort (requires assembly-qualified names for cross-assembly) + return Type.GetType(typeName); + } + + /// + /// Build a mapping from assembly name to file path using the deps.json file. + /// Resolves NuGet package references via the global packages cache. + /// + private static Dictionary BuildAssemblyMap(string assemblyPath, string assemblyDir, bool verbose) + { + var map = new Dictionary(StringComparer.OrdinalIgnoreCase); + string depsPath = Path.ChangeExtension(assemblyPath, ".deps.json"); + if (!File.Exists(depsPath)) + return map; + + string nugetPackages = Environment.GetEnvironmentVariable("NUGET_PACKAGES") + ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); + + try + { + using var stream = File.OpenRead(depsPath); + using var doc = JsonDocument.Parse(stream); + + var root = doc.RootElement; + if (!root.TryGetProperty("targets", out var targets)) + return map; + + // Get the first (and usually only) target + foreach (var target in targets.EnumerateObject()) + { + foreach (var package in target.Value.EnumerateObject()) + { + if (!package.Value.TryGetProperty("runtime", out var runtime)) + continue; + + foreach (var dll in runtime.EnumerateObject()) + { + string dllRelativePath = dll.Name; // e.g. "lib/net10.0/Nethermind.Int256.dll" + string asmName = Path.GetFileNameWithoutExtension(dllRelativePath); + + // Skip if already in the local directory + if (File.Exists(Path.Combine(assemblyDir, asmName + ".dll"))) + continue; + + // Resolve from NuGet cache: packages/{id}/{version}/{path} + string packageId = package.Name; // e.g. "Nethermind.Numerics.Int256/1.4.0" + string[] parts = packageId.Split('/'); + if (parts.Length == 2) + { + string fullPath = Path.Combine(nugetPackages, parts[0].ToLowerInvariant(), parts[1], dllRelativePath); + if (File.Exists(fullPath)) + { + map[asmName] = fullPath; + if (verbose) + Console.Error.WriteLine($"[DEBUG] Deps map: {asmName} → {fullPath}"); + } + } + } + } + break; // Only process the first target + } + } + catch (Exception ex) + { + if (verbose) + Console.Error.WriteLine($"[DEBUG] Failed to parse deps.json: {ex.Message}"); + } + + return map; + } +} + diff --git a/tools/JitAsm/README.md b/tools/JitAsm/README.md new file mode 100644 index 000000000000..705821f64be3 --- /dev/null +++ b/tools/JitAsm/README.md @@ -0,0 +1,494 @@ +# JitAsm + +A tool for viewing JIT-compiled assembly output for .NET methods. Useful for analyzing code generation, verifying optimizations, and comparing different implementations. + +## How It Works + +JitAsm spawns a child process with JIT diagnostic environment variables (`DOTNET_JitDisasm`) to capture disassembly output. + +By default, the tool simulates **Tier-1 recompilation with Dynamic PGO** — the same compilation tier that runs in production after warm-up. This produces the most representative assembly: smaller code, eliminated static base helpers, and PGO-guided branch layout. + +### Compilation Tiers + +The .NET runtime compiles methods through multiple stages: + +| Stage | Description | Typical Size | +|-------|-------------|-------------| +| **Tier-0** | Quick JIT, minimal optimization (`minopt`) | Largest | +| **Instrumented Tier-0** | Tier-0 + PGO probes for profiling | Larger still | +| **Tier-1 + PGO** (default) | Full optimization with profile data | **Smallest** | +| **FullOpts** (`--fullopts`) | Full optimization, no PGO, no tiering | Middle | + +Key difference between Tier-1 and FullOpts: +- **FullOpts** (`DOTNET_TieredCompilation=0`) compiles in a single pass. Cross-module static base helpers (`CORINFO_HELP_GET_NONGCSTATIC_BASE`) persist because the JIT hasn't resolved the static base address yet. +- **Tier-1 + PGO** compiles after Tier-0 has executed. By then, static bases are resolved, and the JIT can embed addresses directly — eliminating the helper entirely. PGO data also improves branch layout and inlining decisions. + +### Two-Pass Static Constructor Handling + +When a method references static fields, the JIT may include static constructor initialization checks (`CORINFO_HELP_GET_NONGCSTATIC_BASE`, `CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE`). JitAsm automatically detects these and runs a second pass with static constructors pre-initialized, showing the steady-state optimized code path. + +In Tier-1 mode (default), many of these helpers are eliminated naturally since the static bases are already resolved by Tier-0 execution. + +### Tier-1 Simulation + +The tool drives through all PGO compilation stages: + +1. **Invoke** method via reflection (triggers Tier-0 JIT + installs call counting stubs) +2. **Wait** for Instrumented Tier-0 recompilation (PGO profiling version) +3. **Invoke** again (triggers call counter through instrumented code) +4. **Wait** for Tier-1 recompilation (fully optimized with PGO data) +5. **Capture** the final Tier-1 assembly output + +Critical env var: `DOTNET_TC_CallCountingDelayMs=0` — without this, counting stubs aren't installed in time and Tier-1 never triggers. + +## Usage + +```bash +dotnet run --project tools/JitAsm -c Release -- [options] +``` + +### Options + +| Option | Description | +|--------|-------------| +| `-a, --assembly ` | Path to the assembly containing the method (required) | +| `-t, --type ` | Fully qualified type name (optional, searches all types if not specified) | +| `-m, --method ` | Method name to disassemble (required) | +| `--type-params ` | Method generic type parameters (comma-separated) | +| `--class-type-params ` | Class generic type parameters (comma-separated, for generic containing types) | +| `--fullopts` | Use single-pass FullOpts compilation (`TieredCompilation=0`) instead of Tier-1 + PGO | +| `--no-annotate` | Disable per-instruction annotations (throughput, latency, uops, ports). Annotations are **on by default** | +| `--arch ` | Target microarchitecture for annotations (default: `zen4`). See [Supported Architectures](#supported-architectures) | +| `--skip-cctor-detection` | Skip automatic static constructor detection (single pass only) | +| `-v, --verbose` | Show resolution details and both passes | + +### Default Mode: Tier-1 + PGO + +By default, the tool simulates Tier-1 recompilation with Dynamic PGO. This is the compilation tier code runs at in production and produces the most optimized output. + +```bash +# Default: Tier-1 + PGO (production-representative) +dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName +``` + +### FullOpts Mode + +Use `--fullopts` when you need the older single-pass compilation. This is faster (no invocation/sleep cycle) but may show static base helpers and lack PGO-guided optimizations. + +```bash +# FullOpts: single compilation, no PGO +dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName --fullopts +``` + +## Prerequisites + +Build the target project in Release configuration before disassembling: + +```bash +dotnet build src/Nethermind/Nethermind.Evm -c Release +``` + +Assemblies are output to `src/Nethermind/artifacts/bin/{ProjectName}/release/{ProjectName}.dll`. For example: +- `src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll` +- `src/Nethermind/artifacts/bin/Nethermind.Core/release/Nethermind.Core.dll` + +## Platform Notes (Windows) + +On Windows, follow these rules to avoid argument parsing errors: + +- **Avoid quoted strings** around paths and type parameters — they can cause `'' was not matched` errors +- **Use forward slashes** in paths (works on both Windows and Linux) +- **Put the entire command on a single line** — avoid line continuations (`\`) +- Use absolute paths when possible for clarity + +```bash +# Good (Windows) +dotnet run --project tools/JitAsm -c Release -- -a D:/GitHub/nethermind/src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmStack -m PushBytesRef + +# Bad (may fail on Windows) +dotnet run --project tools/JitAsm -c Release -- \ + -a "src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll" \ + -t "Nethermind.Evm.EvmStack" \ + -m PushBytesRef +``` + +On Linux/macOS, both styles work fine. + +## Examples + +### Basic Usage + +```bash +# Disassemble a simple method (searches all types, default Tier-1 + PGO) +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m PushBytesRef +``` + +### With Type Specification + +```bash +# Specify the containing type to narrow the search +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmStack -m PushBytesRef +``` + +### Generic Methods (Method-Level Type Parameters) + +Use `--type-params` for type parameters on the method itself: + +```bash +# PushBytes(...) where TTracingInst = OffFlag +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmStack -m PushBytes --type-params Nethermind.Core.OffFlag +``` + +### Generic Classes (Class-Level Type Parameters) + +Use `--class-type-params` when the containing class is generic. This is separate from `--type-params` which is for method-level generics. Generic type names can omit the backtick arity suffix (e.g., `TransactionProcessorBase` matches `TransactionProcessorBase`1`). + +```bash +# TransactionProcessorBase.Execute(...) +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.TransactionProcessing.TransactionProcessorBase -m Execute --class-type-params Nethermind.Evm.GasPolicy.EthereumGasPolicy --type-params Nethermind.Core.OffFlag +``` + +### Multiple Generic Parameters + +Use comma-separated type names (no spaces after commas): + +```bash +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m SomeGenericMethod --type-params System.Int32,System.String +``` + +### EVM Instruction Example + +Disassemble the MUL opcode implementation with specific gas policy and tracing flags: + +```bash +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmInstructions -m InstructionMath2Param --type-params Nethermind.Evm.GasPolicy.EthereumGasPolicy,Nethermind.Evm.EvmInstructions+OpMul,Nethermind.Core.OffFlag +``` + +This shows the JIT output for `InstructionMath2Param`: +- `EthereumGasPolicy` - Standard Ethereum gas accounting +- `OpMul` - The multiplication operation (nested type, use `+` syntax) +- `OffFlag` - Tracing disabled (allows dead code elimination of tracing paths) + +Other math operations can be viewed by replacing `OpMul` with: `OpAdd`, `OpSub`, `OpDiv`, `OpMod`, `OpSDiv`, `OpSMod`, `OpLt`, `OpGt`, `OpSLt`, `OpSGt`, `OpEq`, `OpAnd`, `OpOr`, `OpXor`. + +### Type Aliases + +Common C# type aliases are supported: + +```bash +# These are equivalent +--type-params int +--type-params System.Int32 +``` + +Supported aliases: `bool`, `byte`, `sbyte`, `char`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double`, `decimal`, `string`, `object`, `nint`, `nuint` + +### Verbose Mode + +```bash +# Show detailed output including cctor detection and compilation tier info +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m PushBytesRef -v +``` + +### Skip Static Constructor Detection + +```bash +# Single pass only (faster, but may show cctor overhead) +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m SomeMethod --skip-cctor-detection +``` + +### FullOpts vs Tier-1 Comparison + +Compare the same method under different compilation modes to see what Tier-1 + PGO eliminates: + +```bash +# Tier-1 + PGO (default) — production-representative +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.BlockExecutionContext -m GetBlobBaseFee > tier1.asm 2>&1 + +# FullOpts — single-pass, no PGO +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.BlockExecutionContext -m GetBlobBaseFee --fullopts > fullopts.asm 2>&1 + +# Compare +diff tier1.asm fullopts.asm +``` + +Example result for `GetBlobBaseFee`: +- **FullOpts**: 356 bytes, has `CORINFO_HELP_GET_NONGCSTATIC_BASE` +- **Tier-1 + PGO**: 236 bytes (34% smaller), helper eliminated entirely + +## Example Output + +Default Tier-1 + PGO output: + +``` +; Assembly listing for method Namespace.Type:Method() (Tier1) +; Emitting BLENDED_CODE for generic X64 + VEX + EVEX on Windows +; Tier1 code ← Tier-1 recompilation (production tier) +; optimized code +; optimized using Dynamic PGO +; rsp based frame +; partially interruptible +; with Dynamic PGO: fgCalledCount is 50 + +G_M000_IG01: ;; offset=0x0000 + sub rsp, 40 + ... + +; Total bytes of code 236 +``` + +FullOpts output (with `--fullopts`): + +``` +; Assembly listing for method Namespace.Type:Method() (FullOpts) +; FullOpts code ← Single-pass FullOpts +; optimized code +; No PGO data ← No profile data available + ... + +; Total bytes of code 356 +``` + +## Instruction Annotations + +Per-instruction performance data from [uops.info](https://uops.info) is included **by default**, showing throughput, latency, micro-op count, and execution port usage inline with the assembly output. Use `--no-annotate` to disable. + +### Setup + +Download the uops.info instruction database (110MB, one-time): + +```bash +curl -o tools/JitAsm/instructions.xml https://uops.info/instructions.xml +``` + +On first use, the XML is preprocessed into a compact binary cache (`instructions.db`, ~1-2MB). Subsequent runs load from the cache. + +### Usage + +```bash +# Annotations are on by default (zen4) +dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName + +# Use a different architecture +dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName --arch alder-lake + +# Disable annotations +dotnet run --project tools/JitAsm -c Release -- -a path/to/assembly.dll -m MethodName --no-annotate +``` + +### Annotation Format + +Each instruction line gets an end-of-line annotation: + +``` + add rax, rcx ; [TP:0.25 | Lat: 1 | Uops:1] + mov qword ptr [rbp+10h], rax ; [TP:0.50 | Lat:11 | Uops:1] + vmovdqu ymm0, ymmword ptr [rsi] ; [TP:0.50 | Lat: 8 | Uops:1 | 1*FP_LD] +``` + +| Field | Meaning | +|-------|---------| +| `TP` | Reciprocal throughput (cycles per instruction). Lower = faster. | +| `Lat` | Latency (cycles from input ready to output ready). Matters for dependency chains. | +| `Uops` | Micro-op count. Fewer = less pressure on the execution engine. | +| Ports | Execution port usage (e.g., `1*p0156`). Shows which functional units are used. | + +### Supported Architectures + +| `--arch` value | CPU | Description | +|----------------|-----|-------------| +| `zen4` (default) | AMD Zen 4 | Ryzen 7000 / EPYC 9004 | +| `zen3` | AMD Zen 3 | Ryzen 5000 / EPYC 7003 | +| `zen2` | AMD Zen 2 | Ryzen 3000 / EPYC 7002 | +| `alder-lake` | Intel ADL-P | 12th Gen Core (P-cores) | +| `rocket-lake` | Intel RKL | 11th Gen Core | +| `ice-lake` | Intel ICL | 10th Gen Core | +| `tiger-lake` | Intel TGL | 11th Gen Mobile | +| `skylake` | Intel SKL | 6th Gen Core | + +When data for the selected architecture is unavailable for a specific instruction, the tool falls back to a nearby architecture in the same family. + +### Annotation Details + +**Mnemonic mapping:** The .NET JIT uses Intel-style mnemonic aliases (e.g., `je`, `jne`, `ja`) while uops.info uses canonical forms (`jz`, `jnz`, `jnbe`). The annotator automatically maps between them for conditional jumps, conditional moves (`cmove`→`cmovz`), and set-byte instructions (`sete`→`setz`). + +**LEA handling:** The `lea` instruction computes an address but doesn't access memory. Its second operand is classified as `agen` (address generation) rather than a memory load, matching the uops.info encoding. + +**.NET JIT-specific prefixes:** +- `gword ptr` — GC-tracked pointer-width memory reference. Treated as `m64` on x64. +- `bword ptr` — Pointer-width memory reference without GC tracking. Treated as `m64` on x64. +- `SHORT` / `NEAR` — Jump distance hints from the JIT. Stripped before operand classification. + +**Skipped instructions:** `call`, `ret`, `int3`, `nop`, and `int` are not annotated. `ret` is skipped because uops.info's `TP_unrolled` for `ret` reflects return stack buffer mispredictions in microbenchmarks, not real-world performance. + +## Iterative Workflow + +JitAsm is designed for rapid iteration when optimizing code: + +1. **Modify source code** +2. **Build the target assembly:** + ```bash + dotnet build src/Nethermind/Nethermind.Evm -c Release + ``` +3. **View the generated assembly:** + ```bash + dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -m YourMethod + ``` +4. **Compare output between iterations** + +For fast iteration during optimization, `--fullopts` skips the invocation/sleep cycle (~4s faster) at the cost of less representative output. Use default Tier-1 for final verification. + +## Environment Variables + +The tool sets these JIT diagnostic variables for the child process: + +### Default Mode (Tier-1 + PGO) + +| Variable | Value | Purpose | +|----------|-------|---------| +| `DOTNET_TieredCompilation` | `1` | Enable tiered compilation | +| `DOTNET_TieredPGO` | `1` | Enable Dynamic PGO | +| `DOTNET_TC_CallCountThreshold` | `1` | Minimal call count before recompilation | +| `DOTNET_TC_CallCountingDelayMs` | `0` | Install counting stubs immediately (critical) | +| `DOTNET_JitDisasm` | `` | Output disassembly for matching methods | +| `DOTNET_JitDiffableDasm` | `1` | Consistent, diffable output format | + +### FullOpts Mode (`--fullopts`) + +| Variable | Value | Purpose | +|----------|-------|---------| +| `DOTNET_TieredCompilation` | `0` | Disable tiered compilation | +| `DOTNET_TC_QuickJit` | `0` | Disable quick JIT | +| `DOTNET_JitDisasm` | `` | Output disassembly for matching methods | +| `DOTNET_JitDiffableDasm` | `1` | Consistent, diffable output format | + +## Comparing Generic Specializations (OffFlag vs OnFlag) + +A common workflow is comparing two generic instantiations to verify dead code elimination. Nethermind uses the `IFlag` pattern (`OnFlag`/`OffFlag`) for compile-time branch elimination. + +```bash +# Generate ASM for tracing disabled (common case) +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmInstructions -m InstructionMath2Param --type-params Nethermind.Evm.GasPolicy.EthereumGasPolicy,Nethermind.Evm.EvmInstructions+OpAdd,Nethermind.Core.OffFlag > off.asm 2>&1 + +# Generate ASM for tracing enabled +dotnet run --project tools/JitAsm -c Release -- -a src/Nethermind/artifacts/bin/Nethermind.Evm/release/Nethermind.Evm.dll -t Nethermind.Evm.EvmInstructions -m InstructionMath2Param --type-params Nethermind.Evm.GasPolicy.EthereumGasPolicy,Nethermind.Evm.EvmInstructions+OpAdd,Nethermind.Core.OnFlag > on.asm 2>&1 + +# Compare: the OnFlag version should have tracing code that is absent from OffFlag +diff off.asm on.asm +``` + +**Red flag:** If both versions have identical code size, the JIT may not be eliminating dead code as expected. + +## Extracting Metrics from ASM Output + +Use these patterns to extract key metrics from the disassembly output for tracking optimization progress: + +```bash +# Code size (from the last line, e.g., "; Total bytes of code 40") +tail -1 output.asm + +# Basic block count (fewer = simpler control flow = better branch prediction) +grep -c "G_M000_IG[0-9]*:" output.asm + +# Branch count (conditional jumps) +grep -cE "\bj(e|ne|g|l|ge|le|a|b|ae|be|nz|z)\b" output.asm + +# Call count (should be minimal in hot paths) +grep -c "call" output.asm + +# Register saves in prologue (>4 push = register pressure issue) +grep -c "push" output.asm + +# Stack frame size (from prologue, e.g., "sub rsp, 40") +grep "sub.*rsp" output.asm +``` + +## Reading Assembly Output + +### Output Structure + +The disassembly header contains useful metadata: + +``` +; Assembly listing for method Namespace.Type:Method() (Tier1) +; Emitting BLENDED_CODE for generic X64 + VEX + EVEX on Windows +; Tier1 code ← Tier-1 optimized (production tier) +; optimized code +; optimized using Dynamic PGO ← PGO-guided optimizations applied +; rsp based frame +; partially interruptible +; with Dynamic PGO: fgCalledCount is 50 ← How many times method was called during profiling +``` + +The code is organized into **basic blocks** labeled `G_M000_IG01`, `G_M000_IG02`, etc. Each block is a straight-line sequence of instructions ending with a branch or fall-through. + +### Common Red Flags + +| Pattern in ASM | Meaning | Severity | +|----------------|---------|----------| +| `call CORINFO_HELP_BOX` | Boxing allocation in hot path | High | +| `call CORINFO_HELP_VIRTUAL_FUNC_PTR` | Virtual dispatch not devirtualized (~25-50 cycles) | High | +| `call CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE` | Static constructor check | High | +| `call CORINFO_HELP_GET_NONGCSTATIC_BASE` | Cross-module static base resolution (eliminated in Tier-1) | Medium* | +| `call CORINFO_HELP_ASSIGN_REF` | GC write barrier (bad in loops) | High | +| `callvirt [Interface:Method()]` | Interface dispatch not devirtualized | High | +| `call [SmallType:SmallMethod()]` | Failed inlining of small method | Medium | +| `cmp ...; jae THROW_LABEL` | Bounds check (may be eliminable) | Medium | +| `idiv` | Division by non-constant (~20-80 cycles) | Medium | +| `>4 push` instructions in prologue | Register pressure | Medium | +| `vmovdqu32 zmmword ptr` in prologue | Large stack zeroing (cold locals bloating hot path) | Low | + +*`CORINFO_HELP_GET_NONGCSTATIC_BASE` appears in FullOpts mode for cross-module static field access but is naturally eliminated in Tier-1 + PGO. If you see it in default (Tier-1) output, it indicates the static base couldn't be resolved at runtime — a real issue. + +### What Good ASM Looks Like + +- Few basic blocks (simple control flow) +- No `call` instructions in the hot path (everything inlined) +- Compact code size (better I-cache utilization) +- No redundant loads of the same memory location +- SIMD instructions (`vpaddd`, `vpxor`, etc.) for bulk data operations +- `cmov*` (conditional moves) instead of branches where appropriate +- Fall-through on the hot path, forward jumps to cold/error paths +- `; optimized using Dynamic PGO` in the header (confirms PGO applied) + +## Troubleshooting + +### No disassembly output + +- Verify the assembly path is correct and the DLL exists +- Check if the method name is spelled correctly (case-sensitive) +- Try without the `-t` option to search all types +- Use `-v` for verbose output to see what's happening +- Ensure you built with `-c Release` (Debug builds produce different code) + +### Tier-1 produces no output / shows Tier-0 instead + +- The Tier-1 simulation invokes the method via reflection with default arguments. Methods taking **ref struct** parameters (`Span`, `ReadOnlySpan`, `Memory`) cannot be invoked this way — the simulation silently fails and produces no output. +- **Workaround:** Use `--fullopts` for methods with ref struct parameters. It compiles in a single pass without invocation. +- For other methods: this means the Tier-1 recompilation didn't fire. The method may not be invocable with null/default arguments. +- Check `-v` output for error messages during invocation phases. + +### Method not found + +- Ensure the assembly is built (Release configuration recommended) +- For generic methods, provide all required type parameters via `--type-params` +- For methods in generic classes, provide class type parameters via `--class-type-params` +- Use fully qualified type names for type parameters from other assemblies +- Generic type names can omit the backtick arity (e.g., `TransactionProcessorBase` matches `TransactionProcessorBase`1`) + +### Multiple methods with same name + +- Specify the type with `-t` to narrow the search +- The tool will use the first matching overload if multiple exist + +### On Windows: `'' was not matched` error + +- Remove all quotes from arguments — pass paths and type names unquoted +- Replace backslashes with forward slashes in paths +- Put the entire command on a single line + +## Building + +```bash +dotnet build tools/JitAsm -c Release +``` diff --git a/tools/JitAsm/StaticCtorDetector.cs b/tools/JitAsm/StaticCtorDetector.cs new file mode 100644 index 000000000000..1b512dceb957 --- /dev/null +++ b/tools/JitAsm/StaticCtorDetector.cs @@ -0,0 +1,175 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.RegularExpressions; + +namespace JitAsm; + +internal static partial class StaticCtorDetector +{ + // Patterns for detecting static constructor calls in JIT disassembly + // Example: call Namespace.Type:.cctor() + [GeneratedRegex(@"call\s+(?[\w.+`\[\],]+):\.cctor\(\)", RegexOptions.Compiled)] + private static partial Regex CctorCallPattern(); + + // JIT helpers for static field initialization + // Matches both shared and non-shared variants, with optional brackets: + // call CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE + // call [CORINFO_HELP_GET_NONGCSTATIC_BASE] + // call CORINFO_HELP_CLASSINIT_SHARED_DYNAMICCLASS + [GeneratedRegex(@"call\s+\[?CORINFO_HELP_(?:(?:GETSHARED_|GET_)(?:NON)?GCSTATIC_BASE|CLASSINIT_SHARED_DYNAMICCLASS)\]?", RegexOptions.Compiled)] + private static partial Regex StaticHelperPattern(); + + // Pattern for type references in static helper context + // The JIT output often shows the type being initialized nearby + // Example: ; Namespace.Type + [GeneratedRegex(@";\s*(?[\w.+`\[\],]+)\s*$", RegexOptions.Compiled | RegexOptions.Multiline)] + private static partial Regex TypeCommentPattern(); + + // Pattern for detecting lazy initialization checks + // Example: cmp dword ptr [Namespace.Type:initialized], 0 + [GeneratedRegex(@"\[(?[\w.+`\[\],]+):", RegexOptions.Compiled)] + private static partial Regex StaticFieldAccessPattern(); + + // Pattern for extracting types from call targets in JIT output. + // In diffable mode, long call targets wrap to the next line, e.g.: + // call + // [Nethermind.Core.Eip4844Constants:get_MinBlobGasPrice():...] + // In non-diffable mode, they appear on one line: + // call [Nethermind.Core.Eip4844Constants:get_MinBlobGasPrice():...] + // This pattern matches the "[Type:Method" portion (possibly with [r11] prefix for callvirt). + [GeneratedRegex(@"(?:\[(?:r\d+\])?)?\[?(?[\w.+`\[\],]+):(?!:)", RegexOptions.Compiled)] + private static partial Regex CallTargetTypePattern(); + + public static IReadOnlyList DetectStaticCtors(string disassemblyOutput) + { + var detectedTypes = new HashSet(StringComparer.Ordinal); + + // Detect direct .cctor calls + foreach (Match match in CctorCallPattern().Matches(disassemblyOutput)) + { + var typeName = NormalizeTypeName(match.Groups["type"].Value); + if (!string.IsNullOrEmpty(typeName)) + { + detectedTypes.Add(typeName); + } + } + + // Check for static helper calls and try to find associated types + if (StaticHelperPattern().IsMatch(disassemblyOutput)) + { + int typesBeforeHelperScan = detectedTypes.Count; + + // Look for type references near the helper calls (±3 lines) + var lines = disassemblyOutput.Split('\n'); + for (int i = 0; i < lines.Length; i++) + { + if (StaticHelperPattern().IsMatch(lines[i])) + { + // Check surrounding lines for type context + for (int j = Math.Max(0, i - 3); j <= Math.Min(lines.Length - 1, i + 3); j++) + { + var typeMatch = TypeCommentPattern().Match(lines[j]); + if (typeMatch.Success) + { + var typeName = NormalizeTypeName(typeMatch.Groups["type"].Value); + if (!string.IsNullOrEmpty(typeName) && IsValidTypeName(typeName)) + { + detectedTypes.Add(typeName); + } + } + + var fieldMatch = StaticFieldAccessPattern().Match(lines[j]); + if (fieldMatch.Success) + { + var typeName = NormalizeTypeName(fieldMatch.Groups["type"].Value); + if (!string.IsNullOrEmpty(typeName) && IsValidTypeName(typeName)) + { + detectedTypes.Add(typeName); + } + } + } + } + } + + // Fallback: if helpers were found but no types extracted from nearby context + // (common when JitDiffableDasm strips annotations or helper has no nearby type comment), + // scan the entire method for types referenced in call instructions. + // In diffable mode, long call targets wrap to the next line: + // call + // [Nethermind.Core.Eip4844Constants:get_MinBlobGasPrice():...] + // Pre-running extra cctors is cheap; missing the right one means stale output. + if (detectedTypes.Count == typesBeforeHelperScan) + { + for (int i = 0; i < lines.Length; i++) + { + var line = lines[i]; + // Check current line and continuation lines after bare "call" instructions + if (line.TrimStart().StartsWith("call") || (i > 0 && lines[i - 1].TrimEnd().EndsWith("call"))) + { + var callMatch = CallTargetTypePattern().Match(line); + if (callMatch.Success) + { + var typeName = NormalizeTypeName(callMatch.Groups["type"].Value); + if (!string.IsNullOrEmpty(typeName) && IsValidTypeName(typeName)) + { + detectedTypes.Add(typeName); + } + } + } + } + } + } + + var result = new List(detectedTypes); + result.Sort(StringComparer.Ordinal); + return result; + } + + private static string NormalizeTypeName(string typeName) + { + // Remove generic arity suffix if present (e.g., `1, `2) + var tickIndex = typeName.IndexOf('`'); + if (tickIndex > 0) + { + // Keep up to and including the backtick and number for proper type resolution + var endIndex = tickIndex + 1; + while (endIndex < typeName.Length && char.IsDigit(typeName[endIndex])) + { + endIndex++; + } + + // If there's more after the generic arity, check for nested types + if (endIndex < typeName.Length && typeName[endIndex] == '+') + { + // Keep nested type info + return typeName; + } + + return typeName[..endIndex]; + } + + return typeName.Trim(); + } + + private static bool IsValidTypeName(string typeName) + { + // Filter out obvious non-type names + if (string.IsNullOrWhiteSpace(typeName)) + return false; + + // Must contain at least one letter + if (!typeName.Any(char.IsLetter)) + return false; + + // Should not be just a keyword or register name + var lowered = typeName.ToLowerInvariant(); + string[] invalidNames = ["rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rsp", "rbp", + "eax", "ebx", "ecx", "edx", "esi", "edi", "esp", "ebp", + "ptr", "dword", "qword", "byte", "word"]; + if (invalidNames.Contains(lowered)) + return false; + + return true; + } +} diff --git a/tools/Kute/Nethermind.Tools.Kute/Program.cs b/tools/Kute/Nethermind.Tools.Kute/Program.cs index 6c640ab0f808..e4bd6d9bcfa1 100644 --- a/tools/Kute/Nethermind.Tools.Kute/Program.cs +++ b/tools/Kute/Nethermind.Tools.Kute/Program.cs @@ -14,6 +14,7 @@ using Nethermind.Tools.Kute.SecretProvider; using Nethermind.Tools.Kute.SystemClock; using System.CommandLine; +using System.Net; namespace Nethermind.Tools.Kute; @@ -56,7 +57,18 @@ private static IServiceProvider BuildServiceProvider(ParseResult parseResult) collection.AddSingleton(); collection.AddSingleton(); - collection.AddSingleton(); + collection.AddSingleton(_ => + { + // Disable proxy auto-detection to avoid timeout (~2s) on Windows. + // Each Kute invocation is a separate process, so the proxy lookup + // would otherwise be paid on every single measured request. + var handler = new SocketsHttpHandler + { + UseProxy = false, + AutomaticDecompression = DecompressionMethods.None, + }; + return new HttpClient(handler); + }); collection.AddSingleton(new FileSecretProvider(parseResult.GetValue(Config.JwtSecretFilePath)!)); collection.AddSingleton(provider => new TtlAuth( From 4563cdd7b2dca73aee5b221cbb779520e6619a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Wed, 18 Feb 2026 14:34:57 +0100 Subject: [PATCH 10/13] fix: refresh full extcode cache entries for existing keys --- .../TransactionProcessorEip7702Tests.cs | 114 +++++++++++++++++- .../VirtualMachine.ExtCodeCache.cs | 5 +- 2 files changed, 116 insertions(+), 3 deletions(-) diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs index d861fc4e4d47..bca1e387399e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs @@ -21,6 +21,8 @@ using Nethermind.Blockchain.Tracing; using Nethermind.Core.Test; using Nethermind.Int256; +using Nethermind.Evm.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; namespace Nethermind.Evm.Test; @@ -1108,7 +1110,87 @@ public void Execute_EXTCODECOPY_WhenCodeChangesWithinBlock_ReturnsUpdatedBytes() Assert.That(secondTracer.ReturnValue?.ToArray(), Is.EquivalentTo(new byte[] { (byte)Instruction.MUL })); } + [Test] + [NonParallelizable] + public void Execute_EXTCODESIZE_WhenCacheIsFull_ExistingKeyIsRefreshed() + { + int previousMaxEntries = VirtualMachine.MaxExtCodeCacheEntries; + VirtualMachine.SetMaxExtCodeCacheEntries(1); + try + { + PrivateKey sender = TestItem.PrivateKeyA; + Address inspectedAddress = TestItem.AddressB; + Address codeSource = TestItem.AddressC; + _stateProvider.CreateAccount(sender.Address, 1.Ether()); + + byte[] initialInspectedCode = Prepare.EvmCode + .Op(Instruction.STOP) + .Done; + byte[] updatedInspectedCode = Prepare.EvmCode + .Op(Instruction.ADD) + .Op(Instruction.STOP) + .Done; + DeployCode(inspectedAddress, initialInspectedCode); + + byte[] extcodesizeReaderCode = Prepare.EvmCode + .PushData(inspectedAddress) + .Op(Instruction.EXTCODESIZE) + .Op(Instruction.PUSH0) + .Op(Instruction.MSTORE8) + .PushData(1) + .Op(Instruction.PUSH0) + .Op(Instruction.RETURN) + .Done; + DeployCode(codeSource, extcodesizeReaderCode); + _stateProvider.Commit(Prague.Instance, true); + + TrackingCodeInfoRepository trackingRepository = new(new EthereumCodeInfoRepository(_stateProvider), inspectedAddress); + EthereumVirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); + ITransactionProcessor transactionProcessor = new EthereumTransactionProcessor( + BlobBaseFeeCalculator.Instance, + _specProvider, + _stateProvider, + virtualMachine, + trackingRepository, + LimboLogs.Instance); + + Block block = Build.A.Block.WithNumber(long.MaxValue) + .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) + .WithGasLimit(10_000_000) + .TestObject; + BlockExecutionContext blkCtx = new(block.Header, _specProvider.GetSpec(block.Header)); + + _ = ExecuteCallWithOutput(transactionProcessor, sender, codeSource, blkCtx, 0); + + DeployCode(inspectedAddress, updatedInspectedCode); + _stateProvider.Commit(Prague.Instance, true); + + int before = trackingRepository.TrackedHashLookupCount; + _ = ExecuteCallWithOutput(transactionProcessor, sender, codeSource, blkCtx, 1); + int afterFirst = trackingRepository.TrackedHashLookupCount; + _ = ExecuteCallWithOutput(transactionProcessor, sender, codeSource, blkCtx, 2); + int afterSecond = trackingRepository.TrackedHashLookupCount; + + Assert.That(afterFirst - before, Is.EqualTo(1)); + Assert.That(afterSecond - afterFirst, Is.EqualTo(0)); + } + finally + { + VirtualMachine.SetMaxExtCodeCacheEntries(previousMaxEntries); + } + } + private CallOutputTracer ExecuteCallWithOutput(PrivateKey sender, Address to, BlockExecutionContext blockExecutionContext, ulong nonce) + { + return ExecuteCallWithOutput(_transactionProcessor, sender, to, blockExecutionContext, nonce); + } + + private CallOutputTracer ExecuteCallWithOutput( + ITransactionProcessor transactionProcessor, + PrivateKey sender, + Address to, + BlockExecutionContext blockExecutionContext, + ulong nonce) { Transaction tx = Build.A.Transaction .WithType(TxType.EIP1559) @@ -1119,10 +1201,40 @@ private CallOutputTracer ExecuteCallWithOutput(PrivateKey sender, Address to, Bl .TestObject; CallOutputTracer tracer = new(); - _ = _transactionProcessor.Execute(tx, blockExecutionContext, tracer); + _ = transactionProcessor.Execute(tx, blockExecutionContext, tracer); return tracer; } + private sealed class TrackingCodeInfoRepository(ICodeInfoRepository inner, Address trackedAddress) : ICodeInfoRepository + { + public int TrackedHashLookupCount { get; private set; } + + public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) + => inner.GetCachedCodeInfo(codeSource, followDelegation, vmSpec, out delegationAddress); + + public CodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) + { + if (codeSource == trackedAddress) + { + TrackedHashLookupCount++; + } + + return inner.GetCachedCodeInfo(codeSource, in codeHash, vmSpec); + } + + public ValueHash256 GetExecutableCodeHash(Address address, IReleaseSpec spec) + => inner.GetExecutableCodeHash(address, spec); + + public void InsertCode(ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec) + => inner.InsertCode(code, codeOwner, spec); + + public void SetDelegation(Address codeSource, Address authority, IReleaseSpec spec) + => inner.SetDelegation(codeSource, authority, spec); + + public bool TryGetDelegation(Address address, IReleaseSpec spec, [NotNullWhen(true)] out Address? delegatedAddress) + => inner.TryGetDelegation(address, spec, out delegatedAddress); + } + private void DeployCode(Address codeSource, byte[] code) { _stateProvider.CreateAccountIfNotExists(codeSource, 0); diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs index 63046679a3fd..6a4f18a46c18 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs @@ -114,8 +114,9 @@ private void StoreCacheEntry(AddressAsKey key, in ValueHash256 codeHash, uint co } // Under high-cardinality workloads, clearing the whole dictionary causes avoidable churn. - // Once capacity is reached, keep current hot set and skip admitting new entries. - if (_extCodeCache!.Count >= _maxExtCodeCacheEntries) + // Once capacity is reached, keep current hot set and skip admitting new keys. + // Still allow updating existing keys so hot entries can be refreshed. + if (_extCodeCache!.Count >= _maxExtCodeCacheEntries && !_extCodeCache.ContainsKey(key)) { return; } From 68ad590f032ea8906a1b2271214f359754e4a1ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Wed, 18 Feb 2026 14:40:30 +0100 Subject: [PATCH 11/13] refactor: unify extcode cache resolution and override lookups --- .../CachedCodeInfoRepository.cs | 16 +++++- .../Nethermind.Evm/CodeInfoRepository.cs | 21 +++++-- .../VirtualMachine.ExtCodeCache.cs | 56 ++++++++----------- .../OverridableCodeInfoRepository.cs | 36 +++++++++--- 4 files changed, 81 insertions(+), 48 deletions(-) diff --git a/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs b/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs index d05768a92dc4..556ad6069357 100644 --- a/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs @@ -28,7 +28,7 @@ public class CachedCodeInfoRepository( public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) { - if (vmSpec.IsPrecompile(codeSource) && _cachedPrecompile.TryGetValue(codeSource, out var cachedCodeInfo)) + if (TryGetCachedPrecompile(codeSource, vmSpec, out CodeInfo cachedCodeInfo)) { delegationAddress = null; return cachedCodeInfo; @@ -38,7 +38,7 @@ public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IRe public CodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) { - if (vmSpec.IsPrecompile(codeSource) && _cachedPrecompile.TryGetValue(codeSource, out CodeInfo cachedCodeInfo)) + if (TryGetCachedPrecompile(codeSource, vmSpec, out CodeInfo cachedCodeInfo)) { return cachedCodeInfo; } @@ -67,6 +67,18 @@ public bool TryGetDelegation(Address address, IReleaseSpec spec, return baseCodeInfoRepository.TryGetDelegation(address, spec, out delegatedAddress); } + private bool TryGetCachedPrecompile(Address codeSource, IReleaseSpec vmSpec, [NotNullWhen(true)] out CodeInfo? cachedCodeInfo) + { + if (vmSpec.IsPrecompile(codeSource) && _cachedPrecompile.TryGetValue(codeSource, out CodeInfo precompileCodeInfo)) + { + cachedCodeInfo = precompileCodeInfo; + return true; + } + + cachedCodeInfo = null; + return false; + } + private static CodeInfo CreateCachedPrecompile( in KeyValuePair originalPrecompile, ConcurrentDictionary> cache) diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs index 9f20e84e98b9..0c6ff55f2143 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs @@ -31,9 +31,9 @@ public CodeInfoRepository(IWorldState worldState, IPrecompileProvider precompile public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) { delegationAddress = null; - if (vmSpec.IsPrecompile(codeSource)) // _localPrecompiles have to have all precompiles + if (TryGetPrecompileCodeInfo(codeSource, vmSpec, out CodeInfo precompileCodeInfo)) { - return _localPrecompiles[codeSource]; + return precompileCodeInfo; } CodeInfo cachedCodeInfo = InternalGetCachedCode(_worldState, codeSource, vmSpec); @@ -49,13 +49,26 @@ public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IRe public CodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) { - if (vmSpec.IsPrecompile(codeSource)) + if (TryGetPrecompileCodeInfo(codeSource, vmSpec, out CodeInfo precompileCodeInfo)) { - return _localPrecompiles[codeSource]; + return precompileCodeInfo; } return InternalGetCachedCode(_worldState, in codeHash, vmSpec); } + + private bool TryGetPrecompileCodeInfo(Address codeSource, IReleaseSpec vmSpec, [NotNullWhen(true)] out CodeInfo? precompileCodeInfo) + { + if (vmSpec.IsPrecompile(codeSource)) // _localPrecompiles have to have all precompiles + { + precompileCodeInfo = _localPrecompiles[codeSource]; + return true; + } + + precompileCodeInfo = null; + return false; + } + private CodeInfo InternalGetCachedCode(Address codeSource, IReleaseSpec vmSpec) { ref readonly ValueHash256 codeHash = ref _worldState.GetCodeHash(codeSource); diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs index 6a4f18a46c18..d6f4c21aa0ec 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs @@ -38,28 +38,7 @@ public static void SetMaxExtCodeCacheEntries(int maxEntries) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal uint GetExtCodeSizeCached(Address address, IReleaseSpec spec) - { - if (_maxExtCodeCacheEntries == 0) - { - CodeInfo uncachedCodeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); - return uncachedCodeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)uncachedCodeInfo.CodeSpan.Length; - } - - _extCodeCache ??= new Dictionary(8); - AddressAsKey key = address; - - ValueHash256 codeHash = _worldState.GetCodeHash(address); - if (_extCodeCache.TryGetValue(key, out ExtCodeCacheEntry entry) && entry.CodeHash == codeHash) - { - return entry.CodeSize; - } - - CodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, in codeHash, spec); - uint codeSize = codeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)codeInfo.CodeSpan.Length; - - StoreCacheEntry(key, codeHash, codeSize, codeInfo); - return codeSize; - } + => ResolveExtCodeCacheEntry(address, spec).CodeSize; [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ResetExtCodeCache() @@ -81,48 +60,59 @@ private void ResetExtCodeCacheForBlock() [MethodImpl(MethodImplOptions.AggressiveInlining)] internal CodeInfo GetExtCodeInfoCached(Address address, IReleaseSpec spec) + => ResolveExtCodeCacheEntry(address, spec).CodeInfo; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ExtCodeCacheEntry ResolveExtCodeCacheEntry(Address address, IReleaseSpec spec) { if (_maxExtCodeCacheEntries == 0) { CodeInfo uncachedCodeInfo = _codeInfoRepository.GetCachedCodeInfo(address, followDelegation: false, spec, out _); - return uncachedCodeInfo; + return CreateExtCodeCacheEntry(default, uncachedCodeInfo); } _extCodeCache ??= new Dictionary(8); AddressAsKey key = address; ValueHash256 codeHash = _worldState.GetCodeHash(address); - if (_extCodeCache.TryGetValue(key, out ExtCodeCacheEntry entry) - && entry.CodeHash == codeHash - && entry.CodeInfo is not null) + if (_extCodeCache.TryGetValue(key, out ExtCodeCacheEntry entry) && entry.CodeHash == codeHash) { - return entry.CodeInfo; + return entry; } CodeInfo codeInfo = _codeInfoRepository.GetCachedCodeInfo(address, in codeHash, spec); + ExtCodeCacheEntry refreshedEntry = CreateExtCodeCacheEntry(in codeHash, codeInfo); + StoreCacheEntry(key, in refreshedEntry); + return refreshedEntry; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ExtCodeCacheEntry CreateExtCodeCacheEntry(in ValueHash256 codeHash, CodeInfo codeInfo) + { uint codeSize = codeInfo is EofCodeInfo ? (uint)EofValidator.MAGIC.Length : (uint)codeInfo.CodeSpan.Length; - StoreCacheEntry(key, codeHash, codeSize, codeInfo); - return codeInfo; + return new ExtCodeCacheEntry(codeHash, codeSize, codeInfo); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void StoreCacheEntry(AddressAsKey key, in ValueHash256 codeHash, uint codeSize, CodeInfo? codeInfo) + private void StoreCacheEntry(AddressAsKey key, in ExtCodeCacheEntry entry) { if (_maxExtCodeCacheEntries == 0) { return; } + Dictionary extCodeCache = _extCodeCache!; + // Under high-cardinality workloads, clearing the whole dictionary causes avoidable churn. // Once capacity is reached, keep current hot set and skip admitting new keys. // Still allow updating existing keys so hot entries can be refreshed. - if (_extCodeCache!.Count >= _maxExtCodeCacheEntries && !_extCodeCache.ContainsKey(key)) + if (extCodeCache.Count >= _maxExtCodeCacheEntries && !extCodeCache.ContainsKey(key)) { return; } - _extCodeCache[key] = new ExtCodeCacheEntry(codeHash, codeSize, codeInfo); + extCodeCache[key] = entry; } - private readonly record struct ExtCodeCacheEntry(ValueHash256 CodeHash, uint CodeSize, CodeInfo? CodeInfo); + private readonly record struct ExtCodeCacheEntry(ValueHash256 CodeHash, uint CodeSize, CodeInfo CodeInfo); } diff --git a/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs b/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs index 874dadafcdd8..2ab3c32091c7 100644 --- a/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.State/OverridableEnv/OverridableCodeInfoRepository.cs @@ -21,15 +21,18 @@ public class OverridableCodeInfoRepository(ICodeInfoRepository codeInfoRepositor public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IReleaseSpec vmSpec, out Address? delegationAddress) { delegationAddress = null; - if (_precompileOverrides.TryGetValue(codeSource, out var precompile)) return precompile.codeInfo; + if (TryGetPrecompileOverride(codeSource, out CodeInfo precompileCodeInfo)) + { + return precompileCodeInfo; + } - if (_codeOverrides.TryGetValue(codeSource, out var result)) + if (TryGetCodeOverride(codeSource, out var overrideInfo)) { - return !result.codeInfo.IsEmpty && - ICodeInfoRepository.TryGetDelegatedAddress(result.codeInfo.CodeSpan, out delegationAddress) && + return !overrideInfo.codeInfo.IsEmpty && + ICodeInfoRepository.TryGetDelegatedAddress(overrideInfo.codeInfo.CodeSpan, out delegationAddress) && followDelegation ? GetCachedCodeInfo(delegationAddress, false, vmSpec, out Address? _) - : result.codeInfo; + : overrideInfo.codeInfo; } return codeInfoRepository.GetCachedCodeInfo(codeSource, followDelegation, vmSpec, out delegationAddress); @@ -37,14 +40,14 @@ public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IRe public CodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) { - if (_precompileOverrides.TryGetValue(codeSource, out var precompile)) + if (TryGetPrecompileOverride(codeSource, out CodeInfo precompileCodeInfo)) { - return precompile.codeInfo; + return precompileCodeInfo; } - if (_codeOverrides.TryGetValue(codeSource, out var result)) + if (TryGetCodeOverride(codeSource, out var overrideInfo)) { - return result.codeInfo; + return overrideInfo.codeInfo; } return codeInfoRepository.GetCachedCodeInfo(codeSource, in codeHash, vmSpec); @@ -84,6 +87,21 @@ public ValueHash256 GetExecutableCodeHash(Address address, IReleaseSpec spec) => ? result.codeHash : codeInfoRepository.GetExecutableCodeHash(address, spec); + private bool TryGetPrecompileOverride(Address codeSource, [NotNullWhen(true)] out CodeInfo? precompileCodeInfo) + { + if (_precompileOverrides.TryGetValue(codeSource, out (CodeInfo codeInfo, Address _) precompileOverride)) + { + precompileCodeInfo = precompileOverride.codeInfo; + return true; + } + + precompileCodeInfo = null; + return false; + } + + private bool TryGetCodeOverride(Address codeSource, out (CodeInfo codeInfo, ValueHash256 codeHash) codeOverride) + => _codeOverrides.TryGetValue(codeSource, out codeOverride); + public void ResetOverrides() { _precompileOverrides.Clear(); From afcd5099f6dfa0b70f10cf7c3b4bca0e39672a0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= <43241881+kamilchodola@users.noreply.github.com> Date: Wed, 18 Feb 2026 14:49:58 +0100 Subject: [PATCH 12/13] Update src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs index d6f4c21aa0ec..d38735f922ed 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs @@ -43,7 +43,7 @@ internal uint GetExtCodeSizeCached(Address address, IReleaseSpec spec) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ResetExtCodeCache() { - // Cache is keyed by code hash to remain correct when code changes mid-transaction. + // Cache is keyed by address, with code hash validation to remain correct when code changes mid-transaction. _extCodeCache?.Clear(); } From 277a6f77dc9dee4fdc2d4b96c8ef31229c9b27e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Chodo=C5=82a?= Date: Fri, 27 Feb 2026 16:38:29 +0100 Subject: [PATCH 13/13] refactor: address PR review comments - Simplify GetCachedCodeInfo(codeHash) to ternary expressions in CachedCodeInfoRepository and CodeInfoRepository (LukaszRozmej) - Use ValueKeccak.OfAnEmptyString in ExtCodeHash (LukaszRozmej) - Consolidate EXTCODESIZE/EXTCODECOPY code-change tests into one parameterized test (LukaszRozmej) --- .../TransactionProcessorEip7702Tests.cs | 100 ++++++------------ .../CachedCodeInfoRepository.cs | 13 +-- .../Nethermind.Evm/CodeInfoRepository.cs | 13 +-- .../EvmInstructions.Environment.cs | 2 +- 4 files changed, 44 insertions(+), 84 deletions(-) diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs index bca1e387399e..68699dd65507 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs @@ -1020,33 +1020,40 @@ public void Execute_EXTCODESIZEOnDelegatedThatTriggersOptimization_ReturnsZeroIf Assert.That(tracer.ReturnValue, Is.EquivalentTo(new byte[] { Convert.ToByte(!isDelegated) })); } - [Test] - public void Execute_EXTCODESIZE_WhenCodeChangesWithinBlock_ReturnsUpdatedSize() + [TestCase(Instruction.EXTCODESIZE)] + [TestCase(Instruction.EXTCODECOPY)] + public void Execute_ExtCode_WhenCodeChangesWithinBlock_ReturnsUpdatedValue(Instruction instruction) { PrivateKey sender = TestItem.PrivateKeyA; Address inspectedAddress = TestItem.AddressB; Address codeSource = TestItem.AddressC; _stateProvider.CreateAccount(sender.Address, 1.Ether()); - byte[] initialInspectedCode = Prepare.EvmCode - .Op(Instruction.STOP) - .Done; - byte[] updatedInspectedCode = Prepare.EvmCode - .Op(Instruction.ADD) - .Op(Instruction.STOP) - .Done; + byte[] initialInspectedCode = Prepare.EvmCode.Op(Instruction.ADD).Done; + byte[] updatedInspectedCode = Prepare.EvmCode.Op(Instruction.MUL).Done; DeployCode(inspectedAddress, initialInspectedCode); - byte[] extcodesizeReaderCode = Prepare.EvmCode - .PushData(inspectedAddress) - .Op(Instruction.EXTCODESIZE) - .Op(Instruction.PUSH0) - .Op(Instruction.MSTORE8) - .PushData(1) - .Op(Instruction.PUSH0) - .Op(Instruction.RETURN) - .Done; - DeployCode(codeSource, extcodesizeReaderCode); + byte[] readerCode = instruction == Instruction.EXTCODESIZE + ? Prepare.EvmCode + .PushData(inspectedAddress) + .Op(Instruction.EXTCODESIZE) + .Op(Instruction.PUSH0) + .Op(Instruction.MSTORE8) + .PushData(1) + .Op(Instruction.PUSH0) + .Op(Instruction.RETURN) + .Done + : Prepare.EvmCode + .PushData(1) + .Op(Instruction.PUSH0) + .Op(Instruction.PUSH0) + .PushData(inspectedAddress) + .Op(Instruction.EXTCODECOPY) + .PushData(1) + .Op(Instruction.PUSH0) + .Op(Instruction.RETURN) + .Done; + DeployCode(codeSource, readerCode); _stateProvider.Commit(Prague.Instance, true); Block block = Build.A.Block.WithNumber(long.MaxValue) @@ -1055,59 +1062,22 @@ public void Execute_EXTCODESIZE_WhenCodeChangesWithinBlock_ReturnsUpdatedSize() .TestObject; BlockExecutionContext blkCtx = new(block.Header, _specProvider.GetSpec(block.Header)); - CallOutputTracer firstTracer = ExecuteCallWithOutput(sender, codeSource, blkCtx, 0); - Assert.That(firstTracer.ReturnValue?.ToArray(), Is.EquivalentTo(new byte[] { (byte)initialInspectedCode.Length })); - - DeployCode(inspectedAddress, updatedInspectedCode); - _stateProvider.Commit(Prague.Instance, true); - - CallOutputTracer secondTracer = ExecuteCallWithOutput(sender, codeSource, blkCtx, 1); - Assert.That(secondTracer.ReturnValue?.ToArray(), Is.EquivalentTo(new byte[] { (byte)updatedInspectedCode.Length })); - } - - [Test] - public void Execute_EXTCODECOPY_WhenCodeChangesWithinBlock_ReturnsUpdatedBytes() - { - PrivateKey sender = TestItem.PrivateKeyA; - Address inspectedAddress = TestItem.AddressB; - Address codeSource = TestItem.AddressC; - _stateProvider.CreateAccount(sender.Address, 1.Ether()); - - byte[] initialInspectedCode = Prepare.EvmCode - .Op(Instruction.ADD) - .Done; - byte[] updatedInspectedCode = Prepare.EvmCode - .Op(Instruction.MUL) - .Done; - DeployCode(inspectedAddress, initialInspectedCode); - - byte[] extcodecopyReaderCode = Prepare.EvmCode - .PushData(1) - .Op(Instruction.PUSH0) - .Op(Instruction.PUSH0) - .PushData(inspectedAddress) - .Op(Instruction.EXTCODECOPY) - .PushData(1) - .Op(Instruction.PUSH0) - .Op(Instruction.RETURN) - .Done; - DeployCode(codeSource, extcodecopyReaderCode); - _stateProvider.Commit(Prague.Instance, true); - - Block block = Build.A.Block.WithNumber(long.MaxValue) - .WithTimestamp(MainnetSpecProvider.PragueBlockTimestamp) - .WithGasLimit(10_000_000) - .TestObject; - BlockExecutionContext blkCtx = new(block.Header, _specProvider.GetSpec(block.Header)); + // EXTCODESIZE returns the code length; EXTCODECOPY returns the first byte of code + byte expectedInitial = instruction == Instruction.EXTCODESIZE + ? (byte)initialInspectedCode.Length + : (byte)Instruction.ADD; + byte expectedUpdated = instruction == Instruction.EXTCODESIZE + ? (byte)updatedInspectedCode.Length + : (byte)Instruction.MUL; CallOutputTracer firstTracer = ExecuteCallWithOutput(sender, codeSource, blkCtx, 0); - Assert.That(firstTracer.ReturnValue?.ToArray(), Is.EquivalentTo(new byte[] { (byte)Instruction.ADD })); + Assert.That(firstTracer.ReturnValue?.ToArray(), Is.EquivalentTo(new byte[] { expectedInitial })); DeployCode(inspectedAddress, updatedInspectedCode); _stateProvider.Commit(Prague.Instance, true); CallOutputTracer secondTracer = ExecuteCallWithOutput(sender, codeSource, blkCtx, 1); - Assert.That(secondTracer.ReturnValue?.ToArray(), Is.EquivalentTo(new byte[] { (byte)Instruction.MUL })); + Assert.That(secondTracer.ReturnValue?.ToArray(), Is.EquivalentTo(new byte[] { expectedUpdated })); } [Test] diff --git a/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs b/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs index 556ad6069357..860ea5ae1d09 100644 --- a/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs @@ -36,15 +36,10 @@ public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IRe return baseCodeInfoRepository.GetCachedCodeInfo(codeSource, followDelegation, vmSpec, out delegationAddress); } - public CodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) - { - if (TryGetCachedPrecompile(codeSource, vmSpec, out CodeInfo cachedCodeInfo)) - { - return cachedCodeInfo; - } - - return baseCodeInfoRepository.GetCachedCodeInfo(codeSource, in codeHash, vmSpec); - } + public CodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) => + TryGetCachedPrecompile(codeSource, vmSpec, out CodeInfo cachedCodeInfo) + ? cachedCodeInfo + : baseCodeInfoRepository.GetCachedCodeInfo(codeSource, in codeHash, vmSpec); public ValueHash256 GetExecutableCodeHash(Address address, IReleaseSpec spec) { diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs index ae5d4ce39dcc..d927acd2f75c 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs @@ -47,15 +47,10 @@ public CodeInfo GetCachedCodeInfo(Address codeSource, bool followDelegation, IRe return cachedCodeInfo; } - public CodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) - { - if (TryGetPrecompileCodeInfo(codeSource, vmSpec, out CodeInfo precompileCodeInfo)) - { - return precompileCodeInfo; - } - - return InternalGetCachedCode(_worldState, in codeHash, vmSpec); - } + public CodeInfo GetCachedCodeInfo(Address codeSource, in ValueHash256 codeHash, IReleaseSpec vmSpec) => + TryGetPrecompileCodeInfo(codeSource, vmSpec, out CodeInfo precompileCodeInfo) + ? precompileCodeInfo + : InternalGetCachedCode(_worldState, in codeHash, vmSpec); private bool TryGetPrecompileCodeInfo(Address codeSource, IReleaseSpec vmSpec, [NotNullWhen(true)] out CodeInfo? precompileCodeInfo) { diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs index 15227dcaa469..f1d8c4e09599 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs @@ -678,7 +678,7 @@ public static EvmExceptionType InstructionExtCodeHashEof(in codeHash); return EvmExceptionType.None;

fy^YPs4?G`v`S=9{h0L3^-xnjovo}A`6rTPM=v*H^ delta 7828 zcmbW+RZ|>Hlz?G;aCdhPt`nSL7~Czvb#RwJU~qT$;O?H_5Zv8@6Ck(;31PEU`~88v z>x-w}s;=srbNa0qDLV%V#@fpLil{$S*?^362=|Snl9isvG3KV_FDqjCTZQAt>Tu$W zt=Qd<^qLSnd@m=d?YBHvh4-pwp)h!;>2z_P#=%5saAaHuIJULdFZdX5Y8|&hvdy!@JC-*8I|s z)Q1gId_3t2-w*KQky%fIiNoh|FN3E% z`6Sm6MZi+Q&+fTMSo>dJ6i-84u8km6FF0IO#0JAv9$C30hGC5L1inptO|m5$L23=+ z>r09A7KfpI%gAn5=0Z3)gsVPy_Pg}Z$XxXW z0SLvQxc!K@xbLF8z0$MvEn(|;NaUysH5o}s`49y9pm%)&E^z-sl%N7Q0-sr00Uz9c zAXEx~gd>JJ5EKF+MD3TFa$+tiE>0N{P06Cn8s}8}>e5Fc9fF+7&rDKXZuH7vwH)+& zBxU@QZ4C_FgJSl>l`e`rxD-=BeNAd>6xr9W5PmEXBN0lzru!H?mtOG6GEs|P^UN_w z*M)@0UmgX0s~dBGWYnNknXV>KBWO$i>MA1^`8}pnF+8vIQK9u21hFHiA=uiKRzsan8RqeaBq?NfyS|8O)CP2jHKRxS%ZMx7YIY16{3abhfikdIL6vqUCfjAm9gou zh~`oV5e<%N@FxbfJsQQ}F?<_IH^v-&h{mp*Nr27{1YbwW(U{`EctH3O;sSv>QGKzf>qpF5Vc ztgr?{TIC2SXCzPT}5g z*UO*T-S5|2w=PFe#_*4jbP+7 zV8u`xmk1{uPMt;c5zfI+PxhzchN+tAcS4E^1_qzVPkK{%vDE@nT8lyHt(9VwiZapN zL=!C}9AiNvq%$3n^Ttbf#ZfcExDOZN_8->V9d6m0KZ$yY4l9Ue^JS-7lqs|rR9d*n ziobI6-#R!mJ!lAhS}V>guh#i+qXbh3Xw6Kkes4)XKSr;}Mi(j{{wwX!3F-8Lfv`&E z$VxS{yFn-xoJk0TIB4?n3sx%xfQcXh-GWM~0wrwVG`_!dz$?2wgE_-tTq+;I>l!GR zc_emMwvVnr26Yi)J##+M6cgrLkRHKI^x=T4$IjCC=Ay4>|5+dm1Ta<+|Yw6l>c$|(}@kHlf%S@X~Bq((~Yc1h|85Q~t=3E|&CrJ3bf1KQRe ziSaiPsxJNkrOEgK^^~I-evh9I(+T}&X_9OCE~dL~LxXFG!fEQ@eRxhY#p9AI(Jm7`Rddk3K(t2%s)JyO5-^<0Do8TPHmFIM$SG9ZC${0ICh90l~T$)c| za+%7QD8xfYfz!29v(p9Z%>ZVE$|A^~m7SfW@pVV*w*H~QM3d?JXB$qPA~ADF<|?+A z?CEGQ@;2!L5FG7SXs1u*^EEj-eOZ__$dPTwdEjI`(UlOL7mrz)%Oc;_d9++tiS$dN z1*dT)gPxB)pKG|IKW)-%UJ!-G>>_D8KdwqbMEQa4nS__*(!6~m8#(NrTB{})BK`C5 zx7IACSs%+ba?T_!J(I(*W8$zCOoy+X=iTCRO;4r_9vwYz4ZPY z`LdwhncZsU5>ZygioQYA-sOExm#yLS)?K3xqLRFQ9lc$f{AJniREOd>FcaZxdTCye zCRc(CmGvQO9;-RD)TGJq~i1261YubNh9L3)Hq;<^6DJCiQU(i)F`4@=!=f zj?NB6*hf#d%!^v#zlp1w%XCcs#Nmb zYvduOW_!W!4y3+zDXFOF{5oER#7|VBq}ihqbH*-)QjF^q5N~0Vpd6ieUvo7(uhCi zAW`3ZNiPjjw_iC z=uTIdhsBO&nN>mA|JGHqdrvJ&gppTknP$`1d`kj|5wPPC5lN>V81j?wGad@K{K%e(-Z_b)f&hdSZ;)=uHMMbI#gizh}V+!NA@7#F&P}Fa+Ii3W_;!OL->JT z^s^vuj!=FoqR#PvWo>mRX@pq%Wpx;xIa|lI5NuMlEPSk?Ns0EWZ8{ zgBh~Yj6NR=1CF>qYAWSG&z!(PeTnT!mPvBtm{!d2tfg&seo)938itV-{=1AroA=8X zZlbnhWD$B53)WrvR_JMwk&%oF$=J7qeYC8&*byL^BnYFtvLrzt{3Q5VgEM+i3e}u8 z6ef)u8bpdBjf?c}AdQR4XPUx127lHW^OZ+n0L*wqhFo3Qih&?KF)5S#wSRuL>vLD5 zoWN@pZ(5ug6Icbv)l|t6rX?GkD|p>`O)4NI>;|%_)}L%shFASmjp|IVfk0m1P7_%t zh=l6lcP3#;MPJ=BJ9b}p9fpTwEh}ON`w9(U-1IRHe+Y{AhuF|8S1v-4>sDn@p5qy- z9whbEDQTZ~5^HXtVmP~Te3T*j2-VUfhw`I%Bvak;sFrUvmmVY?#BBCuYNe4I9b~jh zHxWm7Xy3yXymopM&@L;he3~x5e-YE?vU{)1XMS?ON@@`~voCWZl#gc=?=z|lYwblH zNI&M<0(A-E_(w1UQq2C|%dh6uN9GOr#v4x);VqyR9x)(I%Ly;8^3$ZFJmh3il&1pG z@)(MbE@ZwLaf(YN<=;~0SsrTaw(zSf%WYQiMe^sqGjddP@rR$n(@or)Yah#Q{Jnpt ztXGDD{7i+E`it13tnCjDRRhi(3>aq%C&8ch8w*JCl!ex3F`EbAvz}i*@SHQeLR58J zW*&l8s@E=>vPK=}BhUKC->F~B7OPm1tw!+N(fWRQpx#OL*|6LpPuQJfxH0@W@X_*+ zp+ttwk(}vPDR|TG+Gl@qju)r+cyU>oYx%mUiz<*k>(3CZmxhp!H?4vNrnql_k<(Sa z-u~JdJ}{F>5b3@0!K2J-{Z+RiKSU~M%XKUV<}c4YD1vkrybvr7u2xkj9HYc6-$bH| z;tr;+ziw*buya4T>~q6Cxl~ZTriqTlukdSBB)9SouAlmw`2)}REX`6NoE{t|v}^oj z$5u9C%tNLg5ber18RbO=QA^cNzRNP`fLrp667I7<_N%V` zMO5Brg6!LF`oXQ9uz(aFb&6-J6>s!>V;D5RDL-nu+x^zNxvcdYZ8O1CCq;)tW^`b6 z>StLWHh#4Y14`tZ;sjm0uc1I0s1MUV^4iO6feFhMT)CYraRd1dO!7pXDW8@UoQ7Ch zHc(MeML+?IkqtBYSz}jUUq54MN+uysp!^#CKuY|hvv4OSuYlYi!?I2S#>p&7;Oa?J z&g>-Cag346;mucqA3m&$O!}Rotn!%=Ibk5$zB!;W_FEwGjnfE5Z^0>cVgZlj#c#{K zuv@Ocb4ws2OcU?E=9L`z=rC?ByLx?f_NXacheZM3uN2(V!#c))L(2Oe4T9n|tIou;2 zfN!jOJf+idga#eMgqvkS3hhlNefxx-uXNxcN$ICj66R+$)q+Gnp%Sv98O?DX6p}+y z(Yt2z)WE6>i)LanB#t=wlyVrrndh)!fSXH5#^Or6r84Z3$32UoyGN|50e(JX0pY6a z-VyevnTOfwlND7ASDm@j5nH!935cf|BL)>4P0KdjuZ5AYe&wM|x0j+EYktOUuIi(G z$S0}0r0R>-f5z}ZbJHD%(bOR3t}+kNNxj0;(`qfiLL+rJ&MX(<$>NxpZlYtKU>NRu z^l{r=t98b+6bjp6$M#CxHyyr(8l<-jT)VBP6UOGP6XT=N?tcaB$YbA*-r|P1M4O)$ zpGK~(rANiHe?LcwA17rig>9q=)57o*f|82^HsJdtxKIGsz1a1ZOPDNn`B5_`S@|&_LxDdi^h5+CHiQ?8)qTdX;Oq1 zAM<5xZ@qaXU#LD%3@$sbvpqpsd;wFPkr@QT>1UugnQioj>%1`nZT)D-!#uj#DaNhe34!?@YD=yum6E4^Dth8_cmkst4WnqvWzT^W0DpY~#ic&nrHAY(~z&1Ri zaW3qA}q{6{rZ z^~)*#sSDk+2+oP3joT6(f>w<0x8qB#;0&+xgaMPC}`R8#cO}rojWlmIOv?0|8 z-K`Z^)3i^?=8;&g6FFvBCDaXO+89{ZyG1#3l(cGgpfCi+C+Eg`HJ!ZFO|I?DZz|M_ z`YDQdQ1-KruT;47J7)@;ur~%**Rw>j8RkXbru`b~FubXZR&OK;TZi#_+KJ?D_V-~0 zZ|N~N6jOZigJ8{Dv>|6}X(HDd)tP*ETAp+#o7}!kJCe%eeHUKJL2v~r-mvG#(GQBN zUH)QNs#~vD;V&{%vlym15D}kFXhGyDGgp1nZLd-^OtaYjJCeQm-xyr5)xulz4iPXJ zm^(~F8E`*Eur=9$^ayLr)W6Xj7i_!3?>qWDl69}KvSExyZ6>p`;D|h0Bp}OUG!4h| zy2~1S=3}CcZg$m}9SYRXUGOezYV#XU10foWKJ!9uu-vqEBS8ER7 z7A{n48n>>}xu>~P-s~ffLDo z5i16BF)PggHCSEd1GjS0j>3KH$drx1cW3~}aGCR;hI5=lQE7%p zP4qF!vk=5%(za6U4`c1!O&>MqNs)?bfX2@*Bm(M2Ox1`=Ho1MTlIFiAXlNv7-LZzR z_Zh~(k2x>2-LSEieVra!xKy3woQSBJ3=nS^HtQFwXBgf<0tE9-z2aiVPHoJ5-y>MC z*(KOyP)(rd=Hk8S0ol?g#f8;Q7MfmDz&J~~Vq{?f+}L7!~S;e z=e-1TX&CF){jVYNBJ9eiLT5)8>(V=R-b=Z6i)U`GI44DXsK~UpMYN)-GJ$mEhpIm$E!>0>A`-SAfVIORaWX z{_wX@32m0==#JwF``5-qiQTY(O2fPQ?&m~_M3{*Kjl@9wV;kL6r%Iv{U=8;0>Mm%< z{MYI?OM`Q?^i)RFUw-dYa-MQQKFa7_H}2DY302~keB)CSM4Jet7kiY&jkz)*+bkfH z<2`6vDa3d4;=Rbn3Vl`p`<65K-}j(a8xKHM(w?(>>4?hho~Cy zCJ(~GSJ8LlZ`g^VO428yltvqdN91jMhxum&l3=eT?Br#D@+#5kkK6ZepQt6#xWC+P zY%DmHpX9B_4W#hkF31DKnj0JuR*|=tz_3@r%ldsPoN9sF8S1PRVHwEQ%wn47xC)J{ zX=8-;3L*tJYstG*lyTtSD;_(k!qycI%Jz4eK1)p@ z3WP#T6BB*GCz~ z<7BQ4!l9{HUVix7sDvDax*L79v6)=26t;Gea{aQ^Ep21U{m6l}%mK>e>f^$F)^nL* z+lW1T;X-^n&9VcB{#v1e)sjkPu}C~v$zhSMG{eEki^U%~hVuj>sT!CkgTOG2;Q5En zCg*Ue#$sln4Z$s2oj)Q2T3!R=IF7ZN!=QwFHdl^n3lywcwwW{ifiaQ6P}Sdvy_BSF z0{O(;xeUHDw*;G8xE7X%9HIi))lS%&*U14xUJP8?K@-7iOmn`5_X+^w3(8hxCbY7X z5-b>*HpNhD>n8a~Nlwk*T}+ro*3Zck4SN)1bF!=b!1xroHCkfcpIJ+p)BHajdkVKc zo>yBD)Y_{UXV6XNy<1|mKoh%$FXuf)&cMf}#yA|OeuO5FoYTC#3XMQqMMY#^Pp2d{ zqM!C)TOV1hT*Ly!p@yjf4OtZ!k{O1Ie8G(8bBz6X0BduPUX#3`k0vnlW8`eTiyMO` z=AqYqea*YDi@+4HEAX#_=~lYu!k1SjZ1*i@7mc$|S<3W}Br3zqUL|v!%XZ;Pky(z! z+{CF*YiZM_6cPE^p0h9t)n~_@(T^b)7v5~b)N#PmLVw||mh-+81HkL1msS8K)%|k8 zOF*W_Mo3aK_4?*{odb;0*KS$*%~9l6dk}*&*0;{>(jPtoCp8fdn^*H~h-%hUcitN+ zPrM$B|FSP;^Nu*+X~#@kKy}+x}d|zpVG5(gwCzmgnrY zqwvp>S&e_y%Ls*baZ=IK!p+T#@!`Vqf7R{qlmH5m8`yF|=Zn#JoP6=W>^2s+%6WMbS zP_9G2MPu%K!gM~aFLRO2r~=c3hI`=j`l;TeAUawn(9iPYUg)WTeBaI_HY;zE4$=#E zQub`K4aCbRWZBJ9>Z!@hO=WZqc1!!Ck5diCoeGVk>tL-Tn|sI~E)3OmcjnZvCSf(kaLEvIGVj86C1;|V<`K%#*lnmEapi>TjKF!srLO7 zqr^uR4&UdL@P&$ibkdC!Yj1nn5rO=sxkfCgk^&Fusz`DrJMoP*Hu$#f3&I#HZcy$9 z1k=Y~9PSAzCt5y4V!p1pq`RN0KP(iMUrOIg_IFk2Gd~*we=8xmLm5R^Toc2l6Iet4 z8ps;ztRE`|$73TpMW&F*U6xyfd03jjb6`L;{6kRS;>oDm?rz6vXtI2<*jPosQ$aF z?h#5b;06^4|M$Znerk3yHuis4`+9T} z!-4d$K5Jdw(}hZ`)&UoTNE)<(D3qYh7~7pR zAPFQ2&GckGkoEQP`>TL3nJcvI6cDj1O@;WLBuWim+d-(}6iPt{#f e$RZXhlZXu07kxp6XKAjaZ$X6r-fYZVc=|sVKc%?< diff --git a/src/Nethermind/Chains/settlus-mainnet-mainnet.json.zst b/src/Nethermind/Chains/settlus-mainnet-mainnet.json.zst index d9cc94883dbf4e054b83fe0c887f50aa1e88debd..19fde114fbebafecc189f90d15807667f9732e7d 100644 GIT binary patch delta 16283 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 delta 14686 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 diff --git a/src/Nethermind/Chains/settlus-mainnet-mainnet.json.zst b/src/Nethermind/Chains/settlus-mainnet-mainnet.json.zst index 19fde114fbebafecc189f90d15807667f9732e7d..d9cc94883dbf4e054b83fe0c887f50aa1e88debd 100644 GIT binary patch delta 14686 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 diff --git a/src/Nethermind/Chains/settlus-sepolia-sepolia.json.zst b/src/Nethermind/Chains/settlus-sepolia-sepolia.json.zst index 39a352cfee12cf3a3c26e15351e37ba24999a541..91748ecb4ed93874ee7543463dd312527bfd2acb 100644 GIT binary patch delta 18155 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 IJ5K?Q2aJl=IsgCw delta 19804 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 delta 7828 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 diff --git a/src/Nethermind/Chains/settlus-mainnet-mainnet.json.zst b/src/Nethermind/Chains/settlus-mainnet-mainnet.json.zst index d9cc94883dbf4e054b83fe0c887f50aa1e88debd..19fde114fbebafecc189f90d15807667f9732e7d 100644 GIT binary patch delta 16283 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 delta 14686 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