github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/blockchain/receive_block_test.go (about)

     1  package blockchain
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"testing"
     7  	"time"
     8  
     9  	types "github.com/prysmaticlabs/eth2-types"
    10  	blockchainTesting "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
    11  	"github.com/prysmaticlabs/prysm/beacon-chain/core/state"
    12  	testDB "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
    13  	"github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/protoarray"
    14  	"github.com/prysmaticlabs/prysm/beacon-chain/operations/attestations"
    15  	"github.com/prysmaticlabs/prysm/beacon-chain/operations/voluntaryexits"
    16  	"github.com/prysmaticlabs/prysm/beacon-chain/state/stategen"
    17  	ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    18  	"github.com/prysmaticlabs/prysm/proto/eth/v1alpha1/wrapper"
    19  	"github.com/prysmaticlabs/prysm/proto/interfaces"
    20  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    21  	"github.com/prysmaticlabs/prysm/shared/featureconfig"
    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_ReceiveBlock(t *testing.T) {
    30  	ctx := context.Background()
    31  
    32  	genesis, keys := testutil.DeterministicGenesisState(t, 64)
    33  	genFullBlock := func(t *testing.T, conf *testutil.BlockGenConfig, slot types.Slot) *ethpb.SignedBeaconBlock {
    34  		blk, err := testutil.GenerateFullBlock(genesis, keys, conf, slot)
    35  		assert.NoError(t, err)
    36  		return blk
    37  	}
    38  	bc := params.BeaconConfig()
    39  	bc.ShardCommitteePeriod = 0 // Required for voluntary exits test in reasonable time.
    40  	params.OverrideBeaconConfig(bc)
    41  
    42  	type args struct {
    43  		block *ethpb.SignedBeaconBlock
    44  	}
    45  	tests := []struct {
    46  		name      string
    47  		args      args
    48  		wantedErr string
    49  		check     func(*testing.T, *Service)
    50  	}{
    51  		{
    52  			name: "applies block with state transition",
    53  			args: args{
    54  				block: genFullBlock(t, testutil.DefaultBlockGenConfig(), 2 /*slot*/),
    55  			},
    56  			check: func(t *testing.T, s *Service) {
    57  				if hs := s.head.state.Slot(); hs != 2 {
    58  					t.Errorf("Unexpected state slot. Got %d but wanted %d", hs, 2)
    59  				}
    60  				if bs := s.head.block.Block().Slot(); bs != 2 {
    61  					t.Errorf("Unexpected head block slot. Got %d but wanted %d", bs, 2)
    62  				}
    63  			},
    64  		},
    65  		{
    66  			name: "saves attestations to pool",
    67  			args: args{
    68  				block: genFullBlock(t,
    69  					&testutil.BlockGenConfig{
    70  						NumProposerSlashings: 0,
    71  						NumAttesterSlashings: 0,
    72  						NumAttestations:      2,
    73  						NumDeposits:          0,
    74  						NumVoluntaryExits:    0,
    75  					},
    76  					1, /*slot*/
    77  				),
    78  			},
    79  			check: func(t *testing.T, s *Service) {
    80  				if baCount := len(s.cfg.AttPool.BlockAttestations()); baCount != 2 {
    81  					t.Errorf("Did not get the correct number of block attestations saved to the pool. "+
    82  						"Got %d but wanted %d", baCount, 2)
    83  				}
    84  			},
    85  		},
    86  		{
    87  			name: "updates exit pool",
    88  			args: args{
    89  				block: genFullBlock(t, &testutil.BlockGenConfig{
    90  					NumProposerSlashings: 0,
    91  					NumAttesterSlashings: 0,
    92  					NumAttestations:      0,
    93  					NumDeposits:          0,
    94  					NumVoluntaryExits:    3,
    95  				},
    96  					1, /*slot*/
    97  				),
    98  			},
    99  			check: func(t *testing.T, s *Service) {
   100  				pending := s.cfg.ExitPool.PendingExits(genesis, 1, true /* no limit */)
   101  				if len(pending) != 0 {
   102  					t.Errorf(
   103  						"Did not mark the correct number of exits. Got %d pending but wanted %d",
   104  						len(pending),
   105  						0,
   106  					)
   107  				}
   108  			},
   109  		},
   110  		{
   111  			name: "notifies block processed on state feed",
   112  			args: args{
   113  				block: genFullBlock(t, testutil.DefaultBlockGenConfig(), 1 /*slot*/),
   114  			},
   115  			check: func(t *testing.T, s *Service) {
   116  				if recvd := len(s.cfg.StateNotifier.(*blockchainTesting.MockStateNotifier).ReceivedEvents()); recvd < 1 {
   117  					t.Errorf("Received %d state notifications, expected at least 1", recvd)
   118  				}
   119  			},
   120  		},
   121  	}
   122  
   123  	for _, tt := range tests {
   124  		t.Run(tt.name, func(t *testing.T) {
   125  			beaconDB := testDB.SetupDB(t)
   126  			genesisBlockRoot := bytesutil.ToBytes32(nil)
   127  			require.NoError(t, beaconDB.SaveState(ctx, genesis, genesisBlockRoot))
   128  
   129  			cfg := &Config{
   130  				BeaconDB: beaconDB,
   131  				ForkChoiceStore: protoarray.New(
   132  					0, // justifiedEpoch
   133  					0, // finalizedEpoch
   134  					genesisBlockRoot,
   135  				),
   136  				AttPool:       attestations.NewPool(),
   137  				ExitPool:      voluntaryexits.NewPool(),
   138  				StateNotifier: &blockchainTesting.MockStateNotifier{RecordEvents: true},
   139  				StateGen:      stategen.New(beaconDB),
   140  			}
   141  			s, err := NewService(ctx, cfg)
   142  			require.NoError(t, err)
   143  			require.NoError(t, s.saveGenesisData(ctx, genesis))
   144  			gBlk, err := s.cfg.BeaconDB.GenesisBlock(ctx)
   145  			require.NoError(t, err)
   146  			gRoot, err := gBlk.Block().HashTreeRoot()
   147  			require.NoError(t, err)
   148  			s.finalizedCheckpt = &ethpb.Checkpoint{Root: gRoot[:]}
   149  			root, err := tt.args.block.Block.HashTreeRoot()
   150  			require.NoError(t, err)
   151  			err = s.ReceiveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(tt.args.block), root)
   152  			if tt.wantedErr != "" {
   153  				assert.ErrorContains(t, tt.wantedErr, err)
   154  			} else {
   155  				assert.NoError(t, err)
   156  				tt.check(t, s)
   157  			}
   158  		})
   159  	}
   160  }
   161  
   162  func TestService_ReceiveBlockUpdateHead(t *testing.T) {
   163  	ctx := context.Background()
   164  	genesis, keys := testutil.DeterministicGenesisState(t, 64)
   165  	b, err := testutil.GenerateFullBlock(genesis, keys, testutil.DefaultBlockGenConfig(), 1)
   166  	assert.NoError(t, err)
   167  	beaconDB := testDB.SetupDB(t)
   168  	genesisBlockRoot := bytesutil.ToBytes32(nil)
   169  	require.NoError(t, beaconDB.SaveState(ctx, genesis, genesisBlockRoot))
   170  	cfg := &Config{
   171  		BeaconDB: beaconDB,
   172  		ForkChoiceStore: protoarray.New(
   173  			0, // justifiedEpoch
   174  			0, // finalizedEpoch
   175  			genesisBlockRoot,
   176  		),
   177  		AttPool:       attestations.NewPool(),
   178  		ExitPool:      voluntaryexits.NewPool(),
   179  		StateNotifier: &blockchainTesting.MockStateNotifier{RecordEvents: true},
   180  		StateGen:      stategen.New(beaconDB),
   181  	}
   182  	s, err := NewService(ctx, cfg)
   183  	require.NoError(t, err)
   184  	require.NoError(t, s.saveGenesisData(ctx, genesis))
   185  	gBlk, err := s.cfg.BeaconDB.GenesisBlock(ctx)
   186  	require.NoError(t, err)
   187  	gRoot, err := gBlk.Block().HashTreeRoot()
   188  	require.NoError(t, err)
   189  	s.finalizedCheckpt = &ethpb.Checkpoint{Root: gRoot[:]}
   190  	root, err := b.Block.HashTreeRoot()
   191  	require.NoError(t, err)
   192  	wg := sync.WaitGroup{}
   193  	wg.Add(1)
   194  	go func() {
   195  		require.NoError(t, s.ReceiveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(b), root))
   196  		wg.Done()
   197  	}()
   198  	wg.Wait()
   199  	if recvd := len(s.cfg.StateNotifier.(*blockchainTesting.MockStateNotifier).ReceivedEvents()); recvd < 1 {
   200  		t.Errorf("Received %d state notifications, expected at least 1", recvd)
   201  	}
   202  	// Verify fork choice has processed the block. (Genesis block and the new block)
   203  	assert.Equal(t, 2, len(s.cfg.ForkChoiceStore.Nodes()))
   204  }
   205  
   206  func TestService_ReceiveBlockBatch(t *testing.T) {
   207  	ctx := context.Background()
   208  
   209  	genesis, keys := testutil.DeterministicGenesisState(t, 64)
   210  	genFullBlock := func(t *testing.T, conf *testutil.BlockGenConfig, slot types.Slot) *ethpb.SignedBeaconBlock {
   211  		blk, err := testutil.GenerateFullBlock(genesis, keys, conf, slot)
   212  		assert.NoError(t, err)
   213  		return blk
   214  	}
   215  
   216  	type args struct {
   217  		block *ethpb.SignedBeaconBlock
   218  	}
   219  	tests := []struct {
   220  		name      string
   221  		args      args
   222  		wantedErr string
   223  		check     func(*testing.T, *Service)
   224  	}{
   225  		{
   226  			name: "applies block with state transition",
   227  			args: args{
   228  				block: genFullBlock(t, testutil.DefaultBlockGenConfig(), 2 /*slot*/),
   229  			},
   230  			check: func(t *testing.T, s *Service) {
   231  				assert.Equal(t, types.Slot(2), s.head.state.Slot(), "Incorrect head state slot")
   232  				assert.Equal(t, types.Slot(2), s.head.block.Block().Slot(), "Incorrect head block slot")
   233  			},
   234  		},
   235  		{
   236  			name: "notifies block processed on state feed",
   237  			args: args{
   238  				block: genFullBlock(t, testutil.DefaultBlockGenConfig(), 1 /*slot*/),
   239  			},
   240  			check: func(t *testing.T, s *Service) {
   241  				if recvd := len(s.cfg.StateNotifier.(*blockchainTesting.MockStateNotifier).ReceivedEvents()); recvd < 1 {
   242  					t.Errorf("Received %d state notifications, expected at least 1", recvd)
   243  				}
   244  			},
   245  		},
   246  	}
   247  
   248  	for _, tt := range tests {
   249  		t.Run(tt.name, func(t *testing.T) {
   250  			beaconDB := testDB.SetupDB(t)
   251  			genesisBlockRoot, err := genesis.HashTreeRoot(ctx)
   252  			require.NoError(t, err)
   253  			cfg := &Config{
   254  				BeaconDB: beaconDB,
   255  				ForkChoiceStore: protoarray.New(
   256  					0, // justifiedEpoch
   257  					0, // finalizedEpoch
   258  					genesisBlockRoot,
   259  				),
   260  				StateNotifier: &blockchainTesting.MockStateNotifier{RecordEvents: true},
   261  				StateGen:      stategen.New(beaconDB),
   262  			}
   263  			s, err := NewService(ctx, cfg)
   264  			require.NoError(t, err)
   265  			err = s.saveGenesisData(ctx, genesis)
   266  			require.NoError(t, err)
   267  			gBlk, err := s.cfg.BeaconDB.GenesisBlock(ctx)
   268  			require.NoError(t, err)
   269  
   270  			gRoot, err := gBlk.Block().HashTreeRoot()
   271  			require.NoError(t, err)
   272  			s.finalizedCheckpt = &ethpb.Checkpoint{Root: gRoot[:]}
   273  			root, err := tt.args.block.Block.HashTreeRoot()
   274  			require.NoError(t, err)
   275  			blks := []interfaces.SignedBeaconBlock{wrapper.WrappedPhase0SignedBeaconBlock(tt.args.block)}
   276  			roots := [][32]byte{root}
   277  			err = s.ReceiveBlockBatch(ctx, blks, roots)
   278  			if tt.wantedErr != "" {
   279  				assert.ErrorContains(t, tt.wantedErr, err)
   280  			} else {
   281  				assert.NoError(t, err)
   282  				tt.check(t, s)
   283  			}
   284  		})
   285  	}
   286  }
   287  
   288  func TestService_ReceiveBlockBatch_UpdateFinalizedCheckpoint(t *testing.T) {
   289  	// Must enable head timely feature flag to test this.
   290  	resetCfg := featureconfig.InitWithReset(&featureconfig.Flags{
   291  		UpdateHeadTimely: true,
   292  	})
   293  	defer resetCfg()
   294  
   295  	ctx := context.Background()
   296  	genesis, keys := testutil.DeterministicGenesisState(t, 64)
   297  
   298  	// Generate 5 epochs worth of blocks.
   299  	var blks []interfaces.SignedBeaconBlock
   300  	var roots [][32]byte
   301  	copied := genesis.Copy()
   302  	for i := types.Slot(1); i < params.BeaconConfig().SlotsPerEpoch*5; i++ {
   303  		b, err := testutil.GenerateFullBlock(copied, keys, testutil.DefaultBlockGenConfig(), i)
   304  		assert.NoError(t, err)
   305  		copied, err = state.ExecuteStateTransition(context.Background(), copied, wrapper.WrappedPhase0SignedBeaconBlock(b))
   306  		assert.NoError(t, err)
   307  		r, err := b.Block.HashTreeRoot()
   308  		require.NoError(t, err)
   309  		blks = append(blks, wrapper.WrappedPhase0SignedBeaconBlock(b))
   310  		roots = append(roots, r)
   311  	}
   312  
   313  	beaconDB := testDB.SetupDB(t)
   314  	genesisBlockRoot, err := genesis.HashTreeRoot(ctx)
   315  	require.NoError(t, err)
   316  	cfg := &Config{
   317  		BeaconDB: beaconDB,
   318  		ForkChoiceStore: protoarray.New(
   319  			0, // justifiedEpoch
   320  			0, // finalizedEpoch
   321  			genesisBlockRoot,
   322  		),
   323  		StateNotifier: &blockchainTesting.MockStateNotifier{RecordEvents: false},
   324  		StateGen:      stategen.New(beaconDB),
   325  	}
   326  	s, err := NewService(ctx, cfg)
   327  	require.NoError(t, err)
   328  	err = s.saveGenesisData(ctx, genesis)
   329  	require.NoError(t, err)
   330  	gBlk, err := s.cfg.BeaconDB.GenesisBlock(ctx)
   331  	require.NoError(t, err)
   332  
   333  	gRoot, err := gBlk.Block().HashTreeRoot()
   334  	require.NoError(t, err)
   335  	s.finalizedCheckpt = &ethpb.Checkpoint{Root: gRoot[:]}
   336  
   337  	// Process 5 epochs worth of blocks.
   338  	require.NoError(t, s.ReceiveBlockBatch(ctx, blks, roots))
   339  
   340  	// Finalized epoch must be updated.
   341  	require.Equal(t, types.Epoch(2), s.finalizedCheckpt.Epoch)
   342  }
   343  
   344  func TestService_HasInitSyncBlock(t *testing.T) {
   345  	s, err := NewService(context.Background(), &Config{StateNotifier: &blockchainTesting.MockStateNotifier{}})
   346  	require.NoError(t, err)
   347  	r := [32]byte{'a'}
   348  	if s.HasInitSyncBlock(r) {
   349  		t.Error("Should not have block")
   350  	}
   351  	s.saveInitSyncBlock(r, wrapper.WrappedPhase0SignedBeaconBlock(testutil.NewBeaconBlock()))
   352  	if !s.HasInitSyncBlock(r) {
   353  		t.Error("Should have block")
   354  	}
   355  }
   356  
   357  func TestCheckSaveHotStateDB_Enabling(t *testing.T) {
   358  	beaconDB := testDB.SetupDB(t)
   359  	hook := logTest.NewGlobal()
   360  	s, err := NewService(context.Background(), &Config{StateGen: stategen.New(beaconDB)})
   361  	require.NoError(t, err)
   362  	st := params.BeaconConfig().SlotsPerEpoch.Mul(uint64(epochsSinceFinalitySaveHotStateDB))
   363  	s.genesisTime = time.Now().Add(time.Duration(-1*int64(st)*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second)
   364  	s.finalizedCheckpt = &ethpb.Checkpoint{}
   365  
   366  	require.NoError(t, s.checkSaveHotStateDB(context.Background()))
   367  	assert.LogsContain(t, hook, "Entering mode to save hot states in DB")
   368  }
   369  
   370  func TestCheckSaveHotStateDB_Disabling(t *testing.T) {
   371  	beaconDB := testDB.SetupDB(t)
   372  	hook := logTest.NewGlobal()
   373  	s, err := NewService(context.Background(), &Config{StateGen: stategen.New(beaconDB)})
   374  	require.NoError(t, err)
   375  	s.finalizedCheckpt = &ethpb.Checkpoint{}
   376  	require.NoError(t, s.checkSaveHotStateDB(context.Background()))
   377  	s.genesisTime = time.Now()
   378  
   379  	require.NoError(t, s.checkSaveHotStateDB(context.Background()))
   380  	assert.LogsContain(t, hook, "Exiting mode to save hot states in DB")
   381  }
   382  
   383  func TestCheckSaveHotStateDB_Overflow(t *testing.T) {
   384  	beaconDB := testDB.SetupDB(t)
   385  	hook := logTest.NewGlobal()
   386  	s, err := NewService(context.Background(), &Config{StateGen: stategen.New(beaconDB)})
   387  	require.NoError(t, err)
   388  	s.finalizedCheckpt = &ethpb.Checkpoint{Epoch: 10000000}
   389  	s.genesisTime = time.Now()
   390  
   391  	require.NoError(t, s.checkSaveHotStateDB(context.Background()))
   392  	assert.LogsDoNotContain(t, hook, "Entering mode to save hot states in DB")
   393  }