Skip to content

Commit

Permalink
refactored slashing evaluator
Browse files Browse the repository at this point in the history
  • Loading branch information
brightredchilli committed Jan 18, 2024
1 parent 32f7654 commit 64b356f
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 85 deletions.
1 change: 1 addition & 0 deletions testing/endtoend/evaluators/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ go_library(
"operations.go",
"peers.go",
"slashing.go",
"slashing_helper.go",
"validator.go",
],
importpath = "github.com/prysmaticlabs/prysm/v4/testing/endtoend/evaluators",
Expand Down
106 changes: 21 additions & 85 deletions testing/endtoend/evaluators/slashing.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"

"github.com/pkg/errors"
"github.com/prysmaticlabs/go-bitfield"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/signing"
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/config/params"
Expand Down Expand Up @@ -126,109 +125,46 @@ func insertDoubleAttestationIntoPool(_ *e2eTypes.EvaluationContext, conns ...*gr
beaconClient := eth.NewBeaconChainClient(conn)

ctx := context.Background()
chainHead, err := beaconClient.GetChainHead(ctx, &emptypb.Empty{})
if err != nil {
return errors.Wrap(err, "could not get chain head")
}

_, privKeys, err := util.DeterministicDepositsAndKeys(params.BeaconConfig().MinGenesisActiveValidatorCount)
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, &eth.DutiesRequest{
Epoch: chainHead.HeadEpoch,
PublicKeys: pubKeys,
})
if err != nil {
return errors.Wrap(err, "could not get duties")
}

var committeeIndex primitives.CommitteeIndex
var committee []primitives.ValidatorIndex
for _, duty := range duties.Duties {
if duty.AttesterSlot == chainHead.HeadSlot {
committeeIndex = duty.CommitteeIndex
committee = duty.Committee
break
}
}

attDataReq := &eth.AttestationDataRequest{
CommitteeIndex: committeeIndex,
Slot: chainHead.HeadSlot,
}

attData, err := valClient.GetAttestationData(ctx, attDataReq)
if err != nil {
return err
}

