Skip to content

Commit

Permalink
update invoke script
Browse files Browse the repository at this point in the history
  • Loading branch information
Jim8y committed Sep 13, 2024
1 parent 9fabbf5 commit 656bee9
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 58 deletions.
120 changes: 120 additions & 0 deletions src/Plugins/RpcServer/Model/SignerOrWitness.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// SignerOrWitness.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.Cryptography.ECC;
using Neo.Json;
using Neo.Network.P2P.Payloads;
using Neo.Wallets;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

namespace Neo.Plugins.RpcServer.Model;

public class SignerOrWitness
{
private readonly object _value;

public SignerOrWitness(Signer signer)
{
_value = signer ?? throw new ArgumentNullException(nameof(signer));
}

public SignerOrWitness(Witness witness)
{
_value = witness ?? throw new ArgumentNullException(nameof(witness));
}

public bool IsSigner => _value is Signer;

public static bool TryParse(JToken value, ProtocolSettings settings, [NotNullWhen(true)] out SignerOrWitness? signerOrWitness)

Check warning on line 38 in src/Plugins/RpcServer/Model/SignerOrWitness.cs

View workflow job for this annotation

GitHub Actions / Test-Everything

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 38 in src/Plugins/RpcServer/Model/SignerOrWitness.cs

View workflow job for this annotation

GitHub Actions / Test-Everything

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 38 in src/Plugins/RpcServer/Model/SignerOrWitness.cs

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 38 in src/Plugins/RpcServer/Model/SignerOrWitness.cs

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 38 in src/Plugins/RpcServer/Model/SignerOrWitness.cs

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 38 in src/Plugins/RpcServer/Model/SignerOrWitness.cs

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 38 in src/Plugins/RpcServer/Model/SignerOrWitness.cs

View workflow job for this annotation

GitHub Actions / Test (windows-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 38 in src/Plugins/RpcServer/Model/SignerOrWitness.cs

View workflow job for this annotation

GitHub Actions / Test (windows-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 38 in src/Plugins/RpcServer/Model/SignerOrWitness.cs

View workflow job for this annotation

GitHub Actions / Test (macos-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 38 in src/Plugins/RpcServer/Model/SignerOrWitness.cs

View workflow job for this annotation

GitHub Actions / Test (macos-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
{
signerOrWitness = null;

if (value == null)
return false;

if (value is JObject jObject)
{
if (jObject.ContainsProperty("account"))
{
signerOrWitness = new SignerOrWitness(SignerFromJson(jObject, settings));
return true;
}
else if (jObject.ContainsProperty("invocation") || jObject.ContainsProperty("verification"))
{
signerOrWitness = new SignerOrWitness(WitnessFromJson(jObject));
return true;
}
}

return false;
}

private static Signer SignerFromJson(JObject jObject, ProtocolSettings settings)
{
return new Signer
{
Account = AddressToScriptHash(jObject["account"].AsString(), settings.AddressVersion),
Scopes = jObject.ContainsProperty("scopes")
? (WitnessScope)Enum.Parse(typeof(WitnessScope), jObject["scopes"].AsString())
: WitnessScope.CalledByEntry,
AllowedContracts = ((JArray)jObject["allowedcontracts"])?.Select(p => UInt160.Parse(p.AsString())).ToArray() ?? Array.Empty<UInt160>(),
AllowedGroups = ((JArray)jObject["allowedgroups"])?.Select(p => ECPoint.Parse(p.AsString(), ECCurve.Secp256r1)).ToArray() ?? Array.Empty<ECPoint>(),
Rules = ((JArray)jObject["rules"])?.Select(r => WitnessRule.FromJson((JObject)r)).ToArray() ?? Array.Empty<WitnessRule>()
};
}

private static Witness WitnessFromJson(JObject jObject)
{
return new Witness
{
InvocationScript = Convert.FromBase64String(jObject["invocation"]?.AsString() ?? string.Empty),
VerificationScript = Convert.FromBase64String(jObject["verification"]?.AsString() ?? string.Empty)
};
}

public static SignerOrWitness[] ParseArray(JArray array, ProtocolSettings settings)
{
if (array == null)
throw new ArgumentNullException(nameof(array));

if (array.Count > Transaction.MaxTransactionAttributes)
throw new RpcException(RpcError.InvalidParams.WithData("Max allowed signers or witnesses exceeded."));

return array.Select(item =>
{
if (TryParse(item, settings, out var signerOrWitness))
return signerOrWitness;
throw new ArgumentException($"Invalid signer or witness format: {item}");
}).ToArray();
}

public Signer AsSigner()
{
return _value as Signer ?? throw new InvalidOperationException("The value is not a Signer.");
}

public Witness AsWitness()
{
return _value as Witness ?? throw new InvalidOperationException("The value is not a Witness.");
}

private static UInt160 AddressToScriptHash(string address, byte version)
{
if (UInt160.TryParse(address, out var scriptHash))
{
return scriptHash;
}

return address.ToScriptHash(version);
}
}
52 changes: 7 additions & 45 deletions src/Plugins/RpcServer/RpcServer.SmartContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Neo.Json;
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
using Neo.Plugins.RpcServer.Model;
using Neo.SmartContract;
using Neo.SmartContract.Iterators;
using Neo.SmartContract.Native;
Expand Down Expand Up @@ -171,47 +172,6 @@ private static JObject ToJson(StackItem item, Session session)
return json;
}

private static Signer[] SignersFromJson(JArray _params, ProtocolSettings settings)
{
if (_params.Count > Transaction.MaxTransactionAttributes)
{
throw new RpcException(RpcError.InvalidParams.WithData("Max allowed witness exceeded."));
}

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() ?? Array.Empty<UInt160>(),
AllowedGroups = ((JArray)u["allowedgroups"])?.Select(p => ECPoint.Parse(p.AsString(), ECCurve.Secp256r1)).ToArray() ?? Array.Empty<ECPoint>(),
Rules = ((JArray)u["rules"])?.Select(r => WitnessRule.FromJson((JObject)r)).ToArray() ?? Array.Empty<WitnessRule>(),
}).ToArray();

// Validate format

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

return ret;
}

private static Witness[] WitnessesFromJson(JArray _params)
{
if (_params.Count > Transaction.MaxTransactionAttributes)
{
throw new RpcException(RpcError.InvalidParams.WithData("Max allowed witness exceeded."));
}

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();
}

/// <summary>
/// Invokes a smart contract with its scripthash based on the specified operation and parameters and returns the result.
/// </summary>
Expand Down Expand Up @@ -246,14 +206,16 @@ protected internal virtual JToken InvokeFunction(string scriptHash, string opera
/// You must install the plugin RpcServer before you can invoke the method.
/// </remarks>
/// <param name="scriptBase64">A script runnable in the VM, encoded as Base64. e.g. "AQIDBAUGBwgJCgsMDQ4PEA=="</param>
/// <param name="signers">Optional. The list of contract signature accounts.</param>
/// <param name="witnesses">Optional. The list of witnesses for the transaction.</param>
/// <param name="signerOrWitnesses">Optional. The list of contract signature accounts or witnesses for the transaction.</param>
/// <param name="useDiagnostic">Optional. Flag to enable diagnostic information.</param>
/// <returns>A JToken containing the result of the invocation.</returns>
[RpcMethodWithParams]
protected internal virtual JToken InvokeScript(string scriptBase64, Signer[] signers = null, Witness[] witnesses = null, bool useDiagnostic = false)
protected internal virtual JToken InvokeScript(string scriptBase64, SignerOrWitness[] signerOrWitnesses = null, bool useDiagnostic = false)
{
byte[] script = Result.Ok_Or(() => Convert.FromBase64String(scriptBase64), RpcError.InvalidParams);
var script = Result.Ok_Or(() => Convert.FromBase64String(scriptBase64), RpcError.InvalidParams);
var signers = signerOrWitnesses?.Where(u => u.IsSigner).Select(u => u.AsSigner()).ToArray() ?? [];
var witnesses = signerOrWitnesses?.Where(u => !u.IsSigner).Select(u => u.AsWitness()).ToArray() ?? [];

return GetInvokeResult(script, signers, witnesses, useDiagnostic);
}

Expand Down
5 changes: 3 additions & 2 deletions src/Plugins/RpcServer/RpcServer.Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Neo.Json;
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
using Neo.Plugins.RpcServer.Model;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using Neo.VM;
Expand Down Expand Up @@ -477,8 +478,8 @@ protected internal virtual JToken InvokeContractVerify(JArray _params)
{
UInt160 script_hash = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid script hash: {_params[0]}"));
ContractParameter[] args = _params.Count >= 2 ? ((JArray)_params[1]).Select(p => ContractParameter.FromJson((JObject)p)).ToArray() : Array.Empty<ContractParameter>();
Signer[] signers = _params.Count >= 3 ? SignersFromJson((JArray)_params[2], system.Settings) : null;
Witness[] witnesses = _params.Count >= 3 ? WitnessesFromJson((JArray)_params[2]) : null;
Signer[] signers = _params.Count >= 3 ? SignerOrWitness.ParseArray((JArray)_params[2], system.Settings).Where(u => u.IsSigner).Select(u => u.AsSigner()).ToArray() : null;
Witness[] witnesses = _params.Count >= 3 ? SignerOrWitness.ParseArray((JArray)_params[2], system.Settings).Where(u => !u.IsSigner).Select(u => u.AsWitness()).ToArray() : null;
return GetVerificationResult(script_hash, args, signers, witnesses);
}

Expand Down
21 changes: 11 additions & 10 deletions tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using Neo.Network.P2P.Payloads;
using Neo.Network.P2P.Payloads.Conditions;
using Neo.Persistence;
using Neo.Plugins.RpcServer.Model;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using Neo.UnitTests;
Expand All @@ -45,14 +46,14 @@ public partial class UT_RpcServer
.ToScriptHash();
static readonly string MultisigAddress = MultisigScriptHash.ToAddress(ProtocolSettings.Default.AddressVersion);

static readonly Signer[] validatorSigner = [new()
static readonly SignerOrWitness[] validatorSigner = [new(new Signer
{
Account = ValidatorScriptHash.ToString(),
Scopes = WitnessScope.CalledByEntry,
AllowedContracts = [NeoToken.NEO.Hash, GasToken.GAS.Hash],
AllowedGroups = [TestProtocolSettings.SoleNode.StandbyCommittee[0]],
Rules = [new WitnessRule { Action = WitnessRuleAction.Allow, Condition = new CalledByEntryCondition() }],
}];
})];
static readonly Signer[] multisigSigner = [new()
{
Account = MultisigScriptHash,
Expand All @@ -64,7 +65,7 @@ public void TestInvokeFunction()
{
_rpcServer.wallet = _wallet;

JObject resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "totalSupply", [], validatorSigner, true);
JObject resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "totalSupply", [], validatorSigner.Select(u => u.AsSigner()).ToArray(), true);
Assert.AreEqual(resp.Count, 8);
Assert.AreEqual(resp["script"], NeoTotalSupplyScript);
Assert.IsTrue(resp.ContainsProperty("gasconsumed"));
Expand Down Expand Up @@ -119,7 +120,7 @@ public void TestInvokeFunction()
[TestMethod]
public void TestInvokeScript()
{
JObject resp = (JObject)_rpcServer.InvokeScript(NeoTotalSupplyScript, validatorSigner, [], true);
JObject resp = (JObject)_rpcServer.InvokeScript(NeoTotalSupplyScript, validatorSigner, true);
Assert.AreEqual(resp.Count, 7);
Assert.IsTrue(resp.ContainsProperty("gasconsumed"));
Assert.IsTrue(resp.ContainsProperty("diagnostics"));
Expand All @@ -140,7 +141,7 @@ public void TestInvokeScript()
public void TestTraverseIterator()
{
// GetAllCandidates that should return 0 candidates
JObject resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true);
JObject resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner.Select(u => u.AsSigner()).ToArray(), true);
Guid sessionId = Guid.Parse(resp["session"].AsString());
Guid iteratorId = Guid.Parse(resp["stack"][0]["id"].AsString());
JArray respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100);
Expand All @@ -154,7 +155,7 @@ [new ContractParameter
{
Type = ContractParameterType.PublicKey,
Value = TestProtocolSettings.SoleNode.StandbyCommittee[0],
}], validatorSigner, true);
}], validatorSigner.Select(u => u.AsSigner()).ToArray(), true);
Assert.AreEqual(resp["state"], nameof(VM.VMState.HALT));
SnapshotCache snapshot = _neoSystem.GetSnapshotCache();
Transaction? tx = new Transaction
Expand All @@ -170,7 +171,7 @@ [new ContractParameter
engine.SnapshotCache.Commit();

// GetAllCandidates that should return 1 candidate
resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true);
resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner.Select(u => u.AsSigner()).ToArray(), true);
sessionId = Guid.Parse(resp["session"].AsString());
iteratorId = Guid.Parse(resp["stack"][0]["id"].AsString());
respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100);
Expand All @@ -188,7 +189,7 @@ [new ContractParameter
Assert.AreEqual(respArray.Count, 0);

// GetAllCandidates again
resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true);
resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner.Select(u => u.AsSigner()).ToArray(), true);
sessionId = Guid.Parse(resp["session"].AsString());
iteratorId = Guid.Parse(resp["stack"][0]["id"].AsString());

