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

Optimize StorageKey creation #3756

Merged
merged 25 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8abd53a
Optimize key builder
shargon Feb 13, 2025
8b49b1d
Optimize with GetSpan
shargon Feb 13, 2025
92aa290
Merge branch 'master' into optimize-key-builder
shargon Feb 13, 2025
8c95207
Use Span
shargon Feb 13, 2025
c293204
Merge branch 'optimize-key-builder' of https://github.com/neo-project…
shargon Feb 13, 2025
100afe5
Clean
shargon Feb 13, 2025
fbe36a6
Rename
shargon Feb 13, 2025
37f80e5
Update src/Neo/SmartContract/KeyBuilder.cs
shargon Feb 13, 2025
df404d1
Update KeyBuilder.cs
shargon Feb 13, 2025
f94f0a4
Merge branch 'master' into optimize-key-builder
shargon Feb 13, 2025
93b7ec2
Merge branch 'master' into optimize-key-builder
shargon Feb 13, 2025
f703723
Merge branch 'master' into optimize-key-builder
shargon Feb 14, 2025
0239be3
@cschuchardt88 feedback
shargon Feb 14, 2025
31bd1fe
Avoid ToArray in StorageKey constructor
shargon Feb 14, 2025
1ba6ef3
Use sizeof
shargon Feb 14, 2025
5bb2fe9
Merge branch 'master' into optimize-key-builder
cschuchardt88 Feb 14, 2025
734dbb9
Merge branch 'master' into optimize-key-builder
shargon Feb 14, 2025
9a72e73
Optimize ECPoint
shargon Feb 14, 2025
791d9b1
Optimize ECPoint
shargon Feb 14, 2025
2c1274e
Add ISerializableSpan
shargon Feb 14, 2025
6903d12
Merge branch 'master' into optimize-key-builder
shargon Feb 15, 2025
9cbac9f
Move to StorageKey
shargon Feb 15, 2025
2e5183f
Use ReadOnlySpan
shargon Feb 15, 2025
6ed3997
Merge branch 'master' into optimize-key-builder
shargon Feb 15, 2025
a0dfe7d
Merge branch 'master' into optimize-key-builder
shargon Feb 17, 2025
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
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