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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 545 additions and 250 deletions

View File

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
PACKAGES=$(shell go list ./... | grep -Ev 'vendor|importer')
PACKAGES=$(shell go list ./... | grep -Ev 'vendor|importer|rpc/tester')
COMMIT_HASH := $(shell git rev-parse --short HEAD)
BUILD_FLAGS = -tags netgo -ldflags "-X github.com/cosmos/ethermint/version.GitCommit=${COMMIT_HASH}"
DOCKER_TAG = unstable
@ -146,6 +146,9 @@ test-import:
--blockchain blockchain --timeout=5m
# TODO: remove tmp directory after test run to avoid subsequent errors
test-rpc:
@${GO_MOD} go test -v --vet=off ./rpc/tester
godocs:
@echo "--> Wait a few seconds and visit http://localhost:6060/pkg/github.com/cosmos/ethermint"
godoc -http=:6060

View File

@ -59,8 +59,7 @@ var (
crisis.AppModuleBasic{},
slashing.AppModuleBasic{},
supply.AppModuleBasic{},
// TODO: Enable EVM AppModuleBasic
//evm.AppModuleBasic{},
evm.AppModuleBasic{},
)
)
@ -185,7 +184,7 @@ func NewEthermintApp(logger tmlog.Logger, db dbm.DB, loadLatest bool,
app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, &stakingKeeper,
slashingSubspace, slashing.DefaultCodespace)
app.crisisKeeper = crisis.NewKeeper(crisisSubspace, invCheckPeriod, app.supplyKeeper, auth.FeeCollectorName)
app.evmKeeper = evm.NewKeeper(app.accountKeeper, app.evmStoreKey, app.evmCodeKey)
app.evmKeeper = evm.NewKeeper(app.accountKeeper, app.evmStoreKey, app.evmCodeKey, cdc)
// register the proposal types
govRouter := gov.NewRouter()
@ -212,6 +211,7 @@ func NewEthermintApp(logger tmlog.Logger, db dbm.DB, loadLatest bool,
mint.NewAppModule(app.mintKeeper),
slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper),
staking.NewAppModule(app.stakingKeeper, app.distrKeeper, app.accountKeeper, app.supplyKeeper),
evm.NewAppModule(app.evmKeeper),
)
// During begin block slashing happens after distr.BeginBlocker so that

View File

