diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 81ced67a1..c15ecb7cf 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -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. -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(bc.getPrice) return vm diff --git a/pkg/core/blockchainer/blockchainer.go b/pkg/core/blockchainer/blockchainer.go index 0bbd90fe8..e3db276d1 100644 --- a/pkg/core/blockchainer/blockchainer.go +++ b/pkg/core/blockchainer/blockchainer.go @@ -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 diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index b4c98b223..9b52a2d91 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -230,7 +230,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) { diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index 9103cea3b..c52497892 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -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 @@ -572,7 +593,7 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs var ef int64 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) } diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 589dfec16..4d3e1b028 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -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) + } } }) } diff --git a/pkg/rpc/request/param.go b/pkg/rpc/request/param.go index d24ff12db..00713a6df 100644 --- a/pkg/rpc/request/param.go +++ b/pkg/rpc/request/param.go @@ -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) +} diff --git a/pkg/rpc/request/param_test.go b/pkg/rpc/request/param_test.go index f9196ed7d..3df4a9667 100644 --- a/pkg/rpc/request/param_test.go +++ b/pkg/rpc/request/param_test.go @@ -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) }) } diff --git a/pkg/rpc/server/client_test.go b/pkg/rpc/server/client_test.go index b72dec8e9..61d3cde8f 100644 --- a/pkg/rpc/server/client_test.go +++ b/pkg/rpc/server/client_test.go @@ -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)) + }) +} diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 138c91158..f960395f9 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -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() diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 27d1d2e84..f1bf8384d 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -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",