Skip to content

Commit 357f7f9

Browse files
pm47t-bast
andauthored
Catch all connection failures and reconnect (#1760)
The `ReconnectionTask` was only catching `ConnectionResult.Failure.ConnectionFailed`, which is a subset of possible failures. It should instead have caught `ConnectionResult.Failure`. All authentication and initialization failures were not caught and didn't trigger reconnections. Co-authored-by: Bastien Teinturier <[email protected]>
1 parent 48c0c4c commit 357f7f9

File tree

2 files changed

+57
-7
lines changed

2 files changed

+57
-7
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/io/ReconnectionTask.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ class ReconnectionTask(nodeParams: NodeParams, remoteNodeId: PublicKey) extends
5353
startWith(IDLE, IdleData(Nothing))
5454

5555
when(CONNECTING) {
56-
case Event(_: PeerConnection.ConnectionResult.ConnectionFailed, d: ConnectingData) =>
57-
log.info(s"connection failed, next reconnection in ${d.nextReconnectionDelay.toSeconds} seconds")
56+
case Event(failure: PeerConnection.ConnectionResult.Failure, d: ConnectingData) =>
57+
log.info(s"connection failed (reason=$failure), next reconnection in ${d.nextReconnectionDelay.toSeconds} seconds")
5858
setReconnectTimer(d.nextReconnectionDelay)
5959
goto(WAITING) using WaitingData(nextReconnectionDelay(d.nextReconnectionDelay, nodeParams.maxReconnectInterval))
6060

eclair-core/src/test/scala/fr/acinq/eclair/io/ReconnectionTaskSpec.scala

+55-5
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ import scala.concurrent.duration._
3030

3131
class ReconnectionTaskSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with ParallelTestExecution {
3232

33-
val fakeIPAddress = NodeAddress.fromParts("1.2.3.4", 42000).get
34-
val channels = Map(Peer.FinalChannelId(randomBytes32) -> system.deadLetters)
33+
private val fakeIPAddress = NodeAddress.fromParts("1.2.3.4", 42000).get
34+
private val channels = Map(Peer.FinalChannelId(randomBytes32) -> system.deadLetters)
3535

36-
val PeerNothingData = Peer.Nothing
37-
val PeerDisconnectedData = Peer.DisconnectedData(channels)
38-
val PeerConnectedData = Peer.ConnectedData(fakeIPAddress.socketAddress, system.deadLetters, null, null, channels.map { case (k: ChannelId, v) => (k, v) })
36+
private val PeerNothingData = Peer.Nothing
37+
private val PeerDisconnectedData = Peer.DisconnectedData(channels)
38+
private val PeerConnectedData = Peer.ConnectedData(fakeIPAddress.socketAddress, system.deadLetters, null, null, channels.map { case (k: ChannelId, v) => (k, v) })
3939

4040
case class FixtureParam(nodeParams: NodeParams, remoteNodeId: PublicKey, reconnectionTask: TestFSMRef[ReconnectionTask.State, ReconnectionTask.Data, ReconnectionTask], monitor: TestProbe)
4141

@@ -168,6 +168,56 @@ class ReconnectionTaskSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike
168168
assert(waitingData3.nextReconnectionDelay === (waitingData0.nextReconnectionDelay * 8))
169169
}
170170

171+
test("all kind of connection failures should be caught by the reconnection task", Tag("auto_reconnect")) { f =>
172+
import f._
173+
174+
val peer = TestProbe()
175+
nodeParams.db.peers.addOrUpdatePeer(remoteNodeId, fakeIPAddress)
176+
peer.send(reconnectionTask, Peer.Transition(PeerNothingData, PeerDisconnectedData))
177+
val TransitionWithData(ReconnectionTask.IDLE, ReconnectionTask.WAITING, _, _) = monitor.expectMsgType[TransitionWithData]
178+
val TransitionWithData(ReconnectionTask.WAITING, ReconnectionTask.CONNECTING, _, connectingData: ReconnectionTask.ConnectingData) = monitor.expectMsgType[TransitionWithData]
179+
180+
val failures = List(
181+
PeerConnection.ConnectionResult.ConnectionFailed(connectingData.to),
182+
PeerConnection.ConnectionResult.NoAddressFound,
183+
PeerConnection.ConnectionResult.InitializationFailed("incompatible features"),
184+
PeerConnection.ConnectionResult.AuthenticationFailed("authentication timeout")
185+
)
186+
187+
failures.foreach { failure =>
188+
// we simulate a connection error
189+
reconnectionTask ! failure
190+
// a new reconnection task will be scheduled
191+
val TransitionWithData(ReconnectionTask.CONNECTING, ReconnectionTask.WAITING, _, _) = monitor.expectMsgType[TransitionWithData]
192+
// we send the tick manually so we don't wait
193+
reconnectionTask ! ReconnectionTask.TickReconnect
194+
// this triggers a reconnection
195+
val TransitionWithData(ReconnectionTask.WAITING, ReconnectionTask.CONNECTING, _, _) = monitor.expectMsgType[TransitionWithData]
196+
}
197+
}
198+
199+
test("concurrent incoming/outgoing reconnection", Tag("auto_reconnect")) { f =>
200+
import f._
201+
202+
val peer = TestProbe()
203+
nodeParams.db.peers.addOrUpdatePeer(remoteNodeId, fakeIPAddress)
204+
peer.send(reconnectionTask, Peer.Transition(PeerNothingData, PeerDisconnectedData))
205+
val TransitionWithData(ReconnectionTask.IDLE, ReconnectionTask.WAITING, _, _) = monitor.expectMsgType[TransitionWithData]
206+
val TransitionWithData(ReconnectionTask.WAITING, ReconnectionTask.CONNECTING, _, _: ReconnectionTask.ConnectingData) = monitor.expectMsgType[TransitionWithData]
207+
208+
// at this point, we are attempting to connect to the peer
209+
// let's assume that an incoming connection arrives from the peer right before our outgoing connection, but we haven't
210+
// yet received the peer transition
211+
reconnectionTask ! PeerConnection.ConnectionResult.AlreadyConnected
212+
// we will schedule a reconnection
213+
val TransitionWithData(ReconnectionTask.CONNECTING, ReconnectionTask.WAITING, _, _) = monitor.expectMsgType[TransitionWithData]
214+
// but immediately after that we finally get notified that the peer is connected
215+
peer.send(reconnectionTask, Peer.Transition(PeerDisconnectedData, PeerConnectedData))
216+
// we cancel the reconnection and go to idle state
217+
val TransitionWithData(ReconnectionTask.WAITING, ReconnectionTask.IDLE, _, _) = monitor.expectMsgType[TransitionWithData]
218+
219+
}
220+
171221
test("reconnect using the address from node_announcement") { f =>
172222
import f._
173223

0 commit comments

Comments
 (0)