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

Allow traverse iterators #715

Merged
merged 13 commits into from
Jun 24, 2022
235 changes: 136 additions & 99 deletions src/RpcServer/RpcServer.SmartContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,97 +20,105 @@
using Neo.VM.Types;
using Neo.Wallets;
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace Neo.Plugins
{
partial class RpcServer
{
private class Signers : IVerifiable
{
private readonly Signer[] _signers;
public Witness[] Witnesses { get; set; }
public int Size => _signers.Length;

public Signers(Signer[] signers)
{
_signers = signers;
}

public void Serialize(BinaryWriter writer)
{
throw new NotImplementedException();
}
private readonly Dictionary<Guid, Session> sessions = new();
private Timer timer;

public void Deserialize(ref MemoryReader reader)
{
throw new NotImplementedException();
}

public void DeserializeUnsigned(ref MemoryReader reader)
{
throw new NotImplementedException();
}

public UInt160[] GetScriptHashesForVerifying(DataCache snapshot)
{
return _signers.Select(p => p.Account).ToArray();
}
private void Initialize_SmartContract()
{
timer = new(OnTimer, null, settings.SessionExpirationTime, settings.SessionExpirationTime);
}

public Signer[] GetSigners()
private void Dispose_SmartContract()
{
timer.Dispose();
Session[] toBeDestroyed;
lock (sessions)
{
return _signers;
toBeDestroyed = sessions.Values.ToArray();
sessions.Clear();
}
foreach (Session session in toBeDestroyed)
session.Dispose();
}

public void SerializeUnsigned(BinaryWriter writer)
private void OnTimer(object state)
{
List<(Guid Id, Session Session)> toBeDestroyed = new();
lock (sessions)
{
throw new NotImplementedException();
foreach (var (id, session) in sessions)
if (DateTime.UtcNow >= session.StartTime + settings.SessionExpirationTime)
toBeDestroyed.Add((id, session));
foreach (var (id, _) in toBeDestroyed)
sessions.Remove(id);
}
foreach (var (_, session) in toBeDestroyed)
session.Dispose();
}

private JObject GetInvokeResult(byte[] script, Signers signers = null, bool useDiagnostic = false)
private JObject GetInvokeResult(byte[] script, Signer[] signers = null, Witness[] witnesses = null, bool useDiagnostic = false)
{
Transaction tx = signers == null ? null : new Transaction
{
Signers = signers.GetSigners(),
Attributes = System.Array.Empty<TransactionAttribute>(),
Witnesses = signers.Witnesses,
};
using ApplicationEngine engine = ApplicationEngine.Run(script, system.StoreView, container: tx, settings: system.Settings, gas: settings.MaxGasInvoke, diagnostic: useDiagnostic ? new Diagnostic() : null);
JObject json = new();
json["script"] = Convert.ToBase64String(script);
json["state"] = engine.State;
json["gasconsumed"] = engine.GasConsumed.ToString();
json["exception"] = GetExceptionMessage(engine.FaultException);
json["notifications"] = new JArray(engine.Notifications.Select(n =>
{
var obj = new JObject();
obj["eventname"] = n.EventName;
obj["contract"] = n.ScriptHash.ToString();
obj["state"] = ToJson(n.State, settings.MaxIteratorResultItems);
return obj;
}));
if (useDiagnostic)
{
Diagnostic diagnostic = (Diagnostic)engine.Diagnostic;
json["diagnostics"] = new JObject()
Session session = new(system, script, signers, witnesses, settings.MaxGasInvoke, useDiagnostic ? new Diagnostic() : null);
shargon marked this conversation as resolved.
Show resolved Hide resolved
try
{
json["script"] = Convert.ToBase64String(script);
json["state"] = session.Engine.State;
json["gasconsumed"] = session.Engine.GasConsumed.ToString();
json["exception"] = GetExceptionMessage(session.Engine.FaultException);
json["notifications"] = new JArray(session.Engine.Notifications.Select(n =>
{
["invokedcontracts"] = ToJson(diagnostic.InvocationTree.Root),
["storagechanges"] = ToJson(engine.Snapshot.GetChangeSet())
};
var obj = new JObject();
obj["eventname"] = n.EventName;
obj["contract"] = n.ScriptHash.ToString();
obj["state"] = ToJson(n.State, session);
return obj;
}));
if (useDiagnostic)
{
Diagnostic diagnostic = (Diagnostic)session.Engine.Diagnostic;
json["diagnostics"] = new JObject()
{
["invokedcontracts"] = ToJson(diagnostic.InvocationTree.Root),
["storagechanges"] = ToJson(session.Engine.Snapshot.GetChangeSet())
};
}
try
{
json["stack"] = new JArray(session.Engine.ResultStack.Select(p => ToJson(p, session)));
}
catch (InvalidOperationException)
{
json["stack"] = "error: invalid operation";
}
if (session.Engine.State != VMState.FAULT)
{
ProcessInvokeWithWallet(json, signers);
}
}
try
catch
{
json["stack"] = new JArray(engine.ResultStack.Select(p => ToJson(p, settings.MaxIteratorResultItems)));
session.Dispose();
throw;
}
catch (InvalidOperationException)
if (session.Iterators.Count == 0)
{
json["stack"] = "error: invalid operation";
session.Dispose();
}
if (engine.State != VMState.FAULT)
else
{
ProcessInvokeWithWallet(json, signers);
Guid id = Guid.NewGuid();
json["session"] = id.ToString();
lock (sessions)
sessions.Add(id, session);
}
return json;
}
Expand All @@ -126,7 +134,7 @@ private static JObject ToJson(TreeNode<UInt160> node)
return json;
}

private static JObject ToJson(System.Collections.Generic.IEnumerable<DataCache.Trackable> changes)
private static JObject ToJson(IEnumerable<DataCache.Trackable> changes)
{
JArray array = new();
foreach (var entry in changes)
Expand All @@ -141,79 +149,108 @@ private static JObject ToJson(System.Collections.Generic.IEnumerable<DataCache.T
return array;
}

private static JObject ToJson(StackItem item, int max)
private static JObject ToJson(StackItem item, Session session)
erikzhang marked this conversation as resolved.
Show resolved Hide resolved
{
JObject json = item.ToJson();
if (item is InteropInterface interopInterface && interopInterface.GetInterface<object>() is IIterator iterator)
{
JArray array = new();
while (max > 0 && iterator.Next())
{
array.Add(iterator.Value(null).ToJson());
max--;
}
json["iterator"] = array;
json["truncated"] = iterator.Next();
Guid id = Guid.NewGuid();
session.Iterators.Add(id, iterator);
json["id"] = id.ToString();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resulting JSON contains id for each iterator even if sessions are disabled. Is it the desired behaviour?

}
return json;
}

private static Signers SignersFromJson(JArray _params, ProtocolSettings settings)
private static Signer[] SignersFromJson(JArray _params, ProtocolSettings settings)
{
var ret = new Signers(_params.Select(u => new Signer()
var ret = _params.Select(u => new Signer
{
Account = AddressToScriptHash(u["account"].AsString(), settings.AddressVersion),
Scopes = (WitnessScope)Enum.Parse(typeof(WitnessScope), u["scopes"]?.AsString()),
AllowedContracts = ((JArray)u["allowedcontracts"])?.Select(p => UInt160.Parse(p.AsString())).ToArray(),
AllowedGroups = ((JArray)u["allowedgroups"])?.Select(p => ECPoint.Parse(p.AsString(), ECCurve.Secp256r1)).ToArray(),
Rules = ((JArray)u["rules"])?.Select(WitnessRule.FromJson).ToArray(),
}).ToArray())
{
Witnesses = _params
.Select(u => new
{
Invocation = u["invocation"]?.AsString(),
Verification = u["verification"]?.AsString()
})
.Where(x => x.Invocation != null || x.Verification != null)
.Select(x => new Witness()
{
InvocationScript = Convert.FromBase64String(x.Invocation ?? string.Empty),
VerificationScript = Convert.FromBase64String(x.Verification ?? string.Empty)
}).ToArray()
};
}).ToArray();

// Validate format

_ = IO.Helper.ToByteArray(ret.GetSigners()).AsSerializableArray<Signer>();
_ = IO.Helper.ToByteArray(ret).AsSerializableArray<Signer>();

return ret;
}

private static Witness[] WitnessesFromJson(JArray _params)
{
return _params.Select(u => new
{
Invocation = u["invocation"]?.AsString(),
Verification = u["verification"]?.AsString()
}).Where(x => x.Invocation != null || x.Verification != null).Select(x => new Witness()
{
InvocationScript = Convert.FromBase64String(x.Invocation ?? string.Empty),
VerificationScript = Convert.FromBase64String(x.Verification ?? string.Empty)
}).ToArray();
}

[RpcMethod]
protected virtual JObject InvokeFunction(JArray _params)
{
UInt160 script_hash = UInt160.Parse(_params[0].AsString());
string operation = _params[1].AsString();
ContractParameter[] args = _params.Count >= 3 ? ((JArray)_params[2]).Select(p => ContractParameter.FromJson(p)).ToArray() : System.Array.Empty<ContractParameter>();
Signers signers = _params.Count >= 4 ? SignersFromJson((JArray)_params[3], system.Settings) : null;
Signer[] signers = _params.Count >= 4 ? SignersFromJson((JArray)_params[3], system.Settings) : null;
Witness[] witnesses = _params.Count >= 4 ? WitnessesFromJson((JArray)_params[3]) : null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Saved in the same _params as signers? (_params[3])

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

bool useDiagnostic = _params.Count >= 5 && _params[4].GetBoolean();

byte[] script;
using (ScriptBuilder sb = new())
{
script = sb.EmitDynamicCall(script_hash, operation, args).ToArray();
}
return GetInvokeResult(script, signers, useDiagnostic);
return GetInvokeResult(script, signers, witnesses, useDiagnostic);
}

[RpcMethod]
protected virtual JObject InvokeScript(JArray _params)
{
byte[] script = Convert.FromBase64String(_params[0].AsString());
Signers signers = _params.Count >= 2 ? SignersFromJson((JArray)_params[1], system.Settings) : null;
Signer[] signers = _params.Count >= 2 ? SignersFromJson((JArray)_params[1], system.Settings) : null;
Witness[] witnesses = _params.Count >= 2 ? WitnessesFromJson((JArray)_params[1]) : null;
bool useDiagnostic = _params.Count >= 3 && _params[2].GetBoolean();
return GetInvokeResult(script, signers, useDiagnostic);
return GetInvokeResult(script, signers, witnesses, useDiagnostic);
}

[RpcMethod]
protected virtual JObject TraverseIterator(JArray _params)
{
Guid sid = Guid.Parse(_params[0].GetString());
Guid iid = Guid.Parse(_params[1].GetString());
int count = _params[2].GetInt32();
if (count > settings.MaxIteratorResultItems)
throw new ArgumentOutOfRangeException(nameof(count));
Session session;
lock (sessions)
{
session = sessions[sid];
session.ResetExpiration();
shargon marked this conversation as resolved.
Show resolved Hide resolved
}
IIterator iterator = session.Iterators[iid];
JArray json = new();
while (count-- > 0 && iterator.Next())
json.Add(iterator.Value(null).ToJson());
return json;
}

[RpcMethod]
protected virtual JObject TerminateSession(JArray _params)
{
Guid sid = Guid.Parse(_params[0].GetString());
Session session;
bool result;
lock (sessions)
result = sessions.Remove(sid, out session);
if (result) session.Dispose();
return result;
}

[RpcMethod]
Expand Down
22 changes: 10 additions & 12 deletions src/RpcServer/RpcServer.Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,18 +159,15 @@ protected virtual JObject OpenWallet(JArray _params)
return true;
}

private void ProcessInvokeWithWallet(JObject result, Signers signers = null)
private void ProcessInvokeWithWallet(JObject result, Signer[] signers = null)
{
if (wallet == null || signers == null) return;

Signer[] witnessSigners = signers.GetSigners().ToArray();
UInt160 sender = signers.Size > 0 ? signers.GetSigners()[0].Account : null;
if (witnessSigners.Length <= 0) return;
if (wallet == null || signers == null || signers.Length == 0) return;

UInt160 sender = signers[0].Account;
Transaction tx;
try
{
tx = wallet.MakeTransaction(system.StoreView, Convert.FromBase64String(result["script"].AsString()), sender, witnessSigners, maxGas: settings.MaxGasInvoke);
tx = wallet.MakeTransaction(system.StoreView, Convert.FromBase64String(result["script"].AsString()), sender, signers, maxGas: settings.MaxGasInvoke);
}
catch (Exception e)
{
Expand Down Expand Up @@ -327,11 +324,12 @@ protected virtual JObject InvokeContractVerify(JArray _params)
{
UInt160 script_hash = UInt160.Parse(_params[0].AsString());
ContractParameter[] args = _params.Count >= 2 ? ((JArray)_params[1]).Select(p => ContractParameter.FromJson(p)).ToArray() : Array.Empty<ContractParameter>();
Signers signers = _params.Count >= 3 ? SignersFromJson((JArray)_params[2], system.Settings) : null;
return GetVerificationResult(script_hash, args, signers);
Signer[] signers = _params.Count >= 3 ? SignersFromJson((JArray)_params[2], system.Settings) : null;
Witness[] witnesses = _params.Count >= 3 ? WitnessesFromJson((JArray)_params[2]) : null;
return GetVerificationResult(script_hash, args, signers, witnesses);
}

private JObject GetVerificationResult(UInt160 scriptHash, ContractParameter[] args, Signers signers = null)
private JObject GetVerificationResult(UInt160 scriptHash, ContractParameter[] args, Signer[] signers = null, Witness[] witnesses = null)
{
using var snapshot = system.GetSnapshot();
var contract = NativeContract.ContractManagement.GetContract(snapshot, scriptHash);
Expand All @@ -347,9 +345,9 @@ private JObject GetVerificationResult(UInt160 scriptHash, ContractParameter[] ar

Transaction tx = new()
{
Signers = signers == null ? new Signer[] { new() { Account = scriptHash } } : signers.GetSigners(),
Signers = signers ?? new Signer[] { new() { Account = scriptHash } },
Attributes = Array.Empty<TransactionAttribute>(),
Witnesses = signers?.Witnesses,
Witnesses = witnesses,
Script = new[] { (byte)OpCode.RET }
};
using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CreateSnapshot(), settings: system.Settings);
Expand Down
4 changes: 3 additions & 1 deletion src/RpcServer/RpcServer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2015-2021 The Neo Project.
// Copyright (C) 2015-2022 The Neo Project.
//
// The Neo.Network.RPC is free software distributed under the MIT software license,
// see the accompanying file LICENSE in the main directory of the
Expand Down Expand Up @@ -46,6 +46,7 @@ public RpcServer(NeoSystem system, RpcServerSettings settings)
this.settings = settings;
localNode = system.LocalNode.Ask<LocalNode>(new LocalNode.GetInstance()).Result;
RegisterMethods(this);
Initialize_SmartContract();
}

private bool CheckAuth(HttpContext context)
Expand Down Expand Up @@ -93,6 +94,7 @@ private static JObject CreateResponse(JObject id)

public void Dispose()
{
Dispose_SmartContract();
if (host != null)
{
host.Dispose();
Expand Down
Loading