Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions simapp/simd/cmd/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func initRootCmd(
confixcmd.ConfigCommand(),
pruning.Cmd(newApp, simapp.DefaultNodeHome),
snapshot.Cmd(newApp),
NewBankSpeedTest(),
)

server.AddCommandsWithStartCmdOptions(rootCmd, simapp.DefaultNodeHome, newApp, appExport, server.StartCmdOptions{
Expand Down
105 changes: 105 additions & 0 deletions simapp/simd/cmd/speedtest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package cmd

import (
"math/rand"
"os"
"time"

dbm "github.com/cosmos/cosmos-db"
"github.com/spf13/cobra"

"cosmossdk.io/log"
"cosmossdk.io/simapp"

"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
"github.com/cosmos/cosmos-sdk/tools/speedtest"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)

var r = rand.New(rand.NewSource(time.Now().UnixNano()))

func NewBankSpeedTest() *cobra.Command {
dir, err := os.MkdirTemp("", "bankspeedtest-*")
if err != nil {
panic(err)
}
db, err := dbm.NewDB("app", dbm.PebbleDBBackend, dir)
if err != nil {
panic(err)
}
chainID := "foo"
app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, simtestutil.NewAppOptionsWithFlagHome(dir), baseapp.SetChainID(chainID))
gen := generator{
app: app,
accounts: make([]accountInfo, 0),
}
cmd := speedtest.NewCmd(gen.createAccount, gen.generateTx, app, app.AppCodec(), app.DefaultGenesis(), chainID)
cmd.PostRunE = func(_ *cobra.Command, _ []string) error {
return os.RemoveAll(dir)
}
return cmd
}

type generator struct {
app *simapp.SimApp
accounts []accountInfo
}

type accountInfo struct {
privKey cryptotypes.PrivKey
address sdk.AccAddress
accNum uint64
seqNum uint64
}

func (g *generator) createAccount() (*authtypes.BaseAccount, sdk.Coins) {
privKey := secp256k1.GenPrivKey()
addr := sdk.AccAddress(privKey.PubKey().Address())
accNum := len(g.accounts)
baseAcc := authtypes.NewBaseAccount(addr, privKey.PubKey(), uint64(accNum), 0)

g.accounts = append(g.accounts, accountInfo{
privKey: privKey,
address: addr,
accNum: uint64(accNum),
seqNum: 0,
})

return baseAcc, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 1_000_000_000))
}

func (g *generator) generateTx() []byte {
senderIdx := r.Intn(len(g.accounts))
recipientIdx := (senderIdx + 1 + r.Intn(len(g.accounts)-1)) % len(g.accounts)
sender := g.accounts[senderIdx]
recipient := g.accounts[recipientIdx]
sendAmount := sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 1))
msg := banktypes.NewMsgSend(sender.address, recipient.address, sendAmount)
txConfig := g.app.TxConfig()
// Build and sign transaction
tx, err := simtestutil.GenSignedMockTx(
r,
txConfig,
[]sdk.Msg{msg},
sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)),
simtestutil.DefaultGenTxGas,
g.app.ChainID(),
[]uint64{sender.accNum},
[]uint64{sender.seqNum},
sender.privKey,
)
if err != nil {
panic(err)
}
txBytes, err := txConfig.TxEncoder()(tx)
if err != nil {
panic(err)
}
g.accounts[senderIdx].seqNum++
return txBytes
}
167 changes: 167 additions & 0 deletions tools/speedtest/speedtest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package speedtest

import (
"encoding/json"
"fmt"
"math"
"time"

"github.com/cometbft/cometbft/abci/types"
cmtjson "github.com/cometbft/cometbft/libs/json"
"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/codec"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)

type AccountCreator func() (*authtypes.BaseAccount, sdk.Coins)

type GenerateTx func() []byte

var (
numAccounts = 10_000
numTxsPerBlock = 4_000
numBlocksToRun = 100
blockMaxGas = math.MaxInt64
blockMaxBytes = math.MaxInt64
verifyTxs = false
)

