diff --git a/beacon-chain/core/blocks/BUILD.bazel b/beacon-chain/core/blocks/BUILD.bazel index 0c7dbe226..94a132818 100644 --- a/beacon-chain/core/blocks/BUILD.bazel +++ b/beacon-chain/core/blocks/BUILD.bazel @@ -7,7 +7,10 @@ go_library( "block_operations.go", ], importpath = "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks", - visibility = ["//beacon-chain:__subpackages__"], + visibility = [ + "//beacon-chain:__subpackages__", + "//shared/testutil:__pkg__", + ], deps = [ "//beacon-chain/cache:go_default_library", "//beacon-chain/core/helpers:go_default_library", diff --git a/beacon-chain/core/helpers/block.go b/beacon-chain/core/helpers/block.go index a1ca65ac5..f9b5f6884 100644 --- a/beacon-chain/core/helpers/block.go +++ b/beacon-chain/core/helpers/block.go @@ -18,7 +18,7 @@ import ( // return state.block_roots[slot % SLOTS_PER_HISTORICAL_ROOT] func BlockRootAtSlot(state *pb.BeaconState, slot uint64) ([]byte, error) { if slot >= state.Slot || state.Slot > slot+params.BeaconConfig().SlotsPerHistoricalRoot { - return []byte{}, errors.New("slot out of bounds") + return []byte{}, errors.Errorf("slot %d out of bounds", slot) } return state.BlockRoots[slot%params.BeaconConfig().SlotsPerHistoricalRoot], nil } diff --git a/beacon-chain/core/helpers/block_test.go b/beacon-chain/core/helpers/block_test.go index 6cc53e5a4..be60bf6dd 100644 --- a/beacon-chain/core/helpers/block_test.go +++ b/beacon-chain/core/helpers/block_test.go @@ -92,19 +92,19 @@ func TestBlockRootAtSlot_OutOfBounds(t *testing.T) { { slot: 1000, stateSlot: 500, - expectedErr: "slot out of bounds", + expectedErr: "slot 1000 out of bounds", }, { slot: 3000, stateSlot: 3000, - expectedErr: "slot out of bounds", + expectedErr: "slot 3000 out of bounds", }, { // Edge case where stateSlot is over slots per historical root and // slot is not within (stateSlot - HistoricalRootsLimit, statSlot] slot: 1, stateSlot: params.BeaconConfig().SlotsPerHistoricalRoot + 2, - expectedErr: "slot out of bounds", + expectedErr: "slot 1 out of bounds", }, } for _, tt := range tests { diff --git a/beacon-chain/core/state/BUILD.bazel b/beacon-chain/core/state/BUILD.bazel index b159d3a23..f3075b687 100644 --- a/beacon-chain/core/state/BUILD.bazel +++ b/beacon-chain/core/state/BUILD.bazel @@ -12,6 +12,7 @@ go_library( visibility = [ "//beacon-chain:__subpackages__", "//shared/interop:__pkg__", + "//shared/testutil:__pkg__", "//tools/genesis-state-gen:__pkg__", ], deps = [ diff --git a/beacon-chain/core/state/stateutils/BUILD.bazel b/beacon-chain/core/state/stateutils/BUILD.bazel index 80d89429c..06fbcc6b9 100644 --- a/beacon-chain/core/state/stateutils/BUILD.bazel +++ b/beacon-chain/core/state/stateutils/BUILD.bazel @@ -4,7 +4,10 @@ go_library( name = "go_default_library", srcs = ["validator_index_map.go"], importpath = "github.com/prysmaticlabs/prysm/beacon-chain/core/state/stateutils", - visibility = ["//beacon-chain:__subpackages__"], + visibility = [ + "//beacon-chain:__subpackages__", + "//shared/testutil:__pkg__", + ], deps = [ "//proto/beacon/p2p/v1:go_default_library", "//shared/bytesutil:go_default_library", diff --git a/shared/testutil/BUILD.bazel b/shared/testutil/BUILD.bazel index 9a65be315..fe1ad62c5 100644 --- a/shared/testutil/BUILD.bazel +++ b/shared/testutil/BUILD.bazel @@ -4,6 +4,7 @@ go_library( name = "go_default_library", testonly = True, srcs = [ + "block.go", "checkbit.go", "helpers.go", "is_empty.go", @@ -17,7 +18,9 @@ go_library( importpath = "github.com/prysmaticlabs/prysm/shared/testutil", visibility = ["//visibility:public"], deps = [ + "//beacon-chain/core/blocks:go_default_library", "//beacon-chain/core/helpers:go_default_library", + "//beacon-chain/core/state:go_default_library", "//proto/beacon/p2p/v1:go_default_library", "//proto/eth/v1alpha1:go_default_library", "//shared/bls:go_default_library", @@ -27,8 +30,10 @@ go_library( "//shared/trieutil:go_default_library", "@com_github_ghodss_yaml//:go_default_library", "@com_github_gogo_protobuf//proto:go_default_library", + "@com_github_golang_protobuf//proto:go_default_library", "@com_github_json_iterator_go//:go_default_library", "@com_github_pkg_errors//:go_default_library", + "@com_github_prysmaticlabs_go_bitfield//:go_default_library", "@com_github_prysmaticlabs_go_ssz//:go_default_library", "@com_github_sirupsen_logrus//hooks/test:go_default_library", "@in_gopkg_d4l3k_messagediff_v1//:go_default_library", @@ -39,15 +44,19 @@ go_library( go_test( name = "go_default_test", srcs = [ + "block_test.go", "helpers_test.go", "json_to_pb_converter_test.go", ], embed = [":go_default_library"], deps = [ "//beacon-chain/core/helpers:go_default_library", + "//beacon-chain/core/state:go_default_library", + "//beacon-chain/core/state/stateutils:go_default_library", "//proto/beacon/p2p/v1:go_default_library", "//proto/eth/v1alpha1:go_default_library", "//proto/testing:go_default_library", + "//shared/bytesutil:go_default_library", "//shared/params:go_default_library", "@com_github_prysmaticlabs_go_ssz//:go_default_library", ], diff --git a/shared/testutil/block.go b/shared/testutil/block.go new file mode 100644 index 000000000..cf554d299 --- /dev/null +++ b/shared/testutil/block.go @@ -0,0 +1,428 @@ +package testutil + +import ( + "context" + "testing" + + "github.com/golang/protobuf/proto" + "github.com/prysmaticlabs/go-bitfield" + "github.com/prysmaticlabs/go-ssz" + "github.com/prysmaticlabs/prysm/beacon-chain/core/blocks" + "github.com/prysmaticlabs/prysm/beacon-chain/core/helpers" + "github.com/prysmaticlabs/prysm/beacon-chain/core/state" + pb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1" + ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" + "github.com/prysmaticlabs/prysm/shared/bls" + "github.com/prysmaticlabs/prysm/shared/params" +) + +// BlockGenConfig is used to define the requested conditions +// for block generation. +type BlockGenConfig struct { + MaxProposerSlashings uint64 + MaxAttesterSlashings uint64 + MaxAttestations uint64 + MaxDeposits uint64 + MaxVoluntaryExits uint64 +} + +// GenerateFullBlock generates a fully valid block with the requested parameters. +// Use BlockGenConfig to declare the conditions you would like the block generated under. +func GenerateFullBlock( + t testing.TB, + bState *pb.BeaconState, + privs []*bls.SecretKey, + conf *BlockGenConfig, +) *ethpb.BeaconBlock { + + currentSlot := bState.Slot + + pSlashings := []*ethpb.ProposerSlashing{} + if conf.MaxProposerSlashings > 0 { + pSlashings = generateProposerSlashings(t, bState, privs, conf.MaxProposerSlashings) + } + + aSlashings := []*ethpb.AttesterSlashing{} + if conf.MaxAttesterSlashings > 0 { + aSlashings = generateAttesterSlashings(t, bState, privs, conf.MaxAttesterSlashings) + } + + atts := []*ethpb.Attestation{} + if conf.MaxAttestations > 0 { + atts = generateAttestations(t, bState, privs, conf.MaxAttestations) + } + + newDeposits, eth1Data := []*ethpb.Deposit{}, bState.Eth1Data + if conf.MaxDeposits > 0 { + newDeposits, eth1Data = generateDepositsAndEth1Data(t, bState, conf.MaxDeposits) + } + + exits := []*ethpb.VoluntaryExit{} + if conf.MaxVoluntaryExits > 0 { + exits = generateVoluntaryExits(t, bState, privs, conf.MaxVoluntaryExits) + } + + newHeader := proto.Clone(bState.LatestBlockHeader).(*ethpb.BeaconBlockHeader) + prevStateRoot, err := ssz.HashTreeRoot(bState) + if err != nil { + t.Fatal(err) + } + newHeader.StateRoot = prevStateRoot[:] + parentRoot, err := ssz.SigningRoot(newHeader) + if err != nil { + t.Fatal(err) + } + + // Temporarily incrementing the beacon state slot here since BeaconProposerIndex is a + // function deterministic on beacon state slot. + bState.Slot++ + reveal, err := CreateRandaoReveal(bState, helpers.CurrentEpoch(bState), privs) + if err != nil { + t.Fatal(err) + } + bState.Slot-- + + block := ðpb.BeaconBlock{ + Slot: currentSlot + 1, + ParentRoot: parentRoot[:], + Body: ðpb.BeaconBlockBody{ + Eth1Data: eth1Data, + RandaoReveal: reveal, + ProposerSlashings: pSlashings, + AttesterSlashings: aSlashings, + Attestations: atts, + VoluntaryExits: exits, + Deposits: newDeposits, + }, + } + + s, err := state.CalculateStateRoot(context.Background(), bState, block) + if err != nil { + t.Fatal(err) + } + root, err := ssz.HashTreeRoot(s) + if err != nil { + t.Fatal(err) + } + block.StateRoot = root[:] + blockRoot, err := ssz.SigningRoot(block) + if err != nil { + t.Fatal(err) + } + // Temporarily incrementing the beacon state slot here since BeaconProposerIndex is a + // function deterministic on beacon state slot. + bState.Slot++ + proposerIdx, err := helpers.BeaconProposerIndex(bState) + if err != nil { + t.Fatal(err) + } + bState.Slot-- + domain := helpers.Domain(bState.Fork, helpers.CurrentEpoch(bState), params.BeaconConfig().DomainBeaconProposer) + block.Signature = privs[proposerIdx].Sign(blockRoot[:], domain).Marshal() + + return block +} + +func generateProposerSlashings( + t testing.TB, + bState *pb.BeaconState, + privs []*bls.SecretKey, + maxSlashings uint64, +) []*ethpb.ProposerSlashing { + currentSlot := bState.Slot + currentEpoch := helpers.CurrentEpoch(bState) + slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch + + validatorCount, err := helpers.ActiveValidatorCount(bState, currentEpoch) + if err != nil { + t.Fatal(err) + } + proposerSlashings := make([]*ethpb.ProposerSlashing, maxSlashings) + for i := uint64(0); i < maxSlashings; i++ { + proposerIndex := i + uint64(validatorCount/4) + header1 := ðpb.BeaconBlockHeader{ + Slot: currentSlot - (i % slotsPerEpoch), + BodyRoot: []byte{0, 1, 0}, + } + root, err := ssz.SigningRoot(header1) + if err != nil { + t.Fatal(err) + } + domain := helpers.Domain(bState.Fork, currentEpoch, params.BeaconConfig().DomainBeaconProposer) + header1.Signature = privs[proposerIndex].Sign(root[:], domain).Marshal() + + header2 := ðpb.BeaconBlockHeader{ + Slot: currentSlot - (i % slotsPerEpoch), + BodyRoot: []byte{0, 2, 0}, + } + root, err = ssz.SigningRoot(header2) + if err != nil { + t.Fatal(err) + } + header2.Signature = privs[proposerIndex].Sign(root[:], domain).Marshal() + + slashing := ðpb.ProposerSlashing{ + ProposerIndex: proposerIndex, + Header_1: header1, + Header_2: header2, + } + proposerSlashings[i] = slashing + } + return proposerSlashings +} + +func generateAttesterSlashings( + t testing.TB, + bState *pb.BeaconState, + privs []*bls.SecretKey, + maxSlashings uint64, +) []*ethpb.AttesterSlashing { + attesterSlashings := make([]*ethpb.AttesterSlashing, maxSlashings) + for i := uint64(0); i < maxSlashings; i++ { + crosslink := ðpb.Crosslink{ + Shard: i % params.BeaconConfig().ShardCount, + StartEpoch: i, + EndEpoch: i + 1, + } + committee, err := helpers.CrosslinkCommittee(bState, i, crosslink.Shard) + if err != nil { + t.Fatal(err) + } + committeeSize := uint64(len(committee)) + attData1 := ðpb.AttestationData{ + Crosslink: crosslink, + Target: ðpb.Checkpoint{ + Epoch: i, + Root: params.BeaconConfig().ZeroHash[:], + }, + Source: ðpb.Checkpoint{ + Epoch: i + 1, + Root: params.BeaconConfig().ZeroHash[:], + }, + } + aggregationBits := bitfield.NewBitlist(committeeSize) + aggregationBits.SetBitAt(i, true) + custodyBits := bitfield.NewBitlist(committeeSize) + att1 := ðpb.Attestation{ + Data: attData1, + CustodyBits: custodyBits, + AggregationBits: aggregationBits, + } + dataRoot, err := ssz.HashTreeRoot(&pb.AttestationDataAndCustodyBit{ + Data: att1.Data, + CustodyBit: false, + }) + if err != nil { + t.Fatal(err) + } + domain := helpers.Domain(bState.Fork, i, params.BeaconConfig().DomainAttestation) + sig := privs[committee[i]].Sign(dataRoot[:], domain) + att1.Signature = bls.AggregateSignatures([]*bls.Signature{sig}).Marshal() + + attData2 := ðpb.AttestationData{ + Crosslink: crosslink, + Target: ðpb.Checkpoint{ + Epoch: i, + Root: params.BeaconConfig().ZeroHash[:], + }, + Source: ðpb.Checkpoint{ + Epoch: i, + Root: params.BeaconConfig().ZeroHash[:], + }, + } + att2 := ðpb.Attestation{ + Data: attData2, + CustodyBits: custodyBits, + AggregationBits: aggregationBits, + } + dataRoot, err = ssz.HashTreeRoot(&pb.AttestationDataAndCustodyBit{ + Data: att2.Data, + CustodyBit: false, + }) + if err != nil { + t.Fatal(err) + } + sig = privs[committee[i]].Sign(dataRoot[:], domain) + att2.Signature = bls.AggregateSignatures([]*bls.Signature{sig}).Marshal() + + indexedAtt1, err := blocks.ConvertToIndexed(bState, att1) + if err != nil { + t.Fatal(err) + } + indexedAtt2, err := blocks.ConvertToIndexed(bState, att2) + if err != nil { + t.Fatal(err) + } + slashing := ðpb.AttesterSlashing{ + Attestation_1: indexedAtt1, + Attestation_2: indexedAtt2, + } + attesterSlashings[i] = slashing + } + return attesterSlashings +} + +// generateAttestations creates attestations that are entirely valid, for the current state slot. +// This function always returns all validators participating, if maxAttestations is 1, then it will +// return 1 attestation with all validators aggregated into it. If maxAttestations is set to 4, then +// it will return 4 attestations for the same data with their aggregation bits split uniformly. +func generateAttestations( + t testing.TB, + bState *pb.BeaconState, + privs []*bls.SecretKey, + maxAttestations uint64, +) []*ethpb.Attestation { + headState := proto.Clone(bState).(*pb.BeaconState) + headState, err := state.ProcessSlots(context.Background(), headState, bState.Slot+1) + if err != nil { + t.Fatal(err) + } + + currentEpoch := helpers.CurrentEpoch(bState) + attestations := make([]*ethpb.Attestation, maxAttestations) + + committeeCount, err := helpers.CommitteeCount(bState, currentEpoch) + if err != nil { + t.Fatal(err) + } + committeesPerSlot := committeeCount / params.BeaconConfig().SlotsPerEpoch + offSet := committeesPerSlot * (bState.Slot % params.BeaconConfig().SlotsPerEpoch) + startShard, err := helpers.StartShard(bState, currentEpoch) + if err != nil { + t.Fatal(err) + } + shard := (startShard + offSet) % params.BeaconConfig().ShardCount + + parentCrosslink := bState.CurrentCrosslinks[shard] + endEpoch := parentCrosslink.EndEpoch + params.BeaconConfig().MaxEpochsPerCrosslink + if currentEpoch < endEpoch { + endEpoch = currentEpoch + } + parentRoot, err := ssz.HashTreeRoot(parentCrosslink) + if err != nil { + t.Fatal(err) + } + crosslink := ðpb.Crosslink{ + Shard: shard, + StartEpoch: parentCrosslink.EndEpoch, + EndEpoch: endEpoch, + ParentRoot: parentRoot[:], + DataRoot: params.BeaconConfig().ZeroHash[:], + } + committee, err := helpers.CrosslinkCommittee(bState, currentEpoch, shard) + if err != nil { + t.Fatal(err) + } + committeeSize := uint64(len(committee)) + crosslinkParentRoot, err := ssz.HashTreeRoot(parentCrosslink) + if err != nil { + panic(err) + } + crosslink.ParentRoot = crosslinkParentRoot[:] + + headRoot, err := helpers.BlockRootAtSlot(headState, bState.Slot) + if err != nil { + t.Fatal(err) + } + + targetRoot := make([]byte, 32) + epochStartSlot := helpers.StartSlot(currentEpoch) + if epochStartSlot == headState.Slot { + targetRoot = headRoot[:] + } else { + targetRoot, err = helpers.BlockRootAtSlot(headState, epochStartSlot) + if err != nil { + t.Fatal(err) + } + } + + custodyBits := bitfield.NewBitlist(committeeSize) + att := ðpb.Attestation{ + Data: ðpb.AttestationData{ + BeaconBlockRoot: headRoot, + Crosslink: crosslink, + Source: bState.CurrentJustifiedCheckpoint, + Target: ðpb.Checkpoint{ + Epoch: currentEpoch, + Root: targetRoot, + }, + }, + CustodyBits: custodyBits, + } + + dataRoot, err := ssz.HashTreeRoot(&pb.AttestationDataAndCustodyBit{ + Data: att.Data, + CustodyBit: false, + }) + if err != nil { + t.Fatal(err) + } + + if maxAttestations > committeeSize { + t.Fatalf( + "requested %d attestations per block but there are only %d committee members", + maxAttestations, + len(committee), + ) + } + + bitsPerAtt := committeeSize / maxAttestations + domain := helpers.Domain(bState.Fork, parentCrosslink.EndEpoch+1, params.BeaconConfig().DomainAttestation) + for i := uint64(0); i < committeeSize; i += bitsPerAtt { + aggregationBits := bitfield.NewBitlist(committeeSize) + sigs := []*bls.Signature{} + for b := i; b < i+bitsPerAtt; b++ { + aggregationBits.SetBitAt(b, true) + sigs = append(sigs, privs[committee[b]].Sign(dataRoot[:], domain)) + } + att.AggregationBits = aggregationBits + + att.Signature = bls.AggregateSignatures(sigs).Marshal() + attestations[i/bitsPerAtt] = att + } + return attestations +} + +func generateDepositsAndEth1Data( + t testing.TB, + bState *pb.BeaconState, + maxDeposits uint64, +) ( + []*ethpb.Deposit, + *ethpb.Eth1Data, +) { + previousDepsLen := bState.Eth1DepositIndex + currentDeposits, _, _ := SetupInitialDeposits(t, previousDepsLen+maxDeposits) + eth1Data := GenerateEth1Data(t, currentDeposits) + return currentDeposits[previousDepsLen:], eth1Data +} + +func generateVoluntaryExits( + t testing.TB, + bState *pb.BeaconState, + privs []*bls.SecretKey, + maxExits uint64, +) []*ethpb.VoluntaryExit { + currentEpoch := helpers.CurrentEpoch(bState) + validatorCount, err := helpers.ActiveValidatorCount(bState, currentEpoch) + if err != nil { + t.Fatal(err) + } + + voluntaryExits := make([]*ethpb.VoluntaryExit, maxExits) + for i := 0; i < len(voluntaryExits); i++ { + valIndex := float64(validatorCount)*(2.0/3.0) + float64(i) + exit := ðpb.VoluntaryExit{ + Epoch: helpers.PrevEpoch(bState), + ValidatorIndex: uint64(valIndex), + } + root, err := ssz.SigningRoot(exit) + if err != nil { + t.Fatal(err) + } + domain := helpers.Domain(bState.Fork, currentEpoch, params.BeaconConfig().DomainVoluntaryExit) + exit.Signature = privs[uint64(valIndex)].Sign(root[:], domain).Marshal() + voluntaryExits[i] = exit + } + return voluntaryExits +} diff --git a/shared/testutil/block_test.go b/shared/testutil/block_test.go new file mode 100644 index 000000000..85dd47074 --- /dev/null +++ b/shared/testutil/block_test.go @@ -0,0 +1,234 @@ +package testutil + +import ( + "context" + "testing" + + "github.com/prysmaticlabs/prysm/beacon-chain/core/state" + "github.com/prysmaticlabs/prysm/beacon-chain/core/state/stateutils" + "github.com/prysmaticlabs/prysm/shared/bytesutil" + "github.com/prysmaticlabs/prysm/shared/params" +) + +func TestGenerateFullBlock_PassesStateTransition(t *testing.T) { + deposits, _, privs := SetupInitialDeposits(t, 128) + eth1Data := GenerateEth1Data(t, deposits) + beaconState, err := state.GenesisBeaconState(deposits, 0, eth1Data) + if err != nil { + t.Fatal(err) + } + conf := &BlockGenConfig{ + MaxProposerSlashings: 0, + MaxAttesterSlashings: 0, + MaxAttestations: 0, + MaxDeposits: 0, + MaxVoluntaryExits: 0, + } + block := GenerateFullBlock(t, beaconState, privs, conf) + beaconState, err = state.ExecuteStateTransition(context.Background(), beaconState, block) + if err != nil { + t.Fatal(err) + } +} + +func TestGenerateFullBlock_ThousandValidators(t *testing.T) { + params.OverrideBeaconConfig(params.MinimalSpecConfig()) + defer params.OverrideBeaconConfig(params.MainnetConfig()) + deposits, _, privs := SetupInitialDeposits(t, 1024) + eth1Data := GenerateEth1Data(t, deposits) + beaconState, err := state.GenesisBeaconState(deposits, 0, eth1Data) + if err != nil { + t.Fatal(err) + } + conf := &BlockGenConfig{ + MaxProposerSlashings: 0, + MaxAttesterSlashings: 0, + MaxAttestations: 16, + MaxDeposits: 0, + MaxVoluntaryExits: 0, + } + block := GenerateFullBlock(t, beaconState, privs, conf) + beaconState, err = state.ExecuteStateTransition(context.Background(), beaconState, block) + if err != nil { + t.Fatal(err) + } +} + +func TestGenerateFullBlock_Passes4Epochs(t *testing.T) { + // Changing to minimal config as this will process 4 epochs of blocks. + params.OverrideBeaconConfig(params.MinimalSpecConfig()) + defer params.OverrideBeaconConfig(params.MainnetConfig()) + deposits, _, privs := SetupInitialDeposits(t, 128) + eth1Data := GenerateEth1Data(t, deposits) + beaconState, err := state.GenesisBeaconState(deposits, 0, eth1Data) + if err != nil { + t.Fatal(err) + } + + conf := &BlockGenConfig{ + MaxProposerSlashings: 0, + MaxAttesterSlashings: 0, + MaxAttestations: 1, + MaxDeposits: 0, + MaxVoluntaryExits: 0, + } + finalSlot := params.BeaconConfig().SlotsPerEpoch*4 + 3 + for i := 0; i < int(finalSlot); i++ { + block := GenerateFullBlock(t, beaconState, privs, conf) + beaconState, err = state.ExecuteStateTransitionNoVerify(context.Background(), beaconState, block) + if err != nil { + t.Fatal(err) + } + } + + // Blocks are one slot ahead of beacon state. + if finalSlot != beaconState.Slot { + t.Fatalf("expected output slot to be %d, received %d", finalSlot, beaconState.Slot) + } + if beaconState.CurrentJustifiedCheckpoint.Epoch != 3 { + t.Fatalf("expected justified epoch to change to 3, received %d", beaconState.CurrentJustifiedCheckpoint.Epoch) + } + if beaconState.FinalizedCheckpoint.Epoch != 2 { + t.Fatalf("expected finalized epoch to change to 2, received %d", beaconState.CurrentJustifiedCheckpoint.Epoch) + } +} + +func TestGenerateFullBlock_ValidProposerSlashings(t *testing.T) { + deposits, _, privs := SetupInitialDeposits(t, 128) + eth1Data := GenerateEth1Data(t, deposits) + beaconState, err := state.GenesisBeaconState(deposits, 0, eth1Data) + if err != nil { + t.Fatal(err) + } + conf := &BlockGenConfig{ + MaxProposerSlashings: 1, + MaxAttesterSlashings: 0, + MaxAttestations: 0, + MaxDeposits: 0, + MaxVoluntaryExits: 0, + } + block := GenerateFullBlock(t, beaconState, privs, conf) + beaconState, err = state.ExecuteStateTransition(context.Background(), beaconState, block) + if err != nil { + t.Fatal(err) + } + + slashableIndice := block.Body.ProposerSlashings[0].ProposerIndex + if !beaconState.Validators[slashableIndice].Slashed { + t.Fatal("expected validator to be slashed") + } +} + +func TestGenerateFullBlock_ValidAttesterSlashings(t *testing.T) { + deposits, _, privs := SetupInitialDeposits(t, 128) + eth1Data := GenerateEth1Data(t, deposits) + beaconState, err := state.GenesisBeaconState(deposits, 0, eth1Data) + if err != nil { + t.Fatal(err) + } + conf := &BlockGenConfig{ + MaxProposerSlashings: 0, + MaxAttesterSlashings: 1, + MaxAttestations: 0, + MaxDeposits: 0, + MaxVoluntaryExits: 0, + } + block := GenerateFullBlock(t, beaconState, privs, conf) + beaconState, err = state.ExecuteStateTransition(context.Background(), beaconState, block) + if err != nil { + t.Fatal(err) + } + + slashableIndices := block.Body.AttesterSlashings[0].Attestation_1.CustodyBit_0Indices + if !beaconState.Validators[slashableIndices[0]].Slashed { + t.Fatal("expected validator to be slashed") + } +} + +func TestGenerateFullBlock_ValidAttestations(t *testing.T) { + deposits, _, privs := SetupInitialDeposits(t, 128) + eth1Data := GenerateEth1Data(t, deposits) + beaconState, err := state.GenesisBeaconState(deposits, 0, eth1Data) + if err != nil { + t.Fatal(err) + } + // Moving the slot forward one due to ATTESTATION_INCLUSION_DELAY. + beaconState.Slot++ + conf := &BlockGenConfig{ + MaxProposerSlashings: 0, + MaxAttesterSlashings: 0, + MaxAttestations: 2, + MaxDeposits: 0, + MaxVoluntaryExits: 0, + } + block := GenerateFullBlock(t, beaconState, privs, conf) + beaconState, err = state.ExecuteStateTransition(context.Background(), beaconState, block) + if err != nil { + t.Fatal(err) + } + if len(beaconState.CurrentEpochAttestations) != 2 { + t.Fatal("expected 2 attestations to be saved to the beacon state") + } +} + +func TestGenerateFullBlock_ValidDeposits(t *testing.T) { + deposits, _, privs := SetupInitialDeposits(t, 128) + eth1Data := GenerateEth1Data(t, deposits) + beaconState, err := state.GenesisBeaconState(deposits, 0, eth1Data) + if err != nil { + t.Fatal(err) + } + deposits, _, privs = SetupInitialDeposits(t, 129) + eth1Data = GenerateEth1Data(t, deposits) + beaconState.Eth1Data = eth1Data + conf := &BlockGenConfig{ + MaxProposerSlashings: 0, + MaxAttesterSlashings: 0, + MaxAttestations: 0, + MaxDeposits: 1, + MaxVoluntaryExits: 0, + } + block := GenerateFullBlock(t, beaconState, privs, conf) + beaconState, err = state.ExecuteStateTransition(context.Background(), beaconState, block) + if err != nil { + t.Fatal(err) + } + + depositedPubkey := block.Body.Deposits[0].Data.PublicKey + valIndexMap := stateutils.ValidatorIndexMap(beaconState) + index := valIndexMap[bytesutil.ToBytes48(depositedPubkey)] + if beaconState.Validators[index].EffectiveBalance != params.BeaconConfig().MaxEffectiveBalance { + t.Fatalf( + "expected validator balance to be max effective balance, received %d", + beaconState.Validators[index].EffectiveBalance, + ) + } +} + +func TestGenerateFullBlock_ValidVoluntaryExits(t *testing.T) { + deposits, _, privs := SetupInitialDeposits(t, 128) + eth1Data := GenerateEth1Data(t, deposits) + beaconState, err := state.GenesisBeaconState(deposits, 0, eth1Data) + if err != nil { + t.Fatal(err) + } + // Moving the state 2048 epochs forward due to PERSISTENT_COMMITTEE_PERIOD. + beaconState.Slot = 3 + params.BeaconConfig().PersistentCommitteePeriod*params.BeaconConfig().SlotsPerEpoch + conf := &BlockGenConfig{ + MaxProposerSlashings: 0, + MaxAttesterSlashings: 0, + MaxAttestations: 0, + MaxDeposits: 0, + MaxVoluntaryExits: 1, + } + block := GenerateFullBlock(t, beaconState, privs, conf) + beaconState, err = state.ExecuteStateTransition(context.Background(), beaconState, block) + if err != nil { + t.Fatal(err) + } + + exitedIndex := block.Body.VoluntaryExits[0].ValidatorIndex + if beaconState.Validators[exitedIndex].ExitEpoch == params.BeaconConfig().FarFutureEpoch { + t.Fatal("expected exiting validator index to be marked as exiting") + } +}