From 7325d9fc966514963d81337d8974d72c98c1fe6d Mon Sep 17 00:00:00 2001 From: rkapka Date: Fri, 3 Jan 2025 15:06:14 +0100 Subject: [PATCH 01/10] EIP-7549: slasher --- beacon-chain/slasher/BUILD.bazel | 1 + beacon-chain/slasher/chunks.go | 97 +++++++++++ beacon-chain/slasher/detect_attestations.go | 183 +++++++++++++++----- validator/client/attest.go | 20 +-- 4 files changed, 245 insertions(+), 56 deletions(-) diff --git a/beacon-chain/slasher/BUILD.bazel b/beacon-chain/slasher/BUILD.bazel index b0f0391ac116..dbb6fddb1b6c 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", diff --git a/beacon-chain/slasher/chunks.go b/beacon-chain/slasher/chunks.go index 01b5943c66b5..2f89d2044fae 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,54 @@ func (m *MinSpanChunksSlice) CheckSlashable( surroundingVotesTotal.Inc() + // Both attestations should have the same type + if existingAttWrapper.IndexedAttestation.Version() >= version.Electra && incomingAttWrapper.IndexedAttestation.Version() < version.Electra { + incomingAttWrapper = &slashertypes.IndexedAttestationWrapper{ + IndexedAttestation: ðpb.IndexedAttestationElectra{ + AttestingIndices: incomingAttWrapper.IndexedAttestation.GetAttestingIndices(), + Data: incomingAttWrapper.IndexedAttestation.GetData(), + Signature: incomingAttWrapper.IndexedAttestation.GetSignature(), + }, + DataRoot: incomingAttWrapper.DataRoot, + } + } + if incomingAttWrapper.IndexedAttestation.Version() >= version.Electra && existingAttWrapper.IndexedAttestation.Version() < version.Electra { + existingAttWrapper = &slashertypes.IndexedAttestationWrapper{ + IndexedAttestation: ðpb.IndexedAttestationElectra{ + AttestingIndices: existingAttWrapper.IndexedAttestation.GetAttestingIndices(), + Data: existingAttWrapper.IndexedAttestation.GetData(), + Signature: existingAttWrapper.IndexedAttestation.GetSignature(), + }, + DataRoot: existingAttWrapper.DataRoot, + } + } + + postElectra := incomingAttWrapper.IndexedAttestation.Version() >= version.Electra + if postElectra { + existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + if !ok { + return nil, fmt.Errorf("wrong existing attestation type (expected %T, got %T)", ðpb.IndexedAttestationElectra{}, existing) + } + incoming, ok := incomingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + if !ok { + return nil, fmt.Errorf("wrong incoming attestation type (expected %T, got %T)", ðpb.IndexedAttestationElectra{}, incoming) + } + 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 +377,54 @@ func (m *MaxSpanChunksSlice) CheckSlashable( surroundedVotesTotal.Inc() + // Both attestations should have the same type + if existingAttWrapper.IndexedAttestation.Version() >= version.Electra && incomingAttWrapper.IndexedAttestation.Version() < version.Electra { + incomingAttWrapper = &slashertypes.IndexedAttestationWrapper{ + IndexedAttestation: ðpb.IndexedAttestationElectra{ + AttestingIndices: incomingAttWrapper.IndexedAttestation.GetAttestingIndices(), + Data: incomingAttWrapper.IndexedAttestation.GetData(), + Signature: incomingAttWrapper.IndexedAttestation.GetSignature(), + }, + DataRoot: incomingAttWrapper.DataRoot, + } + } + if incomingAttWrapper.IndexedAttestation.Version() >= version.Electra && existingAttWrapper.IndexedAttestation.Version() < version.Electra { + existingAttWrapper = &slashertypes.IndexedAttestationWrapper{ + IndexedAttestation: ðpb.IndexedAttestationElectra{ + AttestingIndices: existingAttWrapper.IndexedAttestation.GetAttestingIndices(), + Data: existingAttWrapper.IndexedAttestation.GetData(), + Signature: existingAttWrapper.IndexedAttestation.GetSignature(), + }, + DataRoot: existingAttWrapper.DataRoot, + } + } + + postElectra := incomingAttWrapper.IndexedAttestation.Version() >= version.Electra + if postElectra { + existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + if !ok { + return nil, fmt.Errorf("wrong existing attestation type (expected %T, got %T)", ðpb.IndexedAttestationElectra{}, existing) + } + incoming, ok := incomingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + if !ok { + return nil, fmt.Errorf("wrong incoming attestation type (expected %T, got %T)", ðpb.IndexedAttestationElectra{}, incoming) + } + 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/detect_attestations.go b/beacon-chain/slasher/detect_attestations.go index bbfc1238b20d..00290e0897f4 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,80 @@ 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, - ) + var slashing ethpb.AttSlashing + + // Both attestations should have the same type + if existingAttWrapper.IndexedAttestation.Version() >= version.Electra && incomingAttWrapper.IndexedAttestation.Version() < version.Electra { + incomingAttWrapper = &slashertypes.IndexedAttestationWrapper{ + IndexedAttestation: ðpb.IndexedAttestationElectra{ + AttestingIndices: incomingAttWrapper.IndexedAttestation.GetAttestingIndices(), + Data: incomingAttWrapper.IndexedAttestation.GetData(), + Signature: incomingAttWrapper.IndexedAttestation.GetSignature(), + }, + DataRoot: incomingAttWrapper.DataRoot, + } } - 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, - ) + if incomingAttWrapper.IndexedAttestation.Version() >= version.Electra && existingAttWrapper.IndexedAttestation.Version() < version.Electra { + existingAttWrapper = &slashertypes.IndexedAttestationWrapper{ + IndexedAttestation: ðpb.IndexedAttestationElectra{ + AttestingIndices: existingAttWrapper.IndexedAttestation.GetAttestingIndices(), + Data: existingAttWrapper.IndexedAttestation.GetData(), + Signature: existingAttWrapper.IndexedAttestation.GetSignature(), + }, + DataRoot: existingAttWrapper.DataRoot, + } } - slashing := ðpb.AttesterSlashing{ - Attestation_1: existing, - Attestation_2: incoming, - } + postElectra := incomingAttWrapper.IndexedAttestation.Version() >= version.Electra + if postElectra { + existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + if !ok { + return nil, fmt.Errorf("wrong existing attestation type (expected %T, got %T)", ðpb.IndexedAttestationElectra{}, existing) + } + incoming, ok := incomingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + if !ok { + return nil, fmt.Errorf("wrong incoming attestation type (expected %T, got %T)", ðpb.IndexedAttestationElectra{}, incoming) + } + 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 { + // 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 +293,80 @@ 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, - ) + var slashing ethpb.AttSlashing + + // Both attestations should have the same type + if wrapper_1.IndexedAttestation.Version() >= version.Electra && wrapper_2.IndexedAttestation.Version() < version.Electra { + wrapper_2 = &slashertypes.IndexedAttestationWrapper{ + IndexedAttestation: ðpb.IndexedAttestationElectra{ + AttestingIndices: wrapper_2.IndexedAttestation.GetAttestingIndices(), + Data: wrapper_2.IndexedAttestation.GetData(), + Signature: wrapper_2.IndexedAttestation.GetSignature(), + }, + DataRoot: wrapper_2.DataRoot, + } } - 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, - ) + if wrapper_2.IndexedAttestation.Version() >= version.Electra && wrapper_1.IndexedAttestation.Version() < version.Electra { + wrapper_1 = &slashertypes.IndexedAttestationWrapper{ + IndexedAttestation: ðpb.IndexedAttestationElectra{ + AttestingIndices: wrapper_1.IndexedAttestation.GetAttestingIndices(), + Data: wrapper_1.IndexedAttestation.GetData(), + Signature: wrapper_1.IndexedAttestation.GetSignature(), + }, + DataRoot: wrapper_1.DataRoot, + } } - slashing := ðpb.AttesterSlashing{ - Attestation_1: att_1, - Attestation_2: att_2, - } + postElectra := wrapper_2.IndexedAttestation.Version() >= version.Electra + if postElectra { + existing, ok := wrapper_1.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + if !ok { + return nil, fmt.Errorf("wrong existing attestation type (expected %T, got %T)", ðpb.IndexedAttestationElectra{}, existing) + } + incoming, ok := wrapper_2.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + if !ok { + return nil, fmt.Errorf("wrong incoming attestation type (expected %T, got %T)", ðpb.IndexedAttestationElectra{}, incoming) + } + slashing = ðpb.AttesterSlashingElectra{ + Attestation_1: existing, + Attestation_2: incoming, + } - // Ensure the attestation with the lower data root is the first attestation. - if bytes.Compare(wrapper_1.DataRoot[:], wrapper_2.DataRoot[:]) > 0 { + // 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: incoming, + Attestation_2: existing, + } + } + } 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/validator/client/attest.go b/validator/client/attest.go index 0c2f92a83cc6..a7c11411037e 100644 --- a/validator/client/attest.go +++ b/validator/client/attest.go @@ -139,18 +139,14 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot primitives.Slot, return } - // TODO: Extend to Electra - phase0Att, ok := indexedAtt.(*ethpb.IndexedAttestation) - if ok { - // Send the attestation to the beacon node. - if err := v.db.SlashableAttestationCheck(ctx, phase0Att, pubKey, signingRoot, v.emitAccountMetrics, ValidatorAttestFailVec); err != nil { - log.WithError(err).Error("Failed attestation slashing protection check") - log.WithFields( - attestationLogFields(pubKey, indexedAtt), - ).Debug("Attempted slashable attestation details") - tracing.AnnotateError(span, err) - return - } + // Send the attestation to the beacon node. + if err := v.db.SlashableAttestationCheck(ctx, indexedAtt, pubKey, signingRoot, v.emitAccountMetrics, ValidatorAttestFailVec); err != nil { + log.WithError(err).Error("Failed attestation slashing protection check") + log.WithFields( + attestationLogFields(pubKey, indexedAtt), + ).Debug("Attempted slashable attestation details") + tracing.AnnotateError(span, err) + return } aggregationBitfield := bitfield.NewBitlist(uint64(len(duty.Committee))) From 672f6c853226018ac8ca2a99d3ed6e9927dfedaf Mon Sep 17 00:00:00 2001 From: rkapka Date: Fri, 3 Jan 2025 17:00:56 +0100 Subject: [PATCH 02/10] update chunks and detection --- beacon-chain/slasher/BUILD.bazel | 1 + beacon-chain/slasher/chunks.go | 72 +++-- beacon-chain/slasher/chunks_test.go | 103 ++++++- beacon-chain/slasher/detect_attestations.go | 84 +++--- .../slasher/detect_attestations_test.go | 266 ++++++++++++------ beacon-chain/slasher/helpers_test.go | 63 +++-- beacon-chain/slasher/queue_test.go | 9 +- beacon-chain/slasher/receive_test.go | 27 +- 8 files changed, 414 insertions(+), 211 deletions(-) diff --git a/beacon-chain/slasher/BUILD.bazel b/beacon-chain/slasher/BUILD.bazel index dbb6fddb1b6c..52c52833630b 100644 --- a/beacon-chain/slasher/BUILD.bazel +++ b/beacon-chain/slasher/BUILD.bazel @@ -84,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 2f89d2044fae..8b8ef523abae 100644 --- a/beacon-chain/slasher/chunks.go +++ b/beacon-chain/slasher/chunks.go @@ -233,18 +233,8 @@ func (m *MinSpanChunksSlice) CheckSlashable( surroundingVotesTotal.Inc() - // Both attestations should have the same type - if existingAttWrapper.IndexedAttestation.Version() >= version.Electra && incomingAttWrapper.IndexedAttestation.Version() < version.Electra { - incomingAttWrapper = &slashertypes.IndexedAttestationWrapper{ - IndexedAttestation: ðpb.IndexedAttestationElectra{ - AttestingIndices: incomingAttWrapper.IndexedAttestation.GetAttestingIndices(), - Data: incomingAttWrapper.IndexedAttestation.GetData(), - Signature: incomingAttWrapper.IndexedAttestation.GetSignature(), - }, - DataRoot: incomingAttWrapper.DataRoot, - } - } - if incomingAttWrapper.IndexedAttestation.Version() >= version.Electra && existingAttWrapper.IndexedAttestation.Version() < version.Electra { + // 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: ðpb.IndexedAttestationElectra{ AttestingIndices: existingAttWrapper.IndexedAttestation.GetAttestingIndices(), @@ -253,17 +243,33 @@ func (m *MinSpanChunksSlice) CheckSlashable( }, DataRoot: existingAttWrapper.DataRoot, } + incomingAttWrapper = &slashertypes.IndexedAttestationWrapper{ + IndexedAttestation: ðpb.IndexedAttestationElectra{ + AttestingIndices: incomingAttWrapper.IndexedAttestation.GetAttestingIndices(), + Data: incomingAttWrapper.IndexedAttestation.GetData(), + Signature: incomingAttWrapper.IndexedAttestation.GetSignature(), + }, + DataRoot: incomingAttWrapper.DataRoot, + } } - postElectra := incomingAttWrapper.IndexedAttestation.Version() >= version.Electra + postElectra := existingAttWrapper.IndexedAttestation.Version() >= version.Electra if postElectra { existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra) if !ok { - return nil, fmt.Errorf("wrong existing attestation type (expected %T, got %T)", ðpb.IndexedAttestationElectra{}, existing) + 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("wrong incoming attestation type (expected %T, got %T)", ðpb.IndexedAttestationElectra{}, incoming) + return nil, fmt.Errorf( + "incoming attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestationElectra{}, + incomingAttWrapper.IndexedAttestation, + ) } slashing := ðpb.AttesterSlashingElectra{ Attestation_1: existing, @@ -377,18 +383,8 @@ func (m *MaxSpanChunksSlice) CheckSlashable( surroundedVotesTotal.Inc() - // Both attestations should have the same type - if existingAttWrapper.IndexedAttestation.Version() >= version.Electra && incomingAttWrapper.IndexedAttestation.Version() < version.Electra { - incomingAttWrapper = &slashertypes.IndexedAttestationWrapper{ - IndexedAttestation: ðpb.IndexedAttestationElectra{ - AttestingIndices: incomingAttWrapper.IndexedAttestation.GetAttestingIndices(), - Data: incomingAttWrapper.IndexedAttestation.GetData(), - Signature: incomingAttWrapper.IndexedAttestation.GetSignature(), - }, - DataRoot: incomingAttWrapper.DataRoot, - } - } - if incomingAttWrapper.IndexedAttestation.Version() >= version.Electra && existingAttWrapper.IndexedAttestation.Version() < version.Electra { + // 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: ðpb.IndexedAttestationElectra{ AttestingIndices: existingAttWrapper.IndexedAttestation.GetAttestingIndices(), @@ -397,17 +393,33 @@ func (m *MaxSpanChunksSlice) CheckSlashable( }, DataRoot: existingAttWrapper.DataRoot, } + incomingAttWrapper = &slashertypes.IndexedAttestationWrapper{ + IndexedAttestation: ðpb.IndexedAttestationElectra{ + AttestingIndices: incomingAttWrapper.IndexedAttestation.GetAttestingIndices(), + Data: incomingAttWrapper.IndexedAttestation.GetData(), + Signature: incomingAttWrapper.IndexedAttestation.GetSignature(), + }, + DataRoot: incomingAttWrapper.DataRoot, + } } - postElectra := incomingAttWrapper.IndexedAttestation.Version() >= version.Electra + postElectra := existingAttWrapper.IndexedAttestation.Version() >= version.Electra if postElectra { existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra) if !ok { - return nil, fmt.Errorf("wrong existing attestation type (expected %T, got %T)", ðpb.IndexedAttestationElectra{}, existing) + 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("wrong incoming attestation type (expected %T, got %T)", ðpb.IndexedAttestationElectra{}, incoming) + return nil, fmt.Errorf( + "incoming attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestationElectra{}, + incomingAttWrapper.IndexedAttestation, + ) } slashing := ðpb.AttesterSlashingElectra{ Attestation_1: existing, diff --git a/beacon-chain/slasher/chunks_test.go b/beacon-chain/slasher/chunks_test.go index 7fc1ab7a6501..061b47089764 100644 --- a/beacon-chain/slasher/chunks_test.go +++ b/beacon-chain/slasher/chunks_test.go @@ -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" ) @@ -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{ @@ -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 @@ -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) @@ -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}, @@ -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) @@ -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{ @@ -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 @@ -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) @@ -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, @@ -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 diff --git a/beacon-chain/slasher/detect_attestations.go b/beacon-chain/slasher/detect_attestations.go index 00290e0897f4..a064cb3ab14a 100644 --- a/beacon-chain/slasher/detect_attestations.go +++ b/beacon-chain/slasher/detect_attestations.go @@ -196,18 +196,8 @@ func (s *Service) checkDoubleVotes( var slashing ethpb.AttSlashing - // Both attestations should have the same type - if existingAttWrapper.IndexedAttestation.Version() >= version.Electra && incomingAttWrapper.IndexedAttestation.Version() < version.Electra { - incomingAttWrapper = &slashertypes.IndexedAttestationWrapper{ - IndexedAttestation: ðpb.IndexedAttestationElectra{ - AttestingIndices: incomingAttWrapper.IndexedAttestation.GetAttestingIndices(), - Data: incomingAttWrapper.IndexedAttestation.GetData(), - Signature: incomingAttWrapper.IndexedAttestation.GetSignature(), - }, - DataRoot: incomingAttWrapper.DataRoot, - } - } - if incomingAttWrapper.IndexedAttestation.Version() >= version.Electra && existingAttWrapper.IndexedAttestation.Version() < version.Electra { + // 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: ðpb.IndexedAttestationElectra{ AttestingIndices: existingAttWrapper.IndexedAttestation.GetAttestingIndices(), @@ -216,17 +206,33 @@ func (s *Service) checkDoubleVotes( }, DataRoot: existingAttWrapper.DataRoot, } + incomingAttWrapper = &slashertypes.IndexedAttestationWrapper{ + IndexedAttestation: ðpb.IndexedAttestationElectra{ + AttestingIndices: incomingAttWrapper.IndexedAttestation.GetAttestingIndices(), + Data: incomingAttWrapper.IndexedAttestation.GetData(), + Signature: incomingAttWrapper.IndexedAttestation.GetSignature(), + }, + DataRoot: incomingAttWrapper.DataRoot, + } } - postElectra := incomingAttWrapper.IndexedAttestation.Version() >= version.Electra + postElectra := existingAttWrapper.IndexedAttestation.Version() >= version.Electra if postElectra { existing, ok := existingAttWrapper.IndexedAttestation.(*ethpb.IndexedAttestationElectra) if !ok { - return nil, fmt.Errorf("wrong existing attestation type (expected %T, got %T)", ðpb.IndexedAttestationElectra{}, existing) + 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("wrong incoming attestation type (expected %T, got %T)", ðpb.IndexedAttestationElectra{}, incoming) + return nil, fmt.Errorf( + "incoming attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestationElectra{}, + incomingAttWrapper.IndexedAttestation, + ) } slashing = ðpb.AttesterSlashingElectra{ Attestation_1: existing, @@ -295,18 +301,8 @@ func (s *Service) checkDoubleVotes( var slashing ethpb.AttSlashing - // Both attestations should have the same type - if wrapper_1.IndexedAttestation.Version() >= version.Electra && wrapper_2.IndexedAttestation.Version() < version.Electra { - wrapper_2 = &slashertypes.IndexedAttestationWrapper{ - IndexedAttestation: ðpb.IndexedAttestationElectra{ - AttestingIndices: wrapper_2.IndexedAttestation.GetAttestingIndices(), - Data: wrapper_2.IndexedAttestation.GetData(), - Signature: wrapper_2.IndexedAttestation.GetSignature(), - }, - DataRoot: wrapper_2.DataRoot, - } - } - if wrapper_2.IndexedAttestation.Version() >= version.Electra && wrapper_1.IndexedAttestation.Version() < version.Electra { + // Both attestations should have the same type. If not, we convert both to Electra attestations. + if wrapper_1.IndexedAttestation.Version() != wrapper_2.IndexedAttestation.Version() { wrapper_1 = &slashertypes.IndexedAttestationWrapper{ IndexedAttestation: ðpb.IndexedAttestationElectra{ AttestingIndices: wrapper_1.IndexedAttestation.GetAttestingIndices(), @@ -315,28 +311,44 @@ func (s *Service) checkDoubleVotes( }, DataRoot: wrapper_1.DataRoot, } + wrapper_2 = &slashertypes.IndexedAttestationWrapper{ + IndexedAttestation: ðpb.IndexedAttestationElectra{ + AttestingIndices: wrapper_2.IndexedAttestation.GetAttestingIndices(), + Data: wrapper_2.IndexedAttestation.GetData(), + Signature: wrapper_2.IndexedAttestation.GetSignature(), + }, + DataRoot: wrapper_2.DataRoot, + } } - postElectra := wrapper_2.IndexedAttestation.Version() >= version.Electra + postElectra := wrapper_1.IndexedAttestation.Version() >= version.Electra if postElectra { - existing, ok := wrapper_1.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + att_1, ok := wrapper_1.IndexedAttestation.(*ethpb.IndexedAttestationElectra) if !ok { - return nil, fmt.Errorf("wrong existing attestation type (expected %T, got %T)", ðpb.IndexedAttestationElectra{}, existing) + return nil, fmt.Errorf( + "first attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestationElectra{}, + wrapper_1.IndexedAttestation, + ) } - incoming, ok := wrapper_2.IndexedAttestation.(*ethpb.IndexedAttestationElectra) + att_2, ok := wrapper_2.IndexedAttestation.(*ethpb.IndexedAttestationElectra) if !ok { - return nil, fmt.Errorf("wrong incoming attestation type (expected %T, got %T)", ðpb.IndexedAttestationElectra{}, incoming) + return nil, fmt.Errorf( + "second attestation has wrong type (expected %T, got %T)", + ðpb.IndexedAttestationElectra{}, + wrapper_2.IndexedAttestation, + ) } slashing = ðpb.AttesterSlashingElectra{ - Attestation_1: existing, - Attestation_2: incoming, + 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: incoming, - Attestation_2: existing, + Attestation_1: att_2, + Attestation_2: att_1, } } } else { diff --git a/beacon-chain/slasher/detect_attestations_test.go b/beacon-chain/slasher/detect_attestations_test.go index 0026f36877fc..52cb20c0b951 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 } @@ -58,13 +61,14 @@ func Test_processAttestations(t *testing.T) { { 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}}, + {ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, + {ver: version.Phase0, 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}}, + ver: version.Phase0, + attestationInfo_1: &attestationInfo{ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, + attestationInfo_2: &attestationInfo{ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{2}}, }, }, }, @@ -76,19 +80,20 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, + {ver: version.Phase0, 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}}, + {ver: version.Phase0, 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}}, + ver: version.Phase0, + attestationInfo_1: &attestationInfo{ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, + attestationInfo_2: &attestationInfo{ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{2}}, }, }, }, @@ -100,8 +105,8 @@ func Test_processAttestations(t *testing.T) { { 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}}, + {ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, + {ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, }, expectedSlashingsInfo: nil, }, @@ -113,14 +118,14 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, + {ver: version.Phase0, 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}}, + {ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: []byte{1}}, }, expectedSlashingsInfo: nil, }, @@ -132,13 +137,14 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, 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}, + ver: version.Phase0, + attestationInfo_1: &attestationInfo{ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{ver: version.Phase0, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, }, }, @@ -150,19 +156,20 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, 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}, + {ver: version.Phase0, 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}, + ver: version.Phase0, + attestationInfo_1: &attestationInfo{ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{ver: version.Phase0, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, }, }, @@ -174,13 +181,14 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 1000, attestationsInfo: []*attestationInfo{ - {source: 50, target: 51, indices: []uint64{0}, beaconBlockRoot: nil}, - {source: 0, target: 1000, indices: []uint64{0}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 50, target: 51, indices: []uint64{0}, beaconBlockRoot: nil}, + {ver: version.Phase0, 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}, + ver: version.Phase0, + attestationInfo_1: &attestationInfo{ver: version.Phase0, source: 0, target: 1000, indices: []uint64{0}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{ver: version.Phase0, source: 50, target: 51, indices: []uint64{0}, beaconBlockRoot: nil}, }, }, }, @@ -192,19 +200,20 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 1000, attestationsInfo: []*attestationInfo{ - {source: 50, target: 51, indices: []uint64{0}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 50, target: 51, indices: []uint64{0}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, { currentEpoch: 1000, attestationsInfo: []*attestationInfo{ - {source: 0, target: 1000, indices: []uint64{0}, beaconBlockRoot: nil}, + {ver: version.Phase0, 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}, + ver: version.Phase0, + attestationInfo_1: &attestationInfo{ver: version.Phase0, source: 0, target: 1000, indices: []uint64{0}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{ver: version.Phase0, source: 50, target: 51, indices: []uint64{0}, beaconBlockRoot: nil}, }, }, }, @@ -216,13 +225,14 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, 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}, + ver: version.Phase0, + attestationInfo_1: &attestationInfo{ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{ver: version.Phase0, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, }, }, @@ -234,19 +244,20 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, 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}, + {ver: version.Phase0, 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}, + ver: version.Phase0, + attestationInfo_1: &attestationInfo{ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{ver: version.Phase0, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, }, }, @@ -258,13 +269,14 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - {source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, 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}, + ver: version.Phase0, + attestationInfo_1: &attestationInfo{ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{ver: version.Phase0, source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, }, }, @@ -276,19 +288,20 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, 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}, + {ver: version.Phase0, 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}, + ver: version.Phase0, + attestationInfo_1: &attestationInfo{ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + attestationInfo_2: &attestationInfo{ver: version.Phase0, source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, }, }, @@ -300,8 +313,8 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0}, beaconBlockRoot: nil}, - {source: 0, target: 3, indices: []uint64{1}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 1, target: 2, indices: []uint64{0}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 0, target: 3, indices: []uint64{1}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, @@ -313,14 +326,14 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 1, target: 2, indices: []uint64{0}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{1}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 0, target: 3, indices: []uint64{1}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, @@ -332,8 +345,8 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - {source: 1, target: 2, indices: []uint64{2, 3}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 1, target: 2, indices: []uint64{2, 3}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, @@ -345,14 +358,14 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, 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}, + {ver: version.Phase0, source: 1, target: 2, indices: []uint64{2, 3}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, @@ -364,8 +377,8 @@ func Test_processAttestations(t *testing.T) { { 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}, + {ver: version.Phase0, source: 0, target: 3, indices: []uint64{0}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 1, target: 2, indices: []uint64{params.BeaconConfig().MinGenesisActiveValidatorCount - 1}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, @@ -377,14 +390,14 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0}, beaconBlockRoot: nil}, + {ver: version.Phase0, 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}, + {ver: version.Phase0, source: 1, target: 2, indices: []uint64{params.BeaconConfig().MinGenesisActiveValidatorCount - 1}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, @@ -396,8 +409,8 @@ func Test_processAttestations(t *testing.T) { { 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}, + {ver: version.Phase0, source: 0, target: 3, indices: []uint64{0}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 1, target: 2, indices: []uint64{params.BeaconConfig().MinGenesisActiveValidatorCount - 1}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, @@ -409,14 +422,14 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0}, beaconBlockRoot: nil}, + {ver: version.Phase0, 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}, + {ver: version.Phase0, source: 1, target: 2, indices: []uint64{params.BeaconConfig().MinGenesisActiveValidatorCount - 1}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, @@ -428,8 +441,8 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - {source: 2, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 2, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, @@ -441,14 +454,14 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 1, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, 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}, + {ver: version.Phase0, source: 2, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, @@ -460,8 +473,8 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - {source: 2, target: 4, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 2, target: 4, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, @@ -473,14 +486,14 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, 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}, + {ver: version.Phase0, source: 2, target: 4, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, @@ -492,8 +505,8 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, @@ -505,14 +518,14 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, 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}, + {ver: version.Phase0, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, @@ -524,8 +537,8 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, - {source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, @@ -537,19 +550,57 @@ func Test_processAttestations(t *testing.T) { { currentEpoch: 4, attestationsInfo: []*attestationInfo{ - {source: 0, target: 3, indices: []uint64{0, 1}, beaconBlockRoot: nil}, + {ver: version.Phase0, 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}, + {ver: version.Phase0, source: 0, target: 2, indices: []uint64{0, 1}, beaconBlockRoot: nil}, }, expectedSlashingsInfo: nil, }, }, }, + { + name: "Electra: Same target with different signing roots - single step", + steps: []*step{ + { + currentEpoch: 4, + attestationsInfo: []*attestationInfo{ + {ver: version.Electra, 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}}, + }, + }, + }, + }, + }, + { + name: "Phase0 & Electra: 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}}, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -629,6 +680,7 @@ func Test_processAttestations(t *testing.T) { // Create a wrapped attestation. attestationWrapper := createAttestationWrapper( t, + attestationInfo.ver, domain, privateKeys, attestationInfo.source, @@ -642,12 +694,13 @@ func Test_processAttestations(t *testing.T) { } // Build expected attester slashings. - expectedSlashings := make(map[[fieldparams.RootLength]byte]*ethpb.AttesterSlashing, len(step.expectedSlashingsInfo)) + expectedSlashings := make(map[[fieldparams.RootLength]byte]ethpb.AttSlashing, len(step.expectedSlashingsInfo)) for _, slashingInfo := range step.expectedSlashingsInfo { // Create attestations. wrapper_1 := createAttestationWrapper( t, + slashingInfo.attestationInfo_1.ver, domain, privateKeys, slashingInfo.attestationInfo_1.source, @@ -658,6 +711,7 @@ func Test_processAttestations(t *testing.T) { wrapper_2 := createAttestationWrapper( t, + slashingInfo.attestationInfo_2.ver, domain, privateKeys, slashingInfo.attestationInfo_2.source, @@ -667,9 +721,18 @@ func Test_processAttestations(t *testing.T) { ) // Create the attester slashing. - expectedSlashing := ðpb.AttesterSlashing{ - Attestation_1: wrapper_1.IndexedAttestation.(*ethpb.IndexedAttestation), - Attestation_2: wrapper_2.IndexedAttestation.(*ethpb.IndexedAttestation), + 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() @@ -757,7 +820,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 +877,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 +1215,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 +1238,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 +1272,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 +1295,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 +1410,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 +1545,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 +1610,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 +1630,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 +1650,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 +1677,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 +1725,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_test.go b/beacon-chain/slasher/helpers_test.go index 0b6284195300..3fff33f92477 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 04350ae36b5a..03295cc2ea8b 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 3bb389be3b78..4b66ac2fe2d4 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 From 998c9e061329e5d3a112a1ceda905a9fbb2236c4 Mon Sep 17 00:00:00 2001 From: rkapka Date: Fri, 10 Jan 2025 20:44:59 +0100 Subject: [PATCH 03/10] update tests --- beacon-chain/blockchain/process_block_test.go | 42 ++ .../core/blocks/attester_slashing_test.go | 146 +++++ beacon-chain/core/transition/transition.go | 8 +- .../core/transition/transition_test.go | 19 + beacon-chain/operations/slashings/BUILD.bazel | 1 + .../slashings/service_attester_test.go | 549 ++++++++++-------- beacon-chain/rpc/eth/events/events_test.go | 35 +- config/params/configset_test.go | 1 + config/params/loader_test.go | 1 + config/params/testnet_config_test.go | 1 + testing/slasher/simulator/BUILD.bazel | 2 + .../simulator/attestation_generator.go | 138 +++-- .../simulator/attestation_generator_test.go | 62 +- testing/slasher/simulator/simulator.go | 27 +- testing/util/attestation.go | 13 + 15 files changed, 717 insertions(+), 328 deletions(-) diff --git a/beacon-chain/blockchain/process_block_test.go b/beacon-chain/blockchain/process_block_test.go index a416ee296eb7..b9094ac27155 100644 --- a/beacon-chain/blockchain/process_block_test.go +++ b/beacon-chain/blockchain/process_block_test.go @@ -1076,6 +1076,48 @@ func TestService_insertSlashingsToForkChoiceStore(t *testing.T) { service.InsertSlashingsToForkChoiceStore(ctx, wb.Block().Body().AttesterSlashings()) } +func TestService_insertSlashingsToForkChoiceStoreElectra(t *testing.T) { + service, tr := minimalTestService(t) + ctx := tr.ctx + + beaconState, privKeys := util.DeterministicGenesisStateElectra(t, 100) + att1 := util.HydrateIndexedAttestationElectra(ðpb.IndexedAttestationElectra{ + Data: ðpb.AttestationData{ + Source: ðpb.Checkpoint{Epoch: 1}, + }, + AttestingIndices: []uint64{0, 1}, + }) + domain, err := signing.Domain(beaconState.Fork(), 0, params.BeaconConfig().DomainBeaconAttester, beaconState.GenesisValidatorsRoot()) + require.NoError(t, err) + signingRoot, err := signing.ComputeSigningRoot(att1.Data, domain) + assert.NoError(t, err, "Could not get signing root of beacon block header") + sig0 := privKeys[0].Sign(signingRoot[:]) + sig1 := privKeys[1].Sign(signingRoot[:]) + aggregateSig := bls.AggregateSignatures([]bls.Signature{sig0, sig1}) + att1.Signature = aggregateSig.Marshal() + + att2 := util.HydrateIndexedAttestationElectra(ðpb.IndexedAttestationElectra{ + AttestingIndices: []uint64{0, 1}, + }) + signingRoot, err = signing.ComputeSigningRoot(att2.Data, domain) + assert.NoError(t, err, "Could not get signing root of beacon block header") + sig0 = privKeys[0].Sign(signingRoot[:]) + sig1 = privKeys[1].Sign(signingRoot[:]) + aggregateSig = bls.AggregateSignatures([]bls.Signature{sig0, sig1}) + att2.Signature = aggregateSig.Marshal() + slashings := []*ethpb.AttesterSlashingElectra{ + { + Attestation_1: att1, + Attestation_2: att2, + }, + } + b := util.NewBeaconBlockElectra() + b.Block.Body.AttesterSlashings = slashings + wb, err := consensusblocks.NewSignedBeaconBlock(b) + require.NoError(t, err) + service.InsertSlashingsToForkChoiceStore(ctx, wb.Block().Body().AttesterSlashings()) +} + func TestOnBlock_ProcessBlocksParallel(t *testing.T) { service, tr := minimalTestService(t) ctx := tr.ctx diff --git a/beacon-chain/core/blocks/attester_slashing_test.go b/beacon-chain/core/blocks/attester_slashing_test.go index 75c5b7efa0cd..8540489724ee 100644 --- a/beacon-chain/core/blocks/attester_slashing_test.go +++ b/beacon-chain/core/blocks/attester_slashing_test.go @@ -395,3 +395,149 @@ func TestProcessAttesterSlashings_AppliesCorrectStatusCapella(t *testing.T) { require.Equal(t, uint64(31000000000), newState.Balances()[1]) require.Equal(t, uint64(32000000000), newState.Balances()[2]) } + +func TestProcessAttesterSlashings_AppliesCorrectStatusDeneb(t *testing.T) { + beaconState, privKeys := util.DeterministicGenesisStateDeneb(t, 100) + for _, vv := range beaconState.Validators() { + vv.WithdrawableEpoch = primitives.Epoch(params.BeaconConfig().SlotsPerEpoch) + } + + att1 := util.HydrateIndexedAttestation(ðpb.IndexedAttestation{ + Data: ðpb.AttestationData{ + Source: ðpb.Checkpoint{Epoch: 1}, + }, + AttestingIndices: []uint64{0, 1}, + }) + domain, err := signing.Domain(beaconState.Fork(), 0, params.BeaconConfig().DomainBeaconAttester, beaconState.GenesisValidatorsRoot()) + require.NoError(t, err) + signingRoot, err := signing.ComputeSigningRoot(att1.Data, domain) + assert.NoError(t, err, "Could not get signing root of beacon block header") + sig0 := privKeys[0].Sign(signingRoot[:]) + sig1 := privKeys[1].Sign(signingRoot[:]) + aggregateSig := bls.AggregateSignatures([]bls.Signature{sig0, sig1}) + att1.Signature = aggregateSig.Marshal() + + att2 := util.HydrateIndexedAttestation(ðpb.IndexedAttestation{ + AttestingIndices: []uint64{0, 1}, + }) + signingRoot, err = signing.ComputeSigningRoot(att2.Data, domain) + assert.NoError(t, err, "Could not get signing root of beacon block header") + sig0 = privKeys[0].Sign(signingRoot[:]) + sig1 = privKeys[1].Sign(signingRoot[:]) + aggregateSig = bls.AggregateSignatures([]bls.Signature{sig0, sig1}) + att2.Signature = aggregateSig.Marshal() + + slashings := []*ethpb.AttesterSlashing{ + { + Attestation_1: att1, + Attestation_2: att2, + }, + } + + currentSlot := 2 * params.BeaconConfig().SlotsPerEpoch + require.NoError(t, beaconState.SetSlot(currentSlot)) + + b := util.NewBeaconBlock() + b.Block = ðpb.BeaconBlock{ + Body: ðpb.BeaconBlockBody{ + AttesterSlashings: slashings, + }, + } + + ss := make([]ethpb.AttSlashing, len(b.Block.Body.AttesterSlashings)) + for i, s := range b.Block.Body.AttesterSlashings { + ss[i] = s + } + newState, err := blocks.ProcessAttesterSlashings(context.Background(), beaconState, ss, v.SlashValidator) + require.NoError(t, err) + newRegistry := newState.Validators() + + // Given the intersection of slashable indices is [1], only validator + // at index 1 should be slashed and exited. We confirm this below. + if newRegistry[1].ExitEpoch != beaconState.Validators()[1].ExitEpoch { + t.Errorf( + ` + Expected validator at index 1's exit epoch to match + %d, received %d instead + `, + beaconState.Validators()[1].ExitEpoch, + newRegistry[1].ExitEpoch, + ) + } + + require.Equal(t, uint64(31000000000), newState.Balances()[1]) + require.Equal(t, uint64(32000000000), newState.Balances()[2]) +} + +func TestProcessAttesterSlashings_AppliesCorrectStatusElectra(t *testing.T) { + beaconState, privKeys := util.DeterministicGenesisStateElectra(t, 100) + for _, vv := range beaconState.Validators() { + vv.WithdrawableEpoch = primitives.Epoch(params.BeaconConfig().SlotsPerEpoch) + } + + att1 := util.HydrateIndexedAttestationElectra(ðpb.IndexedAttestationElectra{ + Data: ðpb.AttestationData{ + Source: ðpb.Checkpoint{Epoch: 1}, + }, + AttestingIndices: []uint64{0, 1}, + }) + domain, err := signing.Domain(beaconState.Fork(), 0, params.BeaconConfig().DomainBeaconAttester, beaconState.GenesisValidatorsRoot()) + require.NoError(t, err) + signingRoot, err := signing.ComputeSigningRoot(att1.Data, domain) + assert.NoError(t, err, "Could not get signing root of beacon block header") + sig0 := privKeys[0].Sign(signingRoot[:]) + sig1 := privKeys[1].Sign(signingRoot[:]) + aggregateSig := bls.AggregateSignatures([]bls.Signature{sig0, sig1}) + att1.Signature = aggregateSig.Marshal() + + att2 := util.HydrateIndexedAttestationElectra(ðpb.IndexedAttestationElectra{ + AttestingIndices: []uint64{0, 1}, + }) + signingRoot, err = signing.ComputeSigningRoot(att2.Data, domain) + assert.NoError(t, err, "Could not get signing root of beacon block header") + sig0 = privKeys[0].Sign(signingRoot[:]) + sig1 = privKeys[1].Sign(signingRoot[:]) + aggregateSig = bls.AggregateSignatures([]bls.Signature{sig0, sig1}) + att2.Signature = aggregateSig.Marshal() + + slashings := []*ethpb.AttesterSlashingElectra{ + { + Attestation_1: att1, + Attestation_2: att2, + }, + } + + currentSlot := 2 * params.BeaconConfig().SlotsPerEpoch + require.NoError(t, beaconState.SetSlot(currentSlot)) + + b := util.NewBeaconBlockElectra() + b.Block = ðpb.BeaconBlockElectra{ + Body: ðpb.BeaconBlockBodyElectra{ + AttesterSlashings: slashings, + }, + } + + ss := make([]ethpb.AttSlashing, len(b.Block.Body.AttesterSlashings)) + for i, s := range b.Block.Body.AttesterSlashings { + ss[i] = s + } + newState, err := blocks.ProcessAttesterSlashings(context.Background(), beaconState, ss, v.SlashValidator) + require.NoError(t, err) + newRegistry := newState.Validators() + + // Given the intersection of slashable indices is [1], only validator + // at index 1 should be slashed and exited. We confirm this below. + if newRegistry[1].ExitEpoch != beaconState.Validators()[1].ExitEpoch { + t.Errorf( + ` + Expected validator at index 1's exit epoch to match + %d, received %d instead + `, + beaconState.Validators()[1].ExitEpoch, + newRegistry[1].ExitEpoch, + ) + } + + require.Equal(t, uint64(31992187500), newState.Balances()[1]) + require.Equal(t, uint64(32000000000), newState.Balances()[2]) +} diff --git a/beacon-chain/core/transition/transition.go b/beacon-chain/core/transition/transition.go index 0ffe051795e7..3657710c163a 100644 --- a/beacon-chain/core/transition/transition.go +++ b/beacon-chain/core/transition/transition.go @@ -391,11 +391,15 @@ func VerifyOperationLengths(_ context.Context, state state.BeaconState, b interf ) } - if uint64(len(body.AttesterSlashings())) > params.BeaconConfig().MaxAttesterSlashings { + maxSlashings := params.BeaconConfig().MaxAttesterSlashings + if body.Version() >= version.Electra { + maxSlashings = params.BeaconConfig().MaxAttesterSlashingsElectra + } + if uint64(len(body.AttesterSlashings())) > maxSlashings { return nil, fmt.Errorf( "number of attester slashings (%d) in block body exceeds allowed threshold of %d", len(body.AttesterSlashings()), - params.BeaconConfig().MaxAttesterSlashings, + maxSlashings, ) } diff --git a/beacon-chain/core/transition/transition_test.go b/beacon-chain/core/transition/transition_test.go index c03bdbafdc5a..5e71635c8ada 100644 --- a/beacon-chain/core/transition/transition_test.go +++ b/beacon-chain/core/transition/transition_test.go @@ -437,6 +437,25 @@ func TestProcessBlock_OverMaxAttesterSlashings(t *testing.T) { assert.ErrorContains(t, want, err) } +func TestProcessBlock_OverMaxAttesterSlashingsElectra(t *testing.T) { + maxSlashings := params.BeaconConfig().MaxAttesterSlashingsElectra + b := ðpb.SignedBeaconBlockElectra{ + Block: ðpb.BeaconBlockElectra{ + Body: ðpb.BeaconBlockBodyElectra{ + AttesterSlashings: make([]*ethpb.AttesterSlashingElectra, maxSlashings+1), + }, + }, + } + want := fmt.Sprintf("number of attester slashings (%d) in block body exceeds allowed threshold of %d", + len(b.Block.Body.AttesterSlashings), params.BeaconConfig().MaxAttesterSlashingsElectra) + s, err := state_native.InitializeFromProtoUnsafeElectra(ðpb.BeaconStateElectra{}) + require.NoError(t, err) + wsb, err := consensusblocks.NewSignedBeaconBlock(b) + require.NoError(t, err) + _, err = transition.VerifyOperationLengths(context.Background(), s, wsb.Block()) + assert.ErrorContains(t, want, err) +} + func TestProcessBlock_OverMaxAttestations(t *testing.T) { b := ðpb.SignedBeaconBlock{ Block: ðpb.BeaconBlock{ diff --git a/beacon-chain/operations/slashings/BUILD.bazel b/beacon-chain/operations/slashings/BUILD.bazel index 39efbd2d5209..d03d3cea6b01 100644 --- a/beacon-chain/operations/slashings/BUILD.bazel +++ b/beacon-chain/operations/slashings/BUILD.bazel @@ -51,6 +51,7 @@ go_test( "//consensus-types/primitives:go_default_library", "//crypto/bls: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/operations/slashings/service_attester_test.go b/beacon-chain/operations/slashings/service_attester_test.go index 9d705c07184b..cd3b56c259fb 100644 --- a/beacon-chain/operations/slashings/service_attester_test.go +++ b/beacon-chain/operations/slashings/service_attester_test.go @@ -9,23 +9,24 @@ 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/testing/assert" "github.com/prysmaticlabs/prysm/v5/testing/require" "github.com/prysmaticlabs/prysm/v5/testing/util" ) -func validAttesterSlashingForValIdx(t *testing.T, beaconState state.BeaconState, privs []bls.SecretKey, valIdx ...uint64) *ethpb.AttesterSlashing { - var slashings []*ethpb.AttesterSlashing +func validAttesterSlashingForValIdx(t *testing.T, beaconState state.BeaconState, privs []bls.SecretKey, valIdx ...uint64) ethpb.AttSlashing { + var slashings []ethpb.AttSlashing for _, idx := range valIdx { generatedSlashing, err := util.GenerateAttesterSlashingForValidator(beaconState, privs[idx], primitives.ValidatorIndex(idx)) require.NoError(t, err) - slashings = append(slashings, generatedSlashing.(*ethpb.AttesterSlashing)) + slashings = append(slashings, generatedSlashing) } var allSig1 []bls.Signature var allSig2 []bls.Signature for _, slashing := range slashings { - sig1 := slashing.Attestation_1.Signature - sig2 := slashing.Attestation_2.Signature + sig1 := slashing.FirstAttestation().GetSignature() + sig2 := slashing.SecondAttestation().GetSignature() sigFromBytes1, err := bls.SignatureFromBytes(sig1) require.NoError(t, err) sigFromBytes2, err := bls.SignatureFromBytes(sig2) @@ -35,31 +36,52 @@ func validAttesterSlashingForValIdx(t *testing.T, beaconState state.BeaconState, } aggSig1 := bls.AggregateSignatures(allSig1) aggSig2 := bls.AggregateSignatures(allSig2) - aggSlashing := ðpb.AttesterSlashing{ + + if beaconState.Version() >= version.Electra { + return ðpb.AttesterSlashingElectra{ + Attestation_1: ðpb.IndexedAttestationElectra{ + AttestingIndices: valIdx, + Data: slashings[0].FirstAttestation().GetData(), + Signature: aggSig1.Marshal(), + }, + Attestation_2: ðpb.IndexedAttestationElectra{ + AttestingIndices: valIdx, + Data: slashings[0].SecondAttestation().GetData(), + Signature: aggSig2.Marshal(), + }, + } + } + + return ðpb.AttesterSlashing{ Attestation_1: ðpb.IndexedAttestation{ AttestingIndices: valIdx, - Data: slashings[0].Attestation_1.Data, + Data: slashings[0].FirstAttestation().GetData(), Signature: aggSig1.Marshal(), }, Attestation_2: ðpb.IndexedAttestation{ AttestingIndices: valIdx, - Data: slashings[0].Attestation_2.Data, + Data: slashings[0].SecondAttestation().GetData(), Signature: aggSig2.Marshal(), }, } - return aggSlashing } -func attesterSlashingForValIdx(valIdx ...uint64) *ethpb.AttesterSlashing { +func attesterSlashingForValIdx(ver int, valIdx ...uint64) ethpb.AttSlashing { + if ver >= version.Electra { + return ðpb.AttesterSlashingElectra{ + Attestation_1: ðpb.IndexedAttestationElectra{AttestingIndices: valIdx}, + Attestation_2: ðpb.IndexedAttestationElectra{AttestingIndices: valIdx}, + } + } return ðpb.AttesterSlashing{ Attestation_1: ðpb.IndexedAttestation{AttestingIndices: valIdx}, Attestation_2: ðpb.IndexedAttestation{AttestingIndices: valIdx}, } } -func pendingSlashingForValIdx(valIdx ...uint64) *PendingAttesterSlashing { +func pendingSlashingForValIdx(ver int, valIdx ...uint64) *PendingAttesterSlashing { return &PendingAttesterSlashing{ - attesterSlashing: attesterSlashingForValIdx(valIdx...), + attesterSlashing: attesterSlashingForValIdx(ver, valIdx...), validatorToSlash: primitives.ValidatorIndex(valIdx[0]), } } @@ -71,229 +93,245 @@ func TestPool_InsertAttesterSlashing(t *testing.T) { wantErr []bool } type args struct { - slashings []*ethpb.AttesterSlashing - } - - beaconState, privKeys := util.DeterministicGenesisState(t, 64) - pendingSlashings := make([]*PendingAttesterSlashing, 20) - slashings := make([]*ethpb.AttesterSlashing, 20) - for i := 0; i < len(pendingSlashings); i++ { - generatedSl, err := util.GenerateAttesterSlashingForValidator(beaconState, privKeys[i], primitives.ValidatorIndex(i)) - require.NoError(t, err) - pendingSlashings[i] = &PendingAttesterSlashing{ - attesterSlashing: generatedSl, - validatorToSlash: primitives.ValidatorIndex(i), - } - sl, ok := generatedSl.(*ethpb.AttesterSlashing) - require.Equal(t, true, ok, "Attester slashing has the wrong type (expected %T, got %T)", ðpb.AttesterSlashing{}, generatedSl) - slashings[i] = sl + slashings []ethpb.AttSlashing } - require.NoError(t, beaconState.SetSlot(params.BeaconConfig().SlotsPerEpoch)) - - // We mark the following validators with some preconditions. - exitedVal, err := beaconState.ValidatorAtIndex(primitives.ValidatorIndex(2)) - require.NoError(t, err) - exitedVal.WithdrawableEpoch = 0 - require.NoError(t, beaconState.UpdateValidatorAtIndex(primitives.ValidatorIndex(2), exitedVal)) - futureWithdrawVal, err := beaconState.ValidatorAtIndex(primitives.ValidatorIndex(4)) - require.NoError(t, err) - futureWithdrawVal.WithdrawableEpoch = 17 - require.NoError(t, beaconState.UpdateValidatorAtIndex(primitives.ValidatorIndex(4), futureWithdrawVal)) - slashedVal, err := beaconState.ValidatorAtIndex(primitives.ValidatorIndex(5)) - require.NoError(t, err) - slashedVal.Slashed = true - require.NoError(t, beaconState.UpdateValidatorAtIndex(primitives.ValidatorIndex(5), slashedVal)) - slashedVal2, err := beaconState.ValidatorAtIndex(primitives.ValidatorIndex(21)) - require.NoError(t, err) - slashedVal2.Slashed = true - require.NoError(t, beaconState.UpdateValidatorAtIndex(primitives.ValidatorIndex(21), slashedVal2)) - - aggSlashing1 := validAttesterSlashingForValIdx(t, beaconState, privKeys, 0, 1, 2) - aggSlashing2 := validAttesterSlashingForValIdx(t, beaconState, privKeys, 5, 9, 13) - aggSlashing3 := validAttesterSlashingForValIdx(t, beaconState, privKeys, 15, 20, 21) - aggSlashing4 := validAttesterSlashingForValIdx(t, beaconState, privKeys, 2, 5, 21) - - tests := []struct { + type testCase struct { name string fields fields args args want []*PendingAttesterSlashing err string - }{ - { - name: "Empty list", - fields: fields{ - pending: make([]*PendingAttesterSlashing, 0), - included: make(map[primitives.ValidatorIndex]bool), - wantErr: []bool{false}, - }, - args: args{ - slashings: slashings[0:1], - }, - want: []*PendingAttesterSlashing{ - { - attesterSlashing: slashings[0], - validatorToSlash: 0, + } + + setupFunc := func(beaconState state.BeaconState, privKeys []bls.SecretKey) []testCase { + pendingSlashings := make([]*PendingAttesterSlashing, 20) + slashings := make([]ethpb.AttSlashing, 20) + for i := 0; i < len(pendingSlashings); i++ { + generatedSl, err := util.GenerateAttesterSlashingForValidator(beaconState, privKeys[i], primitives.ValidatorIndex(i)) + require.NoError(t, err) + pendingSlashings[i] = &PendingAttesterSlashing{ + attesterSlashing: generatedSl, + validatorToSlash: primitives.ValidatorIndex(i), + } + slashings[i] = generatedSl + } + require.NoError(t, beaconState.SetSlot(params.BeaconConfig().SlotsPerEpoch)) + + // We mark the following validators with some preconditions. + exitedVal, err := beaconState.ValidatorAtIndex(primitives.ValidatorIndex(2)) + require.NoError(t, err) + exitedVal.WithdrawableEpoch = 0 + require.NoError(t, beaconState.UpdateValidatorAtIndex(primitives.ValidatorIndex(2), exitedVal)) + futureWithdrawVal, err := beaconState.ValidatorAtIndex(primitives.ValidatorIndex(4)) + require.NoError(t, err) + futureWithdrawVal.WithdrawableEpoch = 17 + require.NoError(t, beaconState.UpdateValidatorAtIndex(primitives.ValidatorIndex(4), futureWithdrawVal)) + slashedVal, err := beaconState.ValidatorAtIndex(primitives.ValidatorIndex(5)) + require.NoError(t, err) + slashedVal.Slashed = true + require.NoError(t, beaconState.UpdateValidatorAtIndex(primitives.ValidatorIndex(5), slashedVal)) + slashedVal2, err := beaconState.ValidatorAtIndex(primitives.ValidatorIndex(21)) + require.NoError(t, err) + slashedVal2.Slashed = true + require.NoError(t, beaconState.UpdateValidatorAtIndex(primitives.ValidatorIndex(21), slashedVal2)) + + aggSlashing1 := validAttesterSlashingForValIdx(t, beaconState, privKeys, 0, 1, 2) + aggSlashing2 := validAttesterSlashingForValIdx(t, beaconState, privKeys, 5, 9, 13) + aggSlashing3 := validAttesterSlashingForValIdx(t, beaconState, privKeys, 15, 20, 21) + aggSlashing4 := validAttesterSlashingForValIdx(t, beaconState, privKeys, 2, 5, 21) + + tests := []testCase{ + { + name: "Empty list", + fields: fields{ + pending: make([]*PendingAttesterSlashing, 0), + included: make(map[primitives.ValidatorIndex]bool), + wantErr: []bool{false}, }, - }, - }, - { - name: "Empty list two validators slashed", - fields: fields{ - pending: make([]*PendingAttesterSlashing, 0), - included: make(map[primitives.ValidatorIndex]bool), - wantErr: []bool{false, false}, - }, - args: args{ - slashings: slashings[0:2], - }, - want: pendingSlashings[0:2], - }, - { - name: "Duplicate identical slashing", - fields: fields{ - pending: []*PendingAttesterSlashing{ - pendingSlashings[1], + args: args{ + slashings: slashings[0:1], }, - included: make(map[primitives.ValidatorIndex]bool), - wantErr: []bool{true}, - }, - args: args{ - slashings: slashings[1:2], - }, - want: pendingSlashings[1:2], - }, - { - name: "Slashing for already exit validator", - fields: fields{ - pending: []*PendingAttesterSlashing{}, - included: make(map[primitives.ValidatorIndex]bool), - wantErr: []bool{true}, - }, - args: args{ - slashings: slashings[5:6], - }, - want: []*PendingAttesterSlashing{}, - }, - { - name: "Slashing for withdrawable validator", - fields: fields{ - pending: []*PendingAttesterSlashing{}, - included: make(map[primitives.ValidatorIndex]bool), - wantErr: []bool{true}, - }, - args: args{ - slashings: slashings[2:3], - }, - want: []*PendingAttesterSlashing{}, - }, - { - name: "Slashing for slashed validator", - fields: fields{ - pending: []*PendingAttesterSlashing{}, - included: make(map[primitives.ValidatorIndex]bool), - wantErr: []bool{false}, - }, - args: args{ - slashings: slashings[4:5], - }, - want: pendingSlashings[4:5], - }, - { - name: "Already included", - fields: fields{ - pending: []*PendingAttesterSlashing{}, - included: map[primitives.ValidatorIndex]bool{ - 1: true, + want: []*PendingAttesterSlashing{ + { + attesterSlashing: slashings[0], + validatorToSlash: 0, + }, }, - wantErr: []bool{true}, }, - args: args{ - slashings: slashings[1:2], - }, - want: []*PendingAttesterSlashing{}, - }, - { - name: "Maintains sorted order", - fields: fields{ - pending: []*PendingAttesterSlashing{ - pendingSlashings[0], - pendingSlashings[2], + { + name: "Empty list two validators slashed", + fields: fields{ + pending: make([]*PendingAttesterSlashing, 0), + included: make(map[primitives.ValidatorIndex]bool), + wantErr: []bool{false, false}, }, - included: make(map[primitives.ValidatorIndex]bool), - wantErr: []bool{false}, - }, - args: args{ - slashings: slashings[1:2], - }, - want: pendingSlashings[0:3], - }, - { - name: "Doesn't reject partially slashed slashings", - fields: fields{ - pending: []*PendingAttesterSlashing{}, - included: make(map[primitives.ValidatorIndex]bool), - wantErr: []bool{false, false, false, true}, - }, - args: args{ - slashings: []*ethpb.AttesterSlashing{ - aggSlashing1, - aggSlashing2, - aggSlashing3, - aggSlashing4, + args: args{ + slashings: slashings[0:2], }, + want: pendingSlashings[0:2], }, - want: []*PendingAttesterSlashing{ - { - attesterSlashing: aggSlashing1, - validatorToSlash: 0, + { + name: "Duplicate identical slashing", + fields: fields{ + pending: []*PendingAttesterSlashing{ + pendingSlashings[1], + }, + included: make(map[primitives.ValidatorIndex]bool), + wantErr: []bool{true}, + }, + args: args{ + slashings: slashings[1:2], + }, + want: pendingSlashings[1:2], + }, + { + name: "Slashing for already exit validator", + fields: fields{ + pending: []*PendingAttesterSlashing{}, + included: make(map[primitives.ValidatorIndex]bool), + wantErr: []bool{true}, }, - { - attesterSlashing: aggSlashing1, - validatorToSlash: 1, + args: args{ + slashings: slashings[5:6], }, - { - attesterSlashing: aggSlashing2, - validatorToSlash: 9, + want: []*PendingAttesterSlashing{}, + }, + { + name: "Slashing for withdrawable validator", + fields: fields{ + pending: []*PendingAttesterSlashing{}, + included: make(map[primitives.ValidatorIndex]bool), + wantErr: []bool{true}, }, - { - attesterSlashing: aggSlashing2, - validatorToSlash: 13, + args: args{ + slashings: slashings[2:3], }, - { - attesterSlashing: aggSlashing3, - validatorToSlash: 15, + want: []*PendingAttesterSlashing{}, + }, + { + name: "Slashing for slashed validator", + fields: fields{ + pending: []*PendingAttesterSlashing{}, + included: make(map[primitives.ValidatorIndex]bool), + wantErr: []bool{false}, + }, + args: args{ + slashings: slashings[4:5], + }, + want: pendingSlashings[4:5], + }, + { + name: "Already included", + fields: fields{ + pending: []*PendingAttesterSlashing{}, + included: map[primitives.ValidatorIndex]bool{ + 1: true, + }, + wantErr: []bool{true}, }, - { - attesterSlashing: aggSlashing3, - validatorToSlash: 20, + args: args{ + slashings: slashings[1:2], + }, + want: []*PendingAttesterSlashing{}, + }, + { + name: "Maintains sorted order", + fields: fields{ + pending: []*PendingAttesterSlashing{ + pendingSlashings[0], + pendingSlashings[2], + }, + included: make(map[primitives.ValidatorIndex]bool), + wantErr: []bool{false}, + }, + args: args{ + slashings: slashings[1:2], + }, + want: pendingSlashings[0:3], + }, + { + name: "Doesn't reject partially slashed slashings", + fields: fields{ + pending: []*PendingAttesterSlashing{}, + included: make(map[primitives.ValidatorIndex]bool), + wantErr: []bool{false, false, false, true}, + }, + args: args{ + slashings: []ethpb.AttSlashing{ + aggSlashing1, + aggSlashing2, + aggSlashing3, + aggSlashing4, + }, + }, + want: []*PendingAttesterSlashing{ + { + attesterSlashing: aggSlashing1, + validatorToSlash: 0, + }, + { + attesterSlashing: aggSlashing1, + validatorToSlash: 1, + }, + { + attesterSlashing: aggSlashing2, + validatorToSlash: 9, + }, + { + attesterSlashing: aggSlashing2, + validatorToSlash: 13, + }, + { + attesterSlashing: aggSlashing3, + validatorToSlash: 15, + }, + { + attesterSlashing: aggSlashing3, + validatorToSlash: 20, + }, }, }, - }, + } + + return tests } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - p := &Pool{ - pendingAttesterSlashing: tt.fields.pending, - included: tt.fields.included, - } - var err error - for i := 0; i < len(tt.args.slashings); i++ { - err = p.InsertAttesterSlashing(context.Background(), beaconState, tt.args.slashings[i]) - if tt.fields.wantErr[i] { - assert.NotNil(t, err) - } else { - assert.NoError(t, err) + + runFunc := func(beaconState state.BeaconState, tests []testCase) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Pool{ + pendingAttesterSlashing: tt.fields.pending, + included: tt.fields.included, } - } - assert.Equal(t, len(tt.want), len(p.pendingAttesterSlashing)) + var err error + for i := 0; i < len(tt.args.slashings); i++ { + err = p.InsertAttesterSlashing(context.Background(), beaconState, tt.args.slashings[i]) + if tt.fields.wantErr[i] { + assert.NotNil(t, err) + } else { + assert.NoError(t, err) + } + } + assert.Equal(t, len(tt.want), len(p.pendingAttesterSlashing)) - for i := range p.pendingAttesterSlashing { - assert.Equal(t, tt.want[i].validatorToSlash, p.pendingAttesterSlashing[i].validatorToSlash) - assert.DeepEqual(t, tt.want[i].attesterSlashing, p.pendingAttesterSlashing[i].attesterSlashing, "At index %d", i) - } - }) + for i := range p.pendingAttesterSlashing { + assert.Equal(t, tt.want[i].validatorToSlash, p.pendingAttesterSlashing[i].validatorToSlash) + assert.DeepEqual(t, tt.want[i].attesterSlashing, p.pendingAttesterSlashing[i].attesterSlashing, "At index %d", i) + } + }) + } } + + t.Run("phase0", func(t *testing.T) { + beaconState, privKeys := util.DeterministicGenesisState(t, 64) + tests := setupFunc(beaconState, privKeys) + runFunc(beaconState, tests) + }) + t.Run("electra", func(t *testing.T) { + beaconState, privKeys := util.DeterministicGenesisStateElectra(t, 64) + tests := setupFunc(beaconState, privKeys) + runFunc(beaconState, tests) + }) } func TestPool_InsertAttesterSlashing_SigFailsVerify_ClearPool(t *testing.T) { @@ -337,7 +375,7 @@ func TestPool_MarkIncludedAttesterSlashing(t *testing.T) { included map[primitives.ValidatorIndex]bool } type args struct { - slashing *ethpb.AttesterSlashing + slashing ethpb.AttSlashing } tests := []struct { name string @@ -346,22 +384,45 @@ func TestPool_MarkIncludedAttesterSlashing(t *testing.T) { want fields }{ { - name: "Included, does not exist in pending", + name: "phase0 included, does not exist in pending", + fields: fields{ + pending: []*PendingAttesterSlashing{ + { + attesterSlashing: attesterSlashingForValIdx(version.Phase0, 1), + validatorToSlash: 1, + }, + }, + included: make(map[primitives.ValidatorIndex]bool), + }, + args: args{ + slashing: attesterSlashingForValIdx(version.Phase0, 3), + }, + want: fields{ + pending: []*PendingAttesterSlashing{ + pendingSlashingForValIdx(version.Phase0, 1), + }, + included: map[primitives.ValidatorIndex]bool{ + 3: true, + }, + }, + }, + { + name: "electra included, does not exist in pending", fields: fields{ pending: []*PendingAttesterSlashing{ { - attesterSlashing: attesterSlashingForValIdx(1), + attesterSlashing: attesterSlashingForValIdx(version.Electra, 1), validatorToSlash: 1, }, }, included: make(map[primitives.ValidatorIndex]bool), }, args: args{ - slashing: attesterSlashingForValIdx(3), + slashing: attesterSlashingForValIdx(version.Electra, 3), }, want: fields{ pending: []*PendingAttesterSlashing{ - pendingSlashingForValIdx(1), + pendingSlashingForValIdx(version.Electra, 1), }, included: map[primitives.ValidatorIndex]bool{ 3: true, @@ -372,21 +433,21 @@ func TestPool_MarkIncludedAttesterSlashing(t *testing.T) { name: "Removes from pending list", fields: fields{ pending: []*PendingAttesterSlashing{ - pendingSlashingForValIdx(1), - pendingSlashingForValIdx(2), - pendingSlashingForValIdx(3), + pendingSlashingForValIdx(version.Phase0, 1), + pendingSlashingForValIdx(version.Phase0, 2), + pendingSlashingForValIdx(version.Phase0, 3), }, included: map[primitives.ValidatorIndex]bool{ 0: true, }, }, args: args{ - slashing: attesterSlashingForValIdx(2), + slashing: attesterSlashingForValIdx(version.Phase0, 2), }, want: fields{ pending: []*PendingAttesterSlashing{ - pendingSlashingForValIdx(1), - pendingSlashingForValIdx(3), + pendingSlashingForValIdx(version.Phase0, 1), + pendingSlashingForValIdx(version.Phase0, 3), }, included: map[primitives.ValidatorIndex]bool{ 0: true, @@ -398,37 +459,37 @@ func TestPool_MarkIncludedAttesterSlashing(t *testing.T) { name: "Removes from long pending list", fields: fields{ pending: []*PendingAttesterSlashing{ - pendingSlashingForValIdx(1), - pendingSlashingForValIdx(2), - pendingSlashingForValIdx(3), - pendingSlashingForValIdx(4), - pendingSlashingForValIdx(5), - pendingSlashingForValIdx(6), - pendingSlashingForValIdx(7), - pendingSlashingForValIdx(8), - pendingSlashingForValIdx(9), - pendingSlashingForValIdx(10), - pendingSlashingForValIdx(11), + pendingSlashingForValIdx(version.Phase0, 1), + pendingSlashingForValIdx(version.Phase0, 2), + pendingSlashingForValIdx(version.Phase0, 3), + pendingSlashingForValIdx(version.Phase0, 4), + pendingSlashingForValIdx(version.Phase0, 5), + pendingSlashingForValIdx(version.Phase0, 6), + pendingSlashingForValIdx(version.Phase0, 7), + pendingSlashingForValIdx(version.Phase0, 8), + pendingSlashingForValIdx(version.Phase0, 9), + pendingSlashingForValIdx(version.Phase0, 10), + pendingSlashingForValIdx(version.Phase0, 11), }, included: map[primitives.ValidatorIndex]bool{ 0: true, }, }, args: args{ - slashing: attesterSlashingForValIdx(6), + slashing: attesterSlashingForValIdx(version.Phase0, 6), }, want: fields{ pending: []*PendingAttesterSlashing{ - pendingSlashingForValIdx(1), - pendingSlashingForValIdx(2), - pendingSlashingForValIdx(3), - pendingSlashingForValIdx(4), - pendingSlashingForValIdx(5), - pendingSlashingForValIdx(7), - pendingSlashingForValIdx(8), - pendingSlashingForValIdx(9), - pendingSlashingForValIdx(10), - pendingSlashingForValIdx(11), + pendingSlashingForValIdx(version.Phase0, 1), + pendingSlashingForValIdx(version.Phase0, 2), + pendingSlashingForValIdx(version.Phase0, 3), + pendingSlashingForValIdx(version.Phase0, 4), + pendingSlashingForValIdx(version.Phase0, 5), + pendingSlashingForValIdx(version.Phase0, 7), + pendingSlashingForValIdx(version.Phase0, 8), + pendingSlashingForValIdx(version.Phase0, 9), + pendingSlashingForValIdx(version.Phase0, 10), + pendingSlashingForValIdx(version.Phase0, 11), }, included: map[primitives.ValidatorIndex]bool{ 0: true, diff --git a/beacon-chain/rpc/eth/events/events_test.go b/beacon-chain/rpc/eth/events/events_test.go index 98259a5b1b05..1db04bf3ea89 100644 --- a/beacon-chain/rpc/eth/events/events_test.go +++ b/beacon-chain/rpc/eth/events/events_test.go @@ -222,6 +222,39 @@ func operationEventsFixtures(t *testing.T) (*topicRequest, []*feed.Event) { }, }, }, + &feed.Event{ + Type: operation.AttesterSlashingReceived, + Data: &operation.AttesterSlashingReceivedData{ + AttesterSlashing: ð.AttesterSlashingElectra{ + Attestation_1: ð.IndexedAttestationElectra{ + AttestingIndices: []uint64{0, 1}, + Data: ð.AttestationData{ + BeaconBlockRoot: make([]byte, fieldparams.RootLength), + Source: ð.Checkpoint{ + Root: make([]byte, fieldparams.RootLength), + }, + Target: ð.Checkpoint{ + Root: make([]byte, fieldparams.RootLength), + }, + }, + Signature: make([]byte, fieldparams.BLSSignatureLength), + }, + Attestation_2: ð.IndexedAttestationElectra{ + AttestingIndices: []uint64{0, 1}, + Data: ð.AttestationData{ + BeaconBlockRoot: make([]byte, fieldparams.RootLength), + Source: ð.Checkpoint{ + Root: make([]byte, fieldparams.RootLength), + }, + Target: ð.Checkpoint{ + Root: make([]byte, fieldparams.RootLength), + }, + }, + Signature: make([]byte, fieldparams.BLSSignatureLength), + }, + }, + }, + }, &feed.Event{ Type: operation.ProposerSlashingReceived, Data: &operation.ProposerSlashingReceivedData{ @@ -544,7 +577,7 @@ func TestStuckReaderScenarios(t *testing.T) { func wedgedWriterTestCase(t *testing.T, queueDepth func([]*feed.Event) int) { topics, events := operationEventsFixtures(t) - require.Equal(t, 8, len(events)) + require.Equal(t, 9, len(events)) // set eventFeedDepth to a number lower than the events we intend to send to force the server to drop the reader. stn := mockChain.NewEventFeedWrapper() diff --git a/config/params/configset_test.go b/config/params/configset_test.go index ea15a994db2f..bfe4f72c0eb6 100644 --- a/config/params/configset_test.go +++ b/config/params/configset_test.go @@ -132,6 +132,7 @@ func compareConfigs(t *testing.T, expected, actual *BeaconChainConfig) { require.DeepEqual(t, expected.ProportionalSlashingMultiplier, actual.ProportionalSlashingMultiplier) require.DeepEqual(t, expected.MaxProposerSlashings, actual.MaxProposerSlashings) require.DeepEqual(t, expected.MaxAttesterSlashings, actual.MaxAttesterSlashings) + require.DeepEqual(t, expected.MaxAttesterSlashingsElectra, actual.MaxAttesterSlashingsElectra) require.DeepEqual(t, expected.MaxAttestations, actual.MaxAttestations) require.DeepEqual(t, expected.MaxDeposits, actual.MaxDeposits) require.DeepEqual(t, expected.MaxVoluntaryExits, actual.MaxVoluntaryExits) diff --git a/config/params/loader_test.go b/config/params/loader_test.go index f2149a3c7a8d..45e1f215cf0b 100644 --- a/config/params/loader_test.go +++ b/config/params/loader_test.go @@ -139,6 +139,7 @@ func assertEqualConfigs(t *testing.T, name string, fields []string, expected, ac // Max operations per block. assert.Equal(t, expected.MaxProposerSlashings, actual.MaxProposerSlashings, "%s: MaxProposerSlashings", name) assert.Equal(t, expected.MaxAttesterSlashings, actual.MaxAttesterSlashings, "%s: MaxAttesterSlashings", name) + assert.Equal(t, expected.MaxAttesterSlashingsElectra, actual.MaxAttesterSlashingsElectra, "%s: MaxAttesterSlashingsElectra", name) assert.Equal(t, expected.MaxAttestations, actual.MaxAttestations, "%s: MaxAttestations", name) assert.Equal(t, expected.MaxDeposits, actual.MaxDeposits, "%s: MaxDeposits", name) assert.Equal(t, expected.MaxVoluntaryExits, actual.MaxVoluntaryExits, "%s: MaxVoluntaryExits", name) diff --git a/config/params/testnet_config_test.go b/config/params/testnet_config_test.go index 617e77a2abfc..e84d8b387f4f 100644 --- a/config/params/testnet_config_test.go +++ b/config/params/testnet_config_test.go @@ -88,6 +88,7 @@ func compareConfigs(t *testing.T, expected, actual *params.BeaconChainConfig) { require.DeepEqual(t, expected.ProportionalSlashingMultiplier, actual.ProportionalSlashingMultiplier) require.DeepEqual(t, expected.MaxProposerSlashings, actual.MaxProposerSlashings) require.DeepEqual(t, expected.MaxAttesterSlashings, actual.MaxAttesterSlashings) + require.DeepEqual(t, expected.MaxAttesterSlashingsElectra, actual.MaxAttesterSlashingsElectra) require.DeepEqual(t, expected.MaxAttestations, actual.MaxAttestations) require.DeepEqual(t, expected.MaxDeposits, actual.MaxDeposits) require.DeepEqual(t, expected.MaxVoluntaryExits, actual.MaxVoluntaryExits) diff --git a/testing/slasher/simulator/BUILD.bazel b/testing/slasher/simulator/BUILD.bazel index 679ded283765..4639acfe5b7b 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 be06c930958e..6c83845ec6f7 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 d42b4b3a5f4d..210771394921 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 e09f13d95bc0..f53b039567c9 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") } } diff --git a/testing/util/attestation.go b/testing/util/attestation.go index b0a79d727569..65ed193bf32e 100644 --- a/testing/util/attestation.go +++ b/testing/util/attestation.go @@ -381,3 +381,16 @@ func HydrateIndexedAttestation(a *ethpb.IndexedAttestation) *ethpb.IndexedAttest a.Data = HydrateAttestationData(a.Data) return a } + +// HydrateIndexedAttestationElectra hydrates an indexed attestation with correct field length sizes +// to comply with fssz marshalling and unmarshalling rules. +func HydrateIndexedAttestationElectra(a *ethpb.IndexedAttestationElectra) *ethpb.IndexedAttestationElectra { + if a.Signature == nil { + a.Signature = make([]byte, 96) + } + if a.Data == nil { + a.Data = ðpb.AttestationData{} + } + a.Data = HydrateAttestationData(a.Data) + return a +} From b23f02ca88fda0596734678561be778835bf425c Mon Sep 17 00:00:00 2001 From: rkapka Date: Wed, 15 Jan 2025 00:36:17 +0100 Subject: [PATCH 04/10] encode+decode --- beacon-chain/db/kv/blocks.go | 6 +- beacon-chain/db/kv/key.go | 7 +- beacon-chain/db/kv/lightclient.go | 10 +- beacon-chain/db/kv/schema.go | 2 +- beacon-chain/db/kv/state.go | 8 +- beacon-chain/db/slasherkv/pruning_test.go | 5 +- beacon-chain/db/slasherkv/slasher.go | 31 ++- beacon-chain/db/slasherkv/slasher_test.go | 227 ++++++++++++++-------- 8 files changed, 190 insertions(+), 106 deletions(-) diff --git a/beacon-chain/db/kv/blocks.go b/beacon-chain/db/kv/blocks.go index ea91e66e3284..6804c88547bc 100644 --- a/beacon-chain/db/kv/blocks.go +++ b/beacon-chain/db/kv/blocks.go @@ -813,9 +813,9 @@ func unmarshalBlock(_ context.Context, enc []byte) (interfaces.ReadOnlySignedBea if err := rawBlock.UnmarshalSSZ(enc[len(denebBlindKey):]); err != nil { return nil, errors.Wrap(err, "could not unmarshal blinded Deneb block") } - case hasElectraKey(enc): + case HasElectraKey(enc): rawBlock = ðpb.SignedBeaconBlockElectra{} - if err := rawBlock.UnmarshalSSZ(enc[len(electraKey):]); err != nil { + if err := rawBlock.UnmarshalSSZ(enc[len(ElectraKey):]); err != nil { return nil, errors.Wrap(err, "could not unmarshal Electra block") } case hasElectraBlindKey(enc): @@ -856,7 +856,7 @@ func keyForBlock(blk interfaces.ReadOnlySignedBeaconBlock) ([]byte, error) { if blk.IsBlinded() { return electraBlindKey, nil } - return electraKey, nil + return ElectraKey, nil case version.Deneb: if blk.IsBlinded() { return denebBlindKey, nil diff --git a/beacon-chain/db/kv/key.go b/beacon-chain/db/kv/key.go index 60fa9052d3d6..69e52339b348 100644 --- a/beacon-chain/db/kv/key.go +++ b/beacon-chain/db/kv/key.go @@ -52,11 +52,12 @@ func hasDenebBlindKey(enc []byte) bool { return bytes.Equal(enc[:len(denebBlindKey)], denebBlindKey) } -func hasElectraKey(enc []byte) bool { - if len(electraKey) >= len(enc) { +// HasElectraKey verifies if the encoding is Electra compatible. +func HasElectraKey(enc []byte) bool { + if len(ElectraKey) >= len(enc) { return false } - return bytes.Equal(enc[:len(electraKey)], electraKey) + return bytes.Equal(enc[:len(ElectraKey)], ElectraKey) } func hasElectraBlindKey(enc []byte) bool { diff --git a/beacon-chain/db/kv/lightclient.go b/beacon-chain/db/kv/lightclient.go index 2e3c8dc9c417..c6252d3d2432 100644 --- a/beacon-chain/db/kv/lightclient.go +++ b/beacon-chain/db/kv/lightclient.go @@ -104,9 +104,9 @@ func decodeLightClientBootstrap(enc []byte) (interfaces.LightClientBootstrap, er return nil, errors.Wrap(err, "could not unmarshal Deneb light client bootstrap") } m = bootstrap - case hasElectraKey(enc): + case HasElectraKey(enc): bootstrap := ðpb.LightClientBootstrapElectra{} - if err := bootstrap.UnmarshalSSZ(enc[len(electraKey):]); err != nil { + if err := bootstrap.UnmarshalSSZ(enc[len(ElectraKey):]); err != nil { return nil, errors.Wrap(err, "could not unmarshal Electra light client bootstrap") } m = bootstrap @@ -212,9 +212,9 @@ func decodeLightClientUpdate(enc []byte) (interfaces.LightClientUpdate, error) { return nil, errors.Wrap(err, "could not unmarshal Deneb light client update") } m = update - case hasElectraKey(enc): + case HasElectraKey(enc): update := ðpb.LightClientUpdateElectra{} - if err := update.UnmarshalSSZ(enc[len(electraKey):]); err != nil { + if err := update.UnmarshalSSZ(enc[len(ElectraKey):]); err != nil { return nil, errors.Wrap(err, "could not unmarshal Electra light client update") } m = update @@ -227,7 +227,7 @@ func decodeLightClientUpdate(enc []byte) (interfaces.LightClientUpdate, error) { func keyForLightClientUpdate(v int) ([]byte, error) { switch v { case version.Electra: - return electraKey, nil + return ElectraKey, nil case version.Deneb: return denebKey, nil case version.Capella: diff --git a/beacon-chain/db/kv/schema.go b/beacon-chain/db/kv/schema.go index f6648a8f928a..41b1193255f2 100644 --- a/beacon-chain/db/kv/schema.go +++ b/beacon-chain/db/kv/schema.go @@ -52,7 +52,7 @@ var ( saveBlindedBeaconBlocksKey = []byte("save-blinded-beacon-blocks") denebKey = []byte("deneb") denebBlindKey = []byte("blind-deneb") - electraKey = []byte("electra") + ElectraKey = []byte("electra") electraBlindKey = []byte("blind-electra") // block root included in the beacon state used by weak subjectivity initial sync diff --git a/beacon-chain/db/kv/state.go b/beacon-chain/db/kv/state.go index 8f840448d452..3d0930f7fc32 100644 --- a/beacon-chain/db/kv/state.go +++ b/beacon-chain/db/kv/state.go @@ -357,7 +357,7 @@ func (s *Store) processElectra(ctx context.Context, pbState *ethpb.BeaconStateEl if err != nil { return err } - encodedState := snappy.Encode(nil, append(electraKey, rawObj...)) + encodedState := snappy.Encode(nil, append(ElectraKey, rawObj...)) if err := bucket.Put(rootHash, encodedState); err != nil { return err } @@ -517,9 +517,9 @@ func (s *Store) unmarshalState(_ context.Context, enc []byte, validatorEntries [ } switch { - case hasElectraKey(enc): + case HasElectraKey(enc): protoState := ðpb.BeaconStateElectra{} - if err := protoState.UnmarshalSSZ(enc[len(electraKey):]); err != nil { + if err := protoState.UnmarshalSSZ(enc[len(ElectraKey):]); err != nil { return nil, errors.Wrap(err, "failed to unmarshal encoding for Electra") } ok, err := s.isStateValidatorMigrationOver() @@ -675,7 +675,7 @@ func marshalState(ctx context.Context, st state.ReadOnlyBeaconState) ([]byte, er if err != nil { return nil, err } - return snappy.Encode(nil, append(electraKey, rawObj...)), nil + return snappy.Encode(nil, append(ElectraKey, rawObj...)), nil default: return nil, errors.New("invalid inner state") } diff --git a/beacon-chain/db/slasherkv/pruning_test.go b/beacon-chain/db/slasherkv/pruning_test.go index 131fdcbd664b..b8d272accc72 100644 --- a/beacon-chain/db/slasherkv/pruning_test.go +++ b/beacon-chain/db/slasherkv/pruning_test.go @@ -8,6 +8,7 @@ import ( slashertypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/slasher/types" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/require" "github.com/prysmaticlabs/prysm/v5/time/slots" logTest "github.com/sirupsen/logrus/hooks/test" @@ -177,8 +178,8 @@ func TestStore_PruneAttestations_OK(t *testing.T) { if i > 0 { source = target - 1 } - att1 := createAttestationWrapper(source, target, []uint64{attester1}, []byte{0}) - att2 := createAttestationWrapper(source, target, []uint64{attester2}, []byte{1}) + att1 := createAttestationWrapper(version.Phase0, source, target, []uint64{attester1}, []byte{0}) + att2 := createAttestationWrapper(version.Phase0, source, target, []uint64{attester2}, []byte{1}) attestations = append(attestations, att1, att2) } } diff --git a/beacon-chain/db/slasherkv/slasher.go b/beacon-chain/db/slasherkv/slasher.go index 86d1157a0c64..81a56e926406 100644 --- a/beacon-chain/db/slasherkv/slasher.go +++ b/beacon-chain/db/slasherkv/slasher.go @@ -11,11 +11,13 @@ import ( "github.com/golang/snappy" "github.com/pkg/errors" ssz "github.com/prysmaticlabs/fastssz" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/kv" slashertypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/slasher/types" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" bolt "go.etcd.io/bbolt" "golang.org/x/sync/errgroup" ) @@ -692,6 +694,11 @@ func encodeAttestationRecord(att *slashertypes.IndexedAttestationWrapper) ([]byt return []byte{}, errors.New("nil proposal record") } + var versionKey []byte + if att.IndexedAttestation.Version() >= version.Electra { + versionKey = kv.ElectraKey + } + // Encode attestation. encodedAtt, err := att.IndexedAttestation.MarshalSSZ() if err != nil { @@ -701,7 +708,14 @@ func encodeAttestationRecord(att *slashertypes.IndexedAttestationWrapper) ([]byt // Compress attestation. compressedAtt := snappy.Encode(nil, encodedAtt) - return append(att.DataRoot[:], compressedAtt...), nil + enc := make([]byte, len(versionKey)+len(att.DataRoot)+len(compressedAtt)) + if len(versionKey) > 0 { + copy(enc, versionKey) + } + copy(enc[len(versionKey):len(versionKey)+len(att.DataRoot)], att.DataRoot[:]) + copy(enc[len(versionKey)+len(att.DataRoot):], compressedAtt) + + return enc, nil } // Decode attestation record from bytes. @@ -711,6 +725,11 @@ func decodeAttestationRecord(encoded []byte) (*slashertypes.IndexedAttestationWr return nil, fmt.Errorf("wrong length for encoded attestation record, want minimum %d, got %d", rootSize, len(encoded)) } + postElectra := kv.HasElectraKey(encoded) + if postElectra { + encoded = encoded[len(kv.ElectraKey):] + } + // Decompress attestation. decodedAttBytes, err := snappy.Decode(nil, encoded[rootSize:]) if err != nil { @@ -718,8 +737,14 @@ func decodeAttestationRecord(encoded []byte) (*slashertypes.IndexedAttestationWr } // Decode attestation. - decodedAtt := ðpb.IndexedAttestation{} - if err := decodedAtt.UnmarshalSSZ(decodedAttBytes); err != nil { + var decodedAtt ethpb.IndexedAtt + if postElectra { + decodedAtt = ðpb.IndexedAttestationElectra{} + } else { + decodedAtt = ðpb.IndexedAttestation{} + } + + if err = decodedAtt.UnmarshalSSZ(decodedAttBytes); err != nil { return nil, err } diff --git a/beacon-chain/db/slasherkv/slasher_test.go b/beacon-chain/db/slasherkv/slasher_test.go index 292e109042c2..c2bac212deb1 100644 --- a/beacon-chain/db/slasherkv/slasher_test.go +++ b/beacon-chain/db/slasherkv/slasher_test.go @@ -14,20 +14,16 @@ 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/require" ) func TestStore_AttestationRecordForValidator_SaveRetrieve(t *testing.T) { const attestationsCount = 11_000 - - // Create context. ctx := context.Background() - - // Create database. beaconDB := setupDB(t) - - // Define the validator index. - validatorIndex := primitives.ValidatorIndex(1) + phase0ValidatorIndex := primitives.ValidatorIndex(1) + electraValidatorIndex := primitives.ValidatorIndex(2) // Defines attestations to save and retrieve. attWrappers := make([]*slashertypes.IndexedAttestationWrapper, attestationsCount) @@ -36,33 +32,71 @@ func TestStore_AttestationRecordForValidator_SaveRetrieve(t *testing.T) { binary.LittleEndian.PutUint64(dataRoot[:], uint64(i)) attWrapper := createAttestationWrapper( + version.Phase0, primitives.Epoch(i), primitives.Epoch(i+1), - []uint64{uint64(validatorIndex)}, + []uint64{uint64(phase0ValidatorIndex)}, dataRoot[:], ) attWrappers[i] = attWrapper } + attWrappersElectra := make([]*slashertypes.IndexedAttestationWrapper, attestationsCount) + for i := 0; i < attestationsCount; i++ { + var dataRoot [32]byte + binary.LittleEndian.PutUint64(dataRoot[:], uint64(i)) - // Check on a sample of validators that no attestation records are available. - for i := 0; i < attestationsCount; i += 100 { - attRecord, err := beaconDB.AttestationRecordForValidator(ctx, validatorIndex, primitives.Epoch(i+1)) - require.NoError(t, err) - require.Equal(t, true, attRecord == nil) + attWrapper := createAttestationWrapper( + version.Electra, + primitives.Epoch(i), + primitives.Epoch(i+1), + []uint64{uint64(electraValidatorIndex)}, + dataRoot[:], + ) + + attWrappersElectra[i] = attWrapper } - // Save the attestation records to the database. - err := beaconDB.SaveAttestationRecordsForValidators(ctx, attWrappers) - require.NoError(t, err) + type testCase struct { + name string + atts []*slashertypes.IndexedAttestationWrapper + vi primitives.ValidatorIndex + } + testCases := []testCase{ + { + name: "phase0", + atts: attWrappers, + vi: phase0ValidatorIndex, + }, + { + name: "electra", + atts: attWrappersElectra, + vi: electraValidatorIndex, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Check on a sample of validators that no attestation records are available. + for i := 0; i < attestationsCount; i += 100 { + attRecord, err := beaconDB.AttestationRecordForValidator(ctx, tc.vi, primitives.Epoch(i+1)) + require.NoError(t, err) + require.Equal(t, true, attRecord == nil) + } + + // Save the attestation records to the database. + err := beaconDB.SaveAttestationRecordsForValidators(ctx, tc.atts) + require.NoError(t, err) - // Check on a sample of validators that attestation records are available. - for i := 0; i < attestationsCount; i += 100 { - expected := attWrappers[i] - actual, err := beaconDB.AttestationRecordForValidator(ctx, validatorIndex, primitives.Epoch(i+1)) - require.NoError(t, err) + // Check on a sample of validators that attestation records are available. + for i := 0; i < attestationsCount; i += 100 { + expected := attWrappers[i] + actual, err := beaconDB.AttestationRecordForValidator(ctx, tc.vi, primitives.Epoch(i+1)) + require.NoError(t, err) - require.DeepEqual(t, expected.IndexedAttestation.GetData().Source.Epoch, actual.IndexedAttestation.GetData().Source.Epoch) + require.DeepEqual(t, expected.IndexedAttestation.GetData().Source.Epoch, actual.IndexedAttestation.GetData().Source.Epoch) + } + }) } } @@ -108,55 +142,60 @@ func TestStore_LastEpochWrittenForValidators(t *testing.T) { func TestStore_CheckAttesterDoubleVotes(t *testing.T) { ctx := context.Background() - beaconDB := setupDB(t) - err := beaconDB.SaveAttestationRecordsForValidators(ctx, []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{1}), - createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{3}), - }) - require.NoError(t, err) - slashableAtts := []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{2}), // Different signing root. - createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{4}), // Different signing root. - } + for _, ver := range []int{version.Phase0, version.Electra} { + t.Run(version.String(ver), func(t *testing.T) { + beaconDB := setupDB(t) + err := beaconDB.SaveAttestationRecordsForValidators(ctx, []*slashertypes.IndexedAttestationWrapper{ + createAttestationWrapper(version.Phase0, 2, 3, []uint64{0, 1}, []byte{1}), + createAttestationWrapper(version.Phase0, 3, 4, []uint64{2, 3}, []byte{3}), + }) + require.NoError(t, err) - wanted := []*slashertypes.AttesterDoubleVote{ - { - ValidatorIndex: 0, - Target: 3, - Wrapper_1: createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{1}), - Wrapper_2: createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{2}), - }, - { - ValidatorIndex: 1, - Target: 3, - Wrapper_1: createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{1}), - Wrapper_2: createAttestationWrapper(2, 3, []uint64{0, 1}, []byte{2}), - }, - { - ValidatorIndex: 2, - Target: 4, - Wrapper_1: createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{3}), - Wrapper_2: createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{4}), - }, - { - ValidatorIndex: 3, - Target: 4, - Wrapper_1: createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{3}), - Wrapper_2: createAttestationWrapper(3, 4, []uint64{2, 3}, []byte{4}), - }, - } - doubleVotes, err := beaconDB.CheckAttesterDoubleVotes(ctx, slashableAtts) - require.NoError(t, err) - sort.SliceStable(doubleVotes, func(i, j int) bool { - return uint64(doubleVotes[i].ValidatorIndex) < uint64(doubleVotes[j].ValidatorIndex) - }) - require.Equal(t, len(wanted), len(doubleVotes)) - for i, double := range doubleVotes { - require.DeepEqual(t, wanted[i].ValidatorIndex, double.ValidatorIndex) - require.DeepEqual(t, wanted[i].Target, double.Target) - require.DeepEqual(t, wanted[i].Wrapper_1, double.Wrapper_1) - require.DeepEqual(t, wanted[i].Wrapper_2, double.Wrapper_2) + slashableAtts := []*slashertypes.IndexedAttestationWrapper{ + createAttestationWrapper(version.Phase0, 2, 3, []uint64{0, 1}, []byte{2}), // Different signing root. + createAttestationWrapper(version.Phase0, 3, 4, []uint64{2, 3}, []byte{4}), // Different signing root. + } + + wanted := []*slashertypes.AttesterDoubleVote{ + { + ValidatorIndex: 0, + Target: 3, + Wrapper_1: createAttestationWrapper(version.Phase0, 2, 3, []uint64{0, 1}, []byte{1}), + Wrapper_2: createAttestationWrapper(version.Phase0, 2, 3, []uint64{0, 1}, []byte{2}), + }, + { + ValidatorIndex: 1, + Target: 3, + Wrapper_1: createAttestationWrapper(version.Phase0, 2, 3, []uint64{0, 1}, []byte{1}), + Wrapper_2: createAttestationWrapper(version.Phase0, 2, 3, []uint64{0, 1}, []byte{2}), + }, + { + ValidatorIndex: 2, + Target: 4, + Wrapper_1: createAttestationWrapper(version.Phase0, 3, 4, []uint64{2, 3}, []byte{3}), + Wrapper_2: createAttestationWrapper(version.Phase0, 3, 4, []uint64{2, 3}, []byte{4}), + }, + { + ValidatorIndex: 3, + Target: 4, + Wrapper_1: createAttestationWrapper(version.Phase0, 3, 4, []uint64{2, 3}, []byte{3}), + Wrapper_2: createAttestationWrapper(version.Phase0, 3, 4, []uint64{2, 3}, []byte{4}), + }, + } + doubleVotes, err := beaconDB.CheckAttesterDoubleVotes(ctx, slashableAtts) + require.NoError(t, err) + sort.SliceStable(doubleVotes, func(i, j int) bool { + return uint64(doubleVotes[i].ValidatorIndex) < uint64(doubleVotes[j].ValidatorIndex) + }) + require.Equal(t, len(wanted), len(doubleVotes)) + for i, double := range doubleVotes { + require.DeepEqual(t, wanted[i].ValidatorIndex, double.ValidatorIndex) + require.DeepEqual(t, wanted[i].Target, double.Target) + require.DeepEqual(t, wanted[i].Wrapper_1, double.Wrapper_1) + require.DeepEqual(t, wanted[i].Wrapper_2, double.Wrapper_2) + } + }) } } @@ -376,12 +415,20 @@ func Test_encodeDecodeAttestationRecord(t *testing.T) { wantErr bool }{ { - name: "empty standard encode/decode", - attWrapper: createAttestationWrapper(0, 0, nil /* indices */, nil /* signingRoot */), + name: "phase0 empty standard encode/decode", + attWrapper: createAttestationWrapper(version.Phase0, 0, 0, nil /* indices */, nil /* signingRoot */), }, { - name: "standard encode/decode", - attWrapper: createAttestationWrapper(15, 6, []uint64{2, 4}, []byte("1") /* signingRoot */), + name: "phase0 standard encode/decode", + attWrapper: createAttestationWrapper(version.Phase0, 15, 6, []uint64{2, 4}, []byte("1") /* signingRoot */), + }, + { + name: "electra empty standard encode/decode", + attWrapper: createAttestationWrapper(version.Electra, 0, 0, nil /* indices */, nil /* signingRoot */), + }, + { + name: "electra standard encode/decode", + attWrapper: createAttestationWrapper(version.Electra, 15, 6, []uint64{2, 4}, []byte("1") /* signingRoot */), }, { name: "failing encode/decode", @@ -433,7 +480,7 @@ func TestStore_HighestAttestations(t *testing.T) { { name: "should get highest att if single att in db", attestationsInDB: []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapper(0, 3, []uint64{1}, []byte{1}), + createAttestationWrapper(version.Phase0, 0, 3, []uint64{1}, []byte{1}), }, indices: []primitives.ValidatorIndex{1}, expected: []*ethpb.HighestAttestation{ @@ -447,10 +494,10 @@ func TestStore_HighestAttestations(t *testing.T) { { name: "should get highest att for multiple with diff histories", attestationsInDB: []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapper(0, 3, []uint64{2}, []byte{1}), - createAttestationWrapper(1, 4, []uint64{3}, []byte{2}), - createAttestationWrapper(2, 3, []uint64{4}, []byte{3}), - createAttestationWrapper(5, 6, []uint64{5}, []byte{4}), + createAttestationWrapper(version.Phase0, 0, 3, []uint64{2}, []byte{1}), + createAttestationWrapper(version.Phase0, 1, 4, []uint64{3}, []byte{2}), + createAttestationWrapper(version.Phase0, 2, 3, []uint64{4}, []byte{3}), + createAttestationWrapper(version.Phase0, 5, 6, []uint64{5}, []byte{4}), }, indices: []primitives.ValidatorIndex{2, 3, 4, 5}, expected: []*ethpb.HighestAttestation{ @@ -479,10 +526,10 @@ func TestStore_HighestAttestations(t *testing.T) { { name: "should get correct highest att for multiple shared atts with diff histories", attestationsInDB: []*slashertypes.IndexedAttestationWrapper{ - createAttestationWrapper(1, 4, []uint64{2, 3}, []byte{1}), - createAttestationWrapper(2, 5, []uint64{3, 5}, []byte{2}), - createAttestationWrapper(4, 5, []uint64{1, 2}, []byte{3}), - createAttestationWrapper(6, 7, []uint64{5}, []byte{4}), + createAttestationWrapper(version.Phase0, 1, 4, []uint64{2, 3}, []byte{1}), + createAttestationWrapper(version.Phase0, 2, 5, []uint64{3, 5}, []byte{2}), + createAttestationWrapper(version.Phase0, 4, 5, []uint64{1, 2}, []byte{3}), + createAttestationWrapper(version.Phase0, 6, 7, []uint64{5}, []byte{4}), }, indices: []primitives.ValidatorIndex{2, 3, 4, 5}, expected: []*ethpb.HighestAttestation{ @@ -533,7 +580,7 @@ func BenchmarkHighestAttestations(b *testing.B) { } atts := make([]*slashertypes.IndexedAttestationWrapper, count) for i := 0; i < count; i++ { - atts[i] = createAttestationWrapper(primitives.Epoch(i), primitives.Epoch(i+2), indicesPerAtt[i], []byte{}) + atts[i] = createAttestationWrapper(version.Phase0, primitives.Epoch(i), primitives.Epoch(i+2), indicesPerAtt[i], []byte{}) } ctx := context.Background() @@ -570,7 +617,7 @@ func BenchmarkStore_CheckDoubleBlockProposals(b *testing.B) { } atts := make([]*slashertypes.IndexedAttestationWrapper, count) for i := 0; i < count; i++ { - atts[i] = createAttestationWrapper(primitives.Epoch(i), primitives.Epoch(i+2), indicesPerAtt[i], []byte{}) + atts[i] = createAttestationWrapper(version.Phase0, primitives.Epoch(i), primitives.Epoch(i+2), indicesPerAtt[i], []byte{}) } ctx := context.Background() @@ -609,7 +656,7 @@ func createProposalWrapper(t *testing.T, slot primitives.Slot, proposerIndex pri } } -func createAttestationWrapper(source, target primitives.Epoch, indices []uint64, dataRootBytes []byte) *slashertypes.IndexedAttestationWrapper { +func createAttestationWrapper(ver int, source, target primitives.Epoch, indices []uint64, dataRootBytes []byte) *slashertypes.IndexedAttestationWrapper { dataRoot := bytesutil.ToBytes32(dataRootBytes) if dataRootBytes == nil { dataRoot = params.BeaconConfig().ZeroHash @@ -627,6 +674,16 @@ func createAttestationWrapper(source, target primitives.Epoch, indices []uint64, }, } + 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, From 20bd8878ff8b512e10bd485b24548fb1207c7987 Mon Sep 17 00:00:00 2001 From: rkapka Date: Thu, 16 Jan 2025 21:54:42 +0100 Subject: [PATCH 05/10] timer --- beacon-chain/blockchain/service_test.go | 2 +- beacon-chain/db/slasherkv/BUILD.bazel | 3 + beacon-chain/node/node.go | 39 +++++- beacon-chain/operations/slashings/BUILD.bazel | 2 + beacon-chain/operations/slashings/service.go | 113 +++++++++++++++++- beacon-chain/operations/slashings/types.go | 10 ++ .../prysm/v1alpha1/beacon/slashings_test.go | 10 +- .../validator/proposer_slashings_test.go | 2 +- .../prysm/v1alpha1/validator/proposer_test.go | 2 +- beacon-chain/sync/subscriber_test.go | 4 +- 10 files changed, 167 insertions(+), 20 deletions(-) diff --git a/beacon-chain/blockchain/service_test.go b/beacon-chain/blockchain/service_test.go index fee2705842fc..331667e56f94 100644 --- a/beacon-chain/blockchain/service_test.go +++ b/beacon-chain/blockchain/service_test.go @@ -95,7 +95,7 @@ func setupBeaconChain(t *testing.T, beaconDB db.Database) *Service { WithDepositCache(depositCache), WithChainStartFetcher(web3Service), WithAttestationPool(attestations.NewPool()), - WithSlashingPool(slashings.NewPool()), + WithSlashingPool(slashings.NewPool(false)), WithExitPool(voluntaryexits.NewPool()), WithP2PBroadcaster(&mockBroadcaster{}), WithStateNotifier(&mockBeaconNode{}), diff --git a/beacon-chain/db/slasherkv/BUILD.bazel b/beacon-chain/db/slasherkv/BUILD.bazel index 3c9fe389817f..6615999499e7 100644 --- a/beacon-chain/db/slasherkv/BUILD.bazel +++ b/beacon-chain/db/slasherkv/BUILD.bazel @@ -15,6 +15,7 @@ go_library( visibility = ["//beacon-chain:__subpackages__"], deps = [ "//beacon-chain/db/iface:go_default_library", + "//beacon-chain/db/kv:go_default_library", "//beacon-chain/slasher/types:go_default_library", "//config/params:go_default_library", "//consensus-types/primitives:go_default_library", @@ -22,6 +23,7 @@ go_library( "//io/file: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_golang_snappy//:go_default_library", "@com_github_pkg_errors//:go_default_library", @@ -50,6 +52,7 @@ go_test( "//consensus-types/primitives:go_default_library", "//encoding/bytesutil:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//runtime/version:go_default_library", "//testing/require:go_default_library", "//time/slots:go_default_library", "@com_github_prysmaticlabs_fastssz//:go_default_library", diff --git a/beacon-chain/node/node.go b/beacon-chain/node/node.go index 65c447f086fd..3b2e146de516 100644 --- a/beacon-chain/node/node.go +++ b/beacon-chain/node/node.go @@ -96,7 +96,6 @@ type BeaconNode struct { attestationCache *cache.AttestationCache attestationPool attestations.Pool exitPool voluntaryexits.PoolManager - slashingsPool slashings.PoolManager syncCommitteePool synccommittee.Pool blsToExecPool blstoexec.PoolManager depositCache cache.DepositCache @@ -148,7 +147,6 @@ func New(cliCtx *cli.Context, cancel context.CancelFunc, opts ...Option) (*Beaco attestationCache: cache.NewAttestationCache(), attestationPool: attestations.NewPool(), exitPool: voluntaryexits.NewPool(), - slashingsPool: slashings.NewPool(), syncCommitteePool: synccommittee.NewPool(), blsToExecPool: blstoexec.NewPool(), trackedValidatorsCache: cache.NewTrackedValidatorsCache(), @@ -321,6 +319,11 @@ func registerServices(cliCtx *cli.Context, beacon *BeaconNode, synchronizer *sta return errors.Wrap(err, "could not register attestation pool service") } + log.Debugln("Registering Slashing Pool Service") + if err := beacon.registerSlashingPool(); err != nil { + return errors.Wrap(err, "could not register slashing pool service") + } + log.Debugln("Registering Blockchain Service") if err := beacon.registerBlockchainService(beacon.forkChoicer, synchronizer, beacon.initialSyncComplete); err != nil { return errors.Wrap(err, "could not register blockchain service") @@ -716,6 +719,11 @@ func (b *BeaconNode) registerAttestationPool() error { return b.services.RegisterService(s) } +func (b *BeaconNode) registerSlashingPool() error { + s := slashings.NewPool(b.ctx, b.clockWaiter, slashings.WithElectraTimer()) + return b.services.RegisterService(s) +} + func (b *BeaconNode) registerBlockchainService(fc forkchoice.ForkChoicer, gs *startup.ClockSynchronizer, syncComplete chan struct{}) error { var web3Service *execution.Service if err := b.services.FetchService(&web3Service); err != nil { @@ -727,6 +735,11 @@ func (b *BeaconNode) registerBlockchainService(fc forkchoice.ForkChoicer, gs *st return err } + var slashingPoolService *slashings.Pool + if err := b.services.FetchService(&slashingPoolService); err != nil { + return err + } + // skipcq: CRT-D0001 opts := append( b.serviceFlagOpts.blockchainFlagOpts, @@ -738,7 +751,7 @@ func (b *BeaconNode) registerBlockchainService(fc forkchoice.ForkChoicer, gs *st blockchain.WithAttestationCache(b.attestationCache), blockchain.WithAttestationPool(b.attestationPool), blockchain.WithExitPool(b.exitPool), - blockchain.WithSlashingPool(b.slashingsPool), + blockchain.WithSlashingPool(slashingPoolService), blockchain.WithBLSToExecPool(b.blsToExecPool), blockchain.WithP2PBroadcaster(b.fetchP2P()), blockchain.WithStateNotifier(b), @@ -811,6 +824,11 @@ func (b *BeaconNode) registerSyncService(initialSyncComplete chan struct{}, bFil return err } + var slashingPoolService *slashings.Pool + if err := b.services.FetchService(&slashingPoolService); err != nil { + return err + } + rs := regularsync.NewService( b.ctx, regularsync.WithDatabase(b.db), @@ -823,7 +841,7 @@ func (b *BeaconNode) registerSyncService(initialSyncComplete chan struct{}, bFil regularsync.WithAttestationCache(b.attestationCache), regularsync.WithAttestationPool(b.attestationPool), regularsync.WithExitPool(b.exitPool), - regularsync.WithSlashingPool(b.slashingsPool), + regularsync.WithSlashingPool(slashingPoolService), regularsync.WithSyncCommsPool(b.syncCommitteePool), regularsync.WithBlsToExecPool(b.blsToExecPool), regularsync.WithStateGen(b.stateGen), @@ -875,6 +893,10 @@ func (b *BeaconNode) registerSlasherService() error { if err := b.services.FetchService(&syncService); err != nil { return err } + var slashingPoolService *slashings.Pool + if err := b.services.FetchService(&slashingPoolService); err != nil { + return err + } slasherSrv, err := slasher.New(b.ctx, &slasher.ServiceConfig{ IndexedAttestationsFeed: b.slasherAttestationsFeed, @@ -883,7 +905,7 @@ func (b *BeaconNode) registerSlasherService() error { StateNotifier: b, AttestationStateFetcher: chainService, StateGen: b.stateGen, - SlashingPoolInserter: b.slashingsPool, + SlashingPoolInserter: slashingPoolService, SyncChecker: syncService, HeadStateFetcher: chainService, ClockWaiter: b.clockWaiter, @@ -917,6 +939,11 @@ func (b *BeaconNode) registerRPCService(router *http.ServeMux) error { } } + var slashingPoolService *slashings.Pool + if err := b.services.FetchService(&slashingPoolService); err != nil { + return err + } + depositFetcher := b.depositCache chainStartFetcher := web3Service @@ -960,7 +987,7 @@ func (b *BeaconNode) registerRPCService(router *http.ServeMux) error { AttestationCache: b.attestationCache, AttestationsPool: b.attestationPool, ExitPool: b.exitPool, - SlashingsPool: b.slashingsPool, + SlashingsPool: slashingPoolService, BLSChangesPool: b.blsToExecPool, SyncCommitteeObjectPool: b.syncCommitteePool, ExecutionChainService: web3Service, diff --git a/beacon-chain/operations/slashings/BUILD.bazel b/beacon-chain/operations/slashings/BUILD.bazel index d03d3cea6b01..0481c0e850e9 100644 --- a/beacon-chain/operations/slashings/BUILD.bazel +++ b/beacon-chain/operations/slashings/BUILD.bazel @@ -19,6 +19,7 @@ go_library( "//beacon-chain/core/blocks:go_default_library", "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/core/time:go_default_library", + "//beacon-chain/startup:go_default_library", "//beacon-chain/state:go_default_library", "//config/params:go_default_library", "//consensus-types/primitives:go_default_library", @@ -26,6 +27,7 @@ go_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", "@com_github_prometheus_client_golang//prometheus/promauto:go_default_library", diff --git a/beacon-chain/operations/slashings/service.go b/beacon-chain/operations/slashings/service.go index 561c77da225d..7bbe6c3c887a 100644 --- a/beacon-chain/operations/slashings/service.go +++ b/beacon-chain/operations/slashings/service.go @@ -4,11 +4,13 @@ import ( "context" "fmt" "sort" + "time" "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" - "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time" + coretime "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/startup" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" @@ -16,16 +18,37 @@ import ( "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" 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/trailofbits/go-mutexasserts" ) +// WithElectraTimer includes functional options for the blockchain service related to CLI flags. +func WithElectraTimer() Option { + return func(p *Pool) error { + p.runElectraTimer = true + return nil + } +} + // NewPool returns an initialized attester slashing and proposer slashing pool. -func NewPool() *Pool { - return &Pool{ +func NewPool(ctx context.Context, cw startup.ClockWaiter, opts ...Option) *Pool { + ctx, cancel := context.WithCancel(ctx) + p := &Pool{ + ctx: ctx, + cancel: cancel, pendingProposerSlashing: make([]*ethpb.ProposerSlashing, 0), pendingAttesterSlashing: make([]*PendingAttesterSlashing, 0), included: make(map[primitives.ValidatorIndex]bool), + cw: cw, } + + for _, opt := range opts { + if err := opt(p); err != nil { + return nil + } + } + + return p } // PendingAttesterSlashings returns attester slashings that are able to be included into a block. @@ -291,8 +314,90 @@ func (p *Pool) validatorSlashingPreconditionCheck( return false, err } // Checking if the validator is slashable. - if !helpers.IsSlashableValidatorUsingTrie(validator, time.CurrentEpoch(state)) { + if !helpers.IsSlashableValidatorUsingTrie(validator, coretime.CurrentEpoch(state)) { return false, nil } return true, nil } + +func (p *Pool) convertToElectra() { + p.lock.Lock() + defer p.lock.Unlock() + + for _, pas := range p.pendingAttesterSlashing { + if pas.attesterSlashing.Version() == version.Phase0 { + first := pas.attesterSlashing.FirstAttestation() + second := pas.attesterSlashing.SecondAttestation() + pas.attesterSlashing = ðpb.AttesterSlashingElectra{ + Attestation_1: ðpb.IndexedAttestationElectra{ + AttestingIndices: first.GetAttestingIndices(), + Data: first.GetData(), + Signature: first.GetSignature(), + }, + Attestation_2: ðpb.IndexedAttestationElectra{ + AttestingIndices: second.GetAttestingIndices(), + Data: second.GetData(), + Signature: second.GetSignature(), + }, + } + } + } +} + +// Start the slashing pool service. +func (p *Pool) Start() { + go p.run() +} + +func (p *Pool) run() { + if !p.runElectraTimer { + return + } + + p.waitForChainInitialization() + + electraSlot, err := slots.EpochStart(params.BeaconConfig().ElectraForkEpoch) + if err != nil { + log.WithError(err).Error("Could not get Electra start slot") + return + } + electraTime, err := slots.ToTime(uint64(p.genesisTime.Unix()), electraSlot) + if err != nil { + log.WithError(err).Error("Could not get Electra start time") + return + } + + t := time.NewTimer(electraTime.Sub(time.Now())) + go func() { + defer t.Stop() + select { + case <-t.C: + log.Info("Converting Phase0 slashings to Electra slashings") + p.convertToElectra() + case <-p.ctx.Done(): + log.Warn("Context cancelled, Electra timer will not execute") + return + } + }() +} +func (p *Pool) waitForChainInitialization() { + clock, err := p.cw.WaitForClock(p.ctx) + if err != nil { + log.WithError(err).Error("Could not receive chain start notification") + } + p.genesisTime = clock.GenesisTime() + log.WithField("genesisTime", p.genesisTime).Info( + "Slashing pool received chain initialization event", + ) +} + +// Stop the slashing pool service. +func (p *Pool) Stop() error { + p.cancel() + return nil +} + +// Status of the slashing pool service. +func (p *Pool) Status() error { + return nil +} diff --git a/beacon-chain/operations/slashings/types.go b/beacon-chain/operations/slashings/types.go index 36e4bc17c637..c7baf9b2263a 100644 --- a/beacon-chain/operations/slashings/types.go +++ b/beacon-chain/operations/slashings/types.go @@ -3,7 +3,9 @@ package slashings import ( "context" "sync" + "time" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/startup" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" @@ -33,12 +35,20 @@ type PoolManager interface { MarkIncludedProposerSlashing(ps *ethpb.ProposerSlashing) } +// Option for pool configuration. +type Option func(p *Pool) error + // Pool is a concrete implementation of PoolManager. type Pool struct { lock sync.RWMutex + ctx context.Context + cancel context.CancelFunc + cw startup.ClockWaiter + genesisTime time.Time pendingProposerSlashing []*ethpb.ProposerSlashing pendingAttesterSlashing []*PendingAttesterSlashing included map[primitives.ValidatorIndex]bool + runElectraTimer bool } // PendingAttesterSlashing represents an attester slashing in the operation pool. diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/slashings_test.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/slashings_test.go index 63bd3a92a158..d9e317aed5d8 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/beacon/slashings_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/slashings_test.go @@ -31,7 +31,7 @@ func TestServer_SubmitProposerSlashing(t *testing.T) { HeadFetcher: &mock.ChainService{ State: st, }, - SlashingsPool: slashings.NewPool(), + SlashingsPool: slashings.NewPool(false), Broadcaster: mb, } @@ -61,7 +61,7 @@ func TestServer_SubmitAttesterSlashing(t *testing.T) { HeadFetcher: &mock.ChainService{ State: st, }, - SlashingsPool: slashings.NewPool(), + SlashingsPool: slashings.NewPool(false), Broadcaster: mb, } @@ -92,7 +92,7 @@ func TestServer_SubmitProposerSlashing_DontBroadcast(t *testing.T) { HeadFetcher: &mock.ChainService{ State: st, }, - SlashingsPool: slashings.NewPool(), + SlashingsPool: slashings.NewPool(false), Broadcaster: mb, } @@ -139,7 +139,7 @@ func TestServer_SubmitAttesterSlashing_DontBroadcast(t *testing.T) { HeadFetcher: &mock.ChainService{ State: st, }, - SlashingsPool: slashings.NewPool(), + SlashingsPool: slashings.NewPool(false), Broadcaster: mb, } @@ -180,7 +180,7 @@ func TestServer_SubmitAttesterSlashingElectra(t *testing.T) { HeadFetcher: &mock.ChainService{ State: st, }, - SlashingsPool: slashings.NewPool(), + SlashingsPool: slashings.NewPool(false), Broadcaster: mb, } diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_slashings_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_slashings_test.go index 5bc23f1e9fc2..1a28d204f767 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_slashings_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_slashings_test.go @@ -16,7 +16,7 @@ func TestServer_getSlashings(t *testing.T) { beaconState, privKeys := util.DeterministicGenesisState(t, 64) proposerServer := &Server{ - SlashingsPool: slashings.NewPool(), + SlashingsPool: slashings.NewPool(false), } proposerSlashings := make([]*ethpb.ProposerSlashing, params.BeaconConfig().MaxProposerSlashings) diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go index 4ce0ff683467..55fa8593ab0c 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go @@ -721,7 +721,7 @@ func getProposerServer(db db.HeadAccessDatabase, headState state.BeaconState, he ForkchoiceFetcher: mockChainService, MockEth1Votes: true, AttPool: attestations.NewPool(), - SlashingsPool: slashings.NewPool(), + SlashingsPool: slashings.NewPool(false), ExitPool: voluntaryexits.NewPool(), StateGen: stategen.New(db, doublylinkedtree.New()), SyncCommitteePool: synccommittee.NewStore(), diff --git a/beacon-chain/sync/subscriber_test.go b/beacon-chain/sync/subscriber_test.go index 0840313c8013..00ecd01a9e72 100644 --- a/beacon-chain/sync/subscriber_test.go +++ b/beacon-chain/sync/subscriber_test.go @@ -141,7 +141,7 @@ func TestSubscribe_ReceivesAttesterSlashing(t *testing.T) { cfg: &config{ p2p: p2pService, initialSync: &mockSync.Sync{IsSyncing: false}, - slashingPool: slashings.NewPool(), + slashingPool: slashings.NewPool(false), chain: chainService, clock: startup.NewClock(gt, vr), beaconDB: d, @@ -194,7 +194,7 @@ func TestSubscribe_ReceivesProposerSlashing(t *testing.T) { cfg: &config{ p2p: p2pService, initialSync: &mockSync.Sync{IsSyncing: false}, - slashingPool: slashings.NewPool(), + slashingPool: slashings.NewPool(false), chain: chainService, beaconDB: d, clock: startup.NewClock(chainService.Genesis, chainService.ValidatorsRoot), From 6607ba0de9332fb55fd12e2a6b91de7e2a34763b Mon Sep 17 00:00:00 2001 From: rkapka Date: Thu, 16 Jan 2025 22:39:39 +0100 Subject: [PATCH 06/10] test fixes --- beacon-chain/blockchain/service_test.go | 7 +++++-- .../rpc/prysm/v1alpha1/beacon/BUILD.bazel | 1 + .../prysm/v1alpha1/beacon/slashings_test.go | 11 ++++++----- .../validator/proposer_slashings_test.go | 5 ++++- .../prysm/v1alpha1/validator/proposer_test.go | 19 ++++++++++--------- beacon-chain/sync/subscriber_test.go | 4 ++-- 6 files changed, 28 insertions(+), 19 deletions(-) diff --git a/beacon-chain/blockchain/service_test.go b/beacon-chain/blockchain/service_test.go index 331667e56f94..b28257cfa594 100644 --- a/beacon-chain/blockchain/service_test.go +++ b/beacon-chain/blockchain/service_test.go @@ -71,6 +71,9 @@ func setupBeaconChain(t *testing.T, beaconDB db.Database) *Service { DepositContainers: []*ethpb.DepositContainer{}, }) require.NoError(t, err) + + clockSync := startup.NewClockSynchronizer() + web3Service, err = execution.NewService( ctx, execution.WithDatabase(beaconDB), @@ -95,7 +98,7 @@ func setupBeaconChain(t *testing.T, beaconDB db.Database) *Service { WithDepositCache(depositCache), WithChainStartFetcher(web3Service), WithAttestationPool(attestations.NewPool()), - WithSlashingPool(slashings.NewPool(false)), + WithSlashingPool(slashings.NewPool(ctx, clockSync)), WithExitPool(voluntaryexits.NewPool()), WithP2PBroadcaster(&mockBroadcaster{}), WithStateNotifier(&mockBeaconNode{}), @@ -103,7 +106,7 @@ func setupBeaconChain(t *testing.T, beaconDB db.Database) *Service { WithAttestationService(attService), WithStateGen(stateGen), WithPayloadIDCache(cache.NewPayloadIDCache()), - WithClockSynchronizer(startup.NewClockSynchronizer()), + WithClockSynchronizer(clockSync), } chainService, err := NewService(ctx, opts...) diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/BUILD.bazel b/beacon-chain/rpc/prysm/v1alpha1/beacon/BUILD.bazel index 81c180d28d46..87270167ba18 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/beacon/BUILD.bazel +++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/BUILD.bazel @@ -86,6 +86,7 @@ go_test( "//beacon-chain/operations/slashings:go_default_library", "//beacon-chain/p2p/testing:go_default_library", "//beacon-chain/rpc/core:go_default_library", + "//beacon-chain/startup:go_default_library", "//beacon-chain/state:go_default_library", "//beacon-chain/state/state-native:go_default_library", "//beacon-chain/state/stategen:go_default_library", diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/slashings_test.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/slashings_test.go index d9e317aed5d8..7073d8737508 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/beacon/slashings_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/slashings_test.go @@ -7,6 +7,7 @@ import ( mock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/slashings" mockp2p "github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/testing" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/startup" "github.com/prysmaticlabs/prysm/v5/config/features" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" @@ -31,7 +32,7 @@ func TestServer_SubmitProposerSlashing(t *testing.T) { HeadFetcher: &mock.ChainService{ State: st, }, - SlashingsPool: slashings.NewPool(false), + SlashingsPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), Broadcaster: mb, } @@ -61,7 +62,7 @@ func TestServer_SubmitAttesterSlashing(t *testing.T) { HeadFetcher: &mock.ChainService{ State: st, }, - SlashingsPool: slashings.NewPool(false), + SlashingsPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), Broadcaster: mb, } @@ -92,7 +93,7 @@ func TestServer_SubmitProposerSlashing_DontBroadcast(t *testing.T) { HeadFetcher: &mock.ChainService{ State: st, }, - SlashingsPool: slashings.NewPool(false), + SlashingsPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), Broadcaster: mb, } @@ -139,7 +140,7 @@ func TestServer_SubmitAttesterSlashing_DontBroadcast(t *testing.T) { HeadFetcher: &mock.ChainService{ State: st, }, - SlashingsPool: slashings.NewPool(false), + SlashingsPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), Broadcaster: mb, } @@ -180,7 +181,7 @@ func TestServer_SubmitAttesterSlashingElectra(t *testing.T) { HeadFetcher: &mock.ChainService{ State: st, }, - SlashingsPool: slashings.NewPool(false), + SlashingsPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), Broadcaster: mb, } diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_slashings_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_slashings_test.go index 1a28d204f767..093f1c10ebaf 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_slashings_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_slashings_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/slashings" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/startup" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" @@ -13,10 +14,12 @@ import ( ) func TestServer_getSlashings(t *testing.T) { + ctx := context.Background() + beaconState, privKeys := util.DeterministicGenesisState(t, 64) proposerServer := &Server{ - SlashingsPool: slashings.NewPool(false), + SlashingsPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), } proposerSlashings := make([]*ethpb.ProposerSlashing, params.BeaconConfig().MaxProposerSlashings) diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go index edcb2210c3ae..02eaf7f6a273 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go @@ -31,6 +31,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/voluntaryexits" mockp2p "github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/testing" "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/testutil" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/startup" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen" @@ -90,7 +91,7 @@ func TestServer_GetBeaconBlock_Phase0(t *testing.T) { require.NoError(t, db.SaveState(ctx, beaconState, parentRoot), "Could not save genesis state") require.NoError(t, db.SaveHeadBlockRoot(ctx, parentRoot), "Could not save genesis state") - proposerServer := getProposerServer(db, beaconState, parentRoot[:]) + proposerServer := getProposerServer(ctx, db, beaconState, parentRoot[:]) randaoReveal, err := util.RandaoReveal(beaconState, 0, privKeys) require.NoError(t, err) @@ -162,7 +163,7 @@ func TestServer_GetBeaconBlock_Altair(t *testing.T) { require.NoError(t, db.SaveState(ctx, beaconState, blkRoot), "Could not save genesis state") require.NoError(t, db.SaveHeadBlockRoot(ctx, blkRoot), "Could not save genesis state") - proposerServer := getProposerServer(db, beaconState, parentRoot[:]) + proposerServer := getProposerServer(ctx, db, beaconState, parentRoot[:]) randaoReveal, err := util.RandaoReveal(beaconState, 0, privKeys) require.NoError(t, err) @@ -275,7 +276,7 @@ func TestServer_GetBeaconBlock_Bellatrix(t *testing.T) { Timestamp: uint64(timeStamp.Unix()), } - proposerServer := getProposerServer(db, beaconState, parentRoot[:]) + proposerServer := getProposerServer(ctx, db, beaconState, parentRoot[:]) proposerServer.Eth1BlockFetcher = c ed, err := blocks.NewWrappedExecutionData(payload) require.NoError(t, err) @@ -401,7 +402,7 @@ func TestServer_GetBeaconBlock_Capella(t *testing.T) { Withdrawals: make([]*enginev1.Withdrawal, 0), } - proposerServer := getProposerServer(db, beaconState, parentRoot[:]) + proposerServer := getProposerServer(ctx, db, beaconState, parentRoot[:]) ed, err := blocks.NewWrappedExecutionData(payload) require.NoError(t, err) proposerServer.ExecutionEngineCaller = &mockExecution.EngineClient{ @@ -524,7 +525,7 @@ func TestServer_GetBeaconBlock_Deneb(t *testing.T) { proofs := [][]byte{[]byte("proof"), []byte("proof1"), []byte("proof2")} blobs := [][]byte{[]byte("blob"), []byte("blob1"), []byte("blob2")} bundle := &enginev1.BlobsBundle{KzgCommitments: kc, Proofs: proofs, Blobs: blobs} - proposerServer := getProposerServer(db, beaconState, parentRoot[:]) + proposerServer := getProposerServer(ctx, db, beaconState, parentRoot[:]) proposerServer.ExecutionEngineCaller = &mockExecution.EngineClient{ PayloadIDBytes: &enginev1.PayloadIDBytes{1}, GetPayloadResponse: &blocks.GetPayloadResponse{ @@ -657,7 +658,7 @@ func TestServer_GetBeaconBlock_Electra(t *testing.T) { BaseFeePerGas: make([]byte, fieldparams.RootLength), BlockHash: make([]byte, fieldparams.RootLength), } - proposerServer := getProposerServer(db, beaconState, parentRoot[:]) + proposerServer := getProposerServer(ctx, db, beaconState, parentRoot[:]) ed, err := blocks.NewWrappedExecutionData(payload) require.NoError(t, err) proposerServer.ExecutionEngineCaller = &mockExecution.EngineClient{ @@ -782,7 +783,7 @@ func TestServer_GetBeaconBlock_Fulu(t *testing.T) { BaseFeePerGas: make([]byte, fieldparams.RootLength), BlockHash: make([]byte, fieldparams.RootLength), } - proposerServer := getProposerServer(db, beaconState, parentRoot[:]) + proposerServer := getProposerServer(ctx, db, beaconState, parentRoot[:]) ed, err := blocks.NewWrappedExecutionData(payload) require.NoError(t, err) proposerServer.ExecutionEngineCaller = &mockExecution.EngineClient{ @@ -832,7 +833,7 @@ func TestServer_GetBeaconBlock_Optimistic(t *testing.T) { require.ErrorContains(t, errOptimisticMode.Error(), err) } -func getProposerServer(db db.HeadAccessDatabase, headState state.BeaconState, headRoot []byte) *Server { +func getProposerServer(ctx context.Context, db db.HeadAccessDatabase, headState state.BeaconState, headRoot []byte) *Server { mockChainService := &mock.ChainService{State: headState, Root: headRoot, ForkChoiceStore: doublylinkedtree.New()} return &Server{ HeadFetcher: mockChainService, @@ -846,7 +847,7 @@ func getProposerServer(db db.HeadAccessDatabase, headState state.BeaconState, he ForkchoiceFetcher: mockChainService, MockEth1Votes: true, AttPool: attestations.NewPool(), - SlashingsPool: slashings.NewPool(false), + SlashingsPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), ExitPool: voluntaryexits.NewPool(), StateGen: stategen.New(db, doublylinkedtree.New()), SyncCommitteePool: synccommittee.NewStore(), diff --git a/beacon-chain/sync/subscriber_test.go b/beacon-chain/sync/subscriber_test.go index 00ecd01a9e72..c045da490268 100644 --- a/beacon-chain/sync/subscriber_test.go +++ b/beacon-chain/sync/subscriber_test.go @@ -141,7 +141,7 @@ func TestSubscribe_ReceivesAttesterSlashing(t *testing.T) { cfg: &config{ p2p: p2pService, initialSync: &mockSync.Sync{IsSyncing: false}, - slashingPool: slashings.NewPool(false), + slashingPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), chain: chainService, clock: startup.NewClock(gt, vr), beaconDB: d, @@ -194,7 +194,7 @@ func TestSubscribe_ReceivesProposerSlashing(t *testing.T) { cfg: &config{ p2p: p2pService, initialSync: &mockSync.Sync{IsSyncing: false}, - slashingPool: slashings.NewPool(false), + slashingPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), chain: chainService, beaconDB: d, clock: startup.NewClock(chainService.Genesis, chainService.ValidatorsRoot), From 6746014e6185ab167c71a769bce044c8a44e4730 Mon Sep 17 00:00:00 2001 From: rkapka Date: Fri, 17 Jan 2025 19:43:41 +0100 Subject: [PATCH 07/10] testing the timer --- beacon-chain/node/node.go | 17 +++-- beacon-chain/operations/slashings/BUILD.bazel | 1 + beacon-chain/operations/slashings/service.go | 46 +++++++------ .../operations/slashings/service_test.go | 68 +++++++++++++++++++ beacon-chain/operations/slashings/types.go | 4 +- 5 files changed, 108 insertions(+), 28 deletions(-) diff --git a/beacon-chain/node/node.go b/beacon-chain/node/node.go index 3b2e146de516..759b01785336 100644 --- a/beacon-chain/node/node.go +++ b/beacon-chain/node/node.go @@ -319,11 +319,6 @@ func registerServices(cliCtx *cli.Context, beacon *BeaconNode, synchronizer *sta return errors.Wrap(err, "could not register attestation pool service") } - log.Debugln("Registering Slashing Pool Service") - if err := beacon.registerSlashingPool(); err != nil { - return errors.Wrap(err, "could not register slashing pool service") - } - log.Debugln("Registering Blockchain Service") if err := beacon.registerBlockchainService(beacon.forkChoicer, synchronizer, beacon.initialSyncComplete); err != nil { return errors.Wrap(err, "could not register blockchain service") @@ -339,6 +334,11 @@ func registerServices(cliCtx *cli.Context, beacon *BeaconNode, synchronizer *sta return errors.Wrap(err, "could not register sync service") } + log.Debugln("Registering Slashing Pool Service") + if err := beacon.registerSlashingPool(); err != nil { + return errors.Wrap(err, "could not register slashing pool service") + } + log.Debugln("Registering Slasher Service") if err := beacon.registerSlasherService(); err != nil { return errors.Wrap(err, "could not register slasher service") @@ -720,7 +720,12 @@ func (b *BeaconNode) registerAttestationPool() error { } func (b *BeaconNode) registerSlashingPool() error { - s := slashings.NewPool(b.ctx, b.clockWaiter, slashings.WithElectraTimer()) + var chainService *blockchain.Service + if err := b.services.FetchService(&chainService); err != nil { + return err + } + + s := slashings.NewPool(b.ctx, slashings.WithElectraTimer(b.clockWaiter, chainService.CurrentSlot)) return b.services.RegisterService(s) } diff --git a/beacon-chain/operations/slashings/BUILD.bazel b/beacon-chain/operations/slashings/BUILD.bazel index 0481c0e850e9..4dd740abcd28 100644 --- a/beacon-chain/operations/slashings/BUILD.bazel +++ b/beacon-chain/operations/slashings/BUILD.bazel @@ -47,6 +47,7 @@ go_test( embed = [":go_default_library"], deps = [ "//beacon-chain/operations/slashings/mock:go_default_library", + "//beacon-chain/startup:go_default_library", "//beacon-chain/state:go_default_library", "//config/fieldparams:go_default_library", "//config/params:go_default_library", diff --git a/beacon-chain/operations/slashings/service.go b/beacon-chain/operations/slashings/service.go index 7bbe6c3c887a..36dfa5c7fa6c 100644 --- a/beacon-chain/operations/slashings/service.go +++ b/beacon-chain/operations/slashings/service.go @@ -23,15 +23,17 @@ import ( ) // WithElectraTimer includes functional options for the blockchain service related to CLI flags. -func WithElectraTimer() Option { +func WithElectraTimer(cw startup.ClockWaiter, currentSlotFn func() primitives.Slot) Option { return func(p *Pool) error { p.runElectraTimer = true + p.cw = cw + p.currentSlotFn = currentSlotFn return nil } } // NewPool returns an initialized attester slashing and proposer slashing pool. -func NewPool(ctx context.Context, cw startup.ClockWaiter, opts ...Option) *Pool { +func NewPool(ctx context.Context, opts ...Option) *Pool { ctx, cancel := context.WithCancel(ctx) p := &Pool{ ctx: ctx, @@ -39,7 +41,6 @@ func NewPool(ctx context.Context, cw startup.ClockWaiter, opts ...Option) *Pool pendingProposerSlashing: make([]*ethpb.ProposerSlashing, 0), pendingAttesterSlashing: make([]*PendingAttesterSlashing, 0), included: make(map[primitives.ValidatorIndex]bool), - cw: cw, } for _, opt := range opts { @@ -354,39 +355,44 @@ func (p *Pool) run() { return } - p.waitForChainInitialization() - electraSlot, err := slots.EpochStart(params.BeaconConfig().ElectraForkEpoch) if err != nil { log.WithError(err).Error("Could not get Electra start slot") return } - electraTime, err := slots.ToTime(uint64(p.genesisTime.Unix()), electraSlot) + if p.currentSlotFn() >= electraSlot { + return + } + + p.waitForChainInitialization() + + electraTime, err := slots.ToTime(uint64(p.clock.GenesisTime().Unix()), electraSlot) if err != nil { log.WithError(err).Error("Could not get Electra start time") return } - t := time.NewTimer(electraTime.Sub(time.Now())) - go func() { - defer t.Stop() - select { - case <-t.C: - log.Info("Converting Phase0 slashings to Electra slashings") - p.convertToElectra() - case <-p.ctx.Done(): - log.Warn("Context cancelled, Electra timer will not execute") - return - } - }() + t := time.NewTimer(electraTime.Sub(p.clock.Now())) + defer t.Stop() + + select { + case <-t.C: + log.Info("Converting Phase0 slashings to Electra slashings") + p.convertToElectra() + return + case <-p.ctx.Done(): + log.Warn("Context cancelled, Electra timer will not execute") + return + } } + func (p *Pool) waitForChainInitialization() { clock, err := p.cw.WaitForClock(p.ctx) if err != nil { log.WithError(err).Error("Could not receive chain start notification") } - p.genesisTime = clock.GenesisTime() - log.WithField("genesisTime", p.genesisTime).Info( + p.clock = clock + log.WithField("genesisTime", clock.GenesisTime()).Info( "Slashing pool received chain initialization event", ) } diff --git a/beacon-chain/operations/slashings/service_test.go b/beacon-chain/operations/slashings/service_test.go index 9334da21496f..e07f72589e17 100644 --- a/beacon-chain/operations/slashings/service_test.go +++ b/beacon-chain/operations/slashings/service_test.go @@ -1,9 +1,17 @@ package slashings import ( + "context" "testing" + "time" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/slashings/mock" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/startup" + fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" + "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/testing/assert" "github.com/prysmaticlabs/prysm/v5/testing/require" ) @@ -19,3 +27,63 @@ func TestPool_validatorSlashingPreconditionCheck_requiresLock(t *testing.T) { _, err := p.validatorSlashingPreconditionCheck(nil, 0) require.ErrorContains(t, "caller must hold read/write lock", err) } + +func Test_convertToElectraWithTimer(t *testing.T) { + ctx := context.Background() + + cfg := params.BeaconConfig().Copy() + cfg.ElectraForkEpoch = 1 + params.OverrideBeaconConfig(cfg) + params.SetupTestConfigCleanup(t) + + indices := []uint64{0, 1} + data := ðpb.AttestationData{ + Slot: 1, + CommitteeIndex: 1, + BeaconBlockRoot: make([]byte, fieldparams.RootLength), + Source: ðpb.Checkpoint{ + Epoch: 0, + Root: make([]byte, fieldparams.RootLength), + }, + Target: ðpb.Checkpoint{ + Epoch: 0, + Root: make([]byte, fieldparams.RootLength), + }, + } + sig := make([]byte, fieldparams.BLSSignatureLength) + + phase0Slashing := &PendingAttesterSlashing{ + attesterSlashing: ðpb.AttesterSlashing{ + Attestation_1: ðpb.IndexedAttestation{ + AttestingIndices: indices, + Data: data, + Signature: sig, + }, + Attestation_2: ðpb.IndexedAttestation{ + AttestingIndices: indices, + Data: data, + Signature: sig, + }, + }, + } + + // TODO: doc + now := time.Now() + electraTime := now.Add(time.Duration(uint64(cfg.ElectraForkEpoch)*uint64(params.BeaconConfig().SlotsPerEpoch)*params.BeaconConfig().SecondsPerSlot) * time.Second) + c := startup.NewClock(now, [32]byte{}, startup.WithNower(func() time.Time { return electraTime })) + cw := startup.NewClockSynchronizer() + require.NoError(t, cw.SetClock(c)) + p := NewPool(ctx, WithElectraTimer(cw, func() primitives.Slot { return 31 })) + p.pendingAttesterSlashing = append(p.pendingAttesterSlashing, phase0Slashing) + + p.run() + + electraSlashing, ok := p.pendingAttesterSlashing[0].attesterSlashing.(*ethpb.AttesterSlashingElectra) + require.Equal(t, true, ok, "Slashing was not converted to Electra") + assert.DeepEqual(t, phase0Slashing.attesterSlashing.FirstAttestation().GetAttestingIndices(), electraSlashing.FirstAttestation().GetAttestingIndices()) + assert.DeepEqual(t, phase0Slashing.attesterSlashing.FirstAttestation().GetData(), electraSlashing.FirstAttestation().GetData()) + assert.DeepEqual(t, phase0Slashing.attesterSlashing.FirstAttestation().GetSignature(), electraSlashing.FirstAttestation().GetSignature()) + assert.DeepEqual(t, phase0Slashing.attesterSlashing.SecondAttestation().GetAttestingIndices(), electraSlashing.SecondAttestation().GetAttestingIndices()) + assert.DeepEqual(t, phase0Slashing.attesterSlashing.SecondAttestation().GetData(), electraSlashing.SecondAttestation().GetData()) + assert.DeepEqual(t, phase0Slashing.attesterSlashing.SecondAttestation().GetSignature(), electraSlashing.SecondAttestation().GetSignature()) +} diff --git a/beacon-chain/operations/slashings/types.go b/beacon-chain/operations/slashings/types.go index c7baf9b2263a..0d727d030238 100644 --- a/beacon-chain/operations/slashings/types.go +++ b/beacon-chain/operations/slashings/types.go @@ -3,7 +3,6 @@ package slashings import ( "context" "sync" - "time" "github.com/prysmaticlabs/prysm/v5/beacon-chain/startup" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" @@ -43,8 +42,9 @@ type Pool struct { lock sync.RWMutex ctx context.Context cancel context.CancelFunc + currentSlotFn func() primitives.Slot cw startup.ClockWaiter - genesisTime time.Time + clock *startup.Clock pendingProposerSlashing []*ethpb.ProposerSlashing pendingAttesterSlashing []*PendingAttesterSlashing included map[primitives.ValidatorIndex]bool From 62238f005678a185f95322cc600e33fe6f770fcc Mon Sep 17 00:00:00 2001 From: rkapka Date: Mon, 20 Jan 2025 16:34:39 +0100 Subject: [PATCH 08/10] Decouple pool from service --- beacon-chain/blockchain/service_test.go | 2 +- beacon-chain/node/node.go | 35 +- beacon-chain/operations/slashings/BUILD.bazel | 2 + beacon-chain/operations/slashings/pool.go | 324 +++++++++++++++++ .../operations/slashings/pool_test.go | 21 ++ beacon-chain/operations/slashings/service.go | 335 +----------------- .../operations/slashings/service_test.go | 21 +- beacon-chain/operations/slashings/types.go | 22 +- .../rpc/prysm/v1alpha1/beacon/BUILD.bazel | 1 - .../prysm/v1alpha1/beacon/slashings_test.go | 11 +- .../validator/proposer_slashings_test.go | 5 +- .../prysm/v1alpha1/validator/proposer_test.go | 3 +- beacon-chain/sync/subscriber_test.go | 4 +- 13 files changed, 398 insertions(+), 388 deletions(-) create mode 100644 beacon-chain/operations/slashings/pool.go create mode 100644 beacon-chain/operations/slashings/pool_test.go diff --git a/beacon-chain/blockchain/service_test.go b/beacon-chain/blockchain/service_test.go index b28257cfa594..d0045052aa66 100644 --- a/beacon-chain/blockchain/service_test.go +++ b/beacon-chain/blockchain/service_test.go @@ -98,7 +98,7 @@ func setupBeaconChain(t *testing.T, beaconDB db.Database) *Service { WithDepositCache(depositCache), WithChainStartFetcher(web3Service), WithAttestationPool(attestations.NewPool()), - WithSlashingPool(slashings.NewPool(ctx, clockSync)), + WithSlashingPool(slashings.NewPool()), WithExitPool(voluntaryexits.NewPool()), WithP2PBroadcaster(&mockBroadcaster{}), WithStateNotifier(&mockBeaconNode{}), diff --git a/beacon-chain/node/node.go b/beacon-chain/node/node.go index 759b01785336..67bd77693f7e 100644 --- a/beacon-chain/node/node.go +++ b/beacon-chain/node/node.go @@ -96,6 +96,7 @@ type BeaconNode struct { attestationCache *cache.AttestationCache attestationPool attestations.Pool exitPool voluntaryexits.PoolManager + slashingsPool slashings.PoolManager syncCommitteePool synccommittee.Pool blsToExecPool blstoexec.PoolManager depositCache cache.DepositCache @@ -147,6 +148,7 @@ func New(cliCtx *cli.Context, cancel context.CancelFunc, opts ...Option) (*Beaco attestationCache: cache.NewAttestationCache(), attestationPool: attestations.NewPool(), exitPool: voluntaryexits.NewPool(), + slashingsPool: slashings.NewPool(), syncCommitteePool: synccommittee.NewPool(), blsToExecPool: blstoexec.NewPool(), trackedValidatorsCache: cache.NewTrackedValidatorsCache(), @@ -335,7 +337,7 @@ func registerServices(cliCtx *cli.Context, beacon *BeaconNode, synchronizer *sta } log.Debugln("Registering Slashing Pool Service") - if err := beacon.registerSlashingPool(); err != nil { + if err := beacon.registerSlashingPoolService(); err != nil { return errors.Wrap(err, "could not register slashing pool service") } @@ -719,13 +721,13 @@ func (b *BeaconNode) registerAttestationPool() error { return b.services.RegisterService(s) } -func (b *BeaconNode) registerSlashingPool() error { +func (b *BeaconNode) registerSlashingPoolService() error { var chainService *blockchain.Service if err := b.services.FetchService(&chainService); err != nil { return err } - s := slashings.NewPool(b.ctx, slashings.WithElectraTimer(b.clockWaiter, chainService.CurrentSlot)) + s := slashings.NewPoolService(b.ctx, b.slashingsPool, slashings.WithElectraTimer(b.clockWaiter, chainService.CurrentSlot)) return b.services.RegisterService(s) } @@ -740,11 +742,6 @@ func (b *BeaconNode) registerBlockchainService(fc forkchoice.ForkChoicer, gs *st return err } - var slashingPoolService *slashings.Pool - if err := b.services.FetchService(&slashingPoolService); err != nil { - return err - } - // skipcq: CRT-D0001 opts := append( b.serviceFlagOpts.blockchainFlagOpts, @@ -756,7 +753,7 @@ func (b *BeaconNode) registerBlockchainService(fc forkchoice.ForkChoicer, gs *st blockchain.WithAttestationCache(b.attestationCache), blockchain.WithAttestationPool(b.attestationPool), blockchain.WithExitPool(b.exitPool), - blockchain.WithSlashingPool(slashingPoolService), + blockchain.WithSlashingPool(b.slashingsPool), blockchain.WithBLSToExecPool(b.blsToExecPool), blockchain.WithP2PBroadcaster(b.fetchP2P()), blockchain.WithStateNotifier(b), @@ -829,11 +826,6 @@ func (b *BeaconNode) registerSyncService(initialSyncComplete chan struct{}, bFil return err } - var slashingPoolService *slashings.Pool - if err := b.services.FetchService(&slashingPoolService); err != nil { - return err - } - rs := regularsync.NewService( b.ctx, regularsync.WithDatabase(b.db), @@ -846,7 +838,7 @@ func (b *BeaconNode) registerSyncService(initialSyncComplete chan struct{}, bFil regularsync.WithAttestationCache(b.attestationCache), regularsync.WithAttestationPool(b.attestationPool), regularsync.WithExitPool(b.exitPool), - regularsync.WithSlashingPool(slashingPoolService), + regularsync.WithSlashingPool(b.slashingsPool), regularsync.WithSyncCommsPool(b.syncCommitteePool), regularsync.WithBlsToExecPool(b.blsToExecPool), regularsync.WithStateGen(b.stateGen), @@ -898,10 +890,6 @@ func (b *BeaconNode) registerSlasherService() error { if err := b.services.FetchService(&syncService); err != nil { return err } - var slashingPoolService *slashings.Pool - if err := b.services.FetchService(&slashingPoolService); err != nil { - return err - } slasherSrv, err := slasher.New(b.ctx, &slasher.ServiceConfig{ IndexedAttestationsFeed: b.slasherAttestationsFeed, @@ -910,7 +898,7 @@ func (b *BeaconNode) registerSlasherService() error { StateNotifier: b, AttestationStateFetcher: chainService, StateGen: b.stateGen, - SlashingPoolInserter: slashingPoolService, + SlashingPoolInserter: b.slashingsPool, SyncChecker: syncService, HeadStateFetcher: chainService, ClockWaiter: b.clockWaiter, @@ -944,11 +932,6 @@ func (b *BeaconNode) registerRPCService(router *http.ServeMux) error { } } - var slashingPoolService *slashings.Pool - if err := b.services.FetchService(&slashingPoolService); err != nil { - return err - } - depositFetcher := b.depositCache chainStartFetcher := web3Service @@ -992,7 +975,7 @@ func (b *BeaconNode) registerRPCService(router *http.ServeMux) error { AttestationCache: b.attestationCache, AttestationsPool: b.attestationPool, ExitPool: b.exitPool, - SlashingsPool: slashingPoolService, + SlashingsPool: b.slashingsPool, BLSChangesPool: b.blsToExecPool, SyncCommitteeObjectPool: b.syncCommitteePool, ExecutionChainService: web3Service, diff --git a/beacon-chain/operations/slashings/BUILD.bazel b/beacon-chain/operations/slashings/BUILD.bazel index 4dd740abcd28..5fe36e4e3382 100644 --- a/beacon-chain/operations/slashings/BUILD.bazel +++ b/beacon-chain/operations/slashings/BUILD.bazel @@ -6,6 +6,7 @@ go_library( "doc.go", "log.go", "metrics.go", + "pool.go", "service.go", "types.go", ], @@ -40,6 +41,7 @@ go_test( name = "go_default_test", size = "small", srcs = [ + "pool_test.go", "service_attester_test.go", "service_proposer_test.go", "service_test.go", diff --git a/beacon-chain/operations/slashings/pool.go b/beacon-chain/operations/slashings/pool.go new file mode 100644 index 000000000000..e315bbf55d21 --- /dev/null +++ b/beacon-chain/operations/slashings/pool.go @@ -0,0 +1,324 @@ +package slashings + +import ( + "context" + "fmt" + "sort" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" + coretime "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" + "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/container/slice" + "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" + "github.com/trailofbits/go-mutexasserts" +) + +// NewPool returns an initialized attester slashing and proposer slashing pool. +func NewPool() *Pool { + return &Pool{ + pendingProposerSlashing: make([]*ethpb.ProposerSlashing, 0), + pendingAttesterSlashing: make([]*PendingAttesterSlashing, 0), + included: make(map[primitives.ValidatorIndex]bool), + } +} + +// PendingAttesterSlashings returns attester slashings that are able to be included into a block. +// This method will return the amount of pending attester slashings for a block transition unless parameter `noLimit` is true +// to indicate the request is for noLimit pending items. +func (p *Pool) PendingAttesterSlashings(ctx context.Context, state state.ReadOnlyBeaconState, noLimit bool) []ethpb.AttSlashing { + p.lock.Lock() + defer p.lock.Unlock() + _, span := trace.StartSpan(ctx, "operations.PendingAttesterSlashing") + defer span.End() + + // Update prom metric. + numPendingAttesterSlashings.Set(float64(len(p.pendingAttesterSlashing))) + + included := make(map[primitives.ValidatorIndex]bool) + + // Allocate pending slice with a capacity of maxAttesterSlashings or len(p.pendingAttesterSlashing)) depending on the request. + maxSlashings := params.BeaconConfig().MaxAttesterSlashings + + // EIP-7549: Beginning from Electra, the max attester slashings is reduced to 1. + if state.Version() >= version.Electra { + maxSlashings = params.BeaconConfig().MaxAttesterSlashingsElectra + } + if noLimit { + maxSlashings = uint64(len(p.pendingAttesterSlashing)) + } + pending := make([]ethpb.AttSlashing, 0, maxSlashings) + for i := 0; i < len(p.pendingAttesterSlashing); i++ { + if uint64(len(pending)) >= maxSlashings { + break + } + slashing := p.pendingAttesterSlashing[i] + valid, err := p.validatorSlashingPreconditionCheck(state, slashing.validatorToSlash) + if err != nil { + log.WithError(err).Error("could not validate attester slashing") + continue + } + if included[slashing.validatorToSlash] || !valid { + p.pendingAttesterSlashing = append(p.pendingAttesterSlashing[:i], p.pendingAttesterSlashing[i+1:]...) + i-- + continue + } + attSlashing := slashing.attesterSlashing + slashedVal := slice.IntersectionUint64( + attSlashing.FirstAttestation().GetAttestingIndices(), + attSlashing.SecondAttestation().GetAttestingIndices(), + ) + for _, idx := range slashedVal { + included[primitives.ValidatorIndex(idx)] = true + } + + pending = append(pending, attSlashing) + } + + return pending +} + +// PendingProposerSlashings returns proposer slashings that are able to be included into a block. +// This method will return the amount of pending proposer slashings for a block transition unless the `noLimit` parameter +// is set to true to indicate the request is for noLimit pending items. +func (p *Pool) PendingProposerSlashings(ctx context.Context, state state.ReadOnlyBeaconState, noLimit bool) []*ethpb.ProposerSlashing { + p.lock.Lock() + defer p.lock.Unlock() + _, span := trace.StartSpan(ctx, "operations.PendingProposerSlashing") + defer span.End() + + // Update prom metric. + numPendingProposerSlashings.Set(float64(len(p.pendingProposerSlashing))) + + // Allocate pending slice with a capacity of len(p.pendingProposerSlashing) or maxProposerSlashings depending on the request. + maxSlashings := params.BeaconConfig().MaxProposerSlashings + if noLimit { + maxSlashings = uint64(len(p.pendingProposerSlashing)) + } + pending := make([]*ethpb.ProposerSlashing, 0, maxSlashings) + for i := 0; i < len(p.pendingProposerSlashing); i++ { + if uint64(len(pending)) >= maxSlashings { + break + } + slashing := p.pendingProposerSlashing[i] + valid, err := p.validatorSlashingPreconditionCheck(state, slashing.Header_1.Header.ProposerIndex) + if err != nil { + log.WithError(err).Error("could not validate proposer slashing") + continue + } + if !valid { + p.pendingProposerSlashing = append(p.pendingProposerSlashing[:i], p.pendingProposerSlashing[i+1:]...) + i-- + continue + } + + pending = append(pending, slashing) + } + return pending +} + +// InsertAttesterSlashing into the pool. This method is a no-op if the attester slashing already exists in the pool, +// has been included into a block recently, or the validator is already exited. +func (p *Pool) InsertAttesterSlashing( + ctx context.Context, + state state.ReadOnlyBeaconState, + slashing ethpb.AttSlashing, +) error { + p.lock.Lock() + defer p.lock.Unlock() + ctx, span := trace.StartSpan(ctx, "operations.InsertAttesterSlashing") + defer span.End() + + if err := blocks.VerifyAttesterSlashing(ctx, state, slashing); err != nil { + return errors.Wrap(err, "could not verify attester slashing") + } + + slashedVal := slice.IntersectionUint64(slashing.FirstAttestation().GetAttestingIndices(), slashing.SecondAttestation().GetAttestingIndices()) + cantSlash := make([]uint64, 0, len(slashedVal)) + slashingReason := "" + for _, val := range slashedVal { + // Has this validator index been included recently? + ok, err := p.validatorSlashingPreconditionCheck(state, primitives.ValidatorIndex(val)) + if err != nil { + return err + } + // If the validator has already exited, has already been slashed, or if its index + // has been recently included in the pool of slashings, skip including this indice. + if !ok { + slashingReason = "validator already exited/slashed or already recently included in slashings pool" + cantSlash = append(cantSlash, val) + continue + } + + // Check if the validator already exists in the list of slashings. + // Use binary search to find the answer. + found := sort.Search(len(p.pendingAttesterSlashing), func(i int) bool { + return uint64(p.pendingAttesterSlashing[i].validatorToSlash) >= val + }) + if found != len(p.pendingAttesterSlashing) && uint64(p.pendingAttesterSlashing[found].validatorToSlash) == val { + slashingReason = "validator already exist in list of pending slashings, no need to attempt to slash again" + cantSlash = append(cantSlash, val) + continue + } + + pendingSlashing := &PendingAttesterSlashing{ + attesterSlashing: slashing, + validatorToSlash: primitives.ValidatorIndex(val), + } + // Insert into pending list and sort again. + p.pendingAttesterSlashing = append(p.pendingAttesterSlashing, pendingSlashing) + sort.Slice(p.pendingAttesterSlashing, func(i, j int) bool { + return p.pendingAttesterSlashing[i].validatorToSlash < p.pendingAttesterSlashing[j].validatorToSlash + }) + numPendingAttesterSlashings.Set(float64(len(p.pendingAttesterSlashing))) + } + if len(cantSlash) == len(slashedVal) { + return fmt.Errorf( + "could not slash any of %d validators in submitted slashing: %s", + len(slashedVal), + slashingReason, + ) + } + return nil +} + +// InsertProposerSlashing into the pool. This method is a no-op if the pending slashing already exists, +// has been included recently, the validator is already exited, or the validator was already slashed. +func (p *Pool) InsertProposerSlashing( + ctx context.Context, + state state.ReadOnlyBeaconState, + slashing *ethpb.ProposerSlashing, +) error { + p.lock.Lock() + defer p.lock.Unlock() + _, span := trace.StartSpan(ctx, "operations.InsertProposerSlashing") + defer span.End() + + if err := blocks.VerifyProposerSlashing(state, slashing); err != nil { + return errors.Wrap(err, "could not verify proposer slashing") + } + + idx := slashing.Header_1.Header.ProposerIndex + ok, err := p.validatorSlashingPreconditionCheck(state, idx) + if err != nil { + return err + } + // If the validator has already exited, has already been slashed, or if its index + // has been recently included in the pool of slashings, do not process this new + // slashing. + if !ok { + return fmt.Errorf("validator at index %d cannot be slashed", idx) + } + + // Check if the validator already exists in the list of slashings. + // Use binary search to find the answer. + found := sort.Search(len(p.pendingProposerSlashing), func(i int) bool { + return p.pendingProposerSlashing[i].Header_1.Header.ProposerIndex >= slashing.Header_1.Header.ProposerIndex + }) + if found != len(p.pendingProposerSlashing) && p.pendingProposerSlashing[found].Header_1.Header.ProposerIndex == + slashing.Header_1.Header.ProposerIndex { + return errors.New("slashing object already exists in pending proposer slashings") + } + + // Insert into pending list and sort again. + p.pendingProposerSlashing = append(p.pendingProposerSlashing, slashing) + sort.Slice(p.pendingProposerSlashing, func(i, j int) bool { + return p.pendingProposerSlashing[i].Header_1.Header.ProposerIndex < p.pendingProposerSlashing[j].Header_1.Header.ProposerIndex + }) + numPendingProposerSlashings.Set(float64(len(p.pendingProposerSlashing))) + + return nil +} + +// MarkIncludedAttesterSlashing is used when an attester slashing has been included in a beacon block. +// Every block seen by this node that contains proposer slashings should call this method to include +// the proposer slashings. +func (p *Pool) MarkIncludedAttesterSlashing(as ethpb.AttSlashing) { + p.lock.Lock() + defer p.lock.Unlock() + slashedVal := slice.IntersectionUint64(as.FirstAttestation().GetAttestingIndices(), as.SecondAttestation().GetAttestingIndices()) + for _, val := range slashedVal { + i := sort.Search(len(p.pendingAttesterSlashing), func(i int) bool { + return uint64(p.pendingAttesterSlashing[i].validatorToSlash) >= val + }) + if i != len(p.pendingAttesterSlashing) && uint64(p.pendingAttesterSlashing[i].validatorToSlash) == val { + p.pendingAttesterSlashing = append(p.pendingAttesterSlashing[:i], p.pendingAttesterSlashing[i+1:]...) + } + p.included[primitives.ValidatorIndex(val)] = true + numAttesterSlashingsIncluded.Inc() + } +} + +// MarkIncludedProposerSlashing is used when an proposer slashing has been included in a beacon block. +// Every block seen by this node that contains proposer slashings should call this method to include +// the proposer slashings. +func (p *Pool) MarkIncludedProposerSlashing(ps *ethpb.ProposerSlashing) { + p.lock.Lock() + defer p.lock.Unlock() + i := sort.Search(len(p.pendingProposerSlashing), func(i int) bool { + return p.pendingProposerSlashing[i].Header_1.Header.ProposerIndex >= ps.Header_1.Header.ProposerIndex + }) + if i != len(p.pendingProposerSlashing) && p.pendingProposerSlashing[i].Header_1.Header.ProposerIndex == ps.Header_1.Header.ProposerIndex { + p.pendingProposerSlashing = append(p.pendingProposerSlashing[:i], p.pendingProposerSlashing[i+1:]...) + } + p.included[ps.Header_1.Header.ProposerIndex] = true + numProposerSlashingsIncluded.Inc() +} + +// ConvertToElectra converts all Phase0 attester slashings to Electra attester slashings. +// This functionality is needed at the time of the Electra fork. +func (p *Pool) ConvertToElectra() { + p.lock.Lock() + defer p.lock.Unlock() + + for _, pas := range p.pendingAttesterSlashing { + if pas.attesterSlashing.Version() == version.Phase0 { + first := pas.attesterSlashing.FirstAttestation() + second := pas.attesterSlashing.SecondAttestation() + pas.attesterSlashing = ðpb.AttesterSlashingElectra{ + Attestation_1: ðpb.IndexedAttestationElectra{ + AttestingIndices: first.GetAttestingIndices(), + Data: first.GetData(), + Signature: first.GetSignature(), + }, + Attestation_2: ðpb.IndexedAttestationElectra{ + AttestingIndices: second.GetAttestingIndices(), + Data: second.GetData(), + Signature: second.GetSignature(), + }, + } + } + } +} + +// this function checks a few items about a validator before proceeding with inserting +// a proposer/attester slashing into the pool. First, it checks if the validator +// has been recently included in the pool, then it checks if the validator is slashable. +// Note: this method requires caller to hold the lock. +func (p *Pool) validatorSlashingPreconditionCheck( + state state.ReadOnlyBeaconState, + valIdx primitives.ValidatorIndex, +) (bool, error) { + if !mutexasserts.RWMutexLocked(&p.lock) && !mutexasserts.RWMutexRLocked(&p.lock) { + return false, errors.New("pool.validatorSlashingPreconditionCheck: caller must hold read/write lock") + } + + // Check if the validator index has been included recently. + if p.included[valIdx] { + return false, nil + } + validator, err := state.ValidatorAtIndexReadOnly(valIdx) + if err != nil { + return false, err + } + // Checking if the validator is slashable. + if !helpers.IsSlashableValidatorUsingTrie(validator, coretime.CurrentEpoch(state)) { + return false, nil + } + return true, nil +} diff --git a/beacon-chain/operations/slashings/pool_test.go b/beacon-chain/operations/slashings/pool_test.go new file mode 100644 index 000000000000..9334da21496f --- /dev/null +++ b/beacon-chain/operations/slashings/pool_test.go @@ -0,0 +1,21 @@ +package slashings + +import ( + "testing" + + "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/slashings/mock" + "github.com/prysmaticlabs/prysm/v5/testing/require" +) + +var ( + _ = PoolManager(&Pool{}) + _ = PoolInserter(&Pool{}) + _ = PoolManager(&mock.PoolMock{}) + _ = PoolInserter(&mock.PoolMock{}) +) + +func TestPool_validatorSlashingPreconditionCheck_requiresLock(t *testing.T) { + p := &Pool{} + _, err := p.validatorSlashingPreconditionCheck(nil, 0) + require.ErrorContains(t, "caller must hold read/write lock", err) +} diff --git a/beacon-chain/operations/slashings/service.go b/beacon-chain/operations/slashings/service.go index 36dfa5c7fa6c..eee340267fab 100644 --- a/beacon-chain/operations/slashings/service.go +++ b/beacon-chain/operations/slashings/service.go @@ -2,29 +2,17 @@ package slashings import ( "context" - "fmt" - "sort" "time" - "github.com/pkg/errors" - "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks" - "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" - coretime "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time" "github.com/prysmaticlabs/prysm/v5/beacon-chain/startup" - "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" - "github.com/prysmaticlabs/prysm/v5/container/slice" - "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace" - 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/trailofbits/go-mutexasserts" ) // WithElectraTimer includes functional options for the blockchain service related to CLI flags. func WithElectraTimer(cw startup.ClockWaiter, currentSlotFn func() primitives.Slot) Option { - return func(p *Pool) error { + return func(p *PoolService) error { p.runElectraTimer = true p.cw = cw p.currentSlotFn = currentSlotFn @@ -32,15 +20,13 @@ func WithElectraTimer(cw startup.ClockWaiter, currentSlotFn func() primitives.Sl } } -// NewPool returns an initialized attester slashing and proposer slashing pool. -func NewPool(ctx context.Context, opts ...Option) *Pool { +// NewPoolService returns a service that manages the Pool. +func NewPoolService(ctx context.Context, pool PoolManager, opts ...Option) *PoolService { ctx, cancel := context.WithCancel(ctx) - p := &Pool{ - ctx: ctx, - cancel: cancel, - pendingProposerSlashing: make([]*ethpb.ProposerSlashing, 0), - pendingAttesterSlashing: make([]*PendingAttesterSlashing, 0), - included: make(map[primitives.ValidatorIndex]bool), + p := &PoolService{ + ctx: ctx, + cancel: cancel, + poolManager: pool, } for _, opt := range opts { @@ -52,305 +38,12 @@ func NewPool(ctx context.Context, opts ...Option) *Pool { return p } -// PendingAttesterSlashings returns attester slashings that are able to be included into a block. -// This method will return the amount of pending attester slashings for a block transition unless parameter `noLimit` is true -// to indicate the request is for noLimit pending items. -func (p *Pool) PendingAttesterSlashings(ctx context.Context, state state.ReadOnlyBeaconState, noLimit bool) []ethpb.AttSlashing { - p.lock.Lock() - defer p.lock.Unlock() - _, span := trace.StartSpan(ctx, "operations.PendingAttesterSlashing") - defer span.End() - - // Update prom metric. - numPendingAttesterSlashings.Set(float64(len(p.pendingAttesterSlashing))) - - included := make(map[primitives.ValidatorIndex]bool) - - // Allocate pending slice with a capacity of maxAttesterSlashings or len(p.pendingAttesterSlashing)) depending on the request. - maxSlashings := params.BeaconConfig().MaxAttesterSlashings - - // EIP-7549: Beginning from Electra, the max attester slashings is reduced to 1. - if state.Version() >= version.Electra { - maxSlashings = params.BeaconConfig().MaxAttesterSlashingsElectra - } - if noLimit { - maxSlashings = uint64(len(p.pendingAttesterSlashing)) - } - pending := make([]ethpb.AttSlashing, 0, maxSlashings) - for i := 0; i < len(p.pendingAttesterSlashing); i++ { - if uint64(len(pending)) >= maxSlashings { - break - } - slashing := p.pendingAttesterSlashing[i] - valid, err := p.validatorSlashingPreconditionCheck(state, slashing.validatorToSlash) - if err != nil { - log.WithError(err).Error("could not validate attester slashing") - continue - } - if included[slashing.validatorToSlash] || !valid { - p.pendingAttesterSlashing = append(p.pendingAttesterSlashing[:i], p.pendingAttesterSlashing[i+1:]...) - i-- - continue - } - attSlashing := slashing.attesterSlashing - slashedVal := slice.IntersectionUint64( - attSlashing.FirstAttestation().GetAttestingIndices(), - attSlashing.SecondAttestation().GetAttestingIndices(), - ) - for _, idx := range slashedVal { - included[primitives.ValidatorIndex(idx)] = true - } - - pending = append(pending, attSlashing) - } - - return pending -} - -// PendingProposerSlashings returns proposer slashings that are able to be included into a block. -// This method will return the amount of pending proposer slashings for a block transition unless the `noLimit` parameter -// is set to true to indicate the request is for noLimit pending items. -func (p *Pool) PendingProposerSlashings(ctx context.Context, state state.ReadOnlyBeaconState, noLimit bool) []*ethpb.ProposerSlashing { - p.lock.Lock() - defer p.lock.Unlock() - _, span := trace.StartSpan(ctx, "operations.PendingProposerSlashing") - defer span.End() - - // Update prom metric. - numPendingProposerSlashings.Set(float64(len(p.pendingProposerSlashing))) - - // Allocate pending slice with a capacity of len(p.pendingProposerSlashing) or maxProposerSlashings depending on the request. - maxSlashings := params.BeaconConfig().MaxProposerSlashings - if noLimit { - maxSlashings = uint64(len(p.pendingProposerSlashing)) - } - pending := make([]*ethpb.ProposerSlashing, 0, maxSlashings) - for i := 0; i < len(p.pendingProposerSlashing); i++ { - if uint64(len(pending)) >= maxSlashings { - break - } - slashing := p.pendingProposerSlashing[i] - valid, err := p.validatorSlashingPreconditionCheck(state, slashing.Header_1.Header.ProposerIndex) - if err != nil { - log.WithError(err).Error("could not validate proposer slashing") - continue - } - if !valid { - p.pendingProposerSlashing = append(p.pendingProposerSlashing[:i], p.pendingProposerSlashing[i+1:]...) - i-- - continue - } - - pending = append(pending, slashing) - } - return pending -} - -// InsertAttesterSlashing into the pool. This method is a no-op if the attester slashing already exists in the pool, -// has been included into a block recently, or the validator is already exited. -func (p *Pool) InsertAttesterSlashing( - ctx context.Context, - state state.ReadOnlyBeaconState, - slashing ethpb.AttSlashing, -) error { - p.lock.Lock() - defer p.lock.Unlock() - ctx, span := trace.StartSpan(ctx, "operations.InsertAttesterSlashing") - defer span.End() - - if err := blocks.VerifyAttesterSlashing(ctx, state, slashing); err != nil { - return errors.Wrap(err, "could not verify attester slashing") - } - - slashedVal := slice.IntersectionUint64(slashing.FirstAttestation().GetAttestingIndices(), slashing.SecondAttestation().GetAttestingIndices()) - cantSlash := make([]uint64, 0, len(slashedVal)) - slashingReason := "" - for _, val := range slashedVal { - // Has this validator index been included recently? - ok, err := p.validatorSlashingPreconditionCheck(state, primitives.ValidatorIndex(val)) - if err != nil { - return err - } - // If the validator has already exited, has already been slashed, or if its index - // has been recently included in the pool of slashings, skip including this indice. - if !ok { - slashingReason = "validator already exited/slashed or already recently included in slashings pool" - cantSlash = append(cantSlash, val) - continue - } - - // Check if the validator already exists in the list of slashings. - // Use binary search to find the answer. - found := sort.Search(len(p.pendingAttesterSlashing), func(i int) bool { - return uint64(p.pendingAttesterSlashing[i].validatorToSlash) >= val - }) - if found != len(p.pendingAttesterSlashing) && uint64(p.pendingAttesterSlashing[found].validatorToSlash) == val { - slashingReason = "validator already exist in list of pending slashings, no need to attempt to slash again" - cantSlash = append(cantSlash, val) - continue - } - - pendingSlashing := &PendingAttesterSlashing{ - attesterSlashing: slashing, - validatorToSlash: primitives.ValidatorIndex(val), - } - // Insert into pending list and sort again. - p.pendingAttesterSlashing = append(p.pendingAttesterSlashing, pendingSlashing) - sort.Slice(p.pendingAttesterSlashing, func(i, j int) bool { - return p.pendingAttesterSlashing[i].validatorToSlash < p.pendingAttesterSlashing[j].validatorToSlash - }) - numPendingAttesterSlashings.Set(float64(len(p.pendingAttesterSlashing))) - } - if len(cantSlash) == len(slashedVal) { - return fmt.Errorf( - "could not slash any of %d validators in submitted slashing: %s", - len(slashedVal), - slashingReason, - ) - } - return nil -} - -// InsertProposerSlashing into the pool. This method is a no-op if the pending slashing already exists, -// has been included recently, the validator is already exited, or the validator was already slashed. -func (p *Pool) InsertProposerSlashing( - ctx context.Context, - state state.ReadOnlyBeaconState, - slashing *ethpb.ProposerSlashing, -) error { - p.lock.Lock() - defer p.lock.Unlock() - _, span := trace.StartSpan(ctx, "operations.InsertProposerSlashing") - defer span.End() - - if err := blocks.VerifyProposerSlashing(state, slashing); err != nil { - return errors.Wrap(err, "could not verify proposer slashing") - } - - idx := slashing.Header_1.Header.ProposerIndex - ok, err := p.validatorSlashingPreconditionCheck(state, idx) - if err != nil { - return err - } - // If the validator has already exited, has already been slashed, or if its index - // has been recently included in the pool of slashings, do not process this new - // slashing. - if !ok { - return fmt.Errorf("validator at index %d cannot be slashed", idx) - } - - // Check if the validator already exists in the list of slashings. - // Use binary search to find the answer. - found := sort.Search(len(p.pendingProposerSlashing), func(i int) bool { - return p.pendingProposerSlashing[i].Header_1.Header.ProposerIndex >= slashing.Header_1.Header.ProposerIndex - }) - if found != len(p.pendingProposerSlashing) && p.pendingProposerSlashing[found].Header_1.Header.ProposerIndex == - slashing.Header_1.Header.ProposerIndex { - return errors.New("slashing object already exists in pending proposer slashings") - } - - // Insert into pending list and sort again. - p.pendingProposerSlashing = append(p.pendingProposerSlashing, slashing) - sort.Slice(p.pendingProposerSlashing, func(i, j int) bool { - return p.pendingProposerSlashing[i].Header_1.Header.ProposerIndex < p.pendingProposerSlashing[j].Header_1.Header.ProposerIndex - }) - numPendingProposerSlashings.Set(float64(len(p.pendingProposerSlashing))) - - return nil -} - -// MarkIncludedAttesterSlashing is used when an attester slashing has been included in a beacon block. -// Every block seen by this node that contains proposer slashings should call this method to include -// the proposer slashings. -func (p *Pool) MarkIncludedAttesterSlashing(as ethpb.AttSlashing) { - p.lock.Lock() - defer p.lock.Unlock() - slashedVal := slice.IntersectionUint64(as.FirstAttestation().GetAttestingIndices(), as.SecondAttestation().GetAttestingIndices()) - for _, val := range slashedVal { - i := sort.Search(len(p.pendingAttesterSlashing), func(i int) bool { - return uint64(p.pendingAttesterSlashing[i].validatorToSlash) >= val - }) - if i != len(p.pendingAttesterSlashing) && uint64(p.pendingAttesterSlashing[i].validatorToSlash) == val { - p.pendingAttesterSlashing = append(p.pendingAttesterSlashing[:i], p.pendingAttesterSlashing[i+1:]...) - } - p.included[primitives.ValidatorIndex(val)] = true - numAttesterSlashingsIncluded.Inc() - } -} - -// MarkIncludedProposerSlashing is used when an proposer slashing has been included in a beacon block. -// Every block seen by this node that contains proposer slashings should call this method to include -// the proposer slashings. -func (p *Pool) MarkIncludedProposerSlashing(ps *ethpb.ProposerSlashing) { - p.lock.Lock() - defer p.lock.Unlock() - i := sort.Search(len(p.pendingProposerSlashing), func(i int) bool { - return p.pendingProposerSlashing[i].Header_1.Header.ProposerIndex >= ps.Header_1.Header.ProposerIndex - }) - if i != len(p.pendingProposerSlashing) && p.pendingProposerSlashing[i].Header_1.Header.ProposerIndex == ps.Header_1.Header.ProposerIndex { - p.pendingProposerSlashing = append(p.pendingProposerSlashing[:i], p.pendingProposerSlashing[i+1:]...) - } - p.included[ps.Header_1.Header.ProposerIndex] = true - numProposerSlashingsIncluded.Inc() -} - -// this function checks a few items about a validator before proceeding with inserting -// a proposer/attester slashing into the pool. First, it checks if the validator -// has been recently included in the pool, then it checks if the validator is slashable. -// Note: this method requires caller to hold the lock. -func (p *Pool) validatorSlashingPreconditionCheck( - state state.ReadOnlyBeaconState, - valIdx primitives.ValidatorIndex, -) (bool, error) { - if !mutexasserts.RWMutexLocked(&p.lock) && !mutexasserts.RWMutexRLocked(&p.lock) { - return false, errors.New("pool.validatorSlashingPreconditionCheck: caller must hold read/write lock") - } - - // Check if the validator index has been included recently. - if p.included[valIdx] { - return false, nil - } - validator, err := state.ValidatorAtIndexReadOnly(valIdx) - if err != nil { - return false, err - } - // Checking if the validator is slashable. - if !helpers.IsSlashableValidatorUsingTrie(validator, coretime.CurrentEpoch(state)) { - return false, nil - } - return true, nil -} - -func (p *Pool) convertToElectra() { - p.lock.Lock() - defer p.lock.Unlock() - - for _, pas := range p.pendingAttesterSlashing { - if pas.attesterSlashing.Version() == version.Phase0 { - first := pas.attesterSlashing.FirstAttestation() - second := pas.attesterSlashing.SecondAttestation() - pas.attesterSlashing = ðpb.AttesterSlashingElectra{ - Attestation_1: ðpb.IndexedAttestationElectra{ - AttestingIndices: first.GetAttestingIndices(), - Data: first.GetData(), - Signature: first.GetSignature(), - }, - Attestation_2: ðpb.IndexedAttestationElectra{ - AttestingIndices: second.GetAttestingIndices(), - Data: second.GetData(), - Signature: second.GetSignature(), - }, - } - } - } -} - // Start the slashing pool service. -func (p *Pool) Start() { +func (p *PoolService) Start() { go p.run() } -func (p *Pool) run() { +func (p *PoolService) run() { if !p.runElectraTimer { return } @@ -378,7 +71,7 @@ func (p *Pool) run() { select { case <-t.C: log.Info("Converting Phase0 slashings to Electra slashings") - p.convertToElectra() + p.poolManager.ConvertToElectra() return case <-p.ctx.Done(): log.Warn("Context cancelled, Electra timer will not execute") @@ -386,24 +79,24 @@ func (p *Pool) run() { } } -func (p *Pool) waitForChainInitialization() { +func (p *PoolService) waitForChainInitialization() { clock, err := p.cw.WaitForClock(p.ctx) if err != nil { log.WithError(err).Error("Could not receive chain start notification") } p.clock = clock log.WithField("genesisTime", clock.GenesisTime()).Info( - "Slashing pool received chain initialization event", + "Slashing pool service received chain initialization event", ) } // Stop the slashing pool service. -func (p *Pool) Stop() error { +func (p *PoolService) Stop() error { p.cancel() return nil } // Status of the slashing pool service. -func (p *Pool) Status() error { +func (p *PoolService) Status() error { return nil } diff --git a/beacon-chain/operations/slashings/service_test.go b/beacon-chain/operations/slashings/service_test.go index e07f72589e17..9df2c99a81f6 100644 --- a/beacon-chain/operations/slashings/service_test.go +++ b/beacon-chain/operations/slashings/service_test.go @@ -5,7 +5,6 @@ import ( "testing" "time" - "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/slashings/mock" "github.com/prysmaticlabs/prysm/v5/beacon-chain/startup" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/config/params" @@ -15,20 +14,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/testing/require" ) -var ( - _ = PoolManager(&Pool{}) - _ = PoolInserter(&Pool{}) - _ = PoolManager(&mock.PoolMock{}) - _ = PoolInserter(&mock.PoolMock{}) -) - -func TestPool_validatorSlashingPreconditionCheck_requiresLock(t *testing.T) { - p := &Pool{} - _, err := p.validatorSlashingPreconditionCheck(nil, 0) - require.ErrorContains(t, "caller must hold read/write lock", err) -} - -func Test_convertToElectraWithTimer(t *testing.T) { +func TestConvertToElectraWithTimer(t *testing.T) { ctx := context.Background() cfg := params.BeaconConfig().Copy() @@ -73,10 +59,11 @@ func Test_convertToElectraWithTimer(t *testing.T) { c := startup.NewClock(now, [32]byte{}, startup.WithNower(func() time.Time { return electraTime })) cw := startup.NewClockSynchronizer() require.NoError(t, cw.SetClock(c)) - p := NewPool(ctx, WithElectraTimer(cw, func() primitives.Slot { return 31 })) + p := NewPool() + s := NewPoolService(ctx, p, WithElectraTimer(cw, func() primitives.Slot { return 31 })) p.pendingAttesterSlashing = append(p.pendingAttesterSlashing, phase0Slashing) - p.run() + s.run() electraSlashing, ok := p.pendingAttesterSlashing[0].attesterSlashing.(*ethpb.AttesterSlashingElectra) require.Equal(t, true, ok, "Slashing was not converted to Electra") diff --git a/beacon-chain/operations/slashings/types.go b/beacon-chain/operations/slashings/types.go index 0d727d030238..05e137d59c49 100644 --- a/beacon-chain/operations/slashings/types.go +++ b/beacon-chain/operations/slashings/types.go @@ -32,23 +32,29 @@ type PoolManager interface { PendingProposerSlashings(ctx context.Context, state state.ReadOnlyBeaconState, noLimit bool) []*ethpb.ProposerSlashing MarkIncludedAttesterSlashing(as ethpb.AttSlashing) MarkIncludedProposerSlashing(ps *ethpb.ProposerSlashing) + ConvertToElectra() } -// Option for pool configuration. -type Option func(p *Pool) error +// Option for pool service configuration. +type Option func(p *PoolService) error + +// PoolService manages the Pool. +type PoolService struct { + ctx context.Context + cancel context.CancelFunc + poolManager PoolManager + currentSlotFn func() primitives.Slot + cw startup.ClockWaiter + clock *startup.Clock + runElectraTimer bool +} // Pool is a concrete implementation of PoolManager. type Pool struct { lock sync.RWMutex - ctx context.Context - cancel context.CancelFunc - currentSlotFn func() primitives.Slot - cw startup.ClockWaiter - clock *startup.Clock pendingProposerSlashing []*ethpb.ProposerSlashing pendingAttesterSlashing []*PendingAttesterSlashing included map[primitives.ValidatorIndex]bool - runElectraTimer bool } // PendingAttesterSlashing represents an attester slashing in the operation pool. diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/BUILD.bazel b/beacon-chain/rpc/prysm/v1alpha1/beacon/BUILD.bazel index 87270167ba18..81c180d28d46 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/beacon/BUILD.bazel +++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/BUILD.bazel @@ -86,7 +86,6 @@ go_test( "//beacon-chain/operations/slashings:go_default_library", "//beacon-chain/p2p/testing:go_default_library", "//beacon-chain/rpc/core:go_default_library", - "//beacon-chain/startup:go_default_library", "//beacon-chain/state:go_default_library", "//beacon-chain/state/state-native:go_default_library", "//beacon-chain/state/stategen:go_default_library", diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/slashings_test.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/slashings_test.go index 7073d8737508..63bd3a92a158 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/beacon/slashings_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/slashings_test.go @@ -7,7 +7,6 @@ import ( mock "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/slashings" mockp2p "github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/testing" - "github.com/prysmaticlabs/prysm/v5/beacon-chain/startup" "github.com/prysmaticlabs/prysm/v5/config/features" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" @@ -32,7 +31,7 @@ func TestServer_SubmitProposerSlashing(t *testing.T) { HeadFetcher: &mock.ChainService{ State: st, }, - SlashingsPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), + SlashingsPool: slashings.NewPool(), Broadcaster: mb, } @@ -62,7 +61,7 @@ func TestServer_SubmitAttesterSlashing(t *testing.T) { HeadFetcher: &mock.ChainService{ State: st, }, - SlashingsPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), + SlashingsPool: slashings.NewPool(), Broadcaster: mb, } @@ -93,7 +92,7 @@ func TestServer_SubmitProposerSlashing_DontBroadcast(t *testing.T) { HeadFetcher: &mock.ChainService{ State: st, }, - SlashingsPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), + SlashingsPool: slashings.NewPool(), Broadcaster: mb, } @@ -140,7 +139,7 @@ func TestServer_SubmitAttesterSlashing_DontBroadcast(t *testing.T) { HeadFetcher: &mock.ChainService{ State: st, }, - SlashingsPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), + SlashingsPool: slashings.NewPool(), Broadcaster: mb, } @@ -181,7 +180,7 @@ func TestServer_SubmitAttesterSlashingElectra(t *testing.T) { HeadFetcher: &mock.ChainService{ State: st, }, - SlashingsPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), + SlashingsPool: slashings.NewPool(), Broadcaster: mb, } diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_slashings_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_slashings_test.go index 093f1c10ebaf..5bc23f1e9fc2 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_slashings_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_slashings_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/slashings" - "github.com/prysmaticlabs/prysm/v5/beacon-chain/startup" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" @@ -14,12 +13,10 @@ import ( ) func TestServer_getSlashings(t *testing.T) { - ctx := context.Background() - beaconState, privKeys := util.DeterministicGenesisState(t, 64) proposerServer := &Server{ - SlashingsPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), + SlashingsPool: slashings.NewPool(), } proposerSlashings := make([]*ethpb.ProposerSlashing, params.BeaconConfig().MaxProposerSlashings) diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go index 02eaf7f6a273..e194899dc613 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go @@ -31,7 +31,6 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/operations/voluntaryexits" mockp2p "github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/testing" "github.com/prysmaticlabs/prysm/v5/beacon-chain/rpc/testutil" - "github.com/prysmaticlabs/prysm/v5/beacon-chain/startup" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen" @@ -847,7 +846,7 @@ func getProposerServer(ctx context.Context, db db.HeadAccessDatabase, headState ForkchoiceFetcher: mockChainService, MockEth1Votes: true, AttPool: attestations.NewPool(), - SlashingsPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), + SlashingsPool: slashings.NewPool(), ExitPool: voluntaryexits.NewPool(), StateGen: stategen.New(db, doublylinkedtree.New()), SyncCommitteePool: synccommittee.NewStore(), diff --git a/beacon-chain/sync/subscriber_test.go b/beacon-chain/sync/subscriber_test.go index c045da490268..0840313c8013 100644 --- a/beacon-chain/sync/subscriber_test.go +++ b/beacon-chain/sync/subscriber_test.go @@ -141,7 +141,7 @@ func TestSubscribe_ReceivesAttesterSlashing(t *testing.T) { cfg: &config{ p2p: p2pService, initialSync: &mockSync.Sync{IsSyncing: false}, - slashingPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), + slashingPool: slashings.NewPool(), chain: chainService, clock: startup.NewClock(gt, vr), beaconDB: d, @@ -194,7 +194,7 @@ func TestSubscribe_ReceivesProposerSlashing(t *testing.T) { cfg: &config{ p2p: p2pService, initialSync: &mockSync.Sync{IsSyncing: false}, - slashingPool: slashings.NewPool(ctx, startup.NewClockSynchronizer()), + slashingPool: slashings.NewPool(), chain: chainService, beaconDB: d, clock: startup.NewClock(chainService.Genesis, chainService.ValidatorsRoot), From 6b5c7fd50d0444d5fd98562bb1e83aae18f10432 Mon Sep 17 00:00:00 2001 From: rkapka Date: Mon, 20 Jan 2025 16:47:06 +0100 Subject: [PATCH 09/10] update mock --- beacon-chain/blockchain/service_test.go | 4 +--- beacon-chain/operations/slashings/mock/mock.go | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/beacon-chain/blockchain/service_test.go b/beacon-chain/blockchain/service_test.go index d0045052aa66..58fff2452605 100644 --- a/beacon-chain/blockchain/service_test.go +++ b/beacon-chain/blockchain/service_test.go @@ -72,8 +72,6 @@ func setupBeaconChain(t *testing.T, beaconDB db.Database) *Service { }) require.NoError(t, err) - clockSync := startup.NewClockSynchronizer() - web3Service, err = execution.NewService( ctx, execution.WithDatabase(beaconDB), @@ -106,7 +104,7 @@ func setupBeaconChain(t *testing.T, beaconDB db.Database) *Service { WithAttestationService(attService), WithStateGen(stateGen), WithPayloadIDCache(cache.NewPayloadIDCache()), - WithClockSynchronizer(clockSync), + WithClockSynchronizer(startup.NewClockSynchronizer()), } chainService, err := NewService(ctx, opts...) diff --git a/beacon-chain/operations/slashings/mock/mock.go b/beacon-chain/operations/slashings/mock/mock.go index b30747a0a4f8..85e4a910cdd0 100644 --- a/beacon-chain/operations/slashings/mock/mock.go +++ b/beacon-chain/operations/slashings/mock/mock.go @@ -35,6 +35,9 @@ func (m *PoolMock) InsertProposerSlashing(_ context.Context, _ state.ReadOnlyBea return nil } +// ConvertToElectra -- +func (*PoolMock) ConvertToElectra() {} + // MarkIncludedAttesterSlashing -- func (*PoolMock) MarkIncludedAttesterSlashing(_ ethpb.AttSlashing) { panic("implement me") From ba6f61861c55be7e447a72101745a56381f3140b Mon Sep 17 00:00:00 2001 From: rkapka Date: Mon, 20 Jan 2025 17:03:22 +0100 Subject: [PATCH 10/10] attest.go fix --- validator/client/attest.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/validator/client/attest.go b/validator/client/attest.go index e3d27ff2a083..cd74fb2d181c 100644 --- a/validator/client/attest.go +++ b/validator/client/attest.go @@ -122,23 +122,6 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot primitives.Slot, return } - var indexInCommittee uint64 - var found bool - for i, vID := range duty.Committee { - if vID == duty.ValidatorIndex { - indexInCommittee = uint64(i) - found = true - break - } - } - if !found { - log.Errorf("Validator ID %d not found in committee of %v", duty.ValidatorIndex, duty.Committee) - if v.emitAccountMetrics { - ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc() - } - return - } - // Send the attestation to the beacon node. if err := v.db.SlashableAttestationCheck(ctx, indexedAtt, pubKey, signingRoot, v.emitAccountMetrics, ValidatorAttestFailVec); err != nil { log.WithError(err).Error("Failed attestation slashing protection check") @@ -149,8 +132,6 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot primitives.Slot, return } - } - var aggregationBitfield bitfield.Bitlist var attResp *ethpb.AttestResponse