@@ -1495,6 +1495,88 @@ func TestUnit_NodeLifecycle_unreachableLoop(t *testing.T) {
14951495 return node .State () == nodeStateAlive
14961496 })
14971497 })
1498+ t .Run ("with PollSuccessThreshold set, without isSyncing, node becomes alive once all probe polls succeed" , func (t * testing.T ) {
1499+ t .Parallel ()
1500+ rpc := newMockRPCClient [ID , Head ](t )
1501+ nodeChainID := RandomID ()
1502+ const pollSuccessThreshold = 2
1503+ node := newAliveNode (t , testNodeOpts {
1504+ rpc : rpc ,
1505+ chainID : nodeChainID ,
1506+ config : testNodeConfig {
1507+ pollSuccessThreshold : pollSuccessThreshold ,
1508+ pollInterval : tests .TestInterval ,
1509+ },
1510+ })
1511+ defer func () { assert .NoError (t , node .close ()) }()
1512+
1513+ rpc .On ("Dial" , mock .Anything ).Return (nil ).Once ()
1514+ rpc .On ("ChainID" , mock .Anything ).Return (nodeChainID , nil ).Once ()
1515+ rpc .On ("ClientVersion" , mock .Anything ).Return ("" , nil ).Twice ()
1516+ setupRPCForAliveLoop (t , rpc )
1517+
1518+ node .declareUnreachable ()
1519+ tests .AssertEventually (t , func () bool {
1520+ return node .State () == nodeStateAlive
1521+ })
1522+ })
1523+ t .Run ("with PollSuccessThreshold set, node becomes alive once all probe polls succeed" , func (t * testing.T ) {
1524+ t .Parallel ()
1525+ rpc := newMockRPCClient [ID , Head ](t )
1526+ nodeChainID := RandomID ()
1527+ const pollSuccessThreshold = 2
1528+ node := newAliveNode (t , testNodeOpts {
1529+ rpc : rpc ,
1530+ chainID : nodeChainID ,
1531+ config : testNodeConfig {
1532+ nodeIsSyncingEnabled : true ,
1533+ pollSuccessThreshold : pollSuccessThreshold ,
1534+ pollInterval : tests .TestInterval ,
1535+ },
1536+ })
1537+ defer func () { assert .NoError (t , node .close ()) }()
1538+
1539+ rpc .On ("Dial" , mock .Anything ).Return (nil ).Once ()
1540+ rpc .On ("ChainID" , mock .Anything ).Return (nodeChainID , nil ).Once ()
1541+ rpc .On ("IsSyncing" , mock .Anything ).Return (false , nil )
1542+ rpc .On ("ClientVersion" , mock .Anything ).Return ("" , nil ).Twice ()
1543+ setupRPCForAliveLoop (t , rpc )
1544+
1545+ node .declareUnreachable ()
1546+ tests .AssertEventually (t , func () bool {
1547+ return node .State () == nodeStateAlive
1548+ })
1549+ })
1550+ t .Run ("with PollSuccessThreshold set, probe poll failure keeps node unreachable and restarts redial" , func (t * testing.T ) {
1551+ t .Parallel ()
1552+ rpc := newMockRPCClient [ID , Head ](t )
1553+ nodeChainID := RandomID ()
1554+ lggr , observedLogs := logger .TestObserved (t , zap .WarnLevel )
1555+ const pollSuccessThreshold = 2
1556+ node := newAliveNode (t , testNodeOpts {
1557+ rpc : rpc ,
1558+ chainID : nodeChainID ,
1559+ lggr : lggr ,
1560+ config : testNodeConfig {
1561+ pollSuccessThreshold : pollSuccessThreshold ,
1562+ pollInterval : tests .TestInterval ,
1563+ },
1564+ })
1565+ defer func () { assert .NoError (t , node .close ()) }()
1566+
1567+ rpc .On ("Dial" , mock .Anything ).Return (nil ).Once ()
1568+ rpc .On ("ChainID" , mock .Anything ).Return (nodeChainID , nil ).Once ()
1569+ rpc .On ("ClientVersion" , mock .Anything ).Return ("" , nil ).Once ()
1570+ rpc .On ("ClientVersion" , mock .Anything ).Return ("" , errors .New ("probe poll failed" )).Once ()
1571+ // after the probe aborts, rpc.Close() is called and the redial backoff fires again; keep failing
1572+ rpc .On ("Dial" , mock .Anything ).Return (errors .New ("failed to dial" ))
1573+ // guard: if current code (no probe) enters aliveLoop, fail the subscribe so the node returns to unreachable
1574+ rpc .On ("SubscribeToHeads" , mock .Anything ).Return (nil , nil , errors .New ("unexpected" )).Maybe ()
1575+
1576+ node .declareUnreachable ()
1577+ tests .AssertLogEventually (t , observedLogs , "Recovery probe poll failed; restarting redial" )
1578+ assert .Equal (t , nodeStateUnreachable , node .State ())
1579+ })
14981580}
14991581
15001582func TestUnit_NodeLifecycle_invalidChainIDLoop (t * testing.T ) {
0 commit comments