Skip to content

Commit

Permalink
Load test runner implementation (#194)
Browse files Browse the repository at this point in the history
* Load test runner implementation

* UTs build fix

* Lint fix

* load-test command

* Better UX

* add ZexCoin to test artifacts

* add erc20 test name log

* gas price increase

* collect all errors for tx sending and just log them

* stale sequence check (temp fix)

* more gasPrice increase

* gather receipt errors and print them

* lint fix

* feeData

* Lint fix

* fix

* ERC721 support

* results to json file

* lint fix

* remove comment

* receipts gather in separate routines

* remove receipts timeout from fund and deploy functions

* ticker timeout and gasPrice increase

* lock in gathering receipts

* increase maxDemotionsNum

* waitForTxPool flag, and gathering results in parallel

* lint fix

* fix ut

* Go mod tidy

* comments fix

* lint fix

* more statistics

* small fix

* small fix

---------

Co-authored-by: Stefan Negovanović <stefan@ethernal.tech>
  • Loading branch information
goran-ethernal and Stefan-Ethernal authored Apr 15, 2024
1 parent 5d6017f commit 162382e
Show file tree
Hide file tree
Showing 25 changed files with 2,768 additions and 24 deletions.
23 changes: 21 additions & 2 deletions command/bridge/fund/fund.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/0xPolygon/polygon-edge/command/bridge/helper"
cmdHelper "github.com/0xPolygon/polygon-edge/command/helper"
polybftsecrets "github.com/0xPolygon/polygon-edge/command/secrets/init"
"github.com/0xPolygon/polygon-edge/jsonrpc"
"github.com/0xPolygon/polygon-edge/txrelayer"
"github.com/0xPolygon/polygon-edge/types"
)
Expand Down Expand Up @@ -78,8 +79,18 @@ func runCommand(cmd *cobra.Command, _ []string) {
outputter := command.InitializeOutputter(cmd)
defer outputter.WriteOutput()

txRelayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(params.jsonRPCAddress),
txrelayer.WithReceiptsTimeout(params.txTimeout))
client, err := jsonrpc.NewEthClient(params.jsonRPCAddress)
if err != nil {
outputter.SetError(fmt.Errorf("failed to initialize eth client: %w", err))

return
}

