Merge PR #6489: Test Network Testing Framework
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
Package testutil implements and exposes a fully operational in-process Tendermint
|
||||
test network that consists of at least one or potentially many validators. This
|
||||
test network can be used primarily for integration tests or unit test suites.
|
||||
|
||||
The testnetwork utilizes SimApp as the ABCI application and uses all the modules
|
||||
defined in the Cosmos SDK. An in-process test network can be configured with any
|
||||
number of validators as well as account funds and even custom genesis state.
|
||||
|
||||
When creating a test network, a series of Validator objects are returned. Each
|
||||
Validator object has useful information such as their address and pubkey. A
|
||||
Validator will also provide its RPC, P2P, and API addresses that can be useful
|
||||
for integration testing. In addition, a Tendermint local RPC client is also provided
|
||||
which can be handy for making direct RPC calls to Tendermint.
|
||||
|
||||
Note, due to limitations in concurrency and the design of the RPC layer in
|
||||
Tendermint, only the first Validator object will have an RPC and API client
|
||||
exposed. Due to this exact same limitation, only a single test network can exist
|
||||
at a time. A caller must be certain it calls Cleanup after it no longer needs
|
||||
the network.
|
||||
|
||||
A typical testing flow might look like the following:
|
||||
|
||||
type IntegrationTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
cfg testutil.Config
|
||||
network *testutil.Network
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) SetupSuite() {
|
||||
s.T().Log("setting up integration test suite")
|
||||
|
||||
cfg := testutil.DefaultConfig()
|
||||
cfg.NumValidators = 1
|
||||
|
||||
s.cfg = cfg
|
||||
s.network = testutil.NewTestNetwork(s.T(), cfg)
|
||||
|
||||
_, err := s.network.WaitForHeight(1)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
||||
s.T().Log("tearing down integration test suite")
|
||||
|
||||
// This is important and must be called to ensure other tests can create
|
||||
// a network!
|
||||
s.network.Cleanup()
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TestQueryBalancesRequestHandlerFn() {
|
||||
val := s.network.Validators[0]
|
||||
baseURL := val.APIAddress
|
||||
|
||||
// Use baseURL to make API HTTP requests or use val.RPCClient to make direct
|
||||
// Tendermint RPC calls.
|
||||
// ...
|
||||
}
|
||||
|
||||
func TestIntegrationTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(IntegrationTestSuite))
|
||||
}
|
||||
*/
|
||||
package testutil
|
||||
@@ -0,0 +1,384 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
tmcfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
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"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
"github.com/cosmos/cosmos-sdk/server/api"
|
||||
srvconfig "github.com/cosmos/cosmos-sdk/server/config"
|
||||
"github.com/cosmos/cosmos-sdk/simapp"
|
||||
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"
|
||||
)
|
||||
|
||||
var (
|
||||
_, cdc = simapp.MakeCodecs()
|
||||
|
||||
// package-wide network lock to only allow one test network at a time
|
||||
lock = new(sync.Mutex)
|
||||
)
|
||||
|
||||
// AppConstructor defines a function which accepts a network configuration and
|
||||
// creates an ABCI Application to provide to Tendermint.
|
||||
type AppConstructor = func(val Validator) server.Application
|
||||
|
||||
func NewSimApp(val Validator) server.Application {
|
||||
return simapp.NewSimApp(
|
||||
val.Ctx.Logger, dbm.NewMemDB(), nil, true, make(map[int64]bool), val.Ctx.Config.RootDir, 0,
|
||||
baseapp.SetPruning(storetypes.NewPruningOptionsFromString(val.AppConfig.Pruning)),
|
||||
baseapp.SetMinGasPrices(val.AppConfig.MinGasPrices),
|
||||
)
|
||||
}
|
||||
|
||||
// Config defines the necessary configuration used to bootstrap and start an
|
||||
// in-process local testing network.
|
||||
type Config struct {
|
||||
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
|
||||
Passphrase string // the passphrase provided to the test keyring
|
||||
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
|
||||
}
|
||||
|
||||
// DefaultConfig returns a sane default configuration suitable for nearly all
|
||||
// testing requirements.
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
AppConstructor: NewSimApp,
|
||||
GenesisState: simapp.ModuleBasics.DefaultGenesis(cdc),
|
||||
TimeoutCommit: 2 * time.Second,
|
||||
ChainID: "chain-" + tmrand.NewRand().Str(6),
|
||||
NumValidators: 4,
|
||||
BondDenom: sdk.DefaultBondDenom,
|
||||
MinGasPrices: fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom),
|
||||
Passphrase: clientkeys.DefaultKeyPass,
|
||||
AccountTokens: sdk.TokensFromConsensusPower(1000),
|
||||
StakingTokens: sdk.TokensFromConsensusPower(500),
|
||||
BondedTokens: sdk.TokensFromConsensusPower(100),
|
||||
PruningStrategy: storetypes.PruningOptionNothing,
|
||||
CleanupDir: true,
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
T *testing.T
|
||||
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 *srvconfig.Config
|
||||
ClientCtx client.Context
|
||||
Ctx *server.Context
|
||||
Dir string
|
||||
NodeID string
|
||||
PubKey crypto.PubKey
|
||||
Moniker string
|
||||
APIAddress string
|
||||
RPCAddress string
|
||||
P2PAddress string
|
||||
Address sdk.AccAddress
|
||||
ValAddress sdk.ValAddress
|
||||
RPCClient tmclient.Client
|
||||
|
||||
tmNode *node.Node
|
||||
api *api.Server
|
||||
}
|
||||
)
|
||||
|
||||
func NewTestNetwork(t *testing.T, cfg Config) *Network {
|
||||
// only one caller/test can create and use a network at a time
|
||||
t.Log("acquiring test network lock")
|
||||
lock.Lock()
|
||||
|
||||
baseDir, err := ioutil.TempDir(os.TempDir(), cfg.ChainID)
|
||||
require.NoError(t, err)
|
||||
t.Logf("created temporary directory: %s", baseDir)
|
||||
|
||||
network := &Network{
|
||||
T: t,
|
||||
BaseDir: baseDir,
|
||||
Validators: make([]*Validator, cfg.NumValidators),
|
||||
config: cfg,
|
||||
}
|
||||
|
||||
t.Log("preparing test network...")
|
||||
|
||||
monikers := make([]string, cfg.NumValidators)
|
||||
nodeIDs := make([]string, cfg.NumValidators)
|
||||
valPubKeys := make([]crypto.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 := srvconfig.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 and API server/client
|
||||
// due to Tendermint in-process constraints.
|
||||
apiAddr := ""
|
||||
tmCfg.RPC.ListenAddress = ""
|
||||
if i == 0 {
|
||||
apiListenAddr, _, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
appCfg.API.Address = apiListenAddr
|
||||
|
||||
apiURL, err := url.Parse(apiListenAddr)
|
||||
require.NoError(t, err)
|
||||
apiAddr = fmt.Sprintf("http://%s:%s", apiURL.Hostname(), apiURL.Port())
|
||||
|
||||
rpcAddr, _, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
tmCfg.RPC.ListenAddress = rpcAddr
|
||||
}
|
||||
|
||||
logger := log.NewNopLogger()
|
||||
if cfg.EnableLogging {
|
||||
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, "simd")
|
||||
clientDir := filepath.Join(network.BaseDir, nodeDirName, "simcli")
|
||||
gentxsDir := filepath.Join(network.BaseDir, "gentxs")
|
||||
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(nodeDir, "config"), 0755))
|
||||
require.NoError(t, os.MkdirAll(clientDir, 0755))
|
||||
|
||||
tmCfg.SetRoot(nodeDir)
|
||||
tmCfg.Moniker = nodeDirName
|
||||
monikers[i] = nodeDirName
|
||||
|
||||
proxyAddr, _, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
tmCfg.ProxyApp = proxyAddr
|
||||
|
||||
p2pAddr, _, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
tmCfg.P2P.ListenAddress = p2pAddr
|
||||
tmCfg.P2P.AddrBookStrict = false
|
||||
tmCfg.P2P.AllowDuplicateIP = true
|
||||
|
||||
nodeID, pubKey, err := genutil.InitializeNodeValidatorFiles(tmCfg)
|
||||
require.NoError(t, err)
|
||||
nodeIDs[i] = nodeID
|
||||
valPubKeys[i] = pubKey
|
||||
|
||||
kb, err := keyring.New(sdk.KeyringServiceName(), keyring.BackendTest, clientDir, buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
addr, secret, err := server.GenerateSaveCoinKey(kb, nodeDirName, cfg.Passphrase, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
info := map[string]string{"secret": secret}
|
||||
infoBz, err := json.Marshal(info)
|
||||
require.NoError(t, err)
|
||||
|
||||
// save private key seed words
|
||||
require.NoError(t, writeFile(fmt.Sprintf("%v.json", "key_seed"), clientDir, infoBz))
|
||||
|
||||
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, Coins: balances.Sort()})
|
||||
genAccounts = append(genAccounts, authtypes.NewBaseAccount(addr, nil, 0, 0))
|
||||
|
||||
createValMsg := stakingtypes.NewMsgCreateValidator(
|
||||
sdk.ValAddress(addr),
|
||||
valPubKeys[i],
|
||||
sdk.NewCoin(sdk.DefaultBondDenom, cfg.BondedTokens),
|
||||
stakingtypes.NewDescription(nodeDirName, "", "", "", ""),
|
||||
stakingtypes.NewCommissionRates(sdk.OneDec(), sdk.OneDec(), sdk.OneDec()),
|
||||
sdk.OneInt(),
|
||||
)
|
||||
|
||||
p2pURL, err := url.Parse(p2pAddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
memo := fmt.Sprintf("%s@%s:%s", nodeIDs[i], p2pURL.Hostname(), p2pURL.Port())
|
||||
tx := authtypes.NewStdTx([]sdk.Msg{createValMsg}, authtypes.StdFee{}, []authtypes.StdSignature{}, memo) //nolint:staticcheck // SA1019: authtypes.StdFee is deprecated
|
||||
txBldr := authtypes.TxBuilder{}.
|
||||
WithChainID(cfg.ChainID).
|
||||
WithMemo(memo).
|
||||
WithKeybase(kb)
|
||||
|
||||
signedTx, err := txBldr.SignStdTx(nodeDirName, tx, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
txBz, err := cdc.MarshalJSON(signedTx)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBz))
|
||||
|
||||
srvconfig.WriteConfigFile(filepath.Join(nodeDir, "config/app.toml"), appCfg)
|
||||
|
||||
network.Validators[i] = &Validator{
|
||||
AppConfig: appCfg,
|
||||
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))
|
||||
}
|
||||
|
||||
t.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
|
||||
}
|
||||
|
||||
// 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()
|
||||
if err == nil && status != nil {
|
||||
latestHeight = status.SyncInfo.LatestBlockHeight
|
||||
if latestHeight >= h {
|
||||
return latestHeight, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.T.Log("released test network lock")
|
||||
}()
|
||||
|
||||
n.T.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 n.config.CleanupDir {
|
||||
_ = os.RemoveAll(n.BaseDir)
|
||||
}
|
||||
|
||||
n.T.Log("finished cleaning up test network")
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type IntegrationTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
network *Network
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) SetupSuite() {
|
||||
s.T().Log("setting up integration test suite")
|
||||
|
||||
s.network = NewTestNetwork(s.T(), DefaultConfig())
|
||||
s.Require().NotNil(s.network)
|
||||
|
||||
_, err := s.network.WaitForHeight(1)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
||||
s.T().Log("tearing down integration test suite")
|
||||
s.network.Cleanup()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func TestIntegrationTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(IntegrationTestSuite))
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"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/client"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/server/api"
|
||||
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"
|
||||
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
|
||||
)
|
||||
|
||||
func startInProcess(cfg Config, val *Validator) error {
|
||||
logger := val.Ctx.Logger
|
||||
tmCfg := val.Ctx.Config
|
||||
tmCfg.Instrumentation.Prometheus = false
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if val.APIAddress != "" {
|
||||
val.ClientCtx = client.Context{}.
|
||||
WithHomeDir(tmCfg.RootDir).
|
||||
WithChainID(cfg.ChainID).
|
||||
WithJSONMarshaler(cdc).
|
||||
WithClient(val.RPCClient).
|
||||
WithTrustNode(true)
|
||||
|
||||
apiSrv := api.New(val.ClientCtx, logger.With("module", "api-server"))
|
||||
app.RegisterAPIRoutes(apiSrv)
|
||||
|
||||
errCh := make(chan error)
|
||||
|
||||
go func() {
|
||||
if err := apiSrv.Start(*val.AppConfig); err != nil {
|
||||
errCh <- err
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
return err
|
||||
case <-time.After(5 * time.Second): // assume server started successfully
|
||||
}
|
||||
|
||||
val.api = apiSrv
|
||||
}
|
||||
|
||||
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, "simd")
|
||||
gentxsDir := filepath.Join(outputDir, "gentxs")
|
||||
|
||||
tmCfg.Moniker = vals[i].Moniker
|
||||
tmCfg.SetRoot(nodeDir)
|
||||
|
||||
initCfg := genutiltypes.NewInitConfig(cfg.ChainID, gentxsDir, vals[i].Moniker, vals[i].NodeID, vals[i].PubKey)
|
||||
|
||||
genFile := tmCfg.GenesisFile()
|
||||
genDoc, err := types.GenesisDocFromFile(genFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
appState, err := genutil.GenAppStateFromConfig(cdc, 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
|
||||
cdc.MustUnmarshalJSON(cfg.GenesisState[authtypes.ModuleName], &authGenState)
|
||||
|
||||
authGenState.Accounts = genAccounts
|
||||
cfg.GenesisState[authtypes.ModuleName] = cdc.MustMarshalJSON(authGenState)
|
||||
|
||||
// set the balances in the genesis state
|
||||
var bankGenState banktypes.GenesisState
|
||||
cdc.MustUnmarshalJSON(cfg.GenesisState[banktypes.ModuleName], &bankGenState)
|
||||
|
||||
bankGenState.Balances = genBalances
|
||||
cfg.GenesisState[banktypes.ModuleName] = cdc.MustMarshalJSON(bankGenState)
|
||||
|
||||
appGenStateJSON, err := codec.MarshalJSONIndent(cdc, 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, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tmos.WriteFile(file, contents, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user