diff --git a/beacon-chain/slasher/BUILD.bazel b/beacon-chain/slasher/BUILD.bazel index b0f0391ac1..52c5283363 100644 --- a/beacon-chain/slasher/BUILD.bazel +++ b/beacon-chain/slasher/BUILD.bazel @@ -41,6 +41,7 @@ go_library( "//encoding/bytesutil:go_default_library", "//monitoring/tracing/trace:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//runtime/version:go_default_library", "//time/slots:go_default_library", "@com_github_pkg_errors//:go_default_library", "@com_github_prometheus_client_golang//prometheus:go_default_library", @@ -83,6 +84,7 @@ go_test( "//crypto/bls/common:go_default_library", "//encoding/bytesutil:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//runtime/version:go_default_library", "//testing/assert:go_default_library", "//testing/require:go_default_library", "//testing/util:go_default_library", diff --git a/beacon-chain/slasher/chunks.go b/beacon-chain/slasher/chunks.go index 01b5943c66..0c371c82cc 100644 --- a/beacon-chain/slasher/chunks.go +++ b/beacon-chain/slasher/chunks.go @@ -11,6 +11,7 @@ import ( slashertypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/slasher/types" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/sirupsen/logrus" ) @@ -232,6 +233,43 @@ func (m *MinSpanChunksSlice) CheckSlashable( surroundingVotesTotal.Inc() + // Both attestations should have the same type. If not, we convert both to Electra attestations. + unifyAttWrapperVersion(existingAttWrapper, incomingAttWrapper) + + postElectra := existingAttWrapper.IndexedAttestation.Version() >= version.Electra + if postElectra { + existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + if !ok { + return nil, fmt.Errorf( + "existing attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestationElectra{}, + existingAttWrapper.IndexedAttestation, + ) + } + incoming, ok := incomingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + if !ok { + return nil, fmt.Errorf( + "incoming attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestationElectra{}, + incomingAttWrapper.IndexedAttestation, + ) + } + slashing := ðpb.AttesterSlashingElectra{ + Attestation_1: existing, + Attestation_2: incoming, + } + + // Ensure the attestation with the lower data root is the first attestation. + if bytes.Compare(existingAttWrapper.DataRoot[:], incomingAttWrapper.DataRoot[:]) > 0 { + slashing = ðpb.AttesterSlashingElectra{ + Attestation_1: incoming, + Attestation_2: existing, + } + } + + return slashing, nil + } + existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestation) if !ok { return nil, fmt.Errorf( @@ -328,6 +366,43 @@ func (m *MaxSpanChunksSlice) CheckSlashable( surroundedVotesTotal.Inc() + // Both attestations should have the same type. If not, we convert the non-Electra attestation into an Electra attestation. + unifyAttWrapperVersion(existingAttWrapper, incomingAttWrapper) + + postElectra := existingAttWrapper.IndexedAttestation.Version() >= version.Electra + if postElectra { + existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + if !ok { + return nil, fmt.Errorf( + "existing attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestationElectra{}, + existingAttWrapper.IndexedAttestation, + ) + } + incoming, ok := incomingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + if !ok { + return nil, fmt.Errorf( + "incoming attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestationElectra{}, + incomingAttWrapper.IndexedAttestation, + ) + } + slashing := ðpb.AttesterSlashingElectra{ + Attestation_1: existing, + Attestation_2: incoming, + } + + // Ensure the attestation with the lower data root is the first attestation. + if bytes.Compare(existingAttWrapper.DataRoot[:], incomingAttWrapper.DataRoot[:]) > 0 { + slashing = ðpb.AttesterSlashingElectra{ + Attestation_1: incoming, + Attestation_2: existing, + } + } + + return slashing, nil + } + existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestation) if !ok { return nil, fmt.Errorf( diff --git a/beacon-chain/slasher/chunks_test.go b/beacon-chain/slasher/chunks_test.go index 7fc1ab7a65..0a66bcbf7c 100644 --- a/beacon-chain/slasher/chunks_test.go +++ b/beacon-chain/slasher/chunks_test.go @@ -3,12 +3,14 @@ package slasher import ( "context" "math" + "reflect" "testing" dbtest "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing" slashertypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/slasher/types" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/assert" "github.com/prysmaticlabs/prysm/v5/testing/require" ) @@ -82,6 +84,99 @@ func TestMaxSpanChunksSlice_MaxChunkSpanFrom(t *testing.T) { func TestMinSpanChunksSlice_CheckSlashable(t *testing.T) { ctx := context.Background() + + for _, v := range []int{version.Phase0, version.Electra} { + t.Run(version.String(v), func(t *testing.T) { + slasherDB := dbtest.SetupSlasherDB(t) + params := &Parameters{ + chunkSize: 3, + validatorChunkSize: 2, + historyLength: 3, + } + validatorIdx := primitives.ValidatorIndex(1) + source := primitives.Epoch(1) + target := primitives.Epoch(2) + att := createAttestationWrapperEmptySig(t, v, source, target, nil, nil) + + // A faulty chunk should lead to error. + chunk := &MinSpanChunksSlice{ + params: params, + data: []uint16{}, + } + _, err := chunk.CheckSlashable(ctx, nil, validatorIdx, att) + require.ErrorContains(t, "could not get min target for validator", err) + + // We initialize a proper slice with 2 chunks with chunk size 3, 2 validators, and + // a history length of 3 representing a perfect attesting history. + // + // val0 val1 + // { } { } + // [2, 2, 2, 2, 2, 2] + data := []uint16{2, 2, 2, 2, 2, 2} + chunk, err = MinChunkSpansSliceFrom(params, data) + require.NoError(t, err) + + // An attestation with source 1 and target 2 should not be slashable + // based on our min chunk for either validator. + slashing, err := chunk.CheckSlashable(ctx, slasherDB, validatorIdx, att) + require.NoError(t, err) + require.Equal(t, nil, slashing) + + slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx.Sub(1), att) + require.NoError(t, err) + require.Equal(t, nil, slashing) + + // Next up we initialize an empty chunks slice and mark an attestation + // with (source 1, target 2) as attested. + chunk = EmptyMinSpanChunksSlice(params) + source = primitives.Epoch(1) + target = primitives.Epoch(2) + att = createAttestationWrapperEmptySig(t, v, source, target, nil, nil) + chunkIndex := uint64(0) + startEpoch := target + currentEpoch := target + _, err = chunk.Update(chunkIndex, currentEpoch, validatorIdx, startEpoch, target) + require.NoError(t, err) + + // Next up, we create a surrounding vote, but it should NOT be slashable + // because we DO NOT have an existing attestation record in our database at the min target epoch. + source = primitives.Epoch(0) + target = primitives.Epoch(3) + surroundingVote := createAttestationWrapperEmptySig(t, v, source, target, nil, nil) + + slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundingVote) + require.NoError(t, err) + require.Equal(t, nil, slashing) + + // Next up, we save the old attestation record, then check if the + // surrounding vote is indeed slashable. + attData := att.IndexedAttestation.GetData() + attRecord := createAttestationWrapperEmptySig(t, v, attData.Source.Epoch, attData.Target.Epoch, []uint64{uint64(validatorIdx)}, []byte{1}) + err = slasherDB.SaveAttestationRecordsForValidators( + ctx, + []*slashertypes.IndexedAttestationWrapper{attRecord}, + ) + require.NoError(t, err) + + slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundingVote) + require.NoError(t, err) + require.Equal(t, false, reflect.ValueOf(slashing).IsNil()) + + // We check the attestation with the lower data root is the first attestation. + // Firstly we require the setup to have the surrounding vote as the second attestation. + // Then we modify the root of the surrounding vote and expect the vote to be the first attestation. + require.DeepEqual(t, surroundingVote.IndexedAttestation, slashing.SecondAttestation()) + surroundingVote.DataRoot = [32]byte{} + slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundingVote) + require.NoError(t, err) + require.Equal(t, false, reflect.ValueOf(slashing).IsNil()) + assert.DeepEqual(t, surroundingVote.IndexedAttestation, slashing.FirstAttestation()) + }) + } +} + +func TestMinSpanChunksSlice_CheckSlashable_DifferentVersions(t *testing.T) { + ctx := context.Background() slasherDB := dbtest.SetupSlasherDB(t) params := &Parameters{ chunkSize: 3, @@ -91,75 +186,138 @@ func TestMinSpanChunksSlice_CheckSlashable(t *testing.T) { validatorIdx := primitives.ValidatorIndex(1) source := primitives.Epoch(1) target := primitives.Epoch(2) - att := createAttestationWrapperEmptySig(t, source, target, nil, nil) - // A faulty chunk should lead to error. - chunk := &MinSpanChunksSlice{ - params: params, - data: []uint16{}, - } - _, err := chunk.CheckSlashable(ctx, nil, validatorIdx, att) - require.ErrorContains(t, "could not get min target for validator", err) + // We create a vote with Phase0 version. + att := createAttestationWrapperEmptySig(t, version.Phase0, source, target, nil, nil) - // We initialize a proper slice with 2 chunks with chunk size 3, 2 validators, and - // a history length of 3 representing a perfect attesting history. - // - // val0 val1 - // { } { } - // [2, 2, 2, 2, 2, 2] - data := []uint16{2, 2, 2, 2, 2, 2} - chunk, err = MinChunkSpansSliceFrom(params, data) - require.NoError(t, err) - - // An attestation with source 1 and target 2 should not be slashable - // based on our min chunk for either validator. - slashing, err := chunk.CheckSlashable(ctx, slasherDB, validatorIdx, att) - require.NoError(t, err) - require.Equal(t, nil, slashing) - - slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx.Sub(1), att) - require.NoError(t, err) - require.Equal(t, nil, slashing) - - // Next up we initialize an empty chunks slice and mark an attestation - // with (source 1, target 2) as attested. - chunk = EmptyMinSpanChunksSlice(params) - source = primitives.Epoch(1) - target = primitives.Epoch(2) - att = createAttestationWrapperEmptySig(t, source, target, nil, nil) + // We initialize an empty chunks slice and mark an attestation with (source 1, target 2) as attested. + chunk := EmptyMinSpanChunksSlice(params) chunkIndex := uint64(0) startEpoch := target currentEpoch := target - _, err = chunk.Update(chunkIndex, currentEpoch, validatorIdx, startEpoch, target) + _, err := chunk.Update(chunkIndex, currentEpoch, validatorIdx, startEpoch, target) require.NoError(t, err) - // Next up, we create a surrounding vote, but it should NOT be slashable - // because we DO NOT have an existing attestation record in our database at the min target epoch. + // We create a surrounding vote with Electra version. source = primitives.Epoch(0) target = primitives.Epoch(3) - surroundingVote := createAttestationWrapperEmptySig(t, source, target, nil, nil) + surroundingVote := createAttestationWrapperEmptySig(t, version.Electra, source, target, nil, nil) - slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundingVote) - require.NoError(t, err) - require.Equal(t, nil, slashing) - - // Next up, we save the old attestation record, then check if the - // surrounding vote is indeed slashable. + // We save the old attestation record, then check if the surrounding vote is indeed slashable. attData := att.IndexedAttestation.GetData() - attRecord := createAttestationWrapperEmptySig(t, attData.Source.Epoch, attData.Target.Epoch, []uint64{uint64(validatorIdx)}, []byte{1}) + attRecord := createAttestationWrapperEmptySig(t, version.Phase0, attData.Source.Epoch, attData.Target.Epoch, []uint64{uint64(validatorIdx)}, []byte{1}) err = slasherDB.SaveAttestationRecordsForValidators( ctx, []*slashertypes.IndexedAttestationWrapper{attRecord}, ) require.NoError(t, err) - slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundingVote) + slashing, err := chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundingVote) require.NoError(t, err) - require.NotEqual(t, (*ethpb.AttesterSlashing)(nil), slashing) + // The old record should be converted to Electra and the resulting slashing should be an Electra slashing. + electraSlashing, ok := slashing.(*ethpb.AttesterSlashingElectra) + require.Equal(t, true, ok, "slashing has the wrong type") + assert.NotNil(t, electraSlashing) } func TestMaxSpanChunksSlice_CheckSlashable(t *testing.T) { ctx := context.Background() + + for _, v := range []int{version.Phase0, version.Electra} { + t.Run(version.String(v), func(t *testing.T) { + slasherDB := dbtest.SetupSlasherDB(t) + params := &Parameters{ + chunkSize: 4, + validatorChunkSize: 2, + historyLength: 4, + } + validatorIdx := primitives.ValidatorIndex(1) + source := primitives.Epoch(1) + target := primitives.Epoch(2) + att := createAttestationWrapperEmptySig(t, v, source, target, nil, nil) + + // A faulty chunk should lead to error. + chunk := &MaxSpanChunksSlice{ + params: params, + data: []uint16{}, + } + _, err := chunk.CheckSlashable(ctx, nil, validatorIdx, att) + require.ErrorContains(t, "could not get max target for validator", err) + + // We initialize a proper slice with 2 chunks with chunk size 4, 2 validators, and + // a history length of 4 representing a perfect attesting history. + // + // val0 val1 + // { } { } + // [0, 0, 0, 0, 0, 0, 0, 0] + data := []uint16{0, 0, 0, 0, 0, 0, 0, 0} + chunk, err = MaxChunkSpansSliceFrom(params, data) + require.NoError(t, err) + + // An attestation with source 1 and target 2 should not be slashable + // based on our max chunk for either validator. + slashing, err := chunk.CheckSlashable(ctx, slasherDB, validatorIdx, att) + require.NoError(t, err) + require.Equal(t, nil, slashing) + + slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx.Sub(1), att) + require.NoError(t, err) + require.Equal(t, nil, slashing) + + // Next up we initialize an empty chunks slice and mark an attestation + // with (source 0, target 3) as attested. + chunk = EmptyMaxSpanChunksSlice(params) + source = primitives.Epoch(0) + target = primitives.Epoch(3) + att = createAttestationWrapperEmptySig(t, v, source, target, nil, nil) + chunkIndex := uint64(0) + startEpoch := source + currentEpoch := target + _, err = chunk.Update(chunkIndex, currentEpoch, validatorIdx, startEpoch, target) + require.NoError(t, err) + + // Next up, we create a surrounded vote, but it should NOT be slashable + // because we DO NOT have an existing attestation record in our database at the max target epoch. + source = primitives.Epoch(1) + target = primitives.Epoch(2) + surroundedVote := createAttestationWrapperEmptySig(t, v, source, target, nil, nil) + + slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundedVote) + require.NoError(t, err) + require.Equal(t, nil, slashing) + + // Next up, we save the old attestation record, then check if the + // surroundedVote vote is indeed slashable. + attData := att.IndexedAttestation.GetData() + signingRoot := [32]byte{1} + attRecord := createAttestationWrapperEmptySig( + t, v, attData.Source.Epoch, attData.Target.Epoch, []uint64{uint64(validatorIdx)}, signingRoot[:], + ) + err = slasherDB.SaveAttestationRecordsForValidators( + ctx, + []*slashertypes.IndexedAttestationWrapper{attRecord}, + ) + require.NoError(t, err) + + slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundedVote) + require.NoError(t, err) + require.Equal(t, false, reflect.ValueOf(slashing).IsNil()) + + // We check the attestation with the lower data root is the first attestation. + // Firstly we require the setup to have the surrounded vote as the second attestation. + // Then we modify the root of the surrounded vote and expect the vote to be the first attestation. + require.DeepEqual(t, surroundedVote.IndexedAttestation, slashing.SecondAttestation()) + surroundedVote.DataRoot = [32]byte{} + slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundedVote) + require.NoError(t, err) + require.Equal(t, false, reflect.ValueOf(slashing).IsNil()) + assert.DeepEqual(t, surroundedVote.IndexedAttestation, slashing.FirstAttestation()) + }) + } +} + +func TestMaxSpanChunksSlice_CheckSlashable_DifferentVersions(t *testing.T) { + ctx := context.Background() slasherDB := dbtest.SetupSlasherDB(t) params := &Parameters{ chunkSize: 4, @@ -167,76 +325,38 @@ func TestMaxSpanChunksSlice_CheckSlashable(t *testing.T) { historyLength: 4, } validatorIdx := primitives.ValidatorIndex(1) - source := primitives.Epoch(1) - target := primitives.Epoch(2) - att := createAttestationWrapperEmptySig(t, source, target, nil, nil) + source := primitives.Epoch(0) + target := primitives.Epoch(3) - // A faulty chunk should lead to error. - chunk := &MaxSpanChunksSlice{ - params: params, - data: []uint16{}, - } - _, err := chunk.CheckSlashable(ctx, nil, validatorIdx, att) - require.ErrorContains(t, "could not get max target for validator", err) + // We create a vote with Phase0 version. + att := createAttestationWrapperEmptySig(t, version.Phase0, source, target, nil, nil) - // We initialize a proper slice with 2 chunks with chunk size 4, 2 validators, and - // a history length of 4 representing a perfect attesting history. - // - // val0 val1 - // { } { } - // [0, 0, 0, 0, 0, 0, 0, 0] - data := []uint16{0, 0, 0, 0, 0, 0, 0, 0} - chunk, err = MaxChunkSpansSliceFrom(params, data) - require.NoError(t, err) - - // An attestation with source 1 and target 2 should not be slashable - // based on our max chunk for either validator. - slashing, err := chunk.CheckSlashable(ctx, slasherDB, validatorIdx, att) - require.NoError(t, err) - require.Equal(t, nil, slashing) - - slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx.Sub(1), att) - require.NoError(t, err) - require.Equal(t, nil, slashing) - - // Next up we initialize an empty chunks slice and mark an attestation - // with (source 0, target 3) as attested. - chunk = EmptyMaxSpanChunksSlice(params) - source = primitives.Epoch(0) - target = primitives.Epoch(3) - att = createAttestationWrapperEmptySig(t, source, target, nil, nil) + // We initialize an empty chunks slice and mark an attestation with (source 0, target 3) as attested. + chunk := EmptyMaxSpanChunksSlice(params) chunkIndex := uint64(0) - startEpoch := source - currentEpoch := target - _, err = chunk.Update(chunkIndex, currentEpoch, validatorIdx, startEpoch, target) + _, err := chunk.Update(chunkIndex, target, validatorIdx, source, target) require.NoError(t, err) - // Next up, we create a surrounded vote, but it should NOT be slashable - // because we DO NOT have an existing attestation record in our database at the max target epoch. + // We create a surrounded vote with Electra version. source = primitives.Epoch(1) target = primitives.Epoch(2) - surroundedVote := createAttestationWrapperEmptySig(t, source, target, nil, nil) + surroundedVote := createAttestationWrapperEmptySig(t, version.Electra, source, target, nil, nil) - slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundedVote) - require.NoError(t, err) - require.Equal(t, nil, slashing) - - // Next up, we save the old attestation record, then check if the - // surroundedVote vote is indeed slashable. + // We save the old attestation record, then check if the surrounded vote is indeed slashable. attData := att.IndexedAttestation.GetData() - signingRoot := [32]byte{1} - attRecord := createAttestationWrapperEmptySig( - t, attData.Source.Epoch, attData.Target.Epoch, []uint64{uint64(validatorIdx)}, signingRoot[:], - ) + attRecord := createAttestationWrapperEmptySig(t, version.Phase0, attData.Source.Epoch, attData.Target.Epoch, []uint64{uint64(validatorIdx)}, []byte{1}) err = slasherDB.SaveAttestationRecordsForValidators( ctx, []*slashertypes.IndexedAttestationWrapper{attRecord}, ) require.NoError(t, err) - slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundedVote) + slashing, err := chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundedVote) require.NoError(t, err) - require.NotEqual(t, (*ethpb.AttesterSlashing)(nil), slashing) + // The old record should be converted to Electra and the resulting slashing should be an Electra slashing. + electraSlashing, ok := slashing.(*ethpb.AttesterSlashingElectra) + require.Equal(t, true, ok, "slashing has wrong type") + assert.NotNil(t, electraSlashing) } func TestMinSpanChunksSlice_Update_MultipleChunks(t *testing.T) { diff --git a/beacon-chain/slasher/detect_attestations.go b/beacon-chain/slasher/detect_attestations.go index bbfc1238b2..550ca6120a 100644 --- a/beacon-chain/slasher/detect_attestations.go +++ b/beacon-chain/slasher/detect_attestations.go @@ -11,6 +11,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "golang.org/x/exp/maps" ) @@ -193,33 +194,69 @@ func (s *Service) checkDoubleVotes( // This is a double vote. doubleVotesTotal.Inc() - existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestation) - if !ok { - return nil, fmt.Errorf( - "existing attestation has wrong type (expected %T, got %T)", - ðpb.IndexedAttestation{}, - existingAttWrapper.IndexedAttestation, - ) - } - incoming, ok := incomingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestation) - if !ok { - return nil, fmt.Errorf( - "incoming attestation has wrong type (expected %T, got %T)", - ðpb.IndexedAttestation{}, - incomingAttWrapper.IndexedAttestation, - ) - } + var slashing ethpb.AttSlashing - slashing := ðpb.AttesterSlashing{ - Attestation_1: existing, - Attestation_2: incoming, - } + // Both attestations should have the same type. If not, we convert both to Electra attestations. + unifyAttWrapperVersion(existingAttWrapper, incomingAttWrapper) - // Ensure the attestation with the lower data root is the first attestation. - if bytes.Compare(existingAttWrapper.DataRoot[:], incomingAttWrapper.DataRoot[:]) > 0 { + postElectra := existingAttWrapper.IndexedAttestation.Version() >= version.Electra + if postElectra { + existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + if !ok { + return nil, fmt.Errorf( + "existing attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestationElectra{}, + existingAttWrapper.IndexedAttestation, + ) + } + incoming, ok := incomingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + if !ok { + return nil, fmt.Errorf( + "incoming attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestationElectra{}, + incomingAttWrapper.IndexedAttestation, + ) + } + slashing = ðpb.AttesterSlashingElectra{ + Attestation_1: existing, + Attestation_2: incoming, + } + + // Ensure the attestation with the lower data root is the first attestation. + if bytes.Compare(existingAttWrapper.DataRoot[:], incomingAttWrapper.DataRoot[:]) > 0 { + slashing = ðpb.AttesterSlashingElectra{ + Attestation_1: incoming, + Attestation_2: existing, + } + } + } else { + existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestation) + if !ok { + return nil, fmt.Errorf( + "existing attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestation{}, + existingAttWrapper.IndexedAttestation, + ) + } + incoming, ok := incomingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestation) + if !ok { + return nil, fmt.Errorf( + "incoming attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestation{}, + incomingAttWrapper.IndexedAttestation, + ) + } slashing = ðpb.AttesterSlashing{ - Attestation_1: incoming, - Attestation_2: existing, + Attestation_1: existing, + Attestation_2: incoming, + } + + // 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: incoming, + Attestation_2: existing, + } } } @@ -245,33 +282,69 @@ func (s *Service) checkDoubleVotes( wrapper_1 := doubleVote.Wrapper_1 wrapper_2 := doubleVote.Wrapper_2 - att_1, ok := wrapper_1.IndexedAttestation.(*ethpb.IndexedAttestation) - if !ok { - return nil, fmt.Errorf( - "first attestation has wrong type (expected %T, got %T)", - ðpb.IndexedAttestation{}, - wrapper_1.IndexedAttestation, - ) - } - att_2, ok := wrapper_2.IndexedAttestation.(*ethpb.IndexedAttestation) - if !ok { - return nil, fmt.Errorf( - "second attestation has wrong type (expected %T, got %T)", - ðpb.IndexedAttestation{}, - wrapper_2.IndexedAttestation, - ) - } + var slashing ethpb.AttSlashing - slashing := ðpb.AttesterSlashing{ - Attestation_1: att_1, - Attestation_2: att_2, - } + // Both attestations should have the same type. If not, we convert both to Electra attestations. + unifyAttWrapperVersion(wrapper_1, wrapper_2) - // Ensure the attestation with the lower data root is the first attestation. - if bytes.Compare(wrapper_1.DataRoot[:], wrapper_2.DataRoot[:]) > 0 { + postElectra := wrapper_1.IndexedAttestation.Version() >= version.Electra + if postElectra { + att_1, ok := wrapper_1.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + if !ok { + return nil, fmt.Errorf( + "first attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestationElectra{}, + wrapper_1.IndexedAttestation, + ) + } + att_2, ok := wrapper_2.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + if !ok { + return nil, fmt.Errorf( + "second attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestationElectra{}, + wrapper_2.IndexedAttestation, + ) + } + slashing = ðpb.AttesterSlashingElectra{ + Attestation_1: att_1, + Attestation_2: att_2, + } + + // Ensure the attestation with the lower data root is the first attestation. + if bytes.Compare(wrapper_1.DataRoot[:], wrapper_2.DataRoot[:]) > 0 { + slashing = ðpb.AttesterSlashingElectra{ + Attestation_1: att_2, + Attestation_2: att_1, + } + } + } else { + att_1, ok := wrapper_1.IndexedAttestation.(*ethpb.IndexedAttestation) + if !ok { + return nil, fmt.Errorf( + "first attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestation{}, + wrapper_1.IndexedAttestation, + ) + } + att_2, ok := wrapper_2.IndexedAttestation.(*ethpb.IndexedAttestation) + if !ok { + return nil, fmt.Errorf( + "second attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestation{}, + wrapper_2.IndexedAttestation, + ) + } slashing = ðpb.AttesterSlashing{ - Attestation_1: att_2, - Attestation_2: att_1, + Attestation_1: att_1, + Attestation_2: att_2, + } + + // 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: att_2, + Attestation_2: att_1, + } } } diff --git a/beacon-chain/slasher/detect_attestations_test.go b/beacon-chain/slasher/detect_attestations_test.go index 0026f36877..4c0fd84019 100644 --- a/beacon-chain/slasher/detect_attestations_test.go +++ b/beacon-chain/slasher/detect_attestations_test.go @@ -20,6 +20,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/crypto/bls/common" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/assert" "github.com/prysmaticlabs/prysm/v5/testing/require" "github.com/prysmaticlabs/prysm/v5/testing/util" @@ -30,6 +31,7 @@ import ( func Test_processAttestations(t *testing.T) { type ( attestationInfo struct { + ver int source primitives.Epoch target primitives.Epoch indices []uint64 @@ -37,6 +39,7 @@ func Test_processAttestations(t *testing.T) { } slashingInfo struct { + ver int attestationInfo_1 *attestationInfo attestationInfo_2 *attestationInfo } @@ -46,661 +49,740 @@ func Test_processAttestations(t *testing.T) { attestationsInfo []*attestationInfo expectedSlashingsInfo []*slashingInfo } + + test struct { + ver int + name string + steps []*step + } ) - tests := []struct { - name string - steps []*step - }{ - { - name: "Same target with different signing roots - single step", - steps: []*step{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, - {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{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, - {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Same target with same 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{1}}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Detects surrounding vote (source 1, target 2), (source 0, target 3) - single step", - steps: []*step{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - {source: 0, target: 3, 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: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - }, - }, - }, - }, - { - name: "Detects surrounding vote (source 1, target 2), (source 0, target 3) - 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: 3, 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: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - }, - }, - }, - }, - { - name: "Detects surrounding vote (source 50, target 51), (source 0, target 1000) - single step", - steps: []*step{ - { - currentEpoch: 1000, - attestationsInfo: []*attestationInfo{ - {source: 50, target: 51, indices: []uint64{0}, beaconBlockRoot: nil}, - {source: 0, target: 1000, indices: []uint64{0}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: []*slashingInfo{ - { - attestationInfo_1: &attestationInfo{source: 0, target: 1000, indices: []uint64{0}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 50, target: 51, indices: []uint64{0}, beaconBlockRoot: nil}, - }, - }, - }, - }, - }, - { - name: "Detects surrounding vote (source 50, target 51), (source 0, target 1000) - two steps", - steps: []*step{ - { - currentEpoch: 1000, - attestationsInfo: []*attestationInfo{ - {source: 50, target: 51, indices: []uint64{0}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - { - currentEpoch: 1000, - attestationsInfo: []*attestationInfo{ - {source: 0, target: 1000, indices: []uint64{0}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: []*slashingInfo{ - { - attestationInfo_1: &attestationInfo{source: 0, target: 1000, indices: []uint64{0}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 50, target: 51, indices: []uint64{0}, 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: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - }, - }, - }, - }, - { - 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: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - attestationInfo_2: &attestationInfo{source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - }, - }, - }, - }, - { - name: "Detects double vote, (source 1, target 2), (source 0, target 2) - single step", - steps: []*step{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - {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{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0}, beaconBlockRoot: nil}, - {source: 0, target: 3, indices: []uint64{1}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Not slashable, surrounding but non-overlapping attesting indices within same validator chunk index - two steps", - steps: []*step{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{1}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Not slashable, surrounded but non-overlapping attesting indices within same validator chunk index - single step", - steps: []*step{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - {source: 1, target: 2, indices: []uint64{2, 3}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Not slashable, surrounded but non-overlapping attesting indices within same validator chunk index - 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{2, 3}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Not slashable, surrounding but non-overlapping attesting indices in different validator chunk index - single step", - steps: []*step{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0}, beaconBlockRoot: nil}, - {source: 1, target: 2, indices: []uint64{params.BeaconConfig().MinGenesisActiveValidatorCount - 1}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Not slashable, surrounding but non-overlapping attesting indices in different validator chunk index - two steps", - steps: []*step{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{params.BeaconConfig().MinGenesisActiveValidatorCount - 1}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Not slashable, surrounded but non-overlapping attesting indices in different validator chunk index - single step", - steps: []*step{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0}, beaconBlockRoot: nil}, - {source: 1, target: 2, indices: []uint64{params.BeaconConfig().MinGenesisActiveValidatorCount - 1}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Not slashable, surrounded but non-overlapping attesting indices in different validator chunk index - two steps", - steps: []*step{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{params.BeaconConfig().MinGenesisActiveValidatorCount - 1}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Not slashable, (source 1, target 2), (source 2, target 3) - single step", - steps: []*step{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - {source: 2, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Not slashable, (source 1, target 2), (source 2, target 3) - 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: 2, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Not slashable, (source 0, target 3), (source 2, target 4) - single step", - steps: []*step{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - {source: 2, target: 4, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Not slashable, (source 0, target 3), (source 2, target 4) - 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: 2, target: 4, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Not slashable, (source 0, target 2), (source 0, target 3) - single step", - steps: []*step{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Not slashable, (source 0, target 2), (source 0, target 3) - two steps", - steps: []*step{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Not slashable, (source 0, target 3), (source 0, target 2) - single step", - steps: []*step{ - { - currentEpoch: 4, - attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - {source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - { - name: "Not slashable, (source 0, target 3), (source 0, 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: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - }, - expectedSlashingsInfo: nil, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Create context. - ctx := context.Background() + var tests []test - // Configure logging. - hook := logTest.NewGlobal() - defer hook.Reset() + for _, v := range []int{version.Phase0, version.Electra} { + tests = append(tests, []test{ + { + ver: v, + name: "Same target with different signing roots - single step", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, + {ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{2}}, + }, + expectedSlashingsInfo: []*slashingInfo{ + { + ver: v, + attestationInfo_1: &attestationInfo{ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, + attestationInfo_2: &attestationInfo{ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{2}}, + }, + }, + }, + }, + }, + { + ver: v, + name: "Same target with different signing roots - two steps", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, + }, + expectedSlashingsInfo: nil, + }, + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{2}}, + }, + expectedSlashingsInfo: []*slashingInfo{ + { + ver: v, + attestationInfo_1: &attestationInfo{ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, + attestationInfo_2: &attestationInfo{ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{2}}, + }, + }, + }, + }, + }, + { + ver: v, + name: "Same target with same signing roots - single step", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, + {ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Same target with same signing roots - two steps", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, + }, + expectedSlashingsInfo: nil, + }, + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Detects surrounding vote (source 1, target 2), (source 0, target 3) - single step", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: v, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: []*slashingInfo{ + { + ver: v, + attestationInfo_1: &attestationInfo{ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{ver: v, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + }, + }, + }, + }, + { + ver: v, + name: "Detects surrounding vote (source 1, target 2), (source 0, target 3) - two steps", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: []*slashingInfo{ + { + ver: v, + attestationInfo_1: &attestationInfo{ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{ver: v, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + }, + }, + }, + }, + { + ver: v, + name: "Detects surrounding vote (source 50, target 51), (source 0, target 1000) - single step", + steps: []*step{ + { + currentEpoch: 1000, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 50, target: 51, indices: []uint64{0}, beaconBlockRoot: nil}, + {ver: v, source: 0, target: 1000, indices: []uint64{0}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: []*slashingInfo{ + { + ver: v, + attestationInfo_1: &attestationInfo{ver: v, source: 0, target: 1000, indices: []uint64{0}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{ver: v, source: 50, target: 51, indices: []uint64{0}, beaconBlockRoot: nil}, + }, + }, + }, + }, + }, + { + ver: v, + name: "Detects surrounding vote (source 50, target 51), (source 0, target 1000) - two steps", + steps: []*step{ + { + currentEpoch: 1000, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 50, target: 51, indices: []uint64{0}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + { + currentEpoch: 1000, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 1000, indices: []uint64{0}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: []*slashingInfo{ + { + ver: v, + attestationInfo_1: &attestationInfo{ver: v, source: 0, target: 1000, indices: []uint64{0}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{ver: v, source: 50, target: 51, indices: []uint64{0}, beaconBlockRoot: nil}, + }, + }, + }, + }, + }, + { + ver: v, + name: "Detects surrounded vote (source 0, target 3), (source 1, target 2) - single step", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: []*slashingInfo{ + { + ver: v, + attestationInfo_1: &attestationInfo{ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{ver: v, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + }, + }, + }, + }, + { + ver: v, + name: "Detects surrounded vote (source 0, target 3), (source 1, target 2) - two steps", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: []*slashingInfo{ + { + ver: v, + attestationInfo_1: &attestationInfo{ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{ver: v, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + }, + }, + }, + }, + { + ver: v, + name: "Detects double vote, (source 1, target 2), (source 0, target 2) - single step", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: v, source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: []*slashingInfo{ + { + ver: v, + attestationInfo_1: &attestationInfo{ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{ver: v, source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + }, + }, + }, + }, + { + ver: v, + name: "Detects double vote, (source 1, target 2), (source 0, target 2) - two steps", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: []*slashingInfo{ + { + ver: v, + attestationInfo_1: &attestationInfo{ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{ver: v, source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + }, + }, + }, + }, + { + ver: v, + name: "Not slashable, surrounding but non-overlapping attesting indices within same validator chunk index - single step", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{0}, beaconBlockRoot: nil}, + {ver: v, source: 0, target: 3, indices: []uint64{1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Not slashable, surrounding but non-overlapping attesting indices within same validator chunk index - two steps", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{0}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 3, indices: []uint64{1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Not slashable, surrounded but non-overlapping attesting indices within same validator chunk index - single step", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: v, source: 1, target: 2, indices: []uint64{2, 3}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Not slashable, surrounded but non-overlapping attesting indices within same validator chunk index - two steps", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{2, 3}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Not slashable, surrounding but non-overlapping attesting indices in different validator chunk index - single step", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 3, indices: []uint64{0}, beaconBlockRoot: nil}, + {ver: v, source: 1, target: 2, indices: []uint64{params.BeaconConfig().MinGenesisActiveValidatorCount - 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Not slashable, surrounding but non-overlapping attesting indices in different validator chunk index - two steps", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 3, indices: []uint64{0}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{params.BeaconConfig().MinGenesisActiveValidatorCount - 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Not slashable, surrounded but non-overlapping attesting indices in different validator chunk index - single step", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 3, indices: []uint64{0}, beaconBlockRoot: nil}, + {ver: v, source: 1, target: 2, indices: []uint64{params.BeaconConfig().MinGenesisActiveValidatorCount - 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Not slashable, surrounded but non-overlapping attesting indices in different validator chunk index - two steps", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 3, indices: []uint64{0}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{params.BeaconConfig().MinGenesisActiveValidatorCount - 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Not slashable, (source 1, target 2), (source 2, target 3) - single step", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: v, source: 2, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Not slashable, (source 1, target 2), (source 2, target 3) - two steps", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 2, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Not slashable, (source 0, target 3), (source 2, target 4) - single step", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: v, source: 2, target: 4, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Not slashable, (source 0, target 3), (source 2, target 4) - two steps", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 2, target: 4, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Not slashable, (source 0, target 2), (source 0, target 3) - single step", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: v, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Not slashable, (source 0, target 2), (source 0, target 3) - two steps", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Not slashable, (source 0, target 3), (source 0, target 2) - single step", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: v, source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + { + ver: v, + name: "Not slashable, (source 0, target 3), (source 0, target 2) - two steps", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: v, source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + }, + expectedSlashingsInfo: nil, + }, + }, + }, + }...) - // Configure the slasher database. - slasherDB := dbtest.SetupSlasherDB(t) + tests = append(tests, test{ + name: "Different versions, same target with different signing roots - single step", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, + {ver: version.Electra, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{2}}, + }, + expectedSlashingsInfo: []*slashingInfo{ + { + ver: version.Electra, + attestationInfo_1: &attestationInfo{ver: version.Electra, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, + attestationInfo_2: &attestationInfo{ver: version.Electra, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{2}}, + }, + }, + }, + }, + }) - // Configure the beacon state. - beaconState, err := util.NewBeaconState() - require.NoError(t, err) + for _, tt := range tests { + name := version.String(tt.ver) + ": " + tt.name + t.Run(name, func(t *testing.T) { + // Create context. + ctx := context.Background() - // Create the mock chain service. - mockChain := &mock.ChainService{State: beaconState} + // Configure logging. + hook := logTest.NewGlobal() + defer hook.Reset() - // Create the mock slashing pool inserter. - mockSlashingPoolInserter := &slashingsmock.PoolMock{} + // Configure the slasher database. + slasherDB := dbtest.SetupSlasherDB(t) - // Create the service configuration. - serviceConfig := &ServiceConfig{ - Database: slasherDB, - HeadStateFetcher: mockChain, - AttestationStateFetcher: mockChain, - SlashingPoolInserter: mockSlashingPoolInserter, - } - - // Create the slasher service. - slasherService, err := New(context.Background(), serviceConfig) - require.NoError(t, err) - - // Initialize validators in the state. - numVals := params.BeaconConfig().MinGenesisActiveValidatorCount - validators := make([]*ethpb.Validator, numVals) - privateKeys := make([]bls.SecretKey, numVals) - - for i := uint64(0); i < numVals; i++ { - // Create a random private key. - privateKey, err := bls.RandKey() + // Configure the beacon state. + beaconState, err := util.NewBeaconState() require.NoError(t, err) - // Add the private key to the list. - privateKeys[i] = privateKey + // Create the mock chain service. + mockChain := &mock.ChainService{State: beaconState} - // Derive the public key from the private key. - publicKey := privateKey.PublicKey().Marshal() + // Create the mock slashing pool inserter. + mockSlashingPoolInserter := &slashingsmock.PoolMock{} - // Initialize the validator. - validator := ðpb.Validator{PublicKey: publicKey} - - // Add the validator to the list. - validators[i] = validator - } - - // Set the validators into the state. - err = beaconState.SetValidators(validators) - require.NoError(t, err) - - // Compute the signing domain. - domain, err := signing.Domain( - beaconState.Fork(), - 0, - params.BeaconConfig().DomainBeaconAttester, - beaconState.GenesisValidatorsRoot(), - ) - require.NoError(t, err) - - for _, step := range tt.steps { - // Build attestation wrappers. - attestationsCount := len(step.attestationsInfo) - attestationWrappers := make([]*slashertypes.IndexedAttestationWrapper, 0, attestationsCount) - for _, attestationInfo := range step.attestationsInfo { - // Create a wrapped attestation. - attestationWrapper := createAttestationWrapper( - t, - domain, - privateKeys, - attestationInfo.source, - attestationInfo.target, - attestationInfo.indices, - attestationInfo.beaconBlockRoot, - ) - - // Add the wrapped attestation to the list. - attestationWrappers = append(attestationWrappers, attestationWrapper) + // Create the service configuration. + serviceConfig := &ServiceConfig{ + Database: slasherDB, + HeadStateFetcher: mockChain, + AttestationStateFetcher: mockChain, + SlashingPoolInserter: mockSlashingPoolInserter, } - // Build expected attester slashings. - expectedSlashings := make(map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing, len(step.expectedSlashingsInfo)) + // Create the slasher service. + slasherService, err := New(context.Background(), serviceConfig) + require.NoError(t, err) - for _, slashingInfo := range step.expectedSlashingsInfo { - // Create attestations. - wrapper_1 := createAttestationWrapper( - t, - domain, - privateKeys, - slashingInfo.attestationInfo_1.source, - slashingInfo.attestationInfo_1.target, - slashingInfo.attestationInfo_1.indices, - slashingInfo.attestationInfo_1.beaconBlockRoot, - ) + // Initialize validators in the state. + numVals := params.BeaconConfig().MinGenesisActiveValidatorCount + validators := make([]*ethpb.Validator, numVals) + privateKeys := make([]bls.SecretKey, numVals) - wrapper_2 := createAttestationWrapper( - t, - domain, - privateKeys, - slashingInfo.attestationInfo_2.source, - slashingInfo.attestationInfo_2.target, - slashingInfo.attestationInfo_2.indices, - slashingInfo.attestationInfo_2.beaconBlockRoot, - ) + for i := uint64(0); i < numVals; i++ { + // Create a random private key. + privateKey, err := bls.RandKey() + require.NoError(t, err) - // Create the attester slashing. - expectedSlashing := ðpb.AttesterSlashing{ - Attestation_1: wrapper_1.IndexedAttestation.(*ethpb.IndexedAttestation), - Attestation_2: wrapper_2.IndexedAttestation.(*ethpb.IndexedAttestation), + // Add the private key to the list. + privateKeys[i] = privateKey + + // Derive the public key from the private key. + publicKey := privateKey.PublicKey().Marshal() + + // Initialize the validator. + validator := ðpb.Validator{PublicKey: publicKey} + + // Add the validator to the list. + validators[i] = validator + } + + // Set the validators into the state. + err = beaconState.SetValidators(validators) + require.NoError(t, err) + + // Compute the signing domain. + domain, err := signing.Domain( + beaconState.Fork(), + 0, + params.BeaconConfig().DomainBeaconAttester, + beaconState.GenesisValidatorsRoot(), + ) + require.NoError(t, err) + + for _, step := range tt.steps { + // Build attestation wrappers. + attestationsCount := len(step.attestationsInfo) + attestationWrappers := make([]*slashertypes.IndexedAttestationWrapper, 0, attestationsCount) + for _, attestationInfo := range step.attestationsInfo { + // Create a wrapped attestation. + attestationWrapper := createAttestationWrapper( + t, + attestationInfo.ver, + domain, + privateKeys, + attestationInfo.source, + attestationInfo.target, + attestationInfo.indices, + attestationInfo.beaconBlockRoot, + ) + + // Add the wrapped attestation to the list. + attestationWrappers = append(attestationWrappers, attestationWrapper) } - root, err := expectedSlashing.HashTreeRoot() - require.NoError(t, err, "failed to hash tree root") + // Build expected attester slashings. + expectedSlashings := make(map[[fieldparams.RootLength]byte]ethpb.AttSlashing, len(step.expectedSlashingsInfo)) - // Add the attester slashing to the map. - expectedSlashings[root] = expectedSlashing + for _, slashingInfo := range step.expectedSlashingsInfo { + // Create attestations. + wrapper_1 := createAttestationWrapper( + t, + slashingInfo.attestationInfo_1.ver, + domain, + privateKeys, + slashingInfo.attestationInfo_1.source, + slashingInfo.attestationInfo_1.target, + slashingInfo.attestationInfo_1.indices, + slashingInfo.attestationInfo_1.beaconBlockRoot, + ) + + wrapper_2 := createAttestationWrapper( + t, + slashingInfo.attestationInfo_2.ver, + domain, + privateKeys, + slashingInfo.attestationInfo_2.source, + slashingInfo.attestationInfo_2.target, + slashingInfo.attestationInfo_2.indices, + slashingInfo.attestationInfo_2.beaconBlockRoot, + ) + + // Create the attester slashing. + var expectedSlashing ethpb.AttSlashing + + if slashingInfo.ver >= version.Electra { + expectedSlashing = ðpb.AttesterSlashingElectra{ + Attestation_1: wrapper_1.IndexedAttestation.(*ethpb.IndexedAttestationElectra), + Attestation_2: wrapper_2.IndexedAttestation.(*ethpb.IndexedAttestationElectra), + } + } else { + expectedSlashing = ðpb.AttesterSlashing{ + Attestation_1: wrapper_1.IndexedAttestation.(*ethpb.IndexedAttestation), + Attestation_2: wrapper_2.IndexedAttestation.(*ethpb.IndexedAttestation), + } + } + + 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. + currentSlot, err := slots.EpochStart(step.currentEpoch) + require.NoError(t, err) + + // Process the attestations. + processedSlashings := slasherService.processAttestations(ctx, attestationWrappers, currentSlot) + + // Check the processed slashings correspond to the expected slashings. + 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") + } } - - // Get the currentSlot for the current epoch. - currentSlot, err := slots.EpochStart(step.currentEpoch) - require.NoError(t, err) - - // Process the attestations. - processedSlashings := slasherService.processAttestations(ctx, attestationWrappers, currentSlot) - - // Check the processed slashings correspond to the expected slashings. - 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") - } - } - }) + }) + } } } @@ -757,7 +839,7 @@ func Test_processQueuedAttestations_MultipleChunkIndices(t *testing.T) { } var sr [32]byte copy(sr[:], fmt.Sprintf("%d", i)) - att := createAttestationWrapperEmptySig(t, source, target, []uint64{0}, sr[:]) + att := createAttestationWrapperEmptySig(t, version.Phase0, source, target, []uint64{0}, sr[:]) s.attsQueue = newAttestationsQueue() s.attsQueue.push(att) slot, err := slots.EpochStart(i) @@ -814,8 +896,8 @@ func Test_processQueuedAttestations_OverlappingChunkIndices(t *testing.T) { }() // We create two attestations fully spanning chunk indices 0 and chunk 1 - att1 := createAttestationWrapperEmptySig(t, primitives.Epoch(slasherParams.chunkSize-2), primitives.Epoch(slasherParams.chunkSize), []uint64{0, 1}, nil) - att2 := createAttestationWrapperEmptySig(t, primitives.Epoch(slasherParams.chunkSize-1), primitives.Epoch(slasherParams.chunkSize+1), []uint64{0, 1}, nil) + att1 := createAttestationWrapperEmptySig(t, version.Phase0, primitives.Epoch(slasherParams.chunkSize-2), primitives.Epoch(slasherParams.chunkSize), []uint64{0, 1}, nil) + att2 := createAttestationWrapperEmptySig(t, version.Phase0, primitives.Epoch(slasherParams.chunkSize-1), primitives.Epoch(slasherParams.chunkSize+1), []uint64{0, 1}, nil) // We attempt to process the batch. s.attsQueue = newAttestationsQueue() @@ -1152,7 +1234,7 @@ func Test_applyAttestationForValidator_MinSpanChunk(t *testing.T) { // We apply attestation with (source 1, target 2) for our validator. source := primitives.Epoch(1) target := primitives.Epoch(2) - att := createAttestationWrapperEmptySig(t, source, target, nil, nil) + att := createAttestationWrapperEmptySig(t, version.Phase0, source, target, nil, nil) slashing, err := srv.applyAttestationForValidator( ctx, chunksByChunkIdx, @@ -1175,7 +1257,7 @@ func Test_applyAttestationForValidator_MinSpanChunk(t *testing.T) { // expect a slashable offense to be returned. source = primitives.Epoch(0) target = primitives.Epoch(3) - slashableAtt := createAttestationWrapperEmptySig(t, source, target, nil, nil) + slashableAtt := createAttestationWrapperEmptySig(t, version.Phase0, source, target, nil, nil) slashing, err = srv.applyAttestationForValidator( ctx, chunksByChunkIdx, @@ -1209,7 +1291,7 @@ func Test_applyAttestationForValidator_MaxSpanChunk(t *testing.T) { // We apply attestation with (source 0, target 3) for our validator. source := primitives.Epoch(0) target := primitives.Epoch(3) - att := createAttestationWrapperEmptySig(t, source, target, nil, nil) + att := createAttestationWrapperEmptySig(t, version.Phase0, source, target, nil, nil) slashing, err := srv.applyAttestationForValidator( ctx, chunksByChunkIdx, @@ -1232,7 +1314,7 @@ func Test_applyAttestationForValidator_MaxSpanChunk(t *testing.T) { // expect a slashable offense to be returned. source = primitives.Epoch(1) target = primitives.Epoch(2) - slashableAtt := createAttestationWrapperEmptySig(t, source, target, nil, nil) + slashableAtt := createAttestationWrapperEmptySig(t, version.Phase0, source, target, nil, nil) slashing, err = srv.applyAttestationForValidator( ctx, chunksByChunkIdx, @@ -1347,7 +1429,7 @@ func TestService_processQueuedAttestations(t *testing.T) { require.NoError(t, err) s.attsQueue.extend([]*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 1, []uint64{0, 1} /* indices */, nil /* signingRoot */), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 1, []uint64{0, 1} /* indices */, nil /* signingRoot */), }) ctx, cancel := context.WithCancel(context.Background()) tickerChan := make(chan primitives.Slot) @@ -1482,6 +1564,7 @@ func runAttestationsBenchmark(b *testing.B, s *Service, numAtts, numValidators u copy(signingRoot[:], fmt.Sprintf("%d", i)) atts[i] = createAttestationWrapperEmptySig( b, + version.Phase0, source, target, /* target */ indices, /* indices */ @@ -1546,7 +1629,7 @@ func Benchmark_checkSurroundVotes(b *testing.B) { // Create the attestation wrapper. // This benchmark assume that all validators produced the exact same head, source and target votes. - attWrapper := createAttestationWrapperEmptySig(b, sourceEpoch, targetEpoch, validatorIndexes, nil) + attWrapper := createAttestationWrapperEmptySig(b, version.Phase0, sourceEpoch, targetEpoch, validatorIndexes, nil) attWrappers := []*slashertypes.IndexedAttestationWrapper{attWrapper} // Run the benchmark. @@ -1566,6 +1649,7 @@ func Benchmark_checkSurroundVotes(b *testing.B) { // The signature of the returned wrapped attestation is empty. func createAttestationWrapperEmptySig( t testing.TB, + ver int, source, target primitives.Epoch, indices []uint64, beaconBlockRoot []byte, @@ -1585,6 +1669,17 @@ func createAttestationWrapperEmptySig( dataRoot, err := data.HashTreeRoot() require.NoError(t, err) + if ver >= version.Electra { + return &slashertypes.IndexedAttestationWrapper{ + IndexedAttestation: ðpb.IndexedAttestationElectra{ + AttestingIndices: indices, + Data: data, + Signature: params.BeaconConfig().EmptySignature[:], + }, + DataRoot: dataRoot, + } + } + return &slashertypes.IndexedAttestationWrapper{ IndexedAttestation: ðpb.IndexedAttestation{ AttestingIndices: indices, @@ -1601,6 +1696,7 @@ func createAttestationWrapperEmptySig( // if validatorIndice = indices[i], then the corresponding private key is privateKeys[validatorIndice]. func createAttestationWrapper( t testing.TB, + ver int, domain []byte, privateKeys []common.SecretKey, source, target primitives.Epoch, @@ -1648,6 +1744,17 @@ func createAttestationWrapper( signature := bls.AggregateSignatures(signatures).Marshal() // Create the attestation wrapper. + if ver >= version.Electra { + return &slashertypes.IndexedAttestationWrapper{ + IndexedAttestation: ðpb.IndexedAttestationElectra{ + AttestingIndices: indices, + Data: attestationData, + Signature: signature, + }, + DataRoot: attestationDataRoot, + } + } + return &slashertypes.IndexedAttestationWrapper{ IndexedAttestation: ðpb.IndexedAttestation{ AttestingIndices: indices, diff --git a/beacon-chain/slasher/helpers.go b/beacon-chain/slasher/helpers.go index 16115c46a3..e13ea04135 100644 --- a/beacon-chain/slasher/helpers.go +++ b/beacon-chain/slasher/helpers.go @@ -13,6 +13,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/container/slice" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/sirupsen/logrus" ) @@ -249,3 +250,24 @@ func closeDB(d *slasherkv.Store) { log.WithError(err).Error("could not close database") } } + +// unifyAttWrapperVersion ensures that the two wrappers wrap indexed attestations of the same version. +// If versions differ, the wrapped attestation with the lower version will be converted to the higher version. +func unifyAttWrapperVersion(w1 *slashertypes.IndexedAttestationWrapper, w2 *slashertypes.IndexedAttestationWrapper) { + if w1.IndexedAttestation.Version() == w2.IndexedAttestation.Version() { + return + } + if w1.IndexedAttestation.Version() != version.Electra { + w1.IndexedAttestation = ðpb.IndexedAttestationElectra{ + AttestingIndices: w1.IndexedAttestation.GetAttestingIndices(), + Data: w1.IndexedAttestation.GetData(), + Signature: w1.IndexedAttestation.GetSignature(), + } + return + } + w2.IndexedAttestation = ðpb.IndexedAttestationElectra{ + AttestingIndices: w2.IndexedAttestation.GetAttestingIndices(), + Data: w2.IndexedAttestation.GetData(), + Signature: w2.IndexedAttestation.GetSignature(), + } +} diff --git a/beacon-chain/slasher/helpers_test.go b/beacon-chain/slasher/helpers_test.go index 0b62841953..3fff33f924 100644 --- a/beacon-chain/slasher/helpers_test.go +++ b/beacon-chain/slasher/helpers_test.go @@ -9,6 +9,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/require" logTest "github.com/sirupsen/logrus/hooks/test" ) @@ -32,13 +33,13 @@ func TestService_groupByValidatorChunkIndex(t *testing.T) { validatorChunkSize: 2, }, atts: []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 1}, nil), - createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 1}, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0, 1}, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0, 1}, nil), }, want: map[uint64][]*slashertypes.IndexedAttestationWrapper{ 0: { - createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 1}, nil), - createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 1}, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0, 1}, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0, 1}, nil), }, }, }, @@ -48,17 +49,17 @@ func TestService_groupByValidatorChunkIndex(t *testing.T) { validatorChunkSize: 2, }, atts: []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 2, 4}, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0, 2, 4}, nil), }, want: map[uint64][]*slashertypes.IndexedAttestationWrapper{ 0: { - createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 2, 4}, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0, 2, 4}, nil), }, 1: { - createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 2, 4}, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0, 2, 4}, nil), }, 2: { - createAttestationWrapperEmptySig(t, 0, 0, []uint64{0, 2, 4}, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0, 2, 4}, nil), }, }, }, @@ -95,13 +96,13 @@ func TestService_groupByChunkIndex(t *testing.T) { historyLength: 3, }, atts: []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 0, nil, nil), - createAttestationWrapperEmptySig(t, 1, 0, nil, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 1, 0, nil, nil), }, want: map[uint64][]*slashertypes.IndexedAttestationWrapper{ 0: { - createAttestationWrapperEmptySig(t, 0, 0, nil, nil), - createAttestationWrapperEmptySig(t, 1, 0, nil, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 1, 0, nil, nil), }, }, }, @@ -112,17 +113,17 @@ func TestService_groupByChunkIndex(t *testing.T) { historyLength: 3, }, atts: []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 0, nil, nil), - createAttestationWrapperEmptySig(t, 1, 0, nil, nil), - createAttestationWrapperEmptySig(t, 2, 0, nil, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 1, 0, nil, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 2, 0, nil, nil), }, want: map[uint64][]*slashertypes.IndexedAttestationWrapper{ 0: { - createAttestationWrapperEmptySig(t, 0, 0, nil, nil), - createAttestationWrapperEmptySig(t, 1, 0, nil, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 1, 0, nil, nil), }, 1: { - createAttestationWrapperEmptySig(t, 2, 0, nil, nil), + createAttestationWrapperEmptySig(t, version.Phase0, 2, 0, nil, nil), }, }, }, @@ -207,7 +208,7 @@ func TestService_filterAttestations(t *testing.T) { { name: "Source > target gets dropped", input: []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 1, 0, []uint64{1}, make([]byte, 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 1, 0, []uint64{1}, make([]byte, 32)), }, inputEpoch: 0, wantedDropped: 1, @@ -215,33 +216,33 @@ func TestService_filterAttestations(t *testing.T) { { name: "Source < target is valid", input: []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 1, []uint64{1}, make([]byte, 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 1, []uint64{1}, make([]byte, 32)), }, inputEpoch: 1, wantedValid: []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 1, []uint64{1}, make([]byte, 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 1, []uint64{1}, make([]byte, 32)), }, wantedDropped: 0, }, { name: "Source == target is valid", input: []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 0, []uint64{1}, make([]byte, 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{1}, make([]byte, 32)), }, inputEpoch: 1, wantedValid: []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 0, []uint64{1}, make([]byte, 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{1}, make([]byte, 32)), }, wantedDropped: 0, }, { name: "Attestation from the future is deferred", input: []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 2, []uint64{1}, make([]byte, 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 2, []uint64{1}, make([]byte, 32)), }, inputEpoch: 1, wantedDeferred: []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 2, []uint64{1}, make([]byte, 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 2, []uint64{1}, make([]byte, 32)), }, wantedDropped: 0, }, @@ -271,22 +272,22 @@ func Test_logSlashingEvent(t *testing.T) { { name: "Surrounding vote", slashing: ðpb.AttesterSlashing{ - Attestation_1: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation), - Attestation_2: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation), + Attestation_1: createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation), + Attestation_2: createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation), }, }, { name: "Surrounded vote", slashing: ðpb.AttesterSlashing{ - Attestation_1: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation), - Attestation_2: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation), + Attestation_1: createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation), + Attestation_2: createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation), }, }, { name: "Double vote", slashing: ðpb.AttesterSlashing{ - Attestation_1: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation), - Attestation_2: createAttestationWrapperEmptySig(t, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation), + Attestation_1: createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation), + Attestation_2: createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, nil, nil).IndexedAttestation.(*ethpb.IndexedAttestation), }, }, } diff --git a/beacon-chain/slasher/queue_test.go b/beacon-chain/slasher/queue_test.go index 04350ae36b..03295cc2ea 100644 --- a/beacon-chain/slasher/queue_test.go +++ b/beacon-chain/slasher/queue_test.go @@ -5,6 +5,7 @@ import ( slashertypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/slasher/types" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/require" ) @@ -12,8 +13,8 @@ func Test_attestationsQueue(t *testing.T) { t.Run("push_and_dequeue", func(tt *testing.T) { attQueue := newAttestationsQueue() wantedAtts := []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 1, []uint64{1}, make([]byte, 32)), - createAttestationWrapperEmptySig(t, 1, 2, []uint64{1}, make([]byte, 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 1, []uint64{1}, make([]byte, 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 1, 2, []uint64{1}, make([]byte, 32)), } attQueue.push(wantedAtts[0]) attQueue.push(wantedAtts[1]) @@ -27,8 +28,8 @@ func Test_attestationsQueue(t *testing.T) { t.Run("extend_and_dequeue", func(tt *testing.T) { attQueue := newAttestationsQueue() wantedAtts := []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 1, []uint64{1}, make([]byte, 32)), - createAttestationWrapperEmptySig(t, 1, 2, []uint64{1}, make([]byte, 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 1, []uint64{1}, make([]byte, 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 1, 2, []uint64{1}, make([]byte, 32)), } attQueue.extend(wantedAtts) require.DeepEqual(t, 2, attQueue.size()) diff --git a/beacon-chain/slasher/receive_test.go b/beacon-chain/slasher/receive_test.go index 3bb389be3b..4b66ac2fe2 100644 --- a/beacon-chain/slasher/receive_test.go +++ b/beacon-chain/slasher/receive_test.go @@ -13,6 +13,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/assert" "github.com/prysmaticlabs/prysm/v5/testing/require" "github.com/prysmaticlabs/prysm/v5/testing/util" @@ -38,8 +39,8 @@ func TestSlasher_receiveAttestations_OK(t *testing.T) { }() firstIndices := []uint64{1, 2, 3} secondIndices := []uint64{4, 5, 6} - att1 := createAttestationWrapperEmptySig(t, 1, 2, firstIndices, nil) - att2 := createAttestationWrapperEmptySig(t, 1, 2, secondIndices, nil) + att1 := createAttestationWrapperEmptySig(t, version.Phase0, 1, 2, firstIndices, nil) + att2 := createAttestationWrapperEmptySig(t, version.Phase0, 1, 2, secondIndices, nil) wrappedAtt1 := &slashertypes.WrappedIndexedAtt{IndexedAtt: att1.IndexedAttestation} wrappedAtt2 := &slashertypes.WrappedIndexedAtt{IndexedAtt: att2.IndexedAttestation} indexedAttsChan <- wrappedAtt1 @@ -67,14 +68,14 @@ func TestService_pruneSlasherDataWithinSlidingWindow_AttestationsPruned(t *testi // Setup attestations for 2 validators at each epoch for epochs 0, 1, 2, 3. err := slasherDB.SaveAttestationRecordsForValidators(ctx, []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 0, []uint64{0}, bytesutil.PadTo([]byte("0a"), 32)), - createAttestationWrapperEmptySig(t, 0, 0, []uint64{1}, bytesutil.PadTo([]byte("0b"), 32)), - createAttestationWrapperEmptySig(t, 0, 1, []uint64{0}, bytesutil.PadTo([]byte("1a"), 32)), - createAttestationWrapperEmptySig(t, 0, 1, []uint64{1}, bytesutil.PadTo([]byte("1b"), 32)), - createAttestationWrapperEmptySig(t, 0, 2, []uint64{0}, bytesutil.PadTo([]byte("2a"), 32)), - createAttestationWrapperEmptySig(t, 0, 2, []uint64{1}, bytesutil.PadTo([]byte("2b"), 32)), - createAttestationWrapperEmptySig(t, 0, 3, []uint64{0}, bytesutil.PadTo([]byte("3a"), 32)), - createAttestationWrapperEmptySig(t, 0, 3, []uint64{1}, bytesutil.PadTo([]byte("3b"), 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{0}, bytesutil.PadTo([]byte("0a"), 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 0, []uint64{1}, bytesutil.PadTo([]byte("0b"), 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 1, []uint64{0}, bytesutil.PadTo([]byte("1a"), 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 1, []uint64{1}, bytesutil.PadTo([]byte("1b"), 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 2, []uint64{0}, bytesutil.PadTo([]byte("2a"), 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 2, []uint64{1}, bytesutil.PadTo([]byte("2b"), 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 3, []uint64{0}, bytesutil.PadTo([]byte("3a"), 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 3, []uint64{1}, bytesutil.PadTo([]byte("3b"), 32)), }) require.NoError(t, err) @@ -95,8 +96,8 @@ func TestService_pruneSlasherDataWithinSlidingWindow_AttestationsPruned(t *testi // Setup attestations for 2 validators at epoch 4. err = slasherDB.SaveAttestationRecordsForValidators(ctx, []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapperEmptySig(t, 0, 4, []uint64{0}, bytesutil.PadTo([]byte("4a"), 32)), - createAttestationWrapperEmptySig(t, 0, 4, []uint64{1}, bytesutil.PadTo([]byte("4b"), 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 4, []uint64{0}, bytesutil.PadTo([]byte("4a"), 32)), + createAttestationWrapperEmptySig(t, version.Phase0, 0, 4, []uint64{1}, bytesutil.PadTo([]byte("4b"), 32)), }) require.NoError(t, err) @@ -224,7 +225,7 @@ func TestSlasher_receiveAttestations_OnlyValidAttestations(t *testing.T) { firstIndices := []uint64{1, 2, 3} secondIndices := []uint64{4, 5, 6} // Add a valid attestation. - validAtt := createAttestationWrapperEmptySig(t, 1, 2, firstIndices, nil) + validAtt := createAttestationWrapperEmptySig(t, version.Phase0, 1, 2, firstIndices, nil) wrappedValidAtt := &slashertypes.WrappedIndexedAtt{IndexedAtt: validAtt.IndexedAttestation} indexedAttsChan <- wrappedValidAtt // Send an invalid, bad attestation which will not diff --git a/changelog/radek_eip-7549-slasher-pt1.md b/changelog/radek_eip-7549-slasher-pt1.md new file mode 100644 index 0000000000..89520e5497 --- /dev/null +++ b/changelog/radek_eip-7549-slasher-pt1.md @@ -0,0 +1,3 @@ +### Added + +- Update slasher service to Electra \ No newline at end of file diff --git a/testing/slasher/simulator/BUILD.bazel b/testing/slasher/simulator/BUILD.bazel index 679ded2837..4639acfe5b 100644 --- a/testing/slasher/simulator/BUILD.bazel +++ b/testing/slasher/simulator/BUILD.bazel @@ -32,6 +32,7 @@ go_library( "//crypto/rand:go_default_library", "//encoding/bytesutil:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//runtime/version:go_default_library", "//time/slots:go_default_library", "@com_github_pkg_errors//:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", @@ -55,6 +56,7 @@ go_test( "//crypto/bls:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "//proto/prysm/v1alpha1/slashings:go_default_library", + "//runtime/version:go_default_library", "//testing/require:go_default_library", "//testing/util:go_default_library", ], diff --git a/testing/slasher/simulator/attestation_generator.go b/testing/slasher/simulator/attestation_generator.go index be06c93095..6c83845ec6 100644 --- a/testing/slasher/simulator/attestation_generator.go +++ b/testing/slasher/simulator/attestation_generator.go @@ -15,15 +15,14 @@ import ( "github.com/prysmaticlabs/prysm/v5/crypto/rand" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/time/slots" "github.com/sirupsen/logrus" ) -func (s *Simulator) generateAttestationsForSlot( - ctx context.Context, slot primitives.Slot, -) ([]*ethpb.IndexedAttestation, []*ethpb.AttesterSlashing, error) { - attestations := make([]*ethpb.IndexedAttestation, 0) - slashings := make([]*ethpb.AttesterSlashing, 0) +func (s *Simulator) generateAttestationsForSlot(ctx context.Context, ver int, slot primitives.Slot) ([]ethpb.IndexedAtt, []ethpb.AttSlashing, error) { + attestations := make([]ethpb.IndexedAtt, 0) + slashings := make([]ethpb.AttSlashing, 0) currentEpoch := slots.ToEpoch(slot) committeesPerSlot := helpers.SlotCommitteeCount(s.srvConfig.Params.NumValidators) @@ -64,12 +63,23 @@ func (s *Simulator) generateAttestationsForSlot( for idx := i; idx < attEndIdx; idx++ { indices = append(indices, idx) } - att := ðpb.IndexedAttestation{ - AttestingIndices: indices, - Data: attData, - Signature: params.BeaconConfig().EmptySignature[:], + + var att ethpb.IndexedAtt + if ver >= version.Electra { + att = ðpb.IndexedAttestationElectra{ + AttestingIndices: indices, + Data: attData, + Signature: params.BeaconConfig().EmptySignature[:], + } + } else { + att = ðpb.IndexedAttestation{ + AttestingIndices: indices, + Data: attData, + Signature: params.BeaconConfig().EmptySignature[:], + } } - beaconState, err := s.srvConfig.AttestationStateFetcher.AttestationTargetState(ctx, att.Data.Target) + + beaconState, err := s.srvConfig.AttestationStateFetcher.AttestationTargetState(ctx, att.GetData().Target) if err != nil { return nil, nil, err } @@ -79,7 +89,12 @@ func (s *Simulator) generateAttestationsForSlot( if err != nil { return nil, nil, err } - att.Signature = aggSig.Marshal() + + if ver >= version.Electra { + att.(*ethpb.IndexedAttestationElectra).Signature = aggSig.Marshal() + } else { + att.(*ethpb.IndexedAttestation).Signature = aggSig.Marshal() + } attestations = append(attestations, att) if rand.NewGenerator().Float64() < s.srvConfig.Params.AttesterSlashingProbab { @@ -88,29 +103,50 @@ func (s *Simulator) generateAttestationsForSlot( if err != nil { return nil, nil, err } - slashableAtt.Signature = aggSig.Marshal() - slashedIndices = append(slashedIndices, slashableAtt.AttestingIndices...) - attDataRoot, err := att.Data.HashTreeRoot() + if ver >= version.Electra { + slashableAtt.(*ethpb.IndexedAttestationElectra).Signature = aggSig.Marshal() + } else { + slashableAtt.(*ethpb.IndexedAttestation).Signature = aggSig.Marshal() + } + + slashedIndices = append(slashedIndices, slashableAtt.GetAttestingIndices()...) + + attDataRoot, err := att.GetData().HashTreeRoot() if err != nil { return nil, nil, errors.Wrap(err, "cannot compte `att` hash tree root") } - slashableAttDataRoot, err := slashableAtt.Data.HashTreeRoot() + slashableAttDataRoot, err := slashableAtt.GetData().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, + var slashing ethpb.AttSlashing + if ver >= version.Electra { + slashing = ðpb.AttesterSlashingElectra{ + Attestation_1: att.(*ethpb.IndexedAttestationElectra), + Attestation_2: slashableAtt.(*ethpb.IndexedAttestationElectra), + } + } else { + slashing = ðpb.AttesterSlashing{ + Attestation_1: att.(*ethpb.IndexedAttestation), + Attestation_2: slashableAtt.(*ethpb.IndexedAttestation), + } } // 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, + if ver >= version.Electra { + slashing = ðpb.AttesterSlashingElectra{ + Attestation_1: slashableAtt.(*ethpb.IndexedAttestationElectra), + Attestation_2: att.(*ethpb.IndexedAttestationElectra), + } + } else { + slashing = ðpb.AttesterSlashing{ + Attestation_1: slashableAtt.(*ethpb.IndexedAttestation), + Attestation_2: att.(*ethpb.IndexedAttestation), + } } } @@ -131,46 +167,55 @@ func (s *Simulator) generateAttestationsForSlot( } func (s *Simulator) aggregateSigForAttestation( - beaconState state.ReadOnlyBeaconState, att *ethpb.IndexedAttestation, + beaconState state.ReadOnlyBeaconState, att ethpb.IndexedAtt, ) (bls.Signature, error) { domain, err := signing.Domain( beaconState.Fork(), - att.Data.Target.Epoch, + att.GetData().Target.Epoch, params.BeaconConfig().DomainBeaconAttester, beaconState.GenesisValidatorsRoot(), ) if err != nil { return nil, err } - signingRoot, err := signing.ComputeSigningRoot(att.Data, domain) + signingRoot, err := signing.ComputeSigningRoot(att.GetData(), domain) if err != nil { return nil, err } - sigs := make([]bls.Signature, len(att.AttestingIndices)) - for i, validatorIndex := range att.AttestingIndices { + sigs := make([]bls.Signature, len(att.GetAttestingIndices())) + for i, validatorIndex := range att.GetAttestingIndices() { privKey := s.srvConfig.PrivateKeysByValidatorIndex[primitives.ValidatorIndex(validatorIndex)] sigs[i] = privKey.Sign(signingRoot[:]) } return bls.AggregateSignatures(sigs), nil } -func makeSlashableFromAtt(att *ethpb.IndexedAttestation, indices []uint64) *ethpb.IndexedAttestation { - if att.Data.Source.Epoch <= 2 { +func makeSlashableFromAtt(att ethpb.IndexedAtt, indices []uint64) ethpb.IndexedAtt { + if att.GetData().Source.Epoch <= 2 { return makeDoubleVoteFromAtt(att, indices) } attData := ðpb.AttestationData{ - Slot: att.Data.Slot, - CommitteeIndex: att.Data.CommitteeIndex, - BeaconBlockRoot: att.Data.BeaconBlockRoot, + Slot: att.GetData().Slot, + CommitteeIndex: att.GetData().CommitteeIndex, + BeaconBlockRoot: att.GetData().BeaconBlockRoot, Source: ðpb.Checkpoint{ - Epoch: att.Data.Source.Epoch - 3, - Root: att.Data.Source.Root, + Epoch: att.GetData().Source.Epoch - 3, + Root: att.GetData().Source.Root, }, Target: ðpb.Checkpoint{ - Epoch: att.Data.Target.Epoch, - Root: att.Data.Target.Root, + Epoch: att.GetData().Target.Epoch, + Root: att.GetData().Target.Root, }, } + + if att.Version() >= version.Electra { + return ðpb.IndexedAttestationElectra{ + AttestingIndices: indices, + Data: attData, + Signature: params.BeaconConfig().EmptySignature[:], + } + } + return ðpb.IndexedAttestation{ AttestingIndices: indices, Data: attData, @@ -178,20 +223,29 @@ func makeSlashableFromAtt(att *ethpb.IndexedAttestation, indices []uint64) *ethp } } -func makeDoubleVoteFromAtt(att *ethpb.IndexedAttestation, indices []uint64) *ethpb.IndexedAttestation { +func makeDoubleVoteFromAtt(att ethpb.IndexedAtt, indices []uint64) ethpb.IndexedAtt { attData := ðpb.AttestationData{ - Slot: att.Data.Slot, - CommitteeIndex: att.Data.CommitteeIndex, + Slot: att.GetData().Slot, + CommitteeIndex: att.GetData().CommitteeIndex, BeaconBlockRoot: bytesutil.PadTo([]byte("slash me"), 32), Source: ðpb.Checkpoint{ - Epoch: att.Data.Source.Epoch, - Root: att.Data.Source.Root, + Epoch: att.GetData().Source.Epoch, + Root: att.GetData().Source.Root, }, Target: ðpb.Checkpoint{ - Epoch: att.Data.Target.Epoch, - Root: att.Data.Target.Root, + Epoch: att.GetData().Target.Epoch, + Root: att.GetData().Target.Root, }, } + + if att.Version() >= version.Electra { + return ðpb.IndexedAttestationElectra{ + AttestingIndices: indices, + Data: attData, + Signature: params.BeaconConfig().EmptySignature[:], + } + } + return ðpb.IndexedAttestation{ AttestingIndices: indices, Data: attData, diff --git a/testing/slasher/simulator/attestation_generator_test.go b/testing/slasher/simulator/attestation_generator_test.go index d42b4b3a5f..2107713949 100644 --- a/testing/slasher/simulator/attestation_generator_test.go +++ b/testing/slasher/simulator/attestation_generator_test.go @@ -6,6 +6,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/slashings" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/require" ) @@ -20,14 +21,18 @@ func TestGenerateAttestationsForSlot_Slashing(t *testing.T) { } srv := setupService(t, simParams) - epoch3Atts, _, err := srv.generateAttestationsForSlot(ctx, params.BeaconConfig().SlotsPerEpoch*3) - require.NoError(t, err) - epoch4Atts, _, err := srv.generateAttestationsForSlot(ctx, params.BeaconConfig().SlotsPerEpoch*4) - require.NoError(t, err) - for i := 0; i < len(epoch3Atts); i += 2 { - goodAtt := epoch3Atts[i] - surroundAtt := epoch4Atts[i+1] - require.Equal(t, true, slashings.IsSurround(surroundAtt, goodAtt)) + for _, v := range []int{version.Phase0, version.Electra} { + t.Run(version.String(v), func(t *testing.T) { + epoch3Atts, _, err := srv.generateAttestationsForSlot(ctx, v, params.BeaconConfig().SlotsPerEpoch*3) + require.NoError(t, err) + epoch4Atts, _, err := srv.generateAttestationsForSlot(ctx, v, params.BeaconConfig().SlotsPerEpoch*4) + require.NoError(t, err) + for i := 0; i < len(epoch3Atts); i += 2 { + goodAtt := epoch3Atts[i] + surroundAtt := epoch4Atts[i+1] + require.Equal(t, true, slashings.IsSurround(surroundAtt, goodAtt)) + } + }) } } @@ -41,24 +46,29 @@ func TestGenerateAttestationsForSlot_CorrectIndices(t *testing.T) { AttesterSlashingProbab: 0, } srv := setupService(t, simParams) - slot0Atts, _, err := srv.generateAttestationsForSlot(ctx, 0) - require.NoError(t, err) - slot1Atts, _, err := srv.generateAttestationsForSlot(ctx, 1) - require.NoError(t, err) - slot2Atts, _, err := srv.generateAttestationsForSlot(ctx, 2) - require.NoError(t, err) - var validatorIndices []uint64 - for _, att := range append(slot0Atts, slot1Atts...) { - validatorIndices = append(validatorIndices, att.AttestingIndices...) - } - for _, att := range slot2Atts { - validatorIndices = append(validatorIndices, att.AttestingIndices...) - } - // Making sure indices are one after the other for attestations. - var validatorIndex uint64 - for _, ii := range validatorIndices { - require.Equal(t, validatorIndex, ii) - validatorIndex++ + for _, v := range []int{version.Phase0, version.Electra} { + t.Run(version.String(v), func(t *testing.T) { + slot0Atts, _, err := srv.generateAttestationsForSlot(ctx, v, 0) + require.NoError(t, err) + slot1Atts, _, err := srv.generateAttestationsForSlot(ctx, v, 1) + require.NoError(t, err) + slot2Atts, _, err := srv.generateAttestationsForSlot(ctx, v, 2) + require.NoError(t, err) + var validatorIndices []uint64 + for _, att := range append(slot0Atts, slot1Atts...) { + validatorIndices = append(validatorIndices, att.GetAttestingIndices()...) + } + for _, att := range slot2Atts { + validatorIndices = append(validatorIndices, att.GetAttestingIndices()...) + } + + // Making sure indices are one after the other for attestations. + var validatorIndex uint64 + for _, ii := range validatorIndices { + require.Equal(t, validatorIndex, ii) + validatorIndex++ + } + }) } } diff --git a/testing/slasher/simulator/simulator.go b/testing/slasher/simulator/simulator.go index e09f13d95b..f53b039567 100644 --- a/testing/slasher/simulator/simulator.go +++ b/testing/slasher/simulator/simulator.go @@ -19,6 +19,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/crypto/bls" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/time/slots" "github.com/sirupsen/logrus" ) @@ -62,7 +63,7 @@ type Simulator struct { sentAttSlashingFeed *event.Feed sentBlockSlashingFeed *event.Feed sentProposerSlashings map[[32]byte]*ethpb.ProposerSlashing - sentAttesterSlashings map[[32]byte]*ethpb.AttesterSlashing + sentAttesterSlashings map[[32]byte]ethpb.AttSlashing genesisTime time.Time } @@ -111,7 +112,7 @@ func New(ctx context.Context, srvConfig *ServiceConfig) (*Simulator, error) { sentAttSlashingFeed: sentAttSlashingFeed, sentBlockSlashingFeed: sentBlockSlashingFeed, sentProposerSlashings: make(map[[32]byte]*ethpb.ProposerSlashing), - sentAttesterSlashings: make(map[[32]byte]*ethpb.AttesterSlashing), + sentAttesterSlashings: make(map[[32]byte]ethpb.AttSlashing), }, nil } @@ -206,7 +207,7 @@ func (s *Simulator) simulateBlocksAndAttestations(ctx context.Context) { s.beaconBlocksFeed.Send(bb) } - atts, attSlashings, err := s.generateAttestationsForSlot(ctx, slot) + atts, attSlashings, err := s.generateAttestationsForSlot(ctx, version.Phase0, slot) if err != nil { log.WithError(err).Fatal("Could not generate attestations for slot") } @@ -271,20 +272,20 @@ func (s *Simulator) verifySlashingsWereDetected(ctx context.Context) { for slashingRoot, slashing := range s.sentAttesterSlashings { if _, ok := detectedAttesterSlashings[slashingRoot]; !ok { log.WithFields(logrus.Fields{ - "targetEpoch": slashing.Attestation_1.Data.Target.Epoch, - "prevTargetEpoch": slashing.Attestation_2.Data.Target.Epoch, - "sourceEpoch": slashing.Attestation_1.Data.Source.Epoch, - "prevSourceEpoch": slashing.Attestation_2.Data.Source.Epoch, - "prevBeaconBlockRoot": fmt.Sprintf("%#x", slashing.Attestation_1.Data.BeaconBlockRoot), - "newBeaconBlockRoot": fmt.Sprintf("%#x", slashing.Attestation_2.Data.BeaconBlockRoot), + "targetEpoch": slashing.FirstAttestation().GetData().Target.Epoch, + "prevTargetEpoch": slashing.SecondAttestation().GetData().Target.Epoch, + "sourceEpoch": slashing.FirstAttestation().GetData().Source.Epoch, + "prevSourceEpoch": slashing.SecondAttestation().GetData().Source.Epoch, + "prevBeaconBlockRoot": fmt.Sprintf("%#x", slashing.FirstAttestation().GetData().BeaconBlockRoot), + "newBeaconBlockRoot": fmt.Sprintf("%#x", slashing.SecondAttestation().GetData().BeaconBlockRoot), }).Errorf("Did not detect simulated attester slashing") continue } log.WithFields(logrus.Fields{ - "targetEpoch": slashing.Attestation_1.Data.Target.Epoch, - "prevTargetEpoch": slashing.Attestation_2.Data.Target.Epoch, - "sourceEpoch": slashing.Attestation_1.Data.Source.Epoch, - "prevSourceEpoch": slashing.Attestation_2.Data.Source.Epoch, + "targetEpoch": slashing.FirstAttestation().GetData().Target.Epoch, + "prevTargetEpoch": slashing.SecondAttestation().GetData().Target.Epoch, + "sourceEpoch": slashing.FirstAttestation().GetData().Source.Epoch, + "prevSourceEpoch": slashing.SecondAttestation().GetData().Source.Epoch, }).Info("Correctly detected simulated attester slashing") } }