rpc: implement invokecontractverify
RPC method
This commit is contained in:
parent
f009e531de
commit
da5eb67e85
10 changed files with 352 additions and 67 deletions
|
@ -1617,10 +1617,10 @@ func (bc *Blockchain) GetEnrollments() ([]state.Validator, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(tx *transaction.Transaction, b *block.Block) *vm.VM {
|
func (bc *Blockchain) GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *vm.VM {
|
||||||
d := bc.dao.GetWrapped().(*dao.Simple)
|
d := bc.dao.GetWrapped().(*dao.Simple)
|
||||||
d.MPT = nil
|
d.MPT = nil
|
||||||
systemInterop := bc.newInteropContext(trigger.Application, d, b, tx)
|
systemInterop := bc.newInteropContext(t, d, b, tx)
|
||||||
vm := systemInterop.SpawnVM()
|
vm := systemInterop.SpawnVM()
|
||||||
vm.SetPriceGetter(getPrice)
|
vm.SetPriceGetter(getPrice)
|
||||||
return vm
|
return vm
|
||||||
|
|
|
@ -55,7 +55,7 @@ type Blockchainer interface {
|
||||||
GetStateRoot(height uint32) (*state.MPTRootState, error)
|
GetStateRoot(height uint32) (*state.MPTRootState, error)
|
||||||
GetStorageItem(id int32, key []byte) *state.StorageItem
|
GetStorageItem(id int32, key []byte) *state.StorageItem
|
||||||
GetStorageItems(id int32) (map[string]*state.StorageItem, error)
|
GetStorageItems(id int32) (map[string]*state.StorageItem, error)
|
||||||
GetTestVM(tx *transaction.Transaction, b *block.Block) *vm.VM
|
GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *vm.VM
|
||||||
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
||||||
mempool.Feer // fee interface
|
mempool.Feer // fee interface
|
||||||
ManagementContractHash() util.Uint160
|
ManagementContractHash() util.Uint160
|
||||||
|
|
|
@ -223,7 +223,7 @@ func (chain *testChain) GetStateRoot(height uint32) (*state.MPTRootState, error)
|
||||||
func (chain *testChain) GetStorageItem(id int32, key []byte) *state.StorageItem {
|
func (chain *testChain) GetStorageItem(id int32, key []byte) *state.StorageItem {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
func (chain *testChain) GetTestVM(tx *transaction.Transaction, b *block.Block) *vm.VM {
|
func (chain *testChain) GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *vm.VM {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
func (chain *testChain) GetStorageItems(id int32) (map[string]*state.StorageItem, error) {
|
func (chain *testChain) GetStorageItems(id int32) (map[string]*state.StorageItem, error) {
|
||||||
|
|
|
@ -17,7 +17,6 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"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/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
@ -424,11 +423,33 @@ func (c *Client) InvokeFunction(contract util.Uint160, operation string, params
|
||||||
return c.invokeSomething("invokefunction", p, signers)
|
return c.invokeSomething("invokefunction", p, signers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InvokeContractVerify returns the results after calling `verify` method of the smart contract
|
||||||
|
// with the given parameters under verification trigger type.
|
||||||
|
// NOTE: this is test invoke and will not affect the blockchain.
|
||||||
|
func (c *Client) InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||||
|
var p = request.NewRawParams(contract.StringLE(), params)
|
||||||
|
return c.invokeSomething("invokecontractverify", p, signers, witnesses...)
|
||||||
|
}
|
||||||
|
|
||||||
// invokeSomething is an inner wrapper for Invoke* functions
|
// invokeSomething is an inner wrapper for Invoke* functions
|
||||||
func (c *Client) invokeSomething(method string, p request.RawParams, signers []transaction.Signer) (*result.Invoke, error) {
|
func (c *Client) invokeSomething(method string, p request.RawParams, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||||
var resp = new(result.Invoke)
|
var resp = new(result.Invoke)
|
||||||
if signers != nil {
|
if signers != nil {
|
||||||
|
if witnesses == nil {
|
||||||
p.Values = append(p.Values, signers)
|
p.Values = append(p.Values, signers)
|
||||||
|
} else {
|
||||||
|
if len(witnesses) != len(signers) {
|
||||||
|
return nil, fmt.Errorf("number of witnesses should match number of signers, got %d vs %d", len(witnesses), len(signers))
|
||||||
|
}
|
||||||
|
signersWithWitnesses := make([]request.SignerWithWitness, len(signers))
|
||||||
|
for i := range signersWithWitnesses {
|
||||||
|
signersWithWitnesses[i] = request.SignerWithWitness{
|
||||||
|
Signer: signers[i],
|
||||||
|
Witness: witnesses[i],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.Values = append(p.Values, signersWithWitnesses)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := c.performRequest(method, p, resp); err != nil {
|
if err := c.performRequest(method, p, resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -571,7 +592,7 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs
|
||||||
size := io.GetVarSize(tx)
|
size := io.GetVarSize(tx)
|
||||||
for i, cosigner := range tx.Signers {
|
for i, cosigner := range tx.Signers {
|
||||||
if accs[i].Contract.Deployed {
|
if accs[i].Contract.Deployed {
|
||||||
res, err := c.InvokeFunction(cosigner.Account, manifest.MethodVerify, []smartcontract.Parameter{}, tx.Signers)
|
res, err := c.InvokeContractVerify(cosigner.Account, smartcontract.Params{}, tx.Signers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to invoke verify: %w", err)
|
return fmt.Errorf("failed to invoke verify: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ import (
|
||||||
type rpcClientTestCase struct {
|
type rpcClientTestCase struct {
|
||||||
name string
|
name string
|
||||||
invoke func(c *Client) (interface{}, error)
|
invoke func(c *Client) (interface{}, error)
|
||||||
|
fails bool
|
||||||
serverResponse string
|
serverResponse string
|
||||||
result func(c *Client) interface{}
|
result func(c *Client) interface{}
|
||||||
check func(t *testing.T, c *Client, result interface{})
|
check func(t *testing.T, c *Client, result interface{})
|
||||||
|
@ -806,6 +807,46 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"invokecontractverify": {
|
||||||
|
{
|
||||||
|
name: "positive",
|
||||||
|
invoke: func(c *Client) (interface{}, error) {
|
||||||
|
contr, err := util.Uint160DecodeStringLE("af7c7328eee5a275a3bcaee2bf0cf662b5e739be")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return c.InvokeContractVerify(contr, nil, []transaction.Signer{{Account: util.Uint160{1, 2, 3}}}, transaction.Witness{InvocationScript: []byte{1, 2, 3}})
|
||||||
|
},
|
||||||
|
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"FCaufGyYYexBhGjB8P3Ep/KWPriRUcEJYmFsYW5jZU9mZ74557Vi9gy/4q68o3Wi5e4oc3yv","state":"HALT","gasconsumed":"0.31100000","stack":[{"type":"ByteString","value":"JivsCEQy"}],"tx":"AAgAAACAlpgAAAAAAAIEEwAAAAAAsAQAAAGqis+FnU/kArNOZz8hVoIXlqSI6wEAVwHoAwwUqorPhZ1P5AKzTmc/IVaCF5akiOsMFOeetm08E0pKd27oB9LluEbdpP2wE8AMCHRyYW5zZmVyDBTnnrZtPBNKSndu6AfS5bhG3aT9sEFifVtSOAFCDEDYNAh3TUvYsZrocFYdBvJ0Trdnj1jRuQzy9Q6YroP2Cwgk4v7q3vbeZBikz8Q7vB+RbDPsWUy+ZiqdkkeG4XoUKQwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CC0GVRA14"}}`,
|
||||||
|
result: func(c *Client) interface{} {
|
||||||
|
return &result.Invoke{}
|
||||||
|
},
|
||||||
|
check: func(t *testing.T, c *Client, uns interface{}) {
|
||||||
|
res, ok := uns.(*result.Invoke)
|
||||||
|
require.True(t, ok)
|
||||||
|
bytes, err := hex.DecodeString("262bec084432")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
script, err := base64.StdEncoding.DecodeString("FCaufGyYYexBhGjB8P3Ep/KWPriRUcEJYmFsYW5jZU9mZ74557Vi9gy/4q68o3Wi5e4oc3yv")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, "HALT", res.State)
|
||||||
|
assert.Equal(t, int64(31100000), res.GasConsumed)
|
||||||
|
assert.Equal(t, script, res.Script)
|
||||||
|
assert.Equal(t, []stackitem.Item{stackitem.NewByteArray(bytes)}, res.Stack)
|
||||||
|
assert.NotNil(t, res.Transaction)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad witness number",
|
||||||
|
invoke: func(c *Client) (interface{}, error) {
|
||||||
|
return c.InvokeContractVerify(util.Uint160{}, nil, []transaction.Signer{{}}, []transaction.Witness{{}, {}}...)
|
||||||
|
},
|
||||||
|
fails: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
"sendrawtransaction": {
|
"sendrawtransaction": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
|
@ -1365,6 +1406,9 @@ func testRPCClient(t *testing.T, newClient func(context.Context, string, Options
|
||||||
}
|
}
|
||||||
|
|
||||||
actual, err := testCase.invoke(c)
|
actual, err := testCase.invoke(c)
|
||||||
|
if testCase.fails {
|
||||||
|
assert.Error(t, err)
|
||||||
|
} else {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
expected := testCase.result(c)
|
expected := testCase.result(c)
|
||||||
|
@ -1373,6 +1417,7 @@ func testRPCClient(t *testing.T, newClient func(context.Context, string, Options
|
||||||
} else {
|
} else {
|
||||||
testCase.check(t, c, actual)
|
testCase.check(t, c, actual)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -56,6 +57,11 @@ type (
|
||||||
ExecutionFilter struct {
|
ExecutionFilter struct {
|
||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
}
|
}
|
||||||
|
// SignerWithWitness represents transaction's signer with the corresponding witness.
|
||||||
|
SignerWithWitness struct {
|
||||||
|
transaction.Signer
|
||||||
|
transaction.Witness
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// These are parameter types accepted by RPC server.
|
// These are parameter types accepted by RPC server.
|
||||||
|
@ -69,7 +75,7 @@ const (
|
||||||
TxFilterT
|
TxFilterT
|
||||||
NotificationFilterT
|
NotificationFilterT
|
||||||
ExecutionFilterT
|
ExecutionFilterT
|
||||||
Signer
|
SignerWithWitnessT
|
||||||
)
|
)
|
||||||
|
|
||||||
var errMissingParameter = errors.New("parameter is missing")
|
var errMissingParameter = errors.New("parameter is missing")
|
||||||
|
@ -209,24 +215,25 @@ func (p *Param) GetBytesBase64() ([]byte, error) {
|
||||||
return base64.StdEncoding.DecodeString(s)
|
return base64.StdEncoding.DecodeString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSigner returns transaction.Signer value of the parameter.
|
// GetSignerWithWitness returns SignerWithWitness value of the parameter.
|
||||||
func (p Param) GetSigner() (transaction.Signer, error) {
|
func (p Param) GetSignerWithWitness() (SignerWithWitness, error) {
|
||||||
c, ok := p.Value.(transaction.Signer)
|
c, ok := p.Value.(SignerWithWitness)
|
||||||
if !ok {
|
if !ok {
|
||||||
return transaction.Signer{}, errors.New("not a signer")
|
return SignerWithWitness{}, errors.New("not a signer")
|
||||||
}
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSigners returns a slice of transaction.Signer with global scope from
|
// GetSignersWithWitnesses returns a slice of SignerWithWitness with global scope from
|
||||||
// array of Uint160 or array of serialized transaction.Signer stored in the
|
// array of Uint160 or array of serialized transaction.Signer stored in the
|
||||||
// parameter.
|
// parameter.
|
||||||
func (p Param) GetSigners() ([]transaction.Signer, error) {
|
func (p Param) GetSignersWithWitnesses() ([]transaction.Signer, []transaction.Witness, error) {
|
||||||
hashes, err := p.GetArray()
|
hashes, err := p.GetArray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
signers := make([]transaction.Signer, len(hashes))
|
signers := make([]transaction.Signer, len(hashes))
|
||||||
|
witnesses := make([]transaction.Witness, len(hashes))
|
||||||
// try to extract hashes first
|
// try to extract hashes first
|
||||||
for i, h := range hashes {
|
for i, h := range hashes {
|
||||||
var u util.Uint160
|
var u util.Uint160
|
||||||
|
@ -241,13 +248,15 @@ func (p Param) GetSigners() ([]transaction.Signer, error) {
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for i, h := range hashes {
|
for i, h := range hashes {
|
||||||
signers[i], err = h.GetSigner()
|
signerWithWitness, err := h.GetSignerWithWitness()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
signers[i] = signerWithWitness.Signer
|
||||||
|
witnesses[i] = signerWithWitness.Witness
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return signers, witnesses, nil
|
||||||
return signers, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler interface.
|
// UnmarshalJSON implements json.Unmarshaler interface.
|
||||||
|
@ -263,7 +272,7 @@ func (p *Param) UnmarshalJSON(data []byte) error {
|
||||||
{TxFilterT, &TxFilter{}},
|
{TxFilterT, &TxFilter{}},
|
||||||
{NotificationFilterT, &NotificationFilter{}},
|
{NotificationFilterT, &NotificationFilter{}},
|
||||||
{ExecutionFilterT, &ExecutionFilter{}},
|
{ExecutionFilterT, &ExecutionFilter{}},
|
||||||
{Signer, &transaction.Signer{}},
|
{SignerWithWitnessT, &signerWithWitnessAux{}},
|
||||||
{ArrayT, &[]Param{}},
|
{ArrayT, &[]Param{}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,8 +307,20 @@ func (p *Param) UnmarshalJSON(data []byte) error {
|
||||||
} else {
|
} else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case *transaction.Signer:
|
case *signerWithWitnessAux:
|
||||||
p.Value = *val
|
aux := *val
|
||||||
|
p.Value = SignerWithWitness{
|
||||||
|
Signer: transaction.Signer{
|
||||||
|
Account: aux.Account,
|
||||||
|
Scopes: aux.Scopes,
|
||||||
|
AllowedContracts: aux.AllowedContracts,
|
||||||
|
AllowedGroups: aux.AllowedGroups,
|
||||||
|
},
|
||||||
|
Witness: transaction.Witness{
|
||||||
|
InvocationScript: aux.InvocationScript,
|
||||||
|
VerificationScript: aux.VerificationScript,
|
||||||
|
},
|
||||||
|
}
|
||||||
case *[]Param:
|
case *[]Param:
|
||||||
p.Value = *val
|
p.Value = *val
|
||||||
}
|
}
|
||||||
|
@ -309,3 +330,27 @@ func (p *Param) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
return errors.New("unknown type")
|
return errors.New("unknown type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// signerWithWitnessAux is an auxiluary struct for JSON marshalling. We need it because of
|
||||||
|
// DisallowUnknownFields JSON marshaller setting.
|
||||||
|
type signerWithWitnessAux struct {
|
||||||
|
Account util.Uint160 `json:"account"`
|
||||||
|
Scopes transaction.WitnessScope `json:"scopes"`
|
||||||
|
AllowedContracts []util.Uint160 `json:"allowedcontracts,omitempty"`
|
||||||
|
AllowedGroups []*keys.PublicKey `json:"allowedgroups,omitempty"`
|
||||||
|
InvocationScript []byte `json:"invocation,omitempty"`
|
||||||
|
VerificationScript []byte `json:"verification,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements json.Unmarshaler interface.
|
||||||
|
func (s *SignerWithWitness) MarshalJSON() ([]byte, error) {
|
||||||
|
signer := &signerWithWitnessAux{
|
||||||
|
Account: s.Account,
|
||||||
|
Scopes: s.Scopes,
|
||||||
|
AllowedContracts: s.AllowedContracts,
|
||||||
|
AllowedGroups: s.AllowedGroups,
|
||||||
|
InvocationScript: s.InvocationScript,
|
||||||
|
VerificationScript: s.VerificationScript,
|
||||||
|
}
|
||||||
|
return json.Marshal(signer)
|
||||||
|
}
|
||||||
|
|
|
@ -104,24 +104,28 @@ func TestParam_UnmarshalJSON(t *testing.T) {
|
||||||
Value: ExecutionFilter{State: "HALT"},
|
Value: ExecutionFilter{State: "HALT"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Type: Signer,
|
Type: SignerWithWitnessT,
|
||||||
Value: transaction.Signer{
|
Value: SignerWithWitness{
|
||||||
|
Signer: transaction.Signer{
|
||||||
Account: accountHash,
|
Account: accountHash,
|
||||||
Scopes: transaction.None,
|
Scopes: transaction.None,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Type: ArrayT,
|
Type: ArrayT,
|
||||||
Value: []Param{
|
Value: []Param{
|
||||||
{
|
{
|
||||||
Type: Signer,
|
Type: SignerWithWitnessT,
|
||||||
Value: transaction.Signer{
|
Value: SignerWithWitness{
|
||||||
|
Signer: transaction.Signer{
|
||||||
Account: accountHash,
|
Account: accountHash,
|
||||||
Scopes: transaction.Global,
|
Scopes: transaction.Global,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var ps Params
|
var ps Params
|
||||||
|
@ -297,17 +301,24 @@ func TestParamGetBytesBase64(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParamGetSigner(t *testing.T) {
|
func TestParamGetSigner(t *testing.T) {
|
||||||
c := transaction.Signer{
|
c := SignerWithWitness{
|
||||||
|
Signer: transaction.Signer{
|
||||||
Account: util.Uint160{1, 2, 3, 4},
|
Account: util.Uint160{1, 2, 3, 4},
|
||||||
Scopes: transaction.Global,
|
Scopes: transaction.Global,
|
||||||
|
},
|
||||||
|
Witness: transaction.Witness{
|
||||||
|
|
||||||
|
InvocationScript: []byte{1, 2, 3},
|
||||||
|
VerificationScript: []byte{1, 2, 3},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
p := Param{Type: Signer, Value: c}
|
p := Param{Type: SignerWithWitnessT, Value: c}
|
||||||
actual, err := p.GetSigner()
|
actual, err := p.GetSignerWithWitness()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, c, actual)
|
require.Equal(t, c, actual)
|
||||||
|
|
||||||
p = Param{Type: Signer, Value: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 0}`}
|
p = Param{Type: SignerWithWitnessT, Value: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 0}`}
|
||||||
_, err = p.GetSigner()
|
_, err = p.GetSignerWithWitness()
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,7 +330,7 @@ func TestParamGetSigners(t *testing.T) {
|
||||||
{Type: StringT, Value: u1.StringLE()},
|
{Type: StringT, Value: u1.StringLE()},
|
||||||
{Type: StringT, Value: u2.StringLE()},
|
{Type: StringT, Value: u2.StringLE()},
|
||||||
}}
|
}}
|
||||||
actual, err := p.GetSigners()
|
actual, _, err := p.GetSignersWithWitnesses()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(actual))
|
require.Equal(t, 2, len(actual))
|
||||||
require.True(t, u1.Equals(actual[0].Account))
|
require.True(t, u1.Equals(actual[0].Account))
|
||||||
|
@ -327,27 +338,48 @@ func TestParamGetSigners(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("from signers", func(t *testing.T) {
|
t.Run("from signers", func(t *testing.T) {
|
||||||
c1 := transaction.Signer{
|
c1 := SignerWithWitness{
|
||||||
|
Signer: transaction.Signer{
|
||||||
Account: u1,
|
Account: u1,
|
||||||
Scopes: transaction.Global,
|
Scopes: transaction.Global,
|
||||||
|
},
|
||||||
|
Witness: transaction.Witness{
|
||||||
|
InvocationScript: []byte{1, 2, 3},
|
||||||
|
VerificationScript: []byte{1, 2, 3},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
c2 := transaction.Signer{
|
c2 := SignerWithWitness{
|
||||||
|
Signer: transaction.Signer{
|
||||||
Account: u2,
|
Account: u2,
|
||||||
Scopes: transaction.CustomContracts,
|
Scopes: transaction.CustomContracts,
|
||||||
AllowedContracts: []util.Uint160{
|
AllowedContracts: []util.Uint160{
|
||||||
{1, 2, 3},
|
{1, 2, 3},
|
||||||
{4, 5, 6},
|
{4, 5, 6},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
p := Param{ArrayT, []Param{
|
p := Param{ArrayT, []Param{
|
||||||
{Type: Signer, Value: c1},
|
{Type: SignerWithWitnessT, Value: c1},
|
||||||
{Type: Signer, Value: c2},
|
{Type: SignerWithWitnessT, Value: c2},
|
||||||
}}
|
}}
|
||||||
actual, err := p.GetSigners()
|
actualS, actualW, err := p.GetSignersWithWitnesses()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(actual))
|
require.Equal(t, 2, len(actualS))
|
||||||
require.Equal(t, c1, actual[0])
|
require.Equal(t, transaction.Signer{
|
||||||
require.Equal(t, c2, actual[1])
|
Account: c1.Account,
|
||||||
|
Scopes: c1.Scopes,
|
||||||
|
}, actualS[0])
|
||||||
|
require.Equal(t, transaction.Signer{
|
||||||
|
Account: c2.Account,
|
||||||
|
Scopes: c2.Scopes,
|
||||||
|
AllowedContracts: c2.AllowedContracts,
|
||||||
|
}, actualS[1])
|
||||||
|
require.EqualValues(t, 2, len(actualW))
|
||||||
|
require.EqualValues(t, transaction.Witness{
|
||||||
|
InvocationScript: c1.InvocationScript,
|
||||||
|
VerificationScript: c1.VerificationScript,
|
||||||
|
}, actualW[0])
|
||||||
|
require.Equal(t, transaction.Witness{}, actualW[1])
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("bad format", func(t *testing.T) {
|
t.Run("bad format", func(t *testing.T) {
|
||||||
|
@ -355,7 +387,7 @@ func TestParamGetSigners(t *testing.T) {
|
||||||
{Type: StringT, Value: u1.StringLE()},
|
{Type: StringT, Value: u1.StringLE()},
|
||||||
{Type: StringT, Value: "bla"},
|
{Type: StringT, Value: "bla"},
|
||||||
}}
|
}}
|
||||||
_, err := p.GetSigners()
|
_, _, err := p.GetSignersWithWitnesses()
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
@ -274,7 +275,49 @@ func TestCreateNEP17TransferTx(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, acc.SignTx(tx))
|
require.NoError(t, acc.SignTx(tx))
|
||||||
require.NoError(t, chain.VerifyTx(tx))
|
require.NoError(t, chain.VerifyTx(tx))
|
||||||
v := chain.GetTestVM(tx, nil)
|
v := chain.GetTestVM(trigger.Application, tx, nil)
|
||||||
v.LoadScriptWithFlags(tx.Script, smartcontract.All)
|
v.LoadScriptWithFlags(tx.Script, smartcontract.All)
|
||||||
require.NoError(t, v.Run())
|
require.NoError(t, v.Run())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInvokeVerify(t *testing.T) {
|
||||||
|
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
|
||||||
|
defer chain.Close()
|
||||||
|
defer rpcSrv.Shutdown()
|
||||||
|
|
||||||
|
c, err := client.New(context.Background(), httpSrv.URL, client.Options{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, c.Init())
|
||||||
|
|
||||||
|
contract, err := util.Uint160DecodeStringLE(verifyContractHash)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("positive, with signer", func(t *testing.T) {
|
||||||
|
res, err := c.InvokeContractVerify(contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "HALT", res.State)
|
||||||
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
require.True(t, res.Stack[0].Value().(bool))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("positive, with signer and witness", func(t *testing.T) {
|
||||||
|
res, err := c.InvokeContractVerify(contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}}, transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH1), byte(opcode.RET)}})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "HALT", res.State)
|
||||||
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
require.True(t, res.Stack[0].Value().(bool))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("error, invalid witness number", func(t *testing.T) {
|
||||||
|
_, err := c.InvokeContractVerify(contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}}, transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH1), byte(opcode.RET)}}, transaction.Witness{InvocationScript: []byte{byte(opcode.RET)}})
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("false", func(t *testing.T) {
|
||||||
|
res, err := c.InvokeContractVerify(contract, smartcontract.Params{}, []transaction.Signer{{Account: util.Uint160{}}})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "HALT", res.State)
|
||||||
|
require.Equal(t, 1, len(res.Stack))
|
||||||
|
require.False(t, res.Stack[0].Value().(bool))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/response"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"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/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
@ -112,6 +113,7 @@ var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *respon
|
||||||
"getversion": (*Server).getVersion,
|
"getversion": (*Server).getVersion,
|
||||||
"invokefunction": (*Server).invokeFunction,
|
"invokefunction": (*Server).invokeFunction,
|
||||||
"invokescript": (*Server).invokescript,
|
"invokescript": (*Server).invokescript,
|
||||||
|
"invokecontractverify": (*Server).invokeContractVerify,
|
||||||
"sendrawtransaction": (*Server).sendrawtransaction,
|
"sendrawtransaction": (*Server).sendrawtransaction,
|
||||||
"submitblock": (*Server).submitBlock,
|
"submitblock": (*Server).submitBlock,
|
||||||
"validateaddress": (*Server).validateAddress,
|
"validateaddress": (*Server).validateAddress,
|
||||||
|
@ -1080,7 +1082,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
|
||||||
tx := &transaction.Transaction{}
|
tx := &transaction.Transaction{}
|
||||||
checkWitnessHashesIndex := len(reqParams)
|
checkWitnessHashesIndex := len(reqParams)
|
||||||
if checkWitnessHashesIndex > 3 {
|
if checkWitnessHashesIndex > 3 {
|
||||||
signers, err := reqParams[3].GetSigners()
|
signers, _, err := reqParams[3].GetSignersWithWitnesses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
|
@ -1095,7 +1097,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
|
||||||
return nil, response.NewInternalServerError("can't create invocation script", err)
|
return nil, response.NewInternalServerError("can't create invocation script", err)
|
||||||
}
|
}
|
||||||
tx.Script = script
|
tx.Script = script
|
||||||
return s.runScriptInVM(script, tx)
|
return s.runScriptInVM(trigger.Application, script, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// invokescript implements the `invokescript` RPC call.
|
// invokescript implements the `invokescript` RPC call.
|
||||||
|
@ -1111,7 +1113,7 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.
|
||||||
|
|
||||||
tx := &transaction.Transaction{}
|
tx := &transaction.Transaction{}
|
||||||
if len(reqParams) > 1 {
|
if len(reqParams) > 1 {
|
||||||
signers, err := reqParams[1].GetSigners()
|
signers, _, err := reqParams[1].GetSignersWithWitnesses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
|
@ -1121,12 +1123,53 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.
|
||||||
tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}}
|
tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}}
|
||||||
}
|
}
|
||||||
tx.Script = script
|
tx.Script = script
|
||||||
return s.runScriptInVM(script, tx)
|
return s.runScriptInVM(trigger.Application, script, tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invokeContractVerify implements the `invokecontractverify` RPC call.
|
||||||
|
func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *response.Error) {
|
||||||
|
scriptHash, responseErr := s.contractScriptHashFromParam(reqParams.Value(0))
|
||||||
|
if responseErr != nil {
|
||||||
|
return nil, responseErr
|
||||||
|
}
|
||||||
|
|
||||||
|
args := make(request.Params, 1)
|
||||||
|
args[0] = request.Param{
|
||||||
|
Type: request.StringT,
|
||||||
|
Value: manifest.MethodVerify,
|
||||||
|
}
|
||||||
|
if len(reqParams) > 1 {
|
||||||
|
args = append(args, reqParams[1])
|
||||||
|
}
|
||||||
|
var tx *transaction.Transaction
|
||||||
|
if len(reqParams) > 2 {
|
||||||
|
signers, witnesses, err := reqParams[2].GetSignersWithWitnesses()
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.ErrInvalidParams
|
||||||
|
}
|
||||||
|
tx = &transaction.Transaction{
|
||||||
|
Signers: signers,
|
||||||
|
Scripts: witnesses,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cs := s.chain.GetContractState(scriptHash)
|
||||||
|
if cs == nil {
|
||||||
|
return nil, response.NewRPCError("unknown contract", scriptHash.StringBE(), nil)
|
||||||
|
}
|
||||||
|
script, err := request.CreateFunctionInvocationScript(cs.Hash, 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, tx *transaction.Transaction) (*result.Invoke, *response.Error) {
|
func (s *Server) runScriptInVM(t trigger.Type, script []byte, tx *transaction.Transaction) (*result.Invoke, *response.Error) {
|
||||||
// When transfering funds, script execution does no auto GAS claim,
|
// When transfering funds, script execution does no auto GAS claim,
|
||||||
// because it depends on persisting tx height.
|
// because it depends on persisting tx height.
|
||||||
// This is why we provide block here.
|
// This is why we provide block here.
|
||||||
|
@ -1138,7 +1181,7 @@ func (s *Server) runScriptInVM(script []byte, tx *transaction.Transaction) (*res
|
||||||
}
|
}
|
||||||
b.Timestamp = hdr.Timestamp + uint64(s.chain.GetConfig().SecondsPerBlock*int(time.Second/time.Millisecond))
|
b.Timestamp = hdr.Timestamp + uint64(s.chain.GetConfig().SecondsPerBlock*int(time.Second/time.Millisecond))
|
||||||
|
|
||||||
vm := s.chain.GetTestVM(tx, b)
|
vm := s.chain.GetTestVM(t, tx, b)
|
||||||
vm.GasLimit = int64(s.config.MaxGasInvoke)
|
vm.GasLimit = int64(s.config.MaxGasInvoke)
|
||||||
vm.LoadScriptWithFlags(script, smartcontract.All)
|
vm.LoadScriptWithFlags(script, smartcontract.All)
|
||||||
err = vm.Run()
|
err = vm.Run()
|
||||||
|
|
|
@ -773,6 +773,62 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
fail: true,
|
fail: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"invokecontractverify": {
|
||||||
|
{
|
||||||
|
name: "positive",
|
||||||
|
params: fmt.Sprintf(`["%s", [], [{"account":"%s"}]]`, verifyContractHash, testchain.PrivateKeyByID(0).PublicKey().GetScriptHash().StringLE()),
|
||||||
|
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.NotNil(t, res.Script)
|
||||||
|
assert.Equal(t, "HALT", res.State)
|
||||||
|
assert.NotEqual(t, 0, res.GasConsumed)
|
||||||
|
assert.Equal(t, true, res.Stack[0].Value().(bool))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, no signers",
|
||||||
|
params: fmt.Sprintf(`["%s", []]`, verifyContractHash),
|
||||||
|
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.NotNil(t, res.Script)
|
||||||
|
assert.Equal(t, "HALT", res.State, res.FaultException)
|
||||||
|
assert.NotEqual(t, 0, res.GasConsumed)
|
||||||
|
assert.Equal(t, false, res.Stack[0].Value().(bool))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive, with scripts",
|
||||||
|
params: fmt.Sprintf(`["%s", [], [{"account":"%s", "invocation":"MQo=", "verification": ""}]]`, verifyContractHash, testchain.PrivateKeyByID(0).PublicKey().GetScriptHash().StringLE()),
|
||||||
|
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.NotNil(t, res.Script)
|
||||||
|
assert.Equal(t, "HALT", res.State)
|
||||||
|
assert.NotEqual(t, 0, res.GasConsumed)
|
||||||
|
assert.Equal(t, true, res.Stack[0].Value().(bool))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown contract",
|
||||||
|
params: fmt.Sprintf(`["%s", []]`, util.Uint160{}.String()),
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no params",
|
||||||
|
params: `[]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "not a string",
|
||||||
|
params: `[42, []]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
"sendrawtransaction": {
|
"sendrawtransaction": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
|
|
Loading…
Reference in a new issue