github.com/ethersphere/bee/v2@v2.2.0/pkg/transaction/backend.go (about)

     1  // Copyright 2020 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package transaction
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"math/big"
    11  	"time"
    12  
    13  	"github.com/ethereum/go-ethereum"
    14  	"github.com/ethereum/go-ethereum/common"
    15  	"github.com/ethereum/go-ethereum/core/types"
    16  	"github.com/ethersphere/bee/v2/pkg/log"
    17  )
    18  
    19  // Backend is the minimum of blockchain backend functions we need.
    20  type Backend interface {
    21  	CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
    22  	CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
    23  	HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
    24  	PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
    25  	SuggestGasPrice(ctx context.Context) (*big.Int, error)
    26  	SuggestGasTipCap(ctx context.Context) (*big.Int, error)
    27  	EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)
    28  	SendTransaction(ctx context.Context, tx *types.Transaction) error
    29  	TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
    30  	TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error)
    31  	BlockNumber(ctx context.Context) (uint64, error)
    32  	BalanceAt(ctx context.Context, address common.Address, block *big.Int) (*big.Int, error)
    33  	NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)
    34  	FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error)
    35  	ChainID(ctx context.Context) (*big.Int, error)
    36  
    37  	Close()
    38  }
    39  
    40  // IsSynced will check if we are synced with the given blockchain backend. This
    41  // is true if the current wall clock is after the block time of last block
    42  // with the given maxDelay as the maximum duration we can be behind the block
    43  // time.
    44  func IsSynced(ctx context.Context, backend Backend, maxDelay time.Duration) (bool, time.Time, error) {
    45  	number, err := backend.BlockNumber(ctx)
    46  	if err != nil {
    47  		return false, time.Time{}, err
    48  	}
    49  	header, err := backend.HeaderByNumber(ctx, big.NewInt(int64(number)))
    50  	if errors.Is(err, ethereum.NotFound) {
    51  		return false, time.Time{}, nil
    52  	}
    53  	if err != nil {
    54  		return false, time.Time{}, err
    55  	}
    56  
    57  	blockTime := time.Unix(int64(header.Time), 0)
    58  
    59  	return blockTime.After(time.Now().UTC().Add(-maxDelay)), blockTime, nil
    60  }
    61  
    62  // WaitSynced will wait until we are synced with the given blockchain backend,
    63  // with the given maxDelay duration as the maximum time we can be behind the
    64  // last block.
    65  func WaitSynced(ctx context.Context, logger log.Logger, backend Backend, maxDelay time.Duration) error {
    66  	logger = logger.WithName(loggerName).Register()
    67  
    68  	for {
    69  		synced, blockTime, err := IsSynced(ctx, backend, maxDelay)
    70  		if err != nil {
    71  			return err
    72  		}
    73  
    74  		if synced {
    75  			return nil
    76  		}
    77  
    78  		logger.Info("still waiting for Ethereum to sync", "block_time", blockTime)
    79  
    80  		select {
    81  		case <-ctx.Done():
    82  			return ctx.Err()
    83  		case <-time.After(5 * time.Second):
    84  		}
    85  	}
    86  }
    87  
    88  func WaitBlockAfterTransaction(ctx context.Context, backend Backend, pollingInterval time.Duration, txHash common.Hash, additionalConfirmations uint64) (*types.Header, error) {
    89  	for {
    90  		receipt, err := backend.TransactionReceipt(ctx, txHash)
    91  		if err != nil {
    92  			if !errors.Is(err, ethereum.NotFound) {
    93  				return nil, err
    94  			}
    95  			select {
    96  			case <-time.After(pollingInterval):
    97  			case <-ctx.Done():
    98  				return nil, ctx.Err()
    99  			}
   100  			continue
   101  		}
   102  
   103  		bn, err := backend.BlockNumber(ctx)
   104  		if err != nil {
   105  			return nil, err
   106  		}
   107  
   108  		nextBlock := receipt.BlockNumber.Uint64() + 1
   109  
   110  		if bn >= nextBlock+additionalConfirmations {
   111  			header, err := backend.HeaderByNumber(ctx, new(big.Int).SetUint64(nextBlock))
   112  			if err != nil {
   113  				if !errors.Is(err, ethereum.NotFound) {
   114  					return nil, err
   115  				}
   116  				// in the case where we cannot find the block even though we already saw a higher number we keep on trying
   117  			} else {
   118  				return header, nil
   119  			}
   120  		}
   121  
   122  		select {
   123  		case <-time.After(pollingInterval):
   124  		case <-ctx.Done():
   125  			return nil, errors.New("context timeout")
   126  		}
   127  	}
   128  }