From 25022f826bccc3991e19cd30e91eabdc1d1ff858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Kunze=20K=C3=BCllmer?= Date: Mon, 22 Nov 2021 09:58:48 +0100 Subject: [PATCH 1/5] cmd: update testnet command --- cmd/evmosd/testnet.go | 5 +- testutil/network/network.go | 675 +------------------------------ testutil/network/network_test.go | 12 +- testutil/network/util.go | 260 ------------ 4 files changed, 29 insertions(+), 923 deletions(-) delete mode 100644 testutil/network/util.go diff --git a/cmd/evmosd/testnet.go b/cmd/evmosd/testnet.go index 1fec932023..3fc0003805 100644 --- a/cmd/evmosd/testnet.go +++ b/cmd/evmosd/testnet.go @@ -42,7 +42,8 @@ import ( ethermint "github.com/tharsis/ethermint/types" evmtypes "github.com/tharsis/ethermint/x/evm/types" - "github.com/tharsis/evmos/testutil/network" + "github.com/tharsis/ethermint/testutil/network" + evmosnetwork "github.com/tharsis/evmos/testutil/network" ) var ( @@ -519,7 +520,7 @@ func calculateIP(ip string, i int) (string, error) { // startTestnet starts an in-process testnet func startTestnet(cmd *cobra.Command, args startArgs) error { - networkConfig := network.DefaultConfig() + networkConfig := evmosnetwork.DefaultConfig() // Default networkConfig.ChainID is random, and we should only override it if chainID provided // is non-empty diff --git a/testutil/network/network.go b/testutil/network/network.go index 467ea13769..cac775da11 100644 --- a/testutil/network/network.go +++ b/testutil/network/network.go @@ -1,70 +1,43 @@ package network import ( - "bufio" - "context" - "encoding/json" - "errors" "fmt" - "net/http" - "net/url" - "os" - "path/filepath" - "strings" - "sync" - "testing" - "time" - "github.com/ethereum/go-ethereum/common" - "github.com/spf13/cobra" - tmcfg "github.com/tendermint/tendermint/config" - tmflags "github.com/tendermint/tendermint/libs/cli/flags" - "github.com/tendermint/tendermint/libs/log" tmrand "github.com/tendermint/tendermint/libs/rand" - "github.com/tendermint/tendermint/node" - tmclient "github.com/tendermint/tendermint/rpc/client" dbm "github.com/tendermint/tm-db" - "google.golang.org/grpc" "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/server" - "github.com/cosmos/cosmos-sdk/server/api" - srvconfig "github.com/cosmos/cosmos-sdk/server/config" servertypes "github.com/cosmos/cosmos-sdk/server/types" "github.com/cosmos/cosmos-sdk/simapp" "github.com/cosmos/cosmos-sdk/simapp/params" storetypes "github.com/cosmos/cosmos-sdk/store/types" - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/cosmos/cosmos-sdk/x/genutil" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/tharsis/ethermint/crypto/hd" "github.com/tharsis/ethermint/encoding" - "github.com/tharsis/ethermint/server/config" - ethermint "github.com/tharsis/ethermint/types" - evmtypes "github.com/tharsis/ethermint/x/evm/types" + "github.com/tharsis/ethermint/testutil/network" "github.com/tharsis/evmos/app" ) -// package-wide network lock to only allow one test network at a time -var lock = new(sync.Mutex) +// DefaultConfig returns a sane default configuration suitable for nearly all +// testing requirements. +func DefaultConfig() network.Config { + encCfg := encoding.MakeConfig(app.ModuleBasics) + cfg := network.DefaultConfig() -// AppConstructor defines a function which accepts a network configuration and -// creates an ABCI Application to provide to Tendermint. -type AppConstructor = func(val Validator) servertypes.Application + cfg.Codec = encCfg.Marshaler + cfg.TxConfig = encCfg.TxConfig + cfg.LegacyAmino = encCfg.Amino + cfg.InterfaceRegistry = encCfg.InterfaceRegistry + cfg.AppConstructor = NewAppConstructor(encCfg) + cfg.GenesisState = app.ModuleBasics.DefaultGenesis(encCfg.Marshaler) + + cfg.ChainID = fmt.Sprintf("evmos_%d-1", tmrand.Int63n(9999999999999)+1) + return cfg +} -// NewAppConstructor returns a new simapp AppConstructor -func NewAppConstructor(encodingCfg params.EncodingConfig) AppConstructor { - return func(val Validator) servertypes.Application { +// NewAppConstructor returns a new Evmos AppConstructor +func NewAppConstructor(encodingCfg params.EncodingConfig) network.AppConstructor { + return func(val network.Validator) servertypes.Application { return app.NewEvmos( val.Ctx.Logger, dbm.NewMemDB(), nil, true, make(map[int64]bool), val.Ctx.Config.RootDir, 0, encodingCfg, @@ -74,613 +47,3 @@ func NewAppConstructor(encodingCfg params.EncodingConfig) AppConstructor { ) } } - -// Config defines the necessary configuration used to bootstrap and start an -// in-process local testing network. -type Config struct { - Codec codec.Codec - LegacyAmino *codec.LegacyAmino // TODO: Remove! - InterfaceRegistry codectypes.InterfaceRegistry - - TxConfig client.TxConfig - AccountRetriever client.AccountRetriever - AppConstructor AppConstructor // the ABCI application constructor - GenesisState simapp.GenesisState // custom gensis state to provide - TimeoutCommit time.Duration // the consensus commitment timeout - ChainID string // the network chain-id - NumValidators int // the total number of validators to create and bond - BondDenom string // the staking bond denomination - MinGasPrices string // the minimum gas prices each validator will accept - AccountTokens sdk.Int // the amount of unique validator tokens (e.g. 1000node0) - StakingTokens sdk.Int // the amount of tokens each validator has available to stake - BondedTokens sdk.Int // the amount of tokens each validator stakes - PruningStrategy string // the pruning strategy each validator will have - EnableTMLogging bool // enable Tendermint logging to STDOUT - CleanupDir bool // remove base temporary directory during cleanup - SigningAlgo string // signing algorithm for keys - KeyringOptions []keyring.Option // keyring configuration options - RPCAddress string // RPC listen address (including port) - JSONRPCAddress string // JSON-RPC listen address (including port) - APIAddress string // REST API listen address (including port) - GRPCAddress string // GRPC server listen address (including port) - PrintMnemonic bool // print the mnemonic of first validator as log output for testing -} - -// DefaultConfig returns a sane default configuration suitable for nearly all -// testing requirements. -func DefaultConfig() Config { - encCfg := encoding.MakeConfig(app.ModuleBasics) - - return Config{ - Codec: encCfg.Marshaler, - TxConfig: encCfg.TxConfig, - LegacyAmino: encCfg.Amino, - InterfaceRegistry: encCfg.InterfaceRegistry, - AccountRetriever: authtypes.AccountRetriever{}, - AppConstructor: NewAppConstructor(encCfg), - GenesisState: app.ModuleBasics.DefaultGenesis(encCfg.Marshaler), - TimeoutCommit: 2 * time.Second, - ChainID: fmt.Sprintf("evmos_%d-1", tmrand.Int63n(9999999999999)+1), - NumValidators: 4, - BondDenom: ethermint.AttoPhoton, - MinGasPrices: fmt.Sprintf("0.000006%s", ethermint.AttoPhoton), - AccountTokens: sdk.TokensFromConsensusPower(1000, ethermint.PowerReduction), - StakingTokens: sdk.TokensFromConsensusPower(500, ethermint.PowerReduction), - BondedTokens: sdk.TokensFromConsensusPower(100, ethermint.PowerReduction), - PruningStrategy: storetypes.PruningOptionNothing, - CleanupDir: true, - SigningAlgo: string(hd.EthSecp256k1Type), - KeyringOptions: []keyring.Option{hd.EthSecp256k1Option()}, - PrintMnemonic: false, - } -} - -type ( - // Network defines a local in-process testing network using SimApp. It can be - // configured to start any number of validators, each with its own RPC and API - // clients. Typically, this test network would be used in client and integration - // testing where user input is expected. - // - // Note, due to Tendermint constraints in regards to RPC functionality, there - // may only be one test network running at a time. Thus, any caller must be - // sure to Cleanup after testing is finished in order to allow other tests - // to create networks. In addition, only the first validator will have a valid - // RPC and API server/client. - Network struct { - Logger Logger - BaseDir string - Validators []*Validator - - Config Config - } - - // Validator defines an in-process Tendermint validator node. Through this object, - // a client can make RPC and API calls and interact with any client command - // or handler. - Validator struct { - AppConfig *config.Config - ClientCtx client.Context - Ctx *server.Context - Dir string - NodeID string - PubKey cryptotypes.PubKey - Moniker string - APIAddress string - RPCAddress string - P2PAddress string - JSONRPCAddress string - Address sdk.AccAddress - ValAddress sdk.ValAddress - RPCClient tmclient.Client - - tmNode *node.Node - api *api.Server - grpc *grpc.Server - grpcWeb *http.Server - jsonrpc *http.Server - jsonrpcDone chan struct{} - } -) - -// Logger is a network logger interface that exposes testnet-level Log() methods for an in-process testing network -// This is not to be confused with logging that may happen at an individual node or validator level -type Logger interface { - Log(args ...interface{}) - Logf(format string, args ...interface{}) -} - -var ( - _ Logger = (*testing.T)(nil) - _ Logger = (*CLILogger)(nil) -) - -type CLILogger struct { - cmd *cobra.Command -} - -func (s CLILogger) Log(args ...interface{}) { - s.cmd.Println(args...) -} - -func (s CLILogger) Logf(format string, args ...interface{}) { - s.cmd.Printf(format, args...) -} - -func NewCLILogger(cmd *cobra.Command) CLILogger { - return CLILogger{cmd} -} - -// New creates a new Network for integration tests or in-process testnets run via the CLI -func New(l Logger, baseDir string, cfg Config) (*Network, error) { - // only one caller/test can create and use a network at a time - l.Log("acquiring test network lock") - lock.Lock() - - if !ethermint.IsValidChainID(cfg.ChainID) { - return nil, fmt.Errorf("invalid chain-id: %s", cfg.ChainID) - } - - network := &Network{ - Logger: l, - BaseDir: baseDir, - Validators: make([]*Validator, cfg.NumValidators), - Config: cfg, - } - - l.Logf("preparing test network with chain-id \"%s\"\n", cfg.ChainID) - - monikers := make([]string, cfg.NumValidators) - nodeIDs := make([]string, cfg.NumValidators) - valPubKeys := make([]cryptotypes.PubKey, cfg.NumValidators) - - var ( - genAccounts []authtypes.GenesisAccount - genBalances []banktypes.Balance - genFiles []string - ) - - buf := bufio.NewReader(os.Stdin) - - // generate private keys, node IDs, and initial transactions - for i := 0; i < cfg.NumValidators; i++ { - appCfg := config.DefaultConfig() - appCfg.Pruning = cfg.PruningStrategy - appCfg.MinGasPrices = cfg.MinGasPrices - appCfg.API.Enable = true - appCfg.API.Swagger = false - appCfg.Telemetry.Enabled = false - - ctx := server.NewDefaultContext() - tmCfg := ctx.Config - tmCfg.Consensus.TimeoutCommit = cfg.TimeoutCommit - - // Only allow the first validator to expose an RPC, API and gRPC - // server/client due to Tendermint in-process constraints. - apiAddr := "" - tmCfg.RPC.ListenAddress = "" - appCfg.GRPC.Enable = false - appCfg.GRPCWeb.Enable = false - apiListenAddr := "" - if i == 0 { - if cfg.APIAddress != "" { - apiListenAddr = cfg.APIAddress - } else { - var err error - apiListenAddr, _, err = server.FreeTCPAddr() - if err != nil { - return nil, err - } - } - - appCfg.API.Address = apiListenAddr - apiURL, err := url.Parse(apiListenAddr) - if err != nil { - return nil, err - } - apiAddr = fmt.Sprintf("http://%s:%s", apiURL.Hostname(), apiURL.Port()) - - if cfg.RPCAddress != "" { - tmCfg.RPC.ListenAddress = cfg.RPCAddress - } else { - rpcAddr, _, err := server.FreeTCPAddr() - if err != nil { - return nil, err - } - tmCfg.RPC.ListenAddress = rpcAddr - } - - if cfg.GRPCAddress != "" { - appCfg.GRPC.Address = cfg.GRPCAddress - } else { - _, grpcPort, err := server.FreeTCPAddr() - if err != nil { - return nil, err - } - appCfg.GRPC.Address = fmt.Sprintf("0.0.0.0:%s", grpcPort) - } - appCfg.GRPC.Enable = true - - _, grpcWebPort, err := server.FreeTCPAddr() - if err != nil { - return nil, err - } - appCfg.GRPCWeb.Address = fmt.Sprintf("0.0.0.0:%s", grpcWebPort) - appCfg.GRPCWeb.Enable = true - - if cfg.JSONRPCAddress != "" { - appCfg.JSONRPC.Address = cfg.JSONRPCAddress - } else { - _, jsonRPCPort, err := server.FreeTCPAddr() - if err != nil { - return nil, err - } - appCfg.JSONRPC.Address = fmt.Sprintf("0.0.0.0:%s", jsonRPCPort) - } - appCfg.JSONRPC.Enable = true - appCfg.JSONRPC.API = config.GetDefaultAPINamespaces() - } - - logger := log.NewNopLogger() - if cfg.EnableTMLogging { - logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) - logger, _ = tmflags.ParseLogLevel("info", logger, tmcfg.DefaultLogLevel) - } - - ctx.Logger = logger - - nodeDirName := fmt.Sprintf("node%d", i) - nodeDir := filepath.Join(network.BaseDir, nodeDirName, "evmosd") - clientDir := filepath.Join(network.BaseDir, nodeDirName, "evmoscli") - gentxsDir := filepath.Join(network.BaseDir, "gentxs") - - err := os.MkdirAll(filepath.Join(nodeDir, "config"), 0o755) - if err != nil { - return nil, err - } - - err = os.MkdirAll(clientDir, 0o755) - if err != nil { - return nil, err - } - - tmCfg.SetRoot(nodeDir) - tmCfg.Moniker = nodeDirName - monikers[i] = nodeDirName - - proxyAddr, _, err := server.FreeTCPAddr() - if err != nil { - return nil, err - } - tmCfg.ProxyApp = proxyAddr - - p2pAddr, _, err := server.FreeTCPAddr() - if err != nil { - return nil, err - } - tmCfg.P2P.ListenAddress = p2pAddr - tmCfg.P2P.AddrBookStrict = false - tmCfg.P2P.AllowDuplicateIP = true - - nodeID, pubKey, err := genutil.InitializeNodeValidatorFiles(tmCfg) - if err != nil { - return nil, err - } - nodeIDs[i] = nodeID - valPubKeys[i] = pubKey - - kb, err := keyring.New(sdk.KeyringServiceName(), keyring.BackendTest, clientDir, buf, cfg.KeyringOptions...) - if err != nil { - return nil, err - } - - keyringAlgos, _ := kb.SupportedAlgorithms() - algo, err := keyring.NewSigningAlgoFromString(cfg.SigningAlgo, keyringAlgos) - if err != nil { - return nil, err - } - - addr, secret, err := server.GenerateSaveCoinKey(kb, nodeDirName, true, algo) - if err != nil { - return nil, err - } - - // if PrintMnemonic is set to true, we print the first validator node's secret to the network's logger - // for debugging and manual testing - if cfg.PrintMnemonic && i == 0 { - printMnemonic(l, secret) - } - - info := map[string]string{"secret": secret} - infoBz, err := json.Marshal(info) - if err != nil { - return nil, err - } - - // save private key seed words - err = WriteFile(fmt.Sprintf("%v.json", "key_seed"), clientDir, infoBz) - if err != nil { - return nil, err - } - - balances := sdk.NewCoins( - sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), cfg.AccountTokens), - sdk.NewCoin(cfg.BondDenom, cfg.StakingTokens), - ) - - genFiles = append(genFiles, tmCfg.GenesisFile()) - genBalances = append(genBalances, banktypes.Balance{Address: addr.String(), Coins: balances.Sort()}) - genAccounts = append(genAccounts, ðermint.EthAccount{ - BaseAccount: authtypes.NewBaseAccount(addr, nil, 0, 0), - CodeHash: common.BytesToHash(evmtypes.EmptyCodeHash).Hex(), - }) - - commission, err := sdk.NewDecFromStr("0.5") - if err != nil { - return nil, err - } - - createValMsg, err := stakingtypes.NewMsgCreateValidator( - sdk.ValAddress(addr), - valPubKeys[i], - sdk.NewCoin(cfg.BondDenom, cfg.BondedTokens), - stakingtypes.NewDescription(nodeDirName, "", "", "", ""), - stakingtypes.NewCommissionRates(commission, sdk.OneDec(), sdk.OneDec()), - sdk.OneInt(), - ) - if err != nil { - return nil, err - } - - p2pURL, err := url.Parse(p2pAddr) - if err != nil { - return nil, err - } - - memo := fmt.Sprintf("%s@%s:%s", nodeIDs[i], p2pURL.Hostname(), p2pURL.Port()) - fee := sdk.NewCoins(sdk.NewCoin(cfg.BondDenom, sdk.NewInt(0))) - txBuilder := cfg.TxConfig.NewTxBuilder() - err = txBuilder.SetMsgs(createValMsg) - if err != nil { - return nil, err - } - txBuilder.SetFeeAmount(fee) // Arbitrary fee - txBuilder.SetGasLimit(1000000) // Need at least 100386 - txBuilder.SetMemo(memo) - - txFactory := tx.Factory{} - txFactory = txFactory. - WithChainID(cfg.ChainID). - WithMemo(memo). - WithKeybase(kb). - WithTxConfig(cfg.TxConfig) - - if err := tx.Sign(txFactory, nodeDirName, txBuilder, true); err != nil { - return nil, err - } - - txBz, err := cfg.TxConfig.TxJSONEncoder()(txBuilder.GetTx()) - if err != nil { - return nil, err - } - - if err := WriteFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBz); err != nil { - return nil, err - } - - customAppTemplate, _ := config.AppConfig(ethermint.AttoPhoton) - srvconfig.SetConfigTemplate(customAppTemplate) - srvconfig.WriteConfigFile(filepath.Join(nodeDir, "config/app.toml"), appCfg) - - clientCtx := client.Context{}. - WithKeyringDir(clientDir). - WithKeyring(kb). - WithHomeDir(tmCfg.RootDir). - WithChainID(cfg.ChainID). - WithInterfaceRegistry(cfg.InterfaceRegistry). - WithCodec(cfg.Codec). - WithLegacyAmino(cfg.LegacyAmino). - WithTxConfig(cfg.TxConfig). - WithAccountRetriever(cfg.AccountRetriever) - - network.Validators[i] = &Validator{ - AppConfig: appCfg, - ClientCtx: clientCtx, - Ctx: ctx, - Dir: filepath.Join(network.BaseDir, nodeDirName), - NodeID: nodeID, - PubKey: pubKey, - Moniker: nodeDirName, - RPCAddress: tmCfg.RPC.ListenAddress, - P2PAddress: tmCfg.P2P.ListenAddress, - APIAddress: apiAddr, - Address: addr, - ValAddress: sdk.ValAddress(addr), - } - } - - err := initGenFiles(cfg, genAccounts, genBalances, genFiles) - if err != nil { - return nil, err - } - err = collectGenFiles(cfg, network.Validators, network.BaseDir) - if err != nil { - return nil, err - } - - l.Log("starting test network...") - for _, v := range network.Validators { - err := startInProcess(cfg, v) - if err != nil { - return nil, err - } - } - - l.Log("started test network") - - // Ensure we cleanup incase any test was abruptly halted (e.g. SIGINT) as any - // defer in a test would not be called. - server.TrapSignal(network.Cleanup) - - return network, nil -} - -// LatestHeight returns the latest height of the network or an error if the -// query fails or no validators exist. -func (n *Network) LatestHeight() (int64, error) { - if len(n.Validators) == 0 { - return 0, errors.New("no validators available") - } - - status, err := n.Validators[0].RPCClient.Status(context.Background()) - if err != nil { - return 0, err - } - - return status.SyncInfo.LatestBlockHeight, nil -} - -// WaitForHeight performs a blocking check where it waits for a block to be -// committed after a given block. If that height is not reached within a timeout, -// an error is returned. Regardless, the latest height queried is returned. -func (n *Network) WaitForHeight(h int64) (int64, error) { - return n.WaitForHeightWithTimeout(h, 10*time.Second) -} - -// WaitForHeightWithTimeout is the same as WaitForHeight except the caller can -// provide a custom timeout. -func (n *Network) WaitForHeightWithTimeout(h int64, t time.Duration) (int64, error) { - ticker := time.NewTicker(time.Second) - timeout := time.After(t) - - if len(n.Validators) == 0 { - return 0, errors.New("no validators available") - } - - var latestHeight int64 - val := n.Validators[0] - - for { - select { - case <-timeout: - ticker.Stop() - return latestHeight, errors.New("timeout exceeded waiting for block") - case <-ticker.C: - status, err := val.RPCClient.Status(context.Background()) - if err == nil && status != nil { - latestHeight = status.SyncInfo.LatestBlockHeight - if latestHeight >= h { - return latestHeight, nil - } - } - } - } -} - -// WaitForNextBlock waits for the next block to be committed, returning an error -// upon failure. -func (n *Network) WaitForNextBlock() error { - lastBlock, err := n.LatestHeight() - if err != nil { - return err - } - - _, err = n.WaitForHeight(lastBlock + 1) - if err != nil { - return err - } - - return err -} - -// Cleanup removes the root testing (temporary) directory and stops both the -// Tendermint and API services. It allows other callers to create and start -// test networks. This method must be called when a test is finished, typically -// in a defer. -func (n *Network) Cleanup() { - defer func() { - lock.Unlock() - n.Logger.Log("released test network lock") - }() - - n.Logger.Log("cleaning up test network...") - - for _, v := range n.Validators { - if v.tmNode != nil && v.tmNode.IsRunning() { - _ = v.tmNode.Stop() - } - - if v.api != nil { - _ = v.api.Close() - } - - if v.grpc != nil { - v.grpc.Stop() - if v.grpcWeb != nil { - _ = v.grpcWeb.Close() - } - } - - if v.jsonrpc != nil { - shutdownCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second) - defer cancelFn() - - if err := v.jsonrpc.Shutdown(shutdownCtx); err != nil { - v.tmNode.Logger.Error("HTTP server shutdown produced a warning", "error", err.Error()) - } else { - v.tmNode.Logger.Info("HTTP server shut down, waiting 5 sec") - select { - case <-time.Tick(5 * time.Second): - case <-v.jsonrpcDone: - } - } - } - } - - if n.Config.CleanupDir { - _ = os.RemoveAll(n.BaseDir) - } - - n.Logger.Log("finished cleaning up test network") -} - -// printMnemonic prints a provided mnemonic seed phrase on a network logger -// for debugging and manual testing -func printMnemonic(l Logger, secret string) { - lines := []string{ - "THIS MNEMONIC IS FOR TESTING PURPOSES ONLY", - "DO NOT USE IN PRODUCTION", - "", - strings.Join(strings.Fields(secret)[0:8], " "), - strings.Join(strings.Fields(secret)[8:16], " "), - strings.Join(strings.Fields(secret)[16:24], " "), - } - - lineLengths := make([]int, len(lines)) - for i, line := range lines { - lineLengths[i] = len(line) - } - - maxLineLength := 0 - for _, lineLen := range lineLengths { - if lineLen > maxLineLength { - maxLineLength = lineLen - } - } - - l.Log("\n") - l.Log(strings.Repeat("+", maxLineLength+8)) - for _, line := range lines { - l.Logf("++ %s ++\n", centerText(line, maxLineLength)) - } - l.Log(strings.Repeat("+", maxLineLength+8)) - l.Log("\n") -} - -// centerText centers text across a fixed width, filling either side with whitespace buffers -func centerText(text string, width int) string { - textLen := len(text) - leftBuffer := strings.Repeat(" ", (width-textLen)/2) - rightBuffer := strings.Repeat(" ", (width-textLen)/2+(width-textLen)%2) - - return fmt.Sprintf("%s%s%s", leftBuffer, text, rightBuffer) -} diff --git a/testutil/network/network_test.go b/testutil/network/network_test.go index 7dbf183518..7cb92defc9 100644 --- a/testutil/network/network_test.go +++ b/testutil/network/network_test.go @@ -1,6 +1,3 @@ -//go:build norace -// +build norace - package network_test import ( @@ -9,7 +6,8 @@ import ( "github.com/stretchr/testify/suite" - "github.com/cosmos/cosmos-sdk/testutil/network" + "github.com/tharsis/ethermint/testutil/network" + evmosnetwork "github.com/tharsis/evmos/testutil/network" ) type IntegrationTestSuite struct { @@ -22,7 +20,7 @@ func (s *IntegrationTestSuite) SetupSuite() { s.T().Log("setting up integration test suite") var err error - s.network, err = network.New(s.T(), s.T().TempDir(), network.DefaultConfig()) + s.network, err = network.New(s.T(), s.T().TempDir(), evmosnetwork.DefaultConfig()) s.Require().NoError(err) _, err = s.network.WaitForHeight(1) @@ -37,6 +35,10 @@ func (s *IntegrationTestSuite) TearDownSuite() { func (s *IntegrationTestSuite) TestNetwork_Liveness() { h, err := s.network.WaitForHeightWithTimeout(10, time.Minute) s.Require().NoError(err, "expected to reach 10 blocks; got %d", h) + + latestHeight, err := s.network.LatestHeight() + s.Require().NoError(err, "latest height failed") + s.Require().GreaterOrEqual(latestHeight, h) } func TestIntegrationTestSuite(t *testing.T) { diff --git a/testutil/network/util.go b/testutil/network/util.go deleted file mode 100644 index 2441f3f21d..0000000000 --- a/testutil/network/util.go +++ /dev/null @@ -1,260 +0,0 @@ -package network - -import ( - "encoding/json" - "fmt" - "path/filepath" - "time" - - tmos "github.com/tendermint/tendermint/libs/os" - "github.com/tendermint/tendermint/node" - "github.com/tendermint/tendermint/p2p" - pvm "github.com/tendermint/tendermint/privval" - "github.com/tendermint/tendermint/proxy" - "github.com/tendermint/tendermint/rpc/client/local" - "github.com/tendermint/tendermint/types" - tmtime "github.com/tendermint/tendermint/types/time" - - "github.com/cosmos/cosmos-sdk/server/api" - servergrpc "github.com/cosmos/cosmos-sdk/server/grpc" - srvtypes "github.com/cosmos/cosmos-sdk/server/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types" - "github.com/cosmos/cosmos-sdk/x/genutil" - genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" - mintypes "github.com/cosmos/cosmos-sdk/x/mint/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - - "github.com/tharsis/ethermint/server" - evmtypes "github.com/tharsis/ethermint/x/evm/types" -) - -func startInProcess(cfg Config, val *Validator) error { - logger := val.Ctx.Logger - tmCfg := val.Ctx.Config - tmCfg.Instrumentation.Prometheus = false - - if err := val.AppConfig.ValidateBasic(); err != nil { - return err - } - - nodeKey, err := p2p.LoadOrGenNodeKey(tmCfg.NodeKeyFile()) - if err != nil { - return err - } - - app := cfg.AppConstructor(*val) - - genDocProvider := node.DefaultGenesisDocProviderFunc(tmCfg) - tmNode, err := node.NewNode( - tmCfg, - pvm.LoadOrGenFilePV(tmCfg.PrivValidatorKeyFile(), tmCfg.PrivValidatorStateFile()), - nodeKey, - proxy.NewLocalClientCreator(app), - genDocProvider, - node.DefaultDBProvider, - node.DefaultMetricsProvider(tmCfg.Instrumentation), - logger.With("module", val.Moniker), - ) - if err != nil { - return err - } - - if err := tmNode.Start(); err != nil { - return err - } - - val.tmNode = tmNode - - if val.RPCAddress != "" { - val.RPCClient = local.New(tmNode) - } - - // We'll need a RPC client if the validator exposes a gRPC or REST endpoint. - if val.APIAddress != "" || val.AppConfig.GRPC.Enable { - val.ClientCtx = val.ClientCtx. - WithClient(val.RPCClient) - - // Add the tx service in the gRPC router. - app.RegisterTxService(val.ClientCtx) - - // Add the tendermint queries service in the gRPC router. - app.RegisterTendermintService(val.ClientCtx) - } - - if val.AppConfig.API.Enable && val.APIAddress != "" { - apiSrv := api.New(val.ClientCtx, logger.With("module", "api-server")) - app.RegisterAPIRoutes(apiSrv, val.AppConfig.API) - - errCh := make(chan error) - - go func() { - if err := apiSrv.Start(val.AppConfig.Config); err != nil { - errCh <- err - } - }() - - select { - case err := <-errCh: - return err - case <-time.After(srvtypes.ServerStartTime): // assume server started successfully - } - - val.api = apiSrv - } - - if val.AppConfig.GRPC.Enable { - grpcSrv, err := servergrpc.StartGRPCServer(val.ClientCtx, app, val.AppConfig.GRPC.Address) - if err != nil { - return err - } - - val.grpc = grpcSrv - - if val.AppConfig.GRPCWeb.Enable { - val.grpcWeb, err = servergrpc.StartGRPCWeb(grpcSrv, val.AppConfig.Config) - if err != nil { - return err - } - } - } - - if val.AppConfig.JSONRPC.Enable && val.JSONRPCAddress != "" { - if val.Ctx == nil || val.Ctx.Viper == nil { - return fmt.Errorf("validator %s context is nil", val.Moniker) - } - - fmt.Println(&val.Ctx, &val.Ctx.Viper) - - tmEndpoint := "/websocket" - val.jsonrpc, val.jsonrpcDone, err = server.StartJSONRPC(val.Ctx, val.ClientCtx, val.JSONRPCAddress, tmEndpoint, *val.AppConfig) - if err != nil { - return err - } - } - - return nil -} - -func collectGenFiles(cfg Config, vals []*Validator, outputDir string) error { - genTime := tmtime.Now() - - for i := 0; i < cfg.NumValidators; i++ { - tmCfg := vals[i].Ctx.Config - - nodeDir := filepath.Join(outputDir, vals[i].Moniker, "evmosd") - gentxsDir := filepath.Join(outputDir, "gentxs") - - tmCfg.Moniker = vals[i].Moniker - tmCfg.SetRoot(nodeDir) - - initCfg := genutiltypes.NewInitConfig(cfg.ChainID, gentxsDir, vals[i].NodeID, vals[i].PubKey) - - genFile := tmCfg.GenesisFile() - genDoc, err := types.GenesisDocFromFile(genFile) - if err != nil { - return err - } - - appState, err := genutil.GenAppStateFromConfig(cfg.Codec, cfg.TxConfig, - tmCfg, initCfg, *genDoc, banktypes.GenesisBalancesIterator{}) - if err != nil { - return err - } - - // overwrite each validator's genesis file to have a canonical genesis time - if err := genutil.ExportGenesisFileWithTime(genFile, cfg.ChainID, nil, appState, genTime); err != nil { - return err - } - } - - return nil -} - -func initGenFiles(cfg Config, genAccounts []authtypes.GenesisAccount, genBalances []banktypes.Balance, genFiles []string) error { - // set the accounts in the genesis state - var authGenState authtypes.GenesisState - cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[authtypes.ModuleName], &authGenState) - - accounts, err := authtypes.PackAccounts(genAccounts) - if err != nil { - return err - } - - authGenState.Accounts = append(authGenState.Accounts, accounts...) - cfg.GenesisState[authtypes.ModuleName] = cfg.Codec.MustMarshalJSON(&authGenState) - - // set the balances in the genesis state - var bankGenState banktypes.GenesisState - bankGenState.Balances = genBalances - cfg.GenesisState[banktypes.ModuleName] = cfg.Codec.MustMarshalJSON(&bankGenState) - - var stakingGenState stakingtypes.GenesisState - cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[stakingtypes.ModuleName], &stakingGenState) - - stakingGenState.Params.BondDenom = cfg.BondDenom - cfg.GenesisState[stakingtypes.ModuleName] = cfg.Codec.MustMarshalJSON(&stakingGenState) - - var govGenState govtypes.GenesisState - cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[govtypes.ModuleName], &govGenState) - - govGenState.DepositParams.MinDeposit[0].Denom = cfg.BondDenom - cfg.GenesisState[govtypes.ModuleName] = cfg.Codec.MustMarshalJSON(&govGenState) - - var mintGenState mintypes.GenesisState - cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[mintypes.ModuleName], &mintGenState) - - mintGenState.Params.MintDenom = cfg.BondDenom - cfg.GenesisState[mintypes.ModuleName] = cfg.Codec.MustMarshalJSON(&mintGenState) - - var crisisGenState crisistypes.GenesisState - cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[crisistypes.ModuleName], &crisisGenState) - - crisisGenState.ConstantFee.Denom = cfg.BondDenom - cfg.GenesisState[crisistypes.ModuleName] = cfg.Codec.MustMarshalJSON(&crisisGenState) - - var evmGenState evmtypes.GenesisState - cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[evmtypes.ModuleName], &evmGenState) - - evmGenState.Params.EvmDenom = cfg.BondDenom - cfg.GenesisState[evmtypes.ModuleName] = cfg.Codec.MustMarshalJSON(&evmGenState) - - appGenStateJSON, err := json.MarshalIndent(cfg.GenesisState, "", " ") - if err != nil { - return err - } - - genDoc := types.GenesisDoc{ - ChainID: cfg.ChainID, - AppState: appGenStateJSON, - Validators: nil, - } - - // generate empty genesis files for each validator and save - for i := 0; i < cfg.NumValidators; i++ { - if err := genDoc.SaveAs(genFiles[i]); err != nil { - return err - } - } - - return nil -} - -func WriteFile(name string, dir string, contents []byte) error { - writePath := filepath.Join(dir) - file := filepath.Join(writePath, name) - - err := tmos.EnsureDir(writePath, 0o755) - if err != nil { - return err - } - - err = tmos.WriteFile(file, contents, 0o644) - if err != nil { - return err - } - - return nil -} From d50ae16bdeb44b854f4e214ecfa0bdc3deafa958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Kunze=20K=C3=BCllmer?= Date: Mon, 22 Nov 2021 14:45:31 +0100 Subject: [PATCH 2/5] tests wip --- x/intrarelayer/testing/integration_test.go | 89 ++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 x/intrarelayer/testing/integration_test.go diff --git a/x/intrarelayer/testing/integration_test.go b/x/intrarelayer/testing/integration_test.go new file mode 100644 index 0000000000..7525d1d6a9 --- /dev/null +++ b/x/intrarelayer/testing/integration_test.go @@ -0,0 +1,89 @@ +package testing + +import ( + "context" + "fmt" + "testing" + + // . "github.com/onsi/ginkgo" + // . "github.com/onsi/gomega" + + "github.com/stretchr/testify/suite" + "google.golang.org/grpc" + + "github.com/ethereum/go-ethereum/ethclient" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/tharsis/ethermint/server/config" + "github.com/tharsis/ethermint/tests" + "github.com/tharsis/ethermint/testutil/network" + evmosnetwork "github.com/tharsis/evmos/testutil/network" + "github.com/tharsis/evmos/x/intrarelayer/types" +) + +// var _ = Describe("E2e", func() { +// }) + +// func TestJsonRpc(t *testing.T) { +// RegisterFailHandler(Fail) +// RunSpecs(t, "JSON-RPC Suite") +// } + +// TODO: migrate to Ginkgo BDD +type IntegrationTestSuite struct { + suite.Suite + + ctx context.Context + cfg network.Config + network *network.Network + grpcQueryClient types.QueryClient + grpcTxClient types.MsgClient +} + +func (s *IntegrationTestSuite) SetupSuite() { + s.T().Log("setting up integration test suite") + + var err error + cfg := evmosnetwork.DefaultConfig() + cfg.JSONRPCAddress = config.DefaultJSONRPCAddress + cfg.NumValidators = 1 + + s.ctx = context.Background() + s.cfg = cfg + s.network, err = network.New(s.T(), s.T().TempDir(), cfg) + s.Require().NoError(err) + s.Require().NotNil(s.network) + + _, err = s.network.WaitForHeight(1) + s.Require().NoError(err) + + if s.network.Validators[0].JSONRPCClient == nil { + address := fmt.Sprintf("http://%s", s.network.Validators[0].AppConfig.JSONRPC.Address) + s.network.Validators[0].JSONRPCClient, err = ethclient.Dial(address) + s.Require().NoError(err) + } + + // Create a connection to the gRPC server. + grpcConn, err := grpc.Dial( + s.network.Validators[0].AppConfig.GRPC.Address, // gRPC server address. + grpc.WithInsecure(), // The Cosmos SDK doesn't support any transport security mechanism. + ) + s.Require().NoError(err) + + s.grpcQueryClient = types.NewQueryClient(grpcConn) + s.grpcTxClient = types.NewMsgClient(grpcConn) +} + +func (s *IntegrationTestSuite) TestConvertCoinNativeCoin() { + from := s.network.Validators[0].ClientCtx.FromAddress + recipient := tests.GenerateAddress() + coin := sdk.NewInt64Coin(s.network.Config.BondDenom, 1) + + res, err := s.grpcTxClient.ConvertCoin(s.ctx, types.NewMsgConvertCoin(coin, recipient, from)) + s.Require().NoError(err, "pair not registered") + s.Require().Nil(res) +} + +func TestIntegrationTestSuite(t *testing.T) { + suite.Run(t, new(IntegrationTestSuite)) +} From 279f20e7b55e7e24450118ae2f7f06531bcc95fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Kunze=20K=C3=BCllmer?= Date: Mon, 22 Nov 2021 15:42:22 +0100 Subject: [PATCH 3/5] tests --- testutil/network/network_test.go | 18 ++++++++++++++++-- x/intrarelayer/testing/integration_test.go | 20 +++++++++++--------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/testutil/network/network_test.go b/testutil/network/network_test.go index 7cb92defc9..05c050c44c 100644 --- a/testutil/network/network_test.go +++ b/testutil/network/network_test.go @@ -1,11 +1,14 @@ package network_test import ( + "fmt" "testing" "time" "github.com/stretchr/testify/suite" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/tharsis/ethermint/server/config" "github.com/tharsis/ethermint/testutil/network" evmosnetwork "github.com/tharsis/evmos/testutil/network" ) @@ -20,11 +23,22 @@ func (s *IntegrationTestSuite) SetupSuite() { s.T().Log("setting up integration test suite") var err error - s.network, err = network.New(s.T(), s.T().TempDir(), evmosnetwork.DefaultConfig()) + cfg := evmosnetwork.DefaultConfig() + cfg.JSONRPCAddress = config.DefaultJSONRPCAddress + cfg.NumValidators = 1 + + s.network, err = network.New(s.T(), s.T().TempDir(), cfg) s.Require().NoError(err) + s.Require().NotNil(s.network) - _, err = s.network.WaitForHeight(1) + _, err = s.network.WaitForHeight(2) s.Require().NoError(err) + + if s.network.Validators[0].JSONRPCClient == nil { + address := fmt.Sprintf("http://%s", s.network.Validators[0].AppConfig.JSONRPC.Address) + s.network.Validators[0].JSONRPCClient, err = ethclient.Dial(address) + s.Require().NoError(err) + } } func (s *IntegrationTestSuite) TearDownSuite() { diff --git a/x/intrarelayer/testing/integration_test.go b/x/intrarelayer/testing/integration_test.go index 7525d1d6a9..a5e24902b1 100644 --- a/x/intrarelayer/testing/integration_test.go +++ b/x/intrarelayer/testing/integration_test.go @@ -13,9 +13,7 @@ import ( "github.com/ethereum/go-ethereum/ethclient" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/tharsis/ethermint/server/config" - "github.com/tharsis/ethermint/tests" "github.com/tharsis/ethermint/testutil/network" evmosnetwork "github.com/tharsis/evmos/testutil/network" "github.com/tharsis/evmos/x/intrarelayer/types" @@ -71,17 +69,21 @@ func (s *IntegrationTestSuite) SetupSuite() { s.Require().NoError(err) s.grpcQueryClient = types.NewQueryClient(grpcConn) + + // FIXME: "unknown service evmos.intrarelayer.v1.Msg" s.grpcTxClient = types.NewMsgClient(grpcConn) } -func (s *IntegrationTestSuite) TestConvertCoinNativeCoin() { - from := s.network.Validators[0].ClientCtx.FromAddress - recipient := tests.GenerateAddress() - coin := sdk.NewInt64Coin(s.network.Config.BondDenom, 1) +func (s *IntegrationTestSuite) TestLiveness() { + // test the gRPC query client to check if everything's ok + resParams, err := s.grpcQueryClient.Params(s.ctx, &types.QueryParamsRequest{}) + s.Require().NoError(err) + s.Require().NotNil(resParams) - res, err := s.grpcTxClient.ConvertCoin(s.ctx, types.NewMsgConvertCoin(coin, recipient, from)) - s.Require().NoError(err, "pair not registered") - s.Require().Nil(res) + // FIXME: enable + // res, err := s.grpcTxClient.ConvertCoin(s.ctx, &types.MsgConvertCoin{}) + // s.Require().NoError(err) + // s.Require().NotNil(res) } func TestIntegrationTestSuite(t *testing.T) { From 7f09a6c75470cd0fa1a6d798afddfac4fe0884d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Kunze=20K=C3=BCllmer?= Date: Mon, 22 Nov 2021 15:45:02 +0100 Subject: [PATCH 4/5] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 399234bd00..f80d240fcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (intrarelayer) [\#99](https://github.com/tharsis/evmos/pull/99) Rename `enable_e_v_m_hook` json parameter to `enable_evm_hook`. +## Improvements + +* (cmd) [\#105](https://github.com/tharsis/evmos/pull/105) Improve testnet command to include JSON-RPC client. + ## Bug Fixes * (intrarelayer) [\#102](https://github.com/tharsis/evmos/pull/102) Add `convert-erc20` cmd From 4cc807feb321f516539f0c803dd39235e6593427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Kunze=20K=C3=BCllmer?= Date: Mon, 22 Nov 2021 16:35:59 +0100 Subject: [PATCH 5/5] norace --- testutil/network/network_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testutil/network/network_test.go b/testutil/network/network_test.go index 05c050c44c..f5a66d61ad 100644 --- a/testutil/network/network_test.go +++ b/testutil/network/network_test.go @@ -1,3 +1,6 @@ +//go:build norace +// +build norace + package network_test import (