req := &eth.DomainRequest{
Epoch: chainHead.HeadEpoch,
Domain: params.BeaconConfig().DomainBeaconAttester[:],
h := doubleAttestationHelper{
valClient: valClient,
beaconClient: beaconClient,
}
resp, err := valClient.DomainData(ctx, req)
err := h.setup(ctx)
if err != nil {
return errors.Wrap(err, "could not get domain data")
return errors.Wrap(err, "could not setup doubleAttestationHelper")
}

valsToSlash := uint64(2)
for i := uint64(0); i < valsToSlash && i < uint64(len(committee)); i++ {
if len(slice.IntersectionUint64(slashedIndices, []uint64{uint64(committee[i])})) > 0 {
for i := uint64(0); i < valsToSlash; i++ {
valIdx := h.validatorIndexAtCommitteeIndex(i)

if len(slice.IntersectionUint64(slashedIndices, []uint64{uint64(valIdx)})) > 0 {
valsToSlash++
continue
}

// msg must be unique so they are not filtered by P2P
blockRoot := bytesutil.ToBytes32([]byte("0 muahahahaha"))
attData.BeaconBlockRoot = blockRoot[:]
signingRoot, err := signing.ComputeSigningRoot(attData, resp.SignatureDomain)
// Need to send proposal to both beacon nodes to avoid flakiness.
// See: https://github.com/prysmaticlabs/prysm/issues/12415#issuecomment-1874643269
c := eth.NewBeaconNodeValidatorClient(conns[0])
att, err := h.getSlashableAttestation(i)
if err != nil {
return errors.Wrap(err, "could not compute signing root")
}
// Set the bits of half the committee to be slashed.
attBitfield := bitfield.NewBitlist(uint64(len(committee)))
attBitfield.SetBitAt(i, true)

att := &eth.Attestation{
AggregationBits: attBitfield,
Data: attData,
Signature: privKeys[committee[i]].Sign(signingRoot[:]).Marshal(),
return err
}
// 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 {
if _, err := c.ProposeAttestation(ctx, att); err != nil {
return errors.Wrap(err, "could not propose attestation")
}


blockRoot = bytesutil.ToBytes32([]byte("1 muahahahaha"))
attData.BeaconBlockRoot = blockRoot[:]
signingRoot, err = signing.ComputeSigningRoot(attData, resp.SignatureDomain)
c1 := eth.NewBeaconNodeValidatorClient(conns[1])
att1, err := h.getSlashableAttestation(i)
if err != nil {
return errors.Wrap(err, "could not compute signing root")
}
// Set the bits of half the committee to be slashed.
attBitfield = bitfield.NewBitlist(uint64(len(committee)))
attBitfield.SetBitAt(i, true)

att = &eth.Attestation{
AggregationBits: attBitfield,
Data: attData,
Signature: privKeys[committee[i]].Sign(signingRoot[:]).Marshal(),
return err
}
// 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[1])
if _, err = client.ProposeAttestation(ctx, att); err != nil {
if _, err := c1.ProposeAttestation(ctx, att1); err != nil {
return errors.Wrap(err, "could not propose attestation")
}
slashedIndices = append(slashedIndices, uint64(committee[i]))

slashedIndices = append(slashedIndices, uint64(valIdx))
}
return nil
}
Expand Down
128 changes: 128 additions & 0 deletions testing/endtoend/evaluators/slashing_helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package evaluators

import (
"context"
"crypto/rand"

"github.com/pkg/errors"
"github.com/prysmaticlabs/go-bitfield"
"github.com/prysmaticlabs/prysm/v4/beacon-chain/core/signing"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v4/crypto/bls"
"github.com/prysmaticlabs/prysm/v4/encoding/bytesutil"
eth "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v4/testing/util"
"google.golang.org/protobuf/types/known/emptypb"
)

type doubleAttestationHelper struct {
valClient eth.BeaconNodeValidatorClient
beaconClient eth.BeaconChainClient
privKeys []bls.SecretKey
pubKeys [][]byte
domainResp *eth.DomainResponse
attData *eth.AttestationData

committee []primitives.ValidatorIndex
}

// Initializes helper with details needed to make a double attestation for testint purposes
// Populates the committee of that is responsible for the
func (h *doubleAttestationHelper) setup(ctx context.Context) error {
chainHead, err := h.beaconClient.GetChainHead(ctx, &emptypb.Empty{})
if err != nil {
return errors.Wrap(err, "could not get chain head")
}
_, privKeys, err := util.DeterministicDepositsAndKeys(params.BeaconConfig().MinGenesisActiveValidatorCount)
if err != nil {
return errors.Wrap(err, "could not get depositsandkeys")
}

pubKeys := make([][]byte, len(privKeys))
for i, priv := range privKeys {
pubKeys[i] = priv.PublicKey().Marshal()
}

duties, err := h.valClient.GetDuties(ctx, &eth.DutiesRequest{
Epoch: chainHead.HeadEpoch,
PublicKeys: pubKeys,
})

if err != nil {
return errors.Wrap(err, "could not get duties")
}

var committeeIndex primitives.CommitteeIndex
var committee []primitives.ValidatorIndex
for _, duty := range duties.Duties {
if duty.AttesterSlot == chainHead.HeadSlot {
committeeIndex = duty.CommitteeIndex
committee = duty.Committee
break
}
}
attDataReq := &eth.AttestationDataRequest{
CommitteeIndex: committeeIndex,
Slot: chainHead.HeadSlot,
}

attData, err := h.valClient.GetAttestationData(ctx, attDataReq)
if err != nil {
return err
}

req := &eth.DomainRequest{
Epoch: chainHead.HeadEpoch,
Domain: params.BeaconConfig().DomainBeaconAttester[:],
}

domainResp, err := h.valClient.DomainData(ctx, req)
if err != nil {
return errors.Wrap(err, "could not get domain data")
}

h.privKeys = privKeys
h.pubKeys = pubKeys
h.domainResp = domainResp
h.committee = committee
h.attData = attData

return nil
}

// Returns the validatorIndex at index idx of the fetched committee in setup()
func (h *doubleAttestationHelper) validatorIndexAtCommitteeIndex(idx uint64) primitives.ValidatorIndex {
return h.committee[idx]
}

// Returns a attestation was previously submitted, at the previous slot, modifying it so that it is signed
// by the validator indicated by idx. idx represents the index in the committee of the attestation.
// The block root value is random, which allows this to be seen by P2P networks as
// new, unique blocks.
func (h *doubleAttestationHelper) getSlashableAttestation(idx uint64) (*eth.Attestation, error) {
// msg must be unique so they are not filtered by P2P
randVal := make([]byte, 4)
_, err := rand.Read(randVal)
if err != nil {
return nil, errors.Wrap(err, "error reading random val")
}
blockRoot := bytesutil.ToBytes32(append(randVal, []byte("muahahahaha evil validator")...))
h.attData.BeaconBlockRoot = blockRoot[:]

signingRoot, err := signing.ComputeSigningRoot(h.attData, h.domainResp.SignatureDomain)
if err != nil {
return nil, errors.Wrap(err, "could not get chain head")
}

valIdx := h.validatorIndexAtCommitteeIndex(idx)

attBitfield := bitfield.NewBitlist(uint64(len(h.committee)))
attBitfield.SetBitAt(idx, true)
att := &eth.Attestation{
AggregationBits: attBitfield,
Data: h.attData,
Signature: h.privKeys[valIdx].Sign(signingRoot[:]).Marshal(),
}
return att, nil
}

0 comments on commit 64b356f

Please sign in to comment.