Skip to content

Commit

Permalink
gas fixes and bug
Browse files Browse the repository at this point in the history
  • Loading branch information
ak88 committed Aug 26, 2024
1 parent 6625445 commit 2f70584
Show file tree
Hide file tree
Showing 11 changed files with 76 additions and 55 deletions.
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(1,(worldStateToWarmUp as IPreBlockCaches)?.Caches.PrecompileCache);
CodeInfoRepository = new CodeInfoRepository(1, (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
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ private static RlpStream TupleRlpStreamWithNull()
Address? codeAddress = null;
Signature sig = new(new byte[64], 0);
int length =
+ Rlp.LengthOf(1)
+Rlp.LengthOf(1)
+ Rlp.LengthOf(codeAddress)
+ Rlp.LengthOf(0)
+ Rlp.LengthOf(sig.RecoveryId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
// SPDX-License-Identifier: LGPL-3.0-only

using Nethermind.Core.Specs;
using System;

namespace Nethermind.Core
{
public static class AccountStateProviderExtensions
{
public static bool HasCode(this IAccountStateProvider stateProvider, Address address) =>
stateProvider.TryGetAccount(address, out AccountStruct account) && account.HasCode;

public static bool IsInvalidContractSender(this IAccountStateProvider stateProvider, IReleaseSpec spec, Address address) =>
spec.IsEip3607Enabled && stateProvider.HasCode(address);
}
}
3 changes: 3 additions & 0 deletions src/Nethermind/Nethermind.Core/Eip7702Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ public static class Eip7702Constants
{
public const byte Magic = 0x05;
public static ReadOnlySpan<byte> DelegationHeader => [0xef, 0x01, 0x00];
public static bool IsDelegatedCode(ReadOnlySpan<byte> code) =>
code.Length == DelegationHeader.Length + Address.Size
&& DelegationHeader.SequenceEqual(code.Slice(0, DelegationHeader.Length));
}
8 changes: 4 additions & 4 deletions src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ public void InsertFromAuthorizations_AuthorityTupleIsCorrect_CodeIsInserted()
};
CodeInsertResult result = sut.InsertFromAuthorizations(Substitute.For<IWorldState>(), tuples, Substitute.For<IReleaseSpec>());

result.Addresses.Should().BeEquivalentTo([authority.Address]);
result.AccessedAddresses.Should().BeEquivalentTo([authority.Address]);
}

public static IEnumerable<object[]> AuthorizationCases()
{
yield return new object[]
{
CreateAuthorizationTuple(TestItem.PrivateKeyA, 1, TestItem.AddressB, 0),
true
true
};
yield return new object[]
{
Expand All @@ -73,7 +73,7 @@ public void InsertFromAuthorizations_MixOfCorrectAndWrongChainIdAndNonce_Inserts

CodeInsertResult result = sut.InsertFromAuthorizations(stateProvider, [tuple], Substitute.For<IReleaseSpec>());

Assert.That(stateProvider.HasCode(result.Addresses.First()), Is.EqualTo(shouldInsert));
Assert.That(stateProvider.HasCode(result.AccessedAddresses.First()), Is.EqualTo(shouldInsert));
}

[Test]
Expand Down Expand Up @@ -150,7 +150,7 @@ public void InsertFromAuthorizations_FourAuthorizationInTotalButTwoAreInvalid_Re
};
CodeInsertResult result = sut.InsertFromAuthorizations(Substitute.For<IWorldState>(), tuples, Substitute.For<IReleaseSpec>());

result.Addresses.Should().BeEquivalentTo([TestItem.AddressA, TestItem.AddressB, TestItem.AddressC, TestItem.AddressD]);
result.AccessedAddresses.Should().BeEquivalentTo([TestItem.AddressA, TestItem.AddressB, TestItem.AddressC, TestItem.AddressD]);
}

[Test]
Expand Down
76 changes: 43 additions & 33 deletions src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public CodeInfoRepository(ulong chainId, ConcurrentDictionary<PreBlockCaches.Pre
_ethereumEcdsa = new EthereumEcdsa(chainId);
_localPrecompiles = precompileCache is null
? _precompiles
: _precompiles.ToFrozenDictionary(kvp => kvp.Key, kvp => CreateCachedPrecompile(kvp, precompileCache));
: _precompiles.ToFrozenDictionary(kvp => kvp.Key, kvp => CreateCachedPrecompile(kvp, precompileCache));
}

public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec)
Expand All @@ -116,13 +116,12 @@ public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IR

