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