2020-03-15 05:09:23 +00:00
|
|
|
package evaluators
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-04-27 15:59:42 +00:00
|
|
|
"fmt"
|
2020-03-15 05:09:23 +00:00
|
|
|
|
2020-05-21 06:02:40 +00:00
|
|
|
"github.com/pkg/errors"
|
2021-02-28 12:49:00 +00:00
|
|
|
types "github.com/prysmaticlabs/eth2-types"
|
2020-03-15 05:09:23 +00:00
|
|
|
"github.com/prysmaticlabs/go-bitfield"
|
2021-09-27 16:19:20 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/signing"
|
2021-12-15 20:14:30 +00:00
|
|
|
fieldparams "github.com/prysmaticlabs/prysm/config/fieldparams"
|
2021-09-21 19:59:25 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/config/params"
|
2021-09-16 17:05:58 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/container/slice"
|
2021-09-23 15:23:37 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
2021-07-21 21:34:07 +00:00
|
|
|
eth "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
2021-09-30 20:28:14 +00:00
|
|
|
e2e "github.com/prysmaticlabs/prysm/testing/endtoend/params"
|
2021-09-15 14:42:05 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/testing/endtoend/policies"
|
|
|
|
e2eTypes "github.com/prysmaticlabs/prysm/testing/endtoend/types"
|
2021-09-23 18:53:46 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/testing/util"
|
2020-03-15 05:09:23 +00:00
|
|
|
"google.golang.org/grpc"
|
2021-05-17 18:32:04 +00:00
|
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
2020-03-15 05:09:23 +00:00
|
|
|
)
|
|
|
|
|
2021-09-30 20:28:14 +00:00
|
|
|
// InjectDoubleVoteOnEpoch broadcasts a double vote into the beacon node pool for the slasher to detect.
|
|
|
|
var InjectDoubleVoteOnEpoch = func(n types.Epoch) e2eTypes.Evaluator {
|
|
|
|
return e2eTypes.Evaluator{
|
|
|
|
Name: "inject_double_vote_%d",
|
|
|
|
Policy: policies.OnEpoch(n),
|
|
|
|
Evaluation: insertDoubleAttestationIntoPool,
|
|
|
|
}
|
2020-03-15 05:09:23 +00:00
|
|
|
}
|
|
|
|
|
2021-09-30 20:28:14 +00:00
|
|
|
// InjectDoubleBlockOnEpoch proposes a double block to the beacon node for the slasher to detect.
|
|
|
|
var InjectDoubleBlockOnEpoch = func(n types.Epoch) e2eTypes.Evaluator {
|
|
|
|
return e2eTypes.Evaluator{
|
|
|
|
Name: "inject_double_block_%d",
|
|
|
|
Policy: policies.OnEpoch(n),
|
|
|
|
Evaluation: proposeDoubleBlock,
|
|
|
|
}
|
2020-05-21 06:02:40 +00:00
|
|
|
}
|
|
|
|
|
2021-09-30 20:28:14 +00:00
|
|
|
// ValidatorsSlashedAfterEpoch ensures the expected amount of validators are slashed.
|
|
|
|
var ValidatorsSlashedAfterEpoch = func(n types.Epoch) e2eTypes.Evaluator {
|
|
|
|
return e2eTypes.Evaluator{
|
|
|
|
Name: "validators_slashed_epoch_%d",
|
|
|
|
Policy: policies.AfterNthEpoch(n),
|
|
|
|
Evaluation: validatorsSlashed,
|
|
|
|
}
|
2020-04-27 15:59:42 +00:00
|
|
|
}
|
|
|
|
|
2021-09-30 20:28:14 +00:00
|
|
|
// SlashedValidatorsLoseBalanceAfterEpoch checks if the validators slashed lose the right balance.
|
|
|
|
var SlashedValidatorsLoseBalanceAfterEpoch = func(n types.Epoch) e2eTypes.Evaluator {
|
|
|
|
return e2eTypes.Evaluator{
|
|
|
|
Name: "slashed_validators_lose_valance_epoch_%d",
|
|
|
|
Policy: policies.AfterNthEpoch(n),
|
|
|
|
Evaluation: validatorsLoseBalance,
|
|
|
|
}
|
2020-04-27 15:59:42 +00:00
|
|
|
}
|
|
|
|
|
2020-03-15 05:09:23 +00:00
|
|
|
var slashedIndices []uint64
|
|
|
|
|
2020-04-27 15:59:42 +00:00
|
|
|
func validatorsSlashed(conns ...*grpc.ClientConn) error {
|
|
|
|
conn := conns[0]
|
|
|
|
ctx := context.Background()
|
|
|
|
client := eth.NewBeaconChainClient(conn)
|
|
|
|
req := ð.GetValidatorActiveSetChangesRequest{}
|
|
|
|
changes, err := client.GetValidatorActiveSetChanges(ctx, req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-05-21 06:02:40 +00:00
|
|
|
if len(changes.SlashedIndices) != len(slashedIndices) {
|
|
|
|
return fmt.Errorf("expected %d indices to be slashed, received %d", len(slashedIndices), len(changes.SlashedIndices))
|
2020-04-27 15:59:42 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func validatorsLoseBalance(conns ...*grpc.ClientConn) error {
|
|
|
|
conn := conns[0]
|
|
|
|
ctx := context.Background()
|
|
|
|
client := eth.NewBeaconChainClient(conn)
|
|
|
|
|
2021-02-23 00:14:50 +00:00
|
|
|
for i, slashedIndex := range slashedIndices {
|
2020-04-27 15:59:42 +00:00
|
|
|
req := ð.GetValidatorRequest{
|
|
|
|
QueryFilter: ð.GetValidatorRequest_Index{
|
2021-02-23 00:14:50 +00:00
|
|
|
Index: types.ValidatorIndex(slashedIndex),
|
2020-04-27 15:59:42 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
valResp, err := client.GetValidator(ctx, req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
slashedPenalty := params.BeaconConfig().MaxEffectiveBalance / params.BeaconConfig().MinSlashingPenaltyQuotient
|
|
|
|
slashedBal := params.BeaconConfig().MaxEffectiveBalance - slashedPenalty + params.BeaconConfig().EffectiveBalanceIncrement/10
|
|
|
|
if valResp.EffectiveBalance >= slashedBal {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"expected slashed validator %d to balance less than %d, received %d",
|
|
|
|
i,
|
|
|
|
slashedBal,
|
|
|
|
valResp.EffectiveBalance,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-03-15 05:09:23 +00:00
|
|
|
func insertDoubleAttestationIntoPool(conns ...*grpc.ClientConn) error {
|
|
|
|
conn := conns[0]
|
|
|
|
valClient := eth.NewBeaconNodeValidatorClient(conn)
|
|
|
|
beaconClient := eth.NewBeaconChainClient(conn)
|
|
|
|
|
|
|
|
ctx := context.Background()
|
2021-05-17 18:32:04 +00:00
|
|
|
chainHead, err := beaconClient.GetChainHead(ctx, &emptypb.Empty{})
|
2020-03-15 05:09:23 +00:00
|
|
|
if err != nil {
|
2020-05-21 06:02:40 +00:00
|
|
|
return errors.Wrap(err, "could not get chain head")
|
2020-03-15 05:09:23 +00:00
|
|
|
}
|
|
|
|
|
2021-09-23 18:53:46 +00:00
|
|
|
_, privKeys, err := util.DeterministicDepositsAndKeys(params.BeaconConfig().MinGenesisActiveValidatorCount)
|
2020-03-15 05:09:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
pubKeys := make([][]byte, len(privKeys))
|
|
|
|
for i, priv := range privKeys {
|
|
|
|
pubKeys[i] = priv.PublicKey().Marshal()
|
|
|
|
}
|
|
|
|
duties, err := valClient.GetDuties(ctx, ð.DutiesRequest{
|
|
|
|
Epoch: chainHead.HeadEpoch,
|
|
|
|
PublicKeys: pubKeys,
|
|
|
|
})
|
|
|
|
if err != nil {
|
2020-05-21 06:02:40 +00:00
|
|
|
return errors.Wrap(err, "could not get duties")
|
2020-03-15 05:09:23 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 00:14:50 +00:00
|
|
|
var committeeIndex types.CommitteeIndex
|
|
|
|
var committee []types.ValidatorIndex
|
2020-03-15 05:09:23 +00:00
|
|
|
for _, duty := range duties.Duties {
|
|
|
|
if duty.AttesterSlot == chainHead.HeadSlot-1 {
|
|
|
|
committeeIndex = duty.CommitteeIndex
|
|
|
|
committee = duty.Committee
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
attDataReq := ð.AttestationDataRequest{
|
|
|
|
CommitteeIndex: committeeIndex,
|
|
|
|
Slot: chainHead.HeadSlot - 1,
|
|
|
|
}
|
|
|
|
|
|
|
|
attData, err := valClient.GetAttestationData(ctx, attDataReq)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
blockRoot := bytesutil.ToBytes32([]byte("muahahahaha I'm an evil validator"))
|
|
|
|
attData.BeaconBlockRoot = blockRoot[:]
|
|
|
|
|
2020-04-14 20:27:03 +00:00
|
|
|
req := ð.DomainRequest{
|
|
|
|
Epoch: chainHead.HeadEpoch,
|
|
|
|
Domain: params.BeaconConfig().DomainBeaconAttester[:],
|
|
|
|
}
|
|
|
|
resp, err := valClient.DomainData(ctx, req)
|
2020-03-15 05:09:23 +00:00
|
|
|
if err != nil {
|
2020-05-21 06:02:40 +00:00
|
|
|
return errors.Wrap(err, "could not get domain data")
|
2020-03-15 05:09:23 +00:00
|
|
|
}
|
2021-09-27 16:19:20 +00:00
|
|
|
signingRoot, err := signing.ComputeSigningRoot(attData, resp.SignatureDomain)
|
2020-03-15 05:09:23 +00:00
|
|
|
if err != nil {
|
2020-05-21 06:02:40 +00:00
|
|
|
return errors.Wrap(err, "could not compute signing root")
|
2020-03-15 05:09:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
valsToSlash := uint64(2)
|
|
|
|
for i := uint64(0); i < valsToSlash && i < uint64(len(committee)); i++ {
|
2021-09-16 17:05:58 +00:00
|
|
|
if len(slice.IntersectionUint64(slashedIndices, []uint64{uint64(committee[i])})) > 0 {
|
2020-03-15 05:09:23 +00:00
|
|
|
valsToSlash++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// Set the bits of half the committee to be slashed.
|
|
|
|
attBitfield := bitfield.NewBitlist(uint64(len(committee)))
|
|
|
|
attBitfield.SetBitAt(i, true)
|
|
|
|
|
|
|
|
att := ð.Attestation{
|
|
|
|
AggregationBits: attBitfield,
|
|
|
|
Data: attData,
|
2020-04-14 20:27:03 +00:00
|
|
|
Signature: privKeys[committee[i]].Sign(signingRoot[:]).Marshal(),
|
2020-03-15 05:09:23 +00:00
|
|
|
}
|
2020-05-21 06:02:40 +00:00
|
|
|
// We only broadcast to conns[0] here since we can trust that at least 1 node will be online.
|
|
|
|
// Only broadcasting the attestation to one node also helps test slashing propagation.
|
|
|
|
client := eth.NewBeaconNodeValidatorClient(conns[0])
|
|
|
|
if _, err = client.ProposeAttestation(ctx, att); err != nil {
|
|
|
|
return errors.Wrap(err, "could not propose attestation")
|
2020-03-15 05:09:23 +00:00
|
|
|
}
|
2021-02-23 00:14:50 +00:00
|
|
|
slashedIndices = append(slashedIndices, uint64(committee[i]))
|
2020-03-15 05:09:23 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2020-05-21 06:02:40 +00:00
|
|
|
|
|
|
|
func proposeDoubleBlock(conns ...*grpc.ClientConn) error {
|
|
|
|
conn := conns[0]
|
|
|
|
valClient := eth.NewBeaconNodeValidatorClient(conn)
|
|
|
|
beaconClient := eth.NewBeaconChainClient(conn)
|
|
|
|
|
|
|
|
ctx := context.Background()
|
2021-05-17 18:32:04 +00:00
|
|
|
chainHead, err := beaconClient.GetChainHead(ctx, &emptypb.Empty{})
|
2020-05-21 06:02:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "could not get chain head")
|
|
|
|
}
|
2021-09-23 18:53:46 +00:00
|
|
|
_, privKeys, err := util.DeterministicDepositsAndKeys(params.BeaconConfig().MinGenesisActiveValidatorCount)
|
2020-05-21 06:02:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
pubKeys := make([][]byte, len(privKeys))
|
|
|
|
for i, priv := range privKeys {
|
|
|
|
pubKeys[i] = priv.PublicKey().Marshal()
|
|
|
|
}
|
|
|
|
duties, err := valClient.GetDuties(ctx, ð.DutiesRequest{
|
|
|
|
Epoch: chainHead.HeadEpoch,
|
|
|
|
PublicKeys: pubKeys,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "could not get duties")
|
|
|
|
}
|
|
|
|
|
2021-02-23 00:14:50 +00:00
|
|
|
var proposerIndex types.ValidatorIndex
|
2020-05-21 06:02:40 +00:00
|
|
|
for i, duty := range duties.CurrentEpochDuties {
|
2021-09-16 17:05:58 +00:00
|
|
|
if slice.IsInSlots(chainHead.HeadSlot-1, duty.ProposerSlots) {
|
2021-02-23 00:14:50 +00:00
|
|
|
proposerIndex = types.ValidatorIndex(i)
|
2020-05-21 06:02:40 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-30 20:28:14 +00:00
|
|
|
validatorNum := int(params.BeaconConfig().MinGenesisActiveValidatorCount)
|
|
|
|
beaconNodeNum := e2e.TestParams.BeaconNodeCount
|
|
|
|
if validatorNum%beaconNodeNum != 0 {
|
|
|
|
return errors.New("validator count is not easily divisible by beacon node count")
|
|
|
|
}
|
|
|
|
validatorsPerNode := validatorNum / beaconNodeNum
|
|
|
|
|
|
|
|
// If the proposer index is in the second validator client, we connect to
|
|
|
|
// the corresponding beacon node instead.
|
|
|
|
if proposerIndex >= types.ValidatorIndex(uint64(validatorsPerNode)) {
|
|
|
|
valClient = eth.NewBeaconNodeValidatorClient(conns[1])
|
|
|
|
}
|
|
|
|
|
2020-05-21 06:02:40 +00:00
|
|
|
hashLen := 32
|
|
|
|
blk := ð.BeaconBlock{
|
2021-09-30 20:28:14 +00:00
|
|
|
Slot: chainHead.HeadSlot + 1,
|
|
|
|
ParentRoot: chainHead.HeadBlockRoot,
|
2020-05-21 06:02:40 +00:00
|
|
|
StateRoot: bytesutil.PadTo([]byte("bad state root"), hashLen),
|
|
|
|
ProposerIndex: proposerIndex,
|
|
|
|
Body: ð.BeaconBlockBody{
|
|
|
|
Eth1Data: ð.Eth1Data{
|
|
|
|
BlockHash: bytesutil.PadTo([]byte("bad block hash"), hashLen),
|
|
|
|
DepositRoot: bytesutil.PadTo([]byte("bad deposit root"), hashLen),
|
|
|
|
DepositCount: 1,
|
|
|
|
},
|
2021-12-15 20:14:30 +00:00
|
|
|
RandaoReveal: bytesutil.PadTo([]byte("bad randao"), fieldparams.BLSSignatureLength),
|
2020-05-21 06:02:40 +00:00
|
|
|
Graffiti: bytesutil.PadTo([]byte("teehee"), hashLen),
|
|
|
|
ProposerSlashings: []*eth.ProposerSlashing{},
|
|
|
|
AttesterSlashings: []*eth.AttesterSlashing{},
|
|
|
|
Attestations: []*eth.Attestation{},
|
|
|
|
Deposits: []*eth.Deposit{},
|
|
|
|
VoluntaryExits: []*eth.SignedVoluntaryExit{},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
req := ð.DomainRequest{
|
|
|
|
Epoch: chainHead.HeadEpoch,
|
|
|
|
Domain: params.BeaconConfig().DomainBeaconProposer[:],
|
|
|
|
}
|
|
|
|
resp, err := valClient.DomainData(ctx, req)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "could not get domain data")
|
|
|
|
}
|
2021-09-27 16:19:20 +00:00
|
|
|
signingRoot, err := signing.ComputeSigningRoot(blk, resp.SignatureDomain)
|
2020-05-21 06:02:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "could not compute signing root")
|
|
|
|
}
|
|
|
|
sig := privKeys[proposerIndex].Sign(signingRoot[:]).Marshal()
|
|
|
|
signedBlk := ð.SignedBeaconBlock{
|
|
|
|
Block: blk,
|
|
|
|
Signature: sig,
|
|
|
|
}
|
|
|
|
|
|
|
|
// We only broadcast to conns[0] here since we can trust that at least 1 node will be online.
|
|
|
|
// Only broadcasting the attestation to one node also helps test slashing propagation.
|
2021-09-30 20:28:14 +00:00
|
|
|
if _, err = valClient.ProposeBlock(ctx, signedBlk); err == nil {
|
2020-05-21 06:02:40 +00:00
|
|
|
return errors.New("expected block to fail processing")
|
|
|
|
}
|
2021-02-23 00:14:50 +00:00
|
|
|
slashedIndices = append(slashedIndices, uint64(proposerIndex))
|
2020-05-21 06:02:40 +00:00
|
|
|
return nil
|
|
|
|
}
|