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 = ðpb.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 = ðpb.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 = ðpb.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 = ðpb.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 = ðpb.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 = ðpb.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 = ðpb.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 }