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 }