Skip to content

Commit

Permalink
Storage credit
Browse files Browse the repository at this point in the history
  • Loading branch information
lock9 committed Dec 16, 2019
1 parent a7d40b4 commit cb9ddc4
Show file tree
Hide file tree
Showing 11 changed files with 437 additions and 48 deletions.
6 changes: 6 additions & 0 deletions neo.sln
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B5339DF7-5D1
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{EDE05FA8-8E73-4924-BC63-DD117127EEE1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "neo-vm", "..\neo-vm\src\neo-vm\neo-vm.csproj", "{DE32E710-8AC7-4A63-BC2A-9BB7CBA66677}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -24,6 +26,10 @@ Global
{5B783B30-B422-4C2F-AC22-187A8D1993F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5B783B30-B422-4C2F-AC22-187A8D1993F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5B783B30-B422-4C2F-AC22-187A8D1993F4}.Release|Any CPU.Build.0 = Release|Any CPU
{DE32E710-8AC7-4A63-BC2A-9BB7CBA66677}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DE32E710-8AC7-4A63-BC2A-9BB7CBA66677}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DE32E710-8AC7-4A63-BC2A-9BB7CBA66677}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DE32E710-8AC7-4A63-BC2A-9BB7CBA66677}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
2 changes: 1 addition & 1 deletion src/neo/IO/Caching/DataCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ public TValue GetOrAdd(TKey key, Func<TValue> factory)
}
}

public TValue TryGet(TKey key)
public virtual TValue TryGet(TKey key)
{
lock (dictionary)
{
Expand Down
35 changes: 34 additions & 1 deletion src/neo/SmartContract/ApplicationEngine.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Neo.Ledger;
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
using Neo.SmartContract.Native;
using Neo.VM;
using Neo.VM.Types;
using System;
Expand All @@ -19,6 +20,8 @@ public partial class ApplicationEngine : ExecutionEngine
private readonly bool testMode;
private readonly List<NotifyEventArgs> notifications = new List<NotifyEventArgs>();
private readonly List<IDisposable> disposables = new List<IDisposable>();
private readonly List<byte[]> updatedKeys = new List<byte[]>();
private long maxConsumedGas = 0;

public TriggerType Trigger { get; }
public IVerifiable ScriptContainer { get; }
Expand All @@ -45,12 +48,34 @@ internal T AddDisposable<T>(T disposable) where T : IDisposable
return disposable;
}

internal bool TryAddUpdatedKey(byte[] key)
{
bool keyAdded = false;
if (!updatedKeys.Contains(key))
{
updatedKeys.Add(key);
keyAdded = true;
}
return keyAdded;
}

private bool AddGas(long gas)
{
if (gas < 0 && GasConsumed > maxConsumedGas)
maxConsumedGas = GasConsumed;
GasConsumed = checked(GasConsumed + gas);
return testMode || GasConsumed <= gas_amount;
}

/// <summary>
/// Recalculate the property GasConsumed to use the gas required to run the whole transaction.
/// </summary>
private void RecalculateConsumedGas()
{
if (maxConsumedGas > GasConsumed)
GasConsumed = maxConsumedGas;
}

protected override void LoadContext(ExecutionContext context)
{
// Set default execution context state
Expand All @@ -73,7 +98,7 @@ public override void Dispose()

protected override bool OnSysCall(uint method)
{
if (!AddGas(InteropService.GetPrice(method, CurrentContext.EvaluationStack)))
if (!AddGas(InteropService.GetPrice(method, this)))
return false;
return InteropService.Invoke(this, method);
}
Expand Down Expand Up @@ -106,6 +131,14 @@ private static Block CreateDummyBlock(StoreView snapshot)
};
}

public override VMState Execute()
{
var resultingState = base.Execute();
RecalculateConsumedGas();
return resultingState;
}


public static ApplicationEngine Run(byte[] script, StoreView snapshot,
IVerifiable container = null, Block persistingBlock = null, bool testMode = false, long extraGAS = default)
{
Expand Down
25 changes: 25 additions & 0 deletions src/neo/SmartContract/InteropDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ internal class InteropDescriptor
public Func<ApplicationEngine, bool> Handler { get; }
public long Price { get; }
public Func<EvaluationStack, long> PriceCalculator { get; }
public Func<ApplicationEngine, long> StoragePriceCalculator { get; }
public bool IsStateful { get; }
public TriggerType AllowedTriggers { get; }

public InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, long price, TriggerType allowedTriggers)
Expand All @@ -24,6 +26,13 @@ public InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, F
this.PriceCalculator = priceCalculator;
}

public InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, Func<ApplicationEngine, long> priceCalculator, TriggerType allowedTriggers)
: this(method, handler, allowedTriggers)
{
this.StoragePriceCalculator = priceCalculator;
this.IsStateful = true;
}

private InteropDescriptor(string method, Func<ApplicationEngine, bool> handler, TriggerType allowedTriggers)
{
this.Method = method;
Expand All @@ -32,8 +41,24 @@ private InteropDescriptor(string method, Func<ApplicationEngine, bool> handler,
this.AllowedTriggers = allowedTriggers;
}

public long GetPrice()
{
return Price;
}

public long GetPrice(ApplicationEngine applicationEngine)
{
return StoragePriceCalculator is null ? Price : StoragePriceCalculator(applicationEngine);
}

public long GetPrice(EvaluationStack stack)
{
#if DEBUG
if (IsStateful)
{
throw new InvalidOperationException();
}
#endif
return PriceCalculator is null ? Price : PriceCalculator(stack);
}
}
Expand Down
77 changes: 72 additions & 5 deletions src/neo/SmartContract/InteropService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ namespace Neo.SmartContract
public static partial class InteropService
{
public const long GasPerByte = 100000;
public const long GasPerReusedByte = -GasPerByte;
public const long GasPerReleasedByte = -GasPerByte;
public const int MaxStorageKeySize = 64;
public const int MaxStorageValueSize = ushort.MaxValue;
public const int MaxNotificationSize = 1024;
Expand Down Expand Up @@ -52,7 +54,7 @@ public static partial class InteropService
public static readonly uint System_Storage_Get = Register("System.Storage.Get", Storage_Get, 0_01000000, TriggerType.Application);
public static readonly uint System_Storage_Put = Register("System.Storage.Put", Storage_Put, GetStoragePrice, TriggerType.Application);
public static readonly uint System_Storage_PutEx = Register("System.Storage.PutEx", Storage_PutEx, GetStoragePrice, TriggerType.Application);
public static readonly uint System_Storage_Delete = Register("System.Storage.Delete", Storage_Delete, 0_01000000, TriggerType.Application);
public static readonly uint System_Storage_Delete = Register("System.Storage.Delete", Storage_Delete, GetDeleteStoragePrice, TriggerType.Application);
public static readonly uint System_StorageContext_AsReadOnly = Register("System.StorageContext.AsReadOnly", StorageContext_AsReadOnly, 0_00000400, TriggerType.Application);

private static bool CheckItemForNotification(StackItem state)
Expand Down Expand Up @@ -109,19 +111,73 @@ private static bool CheckStorageContext(ApplicationEngine engine, StorageContext
return true;
}

public static long GetPrice(uint hash)
{
return methods[hash].GetPrice();
}

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

public static long GetPrice(uint hash, ApplicationEngine applicationEngine)
{
var interopDescriptor = methods[hash];
return interopDescriptor.IsStateful ? interopDescriptor.GetPrice(applicationEngine) : interopDescriptor.GetPrice(applicationEngine.CurrentContext.EvaluationStack);
}

public static Dictionary<uint, string> SupportedMethods()
{
return methods.ToDictionary(p => p.Key, p => p.Value.Method);
}

private static long GetStoragePrice(EvaluationStack stack)
private static long GetDeleteStoragePrice(ApplicationEngine engine)
{
return (stack.Peek(1).GetByteLength() + stack.Peek(2).GetByteLength()) * GasPerByte;
var stack = engine.CurrentContext.EvaluationStack;
var key = stack.Peek(1);
StorageKey skey = new StorageKey
{
ScriptHash = engine.CurrentScriptHash,
Key = key.GetSpan().ToArray()
};

var skeyValue = engine.Snapshot.Storages.TryGet(skey);

if (skey == null || !engine.TryAddUpdatedKey(key.GetSpan().ToArray()))
return 0_01000000;

return skeyValue.Value.Length * GasPerReleasedByte;
}

private static long GetStoragePrice(ApplicationEngine engine)
{
var stack = engine.CurrentContext.EvaluationStack;
var key = stack.Peek(1);
var newDataSize = stack.Peek(2).GetByteLength();
StorageKey skey = new StorageKey
{
ScriptHash = engine.CurrentScriptHash,
Key = key.GetSpan().ToArray()
};

var skeyValue = engine.Snapshot.Storages.TryGet(skey);

if (skeyValue == null || !engine.TryAddUpdatedKey(skey.Key))
return (key.GetByteLength() + newDataSize) * GasPerByte;

var currentOccupiedBytes = skeyValue.Value.Length;

if (newDataSize <= currentOccupiedBytes)
{
var releasedBytes = currentOccupiedBytes - newDataSize;
return releasedBytes * GasPerReleasedByte;
}
else
{
var reusedBytes = currentOccupiedBytes;
return (newDataSize - reusedBytes) * GasPerByte;
}
}

internal static bool Invoke(ApplicationEngine engine, uint method)
Expand All @@ -147,6 +203,13 @@ private static uint Register(string method, Func<ApplicationEngine, bool> handle
return descriptor.Hash;
}

private static uint Register(string method, Func<ApplicationEngine, bool> handler, Func<ApplicationEngine, long> priceCalculator, TriggerType allowedTriggers)
{
InteropDescriptor descriptor = new InteropDescriptor(method, handler, priceCalculator, allowedTriggers);
methods.Add(descriptor.Hash, descriptor);
return descriptor.Hash;
}

private static bool ExecutionEngine_GetScriptContainer(ApplicationEngine engine)
{
engine.CurrentContext.EvaluationStack.Push(
Expand Down Expand Up @@ -515,7 +578,8 @@ private static bool PutEx(ApplicationEngine engine, StorageContext context, byte
Key = key
};

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

if (value.Length == 0 && !flags.HasFlag(StorageFlags.Constant))
{
Expand All @@ -528,6 +592,7 @@ private static bool PutEx(ApplicationEngine engine, StorageContext context, byte
item.Value = value;
item.IsConstant = flags.HasFlag(StorageFlags.Constant);
}

return true;
}

Expand Down Expand Up @@ -564,7 +629,9 @@ private static bool Storage_Delete(ApplicationEngine engine)
ScriptHash = context.ScriptHash,
Key = engine.CurrentContext.EvaluationStack.Pop().GetSpan().ToArray()
};
if (engine.Snapshot.Storages.TryGet(key)?.IsConstant == true) return false;

var value = engine.Snapshot.Storages.TryGet(key);
if (value?.IsConstant == true) return false;
engine.Snapshot.Storages.Delete(key);
return true;
}
Expand Down
12 changes: 6 additions & 6 deletions src/neo/Wallets/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -353,20 +353,20 @@ public static long CalculateNetWorkFee(byte[] witness_script, ref int size)

if (witness_script.IsSignatureContract())
{
size += 66 + witness_script.GetVarSize();
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] + ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Neo_Crypto_ECDsaVerify, null);
size += 67 + witness_script.GetVarSize();
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Neo_Crypto_ECDsaVerify);
}
else if (witness_script.IsMultiSigContract(out int m, out int n))
{
int size_inv = 65 * m;
int size_inv = 66 * m;
size += IO.Helper.GetVarSize(size_inv) + size_inv + witness_script.GetVarSize();
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] * m;
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * m;
using (ScriptBuilder sb = new ScriptBuilder())
networkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(m).ToArray()[0]];
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] * n;
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.Neo_Crypto_ECDsaVerify, null) * n;
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + InteropService.GetPrice(InteropService.Neo_Crypto_ECDsaVerify) * n;
}
else
{
Expand Down
5 changes: 4 additions & 1 deletion src/neo/neo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.1" />
<PackageReference Include="Neo.VM" Version="3.0.0-CI00176" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\neo-vm\src\neo-vm\neo-vm.csproj" />
</ItemGroup>

</Project>
18 changes: 18 additions & 0 deletions tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using Neo.VM;
using Neo.VM.Types;
using System;

Expand Down Expand Up @@ -74,6 +76,22 @@ public void TestNotify()
item.Should().Be(null);
}

private StorageContext InitiateEngineStorage(SnapshotView snapshot, ApplicationEngine engine)
{
var contract = TestUtils.GetContract();
contract.Manifest.Features = Neo.SmartContract.Manifest.ContractFeatures.HasStorage;

var storageContext = new StorageContext
{
ScriptHash = contract.ScriptHash,
IsReadOnly = false
};
snapshot.Contracts.Add(contract.ScriptHash, contract);
engine.LoadScript(new byte[] { 0x01 });

return storageContext;
}

[TestMethod]
public void TestDisposable()
{
Expand Down
Loading

0 comments on commit cb9ddc4

Please sign in to comment.