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 }