github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/sync/pending_blocks_queue.go (about) 1 package sync 2 3 import ( 4 "context" 5 "encoding/hex" 6 "sort" 7 "sync" 8 "time" 9 10 "github.com/pkg/errors" 11 types "github.com/prysmaticlabs/eth2-types" 12 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 13 p2ptypes "github.com/prysmaticlabs/prysm/beacon-chain/p2p/types" 14 "github.com/prysmaticlabs/prysm/proto/interfaces" 15 "github.com/prysmaticlabs/prysm/shared/bytesutil" 16 "github.com/prysmaticlabs/prysm/shared/params" 17 "github.com/prysmaticlabs/prysm/shared/rand" 18 "github.com/prysmaticlabs/prysm/shared/runutil" 19 "github.com/prysmaticlabs/prysm/shared/slotutil" 20 "github.com/prysmaticlabs/prysm/shared/sszutil" 21 "github.com/prysmaticlabs/prysm/shared/traceutil" 22 "github.com/sirupsen/logrus" 23 "github.com/trailofbits/go-mutexasserts" 24 "go.opencensus.io/trace" 25 ) 26 27 var processPendingBlocksPeriod = slotutil.DivideSlotBy(3 /* times per slot */) 28 29 const maxPeerRequest = 50 30 const numOfTries = 5 31 const maxBlocksPerSlot = 3 32 33 // processes pending blocks queue on every processPendingBlocksPeriod 34 func (s *Service) processPendingBlocksQueue() { 35 // Prevents multiple queue processing goroutines (invoked by RunEvery) from contending for data. 36 locker := new(sync.Mutex) 37 runutil.RunEvery(s.ctx, processPendingBlocksPeriod, func() { 38 locker.Lock() 39 if err := s.processPendingBlocks(s.ctx); err != nil { 40 log.WithError(err).Debug("Could not process pending blocks") 41 } 42 locker.Unlock() 43 }) 44 } 45 46 // processes the block tree inside the queue 47 func (s *Service) processPendingBlocks(ctx context.Context) error { 48 ctx, span := trace.StartSpan(ctx, "processPendingBlocks") 49 defer span.End() 50 51 pids := s.cfg.P2P.Peers().Connected() 52 if err := s.validatePendingSlots(); err != nil { 53 return errors.Wrap(err, "could not validate pending slots") 54 } 55 slots := s.sortedPendingSlots() 56 var parentRoots [][32]byte 57 58 span.AddAttributes( 59 trace.Int64Attribute("numSlots", int64(len(slots))), 60 trace.Int64Attribute("numPeers", int64(len(pids))), 61 ) 62 63 randGen := rand.NewGenerator() 64 for _, slot := range slots { 65 // process the blocks during their respective slot. 66 // otherwise wait for the right slot to process the block. 67 if slot > s.cfg.Chain.CurrentSlot() { 68 continue 69 } 70 71 ctx, span := trace.StartSpan(ctx, "processPendingBlocks.InnerLoop") 72 span.AddAttributes(trace.Int64Attribute("slot", int64(slot))) 73 74 s.pendingQueueLock.RLock() 75 bs := s.pendingBlocksInCache(slot) 76 // Skip if there's no block in the queue. 77 if len(bs) == 0 { 78 s.pendingQueueLock.RUnlock() 79 span.End() 80 continue 81 } 82 s.pendingQueueLock.RUnlock() 83 84 // Loop through the pending queue and mark the potential parent blocks as seen. 85 for _, b := range bs { 86 if b == nil || b.IsNil() || b.Block().IsNil() { 87 span.End() 88 continue 89 } 90 91 s.pendingQueueLock.RLock() 92 inPendingQueue := s.seenPendingBlocks[bytesutil.ToBytes32(b.Block().ParentRoot())] 93 s.pendingQueueLock.RUnlock() 94 95 blkRoot, err := b.Block().HashTreeRoot() 96 if err != nil { 97 traceutil.AnnotateError(span, err) 98 span.End() 99 return err 100 } 101 parentIsBad := s.hasBadBlock(bytesutil.ToBytes32(b.Block().ParentRoot())) 102 blockIsBad := s.hasBadBlock(blkRoot) 103 // Check if parent is a bad block. 104 if parentIsBad || blockIsBad { 105 // Set block as bad if its parent block is bad too. 106 if parentIsBad { 107 s.setBadBlock(ctx, blkRoot) 108 } 109 // Remove block from queue. 110 s.pendingQueueLock.Lock() 111 if err := s.deleteBlockFromPendingQueue(slot, b, blkRoot); err != nil { 112 s.pendingQueueLock.Unlock() 113 return err 114 } 115 s.pendingQueueLock.Unlock() 116 span.End() 117 continue 118 } 119 120 inDB := s.cfg.DB.HasBlock(ctx, bytesutil.ToBytes32(b.Block().ParentRoot())) 121 hasPeer := len(pids) != 0 122 123 // Only request for missing parent block if it's not in DB, not in pending cache 124 // and has peer in the peer list. 125 if !inPendingQueue && !inDB && hasPeer { 126 log.WithFields(logrus.Fields{ 127 "currentSlot": b.Block().Slot(), 128 "parentRoot": hex.EncodeToString(bytesutil.Trunc(b.Block().ParentRoot())), 129 }).Debug("Requesting parent block") 130 parentRoots = append(parentRoots, bytesutil.ToBytes32(b.Block().ParentRoot())) 131 132 span.End() 133 continue 134 } 135 136 if !inDB { 137 span.End() 138 continue 139 } 140 141 if err := s.validateBeaconBlock(ctx, b, blkRoot); err != nil { 142 log.Debugf("Could not validate block from slot %d: %v", b.Block().Slot(), err) 143 s.setBadBlock(ctx, blkRoot) 144 traceutil.AnnotateError(span, err) 145 // In the next iteration of the queue, this block will be removed from 146 // the pending queue as it has been marked as a 'bad' block. 147 span.End() 148 continue 149 } 150 151 if err := s.cfg.Chain.ReceiveBlock(ctx, b, blkRoot); err != nil { 152 log.Debugf("Could not process block from slot %d: %v", b.Block().Slot(), err) 153 s.setBadBlock(ctx, blkRoot) 154 traceutil.AnnotateError(span, err) 155 // In the next iteration of the queue, this block will be removed from 156 // the pending queue as it has been marked as a 'bad' block. 157 span.End() 158 continue 159 } 160 161 s.setSeenBlockIndexSlot(b.Block().Slot(), b.Block().ProposerIndex()) 162 163 // Broadcasting the block again once a node is able to process it. 164 if err := s.cfg.P2P.Broadcast(ctx, b.Proto()); err != nil { 165 log.WithError(err).Debug("Could not broadcast block") 166 } 167 168 s.pendingQueueLock.Lock() 169 if err := s.deleteBlockFromPendingQueue(slot, b, blkRoot); err != nil { 170 return err 171 } 172 s.pendingQueueLock.Unlock() 173 174 log.WithFields(logrus.Fields{ 175 "slot": slot, 176 "blockRoot": hex.EncodeToString(bytesutil.Trunc(blkRoot[:])), 177 }).Debug("Processed pending block and cleared it in cache") 178 179 span.End() 180 } 181 } 182 183 return s.sendBatchRootRequest(ctx, parentRoots, randGen) 184 } 185 186 func (s *Service) sendBatchRootRequest(ctx context.Context, roots [][32]byte, randGen *rand.Rand) error { 187 ctx, span := trace.StartSpan(ctx, "sendBatchRootRequest") 188 defer span.End() 189 190 if len(roots) == 0 { 191 return nil 192 } 193 194 _, bestPeers := s.cfg.P2P.Peers().BestFinalized(maxPeerRequest, s.cfg.Chain.FinalizedCheckpt().Epoch) 195 if len(bestPeers) == 0 { 196 return nil 197 } 198 roots = s.dedupRoots(roots) 199 // Randomly choose a peer to query from our best peers. If that peer cannot return 200 // all the requested blocks, we randomly select another peer. 201 pid := bestPeers[randGen.Int()%len(bestPeers)] 202 for i := 0; i < numOfTries; i++ { 203 req := p2ptypes.BeaconBlockByRootsReq(roots) 204 if len(roots) > int(params.BeaconNetworkConfig().MaxRequestBlocks) { 205 req = roots[:params.BeaconNetworkConfig().MaxRequestBlocks] 206 } 207 if err := s.sendRecentBeaconBlocksRequest(ctx, &req, pid); err != nil { 208 traceutil.AnnotateError(span, err) 209 log.Debugf("Could not send recent block request: %v", err) 210 } 211 newRoots := make([][32]byte, 0, len(roots)) 212 s.pendingQueueLock.RLock() 213 for _, rt := range roots { 214 if !s.seenPendingBlocks[rt] { 215 newRoots = append(newRoots, rt) 216 } 217 } 218 s.pendingQueueLock.RUnlock() 219 if len(newRoots) == 0 { 220 break 221 } 222 // Choosing a new peer with the leftover set of 223 // roots to request. 224 roots = newRoots 225 pid = bestPeers[randGen.Int()%len(bestPeers)] 226 } 227 return nil 228 } 229 230 func (s *Service) sortedPendingSlots() []types.Slot { 231 s.pendingQueueLock.RLock() 232 defer s.pendingQueueLock.RUnlock() 233 234 items := s.slotToPendingBlocks.Items() 235 236 slots := make([]types.Slot, 0, len(items)) 237 for k := range items { 238 slot := cacheKeyToSlot(k) 239 slots = append(slots, slot) 240 } 241 sort.Slice(slots, func(i, j int) bool { 242 return slots[i] < slots[j] 243 }) 244 return slots 245 } 246 247 // validatePendingSlots validates the pending blocks 248 // by their slot. If they are before the current finalized 249 // checkpoint, these blocks are removed from the queue. 250 func (s *Service) validatePendingSlots() error { 251 s.pendingQueueLock.Lock() 252 defer s.pendingQueueLock.Unlock() 253 oldBlockRoots := make(map[[32]byte]bool) 254 255 finalizedEpoch := s.cfg.Chain.FinalizedCheckpt().Epoch 256 if s.slotToPendingBlocks == nil { 257 return errors.New("slotToPendingBlocks cache can't be nil") 258 } 259 items := s.slotToPendingBlocks.Items() 260 for k := range items { 261 slot := cacheKeyToSlot(k) 262 blks := s.pendingBlocksInCache(slot) 263 for _, b := range blks { 264 epoch := helpers.SlotToEpoch(slot) 265 // remove all descendant blocks of old blocks 266 if oldBlockRoots[bytesutil.ToBytes32(b.Block().ParentRoot())] { 267 root, err := b.Block().HashTreeRoot() 268 if err != nil { 269 return err 270 } 271 oldBlockRoots[root] = true 272 if err := s.deleteBlockFromPendingQueue(slot, b, root); err != nil { 273 return err 274 } 275 continue 276 } 277 // don't process old blocks 278 if finalizedEpoch > 0 && epoch <= finalizedEpoch { 279 blkRoot, err := b.Block().HashTreeRoot() 280 if err != nil { 281 return err 282 } 283 oldBlockRoots[blkRoot] = true 284 if err := s.deleteBlockFromPendingQueue(slot, b, blkRoot); err != nil { 285 return err 286 } 287 } 288 } 289 } 290 return nil 291 } 292 293 func (s *Service) clearPendingSlots() { 294 s.pendingQueueLock.Lock() 295 defer s.pendingQueueLock.Unlock() 296 s.slotToPendingBlocks.Flush() 297 s.seenPendingBlocks = make(map[[32]byte]bool) 298 } 299 300 // Delete block from the list from the pending queue using the slot as key. 301 // Note: this helper is not thread safe. 302 func (s *Service) deleteBlockFromPendingQueue(slot types.Slot, b interfaces.SignedBeaconBlock, r [32]byte) error { 303 mutexasserts.AssertRWMutexLocked(&s.pendingQueueLock) 304 305 blks := s.pendingBlocksInCache(slot) 306 if len(blks) == 0 { 307 return nil 308 } 309 310 // Defensive check to ignore nil blocks 311 if err := helpers.VerifyNilBeaconBlock(b); err != nil { 312 return err 313 } 314 315 newBlks := make([]interfaces.SignedBeaconBlock, 0, len(blks)) 316 for _, blk := range blks { 317 if sszutil.DeepEqual(blk.Proto(), b.Proto()) { 318 continue 319 } 320 newBlks = append(newBlks, blk) 321 } 322 if len(newBlks) == 0 { 323 s.slotToPendingBlocks.Delete(slotToCacheKey(slot)) 324 return nil 325 } 326 327 // Decrease exp time in proportion to how many blocks are still in the cache for slot key. 328 d := pendingBlockExpTime / time.Duration(len(newBlks)) 329 if err := s.slotToPendingBlocks.Replace(slotToCacheKey(slot), newBlks, d); err != nil { 330 return err 331 } 332 delete(s.seenPendingBlocks, r) 333 return nil 334 } 335 336 // Insert block to the list in the pending queue using the slot as key. 337 // Note: this helper is not thread safe. 338 func (s *Service) insertBlockToPendingQueue(slot types.Slot, b interfaces.SignedBeaconBlock, r [32]byte) error { 339 mutexasserts.AssertRWMutexLocked(&s.pendingQueueLock) 340 341 if s.seenPendingBlocks[r] { 342 return nil 343 } 344 345 if err := s.addPendingBlockToCache(b); err != nil { 346 return err 347 } 348 349 s.seenPendingBlocks[r] = true 350 return nil 351 } 352 353 // This returns signed beacon blocks given input key from slotToPendingBlocks. 354 func (s *Service) pendingBlocksInCache(slot types.Slot) []interfaces.SignedBeaconBlock { 355 k := slotToCacheKey(slot) 356 value, ok := s.slotToPendingBlocks.Get(k) 357 if !ok { 358 return []interfaces.SignedBeaconBlock{} 359 } 360 blks, ok := value.([]interfaces.SignedBeaconBlock) 361 if !ok { 362 return []interfaces.SignedBeaconBlock{} 363 } 364 return blks 365 } 366 367 // This adds input signed beacon block to slotToPendingBlocks cache. 368 func (s *Service) addPendingBlockToCache(b interfaces.SignedBeaconBlock) error { 369 if err := helpers.VerifyNilBeaconBlock(b); err != nil { 370 return err 371 } 372 373 blks := s.pendingBlocksInCache(b.Block().Slot()) 374 375 if len(blks) >= maxBlocksPerSlot { 376 return nil 377 } 378 379 blks = append(blks, b) 380 k := slotToCacheKey(b.Block().Slot()) 381 s.slotToPendingBlocks.Set(k, blks, pendingBlockExpTime) 382 return nil 383 } 384 385 // This converts input string to slot. 386 func cacheKeyToSlot(s string) types.Slot { 387 b := []byte(s) 388 return bytesutil.BytesToSlotBigEndian(b) 389 } 390 391 // This converts input slot to a key to be used for slotToPendingBlocks cache. 392 func slotToCacheKey(s types.Slot) string { 393 b := bytesutil.SlotToBytesBigEndian(s) 394 return string(b) 395 }