mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-24 12:27:18 +00:00
1025 lines
30 KiB
Go
1025 lines
30 KiB
Go
package blockchain
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
gethTypes "github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/ethdb"
|
|
"github.com/gogo/protobuf/proto"
|
|
"github.com/golang/protobuf/ptypes/timestamp"
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/params"
|
|
"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"
|
|
"golang.org/x/crypto/blake2b"
|
|
)
|
|
|
|
// FakeClock represents an mocked clock for testing purposes.
|
|
type fakeClock struct{}
|
|
|
|
// Now represents the mocked functionality of a Clock.Now().
|
|
func (fakeClock) Now() time.Time {
|
|
return time.Date(1970, 2, 1, 1, 0, 0, 0, time.UTC)
|
|
}
|
|
|
|
type faultyFetcher struct{}
|
|
|
|
func (f *faultyFetcher) BlockByHash(ctx context.Context, hash common.Hash) (*gethTypes.Block, error) {
|
|
return nil, errors.New("cannot fetch block")
|
|
}
|
|
|
|
type mockFetcher struct{}
|
|
|
|
func (m *mockFetcher) BlockByHash(ctx context.Context, hash common.Hash) (*gethTypes.Block, error) {
|
|
block := gethTypes.NewBlock(&gethTypes.Header{}, nil, nil, nil)
|
|
return block, nil
|
|
}
|
|
|
|
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()
|
|
active, crystallized, err := types.NewGenesisStates()
|
|
if err != nil {
|
|
t.Errorf("Creating new genesis state failed %v", err)
|
|
}
|
|
if _, err := types.NewGenesisBlock(); err != nil {
|
|
t.Errorf("Creating a new genesis block failed %v", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(beaconChain.ActiveState(), active) {
|
|
t.Errorf("active states not equal. received: %v, wanted: %v", beaconChain.ActiveState(), active)
|
|
}
|
|
|
|
if !reflect.DeepEqual(beaconChain.CrystallizedState(), crystallized) {
|
|
t.Errorf("crystallized states not equal. received: %v, wanted: %v", beaconChain.CrystallizedState(), crystallized)
|
|
}
|
|
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 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.AttestationRecord{
|
|
{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 TestCanProcessBlock(t *testing.T) {
|
|
beaconChain, db := startInMemoryBeaconChain(t)
|
|
defer db.Close()
|
|
|
|
clock = &fakeClock{}
|
|
|
|
// Initialize a parent block.
|
|
parentBlock := NewBlock(t, &pb.BeaconBlock{
|
|
SlotNumber: 1,
|
|
})
|
|
parentHash, err := parentBlock.Hash()
|
|
if err != nil {
|
|
t.Fatalf("Failed to compute parent block's hash: %v", err)
|
|
}
|
|
if err = db.DB().Put(parentHash[:], []byte{}); err != nil {
|
|
t.Fatalf("Failed to put parent block on db: %v", err)
|
|
}
|
|
|
|
// Using a faulty fetcher should throw an error.
|
|
block := NewBlock(t, &pb.BeaconBlock{
|
|
SlotNumber: 2,
|
|
})
|
|
if _, err := beaconChain.CanProcessBlock(&faultyFetcher{}, block, true); err == nil {
|
|
t.Error("Using a faulty fetcher should throw an error, received nil")
|
|
}
|
|
|
|
// Initialize initial state.
|
|
activeState := types.NewActiveState(&pb.ActiveState{RecentBlockHashes: [][]byte{{'A'}}}, make(map[[32]byte]*types.VoteCache))
|
|
beaconChain.state.ActiveState = activeState
|
|
activeHash, err := activeState.Hash()
|
|
if err != nil {
|
|
t.Fatalf("Cannot hash active state: %v", err)
|
|
}
|
|
|
|
crystallized := types.NewCrystallizedState(&pb.CrystallizedState{})
|
|
beaconChain.state.CrystallizedState = crystallized
|
|
crystallizedHash, err := crystallized.Hash()
|
|
if err != nil {
|
|
t.Fatalf("Compute crystallized state hash failed: %v", err)
|
|
}
|
|
|
|
block = NewBlock(t, &pb.BeaconBlock{
|
|
SlotNumber: 2,
|
|
ActiveStateHash: activeHash[:],
|
|
CrystallizedStateHash: crystallizedHash[:],
|
|
ParentHash: parentHash[:],
|
|
})
|
|
|
|
canProcess, err := beaconChain.CanProcessBlock(&mockFetcher{}, block, true)
|
|
if err != nil {
|
|
t.Fatalf("canProcessBlocks failed: %v", err)
|
|
}
|
|
if !canProcess {
|
|
t.Error("Should be able to process block, could not")
|
|
}
|
|
|
|
// Negative scenario, invalid timestamp
|
|
block = NewBlock(t, &pb.BeaconBlock{
|
|
SlotNumber: 1000000,
|
|
ActiveStateHash: activeHash[:],
|
|
CrystallizedStateHash: crystallizedHash[:],
|
|
ParentHash: parentHash[:],
|
|
})
|
|
|
|
canProcess, err = beaconChain.CanProcessBlock(&mockFetcher{}, block, true)
|
|
if err == nil {
|
|
t.Fatalf("CanProcessBlocks failed: %v", err)
|
|
}
|
|
if canProcess {
|
|
t.Error("Should not be able to process block with invalid timestamp condition")
|
|
}
|
|
}
|
|
|
|
func TestIsSlotTransition(t *testing.T) {
|
|
beaconChain, db := startInMemoryBeaconChain(t)
|
|
defer db.Close()
|
|
|
|
if err := beaconChain.SetCrystallizedState(types.NewCrystallizedState(&pb.CrystallizedState{LastStateRecalc: params.CycleLength})); err != nil {
|
|
t.Fatalf("unable to mutate crystallizedstate: %v", err)
|
|
}
|
|
if !beaconChain.IsCycleTransition(128) {
|
|
t.Errorf("there was supposed to be a slot transition but there isn't one now")
|
|
}
|
|
if beaconChain.IsCycleTransition(80) {
|
|
t.Errorf("there is not supposed to be a slot transition but there is one now")
|
|
}
|
|
}
|
|
|
|
func TestCanProcessBlockObserver(t *testing.T) {
|
|
beaconChain, db := startInMemoryBeaconChain(t)
|
|
defer db.Close()
|
|
|
|
clock = &fakeClock{}
|
|
|
|
// Initialize a parent block.
|
|
parentBlock := NewBlock(t, &pb.BeaconBlock{
|
|
SlotNumber: 1,
|
|
})
|
|
parentHash, err := parentBlock.Hash()
|
|
if err != nil {
|
|
t.Fatalf("Failed to compute parent block's hash: %v", err)
|
|
}
|
|
if err = db.DB().Put(parentHash[:], []byte{}); err != nil {
|
|
t.Fatalf("Failed to put parent block on db: %v", err)
|
|
}
|
|
|
|
// Initialize initial state.
|
|
activeState := types.NewActiveState(&pb.ActiveState{RecentBlockHashes: [][]byte{{'A'}}}, make(map[[32]byte]*types.VoteCache))
|
|
beaconChain.state.ActiveState = activeState
|
|
activeHash, err := activeState.Hash()
|
|
if err != nil {
|
|
t.Fatalf("Cannot hash active state: %v", err)
|
|
}
|
|
|
|
crystallized := types.NewCrystallizedState(&pb.CrystallizedState{})
|
|
beaconChain.state.CrystallizedState = crystallized
|
|
crystallizedHash, err := crystallized.Hash()
|
|
if err != nil {
|
|
t.Fatalf("Compute crystallized state hash failed: %v", err)
|
|
}
|
|
|
|
block := NewBlock(t, &pb.BeaconBlock{
|
|
SlotNumber: 2,
|
|
ActiveStateHash: activeHash[:],
|
|
CrystallizedStateHash: crystallizedHash[:],
|
|
ParentHash: parentHash[:],
|
|
})
|
|
|
|
// A properly initialize block should not fail.
|
|
canProcess, err := beaconChain.CanProcessBlock(nil, block, false)
|
|
if err != nil {
|
|
t.Fatalf("CanProcessBlocks failed: %v", err)
|
|
}
|
|
if !canProcess {
|
|
t.Error("Should be able to process block, could not")
|
|
}
|
|
|
|
// Negative scenario, invalid timestamp
|
|
block = NewBlock(t, &pb.BeaconBlock{
|
|
SlotNumber: 1000000,
|
|
ActiveStateHash: activeHash[:],
|
|
CrystallizedStateHash: crystallizedHash[:],
|
|
ParentHash: parentHash[:],
|
|
})
|
|
canProcess, err = beaconChain.CanProcessBlock(nil, block, false)
|
|
if err == nil {
|
|
t.Fatalf("CanProcessBlocks failed: %v", err)
|
|
}
|
|
if canProcess {
|
|
t.Error("Should not be able to process block with invalid timestamp condition")
|
|
}
|
|
}
|
|
|
|
func TestSaveBlockWithNil(t *testing.T) {
|
|
beaconChain, db := startInMemoryBeaconChain(t)
|
|
defer db.Close()
|
|
|
|
if err := beaconChain.saveBlock(&types.Block{}); err == nil {
|
|
t.Error("Save block should have failed with nil block")
|
|
}
|
|
}
|
|
|
|
func TestComputeActiveState(t *testing.T) {
|
|
beaconChain, db := startInMemoryBeaconChain(t)
|
|
defer db.Close()
|
|
active, crystallized, err := types.NewGenesisStates()
|
|
if err != nil {
|
|
t.Fatalf("Can't generate genesis state: %v", err)
|
|
}
|
|
beaconChain.SetCrystallizedState(crystallized)
|
|
if _, err := beaconChain.computeNewActiveState([]*pb.AttestationRecord{}, active, map[[32]byte]*types.VoteCache{}, [32]byte{}); err != nil {
|
|
t.Errorf("computing active state should not have failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCanProcessAttestations(t *testing.T) {
|
|
bc, db := startInMemoryBeaconChain(t)
|
|
defer db.Close()
|
|
|
|
// Process attestation on this block should fail because AttestationRecord's slot # > than block's slot #.
|
|
block := NewBlock(t, &pb.BeaconBlock{
|
|
SlotNumber: 1,
|
|
Attestations: []*pb.AttestationRecord{
|
|
{Slot: 2, ShardId: 0},
|
|
},
|
|
})
|
|
if err := bc.processAttestation(0, block); err == nil {
|
|
t.Error("Process attestation should have failed because attestation slot # > block #")
|
|
}
|
|
|
|
// Process attestation on this should fail because AttestationRecord's slot # > than block's slot #.
|
|
block = NewBlock(t, &pb.BeaconBlock{
|
|
SlotNumber: 2 + params.CycleLength,
|
|
Attestations: []*pb.AttestationRecord{
|
|
{Slot: 1, ShardId: 0},
|
|
},
|
|
})
|
|
|
|
if err := bc.processAttestation(0, block); err == nil {
|
|
t.Error("Process attestation should have failed because attestation slot # < block # + cycle length")
|
|
}
|
|
|
|
block = NewBlock(t, &pb.BeaconBlock{
|
|
SlotNumber: 1,
|
|
Attestations: []*pb.AttestationRecord{
|
|
{Slot: 0, ShardId: 0, ObliqueParentHashes: [][]byte{{'A'}, {'B'}, {'C'}}},
|
|
},
|
|
})
|
|
var recentBlockHashes [][]byte
|
|
for i := 0; i < params.CycleLength; i++ {
|
|
recentBlockHashes = append(recentBlockHashes, []byte{'X'})
|
|
}
|
|
active := types.NewActiveState(&pb.ActiveState{RecentBlockHashes: recentBlockHashes}, make(map[[32]byte]*types.VoteCache))
|
|
if err := bc.SetActiveState(active); err != nil {
|
|
t.Fatalf("unable to mutate active state: %v", err)
|
|
}
|
|
|
|
// Process attestation on this crystallized state should fail because only committee is in shard 1.
|
|
crystallized := types.NewCrystallizedState(&pb.CrystallizedState{
|
|
LastStateRecalc: 0,
|
|
IndicesForSlots: []*pb.ShardAndCommitteeArray{
|
|
{
|
|
ArrayShardAndCommittee: []*pb.ShardAndCommittee{
|
|
{ShardId: 1, Committee: []uint32{0, 1, 2, 3, 4, 5}},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err := bc.SetCrystallizedState(crystallized); err != nil {
|
|
t.Fatalf("unable to mutate crystallized state: %v", err)
|
|
}
|
|
|
|
if err := bc.processAttestation(0, block); err == nil {
|
|
t.Error("Process attestation should have failed, there's no committee in shard 0")
|
|
}
|
|
|
|
// Process attestation should work now, there's a committee in shard 0.
|
|
crystallized = types.NewCrystallizedState(&pb.CrystallizedState{
|
|
LastStateRecalc: 0,
|
|
IndicesForSlots: []*pb.ShardAndCommitteeArray{
|
|
{
|
|
ArrayShardAndCommittee: []*pb.ShardAndCommittee{
|
|
{ShardId: 0, Committee: []uint32{0, 1, 2, 3, 4, 5}},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err := bc.SetCrystallizedState(crystallized); err != nil {
|
|
t.Fatalf("unable to mutate crystallized state: %v", err)
|
|
}
|
|
|
|
// Process attestation should fail because attester bit field has incorrect length.
|
|
block = NewBlock(t, &pb.BeaconBlock{
|
|
SlotNumber: 0,
|
|
Attestations: []*pb.AttestationRecord{
|
|
{Slot: 0, ShardId: 0, AttesterBitfield: []byte{'A', 'B', 'C'}},
|
|
},
|
|
})
|
|
|
|
if err := bc.processAttestation(0, block); err == nil {
|
|
t.Error("Process attestation should have failed, incorrect attester bit field length")
|
|
}
|
|
|
|
// Set attester bitfield to the right length.
|
|
block = NewBlock(t, &pb.BeaconBlock{
|
|
SlotNumber: 0,
|
|
Attestations: []*pb.AttestationRecord{
|
|
{Slot: 0, ShardId: 0, AttesterBitfield: []byte{'a'}},
|
|
},
|
|
})
|
|
// Process attestation should fail because the non-zero leading bits for votes.
|
|
// a is 01100001
|
|
|
|
if err := bc.processAttestation(0, block); err == nil {
|
|
t.Error("Process attestation should have failed, incorrect attester bit field length")
|
|
}
|
|
|
|
// Process attestation should work with correct bitfield bits.
|
|
block = NewBlock(t, &pb.BeaconBlock{
|
|
SlotNumber: 0,
|
|
Attestations: []*pb.AttestationRecord{
|
|
{Slot: 0, ShardId: 0, AttesterBitfield: []byte{'0'}},
|
|
},
|
|
})
|
|
|
|
if err := bc.processAttestation(0, block); err != nil {
|
|
t.Error(err)
|
|
}
|
|
}
|
|
|
|
func TestGetBlock(t *testing.T) {
|
|
beaconChain, db := startInMemoryBeaconChain(t)
|
|
defer db.Close()
|
|
|
|
block := NewBlock(t, &pb.BeaconBlock{
|
|
SlotNumber: 64,
|
|
PowChainRef: []byte("a"),
|
|
})
|
|
|
|
hash, err := block.Hash()
|
|
if err != nil {
|
|
t.Errorf("unable to generate hash of block %v", err)
|
|
}
|
|
|
|
key := blockKey(hash)
|
|
marshalled, err := proto.Marshal(block.Proto())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := beaconChain.db.Put(key, marshalled); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
retBlock, err := beaconChain.getBlock(hash)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !bytes.Equal(block.PowChainRef().Bytes(), retBlock.PowChainRef().Bytes()) {
|
|
t.Fatal("block retrieved does not have the same POW chain ref as the block saved")
|
|
}
|
|
}
|
|
func TestProcessAttestationBadSlot(t *testing.T) {
|
|
bc, db := startInMemoryBeaconChain(t)
|
|
defer db.Close()
|
|
|
|
// Process attestation should work now, there's a committee in shard 0.
|
|
crystallized := types.NewCrystallizedState(&pb.CrystallizedState{
|
|
LastJustifiedSlot: 99,
|
|
LastStateRecalc: 0,
|
|
IndicesForSlots: []*pb.ShardAndCommitteeArray{
|
|
{
|
|
ArrayShardAndCommittee: []*pb.ShardAndCommittee{
|
|
{ShardId: 0, Committee: []uint32{0, 1, 2, 3, 4, 5}},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err := bc.SetCrystallizedState(crystallized); err != nil {
|
|
t.Fatalf("unable to mutate crystallized state: %v", err)
|
|
}
|
|
|
|
// Process attestation should work with correct bitfield bits.
|
|
block := NewBlock(t, &pb.BeaconBlock{
|
|
SlotNumber: 0,
|
|
Attestations: []*pb.AttestationRecord{
|
|
{Slot: 0, ShardId: 0, AttesterBitfield: []byte{'0'}, JustifiedSlot: 98},
|
|
},
|
|
})
|
|
|
|
if err := bc.processAttestation(0, block); err == nil {
|
|
t.Error("Process attestation should have failed, justified slot was incorrect")
|
|
}
|
|
}
|
|
|
|
// Test cycle transition where there's not enough justified streak to finalize a slot.
|
|
func TestInitCycleNotFinalized(t *testing.T) {
|
|
b, db := startInMemoryBeaconChain(t)
|
|
defer db.Close()
|
|
|
|
block := types.NewBlock(nil)
|
|
|
|
active, crystallized, err := types.NewGenesisStates()
|
|
if err != nil {
|
|
t.Errorf("Creating new genesis state failed %v", err)
|
|
}
|
|
crystallized.SetStateRecalc(64)
|
|
newCrystalled, newActive, err := b.stateRecalc(crystallized, active, block)
|
|
if err != nil {
|
|
t.Fatalf("Initialize new cycle transition failed: %v", err)
|
|
}
|
|
|
|
if newCrystalled.LastFinalizedSlot() != 0 {
|
|
t.Errorf("Last finalized slot should be 0 but got: %d", newCrystalled.LastFinalizedSlot())
|
|
}
|
|
if newCrystalled.LastJustifiedSlot() != 0 {
|
|
t.Errorf("Last justified slot should be 0 but got: %d", newCrystalled.LastJustifiedSlot())
|
|
}
|
|
if newCrystalled.JustifiedStreak() != 0 {
|
|
t.Errorf("Justified streak should be 0 but got: %d", newCrystalled.JustifiedStreak())
|
|
}
|
|
if len(newActive.RecentBlockHashes()) != 128 {
|
|
t.Errorf("Recent block hash length should be 128 but got: %d", newActive.RecentBlockHashes())
|
|
}
|
|
}
|
|
|
|
// Test cycle transition where there's enough justified streak to finalize a slot.
|
|
func TestInitCycleFinalized(t *testing.T) {
|
|
b, db := startInMemoryBeaconChain(t)
|
|
defer db.Close()
|
|
|
|
block := types.NewBlock(nil)
|
|
|
|
active, crystallized, err := types.NewGenesisStates()
|
|
if err != nil {
|
|
t.Errorf("Creating new genesis state failed %v", err)
|
|
}
|
|
crystallized.SetStateRecalc(64)
|
|
|
|
var activeStateBlockHashes [][32]byte
|
|
blockVoteCache := make(map[[32]byte]*types.VoteCache)
|
|
for i := 0; i < params.CycleLength; i++ {
|
|
hash := blake2b.Sum256([]byte{byte(i)})
|
|
if err != nil {
|
|
t.Fatalf("Can't proceed test, blake2b hashing function failed: %v", err)
|
|
}
|
|
voteCache := &types.VoteCache{VoteTotalDeposit: 100000}
|
|
blockVoteCache[hash] = voteCache
|
|
activeStateBlockHashes = append(activeStateBlockHashes, hash)
|
|
}
|
|
|
|
active.ReplaceBlockHashes(activeStateBlockHashes)
|
|
active.SetBlockVoteCache(blockVoteCache)
|
|
|
|
// justified block: 63, finalized block: 0, justified streak: 64
|
|
newCrystalled, newActive, err := b.stateRecalc(crystallized, active, block)
|
|
if err != nil {
|
|
t.Fatalf("Initialize new cycle transition failed: %v", err)
|
|
}
|
|
|
|
newActive.ReplaceBlockHashes(activeStateBlockHashes)
|
|
// justified block: 127, finalized block: 63, justified streak: 128
|
|
newCrystalled, newActive, err = b.stateRecalc(newCrystalled, newActive, block)
|
|
if err != nil {
|
|
t.Fatalf("Initialize new cycle transition failed: %v", err)
|
|
}
|
|
|
|
if newCrystalled.LastFinalizedSlot() != 63 {
|
|
t.Errorf("Last finalized slot should be 63 but got: %d", newCrystalled.LastFinalizedSlot())
|
|
}
|
|
if newCrystalled.LastJustifiedSlot() != 127 {
|
|
t.Errorf("Last justified slot should be 127 but got: %d", newCrystalled.LastJustifiedSlot())
|
|
}
|
|
if newCrystalled.JustifiedStreak() != 128 {
|
|
t.Errorf("Justified streak should be 128 but got: %d", newCrystalled.JustifiedStreak())
|
|
}
|
|
if len(newActive.RecentBlockHashes()) != 64 {
|
|
t.Errorf("Recent block hash length should be 64 but got: %d", len(newActive.RecentBlockHashes()))
|
|
}
|
|
}
|
|
|
|
// NewBlock is a helper method to create blocks with valid defaults.
|
|
// For a generic block, use NewBlock(t, nil).
|
|
func NewBlock(t *testing.T, b *pb.BeaconBlock) *types.Block {
|
|
if b == nil {
|
|
b = &pb.BeaconBlock{}
|
|
}
|
|
if b.ActiveStateHash == nil {
|
|
b.ActiveStateHash = make([]byte, 32)
|
|
}
|
|
if b.CrystallizedStateHash == nil {
|
|
b.CrystallizedStateHash = make([]byte, 32)
|
|
}
|
|
if b.ParentHash == nil {
|
|
b.ParentHash = make([]byte, 32)
|
|
}
|
|
|
|
return types.NewBlock(b)
|
|
}
|
|
|
|
// NewAttestation is a helper method to create attestation with valid defaults.
|
|
func NewAttestation(t *testing.T, b *pb.AttestationRecord) *types.Attestation {
|
|
if b == nil {
|
|
b = &pb.AttestationRecord{}
|
|
}
|
|
return types.NewAttestation(b)
|
|
}
|
|
|
|
func TestSaveAndRemoveBlocks(t *testing.T) {
|
|
b, db := startInMemoryBeaconChain(t)
|
|
defer db.Close()
|
|
|
|
block := NewBlock(t, &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 := NewBlock(t, &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 := NewBlock(t, &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 := NewBlock(t, &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 := NewBlock(t, &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.getCanonicalBlockForSlot(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 := NewBlock(t, &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.getCanonicalBlockForSlot(block.SlotNumber()); err == nil {
|
|
t.Fatal("there should be an error because block does not exist in the db")
|
|
}
|
|
}
|
|
|
|
func TestSaveAndRemoveAttestations(t *testing.T) {
|
|
b, db := startInMemoryBeaconChain(t)
|
|
defer db.Close()
|
|
|
|
attestation := NewAttestation(t, &pb.AttestationRecord{
|
|
Slot: 1,
|
|
ShardId: 1,
|
|
AttesterBitfield: []byte{'A'},
|
|
})
|
|
|
|
hash := attestation.Key()
|
|
if err := b.saveAttestation(attestation); err != nil {
|
|
t.Fatalf("unable to save attestation %v", err)
|
|
}
|
|
|
|
exist, err := b.hasAttestation(hash)
|
|
if err != nil {
|
|
t.Fatalf("unable to check attestation %v", err)
|
|
}
|
|
if !exist {
|
|
t.Fatal("saved attestation does not exist")
|
|
}
|
|
|
|
// Adding a different attestation with the same key
|
|
newAttestation := NewAttestation(t, &pb.AttestationRecord{
|
|
Slot: 2,
|
|
ShardId: 2,
|
|
AttesterBitfield: []byte{'B'},
|
|
})
|
|
|
|
key := blockKey(hash)
|
|
marshalled, err := proto.Marshal(newAttestation.Proto())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := b.db.Put(key, marshalled); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
returnedAttestation, err := b.getAttestation(hash)
|
|
if err != nil {
|
|
t.Fatalf("attestation is unable to be retrieved")
|
|
}
|
|
|
|
if returnedAttestation.SlotNumber() != newAttestation.SlotNumber() {
|
|
t.Errorf("slotnumber does not match for saved and retrieved attestation")
|
|
}
|
|
|
|
if !bytes.Equal(returnedAttestation.AttesterBitfield(), newAttestation.AttesterBitfield()) {
|
|
t.Errorf("attester bitfield does not match for saved and retrieved attester")
|
|
}
|
|
|
|
if err := b.removeAttestation(hash); err != nil {
|
|
t.Fatalf("error removing attestation %v", err)
|
|
}
|
|
|
|
if _, err := b.getAttestation(hash); err == nil {
|
|
t.Fatalf("attestation is able to be retrieved")
|
|
}
|
|
}
|
|
|
|
func TestSaveAndRemoveAttestationHashList(t *testing.T) {
|
|
b, db := startInMemoryBeaconChain(t)
|
|
defer db.Close()
|
|
|
|
block := NewBlock(t, &pb.BeaconBlock{
|
|
SlotNumber: 0,
|
|
})
|
|
blockHash, err := block.Hash()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
|
|
attestation := NewAttestation(t, &pb.AttestationRecord{
|
|
Slot: 1,
|
|
ShardId: 1,
|
|
AttesterBitfield: []byte{'A'},
|
|
})
|
|
attestationHash := attestation.Key()
|
|
|
|
if err := b.saveAttestationHash(blockHash, attestationHash); err != nil {
|
|
t.Fatalf("unable to save attestation hash %v", err)
|
|
}
|
|
|
|
exist, err := b.hasAttestationHash(blockHash, attestationHash)
|
|
if err != nil {
|
|
t.Fatalf("unable to check for attestation hash %v", err)
|
|
}
|
|
if !exist {
|
|
t.Error("saved attestation hash does not exist")
|
|
}
|
|
|
|
// Negative test case: try with random attestation, exist should be false.
|
|
exist, err = b.hasAttestationHash(blockHash, [32]byte{'A'})
|
|
if err != nil {
|
|
t.Fatalf("unable to check for attestation hash %v", err)
|
|
}
|
|
if exist {
|
|
t.Error("attestation hash shouldn't have existed")
|
|
}
|
|
|
|
// Remove attestation list by deleting the block hash key.
|
|
if err := b.removeAttestationHashList(blockHash); err != nil {
|
|
t.Fatalf("remove attestation hash list failed %v", err)
|
|
}
|
|
|
|
// Negative test case: try with deleted block hash, this should fail.
|
|
_, err = b.hasAttestationHash(blockHash, attestationHash)
|
|
if err == nil {
|
|
t.Error("attestation hash should't have existed in DB")
|
|
}
|
|
}
|
|
|
|
func TestProcessCrosslinks(t *testing.T) {
|
|
beaconChain, db := startInMemoryBeaconChain(t)
|
|
defer db.Close()
|
|
|
|
// Set up crosslink record for every shard.
|
|
var clRecords []*pb.CrosslinkRecord
|
|
for i := 0; i < params.ShardCount; i++ {
|
|
clRecord := &pb.CrosslinkRecord{Dynasty: 1, Blockhash: []byte{'A'}, Slot: 1}
|
|
clRecords = append(clRecords, clRecord)
|
|
}
|
|
|
|
// Set up validators.
|
|
var validators []*pb.ValidatorRecord
|
|
for i := 0; i < params.ShardCount*params.MinCommiteeSize; i++ {
|
|
validator := &pb.ValidatorRecord{Balance: 10000, StartDynasty: 0, EndDynasty: params.DefaultEndDynasty}
|
|
validators = append(validators, validator)
|
|
}
|
|
|
|
// Set up pending attestations.
|
|
var pAttestations []*pb.AttestationRecord
|
|
for i := 0; i < 100; i++ {
|
|
pAttestation := &pb.AttestationRecord{Slot: 0, ShardId: 0, ShardBlockHash: []byte{'a'}, AttesterBitfield: []byte{'z', 'z'}}
|
|
pAttestations = append(pAttestations, pAttestation)
|
|
}
|
|
|
|
// Process crosslinks happened at slot 50 and dynasty 2.
|
|
newCrosslinks, err := beaconChain.processCrosslinks(
|
|
clRecords,
|
|
validators,
|
|
pAttestations,
|
|
5,
|
|
50)
|
|
if err != nil {
|
|
t.Fatalf("process crosslink failed %v", err)
|
|
}
|
|
|
|
if newCrosslinks[0].Dynasty != 5 {
|
|
t.Errorf("Dynasty did not change for new cross link. Wanted: 5. Got: %d", newCrosslinks[0].Dynasty)
|
|
}
|
|
if newCrosslinks[0].Slot != 50 {
|
|
t.Errorf("Slot did not change for new cross link. Wanted: 50. Got: %d", newCrosslinks[0].Slot)
|
|
}
|
|
if !bytes.Equal(newCrosslinks[0].Blockhash, []byte{'a'}) {
|
|
t.Errorf("Blockhash did not change for new cross link. Wanted a. Got: %s", newCrosslinks[0].Blockhash)
|
|
}
|
|
}
|