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

     1  package sync
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	libp2pcore "github.com/libp2p/go-libp2p-core"
     8  	"github.com/pkg/errors"
     9  	types "github.com/prysmaticlabs/eth2-types"
    10  	"github.com/prysmaticlabs/prysm/beacon-chain/db/filters"
    11  	p2ptypes "github.com/prysmaticlabs/prysm/beacon-chain/p2p/types"
    12  	"github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags"
    13  	pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
    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/traceutil"
    18  	"go.opencensus.io/trace"
    19  )
    20  
    21  // beaconBlocksByRangeRPCHandler looks up the request blocks from the database from a given start block.
    22  func (s *Service) beaconBlocksByRangeRPCHandler(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error {
    23  	ctx, span := trace.StartSpan(ctx, "sync.BeaconBlocksByRangeHandler")
    24  	defer span.End()
    25  	ctx, cancel := context.WithTimeout(ctx, respTimeout)
    26  	defer cancel()
    27  	SetRPCStreamDeadlines(stream)
    28  
    29  	// Ticker to stagger out large requests.
    30  	ticker := time.NewTicker(time.Second)
    31  	defer ticker.Stop()
    32  
    33  	m, ok := msg.(*pb.BeaconBlocksByRangeRequest)
    34  	if !ok {
    35  		return errors.New("message is not type *pb.BeaconBlockByRangeRequest")
    36  	}
    37  	if err := s.validateRangeRequest(m); err != nil {
    38  		s.writeErrorResponseToStream(responseCodeInvalidRequest, err.Error(), stream)
    39  		s.cfg.P2P.Peers().Scorers().BadResponsesScorer().Increment(stream.Conn().RemotePeer())
    40  		traceutil.AnnotateError(span, err)
    41  		return err
    42  	}
    43  
    44  	// The initial count for the first batch to be returned back.
    45  	count := m.Count
    46  	allowedBlocksPerSecond := uint64(flags.Get().BlockBatchLimit)
    47  	if count > allowedBlocksPerSecond {
    48  		count = allowedBlocksPerSecond
    49  	}
    50  	// initial batch start and end slots to be returned to remote peer.
    51  	startSlot := m.StartSlot
    52  	endSlot := startSlot.Add(m.Step * (count - 1))
    53  
    54  	// The final requested slot from remote peer.
    55  	endReqSlot := startSlot.Add(m.Step * (m.Count - 1))
    56  
    57  	blockLimiter, err := s.rateLimiter.topicCollector(string(stream.Protocol()))
    58  	if err != nil {
    59  		return err
    60  	}
    61  	remainingBucketCapacity := blockLimiter.Remaining(stream.Conn().RemotePeer().String())
    62  	span.AddAttributes(
    63  		trace.Int64Attribute("start", int64(startSlot)),
    64  		trace.Int64Attribute("end", int64(endReqSlot)),
    65  		trace.Int64Attribute("step", int64(m.Step)),
    66  		trace.Int64Attribute("count", int64(m.Count)),
    67  		trace.StringAttribute("peer", stream.Conn().RemotePeer().Pretty()),
    68  		trace.Int64Attribute("remaining_capacity", remainingBucketCapacity),
    69  	)
    70  	// prevRoot is used to ensure that returned chains are strictly linear for singular steps
    71  	// by comparing the previous root of the block in the list with the current block's parent.
    72  	var prevRoot [32]byte
    73  	for startSlot <= endReqSlot {
    74  		if err := s.rateLimiter.validateRequest(stream, allowedBlocksPerSecond); err != nil {
    75  			traceutil.AnnotateError(span, err)
    76  			return err
    77  		}
    78  
    79  		if endSlot-startSlot > rangeLimit {
    80  			s.writeErrorResponseToStream(responseCodeInvalidRequest, p2ptypes.ErrInvalidRequest.Error(), stream)
    81  			err := p2ptypes.ErrInvalidRequest
    82  			traceutil.AnnotateError(span, err)
    83  			return err
    84  		}
    85  
    86  		err := s.writeBlockRangeToStream(ctx, startSlot, endSlot, m.Step, &prevRoot, stream)
    87  		if err != nil && !errors.Is(err, p2ptypes.ErrInvalidParent) {
    88  			return err
    89  		}
    90  		// Reduce capacity of peer in the rate limiter first.
    91  		// Decrease allowed blocks capacity by the number of streamed blocks.
    92  		if startSlot <= endSlot {
    93  			s.rateLimiter.add(stream, int64(1+endSlot.SubSlot(startSlot).Div(m.Step)))
    94  		}
    95  		// Exit in the event we have a disjoint chain to
    96  		// return.
    97  		if errors.Is(err, p2ptypes.ErrInvalidParent) {
    98  			break
    99  		}
   100  
   101  		// Recalculate start and end slots for the next batch to be returned to the remote peer.
   102  		startSlot = endSlot.Add(m.Step)
   103  		endSlot = startSlot.Add(m.Step * (allowedBlocksPerSecond - 1))
   104  		if endSlot > endReqSlot {
   105  			endSlot = endReqSlot
   106  		}
   107  
   108  		// do not wait if all blocks have already been sent.
   109  		if startSlot > endReqSlot {
   110  			break
   111  		}
   112  
   113  		// wait for ticker before resuming streaming blocks to remote peer.
   114  		<-ticker.C
   115  	}
   116  	closeStream(stream, log)
   117  	return nil
   118  }
   119  
   120  func (s *Service) writeBlockRangeToStream(ctx context.Context, startSlot, endSlot types.Slot, step uint64,
   121  	prevRoot *[32]byte, stream libp2pcore.Stream) error {
   122  	ctx, span := trace.StartSpan(ctx, "sync.WriteBlockRangeToStream")
   123  	defer span.End()
   124  
   125  	filter := filters.NewFilter().SetStartSlot(startSlot).SetEndSlot(endSlot).SetSlotStep(step)
   126  	blks, roots, err := s.cfg.DB.Blocks(ctx, filter)
   127  	if err != nil {
   128  		log.WithError(err).Debug("Could not retrieve blocks")
   129  		s.writeErrorResponseToStream(responseCodeServerError, p2ptypes.ErrGeneric.Error(), stream)
   130  		traceutil.AnnotateError(span, err)
   131  		return err
   132  	}
   133  	// handle genesis case
   134  	if startSlot == 0 {
   135  		genBlock, genRoot, err := s.retrieveGenesisBlock(ctx)
   136  		if err != nil {
   137  			log.WithError(err).Debug("Could not retrieve genesis block")
   138  			s.writeErrorResponseToStream(responseCodeServerError, p2ptypes.ErrGeneric.Error(), stream)
   139  			traceutil.AnnotateError(span, err)
   140  			return err
   141  		}
   142  		blks = append([]interfaces.SignedBeaconBlock{genBlock}, blks...)
   143  		roots = append([][32]byte{genRoot}, roots...)
   144  	}
   145  	// Filter and sort our retrieved blocks, so that
   146  	// we only return valid sets of blocks.
   147  	blks, roots, err = s.dedupBlocksAndRoots(blks, roots)
   148  	if err != nil {
   149  		s.writeErrorResponseToStream(responseCodeServerError, p2ptypes.ErrGeneric.Error(), stream)
   150  		traceutil.AnnotateError(span, err)
   151  		return err
   152  	}
   153  	blks, roots = s.sortBlocksAndRoots(blks, roots)
   154  
   155  	blks, err = s.filterBlocks(ctx, blks, roots, prevRoot, step, startSlot)
   156  	if err != nil && err != p2ptypes.ErrInvalidParent {
   157  		s.writeErrorResponseToStream(responseCodeServerError, p2ptypes.ErrGeneric.Error(), stream)
   158  		traceutil.AnnotateError(span, err)
   159  		return err
   160  	}
   161  	for _, b := range blks {
   162  		if b == nil || b.IsNil() || b.Block().IsNil() {
   163  			continue
   164  		}
   165  		if chunkErr := s.chunkWriter(stream, b.Proto()); chunkErr != nil {
   166  			log.WithError(chunkErr).Debug("Could not send a chunked response")
   167  			s.writeErrorResponseToStream(responseCodeServerError, p2ptypes.ErrGeneric.Error(), stream)
   168  			traceutil.AnnotateError(span, chunkErr)
   169  			return chunkErr
   170  		}
   171  
   172  	}
   173  	// Return error in the event we have an invalid parent.
   174  	return err
   175  }
   176  
   177  func (s *Service) validateRangeRequest(r *pb.BeaconBlocksByRangeRequest) error {
   178  	startSlot := r.StartSlot
   179  	count := r.Count
   180  	step := r.Step
   181  
   182  	maxRequestBlocks := params.BeaconNetworkConfig().MaxRequestBlocks
   183  	// Add a buffer for possible large range requests from nodes syncing close to the
   184  	// head of the chain.
   185  	buffer := rangeLimit * 2
   186  	highestExpectedSlot := s.cfg.Chain.CurrentSlot().Add(uint64(buffer))
   187  
   188  	// Ensure all request params are within appropriate bounds
   189  	if count == 0 || count > maxRequestBlocks {
   190  		return p2ptypes.ErrInvalidRequest
   191  	}
   192  
   193  	if step == 0 || step > rangeLimit {
   194  		return p2ptypes.ErrInvalidRequest
   195  	}
   196  
   197  	if startSlot > highestExpectedSlot {
   198  		return p2ptypes.ErrInvalidRequest
   199  	}
   200  
   201  	endSlot := startSlot.Add(step * (count - 1))
   202  	if endSlot-startSlot > rangeLimit {
   203  		return p2ptypes.ErrInvalidRequest
   204  	}
   205  	return nil
   206  }
   207  
   208  // filters all the provided blocks to ensure they are canonical
   209  // and are strictly linear.
   210  func (s *Service) filterBlocks(ctx context.Context, blks []interfaces.SignedBeaconBlock, roots [][32]byte, prevRoot *[32]byte,
   211  	step uint64, startSlot types.Slot) ([]interfaces.SignedBeaconBlock, error) {
   212  	if len(blks) != len(roots) {
   213  		return nil, errors.New("input blks and roots are diff lengths")
   214  	}
   215  
   216  	newBlks := make([]interfaces.SignedBeaconBlock, 0, len(blks))
   217  	for i, b := range blks {
   218  		isCanonical, err := s.cfg.Chain.IsCanonical(ctx, roots[i])
   219  		if err != nil {
   220  			return nil, err
   221  		}
   222  		parentValid := *prevRoot != [32]byte{}
   223  		isLinear := *prevRoot == bytesutil.ToBytes32(b.Block().ParentRoot())
   224  		isSingular := step == 1
   225  		slotDiff, err := b.Block().Slot().SafeSubSlot(startSlot)
   226  		if err != nil {
   227  			return nil, err
   228  		}
   229  		slotDiff, err = slotDiff.SafeMod(step)
   230  		if err != nil {
   231  			return nil, err
   232  		}
   233  		isRequestedSlotStep := slotDiff == 0
   234  		if isRequestedSlotStep && isCanonical {
   235  			// Exit early if our valid block is non linear.
   236  			if parentValid && isSingular && !isLinear {
   237  				return newBlks, p2ptypes.ErrInvalidParent
   238  			}
   239  			newBlks = append(newBlks, blks[i])
   240  			// Set the previous root as the
   241  			// newly added block's root
   242  			currRoot := roots[i]
   243  			prevRoot = &currRoot
   244  		}
   245  	}
   246  	return newBlks, nil
   247  }
   248  
   249  func (s *Service) writeErrorResponseToStream(responseCode byte, reason string, stream libp2pcore.Stream) {
   250  	writeErrorResponseToStream(responseCode, reason, stream, s.cfg.P2P)
   251  }
   252  
   253  func (s *Service) retrieveGenesisBlock(ctx context.Context) (interfaces.SignedBeaconBlock, [32]byte, error) {
   254  	genBlock, err := s.cfg.DB.GenesisBlock(ctx)
   255  	if err != nil {
   256  		return nil, [32]byte{}, err
   257  	}
   258  	genRoot, err := genBlock.Block().HashTreeRoot()
   259  	if err != nil {
   260  		return nil, [32]byte{}, err
   261  	}
   262  	return genBlock, genRoot, nil
   263  }