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  }