github.com/amazechain/amc@v0.1.3/internal/sync/rpc_blocks_by_range.go (about)

     1  package sync
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"github.com/amazechain/amc/api/protocol/sync_pb"
     7  	types "github.com/amazechain/amc/common/block"
     8  	p2ptypes "github.com/amazechain/amc/internal/p2p/types"
     9  	"github.com/amazechain/amc/log"
    10  	"github.com/amazechain/amc/utils"
    11  	"github.com/holiman/uint256"
    12  	"time"
    13  
    14  	libp2pcore "github.com/libp2p/go-libp2p/core"
    15  	"github.com/pkg/errors"
    16  	"go.opencensus.io/trace"
    17  )
    18  
    19  // bodiesByRangeRPCHandler looks up the request blocks from the database from a given start block.
    20  func (s *Service) bodiesByRangeRPCHandler(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error {
    21  	ctx, span := trace.StartSpan(ctx, "sync.BodiesByRangeHandler")
    22  	defer span.End()
    23  	ctx, cancel := context.WithTimeout(ctx, respTimeout)
    24  	defer cancel()
    25  	SetRPCStreamDeadlines(stream)
    26  
    27  	// Ticker to stagger out large requests.
    28  	ticker := time.NewTicker(time.Second)
    29  	defer ticker.Stop()
    30  
    31  	m, ok := msg.(*sync_pb.BodiesByRangeRequest)
    32  	if !ok {
    33  		return errors.New("message is not type *pb.BeaconBlockByRangeRequest")
    34  	}
    35  	if err := s.validateRangeRequest(m); err != nil {
    36  		s.writeErrorResponseToStream(responseCodeInvalidRequest, err.Error(), stream)
    37  		s.cfg.p2p.Peers().Scorers().BadResponsesScorer().Increment(stream.Conn().RemotePeer())
    38  		//tracing.AnnotateError(span, err)
    39  		return err
    40  	}
    41  	// Only have range requests with a step of 1 being processed.
    42  	if m.Step > 1 {
    43  		m.Step = 1
    44  	}
    45  	// The initial count for the first batch to be returned back.
    46  	count := m.Count
    47  	allowedBlocksPerSecond := uint64(s.cfg.p2p.GetConfig().P2PLimit.BlockBatchLimit)
    48  	if count > allowedBlocksPerSecond {
    49  		count = allowedBlocksPerSecond
    50  	}
    51  	// initial batch start and end slots to be returned to remote peer.
    52  	startBlockNumber := utils.ConvertH256ToUint256Int(m.StartBlockNumber)
    53  	endBlockNumber := new(uint256.Int).AddUint64(startBlockNumber, m.Step*(count-1))
    54  
    55  	// The final requested slot from remote peer.
    56  	endReqBlockNumber := new(uint256.Int).AddUint64(startBlockNumber, m.Step*(m.Count-1))
    57  
    58  	blockLimiter, err := s.rateLimiter.topicCollector(string(stream.Protocol()))
    59  	if err != nil {
    60  		return err
    61  	}
    62  	remainingBucketCapacity := blockLimiter.Remaining(stream.Conn().RemotePeer().String())
    63  	span.AddAttributes(
    64  		trace.Int64Attribute("start", int64(startBlockNumber.Uint64())), // lint:ignore uintcast -- This conversion is OK for tracing.
    65  		trace.Int64Attribute("end", int64(endReqBlockNumber.Uint64())),  // lint:ignore uintcast -- This conversion is OK for tracing.
    66  		trace.Int64Attribute("step", int64(m.Step)),
    67  		trace.Int64Attribute("count", int64(m.Count)),
    68  		trace.StringAttribute("peer", stream.Conn().RemotePeer().Pretty()),
    69  		trace.Int64Attribute("remaining_capacity", remainingBucketCapacity),
    70  	)
    71  	for startBlockNumber.Cmp(endReqBlockNumber) <= 0 {
    72  		if err := s.rateLimiter.validateRequest(stream, allowedBlocksPerSecond); err != nil {
    73  			//tracing.AnnotateError(span, err)
    74  			return err
    75  		}
    76  
    77  		if new(uint256.Int).Sub(endBlockNumber, startBlockNumber).Uint64() > rangeLimit {
    78  			s.writeErrorResponseToStream(responseCodeInvalidRequest, p2ptypes.ErrInvalidRequest.Error(), stream)
    79  			err := p2ptypes.ErrInvalidRequest
    80  			//tracing.AnnotateError(span, err)
    81  			return err
    82  		}
    83  
    84  		err := s.writeBodiesRangeToStream(ctx, startBlockNumber, endBlockNumber, m.Step, stream)
    85  		if err != nil && !errors.Is(err, p2ptypes.ErrInvalidParent) {
    86  			return err
    87  		}
    88  		// Reduce capacity of peer in the rate limiter first.
    89  		// Decrease allowed blocks capacity by the number of streamed blocks.
    90  		if startBlockNumber.Cmp(endBlockNumber) <= 0 {
    91  			s.rateLimiter.add(stream, int64(1+new(uint256.Int).Div(new(uint256.Int).Sub(endBlockNumber, startBlockNumber), new(uint256.Int).SetUint64(m.Step)).Uint64()))
    92  		}
    93  		// Exit in the event we have a disjoint chain to
    94  		// return.
    95  		if errors.Is(err, p2ptypes.ErrInvalidParent) {
    96  			break
    97  		}
    98  
    99  		// Recalculate start and end slots for the next batch to be returned to the remote peer.
   100  		startBlockNumber = new(uint256.Int).AddUint64(endBlockNumber, m.Step)
   101  
   102  		endBlockNumber = new(uint256.Int).AddUint64(startBlockNumber, m.Step*(allowedBlocksPerSecond-1))
   103  		if endBlockNumber.Cmp(endReqBlockNumber) == 1 {
   104  			endBlockNumber = endReqBlockNumber
   105  		}
   106  
   107  		// do not wait if all blocks have already been sent.
   108  		if startBlockNumber.Cmp(endReqBlockNumber) == 1 {
   109  			break
   110  		}
   111  
   112  		// wait for ticker before resuming streaming blocks to remote peer.
   113  		<-ticker.C
   114  	}
   115  	closeStream(stream)
   116  	return nil
   117  }
   118  
   119  func (s *Service) writeBodiesRangeToStream(ctx context.Context, startSlot, endSlot *uint256.Int, step uint64, stream libp2pcore.Stream) error {
   120  	ctx, span := trace.StartSpan(ctx, "sync.WriteBodiesRangeToStream")
   121  	defer span.End()
   122  
   123  	var blks = make([]types.IBlock, 0)
   124  	for ; startSlot.Cmp(endSlot) <= 0; startSlot = startSlot.AddUint64(startSlot, step) {
   125  		b, err := s.cfg.chain.GetBlockByNumber(startSlot)
   126  		if err != nil {
   127  			//tracing.AnnotateError(span, err)
   128  			log.Warn("Could not retrieve blocks", "err", err)
   129  			s.writeErrorResponseToStream(responseCodeServerError, p2ptypes.ErrGeneric.Error(), stream)
   130  			return err
   131  		}
   132  		if b == nil {
   133  			log.Warn("Could not retrieve blocks", "err", fmt.Errorf("block #%d not found", startSlot.Uint64()))
   134  			s.writeErrorResponseToStream(responseCodeServerError, p2ptypes.ErrInvalidBlockNr.Error(), stream)
   135  			return err
   136  		}
   137  
   138  		blks = append(blks, b)
   139  	}
   140  
   141  	start := time.Now()
   142  
   143  	for _, b := range blks {
   144  		if chunkErr := s.chunkBlockWriter(stream, b); chunkErr != nil {
   145  			log.Debug("Could not send a chunked response", "err", chunkErr)
   146  			s.writeErrorResponseToStream(responseCodeServerError, p2ptypes.ErrGeneric.Error(), stream)
   147  			//tracing.AnnotateError(span, chunkErr)
   148  			return chunkErr
   149  		}
   150  	}
   151  
   152  	rpcBlocksByRangeResponseLatency.Observe(float64(time.Since(start).Milliseconds()))
   153  	// Return error in the event we have an invalid parent.
   154  	return nil
   155  }
   156  
   157  func (s *Service) validateRangeRequest(r *sync_pb.BodiesByRangeRequest) error {
   158  	startSlot := utils.ConvertH256ToUint256Int(r.StartBlockNumber)
   159  	count := r.Count
   160  	step := r.Step
   161  
   162  	// Add a buffer for possible large range requests from nodes syncing close to the
   163  	// head of the chain.
   164  	buffer := rangeLimit * 2
   165  	highestExpectedBlockNumber := new(uint256.Int).AddUint64(s.cfg.chain.CurrentBlock().Number64(), uint64(buffer))
   166  
   167  	// Ensure all request params are within appropriate bounds
   168  	if count == 0 || count > maxRequestBlocks {
   169  		return p2ptypes.ErrInvalidRequest
   170  	}
   171  
   172  	if step == 0 || step > rangeLimit {
   173  		return p2ptypes.ErrInvalidRequest
   174  	}
   175  
   176  	if startSlot.Cmp(highestExpectedBlockNumber) == 1 {
   177  		return p2ptypes.ErrInvalidRequest
   178  	}
   179  
   180  	endSlot := new(uint256.Int).AddUint64(startSlot, step*(count-1))
   181  	if endSlot.Uint64()-startSlot.Uint64() > rangeLimit {
   182  		return p2ptypes.ErrInvalidRequest
   183  	}
   184  	return nil
   185  }
   186  
   187  func (s *Service) writeErrorResponseToStream(responseCode byte, reason string, stream libp2pcore.Stream) {
   188  	writeErrorResponseToStream(responseCode, reason, stream, s.cfg.p2p)
   189  }