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 }