diff --git a/beacon-chain/db/slasherkv/slasher.go b/beacon-chain/db/slasherkv/slasher.go index b672d8cb2b..91225ab805 100644 --- a/beacon-chain/db/slasherkv/slasher.go +++ b/beacon-chain/db/slasherkv/slasher.go @@ -189,10 +189,10 @@ func (s *Store) CheckAttesterDoubleVotes( // Build the proof of double vote. slashAtt := &slashertypes.AttesterDoubleVote{ - ValidatorIndex: primitives.ValidatorIndex(valIdx), - Target: attToProcess.IndexedAttestation.Data.Target.Epoch, - PrevAttestationWrapper: existingAttRecord, - AttestationWrapper: attToProcess, + ValidatorIndex: primitives.ValidatorIndex(valIdx), + Target: attToProcess.IndexedAttestation.Data.Target.Epoch, + Wrapper_1: existingAttRecord, + Wrapper_2: attToProcess, } localDoubleVotes = append(localDoubleVotes, slashAtt) diff --git a/beacon-chain/db/slasherkv/slasher_test.go b/beacon-chain/db/slasherkv/slasher_test.go index 1c67630bd7..cbf6a52252 100644 --- a/beacon-chain/db/slasherkv/slasher_test.go +++ b/beacon-chain/db/slasherkv/slasher_test.go @@ -93,28 +93,28 @@ func TestStore_CheckAttesterDoubleVotes(t *testing.T) { wanted := []*slashertypes.AttesterDoubleVote{ { - ValidatorIndex: 0, - Target: 3, - PrevAttestationWrapper: createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{1}), - AttestationWrapper: createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{2}), + ValidatorIndex: 0, + Target: 3, + Wrapper_1: createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{1}), + Wrapper_2: createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{2}), }, { - ValidatorIndex: 1, - Target: 3, - PrevAttestationWrapper: createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{1}), - AttestationWrapper: createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{2}), + ValidatorIndex: 1, + Target: 3, + Wrapper_1: createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{1}), + Wrapper_2: createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{2}), }, { - ValidatorIndex: 2, - Target: 4, - PrevAttestationWrapper: createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{3}), - AttestationWrapper: createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{4}), + ValidatorIndex: 2, + Target: 4, + Wrapper_1: createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{3}), + Wrapper_2: createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{4}), }, { - ValidatorIndex: 3, - Target: 4, - PrevAttestationWrapper: createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{3}), - AttestationWrapper: createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{4}), + ValidatorIndex: 3, + Target: 4, + Wrapper_1: createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{3}), + Wrapper_2: createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{4}), }, } doubleVotes, err := beaconDB.CheckAttesterDoubleVotes(ctx, slashableAtts) @@ -126,8 +126,8 @@ func TestStore_CheckAttesterDoubleVotes(t *testing.T) { for i, double := range doubleVotes { require.DeepEqual(t, wanted[i].ValidatorIndex, double.ValidatorIndex) require.DeepEqual(t, wanted[i].Target, double.Target) - require.DeepEqual(t, wanted[i].PrevAttestationWrapper, double.PrevAttestationWrapper) - require.DeepEqual(t, wanted[i].AttestationWrapper, double.AttestationWrapper) + require.DeepEqual(t, wanted[i].Wrapper_1, double.Wrapper_1) + require.DeepEqual(t, wanted[i].Wrapper_2, double.Wrapper_2) } } diff --git a/beacon-chain/slasher/chunks.go b/beacon-chain/slasher/chunks.go index dd972b2f3c..f82f7ad17c 100644 --- a/beacon-chain/slasher/chunks.go +++ b/beacon-chain/slasher/chunks.go @@ -1,6 +1,7 @@ package slasher import ( + "bytes" "context" "fmt" "math" @@ -186,10 +187,10 @@ func (m *MinSpanChunksSlice) CheckSlashable( ctx context.Context, slasherDB db.SlasherDatabase, validatorIdx primitives.ValidatorIndex, - attestation *slashertypes.IndexedAttestationWrapper, + incomingAttWrapper *slashertypes.IndexedAttestationWrapper, ) (*ethpb.AttesterSlashing, error) { - sourceEpoch := attestation.IndexedAttestation.Data.Source.Epoch - targetEpoch := attestation.IndexedAttestation.Data.Target.Epoch + sourceEpoch := incomingAttWrapper.IndexedAttestation.Data.Source.Epoch + targetEpoch := incomingAttWrapper.IndexedAttestation.Data.Target.Epoch minTarget, err := chunkDataAtEpoch(m.params, m.data, validatorIdx, sourceEpoch) if err != nil { @@ -204,12 +205,12 @@ func (m *MinSpanChunksSlice) CheckSlashable( } // The incoming attestation surrounds an existing one. - existingAttRecord, err := slasherDB.AttestationRecordForValidator(ctx, validatorIdx, minTarget) + existingAttWrapper, err := slasherDB.AttestationRecordForValidator(ctx, validatorIdx, minTarget) if err != nil { return nil, errors.Wrapf(err, "could not get existing attestation record at target %d", minTarget) } - if existingAttRecord == nil { + if existingAttWrapper == nil { // This case should normally not happen. If this happen, it means we previously // recorded in our min/max DB an distance corresponding to an attestaiton, but WITHOUT // recording the attestation itself. As a consequence, we say there is no surrounding vote, @@ -223,7 +224,7 @@ func (m *MinSpanChunksSlice) CheckSlashable( return nil, nil } - if existingAttRecord.IndexedAttestation.Data.Source.Epoch <= sourceEpoch { + if existingAttWrapper.IndexedAttestation.Data.Source.Epoch <= sourceEpoch { // This case should normally not happen, since if we have targetEpoch > minTarget, // then there is at least one attestation we surround. // However, it can happens if we have multiple attestation with the same target @@ -233,10 +234,21 @@ func (m *MinSpanChunksSlice) CheckSlashable( } surroundingVotesTotal.Inc() - return ðpb.AttesterSlashing{ - Attestation_1: attestation.IndexedAttestation, - Attestation_2: existingAttRecord.IndexedAttestation, - }, nil + + slashing := ðpb.AttesterSlashing{ + Attestation_1: existingAttWrapper.IndexedAttestation, + Attestation_2: incomingAttWrapper.IndexedAttestation, + } + + // Ensure the attestation with the lower data root is the first attestation. + if bytes.Compare(existingAttWrapper.DataRoot[:], incomingAttWrapper.DataRoot[:]) > 0 { + slashing = ðpb.AttesterSlashing{ + Attestation_1: incomingAttWrapper.IndexedAttestation, + Attestation_2: existingAttWrapper.IndexedAttestation, + } + } + + return slashing, nil } // CheckSlashable takes in a validator index and an incoming attestation @@ -254,10 +266,10 @@ func (m *MaxSpanChunksSlice) CheckSlashable( ctx context.Context, slasherDB db.SlasherDatabase, validatorIdx primitives.ValidatorIndex, - attestation *slashertypes.IndexedAttestationWrapper, + incomingAttWrapper *slashertypes.IndexedAttestationWrapper, ) (*ethpb.AttesterSlashing, error) { - sourceEpoch := attestation.IndexedAttestation.Data.Source.Epoch - targetEpoch := attestation.IndexedAttestation.Data.Target.Epoch + sourceEpoch := incomingAttWrapper.IndexedAttestation.Data.Source.Epoch + targetEpoch := incomingAttWrapper.IndexedAttestation.Data.Target.Epoch maxTarget, err := chunkDataAtEpoch(m.params, m.data, validatorIdx, sourceEpoch) if err != nil { @@ -272,12 +284,12 @@ func (m *MaxSpanChunksSlice) CheckSlashable( } // The incoming attestation is surrounded by an existing one. - existingAttRecord, err := slasherDB.AttestationRecordForValidator(ctx, validatorIdx, maxTarget) + existingAttWrapper, err := slasherDB.AttestationRecordForValidator(ctx, validatorIdx, maxTarget) if err != nil { return nil, errors.Wrapf(err, "could not get existing attestation record at target %d", maxTarget) } - if existingAttRecord == nil { + if existingAttWrapper == nil { // This case should normally not happen. If this happen, it means we previously // recorded in our min/max DB an distance corresponding to an attestaiton, but WITHOUT // recording the attestation itself. As a consequence, we say there is no surrounded vote, @@ -291,7 +303,7 @@ func (m *MaxSpanChunksSlice) CheckSlashable( return nil, nil } - if existingAttRecord.IndexedAttestation.Data.Source.Epoch >= sourceEpoch { + if existingAttWrapper.IndexedAttestation.Data.Source.Epoch >= sourceEpoch { // This case should normally not happen, since if we have targetEpoch < maxTarget, // then there is at least one attestation that surrounds us. // However, it can happens if we have multiple attestation with the same target @@ -301,10 +313,21 @@ func (m *MaxSpanChunksSlice) CheckSlashable( } surroundedVotesTotal.Inc() - return ðpb.AttesterSlashing{ - Attestation_1: existingAttRecord.IndexedAttestation, - Attestation_2: attestation.IndexedAttestation, - }, nil + + slashing := ðpb.AttesterSlashing{ + Attestation_1: existingAttWrapper.IndexedAttestation, + Attestation_2: incomingAttWrapper.IndexedAttestation, + } + + // Ensure the attestation with the lower data root is the first attestation. + if bytes.Compare(existingAttWrapper.DataRoot[:], incomingAttWrapper.DataRoot[:]) > 0 { + slashing = ðpb.AttesterSlashing{ + Attestation_1: incomingAttWrapper.IndexedAttestation, + Attestation_2: existingAttWrapper.IndexedAttestation, + } + } + + return slashing, nil } // Update a min span chunk for a validator index starting at the current epoch, e_c, then updating diff --git a/beacon-chain/slasher/detect_attestations.go b/beacon-chain/slasher/detect_attestations.go index dff721fd6a..a9ae996aa6 100644 --- a/beacon-chain/slasher/detect_attestations.go +++ b/beacon-chain/slasher/detect_attestations.go @@ -1,12 +1,14 @@ package slasher import ( + "bytes" "context" "fmt" "time" "github.com/pkg/errors" slashertypes "github.com/prysmaticlabs/prysm/v4/beacon-chain/slasher/types" + fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" "github.com/sirupsen/logrus" @@ -17,14 +19,15 @@ import ( // found attester slashings to the caller. func (s *Service) checkSlashableAttestations( ctx context.Context, currentEpoch primitives.Epoch, atts []*slashertypes.IndexedAttestationWrapper, -) ([]*ethpb.AttesterSlashing, error) { +) (map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing, error) { totalStart := time.Now() - slashings := make([]*ethpb.AttesterSlashing, 0) + slashings := map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing{} // Double votes log.Debug("Checking for double votes") start := time.Now() + doubleVoteSlashings, err := s.checkDoubleVotes(ctx, atts) if err != nil { return nil, errors.Wrap(err, "could not check slashable double votes") @@ -32,7 +35,16 @@ func (s *Service) checkSlashableAttestations( log.WithField("elapsed", time.Since(start)).Debug("Done checking double votes") - slashings = append(slashings, doubleVoteSlashings...) + for root, slashing := range doubleVoteSlashings { + 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) @@ -44,12 +56,14 @@ func (s *Service) checkSlashableAttestations( for validatorChunkIndex, attestations := range groupedByValidatorChunkIndexAtts { // The fact that we use always slashertypes.MinSpan is probably the root cause of // https://github.com/prysmaticlabs/prysm/issues/13591 - attSlashings, err := s.checkSurrounds(ctx, attestations, slashertypes.MinSpan, currentEpoch, validatorChunkIndex) + surroundSlashings, err := s.checkSurrounds(ctx, attestations, slashertypes.MinSpan, currentEpoch, validatorChunkIndex) if err != nil { return nil, err } - slashings = append(slashings, attSlashings...) + for root, slashing := range surroundSlashings { + slashings[root] = slashing + } indices := s.params.validatorIndexesInChunk(validatorChunkIndex) for _, idx := range indices { @@ -96,13 +110,15 @@ func (s *Service) checkSurrounds( chunkKind slashertypes.ChunkKind, currentEpoch primitives.Epoch, validatorChunkIndex uint64, -) ([]*ethpb.AttesterSlashing, error) { +) (map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing, error) { // Map of updated chunks by chunk index, which will be saved at the end. updatedChunks := make(map[uint64]Chunker) groupedByChunkIndexAtts := s.groupByChunkIndex(attestations) validatorIndexes := s.params.validatorIndexesInChunk(validatorChunkIndex) - // Update the min/max span chunks for the change of current epoch. + slashings := map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing{} + + // Update epoch for validators. for _, validatorIndex := range validatorIndexes { // This function modifies `updatedChunks` in place. if err := s.epochUpdateForValidator(ctx, updatedChunks, validatorChunkIndex, chunkKind, currentEpoch, validatorIndex); err != nil { @@ -110,47 +126,134 @@ func (s *Service) checkSurrounds( } } - // Update min and max spans and retrieve any detected slashable offenses. + // Check for surrounding votes. surroundingSlashings, err := s.updateSpans(ctx, updatedChunks, groupedByChunkIndexAtts, slashertypes.MinSpan, validatorChunkIndex, currentEpoch) if err != nil { return nil, errors.Wrapf(err, "could not update min attestation spans for validator chunk index %d", validatorChunkIndex) } + for root, slashing := range surroundingSlashings { + slashings[root] = slashing + } + + // Check for surrounded votes. surroundedSlashings, err := s.updateSpans(ctx, updatedChunks, groupedByChunkIndexAtts, slashertypes.MaxSpan, validatorChunkIndex, currentEpoch) if err != nil { return nil, errors.Wrapf(err, "could not update max attestation spans for validator chunk index %d", validatorChunkIndex) } - slashings := make([]*ethpb.AttesterSlashing, 0, len(surroundingSlashings)+len(surroundedSlashings)) - slashings = append(slashings, surroundingSlashings...) - slashings = append(slashings, surroundedSlashings...) + for root, slashing := range surroundedSlashings { + slashings[root] = slashing + } + if err := s.saveUpdatedChunks(ctx, updatedChunks, chunkKind, validatorChunkIndex); err != nil { return nil, err } + return slashings, nil } // Check for double votes in our database given a list of incoming attestations. func (s *Service) checkDoubleVotes( - ctx context.Context, attestations []*slashertypes.IndexedAttestationWrapper, -) ([]*ethpb.AttesterSlashing, error) { + 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 := ðpb.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 = ðpb.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") } - doubleVoteSlashings := make([]*ethpb.AttesterSlashing, 0) + for _, doubleVote := range doubleVotes { doubleVotesTotal.Inc() - doubleVoteSlashings = append(doubleVoteSlashings, ðpb.AttesterSlashing{ - Attestation_1: doubleVote.PrevAttestationWrapper.IndexedAttestation, - Attestation_2: doubleVote.AttestationWrapper.IndexedAttestation, - }) + + wrapper_1 := doubleVote.Wrapper_1 + wrapper_2 := doubleVote.Wrapper_2 + + slashing := ðpb.AttesterSlashing{ + Attestation_1: wrapper_1.IndexedAttestation, + Attestation_2: wrapper_2.IndexedAttestation, + } + + // Ensure the attestation with the lower data root is the first attestation. + if bytes.Compare(wrapper_1.DataRoot[:], wrapper_2.DataRoot[:]) > 0 { + slashing = ðpb.AttesterSlashing{ + Attestation_1: wrapper_2.IndexedAttestation, + Attestation_2: wrapper_1.IndexedAttestation, + } + } + + root, err := slashing.HashTreeRoot() + if err != nil { + return nil, errors.Wrap(err, "could not hash tree root for attester slashing") + } + + slashings[root] = slashing } - return doubleVoteSlashings, nil + + return slashings, nil } // This function updates `updatedChunks`, representing the slashing spans for a given validator for @@ -215,20 +318,21 @@ func (s *Service) epochUpdateForValidator( func (s *Service) updateSpans( ctx context.Context, updatedChunks map[uint64]Chunker, - attestationsByChunkIdx map[uint64][]*slashertypes.IndexedAttestationWrapper, + attWrapperByChunkIdx map[uint64][]*slashertypes.IndexedAttestationWrapper, kind slashertypes.ChunkKind, validatorChunkIndex uint64, currentEpoch primitives.Epoch, -) ([]*ethpb.AttesterSlashing, error) { +) (map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing, error) { ctx, span := trace.StartSpan(ctx, "Slasher.updateSpans") defer span.End() // Apply the attestations to the related chunks and find any // slashings along the way. - slashings := make([]*ethpb.AttesterSlashing, 0) - for _, attestations := range attestationsByChunkIdx { - for _, attestation := range attestations { - for _, validatorIdx := range attestation.IndexedAttestation.AttestingIndices { + slashings := map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing{} + + for _, attWrappers := range attWrapperByChunkIdx { + for _, attWrapper := range attWrappers { + for _, validatorIdx := range attWrapper.IndexedAttestation.AttestingIndices { validatorIndex := primitives.ValidatorIndex(validatorIdx) computedValidatorChunkIdx := s.params.validatorChunkIndex(validatorIndex) @@ -246,7 +350,7 @@ func (s *Service) updateSpans( } slashing, err := s.applyAttestationForValidator( - ctx, updatedChunks, attestation, kind, validatorChunkIndex, validatorIndex, currentEpoch, + ctx, updatedChunks, attWrapper, kind, validatorChunkIndex, validatorIndex, currentEpoch, ) if err != nil { @@ -257,7 +361,12 @@ func (s *Service) updateSpans( continue } - slashings = append(slashings, slashing) + root, err := slashing.HashTreeRoot() + if err != nil { + return nil, errors.Wrap(err, "could not hash tree root for attester slashing") + } + + slashings[root] = slashing } } } diff --git a/beacon-chain/slasher/detect_attestations_test.go b/beacon-chain/slasher/detect_attestations_test.go index e22ae47a06..76cf7bd67e 100644 --- a/beacon-chain/slasher/detect_attestations_test.go +++ b/beacon-chain/slasher/detect_attestations_test.go @@ -12,6 +12,7 @@ import ( slashingsmock "github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/slashings/mock" slashertypes "github.com/prysmaticlabs/prysm/v4/beacon-chain/slasher/types" "github.com/prysmaticlabs/prysm/v4/beacon-chain/startup" + fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" "github.com/prysmaticlabs/prysm/v4/config/params" "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v4/crypto/bls" @@ -64,6 +65,26 @@ func Test_processAttestations(t *testing.T) { 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}}, @@ -72,35 +93,6 @@ 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}}, - // }, - // { - // 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{ @@ -144,20 +136,8 @@ func Test_processAttestations(t *testing.T) { }, expectedSlashingsInfo: []*slashingInfo{ { - attestationInfo_1: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - { - attestationInfo_1: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - { - attestationInfo_1: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - { - attestationInfo_1: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_1: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, }, }, @@ -180,20 +160,8 @@ func Test_processAttestations(t *testing.T) { }, expectedSlashingsInfo: []*slashingInfo{ { - attestationInfo_1: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - { - attestationInfo_1: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - { - attestationInfo_1: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - { - attestationInfo_1: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_1: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, }, }, @@ -214,10 +182,6 @@ func Test_processAttestations(t *testing.T) { attestationInfo_1: &attestationInfo{source: 0, target: 1000, indices: []uint64{0}, beaconBlockRoot: nil}, attestationInfo_2: &attestationInfo{source: 50, target: 51, indices: []uint64{0}, beaconBlockRoot: nil}, }, - { - attestationInfo_1: &attestationInfo{source: 0, target: 1000, indices: []uint64{0}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 50, target: 51, indices: []uint64{0}, beaconBlockRoot: nil}, - }, }, }, }, @@ -258,12 +222,8 @@ func Test_processAttestations(t *testing.T) { }, expectedSlashingsInfo: []*slashingInfo{ { - attestationInfo_1: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - { - attestationInfo_1: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_1: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, }, }, @@ -290,61 +250,6 @@ func Test_processAttestations(t *testing.T) { // attestationInfo_1: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, // attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, // }, - // { - // attestationInfo_1: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - // attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - // }, - // }, - // }, - // }, - // }, - { - name: "Detects surrounded vote (source 0, target 3), (source 1, target 2) - single step", - steps: []*step{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: []*slashingInfo{ - { - attestationInfo_1: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - { - attestationInfo_1: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - }, - }, - }, - }, - // Uncomment when https://github.com/prysmaticlabs/prysm/issues/13591 is fixed - // { - // name: "Detects surrounded vote (source 0, target 3), (source 1, target 2) - two steps", - // steps: []*step{ - // { - // currentEpoch: 4, - // attestationsInfo: []*attestationInfo{ - // {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - // }, - // expectedSlashingsInfo: nil, - // }, - // { - // currentEpoch: 4, - // attestationsInfo: []*attestationInfo{ - // {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - // }, - // expectedSlashingsInfo: []*slashingInfo{ - // { - // attestationInfo_1: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - // attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - // }, - // { - // attestationInfo_1: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - // attestationInfo_2: &attestationInfo{source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - // }, // }, // }, // }, @@ -363,6 +268,26 @@ func Test_processAttestations(t *testing.T) { 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}, @@ -371,35 +296,6 @@ 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}, - // }, - // { - // 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{ @@ -748,10 +644,11 @@ func Test_processAttestations(t *testing.T) { } // Build expected attester slashings. - expectedSlashings := make([]*ethpb.AttesterSlashing, 0, len(step.expectedSlashingsInfo)) + expectedSlashings := make(map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing, len(step.expectedSlashingsInfo)) + for _, slashingInfo := range step.expectedSlashingsInfo { // Create attestations. - attestation_1 := createAttestationWrapper( + wrapper_1 := createAttestationWrapper( t, domain, privateKeys, @@ -761,7 +658,7 @@ func Test_processAttestations(t *testing.T) { slashingInfo.attestationInfo_1.beaconBlockRoot, ) - attestation_2 := createAttestationWrapper( + wrapper_2 := createAttestationWrapper( t, domain, privateKeys, @@ -772,13 +669,16 @@ func Test_processAttestations(t *testing.T) { ) // Create the attester slashing. - attesterSlashing := ðpb.AttesterSlashing{ - Attestation_1: attestation_1.IndexedAttestation, - Attestation_2: attestation_2.IndexedAttestation, + expectedSlashing := ðpb.AttesterSlashing{ + Attestation_1: wrapper_1.IndexedAttestation, + Attestation_2: wrapper_2.IndexedAttestation, } - // Add the attester slashing to the list. - expectedSlashings = append(expectedSlashings, attesterSlashing) + root, err := expectedSlashing.HashTreeRoot() + require.NoError(t, err, "failed to hash tree root") + + // Add the attester slashing to the map. + expectedSlashings[root] = expectedSlashing } // Get the currentSlot for the current epoch. @@ -789,7 +689,18 @@ func Test_processAttestations(t *testing.T) { processedSlashings := slasherService.processAttestations(ctx, attestationWrappers, currentSlot) // Check the processed slashings correspond to the expected slashings. - assert.DeepSSZEqual(t, expectedSlashings, processedSlashings) + require.Equal(t, len(expectedSlashings), len(processedSlashings), "processed slashings count not equal to expected") + + for root := range expectedSlashings { + // Check the expected slashing is in the processed slashings. + processedSlashing, ok := processedSlashings[root] + require.Equal(t, true, ok, "processed slashing not found") + + // Check the root matches + controlRoot, err := processedSlashing.HashTreeRoot() + require.NoError(t, err, "failed to hash tree root") + require.Equal(t, root, controlRoot, "root not equal") + } } }) } @@ -1113,49 +1024,6 @@ func Test_applyAttestationForValidator_MaxSpanChunk(t *testing.T) { require.NotNil(t, slashing) } -func Test_checkDoubleVotes_SlashableAttestationsOnDisk(t *testing.T) { - slasherDB := dbtest.SetupSlasherDB(t) - ctx := context.Background() - // For a list of input attestations, check that we can - // indeed check there could exist a double vote offense - // within the list with respect to previous entries in the db. - prevAtts := []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 1, []uint64{1, 2}, []byte{1}), - createAttestationWrapperEmptySig(t, 0, 2, []uint64{1, 2}, []byte{1}), - } - srv, err := New(context.Background(), - &ServiceConfig{ - Database: slasherDB, - StateNotifier: &mock.MockStateNotifier{}, - ClockWaiter: startup.NewClockSynchronizer(), - }) - require.NoError(t, err) - - err = slasherDB.SaveAttestationRecordsForValidators(ctx, prevAtts) - require.NoError(t, err) - - prev1 := createAttestationWrapperEmptySig(t, 0, 2, []uint64{1, 2}, []byte{1}) - cur1 := createAttestationWrapperEmptySig(t, 0, 2, []uint64{1, 2}, []byte{2}) - prev2 := createAttestationWrapperEmptySig(t, 0, 2, []uint64{1, 2}, []byte{1}) - cur2 := createAttestationWrapperEmptySig(t, 0, 2, []uint64{1, 2}, []byte{2}) - wanted := []*ethpb.AttesterSlashing{ - { - Attestation_1: prev1.IndexedAttestation, - Attestation_2: cur1.IndexedAttestation, - }, - { - Attestation_1: prev2.IndexedAttestation, - Attestation_2: cur2.IndexedAttestation, - }, - } - newAtts := []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 2, []uint64{1, 2}, []byte{2}), // Different signing root. - } - slashings, err := srv.checkDoubleVotes(ctx, newAtts) - require.NoError(t, err) - require.DeepEqual(t, wanted, slashings) -} - func Test_loadChunks_MinSpans(t *testing.T) { testLoadChunks(t, slashertypes.MinSpan) } diff --git a/beacon-chain/slasher/process_slashings.go b/beacon-chain/slasher/process_slashings.go index f3b18a86eb..2337ae9144 100644 --- a/beacon-chain/slasher/process_slashings.go +++ b/beacon-chain/slasher/process_slashings.go @@ -5,14 +5,17 @@ import ( "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/blocks" + fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" ) // Verifies attester slashings, logs them, and submits them to the slashing operations pool // in the beacon node if they pass validation. -func (s *Service) processAttesterSlashings(ctx context.Context, slashings []*ethpb.AttesterSlashing) ([]*ethpb.AttesterSlashing, error) { - processedSlashings := make([]*ethpb.AttesterSlashing, 0, len(slashings)) +func (s *Service) processAttesterSlashings( + ctx context.Context, slashings map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing, +) (map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing, error) { + processedSlashings := map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing{} // If no slashings, return early. if len(slashings) == 0 { @@ -25,7 +28,7 @@ func (s *Service) processAttesterSlashings(ctx context.Context, slashings []*eth return nil, errors.Wrap(err, "could not get head state") } - for _, slashing := range slashings { + for root, slashing := range slashings { // Verify the signature of the first attestation. if err := s.verifyAttSignature(ctx, slashing.Attestation_1); err != nil { log.WithError(err).WithField("a", slashing.Attestation_1).Warn( @@ -50,7 +53,7 @@ func (s *Service) processAttesterSlashings(ctx context.Context, slashings []*eth log.WithError(err).Error("Could not insert attester slashing into operations pool") } - processedSlashings = append(processedSlashings, slashing) + processedSlashings[root] = slashing } return processedSlashings, nil diff --git a/beacon-chain/slasher/process_slashings_test.go b/beacon-chain/slasher/process_slashings_test.go index 64b1f8e507..a71b5bbc49 100644 --- a/beacon-chain/slasher/process_slashings_test.go +++ b/beacon-chain/slasher/process_slashings_test.go @@ -10,6 +10,7 @@ import ( doublylinkedtree "github.com/prysmaticlabs/prysm/v4/beacon-chain/forkchoice/doubly-linked-tree" slashingsmock "github.com/prysmaticlabs/prysm/v4/beacon-chain/operations/slashings/mock" "github.com/prysmaticlabs/prysm/v4/beacon-chain/state/stategen" + fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" "github.com/prysmaticlabs/prysm/v4/config/params" "github.com/prysmaticlabs/prysm/v4/crypto/bls" "github.com/prysmaticlabs/prysm/v4/encoding/bytesutil" @@ -75,11 +76,16 @@ func TestService_processAttesterSlashings(t *testing.T) { firstAtt.Signature = signature.Marshal() secondAtt.Signature = make([]byte, 96) - slashings := []*ethpb.AttesterSlashing{ - { - Attestation_1: firstAtt, - Attestation_2: secondAtt, - }, + slashing := ðpb.AttesterSlashing{ + Attestation_1: firstAtt, + Attestation_2: secondAtt, + } + + root, err := slashing.HashTreeRoot() + require.NoError(tt, err, "failed to hash tree root") + + slashings := map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing{ + root: slashing, } _, err = s.processAttesterSlashings(ctx, slashings) @@ -94,11 +100,16 @@ func TestService_processAttesterSlashings(t *testing.T) { firstAtt.Signature = make([]byte, 96) secondAtt.Signature = signature.Marshal() - slashings := []*ethpb.AttesterSlashing{ - { - Attestation_1: firstAtt, - Attestation_2: secondAtt, - }, + slashing := ðpb.AttesterSlashing{ + Attestation_1: firstAtt, + Attestation_2: secondAtt, + } + + root, err := slashing.HashTreeRoot() + require.NoError(tt, err, "failed to hash tree root") + + slashings := map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing{ + root: slashing, } _, err = s.processAttesterSlashings(ctx, slashings) @@ -113,11 +124,16 @@ func TestService_processAttesterSlashings(t *testing.T) { firstAtt.Signature = signature.Marshal() secondAtt.Signature = signature.Marshal() - slashings := []*ethpb.AttesterSlashing{ - { - Attestation_1: firstAtt, - Attestation_2: secondAtt, - }, + slashing := ðpb.AttesterSlashing{ + Attestation_1: firstAtt, + Attestation_2: secondAtt, + } + + root, err := slashing.HashTreeRoot() + require.NoError(tt, err, "failed to hash tree root") + + slashings := map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing{ + root: slashing, } _, err = s.processAttesterSlashings(ctx, slashings) diff --git a/beacon-chain/slasher/receive.go b/beacon-chain/slasher/receive.go index 766cc5fb90..bf8ebcb8c0 100644 --- a/beacon-chain/slasher/receive.go +++ b/beacon-chain/slasher/receive.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" slashertypes "github.com/prysmaticlabs/prysm/v4/beacon-chain/slasher/types" + fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams" "github.com/prysmaticlabs/prysm/v4/consensus-types/primitives" ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v4/time/slots" @@ -107,7 +108,7 @@ func (s *Service) processAttestations( ctx context.Context, attestations []*slashertypes.IndexedAttestationWrapper, currentSlot primitives.Slot, -) []*ethpb.AttesterSlashing { +) map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing { // Get the current epoch from the current slot. currentEpoch := slots.ToEpoch(currentSlot) @@ -138,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 { diff --git a/beacon-chain/slasher/types/types.go b/beacon-chain/slasher/types/types.go index c02fc9b6b4..dce6a4f301 100644 --- a/beacon-chain/slasher/types/types.go +++ b/beacon-chain/slasher/types/types.go @@ -24,10 +24,10 @@ type IndexedAttestationWrapper struct { // AttesterDoubleVote represents a double vote instance // which is a slashable event for attesters. type AttesterDoubleVote struct { - Target primitives.Epoch - ValidatorIndex primitives.ValidatorIndex - PrevAttestationWrapper *IndexedAttestationWrapper - AttestationWrapper *IndexedAttestationWrapper + Target primitives.Epoch + ValidatorIndex primitives.ValidatorIndex + Wrapper_1 *IndexedAttestationWrapper + Wrapper_2 *IndexedAttestationWrapper } // DoubleBlockProposal containing an incoming and an existing proposal's signing root. diff --git a/testing/slasher/simulator/BUILD.bazel b/testing/slasher/simulator/BUILD.bazel index b77a58618a..f0ecc62a03 100644 --- a/testing/slasher/simulator/BUILD.bazel +++ b/testing/slasher/simulator/BUILD.bazel @@ -32,6 +32,7 @@ go_library( "//encoding/bytesutil:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "//time/slots:go_default_library", + "@com_github_pkg_errors//:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", ], ) diff --git a/testing/slasher/simulator/attestation_generator.go b/testing/slasher/simulator/attestation_generator.go index ec3e009645..90e8a87c97 100644 --- a/testing/slasher/simulator/attestation_generator.go +++ b/testing/slasher/simulator/attestation_generator.go @@ -1,9 +1,11 @@ package simulator import ( + "bytes" "context" "math" + "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/v4/beacon-chain/core/signing" "github.com/prysmaticlabs/prysm/v4/beacon-chain/state" @@ -88,10 +90,31 @@ func (s *Simulator) generateAttestationsForSlot( } slashableAtt.Signature = aggSig.Marshal() slashedIndices = append(slashedIndices, slashableAtt.AttestingIndices...) - slashings = append(slashings, ðpb.AttesterSlashing{ + + attDataRoot, err := att.Data.HashTreeRoot() + if err != nil { + return nil, nil, errors.Wrap(err, "cannot compte `att` hash tree root") + } + + slashableAttDataRoot, err := slashableAtt.Data.HashTreeRoot() + if err != nil { + return nil, nil, errors.Wrap(err, "cannot compte `slashableAtt` hash tree root") + } + + slashing := ðpb.AttesterSlashing{ Attestation_1: att, Attestation_2: slashableAtt, - }) + } + + // Ensure the attestation with the lower data root is the first attestation. + if bytes.Compare(attDataRoot[:], slashableAttDataRoot[:]) > 0 { + slashing = ðpb.AttesterSlashing{ + Attestation_1: slashableAtt, + Attestation_2: att, + } + } + + slashings = append(slashings, slashing) attestations = append(attestations, slashableAtt) } }