github.com/Finschia/finschia-sdk@v0.48.1/testutil/network/network.go (about)

     1  package network
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"net/http"
    10  	"net/url"
    11  	"os"
    12  	"path/filepath"
    13  	"sync"
    14  	"testing"
    15  	"time"
    16  
    17  	ostcfg "github.com/Finschia/ostracon/config"
    18  	"github.com/Finschia/ostracon/libs/log"
    19  	ostrand "github.com/Finschia/ostracon/libs/rand"
    20  	"github.com/Finschia/ostracon/node"
    21  	ostclient "github.com/Finschia/ostracon/rpc/client"
    22  	"github.com/stretchr/testify/require"
    23  	dbm "github.com/tendermint/tm-db"
    24  	"google.golang.org/grpc"
    25  
    26  	"github.com/Finschia/finschia-sdk/baseapp"
    27  	"github.com/Finschia/finschia-sdk/client"
    28  	"github.com/Finschia/finschia-sdk/client/tx"
    29  	"github.com/Finschia/finschia-sdk/codec"
    30  	codectypes "github.com/Finschia/finschia-sdk/codec/types"
    31  	"github.com/Finschia/finschia-sdk/crypto/hd"
    32  	"github.com/Finschia/finschia-sdk/crypto/keyring"
    33  	cryptotypes "github.com/Finschia/finschia-sdk/crypto/types"
    34  	"github.com/Finschia/finschia-sdk/server"
    35  	"github.com/Finschia/finschia-sdk/server/api"
    36  	srvconfig "github.com/Finschia/finschia-sdk/server/config"
    37  	servertypes "github.com/Finschia/finschia-sdk/server/types"
    38  	"github.com/Finschia/finschia-sdk/simapp"
    39  	"github.com/Finschia/finschia-sdk/simapp/params"
    40  	storetypes "github.com/Finschia/finschia-sdk/store/types"
    41  	"github.com/Finschia/finschia-sdk/testutil"
    42  	sdk "github.com/Finschia/finschia-sdk/types"
    43  	authtypes "github.com/Finschia/finschia-sdk/x/auth/types"
    44  	banktypes "github.com/Finschia/finschia-sdk/x/bank/types"
    45  	"github.com/Finschia/finschia-sdk/x/genutil"
    46  	stakingtypes "github.com/Finschia/finschia-sdk/x/staking/types"
    47  )
    48  
    49  // package-wide network lock to only allow one test network at a time
    50  var lock = new(sync.Mutex)
    51  
    52  // AppConstructor defines a function which accepts a network configuration and
    53  // creates an ABCI Application to provide to Tendermint.
    54  type AppConstructor = func(val Validator) servertypes.Application
    55  
    56  // NewAppConstructor returns a new simapp AppConstructor
    57  func NewAppConstructor(encodingCfg params.EncodingConfig) AppConstructor {
    58  	return func(val Validator) servertypes.Application {
    59  		return simapp.NewSimApp(
    60  			val.Ctx.Logger, dbm.NewMemDB(), nil, true, make(map[int64]bool), val.Ctx.Config.RootDir, 0,
    61  			encodingCfg,
    62  			simapp.EmptyAppOptions{},
    63  			baseapp.SetPruning(storetypes.NewPruningOptionsFromString(val.AppConfig.Pruning)),
    64  			baseapp.SetMinGasPrices(val.AppConfig.MinGasPrices),
    65  		)
    66  	}
    67  }
    68  
    69  // Config defines the necessary configuration used to bootstrap and start an
    70  // in-process local testing network.
    71  type Config struct {
    72  	Codec             codec.Codec
    73  	LegacyAmino       *codec.LegacyAmino // TODO: Remove!
    74  	InterfaceRegistry codectypes.InterfaceRegistry
    75  
    76  	TxConfig         client.TxConfig
    77  	AccountRetriever client.AccountRetriever
    78  	AppConstructor   AppConstructor             // the ABCI application constructor
    79  	GenesisState     map[string]json.RawMessage // custom gensis state to provide
    80  	TimeoutCommit    time.Duration              // the consensus commitment timeout
    81  	ChainID          string                     // the network chain-id
    82  	NumValidators    int                        // the total number of validators to create and bond
    83  	Mnemonics        []string                   // custom user-provided validator operator mnemonics
    84  	BondDenom        string                     // the staking bond denomination
    85  	MinGasPrices     string                     // the minimum gas prices each validator will accept
    86  	AccountTokens    sdk.Int                    // the amount of unique validator tokens (e.g. 1000node0)
    87  	StakingTokens    sdk.Int                    // the amount of tokens each validator has available to stake
    88  	BondedTokens     sdk.Int                    // the amount of tokens each validator stakes
    89  	PruningStrategy  string                     // the pruning strategy each validator will have
    90  	EnableLogging    bool                       // enable Tendermint logging to STDOUT
    91  	CleanupDir       bool                       // remove base temporary directory during cleanup
    92  	SigningAlgo      string                     // signing algorithm for keys
    93  	KeyringOptions   []keyring.Option
    94  }
    95  
    96  // DefaultConfig returns a sane default configuration suitable for nearly all
    97  // testing requirements.
    98  func DefaultConfig() Config {
    99  	encCfg := simapp.MakeTestEncodingConfig()
   100  
   101  	return Config{
   102  		Codec:             encCfg.Marshaler,
   103  		TxConfig:          encCfg.TxConfig,
   104  		LegacyAmino:       encCfg.Amino,
   105  		InterfaceRegistry: encCfg.InterfaceRegistry,
   106  		AccountRetriever:  authtypes.AccountRetriever{},
   107  		AppConstructor:    NewAppConstructor(encCfg),
   108  		GenesisState:      simapp.ModuleBasics.DefaultGenesis(encCfg.Marshaler),
   109  		// 2 second confirm may make some tests to be failed with `tx already in mempool`
   110  		TimeoutCommit:   1 * time.Second,
   111  		ChainID:         "chain-" + ostrand.NewRand().Str(6),
   112  		NumValidators:   4,
   113  		BondDenom:       sdk.DefaultBondDenom,
   114  		MinGasPrices:    fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom),
   115  		AccountTokens:   sdk.TokensFromConsensusPower(1000, sdk.DefaultPowerReduction),
   116  		StakingTokens:   sdk.TokensFromConsensusPower(500, sdk.DefaultPowerReduction),
   117  		BondedTokens:    sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction),
   118  		PruningStrategy: storetypes.PruningOptionNothing,
   119  		CleanupDir:      true,
   120  		SigningAlgo:     string(hd.Secp256k1Type),
   121  		KeyringOptions:  []keyring.Option{},
   122  	}
   123  }
   124  
   125  type (
   126  	// Network defines a local in-process testing network using SimApp. It can be
   127  	// configured to start any number of validators, each with its own RPC and API
   128  	// clients. Typically, this test network would be used in client and integration
   129  	// testing where user input is expected.
   130  	//
   131  	// Note, due to Tendermint constraints in regards to RPC functionality, there
   132  	// may only be one test network running at a time. Thus, any caller must be
   133  	// sure to Cleanup after testing is finished in order to allow other tests
   134  	// to create networks. In addition, only the first validator will have a valid
   135  	// RPC and API server/client.
   136  	Network struct {
   137  		T          *testing.T
   138  		BaseDir    string
   139  		Validators []*Validator
   140  
   141  		Config Config
   142  	}
   143  
   144  	// Validator defines an in-process Tendermint validator node. Through this object,
   145  	// a client can make RPC and API calls and interact with any client command
   146  	// or handler.
   147  	Validator struct {
   148  		AppConfig  *srvconfig.Config
   149  		ClientCtx  client.Context
   150  		Ctx        *server.Context
   151  		Dir        string
   152  		NodeID     string
   153  		PubKey     cryptotypes.PubKey
   154  		Moniker    string
   155  		APIAddress string
   156  		RPCAddress string
   157  		P2PAddress string
   158  		Address    sdk.AccAddress
   159  		ValAddress sdk.ValAddress
   160  		RPCClient  ostclient.Client
   161  
   162  		tmNode  *node.Node
   163  		api     *api.Server
   164  		grpc    *grpc.Server
   165  		grpcWeb *http.Server
   166  	}
   167  )
   168  
   169  // New creates a new Network for integration tests.
   170  func New(t *testing.T, cfg Config) *Network {
   171  	// only one caller/test can create and use a network at a time
   172  	t.Log("acquiring test network lock")
   173  	lock.Lock()
   174  
   175  	baseDir, err := os.MkdirTemp(t.TempDir(), cfg.ChainID)
   176  	require.NoError(t, err)
   177  	t.Logf("created temporary directory: %s", baseDir)
   178  
   179  	network := &Network{
   180  		T:          t,
   181  		BaseDir:    baseDir,
   182  		Validators: make([]*Validator, cfg.NumValidators),
   183  		Config:     cfg,
   184  	}
   185  
   186  	t.Log("preparing test network...")
   187  
   188  	monikers := make([]string, cfg.NumValidators)
   189  	nodeIDs := make([]string, cfg.NumValidators)
   190  	valPubKeys := make([]cryptotypes.PubKey, cfg.NumValidators)
   191  
   192  	var (
   193  		genAccounts []authtypes.GenesisAccount
   194  		genBalances []banktypes.Balance
   195  		genFiles    []string
   196  	)
   197  
   198  	buf := bufio.NewReader(os.Stdin)
   199  
   200  	// generate private keys, node IDs, and initial transactions
   201  	for i := 0; i < cfg.NumValidators; i++ {
   202  		appCfg := srvconfig.DefaultConfig()
   203  		appCfg.Pruning = cfg.PruningStrategy
   204  		appCfg.MinGasPrices = cfg.MinGasPrices
   205  		appCfg.API.Enable = true
   206  		appCfg.API.Swagger = false
   207  		appCfg.Telemetry.Enabled = false
   208  
   209  		ctx := server.NewDefaultContext()
   210  		tmCfg := ctx.Config
   211  		tmCfg.PrivValidatorRemoteAddresses = append(tmCfg.PrivValidatorRemoteAddresses, "127.0.0.1")
   212  		tmCfg.Consensus.TimeoutCommit = cfg.TimeoutCommit
   213  
   214  		// Only allow the first validator to expose an RPC, API and gRPC
   215  		// server/client due to Tendermint in-process constraints.
   216  		apiAddr := ""
   217  		tmCfg.RPC.ListenAddress = ""
   218  		appCfg.GRPC.Enable = false
   219  		appCfg.GRPCWeb.Enable = false
   220  		if i == 0 {
   221  			apiListenAddr, _, err := server.FreeTCPAddr()
   222  			require.NoError(t, err)
   223  			appCfg.API.Address = apiListenAddr
   224  
   225  			apiURL, err := url.Parse(apiListenAddr)
   226  			require.NoError(t, err)
   227  			apiAddr = fmt.Sprintf("http://%s:%s", apiURL.Hostname(), apiURL.Port())
   228  
   229  			rpcAddr, _, err := server.FreeTCPAddr()
   230  			require.NoError(t, err)
   231  			tmCfg.RPC.ListenAddress = rpcAddr
   232  
   233  			_, grpcPort, err := server.FreeTCPAddr()
   234  			require.NoError(t, err)
   235  			appCfg.GRPC.Address = fmt.Sprintf("0.0.0.0:%s", grpcPort)
   236  			appCfg.GRPC.Enable = true
   237  
   238  			_, grpcWebPort, err := server.FreeTCPAddr()
   239  			require.NoError(t, err)
   240  			appCfg.GRPCWeb.Address = fmt.Sprintf("0.0.0.0:%s", grpcWebPort)
   241  			appCfg.GRPCWeb.Enable = true
   242  		}
   243  
   244  		logger := log.NewNopLogger()
   245  		if cfg.EnableLogging {
   246  			logger = log.NewOCLogger(log.NewSyncWriter(os.Stdout))
   247  			logger, _ = log.ParseLogLevel("info", logger, ostcfg.DefaultLogLevel)
   248  		}
   249  
   250  		ctx.Logger = logger
   251  
   252  		nodeDirName := fmt.Sprintf("node%d", i)
   253  		nodeDir := filepath.Join(network.BaseDir, nodeDirName, "simd")
   254  		clientDir := filepath.Join(network.BaseDir, nodeDirName, "simcli")
   255  		gentxsDir := filepath.Join(network.BaseDir, "gentxs")
   256  
   257  		require.NoError(t, os.MkdirAll(filepath.Join(nodeDir, "config"), 0o755))
   258  		require.NoError(t, os.MkdirAll(clientDir, 0o755))
   259  
   260  		tmCfg.SetRoot(nodeDir)
   261  		tmCfg.Moniker = nodeDirName
   262  		monikers[i] = nodeDirName
   263  
   264  		proxyAddr, _, err := server.FreeTCPAddr()
   265  		require.NoError(t, err)
   266  		tmCfg.ProxyApp = proxyAddr
   267  
   268  		p2pAddr, _, err := server.FreeTCPAddr()
   269  		require.NoError(t, err)
   270  
   271  		tmCfg.P2P.ListenAddress = p2pAddr
   272  		tmCfg.P2P.AddrBookStrict = false
   273  		tmCfg.P2P.AllowDuplicateIP = true
   274  		tmCfg.PrivValidatorRemoteAddresses = append(tmCfg.PrivValidatorRemoteAddresses, "127.0.0.1")
   275  
   276  		nodeID, pubKey, err := genutil.InitializeNodeValidatorFiles(tmCfg)
   277  		require.NoError(t, err)
   278  		nodeIDs[i] = nodeID
   279  		valPubKeys[i] = pubKey
   280  
   281  		kb, err := keyring.New(sdk.KeyringServiceName(), keyring.BackendTest, clientDir, buf, cfg.KeyringOptions...)
   282  		require.NoError(t, err)
   283  
   284  		keyringAlgos, _ := kb.SupportedAlgorithms()
   285  		algo, err := keyring.NewSigningAlgoFromString(cfg.SigningAlgo, keyringAlgos)
   286  		require.NoError(t, err)
   287  
   288  		var mnemonic string
   289  		if i < len(cfg.Mnemonics) {
   290  			mnemonic = cfg.Mnemonics[i]
   291  		}
   292  
   293  		addr, secret, err := testutil.GenerateSaveCoinKey(kb, nodeDirName, mnemonic, true, algo)
   294  		require.NoError(t, err)
   295  
   296  		info := map[string]string{"secret": secret}
   297  		infoBz, err := json.Marshal(info)
   298  		require.NoError(t, err)
   299  
   300  		// save private key seed words
   301  		require.NoError(t, writeFile(fmt.Sprintf("%v.json", "key_seed"), clientDir, infoBz))
   302  
   303  		balances := sdk.NewCoins(
   304  			sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), cfg.AccountTokens),
   305  			sdk.NewCoin(cfg.BondDenom, cfg.StakingTokens),
   306  		)
   307  
   308  		genFiles = append(genFiles, tmCfg.GenesisFile())
   309  		genBalances = append(genBalances, banktypes.Balance{Address: addr.String(), Coins: balances.Sort()})
   310  		genAccounts = append(genAccounts, authtypes.NewBaseAccount(addr, nil, 0, 0))
   311  
   312  		commission, err := sdk.NewDecFromStr("0.5")
   313  		require.NoError(t, err)
   314  
   315  		createValMsg, err := stakingtypes.NewMsgCreateValidator(
   316  			sdk.ValAddress(addr),
   317  			valPubKeys[i],
   318  			sdk.NewCoin(cfg.BondDenom, cfg.BondedTokens),
   319  			stakingtypes.NewDescription(nodeDirName, "", "", "", ""),
   320  			stakingtypes.NewCommissionRates(commission, sdk.OneDec(), sdk.OneDec()),
   321  			sdk.OneInt(),
   322  		)
   323  		require.NoError(t, err)
   324  
   325  		p2pURL, err := url.Parse(p2pAddr)
   326  		require.NoError(t, err)
   327  
   328  		memo := fmt.Sprintf("%s@%s:%s", nodeIDs[i], p2pURL.Hostname(), p2pURL.Port())
   329  		fee := sdk.NewCoins(sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), sdk.NewInt(0)))
   330  		txBuilder := cfg.TxConfig.NewTxBuilder()
   331  		require.NoError(t, txBuilder.SetMsgs(createValMsg))
   332  		txBuilder.SetFeeAmount(fee)    // Arbitrary fee
   333  		txBuilder.SetGasLimit(1000000) // Need at least 100386
   334  		txBuilder.SetMemo(memo)
   335  
   336  		txFactory := tx.Factory{}
   337  		txFactory = txFactory.
   338  			WithChainID(cfg.ChainID).
   339  			WithMemo(memo).
   340  			WithKeybase(kb).
   341  			WithTxConfig(cfg.TxConfig)
   342  
   343  		err = tx.Sign(txFactory, nodeDirName, txBuilder, true)
   344  		require.NoError(t, err)
   345  
   346  		txBz, err := cfg.TxConfig.TxJSONEncoder()(txBuilder.GetTx())
   347  		require.NoError(t, err)
   348  		require.NoError(t, writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBz))
   349  
   350  		srvconfig.WriteConfigFile(filepath.Join(nodeDir, "config/app.toml"), appCfg)
   351  
   352  		clientCtx := client.Context{}.
   353  			WithKeyringDir(clientDir).
   354  			WithKeyring(kb).
   355  			WithHomeDir(tmCfg.RootDir).
   356  			WithChainID(cfg.ChainID).
   357  			WithInterfaceRegistry(cfg.InterfaceRegistry).
   358  			WithCodec(cfg.Codec).
   359  			WithLegacyAmino(cfg.LegacyAmino).
   360  			WithTxConfig(cfg.TxConfig).
   361  			WithAccountRetriever(cfg.AccountRetriever)
   362  
   363  		network.Validators[i] = &Validator{
   364  			AppConfig:  appCfg,
   365  			ClientCtx:  clientCtx,
   366  			Ctx:        ctx,
   367  			Dir:        filepath.Join(network.BaseDir, nodeDirName),
   368  			NodeID:     nodeID,
   369  			PubKey:     pubKey,
   370  			Moniker:    nodeDirName,
   371  			RPCAddress: tmCfg.RPC.ListenAddress,
   372  			P2PAddress: tmCfg.P2P.ListenAddress,
   373  			APIAddress: apiAddr,
   374  			Address:    addr,
   375  			ValAddress: sdk.ValAddress(addr),
   376  		}
   377  	}
   378  
   379  	require.NoError(t, initGenFiles(cfg, genAccounts, genBalances, genFiles))
   380  	require.NoError(t, collectGenFiles(cfg, network.Validators, network.BaseDir))
   381  	t.Log("starting test network...")
   382  	for _, v := range network.Validators {
   383  		require.NoError(t, startInProcess(cfg, v))
   384  	}
   385  	t.Log("started test network")
   386  
   387  	// Ensure we cleanup incase any test was abruptly halted (e.g. SIGINT) as any
   388  	// defer in a test would not be called.
   389  	server.TrapSignal(network.Cleanup)
   390  
   391  	return network
   392  }
   393  
   394  // New creates a new Network for integration tests without init.
   395  func NewWithoutInit(t *testing.T, cfg Config, baseDir string, validators []*Validator) *Network {
   396  	// only one caller/test can create and use a network at a time
   397  	t.Log("acquiring test network lock")
   398  	lock.Lock()
   399  
   400  	network := &Network{
   401  		T:          t,
   402  		BaseDir:    baseDir,
   403  		Validators: validators,
   404  		Config:     cfg,
   405  	}
   406  
   407  	t.Log("starting test network...")
   408  	for _, v := range network.Validators {
   409  		require.NoError(t, startInProcess(cfg, v))
   410  	}
   411  
   412  	t.Log("started test network")
   413  
   414  	// Ensure we cleanup incase any test was abruptly halted (e.g. SIGINT) as any
   415  	// defer in a test would not be called.
   416  	server.TrapSignal(network.Cleanup)
   417  
   418  	return network
   419  }
   420  
   421  func AddNewValidator(t *testing.T, network *Network, validator *Validator) {
   422  	t.Log("adding new validator...")
   423  
   424  	require.NoError(t, startInProcess(network.Config, validator))
   425  	network.Validators = append(network.Validators, validator)
   426  
   427  	t.Log("added new validator")
   428  
   429  	server.TrapSignal(network.Cleanup)
   430  }
   431  
   432  // LatestHeight returns the latest height of the network or an error if the
   433  // query fails or no validators exist.
   434  func (n *Network) LatestHeight() (int64, error) {
   435  	if len(n.Validators) == 0 {
   436  		return 0, errors.New("no validators available")
   437  	}
   438  
   439  	status, err := n.Validators[0].RPCClient.Status(context.Background())
   440  	if err != nil {
   441  		return 0, err
   442  	}
   443  
   444  	return status.SyncInfo.LatestBlockHeight, nil
   445  }
   446  
   447  // WaitForHeight performs a blocking check where it waits for a block to be
   448  // committed after a given block. If that height is not reached within a timeout,
   449  // an error is returned. Regardless, the latest height queried is returned.
   450  func (n *Network) WaitForHeight(h int64) (int64, error) {
   451  	return n.WaitForHeightWithTimeout(h, 10*time.Second)
   452  }
   453  
   454  // WaitForHeightWithTimeout is the same as WaitForHeight except the caller can
   455  // provide a custom timeout.
   456  func (n *Network) WaitForHeightWithTimeout(h int64, t time.Duration) (int64, error) {
   457  	ticker := time.NewTicker(time.Second)
   458  	timeout := time.After(t)
   459  
   460  	if len(n.Validators) == 0 {
   461  		return 0, errors.New("no validators available")
   462  	}
   463  
   464  	var latestHeight int64
   465  	val := n.Validators[0]
   466  
   467  	for {
   468  		select {
   469  		case <-timeout:
   470  			ticker.Stop()
   471  			return latestHeight, errors.New("timeout exceeded waiting for block")
   472  		case <-ticker.C:
   473  			status, err := val.RPCClient.Status(context.Background())
   474  			if err == nil && status != nil {
   475  				latestHeight = status.SyncInfo.LatestBlockHeight
   476  				if latestHeight >= h {
   477  					return latestHeight, nil
   478  				}
   479  			}
   480  		}
   481  	}
   482  }
   483  
   484  // WaitForNextBlock waits for the next block to be committed, returning an error
   485  // upon failure.
   486  func (n *Network) WaitForNextBlock() error {
   487  	lastBlock, err := n.LatestHeight()
   488  	if err != nil {
   489  		return err
   490  	}
   491  
   492  	_, err = n.WaitForHeight(lastBlock + 1)
   493  	if err != nil {
   494  		return err
   495  	}
   496  
   497  	return err
   498  }
   499  
   500  // Cleanup removes the root testing (temporary) directory and stops both the
   501  // Tendermint and API services. It allows other callers to create and start
   502  // test networks. This method must be called when a test is finished, typically
   503  // in a defer.
   504  func (n *Network) Cleanup() {
   505  	defer func() {
   506  		lock.Unlock()
   507  		n.T.Log("released test network lock")
   508  	}()
   509  
   510  	n.T.Log("cleaning up test network...")
   511  
   512  	for _, v := range n.Validators {
   513  		if v.tmNode != nil && v.tmNode.IsRunning() {
   514  			_ = v.tmNode.Stop()
   515  		}
   516  
   517  		if v.api != nil {
   518  			_ = v.api.Close()
   519  		}
   520  
   521  		if v.grpc != nil {
   522  			v.grpc.Stop()
   523  			if v.grpcWeb != nil {
   524  				_ = v.grpcWeb.Close()
   525  			}
   526  		}
   527  	}
   528  
   529  	if n.Config.CleanupDir {
   530  		_ = os.RemoveAll(n.BaseDir)
   531  	}
   532  
   533  	n.T.Log("finished cleaning up test network")
   534  }