|
| 1 | +package speedtest |
| 2 | + |
| 3 | +import ( |
| 4 | + "encoding/json" |
| 5 | + "fmt" |
| 6 | + "math" |
| 7 | + "time" |
| 8 | + |
| 9 | + "github.com/cometbft/cometbft/abci/types" |
| 10 | + cmtjson "github.com/cometbft/cometbft/libs/json" |
| 11 | + "github.com/spf13/cobra" |
| 12 | + |
| 13 | + "github.com/cosmos/cosmos-sdk/codec" |
| 14 | + servertypes "github.com/cosmos/cosmos-sdk/server/types" |
| 15 | + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" |
| 16 | + sdk "github.com/cosmos/cosmos-sdk/types" |
| 17 | + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" |
| 18 | + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" |
| 19 | +) |
| 20 | + |
| 21 | +type AccountCreator func() (*authtypes.BaseAccount, sdk.Coins) |
| 22 | + |
| 23 | +type GenerateTx func() []byte |
| 24 | + |
| 25 | +var ( |
| 26 | + numAccounts = 10_000 |
| 27 | + numTxsPerBlock = 4_000 |
| 28 | + numBlocksToRun = 100 |
| 29 | + blockMaxGas = math.MaxInt64 |
| 30 | + blockMaxBytes = math.MaxInt64 |
| 31 | + verifyTxs = false |
| 32 | +) |
| 33 | + |
| 34 | +// NewCmd returns a command that will run an execution test on your application. |
| 35 | +// Balances and accounts are automatically added to the chain's state via AccountCreator. |
| 36 | +func NewCmd( |
| 37 | + createAccount AccountCreator, |
| 38 | + generateTx GenerateTx, |
| 39 | + app servertypes.ABCI, |
| 40 | + cdc codec.Codec, |
| 41 | + genState map[string]json.RawMessage, |
| 42 | + chainID string, |
| 43 | +) *cobra.Command { |
| 44 | + cmd := &cobra.Command{ |
| 45 | + Use: "speedtest", |
| 46 | + Short: "execution speedtest", |
| 47 | + Long: "speedtest is a tool for measuring raw execution TPS of your application", |
| 48 | + Example: "speedtest --accounts 20000 --txs 2000 --blocks 10 --block-max-gas 1000000000 --block-max-bytes 1000000000 --verify-txs", |
| 49 | + RunE: func(cmd *cobra.Command, args []string) error { |
| 50 | + accounts := make([]simtestutil.GenesisAccount, 0, numAccounts) |
| 51 | + balances := make([]banktypes.Balance, 0, numAccounts) |
| 52 | + for range numAccounts { |
| 53 | + account, balance := createAccount() |
| 54 | + genesisAcc := simtestutil.GenesisAccount{ |
| 55 | + GenesisAccount: account, |
| 56 | + Coins: balance, |
| 57 | + } |
| 58 | + accounts = append(accounts, genesisAcc) |
| 59 | + balances = append(balances, banktypes.Balance{ |
| 60 | + Address: account.Address, |
| 61 | + Coins: balance, |
| 62 | + }) |
| 63 | + } |
| 64 | + |
| 65 | + vals, err := simtestutil.CreateRandomValidatorSet() |
| 66 | + if err != nil { |
| 67 | + return err |
| 68 | + } |
| 69 | + |
| 70 | + genAccs := make([]authtypes.GenesisAccount, 0, len(accounts)) |
| 71 | + for _, acc := range accounts { |
| 72 | + genAccs = append(genAccs, acc.GenesisAccount) |
| 73 | + } |
| 74 | + genesisState, err := simtestutil.GenesisStateWithValSet(cdc, genState, vals, genAccs, balances...) |
| 75 | + if err != nil { |
| 76 | + return err |
| 77 | + } |
| 78 | + |
| 79 | + // init chain must be called to stop deliverState from being nil |
| 80 | + stateBytes, err := cmtjson.MarshalIndent(genesisState, "", " ") |
| 81 | + if err != nil { |
| 82 | + return err |
| 83 | + } |
| 84 | + |
| 85 | + cp := simtestutil.DefaultConsensusParams |
| 86 | + cp.Block.MaxGas = int64(blockMaxGas) |
| 87 | + cp.Block.MaxBytes = int64(blockMaxBytes) |
| 88 | + _, err = app.InitChain(&types.RequestInitChain{ |
| 89 | + ChainId: chainID, |
| 90 | + Validators: []types.ValidatorUpdate{}, |
| 91 | + ConsensusParams: cp, |
| 92 | + AppStateBytes: stateBytes, |
| 93 | + }) |
| 94 | + if err != nil { |
| 95 | + return fmt.Errorf("failed to InitChain: %w", err) |
| 96 | + } |
| 97 | + |
| 98 | + // commit genesis changes |
| 99 | + _, err = app.FinalizeBlock(&types.RequestFinalizeBlock{ |
| 100 | + Height: 1, |
| 101 | + NextValidatorsHash: vals.Hash(), |
| 102 | + }) |
| 103 | + if err != nil { |
| 104 | + return fmt.Errorf("failed to finalize genesis block: %w", err) |
| 105 | + } |
| 106 | + |
| 107 | + blocks := make([][][]byte, 0, numBlocksToRun) |
| 108 | + for range numBlocksToRun { |
| 109 | + block := make([][]byte, 0, numBlocksToRun) |
| 110 | + for range numTxsPerBlock { |
| 111 | + tx := generateTx() |
| 112 | + block = append(block, tx) |
| 113 | + } |
| 114 | + blocks = append(blocks, block) |
| 115 | + } |
| 116 | + |
| 117 | + elapsed, err := runBlocks(blocks, app, vals.Proposer.Address, verifyTxs) |
| 118 | + if err != nil { |
| 119 | + return fmt.Errorf("failed to run blocks: %w", err) |
| 120 | + } |
| 121 | + |
| 122 | + cmd.Printf("Finished %d blocks in %s\n", numBlocksToRun, elapsed) |
| 123 | + numTxs := numBlocksToRun * numTxsPerBlock |
| 124 | + tps := float64(numTxs) / elapsed.Seconds() |
| 125 | + cmd.Printf("TPS: %f", tps) |
| 126 | + |
| 127 | + return nil |
| 128 | + }, |
| 129 | + } |
| 130 | + cmd.Flags().IntVar(&numAccounts, "accounts", numAccounts, "number of accounts") |
| 131 | + cmd.Flags().IntVar(&numTxsPerBlock, "txs", numTxsPerBlock, "number of txs") |
| 132 | + cmd.Flags().IntVar(&numBlocksToRun, "blocks", numBlocksToRun, "number of blocks") |
| 133 | + cmd.Flags().BoolVar(&verifyTxs, "verify-txs", verifyTxs, "verify txs passed. this will loop over all tx results and ensure the code == 0.") |
| 134 | + cmd.Flags().IntVar(&blockMaxGas, "block-max-gas", blockMaxGas, "block max gas") |
| 135 | + cmd.Flags().IntVar(&blockMaxBytes, "block-max-bytes", blockMaxBytes, "block max bytes") |
| 136 | + return cmd |
| 137 | +} |
| 138 | + |
| 139 | +func runBlocks(blocks [][][]byte, app servertypes.ABCI, proposer []byte, verify bool) (time.Duration, error) { |
| 140 | + start := time.Now() |
| 141 | + height := int64(1) |
| 142 | + for blockNum, txs := range blocks { |
| 143 | + res, err := app.FinalizeBlock(&types.RequestFinalizeBlock{ |
| 144 | + Height: height, |
| 145 | + Txs: txs, |
| 146 | + Time: time.Now(), |
| 147 | + ProposerAddress: proposer, |
| 148 | + }) |
| 149 | + if err != nil { |
| 150 | + return 0, fmt.Errorf("failed to finalize block #%d: %w", blockNum, err) |
| 151 | + } |
| 152 | + if verify { |
| 153 | + for _, result := range res.TxResults { |
| 154 | + if result.Code != 0 { |
| 155 | + return 0, fmt.Errorf("tx failed in block %d: code=%d codespace=%s", blockNum, result.Code, result.Codespace) |
| 156 | + } |
| 157 | + } |
| 158 | + } |
| 159 | + _, err = app.Commit() |
| 160 | + if err != nil { |
| 161 | + return 0, fmt.Errorf("failed to commit block #%d: %w", blockNum, err) |
| 162 | + } |
| 163 | + height++ |
| 164 | + } |
| 165 | + end := time.Since(start) |
| 166 | + return end, nil |
| 167 | +} |
0 commit comments