rpc: allow to getcontractstate by address, id or name

close #1423
This commit is contained in:
Anna Shaleva 2020-09-25 12:40:57 +03:00
parent 7121686571
commit 15a939b1da
7 changed files with 112 additions and 5 deletions

View file

@ -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# 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. 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 ### Unsupported methods
Methods listed down below are not going to be supported for various reasons Methods listed down below are not going to be supported for various reasons

View file

@ -1073,6 +1073,15 @@ func (bc *Blockchain) GetContractScriptHash(id int32) (util.Uint160, error) {
return bc.dao.GetContractScriptHash(id) 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. // GetConfig returns the config stored in the blockchain.
func (bc *Blockchain) GetConfig() config.ProtocolConfiguration { func (bc *Blockchain) GetConfig() config.ProtocolConfiguration {
return bc.config return bc.config

View file

@ -39,6 +39,7 @@ type Blockchainer interface {
HasBlock(util.Uint256) bool HasBlock(util.Uint256) bool
HasTransaction(util.Uint256) bool HasTransaction(util.Uint256) bool
GetAppExecResult(util.Uint256) (*state.AppExecResult, error) GetAppExecResult(util.Uint256) (*state.AppExecResult, error)
GetNativeContractScriptHash(string) (util.Uint160, error)
GetNextBlockValidators() ([]*keys.PublicKey, error) GetNextBlockValidators() ([]*keys.PublicKey, error)
GetNEP5Balances(util.Uint160) *state.NEP5Balances GetNEP5Balances(util.Uint160) *state.NEP5Balances
GetValidators() ([]*keys.PublicKey, error) GetValidators() ([]*keys.PublicKey, error)

View file

@ -2,6 +2,7 @@ package native
import ( import (
"errors" "errors"
"strings"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
@ -35,6 +36,17 @@ func (cs *Contracts) ByHash(h util.Uint160) interop.Contract {
return nil 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 // NewContracts returns new set of native contracts with new GAS, NEO and Policy
// contracts. // contracts.
func NewContracts() *Contracts { func NewContracts() *Contracts {

View file

@ -89,6 +89,9 @@ func (chain testChain) GetContractState(hash util.Uint160) *state.Contract {
func (chain testChain) GetContractScriptHash(id int32) (util.Uint160, error) { func (chain testChain) GetContractScriptHash(id int32) (util.Uint160, error) {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetNativeContractScriptHash(name string) (util.Uint160, error) {
panic("TODO")
}
func (chain testChain) GetHeaderHash(int) util.Uint256 { func (chain testChain) GetHeaderHash(int) util.Uint256 {
return util.Uint256{} return util.Uint256{}
} }

View file

@ -765,6 +765,42 @@ func (s *Server) contractIDFromParam(param *request.Param) (int32, *response.Err
return result, nil 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) { func (s *Server) getStorage(ps request.Params) (interface{}, *response.Error) {
id, rErr := s.contractIDFromParam(ps.Value(0)) id, rErr := s.contractIDFromParam(ps.Value(0))
if rErr == response.ErrUnknown { if rErr == response.ErrUnknown {
@ -828,11 +864,12 @@ func (s *Server) getTransactionHeight(ps request.Params) (interface{}, *response
return height, nil 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) { 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 { if err != nil {
return nil, response.ErrInvalidParams return nil, err
} }
cs := s.chain.GetContractState(scriptHash) cs := s.chain.GetContractState(scriptHash)
if cs == nil { if cs == nil {

View file

@ -96,7 +96,7 @@ var rpcTestCases = map[string][]rpcTestCase{
}, },
"getcontractstate": { "getcontractstate": {
{ {
name: "positive", name: "positive, by hash",
params: fmt.Sprintf(`["%s"]`, testContractHash), params: fmt.Sprintf(`["%s"]`, testContractHash),
result: func(e *executor) interface{} { return &state.Contract{} }, result: func(e *executor) interface{} { return &state.Contract{} },
check: func(t *testing.T, e *executor, cs interface{}) { 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"]`, params: `["6d1eeca891ee93de2b7a77eb91c26f3b3c04d6c3"]`,
fail: true, fail: true,
}, },
{
name: "negative, bad ID",
params: `[-8]`,
fail: true,
},
{
name: "negative, bad native name",
params: `["unknown_native"]`,
fail: true,
},
{ {
name: "no params", name: "no params",
params: `[]`, params: `[]`,