Skip to content

Commit

Permalink
Optimize StorageKey creation (#3756)
Browse files Browse the repository at this point in the history
* Optimize key builder

* Optimize with GetSpan

* Use Span

* Clean

* Rename

* Update src/Neo/SmartContract/KeyBuilder.cs

Co-authored-by: nan01ab <[email protected]>

* Update KeyBuilder.cs

* @cschuchardt88 feedback

* Avoid ToArray in StorageKey constructor

* Use sizeof

* Optimize ECPoint

* Optimize ECPoint

* Add ISerializableSpan

* Move to StorageKey

* Use ReadOnlySpan

---------

Co-authored-by: nan01ab <[email protected]>
Co-authored-by: Christopher Schuchardt <[email protected]>
  • Loading branch information
3 people authored Feb 18, 2025
1 parent fe0f732 commit 6f9ef50
Show file tree
Hide file tree
Showing 10 changed files with 382 additions and 67 deletions.
20 changes: 10 additions & 10 deletions src/Neo/SmartContract/Native/ContractManagement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,13 @@ internal override async ContractTask OnPersistAsync(ApplicationEngine engine)
if (contract.IsInitializeBlock(engine.ProtocolSettings, engine.PersistingBlock.Index, out var hfs))
{
ContractState contractState = contract.GetContractState(engine.ProtocolSettings, engine.PersistingBlock.Index);
StorageItem state = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Contract).Add(contract.Hash));
StorageItem state = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Contract, contract.Hash));

if (state is null)
{
// Create the contract state
engine.SnapshotCache.Add(CreateStorageKey(Prefix_Contract).Add(contract.Hash), new StorageItem(contractState));
engine.SnapshotCache.Add(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id), new StorageItem(contract.Hash.ToArray()));
engine.SnapshotCache.Add(CreateStorageKey(Prefix_Contract, contract.Hash), new StorageItem(contractState));
engine.SnapshotCache.Add(CreateStorageKey(Prefix_ContractHash, contract.Id), new StorageItem(contract.Hash.ToArray()));

// Initialize the native smart contract if it's active starting from the genesis.
// If it's not the case, then hardfork-based initialization will be performed down below.
Expand Down Expand Up @@ -141,7 +141,7 @@ private void SetMinimumDeploymentFee(ApplicationEngine engine, BigInteger value/
[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)]
public ContractState GetContract(IReadOnlyStore snapshot, UInt160 hash)
{
var key = CreateStorageKey(Prefix_Contract).Add(hash);
var key = CreateStorageKey(Prefix_Contract, hash);
return snapshot.TryGet(key, out var item) ? item.GetInteroperable<ContractState>(false) : null;
}

Expand All @@ -154,7 +154,7 @@ public ContractState GetContract(IReadOnlyStore snapshot, UInt160 hash)
[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)]
public ContractState GetContractById(IReadOnlyStore snapshot, int id)
{
var key = CreateStorageKey(Prefix_ContractHash).AddBigEndian(id);
var key = CreateStorageKey(Prefix_ContractHash, id);
return snapshot.TryGet(key, out var item) ? GetContract(snapshot, new UInt160(item.Value.Span)) : null;
}

Expand Down Expand Up @@ -233,7 +233,7 @@ private async ContractTask<ContractState> Deploy(ApplicationEngine engine, byte[
if (Policy.IsBlocked(engine.SnapshotCache, hash))
throw new InvalidOperationException($"The contract {hash} has been blocked.");

StorageKey key = CreateStorageKey(Prefix_Contract).Add(hash);
StorageKey key = CreateStorageKey(Prefix_Contract, hash);
if (engine.SnapshotCache.Contains(key))
throw new InvalidOperationException($"Contract Already Exists: {hash}");
ContractState contract = new()
Expand All @@ -248,7 +248,7 @@ private async ContractTask<ContractState> Deploy(ApplicationEngine engine, byte[
if (!contract.Manifest.IsValid(engine.Limits, hash)) throw new InvalidOperationException($"Invalid Manifest: {hash}");

engine.SnapshotCache.Add(key, new StorageItem(contract));
engine.SnapshotCache.Add(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id), new StorageItem(hash.ToArray()));
engine.SnapshotCache.Add(CreateStorageKey(Prefix_ContractHash, contract.Id), new StorageItem(hash.ToArray()));

await OnDeployAsync(engine, contract, data, false);

Expand All @@ -268,7 +268,7 @@ private ContractTask Update(ApplicationEngine engine, byte[] nefFile, byte[] man

engine.AddFee(engine.StoragePrice * ((nefFile?.Length ?? 0) + (manifest?.Length ?? 0)));

var contract = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Contract).Add(engine.CallingScriptHash))?.GetInteroperable<ContractState>(false);
var contract = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Contract, engine.CallingScriptHash))?.GetInteroperable<ContractState>(false);
if (contract is null) throw new InvalidOperationException($"Updating Contract Does Not Exist: {engine.CallingScriptHash}");
if (contract.UpdateCounter == ushort.MaxValue) throw new InvalidOperationException($"The contract reached the maximum number of updates.");

