1313import static org .hiero .otter .fixtures .assertions .StatusProgressionStep .target ;
1414
1515import com .swirlds .common .merkle .synchronization .config .ReconnectConfig_ ;
16+ import com .swirlds .logging .legacy .payload .ReconnectStartPayload ;
1617import com .swirlds .platform .consensus .ConsensusConfig_ ;
1718import com .swirlds .platform .wiring .PlatformSchedulersConfig_ ;
1819import edu .umd .cs .findbugs .annotations .NonNull ;
3132import org .hiero .otter .fixtures .result .MultipleNodePlatformStatusResults ;
3233import org .hiero .otter .fixtures .result .SingleNodePlatformStatusResult ;
3334import org .hiero .otter .fixtures .result .SubscriberAction ;
34- import org .junit .jupiter .api .Disabled ;
3535
3636/**
3737 * Tests the reconnect functionality of a node that has fallen behind in the consensus rounds. The test ensures that the
@@ -42,6 +42,8 @@ public class ReconnectTest {
4242 /** Reducing the number of rounds non-expired will allow nodes to require a reconnect faster. */
4343 private static final long ROUNDS_EXPIRED = 100L ;
4444
45+ public static final Duration BEHIND_WAIT_TIME = Duration .ofSeconds (240L );
46+
4547 /**
4648 * Tests that a node which is killed, kept down until it is behind, and then restarted is able to reconnect to the
4749 * network and catch up with consensus.
@@ -249,7 +251,6 @@ private void disableSyntheticBottleneck(@NonNull final Node... nodesToThrottle)
249251 * @param env the test environment
250252 */
251253 @ OtterTest (requires = Capability .RECONNECT )
252- @ Disabled
253254 void testReconnectSucceedsAfterFailure (@ NonNull final TestEnvironment env ) {
254255 final Network network = env .network ();
255256 final TimeManager timeManager = env .timeManager ();
@@ -263,7 +264,9 @@ void testReconnectSucceedsAfterFailure(@NonNull final TestEnvironment env) {
263264 PlatformSchedulersConfig_ .TRANSACTION_HANDLER ,
264265 "SEQUENTIAL_THREAD CAPACITY(100) FLUSHABLE SQUELCHABLE" )
265266 .withConfigValue (ConsensusConfig_ .ROUNDS_EXPIRED , ROUNDS_EXPIRED )
266- .withConfigValue (ReconnectConfig_ .ASYNC_STREAM_TIMEOUT , Duration .ofSeconds (1 ));
267+ .withConfigValue (ReconnectConfig_ .ASYNC_STREAM_TIMEOUT , Duration .ofSeconds (1 ))
268+ .withConfigValue (ReconnectConfig_ .MAXIMUM_RECONNECT_FAILURES_BEFORE_SHUTDOWN , 2 )
269+ .withConfigValue (ReconnectConfig_ .MINIMUM_TIME_BETWEEN_RECONNECTS , Duration .ofMillis (10 ));
267270
268271 network .start ();
269272
@@ -280,7 +283,7 @@ void testReconnectSucceedsAfterFailure(@NonNull final TestEnvironment env) {
280283 enableSyntheticBottleneck (Duration .ofMinutes (10 ), nodeToReconnect );
281284 timeManager .waitForCondition (
282285 nodeToReconnect ::isBehind ,
283- Duration . ofSeconds ( 120L ) ,
286+ BEHIND_WAIT_TIME ,
284287 "Node did not enter BEHIND status within the expected time "
285288 + "frame after synthetic bottleneck was enabled" );
286289
@@ -308,7 +311,6 @@ void testReconnectSucceedsAfterFailure(@NonNull final TestEnvironment env) {
308311 * @param env the test environment
309312 */
310313 @ OtterTest (requires = {Capability .RECONNECT , Capability .SINGLE_NODE_JVM_SHUTDOWN })
311- @ Disabled
312314 void testNodeShutsDownAfterMaxFailedReconnects (@ NonNull final TestEnvironment env ) {
313315 final Network network = env .network ();
314316 final TimeManager timeManager = env .timeManager ();
@@ -325,24 +327,29 @@ void testNodeShutsDownAfterMaxFailedReconnects(@NonNull final TestEnvironment en
325327 "SEQUENTIAL_THREAD CAPACITY(100) FLUSHABLE SQUELCHABLE" )
326328 .withConfigValue (ConsensusConfig_ .ROUNDS_EXPIRED , ROUNDS_EXPIRED )
327329 .withConfigValue (ReconnectConfig_ .ASYNC_STREAM_TIMEOUT , Duration .ofSeconds (1 ))
328- .withConfigValue (ReconnectConfig_ .MAXIMUM_RECONNECT_FAILURES_BEFORE_SHUTDOWN , maxFailedReconnects );
330+ .withConfigValue (ReconnectConfig_ .MAXIMUM_RECONNECT_FAILURES_BEFORE_SHUTDOWN , maxFailedReconnects )
331+ .withConfigValue (ReconnectConfig_ .MINIMUM_TIME_BETWEEN_RECONNECTS , Duration .ofMillis (10 ));
329332
330333 network .start ();
331334
332335 final Node nodeToReconnect = network .nodes ().getLast ();
333336
334- nodeToReconnect .newLogResult ().subscribe (logEntry -> {
335- if (logEntry .message ().contains ("Starting reconnect in role of the receiver." )) {
336- network .isolate (nodeToReconnect );
337- return SubscriberAction .UNSUBSCRIBE ;
337+ network .newReconnectResults ().subscribe (notification -> {
338+ if (notification .payload ().getClass ().equals (ReconnectStartPayload .class )) {
339+ final ReconnectStartPayload payload = (ReconnectStartPayload ) notification .payload ();
340+ final Node node = network .nodes ().stream ()
341+ .filter (n -> n .selfId ().id () == payload .getOtherNodeId ())
342+ .findFirst ()
343+ .orElse (nodeToReconnect );
344+ network .isolate (node );
338345 }
339346 return SubscriberAction .CONTINUE ;
340347 });
341348
342349 enableSyntheticBottleneck (Duration .ofMinutes (10 ), nodeToReconnect );
343350 timeManager .waitForCondition (
344351 nodeToReconnect ::isBehind ,
345- Duration . ofSeconds ( 120L ) ,
352+ BEHIND_WAIT_TIME ,
346353 "Node did not enter BEHIND status within the expected time "
347354 + "frame after synthetic bottleneck was enabled" );
348355
@@ -359,4 +366,59 @@ void testNodeShutsDownAfterMaxFailedReconnects(@NonNull final TestEnvironment en
359366 Duration .ofMinutes (2L ),
360367 "Node did not shut down within the expected time frame after exceeding the maximum number of failed reconnects." );
361368 }
369+
370+ @ OtterTest (requires = {Capability .RECONNECT , Capability .SINGLE_NODE_JVM_SHUTDOWN })
371+ void testIsolateNodeWhileReconnectingAndRestore (@ NonNull final TestEnvironment env ) {
372+ final Network network = env .network ();
373+ final TimeManager timeManager = env .timeManager ();
374+
375+ network .addNodes (5 );
376+
377+ final int maxFailedReconnects = 3 ;
378+
379+ // For this test to work, we need to lower the limit for the transaction handler component
380+ // With the new limit set, once the transaction handler has 100 pending transactions, the node will stop
381+ // gossipping and stop creating events. This will cause the node to go into the checking state.
382+ network .withConfigValue (
383+ PlatformSchedulersConfig_ .TRANSACTION_HANDLER ,
384+ "SEQUENTIAL_THREAD CAPACITY(100) FLUSHABLE SQUELCHABLE" )
385+ .withConfigValue (ConsensusConfig_ .ROUNDS_EXPIRED , ROUNDS_EXPIRED )
386+ .withConfigValue (ReconnectConfig_ .ASYNC_STREAM_TIMEOUT , Duration .ofSeconds (1 ))
387+ .withConfigValue (ReconnectConfig_ .MAXIMUM_RECONNECT_FAILURES_BEFORE_SHUTDOWN , maxFailedReconnects )
388+ .withConfigValue (ReconnectConfig_ .MINIMUM_TIME_BETWEEN_RECONNECTS , Duration .ofMillis (10 ));
389+
390+ network .start ();
391+
392+ final Node nodeToReconnect = network .nodes ().getLast ();
393+
394+ network .newReconnectResults ().subscribe (notification -> {
395+ if (notification .payload ().getClass ().equals (ReconnectStartPayload .class )) {
396+ network .isolate (nodeToReconnect );
397+ return SubscriberAction .UNSUBSCRIBE ;
398+ }
399+ return SubscriberAction .CONTINUE ;
400+ });
401+
402+ enableSyntheticBottleneck (Duration .ofMinutes (10 ), nodeToReconnect );
403+ timeManager .waitForCondition (
404+ nodeToReconnect ::isBehind ,
405+ BEHIND_WAIT_TIME ,
406+ "Node did not enter BEHIND status within the expected time "
407+ + "frame after synthetic bottleneck was enabled" );
408+
409+ network .setBandwidthForAllConnections (nodeToReconnect , BandwidthLimit .ofKilobytesPerSecond (1 ));
410+
411+ disableSyntheticBottleneck (nodeToReconnect );
412+
413+ timeManager .waitForCondition (
414+ () -> nodeToReconnect .newReconnectResult ().numFailedReconnects () > 0 ,
415+ Duration .ofMinutes (2L ),
416+ "Node did not record the expected number of failed reconnect attempts within the expected time frame." );
417+ timeManager .waitFor (Duration .ofSeconds (30 ));
418+ network .restoreConnectivity ();
419+ timeManager .waitForCondition (
420+ nodeToReconnect ::isActive ,
421+ Duration .ofMinutes (2L ),
422+ "Node did not become ACTIVE within the expected time frame after restoring normal connectivity" );
423+ }
362424}
0 commit comments