github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/powchain/block_reader.go (about)

     1  package powchain
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/big"
     7  
     8  	"github.com/ethereum/go-ethereum/common"
     9  	"github.com/pkg/errors"
    10  	"github.com/prysmaticlabs/prysm/beacon-chain/powchain/types"
    11  	"github.com/prysmaticlabs/prysm/shared/params"
    12  	"github.com/prysmaticlabs/prysm/shared/traceutil"
    13  	"go.opencensus.io/trace"
    14  )
    15  
    16  // searchThreshold to apply for when searching for blocks of a particular time. If the buffer
    17  // is exceeded we recalibrate the search again.
    18  const searchThreshold = 5
    19  
    20  // amount of times we repeat a failed search till is satisfies the conditional.
    21  const repeatedSearches = 2 * searchThreshold
    22  
    23  // BlockExists returns true if the block exists, its height and any possible error encountered.
    24  func (s *Service) BlockExists(ctx context.Context, hash common.Hash) (bool, *big.Int, error) {
    25  	ctx, span := trace.StartSpan(ctx, "beacon-chain.web3service.BlockExists")
    26  	defer span.End()
    27  
    28  	if exists, hdrInfo, err := s.headerCache.HeaderInfoByHash(hash); exists || err != nil {
    29  		if err != nil {
    30  			return false, nil, err
    31  		}
    32  		span.AddAttributes(trace.BoolAttribute("blockCacheHit", true))
    33  		return true, hdrInfo.Number, nil
    34  	}
    35  	span.AddAttributes(trace.BoolAttribute("blockCacheHit", false))
    36  	header, err := s.eth1DataFetcher.HeaderByHash(ctx, hash)
    37  	if err != nil {
    38  		return false, big.NewInt(0), errors.Wrap(err, "could not query block with given hash")
    39  	}
    40  
    41  	if err := s.headerCache.AddHeader(header); err != nil {
    42  		return false, big.NewInt(0), err
    43  	}
    44  
    45  	return true, new(big.Int).Set(header.Number), nil
    46  }
    47  
    48  // BlockExistsWithCache returns true if the block exists in cache, its height and any possible error encountered.
    49  func (s *Service) BlockExistsWithCache(ctx context.Context, hash common.Hash) (bool, *big.Int, error) {
    50  	ctx, span := trace.StartSpan(ctx, "beacon-chain.web3service.BlockExistsWithCache")
    51  	defer span.End()
    52  	if exists, hdrInfo, err := s.headerCache.HeaderInfoByHash(hash); exists || err != nil {
    53  		if err != nil {
    54  			return false, nil, err
    55  		}
    56  		span.AddAttributes(trace.BoolAttribute("blockCacheHit", true))
    57  		return true, hdrInfo.Number, nil
    58  	}
    59  	span.AddAttributes(trace.BoolAttribute("blockCacheHit", false))
    60  	return false, nil, nil
    61  }
    62  
    63  // BlockHashByHeight returns the block hash of the block at the given height.
    64  func (s *Service) BlockHashByHeight(ctx context.Context, height *big.Int) (common.Hash, error) {
    65  	ctx, span := trace.StartSpan(ctx, "beacon-chain.web3service.BlockHashByHeight")
    66  	defer span.End()
    67  
    68  	if exists, hInfo, err := s.headerCache.HeaderInfoByHeight(height); exists || err != nil {
    69  		if err != nil {
    70  			return [32]byte{}, err
    71  		}
    72  		span.AddAttributes(trace.BoolAttribute("headerCacheHit", true))
    73  		return hInfo.Hash, nil
    74  	}
    75  	span.AddAttributes(trace.BoolAttribute("headerCacheHit", false))
    76  
    77  	if s.eth1DataFetcher == nil {
    78  		err := errors.New("nil eth1DataFetcher")
    79  		traceutil.AnnotateError(span, err)
    80  		return [32]byte{}, err
    81  	}
    82  
    83  	header, err := s.eth1DataFetcher.HeaderByNumber(ctx, height)
    84  	if err != nil {
    85  		return [32]byte{}, errors.Wrap(err, fmt.Sprintf("could not query header with height %d", height.Uint64()))
    86  	}
    87  	if err := s.headerCache.AddHeader(header); err != nil {
    88  		return [32]byte{}, err
    89  	}
    90  	return header.Hash(), nil
    91  }
    92  
    93  // BlockTimeByHeight fetches an eth1.0 block timestamp by its height.
    94  func (s *Service) BlockTimeByHeight(ctx context.Context, height *big.Int) (uint64, error) {
    95  	ctx, span := trace.StartSpan(ctx, "beacon-chain.web3service.BlockTimeByHeight")
    96  	defer span.End()
    97  	if s.eth1DataFetcher == nil {
    98  		err := errors.New("nil eth1DataFetcher")
    99  		traceutil.AnnotateError(span, err)
   100  		return 0, err
   101  	}
   102  
   103  	header, err := s.eth1DataFetcher.HeaderByNumber(ctx, height)
   104  	if err != nil {
   105  		return 0, errors.Wrap(err, fmt.Sprintf("could not query block with height %d", height.Uint64()))
   106  	}
   107  	return header.Time, nil
   108  }
   109  
   110  // BlockByTimestamp returns the most recent block number up to a given timestamp.
   111  // This is an optimized version with the worst case being O(2*repeatedSearches) number of calls
   112  // while in best case search for the block is performed in O(1).
   113  func (s *Service) BlockByTimestamp(ctx context.Context, time uint64) (*types.HeaderInfo, error) {
   114  	ctx, span := trace.StartSpan(ctx, "beacon-chain.web3service.BlockByTimestamp")
   115  	defer span.End()
   116  
   117  	latestBlkHeight := s.latestEth1Data.BlockHeight
   118  	latestBlkTime := s.latestEth1Data.BlockTime
   119  
   120  	if time > latestBlkTime {
   121  		return nil, errors.New("provided time is later than the current eth1 head")
   122  	}
   123  	// Initialize a pointer to eth1 chain's history to start our search
   124  	// from.
   125  	cursorNum := big.NewInt(int64(latestBlkHeight))
   126  	cursorTime := latestBlkTime
   127  
   128  	numOfBlocks := uint64(0)
   129  	estimatedBlk := cursorNum.Uint64()
   130  	maxTimeBuffer := searchThreshold * params.BeaconConfig().SecondsPerETH1Block
   131  	// Terminate if we cant find an acceptable block after
   132  	// repeated searches.
   133  	for i := 0; i < repeatedSearches; i++ {
   134  		if ctx.Err() != nil {
   135  			return nil, ctx.Err()
   136  		}
   137  		if time > cursorTime+maxTimeBuffer {
   138  			numOfBlocks = (time - cursorTime) / params.BeaconConfig().SecondsPerETH1Block
   139  			// In the event we have an infeasible estimated block, this is a defensive
   140  			// check to ensure it does not exceed rational bounds.
   141  			if cursorNum.Uint64()+numOfBlocks > latestBlkHeight {
   142  				break
   143  			}
   144  			estimatedBlk = cursorNum.Uint64() + numOfBlocks
   145  		} else if time+maxTimeBuffer < cursorTime {
   146  			numOfBlocks = (cursorTime - time) / params.BeaconConfig().SecondsPerETH1Block
   147  			// In the event we have an infeasible number of blocks
   148  			// we exit early.
   149  			if numOfBlocks >= cursorNum.Uint64() {
   150  				break
   151  			}
   152  			estimatedBlk = cursorNum.Uint64() - numOfBlocks
   153  		} else {
   154  			// Exit if we are in the range of
   155  			// time - buffer <= head.time <= time + buffer
   156  			break
   157  		}
   158  		hinfo, err := s.retrieveHeaderInfo(ctx, estimatedBlk)
   159  		if err != nil {
   160  			return nil, err
   161  		}
   162  		cursorNum = hinfo.Number
   163  		cursorTime = hinfo.Time
   164  	}
   165  
   166  	// Exit early if we get the desired block.
   167  	if cursorTime == time {
   168  		return s.retrieveHeaderInfo(ctx, cursorNum.Uint64())
   169  	}
   170  	if cursorTime > time {
   171  		return s.findLessTargetEth1Block(ctx, big.NewInt(int64(estimatedBlk)), time)
   172  	}
   173  	return s.findMoreTargetEth1Block(ctx, big.NewInt(int64(estimatedBlk)), time)
   174  }
   175  
   176  // Performs a search to find a target eth1 block which is earlier than or equal to the
   177  // target time. This method is used when head.time > targetTime
   178  func (s *Service) findLessTargetEth1Block(ctx context.Context, startBlk *big.Int, targetTime uint64) (*types.HeaderInfo, error) {
   179  	for bn := startBlk; ; bn = big.NewInt(0).Sub(bn, big.NewInt(1)) {
   180  		if ctx.Err() != nil {
   181  			return nil, ctx.Err()
   182  		}
   183  		info, err := s.retrieveHeaderInfo(ctx, bn.Uint64())
   184  		if err != nil {
   185  			return nil, err
   186  		}
   187  		if info.Time <= targetTime {
   188  			return info, nil
   189  		}
   190  	}
   191  }
   192  
   193  // Performs a search to find a target eth1 block which is just earlier than or equal to the
   194  // target time. This method is used when head.time < targetTime
   195  func (s *Service) findMoreTargetEth1Block(ctx context.Context, startBlk *big.Int, targetTime uint64) (*types.HeaderInfo, error) {
   196  	for bn := startBlk; ; bn = big.NewInt(0).Add(bn, big.NewInt(1)) {
   197  		if ctx.Err() != nil {
   198  			return nil, ctx.Err()
   199  		}
   200  		info, err := s.retrieveHeaderInfo(ctx, bn.Uint64())
   201  		if err != nil {
   202  			return nil, err
   203  		}
   204  		// Return the last block before we hit the threshold
   205  		// time.
   206  		if info.Time > targetTime {
   207  			return s.retrieveHeaderInfo(ctx, info.Number.Uint64()-1)
   208  		}
   209  		// If time is equal, this is our target block.
   210  		if info.Time == targetTime {
   211  			return info, nil
   212  		}
   213  	}
   214  }
   215  
   216  func (s *Service) retrieveHeaderInfo(ctx context.Context, bNum uint64) (*types.HeaderInfo, error) {
   217  	bn := big.NewInt(int64(bNum))
   218  	exists, info, err := s.headerCache.HeaderInfoByHeight(bn)
   219  	if err != nil {
   220  		return nil, err
   221  	}
   222  	if !exists {
   223  		blk, err := s.eth1DataFetcher.HeaderByNumber(ctx, bn)
   224  		if err != nil {
   225  			return nil, err
   226  		}
   227  		if blk == nil {
   228  			return nil, errors.New("header with the provided number does not exist")
   229  		}
   230  		if err := s.headerCache.AddHeader(blk); err != nil {
   231  			return nil, err
   232  		}
   233  		info, err = types.HeaderToHeaderInfo(blk)
   234  		if err != nil {
   235  			return nil, err
   236  		}
   237  	}
   238  	return info, nil
   239  }