diff --git a/accounts/abi/bind/v2/backend.go b/accounts/abi/bind/v2/backend.go index 2f5f17b31e..2ae75c1622 100644 --- a/accounts/abi/bind/v2/backend.go +++ b/accounts/abi/bind/v2/backend.go @@ -103,6 +103,9 @@ type ContractTransactor interface { // PendingNonceAt retrieves the current pending nonce associated with an account. PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) + + // TransactionByHash retrieves the transaction corresponding to the given hash. + TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error) } // DeployBackend wraps the operations needed by WaitMined and WaitDeployed. diff --git a/accounts/abi/bind/v2/lib.go b/accounts/abi/bind/v2/lib.go index 3831161341..f682dbdac3 100644 --- a/accounts/abi/bind/v2/lib.go +++ b/accounts/abi/bind/v2/lib.go @@ -27,14 +27,16 @@ package bind import ( + "context" "errors" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "time" ) // ContractEvent is a type constraint for ABI event types. @@ -227,6 +229,34 @@ func DeployContract(opts *TransactOpts, bytecode []byte, backend ContractBackend return crypto.CreateAddress(opts.From, tx.Nonce()), tx, nil } +// waitAccepted polls the backend until the transaction is accepted or the +// context is cancelled. If the transaction was not accepted into the pool, +// an error is returned. +func waitAccepted(ctx context.Context, d ContractBackend, txHash common.Hash) error { + queryTicker := time.NewTicker(time.Second) + defer queryTicker.Stop() + logger := log.New("hash", txHash) + for { + _, _, err := d.TransactionByHash(ctx, txHash) + if err == nil { + return nil + } + + if errors.Is(err, ethereum.NotFound) { // TODO: check this is emitted + logger.Trace("Transaction not yet accepted") + } else { + logger.Trace("Transaction submission failed", "err", err) + } + + // Wait for the next round. + select { + case <-ctx.Done(): + return ctx.Err() + case <-queryTicker.C: + } + } +} + // DefaultDeployer returns a DeployFn that signs and submits creation transactions // using the given signer. // @@ -238,6 +268,10 @@ func DefaultDeployer(opts *TransactOpts, backend ContractBackend) DeployFn { if err != nil { return common.Address{}, nil, err } + ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) + if err := waitAccepted(ctx, backend, tx.Hash()); err != nil { + return common.Address{}, nil, err + } return addr, tx, nil } }