Skip to content

feat(load2): add contract tests #4071

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion tests/load2/main/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,15 @@ func main() {
chainID, err := workers[0].Client.ChainID(ctx)
require.NoError(err)

randomTest, err := load2.NewRandomTests(ctx, chainID, &workers[0])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why randomTest but NewRandomTests?

require.NoError(err)

generator, err := load2.NewLoadGenerator(
workers,
chainID,
metricsNamespace,
registry,
load2.ZeroTransferTest{},
randomTest,
)
require.NoError(err)

Expand Down
332 changes: 331 additions & 1 deletion tests/load2/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,169 @@ 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"
"github.com/ava-labs/libevm/params"
"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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this Tests plural despite returning RandomWeightedTest? Or maybe, why isn't it RandomWeightedTests given that it is a wrapper around a set of tests?

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this reflect load testing reproducibility, should we consider using a seeded random source?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd second that suggestion. It wouldn't have to be the default behavior, but I think it would make sense to accept an optional seed.

require.True(ok)

r.tests[index].Run(tc, ctx, wallet)
}

type WeightedTest struct {
Test Test
Weight uint64
}

type ZeroTransferTest struct{}

Expand Down Expand Up @@ -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
}