package blockchain import ( "sync" "testing" "time" blockchainTesting "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/testing" "github.com/OffchainLabs/prysm/v7/beacon-chain/cache" statefeed "github.com/OffchainLabs/prysm/v7/beacon-chain/core/feed/state" "github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers" "github.com/OffchainLabs/prysm/v7/beacon-chain/das" forkchoicetypes "github.com/OffchainLabs/prysm/v7/beacon-chain/forkchoice/types" lightClient "github.com/OffchainLabs/prysm/v7/beacon-chain/light-client" "github.com/OffchainLabs/prysm/v7/beacon-chain/operations/voluntaryexits" "github.com/OffchainLabs/prysm/v7/config/features" fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams" "github.com/OffchainLabs/prysm/v7/config/params" "github.com/OffchainLabs/prysm/v7/consensus-types/blocks" "github.com/OffchainLabs/prysm/v7/consensus-types/primitives" "github.com/OffchainLabs/prysm/v7/encoding/bytesutil" ethpbv1 "github.com/OffchainLabs/prysm/v7/proto/eth/v1" ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1" "github.com/OffchainLabs/prysm/v7/runtime/version" "github.com/OffchainLabs/prysm/v7/testing/assert" "github.com/OffchainLabs/prysm/v7/testing/require" "github.com/OffchainLabs/prysm/v7/testing/util" "github.com/OffchainLabs/prysm/v7/time/slots" "github.com/ethereum/go-ethereum/common/hexutil" logTest "github.com/sirupsen/logrus/hooks/test" ) func TestService_ReceiveBlock(t *testing.T) { ctx := t.Context() genesis, keys := util.DeterministicGenesisState(t, 64) copiedGen := genesis.Copy() genFullBlock := func(t *testing.T, conf *util.BlockGenConfig, slot primitives.Slot) *ethpb.SignedBeaconBlock { blk, err := util.GenerateFullBlock(copiedGen.Copy(), keys, conf, slot) require.NoError(t, err) return blk } //params.SetupTestConfigCleanupWithLock(t) bc := params.BeaconConfig().Copy() bc.ShardCommitteePeriod = 0 // Required for voluntary exits test in reasonable time. params.OverrideBeaconConfig(bc) badBlock := genFullBlock(t, util.DefaultBlockGenConfig(), 101) badRoot, err := badBlock.Block.HashTreeRoot() require.NoError(t, err) badRoots := make(map[[32]byte]struct{}) badRoots[badRoot] = struct{}{} resetCfg := features.InitWithReset(&features.Flags{ BlacklistedRoots: badRoots, }) defer resetCfg() type args struct { block *ethpb.SignedBeaconBlock } tests := []struct { name string args args wantedErr string check func(*testing.T, *Service) }{ { name: "applies block with state transition", args: args{ block: genFullBlock(t, util.DefaultBlockGenConfig(), 2 /*slot*/), }, check: func(t *testing.T, s *Service) { if hs := s.head.state.Slot(); hs != 2 { t.Errorf("Unexpected state slot. Got %d but wanted %d", hs, 2) } if bs := s.head.block.Block().Slot(); bs != 2 { t.Errorf("Unexpected head block slot. Got %d but wanted %d", bs, 2) } }, }, { name: "saves attestations to pool", args: args{ block: genFullBlock(t, &util.BlockGenConfig{ NumProposerSlashings: 0, NumAttesterSlashings: 0, NumAttestations: 2, NumDeposits: 0, NumVoluntaryExits: 0, }, 1, /*slot*/ ), }, check: func(t *testing.T, s *Service) { if baCount := len(s.cfg.AttPool.BlockAttestations()); baCount != 0 { t.Errorf("Did not get the correct number of block attestations saved to the pool. "+ "Got %d but wanted %d", baCount, 0) } }, }, { name: "updates exit pool", args: args{ block: genFullBlock(t, &util.BlockGenConfig{ NumProposerSlashings: 0, NumAttesterSlashings: 0, NumAttestations: 0, NumDeposits: 0, NumVoluntaryExits: 3, }, 1, /*slot*/ ), }, check: func(t *testing.T, s *Service) { pending, err := s.cfg.ExitPool.PendingExits() require.NoError(t, err) if len(pending) != 0 { t.Errorf( "Did not mark the correct number of exits. Got %d pending but wanted %d", len(pending), 0, ) } }, }, { name: "notifies block processed on state feed", args: args{ block: genFullBlock(t, util.DefaultBlockGenConfig(), 1 /*slot*/), }, check: func(t *testing.T, s *Service) { // Hacky sleep, should use a better way to be able to resolve the race // between event being sent out and processed. time.Sleep(100 * time.Millisecond) if recvd := len(s.cfg.StateNotifier.(*blockchainTesting.MockStateNotifier).ReceivedEvents()); recvd < 1 { t.Errorf("Received %d state notifications, expected at least 1", recvd) } }, }, { name: "The block is blacklisted", args: args{ block: badBlock, }, wantedErr: errBlacklistedRoot.Error(), }, } wg := new(sync.WaitGroup) for _, tt := range tests { wg.Add(1) t.Run(tt.name, func(t *testing.T) { defer func() { wg.Done() }() genesis = genesis.Copy() s, tr := minimalTestService(t, WithFinalizedStateAtStartUp(genesis), WithExitPool(voluntaryexits.NewPool()), WithStateNotifier(&blockchainTesting.MockStateNotifier{RecordEvents: true}), WithTrackedValidatorsCache(cache.NewTrackedValidatorsCache()), ) beaconDB := tr.db genesisBlockRoot := bytesutil.ToBytes32(nil) require.NoError(t, beaconDB.SaveState(ctx, genesis, genesisBlockRoot)) // Initialize it here. _ = s.cfg.StateNotifier.StateFeed() require.NoError(t, s.saveGenesisData(ctx, genesis)) root, err := tt.args.block.Block.HashTreeRoot() require.NoError(t, err) wsb, err := blocks.NewSignedBeaconBlock(tt.args.block) require.NoError(t, err) err = s.ReceiveBlock(ctx, wsb, root, nil) if tt.wantedErr != "" { assert.ErrorContains(t, tt.wantedErr, err) } else { require.NoError(t, err) tt.check(t, s) } }) } wg.Wait() } func TestHandleDA(t *testing.T) { signedBeaconBlock, err := blocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlock{ Block: ðpb.BeaconBlock{ Body: ðpb.BeaconBlockBody{}, }, }) require.NoError(t, err) s, _ := minimalTestService(t) block, err := blocks.NewROBlockWithRoot(signedBeaconBlock, [32]byte{}) require.NoError(t, err) elapsed, err := s.handleDA(t.Context(), nil, block) require.NoError(t, err) require.Equal(t, true, elapsed > 0, "Elapsed time should be greater than 0") } func TestService_ReceiveBlockUpdateHead(t *testing.T) { s, tr := minimalTestService(t, WithExitPool(voluntaryexits.NewPool()), WithStateNotifier(&blockchainTesting.MockStateNotifier{RecordEvents: true})) ctx, beaconDB := tr.ctx, tr.db genesis, keys := util.DeterministicGenesisState(t, 64) b, err := util.GenerateFullBlock(genesis, keys, util.DefaultBlockGenConfig(), 1) assert.NoError(t, err) genesisBlockRoot := bytesutil.ToBytes32(nil) require.NoError(t, beaconDB.SaveState(ctx, genesis, genesisBlockRoot)) // Initialize it here. _ = s.cfg.StateNotifier.StateFeed() require.NoError(t, s.saveGenesisData(ctx, genesis)) root, err := b.Block.HashTreeRoot() require.NoError(t, err) wg := sync.WaitGroup{} wg.Go(func() { wsb, err := blocks.NewSignedBeaconBlock(b) require.NoError(t, err) require.NoError(t, s.ReceiveBlock(ctx, wsb, root, nil)) }) wg.Wait() time.Sleep(100 * time.Millisecond) if recvd := len(s.cfg.StateNotifier.(*blockchainTesting.MockStateNotifier).ReceivedEvents()); recvd < 1 { t.Errorf("Received %d state notifications, expected at least 1", recvd) } // Verify fork choice has processed the block. (Genesis block and the new block) assert.Equal(t, 2, s.cfg.ForkChoiceStore.NodeCount()) } func TestService_ReceiveBlockBatch(t *testing.T) { ctx := t.Context() genesis, keys := util.DeterministicGenesisState(t, 64) genFullBlock := func(t *testing.T, conf *util.BlockGenConfig, slot primitives.Slot) *ethpb.SignedBeaconBlock { blk, err := util.GenerateFullBlock(genesis, keys, conf, slot) assert.NoError(t, err) return blk } type args struct { block *ethpb.SignedBeaconBlock } tests := []struct { name string args args wantedErr string check func(*testing.T, *Service) }{ { name: "applies block with state transition", args: args{ block: genFullBlock(t, util.DefaultBlockGenConfig(), 2 /*slot*/), }, check: func(t *testing.T, s *Service) { assert.Equal(t, primitives.Slot(2), s.head.state.Slot(), "Incorrect head state slot") assert.Equal(t, primitives.Slot(2), s.head.block.Block().Slot(), "Incorrect head block slot") }, }, { name: "notifies block processed on state feed", args: args{ block: genFullBlock(t, util.DefaultBlockGenConfig(), 1 /*slot*/), }, check: func(t *testing.T, s *Service) { time.Sleep(100 * time.Millisecond) if recvd := len(s.cfg.StateNotifier.(*blockchainTesting.MockStateNotifier).ReceivedEvents()); recvd < 1 { t.Errorf("Received %d state notifications, expected at least 1", recvd) } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { s, _ := minimalTestService(t, WithStateNotifier(&blockchainTesting.MockStateNotifier{RecordEvents: true})) err := s.saveGenesisData(ctx, genesis) require.NoError(t, err) wsb, err := blocks.NewSignedBeaconBlock(tt.args.block) require.NoError(t, err) rwsb, err := blocks.NewROBlock(wsb) require.NoError(t, err) err = s.ReceiveBlockBatch(ctx, []blocks.ROBlock{rwsb}, &das.MockAvailabilityStore{}) if tt.wantedErr != "" { assert.ErrorContains(t, tt.wantedErr, err) } else { assert.NoError(t, err) tt.check(t, s) } }) } } func TestService_HasBlock(t *testing.T) { s, _ := minimalTestService(t) r := [32]byte{'a'} if s.HasBlock(t.Context(), r) { t.Error("Should not have block") } wsb, err := blocks.NewSignedBeaconBlock(util.NewBeaconBlock()) require.NoError(t, err) require.NoError(t, s.saveInitSyncBlock(t.Context(), r, wsb)) if !s.HasBlock(t.Context(), r) { t.Error("Should have block") } b := util.NewBeaconBlock() b.Block.Slot = 1 util.SaveBlock(t, t.Context(), s.cfg.BeaconDB, b) r, err = b.Block.HashTreeRoot() require.NoError(t, err) require.Equal(t, true, s.HasBlock(t.Context(), r)) err = s.blockBeingSynced.set(r) require.NoError(t, err) err = s.blockBeingSynced.set(r) require.ErrorIs(t, err, errBlockBeingSynced) require.Equal(t, false, s.HasBlock(t.Context(), r)) } func TestCheckSaveHotStateDB_Enabling(t *testing.T) { hook := logTest.NewGlobal() s, _ := minimalTestService(t) st := params.BeaconConfig().SlotsPerEpoch.Mul(uint64(epochsSinceFinalitySaveHotStateDB)) s.genesisTime = time.Now().Add(time.Duration(-1*int64(st)*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second) require.NoError(t, s.checkSaveHotStateDB(t.Context())) assert.LogsContain(t, hook, "Entering mode to save hot states in DB") } func TestCheckSaveHotStateDB_Disabling(t *testing.T) { hook := logTest.NewGlobal() s, _ := minimalTestService(t) st := params.BeaconConfig().SlotsPerEpoch.Mul(uint64(epochsSinceFinalitySaveHotStateDB)) s.genesisTime = time.Now().Add(time.Duration(-1*int64(st)*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second) require.NoError(t, s.checkSaveHotStateDB(t.Context())) s.genesisTime = time.Now() require.NoError(t, s.checkSaveHotStateDB(t.Context())) assert.LogsContain(t, hook, "Exiting mode to save hot states in DB") } func TestCheckSaveHotStateDB_Overflow(t *testing.T) { hook := logTest.NewGlobal() s, _ := minimalTestService(t) s.SetGenesisTime(time.Now()) require.NoError(t, s.checkSaveHotStateDB(t.Context())) assert.LogsDoNotContain(t, hook, "Entering mode to save hot states in DB") } func TestHandleCaches_EnablingLargeSize(t *testing.T) { hook := logTest.NewGlobal() s, _ := minimalTestService(t) st := params.BeaconConfig().SlotsPerEpoch.Mul(uint64(epochsSinceFinalitySaveHotStateDB)) s.SetGenesisTime(time.Now().Add(time.Duration(-1*int64(st)*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second)) helpers.ClearCache() require.NoError(t, s.handleCaches()) assert.LogsContain(t, hook, "Expanding committee cache size") } func TestHandleCaches_DisablingLargeSize(t *testing.T) { hook := logTest.NewGlobal() s, _ := minimalTestService(t) st := params.BeaconConfig().SlotsPerEpoch.Mul(uint64(epochsSinceFinalitySaveHotStateDB)) s.genesisTime = time.Now().Add(time.Duration(-1*int64(st)*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second) require.NoError(t, s.handleCaches()) s.genesisTime = time.Now() require.NoError(t, s.handleCaches()) assert.LogsContain(t, hook, "Reducing committee cache size") } func TestHandleBlockBLSToExecutionChanges(t *testing.T) { service, tr := minimalTestService(t) pool := tr.blsPool t.Run("pre Capella block", func(t *testing.T) { body := ðpb.BeaconBlockBodyBellatrix{} pbb := ðpb.BeaconBlockBellatrix{ Body: body, } blk, err := blocks.NewBeaconBlock(pbb) require.NoError(t, err) require.NoError(t, service.markIncludedBlockBLSToExecChanges(blk)) }) t.Run("Post Capella no changes", func(t *testing.T) { body := ðpb.BeaconBlockBodyCapella{} pbb := ðpb.BeaconBlockCapella{ Body: body, } blk, err := blocks.NewBeaconBlock(pbb) require.NoError(t, err) require.NoError(t, service.markIncludedBlockBLSToExecChanges(blk)) }) t.Run("Post Capella some changes", func(t *testing.T) { idx := primitives.ValidatorIndex(123) change := ðpb.BLSToExecutionChange{ ValidatorIndex: idx, } signedChange := ðpb.SignedBLSToExecutionChange{ Message: change, } body := ðpb.BeaconBlockBodyCapella{ BlsToExecutionChanges: []*ethpb.SignedBLSToExecutionChange{signedChange}, } pbb := ðpb.BeaconBlockCapella{ Body: body, } blk, err := blocks.NewBeaconBlock(pbb) require.NoError(t, err) pool.InsertBLSToExecChange(signedChange) require.Equal(t, true, pool.ValidatorExists(idx)) require.NoError(t, service.markIncludedBlockBLSToExecChanges(blk)) require.Equal(t, false, pool.ValidatorExists(idx)) }) } func Test_sendNewFinalizedEvent(t *testing.T) { s, _ := minimalTestService(t) notifier := &blockchainTesting.MockStateNotifier{RecordEvents: true} s.cfg.StateNotifier = notifier finalizedSt, err := util.NewBeaconState() require.NoError(t, err) finalizedStRoot, err := finalizedSt.HashTreeRoot(s.ctx) require.NoError(t, err) b := util.NewBeaconBlock() b.Block.StateRoot = finalizedStRoot[:] sbb, err := blocks.NewSignedBeaconBlock(b) require.NoError(t, err) sbbRoot, err := sbb.Block().HashTreeRoot() require.NoError(t, err) require.NoError(t, s.cfg.BeaconDB.SaveBlock(s.ctx, sbb)) st, err := util.NewBeaconState() require.NoError(t, err) require.NoError(t, st.SetFinalizedCheckpoint(ðpb.Checkpoint{ Epoch: 123, Root: sbbRoot[:], })) s.sendNewFinalizedEvent(s.ctx, st) require.Equal(t, 1, len(notifier.ReceivedEvents())) e := notifier.ReceivedEvents()[0] assert.Equal(t, statefeed.FinalizedCheckpoint, int(e.Type)) fc, ok := e.Data.(*ethpbv1.EventFinalizedCheckpoint) require.Equal(t, true, ok, "event has wrong data type") assert.Equal(t, primitives.Epoch(123), fc.Epoch) assert.DeepEqual(t, sbbRoot[:], fc.Block) assert.DeepEqual(t, finalizedStRoot[:], fc.State) assert.Equal(t, false, fc.ExecutionOptimistic) } func Test_executePostFinalizationTasks(t *testing.T) { logHook := logTest.NewGlobal() headState, err := util.NewBeaconStateElectra() require.NoError(t, err) finalizedStRoot, err := headState.HashTreeRoot(t.Context()) require.NoError(t, err) genesis := util.NewBeaconBlock() genesisRoot, err := genesis.Block.HashTreeRoot() require.NoError(t, err) finalizedSlot := params.BeaconConfig().SlotsPerEpoch*122 + 1 headBlock := util.NewBeaconBlock() headBlock.Block.Slot = finalizedSlot headBlock.Block.StateRoot = finalizedStRoot[:] headBlock.Block.ParentRoot = bytesutil.PadTo(genesisRoot[:], 32) headRoot, err := headBlock.Block.HashTreeRoot() require.NoError(t, err) hexKey := "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a" key, err := hexutil.Decode(hexKey) require.NoError(t, err) require.NoError(t, headState.SetValidators([]*ethpb.Validator{ { PublicKey: key, WithdrawalCredentials: make([]byte, fieldparams.RootLength), }, })) require.NoError(t, headState.SetSlot(finalizedSlot)) require.NoError(t, headState.SetFinalizedCheckpoint(ðpb.Checkpoint{ Epoch: 123, Root: headRoot[:], })) require.NoError(t, headState.SetGenesisValidatorsRoot(params.BeaconConfig().ZeroHash[:])) t.Run("pre deposit request", func(t *testing.T) { require.NoError(t, headState.SetEth1DepositIndex(1)) s, tr := minimalTestService(t, WithFinalizedStateAtStartUp(headState)) ctx, beaconDB, stateGen := tr.ctx, tr.db, tr.sg require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, genesisRoot)) util.SaveBlock(t, ctx, beaconDB, genesis) require.NoError(t, beaconDB.SaveState(ctx, headState, headRoot)) require.NoError(t, beaconDB.SaveState(ctx, headState, genesisRoot)) util.SaveBlock(t, ctx, beaconDB, headBlock) require.NoError(t, beaconDB.SaveFinalizedCheckpoint(ctx, ðpb.Checkpoint{Epoch: slots.ToEpoch(finalizedSlot), Root: headRoot[:]})) require.NoError(t, err) require.NoError(t, stateGen.SaveState(ctx, headRoot, headState)) require.NoError(t, beaconDB.SaveLastValidatedCheckpoint(ctx, ðpb.Checkpoint{Epoch: slots.ToEpoch(finalizedSlot), Root: headRoot[:]})) notifier := &blockchainTesting.MockStateNotifier{RecordEvents: true} s.cfg.StateNotifier = notifier s.executePostFinalizationTasks(s.ctx, headState) time.Sleep(1 * time.Second) // sleep for a second because event is in a separate go routine require.Equal(t, 1, len(notifier.ReceivedEvents())) e := notifier.ReceivedEvents()[0] assert.Equal(t, statefeed.FinalizedCheckpoint, int(e.Type)) fc, ok := e.Data.(*ethpbv1.EventFinalizedCheckpoint) require.Equal(t, true, ok, "event has wrong data type") assert.Equal(t, primitives.Epoch(123), fc.Epoch) assert.DeepEqual(t, headRoot[:], fc.Block) assert.DeepEqual(t, finalizedStRoot[:], fc.State) assert.Equal(t, false, fc.ExecutionOptimistic) // check the cache index, ok := headState.ValidatorIndexByPubkey(bytesutil.ToBytes48(key)) require.Equal(t, true, ok) require.Equal(t, primitives.ValidatorIndex(0), index) // first index // check deposit require.LogsContain(t, logHook, "Finalized deposit insertion completed at index") }) t.Run("deposit requests started", func(t *testing.T) { require.NoError(t, headState.SetEth1DepositIndex(1)) require.NoError(t, headState.SetDepositRequestsStartIndex(1)) s, tr := minimalTestService(t, WithFinalizedStateAtStartUp(headState)) ctx, beaconDB, stateGen := tr.ctx, tr.db, tr.sg require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, genesisRoot)) util.SaveBlock(t, ctx, beaconDB, genesis) require.NoError(t, beaconDB.SaveState(ctx, headState, headRoot)) require.NoError(t, beaconDB.SaveState(ctx, headState, genesisRoot)) util.SaveBlock(t, ctx, beaconDB, headBlock) require.NoError(t, beaconDB.SaveFinalizedCheckpoint(ctx, ðpb.Checkpoint{Epoch: slots.ToEpoch(finalizedSlot), Root: headRoot[:]})) require.NoError(t, err) require.NoError(t, stateGen.SaveState(ctx, headRoot, headState)) require.NoError(t, beaconDB.SaveLastValidatedCheckpoint(ctx, ðpb.Checkpoint{Epoch: slots.ToEpoch(finalizedSlot), Root: headRoot[:]})) notifier := &blockchainTesting.MockStateNotifier{RecordEvents: true} s.cfg.StateNotifier = notifier s.executePostFinalizationTasks(s.ctx, headState) time.Sleep(1 * time.Second) // sleep for a second because event is in a separate go routine require.Equal(t, 1, len(notifier.ReceivedEvents())) e := notifier.ReceivedEvents()[0] assert.Equal(t, statefeed.FinalizedCheckpoint, int(e.Type)) fc, ok := e.Data.(*ethpbv1.EventFinalizedCheckpoint) require.Equal(t, true, ok, "event has wrong data type") assert.Equal(t, primitives.Epoch(123), fc.Epoch) assert.DeepEqual(t, headRoot[:], fc.Block) assert.DeepEqual(t, finalizedStRoot[:], fc.State) assert.Equal(t, false, fc.ExecutionOptimistic) // check the cache index, ok := headState.ValidatorIndexByPubkey(bytesutil.ToBytes48(key)) require.Equal(t, true, ok) require.Equal(t, primitives.ValidatorIndex(0), index) // first index }) } func TestProcessLightClientBootstrap(t *testing.T) { featCfg := &features.Flags{} featCfg.EnableLightClient = true reset := features.InitWithReset(featCfg) defer reset() s, tr := minimalTestService(t, WithLCStore()) ctx := tr.ctx for testVersion := version.Altair; testVersion <= version.Electra; testVersion++ { t.Run(version.String(testVersion), func(t *testing.T) { l := util.NewTestLightClient(t, testVersion) require.NoError(t, s.cfg.BeaconDB.SaveBlock(ctx, l.FinalizedBlock)) finalizedBlockRoot, err := l.FinalizedBlock.Block().HashTreeRoot() require.NoError(t, err) require.NoError(t, s.cfg.BeaconDB.SaveState(ctx, l.FinalizedState, finalizedBlockRoot)) cp := l.AttestedState.FinalizedCheckpoint() require.DeepSSZEqual(t, finalizedBlockRoot, [32]byte(cp.Root)) require.NoError(t, s.cfg.ForkChoiceStore.UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Epoch: cp.Epoch, Root: [32]byte(cp.Root)})) s.executePostFinalizationTasks(s.ctx, l.AttestedState) // wait for the goroutine to finish processing time.Sleep(1 * time.Second) // Check that the light client bootstrap is saved b, err := s.lcStore.LightClientBootstrap(ctx, [32]byte(cp.Root)) require.NoError(t, err) require.NotNil(t, b) btst, err := lightClient.NewLightClientBootstrapFromBeaconState(ctx, l.FinalizedState.Slot(), l.FinalizedState, l.FinalizedBlock) require.NoError(t, err) require.DeepEqual(t, btst, b) require.Equal(t, b.Version(), testVersion) }) } }