Expand Down Expand Up @@ -300,11 +300,11 @@ private ContractTask Update(ApplicationEngine engine, byte[] nefFile, byte[] man
private void Destroy(ApplicationEngine engine)
{
UInt160 hash = engine.CallingScriptHash;
StorageKey ckey = CreateStorageKey(Prefix_Contract).Add(hash);
StorageKey ckey = CreateStorageKey(Prefix_Contract, hash);
ContractState contract = engine.SnapshotCache.TryGet(ckey)?.GetInteroperable<ContractState>(false);
if (contract is null) return;
engine.SnapshotCache.Delete(ckey);
engine.SnapshotCache.Delete(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id));
engine.SnapshotCache.Delete(CreateStorageKey(Prefix_ContractHash, contract.Id));
foreach (var (key, _) in engine.SnapshotCache.Find(StorageKey.CreateSearchPrefix(contract.Id, ReadOnlySpan<byte>.Empty)))
engine.SnapshotCache.Delete(key);
// lock contract
Expand Down
10 changes: 5 additions & 5 deletions src/Neo/SmartContract/Native/FungibleToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ internal async ContractTask Mint(ApplicationEngine engine, UInt160 account, BigI
{
if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount));
if (amount.IsZero) return;
StorageItem storage = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Account).Add(account), () => new StorageItem(new TState()));
StorageItem storage = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Account, account), () => new StorageItem(new TState()));
TState state = storage.GetInteroperable<TState>();
OnBalanceChanging(engine, account, state, amount);
state.Balance += amount;
Expand All @@ -87,7 +87,7 @@ internal async ContractTask Burn(ApplicationEngine engine, UInt160 account, BigI
{
if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount));
if (amount.IsZero) return;
StorageKey key = CreateStorageKey(Prefix_Account).Add(account);
StorageKey key = CreateStorageKey(Prefix_Account, account);
StorageItem storage = engine.SnapshotCache.GetAndChange(key);
TState state = storage.GetInteroperable<TState>();
if (state.Balance < amount) throw new InvalidOperationException();
Expand Down Expand Up @@ -122,7 +122,7 @@ public virtual BigInteger TotalSupply(IReadOnlyStore snapshot)
[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)]
public virtual BigInteger BalanceOf(IReadOnlyStore snapshot, UInt160 account)
{
var key = CreateStorageKey(Prefix_Account).Add(account);
var key = CreateStorageKey(Prefix_Account, account);
if (snapshot.TryGet(key, out var item))
return item.GetInteroperable<TState>().Balance;
return BigInteger.Zero;
Expand All @@ -136,7 +136,7 @@ private protected async ContractTask<bool> Transfer(ApplicationEngine engine, UI
if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount));
if (!from.Equals(engine.CallingScriptHash) && !engine.CheckWitnessInternal(from))
return false;
StorageKey key_from = CreateStorageKey(Prefix_Account).Add(from);
StorageKey key_from = CreateStorageKey(Prefix_Account, from);
StorageItem storage_from = engine.SnapshotCache.GetAndChange(key_from);
if (amount.IsZero)
{
Expand All @@ -162,7 +162,7 @@ private protected async ContractTask<bool> Transfer(ApplicationEngine engine, UI
engine.SnapshotCache.Delete(key_from);
else
state_from.Balance -= amount;
StorageKey key_to = CreateStorageKey(Prefix_Account).Add(to);
StorageKey key_to = CreateStorageKey(Prefix_Account, to);
StorageItem storage_to = engine.SnapshotCache.GetAndChange(key_to, () => new StorageItem(new TState()));
TState state_to = storage_to.GetInteroperable<TState>();
OnBalanceChanging(engine, to, state_to, amount);
Expand Down
22 changes: 11 additions & 11 deletions src/Neo/SmartContract/Native/LedgerContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,22 @@ internal override ContractTask OnPersistAsync(ApplicationEngine engine)
Transaction = p,
State = VMState.NONE
}).ToArray();
engine.SnapshotCache.Add(CreateStorageKey(Prefix_BlockHash).AddBigEndian(engine.PersistingBlock.Index), new StorageItem(engine.PersistingBlock.Hash.ToArray()));
engine.SnapshotCache.Add(CreateStorageKey(Prefix_Block).Add(engine.PersistingBlock.Hash), new StorageItem(Trim(engine.PersistingBlock).ToArray()));
engine.SnapshotCache.Add(CreateStorageKey(Prefix_BlockHash, engine.PersistingBlock.Index), new StorageItem(engine.PersistingBlock.Hash.ToArray()));
engine.SnapshotCache.Add(CreateStorageKey(Prefix_Block, engine.PersistingBlock.Hash), new StorageItem(Trim(engine.PersistingBlock).ToArray()));
foreach (TransactionState tx in transactions)
{
// It's possible that there are previously saved malicious conflict records for this transaction.
// If so, then remove it and store the relevant transaction itself.
engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Transaction).Add(tx.Transaction.Hash), () => new StorageItem(new TransactionState())).FromReplica(new StorageItem(tx));
engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Transaction, tx.Transaction.Hash), () => new StorageItem(new TransactionState())).FromReplica(new StorageItem(tx));

