Merge pull request #1524 from nspcc-dev/rpc/invoke_verify

rpc: add `invokecontractverify` RPC-method
This commit is contained in:
Roman Khimov 2020-12-21 10:36:54 +03:00 committed by GitHub
commit dee97d8542
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 352 additions and 67 deletions

View file

@ -1622,10 +1622,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(bc.getPrice) vm.SetPriceGetter(bc.getPrice)
return vm return vm

View file

@ -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

View file

@ -230,7 +230,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) {

View file

@ -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
@ -572,7 +593,7 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs
var ef int64 var ef int64
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)
} }

View file

@ -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)
} }
}
}) })
} }
}) })

View file

@ -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)
}

View file

@ -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)
}) })
} }

View file

@ -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))
})
}

View file

@ -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()

View file

@ -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",