@ -1,6 +1,8 @@
package main
import (
"github.com/cosmos/ethermint/rpc"
"github.com/tendermint/go-amino"
"os"
"path"
@ -10,7 +12,6 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
emintapp "github.com/cosmos/ethermint/app"
"github.com/cosmos/ethermint/rpc"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/libs/cli"
@ -43,7 +44,7 @@ func main() {
rootCmd.AddCommand(
sdkrpc.StatusCommand(),
client.ConfigCmd(emintapp.DefaultCLIHome),
// TODO: Set up query command
queryCmd(cdc),
// TODO: Set up tx command
// TODO: Set up rest routes (if included, different from web3 api)
rpc.Web3RpcCmd(cdc),
@ -59,6 +60,24 @@ func main() {
}
}
func queryCmd(cdc *amino.Codec) *cobra.Command {
queryCmd := &cobra.Command{
Use: "query",
Aliases: []string{"q"},
Short: "Querying subcommands",
}
// TODO: Possibly add these query commands from other modules
//queryCmd.AddCommand(
// ...
//)
// add modules' query commands
emintapp.ModuleBasics.AddQueryCommands(queryCmd, cdc)
return queryCmd
}
func initConfig(cmd *cobra.Command) error {
home, err := cmd.PersistentFlags().GetString(cli.HomeFlag)
if err != nil {

1
go.mod
View File

@ -46,6 +46,7 @@ require (
github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect
github.com/stretchr/testify v1.3.0
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/tendermint/go-amino v0.15.0
github.com/tendermint/tendermint v0.32.0
github.com/tyler-smith/go-bip39 v1.0.0 // indirect
github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 // indirect

10
go.sum
View File

@ -39,15 +39,11 @@ github.com/cespare/cp v1.1.1/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cosmos/cosmos-sdk v0.28.2-0.20190709220430-3f519832a7a5 h1:gakqjbZrqlUB1/rx8r/s86SVcRatOafVDfJF99yBcng=
github.com/cosmos/cosmos-sdk v0.28.2-0.20190709220430-3f519832a7a5/go.mod h1:qzvnGkt2+ynMpjmf9/dws/94/qM87awRbuyvF7r2R8Q=
github.com/cosmos/cosmos-sdk v0.28.2-0.20190711105643-280734d0e37f h1:jmVM19bsHZRVVe8rugzfILuL3VPgCj5b6941I20Naw0=
github.com/cosmos/cosmos-sdk v0.28.2-0.20190711105643-280734d0e37f/go.mod h1:qzvnGkt2+ynMpjmf9/dws/94/qM87awRbuyvF7r2R8Q=
github.com/cosmos/cosmos-sdk v0.35.0 h1:EPeie1aKHwnXtTzKggvabG7aAPN+DDmju2xquvjFwao=
github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8 h1:Iwin12wRQtyZhH6FV3ykFcdGNlYEzoeR0jN8Vn+JWsI=
github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y=
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU=
@ -124,8 +120,6 @@ github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
@ -146,7 +140,6 @@ github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
@ -212,7 +205,6 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
@ -301,7 +293,6 @@ github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+m
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8=
github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -340,7 +331,6 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7 h1:bit1t3mgdR35yN0cX0G8orgLtOuyL9Wqxa1mccLB0ig=
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View File

@ -1,77 +0,0 @@
package rpc
import (
"context"
sdkcontext"github.com/cosmos/cosmos-sdk/client/context"
"testing"
"time"
"github.com/cosmos/ethermint/version"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
type apisTestSuite struct {
suite.Suite
Stop context.CancelFunc
Port int
}
func (s *apisTestSuite) SetupSuite() {
stop, port, err := startAPIServer()
require.Nil(s.T(), err, "unexpected error")
s.Stop = stop
s.Port = port
}
func (s *apisTestSuite) TearDownSuite() {
s.Stop()
}
func (s *apisTestSuite) TestPublicWeb3APIClientVersion() {
res, err := rpcCall(s.Port, "web3_clientVersion", []string{})
require.Nil(s.T(), err, "unexpected error")
require.Equal(s.T(), version.ClientVersion(), res)
}
func (s *apisTestSuite) TestPublicWeb3APISha3() {
res, err := rpcCall(s.Port, "web3_sha3", []string{"0x67656c6c6f20776f726c64"})
require.Nil(s.T(), err, "unexpected error")
require.Equal(s.T(), "0x1b84adea42d5b7d192fd8a61a85b25abe0757e9a65cab1da470258914053823f", res)
}
func (s *apisTestSuite) TestMiningAPIs() {
res, err := rpcCall(s.Port, "eth_mining", nil)
require.Nil(s.T(), err, "unexpected error")
require.Equal(s.T(), false, res)
res, err = rpcCall(s.Port, "eth_hashrate", nil)
require.Nil(s.T(), err, "unexpected error")
require.Equal(s.T(), "0x0", res)
}
func TestAPIsTestSuite(t *testing.T) {
suite.Run(t, new(apisTestSuite))
}
func startAPIServer() (context.CancelFunc, int, error) {
config := &Config{
RPCAddr: "127.0.0.1",
RPCPort: randomPort(),
}
timeouts := rpc.HTTPTimeouts{
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
IdleTimeout: 5 * time.Second,
}
ctx, cancel := context.WithCancel(context.Background())
_, err := StartHTTPEndpoint(ctx, config, GetRPCAPIs(sdkcontext.NewCLIContext()), timeouts)
if err != nil {
return cancel, 0, err
}
return cancel, config.RPCPort, nil
}

View File

@ -1,8 +1,10 @@
package rpc
import (
"fmt"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/ethermint/version"
"github.com/cosmos/ethermint/x/evm/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
@ -61,20 +63,43 @@ func (e *PublicEthAPI) Accounts() []common.Address {
// BlockNumber returns the current block number.
func (e *PublicEthAPI) BlockNumber() *big.Int {
return big.NewInt(0)
res, _, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/blockNumber", types.ModuleName), nil)
if err != nil {
fmt.Printf("could not resolve: %s\n", err)
return nil
}
var out types.QueryResBlockNumber
e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
return out.Number
}
// GetBalance returns the provided account's balance up to the provided block number.
func (e *PublicEthAPI) GetBalance(address common.Address, blockNum rpc.BlockNumber) *hexutil.Big {
out := big.NewInt(0)
return (*hexutil.Big)(out)
res, _, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/balance/%s", types.ModuleName, address), nil)
if err != nil {
fmt.Printf("could not resolve: %s\n", err)
return nil
}
var out types.QueryResBalance
e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
return (*hexutil.Big)(out.Balance)
}
// GetStorageAt returns the contract storage at the given address, block number, and key.
func (e *PublicEthAPI) GetStorageAt(address common.Address, key string, blockNum rpc.BlockNumber) hexutil.Bytes {
res, _, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/storage/%s/%s", types.ModuleName, address, key), nil)
if err != nil {
fmt.Printf("could not resolve: %s\n", err)
return nil
}
var out types.QueryResStorage
e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
return out.Value[:]
}
// GetTransactionCount returns the number of transactions at the given address up to the given block number.
func (e *PublicEthAPI) GetTransactionCount(address common.Address, blockNum rpc.BlockNumber) hexutil.Uint64 {
return 0
@ -102,9 +127,17 @@ func (e *PublicEthAPI) GetUncleCountByBlockNumber(blockNum rpc.BlockNumber) hexu
// GetCode returns the contract code at the given address and block number.
func (e *PublicEthAPI) GetCode(address common.Address, blockNumber rpc.BlockNumber) hexutil.Bytes {
res, _, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/code/%s", types.ModuleName, address), nil)
if err != nil {
fmt.Printf("could not resolve: %s\n", err)
return nil
}
var out types.QueryResCode
e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
return out.Code
}
// Sign signs the provided data using the private key of address via Geth's signature standard.
func (e *PublicEthAPI) Sign(address common.Address, data hexutil.Bytes) hexutil.Bytes {
return nil

View File

@ -1,38 +0,0 @@
package rpc
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/rpc"
)
// StartHTTPEndpoint starts the Tendermint Web3-compatible RPC layer. Consumes
// a Context for cancellation, a config struct, and a list of rpc.API interfaces
// that will be automatically wired into a JSON-RPC webserver.
func StartHTTPEndpoint(ctx context.Context, config *Config, apis []rpc.API, timeouts rpc.HTTPTimeouts) (*rpc.Server, error) {
uniqModules := make(map[string]string)
for _, api := range apis {
uniqModules[api.Namespace] = api.Namespace
}
modules := make([]string, len(uniqModules))
i := 0
for k := range uniqModules {
modules[i] = k
i++
}
endpoint := fmt.Sprintf("%s:%d", config.RPCAddr, config.RPCPort)
_, server, err := rpc.StartHTTPEndpoint(
endpoint, apis, modules, config.RPCCORSDomains, config.RPCVHosts, timeouts,
)
go func() {
<-ctx.Done()
fmt.Println("Shutting down server.")
server.Stop()
}()
return server, err
}

View File

@ -1,94 +0,0 @@
package rpc
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"strings"
"testing"
"time"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
)
type TestService struct{}
func (s *TestService) Foo(arg string) string {
return arg
}
func TestStartHTTPEndpointStartStop(t *testing.T) {
config := &Config{
RPCAddr: "127.0.0.1",
RPCPort: randomPort(),
}
ctx, cancel := context.WithCancel(context.Background())
_, err := StartHTTPEndpoint(
ctx, config, []rpc.API{
{
Namespace: "test",
Version: "1.0",
Service: &TestService{},
Public: true,
},
},
rpc.HTTPTimeouts{
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
IdleTimeout: 5 * time.Second,
},
)
require.Nil(t, err, "unexpected error")
res, err := rpcCall(config.RPCPort, "test_foo", []string{"baz"})
require.Nil(t, err, "unexpected error")
resStr := res.(string)
require.Equal(t, "baz", resStr)
cancel()
_, err = rpcCall(config.RPCPort, "test_foo", []string{"baz"})
require.NotNil(t, err)
}
func rpcCall(port int, method string, params []string) (interface{}, error) {
parsedParams, err := json.Marshal(params)
if err != nil {
return nil, err
}
fullBody := fmt.Sprintf(
`{ "id": 1, "jsonrpc": "2.0", "method": "%s", "params": %s }`,
method, string(parsedParams),
)
res, err := http.Post(fmt.Sprintf("http://127.0.0.1:%d", port), "application/json", strings.NewReader(fullBody))
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
var out map[string]interface{}
err = json.Unmarshal(data, &out)
if err != nil {
return nil, err
}
result := out["result"].(interface{})
return result, nil
}
func randomPort() int {
return rand.Intn(65535-1025) + 1025
}

129
rpc/tester/tester_test.go Normal file
View File

@ -0,0 +1,129 @@
// This is a test utility for Ethermint's Web3 JSON-RPC services.
//
// To run these tests please first ensure you have the emintd running
// and have started the RPC service with `emintcl rest-server`.
//
// You can configure the desired port (or host) below.
package tester
import (
"bytes"
"encoding/json"
"fmt"
"github.com/cosmos/ethermint/version"
"github.com/cosmos/ethermint/x/evm/types"
"io/ioutil"
"math/big"
"net/http"
"testing"
)
const (
host = "127.0.0.1"
port = 1317
addrA = "0xc94770007dda54cF92009BFF0dE90c06F603a09f"
addrAStoreKey = 0
)
var addr = fmt.Sprintf("http://%s:%d/rpc", host, port)
type Request struct {
Version string `json:"jsonrpc"`
Method string `json:"method"`
Params []string `json:"params"`
Id int `json:"id"`
}
func createRequest(method string, params []string) Request {
return Request{
Version: "2.0",
Method: method,
Params: params,
Id: 1,
}
}
func call(t *testing.T, method string, params []string, resp interface{}) {
req, err := json.Marshal(createRequest(method, params))
if err != nil {
t.Error(err)
}
res, err := http.Post(addr, "application/json", bytes.NewBuffer(req))
if err != nil {
t.Error(err)
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Error(err)
}
err = json.Unmarshal(body, resp)
if err != nil {
t.Error(err)
}
}
func TestEth_protocolVersion(t *testing.T) {
expectedRes := version.ProtocolVersion
res := &types.QueryResProtocolVersion{}
call(t, "eth_protocolVersion", []string{}, res)
t.Logf("Got protocol version: %s\n", res.Version)
if res.Version != expectedRes {
t.Errorf("expected: %s got: %s\n", expectedRes, res)
}
}
func TestEth_blockNumber(t *testing.T) {
res := &types.QueryResBlockNumber{}
call(t, "eth_blockNumber", []string{}, res)
t.Logf("Got block number: %s\n", res.Number.String())
// -1 if x < y, 0 if x == y; where x is res, y is 0
if res.Number.Cmp(big.NewInt(0)) < 1 {
t.Errorf("Invalid block number got: %v", res)
}
}
func TestEth_GetBalance(t *testing.T) {
//expectedRes := types.QueryResBalance{Balance:}
res := &types.QueryResBalance{}
call(t, "eth_getBalance", []string{addrA, "latest"}, res)
t.Logf("Got balance %s for %s\n", res.Balance.String(), addrA)
// 0 if x == y; where x is res, y is 0
if res.Balance.ToInt().Cmp(big.NewInt(0)) != 0 {
t.Errorf("expected balance: %d, got: %s", 0, res.Balance.String())
}
}
func TestEth_GetStorageAt(t *testing.T) {
expectedRes := types.QueryResStorage{Value: []byte{}}
res := &types.QueryResStorage{}
call(t, "eth_getStorageAt", []string{addrA, string(addrAStoreKey), "latest"}, res)
t.Logf("Got value [%X] for %s with key %X\n", res.Value, addrA, addrAStoreKey)
if !bytes.Equal(res.Value, expectedRes.Value) {
t.Errorf("expected: %X got: %X", expectedRes.Value, res.Value)
}
}
func TestEth_GetCode(t *testing.T) {
expectedRes := types.QueryResCode{Code: []byte{}}
res := &types.QueryResCode{}
call(t, "eth_getCode", []string{addrA, "latest"}, res)
t.Logf("Got code [%X] for %s\n", res.Code, addrA)
if !bytes.Equal(expectedRes.Code, res.Code) {
t.Errorf("expected: %X got: %X", expectedRes.Code, res.Code)
}
}

96
x/evm/client/cli/query.go Normal file
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)
},
}
}

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}
}

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
// ----------------------------------------------------------------------------

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
x/evm/querier.go Normal file
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
}

View File

@ -6,4 +6,6 @@ const (
EvmStoreKey = "evmstore"
EvmCodeKey = "evmcode"
RouterKey = ModuleName
)

46
x/evm/types/querier.go Normal file
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)
}