Skip to content

Commit

Permalink
Refactor InteropService (neo-project#1655)
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 d785e11 commit aa047c3
Show file tree
Hide file tree
Showing 51 changed files with 1,220 additions and 1,860 deletions.
2 changes: 1 addition & 1 deletion src/neo/Ledger/Blockchain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ private static Transaction DeployNativeContracts()
byte[] script;
using (ScriptBuilder sb = new ScriptBuilder())
{
sb.EmitSysCall(InteropService.Native.Deploy);
sb.EmitSysCall(ApplicationEngine.Neo_Native_Deploy);
script = sb.ToArray();
}
return new Transaction
Expand Down
20 changes: 20 additions & 0 deletions src/neo/SmartContract/ApplicationEngine.Binary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Neo.VM.Types;

namespace Neo.SmartContract
{
partial class ApplicationEngine
{
public static readonly InteropDescriptor System_Binary_Serialize = Register("System.Binary.Serialize", nameof(BinarySerialize), 0_00100000, TriggerType.All, CallFlags.None);
public static readonly InteropDescriptor System_Binary_Deserialize = Register("System.Binary.Deserialize", nameof(BinaryDeserialize), 0_00500000, TriggerType.All, CallFlags.None);

internal byte[] BinarySerialize(StackItem item)
{
return BinarySerializer.Serialize(item, MaxItemSize);
}

internal StackItem BinaryDeserialize(byte[] data)
{
return BinarySerializer.Deserialize(data, MaxStackSize, MaxItemSize, ReferenceCounter);
}
}
}
103 changes: 103 additions & 0 deletions src/neo/SmartContract/ApplicationEngine.Blockchain.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using Neo.Ledger;
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
using System;
using System.Numerics;

namespace Neo.SmartContract
{
partial class ApplicationEngine
{
public const uint MaxTraceableBlocks = Transaction.MaxValidUntilBlockIncrement;

public static readonly InteropDescriptor System_Blockchain_GetHeight = Register("System.Blockchain.GetHeight", nameof(GetBlockchainHeight), 0_00000400, TriggerType.Application, CallFlags.AllowStates);
public static readonly InteropDescriptor System_Blockchain_GetBlock = Register("System.Blockchain.GetBlock", nameof(GetBlock), 0_02500000, TriggerType.Application, CallFlags.AllowStates);
public static readonly InteropDescriptor System_Blockchain_GetTransaction = Register("System.Blockchain.GetTransaction", nameof(GetTransaction), 0_01000000, TriggerType.Application, CallFlags.AllowStates);
public static readonly InteropDescriptor System_Blockchain_GetTransactionHeight = Register("System.Blockchain.GetTransactionHeight", nameof(GetTransactionHeight), 0_01000000, TriggerType.Application, CallFlags.AllowStates);
public static readonly InteropDescriptor System_Blockchain_GetTransactionFromBlock = Register("System.Blockchain.GetTransactionFromBlock", nameof(GetTransactionFromBlock), 0_01000000, TriggerType.Application, CallFlags.AllowStates);
public static readonly InteropDescriptor System_Blockchain_GetContract = Register("System.Blockchain.GetContract", nameof(GetContract), 0_01000000, TriggerType.Application, CallFlags.AllowStates);

internal uint GetBlockchainHeight()
{
return Snapshot.Height;
}

internal Block GetBlock(byte[] indexOrHash)
{
UInt256 hash;
if (indexOrHash.Length < UInt256.Length)
{
BigInteger bi = new BigInteger(indexOrHash);
if (bi < uint.MinValue || bi > uint.MaxValue)
throw new ArgumentOutOfRangeException(nameof(indexOrHash));
hash = Blockchain.Singleton.GetBlockHash((uint)bi);
}
else if (indexOrHash.Length == UInt256.Length)
{
hash = new UInt256(indexOrHash);
}
else
{
throw new ArgumentException();
}
if (hash is null) return null;
Block block = Snapshot.GetBlock(hash);
if (block is null) return null;
if (!IsTraceableBlock(Snapshot, block.Index)) return null;
return block;
}

internal Transaction GetTransaction(UInt256 hash)
{
TransactionState state = Snapshot.Transactions.TryGet(hash);
if (state != null && !IsTraceableBlock(Snapshot, state.BlockIndex)) state = null;
return state?.Transaction;
}

internal int GetTransactionHeight(UInt256 hash)
{
TransactionState state = Snapshot.Transactions.TryGet(hash);
if (state is null) return -1;
if (!IsTraceableBlock(Snapshot, state.BlockIndex)) return -1;
return (int)state.BlockIndex;
}

internal Transaction GetTransactionFromBlock(byte[] blockIndexOrHash, int txIndex)
{
UInt256 hash;
if (blockIndexOrHash.Length < UInt256.Length)
{
BigInteger bi = new BigInteger(blockIndexOrHash);
if (bi < uint.MinValue || bi > uint.MaxValue)
throw new ArgumentOutOfRangeException(nameof(blockIndexOrHash));
hash = Blockchain.Singleton.GetBlockHash((uint)bi);
}
else if (blockIndexOrHash.Length == UInt256.Length)
{
hash = new UInt256(blockIndexOrHash);
}
else
{
throw new ArgumentException();
}
if (hash is null) return null;
TrimmedBlock block = Snapshot.Blocks.TryGet(hash);
if (block is null) return null;
if (!IsTraceableBlock(Snapshot, block.Index)) return null;
if (txIndex < 0 || txIndex >= block.Hashes.Length - 1)
throw new ArgumentOutOfRangeException(nameof(txIndex));
return Snapshot.GetTransaction(block.Hashes[txIndex + 1]);
}

internal ContractState GetContract(UInt160 hash)
{
return Snapshot.Contracts.TryGet(hash);
}

private static bool IsTraceableBlock(StoreView snapshot, uint index)
{
if (index > snapshot.Height) return false;
return index + MaxTraceableBlocks > snapshot.Height;
}
}
}
181 changes: 181 additions & 0 deletions src/neo/SmartContract/ApplicationEngine.Contract.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
using Neo.Cryptography.ECC;
using Neo.IO;
using Neo.Ledger;
using Neo.SmartContract.Manifest;
using Neo.SmartContract.Native;
using Neo.VM;
using System;
using System.Linq;
using Array = Neo.VM.Types.Array;

namespace Neo.SmartContract
{
partial class ApplicationEngine
{
public const int MaxContractLength = 1024 * 1024;

public static readonly InteropDescriptor System_Contract_Create = Register("System.Contract.Create", nameof(CreateContract), 0, TriggerType.Application, CallFlags.AllowModifyStates);
public static readonly InteropDescriptor System_Contract_Update = Register("System.Contract.Update", nameof(UpdateContract), 0, TriggerType.Application, CallFlags.AllowModifyStates);
public static readonly InteropDescriptor System_Contract_Destroy = Register("System.Contract.Destroy", nameof(DestroyContract), 0_01000000, TriggerType.Application, CallFlags.AllowModifyStates);
public static readonly InteropDescriptor System_Contract_Call = Register("System.Contract.Call", nameof(CallContract), 0_01000000, TriggerType.System | TriggerType.Application, CallFlags.AllowCall);
public static readonly InteropDescriptor System_Contract_CallEx = Register("System.Contract.CallEx", nameof(CallContractEx), 0_01000000, TriggerType.System | TriggerType.Application, CallFlags.AllowCall);
public static readonly InteropDescriptor System_Contract_IsStandard = Register("System.Contract.IsStandard", nameof(IsStandardContract), 0_00030000, TriggerType.All, CallFlags.None);
public static readonly InteropDescriptor System_Contract_GetCallFlags = Register("System.Contract.GetCallFlags", nameof(GetCallFlags), 0_00030000, TriggerType.All, CallFlags.None);
/// <summary>
/// Calculate corresponding account scripthash for given public key
/// Warning: check first that input public key is valid, before creating the script.
/// </summary>
public static readonly InteropDescriptor System_Contract_CreateStandardAccount = Register("System.Contract.CreateStandardAccount", nameof(CreateStandardAccount), 0_00010000, TriggerType.All, CallFlags.None);

internal ContractState CreateContract(byte[] script, byte[] manifest)
{
if (script.Length == 0 || script.Length > MaxContractLength || manifest.Length == 0 || manifest.Length > ContractManifest.MaxLength)
throw new ArgumentException();

if (!AddGas(StoragePrice * (script.Length + manifest.Length)))
throw new InvalidOperationException();

UInt160 hash = script.ToScriptHash();
ContractState contract = Snapshot.Contracts.TryGet(hash);
if (contract != null) throw new InvalidOperationException();
contract = new ContractState
{
Id = Snapshot.ContractId.GetAndChange().NextId++,
Script = script.ToArray(),
Manifest = ContractManifest.Parse(manifest)
};

if (!contract.Manifest.IsValid(hash)) throw new InvalidOperationException();

Snapshot.Contracts.Add(hash, contract);
return contract;
}

internal void UpdateContract(byte[] script, byte[] manifest)
{
if (!AddGas(StoragePrice * (script?.Length ?? 0 + manifest?.Length ?? 0)))
throw new InvalidOperationException();

var contract = Snapshot.Contracts.TryGet(CurrentScriptHash);
if (contract is null) throw new InvalidOperationException();

if (script != null)
{
if (script.Length == 0 || script.Length > MaxContractLength)
throw new ArgumentException();
UInt160 hash_new = script.ToScriptHash();
if (hash_new.Equals(CurrentScriptHash) || Snapshot.Contracts.TryGet(hash_new) != null)
throw new InvalidOperationException();
contract = new ContractState
{
Id = contract.Id,
Script = script.ToArray(),
Manifest = contract.Manifest
};
contract.Manifest.Abi.Hash = hash_new;
Snapshot.Contracts.Add(hash_new, contract);
Snapshot.Contracts.Delete(CurrentScriptHash);
}
if (manifest != null)
{
if (manifest.Length == 0 || manifest.Length > ContractManifest.MaxLength)
throw new ArgumentException();
contract = Snapshot.Contracts.GetAndChange(contract.ScriptHash);
contract.Manifest = ContractManifest.Parse(manifest);
if (!contract.Manifest.IsValid(contract.ScriptHash))
throw new InvalidOperationException();
if (!contract.HasStorage && Snapshot.Storages.Find(BitConverter.GetBytes(contract.Id)).Any())
throw new InvalidOperationException();
}
}

internal void DestroyContract()
{
UInt160 hash = CurrentScriptHash;
ContractState contract = Snapshot.Contracts.TryGet(hash);
if (contract == null) return;
Snapshot.Contracts.Delete(hash);
if (contract.HasStorage)
foreach (var (key, _) in Snapshot.Storages.Find(BitConverter.GetBytes(contract.Id)))
Snapshot.Storages.Delete(key);
}

internal void CallContract(UInt160 contractHash, string method, Array args)
{
CallContractInternal(contractHash, method, args, CallFlags.All);
}

internal void CallContractEx(UInt160 contractHash, string method, Array args, CallFlags callFlags)
{
if ((callFlags & ~CallFlags.All) != 0)
throw new ArgumentOutOfRangeException(nameof(callFlags));
CallContractInternal(contractHash, method, args, callFlags);
}

private void CallContractInternal(UInt160 contractHash, string method, Array args, CallFlags flags)
{
if (method.StartsWith('_')) throw new ArgumentException();

ContractState contract = Snapshot.Contracts.TryGet(contractHash);
if (contract is null) throw new InvalidOperationException();

ContractManifest currentManifest = Snapshot.Contracts.TryGet(CurrentScriptHash)?.Manifest;

if (currentManifest != null && !currentManifest.CanCall(contract.Manifest, method))
throw new InvalidOperationException();

if (invocationCounter.TryGetValue(contract.ScriptHash, out var counter))
{
invocationCounter[contract.ScriptHash] = counter + 1;
}
else
{
invocationCounter[contract.ScriptHash] = 1;
}

ExecutionContextState state = CurrentContext.GetState<ExecutionContextState>();
UInt160 callingScriptHash = state.ScriptHash;
CallFlags callingFlags = state.CallFlags;

ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod(method);
if (md is null) throw new InvalidOperationException();
int rvcount = md.ReturnType == ContractParameterType.Void ? 0 : 1;
ExecutionContext context_new = LoadScript(contract.Script, rvcount);
state = context_new.GetState<ExecutionContextState>();
state.CallingScriptHash = callingScriptHash;
state.CallFlags = flags & callingFlags;

if (NativeContract.IsNative(contractHash))
{
context_new.EvaluationStack.Push(args);
context_new.EvaluationStack.Push(method);
}
else
{
for (int i = args.Count - 1; i >= 0; i--)
context_new.EvaluationStack.Push(args[i]);
context_new.InstructionPointer = md.Offset;
}

md = contract.Manifest.Abi.GetMethod("_initialize");
if (md != null) LoadClonedContext(md.Offset);
}

internal bool IsStandardContract(UInt160 hash)
{
ContractState contract = Snapshot.Contracts.TryGet(hash);
return contract is null || contract.Script.IsStandardContract();
}

internal CallFlags GetCallFlags()
{
var state = CurrentContext.GetState<ExecutionContextState>();
return state.CallFlags;
}

internal UInt160 CreateStandardAccount(ECPoint pubKey)
{
return Contract.CreateSignatureRedeemScript(pubKey).ToScriptHash();
}
}
}
Loading

0 comments on commit aa047c3

Please sign in to comment.