Skip to content

Commit

Permalink
Optimization/cache precompiles (#7106)
Browse files Browse the repository at this point in the history
Co-authored-by: Oleg Jakushkin <oleg.jakushkin@gmail.com>
Co-authored-by: Ruben Buniatyan <rubo@users.noreply.github.com>
Co-authored-by: Kamil Chodoła <kamil@nethermind.io>
Co-authored-by: Kamil Chodoła <43241881+kamilchodola@users.noreply.github.com>
Co-authored-by: Ahmad Bitar <33181301+smartprogrammer93@users.noreply.github.com>
Co-authored-by: Ben Adams <thundercat@illyriad.co.uk>
  • Loading branch information
7 people authored Jun 12, 2024
1 parent c0072bf commit 2e0e5c1
Show file tree
Hide file tree
Showing 39 changed files with 309 additions and 410 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Nethermind.Blockchain.Contracts.Json;
using Nethermind.Consensus;
using Nethermind.Core;
using Nethermind.Core.Collections;
using Nethermind.Core.Crypto;
using Nethermind.Core.Extensions;
using Nethermind.Core.Test.Builders;
Expand Down Expand Up @@ -96,9 +97,10 @@ public Address GetAccountAddress(TestRpcBlockchain chain, Address entryPointAddr
.SignedAndResolved(TestItem.PrivateKeyA)
.TestObject;

Address accountAddress = new(Bytes.FromHexString(chain.EthRpcModule.eth_call(new TransactionForRpc(getAccountAddressTransaction)).Data).SliceWithZeroPaddingEmptyOnError(12, 20));
using ArrayPoolList<byte> data = Bytes.FromHexString(chain.EthRpcModule.eth_call(new TransactionForRpc(getAccountAddressTransaction)).Data)
.SliceWithZeroPaddingEmptyOnError(12, 20);

return accountAddress;
return new(data.ToArray());
}

public byte[] GetWalletConstructor(Address entryPointAddress)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public PerfStateTest(ITestSourceLoader testsSource)
public IEnumerable<EthereumTestResult> RunTests()
{
List<EthereumTestResult> results = new List<EthereumTestResult>();
Console.WriteLine($"RUNNING tests");
Console.WriteLine("RUNNING tests");
Stopwatch stopwatch = new Stopwatch();
IEnumerable<GeneralStateTest> tests = (IEnumerable<GeneralStateTest>)_testsSource.LoadTests();
bool isNewLine = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ public AuRaBlockProcessor(
IAuRaValidator? auRaValidator,
ITxFilter? txFilter = null,
AuRaContractGasLimitOverride? gasLimitOverride = null,
ContractRewriter? contractRewriter = null)
ContractRewriter? contractRewriter = null,
IBlockCachePreWarmer? preWarmer = null)
: base(
specProvider,
blockValidator,
Expand All @@ -55,7 +56,8 @@ public AuRaBlockProcessor(
receiptStorage,
new BlockhashStore(blockTree, specProvider, stateProvider),
logManager,
withdrawalProcessor)
withdrawalProcessor,
preWarmer: preWarmer)
{
_specProvider = specProvider;
_blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ protected override async Task InitBlockchain()
}
}

protected override BlockProcessor CreateBlockProcessor()
protected override BlockProcessor CreateBlockProcessor(BlockCachePreWarmer? preWarmer)
{
if (_api.SpecProvider is null) throw new StepDependencyException(nameof(_api.SpecProvider));
if (_api.ChainHeadStateProvider is null) throw new StepDependencyException(nameof(_api.ChainHeadStateProvider));
Expand All @@ -89,10 +89,10 @@ protected override BlockProcessor CreateBlockProcessor()
_api,
new ServiceTxFilter(_api.SpecProvider));

return NewAuraBlockProcessor(auRaTxFilter);
return NewAuraBlockProcessor(auRaTxFilter, preWarmer);
}

