-
Notifications
You must be signed in to change notification settings - Fork 275
Turn list of txs into tx sequence #683
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
Changes from 6 commits
9a0b4fe
024b54b
774320f
61ff4b2
fabe777
ae385ba
4639809
c3192ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,40 +11,48 @@ import ( | |
|
|
||
| "github.com/ava-labs/subnet-evm/cmd/simulator/config" | ||
| "github.com/ava-labs/subnet-evm/cmd/simulator/key" | ||
| "github.com/ava-labs/subnet-evm/cmd/simulator/txs" | ||
| "github.com/ava-labs/subnet-evm/core/types" | ||
| "github.com/ava-labs/subnet-evm/ethclient" | ||
| "github.com/ava-labs/subnet-evm/params" | ||
| "github.com/ethereum/go-ethereum/common" | ||
| ethcrypto "github.com/ethereum/go-ethereum/crypto" | ||
| "github.com/ethereum/go-ethereum/log" | ||
| "golang.org/x/sync/errgroup" | ||
| ) | ||
|
|
||
| // CreateLoader creates a WorkerGroup from [config] to perform the specified simulation. | ||
| func CreateLoader(ctx context.Context, config config.Config) (*WorkerGroup, error) { | ||
| // ExecuteLoader creates txSequences from [config] and has txAgents execute the specified simulation. | ||
| func ExecuteLoader(ctx context.Context, config config.Config) error { | ||
anusha-ctrl marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if config.Timeout > 0 { | ||
| var cancel context.CancelFunc | ||
| ctx, cancel = context.WithTimeout(ctx, config.Timeout) | ||
| defer cancel() | ||
| } | ||
|
|
||
| // Construct the arguments for the load simulator | ||
| clients := make([]ethclient.Client, 0, len(config.Endpoints)) | ||
| for i := 0; i < config.Workers; i++ { | ||
| clientURI := config.Endpoints[i%len(config.Endpoints)] | ||
| client, err := ethclient.Dial(clientURI) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to dial client at %s: %w", clientURI, err) | ||
| return fmt.Errorf("failed to dial client at %s: %w", clientURI, err) | ||
| } | ||
| clients = append(clients, client) | ||
| } | ||
|
|
||
| keys, err := key.LoadAll(ctx, config.KeyDir) | ||
| if err != nil { | ||
| return nil, err | ||
| return err | ||
| } | ||
| // Ensure there are at least [config.Workers] keys and save any newly generated ones. | ||
| if len(keys) < config.Workers { | ||
| for i := 0; len(keys) < config.Workers; i++ { | ||
| newKey, err := key.Generate() | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to generate %d new key: %w", i, err) | ||
| return fmt.Errorf("failed to generate %d new key: %w", i, err) | ||
| } | ||
| if err := newKey.Save(config.KeyDir); err != nil { | ||
| return nil, fmt.Errorf("failed to save %d new key: %w", i, err) | ||
| return fmt.Errorf("failed to save %d new key: %w", i, err) | ||
| } | ||
| keys = append(keys, newKey) | ||
| } | ||
|
|
@@ -57,8 +65,9 @@ func CreateLoader(ctx context.Context, config config.Config) (*WorkerGroup, erro | |
| log.Info("Distributing funds", "numTxsPerWorker", config.TxsPerWorker, "minFunds", minFundsPerAddr) | ||
| keys, err = DistributeFunds(ctx, clients[0], keys, config.Workers, minFundsPerAddr) | ||
| if err != nil { | ||
| return nil, err | ||
| return err | ||
| } | ||
| log.Info("Distributed funds successfully") | ||
|
|
||
| pks := make([]*ecdsa.PrivateKey, 0, len(keys)) | ||
| senders := make([]common.Address, 0, len(keys)) | ||
|
|
@@ -73,10 +82,11 @@ func CreateLoader(ctx context.Context, config config.Config) (*WorkerGroup, erro | |
| client := clients[0] | ||
| chainID, err := client.ChainID(ctx) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to fetch chainID: %w", err) | ||
| return fmt.Errorf("failed to fetch chainID: %w", err) | ||
| } | ||
| signer := types.LatestSignerForChainID(chainID) | ||
|
|
||
| log.Info("Creating transaction sequences...") | ||
| txGenerator := func(key *ecdsa.PrivateKey, nonce uint64) (*types.Transaction, error) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems this txGenerator func can just be a series of options/params for |
||
| addr := ethcrypto.PubkeyToAddress(key.PublicKey) | ||
| tx, err := types.SignNewTx(key, signer, &types.DynamicFeeTx{ | ||
|
|
@@ -94,26 +104,30 @@ func CreateLoader(ctx context.Context, config config.Config) (*WorkerGroup, erro | |
| } | ||
| return tx, nil | ||
| } | ||
| txSequences, err := GenerateTxSequences(ctx, txGenerator, clients[0], pks, config.TxsPerWorker) | ||
| txSequences, err := txs.GenerateTxSequences(ctx, txGenerator, clients[0], pks, config.TxsPerWorker) | ||
| if err != nil { | ||
| return nil, err | ||
| return err | ||
| } | ||
|
|
||
| wg := NewWorkerGroup(clients[:config.Workers], senders[:config.Workers], txSequences[:config.Workers]) | ||
| return wg, nil | ||
| } | ||
| log.Info("Constructing tx agents...", "numAgents", config.Workers) | ||
| agents := make([]txs.Agent[*types.Transaction], 0, config.Workers) | ||
| for i := 0; i < config.Workers; i++ { | ||
| agents = append(agents, txs.NewIssueNAgent[*types.Transaction](txSequences[i], NewSingleAddressTxWorker(ctx, clients[i], senders[i]), config.BatchSize)) | ||
| } | ||
|
|
||
| // ExecuteLoader runs the load simulation specified by config. | ||
| func ExecuteLoader(ctx context.Context, config config.Config) error { | ||
| if config.Timeout > 0 { | ||
| var cancel context.CancelFunc | ||
| ctx, cancel = context.WithTimeout(ctx, config.Timeout) | ||
| defer cancel() | ||
| log.Info("Starting tx agents...") | ||
| eg := errgroup.Group{} | ||
| for _, agent := range agents { | ||
| agent := agent | ||
| eg.Go(func() error { | ||
| return agent.Execute(ctx) | ||
| }) | ||
| } | ||
|
|
||
| loader, err := CreateLoader(ctx, config) | ||
| if err != nil { | ||
| log.Info("Waiting for tx agents...") | ||
| if err := eg.Wait(); err != nil { | ||
| return err | ||
| } | ||
| return loader.Execute(ctx) | ||
| log.Info("Tx agents completed successfully.") | ||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -10,102 +10,74 @@ import ( | |||||
|
|
||||||
| "github.com/ava-labs/subnet-evm/core/types" | ||||||
| "github.com/ava-labs/subnet-evm/ethclient" | ||||||
| "github.com/ava-labs/subnet-evm/interfaces" | ||||||
| "github.com/ethereum/go-ethereum/common" | ||||||
| "github.com/ethereum/go-ethereum/log" | ||||||
| ) | ||||||
|
|
||||||
| type Worker struct { | ||||||
| client ethclient.Client | ||||||
| address common.Address | ||||||
| txs []*types.Transaction | ||||||
| } | ||||||
| type singleAddressTxWorker struct { | ||||||
| client ethclient.Client | ||||||
|
|
||||||
| // NewWorker creates a new worker that will issue the sequence of transactions from the given address | ||||||
| // | ||||||
| // Assumes that all transactions are from the same address, ordered by nonce, and this worker has exclusive access | ||||||
| // to issuance of transactions from the underlying private key. | ||||||
| func NewWorker(client ethclient.Client, address common.Address, txs []*types.Transaction) *Worker { | ||||||
| return &Worker{ | ||||||
| client: client, | ||||||
| address: address, | ||||||
| txs: txs, | ||||||
| } | ||||||
| } | ||||||
| acceptedNonce uint64 | ||||||
| address common.Address | ||||||
|
|
||||||
| func (w *Worker) ExecuteTxsFromAddress(ctx context.Context) error { | ||||||
| log.Info("Executing txs", "numTxs", len(w.txs)) | ||||||
| for i, tx := range w.txs { | ||||||
| start := time.Now() | ||||||
| err := w.client.SendTransaction(ctx, tx) | ||||||
| if err != nil { | ||||||
| return fmt.Errorf("failed to issue tx %d: %w", i, err) | ||||||
| } | ||||||
| log.Info("execute tx", "tx", tx.Hash(), "nonce", tx.Nonce(), "duration", time.Since(start)) | ||||||
| } | ||||||
| return nil | ||||||
| sub interfaces.Subscription | ||||||
| newHeads chan *types.Header | ||||||
| } | ||||||
|
|
||||||
| // AwaitTxs awaits for the nonce of the last transaction issued by the worker to be confirmed or | ||||||
| // rejected by the network. | ||||||
| // | ||||||
| // Assumes that a non-zero number of transactions were already generated and that they were issued | ||||||
| // by this worker. | ||||||
| func (w *Worker) AwaitTxs(ctx context.Context) error { | ||||||
| nonce := w.txs[len(w.txs)-1].Nonce() | ||||||
|
|
||||||
| // NewSingleAddressTxWorker creates and returns a singleAddressTxWorker | ||||||
| func NewSingleAddressTxWorker(ctx context.Context, client ethclient.Client, address common.Address) *singleAddressTxWorker { | ||||||
darioush marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| newHeads := make(chan *types.Header) | ||||||
| defer close(newHeads) | ||||||
| tw := &singleAddressTxWorker{ | ||||||
| client: client, | ||||||
| address: address, | ||||||
| newHeads: newHeads, | ||||||
| } | ||||||
|
|
||||||
| sub, err := w.client.SubscribeNewHead(ctx, newHeads) | ||||||
| sub, err := client.SubscribeNewHead(ctx, newHeads) | ||||||
| if err != nil { | ||||||
| log.Debug("failed to subscribe new heads, falling back to polling", "err", err) | ||||||
| } else { | ||||||
| defer sub.Unsubscribe() | ||||||
| tw.sub = sub | ||||||
| } | ||||||
|
|
||||||
| return tw | ||||||
| } | ||||||
|
|
||||||
| func (tw *singleAddressTxWorker) IssueTx(ctx context.Context, tx *types.Transaction) error { | ||||||
| return tw.client.SendTransaction(ctx, tx) | ||||||
| } | ||||||
|
|
||||||
| func (tw *singleAddressTxWorker) ConfirmTx(ctx context.Context, tx *types.Transaction) error { | ||||||
| txNonce := tx.Nonce() | ||||||
|
|
||||||
| for { | ||||||
| // If the is less than what has already been accepted, the transaction is confirmed | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks |
||||||
| if txNonce < tw.acceptedNonce { | ||||||
| return nil | ||||||
| } | ||||||
|
|
||||||
| select { | ||||||
| case <-newHeads: | ||||||
| case <-tw.newHeads: | ||||||
| case <-time.After(time.Second): | ||||||
| case <-ctx.Done(): | ||||||
| return fmt.Errorf("failed to await nonce: %w", ctx.Err()) | ||||||
| return fmt.Errorf("failed to await tx %s nonce %d: %w", tx.Hash(), txNonce, ctx.Err()) | ||||||
| } | ||||||
|
|
||||||
| currentNonce, err := w.client.NonceAt(ctx, w.address, nil) | ||||||
| // Update the worker's accepted nonce, so we can check on the next iteration | ||||||
| // if the transaction has been accepted. | ||||||
| acceptedNonce, err := tw.client.NonceAt(ctx, tw.address, nil) | ||||||
| if err != nil { | ||||||
| log.Warn("failed to get nonce", "err", err) | ||||||
| } | ||||||
| if currentNonce >= nonce { | ||||||
| return nil | ||||||
| } else { | ||||||
| log.Info("fetched nonce", "awaiting", nonce, "currentNonce", currentNonce) | ||||||
| return fmt.Errorf("failed to await tx %s nonce %d: %w", tx.Hash(), txNonce, err) | ||||||
| } | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we still keep the awaiting line. It's helpful to the user.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's excessive to do so for every nonce at the info level. Could we make it debug? |
||||||
| tw.acceptedNonce = acceptedNonce | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| // ConfirmAllTransactions iterates over every transaction of this worker and confirms it | ||||||
| // via eth_getTransactionByHash | ||||||
| func (w *Worker) ConfirmAllTransactions(ctx context.Context) error { | ||||||
| for i, tx := range w.txs { | ||||||
| _, isPending, err := w.client.TransactionByHash(ctx, tx.Hash()) | ||||||
| if err != nil { | ||||||
| return fmt.Errorf("failed to confirm tx at index %d: %s", i, tx.Hash()) | ||||||
| } | ||||||
| if isPending { | ||||||
| return fmt.Errorf("failed to confirm tx at index %d: pending", i) | ||||||
| } | ||||||
| func (tw *singleAddressTxWorker) Close(ctx context.Context) error { | ||||||
| if tw.sub != nil { | ||||||
| tw.sub.Unsubscribe() | ||||||
| } | ||||||
| log.Info("Confirmed all transactions") | ||||||
| close(tw.newHeads) | ||||||
| return nil | ||||||
| } | ||||||
|
|
||||||
| // Execute issues and confirms all transactions for the worker. | ||||||
| func (w *Worker) Execute(ctx context.Context) error { | ||||||
| if err := w.ExecuteTxsFromAddress(ctx); err != nil { | ||||||
| return err | ||||||
| } | ||||||
| if err := w.AwaitTxs(ctx); err != nil { | ||||||
| return err | ||||||
| } | ||||||
| return w.ConfirmAllTransactions(ctx) | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does this change do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my editor added that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will fix