Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Neo Rpc Methods] fix contact rpc methods parameters #3485

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
120 changes: 120 additions & 0 deletions src/Plugins/RpcServer/Model/SignerOrWitness.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// SignerOrWitness.cs file belongs to the neo project and is free
// software distributed under the MIT software license, see the
// accompanying file LICENSE in the main directory of the
// repository or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.Cryptography.ECC;
using Neo.Json;
using Neo.Network.P2P.Payloads;
using Neo.Wallets;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

namespace Neo.Plugins.RpcServer.Model;

public class SignerOrWitness
Copy link
Member

Choose a reason for hiding this comment

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

Why not make a base class for common stuff? Than two different classes one, being Signer and other being Witness. This way we can get Signer attributes like Rules or other properties that separate the two classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

make it work first, optimize it later. I think you are better than me to make the code more elegent.

Jim8y marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly object _value;

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

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

public bool IsSigner => _value is Signer;

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

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

View workflow job for this annotation

GitHub Actions / Test-Everything

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

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

View workflow job for this annotation

GitHub Actions / Test-Everything

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

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

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

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

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

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

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

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

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

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

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

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

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

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

View workflow job for this annotation

GitHub Actions / Test (windows-latest)

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

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

View workflow job for this annotation

GitHub Actions / Test (windows-latest)

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

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

View workflow job for this annotation

GitHub Actions / Test (macos-latest)

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

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

View workflow job for this annotation

GitHub Actions / Test (macos-latest)

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

if (value == null)
return false;

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

return false;
}

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

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

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

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

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

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

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

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

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

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

var ret = _params.Select(u => new Signer
{
Account = AddressToScriptHash(u["account"].AsString(), settings.AddressVersion),
Scopes = (WitnessScope)Enum.Parse(typeof(WitnessScope), u["scopes"]?.AsString()),
AllowedContracts = ((JArray)u["allowedcontracts"])?.Select(p => UInt160.Parse(p.AsString())).ToArray() ?? Array.Empty<UInt160>(),
AllowedGroups = ((JArray)u["allowedgroups"])?.Select(p => ECPoint.Parse(p.AsString(), ECCurve.Secp256r1)).ToArray() ?? Array.Empty<ECPoint>(),
Rules = ((JArray)u["rules"])?.Select(r => WitnessRule.FromJson((JObject)r)).ToArray() ?? Array.Empty<WitnessRule>(),
}).ToArray();

// Validate format

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

return ret;
}

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

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

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

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

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

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

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

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

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

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

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

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