From 51382b5304615354998dafb1f3ed4627c64a88f8 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 25 May 2024 01:53:10 +0100 Subject: [PATCH 1/4] Fast Call to Non contract addresses --- .../Nethermind.Evm/CodeAnalysis/CodeInfo.cs | 1 + .../Nethermind.Evm/VirtualMachine.cs | 31 ++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs index 18c04dc057f..e1bb762bceb 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs @@ -29,6 +29,7 @@ public CodeInfo(ReadOnlyMemory code) } public bool IsPrecompile => Precompile is not null; + public bool IsEmpty => ReferenceEquals(_analyzer, _emptyAnalyzer) && !IsPrecompile; public CodeInfo(IPrecompile precompile) { diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index 1fcd07bdc3b..acabc1327f0 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -128,7 +128,7 @@ private static FrozenDictionary InitializePrecompiledCon }.ToFrozenDictionary(); } - internal readonly ref struct CallResult + internal readonly struct CallResult { public static CallResult InvalidSubroutineEntry => new(EvmExceptionType.InvalidSubroutineEntry); public static CallResult InvalidSubroutineReturn => new(EvmExceptionType.InvalidSubroutineReturn); @@ -141,6 +141,7 @@ internal readonly ref struct CallResult public static CallResult StackUnderflowException => new(EvmExceptionType.StackUnderflow); // TODO: use these to avoid CALL POP attacks public static CallResult InvalidCodeException => new(EvmExceptionType.InvalidCode); public static CallResult Empty => new(default, null); + public static object BoxedEmpty { get; } = Empty; public CallResult(EvmState stateToExecute) { @@ -1869,6 +1870,12 @@ private CallResult ExecuteCode( return EvmExceptionType.None; } - ReadOnlyMemory callData = vmState.Memory.Load(in dataOffset, dataLength); - - Snapshot snapshot = _worldState.TakeSnapshot(); _state.SubtractFromBalance(caller, transferValue, spec); + if (codeInfo.IsEmpty && typeof(TTracingInstructions) != typeof(IsTracing) && !_txTracer.IsTracingActions) + { + UpdateGasUp(gasLimitUl, ref gasAvailable); + if (!_state.AccountExists(target)) + { + _state.CreateAccount(target, transferValue); + } + else + { + _state.AddToBalance(target, transferValue, spec); + } + Metrics.EmptyCalls++; + + returnData = CallResult.BoxedEmpty; + return EvmExceptionType.None; + } + + Snapshot snapshot = _worldState.TakeSnapshot(); + ReadOnlyMemory callData = vmState.Memory.Load(in dataOffset, dataLength); ExecutionEnvironment callEnv = new ( txExecutionContext: in env.TxExecutionContext, From 93110574b1c6b8ace74cce7bffbb6c4954bc4214 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 25 May 2024 03:34:58 +0100 Subject: [PATCH 2/4] Snapshot in correct place --- src/Nethermind/Nethermind.Evm/VirtualMachine.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index acabc1327f0..9ac8d63f5f7 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -128,7 +128,7 @@ private static FrozenDictionary InitializePrecompiledCon }.ToFrozenDictionary(); } - internal readonly struct CallResult + internal readonly ref struct CallResult { public static CallResult InvalidSubroutineEntry => new(EvmExceptionType.InvalidSubroutineEntry); public static CallResult InvalidSubroutineReturn => new(EvmExceptionType.InvalidSubroutineReturn); @@ -141,7 +141,7 @@ internal readonly struct CallResult public static CallResult StackUnderflowException => new(EvmExceptionType.StackUnderflow); // TODO: use these to avoid CALL POP attacks public static CallResult InvalidCodeException => new(EvmExceptionType.InvalidCode); public static CallResult Empty => new(default, null); - public static object BoxedEmpty { get; } = Empty; + public static object BoxedEmpty { get; } = new object(); public CallResult(EvmState stateToExecute) { @@ -2263,6 +2263,7 @@ private EvmExceptionType InstructionCall( return EvmExceptionType.None; } + Snapshot snapshot = _worldState.TakeSnapshot(); _state.SubtractFromBalance(caller, transferValue, spec); if (codeInfo.IsEmpty && typeof(TTracingInstructions) != typeof(IsTracing) && !_txTracer.IsTracingActions) @@ -2282,7 +2283,6 @@ private EvmExceptionType InstructionCall( return EvmExceptionType.None; } - Snapshot snapshot = _worldState.TakeSnapshot(); ReadOnlyMemory callData = vmState.Memory.Load(in dataOffset, dataLength); ExecutionEnvironment callEnv = new ( From a67574946548ea74d455bdf5400d01dd30326773 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 25 May 2024 03:46:14 +0100 Subject: [PATCH 3/4] Add comments --- src/Nethermind/Nethermind.Evm/VirtualMachine.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index 9ac8d63f5f7..dea1550996c 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -1872,6 +1872,7 @@ private CallResult ExecuteCode( if (codeInfo.IsEmpty && typeof(TTracingInstructions) != typeof(IsTracing) && !_txTracer.IsTracingActions) { + // Non contract call, no need to construct call frame can just credit balance and return gas UpdateGasUp(gasLimitUl, ref gasAvailable); if (!_state.AccountExists(target)) { From c382180df845c579599af4a5863cc520a92b7a7f Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 25 May 2024 04:28:28 +0100 Subject: [PATCH 4/4] Clean up --- .../Nethermind.Evm/VirtualMachine.cs | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index dea1550996c..d638965d2cf 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -1872,9 +1872,7 @@ private CallResult ExecuteCode( if (typeof(TLogger) == typeof(IsTracing)) { - _logger.Trace($"caller {caller}"); - _logger.Trace($"code source {codeSource}"); - _logger.Trace($"target {target}"); - _logger.Trace($"value {callValue}"); - _logger.Trace($"transfer value {transferValue}"); + TraceCallDetails(codeSource, ref callValue, ref transferValue, caller, target); } long gasExtra = 0L; @@ -2270,19 +2264,10 @@ private EvmExceptionType InstructionCall( if (codeInfo.IsEmpty && typeof(TTracingInstructions) != typeof(IsTracing) && !_txTracer.IsTracingActions) { // Non contract call, no need to construct call frame can just credit balance and return gas + _returnDataBuffer = default; + stack.PushBytes(StatusCode.SuccessBytes.Span); UpdateGasUp(gasLimitUl, ref gasAvailable); - if (!_state.AccountExists(target)) - { - _state.CreateAccount(target, transferValue); - } - else - { - _state.AddToBalance(target, transferValue, spec); - } - Metrics.EmptyCalls++; - - returnData = CallResult.BoxedEmpty; - return EvmExceptionType.None; + return FastCall(spec, out returnData, in transferValue, target); } ReadOnlyMemory callData = vmState.Memory.Load(in dataOffset, dataLength); @@ -2321,6 +2306,32 @@ private EvmExceptionType InstructionCall( isCreateOnPreExistingAccount: false); return EvmExceptionType.None; + + EvmExceptionType FastCall(IReleaseSpec spec, out object returnData, in UInt256 transferValue, Address target) + { + if (!_state.AccountExists(target)) + { + _state.CreateAccount(target, transferValue); + } + else + { + _state.AddToBalance(target, transferValue, spec); + } + Metrics.EmptyCalls++; + + returnData = CallResult.BoxedEmpty; + return EvmExceptionType.None; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + void TraceCallDetails(Address codeSource, ref UInt256 callValue, ref UInt256 transferValue, Address caller, Address target) + { + _logger.Trace($"caller {caller}"); + _logger.Trace($"code source {codeSource}"); + _logger.Trace($"target {target}"); + _logger.Trace($"value {callValue}"); + _logger.Trace($"transfer value {transferValue}"); + } } [SkipLocalsInit]