prysm-pulse/slasher/detection/detect.go
Ivan Martinez cc5fc0af1a
Plug-in double voting detection into detection service (#4960)
* Add double vote detection to spanner
* Add documentation
* Update slasher/detection/attestations/spanner.go
* Merge branch 'master' of https://github.com/prysmaticlabs/Prysm into slasher-spanner-double
* Merge branch 'slasher-spanner-double' of https://github.com/0xKiwi/Prysm into slasher-spanner-double
* Merge branch 'master' of https://github.com/prysmaticlabs/Prysm into slasher-spanner-double
* Gazelle
* Add double vote detection func
* Implement double voting detection
* Merge branch 'master' of https://github.com/prysmaticlabs/Prysm into slasher-implement-double
* Merge branch 'master' into slasher-implement-double
* Merge branch 'slasher-implement-double' of https://github.com/0xKiwi/Prysm into slasher-implement-double
* Fix typo
* Remove filter, replace with slot + committee index
* Change bloom filter to 2 sig bytes
* Merge branch 'master' of https://github.com/prysmaticlabs/Prysm into slasher-change-filter
* Merge branch 'master' of https://github.com/prysmaticlabs/Prysm into slasher-implement-double
* Merge branch 'slasher-change-filter' of https://github.com/0xKiwi/Prysm into slasher-implement-double
* Change detection to use prefix
* Fix runtime
* Merge branch 'master' of https://github.com/prysmaticlabs/Prysm into slasher-implement-double
* Fix bug and comments
* Merge branch 'master' of https://github.com/prysmaticlabs/Prysm into slasher-implement-double
* Fix flaky test
* Merge branch 'master' into slasher-implement-double
* Improve logs
* Merge branch 'slasher-implement-double' of https://github.com/0xKiwi/Prysm into slasher-implement-double
* Add ok check
* Fix test
* Merge branch 'master' into slasher-implement-double
2020-03-03 18:08:21 +00:00

152 lines
4.7 KiB
Go

package detection
import (
"context"
"github.com/gogo/protobuf/proto"
"github.com/pkg/errors"
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
"github.com/prysmaticlabs/prysm/shared/hashutil"
"github.com/prysmaticlabs/prysm/shared/sliceutil"
"github.com/prysmaticlabs/prysm/slasher/detection/attestations/types"
)
func (ds *Service) detectAttesterSlashings(
ctx context.Context,
att *ethpb.IndexedAttestation,
) ([]*ethpb.AttesterSlashing, error) {
slashings := make([]*ethpb.AttesterSlashing, 0)
for i := 0; i < len(att.AttestingIndices); i++ {
valIdx := att.AttestingIndices[i]
result, err := ds.minMaxSpanDetector.DetectSlashingForValidator(ctx, valIdx, att.Data)
if err != nil {
return nil, err
}
// If the response is nil, there was no slashing detected.
if result == nil {
continue
}
var slashing *ethpb.AttesterSlashing
switch result.Kind {
case types.DoubleVote:
slashing, err = ds.detectDoubleVote(ctx, att, result)
if err != nil {
return nil, errors.Wrap(err, "could not detect double votes on attestation")
}
case types.SurroundVote:
slashing, err = ds.detectSurroundVotes(ctx, att, result)
if err != nil {
return nil, errors.Wrap(err, "could not detect surround votes on attestation")
}
}
slashings = append(slashings, slashing)
}
// Clear out any duplicate slashings.
keys := make(map[[32]byte]bool)
var slashingList []*ethpb.AttesterSlashing
for _, ss := range slashings {
hash, err := hashutil.HashProto(ss)
if err != nil {
return nil, err
}
if _, value := keys[hash]; !value {
keys[hash] = true
slashingList = append(slashingList, ss)
}
}
return slashingList, nil
}
// detectDoubleVote cross references the passed in attestation with the bloom filter maintained
// for every epoch for the validator in order to determine if it is a double vote.
func (ds *Service) detectDoubleVote(
ctx context.Context,
incomingAtt *ethpb.IndexedAttestation,
detectionResult *types.DetectionResult,
) (*ethpb.AttesterSlashing, error) {
if detectionResult == nil || detectionResult.Kind != types.DoubleVote {
return nil, nil
}
otherAtts, err := ds.slasherDB.IndexedAttestationsWithPrefix(ctx, detectionResult.SlashableEpoch, detectionResult.SigBytes[:])
if err != nil {
return nil, err
}
for _, att := range otherAtts {
if att.Data == nil {
continue
}
// If there are no shared indices, there is no validator to slash.
if len(sliceutil.IntersectionUint64(att.AttestingIndices, incomingAtt.AttestingIndices)) == 0 {
continue
}
if isDoubleVote(incomingAtt, att) {
return &ethpb.AttesterSlashing{
Attestation_1: incomingAtt,
Attestation_2: att,
}, nil
}
}
return nil, errors.New("unexpected false positive in double vote detection")
}
// detectSurroundVotes cross references the passed in attestation with the requested validator's
// voting history in order to detect any possible surround votes.
func (ds *Service) detectSurroundVotes(
ctx context.Context,
incomingAtt *ethpb.IndexedAttestation,
detectionResult *types.DetectionResult,
) (*ethpb.AttesterSlashing, error) {
if detectionResult == nil || detectionResult.Kind != types.SurroundVote {
return nil, nil
}
otherAtts, err := ds.slasherDB.IndexedAttestationsWithPrefix(ctx, detectionResult.SlashableEpoch, detectionResult.SigBytes[:])
if err != nil {
return nil, err
}
for _, att := range otherAtts {
if att.Data == nil {
continue
}
// If there are no shared indices, there is no validator to slash.
if len(sliceutil.IntersectionUint64(att.AttestingIndices, incomingAtt.AttestingIndices)) == 0 {
continue
}
// Slashings must be submitted as the incoming attestation surrounding the saved attestation.
// So we swap the order if needed.
if isSurrounding(incomingAtt, att) {
return &ethpb.AttesterSlashing{
Attestation_1: incomingAtt,
Attestation_2: att,
}, nil
} else if isSurrounded(incomingAtt, att) {
return &ethpb.AttesterSlashing{
Attestation_1: att,
Attestation_2: incomingAtt,
}, nil
}
}
return nil, errors.New("unexpected false positive in surround vote detection")
}
func isDoubleVote(incomingAtt *ethpb.IndexedAttestation, prevAtt *ethpb.IndexedAttestation) bool {
return !proto.Equal(incomingAtt.Data, prevAtt.Data) && incomingAtt.Data.Target.Epoch == prevAtt.Data.Target.Epoch
}
func isSurrounding(incomingAtt *ethpb.IndexedAttestation, prevAtt *ethpb.IndexedAttestation) bool {
return incomingAtt.Data.Source.Epoch < prevAtt.Data.Source.Epoch &&
incomingAtt.Data.Target.Epoch > prevAtt.Data.Target.Epoch
}
func isSurrounded(incomingAtt *ethpb.IndexedAttestation, prevAtt *ethpb.IndexedAttestation) bool {
return incomingAtt.Data.Source.Epoch > prevAtt.Data.Source.Epoch &&
incomingAtt.Data.Target.Epoch < prevAtt.Data.Target.Epoch
}