Skip to content

Commit

Permalink
Detect double votes in different batches.
Browse files Browse the repository at this point in the history
In order to do that:
1. Each attestation of the batch is tested against the other attestations of the batch.
2. Each attestation of the batch is tested against the content of the database.
2. Attestations are saved into the database.

Fixes #13590.
  • Loading branch information
nalepae committed Feb 12, 2024
1 parent b798f69 commit 544918c
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 64 deletions.
75 changes: 69 additions & 6 deletions beacon-chain/slasher/detect_attestations.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ func (s *Service) checkSlashableAttestations(
slashings[root] = slashing
}

// Save the attestation records to our database.
// If multiple attestations are provided for the same validator index + target epoch combination,
// then the first (validator index + target epoch) => signing root) link is kept into the database.
if err := s.serviceCfg.Database.SaveAttestationRecordsForValidators(ctx, atts); err != nil {
return nil, errors.Wrap(err, couldNotSaveAttRecord)
}

// Surrounding / surrounded votes
groupedByValidatorChunkIndexAtts := s.groupByValidatorChunkIndex(atts)
log.WithField("numBatches", len(groupedByValidatorChunkIndexAtts)).Debug("Batching attestations by validator chunk index")
Expand Down Expand Up @@ -148,21 +155,77 @@ func (s *Service) checkSurrounds(

// Check for double votes in our database given a list of incoming attestations.
func (s *Service) checkDoubleVotes(
ctx context.Context, attestations []*slashertypes.IndexedAttestationWrapper,
ctx context.Context, incomingAttWrappers []*slashertypes.IndexedAttestationWrapper,
) (map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing, error) {
ctx, span := trace.StartSpan(ctx, "Slasher.checkDoubleVotesOnDisk")
defer span.End()

doubleVotes, err := s.serviceCfg.Database.CheckAttesterDoubleVotes(
ctx, attestations,
)
type attestationInfo struct {
validatorIndex uint64
epoch primitives.Epoch
}

slashings := map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing{}

// Check each incoming attestation for double votes against other incoming attestations.
existingAttWrappers := make(map[attestationInfo]*slashertypes.IndexedAttestationWrapper)

for _, incomingAttWrapper := range incomingAttWrappers {
targetEpoch := incomingAttWrapper.IndexedAttestation.Data.Target.Epoch

for _, validatorIndex := range incomingAttWrapper.IndexedAttestation.AttestingIndices {
info := attestationInfo{
validatorIndex: validatorIndex,
epoch: targetEpoch,
}

existingAttWrapper, ok := existingAttWrappers[info]
if !ok {
// This is the first attestation for this `validator index x epoch` combination.
// There is no double vote. This attestation is memoized for future checks.
existingAttWrappers[info] = incomingAttWrapper
continue
}

if existingAttWrapper.DataRoot == incomingAttWrapper.DataRoot {
// Both attestations are the same, this is not a double vote.
continue
}

// There is two different attestations for the same `validator index x epoch` combination.
// This is a double vote.
doubleVotesTotal.Inc()

slashing := &ethpb.AttesterSlashing{
Attestation_1: existingAttWrapper.IndexedAttestation,
Attestation_2: incomingAttWrapper.IndexedAttestation,
}

// Ensure the attestation with the lower data root is the first attestation.
// It will be useful for comparing with other double votes.
if bytes.Compare(existingAttWrapper.DataRoot[:], incomingAttWrapper.DataRoot[:]) > 0 {
slashing = &ethpb.AttesterSlashing{
Attestation_1: incomingAttWrapper.IndexedAttestation,
Attestation_2: existingAttWrapper.IndexedAttestation,
}
}

root, err := slashing.HashTreeRoot()
if err != nil {
return nil, errors.Wrap(err, "could not hash tree root for attester slashing")
}

slashings[root] = slashing
}
}

// Check each incoming attestation for double votes against the database.
doubleVotes, err := s.serviceCfg.Database.CheckAttesterDoubleVotes(ctx, incomingAttWrappers)

if err != nil {
return nil, errors.Wrap(err, "could not retrieve potential double votes from disk")
}

slashings := map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing{}

for _, doubleVote := range doubleVotes {
doubleVotesTotal.Inc()

Expand Down
98 changes: 48 additions & 50 deletions beacon-chain/slasher/detect_attestations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,31 +69,30 @@ func Test_processAttestations(t *testing.T) {
},
},
},
// Uncomment when https://github.com/prysmaticlabs/prysm/issues/13590 is fixed
// {
// name: "Same target with different signing roots - two steps",
// steps: []*step{
// {
// currentEpoch: 4,
// attestationsInfo: []*attestationInfo{
// {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}},
// },
// expectedSlashingsInfo: nil,
// },
// {
// currentEpoch: 4,
// attestationsInfo: []*attestationInfo{
// {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{2}},
// },
// expectedSlashingsInfo: []*slashingInfo{
// {
// attestationInfo_1: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}},
// attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{2}},
// },
// },
// },
// },
// },
{
name: "Same target with different signing roots - two steps",
steps: []*step{
{
currentEpoch: 4,
attestationsInfo: []*attestationInfo{
{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}},
},
expectedSlashingsInfo: nil,
},
{
currentEpoch: 4,
attestationsInfo: []*attestationInfo{
{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{2}},
},
expectedSlashingsInfo: []*slashingInfo{
{
attestationInfo_1: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}},
attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{2}},
},
},
},
},
},
{
name: "Same target with same signing roots - single step",
steps: []*step{
Expand Down Expand Up @@ -273,31 +272,30 @@ func Test_processAttestations(t *testing.T) {
},
},
},
// Uncomment when https://github.com/prysmaticlabs/prysm/issues/13590 is fixed
// {
// name: "Detects double vote, (source 1, target 2), (source 0, target 2) - two steps",
// steps: []*step{
// {
// currentEpoch: 4,
// attestationsInfo: []*attestationInfo{
// {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil},
// },
// expectedSlashingsInfo: nil,
// },
// {
// currentEpoch: 4,
// attestationsInfo: []*attestationInfo{
// {source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil},
// },
// expectedSlashingsInfo: []*slashingInfo{
// {
// attestationInfo_1: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil},
// attestationInfo_2: &attestationInfo{source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil},
// },
// },
// },
// },
// },
{
name: "Detects double vote, (source 1, target 2), (source 0, target 2) - two steps",
steps: []*step{
{
currentEpoch: 4,
attestationsInfo: []*attestationInfo{
{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil},
},
expectedSlashingsInfo: nil,
},
{
currentEpoch: 4,
attestationsInfo: []*attestationInfo{
{source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil},
},
expectedSlashingsInfo: []*slashingInfo{
{
attestationInfo_1: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil},
attestationInfo_2: &attestationInfo{source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil},
},
},
},
},
},
{
name: "Not slashable, surrounding but non-overlapping attesting indices within same validator chunk index - single step",
steps: []*step{
Expand Down
8 changes: 0 additions & 8 deletions beacon-chain/slasher/receive.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,6 @@ func (s *Service) processAttestations(
"attsQueueSize": queuedAttestationsCount,
}).Info("Processing queued attestations for slashing detection")

// Save the attestation records to our database.
// If multiple attestations are provided for the same validator index + target epoch combination,
// then the first (validator index + target epoch) => signing root) link is kept into the database.
if err := s.serviceCfg.Database.SaveAttestationRecordsForValidators(ctx, validAttestations); err != nil {
log.WithError(err).Error(couldNotSaveAttRecord)
return nil
}

// Check for attestatinos slashings (double, sourrounding, surrounded votes).
slashings, err := s.checkSlashableAttestations(ctx, currentEpoch, validAttestations)
if err != nil {
Expand Down

0 comments on commit 544918c

Please sign in to comment.