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: &eth.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: &eth.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: &eth.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: &eth.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  }