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.
|
||||
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.MPT = nil
|
||||
systemInterop := bc.newInteropContext(trigger.Application, d, b, tx)
|
||||
systemInterop := bc.newInteropContext(t, d, b, tx)
|
||||
vm := systemInterop.SpawnVM()
|
||||
vm.SetPriceGetter(getPrice)
|
||||
return vm
|
||||
|
|
|
@ -55,7 +55,7 @@ type Blockchainer interface {
|
|||
GetStateRoot(height uint32) (*state.MPTRootState, error)
|
||||
GetStorageItem(id int32, key []byte) *state.StorageItem
|
||||
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)
|
||||
mempool.Feer // fee interface
|
||||
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 {
|
||||
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")
|
||||
}
|
||||
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/response/result"
|
||||
"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/util"
|
||||
"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)
|
||||
}
|
||||
|
||||
// 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
|
||||
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)
|
||||
if signers != nil {
|
||||
p.Values = append(p.Values, signers)
|
||||
if witnesses == nil {
|
||||
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 {
|
||||
return nil, err
|
||||
|
@ -571,7 +592,7 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs
|
|||
size := io.GetVarSize(tx)
|
||||
for i, cosigner := range tx.Signers {
|
||||
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 {
|
||||
return fmt.Errorf("failed to invoke verify: %w", err)
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ import (
|
|||
type rpcClientTestCase struct {
|
||||
name string
|
||||
invoke func(c *Client) (interface{}, error)
|
||||
fails bool
|
||||
serverResponse string
|
||||
result func(c *Client) 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": {
|
||||
{
|
||||
name: "positive",
|
||||
|
@ -1365,13 +1406,17 @@ func testRPCClient(t *testing.T, newClient func(context.Context, string, Options
|
|||
}
|
||||
|
||||
actual, err := testCase.invoke(c)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := testCase.result(c)
|
||||
if testCase.check == nil {
|
||||
assert.Equal(t, expected, actual)
|
||||
if testCase.fails {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
testCase.check(t, c, actual)
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := testCase.result(c)
|
||||
if testCase.check == nil {
|
||||
assert.Equal(t, expected, actual)
|
||||
} else {
|
||||
testCase.check(t, c, actual)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"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/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
|
@ -56,6 +57,11 @@ type (
|
|||
ExecutionFilter struct {
|
||||
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.
|
||||
|
@ -69,7 +75,7 @@ const (
|
|||
TxFilterT
|
||||
NotificationFilterT
|
||||
ExecutionFilterT
|
||||
Signer
|
||||
SignerWithWitnessT
|
||||
)
|
||||
|
||||
var errMissingParameter = errors.New("parameter is missing")
|
||||
|
@ -209,24 +215,25 @@ func (p *Param) GetBytesBase64() ([]byte, error) {
|
|||
return base64.StdEncoding.DecodeString(s)
|
||||
}
|
||||
|
||||
// GetSigner returns transaction.Signer value of the parameter.
|
||||
func (p Param) GetSigner() (transaction.Signer, error) {
|
||||
c, ok := p.Value.(transaction.Signer)
|
||||
// GetSignerWithWitness returns SignerWithWitness value of the parameter.
|
||||
func (p Param) GetSignerWithWitness() (SignerWithWitness, error) {
|
||||
c, ok := p.Value.(SignerWithWitness)
|
||||
if !ok {
|
||||
return transaction.Signer{}, errors.New("not a signer")
|
||||
return SignerWithWitness{}, errors.New("not a signer")
|
||||
}
|
||||
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
|
||||
// parameter.
|
||||
func (p Param) GetSigners() ([]transaction.Signer, error) {
|
||||
func (p Param) GetSignersWithWitnesses() ([]transaction.Signer, []transaction.Witness, error) {
|
||||
hashes, err := p.GetArray()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
signers := make([]transaction.Signer, len(hashes))
|
||||
witnesses := make([]transaction.Witness, len(hashes))
|
||||
// try to extract hashes first
|
||||
for i, h := range hashes {
|
||||
var u util.Uint160
|
||||
|
@ -241,13 +248,15 @@ func (p Param) GetSigners() ([]transaction.Signer, error) {
|
|||
}
|
||||
if err != nil {
|
||||
for i, h := range hashes {
|
||||
signers[i], err = h.GetSigner()
|
||||
signerWithWitness, err := h.GetSignerWithWitness()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
signers[i] = signerWithWitness.Signer
|
||||
witnesses[i] = signerWithWitness.Witness
|
||||
}
|
||||
}
|
||||
return signers, nil
|
||||
return signers, witnesses, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler interface.
|
||||
|
@ -263,7 +272,7 @@ func (p *Param) UnmarshalJSON(data []byte) error {
|
|||
{TxFilterT, &TxFilter{}},
|
||||
{NotificationFilterT, &NotificationFilter{}},
|
||||
{ExecutionFilterT, &ExecutionFilter{}},
|
||||
{Signer, &transaction.Signer{}},
|
||||
{SignerWithWitnessT, &signerWithWitnessAux{}},
|
||||
{ArrayT, &[]Param{}},
|
||||
}
|
||||
|
||||
|
@ -298,8 +307,20 @@ func (p *Param) UnmarshalJSON(data []byte) error {
|
|||
} else {
|
||||
continue
|
||||
}
|
||||
case *transaction.Signer:
|
||||
p.Value = *val
|
||||
case *signerWithWitnessAux:
|
||||
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:
|
||||
p.Value = *val
|
||||
}
|
||||
|
@ -309,3 +330,27 @@ func (p *Param) UnmarshalJSON(data []byte) error {
|
|||
|
||||
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,20 +104,24 @@ func TestParam_UnmarshalJSON(t *testing.T) {
|
|||
Value: ExecutionFilter{State: "HALT"},
|
||||
},
|
||||
{
|
||||
Type: Signer,
|
||||
Value: transaction.Signer{
|
||||
Account: accountHash,
|
||||
Scopes: transaction.None,
|
||||
Type: SignerWithWitnessT,
|
||||
Value: SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
Account: accountHash,
|
||||
Scopes: transaction.None,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: ArrayT,
|
||||
Value: []Param{
|
||||
{
|
||||
Type: Signer,
|
||||
Value: transaction.Signer{
|
||||
Account: accountHash,
|
||||
Scopes: transaction.Global,
|
||||
Type: SignerWithWitnessT,
|
||||
Value: SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
Account: accountHash,
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -297,17 +301,24 @@ func TestParamGetBytesBase64(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestParamGetSigner(t *testing.T) {
|
||||
c := transaction.Signer{
|
||||
Account: util.Uint160{1, 2, 3, 4},
|
||||
Scopes: transaction.Global,
|
||||
c := SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
Account: util.Uint160{1, 2, 3, 4},
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
Witness: transaction.Witness{
|
||||
|
||||
InvocationScript: []byte{1, 2, 3},
|
||||
VerificationScript: []byte{1, 2, 3},
|
||||
},
|
||||
}
|
||||
p := Param{Type: Signer, Value: c}
|
||||
actual, err := p.GetSigner()
|
||||
p := Param{Type: SignerWithWitnessT, Value: c}
|
||||
actual, err := p.GetSignerWithWitness()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c, actual)
|
||||
|
||||
p = Param{Type: Signer, Value: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 0}`}
|
||||
_, err = p.GetSigner()
|
||||
p = Param{Type: SignerWithWitnessT, Value: `{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": 0}`}
|
||||
_, err = p.GetSignerWithWitness()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
|
@ -319,7 +330,7 @@ func TestParamGetSigners(t *testing.T) {
|
|||
{Type: StringT, Value: u1.StringLE()},
|
||||
{Type: StringT, Value: u2.StringLE()},
|
||||
}}
|
||||
actual, err := p.GetSigners()
|
||||
actual, _, err := p.GetSignersWithWitnesses()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(actual))
|
||||
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) {
|
||||
c1 := transaction.Signer{
|
||||
Account: u1,
|
||||
Scopes: transaction.Global,
|
||||
c1 := SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
Account: u1,
|
||||
Scopes: transaction.Global,
|
||||
},
|
||||
Witness: transaction.Witness{
|
||||
InvocationScript: []byte{1, 2, 3},
|
||||
VerificationScript: []byte{1, 2, 3},
|
||||
},
|
||||
}
|
||||
c2 := transaction.Signer{
|
||||
Account: u2,
|
||||
Scopes: transaction.CustomContracts,
|
||||
AllowedContracts: []util.Uint160{
|
||||
{1, 2, 3},
|
||||
{4, 5, 6},
|
||||
c2 := SignerWithWitness{
|
||||
Signer: transaction.Signer{
|
||||
Account: u2,
|
||||
Scopes: transaction.CustomContracts,
|
||||
AllowedContracts: []util.Uint160{
|
||||
{1, 2, 3},
|
||||
{4, 5, 6},
|
||||
},
|
||||
},
|
||||
}
|
||||
p := Param{ArrayT, []Param{
|
||||
{Type: Signer, Value: c1},
|
||||
{Type: Signer, Value: c2},
|
||||
{Type: SignerWithWitnessT, Value: c1},
|
||||
{Type: SignerWithWitnessT, Value: c2},
|
||||
}}
|
||||
actual, err := p.GetSigners()
|
||||
actualS, actualW, err := p.GetSignersWithWitnesses()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(actual))
|
||||
require.Equal(t, c1, actual[0])
|
||||
require.Equal(t, c2, actual[1])
|
||||
require.Equal(t, 2, len(actualS))
|
||||
require.Equal(t, transaction.Signer{
|
||||
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) {
|
||||
|
@ -355,7 +387,7 @@ func TestParamGetSigners(t *testing.T) {
|
|||
{Type: StringT, Value: u1.StringLE()},
|
||||
{Type: StringT, Value: "bla"},
|
||||
}}
|
||||
_, err := p.GetSigners()
|
||||
_, _, err := p.GetSignersWithWitnesses()
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"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/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
|
@ -274,7 +275,49 @@ func TestCreateNEP17TransferTx(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.NoError(t, acc.SignTx(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)
|
||||
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/result"
|
||||
"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/util"
|
||||
"go.uber.org/zap"
|
||||
|
@ -112,6 +113,7 @@ var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *respon
|
|||
"getversion": (*Server).getVersion,
|
||||
"invokefunction": (*Server).invokeFunction,
|
||||
"invokescript": (*Server).invokescript,
|
||||
"invokecontractverify": (*Server).invokeContractVerify,
|
||||
"sendrawtransaction": (*Server).sendrawtransaction,
|
||||
"submitblock": (*Server).submitBlock,
|
||||
"validateaddress": (*Server).validateAddress,
|
||||
|
@ -1080,7 +1082,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
|
|||
tx := &transaction.Transaction{}
|
||||
checkWitnessHashesIndex := len(reqParams)
|
||||
if checkWitnessHashesIndex > 3 {
|
||||
signers, err := reqParams[3].GetSigners()
|
||||
signers, _, err := reqParams[3].GetSignersWithWitnesses()
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
tx.Script = script
|
||||
return s.runScriptInVM(script, tx)
|
||||
return s.runScriptInVM(trigger.Application, script, tx)
|
||||
}
|
||||
|
||||
// invokescript implements the `invokescript` RPC call.
|
||||
|
@ -1111,7 +1113,7 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.
|
|||
|
||||
tx := &transaction.Transaction{}
|
||||
if len(reqParams) > 1 {
|
||||
signers, err := reqParams[1].GetSigners()
|
||||
signers, _, err := reqParams[1].GetSignersWithWitnesses()
|
||||
if err != nil {
|
||||
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.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
|
||||
// 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,
|
||||
// because it depends on persisting tx height.
|
||||
// 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))
|
||||
|
||||
vm := s.chain.GetTestVM(tx, b)
|
||||
vm := s.chain.GetTestVM(t, tx, b)
|
||||
vm.GasLimit = int64(s.config.MaxGasInvoke)
|
||||
vm.LoadScriptWithFlags(script, smartcontract.All)
|
||||
err = vm.Run()
|
||||
|
|
|
@ -773,6 +773,62 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
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": {
|
||||
{
|
||||
name: "positive",
|
||||
|
|
Loading…
Reference in a new issue