// Store transaction's conflicits.
var conflictingSigners = tx.Transaction.Signers.Select(s => s.Account);
foreach (var attr in tx.Transaction.GetAttributes<Conflicts>())
{
engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Transaction).Add(attr.Hash), () => new StorageItem(new TransactionState())).FromReplica(new StorageItem(new TransactionState() { BlockIndex = engine.PersistingBlock.Index }));
engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Transaction, attr.Hash), () => new StorageItem(new TransactionState())).FromReplica(new StorageItem(new TransactionState() { BlockIndex = engine.PersistingBlock.Index }));
foreach (var signer in conflictingSigners)
{
engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Transaction).Add(attr.Hash).Add(signer), () => new StorageItem(new TransactionState())).FromReplica(new StorageItem(new TransactionState() { BlockIndex = engine.PersistingBlock.Index }));
engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Transaction, attr.Hash, signer), () => new StorageItem(new TransactionState())).FromReplica(new StorageItem(new TransactionState() { BlockIndex = engine.PersistingBlock.Index }));
}
}
}
Expand Down Expand Up @@ -105,7 +105,7 @@ public UInt256 GetBlockHash(IReadOnlyStore snapshot, uint index)
if (snapshot is null)
throw new ArgumentNullException(nameof(snapshot));

var key = CreateStorageKey(Prefix_BlockHash).AddBigEndian(index);
var key = CreateStorageKey(Prefix_BlockHash, index);
return snapshot.TryGet(key, out var item) ? new UInt256(item.Value.Span) : null;
}

Expand Down Expand Up @@ -150,7 +150,7 @@ public bool ContainsBlock(IReadOnlyStore snapshot, UInt256 hash)
if (snapshot is null)
throw new ArgumentNullException(nameof(snapshot));

return snapshot.Contains(CreateStorageKey(Prefix_Block).Add(hash));
return snapshot.Contains(CreateStorageKey(Prefix_Block, hash));
}

