From 380c1ac255ea740a09308505264b61237958cba5 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Wed, 11 Sep 2024 23:49:04 +0800 Subject: [PATCH 1/8] fix contact rpc methods parameters --- src/Plugins/RpcServer/ParameterConverter.cs | 83 ++++++++++++- .../RpcServer/RpcServer.SmartContract.cs | 117 ++++++++++++------ .../UT_RpcServer.SmartContract.cs | 84 ++++++------- .../UT_RpcServer.Wallet.cs | 33 +++-- 4 files changed, 225 insertions(+), 92 deletions(-) diff --git a/src/Plugins/RpcServer/ParameterConverter.cs b/src/Plugins/RpcServer/ParameterConverter.cs index 7d9b754f7c..3d950a0a16 100644 --- a/src/Plugins/RpcServer/ParameterConverter.cs +++ b/src/Plugins/RpcServer/ParameterConverter.cs @@ -10,10 +10,13 @@ // modifications are permitted. using Neo.Json; +using Neo.Network.P2P.Payloads; using Neo.Plugins.RpcServer.Model; +using Neo.SmartContract; using Neo.Wallets; using System; using System.Collections.Generic; +using System.Linq; using JToken = Neo.Json.JToken; namespace Neo.Plugins.RpcServer; @@ -39,7 +42,12 @@ static ParameterConverter() { typeof(bool), token => Result.Ok_Or(token.AsBoolean, CreateInvalidParamError(token)) }, { typeof(UInt256), ConvertUInt256 }, { typeof(ContractNameOrHashOrId), ConvertContractNameOrHashOrId }, - { typeof(BlockHashOrIndex), ConvertBlockHashOrIndex } + { typeof(BlockHashOrIndex), ConvertBlockHashOrIndex }, + { typeof(Signer), ConvertSigner }, + { typeof(ContractParameter), ConvertContractParameter }, + { typeof(Signer[]), ConvertSignerArray }, + { typeof(ContractParameter[]), ConvertContractParameterArray }, + { typeof(Guid), ConvertGuid } }; } @@ -134,6 +142,79 @@ private static object ConvertBlockHashOrIndex(JToken token) throw new RpcException(RpcError.InvalidParams.WithData($"Invalid block hash or index Format: {token}")); } + private static object ConvertSigner(JToken token) + { + if (token is JObject jObject) + { + try + { + return Signer.FromJson(jObject); + } + catch (FormatException) + { + throw new RpcException(CreateInvalidParamError(token)); + } + } + throw new RpcException(CreateInvalidParamError(token)); + } + + private static object ConvertContractParameter(JToken token) + { + if (token is JObject jObject) + { + try + { + return ContractParameter.FromJson(jObject); + } + catch (FormatException) + { + throw new RpcException(CreateInvalidParamError(token)); + } + } + throw new RpcException(CreateInvalidParamError(token)); + } + + private static object ConvertSignerArray(JToken token) + { + if (token is JArray jArray) + { + try + { + return jArray.Select(t => Signer.FromJson(t as JObject)).ToArray(); + } + catch (FormatException) + { + throw new RpcException(CreateInvalidParamError(token)); + } + } + throw new RpcException(CreateInvalidParamError(token)); + } + + private static object ConvertContractParameterArray(JToken token) + { + if (token is JArray jArray) + { + try + { + return jArray.Select(t => ContractParameter.FromJson(t as JObject)).ToArray(); + } + catch (FormatException) + { + throw new RpcException(CreateInvalidParamError(token)); + } + } + throw new RpcException(CreateInvalidParamError(token)); + } + + private static object ConvertGuid(JToken token) + { + if (Guid.TryParse(token.AsString(), out var guid)) + { + return guid; + } + throw new RpcException(CreateInvalidParamError(token)); + } + private static RpcError CreateInvalidParamError(JToken token) { return RpcError.InvalidParams.WithData($"Invalid {typeof(T)} value: {token}"); diff --git a/src/Plugins/RpcServer/RpcServer.SmartContract.cs b/src/Plugins/RpcServer/RpcServer.SmartContract.cs index a3de939f4b..bf3dd332f7 100644 --- a/src/Plugins/RpcServer/RpcServer.SmartContract.cs +++ b/src/Plugins/RpcServer/RpcServer.SmartContract.cs @@ -212,81 +212,124 @@ private static Witness[] WitnessesFromJson(JArray _params) }).ToArray(); } - [RpcMethod] - protected internal virtual JToken InvokeFunction(JArray _params) + /// + /// Invokes a smart contract with its scripthash based on the specified operation and parameters and returns the result. + /// + /// + /// This method is used to test your VM script as if they ran on the blockchain at that point in time. + /// This RPC call does not affect the blockchain in any way. + /// + /// Smart contract scripthash. Use big endian for Hash160, little endian for ByteArray. + /// The operation name (string) + /// Optional. The parameters to be passed into the smart contract operation + /// Optional. List of contract signature accounts. + /// Optional. Flag to enable diagnostic information. + /// A JToken containing the result of the invocation. + [RpcMethodWithParams] + protected internal virtual JToken InvokeFunction(string scriptHash, string operation, ContractParameter[] args = null, Signer[] signers = null, bool useDiagnostic = false) { - UInt160 script_hash = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid script hash {nameof(script_hash)}")); - string operation = Result.Ok_Or(() => _params[1].AsString(), RpcError.InvalidParams); - ContractParameter[] args = _params.Count >= 3 ? ((JArray)_params[2]).Select(p => ContractParameter.FromJson((JObject)p)).ToArray() : System.Array.Empty(); - Signer[] signers = _params.Count >= 4 ? SignersFromJson((JArray)_params[3], system.Settings) : null; - Witness[] witnesses = _params.Count >= 4 ? WitnessesFromJson((JArray)_params[3]) : null; - bool useDiagnostic = _params.Count >= 5 && _params[4].GetBoolean(); - + UInt160 contractHash = Result.Ok_Or(() => UInt160.Parse(scriptHash), RpcError.InvalidParams); byte[] script; using (ScriptBuilder sb = new()) { - script = sb.EmitDynamicCall(script_hash, operation, args).ToArray(); + script = sb.EmitDynamicCall(contractHash, operation, args ?? Array.Empty()).ToArray(); } - return GetInvokeResult(script, signers, witnesses, useDiagnostic); + return GetInvokeResult(script, signers, [], useDiagnostic); } - [RpcMethod] - protected internal virtual JToken InvokeScript(JArray _params) + /// + /// Returns the result after passing a script through the VM. + /// + /// + /// This method is to test your VM script as if they ran on the blockchain at that point in time. + /// This RPC call does not affect the blockchain in any way. + /// 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. 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) { - byte[] script = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()), RpcError.InvalidParams); - 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(); + byte[] script = Result.Ok_Or(() => Convert.FromBase64String(scriptBase64), RpcError.InvalidParams); return GetInvokeResult(script, signers, witnesses, useDiagnostic); } - [RpcMethod] - protected internal virtual JToken TraverseIterator(JArray _params) + /// + /// Gets the Iterator value from session and Iterator id returned by invokefunction or invokescript. + /// + /// + /// + /// This method queries Iterator type data and does not affect the blockchain data. + /// You must install the plugin RpcServer before you can invoke the method. + /// Before you can use the method, make sure that the SessionEnabled value in config.json of the plugin RpcServer is true, + /// and you have obtained Iterator id and session by invoking invokefunction or invokescript. + /// + /// + /// The validity of the session and iterator id is set by SessionExpirationTime in the config.json file of the RpcServer plug-in, in seconds. + /// + /// + /// Cache id. It is session returned by invokefunction or invokescript. e.g. "c5b628b6-10d9-4cc5-b850-3cfc0b659fcf" + /// Iterator data id. It is the id of stack returned by invokefunction or invokescript. e.g. "593b02c6-138d-4945-846d-1e5974091daa" + /// The number of values returned. It cannot exceed the value of the MaxIteratorResultItems field in config.json of the RpcServer plug-in. + /// A JToken containing the iterator values. + [RpcMethodWithParams] + protected internal virtual JToken TraverseIterator(string session, string iteratorId, int count) { settings.SessionEnabled.True_Or(RpcError.SessionsDisabled); - Guid sid = Result.Ok_Or(() => Guid.Parse(_params[0].GetString()), RpcError.InvalidParams.WithData($"Invalid session id {nameof(sid)}")); - Guid iid = Result.Ok_Or(() => Guid.Parse(_params[1].GetString()), RpcError.InvalidParams.WithData($"Invliad iterator id {nameof(iid)}")); - int count = _params[2].GetInt32(); - Result.True_Or(() => count <= settings.MaxIteratorResultItems, RpcError.InvalidParams.WithData($"Invalid iterator items count {nameof(count)}")); - Session session; + Guid sid = Result.Ok_Or(() => Guid.Parse(session), RpcError.InvalidParams.WithData($"Invalid session id")); + Guid iid = Result.Ok_Or(() => Guid.Parse(iteratorId), RpcError.InvalidParams.WithData($"Invalid iterator id")); + Result.True_Or(() => count <= settings.MaxIteratorResultItems, RpcError.InvalidParams.WithData($"Invalid iterator items count: {count}")); + + Session currentSession; lock (sessions) { - session = Result.Ok_Or(() => sessions[sid], RpcError.UnknownSession); - session.ResetExpiration(); + currentSession = Result.Ok_Or(() => sessions[sid], RpcError.UnknownSession); + currentSession.ResetExpiration(); } - IIterator iterator = Result.Ok_Or(() => session.Iterators[iid], RpcError.UnknownIterator); + IIterator iterator = Result.Ok_Or(() => currentSession.Iterators[iid], RpcError.UnknownIterator); JArray json = new(); while (count-- > 0 && iterator.Next()) json.Add(iterator.Value(null).ToJson()); return json; } - [RpcMethod] - protected internal virtual JToken TerminateSession(JArray _params) + /// + /// Terminates a session with the specified GUID. + /// + /// The GUID of the session to terminate. e.g. "00000000-0000-0000-0000-000000000000" + /// A JToken indicating whether the session was successfully terminated. + [RpcMethodWithParams] + protected internal virtual JToken TerminateSession(Guid guid) { settings.SessionEnabled.True_Or(RpcError.SessionsDisabled); - Guid sid = Result.Ok_Or(() => Guid.Parse(_params[0].GetString()), RpcError.InvalidParams.WithData("Invalid session id")); Session session = null; bool result; lock (sessions) { - result = Result.Ok_Or(() => sessions.Remove(sid, out session), RpcError.UnknownSession); + result = Result.Ok_Or(() => sessions.Remove(guid, out session), RpcError.UnknownSession); } if (result) session.Dispose(); return result; } - [RpcMethod] - protected internal virtual JToken GetUnclaimedGas(JArray _params) + /// + /// Gets the unclaimed GAS for the specified address. + /// + /// The account to check for unclaimed GAS. e.g. "NQ5D43HX4QBXZ3XZ4QBXZ3XZ4QBXZ3XZ" + /// A JToken containing the unclaimed GAS amount and the address. + [RpcMethodWithParams] + protected internal virtual JToken GetUnclaimedGas(string account) { - string address = Result.Ok_Or(() => _params[0].AsString(), RpcError.InvalidParams.WithData($"Invalid address {nameof(address)}")); JObject json = new(); - UInt160 script_hash = Result.Ok_Or(() => AddressToScriptHash(address, system.Settings.AddressVersion), RpcError.InvalidParams); + var scriptHash = Result.Ok_Or(() => AddressToScriptHash(account, system.Settings.AddressVersion), RpcError.InvalidParams) ?? throw new ArgumentNullException("Result.Ok_Or(() => AddressToScriptHash(account, system.Settings.AddressVersion), RpcError.InvalidParams)"); var snapshot = system.StoreView; - json["unclaimed"] = NativeContract.NEO.UnclaimedGas(snapshot, script_hash, NativeContract.Ledger.CurrentIndex(snapshot) + 1).ToString(); - json["address"] = script_hash.ToAddress(system.Settings.AddressVersion); + json["unclaimed"] = NativeContract.NEO.UnclaimedGas(snapshot, scriptHash, NativeContract.Ledger.CurrentIndex(snapshot) + 1).ToString(); + json["address"] = scriptHash.ToAddress(system.Settings.AddressVersion); return json; } diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs index 9a8b87da9a..b09c9514e0 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs @@ -45,18 +45,18 @@ public partial class UT_RpcServer .ToScriptHash(); static readonly string MultisigAddress = MultisigScriptHash.ToAddress(ProtocolSettings.Default.AddressVersion); - static readonly JArray validatorSigner = [new JObject() + static readonly Signer[] validatorSigner = [new() { - ["account"] = ValidatorScriptHash.ToString(), - ["scopes"] = nameof(WitnessScope.CalledByEntry), - ["allowedcontracts"] = new JArray([NeoToken.NEO.Hash.ToString(), GasToken.GAS.Hash.ToString()]), - ["allowedgroups"] = new JArray([TestProtocolSettings.SoleNode.StandbyCommittee[0].ToString()]), - ["rules"] = new JArray([new JObject() { ["action"] = nameof(WitnessRuleAction.Allow), ["condition"] = new JObject { ["type"] = nameof(WitnessConditionType.CalledByEntry) } }]), + 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 JArray multisigSigner = [new JObject() + static readonly Signer[] multisigSigner = [new() { - ["account"] = MultisigScriptHash.ToString(), - ["scopes"] = nameof(WitnessScope.CalledByEntry), + Account = MultisigScriptHash, + Scopes = WitnessScope.CalledByEntry, }]; [TestMethod] @@ -64,7 +64,7 @@ public void TestInvokeFunction() { _rpcServer.wallet = _wallet; - JObject resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "totalSupply", new JArray([]), validatorSigner, true)); + JObject resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "totalSupply", [], validatorSigner, true); Assert.AreEqual(resp.Count, 8); Assert.AreEqual(resp["script"], NeoTotalSupplyScript); Assert.IsTrue(resp.ContainsProperty("gasconsumed")); @@ -78,7 +78,7 @@ public void TestInvokeFunction() Assert.AreEqual(resp["stack"][0]["value"], "100000000"); Assert.IsTrue(resp.ContainsProperty("tx")); - resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "symbol")); + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "symbol"); Assert.AreEqual(resp.Count, 6); Assert.IsTrue(resp.ContainsProperty("script")); Assert.IsTrue(resp.ContainsProperty("gasconsumed")); @@ -89,12 +89,12 @@ public void TestInvokeFunction() Assert.AreEqual(resp["stack"][0]["value"], Convert.ToBase64String(Encoding.UTF8.GetBytes("NEO"))); // This call triggers not only NEO but also unclaimed GAS - resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "transfer", new JArray([ - new JObject() { ["type"] = nameof(ContractParameterType.Hash160), ["value"] = MultisigScriptHash.ToString() }, - new JObject() { ["type"] = nameof(ContractParameterType.Hash160), ["value"] = ValidatorScriptHash.ToString() }, - new JObject() { ["type"] = nameof(ContractParameterType.Integer), ["value"] = "1" }, - new JObject() { ["type"] = nameof(ContractParameterType.Any) }, - ]), multisigSigner, true)); + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "transfer", [ + new ContractParameter { Type = ContractParameterType.Hash160, Value = MultisigScriptHash }, + new ContractParameter { Type = ContractParameterType.Hash160, Value = ValidatorScriptHash }, + new ContractParameter { Type = ContractParameterType.Integer, Value = 1 }, + new ContractParameter { Type = ContractParameterType.Any }, + ], multisigSigner, true); Assert.AreEqual(resp.Count, 7); Assert.AreEqual(resp["script"], NeoTransferScript); Assert.IsTrue(resp.ContainsProperty("gasconsumed")); @@ -119,7 +119,7 @@ public void TestInvokeFunction() [TestMethod] public void TestInvokeScript() { - JObject resp = (JObject)_rpcServer.InvokeScript(new JArray(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")); @@ -130,7 +130,7 @@ public void TestInvokeScript() Assert.AreEqual(resp["stack"][0]["type"], nameof(Neo.VM.Types.Integer)); Assert.AreEqual(resp["stack"][0]["value"], "100000000"); - resp = (JObject)_rpcServer.InvokeScript(new JArray(NeoTransferScript)); + resp = (JObject)_rpcServer.InvokeScript(NeoTransferScript); Assert.AreEqual(resp.Count, 6); Assert.AreEqual(resp["stack"][0]["type"], nameof(Neo.VM.Types.Boolean)); Assert.AreEqual(resp["stack"][0]["value"], false); @@ -140,21 +140,21 @@ public void TestInvokeScript() public void TestTraverseIterator() { // GetAllCandidates that should return 0 candidates - JObject resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "getAllCandidates", new JArray([]), validatorSigner, true)); + JObject resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "getAllCandidates", [], validatorSigner, true); string sessionId = resp["session"].AsString(); string iteratorId = resp["stack"][0]["id"].AsString(); - JArray respArray = (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 100]); + JArray respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100); Assert.AreEqual(respArray.Count, 0); - _rpcServer.TerminateSession([sessionId]); - Assert.ThrowsException(() => (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 100]), "Unknown session"); + _rpcServer.TerminateSession(Guid.Parse(sessionId)); + Assert.ThrowsException(() => (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100), "Unknown session"); // register candidate in snapshot - resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "registerCandidate", - new JArray([new JObject() + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "registerCandidate", + [new ContractParameter { - ["type"] = nameof(ContractParameterType.PublicKey), - ["value"] = TestProtocolSettings.SoleNode.StandbyCommittee[0].ToString(), - }]), validatorSigner, true)); + Type = ContractParameterType.PublicKey, + Value = TestProtocolSettings.SoleNode.StandbyCommittee[0], + }], validatorSigner, true); Assert.AreEqual(resp["state"], nameof(VM.VMState.HALT)); SnapshotCache snapshot = _neoSystem.GetSnapshotCache(); Transaction? tx = new Transaction @@ -170,10 +170,10 @@ public void TestTraverseIterator() engine.SnapshotCache.Commit(); // GetAllCandidates that should return 1 candidate - resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "getAllCandidates", new JArray([]), validatorSigner, true)); + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "getAllCandidates", [], validatorSigner, true); sessionId = resp["session"].AsString(); iteratorId = resp["stack"][0]["id"].AsString(); - respArray = (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 100]); + respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100); Assert.AreEqual(respArray.Count, 1); Assert.AreEqual(respArray[0]["type"], nameof(Neo.VM.Types.Struct)); JArray value = (JArray)respArray[0]["value"]; @@ -184,50 +184,50 @@ public void TestTraverseIterator() Assert.AreEqual(value[1]["value"], "0"); // No result when traversed again - respArray = (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 100]); + respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100); Assert.AreEqual(respArray.Count, 0); // GetAllCandidates again - resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "getAllCandidates", new JArray([]), validatorSigner, true)); + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "getAllCandidates", [], validatorSigner, true); sessionId = resp["session"].AsString(); iteratorId = resp["stack"][0]["id"].AsString(); // Insufficient result count limit - respArray = (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 0]); + respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 0); Assert.AreEqual(respArray.Count, 0); - respArray = (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 1]); + respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 1); Assert.AreEqual(respArray.Count, 1); - respArray = (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 1]); + respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 1); Assert.AreEqual(respArray.Count, 0); // Mocking session timeout Thread.Sleep((int)_rpcServerSettings.SessionExpirationTime.TotalMilliseconds + 1); // build another session that did not expire - resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "getAllCandidates", new JArray([]), validatorSigner, true)); + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "getAllCandidates", [], validatorSigner, true); string notExpiredSessionId = resp["session"].AsString(); string notExpiredIteratorId = resp["stack"][0]["id"].AsString(); _rpcServer.OnTimer(new object()); - Assert.ThrowsException(() => (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 100]), "Unknown session"); + Assert.ThrowsException(() => (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100), "Unknown session"); // If you want to run the following line without exception, // DO NOT BREAK IN THE DEBUGGER, because the session expires quickly - respArray = (JArray)_rpcServer.TraverseIterator([notExpiredSessionId, notExpiredIteratorId, 1]); + respArray = (JArray)_rpcServer.TraverseIterator(notExpiredSessionId, notExpiredIteratorId, 1); Assert.AreEqual(respArray.Count, 1); // Mocking disposal - resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "getAllCandidates", new JArray([]), validatorSigner, true)); + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "getAllCandidates", [], validatorSigner, true); sessionId = resp["session"].AsString(); iteratorId = resp["stack"][0]["id"].AsString(); _rpcServer.Dispose_SmartContract(); - Assert.ThrowsException(() => (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 100]), "Unknown session"); + Assert.ThrowsException(() => (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100), "Unknown session"); } [TestMethod] public void TestGetUnclaimedGas() { - JObject resp = (JObject)_rpcServer.GetUnclaimedGas([MultisigAddress]); + JObject resp = (JObject)_rpcServer.GetUnclaimedGas(MultisigAddress); Assert.AreEqual(resp["unclaimed"], "50000000"); Assert.AreEqual(resp["address"], MultisigAddress); - resp = (JObject)_rpcServer.GetUnclaimedGas([ValidatorAddress]); + resp = (JObject)_rpcServer.GetUnclaimedGas(ValidatorAddress); Assert.AreEqual(resp["unclaimed"], "0"); Assert.AreEqual(resp["address"], ValidatorAddress); } diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs index 0897a12804..78eda4f2fd 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs @@ -9,7 +9,6 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; using Neo.Json; @@ -21,6 +20,7 @@ using Neo.UnitTests; using Neo.UnitTests.Extensions; using System; +using System.Buffers.Text; using System.IO; using System.Linq; @@ -28,6 +28,15 @@ namespace Neo.Plugins.RpcServer.Tests; partial class UT_RpcServer { + private static readonly JArray ValidatorSigner = [new JObject() + { + ["account"] = ValidatorScriptHash.ToString(), + ["scopes"] = nameof(WitnessScope.CalledByEntry), + ["allowedcontracts"] = new JArray([NeoToken.NEO.Hash.ToString(), GasToken.GAS.Hash.ToString()]), + ["allowedgroups"] = new JArray([TestProtocolSettings.SoleNode.StandbyCommittee[0].ToString()]), + ["rules"] = new JArray([new JObject() { ["action"] = nameof(WitnessRuleAction.Allow), ["condition"] = new JObject { ["type"] = nameof(WitnessConditionType.CalledByEntry) } }]), + }]; + [TestMethod] public void TestOpenWallet() { @@ -393,7 +402,7 @@ public void TestInvokeContractVerify() exception = Assert.ThrowsException(() => _rpcServer.InvokeContractVerify(invalidParamsArray), "Should throw RpcException for invalid script hash"); Assert.AreEqual(exception.HResult, RpcError.InvalidParams.Code); - // deploy a contract with `Verify` method; + // deploy a contract with `Verify` method; string _contractSourceCode = """ using Neo;using Neo.SmartContract.Framework;using Neo.SmartContract.Framework.Services; namespace ContractWithVerify{public class ContractWithVerify:SmartContract { @@ -407,13 +416,13 @@ public static void _deploy(object data, bool update) { """; string base64NefFile = "TkVGM05lby5Db21waWxlci5DU2hhcnAgMy43LjQrNjAzNGExODIxY2E3MDk0NjBlYzMxMzZjNzBjMmRjYzNiZWEuLi4AAAAAAGNXAAJ5JgQiGEEtUQgwE84MASDbMEGb9mfOQeY/GIRADAEg2zBBm/ZnzkGSXegxStgkCUrKABQoAzpB\u002BCfsjEBXAAERiEoQeNBBm/ZnzkGSXegxStgkCUrKABQoAzpB\u002BCfsjEDo2WhC"; string manifest = """{"name":"ContractWithVerify","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"_deploy","parameters":[{"name":"data","type":"Any"},{"name":"update","type":"Boolean"}],"returntype":"Void","offset":0,"safe":false},{"name":"verify","parameters":[],"returntype":"Boolean","offset":31,"safe":false},{"name":"verify","parameters":[{"name":"prefix","type":"Integer"}],"returntype":"Boolean","offset":63,"safe":false}],"events":[]},"permissions":[],"trusts":[],"extra":{"nef":{"optimization":"All"}}}"""; - JObject deployResp = (JObject)_rpcServer.InvokeFunction(new JArray([ContractManagement.ContractManagement.Hash.ToString(), + JObject deployResp = (JObject)_rpcServer.InvokeFunction(ContractManagement.ContractManagement.Hash, "deploy", - new JArray([ - new JObject() { ["type"] = nameof(ContractParameterType.ByteArray), ["value"] = base64NefFile }, - new JObject() { ["type"] = nameof(ContractParameterType.String), ["value"] = manifest }, - ]), - validatorSigner])); + [ + new ContractParameter { Type = ContractParameterType.ByteArray, Value = Convert.FromBase64String(base64NefFile) }, + new ContractParameter { Type = ContractParameterType.String, Value = manifest }, + ], + validatorSigner); 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(); @@ -434,19 +443,19 @@ public static void _deploy(object data, bool update) { Assert.AreEqual(resp["state"], nameof(VM.VMState.HALT)); Assert.AreEqual(resp["stack"][0]["value"].AsBoolean(), false); // invoke verify with signer; should return true - resp = (JObject)_rpcServer.InvokeContractVerify([deployedScriptHash.ToString(), new JArray([]), validatorSigner]); + resp = (JObject)_rpcServer.InvokeContractVerify([deployedScriptHash.ToString(), new JArray([]), ValidatorSigner]); Assert.AreEqual(resp["state"], nameof(VM.VMState.HALT)); Assert.AreEqual(resp["stack"][0]["value"].AsBoolean(), true); // invoke verify with wrong input value; should FAULT - resp = (JObject)_rpcServer.InvokeContractVerify([deployedScriptHash.ToString(), new JArray([new JObject() { ["type"] = nameof(ContractParameterType.Integer), ["value"] = "0" }]), validatorSigner]); + resp = (JObject)_rpcServer.InvokeContractVerify([deployedScriptHash.ToString(), new JArray([new JObject() { ["type"] = nameof(ContractParameterType.Integer), ["value"] = "0" }]), ValidatorSigner]); Assert.AreEqual(resp["state"], nameof(VM.VMState.FAULT)); Assert.AreEqual(resp["exception"], "Object reference not set to an instance of an object."); // invoke verify with 1 param and signer; should return true - resp = (JObject)_rpcServer.InvokeContractVerify([deployedScriptHash.ToString(), new JArray([new JObject() { ["type"] = nameof(ContractParameterType.Integer), ["value"] = "32" }]), validatorSigner]); + resp = (JObject)_rpcServer.InvokeContractVerify([deployedScriptHash.ToString(), new JArray([new JObject() { ["type"] = nameof(ContractParameterType.Integer), ["value"] = "32" }]), ValidatorSigner]); Assert.AreEqual(resp["state"], nameof(VM.VMState.HALT)); Assert.AreEqual(resp["stack"][0]["value"].AsBoolean(), true); // invoke verify with 2 param (which does not exist); should throw Exception - Assert.ThrowsException(() => _rpcServer.InvokeContractVerify([deployedScriptHash.ToString(), new JArray([new JObject() { ["type"] = nameof(ContractParameterType.Integer), ["value"] = "32" }, new JObject() { ["type"] = nameof(ContractParameterType.Integer), ["value"] = "32" }]), validatorSigner]), + Assert.ThrowsException(() => _rpcServer.InvokeContractVerify([deployedScriptHash.ToString(), new JArray([new JObject() { ["type"] = nameof(ContractParameterType.Integer), ["value"] = "32" }, new JObject() { ["type"] = nameof(ContractParameterType.Integer), ["value"] = "32" }]), ValidatorSigner]), $"Invalid contract verification function - The smart contract {deployedScriptHash} haven't got verify method with 2 input parameters."); } From 292c3cc2fc7e8d763fd74a3966f93646bb94f2af Mon Sep 17 00:00:00 2001 From: Jimmy Date: Wed, 11 Sep 2024 23:51:59 +0800 Subject: [PATCH 2/8] update hash string --- .../UT_RpcServer.SmartContract.cs | 18 +++++++++--------- .../UT_RpcServer.Wallet.cs | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs index b09c9514e0..bc7c353b03 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs @@ -64,7 +64,7 @@ public void TestInvokeFunction() { _rpcServer.wallet = _wallet; - JObject resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "totalSupply", [], validatorSigner, true); + JObject resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "totalSupply", [], validatorSigner, true); Assert.AreEqual(resp.Count, 8); Assert.AreEqual(resp["script"], NeoTotalSupplyScript); Assert.IsTrue(resp.ContainsProperty("gasconsumed")); @@ -78,7 +78,7 @@ public void TestInvokeFunction() Assert.AreEqual(resp["stack"][0]["value"], "100000000"); Assert.IsTrue(resp.ContainsProperty("tx")); - resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "symbol"); + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "symbol"); Assert.AreEqual(resp.Count, 6); Assert.IsTrue(resp.ContainsProperty("script")); Assert.IsTrue(resp.ContainsProperty("gasconsumed")); @@ -89,7 +89,7 @@ public void TestInvokeFunction() Assert.AreEqual(resp["stack"][0]["value"], Convert.ToBase64String(Encoding.UTF8.GetBytes("NEO"))); // This call triggers not only NEO but also unclaimed GAS - resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "transfer", [ + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "transfer", [ new ContractParameter { Type = ContractParameterType.Hash160, Value = MultisigScriptHash }, new ContractParameter { Type = ContractParameterType.Hash160, Value = ValidatorScriptHash }, new ContractParameter { Type = ContractParameterType.Integer, Value = 1 }, @@ -140,7 +140,7 @@ public void TestInvokeScript() public void TestTraverseIterator() { // GetAllCandidates that should return 0 candidates - JObject resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "getAllCandidates", [], validatorSigner, true); + JObject resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true); string sessionId = resp["session"].AsString(); string iteratorId = resp["stack"][0]["id"].AsString(); JArray respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100); @@ -149,7 +149,7 @@ public void TestTraverseIterator() Assert.ThrowsException(() => (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100), "Unknown session"); // register candidate in snapshot - resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "registerCandidate", + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "registerCandidate", [new ContractParameter { Type = ContractParameterType.PublicKey, @@ -170,7 +170,7 @@ [new ContractParameter engine.SnapshotCache.Commit(); // GetAllCandidates that should return 1 candidate - resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "getAllCandidates", [], validatorSigner, true); + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true); sessionId = resp["session"].AsString(); iteratorId = resp["stack"][0]["id"].AsString(); respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100); @@ -188,7 +188,7 @@ [new ContractParameter Assert.AreEqual(respArray.Count, 0); // GetAllCandidates again - resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "getAllCandidates", [], validatorSigner, true); + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true); sessionId = resp["session"].AsString(); iteratorId = resp["stack"][0]["id"].AsString(); @@ -203,7 +203,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, "getAllCandidates", [], validatorSigner, true); + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true); string notExpiredSessionId = resp["session"].AsString(); string notExpiredIteratorId = resp["stack"][0]["id"].AsString(); _rpcServer.OnTimer(new object()); @@ -214,7 +214,7 @@ [new ContractParameter Assert.AreEqual(respArray.Count, 1); // Mocking disposal - resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash, "getAllCandidates", [], validatorSigner, true); + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true); sessionId = resp["session"].AsString(); iteratorId = 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 78eda4f2fd..477934552c 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs @@ -416,7 +416,7 @@ public static void _deploy(object data, bool update) { """; string base64NefFile = "TkVGM05lby5Db21waWxlci5DU2hhcnAgMy43LjQrNjAzNGExODIxY2E3MDk0NjBlYzMxMzZjNzBjMmRjYzNiZWEuLi4AAAAAAGNXAAJ5JgQiGEEtUQgwE84MASDbMEGb9mfOQeY/GIRADAEg2zBBm/ZnzkGSXegxStgkCUrKABQoAzpB\u002BCfsjEBXAAERiEoQeNBBm/ZnzkGSXegxStgkCUrKABQoAzpB\u002BCfsjEDo2WhC"; string manifest = """{"name":"ContractWithVerify","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"_deploy","parameters":[{"name":"data","type":"Any"},{"name":"update","type":"Boolean"}],"returntype":"Void","offset":0,"safe":false},{"name":"verify","parameters":[],"returntype":"Boolean","offset":31,"safe":false},{"name":"verify","parameters":[{"name":"prefix","type":"Integer"}],"returntype":"Boolean","offset":63,"safe":false}],"events":[]},"permissions":[],"trusts":[],"extra":{"nef":{"optimization":"All"}}}"""; - JObject deployResp = (JObject)_rpcServer.InvokeFunction(ContractManagement.ContractManagement.Hash, + JObject deployResp = (JObject)_rpcServer.InvokeFunction(ContractManagement.ContractManagement.Hash.ToString(), "deploy", [ new ContractParameter { Type = ContractParameterType.ByteArray, Value = Convert.FromBase64String(base64NefFile) }, From 9fabbf5b48856b0a5f7513b9ff65ae0e109a33cc Mon Sep 17 00:00:00 2001 From: Jimmy Date: Thu, 12 Sep 2024 00:04:22 +0800 Subject: [PATCH 3/8] update session and iterator --- .../RpcServer/RpcServer.SmartContract.cs | 8 +++---- .../UT_RpcServer.SmartContract.cs | 22 +++++++++---------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/Plugins/RpcServer/RpcServer.SmartContract.cs b/src/Plugins/RpcServer/RpcServer.SmartContract.cs index bf3dd332f7..28a71a2e1e 100644 --- a/src/Plugins/RpcServer/RpcServer.SmartContract.cs +++ b/src/Plugins/RpcServer/RpcServer.SmartContract.cs @@ -276,20 +276,18 @@ protected internal virtual JToken InvokeScript(string scriptBase64, Signer[] sig /// The number of values returned. It cannot exceed the value of the MaxIteratorResultItems field in config.json of the RpcServer plug-in. /// A JToken containing the iterator values. [RpcMethodWithParams] - protected internal virtual JToken TraverseIterator(string session, string iteratorId, int count) + protected internal virtual JToken TraverseIterator(Guid session, Guid iteratorId, int count) { settings.SessionEnabled.True_Or(RpcError.SessionsDisabled); - Guid sid = Result.Ok_Or(() => Guid.Parse(session), RpcError.InvalidParams.WithData($"Invalid session id")); - Guid iid = Result.Ok_Or(() => Guid.Parse(iteratorId), RpcError.InvalidParams.WithData($"Invalid iterator id")); Result.True_Or(() => count <= settings.MaxIteratorResultItems, RpcError.InvalidParams.WithData($"Invalid iterator items count: {count}")); Session currentSession; lock (sessions) { - currentSession = Result.Ok_Or(() => sessions[sid], RpcError.UnknownSession); + currentSession = Result.Ok_Or(() => sessions[session], RpcError.UnknownSession); currentSession.ResetExpiration(); } - IIterator iterator = Result.Ok_Or(() => currentSession.Iterators[iid], RpcError.UnknownIterator); + IIterator iterator = Result.Ok_Or(() => currentSession.Iterators[iteratorId], RpcError.UnknownIterator); JArray json = new(); while (count-- > 0 && iterator.Next()) json.Add(iterator.Value(null).ToJson()); diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs index bc7c353b03..a703ddbd96 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs @@ -141,11 +141,11 @@ public void TestTraverseIterator() { // GetAllCandidates that should return 0 candidates JObject resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true); - string sessionId = resp["session"].AsString(); - string iteratorId = resp["stack"][0]["id"].AsString(); + Guid sessionId = Guid.Parse(resp["session"].AsString()); + Guid iteratorId = Guid.Parse(resp["stack"][0]["id"].AsString()); JArray respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100); Assert.AreEqual(respArray.Count, 0); - _rpcServer.TerminateSession(Guid.Parse(sessionId)); + _rpcServer.TerminateSession(sessionId); Assert.ThrowsException(() => (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100), "Unknown session"); // register candidate in snapshot @@ -171,8 +171,8 @@ [new ContractParameter // GetAllCandidates that should return 1 candidate resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true); - sessionId = resp["session"].AsString(); - iteratorId = resp["stack"][0]["id"].AsString(); + sessionId = Guid.Parse(resp["session"].AsString()); + iteratorId = Guid.Parse(resp["stack"][0]["id"].AsString()); respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100); Assert.AreEqual(respArray.Count, 1); Assert.AreEqual(respArray[0]["type"], nameof(Neo.VM.Types.Struct)); @@ -189,8 +189,8 @@ [new ContractParameter // GetAllCandidates again resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true); - sessionId = resp["session"].AsString(); - iteratorId = resp["stack"][0]["id"].AsString(); + sessionId = Guid.Parse(resp["session"].AsString()); + iteratorId = Guid.Parse(resp["stack"][0]["id"].AsString()); // Insufficient result count limit respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 0); @@ -204,8 +204,8 @@ [new ContractParameter 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); - string notExpiredSessionId = resp["session"].AsString(); - string notExpiredIteratorId = resp["stack"][0]["id"].AsString(); + Guid notExpiredSessionId = Guid.Parse(resp["session"].AsString()); + Guid notExpiredIteratorId = Guid.Parse(resp["stack"][0]["id"].AsString()); _rpcServer.OnTimer(new object()); Assert.ThrowsException(() => (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100), "Unknown session"); // If you want to run the following line without exception, @@ -215,8 +215,8 @@ [new ContractParameter // Mocking disposal resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true); - sessionId = resp["session"].AsString(); - iteratorId = resp["stack"][0]["id"].AsString(); + sessionId = Guid.Parse(resp["session"].AsString()); + iteratorId = Guid.Parse(resp["stack"][0]["id"].AsString()); _rpcServer.Dispose_SmartContract(); Assert.ThrowsException(() => (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100), "Unknown session"); } From 656bee9dc0f39b23483e089183ce2355f17b6639 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Fri, 13 Sep 2024 12:04:24 +0800 Subject: [PATCH 4/8] 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(); From a8578c7332ac13208eceae1951717eba1087797b Mon Sep 17 00:00:00 2001 From: Jimmy Date: Fri, 13 Sep 2024 12:18:28 +0800 Subject: [PATCH 5/8] update function as well --- src/Plugins/RpcServer/ParameterConverter.cs | 16 ++++++++++++++++ .../RpcServer/RpcServer.SmartContract.cs | 8 +++++--- src/Plugins/RpcServer/RpcServer.cs | 5 +++++ .../UT_RpcServer.SmartContract.cs | 18 +++++++++--------- .../UT_RpcServer.Wallet.cs | 2 +- 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/Plugins/RpcServer/ParameterConverter.cs b/src/Plugins/RpcServer/ParameterConverter.cs index 3d950a0a16..33095953ad 100644 --- a/src/Plugins/RpcServer/ParameterConverter.cs +++ b/src/Plugins/RpcServer/ParameterConverter.cs @@ -114,6 +114,22 @@ internal static object ConvertUInt160(JToken token, byte addressVersion) return Result.Ok_Or(() => value.ToScriptHash(addressVersion), RpcError.InvalidParams.WithData($"Invalid UInt160 Format: {token}")); } + + internal static object ConvertSignerOrWitnessArray(JToken token, ProtocolSettings settings) + { + if (token is JArray jArray) + { + return SignerOrWitness.ParseArray(jArray, settings); + } + else if (token is JObject jObject) + { + if (SignerOrWitness.TryParse(jObject, settings, out var signerOrWitness)) + { + return new[] { signerOrWitness }; + } + } + throw new RpcException(RpcError.InvalidParams.WithData($"Invalid SignerOrWitness format: {token}")); + } private static object ConvertUInt256(JToken token) { diff --git a/src/Plugins/RpcServer/RpcServer.SmartContract.cs b/src/Plugins/RpcServer/RpcServer.SmartContract.cs index 83dbb4687c..eb23c8c609 100644 --- a/src/Plugins/RpcServer/RpcServer.SmartContract.cs +++ b/src/Plugins/RpcServer/RpcServer.SmartContract.cs @@ -182,11 +182,11 @@ private static JObject ToJson(StackItem item, Session session) /// Smart contract scripthash. Use big endian for Hash160, little endian for ByteArray. /// The operation name (string) /// Optional. The parameters to be passed into the smart contract operation - /// Optional. List of contract signature accounts. + /// Optional. List of contract signature accounts or witnesses. /// Optional. Flag to enable diagnostic information. /// A JToken containing the result of the invocation. [RpcMethodWithParams] - protected internal virtual JToken InvokeFunction(string scriptHash, string operation, ContractParameter[] args = null, Signer[] signers = null, bool useDiagnostic = false) + protected internal virtual JToken InvokeFunction(string scriptHash, string operation, ContractParameter[] args = null, SignerOrWitness[] signerOrWitnesses = null, bool useDiagnostic = false) { UInt160 contractHash = Result.Ok_Or(() => UInt160.Parse(scriptHash), RpcError.InvalidParams); byte[] script; @@ -194,7 +194,9 @@ protected internal virtual JToken InvokeFunction(string scriptHash, string opera { script = sb.EmitDynamicCall(contractHash, operation, args ?? Array.Empty()).ToArray(); } - return GetInvokeResult(script, signers, [], useDiagnostic); + 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.cs b/src/Plugins/RpcServer/RpcServer.cs index c90bbad539..1ef34bdaf8 100644 --- a/src/Plugins/RpcServer/RpcServer.cs +++ b/src/Plugins/RpcServer/RpcServer.cs @@ -18,6 +18,7 @@ using Microsoft.Extensions.DependencyInjection; using Neo.Json; using Neo.Network.P2P; +using Neo.Plugins.RpcServer.Model; using System; using System.Collections.Generic; using System.IO; @@ -303,6 +304,10 @@ private async Task ProcessRequestAsync(HttpContext context, JObject req { args[i] = ParameterConverter.ConvertUInt160(jsonParameters[i], system.Settings.AddressVersion); } + else if (param.ParameterType == typeof(SignerOrWitness)) + { + args[i] = ParameterConverter.ConvertSignerOrWitnessArray(jsonParameters[i], system.Settings); + } else { args[i] = ParameterConverter.ConvertParameter(jsonParameters[i], param.ParameterType); diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs index 4bfbb09264..8139b02ee7 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs @@ -54,18 +54,18 @@ public partial class UT_RpcServer AllowedGroups = [TestProtocolSettings.SoleNode.StandbyCommittee[0]], Rules = [new WitnessRule { Action = WitnessRuleAction.Allow, Condition = new CalledByEntryCondition() }], })]; - static readonly Signer[] multisigSigner = [new() + static readonly SignerOrWitness[] multisigSigner = [new(new Signer { Account = MultisigScriptHash, Scopes = WitnessScope.CalledByEntry, - }]; + })]; [TestMethod] public void TestInvokeFunction() { _rpcServer.wallet = _wallet; - JObject resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "totalSupply", [], validatorSigner.Select(u => u.AsSigner()).ToArray(), true); + JObject resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "totalSupply", [], validatorSigner, true); Assert.AreEqual(resp.Count, 8); Assert.AreEqual(resp["script"], NeoTotalSupplyScript); Assert.IsTrue(resp.ContainsProperty("gasconsumed")); @@ -141,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.Select(u => u.AsSigner()).ToArray(), true); + JObject resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, 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); @@ -155,7 +155,7 @@ [new ContractParameter { Type = ContractParameterType.PublicKey, Value = TestProtocolSettings.SoleNode.StandbyCommittee[0], - }], validatorSigner.Select(u => u.AsSigner()).ToArray(), true); + }], validatorSigner, true); Assert.AreEqual(resp["state"], nameof(VM.VMState.HALT)); SnapshotCache snapshot = _neoSystem.GetSnapshotCache(); Transaction? tx = new Transaction @@ -171,7 +171,7 @@ [new ContractParameter engine.SnapshotCache.Commit(); // GetAllCandidates that should return 1 candidate - resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner.Select(u => u.AsSigner()).ToArray(), true); + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true); sessionId = Guid.Parse(resp["session"].AsString()); iteratorId = Guid.Parse(resp["stack"][0]["id"].AsString()); respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100); @@ -189,7 +189,7 @@ [new ContractParameter Assert.AreEqual(respArray.Count, 0); // GetAllCandidates again - resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner.Select(u => u.AsSigner()).ToArray(), true); + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true); sessionId = Guid.Parse(resp["session"].AsString()); iteratorId = Guid.Parse(resp["stack"][0]["id"].AsString()); @@ -204,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.Select(u => u.AsSigner()).ToArray(), true); + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true); Guid notExpiredSessionId = Guid.Parse(resp["session"].AsString()); Guid notExpiredIteratorId = Guid.Parse(resp["stack"][0]["id"].AsString()); _rpcServer.OnTimer(new object()); @@ -215,7 +215,7 @@ [new ContractParameter Assert.AreEqual(respArray.Count, 1); // Mocking disposal - resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner.Select(u => u.AsSigner()).ToArray(), true); + resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, 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 276d02d65c..477934552c 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.Select(u => u.AsSigner()).ToArray()); + validatorSigner); 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(); From 12fb1b64b42c1a26432a4ed157bc4ee573eda273 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Fri, 13 Sep 2024 12:18:39 +0800 Subject: [PATCH 6/8] update function as well --- src/Plugins/RpcServer/ParameterConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugins/RpcServer/ParameterConverter.cs b/src/Plugins/RpcServer/ParameterConverter.cs index 33095953ad..57e91132fe 100644 --- a/src/Plugins/RpcServer/ParameterConverter.cs +++ b/src/Plugins/RpcServer/ParameterConverter.cs @@ -114,7 +114,7 @@ internal static object ConvertUInt160(JToken token, byte addressVersion) return Result.Ok_Or(() => value.ToScriptHash(addressVersion), RpcError.InvalidParams.WithData($"Invalid UInt160 Format: {token}")); } - + internal static object ConvertSignerOrWitnessArray(JToken token, ProtocolSettings settings) { if (token is JArray jArray) From c0930c1e1439d976f8eff44f91ae78274609cf60 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sun, 15 Sep 2024 00:01:04 +0800 Subject: [PATCH 7/8] update to signerwithwitness --- ...ignerOrWitness.cs => SignerWithWitness.cs} | 54 +++++++------------ src/Plugins/RpcServer/ParameterConverter.cs | 4 +- .../RpcServer/RpcServer.SmartContract.cs | 16 +++--- src/Plugins/RpcServer/RpcServer.Wallet.cs | 4 +- src/Plugins/RpcServer/RpcServer.cs | 2 +- .../UT_RpcServer.SmartContract.cs | 8 +-- 6 files changed, 37 insertions(+), 51 deletions(-) rename src/Plugins/RpcServer/Model/{SignerOrWitness.cs => SignerWithWitness.cs} (68%) diff --git a/src/Plugins/RpcServer/Model/SignerOrWitness.cs b/src/Plugins/RpcServer/Model/SignerWithWitness.cs similarity index 68% rename from src/Plugins/RpcServer/Model/SignerOrWitness.cs rename to src/Plugins/RpcServer/Model/SignerWithWitness.cs index 225291f828..5261530a47 100644 --- a/src/Plugins/RpcServer/Model/SignerOrWitness.cs +++ b/src/Plugins/RpcServer/Model/SignerWithWitness.cs @@ -1,6 +1,6 @@ // Copyright (C) 2015-2024 The Neo Project. // -// SignerOrWitness.cs file belongs to the neo project and is free +// SignerWithWitness.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 @@ -19,39 +19,35 @@ namespace Neo.Plugins.RpcServer.Model; -public class SignerOrWitness +public class SignerWithWitness(Signer? signer, Witness? witness) { - private readonly object _value; + public Signer? Signer { get; } = signer; + public Witness? Witness { get; } = witness; - public SignerOrWitness(Signer signer) + public static bool TryParse(JToken value, ProtocolSettings settings, [NotNullWhen(true)] out SignerWithWitness? signerWithWitness) { - _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; + signerWithWitness = null; if (value == null) return false; if (value is JObject jObject) { + Signer? signer = null; + Witness? witness = null; + if (jObject.ContainsProperty("account")) { - signerOrWitness = new SignerOrWitness(SignerFromJson(jObject, settings)); - return true; + signer = SignerFromJson(jObject, settings); } - else if (jObject.ContainsProperty("invocation") || jObject.ContainsProperty("verification")) + if (jObject.ContainsProperty("invocation") || jObject.ContainsProperty("verification")) { - signerOrWitness = new SignerOrWitness(WitnessFromJson(jObject)); + witness = WitnessFromJson(jObject); + } + + if (signer != null || witness != null) + { + signerWithWitness = new SignerWithWitness(signer, witness); return true; } } @@ -82,7 +78,7 @@ private static Witness WitnessFromJson(JObject jObject) }; } - public static SignerOrWitness[] ParseArray(JArray array, ProtocolSettings settings) + public static SignerWithWitness[] ParseArray(JArray array, ProtocolSettings settings) { if (array == null) throw new ArgumentNullException(nameof(array)); @@ -92,22 +88,12 @@ public static SignerOrWitness[] ParseArray(JArray array, ProtocolSettings settin return array.Select(item => { - if (TryParse(item, settings, out var signerOrWitness)) - return signerOrWitness; + if (TryParse(item, settings, out var signerWithWitness)) + return signerWithWitness; 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)) diff --git a/src/Plugins/RpcServer/ParameterConverter.cs b/src/Plugins/RpcServer/ParameterConverter.cs index 57e91132fe..dda5805a43 100644 --- a/src/Plugins/RpcServer/ParameterConverter.cs +++ b/src/Plugins/RpcServer/ParameterConverter.cs @@ -119,11 +119,11 @@ internal static object ConvertSignerOrWitnessArray(JToken token, ProtocolSetting { if (token is JArray jArray) { - return SignerOrWitness.ParseArray(jArray, settings); + return SignerWithWitness.ParseArray(jArray, settings); } else if (token is JObject jObject) { - if (SignerOrWitness.TryParse(jObject, settings, out var signerOrWitness)) + if (SignerWithWitness.TryParse(jObject, settings, out var signerOrWitness)) { return new[] { signerOrWitness }; } diff --git a/src/Plugins/RpcServer/RpcServer.SmartContract.cs b/src/Plugins/RpcServer/RpcServer.SmartContract.cs index eb23c8c609..8a1d27b445 100644 --- a/src/Plugins/RpcServer/RpcServer.SmartContract.cs +++ b/src/Plugins/RpcServer/RpcServer.SmartContract.cs @@ -182,11 +182,11 @@ private static JObject ToJson(StackItem item, Session session) /// Smart contract scripthash. Use big endian for Hash160, little endian for ByteArray. /// The operation name (string) /// Optional. The parameters to be passed into the smart contract operation - /// Optional. List of contract signature accounts or witnesses. + /// Optional. List of signers and/or witnesses. /// Optional. Flag to enable diagnostic information. /// A JToken containing the result of the invocation. [RpcMethodWithParams] - protected internal virtual JToken InvokeFunction(string scriptHash, string operation, ContractParameter[] args = null, SignerOrWitness[] signerOrWitnesses = null, bool useDiagnostic = false) + protected internal virtual JToken InvokeFunction(string scriptHash, string operation, ContractParameter[] args = null, SignerWithWitness[] signerWithWitnesses = null, bool useDiagnostic = false) { UInt160 contractHash = Result.Ok_Or(() => UInt160.Parse(scriptHash), RpcError.InvalidParams); byte[] script; @@ -194,8 +194,8 @@ protected internal virtual JToken InvokeFunction(string scriptHash, string opera { script = sb.EmitDynamicCall(contractHash, operation, args ?? Array.Empty()).ToArray(); } - var signers = signerOrWitnesses?.Where(u => u.IsSigner).Select(u => u.AsSigner()).ToArray() ?? []; - var witnesses = signerOrWitnesses?.Where(u => !u.IsSigner).Select(u => u.AsWitness()).ToArray() ?? []; + var signers = signerWithWitnesses?.Where(u => u.Signer != null).Select(u => u.Signer).ToArray() ?? []; + var witnesses = signerWithWitnesses?.Where(u => u.Witness != null).Select(u => u.Witness).ToArray() ?? []; return GetInvokeResult(script, signers, witnesses, useDiagnostic); } @@ -208,15 +208,15 @@ 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 or witnesses for the transaction. + /// Optional. The list of contract signature accounts and/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, SignerOrWitness[] signerOrWitnesses = null, bool useDiagnostic = false) + protected internal virtual JToken InvokeScript(string scriptBase64, SignerWithWitness[] signerWithWitnesses = null, bool useDiagnostic = false) { 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() ?? []; + var signers = signerWithWitnesses?.Where(u => u.Signer != null).Select(u => u.Signer).ToArray() ?? []; + var witnesses = signerWithWitnesses?.Where(u => u.Witness != null).Select(u => u.Witness).ToArray() ?? []; return GetInvokeResult(script, signers, witnesses, useDiagnostic); } diff --git a/src/Plugins/RpcServer/RpcServer.Wallet.cs b/src/Plugins/RpcServer/RpcServer.Wallet.cs index 42874879b6..77d04f1160 100644 --- a/src/Plugins/RpcServer/RpcServer.Wallet.cs +++ b/src/Plugins/RpcServer/RpcServer.Wallet.cs @@ -478,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 ? 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; + Signer[] signers = _params.Count >= 3 ? SignerWithWitness.ParseArray((JArray)_params[2], system.Settings).Where(u => u.Signer != null).Select(u => u.Signer).ToArray() : null; + Witness[] witnesses = _params.Count >= 3 ? SignerWithWitness.ParseArray((JArray)_params[2], system.Settings).Where(u => u.Witness != null).Select(u => u.Witness).ToArray() : null; return GetVerificationResult(script_hash, args, signers, witnesses); } diff --git a/src/Plugins/RpcServer/RpcServer.cs b/src/Plugins/RpcServer/RpcServer.cs index 1ef34bdaf8..9b98f5cfe4 100644 --- a/src/Plugins/RpcServer/RpcServer.cs +++ b/src/Plugins/RpcServer/RpcServer.cs @@ -304,7 +304,7 @@ private async Task ProcessRequestAsync(HttpContext context, JObject req { args[i] = ParameterConverter.ConvertUInt160(jsonParameters[i], system.Settings.AddressVersion); } - else if (param.ParameterType == typeof(SignerOrWitness)) + else if (param.ParameterType == typeof(SignerWithWitness)) { args[i] = ParameterConverter.ConvertSignerOrWitnessArray(jsonParameters[i], system.Settings); } diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs index 8139b02ee7..dfa84b89bb 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs @@ -46,19 +46,19 @@ public partial class UT_RpcServer .ToScriptHash(); static readonly string MultisigAddress = MultisigScriptHash.ToAddress(ProtocolSettings.Default.AddressVersion); - static readonly SignerOrWitness[] validatorSigner = [new(new Signer + static readonly SignerWithWitness[] 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 SignerOrWitness[] multisigSigner = [new(new Signer + }, null)]; + static readonly SignerWithWitness[] multisigSigner = [new(new Signer { Account = MultisigScriptHash, Scopes = WitnessScope.CalledByEntry, - })]; + }, null)]; [TestMethod] public void TestInvokeFunction() From bd615ebbb884a7f468003ad36a8aad6f854e4836 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Sat, 21 Sep 2024 13:59:38 +0800 Subject: [PATCH 8/8] mock the complete http request process. --- .../RpcServer/Model/SignerWithWitness.cs | 6 +- src/Plugins/RpcServer/ParameterConverter.cs | 11 +- src/Plugins/RpcServer/RpcServer.cs | 6 +- .../RpcServerMock.cs | 33 +++++ .../UT_RpcServer.SmartContract.cs | 123 +++++++++--------- .../UT_RpcServer.Wallet.cs | 10 +- .../UT_RpcServer.cs | 10 +- 7 files changed, 119 insertions(+), 80 deletions(-) create mode 100644 tests/Neo.Plugins.RpcServer.Tests/RpcServerMock.cs diff --git a/src/Plugins/RpcServer/Model/SignerWithWitness.cs b/src/Plugins/RpcServer/Model/SignerWithWitness.cs index 5261530a47..21a4233179 100644 --- a/src/Plugins/RpcServer/Model/SignerWithWitness.cs +++ b/src/Plugins/RpcServer/Model/SignerWithWitness.cs @@ -60,12 +60,10 @@ 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, + Scopes = (WitnessScope)Enum.Parse(typeof(WitnessScope), jObject["scopes"]?.AsString()), 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() + Rules = ((JArray)jObject["rules"])?.Select(r => WitnessRule.FromJson((JObject)r)).ToArray() ?? Array.Empty(), }; } diff --git a/src/Plugins/RpcServer/ParameterConverter.cs b/src/Plugins/RpcServer/ParameterConverter.cs index dda5805a43..c37f96eee7 100644 --- a/src/Plugins/RpcServer/ParameterConverter.cs +++ b/src/Plugins/RpcServer/ParameterConverter.cs @@ -115,20 +115,21 @@ internal static object ConvertUInt160(JToken token, byte addressVersion) RpcError.InvalidParams.WithData($"Invalid UInt160 Format: {token}")); } - internal static object ConvertSignerOrWitnessArray(JToken token, ProtocolSettings settings) + internal static object ConvertSignerWithWitnessArray(JToken token, ProtocolSettings settings) { if (token is JArray jArray) { return SignerWithWitness.ParseArray(jArray, settings); } - else if (token is JObject jObject) + + if (token is JObject jObject) { - if (SignerWithWitness.TryParse(jObject, settings, out var signerOrWitness)) + if (SignerWithWitness.TryParse(jObject, settings, out var signerWithWitness)) { - return new[] { signerOrWitness }; + return new[] { signerWithWitness }; } } - throw new RpcException(RpcError.InvalidParams.WithData($"Invalid SignerOrWitness format: {token}")); + throw new RpcException(RpcError.InvalidParams.WithData($"Invalid SignerWithWitness format: {token}")); } private static object ConvertUInt256(JToken token) diff --git a/src/Plugins/RpcServer/RpcServer.cs b/src/Plugins/RpcServer/RpcServer.cs index 9b98f5cfe4..b546a38fe7 100644 --- a/src/Plugins/RpcServer/RpcServer.cs +++ b/src/Plugins/RpcServer/RpcServer.cs @@ -261,7 +261,7 @@ public async Task ProcessAsync(HttpContext context) await context.Response.WriteAsync(response.ToString(), Encoding.UTF8); } - private async Task ProcessRequestAsync(HttpContext context, JObject request) + protected async Task ProcessRequestAsync(HttpContext context, JObject request) { if (!request.ContainsProperty("id")) return null; var @params = request["params"] ?? new JArray(); @@ -304,9 +304,9 @@ private async Task ProcessRequestAsync(HttpContext context, JObject req { args[i] = ParameterConverter.ConvertUInt160(jsonParameters[i], system.Settings.AddressVersion); } - else if (param.ParameterType == typeof(SignerWithWitness)) + else if (param.ParameterType == typeof(SignerWithWitness[])) { - args[i] = ParameterConverter.ConvertSignerOrWitnessArray(jsonParameters[i], system.Settings); + args[i] = ParameterConverter.ConvertSignerWithWitnessArray(jsonParameters[i], system.Settings); } else { diff --git a/tests/Neo.Plugins.RpcServer.Tests/RpcServerMock.cs b/tests/Neo.Plugins.RpcServer.Tests/RpcServerMock.cs new file mode 100644 index 0000000000..19d194f0d8 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/RpcServerMock.cs @@ -0,0 +1,33 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcServerMock.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 Microsoft.AspNetCore.Http; +using Neo.Json; +using System.Threading.Tasks; + +namespace Neo.Plugins.RpcServer.Tests; + +public class RpcServerMock(NeoSystem system, RpcServerSettings settings) : RpcServer(system, settings) +{ + public async Task ProcessRequestMock(HttpContext context, string method, JArray parameters) + { + var request = new JObject + { + ["jsonrpc"] = "2.0", + ["id"] = 1, + ["method"] = method, + ["params"] = parameters + }; + + var res = await ProcessRequestAsync(context, request); + return (JObject)res["result"]; + } +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs index dfa84b89bb..187a803b75 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs @@ -9,25 +9,17 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using FluentAssertions; -using Microsoft.AspNetCore.Http; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Cryptography.ECC; using Neo.IO; using Neo.Json; 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; -using Neo.UnitTests.Extensions; using Neo.Wallets; using System; -using System.IO; -using System.Linq; using System.Text; using System.Threading; @@ -46,26 +38,26 @@ public partial class UT_RpcServer .ToScriptHash(); static readonly string MultisigAddress = MultisigScriptHash.ToAddress(ProtocolSettings.Default.AddressVersion); - static readonly SignerWithWitness[] validatorSigner = [new(new Signer + static readonly JArray validatorSigner = [new JObject() { - 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() }], - }, null)]; - static readonly SignerWithWitness[] multisigSigner = [new(new Signer + ["account"] = ValidatorScriptHash.ToString(), + ["scopes"] = nameof(WitnessScope.CalledByEntry), + ["allowedcontracts"] = new JArray([NeoToken.NEO.Hash.ToString(), GasToken.GAS.Hash.ToString()]), + ["allowedgroups"] = new JArray([TestProtocolSettings.SoleNode.StandbyCommittee[0].ToString()]), + ["rules"] = new JArray([new JObject() { ["action"] = nameof(WitnessRuleAction.Allow), ["condition"] = new JObject { ["type"] = nameof(WitnessConditionType.CalledByEntry) } }]), + }]; + static readonly JArray multisigSigner = [new JObject() { - Account = MultisigScriptHash, - Scopes = WitnessScope.CalledByEntry, - }, null)]; + ["account"] = MultisigScriptHash.ToString(), + ["scopes"] = nameof(WitnessScope.CalledByEntry), + }]; [TestMethod] public void TestInvokeFunction() { _rpcServer.wallet = _wallet; - JObject resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "totalSupply", [], validatorSigner, true); + var resp = _rpcServer.ProcessRequestMock(_mockHttpContext.Object, "invokefunction", new JArray(NeoToken.NEO.Hash.ToString(), "totalSupply", new JArray([]), validatorSigner, true)).GetAwaiter().GetResult(); Assert.AreEqual(resp.Count, 8); Assert.AreEqual(resp["script"], NeoTotalSupplyScript); Assert.IsTrue(resp.ContainsProperty("gasconsumed")); @@ -79,7 +71,7 @@ public void TestInvokeFunction() Assert.AreEqual(resp["stack"][0]["value"], "100000000"); Assert.IsTrue(resp.ContainsProperty("tx")); - resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "symbol"); + resp = _rpcServer.ProcessRequestMock(_mockHttpContext.Object, "invokefunction", new JArray(NeoToken.NEO.Hash.ToString(), "symbol")).GetAwaiter().GetResult(); Assert.AreEqual(resp.Count, 6); Assert.IsTrue(resp.ContainsProperty("script")); Assert.IsTrue(resp.ContainsProperty("gasconsumed")); @@ -90,12 +82,12 @@ public void TestInvokeFunction() Assert.AreEqual(resp["stack"][0]["value"], Convert.ToBase64String(Encoding.UTF8.GetBytes("NEO"))); // This call triggers not only NEO but also unclaimed GAS - resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "transfer", [ - new ContractParameter { Type = ContractParameterType.Hash160, Value = MultisigScriptHash }, - new ContractParameter { Type = ContractParameterType.Hash160, Value = ValidatorScriptHash }, - new ContractParameter { Type = ContractParameterType.Integer, Value = 1 }, - new ContractParameter { Type = ContractParameterType.Any }, - ], multisigSigner, true); + resp = _rpcServer.ProcessRequestMock(_mockHttpContext.Object, "invokefunction", new JArray(NeoToken.NEO.Hash.ToString(), "transfer", new JArray([ + new JObject() { ["type"] = nameof(ContractParameterType.Hash160), ["value"] = MultisigScriptHash.ToString() }, + new JObject() { ["type"] = nameof(ContractParameterType.Hash160), ["value"] = ValidatorScriptHash.ToString() }, + new JObject() { ["type"] = nameof(ContractParameterType.Integer), ["value"] = "1" }, + new JObject() { ["type"] = nameof(ContractParameterType.Any) }, + ]), multisigSigner, true)).GetAwaiter().GetResult(); Assert.AreEqual(resp.Count, 7); Assert.AreEqual(resp["script"], NeoTransferScript); Assert.IsTrue(resp.ContainsProperty("gasconsumed")); @@ -105,7 +97,7 @@ public void TestInvokeFunction() Assert.AreEqual(resp["state"], nameof(VM.VMState.HALT)); Assert.AreEqual(resp["exception"], $"The smart contract or address {MultisigScriptHash} ({MultisigAddress}) is not found. " + $"If this is your wallet address and you want to sign a transaction with it, make sure you have opened this wallet."); - JArray notifications = (JArray)resp["notifications"]; + var notifications = (JArray)resp["notifications"]; Assert.AreEqual(notifications.Count, 2); Assert.AreEqual(notifications[0]["eventname"].AsString(), "Transfer"); Assert.AreEqual(notifications[0]["contract"].AsString(), NeoToken.NEO.Hash.ToString()); @@ -120,7 +112,7 @@ public void TestInvokeFunction() [TestMethod] public void TestInvokeScript() { - JObject resp = (JObject)_rpcServer.InvokeScript(NeoTotalSupplyScript, validatorSigner, true); + var resp = _rpcServer.ProcessRequestMock(_mockHttpContext.Object, "invokescript", new JArray(NeoTotalSupplyScript, validatorSigner, true)).GetAwaiter().GetResult(); Assert.AreEqual(resp.Count, 7); Assert.IsTrue(resp.ContainsProperty("gasconsumed")); Assert.IsTrue(resp.ContainsProperty("diagnostics")); @@ -131,31 +123,31 @@ public void TestInvokeScript() Assert.AreEqual(resp["stack"][0]["type"], nameof(Neo.VM.Types.Integer)); Assert.AreEqual(resp["stack"][0]["value"], "100000000"); - resp = (JObject)_rpcServer.InvokeScript(NeoTransferScript); - Assert.AreEqual(resp.Count, 6); + resp = _rpcServer.ProcessRequestMock(_mockHttpContext.Object, "invokescript", new JArray(NeoTransferScript, validatorSigner, true)).GetAwaiter().GetResult(); + Assert.AreEqual(resp.Count, 7); Assert.AreEqual(resp["stack"][0]["type"], nameof(Neo.VM.Types.Boolean)); Assert.AreEqual(resp["stack"][0]["value"], false); } [TestMethod] - public void TestTraverseIterator() + public async void TestTraverseIterator() { // GetAllCandidates that should return 0 candidates - JObject resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, 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); + var resp = _rpcServer.ProcessRequestMock(_mockHttpContext.Object, "invokefunction", new JArray(NeoToken.NEO.Hash.ToString(), "getAllCandidates", new JArray([]), validatorSigner, true)).GetAwaiter().GetResult(); + var sessionId = resp["session"].AsString(); + var iteratorId = resp["stack"][0]["id"].AsString(); + var respArray = (JArray)(JToken)_rpcServer.ProcessRequestMock(_mockHttpContext.Object, "traverseiterator", new JArray([sessionId, iteratorId, 100])).GetAwaiter().GetResult(); Assert.AreEqual(respArray.Count, 0); - _rpcServer.TerminateSession(sessionId); - Assert.ThrowsException(() => (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100), "Unknown session"); + _rpcServer.ProcessRequestMock(_mockHttpContext.Object, "terminatesession", new JArray([sessionId])).GetAwaiter().GetResult(); + Assert.ThrowsException(() => _rpcServer.ProcessRequestMock(_mockHttpContext.Object, "traverseiterator", new JArray([sessionId, iteratorId, 100])).GetAwaiter().GetResult(), "Unknown session"); // register candidate in snapshot - resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "registerCandidate", - [new ContractParameter + resp = (JObject)_rpcServer.ProcessRequestMock(_mockHttpContext.Object, "invokefunction", new JArray(NeoToken.NEO.Hash.ToString(), "registerCandidate", + new JArray([new JObject() { - Type = ContractParameterType.PublicKey, - Value = TestProtocolSettings.SoleNode.StandbyCommittee[0], - }], validatorSigner, true); + ["type"] = nameof(ContractParameterType.PublicKey), + ["value"] = TestProtocolSettings.SoleNode.StandbyCommittee[0].ToString(), + }]), validatorSigner, true)).GetAwaiter().GetResult(); Assert.AreEqual(resp["state"], nameof(VM.VMState.HALT)); SnapshotCache snapshot = _neoSystem.GetSnapshotCache(); Transaction? tx = new Transaction @@ -171,10 +163,11 @@ [new ContractParameter engine.SnapshotCache.Commit(); // GetAllCandidates that should return 1 candidate - resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true); - sessionId = Guid.Parse(resp["session"].AsString()); - iteratorId = Guid.Parse(resp["stack"][0]["id"].AsString()); - respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100); + resp = _rpcServer.ProcessRequestMock(_mockHttpContext.Object, "invokeFunction", new JArray(NeoToken.NEO.Hash.ToString(), "getAllCandidates", new JArray([]), validatorSigner, true)).GetAwaiter().GetResult(); + sessionId = resp["session"].AsString(); + iteratorId = resp["stack"][0]["id"].AsString(); + respArray = (JArray)(JToken)_rpcServer.ProcessRequestMock(_mockHttpContext.Object, "traverseIterator", new JArray([sessionId, iteratorId, 100])).GetAwaiter().GetResult(); + Assert.AreEqual(respArray.Count, 1); Assert.AreEqual(respArray[0]["type"], nameof(Neo.VM.Types.Struct)); JArray value = (JArray)respArray[0]["value"]; @@ -185,50 +178,50 @@ [new ContractParameter Assert.AreEqual(value[1]["value"], "0"); // No result when traversed again - respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100); + respArray = (JArray)(JToken)_rpcServer.ProcessRequestMock(_mockHttpContext.Object, "traverseIterator", new JArray([sessionId, iteratorId, 100])).GetAwaiter().GetResult(); Assert.AreEqual(respArray.Count, 0); // GetAllCandidates again - resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true); - sessionId = Guid.Parse(resp["session"].AsString()); - iteratorId = Guid.Parse(resp["stack"][0]["id"].AsString()); + resp = _rpcServer.ProcessRequestMock(_mockHttpContext.Object, "invokeFunction", new JArray(NeoToken.NEO.Hash.ToString(), "getAllCandidates", new JArray([]), validatorSigner, true)).GetAwaiter().GetResult(); + sessionId = resp["session"].AsString(); + iteratorId = resp["stack"][0]["id"].AsString(); // Insufficient result count limit - respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 0); + respArray = (JArray)(JToken)_rpcServer.ProcessRequestMock(_mockHttpContext.Object, "traverseIterator", new JArray([sessionId, iteratorId, 0])).GetAwaiter().GetResult(); Assert.AreEqual(respArray.Count, 0); - respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 1); + respArray = (JArray)(JToken)_rpcServer.ProcessRequestMock(_mockHttpContext.Object, "traverseIterator", new JArray([sessionId, iteratorId, 1])).GetAwaiter().GetResult(); Assert.AreEqual(respArray.Count, 1); - respArray = (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 1); + respArray = (JArray)(JToken)_rpcServer.ProcessRequestMock(_mockHttpContext.Object, "traverseIterator", new JArray([sessionId, iteratorId, 1])).GetAwaiter().GetResult(); Assert.AreEqual(respArray.Count, 0); // 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); - Guid notExpiredSessionId = Guid.Parse(resp["session"].AsString()); - Guid notExpiredIteratorId = Guid.Parse(resp["stack"][0]["id"].AsString()); + resp = _rpcServer.ProcessRequestMock(_mockHttpContext.Object, "InvokeFunction", new JArray(NeoToken.NEO.Hash.ToString(), "getAllCandidates", new JArray([]), validatorSigner, true)).GetAwaiter().GetResult(); + string notExpiredSessionId = resp["session"].AsString(); + string notExpiredIteratorId = resp["stack"][0]["id"].AsString(); _rpcServer.OnTimer(new object()); - Assert.ThrowsException(() => (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100), "Unknown session"); + Assert.ThrowsException(() => _rpcServer.ProcessRequestMock(_mockHttpContext.Object, "traverseIterator", new JArray([sessionId, iteratorId, 100])).GetAwaiter().GetResult(), "Unknown session"); // If you want to run the following line without exception, // DO NOT BREAK IN THE DEBUGGER, because the session expires quickly - respArray = (JArray)_rpcServer.TraverseIterator(notExpiredSessionId, notExpiredIteratorId, 1); + respArray = (JArray)(JToken)_rpcServer.ProcessRequestMock(_mockHttpContext.Object, "traverseIterator", new JArray([notExpiredSessionId, notExpiredIteratorId, 1])).GetAwaiter().GetResult(); Assert.AreEqual(respArray.Count, 1); // Mocking disposal - resp = (JObject)_rpcServer.InvokeFunction(NeoToken.NEO.Hash.ToString(), "getAllCandidates", [], validatorSigner, true); - sessionId = Guid.Parse(resp["session"].AsString()); - iteratorId = Guid.Parse(resp["stack"][0]["id"].AsString()); + resp = _rpcServer.ProcessRequestMock(_mockHttpContext.Object, "invokeFunction", new JArray(NeoToken.NEO.Hash.ToString(), "getAllCandidates", new JArray([]), validatorSigner, true)).GetAwaiter().GetResult(); + sessionId = resp["session"].AsString(); + iteratorId = resp["stack"][0]["id"].AsString(); _rpcServer.Dispose_SmartContract(); - Assert.ThrowsException(() => (JArray)_rpcServer.TraverseIterator(sessionId, iteratorId, 100), "Unknown session"); + Assert.ThrowsException(() => _rpcServer.ProcessRequestMock(_mockHttpContext.Object, "traverseIterator", new JArray([sessionId, iteratorId, 100])).GetAwaiter().GetResult(), "Unknown session"); } [TestMethod] public void TestGetUnclaimedGas() { - JObject resp = (JObject)_rpcServer.GetUnclaimedGas(MultisigAddress); + var resp = _rpcServer.ProcessRequestMock(_mockHttpContext.Object, "getunclaimedgas", new JArray([MultisigAddress])).GetAwaiter().GetResult(); Assert.AreEqual(resp["unclaimed"], "50000000"); Assert.AreEqual(resp["address"], MultisigAddress); - resp = (JObject)_rpcServer.GetUnclaimedGas(ValidatorAddress); + resp = (JObject)_rpcServer.ProcessRequestMock(_mockHttpContext.Object, "getunclaimedgas", new JArray([ValidatorAddress])).GetAwaiter().GetResult(); Assert.AreEqual(resp["unclaimed"], "0"); Assert.AreEqual(resp["address"], ValidatorAddress); } diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs index 477934552c..9829ce173f 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs @@ -15,6 +15,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; @@ -422,7 +423,14 @@ 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); + [new SignerWithWitness(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() }], + }, null)]); 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(); diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs index b63380f803..336c70b070 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; using Neo.IO; using Neo.Ledger; using Neo.Persistence; @@ -32,7 +33,9 @@ public partial class UT_RpcServer { private NeoSystem _neoSystem; private RpcServerSettings _rpcServerSettings; - private RpcServer _rpcServer; + private RpcServerMock _rpcServer; + private Mock _mockHttpContext; + private TestMemoryStoreProvider _memoryStoreProvider; private MemoryStore _memoryStore; private readonly NEP6Wallet _wallet = TestUtils.GenerateTestWallet("123"); @@ -54,7 +57,10 @@ public void TestSetup() MaxGasInvoke = 1500_0000_0000, Network = TestProtocolSettings.SoleNode.Network, }; - _rpcServer = new RpcServer(_neoSystem, _rpcServerSettings); + _rpcServer = new RpcServerMock(_neoSystem, _rpcServerSettings); + _mockHttpContext = new Mock(); + _mockHttpContext.Setup(c => c.Request.Headers).Returns(new HeaderDictionary()); + _mockHttpContext.Setup(c => c.Response.Headers).Returns(new HeaderDictionary()); _walletAccount = _wallet.Import("KxuRSsHgJMb3AMSN6B9P3JHNGMFtxmuimqgR9MmXPcv3CLLfusTd"); var key = new KeyBuilder(NativeContract.GAS.Id, 20).Add(_walletAccount.ScriptHash); var snapshot = _neoSystem.GetSnapshotCache();