Skip to content

Commit 8542edb

Browse files
committed
loadtests: add reusable nonces; refactoring account pool usage;
1 parent 40640fe commit 8542edb

File tree

5 files changed

+125
-132
lines changed

5 files changed

+125
-132
lines changed

cmd/loadtest/account.go

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/ecdsa"
66
"fmt"
77
"math/big"
8+
"slices"
89
"sync"
910
"time"
1011

@@ -18,10 +19,11 @@ import (
1819
)
1920

2021
type Account struct {
21-
address common.Address
22-
privateKey *ecdsa.PrivateKey
23-
nonce uint64
24-
funded bool
22+
address common.Address
23+
privateKey *ecdsa.PrivateKey
24+
nonce uint64
25+
funded bool
26+
reusableNonces []uint64
2527
}
2628

2729
func newAccount(client *ethclient.Client, privateKey *ecdsa.PrivateKey) (*Account, error) {
@@ -35,9 +37,11 @@ func newAccount(client *ethclient.Client, privateKey *ecdsa.PrivateKey) (*Accoun
3537
}
3638

3739
return &Account{
38-
privateKey: privateKey,
39-
address: address,
40-
nonce: nonce,
40+
privateKey: privateKey,
41+
address: address,
42+
nonce: nonce,
43+
funded: false,
44+
reusableNonces: make([]uint64, 0),
4145
}, nil
4246
}
4347

@@ -52,9 +56,11 @@ func (a *Account) Nonce() uint64 {
5256
}
5357

5458
type AccountPool struct {
59+
accounts []Account
60+
accountsPositions map[common.Address]int
61+
5562
mu sync.Mutex
5663
client *ethclient.Client
57-
accounts []Account
5864
currentAccountIndex int
5965
fundingPrivateKey *ecdsa.PrivateKey
6066
fundingAmount *big.Int
@@ -91,6 +97,7 @@ func NewAccountPool(ctx context.Context, client *ethclient.Client, fundingPrivat
9197
fundingPrivateKey: fundingPrivateKey,
9298
fundingAmount: fundingAmount,
9399
chainID: chainID,
100+
accountsPositions: make(map[common.Address]int),
94101
}
95102
}
96103

@@ -129,6 +136,28 @@ func (ap *AccountPool) Add(privateKey *ecdsa.PrivateKey) error {
129136
}
130137

131138
ap.accounts = append(ap.accounts, *account)
139+
ap.accountsPositions[account.address] = len(ap.accounts) - 1
140+
return nil
141+
}
142+
143+
func (ap *AccountPool) AddReusableNonce(address common.Address, nonce uint64) error {
144+
ap.mu.Lock()
145+
defer ap.mu.Unlock()
146+
147+
accountPos, found := ap.accountsPositions[address]
148+
if !found {
149+
return fmt.Errorf("account not found in pool")
150+
}
151+
if accountPos >= len(ap.accounts)-1 {
152+
return fmt.Errorf("account position out of bounds")
153+
}
154+
155+
ap.accounts[accountPos].reusableNonces = append(ap.accounts[accountPos].reusableNonces, nonce)
156+
157+
// sort the reusable nonces ascending because we want to use the lowest nonce first
158+
// and we pay the price of sorting only once when adding it
159+
slices.Sort(ap.accounts[accountPos].reusableNonces)
160+
132161
return nil
133162
}
134163

@@ -160,20 +189,16 @@ func (ap *AccountPool) Nonces() map[common.Address]uint64 {
160189
return nonces
161190
}
162191

163-
func (ap *AccountPool) Run(ctx context.Context, f func(account Account) error) error {
192+
func (ap *AccountPool) Next(ctx context.Context) (Account, error) {
164193
account, err := ap.next(ctx)
165194
log.Debug().
166195
Str("address", account.address.Hex()).
167196
Str("nonce", fmt.Sprintf("%d", account.nonce)).
168197
Msg("account returned from pool")
169198
if err != nil {
170-
return err
171-
}
172-
err = f(account)
173-
if err != nil {
174-
return err
199+
return Account{}, err
175200
}
176-
return nil
201+
return account, nil
177202
}
178203

179204
func (ap *AccountPool) next(ctx context.Context) (Account, error) {
@@ -189,7 +214,14 @@ func (ap *AccountPool) next(ctx context.Context) (Account, error) {
189214
return Account{}, err
190215
}
191216
ap.accounts[ap.currentAccountIndex].funded = true
192-
ap.accounts[ap.currentAccountIndex].nonce++
217+
218+
// Check if the account has a reusable nonce
219+
if len(account.reusableNonces) > 0 {
220+
account.nonce = ap.accounts[ap.currentAccountIndex].reusableNonces[0]
221+
ap.accounts[ap.currentAccountIndex].reusableNonces = ap.accounts[ap.currentAccountIndex].reusableNonces[1:]
222+
} else {
223+
ap.accounts[ap.currentAccountIndex].nonce++
224+
}
193225

194226
// move current account index to next account
195227
ap.currentAccountIndex++

cmd/loadtest/app.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ type (
8484
GasPriceMultiplier *float64
8585
SendingAddressCount *uint64
8686
AddressFundingAmount *uint64
87-
FundSendingAddressesOnDemand *bool
87+
PreFundSendingAddresses *bool
8888

8989
// Computed
9090
CurrentGasPrice *big.Int
@@ -247,7 +247,7 @@ func initFlags() {
247247
ltp.BlobFeeCap = LoadtestCmd.Flags().Uint64("blob-fee-cap", 100000, "The blob fee cap, or the maximum blob fee per chunk, in Gwei.")
248248
ltp.SendingAddressCount = LoadtestCmd.Flags().Uint64("sending-address-count", 1, "The number of sending addresses to use. This is useful for avoiding pool account queue.")
249249
ltp.AddressFundingAmount = LoadtestCmd.Flags().Uint64("address-funding-amount", 1000000000000000000, "The amount in gwei to fund the sending addresses with.")
250-
ltp.FundSendingAddressesOnDemand = LoadtestCmd.Flags().Bool("fund-sending-addresses-on-demand", true, "If set to true, the sending addresses will be funded when used for the first time, otherwise all addresses will be fund at the start of the execution.")
250+
ltp.PreFundSendingAddresses = LoadtestCmd.Flags().Bool("pre-fund-sending-addresses", false, "If set to true, the sending addresses will be fund at the start of the execution, otherwise all addresses will be funded when used for the first time.")
251251

252252
// Local flags.
253253
ltp.Modes = LoadtestCmd.Flags().StringSliceP("mode", "m", []string{"t"}, `The testing mode to use. It can be multiple like: "c,d,f,t"

0 commit comments

Comments
 (0)