package backfill import ( "context" "testing" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/db" "github.com/pkg/errors" types "github.com/prysmaticlabs/eth2-types" "github.com/prysmaticlabs/prysm/config/params" "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block" "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper" "github.com/prysmaticlabs/prysm/testing/require" "github.com/prysmaticlabs/prysm/testing/util" ) var errEmptyMockDBMethod = errors.New("uninitialized mock db method called") type mockBackfillDB struct { saveBackfillBlockRoot func(ctx context.Context, blockRoot [32]byte) error genesisBlockRoot func(ctx context.Context) ([32]byte, error) originCheckpointBlockRoot func(ctx context.Context) ([32]byte, error) backfillBlockRoot func(ctx context.Context) ([32]byte, error) block func(ctx context.Context, blockRoot [32]byte) (block.SignedBeaconBlock, error) } var _ BackfillDB = &mockBackfillDB{} func (db *mockBackfillDB) SaveBackfillBlockRoot(ctx context.Context, blockRoot [32]byte) error { if db.saveBackfillBlockRoot != nil { return db.saveBackfillBlockRoot(ctx, blockRoot) } return errEmptyMockDBMethod } func (db *mockBackfillDB) GenesisBlockRoot(ctx context.Context) ([32]byte, error) { if db.genesisBlockRoot != nil { return db.genesisBlockRoot(ctx) } return [32]byte{}, errEmptyMockDBMethod } func (db *mockBackfillDB) OriginCheckpointBlockRoot(ctx context.Context) ([32]byte, error) { if db.originCheckpointBlockRoot != nil { return db.originCheckpointBlockRoot(ctx) } return [32]byte{}, errEmptyMockDBMethod } func (db *mockBackfillDB) BackfillBlockRoot(ctx context.Context) ([32]byte, error) { if db.backfillBlockRoot != nil { return db.backfillBlockRoot(ctx) } return [32]byte{}, errEmptyMockDBMethod } func (db *mockBackfillDB) Block(ctx context.Context, blockRoot [32]byte) (block.SignedBeaconBlock, error) { if db.block != nil { return db.block(ctx, blockRoot) } return nil, errEmptyMockDBMethod } func TestSlotCovered(t *testing.T) { cases := []struct { name string slot types.Slot status *Status result bool }{ { name: "below start true", status: &Status{start: 1}, slot: 0, result: true, }, { name: "above end true", status: &Status{end: 1}, slot: 2, result: true, }, { name: "equal end true", status: &Status{end: 1}, slot: 1, result: true, }, { name: "equal start true", status: &Status{start: 2}, slot: 2, result: true, }, { name: "between false", status: &Status{start: 1, end: 3}, slot: 2, result: false, }, { name: "genesisSync always true", status: &Status{genesisSync: true}, slot: 100, result: true, }, } for _, c := range cases { result := c.status.SlotCovered(c.slot) require.Equal(t, c.result, result) } } func TestAdvance(t *testing.T) { ctx := context.Background() saveBackfillBuf := make([][32]byte, 0) mdb := &mockBackfillDB{ saveBackfillBlockRoot: func(ctx context.Context, root [32]byte) error { saveBackfillBuf = append(saveBackfillBuf, root) return nil }, } s := &Status{end: 100, store: mdb} var root [32]byte copy(root[:], []byte{0x23, 0x23}) require.NoError(t, s.Advance(ctx, 90, root)) require.Equal(t, root, saveBackfillBuf[0]) not := s.SlotCovered(95) require.Equal(t, false, not) // this should still be len 1 after failing to advance require.Equal(t, 1, len(saveBackfillBuf)) require.ErrorIs(t, s.Advance(ctx, s.end+1, root), ErrAdvancePastOrigin) // this has an element in it from the previous test, there shouldn't be an additional one require.Equal(t, 1, len(saveBackfillBuf)) } func goodBlockRoot(root [32]byte) func(ctx context.Context) ([32]byte, error) { return func(ctx context.Context) ([32]byte, error) { return root, nil } } func setupTestBlock(slot types.Slot) (block.SignedBeaconBlock, error) { bRaw := util.NewBeaconBlock() b, err := wrapper.WrappedSignedBeaconBlock(bRaw) if err != nil { return nil, err } return b, wrapper.SetBlockSlot(b, slot) } func TestReload(t *testing.T) { ctx := context.Background() derp := errors.New("derp") originSlot := types.Slot(100) var originRoot [32]byte copy(originRoot[:], []byte{0x01}) originBlock, err := setupTestBlock(originSlot) require.NoError(t, err) backfillSlot := types.Slot(50) var backfillRoot [32]byte copy(originRoot[:], []byte{0x02}) backfillBlock, err := setupTestBlock(backfillSlot) require.NoError(t, err) cases := []struct { name string db BackfillDB err error expected *Status }{ { name: "origin not found, implying genesis sync ", db: &mockBackfillDB{ genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash), originCheckpointBlockRoot: func(ctx context.Context) ([32]byte, error) { return [32]byte{}, db.ErrNotFoundOriginBlockRoot }}, expected: &Status{genesisSync: true}, }, { name: "genesis not found error", err: db.ErrNotFoundGenesisBlockRoot, db: &mockBackfillDB{ genesisBlockRoot: func(ctx context.Context) ([32]byte, error) { return [32]byte{}, db.ErrNotFoundGenesisBlockRoot }, originCheckpointBlockRoot: goodBlockRoot(originRoot), block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) { switch root { case originRoot: return originBlock, nil } return nil, nil }, }, }, { name: "other genesis error", err: derp, db: &mockBackfillDB{ genesisBlockRoot: func(ctx context.Context) ([32]byte, error) { return [32]byte{}, derp }, originCheckpointBlockRoot: goodBlockRoot(originRoot), block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) { switch root { case originRoot: return originBlock, nil } return nil, nil }, }, }, { name: "origin other error", db: &mockBackfillDB{ genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash), originCheckpointBlockRoot: func(ctx context.Context) ([32]byte, error) { return [32]byte{}, derp }}, err: derp, }, { name: "origin root found, block missing", db: &mockBackfillDB{ genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash), originCheckpointBlockRoot: goodBlockRoot(originRoot), block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) { return nil, nil }, }, err: helpers.ErrNilSignedBeaconBlock, }, { name: "origin root found, block error", db: &mockBackfillDB{ genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash), originCheckpointBlockRoot: goodBlockRoot(originRoot), block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) { return nil, derp }, }, err: derp, }, { name: "origin root found, block found, backfill root not found", db: &mockBackfillDB{ genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash), originCheckpointBlockRoot: goodBlockRoot(originRoot), block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) { return originBlock, nil }, backfillBlockRoot: func(ctx context.Context) ([32]byte, error) { return [32]byte{}, db.ErrNotFoundBackfillBlockRoot }, }, err: db.ErrNotFoundBackfillBlockRoot, }, { name: "origin root found, block found, random backfill root err", db: &mockBackfillDB{ genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash), originCheckpointBlockRoot: goodBlockRoot(originRoot), block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) { switch root { case originRoot: return originBlock, nil case backfillRoot: return nil, nil } return nil, derp }, backfillBlockRoot: func(ctx context.Context) ([32]byte, error) { return [32]byte{}, derp }, }, err: derp, }, { name: "origin root found, block found, backfill root found, backfill block not found", db: &mockBackfillDB{ genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash), originCheckpointBlockRoot: goodBlockRoot(originRoot), block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) { switch root { case originRoot: return originBlock, nil case backfillRoot: return nil, nil } return nil, derp }, backfillBlockRoot: goodBlockRoot(backfillRoot), }, err: helpers.ErrNilSignedBeaconBlock, }, { name: "origin root found, block found, backfill root found, backfill block random err", db: &mockBackfillDB{ genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash), originCheckpointBlockRoot: goodBlockRoot(originRoot), block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) { switch root { case originRoot: return originBlock, nil case backfillRoot: return nil, derp } return nil, errors.New("not derp") }, backfillBlockRoot: goodBlockRoot(backfillRoot), }, err: derp, }, { name: "complete happy path", db: &mockBackfillDB{ genesisBlockRoot: goodBlockRoot(params.BeaconConfig().ZeroHash), originCheckpointBlockRoot: goodBlockRoot(originRoot), block: func(ctx context.Context, root [32]byte) (block.SignedBeaconBlock, error) { switch root { case originRoot: return originBlock, nil case backfillRoot: return backfillBlock, nil } return nil, errors.New("not derp") }, backfillBlockRoot: goodBlockRoot(backfillRoot), }, err: derp, expected: &Status{genesisSync: false, start: backfillSlot, end: originSlot}, }, } for _, c := range cases { s := &Status{ store: c.db, } err := s.Reload(ctx) if err != nil { require.ErrorIs(t, err, c.err) continue } require.NoError(t, err) if c.expected == nil { continue } require.Equal(t, c.expected.genesisSync, s.genesisSync) require.Equal(t, c.expected.start, s.start) require.Equal(t, c.expected.end, s.end) } }