Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EIP-7549: slasher #14774

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions beacon-chain/slasher/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
109 changes: 109 additions & 0 deletions beacon-chain/slasher/chunks.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -232,6 +233,60 @@ func (m *MinSpanChunksSlice) CheckSlashable(

surroundingVotesTotal.Inc()

// Both attestations should have the same type. If not, we convert both to Electra attestations.
if existingAttWrapper.IndexedAttestation.Version() != incomingAttWrapper.IndexedAttestation.Version() {
existingAttWrapper = &slashertypes.IndexedAttestationWrapper{
IndexedAttestation: &ethpb.IndexedAttestationElectra{
AttestingIndices: existingAttWrapper.IndexedAttestation.GetAttestingIndices(),
Data: existingAttWrapper.IndexedAttestation.GetData(),
Signature: existingAttWrapper.IndexedAttestation.GetSignature(),
},
DataRoot: existingAttWrapper.DataRoot,
}
incomingAttWrapper = &slashertypes.IndexedAttestationWrapper{
IndexedAttestation: &ethpb.IndexedAttestationElectra{
AttestingIndices: incomingAttWrapper.IndexedAttestation.GetAttestingIndices(),
Data: incomingAttWrapper.IndexedAttestation.GetData(),
Signature: incomingAttWrapper.IndexedAttestation.GetSignature(),
},
DataRoot: incomingAttWrapper.DataRoot,
}
}

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)",
&ethpb.IndexedAttestationElectra{},
existingAttWrapper.IndexedAttestation,
)
}
incoming, ok := incomingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra)
if !ok {
return nil, fmt.Errorf(
"incoming attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestationElectra{},
incomingAttWrapper.IndexedAttestation,
)
}
slashing := &ethpb.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 = &ethpb.AttesterSlashingElectra{
Attestation_1: incoming,
Attestation_2: existing,
}
}

return slashing, nil
}

existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestation)
if !ok {
return nil, fmt.Errorf(
Expand Down Expand Up @@ -328,6 +383,60 @@ func (m *MaxSpanChunksSlice) CheckSlashable(

surroundedVotesTotal.Inc()

// Both attestations should have the same type. If not, we convert both to Electra attestations.
if existingAttWrapper.IndexedAttestation.Version() != incomingAttWrapper.IndexedAttestation.Version() {
existingAttWrapper = &slashertypes.IndexedAttestationWrapper{
IndexedAttestation: &ethpb.IndexedAttestationElectra{
AttestingIndices: existingAttWrapper.IndexedAttestation.GetAttestingIndices(),
Data: existingAttWrapper.IndexedAttestation.GetData(),
Signature: existingAttWrapper.IndexedAttestation.GetSignature(),
},
DataRoot: existingAttWrapper.DataRoot,
}
incomingAttWrapper = &slashertypes.IndexedAttestationWrapper{
IndexedAttestation: &ethpb.IndexedAttestationElectra{
AttestingIndices: incomingAttWrapper.IndexedAttestation.GetAttestingIndices(),
Data: incomingAttWrapper.IndexedAttestation.GetData(),
Signature: incomingAttWrapper.IndexedAttestation.GetSignature(),
},
DataRoot: incomingAttWrapper.DataRoot,
}
}

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)",
&ethpb.IndexedAttestationElectra{},
existingAttWrapper.IndexedAttestation,
)
}
incoming, ok := incomingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra)
if !ok {
return nil, fmt.Errorf(
"incoming attestation has wrong type (expected %T, got %T)",
&ethpb.IndexedAttestationElectra{},
incomingAttWrapper.IndexedAttestation,
)
}
slashing := &ethpb.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 = &ethpb.AttesterSlashingElectra{
Attestation_1: incoming,
Attestation_2: existing,
}
}

return slashing, nil
}

existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestation)
if !ok {
return nil, fmt.Errorf(
Expand Down
103 changes: 95 additions & 8 deletions beacon-chain/slasher/chunks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,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/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
Expand Down Expand Up @@ -91,7 +92,7 @@ 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)
att := createAttestationWrapperEmptySig(t, version.Phase0, source, target, nil, nil)

// A faulty chunk should lead to error.
chunk := &MinSpanChunksSlice{
Expand Down Expand Up @@ -126,7 +127,7 @@ func TestMinSpanChunksSlice_CheckSlashable(t *testing.T) {
chunk = EmptyMinSpanChunksSlice(params)
source = primitives.Epoch(1)
target = primitives.Epoch(2)
att = createAttestationWrapperEmptySig(t, source, target, nil, nil)
att = createAttestationWrapperEmptySig(t, version.Phase0, source, target, nil, nil)
chunkIndex := uint64(0)
startEpoch := target
currentEpoch := target
Expand All @@ -137,7 +138,7 @@ func TestMinSpanChunksSlice_CheckSlashable(t *testing.T) {
// 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, source, target, nil, nil)
surroundingVote := createAttestationWrapperEmptySig(t, version.Phase0, source, target, nil, nil)

slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundingVote)
require.NoError(t, err)
Expand All @@ -146,7 +147,7 @@ func TestMinSpanChunksSlice_CheckSlashable(t *testing.T) {
// Next up, 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},
Expand All @@ -158,6 +159,49 @@ func TestMinSpanChunksSlice_CheckSlashable(t *testing.T) {
require.NotEqual(t, (*ethpb.AttesterSlashing)(nil), slashing)
}

func TestMinSpanChunksSlice_CheckSlashable_DifferentVersions(t *testing.T) {
ctx := context.Background()
slasherDB := dbtest.SetupSlasherDB(t)
params := &Parameters{
chunkSize: 3,
validatorChunkSize: 2,
historyLength: 3,
}
validatorIdx := primitives.ValidatorIndex(1)
source := primitives.Epoch(1)
target := primitives.Epoch(2)

// We create a vote with Phase0 version.
att := createAttestationWrapperEmptySig(t, version.Phase0, 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)
require.NoError(t, err)

// We create a surrounding vote with Electra version.
source = primitives.Epoch(0)
target = primitives.Epoch(3)
surroundingVote := createAttestationWrapperEmptySig(t, version.Electra, source, target, nil, nil)

// We save the old attestation record, then check if the surrounding vote is indeed slashable.
attData := att.IndexedAttestation.GetData()
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)
require.NoError(t, err)
// The old record should be converted to Electra and the resulting slashing should be an Electra slashing.
require.NotEqual(t, (*ethpb.AttesterSlashingElectra)(nil), slashing)
}

func TestMaxSpanChunksSlice_CheckSlashable(t *testing.T) {
ctx := context.Background()
slasherDB := dbtest.SetupSlasherDB(t)
Expand All @@ -169,7 +213,7 @@ func TestMaxSpanChunksSlice_CheckSlashable(t *testing.T) {
validatorIdx := primitives.ValidatorIndex(1)
source := primitives.Epoch(1)
target := primitives.Epoch(2)
att := createAttestationWrapperEmptySig(t, source, target, nil, nil)
att := createAttestationWrapperEmptySig(t, version.Phase0, source, target, nil, nil)

// A faulty chunk should lead to error.
chunk := &MaxSpanChunksSlice{
Expand Down Expand Up @@ -204,7 +248,7 @@ func TestMaxSpanChunksSlice_CheckSlashable(t *testing.T) {
chunk = EmptyMaxSpanChunksSlice(params)
source = primitives.Epoch(0)
target = primitives.Epoch(3)
att = createAttestationWrapperEmptySig(t, source, target, nil, nil)
att = createAttestationWrapperEmptySig(t, version.Phase0, source, target, nil, nil)
chunkIndex := uint64(0)
startEpoch := source
currentEpoch := target
Expand All @@ -215,7 +259,7 @@ func TestMaxSpanChunksSlice_CheckSlashable(t *testing.T) {
// 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, source, target, nil, nil)
surroundedVote := createAttestationWrapperEmptySig(t, version.Phase0, source, target, nil, nil)

slashing, err = chunk.CheckSlashable(ctx, slasherDB, validatorIdx, surroundedVote)
require.NoError(t, err)
Expand All @@ -226,7 +270,7 @@ func TestMaxSpanChunksSlice_CheckSlashable(t *testing.T) {
attData := att.IndexedAttestation.GetData()
signingRoot := [32]byte{1}
attRecord := createAttestationWrapperEmptySig(
t, attData.Source.Epoch, attData.Target.Epoch, []uint64{uint64(validatorIdx)}, signingRoot[:],
t, version.Phase0, attData.Source.Epoch, attData.Target.Epoch, []uint64{uint64(validatorIdx)}, signingRoot[:],
)
err = slasherDB.SaveAttestationRecordsForValidators(
ctx,
Expand All @@ -239,6 +283,49 @@ func TestMaxSpanChunksSlice_CheckSlashable(t *testing.T) {
require.NotEqual(t, (*ethpb.AttesterSlashing)(nil), slashing)
}

func TestMaxSpanChunksSlice_CheckSlashable_DifferentVersions(t *testing.T) {
ctx := context.Background()
slasherDB := dbtest.SetupSlasherDB(t)
params := &Parameters{
chunkSize: 4,
validatorChunkSize: 2,
historyLength: 4,
}
validatorIdx := primitives.ValidatorIndex(1)
source := primitives.Epoch(0)
target := primitives.Epoch(3)

// We create a vote with Phase0 version.
att := createAttestationWrapperEmptySig(t, version.Phase0, source, target, nil, nil)

// We initialize an empty chunks slice and mark an attestation with (source 0, target 3) as attested.
chunk := EmptyMinSpanChunksSlice(params)
chunkIndex := uint64(0)
startEpoch := target
currentEpoch := target
_, err := chunk.Update(chunkIndex, currentEpoch, validatorIdx, startEpoch, target)
require.NoError(t, err)

// We create a surrounded vote with Electra version.
source = primitives.Epoch(1)
target = primitives.Epoch(2)
surroundedVote := createAttestationWrapperEmptySig(t, version.Electra, source, target, nil, nil)

// We save the old attestation record, then check if the sorrounded vote is indeed slashable.
attData := att.IndexedAttestation.GetData()
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)
require.NoError(t, err)
// The old record should be converted to Electra and the resulting slashing should be an Electra slashing.
require.NotEqual(t, (*ethpb.AttesterSlashingElectra)(nil), slashing)
}

func TestMinSpanChunksSlice_Update_MultipleChunks(t *testing.T) {
// Let's set H = historyLength = 2, meaning a min span
// will hold 2 epochs worth of attesting history. Then we set C = 2 meaning we will
Expand Down
Loading
Loading