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 }