rpc: update RPC server invoke* calls

part of #1036
This commit is contained in:
Anna Shaleva 2020-06-10 14:45:55 +03:00
parent 3549515fd7
commit d5355acfa9
9 changed files with 229 additions and 11 deletions

View file

@ -1245,8 +1245,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.
func (bc *Blockchain) GetTestVM() *vm.VM {
systemInterop := bc.newInteropContext(trigger.Application, bc.dao, nil, nil)
func (bc *Blockchain) GetTestVM(tx *transaction.Transaction) *vm.VM {
systemInterop := bc.newInteropContext(trigger.Application, bc.dao, nil, tx)
vm := SpawnVM(systemInterop)
vm.SetPriceGetter(getPrice)
return vm

View file

@ -41,7 +41,7 @@ type Blockchainer interface {
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
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)
mempool.Feer // fee interface
PoolTx(*transaction.Transaction) error

View file

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

View file

@ -7,6 +7,7 @@ import (
"fmt"
"strconv"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
@ -65,6 +66,7 @@ const (
TxFilterT
NotificationFilterT
ExecutionFilterT
Cosigner
)
func (p Param) String() string {
@ -154,6 +156,47 @@ func (p Param) GetBytesHex() ([]byte, error) {
return hex.DecodeString(s)
}
// GetCosigner returns transaction.Cosigner value of the parameter.
func (p Param) GetCosigner() (transaction.Cosigner, error) {
c, ok := p.Value.(transaction.Cosigner)
if !ok {
return transaction.Cosigner{}, errors.New("not a cosigner")
}
return c, nil
}
// GetCosigners returns a slice of transaction.Cosigner with global scope from
// array of Uint160 or array of serialized transaction.Cosigner stored in the
// parameter.
func (p Param) GetCosigners() ([]transaction.Cosigner, error) {
hashes, err := p.GetArray()
if err != nil {
return nil, err
}
cosigners := make([]transaction.Cosigner, len(hashes))
// try to extract hashes first
for i, h := range hashes {
var u util.Uint160
u, err = h.GetUint160FromHex()
if err != nil {
break
}
cosigners[i] = transaction.Cosigner{
Account: u,
Scopes: transaction.Global,
}
}
if err != nil {
for i, h := range hashes {
cosigners[i], err = h.GetCosigner()
if err != nil {
return nil, err
}
}
}
return cosigners, nil
}
// UnmarshalJSON implements json.Unmarshaler interface.
func (p *Param) UnmarshalJSON(data []byte) error {
var s string
@ -167,6 +210,7 @@ func (p *Param) UnmarshalJSON(data []byte) error {
{TxFilterT, &TxFilter{}},
{NotificationFilterT, &NotificationFilter{}},
{ExecutionFilterT, &ExecutionFilter{}},
{Cosigner, &transaction.Cosigner{}},
{ArrayT, &[]Param{}},
}
@ -196,6 +240,8 @@ func (p *Param) UnmarshalJSON(data []byte) error {
} else {
continue
}
case *transaction.Cosigner:
p.Value = *val
case *[]Param:
p.Value = *val
}

View file

@ -5,6 +5,7 @@ import (
"encoding/json"
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
@ -19,9 +20,13 @@ func TestParam_UnmarshalJSON(t *testing.T) {
{"cosigner": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"},
{"sender": "f84d6a337fbc3d3a201d41da99e86b479e7a2554", "cosigner": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"},
{"contract": "f84d6a337fbc3d3a201d41da99e86b479e7a2554"},
{"state": "HALT"}]`
{"state": "HALT"},
{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 0},
[{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 0}]]`
contr, err := util.Uint160DecodeStringLE("f84d6a337fbc3d3a201d41da99e86b479e7a2554")
require.NoError(t, err)
accountHash, err := util.Uint160DecodeStringLE("cadb3dc2faa3ef14a13b619c9a43124755aa2569")
require.NoError(t, err)
expected := Params{
{
Type: StringT,
@ -83,6 +88,25 @@ func TestParam_UnmarshalJSON(t *testing.T) {
Type: ExecutionFilterT,
Value: ExecutionFilter{State: "HALT"},
},
{
Type: Cosigner,
Value: transaction.Cosigner{
Account: accountHash,
Scopes: transaction.Global,
},
},
{
Type: ArrayT,
Value: []Param{
{
Type: Cosigner,
Value: transaction.Cosigner{
Account: accountHash,
Scopes: transaction.Global,
},
},
},
},
}
var ps Params
@ -214,3 +238,67 @@ func TestParamGetBytesHex(t *testing.T) {
_, err = p.GetBytesHex()
require.NotNil(t, err)
}
func TestParamGetCosigner(t *testing.T) {
c := transaction.Cosigner{
Account: util.Uint160{1, 2, 3, 4},
Scopes: transaction.Global,
}
p := Param{Type: Cosigner, Value: c}
actual, err := p.GetCosigner()
require.NoError(t, err)
require.Equal(t, c, actual)
p = Param{Type: Cosigner, Value: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 0}`}
_, err = p.GetCosigner()
require.Error(t, err)
}
func TestParamGetCosigners(t *testing.T) {
u1 := util.Uint160{1, 2, 3, 4}
u2 := util.Uint160{5, 6, 7, 8}
t.Run("from hashes", func(t *testing.T) {
p := Param{ArrayT, []Param{
{Type: StringT, Value: u1.StringLE()},
{Type: StringT, Value: u2.StringLE()},
}}
actual, err := p.GetCosigners()
require.NoError(t, err)
require.Equal(t, 2, len(actual))
require.True(t, u1.Equals(actual[0].Account))
require.True(t, u2.Equals(actual[1].Account))
})
t.Run("from cosigners", func(t *testing.T) {
c1 := transaction.Cosigner{
Account: u1,
Scopes: transaction.Global,
}
c2 := transaction.Cosigner{
Account: u2,
Scopes: transaction.CustomContracts,
AllowedContracts: []util.Uint160{
{1, 2, 3},
{4, 5, 6},
},
}
p := Param{ArrayT, []Param{
{Type: Cosigner, Value: c1},
{Type: Cosigner, Value: c2},
}}
actual, err := p.GetCosigners()
require.NoError(t, err)
require.Equal(t, 2, len(actual))
require.Equal(t, c1, actual[0])
require.Equal(t, c2, actual[1])
})
t.Run("bad format", func(t *testing.T) {
p := Param{ArrayT, []Param{
{Type: StringT, Value: u1.StringLE()},
{Type: StringT, Value: "bla"},
}}
_, err := p.GetCosigners()
require.Error(t, err)
})
}

View file

@ -618,7 +618,7 @@ func (s *Server) getDecimals(h util.Uint160, cache map[util.Uint160]int64) (int6
if err != nil {
return 0, response.NewInternalServerError("Can't create script", err)
}
res := s.runScriptInVM(script)
res := s.runScriptInVM(script, nil)
if res == nil || res.State != "HALT" || len(res.Stack) == 0 {
return 0, response.NewInternalServerError("execution error", errors.New("no result"))
}
@ -864,11 +864,21 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
if err != nil {
return nil, response.ErrInvalidParams
}
script, err := request.CreateFunctionInvocationScript(scriptHash, reqParams[1:])
tx := &transaction.Transaction{}
checkWitnessHashesIndex := len(reqParams)
if checkWitnessHashesIndex > 3 {
cosigners, err := reqParams[3].GetCosigners()
if err != nil {
return nil, response.ErrInvalidParams
}
tx.Cosigners = cosigners
checkWitnessHashesIndex--
}
script, err := request.CreateFunctionInvocationScript(scriptHash, reqParams[1:checkWitnessHashesIndex])
if err != nil {
return nil, response.NewInternalServerError("can't create invocation script", err)
}
return s.runScriptInVM(script), nil
return s.runScriptInVM(script, tx), nil
}
// invokescript implements the `invokescript` RPC call.
@ -882,13 +892,21 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.
return nil, response.ErrInvalidParams
}
return s.runScriptInVM(script), nil
tx := &transaction.Transaction{}
if len(reqParams) > 1 {
cosigners, err := reqParams[1].GetCosigners()
if err != nil {
return nil, response.ErrInvalidParams
}
tx.Cosigners = cosigners
}
return s.runScriptInVM(script, tx), nil
}
// runScriptInVM runs given script in a new test VM and returns the invocation
// result.
func (s *Server) runScriptInVM(script []byte) *result.Invoke {
vm := s.chain.GetTestVM()
func (s *Server) runScriptInVM(script []byte, tx *transaction.Transaction) *result.Invoke {
vm := s.chain.GetTestVM(tx)
vm.SetGasLimit(s.config.MaxGasInvoke)
vm.LoadScript(script)
_ = vm.Run()

View file

@ -620,6 +620,55 @@ var rpcTestCases = map[string][]rpcTestCase{
assert.NotEqual(t, 0, res.GasConsumed)
},
},
{
name: "positive, good witness",
// script is hex-encoded `test_verify.avm` representation, hashes are hex-encoded LE bytes of hashes used in the contract with `0x` prefix
params: `["5707000c14010c030e05060c0d020e0f0d030e070900000000db307068115541f827ec8c21aa270700000011400c140d0f03020900020103070304050201000e060c09db307169115541f827ec8c21aa270700000012401340",["0x0000000009070e030d0f0e020d0c06050e030c01","0x090c060e00010205040307030102000902030f0d"]]`,
result: func(e *executor) interface{} { return &result.Invoke{} },
check: func(t *testing.T, e *executor, inv interface{}) {
res, ok := inv.(*result.Invoke)
require.True(t, ok)
assert.Equal(t, "HALT", res.State)
require.Equal(t, 1, len(res.Stack))
require.Equal(t, int64(3), res.Stack[0].Value)
},
},
{
name: "positive, bad witness of second hash",
params: `["5707000c14010c030e05060c0d020e0f0d030e070900000000db307068115541f827ec8c21aa270700000011400c140d0f03020900020103070304050201000e060c09db307169115541f827ec8c21aa270700000012401340",["0x0000000009070e030d0f0e020d0c06050e030c01"]]`,
result: func(e *executor) interface{} { return &result.Invoke{} },
check: func(t *testing.T, e *executor, inv interface{}) {
res, ok := inv.(*result.Invoke)
require.True(t, ok)
assert.Equal(t, "HALT", res.State)
require.Equal(t, 1, len(res.Stack))
require.Equal(t, int64(2), res.Stack[0].Value)
},
},
{
name: "positive, no good hashes",
params: `["5707000c14010c030e05060c0d020e0f0d030e070900000000db307068115541f827ec8c21aa270700000011400c140d0f03020900020103070304050201000e060c09db307169115541f827ec8c21aa270700000012401340"]`,
result: func(e *executor) interface{} { return &result.Invoke{} },
check: func(t *testing.T, e *executor, inv interface{}) {
res, ok := inv.(*result.Invoke)
require.True(t, ok)
assert.Equal(t, "HALT", res.State)
require.Equal(t, 1, len(res.Stack))
require.Equal(t, int64(1), res.Stack[0].Value)
},
},
{
name: "positive, bad hashes witness",
params: `["5707000c14010c030e05060c0d020e0f0d030e070900000000db307068115541f827ec8c21aa270700000011400c140d0f03020900020103070304050201000e060c09db307169115541f827ec8c21aa270700000012401340",["0x0000000009070e030d0f0e020d0c06050e030c02"]]`,
result: func(e *executor) interface{} { return &result.Invoke{} },
check: func(t *testing.T, e *executor, inv interface{}) {
res, ok := inv.(*result.Invoke)
require.True(t, ok)
assert.Equal(t, "HALT", res.State)
assert.Equal(t, 1, len(res.Stack))
assert.Equal(t, int64(1), res.Stack[0].Value)
},
},
{
name: "no params",
params: `[]`,

BIN
pkg/rpc/server/testdata/test_verify.avm vendored Executable file

Binary file not shown.

17
pkg/rpc/server/testdata/test_verify.go vendored Normal file
View file

@ -0,0 +1,17 @@
package testdata
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
// This contract is used to test `invokescript` and `invokefunction` RPC-calls
func Main() int {
// h1 and h2 are just random uint160 hashes
h1 := []byte{1, 12, 3, 14, 5, 6, 12, 13, 2, 14, 15, 13, 3, 14, 7, 9, 0, 0, 0, 0}
if !runtime.CheckWitness(h1) {
return 1
}
h2 := []byte{13, 15, 3, 2, 9, 0, 2, 1, 3, 7, 3, 4, 5, 2, 1, 0, 14, 6, 12, 9}
if !runtime.CheckWitness(h2) {
return 2
}
return 3
}