/// <summary>
Expand Down Expand Up @@ -188,15 +188,15 @@ public bool ContainsConflictHash(IReadOnlyStore snapshot, UInt256 hash, IEnumera
throw new ArgumentNullException(nameof(signers));

// Check the dummy stub firstly to define whether there's exist at least one conflict record.
var key = CreateStorageKey(Prefix_Transaction).Add(hash);
var key = CreateStorageKey(Prefix_Transaction, hash);
var stub = snapshot.TryGet(key, out var item) ? item.GetInteroperable<TransactionState>() : null;
if (stub is null || stub.Transaction is not null || !IsTraceableBlock(snapshot, stub.BlockIndex, maxTraceableBlocks))
return false;

// At least one conflict record is found, then need to check signers intersection.
foreach (var signer in signers)
{
key = CreateStorageKey(Prefix_Transaction).Add(hash).Add(signer);
key = CreateStorageKey(Prefix_Transaction, hash, signer);
var state = snapshot.TryGet(key, out var tx) ? tx.GetInteroperable<TransactionState>() : null;
if (state is not null && IsTraceableBlock(snapshot, state.BlockIndex, maxTraceableBlocks))
return true;
Expand All @@ -216,7 +216,7 @@ public TrimmedBlock GetTrimmedBlock(IReadOnlyStore snapshot, UInt256 hash)
if (snapshot is null)
throw new ArgumentNullException(nameof(snapshot));

var key = CreateStorageKey(Prefix_Block).Add(hash);
var key = CreateStorageKey(Prefix_Block, hash);
if (snapshot.TryGet(key, out var item))
return item.Value.AsSerializable<TrimmedBlock>();
return null;
Expand Down Expand Up @@ -303,7 +303,7 @@ public TransactionState GetTransactionState(IReadOnlyStore snapshot, UInt256 has
if (snapshot is null)
throw new ArgumentNullException(nameof(snapshot));

var key = CreateStorageKey(Prefix_Transaction).Add(hash);
var key = CreateStorageKey(Prefix_Transaction, hash);
var state = snapshot.TryGet(key, out var item) ? item.GetInteroperable<TransactionState>() : null;
return state?.Transaction is null ? null : state;
}
Expand Down
41 changes: 37 additions & 4 deletions src/Neo/SmartContract/Native/NativeContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.Cryptography.ECC;
using Neo.SmartContract.Manifest;
using Neo.VM;
using System;
Expand Down Expand Up @@ -347,10 +348,42 @@ protected static bool CheckCommittee(ApplicationEngine engine)
return engine.CheckWitnessInternal(committeeMultiSigAddr);
}

private protected KeyBuilder CreateStorageKey(byte prefix)
{
return new KeyBuilder(Id, prefix);
}
#region Storage keys

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private protected StorageKey CreateStorageKey(byte prefix) => StorageKey.Create(Id, prefix);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private protected StorageKey CreateStorageKey(byte prefix, byte data) => StorageKey.Create(Id, prefix, data);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private protected StorageKey CreateStorageKey(byte prefix, int bigEndianKey) => StorageKey.Create(Id, prefix, bigEndianKey);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private protected StorageKey CreateStorageKey(byte prefix, uint bigEndianKey) => StorageKey.Create(Id, prefix, bigEndianKey);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private protected StorageKey CreateStorageKey(byte prefix, long bigEndianKey) => StorageKey.Create(Id, prefix, bigEndianKey);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private protected StorageKey CreateStorageKey(byte prefix, ulong bigEndianKey) => StorageKey.Create(Id, prefix, bigEndianKey);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private protected StorageKey CreateStorageKey(byte prefix, ReadOnlySpan<byte> content) => StorageKey.Create(Id, prefix, content);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private protected StorageKey CreateStorageKey(byte prefix, UInt160 hash) => StorageKey.Create(Id, prefix, hash);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private protected StorageKey CreateStorageKey(byte prefix, UInt256 hash) => StorageKey.Create(Id, prefix, hash);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private protected StorageKey CreateStorageKey(byte prefix, ECPoint pubKey) => StorageKey.Create(Id, prefix, pubKey);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private protected StorageKey CreateStorageKey(byte prefix, UInt256 hash, UInt160 signer) => StorageKey.Create(Id, prefix, hash, signer);

#endregion

/// <summary>
/// Gets the native contract with the specified hash.
Expand Down
Loading

0 comments on commit 6f9ef50

Please sign in to comment.