mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-23 14:40:34 +01:00
Fix flaky zeroconf test (#2352)
The flaky test was `ZeroConfAliasIntegrationSpec`.`a->b->c (b-c private)`. Due to a race between node watchers, we were sometimes entering into this scenario between bob and carol: 1) carol's watcher wins the race and sends her `channel_ready` to bob before bob has received `WatchFundingConfirmedTriggered` from his own watcher 2) the channel isn't officially zeroconf, but bob gladly accepts carol's early `channel_ready` and moves directly to `WAIT_FOR_CHANNEL_READY` and then `NORMAL`, *without a real scid*. 3) bob ignores the `WatchFundingConfirmedTriggered` that it receives in state `WAIT_FOR_CHANNEL_READY` or `NORMAL` 4) bob later receives the `WatchFundingDeeplyBuriedTriggered`, discovers his own real scid and emits a `ShortChannelIdAssigned` event, but the channel relayer only listens to `LocalChannelUpdate`. 5) subsequent a->b->c payment using a real scid for b-c as routing hint fails because b's relayer doesn't know it. There are two ways of fixing this: a) make the relayer listen to `ShortChannelIdAssigned` (like it did before) b) emit a `LocalChannelUpdate` event containing the new real scid and the same `channel_update`. In this commit, we chose to implement (b). Co-authored-by: t-bast <bastuc@hotmail.fr>
This commit is contained in:
parent
214873e4a7
commit
23de6c4efd
3 changed files with 32 additions and 18 deletions
|
@ -1627,7 +1627,7 @@ class Channel(val nodeParams: NodeParams, val wallet: OnChainChannelFunder, val
|
|||
case (WAIT_FOR_INIT_INTERNAL, OFFLINE, _, d: DATA_NORMAL) => Some(EmitLocalChannelUpdate("restore", d, sendToPeer = false))
|
||||
case (WAIT_FOR_FUNDING_CONFIRMED, NORMAL, _, d: DATA_NORMAL) => Some(EmitLocalChannelUpdate("initial", d, sendToPeer = true))
|
||||
case (WAIT_FOR_CHANNEL_READY, NORMAL, _, d: DATA_NORMAL) => Some(EmitLocalChannelUpdate("initial", d, sendToPeer = true))
|
||||
case (NORMAL, NORMAL, d1: DATA_NORMAL, d2: DATA_NORMAL) if d1.channelUpdate != d2.channelUpdate || d1.channelAnnouncement != d2.channelAnnouncement => Some(EmitLocalChannelUpdate("normal->normal", d2, sendToPeer = d2.channelAnnouncement.isEmpty))
|
||||
case (NORMAL, NORMAL, d1: DATA_NORMAL, d2: DATA_NORMAL) if d1.shortIds.real.toOption != d2.shortIds.real.toOption || d1.channelUpdate != d2.channelUpdate || d1.channelAnnouncement != d2.channelAnnouncement => Some(EmitLocalChannelUpdate("normal->normal", d2, sendToPeer = d2.channelAnnouncement.isEmpty && d1.channelUpdate != d2.channelUpdate))
|
||||
case (SYNCING, NORMAL, d1: DATA_NORMAL, d2: DATA_NORMAL) if d1.channelUpdate != d2.channelUpdate || d1.channelAnnouncement != d2.channelAnnouncement => Some(EmitLocalChannelUpdate("syncing->normal", d2, sendToPeer = d2.channelAnnouncement.isEmpty))
|
||||
case (NORMAL, OFFLINE, d1: DATA_NORMAL, d2: DATA_NORMAL) if d1.channelUpdate != d2.channelUpdate || d1.channelAnnouncement != d2.channelAnnouncement => Some(EmitLocalChannelUpdate("normal->offline", d2, sendToPeer = false))
|
||||
case (OFFLINE, OFFLINE, d1: DATA_NORMAL, d2: DATA_NORMAL) if d1.channelUpdate != d2.channelUpdate || d1.channelAnnouncement != d2.channelAnnouncement => Some(EmitLocalChannelUpdate("offline->offline", d2, sendToPeer = false))
|
||||
|
|
|
@ -356,7 +356,7 @@ trait ChannelOpenSingleFunder extends FundingHandlers with ErrorHandlers {
|
|||
when(WAIT_FOR_FUNDING_CONFIRMED)(handleExceptions {
|
||||
case Event(remoteChannelReady: ChannelReady, d: DATA_WAIT_FOR_FUNDING_CONFIRMED) =>
|
||||
if (remoteChannelReady.alias_opt.isDefined && d.commitments.localParams.isInitiator) {
|
||||
log.info("this chanel isn't zero-conf, but we are funder and they sent an early channel_ready with an alias: no need to wait for confirmations")
|
||||
log.info("this channel isn't zero-conf, but we are funder and they sent an early channel_ready with an alias: no need to wait for confirmations")
|
||||
// No need to emit ShortChannelIdAssigned: we will emit it when handling their channel_ready in WAIT_FOR_CHANNEL_READY
|
||||
val (shortIds, localChannelReady) = acceptFundingTx(d.commitments, RealScidStatus.Unknown)
|
||||
self ! remoteChannelReady
|
||||
|
|
|
@ -28,6 +28,7 @@ import fr.acinq.eclair._
|
|||
import fr.acinq.eclair.blockchain.bitcoind.ZmqWatcher._
|
||||
import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw, FeeratesPerKw}
|
||||
import fr.acinq.eclair.blockchain.{CurrentBlockHeight, CurrentFeerates}
|
||||
import fr.acinq.eclair.channel.RealScidStatus.Final
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.channel.fsm.Channel
|
||||
import fr.acinq.eclair.channel.fsm.Channel._
|
||||
|
@ -3441,8 +3442,8 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
|||
test("recv WatchFundingDeeplyBuriedTriggered (public channel, zero-conf)", Tag(ChannelStateTestsTags.ChannelsPublic), Tag(ChannelStateTestsTags.AnchorOutputsZeroFeeHtlcTxs), Tag(ChannelStateTestsTags.ZeroConf)) { f =>
|
||||
import f._
|
||||
// in zero-conf channel we don't have a real short channel id when going to NORMAL state
|
||||
val aliceIds = alice.stateData.asInstanceOf[DATA_NORMAL].shortIds
|
||||
assert(aliceIds.real == RealScidStatus.Unknown)
|
||||
val aliceState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
assert(aliceState.shortIds.real == RealScidStatus.Unknown)
|
||||
// funding tx coordinates (unknown before)
|
||||
val (blockHeight, txIndex) = (BlockHeight(400000), 42)
|
||||
alice ! WatchFundingDeeplyBuriedTriggered(blockHeight, txIndex, null)
|
||||
|
@ -3452,14 +3453,17 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
|||
// alice updates her internal state
|
||||
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Final(realShortChannelId))
|
||||
alice2bob.expectNoMessage(100 millis)
|
||||
channelUpdateListener.expectNoMessage(100 millis)
|
||||
assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == aliceIds.localAlias)
|
||||
// we emit a new local channel update containing the same channel_update, but also the new real scid
|
||||
val lcu = channelUpdateListener.expectMsgType[LocalChannelUpdate]
|
||||
assert(lcu.shortIds.real == Final(realShortChannelId))
|
||||
assert(lcu.channelUpdate == aliceState.channelUpdate)
|
||||
assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == aliceState.shortIds.localAlias)
|
||||
}
|
||||
|
||||
test("recv WatchFundingDeeplyBuriedTriggered (public channel, short channel id changed)", Tag(ChannelStateTestsTags.ChannelsPublic)) { f =>
|
||||
import f._
|
||||
val aliceIds = alice.stateData.asInstanceOf[DATA_NORMAL].shortIds
|
||||
val realShortChannelId = aliceIds.real.asInstanceOf[RealScidStatus.Temporary].realScid
|
||||
val aliceState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
val realShortChannelId = aliceState.shortIds.real.asInstanceOf[RealScidStatus.Temporary].realScid
|
||||
// existing funding tx coordinates
|
||||
val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId)
|
||||
// new funding tx coordinates (there was a reorg)
|
||||
|
@ -3470,9 +3474,12 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
|||
assert(annSigs.shortChannelId == newRealShortChannelId)
|
||||
// update data with real short channel id
|
||||
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Final(newRealShortChannelId))
|
||||
alice2bob.expectNoMessage(100 millis)
|
||||
// we emit a new local channel update containing the same channel_update, but also the new real scid
|
||||
val lcu = channelUpdateListener.expectMsgType[LocalChannelUpdate]
|
||||
assert(lcu.shortIds.real == Final(newRealShortChannelId))
|
||||
assert(lcu.channelUpdate == aliceState.channelUpdate)
|
||||
channelUpdateListener.expectNoMessage(100 millis)
|
||||
assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == aliceIds.localAlias)
|
||||
assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == aliceState.shortIds.localAlias)
|
||||
}
|
||||
|
||||
test("recv WatchFundingDeeplyBuriedTriggered (private channel)") { f =>
|
||||
|
@ -3496,24 +3503,27 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
|||
val listener = TestProbe()
|
||||
alice.underlying.system.eventStream.subscribe(listener.ref, classOf[TransactionConfirmed])
|
||||
// zero-conf channel: the funding tx isn't confirmed
|
||||
val aliceIds = alice.stateData.asInstanceOf[DATA_NORMAL].shortIds
|
||||
assert(aliceIds.real == RealScidStatus.Unknown)
|
||||
val aliceState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
assert(aliceState.shortIds.real == RealScidStatus.Unknown)
|
||||
alice ! WatchFundingDeeplyBuriedTriggered(BlockHeight(42000), 42, null)
|
||||
val realShortChannelId = RealShortChannelId(BlockHeight(42000), 42, 0)
|
||||
// update data with real short channel id
|
||||
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Final(realShortChannelId))
|
||||
// private channel: we'll use the remote alias in the channel_update we sent to our peer, so there is no change and we don't send a new one
|
||||
alice2bob.expectNoMessage(100 millis)
|
||||
channelUpdateListener.expectNoMessage(100 millis)
|
||||
assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == aliceIds.localAlias)
|
||||
// we emit a new local channel update containing the same channel_update, but also the new real scid
|
||||
val lcu = channelUpdateListener.expectMsgType[LocalChannelUpdate]
|
||||
assert(lcu.shortIds.real == Final(realShortChannelId))
|
||||
assert(lcu.channelUpdate == aliceState.channelUpdate)
|
||||
assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == aliceState.shortIds.localAlias)
|
||||
// this is the first time we know the funding tx has been confirmed
|
||||
listener.expectMsgType[TransactionConfirmed]
|
||||
}
|
||||
|
||||
test("recv WatchFundingDeeplyBuriedTriggered (private channel, short channel id changed)") { f =>
|
||||
import f._
|
||||
val aliceIds = alice.stateData.asInstanceOf[DATA_NORMAL].shortIds
|
||||
val realShortChannelId = aliceIds.real.asInstanceOf[RealScidStatus.Temporary].realScid
|
||||
val aliceState = alice.stateData.asInstanceOf[DATA_NORMAL]
|
||||
val realShortChannelId = aliceState.shortIds.real.asInstanceOf[RealScidStatus.Temporary].realScid
|
||||
// existing funding tx coordinates
|
||||
val TxCoordinates(blockHeight, txIndex, _) = ShortChannelId.coordinates(realShortChannelId)
|
||||
// new funding tx coordinates (there was a reorg)
|
||||
|
@ -3524,8 +3534,12 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
|
|||
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].shortIds.real == RealScidStatus.Final(newRealShortChannelId))
|
||||
// private channel: we'll use the remote alias in the channel_update we sent to our peer, so there is no change and we don't send a new one
|
||||
alice2bob.expectNoMessage(100 millis)
|
||||
channelUpdateListener.expectNoMessage(100 millis)
|
||||
assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == aliceIds.localAlias)
|
||||
// we emit a new local channel update containing the same channel_update, but also the new real scid
|
||||
val lcu = channelUpdateListener.expectMsgType[LocalChannelUpdate]
|
||||
assert(lcu.shortIds.real == Final(newRealShortChannelId))
|
||||
assert(lcu.channelUpdate == aliceState.channelUpdate)
|
||||
assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == aliceState.shortIds.localAlias)
|
||||
assert(alice.stateData.asInstanceOf[DATA_NORMAL].channelUpdate.shortChannelId == aliceState.shortIds.localAlias)
|
||||
}
|
||||
|
||||
test("recv AnnouncementSignatures", Tag(ChannelStateTestsTags.ChannelsPublic)) { f =>
|
||||
|
|
Loading…
Add table
Reference in a new issue