forked from cerc-io/laconicd-deprecated
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:
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -6,4 +6,6 @@ const (
|
||||
|
||||
EvmStoreKey = "evmstore"
|
||||
EvmCodeKey = "evmcode"
|
||||
|
||||
RouterKey = ModuleName
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user