Basic RPC and CLI Queries (#77)

- Adds ethermint query command (`emintcli query ethermint <query>`)
    - Supports block number, storage, code, balance lookups
- Implements RPC API methods `eth_blockNumber`, `eth_getStorageAt`, `eth_getBalance`, and `eth_getCode`
- Adds tester utility for RPC calls
    - Adheres to go test format, but should not be run with regular suite
    - Requires daemon and RPC server to be running
    - Excluded from `make test`, available with `make test-rpc`
- Implemented AppModule interface and added EVM module to app
    - Required for routing
- Implements `InitGenesis` (`x/evm/genesis.go`) and stubs `ExportGenesis`
- Modifies GenesisAccount to match expected format
This commit is contained in:
David Ansermino
2019-07-25 16:38:55 -04:00
committed by GitHub
parent d9d45b48b9
commit 92dc7d9a59
18 changed files with 545 additions and 250 deletions
+96
View File
@@ -0,0 +1,96 @@
package cli
import (
"fmt"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/ethermint/x/evm/types"
"github.com/spf13/cobra"
)
func GetQueryCmd(moduleName string, cdc *codec.Codec) *cobra.Command {
evmQueryCmd := &cobra.Command{
Use: types.ModuleName,
Short: "Querying commands for the evm module",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
evmQueryCmd.AddCommand(client.GetCommands(
GetCmdGetBlockNumber(moduleName, cdc),
GetCmdGetStorageAt(moduleName, cdc),
GetCmdGetCode(moduleName, cdc),
)...)
return evmQueryCmd
}
// GetCmdGetBlockNumber queries information about the current block number
func GetCmdGetBlockNumber(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "block-number",
Short: "Gets block number (block height)",
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/blockNumber", queryRoute), nil)
if err != nil {
fmt.Printf("could not resolve: %s\n", err)
return nil
}
var out types.QueryResBlockNumber
cdc.MustUnmarshalJSON(res, &out)
return cliCtx.PrintOutput(out)
},
}
}
// GetCmdGetStorageAt queries a key in an accounts storage
func GetCmdGetStorageAt(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "storage [account] [key]",
Short: "Gets storage for an account at a given key",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
// TODO: Validate args
account := args[0]
key := args[1]
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/storage/%s/%s", queryRoute, account, key), nil)
if err != nil {
fmt.Printf("could not resolve: %s\n", err)
return nil
}
var out types.QueryResStorage
cdc.MustUnmarshalJSON(res, &out)
return cliCtx.PrintOutput(out)
},
}
}
// GetCmdGetCode queries the code field of a given address
func GetCmdGetCode(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "code [account]",
Short: "Gets code from an account",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
// TODO: Validate args
account := args[0]
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/code/%s", queryRoute, account), nil)
if err != nil {
fmt.Printf("could not resolve: %s\n", err)
return nil
}
var out types.QueryResCode
cdc.MustUnmarshalJSON(res, &out)
return cliCtx.PrintOutput(out)
},
}
}
+20 -16
View File
@@ -4,6 +4,9 @@ import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
abci "github.com/tendermint/tendermint/abci/types"
"math/big"
)
type (
@@ -15,8 +18,8 @@ type (
// GenesisAccount defines an account to be initialized in the genesis state.
GenesisAccount struct {
Address sdk.AccAddress `json:"address"`
Coins sdk.Coins `json:"coins"`
Address ethcmn.Address `json:"address"`
Balance *big.Int `json:"balance"`
Code []byte `json:"code,omitempty"`
Storage types.Storage `json:"storage,omitempty"`
}
@@ -24,11 +27,11 @@ type (
func ValidateGenesis(data GenesisState) error {
for _, acct := range data.Accounts {
if acct.Address == nil {
if len(acct.Address.Bytes()) == 0 {
return fmt.Errorf("Invalid GenesisAccount Error: Missing Address")
}
if acct.Coins == nil {
return fmt.Errorf("Invalid GenesisAccount Error: Missing Coins")
if acct.Balance == nil {
return fmt.Errorf("Invalid GenesisAccount Error: Missing Balance")
}
}
return nil
@@ -40,14 +43,15 @@ func DefaultGenesisState() GenesisState {
}
}
// TODO: Implement these once keeper is established
//func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) []abci.ValidatorUpdate {
// for _, record := range data.Accounts {
// // TODO: Add to keeper
// }
// return []abci.ValidatorUpdate{}
//}
//
//func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState {
// return GenesisState{Accounts: nil}
//}
func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) []abci.ValidatorUpdate {
for _, record := range data.Accounts {
keeper.SetCode(ctx, record.Address, record.Code)
keeper.CreateGenesisAccount(ctx, record)
}
return []abci.ValidatorUpdate{}
}
// TODO: Implement
func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState {
return GenesisState{Accounts: nil}
}
+19 -1
View File
@@ -1,6 +1,7 @@
package evm
import (
"github.com/cosmos/cosmos-sdk/codec"
ethcmn "github.com/ethereum/go-ethereum/common"
ethvm "github.com/ethereum/go-ethereum/core/vm"
@@ -17,14 +18,31 @@ import (
// to the StateDB interface
type Keeper struct {
csdb *types.CommitStateDB
cdc *codec.Codec
}
func NewKeeper(ak auth.AccountKeeper, storageKey, codeKey sdk.StoreKey) Keeper {
func NewKeeper(ak auth.AccountKeeper, storageKey, codeKey sdk.StoreKey, cdc *codec.Codec) Keeper {
return Keeper{
csdb: types.NewCommitStateDB(sdk.Context{}, ak, storageKey, codeKey),
cdc: cdc,
}
}
// ----------------------------------------------------------------------------
// Genesis
// ----------------------------------------------------------------------------
// CreateGenesisAccount initializes an account and its balance, code, and storage
func (k *Keeper) CreateGenesisAccount(ctx sdk.Context, account GenesisAccount) {
csdb := k.csdb.WithContext(ctx)
csdb.SetBalance(account.Address, account.Balance)
csdb.SetCode(account.Address, account.Code)
for _, key := range account.Storage {
csdb.SetState(account.Address, key, account.Storage[key])
}
}
// ----------------------------------------------------------------------------
// Setters
// ----------------------------------------------------------------------------
+59 -1
View File
@@ -4,11 +4,18 @@ import (
"encoding/json"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/ethermint/x/evm/client/cli"
"github.com/cosmos/ethermint/x/evm/types"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types"
)
var _ module.AppModuleBasic = AppModuleBasic{}
var _ module.AppModule = AppModule{}
// app module Basics object
type AppModuleBasic struct{}
@@ -42,10 +49,61 @@ func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router
// Get the root query command of this module
func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command {
return nil // cli.GetQueryCmd(StoreKey, cdc)
return cli.GetQueryCmd(types.ModuleName, cdc)
}
// Get the root tx command of this module
func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command {
return nil // cli.GetTxCmd(StoreKey, cdc)
}
type AppModule struct {
AppModuleBasic
keeper Keeper
}
// NewAppModule creates a new AppModule Object
func NewAppModule(keeper Keeper) AppModule {
return AppModule{
AppModuleBasic: AppModuleBasic{},
keeper: keeper,
}
}
func (AppModule) Name() string {
return types.ModuleName
}
func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {}
func (am AppModule) Route() string {
return types.RouterKey
}
func (am AppModule) NewHandler() sdk.Handler {
return nil // NewHandler(am.keeper)
}
func (am AppModule) QuerierRoute() string {
return types.ModuleName
}
func (am AppModule) NewQuerierHandler() sdk.Querier {
return NewQuerier(am.keeper)
}
func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {}
func (am AppModule) EndBlock(sdk.Context, abci.RequestEndBlock) []abci.ValidatorUpdate {
return []abci.ValidatorUpdate{}
}
func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate {
var genesisState GenesisState
types.ModuleCdc.MustUnmarshalJSON(data, &genesisState)
return InitGenesis(ctx, am.keeper, genesisState)
}
func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage {
gs := ExportGenesis(ctx, am.keeper)
return types.ModuleCdc.MustMarshalJSON(gs)
}
+105
View File
@@ -0,0 +1,105 @@
package evm
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/version"
"github.com/cosmos/ethermint/x/evm/types"
ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
abci "github.com/tendermint/tendermint/abci/types"
"math/big"
)
// Supported endpoints
const (
QueryProtocolVersion = "protocolVersion"
QueryBalance = "balance"
QueryBlockNumber = "blockNumber"
QueryStorage = "storage"
QueryCode = "code"
)
// NewQuerier is the module level router for state queries
func NewQuerier(keeper Keeper) sdk.Querier {
return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) {
switch path[0] {
case QueryProtocolVersion:
return queryProtocolVersion(keeper)
case QueryBalance:
return queryBalance(ctx, path, keeper)
case QueryBlockNumber:
return queryBlockNumber(ctx, keeper)
case QueryStorage:
return queryStorage(ctx, path, keeper)
case QueryCode:
return queryCode(ctx, path, keeper)
default:
return nil, sdk.ErrUnknownRequest("unknown query endpoint")
}
}
}
func queryProtocolVersion(keeper Keeper) ([]byte, sdk.Error) {
vers := version.ProtocolVersion
res, err := codec.MarshalJSONIndent(keeper.cdc, vers)
if err != nil {
panic("could not marshal result to JSON")
}
return res, nil
}
func queryBalance(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) {
addr := ethcmn.BytesToAddress([]byte(path[1]))
balance := keeper.GetBalance(ctx, addr)
hBalance := &hexutil.Big{}
err := hBalance.UnmarshalText(balance.Bytes())
if err != nil {
panic("could not marshal big.Int to hexutil.Big")
}
bRes := types.QueryResBalance{Balance: hBalance}
res, err := codec.MarshalJSONIndent(keeper.cdc, bRes)
if err != nil {
panic("could not marshal result to JSON")
}
return res, nil
}
func queryBlockNumber(ctx sdk.Context, keeper Keeper) ([]byte, sdk.Error) {
num := ctx.BlockHeight()
bnRes := types.QueryResBlockNumber{Number: big.NewInt(num)}
res, err := codec.MarshalJSONIndent(keeper.cdc, bnRes)
if err != nil {
panic("could not marshal result to JSON")
}
return res, nil
}
func queryStorage(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) {
addr := ethcmn.BytesToAddress([]byte(path[1]))
key := ethcmn.BytesToHash([]byte(path[2]))
val := keeper.GetState(ctx, addr, key)
bRes := types.QueryResStorage{Value: val.Bytes()}
res, err := codec.MarshalJSONIndent(keeper.cdc, bRes)
if err != nil {
panic("could not marshal result to JSON")
}
return res, nil
}
func queryCode(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) {
addr := ethcmn.BytesToAddress([]byte(path[1]))
code := keeper.GetCode(ctx, addr)
cRes := types.QueryResCode{Code: code}
res, err := codec.MarshalJSONIndent(keeper.cdc, cRes)
if err != nil {
panic("could not marshal result to JSON")
}
return res, nil
}
+2
View File
@@ -6,4 +6,6 @@ const (
EvmStoreKey = "evmstore"
EvmCodeKey = "evmcode"
RouterKey = ModuleName
)
+46
View File
@@ -0,0 +1,46 @@
package types
import (
"github.com/ethereum/go-ethereum/common/hexutil"
"math/big"
)
type QueryResProtocolVersion struct {
Version string `json:"result"`
}
func (q QueryResProtocolVersion) String() string {
return q.Version
}
type QueryResBalance struct {
Balance *hexutil.Big `json:"result"`
}
func (q QueryResBalance) String() string {
return q.Balance.String()
}
type QueryResBlockNumber struct {
Number *big.Int `json:"result"`
}
func (q QueryResBlockNumber) String() string {
return q.Number.String()
}
type QueryResStorage struct {
Value []byte `json:"value"`
}
func (q QueryResStorage) String() string {
return string(q.Value)
}
type QueryResCode struct {
Code []byte
}
func (q QueryResCode) String() string {
return string(q.Code)
}