rpc: refactor invokecontractverify
In `(c *Client) AddNetworkFee` we define network fee for contract witness verification via `invokecontractverify` RPC call, and that's the initial purpose of this RPC method. But it was not implemented correctly. It used `System.Contract.Call` instead of beheiving like `initVerificationVM`. During real contract witness verification the whole contract's script is loaded into VM, and then we jump to the `verify` method. Thus, to define exact contract verification price, we should act like this (and not just perform `System.Contract.Call` of `verify` method). Tests are added. This bug is the reason of adding extra GAS (c.notary.extraVerifyFee) to pre-calculated value in https://github.com/nspcc-dev/neofs-node/pull/404/files#diff-639db437ca2578db46c9e8cbf18f9aa01f8ca5aee30e0fa7e70ba0354822d7b3R237
This commit is contained in:
parent
1261dd5306
commit
edfca68a17
8 changed files with 207 additions and 71 deletions
|
@ -36,9 +36,9 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/services/oracle"
|
||||
"github.com/nspcc-dev/neo-go/pkg/services/oracle/broadcaster"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
@ -1118,7 +1118,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
|
|||
return nil, response.NewInternalServerError("can't create invocation script", err)
|
||||
}
|
||||
tx.Script = script
|
||||
return s.runScriptInVM(trigger.Application, script, tx)
|
||||
return s.runScriptInVM(trigger.Application, script, util.Uint160{}, tx)
|
||||
}
|
||||
|
||||
// invokescript implements the `invokescript` RPC call.
|
||||
|
@ -1144,7 +1144,7 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.
|
|||
tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}}
|
||||
}
|
||||
tx.Script = script
|
||||
return s.runScriptInVM(trigger.Application, script, tx)
|
||||
return s.runScriptInVM(trigger.Application, script, util.Uint160{}, tx)
|
||||
}
|
||||
|
||||
// invokeContractVerify implements the `invokecontractverify` RPC call.
|
||||
|
@ -1154,36 +1154,43 @@ func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *r
|
|||
return nil, responseErr
|
||||
}
|
||||
|
||||
args := reqParams[1:2]
|
||||
var tx *transaction.Transaction
|
||||
bw := io.NewBufBinWriter()
|
||||
if len(reqParams) > 1 {
|
||||
args, err := reqParams[1].GetArray() // second `invokecontractverify` parameter is an array of arguments for `verify` method
|
||||
if err != nil {
|
||||
return nil, response.WrapErrorWithData(response.ErrInvalidParams, err)
|
||||
}
|
||||
if len(args) > 0 {
|
||||
err := request.ExpandArrayIntoScript(bw.BinWriter, args)
|
||||
if err != nil {
|
||||
return nil, response.NewRPCError("can't create witness invocation script", err.Error(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
invocationScript := bw.Bytes()
|
||||
|
||||
tx := &transaction.Transaction{Script: []byte{byte(opcode.RET)}} // need something in script
|
||||
if len(reqParams) > 2 {
|
||||
signers, witnesses, err := reqParams[2].GetSignersWithWitnesses()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
}
|
||||
tx = &transaction.Transaction{
|
||||
Signers: signers,
|
||||
Scripts: witnesses,
|
||||
}
|
||||
tx.Signers = signers
|
||||
tx.Scripts = witnesses
|
||||
} else { // fill the only known signer - the contract with `verify` method
|
||||
tx.Signers = []transaction.Signer{{Account: scriptHash}}
|
||||
tx.Scripts = []transaction.Witness{{InvocationScript: invocationScript, VerificationScript: []byte{}}}
|
||||
}
|
||||
|
||||
cs := s.chain.GetContractState(scriptHash)
|
||||
if cs == nil {
|
||||
return nil, response.NewRPCError("unknown contract", scriptHash.StringBE(), nil)
|
||||
}
|
||||
script, err := request.CreateFunctionInvocationScript(cs.Hash, manifest.MethodVerify, args)
|
||||
if err != nil {
|
||||
return nil, response.NewInternalServerError("can't create invocation script", err)
|
||||
}
|
||||
if tx != nil {
|
||||
tx.Script = script
|
||||
}
|
||||
return s.runScriptInVM(trigger.Verification, script, tx)
|
||||
return s.runScriptInVM(trigger.Verification, invocationScript, scriptHash, tx)
|
||||
}
|
||||
|
||||
// runScriptInVM runs given script in a new test VM and returns the invocation
|
||||
// result.
|
||||
func (s *Server) runScriptInVM(t trigger.Type, script []byte, tx *transaction.Transaction) (*result.Invoke, *response.Error) {
|
||||
// result. The script is either a simple script in case of `application` trigger
|
||||
// witness invocation script in case of `verification` trigger (it pushes `verify`
|
||||
// arguments on stack before verification). In case of contract verification
|
||||
// contractScriptHash should be specified.
|
||||
func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction) (*result.Invoke, *response.Error) {
|
||||
// When transferring funds, script execution does no auto GAS claim,
|
||||
// because it depends on persisting tx height.
|
||||
// This is why we provide block here.
|
||||
|
@ -1197,7 +1204,27 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, tx *transaction.Tr
|
|||
|
||||
vm := s.chain.GetTestVM(t, tx, b)
|
||||
vm.GasLimit = int64(s.config.MaxGasInvoke)
|
||||
vm.LoadScriptWithFlags(script, callflag.All)
|
||||
if t == trigger.Verification {
|
||||
// We need this special case because witnesses verification is not the simple System.Contract.Call,
|
||||
// and we need to define exactly the amount of gas consumed for a contract witness verification.
|
||||
gasPolicy := s.chain.GetPolicer().GetMaxVerificationGAS()
|
||||
if vm.GasLimit > gasPolicy {
|
||||
vm.GasLimit = gasPolicy
|
||||
}
|
||||
|
||||
err := s.chain.InitVerificationVM(vm, func(h util.Uint160) (*state.Contract, error) {
|
||||
res := s.chain.GetContractState(h)
|
||||
if res == nil {
|
||||
return nil, fmt.Errorf("unknown contract: %s", h.StringBE())
|
||||
}
|
||||
return res, nil
|
||||
}, contractScriptHash, &transaction.Witness{InvocationScript: script, VerificationScript: []byte{}})
|
||||
if err != nil {
|
||||
return nil, response.NewInternalServerError("can't prepare verification VM", err)
|
||||
}
|
||||
} else {
|
||||
vm.LoadScriptWithFlags(script, callflag.All)
|
||||
}
|
||||
err = vm.Run()
|
||||
var faultException string
|
||||
if err != nil {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue