Skip to content

Commit a99735b

Browse files
feat(tools): speedtest (cosmos#25555)
2 parents 325046f + 94aebcd commit a99735b

File tree

3 files changed

+273
-0
lines changed

3 files changed

+273
-0
lines changed

simapp/simd/cmd/commands.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ func initRootCmd(
113113
confixcmd.ConfigCommand(),
114114
pruning.Cmd(newApp, simapp.DefaultNodeHome),
115115
snapshot.Cmd(newApp),
116+
NewBankSpeedTest(),
116117
)
117118

118119
server.AddCommandsWithStartCmdOptions(rootCmd, simapp.DefaultNodeHome, newApp, appExport, server.StartCmdOptions{

simapp/simd/cmd/speedtest.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package cmd
2+
3+
import (
4+
"math/rand"
5+
"os"
6+
"time"
7+
8+
dbm "github.com/cosmos/cosmos-db"
9+
"github.com/spf13/cobra"
10+
11+
"cosmossdk.io/log"
12+
"cosmossdk.io/simapp"
13+
14+
"github.com/cosmos/cosmos-sdk/baseapp"
15+
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
16+
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
17+
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
18+
"github.com/cosmos/cosmos-sdk/tools/speedtest"
19+
sdk "github.com/cosmos/cosmos-sdk/types"
20+
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
21+
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
22+
)
23+
24+
var r = rand.New(rand.NewSource(time.Now().UnixNano()))
25+
26+
func NewBankSpeedTest() *cobra.Command {
27+
dir, err := os.MkdirTemp("", "bankspeedtest-*")
28+
if err != nil {
29+
panic(err)
30+
}
31+
db, err := dbm.NewDB("app", dbm.PebbleDBBackend, dir)
32+
if err != nil {
33+
panic(err)
34+
}
35+
chainID := "foo"
36+
app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, simtestutil.NewAppOptionsWithFlagHome(dir), baseapp.SetChainID(chainID))
37+
gen := generator{
38+
app: app,
39+
accounts: make([]accountInfo, 0),
40+
}
41+
cmd := speedtest.NewCmd(gen.createAccount, gen.generateTx, app, app.AppCodec(), app.DefaultGenesis(), chainID)
42+
cmd.PostRunE = func(_ *cobra.Command, _ []string) error {
43+
return os.RemoveAll(dir)
44+
}
45+
return cmd
46+
}
47+
48+
type generator struct {
49+
app *simapp.SimApp
50+
accounts []accountInfo
51+
}
52+
53+
type accountInfo struct {
54+
privKey cryptotypes.PrivKey
55+
address sdk.AccAddress
56+
accNum uint64
57+
seqNum uint64
58+
}
59+
60+
func (g *generator) createAccount() (*authtypes.BaseAccount, sdk.Coins) {
61+
privKey := secp256k1.GenPrivKey()
62+
addr := sdk.AccAddress(privKey.PubKey().Address())
63+
accNum := len(g.accounts)
64+
baseAcc := authtypes.NewBaseAccount(addr, privKey.PubKey(), uint64(accNum), 0)
65+
66+
g.accounts = append(g.accounts, accountInfo{
67+
privKey: privKey,
68+
address: addr,
69+
accNum: uint64(accNum),
70+
seqNum: 0,
71+
})
72+
73+
return baseAcc, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 1_000_000_000))
74+
}
75+
76+
func (g *generator) generateTx() []byte {
77+
senderIdx := r.Intn(len(g.accounts))
78+
recipientIdx := (senderIdx + 1 + r.Intn(len(g.accounts)-1)) % len(g.accounts)
79+
sender := g.accounts[senderIdx]
80+
recipient := g.accounts[recipientIdx]
81+
sendAmount := sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 1))
82+
msg := banktypes.NewMsgSend(sender.address, recipient.address, sendAmount)
83+
txConfig := g.app.TxConfig()
84+
// Build and sign transaction
85+
tx, err := simtestutil.GenSignedMockTx(
86+
r,
87+
txConfig,
88+
[]sdk.Msg{msg},
89+
sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)),
90+
simtestutil.DefaultGenTxGas,
91+
g.app.ChainID(),
92+
[]uint64{sender.accNum},
93+
[]uint64{sender.seqNum},
94+
sender.privKey,
95+
)
96+
if err != nil {
97+
panic(err)
98+
}
99+
txBytes, err := txConfig.TxEncoder()(tx)
100+
if err != nil {
101+
panic(err)
102+
}
103+
g.accounts[senderIdx].seqNum++
104+
return txBytes
105+
}

tools/speedtest/speedtest.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
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

Comments
 (0)