package kv import ( "context" "sort" "testing" "github.com/gogo/protobuf/proto" ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1" "github.com/prysmaticlabs/prysm/beacon-chain/db/filters" "github.com/prysmaticlabs/prysm/beacon-chain/state/stateutil" "github.com/prysmaticlabs/prysm/shared/bytesutil" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/testutil" ) func TestStore_SaveBlock_NoDuplicates(t *testing.T) { BlockCacheSize = 1 db := setupDB(t) slot := uint64(20) ctx := context.Background() // First we save a previous block to ensure the cache max size is reached. prevBlock := ðpb.SignedBeaconBlock{ Block: ðpb.BeaconBlock{ Slot: slot - 1, ParentRoot: bytesutil.PadTo([]byte{1, 2, 3}, 32), }, } if err := db.SaveBlock(ctx, prevBlock); err != nil { t.Fatal(err) } block := ðpb.SignedBeaconBlock{ Block: ðpb.BeaconBlock{ Slot: slot, ParentRoot: bytesutil.PadTo([]byte{1, 2, 3}, 32), }, } // Even with a full cache, saving new blocks should not cause // duplicated blocks in the DB. for i := 0; i < 100; i++ { if err := db.SaveBlock(ctx, block); err != nil { t.Fatal(err) } } f := filters.NewFilter().SetStartSlot(slot).SetEndSlot(slot) retrieved, err := db.Blocks(ctx, f) if err != nil { t.Fatal(err) } if len(retrieved) != 1 { t.Errorf("Expected 1, received %d: %v", len(retrieved), retrieved) } // We reset the block cache size. BlockCacheSize = 256 } func TestStore_BlocksCRUD(t *testing.T) { db := setupDB(t) ctx := context.Background() block := ðpb.SignedBeaconBlock{ Block: ðpb.BeaconBlock{ Slot: 20, ParentRoot: bytesutil.PadTo([]byte{1, 2, 3}, 32), }, } blockRoot, err := stateutil.BlockRoot(block.Block) if err != nil { t.Fatal(err) } retrievedBlock, err := db.Block(ctx, blockRoot) if err != nil { t.Fatal(err) } if retrievedBlock != nil { t.Errorf("Expected nil block, received %v", retrievedBlock) } if err := db.SaveBlock(ctx, block); err != nil { t.Fatal(err) } if !db.HasBlock(ctx, blockRoot) { t.Error("Expected block to exist in the db") } retrievedBlock, err = db.Block(ctx, blockRoot) if err != nil { t.Fatal(err) } if !proto.Equal(block, retrievedBlock) { t.Errorf("Wanted %v, received %v", block, retrievedBlock) } if err := db.deleteBlock(ctx, blockRoot); err != nil { t.Fatal(err) } if db.HasBlock(ctx, blockRoot) { t.Error("Expected block to have been deleted from the db") } } func TestStore_BlocksBatchDelete(t *testing.T) { db := setupDB(t) ctx := context.Background() numBlocks := 10 totalBlocks := make([]*ethpb.SignedBeaconBlock, numBlocks) blockRoots := make([][32]byte, 0) oddBlocks := make([]*ethpb.SignedBeaconBlock, 0) for i := 0; i < len(totalBlocks); i++ { b := testutil.NewBeaconBlock() b.Block.Slot = uint64(i) b.Block.ParentRoot = bytesutil.PadTo([]byte("parent"), 32) totalBlocks[i] = b if i%2 == 0 { r, err := stateutil.BlockRoot(totalBlocks[i].Block) if err != nil { t.Fatal(err) } blockRoots = append(blockRoots, r) } else { oddBlocks = append(oddBlocks, totalBlocks[i]) } } if err := db.SaveBlocks(ctx, totalBlocks); err != nil { t.Fatal(err) } retrieved, err := db.Blocks(ctx, filters.NewFilter().SetParentRoot(bytesutil.PadTo([]byte("parent"), 32))) if err != nil { t.Fatal(err) } if len(retrieved) != numBlocks { t.Errorf("Received %d blocks, wanted 1000", len(retrieved)) } // We delete all even indexed blocks. if err := db.deleteBlocks(ctx, blockRoots); err != nil { t.Fatal(err) } // When we retrieve the data, only the odd indexed blocks should remain. retrieved, err = db.Blocks(ctx, filters.NewFilter().SetParentRoot(bytesutil.PadTo([]byte("parent"), 32))) if err != nil { t.Fatal(err) } sort.Slice(retrieved, func(i, j int) bool { return retrieved[i].Block.Slot < retrieved[j].Block.Slot }) for i, block := range retrieved { if !proto.Equal(block, oddBlocks[i]) { t.Errorf("Wanted %v, received %v", oddBlocks[i], block) } } } func TestStore_GenesisBlock(t *testing.T) { db := setupDB(t) ctx := context.Background() genesisBlock := testutil.NewBeaconBlock() genesisBlock.Block.ParentRoot = bytesutil.PadTo([]byte{1, 2, 3}, 32) blockRoot, err := stateutil.BlockRoot(genesisBlock.Block) if err != nil { t.Fatal(err) } if err := db.SaveGenesisBlockRoot(ctx, blockRoot); err != nil { t.Fatal(err) } if err := db.SaveBlock(ctx, genesisBlock); err != nil { t.Fatal(err) } retrievedBlock, err := db.GenesisBlock(ctx) if err != nil { t.Fatal(err) } if !proto.Equal(genesisBlock, retrievedBlock) { t.Errorf("Wanted %v, received %v", genesisBlock, retrievedBlock) } } func TestStore_BlocksCRUD_NoCache(t *testing.T) { db := setupDB(t) ctx := context.Background() block := testutil.NewBeaconBlock() block.Block.Slot = 20 block.Block.ParentRoot = bytesutil.PadTo([]byte{1, 2, 3}, 32) blockRoot, err := stateutil.BlockRoot(block.Block) if err != nil { t.Fatal(err) } retrievedBlock, err := db.Block(ctx, blockRoot) if err != nil { t.Fatal(err) } if retrievedBlock != nil { t.Errorf("Expected nil block, received %v", retrievedBlock) } if err := db.SaveBlock(ctx, block); err != nil { t.Fatal(err) } db.blockCache.Del(string(blockRoot[:])) if !db.HasBlock(ctx, blockRoot) { t.Error("Expected block to exist in the db") } retrievedBlock, err = db.Block(ctx, blockRoot) if err != nil { t.Fatal(err) } if !proto.Equal(block, retrievedBlock) { t.Errorf("Wanted %v, received %v", block, retrievedBlock) } if err := db.deleteBlock(ctx, blockRoot); err != nil { t.Fatal(err) } if db.HasBlock(ctx, blockRoot) { t.Error("Expected block to have been deleted from the db") } } func TestStore_Blocks_FiltersCorrectly(t *testing.T) { db := setupDB(t) b4 := testutil.NewBeaconBlock() b4.Block.Slot = 4 b4.Block.ParentRoot = bytesutil.PadTo([]byte("parent"), 32) b5 := testutil.NewBeaconBlock() b5.Block.Slot = 5 b5.Block.ParentRoot = bytesutil.PadTo([]byte("parent2"), 32) b6 := testutil.NewBeaconBlock() b6.Block.Slot = 6 b6.Block.ParentRoot = bytesutil.PadTo([]byte("parent2"), 32) b7 := testutil.NewBeaconBlock() b7.Block.Slot = 7 b7.Block.ParentRoot = bytesutil.PadTo([]byte("parent3"), 32) b8 := testutil.NewBeaconBlock() b8.Block.Slot = 8 b8.Block.ParentRoot = bytesutil.PadTo([]byte("parent4"), 32) blocks := []*ethpb.SignedBeaconBlock{b4, b5, b6, b7, b8} ctx := context.Background() if err := db.SaveBlocks(ctx, blocks); err != nil { t.Fatal(err) } tests := []struct { filter *filters.QueryFilter expectedNumBlocks int }{ { filter: filters.NewFilter().SetParentRoot(bytesutil.PadTo([]byte("parent2"), 32)), expectedNumBlocks: 2, }, { // No block meets the criteria below. filter: filters.NewFilter().SetParentRoot(bytesutil.PadTo([]byte{3, 4, 5}, 32)), expectedNumBlocks: 0, }, { // Block slot range filter criteria. filter: filters.NewFilter().SetStartSlot(5).SetEndSlot(7), expectedNumBlocks: 3, }, { filter: filters.NewFilter().SetStartSlot(7).SetEndSlot(7), expectedNumBlocks: 1, }, { filter: filters.NewFilter().SetStartSlot(4).SetEndSlot(8), expectedNumBlocks: 5, }, { filter: filters.NewFilter().SetStartSlot(4).SetEndSlot(5), expectedNumBlocks: 2, }, { filter: filters.NewFilter().SetStartSlot(5), expectedNumBlocks: 4, }, { filter: filters.NewFilter().SetEndSlot(7), expectedNumBlocks: 4, }, { filter: filters.NewFilter().SetEndSlot(8), expectedNumBlocks: 5, }, { filter: filters.NewFilter().SetStartSlot(5).SetEndSlot(10), expectedNumBlocks: 4, }, { // Composite filter criteria. filter: filters.NewFilter(). SetParentRoot(bytesutil.PadTo([]byte("parent2"), 32)). SetStartSlot(6). SetEndSlot(8), expectedNumBlocks: 1, }, } for _, tt := range tests { retrievedBlocks, err := db.Blocks(ctx, tt.filter) if err != nil { t.Fatal(err) } if len(retrievedBlocks) != tt.expectedNumBlocks { t.Errorf("Expected %d blocks, received %d", tt.expectedNumBlocks, len(retrievedBlocks)) } } } func TestStore_Blocks_Retrieve_SlotRange(t *testing.T) { db := setupDB(t) totalBlocks := make([]*ethpb.SignedBeaconBlock, 500) for i := 0; i < 500; i++ { b := testutil.NewBeaconBlock() b.Block.Slot = uint64(i) b.Block.ParentRoot = bytesutil.PadTo([]byte("parent"), 32) totalBlocks[i] = b } ctx := context.Background() if err := db.SaveBlocks(ctx, totalBlocks); err != nil { t.Fatal(err) } retrieved, err := db.Blocks(ctx, filters.NewFilter().SetStartSlot(100).SetEndSlot(399)) if err != nil { t.Fatal(err) } want := 300 if len(retrieved) != want { t.Errorf("Wanted %d, received %d", want, len(retrieved)) } } func TestStore_Blocks_Retrieve_Epoch(t *testing.T) { db := setupDB(t) slots := params.BeaconConfig().SlotsPerEpoch * 7 totalBlocks := make([]*ethpb.SignedBeaconBlock, slots) for i := uint64(0); i < slots; i++ { b := testutil.NewBeaconBlock() b.Block.Slot = i b.Block.ParentRoot = bytesutil.PadTo([]byte("parent"), 32) totalBlocks[i] = b } ctx := context.Background() if err := db.SaveBlocks(ctx, totalBlocks); err != nil { t.Fatal(err) } retrieved, err := db.Blocks(ctx, filters.NewFilter().SetStartEpoch(5).SetEndEpoch(6)) if err != nil { t.Fatal(err) } want := params.BeaconConfig().SlotsPerEpoch * 2 if uint64(len(retrieved)) != want { t.Errorf("Wanted %d, received %d", want, len(retrieved)) } retrieved, err = db.Blocks(ctx, filters.NewFilter().SetStartEpoch(0).SetEndEpoch(0)) if err != nil { t.Fatal(err) } want = params.BeaconConfig().SlotsPerEpoch if uint64(len(retrieved)) != want { t.Errorf("Wanted %d, received %d", want, len(retrieved)) } } func TestStore_Blocks_Retrieve_SlotRangeWithStep(t *testing.T) { db := setupDB(t) totalBlocks := make([]*ethpb.SignedBeaconBlock, 500) for i := 0; i < 500; i++ { b := testutil.NewBeaconBlock() b.Block.Slot = uint64(i) b.Block.ParentRoot = bytesutil.PadTo([]byte("parent"), 32) totalBlocks[i] = b } const step = 2 ctx := context.Background() if err := db.SaveBlocks(ctx, totalBlocks); err != nil { t.Fatal(err) } retrieved, err := db.Blocks(ctx, filters.NewFilter().SetStartSlot(100).SetEndSlot(399).SetSlotStep(step)) if err != nil { t.Fatal(err) } want := 150 if len(retrieved) != want { t.Errorf("Wanted %d, received %d", want, len(retrieved)) } for _, b := range retrieved { if (b.Block.Slot-100)%step != 0 { t.Errorf("Unexpect block slot %d", b.Block.Slot) } } } func TestStore_SaveBlock_CanGetHighestAt(t *testing.T) { db := setupDB(t) ctx := context.Background() block1 := testutil.NewBeaconBlock() block1.Block.Slot = 1 if err := db.SaveBlock(ctx, block1); err != nil { t.Fatal(err) } block2 := testutil.NewBeaconBlock() block2.Block.Slot = 10 if err := db.SaveBlock(ctx, block2); err != nil { t.Fatal(err) } block3 := testutil.NewBeaconBlock() block3.Block.Slot = 100 if err := db.SaveBlock(ctx, block3); err != nil { t.Fatal(err) } highestAt, err := db.HighestSlotBlocksBelow(ctx, 2) if err != nil { t.Fatal(err) } if len(highestAt) <= 0 { t.Fatal("Got empty highest at slice") } if !proto.Equal(block1, highestAt[0]) { t.Errorf("Wanted %v, received %v", block1, highestAt) } highestAt, err = db.HighestSlotBlocksBelow(ctx, 11) if err != nil { t.Fatal(err) } if len(highestAt) <= 0 { t.Fatal("Got empty highest at slice") } if !proto.Equal(block2, highestAt[0]) { t.Errorf("Wanted %v, received %v", block2, highestAt) } highestAt, err = db.HighestSlotBlocksBelow(ctx, 101) if err != nil { t.Fatal(err) } if len(highestAt) <= 0 { t.Fatal("Got empty highest at slice") } if !proto.Equal(block3, highestAt[0]) { t.Errorf("Wanted %v, received %v", block3, highestAt) } r3, err := stateutil.BlockRoot(block3.Block) if err != nil { t.Fatal(err) } if err := db.deleteBlock(ctx, r3); err != nil { t.Fatal(err) } highestAt, err = db.HighestSlotBlocksBelow(ctx, 101) if err != nil { t.Fatal(err) } if !proto.Equal(block2, highestAt[0]) { t.Errorf("Wanted %v, received %v", block2, highestAt) } } func TestStore_GenesisBlock_CanGetHighestAt(t *testing.T) { db := setupDB(t) ctx := context.Background() genesisBlock := testutil.NewBeaconBlock() genesisRoot, err := stateutil.BlockRoot(genesisBlock.Block) if err != nil { t.Fatal(err) } if err := db.SaveGenesisBlockRoot(ctx, genesisRoot); err != nil { t.Fatal(err) } if err := db.SaveBlock(ctx, genesisBlock); err != nil { t.Fatal(err) } block1 := testutil.NewBeaconBlock() block1.Block.Slot = 1 if err := db.SaveBlock(ctx, block1); err != nil { t.Fatal(err) } highestAt, err := db.HighestSlotBlocksBelow(ctx, 2) if err != nil { t.Fatal(err) } if !proto.Equal(block1, highestAt[0]) { t.Errorf("Wanted %v, received %v", block1, highestAt) } highestAt, err = db.HighestSlotBlocksBelow(ctx, 1) if err != nil { t.Fatal(err) } if !proto.Equal(genesisBlock, highestAt[0]) { t.Errorf("Wanted %v, received %v", genesisBlock, highestAt) } highestAt, err = db.HighestSlotBlocksBelow(ctx, 0) if err != nil { t.Fatal(err) } if !proto.Equal(genesisBlock, highestAt[0]) { t.Errorf("Wanted %v, received %v", genesisBlock, highestAt) } } func TestStore_SaveBlocks_HasCachedBlocks(t *testing.T) { db := setupDB(t) ctx := context.Background() b := make([]*ethpb.SignedBeaconBlock, 500) for i := 0; i < 500; i++ { b[i] = ðpb.SignedBeaconBlock{ Block: ðpb.BeaconBlock{ ParentRoot: bytesutil.PadTo([]byte("parent"), 32), Slot: uint64(i), }, } } if err := db.SaveBlock(ctx, b[0]); err != nil { t.Fatal(err) } if err := db.SaveBlocks(ctx, b); err != nil { t.Fatal(err) } f := filters.NewFilter().SetStartSlot(0).SetEndSlot(500) blks, err := db.Blocks(ctx, f) if err != nil { t.Fatal(err) } if len(blks) != 500 { t.Log(len(blks)) t.Error("Did not get wanted blocks") } }