// NewCmd returns a command that will run an execution test on your application.
// Balances and accounts are automatically added to the chain's state via AccountCreator.
func NewCmd(
createAccount AccountCreator,
generateTx GenerateTx,
app servertypes.ABCI,
cdc codec.Codec,
genState map[string]json.RawMessage,
chainID string,
) *cobra.Command {
cmd := &cobra.Command{
Use: "speedtest",
Short: "execution speedtest",
Long: "speedtest is a tool for measuring raw execution TPS of your application",
Example: "speedtest --accounts 20000 --txs 2000 --blocks 10 --block-max-gas 1000000000 --block-max-bytes 1000000000 --verify-txs",
RunE: func(cmd *cobra.Command, args []string) error {
accounts := make([]simtestutil.GenesisAccount, 0, numAccounts)
balances := make([]banktypes.Balance, 0, numAccounts)
for range numAccounts {
account, balance := createAccount()
genesisAcc := simtestutil.GenesisAccount{
GenesisAccount: account,
Coins: balance,
}
accounts = append(accounts, genesisAcc)
balances = append(balances, banktypes.Balance{
Address: account.Address,
Coins: balance,
})
}

vals, err := simtestutil.CreateRandomValidatorSet()
if err != nil {
return err
}

genAccs := make([]authtypes.GenesisAccount, 0, len(accounts))
for _, acc := range accounts {
genAccs = append(genAccs, acc.GenesisAccount)
}
genesisState, err := simtestutil.GenesisStateWithValSet(cdc, genState, vals, genAccs, balances...)
if err != nil {
return err
}

// init chain must be called to stop deliverState from being nil
stateBytes, err := cmtjson.MarshalIndent(genesisState, "", " ")
if err != nil {
return err
}

cp := simtestutil.DefaultConsensusParams
cp.Block.MaxGas = int64(blockMaxGas)
cp.Block.MaxBytes = int64(blockMaxBytes)
_, err = app.InitChain(&types.RequestInitChain{
ChainId: chainID,
Validators: []types.ValidatorUpdate{},
ConsensusParams: cp,
AppStateBytes: stateBytes,
})
if err != nil {
return fmt.Errorf("failed to InitChain: %w", err)
}

// commit genesis changes
_, err = app.FinalizeBlock(&types.RequestFinalizeBlock{
Height: 1,
NextValidatorsHash: vals.Hash(),
})
if err != nil {
return fmt.Errorf("failed to finalize genesis block: %w", err)
}

blocks := make([][][]byte, 0, numBlocksToRun)
for range numBlocksToRun {
block := make([][]byte, 0, numBlocksToRun)
for range numTxsPerBlock {
tx := generateTx()
block = append(block, tx)
}
blocks = append(blocks, block)
}

elapsed, err := runBlocks(blocks, app, vals.Proposer.Address, verifyTxs)
if err != nil {
return fmt.Errorf("failed to run blocks: %w", err)
}

cmd.Printf("Finished %d blocks in %s\n", numBlocksToRun, elapsed)
numTxs := numBlocksToRun * numTxsPerBlock
tps := float64(numTxs) / elapsed.Seconds()
cmd.Printf("TPS: %f", tps)

return nil
},
}
cmd.Flags().IntVar(&numAccounts, "accounts", numAccounts, "number of accounts")
cmd.Flags().IntVar(&numTxsPerBlock, "txs", numTxsPerBlock, "number of txs")
cmd.Flags().IntVar(&numBlocksToRun, "blocks", numBlocksToRun, "number of blocks")
cmd.Flags().BoolVar(&verifyTxs, "verify-txs", verifyTxs, "verify txs passed. this will loop over all tx results and ensure the code == 0.")
cmd.Flags().IntVar(&blockMaxGas, "block-max-gas", blockMaxGas, "block max gas")
cmd.Flags().IntVar(&blockMaxBytes, "block-max-bytes", blockMaxBytes, "block max bytes")
return cmd
}

func runBlocks(blocks [][][]byte, app servertypes.ABCI, proposer []byte, verify bool) (time.Duration, error) {
start := time.Now()
height := int64(1)
for blockNum, txs := range blocks {
res, err := app.FinalizeBlock(&types.RequestFinalizeBlock{
Height: height,
Txs: txs,
Time: time.Now(),
ProposerAddress: proposer,
})
if err != nil {
return 0, fmt.Errorf("failed to finalize block #%d: %w", blockNum, err)
}
if verify {
for _, result := range res.TxResults {
if result.Code != 0 {
return 0, fmt.Errorf("tx failed in block %d: code=%d codespace=%s", blockNum, result.Code, result.Codespace)
}
}
}
_, err = app.Commit()
if err != nil {
return 0, fmt.Errorf("failed to commit block #%d: %w", blockNum, err)
}
height++
}
end := time.Since(start)
return end, nil
}
Loading