From 15a939b1dae72f5ee3b3c8e7aa6c76b89e8d7f30 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 25 Sep 2020 12:40:57 +0300 Subject: [PATCH 1/5] rpc: allow to `getcontractstate` by address, id or name close #1423 --- docs/rpc.md | 5 +++ pkg/core/blockchain.go | 9 ++++++ pkg/core/blockchainer/blockchainer.go | 1 + pkg/core/native/contract.go | 12 ++++++++ pkg/network/helper_test.go | 3 ++ pkg/rpc/server/server.go | 43 ++++++++++++++++++++++++-- pkg/rpc/server/server_test.go | 44 +++++++++++++++++++++++++-- 7 files changed, 112 insertions(+), 5 deletions(-) diff --git a/docs/rpc.md b/docs/rpc.md index a16d81cee..045dc83db 100644 --- a/docs/rpc.md +++ b/docs/rpc.md @@ -77,6 +77,11 @@ Both methods also don't currently support arrays in function parameters. It's possible to call this method for any address with neo-go, unlike with C# node where it only works for addresses from opened wallet. +##### `getcontractstate` + +It's possible to get non-native contract state by its ID, unlike with C# node where +it only works for native contracts. + ### Unsupported methods Methods listed down below are not going to be supported for various reasons diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index dad9fa944..84d2de535 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1073,6 +1073,15 @@ func (bc *Blockchain) GetContractScriptHash(id int32) (util.Uint160, error) { return bc.dao.GetContractScriptHash(id) } +// GetNativeContractScriptHash returns native contract script hash by its name. +func (bc *Blockchain) GetNativeContractScriptHash(name string) (util.Uint160, error) { + c := bc.contracts.ByName(name) + if c != nil { + return c.Metadata().Hash, nil + } + return util.Uint160{}, errors.New("Unknown native contract") +} + // GetConfig returns the config stored in the blockchain. func (bc *Blockchain) GetConfig() config.ProtocolConfiguration { return bc.config diff --git a/pkg/core/blockchainer/blockchainer.go b/pkg/core/blockchainer/blockchainer.go index b02ff1316..04e72b569 100644 --- a/pkg/core/blockchainer/blockchainer.go +++ b/pkg/core/blockchainer/blockchainer.go @@ -39,6 +39,7 @@ type Blockchainer interface { HasBlock(util.Uint256) bool HasTransaction(util.Uint256) bool GetAppExecResult(util.Uint256) (*state.AppExecResult, error) + GetNativeContractScriptHash(string) (util.Uint160, error) GetNextBlockValidators() ([]*keys.PublicKey, error) GetNEP5Balances(util.Uint160) *state.NEP5Balances GetValidators() ([]*keys.PublicKey, error) diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index a250bfed5..a32dbc827 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -2,6 +2,7 @@ package native import ( "errors" + "strings" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/io" @@ -35,6 +36,17 @@ func (cs *Contracts) ByHash(h util.Uint160) interop.Contract { return nil } +// ByName returns native contract with the specified name. +func (cs *Contracts) ByName(name string) interop.Contract { + name = strings.ToLower(name) + for _, ctr := range cs.Contracts { + if strings.ToLower(ctr.Metadata().Name) == name { + return ctr + } + } + return nil +} + // NewContracts returns new set of native contracts with new GAS, NEO and Policy // contracts. func NewContracts() *Contracts { diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 5f74e24b7..b8a619006 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -89,6 +89,9 @@ func (chain testChain) GetContractState(hash util.Uint160) *state.Contract { func (chain testChain) GetContractScriptHash(id int32) (util.Uint160, error) { panic("TODO") } +func (chain testChain) GetNativeContractScriptHash(name string) (util.Uint160, error) { + panic("TODO") +} func (chain testChain) GetHeaderHash(int) util.Uint256 { return util.Uint256{} } diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 305838f15..d820d8332 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -765,6 +765,42 @@ func (s *Server) contractIDFromParam(param *request.Param) (int32, *response.Err return result, nil } +// getContractScriptHashFromParam returns the contract script hash by hex contract hash, address, id or native contract name. +func (s *Server) contractScriptHashFromParam(param *request.Param) (util.Uint160, *response.Error) { + var result util.Uint160 + if param == nil { + return result, response.ErrInvalidParams + } + switch param.Type { + case request.StringT: + var err error + result, err = param.GetUint160FromAddressOrHex() + if err == nil { + return result, nil + } + name, err := param.GetString() + if err != nil { + return result, response.ErrInvalidParams + } + result, err = s.chain.GetNativeContractScriptHash(name) + if err != nil { + return result, response.NewRPCError("Unknown contract: querying by name is supported for native contracts only", "", nil) + } + case request.NumberT: + id, err := param.GetInt() + if err != nil { + return result, response.ErrInvalidParams + } + result, err = s.chain.GetContractScriptHash(int32(id)) + if err != nil { + return result, response.NewRPCError("Unknown contract", "", err) + } + default: + return result, response.ErrInvalidParams + } + return result, nil +} + func (s *Server) getStorage(ps request.Params) (interface{}, *response.Error) { id, rErr := s.contractIDFromParam(ps.Value(0)) if rErr == response.ErrUnknown { @@ -828,11 +864,12 @@ func (s *Server) getTransactionHeight(ps request.Params) (interface{}, *response return height, nil } -// getContractState returns contract state (contract information, according to the contract script hash). +// getContractState returns contract state (contract information, according to the contract script hash, +// contract id or native contract name). func (s *Server) getContractState(reqParams request.Params) (interface{}, *response.Error) { - scriptHash, err := reqParams.ValueWithType(0, request.StringT).GetUint160FromHex() + scriptHash, err := s.contractScriptHashFromParam(reqParams.Value(0)) if err != nil { - return nil, response.ErrInvalidParams + return nil, err } cs := s.chain.GetContractState(scriptHash) if cs == nil { diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 9d2c9a0ad..66f5a02ba 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -96,7 +96,7 @@ var rpcTestCases = map[string][]rpcTestCase{ }, "getcontractstate": { { - name: "positive", + name: "positive, by hash", params: fmt.Sprintf(`["%s"]`, testContractHash), result: func(e *executor) interface{} { return &state.Contract{} }, check: func(t *testing.T, e *executor, cs interface{}) { @@ -106,10 +106,50 @@ var rpcTestCases = map[string][]rpcTestCase{ }, }, { - name: "negative", + name: "positive, by id", + params: `[0]`, + result: func(e *executor) interface{} { return &state.Contract{} }, + check: func(t *testing.T, e *executor, cs interface{}) { + res, ok := cs.(*state.Contract) + require.True(t, ok) + assert.Equal(t, int32(0), res.ID) + }, + }, + { + name: "positive, native by id", + params: `[-3]`, + result: func(e *executor) interface{} { return &state.Contract{} }, + check: func(t *testing.T, e *executor, cs interface{}) { + res, ok := cs.(*state.Contract) + require.True(t, ok) + assert.Equal(t, int32(-3), res.ID) + }, + }, + { + name: "positive, native by name", + params: `["Policy"]`, + result: func(e *executor) interface{} { return &state.Contract{} }, + check: func(t *testing.T, e *executor, cs interface{}) { + res, ok := cs.(*state.Contract) + require.True(t, ok) + assert.Equal(t, int32(-3), res.ID) + }, + }, + { + name: "negative, bad hash", params: `["6d1eeca891ee93de2b7a77eb91c26f3b3c04d6c3"]`, fail: true, }, + { + name: "negative, bad ID", + params: `[-8]`, + fail: true, + }, + { + name: "negative, bad native name", + params: `["unknown_native"]`, + fail: true, + }, { name: "no params", params: `[]`, From d3daaafbe4e7c70f691e7f2a9f3ff9efd1244826 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 25 Sep 2020 18:43:47 +0300 Subject: [PATCH 2/5] rpc: allow to get contract scripthash from address, id or name [Server] ... for `invokefunction` RPC method. --- docs/rpc.md | 4 ++++ pkg/rpc/server/server.go | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/rpc.md b/docs/rpc.md index 045dc83db..f4189d80b 100644 --- a/docs/rpc.md +++ b/docs/rpc.md @@ -70,6 +70,10 @@ key in the server which doesn't fit the model of our node-client interactions. Lacking this signature the transaction is almost useless, so there is no point in returning it. +It's possible to use `invokefunction` not only with contract scripthash, but also +with contract name (for native contracts) or contract ID (for all contracts). This +feature is not supported by the C# node. + Both methods also don't currently support arrays in function parameters. ##### `getunclaimedgas` diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index d820d8332..92ef4b7d9 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -984,9 +984,9 @@ func (s *Server) getCommittee(_ request.Params) (interface{}, *response.Error) { // invokeFunction implements the `invokeFunction` RPC call. func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *response.Error) { - scriptHash, err := reqParams.ValueWithType(0, request.StringT).GetUint160FromHex() - if err != nil { - return nil, response.ErrInvalidParams + scriptHash, responseErr := s.contractScriptHashFromParam(reqParams.Value(0)) + if responseErr != nil { + return nil, responseErr } tx := &transaction.Transaction{} checkWitnessHashesIndex := len(reqParams) From b8a88f93783cf2302b9e01f9af718e1e7e310199 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 25 Sep 2020 19:32:53 +0300 Subject: [PATCH 3/5] rpc: allow to use address, id or name instead of scripthash [Client] ... for getcontractstate RPC client method. --- cli/multisig_test.go | 3 +- cli/nep5_test.go | 25 +++++--- cli/options/options_test.go | 33 ---------- cli/options_test.go | 32 ++++++++++ cli/wallet/nep5.go | 23 +++---- cli/wallet/validator.go | 13 +++- cli/wallet/wallet.go | 9 ++- pkg/rpc/client/client.go | 21 ++++++- pkg/rpc/client/nep5.go | 7 --- pkg/rpc/client/rpc.go | 37 +++++++++++- pkg/rpc/client/rpc_test.go | 111 +++++++++++++++++++++++++++------- pkg/rpc/server/client_test.go | 5 +- 12 files changed, 220 insertions(+), 99 deletions(-) create mode 100644 cli/options_test.go diff --git a/cli/multisig_test.go b/cli/multisig_test.go index 66d093067..c1829ec9f 100644 --- a/cli/multisig_test.go +++ b/cli/multisig_test.go @@ -11,7 +11,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "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/rpc/client" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/stretchr/testify/require" ) @@ -91,7 +90,7 @@ func TestSignMultisigTx(t *testing.T) { "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", wallet1Path, "--address", multisigAddr, "--out", txPath, - client.NeoContractHash.StringLE(), "transfer", + e.Chain.GoverningTokenHash().StringLE(), "transfer", "bytes:"+multisigHash.StringBE(), "bytes:"+priv.GetScriptHash().StringBE(), "int:1", diff --git a/cli/nep5_test.go b/cli/nep5_test.go index cdd7e8b42..cf6b2f4a1 100644 --- a/cli/nep5_test.go +++ b/cli/nep5_test.go @@ -10,7 +10,6 @@ import ( "testing" "github.com/nspcc-dev/neo-go/pkg/encoding/address" - "github.com/nspcc-dev/neo-go/pkg/rpc/client" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/require" @@ -138,6 +137,8 @@ func TestNEP5MultiTransfer(t *testing.T) { e := newExecutor(t, true) defer e.Close(t) + neoContractHash, err := e.Chain.GetNativeContractScriptHash("neo") + require.NoError(t, err) args := []string{ "neo-go", "wallet", "nep5", "multitransfer", "--rpc-endpoint", "http://" + e.RPC.Addr, @@ -145,7 +146,7 @@ func TestNEP5MultiTransfer(t *testing.T) { "--from", validatorAddr, "neo:" + privs[0].Address() + ":42", "GAS:" + privs[1].Address() + ":7", - client.NeoContractHash.StringLE() + ":" + privs[2].Address() + ":13", + neoContractHash.StringLE() + ":" + privs[2].Address() + ":13", } e.In.WriteString("one\r") @@ -168,27 +169,31 @@ func TestNEP5ImportToken(t *testing.T) { walletPath := path.Join(tmpDir, "walletForImport.json") defer os.Remove(walletPath) + neoContractHash, err := e.Chain.GetNativeContractScriptHash("neo") + require.NoError(t, err) + gasContractHash, err := e.Chain.GetNativeContractScriptHash("gas") + require.NoError(t, err) e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath) e.Run(t, "neo-go", "wallet", "nep5", "import", "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", walletPath, - "--token", client.GasContractHash.StringLE()) + "--token", gasContractHash.StringLE()) e.Run(t, "neo-go", "wallet", "nep5", "import", "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", walletPath, - "--token", client.NeoContractHash.StringLE()) + "--token", neoContractHash.StringLE()) t.Run("Info", func(t *testing.T) { checkGASInfo := func(t *testing.T) { e.checkNextLine(t, "^Name:\\s*GAS") e.checkNextLine(t, "^Symbol:\\s*gas") - e.checkNextLine(t, "^Hash:\\s*"+client.GasContractHash.StringLE()) + e.checkNextLine(t, "^Hash:\\s*"+gasContractHash.StringLE()) e.checkNextLine(t, "^Decimals:\\s*8") - e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(client.GasContractHash)) + e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(gasContractHash)) } t.Run("WithToken", func(t *testing.T) { e.Run(t, "neo-go", "wallet", "nep5", "info", - "--wallet", walletPath, "--token", client.GasContractHash.StringLE()) + "--wallet", walletPath, "--token", gasContractHash.StringLE()) checkGASInfo(t) }) t.Run("NoToken", func(t *testing.T) { @@ -199,14 +204,14 @@ func TestNEP5ImportToken(t *testing.T) { require.NoError(t, err) e.checkNextLine(t, "^Name:\\s*NEO") e.checkNextLine(t, "^Symbol:\\s*neo") - e.checkNextLine(t, "^Hash:\\s*"+client.NeoContractHash.StringLE()) + e.checkNextLine(t, "^Hash:\\s*"+neoContractHash.StringLE()) e.checkNextLine(t, "^Decimals:\\s*0") - e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(client.NeoContractHash)) + e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(neoContractHash)) }) t.Run("Remove", func(t *testing.T) { e.In.WriteString("y\r") e.Run(t, "neo-go", "wallet", "nep5", "remove", - "--wallet", walletPath, "--token", client.NeoContractHash.StringLE()) + "--wallet", walletPath, "--token", neoContractHash.StringLE()) e.Run(t, "neo-go", "wallet", "nep5", "info", "--wallet", walletPath) checkGASInfo(t) diff --git a/cli/options/options_test.go b/cli/options/options_test.go index ba0c48ecf..57ea27683 100644 --- a/cli/options/options_test.go +++ b/cli/options/options_test.go @@ -2,8 +2,6 @@ package options import ( "flag" - "net/http" - "net/http/httptest" "testing" "time" @@ -57,34 +55,3 @@ func TestGetTimeoutContext(t *testing.T) { require.True(t, start.Before(dl) && dl.Before(end.Add(time.Nanosecond*20))) }) } - -func TestGetRPCClient(t *testing.T) { - // need test server for proper client.Init() handling - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - response := `{"id":1,"jsonrpc":"2.0","result":{"magic":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}` - - _, err := w.Write([]byte(response)) - if err != nil { - t.Fatalf("Error writing response: %s", err.Error()) - } - })) - defer srv.Close() - t.Run("no endpoint", func(t *testing.T) { - set := flag.NewFlagSet("flagSet", flag.ExitOnError) - ctx := cli.NewContext(cli.NewApp(), set, nil) - gctx, _ := GetTimeoutContext(ctx) - _, ec := GetRPCClient(gctx, ctx) - require.Equal(t, 1, ec.ExitCode()) - }) - - t.Run("success", func(t *testing.T) { - set := flag.NewFlagSet("flagSet", flag.ExitOnError) - set.String(RPCEndpointFlag, srv.URL, "") - ctx := cli.NewContext(cli.NewApp(), set, nil) - gctx, _ := GetTimeoutContext(ctx) - _, ec := GetRPCClient(gctx, ctx) - require.Nil(t, ec) - }) - -} diff --git a/cli/options_test.go b/cli/options_test.go new file mode 100644 index 000000000..912501f89 --- /dev/null +++ b/cli/options_test.go @@ -0,0 +1,32 @@ +package main + +import ( + "flag" + "testing" + + "github.com/nspcc-dev/neo-go/cli/options" + "github.com/stretchr/testify/require" + "github.com/urfave/cli" +) + +func TestGetRPCClient(t *testing.T) { + e := newExecutor(t, true) + defer e.Close(t) + + t.Run("no endpoint", func(t *testing.T) { + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + ctx := cli.NewContext(cli.NewApp(), set, nil) + gctx, _ := options.GetTimeoutContext(ctx) + _, ec := options.GetRPCClient(gctx, ctx) + require.Equal(t, 1, ec.ExitCode()) + }) + + t.Run("success", func(t *testing.T) { + set := flag.NewFlagSet("flagSet", flag.ExitOnError) + set.String(options.RPCEndpointFlag, "http://"+e.RPC.Addr, "") + ctx := cli.NewContext(cli.NewApp(), set, nil) + gctx, _ := options.GetTimeoutContext(ctx) + _, ec := options.GetRPCClient(gctx, ctx) + require.Nil(t, ec) + }) +} diff --git a/cli/wallet/nep5.go b/cli/wallet/nep5.go index 961286c8e..e95ceac8a 100644 --- a/cli/wallet/nep5.go +++ b/cli/wallet/nep5.go @@ -15,11 +15,6 @@ import ( "github.com/urfave/cli" ) -var ( - neoToken = wallet.NewToken(client.NeoContractHash, "NEO", "neo", 0) - gasToken = wallet.NewToken(client.GasContractHash, "GAS", "gas", 8) -) - var ( tokenFlag = cli.StringFlag{ Name: "token", @@ -163,6 +158,10 @@ func getNEP5Balance(ctx *cli.Context) error { if err != nil { return err } + err = c.Init() + if err != nil { + return cli.NewExitError(fmt.Errorf("failed to init RPC client: %w", err), 1) + } name := ctx.String("token") @@ -210,12 +209,6 @@ func getNEP5Balance(ctx *cli.Context) error { } func getMatchingToken(ctx *cli.Context, w *wallet.Wallet, name string) (*wallet.Token, error) { - switch strings.ToLower(name) { - case "neo", client.NeoContractHash.StringLE(): - return neoToken, nil - case "gas", client.GasContractHash.StringLE(): - return gasToken, nil - } return getMatchingTokenAux(ctx, func(i int) *wallet.Token { return w.Extra.Tokens[i] }, len(w.Extra.Tokens), name) @@ -373,6 +366,10 @@ func multiTransferNEP5(ctx *cli.Context) error { if err != nil { return err } + err = c.Init() + if err != nil { + return cli.NewExitError(fmt.Errorf("failed to init RPC client: %w", err), 1) + } if ctx.NArg() == 0 { return cli.NewExitError("empty recipients list", 1) @@ -436,6 +433,10 @@ func transferNEP5(ctx *cli.Context) error { if err != nil { return err } + err = c.Init() + if err != nil { + return cli.NewExitError(fmt.Errorf("failed to init RPC client: %w", err), 1) + } toFlag := ctx.Generic("to").(*flags.Address) to := toFlag.Uint160() diff --git a/cli/wallet/validator.go b/cli/wallet/validator.go index 16d33fdb4..705b7667f 100644 --- a/cli/wallet/validator.go +++ b/cli/wallet/validator.go @@ -10,7 +10,6 @@ import ( "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/io" - "github.com/nspcc-dev/neo-go/pkg/rpc/client" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" @@ -100,8 +99,12 @@ func handleCandidate(ctx *cli.Context, method string) error { } gas := flags.Fixed8FromContext(ctx, "gas") + neoContractHash, err := c.GetNativeContractHash("neo") + if err != nil { + return err + } w := io.NewBufBinWriter() - emit.AppCallWithOperationAndArgs(w.BinWriter, client.NeoContractHash, method, acc.PrivateKey().PublicKey().Bytes()) + emit.AppCallWithOperationAndArgs(w.BinWriter, neoContractHash, method, acc.PrivateKey().PublicKey().Bytes()) emit.Opcodes(w.BinWriter, opcode.ASSERT) tx, err := c.CreateTxFromScript(w.Bytes(), acc, -1, int64(gas), transaction.Signer{ Account: acc.Contract.ScriptHash(), @@ -158,8 +161,12 @@ func handleVote(ctx *cli.Context) error { } gas := flags.Fixed8FromContext(ctx, "gas") + neoContractHash, err := c.GetNativeContractHash("neo") + if err != nil { + return cli.NewExitError(err, 1) + } w := io.NewBufBinWriter() - emit.AppCallWithOperationAndArgs(w.BinWriter, client.NeoContractHash, "vote", addr.BytesBE(), pubArg) + emit.AppCallWithOperationAndArgs(w.BinWriter, neoContractHash, "vote", addr.BytesBE(), pubArg) emit.Opcodes(w.BinWriter, opcode.ASSERT) tx, err := c.CreateTxFromScript(w.Bytes(), acc, -1, int64(gas), transaction.Signer{ diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index 2b3c98532..66318f727 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -12,7 +12,6 @@ import ( "github.com/nspcc-dev/neo-go/cli/options" "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/rpc/client" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/wallet" @@ -234,7 +233,11 @@ func claimGas(ctx *cli.Context) error { return err } - hash, err := c.TransferNEP5(acc, scriptHash, client.NeoContractHash, 0, 0) + neoContractHash, err := c.GetNativeContractHash("neo") + if err != nil { + return cli.NewExitError(err, 1) + } + hash, err := c.TransferNEP5(acc, scriptHash, neoContractHash, 0, 0) if err != nil { return cli.NewExitError(err, 1) } @@ -426,7 +429,7 @@ func importDeployed(ctx *cli.Context) error { return err } - cs, err := c.GetContractState(h) + cs, err := c.GetContractStateByHash(h) if err != nil { return cli.NewExitError(fmt.Errorf("can't fetch contract info: %w", err), 1) } diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index 44fe25f29..e72becf7f 100644 --- a/pkg/rpc/client/client.go +++ b/pkg/rpc/client/client.go @@ -14,6 +14,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/rpc/response" + "github.com/nspcc-dev/neo-go/pkg/util" ) const ( @@ -53,6 +54,7 @@ type Options struct { // cache stores cache values for the RPC client methods type cache struct { calculateValidUntilBlock calculateValidUntilBlockCache + nativeHashes map[string]util.Uint160 } // calculateValidUntilBlockCache stores cached number of validators and @@ -95,21 +97,34 @@ func New(ctx context.Context, endpoint string, opts Options) (*Client, error) { ctx: ctx, cli: httpClient, endpoint: url, + cache: cache{ + nativeHashes: make(map[string]util.Uint160), + }, } cl.opts = opts cl.requestF = cl.makeHTTPRequest return cl, nil } -// Init sets magic of the network client connected to. This method should be called -// before any transaction-, header- or block-related requests in order to deserialize -// responses properly. +// Init sets magic of the network client connected to and native NEO and GAS +// contracts scripthashes. This method should be called before any transaction-, +// header- or block-related requests in order to deserialize responses properly. func (c *Client) Init() error { version, err := c.GetVersion() if err != nil { return fmt.Errorf("failed to get network magic: %w", err) } c.network = version.Magic + neoContractHash, err := c.GetContractStateByAddressOrName("neo") + if err != nil { + return fmt.Errorf("failed to get NEO contract scripthash: %w", err) + } + c.cache.nativeHashes["neo"] = neoContractHash.ScriptHash() + gasContractHash, err := c.GetContractStateByAddressOrName("gas") + if err != nil { + return fmt.Errorf("failed to get GAS contract scripthash: %w", err) + } + c.cache.nativeHashes["gas"] = gasContractHash.ScriptHash() c.initDone = true return nil } diff --git a/pkg/rpc/client/nep5.go b/pkg/rpc/client/nep5.go index 3de9fbae7..ea8db995a 100644 --- a/pkg/rpc/client/nep5.go +++ b/pkg/rpc/client/nep5.go @@ -23,13 +23,6 @@ type TransferTarget struct { Amount int64 } -var ( - // NeoContractHash is a hash of the NEO native contract. - NeoContractHash, _ = util.Uint160DecodeStringBE("25059ecb4878d3a875f91c51ceded330d4575fde") - // GasContractHash is a hash of the GAS native contract. - GasContractHash, _ = util.Uint160DecodeStringBE("bcaf41d684c7d4ad6ee0d99da9707b9d1f0c8e66") -) - // NEP5Decimals invokes `decimals` NEP5 method on a specified contract. func (c *Client) NEP5Decimals(tokenHash util.Uint160) (int64, error) { result, err := c.InvokeFunction(tokenHash, "decimals", []smartcontract.Parameter{}, nil) diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index 33a596c01..7ac474acb 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "errors" "fmt" + "strings" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core" @@ -208,10 +209,25 @@ func (c *Client) GetCommittee() (keys.PublicKeys, error) { return *resp, nil } -// GetContractState queries contract information, according to the contract script hash. -func (c *Client) GetContractState(hash util.Uint160) (*state.Contract, error) { +// GetContractStateByHash queries contract information, according to the contract script hash. +func (c *Client) GetContractStateByHash(hash util.Uint160) (*state.Contract, error) { + return c.getContractState(hash.StringLE()) +} + +// GetContractStateByAddressOrName queries contract information, according to the contract address or name. +func (c *Client) GetContractStateByAddressOrName(addressOrName string) (*state.Contract, error) { + return c.getContractState(addressOrName) +} + +// GetContractStateByID queries contract information, according to the contract ID. +func (c *Client) GetContractStateByID(id int32) (*state.Contract, error) { + return c.getContractState(id) +} + +// getContractState is an internal representation of GetContractStateBy* methods. +func (c *Client) getContractState(param interface{}) (*state.Contract, error) { var ( - params = request.NewRawParams(hash.StringLE()) + params = request.NewRawParams(param) resp = &state.Contract{} ) if err := c.performRequest("getcontractstate", params, resp); err != nil { @@ -597,3 +613,18 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs func (c *Client) GetNetwork() netmode.Magic { return c.network } + +// GetNativeContractHash returns native contract hash by its name. It is not case-sensitive. +func (c *Client) GetNativeContractHash(name string) (util.Uint160, error) { + lowercasedName := strings.ToLower(name) + hash, ok := c.cache.nativeHashes[lowercasedName] + if ok { + return hash, nil + } + cs, err := c.GetContractStateByAddressOrName(name) + if err != nil { + return util.Uint160{}, err + } + c.cache.nativeHashes[lowercasedName] = cs.ScriptHash() + return cs.ScriptHash(), nil +} diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index d1913a12f..66844cd74 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -315,13 +315,57 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ }, "getcontractstate": { { - name: "positive", + name: "positive, by hash", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint160DecodeStringLE("1b4357bff5a01bdf2a6581247cf9ed1e24629176") if err != nil { panic(err) } - return c.GetContractState(hash) + return c.GetContractStateByHash(hash) + }, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, + result: func(c *Client) interface{} { + script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==") + if err != nil { + panic(err) + } + m := manifest.NewManifest(hash.Hash160(script)) + m.Features = smartcontract.HasStorage + cs := &state.Contract{ + ID: 0, + Script: script, + Manifest: *m, + } + _ = cs.ScriptHash() + return cs + }, + }, + { + name: "positive, by address", + invoke: func(c *Client) (interface{}, error) { + return c.GetContractStateByAddressOrName("NWiu5oejTu925aeL9Hc1LX8SvaJhE23h15") + }, + serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, + result: func(c *Client) interface{} { + script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==") + if err != nil { + panic(err) + } + m := manifest.NewManifest(hash.Hash160(script)) + m.Features = smartcontract.HasStorage + cs := &state.Contract{ + ID: 0, + Script: script, + Manifest: *m, + } + _ = cs.ScriptHash() + return cs + }, + }, + { + name: "positive, by id", + invoke: func(c *Client) (interface{}, error) { + return c.GetContractStateByID(0) }, serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, result: func(c *Client) interface{} { @@ -645,7 +689,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ }, "invokefunction": { { - name: "positive", + name: "positive, by scripthash", invoke: func(c *Client) (interface{}, error) { hash, err := util.Uint160DecodeStringLE("91b83e96f2a7c4fdf0c1688441ec61986c7cae26") if err != nil { @@ -991,7 +1035,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{ { name: "getcontractstate_invalid_params_error", invoke: func(c *Client) (interface{}, error) { - return c.GetContractState(util.Uint160{}) + return c.GetContractStateByHash(util.Uint160{}) }, }, { @@ -1178,7 +1222,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{ { name: "getcontractstate_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { - return c.GetContractState(util.Uint160{}) + return c.GetContractStateByHash(util.Uint160{}) }, }, { @@ -1372,13 +1416,7 @@ func initTestServer(t *testing.T, resp string) *httptest.Server { if err != nil { t.Fatalf("Cannot decode request body: %s", req.Body) } - var response string - switch r.Method { - case "getversion": - response = `{"id":1,"jsonrpc":"2.0","result":{"magic":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}` - default: - response = resp - } + response := wrapInitResponse(r, resp) ws.SetWriteDeadline(time.Now().Add(2 * time.Second)) err = ws.WriteMessage(1, []byte(response)) if err != nil { @@ -1393,27 +1431,49 @@ func initTestServer(t *testing.T, resp string) *httptest.Server { if err != nil { t.Fatalf("Cannot decode request body: %s", req.Body) } - requestHandler(t, r.Method, w, resp) + requestHandler(t, r, w, resp) })) return srv } -func requestHandler(t *testing.T, method string, w http.ResponseWriter, resp string) { +func requestHandler(t *testing.T, r *request.In, w http.ResponseWriter, resp string) { w.Header().Set("Content-Type", "application/json; charset=utf-8") - var response string - switch method { - case "getversion": - response = `{"id":1,"jsonrpc":"2.0","result":{"magic":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}` - default: - response = resp - } + response := wrapInitResponse(r, resp) _, err := w.Write([]byte(response)) if err != nil { t.Fatalf("Error writing response: %s", err.Error()) } } +func wrapInitResponse(r *request.In, resp string) string { + var response string + switch r.Method { + case "getversion": + response = `{"id":1,"jsonrpc":"2.0","result":{"magic":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}` + case "getcontractstate": + p, err := r.Params() + if err != nil { + response = resp + } + name, err := p.ValueWithType(0, request.StringT).GetString() + if err != nil { + response = resp + } + switch name { + case "neo": + response = `{"id":1,"jsonrpc":"2.0","result":{"id":-1,"script":"DANORU9Ba2d4Cw==","manifest":{"abi":{"hash":"0xde5f57d430d3dece511cf975a8d37848cb9e0525","methods":[{"name":"name","offset":0,"parameters":null,"returntype":"String"},{"name":"symbol","offset":0,"parameters":null,"returntype":"String"},{"name":"decimals","offset":0,"parameters":null,"returntype":"Integer"},{"name":"totalSupply","offset":0,"parameters":null,"returntype":"Integer"},{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer"},{"name":"transfer","offset":0,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Boolean"},{"name":"onPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"postPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"unclaimedGas","offset":0,"parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer"},{"name":"registerCandidate","offset":0,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"unregisterCandidate","offset":0,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"vote","offset":0,"parameters":[{"name":"account","type":"Hash160"},{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"getCandidates","offset":0,"parameters":null,"returntype":"Array"},{"name":"getŠ”ommittee","offset":0,"parameters":null,"returntype":"Array"},{"name":"getNextBlockValidators","offset":0,"parameters":null,"returntype":"Array"},{"name":"getGasPerBlock","offset":0,"parameters":null,"returntype":"Integer"},{"name":"setGasPerBlock","offset":0,"parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Boolean"}],"events":[{"name":"Transfer","parameters":null}]},"groups":[],"features":{"payable":false,"storage":false},"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-5"],"trusts":[],"safemethods":["name","symbol","decimals","totalSupply","balanceOf","unclaimedGas","getCandidates","getŠ”ommittee","getNextBlockValidators"],"extra":null},"hash":"0xde5f57d430d3dece511cf975a8d37848cb9e0525"}}` + case "gas": + response = `{"id":1,"jsonrpc":"2.0","result":{"id":-2,"script":"DANHQVNBa2d4Cw==","manifest":{"abi":{"hash":"0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc","methods":[{"name":"name","offset":0,"parameters":null,"returntype":"String"},{"name":"symbol","offset":0,"parameters":null,"returntype":"String"},{"name":"decimals","offset":0,"parameters":null,"returntype":"Integer"},{"name":"totalSupply","offset":0,"parameters":null,"returntype":"Integer"},{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer"},{"name":"transfer","offset":0,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Boolean"},{"name":"onPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"postPersist","offset":0,"parameters":null,"returntype":"Void"}],"events":[{"name":"Transfer","parameters":null}]},"groups":[],"features":{"payable":false,"storage":false},"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-5"],"trusts":[],"safemethods":["name","symbol","decimals","totalSupply","balanceOf"],"extra":null},"hash":"0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc"}}` + default: + response = resp + } + default: + response = resp + } + return response +} + func TestCalculateValidUntilBlock(t *testing.T) { var ( getBlockCountCalled int @@ -1434,7 +1494,7 @@ func TestCalculateValidUntilBlock(t *testing.T) { getValidatorsCalled++ response = `{"id":1,"jsonrpc":"2.0","result":[{"publickey":"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2","votes":"0","active":true},{"publickey":"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e","votes":"0","active":true},{"publickey":"03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699","votes":"0","active":true},{"publickey":"02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62","votes":"0","active":true}]}` } - requestHandler(t, r.Method, w, response) + requestHandler(t, r, w, response) })) defer srv.Close() @@ -1462,8 +1522,13 @@ func TestCalculateValidUntilBlock(t *testing.T) { func TestGetNetwork(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + r := request.NewIn() + err := r.DecodeData(req.Body) + if err != nil { + t.Fatalf("Cannot decode request body: %s", req.Body) + } // request handler already have `getversion` response wrapper - requestHandler(t, "getversion", w, "") + requestHandler(t, r, w, "") })) defer srv.Close() endpoint := srv.URL diff --git a/pkg/rpc/server/client_test.go b/pkg/rpc/server/client_test.go index f5e8adc96..4fe350bc1 100644 --- a/pkg/rpc/server/client_test.go +++ b/pkg/rpc/server/client_test.go @@ -277,7 +277,10 @@ func TestCreateNEP5TransferTx(t *testing.T) { acc, err := wallet.NewAccountFromWIF(priv.WIF()) require.NoError(t, err) - tx, err := c.CreateNEP5TransferTx(acc, util.Uint160{}, client.GasContractHash, 1000, 0) + gasContractHash, err := c.GetNativeContractHash("gas") + require.NoError(t, err) + + tx, err := c.CreateNEP5TransferTx(acc, util.Uint160{}, gasContractHash, 1000, 0) require.NoError(t, err) require.NoError(t, acc.SignTx(tx)) require.NoError(t, chain.VerifyTx(tx)) From 2f9d64821fec57fba700797c944a718c0c86a611 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 3 Nov 2020 14:08:59 +0300 Subject: [PATCH 4/5] cli: add ExitError where needed --- cli/wallet/multisig.go | 2 +- cli/wallet/nep5.go | 8 ++++---- cli/wallet/validator.go | 4 ++-- cli/wallet/wallet.go | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cli/wallet/multisig.go b/cli/wallet/multisig.go index bda83a331..4c730a33b 100644 --- a/cli/wallet/multisig.go +++ b/cli/wallet/multisig.go @@ -80,7 +80,7 @@ func signMultisig(ctx *cli.Context) error { c, err := options.GetRPCClient(gctx, ctx) if err != nil { - return err + return cli.NewExitError(err, 1) } res, err := c.SendRawTransaction(tx) if err != nil { diff --git a/cli/wallet/nep5.go b/cli/wallet/nep5.go index e95ceac8a..7ad69463d 100644 --- a/cli/wallet/nep5.go +++ b/cli/wallet/nep5.go @@ -156,7 +156,7 @@ func getNEP5Balance(ctx *cli.Context) error { c, err := options.GetRPCClient(gctx, ctx) if err != nil { - return err + return cli.NewExitError(err, 1) } err = c.Init() if err != nil { @@ -271,7 +271,7 @@ func importNEP5Token(ctx *cli.Context) error { c, err := options.GetRPCClient(gctx, ctx) if err != nil { - return err + return cli.NewExitError(err, 1) } tok, err := c.NEP5TokenInfo(tokenHash) @@ -364,7 +364,7 @@ func multiTransferNEP5(ctx *cli.Context) error { c, err := options.GetRPCClient(gctx, ctx) if err != nil { - return err + return cli.NewExitError(err, 1) } err = c.Init() if err != nil { @@ -431,7 +431,7 @@ func transferNEP5(ctx *cli.Context) error { c, err := options.GetRPCClient(gctx, ctx) if err != nil { - return err + return cli.NewExitError(err, 1) } err = c.Init() if err != nil { diff --git a/cli/wallet/validator.go b/cli/wallet/validator.go index 705b7667f..3efd6c4ec 100644 --- a/cli/wallet/validator.go +++ b/cli/wallet/validator.go @@ -95,7 +95,7 @@ func handleCandidate(ctx *cli.Context, method string) error { c, err := options.GetRPCClient(gctx, ctx) if err != nil { - return err + return cli.NewExitError(err, 1) } gas := flags.Fixed8FromContext(ctx, "gas") @@ -152,7 +152,7 @@ func handleVote(ctx *cli.Context) error { c, err := options.GetRPCClient(gctx, ctx) if err != nil { - return err + return cli.NewExitError(err, 1) } var pubArg interface{} diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index 66318f727..34564c8b7 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -230,7 +230,7 @@ func claimGas(ctx *cli.Context) error { c, err := options.GetRPCClient(gctx, ctx) if err != nil { - return err + return cli.NewExitError(err, 1) } neoContractHash, err := c.GetNativeContractHash("neo") @@ -426,7 +426,7 @@ func importDeployed(ctx *cli.Context) error { c, err := options.GetRPCClient(gctx, ctx) if err != nil { - return err + return cli.NewExitError(err, 1) } cs, err := c.GetContractStateByHash(h) From fc018afb95ec12d69bd503d5f53866d0215998ca Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 3 Nov 2020 14:42:41 +0300 Subject: [PATCH 5/5] docs: adjust `invokefunction` documentation --- docs/rpc.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/rpc.md b/docs/rpc.md index f4189d80b..c70ef5bc9 100644 --- a/docs/rpc.md +++ b/docs/rpc.md @@ -74,8 +74,6 @@ It's possible to use `invokefunction` not only with contract scripthash, but als with contract name (for native contracts) or contract ID (for all contracts). This feature is not supported by the C# node. -Both methods also don't currently support arrays in function parameters. - ##### `getunclaimedgas` It's possible to call this method for any address with neo-go, unlike with C#