Implemented rcp method GetAccountState (#124)
* Implemented rcp method GetAccountState * code clean up * Removed empty line * Used consistently github.com/pkg/errors package. Amended error message * Get rid of fmt.Sprintf and use either errors.Errorf or errors.Wrapf * cosmetic changes
This commit is contained in:
parent
a5e2df6942
commit
7e43717657
6 changed files with 105 additions and 23 deletions
|
@ -548,8 +548,8 @@ func (bc *Blockchain) HeaderHeight() uint32 {
|
||||||
return uint32(bc.headerListLen() - 1)
|
return uint32(bc.headerListLen() - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAssetState returns asset state from its assetID
|
||||||
func (bc *Blockchain) GetAssetState(assetID util.Uint256) *AssetState {
|
func (bc *Blockchain) GetAssetState(assetID util.Uint256) *AssetState {
|
||||||
|
|
||||||
var as *AssetState
|
var as *AssetState
|
||||||
bc.Store.Seek(storage.STAsset.Bytes(), func(k, v []byte) {
|
bc.Store.Seek(storage.STAsset.Bytes(), func(k, v []byte) {
|
||||||
var a AssetState
|
var a AssetState
|
||||||
|
@ -562,6 +562,19 @@ func (bc *Blockchain) GetAssetState(assetID util.Uint256) *AssetState {
|
||||||
return as
|
return as
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAccountState returns the account state from its script hash
|
||||||
|
func (bc *Blockchain) GetAccountState(scriptHash util.Uint160) *AccountState {
|
||||||
|
var as *AccountState
|
||||||
|
bc.Store.Seek(storage.STAccount.Bytes(), func(k, v []byte) {
|
||||||
|
var a AccountState
|
||||||
|
if err := a.DecodeBinary(bytes.NewReader(v)); err == nil && a.ScriptHash == scriptHash {
|
||||||
|
as = &a
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return as
|
||||||
|
}
|
||||||
|
|
||||||
func hashAndIndexToBytes(h util.Uint256, index uint32) []byte {
|
func hashAndIndexToBytes(h util.Uint256, index uint32) []byte {
|
||||||
buf := make([]byte, 4)
|
buf := make([]byte, 4)
|
||||||
binary.LittleEndian.PutUint32(buf, index)
|
binary.LittleEndian.PutUint32(buf, index)
|
||||||
|
|
|
@ -18,4 +18,5 @@ type Blockchainer interface {
|
||||||
HasBlock(util.Uint256) bool
|
HasBlock(util.Uint256) bool
|
||||||
HasTransaction(util.Uint256) bool
|
HasTransaction(util.Uint256) bool
|
||||||
GetAssetState(util.Uint256) *AssetState
|
GetAssetState(util.Uint256) *AssetState
|
||||||
|
GetAccountState(util.Uint160) *AccountState
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,9 @@ func (chain testChain) GetHeaderHash(int) util.Uint256 {
|
||||||
func (chain testChain) GetAssetState(util.Uint256) *core.AssetState {
|
func (chain testChain) GetAssetState(util.Uint256) *core.AssetState {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (chain testChain) GetAccountState(util.Uint160) *core.AccountState {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
func (chain testChain) CurrentHeaderHash() util.Uint256 {
|
func (chain testChain) CurrentHeaderHash() util.Uint256 {
|
||||||
return util.Uint256{}
|
return util.Uint256{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,17 @@ package rpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/core"
|
"github.com/CityOfZion/neo-go/pkg/core"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
"github.com/CityOfZion/neo-go/pkg/network"
|
"github.com/CityOfZion/neo-go/pkg/network"
|
||||||
"github.com/CityOfZion/neo-go/pkg/rpc/result"
|
"github.com/CityOfZion/neo-go/pkg/rpc/result"
|
||||||
"github.com/CityOfZion/neo-go/pkg/rpc/wrappers"
|
"github.com/CityOfZion/neo-go/pkg/rpc/wrappers"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ type (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
invalidBlockHeightError = func(index int, height int) error {
|
invalidBlockHeightError = func(index int, height int) error {
|
||||||
return fmt.Errorf("Param at index %d should be greater than or equal to 0 and less then or equal to current block height, got: %d", index, height)
|
return errors.Errorf("Param at index %d should be greater than or equal to 0 and less then or equal to current block height, got: %d", index, height)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -180,35 +181,48 @@ Methods:
|
||||||
|
|
||||||
results = peers
|
results = peers
|
||||||
|
|
||||||
case "validateaddress", "getblocksysfee", "getcontractstate", "getrawmempool", "getrawtransaction", "getstorage", "submitblock", "gettxout", "invoke", "invokefunction", "invokescript", "sendrawtransaction", "getaccountstate":
|
case "validateaddress", "getblocksysfee", "getcontractstate", "getrawmempool", "getrawtransaction", "getstorage", "submitblock", "gettxout", "invoke", "invokefunction", "invokescript", "sendrawtransaction":
|
||||||
results = "TODO"
|
results = "TODO"
|
||||||
|
|
||||||
case "getassetstate":
|
case "getassetstate":
|
||||||
var err error
|
var err error
|
||||||
|
param, exists := reqParams.ValueAtAndType(0, "string")
|
||||||
param, exists := reqParams.ValueAt(0)
|
|
||||||
if !exists {
|
if !exists {
|
||||||
err = errors.New("Param at index at 0 doesn't exist")
|
err = errors.New("expected param at index 0 to be a valid string assetID parameter")
|
||||||
resultsErr = NewInvalidParamsError(err.Error(), err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if param.Type != "string" {
|
|
||||||
err = errors.New("Param need to be a string")
|
|
||||||
resultsErr = NewInvalidParamsError(err.Error(), err)
|
resultsErr = NewInvalidParamsError(err.Error(), err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
paramAssetID, err := util.Uint256DecodeString(param.StringVal)
|
paramAssetID, err := util.Uint256DecodeString(param.StringVal)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrapf(err, "unable to decode %s to Uint256", param.StringVal)
|
||||||
|
resultsErr = NewInvalidParamsError(err.Error(), err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
as := s.chain.GetAssetState(paramAssetID)
|
as := s.chain.GetAssetState(paramAssetID)
|
||||||
|
|
||||||
if as != nil {
|
if as != nil {
|
||||||
results = wrappers.NewAssetState(as)
|
results = wrappers.NewAssetState(as)
|
||||||
} else {
|
} else {
|
||||||
results = "Invalid assetid"
|
results = "Invalid assetid"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "getaccountstate":
|
||||||
|
var err error
|
||||||
|
|
||||||
|
param, exists := reqParams.ValueAtAndType(0, "string")
|
||||||
|
if !exists {
|
||||||
|
err = errors.New("expected param at index 0 to be a valid string account address parameter")
|
||||||
|
resultsErr = NewInvalidParamsError(err.Error(), err)
|
||||||
|
} else if scriptHash, err := crypto.Uint160DecodeAddress(param.StringVal); err != nil {
|
||||||
|
err = errors.Wrapf(err, "unable to decode %s to Uint160", param.StringVal)
|
||||||
|
resultsErr = NewInvalidParamsError(err.Error(), err)
|
||||||
|
} else if as := s.chain.GetAccountState(scriptHash); as != nil {
|
||||||
|
results = wrappers.NewAccountState(as)
|
||||||
|
} else {
|
||||||
|
results = "Invalid public account address"
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
resultsErr = NewMethodNotFoundError(fmt.Sprintf("Method '%s' not supported", req.Method), nil)
|
resultsErr = NewMethodNotFoundError(fmt.Sprintf("Method '%s' not supported", req.Method), nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHandler(t *testing.T) {
|
func TestHandler(t *testing.T) {
|
||||||
|
|
||||||
// setup rpcServer server
|
// setup rpcServer server
|
||||||
net := config.ModeUnitTestNet
|
net := config.ModeUnitTestNet
|
||||||
|
|
||||||
configPath := "../../config"
|
configPath := "../../config"
|
||||||
|
|
||||||
cfg, err := config.Load(configPath, net)
|
cfg, err := config.Load(configPath, net)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("could not create levelDB chain", err)
|
t.Fatal("could not create levelDB chain", err)
|
||||||
|
@ -34,7 +31,6 @@ func TestHandler(t *testing.T) {
|
||||||
|
|
||||||
serverConfig := network.NewServerConfig(cfg)
|
serverConfig := network.NewServerConfig(cfg)
|
||||||
server := network.NewServer(serverConfig, chain)
|
server := network.NewServer(serverConfig, chain)
|
||||||
|
|
||||||
rpcServer := NewServer(chain, cfg.ApplicationConfiguration.RPCPort, server)
|
rpcServer := NewServer(chain, cfg.ApplicationConfiguration.RPCPort, server)
|
||||||
|
|
||||||
// setup handler
|
// setup handler
|
||||||
|
@ -55,11 +51,11 @@ func TestHandler(t *testing.T) {
|
||||||
|
|
||||||
{`{"jsonrpc": "2.0", "id": 1, "method": "getassetstate", "params": ["62c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"] }`,
|
{`{"jsonrpc": "2.0", "id": 1, "method": "getassetstate", "params": ["62c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"] }`,
|
||||||
"getassetstate_3",
|
"getassetstate_3",
|
||||||
`{"jsonrpc":"2.0","result":"Invalid assetid","id":1}`},
|
`{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params","data":"unable to decode 62c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7 to Uint256: expected string size of 64 got 63"},"id":1}`},
|
||||||
|
|
||||||
{`{"jsonrpc": "2.0", "id": 1, "method": "getassetstate", "params": [123] }`,
|
{`{"jsonrpc": "2.0", "id": 1, "method": "getassetstate", "params": [123] }`,
|
||||||
"getassetstate_4",
|
"getassetstate_4",
|
||||||
`{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params","data":"Param need to be a string"},"id":1}`},
|
`{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params","data":"expected param at index 0 to be a valid string assetID parameter"},"id":1}`},
|
||||||
|
|
||||||
{`{"jsonrpc": "2.0", "id": 1, "method": "getblockhash", "params": [10] }`,
|
{`{"jsonrpc": "2.0", "id": 1, "method": "getblockhash", "params": [10] }`,
|
||||||
"getblockhash_1",
|
"getblockhash_1",
|
||||||
|
@ -92,10 +88,24 @@ func TestHandler(t *testing.T) {
|
||||||
{`{"jsonrpc": "2.0", "id": 1, "method": "getpeers", "params": [] }`,
|
{`{"jsonrpc": "2.0", "id": 1, "method": "getpeers", "params": [] }`,
|
||||||
"getpeers",
|
"getpeers",
|
||||||
`{"jsonrpc":"2.0","result":{"unconnected":[],"connected":[],"bad":[]},"id":1}`},
|
`{"jsonrpc":"2.0","result":{"unconnected":[],"connected":[],"bad":[]},"id":1}`},
|
||||||
|
|
||||||
|
{`{ "jsonrpc": "2.0", "id": 1, "method": "getaccountstate", "params": ["AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y"] }`,
|
||||||
|
"getaccountstate_1",
|
||||||
|
`{"jsonrpc":"2.0","result":{"version":0,"address":"AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y","script_hash":"0xe9eed8dc39332032dc22e5d6e86332c50327ba23","frozen":false,"votes":[],"balances":{"602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7":"72099.99960000","c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b":"99989900"}},"id":1}`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{`{ "jsonrpc": "2.0", "id": 1, "method": "getaccountstate", "params": ["AK2nJJpJr6o664CWJKi1QRXjqeic2zR"] }`,
|
||||||
|
"getaccountstate_2",
|
||||||
|
`{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params","data":"unable to decode AK2nJJpJr6o664CWJKi1QRXjqeic2zR to Uint160: invalid base-58 check string: invalid checksum."},"id":1}`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{`{ "jsonrpc": "2.0", "id": 1, "method": "getaccountstate", "params": [123] }`,
|
||||||
|
"getaccountstate_3",
|
||||||
|
`{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid Params","data":"expected param at index 0 to be a valid string account address parameter"},"id":1}`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|
||||||
t.Run(fmt.Sprintf("method: %s, rpc call: %s", tc.method, tc.rpcCall), func(t *testing.T) {
|
t.Run(fmt.Sprintf("method: %s, rpc call: %s", tc.method, tc.rpcCall), func(t *testing.T) {
|
||||||
|
|
||||||
jsonStr := []byte(tc.rpcCall)
|
jsonStr := []byte(tc.rpcCall)
|
||||||
|
@ -105,9 +115,7 @@ func TestHandler(t *testing.T) {
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
handler(w, req)
|
handler(w, req)
|
||||||
|
|
||||||
resp := w.Result()
|
resp := w.Result()
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("could not read response from the request: %s", tc.rpcCall)
|
t.Errorf("could not read response from the request: %s", tc.rpcCall)
|
||||||
|
|
43
pkg/rpc/wrappers/account_state.go
Normal file
43
pkg/rpc/wrappers/account_state.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package wrappers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccountState wrapper used for the representation of
|
||||||
|
// core.AccountState on the RPC Server.
|
||||||
|
type AccountState struct {
|
||||||
|
Version uint8 `json:"version"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
ScriptHash util.Uint160 `json:"script_hash"`
|
||||||
|
IsFrozen bool `json:"frozen"`
|
||||||
|
Votes []*crypto.PublicKey `json:"votes"`
|
||||||
|
Balances map[string]util.Fixed8 `json:"balances"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAccountState creates a new AccountState wrapper.
|
||||||
|
func NewAccountState(a *core.AccountState) AccountState {
|
||||||
|
balances := make(map[string]util.Fixed8)
|
||||||
|
address := crypto.AddressFromUint160(a.ScriptHash)
|
||||||
|
|
||||||
|
for k, v := range a.Balances {
|
||||||
|
balances[k.String()] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// reverse scriptHash to be consistent with other client
|
||||||
|
scriptHash, err := util.Uint160DecodeBytes(a.ScriptHash.BytesReverse())
|
||||||
|
if err != nil {
|
||||||
|
scriptHash = a.ScriptHash
|
||||||
|
}
|
||||||
|
|
||||||
|
return AccountState{
|
||||||
|
Version: a.Version,
|
||||||
|
ScriptHash: scriptHash,
|
||||||
|
IsFrozen: a.IsFrozen,
|
||||||
|
Votes: a.Votes,
|
||||||
|
Balances: balances,
|
||||||
|
Address: address,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue