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  }