github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/sync/initial-sync/service_test.go (about) 1 package initialsync 2 3 import ( 4 "context" 5 "sync" 6 "testing" 7 "time" 8 9 "github.com/paulbellamy/ratecounter" 10 types "github.com/prysmaticlabs/eth2-types" 11 mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing" 12 "github.com/prysmaticlabs/prysm/beacon-chain/core/feed" 13 statefeed "github.com/prysmaticlabs/prysm/beacon-chain/core/feed/state" 14 "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" 15 dbtest "github.com/prysmaticlabs/prysm/beacon-chain/db/testing" 16 p2pt "github.com/prysmaticlabs/prysm/beacon-chain/p2p/testing" 17 "github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags" 18 eth "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 19 "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1/wrapper" 20 "github.com/prysmaticlabs/prysm/shared/abool" 21 "github.com/prysmaticlabs/prysm/shared/event" 22 "github.com/prysmaticlabs/prysm/shared/params" 23 "github.com/prysmaticlabs/prysm/shared/testutil" 24 "github.com/prysmaticlabs/prysm/shared/testutil/assert" 25 "github.com/prysmaticlabs/prysm/shared/testutil/require" 26 logTest "github.com/sirupsen/logrus/hooks/test" 27 ) 28 29 func TestService_Constants(t *testing.T) { 30 if params.BeaconConfig().MaxPeersToSync*flags.Get().BlockBatchLimit > 1000 { 31 t.Fatal("rpc rejects requests over 1000 range slots") 32 } 33 } 34 35 func TestService_InitStartStop(t *testing.T) { 36 hook := logTest.NewGlobal() 37 tests := []struct { 38 name string 39 assert func() 40 methodRuns func(fd *event.Feed) 41 chainService func() *mock.ChainService 42 }{ 43 { 44 name: "head is not ready", 45 assert: func() { 46 assert.LogsContain(t, hook, "Waiting for state to be initialized") 47 }, 48 }, 49 { 50 name: "future genesis", 51 chainService: func() *mock.ChainService { 52 // Set to future time (genesis time hasn't arrived yet). 53 st, err := testutil.NewBeaconState() 54 require.NoError(t, err) 55 56 return &mock.ChainService{ 57 State: st, 58 FinalizedCheckPoint: ð.Checkpoint{ 59 Epoch: 0, 60 }, 61 } 62 }, 63 methodRuns: func(fd *event.Feed) { 64 // Send valid event. 65 fd.Send(&feed.Event{ 66 Type: statefeed.Initialized, 67 Data: &statefeed.InitializedData{ 68 StartTime: time.Unix(4113849600, 0), 69 GenesisValidatorsRoot: make([]byte, 32), 70 }, 71 }) 72 }, 73 assert: func() { 74 assert.LogsContain(t, hook, "Genesis time has not arrived - not syncing") 75 assert.LogsContain(t, hook, "Waiting for state to be initialized") 76 }, 77 }, 78 { 79 name: "zeroth epoch", 80 chainService: func() *mock.ChainService { 81 // Set to nearby slot. 82 st, err := testutil.NewBeaconState() 83 require.NoError(t, err) 84 return &mock.ChainService{ 85 State: st, 86 FinalizedCheckPoint: ð.Checkpoint{ 87 Epoch: 0, 88 }, 89 } 90 }, 91 methodRuns: func(fd *event.Feed) { 92 // Send valid event. 93 fd.Send(&feed.Event{ 94 Type: statefeed.Initialized, 95 Data: &statefeed.InitializedData{ 96 StartTime: time.Now().Add(-5 * time.Minute), 97 GenesisValidatorsRoot: make([]byte, 32), 98 }, 99 }) 100 }, 101 assert: func() { 102 assert.LogsContain(t, hook, "Chain started within the last epoch - not syncing") 103 assert.LogsDoNotContain(t, hook, "Genesis time has not arrived - not syncing") 104 assert.LogsContain(t, hook, "Waiting for state to be initialized") 105 }, 106 }, 107 { 108 name: "already synced", 109 chainService: func() *mock.ChainService { 110 // Set to some future slot, and then make sure that current head matches it. 111 st, err := testutil.NewBeaconState() 112 require.NoError(t, err) 113 futureSlot := types.Slot(27354) 114 require.NoError(t, st.SetSlot(futureSlot)) 115 return &mock.ChainService{ 116 State: st, 117 FinalizedCheckPoint: ð.Checkpoint{ 118 Epoch: helpers.SlotToEpoch(futureSlot), 119 }, 120 } 121 }, 122 methodRuns: func(fd *event.Feed) { 123 futureSlot := types.Slot(27354) 124 // Send valid event. 125 fd.Send(&feed.Event{ 126 Type: statefeed.Initialized, 127 Data: &statefeed.InitializedData{ 128 StartTime: makeGenesisTime(futureSlot), 129 GenesisValidatorsRoot: make([]byte, 32), 130 }, 131 }) 132 }, 133 assert: func() { 134 assert.LogsContain(t, hook, "Starting initial chain sync...") 135 assert.LogsContain(t, hook, "Already synced to the current chain head") 136 assert.LogsDoNotContain(t, hook, "Chain started within the last epoch - not syncing") 137 assert.LogsDoNotContain(t, hook, "Genesis time has not arrived - not syncing") 138 assert.LogsContain(t, hook, "Waiting for state to be initialized") 139 }, 140 }, 141 } 142 143 p := p2pt.NewTestP2P(t) 144 connectPeers(t, p, []*peerData{}, p.Peers()) 145 for i, tt := range tests { 146 if i == 0 { 147 continue 148 } 149 t.Run(tt.name, func(t *testing.T) { 150 defer hook.Reset() 151 ctx, cancel := context.WithCancel(context.Background()) 152 defer cancel() 153 mc := &mock.ChainService{} 154 // Allow overriding with customized chain service. 155 if tt.chainService != nil { 156 mc = tt.chainService() 157 } 158 // Initialize feed 159 notifier := &mock.MockStateNotifier{} 160 s := NewService(ctx, &Config{ 161 P2P: p, 162 Chain: mc, 163 StateNotifier: notifier, 164 }) 165 time.Sleep(500 * time.Millisecond) 166 assert.NotNil(t, s) 167 if tt.methodRuns != nil { 168 tt.methodRuns(notifier.StateFeed()) 169 } 170 171 wg := &sync.WaitGroup{} 172 wg.Add(1) 173 go func() { 174 s.Start() 175 wg.Done() 176 }() 177 178 go func() { 179 // Allow to exit from test (on no head loop waiting for head is started). 180 // In most tests, this is redundant, as Start() already exited. 181 time.AfterFunc(3*time.Second, func() { 182 cancel() 183 }) 184 }() 185 if testutil.WaitTimeout(wg, time.Second*4) { 186 t.Fatalf("Test should have exited by now, timed out") 187 } 188 tt.assert() 189 }) 190 } 191 } 192 193 func TestService_waitForStateInitialization(t *testing.T) { 194 hook := logTest.NewGlobal() 195 newService := func(ctx context.Context, mc *mock.ChainService) *Service { 196 ctx, cancel := context.WithCancel(ctx) 197 s := &Service{ 198 cfg: &Config{Chain: mc, StateNotifier: mc.StateNotifier()}, 199 ctx: ctx, 200 cancel: cancel, 201 synced: abool.New(), 202 chainStarted: abool.New(), 203 counter: ratecounter.NewRateCounter(counterSeconds * time.Second), 204 genesisChan: make(chan time.Time), 205 } 206 return s 207 } 208 209 t.Run("no state and context close", func(t *testing.T) { 210 defer hook.Reset() 211 ctx, cancel := context.WithCancel(context.Background()) 212 defer cancel() 213 214 s := newService(ctx, &mock.ChainService{}) 215 wg := &sync.WaitGroup{} 216 wg.Add(1) 217 go func() { 218 go s.waitForStateInitialization() 219 currTime := <-s.genesisChan 220 assert.Equal(t, true, currTime.IsZero()) 221 wg.Done() 222 }() 223 go func() { 224 time.AfterFunc(500*time.Millisecond, func() { 225 cancel() 226 }) 227 }() 228 229 if testutil.WaitTimeout(wg, time.Second*2) { 230 t.Fatalf("Test should have exited by now, timed out") 231 } 232 assert.LogsContain(t, hook, "Waiting for state to be initialized") 233 assert.LogsContain(t, hook, "Context closed, exiting goroutine") 234 assert.LogsDoNotContain(t, hook, "Subscription to state notifier failed") 235 }) 236 237 t.Run("no state and state init event received", func(t *testing.T) { 238 defer hook.Reset() 239 ctx, cancel := context.WithCancel(context.Background()) 240 defer cancel() 241 s := newService(ctx, &mock.ChainService{}) 242 243 expectedGenesisTime := time.Unix(358544700, 0) 244 var receivedGenesisTime time.Time 245 wg := &sync.WaitGroup{} 246 wg.Add(1) 247 go func() { 248 go s.waitForStateInitialization() 249 receivedGenesisTime = <-s.genesisChan 250 assert.Equal(t, false, receivedGenesisTime.IsZero()) 251 wg.Done() 252 }() 253 go func() { 254 time.AfterFunc(500*time.Millisecond, func() { 255 // Send invalid event at first. 256 s.cfg.StateNotifier.StateFeed().Send(&feed.Event{ 257 Type: statefeed.Initialized, 258 Data: &statefeed.BlockProcessedData{}, 259 }) 260 // Send valid event. 261 s.cfg.StateNotifier.StateFeed().Send(&feed.Event{ 262 Type: statefeed.Initialized, 263 Data: &statefeed.InitializedData{ 264 StartTime: expectedGenesisTime, 265 GenesisValidatorsRoot: make([]byte, 32), 266 }, 267 }) 268 }) 269 }() 270 271 if testutil.WaitTimeout(wg, time.Second*2) { 272 t.Fatalf("Test should have exited by now, timed out") 273 } 274 assert.Equal(t, expectedGenesisTime, receivedGenesisTime) 275 assert.LogsContain(t, hook, "Event feed data is not type *statefeed.InitializedData") 276 assert.LogsContain(t, hook, "Waiting for state to be initialized") 277 assert.LogsContain(t, hook, "Received state initialized event") 278 assert.LogsDoNotContain(t, hook, "Context closed, exiting goroutine") 279 }) 280 281 t.Run("no state and state init event received and service start", func(t *testing.T) { 282 defer hook.Reset() 283 ctx, cancel := context.WithCancel(context.Background()) 284 defer cancel() 285 s := newService(ctx, &mock.ChainService{}) 286 // Initialize mock feed 287 _ = s.cfg.StateNotifier.StateFeed() 288 289 expectedGenesisTime := time.Now().Add(60 * time.Second) 290 wg := &sync.WaitGroup{} 291 wg.Add(1) 292 go func() { 293 s.waitForStateInitialization() 294 wg.Done() 295 }() 296 297 wg.Add(1) 298 go func() { 299 time.AfterFunc(500*time.Millisecond, func() { 300 // Send valid event. 301 s.cfg.StateNotifier.StateFeed().Send(&feed.Event{ 302 Type: statefeed.Initialized, 303 Data: &statefeed.InitializedData{ 304 StartTime: expectedGenesisTime, 305 GenesisValidatorsRoot: make([]byte, 32), 306 }, 307 }) 308 }) 309 s.Start() 310 wg.Done() 311 }() 312 313 if testutil.WaitTimeout(wg, time.Second*5) { 314 t.Fatalf("Test should have exited by now, timed out") 315 } 316 assert.LogsContain(t, hook, "Waiting for state to be initialized") 317 assert.LogsContain(t, hook, "Received state initialized event") 318 assert.LogsDoNotContain(t, hook, "Context closed, exiting goroutine") 319 }) 320 } 321 322 func TestService_markSynced(t *testing.T) { 323 mc := &mock.ChainService{} 324 ctx, cancel := context.WithCancel(context.Background()) 325 defer cancel() 326 s := NewService(ctx, &Config{ 327 Chain: mc, 328 StateNotifier: mc.StateNotifier(), 329 }) 330 require.NotNil(t, s) 331 assert.Equal(t, false, s.chainStarted.IsSet()) 332 assert.Equal(t, false, s.synced.IsSet()) 333 assert.Equal(t, true, s.Syncing()) 334 assert.NoError(t, s.Status()) 335 s.chainStarted.Set() 336 assert.ErrorContains(t, "syncing", s.Status()) 337 338 expectedGenesisTime := time.Unix(358544700, 0) 339 var receivedGenesisTime time.Time 340 341 stateChannel := make(chan *feed.Event, 1) 342 stateSub := s.cfg.StateNotifier.StateFeed().Subscribe(stateChannel) 343 defer stateSub.Unsubscribe() 344 345 wg := &sync.WaitGroup{} 346 wg.Add(1) 347 go func() { 348 select { 349 case stateEvent := <-stateChannel: 350 if stateEvent.Type == statefeed.Synced { 351 data, ok := stateEvent.Data.(*statefeed.SyncedData) 352 require.Equal(t, true, ok, "Event feed data is not type *statefeed.SyncedData") 353 receivedGenesisTime = data.StartTime 354 } 355 case <-s.ctx.Done(): 356 } 357 wg.Done() 358 }() 359 s.markSynced(expectedGenesisTime) 360 361 if testutil.WaitTimeout(wg, time.Second*2) { 362 t.Fatalf("Test should have exited by now, timed out") 363 } 364 assert.Equal(t, expectedGenesisTime, receivedGenesisTime) 365 assert.Equal(t, false, s.Syncing()) 366 } 367 368 func TestService_Resync(t *testing.T) { 369 p := p2pt.NewTestP2P(t) 370 connectPeers(t, p, []*peerData{ 371 {blocks: makeSequence(1, 160), finalizedEpoch: 5, headSlot: 160}, 372 }, p.Peers()) 373 cache.initializeRootCache(makeSequence(1, 160), t) 374 beaconDB := dbtest.SetupDB(t) 375 err := beaconDB.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(testutil.NewBeaconBlock())) 376 require.NoError(t, err) 377 cache.RLock() 378 genesisRoot := cache.rootCache[0] 379 cache.RUnlock() 380 381 hook := logTest.NewGlobal() 382 tests := []struct { 383 name string 384 assert func(s *Service) 385 chainService func() *mock.ChainService 386 wantedErr string 387 }{ 388 { 389 name: "no head state", 390 wantedErr: "could not retrieve head state", 391 }, 392 { 393 name: "resync ok", 394 chainService: func() *mock.ChainService { 395 st, err := testutil.NewBeaconState() 396 require.NoError(t, err) 397 futureSlot := types.Slot(160) 398 require.NoError(t, st.SetGenesisTime(uint64(makeGenesisTime(futureSlot).Unix()))) 399 return &mock.ChainService{ 400 State: st, 401 Root: genesisRoot[:], 402 DB: beaconDB, 403 FinalizedCheckPoint: ð.Checkpoint{ 404 Epoch: helpers.SlotToEpoch(futureSlot), 405 }, 406 } 407 }, 408 assert: func(s *Service) { 409 assert.LogsContain(t, hook, "Resync attempt complete") 410 assert.Equal(t, types.Slot(160), s.cfg.Chain.HeadSlot()) 411 }, 412 }, 413 } 414 for _, tt := range tests { 415 t.Run(tt.name, func(t *testing.T) { 416 defer hook.Reset() 417 ctx, cancel := context.WithCancel(context.Background()) 418 defer cancel() 419 mc := &mock.ChainService{} 420 // Allow overriding with customized chain service. 421 if tt.chainService != nil { 422 mc = tt.chainService() 423 } 424 s := NewService(ctx, &Config{ 425 DB: beaconDB, 426 P2P: p, 427 Chain: mc, 428 StateNotifier: mc.StateNotifier(), 429 }) 430 assert.NotNil(t, s) 431 assert.Equal(t, types.Slot(0), s.cfg.Chain.HeadSlot()) 432 err := s.Resync() 433 if tt.wantedErr != "" { 434 assert.ErrorContains(t, tt.wantedErr, err) 435 } else { 436 assert.NoError(t, err) 437 } 438 if tt.assert != nil { 439 tt.assert(s) 440 } 441 }) 442 } 443 } 444 445 func TestService_Initialized(t *testing.T) { 446 s := NewService(context.Background(), &Config{ 447 StateNotifier: &mock.MockStateNotifier{}, 448 }) 449 s.chainStarted.Set() 450 assert.Equal(t, true, s.Initialized()) 451 s.chainStarted.UnSet() 452 assert.Equal(t, false, s.Initialized()) 453 } 454 455 func TestService_Synced(t *testing.T) { 456 s := NewService(context.Background(), &Config{ 457 StateNotifier: &mock.MockStateNotifier{}, 458 }) 459 s.synced.UnSet() 460 assert.Equal(t, false, s.Synced()) 461 s.synced.Set() 462 assert.Equal(t, true, s.Synced()) 463 }