protected virtual AuRaBlockProcessor NewAuraBlockProcessor(ITxFilter txFilter)
protected virtual AuRaBlockProcessor NewAuraBlockProcessor(ITxFilter txFilter, BlockCachePreWarmer? preWarmer)
{
IDictionary<long, IDictionary<Address, byte[]>> rewriteBytecode = _api.ChainSpec.AuRa.RewriteBytecode;
ContractRewriter? contractRewriter = rewriteBytecode?.Count > 0 ? new ContractRewriter(rewriteBytecode) : null;
Expand All @@ -112,7 +112,8 @@ protected virtual AuRaBlockProcessor NewAuraBlockProcessor(ITxFilter txFilter)
CreateAuRaValidator(),
txFilter,
GetGasLimitCalculator(),
contractRewriter
contractRewriter,
preWarmer: preWarmer
);
}

Expand Down
11 changes: 7 additions & 4 deletions src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@ the previous head state.*/
}

using CancellationTokenSource cancellationTokenSource = new();
Task? preWarmTask = suggestedBlock.Transactions.Length < 3 ?
null :
_preWarmer?.PreWarmCaches(suggestedBlock, preBlockStateRoot!, cancellationTokenSource.Token);
Task? preWarmTask = suggestedBlock.Transactions.Length < 3
? null
: _preWarmer?.PreWarmCaches(suggestedBlock, preBlockStateRoot!, cancellationTokenSource.Token);
(Block processedBlock, TxReceipt[] receipts) = ProcessOne(suggestedBlock, options, blockTracer);
cancellationTokenSource.Cancel();
preWarmTask?.GetAwaiter().GetResult();
Expand Down Expand Up @@ -160,9 +160,12 @@ the previous head state.*/
{
_logger.Trace($"Encountered exception {ex} while processing blocks.");
RestoreBranch(previousBranchStateRoot);
_preWarmer?.ClearCaches();
throw;
}
finally
{
_preWarmer?.ClearCaches();
}
}

