Skip to content
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f90ae86
perf(evm): optimize extcodesize handling
kamilchodola Feb 5, 2026
4eff00a
perf(evm): reuse extcode cache for extcodehash/copy
kamilchodola Feb 5, 2026
dc7833f
perf(evm): rename extcode cache and make size configurable
kamilchodola Feb 5, 2026
3325ebf
perf(evm): scope extcode cache per block
kamilchodola Feb 5, 2026
bd8098c
Merge branch 'kch/gas-benchmarks-bdn' into perf/extcodesize-cache-bdn
kamilchodola Feb 17, 2026
44c4e48
Revert "Merge branch 'kch/gas-benchmarks-bdn' into perf/extcodesize-c…
kamilchodola Feb 17, 2026
a348e35
port non-benchmark runtime changes from bdn merge
kamilchodola Feb 17, 2026
a0a0b19
perf(evm): integrate hash-aware code info path for extcode cache
kamilchodola Feb 17, 2026
70b3b01
merge master (#10563)
kamilchodola Feb 17, 2026
eb0f4cf
Merge branch 'perf/extcodesize-cache' of https://github.com/Nethermin…
kamilchodola Feb 18, 2026
a9439f5
Merge remote-tracking branch 'origin/master' into perf/extcodesize-cache
kamilchodola Feb 18, 2026
4563cdd
fix: refresh full extcode cache entries for existing keys
kamilchodola Feb 18, 2026
68ad590
refactor: unify extcode cache resolution and override lookups
kamilchodola Feb 18, 2026
afcd509
Update src/Nethermind/Nethermind.Evm/VirtualMachine.ExtCodeCache.cs
kamilchodola Feb 18, 2026
94d449f
Merge branch 'master' into perf/extcodesize-cache
kamilchodola Feb 19, 2026
3ea784d
Merge branch 'master' into perf/extcodesize-cache
kamilchodola Feb 26, 2026
8837549
Merge branch 'master' into perf/extcodesize-cache
kamilchodola Feb 26, 2026
277a6f7
refactor: address PR review comments
kamilchodola Feb 27, 2026
9e85a89
Merge branch 'master' into perf/extcodesize-cache
smartprogrammer93 Feb 27, 2026
127acf8
Merge remote-tracking branch 'origin/master' into codex/update-pr-10417
kamilchodola Apr 22, 2026
f37a4b2
Merge remote-tracking branch 'origin/master' into codex/update-pr-10417
kamilchodola Apr 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -491,7 +493,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);

Expand Down Expand Up @@ -944,7 +946,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);

Expand Down Expand Up @@ -1018,6 +1020,221 @@ 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 }));
}

[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);
}
}
Comment thread
kamilchodola marked this conversation as resolved.
Outdated

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)
.WithNonce(nonce)
.WithTo(to)
.WithGasLimit(100_000)
.SignedAndResolved(_ethereumEcdsa, sender, true)
.TestObject;

CallOutputTracer tracer = new();
_ = 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<byte> 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);
Expand Down
24 changes: 23 additions & 1 deletion src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,24 @@ 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;
}
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);
}
Comment thread
kamilchodola marked this conversation as resolved.
Outdated

public ValueHash256 GetExecutableCodeHash(Address address, IReleaseSpec spec)
{
return baseCodeInfoRepository.GetExecutableCodeHash(address, spec);
Expand All @@ -57,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<AddressAsKey, CodeInfo> originalPrecompile,
ConcurrentDictionary<PreBlockCaches.PrecompileCacheKey, Result<byte[]>> cache)
Expand Down
2 changes: 2 additions & 0 deletions src/Nethermind/Nethermind.Config/BlocksConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/Nethermind/Nethermind.Config/IBlocksConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 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; }

Expand Down
27 changes: 24 additions & 3 deletions src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -47,6 +47,28 @@ 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);
}
Comment thread
kamilchodola marked this conversation as resolved.
Outdated

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);
Expand Down Expand Up @@ -187,4 +209,3 @@ public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out CodeInfo? c
}
}
}

1 change: 1 addition & 0 deletions src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Nethermind.Evm;
public interface ICodeInfoRepository
{
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<byte> code, Address codeOwner, IReleaseSpec spec);
void SetDelegation(Address codeSource, Address authority, IReleaseSpec spec);
Expand Down
Loading
Loading