From 15a939b1dae72f5ee3b3c8e7aa6c76b89e8d7f30 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 25 Sep 2020 12:40:57 +0300 Subject: [PATCH] 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: `[]`,