From 1938deaa6908afe0a22044426fe269aceaefeaf7 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Thu, 12 Mar 2026 10:58:45 +0800 Subject: [PATCH 1/4] Problem: base_mnemonic prefund only wallet[0] at genesis for non-persistent run prefund derived num_wallets of wallets (n > 0) from base_mnemonic for interval/block runs --- chains/ethereum/runner/runner.go | 80 ++++++++++++++++++++++++++++---- go.mod | 7 +-- go.sum | 8 ++++ 3 files changed, 82 insertions(+), 13 deletions(-) diff --git a/chains/ethereum/runner/runner.go b/chains/ethereum/runner/runner.go index 09e4e42..466e0b4 100644 --- a/chains/ethereum/runner/runner.go +++ b/chains/ethereum/runner/runner.go @@ -3,7 +3,7 @@ package runner import ( "context" "fmt" - "math/rand" + "math/big" "net" "net/http" "slices" @@ -91,6 +91,18 @@ func NewRunner(ctx context.Context, logger *zap.Logger, spec loadtesttypes.LoadT return nil, err } + isNonPersistentRun := (spec.NumBatches > 0 && spec.SendInterval > 0) || spec.NumOfBlocks > 0 + if isNonPersistentRun { + if err := preFundDerivedWallets(ctx, logger, wallets); err != nil { + return nil, fmt.Errorf("prefund derived wallets: %w", err) + } + } + + if spec.InitialWallets <= 0 { + logger.Info("initial_wallets not set; defaulting to 1", zap.Int("num_wallets", spec.NumWallets)) + spec.InitialWallets = 1 + } + var distribution txfactory.TxDistribution if spec.InitialWallets > 0 && spec.InitialWallets < spec.NumWallets { logger.Info( @@ -313,16 +325,14 @@ func (r *Runner) Run(ctx context.Context) (loadtesttypes.LoadTestResult, error) } func (r *Runner) buildLoad(msgSpec loadtesttypes.LoadTestMsg, useBaseline bool) ([]*gethtypes.Transaction, error) { - // For ERC20 transactions, use optimal sender selection from factory - var fromWallet *wallet.InteractingWallet - switch msgSpec.Type { - case inttypes.MsgTransferERC0, inttypes.MsgNativeTransferERC20: - fromWallet = r.txFactory.GetNextSender() - case inttypes.MsgDeployERC20, inttypes.MsgCreateContract: + // Deployments must come from wallet[0]; all other traffic uses distribution-aware senders. + fromWallet := r.txFactory.GetNextSender() + if msgSpec.Type == inttypes.MsgDeployERC20 || msgSpec.Type == inttypes.MsgCreateContract { fromWallet = r.wallets[0] - default: - // For non-ERC20 transactions, keep random selection - fromWallet = r.wallets[rand.Intn(len(r.wallets))] + } + + if fromWallet == nil { + return nil, nil } nonce, ok := r.nonces.Load(fromWallet.Address()) @@ -348,3 +358,53 @@ func (r *Runner) buildLoad(msgSpec loadtesttypes.LoadTestMsg, useBaseline bool) r.nonces.Store(fromWallet.Address(), lastTx.Nonce()+1) return txs, nil } + +func preFundDerivedWallets(ctx context.Context, logger *zap.Logger, wallets []*wallet.InteractingWallet) error { + if len(wallets) <= 1 { + return nil + } + + unfunded := make([]int, 0, len(wallets)-1) + for i := 1; i < len(wallets); i++ { + bal, err := wallets[i].GetBalance(ctx) + if err != nil { + return fmt.Errorf("getting balance for wallet[%d] %s: %w", i, wallets[i].FormattedAddress(), err) + } + if bal.Sign() == 0 { + unfunded = append(unfunded, i) + } + } + + if len(unfunded) == 0 { + return nil + } + + funder := wallets[0] + funderBal, err := funder.GetBalance(ctx) + if err != nil { + return fmt.Errorf("getting funder balance for %s: %w", funder.FormattedAddress(), err) + } + if funderBal.Sign() == 0 { + return fmt.Errorf("funder %s has zero balance", funder.FormattedAddress()) + } + + denom := big.NewInt(int64((len(unfunded) + 1) * 2)) + amountPerWallet := new(big.Int).Div(funderBal, denom) + if amountPerWallet.Sign() == 0 { + return fmt.Errorf("insufficient funder balance %s to pre-fund %d wallets", funderBal.String(), len(unfunded)) + } + + for _, i := range unfunded { + to := wallets[i].Address() + txHash, err := funder.CreateAndSendTransaction(ctx, &to, amountPerWallet, 21_000, nil, nil, nil) + if err != nil { + return fmt.Errorf("sending prefund tx to wallet[%d] %s: %w", i, to.Hex(), err) + } + if _, err := funder.WaitForTxReceipt(ctx, txHash, 30*time.Second); err != nil { + return fmt.Errorf("waiting prefund receipt for wallet[%d] %s (tx=%s): %w", i, to.Hex(), txHash.Hex(), err) + } + logger.Info("prefunded derived wallet", zap.Int("wallet_index", i), zap.String("address", to.Hex()), zap.String("amount", amountPerWallet.String())) + } + + return nil +} diff --git a/go.mod b/go.mod index 51f3500..2ce501d 100644 --- a/go.mod +++ b/go.mod @@ -60,12 +60,13 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.6 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect - github.com/bytedance/sonic v1.14.0 // indirect - github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cilium/ipam v0.0.0-20230509084518-fd66eae7909b // indirect - github.com/cloudwego/base64x v0.1.5 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect github.com/cockroachdb/errors v1.12.0 // indirect github.com/cockroachdb/fifo v0.0.0-20240616162244-4768e80dfb9a // indirect github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect diff --git a/go.sum b/go.sum index 5635751..e575c76 100644 --- a/go.sum +++ b/go.sum @@ -185,11 +185,17 @@ github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/ github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= @@ -217,6 +223,8 @@ github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= From 17fe5cc09582c8ff1c4992591c689a867b8e6e7a Mon Sep 17 00:00:00 2001 From: mmsqe Date: Thu, 12 Mar 2026 11:02:25 +0800 Subject: [PATCH 2/4] fasten --- chains/ethereum/runner/runner.go | 64 ++++++++++++++++++++++++++------ 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/chains/ethereum/runner/runner.go b/chains/ethereum/runner/runner.go index 466e0b4..85fc667 100644 --- a/chains/ethereum/runner/runner.go +++ b/chains/ethereum/runner/runner.go @@ -364,15 +364,27 @@ func preFundDerivedWallets(ctx context.Context, logger *zap.Logger, wallets []*w return nil } + balanceMu := sync.Mutex{} unfunded := make([]int, 0, len(wallets)-1) + balanceEG := errgroup.Group{} + balanceEG.SetLimit(16) for i := 1; i < len(wallets); i++ { - bal, err := wallets[i].GetBalance(ctx) - if err != nil { - return fmt.Errorf("getting balance for wallet[%d] %s: %w", i, wallets[i].FormattedAddress(), err) - } - if bal.Sign() == 0 { - unfunded = append(unfunded, i) - } + i := i + balanceEG.Go(func() error { + bal, err := wallets[i].GetBalance(ctx) + if err != nil { + return fmt.Errorf("getting balance for wallet[%d] %s: %w", i, wallets[i].FormattedAddress(), err) + } + if bal.Sign() == 0 { + balanceMu.Lock() + unfunded = append(unfunded, i) + balanceMu.Unlock() + } + return nil + }) + } + if err := balanceEG.Wait(); err != nil { + return err } if len(unfunded) == 0 { @@ -394,16 +406,44 @@ func preFundDerivedWallets(ctx context.Context, logger *zap.Logger, wallets []*w return fmt.Errorf("insufficient funder balance %s to pre-fund %d wallets", funderBal.String(), len(unfunded)) } + startNonce, err := funder.GetNonce(ctx) + if err != nil { + return fmt.Errorf("getting funder nonce for %s: %w", funder.FormattedAddress(), err) + } + + type prefundTx struct { + walletIndex int + address common.Address + txHash common.Hash + } + prefundTxs := make([]prefundTx, 0, len(unfunded)) + nextNonce := startNonce + for _, i := range unfunded { to := wallets[i].Address() - txHash, err := funder.CreateAndSendTransaction(ctx, &to, amountPerWallet, 21_000, nil, nil, nil) + nonce := nextNonce + nextNonce++ + txHash, err := funder.CreateAndSendTransaction(ctx, &to, amountPerWallet, 21_000, nil, nil, &nonce) if err != nil { return fmt.Errorf("sending prefund tx to wallet[%d] %s: %w", i, to.Hex(), err) } - if _, err := funder.WaitForTxReceipt(ctx, txHash, 30*time.Second); err != nil { - return fmt.Errorf("waiting prefund receipt for wallet[%d] %s (tx=%s): %w", i, to.Hex(), txHash.Hex(), err) - } - logger.Info("prefunded derived wallet", zap.Int("wallet_index", i), zap.String("address", to.Hex()), zap.String("amount", amountPerWallet.String())) + prefundTxs = append(prefundTxs, prefundTx{walletIndex: i, address: to, txHash: txHash}) + } + + receiptEG := errgroup.Group{} + receiptEG.SetLimit(16) + for i := range prefundTxs { + tx := prefundTxs[i] + receiptEG.Go(func() error { + if _, err := funder.WaitForTxReceipt(ctx, tx.txHash, 30*time.Second); err != nil { + return fmt.Errorf("waiting prefund receipt for wallet[%d] %s (tx=%s): %w", tx.walletIndex, tx.address.Hex(), tx.txHash.Hex(), err) + } + logger.Info("prefunded derived wallet", zap.Int("wallet_index", tx.walletIndex), zap.String("address", tx.address.Hex()), zap.String("amount", amountPerWallet.String())) + return nil + }) + } + if err := receiptEG.Wait(); err != nil { + return err } return nil From 0f98b2c55d605c27b588a1d68afd871cf6ea06a5 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Thu, 12 Mar 2026 11:19:21 +0800 Subject: [PATCH 3/4] Apply suggestions from code review --- chains/ethereum/runner/runner.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/chains/ethereum/runner/runner.go b/chains/ethereum/runner/runner.go index 85fc667..1d5f4a5 100644 --- a/chains/ethereum/runner/runner.go +++ b/chains/ethereum/runner/runner.go @@ -325,10 +325,12 @@ func (r *Runner) Run(ctx context.Context) (loadtesttypes.LoadTestResult, error) } func (r *Runner) buildLoad(msgSpec loadtesttypes.LoadTestMsg, useBaseline bool) ([]*gethtypes.Transaction, error) { - // Deployments must come from wallet[0]; all other traffic uses distribution-aware senders. - fromWallet := r.txFactory.GetNextSender() + // Deployments must come from wallet[0] and must not advance sender distribution state. + var fromWallet *wallet.InteractingWallet if msgSpec.Type == inttypes.MsgDeployERC20 || msgSpec.Type == inttypes.MsgCreateContract { fromWallet = r.wallets[0] + } else { + fromWallet = r.txFactory.GetNextSender() } if fromWallet == nil { From 8ec224a294c5101dc7088500a3b1da61d573740c Mon Sep 17 00:00:00 2001 From: mmsqe Date: Thu, 12 Mar 2026 11:29:03 +0800 Subject: [PATCH 4/4] align prefund and distribution --- chains/ethereum/runner/runner.go | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/chains/ethereum/runner/runner.go b/chains/ethereum/runner/runner.go index 1d5f4a5..8185f05 100644 --- a/chains/ethereum/runner/runner.go +++ b/chains/ethereum/runner/runner.go @@ -98,19 +98,32 @@ func NewRunner(ctx context.Context, logger *zap.Logger, spec loadtesttypes.LoadT } } - if spec.InitialWallets <= 0 { + initialWallets := spec.InitialWallets + if initialWallets <= 0 { + initialWallets = 1 + } + + if isNonPersistentRun { + if initialWallets < spec.NumWallets { + logger.Info( + "prefund complete; using all wallets for sender distribution", + zap.Int("initial_wallets", initialWallets), + zap.Int("num_wallets", spec.NumWallets), + ) + } + initialWallets = spec.NumWallets + } else if spec.InitialWallets <= 0 { logger.Info("initial_wallets not set; defaulting to 1", zap.Int("num_wallets", spec.NumWallets)) - spec.InitialWallets = 1 } var distribution txfactory.TxDistribution - if spec.InitialWallets > 0 && spec.InitialWallets < spec.NumWallets { + if initialWallets > 0 && initialWallets < spec.NumWallets { logger.Info( "Using TxDistributionBootstrapped", - zap.Int("initial_wallets", spec.InitialWallets), + zap.Int("initial_wallets", initialWallets), zap.Int("num_wallets", spec.NumWallets), ) - distribution = txfactory.NewTxDistributionBootstrapped(logger, wallets, spec.InitialWallets) + distribution = txfactory.NewTxDistributionBootstrapped(logger, wallets, initialWallets) } else { logger.Info("Using TxDistributionEven") distribution = txfactory.NewTxDistributionEven(wallets)