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 }