github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/sync/initial-sync/service.go (about)

     1  // Package initialsync includes all initial block download and processing
     2  // logic for the beacon node, using a round robin strategy and a finite-state-machine
     3  // to handle edge-cases in a beacon node's sync status.
     4  package initialsync
     5  
     6  import (
     7  	"context"
     8  	"time"
     9  
    10  	"github.com/paulbellamy/ratecounter"
    11  	"github.com/pkg/errors"
    12  	"github.com/prysmaticlabs/prysm/beacon-chain/blockchain"
    13  	"github.com/prysmaticlabs/prysm/beacon-chain/core/feed"
    14  	blockfeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/block"
    15  	statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state"
    16  	"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
    17  	"github.com/prysmaticlabs/prysm/beacon-chain/db"
    18  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p"
    19  	"github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags"
    20  	"github.com/prysmaticlabs/prysm/shared"
    21  	"github.com/prysmaticlabs/prysm/shared/abool"
    22  	"github.com/prysmaticlabs/prysm/shared/params"
    23  	"github.com/prysmaticlabs/prysm/shared/timeutils"
    24  	"github.com/sirupsen/logrus"
    25  )
    26  
    27  var _ shared.Service = (*Service)(nil)
    28  
    29  // blockchainService defines the interface for interaction with block chain service.
    30  type blockchainService interface {
    31  	blockchain.BlockReceiver
    32  	blockchain.ChainInfoFetcher
    33  }
    34  
    35  // Config to set up the initial sync service.
    36  type Config struct {
    37  	P2P           p2p.P2P
    38  	DB            db.ReadOnlyDatabase
    39  	Chain         blockchainService
    40  	StateNotifier statefeed.Notifier
    41  	BlockNotifier blockfeed.Notifier
    42  }
    43  
    44  // Service service.
    45  type Service struct {
    46  	cfg          *Config
    47  	ctx          context.Context
    48  	cancel       context.CancelFunc
    49  	synced       *abool.AtomicBool
    50  	chainStarted *abool.AtomicBool
    51  	counter      *ratecounter.RateCounter
    52  	genesisChan  chan time.Time
    53  }
    54  
    55  // NewService configures the initial sync service responsible for bringing the node up to the
    56  // latest head of the blockchain.
    57  func NewService(ctx context.Context, cfg *Config) *Service {
    58  	ctx, cancel := context.WithCancel(ctx)
    59  	s := &Service{
    60  		cfg:          cfg,
    61  		ctx:          ctx,
    62  		cancel:       cancel,
    63  		synced:       abool.New(),
    64  		chainStarted: abool.New(),
    65  		counter:      ratecounter.NewRateCounter(counterSeconds * time.Second),
    66  		genesisChan:  make(chan time.Time),
    67  	}
    68  	go s.waitForStateInitialization()
    69  	return s
    70  }
    71  
    72  // Start the initial sync service.
    73  func (s *Service) Start() {
    74  	// Wait for state initialized event.
    75  	genesis := <-s.genesisChan
    76  	if genesis.IsZero() {
    77  		log.Debug("Exiting Initial Sync Service")
    78  		return
    79  	}
    80  	if flags.Get().DisableSync {
    81  		s.markSynced(genesis)
    82  		log.WithField("genesisTime", genesis).Info("Due to Sync Being Disabled, entering regular sync immediately.")
    83  		return
    84  	}
    85  	if genesis.After(timeutils.Now()) {
    86  		s.markSynced(genesis)
    87  		log.WithField("genesisTime", genesis).Info("Genesis time has not arrived - not syncing")
    88  		return
    89  	}
    90  	currentSlot := helpers.SlotsSince(genesis)
    91  	if helpers.SlotToEpoch(currentSlot) == 0 {
    92  		log.WithField("genesisTime", genesis).Info("Chain started within the last epoch - not syncing")
    93  		s.markSynced(genesis)
    94  		return
    95  	}
    96  	s.chainStarted.Set()
    97  	log.Info("Starting initial chain sync...")
    98  	// Are we already in sync, or close to it?
    99  	if helpers.SlotToEpoch(s.cfg.Chain.HeadSlot()) == helpers.SlotToEpoch(currentSlot) {
   100  		log.Info("Already synced to the current chain head")
   101  		s.markSynced(genesis)
   102  		return
   103  	}
   104  	s.waitForMinimumPeers()
   105  	if err := s.roundRobinSync(genesis); err != nil {
   106  		if errors.Is(s.ctx.Err(), context.Canceled) {
   107  			return
   108  		}
   109  		panic(err)
   110  	}
   111  	log.Infof("Synced up to slot %d", s.cfg.Chain.HeadSlot())
   112  	s.markSynced(genesis)
   113  }
   114  
   115  // Stop initial sync.
   116  func (s *Service) Stop() error {
   117  	s.cancel()
   118  	return nil
   119  }
   120  
   121  // Status of initial sync.
   122  func (s *Service) Status() error {
   123  	if s.synced.IsNotSet() && s.chainStarted.IsSet() {
   124  		return errors.New("syncing")
   125  	}
   126  	return nil
   127  }
   128  
   129  // Syncing returns true if initial sync is still running.
   130  func (s *Service) Syncing() bool {
   131  	return s.synced.IsNotSet()
   132  }
   133  
   134  // Initialized returns true if initial sync has been started.
   135  func (s *Service) Initialized() bool {
   136  	return s.chainStarted.IsSet()
   137  }
   138  
   139  // Synced returns true if initial sync has been completed.
   140  func (s *Service) Synced() bool {
   141  	return s.synced.IsSet()
   142  }
   143  
   144  // Resync allows a node to start syncing again if it has fallen
   145  // behind the current network head.
   146  func (s *Service) Resync() error {
   147  	headState, err := s.cfg.Chain.HeadState(s.ctx)
   148  	if err != nil || headState == nil || headState.IsNil() {
   149  		return errors.Errorf("could not retrieve head state: %v", err)
   150  	}
   151  
   152  	// Set it to false since we are syncing again.
   153  	s.synced.UnSet()
   154  	defer func() { s.synced.Set() }() // Reset it at the end of the method.
   155  	genesis := time.Unix(int64(headState.GenesisTime()), 0)
   156  
   157  	s.waitForMinimumPeers()
   158  	if err = s.roundRobinSync(genesis); err != nil {
   159  		log = log.WithError(err)
   160  	}
   161  	log.WithField("slot", s.cfg.Chain.HeadSlot()).Info("Resync attempt complete")
   162  	return nil
   163  }
   164  
   165  func (s *Service) waitForMinimumPeers() {
   166  	required := params.BeaconConfig().MaxPeersToSync
   167  	if flags.Get().MinimumSyncPeers < required {
   168  		required = flags.Get().MinimumSyncPeers
   169  	}
   170  	for {
   171  		_, peers := s.cfg.P2P.Peers().BestNonFinalized(flags.Get().MinimumSyncPeers, s.cfg.Chain.FinalizedCheckpt().Epoch)
   172  		if len(peers) >= required {
   173  			break
   174  		}
   175  		log.WithFields(logrus.Fields{
   176  			"suitable": len(peers),
   177  			"required": required,
   178  		}).Info("Waiting for enough suitable peers before syncing")
   179  		time.Sleep(handshakePollingInterval)
   180  	}
   181  }
   182  
   183  // waitForStateInitialization makes sure that beacon node is ready to be accessed: it is either
   184  // already properly configured or system waits up until state initialized event is triggered.
   185  func (s *Service) waitForStateInitialization() {
   186  	// Wait for state to be initialized.
   187  	stateChannel := make(chan *feed.Event, 1)
   188  	stateSub := s.cfg.StateNotifier.StateFeed().Subscribe(stateChannel)
   189  	defer stateSub.Unsubscribe()
   190  	log.Info("Waiting for state to be initialized")
   191  	for {
   192  		select {
   193  		case event := <-stateChannel:
   194  			if event.Type == statefeed.Initialized {
   195  				data, ok := event.Data.(*statefeed.InitializedData)
   196  				if !ok {
   197  					log.Error("Event feed data is not type *statefeed.InitializedData")
   198  					continue
   199  				}
   200  				log.WithField("starttime", data.StartTime).Debug("Received state initialized event")
   201  				s.genesisChan <- data.StartTime
   202  				return
   203  			}
   204  		case <-s.ctx.Done():
   205  			log.Debug("Context closed, exiting goroutine")
   206  			// Send a zero time in the event we are exiting.
   207  			s.genesisChan <- time.Time{}
   208  			return
   209  		case err := <-stateSub.Err():
   210  			log.WithError(err).Error("Subscription to state notifier failed")
   211  			// Send a zero time in the event we are exiting.
   212  			s.genesisChan <- time.Time{}
   213  			return
   214  		}
   215  	}
   216  }
   217  
   218  // markSynced marks node as synced and notifies feed listeners.
   219  func (s *Service) markSynced(genesis time.Time) {
   220  	s.synced.Set()
   221  	s.cfg.StateNotifier.StateFeed().Send(&feed.Event{
   222  		Type: statefeed.Synced,
   223  		Data: &statefeed.SyncedData{
   224  			StartTime: genesis,
   225  		},
   226  	})
   227  }