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 }