package blockchain import ( "bytes" "reflect" "testing" "github.com/ethereum/go-ethereum/ethdb" "github.com/gogo/protobuf/proto" "github.com/golang/protobuf/ptypes/timestamp" "github.com/prysmaticlabs/prysm/beacon-chain/types" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" "github.com/prysmaticlabs/prysm/shared/database" logTest "github.com/sirupsen/logrus/hooks/test" ) type faultyDB struct{} func (f *faultyDB) Get(k []byte) ([]byte, error) { return []byte{}, nil } func (f *faultyDB) Has(k []byte) (bool, error) { return true, nil } func (f *faultyDB) Put(k []byte, v []byte) error { return nil } func (f *faultyDB) Delete(k []byte) error { return nil } func (f *faultyDB) Close() {} func (f *faultyDB) NewBatch() ethdb.Batch { return nil } func startInMemoryBeaconChain(t *testing.T) (*BeaconChain, *database.DB) { config := &database.DBConfig{DataDir: "", Name: "", InMemory: true} db, err := database.NewDB(config) if err != nil { t.Fatalf("unable to setup db: %v", err) } beaconChain, err := NewBeaconChain("", db.DB()) if err != nil { t.Fatalf("unable to setup beacon chain: %v", err) } return beaconChain, db } func TestNewBeaconChain(t *testing.T) { hook := logTest.NewGlobal() beaconChain, db := startInMemoryBeaconChain(t) defer db.Close() msg := hook.LastEntry().Message want := "No chainstate found on disk, initializing beacon from genesis" if msg != want { t.Errorf("incorrect log, expected %s, got %s", want, msg) } hook.Reset() aState := types.NewGenesisActiveState() cState, err := types.NewGenesisCrystallizedState("") if err != nil { t.Errorf("Creating new genesis state failed %v", err) } if !proto.Equal(beaconChain.ActiveState().Proto(), aState.Proto()) { t.Errorf("active states not equal. received: %v, wanted: %v", beaconChain.ActiveState(), aState) } if !proto.Equal(beaconChain.CrystallizedState().Proto(), cState.Proto()) { t.Errorf("crystallized states not equal. received: %v, wanted: %v", beaconChain.CrystallizedState(), cState) } if _, err := beaconChain.genesisBlock(); err != nil { t.Errorf("Getting new beaconchain genesis failed: %v", err) } } func TestGetGenesisBlock(t *testing.T) { beaconChain, db := startInMemoryBeaconChain(t) defer db.Close() block := &pb.BeaconBlock{ ParentHash: make([]byte, 32), Timestamp: ×tamp.Timestamp{ Seconds: 13000000, }, } bytes, err := proto.Marshal(block) if err != nil { t.Errorf("unable to Marshal genesis block: %v", err) } if err := db.DB().Put([]byte("genesis"), bytes); err != nil { t.Errorf("unable to save key value of genesis: %v", err) } genesisBlock, err := beaconChain.genesisBlock() if err != nil { t.Errorf("unable to get key value of genesis: %v", err) } time, err := genesisBlock.Timestamp() if err != nil { t.Errorf("Timestamp could not be retrieved: %v", err) } if time.Second() != 40 { t.Errorf("Timestamp was not saved properly: %v", time.Second()) } } func TestGetGenesisBlock_GenesisNotExist(t *testing.T) { beaconChain, db := startInMemoryBeaconChain(t) defer db.Close() if err := db.DB().Delete([]byte("genesis")); err != nil { t.Errorf("unable to delete key value of genesis: %v", err) } genesisBlock, err := beaconChain.genesisBlock() if err != nil { t.Errorf("unable to get key value of genesis: %v", err) } time, err := genesisBlock.Timestamp() if err != nil { t.Errorf("Timestamp could not be retrieved: %v", err) } if time.Unix() != 1535673600 { t.Errorf("Timestamp was not saved properly: %v", time.Second()) } } func TestCanonicalHead(t *testing.T) { chain, err := NewBeaconChain("", &faultyDB{}) if err != nil { t.Fatalf("unable to setup second beacon chain: %v", err) } // Using a faultydb that returns true on has, but nil on get should cause // proto.Unmarshal to throw error. block, err := chain.CanonicalHead() if err != nil { t.Fatal("expected canonical head to throw error") } expectedBlock := types.NewBlock(&pb.BeaconBlock{}) if !reflect.DeepEqual(block, expectedBlock) { t.Errorf("mismatched canonical head: expected %v, received %v", expectedBlock, block) } } func TestSaveCanonicalBlock(t *testing.T) { block := types.NewBlock(&pb.BeaconBlock{}) chain, err := NewBeaconChain("", &faultyDB{}) if err != nil { t.Fatalf("unable to setup second beacon chain: %v", err) } if err := chain.saveCanonicalBlock(block); err != nil { t.Errorf("save canonical should pass: %v", err) } } func TestSetActiveState(t *testing.T) { beaconChain, db := startInMemoryBeaconChain(t) defer db.Close() data := &pb.ActiveState{ PendingAttestations: []*pb.AggregatedAttestation{ {Slot: 0, ShardBlockHash: []byte{1}}, {Slot: 1, ShardBlockHash: []byte{2}}, }, RecentBlockHashes: [][]byte{ {'A'}, {'B'}, {'C'}, {'D'}, }, } active := types.NewActiveState(data, make(map[[32]byte]*types.VoteCache)) if err := beaconChain.SetActiveState(active); err != nil { t.Fatalf("unable to mutate active state: %v", err) } if !reflect.DeepEqual(beaconChain.state.ActiveState, active) { t.Errorf("active state was not updated. wanted %v, got %v", active, beaconChain.state.ActiveState) } } func TestSetCrystallizedState(t *testing.T) { beaconChain, db := startInMemoryBeaconChain(t) defer db.Close() data := &pb.CrystallizedState{ CurrentDynasty: 3, DynastySeed: []byte{'A'}, } crystallized := types.NewCrystallizedState(data) if err := beaconChain.SetCrystallizedState(crystallized); err != nil { t.Fatalf("unable to mutate crystallized state: %v", err) } if !reflect.DeepEqual(beaconChain.state.CrystallizedState, crystallized) { t.Errorf("crystallized state was not updated. wanted %v, got %v", crystallized, beaconChain.state.CrystallizedState) } // Initializing a new beacon chain should deserialize persisted state from disk. newBeaconChain, err := NewBeaconChain("", db.DB()) if err != nil { t.Fatalf("unable to setup second beacon chain: %v", err) } // The crystallized state should still be the one we mutated and persited earlier. if crystallized.CurrentDynasty() != newBeaconChain.state.CrystallizedState.CurrentDynasty() { t.Errorf("crystallized state dynasty incorrect. wanted %v, got %v", crystallized.CurrentDynasty(), newBeaconChain.state.CrystallizedState.CurrentDynasty()) } if crystallized.DynastySeed() != newBeaconChain.state.CrystallizedState.DynastySeed() { t.Errorf("crystallized state current checkpoint incorrect. wanted %v, got %v", crystallized.DynastySeed(), newBeaconChain.state.CrystallizedState.DynastySeed()) } } func TestSaveAndRemoveBlocks(t *testing.T) { b, db := startInMemoryBeaconChain(t) defer db.Close() block := types.NewBlock(&pb.BeaconBlock{ SlotNumber: 64, PowChainRef: []byte("a"), }) hash, err := block.Hash() if err != nil { t.Fatalf("unable to generate hash of block %v", err) } if err := b.saveBlock(block); err != nil { t.Fatalf("unable to save block %v", err) } // Adding a different block with the same key newblock := types.NewBlock(&pb.BeaconBlock{ SlotNumber: 4, PowChainRef: []byte("b"), }) key := blockKey(hash) marshalled, err := proto.Marshal(newblock.Proto()) if err != nil { t.Fatal(err) } if err := b.db.Put(key, marshalled); err != nil { t.Fatal(err) } retblock, err := b.getBlock(hash) if err != nil { t.Fatalf("block is unable to be retrieved") } if retblock.SlotNumber() != newblock.SlotNumber() { t.Errorf("slotnumber does not match for saved and retrieved blocks") } if !bytes.Equal(retblock.PowChainRef().Bytes(), newblock.PowChainRef().Bytes()) { t.Errorf("POW chain ref does not match for saved and retrieved blocks") } if err := b.removeBlock(hash); err != nil { t.Fatalf("error removing block %v", err) } if _, err := b.getBlock(hash); err == nil { t.Fatalf("block is able to be retrieved") } if err := b.removeBlock(hash); err != nil { t.Fatalf("unable to remove block a second time %v", err) } } func TestCheckBlockBySlotNumber(t *testing.T) { beaconChain, db := startInMemoryBeaconChain(t) defer db.Close() block := types.NewBlock(&pb.BeaconBlock{ SlotNumber: 64, PowChainRef: []byte("a"), }) hash, err := block.Hash() if err != nil { t.Error(err) } if err := beaconChain.saveCanonicalSlotNumber(block.SlotNumber(), hash); err != nil { t.Fatalf("unable to save canonical slot %v", err) } if err := beaconChain.saveBlock(block); err != nil { t.Fatalf("unable to save block %v", err) } slotExists, err := beaconChain.hasCanonicalBlockForSlot(block.SlotNumber()) if err != nil { t.Fatalf("unable to check for block by slot %v", err) } if !slotExists { t.Error("slot does not exist despite blockhash of canonical block being saved in the db") } alternateblock := types.NewBlock(&pb.BeaconBlock{ SlotNumber: 64, PowChainRef: []byte("d"), }) althash, err := alternateblock.Hash() if err != nil { t.Fatalf("unable to hash block %v", err) } if err := beaconChain.saveCanonicalSlotNumber(block.SlotNumber(), althash); err != nil { t.Fatalf("unable to save canonical slot %v", err) } retrievedHash, err := beaconChain.db.Get(canonicalBlockKey(block.SlotNumber())) if err != nil { t.Fatalf("unable to retrieve blockhash %v", err) } if !bytes.Equal(retrievedHash, althash[:]) { t.Errorf("unequal hashes between what was saved and what was retrieved %v, %v", retrievedHash, althash) } } func TestGetBlockBySlotNumber(t *testing.T) { beaconChain, db := startInMemoryBeaconChain(t) defer db.Close() block := types.NewBlock(&pb.BeaconBlock{ SlotNumber: 64, PowChainRef: []byte("a"), }) hash, err := block.Hash() if err != nil { t.Error(err) } if err := beaconChain.saveCanonicalSlotNumber(block.SlotNumber(), hash); err != nil { t.Fatalf("unable to save canonical slot %v", err) } if err := beaconChain.saveBlock(block); err != nil { t.Fatalf("unable to save block %v", err) } retblock, err := beaconChain.canonicalBlockForSlot(block.SlotNumber()) if err != nil { t.Fatalf("unable to get block from db %v", err) } if !bytes.Equal(retblock.PowChainRef().Bytes(), block.PowChainRef().Bytes()) { t.Error("canonical block saved different from block retrieved") } alternateblock := types.NewBlock(&pb.BeaconBlock{ SlotNumber: 64, PowChainRef: []byte("d"), }) althash, err := alternateblock.Hash() if err != nil { t.Fatalf("unable to hash block %v", err) } if err := beaconChain.saveCanonicalSlotNumber(block.SlotNumber(), althash); err != nil { t.Fatalf("unable to save canonical slot %v", err) } if _, err = beaconChain.canonicalBlockForSlot(block.SlotNumber()); err == nil { t.Fatal("there should be an error because block does not exist in the db") } }