Skip to content

Commit

Permalink
Optimize the GetPrice() mechanism of SYSCALLs (neo-project#1650)
Browse files Browse the repository at this point in the history
  • Loading branch information
erikzhang authored and Tommo-L committed Jun 22, 2020
1 parent 6e17c69 commit 1c0a23e
Show file tree
Hide file tree
Showing 11 changed files with 59 additions and 170 deletions.
2 changes: 0 additions & 2 deletions src/neo/SmartContract/ApplicationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@ public override void Dispose()

protected override bool OnSysCall(uint method)
{
if (!AddGas(InteropService.GetPrice(method, CurrentContext.EvaluationStack, Snapshot)))
return false;
return InteropService.Invoke(this, method);
}

Expand Down
25 changes: 3 additions & 22 deletions src/neo/SmartContract/InteropDescriptor.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Neo.Cryptography;
using Neo.Persistence;
using Neo.VM;
using System;
using System.Text;

Expand All @@ -11,37 +9,20 @@ public class InteropDescriptor
public string Method { get; }
public uint Hash { get; }
internal Func<ApplicationEngine, bool> Handler { get; }
public long Price { get; }
public Func<EvaluationStack, StoreView, long> PriceCalculator { get; }
public long FixedPrice { get; }
public TriggerType AllowedTriggers { get; }
public CallFlags RequiredCallFlags { get; }

internal InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, long price, TriggerType allowedTriggers, CallFlags requiredCallFlags)
: this(method, handler, allowedTriggers, requiredCallFlags)
{
this.Price = price;
}

internal InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, Func<EvaluationStack, StoreView, long> priceCalculator, TriggerType allowedTriggers, CallFlags requiredCallFlags)
: this(method, handler, allowedTriggers, requiredCallFlags)
{
this.PriceCalculator = priceCalculator;
}

private InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, TriggerType allowedTriggers, CallFlags requiredCallFlags)
internal InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, long fixedPrice, TriggerType allowedTriggers, CallFlags requiredCallFlags)
{
this.Method = method;
this.Hash = BitConverter.ToUInt32(Encoding.ASCII.GetBytes(method).Sha256(), 0);
this.Handler = handler;
this.FixedPrice = fixedPrice;
this.AllowedTriggers = allowedTriggers;
this.RequiredCallFlags = requiredCallFlags;
}

public long GetPrice(EvaluationStack stack, StoreView snapshot)
{
return PriceCalculator is null ? Price : PriceCalculator(stack, snapshot);
}

