Skip to content

Commit

Permalink
Optimize contract calls (#3170)
Browse files Browse the repository at this point in the history
* Avoid load nefFile without some checksum

* Core's feedback

* Anna's feedback

* Add defaults
  • Loading branch information
shargon authored Mar 7, 2024
1 parent 199ffee commit 413cfc4
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 12 deletions.
13 changes: 9 additions & 4 deletions src/Neo/SmartContract/ContractState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace Neo.SmartContract
/// <summary>
/// Represents a deployed contract.
/// </summary>
public class ContractState : IInteroperable
public class ContractState : IInteroperableVerifiable
{
/// <summary>
/// The id of the contract.
Expand Down Expand Up @@ -69,7 +69,7 @@ IInteroperable IInteroperable.Clone()

void IInteroperable.FromReplica(IInteroperable replica)
{
ContractState from = (ContractState)replica;
var from = (ContractState)replica;
Id = from.Id;
UpdateCounter = from.UpdateCounter;
Hash = from.Hash;
Expand All @@ -79,11 +79,16 @@ void IInteroperable.FromReplica(IInteroperable replica)

void IInteroperable.FromStackItem(StackItem stackItem)
{
Array array = (Array)stackItem;
((IInteroperableVerifiable)this).FromStackItem(stackItem, true);
}

void IInteroperableVerifiable.FromStackItem(StackItem stackItem, bool verify)
{
var array = (Array)stackItem;
Id = (int)array[0].GetInteger();
UpdateCounter = (ushort)array[1].GetInteger();
Hash = new UInt160(array[2].GetSpan());
Nef = ((ByteString)array[3]).Memory.AsSerializable<NefFile>();
Nef = NefFile.Parse(((ByteString)array[3]).Memory, verify);
Manifest = array[4].ToInteroperable<ContractManifest>();
}

Expand Down
29 changes: 29 additions & 0 deletions src/Neo/SmartContract/IInteroperableVerifiable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// IInteroperableVerifiable.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.VM.Types;

namespace Neo.SmartContract
{
/// <summary>
/// Represents the object that can be converted to and from <see cref="StackItem"/>
/// and allows you to specify whether a verification is required.
/// </summary>
public interface IInteroperableVerifiable : IInteroperable
{
/// <summary>
/// Convert a <see cref="StackItem"/> to the current object.
/// </summary>
/// <param name="stackItem">The <see cref="StackItem"/> to convert.</param>
/// <param name="verify">Verify the content</param>
void FromStackItem(StackItem stackItem, bool verify = true);
}
}
10 changes: 5 additions & 5 deletions src/Neo/SmartContract/Native/ContractManagement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ internal override async ContractTask OnPersist(ApplicationEngine engine)
else
{
// Parse old contract
var oldContract = state.GetInteroperable<ContractState>();
var oldContract = state.GetInteroperable<ContractState>(false);
// Increase the update counter
oldContract.UpdateCounter++;
// Modify nef and manifest
Expand Down Expand Up @@ -122,7 +122,7 @@ private void SetMinimumDeploymentFee(ApplicationEngine engine, BigInteger value)
[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)]
public ContractState GetContract(DataCache snapshot, UInt160 hash)
{
return snapshot.TryGet(CreateStorageKey(Prefix_Contract).Add(hash))?.GetInteroperable<ContractState>();
return snapshot.TryGet(CreateStorageKey(Prefix_Contract).Add(hash))?.GetInteroperable<ContractState>(false);
}

/// <summary>
Expand Down Expand Up @@ -183,7 +183,7 @@ public bool HasMethod(DataCache snapshot, UInt160 hash, string method, int pcoun
public IEnumerable<ContractState> ListContracts(DataCache snapshot)
{
byte[] listContractsPrefix = CreateStorageKey(Prefix_Contract).ToArray();
return snapshot.Find(listContractsPrefix).Select(kvp => kvp.Value.GetInteroperable<ContractState>());
return snapshot.Find(listContractsPrefix).Select(kvp => kvp.Value.GetInteroperable<ContractState>(false));
}

[ContractMethod(RequiredCallFlags = CallFlags.All)]
Expand Down Expand Up @@ -250,7 +250,7 @@ private ContractTask Update(ApplicationEngine engine, byte[] nefFile, byte[] man

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

var contract = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Contract).Add(engine.CallingScriptHash))?.GetInteroperable<ContractState>();
var contract = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Contract).Add(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 @@ -283,7 +283,7 @@ private void Destroy(ApplicationEngine engine)
{
UInt160 hash = engine.CallingScriptHash;
StorageKey ckey = CreateStorageKey(Prefix_Contract).Add(hash);
ContractState contract = engine.Snapshot.TryGet(ckey)?.GetInteroperable<ContractState>();
ContractState contract = engine.Snapshot.TryGet(ckey)?.GetInteroperable<ContractState>(false);
if (contract is null) return;
engine.Snapshot.Delete(ckey);
engine.Snapshot.Delete(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id));
Expand Down
25 changes: 22 additions & 3 deletions src/Neo/SmartContract/NefFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,20 @@ public class NefFile : ISerializable
Script.GetVarSize() + // Script
sizeof(uint); // Checksum

/// <summary>
/// Parse NefFile from memory
/// </summary>
/// <param name="memory">Memory</param>
/// <param name="verify">Do checksum and MaxItemSize checks</param>
/// <returns>NefFile</returns>
public static NefFile Parse(ReadOnlyMemory<byte> memory, bool verify = true)
{
var reader = new MemoryReader(memory);
var nef = new NefFile();
nef.Deserialize(ref reader, verify);
return nef;
}

public void Serialize(BinaryWriter writer)
{
SerializeHeader(writer);
Expand All @@ -103,7 +117,9 @@ private void SerializeHeader(BinaryWriter writer)
writer.WriteFixedString(Compiler, 64);
}

public void Deserialize(ref MemoryReader reader)
public void Deserialize(ref MemoryReader reader) => Deserialize(ref reader, true);

public void Deserialize(ref MemoryReader reader, bool verify = true)
{
long startPosition = reader.Position;
if (reader.ReadUInt32() != Magic) throw new FormatException("Wrong magic");
Expand All @@ -115,8 +131,11 @@ public void Deserialize(ref MemoryReader reader)
Script = reader.ReadVarMemory((int)ExecutionEngineLimits.Default.MaxItemSize);
if (Script.Length == 0) throw new ArgumentException($"Script can't be empty");
CheckSum = reader.ReadUInt32();
if (CheckSum != ComputeChecksum(this)) throw new FormatException("CRC verification fail");
if (reader.Position - startPosition > ExecutionEngineLimits.Default.MaxItemSize) throw new FormatException("Max vm item size exceed");
if (verify)
{
if (CheckSum != ComputeChecksum(this)) throw new FormatException("CRC verification fail");
if (reader.Position - startPosition > ExecutionEngineLimits.Default.MaxItemSize) throw new FormatException("Max vm item size exceed");
}
}

/// <summary>
Expand Down
18 changes: 18 additions & 0 deletions src/Neo/SmartContract/StorageItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,24 @@ public void FromReplica(StorageItem replica)
return (T)cache;
}

/// <summary>
/// Gets an <see cref="IInteroperable"/> from the storage.
/// </summary>
/// <param name="verify">Verify deserialization</param>
/// <typeparam name="T">The type of the <see cref="IInteroperable"/>.</typeparam>
/// <returns>The <see cref="IInteroperable"/> in the storage.</returns>
public T GetInteroperable<T>(bool verify = true) where T : IInteroperableVerifiable, new()
{
if (cache is null)
{
var interoperable = new T();
interoperable.FromStackItem(BinarySerializer.Deserialize(value, ExecutionEngineLimits.Default), verify);
cache = interoperable;
}
value = null;
return (T)cache;
}

public void Serialize(BinaryWriter writer)
{
writer.Write(Value.Span);
Expand Down

0 comments on commit 413cfc4

Please sign in to comment.