txRelayer, err := txrelayer.NewTxRelayer(
txrelayer.WithClient(client),
txrelayer.WithReceiptsTimeout(params.txTimeout),
txrelayer.WithoutNonceGet(),
)
if err != nil {
outputter.SetError(fmt.Errorf("failed to initialize tx relayer: %w", err))

Expand All @@ -93,6 +104,13 @@ func runCommand(cmd *cobra.Command, _ []string) {
return
}

senderNonce, err := client.GetNonce(deployerKey.Address(), jsonrpc.PendingBlockNumberOrHash)
if err != nil {
outputter.SetError(fmt.Errorf("failed to get deployer nonce: %w", err))

return
}

results := make([]command.CommandResult, len(params.addresses))
g, ctx := errgroup.WithContext(cmd.Context())

Expand All @@ -107,6 +125,7 @@ func runCommand(cmd *cobra.Command, _ []string) {
default:
fundAddr := params.addresses[i]
txn := helper.CreateTransaction(types.ZeroAddress, &fundAddr, nil, params.amountValues[i], true)
txn.SetNonce(senderNonce + uint64(i))

var (
receipt *ethgo.Receipt
Expand Down
135 changes: 135 additions & 0 deletions command/loadtest/load_test_run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package loadtest

import (
"time"

"github.com/0xPolygon/polygon-edge/command"
"github.com/0xPolygon/polygon-edge/command/helper"
"github.com/0xPolygon/polygon-edge/loadtest/runner"
"github.com/spf13/cobra"
)

var (
params loadTestParams
)

func GetCommand() *cobra.Command {
loadTestCmd := &cobra.Command{
Use: "load-test",
Short: "Runs a load test on a specified network",
PreRunE: preRunCommand,
Run: runCommand,
}

helper.RegisterJSONRPCFlag(loadTestCmd)

setFlags(loadTestCmd)

return loadTestCmd
}

func preRunCommand(cmd *cobra.Command, _ []string) error {
params.jsonRPCAddress = helper.GetJSONRPCAddress(cmd)

return params.validateFlags()
}

func setFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(
&params.mnemonic,
mnemonicFlag,
"",
"the mnemonic used to generate and fund virtual users",
)

cmd.Flags().StringVar(
&params.loadTestType,
loadTestTypeFlag,
"eoa",
"the type of load test to run (supported types: eoa, erc20, erc721)",
)

cmd.Flags().StringVar(
&params.loadTestName,
loadTestNameFlag,
"load test",
"the name of the load test",
)

cmd.Flags().IntVar(
&params.vus,
vusFlag,
1,
"the number of virtual users",
)

cmd.Flags().IntVar(
&params.txsPerUser,
txsPerUserFlag,
1,
"the number of transactions per virtual user",
)

cmd.Flags().BoolVar(
&params.dynamicTxs,
dynamicTxsFlag,
false,
"indicates whether the load test should generate dynamic transactions",
)

cmd.Flags().DurationVar(
&params.receiptsTimeout,
receiptsTimeoutFlag,
30*time.Second,
"the timeout for waiting for transaction receipts",
)

cmd.Flags().DurationVar(
&params.txPoolTimeout,
txPoolTimeoutFlag,
10*time.Minute,
"the timeout for waiting for the transaction pool to empty",
)

cmd.Flags().BoolVar(
&params.toJSON,
saveToJSONFlag,
false,
"saves results to JSON file",
)

cmd.Flags().BoolVar(
&params.waitForTxPoolToEmpty,
waitForTxPoolToEmptyFlag,
false,
"waits for tx pool to empty before collecting results",
)

_ = cmd.MarkFlagRequired(mnemonicFlag)
_ = cmd.MarkFlagRequired(loadTestTypeFlag)
}

func runCommand(cmd *cobra.Command, _ []string) {
outputter := command.InitializeOutputter(cmd)
defer outputter.WriteOutput()

loadTestRunner := &runner.LoadTestRunner{}

err := loadTestRunner.Run(runner.LoadTestConfig{
Mnemonnic: params.mnemonic,
LoadTestType: params.loadTestType,
LoadTestName: params.loadTestName,
JSONRPCUrl: params.jsonRPCAddress,
ReceiptsTimeout: params.receiptsTimeout,
TxPoolTimeout: params.txPoolTimeout,
VUs: params.vus,
TxsPerUser: params.txsPerUser,
DynamicTxs: params.dynamicTxs,
ResultsToJSON: params.toJSON,
WaitForTxPoolToEmpty: params.waitForTxPoolToEmpty,
})

if err != nil {
outputter.SetError(err)
}
}
73 changes: 73 additions & 0 deletions command/loadtest/params.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package loadtest

import (
"errors"
"time"

"github.com/0xPolygon/polygon-edge/loadtest/runner"
)

const (
mnemonicFlag = "mnemonic"
loadTestTypeFlag = "type"
loadTestNameFlag = "name"

receiptsTimeoutFlag = "receipts-timeout"
txPoolTimeoutFlag = "txpool-timeout"

vusFlag = "vus"
txsPerUserFlag = "txs-per-user"
dynamicTxsFlag = "dynamic"

saveToJSONFlag = "to-json"
waitForTxPoolToEmptyFlag = "wait-txpool"
)

var (
errNoMnemonicProvided = errors.New("no mnemonic provided")
errNoLoadTestTypeProvided = errors.New("no load test type provided")
errUnsupportedLoadTestType = errors.New("unsupported load test type")
errInvalidVUs = errors.New("vus must be greater than 0")
errInvalidTxsPerUser = errors.New("txs-per-user must be greater than 0")
)

type loadTestParams struct {
mnemonic string
loadTestType string
loadTestName string
jsonRPCAddress string

receiptsTimeout time.Duration
txPoolTimeout time.Duration

vus int
txsPerUser int

dynamicTxs bool
toJSON bool
waitForTxPoolToEmpty bool
}

func (ltp *loadTestParams) validateFlags() error {
if ltp.mnemonic == "" {
return errNoMnemonicProvided
}

if ltp.loadTestType == "" {
return errNoLoadTestTypeProvided
}

if !runner.IsLoadTestSupported(ltp.loadTestType) {
return errUnsupportedLoadTestType
}

if ltp.vus < 1 {
return errInvalidVUs
}

if ltp.txsPerUser < 1 {
return errInvalidTxsPerUser
}

return nil
}
2 changes: 2 additions & 0 deletions command/root/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/0xPolygon/polygon-edge/command/bridge"
"github.com/0xPolygon/polygon-edge/command/genesis"
"github.com/0xPolygon/polygon-edge/command/helper"
"github.com/0xPolygon/polygon-edge/command/loadtest"
"github.com/0xPolygon/polygon-edge/command/mint"
"github.com/0xPolygon/polygon-edge/command/monitor"
"github.com/0xPolygon/polygon-edge/command/peers"
Expand Down Expand Up @@ -57,6 +58,7 @@ func (rc *RootCommand) registerSubCommands() {
regenesis.GetCommand(),
mint.GetCommand(),
validator.GetCommand(),
loadtest.GetCommand(),
)
}

Expand Down
4 changes: 4 additions & 0 deletions consensus/polybft/checkpoint_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,10 @@ func (d *dummyTxRelayer) Client() *jsonrpc.EthClient {
return nil
}

func (d *dummyTxRelayer) GetTxnHashes() []types.Hash {
return nil
}

func getBlockNumberCheckpointSubmitInput(t *testing.T, input []byte) uint64 {
t.Helper()

Expand Down
14 changes: 14 additions & 0 deletions consensus/polybft/contractsapi/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ var (
TestRewardToken *contracts.Artifact
Wrapper *contracts.Artifact
NumberPersister *contracts.Artifact
ZexCoinERC20 *contracts.Artifact
ZexNFT *contracts.Artifact

contractArtifacts map[string]*contracts.Artifact
)
Expand Down Expand Up @@ -330,6 +332,16 @@ func init() {
log.Fatal(err)
}

ZexCoinERC20, err = contracts.DecodeArtifact(readTestContractContent("ZexCoinERC20.json"))
if err != nil {
log.Fatal(err)
}

ZexNFT, err = contracts.DecodeArtifact(readTestContractContent("ZexNFT.json"))
if err != nil {
log.Fatal(err)
}

contractArtifacts = map[string]*contracts.Artifact{
"CheckpointManager": CheckpointManager,
"ExitHelper": ExitHelper,
Expand Down Expand Up @@ -381,6 +393,8 @@ func init() {
"RootERC20": RootERC20,
"TestSimple": TestSimple,
"TestRewardToken": TestRewardToken,
"ZexCoinERC20": ZexCoinERC20,
"ZexNFT": ZexNFT,
}
}

Expand Down
Loading

0 comments on commit 162382e

Please sign in to comment.