2020-02-27 17:22:39 +00:00
package detection
import (
2020-04-14 20:27:03 +00:00
"bytes"
2020-02-27 17:22:39 +00:00
"context"
"github.com/pkg/errors"
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
2020-05-12 20:55:17 +00:00
"github.com/prysmaticlabs/prysm/shared/attestationutil"
2020-05-19 00:50:35 +00:00
"github.com/prysmaticlabs/prysm/shared/bytesutil"
2020-03-13 18:04:22 +00:00
"github.com/prysmaticlabs/prysm/shared/hashutil"
2020-02-27 17:22:39 +00:00
"github.com/prysmaticlabs/prysm/shared/sliceutil"
2020-03-12 16:38:58 +00:00
status "github.com/prysmaticlabs/prysm/slasher/db/types"
2020-02-27 17:22:39 +00:00
"github.com/prysmaticlabs/prysm/slasher/detection/attestations/types"
2020-03-08 17:56:43 +00:00
"go.opencensus.io/trace"
2020-02-27 17:22:39 +00:00
)
2020-03-24 18:30:21 +00:00
// DetectAttesterSlashings detects double, surround and surrounding attestation offences given an attestation.
func ( ds * Service ) DetectAttesterSlashings (
2020-02-27 17:22:39 +00:00
ctx context . Context ,
att * ethpb . IndexedAttestation ,
) ( [ ] * ethpb . AttesterSlashing , error ) {
2020-03-24 18:30:21 +00:00
ctx , span := trace . StartSpan ( ctx , "detection.DetectAttesterSlashings" )
2020-03-08 17:56:43 +00:00
defer span . End ( )
2020-03-09 18:14:19 +00:00
results , err := ds . minMaxSpanDetector . DetectSlashingsForAttestation ( ctx , att )
if err != nil {
return nil , err
}
// If the response is nil, there was no slashing detected.
if len ( results ) == 0 {
return nil , nil
}
2020-03-03 18:08:21 +00:00
2020-05-19 00:50:35 +00:00
resultsToAtts , err := ds . mapResultsToAtts ( ctx , results )
if err != nil {
return nil , err
}
2020-03-09 21:21:39 +00:00
var slashings [ ] * ethpb . AttesterSlashing
2020-03-09 18:14:19 +00:00
for _ , result := range results {
2020-05-19 00:50:35 +00:00
resultKey := resultHash ( result )
2020-03-03 18:08:21 +00:00
var slashing * ethpb . AttesterSlashing
switch result . Kind {
case types . DoubleVote :
2020-05-19 00:50:35 +00:00
slashing , err = ds . detectDoubleVote ( ctx , resultsToAtts [ resultKey ] , att , result )
2020-03-03 18:08:21 +00:00
if err != nil {
return nil , errors . Wrap ( err , "could not detect double votes on attestation" )
}
case types . SurroundVote :
2020-05-19 00:50:35 +00:00
slashing , err = ds . detectSurroundVotes ( ctx , resultsToAtts [ resultKey ] , att , result )
2020-03-03 18:08:21 +00:00
if err != nil {
return nil , errors . Wrap ( err , "could not detect surround votes on attestation" )
}
}
2020-03-09 21:21:39 +00:00
if slashing != nil {
slashings = append ( slashings , slashing )
}
2020-03-03 18:08:21 +00:00
}
2020-03-13 18:04:22 +00:00
// Clear out any duplicate results.
keys := make ( map [ [ 32 ] byte ] bool )
var slashingList [ ] * ethpb . AttesterSlashing
for _ , ss := range slashings {
hash , err := hashutil . HashProto ( ss )
if err != nil {
return nil , errors . Wrap ( err , "could not hash slashing" )
}
if _ , value := keys [ hash ] ; ! value {
keys [ hash ] = true
slashingList = append ( slashingList , ss )
}
}
2020-03-12 16:38:58 +00:00
if err = ds . slasherDB . SaveAttesterSlashings ( ctx , status . Active , slashings ) ; err != nil {
return nil , err
}
2020-03-13 18:04:22 +00:00
return slashingList , nil
2020-02-27 17:22:39 +00:00
}
2020-03-26 18:31:20 +00:00
// UpdateSpans passthrough function that updates span maps given an indexed attestation.
func ( ds * Service ) UpdateSpans ( ctx context . Context , att * ethpb . IndexedAttestation ) error {
return ds . minMaxSpanDetector . UpdateSpans ( ctx , att )
}
2020-03-03 18:08:21 +00:00
// 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 (
2020-02-27 17:22:39 +00:00
ctx context . Context ,
2020-05-19 00:50:35 +00:00
possibleAtts [ ] * ethpb . IndexedAttestation ,
2020-03-03 18:08:21 +00:00
incomingAtt * ethpb . IndexedAttestation ,
detectionResult * types . DetectionResult ,
) ( * ethpb . AttesterSlashing , error ) {
2020-03-08 17:56:43 +00:00
ctx , span := trace . StartSpan ( ctx , "detection.detectDoubleVote" )
defer span . End ( )
2020-03-03 18:08:21 +00:00
if detectionResult == nil || detectionResult . Kind != types . DoubleVote {
return nil , nil
}
2020-05-19 00:50:35 +00:00
for _ , att := range possibleAtts {
2020-03-03 18:08:21 +00:00
if att . Data == nil {
continue
}
2020-03-13 18:04:22 +00:00
2020-05-13 06:37:24 +00:00
if ! isDoubleVote ( incomingAtt , att ) {
2020-03-03 18:08:21 +00:00
continue
}
2020-05-13 06:37:24 +00:00
// If there are no shared indices, there is no validator to slash.
if ! sliceutil . IsInUint64 ( detectionResult . ValidatorIndex , att . AttestingIndices ) {
continue
2020-03-03 18:08:21 +00:00
}
2020-03-13 18:04:22 +00:00
2020-05-13 06:37:24 +00:00
doubleVotesDetected . Inc ( )
return & ethpb . AttesterSlashing {
Attestation_1 : incomingAtt ,
Attestation_2 : att ,
} , nil
2020-03-03 18:08:21 +00:00
}
2020-03-08 17:56:43 +00:00
return nil , nil
2020-02-27 17:22:39 +00:00
}
// 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 ,
2020-05-19 00:50:35 +00:00
possibleAtts [ ] * ethpb . IndexedAttestation ,
2020-02-27 17:22:39 +00:00
incomingAtt * ethpb . IndexedAttestation ,
2020-03-03 18:08:21 +00:00
detectionResult * types . DetectionResult ,
) ( * ethpb . AttesterSlashing , error ) {
2020-03-08 17:56:43 +00:00
ctx , span := trace . StartSpan ( ctx , "detection.detectSurroundVotes" )
defer span . End ( )
2020-03-03 18:08:21 +00:00
if detectionResult == nil || detectionResult . Kind != types . SurroundVote {
2020-02-27 17:22:39 +00:00
return nil , nil
}
2020-05-19 00:50:35 +00:00
for _ , att := range possibleAtts {
2020-02-27 17:22:39 +00:00
if att . Data == nil {
continue
}
2020-05-13 06:37:24 +00:00
isSurround := isSurrounding ( incomingAtt , att )
isSurrounded := isSurrounding ( att , incomingAtt )
if ! isSurround && ! isSurrounded {
continue
}
2020-02-27 17:22:39 +00:00
// If there are no shared indices, there is no validator to slash.
2020-05-13 06:37:24 +00:00
if ! sliceutil . IsInUint64 ( detectionResult . ValidatorIndex , att . AttestingIndices ) {
2020-02-27 17:22:39 +00:00
continue
}
2020-03-03 18:08:21 +00:00
// Slashings must be submitted as the incoming attestation surrounding the saved attestation.
// So we swap the order if needed.
2020-05-13 06:37:24 +00:00
if isSurround {
2020-03-10 19:41:55 +00:00
surroundingVotesDetected . Inc ( )
2020-03-03 18:08:21 +00:00
return & ethpb . AttesterSlashing {
Attestation_1 : incomingAtt ,
Attestation_2 : att ,
} , nil
2020-05-13 06:37:24 +00:00
} else if isSurrounded {
2020-03-10 19:41:55 +00:00
surroundedVotesDetected . Inc ( )
2020-03-03 18:08:21 +00:00
return & ethpb . AttesterSlashing {
2020-02-27 17:22:39 +00:00
Attestation_1 : att ,
Attestation_2 : incomingAtt ,
2020-03-03 18:08:21 +00:00
} , nil
2020-02-27 17:22:39 +00:00
}
}
2020-03-03 18:08:21 +00:00
return nil , errors . New ( "unexpected false positive in surround vote detection" )
}
2020-03-24 18:30:21 +00:00
// DetectDoubleProposals checks if the given signed beacon block is a slashable offense and returns the slashing.
func ( ds * Service ) DetectDoubleProposals ( ctx context . Context , incomingBlock * ethpb . SignedBeaconBlockHeader ) ( * ethpb . ProposerSlashing , error ) {
return ds . proposalsDetector . DetectDoublePropose ( ctx , incomingBlock )
2020-03-19 11:59:35 +00:00
}
2020-05-19 00:50:35 +00:00
// mapResultsToAtts handles any duplicate detections by ensuring they reuse the same pool of attestations, instead of re-checking the DB for the same data.
func ( ds * Service ) mapResultsToAtts ( ctx context . Context , results [ ] * types . DetectionResult ) ( map [ [ 32 ] byte ] [ ] * ethpb . IndexedAttestation , error ) {
ctx , span := trace . StartSpan ( ctx , "detection.mapResultsToAtts" )
defer span . End ( )
resultsToAtts := make ( map [ [ 32 ] byte ] [ ] * ethpb . IndexedAttestation )
for _ , result := range results {
resultKey := resultHash ( result )
if _ , ok := resultsToAtts [ resultKey ] ; ok {
continue
}
matchingAtts , err := ds . slasherDB . IndexedAttestationsWithPrefix ( ctx , result . SlashableEpoch , result . SigBytes [ : ] )
if err != nil {
return nil , err
}
resultsToAtts [ resultKey ] = matchingAtts
}
return resultsToAtts , nil
}
func resultHash ( result * types . DetectionResult ) [ 32 ] byte {
resultBytes := append ( bytesutil . Bytes8 ( result . SlashableEpoch ) , result . SigBytes [ : ] ... )
return hashutil . Hash ( resultBytes )
}
2020-04-14 20:27:03 +00:00
func isDoublePropose (
incomingBlockHeader * ethpb . SignedBeaconBlockHeader ,
prevBlockHeader * ethpb . SignedBeaconBlockHeader ,
) bool {
return incomingBlockHeader . Header . ProposerIndex == prevBlockHeader . Header . ProposerIndex &&
! bytes . Equal ( incomingBlockHeader . Signature , prevBlockHeader . Signature ) &&
incomingBlockHeader . Header . Slot == prevBlockHeader . Header . Slot
}
2020-03-03 18:08:21 +00:00
func isDoubleVote ( incomingAtt * ethpb . IndexedAttestation , prevAtt * ethpb . IndexedAttestation ) bool {
2020-05-12 20:55:17 +00:00
return ! attestationutil . AttDataIsEqual ( incomingAtt . Data , prevAtt . Data ) && incomingAtt . Data . Target . Epoch == prevAtt . Data . Target . Epoch
2020-02-27 17:22:39 +00:00
}
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
}