From c4786f3a7b9458ec62251f4ca1455fcc72a13b0c Mon Sep 17 00:00:00 2001 From: Bastien Teinturier <31281497+t-bast@users.noreply.github.com> Date: Wed, 8 Jun 2022 16:33:59 +0200 Subject: [PATCH] Store disabled channel update when offline (#2307) When a channel is offline and we want to relay an HTLC through it, we emit a new `channel_update` with the disable bit set (unless it was already disabled). We previously didn't persist our internal state with this new `channel_update`, which created the following issue: if eclair is restarted before the channel comes back online, eclair would only try to rebroadcast the previous `channel_update` which has a lower timestamp than the one disabling the channel. That `channel_update` is then rejected by the network, causing the channel to stay disabled until the next refresh, which only happens after 10 days. --- .../main/scala/fr/acinq/eclair/channel/fsm/Channel.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala index adc691063d..3b1b73c223 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala @@ -699,18 +699,17 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val // we cancel the timer that would have made us send the enabled update after reconnection (flappy channel protection) cancelTimer(Reconnected.toString) // if we have pending unsigned htlcs, then we cancel them and generate an update with the disabled flag set, that will be returned to the sender in a temporary channel failure - val d1 = if (d.commitments.localChanges.proposed.collectFirst { case add: UpdateAddHtlc => add }.isDefined) { + if (d.commitments.localChanges.proposed.collectFirst { case add: UpdateAddHtlc => add }.isDefined) { log.debug("updating channel_update announcement (reason=disabled)") val channelUpdate1 = Announcements.makeChannelUpdate(nodeParams.chainHash, nodeParams.privateKey, remoteNodeId, d.shortChannelId, d.channelUpdate.cltvExpiryDelta, d.channelUpdate.htlcMinimumMsat, d.channelUpdate.feeBaseMsat, d.channelUpdate.feeProportionalMillionths, d.commitments.capacity.toMilliSatoshi, enable = false) // NB: the htlcs stay() in the commitments.localChange, they will be cleaned up after reconnection d.commitments.localChanges.proposed.collect { case add: UpdateAddHtlc => relayer ! RES_ADD_SETTLED(d.commitments.originChannels(add.id), add, HtlcResult.DisconnectedBeforeSigned(channelUpdate1)) } - d.copy(channelUpdate = channelUpdate1) + goto(OFFLINE) using d.copy(channelUpdate = channelUpdate1) storing() } else { - d + goto(OFFLINE) using d } - goto(OFFLINE) using d1 case Event(e: Error, d: DATA_NORMAL) => handleRemoteError(e, d) @@ -1788,7 +1787,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val // then we update the state and replay the request self forward c // we use goto() to fire transitions - goto(stateName) using d.copy(channelUpdate = channelUpdate1) + goto(stateName) using d.copy(channelUpdate = channelUpdate1) storing() } else { // channel is already disabled, we reply to the request val error = ChannelUnavailable(d.channelId)