-
Notifications
You must be signed in to change notification settings - Fork 100
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
fix verify api #564
fix verify api #564
Conversation
src/RpcServer/RpcServer.Wallet.cs
Outdated
|
||
Transaction tx = signers == null ? null : new Transaction | ||
{ | ||
Signers = signers.GetSigners(), | ||
Attributes = Array.Empty<TransactionAttribute>() | ||
Attributes = Array.Empty<TransactionAttribute>(), | ||
Witnesses = signers.Witnesses, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Witnesses
it wil lbe null, isn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, just like
Witnesses = signers.Witnesses, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed it since no use here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we pass witnesses via RPC request parameters, just like signers are passed? And then fill Witnesses
here.
src/RpcServer/RpcServer.Wallet.cs
Outdated
@@ -348,20 +347,20 @@ private JObject GetVerificationResult(UInt160 scriptHash, ContractParameter[] ar | |||
{ | |||
throw new RpcException(-100, "Unknown contract"); | |||
} | |||
var methodName = "verify"; | |||
var md = contract.Manifest.Abi.GetMethod("verify", 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we set pcount
to -1 here in order to be able to call verify
with arguments? Just like in https://github.com/neo-project/neo/blob/master/src/neo/SmartContract/Helper.cs#L222.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated it.
using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CreateSnapshot(), settings: system.Settings); | ||
engine.LoadScript(new ScriptBuilder().EmitDynamicCall(scriptHash, methodName, args).ToArray(), rvcount: 1); | ||
|
||
engine.LoadContract(contract, md, CallFlags.ReadOnly); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And create an invocation script from the given args (it will push all arguments on the stack). And load the script right after the contract script, just like in https://github.com/neo-project/neo/blob/master/src/neo/SmartContract/Helper.cs#L246.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated it. Please test it like:
{
"jsonrpc": "2.0",
"method": "invokecontractverify",
"params": [
"0x31f714e04766139c8f3705f5ce94518e1411ec07",
[
{
"type": "ByteArray",
"value": "DED2HMp00JZ2zcJYjKgTp4Tl0GODm5r14PKN6ip8i2+8ox3JYqN2xNHxQzCgzENqSD52GhVF4eGx2i405zxM1KTH"
}
],
[
{
"account": "NMA2FKN8up2cEwaJgtmAiDrZWB69ApnDfp",
"scopes": "CalledByEntry"
}
]
],
"id": 1
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not very convenient to construct an invocation script on the client. I would prefer to pass simple standard parameters to the server as we do in the InvokeFunction
RPC method. Like the following:
{
"jsonrpc": "2.0",
"method": "invokecontractverify",
"params": [
"0x31f714e04766139c8f3705f5ce94518e1411ec07",
[
{
"type": "Boolean",
"value": "true"
},
{
"type": "Integer",
"value": 123
}
],
[
{
"account": "NMA2FKN8up2cEwaJgtmAiDrZWB69ApnDfp",
"scopes": "CalledByEntry"
}
]
],
"id": 1
}
And then we can construct an invocation script on the server by performing the following:
using (ScriptBuilder sb = new ScriptBuilder())
{
for (int i = args.Count - 1; i >= 0; i--)
sb.EmitPush(args[i]);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with @AnnaShaleva
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
src/RpcServer/RpcServer.Wallet.cs
Outdated
if (md is null) | ||
throw new RpcException(-101, $"The smart contract {contract.Hash} haven't got verify method without arguments"); | ||
if (md.ReturnType != ContractParameterType.Boolean) | ||
throw new RpcException(-102, "The verify method doesn't return boolean value."); | ||
|
||
Transaction tx = signers == null ? null : new Transaction |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we also fill the transaction when signers == null
? We can do it because we know exactly at least one signer (it's the contract to be called). And we also know the witness for this signer: invocation script pushes args
on the stack; verification script is empty because it's a contract witness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok.
src/RpcServer/RpcServer.Wallet.cs
Outdated
var methodName = "verify"; | ||
var md = contract.Manifest.Abi.GetMethod("verify", -1); | ||
if (md is null) | ||
throw new RpcException(-101, $"The smart contract {contract.Hash} haven't got verify method without arguments"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exception message should also be updated:
throw new RpcException(-101, $"The smart contract {contract.Hash} haven't got verify method");
|
||
Transaction tx = signers == null ? null : new Transaction | ||
{ | ||
Signers = signers.GetSigners(), | ||
Attributes = Array.Empty<TransactionAttribute>() | ||
Attributes = Array.Empty<TransactionAttribute>(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we also fill the transaction script? I think that []byte{OpCode.RET} is enough.
This is needed for those verify
methods that use System.Runtime.GetScriptContainer
interop, because transaction can't be serialized without a script.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
@AnnaShaleva Need your review again. |
src/RpcServer/RpcServer.Wallet.cs
Outdated
{ | ||
Signers = signers.GetSigners(), | ||
Attributes = Array.Empty<TransactionAttribute>() | ||
defaultSigner.AddRange(signers.GetSigners()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make here defaultSigner = signers.GetSigners();
?
It shouldn't be mandatory for the contract signer to be a sender. And if the user wants to provide signers by himself, then he should include the contract signer with the desired scope in the parameters list.
The other option is to check that signers
does not contain the contract signer and if the contract signer is missing, we should add it to the signers
list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated it.
Attributes = Array.Empty<TransactionAttribute>() | ||
Signers = signers == null ? new Signer[] { new() { Account = scriptHash } } : signers.GetSigners(), | ||
Attributes = Array.Empty<TransactionAttribute>(), | ||
Script = new[] { (byte)OpCode.RET } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And the last issue: at the current moment, the transaction is completely missing witnesses. I would suggest to pass witnesses along with signers via RPC request parameters using the following structure:
{
"jsonrpc": "2.0",
"method": "invokecontractverify",
"params": [
"0x31f714e04766139c8f3705f5ce94518e1411ec07",
[ ],
[
{
"account": "NiRqSd5MtRZT5yUhgWd7oG11brkDG76Jim",
"scopes": "CalledByEntry",
"invocation": "DEDr2gA/8T/wxQvgOZVfCdkbj6uGrprkDgJvpOJCcbl+tvlKZkZytCZEWm6NoZhJyIlEI3VQSLtU3AHuJfShAT5L",
"verification": "DCEDAS1H52IQrsc745qz0YbgpA/o2Gv6PU+r/aV7oTuI+WoLQZVEDXg=",
},
{
"account": "NMA2FKN8up2cEwaJgtmAiDrZWB69ApnDfp",
"scopes": "CalledByEntry",
}
]
],
"id": 1
}
And then parse the provided witnesses in GetSignersFromJson
method (https://github.com/neo-project/neo-modules/blob/master/src/RpcServer/RpcServer.SmartContract.cs#L94).
And then fill the transaction like the following:
Transaction tx = new Transaction
{
Witnesses = signers.Witnesses,
...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will help us to get rid of the private key dependency (because if we parse witnesses, then we don't need private keys in the RPC node wallet anymore). And that's the answer to the #552 (comment) question.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there.
Attributes = Array.Empty<TransactionAttribute>() | ||
Signers = signers == null ? new Signer[] { new() { Account = scriptHash } } : signers.GetSigners(), | ||
Attributes = Array.Empty<TransactionAttribute>(), | ||
Witnesses = signers?.Witnesses, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Witnesses = signers?.Witnesses, | |
Witnesses = signers == null ? new Witness() { InvocationScript = sb.ToArray() } : signers.Witnesses, |
where sb
is from
if (args.Length > 0)
{
using ScriptBuilder sb = new ScriptBuilder();
for (int i = args.Length - 1; i >= 0; i--)
sb.EmitPush(args[i]);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sb
will be loaded into engine directly, is this InvocationScript
necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We know exactly the invocation script, why not to fill it in? By doing this we're getting closer to the real transaction witnesses.
.Where(x => x.Invocation != null && x.Verification != null) | ||
.Select(x => new Witness() | ||
{ | ||
InvocationScript = Convert.FromBase64String(x.Invocation), | ||
VerificationScript = Convert.FromBase64String(x.Verification) | ||
}).ToArray() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.Where(x => x.Invocation != null && x.Verification != null) | |
.Select(x => new Witness() | |
{ | |
InvocationScript = Convert.FromBase64String(x.Invocation), | |
VerificationScript = Convert.FromBase64String(x.Verification) | |
}).ToArray() | |
.Where(x => x.Invocation != null || x.Verification != null) | |
.Select(x => new Witness() | |
{ | |
InvocationScript = x.Invocation != null ?? Convert.FromBase64String(x.Invocation) : null, | |
VerificationScript = x.Verification != null ?? Convert.FromBase64String(x.Verification) : null | |
}).ToArray() |
src/RpcServer/RpcServer.Wallet.cs
Outdated
|
||
engine.LoadScript(new Script(sb.ToArray()), configureState: p => p.CallFlags = CallFlags.None); | ||
} | ||
JObject json = new JObject(); | ||
json["script"] = Convert.ToBase64String(contract.Script); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And let the resulting script
be the InvocationScript (which is sb.ToArray()
). Because 1) the user is aware of the contract being invoked and 2) the user should have the ability to check that all the parameters were converted as expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What script
mean if sb
is empty?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That no parameters were pushed on the stack before verify
execution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice changes, it solves the problem.
* fix verify api * refac * default signer & parameter * refac * add Witness * format * update * update * update Co-authored-by: Owen Zhang <38493437+superboyiii@users.noreply.github.com>
Close #552