Expand All @@ -203,7 +204,7 @@ [new ContractParameter
// Mocking session timeout
Thread.Sleep((int)_rpcServerSettings.SessionExpirationTime.TotalMilliseconds + 1);
// build another session that did not expire
resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true);
resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner.Select(u => u.AsSigner()).ToArray(), true);
Guid notExpiredSessionId = Guid.Parse(resp["session"].AsString());
Guid notExpiredIteratorId = Guid.Parse(resp["stack"][0]["id"].AsString());
_rpcServer.OnTimer(new object());
Expand All @@ -214,7 +215,7 @@ [new ContractParameter
Assert.AreEqual(respArray.Count, 1);

// Mocking disposal
resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true);
resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner.Select(u => u.AsSigner()).ToArray(), true);
sessionId = Guid.Parse(resp["session"].AsString());
iteratorId = Guid.Parse(resp["stack"][0]["id"].AsString());
_rpcServer.Dispose_SmartContract();
Expand Down
2 changes: 1 addition & 1 deletion tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ public static void _deploy(object data, bool update) {
new ContractParameter { Type = ContractParameterType.ByteArray, Value = Convert.FromBase64String(base64NefFile) },
new ContractParameter { Type = ContractParameterType.String, Value = manifest },
],
validatorSigner);
validatorSigner.Select(u => u.AsSigner()).ToArray());
Assert.AreEqual(deployResp["state"], nameof(VM.VMState.HALT));
UInt160 deployedScriptHash = new UInt160(Convert.FromBase64String(deployResp["notifications"][0]["state"]["value"][0]["value"].AsString()));
SnapshotCache snapshot = _neoSystem.GetSnapshotCache();
Expand Down

0 comments on commit 656bee9

Please sign in to comment.