Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hunting allocations 3 - introduce AccountStruct #6605

Merged
merged 20 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -222,7 +222,7 @@ public async Task Should_execute_well_formed_op_successfully_if_codehash_not_cha
chain.SendUserOperation(entryPointAddress[0], op);
if (changeCodeHash)
{
chain.State.InsertCode(walletAddress[0]!, Bytes.Concat(chain.State.GetCode(walletAddress[0]!), 0x00), chain.SpecProvider.GenesisSpec);
chain.State.InsertCode(walletAddress[0]!, Bytes.Concat(chain.State.GetCode(walletAddress[0]!)!, 0x00), chain.SpecProvider.GenesisSpec);
chain.State.Commit(chain.SpecProvider.GenesisSpec);
chain.State.RecalculateStateRoot();
chain.State.CommitTree(chain.BlockTree.Head!.Number);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ public void Should_make_sure_external_contract_extcodehashes_stays_same_after_si
.Op(Instruction.STOP)
.Done;

Hash256 initialCodeHash = TestState.GetCodeHash(externalContractAddress);
(UserOperationTxTracer tracer, _, _) = ExecuteAndTraceAccessCall(SenderRecipientAndMiner.Default, code, whitelisted);
ValueHash256 initialCodeHash = TestState.GetCodeHash(externalContractAddress);
ExecuteAndTraceAccessCall(SenderRecipientAndMiner.Default, code, whitelisted);
if (shouldMatch)
{
TestState.GetCodeHash(externalContractAddress).Should().Be(initialCodeHash);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public ResultWrapper<Hash256> Simulate(UserOperation userOperation,
foreach (KeyValuePair<Address, Hash256> kv in userOperation.AddressesToCodeHashes)
{
(Address address, Hash256 expectedCodeHash) = kv;
Hash256 codeHash = _stateProvider.GetCodeHash(address);
ValueHash256 codeHash = _stateProvider.GetCodeHash(address);
if (codeHash != expectedCodeHash)
{
return ResultWrapper<Hash256>.Fail($"codehash of address {address} changed since initial simulation");
Expand Down Expand Up @@ -148,7 +148,7 @@ private UserOperationSimulationResult SimulateValidation(
IDictionary<Address, Hash256> addressesToCodeHashes = new Dictionary<Address, Hash256>();
foreach (Address accessedAddress in txTracer.AccessedAddresses)
{
addressesToCodeHashes[accessedAddress] = _stateProvider.GetCodeHash(accessedAddress);
addressesToCodeHashes[accessedAddress] = new Hash256(_stateProvider.GetCodeHash(accessedAddress));
}

return new UserOperationSimulationResult()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void transactions_are_addable_to_block_after_sealing()
Address nodeAddress = TestItem.AddressA;

UInt256 expectedNonce = 10;
stateReader.GetAccount(blockHeader.StateRoot, nodeAddress).Returns(Account.TotallyEmpty.WithChangedNonce(expectedNonce));
stateReader.GetAccount(blockHeader.StateRoot, nodeAddress).Returns(new AccountStruct(expectedNonce, UInt256.Zero));

ulong expectedTimeStamp = 100;
timestamper.UnixTime.Returns(UnixTime.FromSeconds(expectedTimeStamp));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,26 @@ public SpecificBlockReadOnlyStateProvider(IStateReader stateReader, Hash256? sta

public virtual Hash256 StateRoot { get; }

public Account GetAccount(Address address) => _stateReader.GetAccount(StateRoot, address) ?? Account.TotallyEmpty;
public AccountStruct GetAccount(Address address) => _stateReader.GetAccount(StateRoot, address) ?? default;

public bool IsContract(Address address) => GetAccount(address).IsContract;

public UInt256 GetNonce(Address address) => GetAccount(address).Nonce;

public UInt256 GetBalance(Address address) => GetAccount(address).Balance;

public Hash256? GetStorageRoot(Address address) => GetAccount(address).StorageRoot;
public ValueHash256 GetStorageRoot(Address address) => GetAccount(address).StorageRoot;

public byte[] GetCode(Address address)
public byte[]? GetCode(Address address)
{
Account account = GetAccount(address);
if (!account.HasCode)
{
return Array.Empty<byte>();
}

return _stateReader.GetCode(account.CodeHash);
AccountStruct account = GetAccount(address);
return !account.HasCode ? Array.Empty<byte>() : _stateReader.GetCode(account.CodeHash);
}

public byte[] GetCode(Hash256 codeHash) => _stateReader.GetCode(codeHash);
public byte[]? GetCode(Hash256 codeHash) => _stateReader.GetCode(codeHash);
public byte[]? GetCode(ValueHash256 codeHash) => _stateReader.GetCode(codeHash);

public Hash256 GetCodeHash(Address address)
{
Account account = GetAccount(address);
return account.CodeHash;
}
public ValueHash256 GetCodeHash(Address address) => GetAccount(address).CodeHash;

public void Accept(ITreeVisitor visitor, Hash256 stateRoot, VisitingOptions? visitingOptions)
{
Expand All @@ -59,15 +51,8 @@ public void Accept(ITreeVisitor visitor, Hash256 stateRoot, VisitingOptions? vis
public bool AccountExists(Address address) => _stateReader.GetAccount(StateRoot, address) is not null;

public bool IsEmptyAccount(Address address) => GetAccount(address).IsEmpty;
public bool HasStateForRoot(Hash256 stateRoot)
{
return _stateReader.HasStateForRoot(stateRoot);
}
public bool HasStateForRoot(Hash256 stateRoot) => _stateReader.HasStateForRoot(stateRoot);

public bool IsDeadAccount(Address address)
{
Account account = GetAccount(address);
return account.IsEmpty;
}
public bool IsDeadAccount(Address address) => GetAccount(address).IsEmpty;
}
}
41 changes: 40 additions & 1 deletion src/Nethermind/Nethermind.Core/Account.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Nethermind.Core
{
public class Account
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah still exist?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are places where we need to put it on heap, not sure if worth paying for boxing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But will try to move some usages to AccountStruct in next PR.

{
public static Account TotallyEmpty = new();
public static readonly Account TotallyEmpty = new();

private readonly Hash256? _codeHash;
private readonly Hash256? _storageRoot;
Expand Down Expand Up @@ -100,5 +100,44 @@ public Account WithChangedCodeHash(Hash256 newCodeHash)
{
return new(newCodeHash, this);
}

public AccountStruct ToStruct() => new(Nonce, Balance, StorageRoot, CodeHash);
}

public readonly struct AccountStruct
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a 128 bytes struct (0.125 kB); want to make sure are passing by in etc everywhere otherwise big copies (if low GC)

Copy link
Contributor

@Scooletz Scooletz Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How this comment ended? I see that in lot of places this is returned, no TryGet or ref semantics there? I'm asking as I'm applying these changes to Paprika and it looks that we're potentially copying to much here?

@benaadams @LukaszRozmej

{
private readonly ValueHash256 _codeHash = Keccak.OfAnEmptyString.ValueHash256;
private readonly ValueHash256 _storageRoot = Keccak.EmptyTreeHash.ValueHash256;

public AccountStruct(in UInt256 nonce, in UInt256 balance, in ValueHash256 storageRoot, in ValueHash256 codeHash)
{
_codeHash = codeHash;
_storageRoot = storageRoot;
Nonce = nonce;
Balance = balance;
}

public AccountStruct(in UInt256 nonce, in UInt256 balance)
{
Nonce = nonce;
Balance = balance;
}

public AccountStruct(in UInt256 balance)
{
Balance = balance;
}

public bool HasCode => _codeHash != Keccak.OfAnEmptyString.ValueHash256;

public bool HasStorage => _storageRoot != Keccak.EmptyTreeHash.ValueHash256;

public UInt256 Nonce { get; }
public UInt256 Balance { get; }
public ValueHash256 StorageRoot => _storageRoot;
public ValueHash256 CodeHash => _codeHash;
public bool IsTotallyEmpty => _storageRoot == Keccak.EmptyTreeHash.ValueHash256 && IsEmpty;
public bool IsEmpty => _codeHash == Keccak.OfAnEmptyString.ValueHash256 && Balance.IsZero && Nonce.IsZero;
public bool IsContract => _codeHash != Keccak.OfAnEmptyString.ValueHash256;
}
}
1 change: 1 addition & 0 deletions src/Nethermind/Nethermind.Core/Crypto/Hash256.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace Nethermind.Core.Crypto
{
[DebuggerStepThrough]
[DebuggerDisplay("{ToString()}")]
[JsonConverter(typeof(ValueHash256Converter))]
public readonly struct ValueHash256 : IEquatable<ValueHash256>, IComparable<ValueHash256>, IEquatable<Hash256>
{
private readonly Vector256<byte> _bytes;
Expand Down
29 changes: 29 additions & 0 deletions src/Nethermind/Nethermind.Core/Crypto/ValueHash256Converter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Nethermind.Core.Crypto;

namespace Nethermind.Serialization.Json;

public class ValueHash256Converter : JsonConverter<ValueHash256>
{
public override ValueHash256 Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
byte[]? bytes = ByteArrayConverter.Convert(ref reader);
return bytes is null ? null : new ValueHash256(bytes);
}

public override void Write(
Utf8JsonWriter writer,
ValueHash256 keccak,
JsonSerializerOptions options)
{
ByteArrayConverter.Convert(writer, keccak.Bytes, skipLeadingZeros: false);
}
}
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Core/IAccountStateProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ namespace Nethermind.Core
{
public interface IAccountStateProvider
{
Account GetAccount(Address address);
AccountStruct GetAccount(Address address);
}
}
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Evm.Test/Eip1014Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public void Test_out_of_gas_existing_account_with_storage()
TestState.Commit(Spec);
TestState.CommitTree(0);

Hash256 storageRoot = TestState.GetAccount(expectedAddress).StorageRoot;
ValueHash256 storageRoot = TestState.GetAccount(expectedAddress).StorageRoot;
storageRoot.Should().NotBe(PatriciaTree.EmptyTreeHash);

TestState.CreateAccount(TestItem.AddressC, 1.Ether());
Expand Down
9 changes: 5 additions & 4 deletions src/Nethermind/Nethermind.Evm/VirtualMachine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using System.Runtime.Intrinsics;
using static Nethermind.Evm.VirtualMachine;
using static System.Runtime.CompilerServices.Unsafe;
using ValueHash256 = Nethermind.Core.Crypto.ValueHash256;

#if DEBUG
using Nethermind.Evm.Tracing.Debugger;
Expand Down Expand Up @@ -533,16 +534,16 @@ public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IR
}

CodeInfo cachedCodeInfo = null;
Hash256 codeHash = worldState.GetCodeHash(codeSource);
if (ReferenceEquals(codeHash, Keccak.OfAnEmptyString))
ValueHash256 codeHash = worldState.GetCodeHash(codeSource);
if (codeHash == Keccak.OfAnEmptyString.ValueHash256)
{
cachedCodeInfo = CodeInfo.Empty;
}

cachedCodeInfo ??= CodeCache.Get(codeHash);
if (cachedCodeInfo is null)
{
byte[] code = worldState.GetCode(codeHash);
byte[]? code = worldState.GetCode(codeHash);

if (code is null)
{
Expand All @@ -563,7 +564,7 @@ public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IR

[DoesNotReturn]
[StackTraceHidden]
static void MissingCode(Address codeSource, Hash256 codeHash)
static void MissingCode(Address codeSource, in ValueHash256 codeHash)
{
throw new NullReferenceException($"Code {codeHash} missing in the state for address {codeSource}");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -823,9 +823,9 @@ public async Task Trace_replayBlockTransactions_stateDiff()
.WithGasLimit(50000)
.WithGasPrice(10).SignedAndResolved(TestItem.PrivateKeyA).TestObject;

Account accountA = blockchain.State.GetAccount(TestItem.AddressA);
Account accountD = blockchain.State.GetAccount(TestItem.AddressD);
Account accountF = blockchain.State.GetAccount(TestItem.AddressF);
AccountStruct accountA = blockchain.State.GetAccount(TestItem.AddressA);
AccountStruct accountD = blockchain.State.GetAccount(TestItem.AddressD);
AccountStruct accountF = blockchain.State.GetAccount(TestItem.AddressF);

string[] traceTypes = { "stateDiff" };

Expand Down
20 changes: 13 additions & 7 deletions src/Nethermind/Nethermind.JsonRpc/Data/AccountForRpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@

namespace Nethermind.JsonRpc.Data;

public class AccountForRpc
public struct AccountForRpc
{
private Account _Account { get; set; }
private readonly Account? _account;
private readonly AccountStruct? _accountStruct;
public AccountForRpc(Account account)
{
_Account = account;
_account = account;
}

public Hash256 CodeHash => _Account.CodeHash;
public Hash256 StorageRoot => _Account.StorageRoot;
public UInt256 Balance => _Account.Balance;
public UInt256 Nonce => _Account.Nonce;
public AccountForRpc(in AccountStruct? account)
{
_accountStruct = account;
}

public ValueHash256 CodeHash => (_accountStruct?.CodeHash ?? _account?.CodeHash.ValueHash256)!.Value;
public ValueHash256 StorageRoot => (_accountStruct?.StorageRoot ?? _account?.StorageRoot.ValueHash256)!.Value;
public UInt256 Balance => (_accountStruct?.Balance ?? _account?.Balance)!.Value;
public UInt256 Nonce => (_accountStruct?.Nonce ?? _account?.Nonce)!.Value;

}
24 changes: 9 additions & 15 deletions src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ public ResultWrapper<IEnumerable<Address>> eth_accounts()
return Task.FromResult(GetStateFailureResult<UInt256?>(header));
}

Account account = _stateReader.GetAccount(header!.StateRoot!, address);
AccountStruct? account = _stateReader.GetAccount(header!.StateRoot!, address);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it have to be nullable? Extra byte it adds is problematic on copy size (128 bytes -> 129 bytes)

better to init with empy code hash and empty storage has and pass that back if null?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So been thinking about it, how to avoid copies to. Maybe the answer is something like:

bool TryGetAccount(root, address, ref AccountStruct account)

not sure if we could write directly to stack allocated by the calling method?

return Task.FromResult(ResultWrapper<UInt256?>.Success(account?.Balance ?? UInt256.Zero));
}

Expand Down Expand Up @@ -200,7 +200,7 @@ public Task<ResultWrapper<UInt256>> eth_getTransactionCount(Address address, Blo
return Task.FromResult(GetStateFailureResult<UInt256>(header));
}

Account account = _stateReader.GetAccount(header!.StateRoot!, address);
AccountStruct? account = _stateReader.GetAccount(header!.StateRoot!, address);
UInt256 nonce = account?.Nonce ?? 0;

return Task.FromResult(ResultWrapper<UInt256>.Success(nonce));
Expand Down Expand Up @@ -257,14 +257,8 @@ public ResultWrapper<byte[]> eth_getCode(Address address, BlockParameter? blockP
return GetStateFailureResult<byte[]>(header);
}

Account account = _stateReader.GetAccount(header!.StateRoot!, address);
if (account is null)
{
return ResultWrapper<byte[]>.Success(Array.Empty<byte>());
}

byte[]? code = _stateReader.GetCode(account.CodeHash);
return ResultWrapper<byte[]>.Success(code);
AccountStruct? account = _stateReader.GetAccount(header!.StateRoot!, address);
return ResultWrapper<byte[]>.Success(account is null ? Array.Empty<byte>() : _stateReader.GetCode(account.Value.CodeHash));
}

public ResultWrapper<byte[]> eth_sign(Address addressData, byte[] message)
Expand Down Expand Up @@ -701,22 +695,22 @@ private static IEnumerable<FilterLog> GetLogs(IEnumerable<FilterLog> logs, Cance
}
}

public ResultWrapper<AccountForRpc> eth_getAccount(Address accountAddress, BlockParameter? blockParameter)
public ResultWrapper<AccountForRpc?> eth_getAccount(Address accountAddress, BlockParameter? blockParameter)
{
SearchResult<BlockHeader> searchResult = _blockFinder.SearchForHeader(blockParameter);
if (searchResult.IsError)
{
return GetFailureResult<AccountForRpc, BlockHeader>(searchResult, _ethSyncingInfo.SyncMode.HaveNotSyncedHeadersYet());
return GetFailureResult<AccountForRpc?, BlockHeader>(searchResult, _ethSyncingInfo.SyncMode.HaveNotSyncedHeadersYet());
}

BlockHeader header = searchResult.Object;
if (!HasStateForBlock(_blockchainBridge, header!))
{
return GetStateFailureResult<AccountForRpc>(header);
return GetStateFailureResult<AccountForRpc?>(header);
}

Account account = _stateReader.GetAccount(header!.StateRoot!, accountAddress);
return ResultWrapper<AccountForRpc>.Success(account is null ? null : new AccountForRpc(account));
AccountStruct? account = _stateReader.GetAccount(header!.StateRoot!, accountAddress);
return ResultWrapper<AccountForRpc?>.Success(account is null ? null : new AccountForRpc(account.Value));
}

private static ResultWrapper<TResult> GetFailureResult<TResult, TSearch>(SearchResult<TSearch> searchResult, bool isTemporary) where TSearch : class =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,6 @@ ResultWrapper<TransactionForRpc> eth_getTransactionByBlockNumberAndIndex(
ResultWrapper<AccountProof> eth_getProof([JsonRpcParameter(ExampleValue = "[\"0x7F0d15C7FAae65896648C8273B6d7E43f58Fa842\",[ \"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\" ],\"latest\"]")] Address accountAddress, UInt256[] hashRate, BlockParameter blockParameter);

[JsonRpcMethod(IsImplemented = true, Description = "Retrieves Accounts via Address and Blocknumber", IsSharable = true)]
ResultWrapper<AccountForRpc> eth_getAccount([JsonRpcParameter(ExampleValue = "[\"0xaa00000000000000000000000000000000000000\", \"latest\"]")] Address accountAddress, BlockParameter blockParameter = null);
ResultWrapper<AccountForRpc?> eth_getAccount([JsonRpcParameter(ExampleValue = "[\"0xaa00000000000000000000000000000000000000\", \"latest\"]")] Address accountAddress, BlockParameter blockParameter = null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,16 @@ private void AssertExecutionStatusChanged(IBlockFinder blockFinder, Hash256 head
private (UInt256, UInt256) AddTransactions(MergeTestBlockchain chain, ExecutionPayload executePayloadRequest,
PrivateKey from, Address to, uint count, int value, out BlockHeader parentHeader)
{
Transaction[] transactions = BuildTransactions(chain, executePayloadRequest.ParentHash, from, to, count, value, out Account accountFrom, out parentHeader);
Transaction[] transactions = BuildTransactions(chain, executePayloadRequest.ParentHash, from, to, count, value, out AccountStruct accountFrom, out parentHeader);
executePayloadRequest.SetTransactions(transactions);
UInt256 totalValue = ((int)(count * value)).GWei();
return (accountFrom.Balance - totalValue, chain.StateReader.GetBalance(parentHeader.StateRoot!, to) + totalValue);
}

private Transaction[] BuildTransactions(MergeTestBlockchain chain, Hash256 parentHash, PrivateKey from,
Address to, uint count, int value, out Account accountFrom, out BlockHeader parentHeader, int blobCountPerTx = 0)
Address to, uint count, int value, out AccountStruct accountFrom, out BlockHeader parentHeader, int blobCountPerTx = 0)
{
Transaction BuildTransaction(uint index, Account senderAccount) =>
Transaction BuildTransaction(uint index, AccountStruct senderAccount) =>
Build.A.Transaction.WithNonce(senderAccount.Nonce + index)
.WithTimestamp(Timestamper.UnixTime.Seconds)
.WithTo(to)
Expand All @@ -66,7 +66,7 @@ Transaction BuildTransaction(uint index, Account senderAccount) =>
.SignedAndResolved(from).TestObject;

parentHeader = chain.BlockTree.FindHeader(parentHash, BlockTreeLookupOptions.None)!;
Account account = chain.StateReader.GetAccount(parentHeader.StateRoot!, from.Address)!;
AccountStruct account = chain.StateReader.GetAccount(parentHeader.StateRoot!, from.Address)!.Value;
accountFrom = account;

return Enumerable.Range(0, (int)count).Select(i => BuildTransaction((uint)i, account)).ToArray();
Expand Down
Loading