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

fix verify api #564

Merged
merged 12 commits into from
Mar 17, 2021
Merged

fix verify api #564

merged 12 commits into from
Mar 17, 2021

Conversation

Ashuaidehao
Copy link
Contributor

Close #552


Transaction tx = signers == null ? null : new Transaction
{
Signers = signers.GetSigners(),
Attributes = Array.Empty<TransactionAttribute>()
Attributes = Array.Empty<TransactionAttribute>(),
Witnesses = signers.Witnesses,
Copy link
Member

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?

Copy link
Contributor Author

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,

Copy link
Contributor Author

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.

Copy link
Member

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.

@@ -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);
Copy link
Member

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.

Copy link
Contributor Author

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);
Copy link
Member

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.

Copy link
Contributor Author

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
}

Copy link
Member

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

Copy link
Member

Choose a reason for hiding this comment

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

Agree with @AnnaShaleva

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated.

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
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok.

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");
Copy link
Member

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>(),
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated.

@superboyiii
Copy link
Member

@AnnaShaleva Need your review again.

{
Signers = signers.GetSigners(),
Attributes = Array.Empty<TransactionAttribute>()
defaultSigner.AddRange(signers.GetSigners());
Copy link
Member

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.

Copy link
Contributor Author

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 }
Copy link
Member

@AnnaShaleva AnnaShaleva Mar 17, 2021

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,
                ...
            }

Copy link
Member

@AnnaShaleva AnnaShaleva Mar 17, 2021

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok

Copy link
Member

@AnnaShaleva AnnaShaleva left a 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,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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]);
             }

Copy link
Contributor Author

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?

Copy link
Member

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.

Comment on lines 108 to 113
.Where(x => x.Invocation != null && x.Verification != null)
.Select(x => new Witness()
{
InvocationScript = Convert.FromBase64String(x.Invocation),
VerificationScript = Convert.FromBase64String(x.Verification)
}).ToArray()
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
.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()


engine.LoadScript(new Script(sb.ToArray()), configureState: p => p.CallFlags = CallFlags.None);
}
JObject json = new JObject();
json["script"] = Convert.ToBase64String(contract.Script);
Copy link
Member

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.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Member

@AnnaShaleva AnnaShaleva left a 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.

@superboyiii superboyiii merged commit 1c24a3f into neo-project:master Mar 17, 2021
@Ashuaidehao Ashuaidehao deleted the Fix-verifyfee branch April 7, 2021 08:25
joeqian10 pushed a commit to joeqian10/neo-modules that referenced this pull request Apr 7, 2021
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

RpcServer Plugin: unexpected invokecontractverify behaviour
4 participants