From 656bee9dc0f39b23483e089183ce2355f17b6639 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Fri, 13 Sep 2024 12:04:24 +0800 Subject: [PATCH] update invoke script --- .../RpcServer/Model/SignerOrWitness.cs | 120 ++++++++++++++++++ .../RpcServer/RpcServer.SmartContract.cs | 52 +------- src/Plugins/RpcServer/RpcServer.Wallet.cs | 5 +- .../UT_RpcServer.SmartContract.cs | 21 +-- .../UT_RpcServer.Wallet.cs | 2 +- 5 files changed, 142 insertions(+), 58 deletions(-) create mode 100644 src/Plugins/RpcServer/Model/SignerOrWitness.cs diff --git a/src/Plugins/RpcServer/Model/SignerOrWitness.cs b/src/Plugins/RpcServer/Model/SignerOrWitness.cs new file mode 100644 index 0000000000..225291f828 --- /dev/null +++ b/src/Plugins/RpcServer/Model/SignerOrWitness.cs @@ -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) + { + 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(), + AllowedGroups = ((JArray)jObject["allowedgroups"])?.Select(p => ECPoint.Parse(p.AsString(), ECCurve.Secp256r1)).ToArray() ?? Array.Empty(), + Rules = ((JArray)jObject["rules"])?.Select(r => WitnessRule.FromJson((JObject)r)).ToArray() ?? Array.Empty() + }; + } + + 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); + } +} diff --git a/src/Plugins/RpcServer/RpcServer.SmartContract.cs b/src/Plugins/RpcServer/RpcServer.SmartContract.cs index 28a71a2e1e..83dbb4687c 100644 --- a/src/Plugins/RpcServer/RpcServer.SmartContract.cs +++ b/src/Plugins/RpcServer/RpcServer.SmartContract.cs @@ -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; @@ -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(), - AllowedGroups = ((JArray)u["allowedgroups"])?.Select(p => ECPoint.Parse(p.AsString(), ECCurve.Secp256r1)).ToArray() ?? Array.Empty(), - Rules = ((JArray)u["rules"])?.Select(r => WitnessRule.FromJson((JObject)r)).ToArray() ?? Array.Empty(), - }).ToArray(); - - // Validate format - - _ = IO.Helper.ToByteArray(ret).AsSerializableArray(); - - 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(); - } - /// /// Invokes a smart contract with its scripthash based on the specified operation and parameters and returns the result. /// @@ -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. /// /// A script runnable in the VM, encoded as Base64. e.g. "AQIDBAUGBwgJCgsMDQ4PEA==" - /// Optional. The list of contract signature accounts. - /// Optional. The list of witnesses for the transaction. + /// Optional. The list of contract signature accounts or witnesses for the transaction. /// Optional. Flag to enable diagnostic information. /// A JToken containing the result of the invocation. [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); } diff --git a/src/Plugins/RpcServer/RpcServer.Wallet.cs b/src/Plugins/RpcServer/RpcServer.Wallet.cs index 25a7333aac..42874879b6 100644 --- a/src/Plugins/RpcServer/RpcServer.Wallet.cs +++ b/src/Plugins/RpcServer/RpcServer.Wallet.cs @@ -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; @@ -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(); - 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); } diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs index a703ddbd96..4bfbb09264 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs @@ -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; @@ -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, @@ -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")); @@ -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")); @@ -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); @@ -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 @@ -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); @@ -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()); @@ -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()); @@ -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(); diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs index 477934552c..276d02d65c 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs @@ -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();