github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/sync/rpc_status.go (about) 1 package sync 2 3 import ( 4 "bytes" 5 "context" 6 "sync" 7 "time" 8 9 libp2pcore "github.com/libp2p/go-libp2p-core" 10 "github.com/libp2p/go-libp2p-core/network" 11 "github.com/libp2p/go-libp2p-core/peer" 12 "github.com/pkg/errors" 13 types "github.com/prysmaticlabs/eth2-types" 14 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 15 "github.com/prysmaticlabs/prysm/beacon-chain/p2p" 16 "github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers" 17 p2ptypes "github.com/prysmaticlabs/prysm/beacon-chain/p2p/types" 18 "github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags" 19 pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" 20 "github.com/prysmaticlabs/prysm/shared/bytesutil" 21 "github.com/prysmaticlabs/prysm/shared/params" 22 "github.com/prysmaticlabs/prysm/shared/runutil" 23 "github.com/prysmaticlabs/prysm/shared/slotutil" 24 "github.com/prysmaticlabs/prysm/shared/timeutils" 25 "github.com/sirupsen/logrus" 26 ) 27 28 // maintainPeerStatuses by infrequently polling peers for their latest status. 29 func (s *Service) maintainPeerStatuses() { 30 // Run twice per epoch. 31 interval := time.Duration(params.BeaconConfig().SlotsPerEpoch.Div(2).Mul(params.BeaconConfig().SecondsPerSlot)) * time.Second 32 runutil.RunEvery(s.ctx, interval, func() { 33 wg := new(sync.WaitGroup) 34 for _, pid := range s.cfg.P2P.Peers().Connected() { 35 wg.Add(1) 36 go func(id peer.ID) { 37 defer wg.Done() 38 // If our peer status has not been updated correctly we disconnect over here 39 // and set the connection state over here instead. 40 if s.cfg.P2P.Host().Network().Connectedness(id) != network.Connected { 41 s.cfg.P2P.Peers().SetConnectionState(id, peers.PeerDisconnecting) 42 if err := s.cfg.P2P.Disconnect(id); err != nil { 43 log.Debugf("Error when disconnecting with peer: %v", err) 44 } 45 s.cfg.P2P.Peers().SetConnectionState(id, peers.PeerDisconnected) 46 return 47 } 48 // Disconnect from peers that are considered bad by any of the registered scorers. 49 if s.cfg.P2P.Peers().IsBad(id) { 50 s.disconnectBadPeer(s.ctx, id) 51 return 52 } 53 // If the status hasn't been updated in the recent interval time. 54 lastUpdated, err := s.cfg.P2P.Peers().ChainStateLastUpdated(id) 55 if err != nil { 56 // Peer has vanished; nothing to do. 57 return 58 } 59 if timeutils.Now().After(lastUpdated.Add(interval)) { 60 if err := s.reValidatePeer(s.ctx, id); err != nil { 61 log.WithField("peer", id).WithError(err).Debug("Could not revalidate peer") 62 s.cfg.P2P.Peers().Scorers().BadResponsesScorer().Increment(id) 63 } 64 } 65 }(pid) 66 } 67 // Wait for all status checks to finish and then proceed onwards to 68 // pruning excess peers. 69 wg.Wait() 70 peerIds := s.cfg.P2P.Peers().PeersToPrune() 71 peerIds = s.filterNeededPeers(peerIds) 72 for _, id := range peerIds { 73 if err := s.sendGoodByeAndDisconnect(s.ctx, p2ptypes.GoodbyeCodeTooManyPeers, id); err != nil { 74 log.WithField("peer", id).WithError(err).Debug("Could not disconnect with peer") 75 } 76 } 77 }) 78 } 79 80 // resyncIfBehind checks periodically to see if we are in normal sync but have fallen behind our peers 81 // by more than an epoch, in which case we attempt a resync using the initial sync method to catch up. 82 func (s *Service) resyncIfBehind() { 83 millisecondsPerEpoch := int64(params.BeaconConfig().SlotsPerEpoch.Mul(1000).Mul(params.BeaconConfig().SecondsPerSlot)) 84 // Run sixteen times per epoch. 85 interval := time.Duration(millisecondsPerEpoch/16) * time.Millisecond 86 runutil.RunEvery(s.ctx, interval, func() { 87 if s.shouldReSync() { 88 syncedEpoch := helpers.SlotToEpoch(s.cfg.Chain.HeadSlot()) 89 // Factor number of expected minimum sync peers, to make sure that enough peers are 90 // available to resync (some peers may go away between checking non-finalized peers and 91 // actual resyncing). 92 highestEpoch, _ := s.cfg.P2P.Peers().BestNonFinalized(flags.Get().MinimumSyncPeers*2, syncedEpoch) 93 // Check if the current node is more than 1 epoch behind. 94 if highestEpoch > (syncedEpoch + 1) { 95 log.WithFields(logrus.Fields{ 96 "currentEpoch": helpers.SlotToEpoch(s.cfg.Chain.CurrentSlot()), 97 "syncedEpoch": syncedEpoch, 98 "peersEpoch": highestEpoch, 99 }).Info("Fallen behind peers; reverting to initial sync to catch up") 100 numberOfTimesResyncedCounter.Inc() 101 s.clearPendingSlots() 102 if err := s.cfg.InitialSync.Resync(); err != nil { 103 log.Errorf("Could not resync chain: %v", err) 104 } 105 } 106 } 107 }) 108 } 109 110 // shouldReSync returns true if the node is not syncing and falls behind two epochs. 111 func (s *Service) shouldReSync() bool { 112 syncedEpoch := helpers.SlotToEpoch(s.cfg.Chain.HeadSlot()) 113 currentEpoch := helpers.SlotToEpoch(s.cfg.Chain.CurrentSlot()) 114 prevEpoch := types.Epoch(0) 115 if currentEpoch > 1 { 116 prevEpoch = currentEpoch - 1 117 } 118 return s.cfg.InitialSync != nil && !s.cfg.InitialSync.Syncing() && syncedEpoch < prevEpoch 119 } 120 121 // sendRPCStatusRequest for a given topic with an expected protobuf message type. 122 func (s *Service) sendRPCStatusRequest(ctx context.Context, id peer.ID) error { 123 ctx, cancel := context.WithTimeout(ctx, respTimeout) 124 defer cancel() 125 126 headRoot, err := s.cfg.Chain.HeadRoot(ctx) 127 if err != nil { 128 return err 129 } 130 131 forkDigest, err := s.forkDigest() 132 if err != nil { 133 return err 134 } 135 resp := &pb.Status{ 136 ForkDigest: forkDigest[:], 137 FinalizedRoot: s.cfg.Chain.FinalizedCheckpt().Root, 138 FinalizedEpoch: s.cfg.Chain.FinalizedCheckpt().Epoch, 139 HeadRoot: headRoot, 140 HeadSlot: s.cfg.Chain.HeadSlot(), 141 } 142 stream, err := s.cfg.P2P.Send(ctx, resp, p2p.RPCStatusTopicV1, id) 143 if err != nil { 144 return err 145 } 146 defer closeStream(stream, log) 147 148 code, errMsg, err := ReadStatusCode(stream, s.cfg.P2P.Encoding()) 149 if err != nil { 150 return err 151 } 152 153 if code != 0 { 154 s.cfg.P2P.Peers().Scorers().BadResponsesScorer().Increment(id) 155 return errors.New(errMsg) 156 } 157 // No-op for now with the rpc context. 158 _, err = readContextFromStream(stream, s.cfg.Chain) 159 if err != nil { 160 return err 161 } 162 msg := &pb.Status{} 163 if err := s.cfg.P2P.Encoding().DecodeWithMaxLength(stream, msg); err != nil { 164 return err 165 } 166 167 // If validation fails, validation error is logged, and peer status scorer will mark peer as bad. 168 err = s.validateStatusMessage(ctx, msg) 169 s.cfg.P2P.Peers().Scorers().PeerStatusScorer().SetPeerStatus(id, msg, err) 170 if s.cfg.P2P.Peers().IsBad(id) { 171 s.disconnectBadPeer(s.ctx, id) 172 } 173 return err 174 } 175 176 func (s *Service) reValidatePeer(ctx context.Context, id peer.ID) error { 177 s.cfg.P2P.Peers().Scorers().PeerStatusScorer().SetHeadSlot(s.cfg.Chain.HeadSlot()) 178 if err := s.sendRPCStatusRequest(ctx, id); err != nil { 179 return err 180 } 181 // Do not return an error for ping requests. 182 if err := s.sendPingRequest(ctx, id); err != nil { 183 log.WithError(err).Debug("Could not ping peer") 184 } 185 return nil 186 } 187 188 // statusRPCHandler reads the incoming Status RPC from the peer and responds with our version of a status message. 189 // This handler will disconnect any peer that does not match our fork version. 190 func (s *Service) statusRPCHandler(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error { 191 ctx, cancel := context.WithTimeout(ctx, ttfbTimeout) 192 defer cancel() 193 SetRPCStreamDeadlines(stream) 194 log := log.WithField("handler", "status") 195 m, ok := msg.(*pb.Status) 196 if !ok { 197 return errors.New("message is not type *pb.Status") 198 } 199 if err := s.rateLimiter.validateRequest(stream, 1); err != nil { 200 return err 201 } 202 s.rateLimiter.add(stream, 1) 203 204 remotePeer := stream.Conn().RemotePeer() 205 if err := s.validateStatusMessage(ctx, m); err != nil { 206 log.WithFields(logrus.Fields{ 207 "peer": remotePeer, 208 "error": err, 209 }).Debug("Invalid status message from peer") 210 211 respCode := byte(0) 212 switch err { 213 case p2ptypes.ErrGeneric: 214 respCode = responseCodeServerError 215 case p2ptypes.ErrWrongForkDigestVersion: 216 // Respond with our status and disconnect with the peer. 217 s.cfg.P2P.Peers().SetChainState(remotePeer, m) 218 if err := s.respondWithStatus(ctx, stream); err != nil { 219 return err 220 } 221 // Close before disconnecting, and wait for the other end to ack our response. 222 closeStreamAndWait(stream, log) 223 if err := s.sendGoodByeAndDisconnect(ctx, p2ptypes.GoodbyeCodeWrongNetwork, remotePeer); err != nil { 224 return err 225 } 226 return nil 227 default: 228 respCode = responseCodeInvalidRequest 229 s.cfg.P2P.Peers().Scorers().BadResponsesScorer().Increment(remotePeer) 230 } 231 232 originalErr := err 233 resp, err := s.generateErrorResponse(respCode, err.Error()) 234 if err != nil { 235 log.WithError(err).Debug("Could not generate a response error") 236 } else if _, err := stream.Write(resp); err != nil { 237 // The peer may already be ignoring us, as we disagree on fork version, so log this as debug only. 238 log.WithError(err).Debug("Could not write to stream") 239 } 240 closeStreamAndWait(stream, log) 241 if err := s.sendGoodByeAndDisconnect(ctx, p2ptypes.GoodbyeCodeGenericError, remotePeer); err != nil { 242 return err 243 } 244 return originalErr 245 } 246 s.cfg.P2P.Peers().SetChainState(remotePeer, m) 247 248 if err := s.respondWithStatus(ctx, stream); err != nil { 249 return err 250 } 251 closeStream(stream, log) 252 return nil 253 } 254 255 func (s *Service) respondWithStatus(ctx context.Context, stream network.Stream) error { 256 headRoot, err := s.cfg.Chain.HeadRoot(ctx) 257 if err != nil { 258 return err 259 } 260 261 forkDigest, err := s.forkDigest() 262 if err != nil { 263 return err 264 } 265 resp := &pb.Status{ 266 ForkDigest: forkDigest[:], 267 FinalizedRoot: s.cfg.Chain.FinalizedCheckpt().Root, 268 FinalizedEpoch: s.cfg.Chain.FinalizedCheckpt().Epoch, 269 HeadRoot: headRoot, 270 HeadSlot: s.cfg.Chain.HeadSlot(), 271 } 272 273 if _, err := stream.Write([]byte{responseCodeSuccess}); err != nil { 274 log.WithError(err).Debug("Could not write to stream") 275 } 276 _, err = s.cfg.P2P.Encoding().EncodeWithMaxLength(stream, resp) 277 return err 278 } 279 280 func (s *Service) validateStatusMessage(ctx context.Context, msg *pb.Status) error { 281 forkDigest, err := s.forkDigest() 282 if err != nil { 283 return err 284 } 285 if !bytes.Equal(forkDigest[:], msg.ForkDigest) { 286 return p2ptypes.ErrWrongForkDigestVersion 287 } 288 genesis := s.cfg.Chain.GenesisTime() 289 finalizedEpoch := s.cfg.Chain.FinalizedCheckpt().Epoch 290 maxEpoch := slotutil.EpochsSinceGenesis(genesis) 291 // It would take a minimum of 2 epochs to finalize a 292 // previous epoch 293 maxFinalizedEpoch := types.Epoch(0) 294 if maxEpoch > 2 { 295 maxFinalizedEpoch = maxEpoch - 2 296 } 297 if msg.FinalizedEpoch > maxFinalizedEpoch { 298 return p2ptypes.ErrInvalidEpoch 299 } 300 // Exit early if the peer's finalized epoch 301 // is less than that of the remote peer's. 302 if finalizedEpoch < msg.FinalizedEpoch { 303 return nil 304 } 305 finalizedAtGenesis := msg.FinalizedEpoch == 0 306 rootIsEqual := bytes.Equal(params.BeaconConfig().ZeroHash[:], msg.FinalizedRoot) 307 // If peer is at genesis with the correct genesis root hash we exit. 308 if finalizedAtGenesis && rootIsEqual { 309 return nil 310 } 311 if !s.cfg.DB.IsFinalizedBlock(ctx, bytesutil.ToBytes32(msg.FinalizedRoot)) { 312 return p2ptypes.ErrInvalidFinalizedRoot 313 } 314 blk, err := s.cfg.DB.Block(ctx, bytesutil.ToBytes32(msg.FinalizedRoot)) 315 if err != nil { 316 return p2ptypes.ErrGeneric 317 } 318 if blk == nil || blk.IsNil() { 319 return p2ptypes.ErrGeneric 320 } 321 if helpers.SlotToEpoch(blk.Block().Slot()) == msg.FinalizedEpoch { 322 return nil 323 } 324 325 startSlot, err := helpers.StartSlot(msg.FinalizedEpoch) 326 if err != nil { 327 return p2ptypes.ErrGeneric 328 } 329 if startSlot > blk.Block().Slot() { 330 childBlock, err := s.cfg.DB.FinalizedChildBlock(ctx, bytesutil.ToBytes32(msg.FinalizedRoot)) 331 if err != nil { 332 return p2ptypes.ErrGeneric 333 } 334 // Is a valid finalized block if no 335 // other child blocks exist yet. 336 if childBlock == nil || childBlock.IsNil() { 337 return nil 338 } 339 // If child finalized block also has a smaller or 340 // equal slot number we return an error. 341 if startSlot >= childBlock.Block().Slot() { 342 return p2ptypes.ErrInvalidEpoch 343 } 344 return nil 345 } 346 return p2ptypes.ErrInvalidEpoch 347 }