99 mockSync "github.com/OffchainLabs/prysm/v6/beacon-chain/sync/initial-sync/testing"
1010 "github.com/OffchainLabs/prysm/v6/config/params"
1111 "github.com/OffchainLabs/prysm/v6/consensus-types/blocks"
12+ "github.com/OffchainLabs/prysm/v6/consensus-types/primitives"
1213 "github.com/OffchainLabs/prysm/v6/crypto/bls"
1314 "github.com/OffchainLabs/prysm/v6/encoding/bytesutil"
1415 ethpb "github.com/OffchainLabs/prysm/v6/proto/prysm/v1alpha1"
@@ -51,15 +52,15 @@ func TestProposer_GetSyncAggregate_OK(t *testing.T) {
5152 require .NoError (t , proposerServer .SyncCommitteePool .SaveSyncCommitteeContribution (cont ))
5253 }
5354
54- aggregate , err := proposerServer .getSyncAggregate (t .Context (), 1 , bytesutil .ToBytes32 (conts [0 ].BlockRoot ))
55+ aggregate , err := proposerServer .getSyncAggregate (t .Context (), 1 , bytesutil .ToBytes32 (conts [0 ].BlockRoot ), st )
5556 require .NoError (t , err )
5657 require .DeepEqual (t , bitfield.Bitvector32 {0xf , 0xf , 0xf , 0xf }, aggregate .SyncCommitteeBits )
5758
58- aggregate , err = proposerServer .getSyncAggregate (t .Context (), 2 , bytesutil .ToBytes32 (conts [0 ].BlockRoot ))
59+ aggregate , err = proposerServer .getSyncAggregate (t .Context (), 2 , bytesutil .ToBytes32 (conts [0 ].BlockRoot ), st )
5960 require .NoError (t , err )
6061 require .DeepEqual (t , bitfield.Bitvector32 {0xaa , 0xaa , 0xaa , 0xaa }, aggregate .SyncCommitteeBits )
6162
62- aggregate , err = proposerServer .getSyncAggregate (t .Context (), 3 , bytesutil .ToBytes32 (conts [0 ].BlockRoot ))
63+ aggregate , err = proposerServer .getSyncAggregate (t .Context (), 3 , bytesutil .ToBytes32 (conts [0 ].BlockRoot ), st )
6364 require .NoError (t , err )
6465 require .DeepEqual (t , bitfield .NewBitvector32 (), aggregate .SyncCommitteeBits )
6566}
@@ -68,7 +69,7 @@ func TestServer_SetSyncAggregate_EmptyCase(t *testing.T) {
6869 b , err := blocks .NewSignedBeaconBlock (util .NewBeaconBlockAltair ())
6970 require .NoError (t , err )
7071 s := & Server {} // Sever is not initialized with sync committee pool.
71- s .setSyncAggregate (t .Context (), b )
72+ s .setSyncAggregate (t .Context (), b , nil )
7273 agg , err := b .Block ().Body ().SyncAggregate ()
7374 require .NoError (t , err )
7475
@@ -138,7 +139,7 @@ func TestProposer_GetSyncAggregate_IncludesSyncCommitteeMessages(t *testing.T) {
138139 }
139140
140141 // The final sync aggregates must have indexes [0,1,2,3] set for both subcommittees
141- sa , err := proposerServer .getSyncAggregate (t .Context (), 1 , r )
142+ sa , err := proposerServer .getSyncAggregate (t .Context (), 1 , r , st )
142143 require .NoError (t , err )
143144 assert .Equal (t , true , sa .SyncCommitteeBits .BitAt (0 ))
144145 assert .Equal (t , true , sa .SyncCommitteeBits .BitAt (1 ))
@@ -194,8 +195,99 @@ func Test_aggregatedSyncCommitteeMessages_NoIntersectionWithPoolContributions(t
194195 BlockRoot : r [:],
195196 }
196197
197- aggregated , err := proposerServer .aggregatedSyncCommitteeMessages (t .Context (), 1 , r , []* ethpb.SyncCommitteeContribution {cont })
198+ aggregated , err := proposerServer .aggregatedSyncCommitteeMessages (t .Context (), 1 , r , []* ethpb.SyncCommitteeContribution {cont }, st )
198199 require .NoError (t , err )
199200 require .Equal (t , 1 , len (aggregated ))
200201 assert .Equal (t , false , aggregated [0 ].AggregationBits .BitAt (3 ))
201202}
203+
204+ func TestGetSyncAggregate_CorrectStateAtSyncCommitteePeriodBoundary (t * testing.T ) {
205+ helpers .ClearCache ()
206+ syncPeriodBoundaryEpoch := primitives .Epoch (274176 ) // Real epoch from the bug report
207+ slotsPerEpoch := params .BeaconConfig ().SlotsPerEpoch
208+
209+ preEpochState , keys := util .DeterministicGenesisStateAltair (t , 100 )
210+ require .NoError (t , preEpochState .SetSlot (primitives .Slot (syncPeriodBoundaryEpoch )* slotsPerEpoch - 1 )) // Last slot of previous epoch
211+
212+ postEpochState := preEpochState .Copy ()
213+ require .NoError (t , postEpochState .SetSlot (primitives .Slot (syncPeriodBoundaryEpoch )* slotsPerEpoch + 2 )) // After 2 missed slots
214+
215+ oldCommittee := & ethpb.SyncCommittee {
216+ Pubkeys : make ([][]byte , params .BeaconConfig ().SyncCommitteeSize ),
217+ }
218+ newCommittee := & ethpb.SyncCommittee {
219+ Pubkeys : make ([][]byte , params .BeaconConfig ().SyncCommitteeSize ),
220+ }
221+
222+ for i := 0 ; i < int (params .BeaconConfig ().SyncCommitteeSize ); i ++ {
223+ if i < len (keys ) {
224+ oldCommittee .Pubkeys [i ] = keys [i % len (keys )].PublicKey ().Marshal ()
225+ // Use different keys for new committee to simulate rotation
226+ newCommittee .Pubkeys [i ] = keys [(i + 10 )% len (keys )].PublicKey ().Marshal ()
227+ }
228+ }
229+
230+ require .NoError (t , preEpochState .SetCurrentSyncCommittee (oldCommittee ))
231+ require .NoError (t , postEpochState .SetCurrentSyncCommittee (newCommittee ))
232+
233+ mockChainService := & chainmock.ChainService {
234+ State : postEpochState ,
235+ }
236+
237+ proposerServer := & Server {
238+ HeadFetcher : mockChainService ,
239+ SyncChecker : & mockSync.Sync {IsSyncing : false },
240+ SyncCommitteePool : synccommittee .NewStore (),
241+ }
242+
243+ slot := primitives .Slot (syncPeriodBoundaryEpoch )* slotsPerEpoch + 1 // First slot of new epoch
244+ blockRoot := [32 ]byte {0x01 , 0x02 , 0x03 }
245+
246+ msg1 := & ethpb.SyncCommitteeMessage {
247+ Slot : slot ,
248+ BlockRoot : blockRoot [:],
249+ ValidatorIndex : 0 , // This validator is in position 0 of OLD committee
250+ Signature : bls .NewAggregateSignature ().Marshal (),
251+ }
252+ msg2 := & ethpb.SyncCommitteeMessage {
253+ Slot : slot ,
254+ BlockRoot : blockRoot [:],
255+ ValidatorIndex : 1 , // This validator is in position 1 of OLD committee
256+ Signature : bls .NewAggregateSignature ().Marshal (),
257+ }
258+
259+ require .NoError (t , proposerServer .SyncCommitteePool .SaveSyncCommitteeMessage (msg1 ))
260+ require .NoError (t , proposerServer .SyncCommitteePool .SaveSyncCommitteeMessage (msg2 ))
261+
262+ aggregateWrongState , err := proposerServer .getSyncAggregate (t .Context (), slot , blockRoot , postEpochState )
263+ require .NoError (t , err )
264+
265+ aggregateCorrectState , err := proposerServer .getSyncAggregate (t .Context (), slot , blockRoot , preEpochState )
266+ require .NoError (t , err )
267+
268+ wrongStateBits := bitfield .Bitlist (aggregateWrongState .SyncCommitteeBits )
269+ correctStateBits := bitfield .Bitlist (aggregateCorrectState .SyncCommitteeBits )
270+
271+ wrongStateHasValidators := false
272+ correctStateHasValidators := false
273+
274+ for i := 0 ; i < len (wrongStateBits ); i ++ {
275+ if wrongStateBits [i ] != 0 {
276+ wrongStateHasValidators = true
277+ break
278+ }
279+ }
280+
281+ for i := 0 ; i < len (correctStateBits ); i ++ {
282+ if correctStateBits [i ] != 0 {
283+ correctStateHasValidators = true
284+ break
285+ }
286+ }
287+
288+ assert .Equal (t , true , correctStateHasValidators , "Correct state should include validators that sent messages" )
289+ assert .Equal (t , false , wrongStateHasValidators , "Wrong state should not find validators in incorrect sync committee" )
290+
291+ t .Logf ("Wrong state aggregate bits: %x (has validators: %v)" , wrongStateBits , wrongStateHasValidators )
292+ t .Logf ("Correct state aggregate bits: %x (has validators: %v)" , correctStateBits , correctStateHasValidators )
293+ }
0 commit comments