CodeInfo cachedCodeInfo = InternalGetCachedCode(worldState, codeSource);

if (IsDelegatedCode(cachedCodeInfo))
if (Eip7702Constants.IsDelegatedCode(cachedCodeInfo.MachineCode.Span))
{
cachedCodeInfo = InternalGetCachedCode(worldState, ParseDelegatedAddress(cachedCodeInfo.MachineCode.Span));
}

return cachedCodeInfo;

}

private static CodeInfo InternalGetCachedCode(IWorldState worldState, Address codeSource)
Expand Down Expand Up @@ -188,15 +187,15 @@ public void InsertCode(IWorldState state, ReadOnlyMemory<byte> code, Address cod

/// <summary>
/// Insert code delegations from transaction authorization_list authorized by signature,
/// and return all authority addresses that was accessed.
/// and return all authority addresses that was accessed and amount of autorization refunds.
/// eip-7702
/// </summary>
public CodeInsertResult InsertFromAuthorizations(
IWorldState worldState,
AuthorizationTuple?[] authorizations,
IReleaseSpec spec)
{
List<Address> result = new();
HashSet<Address> accessedAddresses = new();
int refunds = 0;
//TODO optimize
foreach (AuthorizationTuple? authTuple in authorizations)
Expand All @@ -206,23 +205,20 @@ public CodeInsertResult InsertFromAuthorizations(
authTuple.Authority = authTuple.Authority ?? _ethereumEcdsa.RecoverAddress(authTuple);
string? error;

if (!result.Contains(authTuple.Authority))
result.Add(authTuple.Authority);

if (!IsValidForExecution(authTuple, worldState, _ethereumEcdsa.ChainId, spec, out error))
if (!IsValidForExecution(authTuple, worldState, _ethereumEcdsa.ChainId, accessedAddresses, out error))
continue;

if (!worldState.AccountExists(authTuple.Authority))
worldState.CreateAccount(authTuple.Authority, 0);
else
if (worldState.AccountExists(authTuple.Authority))
refunds++;
else
worldState.CreateAccount(authTuple.Authority, 0);

InsertAuthorizedCode(worldState, authTuple.CodeAddress, authTuple.Authority, spec);
InsertDelegationCode(worldState, authTuple.CodeAddress, authTuple.Authority, spec);
worldState.IncrementNonce(authTuple.Authority);
}
return new CodeInsertResult(result, refunds);
return new CodeInsertResult(accessedAddresses, refunds);

void InsertAuthorizedCode(IWorldState state, Address codeSource, Address authority, IReleaseSpec spec)
void InsertDelegationCode(IWorldState state, Address codeSource, Address authority, IReleaseSpec spec)
{
byte[] authorizedBuffer = new byte[Eip7702Constants.DelegationHeader.Length + Address.Size];
Eip7702Constants.DelegationHeader.CopyTo(authorizedBuffer);
Expand All @@ -234,14 +230,34 @@ void InsertAuthorizedCode(IWorldState state, Address codeSource, Address authori
}
}

/// <summary>
/// Retrieves code hash of delegation if delegated. Otherwise code hash of <paramref name="address"/>.
/// </summary>
/// <param name="worldState"></param>
/// <param name="address"></param>
public ValueHash256 GetCodeHash(IWorldState worldState, Address address)
{
ValueHash256 codeHash = worldState.GetCodeHash(address);
if (codeHash == Keccak.OfAnEmptyString.ValueHash256)
return Keccak.OfAnEmptyString.ValueHash256;

CodeInfo codeInfo = InternalGetCachedCode(worldState, address);
if (codeInfo.IsEmpty)
return Keccak.OfAnEmptyString.ValueHash256;

if (Eip7702Constants.IsDelegatedCode(codeInfo.MachineCode.Span))
return worldState.GetCodeHash(ParseDelegatedAddress(codeInfo.MachineCode.Span));
return codeHash;
}

/// <summary>
/// Determines if a <see cref="AuthorizationTuple"/> is wellformed according to spec.
/// </summary>
private bool IsValidForExecution(
AuthorizationTuple authorizationTuple,
IWorldState stateProvider,
ulong chainId,
IReleaseSpec spec,
HashSet<Address> accessedAddresses,
[NotNullWhen(false)] out string? error)
{
if (authorizationTuple.Authority is null)
Expand All @@ -254,6 +270,8 @@ private bool IsValidForExecution(
error = $"Chain id ({authorizationTuple.ChainId}) does not match.";
return false;
}
accessedAddresses.Add(authorizationTuple.Authority);

if (stateProvider.HasCode(authorizationTuple.Authority)
&& !HasDelegatedCode(stateProvider, authorizationTuple.Authority))
{
Expand All @@ -274,15 +292,7 @@ private bool IsValidForExecution(
private bool HasDelegatedCode(IWorldState worldState, Address source)
{
return
IsDelegatedCode(InternalGetCachedCode(worldState, source));
}

private static bool IsDelegatedCode(CodeInfo code)
{
return
code.MachineCode.Length == 23
&& Eip7702Constants.DelegationHeader.SequenceEqual(
code.MachineCode.Span.Slice(0, Eip7702Constants.DelegationHeader.Length));
Eip7702Constants.IsDelegatedCode(InternalGetCachedCode(worldState, source).MachineCode.Span);
}

private static Address ParseDelegatedAddress(ReadOnlySpan<byte> code)
Expand All @@ -297,16 +307,16 @@ private CodeInfo CreateCachedPrecompile(
ConcurrentDictionary<PreBlockCaches.PrecompileCacheKey, (ReadOnlyMemory<byte>, bool)> cache) =>
new(new CachedPrecompile(originalPrecompile.Key.Value, originalPrecompile.Value.Precompile!, cache));

public bool IsDelegation(IWorldState worldState, Address address,[NotNullWhen(true)] out Address? delegatedAddress)
public bool IsDelegation(IWorldState worldState, Address address, [NotNullWhen(true)] out Address? delegatedAddress)
{
CodeInfo codeInfo = InternalGetCachedCode(worldState, address);
if (IsDelegatedCode(codeInfo))
if (Eip7702Constants.IsDelegatedCode(codeInfo.MachineCode.Span))
{
delegatedAddress = ParseDelegatedAddress(codeInfo.MachineCode.Span);
return true;
}
delegatedAddress = null;
return false;
delegatedAddress = null;
return false;
}

private class CachedPrecompile(
Expand Down Expand Up @@ -337,16 +347,16 @@ private class CachedPrecompile(
}
public readonly struct CodeInsertResult
{
public CodeInsertResult(IEnumerable<Address> addresses, int refunds)
public CodeInsertResult(IEnumerable<Address> accessedAddresses, int refunds)
{
Addresses = addresses;
AccessedAddresses = accessedAddresses;
Refunds = refunds;
}
public CodeInsertResult()
{
Addresses = Array.Empty<Address>();
AccessedAddresses = Array.Empty<Address>();
}
public readonly IEnumerable<Address> Addresses;
public readonly IEnumerable<Address> AccessedAddresses;
public readonly int Refunds;
}

1 change: 1 addition & 0 deletions src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace Nethermind.Evm;
public interface ICodeInfoRepository
{
CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec);
ValueHash256 GetCodeHash(IWorldState worldState, Address address);
CodeInfo GetOrAdd(ValueHash256 codeHash, ReadOnlySpan<byte> initCode);
void InsertCode(IWorldState state, ReadOnlyMemory<byte> code, Address codeOwner, IReleaseSpec spec, bool isSystemEnv);
CodeInsertResult InsertFromAuthorizations(IWorldState worldState, AuthorizationTuple?[] authorizations, IReleaseSpec spec);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ protected virtual TransactionResult Execute(Transaction tx, in BlockExecutionCon

CodeInsertResult codeInsertResult = new();
if (spec.IsEip7702Enabled)
{
{
if (tx.HasAuthorizationList)
{
codeInsertResult = _codeInfoRepository.InsertFromAuthorizations(WorldState, tx.AuthorizationList, spec);
Expand Down Expand Up @@ -461,7 +461,7 @@ protected void ExecuteEvmCall(

using (EvmState state = new(unspentGas, env, executionType, true, snapshot, false))
{
WarmUp(tx, header, spec, env, state, codeInsertResult.Addresses);
WarmUp(tx, header, spec, env, state, codeInsertResult.AccessedAddresses);

substate = !tracer.IsTracingActions
? VirtualMachine.Run<NotTracing>(state, WorldState, tracer)
Expand Down Expand Up @@ -606,7 +606,7 @@ protected virtual long Refund(Transaction tx, BlockHeader header, IReleaseSpec s
? 0
: RefundHelper.CalculateClaimableRefund(spentGas,
substate.Refund + substate.DestroyList.Count * RefundOf.Destroy(spec.IsEip3529Enabled)
+ (GasCostOf.NewAccount-GasCostOf.PerAuthBaseCost) * codeInsertRefunds, spec);
+ (GasCostOf.NewAccount - GasCostOf.PerAuthBaseCost) * codeInsertRefunds, spec);

if (Logger.IsTrace)
Logger.Trace("Refunding unused gas of " + unspentGas + " and refund of " + refund);
Expand Down
21 changes: 11 additions & 10 deletions src/Nethermind/Nethermind.Evm/VirtualMachine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -481,14 +481,15 @@ private bool ChargeAccountAccessGas(ref long gasAvailable, EvmState vmState, Add
{
return true;
}
if (chargeForDelegation
&& vmState.Env.TxExecutionContext.CodeInfoRepository.IsDelegation(_state, address, out Address delegated))
bool notOutOfGas = ChargeAccountGas(ref gasAvailable, vmState, address, spec);
if (notOutOfGas
&& chargeForDelegation
&& vmState.Env.TxExecutionContext.CodeInfoRepository.IsDelegation(_state, address, out Address delegated)
)
{
address = delegated;
if (!ChargeAccountGas(ref gasAvailable, vmState, address, spec))
return false;
return ChargeAccountGas(ref gasAvailable, vmState, delegated, spec);
}
return ChargeAccountGas(ref gasAvailable, vmState, address, spec);
return notOutOfGas;

bool ChargeAccountGas(ref long gasAvailable, EvmState vmState, Address address, IReleaseSpec spec)
{
Expand Down Expand Up @@ -1264,7 +1265,7 @@ private CallResult ExecuteCode<TTracingInstructions, TTracingRefunds, TTracingSt

Address address = stack.PopAddress();
if (address is null) goto StackUnderflow;

if (!ChargeAccountAccessGas(ref gasAvailable, vmState, address, true, spec)) goto OutOfGas;

if (typeof(TTracingInstructions) != typeof(IsTracing) && programCounter < code.Length)
Expand Down Expand Up @@ -1891,15 +1892,15 @@ private CallResult ExecuteCode<TTracingInstructions, TTracingRefunds, TTracingSt

Address address = stack.PopAddress();
if (address is null) goto StackUnderflow;
if (!ChargeAccountAccessGas(ref gasAvailable, vmState, address,true, spec)) goto OutOfGas;
if (!ChargeAccountAccessGas(ref gasAvailable, vmState, address, true, spec)) goto OutOfGas;

if (!_state.AccountExists(address) || _state.IsDeadAccount(address))
{
stack.PushZero();
}
else
{
stack.PushBytes(_state.GetCodeHash(address).Bytes);
stack.PushBytes(env.TxExecutionContext.CodeInfoRepository.GetCodeHash(_state, address).BytesAsSpan);
}

break;
Expand Down Expand Up @@ -2123,7 +2124,7 @@ private EvmExceptionType InstructionCall<TTracingInstructions, TTracingRefunds>(
!UpdateMemoryCost(vmState, ref gasAvailable, in dataOffset, dataLength) ||
!UpdateMemoryCost(vmState, ref gasAvailable, in outputOffset, outputLength) ||
!UpdateGas(gasExtra, ref gasAvailable)) return EvmExceptionType.OutOfGas;

CodeInfo codeInfo = vmState.Env.TxExecutionContext.CodeInfoRepository.GetCachedCodeInfo(_state, codeSource, spec);
codeInfo.AnalyseInBackgroundIfRequired();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,9 @@ public bool IsDelegation(IWorldState worldState, Address address, [NotNullWhen(t
{
return codeInfoRepository.IsDelegation(worldState, address, out delegatedAddress);
}

public ValueHash256 GetCodeHash(IWorldState worldState, Address address)
{
return codeInfoRepository.GetCodeHash(worldState, address);
}
}
3 changes: 3 additions & 0 deletions src/Nethermind/Nethermind.State/IWorldStateExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public static void InsertCode(this IWorldState worldState, Address address, Read
worldState.InsertCode(address, codeHash, code, spec, isGenesis);
}

public static bool IsInvalidContractSender(this IWorldState stateProvider, IReleaseSpec spec, Address address) =>
spec.IsEip3607Enabled && stateProvider.HasCode(address) && !Eip7702Constants.IsDelegatedCode(GetCode(stateProvider, address));

public static string DumpState(this IWorldState stateProvider)
{
TreeDumper dumper = new();
Expand Down

0 comments on commit 2f70584

Please sign in to comment.