1313
1414package tech .pegasys .teku .networking .eth2 .gossip .forks ;
1515
16+ import static org .assertj .core .api .Assertions .assertThat ;
1617import static org .assertj .core .api .Assertions .assertThatThrownBy ;
18+ import static org .junit .jupiter .api .Assertions .assertEquals ;
1719import static org .mockito .ArgumentMatchers .any ;
1820import static org .mockito .ArgumentMatchers .anyBoolean ;
1921import static org .mockito .Mockito .mock ;
2022import static org .mockito .Mockito .never ;
23+ import static org .mockito .Mockito .reset ;
2124import static org .mockito .Mockito .times ;
2225import static org .mockito .Mockito .verify ;
2326import static org .mockito .Mockito .when ;
3235import org .junit .jupiter .params .provider .MethodSource ;
3336import tech .pegasys .teku .infrastructure .unsigned .UInt64 ;
3437import tech .pegasys .teku .spec .Spec ;
38+ import tech .pegasys .teku .spec .SpecMilestone ;
3539import tech .pegasys .teku .spec .TestSpecFactory ;
3640import tech .pegasys .teku .spec .datastructures .attestation .ValidatableAttestation ;
3741import tech .pegasys .teku .spec .datastructures .blocks .SignedBeaconBlock ;
3842import tech .pegasys .teku .spec .datastructures .genesis .GenesisData ;
43+ import tech .pegasys .teku .spec .datastructures .operations .SignedVoluntaryExit ;
44+ import tech .pegasys .teku .spec .datastructures .operations .VoluntaryExit ;
3945import tech .pegasys .teku .spec .datastructures .operations .versions .altair .ValidatableSyncCommitteeMessage ;
4046import tech .pegasys .teku .spec .util .DataStructureUtil ;
4147import tech .pegasys .teku .storage .client .RecentChainData ;
48+ import tech .pegasys .teku .storage .store .UpdatableStore ;
4249
4350class GossipForkManagerTest {
4451 private static final Bytes32 GENESIS_VALIDATORS_ROOT = Bytes32 .fromHexString ("0x12345678446687" );
@@ -49,6 +56,7 @@ class GossipForkManagerTest {
4956
5057 @ BeforeEach
5158 void setUp () {
59+ reset (recentChainData );
5260 when (recentChainData .getGenesisData ())
5361 .thenReturn (
5462 Optional .of (new GenesisData (UInt64 .valueOf (134234134L ), GENESIS_VALIDATORS_ROOT )));
@@ -348,6 +356,62 @@ void shouldNotPublishSyncCommitteeMessagesToForksThatAreNotActive() {
348356 verify (secondFork , never ()).publishSyncCommitteeMessage (message );
349357 }
350358
359+ @ Test
360+ void shouldPublishVoluntaryExitOnCapella () {
361+ final Spec specCapella = TestSpecFactory .createMinimalCapella ();
362+ final GossipForkSubscriptions capellaFork = forkAtEpoch (0 );
363+ final GossipForkManager .Builder builder =
364+ GossipForkManager .builder ().recentChainData (recentChainData ).spec (specCapella );
365+ Stream .of (capellaFork ).forEach (builder ::fork );
366+ final GossipForkManager manager = builder .build ();
367+
368+ final UpdatableStore store = mock (UpdatableStore .class );
369+ when (recentChainData .getStore ()).thenReturn (store );
370+ when (store .getGenesisTime ()).thenReturn (UInt64 .ZERO );
371+ when (store .getTimeSeconds ()).thenReturn (UInt64 .ONE );
372+
373+ final VoluntaryExit voluntaryExit = new VoluntaryExit (UInt64 .ZERO , UInt64 .ONE );
374+ final SignedVoluntaryExit capellaVoluntaryExit =
375+ new SignedVoluntaryExit (voluntaryExit , dataStructureUtil .randomSignature ());
376+
377+ manager .configureGossipForEpoch (UInt64 .ZERO );
378+
379+ manager .publishVoluntaryExit (capellaVoluntaryExit );
380+ verify (capellaFork ).publishVoluntaryExit (capellaVoluntaryExit );
381+ }
382+
383+ @ Test
384+ void shouldPublishCapellaVoluntaryExitAfterCapella () {
385+ final Spec specDeneb = TestSpecFactory .createMinimalWithDenebForkEpoch (UInt64 .ONE );
386+ final GossipForkSubscriptions capellaFork = forkAtEpoch (0 );
387+ final GossipForkSubscriptions denebFork = forkAtEpoch (1 );
388+ final GossipForkManager .Builder builder =
389+ GossipForkManager .builder ().recentChainData (recentChainData ).spec (specDeneb );
390+ Stream .of (capellaFork , denebFork ).forEach (builder ::fork );
391+ final GossipForkManager manager = builder .build ();
392+
393+ final UpdatableStore store = mock (UpdatableStore .class );
394+ when (recentChainData .getStore ()).thenReturn (store );
395+ when (store .getGenesisTime ()).thenReturn (UInt64 .ZERO );
396+ when (store .getTimeSeconds ()).thenReturn (UInt64 .valueOf (9000 ));
397+ assertThat (specDeneb .getCurrentEpoch (store )).isGreaterThanOrEqualTo (UInt64 .valueOf (3 ));
398+
399+ final VoluntaryExit voluntaryExit = new VoluntaryExit (UInt64 .ZERO , UInt64 .ONE );
400+ final SignedVoluntaryExit capellaVoluntaryExit =
401+ new SignedVoluntaryExit (voluntaryExit , dataStructureUtil .randomSignature ());
402+ assertEquals (
403+ SpecMilestone .CAPELLA ,
404+ specDeneb .atEpoch (capellaVoluntaryExit .getMessage ().getEpoch ()).getMilestone ());
405+
406+ // Deneb
407+ // Previous subscriptions are stopped in 2 epochs after fork transition
408+ manager .configureGossipForEpoch (UInt64 .valueOf (3 ));
409+
410+ manager .publishVoluntaryExit (capellaVoluntaryExit );
411+ verify (capellaFork , never ()).publishVoluntaryExit (capellaVoluntaryExit );
412+ verify (denebFork ).publishVoluntaryExit (capellaVoluntaryExit );
413+ }
414+
351415 @ ParameterizedTest
352416 @ MethodSource ("subnetSubscriptionTypes" )
353417 void shouldSubscribeToAttestationSubnetsPriorToStarting (final SubscriptionType subscriptionType ) {
0 commit comments