Merge pull request #1282 from nspcc-dev/rpc/invoke_with_hashes

rpc: use hashes for verifying in `invoke*` calls
This commit is contained in:
Roman Khimov 2020-08-07 17:00:36 +03:00 committed by GitHub
commit ebcec6e5dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 183 additions and 69 deletions

View file

@ -68,6 +68,9 @@ import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
func Main(op string, args []interface{}) { func Main(op string, args []interface{}) {
runtime.Notify("Hello world!") runtime.Notify("Hello world!")
}` }`
// hashesForVerifyingSeparator delimits `invoke*` parameters from hashes for verifying
hashesForVerifyingSeparator = "--"
) )
// NewCommands returns 'contract' command. // NewCommands returns 'contract' command.
@ -169,8 +172,9 @@ func NewCommands() []cli.Command {
{ {
Name: "testinvoke", Name: "testinvoke",
Usage: "invoke deployed contract on the blockchain (test mode)", Usage: "invoke deployed contract on the blockchain (test mode)",
UsageText: "neo-go contract testinvoke -e endpoint scripthash [arguments...]", UsageText: "neo-go contract testinvoke -e endpoint scripthash [arguments...] [-- hashesForVerifying...]",
Description: `Executes given (as a script hash) deployed script with the given arguments. Description: `Executes given (as a script hash) deployed script with the given arguments
and hashes to verify using runtime.CheckWitness syscall.
It's very similar to the tesinvokefunction command, but differs in the way It's very similar to the tesinvokefunction command, but differs in the way
arguments are being passed. This invoker does not accept method parameter arguments are being passed. This invoker does not accept method parameter
and it passes all given parameters as plain values to the contract, not and it passes all given parameters as plain values to the contract, not
@ -189,13 +193,14 @@ func NewCommands() []cli.Command {
{ {
Name: "testinvokefunction", Name: "testinvokefunction",
Usage: "invoke deployed contract on the blockchain (test mode)", Usage: "invoke deployed contract on the blockchain (test mode)",
UsageText: "neo-go contract testinvokefunction -e endpoint scripthash [method] [arguments...]", UsageText: "neo-go contract testinvokefunction -e endpoint scripthash [method] [arguments...] [-- hashesForVerifying...]",
Description: `Executes given (as a script hash) deployed script with the given method and Description: `Executes given (as a script hash) deployed script with the given method,
arguments. If no method is given "" is passed to the script, if no arguments arguments and hashes to verify using System.Runtime.CheckWitness syscall.
are given, an empty array is passed. All of the given arguments are If no method is given "" is passed to the script, if no arguments
encapsulated into array before invoking the script. The script thus should are given, an empty array is passed, if no hashes are given, no array is
follow the regular convention of smart contract arguments (method string and passed. All of the given arguments are encapsulated into array before
an array of other arguments). invoking the script. The script thus should follow the regular convention
of smart contract arguments (method string and an array of other arguments).
Arguments always do have regular Neo smart contract parameter types, either Arguments always do have regular Neo smart contract parameter types, either
specified explicitly or being inferred from the value. To specify the type specified explicitly or being inferred from the value. To specify the type
@ -251,6 +256,15 @@ func NewCommands() []cli.Command {
* 'string\:string' is a string with a value of 'string:string' * 'string\:string' is a string with a value of 'string:string'
* '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c' is a * '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c' is a
key with a value of '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c' key with a value of '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c'
HashesForVerifying represent a set of Uint160 hashes which are used to verify
hashes in System.Runtime.CheckWitness syscall. To specify hash use its
hex-encoded 160 bit (20 byte) LE representation with optional '0x' prefix.
If no hashes were specified, no array is passed.
Examples:
* '0000000009070e030d0f0e020d0c06050e030c02'
* '0x0000000009070e030d0f0e020d0c06050e030c02'
`, `,
Action: testInvokeFunction, Action: testInvokeFunction,
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -260,6 +274,7 @@ func NewCommands() []cli.Command {
{ {
Name: "testinvokescript", Name: "testinvokescript",
Usage: "Invoke compiled AVM code on the blockchain (test mode, not creating a transaction for it)", Usage: "Invoke compiled AVM code on the blockchain (test mode, not creating a transaction for it)",
UsageText: "neo-go contract testinvokescript -e endpoint -i testcontract.avm [-- hashesForVerifying...]",
Action: testInvokeScript, Action: testInvokeScript,
Flags: []cli.Flag{ Flags: []cli.Flag{
endpointFlag, endpointFlag,
@ -412,6 +427,8 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
operation string operation string
params = make([]smartcontract.Parameter, 0) params = make([]smartcontract.Parameter, 0)
paramsStart = 1 paramsStart = 1
hashesForVerifying []util.Uint160
hashesForVerifyingStart = 0
resp *result.Invoke resp *result.Invoke
acc *wallet.Account acc *wallet.Account
) )
@ -435,6 +452,13 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
} }
if len(args) > paramsStart { if len(args) > paramsStart {
for k, s := range args[paramsStart:] { for k, s := range args[paramsStart:] {
if s == hashesForVerifyingSeparator {
if signAndPush {
return cli.NewExitError("adding hashes for verifying available for test invokes only", 1)
}
hashesForVerifyingStart = paramsStart + k + 1
break
}
param, err := smartcontract.NewParameterFromString(s) param, err := smartcontract.NewParameterFromString(s)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to parse argument #%d: %v", k+paramsStart+1, err), 1) return cli.NewExitError(fmt.Errorf("failed to parse argument #%d: %v", k+paramsStart+1, err), 1)
@ -443,6 +467,16 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
} }
} }
if len(args) >= hashesForVerifyingStart && hashesForVerifyingStart > 0 {
for i, c := range args[hashesForVerifyingStart:] {
h, err := parseUint160(c)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to parse hash for verifying #%d: %v", i+1, err), 1)
}
hashesForVerifying = append(hashesForVerifying, h)
}
}
if signAndPush { if signAndPush {
gas = flags.Fixed8FromContext(ctx, "gas") gas = flags.Fixed8FromContext(ctx, "gas")
acc, err = getAccFromContext(ctx) acc, err = getAccFromContext(ctx)
@ -456,9 +490,9 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
} }
if withMethod { if withMethod {
resp, err = c.InvokeFunction(script, operation, params) resp, err = c.InvokeFunction(script, operation, params, hashesForVerifying)
} else { } else {
resp, err = c.Invoke(script, params) resp, err = c.Invoke(script, params, hashesForVerifying)
} }
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
@ -503,13 +537,25 @@ func testInvokeScript(ctx *cli.Context) error {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
args := ctx.Args()
var hashesForVerifying []util.Uint160
if args.Present() {
for i, c := range args[:] {
h, err := parseUint160(c)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to parse hash for verifying #%d: %v", i+1, err), 1)
}
hashesForVerifying = append(hashesForVerifying, h)
}
}
c, err := client.New(context.TODO(), endpoint, client.Options{}) c, err := client.New(context.TODO(), endpoint, client.Options{})
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
scriptHex := hex.EncodeToString(b) scriptHex := hex.EncodeToString(b)
resp, err := c.InvokeScript(scriptHex) resp, err := c.InvokeScript(scriptHex, hashesForVerifying)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
@ -524,6 +570,13 @@ func testInvokeScript(ctx *cli.Context) error {
return nil return nil
} }
func parseUint160(s string) (util.Uint160, error) {
if len(s) == 2*util.Uint160Size+2 && s[0] == '0' && s[1] == 'x' {
s = s[2:]
}
return util.Uint160DecodeStringLE(s)
}
// ProjectConfig contains project metadata. // ProjectConfig contains project metadata.
type ProjectConfig struct { type ProjectConfig struct {
Version uint Version uint

View file

@ -2450,8 +2450,8 @@ func (bc *Blockchain) GetScriptHashesForVerifying(t *transaction.Transaction) ([
} }
// GetTestVM returns a VM and a Store setup for a test run of some sort of code. // GetTestVM returns a VM and a Store setup for a test run of some sort of code.
func (bc *Blockchain) GetTestVM() *vm.VM { func (bc *Blockchain) GetTestVM(tx *transaction.Transaction) *vm.VM {
systemInterop := bc.newInteropContext(trigger.Application, bc.dao, nil, nil) systemInterop := bc.newInteropContext(trigger.Application, bc.dao, nil, tx)
vm := systemInterop.SpawnVM() vm := systemInterop.SpawnVM()
vm.SetPriceGetter(getPrice) vm.SetPriceGetter(getPrice)
return vm return vm

View file

@ -45,7 +45,7 @@ type Blockchainer interface {
GetStateRoot(height uint32) (*state.MPTRootState, error) GetStateRoot(height uint32) (*state.MPTRootState, error)
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)
GetTestVM() *vm.VM GetTestVM(tx *transaction.Transaction) *vm.VM
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error) GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
GetUnspentCoinState(util.Uint256) *state.UnspentCoin GetUnspentCoinState(util.Uint256) *state.UnspentCoin
References(t *transaction.Transaction) ([]transaction.InOut, error) References(t *transaction.Transaction) ([]transaction.InOut, error)

View file

@ -602,7 +602,7 @@ func (ic *interopContext) contractMigrate(v *vm.VM) error {
ic.dao.MigrateNEP5Balances(hash, contract.ScriptHash()) ic.dao.MigrateNEP5Balances(hash, contract.ScriptHash())
// save NEP5 metadata if any // save NEP5 metadata if any
v := ic.bc.GetTestVM() v := ic.bc.GetTestVM(nil)
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, hash, "decimals") emit.AppCallWithOperationAndArgs(w.BinWriter, hash, "decimals")
v.SetGasLimit(ic.bc.GetConfig().FreeGasLimit) v.SetGasLimit(ic.bc.GetConfig().FreeGasLimit)

View file

@ -123,7 +123,7 @@ func (chain testChain) GetStateRoot(height uint32) (*state.MPTRootState, error)
func (chain testChain) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem { func (chain testChain) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetTestVM() *vm.VM { func (chain testChain) GetTestVM(tx *transaction.Transaction) *vm.VM {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) { func (chain testChain) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) {

View file

@ -18,7 +18,7 @@ import (
// NEP5Decimals invokes `decimals` NEP5 method on a specified contract. // NEP5Decimals invokes `decimals` NEP5 method on a specified contract.
func (c *Client) NEP5Decimals(tokenHash util.Uint160) (int64, error) { func (c *Client) NEP5Decimals(tokenHash util.Uint160) (int64, error) {
result, err := c.InvokeFunction(tokenHash.StringLE(), "decimals", []smartcontract.Parameter{}) result, err := c.InvokeFunction(tokenHash.StringLE(), "decimals", []smartcontract.Parameter{}, nil)
if err != nil { if err != nil {
return 0, err return 0, err
} else if result.State != "HALT" || len(result.Stack) == 0 { } else if result.State != "HALT" || len(result.Stack) == 0 {
@ -30,7 +30,7 @@ func (c *Client) NEP5Decimals(tokenHash util.Uint160) (int64, error) {
// NEP5Name invokes `name` NEP5 method on a specified contract. // NEP5Name invokes `name` NEP5 method on a specified contract.
func (c *Client) NEP5Name(tokenHash util.Uint160) (string, error) { func (c *Client) NEP5Name(tokenHash util.Uint160) (string, error) {
result, err := c.InvokeFunction(tokenHash.StringLE(), "name", []smartcontract.Parameter{}) result, err := c.InvokeFunction(tokenHash.StringLE(), "name", []smartcontract.Parameter{}, nil)
if err != nil { if err != nil {
return "", err return "", err
} else if result.State != "HALT" || len(result.Stack) == 0 { } else if result.State != "HALT" || len(result.Stack) == 0 {
@ -42,7 +42,7 @@ func (c *Client) NEP5Name(tokenHash util.Uint160) (string, error) {
// NEP5Symbol invokes `symbol` NEP5 method on a specified contract. // NEP5Symbol invokes `symbol` NEP5 method on a specified contract.
func (c *Client) NEP5Symbol(tokenHash util.Uint160) (string, error) { func (c *Client) NEP5Symbol(tokenHash util.Uint160) (string, error) {
result, err := c.InvokeFunction(tokenHash.StringLE(), "symbol", []smartcontract.Parameter{}) result, err := c.InvokeFunction(tokenHash.StringLE(), "symbol", []smartcontract.Parameter{}, nil)
if err != nil { if err != nil {
return "", err return "", err
} else if result.State != "HALT" || len(result.Stack) == 0 { } else if result.State != "HALT" || len(result.Stack) == 0 {
@ -54,7 +54,7 @@ func (c *Client) NEP5Symbol(tokenHash util.Uint160) (string, error) {
// NEP5TotalSupply invokes `totalSupply` NEP5 method on a specified contract. // NEP5TotalSupply invokes `totalSupply` NEP5 method on a specified contract.
func (c *Client) NEP5TotalSupply(tokenHash util.Uint160) (int64, error) { func (c *Client) NEP5TotalSupply(tokenHash util.Uint160) (int64, error) {
result, err := c.InvokeFunction(tokenHash.StringLE(), "totalSupply", []smartcontract.Parameter{}) result, err := c.InvokeFunction(tokenHash.StringLE(), "totalSupply", []smartcontract.Parameter{}, nil)
if err != nil { if err != nil {
return 0, err return 0, err
} else if result.State != "HALT" || len(result.Stack) == 0 { } else if result.State != "HALT" || len(result.Stack) == 0 {
@ -66,7 +66,7 @@ func (c *Client) NEP5TotalSupply(tokenHash util.Uint160) (int64, error) {
// NEP5BalanceOf invokes `balanceOf` NEP5 method on a specified contract. // NEP5BalanceOf invokes `balanceOf` NEP5 method on a specified contract.
func (c *Client) NEP5BalanceOf(tokenHash util.Uint160) (int64, error) { func (c *Client) NEP5BalanceOf(tokenHash util.Uint160) (int64, error) {
result, err := c.InvokeFunction(tokenHash.StringLE(), "balanceOf", []smartcontract.Parameter{}) result, err := c.InvokeFunction(tokenHash.StringLE(), "balanceOf", []smartcontract.Parameter{}, nil)
if err != nil { if err != nil {
return 0, err return 0, err
} else if result.State != "HALT" || len(result.Stack) == 0 { } else if result.State != "HALT" || len(result.Stack) == 0 {

View file

@ -394,39 +394,34 @@ func (c *Client) GetVersion() (*result.Version, error) {
// InvokeScript returns the result of the given script after running it true the VM. // InvokeScript returns the result of the given script after running it true the VM.
// NOTE: This is a test invoke and will not affect the blockchain. // NOTE: This is a test invoke and will not affect the blockchain.
func (c *Client) InvokeScript(script string) (*result.Invoke, error) { func (c *Client) InvokeScript(script string, hashesForVerifying []util.Uint160) (*result.Invoke, error) {
var ( params := request.NewRawParams(script)
params = request.NewRawParams(script) return c.invokeSomething("invokescript", params, hashesForVerifying)
resp = &result.Invoke{}
)
if err := c.performRequest("invokescript", params, resp); err != nil {
return nil, err
}
return resp, nil
} }
// InvokeFunction returns the results after calling the smart contract scripthash // InvokeFunction returns the results after calling the smart contract scripthash
// with the given operation and parameters. // with the given operation and parameters.
// NOTE: this is test invoke and will not affect the blockchain. // NOTE: this is test invoke and will not affect the blockchain.
func (c *Client) InvokeFunction(script, operation string, params []smartcontract.Parameter) (*result.Invoke, error) { func (c *Client) InvokeFunction(script, operation string, params []smartcontract.Parameter, hashesForVerifying []util.Uint160) (*result.Invoke, error) {
var ( p := request.NewRawParams(script, operation, params)
p = request.NewRawParams(script, operation, params) return c.invokeSomething("invokefunction", p, hashesForVerifying)
resp = &result.Invoke{}
)
if err := c.performRequest("invokefunction", p, resp); err != nil {
return nil, err
}
return resp, nil
} }
// Invoke returns the results after calling the smart contract scripthash // Invoke returns the results after calling the smart contract scripthash
// with the given parameters. // with the given parameters.
func (c *Client) Invoke(script string, params []smartcontract.Parameter) (*result.Invoke, error) { // NOTE: this is test invoke and will not affect the blockchain.
var ( func (c *Client) Invoke(script string, params []smartcontract.Parameter, hashesForVerifying []util.Uint160) (*result.Invoke, error) {
p = request.NewRawParams(script, params) p := request.NewRawParams(script, params)
resp = &result.Invoke{} return c.invokeSomething("invoke", p, hashesForVerifying)
) }
if err := c.performRequest("invoke", p, resp); err != nil {
// invokeSomething is an inner wrapper for Invoke* functions
func (c *Client) invokeSomething(method string, p request.RawParams, hashesForVerifying []util.Uint160) (*result.Invoke, error) {
var resp = new(result.Invoke)
if hashesForVerifying != nil {
p.Values = append(p.Values, hashesForVerifying)
}
if err := c.performRequest(method, p, resp); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil

View file

@ -813,7 +813,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
Type: smartcontract.Hash160Type, Type: smartcontract.Hash160Type,
Value: hash, Value: hash,
}, },
}) }, nil)
}, },
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf","state":"HALT","gas_consumed":"0.311","stack":[{"type":"ByteArray","value":"262bec084432"}],"tx":"d101361426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf000000000000000000000000"}}`, serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf","state":"HALT","gas_consumed":"0.311","stack":[{"type":"ByteArray","value":"262bec084432"}],"tx":"d101361426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf000000000000000000000000"}}`,
result: func(c *Client) interface{} { result: func(c *Client) interface{} {
@ -839,7 +839,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
{ {
name: "positive", name: "positive",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.InvokeScript("00046e616d656724058e5e1b6008847cd662728549088a9ee82191") return c.InvokeScript("00046e616d656724058e5e1b6008847cd662728549088a9ee82191", nil)
}, },
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"00046e616d656724058e5e1b6008847cd662728549088a9ee82191","state":"HALT","gas_consumed":"0.161","stack":[{"type":"ByteArray","value":"4e45503520474153"}],"tx":"d1011b00046e616d656724058e5e1b6008847cd662728549088a9ee82191000000000000000000000000"}}`, serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"00046e616d656724058e5e1b6008847cd662728549088a9ee82191","state":"HALT","gas_consumed":"0.161","stack":[{"type":"ByteArray","value":"4e45503520474153"}],"tx":"d1011b00046e616d656724058e5e1b6008847cd662728549088a9ee82191000000000000000000000000"}}`,
result: func(c *Client) interface{} { result: func(c *Client) interface{} {
@ -1186,13 +1186,13 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
{ {
name: "invokefunction_invalid_params_error", name: "invokefunction_invalid_params_error",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.InvokeFunction("", "", []smartcontract.Parameter{}) return c.InvokeFunction("", "", []smartcontract.Parameter{}, nil)
}, },
}, },
{ {
name: "invokescript_invalid_params_error", name: "invokescript_invalid_params_error",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.InvokeScript("") return c.InvokeScript("", nil)
}, },
}, },
{ {
@ -1392,13 +1392,13 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
{ {
name: "invokefunction_unmarshalling_error", name: "invokefunction_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.InvokeFunction("", "", []smartcontract.Parameter{}) return c.InvokeFunction("", "", []smartcontract.Parameter{}, nil)
}, },
}, },
{ {
name: "invokescript_unmarshalling_error", name: "invokescript_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.InvokeScript("") return c.InvokeScript("", nil)
}, },
}, },
{ {

View file

@ -164,6 +164,27 @@ func (p *Param) GetUint160FromAddressOrHex() (util.Uint160, error) {
return p.GetUint160FromAddress() return p.GetUint160FromAddress()
} }
// GetArrayUint160FromHex returns array of Uint160 values of the parameter that
// was supply as array of raw hex.
func (p *Param) GetArrayUint160FromHex() ([]util.Uint160, error) {
if p == nil {
return nil, nil
}
arr, err := p.GetArray()
if err != nil {
return nil, err
}
var result = make([]util.Uint160, len(arr))
for i, parameter := range arr {
hash, err := parameter.GetUint160FromHex()
if err != nil {
return nil, err
}
result[i] = hash
}
return result, nil
}
// GetFuncParam returns current parameter as a function call parameter. // GetFuncParam returns current parameter as a function call parameter.
func (p *Param) GetFuncParam() (FuncParam, error) { func (p *Param) GetFuncParam() (FuncParam, error) {
if p == nil { if p == nil {

View file

@ -185,6 +185,24 @@ func TestParam_GetUint160FromAddressOrHex(t *testing.T) {
}) })
} }
func TestParam_GetArrayUint160FromHex(t *testing.T) {
in1 := util.Uint160{1, 2, 3}
in2 := util.Uint160{4, 5, 6}
p := Param{Type: ArrayT, Value: []Param{
{
Type: StringT,
Value: in1.StringLE(),
},
{
Type: StringT,
Value: in2.StringLE(),
},
}}
arr, err := p.GetArrayUint160FromHex()
require.NoError(t, err)
require.Equal(t, []util.Uint160{in1, in2}, arr)
}
func TestParamGetFuncParam(t *testing.T) { func TestParamGetFuncParam(t *testing.T) {
fp := FuncParam{ fp := FuncParam{
Type: smartcontract.StringType, Type: smartcontract.StringType,

View file

@ -1097,24 +1097,37 @@ func (s *Server) invoke(reqParams request.Params) (interface{}, *response.Error)
if err != nil { if err != nil {
return nil, response.ErrInvalidParams return nil, response.ErrInvalidParams
} }
hashesForVerifying, err := reqParams.ValueWithType(2, request.ArrayT).GetArrayUint160FromHex()
if err != nil {
return nil, response.ErrInvalidParams
}
script, err := request.CreateInvocationScript(scriptHash, slice) script, err := request.CreateInvocationScript(scriptHash, slice)
if err != nil { if err != nil {
return nil, response.NewInternalServerError("can't create invocation script", err) return nil, response.NewInternalServerError("can't create invocation script", err)
} }
return s.runScriptInVM(script), nil return s.runScriptInVM(script, hashesForVerifying), nil
} }
// invokescript implements the `invokescript` RPC call. // invokeFunction implements the `invokefunction` RPC call.
func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *response.Error) { func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *response.Error) {
scriptHash, err := reqParams.ValueWithType(0, request.StringT).GetUint160FromHex() scriptHash, err := reqParams.ValueWithType(0, request.StringT).GetUint160FromHex()
if err != nil { if err != nil {
return nil, response.ErrInvalidParams return nil, response.ErrInvalidParams
} }
script, err := request.CreateFunctionInvocationScript(scriptHash, reqParams[1:]) var hashesForVerifying []util.Uint160
hashesForVerifyingIndex := len(reqParams)
if hashesForVerifyingIndex > 3 {
hashesForVerifying, err = reqParams.ValueWithType(3, request.ArrayT).GetArrayUint160FromHex()
if err != nil {
return nil, response.ErrInvalidParams
}
hashesForVerifyingIndex--
}
script, err := request.CreateFunctionInvocationScript(scriptHash, reqParams[1:hashesForVerifyingIndex])
if err != nil { if err != nil {
return nil, response.NewInternalServerError("can't create invocation script", err) return nil, response.NewInternalServerError("can't create invocation script", err)
} }
return s.runScriptInVM(script), nil return s.runScriptInVM(script, hashesForVerifying), nil
} }
// invokescript implements the `invokescript` RPC call. // invokescript implements the `invokescript` RPC call.
@ -1128,13 +1141,27 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.
return nil, response.ErrInvalidParams return nil, response.ErrInvalidParams
} }
return s.runScriptInVM(script), nil hashesForVerifying, err := reqParams.ValueWithType(1, request.ArrayT).GetArrayUint160FromHex()
if err != nil {
return nil, response.ErrInvalidParams
}
return s.runScriptInVM(script, hashesForVerifying), nil
} }
// runScriptInVM runs given script in a new test VM and returns the invocation // runScriptInVM runs given script in a new test VM and returns the invocation
// result. // result.
func (s *Server) runScriptInVM(script []byte) *result.Invoke { func (s *Server) runScriptInVM(script []byte, scriptHashesForVerifying []util.Uint160) *result.Invoke {
vm := s.chain.GetTestVM() var tx *transaction.Transaction
if count := len(scriptHashesForVerifying); count != 0 {
tx := new(transaction.Transaction)
tx.Attributes = make([]transaction.Attribute, count)
for i, a := range tx.Attributes {
a.Data = scriptHashesForVerifying[i].BytesBE()
a.Usage = transaction.Script
}
}
vm := s.chain.GetTestVM(tx)
vm.SetGasLimit(s.config.MaxGasInvoke) vm.SetGasLimit(s.config.MaxGasInvoke)
vm.LoadScript(script) vm.LoadScript(script)
_ = vm.Run() _ = vm.Run()