fix: use assigned committee index in attestation data after Electra (#15696)

* fix: use assigned committee index in attestation data after Electra

* update change log

* update add unit test

---------

Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
This commit is contained in:
Muzry
2025-09-18 01:08:56 +08:00
committed by GitHub
parent 7e32bbc199
commit 54991bbc52
3 changed files with 103 additions and 3 deletions

View File

@@ -0,0 +1,3 @@
### Fixed
- Fixed incorrect attestation data request where the assigned committee index was used after Electra, instead of 0.

View File

@@ -71,9 +71,15 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot primitives.Slot,
return
}
committeeIndex := duty.CommitteeIndex
postElectra := slots.ToEpoch(slot) >= params.BeaconConfig().ElectraForkEpoch
if postElectra {
committeeIndex = 0
}
req := &ethpb.AttestationDataRequest{
Slot: slot,
CommitteeIndex: duty.CommitteeIndex,
CommitteeIndex: committeeIndex,
}
data, err := v.validatorClient.AttestationData(ctx, req)
if err != nil {
@@ -95,8 +101,6 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot primitives.Slot,
return
}
postElectra := slots.ToEpoch(slot) >= params.BeaconConfig().ElectraForkEpoch
var indexedAtt ethpb.IndexedAtt
if postElectra {
indexedAtt = &ethpb.IndexedAttestationElectra{

View File

@@ -104,6 +104,99 @@ func TestAttestToBlockHead_SubmitAttestation_RequestFailure(t *testing.T) {
}
}
func TestSubmitAttestation_ElectraCommitteeIndex(t *testing.T) {
tests := []struct {
name string
electraForkEpoch uint64
attestationSlot primitives.Slot
assignedCommitteeIndex primitives.CommitteeIndex
expectedCommitteeIndex primitives.CommitteeIndex
isPostElectra bool
}{
{
name: "Pre-Electra uses assigned committee index",
electraForkEpoch: 10,
attestationSlot: 300,
assignedCommitteeIndex: 5,
expectedCommitteeIndex: 5,
isPostElectra: false,
},
{
name: "Post-Electra uses committee index 0",
electraForkEpoch: 1,
attestationSlot: 32,
assignedCommitteeIndex: 5,
expectedCommitteeIndex: 0,
isPostElectra: true,
},
}
for _, tt := range tests {
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("%s (SlashingProtectionMinimal:%v)", tt.name, isSlashingProtectionMinimal), func(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.ElectraForkEpoch = primitives.Epoch(tt.electraForkEpoch)
params.OverrideBeaconConfig(cfg)
validator, m, validatorKey, finish := setup(t, isSlashingProtectionMinimal)
defer finish()
validatorIndex := primitives.ValidatorIndex(7)
committee := []primitives.ValidatorIndex{0, 3, 4, 2, validatorIndex, 6, 8, 9, 10}
var pubKey [fieldparams.BLSPubkeyLength]byte
copy(pubKey[:], validatorKey.PublicKey().Marshal())
validator.duties = &ethpb.ValidatorDutiesContainer{CurrentEpochDuties: []*ethpb.ValidatorDuty{
{
PublicKey: validatorKey.PublicKey().Marshal(),
CommitteeIndex: tt.assignedCommitteeIndex,
CommitteeLength: uint64(len(committee)),
ValidatorIndex: validatorIndex,
},
}}
var capturedRequest *ethpb.AttestationDataRequest
// Capture the actual request to verify committee index
m.validatorClient.EXPECT().AttestationData(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.AttestationDataRequest{}),
).Do(func(_ context.Context, req *ethpb.AttestationDataRequest) {
capturedRequest = req
}).Return(&ethpb.AttestationData{
BeaconBlockRoot: make([]byte, fieldparams.RootLength),
Target: &ethpb.Checkpoint{Root: make([]byte, fieldparams.RootLength)},
Source: &ethpb.Checkpoint{Root: make([]byte, fieldparams.RootLength)},
}, nil)
m.validatorClient.EXPECT().DomainData(
gomock.Any(), // ctx
gomock.Any(), // epoch
).Times(2).Return(&ethpb.DomainResponse{SignatureDomain: make([]byte, 32)}, nil)
if tt.isPostElectra {
m.validatorClient.EXPECT().ProposeAttestationElectra(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.SingleAttestation{}),
).Return(&ethpb.AttestResponse{}, nil)
} else {
m.validatorClient.EXPECT().ProposeAttestation(
gomock.Any(), // ctx
gomock.AssignableToTypeOf(&ethpb.Attestation{}),
).Return(&ethpb.AttestResponse{}, nil)
}
validator.SubmitAttestation(t.Context(), tt.attestationSlot, pubKey)
// Verify the committee index in the request
require.NotNil(t, capturedRequest, "AttestationDataRequest should have been called")
assert.Equal(t, tt.expectedCommitteeIndex, capturedRequest.CommitteeIndex,
"Committee index mismatch: expected %d, got %d", tt.expectedCommitteeIndex, capturedRequest.CommitteeIndex)
assert.Equal(t, tt.attestationSlot, capturedRequest.Slot,
"Slot should match the provided slot")
})
}
}
}
func TestAttestToBlockHead_AttestsCorrectly(t *testing.T) {
for _, isSlashingProtectionMinimal := range [...]bool{false, true} {
t.Run(fmt.Sprintf("Phase 0 (SlashingProtectionMinimal:%v)", isSlashingProtectionMinimal), func(t *testing.T) {