diff --git a/tests/load2/main/main.go b/tests/load2/main/main.go index 07646d4851c7..96f5dd4259cd 100644 --- a/tests/load2/main/main.go +++ b/tests/load2/main/main.go @@ -107,12 +107,15 @@ func main() { chainID, err := workers[0].Client.ChainID(ctx) require.NoError(err) + randomTest, err := load2.NewRandomTests(ctx, chainID, &workers[0]) + require.NoError(err) + generator, err := load2.NewLoadGenerator( workers, chainID, metricsNamespace, registry, - load2.ZeroTransferTest{}, + randomTest, ) require.NoError(err) diff --git a/tests/load2/tests.go b/tests/load2/tests.go index c552b455e125..d0bfd45df636 100644 --- a/tests/load2/tests.go +++ b/tests/load2/tests.go @@ -5,8 +5,12 @@ package load2 import ( "context" + "crypto/ecdsa" + "fmt" "math/big" + "math/rand/v2" + "github.com/ava-labs/libevm/accounts/abi/bind" "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/crypto" @@ -14,9 +18,156 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/tests" + "github.com/ava-labs/avalanchego/tests/load/c/contracts" + "github.com/ava-labs/avalanchego/utils/sampler" ) -var _ Test = (*ZeroTransferTest)(nil) +var maxFeeCap = big.NewInt(300000000000) + +// NewRandomTests creates a RandomWeightedTest containing a collection of EVM +// load testing scenarios. +// +// This function handles the setup of the tests and also assigns each test +// an equal weight, making them equally likely to be selected during random test execution. +func NewRandomTests(ctx context.Context, chainID *big.Int, worker *Worker) (RandomWeightedTest, error) { + txOpts, err := bind.NewKeyedTransactorWithChainID(worker.PrivKey, chainID) + if err != nil { + return RandomWeightedTest{}, err + } + + _, tx, contract, err := contracts.DeployEVMLoadSimulator(txOpts, worker.Client) + if err != nil { + return RandomWeightedTest{}, err + } + + if _, err := bind.WaitDeployed(ctx, worker.Client, tx); err != nil { + return RandomWeightedTest{}, err + } + + worker.Nonce++ + + weight := uint64(100) + count := big.NewInt(5) + weightedTests := []WeightedTest{ + { + Test: ZeroTransferTest{}, + Weight: weight, + }, + { + Test: ReadTest{ + Contract: contract, + Count: count, + }, + Weight: weight, + }, + { + Test: WriteTest{ + Contract: contract, + Count: count, + }, + Weight: weight, + }, + { + Test: StateModificationTest{ + Contract: contract, + Count: count, + }, + Weight: weight, + }, + { + Test: HashingTest{ + Contract: contract, + Count: count, + }, + Weight: weight, + }, + { + Test: MemoryTest{ + Contract: contract, + Count: count, + }, + Weight: weight, + }, + { + Test: CallDepthTest{ + Contract: contract, + Count: count, + }, + Weight: weight, + }, + { + Test: ContractCreationTest{Contract: contract}, + Weight: weight, + }, + { + Test: PureComputeTest{ + Contract: contract, + NumIterations: count, + }, + Weight: weight, + }, + { + Test: LargeEventTest{ + Contract: contract, + NumEvents: count, + }, + Weight: weight, + }, + { + Test: ExternalCallTest{Contract: contract}, + Weight: weight, + }, + } + + return NewRandomWeightedTest(weightedTests) +} + +type RandomWeightedTest struct { + tests []Test + weighted sampler.Weighted + totalWeight uint64 +} + +func NewRandomWeightedTest(weightedTests []WeightedTest) (RandomWeightedTest, error) { + weighted := sampler.NewWeighted() + + // Initialize weighted set + tests := make([]Test, len(weightedTests)) + weights := make([]uint64, len(weightedTests)) + totalWeight := uint64(0) + for i, w := range weightedTests { + tests[i] = w.Test + weights[i] = w.Weight + totalWeight += w.Weight + } + if err := weighted.Initialize(weights); err != nil { + return RandomWeightedTest{}, err + } + + return RandomWeightedTest{ + tests: tests, + weighted: weighted, + totalWeight: totalWeight, + }, nil +} + +func (r RandomWeightedTest) Run( + tc tests.TestContext, + ctx context.Context, + wallet *Wallet, +) { + require := require.New(tc) + + index, ok := r.weighted.Sample(rand.Uint64N(r.totalWeight)) //#nosec G404 + require.True(ok) + + r.tests[index].Run(tc, ctx, wallet) +} + +type WeightedTest struct { + Test Test + Weight uint64 +} type ZeroTransferTest struct{} @@ -47,3 +198,182 @@ func (ZeroTransferTest) Run( require.NoError(wallet.SendTx(ctx, tx)) } + +type ReadTest struct { + Contract *contracts.EVMLoadSimulator + Count *big.Int +} + +func (r ReadTest) Run( + tc tests.TestContext, + ctx context.Context, + wallet *Wallet, +) { + executeContractTx(tc, ctx, wallet, func(txOpts *bind.TransactOpts) (*types.Transaction, error) { + return r.Contract.SimulateReads(txOpts, r.Count) + }) +} + +type WriteTest struct { + Contract *contracts.EVMLoadSimulator + Count *big.Int +} + +func (w WriteTest) Run( + tc tests.TestContext, + ctx context.Context, + wallet *Wallet, +) { + executeContractTx(tc, ctx, wallet, func(txOpts *bind.TransactOpts) (*types.Transaction, error) { + return w.Contract.SimulateRandomWrite(txOpts, w.Count) + }) +} + +type StateModificationTest struct { + Contract *contracts.EVMLoadSimulator + Count *big.Int +} + +func (s StateModificationTest) Run( + tc tests.TestContext, + ctx context.Context, + wallet *Wallet, +) { + executeContractTx(tc, ctx, wallet, func(txOpts *bind.TransactOpts) (*types.Transaction, error) { + return s.Contract.SimulateModification(txOpts, s.Count) + }) +} + +type HashingTest struct { + Contract *contracts.EVMLoadSimulator + Count *big.Int +} + +func (h HashingTest) Run( + tc tests.TestContext, + ctx context.Context, + wallet *Wallet, +) { + executeContractTx(tc, ctx, wallet, func(txOpts *bind.TransactOpts) (*types.Transaction, error) { + return h.Contract.SimulateHashing(txOpts, h.Count) + }) +} + +type MemoryTest struct { + Contract *contracts.EVMLoadSimulator + Count *big.Int +} + +func (m MemoryTest) Run( + tc tests.TestContext, + ctx context.Context, + wallet *Wallet, +) { + executeContractTx(tc, ctx, wallet, func(txOpts *bind.TransactOpts) (*types.Transaction, error) { + return m.Contract.SimulateMemory(txOpts, m.Count) + }) +} + +type CallDepthTest struct { + Contract *contracts.EVMLoadSimulator + Count *big.Int +} + +func (c CallDepthTest) Run( + tc tests.TestContext, + ctx context.Context, + wallet *Wallet, +) { + executeContractTx(tc, ctx, wallet, func(txOpts *bind.TransactOpts) (*types.Transaction, error) { + return c.Contract.SimulateCallDepth(txOpts, c.Count) + }) +} + +type ContractCreationTest struct { + Contract *contracts.EVMLoadSimulator +} + +func (c ContractCreationTest) Run( + tc tests.TestContext, + ctx context.Context, + wallet *Wallet, +) { + executeContractTx(tc, ctx, wallet, c.Contract.SimulateContractCreation) +} + +type PureComputeTest struct { + Contract *contracts.EVMLoadSimulator + NumIterations *big.Int +} + +func (p PureComputeTest) Run( + tc tests.TestContext, + ctx context.Context, + wallet *Wallet, +) { + executeContractTx(tc, ctx, wallet, func(txOpts *bind.TransactOpts) (*types.Transaction, error) { + return p.Contract.SimulatePureCompute(txOpts, p.NumIterations) + }) +} + +type LargeEventTest struct { + Contract *contracts.EVMLoadSimulator + NumEvents *big.Int +} + +func (l LargeEventTest) Run( + tc tests.TestContext, + ctx context.Context, + wallet *Wallet, +) { + executeContractTx(tc, ctx, wallet, func(txOpts *bind.TransactOpts) (*types.Transaction, error) { + return l.Contract.SimulateLargeEvent(txOpts, l.NumEvents) + }) +} + +type ExternalCallTest struct { + Contract *contracts.EVMLoadSimulator +} + +func (e ExternalCallTest) Run( + tc tests.TestContext, + ctx context.Context, + wallet *Wallet, +) { + executeContractTx(tc, ctx, wallet, e.Contract.SimulateExternalCall) +} + +func executeContractTx( + tc tests.TestContext, + ctx context.Context, + wallet *Wallet, + txFunc func(*bind.TransactOpts) (*types.Transaction, error), +) { + require := require.New(tc) + + txOpts, err := newTxOpts(wallet.privKey, wallet.chainID, maxFeeCap, wallet.nonce) + require.NoError(err) + + tx, err := txFunc(txOpts) + require.NoError(err) + + require.NoError(wallet.SendTx(ctx, tx)) +} + +// newTxOpts returns transactions options for contract calls, with sending disabled +func newTxOpts( + key *ecdsa.PrivateKey, + chainID *big.Int, + maxFeeCap *big.Int, + nonce uint64, +) (*bind.TransactOpts, error) { + txOpts, err := bind.NewKeyedTransactorWithChainID(key, chainID) + if err != nil { + return nil, fmt.Errorf("failed to create transaction opts: %w", err) + } + txOpts.Nonce = new(big.Int).SetUint64(nonce) + txOpts.GasFeeCap = maxFeeCap + txOpts.GasTipCap = common.Big1 + txOpts.NoSend = true + return txOpts, nil +}