public static implicit operator uint(InteropDescriptor descriptor)
{
return descriptor.Hash;
Expand Down
15 changes: 6 additions & 9 deletions src/neo/SmartContract/InteropService.Contract.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Neo.Cryptography.ECC;
using Neo.IO;
using Neo.Ledger;
using Neo.Persistence;
using Neo.SmartContract.Manifest;
using Neo.SmartContract.Native;
using Neo.VM;
Expand All @@ -18,8 +17,8 @@ public static class Contract
{
public const int MaxLength = 1024 * 1024;

public static readonly InteropDescriptor Create = Register("System.Contract.Create", Contract_Create, GetDeploymentPrice, TriggerType.Application, CallFlags.AllowModifyStates);
public static readonly InteropDescriptor Update = Register("System.Contract.Update", Contract_Update, GetDeploymentPrice, TriggerType.Application, CallFlags.AllowModifyStates);
public static readonly InteropDescriptor Create = Register("System.Contract.Create", Contract_Create, 0, TriggerType.Application, CallFlags.AllowModifyStates);
public static readonly InteropDescriptor Update = Register("System.Contract.Update", Contract_Update, 0, TriggerType.Application, CallFlags.AllowModifyStates);
public static readonly InteropDescriptor Destroy = Register("System.Contract.Destroy", Contract_Destroy, 0_01000000, TriggerType.Application, CallFlags.AllowModifyStates);
public static readonly InteropDescriptor Call = Register("System.Contract.Call", Contract_Call, 0_01000000, TriggerType.System | TriggerType.Application, CallFlags.AllowCall);
public static readonly InteropDescriptor CallEx = Register("System.Contract.CallEx", Contract_CallEx, 0_01000000, TriggerType.System | TriggerType.Application, CallFlags.AllowCall);
Expand All @@ -32,12 +31,6 @@ public static class Contract
/// </summary>
public static readonly InteropDescriptor CreateStandardAccount = Register("System.Contract.CreateStandardAccount", Contract_CreateStandardAccount, 0_00010000, TriggerType.All, CallFlags.None);

private static long GetDeploymentPrice(EvaluationStack stack, StoreView snapshot)
{
int size = stack.Peek(0).GetByteLength() + stack.Peek(1).GetByteLength();
return Storage.GasPerByte * size;
}

private static bool Contract_GetCallFlags(ApplicationEngine engine)
{
var state = engine.CurrentContext.GetState<ExecutionContextState>();
Expand All @@ -53,6 +46,8 @@ private static bool Contract_Create(ApplicationEngine engine)
if (!engine.TryPop(out ReadOnlySpan<byte> manifest)) return false;
if (manifest.Length == 0 || manifest.Length > ContractManifest.MaxLength) return false;

if (!engine.AddGas(Storage.GasPerByte * (script.Length + manifest.Length))) return false;

UInt160 hash = script.ToScriptHash();
ContractState contract = engine.Snapshot.Contracts.TryGet(hash);
if (contract != null) return false;
Expand All @@ -75,6 +70,8 @@ private static bool Contract_Update(ApplicationEngine engine)
if (!engine.TryPop(out StackItem item0)) return false;
if (!engine.TryPop(out StackItem item1)) return false;

if (!engine.AddGas(Storage.GasPerByte * (item0.GetByteLength() + item1.GetByteLength()))) return false;

var contract = engine.Snapshot.Contracts.TryGet(engine.CurrentScriptHash);
if (contract is null) return false;

Expand Down
17 changes: 3 additions & 14 deletions src/neo/SmartContract/InteropService.Crypto.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Neo.Cryptography;
using Neo.Network.P2P;
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
using Neo.VM;
using Neo.VM.Types;
using System;
Expand All @@ -18,19 +17,8 @@ public static class Crypto

public static readonly InteropDescriptor VerifyWithECDsaSecp256r1 = Register("Neo.Crypto.ECDsa.Secp256r1.Verify", Crypto_ECDsaSecp256r1Verify, 0_01000000, TriggerType.All, CallFlags.None);
public static readonly InteropDescriptor VerifyWithECDsaSecp256k1 = Register("Neo.Crypto.ECDsa.Secp256k1.Verify", Crypto_ECDsaSecp256k1Verify, 0_01000000, TriggerType.All, CallFlags.None);
public static readonly InteropDescriptor CheckMultisigWithECDsaSecp256r1 = Register("Neo.Crypto.ECDsa.Secp256r1.CheckMultiSig", Crypto_ECDsaSecp256r1CheckMultiSig, GetECDsaCheckMultiSigPrice, TriggerType.All, CallFlags.None);
public static readonly InteropDescriptor CheckMultisigWithECDsaSecp256k1 = Register("Neo.Crypto.ECDsa.Secp256k1.CheckMultiSig", Crypto_ECDsaSecp256k1CheckMultiSig, GetECDsaCheckMultiSigPrice, TriggerType.All, CallFlags.None);

private static long GetECDsaCheckMultiSigPrice(EvaluationStack stack, StoreView snapshot)
{
if (stack.Count < 2) return 0;
var item = stack.Peek(1);
int n;
if (item is Array array) n = array.Count;
else n = (int)item.GetBigInteger();
if (n < 1) return 0;
return VerifyWithECDsaSecp256r1.Price * n;
}
public static readonly InteropDescriptor CheckMultisigWithECDsaSecp256r1 = Register("Neo.Crypto.ECDsa.Secp256r1.CheckMultiSig", Crypto_ECDsaSecp256r1CheckMultiSig, 0, TriggerType.All, CallFlags.None);
public static readonly InteropDescriptor CheckMultisigWithECDsaSecp256k1 = Register("Neo.Crypto.ECDsa.Secp256k1.CheckMultiSig", Crypto_ECDsaSecp256k1CheckMultiSig, 0, TriggerType.All, CallFlags.None);

private static bool Crypto_SHA256(ApplicationEngine engine)
{
Expand Down Expand Up @@ -114,6 +102,7 @@ private static bool Crypto_ECDsaCheckMultiSig(ApplicationEngine engine, Cryptogr
for (int i = 0; i < n; i++)
pubkeys[i] = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray();
}
if (!engine.AddGas(VerifyWithECDsaSecp256r1.FixedPrice * n)) return false;
int m;
byte[][] signatures;
item = engine.CurrentContext.EvaluationStack.Pop();
Expand Down
45 changes: 18 additions & 27 deletions src/neo/SmartContract/InteropService.Storage.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Neo.Ledger;
using Neo.Persistence;
using Neo.SmartContract.Iterators;
using Neo.VM;
using Neo.VM.Types;
Expand All @@ -21,46 +20,38 @@ public static class Storage
public static readonly InteropDescriptor AsReadOnly = Register("System.Storage.AsReadOnly", Storage_AsReadOnly, 0_00000400, TriggerType.Application, CallFlags.AllowStates);
public static readonly InteropDescriptor Get = Register("System.Storage.Get", Storage_Get, 0_01000000, TriggerType.Application, CallFlags.AllowStates);
public static readonly InteropDescriptor Find = Register("System.Storage.Find", Storage_Find, 0_01000000, TriggerType.Application, CallFlags.AllowStates);
public static readonly InteropDescriptor Put = Register("System.Storage.Put", Storage_Put, GetStoragePrice, TriggerType.Application, CallFlags.AllowModifyStates);
public static readonly InteropDescriptor PutEx = Register("System.Storage.PutEx", Storage_PutEx, GetStoragePrice, TriggerType.Application, CallFlags.AllowModifyStates);
public static readonly InteropDescriptor Put = Register("System.Storage.Put", Storage_Put, 0, TriggerType.Application, CallFlags.AllowModifyStates);
public static readonly InteropDescriptor PutEx = Register("System.Storage.PutEx", Storage_PutEx, 0, TriggerType.Application, CallFlags.AllowModifyStates);
public static readonly InteropDescriptor Delete = Register("System.Storage.Delete", Storage_Delete, 1 * GasPerByte, TriggerType.Application, CallFlags.AllowModifyStates);

private static long GetStoragePrice(EvaluationStack stack, StoreView snapshot)
{
StorageContext context = ((InteropInterface)stack.Peek(0)).GetInterface<StorageContext>();
ReadOnlySpan<byte> key = stack.Peek(1).GetSpan();
ReadOnlySpan<byte> value = stack.Peek(2).GetSpan();
int newDataSize;
StorageKey skey = new StorageKey
{
Id = context.Id,
Key = key.ToArray()
};
var skeyValue = snapshot.Storages.TryGet(skey);
if (skeyValue is null)
newDataSize = key.Length + value.Length;
else if (value.Length <= skeyValue.Value.Length)
newDataSize = 1;
else
newDataSize = value.Length - skeyValue.Value.Length;
return newDataSize * GasPerByte;
}

private static bool PutExInternal(ApplicationEngine engine, StorageContext context, byte[] key, byte[] value, StorageFlags flags)
{
if (key.Length > MaxKeySize) return false;
if (value.Length > MaxValueSize) return false;
if (context.IsReadOnly) return false;

int newDataSize;
StorageKey skey = new StorageKey
{
Id = context.Id,
Key = key
};
StorageItem item = engine.Snapshot.Storages.GetAndChange(skey);
if (item is null)
{
newDataSize = key.Length + value.Length;
engine.Snapshot.Storages.Add(skey, item = new StorageItem());
}
else
{
if (item.IsConstant) return false;
if (value.Length <= item.Value.Length)
newDataSize = 1;
else
newDataSize = value.Length - item.Value.Length;
}
if (!engine.AddGas(newDataSize * GasPerByte)) return false;

if (engine.Snapshot.Storages.TryGet(skey)?.IsConstant == true) return false;

StorageItem item = engine.Snapshot.Storages.GetAndChange(skey, () => new StorageItem());
item.Value = value;
item.IsConstant = flags.HasFlag(StorageFlags.Constant);

Expand Down
16 changes: 2 additions & 14 deletions src/neo/SmartContract/InteropService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using Neo.Persistence;
using Neo.VM;
using System;
using System.Collections.Generic;
using System.Reflection;
Expand All @@ -16,11 +14,6 @@ static InteropService()
t.GetFields()[0].GetValue(null);
}

public static long GetPrice(uint hash, EvaluationStack stack, StoreView snapshot)
{
return methods[hash].GetPrice(stack, snapshot);
}

public static IEnumerable<InteropDescriptor> SupportedMethods()
{
return methods.Values;
Expand All @@ -35,6 +28,8 @@ internal static bool Invoke(ApplicationEngine engine, uint method)
ExecutionContextState state = engine.CurrentContext.GetState<ExecutionContextState>();
if (!state.CallFlags.HasFlag(descriptor.RequiredCallFlags))
return false;
if (!engine.AddGas(descriptor.FixedPrice))
return false;
return descriptor.Handler(engine);
}

Expand All @@ -44,12 +39,5 @@ private static InteropDescriptor Register(string method, Func<ApplicationEngine,
methods.Add(descriptor.Hash, descriptor);
return descriptor;
}

private static InteropDescriptor Register(string method, Func<ApplicationEngine, bool> handler, Func<EvaluationStack, StoreView, long> priceCalculator, TriggerType allowedTriggers, CallFlags requiredCallFlags)
{
InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers, requiredCallFlags);
methods.Add(descriptor.Hash, descriptor);
return descriptor;
}
}
}
4 changes: 2 additions & 2 deletions src/neo/Wallets/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ public static long CalculateNetworkFee(byte[] witness_script, ref int size)
if (witness_script.IsSignatureContract())
{
size += 67 + witness_script.GetVarSize();
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.VerifyWithECDsaSecp256r1, null, null);
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.Crypto.VerifyWithECDsaSecp256r1.FixedPrice;
}
else if (witness_script.IsMultiSigContract(out int m, out int n))
{
Expand All @@ -368,7 +368,7 @@ public static long CalculateNetworkFee(byte[] witness_script, ref int size)
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * n;
using (ScriptBuilder sb = new ScriptBuilder())
networkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]];
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Crypto.VerifyWithECDsaSecp256r1, null, null) * n;
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.Crypto.VerifyWithECDsaSecp256r1.FixedPrice * n;
}
else
{
Expand Down
2 changes: 1 addition & 1 deletion tests/neo.UnitTests/SmartContract/UT_InteropDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public void TestGetMethod()
TriggerType allowedTriggers = TriggerType.All;
InteropDescriptor descriptor = new InteropDescriptor(method, TestHandler, price, allowedTriggers, CallFlags.None);
descriptor.Method.Should().Be(method);
descriptor.Price.Should().Be(price);
descriptor.FixedPrice.Should().Be(price);
}

private bool TestHandler(ApplicationEngine engine)
Expand Down
Loading

0 comments on commit 1c0a23e

Please sign in to comment.