forked from cerc-io/laconicd-deprecated
tests: integration tests with JSON-RPC client (#704)
* tests: integration tests with JSON-RPC client * fix package * tests: networking configuration fixed (#706) * update testnet hdPath, fixes #688 * lint * header * e2e wip * fix getBlock response * enable personal API * changelog Co-authored-by: Guillermo Paoletti <guillermo.paoletti@gmail.com> Co-authored-by: Freddy Caceres <freddy.caceres@crypto.com>
This commit is contained in:
co-authored by
Guillermo Paoletti
Freddy Caceres
parent
481e3b82ce
commit
32c905ab87
+318
-144
@@ -6,21 +6,18 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
jsonrpc "github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/spf13/cobra"
|
||||
tmcfg "github.com/tendermint/tendermint/config"
|
||||
tmflags "github.com/tendermint/tendermint/libs/cli/flags"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
@@ -28,6 +25,7 @@ import (
|
||||
"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"
|
||||
@@ -38,7 +36,7 @@ import (
|
||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
"github.com/cosmos/cosmos-sdk/server/api"
|
||||
"github.com/cosmos/cosmos-sdk/server/config"
|
||||
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"
|
||||
@@ -49,11 +47,13 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/x/genutil"
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
|
||||
"github.com/tharsis/ethermint/app"
|
||||
"github.com/tharsis/ethermint/crypto/hd"
|
||||
"github.com/tharsis/ethermint/encoding"
|
||||
srvconfig "github.com/tharsis/ethermint/server/config"
|
||||
"github.com/tharsis/ethermint/server/config"
|
||||
ethermint "github.com/tharsis/ethermint/types"
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
|
||||
"github.com/tharsis/ethermint/app"
|
||||
)
|
||||
|
||||
// package-wide network lock to only allow one test network at a time
|
||||
@@ -72,7 +72,6 @@ func NewAppConstructor(encodingCfg params.EncodingConfig) AppConstructor {
|
||||
simapp.EmptyAppOptions{},
|
||||
baseapp.SetPruning(storetypes.NewPruningOptionsFromString(val.AppConfig.Pruning)),
|
||||
baseapp.SetMinGasPrices(val.AppConfig.MinGasPrices),
|
||||
baseapp.SetTrace(true),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -80,27 +79,31 @@ 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 {
|
||||
KeyringOptions []keyring.Option // keyring configuration options
|
||||
Codec codec.Codec
|
||||
LegacyAmino *codec.LegacyAmino // TODO: Remove!
|
||||
InterfaceRegistry codectypes.InterfaceRegistry
|
||||
|
||||
TxConfig client.TxConfig
|
||||
AccountRetriever client.AccountRetriever
|
||||
AppConstructor AppConstructor // the ABCI application constructor
|
||||
GenesisState map[string]json.RawMessage // 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
|
||||
EnableLogging bool // enable Tendermint logging to STDOUT
|
||||
CleanupDir bool // remove base temporary directory during cleanup
|
||||
SigningAlgo string // signing algorithm for keys
|
||||
KeyringOptions []keyring.Option
|
||||
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
|
||||
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
|
||||
NumValidators int // the total number of validators to create and bond
|
||||
ChainID string // the network chain-id
|
||||
BondDenom string // the staking bond denomination
|
||||
MinGasPrices string // the minimum gas prices each validator will accept
|
||||
PruningStrategy string // the pruning strategy each validator will have
|
||||
SigningAlgo string // signing algorithm for keys
|
||||
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)
|
||||
EnableTMLogging bool // enable Tendermint logging to STDOUT
|
||||
CleanupDir bool // remove base temporary directory during cleanup
|
||||
PrintMnemonic bool // print the mnemonic of first validator as log output for testing
|
||||
}
|
||||
|
||||
// DefaultConfig returns a sane default configuration suitable for nearly all
|
||||
@@ -115,9 +118,9 @@ func DefaultConfig() Config {
|
||||
InterfaceRegistry: encCfg.InterfaceRegistry,
|
||||
AccountRetriever: authtypes.AccountRetriever{},
|
||||
AppConstructor: NewAppConstructor(encCfg),
|
||||
GenesisState: app.NewDefaultGenesisState(),
|
||||
GenesisState: app.ModuleBasics.DefaultGenesis(encCfg.Marshaler),
|
||||
TimeoutCommit: 2 * time.Second,
|
||||
ChainID: "ethermint_9000-" + fmt.Sprintf("%d", tmrand.NewRand().Int63n(1000)),
|
||||
ChainID: fmt.Sprintf("ethermint_%d-1", tmrand.Int63n(9999999999999)+1),
|
||||
NumValidators: 4,
|
||||
BondDenom: ethermint.AttoPhoton,
|
||||
MinGasPrices: fmt.Sprintf("0.000006%s", ethermint.AttoPhoton),
|
||||
@@ -128,6 +131,7 @@ func DefaultConfig() Config {
|
||||
CleanupDir: true,
|
||||
SigningAlgo: string(hd.EthSecp256k1Type),
|
||||
KeyringOptions: []keyring.Option{hd.EthSecp256k1Option()},
|
||||
PrintMnemonic: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +147,7 @@ type (
|
||||
// to create networks. In addition, only the first validator will have a valid
|
||||
// RPC and API server/client.
|
||||
Network struct {
|
||||
T *testing.T
|
||||
Logger Logger
|
||||
BaseDir string
|
||||
Validators []*Validator
|
||||
|
||||
@@ -154,54 +158,80 @@ type (
|
||||
// a client can make RPC and API calls and interact with any client command
|
||||
// or handler.
|
||||
Validator struct {
|
||||
AppConfig *srvconfig.Config
|
||||
ClientCtx client.Context
|
||||
Ctx *server.Context
|
||||
Dir string
|
||||
NodeID string
|
||||
PubKey cryptotypes.PubKey
|
||||
Moniker string
|
||||
APIAddress string
|
||||
RPCAddress string
|
||||
P2PAddress string
|
||||
JSONRPCAddress string
|
||||
EthereumAddress common.Address
|
||||
Address sdk.AccAddress
|
||||
ValAddress sdk.ValAddress
|
||||
RPCClient tmclient.Client
|
||||
JSONRPCClient *ethclient.Client
|
||||
AppConfig *config.Config
|
||||
ClientCtx client.Context
|
||||
Ctx *server.Context
|
||||
Dir string
|
||||
NodeID string
|
||||
PubKey cryptotypes.PubKey
|
||||
Moniker string
|
||||
APIAddress string
|
||||
RPCAddress string
|
||||
P2PAddress string
|
||||
Address sdk.AccAddress
|
||||
ValAddress sdk.ValAddress
|
||||
RPCClient tmclient.Client
|
||||
JSONRPCClient *ethclient.Client
|
||||
|
||||
tmNode *node.Node
|
||||
api *api.Server
|
||||
grpc *grpc.Server
|
||||
jsonRPC *jsonrpc.Server
|
||||
tmNode *node.Node
|
||||
api *api.Server
|
||||
grpc *grpc.Server
|
||||
grpcWeb *http.Server
|
||||
jsonrpc *http.Server
|
||||
jsonrpcDone chan struct{}
|
||||
}
|
||||
)
|
||||
|
||||
// New creates a new Network for integration tests.
|
||||
func New(t *testing.T, cfg Config) *Network {
|
||||
// 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
|
||||
t.Log("acquiring test network lock")
|
||||
l.Log("acquiring test network lock")
|
||||
lock.Lock()
|
||||
|
||||
baseDir, err := ioutil.TempDir(t.TempDir(), cfg.ChainID)
|
||||
require.NoError(t, err)
|
||||
t.Logf("created temporary directory: %s", baseDir)
|
||||
if !ethermint.IsValidChainID(cfg.ChainID) {
|
||||
return nil, fmt.Errorf("invalid chain-id: %s", cfg.ChainID)
|
||||
}
|
||||
|
||||
network := &Network{
|
||||
T: t,
|
||||
Logger: l,
|
||||
BaseDir: baseDir,
|
||||
Validators: make([]*Validator, cfg.NumValidators),
|
||||
Config: cfg,
|
||||
}
|
||||
|
||||
t.Log("preparing test network...")
|
||||
l.Logf("preparing test network with chain-id \"%s\"\n", cfg.ChainID)
|
||||
|
||||
var (
|
||||
monikers = make([]string, cfg.NumValidators)
|
||||
nodeIDs = make([]string, cfg.NumValidators)
|
||||
valPubKeys = make([]cryptotypes.PubKey, cfg.NumValidators)
|
||||
)
|
||||
monikers := make([]string, cfg.NumValidators)
|
||||
nodeIDs := make([]string, cfg.NumValidators)
|
||||
valPubKeys := make([]cryptotypes.PubKey, cfg.NumValidators)
|
||||
|
||||
var (
|
||||
genAccounts []authtypes.GenesisAccount
|
||||
@@ -213,57 +243,86 @@ func New(t *testing.T, cfg Config) *Network {
|
||||
|
||||
// generate private keys, node IDs, and initial transactions
|
||||
for i := 0; i < cfg.NumValidators; i++ {
|
||||
appCfg := srvconfig.DefaultConfig()
|
||||
appCfg := config.DefaultConfig()
|
||||
appCfg.Pruning = cfg.PruningStrategy
|
||||
appCfg.MinGasPrices = cfg.MinGasPrices
|
||||
appCfg.API.Enable = true
|
||||
appCfg.API.Swagger = false
|
||||
appCfg.Telemetry.Enabled = false
|
||||
appCfg.Telemetry.GlobalLabels = [][]string{{"chain_id", cfg.ChainID}}
|
||||
|
||||
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 := ""
|
||||
jsonRPCAddr := ""
|
||||
tmCfg.RPC.ListenAddress = ""
|
||||
appCfg.GRPC.Enable = false
|
||||
appCfg.JSONRPC.Enable = false
|
||||
|
||||
appCfg.GRPCWeb.Enable = false
|
||||
apiListenAddr := ""
|
||||
if i == 0 {
|
||||
apiListenAddr, _, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
appCfg.API.Address = apiListenAddr
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apiAddr = fmt.Sprintf("http://%s:%s", apiURL.Hostname(), apiURL.Port())
|
||||
|
||||
jsonRPCListenAddr, _, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
t.Log(jsonRPCListenAddr)
|
||||
appCfg.JSONRPC.Address = jsonRPCListenAddr
|
||||
appCfg.JSONRPC.Enable = true
|
||||
if cfg.RPCAddress != "" {
|
||||
tmCfg.RPC.ListenAddress = cfg.RPCAddress
|
||||
} else {
|
||||
rpcAddr, _, err := server.FreeTCPAddr()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmCfg.RPC.ListenAddress = rpcAddr
|
||||
}
|
||||
|
||||
jsonRPCAPIURL, err := url.Parse(jsonRPCListenAddr)
|
||||
require.NoError(t, err)
|
||||
jsonRPCAddr = fmt.Sprintf("http://%s:%s", jsonRPCAPIURL.Hostname(), jsonRPCAPIURL.Port())
|
||||
|
||||
rpcAddr, _, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
tmCfg.RPC.ListenAddress = rpcAddr
|
||||
|
||||
_, grpcPort, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
appCfg.GRPC.Address = fmt.Sprintf("0.0.0.0:%s", grpcPort)
|
||||
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.EnableLogging {
|
||||
if cfg.EnableTMLogging {
|
||||
logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
logger, _ = tmflags.ParseLogLevel("info", logger, tmcfg.DefaultLogLevel)
|
||||
}
|
||||
@@ -271,46 +330,78 @@ func New(t *testing.T, cfg Config) *Network {
|
||||
ctx.Logger = logger
|
||||
|
||||
nodeDirName := fmt.Sprintf("node%d", i)
|
||||
nodeDir := filepath.Join(network.BaseDir, nodeDirName, "ethermintd")
|
||||
nodeDir := filepath.Join(network.BaseDir, nodeDirName, "evmosd")
|
||||
clientDir := filepath.Join(network.BaseDir, nodeDirName, "evmoscli")
|
||||
gentxsDir := filepath.Join(network.BaseDir, "gentxs")
|
||||
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(nodeDir, "config"), 0o755))
|
||||
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()
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmCfg.ProxyApp = proxyAddr
|
||||
|
||||
p2pAddr, _, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmCfg.P2P.ListenAddress = p2pAddr
|
||||
tmCfg.P2P.AddrBookStrict = false
|
||||
tmCfg.P2P.AllowDuplicateIP = true
|
||||
|
||||
nodeID, pubKey, err := genutil.InitializeNodeValidatorFiles(tmCfg)
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodeIDs[i] = nodeID
|
||||
valPubKeys[i] = pubKey
|
||||
|
||||
kb, err := keyring.New(sdk.KeyringServiceName(), keyring.BackendTest, nodeDir, buf, cfg.KeyringOptions...)
|
||||
require.NoError(t, err)
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr, secret, err := server.GenerateSaveCoinKey(kb, nodeDirName, true, algo)
|
||||
require.NoError(t, err)
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// save private key seed words
|
||||
require.NoError(t, writeFile(fmt.Sprintf("%v.json", "key_seed"), nodeDir, infoBz))
|
||||
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),
|
||||
@@ -319,10 +410,15 @@ func New(t *testing.T, cfg Config) *Network {
|
||||
|
||||
genFiles = append(genFiles, tmCfg.GenesisFile())
|
||||
genBalances = append(genBalances, banktypes.Balance{Address: addr.String(), Coins: balances.Sort()})
|
||||
genAccounts = append(genAccounts, authtypes.NewBaseAccount(addr, nil, 0, 0))
|
||||
genAccounts = append(genAccounts, ðermint.EthAccount{
|
||||
BaseAccount: authtypes.NewBaseAccount(addr, nil, 0, 0),
|
||||
CodeHash: common.BytesToHash(evmtypes.EmptyCodeHash).Hex(),
|
||||
})
|
||||
|
||||
commission, err := sdk.NewDecFromStr("0.5")
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createValMsg, err := stakingtypes.NewMsgCreateValidator(
|
||||
sdk.ValAddress(addr),
|
||||
@@ -332,15 +428,22 @@ func New(t *testing.T, cfg Config) *Network {
|
||||
stakingtypes.NewCommissionRates(commission, sdk.OneDec(), sdk.OneDec()),
|
||||
sdk.OneInt(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p2pURL, err := url.Parse(p2pAddr)
|
||||
require.NoError(t, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
memo := fmt.Sprintf("%s@%s:%s", nodeIDs[i], p2pURL.Hostname(), p2pURL.Port())
|
||||
fee := sdk.NewCoins(sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), sdk.NewInt(0)))
|
||||
fee := sdk.NewCoins(sdk.NewCoin(cfg.BondDenom, sdk.NewInt(0)))
|
||||
txBuilder := cfg.TxConfig.NewTxBuilder()
|
||||
require.NoError(t, txBuilder.SetMsgs(createValMsg))
|
||||
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)
|
||||
@@ -352,24 +455,32 @@ func New(t *testing.T, cfg Config) *Network {
|
||||
WithKeybase(kb).
|
||||
WithTxConfig(cfg.TxConfig)
|
||||
|
||||
err = tx.Sign(txFactory, nodeDirName, txBuilder, true)
|
||||
require.NoError(t, err)
|
||||
if err := tx.Sign(txFactory, nodeDirName, txBuilder, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txBz, err := cfg.TxConfig.TxJSONEncoder()(txBuilder.GetTx())
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBz))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config.WriteConfigFile(filepath.Join(nodeDir, "config/app.toml"), appCfg)
|
||||
ctx.Viper.AddConfigPath(fmt.Sprintf("%s/config", nodeDir))
|
||||
ctx.Viper.SetConfigName("app")
|
||||
ctx.Viper.SetConfigType("toml")
|
||||
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)
|
||||
|
||||
ctx.Viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
|
||||
ctx.Viper.SetConfigFile(filepath.Join(nodeDir, "config/app.toml"))
|
||||
err = ctx.Viper.ReadInConfig()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientCtx := client.Context{}.
|
||||
WithKeyringDir(nodeDir).
|
||||
WithKeyringDir(clientDir).
|
||||
WithKeyring(kb).
|
||||
WithHomeDir(tmCfg.RootDir).
|
||||
WithChainID(cfg.ChainID).
|
||||
@@ -380,38 +491,45 @@ func New(t *testing.T, cfg Config) *Network {
|
||||
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,
|
||||
JSONRPCAddress: jsonRPCAddr,
|
||||
EthereumAddress: common.BytesToAddress(addr),
|
||||
Address: addr,
|
||||
ValAddress: sdk.ValAddress(addr),
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
require.NoError(t, initGenFiles(cfg, genAccounts, genBalances, genFiles))
|
||||
require.NoError(t, collectGenFiles(cfg, network.Validators, network.BaseDir))
|
||||
|
||||
t.Log("starting test network...")
|
||||
for _, v := range network.Validators {
|
||||
require.NoError(t, startInProcess(cfg, v))
|
||||
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
|
||||
}
|
||||
|
||||
t.Log("started test network")
|
||||
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
|
||||
return network, nil
|
||||
}
|
||||
|
||||
// LatestHeight returns the latest height of the network or an error if the
|
||||
@@ -489,10 +607,10 @@ func (n *Network) WaitForNextBlock() error {
|
||||
func (n *Network) Cleanup() {
|
||||
defer func() {
|
||||
lock.Unlock()
|
||||
n.T.Log("released test network lock")
|
||||
n.Logger.Log("released test network lock")
|
||||
}()
|
||||
|
||||
n.T.Log("cleaning up test network...")
|
||||
n.Logger.Log("cleaning up test network...")
|
||||
|
||||
for _, v := range n.Validators {
|
||||
if v.tmNode != nil && v.tmNode.IsRunning() {
|
||||
@@ -505,10 +623,24 @@ func (n *Network) Cleanup() {
|
||||
|
||||
if v.grpc != nil {
|
||||
v.grpc.Stop()
|
||||
if v.grpcWeb != nil {
|
||||
_ = v.grpcWeb.Close()
|
||||
}
|
||||
}
|
||||
|
||||
if v.jsonRPC != nil {
|
||||
v.jsonRPC.Stop()
|
||||
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:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,5 +648,47 @@ func (n *Network) Cleanup() {
|
||||
_ = os.RemoveAll(n.BaseDir)
|
||||
}
|
||||
|
||||
n.T.Log("finished cleaning up test network")
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -21,10 +21,11 @@ type IntegrationTestSuite struct {
|
||||
func (s *IntegrationTestSuite) SetupSuite() {
|
||||
s.T().Log("setting up integration test suite")
|
||||
|
||||
s.network = network.New(s.T(), network.DefaultConfig())
|
||||
s.Require().NotNil(s.network)
|
||||
var err error
|
||||
s.network, err = network.New(s.T(), s.T().TempDir(), network.DefaultConfig())
|
||||
s.Require().NoError(err)
|
||||
|
||||
_, err := s.network.WaitForHeight(1)
|
||||
_, err = s.network.WaitForHeight(1)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
|
||||
+69
-83
@@ -3,16 +3,10 @@ package network
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
jsonrpc "github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/improbable-eng/grpc-web/go/grpcweb"
|
||||
"github.com/rs/cors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
tmos "github.com/tendermint/tendermint/libs/os"
|
||||
"github.com/tendermint/tendermint/node"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
@@ -24,15 +18,18 @@ import (
|
||||
|
||||
"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/rpc"
|
||||
ethsrv "github.com/tharsis/ethermint/server"
|
||||
ethermint "github.com/tharsis/ethermint/types"
|
||||
"github.com/tharsis/ethermint/server"
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
)
|
||||
|
||||
func startInProcess(cfg Config, val *Validator) error {
|
||||
@@ -40,9 +37,13 @@ func startInProcess(cfg Config, val *Validator) error {
|
||||
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 fmt.Errorf("failed to load or generate node key: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
app := cfg.AppConstructor(*val)
|
||||
@@ -59,11 +60,11 @@ func startInProcess(cfg Config, val *Validator) error {
|
||||
logger.With("module", val.Moniker),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create node: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tmNode.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start node: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
val.tmNode = tmNode
|
||||
@@ -84,7 +85,7 @@ func startInProcess(cfg Config, val *Validator) error {
|
||||
app.RegisterTendermintService(val.ClientCtx)
|
||||
}
|
||||
|
||||
if val.APIAddress != "" {
|
||||
if val.AppConfig.API.Enable && val.APIAddress != "" {
|
||||
apiSrv := api.New(val.ClientCtx, logger.With("module", "api-server"))
|
||||
app.RegisterAPIRoutes(apiSrv, val.AppConfig.API)
|
||||
|
||||
@@ -98,8 +99,8 @@ func startInProcess(cfg Config, val *Validator) error {
|
||||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
return fmt.Errorf("failed to start API server: %w", err)
|
||||
case <-time.After(5 * time.Second): // assume server started successfully
|
||||
return err
|
||||
case <-time.After(srvtypes.ServerStartTime): // assume server started successfully
|
||||
}
|
||||
|
||||
val.api = apiSrv
|
||||
@@ -108,74 +109,37 @@ func startInProcess(cfg Config, val *Validator) error {
|
||||
if val.AppConfig.GRPC.Enable {
|
||||
grpcSrv, err := servergrpc.StartGRPCServer(val.ClientCtx, app, val.AppConfig.GRPC.Address)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start gRPC server: %w", err)
|
||||
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 {
|
||||
if val.AppConfig.JSONRPC.Enable && val.AppConfig.JSONRPC.Address != "" {
|
||||
if val.Ctx == nil || val.Ctx.Viper == nil {
|
||||
return fmt.Errorf("validator %s context is nil", val.Moniker)
|
||||
}
|
||||
|
||||
tmEndpoint := "/websocket"
|
||||
tmRPCAddr := val.Ctx.Config.RPC.ListenAddress
|
||||
tmWsClient := ethsrv.ConnectTmWS(tmRPCAddr, tmEndpoint, val.Ctx.Logger)
|
||||
tmRPCAddr := fmt.Sprintf("tcp://%s", val.AppConfig.GRPC.Address)
|
||||
|
||||
val.jsonRPC = jsonrpc.NewServer()
|
||||
|
||||
rpcAPIArr := val.AppConfig.JSONRPC.API
|
||||
apis := rpc.GetRPCAPIs(val.Ctx, val.ClientCtx, tmWsClient, rpcAPIArr)
|
||||
|
||||
for _, api := range apis {
|
||||
if err := val.jsonRPC.RegisterName(api.Namespace, api.Service); err != nil {
|
||||
return fmt.Errorf("failed to register JSON-RPC namespace %s: %w", api.Namespace, err)
|
||||
}
|
||||
val.jsonrpc, val.jsonrpcDone, err = server.StartJSONRPC(val.Ctx, val.ClientCtx, tmRPCAddr, tmEndpoint, *val.AppConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := mux.NewRouter()
|
||||
r.HandleFunc("/", val.jsonRPC.ServeHTTP).Methods("POST")
|
||||
if val.grpc != nil {
|
||||
grpcWeb := grpcweb.WrapServer(val.grpc)
|
||||
ethsrv.MountGRPCWebServices(r, grpcWeb, grpcweb.ListGRPCResources(val.grpc), val.Ctx.Logger)
|
||||
}
|
||||
address := fmt.Sprintf("http://%s", val.AppConfig.JSONRPC.Address)
|
||||
|
||||
handlerWithCors := cors.New(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: []string{
|
||||
http.MethodHead,
|
||||
http.MethodGet,
|
||||
http.MethodPost,
|
||||
http.MethodPut,
|
||||
http.MethodPatch,
|
||||
http.MethodDelete,
|
||||
},
|
||||
AllowedHeaders: []string{"*"},
|
||||
AllowCredentials: false,
|
||||
OptionsPassthrough: false,
|
||||
})
|
||||
|
||||
httpSrv := &http.Server{
|
||||
Addr: strings.TrimPrefix(val.AppConfig.JSONRPC.Address, "tcp://"), // FIXME: timeouts
|
||||
// Addr: val.AppConfig.JSONRPC.RPCAddress, // FIXME: address has too many colons
|
||||
Handler: handlerWithCors.Handler(r),
|
||||
}
|
||||
|
||||
httpSrvDone := make(chan struct{}, 1)
|
||||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
if err := httpSrv.ListenAndServe(); err != nil {
|
||||
if err == http.ErrServerClosed {
|
||||
close(httpSrvDone)
|
||||
return
|
||||
}
|
||||
|
||||
errCh <- err
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
return fmt.Errorf("JSON-RPC go routine failed to start: %w", err)
|
||||
case <-time.After(1 * time.Second): // assume EVM RPC server started successfully
|
||||
val.JSONRPCClient, err = ethclient.Dial(address)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to dial JSON-RPC at %s: %w", val.AppConfig.JSONRPC.Address, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +152,7 @@ func collectGenFiles(cfg Config, vals []*Validator, outputDir string) error {
|
||||
for i := 0; i < cfg.NumValidators; i++ {
|
||||
tmCfg := vals[i].Ctx.Config
|
||||
|
||||
nodeDir := filepath.Join(outputDir, vals[i].Moniker, "ethermintd")
|
||||
nodeDir := filepath.Join(outputDir, vals[i].Moniker, "evmosd")
|
||||
gentxsDir := filepath.Join(outputDir, "gentxs")
|
||||
|
||||
tmCfg.Moniker = vals[i].Moniker
|
||||
@@ -199,18 +163,18 @@ func collectGenFiles(cfg Config, vals []*Validator, outputDir string) error {
|
||||
genFile := tmCfg.GenesisFile()
|
||||
genDoc, err := types.GenesisDocFromFile(genFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create genesis doc: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
appState, err := genutil.GenAppStateFromConfig(cfg.Codec, cfg.TxConfig,
|
||||
tmCfg, initCfg, *genDoc, banktypes.GenesisBalancesIterator{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create app state: %w", err)
|
||||
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 fmt.Errorf("failed to export genesis: %w", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,22 +191,44 @@ func initGenFiles(cfg Config, genAccounts []authtypes.GenesisAccount, genBalance
|
||||
return err
|
||||
}
|
||||
|
||||
authGenState.Accounts = accounts
|
||||
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
|
||||
cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[banktypes.ModuleName], &bankGenState)
|
||||
|
||||
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 = ethermint.AttoPhoton
|
||||
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
|
||||
@@ -264,7 +250,7 @@ func initGenFiles(cfg Config, genAccounts []authtypes.GenesisAccount, genBalance
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeFile(name, dir string, contents []byte) error {
|
||||
func WriteFile(name string, dir string, contents []byte) error {
|
||||
writePath := filepath.Join(dir)
|
||||
file := filepath.Join(writePath, name)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user