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 }