public event EventHandler<BlocksProcessingEventArgs>? BlocksProcessing;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public ReadOnlyTxProcessingEnv(
IWorldState? worldStateToWarmUp = null
) : base(worldStateManager, readOnlyBlockTree, specProvider, logManager, worldStateToWarmUp)
{
CodeInfoRepository = new CodeInfoRepository();
CodeInfoRepository = new CodeInfoRepository((worldStateToWarmUp as IPreBlockCaches)?.Caches.PrecompileCache);
Machine = new VirtualMachine(BlockhashProvider, specProvider, CodeInfoRepository, logManager);
BlockTree = readOnlyBlockTree ?? throw new ArgumentNullException(nameof(readOnlyBlockTree));
BlockhashProvider = new BlockhashProvider(BlockTree, specProvider, StateProvider, logManager);
Expand Down
21 changes: 10 additions & 11 deletions src/Nethermind/Nethermind.Core/Extensions/ByteArrayExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using Nethermind.Core.Collections;

namespace Nethermind.Core.Extensions
{
Expand Down Expand Up @@ -34,39 +35,37 @@ public static byte[] Slice(this byte[] bytes, int startIndex, int length)
{
if (length == 1)
{
return new[] { bytes[startIndex] };
return [bytes[startIndex]];
}

byte[] slice = new byte[length];
Buffer.BlockCopy(bytes, startIndex, slice, 0, length);
return slice;
}

public static byte[] SliceWithZeroPaddingEmptyOnError(this byte[] bytes, int startIndex, int length)
public static ArrayPoolList<byte> SliceWithZeroPaddingEmptyOnError(this byte[] bytes, int startIndex, int length)
{
int copiedFragmentLength = Math.Min(bytes.Length - startIndex, length);
if (copiedFragmentLength <= 0)
{
return Array.Empty<byte>();
return ArrayPoolList<byte>.Empty();
}

byte[] slice = new byte[length];

Buffer.BlockCopy(bytes, startIndex, slice, 0, copiedFragmentLength);
ArrayPoolList<byte> slice = new(length, length);
bytes.Slice(startIndex, copiedFragmentLength).CopyTo(slice.AsSpan().Slice(0, copiedFragmentLength));
return slice;
}

public static byte[] SliceWithZeroPaddingEmptyOnError(this ReadOnlySpan<byte> bytes, int startIndex, int length)
public static ArrayPoolList<byte> SliceWithZeroPaddingEmptyOnError(this ReadOnlySpan<byte> bytes, int startIndex, int length)
{
int copiedFragmentLength = Math.Min(bytes.Length - startIndex, length);
if (copiedFragmentLength <= 0)
{
return Array.Empty<byte>();
return ArrayPoolList<byte>.Empty();
}

byte[] slice = new byte[length];

bytes.Slice(startIndex, copiedFragmentLength).CopyTo(slice.AsSpan(0, copiedFragmentLength));
ArrayPoolList<byte> slice = new(length, length);
bytes.Slice(startIndex, copiedFragmentLength).CopyTo(slice.AsSpan().Slice(0, copiedFragmentLength));
return slice;
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/Nethermind/Nethermind.Core/Extensions/Bytes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,12 @@ public static int LeadingZerosCount(this Span<byte> bytes, int startIndex = 0)
return nonZeroIndex < 0 ? bytes.Length - startIndex : nonZeroIndex;
}

public static int LeadingZerosCount(this ReadOnlySpan<byte> bytes, int startIndex = 0)
{
int nonZeroIndex = bytes[startIndex..].IndexOfAnyExcept((byte)0);
return nonZeroIndex < 0 ? bytes.Length - startIndex : nonZeroIndex;
}

public static int TrailingZerosCount(this byte[] bytes)
{
int lastIndex = bytes.AsSpan().LastIndexOfAnyExcept((byte)0);
Expand Down
42 changes: 41 additions & 1 deletion src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Collections.Concurrent;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
Expand Down Expand Up @@ -61,6 +62,7 @@ public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out CodeInfo? c

private static readonly FrozenDictionary<AddressAsKey, CodeInfo> _precompiles = InitializePrecompiledContracts();
private static readonly CodeLruCache _codeCache = new();
private readonly FrozenDictionary<AddressAsKey, CodeInfo> _localPrecompiles;

private static FrozenDictionary<AddressAsKey, CodeInfo> InitializePrecompiledContracts()
{
Expand Down Expand Up @@ -92,11 +94,18 @@ private static FrozenDictionary<AddressAsKey, CodeInfo> InitializePrecompiledCon
}.ToFrozenDictionary();
}

public CodeInfoRepository(ConcurrentDictionary<PreBlockCaches.PrecompileCacheKey, (ReadOnlyMemory<byte>, bool)>? precompileCache = null)
{
_localPrecompiles = precompileCache is null
? _precompiles
: _precompiles.ToFrozenDictionary(kvp => kvp.Key, kvp => CreateCachedPrecompile(kvp, precompileCache));
}

public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec)
{
if (codeSource.IsPrecompile(vmSpec))
{
return _precompiles[codeSource];
return _localPrecompiles[codeSource];
}

CodeInfo? cachedCodeInfo = null;
Expand Down Expand Up @@ -158,4 +167,35 @@ public void InsertCode(IWorldState state, ReadOnlyMemory<byte> code, Address cod
state.InsertCode(codeOwner, codeHash, code, spec);
_codeCache.Set(codeHash, codeInfo);
}

private CodeInfo CreateCachedPrecompile(
in KeyValuePair<AddressAsKey, CodeInfo> originalPrecompile,
ConcurrentDictionary<PreBlockCaches.PrecompileCacheKey, (ReadOnlyMemory<byte>, bool)> cache) =>
new(new CachedPrecompile(originalPrecompile.Key.Value, originalPrecompile.Value.Precompile!, cache));

private class CachedPrecompile(
Address address,
IPrecompile precompile,
ConcurrentDictionary<PreBlockCaches.PrecompileCacheKey, (ReadOnlyMemory<byte>, bool)> cache) : IPrecompile
{
public static Address Address => Address.Zero;

public long BaseGasCost(IReleaseSpec releaseSpec) => precompile.BaseGasCost(releaseSpec);

public long DataGasCost(in ReadOnlyMemory<byte> inputData, IReleaseSpec releaseSpec) => precompile.DataGasCost(inputData, releaseSpec);

public (ReadOnlyMemory<byte>, bool) Run(in ReadOnlyMemory<byte> inputData, IReleaseSpec releaseSpec)
{
PreBlockCaches.PrecompileCacheKey key = new(address, inputData);
if (!cache.TryGetValue(key, out (ReadOnlyMemory<byte>, bool) result))
{
result = precompile.Run(inputData, releaseSpec);
// we need to rebuild the key with data copy as the data can be changed by VM processing
key = new PreBlockCaches.PrecompileCacheKey(address, inputData.ToArray());
cache.TryAdd(key, result);
}

return result;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ public long DataGasCost(in ReadOnlyMemory<byte> inputData, IReleaseSpec releaseS
{
if (inputData.Length != RequiredInputLength)
{
return (Array.Empty<byte>(), false);
return IPrecompile.Failure;
}

byte finalByte = inputData.Span[212];
if (finalByte != 0 && finalByte != 1)
{
return (Array.Empty<byte>(), false);
return IPrecompile.Failure;
}

byte[] result = new byte[64];
Expand Down
25 changes: 4 additions & 21 deletions src/Nethermind/Nethermind.Evm/Precompiles/Bls/G1AddPrecompile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,37 +21,20 @@ private G1AddPrecompile()

public static Address Address { get; } = Address.FromNumber(0x0b);

public long BaseGasCost(IReleaseSpec releaseSpec)
{
return 500L;
}
public long BaseGasCost(IReleaseSpec releaseSpec) => 500L;

public long DataGasCost(in ReadOnlyMemory<byte> inputData, IReleaseSpec releaseSpec)
{
return 0L;
}
public long DataGasCost(in ReadOnlyMemory<byte> inputData, IReleaseSpec releaseSpec) => 0L;

public (ReadOnlyMemory<byte>, bool) Run(in ReadOnlyMemory<byte> inputData, IReleaseSpec releaseSpec)
{
const int expectedInputLength = 4 * BlsParams.LenFp;
if (inputData.Length != expectedInputLength)
{
return (Array.Empty<byte>(), false);
return IPrecompile.Failure;
}

(byte[], bool) result;

Span<byte> output = stackalloc byte[2 * BlsParams.LenFp];
bool success = Pairings.BlsG1Add(inputData.Span, output);
if (success)
{
result = (output.ToArray(), true);
}
else
{
result = (Array.Empty<byte>(), false);
}

return result;
return success ? (output.ToArray(), true) : IPrecompile.Failure;
}
}
27 changes: 6 additions & 21 deletions src/Nethermind/Nethermind.Evm/Precompiles/Bls/G1MulPrecompile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Runtime.CompilerServices;
using Nethermind.Core;
using Nethermind.Core.Specs;
using Nethermind.Crypto;
Expand All @@ -21,39 +22,23 @@ private G1MulPrecompile()

public static Address Address { get; } = Address.FromNumber(0x0c);

public long BaseGasCost(IReleaseSpec releaseSpec)
{
return 12000L;
}
public long BaseGasCost(IReleaseSpec releaseSpec) => 12000L;

public long DataGasCost(in ReadOnlyMemory<byte> inputData, IReleaseSpec releaseSpec)
{
return 0L;
}
public long DataGasCost(in ReadOnlyMemory<byte> inputData, IReleaseSpec releaseSpec) => 0L;

[SkipLocalsInit]
public (ReadOnlyMemory<byte>, bool) Run(in ReadOnlyMemory<byte> inputData, IReleaseSpec releaseSpec)
{
const int expectedInputLength = 2 * BlsParams.LenFp + BlsParams.LenFr;
if (inputData.Length != expectedInputLength)
{
return (Array.Empty<byte>(), false);
return IPrecompile.Failure;
}

(byte[], bool) result;

Span<byte> output = stackalloc byte[2 * BlsParams.LenFp];
bool success = SubgroupChecks.G1IsInSubGroup(inputData.Span[..(2 * BlsParams.LenFp)])
&& Pairings.BlsG1Mul(inputData.Span, output);

if (success)
{
result = (output.ToArray(), true);
}
else
{
result = (Array.Empty<byte>(), false);
}

return result;
return success ? (output.ToArray(), true) : IPrecompile.Failure;
}
}
Loading

0 comments on commit 2e0e5c1

Please sign in to comment.