From 3820c52e587ad649c62657ab47ac25ae5cf1c78a Mon Sep 17 00:00:00 2001 From: Jesse Lee Date: Fri, 30 Jun 2023 17:39:37 -0400 Subject: [PATCH] Jesse/loadtest legacy mode (#92) * default to dynamic tx with legacy flag as option * uncomment nonce * suggest gas tip cap and calculate fee cap * use london signer * remove test line * only legacy mode control force gas price * pass in legacy flag to wisely force gas prices * remove redundant option configure call & refractor * remove some redundant arguments * clean up * use transactOpts function to determine signer * remove header stuff * just use transact opts * push * minor fix: legacy mode and priority gas fee * revert fuzz * variable name change for lint * remove error catch * gas tip price estimation only for non legacy * lint :) * hello * swap again * real gucci * real fix * feat: adding tx type to monitor * Jesse/loadtest chain id query (#95) * auto determine chain ID * chain id 0 * chain id command * Update cmd/loadtest/loadtest.go Co-authored-by: John Hilliard * fix: minor duplicate log messages --------- Co-authored-by: John Hilliard * last changes for logic * gas price logic * remove empty if statement * 1559 aware monitor variables (#97) * 1559 aware monitor variables * put back * add to struct --------- Co-authored-by: John Hilliard --- cmd/loadtest/loadtest.go | 110 ++++++++++++++++++++++++++++++++------- metrics/metrics.go | 3 ++ rpctypes/rpctypes.go | 18 +++++++ 3 files changed, 112 insertions(+), 19 deletions(-) diff --git a/cmd/loadtest/loadtest.go b/cmd/loadtest/loadtest.go index 250a6c0a..cfe4d68e 100644 --- a/cmd/loadtest/loadtest.go +++ b/cmd/loadtest/loadtest.go @@ -21,6 +21,7 @@ import ( "crypto/ecdsa" "encoding/hex" "encoding/json" + "errors" "fmt" "io" "math" @@ -153,8 +154,6 @@ var LoadtestCmd = &cobra.Command{ Short: "A simple script for quickly running a load test", Long: `Loadtest gives us a simple way to run a generic load test against an eth/EVM style json RPC endpoint`, RunE: func(cmd *cobra.Command, args []string) error { - log.Debug().Msg("Starting Loadtest") - err := runLoadTest(cmd.Context()) if err != nil { return err @@ -236,16 +235,20 @@ type ( ForceContractDeploy *bool ForceGasLimit *uint64 ForceGasPrice *uint64 + ForcePriorityGasPrice *uint64 ShouldProduceSummary *bool SummaryOutputMode *string + LegacyTransactionMode *bool // Computed - CurrentGas *big.Int - CurrentNonce *uint64 - ECDSAPrivateKey *ecdsa.PrivateKey - FromETHAddress *ethcommon.Address - ToETHAddress *ethcommon.Address - SendAmount *big.Int + CurrentGas *big.Int + CurrentGasTipCap *big.Int + CurrentNonce *uint64 + ECDSAPrivateKey *ecdsa.PrivateKey + FromETHAddress *ethcommon.Address + ToETHAddress *ethcommon.Address + SendAmount *big.Int + BaseFee *big.Int ToAvailAddress *gstypes.MultiAddress FromAvailAddress *gssignature.KeyringPair @@ -268,7 +271,7 @@ func init() { // extended parameters ltp.PrivateKey = LoadtestCmd.PersistentFlags().String("private-key", codeQualityPrivateKey, "The hex encoded private key that we'll use to sending transactions") - ltp.ChainID = LoadtestCmd.PersistentFlags().Uint64("chain-id", 1256, "The chain id for the transactions that we're going to send") + ltp.ChainID = LoadtestCmd.PersistentFlags().Uint64("chain-id", 0, "The chain id for the transactions that we're going to send") ltp.ToAddress = LoadtestCmd.PersistentFlags().String("to-address", "0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", "The address that we're going to send to") ltp.ToRandom = LoadtestCmd.PersistentFlags().Bool("to-random", false, "When doing a transfer test, should we send to random addresses rather than DEADBEEFx5") ltp.HexSendAmount = LoadtestCmd.PersistentFlags().String("send-amount", "0x38D7EA4C68000", "The amount of wei that we'll send every transaction") @@ -302,9 +305,11 @@ r - random modes ltp.ForceContractDeploy = LoadtestCmd.PersistentFlags().Bool("force-contract-deploy", false, "Some loadtest modes don't require a contract deployment. Set this flag to true to force contract deployments. This will still respect the --del-address and --il-address flags.") ltp.ForceGasLimit = LoadtestCmd.PersistentFlags().Uint64("gas-limit", 0, "In environments where the gas limit can't be computed on the fly, we can specify it manually") ltp.ForceGasPrice = LoadtestCmd.PersistentFlags().Uint64("gas-price", 0, "In environments where the gas price can't be estimated, we can specify it manually") + ltp.ForcePriorityGasPrice = LoadtestCmd.PersistentFlags().Uint64("priority-gas-price", 0, "Specify Gas Tip Price in the case of EIP-1559") ltp.ShouldProduceSummary = LoadtestCmd.PersistentFlags().Bool("summarize", false, "Should we produce an execution summary after the load test has finished. If you're running a large loadtest, this can take a long time") ltp.BatchSize = LoadtestCmd.PersistentFlags().Uint64("batch-size", 999, "Number of batches to perform at a time for receipt fetching. Default is 999 requests at a time.") ltp.SummaryOutputMode = LoadtestCmd.PersistentFlags().String("output-mode", "text", "Format mode for summary output (json | text)") + ltp.LegacyTransactionMode = LoadtestCmd.PersistentFlags().Bool("legacy", false, "Send a legacy transaction instead of an EIP1559 transaction.") inputLoadTestParams = *ltp // TODO batch size @@ -321,6 +326,16 @@ func initializeLoadTestParams(ctx context.Context, c *ethclient.Client) error { } log.Trace().Interface("gasprice", gas).Msg("Retreived current gas price") + if !*inputLoadTestParams.LegacyTransactionMode { + gasTipCap, _err := c.SuggestGasTipCap(ctx) + if _err != nil { + log.Error().Err(_err).Msg("Unable to retrieve gas tip cap") + return _err + } + log.Trace().Interface("gastipcap", gasTipCap).Msg("Retreived current gas tip cap") + inputLoadTestParams.CurrentGasTipCap = gasTipCap + } + privateKey, err := ethcrypto.HexToECDSA(*inputLoadTestParams.PrivateKey) if err != nil { log.Error().Err(err).Msg("Couldn't process the hex private key") @@ -357,12 +372,37 @@ func initializeLoadTestParams(ctx context.Context, c *ethclient.Client) error { return err } + header, err := c.HeaderByNumber(ctx, nil) + if err != nil { + log.Error().Err(err).Msg("Unable to get header") + return err + } + + chainID, err := c.ChainID(ctx) + if err != nil { + log.Error().Err(err).Msg("Unable to fetch chain ID") + return err + } + log.Trace().Uint64("chainID", chainID.Uint64()).Msg("Detected Chain ID") + + if *inputLoadTestParams.LegacyTransactionMode && *inputLoadTestParams.ForcePriorityGasPrice > 0 { + log.Warn().Msg("Cannot set priority gas price in legacy mode") + } + if *inputLoadTestParams.ForceGasPrice < *inputLoadTestParams.ForcePriorityGasPrice { + log.Error().Msg("Max priority fee per gas higher than max fee per gas") + return errors.New("max priority fee per gas higher than max fee per gas") + } + inputLoadTestParams.ToETHAddress = &toAddr inputLoadTestParams.SendAmount = amt inputLoadTestParams.CurrentGas = gas inputLoadTestParams.CurrentNonce = &nonce inputLoadTestParams.ECDSAPrivateKey = privateKey inputLoadTestParams.FromETHAddress = ðAddress + if *inputLoadTestParams.ChainID == 0 { + *inputLoadTestParams.ChainID = chainID.Uint64() + } + inputLoadTestParams.BaseFee = header.BaseFee rand.Seed(*inputLoadTestParams.Seed) @@ -427,7 +467,6 @@ func runLoadTest(ctx context.Context) error { } } else { - log.Info().Msg("Starting Load Test") loopFunc = func() error { err = initializeLoadTestParams(ctx, ec) if err != nil { @@ -667,8 +706,6 @@ func mainLoop(ctx context.Context, c *ethclient.Client, rpc *ethrpc.Client) erro } tops.Nonce = new(big.Int).SetUint64(currentNonce) - tops = configureTransactOpts(tops) - tops.GasLimit = 10000000 _, err = erc20Contract.Mint(tops, metrics.UnitMegaether) if err != nil { @@ -720,8 +757,6 @@ func mainLoop(ctx context.Context, c *ethclient.Client, rpc *ethrpc.Client) erro } tops.Nonce = new(big.Int).SetUint64(currentNonce) - tops = configureTransactOpts(tops) - tops.GasLimit = 10000000 err = blockUntilSuccessful(ctx, c, func() error { _, err = erc721Contract.MintBatch(tops, *ltp.FromETHAddress, new(big.Int).SetUint64(1)) @@ -952,8 +987,6 @@ func blockUntilSuccessful(ctx context.Context, c *ethclient.Client, f func() err func loadtestTransaction(ctx context.Context, c *ethclient.Client, nonce uint64) (t1 time.Time, t2 time.Time, err error) { ltp := inputLoadTestParams - gasPrice := ltp.CurrentGas - to := ltp.ToETHAddress if *ltp.ToRandom { to = getRandomAddress() @@ -963,9 +996,34 @@ func loadtestTransaction(ctx context.Context, c *ethclient.Client, nonce uint64) chainID := new(big.Int).SetUint64(*ltp.ChainID) privateKey := ltp.ECDSAPrivateKey - gasLimit := uint64(21000) - tx := ethtypes.NewTransaction(nonce, *to, amount, gasLimit, gasPrice, nil) - stx, err := ethtypes.SignTx(tx, ethtypes.NewEIP155Signer(chainID), privateKey) + tops, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) + if err != nil { + log.Error().Err(err).Msg("Unable create transaction signer") + return + } + tops.GasLimit = uint64(21000) + tops = configureTransactOpts(tops) + + var tx *ethtypes.Transaction + if *ltp.LegacyTransactionMode { + tx = ethtypes.NewTransaction(nonce, *to, amount, tops.GasLimit, tops.GasPrice, nil) + } else { + gasTipCap := tops.GasTipCap + gasFeeCap := new(big.Int).Add(gasTipCap, ltp.BaseFee) + dynamicFeeTx := ðtypes.DynamicFeeTx{ + ChainID: chainID, + Nonce: nonce, + To: to, + Gas: tops.GasLimit, + GasFeeCap: gasFeeCap, + GasTipCap: gasTipCap, + Data: nil, + Value: amount, + } + tx = ethtypes.NewTx(dynamicFeeTx) + } + + stx, err := tops.Signer(*ltp.FromETHAddress, tx) if err != nil { log.Error().Err(err).Msg("Unable to sign transaction") return @@ -1490,6 +1548,20 @@ func configureTransactOpts(tops *bind.TransactOpts) *bind.TransactOpts { ltp := inputLoadTestParams if ltp.ForceGasPrice != nil && *ltp.ForceGasPrice != 0 { tops.GasPrice = big.NewInt(0).SetUint64(*ltp.ForceGasPrice) + } else { + tops.GasPrice = ltp.CurrentGas + } + if !*ltp.LegacyTransactionMode { + if ltp.ForceGasPrice != nil && *ltp.ForceGasPrice != 0 { + tops.GasPrice = big.NewInt(0).SetUint64(*ltp.ForceGasPrice) + } else { + tops.GasPrice = big.NewInt(0).Add(ltp.BaseFee, ltp.CurrentGasTipCap) + } + if ltp.ForcePriorityGasPrice != nil && *ltp.ForcePriorityGasPrice != 0 { + tops.GasTipCap = big.NewInt(0).SetUint64(*ltp.ForcePriorityGasPrice) + } else { + tops.GasTipCap = ltp.CurrentGasTipCap + } } if ltp.ForceGasLimit != nil && *ltp.ForceGasLimit != 0 { tops.GasLimit = *ltp.ForceGasLimit diff --git a/metrics/metrics.go b/metrics/metrics.go index 86cbe682..b8cd5d8a 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -265,7 +265,10 @@ func GetSimpleTxFields(tx rpctypes.PolyTransaction, chainID, baseFee *big.Int) [ fields = append(fields, fmt.Sprintf("Value: %s", tx.Value())) fields = append(fields, fmt.Sprintf("Gas Limit: %d", tx.Gas())) fields = append(fields, fmt.Sprintf("Gas Price: %s", tx.GasPrice())) + fields = append(fields, fmt.Sprintf("Gas Tip: %d", tx.MaxPriorityFeePerGas())) + fields = append(fields, fmt.Sprintf("Gas Fee: %d", tx.MaxFeePerGas())) fields = append(fields, fmt.Sprintf("Nonce: %d", tx.Nonce())) + fields = append(fields, fmt.Sprintf("Type: %d", tx.Type())) fields = append(fields, fmt.Sprintf("Data Len: %d", len(tx.Data()))) fields = append(fields, fmt.Sprintf("Data: %s", hex.EncodeToString(tx.Data()))) diff --git a/rpctypes/rpctypes.go b/rpctypes/rpctypes.go index 6bd20f15..efa7a8aa 100644 --- a/rpctypes/rpctypes.go +++ b/rpctypes/rpctypes.go @@ -38,6 +38,12 @@ type ( // gasPrice: QUANTITY - gas price provided by the sender in Wei. GasPrice RawQuantityResponse `json:"gasPrice"` + // gas: QUANTITY - gas provided by the sender. + MaxPriorityFeePerGas RawQuantityResponse `json:"maxPriorityFeePerGas"` + + // gas: QUANTITY - gas provided by the sender. + MaxFeePerGas RawQuantityResponse `json:"maxFeePerGas"` + // hash: DATA, 32 Bytes - hash of the transaction. Hash RawData32Response `json:"hash"` @@ -215,6 +221,9 @@ type ( Nonce() uint64 String() string MarshalJSON() ([]byte, error) + Type() uint64 + MaxPriorityFeePerGas() uint64 + MaxFeePerGas() uint64 V() *big.Int R() *big.Int S() *big.Int @@ -342,9 +351,18 @@ func (i *implPolyTransaction) GasPrice() *big.Int { func (i *implPolyTransaction) Gas() uint64 { return i.inner.Gas.ToUint64() } +func (i *implPolyTransaction) MaxPriorityFeePerGas() uint64 { + return i.inner.MaxPriorityFeePerGas.ToUint64() +} +func (i *implPolyTransaction) MaxFeePerGas() uint64 { + return i.inner.MaxFeePerGas.ToUint64() +} func (i *implPolyTransaction) Nonce() uint64 { return i.inner.Nonce.ToUint64() } +func (i *implPolyTransaction) Type() uint64 { + return i.inner.Type.ToUint64() +} func (i *implPolyTransaction) Value() *big.Int { return i.inner.Value.ToBigInt() }