diff --git a/beacon-chain/blockchain/process_block_test.go b/beacon-chain/blockchain/process_block_test.go index a416ee296e..b9094ac271 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 75c5b7efa0..e6be851117 100644 --- a/beacon-chain/core/blocks/attester_slashing_test.go +++ b/beacon-chain/core/blocks/attester_slashing_test.go @@ -7,12 +7,14 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing" v "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/validators" + "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/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/crypto/bls" "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" @@ -105,293 +107,162 @@ func TestProcessAttesterSlashings_IndexedAttestationFailedToVerify(t *testing.T) } func TestProcessAttesterSlashings_AppliesCorrectStatus(t *testing.T) { - beaconState, privKeys := util.DeterministicGenesisState(t, 100) - for _, vv := range beaconState.Validators() { - vv.WithdrawableEpoch = primitives.Epoch(params.BeaconConfig().SlotsPerEpoch) - } + statePhase0, keysPhase0 := util.DeterministicGenesisState(t, 100) + stateAltair, keysAltair := util.DeterministicGenesisStateAltair(t, 100) + stateBellatrix, keysBellatrix := util.DeterministicGenesisStateBellatrix(t, 100) + stateCapella, keysCapella := util.DeterministicGenesisStateCapella(t, 100) + stateDeneb, keysDeneb := util.DeterministicGenesisStateDeneb(t, 100) + stateElectra, keysElectra := util.DeterministicGenesisStateElectra(t, 100) - att1 := util.HydrateIndexedAttestation(ðpb.IndexedAttestation{ + att1Phase0 := 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{ + att2Phase0 := 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(31750000000), newState.Balances()[1]) - require.Equal(t, uint64(32000000000), newState.Balances()[2]) -} - -func TestProcessAttesterSlashings_AppliesCorrectStatusAltair(t *testing.T) { - beaconState, privKeys := util.DeterministicGenesisStateAltair(t, 100) - for _, vv := range beaconState.Validators() { - vv.WithdrawableEpoch = primitives.Epoch(params.BeaconConfig().SlotsPerEpoch) - } - - att1 := util.HydrateIndexedAttestation(ðpb.IndexedAttestation{ + att1Electra := 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.HydrateIndexedAttestation(ðpb.IndexedAttestation{ + att2Electra := 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.AttesterSlashing{ + slashingPhase0 := ðpb.AttesterSlashing{ + Attestation_1: att1Phase0, + Attestation_2: att2Phase0, + } + slashingElectra := ðpb.AttesterSlashingElectra{ + Attestation_1: att1Electra, + Attestation_2: att2Electra, + } + + type testCase struct { + name string + st state.BeaconState + keys []bls.SecretKey + att1 ethpb.IndexedAtt + att2 ethpb.IndexedAtt + slashing ethpb.AttSlashing + slashedBalance uint64 + } + + testCases := []testCase{ { - Attestation_1: att1, - Attestation_2: att2, + name: "phase0", + st: statePhase0, + keys: keysPhase0, + att1: att1Phase0, + att2: att2Phase0, + slashing: slashingPhase0, + slashedBalance: 31750000000, + }, + { + name: "altair", + st: stateAltair, + keys: keysAltair, + att1: att1Phase0, + att2: att2Phase0, + slashing: slashingPhase0, + slashedBalance: 31500000000, + }, + { + name: "bellatrix", + st: stateBellatrix, + keys: keysBellatrix, + att1: att1Phase0, + att2: att2Phase0, + slashing: slashingPhase0, + slashedBalance: 31000000000, + }, + { + name: "capella", + st: stateCapella, + keys: keysCapella, + att1: att1Phase0, + att2: att2Phase0, + slashing: slashingPhase0, + slashedBalance: 31000000000, + }, + { + name: "deneb", + st: stateDeneb, + keys: keysDeneb, + att1: att1Phase0, + att2: att2Phase0, + slashing: slashingPhase0, + slashedBalance: 31000000000, + }, + { + name: "electra", + st: stateElectra, + keys: keysElectra, + att1: att1Electra, + att2: att2Electra, + slashing: slashingElectra, + slashedBalance: 31992187500, }, } - currentSlot := 2 * params.BeaconConfig().SlotsPerEpoch - require.NoError(t, beaconState.SetSlot(currentSlot)) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + for _, vv := range tc.st.Validators() { + vv.WithdrawableEpoch = primitives.Epoch(params.BeaconConfig().SlotsPerEpoch) + } - b := util.NewBeaconBlock() - b.Block = ðpb.BeaconBlock{ - Body: ðpb.BeaconBlockBody{ - AttesterSlashings: slashings, - }, - } + domain, err := signing.Domain(tc.st.Fork(), 0, params.BeaconConfig().DomainBeaconAttester, tc.st.GenesisValidatorsRoot()) + require.NoError(t, err) + signingRoot, err := signing.ComputeSigningRoot(tc.att1.GetData(), domain) + assert.NoError(t, err, "Could not get signing root of beacon block header") + sig0 := tc.keys[0].Sign(signingRoot[:]) + sig1 := tc.keys[1].Sign(signingRoot[:]) + aggregateSig := bls.AggregateSignatures([]bls.Signature{sig0, sig1}) - 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() + if tc.att1.Version() >= version.Electra { + tc.att1.(*ethpb.IndexedAttestationElectra).Signature = aggregateSig.Marshal() + } else { + tc.att1.(*ethpb.IndexedAttestation).Signature = aggregateSig.Marshal() + } - // 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( - ` + signingRoot, err = signing.ComputeSigningRoot(tc.att2.GetData(), domain) + assert.NoError(t, err, "Could not get signing root of beacon block header") + sig0 = tc.keys[0].Sign(signingRoot[:]) + sig1 = tc.keys[1].Sign(signingRoot[:]) + aggregateSig = bls.AggregateSignatures([]bls.Signature{sig0, sig1}) + + if tc.att2.Version() >= version.Electra { + tc.att2.(*ethpb.IndexedAttestationElectra).Signature = aggregateSig.Marshal() + } else { + tc.att2.(*ethpb.IndexedAttestation).Signature = aggregateSig.Marshal() + } + + currentSlot := 2 * params.BeaconConfig().SlotsPerEpoch + require.NoError(t, tc.st.SetSlot(currentSlot)) + + newState, err := blocks.ProcessAttesterSlashings(context.Background(), tc.st, []ethpb.AttSlashing{tc.slashing}, 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 != tc.st.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, - ) - } + tc.st.Validators()[1].ExitEpoch, + newRegistry[1].ExitEpoch, + ) + } - require.Equal(t, uint64(31500000000), newState.Balances()[1]) - require.Equal(t, uint64(32000000000), newState.Balances()[2]) -} - -func TestProcessAttesterSlashings_AppliesCorrectStatusBellatrix(t *testing.T) { - beaconState, privKeys := util.DeterministicGenesisStateBellatrix(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_AppliesCorrectStatusCapella(t *testing.T) { - beaconState, privKeys := util.DeterministicGenesisStateCapella(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]) + require.Equal(t, tc.slashedBalance, 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 f8e742e2f7..dc0cc3ef53 100644 --- a/beacon-chain/core/transition/transition.go +++ b/beacon-chain/core/transition/transition.go @@ -403,11 +403,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 6b79b1d6a1..37561e699b 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/db/kv/blocks.go b/beacon-chain/db/kv/blocks.go index 9c5e1cb2da..6539d772de 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): @@ -874,7 +874,7 @@ func keyForBlock(blk interfaces.ReadOnlySignedBeaconBlock) ([]byte, error) { if blk.IsBlinded() { return electraBlindKey, nil } - return electraKey, nil + return ElectraKey, nil } if v >= version.Deneb { diff --git a/beacon-chain/db/kv/key.go b/beacon-chain/db/kv/key.go index 4bbb2008f9..3ec366e3b6 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 cc1655c18c..9969a9962d 100644 --- a/beacon-chain/db/kv/lightclient.go +++ b/beacon-chain/db/kv/lightclient.go @@ -167,13 +167,13 @@ func decodeLightClientBootstrap(enc []byte) (interfaces.LightClientBootstrap, [] } m = bootstrap syncCommitteeHash = enc[len(denebKey) : len(denebKey)+32] - case hasElectraKey(enc): + case HasElectraKey(enc): bootstrap := ðpb.LightClientBootstrapElectra{} - if err := bootstrap.UnmarshalSSZ(enc[len(electraKey)+32:]); err != nil { + if err := bootstrap.UnmarshalSSZ(enc[len(ElectraKey)+32:]); err != nil { return nil, nil, errors.Wrap(err, "could not unmarshal Electra light client bootstrap") } m = bootstrap - syncCommitteeHash = enc[len(electraKey) : len(electraKey)+32] + syncCommitteeHash = enc[len(ElectraKey) : len(ElectraKey)+32] default: return nil, nil, errors.New("decoding of saved light client bootstrap is unsupported") } @@ -277,9 +277,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 @@ -292,7 +292,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 7d359a9897..77264be47c 100644 --- a/beacon-chain/db/kv/schema.go +++ b/beacon-chain/db/kv/schema.go @@ -53,7 +53,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") fuluKey = []byte("fulu") fuluBlindKey = []byte("blind-fulu") diff --git a/beacon-chain/db/kv/state.go b/beacon-chain/db/kv/state.go index d2e01025a8..365ef557ce 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 } @@ -530,9 +530,9 @@ func (s *Store) unmarshalState(_ context.Context, enc []byte, validatorEntries [ protoState.Validators = validatorEntries } return statenative.InitializeFromProtoUnsafeFulu(protoState) - 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() @@ -688,7 +688,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 case version.Fulu: rState, ok := st.ToProtoUnsafe().(*ethpb.BeaconStateFulu) if !ok { diff --git a/beacon-chain/db/slasherkv/BUILD.bazel b/beacon-chain/db/slasherkv/BUILD.bazel index 3c9fe38981..6615999499 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/db/slasherkv/pruning_test.go b/beacon-chain/db/slasherkv/pruning_test.go index 131fdcbd66..b8d272accc 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 86d1157a0c..81a56e9264 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 292e109042..c2bac212de 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, + }, + } - // 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) + 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) + } - require.DeepEqual(t, expected.IndexedAttestation.GetData().Source.Epoch, actual.IndexedAttestation.GetData().Source.Epoch) + // 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, tc.vi, primitives.Epoch(i+1)) + require.NoError(t, err) + + 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, diff --git a/beacon-chain/rpc/eth/events/events_test.go b/beacon-chain/rpc/eth/events/events_test.go index 44eb1b4b98..09c82309d3 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/changelog/radek_eip-7549-slasher-pt1.md b/changelog/radek_eip-7549-slasher-pt1.md index 89520e5497..fce7603c4c 100644 --- a/changelog/radek_eip-7549-slasher-pt1.md +++ b/changelog/radek_eip-7549-slasher-pt1.md @@ -1,3 +1,3 @@ ### Added -- Update slasher service to Electra \ No newline at end of file +- Update slasher service to Electra. \ No newline at end of file diff --git a/changelog/radek_eip-7549-slasher-pt2.md b/changelog/radek_eip-7549-slasher-pt2.md new file mode 100644 index 0000000000..b2b86c51c1 --- /dev/null +++ b/changelog/radek_eip-7549-slasher-pt2.md @@ -0,0 +1,3 @@ +### Added + +- Handle `AttesterSlashingElectra` everywhere in the codebase. \ No newline at end of file diff --git a/config/params/configset_test.go b/config/params/configset_test.go index ea15a994db..bfe4f72c0e 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 3a69241404..cf900af176 100644 --- a/config/params/loader_test.go +++ b/config/params/loader_test.go @@ -135,6 +135,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 617e77a2ab..e84d8b387f 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/util/attestation.go b/testing/util/attestation.go index 713d87cb12..73e92f7c8a 100644 --- a/testing/util/attestation.go +++ b/testing/util/attestation.go @@ -391,3 +391,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 +} diff --git a/validator/client/attest.go b/validator/client/attest.go index 1ec917aa47..cd74fb2d18 100644 --- a/validator/client/attest.go +++ b/validator/client/attest.go @@ -122,18 +122,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 } var aggregationBitfield bitfield.Bitlist