package state_test import ( "crypto/rand" "encoding/binary" "fmt" "strings" "testing" "time" "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/beacon-chain/core/state" "github.com/prysmaticlabs/prysm/shared/forkutils" pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" "github.com/prysmaticlabs/prysm/shared/bls" "github.com/prysmaticlabs/prysm/shared/params" ) func setupInitialDeposits(t *testing.T, numDeposits uint64) ([]*pb.Deposit, []*bls.SecretKey) { privKeys := make([]*bls.SecretKey, numDeposits) deposits := make([]*pb.Deposit, numDeposits) for i := 0; i < len(deposits); i++ { priv, err := bls.RandKey(rand.Reader) if err != nil { t.Fatal(err) } depositInput := &pb.DepositInput{ Pubkey: priv.PublicKey().Marshal(), } balance := params.BeaconConfig().MaxDepositAmount depositData, err := helpers.EncodeDepositData(depositInput, balance, time.Now().Unix()) if err != nil { t.Fatalf("Cannot encode data: %v", err) } deposits[i] = &pb.Deposit{DepositData: depositData} privKeys[i] = priv } return deposits, privKeys } func createRandaoReveal(t *testing.T, beaconState *pb.BeaconState, privKeys []*bls.SecretKey) []byte { // We fetch the proposer's index as that is whom the RANDAO will be verified against. proposerIdx, err := helpers.BeaconProposerIndex(beaconState, beaconState.Slot) if err != nil { t.Fatal(err) } epoch := helpers.SlotToEpoch(params.BeaconConfig().GenesisSlot) buf := make([]byte, 32) binary.LittleEndian.PutUint64(buf, epoch) domain := forkutils.DomainVersion(beaconState.Fork, epoch, params.BeaconConfig().DomainRandao) // We make the previous validator's index sign the message instead of the proposer. epochSignature := privKeys[proposerIdx].Sign(buf, domain) return epochSignature.Marshal() } func TestProcessBlock_IncorrectSlot(t *testing.T) { beaconState := &pb.BeaconState{ Slot: 5, } block := &pb.BeaconBlock{ Slot: 4, } want := fmt.Sprintf( "block.slot != state.slot, block.slot = %d, state.slot = %d", 4, 5, ) if _, err := state.ProcessBlock(beaconState, block, false); !strings.Contains(err.Error(), want) { t.Errorf("Expected %s, received %v", want, err) } } func TestProcessBlock_IncorrectProposerSlashing(t *testing.T) { deposits, privKeys := setupInitialDeposits(t, params.BeaconConfig().SlotsPerEpoch) beaconState, err := state.GenesisBeaconState(deposits, uint64(0), []byte{}) if err != nil { t.Fatal(err) } slashings := make([]*pb.ProposerSlashing, params.BeaconConfig().MaxProposerSlashings+1) randaoReveal := createRandaoReveal(t, beaconState, privKeys) block := &pb.BeaconBlock{ Slot: params.BeaconConfig().GenesisSlot, RandaoReveal: randaoReveal, Eth1Data: &pb.Eth1Data{ DepositRootHash32: []byte{2}, BlockHash32: []byte{3}, }, Body: &pb.BeaconBlockBody{ ProposerSlashings: slashings, }, } want := "could not verify block proposer slashing" if _, err := state.ProcessBlock(beaconState, block, false); !strings.Contains(err.Error(), want) { t.Errorf("Expected %s, received %v", want, err) } } func TestProcessBlock_IncorrectAttesterSlashing(t *testing.T) { deposits, privKeys := setupInitialDeposits(t, params.BeaconConfig().SlotsPerEpoch) beaconState, err := state.GenesisBeaconState(deposits, uint64(0), []byte{}) if err != nil { t.Fatal(err) } slashings := []*pb.ProposerSlashing{ { ProposerIndex: 1, ProposalData_1: &pb.ProposalSignedData{ Slot: 1, Shard: 1, BlockRootHash32: []byte{0, 1, 0}, }, ProposalData_2: &pb.ProposalSignedData{ Slot: 1, Shard: 1, BlockRootHash32: []byte{0, 1, 0}, }, }, } attesterSlashings := make([]*pb.AttesterSlashing, params.BeaconConfig().MaxAttesterSlashings+1) randaoReveal := createRandaoReveal(t, beaconState, privKeys) block := &pb.BeaconBlock{ Slot: params.BeaconConfig().GenesisSlot, RandaoReveal: randaoReveal, Eth1Data: &pb.Eth1Data{ DepositRootHash32: []byte{2}, BlockHash32: []byte{3}, }, Body: &pb.BeaconBlockBody{ ProposerSlashings: slashings, AttesterSlashings: attesterSlashings, }, } want := "could not verify block attester slashing" if _, err := state.ProcessBlock(beaconState, block, false); !strings.Contains(err.Error(), want) { t.Errorf("Expected %s, received %v", want, err) } } func TestProcessBlock_IncorrectProcessBlockAttestations(t *testing.T) { deposits, privKeys := setupInitialDeposits(t, params.BeaconConfig().SlotsPerEpoch) beaconState, err := state.GenesisBeaconState(deposits, uint64(0), []byte{}) if err != nil { t.Fatal(err) } proposerSlashings := []*pb.ProposerSlashing{ { ProposerIndex: 1, ProposalData_1: &pb.ProposalSignedData{ Slot: 1, Shard: 1, BlockRootHash32: []byte{0, 1, 0}, }, ProposalData_2: &pb.ProposalSignedData{ Slot: 1, Shard: 1, BlockRootHash32: []byte{0, 1, 0}, }, }, } att1 := &pb.AttestationData{ Slot: params.BeaconConfig().GenesisSlot + 5, JustifiedEpoch: params.BeaconConfig().GenesisEpoch + 5, } att2 := &pb.AttestationData{ Slot: params.BeaconConfig().GenesisSlot + 5, JustifiedEpoch: params.BeaconConfig().GenesisEpoch + 4, } attesterSlashings := []*pb.AttesterSlashing{ { SlashableAttestation_1: &pb.SlashableAttestation{ Data: att1, ValidatorIndices: []uint64{1, 2, 3, 4, 5, 6, 7, 8}, CustodyBitfield: []byte{0xFF}, }, SlashableAttestation_2: &pb.SlashableAttestation{ Data: att2, ValidatorIndices: []uint64{1, 2, 3, 4, 5, 6, 7, 8}, CustodyBitfield: []byte{0xFF}, }, }, } blockAttestations := make([]*pb.Attestation, params.BeaconConfig().MaxAttestations+1) randaoReveal := createRandaoReveal(t, beaconState, privKeys) block := &pb.BeaconBlock{ Slot: params.BeaconConfig().GenesisSlot, RandaoReveal: randaoReveal, Eth1Data: &pb.Eth1Data{ DepositRootHash32: []byte{2}, BlockHash32: []byte{3}, }, Body: &pb.BeaconBlockBody{ ProposerSlashings: proposerSlashings, AttesterSlashings: attesterSlashings, Attestations: blockAttestations, }, } want := "could not process block attestations" if _, err := state.ProcessBlock(beaconState, block, false); !strings.Contains(err.Error(), want) { t.Errorf("Expected %s, received %v", want, err) } } func TestProcessBlock_IncorrectProcessExits(t *testing.T) { deposits, privKeys := setupInitialDeposits(t, params.BeaconConfig().SlotsPerEpoch) beaconState, err := state.GenesisBeaconState(deposits, uint64(0), []byte{}) if err != nil { t.Fatal(err) } proposerSlashings := []*pb.ProposerSlashing{ { ProposerIndex: 1, ProposalData_1: &pb.ProposalSignedData{ Slot: params.BeaconConfig().GenesisSlot + 1, Shard: 1, BlockRootHash32: []byte{0, 1, 0}, }, ProposalData_2: &pb.ProposalSignedData{ Slot: params.BeaconConfig().GenesisSlot + 1, Shard: 1, BlockRootHash32: []byte{0, 1, 0}, }, }, } att1 := &pb.AttestationData{ Slot: params.BeaconConfig().GenesisSlot + 5, JustifiedEpoch: params.BeaconConfig().GenesisEpoch + 5, } att2 := &pb.AttestationData{ Slot: params.BeaconConfig().GenesisSlot + 5, JustifiedEpoch: params.BeaconConfig().GenesisEpoch + 4, } attesterSlashings := []*pb.AttesterSlashing{ { SlashableAttestation_1: &pb.SlashableAttestation{ Data: att1, ValidatorIndices: []uint64{1, 2, 3, 4, 5, 6, 7, 8}, CustodyBitfield: []byte{0xFF}, }, SlashableAttestation_2: &pb.SlashableAttestation{ Data: att2, ValidatorIndices: []uint64{1, 2, 3, 4, 5, 6, 7, 8}, CustodyBitfield: []byte{0xFF}, }, }, } var blockRoots [][]byte for i := uint64(0); i < params.BeaconConfig().LatestBlockRootsLength; i++ { blockRoots = append(blockRoots, []byte{byte(i)}) } beaconState.LatestBlockRootHash32S = blockRoots beaconState.LatestCrosslinks = []*pb.Crosslink{ { ShardBlockRootHash32: []byte{1}, }, } beaconState.Slot = params.BeaconConfig().GenesisSlot + 10 blockAtt := &pb.Attestation{ Data: &pb.AttestationData{ Shard: 0, Slot: params.BeaconConfig().GenesisSlot, JustifiedEpoch: params.BeaconConfig().GenesisEpoch, JustifiedBlockRootHash32: blockRoots[0], LatestCrosslink: &pb.Crosslink{ShardBlockRootHash32: []byte{1}}, ShardBlockRootHash32: []byte{}, }, AggregationBitfield: []byte{1}, CustodyBitfield: []byte{1}, } attestations := []*pb.Attestation{blockAtt} exits := make([]*pb.VoluntaryExit, params.BeaconConfig().MaxVoluntaryExits+1) randaoReveal := createRandaoReveal(t, beaconState, privKeys) block := &pb.BeaconBlock{ Slot: params.BeaconConfig().GenesisSlot + 10, RandaoReveal: randaoReveal, Eth1Data: &pb.Eth1Data{ DepositRootHash32: []byte{2}, BlockHash32: []byte{3}, }, Body: &pb.BeaconBlockBody{ ProposerSlashings: proposerSlashings, AttesterSlashings: attesterSlashings, Attestations: attestations, VoluntaryExits: exits, }, } want := "could not process validator exits" if _, err := state.ProcessBlock(beaconState, block, false); !strings.Contains(err.Error(), want) { t.Errorf("Expected %s, received %v", want, err) } } func TestProcessBlock_PassesProcessingConditions(t *testing.T) { deposits, privKeys := setupInitialDeposits(t, params.BeaconConfig().SlotsPerEpoch) beaconState, err := state.GenesisBeaconState(deposits, uint64(0), []byte{}) if err != nil { t.Fatal(err) } proposerSlashings := []*pb.ProposerSlashing{ { ProposerIndex: 1, ProposalData_1: &pb.ProposalSignedData{ Slot: 1, Shard: 1, BlockRootHash32: []byte{0, 1, 0}, }, ProposalData_2: &pb.ProposalSignedData{ Slot: 1, Shard: 1, BlockRootHash32: []byte{0, 1, 0}, }, }, } att1 := &pb.AttestationData{ Slot: 5, JustifiedEpoch: params.BeaconConfig().GenesisEpoch + 5, } att2 := &pb.AttestationData{ Slot: 5, JustifiedEpoch: params.BeaconConfig().GenesisEpoch + 4, } attesterSlashings := []*pb.AttesterSlashing{ { SlashableAttestation_1: &pb.SlashableAttestation{ Data: att1, ValidatorIndices: []uint64{1, 2, 3, 4, 5, 6, 7, 8}, CustodyBitfield: []byte{0xFF}, }, SlashableAttestation_2: &pb.SlashableAttestation{ Data: att2, ValidatorIndices: []uint64{1, 2, 3, 4, 5, 6, 7, 8}, CustodyBitfield: []byte{0xFF}, }, }, } var blockRoots [][]byte for i := uint64(0); i < params.BeaconConfig().LatestBlockRootsLength; i++ { blockRoots = append(blockRoots, []byte{byte(i)}) } beaconState.LatestBlockRootHash32S = blockRoots beaconState.LatestCrosslinks = []*pb.Crosslink{ { ShardBlockRootHash32: []byte{1}, }, } beaconState.Slot = params.BeaconConfig().GenesisSlot + 10 blockAtt := &pb.Attestation{ Data: &pb.AttestationData{ Shard: 0, Slot: params.BeaconConfig().GenesisSlot, JustifiedEpoch: params.BeaconConfig().GenesisEpoch, JustifiedBlockRootHash32: blockRoots[0], LatestCrosslink: &pb.Crosslink{ShardBlockRootHash32: []byte{1}}, ShardBlockRootHash32: []byte{}, }, AggregationBitfield: []byte{1}, CustodyBitfield: []byte{1}, } attestations := []*pb.Attestation{blockAtt} exits := []*pb.VoluntaryExit{ { ValidatorIndex: 10, Epoch: params.BeaconConfig().GenesisEpoch, }, } randaoReveal := createRandaoReveal(t, beaconState, privKeys) block := &pb.BeaconBlock{ Slot: params.BeaconConfig().GenesisSlot + 10, RandaoReveal: randaoReveal, Eth1Data: &pb.Eth1Data{ DepositRootHash32: []byte{2}, BlockHash32: []byte{3}, }, Body: &pb.BeaconBlockBody{ ProposerSlashings: proposerSlashings, AttesterSlashings: attesterSlashings, Attestations: attestations, VoluntaryExits: exits, }, } if _, err := state.ProcessBlock(beaconState, block, false); err != nil { t.Errorf("Expected block to pass processing conditions: %v", err) } } func TestProcessEpoch_PassesProcessingConditions(t *testing.T) { var validatorRegistry []*pb.Validator for i := uint64(0); i < 10; i++ { validatorRegistry = append(validatorRegistry, &pb.Validator{ ExitEpoch: params.BeaconConfig().FarFutureEpoch, }) } validatorBalances := make([]uint64, len(validatorRegistry)) for i := 0; i < len(validatorBalances); i++ { validatorBalances[i] = params.BeaconConfig().MaxDepositAmount } var attestations []*pb.PendingAttestation for i := uint64(0); i < params.BeaconConfig().SlotsPerEpoch*2; i++ { attestations = append(attestations, &pb.PendingAttestation{ Data: &pb.AttestationData{ Slot: i + params.BeaconConfig().SlotsPerEpoch + params.BeaconConfig().GenesisSlot, Shard: 1, JustifiedEpoch: params.BeaconConfig().GenesisEpoch + 1, JustifiedBlockRootHash32: []byte{0}, }, InclusionSlot: i + params.BeaconConfig().SlotsPerEpoch + 1 + params.BeaconConfig().GenesisSlot, }) } var blockRoots [][]byte for i := uint64(0); i < params.BeaconConfig().LatestBlockRootsLength; i++ { blockRoots = append(blockRoots, []byte{byte(i)}) } var randaoHashes [][]byte for i := uint64(0); i < params.BeaconConfig().SlotsPerEpoch; i++ { randaoHashes = append(randaoHashes, []byte{byte(i)}) } crosslinkRecord := []*pb.Crosslink{{}, {}} newState := &pb.BeaconState{ Slot: params.BeaconConfig().SlotsPerEpoch + params.BeaconConfig().GenesisSlot + 1, LatestAttestations: attestations, ValidatorBalances: validatorBalances, ValidatorRegistry: validatorRegistry, LatestBlockRootHash32S: blockRoots, LatestCrosslinks: crosslinkRecord, LatestRandaoMixes: randaoHashes, LatestIndexRootHash32S: make([][]byte, params.BeaconConfig().LatestActiveIndexRootsLength), LatestSlashedBalances: make([]uint64, params.BeaconConfig().LatestSlashedExitLength), } _, err := state.ProcessEpoch(newState) if err != nil { t.Errorf("Expected epoch transition to pass processing conditions: %v", err) } } func TestProcessEpoch_InactiveConditions(t *testing.T) { defaultBalance := params.BeaconConfig().MaxDepositAmount validatorRegistry := []*pb.Validator{ {ExitEpoch: params.BeaconConfig().FarFutureEpoch}, {ExitEpoch: params.BeaconConfig().FarFutureEpoch}, {ExitEpoch: params.BeaconConfig().FarFutureEpoch}, {ExitEpoch: params.BeaconConfig().FarFutureEpoch}, {ExitEpoch: params.BeaconConfig().FarFutureEpoch}, {ExitEpoch: params.BeaconConfig().FarFutureEpoch}, {ExitEpoch: params.BeaconConfig().FarFutureEpoch}, {ExitEpoch: params.BeaconConfig().FarFutureEpoch}} validatorBalances := []uint64{ defaultBalance, defaultBalance, defaultBalance, defaultBalance, defaultBalance, defaultBalance, defaultBalance, defaultBalance, } var attestations []*pb.PendingAttestation for i := uint64(0); i < params.BeaconConfig().SlotsPerEpoch*2; i++ { attestations = append(attestations, &pb.PendingAttestation{ Data: &pb.AttestationData{ Slot: i + params.BeaconConfig().SlotsPerEpoch + params.BeaconConfig().GenesisSlot, Shard: 1, JustifiedEpoch: params.BeaconConfig().GenesisEpoch + 1, JustifiedBlockRootHash32: []byte{0}, }, AggregationBitfield: []byte{}, InclusionSlot: i + params.BeaconConfig().SlotsPerEpoch + 1 + params.BeaconConfig().GenesisSlot, }) } var blockRoots [][]byte for i := uint64(0); i < 2*params.BeaconConfig().SlotsPerEpoch; i++ { blockRoots = append(blockRoots, []byte{byte(i)}) } var randaoHashes [][]byte for i := uint64(0); i < 5*params.BeaconConfig().SlotsPerEpoch; i++ { randaoHashes = append(randaoHashes, []byte{byte(i)}) } crosslinkRecord := []*pb.Crosslink{{}, {}} newState := &pb.BeaconState{ Slot: params.BeaconConfig().SlotsPerEpoch + params.BeaconConfig().GenesisSlot + 1, LatestAttestations: attestations, ValidatorBalances: validatorBalances, ValidatorRegistry: validatorRegistry, LatestBlockRootHash32S: blockRoots, LatestCrosslinks: crosslinkRecord, LatestRandaoMixes: randaoHashes, LatestIndexRootHash32S: make([][]byte, params.BeaconConfig().LatestActiveIndexRootsLength), LatestSlashedBalances: make([]uint64, params.BeaconConfig().LatestSlashedExitLength), } _, err := state.ProcessEpoch(newState) if err != nil { t.Errorf("Expected epoch transition to pass processing conditions: %v", err) } } func TestProcessEpoch_CantGetBoundaryAttestation(t *testing.T) { newState := &pb.BeaconState{ Slot: params.BeaconConfig().GenesisSlot + params.BeaconConfig().SlotsPerEpoch, LatestAttestations: []*pb.PendingAttestation{ {Data: &pb.AttestationData{Slot: params.BeaconConfig().GenesisSlot + 100}}, }} want := fmt.Sprintf( "could not get current boundary attestations: slot %d is not within expected range of %d to %d", newState.Slot, newState.Slot-params.BeaconConfig().LatestBlockRootsLength, newState.Slot, ) if _, err := state.ProcessEpoch(newState); !strings.Contains(err.Error(), want) { t.Errorf("Expected: %s, received: %v", want, err) } } func TestProcessEpoch_CantGetCurrentValidatorIndices(t *testing.T) { latestBlockRoots := make([][]byte, params.BeaconConfig().LatestBlockRootsLength) for i := 0; i < len(latestBlockRoots); i++ { latestBlockRoots[i] = params.BeaconConfig().ZeroHash[:] } var attestations []*pb.PendingAttestation for i := uint64(0); i < params.BeaconConfig().SlotsPerEpoch*2; i++ { attestations = append(attestations, &pb.PendingAttestation{ Data: &pb.AttestationData{ Slot: params.BeaconConfig().GenesisSlot + 1, Shard: 1, JustifiedBlockRootHash32: make([]byte, 32), }, AggregationBitfield: []byte{0xff}, }) } newState := &pb.BeaconState{ Slot: params.BeaconConfig().GenesisSlot + params.BeaconConfig().SlotsPerEpoch, LatestAttestations: attestations, LatestBlockRootHash32S: latestBlockRoots, } wanted := fmt.Sprintf("wanted participants bitfield length %d, got: %d", 0, 1) if _, err := state.ProcessEpoch(newState); !strings.Contains(err.Error(), wanted) { t.Errorf("Expected: %s, received: %v", wanted, err) } }